[
  {
    "path": ".coveragerc",
    "content": "[paths]\nsource =\n    src\n    */site-packages\n\n[report]\nomit =\n    */python?.?/*\n    */site-packages/nose/*\n    *__init__*\nexclude_lines =\n    pragma: no cover\n    if self._testing:\n    if not self._testing:\n    raise NotImplementedError\n    raise AssertionError\n\n[run]\nbranch = false\nparallel = true\nsource =\n    instruments\nrelative_files = True\n"
  },
  {
    "path": ".github/workflows/deploy.yml",
    "content": "name: Upload Python Package\n\non:\n  release:\n    types: [published]\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n        with:\n          fetch-depth: 0\n      - name: Set up Python\n        uses: actions/setup-python@v2\n        with:\n          python-version: '3.x'\n      - name: Install dependencies\n        run: |\n          python -m pip install --upgrade pip virtualenv\n          pip install build\n      - name: Build package\n        run: python -m build\n      - name: Publish package\n        uses: pypa/gh-action-pypi-publish@release/v1\n        with:\n          user: __token__\n          password: ${{ secrets.PYPI_API_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Testing\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n\njobs:\n  static-checks:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v4\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v5\n      with:\n        python-version: ${{ matrix.python-version }}\n    - name: Install dependencies\n      run: pip install --upgrade pre-commit\n    - name: Run static checks via pre-commit\n      run: SKIP=no-commit-to-branch pre-commit run --all --show-diff-on-failure\n  test:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        include:\n          - python-version: 3.9\n            TOXENV: \"py39\"\n          - python-version: 3.9\n            TOXENV: \"py39-numpy\"\n          - python-version: \"3.10\"\n            TOXENV: \"py310\"\n          - python-version: \"3.11\"\n            TOXENV: \"py311\"\n          - python-version: \"3.12\"\n            TOXENV: \"py312\"\n          - python-version: \"3.13\"\n            TOXENV: \"py313\"\n    steps:\n    - uses: actions/checkout@v4\n      with:\n        fetch-depth: 0\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v5\n      with:\n        python-version: ${{ matrix.python-version }}\n    - name: Install dependencies\n      run: pip install --upgrade pip setuptools wheel virtualenv tox\n    - name: Test with tox\n      env:\n        TOXENV: ${{ matrix.TOXENV }}\n      run: tox\n    - name: Upload coverage to Codecov\n      if: github.repository_owner == 'instrumentkit'\n      uses: codecov/codecov-action@v4\n      env:\n        TOXENV: ${{ matrix.TOXENV }}\n        CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}\n      with:\n        env_vars: TOXENV\n        fail_ci_if_error: true\n        flags: unittests\n        name: codecov-umbrella\n        verbose: true\n        files: ./coverage.xml\n"
  },
  {
    "path": ".gitignore",
    "content": "## Temporary files ##\n*~\n*.tmp\n\n## OS generated files ##\n.DS_Store*\ndesktop.ini\nThumbs.db\n\n## Build directories ##\ndoc/_build\n\n## Venv\n.venv/\n.python-version\n\n## setup.py generated files ##\nMANIFEST\n\n# The following ignores are drawn from github@github:gitignore/Python.gitignore.\n*.py[cod]\n\n# C extensions\n*.so\n\n# Packages\n.egg/\n*.egg\n*.egg-info\ndist\nbuild\neggs\nparts\nbin\nvar\nsdist\ndevelop-eggs\n.installed.cfg\nlib\nlib64\n\n# Installer logs\npip-log.txt\n\n# Unit test / coverage reports\n.coverage\n.tox\nnosetests.xml\n.pytest_cache\ncoverage.xml\n\n#Translations\n*.mo\n\n#Mr Developer\n.mr.developer.cfg\n\n#pycharm generated\n.idea\n\n# VS Code IDE internals\n.vscode/\n\n# nosetests metadata\n.noseids\n\n# Hypothesis files\n.hypothesis/\n\n# version file generated by setuptools_scm\nsrc/instruments/_version.py\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "repos:\n  - repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v6.0.0\n    hooks:\n      - id: no-commit-to-branch\n        args: [--branch, main]\n      - id: trailing-whitespace\n      - id: end-of-file-fixer\n      - id: check-yaml\n      - id: debug-statements\n  - repo: https://github.com/psf/black-pre-commit-mirror\n    rev: 26.3.1\n    hooks:\n      - id: black\n  - repo: https://github.com/asottile/pyupgrade\n    rev: v3.21.2\n    hooks:\n      - id: pyupgrade\n        args: [ --py39-plus ]\n"
  },
  {
    "path": ".readthedocs.yml",
    "content": "version: 2\nbuild:\n  os: ubuntu-24.04\n  tools:\n    python: \"3.13\"\n\nsphinx:\n  builder: html\n  configuration: doc/source/conf.py\n  fail_on_warning: false\n\npython:\n  install:\n    - method: pip\n      path: .\n      extra_requirements:\n        - docs\n"
  },
  {
    "path": "README.rst",
    "content": "InstrumentKit\n=============\n\n.. image:: https://github.com/instrumentkit/InstrumentKit/workflows/Testing/badge.svg?branch=main\n    :target: https://github.com/instrumentkit/InstrumentKit\n    :alt: Github Actions build status\n\n.. image:: https://codecov.io/gh/instrumentkit/InstrumentKit/branch/main/graph/badge.svg?token=Q2wcdW3t4A\n    :target: https://codecov.io/gh/instrumentkit/InstrumentKit\n    :alt: Codecov code coverage\n\n.. image:: https://readthedocs.org/projects/instrumentkit/badge/?version=latest\n    :target: https://readthedocs.org/projects/instrumentkit/?badge=latest\n    :alt: Documentation\n\n.. image:: https://img.shields.io/pypi/v/instrumentkit.svg?maxAge=86400\n    :target: https://pypi.python.org/pypi/instrumentkit\n    :alt: PyPI version\n\n.. image:: https://img.shields.io/pypi/pyversions/instrumentkit.svg?maxAge=2592000\n    :alt: Python versions\n\nInstrumentKit is an open source Python library designed to help the\nend-user get straight into communicating with their equipment via a PC.\nInstrumentKit aims to accomplish this by providing a connection- and\nvendor-agnostic API. Users can freely swap between a variety of\nconnection types (ethernet, gpib, serial, usb) without impacting their\ncode. Since the API is consistent across similar instruments, a user\ncan, for example, upgrade from their 1980's multimeter using GPIB to a\nmodern Keysight 34461a using ethernet with only a single line change.\n\nSupported means of communication are:\n\n- Galvant Industries GPIBUSB adapter (``open_gpibusb``)\n- Serial (``open_serial``)\n- Sockets (``open_tcpip``)\n- VISA (``open_visa``)\n- Read/write from unix files (``open_file``)\n- USBTMC (``open_usbtmc``)\n- VXI11 over Ethernet (``open_vxi11``)\n- Raw USB (``open_usb``)\n\nThere is planned support for HiSLIP someday, but a good Python HiSLIP library will be needed first.\n\nIf you have any problems or have code you wish to contribute back to the\nproject please feel free to open an issue or a pull request!\n\nInstallation\n------------\n\nThe ``instruments`` package can be installed from this repository by the\nfollowing means:\n\nFrom Git:\n\n.. code-block:: console\n\n    $ git clone git@github.com:instrumentkit/InstrumentKit.git\n    $ cd InstrumentKit\n    $ pip install -e .\n\nFrom Github using pip:\n\n.. code-block:: console\n\n    $ pip install -e git+https://www.github.com/instrumentkit/InstrumentKit.git\n\nFrom pypi using pip (the latest stable release):\n\n.. code-block:: console\n\n    $ pip install instrumentkit\n\nFrom pypi using pip (the latest pre-release):\n\n.. code-block:: console\n\n    $ pip install instrumentkit --pre\n\n\nUsage Example\n-------------\n\nTo open a connection to a generic SCPI-compatible multimeter using a Galvant\nIndustries' GPIBUSB adapter:\n\n.. code-block:: python\n\n    >>> import instruments as ik\n    >>> inst = ik.generic_scpi.SCPIMultimeter.open_gpibusb(\"/dev/ttyUSB0\", 1)\n\nFrom there, various built-in properties and functions can be called. For\nexample, the instrument's identification information can be retrieved by\ncalling the name property:\n\n.. code-block:: python\n\n    >>> print(inst.name)\n\nOr, since in the demo we connected to an ``SCPIMultimeter``, we can preform\nmultimeter-specific tasks, such as switching functions, and taking a\nmeasurement reading:\n\n.. code-block:: python\n\n    >>> reading = inst.measure(inst.Mode.voltage_dc)\n    >>> print(f\"Value: {reading.magnitude}, units: {reading.units}\")\n\nDue to the sheer number of commands most instruments support, not every single\none is included in InstrumentKit. If there is a specific command you wish to\nsend, one can use the following functions to do so:\n\n.. code-block:: python\n\n    >>> inst.sendcmd(\"DATA\") # Send command with no response\n    >>> resp = inst.query(\"*IDN?\") # Send command and retrieve response\n\nPython Version Compatibility\n----------------------------\n\nAt this time, Python  3.9, 3.10, 3.11, 3.12, and 3.13 are supported. Should you encounter\nany problems with this library that occur in one version or another, please\ndo not hesitate to let us know.\n\nDocumentation\n-------------\n\nYou can find the project documentation at our ReadTheDocs pages located at\nhttp://instrumentkit.readthedocs.org/en/latest/index.html\n\nContributing\n------------\n\nThe InstrumentKit team always welcome additional contributions to the project.\nHowever, we ask that you please review our contributing developer guidelines\nwhich can be found in the documentation. We also suggest that you look at\nexisting classes which are similar to your work to learn more about the\nstructure of this project.\n\nTo run the tests against all supported version of Python, you will need to\nhave the binary for each installed. The easiest way to accomplish this is\nto use the tool `pyenv <https://github.com/pyenv/pyenv>`_.\n\nWith the required system packages installed, all tests can be run with ``tox``:\n\n.. code-block:: console\n\n    $ pip install tox\n    $ tox\n\nPre-commit\n----------\n\nA variety of static code checks are managed and executed via the tool\n`pre-commit <https://pre-commit.com/>`_. This only needs to be setup once\nand then it'll manage everything for you.\n\n.. code-block:: console\n\n    $ pip install pre-commit\n    $ pre-commit install\n\nAfterwards, when you go to make a git commit, all the plugins (as specified\nby the configuration file ``.pre-commit-config.yaml``) will be executed against\nthe files that have changed. If any plugins make changes to the files, the\ncommit will abort, allowing you to add those changes to your changeset and\ntry to commit again. This tool will gate CI, so be sure to let them run\nand pass!\n\nYou can also run all the hooks against all the files by directly calling\npre-commit, or though the ``tox`` environment:\n\n.. code-block:: console\n\n    $ pre-commit run --all\n\nor\n\n.. code-block:: console\n\n    $ tox -e precommit\n\nSee the ``pre-commit`` documentation for more information.\n\nLicense\n-------\n\nAll code in this repository is released under the AGPL-v3 license. Please see\nthe ``license`` folder for more information.\n"
  },
  {
    "path": "doc/Makefile",
    "content": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nPAPER         =\nBUILDDIR      = build\n\n# Internal variables.\nPAPEROPT_a4     = -D latex_paper_size=a4\nPAPEROPT_letter = -D latex_paper_size=letter\nALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source\n# the i18n builder cannot share the environment and doctrees with the others\nI18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source\n\n.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext\n\nhelp:\n\t@echo \"Please use \\`make <target>' where <target> is one of\"\n\t@echo \"  html       to make standalone HTML files\"\n\t@echo \"  dirhtml    to make HTML files named index.html in directories\"\n\t@echo \"  singlehtml to make a single large HTML file\"\n\t@echo \"  pickle     to make pickle files\"\n\t@echo \"  json       to make JSON files\"\n\t@echo \"  htmlhelp   to make HTML files and a HTML help project\"\n\t@echo \"  qthelp     to make HTML files and a qthelp project\"\n\t@echo \"  devhelp    to make HTML files and a Devhelp project\"\n\t@echo \"  epub       to make an epub\"\n\t@echo \"  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\"\n\t@echo \"  latexpdf   to make LaTeX files and run them through pdflatex\"\n\t@echo \"  text       to make text files\"\n\t@echo \"  man        to make manual pages\"\n\t@echo \"  texinfo    to make Texinfo files\"\n\t@echo \"  info       to make Texinfo files and run them through makeinfo\"\n\t@echo \"  gettext    to make PO message catalogs\"\n\t@echo \"  changes    to make an overview of all changed/added/deprecated items\"\n\t@echo \"  linkcheck  to check all external links for integrity\"\n\t@echo \"  doctest    to run all doctests embedded in the documentation (if enabled)\"\n\nclean:\n\t-rm -rf $(BUILDDIR)/*\n\nhtml:\n\t$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/html.\"\n\ndirhtml:\n\t$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/dirhtml.\"\n\nsinglehtml:\n\t$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml\n\t@echo\n\t@echo \"Build finished. The HTML page is in $(BUILDDIR)/singlehtml.\"\n\npickle:\n\t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle\n\t@echo\n\t@echo \"Build finished; now you can process the pickle files.\"\n\njson:\n\t$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json\n\t@echo\n\t@echo \"Build finished; now you can process the JSON files.\"\n\nhtmlhelp:\n\t$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp\n\t@echo\n\t@echo \"Build finished; now you can run HTML Help Workshop with the\" \\\n\t      \".hhp project file in $(BUILDDIR)/htmlhelp.\"\n\nqthelp:\n\t$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp\n\t@echo\n\t@echo \"Build finished; now you can run \"qcollectiongenerator\" with the\" \\\n\t      \".qhcp project file in $(BUILDDIR)/qthelp, like this:\"\n\t@echo \"# qcollectiongenerator $(BUILDDIR)/qthelp/GPIBUSBAdapterDriverLibrary.qhcp\"\n\t@echo \"To view the help file:\"\n\t@echo \"# assistant -collectionFile $(BUILDDIR)/qthelp/GPIBUSBAdapterDriverLibrary.qhc\"\n\ndevhelp:\n\t$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp\n\t@echo\n\t@echo \"Build finished.\"\n\t@echo \"To view the help file:\"\n\t@echo \"# mkdir -p $$HOME/.local/share/devhelp/GPIBUSBAdapterDriverLibrary\"\n\t@echo \"# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/GPIBUSBAdapterDriverLibrary\"\n\t@echo \"# devhelp\"\n\nepub:\n\t$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub\n\t@echo\n\t@echo \"Build finished. The epub file is in $(BUILDDIR)/epub.\"\n\nlatex:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo\n\t@echo \"Build finished; the LaTeX files are in $(BUILDDIR)/latex.\"\n\t@echo \"Run \\`make' in that directory to run these through (pdf)latex\" \\\n\t      \"(use \\`make latexpdf' here to do that automatically).\"\n\nlatexpdf:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through pdflatex...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\ntext:\n\t$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text\n\t@echo\n\t@echo \"Build finished. The text files are in $(BUILDDIR)/text.\"\n\nman:\n\t$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man\n\t@echo\n\t@echo \"Build finished. The manual pages are in $(BUILDDIR)/man.\"\n\ntexinfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo\n\t@echo \"Build finished. The Texinfo files are in $(BUILDDIR)/texinfo.\"\n\t@echo \"Run \\`make' in that directory to run these through makeinfo\" \\\n\t      \"(use \\`make info' here to do that automatically).\"\n\ninfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo \"Running Texinfo files through makeinfo...\"\n\tmake -C $(BUILDDIR)/texinfo info\n\t@echo \"makeinfo finished; the Info files are in $(BUILDDIR)/texinfo.\"\n\ngettext:\n\t$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale\n\t@echo\n\t@echo \"Build finished. The message catalogs are in $(BUILDDIR)/locale.\"\n\nchanges:\n\t$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes\n\t@echo\n\t@echo \"The overview file is in $(BUILDDIR)/changes.\"\n\nlinkcheck:\n\t$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck\n\t@echo\n\t@echo \"Link check complete; look for any errors in the above output \" \\\n\t      \"or in $(BUILDDIR)/linkcheck/output.txt.\"\n\ndoctest:\n\t$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest\n\t@echo \"Testing of doctests in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/doctest/output.txt.\"\n"
  },
  {
    "path": "doc/examples/.ipynb_checkpoints/ex_keithley6514-checkpoint.ipynb",
    "content": "{\n \"metadata\": {\n  \"name\": \"\"\n },\n \"nbformat\": 3,\n \"nbformat_minor\": 0,\n \"worksheets\": [\n  {\n   \"cells\": [\n    {\n     \"cell_type\": \"heading\",\n     \"level\": 1,\n     \"metadata\": {},\n     \"source\": [\n      \"InstrumentKit Example Library\"\n     ]\n    },\n    {\n     \"cell_type\": \"heading\",\n     \"level\": 2,\n     \"metadata\": {},\n     \"source\": [\n      \"Keithley 6514 Electrometer\"\n     ]\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"%pylab inline\\n\",\n      \"import instruments as ik\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [\n      {\n       \"output_type\": \"stream\",\n       \"stream\": \"stdout\",\n       \"text\": [\n        \"Populating the interactive namespace from numpy and matplotlib\\n\"\n       ]\n      }\n     ],\n     \"prompt_number\": 1\n    },\n    {\n     \"cell_type\": \"markdown\",\n     \"metadata\": {},\n     \"source\": [\n      \"Here, our device is connected via a gpibusb adaptor, on COM5, address 14.\"\n     ]\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"elec=ik.keithley.Keithley6514.open_gpibusb('COM5', 14)\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [],\n     \"prompt_number\": 2\n    },\n    {\n     \"cell_type\": \"markdown\",\n     \"metadata\": {},\n     \"source\": [\n      \"Now we query a few standard bits of information:\"\n     ]\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"print elec.query('*IDN?')\\n\",\n      \"print \\\"Mode: {}\\\".format(elec.mode.value)\\n\",\n      \"print \\\"Unit: {}\\\".format(elec.unit)\\n\",\n      \"print \\\"Upper Range Limit: {}\\\".format(elec.input_range)\\n\",\n      \"print \\\"Zero Check: {}\\\".format(elec.zero_check)\\n\",\n      \"print \\\"Zero Correct: {}\\\".format(elec.zero_correct)\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [\n      {\n       \"output_type\": \"stream\",\n       \"stream\": \"stdout\",\n       \"text\": [\n        \"KEITHLEY INSTRUMENTS INC.,MODEL 6514,1344784,A12   Aug 29 2008 15:40:25/A02  /D\\n\",\n        \"Mode: CURR:DC\\n\",\n        \"Unit: 1 A (ampere)\"\n       ]\n      },\n      {\n       \"output_type\": \"stream\",\n       \"stream\": \"stdout\",\n       \"text\": [\n        \"\\n\",\n        \"Upper Range Limit: 2.1e-10 A\"\n       ]\n      },\n      {\n       \"output_type\": \"stream\",\n       \"stream\": \"stdout\",\n       \"text\": [\n        \"\\n\",\n        \"Zero Check: False\\n\",\n        \"Zero Correct: False\"\n       ]\n      },\n      {\n       \"output_type\": \"stream\",\n       \"stream\": \"stdout\",\n       \"text\": [\n        \"\\n\"\n       ]\n      }\n     ],\n     \"prompt_number\": 3\n    },\n    {\n     \"cell_type\": \"markdown\",\n     \"metadata\": {},\n     \"source\": [\n      \"Set up the zero checking/correcting:\"\n     ]\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"elec.zero_check = False\\n\",\n      \"elec.zero_correct = True\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [],\n     \"prompt_number\": 6\n    },\n    {\n     \"cell_type\": \"markdown\",\n     \"metadata\": {},\n     \"source\": [\n      \"Take a single reading:\"\n     ]\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"reading, timestamp = elec.read()\\n\",\n      \"print \\\"Current Reading: {}\\\".format(reading)\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [\n      {\n       \"output_type\": \"stream\",\n       \"stream\": \"stdout\",\n       \"text\": [\n        \"Current Reading: 2.485788e-11 A\\n\"\n       ]\n      }\n     ],\n     \"prompt_number\": 7\n    }\n   ],\n   \"metadata\": {}\n  }\n ]\n}\n"
  },
  {
    "path": "doc/examples/ex_generic_scpi.ipynb",
    "content": "{\n \"metadata\": {\n  \"name\": \"ex_generic_scpi\"\n },\n \"nbformat\": 3,\n \"nbformat_minor\": 0,\n \"worksheets\": [\n  {\n   \"cells\": [\n    {\n     \"cell_type\": \"heading\",\n     \"level\": 1,\n     \"metadata\": {},\n     \"source\": [\n      \"InstrumentKit Library Examples\"\n     ]\n    },\n    {\n     \"cell_type\": \"heading\",\n     \"level\": 2,\n     \"metadata\": {},\n     \"source\": [\n      \"Generic SCPI Instrument\"\n     ]\n    },\n    {\n     \"cell_type\": \"markdown\",\n     \"metadata\": {},\n     \"source\": [\n      \"In this example, we will demonstrate how to connect to a generic SCPI \\n\",\n      \"instrument and query its identification information.\"\n     ]\n    },\n    {\n     \"cell_type\": \"markdown\",\n     \"metadata\": {},\n     \"source\": [\n      \"We start by importing the InstrumentKit package.\"\n     ]\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"import instruments as ik\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": []\n    },\n    {\n     \"cell_type\": \"markdown\",\n     \"metadata\": {},\n     \"source\": [\n      \"Next, we open our connection to the instrument. Here we use the generic \\n\",\n      \"SCPIInstrument class and open the connection using the Galvant Industries'\\n\",\n      \"GPIBUSB adapter. Our connection is made to the virtual serial port located at\\n\",\n      \"/dev/ttyUSB0 and GPIB address 1\"\n     ]\n    },\n    {\n     \"cell_type\": \"markdown\",\n     \"metadata\": {},\n     \"source\": [\n      \"The connection method used will have to be changed to match your setup.\"\n     ]\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": true,\n     \"input\": [\n      \"inst = ik.generic_scpi.SCPIInstrument.open_gpibusb('/dev/ttyUSB0', 1)\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": []\n    },\n    {\n     \"cell_type\": \"markdown\",\n     \"metadata\": {},\n     \"source\": [\n      \"Now that we are connected to the instrument, we can query its identification\\n\",\n      \"information. We will do this by using the SCPIInstrument property 'name'.\"\n     ]\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": true,\n     \"input\": [\n      \"print inst.name\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": []\n    }\n   ],\n   \"metadata\": {}\n  }\n ]\n}\n"
  },
  {
    "path": "doc/examples/ex_generic_scpi.py",
    "content": "# <nbformat>3.0</nbformat>\n\n# <headingcell level=1>\n\n# InstrumentKit Library Examples\n\n# <headingcell level=2>\n\n# Generic SCPI Instrument\n\n# <markdowncell>\n\n# In this example, we will demonstrate how to connect to a generic SCPI\n# instrument and query its identification information.\n\n# <markdowncell>\n\n# We start by importing the InstrumentKit package.\n\n# <codecell>\n\nimport instruments as ik\n\n# <markdowncell>\n\n# Next, we open our connection to the instrument. Here we use the generic\n# SCPIInstrument class and open the connection using the Galvant Industries'\n# GPIBUSB adapter. Our connection is made to the virtual serial port located at\n# /dev/ttyUSB0 and GPIB address 1\n\n# <markdowncell>\n\n# The connection method used will have to be changed to match your setup.\n\n# <codecell>\n\ninst = ik.generic_scpi.SCPIInstrument.open_gpibusb(\"/dev/ttyUSB0\", 1)\n\n# <markdowncell>\n\n# Now that we are connected to the instrument, we can query its identification\n# information. We will do this by using the SCPIInstrument property 'name'.\n\n# <codecell>\n\nprint(inst.name)\n"
  },
  {
    "path": "doc/examples/ex_hp3325.py",
    "content": "import logging\n\nimport instruments as ik\n\nfcngen = ik.hp.HP3325a.open_gpibusb(\"COM4\", 17, model=\"pl\")\nlogging.basicConfig(level=logging.DEBUG)\nfcngen._file.debug = True\n\nfcngen.amplitude = 2.0  # V\nfcngen.frequency = 512.53  # Hz\nfcngen.function = fcngen.Function.square\n\nprint(f\"Actual voltage={fcngen.amplitude} V\")\nprint(f\"Actual frequency={fcngen.frequency} Hz\")\n"
  },
  {
    "path": "doc/examples/ex_hp3456.out",
    "content": "DEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'T4'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'W6STG'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'R1W'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'W10STN'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'W1STI'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'S0F4'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'M2'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'T3'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '+000.1055E+0,+000.1043E+0,+000.1005E+0,+000.1014E+0,+000.0993E+0,+000.1037E+0,+000.0995E+0,+000.1002E+0,+000.1041E+0,+000.1025E+0'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'REV'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '+04.93111E-6'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'REC'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '+10.00000E+0'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'REM'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '+102.1000E-3'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'W10STI'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'REN'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '+10.00000E+0'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'REG'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '+06.00000E+0'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'REI'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '+10.00000E+0'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'RED'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '-000.0000E+0'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'REM'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '+102.1000E-3'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'REV'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '+04.93111E-6'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'REC'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '+10.00000E+0'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'REL'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '+099.3000E-3'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'REU'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '+105.5000E-3'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'RER'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '+0600.000E+0'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'REY'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '+1.000000E+0'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'REZ'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '+105.5000E-3'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'W100STI'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'REC'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '+10.00000E+0'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'S1F1W1STNT3'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '+00.00000E-3'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'S0F4W1STNT3'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '+000.1010E+0'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'W1STI'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'R2W'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'S0F1W1STNT3'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '+000.0031E-3'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'R3W'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'S0F1W1STNT3'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '+0.000003E+0'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'R4W'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'S0F1W1STNT3'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '+00.00002E+0'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'R5W'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'S0F1W1STNT3'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '+000.0003E+0'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'R6W'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'S0F1W1STNT3'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '+0000.002E+0'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'M0'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'S0F4W1STNT3'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '+0000.003E+3'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'M3'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'S0F4W1STNT3'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '+0000.000E+3'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'M0'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'S0F4W1STNT3'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '-0000.002E+3'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'Z0'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'FL0'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'R1W'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'S0F1W1STNT3'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '-000.0017E-3'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'Z1'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'S0F1W1STNT3'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '+000.0014E-3'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'FL1'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'S0F1W1STNT3'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '+000.0007E-3'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'Z0'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- 'S0F1W1STNT3'\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: <- ''\nDEBUG:instruments.abstract_instruments.comm.gi_gpib: -> '+000.0002E-3'\n10\n4.93111e-06\n10\n0.1021\nn = 10.0\ng = 6.0\np = 10.0\nd = -0.0 s\n0.1021\n4.93111e-06\n10\n0.0993\n0.1055\n600.0\n1.0\n0.1055\n10\n0.0\n0.101 ohm\n3.1e-06 V\n3e-06 V\n2e-05 V\n0.0003 V\n0.002 V\n3.0 ohm\n0.0 ohm\n-2.0 ohm\n-1.7e-06 V\n1.4e-06 V\n7e-07 V\n2e-07 V\n"
  },
  {
    "path": "doc/examples/ex_hp3456.py",
    "content": "#!/usr/bin/python\n\nimport logging\nimport time\nimport instruments as ik\nimport instruments.units as u\n\ndmm = ik.hp.HP3456a.open_gpibusb(\"/dev/ttyUSB0\", 22)\nlogging.basicConfig(level=logging.DEBUG)\ndmm._file.debug = True\ndmm.trigger_mode = dmm.TriggerMode.hold\ndmm.number_of_digits = 6\ndmm.auto_range()\n\nn = 10\ndmm.number_of_readings = n\ndmm.nplc = 1\ndmm.mode = dmm.Mode.resistance_2wire\ndmm.math_mode = dmm.MathMode.statistic\ndmm.trigger()\ntime.sleep(n * 0.04)\nv = dmm.fetch(dmm.Mode.resistance_2wire)\nprint(len(v))\nprint(dmm.variance)\nprint(dmm.count)\nprint(dmm.mean)\n\n# Read registers\ndmm.nplc = 10\nprint(f\"n = {dmm.number_of_readings}\")\nprint(f\"g = {dmm.number_of_digits}\")\nprint(f\"p = {dmm.nplc}\")\nprint(f\"d = {dmm.delay}\")\nprint(dmm.mean)\nprint(dmm.variance)\nprint(dmm.count)\nprint(dmm.lower)\nprint(dmm.upper)\nprint(dmm.r)\nprint(dmm.y)\nprint(dmm.z)\n\n# Walk through input range\ndmm.nplc = 100\nprint(dmm.count)\nprint(dmm.measure(dmm.Mode.ratio_dcv_dcv))\nprint(dmm.measure(dmm.Mode.resistance_2wire))\ndmm.nplc = 1\nfor i in range(-1, 4):\n    value = (10**i) * u.volt\n    dmm.input_range = value\n    print(dmm.measure(dmm.Mode.dcv))\n\n# Walk through relative / null mode\ndmm.relative = False\nprint(dmm.measure(dmm.Mode.resistance_2wire))\ndmm.relative = True\nprint(dmm.measure(dmm.Mode.resistance_2wire))\ndmm.relative = False\nprint(dmm.measure(dmm.Mode.resistance_2wire))\n\n# Measure with autozero off\ndmm.autozero = 0\ndmm.filter = 0\ndmm.auto_range()\n\nprint(dmm.measure(dmm.Mode.dcv))\ndmm.autozero = 1\nprint(dmm.measure(dmm.Mode.dcv))\ndmm.filter = 1\nprint(dmm.measure(dmm.Mode.dcv))\ndmm.autozero = 0\nprint(dmm.measure(dmm.Mode.dcv))\n"
  },
  {
    "path": "doc/examples/ex_keithley195.ipynb",
    "content": "{\n \"metadata\": {\n  \"name\": \"ex_keithley195\"\n },\n \"nbformat\": 3,\n \"nbformat_minor\": 0,\n \"worksheets\": [\n  {\n   \"cells\": [\n    {\n     \"cell_type\": \"heading\",\n     \"level\": 1,\n     \"metadata\": {},\n     \"source\": [\n      \"InstrumentKit Library Examples\"\n     ]\n    },\n    {\n     \"cell_type\": \"heading\",\n     \"level\": 2,\n     \"metadata\": {},\n     \"source\": [\n      \"Keithley 195 Multimeter\"\n     ]\n    },\n    {\n     \"cell_type\": \"markdown\",\n     \"metadata\": {},\n     \"source\": [\n      \"In this example, we will demonstrate how to connect to a Keithley 195\\n\",\n      \"multimeter and transfer a single reading to the PC.\"\n     ]\n    },\n    {\n     \"cell_type\": \"markdown\",\n     \"metadata\": {},\n     \"source\": [\n      \"We start by importing the InstrumentKit package.\"\n     ]\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"import instruments as ik\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": []\n    },\n    {\n     \"cell_type\": \"markdown\",\n     \"metadata\": {},\n     \"source\": [\n      \"Next, we open our connection to the instrument. Here we use the \\n\",\n      \"Keithley195 class and open the connection using Galvant Industries'\\n\",\n      \"GPIBUSB adapter. Our connection is made to the virtual serial port located at\\n\",\n      \"/dev/ttyUSB0 and GPIB address 16.\"\n     ]\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"dmm = ik.keithley.Keithley195.open_gpibusb('/dev/ttyUSB0', 1)\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": []\n    },\n    {\n     \"cell_type\": \"markdown\",\n     \"metadata\": {},\n     \"source\": [\n      \"Now, we retreive the measurement currently displayed on the front panel.\"\n     ]\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"print dmm.measure()\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": []\n    }\n   ],\n   \"metadata\": {}\n  }\n ]\n}\n"
  },
  {
    "path": "doc/examples/ex_keithley195.py",
    "content": "# <nbformat>3.0</nbformat>\n\n# <headingcell level=1>\n\n# InstrumentKit Library Examples\n\n# <headingcell level=2>\n\n# Keithley 195 Multimeter\n\n# <markdowncell>\n\n# In this example, we will demonstrate how to connect to a Keithley 195\n# multimeter and transfer a single reading to the PC.\n\n# <markdowncell>\n\n# We start by importing the InstrumentKit package.\n\n# <codecell>\n\nimport instruments as ik\n\n# <markdowncell>\n\n# Next, we open our connection to the instrument. Here we use the\n# Keithley195 class and open the connection using Galvant Industries'\n# GPIBUSB adapter. Our connection is made to the virtual serial port located at\n# /dev/ttyUSB0 and GPIB address 16.\n\n# <codecell>\n\ndmm = ik.keithley.Keithley195.open_gpibusb(\"/dev/ttyUSB0\", 1)\n\n# <markdowncell>\n\n# Now, we retreive the measurement currently displayed on the front panel.\n\n# <codecell>\n\nprint(dmm.measure())\n"
  },
  {
    "path": "doc/examples/ex_keithley6514.ipynb",
    "content": "{\n \"metadata\": {\n  \"name\": \"\"\n },\n \"nbformat\": 3,\n \"nbformat_minor\": 0,\n \"worksheets\": [\n  {\n   \"cells\": [\n    {\n     \"cell_type\": \"heading\",\n     \"level\": 1,\n     \"metadata\": {},\n     \"source\": [\n      \"InstrumentKit Example Library\"\n     ]\n    },\n    {\n     \"cell_type\": \"heading\",\n     \"level\": 2,\n     \"metadata\": {},\n     \"source\": [\n      \"Keithley 6514 Electrometer\"\n     ]\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"%pylab inline\\n\",\n      \"import instruments as ik\\n\",\n      \"import time, datetime\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [\n      {\n       \"output_type\": \"stream\",\n       \"stream\": \"stdout\",\n       \"text\": [\n        \"Populating the interactive namespace from numpy and matplotlib\\n\"\n       ]\n      }\n     ],\n     \"prompt_number\": 1\n    },\n    {\n     \"cell_type\": \"markdown\",\n     \"metadata\": {},\n     \"source\": [\n      \"Here, our device is connected via a gpibusb adaptor, on COM5, address 14.\"\n     ]\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"elec=ik.keithley.Keithley6514.open_gpibusb('COM5', 14)\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [],\n     \"prompt_number\": 2\n    },\n    {\n     \"cell_type\": \"markdown\",\n     \"metadata\": {},\n     \"source\": [\n      \"Now we query a few standard bits of information:\"\n     ]\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"print elec.query('*IDN?')\\n\",\n      \"print \\\"Mode: {}\\\".format(elec.mode.value)\\n\",\n      \"print \\\"Unit: {}\\\".format(elec.unit)\\n\",\n      \"print \\\"Upper Range Limit: {}\\\".format(elec.input_range)\\n\",\n      \"print \\\"Zero Check: {}\\\".format(elec.zero_check)\\n\",\n      \"print \\\"Zero Correct: {}\\\".format(elec.zero_correct)\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [\n      {\n       \"output_type\": \"stream\",\n       \"stream\": \"stdout\",\n       \"text\": [\n        \"KEITHLEY INSTRUMENTS INC.,MODEL 6514,1344784,A12   Aug 29 2008 15:40:25/A02  /D\\n\",\n        \"Mode: RES\\n\",\n        \"Unit: 1 ohm (ohm)\"\n       ]\n      },\n      {\n       \"output_type\": \"stream\",\n       \"stream\": \"stdout\",\n       \"text\": [\n        \"\\n\",\n        \"Upper Range Limit: 2100000.0 ohm\"\n       ]\n      },\n      {\n       \"output_type\": \"stream\",\n       \"stream\": \"stdout\",\n       \"text\": [\n        \"\\n\",\n        \"Zero Check: False\\n\",\n        \"Zero Correct: False\"\n       ]\n      },\n      {\n       \"output_type\": \"stream\",\n       \"stream\": \"stdout\",\n       \"text\": [\n        \"\\n\"\n       ]\n      }\n     ],\n     \"prompt_number\": 3\n    },\n    {\n     \"cell_type\": \"markdown\",\n     \"metadata\": {},\n     \"source\": [\n      \"Use the auto config function to set up a resistance measurement.\"\n     ]\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"elec.auto_config(elec.Mode.resistance)\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [],\n     \"prompt_number\": 4\n    },\n    {\n     \"cell_type\": \"markdown\",\n     \"metadata\": {},\n     \"source\": [\n      \"Check the status of things again:\"\n     ]\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"print elec.query('*IDN?')\\n\",\n      \"print \\\"Mode: {}\\\".format(elec.mode.value)\\n\",\n      \"print \\\"Unit: {}\\\".format(elec.unit)\\n\",\n      \"print \\\"Upper Range Limit: {}\\\".format(elec.input_range)\\n\",\n      \"print \\\"Zero Check: {}\\\".format(elec.zero_check)\\n\",\n      \"print \\\"Zero Correct: {}\\\".format(elec.zero_correct)\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [\n      {\n       \"output_type\": \"stream\",\n       \"stream\": \"stdout\",\n       \"text\": [\n        \"KEITHLEY INSTRUMENTS INC.,MODEL 6514,1344784,A12   Aug 29 2008 15:40:25/A02  /D\\n\",\n        \"Mode: RES\\n\",\n        \"Unit: 1 ohm (ohm)\"\n       ]\n      },\n      {\n       \"output_type\": \"stream\",\n       \"stream\": \"stdout\",\n       \"text\": [\n        \"\\n\",\n        \"Upper Range Limit: 2100000.0 ohm\"\n       ]\n      },\n      {\n       \"output_type\": \"stream\",\n       \"stream\": \"stdout\",\n       \"text\": [\n        \"\\n\",\n        \"Zero Check: False\\n\",\n        \"Zero Correct: False\"\n       ]\n      },\n      {\n       \"output_type\": \"stream\",\n       \"stream\": \"stdout\",\n       \"text\": [\n        \"\\n\"\n       ]\n      }\n     ],\n     \"prompt_number\": 5\n    },\n    {\n     \"cell_type\": \"markdown\",\n     \"metadata\": {},\n     \"source\": [\n      \"Take a single reading:\"\n     ]\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"reading, timestamp = elec.read()\\n\",\n      \"print \\\"Current Reading: {}\\\".format(reading)\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [\n      {\n       \"output_type\": \"stream\",\n       \"stream\": \"stdout\",\n       \"text\": [\n        \"Current Reading: 675582.8 ohm\\n\"\n       ]\n      }\n     ],\n     \"prompt_number\": 6\n    },\n    {\n     \"cell_type\": \"markdown\",\n     \"metadata\": {},\n     \"source\": [\n      \"Take 100 readings and plot them:\"\n     ]\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"timestamps = np.empty(100)\\n\",\n      \"readings = np.empty(100)\\n\",\n      \"for idx in xrange(100):\\n\",\n      \"    readings[idx], timestamps[idx] = elec.read()\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [],\n     \"prompt_number\": 7\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"plot(timestamps, readings)\\n\",\n      \"xlabel('time (s)')\\n\",\n      \"ylabel('({})'.format(elec.unit.name))\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [\n      {\n       \"metadata\": {},\n       \"output_type\": \"pyout\",\n       \"prompt_number\": 8,\n       \"text\": [\n        \"<matplotlib.text.Text at 0x8425f98>\"\n       ]\n      },\n      {\n       \"metadata\": {},\n       \"output_type\": \"display_data\",\n       \"png\": \"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\\nYII=\\n\",\n       \"text\": [\n        \"<matplotlib.figure.Figure at 0x84186a0>\"\n       ]\n      }\n     ],\n     \"prompt_number\": 8\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"with open(r'C:\\\\Users\\\\fpga\\\\Documents\\\\log.txt','w') as f:\\n\",\n      \"    f.write('Time,Resistance (Ohms)\\\\n')\\n\",\n      \"    \\n\",\n      \"elec.sendcmd('SYST:TIME:RESET')\\n\",\n      \"\\n\",\n      \"while True:\\n\",\n      \"    with open(r'C:\\\\Users\\\\fpga\\\\Documents\\\\log.txt','a') as f:\\n\",\n      \"        reading, timestamp = elec.read()\\n\",\n      \"        \\n\",\n      \"        f.write('{},{:e}\\\\n'.format(timestamp,reading.item()))\\n\",\n      \"        f.flush()\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [\n      {\n       \"ename\": \"KeyboardInterrupt\",\n       \"evalue\": \"\",\n       \"output_type\": \"pyerr\",\n       \"traceback\": [\n        \"\\u001b[1;31m---------------------------------------------------------------------------\\u001b[0m\\n\\u001b[1;31mKeyboardInterrupt\\u001b[0m                         Traceback (most recent call last)\",\n        \"\\u001b[1;32m<ipython-input-5-1fa8f93169f3>\\u001b[0m in \\u001b[0;36m<module>\\u001b[1;34m()\\u001b[0m\\n\\u001b[0;32m      6\\u001b[0m \\u001b[1;32mwhile\\u001b[0m \\u001b[0mTrue\\u001b[0m\\u001b[1;33m:\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0;32m      7\\u001b[0m     \\u001b[1;32mwith\\u001b[0m \\u001b[0mopen\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[1;34mr'C:\\\\Users\\\\fpga\\\\Documents\\\\log.txt'\\u001b[0m\\u001b[1;33m,\\u001b[0m\\u001b[1;34m'a'\\u001b[0m\\u001b[1;33m)\\u001b[0m \\u001b[1;32mas\\u001b[0m \\u001b[0mf\\u001b[0m\\u001b[1;33m:\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[1;32m----> 8\\u001b[1;33m         \\u001b[0mreading\\u001b[0m\\u001b[1;33m,\\u001b[0m \\u001b[0mtimestamp\\u001b[0m \\u001b[1;33m=\\u001b[0m \\u001b[0melec\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0mread\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0m\\u001b[0;32m      9\\u001b[0m \\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0;32m     10\\u001b[0m         \\u001b[0mf\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0mwrite\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[1;34m'{},{:e}\\\\n'\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0mformat\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[0mtimestamp\\u001b[0m\\u001b[1;33m,\\u001b[0m\\u001b[0mreading\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0mitem\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\",\n        \"\\u001b[1;32mC:\\\\Users\\\\fpga\\\\AppData\\\\Local\\\\Enthought\\\\Canopy\\\\User\\\\lib\\\\site-packages\\\\instruments\\\\keithley\\\\keithley6514.pyc\\u001b[0m in \\u001b[0;36mread\\u001b[1;34m(self)\\u001b[0m\\n\\u001b[0;32m    224\\u001b[0m         '''\\n\\u001b[0;32m    225\\u001b[0m         \\u001b[1;31m# TODO: figure out what to do with the status info\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[1;32m--> 226\\u001b[1;33m         \\u001b[0mraw\\u001b[0m \\u001b[1;33m=\\u001b[0m \\u001b[0mself\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0mquery\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[1;34m'READ?'\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0m\\u001b[0;32m    227\\u001b[0m         \\u001b[0mreading\\u001b[0m\\u001b[1;33m,\\u001b[0m \\u001b[0mtimestamp\\u001b[0m\\u001b[1;33m,\\u001b[0m \\u001b[0mstatus\\u001b[0m \\u001b[1;33m=\\u001b[0m \\u001b[0mself\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0m_parse_measurement\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[0mraw\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0;32m    228\\u001b[0m         \\u001b[1;32mreturn\\u001b[0m \\u001b[0mreading\\u001b[0m\\u001b[1;33m,\\u001b[0m \\u001b[0mtimestamp\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\",\n        \"\\u001b[1;32mC:\\\\Users\\\\fpga\\\\AppData\\\\Local\\\\Enthought\\\\Canopy\\\\User\\\\lib\\\\site-packages\\\\instruments\\\\abstract_instruments\\\\instrument.pyc\\u001b[0m in \\u001b[0;36mquery\\u001b[1;34m(self, cmd, size)\\u001b[0m\\n\\u001b[0;32m    111\\u001b[0m         \\u001b[1;33m:\\u001b[0m\\u001b[0mrtype\\u001b[0m\\u001b[1;33m:\\u001b[0m \\u001b[1;33m`\\u001b[0m\\u001b[0mstr\\u001b[0m\\u001b[1;33m`\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0;32m    112\\u001b[0m         \\\"\\\"\\\"\\n\\u001b[1;32m--> 113\\u001b[1;33m         \\u001b[1;32mreturn\\u001b[0m \\u001b[0mself\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0m_file\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0mquery\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[0mcmd\\u001b[0m\\u001b[1;33m,\\u001b[0m \\u001b[0msize\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0m\\u001b[0;32m    114\\u001b[0m \\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0;32m    115\\u001b[0m     \\u001b[1;31m## PROPERTIES ##\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\",\n        \"\\u001b[1;32mC:\\\\Users\\\\fpga\\\\AppData\\\\Local\\\\Enthought\\\\Canopy\\\\User\\\\lib\\\\site-packages\\\\instruments\\\\abstract_instruments\\\\gi_gpib.pyc\\u001b[0m in \\u001b[0;36mquery\\u001b[1;34m(self, msg, size)\\u001b[0m\\n\\u001b[0;32m    200\\u001b[0m         '''\\n\\u001b[0;32m    201\\u001b[0m         '''\\n\\u001b[1;32m--> 202\\u001b[1;33m         \\u001b[0mself\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0msendcmd\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[0mmsg\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0m\\u001b[0;32m    203\\u001b[0m         \\u001b[1;32mif\\u001b[0m \\u001b[1;34m'?'\\u001b[0m \\u001b[1;32mnot\\u001b[0m \\u001b[1;32min\\u001b[0m \\u001b[0mmsg\\u001b[0m\\u001b[1;33m:\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0;32m    204\\u001b[0m             \\u001b[0mself\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0m_file\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0msendcmd\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[1;34m'+read'\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\",\n        \"\\u001b[1;32mC:\\\\Users\\\\fpga\\\\AppData\\\\Local\\\\Enthought\\\\Canopy\\\\User\\\\lib\\\\site-packages\\\\instruments\\\\abstract_instruments\\\\gi_gpib.pyc\\u001b[0m in \\u001b[0;36msendcmd\\u001b[1;34m(self, msg)\\u001b[0m\\n\\u001b[0;32m    186\\u001b[0m             \\u001b[1;32mreturn\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0;32m    187\\u001b[0m         \\u001b[0mself\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0m_file\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0msendcmd\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[1;34m'+a:'\\u001b[0m \\u001b[1;33m+\\u001b[0m \\u001b[0mstr\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[0mself\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0m_gpib_address\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[1;32m--> 188\\u001b[1;33m         \\u001b[0mtime\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0msleep\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[1;36m0.01\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0m\\u001b[0;32m    189\\u001b[0m         \\u001b[0mself\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0m_file\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0msendcmd\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[1;34m'+eoi:{}'\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0mformat\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[0mself\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0m_eoi\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0;32m    190\\u001b[0m         \\u001b[0mtime\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0msleep\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[1;36m0.01\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\",\n        \"\\u001b[1;31mKeyboardInterrupt\\u001b[0m: \"\n       ]\n      }\n     ],\n     \"prompt_number\": 5\n    }\n   ],\n   \"metadata\": {}\n  }\n ]\n}\n"
  },
  {
    "path": "doc/examples/ex_maui.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# MAUI Oscilloscope controller\\n\",\n    \"\\n\",\n    \"The middle to high-end Teledyne-LeCroy oscilloscope come with the MAUI (Most Advanced User Interface) control interface. Each of these MAUI-enabled scopes can be controlled in the same way, assuming they have the same functionality, etc. The `MAUI` class presents a control interface to remotely access and setup an oscilloscope. Not every functionality is incorporated at this point, but the most imporant and basic ones are, i.e.:\\n\",\n    \" * General Oscilloscope controls, i.e., triggering\\n\",\n    \" * Channels\\n\",\n    \" * Math functions\\n\",\n    \" * Measurement setup and data retrieval\\n\",\n    \" * Waveform retrieval\\n\",\n    \"Here, some detailed examples for various applications are shown. \\n\",\n    \"\\n\",\n    \"## Communications\\n\",\n    \"These Oscilloscopes have many different ways of communicating with the host computer. This class only supports the `LXI (VXI11)` protocol, which should come by default on theses oscilloscopes. The reason for this is that this protocoll supports the NI-VISA protocol, which can be completely replaced with the open PyVISA. Thus the oscilloscope can be controlled from any OS, in fact, most of the development have taken place on Linux. *Note*: The scope that the software was developed with is an older wavesurfer 3054, which was at least supposed to support the `LXI (VXI11)` protocol. However, it could not be activated. After contacting Teledyne-LeCroy, they responded fairly quickly and sent an activation code to enable the protocol, free of charge.\\n\",\n    \"\\n\",\n    \"In order to successfully communicate with the oscilloscope, PyVISA requires the [pyvisa-py](https://pyvisa-py.readthedocs.io/en/latest/) backend. This should be the requirements for the package now. If not or not yet installed on your setup, you can install it by typing:\\n\",\n    \"\\n\",\n    \"    pip install pyvisa-py\\n\",\n    \"\\n\",\n    \"## Importing the pre-requisites\\n\",\n    \"First let's import some packages that we'll need, mostly instrumentkit of course :)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import sys, os\\n\",\n    \"\\n\",\n    \"# if you run this script from a cloned InstrumentKit path without a full installation, leave the following line in\\n\",\n    \"sys.path.insert(0, os.path.abspath('../../'))\\n\",\n    \"\\n\",\n    \"# import the instrument kit\\n\",\n    \"import instruments as ik\\n\",\n    \"import instruments.units as u\\n\",\n    \"\\n\",\n    \"# imports for specific functions in this script\\n\",\n    \"import matplotlib.pyplot as plt\\n\",\n    \"from time import sleep\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Enabling the oscilloscope\\n\",\n    \"\\n\",\n    \"First let us look at how to establish communications with the oscilloscope. ON the oscilloscope itself, go to `Utilities` -> `Utilities Setup` -> `Remote` and select on the left side the `LXI (VXI11)` communications protocol. Connect the oscilloscope to your local area network and check it's IP address. The example IP address that will be used here is `192.168.8.154`.\\n\",\n    \"\\n\",\n    \"Then you can load the oscilloscope and enable communications in the following way:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"inst = ik.teledyne.MAUI.open_visa(\\\"TCPIP0::192.168.0.10::INSTR\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Specifying your oscilloscope setup\\n\",\n    \"\\n\",\n    \"The MAUI interface works for mulitple different Teledyne-LeCroy oscilloscopes. Not all of these oscilloscopes will have the same options, so some commands might not be available on your scope. To make the oscilloscope controller versatile, the number of available channels (default 4), available functions (default 2), and available measurements (default 6) can be adjusted. The number of channels is simply how many inputs are available on the front. The number of functions is how many functions can be set up in the scopes math menu, usually labeled as `F1`, ... `Fn` in th oscilloscope software. The number of available measurements is the number of measurements that can be configured on the scope, usually labeled as `P1`, ... `Pn`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"4\"\n      ]\n     },\n     \"execution_count\": 3,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"# setting and getting the number of channels\\n\",\n    \"inst.number_channels = 4\\n\",\n    \"inst.number_channels\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"2\"\n      ]\n     },\n     \"execution_count\": 4,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"# setting and getting the number of functions\\n\",\n    \"inst.number_functions = 2\\n\",\n    \"inst.number_functions\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"6\"\n      ]\n     },\n     \"execution_count\": 5,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"# setting and getting the number of measurements\\n\",\n    \"inst.number_measurements = 6\\n\",\n    \"inst.number_measurements\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Triggering the scope\\n\",\n    \"\\n\",\n    \"The simplest possible way to stop and start the oscilloscope from triggering is as following:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# stop the oscilloscope from triggering\\n\",\n    \"inst.stop()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# start the oscilloscope in automatic triggering mode\\n\",\n    \"inst.run()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"However, the four triggering states can also be controlled manually. These states are: automatic triggering `auto`, normal triggering `normal`, a single trigger `single` and no triggering `stop`. These trigger states are implemented as a `inst.TriggerState` subclass under the instrument class. Reading the trigger state (should be `auto` from just before) and then setting it to `normal` can be done in the following way:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"<TriggerState.auto: 'AUTO'>\"\n      ]\n     },\n     \"execution_count\": 8,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"# get the current trigger state\\n\",\n    \"inst.trigger_state\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# restart the trigger with a single trigger\\n\",\n    \"inst.trigger_state = inst.TriggerState.normal\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"In addition, e.g., for a measurement, a trigger can also be forced upon request. For this to work, set the oscilloscope into stop mode, then force a trigger.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Stop the triggering\\n\",\n    \"inst.stop()\\n\",\n    \"\\n\",\n    \"# A trigger can also be forced by calling:\\n\",\n    \"inst.force_trigger()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The oscilloscope will be put back into stopped mode. To continue triggering in normal mode, run:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 11,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"inst.trigger_state = inst.TriggerState.normal\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"In addition to selecting the triggering state, the triggering source, type, and level can also be chosen. For most oscilloscopes, all channels and an external triggering source can be chosen from, optional settings are possible. Possible triggering sources are stored in the `enum` class `TriggerSource`, while triggering types are stored in the `TriggerType` `enum` class.\\n\",\n    \"\\n\",\n    \"Let's set the triggering source to the external trigger and trigger on the edge. This can be accomplished with the following commands:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 12,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"inst.trigger_source = inst.TriggerSource.ext\\n\",\n    \"inst.trigger_type = inst.TriggerType.edge\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Time base\\n\",\n    \"\\n\",\n    \"The timebase is the same for all channels and therefore implemented on the instrument level. Setting the timebase of the scope expects a unitful value. If no units are given, seconds are assumed. To set the time per division to 20 ns and read it back out, run the following commands.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 13,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"array(2.e-08) * s\"\n      ]\n     },\n     \"execution_count\": 13,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"inst.time_div = u.Quantity(20, u.ns)\\n\",\n    \"inst.time_div\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"To shift the timebase with respect to the trigger, a trigger delay can be called. This call is unitful as well. To set a trigger delay of 60 ns and read it back, run the following command.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 14,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"array(6.e-08) * s\"\n      ]\n     },\n     \"execution_count\": 14,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"inst.trigger_delay = u.Quantity(60, u.ns)\\n\",\n    \"inst.trigger_delay\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Controlling a channel\\n\",\n    \"\\n\",\n    \"To control a channel, several functions are implemented. The first channel is referred to as `0`, as is common in python. To create an instance of the first channel you can run:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 15,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"channel = inst.channel[0]\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Turning a trace on and off can be done by setting the `channel.trace` with a bool. For example, to turn the trace on (no matter what state it is in) and then read its state back, run\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 16,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"True\"\n      ]\n     },\n     \"execution_count\": 16,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"channel.trace = True\\n\",\n    \"channel.trace\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Control over the coupling of the specific channel is supplied via the `channel.Coupling` class. To set the coupling to $50\\\\,\\\\Omega$ and then read it back, run the following commands:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 17,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"<Coupling.dc50: 'D50'>\"\n      ]\n     },\n     \"execution_count\": 17,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"channel.coupling = channel.Coupling.dc50\\n\",\n    \"channel.coupling\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The scale (i.e., the volts per division) of a channel can be set unitful as well. If no units are given it is assumed that the user means Volts per division. To set the scale to 1 V per division and read its state back, run\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 18,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"array(1.) * V\"\n      ]\n     },\n     \"execution_count\": 18,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"channel.scale = u.Quantity(1, u.V)\\n\",\n    \"channel.scale\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"In the same manner, the trace can also be shifted to, let's say -2950 mV in vertical position. This offset can be set / read as following:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 19,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"array(-2.95) * V\"\n      ]\n     },\n     \"execution_count\": 19,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"channel.offset = u.Quantity(-2950, u.mV)\\n\",\n    \"channel.offset\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Finally, after having gone through all configurations, the waveform can be read back to the computer. The waveform is reutrned as a two dimensional numpy array representing the timebase and the signal. We can directly unpack the waveform via:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 20,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"timebase, signal = channel.read_waveform()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Furthermore we know that the signal has been shifted by -2.95 V in the negative direction and by 60 ns in the positive time base direction. Let's see how the signal looks.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 21,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"Text(0, 0.5, 'Signal (V)')\"\n      ]\n     },\n     \"execution_count\": 21,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    },\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<Figure size 432x288 with 1 Axes>\"\n      ]\n     },\n     \"metadata\": {\n      \"needs_background\": \"light\"\n     },\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"plt.plot(timebase, signal)\\n\",\n    \"plt.xlabel(\\\"Time since trigger (s)\\\")\\n\",\n    \"plt.ylabel(\\\"Signal (V)\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The offset in horizontal and vertical access are automatically taken into account such that the signal that is returned can directly be interpreted in time relative to the trigger and in the signal in absolute voltage. Clearly, this peak is almost 4 V heigh and very short. Let us move the peak in horizontal and vertical direction to the center of the oscilloscope display and read back one waveform with 1 V per division on the vertical axis and one waveform with 250 mV per division. The latter will surely clip the peak at the top.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 22,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"Text(0.5, 1.0, '1 V / division: Signal visible')\"\n      ]\n     },\n     \"execution_count\": 22,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    },\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<Figure size 864x288 with 2 Axes>\"\n      ]\n     },\n     \"metadata\": {\n      \"needs_background\": \"light\"\n     },\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# set offsets to zero\\n\",\n    \"inst.trigger_delay = 0\\n\",\n    \"channel.offset = 0\\n\",\n    \"\\n\",\n    \"# set horizontal axis to 5 ns per division\\n\",\n    \"inst.time_div = u.Quantity(5, u.ns)\\n\",\n    \"\\n\",\n    \"# set to 250 mV per division and read waveform back\\n\",\n    \"channel.scale = u.Quantity(250, u.mV)\\n\",\n    \"x1, y1 = channel.read_waveform()\\n\",\n    \"\\n\",\n    \"# allow for 250 ms to not have it read to fast\\n\",\n    \"sleep(0.25)\\n\",\n    \"\\n\",\n    \"# set to 1 V per division and read the waveform back\\n\",\n    \"channel.scale = u.Quantity(1, u.V)\\n\",\n    \"x2, y2 = channel.read_waveform()\\n\",\n    \"\\n\",\n    \"# plot the results\\n\",\n    \"fig, ax = plt.subplots(1, 2, sharey=True, figsize=(12,4))\\n\",\n    \"\\n\",\n    \"ax[0].plot(x1, y1)\\n\",\n    \"ax[0].set_xlabel(\\\"Time (s)\\\")\\n\",\n    \"ax[0].set_ylabel(\\\"Signal (V)\\\")\\n\",\n    \"ax[0].set_title(\\\"250 mV / division: Signal clipped\\\")\\n\",\n    \"ax[1].plot(x2, y2)\\n\",\n    \"ax[1].set_xlabel(\\\"Time (s)\\\")\\n\",\n    \"ax[1].set_title(\\\"1 V / division: Signal visible\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Clearly, the left signal is clipped on top. This results from the fact that the signal did not fit on the oscilloscope screen. If the signal is off scale, the data returned is simple the largest possible one, in this case, full signal. Since we artificially scale the two figures to the same y scale, this of course does not show up on top but at 1 V, which is equivalent to 4 divisions up.\\n\",\n    \"\\n\",\n    \"**Remember**: Reading a wave form only returns the data that is displayed on the oscilloscope screen.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Math functions\\n\",\n    \"\\n\",\n    \"Many math functions are available on these oscilloscopes, depending on the options that the oscilloscope is shipped with, more or less functions are available. For an overview of the implemented functions and how they work, have a look at the InstrumentKit documentation. You will find all functions in the `Math.Operators` subclass.\\n\",\n    \"\\n\",\n    \"For now, let's set up averaging of the first channel.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 23,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Access the first math function `F1`\\n\",\n    \"function = inst.math[0]\\n\",\n    \"\\n\",\n    \"# turn on the trace of this math function\\n\",\n    \"function.trace = True\\n\",\n    \"\\n\",\n    \"# set it to averaging of channel 1 (0 in python)\\n\",\n    \"function.operator.average(('C', 0))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"This will have set up the first function to average the first channel. The parameter that is passed on to to average, the required parameter, is the source it should average. Different operators have of course different parameters that are required / optional. \\n\",\n    \"\\n\",\n    \"**Note**: There are two ways that sources can be specified. If an integer is given, it is assumed that the source is a channel. Alternatively another source, e.g., a math function could also be defined. This would be done by submitting a tuple, i.e., `('F', 0)` to select the first math function (called `F1` in the oscilloscope).\\n\",\n    \"\\n\",\n    \"To check if this all worked we can read back the channel itself and the average math function and plot the results.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 24,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"Text(0.5, 1.0, 'Average of the channel (math)')\"\n      ]\n     },\n     \"execution_count\": 24,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    },\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<Figure size 864x288 with 2 Axes>\"\n      ]\n     },\n     \"metadata\": {\n      \"needs_background\": \"light\"\n     },\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# read channel\\n\",\n    \"x_ch, y_ch = channel.read_waveform()\\n\",\n    \"\\n\",\n    \"# allow for 250 ms to not have it read to fast\\n\",\n    \"sleep(0.25)\\n\",\n    \"\\n\",\n    \"# read math\\n\",\n    \"x_math, y_math = function.read_waveform()\\n\",\n    \"\\n\",\n    \"# plot\\n\",\n    \"fig, ax = plt.subplots(1, 2, sharey=True, figsize=(12, 4))\\n\",\n    \"\\n\",\n    \"ax[0].plot(x_ch, y_ch)\\n\",\n    \"ax[0].set_xlabel(\\\"Time (s)\\\")\\n\",\n    \"ax[0].set_ylabel(\\\"Signal (V)\\\")\\n\",\n    \"ax[0].set_title(\\\"Channel readout\\\")\\n\",\n    \"ax[1].plot(x_math, y_math)\\n\",\n    \"ax[1].set_xlabel(\\\"Time (s)\\\")\\n\",\n    \"ax[1].set_title(\\\"Average of the channel (math)\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"With a poorer signal generator, the average should look a lot smoother than the channel. To finish up the math section, let's turn off the math trace.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 25,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"function.trace = False\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Measurements and statistics\\n\",\n    \"\\n\",\n    \"In addition to mathematical operations on channels, oscilloscopes can take measurements and display the statistics. Many measurement parameters are already implemented, check out the documentation and look for the `inst.MeasurementParameters` class. Currently, only measurement parameters that act on a single source are available.\\n\",\n    \"\\n\",\n    \"As an example, let us set up 2 measurements. Measurement 1 determines the rise time (10% to 90%), measurement 2 the fall time (80% to 20%) of the first channel readout. Setting up the measurement can be done as following:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 26,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# assign the two measurements\\n\",\n    \"msr1 = inst.measurement[0]\\n\",\n    \"msr2 = inst.measurement[1]\\n\",\n    \"\\n\",\n    \"# turn on the measurements (only one is really necessary, the other one is turned on automatically!)\\n\",\n    \"msr1.measurement_state = msr1.State.both\\n\",\n    \"\\n\",\n    \"# assign the measurement types and which source the measurement should be on\\n\",\n    \"msr1.set_parameter(inst.MeasurementParameters.rise_time_10_90, 0)\\n\",\n    \"msr2.set_parameter(inst.MeasurementParameters.fall_time_80_20, 0)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The oscilloscope will now automatically set up these measurements and will start accumulating statistics. The statistics can be returned at any point, which will return a tuple containing 5 floats. These floats are:\\n\",\n    \"\\n\",\n    \" 1. Average\\n\",\n    \" 2. Lowest value measured\\n\",\n    \" 3. Highest value measured\\n\",\n    \" 4. Standard deviation\\n\",\n    \" 5. Number of sweeps\\n\",\n    \" \\n\",\n    \"These returns are not unitful, so you will need to know what was set up. For the rise and fall times, the returns will of course be in the form of time. SI units are always returned, in this case, seconds.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 27,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"(1.52152e-09, 1.49e-09, 1.56e-09, 2.553e-11, 4.0)\"\n      ]\n     },\n     \"execution_count\": 27,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"msr1.statistics\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 28,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"(9.6247e-10, 9.02e-10, 1.01e-09, 2.415e-11, 24.0)\"\n      ]\n     },\n     \"execution_count\": 28,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"msr2.statistics\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"To start a new series of measurements, i.e., reset the number of sweeps, the following command can be executed:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 29,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"(1.56e-09, 1.56e-09, 1.56e-09, 0.0, 1.0)\"\n      ]\n     },\n     \"execution_count\": 29,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"inst.clear_sweeps()\\n\",\n    \"\\n\",\n    \"# getting statistics for `msr1` again:\\n\",\n    \"msr1.statistics\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"To delete the measurement parameters again and turn off the table, the following commands are used:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 30,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# delete parameters\\n\",\n    \"msr1.delete()\\n\",\n    \"msr2.delete()\\n\",\n    \"\\n\",\n    \"# turn off measurement\\n\",\n    \"msr1.measurement_state = msr1.State.off\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Further information\\n\",\n    \"\\n\",\n    \"Please check out the documention on [readthedocs.io](https://instrumentkit.readthedocs.io/en/latest/) and feel free to open an issue on the github repository if you have any problems / feature requests.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": []\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.8.3\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 4\n}\n"
  },
  {
    "path": "doc/examples/ex_oscilloscope_waveform.ipynb",
    "content": "{\n \"metadata\": {\n  \"name\": \"ex_oscilloscope_waveform\"\n },\n \"nbformat\": 3,\n \"nbformat_minor\": 0,\n \"worksheets\": [\n  {\n   \"cells\": [\n    {\n     \"cell_type\": \"heading\",\n     \"level\": 1,\n     \"metadata\": {},\n     \"source\": [\n      \"InstrumentKit Library Examples\"\n     ]\n    },\n    {\n     \"cell_type\": \"heading\",\n     \"level\": 2,\n     \"metadata\": {},\n     \"source\": [\n      \"Tektronix DPO 4104 Oscilloscope\"\n     ]\n    },\n    {\n     \"cell_type\": \"markdown\",\n     \"metadata\": {},\n     \"source\": [\n      \"In this example, we will demonstrate how to connect to a Tektronix DPO 4104 \\n\",\n      \"oscilloscope and transfer the waveform from channel 1 into memory.\"\n     ]\n    },\n    {\n     \"cell_type\": \"markdown\",\n     \"metadata\": {},\n     \"source\": [\n      \"We start by importing the InstrumentKit and numpy packages. In this example \\n\",\n      \"we require numpy because the waveforms will be returned as numpy arrays.\"\n     ]\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"import instruments as ik\\n\",\n      \"import numpy as np\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": []\n    },\n    {\n     \"cell_type\": \"markdown\",\n     \"metadata\": {},\n     \"source\": [\n      \"Next, we open our connection to the oscilloscope. Here we use the associated\\n\",\n      \"TekDPO4104 class and open the connection via TCP/IP to address 192.168.0.2 on\\n\",\n      \"port 8080.\"\n     ]\n    },\n    {\n     \"cell_type\": \"markdown\",\n     \"metadata\": {},\n     \"source\": [\n      \"The connection method used will have to be changed to match your setup.\"\n     ]\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"tek = ik.tektronix.TekTDS224.open_tcpip('192.168.0.2', 8080)\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": []\n    },\n    {\n     \"cell_type\": \"markdown\",\n     \"metadata\": {},\n     \"source\": [\n      \"Now that we are connected to the instrument, we can transfer the waveform \\n\",\n      \"from the oscilloscope. Note that Python channel[0] specifies the physical\\n\",\n      \"channel 1. This is due to Python's zero-based numbering vs Tektronix's\\n\",\n      \"one-based numbering.\"\n     ]\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"[x,y] = tek.channel[0].read_waveform()\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": []\n    },\n    {\n     \"cell_type\": \"markdown\",\n     \"metadata\": {},\n     \"source\": [\n      \"With the waveform now in memory, any other data analysis can be performed.\\n\",\n      \"Here we simply compute the mean value from the y-data.\"\n     ]\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"print np.mean(y)\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": []\n    }\n   ],\n   \"metadata\": {}\n  }\n ]\n}\n"
  },
  {
    "path": "doc/examples/ex_oscilloscope_waveform.py",
    "content": "# <nbformat>3.0</nbformat>\n\n# <headingcell level=1>\n\n# InstrumentKit Library Examples\n\n# <headingcell level=2>\n\n# Tektronix DPO 4104 Oscilloscope\n\n# <markdowncell>\n\n# In this example, we will demonstrate how to connect to a Tektronix DPO 4104\n# oscilloscope and transfer the waveform from channel 1 into memory.\n\n# <markdowncell>\n\n# We start by importing the InstrumentKit and numpy packages. In this example\n# we require numpy because the waveforms will be returned as numpy arrays.\n\n# <codecell>\n\nimport instruments as ik\nimport numpy as np\n\n# <markdowncell>\n\n# Next, we open our connection to the oscilloscope. Here we use the associated\n# TekDPO4104 class and open the connection via TCP/IP to address 192.168.0.2 on\n# port 8080.\n\n# <markdowncell>\n\n# The connection method used will have to be changed to match your setup.\n\n# <codecell>\n\ntek = ik.tektronix.TekTDS224.open_tcpip(\"192.168.0.2\", 8080)\n\n# <markdowncell>\n\n# Now that we are connected to the instrument, we can transfer the waveform\n# from the oscilloscope. Note that Python channel[0] specifies the physical\n# channel 1. This is due to Python's zero-based numbering vs Tektronix's\n# one-based numbering.\n\n# <codecell>\n\n[x, y] = tek.channel[0].read_waveform()\n\n# <markdowncell>\n\n# With the waveform now in memory, any other data analysis can be performed.\n# Here we simply compute the mean value from the y-data.\n\n# <codecell>\n\nprint(np.mean(y))\n"
  },
  {
    "path": "doc/examples/ex_qubitekk_gui.py",
    "content": "#!/usr/bin/python\n# Qubitekk Coincidence Counter example\nimport matplotlib\n\nmatplotlib.use(\"TkAgg\")\n\nfrom matplotlib.figure import Figure\n\nfrom numpy import arange, sin, pi\nfrom matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg\n\n# implement the default mpl key bindings\nfrom matplotlib.backend_bases import key_press_handler\nfrom sys import platform as _platform\n\nimport instruments as ik\nimport tkinter as tk\nimport re\n\n\ndef clear_counts(*args):\n    cc.clear_counts()\n\n\ndef getvalues(i):\n    # set counts labels\n    chan1counts.set(cc.channel[0].count)\n    chan2counts.set(cc.channel[1].count)\n    coinc_counts.set(cc.channel[2].count)\n    # add count values to arrays for plotting\n    chan1vals.append(chan1counts.get())\n    chan2vals.append(chan1counts.get())\n    coincvals.append(chan1counts.get())\n    if cc.channel[0].count < 0:\n        chan1counts.set(\"Overflow\")\n    if cc.channel[1].count < 0:\n        chan2counts.set(\"Overflow\")\n    if cc.channel[2].count < 0:\n        coinc_counts.set(\"Overflow\")\n    t.append(i * time_diff)\n    i += 1\n    # plot values\n    (p1,) = a.plot(t, coincvals, color=\"r\", linewidth=2.0)\n    (p2,) = a.plot(t, chan1vals, color=\"b\", linewidth=2.0)\n    (p3,) = a.plot(t, chan2vals, color=\"g\", linewidth=2.0)\n    a.legend([p1, p2, p3], [\"Coincidences\", \"Channel 1\", \"Channel 2\"])\n    a.set_xlabel(\"Time (s)\")\n    a.set_ylabel(\"Counts (Hz)\")\n\n    canvas.show()\n    # get the values again in the specified amount of time\n    root.after(int(time_diff * 1000), getvalues, i)\n\n\ndef gate_enable():\n    if gate_enabled.get():\n        cc.gate = True\n    else:\n        cc.gate = False\n\n\ndef subtract_enable():\n    if subtract_enabled.get():\n        cc.subtract = True\n    else:\n        cc.subtract = False\n\n\ndef trigger_enable():\n    if trigger_enabled.get():\n        cc.trigger = cc.TriggerMode.continuous\n    else:\n        cc.trigger = cc.TriggerMode.start_stop\n\n\ndef parse(*args):\n    cc.dwell_time = float(re.sub(\"[A-z]\", \"\", dwell_time.get()))\n    cc.window = float(re.sub(\"[A-z]\", \"\", window.get()))\n\n\ndef reset(*args):\n    cc.reset()\n    dwell_time.set(cc.dwell_time)\n    window.set(cc.window)\n    trigger_enabled.set(cc.count_enable)\n    gate_enabled.set(cc.gate_enable)\n\n\nif __name__ == \"__main__\":\n    cc = ik.qubitekk.CC1.open_serial(vid=1027, pid=24577, baud=19200, timeout=10)\n    print(cc.firmware)\n    # i is used to keep track of time\n    i = 0\n    # read counts every 0.5 seconds\n    time_diff = 0.5\n\n    root = tk.Tk()\n    root.title(\"Qubitekk Coincidence Counter Control Software\")\n\n    # set up the Tkinter grid layout\n    mainframe = tk.Frame(root)\n    mainframe.padding = \"3 3 12 12\"\n    mainframe.grid(column=0, row=0, sticky=(tk.N, tk.W, tk.E, tk.S))\n    mainframe.columnconfigure(0, weight=1)\n    mainframe.rowconfigure(0, weight=1)\n\n    # set up the label text\n    dwell_time = tk.StringVar()\n    dwell_time.set(cc.dwell_time)\n\n    window = tk.StringVar()\n    window.set(cc.window)\n\n    chan1counts = tk.StringVar()\n    chan1counts.set(cc.channel[0].count)\n    chan2counts = tk.StringVar()\n    chan2counts.set(cc.channel[1].count)\n    coinc_counts = tk.StringVar()\n    coinc_counts.set(cc.channel[2].count)\n\n    gate_enabled = tk.IntVar()\n    subtract_enabled = tk.IntVar()\n    trigger_enabled = tk.IntVar()\n\n    # set up the initial checkbox value for the gate enable\n    if cc.gate:\n        gate_enabled.set(1)\n    else:\n        gate_enabled.set(0)\n\n    # set up the initial checkbox value for the trigger enable\n    if cc.subtract:\n        subtract_enabled.set(1)\n    else:\n        subtract_enabled.set(0)\n\n    # set up the initial checkbox value for the trigger enable\n    if cc.trigger:\n        trigger_enabled.set(1)\n    else:\n        trigger_enabled.set(0)\n\n    # set up the plotting area\n\n    f = Figure(figsize=(10, 8), dpi=100)\n    a = f.add_subplot(111, axisbg=\"black\")\n\n    t = []\n    coincvals = []\n    chan1vals = []\n    chan2vals = []\n\n    # a tk.DrawingArea\n    canvas = FigureCanvasTkAgg(f, mainframe)\n    canvas.get_tk_widget().grid(column=3, row=1, rowspan=11, sticky=tk.W)\n\n    # label initialization\n    dwell_time_entry = tk.Entry(\n        mainframe, width=7, textvariable=dwell_time, font=\"Verdana 20\"\n    )\n    dwell_time_entry.grid(column=2, row=2, sticky=(tk.W, tk.E))\n    window_entry = tk.Entry(mainframe, width=7, textvariable=window, font=\"Verdana 20\")\n    window_entry.grid(column=2, row=3, sticky=(tk.W, tk.E))\n\n    tk.Label(mainframe, text=\"Dwell Time:\", font=\"Verdana 20\").grid(\n        column=1, row=2, sticky=tk.W\n    )\n    tk.Label(mainframe, text=\"Window size:\", font=\"Verdana 20\").grid(\n        column=1, row=3, sticky=tk.W\n    )\n\n    tk.Checkbutton(\n        mainframe, font=\"Verdana 20\", variable=gate_enabled, command=gate_enable\n    ).grid(column=2, row=4)\n    tk.Label(mainframe, text=\"Gate Enable: \", font=\"Verdana 20\").grid(\n        column=1, row=4, sticky=tk.W\n    )\n\n    tk.Checkbutton(\n        mainframe, font=\"Verdana 20\", variable=subtract_enabled, command=subtract_enable\n    ).grid(column=2, row=5)\n    tk.Label(mainframe, text=\"Subtract Accidentals: \", font=\"Verdana 20\").grid(\n        column=1, row=5, sticky=tk.W\n    )\n\n    tk.Checkbutton(\n        mainframe, font=\"Verdana 20\", variable=trigger_enabled, command=trigger_enable\n    ).grid(column=2, row=6)\n    tk.Label(mainframe, text=\"Continuous Trigger: \", font=\"Verdana 20\").grid(\n        column=1, row=6, sticky=tk.W\n    )\n\n    tk.Label(mainframe, text=\"Channel 1: \", font=\"Verdana 20\").grid(\n        column=1, row=7, sticky=tk.W\n    )\n    tk.Label(mainframe, text=\"Channel 2: \", font=\"Verdana 20\").grid(\n        column=1, row=8, sticky=tk.W\n    )\n    tk.Label(mainframe, text=\"Coincidences: \", font=\"Verdana 20\").grid(\n        column=1, row=9, sticky=tk.W\n    )\n\n    tk.Label(\n        mainframe, textvariable=chan1counts, font=\"Verdana 34\", fg=\"white\", bg=\"black\"\n    ).grid(column=2, row=7, sticky=tk.W)\n    tk.Label(\n        mainframe, textvariable=chan2counts, font=\"Verdana 34\", fg=\"white\", bg=\"black\"\n    ).grid(column=2, row=8, sticky=tk.W)\n    tk.Label(\n        mainframe, textvariable=coinc_counts, font=\"Verdana 34\", fg=\"white\", bg=\"black\"\n    ).grid(column=2, row=9, sticky=tk.W)\n\n    tk.Button(mainframe, text=\"Reset\", font=\"Verdana 24\", command=reset).grid(\n        column=1, row=10, sticky=tk.W\n    )\n\n    tk.Button(\n        mainframe, text=\"Clear Counts\", font=\"Verdana 24\", command=clear_counts\n    ).grid(column=2, row=10, sticky=tk.W)\n\n    tk.Label(\n        mainframe, text=\"Firmware Version: \" + str(cc.firmware), font=\"Verdana 20\"\n    ).grid(column=1, row=11, columnspan=2, sticky=tk.W)\n\n    for child in mainframe.winfo_children():\n        child.grid_configure(padx=5, pady=5)\n    # when the enter key is pressed, send the current values in the entries to the dwelltime and window to the\n    # coincidence counter\n    root.bind(\"<Return>\", parse)\n    # in 100 milliseconds, get the counts values off of the coincidence counter\n    root.after(int(time_diff * 1000), getvalues, i)\n    # start the GUI\n    root.mainloop()\n"
  },
  {
    "path": "doc/examples/ex_qubitekkcc.py",
    "content": "#!/usr/bin/python\nfrom sys import platform as _platform\n\nimport instruments as ik\nimport instruments.units as u\n\n\ndef main():\n    cc1 = ik.qubitekk.CC1.open_serial(vid=1027, pid=24577, baud=19200, timeout=10)\n    cc1.dwell_time = 1.0 * u.s\n    print(cc1.dwell_time)\n    cc1.delay = 0.0 * u.ns\n    print(cc1.delay)\n    cc1.window = 3.0 * u.ns\n    print(cc1.window)\n    cc1.trigger = ik.qubitekk.TriggerModeInt.start_stop\n    print(cc1.trigger)\n    print(\"Fetching Counts\")\n    print(cc1.channel[0].count)\n    print(cc1.channel[1].count)\n    print(cc1.channel[2].count)\n    print(\"Fetched Counts\")\n\n\nif __name__ == \"__main__\":\n    while True:\n        main()\n"
  },
  {
    "path": "doc/examples/ex_qubitekkcc_simple.py",
    "content": "#!/usr/bin/python\n# Qubitekk Coincidence Counter example\n\nfrom sys import platform as _platform\n\nimport instruments as ik\nimport instruments.units as u\n\nif __name__ == \"__main__\":\n    # open connection to coincidence counter. If you are using Windows, this will be a com port. On linux, it will show\n    # up in /dev/ttyusb\n    if _platform == \"linux\" or _platform == \"linux2\":\n        cc = ik.qubitekk.CC1.open_serial(\"/dev/ttyUSB0\", 19200, timeout=1)\n    else:\n        cc = ik.qubitekk.CC1.open_serial(\"COM8\", 19200, timeout=1)\n\n    print(\"Initializing Coincidence Counter\")\n    cc.dwell_time = 1.0 * u.s\n    cc.delay = 0.0 * u.ns\n    cc.window = 3.0 * u.ns\n    cc.trigger = cc.TriggerMode.start_stop\n    print(f\"ch1 counts: {str(cc.channel[0].count)}\")\n    print(f\"ch2 counts: {str(cc.channel[1].count)}\")\n    print(f\"counts counts: {str(cc.channel[2].count)}\")\n\n    print(\"Finished Initializing Coincidence Counter\")\n"
  },
  {
    "path": "doc/examples/ex_tekdpo70000.ipynb",
    "content": "{\n \"metadata\": {\n  \"name\": \"\"\n },\n \"nbformat\": 3,\n \"nbformat_minor\": 0,\n \"worksheets\": [\n  {\n   \"cells\": [\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"%pylab inline\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [\n      {\n       \"output_type\": \"stream\",\n       \"stream\": \"stdout\",\n       \"text\": [\n        \"Populating the interactive namespace from numpy and matplotlib\\n\"\n       ]\n      }\n     ],\n     \"prompt_number\": 16\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"from __future__ import division\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [],\n     \"prompt_number\": 17\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"import instruments as ik\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [],\n     \"prompt_number\": 1\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"scope=ik.tektronix.TekDPO70000Series.open_tcpip('192.168.0.4',4005)\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [],\n     \"prompt_number\": 2\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"scope._file._debug = True\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [],\n     \"prompt_number\": 3\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"scope._file.timeout = 2\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [],\n     \"prompt_number\": 4\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"scope.query('*IDN?')\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [\n      {\n       \"output_type\": \"stream\",\n       \"stream\": \"stdout\",\n       \"text\": [\n        \" <- '*IDN?\\\\n' \\n\",\n        \" -> 'TEKTRONIX,DPO71254C,B130126,CF:91.1CT FV:5.3.0 Build 83\\\\n'\\n\"\n       ]\n      },\n      {\n       \"metadata\": {},\n       \"output_type\": \"pyout\",\n       \"prompt_number\": 5,\n       \"text\": [\n        \"'TEKTRONIX,DPO71254C,B130126,CF:91.1CT FV:5.3.0 Build 83\\\\n'\"\n       ]\n      }\n     ],\n     \"prompt_number\": 5\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"ch0 = scope.channel[0]\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [],\n     \"prompt_number\": 6\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"ch0.bandwidth\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [\n      {\n       \"output_type\": \"stream\",\n       \"stream\": \"stdout\",\n       \"text\": [\n        \" <- 'CH1:BAN?\\\\n' \\n\",\n        \" -> '12.5000E+9\\\\n'\\n\"\n       ]\n      },\n      {\n       \"metadata\": {},\n       \"output_type\": \"pyout\",\n       \"prompt_number\": 7,\n       \"text\": [\n        \"array(12500000000.0) * Hz\"\n       ]\n      }\n     ],\n     \"prompt_number\": 7\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"_.to('GHz')\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [\n      {\n       \"metadata\": {},\n       \"output_type\": \"pyout\",\n       \"prompt_number\": 8,\n       \"text\": [\n        \"array(12.5) * GHz\"\n       ]\n      }\n     ],\n     \"prompt_number\": 8\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"scope.outgoing_waveform_encoding = scope.WaveformEncoding.binary\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [\n      {\n       \"output_type\": \"stream\",\n       \"stream\": \"stdout\",\n       \"text\": [\n        \" <- 'WFMO:ENC BINARY\\\\n' \\n\"\n       ]\n      }\n     ],\n     \"prompt_number\": 9\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"scope.select_fastest_encoding()\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [\n      {\n       \"output_type\": \"stream\",\n       \"stream\": \"stdout\",\n       \"text\": [\n        \" <- 'DAT:ENC FAS\\\\n' \\n\"\n       ]\n      }\n     ],\n     \"prompt_number\": 10\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"scope.acquire_mode_actual\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [\n      {\n       \"ename\": \"timeout\",\n       \"evalue\": \"timed out\",\n       \"output_type\": \"pyerr\",\n       \"traceback\": [\n        \"\\u001b[1;31m---------------------------------------------------------------------------\\u001b[0m\\n\\u001b[1;31mtimeout\\u001b[0m                                   Traceback (most recent call last)\",\n        \"\\u001b[1;32m<ipython-input-11-fc328e0e3863>\\u001b[0m in \\u001b[0;36m<module>\\u001b[1;34m()\\u001b[0m\\n\\u001b[1;32m----> 1\\u001b[1;33m \\u001b[0mscope\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0macquire_mode_actual\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0m\",\n        \"\\u001b[1;32mC:\\\\Users\\\\fpga\\\\AppData\\\\Local\\\\Enthought\\\\Canopy\\\\User\\\\lib\\\\site-packages\\\\instruments\\\\util_fns.pyc\\u001b[0m in \\u001b[0;36mgetter\\u001b[1;34m(self)\\u001b[0m\\n\\u001b[0;32m    109\\u001b[0m         \\u001b[1;32mreturn\\u001b[0m \\u001b[0mval\\u001b[0m \\u001b[1;32mif\\u001b[0m \\u001b[0moutput_decoration\\u001b[0m \\u001b[1;32mis\\u001b[0m \\u001b[0mNone\\u001b[0m \\u001b[1;32melse\\u001b[0m \\u001b[0moutput_decoration\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[0mval\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0;32m    110\\u001b[0m     \\u001b[1;32mdef\\u001b[0m \\u001b[0mgetter\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[0mself\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m:\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[1;32m--> 111\\u001b[1;33m         \\u001b[1;32mreturn\\u001b[0m \\u001b[0menum\\u001b[0m\\u001b[1;33m[\\u001b[0m\\u001b[0min_decor_fcn\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[0mself\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0mquery\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[1;34m\\\"{}?\\\"\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0mformat\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[0mname\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0mstrip\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m]\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0m\\u001b[0;32m    112\\u001b[0m     \\u001b[1;32mdef\\u001b[0m \\u001b[0msetter\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[0mself\\u001b[0m\\u001b[1;33m,\\u001b[0m \\u001b[0mnewval\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m:\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0;32m    113\\u001b[0m         \\u001b[0mself\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0msendcmd\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[1;34m\\\"{} {}\\\"\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0mformat\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[0mname\\u001b[0m\\u001b[1;33m,\\u001b[0m \\u001b[0mout_decor_fcn\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[0menum\\u001b[0m\\u001b[1;33m[\\u001b[0m\\u001b[0mnewval\\u001b[0m\\u001b[1;33m]\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0mvalue\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\",\n        \"\\u001b[1;32mC:\\\\Users\\\\fpga\\\\AppData\\\\Local\\\\Enthought\\\\Canopy\\\\User\\\\lib\\\\site-packages\\\\instruments\\\\abstract_instruments\\\\instrument.pyc\\u001b[0m in \\u001b[0;36mquery\\u001b[1;34m(self, cmd, size)\\u001b[0m\\n\\u001b[0;32m    111\\u001b[0m         \\u001b[1;33m:\\u001b[0m\\u001b[0mrtype\\u001b[0m\\u001b[1;33m:\\u001b[0m \\u001b[1;33m`\\u001b[0m\\u001b[0mstr\\u001b[0m\\u001b[1;33m`\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0;32m    112\\u001b[0m         \\\"\\\"\\\"\\n\\u001b[1;32m--> 113\\u001b[1;33m         \\u001b[1;32mreturn\\u001b[0m \\u001b[0mself\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0m_file\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0mquery\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[0mcmd\\u001b[0m\\u001b[1;33m,\\u001b[0m \\u001b[0msize\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0m\\u001b[0;32m    114\\u001b[0m \\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0;32m    115\\u001b[0m     \\u001b[1;31m## PROPERTIES ##\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\",\n        \"\\u001b[1;32mC:\\\\Users\\\\fpga\\\\AppData\\\\Local\\\\Enthought\\\\Canopy\\\\User\\\\lib\\\\site-packages\\\\instruments\\\\abstract_instruments\\\\socketwrapper.pyc\\u001b[0m in \\u001b[0;36mquery\\u001b[1;34m(self, msg, size)\\u001b[0m\\n\\u001b[0;32m    139\\u001b[0m         '''\\n\\u001b[0;32m    140\\u001b[0m         \\u001b[0mself\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0msendcmd\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[0mmsg\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[1;32m--> 141\\u001b[1;33m         \\u001b[0mresp\\u001b[0m \\u001b[1;33m=\\u001b[0m \\u001b[0mself\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0mread\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[0msize\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0m\\u001b[0;32m    142\\u001b[0m         \\u001b[1;32mif\\u001b[0m \\u001b[0mself\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0m_debug\\u001b[0m\\u001b[1;33m:\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0;32m    143\\u001b[0m             \\u001b[1;32mprint\\u001b[0m \\u001b[1;34m\\\" -> {}\\\"\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0mformat\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[0mrepr\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[0mresp\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\",\n        \"\\u001b[1;32mC:\\\\Users\\\\fpga\\\\AppData\\\\Local\\\\Enthought\\\\Canopy\\\\User\\\\lib\\\\site-packages\\\\instruments\\\\abstract_instruments\\\\socketwrapper.pyc\\u001b[0m in \\u001b[0;36mread\\u001b[1;34m(self, size)\\u001b[0m\\n\\u001b[0;32m    103\\u001b[0m             \\u001b[0mc\\u001b[0m \\u001b[1;33m=\\u001b[0m \\u001b[1;36m0\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0;32m    104\\u001b[0m             \\u001b[1;32mwhile\\u001b[0m \\u001b[0mc\\u001b[0m \\u001b[1;33m!=\\u001b[0m \\u001b[0mself\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0m_terminator\\u001b[0m\\u001b[1;33m:\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[1;32m--> 105\\u001b[1;33m                 \\u001b[0mc\\u001b[0m \\u001b[1;33m=\\u001b[0m \\u001b[0mself\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0m_conn\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0mrecv\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[1;36m1\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0m\\u001b[0;32m    106\\u001b[0m                 \\u001b[0mresult\\u001b[0m \\u001b[1;33m+=\\u001b[0m \\u001b[0mc\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0;32m    107\\u001b[0m             \\u001b[1;32mreturn\\u001b[0m \\u001b[0mbytes\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[0mresult\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\",\n        \"\\u001b[1;31mtimeout\\u001b[0m: timed out\"\n       ]\n      },\n      {\n       \"output_type\": \"stream\",\n       \"stream\": \"stdout\",\n       \"text\": [\n        \" <- 'ACQ:MOD:ACT?\\\\n' \\n\"\n       ]\n      }\n     ],\n     \"prompt_number\": 11\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"wav0 = ch0.read_waveform()\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [\n      {\n       \"output_type\": \"stream\",\n       \"stream\": \"stdout\",\n       \"text\": [\n        \" <- 'DAT:SOU?\\\\n' \\n\",\n        \" -> 'CH1\\\\n'\\n\",\n        \" <- 'DAT:SOU CH1\\\\n' \\n\",\n        \" <- 'DAT:ENC FAS\\\\n' \\n\",\n        \" <- 'WFMO:BYT_N?\\\\n' \\n\",\n        \" -> '2\\\\n'\"\n       ]\n      },\n      {\n       \"output_type\": \"stream\",\n       \"stream\": \"stdout\",\n       \"text\": [\n        \"\\n\",\n        \" <- 'WFMO:BN_F?\\\\n' \\n\",\n        \" -> 'RI\\\\n'\\n\",\n        \" <- 'WFMO:BYT_O?\\\\n' \\n\",\n        \" -> 'LSB\\\\n'\\n\",\n        \" <- 'CURV?\\\\n' \\n\",\n        \" <- 'CH1:SCALE?\\\\n' \\n\",\n        \" -> '100.0000E-3\\\\n'\\n\",\n        \" <- 'CH1:POS?\\\\n' \\n\",\n        \" -> '0.0000\\\\n'\\n\",\n        \" <- 'CH1:OFFS?\\\\n' \\n\",\n        \" -> '0.0000\\\\n'\\n\",\n        \" <- 'DAT:SOU CH1\\\\n' \\n\"\n       ]\n      }\n     ],\n     \"prompt_number\": 13\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"plot(wav0.magnitude)\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [\n      {\n       \"metadata\": {},\n       \"output_type\": \"pyout\",\n       \"prompt_number\": 15,\n       \"text\": [\n        \"[<matplotlib.lines.Line2D at 0x866bd30>]\"\n       ]\n      },\n      {\n       \"metadata\": {},\n       \"output_type\": \"display_data\",\n       \"png\": \"iVBORw0KGgoAAAANSUhEUgAAAZAAAAEACAYAAACd2SCPAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\\nAAALEgAACxIB0t1+/AAAIABJREFUeJztnXt4VcW9978JdxW5yL0hUCDkApREgVAVCBQRylEQPVV6\\n6mkLvo1QK1Wxam/S9xw9Xo49VFTM43uwHi+19VhKj1Z8QI3ghQQKag8GBSRivRLkEjRgSOb9Y5zs\\n2ZOZWbPWXnvvtXd+n+fJs/dea9bMrJlZ85vv/GZWchhjDARBEAThk9x0Z4AgCILITMiAEARBEIEg\\nA0IQBEEEggwIQRAEEQgyIARBEEQgyIAQBEEQgUjYgGzatAnFxcUoKCjAqlWrtGFuuukmjBgxAmed\\ndRZ27drVdnzRokUYOHAgxo0bFxe+sbER8+bNQ35+PubPn49jx44lmk2CIAgiZBI2IMuWLUNVVRU2\\nbtyIe++9Fw0NDXHna2trsXnzZmzbtg3Lly/H8uXL2859//vfx/r169vFuXr1auTn52P37t3Iy8vD\\n/fffn2g2CYIgiJBJyIAcOXIEADB16lQMGzYMs2bNQk1NTVyYmpoaXHLJJejbty8WLlyIurq6tnNT\\npkxBnz592sVbW1uLxYsXo1u3bli0aFG7OAmCIIj0k5AB2bp1K4qKitp+l5SUYMuWLXFhamtrUVJS\\n0va7f//+2Lt3r3O8RUVFqK2tTSSbBEEQRBJIuhOdMQb1bSk5OTme1xAEQRDRpnMiF0+cOBHXX399\\n2++dO3di9uzZcWHKy8vx5ptv4vzzzwcAHDhwACNGjPCMt66uDmVlZairq8PEiRPbhRk1apSnkiEI\\ngiDiGTlyJPbs2RNKXAkpkF69egHgK7Hq6+uxYcMGlJeXx4UpLy/Hk08+iYMHD+Kxxx5DcXGxZ7zl\\n5eVYs2YNmpqasGbNGkyePLldmL1797apm47+d/PNN6c9D1H5o7KgsqCysP+FOfBOeApr5cqVqKys\\nxMyZM7F06VL069cPVVVVqKqqAgBMmjQJ5557LiZMmIC77roLd955Z9u1CxcuxNlnn423334bQ4cO\\nxYMPPggAWLJkCfbv34/CwkK8//77uPLKKxPNJkEQBBEyCU1hAcC0adPiVlYBQGVlZdzv2267Dbfd\\ndlu7a3/3u99p4+zZsyfWrVuXaNYIgiCIJEI70bOAioqKdGchMlBZxKCyiEFlkRxyGGMZueQpJycH\\nGZp1giCItBFm30kKhCAIgggEGRCCIAgiEGRACIIgiECQASEIgiACQQaEIAiCCAQZEIIgCCIQZEAI\\ngiCIQJABIQiCIAJBBoQgCIIIBBkQgiAIIhBkQAiCIIhAkAEhCIIgAkEGhCCIOBgDtm5Ndy6ITIDe\\nxksQRBxvvgmMGcMNCZF90Nt4CYJIGi0t6c4BkSmQASEIIo5c6hUIR6ipEAQRR05OunNAZApkQAiC\\niIMUCOEKNRWCIOIgA0K4Qk2FIIg4aAqLcIUMCEEQcZACIVyhpkIQRBykQAhXyIAQBBGHMCC0kZDw\\nggwIQRBxkAEhXCEDQhBEHMJw0I50wgsyIARBxCEMSGtrevNBRB8yIARBxEEKhHCFDAhBEHGQAiFc\\nIQNCEIQWUiCEF2RACIKIgxQI4QoZEIIg4iAfCOEKGRCCIOIgBUK4QgaEIIg4SIEQrpABIQhCCykQ\\nwouEDcimTZtQXFyMgoICrFq1ShvmpptuwogRI3DWWWdh165dnteuWLECeXl5KCsrQ1lZGdavX59o\\nNgmCcISmsAhXEjYgy5YtQ1VVFTZu3Ih7770XDQ0Ncedra2uxefNmbNu2DcuXL8fy5cuN1x48eBAA\\nkJOTg2uvvRY7duzAjh07MHv27ESzSRCEIzSFRbiSkAE5cuQIAGDq1KkYNmwYZs2ahZqamrgwNTU1\\nuOSSS9C3b18sXLgQdXV1xmu3bNnSdh2jN7kRRFogBUK4kpAB2bp1K4qKitp+l5SUxBkBgCuQkpKS\\ntt/9+/fH3r17Pa9dtWoVJk+ejNtvvx2NjY2JZJMgCB+QAiFc6ZzsBBhj7dREjsd/rFmyZAl++ctf\\n4ujRo7j++utRVVUVN/UlWLFiRdv3iooKVFRUhJFlgujQkALJLqqrq1FdXZ2UuHNYAnNFR44cQUVF\\nBXbs2AEA+NGPfoTZs2dj7ty5bWFWrVqFkydP4pprrgEAjBw5Env37sXhw4cxffp067UA8Prrr2Pp\\n0qV4+eWX4zOek0PTXASRBP72N+BrXwN27gSkyQMiSwiz70xoCqtXr14A+Gqq+vp6bNiwAeXl5XFh\\nysvL8eSTT+LgwYN47LHHUFxcDADo3bu38doPP/wQAHDy5Ek89thj+OY3v5lINgmC8AEpEMKVhKew\\nVq5cicrKSjQ3N+Pqq69Gv379UFVVBQCorKzEpEmTcO6552LChAno27cvHnnkEeu1AHDDDTfgtdde\\nQ9euXTF16lQsWbIk0WwSBOEI+UAIVxKawkonNIVFEMnhtdeAsjJg+3b+SWQXkZnCIggi+yAFQrhC\\nBoQgCC3kAyG8IANCEEQcpEAIV8iAEAQRB63CIlwhA5JBvPMO8OW2GQDAhg3A0aP+42EMWLUK+Oij\\n8PLmwmuvAXv2xH5//jnwl7+4X79rF9+boOOPfwT++lfztfv2mc8zxq/3gjHgnnuAL1eZp4TGRuCu\\nu4DmZv573Trg5MnkpplsBfLyy6ktQ7+89Rbwv/+rP9fSAvzpT7Hf6jMp8/TTwCuvhJevJ5+M1Y2O\\n2lpg//7w0nOCZSgZnPXAjBjBmHzbAGMrVviP54sv+LVPPhle3lxYtoyxf//32O/Vq+Pvx4tu3czh\\nAca+8Q3ztcXF5msPHWLstNO8029u5nE88YR32LB49VWeZn09/z1sGGN79iQ3zdpanuYLLyQnfoCx\\nCy9MTtxhcOqp5rby7ruMDRkS+60+kzIAb3dhATD22Wf285Mnu8QTXt9JCiSDCGtKQcST6imK1tbE\\n0vRaeWgbmduudc1XOspNTTPRMvRDR10l76eteNXFiRPh5Eng8RaolEMGJIMI64FOl5O0pSU+zdyI\\ntD41X7Zw8mcqUOvKNa9hpNlRDYgNtfy9yigqbTxZZPntZRdhjTzT5SRVR29RGU1FWYGodZUKBUIG\\nxIxfBRKVNp4syIBkENmmQKLycJEC0adJBqQ9fhVIWG08qnVCBiSDCKvxRMUHErYBCRpflBVIOnwg\\ntIzXjFr+qTIg6XpmvSADkkFkmwIJe3446MMq8uT1cJICIdLlA4lqnZABySDIB5IcXEd3HcUHoqZN\\nxEiXD4QMCJEwusYTpEFFRYFExYC4KgtSIITf8icDQkQG8oHYScQHIn8mGi5MyAcSLdLlA4lqnZAB\\nySAy3QeiPnypXCNvK7tMUyC0jDd9qArEqy7CauMinajVCRmQDCLTfSBRncKKsgLR+UBoCit9tLby\\ncnEtI5rCIiIDKRA7iU5heZWHa7gwSYcCUdMmYqhKgAwIkTFkug8k2QokVct40+UDESPfVCmQqM23\\nRwF1GpN8IETGkG0KJJXYHuRMmcJKVScS1dFuFNAtarAR9kbCqNUJGZAMImwDkm4FEpUXzWWKEz1V\\n6ZMBMeNXgdBGQiIyZLoTnTYS+keuq1SlH9XOKgqodUA+ECJjyPQprKj7QEiB6NMmYqTbBxK1OiED\\nkkFkuhOdNhL6R7eBkDYSpo90K5Co1QkZkAwi2xQI+UC8IR9ItFDrgDYSEhkD+UCSQ5QVCPlAokW6\\nFUjU6oQMSAaR6QrEZEBS8VBkw6tMUrUPJaqdVRQgH0g8ZEAyiEz3gZj+l0JY+egoPhDaSJg+0q1A\\nolYnZEAyiGxTIGF3iNn+KpNU74SP2mg3CvjdSEg+ECIyZLoPJNkKJChRfpWJzgdCTvT0QVNY8ZAB\\nySCyTYGkKx8qpED0aUats4oC6Z7CilqdkAHJIHSdb5AGRT6QeKKsQMgHEi3SrUCiVidkQDKURFYE\\nRUWBpGNEr4MUiD7NqI12o4CrAhHHSYEQkSQRFUE+kHiirEDS4QNR0yZiuCqQsMuOnOhEqGSDAknl\\nFFY2vM6dNhKmH79tJWy/ZdTqJGEDsmnTJhQXF6OgoACrVq3ShrnpppswYsQInHXWWdi1a5fntY2N\\njZg3bx7y8/Mxf/58HDt2LNFsZg1iWWAmKpDWVr0CSYUhy6aNhLQKK33o6kA3OHH1kbgS1TpJ2IAs\\nW7YMVVVV2LhxI+699140NDTEna+trcXmzZuxbds2LF++HMuXLzdee/DgQQDA6tWrkZ+fj927dyMv\\nLw/3339/otnMGkRjTWQ6I12+B/XfsYY9ou4oGwnpZYrpI11tJap1kpABOXLkCABg6tSpGDZsGGbN\\nmoWampq4MDU1NbjkkkvQt29fLFy4EHV1dcZrt2zZAoAbncWLF6Nbt25YtGhRuzg7MqKTTGQ+Pirv\\nworKMt5MUyA0hZU+dG1At1mQFIgDW7duRVFRUdvvkpKSNiMgqK2tRUlJSdvv/v37Y+/evdZr5XNF\\nRUWora1NJJtZRRgKhJzo8URZgZATPVro2oBO+YbtA4mqE71zshNgjIEpd51jmGsQx9XwJh56CPju\\nd/Xnfvc7Xtjf/jZQXw9s2wZccok+7HvvAbffDkyfDhw+DPTrB3zyCbB7N1BZCfzpT0BREQ9XXw+s\\nWAE0NgI/+QnwrW8BTzwBXHklMGkS8PzzwMMPAz/4ATB6NHDffcDUqcB//RewciXwwAPA3/4GDBsG\\nfPwx8G//BnTqBNxxB/Av/8Lzc/w48P/+H/9eUAD06QOsXy/KiH+KTmTDBmDdOv7Zty+P94wz+H28\\n+CJw4YXARRfF7vXhhwHhhmpp4fe4ejXw61/zNE8/HTh0iIfp1Am44gp+7yrvvAPcfz/w+ef8vv77\\nv2P39f77QOfOQP/+wPDhwB//yMt2/36gWzdgyRKenpcC+fd/B559Frj2WmDOnNjxJUuAuXN5ejNm\\nAFVVsbK55x5g1Chg9mx+X0OGAO++G7v2978HLr00lu6qVUDPnvx3czNw9dXAqafyemltBZYv53kF\\nYg/x734H/OpXwBdfAC+9xOta5oYbgIoK4JlngB49gMsvB8aO5ef+/Gdg7Vqexg038Hi6deNl2KUL\\nD3PjjXxUO39+rIw+/BD413/l32+5hbetN97geZgwgdfjddfx+L7/fZ7/n/+cXzNwIK+PvXuBwkJe\\nL2ecweOaP59/bt3K8ztjBvCHP/BjL7/M296RI7ytnjgBlJTwuhw8OP6eW1v5PVx7LfDII8BrrwF3\\n3glccw0Pf+AAb0uCffuAZcv4PY8fz8tIsHYtr9t33+Xlt3Ilb/9PPgmccgrQuzf/3L+fl+OttwKf\\nfQb8z/8A3/sev+devYAf/YiXwY03AqedBvziF7w8zz8fmDIlPv+ffcaf0QMHeJsG+Od11/H4TjmF\\n31vfvvFtAQBOngSWLgV++UteT7//PfDlbHxch9/SwttX3778uZ44kT/3c+cCDQ3AtGlAcXEs/J49\\nPO2RI3nbBHjdvPYav5/nnuPt909/4s87AGzZwu931Chg0CBeZsuWAaWlvJ9S6y1hWAIcPnyYlZaW\\ntv2+6qqr2FNPPRUX5u6772a//vWv236PGDGCMcbYoUOHjNcuWLCAbd++nTHG2LZt29jFF1/cLm0A\\nDLiZ3Xwz/3vhhRfizufmMibu7r//m7F588z38fTTPGyvXvxz5Ej+CTC2cCH/HDcudmz7dsZeeol/\\nv/hi/nnHHTyu66/nad9yC2O//S0/d9NN/HPnTsaKihi76qpYXK+9xthf/hLLK2OMvfMOY0OH8mPn\\nnsvYtdfGwnfvzsN89BH//c//zPOQkxPL/7BhjJ13HmPl5Yx997tqucX+Lr2UsUcf5d9bW+PPib8H\\nH9SX2Y9/HAvz6af6awHGfvhD/fF9+3jcAGN79ujTkMMzxljXrrHfpaX889/+LXbsggv459lnx64f\\nPJixAQN4uctxMcbYwYOMnX46Y1VV/Pjvf8/LUZTx4cPx4R99lLHZsxkbNIiHveyy+PNyvufOjaV3\\nzz2xc4sWMfa97zF26qk8juJixnr2ZOzjj9vf9/LljD3wQKxO5fLIyYl979Qplg+AsZtvjt2PfE15\\nOWPDhzN25pnty2L58vZtX/f3jW8wtn59+3v+7DNeP4wxNnMmD9vc3L4O1XoFGJsxIz6uIUP48bFj\\nGevRg7ev//N/eFs+7bT212/fztjmzYxNnhyL/7TTYt83boxP95//uX3+d+5kbPTo+Hh37+aftbX8\\nmZSf95dean8vL7zA2P/9v/HHvv71WBpHj8af++1veV/x7W/r86XWn/wn+qVjx3gbV8+PHcvYD37A\\n47/66hfYzTffzICbWd++N7MEu/04EprC6tWrFwC+mqq+vh4bNmxAeXl5XJjy8nI8+eSTOHjwIB57\\n7DEUf2lie/fubby2vLwca9asQVNTE9asWYPJkycbcrACK1bwv4qKirgz8ghBnTpRYYx/njwZ/1sX\\nRo4TiI0M5LnRrl3j05OnHlpa+AhIjUeN25RfkQ/GgAEDgHPO4XmQ02SMfx892n7fslM7kWkZWxqm\\neOW0deXtFZerP+LkSR7GtFJGzkdzM1cDpjhbW/nIfdo0t3R1tLRwRdqjB08vL4+Pol3q24Q6B6+2\\nTYFoD7q0RBxedWG6Xj4ulJTrVJsIr5Kfz8tJxD1lCtC9u1u+TjnFLW3T9eKY+BTlIspU1669+g1d\\n/GpfoUvfK98ijVNO4e0XiJVb167AyJEVWLFiBYAV6NdvhXekPkh4CmvlypWorKxEc3Mzrr76avTr\\n1w9VX84pVFZWYtKkSTj33HMxYcIE9O3bF4888oj1WgBYsmQJvvOd76CwsBBnnnkmbr/99oTyqDpv\\nVUQF+OlE5U5H/t3ayh8I02ojcV495ye/Is85OXyaqblZn2aXLnw6zHYPXgbEpXO35dV0rrXVrXM0\\n5cWP36K1Ve/oFOfkDrdLl/Ydr6ClhcfTqZN3uiYDIvIi6i03l/95lb+tjFTjqLZNgWgjurRU35oJ\\n0/XycZFXXQfoMjgTeenUKVY2otxMb7c1veXAFd19yW1MNSBB7k0Xv/rc6tK3nVMXpXTqxL/n5HjH\\nHwYJG5Bp06a1rawSVFZWxv2+7bbbcNtttzldCwA9e/bEunXrEs1aG64KxI9z0qZAunSJj0s+J86r\\n5/zkV+RZPFCi4xNzt3I+PvvMfg9eI3nXUZDfc/LIKYgB8bNyqrXVXYF07tx+tZPovFpb+QMqFJ5X\\nuqbjomNsbubfO3VKTIGo92ZSIKJd6tJy/edeNgUiJlBM6ZviVzs4kRdhaEWanTqZl2qbFmi4ki4F\\novYVuvRt59R7Fn1LTo53/GHQIXaie43og0zjpEuByCPFnJx4A6JTIF737XXvLmWSqAJxLXcvBWJa\\nDWMyIDoF0rlzLC01jaAKRM23bPi9FIhL2wxTgXh1vDYFop7XGRDbtWpe5LIR5WYyIGq+/BoQLwWi\\n3pOuU/a6t2QrkNbWWPtNlQLpEAbEVYGoElz9rsYJuCkQmwExjeZ0eVHzrE5hqWl6jT5cfCDJVCBe\\n96iiMyBy/LpOUBgIVwWiTpvIaQgFIs7b8q2rf3FcqI5EFIh8P34MiHy/MmH4QNTzrgpEjU+ewhLG\\nOp0KRDeFZVIgarp+FIjJz2rKr5o3eQorN5cUSGh4KZBk+0BsU1imUYtLh2JTICIdr1GM11RQKhSI\\n34ddjtcrf14KRJ1yEaNeuUNRFYg4r0NdkCGuk9P0o0CS4QOxTWG5lqfuuHo+6BSWMGZyWcvl5pIv\\nv6NumwLRTWFFxQeiGs2M84FkAq4GxA9eCkTXmIMoEFueZWdsZ6UmU6lAbHm1GaZEDIgfH4hJgagd\\nnawIbApEnt5SUTsaOR05jkQViIxfH0giCuTkyfANSBgKRDWMQaawwlAgtmNq/F7PqMtzpU7byfXo\\nMohMlA6hQLymsIKOoAGzApHTC1OBmHwg6ugslQokSNnKo7ogDTwMBaJ2tGEoEF3nqbaFID6QVCkQ\\nr463uTn8KSz13lUfiKxAbFNYamfqB51h9atAdNOaNlXk9Yy6PFeq0ZT7B5dBZKJ0CAPiV4HIjdSr\\nchNVICYD4qJAZB+IGK3JcYShQJI5heXSOZrwo0C87k1MN508qVcg8qd8XkeyFIianlxmpn0g6lJi\\nmwLRxaujudldgeiWMtuuFegUiCi3qDjRTQpErUcXBSLiMg0ETPlVwwgjK9JNxRRWhzAgXgrE1Rkq\\nsDWo1tb4paByHCIf8nSTaTTn1weiUyCdOyeuQFxGLy7OPt3xMKawvDoNm4F0VSByWq4+kKAKRF3A\\nEeZGQtEeEvGBJEOBqPHpfCByuamIe0pkCkv3zMn3pPq2TAbEZuhNCiTIs2d6bmUlKfoAUiAJ4leB\\nqNfa4kv2Mt6cHPPcqosC8ZpHTacCCduJLuLRjVJto2EvH4g6hSXO69Lxo0BOnmy/LFi9n7Cd6F4K\\nxKuz8aNAEp3CUlfEmRSIOG+bLvJCvV426nJb9XKi+zEgXs+oqwLRrcgjBRIifhWITXaq8YmOSZ3C\\ncvWBmOKXK93UWMWITHREOh+I6b5l56QpDdtx1zDJUiBypy6wxaczIEEUiOjU5PzLeK3CUhWIHJ8u\\nbJBlvCYFYNsHosu3DvFqGJUwFYhpI6HJByLOh6lAxEY8cc7Vie5nCstLIQRRIHJ78eoDwqBDGBAv\\nBeL3XCoUiOm36kSXX4nhR4GI6YwoKJAgIySbAtERpgJxcXq7+kDU+NSwXvUDhONEF2UXZQViMiA6\\nBeKq3tQ45Dz4VSC6fsamisJSIAJ5ACn3NaRAEiQZPhB1lGVTIDYDohs16ZxtujzLPhDxsMnX2EYf\\nYuTj1UElU4GINIMoENnxLfBbjzYFIncGqgJJxImuUyCyE11tN6ZOUDeoUM/pfCCA3Zh6KZBEfSC6\\ncvNSIOJ5s01h6RSIzgCYsCkQ24BRJogCSdQHYmoHpEBCxEuB+PWByJ1fGBsJ1Y5UTdM0TWNTICId\\n0327TmGlQoGEZUBcwuvyplMgcoeiUyCm8vJyoouRtIsCcZ3mczUgcrqmfLsYkGQrEDEQkqf3RLmZ\\n/vuf+oyr6k1dnKDLgzqal+s9qj4Q1eipeSEFEgKJGJCgCkQnXU0KRG0MppGUmmfdXLqcZqoUiK1s\\nbYbJ1jm6LCeVP72ucTUgqVAgtpcpuioQ3X3Iaah5AOLTNeU7UQOSDB+IiwJRp+ZkNaCqC1P+5byp\\nCiRZPpAwX2VCCiRJeE1h+R1Bi443J4c/cOp8qc6JLsKKjl+OXx3p2hSIzgcir+aRr3HxgYShQLzK\\n1uT4tPlAvAyI6OgSMSByR5eTE44C0U0FJaJARPnZ7k01MqYOXKSrKwtXFejlRA9Dgag+EFmB6BB1\\nJcejTmHpZgR0+ZfzoFMg4nkP6gNR95h5KRDbvhc1H+QDSRLJUiBCScgGw6RAunSJdfRq/DYFIq7X\\n5Vn4QIBoKxDdPwzSTd2p18kG0VRHrgbEVI8iDvF/QMJSILp05Dj8KJDcXDcFovPpyKhtxJRHL1ym\\nsEz/V8U0IJOxKRBTfmwKRD1ny7+cB51RBMydsosCUaevvV5lYvpnW7p+xqRAyIAkiJcCCeoDkQ2I\\n/ACJzlketYgHSn0I/CoQ1Qcib7pKlw/ES4HoHgKv+X3R0XrlIywFItdPGD4QXTpyHH58IF4GRMSv\\nqjKdAjEZED9+KNsIXpSbyYAk6gMx5cemQNRziSgQwNzpu/hA1Olrr1eZmAyIzvjofCDJ3khIL1NE\\n8hSI/GDLI1w1/qAKRDYaOgViazxRUCA2AyI6Wq80xPSTvGxTh5cPxI8CMfmlTPdiUyDyXgc5LRGW\\nMe//P2IyHKYpLNd8m0hEgfj1gYi68OsDkeMNokBUBaMaEBcFok49kgLJUMLwgciV46VA1B2/4m25\\nOgPiV4HoXqYImDcSmu4tlT4QLwVieqBdFYipU5RxVSC6jYR+FIjXFI0ch7z4waRARDno2qF6b14G\\nxDaFFSUFIpCNqx8For6WJYgCURWMiwJRB6rqfqEgCkR9y7Yal0mByIY8mQqkQxiQMBSIKkXlypVH\\n+joFok6RqPHbFIhtFZZwogP+X6YoP5givO3+bXgpEN1D4FeBmPIhl6lXfKZ866awTArEayOhqwJR\\nl197+UBs9SC3M92nIFMUiDqFJSsQ2xJcNR75vVVRUSDys5CoAlHfdSc/L6RAQiRsH4j8kADeCkRM\\nYemc6HJHpevMTcZP5wPROdG9FIiXAUmmAhFxmzoF9X50o2+h7uR8uI5y5SlGWSGqU1jyZ6JOdD8K\\nRDYgLipZdNi6FWpAeE70RBSIrh7VtHVOdJsCEQZCTGOKMpOX2Kvl6nVPav3L15heUqoO9lQDolMg\\ntheeuvhAbFNYpEBCIhEFYmpsLj4Q0whXjd82haUaP91GQsDsRPdSIGFMYXl1bkF9IOr96JSMzoCo\\nHYZXvkX9yK9zDzqF5TXC1ikQOT617tVl36ZlneI+5E912i7VCsS0XFj3z8/UfAL+nOjqsyNvmFSf\\nf5salfPpdworqA9EpKMzrIk60UmBhEAiCsQ0heWiQExz7Gr8tiksmwKRVYdpGW+iCsRl9GILI9SX\\n7hqvVVjq/eg6P9mAyFMWfpRTKpfx6hSIHJ9uCsvFxyPuQ/cpiIoPRDeNKxP0VSZyXKoC8ZrC0vlA\\ngkxh+VUgXq8yCcOJTgokQbw6FNs5XaeeTgWi20gI+Fcgwm+TbAViMiDyQ2l6oF0ViAindmKu+Q5z\\nGa9X5yQMo5+NhGEZkKj4QLwUiG0joUnhiTjkTbWpViDyswy0n3r0UiC6+PwoENpImCQSmcLSPYwu\\nCkRuTMlSIC4bCVOhQGxlK+5dd424zlWBuE5huSoQnQFJhQIJspHQhUQUiJ9OxmZAhOEVPiVdOLUe\\n/SgQU35kAyKeC/lfLXgpEPWYOrCTzydLgahthxRIRPA7hSWH1c0neykQdY+FvIw3UQUi51lVIKJz\\ncGk8YfpAbGVrMiBeU1g6BeI1heVXgegMvB8Fosu3iw8kyEZCF1KlQGxTWC4KRPcMyAR9lQkQb0D8\\nKBDdFJbbPPNOAAAgAElEQVRfBZKoD0TFrwIxbSQkBZIgfhWIbCyirEBUH4gcJ2BvPIkqEHVkZcKm\\nQPz6QKKiQMJYxut3I6ELqdqJHvYUlk2B+NlIKOKS31ItzvtVIHK+5LYKuCsQdcpNNQg6I6DmybQP\\nRFYgIo10bCTsEDvRvUakagHLDV/3MIpO389GwqA+ELXxy8dVBSLnWZb/OhLdSKj6hEy4KBDTA+2i\\nQMTKKfFdXOtHOflVIOK8qTMzpaPGIfKeKh+IuldIJp1OdLXM5A7Rz0ZCQK9A5GfRK/+mewqykVB+\\nHYo473cjoYsC0Q3EhGGhKawQ8KtAvAyI6Hi9nOhhKBC18ct5Vn0gcp51zl75PhN9lYn6YJgI6gMR\\nHa0c3s8yXpeHJlMUSJhTWFFUIOoIX/7uZyMhYHai+1Ug4u3ZuvwlspHQrwJx8YHofCi0kTBE/PpA\\nXAyIHwUibyTUjb7UTlztcGwGRKdA5P0M8rXyfaq7rU2NzOV4Mnwg6sjbNBJPZArLpkB0ZSOrh6AG\\nRI7Dz0ZCF1w2Etoc0a7YRuviXxb4caKrHa2pnXptJBTpeykQL5Uo8mRStDYfiKpAkrWMV/QppoEY\\nbSQMEdtIV3fcZQorTAWijj5VBWJqrCYfiEmByN9dl/G6HA+qQLyc6HJZid/qtFGyFIiubGT14OIb\\nkvMup+mqQISRDXMZb7IViE4BqOmo92NTynJdmHwgqor38oGYpp9k5Ne566awXBSI6gMxKRDZUKl5\\nMhkQ0WZ1U61yXsgH4sH8+cCRI+2Piwdlxgzgww/597IyYNAg4J13gK98JRa2X7/4a7/4IvZd9zCu\\nXAk0NQHnnst/d+8O7NwJTJ8O/P3vQLduvPLWruXnT5wATj0VWL8eGDgwPq2nngIOHeLff/YzYPRo\\n4LnnYvfw4YfAp5/GRqnHjwPnnAMUFbVXIOLhOnECOP103ng+/5znKycHyM+PpSsa3wsv8O+/+IW+\\nfP/3f3kZigehZ09g7lzgf/4nFmbx4th3Ue7CR9PczMtD5dprgaFDY2F/8ANeRp9/zvP/5pvxD893\\nvsN/y6O63FxeV6L+ROf10UfAggXmqaYpU3gbGD48dl3nzjyukyeBrl2Bn/wkVud33cX/qquByy7j\\n50UdAUBFBc/XhRcCF1zQPr1XXuF1cP75MUPYtStPr0uXWD7/8z+BK66IXffGG8BLLwElJbFj+flA\\nfX37NIBYfk+ciJW9TJcu7Tvv6dP5MVNHBfCyESN7xoCqKl7/o0fHwsiqR11lJupg+nTgk0/i4x40\\niNfXFVcA773H20BNDT/XtSv/u+MO4ODB9savWzd+rytXAnv28GPnnBNrQ5WV/FhLS3x9vf02r4sh\\nQ2Jl2dgYH3dLC38eu3QBHnkk1oeI8jh2DJg6Nf6aX/2K9wuCvDxehxMnAuPH87o866z4NGSl9uij\\nwAcf8DLu25eXYffu0NLczM/dcgu/X4Bfl5cH7N8fP0vS0hJ7Rt9+Wx9fYFiGAoABjHXpwtjzz7f/\\n++tf+d/06Yzxoo3/u+kmHm72bMaKihj7p39irKaGnxswgLE33mDs/PPjr+nUiYcVaWzYwI/feSdj\\nmzbxYy+/zNj27YyNH89Y376MPfooYy+9xFhDAz+/axfP/65djF1zDb/+O99h7LHHGOvTJz69YcMY\\ne+UVxl58kbHy8vhzhYWMVVQwdvAg/33llYzt28fY66/z+3jtNZ5ObS1P9+tf5+FWrOCf//RPjG3b\\nxtgLLzC2ezcP88orjFVX8/NduzJ2zz38+9lnx+65Rw/GFixg7IYbGHv1VR4PwNizzzK2dStj/fsz\\nNmUK/z5oED/3t7/xcnnvPcZ27mSsspIff/xxHnbduvb1s2gRL6P6esY2b+Zp19Ux1rkz/wMYe+IJ\\nXu6ffsrYAw8w9pWvMDZ0KL+P559n7Lnn4uN8/XWej6FD26e3cCH/fPBBnqa434kT+fEZM/jnH//I\\nWFMTL7cXXoil9S//wtgFFzD29ts8/upqxvbs4b+rqxm74w7GZs5k7LTTGDt6lLEPP+TXNTYy9sMf\\nMnb33YyNG8fT+N73GHvrLcaqqvjvr30tViaHDsXK5NVXGfvWt2L38PjjvAyefz7WtsTfyy8zdvIk\\nYxMmxI7dfTcPO2YMb9dy+Nxc/nn77fxZ2LqVtzc5zJw5/Pobb4zPQ7dujF10EW8nF13E71+UE8DY\\nqFGM7djBy+fddxn7139tXx8PPMDYsWO8nP7hH/ixQ4d4uxdhfv97XgarVumf8W7dGLv4YsZuvZWx\\nn/yEsUsv5W1o82bG8vN5mN/+lrHvfz/+ur/9LfZ9717eXuV0Fy7k96ZL8+67eZ2/+iqvQ/nc7bcz\\n9sEHvC5ychj75jcZW7s2PsyMGbFyuugixh5+mPcnurT27eNha2v579NP5+X1yiuMDRzIWO/evL1O\\nncr7ol/8QlwbXref8QqkSxc+sjExeLD++Nix/LpHH+Wjl8GDgUmT+Llhw4Bx44ARI+Kv6dSJj5hE\\netu28c+uXfmoVvDGG9zq9+wJnH12bKQr57OwEDjjDP59xAgermfPmBoR6X396/y7bumj6kQX6chM\\nnMg/BwzgnxMmxMLLo6FRo+KvGzIkFt/AgbG8i6mekhJg8mTgoYf48a9/nec/N5ePDCdMiOVt0CBe\\n3oLevfnnuefy8heK4rTT+MhO5KewkH8fNix2bU5ObERcWMjrCeD5EdNDoswOHIi/p7w8PrLr04eP\\ndmXECDwvj6cn0hw6FNi6lR8X5da9O1cdMseOAVu28Hvp1g2YNi12rqCAl9n69TEFMmgQ/xNxCsUG\\n8HY4enRMWQvVMHYsL7vevWP5Ky6OpdPczNXY9OnAq6/G56+8vP1GwnPP5aq8Tx+uAmSEcpg8OVbG\\nqpLMy+Npvf9+fB6EAjlxAujVK74sAF5GpaWx32eeiXaMH8/VyKmnxpSqqp4GDuT5M3H66byNiCmu\\ns87iyh3gbRXgz93f/x5/nVB8+fn82Rw9Gti4MXa+S5dYO1UpK+P1XVDQPsyZZ/J+5owzYtOWot0J\\nNTV4MC9TMcV5yik8Th15efHPPGOxNiVMhbxYprzcXFZByXgfiNf8sOm8OC6WU6prqIH2863qAyi+\\nq9JaXsVjc4DqNkyp8ahhBSLPOie6Dvl+dfGpyHmXy1B2/srp6jY02spHXGP6f9+mehMGRI1XzFnL\\n96Wma7tn8SCb8mo6L+dX+HV06cjOYC8fgFqmtjrWLd/WhZXbmi4dU9uzlaeaPzkPso9HRa1vXRg5\\nXdP96+5JRd7JLl8v5129XsQrxy+Xj2mFmZoXW3mJtqBryyKMbtGNjHqNmCqU/8GanJZtmjIoGW9A\\nvDpC0wMvP2i616zrru3cWd9AdJWva7S2POjm622NUeRZ18nb0nJ1yKr5k4/JHYPOkJjOCVRjo/NT\\nmMotJyf2IOhWtOk6Ca84AXcDYio/YQRMgwbZGWxqL2qa4lN0Mrq0TQbE1CmaBkDqm3N1dWcqTzUP\\n6iIBFbW+vZ49dcBhS18Xj65e1GdPRi0r1cDbDIguDV2a8tJkOU05rKn81HwKZN+gbKDURT9hkvEG\\nxAsXBaKOXNURiKBzZ/MoRkZutLYOWx7h6JaG2hSIvNpEDavDlFcT8moZVYHIIyOdsTCdEySqQHSd\\nvYjHRYHoBh0mAyF+h6lAdB2L3EGp5W4apar51SlDgW60rj4DuniDKhDbCFqtb12YsBWI+izaFIgu\\nftnAuioQU1vKyYktE7bdk27jsS6sQJ45kaewIqlAGhsbMW/ePOTn52P+/Pk4ZpgU3LRpE4qLi1FQ\\nUIBVq1Z5Xl9fX48ePXqgrKwMZWVlWLp0adAsAnBTIKoBEegMSJgKRH1lg+kh1uVF5wOx4TJak5E7\\numQqEJMBsdWbrrMXu351HY8cxkRYCsRkQGyDirAUiNwh2fKpS0f3v0MAe3nq2oe8uz6sKSz5nuRr\\ndapKxaT8bApEF7+rAXFRIHKctnryUiAqOgMi90WRMiCrV69Gfn4+du/ejby8PNx///3acMuWLUNV\\nVRU2btyIe++9FwcPHvS8ftSoUdixYwd27NiB++67L2gWAQTzgQi8fCDpVCCJ+kC8sCkQ3UhX/kyX\\nAlENiGl0pyMVCkR0QrrpCpsCsdWxqwLRhVefATVPal5dFIC6jDfdCkSezkm1AjENnMQ5m4EQ5Zeo\\nAZGd6JEyILW1tVi8eDG6deuGRYsWoUYs3pY48uUykqlTp2LYsGGYNWsWtmzZ4nx9GLgokKj4QGwK\\nRI0nqA/EtUEy5qZABOqCADlPNgWSm+tPgZh8IMKJrjPwapy69MJUIKa2ZOpQw1IgNh+ILryqwtU8\\nqeFd4nTxgbgoEJMq8esDMT2LLgrEVD6uCkQ3UJDzZZuisrUXE6INiWdKnsKKnALZunUrir5cE1dU\\nVITa2lprGAAoKSlpMyC26/ft24fS0lJUVlbi9ddfD5pFAOEqkCj5QESexV8yFYjOgNgavqsCEVNY\\nQZ3oUVQgah7k86YOVYyS1TRVA5JKBSLP18v59IrTRYHYFosIvBbHyGG8FIjuWfQzhRVUgdjOJVuB\\nqE50YUzCxhrleeedh4/UBeIAbrnlFjDdMM6BnC9rxXT9kCFD8N5776FPnz545plncPnll+ONN94I\\nlBbgPrpwWcbrV4G4LuN1USAmH4i4Ppk+EN0Ulq3h66a11DDiM5EpLDWMOn0UNR+Iac67U6f4tx+o\\n5Z4OBaKbwkqlAvFjQIIoEJcprEQViO2ciwLxWsarYlrGq053h4nVgGzYsMF47qGHHkJdXR3KyspQ\\nV1eHiWLHmsTEiRNx/fXXt/3euXMnZs+e3XZOd33Xrl3RtWtXAMCcOXPws5/9DHv27MEodacbAGAF\\njh8HVqwAKioqUFFR0S5EmApE7hzVOGREg/PjRA+qQHT50pEOBWJa9aQqkKBOdC8FoqYbxIC4KhCh\\nImwGxFWBqIY32T4Q0VmpeQKSo0D8+kBM16ZKgajlIzay6ghLgXid12HygRw9Wg2gGvfc4x6XK4Gn\\nsMrLy7FmzRo0NTVhzZo1mKzZEtqrVy8AfCVWfX09NmzYgPIvt0Oarm9oaEDLl+Z++/btaGpqMhgP\\nAFiB7t1XYMWKFVrjAbgrEJd5WFcFIjoEryksuYMIokBcHgKvvJqQ5/KDKhATsjET87WmMCp+fCBq\\nGFu+TKN8VYHY7tvmA7F1qGrdq8beRYHk5ARXIK5TWOn2gZjCJEuBqBsJxe9UKBCv8zrkfSCyAenc\\nuQJduqzANdesALDCPUIHAhuQJUuWYP/+/SgsLMT777+PK6+8EgDwwQcfYO7cuW3hVq5cicrKSsyc\\nORNLly5Fvy/ffGe6ftOmTRg/fjxKS0tx6623oqqqKpH7c1IgQLg+EFOjNcXvokBsMt/2EJjy6jVF\\nEIYCMSErk2T6QHRhTLgqENt923wgtg5VrXu1Y3Txgagdjl8FYgrn1QbVOJPtA9EpTJsBCEuByOmk\\nwgcSRIEIVB+IXCdhE9it0rNnT6xbt67d8SFDhuDpp59u+z1t2jTU1dU5X79gwQIsWLAgaLba4aJA\\ngGAGxKZA/DrRdQrEq/NTfSg2UqVAXPKiGpBkLePVhTFhMhAunbi4zmsZb6IKxDZSlzsKWz5NCsQU\\nzjaFJY94Bcl4lYkJLx+Iug/CrwJRy0dMXSVTgYiyCaJABKoPRFaFYZOEKKNFmAZEbWw2BWJ7rYUa\\nv6nTdR3JuygQr6kNFdn46RSIrSP10+hlBSJ3LLZ6M01hyZ9yftQwOsJQIF5O9KAKxOa/MikQL+Ut\\nf7cZpqBTWKn0gXh12mEpEGHIwzAgYSsQVZmphihZCiTrDYjrFJbLPKwfBSIvszWhdhC6eAQ2R7PL\\nqN9rakPFS4HYOhM/Ix1RVoB+FK5iUiByfKbfiSgQPxsJTUbC5kRPlgKxKTLboEI31elnCitRBeLS\\nhlwGRWH5QAC7AXFx6PtR8n4ViFpPYkBCCiRBwp7CclUgLqMH0xyrGr/L9WErEC8fSJgKRBgQ2ZFr\\nGxVGWYHYfCC2ZbxBNxKq0ytqmzQZRPmcTf3qwqvIYeR/p5wqH0gQBSK3F1cFYvOBuOZF/u71KhPT\\nBmdbXsV3+V158n+JDJusNyCuCiTIMl6bApHftGlCHbWocXuNnoMs4xXhvLbx2Hai2+7NrwKRV0+5\\nGhDT69zlT6941PNhKhCTD8T2xgOdAvFSqPIxkwLRGUTTFJlObajHvIyN+m+BgyoQPz6QIArEjwFR\\n+wrd/xkPokBsBkKcd+301XsT7VA2VKRAAhCmAlFH17aH1Ja2Gr8uHlXteF3vqkBcG5FtI6EtHr8N\\nVSgQ9TULLlNYrgrEVjbytARgngILwwdiut6kQNSpE921cljbSy7l8KoBNg0UgPY+EN1uZtWI6YyU\\njMvr3MNwootzXpt6vZSVqgR1rwQJokD8pO2FTpkJBSLiIQMSgGQqEK9pAlcDootH/K9s1+tdFUgY\\nU1i2ePxKZaFA1I7JZvhNq7DkTzm87RUOIq5ENxK6KBDT9aoCMSkE2+hf7rx1ccjhVWNoq2dVgcid\\np9p+xdSO3AZc2oKfKSxdGFsapiksNYxL/DYD4vVcqOdM9WOblbBhMiByvmgKKwCuCsQ2PSD/dmkE\\nrgbElocuXeLT8nKih61AbE50Wzx+FYgssV1W/AgFYlqgoOswbWWjGhCvKSwXBWJrSy4KRO18XRSI\\nOoVlG9yoxtBWz2qd2DpPYUC8FEgUNhKqYXTYVmF5hbXlV07Ty6i5dvqkQJJEOhSIK2qjk9PrSApE\\ndryq6ejC68om3QpEniqxGbYgCsRlI6FJgah1IRsB2wDGVPe6ztOvAonCyxTVMLb41bK0TeMlqkBc\\nz5vCA3oF4ve5dCXrDYhrBbkYEFcF4ortAVAViC1/LqMLv3lNpQ9EjA51HZguvFo24rj8Kcfjx4B4\\nKRDbfdt8ILYOVRgfU9iwfSCqgtPlTfdcpFKBhOUD8aNATHWv1oOuPYWtQPwO+EzlJcdDCiQAyVQg\\n4iEMS4HI+FUgXnlwuV+ZdCgQXTq6+NOpQGz37fUyRV38Ik6dE922yELNnzAgrj4Qr4GQzoCEpUBS\\n9TJFMQJ3VSCmQYmsBOVVgDrSpUBM5UUKJEFcK8hlHlY30k9kbtHWGaudpG0HbjIUSFAfiN/yyM3V\\nr1P3UiAmA6KrnzB9ILZRrNdGQl38Ik7dFJbANtCQOzfdMl6TD8RrNKwbaJgMiKpA5DYQVIG4tCHb\\n7IHAjwJRDYPOiW5q3y7PFymQDCRsBaKbU06WAvEaMSWiQLwIqkD8lodfBRJkCsuPAUlEgXhtJNTF\\nL+IU/hPdPdjK3OQDMdW3mNLzGg3rngvTFJYuD7b2FvZGQhthKhBxX7b8ek2nqWmmUoG4lJdfst6A\\nuFaQqw8kTAVik+Dqa1PUNOXrk61AdB17WApE9oHoHjBdeNv0nt8pLL/7QFwUSBAfiOjkdCSiQHT3\\nY1IgXvVsKstkKBA/PhAbLgpELkPdcRcFYvondLr45DjDUiBe95aM6SugAxiQbFEguiWuyVQg8ohN\\nN7WRbAXiNYXlR4Ek4kT3q0AS8YGou5sFtoGGXB8urzKx+UB0x/wokC5d2q+oC1uByNeEpUC8prBc\\nFIjJ+OvSkeNMtgIRcSRj+groAAbEtYJc5mHDViC20YHNB6LLe9gKxHRN2AokN1fvA7HFH6YPRF1Z\\nE4YCMRkJ0/VeCsSljYatQEw+kFQpEJf27NLO/CgQ06DEjwLRoWubtvYgn0/UByLSSJYCScK/WY8W\\n2aJA1DTl65OhQEzXZKICsT3c6oOs1kVYPhAvFSH7QPxeC/jzgbgoEJMPxJYHeRmvrZ0ksgpLvsZV\\ngXj9d1BXBSJ8Ry73pMajKlOvqSWbARbx6fKqQ322woQUiEaqC9LpA7HN86t5DluB2JaPhq1AwvKB\\nBF3Gq3aUJgd2oj4QEcamQLymsFKpQEx1r2urfhWI6XoZL+OQm+tvCitVPhBXvMrHplBsU6SmtEiB\\nBMTLwmeKAknUB+LXgMirsDJdgXg9XDbC8oGIMMlSIJ06hatAdAMrU1n6VSAqQQyIqwKRyzbdPhBd\\nmn6n1eR8eR2T0yIFEhDbSFY+n04FYjIgYSoQFZc5W12nnG4FIhSFV72q4U2INE3loXbetnyJfyNq\\nMyDJUiDCgHgpa50CsSlNte4TVSCuS0lN4eR/o+sVl/BHef13UFNnrZvCClOBuAxsbHWuy2uQtBKh\\nwxgQ16ks3Tn5d5gGxDbKsDnRdfP2YTYQr9GMnLaK37zk5sac6H4UiKlj0tWPbRTsmleTwpHPy8ZQ\\nh2n0mpvrpkCCTGGp6dmmsLx8IKk0IF714trOhAJJ9kZCvwbES6nbFFwQBZKsKayMNiCjRwPjxtnD\\nnHIK/zzzTP5ZVMQ/xZSEON+9O/88/XRg/Hj+Xa2Unj2B006LP9arVywOU9om1I7h1FNj5wYMaJ+W\\nyMPYsfx7t27mfHnlRZSDjrIyYMyY2O8ePWLfRZpdu/LPUaPa50/kpaCAl6dKfn7se04OsG4d0NDA\\ny0HEL+pD5dRTgYEDeToypg6+Z8/49MT9ifofOJB/etWhbapTkJsLPP44cPSo+byu8znlFOCVV4BP\\nPuG/e/WKP/+Vr/BPuR7U/PXtC3zxRSyMOD5iRHz4nj1525LrxTRdpR4T1wpE3Yu2IOdBpK/m+cwz\\nY+3XhtyZy22ssBDo04f/ibai0qUL7xsmTeL3tm8fsHt3fF7y8mLfxfGRI+PjGTaMf8rPZ69e+noo\\nLTXfS1lZ+2NyWyguBr72Nf5dPJc6AyzClJXxZ0tm7Nj4ewLi1VqyFAhYhgKANTYy9tln9nCtrYx9\\n8gljx44x9vHHjDU2Mvb55/Hn332XsZYW/vvECcZOnuTfV60SkxL878QJxpqb4+OX45I5eJCxQ4fs\\nefvjH3m8b7zBfx86xK/7+GOezokTsbCzZ/OwR44w1tTE2Acf8E+Rh9ZWe1otLYwdOMC/NzXF7lfH\\nsWO8nBjj18hhP/uMsQ8/jP1ubY3lg7H48mtq4vnVIcrtxhv5ff30p4yNHs1YWRm/fxPiOrXc9+/n\\n8cye3T78yZOMNTTEjp08yfP58cf8uygXXV3K5Waqa0HXroxdeCFjVVX68716tc8fY7E2+MEH7dMQ\\ndWsrk48/5vfzzjuMffFF/PEvvmDs009jx5qb+TE5nZUredmNGxdr65ddxj/FvTMWK7fPP+d1K7e5\\nTz7h5/ft422kpYWx+vr27VJuWzIHDvDjR4/yuNTykev9yBF9OZ04weM/fJjfY3MzY9u38/soKIgP\\nf+JE/POpltWhQ4wdP86/v/suj+P662Ppvv8+T6uhgR9rbo5vY4Kmpvg2JrjgAsZOOYV/b2zkccnP\\n5be+xVhOTiz88eM8nk8+4eWrluHnn/OyEwCMdenCvw8YwNj06bFwYXb7Ge1E9xp1A3wE1b8//y6P\\n8OXz8ghVjKbEORn5nEA3GgH4aMwlb0BsdNC7tzmsGE2IkePgwd55kMnNBfr1499No3uBXE7iGsEp\\np8SP1nNy4uOTy6h7d3NaIs9ipNunT2ykJI9yTdep92xSCCLcGWfEh+3UKZaOuEddOcrl5lXOYipK\\nVRDyedP8taqS1PzbykSc++pX9cf79Ikd0/1PC3kaTD3msowXiD1jw4fHjokRvIzuGQTi25lOXcr1\\nrqsHcUx9RsV9yGUgwslh1bKSn0V5OlCkM2RI+/uR25hAtH/1OZLbgq4fU9uKUFuinFV0ZSKmROW4\\nXPoKP2T0FFaySZrsU+J3mZ/0O8eaKchSPZG5Wi9fVypQHdm688mai04EnQ/AZcouE/Dy2aUqDl2c\\nXj6QRNuK6DPIB5Imkv3w2JyjKn6XCWYKsrMwkbnaKHR4ubnxS2l159Np4EzoBjJRKM8w8DNIS2Yc\\nuji9VmEl2lZS4QOJYHOODqkyIKRAwlMg6ezwXBRIFA2IToFEQdGFQZQViC2+MBWI10rERMjw5pFc\\noqRAstWAdDQFEsUpLFcfSCYSZQXitbw8LINFGwnTBPlAkk+2+UDEnhbT+SiO6LPZBxJGu0iGAvEy\\nEGG2FVIgaSJKCiRbfSCyEU1k1BWFEbNQIKZ7iLoCyUYfSBidfzIUiNdgKdG24vIKmjAgA2IhSgYk\\nWxWI/IAnokCi0OFluhOdprDscaRSgSTaVuRryYmeJsiJnnyySYFk0zJeP4ObKBOmEz2VCiTRtuK6\\nfydRMrx5JJdU+UBIgcSMR6JO9HR2eNmkQLze/ZUpdFQFor7DjBRIGoiSAukIPpCOsIw3ygpEzlu2\\nGBBSIKRA0gb5QJJPWAokCiP7TFcgrm9+zSTCVCBhlgUpkA5AlBRIthqQsJbxCtJZTpmuQOQpLHkT\\nWiYThgJJhhpLtgKR75cUSJogBZJ8wtpIKEjnVF+mKxDdP94iBZIcOrwCaWxsxLx585Cfn4/58+fj\\n2LFj2nCbNm1CcXExCgoKsGrVqrbjTzzxBMaMGYNOnTph+/btcdfcfffdKCgoQElJCV566aWgWUwY\\n2kiYfMJWIOnEZSNhFO/PNkrPdAMShcUVOjq8D2T16tXIz8/H7t27kZeXh/vvv18bbtmyZaiqqsLG\\njRtx7733oqGhAQAwbtw4rF27FlOnTo0L/8knn+C+++7Dc889h9WrV+Pqq68OmsWEiZIC6QhO9DBG\\nSuk0tC4bCaPWkQH6VUbZ4kSPwhsKdHR4BVJbW4vFixejW7duWLRoEWpqatqFOXLkCABg6tSpGDZs\\nGGbNmtUWrqioCKNHj253TU1NDWbPno38/HxMmzYNjDE0NjYGzWZCJPshIh9IeBsJBek0tJ068X+d\\narqHqL/KRM6baG/ZYkCipvy8OvVE24q6kTByCmTr1q0o+vL/LxYVFaG2ttYaBgBKSkqwZcsWa7y1\\ntbUoLi5u+11YWKiNOxUkezMV+UCyT4HIn7rzUevIAH0nmy2KN1OnsBJtK6l6lYn1PxKed955+Oij\\njyEtr98AAAvPSURBVNodv+WWW8CS9KTq4s0xDINWrFjR9r2iogIVFRWh5iXZewv8xJ+tBiSsZbyC\\nKBiQTHWiy/nOlvaWqQokzCmsw4erUVtbDam7DA2rAdmwYYPx3EMPPYS6ujqUlZWhrq4OEydObBdm\\n4sSJuP7669t+79y5E7Nnz7ZmqLy8HBs3bmz7vWvXLm3cQLwBSQapmMJybdjZMiJUCWsjoSDdy3iB\\n7HCiZ5sBCcNwZ9IyXjmv/ftXYMyYijYD8qtf/Sp4xAqBi7W8vBxr1qxBU1MT1qxZg8mTJ7cL0+vL\\nfw69adMm1NfXY8OGDSgvL28XTlYdkyZNwrPPPov9+/ejuroaubm56Kn+k+QUkQoD4tqws+WBVglb\\ngaR7GS+QuQokGw1IVH04yVYgqg8kck70JUuWYP/+/SgsLMT777+PK6+8EgDwwQcfYO7cuW3hVq5c\\nicrKSsycORNLly5Fvy//u/zatWsxdOhQbNmyBXPnzsWcOXMAAAMHDsSSJUswY8YMLF26FL/5zW8S\\nub+EIAWSfEiBpB/dqzqytb1FhWxZxmudwrLRs2dPrFu3rt3xIUOG4Omnn277PW3aNNTV1bULd9FF\\nF+Giiy7Sxr1s2TIsW7YsaNZCI9lLAP2MDLJlRKgil3G2+EAybRmvbqCUre0tKnT4ZbwdgSgpkGx9\\noOXRb7YoENsy3igqEDIgqSdbFAgZEAvkA0k+pECiSba2t6jQ4X0gHYEoKZBsnZMOW4GkeyOh/Kk7\\nH0UFoiNb21tUIAXSAUjFRsJMGZEmC1Ig0YQUSHvCLBPygXQAUrGRkHwg/DOsV5lE3QdCBoQAvNtC\\nmAokkq8y6QhEyQeSrVMK9CqTaEIGpD1h/0Mpr1eZhKVAkjlwIQNiIUoGJFsfaNpIGE2ytb1FBXKi\\ndwDIiZ58aCNhNMnW9hYVyIneAaCNhMmHnOjRJFvbW1QgJ3oHIEoKJFsfaNpIGE2ytb1FBVIgHYDe\\nvfnnjBmA9G9NQqNXL2DYMLew//APwJQp4ech3Ygy7tkTGDQIGDgweFxDhwJlZeHkKwh9+vBP3f8W\\nB4AhQ4D+/VOXH1cGD+afw4fHjn3jG2nJSlLIzQUKChKPZ8yYxOMQDBgQK3cd/fvz9hKUKVOAr32N\\nfx88mKeXDHJYsv6xR5LJyclJ2v8kkZGTiOqbPTMdxsIp23T/F710px8GYdVFlMjGe/LC1hbD7DsD\\nv0yxo9DRGl46CKuM011X6U4/DLLhHlSy8Z68SNU90xQWQRAEEQgyIARBEEQgyIAQBEEQgSADQhAE\\nQQSCDAhBEAQRCDIgBEEQRCDIgBAEQRCBIANCEARBBIIMCEEQBBEIMiAEQRBEIMiAEARBEIEgA0IQ\\nBEEEggwIQRAEEQgyIARBEEQgyIAQBEEQgSADQhAEQQSCDAhBEAQRCDIgBEEQRCDIgBAEQRCBIANC\\nEARBBIIMCEEQBBEIMiAEQRBEIAIbkMbGRsybNw/5+fmYP38+jh07pg23adMmFBcXo6CgAKtWrWo7\\n/sQTT2DMmDHo1KkTtm/f3na8vr4ePXr0QFlZGcrKyrB06dKgWSQIgiCSSGADsnr1auTn52P37t3I\\ny8vD/fffrw23bNkyVFVVYePGjbj33nvR0NAAABg3bhzWrl2LqVOntrtm1KhR2LFjB3bs2IH77rsv\\naBY7DNXV1enOQmSgsohBZRGDyiI5BDYgtbW1WLx4Mbp164ZFixahpqamXZgjR44AAKZOnYphw4Zh\\n1qxZbeGKioowevTooMkTEvRwxKCyiEFlEYPKIjkENiBbt25FUVERAG4MamtrrWEAoKSkBFu2bPGM\\ne9++fSgtLUVlZSVef/31oFkkCIIgkkhn28nzzjsPH330Ubvjt9xyCxhjScnQkCFD8N5776FPnz54\\n5plncPnll+ONN95ISloEQRBEArCALFiwgG3fvp0xxti2bdvYxRdf3C7M4cOHWWlpadvvq666ij31\\n1FNxYSoqKthf//pXYzplZWVs9+7d7Y6PHDmSAaA/+qM/+qM/H38jR44M2u23w6pAbJSXl2PNmjW4\\n4447sGbNGkyePLldmF69egHgK7Hy8/OxYcMG3Hzzze3CMUnNNDQ0oE+fPm2rs5qamjBq1Kh21+zZ\\nsydo1gmCIIgQCOwDWbJkCfbv34/CwkK8//77uPLKKwEAH3zwAebOndsWbuXKlaisrMTMmTOxdOlS\\n9OvXDwCwdu1aDB06FFu2bMHcuXMxZ84cAMCLL76I8ePHo7S0FLfeeiuqqqoSuT+CIAgiSeQwliRn\\nBkEQBJHVZOROdNPmxGzlvffew/Tp0zFmzBhUVFTgscceA2DfzHn33XejoKAAJSUleOmll9KV9aTQ\\n0tKCsrIyXHDBBQA6bjkAwGeffYbvfve7GD16NEpKSlBTU9Mhy+OBBx7A2WefjbPOOgs//vGPAXSc\\ndrFo0SIMHDgQ48aNazsW5N7r6upw5plnYsSIEfjZz37mlnho3pQUUlpayl588UVWX1/PCgsL2YED\\nB9KdpaTy4Ycfsh07djDGGDtw4AD76le/yo4ePcpuv/12dtVVV7Hjx4+zH/7wh+zOO+9kjDH28ccf\\ns8LCQvbuu++y6upqVlZWls7sh85dd93Fvv3tb7MLLriAMcY6bDkwxth1113Hfv7zn7OmpibW3NzM\\nDh8+3OHK4+DBg2z48OHs2LFjrKWlhc2ZM4etX7++w5TDpk2b2Pbt29nYsWPbjgW59zlz5rDHH3+c\\nNTQ0sHPOOYdt3brVM+2MUyC2zYnZyqBBg1BaWgoA6NevH8aMGYOtW7caN3PW1NRg9uzZyM/Px7Rp\\n08AYQ2NjYzpvITT+/ve/4y9/+QuuuOKKtsUXHbEcBBs3bsRPf/pTdO/eHZ07d0avXr06XHn06NED\\njDEcOXIETU1N+Pzzz9G7d+8OUw5TpkxBnz594o75uXehTt566y1ceumlOOOMM7BgwQKnfjXjDEjQ\\nzYnZwp49e7Bz505MmjTJuJmzpqYGxcXFbdcUFhZqN3pmItdccw3uvPNO5ObGmm5HLAeAG9Pjx49j\\nyZIlKC8vx+23346mpqYOVx49evTA6tWrMXz4cAwaNAjnnHMOysvLO1w5yPi595qaGuzZswcDBgxo\\nO+7ar2acAenINDY24tJLL8V//Md/4LTTTvO1mTMnJyeJOUsNTz31FAYMGICysrK4e+9o5SA4fvw4\\n3n77bVx88cWorq7Gzp078Yc//KHDlceBAwewZMkSvPnmm6ivr8err76Kp556qsOVg0yi9+56fcYZ\\nkIkTJ2LXrl1tv3fu3Kndg5JtNDc34+KLL8bll1+OefPmAeBlUVdXB4A7wCZOnAiA79F58803267d\\ntWtX27lM5pVXXsGf//xnfPWrX8XChQvx/PPP4/LLL+9w5SAYNWoUCgsLccEFF6BHjx5YuHAh1q9f\\n3+HKo7a2FpMnT8aoUaNwxhln4B//8R+xefPmDlcOMn7vfdSoUfj444/bjr/55ptO/WrGGRB5c2J9\\nfT02bNiA8vLyNOcquTDGsHjxYowdO7ZthQkQ28zZ1NQUt5lz0qRJePbZZ7F//35UV1cjNzcXPXv2\\nTFf2Q+PWW2/Fe++9h3379uHxxx/HjBkz8PDDD3e4cpApKChATU0NWltb8fTTT2PmzJkdrjymTJmC\\nbdu24dNPP8WJEyfwzDPPYNasWR2uHGSC3HtRUREef/xxNDQ0YO3atW79agiLAFJOdXU1KyoqYiNH\\njmS/+c1v0p2dpLN582aWk5PDxo8fz0pLS1lpaSl75pln2NGjR9mFF17Ihg4dyubNm8caGxvbrlm5\\nciUbOXIkKy4uZps2bUpj7pNDdXV12yqsjlwOb731FisvL2fjx49n1113HTt27FiHLI8HH3yQTZ06\\nlU2YMIH9/Oc/Zy0tLR2mHC677DI2ePBg1rVrV5aXl8fWrFkT6N537tzJysrK2PDhw9mNN97olDZt\\nJCQIgiACkXFTWARBEEQ0IANCEARBBIIMCEEQBBEIMiAEQRBEIMiAEARBEIEgA0IQBEEEggwIQRAE\\nEQgyIARBEEQg/j8lnQrZEk2SlgAAAABJRU5ErkJggg==\\n\",\n       \"text\": [\n        \"<matplotlib.figure.Figure at 0x82bbac8>\"\n       ]\n      }\n     ],\n     \"prompt_number\": 15\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": []\n    }\n   ],\n   \"metadata\": {}\n  }\n ]\n}\n"
  },
  {
    "path": "doc/examples/ex_thorlabslcc.py",
    "content": "# Thorlabs Liquid Crystal Controller example\n\nimport instruments as ik\n\nlcc = ik.thorlabs.LCC25.open_serial(\"COM10\", 115200, timeout=1)\n\n# put model in voltage1 setting:\nlcc.mode = llc.Mode.voltage1\n\nprint(\"The current frequency is: \", lcc.frequency)\nprint(\"The current voltage is: \", lcc.voltage1)\n"
  },
  {
    "path": "doc/examples/ex_thorlabssc10.py",
    "content": "# Thorlabs Shutter Controller example\n\nimport instruments as ik\n\n# if the baud mode is set to 1, then the baud rate is 115200\n# otherwise, the baud rate is 9600\nsc = ik.thorlabs.SC10.open_serial(\"COM9\", 9600, timeout=1)\n\n\nprint(\"It is a: \", sc.name)\nprint(\"Setting shutter open time to 10 ms\")\nsc.open_time = 10\nprint(\"The shutter open time is: \", sc.open_time)\nprint(\"Setting shutter open time to 50 ms\")\nsc.open_time = 50\nprint(\"The shutter open time is: \", sc.open_time)\nprint(\"Setting shutter close time to 10 ms\")\nsc.open_time = 10\nprint(\"The shutter close time is: \", sc.open_time)\nprint(\"Setting shutter close time to 50 ms\")\nsc.open_time = 50\nprint(\"The shutter close time is: \", sc.open_time)\n\nprint(\"Setting repeat count  to 4\")\nsc.repeat = 4\nprint(\"The repeat count is: \", sc.repeat)\nprint(\"Setting repeat count to 8\")\nsc.repeat = 8\nprint(\"The repeat count is: \", sc.repeat)\n\nprint(\"setting mode to auto\")\nsc.mode = sc.Mode.auto\n"
  },
  {
    "path": "doc/examples/ex_thorlabstc200.py",
    "content": "# Thorlabs Temperature Controller example\n\nimport instruments as ik\nimport instruments.units as u\n\ntc = ik.thorlabs.TC200.open_serial(\"/dev/tc200\", 115200)\n\ntc.temperature_set = 70 * u.degF\nprint(\"The current temperature is: \", tc.temperature)\n\ntc.mode = tc.Mode.normal\nprint(\"The current mode is: \", tc.mode)\n\ntc.enable = True\nprint(\"The current enabled state is: \", tc.enable)\n\ntc.p = 200\nprint(\"The current p gain is: \", tc.p)\n\ntc.i = 2\nprint(\"The current i gain is: \", tc.i)\n\ntc.d = 2\nprint(\"The current d gain is: \", tc.d)\n\ntc.degrees = u.degF\nprint(\"The current degrees settings is: \", tc.degrees)\n\ntc.sensor = tc.Sensor.ptc100\nprint(\"The current sensor setting is: \", tc.sensor)\n\ntc.beta = 3900\nprint(\"The current beta settings is: \", tc.beta)\n\ntc.max_temperature = 150 * u.degC\nprint(\"The current max temperature setting is: \", tc.max_temperature)\n\ntc.max_power = 1000 * u.mW\nprint(\"The current max power setting is: \", tc.max_power)\n"
  },
  {
    "path": "doc/examples/ex_topticatopmode.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nToptica Topmode example\n\"\"\"\n\nimport instruments as ik\nimport instruments.units as u\nfrom platform import system\n\nif system() == \"Windows\":\n    tm = ik.toptica.TopMode.open_serial(\"COM17\", 115200)\nelse:\n    tm = ik.toptica.TopMode.open_serial(\"/dev/ttyACM0\", 115200)\n\nprint(\"The top mode's firmware is: \", tm.firmware)\nprint(\"The top mode's serial number is: \", tm.serial_number)\n\nprint(\"The current lock state is: \", tm.locked)\nprint(\"The current interlock state is: \", tm.interlock)\nprint(\"The current fpga state is: \", tm.fpga_status)\nprint(\"The current temperature state is: \", tm.temperature_status)\nprint(\"The current current state is: \", tm.current_status)\n\nprint(\"The laser1's serial number is: \", tm.laser[0].serial_number)\nprint(\"The laser1's model number is: \", tm.laser[0].model)\nprint(\"The laser1's wavelength is: \", tm.laser[0].wavelength)\nprint(\"The laser1's production date is: \", tm.laser[0].production_date)\nprint(\"The laser1's enable state is: \", tm.laser[0].enable)\nprint(\"The laser1's up time is: \", tm.laser[0].on_time)\nprint(\"The laser1's charm state is: \", tm.laser[0].charm_status)\nprint(\n    \"The laser1's temperature controller state is: \",\n    tm.laser[0].temperature_control_status,\n)\nprint(\"The laser1's current controller state is: \", tm.laser[0].current_control_status)\nprint(\"The laser1's tec state is: \", tm.laser[0].tec_status)\nprint(\"The laser1's intensity is: \", tm.laser[0].intensity)\nprint(\"The laser1's mode hop state is: \", tm.laser[0].mode_hop)\nprint(\"The laser1's correction status is: \", tm.laser[0].correction_status)\nprint(\"The laser1's lock start time is: \", tm.laser[0].lock_start)\nprint(\"The laser1's first mode hop time is: \", tm.laser[0].first_mode_hop_time)\nprint(\"The laser1's latest mode hop time is: \", tm.laser[0].latest_mode_hop_time)\nprint(\"The current emission state is: \", tm.enable)\n\ntm.laser[0].enable = True\n"
  },
  {
    "path": "doc/examples/example2.py",
    "content": "#!/usr/bin/python\n# Filename: example2.py\n\n# Example 1:\n# \t- Import required packages\n# \t- Create object for our Tek TDS 224\n# \t- Transfer the waveform from the oscilloscope on channel 1 using binary block reading\n# \t- Calculate the FFT of the transfered waveform\n# \t- Graph resultant data\n\nfrom instruments import *\n\nimport numpy as np\nimport matplotlib.pyplot as plt\n\ntek = Tektds224(\"/dev/ttyUSB0\", 1, 30)\n\n[x, y] = tek.readWaveform(\"CH1\", \"BINARY\")\nfreq = np.fft.fft(y)  # Calculate FFT\ntimestep = float(tek.query(\"WFMP:XIN?\"))  # Query the timestep between data points\nfreqx = np.fft.fftfreq(freq.size, timestep)  # Compute the x-axis for the FFT data\nplt.plot(freqx, abs(freq))  # Plot the data using matplotlib\nplt.ylim(0, 500)  # Adjust the vertical scale\nplt.show()  # Show the graph\n"
  },
  {
    "path": "doc/examples/minghe/ex_minghe_mhs5200.py",
    "content": "#!/usr/bin/python\nfrom instruments.minghe import MHS5200\nimport instruments.units as u\n\nmhs = MHS5200.open_serial(vid=6790, pid=29987, baud=57600)\nprint(mhs.serial_number)\nmhs.channel[0].frequency = 3000000 * u.Hz\nprint(mhs.channel[0].frequency)\nmhs.channel[0].function = MHS5200.Function.sawtooth_down\nprint(mhs.channel[0].function)\nmhs.channel[0].amplitude = 9.0 * u.V\nprint(mhs.channel[0].amplitude)\nmhs.channel[0].offset = -0.5\nprint(mhs.channel[0].offset)\nmhs.channel[0].phase = 90\nprint(mhs.channel[0].phase)\n\nmhs.channel[1].frequency = 2000000 * u.Hz\nprint(mhs.channel[1].frequency)\nmhs.channel[1].function = MHS5200.Function.square\nprint(mhs.channel[1].function)\nmhs.channel[1].amplitude = 2.0 * u.V\nprint(mhs.channel[1].amplitude)\nmhs.channel[1].offset = 0.0\nprint(mhs.channel[1].offset)\nmhs.channel[1].phase = 15\nprint(mhs.channel[1].phase)\n"
  },
  {
    "path": "doc/examples/qubitekk/ex_qubitekk_mc1.py",
    "content": "#!/usr/bin/python\n# Qubitekk Motor controller example\nfrom time import sleep\n\nfrom instruments.qubitekk import MC1\nimport instruments.units as u\n\nif __name__ == \"__main__\":\n    mc1 = MC1.open_serial(vid=1027, pid=24577, baud=9600, timeout=1)\n    mc1.step_size = 25 * u.ms\n    mc1.inertia = 10 * u.ms\n    print(\"step size:\", mc1.step_size)\n    print(\"inertial force: \", mc1.inertia)\n\n    print(\"Firmware\", mc1.firmware)\n    print(\"Motor controller type: \", mc1.controller)\n    print(\"centering\")\n\n    mc1.center()\n    while mc1.is_centering():\n        print(str(mc1.metric_position) + \" \" + str(mc1.direction))\n        pass\n\n    print(\"Stage Centered\")\n    # for the motor in the mechanical delay line, the travel is limited from\n    # the full range of travel. Here's how to set the limits.\n    mc1.lower_limit = -260 * u.ms\n    mc1.upper_limit = 300 * u.ms\n    mc1.increment = 5 * u.ms\n    x_pos = mc1.lower_limit\n    while x_pos <= mc1.upper_limit:\n        print(str(mc1.metric_position) + \" \" + str(mc1.direction))\n        mc1.move(x_pos)\n        while mc1.move_timeout > 0:\n            sleep(0.5)\n        sleep(1)\n        x_pos += mc1.increment\n"
  },
  {
    "path": "doc/examples/srs_DG645.ipynb",
    "content": "{\n \"metadata\": {\n  \"name\": \"srs_DG645\"\n },\n \"nbformat\": 3,\n \"nbformat_minor\": 0,\n \"worksheets\": [\n  {\n   \"cells\": [\n    {\n     \"cell_type\": \"heading\",\n     \"level\": 1,\n     \"metadata\": {},\n     \"source\": [\n      \"GPIB-USB Communication Library Examples\"\n     ]\n    },\n    {\n     \"cell_type\": \"heading\",\n     \"level\": 2,\n     \"metadata\": {},\n     \"source\": [\n      \"Stanford Research Systems DG645 Digital Delay Generator\"\n     ]\n    },\n    {\n     \"cell_type\": \"markdown\",\n     \"metadata\": {},\n     \"source\": [\n      \"In this example, we will demonstrate how to connect to an SRS DG645 digital delay generator, and how to set a new delay pattern.\"\n     ]\n    },\n    {\n     \"cell_type\": \"markdown\",\n     \"metadata\": {},\n     \"source\": [\n      \"We start by importing the `srs` package from within the main `instruments` package, along with the `quantities` package\\n\",\n      \"that is used to track physical quantities.\"\n     ]\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"from instruments.srs import SRSDG645\\n\",\n      \"import instruments.units as u\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [],\n     \"prompt_number\": 2\n    },\n    {\n     \"cell_type\": \"markdown\",\n     \"metadata\": {},\n     \"source\": [\n      \"Next, we open the instrument, assuming that it is connected via GPIB. Note that you may have to change this line\\n\",\n      \"to match your setup.\"\n     ]\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"ddg = SRSDG645.open_gpibusb('/dev/ttyUSB0', 15)\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [\n      {\n       \"ename\": \"AttributeError\",\n       \"evalue\": \"'NoneType' object has no attribute 'terminator'\",\n       \"output_type\": \"pyerr\",\n       \"traceback\": [\n        \"\\u001b[1;31m---------------------------------------------------------------------------\\u001b[0m\\n\\u001b[1;31mAttributeError\\u001b[0m                            Traceback (most recent call last)\",\n        \"\\u001b[1;32m<ipython-input-3-d987b8421bbc>\\u001b[0m in \\u001b[0;36m<module>\\u001b[1;34m()\\u001b[0m\\n\\u001b[1;32m----> 1\\u001b[1;33m \\u001b[0mddg\\u001b[0m \\u001b[1;33m=\\u001b[0m \\u001b[0mSRSDG645\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0mopen_gpibusb\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[1;34m'/dev/ttyUSB0'\\u001b[0m\\u001b[1;33m,\\u001b[0m \\u001b[1;36m15\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0m\",\n        \"\\u001b[1;32mC:\\\\Users\\\\cgranade\\\\AppData\\\\Local\\\\Enthought\\\\Canopy\\\\User\\\\lib\\\\site-packages\\\\instruments\\\\abstract_instruments\\\\instrument.pyc\\u001b[0m in \\u001b[0;36mopen_gpibusb\\u001b[1;34m(cls, port, gpib_address, timeout, writeTimeout)\\u001b[0m\\n\\u001b[0;32m    294\\u001b[0m                 \\u001b[0mtimeout\\u001b[0m\\u001b[1;33m=\\u001b[0m\\u001b[0mtimeout\\u001b[0m\\u001b[1;33m,\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0;32m    295\\u001b[0m                  writeTimeout=writeTimeout)\\n\\u001b[1;32m--> 296\\u001b[1;33m         \\u001b[1;32mreturn\\u001b[0m \\u001b[0mcls\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[0mgi_gpib\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0mGPIBWrapper\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[0mser\\u001b[0m\\u001b[1;33m,\\u001b[0m \\u001b[0mgpib_address\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0m\\u001b[0;32m    297\\u001b[0m \\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0;32m    298\\u001b[0m     \\u001b[1;33m@\\u001b[0m\\u001b[0mclassmethod\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\",\n        \"\\u001b[1;32mC:\\\\Users\\\\cgranade\\\\AppData\\\\Local\\\\Enthought\\\\Canopy\\\\User\\\\lib\\\\site-packages\\\\instruments\\\\abstract_instruments\\\\gi_gpib.pyc\\u001b[0m in \\u001b[0;36m__init__\\u001b[1;34m(self, filelike, gpib_address)\\u001b[0m\\n\\u001b[0;32m     49\\u001b[0m         \\u001b[0mself\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0m_terminator\\u001b[0m \\u001b[1;33m=\\u001b[0m \\u001b[1;36m10\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0;32m     50\\u001b[0m         \\u001b[0mself\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0m_eoi\\u001b[0m \\u001b[1;33m=\\u001b[0m \\u001b[1;36m1\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[1;32m---> 51\\u001b[1;33m         \\u001b[0mself\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0m_file\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0mterminator\\u001b[0m \\u001b[1;33m=\\u001b[0m \\u001b[1;34m'\\\\r'\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0m\\u001b[0;32m     52\\u001b[0m         \\u001b[0mself\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0m_strip\\u001b[0m \\u001b[1;33m=\\u001b[0m \\u001b[1;36m0\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0;32m     53\\u001b[0m \\u001b[1;33m\\u001b[0m\\u001b[0m\\n\",\n        \"\\u001b[1;31mAttributeError\\u001b[0m: 'NoneType' object has no attribute 'terminator'\"\n       ]\n      },\n      {\n       \"output_type\": \"stream\",\n       \"stream\": \"stdout\",\n       \"text\": [\n        \"Serial connection error. Connection not added to serial                 manager. Error message:None\\n\"\n       ]\n      }\n     ],\n     \"prompt_number\": 3\n    },\n    {\n     \"cell_type\": \"markdown\",\n     \"metadata\": {},\n     \"source\": [\n      \"We can now set the delay on channel $A$ to be $B + 10\\\\ \\\\mu\\\\text{s}$.\"\n     ]\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [\n      \"ddg.channel[ddg.Channels.A].delay = (ddg.Channels.B, u.Quantity(10, 'us'))\"\n     ],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": [\n      {\n       \"ename\": \"NameError\",\n       \"evalue\": \"name 'ddg' is not defined\",\n       \"output_type\": \"pyerr\",\n       \"traceback\": [\n        \"\\u001b[1;31m---------------------------------------------------------------------------\\u001b[0m\\n\\u001b[1;31mNameError\\u001b[0m                                 Traceback (most recent call last)\",\n        \"\\u001b[1;32m<ipython-input-5-5c0f7101522f>\\u001b[0m in \\u001b[0;36m<module>\\u001b[1;34m()\\u001b[0m\\n\\u001b[1;32m----> 1\\u001b[1;33m \\u001b[0mddg\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0mchannel\\u001b[0m\\u001b[1;33m[\\u001b[0m\\u001b[0mddg\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0mChannels\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0mA\\u001b[0m\\u001b[1;33m]\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0mdelay\\u001b[0m \\u001b[1;33m=\\u001b[0m \\u001b[1;33m(\\u001b[0m\\u001b[0mddg\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0mChannels\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0mB\\u001b[0m\\u001b[1;33m,\\u001b[0m \\u001b[0mpq\\u001b[0m\\u001b[1;33m.\\u001b[0m\\u001b[0mQuantity\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[1;36m10\\u001b[0m\\u001b[1;33m,\\u001b[0m \\u001b[1;34m'us'\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0m\",\n        \"\\u001b[1;31mNameError\\u001b[0m: name 'ddg' is not defined\"\n       ]\n      }\n     ],\n     \"prompt_number\": 5\n    },\n    {\n     \"cell_type\": \"code\",\n     \"collapsed\": false,\n     \"input\": [],\n     \"language\": \"python\",\n     \"metadata\": {},\n     \"outputs\": []\n    }\n   ],\n   \"metadata\": {}\n  }\n ]\n}\n"
  },
  {
    "path": "doc/examples/srs_DG645.py",
    "content": "# <nbformat>3.0</nbformat>\n\n# <headingcell level=1>\n\n# InstrumentKit Library Examples\n\n# <headingcell level=2>\n\n# Stanford Research Systems DG645 Digital Delay Generator\n\n# <markdowncell>\n\n# In this example, we will demonstrate how to connect to an SRS DG645 digital delay generator, and how to set a new delay pattern.\n\n# <markdowncell>\n\n# We start by importing the `srs` package from within the main `instruments` package, along with the `instruments.units` package\n# that is used to track physical quantities.\n\n# <codecell>\n\nfrom instruments.srs import SRSDG645\nimport instruments.units as u\n\n# <markdowncell>\n\n# Next, we open the instrument, assuming that it is connected via GPIB. Note that you may have to change this line\n# to match your setup.\n\n# <codecell>\n\nddg = SRSDG645.open_gpibusb(\"/dev/ttyUSB0\", 15)\n\n# <markdowncell>\n\n# We can now set the delay on channel $A$ to be $B + 10\\ \\mu\\text{s}$.\n\n# <codecell>\n\nddg.channel[ddg.Channels.A].delay = (ddg.Channels.B, u.Quantity(10, \"us\"))\n\n# <codecell>\n"
  },
  {
    "path": "doc/make.bat",
    "content": "@ECHO OFF\n\nREM Command file for Sphinx documentation\n\nif \"%SPHINXBUILD%\" == \"\" (\n\tset SPHINXBUILD=sphinx-build\n)\nset BUILDDIR=build\nset ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source\nset I18NSPHINXOPTS=%SPHINXOPTS% source\nif NOT \"%PAPER%\" == \"\" (\n\tset ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%\n\tset I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%\n)\n\nif \"%1\" == \"\" goto help\n\nif \"%1\" == \"help\" (\n\t:help\n\techo.Please use `make ^<target^>` where ^<target^> is one of\n\techo.  html       to make standalone HTML files\n\techo.  dirhtml    to make HTML files named index.html in directories\n\techo.  singlehtml to make a single large HTML file\n\techo.  pickle     to make pickle files\n\techo.  json       to make JSON files\n\techo.  htmlhelp   to make HTML files and a HTML help project\n\techo.  qthelp     to make HTML files and a qthelp project\n\techo.  devhelp    to make HTML files and a Devhelp project\n\techo.  epub       to make an epub\n\techo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\n\techo.  text       to make text files\n\techo.  man        to make manual pages\n\techo.  texinfo    to make Texinfo files\n\techo.  gettext    to make PO message catalogs\n\techo.  changes    to make an overview over all changed/added/deprecated items\n\techo.  linkcheck  to check all external links for integrity\n\techo.  doctest    to run all doctests embedded in the documentation if enabled\n\tgoto end\n)\n\nif \"%1\" == \"clean\" (\n\tfor /d %%i in (%BUILDDIR%\\*) do rmdir /q /s %%i\n\tdel /q /s %BUILDDIR%\\*\n\tgoto end\n)\n\nif \"%1\" == \"html\" (\n\t%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/html.\n\tgoto end\n)\n\nif \"%1\" == \"dirhtml\" (\n\t%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.\n\tgoto end\n)\n\nif \"%1\" == \"singlehtml\" (\n\t%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.\n\tgoto end\n)\n\nif \"%1\" == \"pickle\" (\n\t%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can process the pickle files.\n\tgoto end\n)\n\nif \"%1\" == \"json\" (\n\t%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can process the JSON files.\n\tgoto end\n)\n\nif \"%1\" == \"htmlhelp\" (\n\t%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can run HTML Help Workshop with the ^\n.hhp project file in %BUILDDIR%/htmlhelp.\n\tgoto end\n)\n\nif \"%1\" == \"qthelp\" (\n\t%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can run \"qcollectiongenerator\" with the ^\n.qhcp project file in %BUILDDIR%/qthelp, like this:\n\techo.^> qcollectiongenerator %BUILDDIR%\\qthelp\\GPIBUSBAdapterDriverLibrary.qhcp\n\techo.To view the help file:\n\techo.^> assistant -collectionFile %BUILDDIR%\\qthelp\\GPIBUSBAdapterDriverLibrary.ghc\n\tgoto end\n)\n\nif \"%1\" == \"devhelp\" (\n\t%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished.\n\tgoto end\n)\n\nif \"%1\" == \"epub\" (\n\t%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The epub file is in %BUILDDIR%/epub.\n\tgoto end\n)\n\nif \"%1\" == \"latex\" (\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; the LaTeX files are in %BUILDDIR%/latex.\n\tgoto end\n)\n\nif \"%1\" == \"text\" (\n\t%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The text files are in %BUILDDIR%/text.\n\tgoto end\n)\n\nif \"%1\" == \"man\" (\n\t%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The manual pages are in %BUILDDIR%/man.\n\tgoto end\n)\n\nif \"%1\" == \"texinfo\" (\n\t%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.\n\tgoto end\n)\n\nif \"%1\" == \"gettext\" (\n\t%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The message catalogs are in %BUILDDIR%/locale.\n\tgoto end\n)\n\nif \"%1\" == \"changes\" (\n\t%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.The overview file is in %BUILDDIR%/changes.\n\tgoto end\n)\n\nif \"%1\" == \"linkcheck\" (\n\t%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Link check complete; look for any errors in the above output ^\nor in %BUILDDIR%/linkcheck/output.txt.\n\tgoto end\n)\n\nif \"%1\" == \"doctest\" (\n\t%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Testing of doctests in the sources finished, look at the ^\nresults in %BUILDDIR%/doctest/output.txt.\n\tgoto end\n)\n\n:end\n"
  },
  {
    "path": "doc/source/acknowledgements.rst",
    "content": "================\nAcknowledgements\n================\n\nHere I've done my best to keep a list of all those who have made a contribution\nto this project. All names listed below are the Github account names associated\nwith their commits.\n\nFirst off, I'd like to give special thanks to cgranade for his help with pretty\nmuch every step along the way. I would be hard pressed to find something that he\nhad nothing to do with.\n\n- ihincks for the fantastic property factories (used throughout all classes) and for the Tektronix DPO70000 series class.\n- dijkstrw for contributing several classes (HP6632b, HP3456a, Keithley 580) as well as plenty of general IK testing.\n- CatherineH for the Qubitekk CC1, Thorlabs LCC25, SC10, and TC200 classes\n- silverchris for the TekTDS5xx class\n- wil-langford for the HP6652a class\n- whitewhim2718 for the Newport ESP 301\n"
  },
  {
    "path": "doc/source/apiref/agilent.rst",
    "content": "..\n    TODO: put documentation license header here.\n\n.. currentmodule:: instruments.agilent\n\n=======\nAgilent\n=======\n\n:class:`Agilent33220a` Function Generator\n=========================================\n\n.. autoclass:: Agilent33220a\n    :members:\n    :undoc-members:\n\n:class:`Agilent34410a` Digital Multimeter\n=========================================\n\n.. autoclass:: Agilent34410a\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/apiref/aimtti.rst",
    "content": ".. currentmodule:: instruments.aimtti\n\n=======\nAim-TTi\n=======\n\n:class:`AimTTiEL302P` Power Supply\n=========================================\n\n.. autoclass:: AimTTiEL302P\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/apiref/comet.rst",
    "content": ".. currentmodule:: instruments.comet\n\n=====\nComet\n=====\n\n:class:`CitoPlus1310` RF Generator\n=========================================\n\n.. autoclass:: CitoPlus1310\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/apiref/config.rst",
    "content": "==========================\nConfiguration File Support\n==========================\n\n.. currentmodule:: instruments\n\nThe `instruments` package provides support for loading instruments from a\nconfiguration file, so that instrument parameters can be abstracted from the\nsoftware that connects to those instruments. Configuration files recognized\nby `instruments` are `YAML`_ files that specify for each instrument a class\nresponsible for loading that instrument, along with a URI specifying how that\ninstrument is connected.\n\nConfiguration files are loaded by the use of the `load_instruments` function,\ndocumented below.\n\nFunctions\n=========\n\n.. autofunction:: load_instruments\n\n.. _YAML: http://yaml.org/\n"
  },
  {
    "path": "doc/source/apiref/delta_elektronika.rst",
    "content": ".. currentmodule:: instruments.delta_elektronika\n\n=================\nDelta Elektronika\n=================\n\n:class:`PscEth` Power Supply over Ethernet controller\n=====================================================\n\n.. autoclass:: PscEth\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/apiref/dressler.rst",
    "content": ".. currentmodule:: instruments.dressler\n\n========\nDressler\n========\n\n:class:`Cesar1312` RF Generator\n===============================\n\n.. autoclass:: Cesar1312\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/apiref/fluke.rst",
    "content": "..\n    TODO: put documentation license header here.\n\n.. currentmodule:: instruments.fluke\n\n=====\nFluke\n=====\n\n:class:`Fluke3000` Industrial System\n====================================\n\n.. autoclass:: Fluke3000\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/apiref/generic_scpi.rst",
    "content": "..\n    TODO: put documentation license header here.\n\n.. _apiref-generic_scpi:\n.. currentmodule:: instruments.generic_scpi\n\n========================\nGeneric SCPI Instruments\n========================\n\n:class:`SCPIInstrument` - Base class for instruments using the SCPI protocol\n============================================================================\n\n.. autoclass:: SCPIInstrument\n    :members:\n    :undoc-members:\n\n:class:`SCPIMultimeter` - Generic multimeter using SCPI commands\n================================================================\n\n.. autoclass:: SCPIMultimeter\n    :members:\n    :undoc-members:\n\n:class:`SCPIFunctionGenerator` - Generic multimeter using SCPI commands\n=======================================================================\n\n.. autoclass:: SCPIFunctionGenerator\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/apiref/gentec-eo.rst",
    "content": ".. currentmodule:: instruments.gentec_eo\n\n=========\nGentec-EO\n=========\n\n:class:`Blu` Power Meter\n=======================================\n\n.. autoclass:: Blu\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/apiref/glassman.rst",
    "content": "..\n    TODO: put documentation license header here.\n\n.. currentmodule:: instruments.glassman\n\n========\nGlassman\n========\n\n:class:`GlassmanFR` Single Output Power Supply\n==============================================\n\n.. autoclass:: GlassmanFR\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/apiref/hcp.rst",
    "content": "..\n    TODO: put documentation license header here.\n\n.. currentmodule:: instruments.hcp\n\n============\nHC Photonics\n============\n\n:class:`TC038` Crystal oven AC\n==============================\n\n.. autoclass:: TC038\n    :members:\n    :undoc-members:\n\n:class:`TC038D` Crystal oven DC\n===============================\n\n.. autoclass:: TC038D\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/apiref/holzworth.rst",
    "content": "..\n    TODO: put documentation license header here.\n\n.. currentmodule:: instruments.holzworth\n\n=========\nHolzworth\n=========\n\n:class:`HS9000` Multichannel frequency synthesizer\n==================================================\n\n.. autoclass:: HS9000\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/apiref/hp.rst",
    "content": "..\n    TODO: put documentation license header here.\n\n.. currentmodule:: instruments.hp\n\n===============\nHewlett-Packard\n===============\n\n:class:`HP3456a` Digital Voltmeter\n==================================\n\n.. autoclass:: HP3456a\n    :members:\n    :undoc-members:\n\n:class:`HP6624a` Power Supply\n=============================\n\n.. autoclass:: HP6624a\n    :members:\n    :undoc-members:\n\n:class:`HP6632b` Power Supply\n=============================\n\n.. autoclass:: HP6632b\n    :members:\n    :undoc-members:\n\n:class:`HP6652a` Single Output Power Supply\n===========================================\n\n.. autoclass:: HP6652a\n    :members:\n    :undoc-members:\n\n:class:`HPe3631a` Power Supply\n==============================\n\n.. autoclass:: HPe3631a\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/apiref/index.rst",
    "content": "..\n    TODO: put documentation license header here.\n\n.. _apiref:\n\nInstrumentKit API Reference\n===========================\n\nContents:\n\n.. toctree::\n    :maxdepth: 2\n\n    instrument\n    generic_scpi\n    agilent\n    aimtti\n    comet\n    dressler\n    fluke\n    gentec-eo\n    glassman\n    hcp\n    holzworth\n    hp\n    keithley\n    lakeshore\n    minghe\n    mettler_toledo\n    newport\n    ondax\n    oxford\n    pfeiffer\n    phasematrix\n    picowatt\n    qubitekk\n    rigol\n    srs\n    sunpower\n    tektronix\n    teledyne\n    thorlabs\n    toptica\n    yokogawa\n    config\n"
  },
  {
    "path": "doc/source/apiref/instrument.rst",
    "content": "..\n    TODO: put documentation license header here.\n\n.. currentmodule:: instruments\n\n=======================\nInstrument Base Classes\n=======================\n\n:class:`Instrument` - Base class for instrument communication\n=============================================================\n\n.. autoclass:: Instrument\n    :members:\n    :undoc-members:\n\n:class:`Electrometer` - Abstract class for electrometer instruments\n===================================================================\n\n.. autoclass:: instruments.abstract_instruments.Electrometer\n    :members:\n    :undoc-members:\n\n:class:`FunctionGenerator` - Abstract class for function generator instruments\n==============================================================================\n\n.. autoclass:: instruments.abstract_instruments.FunctionGenerator\n    :members:\n    :undoc-members:\n\n:class:`Multimeter` - Abstract class for multimeter instruments\n===============================================================\n\n.. autoclass:: instruments.abstract_instruments.Multimeter\n    :members:\n    :undoc-members:\n\n:class:`Oscilloscope` - Abstract class for oscilloscope instruments\n===================================================================\n\n.. autoclass:: instruments.abstract_instruments.Oscilloscope\n    :members:\n    :undoc-members:\n\n:class:`OpticalSpectrumAnalyzer` - Abstract class for optical spectrum analyzer instruments\n===========================================================================================\n\n.. autoclass:: instruments.abstract_instruments.OpticalSpectrumAnalyzer\n    :members:\n    :undoc-members:\n\n:class:`PowerSupply` - Abstract class for power supply instruments\n==================================================================\n\n.. autoclass:: instruments.abstract_instruments.PowerSupply\n    :members:\n    :undoc-members:\n\n:class:`SignalGenerator` - Abstract class for Signal Generators\n===============================================================\n\n.. autoclass:: instruments.abstract_instruments.signal_generator.SignalGenerator\n    :members:\n    :undoc-members:\n\n:class:`SingleChannelSG` - Class for Signal Generators with a Single Channel\n=====================================================================================\n\n.. autoclass:: instruments.abstract_instruments.signal_generator.SingleChannelSG\n    :members:\n    :undoc-members:\n\n:class:`SGChannel` - Abstract class for Signal Generator Channels\n=================================================================\n\n.. autoclass:: instruments.abstract_instruments.signal_generator.SGChannel\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/apiref/keithley.rst",
    "content": "..\n    TODO: put documentation license header here.\n\n.. currentmodule:: instruments.keithley\n\n========\nKeithley\n========\n\n:class:`Keithley195` Digital Multimeter\n=======================================\n\n.. autoclass:: Keithley195\n    :members:\n    :undoc-members:\n\n:class:`Keithley485` Picoammeter\n================================\n\n.. autoclass:: Keithley485\n    :members:\n    :undoc-members:\n\n:class:`Keithley580` Microohm Meter\n===================================\n\n.. autoclass:: Keithley580\n    :members:\n    :undoc-members:\n\n:class:`Keithley2182` Nano-voltmeter\n====================================\n\n.. autoclass:: Keithley2182\n    :members:\n    :undoc-members:\n.. _user's guide: http://www.keithley.com/products/dcac/sensitive/lowvoltage/?mn=2182A\n\n:class:`Keithley6220` Constant Current Supply\n=============================================\n\n.. autoclass:: Keithley6220\n    :members:\n    :undoc-members:\n\n:class:`Keithley6514` Electrometer\n==================================\n\n.. autoclass:: Keithley6514\n    :members:\n    :undoc-members:\n.. _Keithley 6514: http://www.tunl.duke.edu/documents/public/electronics/Keithley/keithley-6514-electrometer-manual.pdf\n"
  },
  {
    "path": "doc/source/apiref/lakeshore.rst",
    "content": "..\n    TODO: put documentation license header here.\n\n.. currentmodule:: instruments.lakeshore\n\n=========\nLakeshore\n=========\n\n:class:`Lakeshore336` Cryogenic Temperature Controller\n======================================================\n\n.. autoclass:: Lakeshore336\n    :members:\n    :undoc-members:\n\n:class:`Lakeshore340` Cryogenic Temperature Controller\n======================================================\n\n.. autoclass:: Lakeshore340\n    :members:\n    :undoc-members:\n\n:class:`Lakeshore370` AC Resistance Bridge\n==========================================\n\n.. autoclass:: Lakeshore370\n    :members:\n    :undoc-members:\n\n:class:`Lakeshore475` Gaussmeter\n================================\n\n.. autoclass:: Lakeshore475\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/apiref/mettler_toledo.rst",
    "content": "..\n    TODO: put documentation license header here.\n\n.. currentmodule:: instruments.mettler_toledo\n\n==============\nMettler Toledo\n==============\n\n:class:`MTSICS` MT Standard Interface Communication Software\n============================================================\n\n.. autoclass:: MTSICS\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/apiref/minghe.rst",
    "content": "..\n    TODO: put documentation license header here.\n\n.. currentmodule:: instruments.minghe\n\n======\nMinghe\n======\n\n:class:`MHS5200` Function Generator\n===================================\n\n.. autoclass:: MHS5200\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/apiref/newport.rst",
    "content": "..\n    TODO: put documentation license header here.\n\n.. currentmodule:: instruments.newport\n\n=======\nNewport\n=======\n\n:class:`Agilis` Piezo Motor Controller\n======================================\n\n.. autoclass:: AGUC2\n    :members:\n    :undoc-members:\n\n:class:`NewportESP301` Motor Controller\n=======================================\n\n.. autoclass:: NewportESP301\n    :members:\n    :undoc-members:\n\n:class:`NewportError`\n=====================\n\n.. autoclass:: NewportError\n    :members:\n    :undoc-members:\n\n:class:`PicoMotorController8742`\n================================\n\n.. autoclass:: PicoMotorController8742\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/apiref/ondax.rst",
    "content": "..\n    TODO: put documentation license header here.\n\n.. currentmodule:: instruments.ondax\n\n=====\nOndax\n=====\n\n:class:`LM` Ondax SureLock Laser Module\n=======================================\n\n.. autoclass:: LM\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/apiref/oxford.rst",
    "content": "..\n    TODO: put documentation license header here.\n\n.. currentmodule:: instruments.oxford\n\n======\nOxford\n======\n\n:class:`OxfordITC503` Temperature Controller\n============================================\n\n.. autoclass:: OxfordITC503\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/apiref/pfeiffer.rst",
    "content": ".. currentmodule:: instruments.pfeiffer\n\n===========================\nPfeiffer Vacuum Instruments\n===========================\n\n:class:`TPG36x` Vacuum Gauge Controller\n=======================================\n\n.. autoclass:: TPG36x\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/apiref/phasematrix.rst",
    "content": "..\n    TODO: put documentation license header here.\n\n.. currentmodule:: instruments.phasematrix\n\n===========\nPhaseMatrix\n===========\n\n:class:`PhaseMatrixFSW0020` Signal Generator\n============================================\n\n.. autoclass:: PhaseMatrixFSW0020\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/apiref/picowatt.rst",
    "content": "..\n    TODO: put documentation license header here.\n\n.. currentmodule:: instruments.picowatt\n\n========\nPicowatt\n========\n\n:class:`PicowattAVS47` Resistance Bridge\n========================================\n\n.. autoclass:: PicowattAVS47\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/apiref/qubitekk.rst",
    "content": "..\n    TODO: put documentation license header here.\n\n.. currentmodule:: instruments.qubitekk\n\n========\nQubitekk\n========\n\n:class:`CC1` Coincidence Counter\n================================\n\n.. autoclass:: CC1\n    :members:\n    :undoc-members:\n\n:class:`MC1` Motor Controller\n=============================\n\n.. autoclass:: MC1\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/apiref/rigol.rst",
    "content": "..\n    TODO: put documentation license header here.\n\n.. currentmodule:: instruments.rigol\n\n=====\nRigol\n=====\n\n:class:`RigolDS1000Series` Oscilloscope\n=======================================\n\n.. autoclass:: RigolDS1000Series\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/apiref/srs.rst",
    "content": "..\n    TODO: put documentation license header here.\n\n.. currentmodule:: instruments.srs\n\n=========================\nStanford Research Systems\n=========================\n\n:class:`SRS345` Function Generator\n==================================\n\n.. autoclass:: SRS345\n    :members:\n    :undoc-members:\n\n:class:`SRS830` Lock-In Amplifier\n=================================\n\n.. autoclass:: SRS830\n    :members:\n    :undoc-members:\n\n:class:`SRSCTC100` Cryogenic Temperature Controller\n===================================================\n\n.. autoclass:: SRSCTC100\n    :members:\n    :undoc-members:\n\n:class:`SRSDG645` Digital Delay Generator\n=========================================\n\n.. autoclass:: SRSDG645\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/apiref/sunpower.rst",
    "content": ".. currentmodule:: instruments.sunpower\n\n====================\nSunpower Instruments\n====================\n\n:class:`CryoTelGT` Cryocooler\n=============================\n\n.. autoclass:: CryoTelGT\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/apiref/tektronix.rst",
    "content": "..\n    TODO: put documentation license header here.\n\n.. currentmodule:: instruments.tektronix\n\n=========\nTektronix\n=========\n\n:class:`TekAWG2000` Arbitrary Wave Generator\n============================================\n\n.. autoclass:: TekAWG2000\n    :members:\n    :undoc-members:\n\n:class:`TekDPO4104` Oscilloscope\n================================\n\n.. autoclass:: TekDPO4104\n    :members:\n    :undoc-members:\n\n:class:`TekDPO70000` Oscilloscope\n=================================\n\n.. autoclass:: TekDPO70000\n    :members:\n    :undoc-members:\n\n:class:`TekTDS224` Oscilloscope\n===============================\n\n.. autoclass:: TekTDS224\n    :members:\n    :undoc-members:\n\n:class:`TekTDS5xx` Oscilloscope\n===============================\n\n.. autoclass:: TekTDS5xx\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/apiref/teledyne.rst",
    "content": "..\n    TODO: put documentation license header here.\n\n.. currentmodule:: instruments.teledyne\n\n===============\nTeledyne-LeCroy\n===============\n\n:class:`MAUI` Oscilloscope Controller\n=======================================\n\n.. autoclass:: MAUI\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/apiref/thorlabs.rst",
    "content": "..\n    TODO: put documentation license header here.\n\n.. currentmodule:: instruments.thorlabs\n\n========\nThorLabs\n========\n\n:class:`PM100USB` USB Power Meter\n=================================\n\n.. autoclass:: PM100USB\n    :members:\n    :undoc-members:\n\n:class:`ThorLabsAPT` ThorLabs APT Controller\n============================================\n\n.. autoclass:: ThorLabsAPT\n    :members:\n    :undoc-members:\n\n.. autoclass:: APTPiezoInertiaActuator\n    :members:\n    :undoc-members:\n\n.. autoclass:: APTPiezoStage\n    :members:\n    :undoc-members:\n\n.. autoclass:: APTStrainGaugeReader\n    :members:\n    :undoc-members:\n\n.. autoclass:: APTMotorController\n    :members:\n    :undoc-members:\n\n:class:`SC10` Optical Beam Shutter Controller\n=============================================\n\n.. autoclass:: SC10\n    :members:\n    :undoc-members:\n\n:class:`LCC25` Liquid Crystal Controller\n========================================\n\n.. autoclass:: LCC25\n    :members:\n    :undoc-members:\n\n:class:`TC200` Temperature Controller\n=====================================\n\n.. autoclass:: TC200\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/apiref/toptica.rst",
    "content": "..\n    TODO: put documentation license header here.\n\n.. currentmodule:: instruments.toptica\n\n=======\nToptica\n=======\n\n:class:`TopMode` Diode Laser\n============================\n\n.. autoclass:: TopMode\n    :members:\n    :undoc-members:\n.. _Toptica Topmode: http://www.toptica.com/fileadmin/user_upload/products/Diode_Lasers/Industrial_OEM/Single_Frequency/TopMode/toptica_BR_TopMode.pdf\n"
  },
  {
    "path": "doc/source/apiref/yokogawa.rst",
    "content": "..\n    TODO: put documentation license header here.\n\n.. currentmodule:: instruments.yokogawa\n\n========\nYokogawa\n========\n\n:class:`Yokogawa6370` Optical Spectrum Analyzer\n===============================================\n\n.. autoclass:: Yokogawa6370\n    :members:\n    :undoc-members:\n\n:class:`Yokogawa7651` Power Supply\n==================================\n\n.. autoclass:: Yokogawa7651\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "doc/source/conf.py",
    "content": "#\n# InstrumentKit Library documentation build configuration file, created by\n# sphinx-quickstart on Fri Apr  5 10:37:03 2013.\n#\n# This file is execfile()d with the current directory set to its containing dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\n\nfrom importlib.metadata import version\nimport os\nimport sys\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\nsys.path.insert(0, os.path.abspath(\"../../\"))\n\n# -- General configuration -----------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n# needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be extensions\n# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.\nextensions = [\n    \"sphinx.ext.autodoc\",\n    \"sphinx.ext.doctest\",\n    \"sphinx.ext.intersphinx\",\n    \"sphinx.ext.coverage\",\n    \"sphinx.ext.mathjax\",\n    \"sphinx.ext.viewcode\",\n]\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = [\"_templates\"]\n\n# The suffix of source filenames.\nsource_suffix = {\".rst\": \"restructuredtext\"}\n\n# The encoding of source files.\n# source_encoding = 'utf-8-sig'\n\n# The master toctree document.\nmaster_doc = \"index\"\n\n# General information about the project.\nproject = \"InstrumentKit Library\"\ncopyright = \"2013-2025, Steven Casagrande\"\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\n# The full release version\nrelease = version(\"instrumentkit\")\n# The short X.Y version\nversion = \".\".join(release.split(\".\")[:2])\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n# language = None\n\n# There are two options for replacing |today|: either, you set today to some\n# non-false value, then it is used:\n# today = ''\n# Else, today_fmt is used as the format for a strftime call.\n# today_fmt = '%B %d, %Y'\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\nexclude_patterns = []\n\n# The reST default role (used for this markup: `text`) to use for all documents.\ndefault_role = \"obj\"\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n# add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\n# add_module_names = True\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\n# show_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = \"sphinx\"\n\n# A list of ignored prefixes for module index sorting.\n# modindex_common_prefix = []\n\n\n# -- Options for HTML output ---------------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\nhtml_theme = \"sphinx_rtd_theme\"\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\n# html_theme_options = {}\n\n# Add any paths that contain custom themes here, relative to this directory.\n# html_theme_path = []\n\n# The name for this set of Sphinx documents.  If None, it defaults to\n# \"<project> v<release> documentation\".\n# html_title = None\n\n# A shorter title for the navigation bar.  Default is the same as html_title.\n# html_short_title = None\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\n# html_logo = None\n\n# The name of an image file (within the static path) to use as favicon of the\n# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32\n# pixels large.\n# html_favicon = None\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\n# html_static_path = [\"_static\"]\n\n# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,\n# using the given strftime format.\n# html_last_updated_fmt = '%b %d, %Y'\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n# html_use_smartypants = True\n\n# Custom sidebar templates, maps document names to template names.\n# html_sidebars = {}\n\n# Additional templates that should be rendered to pages, maps page names to\n# template names.\n# html_additional_pages = {}\n\n# If false, no module index is generated.\n# html_domain_indices = True\n\n# If false, no index is generated.\n# html_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n# html_split_index = False\n\n# If true, links to the reST sources are added to the pages.\n# html_show_sourcelink = True\n\n# If true, \"Created using Sphinx\" is shown in the HTML footer. Default is True.\n# html_show_sphinx = True\n\n# If true, \"(C) Copyright ...\" is shown in the HTML footer. Default is True.\n# html_show_copyright = True\n\n# If true, an OpenSearch description file will be output, and all pages will\n# contain a <link> tag referring to it.  The value of this option must be the\n# base URL from which the finished HTML is served.\n# html_use_opensearch = ''\n\n# This is the file name suffix for HTML files (e.g. \".xhtml\").\n# html_file_suffix = None\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = \"InstrumentKitLibrarydoc\"\n\n\n# -- Options for LaTeX output --------------------------------------------------\n\nlatex_elements = {\n    # The paper size ('letterpaper' or 'a4paper').\n    #'papersize': 'letterpaper',\n    # The font size ('10pt', '11pt' or '12pt').\n    #'pointsize': '10pt',\n    # Additional stuff for the LaTeX preamble.\n    #'preamble': '',\n}\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title, author, documentclass [howto/manual]).\nlatex_documents = [\n    (\n        \"index\",\n        \"InstrumentKitLibrary.tex\",\n        \"InstrumentKit Library Documentation\",\n        \"Steven Casagrande\",\n        \"manual\",\n    ),\n]\n\n# The name of an image file (relative to this directory) to place at the top of\n# the title page.\n# latex_logo = None\n\n# For \"manual\" documents, if this is true, then toplevel headings are parts,\n# not chapters.\n# latex_use_parts = False\n\n# If true, show page references after internal links.\n# latex_show_pagerefs = False\n\n# If true, show URL addresses after external links.\n# latex_show_urls = False\n\n# Documents to append as an appendix to all manuals.\n# latex_appendices = []\n\n# If false, no module index is generated.\n# latex_domain_indices = True\n\n\n# -- Options for manual page output --------------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [\n    (\n        \"index\",\n        \"instrumentkitlibrary\",\n        \"InstrumentKit Library Documentation\",\n        [\"Steven Casagrande\"],\n        1,\n    )\n]\n\n# If true, show URL addresses after external links.\n# man_show_urls = False\n\n\n# -- Options for Texinfo output ------------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n    (\n        \"index\",\n        \"InstrumentKitLibrary\",\n        \"InstrumentKit Library Documentation\",\n        \"Steven Casagrande\",\n        \"InstrumentKitLibrary\",\n        \"One line description of project.\",\n        \"Miscellaneous\",\n    ),\n]\n\n# Documents to append as an appendix to all manuals.\n# texinfo_appendices = []\n\n# If false, no module index is generated.\n# texinfo_domain_indices = True\n\n# How to display URL addresses: 'footnote', 'no', or 'inline'.\n# texinfo_show_urls = 'footnote'\n\n\n# Example configuration for intersphinx: refer to the Python standard library.\nintersphinx_mapping = {\n    \"numpy\": (\"http://docs.scipy.org/doc/numpy\", None),\n    \"serial\": (\"http://pyserial.sourceforge.net/\", None),\n    \"pint\": (\"https://pint.readthedocs.io/en/stable/\", None),\n}\n\nautodoc_member_order = \"groupwise\"\n"
  },
  {
    "path": "doc/source/devguide/code_style.rst",
    "content": ".. _code_style:\n\n============\nCoding Style\n============\n\nData Types\n==========\n\nNumeric Data\n------------\n\nWhen appropriate, use :class:`pint.Quantity` objects to track units.\nIf this is not possible or appropriate, use a bare `float` for scalars\nand `np.ndarray` for array-valued data.\n\nBoolean and Enumerated Data\n---------------------------\n\nIf a property or method argument can take exactly two values,\nof which one can be interpreted in the affirmative, use Python\n`bool` data types to represent this. Be permissive in what you accept\nas `True` and `False`, in order to be consistent with Python conventions\nfor truthy and falsey values. This can be accomplished using the\n`bool` function to convert to Booleans, and is done implicitly by\nthe `if` statement.\n\nIf a property has more than two permissible values, or the two allowable\nvalues are not naturally interpreted as a Boolean (e.g.: positive/negative,\nAC/DC coupling, etc.), then consider using an `~enum.Enum` or `~enum.IntEnum` as\nprovided by `enum`. The latter is useful in for wrapping integer values that\nare meaningful to the device.\n\nFor example, if an instrument can operate in AC or DC mode, use an enumeration\nlike the following::\n\n\tclass SomeInstrument(Instrument):\n\n\t\t# Define as an inner class.\n\t\tclass Mode(Enum):\n\t\t\t\"\"\"\n\t\t\tWhen appropriate, document the enumeration itself...\n\t\t\t\"\"\"\n\t\t\t#: ...and each of the enumeration values.\n\t\t\tac = \"AC\"\n\t\t\t#: The \"#:\" notation means that this line documents\n\t\t\t#: the following member, SomeInstrument.Mode.dc.\n\t\t\tdc = \"DC\"\n\n\t\t# For SCPI-like instruments, enum_property\n\t\t# works well to expose the enumeration.\n\t\t# This will generate commands like \"MODE AC\"\n\t\t# and \"MODE DC\".\n\t\tmode = enum_property(\n\t\t    name=\":MODE\",\n\t\t    enum=SomeInstrument.Mode,\n\t\t    doc=\"\"\"\n\t\t    And here is the docstring for this property\n\t\t    \"\"\"\n        )\n\n\t# To set the mode is now straightforward.\n\tins = SomeInstrument.open_somehow()\n\tins.mode = ins.Mode.ac\n\nNote that the enumeration is an inner class, as described below\nin :ref:`associated_types`.\n\nObject Oriented Design\n======================\n\n.. _associated_types:\n\nAssociated Types\n----------------\n\nMany instrument classes have associated types, such as channels and\naxes, so that these properties of the instrument can be manipulated\nindependently of the underlying instrument::\n\n\t>>> channels = [ins1.channel[0], ins2.channel[3]]\n\nHere, the user of ``channels`` need not know or care that the two\nchannels are from different instruments, as is useful for large\ninstallations. This lets users quickly redefine their setups\nwith minimal code changes.\n\nTo enable this, the associated types should be made inner classes\nthat are exposed using :class:`~instruments.util_fns.ProxyList`.\nFor example::\n\n\tclass SomeInstrument(Instrument):\n\t\t# If there's a more appropriate base class, please use it\n\t\t# in preference to object!\n\t\tclass Channel:\n\t\t\t# We use a three-argument initializer,\n\t\t\t# to remember which instrument this channel belongs to,\n\t\t\t# as well as its index or label on that instrument.\n\t\t\t# This will be useful in sending commands, and in exposing\n\t\t\t# via ProxyList.\n\t\t\tdef __init__(self, parent, idx):\n\t\t\t\tself._parent = parent\n\t\t\t\tself._idx = idx\n\t\t\t# define some things here...\n\n\t\t@property\n\t\tdef channel(self):\n\t\t\treturn ProxyList(self, SomeInstrument.Channel, range(2))\n\nThis defines an instrument with two channels, having labels ``0`` and ``1``.\nBy using an inner class, the channel is clearly associated with the instrument,\nand appears with the instrument in documentation.\n\nSince this convention is somewhat recent, you may find older code that uses\na style more like this::\n\n\tclass _SomeInstrumentChannel:\n\t\t# stuff\n\n\tclass SomeInstrument(Instrument):\n\t\t@property\n\t\tdef channel(self):\n\t\t\treturn ProxyList(self, _SomeInstrumentChannel, range(2))\n\nThis can be redefined in a backwards-compatible way by bringing the channel\nclass inside, then defining a new module-level variable for the old name::\n\n\tclass SomeInstrument(Instrument):\n\t\tclass Channel:\n\t\t\t# stuff\n\n\t\t@property\n\t\tdef channel(self):\n\t\t\treturn ProxyList(self, _SomeInstrumentChannel, range(2))\n\n\t_SomeInstrumentChannel = SomeInstrument.Channel\n"
  },
  {
    "path": "doc/source/devguide/design_philosophy.rst",
    "content": "=================\nDesign Philosophy\n=================\n\nHere, we describe the design philosophy behind InstrumentKit at a high-level.\nSpecific implications of this philosophy for coding style and practices\nare detailed in :ref:`code_style`.\n\nPythonic\n========\n\nInstrumentKit aims to make instruments and devices look and feel native to the\nPython development culture. Users should not have to worry if a given\ninstrument names channels starting with 1 or 0, because Python itself is zero-\nbased.\n\n>>> scope.data_source = scope.channel[0] # doctest: +SKIP\n\nAccessing parts of an instrument should be supported in a way that supports\nstandard Python idioms, most notably iteration.\n\n>>> for channel in scope.channel: # doctest: +SKIP\n...     channel.coupling = scope.Coupling.ground\n\nValues that can be queried and set should be exposed as properties.\nInstrument modes that should be entered and exited on a temporary basis should\nbe exposed as context managers. In short, anyone familiar with Python should\nbe able to read InstrumentKit-based programs with little to no confusion.\n\nAbstract\n========\n\nUsers should not have to worry overmuch about the particular instruments that\nare being used, but about the functionality that instrument exposes. To a large\ndegree, this is enabled by using common base classes, such as\n:class:`instruments.generic_scpi.SCPIOscilloscope`. While every instrument does\noffer its own unique functionality, by consolidating common functionality in\nbase classes, users can employ some subset without worrying too much about the\nparticulars.\n\nThis also extends to communications methods. By consolidating communication\nlogic in the\n:class:`instruments.abstract_instruments.comm.AbstractCommunicator` class,\nusers can connect instruments however is convienent for them, and can change\ncommunications methods without affecting their software very much.\n\nRobust\n======\n\nCommunications with instruments should be handled in such a way that errors\nare reported in a natural and Python-ic way, such that incorrect or unsafe\noperations are avoided, and such that all communications are correct.\n\nAn important consequence of this is that all quantities communicated to or from\nthe instrument should be *unitful*. In this way, users can specify the\ndimensionality of values to be sent to the device without regards for what the\ninstrument expects; the unit conversions will be handled by InstrumentKit in a\nway that ensures that the expectations of the instrument are properly met,\nirrespective of what the user knows.\n"
  },
  {
    "path": "doc/source/devguide/index.rst",
    "content": "===============================\nInstrumentKit Development Guide\n===============================\n\n.. toctree::\n    :maxdepth: 2\n\n    design_philosophy\n    code_style\n    testing\n    util_fns\n\nIntroduction\n============\n\nThis guide details how InstrumentKit is laid out from a developer's point of\nview, how to add instruments, communication methods and unit tests.\n\nGetting Started\n===============\n\nTo get started with development for InstrumentKit, a few additional supporting\npackages must be installed. The core development packages can be found in\n`setup.cfg` under the `dev` extras dependencies. These will allow you to run\nthe tests.\n\nThis repo also contains a series of static code checks that are managed\nvia ``pre-commit``. This tool, once setup, will manage running all of these\nchecks prior to each commit on your local machine.::\n\n$ pip install pre-commit\n$ pre-commit install\n\nThese checks are also run in CI, and must pass in order to generate\na passing build. It is suggested that you install the git hooks, but\nthey can be run manually on all files. See the ``pre-commit`` homepage\nfor more information.\n\nRequired Development Dependencies\n---------------------------------\n\nUsing ``pip``, these requirements can be obtained automatically by using the\nprovided project definitions::\n\n$ pip install -e .[dev]\n\nOptional Development Dependencies\n---------------------------------\n\nIn addition to the required dev dependencies, there are optional ones.\nThe package `tox`_ allows you to quickly run the tests against all supported\nversions of Python, assuming you have them installed. It is suggested that you\ninstall ``tox`` and regularly run your tests by calling the simple command::\n\n$ tox\n\nMore details on running tests can be found in :ref:`testing`.\n\n.. _tox: https://tox.readthedocs.org/en/latest/\n\nContributing Code\n=================\n\nWe love getting new instruments and new functionality! When sending\nin pull requests, however, it helps us out a lot in maintaining InstrumentKit\nas a usable library if you can do a couple things for us with your submission:\n\n- Make sure code follows `PEP 8`_ as best as possible. This helps keep the\n  code readable and maintainable.\n- Document properties and methods, including units where appropriate.\n- Contributed classes should feature complete code coverage to prevent future\n  changes from breaking functionality. This is especially important if the lead\n  developers do not have access to the physical hardware.\n- Please use :ref:`property_factories` when appropriate, to consolidate parsing\n  logic into a small number of easily-tested functions. This will also reduce\n  the number of tests required to be written.\n\nWe can help with any and all of these, so please ask, and thank you for helping\nmake InstrumentKit even better.\n\n.. _PEP 8: http://legacy.python.org/dev/peps/pep-0008/\n"
  },
  {
    "path": "doc/source/devguide/testing.rst",
    "content": "================================\nTesting Instrument Functionality\n================================\n\n.. currentmodule:: instruments.tests\n\nOverview\n========\n\nWhen developing new instrument classes, or adding functionality to existing\ninstruments, it is important to also add automated checks for the correctness\nof the new functionality. Such tests serve two distinct purposes:\n\n- Ensures that the protocol for each instrument is being followed correctly,\n  even with changes in the underlying InstrumentKit behavior.\n- Ensures that the API seen by external users is kept stable and consistent.\n\nThe former is especially important for instrument control, as the developers\nof InstrumentKit will not, in general, have access to each instrument that\nis supported--- we rely on automated testing to ensure that future changes\ndo not cause invalid or undesired operation.\n\nFor InstrumentKit, we rely heavily on `pytest`_, a mature and flexible\nunit-testing framework for Python. When run from the command line via\n``pytest``, or when run by Travis CI, pytest will automatically execute\nfunctions and methods whose names start with ``test`` in packages, modules\nand classes whose names start with ``test`` or ``Test``, depending. (Please\nsee the `pytest`_ documentation for full details, as this is not intended\nto be a guide to pytest so much as a guide to how we use it in IK.)\nBecause of this, we keep all test cases in the ``instruments.tests``\npackage, under a subpackage named for the particular manufacturer,\nsuch as ``instruments.tests.test_srs``. The tests for each instrument should\nbe contained within its own file. Please see current tests as an example. If\nthe number of tests for a given instrument is numerous, please consider making\nmodules within a manufacturer test subpackage for each particular device.\n\nBelow, we discuss two distinct kinds of unit tests: those that check\nthat InstrumentKit functionality such as :ref:`property_factories` work correctly\nfor new instruments, and those that check that existing instruments produce\ncorrect protocols.\n\ntox Based Testing\n=================\n\nWhen submitting a PR, tests are run through the tool ``tox``. It helps to\nprovide some isolation between your source code and test code, and gives you\nquick access to separate dedicated test venvs.\n\nWhile ``tox`` will setup and manage its virtual environments automatically,\nyou will need a copy of each major version of Python you wish to test against.\nI suggest you use `pyenv`_ to do this. You can install this tool via the\n`pyenv-installer`_ tool. After installation, each Python version can be\ninstalled via the following pattern:\n\n.. code-block:: console\n\n    $ pyenv install 3.9.21\n\nAfterwards, you can use ``tox`` to run the tests under that specific Python\nenvironment:\n\n.. code-block:: console\n\n    $ tox -e py39,py39-numpy\n\nHere we have specified two ``tox`` environments that will be run: both under\nPython 3.8, one with ``numpy`` installed and the other without.\n\n``py39`` can be subsituted for other supported versions of Python, assuming\nthey have been installed.\n\nYou can also run all defined ``tox`` environments by simply running:\n\n.. code-block:: console\n\n    $ tox\n\nMock Instruments\n================\n\nTODO\n\nExpected Protocols\n==================\n\nAs an example of asserting correctness of implemented protocols, let's consider\na simple test case for :class:`instruments.srs.SRSDG645`::\n\n\tdef test_srsdg645_output_level():\n\t    \"\"\"\n\t    SRSDG645: Checks getting/setting unitful ouput level.\n\t    \"\"\"\n\t    with expected_protocol(ik.srs.SRSDG645,\n\t            [\n\t                \"LAMP? 1\",\n\t                \"LAMP 1,4.0\",\n\t            ], [\n\t                \"3.2\"\n\t            ],\n\t            sep=\"\\n\"\n\t    ) as ddg:\n\t        unit_eq(ddg.output['AB'].level_amplitude, u.Quantity(3.2, \"V\"))\n\t        ddg.output['AB'].level_amplitude = 4.0\n\nHere, we see that the test has a name beginning with ``test_``, has a simple\ndocstring that will be printed in reports on failing tests, and then has a\ncall to :func:`expected_protocol`. The latter consists of specifying an\ninstrument class, here given as ``ik.srs.DG645``, then a list of expected\noutputs and playback to check property accessors.\n\nNote that :func:`expected_protocol` acts as a context manager, such that it will,\nat the end of the indented block, assert the correct operation of the contents of\nthat block. In this example, the second argument to :func:`expected_protocol`\nspecifies that the instrument class should have sent out two strings,\n``\"LAMP? 1\"`` and ``LAMP 1,4.0``, during the block, and should act correctly\nwhen given an answer of ``\"3.2\"`` back from the instrument. The third parameter,\n``sep`` specifies what will be appended to the end of each lines in the\nprevious parameters. This lets you specify the termination character that\nwill be used in the communication without having to write it out each and\nevery time.\n\nProtocol Assertion Functions\n----------------------------\n\n.. autofunction:: expected_protocol\n\n.. _pytest: https://docs.pytest.org/en/latest/\n.. _pyenv: https://github.com/pyenv/pyenv\n.. _pyenv-installer: https://github.com/pyenv/pyenv-installer\n"
  },
  {
    "path": "doc/source/devguide/util_fns.rst",
    "content": "=============================\nUtility Functions and Classes\n=============================\n\n.. currentmodule:: instruments.util_fns\n\nUnit Handling\n=============\n\n.. autofunction:: assume_units\n\n.. autofunction:: split_unit_str\n\n.. autofunction:: convert_temperature\n\nEnumerating Instrument Functionality\n====================================\n\nTo expose parts of an instrument or device in a Python-ic way, the\n:class:`ProxyList` class can be used to emulate a list type by calling the\ninitializer for some inner class. This is used to expose everything from\nchannels to axes.\n\n.. _property_factories:\n\nProperty Factories\n==================\n\nTo help expose instrument properties in a consistent and predictable manner,\nInstrumentKit offers several functions that return instances of `property`\nthat are backed by the :meth:`~instruments.Instrument.sendcmd` and\n:meth:`~instruments.Instrument.query` protocol. These factories assume\na command protocol that at least resembles the SCPI style::\n\n    -> FOO:BAR?\n    <- 42\n    -> FOO:BAR 6\n    -> FOO:BAR?\n    <- 6\n\nIt is recommended to use the property factories whenever possible to help\nreduce the amount of copy-paste throughout the code base. The factories allow\nfor a centralized location for input/output error checking, units handling,\nand type conversions. In addition, improvements to the property factories\nbenefit all classes that use it.\n\nLets say, for example, that you were writing a class for a power supply. This\nclass might require these two properties: ``output`` and ``voltage``. The first\nwill be used to enable/disable the output on the power supply, while the second\nwill be the desired output voltage when the output is enabled. The first lends\nitself well to a `bool_property`. The output voltage property corresponds with\na physical quantity (voltage, of course) and so it is best to use either\n`unitful_property` or `bounded_unitful_property`, depending if you wish to\nbound user input to some set limits. `bounded_unitful_property` can take\neither hard-coded set limits, or it can query the instrument during runtime\nto determine what those bounds are, and constrain user input to within them.\n\nExamples\n--------\n\nThese properties, when implemented in your class, might look like this::\n\n    output = bool_property(\n        \"OUT\",\n        inst_true=\"1\",\n        inst_false=\"0\",\n        doc=\"\"\"\n        Gets/sets the output status of the power supply\n\n        :type: `bool`\n        \"\"\"\n    )\n\n    voltage, voltage_min, voltage_max = bounded_unitful_property(\n        voltage = unitful_property(\n        \"VOLT\",\n        u.volt,\n        valid_range=(0*u.volt, 10*u.volt)\n        doc=\"\"\"\n        Gets/sets the output voltage.\n\n        :units: As specified, or assumed to be :math:`\\\\text{V}` otherwise.\n        :type: `float` or `~pint.Quantity`\n        \"\"\"\n    )\n\nThe most difficult to use parameters for the property factories are\n``input_decoration`` and ``output_decoration``. These are callable objects\nthat will be applied to the data immediately after receiving it from the\ninstrument (input) or before it is inserted into the string that will be sent\nout to the instrument (output).\n\nUsing `enum_property` as the simple example, a frequent use case for\n``input_decoration`` will be to convert a `str` containing a numeric digit\ninto an actual `int` so that it can be looked up in `enum.IntEnum`. Here is\nan example of this::\n\n    class Mode(IntEnum):\n\n        \"\"\"\n        Enum containing valid output modes of the ABC123 instrument\n        \"\"\"\n        foo = 0\n        bar = 1\n        bloop = 2\n\n    mode = enum_property(\n        \"MODE\",\n        enum=Mode,\n        input_decoration=int,\n        set_fmt=\"{}={}\",\n        doc=\"\"\"\n        Gets/sets the output mode of the ABC123 instrument\n\n        :rtype: `ABC123.Mode`\n        \"\"\"\n    )\n\nSo in this example, when querying the ``mode`` property, the string ``MODE?``\nwill first be sent to the instrument, at which point it will return one of\n``\"0\"``, ``\"1\"``, or ``\"2\"``. However, before this value can be used to get\nthe correct enum value, it needs to be converted into an `int`. This is what\n``input_decoration`` is used for. Since `int` is callable and can convert\na `str` to an `int`, this accomplishes exactly what we're looking for.\n\nPretty much anything callable can be passed into these parameters. Here is\nan example using a lambda function with a `unitful_property` taken from\nthe `~instruments.thorlabs.TC200` class::\n\n    temperature = unitful_property(\n        \"tact\",\n        units=u.degC,\n        readonly=True,\n        input_decoration=lambda x: x.replace(\n            \" C\", \"\").replace(\" F\", \"\").replace(\" K\", \"\"),\n        doc=\"\"\"\n        Gets the actual temperature of the sensor\n\n        :units: As specified (if a `~pint.Quantity`) or assumed\n            to be of units degrees C.\n        :type: `~pint.Quantity` or `int`\n        :return: the temperature (in degrees C)\n        :rtype: `~pint.Quantity`\n        \"\"\"\n    )\n\nAn alternative to lambda functions is passing in static methods\n(`staticmethod`).\n\nBool Property\n-------------\n\n.. autofunction:: bool_property\n\nEnum Property\n-------------\n\n.. autofunction:: enum_property\n\nUnitless Property\n-----------------\n\n.. autofunction:: unitless_property\n\nInt Property\n------------\n\n.. autofunction:: int_property\n\nUnitful Property\n----------------\n\n.. autofunction:: unitful_property\n\nBounded Unitful Property\n------------------------\n\n.. autofunction:: bounded_unitful_property\n\nString Property\n---------------\n\n.. autofunction:: string_property\n\n\nNamed Structures\n================\n\nThe :class:`~instruments.named_struct.NamedStruct` class can be used to represent\nC-style structures for serializing and deserializing data.\n\n.. autoclass:: instruments.named_struct.NamedStruct\n\n.. autoclass:: instruments.named_struct.Field\n\n.. autoclass:: instruments.named_struct.Padding\n"
  },
  {
    "path": "doc/source/index.rst",
    "content": "..\n    TODO: put documentation license header here.\n\nWelcome to InstrumentKit Library's documentation!\n=================================================\n\nContents:\n\n.. toctree::\n    :maxdepth: 1\n\n    intro\n    apiref/index\n    devguide/index\n    acknowledgements\n\n\n\nIndices and tables\n==================\n\n* :ref:`genindex`\n* :ref:`modindex`\n* :ref:`search`\n"
  },
  {
    "path": "doc/source/intro.rst",
    "content": "..\n    TODO: put documentation license header here.\n\n============\nIntroduction\n============\n\n**InstrumentKit** allows for the control of scientific instruments in a\nplatform-independent manner, abstracted from the details of how the instrument\nis connected. In particular, InstrumentKit supports connecting to instruments\nvia serial port (including USB-based virtual serial connections), GPIB, USBTMC,\nTCP/IP or by using the VISA layer.\n\nInstalling\n==========\n\nDependencies\n------------\n\nMost of the required and optional dependencies can be obtained using  ``pip``.\n\nGetting Started\n===============\n\nInstruments and Instrument Classes\n----------------------------------\n\nEach make and model of instrument that is supported by InstrumentKit is\nrepresented by a specific class, as documented in the :ref:`apiref`.\nInstruments that offer common functionality, such as multimeters, are\nrepresented by base classes, such that specific instruments can be exchanged\nwithout affecting code, so long as the proper functionality is provided.\n\nFor some instruments, a specific instrument class is not needed, as the\n:ref:`apiref-generic_scpi` classes can be used to expose functionality of these\ninstruments. If you don't see your specific instrument listed, then, please\ncheck in the instrument's manual whether it uses a standard set of SCPI\ncommands.\n\nConnecting to Instruments\n-------------------------\n\nEach instrument class in InstrumentKit is constructed using a *communicator*\nclass that wraps a file-like object with additional information about newlines,\nterminators and other useful details. Most of the time, it is easiest to not\nworry with creating communicators directly, as convienence methods are provided\nto quickly connect to instruments over a wide range of common communication\nprotocols and physical connections.\n\nFor instance, to connect to a generic SCPI-compliant multimeter using a\n`Galvant Industries GPIB-USB adapter`_, the\n`~instruments.Instrument.open_gpibusb` method can be used::\n\n>>> import instruments as ik\n>>> inst = ik.generic_scpi.SCPIMultimeter.open_gpibusb(\"/dev/ttyUSB0\", 1)\n\nSimilarly, many instruments connected by USB use an FTDI or similar chip to\nemulate serial ports, and can be connected using the\n`~instruments.Instrument.open_serial` method by specifying the serial port\ndevice file (on Linux) or name (on Windows) along with the baud rate of the\nemulated port::\n\n>>> inst = ik.generic_scpi.SCPIMultimeter.open_serial(\"COM10\", 115200)\n\nAs a convienence, an instrument connection can also be specified using a\nuniform resource identifier (URI) string::\n\n>>> inst = ik.generic_scpi.SCPIMultimeter.open_from_uri(\"tcpip://192.168.0.10:4100:)\n\nInstrument connection URIs of this kind are useful for storing in configuration\nfiles, as the same method, `~instruments.Instrument.open_from_uri`, is used,\nregardless of the communication protocol and physical connection being used.\nInstrumentKit provides special support for this usage, and can load instruments\nfrom specifications listed in a YAML-formatted configuration file. See the\n`~instruments.load_instruments` function for more details.\n\n.. _Galvant Industries GPIB-USB adapter: http://galvant.ca/shop/gpibusb/\n\n\nUsing Connected Instruments\n---------------------------\n\nOnce connected, functionality of each instrument is exposed by methods and\nproperties of the instrument object. For instance, the name of an instrument\ncan be queried by getting the ``name`` property::\n\n>>> print(inst.name)\n\nFor details of how to use each instrument, please see the :ref:`apiref` entry\nfor that instrument's class. If that class does not implement a given command,\nraw commands and queries can be issued by using the\n`~instruments.Instrument.sendcmd` and `~instruments.Instrument.query` methods,\nrespectively::\n\n>>> inst.sendcmd(\"DATA\") # Send command with no response\n>>> resp = inst.query(\"*IDN?\") # Send command and retrieve response\n\nOS-Specific Instructions\n========================\n\nLinux\n-----\n\nRaw USB Device Configuration\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nTo enable writing to a USB device in raw or usbtmc mode, the device file\nmust be readable writable by users. As this is not normally the default, you\nneed to add rules to ``/etc/udev/rules.d`` to override the default permissions.\nFor instance, to add a Tektronix DPO 4104 oscilloscope with world-writable\npermissions, add the following to rules.d::\n\n    ATTRS{idVendor}==\"0699\", ATTRS{idProduct}==\"0401\", SYMLINK+=\"tekdpo4104\", MODE=\"0666\"\n\n.. warning::\n    This configuration causes the USB device to be world-writable. Do not do\n    this on a multi-user system with untrusted users.\n"
  },
  {
    "path": "license/AUTHOR.TXT",
    "content": "Original author:\n\nSteven Casagrande\nstevencasagrande@gmail.com\ntwitter.com/stevecasagrande\n\n2012-2025\n"
  },
  {
    "path": "license/LICENSE.TXT",
    "content": "GNU AFFERO GENERAL PUBLIC LICENSE\n\nVersion 3, 19 November 2007\n\nCopyright © 2007 Free Software Foundation, Inc. <http://fsf.org/>\nEveryone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.\nPreamble\n\nThe GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software.\n\nThe licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users.\n\nWhen we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things.\n\nDevelopers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software.\n\nA secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public.\n\nThe GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version.\n\nAn older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license.\n\nThe precise terms and conditions for copying, distribution and modification follow.\nTERMS AND CONDITIONS\n0. Definitions.\n\n\"This License\" refers to version 3 of the GNU Affero General Public License.\n\n\"Copyright\" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks.\n\n\"The Program\" refers to any copyrightable work licensed under this License. Each licensee is addressed as \"you\". \"Licensees\" and \"recipients\" may be individuals or organizations.\n\nTo \"modify\" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a \"modified version\" of the earlier work or a work \"based on\" the earlier work.\n\nA \"covered work\" means either the unmodified Program or a work based on the Program.\n\nTo \"propagate\" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well.\n\nTo \"convey\" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying.\n\nAn interactive user interface displays \"Appropriate Legal Notices\" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion.\n1. Source Code.\n\nThe \"source code\" for a work means the preferred form of the work for making modifications to it. \"Object code\" means any non-source form of a work.\n\nA \"Standard Interface\" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language.\n\nThe \"System Libraries\" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A \"Major Component\", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it.\n\nThe \"Corresponding Source\" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work.\n\nThe Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source.\n\nThe Corresponding Source for a work in source code form is that same work.\n2. Basic Permissions.\n\nAll rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law.\n\nYou may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you.\n\nConveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary.\n3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\nNo covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures.\n\nWhen you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures.\n4. Conveying Verbatim Copies.\n\nYou may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program.\n\nYou may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee.\n5. Conveying Modified Source Versions.\n\nYou may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified it, and giving a relevant date.\n    b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to \"keep intact all notices\".\n    c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it.\n    d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so.\n\nA compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an \"aggregate\" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate.\n6. Conveying Non-Source Forms.\n\nYou may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange.\n    b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge.\n    c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b.\n    d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements.\n    e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d.\n\nA separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work.\n\nA \"User Product\" is either (1) a \"consumer product\", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, \"normally used\" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product.\n\n\"Installation Information\" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made.\n\nIf you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM).\n\nThe requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network.\n\nCorresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying.\n7. Additional Terms.\n\n\"Additional permissions\" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions.\n\nWhen you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission.\n\nNotwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or\n    b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or\n    c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or\n    d) Limiting the use for publicity purposes of names of licensors or authors of the material; or\n    e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or\n    f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors.\n\nAll other non-permissive additional terms are considered \"further restrictions\" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying.\n\nIf you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms.\n\nAdditional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way.\n8. Termination.\n\nYou may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11).\n\nHowever, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.\n\nMoreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.\n\nTermination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10.\n9. Acceptance Not Required for Having Copies.\n\nYou are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so.\n10. Automatic Licensing of Downstream Recipients.\n\nEach time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License.\n\nAn \"entity transaction\" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts.\n\nYou may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it.\n11. Patents.\n\nA \"contributor\" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's \"contributor version\".\n\nA contributor's \"essential patent claims\" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, \"control\" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License.\n\nEach contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version.\n\nIn the following three paragraphs, a \"patent license\" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To \"grant\" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party.\n\nIf you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. \"Knowingly relying\" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid.\n\nIf, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it.\n\nA patent license is \"discriminatory\" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007.\n\nNothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law.\n12. No Surrender of Others' Freedom.\n\nIf conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program.\n13. Remote Network Interaction; Use with the GNU General Public License.\n\nNotwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph.\n\nNotwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License.\n14. Revised Versions of this License.\n\nThe Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.\n\nEach version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License \"or any later version\" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation.\n\nIf the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program.\n\nLater license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version.\n15. Disclaimer of Warranty.\n\nTHERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n16. Limitation of Liability.\n\nIN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.\n17. Interpretation of Sections 15 and 16.\n\nIf the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee.\n\nEND OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "matlab/matlab-example.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Loading Instruments from MATLAB #\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"MATLAB 2016a supports calling into Python, such that we can open instruments and communicate with them from within MATLAB applications. This takes a little bit of work, however, due to bugs in MATLAB's Python interface. Here, we'll demonstrate using the ``open_instrument.m`` MATLAB function to open instruments from their URIs.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stderr\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"\\n\",\n      \"  File build/bdist.linux-x86_64/egg/serial/serialposix.py, line 294, in open\\n\",\n      \"\\n\",\n      \"  File build/bdist.linux-x86_64/egg/serial/serialutil.py, line 180, in __init__\\n\",\n      \"\\n\",\n      \"  File build/bdist.linux-x86_64/egg/instruments/abstract_instruments/comm/serial_manager.py, line 64, in new_serial_connection\\n\",\n      \"\\n\",\n      \"  File build/bdist.linux-x86_64/egg/instruments/abstract_instruments/instrument.py, line 438, in open_serial\\n\",\n      \"\\n\",\n      \"  File build/bdist.linux-x86_64/egg/instruments/abstract_instruments/instrument.py, line 355, in open_from_uri\\n\",\n      \"\\n\",\n      \"  File <string>, line 1, in <module>\\n\",\n      \"Python Error: SerialException: [Errno 2] could not open port /dev/ttyUSB0: [Errno 2] No such file or directory: '/dev/ttyUSB0'\\n\",\n      \"\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"instrument = open_instrument('phasematrix.PhaseMatrixFSW0020', 'serial:/dev/ttyUSB0')\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": []\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"MLegacy Kernel\",\n   \"language\": \"python\",\n   \"name\": \"kernel_mlegacy\"\n  },\n  \"language_info\": {\n   \"file_extension\": \".m\",\n   \"help_links\": [\n    {\n     \"text\": \"MetaKernel Magics\",\n     \"url\": \"https://github.com/calysto/metakernel/blob/master/metakernel/magics/README.md\"\n    }\n   ],\n   \"mimetype\": \"text/x-matlab\",\n   \"name\": \"matlab\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "matlab/open_instrument.m",
    "content": "function instrument = open_instrument(name, uri)\n% open_instrument Opens an instrument given its InstrumentKit URI.\n%\n% WARNING: this function can execute arbitrary Python code, so do *not*\n% call for untrusted URIs.\n\n    % We need to use py.eval, since using py.* directly doesn't work for\n    % @classmethods. This presents two drawbacks: first, we need to manage\n    % the Python globals() dict directly, and second, we need to do string\n    % manipulation to make the line of source code to evaluate.\n\n    % To manage globals() ourselves, we need to make a new dict() that we will\n    % pass to py.eval.\n    namespace = py.dict();\n\n    % Next, py.eval doesn't respect MATLAB's import py.* command-form function.\n    % Thus, we need to use the __import__  built-in function to return the module\n    % object for InstrumentKit. We'll save it directly into our new namespace,\n    % so that it becomes a global for the next py.eval. Recall that d{'x'} on the\n    % MATLAB side corresponds to d['x'] on the Python side, for d a Python dict().\n    namespace{'ik'} = py.eval('__import__(\"instruments\")', namespace);\n\n    % Finally, we're equipped to run the open_from_uri @classmethod. To do so,\n    % we want to evaluate a line that looks like:\n    %     ik.holzworth.Holzworth.HS9000.open_from_uri(r\"serial:/dev/ttyUSB0\")\n    % We use r to cut down on accidental escaping errors, but importantly, this will\n    % do *nothing* to cut down intentional abuse of eval.\n    instrument = py.eval(['ik.' name '.open_from_uri(r\"' uri '\")'], namespace);\n\nend\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"setuptools>=61.2\", \"setuptools_scm>=9.2\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nname = \"instrumentkit\"\ndescription = \"Test and measurement communication library\"\nauthors = [{name = \"Steven Casagrande\", email = \"stevencasagrande@gmail.com\"}]\nlicense = {text = \"AGPLv3\"}\nclassifiers = [\n    \"Development Status :: 4 - Beta\",\n    \"Programming Language :: Python\",\n    \"Programming Language :: Python :: 3\",\n    \"Programming Language :: Python :: 3.9\",\n    \"Programming Language :: Python :: 3.10\",\n    \"Programming Language :: Python :: 3.11\",\n    \"Programming Language :: Python :: 3.12\",\n    \"Programming Language :: Python :: 3.13\",\n    \"Operating System :: OS Independent\",\n    \"Intended Audience :: Science/Research\",\n    \"Intended Audience :: Manufacturing\",\n    \"Topic :: Scientific/Engineering\",\n    \"Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator\",\n    \"Topic :: Software Development :: Libraries\",\n]\ndependencies = [\n    \"pint>=0.21.0\",\n    \"pyserial>=3.3\",\n    \"python-usbtmc\",\n    \"python-vxi11>=0.8\",\n    \"pyusb>=1.0\",\n    \"pyvisa>=1.9\",\n    \"ruamel.yaml>=0.18\",\n    \"typing_extensions>=4.0.1\",\n    \"standard-xdrlib;python_version>='3.13'\",\n]\ndynamic = [\"version\"]\n\n[project.readme]\nfile = \"README.rst\"\ncontent-type = \"text/x-rst\"\n\n[project.urls]\nHomepage = \"https://www.github.com/instrumentkit/InstrumentKit\"\n\n[project.optional-dependencies]\nnumpy = [\"numpy\"]\ndev = [\n    \"coverage\",\n    \"hypothesis~=6.139.2\",\n    \"mock\",\n    \"pytest-cov\",\n    \"pytest-mock\",\n    \"pytest-xdist\",\n    \"pytest~=8.4.0\",\n    \"pyvisa-sim\",\n    \"six\",\n]\ndocs = [\n    \"sphinx~=8.2.0\",\n    \"sphinx_rtd_theme\"\n]\n\n[tool.setuptools]\ninclude-package-data = true\npackage-dir = {\"\" = \"src\"}\n\n[tool.setuptools.packages.find]\nwhere = [\"src\"]\nnamespaces = false\n\n[tool.distutils.bdist_wheel]\nuniversal = 1\n\n[tool.setuptools_scm]\nwrite_to = \"src/instruments/_version.py\"\n"
  },
  {
    "path": "src/instruments/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nDefines globally-available subpackages and symbols for the instruments package.\n\"\"\"\n\n# IMPORTS ####################################################################\n\n__all__ = [\"units\"]\n\n\nfrom . import abstract_instruments\nfrom .abstract_instruments import Instrument\n\nfrom . import agilent\nfrom . import aimtti\nfrom . import comet\nfrom . import dressler\nfrom . import delta_elektronika\nfrom . import generic_scpi\nfrom . import fluke\nfrom . import gentec_eo\nfrom . import glassman\nfrom . import hcp\nfrom . import holzworth\nfrom . import hp\nfrom . import keithley\nfrom . import lakeshore\nfrom . import mettler_toledo\nfrom . import minghe\nfrom . import newport\nfrom . import oxford\nfrom . import phasematrix\nfrom . import pfeiffer\nfrom . import picowatt\nfrom . import qubitekk\nfrom . import rigol\nfrom . import srs\nfrom . import sunpower\nfrom . import tektronix\nfrom . import teledyne\nfrom . import thorlabs\nfrom . import toptica\nfrom . import yokogawa\n\nfrom .config import load_instruments\nfrom .units import ureg as units\n"
  },
  {
    "path": "src/instruments/abstract_instruments/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing instrument abstract base classes and communication layers\n\"\"\"\n\nfrom .instrument import Instrument\nfrom .electrometer import Electrometer\nfrom .function_generator import FunctionGenerator\nfrom .multimeter import Multimeter\nfrom .oscilloscope import Oscilloscope\nfrom .optical_spectrum_analyzer import OpticalSpectrumAnalyzer\nfrom .power_supply import PowerSupply\n"
  },
  {
    "path": "src/instruments/abstract_instruments/comm/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing communication layers\n\"\"\"\n\nfrom .abstract_comm import AbstractCommunicator\n\nfrom .file_communicator import FileCommunicator\nfrom .gpib_communicator import GPIBCommunicator\nfrom .loopback_communicator import LoopbackCommunicator\nfrom .serial_communicator import SerialCommunicator\nfrom .socket_communicator import SocketCommunicator\nfrom .usb_communicator import USBCommunicator\nfrom .usbtmc_communicator import USBTMCCommunicator\nfrom .visa_communicator import VisaCommunicator\nfrom .vxi11_communicator import VXI11Communicator\n"
  },
  {
    "path": "src/instruments/abstract_instruments/comm/abstract_comm.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides an abstract base class for file-like communication layer classes\n\"\"\"\n\n# IMPORTS ####################################################################\n\nimport abc\nimport codecs\nimport logging\nimport struct\n\n# CLASSES ####################################################################\n\n\nclass AbstractCommunicator(metaclass=abc.ABCMeta):\n    \"\"\"\n    Abstract base class for electrometer instruments.\n\n    All applicable concrete instruments should inherit from this ABC to\n    provide a consistent interface to the user.\n    \"\"\"\n\n    # INITIALIZER #\n\n    def __init__(self, *args, **kwargs):  # pylint: disable=unused-argument\n        self._debug = False\n\n        # Create a new logger for the module containing the concrete\n        # subclass that we're a part of.\n        self._logger = logging.getLogger(type(self).__module__)\n\n        # Ensure that there's at least something setup to receive logs.\n        self._logger.addHandler(logging.NullHandler())\n\n    # FORMATTING METHODS #\n\n    def __repr__(self):\n        try:\n            addr = repr(self.address)\n        except:  # noqa: E722\n            addr = \"unknown\"\n        return f\"<{type(self).__name__} object at 0x{id(self):X} connected to {addr}>\"\n\n    # CONCRETE PROPERTIES #\n\n    @property\n    def debug(self):\n        \"\"\"\n        Enables or disables debug support. If active, all messages sent to\n        or received from this communicator are logged to the Python logging\n        service, with the logger name given by the module of the current\n        communicator.\n        Generating log messages for each exchanged command is slow, so these\n        log messages are suppressed by default.\n\n        Note that you must turn on logging to at least the DEBUG level in order\n        to see these messages. For instance:\n\n        >>> import logging\n        >>> logging.basicConfig(level=logging.DEBUG)\n        \"\"\"\n        return self._debug\n\n    @debug.setter\n    def debug(self, newval):\n        self._debug = bool(newval)\n\n    # ABSTRACT PROPERTIES #\n\n    @property\n    @abc.abstractmethod\n    def address(self):\n        \"\"\"\n        Reads or changes the current address for this communicator.\n        \"\"\"\n        raise NotImplementedError\n\n    @address.setter\n    @abc.abstractmethod\n    def address(self, newval):\n        raise NotImplementedError\n\n    @property\n    @abc.abstractmethod\n    def terminator(self):\n        \"\"\"\n        Reads or changes the EOS termination.\n        \"\"\"\n        raise NotImplementedError\n\n    @terminator.setter\n    @abc.abstractmethod\n    def terminator(self, newval):\n        raise NotImplementedError\n\n    @property\n    @abc.abstractmethod\n    def timeout(self):\n        \"\"\"\n        Get the connection interface timeout.\n        \"\"\"\n        raise NotImplementedError\n\n    @timeout.setter\n    @abc.abstractmethod\n    def timeout(self, newval):\n        raise NotImplementedError\n\n    # ABSTRACT METHODS #\n\n    @abc.abstractmethod\n    def read_raw(self, size=-1):\n        \"\"\"\n        Read bytes in from the connection.\n\n        :param int size: The number of bytes to read in from the\n            connection.\n\n        :return: The read bytes\n        :rtype: `bytes`\n        \"\"\"\n\n    @abc.abstractmethod\n    def write_raw(self, msg):\n        \"\"\"\n        Write bytes to the connection.\n\n        :param bytes msg: Bytes to be sent to the instrument over the\n            connection.\n        \"\"\"\n\n    @abc.abstractmethod\n    def _sendcmd(self, msg):\n        \"\"\"\n        Sends a message to the connected device, handling all proper\n        termination characters and secondary commands as required.\n\n        Note that this is called by :class:`AbstractCommunicator.sendcmd`,\n        which also handles debug, event and capture support.\n        \"\"\"\n\n    @abc.abstractmethod\n    def _query(self, msg, size=-1):\n        \"\"\"\n        Send a string to the connected instrument using sendcmd and read the\n        response. This is an abstract method because there are situations where\n        information contained in the sent command is needed for reading logic.\n\n        An example of this is the Galvant Industries GPIB adapter where if\n        you are connected to an older instrument and the query command does not\n        contain a `?`, then the command `+read` needs to be send to force the\n        instrument to send its response.\n\n        Note that this is called by :class:`AbstractCommunicator.query`,\n        which also handles debug, event and capture support.\n        \"\"\"\n\n    @abc.abstractmethod\n    def flush_input(self):\n        \"\"\"\n        Instruct the communicator to flush the input buffer, discarding the\n        entirety of its contents.\n        \"\"\"\n        raise NotImplementedError\n\n    # CONCRETE METHODS #\n\n    def write(self, msg, encoding=\"utf-8\"):\n        \"\"\"\n        Write a string to the connection. This string will be converted\n        to `bytes` using the provided encoding method.\n\n        .. seealso:: To send `bytes` in Python 3, see `write_raw`.\n\n        :param str msg: String to be sent to the instrument over the\n            connection.\n        :param str encoding: Encoding to apply on msg to convert the message\n            into bytes\n        \"\"\"\n        self.write_raw(msg.encode(encoding))\n\n    def read(self, size=-1, encoding=\"utf-8\"):\n        \"\"\"\n        Read bytes in from the connection, returning a decoded string\n        using the provided encoding method.\n\n        .. seealso:: To read `bytes` in Python 3, see `read_raw`.\n\n        :param int size: The number of bytes to read in from the\n            connection.\n        :param str encoding: Encoding that will be applied to the read bytes\n\n        :return: The read string from the connection\n        :rtype: `str`\n        \"\"\"\n        try:\n            codecs.lookup(encoding)\n            return self.read_raw(size).decode(encoding)\n        except LookupError:\n            if encoding == \"IEEE-754/64\":\n                return struct.unpack(\">d\", self.read_raw(size))[0]\n            else:\n                raise ValueError(f\"Encoding {encoding} is not currently supported.\")\n\n    def sendcmd(self, msg):\n        \"\"\"\n        Sends the incoming msg down to the wrapped file-like object\n        but appends any other commands or termination characters required\n        by the communication.\n\n        This differs from the communicator .write method which directly exposes\n        the communication channel without appending other data.\n        \"\"\"\n        if self.debug:\n            self._logger.debug(\" <- %s\", repr(msg))\n        self._sendcmd(msg)\n\n    def query(self, msg, size=-1):\n        \"\"\"\n        Send a string to the connected instrument using sendcmd and read the\n        response. This is an abstract method because there are situations where\n        information contained in the sent command is needed for reading logic.\n\n        An example of this is the Galvant Industries GPIB adapter where if\n        you are connected to an older instrument and the query command does not\n        contain a `?`, then the command `+read` needs to be send to force the\n        instrument to send its response.\n        \"\"\"\n        if self.debug:\n            self._logger.debug(\" <- %s\", repr(msg))\n        resp = self._query(msg, size)\n        if self.debug:\n            self._logger.debug(\" -> %s\", repr(resp))\n        return resp\n"
  },
  {
    "path": "src/instruments/abstract_instruments/comm/file_communicator.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides a communication layer for an instrument with a file on the filesystem\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport errno\nimport io\nimport time\nimport logging\n\nfrom instruments.abstract_instruments.comm import AbstractCommunicator\n\nlogger = logging.getLogger(__name__)\nlogger.addHandler(logging.NullHandler())\n\n# CLASSES #####################################################################\n\n\nclass FileCommunicator(io.IOBase, AbstractCommunicator):\n    \"\"\"\n    Wraps a `file` object, providing ``sendcmd`` and ``query`` methods,\n    while passing everything else through.\n\n    :param filelike: File or name of a file to be wrapped as a communicator.\n        Any file-like object wrapped by this class **must** support both\n        reading and writing. If using the `open` builtin function, the mode\n        ``rb+`` is recommended, and has been tested to work with character\n        devices under Linux.\n    :type filelike: `str` or `file`\n    \"\"\"\n\n    def __init__(self, filelike):\n        super().__init__(self)\n        if isinstance(filelike, str):  # pragma: no cover\n            filelike = open(filelike, \"rb+\")\n\n        self._filelike = filelike\n        self._terminator = \"\\n\"\n        self._testing = False\n\n    # PROPERTIES #\n\n    @property\n    def address(self):\n        \"\"\"\n        Gets the name of the filesystem file that this communicator has been\n        opened against.\n\n        :type: `str`\n        \"\"\"\n        if hasattr(self._filelike, \"name\"):\n            return self._filelike.name\n\n        return None\n\n    @address.setter\n    def address(self, newval):\n        raise NotImplementedError(\n            \"Changing addresses of a file communicator\" \" is not yet supported.\"\n        )\n\n    @property\n    def terminator(self):\n        \"\"\"\n        Gets/sets the end-of-line termination character.\n\n        :type: `str`\n        \"\"\"\n        return self._terminator\n\n    @terminator.setter\n    def terminator(self, newval):\n        if isinstance(newval, bytes):\n            newval = newval.decode(\"utf-8\")\n        if not isinstance(newval, str) or len(newval) > 1:\n            raise TypeError(\n                \"Terminator for socket communicator must be \"\n                \"specified as a single character string.\"\n            )\n        self._terminator = newval\n\n    @property\n    def timeout(self):\n        \"\"\"\n        Getting and setting the timeout property for `FileCommunicator` is\n        not supported.\n        \"\"\"\n        raise NotImplementedError\n\n    @timeout.setter\n    def timeout(self, newval):\n        raise NotImplementedError\n\n    # FILE-LIKE METHODS #\n\n    def close(self):\n        \"\"\"\n        Close connection to the filesystem file.\n        \"\"\"\n        try:\n            self._filelike.close()\n        except OSError as e:  # pragma: no cover\n            logger.warning(\"Failed to close file, exception: %s\", repr(e))\n\n    def read_raw(self, size=-1):\n        \"\"\"\n        Read bytes in from the file.\n\n        :param int size: The number of bytes to be read in from the file\n        :rtype: `bytes`\n        \"\"\"\n        if size >= 0:\n            return self._filelike.read(size)\n        elif size == -1:\n            result = b\"\"\n            c = b\"\"\n            while c != self._terminator.encode(\"utf-8\"):\n                c = self._filelike.read(1)\n                if c == b\"\":\n                    break\n                if c != self._terminator.encode(\"utf-8\"):\n                    result += c\n            return result\n        else:\n            raise ValueError(\"Must read a positive value of characters.\")\n\n    def write_raw(self, msg):\n        \"\"\"\n        Write bytes to the file.\n\n        :param bytes msg: Bytes to be written to file\n        \"\"\"\n        self._filelike.write(msg)\n\n    def seek(self, offset):\n        \"\"\"\n        Seek to a specified offset in the file. Useful for when using a static\n        file, but less so when communicating with a physical instrument\n        via a unix socket.\n\n        :param int offset: The offset to seek to\n        \"\"\"\n        self._filelike.seek(offset)\n\n    def tell(self):\n        \"\"\"\n        Gets the file's current position.\n\n        :rtype: `int`\n        \"\"\"\n        return self._filelike.tell()\n\n    def flush_input(self):\n        \"\"\"\n        Flush the internal buffer to make sure everything has actually been\n        written to the file. This can be equivalent to a no-op on some\n        filelike objects.\n        \"\"\"\n        self._filelike.flush()\n\n    # METHODS #\n\n    def _sendcmd(self, msg):\n        \"\"\"\n        This is the implementation of ``sendcmd`` for communicating with\n        files on a unix system. This function is in turn wrapped by the\n        concrete method `AbstractCommunicator.sendcmd` to provide consistent\n        logging functionality across all communication layers.\n\n        :param str msg: The command message to send to the instrument\n        \"\"\"\n        msg += self._terminator\n        self.write(msg)\n        try:\n            self.flush()\n        except OSError as e:\n            logger.warning(\"Exception %s occured during flush().\", repr(e))\n\n    def _query(self, msg, size=-1):\n        \"\"\"\n        This is the implementation of ``query`` for communicating with\n        files on a unix system. This function is in turn wrapped by the\n        concrete method `AbstractCommunicator.query` to provide consistent\n        logging functionality across all communication layers.\n\n        :param str msg: The query message to send to the instrument\n        :param int size: The number of bytes to read back from the instrument\n            response.\n        :return: The instrument response to the query\n        :rtype: `str`\n        \"\"\"\n        self.sendcmd(msg)\n        if not self._testing:\n            time.sleep(0.02)  # Give the bus time to respond.\n        resp = b\"\"\n        try:\n            # FIXME: this is slow, but we do it to avoid unreliable\n            #        filelike devices such as some usbtmc-class devices.\n            while True:\n                nextchar = self._filelike.read(1)\n                if not nextchar:\n                    break\n                resp += nextchar\n                if nextchar.endswith(self._terminator.encode(\"utf-8\")):\n                    resp = resp[: -len(self._terminator)]\n                    break\n        except OSError as ex:\n            if ex.errno == errno.ETIMEDOUT:\n                # We don't mind timeouts if resp is nonempty,\n                # and will just return what we have.\n                if not resp:\n                    raise\n            elif ex.errno != errno.EPIPE:\n                raise  # Reraise the existing exception.\n            else:  # Give a more helpful and specific exception.\n                raise OSError(\n                    \"Pipe broken when reading from {}; this probably \"\n                    \"indicates that the driver \"\n                    \"providing the device file is unable to communicate with \"\n                    \"the instrument. Consider restarting the instrument.\".format(\n                        self.address\n                    )\n                )\n        return resp.decode(\"utf-8\")\n"
  },
  {
    "path": "src/instruments/abstract_instruments/comm/gpib_communicator.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides a communication layer for an instrument connected via a Galvant\nIndustries or Prologix GPIB adapter.\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nfrom enum import Enum\nimport io\nimport time\n\nfrom instruments.units import ureg as u\n\nfrom instruments.abstract_instruments.comm import AbstractCommunicator\nfrom instruments.util_fns import assume_units\n\n# CLASSES #####################################################################\n\n\nclass GPIBCommunicator(io.IOBase, AbstractCommunicator):\n    \"\"\"\n    Communicates with a SocketCommunicator or SerialCommunicator object for\n    use with Galvant Industries or Prologix GPIBUSB or GPIBETHERNET adapters.\n\n    It essentially wraps those physical communication layers with the extra\n    overhead required by the GPIB adapters.\n    \"\"\"\n\n    # pylint: disable=too-many-instance-attributes\n    def __init__(self, filelike, gpib_address, model=\"gi\"):\n        super().__init__(self)\n        self._model = self.Model(model)\n        self._file = filelike\n        self._gpib_address = gpib_address\n        self._file.terminator = \"\\r\"\n        if self._model == GPIBCommunicator.Model.gi:\n            self._version = int(self._file.query(\"+ver\"))\n        if self._model == GPIBCommunicator.Model.pl:\n            self._file.sendcmd(\"++auto 0\")\n        self._terminator = None\n        self.terminator = \"\\n\"\n        self._eoi = True\n        self._timeout = 1000 * u.millisecond\n        if self._model == GPIBCommunicator.Model.gi and self._version <= 4:\n            self._eos = 10\n        else:\n            self._eos = \"\\n\"\n\n    # ENUMS #\n\n    class Model(Enum):\n        \"\"\"\n        Enum containing the supported GPIB controller models\n        \"\"\"\n\n        #: Galvant Industries\n        gi = \"gi\"\n        #: Prologix, LLC\n        pl = \"pl\"\n\n    # PROPERTIES #\n\n    @property\n    def address(self):\n        \"\"\"\n        Gets/sets the GPIB address and downstream address associated with\n        the instrument.\n\n        When setting, if specified as an integer, only changes the GPIB\n        address. If specified as a list, the first element changes the GPIB\n        address, while the second is passed downstream.\n\n        Example: [<int>gpib_address, downstream_address]\n\n        Where downstream_address needs to be formatted as appropriate for the\n        connection (eg SerialCommunicator, SocketCommunicator, etc).\n        \"\"\"\n        return self._gpib_address, self._file.address\n\n    @address.setter\n    def address(self, newval):\n        if isinstance(newval, int):\n            if (newval < 1) or (newval > 30):\n                raise ValueError(\"GPIB address must be between 1 and 30.\")\n            self._gpib_address = newval\n        elif isinstance(newval, list):\n            self.address = newval[0]  # Set GPIB address\n            self._file.address = newval[1]  # Send downstream address\n        else:\n            raise TypeError(\"Not a valid input type for Instrument address.\")\n\n    @property\n    def timeout(self):\n        \"\"\"\n        Gets/sets the timeeout of both the GPIB bus and the connection\n        channel between the PC and the GPIB adapter.\n\n        :type: `~pint.Quantity`\n        :units: As specified, or assumed to be of units ``seconds``\n        \"\"\"\n        return self._timeout\n\n    @timeout.setter\n    def timeout(self, newval):\n        newval = assume_units(newval, u.second)\n        if self._model == GPIBCommunicator.Model.gi and self._version <= 4:\n            newval = newval.to(u.second)\n            self._file.sendcmd(f\"+t:{int(newval.magnitude)}\")\n        else:\n            newval = newval.to(u.millisecond)\n            self._file.sendcmd(f\"++read_tmo_ms {int(newval.magnitude)}\")\n        self._file.timeout = newval.to(u.second)\n        self._timeout = newval.to(u.second)\n\n    @property\n    def terminator(self):\n        \"\"\"\n        Gets/sets the GPIB termination character. This can be set to\n        ``\\n``, ``\\r``, ``\\r\\n``, or ``eoi``.\n\n        .. seealso:: `eos` and `eoi` for direct manipulation of these\n            parameters.\n\n        :type: `str`\n        \"\"\"\n        if not self._eoi:\n            return self._terminator\n\n        return \"eoi\"\n\n    @terminator.setter\n    def terminator(self, newval):\n        if isinstance(newval, bytes):\n            newval = newval.decode(\"utf-8\")\n        if isinstance(newval, str):\n            newval = newval.lower()\n\n        if self._model == GPIBCommunicator.Model.gi and self._version <= 4:\n            if newval == \"eoi\":\n                self.eoi = True\n            elif not isinstance(newval, int):\n                if len(newval) == 1:\n                    newval = ord(newval)\n                    self.eoi = False\n                    self.eos = newval\n                else:\n                    raise TypeError(\n                        \"GPIB termination must be integer 0-255 \"\n                        \"represending decimal value of ASCII \"\n                        \"termination character or a string\"\n                        'containing \"eoi\".'\n                    )\n            elif (newval < 0) or (newval > 255):\n                raise ValueError(\n                    \"GPIB termination must be integer 0-255 \"\n                    \"represending decimal value of ASCII \"\n                    \"termination character.\"\n                )\n            else:\n                self.eoi = False\n                self.eos = newval\n                self._terminator = chr(newval)\n        else:\n            if newval != \"eoi\":\n                self.eos = newval\n                self.eoi = False\n                self._terminator = self.eos\n            elif newval == \"eoi\":\n                self.eos = None\n                self._terminator = \"eoi\"\n                self.eoi = True\n\n    @property\n    def eoi(self):\n        \"\"\"\n        Gets/sets the EOI usage status.\n\n        EOI is a dedicated line on the GPIB bus. When used, it is used by\n        instruments to signal that the current byte being transmitted is the\n        last in the message. This avoids the need to use a dedicated\n        termination character such as ``\\n``. Frequently, instruments will\n        use both EOI-signalling and append an end-of-string (EOS) character.\n        Some will only use one or the other.\n\n        .. seealso:: `terminator`, `eos` for more communication termination\n            related properties.\n\n        :type: `bool`\n        \"\"\"\n        return self._eoi\n\n    @eoi.setter\n    def eoi(self, newval):\n        if not isinstance(newval, bool):\n            raise TypeError(\"EOI status must be specified as a boolean\")\n        self._eoi = newval\n        if self._model == GPIBCommunicator.Model.gi and self._version <= 4:\n            self._file.sendcmd(\"+eoi:{}\".format(\"1\" if newval else \"0\"))\n        else:\n            self._file.sendcmd(\"++eoi {}\".format(\"1\" if newval else \"0\"))\n\n    @property\n    def eos(self):\n        \"\"\"\n        Gets/sets the end-of-string (EOS) character.\n\n        Valid EOS settings are ``\\n``, ``\\r``, ``\\r\\n`` and `None`.\n\n        .. seealso:: `terminator`, `eoi` for more communication termination\n            related properties.\n\n        :type: `str` or `None`\n        \"\"\"\n        return self._eos\n\n    @eos.setter\n    def eos(self, newval):\n        if self._model == GPIBCommunicator.Model.gi and self._version <= 4:\n            if isinstance(newval, (str, bytes)):\n                newval = ord(newval)\n            self._file.sendcmd(f\"+eos:{newval}\")\n            self._eos = newval\n        else:\n            if isinstance(newval, int):\n                newval = str(chr(newval))\n            if newval == \"\\r\\n\":\n                self._eos = newval\n                newval = 0\n            elif newval == \"\\r\":\n                self._eos = newval\n                newval = 1\n            elif newval == \"\\n\":\n                self._eos = newval\n                newval = 2\n            elif newval is None:\n                self._eos = newval\n                newval = 3\n            else:\n                raise ValueError(\"EOS must be CRLF, CR, LF, or None\")\n            self._file.sendcmd(f\"++eos {newval}\")\n\n    # FILE-LIKE METHODS #\n\n    def close(self):\n        \"\"\"\n        Close connection to the underlying physical connection channel\n        of the GPIB connection. This is typically a serial connection that\n        is then closed.\n        \"\"\"\n        self._file.close()\n\n    def read_raw(self, size=-1):\n        \"\"\"\n        Read bytes in from the gpibusb connection.\n\n        :param int size: The number of bytes to read in from the\n            connection.\n\n        :return: The read bytes from the connection\n        :rtype: `bytes`\n        \"\"\"\n        return self._file.read_raw(size)\n\n    def read(self, size=-1, encoding=\"utf-8\"):\n        \"\"\"\n        Read characters from wrapped class (ie SocketCommunicator or\n        SerialCommunicator).\n\n        If size = -1, characters will be read until termination character\n        is found.\n\n        GI GPIB adapters always terminate serial connections with a CR.\n        Function will read until a CR is found.\n\n        :param int size: Number of bytes to read\n        :param str encoding: Encoding that will be applied to the read bytes\n\n        :return: Data read from the GPIB adapter\n        :rtype: `str`\n        \"\"\"\n        return self._file.read(size, encoding)\n\n    def write_raw(self, msg):\n        \"\"\"\n        Write bytes to the gpibusb connection.\n\n        :param bytes msg: Bytes to be sent to the instrument over the\n            connection.\n        \"\"\"\n        self._file.write_raw(msg)\n\n    def write(self, msg, encoding=\"utf-8\"):\n        \"\"\"\n        Write data string to GPIB connected instrument.\n\n        :param str msg: String to write to the instrument\n        :param str encoding: Encoding to apply on msg to convert the message\n            into bytes\n        \"\"\"\n        self._file.write(msg, encoding)\n\n    def flush_input(self):\n        \"\"\"\n        Instruct the communicator to flush the input buffer, discarding the\n        entirety of its contents.\n        \"\"\"\n        self._file.flush_input()\n\n    # METHODS #\n\n    def _sendcmd(self, msg):\n        \"\"\"\n        This is the implementation of ``sendcmd`` for communicating with\n        the GPIB adapters. This function is in turn wrapped by the concrete\n        method `AbstractCommunicator.sendcmd` to provide consistent\n        logging functionality across all communication layers.\n\n        :param str msg: The command message to send to the instrument\n        \"\"\"\n        sleep_time = 0.01\n\n        if msg == \"\":\n            return\n        if self._model == GPIBCommunicator.Model.gi:\n            self._file.sendcmd(f\"+a:{str(self._gpib_address)}\")\n        else:\n            self._file.sendcmd(f\"++addr {str(self._gpib_address)}\")\n        time.sleep(sleep_time)\n        self.eoi = self.eoi\n        time.sleep(sleep_time)\n        self.timeout = self.timeout\n        time.sleep(sleep_time)\n        self.eos = self.eos\n        time.sleep(sleep_time)\n        self._file.sendcmd(msg)\n        time.sleep(sleep_time)\n\n    def _query(self, msg, size=-1):\n        \"\"\"\n        This is the implementation of ``query`` for communicating with\n        the GPIB adapters. This function is in turn wrapped by the concrete\n        method `AbstractCommunicator.query` to provide consistent\n        logging functionality across all communication layers.\n\n        The Galvant Industries adaptor is set to automatically get a\n        response if a ``?`` is present in ``msg``. If it is not present,\n        then the adapter will be instructed to get the response from the\n        instrument via the ``+read`` command.\n\n        The Prologix adapter is set to not get a response unless told to do\n        so. It is instructed to get a response from the instrument via the\n        ``++read`` command.\n\n        :param str msg: The query message to send to the instrument\n        :param int size: The number of bytes to read back from the instrument\n            response.\n        :return: The instrument response to the query\n        :rtype: `str`\n        \"\"\"\n        self.sendcmd(msg)\n        if self._model == GPIBCommunicator.Model.gi and \"?\" not in msg:\n            self._file.sendcmd(\"+read\")\n        if self._model == GPIBCommunicator.Model.pl:\n            self._file.sendcmd(\"++read\")\n        return self._file.read(size).strip()\n"
  },
  {
    "path": "src/instruments/abstract_instruments/comm/loopback_communicator.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides a loopback communicator, used for creating unit tests or for opening\ntest connections to explore the InstrumentKit API.\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport io\nimport sys\n\nfrom instruments.abstract_instruments.comm import AbstractCommunicator\n\n# CLASSES #####################################################################\n\n\nclass LoopbackCommunicator(io.IOBase, AbstractCommunicator):\n    \"\"\"\n    Used to provide a loopback connection for an instrument class. The most\n    common use cases for this communicator are writing unit tests, opening\n    test connections to explore the API without having the physical instrument\n    connected, and testing the behaviour of code under development.\n    \"\"\"\n\n    def __init__(self, stdin=None, stdout=None):\n        super().__init__(self)\n        self._terminator = \"\\n\"\n        self._stdout = stdout\n        self._stdin = stdin\n\n    # PROPERTIES #\n\n    @property\n    def address(self):\n        \"\"\"\n        Gets the name of ``stdin``\n\n        :return: `sys.stdin.name`\n        \"\"\"\n        return sys.stdin.name\n\n    @address.setter\n    def address(self, newval):\n        raise NotImplementedError\n\n    @property\n    def terminator(self):\n        \"\"\"\n        Gets/sets the termination character for the loopback communicator.\n        This should be specified as a single character string.\n\n        :type: `str`\n        :return: The termination character\n        \"\"\"\n        return self._terminator\n\n    @terminator.setter\n    def terminator(self, newval):\n        if isinstance(newval, bytes):\n            newval = newval.decode(\"utf-8\")\n        if not isinstance(newval, str):\n            raise TypeError(\n                \"Terminator for loopback communicator must be \"\n                \"specified as a byte or unicode string.\"\n            )\n        self._terminator = newval\n\n    @property\n    def timeout(self):\n        \"\"\"\n        Gets the timeout for the loopback communicator. This will always\n        return 0.\n\n        :type: `int`\n        \"\"\"\n        return 0\n\n    @timeout.setter\n    def timeout(self, newval):\n        pass\n\n    # FILE-LIKE METHODS #\n\n    def close(self):\n        \"\"\"\n        Close connection to stdin\n        \"\"\"\n        try:\n            if self._stdin is not None:\n                self._stdin.close()\n        except OSError:\n            pass\n\n    def read_raw(self, size=-1):\n        \"\"\"\n        Gets desired response command from stdin. If ``stdin`` is `None`, then\n        the user will be prompted to enter a mock response in the Python\n        interpreter.\n\n        :param int size: Number of characters to read. Default value of -1\n            will read until termination character is found.\n        :rtype: `bytes`\n        \"\"\"\n        if self._stdin is not None:\n            if size == -1 or size is None:\n                result = b\"\"\n                if self._terminator:\n                    while result.endswith(self._terminator.encode(\"utf-8\")) is False:\n                        c = self._stdin.read(1)\n                        if c == b\"\":\n                            break\n                        result += c\n                    return result[: -len(self._terminator)]\n                return self._stdin.read(-1)\n\n            elif size >= 0:\n                input_var = self._stdin.read(size)\n                return bytes(input_var)\n\n            else:\n                raise ValueError(\"Must read a positive value of characters.\")\n        else:\n            input_var = input(\"Desired Response: \").encode(\"utf-8\")\n        return input_var\n\n    def write_raw(self, msg):\n        \"\"\"\n        Write raw bytes to the loopback communicator's stdout. If ``stdout`` is\n        `None` then it will be simply printed to the Python interpreter\n        console.\n\n        :param bytes msg: The bytes to be written\n        \"\"\"\n        if self._stdout is not None:\n            self._stdout.write(msg)\n        else:\n            print(f\" <- {repr(msg)} \")\n\n    def seek(self, offset):  # pylint: disable=unused-argument,no-self-use\n        \"\"\"\n        Go to a specific offset for the input data source.\n\n        Not implemented for loopback communicator.\n        \"\"\"\n        raise NotImplementedError\n\n    def tell(self):  # pylint: disable=no-self-use\n        \"\"\"\n        Get the current positional offset for the input data source.\n\n        Not implemented for loopback communicator.\n        \"\"\"\n        raise NotImplementedError\n\n    def flush_input(self):\n        \"\"\"\n        Flush the input buffer, discarding all remaining input contents.\n\n        For the loopback communicator, this will do nothing and just `pass`.\n        \"\"\"\n\n    # METHODS #\n\n    def _sendcmd(self, msg):\n        \"\"\"\n        This is the implementation of ``sendcmd`` for the loopback communicator.\n        This function is in turn wrapped by the concrete method\n        `AbstractCommunicator.sendcmd` to provide consistent logging\n        functionality across all communication layers.\n\n        :param str msg: The command message to send to the instrument\n        \"\"\"\n        if msg != \"\":\n            msg = f\"{msg}{self._terminator}\"\n            self.write(msg)\n\n    def _query(self, msg, size=-1):\n        \"\"\"\n        This is the implementation of ``query`` for communicating with\n        the loopback communicator. This function is in turn wrapped by\n        the concrete method `AbstractCommunicator.query` to provide consistent\n        logging functionality across all communication layers.\n\n        :param str msg: The query message to send to the instrument\n        :param int size: The number of bytes to read back from the instrument\n            response.\n        :return: The instrument response to the query\n        :rtype: `str`\n        \"\"\"\n        self.sendcmd(msg)\n        resp = self.read(size)\n        return resp\n"
  },
  {
    "path": "src/instruments/abstract_instruments/comm/serial_communicator.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides a serial communicator for connecting with instruments over serial\nconnections.\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport io\nimport serial\n\nfrom instruments.units import ureg as u\n\nfrom instruments.abstract_instruments.comm import AbstractCommunicator\nfrom instruments.util_fns import assume_units\n\n# CLASSES #####################################################################\n\n\nclass SerialCommunicator(io.IOBase, AbstractCommunicator):\n    \"\"\"\n    Wraps a `pyserial.Serial` object to add a few properties as well as\n    handling of termination characters.\n    \"\"\"\n\n    def __init__(self, conn):\n        super().__init__(self)\n\n        if isinstance(conn, serial.Serial):\n            self._conn = conn\n            self._terminator = \"\\n\"\n            self._debug = False\n        else:\n            raise TypeError(\"SerialCommunicator must wrap a serial.Serial \" \"object.\")\n\n    # PROPERTIES #\n\n    @property\n    def address(self):\n        \"\"\"\n        Gets/sets the address port for the serial object.\n\n        :type: `str`\n        \"\"\"\n        return self._conn.port\n\n    @address.setter\n    def address(self, newval):\n        # TODO: Input checking on Serial port newval\n        # TODO: Add port changing capability to serialmanager\n        # self._conn.port = newval\n        raise NotImplementedError\n\n    @property\n    def terminator(self):\n        \"\"\"\n        Gets/sets the termination character for the serial communication\n        channel. This is apended to the end of commands when writing,\n        and used to detect when transmission is done when receiving.\n\n        :type: `str`\n        \"\"\"\n        return self._terminator\n\n    @terminator.setter\n    def terminator(self, newval):\n        if isinstance(newval, bytes):\n            newval = newval.decode(\"utf-8\")\n        if not isinstance(newval, str):\n            raise TypeError(\n                \"Terminator for serial communicator must be \"\n                \"specified as a byte or unicode string.\"\n            )\n        self._terminator = newval\n\n    @property\n    def timeout(self):\n        \"\"\"\n        Gets/sets the communication timeout of the serial comm channel.\n\n        :type: `~pint.Quantity`\n        :units: As specified or assumed to be of units ``seconds``\n        \"\"\"\n        return self._conn.timeout * u.second\n\n    @timeout.setter\n    def timeout(self, newval):\n        newval = assume_units(newval, u.second).to(u.second).magnitude\n        self._conn.timeout = newval\n\n    @property\n    def parity(self):\n        \"\"\"\n        Gets / sets the communication parity.\n\n        :type: `str`\n        \"\"\"\n        return self._conn.parity\n\n    @parity.setter\n    def parity(self, newval):\n        self._conn.parity = newval\n\n    # FILE-LIKE METHODS #\n\n    def close(self):\n        \"\"\"\n        Shutdown and close the `pyserial.Serial` connection.\n        \"\"\"\n        try:\n            self._conn.shutdown()\n        finally:\n            self._conn.close()\n\n    def read_raw(self, size=-1):\n        \"\"\"\n        Read bytes in from the serial port.\n\n        :param int size: The number of bytes to be read in from the serial port\n        :rtype: `bytes`\n        \"\"\"\n        if size >= 0:\n            resp = self._conn.read(size)\n            return resp\n        elif size == -1:\n            result = b\"\"\n            # If the terminator is empty, we can't use endswith, but must\n            # read as many bytes as are available.\n            # On the other hand, if terminator is nonempty, we can check\n            # that the tail end of the buffer matches it.\n            c = None\n            term = self._terminator.encode(\"utf-8\") if self._terminator else None\n            while not (result.endswith(term) if term is not None else c == b\"\"):\n                c = self._conn.read(1)\n                if c == b\"\" and term is not None:\n                    raise OSError(\n                        \"Serial connection timed out before reading \"\n                        \"a termination character.\"\n                    )\n                result += c\n            return result[: -len(term)] if term is not None else result\n        else:\n            raise ValueError(\"Must read a positive value of characters.\")\n\n    def write_raw(self, msg):\n        \"\"\"\n        Write bytes to the `pyserial.Serial` object.\n\n        :param bytes msg: Bytes to be written to the serial port\n        \"\"\"\n        self._conn.write(msg)\n\n    def seek(self, offset):  # pylint: disable=unused-argument,no-self-use\n        \"\"\"\n        Go to a specific offset for the input data source.\n\n        Not implemented for serial communicator.\n        \"\"\"\n        raise NotImplementedError\n\n    def tell(self):  # pylint: disable=no-self-use\n        \"\"\"\n        Get the current positional offset for the input data source.\n\n        Not implemented for serial communicator.\n        \"\"\"\n        raise NotImplementedError\n\n    def flush_input(self):\n        \"\"\"\n        Instruct the communicator to flush the input buffer, discarding the\n        entirety of its contents.\n\n        Calls the pyserial flushInput() method.\n        \"\"\"\n        self._conn.flushInput()\n\n    # METHODS #\n\n    def _sendcmd(self, msg):\n        \"\"\"\n        This is the implementation of ``sendcmd`` for communicating with\n        serial connections. This function is in turn wrapped by the concrete\n        method `AbstractCommunicator.sendcmd` to provide consistent logging\n        functionality across all communication layers.\n\n        :param str msg: The command message to send to the instrument\n        \"\"\"\n        msg += self._terminator\n        self.write(msg)\n\n    def _query(self, msg, size=-1):\n        \"\"\"\n        This is the implementation of ``query`` for communicating with\n        serial connections. This function is in turn wrapped by the concrete\n        method `AbstractCommunicator.query` to provide consistent logging\n        functionality across all communication layers.\n\n        :param str msg: The query message to send to the instrument\n        :param int size: The number of bytes to read back from the instrument\n            response.\n        :return: The instrument response to the query\n        :rtype: `str`\n        \"\"\"\n        self.sendcmd(msg)\n        return self.read(size)\n"
  },
  {
    "path": "src/instruments/abstract_instruments/comm/serial_manager.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nThis module handles creating the serial objects for the instrument classes.\n\nThis is needed for Windows because only 1 serial object can have an open\nconnection to a serial port at a time. This is not needed on Linux, as multiple\npyserial connections can be open at the same time to the same serial port.\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport weakref\nimport serial\n\nfrom instruments.abstract_instruments.comm import SerialCommunicator\n\n# GLOBALS #####################################################################\n\n# We want to only *weakly* hold references to serial ports, to allow for them\n# to be deleted and reopened as need be.\n#\n# A WeakValueDictionary *will* delete entries when their values\n# no longer exist. As a consequence, great care must be taken when iterating\n# over the dictionary in any way.\n# See http://docs.python.org/2/library/weakref.html#weakref.WeakValueDictionary\n# for more details about what \"great care\" implies.\nserialObjDict = weakref.WeakValueDictionary()\n\n# METHODS #####################################################################\n\n\ndef new_serial_connection(port, baud=460800, timeout=3, write_timeout=3, **kwargs):\n    \"\"\"\n    Return a `pyserial.Serial` connection object for the specified serial\n    port address. The same object will be returned for identical port\n    addresses. This is done for Windows which doesn't like when you have\n    multiple things opening the same serial port. Typically this isn't a\n    problem because you only have one instrument per serial port, but adapters\n    such as the Galvant Industries GPIBUSB adapter can have multiple\n    instruments on a single virtual serial port.\n\n    :param str port: Port address for the serial port\n    :param int baud: Baud rate for the serial port connection\n    :param int timeout: Communication timeout for reading from the serial port\n        connection. Units are seconds.\n    :param write_timeout: Communication timeout for writing to the serial\n        port connection. Units are seconds.\n    :return: A :class:`SerialCommunicator` object wrapping the connection\n    :rtype: `SerialCommunicator`\n    \"\"\"\n    if not isinstance(port, str):\n        raise TypeError(\"Serial port must be specified as a string.\")\n\n    if port not in serialObjDict or serialObjDict[port] is None:\n        conn = SerialCommunicator(\n            serial.Serial(\n                port,\n                baudrate=baud,\n                timeout=timeout,\n                writeTimeout=write_timeout,\n                **kwargs\n            )\n        )\n        serialObjDict[port] = conn\n    # pylint: disable=protected-access\n    if not serialObjDict[port]._conn.isOpen():\n        serialObjDict[port]._conn.open()\n    return serialObjDict[port]\n"
  },
  {
    "path": "src/instruments/abstract_instruments/comm/socket_communicator.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides a tcpip socket communicator for connecting with instruments over\nraw ethernet connections.\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport io\nimport socket\n\nfrom instruments.units import ureg as u\n\nfrom instruments.abstract_instruments.comm import AbstractCommunicator\nfrom instruments.util_fns import assume_units\n\n# CLASSES #####################################################################\n\n\nclass SocketCommunicator(io.IOBase, AbstractCommunicator):\n    \"\"\"\n    Communicates with a socket and makes it look like a `file`. Note that this\n    is used instead of `socket.makefile`, as that method does not support\n    timeouts. We do not support all features of `file`-like objects here, but\n    enough to make `~instrument.Instrument` happy.\n    \"\"\"\n\n    def __init__(self, conn):\n        super().__init__(self)\n\n        if isinstance(conn, socket.socket):\n            self._conn = conn\n            self._terminator = \"\\n\"\n        else:\n            raise TypeError(\n                \"SocketCommunicator must wrap a \"\n                \":class:`socket.socket` object, instead got \"\n                \"{}\".format(type(conn))\n            )\n\n    # PROPERTIES #\n\n    @property\n    def address(self):\n        \"\"\"\n        Returns the socket peer address information as a tuple.\n        \"\"\"\n        return self._conn.getpeername()\n\n    @address.setter\n    def address(self, newval):\n        raise NotImplementedError(\"Unable to change address of sockets.\")\n\n    @property\n    def terminator(self):\n        return self._terminator\n\n    @terminator.setter\n    def terminator(self, newval):\n        if isinstance(newval, bytes):\n            newval = newval.decode(\"utf-8\")\n        if not isinstance(newval, str):\n            raise TypeError(\n                \"Terminator for socket communicator must be \"\n                \"specified as a byte or unicode string.\"\n            )\n        self._terminator = newval\n\n    @property\n    def timeout(self):\n        \"\"\"\n        Gets/sets the connection timeout of the socket comm channel.\n\n        :type: `~pint.Quantity`\n        :units: As specified or assumed to be of units ``seconds``\n        \"\"\"\n        return self._conn.gettimeout() * u.second\n\n    @timeout.setter\n    def timeout(self, newval):\n        newval = assume_units(newval, u.second).to(u.second).magnitude\n        self._conn.settimeout(newval)\n\n    # FILE-LIKE METHODS #\n\n    def close(self):\n        \"\"\"\n        Shutdown and close the `socket.socket` connection.\n        \"\"\"\n        try:\n            self._conn.shutdown(socket.SHUT_RDWR)\n        finally:\n            self._conn.close()\n\n    def read_raw(self, size=-1):\n        \"\"\"\n        Read bytes in from the socket connection.\n\n        :param int size: The number of bytes to read in from the socket\n            connection.\n        :return: The read bytes\n        :rtype: `bytes`\n        \"\"\"\n        if size >= 0:\n            return self._conn.recv(size)\n        elif size == -1:\n            result = b\"\"\n            while result.endswith(self._terminator.encode(\"utf-8\")) is False:\n                c = self._conn.recv(1)\n                if c == b\"\":\n                    raise OSError(\n                        \"Socket connection timed out before reading \"\n                        \"a termination character.\"\n                    )\n                result += c\n            return result[: -len(self._terminator)]\n        else:\n            raise ValueError(\"Must read a positive value of characters.\")\n\n    def write_raw(self, msg):\n        \"\"\"\n        Write bytes to the `socket.socket` connection object.\n\n        :param bytes msg: Bytes to be sent to the instrument over the socket\n            connection.\n        \"\"\"\n        self._conn.sendall(msg)\n\n    def seek(self, offset):  # pylint: disable=unused-argument,no-self-use\n        \"\"\"\n        Go to a specific offset for the input data source.\n\n        Not implemented for socket communicator.\n        \"\"\"\n        raise NotImplementedError\n\n    def tell(self):  # pylint: disable=no-self-use\n        \"\"\"\n        Get the current positional offset for the input data source.\n\n        Not implemented for socket communicator.\n        \"\"\"\n        raise NotImplementedError\n\n    def flush_input(self):\n        \"\"\"\n        Instruct the communicator to flush the input buffer, discarding the\n        entirety of its contents.\n        \"\"\"\n        _ = self.read(-1)  # Read in everything in the buffer and trash it\n\n    # METHODS #\n\n    def _sendcmd(self, msg):\n        \"\"\"\n        This is the implementation of ``sendcmd`` for communicating with\n        socket connections. This function is in turn wrapped by the concrete\n        method `AbstractCommunicator.sendcmd` to provide consistent logging\n        functionality across all communication layers.\n\n        :param str msg: The command message to send to the instrument\n        \"\"\"\n        msg += self._terminator\n        self.write(msg)\n\n    def _query(self, msg, size=-1):\n        \"\"\"\n        This is the implementation of ``query`` for communicating with\n        socket connections. This function is in turn wrapped by the concrete\n        method `AbstractCommunicator.query` to provide consistent logging\n        functionality across all communication layers.\n\n        :param str msg: The query message to send to the instrument\n        :param int size: The number of bytes to read back from the instrument\n            response.\n        :return: The instrument response to the query\n        :rtype: `str`\n        \"\"\"\n        self.sendcmd(msg)\n        resp = self.read(size)\n        return resp\n"
  },
  {
    "path": "src/instruments/abstract_instruments/comm/usb_communicator.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides a USB communicator for connecting with instruments over raw usb\nconnections.\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport io\n\nimport usb.core\nimport usb.util\n\nfrom instruments.abstract_instruments.comm import AbstractCommunicator\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import assume_units\n\n# CLASSES #####################################################################\n\n\nclass USBCommunicator(io.IOBase, AbstractCommunicator):\n    \"\"\"\n    This communicator is used to wrap a pyusb connection object. This is\n    typically *not* the suggested way to interact with a USB-connected\n    instrument. Most USB instruments can be interfaced through other\n    communicators such as `FileCommunicator` (usbtmc on Linux),\n    `VisaCommunicator`, or `USBTMCCommunicator`.\n\n    .. warning:: The operational status of this communicator is poorly tested.\n    \"\"\"\n\n    def __init__(self, dev):\n        super().__init__(self)\n        if not isinstance(dev, usb.core.Device):\n            raise TypeError(\"USBCommunicator must wrap a usb.core.Device object.\")\n\n        # follow (mostly) pyusb tutorial\n\n        # set the active configuration. With no arguments, the first\n        # configuration will be the active one\n        dev.set_configuration()\n\n        # get an endpoint instance\n        cfg = dev.get_active_configuration()\n        intf = cfg[(0, 0)]\n\n        # initialize in and out endpoints\n        ep_out = usb.util.find_descriptor(\n            intf,\n            # match the first OUT endpoint\n            custom_match=lambda e: usb.util.endpoint_direction(e.bEndpointAddress)\n            == usb.util.ENDPOINT_OUT,\n        )\n\n        ep_in = usb.util.find_descriptor(\n            intf,\n            # match the first OUT endpoint\n            custom_match=lambda e: usb.util.endpoint_direction(e.bEndpointAddress)\n            == usb.util.ENDPOINT_IN,\n        )\n\n        if (ep_in or ep_out) is None:\n            raise OSError(\"USB endpoint not found.\")\n\n        # read the maximum package size from the ENDPOINT_IN\n        self._max_packet_size = ep_in.wMaxPacketSize\n\n        self._dev = dev\n        self._ep_in = ep_in\n        self._ep_out = ep_out\n        self._terminator = \"\\n\"\n\n    # PROPERTIES #\n\n    @property\n    def address(self):\n        raise NotImplementedError\n\n    @address.setter\n    def address(self, _):\n        raise ValueError(\"Unable to change USB target address.\")\n\n    @property\n    def terminator(self):\n        \"\"\"\n        Gets/sets the termination character used for communicating with raw\n        USB objects.\n        :return:\n        \"\"\"\n        return self._terminator\n\n    @terminator.setter\n    def terminator(self, newval):\n        if not isinstance(newval, str):\n            raise TypeError(\n                \"Terminator for USBCommunicator must be specified \"\n                \"as a character string.\"\n            )\n        self._terminator = newval\n\n    @property\n    def timeout(self):\n        \"\"\"\n        Gets/sets the communication timeout of the USB communicator.\n\n        :type: `~pint.Quantity`\n        :units: As specified or assumed to be of units ``seconds``\n        \"\"\"\n        return assume_units(self._dev.default_timeout, u.ms).to(u.second)\n\n    @timeout.setter\n    def timeout(self, newval):\n        newval = assume_units(newval, u.second).to(u.ms).magnitude\n        self._dev.default_timeout = newval\n\n    # FILE-LIKE METHODS #\n\n    def close(self):\n        \"\"\"\n        Shutdown and close the USB connection\n        \"\"\"\n        self._dev.reset()\n        usb.util.dispose_resources(self._dev)\n\n    def read_raw(self, size=-1):\n        \"\"\"Read raw string back from device and return.\n\n        String returned is most likely shorter than the size requested. Will\n        terminate by itself.\n        Read size of -1 will be transformed into 1000 bytes.\n\n        :param size: Size to read in bytes\n        :type size: int\n        \"\"\"\n        if size == -1:\n            size = self._max_packet_size\n        term = self._terminator.encode(\"utf-8\")\n        read_val = bytes(self._ep_in.read(size))\n        if term not in read_val:\n            raise OSError(\n                f\"Did not find the terminator in the returned string. \"\n                f\"Total size of {size} might not be enough.\"\n            )\n        return read_val.rstrip(term)\n\n    def write_raw(self, msg):\n        \"\"\"Write bytes to the raw usb connection object.\n\n        :param bytes msg: Bytes to be sent to the instrument over the usb\n            connection.\n        \"\"\"\n        self._ep_out.write(msg)\n\n    def seek(self, offset):  # pylint: disable=unused-argument,no-self-use\n        raise NotImplementedError\n\n    def tell(self):  # pylint: disable=no-self-use\n        raise NotImplementedError\n\n    def flush_input(self):\n        \"\"\"\n        Instruct the communicator to flush the input buffer, discarding the\n        entirety of its contents.\n        \"\"\"\n        self._ep_in.read(self._max_packet_size)\n\n    # METHODS #\n\n    def _sendcmd(self, msg):\n        \"\"\"\n        This is the implementation of ``sendcmd`` for communicating with\n        raw usb connections. This function is in turn wrapped by the concrete\n        method `AbstractCommunicator.sendcmd` to provide consistent logging\n        functionality across all communication layers.\n\n        :param str msg: The command message to send to the instrument\n        \"\"\"\n        msg += self._terminator\n        self.write(msg)\n\n    def _query(self, msg, size=-1):\n        \"\"\"\n        This is the implementation of ``query`` for communicating with\n        raw usb connections. This function is in turn wrapped by the concrete\n        method `AbstractCommunicator.query` to provide consistent logging\n        functionality across all communication layers.\n\n        :param str msg: The query message to send to the instrument\n        :param int size: The number of bytes to read back from the instrument\n            response.\n        :return: The instrument response to the query\n        :rtype: `str`\n        \"\"\"\n        self.sendcmd(msg)\n        return self.read(size)\n"
  },
  {
    "path": "src/instruments/abstract_instruments/comm/usbtmc_communicator.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides a communicator that uses Python-USBTMC for connecting with TMC\ninstruments.\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport io\n\nimport usbtmc\n\nfrom instruments.abstract_instruments.comm import AbstractCommunicator\nfrom instruments.util_fns import assume_units\nfrom instruments.units import ureg as u\n\n# CLASSES #####################################################################\n\n\nclass USBTMCCommunicator(io.IOBase, AbstractCommunicator):\n    \"\"\"\n    Wraps a USBTMC device. Arguments are passed to `usbtmc.Instrument`.\n    \"\"\"\n\n    def __init__(self, *args, **kwargs):\n        if usbtmc is None:\n            raise ImportError(\"usbtmc is required for TMC instruments.\")\n        super().__init__(self)\n\n        self._filelike = usbtmc.Instrument(*args, **kwargs)\n        self._terminator = \"\\n\"\n\n    # PROPERTIES #\n\n    @property\n    def address(self):\n        if hasattr(self._filelike, \"name\"):\n            return id(self._filelike)  # TODO: replace with something more useful.\n\n        return None\n\n    @property\n    def terminator(self):\n        \"\"\"\n        Gets/sets the termination character used for communicating with the\n        USBTMC instrument.\n\n        :type: `str`\n        \"\"\"\n        return chr(self._filelike.term_char)\n\n    @terminator.setter\n    def terminator(self, newval):\n        if isinstance(newval, bytes):\n            newval = newval.decode(\"utf-8\")\n        if not isinstance(newval, str) or len(newval) > 1:\n            raise TypeError(\n                \"Terminator for loopback communicator must be \"\n                \"specified as a single character string.\"\n            )\n        self._terminator = newval\n        self._filelike.term_char = ord(newval)\n\n    @property\n    def timeout(self):\n        \"\"\"\n        Gets/sets the communication timeout of the usbtmc comm channel.\n\n        :type: `~pint.Quantity`\n        :units: As specified or assumed to be of units ``seconds``\n        \"\"\"\n        return self._filelike.timeout * u.second\n\n    @timeout.setter\n    def timeout(self, newval):\n        newval = assume_units(newval, u.second).to(u.s).magnitude\n        self._filelike.timeout = newval\n\n    # FILE-LIKE METHODS #\n\n    def close(self):\n        \"\"\"\n        Close the USBTMC connection object\n        \"\"\"\n        try:\n            self._filelike.close()\n        except OSError:\n            pass\n\n    def read(self, size=-1, encoding=\"utf-8\"):\n        \"\"\"\n        Read bytes in from the usbtmc connection, returning a decoded string\n        using the provided encoding method.\n\n        :param int size: The number of bytes to read in from the usbtmc\n            connection.\n        :param str encoding: Encoding that will be applied to the read bytes\n\n        :return: The read string from the connection\n        :rtype: `str`\n        \"\"\"\n        return self._filelike.read(num=size, encoding=encoding)\n\n    def read_raw(self, size=-1):\n        \"\"\"\n        Read bytes in from the usbtmc connection.\n\n        :param int size: The number of bytes to read in from the usbtmc\n            connection.\n\n        :return: The read bytes\n        :rtype: `bytes`\n        \"\"\"\n        return self._filelike.read_raw(num=size)\n\n    def write(self, msg, encoding=\"utf-8\"):\n        \"\"\"\n        Write a string to the usbtmc connection. This string will be converted\n        to `bytes` using the provided encoding method.\n\n        :param str msg: String to be sent to the instrument over the usbtmc\n            connection.\n        :param str encoding: Encoding to apply on msg to convert the message\n            into bytes\n        \"\"\"\n        self._filelike.write(msg, encoding=encoding)\n\n    def write_raw(self, msg):\n        \"\"\"\n        Write bytes to the usbtmc connection.\n\n        :param bytes msg: Bytes to be sent to the instrument over the usbtmc\n            connection.\n        \"\"\"\n        self._filelike.write_raw(msg)\n\n    def seek(self, offset):\n        raise NotImplementedError\n\n    def tell(self):\n        raise NotImplementedError\n\n    def flush_input(self):\n        \"\"\"\n        For a USBTMC connection, this function does not actually do anything\n        and simply returns.\n        \"\"\"\n\n    # METHODS #\n\n    def _sendcmd(self, msg):\n        \"\"\"\n        This is the implementation of ``sendcmd`` for communicating with\n        usbtmc connections. This function is in turn wrapped by the concrete\n        method `AbstractCommunicator.sendcmd` to provide consistent logging\n        functionality across all communication layers.\n\n        :param str msg: The command message to send to the instrument\n        \"\"\"\n        self.write(msg)\n\n    def _query(self, msg, size=-1):\n        \"\"\"\n        This is the implementation of ``query`` for communicating with\n        usbtmc connections. This function is in turn wrapped by the concrete\n        method `AbstractCommunicator.query` to provide consistent logging\n        functionality across all communication layers.\n\n        :param str msg: The query message to send to the instrument\n        :param int size: The number of bytes to read back from the instrument\n            response.\n        :return: The instrument response to the query\n        :rtype: `str`\n        \"\"\"\n        return self._filelike.ask(msg, num=size, encoding=\"utf-8\")\n"
  },
  {
    "path": "src/instruments/abstract_instruments/comm/visa_communicator.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides a VISA communicator for connecting with instruments via the VISA\nlibrary.\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport io\n\nimport pyvisa\n\nfrom instruments.abstract_instruments.comm import AbstractCommunicator\nfrom instruments.util_fns import assume_units\nfrom instruments.units import ureg as u\n\n# CLASSES #####################################################################\n\n\nclass VisaCommunicator(io.IOBase, AbstractCommunicator):\n    \"\"\"\n    Communicates a connection exposed by the VISA library and exposes it as a\n    file-like object.\n    \"\"\"\n\n    def __init__(self, conn):\n        super().__init__(self)\n\n        self._terminator = None\n\n        version = int(pyvisa.__version__.replace(\".\", \"\").ljust(3, \"0\"))\n        # pylint: disable=no-member\n        if (version < 160 and isinstance(conn, pyvisa.Instrument)) or (\n            version >= 160 and isinstance(conn, pyvisa.Resource)\n        ):\n            self._conn = conn\n            self.terminator = \"\\n\"\n        else:\n            raise TypeError(\"VisaCommunicator must wrap a VISA Instrument.\")\n\n        # Make a bytearray for holding data read in from the device\n        # so that we can buffer for two-argument read.\n        self._buf = bytearray()\n\n    # PROPERTIES #\n\n    @property\n    def address(self):\n        \"\"\"\n        Gets the address or \"resource name\" for the VISA connection\n\n        :type: `str`\n        \"\"\"\n        return self._conn.resource_name\n\n    @address.setter\n    def address(self, newval):\n        raise NotImplementedError(\n            \"Changing addresses of a VISA Instrument \" \"is not supported.\"\n        )\n\n    @property\n    def read_termination(self):\n        \"\"\"Get / Set the read termination that is defined in pyvisa.\"\"\"\n        return self._conn.read_termination\n\n    @read_termination.setter\n    def read_termination(self, newval):\n        if not isinstance(newval, str):\n            raise TypeError(\n                \"Read terminator for VisaCommunicator must be specified as a string.\"\n            )\n        self._conn.read_termination = newval\n\n    @property\n    def terminator(self):\n        \"\"\"\n        Gets/sets the termination character used for VISA connections\n\n        :type: `str`\n        \"\"\"\n        return self._terminator\n\n    @terminator.setter\n    def terminator(self, newval):\n        if not isinstance(newval, str):\n            raise TypeError(\n                \"Terminator for VisaCommunicator must be specified as a string.\"\n            )\n        self._terminator = newval\n        self.read_termination = newval\n        self.write_termination = newval\n\n    @property\n    def timeout(self):\n        return self._conn.timeout * u.second\n\n    @timeout.setter\n    def timeout(self, newval):\n        newval = assume_units(newval, u.second).to(u.second).magnitude\n        self._conn.timeout = newval\n\n    @property\n    def write_termination(self):\n        \"\"\"Get / Set the write termination that is defined in pyvisa.\"\"\"\n        return self._conn.write_termination\n\n    @write_termination.setter\n    def write_termination(self, newval):\n        if not isinstance(newval, str):\n            raise TypeError(\n                \"Write terminator for VisaCommunicator must be specified as a string.\"\n            )\n        self._conn.write_termination = newval\n\n    # FILE-LIKE METHODS #\n\n    def close(self):\n        \"\"\"\n        Close the pyVISA connection object\n        \"\"\"\n        try:\n            self._conn.close()\n        except OSError:\n            pass\n\n    def read_raw(self, size=-1):\n        \"\"\"\n        Read bytes in from the pyVISA connection.\n\n        :param int size: The number of bytes to read in from the VISA\n            connection.\n\n        :return: The read bytes from the VISA connection\n        :rtype: `bytes`\n        \"\"\"\n        if size >= 0:\n            self._buf += self._conn.read_bytes(size)\n            msg = self._buf[:size]\n            # Remove the front of the buffer.\n            del self._buf[:size]\n\n        elif size == -1:\n            # Read the whole contents, appending the buffer we've already read.\n            msg = self._buf + self._conn.read_raw()\n            # Reset the contents of the buffer.\n            self._buf = bytearray()\n        else:\n            raise ValueError(\n                \"Must read a positive value of characters, or \" \"-1 for all characters.\"\n            )\n        return msg\n\n    def write_raw(self, msg):\n        \"\"\"\n        Write bytes to the VISA connection.\n\n        :param bytes msg: Bytes to be sent to the instrument over the VISA\n            connection.\n        \"\"\"\n        self._conn.write_raw(msg)\n\n    def seek(self, offset):  # pylint: disable=unused-argument,no-self-use\n        raise NotImplementedError\n\n    def tell(self):  # pylint: disable=no-self-use\n        raise NotImplementedError\n\n    def flush_input(self):\n        \"\"\"\n        Instruct the communicator to flush the input buffer, discarding the\n        entirety of its contents.\n        \"\"\"\n        # TODO: Find out how to flush with pyvisa\n\n    # METHODS #\n\n    def _sendcmd(self, msg):\n        \"\"\"\n        This is the implementation of ``sendcmd`` for communicating with\n        VISA connections. This function is in turn wrapped by the concrete\n        method `AbstractCommunicator.sendcmd` to provide consistent logging\n        functionality across all communication layers.\n        Termination characters are automatically added by pyvisa.\n\n        :param str msg: The command message to send to the instrument\n        \"\"\"\n        self.write(msg)\n\n    def _query(self, msg, size=-1):\n        \"\"\"\n        This is the implementation of ``query`` for communicating with\n        VISA connections. This function is in turn wrapped by the concrete\n        method `AbstractCommunicator.query` to provide consistent logging\n        functionality across all communication layers.\n        Termination characters are automatically added by pyvisa.\n\n        :param str msg: The query message to send to the instrument\n        :param int size: The number of bytes to read back from the instrument\n            response.\n        :return: The instrument response to the query\n        :rtype: `str`\n        \"\"\"\n        return self._conn.query(msg)\n"
  },
  {
    "path": "src/instruments/abstract_instruments/comm/vxi11_communicator.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides a communication layer that uses python-vxi11 to interface with\nVXI11 devices.\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport io\nimport logging\n\nimport vxi11\n\nfrom instruments.abstract_instruments.comm import AbstractCommunicator\n\nlogger = logging.getLogger(__name__)\nlogger.addHandler(logging.NullHandler())\n\n# CLASSES #####################################################################\n\n\nclass VXI11Communicator(io.IOBase, AbstractCommunicator):\n    \"\"\"\n    Wraps a VXI-11 device. Arguments are all essentially just passed\n    to `vxi11.Instrument`.\n\n    VXI-11 is an RPC-based communication protocol over ethernet primarily used\n    for connecting test and measurement equipment to controller hardware.\n    VXI-11 allows for improved communication speeds and reduced latency over\n    that of communicating using TCP over a socket connection.\n\n    VXI-11 is developed and maintained by the IVI Foundation. More information\n    can be found on their website, as well as that of the LXI standard website.\n\n    VXI-11 has since been superseded by HiSLIP, which features fixes, improved\n    performance, and new features such as IPv6 support.\n    \"\"\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(self)\n        if vxi11 is None:\n            raise ImportError(\n                \"Package python-vxi11 is required for XVI11 \" \"connected instruments.\"\n            )\n        AbstractCommunicator.__init__(self)\n\n        self._inst = vxi11.Instrument(*args, **kwargs)\n\n    # PROPERTIES #\n\n    @property\n    def address(self):\n        \"\"\"\n        Gets the host and name for the vxi11 connection. Returns the ``host``\n        and ``name`` in a list.\n\n        :rtype: `list`[`str`]\n        \"\"\"\n        return [self._inst.host, self._inst.name]\n\n    @property\n    def terminator(self):\n        \"\"\"\n        Gets/sets the termination character for the VXI11 communication\n        channel. This is apended to the end of commands when writing,\n        and used to detect when transmission is done when receiving.\n\n        :type: `str`\n        \"\"\"\n        return self._inst.term_char\n\n    @terminator.setter\n    def terminator(self, newval):\n        if isinstance(newval, bytes):\n            newval = newval.decode(\"utf-8\")\n        if not isinstance(newval, str):\n            raise TypeError(\n                \"Terminator for VXI11 communicator must be \"\n                \"specified as a byte or unicode string.\"\n            )\n        if len(newval) > 1:\n            logger.warning(\n                \"VXI11 instruments only support termination\"\n                \"characters of length 1. The first character\"\n                \"specified will be used.\"\n            )\n        self._inst.term_char = newval\n\n    @property\n    def timeout(self):\n        \"\"\"\n        Gets/sets the communication timeout of the vxi11 comm channel.\n\n        :type: `~pint.Quantity`\n        :units: As specified or assumed to be of units ``seconds``\n        \"\"\"\n        return self._inst.timeout\n\n    @timeout.setter\n    def timeout(self, newval):\n        self._inst.timeout = newval  # In seconds\n\n    # FILE-LIKE METHODS #\n\n    def close(self):\n        \"\"\"\n        Shutdown and close the vxi11 connection.\n        \"\"\"\n        try:\n            self._inst.close()\n        except OSError:\n            pass\n\n    def read_raw(self, size=-1):\n        \"\"\"\n        Read bytes in from the vxi11 connection.\n\n        :param int size: The number of bytes to be read in from the vxi11\n            connection\n        :rtype: `bytes`\n        \"\"\"\n        return self._inst.read_raw(num=size)\n\n    def write_raw(self, msg):\n        \"\"\"\n        Write bytes to the vxi11 connection.\n\n        :param bytes msg: Bytes to be written to the vxi11 connection\n        \"\"\"\n        self._inst.write_raw(msg)\n\n    def seek(self, offset):\n        \"\"\"\n        Go to a specific offset for the input data source.\n\n        Not implemented for vxi11 communicator.\n        \"\"\"\n        raise NotImplementedError\n\n    def tell(self):\n        \"\"\"\n        Get the current positional offset for the input data source.\n\n        Not implemented for vxi11 communicator.\n        \"\"\"\n        raise NotImplementedError\n\n    def flush_input(self):\n        \"\"\"\n        Instruct the communicator to flush the input buffer, discarding the\n        entirety of its contents.\n\n        Not implemented for vxi11 communicator.\n        \"\"\"\n        raise NotImplementedError\n\n    # METHODS #\n\n    def _sendcmd(self, msg):\n        \"\"\"\n        This is the implementation of ``sendcmd`` for communicating with\n        vxi11 connections. This function is in turn wrapped by the concrete\n        method `AbstractCommunicator.sendcmd` to provide consistent logging\n        functionality across all communication layers.\n\n        :param str msg: The command message to send to the instrument\n        \"\"\"\n        self.write(msg)\n\n    def _query(self, msg, size=-1):\n        \"\"\"\n        This is the implementation of ``query`` for communicating with\n        vxi11 connections. This function is in turn wrapped by the concrete\n        method `AbstractCommunicator.query` to provide consistent logging\n        functionality across all communication layers.\n\n        :param str msg: The query message to send to the instrument\n        :param int size: The number of bytes to read back from the instrument\n            response.\n        :return: The instrument response to the query\n        :rtype: `str`\n        \"\"\"\n        return self._inst.ask(msg, num=size)\n"
  },
  {
    "path": "src/instruments/abstract_instruments/electrometer.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides an abstract base class for electrometer instruments\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport abc\n\nfrom instruments.abstract_instruments import Instrument\n\n# CLASSES #####################################################################\n\n\nclass Electrometer(Instrument, metaclass=abc.ABCMeta):\n    \"\"\"\n    Abstract base class for electrometer instruments.\n\n    All applicable concrete instruments should inherit from this ABC to\n    provide a consistent interface to the user.\n    \"\"\"\n\n    # PROPERTIES #\n\n    @property\n    @abc.abstractmethod\n    def mode(self):\n        \"\"\"\n        Gets/sets the measurement mode for the electrometer. This is an\n        abstract method.\n\n        :type: `~enum.Enum`\n        \"\"\"\n\n    @mode.setter\n    @abc.abstractmethod\n    def mode(self, newval):\n        pass\n\n    @property\n    @abc.abstractmethod\n    def unit(self):\n        \"\"\"\n        Gets/sets the measurement mode for the electrometer. This is an\n        abstract method.\n\n        :type: `~pint.Unit`\n        \"\"\"\n\n    @property\n    @abc.abstractmethod\n    def trigger_mode(self):\n        \"\"\"\n        Gets/sets the trigger mode for the electrometer. This is an\n        abstract method.\n\n        :type: `~enum.Enum`\n        \"\"\"\n\n    @trigger_mode.setter\n    @abc.abstractmethod\n    def trigger_mode(self, newval):\n        pass\n\n    @property\n    @abc.abstractmethod\n    def input_range(self):\n        \"\"\"\n        Gets/sets the input range setting for the electrometer. This is an\n        abstract method.\n\n        :type: `~enum.Enum`\n        \"\"\"\n\n    @input_range.setter\n    @abc.abstractmethod\n    def input_range(self, newval):\n        pass\n\n    @property\n    @abc.abstractmethod\n    def zero_check(self):\n        \"\"\"\n        Gets/sets the zero check status for the electrometer. This is an\n        abstract method.\n\n        :type: `bool`\n        \"\"\"\n\n    @zero_check.setter\n    @abc.abstractmethod\n    def zero_check(self, newval):\n        pass\n\n    @property\n    @abc.abstractmethod\n    def zero_correct(self):\n        \"\"\"\n        Gets/sets the zero correct status for the electrometer. This is an\n        abstract method.\n\n        :type: `bool`\n        \"\"\"\n\n    @zero_correct.setter\n    @abc.abstractmethod\n    def zero_correct(self, newval):\n        pass\n\n    # METHODS #\n\n    @abc.abstractmethod\n    def fetch(self):\n        \"\"\"\n        Request the latest post-processed readings using the current mode.\n        (So does not issue a trigger)\n        \"\"\"\n        raise NotImplementedError\n\n    @abc.abstractmethod\n    def read_measurements(self):\n        \"\"\"\n        Trigger and acquire readings using the current mode.\n        \"\"\"\n        raise NotImplementedError\n"
  },
  {
    "path": "src/instruments/abstract_instruments/function_generator.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides an abstract base class for function generator instruments\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport abc\nfrom enum import Enum\n\nfrom pint.errors import DimensionalityError\n\nfrom instruments.abstract_instruments import Instrument\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import assume_units, ProxyList\n\n# CLASSES #####################################################################\n\n\nclass FunctionGenerator(Instrument, metaclass=abc.ABCMeta):\n    \"\"\"\n    Abstract base class for function generator instruments.\n\n    All applicable concrete instruments should inherit from this ABC to\n    provide a consistent interface to the user.\n    \"\"\"\n\n    def __init__(self, filelike):\n        super().__init__(filelike)\n        self._channel_count = 1\n\n    # pylint:disable=protected-access\n    class Channel(metaclass=abc.ABCMeta):\n        \"\"\"\n        Abstract base class for physical channels on a function generator.\n\n        All applicable concrete instruments should inherit from this ABC to\n        provide a consistent interface to the user.\n\n        Function generators that only have a single channel do not need to\n        define their own concrete implementation of this class. Ones with\n        multiple channels need their own definition of this class, where\n        this class contains the concrete implementations of the below\n        abstract methods. Instruments with 1 channel have their concrete\n        implementations at the parent instrument level.\n        \"\"\"\n\n        def __init__(self, parent, name):\n            self._parent = parent\n            self._name = name\n\n        # ABSTRACT PROPERTIES #\n\n        @property\n        def frequency(self):\n            \"\"\"\n            Gets/sets the output frequency of the function generator. This is\n            an abstract property.\n\n            :type: `~pint.Quantity`\n            \"\"\"\n            if self._parent._channel_count == 1:\n                return self._parent.frequency\n            else:\n                raise NotImplementedError()\n\n        @frequency.setter\n        def frequency(self, newval):\n            if self._parent._channel_count == 1:\n                self._parent.frequency = newval\n            else:\n                raise NotImplementedError()\n\n        @property\n        def function(self):\n            \"\"\"\n            Gets/sets the output function mode of the function generator. This is\n            an abstract property.\n\n            :type: `~enum.Enum`\n            \"\"\"\n            if self._parent._channel_count == 1:\n                return self._parent.function\n            else:\n                raise NotImplementedError()\n\n        @function.setter\n        def function(self, newval):\n            if self._parent._channel_count == 1:\n                self._parent.function = newval\n            else:\n                raise NotImplementedError()\n\n        @property\n        def offset(self):\n            \"\"\"\n            Gets/sets the output offset voltage of the function generator. This is\n            an abstract property.\n\n            :type: `~pint.Quantity`\n            \"\"\"\n            if self._parent._channel_count == 1:\n                return self._parent.offset\n            else:\n                raise NotImplementedError()\n\n        @offset.setter\n        def offset(self, newval):\n            if self._parent._channel_count == 1:\n                self._parent.offset = newval\n            else:\n                raise NotImplementedError()\n\n        @property\n        def phase(self):\n            \"\"\"\n            Gets/sets the output phase of the function generator. This is an\n            abstract property.\n\n            :type: `~pint.Quantity`\n            \"\"\"\n            if self._parent._channel_count == 1:\n                return self._parent.phase\n            else:\n                raise NotImplementedError()\n\n        @phase.setter\n        def phase(self, newval):\n            if self._parent._channel_count == 1:\n                self._parent.phase = newval\n            else:\n                raise NotImplementedError()\n\n        def _get_amplitude_(self):\n            if self._parent._channel_count == 1:\n                return self._parent._get_amplitude_()\n            else:\n                raise NotImplementedError()\n\n        def _set_amplitude_(self, magnitude, units):\n            if self._parent._channel_count == 1:\n                self._parent._set_amplitude_(magnitude=magnitude, units=units)\n            else:\n                raise NotImplementedError()\n\n        @property\n        def amplitude(self):\n            \"\"\"\n            Gets/sets the output amplitude of the function generator.\n\n            If set with units of :math:`\\\\text{dBm}`, then no voltage mode can\n            be passed.\n\n            If set with units of :math:`\\\\text{V}` as a `~pint.Quantity` or a\n            `float` without a voltage mode, then the voltage mode is assumed to be\n            peak-to-peak.\n\n            :units: As specified, or assumed to be :math:`\\\\text{V}` if not\n                specified.\n            :type: Either a `tuple` of a `~pint.Quantity` and a\n                `FunctionGenerator.VoltageMode`, or a `~pint.Quantity`\n                if no voltage mode applies.\n            \"\"\"\n            mag, units = self._get_amplitude_()\n\n            if units == self._parent.VoltageMode.dBm:\n                return u.Quantity(mag, u.dBm)\n\n            return u.Quantity(mag, u.V), units\n\n        @amplitude.setter\n        def amplitude(self, newval):\n            # Try and rescale to dBm... if it succeeds, set the magnitude\n            # and units accordingly, otherwise handle as a voltage.\n            try:\n                newval_dbm = newval.to(u.dBm)\n                mag = float(newval_dbm.magnitude)\n                units = self._parent.VoltageMode.dBm\n            except (AttributeError, ValueError, DimensionalityError):\n                # OK, we have volts. Now, do we have a tuple? If not, assume Vpp.\n                if not isinstance(newval, tuple):\n                    mag = newval\n                    units = self._parent.VoltageMode.peak_to_peak\n                else:\n                    mag, units = newval\n\n                # Finally, convert the magnitude out to a float.\n                mag = float(assume_units(mag, u.V).to(u.V).magnitude)\n\n            self._set_amplitude_(mag, units)\n\n        def sendcmd(self, cmd):\n            self._parent.sendcmd(cmd)\n\n        def query(self, cmd, size=-1):\n            return self._parent.query(cmd, size)\n\n    # ENUMS #\n\n    class VoltageMode(Enum):\n        \"\"\"\n        Enum containing valid voltage modes for many function generators\n        \"\"\"\n\n        peak_to_peak = \"VPP\"\n        rms = \"VRMS\"\n        dBm = \"DBM\"\n\n    class Function(Enum):\n        \"\"\"\n        Enum containg valid output function modes for many function generators\n        \"\"\"\n\n        sinusoid = \"SIN\"\n        square = \"SQU\"\n        triangle = \"TRI\"\n        ramp = \"RAMP\"\n        noise = \"NOIS\"\n        arbitrary = \"ARB\"\n\n    @property\n    def channel(self):\n        \"\"\"\n        Gets a channel object for the function generator. This should use\n        `~instruments.util_fns.ProxyList` to achieve this.\n\n        The number of channels accessible depends on the value\n        of FunctionGenerator._channel_count\n\n        :rtype: `FunctionGenerator.Channel`\n        \"\"\"\n        return ProxyList(self, self.Channel, range(self._channel_count))\n\n    # PASSTHROUGH PROPERTIES #\n\n    @property\n    def amplitude(self):\n        \"\"\"\n        Gets/sets the output amplitude of the first channel\n        of the function generator\n\n        :type: `~pint.Quantity`\n        \"\"\"\n        return self.channel[0].amplitude\n\n    @amplitude.setter\n    def amplitude(self, newval):\n        self.channel[0].amplitude = newval\n\n    def _get_amplitude_(self):\n        raise NotImplementedError()\n\n    def _set_amplitude_(self, magnitude, units):\n        raise NotImplementedError()\n\n    @property\n    def frequency(self):\n        \"\"\"\n        Gets/sets the output frequency of the function generator. This is\n        an abstract property.\n\n        :type: `~pint.Quantity`\n        \"\"\"\n        if self._channel_count > 1:\n            return self.channel[0].frequency\n        else:\n            raise NotImplementedError()\n\n    @frequency.setter\n    def frequency(self, newval):\n        if self._channel_count > 1:\n            self.channel[0].frequency = newval\n        else:\n            raise NotImplementedError()\n\n    @property\n    def function(self):\n        \"\"\"\n        Gets/sets the output function mode of the function generator. This is\n        an abstract property.\n\n        :type: `~enum.Enum`\n        \"\"\"\n        if self._channel_count > 1:\n            return self.channel[0].function\n        else:\n            raise NotImplementedError()\n\n    @function.setter\n    def function(self, newval):\n        if self._channel_count > 1:\n            self.channel[0].function = newval\n        else:\n            raise NotImplementedError()\n\n    @property\n    def offset(self):\n        \"\"\"\n        Gets/sets the output offset voltage of the function generator. This is\n        an abstract property.\n\n        :type: `~pint.Quantity`\n        \"\"\"\n        if self._channel_count > 1:\n            return self.channel[0].offset\n        else:\n            raise NotImplementedError()\n\n    @offset.setter\n    def offset(self, newval):\n        if self._channel_count > 1:\n            self.channel[0].offset = newval\n        else:\n            raise NotImplementedError()\n\n    @property\n    def phase(self):\n        \"\"\"\n        Gets/sets the output phase of the function generator. This is an\n        abstract property.\n\n        :type: `~pint.Quantity`\n        \"\"\"\n        if self._channel_count > 1:\n            return self.channel[0].phase\n        else:\n            raise NotImplementedError()\n\n    @phase.setter\n    def phase(self, newval):\n        if self._channel_count > 1:\n            self.channel[0].phase = newval\n        else:\n            raise NotImplementedError()\n"
  },
  {
    "path": "src/instruments/abstract_instruments/instrument.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides the base Instrument class for all instruments.\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport os\nimport collections\nimport socket\nimport struct\nimport typing_extensions\nimport urllib.parse as parse\n\nfrom serial import SerialException\nfrom serial.tools.list_ports import comports\nimport pyvisa\nimport usb.core\n\nfrom instruments.abstract_instruments.comm import (\n    SocketCommunicator,\n    USBCommunicator,\n    VisaCommunicator,\n    FileCommunicator,\n    LoopbackCommunicator,\n    GPIBCommunicator,\n    AbstractCommunicator,\n    USBTMCCommunicator,\n    VXI11Communicator,\n    serial_manager,\n)\nfrom instruments.optional_dep_finder import numpy\nfrom instruments.errors import AcknowledgementError, PromptError\n\n# CONSTANTS ###################################################################\n\n_DEFAULT_FORMATS = collections.defaultdict(lambda: \">b\")\n_DEFAULT_FORMATS.update({1: \">b\", 2: \">h\", 4: \">i\"})\n\n# CLASSES #####################################################################\n\n\nclass Instrument:\n    \"\"\"\n    This is the base instrument class from which all others are derived from.\n    It provides the basic implementation for all communication related\n    tasks. In addition, it also contains several class methods for opening\n    connections via the supported hardware channels.\n    \"\"\"\n\n    def __init__(self, filelike, *args, **kwargs):\n        # Check to make sure filelike is a subclass of AbstractCommunicator\n        if isinstance(filelike, AbstractCommunicator):\n            self._file = filelike\n        else:\n            raise TypeError(\n                \"Instrument must be initialized with a filelike \"\n                \"object that is a subclass of \"\n                \"AbstractCommunicator.\"\n            )\n        # Record if we're using the Loopback Communicator and put class in\n        # testing mode so we can disable sleeps in class implementations\n        self._testing = isinstance(self._file, LoopbackCommunicator)\n\n        self._prompt = None\n        self._terminator = \"\\n\"\n\n    # COMMAND-HANDLING METHODS #\n\n    def _ack_expected(self, msg=\"\"):  # pylint: disable=unused-argument,no-self-use\n        return None\n\n    def _authenticate(self, auth):\n        \"\"\"\n        Authenticate with credentials for establishing the communication with the\n        instrument.\n\n        :param auth: Credentials for authentication.\n        \"\"\"\n        raise NotImplementedError\n\n    def sendcmd(self, cmd):\n        \"\"\"\n        Sends a command without waiting for a response.\n\n        :param str cmd: String containing the command to\n            be sent.\n        \"\"\"\n        self._file.sendcmd(str(cmd))\n        ack_expected_list = self._ack_expected(\n            cmd\n        )  # pylint: disable=assignment-from-none\n        if not isinstance(ack_expected_list, (list, tuple)):\n            ack_expected_list = [ack_expected_list]\n        for ack_expected in ack_expected_list:\n            if ack_expected is None:\n                break\n            ack = self.read()\n            if ack != ack_expected:\n                raise AcknowledgementError(\n                    \"Incorrect ACK message received: got {} \"\n                    \"expected {}\".format(ack, ack_expected)\n                )\n        if self.prompt is not None:\n            prompt = self.read(len(self.prompt))\n            if prompt != self.prompt:\n                raise PromptError(\n                    \"Incorrect prompt message received: got {} \"\n                    \"expected {}\".format(prompt, self.prompt)\n                )\n\n    def query(self, cmd, size=-1):\n        \"\"\"\n        Executes the given query.\n\n        :param str cmd: String containing the query to\n            execute.\n        :param int size: Number of bytes to be read. Default is read until\n            termination character is found.\n        :return: The result of the query as returned by the\n            connected instrument.\n        :rtype: `str`\n        \"\"\"\n        ack_expected_list = self._ack_expected(\n            cmd\n        )  # pylint: disable=assignment-from-none\n        if not isinstance(ack_expected_list, (list, tuple)):\n            ack_expected_list = [ack_expected_list]\n\n        if ack_expected_list[0] is None:  # Case no ACK\n            value = self._file.query(cmd, size)\n        else:  # Case with ACKs\n            _ = self._file.query(cmd, size=0)  # Send the cmd, don't read\n            for ack_expected in ack_expected_list:  # Read and verify ACKs\n                ack = self.read()\n                if ack != ack_expected:\n                    raise AcknowledgementError(\n                        f\"Incorrect ACK message received: got {ack} expected {ack_expected}\"\n                    )\n            value = self.read(size)  # Now read in our return data\n        if self.prompt is not None:\n            prompt = self.read(len(self.prompt))\n            if prompt != self.prompt:\n                raise PromptError(\n                    f\"Incorrect prompt message received: got {prompt} expected {self.prompt}\"\n                )\n        return value\n\n    def read(self, size=-1, encoding=\"utf-8\"):\n        \"\"\"\n        Read the last line.\n\n        :param int size: Number of bytes to be read. Default is read until\n            termination character is found.\n        :return: The result of the read as returned by the\n            connected instrument.\n        :rtype: `str`\n        \"\"\"\n        return self._file.read(size, encoding)\n\n    def read_raw(self, size=-1):\n        \"\"\"\n        Read the raw last line.\n\n        :param int size: Number of bytes to be read. Default is read until\n            termination character is found.\n        :return: The result of the read as returned by the\n            connected instrument.\n        :rtype: `str`\n        \"\"\"\n        return self._file.read_raw(size)\n\n    # PROPERTIES #\n\n    @property\n    def timeout(self):\n        \"\"\"\n        Gets/sets the communication timeout for this instrument. Note that\n        setting this value after opening the connection is not supported for\n        all connection types.\n\n        :type: `int`\n        \"\"\"\n        return self._file.timeout\n\n    @timeout.setter\n    def timeout(self, newval):\n        self._file.timeout = newval\n\n    @property\n    def address(self):\n        \"\"\"\n        Gets/sets the target communication of the instrument.\n\n        This is useful for situations when running straight from a Python shell\n        and your instrument has enumerated with a different address. An example\n        when this can happen is if you are using a USB to Serial adapter and\n        you disconnect/reconnect it.\n\n        :type: `int` for GPIB address, `str` for other\n        \"\"\"\n        return self._file.address\n\n    @address.setter\n    def address(self, newval):\n        self._file.address = newval\n\n    @property\n    def terminator(self):\n        \"\"\"\n        Gets/sets the terminator used for communication.\n\n        For communication options where this is applicable, the value\n        corresponds to the ASCII character used for termination in decimal\n        format. Example: 10 sets the character to NEWLINE.\n\n        :type: `int`, or `str` for GPIB adapters.\n        \"\"\"\n        return self._file.terminator\n\n    @terminator.setter\n    def terminator(self, newval):\n        self._file.terminator = newval\n\n    @property\n    def prompt(self):\n        \"\"\"\n        Gets/sets the prompt used for communication.\n\n        The prompt refers to a character that is sent back from the instrument\n        after it has finished processing your last command. Typically this is\n        used to indicate to an end-user that the device is ready for input when\n        connected to a serial-terminal interface.\n\n        In IK, the prompt is specified that that it (and its associated\n        termination character) are read in. The value read in from the device\n        is also checked against the stored prompt value to make sure that\n        everything is still in sync.\n\n        :type: `str`\n        \"\"\"\n        return self._prompt\n\n    @prompt.setter\n    def prompt(self, newval):\n        self._prompt = newval\n\n    # BASIC I/O METHODS #\n\n    def write(self, msg):\n        \"\"\"\n        Write data string to the connected instrument. This will call\n        the write method for the attached filelike object. This will typically\n        bypass attaching any termination characters or other communication\n        channel related work.\n\n        .. seealso:: `Instrument.sendcmd` if you wish to send a string to the\n            instrument, while still having InstrumentKit handle termination\n            characters and other communication channel related work.\n\n        :param str msg: String that will be written to the filelike object\n            (`Instrument._file`) attached to this instrument.\n        \"\"\"\n        self._file.write(msg)\n\n    def binblockread(self, data_width, fmt=None):\n        \"\"\" \"\n        Read a binary data block from attached instrument.\n        This requires that the instrument respond in a particular manner\n        as EOL terminators naturally can not be used in binary transfers.\n\n        The format is as follows:\n        #{number of following digits:1-F}{num of bytes to be read}{data bytes}\n\n        :param int data_width: Specify the number of bytes wide each data\n            point is. One of [1,2,4].\n\n        :param str fmt: Format string as specified by the :mod:`struct` module,\n            or `None` to choose a format automatically based on the data\n            width. Typically you can just specify `data_width` and leave this\n            default.\n        \"\"\"\n        # This needs to be a # symbol for valid binary block\n        symbol = self._file.read_raw(1)\n        if symbol != b\"#\":  # Check to make sure block is valid\n            raise OSError(\n                \"Not a valid binary block start. Binary blocks \"\n                \"require the first character to be #, instead got \"\n                \"{}\".format(symbol)\n            )\n        else:\n            # Read in the num of digits for next part\n            digits = int(self._file.read_raw(1), 16)\n\n            # Read in the num of bytes to be read\n            num_of_bytes = int(self._file.read_raw(digits))\n\n            # Make or use the required format string.\n            if fmt is None:\n                fmt = _DEFAULT_FORMATS[data_width]\n\n            # Read in the data bytes, and pass them to numpy using the specified\n            # data type (format).\n            # This is looped in case a communication timeout occurs midway\n            # through transfer and multiple reads are required\n            tries = 3\n            data = self._file.read_raw(num_of_bytes)\n            while len(data) < num_of_bytes:\n                old_len = len(data)\n                data += self._file.read_raw(num_of_bytes - old_len)\n                if old_len == len(data):\n                    tries -= 1\n                if tries == 0:\n                    raise OSError(\n                        \"Did not read in the required number of bytes \"\n                        \"during binblock read. Got {}, expected \"\n                        \"{}\".format(len(data), num_of_bytes)\n                    )\n            if numpy:\n                return numpy.frombuffer(data, dtype=fmt)\n            return struct.unpack(f\"{fmt[0]}{int(len(data)/data_width)}{fmt[-1]}\", data)\n\n    # CLASS METHODS #\n\n    URI_SCHEMES = [\n        \"serial\",\n        \"tcpip\",\n        \"gpib+usb\",\n        \"gpib+serial\",\n        \"visa\",\n        \"file\",\n        \"usbtmc\",\n        \"vxi11\",\n        \"test\",\n    ]\n\n    @classmethod\n    def open_from_uri(cls, uri):\n        # pylint: disable=too-many-return-statements,too-many-branches\n        \"\"\"\n        Given an instrument URI, opens the instrument named by that URI.\n        Instrument URIs are formatted with a scheme, such as ``serial://``,\n        followed by a location that is interpreted differently for each\n        scheme. The following examples URIs demonstrate the currently supported\n        schemes and location formats::\n\n            serial://COM3\n            serial:///dev/ttyACM0\n            tcpip://192.168.0.10:4100\n            gpib+usb://COM3/15\n            gpib+serial://COM3/15\n            gpib+serial:///dev/ttyACM0/15 # Currently non-functional.\n            visa://USB::0x0699::0x0401::C0000001::0::INSTR\n            usbtmc://USB::0x0699::0x0401::C0000001::0::INSTR\n            test://\n\n        For the ``serial`` URI scheme, baud rates may be explicitly specified\n        using the query parameter ``baud=``, as in the example\n        ``serial://COM9?baud=115200``. If not specified, the baud rate\n        is assumed to be 115200.\n\n        :param str uri: URI for the instrument to be loaded.\n        :rtype: `Instrument`\n\n        .. seealso::\n            `PySerial`_ documentation for serial port URI format\n\n        .. _PySerial: http://pyserial.sourceforge.net/\n        \"\"\"\n        # Make sure that urlparse knows that we want query strings.\n        for scheme in cls.URI_SCHEMES:\n            if scheme not in parse.uses_query:\n                parse.uses_query.append(scheme)\n\n        # Break apart the URI using urlparse. This returns a named tuple whose\n        # parts describe the incoming URI.\n        parsed_uri = parse.urlparse(uri)\n\n        # We always want the query string to provide keyword args to the\n        # class method.\n        # FIXME: This currently won't work, as everything is strings,\n        #        but the other class methods expect ints or floats, depending.\n        kwargs = parse.parse_qs(parsed_uri.query)\n        if parsed_uri.scheme == \"serial\":\n            # Ex: serial:///dev/ttyACM0\n            # We want to pass just the netloc and the path to PySerial,\n            # sending the query string as kwargs. Thus, we should make the\n            # device name here.\n            dev_name = parsed_uri.netloc\n            if parsed_uri.path:\n                dev_name = os.path.join(dev_name, parsed_uri.path)\n\n            # We should handle the baud rate separately, however, to ensure\n            # that the default is set correctly and that the type is `int`,\n            # as expected.\n            if \"baud\" in kwargs:\n                kwargs[\"baud\"] = int(kwargs[\"baud\"][0])\n            else:\n                kwargs[\"baud\"] = 115200\n\n            return cls.open_serial(dev_name, **kwargs)\n        elif parsed_uri.scheme == \"tcpip\":\n            # Ex: tcpip://192.168.0.10:4100\n            host, port = parsed_uri.netloc.split(\":\")\n            port = int(port)\n            return cls.open_tcpip(host, port, **kwargs)\n        elif parsed_uri.scheme == \"gpib+usb\" or parsed_uri.scheme == \"gpib+serial\":\n            # Ex: gpib+usb://COM3/15\n            #     scheme=\"gpib+usb\", netloc=\"COM3\", path=\"/15\"\n            # Make a new device path by joining the netloc (if any)\n            # with all but the last segment of the path.\n            uri_head, uri_tail = os.path.split(parsed_uri.path)\n            dev_path = os.path.join(parsed_uri.netloc, uri_head)\n            return cls.open_gpibusb(dev_path, int(uri_tail), **kwargs)\n        elif parsed_uri.scheme == \"visa\":\n            # Ex: visa://USB::{VID}::{PID}::{SERIAL}::0::INSTR\n            #     where {VID}, {PID} and {SERIAL} are to be replaced with\n            #     the vendor ID, product ID and serial number of the USB-VISA\n            #     device.\n            return cls.open_visa(parsed_uri.netloc, **kwargs)\n        elif parsed_uri.scheme == \"usbtmc\":\n            # TODO: check for other kinds of usbtmc URLs.\n            # Ex: usbtmc can take URIs exactly like visa://.\n            return cls.open_usbtmc(parsed_uri.netloc, **kwargs)\n        elif parsed_uri.scheme == \"file\":\n            return cls.open_file(\n                os.path.join(parsed_uri.netloc, parsed_uri.path), **kwargs\n            )\n        elif parsed_uri.scheme == \"vxi11\":\n            # Examples:\n            #   vxi11://192.168.1.104\n            #   vxi11://TCPIP::192.168.1.105::gpib,5::INSTR\n            return cls.open_vxi11(parsed_uri.netloc, **kwargs)\n        elif parsed_uri.scheme == \"test\":\n            return cls.open_test(**kwargs)\n        else:\n            raise NotImplementedError(\"Invalid scheme or not yet \" \"implemented.\")\n\n    @classmethod\n    def open_tcpip(cls, host, port, auth=None):\n        \"\"\"\n        Opens an instrument, connecting via TCP/IP to a given host and TCP port.\n\n        :param str host: Name or IP address of the instrument.\n        :param int port: TCP port on which the insturment is listening.\n        :param auth: Authentication credentials to establish connection.\n            Type depends on instrument and authentication method used.\n\n        :rtype: `Instrument`\n        :return: Object representing the connected instrument.\n\n        .. seealso::\n            `~socket.socket.connect` for description of `host` and `port`\n            parameters in the TCP/IP address family.\n        \"\"\"\n        conn = socket.socket()\n        conn.connect((host, port))\n\n        if auth is None:\n            ret_cls = cls(SocketCommunicator(conn))\n        else:\n            ret_cls = cls(SocketCommunicator(conn), auth=auth)\n\n        return ret_cls\n\n    # pylint: disable=too-many-arguments\n    @classmethod\n    def open_serial(\n        cls,\n        port=None,\n        baud=9600,\n        vid=None,\n        pid=None,\n        serial_number=None,\n        timeout=3,\n        write_timeout=3,\n        **kwargs,\n    ):\n        \"\"\"\n        Opens an instrument, connecting via a physical or emulated serial port.\n        Note that many instruments which connect via USB are exposed to the\n        operating system as serial ports, so this method will very commonly\n        be used for connecting instruments via USB.\n\n        This method can be called by either supplying a port as a string,\n        or by specifying vendor and product IDs, and an optional serial\n        number (used when more than one device with the same IDs is\n        attached). If both the port and IDs are supplied, the port will\n        default to the supplied port string, else it will search the\n        available com ports for a port matching the defined IDs and serial\n        number.\n\n        :param str port: Name of the the port or device file to open a\n            connection on. For example, ``\"COM10\"`` on Windows or\n            ``\"/dev/ttyUSB0\"`` on Linux.\n        :param int baud: The baud rate at which instrument communicates.\n        :param int vid: the USB port vendor id.\n        :param int pid: the USB port product id.\n        :param str serial_number: The USB port serial_number.\n        :param float timeout: Number of seconds to wait when reading from the\n            instrument before timing out.\n        :param float write_timeout: Number of seconds to wait when writing to the\n            instrument before timing out.\n        :param kwargs: Additional keyword arguments that will be passed on to\n            serial, e.g., `parity`.\n\n        :rtype: `Instrument`\n        :return: Object representing the connected instrument.\n\n        .. seealso::\n            `~serial.Serial` for description of `port`, baud rates and timeouts.\n        \"\"\"\n        if port is None and vid is None:\n            raise ValueError(\n                \"One of port, or the USB VID/PID pair, must be \" \"specified when \"\n            )\n        if port is not None and vid is not None:\n            raise ValueError(\n                \"Cannot specify both a specific port, and a USB\" \"VID/PID pair.\"\n            )\n        if (vid is not None and pid is None) or (pid is not None and vid is None):\n            raise ValueError(\n                \"Both VID and PID must be specified when opening\"\n                \"a serial connection via a USB VID/PID pair.\"\n            )\n\n        if port is None:\n            match_count = 0\n            for _port in comports():\n                # If no match on vid/pid, go to next comport\n                if not _port.pid == pid or not _port.vid == vid:\n                    continue\n                # If we specified a serial num, verify then break\n                if serial_number is not None and _port.serial_number == serial_number:\n                    port = _port.device\n                    break\n                # If no provided serial number, match, but also keep a count\n                if serial_number is None:\n                    port = _port.device\n                    match_count += 1\n                # If we found more than 1 vid/pid device, but no serial number,\n                # raise an exception due to ambiguity\n                if match_count > 1:\n                    raise SerialException(\n                        \"Found more than one matching serial \" \"port from VID/PID pair\"\n                    )\n\n        # if the port is still None after that, raise an error.\n        if port is None and vid is not None:\n            err_msg = (\n                \"Could not find a port with the attributes vid: {vid}, \"\n                \"pid: {pid}, serial number: {serial_number}\"\n            )\n            raise ValueError(\n                err_msg.format(\n                    vid=vid,\n                    pid=pid,\n                    serial_number=\"any\" if serial_number is None else serial_number,\n                )\n            )\n\n        ser = serial_manager.new_serial_connection(\n            port, baud=baud, timeout=timeout, write_timeout=write_timeout, **kwargs\n        )\n        return cls(ser)\n\n    @classmethod\n    def open_gpibusb(cls, port, gpib_address, timeout=3, write_timeout=3, model=\"gi\"):\n        \"\"\"\n        Opens an instrument, connecting via a\n        `Galvant Industries GPIB-USB adapter`_.\n\n        :param str port: Name of the the port or device file to open a\n            connection on. Note that because the GI GPIB-USB\n            adapter identifies as a serial port to the operating system, this\n            should be the name of a serial port.\n        :param int gpib_address: Address on the connected GPIB bus assigned to\n            the instrument.\n        :param float timeout: Number of seconds to wait when reading from the\n            instrument before timing out.\n        :param float write_timeout: Number of seconds to wait when writing to the\n            instrument before timing out.\n        :param str model: The brand of adapter to be connected to. Currently supported\n            is \"gi\" for Galvant Industries, and \"pl\" for Prologix LLC.\n\n        :rtype: `Instrument`\n        :return: Object representing the connected instrument.\n\n        .. seealso::\n            `~serial.Serial` for description of `port` and timeouts.\n\n        .. _Galvant Industries GPIB-USB adapter: galvant.ca/#!/store/gpibusb\n        \"\"\"\n        ser = serial_manager.new_serial_connection(\n            port, baud=460800, timeout=timeout, write_timeout=write_timeout\n        )\n        return cls(GPIBCommunicator(ser, gpib_address, model))\n\n    @classmethod\n    def open_gpibethernet(cls, host, port, gpib_address, model=\"pl\"):\n        \"\"\"\n        Opens an instrument, connecting via a Prologix GPIBETHERNET adapter.\n\n        :param str host: Name or IP address of the instrument.\n        :param int port: TCP port on which the insturment is listening.\n        :param int gpib_address: Address on the connected GPIB bus assigned to\n            the instrument.\n        :param str model: The brand of adapter to be connected to. Currently supported\n            is \"gi\" for Galvant Industries, and \"pl\" for Prologix LLC.\n\n        .. warning:: This function has been setup for use with the Prologix\n            GPIBETHERNET adapter but has not been tested as confirmed working.\n        \"\"\"\n        conn = socket.socket()\n        conn.connect((host, port))\n        return cls(GPIBCommunicator(conn, gpib_address, model))\n\n    @classmethod\n    def open_visa(cls, resource_name):\n        \"\"\"\n        Opens an instrument, connecting using the VISA library. Note that\n        `PyVISA`_ and a VISA implementation must both be present and installed\n        for this method to function.\n\n        :param str resource_name: Name of a VISA resource representing the\n            given instrument.\n\n        :rtype: `Instrument`\n        :return: Object representing the connected instrument.\n\n        .. seealso::\n            `National Instruments help page on VISA resource names\n            <http://zone.ni.com/reference/en-XX/help/371361J-01/lvinstio/visa_resource_name_generic/>`_.\n\n        .. _PyVISA: http://pyvisa.sourceforge.net/\n        \"\"\"\n        version = list(map(int, pyvisa.__version__.split(\".\")))\n        while len(version) < 3:\n            version += [0]\n        if version[0] >= 1 and version[1] >= 6:\n            ins = pyvisa.ResourceManager().open_resource(resource_name)\n        else:\n            ins = pyvisa.instrument(resource_name)  # pylint: disable=no-member\n        return cls(VisaCommunicator(ins))\n\n    @classmethod\n    def open_test(cls, stdin=None, stdout=None):\n        \"\"\"\n        Opens an instrument using a loopback communicator for a test\n        connection. The primary use case of this is to instantiate a specific\n        instrument class without requiring an actual physical connection\n        of any kind. This is also very useful for creating unit tests through\n        the parameters of this class method.\n\n        :param stdin: The stream of data coming from the instrument\n        :type stdin: `io.BytesIO` or `None`\n        :param stdout: Empty data stream that will hold data sent from the\n            Python class to the loopback communicator. This can then be checked\n            for the contents.\n        :type stdout: `io.BytesIO` or `None`\n        :return: Object representing the virtually-connected instrument\n        \"\"\"\n        return cls(LoopbackCommunicator(stdin, stdout))\n\n    @classmethod\n    def open_usbtmc(cls, *args, **kwargs):\n        \"\"\"\n        Opens an instrument, connecting to a USB-TMC device using the Python\n        `usbtmc` library.\n\n        .. warning:: The operational status of this is unknown. It is suggested\n            that you connect via the other provided class methods. For Linux,\n            if you have the ``usbtmc`` kernel module, the\n            `~instruments.Instrument.open_file` class method will work. On\n            Windows, using the `~instruments.Instrument.open_visa` class\n            method along with having the VISA libraries installed will work.\n\n        :return: Object representing the connected instrument\n        \"\"\"\n        usbtmc_comm = USBTMCCommunicator(*args, **kwargs)\n        return cls(usbtmc_comm)\n\n    @classmethod\n    def open_vxi11(cls, *args, **kwargs):\n        \"\"\"\n        Opens a vxi11 enabled instrument, connecting using the python\n        library `python-vxi11`_. This package must be present and installed\n        for this method to function.\n\n        :rtype: `Instrument`\n        :return: Object representing the connected instrument.\n\n        .. _python-vxi11: https://github.com/python-ivi/python-vxi11\n        \"\"\"\n        vxi11_comm = VXI11Communicator(*args, **kwargs)\n        return cls(vxi11_comm)\n\n    @classmethod\n    def open_usb(cls, vid, pid):\n        \"\"\"\n        Opens an instrument, connecting via a raw USB stream.\n\n        .. note::\n            Note that raw USB a very uncommon of connecting to instruments,\n            even for those that are connected by USB. Most will identify as\n            either serial ports (in which case,\n            `~instruments.Instrument.open_serial` should be used), or as\n            USB-TMC devices. On Linux, USB-TMC devices can be connected using\n            `~instruments.Instrument.open_file`, provided that the ``usbtmc``\n            kernel module is loaded. On Windows, some such devices can be opened\n            using the VISA library and the `~instruments.Instrument.open_visa`\n            method.\n\n        :param str vid: Vendor ID of the USB device to open.\n        :param str pid: Product ID of the USB device to open.\n\n        :rtype: `Instrument`\n        :return: Object representing the connected instrument.\n        \"\"\"\n        dev = usb.core.find(idVendor=vid, idProduct=pid)\n        if dev is None:\n            raise OSError(\"No such device found.\")\n\n        return cls(USBCommunicator(dev))\n\n    @classmethod\n    def open_file(cls, filename):\n        \"\"\"\n        Given a file, treats that file as a character device file that can\n        be read from and written to in order to communicate with the\n        instrument. This may be the case, for instance, if the instrument\n        is connected by the Linux ``usbtmc`` kernel driver.\n\n        :param str filename: Name of the character device to open.\n\n        :rtype: `Instrument`\n        :return: Object representing the connected instrument.\n        \"\"\"\n        return cls(FileCommunicator(filename))\n\n    def __enter__(self) -> typing_extensions.Self:\n        return self\n\n    def __exit__(self, *exc):\n        self._file.__exit__(*exc)\n"
  },
  {
    "path": "src/instruments/abstract_instruments/multimeter.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides an abstract base class for multimeter instruments\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport abc\n\nfrom instruments.abstract_instruments import Instrument\n\n# CLASSES #####################################################################\n\n\nclass Multimeter(Instrument, metaclass=abc.ABCMeta):\n    \"\"\"\n    Abstract base class for multimeter instruments.\n\n    All applicable concrete instruments should inherit from this ABC to\n    provide a consistent interface to the user.\n    \"\"\"\n\n    # PROPERTIES #\n\n    @property\n    @abc.abstractmethod\n    def mode(self):\n        \"\"\"\n        Gets/sets the measurement mode for the multimeter. This is an\n        abstract method.\n\n        :type: `~enum.Enum`\n        \"\"\"\n\n    @mode.setter\n    @abc.abstractmethod\n    def mode(self, newval):\n        pass\n\n    @property\n    @abc.abstractmethod\n    def trigger_mode(self):\n        \"\"\"\n        Gets/sets the trigger mode for the multimeter. This is an\n        abstract method.\n\n        :type: `~enum.Enum`\n        \"\"\"\n\n    @trigger_mode.setter\n    @abc.abstractmethod\n    def trigger_mode(self, newval):\n        pass\n\n    @property\n    @abc.abstractmethod\n    def relative(self):\n        \"\"\"\n        Gets/sets the status of relative measuring mode for the multimeter.\n        This is an abstract method.\n\n        :type: `bool`\n        \"\"\"\n\n    @relative.setter\n    @abc.abstractmethod\n    def relative(self, newval):\n        pass\n\n    @property\n    @abc.abstractmethod\n    def input_range(self):\n        \"\"\"\n        Gets/sets the current input range setting of the multimeter.\n        This is an abstract method.\n\n        :type: `~pint.Quantity` or `~enum.Enum`\n        \"\"\"\n\n    @input_range.setter\n    @abc.abstractmethod\n    def input_range(self, newval):\n        pass\n\n    # METHODS ##\n\n    @abc.abstractmethod\n    def measure(self, mode):\n        \"\"\"\n        Perform a measurement as specified by mode parameter.\n        \"\"\"\n"
  },
  {
    "path": "src/instruments/abstract_instruments/optical_spectrum_analyzer.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides an abstract base class for optical spectrum analyzer instruments\n\"\"\"\n\n# IMPORTS #####################################################################\n\nimport abc\n\nfrom instruments.abstract_instruments import Instrument\nfrom instruments.util_fns import ProxyList\n\n# CLASSES #####################################################################\n\n\nclass OpticalSpectrumAnalyzer(Instrument, metaclass=abc.ABCMeta):\n    \"\"\"\n    Abstract base class for optical spectrum analyzer instruments.\n\n    All applicable concrete instruments should inherit from this ABC to\n    provide a consistent interface to the user.\n    \"\"\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._channel_count = 1\n\n    class Channel(metaclass=abc.ABCMeta):\n        \"\"\"\n        Abstract base class for physical channels on an optical spectrum analyzer.\n\n        All applicable concrete instruments should inherit from this ABC to\n        provide a consistent interface to the user.\n\n        Optical spectrum analyzers that only have a single channel do not need to\n        define their own concrete implementation of this class. Ones with\n        multiple channels need their own definition of this class, where\n        this class contains the concrete implementations of the below\n        abstract methods. Instruments with 1 channel have their concrete\n        implementations at the parent instrument level.\n        \"\"\"\n\n        def __init__(self, parent, name):\n            self._parent = parent\n            self._name = name\n\n        # METHODS #\n\n        @abc.abstractmethod\n        def wavelength(self, bin_format=True):\n            \"\"\"\n            Gets the x-axis of the specified data source channel. This is an\n            abstract property.\n\n            :param bool bin_format: If the waveform should be transfered in binary\n                (``True``) or ASCII (``False``) formats.\n            :return: The wavelength component of the waveform.\n            :rtype: `numpy.ndarray`\n            \"\"\"\n            if self._parent._channel_count == 1:\n                return self._parent.wavelength(bin_format=bin_format)\n            else:\n                raise NotImplementedError\n\n        @abc.abstractmethod\n        def data(self, bin_format=True):\n            \"\"\"\n            Gets the y-axis of the specified data source channel. This is an\n            abstract property.\n\n            :param bool bin_format: If the waveform should be transfered in binary\n                (``True``) or ASCII (``False``) formats.\n            :return: The y-component of the waveform.\n            :rtype: `numpy.ndarray`\n            \"\"\"\n            if self._parent._channel_count == 1:\n                return self._parent.data(bin_format=bin_format)\n            else:\n                raise NotImplementedError\n\n    # PROPERTIES #\n\n    @property\n    def channel(self):\n        \"\"\"\n        Gets an iterator or list for easy Pythonic access to the various\n        channel objects on the OSA instrument. Typically generated\n        by the `~instruments.util_fns.ProxyList` helper.\n        \"\"\"\n        return ProxyList(self, self.Channel, range(self._channel_count))\n\n    @property\n    def start_wl(self):\n        \"\"\"\n        Gets/sets the the start wavelength of the OSA. This is\n        an abstract property.\n\n        :type: `~pint.Quantity`\n        \"\"\"\n        raise NotImplementedError\n\n    @start_wl.setter\n    def start_wl(self, newval):\n        raise NotImplementedError\n\n    @property\n    def stop_wl(self):\n        \"\"\"\n        Gets/sets the the stop wavelength of the OSA. This is\n        an abstract property.\n\n        :type: `~pint.Quantity`\n        \"\"\"\n        raise NotImplementedError\n\n    @stop_wl.setter\n    def stop_wl(self, newval):\n        raise NotImplementedError\n\n    @property\n    def bandwidth(self):\n        \"\"\"\n        Gets/sets the the bandwidth of the OSA. This is\n        an abstract property.\n\n        :type: `~pint.Quantity`\n        \"\"\"\n        raise NotImplementedError\n\n    @bandwidth.setter\n    def bandwidth(self, newval):\n        raise NotImplementedError\n\n    # METHODS #\n\n    @abc.abstractmethod\n    def start_sweep(self):\n        \"\"\"\n        Forces a start sweep on the attached OSA.\n        \"\"\"\n        raise NotImplementedError\n\n    def wavelength(self, bin_format=True):\n        \"\"\"\n        Gets the x-axis of the specified data source channel. This is an\n        abstract property.\n\n        :param bool bin_format: If the waveform should be transfered in binary\n            (``True``) or ASCII (``False``) formats.\n        :return: The wavelength component of the waveform.\n        :rtype: `numpy.ndarray`\n        \"\"\"\n        if self._channel_count > 1:\n            return self.channel[0].wavelength(bin_format=bin_format)\n        else:\n            raise NotImplementedError\n\n    def data(self, bin_format=True):\n        \"\"\"\n        Gets the y-axis of the specified data source channel. This is an\n        abstract property.\n\n        :param bool bin_format: If the waveform should be transfered in binary\n            (``True``) or ASCII (``False``) formats.\n        :return: The y-component of the waveform.\n        :rtype: `numpy.ndarray`\n        \"\"\"\n        if self._channel_count > 1:\n            return self.channel[0].data(bin_format=bin_format)\n        else:\n            raise NotImplementedError\n"
  },
  {
    "path": "src/instruments/abstract_instruments/oscilloscope.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides an abstract base class for oscilloscope instruments\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport abc\n\nfrom instruments.abstract_instruments import Instrument\n\n# CLASSES #####################################################################\n\n\nclass Oscilloscope(Instrument, metaclass=abc.ABCMeta):\n    \"\"\"\n    Abstract base class for oscilloscope instruments.\n\n    All applicable concrete instruments should inherit from this ABC to\n    provide a consistent interface to the user.\n    \"\"\"\n\n    class Channel(metaclass=abc.ABCMeta):\n        \"\"\"\n        Abstract base class for physical channels on an oscilloscope.\n\n        All applicable concrete instruments should inherit from this ABC to\n        provide a consistent interface to the user.\n        \"\"\"\n\n        # PROPERTIES #\n\n        @property\n        @abc.abstractmethod\n        def coupling(self):\n            \"\"\"\n            Gets/sets the coupling setting for the oscilloscope. This is an\n            abstract method.\n\n            :type: `~enum.Enum`\n            \"\"\"\n            raise NotImplementedError\n\n        @coupling.setter\n        @abc.abstractmethod\n        def coupling(self, newval):\n            raise NotImplementedError\n\n    class DataSource(metaclass=abc.ABCMeta):\n        \"\"\"\n        Abstract base class for data sources (physical channels, math, ref) on\n        an oscilloscope.\n\n        All applicable concrete instruments should inherit from this ABC to\n        provide a consistent interface to the user.\n        \"\"\"\n\n        def __init__(self, parent, name):\n            self._parent = parent\n            self._name = name\n            self._old_dsrc = None\n\n        def __enter__(self):\n            self._old_dsrc = self._parent.data_source\n            if self._old_dsrc != self:\n                # Set the new data source, and let __exit__ cleanup.\n                self._parent.data_source = self\n            else:\n                # There's nothing to do or undo in this case.\n                self._old_dsrc = None\n\n        def __exit__(self, type, value, traceback):\n            if self._old_dsrc is not None:\n                self._parent.data_source = self._old_dsrc\n\n        def __eq__(self, other):\n            if not isinstance(other, type(self)):\n                return NotImplemented\n\n            return other.name == self.name\n\n        __hash__ = None\n\n        # PROPERTIES #\n\n        @property\n        @abc.abstractmethod\n        def name(self):\n            \"\"\"\n            Gets the name of the channel. This is an abstract property.\n\n            :type: `str`\n            \"\"\"\n            raise NotImplementedError\n\n        # METHODS #\n\n        @abc.abstractmethod\n        def read_waveform(self, bin_format=True):\n            \"\"\"\n            Gets the waveform of the specified data source channel. This is an\n            abstract property.\n\n            :param bool bin_format: If the waveform should be transfered in binary\n                (``True``) or ASCII (``False``) formats.\n            :return: The waveform with both x and y components.\n            :rtype: `numpy.ndarray`\n            \"\"\"\n            raise NotImplementedError\n\n    # PROPERTIES #\n\n    @property\n    @abc.abstractmethod\n    def channel(self):\n        \"\"\"\n        Gets an iterator or list for easy Pythonic access to the various\n        channel objects on the oscilloscope instrument. Typically generated\n        by the `~instruments.util_fns.ProxyList` helper.\n        \"\"\"\n        raise NotImplementedError\n\n    @property\n    @abc.abstractmethod\n    def ref(self):\n        \"\"\"\n        Gets an iterator or list for easy Pythonic access to the various\n        ref data sources objects on the oscilloscope instrument. Typically\n        generated by the `~instruments.util_fns.ProxyList` helper.\n        \"\"\"\n        raise NotImplementedError\n\n    @property\n    @abc.abstractmethod\n    def math(self):\n        \"\"\"\n        Gets an iterator or list for easy Pythonic access to the various\n        math data sources objects on the oscilloscope instrument. Typically\n        generated by the `~instruments.util_fns.ProxyList` helper.\n        \"\"\"\n        raise NotImplementedError\n\n    # METHODS #\n\n    @abc.abstractmethod\n    def force_trigger(self):\n        \"\"\"\n        Forces a trigger event to occur on the attached oscilloscope.\n        \"\"\"\n        raise NotImplementedError\n"
  },
  {
    "path": "src/instruments/abstract_instruments/power_supply.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides an abstract base class for power supply instruments\n\"\"\"\n\n# IMPORTS #####################################################################\n\nimport abc\n\nfrom instruments.abstract_instruments import Instrument\n\n# CLASSES #####################################################################\n\n\nclass PowerSupply(Instrument, metaclass=abc.ABCMeta):\n    \"\"\"\n    Abstract base class for power supply instruments.\n\n    All applicable concrete instruments should inherit from this ABC to\n    provide a consistent interface to the user.\n    \"\"\"\n\n    class Channel(metaclass=abc.ABCMeta):\n        \"\"\"\n        Abstract base class for power supply output channels.\n\n        All applicable concrete instruments should inherit from this ABC to\n        provide a consistent interface to the user.\n        \"\"\"\n\n        # PROPERTIES #\n\n        @property\n        @abc.abstractmethod\n        def mode(self):\n            \"\"\"\n            Gets/sets the output mode for the power supply channel. This is an\n            abstract method.\n\n            :type: `~enum.Enum`\n            \"\"\"\n\n        @mode.setter\n        @abc.abstractmethod\n        def mode(self, newval):\n            pass\n\n        @property\n        @abc.abstractmethod\n        def voltage(self):\n            \"\"\"\n            Gets/sets the output voltage for the power supply channel. This is an\n            abstract method.\n\n            :type: `~pint.Quantity`\n            \"\"\"\n\n        @voltage.setter\n        @abc.abstractmethod\n        def voltage(self, newval):\n            pass\n\n        @property\n        @abc.abstractmethod\n        def current(self):\n            \"\"\"\n            Gets/sets the output current for the power supply channel. This is an\n            abstract method.\n\n            :type: `~pint.Quantity`\n            \"\"\"\n\n        @current.setter\n        @abc.abstractmethod\n        def current(self, newval):\n            pass\n\n        @property\n        @abc.abstractmethod\n        def output(self):\n            \"\"\"\n            Gets/sets the output status for the power supply channel. This is an\n            abstract method.\n\n            :type: `bool`\n            \"\"\"\n\n        @output.setter\n        @abc.abstractmethod\n        def output(self, newval):\n            pass\n\n    # PROPERTIES #\n\n    @property\n    @abc.abstractmethod\n    def channel(self):\n        \"\"\"\n        Gets a channel object for the power supply. This should use\n        `~instruments.util_fns.ProxyList` to achieve this.\n\n        This is an abstract method.\n\n        :rtype: `PowerSupply.Channel`\n        \"\"\"\n        raise NotImplementedError\n\n    @property\n    @abc.abstractmethod\n    def voltage(self):\n        \"\"\"\n        Gets/sets the output voltage for all channel on the power supply.\n        This is an abstract method.\n\n        :type: `~pint.Quantity`\n        \"\"\"\n\n    @voltage.setter\n    @abc.abstractmethod\n    def voltage(self, newval):\n        pass\n\n    @property\n    @abc.abstractmethod\n    def current(self):\n        \"\"\"\n        Gets/sets the output current for all channel on the power supply.\n        This is an abstract method.\n\n        :type: `~pint.Quantity`\n        \"\"\"\n\n    @current.setter\n    @abc.abstractmethod\n    def current(self, newval):\n        pass\n"
  },
  {
    "path": "src/instruments/abstract_instruments/signal_generator/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing signal generator abstract base classes\n\"\"\"\n\nfrom .signal_generator import SignalGenerator\nfrom .single_channel_sg import SingleChannelSG\nfrom .channel import SGChannel\n"
  },
  {
    "path": "src/instruments/abstract_instruments/signal_generator/channel.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides an abstract base class for signal generator output channels\n\"\"\"\n\n# IMPORTS #####################################################################\n\nimport abc\n\n# CLASSES #####################################################################\n\n\nclass SGChannel(metaclass=abc.ABCMeta):\n    \"\"\"\n    Python abstract base class representing a single channel for a signal\n    generator.\n\n    .. warning:: This class should NOT be manually created by the user. It is\n        designed to be initialized by the `~instruments.SignalGenerator` class.\n    \"\"\"\n\n    # PROPERTIES #\n\n    @property\n    @abc.abstractmethod\n    def frequency(self):\n        \"\"\"\n        Gets/sets the output frequency of the signal generator channel\n\n        :type: `~pint.Quantity`\n        \"\"\"\n\n    @frequency.setter\n    @abc.abstractmethod\n    def frequency(self, newval):\n        pass\n\n    @property\n    @abc.abstractmethod\n    def power(self):\n        \"\"\"\n        Gets/sets the output power of the signal generator channel\n\n        :type: `~pint.Quantity`\n        \"\"\"\n\n    @power.setter\n    @abc.abstractmethod\n    def power(self, newval):\n        pass\n\n    @property\n    @abc.abstractmethod\n    def phase(self):\n        \"\"\"\n        Gets/sets the output phase of the signal generator channel\n\n        :type: `~pint.Quantity`\n        \"\"\"\n\n    @phase.setter\n    @abc.abstractmethod\n    def phase(self, newval):\n        pass\n\n    @property\n    @abc.abstractmethod\n    def output(self):\n        \"\"\"\n        Gets/sets the output status of the signal generator channel\n\n        :type: `bool`\n        \"\"\"\n\n    @output.setter\n    @abc.abstractmethod\n    def output(self, newval):\n        pass\n"
  },
  {
    "path": "src/instruments/abstract_instruments/signal_generator/signal_generator.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides an abstract base class for signal generator instruments\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport abc\n\nfrom instruments.abstract_instruments import Instrument\n\n# CLASSES #####################################################################\n\n\nclass SignalGenerator(Instrument, metaclass=abc.ABCMeta):\n    \"\"\"\n    Python abstract base class for signal generators (eg microwave sources).\n\n    This ABC is not for function generators, which have their own separate ABC.\n\n    .. seealso::\n        `~instruments.FunctionGenerator`\n    \"\"\"\n\n    # PROPERTIES #\n\n    @property\n    @abc.abstractmethod\n    def channel(self):\n        \"\"\"\n        Gets a specific channel object for the SignalGenerator.\n\n        :rtype: A class inherited from `~instruments.SGChannel`\n        \"\"\"\n        raise NotImplementedError\n"
  },
  {
    "path": "src/instruments/abstract_instruments/signal_generator/single_channel_sg.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides an abstract base class for signal generators with only a single\noutput channel.\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nfrom instruments.abstract_instruments.signal_generator import SignalGenerator\nfrom instruments.abstract_instruments.signal_generator.channel import SGChannel\n\n# CLASSES #####################################################################\n\n# pylint: disable=abstract-method\n\n\nclass SingleChannelSG(SignalGenerator, SGChannel):\n    \"\"\"\n    Class for representing a Signal Generator that only has a single output\n    channel. The sole property in this class allows for the user to use the API\n    for SGs with multiple channels and a more compact form since it only has\n    one output.\n\n    For example, both of the following calls would work the same:\n\n    >>> print sg.channel[0].freq # Multi-channel style\n    >>> print sg.freq # Compact style\n\n    \"\"\"\n\n    # PROPERTIES #\n\n    @property\n    def channel(self):\n        return (self,)\n"
  },
  {
    "path": "src/instruments/agilent/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing Agilent instruments\n\"\"\"\n\nfrom instruments.agilent.agilent33220a import Agilent33220a\nfrom instruments.agilent.agilent34410a import Agilent34410a\n"
  },
  {
    "path": "src/instruments/agilent/agilent33220a.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the Agilent 33220a function generator.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom enum import Enum\n\n\nfrom instruments.generic_scpi import SCPIFunctionGenerator\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import (\n    enum_property,\n    int_property,\n    bool_property,\n    assume_units,\n)\n\n# CLASSES #####################################################################\n\n\nclass Agilent33220a(SCPIFunctionGenerator):\n    \"\"\"\n    The `Agilent/Keysight 33220a`_ is a 20MHz function/arbitrary waveform\n    generator. This model has been replaced by the Keysight 33500 series\n    waveform generators. This class may or may not work with these newer\n    models.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> import instruments.units as u\n    >>> inst = ik.agilent.Agilent33220a.open_gpibusb('/dev/ttyUSB0', 1)\n    >>> inst.function = inst.Function.sinusoid\n    >>> inst.frequency = 1 * u.kHz\n    >>> inst.output = True\n\n    .. _Agilent/Keysight 33220a: http://www.keysight.com/en/pd-127539-pn-33220A\n\n    \"\"\"\n\n    # ENUMS #\n\n    class Function(Enum):\n        \"\"\"\n        Enum containing valid functions for the Agilent/Keysight 33220a\n        \"\"\"\n\n        sinusoid = \"SIN\"\n        square = \"SQU\"\n        ramp = \"RAMP\"\n        pulse = \"PULS\"\n        noise = \"NOIS\"\n        dc = \"DC\"\n        user = \"USER\"\n\n    class LoadResistance(Enum):\n        \"\"\"\n        Enum containing valid load resistance for the Agilent/Keysight 33220a\n        \"\"\"\n\n        minimum = \"MIN\"\n        maximum = \"MAX\"\n        high_impedance = \"INF\"\n\n    class OutputPolarity(Enum):\n        \"\"\"\n        Enum containg valid output polarity modes for the\n        Agilent/Keysight 33220a\n        \"\"\"\n\n        normal = \"NORM\"\n        inverted = \"INV\"\n\n    # PROPERTIES #\n\n    function = enum_property(\n        command=\"FUNC\",\n        enum=Function,\n        doc=\"\"\"\n        Gets/sets the output function of the function generator\n\n        :type: `Agilent33220a.Function`\n        \"\"\",\n        set_fmt=\"{}:{}\",\n    )\n\n    duty_cycle = int_property(\n        command=\"FUNC:SQU:DCYC\",\n        doc=\"\"\"\n        Gets/sets the duty cycle of a square wave.\n\n        Duty cycle represents the amount of time that the square wave is at a\n        high level.\n\n        :type: `int`\n        \"\"\",\n        valid_set=range(101),\n    )\n\n    ramp_symmetry = int_property(\n        command=\"FUNC:RAMP:SYMM\",\n        doc=\"\"\"\n        Gets/sets the ramp symmetry for ramp waves.\n\n        Symmetry represents the amount of time per cycle that the ramp wave is\n        rising (unless polarity is inverted).\n\n        :type: `int`\n        \"\"\",\n        valid_set=range(101),\n    )\n\n    output = bool_property(\n        command=\"OUTP\",\n        inst_true=\"ON\",\n        inst_false=\"OFF\",\n        doc=\"\"\"\n        Gets/sets the output enable status of the front panel output connector.\n\n        The value `True` corresponds to the output being on, while `False` is\n        the output being off.\n\n        :type: `bool`\n        \"\"\",\n    )\n\n    output_sync = bool_property(\n        command=\"OUTP:SYNC\",\n        inst_true=\"ON\",\n        inst_false=\"OFF\",\n        doc=\"\"\"\n        Gets/sets the enabled status of the front panel sync connector.\n\n        :type: `bool`\n        \"\"\",\n    )\n\n    output_polarity = enum_property(\n        command=\"OUTP:POL\",\n        enum=OutputPolarity,\n        doc=\"\"\"\n        Gets/sets the polarity of the waveform relative to the offset voltage.\n\n        :type: `~Agilent33220a.OutputPolarity`\n        \"\"\",\n    )\n\n    @property\n    def load_resistance(self):\n        \"\"\"\n        Gets/sets the desired output termination load (ie, the impedance of the\n        load attached to the front panel output connector).\n\n        The instrument has a fixed series output impedance of 50ohms. This\n        function allows the instrument to compensate of the voltage divider\n        and accurately report the voltage across the attached load.\n\n        :units: As specified (if a `~pint.Quantity`) or assumed\n            to be of units :math:`\\\\Omega` (ohm).\n        :type: `~pint.Quantity` or `Agilent33220a.LoadResistance`\n        \"\"\"\n        value = self.query(\"OUTP:LOAD?\")\n        try:\n            return int(value) * u.ohm\n        except ValueError:\n            return self.LoadResistance(value.strip())\n\n    @load_resistance.setter\n    def load_resistance(self, newval):\n        if isinstance(newval, self.LoadResistance):\n            newval = newval.value\n        else:\n            newval = assume_units(newval, u.ohm).to(u.ohm).magnitude\n            if (newval < 0) or (newval > 10000):\n                raise ValueError(\"Load resistance must be between 0 and 10,000\")\n        self.sendcmd(f\"OUTP:LOAD {newval}\")\n\n    @property\n    def phase(self):\n        raise NotImplementedError\n\n    @phase.setter\n    def phase(self, newval):\n        raise NotImplementedError\n"
  },
  {
    "path": "src/instruments/agilent/agilent34410a.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the Agilent 34410a digital multimeter.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom instruments.generic_scpi import SCPIMultimeter\nfrom instruments.optional_dep_finder import numpy\nfrom instruments.units import ureg as u\n\n# CLASSES #####################################################################\n\n\nclass Agilent34410a(SCPIMultimeter):  # pylint: disable=abstract-method\n    \"\"\"\n    The Agilent 34410a is a very popular 6.5 digit DMM. This class should also\n    cover the Agilent 34401a, 34411a, as well as the backwards compatability\n    mode in the newer Agilent/Keysight 34460a/34461a. You can find the full\n    specifications for these instruments on the `Keysight website`_.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> import instruments.units as u\n    >>> dmm = ik.agilent.Agilent34410a.open_gpibusb('/dev/ttyUSB0', 1)\n    >>> print(dmm.measure(dmm.Mode.resistance))\n\n    .. _Keysight website: http://www.keysight.com/\n    \"\"\"\n\n    # PROPERTIES #\n\n    @property\n    def data_point_count(self):\n        \"\"\"\n        Gets the total number of readings that are located in reading memory\n        (RGD_STORE).\n\n        :rtype: `int`\n        \"\"\"\n        return int(self.query(\"DATA:POIN?\"))\n\n    # STATE MANAGEMENT METHODS #\n\n    def init(self):\n        \"\"\"\n        Switch device from \"idle\" state to \"wait-for-trigger state\".\n        Measurements will begin when specified triggering conditions are met,\n        following the receipt of the INIT command.\n\n        Note that this command will also clear the previous set of readings\n        from memory.\n        \"\"\"\n        self.sendcmd(\"INIT\")\n\n    def abort(self):\n        \"\"\"\n        Abort all measurements currently in progress.\n        \"\"\"\n        self.sendcmd(\"ABOR\")\n\n    # MEMORY MANAGEMENT METHODS #\n\n    def clear_memory(self):\n        \"\"\"\n        Clears the non-volatile memory of the Agilent 34410a.\n        \"\"\"\n        self.sendcmd(\"DATA:DEL NVMEM\")\n\n    def r(self, count):\n        \"\"\"\n        Have the multimeter perform a specified number of measurements and then\n        transfer them using a binary transfer method. Data will be cleared from\n        instrument memory after transfer is complete. Data is transfered\n        from the instrument in 64-bit double floating point precision format.\n\n        :param int count: Number of samples to take.\n\n        :rtype: `tuple`[`~pint.Quantity`, ...]\n            or if numpy is installed, `~pint.Quantity` with `numpy.array` data\n        \"\"\"\n        mode = self.mode\n        units = UNITS[mode]\n        if not isinstance(count, int):\n            raise TypeError('Parameter \"count\" must be an integer')\n        if count == 0:\n            msg = \"R?\"\n        else:\n            msg = \"R? \" + str(count)\n        self.sendcmd(\"FORM:DATA REAL,64\")\n        self.sendcmd(msg)\n        data = self.binblockread(8, fmt=\">d\")\n        if numpy:\n            return data * units\n        return tuple(val * units for val in data)\n\n    # DATA READING METHODS #\n\n    def fetch(self):\n        \"\"\"\n        Transfer readings from instrument memory to the output buffer, and\n        thus to the computer.\n        If currently taking a reading, the instrument will wait until it is\n        complete before executing this command.\n        Readings are NOT erased from memory when using fetch. Use the R?\n        command to read and erase data.\n        Note that the data is transfered as ASCII, and thus it is not\n        recommended to transfer a large number of\n        data points using this method.\n\n        :rtype: `tuple`[`~pint.Quantity`, ...]\n            or if numpy is installed, `~pint.Quantity` with `numpy.array` data\n        \"\"\"\n        units = UNITS[self.mode]\n        data = list(map(float, self.query(\"FETC?\").split(\",\")))\n        if numpy:\n            return data * units\n        return tuple(val * units for val in data)\n\n    def read_data(self, sample_count):\n        \"\"\"\n        Transfer specified number of data points from reading memory\n        (RGD_STORE) to output buffer.\n        First data point sent to output buffer is the oldest.\n        Data is erased after being sent to output buffer.\n\n        :param int sample_count: Number of data points to be transfered to\n            output buffer. If set to -1, all points in memory will be\n            transfered.\n\n        :rtype: `tuple`[`~pint.Quantity`, ...]\n            or if numpy is installed, `~pint.Quantity` with `numpy.array` data\n        \"\"\"\n        if not isinstance(sample_count, int):\n            raise TypeError('Parameter \"sample_count\" must be an integer.')\n\n        if sample_count == -1:\n            sample_count = self.data_point_count\n        units = UNITS[self.mode]\n        self.sendcmd(\"FORM:DATA ASC\")\n        data = self.query(f\"DATA:REM? {sample_count}\").split(\",\")\n        data = list(map(float, data))\n        if numpy:\n            return data * units\n        return tuple(val * units for val in data)\n\n    def read_data_nvmem(self):\n        \"\"\"\n        Returns all readings in non-volatile memory (NVMEM).\n\n        :rtype: `tuple`[`~pint.Quantity`, ...]\n            or if numpy is installed, `~pint.Quantity` with `numpy.array` data\n        \"\"\"\n        units = UNITS[self.mode]\n        data = list(map(float, self.query(\"DATA:DATA? NVMEM\").split(\",\")))\n        if numpy:\n            return data * units\n        return tuple(val * units for val in data)\n\n    def read_last_data(self):\n        \"\"\"\n        Retrieve the last measurement taken. This can be executed at any time,\n        including when the instrument is currently taking measurements.\n        If there are no data points available, the value ``9.91000000E+37`` is\n        returned.\n\n        :units: As specified by the data returned by the instrument.\n        :rtype: `~pint.Quantity`\n        \"\"\"\n        data = self.query(\"DATA:LAST?\")\n        unit_map = {\n            \"VDC\": \"V\",\n            \"VAC\": \"V\",\n        }\n\n        if data == \"9.91000000E+37\":\n            return float(data)\n        else:\n            data = data.split(\" \")\n            data[0] = float(data[0])\n            if data[1] in unit_map:\n                data[1] = unit_map[data[1]]\n            return u.Quantity(*data)\n\n    def read_meter(self):\n        \"\"\"\n        Switch device from \"idle\" state to \"wait-for-trigger\" state.\n        Immediately after the trigger conditions are met, the data will be sent\n        to the output buffer of the instrument.\n\n        This is similar to calling `~Agilent34410a.init` and then immediately\n        following `~Agilent34410a.fetch`.\n\n        :rtype: `~pint.Quantity`\n        \"\"\"\n        mode = self.mode\n        units = UNITS[mode]\n        return float(self.query(\"READ?\")) * units\n\n\n# UNITS #######################################################################\n\nUNITS = {\n    Agilent34410a.Mode.capacitance: u.farad,\n    Agilent34410a.Mode.voltage_dc: u.volt,\n    Agilent34410a.Mode.voltage_ac: u.volt,\n    Agilent34410a.Mode.diode: u.volt,\n    Agilent34410a.Mode.current_ac: u.amp,\n    Agilent34410a.Mode.current_dc: u.amp,\n    Agilent34410a.Mode.resistance: u.ohm,\n    Agilent34410a.Mode.fourpt_resistance: u.ohm,\n    Agilent34410a.Mode.frequency: u.hertz,\n    Agilent34410a.Mode.period: u.second,\n    Agilent34410a.Mode.temperature: u.kelvin,\n    Agilent34410a.Mode.continuity: 1,\n}\n"
  },
  {
    "path": "src/instruments/aimtti/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing Aim-TTi power supplies\n\"\"\"\n\nfrom .aimttiel302p import AimTTiEL302P\n"
  },
  {
    "path": "src/instruments/aimtti/aimttiel302p.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the Aim-TTI EL302P power supply\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom enum import Enum\n\nfrom instruments.abstract_instruments import PowerSupply\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import (\n    bounded_unitful_property,\n    enum_property,\n    unitful_property,\n)\n\n# CLASSES #####################################################################\n\n\nclass AimTTiEL302P(PowerSupply, PowerSupply.Channel):\n    \"\"\"\n    The Aim-TTI EL302P is a single output power supply.\n\n    Because it is a single channel output, this object inherits from both\n    PowerSupply and PowerSupply.Channel.\n\n    Before this power supply can be remotely operated, remote communication\n    must be enabled and the unit must be on. Please refer to the manual.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> psu = ik.aimtti.AimTTiEL302P.open_serial('/dev/ttyUSB0', 9600)\n    >>> psu.voltage = 10 # Sets output voltage to 10V.\n    >>> psu.voltage\n    array(10.0) * V\n    >>> psu.output = True # Turns on the power supply\n    \"\"\"\n\n    # ENUMS ##\n\n    class Mode(Enum):\n        \"\"\"\n        Enum containing the possible modes of operations of the instrument.\n        \"\"\"\n\n        #: Constant voltage mode\n        voltage = \"M CV\"\n        #: Constant current mode\n        current = \"M CC\"\n\n    class Error(Enum):\n        \"\"\"\n        Enum containing the possible error codes returned by the instrument.\n        \"\"\"\n\n        #: No errors\n        error_none = \"ERR 0\"\n        #: Command not recognized\n        error_not_recognized = \"ERR 1\"\n        #: Command value outside instrument limits\n        error_outside_limits = \"ERR 2\"\n\n    # PROPERTIES ##\n\n    voltage, voltage_min, voltage_max = bounded_unitful_property(\n        \"V\",\n        u.volt,\n        valid_range=(0.0 * u.volt, 30.0 * u.volt),\n        doc=\"\"\"\n        Gets/sets the output voltage of the source. Value must be between\n        0V and 30V.\n\n        :units: As specified, or assumed to be :math:`\\\\text{V}` otherwise.\n        :type: `float` or `~pint.Quantity`\n        \"\"\",\n        input_decoration=lambda x: float(x[2:]),\n        format_code=\"{}\",\n    )\n\n    voltage_sense = unitful_property(\n        command=\"VO\",\n        units=u.volt,\n        doc=\"\"\"\n        Gets the actual output voltage measured by the power supply.\n\n        :units: :math:`\\\\text{V}`\n        :rtype: `~pint.Quantity`\n        \"\"\",\n        input_decoration=lambda x: float(x[2:]),\n        readonly=True,\n    )\n\n    current, current_min, current_max = bounded_unitful_property(\n        \"I\",\n        u.amp,\n        valid_range=(0.01 * u.amp, 2.0 * u.amp),\n        doc=\"\"\"\n        Gets/sets the output current of the source. Value must be between\n        0.01A and 2A.\n\n        :units: As specified, or assumed to be :math:`\\\\text{A}` otherwise.\n        :type: `float` or `~pint.Quantity`\n        \"\"\",\n        input_decoration=lambda x: float(x[2:]),\n        format_code=\"{}\",\n    )\n\n    current_sense = unitful_property(\n        command=\"IO\",\n        units=u.amp,\n        doc=\"\"\"\n        Gets the actual output current measured by the power supply.\n\n        :units: :math:`\\\\text{A}`\n        :rtype: `~pint.Quantity`\n        \"\"\",\n        input_decoration=lambda x: float(x[2:]),\n        readonly=True,\n    )\n\n    @property\n    def output(self):\n        return self.query(\"OUT?\") == \"OUT ON\"\n\n    @output.setter\n    def output(self, newval):\n        value = \"ON\" if newval is True else \"OFF\"\n        self.sendcmd(f\"{value}\")\n\n    mode = enum_property(\n        \"M\",\n        Mode,\n        doc=\"\"\"\n        Gets output mode status.\n        \"\"\",\n        readonly=True,\n    )\n\n    error = enum_property(\n        \"ERR\",\n        Error,\n        doc=\"\"\"\n        Gets the value in the error register.\n        \"\"\",\n        readonly=True,\n    )\n\n    @property\n    def name(self):\n        \"\"\"\n        Gets the name of the connected instrument.\n\n        :rtype: `str`\n        \"\"\"\n        idn_string = self.query(\"*IDN?\")\n        idn_list = idn_string.split(\",\")\n        return \" \".join(idn_list[:2])\n\n    def reset(self):\n        \"\"\"\n        Resets the instrument to the default power-up settings\n        (1.00V, 1.00A, output off).\n        \"\"\"\n        self.sendcmd(\"*RST\")\n\n    @property\n    def channel(self):\n        \"\"\"\n        Return the channel (which in this case is the entire instrument, since\n        there is only 1 channel on the EL302P.)\n\n        :rtype: 'tuple' of length 1 containing a reference back to the parent\n            EL302P object.\n        \"\"\"\n        return (self,)\n"
  },
  {
    "path": "src/instruments/comet/__init__.py",
    "content": "\"\"\"\nModule containing Comet instruments.\n\"\"\"\n\nfrom .cito_plus_1310 import CitoPlus1310\n"
  },
  {
    "path": "src/instruments/comet/cito_plus_1310.py",
    "content": "#!/usr/bin/env python\n\"\"\"Support for Comet Cito Plus RF generator.\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom enum import IntEnum\nfrom typing import Union\n\nfrom instruments.abstract_instruments import Instrument\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import assume_units\n\n# CLASSES #####################################################################\n\n\nclass CitoPlus1310(Instrument):\n    \"\"\"Communicate with the Comet Cito Plus 1310 RF generator.\n\n    Various connection options are available for different models.\n    Note that this driver is only tested with the RS-232 interface\n    and that, according to the manual, communication over TCP/IP is\n    different.\n\n    Important: Make sure that the correct parity is set in the instrument\n    and when calling the instrument. The default seems to be even parity.\n    Below example used even parity for the communication.\n    In general, all communication parameters (baud rate, parity, etc.) can\n    be set in the instrument itself. Below example just shows one possible\n    configuration.\n\n    Example:\n        >>> import serial\n        >>> import instruments as ik\n        >>> port = '/dev/ttyUSB0'\n        >>> baud = 115200\n        >>> inst = ik.comet.CitoPlus1310.open_serial(port, baud, parity=serial.PARITY_EVEN)\n        >>> inst.rf  # query RF state\n        False\n        >>> inst.rf = True  # turn on RF\n    \"\"\"\n\n    class RegulationMode(IntEnum):\n        \"\"\"Regulation modes that are available on the Cito Plus 1310.\"\"\"\n\n        ForwardPower = 0\n        LoadPower = 1\n        ProcessControl = 2\n\n    def __init__(self, filelike):\n        super().__init__(filelike)\n\n        self._address = 0x0A\n\n        self._exception_codes = {\n            0x01: \"Unknown parameter or illegal function code\",\n            0x04: \"Value invalid\",\n            0x05: \"Parameter not writable\",\n            0x06: \"Parameter not readable\",\n            0x07: \"Stop\",\n            0x08: \"Not allowed\",\n            0x09: \"Wrong data type\",\n            0x0A: \"Internal error\",\n            0x0B: \"Value too high\",\n            0x0C: \"Value too low\",\n        }\n\n        self._byte_order = \"big\"  # byte order for command and data\n        self._byte_order_crc = \"little\"  # byte order for CRC-16 checksum\n\n    @property\n    def name(self) -> str:\n        \"\"\"Get the name of the instrument.\"\"\"\n        data = self.query(self._make_pkg(10))\n        return data.decode(\"utf-8\")\n\n    @property\n    def forward_power(self) -> u.Quantity:\n        \"\"\"Get the actual forward power of the generator in W.\n\n        :return: Forward power.\n        :rtype: Quantity\n        \"\"\"\n        data = self.query(self._make_pkg(8021))\n        data = int.from_bytes(data, byteorder=self._byte_order)\n        return assume_units(data, u.mW).to(u.W)\n\n    @property\n    def load_power(self) -> u.Quantity:\n        \"\"\"Get the actual load power of the generator in W.\n\n        :return: Load power.\n        :rtype: Quantity\n        \"\"\"\n        data = self.query(self._make_pkg(8023))\n        data = int.from_bytes(data, byteorder=self._byte_order)\n        return assume_units(data, u.mW).to(u.W)\n\n    @property\n    def output_power(self) -> u.Quantity:\n        \"\"\"Get/set the set output power of the generator in W.\n\n        :return: Output power.\n        :rtype: Quantity\n        \"\"\"\n        data = self.query(self._make_pkg(1206))\n        data = int.from_bytes(data, byteorder=self._byte_order)\n        return assume_units(data, u.mW).to(u.W)\n\n    @output_power.setter\n    def output_power(self, value: u.Quantity) -> None:\n        value = assume_units(value, u.W).to(u.mW)\n        if value < 1 * u.W:\n            value = 0 * u.W  # instrument can't set anything lower\n        value = int(value.magnitude)\n        self.sendcmd(self._make_pkg(1206, value))\n\n    @property\n    def reflected_power(self) -> u.Quantity:\n        \"\"\"Get the actual reflected power of the generator in W.\n\n        :return: Reflected power.\n        :rtype: Quantity\n        \"\"\"\n        data = self.query(self._make_pkg(8022))\n        data = int.from_bytes(data, byteorder=self._byte_order)\n        return assume_units(data, u.mW).to(u.W)\n\n    @property\n    def regulation_mode(self) -> RegulationMode:\n        \"\"\"Get/set the regulation mode of the generator.\n\n        :return: Regulation mode.\n        :rtype: RegulationMode\n        \"\"\"\n        data = self.query(self._make_pkg(1201))\n        return self.RegulationMode(int.from_bytes(data, byteorder=self._byte_order))\n\n    @regulation_mode.setter\n    def regulation_mode(self, value) -> None:\n        self.sendcmd(self._make_pkg(1201, value.value))\n\n    @property\n    def rf(self) -> bool:\n        \"\"\"Get/set the RF state.\n\n        :return: The RF state.\n        :rtype: bool\n        \"\"\"\n        data = self.query(self._make_pkg(8000))\n        return int.from_bytes(data, byteorder=self._byte_order) != 1\n\n    @rf.setter\n    def rf(self, value: bool) -> None:\n        data = 1 if value else 0\n        self.sendcmd(self._make_pkg(1001, data))\n\n    def sendcmd(self, pkg: bytes) -> None:\n        \"\"\"Write a command to the instrument.\n\n        Uses the query command to check return, i.e., that everything is fine,\n        but does not return data.\n\n        :param bytes pkg: The package to send to the instrument.\n        \"\"\"\n        self.query(pkg, write_cmd=True)\n\n    def query(self, pkg: bytes, write_cmd=False) -> Union[None, bytes]:\n        \"\"\"Query instrument.\n\n        This will check if the command is accepted by the instrument and if not,\n        raise an OSError with the appropriate return code that came back.\n\n        :param bytes pkg: The package to send to the instrument.\n        :param boolwrite_cmd: If True, this is a write command and will only check\n                if received package the same as sent one.\n        \"\"\"\n        self._file.write_raw(pkg)\n\n        hdr = self._file.read_raw(2)\n        fn_code = hdr[1]\n\n        if fn_code != 0x41 and fn_code != 0x42:\n            exc_code = self._file.read_raw(1)[0]\n            self._check_exception(fn_code, exc_code)\n\n        if write_cmd:\n            # read the rest, make sure the packages agree and if not raise OSError.\n            len_to_read = len(pkg) - 2\n            rest = self._file.read_raw(len_to_read)\n            pkg_return = hdr + rest\n            if pkg_return != pkg:\n                raise OSError(\"Received package does not match sent package.\")\n            return\n\n        # so it is a query and we expect data\n        data_length = self._file.read_raw(1)\n        data = self._file.read_raw(\n            int.from_bytes(data_length, byteorder=self._byte_order)\n        )\n        crc = self._file.read_raw(2)\n\n        crc_exp = _crc16(hdr + data_length + data).to_bytes(\n            2, byteorder=self._byte_order_crc\n        )\n\n        if crc != crc_exp:\n            raise OSError(\"CRC-16 checksum of returned package does not match.\")\n\n        return data\n\n    def _check_exception(self, fn_code: int, exc_code: int) -> None:\n        \"\"\"Checks if the function code is an exception and raises an OSError if so.\n\n        :param int fn_code: The function code.\n        :param int exc_code: The exception code.\n\n        :raises OSError: If the function code is an exception.\n        \"\"\"\n        if fn_code != 0x41 or fn_code != 0x42:\n            raise OSError(\n                f\"Exception code: {hex(exc_code)}: {self._exception_codes.get(exc_code, 'Unknown')}\"\n            )\n\n    def _make_hdr(self, fn_code: int) -> bytes:\n        \"\"\"Make the header according to our init settings.\n\n        :param int fn_code: The function code to use.\n\n        :return: The header bytes.\n        :rtype: bytes\n        \"\"\"\n        hdr = bytes([self._address, fn_code])\n        return hdr\n\n    def _make_pkg(self, cmd_code, data=None, data_length=4):\n        \"\"\"Create a package to send to the instrument.\n\n        :param int cmd_code: The command code.\n        :param data: The data to send. If None, this is a read command. Defaults to None.\n        :param int data_length: The length of the data in bytes. Only used when writing.\n\n        :return: Properly packed data to send to the instrument.\n        :rtype: bytes\n        \"\"\"\n        if data is None:\n            fn_code = 0x41\n        else:\n            fn_code = 0x42\n\n        hdr = self._make_hdr(fn_code)\n\n        cmd = cmd_code.to_bytes(length=2, byteorder=self._byte_order)\n\n        if data is not None:\n            dat = data.to_bytes(length=data_length, byteorder=self._byte_order)\n        else:\n            dat = (0x01).to_bytes(length=2, byteorder=self._byte_order)\n\n        pkg = hdr + cmd + dat\n        crc = _crc16(pkg)\n        crc_bytes = crc.to_bytes(2, byteorder=self._byte_order_crc)\n\n        return pkg + crc_bytes\n\n\ndef _crc16(data: bytes):\n    \"\"\"Create the CRC-16 checksum for the given data.\n\n    :param bytes data: The data for which to create the checksum.\n\n    :return: The CRC-16 checksum.\n    :rtype: int\n    \"\"\"\n    crc16tab = [\n        0x0000,\n        0xC0C1,\n        0xC181,\n        0x0140,\n        0xC301,\n        0x03C0,\n        0x0280,\n        0xC241,\n        0xC601,\n        0x06C0,\n        0x0780,\n        0xC741,\n        0x0500,\n        0xC5C1,\n        0xC481,\n        0x0440,\n        0xCC01,\n        0x0CC0,\n        0x0D80,\n        0xCD41,\n        0x0F00,\n        0xCFC1,\n        0xCE81,\n        0x0E40,\n        0x0A00,\n        0xCAC1,\n        0xCB81,\n        0x0B40,\n        0xC901,\n        0x09C0,\n        0x0880,\n        0xC841,\n        0xD801,\n        0x18C0,\n        0x1980,\n        0xD941,\n        0x1B00,\n        0xDBC1,\n        0xDA81,\n        0x1A40,\n        0x1E00,\n        0xDEC1,\n        0xDF81,\n        0x1F40,\n        0xDD01,\n        0x1DC0,\n        0x1C80,\n        0xDC41,\n        0x1400,\n        0xD4C1,\n        0xD581,\n        0x1540,\n        0xD701,\n        0x17C0,\n        0x1680,\n        0xD641,\n        0xD201,\n        0x12C0,\n        0x1380,\n        0xD341,\n        0x1100,\n        0xD1C1,\n        0xD081,\n        0x1040,\n        0xF001,\n        0x30C0,\n        0x3180,\n        0xF141,\n        0x3300,\n        0xF3C1,\n        0xF281,\n        0x3240,\n        0x3600,\n        0xF6C1,\n        0xF781,\n        0x3740,\n        0xF501,\n        0x35C0,\n        0x3480,\n        0xF441,\n        0x3C00,\n        0xFCC1,\n        0xFD81,\n        0x3D40,\n        0xFF01,\n        0x3FC0,\n        0x3E80,\n        0xFE41,\n        0xFA01,\n        0x3AC0,\n        0x3B80,\n        0xFB41,\n        0x3900,\n        0xF9C1,\n        0xF881,\n        0x3840,\n        0x2800,\n        0xE8C1,\n        0xE981,\n        0x2940,\n        0xEB01,\n        0x2BC0,\n        0x2A80,\n        0xEA41,\n        0xEE01,\n        0x2EC0,\n        0x2F80,\n        0xEF41,\n        0x2D00,\n        0xEDC1,\n        0xEC81,\n        0x2C40,\n        0xE401,\n        0x24C0,\n        0x2580,\n        0xE541,\n        0x2700,\n        0xE7C1,\n        0xE681,\n        0x2640,\n        0x2200,\n        0xE2C1,\n        0xE381,\n        0x2340,\n        0xE101,\n        0x21C0,\n        0x2080,\n        0xE041,\n        0xA001,\n        0x60C0,\n        0x6180,\n        0xA141,\n        0x6300,\n        0xA3C1,\n        0xA281,\n        0x6240,\n        0x6600,\n        0xA6C1,\n        0xA781,\n        0x6740,\n        0xA501,\n        0x65C0,\n        0x6480,\n        0xA441,\n        0x6C00,\n        0xACC1,\n        0xAD81,\n        0x6D40,\n        0xAF01,\n        0x6FC0,\n        0x6E80,\n        0xAE41,\n        0xAA01,\n        0x6AC0,\n        0x6B80,\n        0xAB41,\n        0x6900,\n        0xA9C1,\n        0xA881,\n        0x6840,\n        0x7800,\n        0xB8C1,\n        0xB981,\n        0x7940,\n        0xBB01,\n        0x7BC0,\n        0x7A80,\n        0xBA41,\n        0xBE01,\n        0x7EC0,\n        0x7F80,\n        0xBF41,\n        0x7D00,\n        0xBDC1,\n        0xBC81,\n        0x7C40,\n        0xB401,\n        0x74C0,\n        0x7580,\n        0xB541,\n        0x7700,\n        0xB7C1,\n        0xB681,\n        0x7640,\n        0x7200,\n        0xB2C1,\n        0xB381,\n        0x7340,\n        0xB101,\n        0x71C0,\n        0x7080,\n        0xB041,\n        0x5000,\n        0x90C1,\n        0x9181,\n        0x5140,\n        0x9301,\n        0x53C0,\n        0x5280,\n        0x9241,\n        0x9601,\n        0x56C0,\n        0x5780,\n        0x9741,\n        0x5500,\n        0x95C1,\n        0x9481,\n        0x5440,\n        0x9C01,\n        0x5CC0,\n        0x5D80,\n        0x9D41,\n        0x5F00,\n        0x9FC1,\n        0x9E81,\n        0x5E40,\n        0x5A00,\n        0x9AC1,\n        0x9B81,\n        0x5B40,\n        0x9901,\n        0x59C0,\n        0x5880,\n        0x9841,\n        0x8801,\n        0x48C0,\n        0x4980,\n        0x8941,\n        0x4B00,\n        0x8BC1,\n        0x8A81,\n        0x4A40,\n        0x4E00,\n        0x8EC1,\n        0x8F81,\n        0x4F40,\n        0x8D01,\n        0x4DC0,\n        0x4C80,\n        0x8C41,\n        0x4400,\n        0x84C1,\n        0x8581,\n        0x4540,\n        0x8701,\n        0x47C0,\n        0x4680,\n        0x8641,\n        0x8201,\n        0x42C0,\n        0x4380,\n        0x8341,\n        0x4100,\n        0x81C1,\n        0x8081,\n        0x4040,\n    ]\n    crc = 0\n    for dat in data:\n        tmp = (0xFF & crc) ^ dat  # only last 16 bits of `crc`!\n        crc = (crc >> 8) ^ crc16tab[tmp]\n    return crc\n"
  },
  {
    "path": "src/instruments/config.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing support for loading instruments from configuration files.\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport warnings\n\nfrom ruamel.yaml import YAML\n\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import setattr_expression, split_unit_str\n\n# FUNCTIONS ###################################################################\n\n\ndef walk_dict(d, path):\n    \"\"\"\n    Given a \"path\" in a dictionary, returns the element specified by that path.\n    For instance, given ``{'a': {'b': 42, 'c': {'d': ['foo']}}}`,\n    the path ``\"/\"`` returns the whole dictionary, ``\"/a\"`` returns\n    ``{'b': 42, 'c': {'d': ['foo']}}`` and ``/a/c/d\"`` returns ``['foo']``.\n\n    If ``path`` is a list, it is treated identically to\n    ``\"/\" + \"/\".join(path)``.\n\n    :param dict d: The dictionary to walk through\n\n    :param path: The walking path through the dictionary\n    :type path: `str` or `list`\n    \"\"\"\n    # Treat as a base case that the path is empty.\n    if not path:\n        return d\n    if not isinstance(path, list):\n        path = path.split(\"/\")\n\n    if not path[0]:\n        # If the first part of the path is empty, do nothing.\n        return walk_dict(d, path[1:])\n\n    # Otherwise, resolve that segment and recurse.\n    return walk_dict(d[path[0]], path[1:])\n\n\ndef quantity_constructor(loader, node):\n    \"\"\"\n    Constructs a `u.Quantity` instance from a PyYAML\n    node tagged as ``!Q``.\n    \"\"\"\n    # Follows the example of http://stackoverflow.com/a/43081967/267841.\n    value = loader.construct_scalar(node)\n    return u.Quantity(*split_unit_str(value))\n\n\nyaml = YAML(typ=\"unsafe\")\n# We avoid having to register !Q every time by doing as soon as the\n# relevant constructor is defined.\nyaml.Constructor.add_constructor(\"!Q\", quantity_constructor)\n\n\ndef load_instruments(conf_file_name, conf_path=\"/\"):\n    \"\"\"\n    Given the path to a YAML-formatted configuration file and a path within\n    that file, loads the instruments described in that configuration file.\n    The subsection of the configuration file is expected to look like a map from\n    names to YAML nodes giving the class and instrument URI for each instrument.\n    For example::\n\n        ddg:\n            class: !!python/name:instruments.srs.SRSDG645\n            uri: gpib+usb://COM7/15\n\n    Loading instruments from this configuration will result in a dictionary of\n    the form\n    ``{'ddg': instruments.srs.SRSDG645.open_from_uri('gpib+usb://COM7/15')}``.\n\n    Each instrument configuration section can also specify one or more attributes\n    to set. These attributes are specified using a ``attrs`` section as well as the\n    required ``class`` and ``uri`` sections. For instance, the following\n    dictionary creates a ThorLabs APT motor controller instrument with a single motor\n    model configured::\n\n        rot_stage:\n            class: !!python/name:instruments.thorabsapt.APTMotorController\n            uri: serial:///dev/ttyUSB0?baud=115200\n            attrs:\n                channel[0].motor_model: PRM1-Z8\n\n    Unitful attributes can be specified by using the ``!Q`` tag to quickly create\n    instances of `u.Quantity`. In the example above, for instance, we can set a motion\n    timeout as a unitful quantity::\n\n        attrs:\n            motion_timeout: !Q 1 minute\n\n    When using the ``!Q`` tag, any text before a space is taken to be the magnitude\n    of the quantity, and text following is taken to be the unit specification.\n\n    By specifying a path within the configuration file, one can load only a part\n    of the given file. For instance, consider the configuration::\n\n        instruments:\n            ddg:\n                class: !!python/name:instruments.srs.SRSDG645\n                uri: gpib+usb://COM7/15\n        prefs:\n            ...\n\n    Then, specifying ``\"/instruments\"`` as the configuration path will cause\n    this function to load the instruments named in that block, and ignore\n    all other keys in the YAML file.\n\n    :param str conf_file_name: Name of the configuration file to load\n        instruments from. Alternatively, a file-like object may be provided.\n    :param str conf_path: ``\"/\"`` separated path to the section in the\n        configuration file to load.\n\n    :rtype: `dict`\n\n    .. warning::\n        The configuration file must be trusted, as the class name references\n        allow for executing arbitrary code. Do not load instruments from\n        configuration files sent over network connections.\n\n        Note that keys in sections excluded by the ``conf_path`` argument are\n        still processed, such that any side effects that may occur due to\n        such processing will occur independently of the value of ``conf_path``.\n    \"\"\"\n\n    if isinstance(conf_file_name, str):\n        with open(conf_file_name) as f:\n            conf_dict = yaml.load(f)\n    else:\n        conf_dict = yaml.load(conf_file_name)\n\n    conf_dict = walk_dict(conf_dict, conf_path)\n\n    inst_dict = {}\n    for name, value in conf_dict.items():\n        try:\n            inst_dict[name] = value[\"class\"].open_from_uri(value[\"uri\"])\n\n            if \"attrs\" in value:\n                # We have some attrs we can set on the newly created instrument.\n                for attr_name, attr_value in value[\"attrs\"].items():\n                    setattr_expression(inst_dict[name], attr_name, attr_value)\n\n        except OSError as ex:\n            # FIXME: need to subclass Warning so that repeated warnings\n            #        aren't ignored.\n            warnings.warn(\n                \"Exception occured loading device with URI \"\n                \"{}:\\n\\t{}.\".format(value[\"uri\"], ex),\n                RuntimeWarning,\n            )\n            inst_dict[name] = None\n\n    return inst_dict\n"
  },
  {
    "path": "src/instruments/delta_elektronika/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing Delta Elektronika instruments\n\"\"\"\n\nfrom instruments.delta_elektronika.psc_eth import PscEth\n"
  },
  {
    "path": "src/instruments/delta_elektronika/psc_eth.py",
    "content": "\"\"\"Support for Delta Elektronika DC power supplies with PSC-ETH-2 interface.\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom enum import IntEnum\nfrom typing import Tuple, Union\n\nfrom instruments.abstract_instruments import Instrument\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import assume_units, unitful_property\n\n# CLASSES #####################################################################\n\n\nclass PscEth(Instrument):\n    \"\"\"Communicate with a Delta Elektronica one channel power supply via the\n    PSC-ETH-2 ethernet interface.\n\n    For communication, make sure the device is set to \"ethernet\" mode.\n\n    Example:\n        >>> import instruments as ik\n        >>> from instruments import units as u\n        >>> i = ik.delta_elektronika.PscEth.open_tcpip(\"192.168.127.100\", port=8462)\n        >>> print(i.name)\n    \"\"\"\n\n    def __init__(self, filelike):\n        super().__init__(filelike)\n\n    class LimitStatus(IntEnum):\n        \"\"\"Enum class for the limit status.\"\"\"\n\n        OFF = 0\n        ON = 1\n\n    # CLASS PROPERTIES #\n\n    @property\n    def name(self) -> str:\n        return self.query(\"*IDN?\")\n\n    @property\n    def current_limit(self) -> tuple[\"PscEth.LimitStatus\", u.Quantity]:\n        \"\"\"Get the current limit status.\n\n        :return: A tuple of the current limit status and the current limit value.\n        :rtype: `tuple` of (`PscEth.LimitStatus`, `~pint.Quantity`)\n        \"\"\"\n        resp = self.query(\"SYST:LIM:CUR?\")\n        val, status = resp.split(\",\")\n        ls = self.LimitStatus.OFF if \"off\" in status.lower() else self.LimitStatus.ON\n        return ls, assume_units(float(val), u.A)\n\n    @property\n    def voltage_limit(self) -> tuple[\"PscEth.LimitStatus\", u.Quantity]:\n        \"\"\"Get the voltage limit status.\n\n        :return: A tuple of the voltage limit status and the voltage limit value.\n        :rtype: `tuple` of (`PscEth.LimitStatus`, `~pint.Quantity`)\n        \"\"\"\n        resp = self.query(\"SYST:LIM:VOL?\")\n        val, status = resp.split(\",\")\n        ls = self.LimitStatus.OFF if \"off\" in status.lower() else self.LimitStatus.ON\n        return ls, assume_units(float(val), u.V)\n\n    current = unitful_property(\n        \"SOUR:CURR\",\n        u.A,\n        format_code=\"{:.15f}\",\n        doc=\"\"\"\n        Set/get the output current.\n\n        Note: There is no bound checking of the value specified.\n\n        :newval: The output current to set.\n        :uval: `float` (assumes milliamps) or `~pint.Quantity`\n        \"\"\",\n    )\n\n    current_max = unitful_property(\n        \"SOUR:CURR:MAX\",\n        u.A,\n        format_code=\"{:.15f}\",\n        doc=\"\"\"\n        Set/get the maximum output current.\n\n        Note: This value should generally not be used. It sets the maximum\n        capable current of the power supply, which is fixed by the hardware.\n        If you set this to other values, you will get strange measurement results.\n\n        :newval: The maximum output current to set.\n        :uval: `float` (assumes milliamps) or `~pint.Quantity`\n        \"\"\",\n    )\n\n    current_measure = unitful_property(\n        \"MEAS:CURR\",\n        u.A,\n        format_code=\"{:.15f}\",\n        readonly=True,\n        doc=\"\"\"\n        Get the measured output current.\n\n        :rtype: `~pint.Quantity`\n        \"\"\",\n    )\n\n    current_stepsize = unitful_property(\n        \"SOUR:CUR:STE\",\n        u.A,\n        format_code=\"{:.15f}\",\n        readonly=True,\n        doc=\"\"\"\n        Get the output current step size.\n\n        :rtype: `~pint.Quantity`\n        \"\"\",\n    )\n\n    voltage = unitful_property(\n        \"SOUR:VOL\",\n        u.V,\n        format_code=\"{:.15f}\",\n        doc=\"\"\"\n        Set/get the output voltage.\n\n        Note: There is no bound checking of the value specified.\n\n        :newval: The output voltage to set.\n        :uval: `float` (assumes volts) or `~pint.Quantity`\n        \"\"\",\n    )\n\n    voltage_max = unitful_property(\n        \"SOUR:VOLT:MAX\",\n        u.V,\n        format_code=\"{:.15f}\",\n        doc=\"\"\"\n        Set/get the maximum output voltage.\n\n        Note: This value should generally not be used. It sets the maximum\n        capable voltage of the power supply, which is fixed by the hardware.\n        If you set this to other values, you will get strange measurement results.\n\n        :newval: The maximum output voltage to set.\n        :uval: `float` (assumes volts) or `~pint.Quantity`\n        \"\"\",\n    )\n\n    voltage_measure = unitful_property(\n        \"MEAS:VOLT\",\n        u.V,\n        format_code=\"{:.15f}\",\n        readonly=True,\n        doc=\"\"\"\n        Get the measured output voltage.\n\n        :rtype: `~pint.Quantity`\n        \"\"\",\n    )\n\n    voltage_stepsize = unitful_property(\n        \"SOUR:VOL:STE\",\n        u.V,\n        format_code=\"{:.15f}\",\n        readonly=True,\n        doc=\"\"\"\n        Get the output voltage step size.\n\n        :rtype: `~pint.Quantity`\n        \"\"\",\n    )\n\n    def recall(self) -> None:\n        \"\"\"Recall the settings from non-volatile memory.\"\"\"\n        self.sendcmd(\"*RCL\")\n\n    def reset(self) -> None:\n        \"\"\"Reset the instrument to default settings.\"\"\"\n        self.sendcmd(\"*RST\")\n\n    def save(self) -> None:\n        \"\"\"Save the current settings to non-volatile memory.\"\"\"\n        self.sendcmd(\"*SAV\")\n\n    def set_current_limit(\n        self, stat: \"PscEth.LimitStatus\", val: Union[float, u.Quantity] = 0\n    ) -> None:\n        \"\"\"Set the current limit.\n\n        :param stat: The limit status to set.\n        :type stat: `PscEth.LimitStatus`\n        :param val: The current limit value to set. Only requiered when turning it on.\n        :type val: `float` (assumes milliamps) or `~pint.Quantity`\n        \"\"\"\n        if not isinstance(stat, PscEth.LimitStatus):\n            raise TypeError(\"stat must be of type PscEth.LimitStatus\")\n        val = assume_units(val, u.A).to(u.A).magnitude\n        cmd = f\"SYST:LIM:CUR {val:.15f},{stat.name}\"\n        self.sendcmd(cmd)\n\n    def set_voltage_limit(\n        self, stat: \"PscEth.LimitStatus\", val: Union[float, u.Quantity] = 0\n    ) -> None:\n        \"\"\"Set the voltage limit.\n\n        :param stat: The limit status to set.\n        :type stat: `PscEth.LimitStatus`\n        :param val: The voltage limit value to set. Only requiered when turning it on.\n        :type val: `float` (assumes volts) or `~pint.Quantity`\n        \"\"\"\n        if not isinstance(stat, PscEth.LimitStatus):\n            raise TypeError(\"stat must be of type PscEth.LimitStatus\")\n        val = assume_units(val, u.V).to(u.V).magnitude\n        cmd = f\"SYST:LIM:VOL {val:.15f},{stat.name}\"\n        self.sendcmd(cmd)\n"
  },
  {
    "path": "src/instruments/dressler/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing Dressler instruments\n\"\"\"\n\nfrom instruments.dressler.cesar_1312 import Cesar1312\n"
  },
  {
    "path": "src/instruments/dressler/cesar_1312.py",
    "content": "\"\"\"Support for Dressler Cesar 1312 RF generator.\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom enum import IntEnum\nfrom typing import List, Tuple, Union\n\nfrom instruments.abstract_instruments import Instrument\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import assume_units\n\n# CLASSES #####################################################################\n\n\nclass Cesar1312(Instrument):\n    \"\"\"Communicate with the Dressler Cesar 1312 RF generator.\n\n    Various connection options are available for different models.\n    This driver has been tested using the RS-232 option.\n    The instrument for which this driver was tested required\n    odd parity mode. You must provide the correct parity for your\n    device when opening the serial connection.\n\n    Note that you must set the control mode to `ControlMode.Host`\n    in order to send any commands from the computer to the device.\n\n    Example:\n        >>> import serial\n        >>> import instruments as ik\n        >>> port = '/dev/ttyUSB0'\n        >>> baud = 115200\n        >>> inst = ik.dressler.Cesar1312.open_serial(port, baud, parity=serial.PARITY_ODD)\n        >>> inst.control_mode = inst.ControlMode.Host\n        >>> inst.rf  # query RF state\n        False\n        >>> inst.rf = True  # turn on RF\n    \"\"\"\n\n    class ControlMode(IntEnum):\n        \"\"\"Control modes of the Cesar 1312 RF generator.\"\"\"\n\n        Host = 2\n        UserPort = 4\n        FrontPanel = 6\n\n    class RegulationMode(IntEnum):\n        \"\"\"Regulation modes of the Cesar 1312 RF generator.\"\"\"\n\n        ForwardPower = 6\n        LoadPower = 7\n        ExternalPower = 8\n\n    def __init__(self, filelike):\n        super().__init__(filelike)\n\n        self._retries = 3\n\n        self._csr_codes = {\n            0: \"OK\",\n            1: \"Command rejected because unit is in wrong control mode.\",\n            2: \"Command rejected because RF output is on.\",\n            4: \"Command rejected because data sent is out of range.\",\n            5: \"Command rejected because User Port RF signal is off.\",\n            7: \"Command rejected because active fault(s) exist in generator.\",\n            9: \"Command rejected because the data byte count is incorrect.\",\n            19: \"Command rejected because the recipe mode is active\",\n            50: \"Command rejected because the frequency is out of range.\",\n            51: \"Command rejected because the duty cycle is out of range.\",\n            99: \"Command not implemented.\",\n        }\n        self._address = 0x01\n        self._ack = bytes([0x06])\n        self._nak = bytes([0x15])\n\n    # CLASS PROPERTIES #\n\n    @property\n    def address(self) -> int:\n        \"\"\"Set/get the address of the device.\n\n        Note that an address of 0 is the broadcast address.\n        Most likely, you can leave this at the default of 1.\n\n        :return: The set address.\n        :rtype: int\n        \"\"\"\n        return self._address\n\n    @address.setter\n    def address(self, value: int) -> None:\n        if value < 0 or value > 31:\n            raise ValueError(\"Address must be in the range 0-31.\")\n        self._address = value\n\n    @property\n    def retries(self) -> int:\n        \"\"\"Set/get the number of retries if a command fails.\n\n        :return: The number of retries as an integer.\n        :rtype: int\n        \"\"\"\n        return self._retries\n\n    @retries.setter\n    def retries(self, value: int) -> tuple[int, int, bytes]:\n        if value < 0:\n            raise ValueError(\"Retries must be greater than or equal to 0.\")\n        self._retries = value\n\n    # INSTRUMENT PROPERTIES #\n\n    @property\n    def control_mode(self) -> ControlMode:\n        \"\"\"Set/get the active control of the RF generator.\n\n        Possible values are given in the `ControlMode` enum. For computer\n        control, you likely want to set this to `ControlMode.Host`.\n\n        ..note:: If you set the control mode at any time back to\n            `ControlMode.FrontPanel`, the RF will turn off.\n\n        :return: The current control mode.\n        :rtype: ControlMode\n\n        Example:\n            >>> inst.control_mode = ik.dressler.Cesar1312.ControlMode.Host\n            >>> inst.control_mode\n            <ControlMode.Host: 2>\n        \"\"\"\n        data = self.query(self._make_pkg(155))\n        return self.ControlMode(data[0])\n\n    @control_mode.setter\n    def control_mode(self, value: ControlMode) -> None:\n        self.sendcmd(self._make_pkg(14, self._make_data(1, value.value)))\n\n    @property\n    def name(self) -> str:\n        \"\"\"Get the supply type and size of the RF generator.\n\n        :return: The supply type and size.\n        :rtype: str\n\n        Example:\n            >>> inst.name\n            'CESAR_1312'\n        \"\"\"\n        name_type = self.query(self._make_pkg(128)).decode(\"utf-8\")\n        name_size = self.query(self._make_pkg(129)).decode(\"utf-8\")\n        return f\"{name_type}{name_size}\"\n\n    @property\n    def output_power(self) -> u.Quantity:\n        \"\"\"Set/get the output power of the device in W.\n\n        :return: The output power in W for the defined mode.\n        :rtype: u.Quantity\n\n        Example:\n            >>> inst.output_power = 10 * u.W\n            >>> inst.output_power\n            <Quantity(10, 'watt')>\n        \"\"\"\n        ret_data = self.query(self._make_pkg(164))[:2]\n        return u.Quantity(int.from_bytes(ret_data, \"little\"), u.W)\n\n    @output_power.setter\n    def output_power(self, value: u.Quantity) -> None:\n        value = assume_units(value, u.W).to(u.W)\n        data = self._make_data(2, int(value.magnitude))\n        self.sendcmd(self._make_pkg(8, data))\n\n    @property\n    def reflected_power(self) -> u.Quantity:\n        \"\"\"Get the reflected power in W.\n\n        :return: The reflected power in W.\n        :rtype: u.Quantity\n\n        Example:\n            >>> inst.reflected_power\n            <Quantity(0, 'watt')>\n        \"\"\"\n        ret_data = self.query(self._make_pkg(166))\n        return u.Quantity(int.from_bytes(ret_data, \"little\"), u.W)\n\n    @property\n    def regulation_mode(self) -> RegulationMode:\n        \"\"\"Set/get the regulation mode.\n\n        Possible values are given in the `RegulationMode` enum.\n\n        :return: The current regulation mode.\n        :rtype: RegulationMode\n\n        Example:\n            >>> inst.regulation_mode = ik.dressler.Cesar1312.RegulationMode.ForwardPower\n            >>> inst.regulation_mode\n            <RegulationMode.ForwardPower: 6>\n        \"\"\"\n        data = self.query(self._make_pkg(154))\n        return self.RegulationMode(data[0])\n\n    @regulation_mode.setter\n    def regulation_mode(self, value: RegulationMode) -> None:\n        self.sendcmd(self._make_pkg(3, self._make_data(1, value.value)))\n\n    @property\n    def rf(self) -> bool:\n        \"\"\"Set/get the RF output state of the device.\n\n        RF on will be `True` while RF off will be `False`.\n\n        :return: The current RF state.\n        :rtype: bool\n\n        Example:\n            >>> inst.rf = True\n            >>> inst.rf\n            True\n        \"\"\"\n        data = self.query(self._make_pkg(162))\n        return bool(data[0] & 0b00100000)\n\n    @rf.setter\n    def rf(self, value: bool) -> None:\n        cmd = 2 if value else 1\n        self.sendcmd(self._make_pkg(cmd))\n\n    # METHODS #\n\n    def query(self, package: bytes, len_data=1) -> bytes:\n        \"\"\"Send a package to the instrument, assert it's all good, and return answer.\n\n        This sends the package and checks the response. If the response is NAK,\n        it retries until an ACK is received or the number of retries is reached.\n\n        Once an ACK is received, it listens for the response of the instrument\n        parsed the header, command, and optinally the data length (if > 6),\n        then listens to the data and checksum and ensures that the overallc hecksum\n        is zero. If not, it will send a NAK and retry reading until the checksum is\n        zero. Then it will send an ACK to finish the communication.\n\n        :param bytes package: The package to send.\n\n        :return: The data received from the device.\n        :rtype: bytes\n        \"\"\"\n        tries = 0\n        got_ack = False\n        while tries < self.retries + 1:\n            self._file.write_raw(package)\n            response = self.read_raw(1)\n            if response == self._ack:\n                got_ack = True\n                break\n            else:\n                tries += 1\n\n        if not got_ack:\n            raise OSError(\"Failed to get ACK from device after sending the command.\")\n\n        tries = 0\n        got_pkg = False\n        while tries < self.retries:\n            header = self.read_raw(1)\n            cmd = self.read_raw(1)\n\n            adr, dlength = self._unpack_header(header)\n\n            optional_data_length = None\n            if dlength == 0b111:\n                optional_data_length = self.read_raw(1)\n                dlength = int(optional_data_length.hex(), 16)\n\n            data = self.read_raw(dlength) if dlength > 0 else None\n\n            checksum = self.read_raw(1)\n\n            pkg = header + cmd\n            if optional_data_length:\n                pkg += optional_data_length\n\n            if data:\n                pkg += data\n            pkg += checksum\n\n            if self._calculate_checksum(pkg) == bytes([0x0]):\n                self._file.write_raw(self._ack)\n                got_pkg = True\n                break\n            else:\n                tries += 1\n                self._file.write_raw(self._nak)\n\n        if not got_pkg:\n            raise OSError(\"Failed to get a valid package from the device.\")\n\n        return data\n\n    def sendcmd(self, pkg: bytes) -> None:\n        \"\"\"Send a package to the instrument and assert it's all good.\n\n        Uses the query routine and interprets the data, which should be one byte,\n        as a CSR. If the CSR is not OK (0), will print a warning with the message.\n\n        :param bytes pkg: The package to send.\n        \"\"\"\n        data = self.query(pkg)\n        if data:\n            csr = int(data.hex(), 16)\n            if csr != 0:\n                raise OSError(\n                    f\"{self._csr_codes.get(csr, 'Unknown error')} (CSR={csr})\"\n                )\n        else:\n            raise ValueError(\"No data received from the device.\")\n\n    def _make_data(\n        self, length: Union[int, list[int]], data: Union[int, list[int]]\n    ) -> bytes:\n        \"\"\"Create the data bytes for the package.\n\n        If only one number is given, provide the length and the actual value as integers (or list).\n        If more than one number is given, provide both as lists.\n\n        :param Union[int, list[int]] length: The length of the data.\n        :param Union[int, list[int]] data: The data to send.\n\n        :return: Data in appropriate order.\n        :rtype: bytes\n        \"\"\"\n        if isinstance(length, int):\n            length = [length]\n        if isinstance(data, int):\n            data = [data]\n\n        data_bytes = b\"\"\n        for ll, dd in zip(length, data):\n            data_bytes += dd.to_bytes(ll, byteorder=\"little\", signed=False)\n\n        return data_bytes\n\n    def _make_pkg(self, cmd_number: int, data: Union[None, bytes] = None) -> bytes:\n        \"\"\"Make a package and return it packed as bytes.\n\n        :param int cmd_number: The command number.\n        :param bytes data: The data to send, already in proper order as bytes, or None.\n            Defaults to None, which makes it a query command.\n        \"\"\"\n        data_length = len(data) if data else 0\n\n        header = self._pack_header(data_length)\n\n        if data_length > 255:\n            raise ValueError(\"Data length too long, must be <= 255.\")\n\n        if cmd_number > 255:\n            raise ValueError(\"Command number too long, must be <= 255.\")\n\n        if data_length > 6:\n            pkg = [header, cmd_number, data_length]\n        else:\n            pkg = [header, cmd_number]\n\n        pkg = bytes(pkg)\n        if data is not None:\n            pkg += data\n\n        pkg = pkg + self._calculate_checksum(pkg)\n        return pkg\n\n    @staticmethod\n    def _calculate_checksum(data: bytes) -> bytes:\n        \"\"\"Calculate the checksum of the data.\n\n        :param bytes data: The data to calculate the checksum for.\n\n        :return: Checksum.\n        :rtype: bytes\n        \"\"\"\n        checksum = data[0]\n        for it, bt in enumerate(data):\n            if it > 0:\n                checksum ^= bt\n        return bytes([checksum])\n\n    def _pack_header(self, data_length: int):\n        \"\"\"Make the header of the package.\n\n        :param int data_length: The length of the data. If > 6, will be set to 7.\n\n        :return: The header as an integer.\n        \"\"\"\n        if data_length > 6:\n            data_length = 7  # need an extra byte for data length\n        return (self._address << 3) + data_length\n\n    @staticmethod\n    def _unpack_header(hdr: bytes) -> tuple[int]:\n        \"\"\"Parse the header and return address and data length.\n\n        :param bytes hdr: The header byte.\n\n        :return: The address and data length as integers.\n        :rtype: tuple[int]\n        \"\"\"\n        addr = hdr[0] >> 3\n        data_length = hdr[0] & 0b00000111\n        return addr, data_length\n"
  },
  {
    "path": "src/instruments/errors.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing custom exception errors used by various instruments.\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\n# CLASSES #####################################################################\n\n\nclass AcknowledgementError(IOError):\n    \"\"\"\n    This error is raised when an instrument fails to send the expected\n    acknowledgement string.\n    \"\"\"\n\n\nclass PromptError(IOError):\n    \"\"\"\n    This error is raised when an instrument fails to send a \"prompt\"\n    character when one is expected. Typically most instruments do not send\n    these characters, but some do in a misguided attempt to be more \"user\n    friendly\".\n    \"\"\"\n"
  },
  {
    "path": "src/instruments/fluke/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing Fluke instruments\n\"\"\"\n\nfrom .fluke3000 import Fluke3000\n"
  },
  {
    "path": "src/instruments/fluke/fluke3000.py",
    "content": "#!/usr/bin/env python\n#\n# fluke3000.py: Driver for the Fluke 3000 FC Industrial System\n#\n# © 2019 Francois Drielsma (francois.drielsma@gmail.com).\n#\n# This file is a part of the InstrumentKit project.\n# Licensed under the AGPL version 3.\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Affero General Public License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with this program. If not, see <http://www.gnu.org/licenses/>.\n#\n\"\"\"\nDriver for the Fluke 3000 FC Industrial System\n\nOriginally contributed and copyright held by Francois Drielsma\n(francois.drielsma@gmail.com)\n\nAn unrestricted license has been provided to the maintainers of the Instrument\nKit project.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nimport time\nfrom enum import Enum\n\nfrom instruments.abstract_instruments import Multimeter\nfrom instruments.units import ureg as u\n\n# CLASSES #####################################################################\n\n\nclass Fluke3000(Multimeter):\n    \"\"\"The `Fluke3000` is an ecosystem of devices produced by Fluke that may be\n    connected simultaneously to a Fluke PC3000 wireless adapter which exposes\n    a serial port to the computer to send and receive commands.\n\n    The `Fluke3000` ecosystem supports the following instruments:\n     - Fluke 3000 FC Series Wireless Multimeter\n     - Fluke v3000 FC Wireless AC Voltage Module\n     - Fluke v3001 FC Wireless DC Voltage Module\n     - Fluke t3000 FC Wireless Temperature Module\n\n    `Fluke3000` is a USB instrument that communicates through a serial port\n    via the PC3000 dongle. The commands used to communicate to the dongle\n    do not follow the SCPI standard.\n\n    When the device is reset, it searches for available wireless modules\n    and binds them to the PC3000 dongle. At each initialization, this class\n    checks what device has been bound and saves their module number.\n\n    This class has been tested with the 3000 FC Wireless Multimeter and\n    the t3000 FC Wireless Temperature Module. They have been operated\n    separately and simultaneously. It does not support the Wireless AC/DC\n    Voltage Modules as the author did not have them on hand.\n\n    It is important to note that the mode of the multimeter cannot be set\n    remotely. If must be set on the device prior to the measurement. If\n    the measurement read back from the multimeter is not expressed in the\n    expected units, this module will raise an error.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> mult = ik.fluke.Fluke3000.open_serial(\"/dev/ttyUSB0\", 115200)\n    >>> mult.measure(mult.Mode.voltage_dc) # Measures the DC voltage\n    array(12.345) * V\n\n    It is crucial not to kill this program in the process of making a measurement,\n    as for the Fluke 3000 FC Wireless Multimeter, one has to open continuous\n    readout, make a read and close it. If the process is killed, the read out\n    may not be closed and the serial cache will be constantly filled with measurements\n    that will interfere with any status query. If the multimeter is stuck in\n    continuous trigger after a bad kill, simply do:\n\n    >>> mult.reset()\n    >>> mult.flush()\n    >>> mult.connect()\n\n    Follow the same procedure if you want to add/remove an instrument to/from\n    the readout chain as the code will not look for new instruments if some\n    have already been connected to the PC3000 dongle.\n    \"\"\"\n\n    def __init__(self, filelike):\n        \"\"\"\n        Initialize the instrument, and set the properties needed for communication.\n        \"\"\"\n        super().__init__(filelike)\n        self.timeout = 3 * u.second\n        self.terminator = \"\\r\"\n        self.positions = {}\n        self.connect()\n\n    # ENUMS ##\n\n    class Module(Enum):\n        \"\"\"\n        Enum containing the supported modules serial numbers.\n        \"\"\"\n\n        #: Multimeter\n        m3000 = 46333030304643\n        #: Temperature module\n        t3000 = 54333030304643\n\n    class Mode(Enum):\n        \"\"\"\n        Enum containing the supported mode codes.\n        \"\"\"\n\n        #: AC Voltage\n        voltage_ac = \"01\"\n        #: DC Voltage\n        voltage_dc = \"02\"\n        #: AC Current\n        current_ac = \"03\"\n        #: DC Current\n        current_dc = \"04\"\n        #: Frequency\n        frequency = \"05\"\n        #: Temperature\n        temperature = \"07\"\n        #: Resistance\n        resistance = \"0B\"\n        #: Capacitance\n        capacitance = \"0F\"\n\n    # PROPERTIES ##\n\n    @property\n    def mode(self):\n        \"\"\"\n        Gets/sets the measurement mode for the multimeter.\n\n        The measurement mode of the multimeter must be set on the\n        device manually and cannot be set remotely. If a multimeter\n        is bound to the PC3000, returns its measurement mode by\n        making a measurement and checking the units bytes in response.\n\n        :rtype: `Fluke3000.Mode`\n        \"\"\"\n        if self.Module.m3000 not in self.positions:\n            raise KeyError(\"No `Fluke3000` FC multimeter is bound\")\n        port_id = self.positions[self.Module.m3000]\n        value = self.query_lines(f\"rfemd 0{port_id} 1\", 2)[-1]\n        self.query(f\"rfemd 0{port_id} 2\")\n        data = value.split(\"PH=\")[-1]\n        return self.Mode(self._parse_mode(data))\n\n    @property\n    def trigger_mode(self):\n        \"\"\"\n        Gets/sets the trigger mode for the multimeter.\n\n        The only supported mode is to trigger the device once when a\n        measurement is queried. This device does support continuous\n        triggering but it would quickly flood the serial input cache as\n        readouts do not overwrite each other and are accumulated.\n\n        :rtype: `str`\n        \"\"\"\n        raise AttributeError(\n            \"The `Fluke3000` only supports single trigger when queried\"\n        )\n\n    @property\n    def relative(self):\n        \"\"\"\n        Gets/sets the status of relative measuring mode for the multimeter.\n\n        The `Fluke3000` FC does not support relative measurements.\n\n        :rtype: `bool`\n        \"\"\"\n        raise AttributeError(\n            \"The `Fluke3000` FC does not support relative measurements\"\n        )\n\n    @property\n    def input_range(self):\n        \"\"\"\n        Gets/sets the current input range setting of the multimeter.\n\n        The `Fluke3000` FC is an autoranging only multimeter.\n\n        :rtype: `str`\n        \"\"\"\n        raise AttributeError(\"The `Fluke3000` FC is an autoranging only multimeter\")\n\n    # METHODS #\n\n    def connect(self):\n        \"\"\"\n        Connect to available modules and returns a dictionary\n        of the modules found and their port ID.\n        \"\"\"\n        self.scan()  # Look for connected devices\n        if not self.positions:\n            self.reset()  # Reset the PC3000 dongle\n            timeout = self.timeout  # Store default timeout\n            self.timeout = (\n                30 * u.second\n            )  # PC 3000 can take a while to bind with wireless devices\n            self.query_lines(\"rfdis\", 3)  # Discover available modules and bind them\n            self.timeout = timeout  # Restore default timeout\n            self.scan()  # Look for connected devices\n\n        if not self.positions:\n            raise ValueError(\"No `Fluke3000` modules available\")\n\n    def scan(self):\n        \"\"\"\n        Search for available modules and reformat. Returns a dictionary\n        of the modules found and their port ID.\n        \"\"\"\n        # Loop over possible channels, store device locations\n        positions = {}\n        for port_id in range(1, 7):\n            # Check if a device is connected to port port_id\n            output = self.query(f\"rfebd 0{port_id} 0\")\n            if \"RFEBD\" not in output:\n                continue\n\n            # If it is, identify the device\n            self.read()\n            output = self.query_lines(f\"rfgus 0{port_id}\", 2)[-1]\n            module_id = int(output.split(\"PH=\")[-1])\n            if module_id == self.Module.m3000.value:\n                positions[self.Module.m3000] = port_id\n            elif module_id == self.Module.t3000.value:\n                positions[self.Module.t3000] = port_id\n            else:\n                raise NotImplementedError(f\"Module ID {module_id} not implemented\")\n\n        self.positions = positions\n\n    def reset(self):\n        \"\"\"\n        Resets the device and unbinds all modules.\n        \"\"\"\n        self.query_lines(\"ri\", 3)  # Resets the device\n        self.query_lines(\"rfsm 1\", 2)  # Turns comms on\n\n    def read_lines(self, nlines=1):\n        \"\"\"\n        Function that keeps reading until reaches a termination\n        character a set amount of times. This is implemented\n        to handle the mutiline output of the PC3000.\n\n        :param nlines: Number of termination characters to reach\n\n        :type nlines: 'int'\n\n        :return: Array of lines read out\n        :rtype: Array of `str`\n\n        \"\"\"\n        return [self.read() for _ in range(nlines)]\n\n    def query_lines(self, cmd, nlines=1):\n        \"\"\"\n        Function used to send a query to the instrument while allowing\n        for the multiline output of the PC3000.\n\n        :param cmd: Command that will be sent to the instrument\n        :param nlines: Number of termination characters to reach\n\n        :type cmd: 'str'\n        :type nlines: 'int'\n\n        :return: The multiline result from the query\n        :rtype: Array of `str`\n\n        \"\"\"\n        self.sendcmd(cmd)\n        return self.read_lines(nlines)\n\n    def flush(self):\n        \"\"\"\n        Flushes the serial input cache.\n\n        This device outputs a terminator after each output line.\n        The serial input cache is flushed by repeatedly reading\n        until a terminator is not found.\n        \"\"\"\n        timeout = self.timeout\n        self.timeout = 0.1 * u.second\n        init_time = time.time()\n        while time.time() - init_time < 1.0:\n            try:\n                self.read()\n            except OSError:\n                break\n        self.timeout = timeout\n\n    def measure(self, mode):\n        \"\"\"Instruct the Fluke3000 to perform a one time measurement.\n\n        :param mode: Desired measurement mode.\n\n        :type mode: `Fluke3000.Mode`\n\n        :return: A measurement from the multimeter.\n        :rtype: `~pint.Quantity`\n\n        \"\"\"\n        # Check that the mode is supported\n        if not isinstance(mode, self.Mode):\n            raise ValueError(f\"Mode {mode} is not supported\")\n\n        # Check that the module associated with this mode is available\n        module = self._get_module(mode)\n        if module not in self.positions:\n            raise ValueError(f\"Device necessary to measure {mode} is not available\")\n\n        # Query the module\n        value = \"\"\n        port_id = self.positions[module]\n        init_time = time.time()\n        while time.time() - init_time < 3.0:\n            # Read out\n            if mode == self.Mode.temperature:\n                # The temperature module supports single readout\n                value = self.query_lines(f\"rfemd 0{port_id} 0\", 2)[-1]\n            else:\n                # The multimeter does not support single readout,\n                # have to open continuous readout, read, then close it\n                value = self.query_lines(f\"rfemd 0{port_id} 1\", 2)[-1]\n                self.query(f\"rfemd 0{port_id} 2\")\n\n            # Check that value is consistent with the request, break\n            if \"PH\" in value:\n                data = value.split(\"PH=\")[-1]\n                if self._parse_mode(data) != mode.value:\n                    if self.Module.m3000 in self.positions.keys():\n                        self.query(f\"rfemd 0{self.positions[self.Module.m3000]} 2\")\n                    self.flush()\n                else:\n                    break\n\n        # Parse the output\n        value = self._parse(value, mode)\n\n        # Return with the appropriate units\n        units = UNITS[mode]\n        return u.Quantity(value, units)\n\n    def _get_module(self, mode):\n        \"\"\"Gets the module associated with this measurement mode.\n\n        :param mode: Desired measurement mode.\n\n        :type mode: `Fluke3000.Mode`\n\n        :return: A Fluke3000 module.\n        :rtype: `Fluke3000.Module`\n\n        \"\"\"\n        if mode == self.Mode.temperature:\n            return self.Module.t3000\n\n        return self.Module.m3000\n\n    def _parse(self, result, mode):\n        \"\"\"Parses the module output.\n\n        :param result: Output of the query.\n        :param mode: Desired measurement mode.\n\n        :type result: `string`\n        :type mode: `Fluke3000.Mode`\n\n        :return: A measurement from the multimeter.\n        :rtype: `Quantity`\n\n        \"\"\"\n        # Check that a value is contained\n        if \"PH\" not in result:\n            raise ValueError(\n                \"Cannot parse a string that does not contain a return value\"\n            )\n\n        # Isolate the data string from the output\n        data = result.split(\"PH=\")[-1]\n\n        # Check that the multimeter is in the right mode (fifth byte)\n        if self._parse_mode(data) != mode.value:\n            error = (\n                f\"Mode {mode.name} was requested but the Fluke 3000FC Multimeter is in \"\n                f\"mode {self.Mode(data[8:10]).name} instead. Could not read the requested quantity.\"\n            )\n            raise ValueError(error)\n\n        # Extract the value from the first two bytes\n        value = self._parse_value(data)\n\n        # Extract the prefactor from the fourth byte\n        scale = self._parse_factor(data)\n\n        # Combine and return\n        return scale * value\n\n    @staticmethod\n    def _parse_mode(data):\n        \"\"\"Parses the measurement mode.\n\n        :param data: Measurement output.\n\n        :type data: `str`\n\n        :return: A Mode string.\n        :rtype: `str`\n\n        \"\"\"\n        # The fixth dual hex byte encodes the measurement mode\n        return data[8:10]\n\n    @staticmethod\n    def _parse_value(data):\n        \"\"\"Parses the measurement value.\n\n        :param data: Measurement output.\n\n        :type data: `str`\n\n        :return: A value.\n        :rtype: `float`\n\n        \"\"\"\n        # The second dual hex byte is the most significant byte\n        return int(data[2:4] + data[:2], 16)\n\n    @staticmethod\n    def _parse_factor(data):\n        \"\"\"Parses the measurement prefactor.\n\n        :param data: Measurement output.\n\n        :type data: `str`\n\n        :return: A prefactor.\n        :rtype: `float`\n\n        \"\"\"\n        # Convert the fourth dual hex byte to an 8 bits string\n        byte = format(int(data[6:8], 16), \"08b\")\n\n        # The first bit encodes the sign (0 positive, 1 negative)\n        sign = 1 if byte[0] == \"0\" else -1\n\n        # The second to fourth bits encode the metric prefix\n        code = int(byte[1:4], 2)\n        if code not in PREFIXES:\n            raise ValueError(f\"Metric prefix not recognized: {code}\")\n        prefix = PREFIXES[code]\n\n        # The sixth and seventh bit encode the decimal place\n        scale = 10 ** (-int(byte[5:7], 2))\n\n        # Return the combination\n        return sign * prefix * scale\n\n\n# UNITS #######################################################################\n\nUNITS = {\n    None: 1,\n    Fluke3000.Mode.voltage_ac: u.volt,\n    Fluke3000.Mode.voltage_dc: u.volt,\n    Fluke3000.Mode.current_ac: u.amp,\n    Fluke3000.Mode.current_dc: u.amp,\n    Fluke3000.Mode.frequency: u.hertz,\n    Fluke3000.Mode.temperature: u.degC,\n    Fluke3000.Mode.resistance: u.ohm,\n    Fluke3000.Mode.capacitance: u.farad,\n}\n\n# METRIC PREFIXES #############################################################\n\nPREFIXES = {\n    0: 1e0,  # None\n    2: 1e6,  # Mega\n    3: 1e3,  # Kilo\n    4: 1e-3,  # milli\n    5: 1e-6,  # micro\n    6: 1e-9,  # nano\n}\n"
  },
  {
    "path": "src/instruments/generic_scpi/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing generic SCPI instruments\n\"\"\"\n\nfrom .scpi_instrument import SCPIInstrument\nfrom .scpi_multimeter import SCPIMultimeter\nfrom .scpi_function_generator import SCPIFunctionGenerator\n"
  },
  {
    "path": "src/instruments/generic_scpi/scpi_function_generator.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for SCPI compliant function generators\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nfrom instruments.units import ureg as u\n\nfrom instruments.abstract_instruments import FunctionGenerator\nfrom instruments.generic_scpi import SCPIInstrument\nfrom instruments.util_fns import enum_property, unitful_property\n\n# CLASSES #####################################################################\n\n\nclass SCPIFunctionGenerator(FunctionGenerator, SCPIInstrument):\n    \"\"\"\n    This class is used for communicating with generic SCPI-compliant\n    function generators.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> import instruments.units as u\n    >>> inst = ik.generic_scpi.SCPIFunctionGenerator.open_tcpip(\"192.168.1.1\")\n    >>> inst.frequency = 1 * u.kHz\n    \"\"\"\n\n    # CONSTANTS #\n\n    _UNIT_MNEMONICS = {\n        FunctionGenerator.VoltageMode.peak_to_peak: \"VPP\",\n        FunctionGenerator.VoltageMode.rms: \"VRMS\",\n        FunctionGenerator.VoltageMode.dBm: \"DBM\",\n    }\n\n    _MNEMONIC_UNITS = {mnem: unit for unit, mnem in _UNIT_MNEMONICS.items()}\n\n    # FunctionGenerator CONTRACT #\n\n    def _get_amplitude_(self):\n        \"\"\"\n        Gets the amplitude for a generic SCPI function generator\n\n        :type: `tuple` containing `float` for value, and\n            `FunctionGenerator.VoltageMode` for the type of measurement\n            (eg VPP, VRMS, DBM).\n        \"\"\"\n        units = self.query(\"VOLT:UNIT?\").strip()\n\n        return (float(self.query(\"VOLT?\").strip()), self._MNEMONIC_UNITS[units])\n\n    def _set_amplitude_(self, magnitude, units):\n        \"\"\"\n        Sets the amplitude for a generic SCPI function generator\n\n        :param magnitude: Desired amplitude magnitude\n        :type magnitude: `float`\n        :param units: The type of voltage measurements units\n        :type units: `FunctionGenerator.VoltageMode`\n        \"\"\"\n        self.sendcmd(f\"VOLT:UNIT {self._UNIT_MNEMONICS[units]}\")\n        self.sendcmd(f\"VOLT {magnitude}\")\n\n    # PROPERTIES #\n\n    frequency = unitful_property(\n        command=\"FREQ\",\n        units=u.Hz,\n        doc=\"\"\"\n        Gets/sets the output frequency.\n\n        :units: As specified, or assumed to be :math:`\\\\text{Hz}` otherwise.\n        :type: `float` or `~pint.Quantity`\n        \"\"\",\n    )\n\n    function = enum_property(\n        command=\"FUNC\",\n        enum=FunctionGenerator.Function,\n        doc=\"\"\"\n        Gets/sets the output function of the function generator\n\n        :type: `SCPIFunctionGenerator.Function`\n        \"\"\",\n    )\n\n    offset = unitful_property(\n        command=\"VOLT:OFFS\",\n        units=u.volt,\n        doc=\"\"\"\n        Gets/sets the offset voltage of the function generator.\n\n        Set value should be within correct bounds of instrument.\n\n        :units: As specified  (if a `~pint.Quantity`) or assumed\n            to be of units volts.\n        :type: `~pint.Quantity` with units volts.\n        \"\"\",\n    )\n\n    @property\n    def phase(self):\n        raise NotImplementedError\n\n    @phase.setter\n    def phase(self, newval):\n        raise NotImplementedError\n"
  },
  {
    "path": "src/instruments/generic_scpi/scpi_instrument.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for SCPI compliant instruments\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom enum import IntEnum\n\nfrom instruments.abstract_instruments import Instrument\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import assume_units\n\n# CLASSES #####################################################################\n\n\nclass SCPIInstrument(Instrument):\n    r\"\"\"\n    Base class for all SCPI-compliant instruments. Inherits from\n    from `~instruments.Instrument`.\n\n    This class does not implement any instrument-specific communication\n    commands. What it does add is several of the generic SCPI star commands.\n    This includes commands such as ``*IDN?``, ``*OPC?``, and ``*RST``.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> inst = ik.generic_scpi.SCPIInstrument.open_tcpip('192.168.0.2', 8888)\n    >>> print(inst.name)\n    \"\"\"\n\n    # PROPERTIES #\n\n    @property\n    def name(self):\n        \"\"\"\n        The name of the connected instrument, as reported by the\n        standard SCPI command ``*IDN?``.\n\n        :rtype: `str`\n        \"\"\"\n        return self.query(\"*IDN?\")\n\n    @property\n    def scpi_version(self):\n        \"\"\"\n        Returns the version of the SCPI protocol supported by this instrument,\n        as specified by the ``SYST:VERS?`` command described in section 21.21\n        of the SCPI 1999 standard.\n        \"\"\"\n        return self.query(\"SYST:VERS?\")\n\n    @property\n    def op_complete(self):\n        \"\"\"\n        Check if all operations sent to the instrument have been completed.\n\n        :rtype: `bool`\n        \"\"\"\n        result = self.query(\"*OPC?\")\n        return bool(int(result))\n\n    @property\n    def power_on_status(self):\n        \"\"\"\n        Gets/sets the power on status for the instrument.\n\n        :type: `bool`\n        \"\"\"\n        result = self.query(\"*PSC?\")\n        return bool(int(result))\n\n    @power_on_status.setter\n    def power_on_status(self, newval):\n        on = [\"on\", \"1\", 1, True]\n        off = [\"off\", \"0\", 0, False]\n        if isinstance(newval, str):\n            newval = newval.lower()\n        if newval in on:\n            self.sendcmd(\"*PSC 1\")\n        elif newval in off:\n            self.sendcmd(\"*PSC 0\")\n        else:\n            raise ValueError\n\n    @property\n    def self_test_ok(self):\n        \"\"\"\n        Gets the results of the instrument's self test. This lets you check\n        if the self test was sucessful or not.\n\n        :rtype: `bool`\n        \"\"\"\n        result = self.query(\"*TST?\")\n        try:\n            result = int(result)\n            return result == 0\n        except ValueError:\n            return False\n\n    # BASIC SCPI COMMANDS ##\n\n    def reset(self):\n        \"\"\"\n        Reset instrument. On many instruments this is a factory reset and will\n        revert all settings to default.\n        \"\"\"\n        self.sendcmd(\"*RST\")\n\n    def clear(self):\n        \"\"\"\n        Clear instrument. Consult manual for specifics related to that\n        instrument.\n        \"\"\"\n        self.sendcmd(\"*CLS\")\n\n    def trigger(self):\n        \"\"\"\n        Send a software trigger event to the instrument. On most instruments\n        this will cause some sort of hardware event to start. For example, a\n        multimeter might take a measurement.\n\n        This software trigger usually performs the same action as a hardware\n        trigger to your instrument.\n        \"\"\"\n        self.sendcmd(\"*TRG\")\n\n    def wait_to_continue(self):\n        \"\"\"\n        Instruct the instrument to wait until it has completed all received\n        commands before continuing.\n        \"\"\"\n        self.sendcmd(\"*WAI\")\n\n    # SYSTEM COMMANDS ##\n\n    @property\n    def line_frequency(self):\n        \"\"\"\n        Gets/sets the power line frequency setting for the instrument.\n\n        :return: The power line frequency\n        :units: Hertz\n        :type: `~pint.Quantity`\n        \"\"\"\n        return u.Quantity(float(self.query(\"SYST:LFR?\")), \"Hz\")\n\n    @line_frequency.setter\n    def line_frequency(self, newval):\n        self.sendcmd(\n            \"SYST:LFR {}\".format(assume_units(newval, \"Hz\").to(\"Hz\").magnitude)\n        )\n\n    # ERROR QUEUE HANDLING ##\n    # NOTE: This functionality is still quite incomplete, and could be fleshed\n    #       out significantly still. One good thing would be to add handling\n    #       for SCPI-defined error codes.\n    #\n    #       Another good use of this functionality would be to allow users to\n    #       automatically check errors after each command or query.\n    class ErrorCodes(IntEnum):\n        \"\"\"\n        Enumeration describing error codes as defined by SCPI 1999.0.\n        Error codes that are equal to 0 mod 100 are defined to be *generic*.\n        \"\"\"\n\n        # NOTE: this class may be overriden by subclasses, since the only access\n        #       to this enumeration from within SCPIInstrument is by \"self,\"\n        #       not by explicit name. Thus, if an instrument supports additional\n        #       error codes from the SCPI base, they can be added in a natural\n        #       way.\n        no_error = 0\n\n        # -100 BLOCK: COMMAND ERRORS ##\n        command_error = -100\n        invalid_character = -101\n        syntax_error = -102\n        invalid_separator = -103\n        data_type_error = -104\n        get_not_allowed = -105\n        # -106 and -107 not specified.\n        parameter_not_allowed = -108\n        missing_parameter = -109\n        command_header_error = -110\n        header_separator_error = -111\n        program_mnemonic_too_long = -112\n        undefined_header = -113\n        header_suffix_out_of_range = -114\n        unexpected_number_of_parameters = -115\n        numeric_data_error = -120\n        invalid_character_in_number = -121\n        exponent_too_large = -123\n        too_many_digits = -124\n        numeric_data_not_allowed = -128\n        suffix_error = -130\n        invalid_suffix = -131\n        suffix_too_long = -134\n        suffix_not_allowed = -138\n        character_data_error = -140\n        invalid_character_data = -141\n        character_data_too_long = -144\n        character_data_not_allowed = -148\n        string_data_error = -150\n        invalid_string_data = -151\n        string_data_not_allowed = -158\n        block_data_error = -160\n        invalid_block_data = -161\n        block_data_not_allowed = -168\n        expression_error = -170\n        invalid_expression = -171\n        expression_not_allowed = -178\n        macro_error = -180\n        invalid_outside_macro_definition = -181\n        invalid_inside_macro_definition = -183\n        macro_parameter_error = -184\n\n        # pylint: disable=fixme\n        # TODO: copy over other blocks.\n        # -200 BLOCK: EXECUTION ERRORS ##\n        # -300 BLOCK: DEVICE-SPECIFIC ERRORS ##\n        # Note that device-specific errors also include all positive numbers.\n        # -400 BLOCK: QUERY ERRORS ##\n\n        # OTHER ERRORS ##\n\n        #: Raised when the instrument detects that it has been turned from\n        #: off to on.\n        power_on = -500  # Yes, SCPI 1999 defines the instrument turning on as\n        # an error. Yes, this makes my brain hurt.\n        user_request_event = -600\n        request_control_event = -700\n        operation_complete = -800\n\n    def check_error_queue(self):\n        \"\"\"\n        Checks and clears the error queue for this device, returning a list of\n        :class:`SCPIInstrument.ErrorCodes` or `int` elements for each error\n        reported by the connected instrument.\n        \"\"\"\n        # pylint: disable=fixme\n        # TODO: use SYST:ERR:ALL instead of SYST:ERR:CODE:ALL to get\n        #       messages as well. Should be just a bit more parsing, but the\n        #       SCPI standard isn't clear on how the pairs are represented,\n        #       so it'd be helpful to have an example first.\n        err_list = map(int, self.query(\"SYST:ERR:CODE:ALL?\").split(\",\"))\n        return [\n            self.ErrorCodes[err] if isinstance(err, self.ErrorCodes) else err\n            for err in err_list\n            if err != self.ErrorCodes.no_error\n        ]\n\n    # DISPLAY COMMANDS ##\n\n    @property\n    def display_brightness(self):\n        \"\"\"\n        Brightness of the display on the connected instrument, represented as\n        a float ranging from 0 (dark) to 1 (full brightness).\n\n        :type: `float`\n        \"\"\"\n        return float(self.query(\"DISP:BRIG?\"))\n\n    @display_brightness.setter\n    def display_brightness(self, newval):\n        if newval < 0 or newval > 1:\n            raise ValueError(\"Display brightness must be a number between 0\" \" and 1.\")\n        self.sendcmd(f\"DISP:BRIG {newval}\")\n\n    @property\n    def display_contrast(self):\n        \"\"\"\n        Contrast of the display on the connected instrument, represented as\n        a float ranging from 0 (no contrast) to 1 (full contrast).\n\n        :type: `float`\n        \"\"\"\n        return float(self.query(\"DISP:CONT?\"))\n\n    @display_contrast.setter\n    def display_contrast(self, newval):\n        if newval < 0 or newval > 1:\n            raise ValueError(\"Display contrast must be a number between 0\" \" and 1.\")\n        self.sendcmd(f\"DISP:CONT {newval}\")\n"
  },
  {
    "path": "src/instruments/generic_scpi/scpi_multimeter.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for SCPI compliant multimeters\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nfrom enum import Enum\n\nfrom instruments.units import ureg as u\n\nfrom instruments.abstract_instruments import Multimeter\nfrom instruments.generic_scpi import SCPIInstrument\nfrom instruments.util_fns import assume_units, enum_property, unitful_property\n\n# CONSTANTS ###################################################################\n\nVALID_FRES_NAMES = [\"4res\", \"4 res\", \"four res\", \"f res\"]\n\nUNITS_CAPACITANCE = [\"cap\"]\nUNITS_VOLTAGE = [\"volt:dc\", \"volt:ac\", \"diod\"]\nUNITS_CURRENT = [\"curr:dc\", \"curr:ac\"]\nUNITS_RESISTANCE = [\"res\", \"fres\"] + VALID_FRES_NAMES\nUNITS_FREQUENCY = [\"freq\"]\nUNITS_TIME = [\"per\"]\nUNITS_TEMPERATURE = [\"temp\"]\n\n# CLASSES #####################################################################\n\n\nclass SCPIMultimeter(SCPIInstrument, Multimeter):\n    \"\"\"\n    This class is used for communicating with generic SCPI-compliant\n    multimeters.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> inst = ik.generic_scpi.SCPIMultimeter.open_tcpip(\"192.168.1.1\")\n    >>> print(inst.measure(inst.Mode.resistance))\n    \"\"\"\n\n    # ENUMS ##\n\n    class Mode(Enum):\n        \"\"\"\n        Enum of valid measurement modes for (most) SCPI compliant multimeters\n        \"\"\"\n\n        capacitance = \"CAP\"\n        continuity = \"CONT\"\n        current_ac = \"CURR:AC\"\n        current_dc = \"CURR:DC\"\n        diode = \"DIOD\"\n        frequency = \"FREQ\"\n        fourpt_resistance = \"FRES\"\n        period = \"PER\"\n        resistance = \"RES\"\n        temperature = \"TEMP\"\n        voltage_ac = \"VOLT:AC\"\n        voltage_dc = \"VOLT:DC\"\n\n    class TriggerMode(Enum):\n        \"\"\"\n        Valid trigger sources for most SCPI Multimeters.\n\n        \"Immediate\": This is a continuous trigger. This means the trigger\n        signal is always present.\n\n        \"External\": External TTL pulse on the back of the instrument. It\n        is active low.\n\n        \"Bus\": Causes the instrument to trigger when a ``*TRG`` command is\n        sent by software. This means calling the trigger() function.\n        \"\"\"\n\n        immediate = \"IMM\"\n        external = \"EXT\"\n        bus = \"BUS\"\n\n    class InputRange(Enum):\n        \"\"\"\n        Valid device range parameters outside of directly specifying the range.\n        \"\"\"\n\n        minimum = \"MIN\"\n        maximum = \"MAX\"\n        default = \"DEF\"\n        automatic = \"AUTO\"\n\n    class Resolution(Enum):\n        \"\"\"\n        Valid measurement resolution parameters outside of directly the\n        resolution.\n        \"\"\"\n\n        minimum = \"MIN\"\n        maximum = \"MAX\"\n        default = \"DEF\"\n\n    class TriggerCount(Enum):\n        \"\"\"\n        Valid trigger count parameters outside of directly the value.\n        \"\"\"\n\n        minimum = \"MIN\"\n        maximum = \"MAX\"\n        default = \"DEF\"\n        infinity = \"INF\"\n\n    class SampleCount(Enum):\n        \"\"\"\n        Valid sample count parameters outside of directly the value.\n        \"\"\"\n\n        minimum = \"MIN\"\n        maximum = \"MAX\"\n        default = \"DEF\"\n\n    class SampleSource(Enum):\n        \"\"\"\n        Valid sample source parameters.\n\n        #. \"immediate\": The trigger delay time is inserted between successive\n            samples. After the first measurement is completed, the instrument\n            waits the time specified by the trigger delay and then performs the\n            next sample.\n        #. \"timer\": Successive samples start one sample interval after the\n            START of the previous sample.\n        \"\"\"\n\n        immediate = \"IMM\"\n        timer = \"TIM\"\n\n    # PROPERTIES ##\n\n    # pylint: disable=unnecessary-lambda,undefined-variable\n    mode = enum_property(\n        command=\"CONF\",\n        enum=Mode,\n        doc=\"\"\"\n        Gets/sets the current measurement mode for the multimeter.\n\n        Example usage:\n\n        >>> dmm.mode = dmm.Mode.voltage_dc\n\n        :type: `~SCPIMultimeter.Mode`\n        \"\"\",\n        input_decoration=lambda x: SCPIMultimeter._mode_parse(x),\n        set_fmt=\"{}:{}\",\n    )\n\n    trigger_mode = enum_property(\n        command=\"TRIG:SOUR\",\n        enum=TriggerMode,\n        doc=\"\"\"\n            Gets/sets the SCPI Multimeter trigger mode.\n\n            Example usage:\n\n            >>> dmm.trigger_mode = dmm.TriggerMode.external\n\n            :type: `~SCPIMultimeter.TriggerMode`\n        \"\"\",\n    )\n\n    @property\n    def input_range(self):\n        \"\"\"\n        Gets/sets the device input range for the device range for the currently\n        set multimeter mode.\n\n        Example usages:\n\n        >>> dmm.input_range = dmm.InputRange.automatic\n        >>> dmm.input_range = 1 * u.millivolt\n\n        :units: As appropriate for the current mode setting.\n        :type: `~pint.Quantity`, or `~SCPIMultimeter.InputRange`\n        \"\"\"\n        value = self.query(\"CONF?\")\n        mode = self.Mode(self._mode_parse(value))\n        value = value.split(\" \")[1].split(\",\")[0]  # Extract device range\n        try:\n            return float(value) * UNITS[mode]\n        except ValueError:\n            return self.InputRange(value.strip())\n\n    @input_range.setter\n    def input_range(self, newval):\n        current = self.query(\"CONF?\")\n        mode = self.Mode(self._mode_parse(current))\n        units = UNITS[mode]\n        if isinstance(newval, self.InputRange):\n            newval = newval.value\n        else:\n            newval = assume_units(newval, units).to(units).magnitude\n        self.sendcmd(f\"CONF:{mode.value} {newval}\")\n\n    @property\n    def resolution(self):\n        \"\"\"\n        Gets/sets the measurement resolution for the multimeter. When\n        specified as a float it is assumed that the user is providing an\n        appropriate value.\n\n        Example usage:\n\n        >>> dmm.resolution = 3e-06\n        >>> dmm.resolution = dmm.Resolution.maximum\n\n        :type: `int`, `float` or `~SCPIMultimeter.Resolution`\n        \"\"\"\n        value = self.query(\"CONF?\")\n        value = value.split(\" \")[1].split(\",\")[1]  # Extract resolution\n        try:\n            return float(value)\n        except ValueError:\n            return self.Resolution(value.strip())\n\n    @resolution.setter\n    def resolution(self, newval):\n        current = self.query(\"CONF?\")\n        mode = self.Mode(self._mode_parse(current))\n        input_range = current.split(\" \")[1].split(\",\")[0]\n        if isinstance(newval, self.Resolution):\n            newval = newval.value\n        elif not isinstance(newval, (float, int)):\n            raise TypeError(\n                \"Resolution must be specified as an int, float, \"\n                \"or SCPIMultimeter.Resolution value.\"\n            )\n        self.sendcmd(f\"CONF:{mode.value} {input_range},{newval}\")\n\n    @property\n    def trigger_count(self):\n        \"\"\"\n        Gets/sets the number of triggers that the multimeter will accept before\n        returning to an \"idle\" trigger state.\n\n        Note that if the sample_count propery has been changed, the number\n        of readings taken total will be a multiplication of sample count and\n        trigger count (see property `SCPIMulimeter.sample_count`).\n\n        If specified as a `~SCPIMultimeter.TriggerCount` value, the following\n        options apply:\n\n        #. \"minimum\": 1 trigger\n        #. \"maximum\": Maximum value as per instrument manual\n        #. \"default\": Instrument default as per instrument manual\n        #. \"infinity\": Continuous. Typically when the buffer is filled in this\n            case, the older data points are overwritten.\n\n        Note that when using triggered measurements, it is recommended that you\n        disable autorange by either explicitly disabling it or specifying your\n        desired range.\n\n        :type: `int` or `~SCPIMultimeter.TriggerCount`\n        \"\"\"\n        value = self.query(\"TRIG:COUN?\")\n        try:\n            return int(value)\n        except ValueError:\n            return self.TriggerCount(value.strip())\n\n    @trigger_count.setter\n    def trigger_count(self, newval):\n        if isinstance(newval, self.TriggerCount):\n            newval = newval.value\n        elif not isinstance(newval, int):\n            raise TypeError(\n                \"Trigger count must be specified as an int \"\n                \"or SCPIMultimeter.TriggerCount value.\"\n            )\n        self.sendcmd(f\"TRIG:COUN {newval}\")\n\n    @property\n    def sample_count(self):\n        \"\"\"\n        Gets/sets the number of readings (samples) that the multimeter will\n        take per trigger event.\n\n        The time between each measurement is defined with the sample_timer\n        property.\n\n        Note that if the trigger_count propery has been changed, the number\n        of readings taken total will be a multiplication of sample count and\n        trigger count (see property `SCPIMulimeter.trigger_count`).\n\n        If specified as a `~SCPIMultimeter.SampleCount` value, the following\n        options apply:\n\n        #. \"minimum\": 1 sample per trigger\n        #. \"maximum\": Maximum value as per instrument manual\n        #. \"default\": Instrument default as per instrument manual\n\n        Note that when using triggered measurements, it is recommended that you\n        disable autorange by either explicitly disabling it or specifying your\n        desired range.\n\n        :type: `int` or `~SCPIMultimeter.SampleCount`\n        \"\"\"\n        value = self.query(\"SAMP:COUN?\")\n        try:\n            return int(value)\n        except ValueError:\n            return self.SampleCount(value.strip())\n\n    @sample_count.setter\n    def sample_count(self, newval):\n        if isinstance(newval, self.SampleCount):\n            newval = newval.value\n        elif not isinstance(newval, int):\n            raise TypeError(\n                \"Sample count must be specified as an int \"\n                \"or SCPIMultimeter.SampleCount value.\"\n            )\n        self.sendcmd(f\"SAMP:COUN {newval}\")\n\n    trigger_delay = unitful_property(\n        command=\"TRIG:DEL\",\n        units=u.second,\n        doc=\"\"\"\n        Gets/sets the time delay which the multimeter will use following\n        receiving a trigger event before starting the measurement.\n\n        :units: As specified, or assumed to be of units seconds otherwise.\n        :type: `~pint.Quantity`\n        \"\"\",\n    )\n\n    sample_source = enum_property(\n        command=\"SAMP:SOUR\",\n        enum=SampleSource,\n        doc=\"\"\"\n        Gets/sets the multimeter sample source. This determines whether the\n        trigger delay or the sample timer is used to dtermine sample timing when\n        the sample count is greater than 1.\n\n        In both cases, the first sample is taken one trigger delay time period\n        after the trigger event. After that, it depends on which mode is used.\n\n        :type: `SCPIMultimeter.SampleSource`\n        \"\"\",\n    )\n\n    sample_timer = unitful_property(\n        command=\"SAMP:TIM\",\n        units=u.second,\n        doc=\"\"\"\n        Gets/sets the sample interval when the sample counter is greater than\n        one and when the sample source is set to timer (see\n        `SCPIMultimeter.sample_source`).\n\n        This command does not effect the delay between the trigger occuring and\n        the start of the first sample. This trigger delay is set with the\n        `~SCPIMultimeter.trigger_delay` property.\n\n        :units: As specified, or assumed to be of units seconds otherwise.\n        :type: `~pint.Quantity`\n        \"\"\",\n    )\n\n    @property\n    def relative(self):\n        raise NotImplementedError\n\n    @relative.setter\n    def relative(self, newval):\n        raise NotImplementedError\n\n    # METHODS ##\n\n    def measure(self, mode=None):\n        \"\"\"\n        Instruct the multimeter to perform a one time measurement. The\n        instrument will use default parameters for the requested measurement.\n        The measurement will immediately take place, and the results are\n        directly sent to the instrument's output buffer.\n\n        Method returns a Python quantity consisting of a numpy array with the\n        instrument value and appropriate units. If no appropriate units exist,\n        (for example, continuity), then return type is `float`.\n\n        :param mode: Desired measurement mode. If set to `None`, will default\n            to the current mode.\n        :type mode: `~SCPIMultimeter.Mode`\n        \"\"\"\n        if mode is None:\n            mode = self.mode\n        if not isinstance(mode, SCPIMultimeter.Mode):\n            raise TypeError(\n                \"Mode must be specified as a SCPIMultimeter.Mode \"\n                \"value, got {} instead.\".format(type(mode))\n            )\n        # pylint: disable=no-member\n        value = float(self.query(f\"MEAS:{mode.value}?\"))\n        return value * UNITS[mode]\n\n    # INTERNAL FUNCTIONS ##\n\n    @staticmethod\n    def _mode_parse(val):\n        \"\"\"\n        When given a string of the form\n\n        \"VOLT +1.00000000E+01,+3.00000000E-06\"\n\n        this function will return just the first component representing the mode\n        the multimeter is currently in.\n\n        :param str val: Input string to be parsed.\n\n        :rtype: `str`\n        \"\"\"\n        val = val.split(\" \")[0]\n        if val == \"VOLT\":\n            val = \"VOLT:DC\"\n        return val\n\n\n# UNITS #######################################################################\n\nUNITS = {\n    SCPIMultimeter.Mode.capacitance: u.farad,\n    SCPIMultimeter.Mode.voltage_dc: u.volt,\n    SCPIMultimeter.Mode.voltage_ac: u.volt,\n    SCPIMultimeter.Mode.diode: u.volt,\n    SCPIMultimeter.Mode.current_ac: u.amp,\n    SCPIMultimeter.Mode.current_dc: u.amp,\n    SCPIMultimeter.Mode.resistance: u.ohm,\n    SCPIMultimeter.Mode.fourpt_resistance: u.ohm,\n    SCPIMultimeter.Mode.frequency: u.hertz,\n    SCPIMultimeter.Mode.period: u.second,\n    SCPIMultimeter.Mode.temperature: u.kelvin,\n    SCPIMultimeter.Mode.continuity: 1,\n}\n"
  },
  {
    "path": "src/instruments/gentec_eo/__init__.py",
    "content": "\"\"\"Module containing Gentec-eo instruments.\"\"\"\n\nfrom .blu import Blu\n"
  },
  {
    "path": "src/instruments/gentec_eo/blu.py",
    "content": "\"\"\"Support for Gentec-EO Blu devices.\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom enum import Enum\nfrom time import sleep\n\nfrom instruments.abstract_instruments import Instrument\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import assume_units\n\n# CLASSES #####################################################################\n\n\nclass Blu(Instrument):\n    \"\"\"Communicate with Gentec-eo BLU power / energy meter interfaces.\n\n    These instruments communicate via USB or via bluetooth. The\n    bluetooth sender / receiver that is provided with the instrument is\n    simply emulating a COM port. This routine cannot pair the device\n    with bluetooth, but once it is paired, it can communicate with the\n    port. Alternatively, you can plug the device into the computer using\n    a USB cable.\n\n    .. warning:: If commands are issued too fast, the device will not\n        answer. Experimentally, a 1 ms delay should be enough to get the\n        device into answering mode. Keep this in mind when issuing many\n        commands at once. No wait time included in this class.\n\n    .. note:: The instrument also has a possiblity to read a continuous\n        data stream. This is currently not implemented here since it\n        would have to be threaded out.\n\n    Example:\n        >>> import instruments as ik\n        >>> inst = ik.gentec_eo.Blu.open_serial('/dev/ttyACM0')\n        >>> inst.current_value\n        3.004 W\n    \"\"\"\n\n    def __init__(self, filelike):\n        super().__init__(filelike)\n\n        # use a terminator for blu, even though none required\n        self.terminator = \"\\r\\n\"\n\n        # define the power mode\n        self._power_mode = None\n\n        # acknowledgement message\n        self._ack_message = \"ACK\"\n\n    def _ack_expected(self, msg=\"\"):\n        \"\"\"Set up acknowledgement checking.\"\"\"\n        return self._ack_message\n\n    # ENUMS #\n\n    class Scale(Enum):\n        \"\"\"Available scales for Blu devices.\n\n        The following list maps available scales of the Blu devices\n        to the respective indexes. All scales are either in watts or\n        joules, depending if power or energy mode is activated.\n        Furthermore, the maximum value that can be measured determines\n        the name of the scale to be set. Prefixes are given in the\n        `enum` class while the unit is omitted since it depends on the\n        mode the head is in.\n        \"\"\"\n\n        max1pico = \"00\"\n        max3pico = \"01\"\n        max10pico = \"02\"\n        max30pico = \"03\"\n        max100pico = \"04\"\n        max300pico = \"05\"\n        max1nano = \"06\"\n        max3nano = \"07\"\n        max10nano = \"08\"\n        max30nano = \"09\"\n        max100nano = \"10\"\n        max300nano = \"11\"\n        max1micro = \"12\"\n        max3micro = \"13\"\n        max10micro = \"14\"\n        max30micro = \"15\"\n        max100micro = \"16\"\n        max300micro = \"17\"\n        max1milli = \"18\"\n        max3milli = \"19\"\n        max10milli = \"20\"\n        max30milli = \"21\"\n        max100milli = \"22\"\n        max300milli = \"23\"\n        max1 = \"24\"\n        max3 = \"25\"\n        max10 = \"26\"\n        max30 = \"27\"\n        max100 = \"28\"\n        max300 = \"29\"\n        max1kilo = \"30\"\n        max3kilo = \"31\"\n        max10kilo = \"32\"\n        max30kilo = \"33\"\n        max100kilo = \"34\"\n        max300kilo = \"35\"\n        max1Mega = \"36\"\n        max3Mega = \"37\"\n        max10Mega = \"38\"\n        max30Mega = \"39\"\n        max100Mega = \"40\"\n        max300Mega = \"41\"\n\n    # PROPERTIES #\n\n    @property\n    def anticipation(self):\n        \"\"\"Get / Set anticipation.\n\n        This command is used to enable or disable the anticipation\n        processing when the device is reading from a wattmeter. The\n        anticipation is a software-based acceleration algorithm that\n        provides faster readings using the detector’s calibration.\n\n        :return: Is anticipation enabled or not.\n        :rtype: bool\n\n        Example:\n            >>> import instruments as ik\n            >>> inst = ik.gentec_eo.Blu.open_serial('/dev/ttyACM0')\n            >>> inst.anticipation\n            True\n            >>> inst.anticipation = False\n        \"\"\"\n        return self._value_query(\"*GAN\", tp=int) == 1\n\n    @anticipation.setter\n    def anticipation(self, newval):\n        sendval = 1 if newval else 0\n        self.sendcmd(f\"*ANT{sendval}\")\n\n    @property\n    def auto_scale(self):\n        \"\"\"Get / Set auto scale on the device.\n\n        :return: Status of auto scale enabled feature.\n        :rtype: bool\n\n        :raises ValueError: The command was not acknowledged by the\n            device.\n\n        Example:\n            >>> import instruments as ik\n            >>> inst = ik.gentec_eo.Blu.open_serial('/dev/ttyACM0')\n            >>> inst.auto_scale\n            True\n            >>> inst.auto_scale = False\n        \"\"\"\n        resp = self._value_query(\"*GAS\", tp=int)\n        return resp == 1\n\n    @auto_scale.setter\n    def auto_scale(self, newval):\n        sendval = 1 if newval else 0\n        self.sendcmd(f\"*SAS{sendval}\")\n\n    @property\n    def available_scales(self):\n        \"\"\"Get available scales from connected device.\n\n        :return: Scales currently available on device.\n        :rtype: :class:`Blu.Scale`\n\n        Example:\n            >>> import instruments as ik\n            >>> inst = ik.gentec_eo.Blu.open_serial('/dev/ttyACM0')\n            >>> inst.available_scales\n            [<Scale.max100milli: '22'>, <Scale.max300milli: '23'>,\n            <Scale.max1: '24'>, <Scale.max3: '25'>, <Scale.max10: '26'>,\n            <Scale.max30: '27'>, <Scale.max100: '28'>]\n        \"\"\"\n        # set no terminator and a 1 second timeout\n        _terminator = self.terminator\n        self.terminator = \"\"\n        _timeout = self.timeout\n        self.timeout = u.Quantity(1, u.s)\n\n        try:\n            # get the response\n            resp = self._no_ack_query(\"*DVS\").split(\"\\r\\n\")\n        finally:\n            # set back terminator and 3 second timeout\n            self.terminator = _terminator\n            self.timeout = _timeout\n\n        # prepare return\n        retlist = []  # init return list of enums\n        for line in resp:\n            if len(line) > 0:  # account for empty lines\n                index = line[line.find(\"[\") + 1 : line.find(\"]\")]\n                retlist.append(self.Scale(index))\n        return retlist\n\n    @property\n    def battery_state(self):\n        \"\"\"Get the charge state of the battery.\n\n        :return: Charge state of battery\n        :rtype: u.percent\n\n        Example:\n            >>> import instruments as ik\n            >>> inst = ik.gentec_eo.Blu.open_serial('/dev/ttyACM0')\n            >>> inst.battery_state\n            array(100.) * %\n        \"\"\"\n        resp = self._no_ack_query(\"*QSO\").rstrip()\n        resp = float(resp[resp.find(\"=\") + 1 : len(resp)])\n        return u.Quantity(resp, u.percent)\n\n    @property\n    def current_value(self):\n        \"\"\"Get the currently measured value (unitful).\n\n        :return: Currently measured value\n        :rtype: u.Quantity\n\n        Example:\n            >>> import instruments as ik\n            >>> inst = ik.gentec_eo.Blu.open_serial('/dev/ttyACM0')\n            >>> inst.current_value\n            3.004 W\n        \"\"\"\n        if self._power_mode is None:\n            _ = self.measure_mode  # determine the power mode\n            sleep(0.01)\n\n        unit = u.W if self._power_mode else u.J\n        return u.Quantity(float(self._no_ack_query(\"*CVU\")), unit)\n\n    @property\n    def head_type(self):\n        \"\"\"Get the head type information.\n\n        :return: Type of instrument head.\n        :rtype: str\n\n        Example:\n            >>> import instruments as ik\n            >>> inst = ik.gentec_eo.Blu.open_serial('/dev/ttyACM0')\n            >>> inst.head_type\n            'NIG : 104552, Wattmeter, V1.95'\n        \"\"\"\n        return self._no_ack_query(\"*GFW\")\n\n    @property\n    def measure_mode(self):\n        \"\"\"Get the current measurement mode.\n\n        Potential return values are 'power', which inidcates power mode\n        in W and 'sse', indicating single shot energy mode in J.\n\n        :return: 'power' if in power mode, 'sse' if in single shot\n            energy mode.\n        :rtype: str\n\n        Example:\n            >>> import instruments as ik\n            >>> inst = ik.gentec_eo.Blu.open_serial('/dev/ttyACM0')\n            >>> inst.measure_mode\n            'power'\n        \"\"\"\n        resp = self._value_query(\"*GMD\", tp=int)\n        if resp == 0:\n            self._power_mode = True\n            return \"power\"\n        else:\n            self._power_mode = False\n            return \"sse\"\n\n    @property\n    def new_value_ready(self):\n        \"\"\"Get status if a new value is ready.\n\n        This command is used to check whether a new value is available\n        from the device. Though optional, its use is recommended when\n        used with single pulse operation.\n\n        :return: Is a new value ready?\n        :rtype: bool\n\n        Example:\n            >>> import instruments as ik\n            >>> inst = ik.gentec_eo.Blu.open_serial('/dev/ttyACM0')\n            >>> inst.new_value_ready\n            False\n        \"\"\"\n        resp = self._no_ack_query(\"*NVU\")\n        return False if resp.find(\"Not\") > -1 else True\n\n    @property\n    def scale(self):\n        \"\"\"Get / Set measurement scale.\n\n        The measurement scales are chosen from the the `Scale` enum\n        class. Scales are either in watts or joules, depending on what\n        state the power meter is currently in.\n\n        .. note:: Setting a scale manually will automatically turn of\n            auto scale.\n\n        :return: Scale that is currently set.\n        :rtype: :class:`Blu.Scale`\n\n        :raises ValueError: The command was not acknowledged by the\n            device. A scale that is not available might have been\n            selected. Use `available_scales` to display scales that\n            are possible on your device.\n\n        Example:\n            >>> import instruments as ik\n            >>> inst = ik.gentec_eo.Blu.open_serial('/dev/ttyACM0')\n            >>> inst.scale = inst.Scale.max3\n            >>> inst.scale\n            <Scale.max3: '25'>\n        \"\"\"\n        return self.Scale(self._value_query(\"*GCR\"))\n\n    @scale.setter\n    def scale(self, newval):\n        self.sendcmd(f\"*SCS{newval.value}\")\n\n    @property\n    def single_shot_energy_mode(self):\n        \"\"\"Get / Set single shot energy mode.\n\n        :return: Is single shot energy mode turned on?\n        :rtype: bool\n\n        Example:\n            >>> import instruments as ik\n            >>> inst = ik.gentec_eo.Blu.open_serial('/dev/ttyACM0')\n            >>> inst.single_shot_energy_mode\n            False\n            >>> inst.single_shot_energy_mode = True\n        \"\"\"\n        val = self._value_query(\"*GSE\", tp=int) == 1\n        self._power_mode = False if val else True\n        return val\n\n    @single_shot_energy_mode.setter\n    def single_shot_energy_mode(self, newval):\n        sendval = 1 if newval else 0  # set send value\n        self._power_mode = False if newval else True  # set power mode\n        self.sendcmd(f\"*SSE{sendval}\")\n\n    @property\n    def trigger_level(self):\n        \"\"\"Get / Set trigger level when in energy mode.\n\n        The trigger level must be between 0.001 and 0.998.\n\n        :return: Trigger level (absolute) with respect to the currently\n            set scale\n        :rtype: float\n\n        :raise ValueError: Trigger level out of range.\n\n        Example:\n            >>> import instruments as ik\n            >>> inst = ik.gentec_eo.Blu.open_serial('/dev/ttyACM0')\n            >>> inst.trigger_level = 0.153\n            >>> inst.trigger_level\n            0.153\n\n        \"\"\"\n        level = self._no_ack_query(\"*GTL\")\n        # get the percent\n        retval = float(level[level.find(\":\") + 1 : level.find(\"%\")]) / 100\n        return retval\n\n    @trigger_level.setter\n    def trigger_level(self, newval):\n        if newval < 0.001 or newval > 0.99:\n            raise ValueError(\n                \"Trigger level {} is out of range. It must be \"\n                \"between 0.001 and 0.998.\".format(newval)\n            )\n\n        newval = newval * 100.0\n        if newval >= 10:\n            newval = str(round(newval, 1)).zfill(4)\n        else:\n            newval = str(round(newval, 2)).zfill(4)\n\n        self.sendcmd(f\"*STL{newval}\")\n\n    @property\n    def usb_state(self):\n        \"\"\"Get status if USB cable is connected.\n\n        :return: Is a USB cable connected?\n        :rtype: bool\n\n        Example:\n            >>> import instruments as ik\n            >>> inst = ik.gentec_eo.Blu.open_serial('/dev/ttyACM0')\n            >>> inst.usb_state\n            True\n        \"\"\"\n        return self._value_query(\"*USB\", tp=int) == 1\n\n    @property\n    def user_multiplier(self):\n        \"\"\"Get / Set user multiplier.\n\n        :return: User multiplier\n        :rtype: u.Quantity\n\n        Example:\n            >>> import instruments as ik\n            >>> inst = ik.gentec_eo.Blu.open_serial('/dev/ttyACM0')\n            >>> inst.user_multiplier = 10\n            >>> inst.user_multiplier\n            10.0\n        \"\"\"\n        return self._value_query(\"*GUM\", tp=float)\n\n    @user_multiplier.setter\n    def user_multiplier(self, newval):\n        sendval = _format_eight(newval)  # sendval: 8 characters long\n        self.sendcmd(f\"*MUL{sendval}\")\n\n    @property\n    def user_offset(self):\n        \"\"\"Get / Set user offset.\n\n        The user offset can be set unitful in watts or joules and set\n        to the device.\n\n        :return: User offset\n        :rtype: u.Quantity\n\n        :raises ValueError: Unit not supported or value for offset is\n            out of range.\n\n        Example:\n            >>> import instruments as ik\n            >>> inst = ik.gentec_eo.Blu.open_serial('/dev/ttyACM0')\n            >>> inst.user_offset = 10\n            >>> inst.user_offset\n            array(10.) * W\n        \"\"\"\n        if self._power_mode is None:\n            _ = self.measure_mode  # determine the power mode\n            sleep(0.01)\n\n        if self._power_mode:\n            return assume_units(self._value_query(\"*GUO\", tp=float), u.W)\n        else:\n            return assume_units(self._value_query(\"*GUO\", tp=float), u.J)\n\n    @user_offset.setter\n    def user_offset(self, newval):\n        # if unitful, try to rescale and grab magnitude\n        if isinstance(newval, u.Quantity):\n            if newval.is_compatible_with(u.W):\n                newval = newval.to(u.W).magnitude\n            elif newval.is_compatible_with(u.J):\n                newval = newval.to(u.J).magnitude\n            else:\n                raise ValueError(\n                    \"Value must be given in watts, \" \"joules, or unitless.\"\n                )\n        sendval = _format_eight(newval)  # sendval: 8 characters long\n        self.sendcmd(f\"*OFF{sendval}\")\n\n    @property\n    def version(self):\n        \"\"\"Get device information.\n\n        :return: Version and device type\n        :rtype: str\n\n        Example:\n            >>> import instruments as ik\n            >>> inst = ik.gentec_eo.Blu.open_serial('/dev/ttyACM0')\n            >>> inst.version\n            'Blu firmware Version 1.95'\n        \"\"\"\n        return self._no_ack_query(\"*VER\")\n\n    @property\n    def wavelength(self):\n        \"\"\"Get / Set the wavelength.\n\n        The wavelength can be set unitful. Specifying zero as a\n        wavelength or providing an out-of-bound value as a parameter\n        restores the default settings, typically 1064nm. If no units\n        are provided, nm are assumed.\n\n        :return: Wavelength in nm\n        :rtype: u.Quantity\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> inst = ik.gentec_eo.Blu.open_serial('/dev/ttyACM0')\n            >>> inst.wavelength = u.Quantity(527, u.nm)\n            >>> inst.wavelength\n            array(527) * nm\n        \"\"\"\n        return u.Quantity(self._value_query(\"*GWL\", tp=int), u.nm)\n\n    @wavelength.setter\n    def wavelength(self, newval):\n        val = round(assume_units(newval, u.nm).to(u.nm).magnitude)\n        if val >= 1000000 or val < 0:  # can only send 5 digits\n            val = 0  # out of bound anyway\n        val = str(int(val)).zfill(5)\n        self.sendcmd(f\"*PWC{val}\")\n\n    @property\n    def zero_offset(self):\n        \"\"\"Get / Set zero offset.\n\n        Gets the status if zero offset is enabled. When set to `True`,\n        the device will read the current level immediately for around\n        three seconds and then set the baseline to the averaged value.\n        If activated and set to `True` again, a new value for the\n        baseline will be established.\n\n        :return: Is zero offset enabled?\n        :rtype: bool\n\n        Example:\n            >>> import instruments as ik\n            >>> inst = ik.gentec_eo.Blu.open_serial('/dev/ttyACM0')\n            >>> inst.zero_offset\n            True\n            >>> inst.zero_offset = False\n        \"\"\"\n        return self._value_query(\"*GZO\", tp=int) == 1\n\n    @zero_offset.setter\n    def zero_offset(self, newval):\n        if newval:\n            self.sendcmd(\"*SOU\")\n        else:\n            self.sendcmd(\"*COU\")\n\n    # METHODS #\n\n    def confirm_connection(self):\n        \"\"\"Confirm a connection to the device.\n\n        Turns of bluetooth searching by confirming a connection.\n\n        Example:\n            >>> import instruments as ik\n            >>> inst = ik.gentec_eo.Blu.open_serial('/dev/ttyACM0')\n            >>> inst.confirm_connection()\n        \"\"\"\n        self.sendcmd(\"*RDY\")\n\n    def disconnect(self):\n        \"\"\"Disconnect the device.\n\n        Example:\n            >>> import instruments as ik\n            >>> inst = ik.gentec_eo.Blu.open_serial('/dev/ttyACM0')\n            >>> inst.disconnect()\n        \"\"\"\n        self.sendcmd(\"*BTD\")\n\n    def scale_down(self):\n        \"\"\"Set scale to next lower level.\n\n        Sets the power meter to the next lower scale. If already at\n        the lowest possible scale, no change will be made.\n\n        Example:\n            >>> import instruments as ik\n            >>> inst = ik.gentec_eo.Blu.open_serial('/dev/ttyACM0')\n            >>> inst.scale_down()\n        \"\"\"\n        self.sendcmd(\"*SSD\")\n\n    def scale_up(self):\n        \"\"\"Set scale to next higher level.\n\n        Sets the power meter to the next higher scale. If already at\n        the highest possible scale, no change will be made.\n\n        Example:\n            >>> import instruments as ik\n            >>> inst = ik.gentec_eo.Blu.open_serial('/dev/ttyACM0')\n            >>> inst.scale_up()\n        \"\"\"\n        self.sendcmd(\"*SSU\")\n\n    # PRIVATE METHODS #\n\n    def _no_ack_query(self, cmd, size=-1):\n        \"\"\"Query a value and don't expect an ACK message.\"\"\"\n        self._ack_message = None\n        try:\n            value = self.query(cmd, size=size)\n        finally:\n            self._ack_message = \"ACK\"\n        return value\n\n    def _value_query(self, cmd, tp=str):\n        \"\"\"Query one specific value and return it.\n\n        :param cmd: Command to send to self._no_ack_query.\n        :type cmd: str\n        :param tp: Type of the value to be returned, default: str\n        :type tp: type\n\n        :return: Single value of query.\n        :rtype: tp (selected type)\n\n        :raises ValueError: Conversion of response into given type was\n            unsuccessful.\n        \"\"\"\n        resp = self._no_ack_query(cmd).rstrip()  # strip \\r\\n\n        resp = resp.split(\":\")[1]  # strip header off\n        resp = resp.replace(\" \", \"\")  # strip white space\n        if isinstance(resp, tp):\n            return resp\n        else:\n            return tp(resp)\n\n\ndef _format_eight(value):\n    \"\"\"Formats a value to eight characters total.\n\n    :param value: value to be formatted, > -1e100 and < 1e100\n    :type value: int,float\n\n    :return: Value formatted to 8 characters\n    :rtype: str\n    \"\"\"\n    if abs(value) < 1e-99:\n        return \"0\".zfill(8)\n\n    for p in range(8, 1, -1):\n        val = f\"{value:.{p}g}\".zfill(8)\n        if len(val) == 8:\n            return val\n"
  },
  {
    "path": "src/instruments/glassman/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing Glassman power supplies\n\"\"\"\n\nfrom .glassmanfr import GlassmanFR\n"
  },
  {
    "path": "src/instruments/glassman/glassmanfr.py",
    "content": "#!/usr/bin/env python\n#\n# hpe3631a.py: Driver for the Glassman FR Series Power Supplies\n#\n# © 2019 Francois Drielsma (francois.drielsma@gmail.com).\n#\n# This file is a part of the InstrumentKit project.\n# Licensed under the AGPL version 3.\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Affero General Public License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with this program. If not, see <http://www.gnu.org/licenses/>.\n#\n\"\"\"\nDriver for the Glassman FR Series Power Supplies\n\nOriginally contributed and copyright held by Francois Drielsma\n(francois.drielsma@gmail.com)\n\nAn unrestricted license has been provided to the maintainers of the Instrument\nKit project.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom struct import unpack\nfrom enum import Enum\n\nfrom instruments.abstract_instruments import PowerSupply\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import assume_units\n\n# CLASSES #####################################################################\n\n\nclass GlassmanFR(PowerSupply, PowerSupply.Channel):\n    \"\"\"\n    The GlassmanFR is a single output power supply.\n\n    Because it is a single channel output, this object inherits from both\n    PowerSupply and PowerSupply.Channel.\n\n    This class should work for any of the Glassman FR Series power supplies\n    and is also likely to work for the EJ, ET, EY and FJ Series which seem\n    to share their communication protocols. The code has only been tested\n    by the author with an Glassman FR50R6 power supply.\n\n    Before this power supply can be remotely operated, remote communication\n    must be enabled and the HV must be on. Please refer to the manual.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> psu = ik.glassman.GlassmanFR.open_serial('/dev/ttyUSB0', 9600)\n    >>> psu.voltage = 100 # Sets output voltage to 100V.\n    >>> psu.voltage\n    array(100.0) * V\n    >>> psu.output = True # Turns on the power supply\n    >>> psu.voltage_sense < 200 * u.volt\n    True\n\n    This code uses default values of `voltage_max`, `current_max` and\n    `polarity` that are only valid of the FR50R6 in its positive setting.\n    If your power supply differs, reset those values by calling:\n\n    >>> import instruments.units as u\n    >>> psu.voltage_max = 40.0 * u.kilovolt\n    >>> psu.current_max = 7.5 * u.milliamp\n    >>> psu.polarity = -1\n    \"\"\"\n\n    def __init__(self, filelike):\n        \"\"\"\n        Initialize the instrument, and set the properties needed for communication.\n        \"\"\"\n        super().__init__(filelike)\n        self.terminator = \"\\r\"\n        self.voltage_max = 50.0 * u.kilovolt\n        self.current_max = 6.0 * u.milliamp\n        self.polarity = +1\n        self._device_timeout = False\n        self._voltage = 0.0 * u.volt\n        self._current = 0.0 * u.amp\n\n    # ENUMS ##\n\n    class Mode(Enum):\n        \"\"\"\n        Enum containing the possible modes of operations of the instrument\n        \"\"\"\n\n        #: Constant voltage mode\n        voltage = \"0\"\n        #: Constant current mode\n        current = \"1\"\n\n    class ResponseCode(Enum):\n        \"\"\"\n        Enum containing the possible reponse codes returned by the instrument.\n        \"\"\"\n\n        #: A set command expects an acknowledge response (`A`)\n        S = \"A\"\n        #: A query command expects a response packet (`R`)\n        Q = \"R\"\n        #: A version query expects a different response packet (`B`)\n        V = \"B\"\n        #: A configure command expects an acknowledge response (`A`)\n        C = \"A\"\n\n    class ErrorCode(Enum):\n        \"\"\"\n        Enum containing the possible error codes returned by the instrument.\n        \"\"\"\n\n        #: Undefined command received (not S, Q, V or C)\n        undefined_command = \"1\"\n        #: The checksum calculated by the instrument does not correspond to the one received\n        checksum_error = \"2\"\n        #: The command was longer than expected\n        extra_bytes = \"3\"\n        #: The digital control byte set was not one of HV On, HV Off or Power supply Reset\n        illegal_control = \"4\"\n        #: A send command was sent without a reset byte while the power supply is faulted\n        illegal_while_fault = \"5\"\n        #: Command valid, error while executing it\n        processing_error = \"6\"\n\n    # PROPERTIES ##\n\n    @property\n    def channel(self):\n        \"\"\"\n        Return the channel (which in this case is the entire instrument, since\n        there is only 1 channel on the GlassmanFR.)\n\n        :rtype: 'tuple' of length 1 containing a reference back to the parent\n            GlassmanFR object.\n        \"\"\"\n        return [self]\n\n    @property\n    def voltage(self):\n        \"\"\"\n        Gets/sets the output voltage setting.\n\n        :units: As specified, or assumed to be :math:`\\\\text{V}` otherwise.\n        :type: `float` or `~pint.Quantity`\n        \"\"\"\n        return self.polarity * self._voltage\n\n    @voltage.setter\n    def voltage(self, newval):\n        self.set_status(voltage=assume_units(newval, u.volt))\n\n    @property\n    def current(self):\n        \"\"\"\n        Gets/sets the output current setting.\n\n        :units: As specified, or assumed to be :math:`\\\\text{A}` otherwise.\n        :type: `float` or `~pint.Quantity`\n        \"\"\"\n        return self.polarity * self._current\n\n    @current.setter\n    def current(self, newval):\n        self.set_status(current=assume_units(newval, u.amp))\n\n    @property\n    def voltage_sense(self):\n        \"\"\"\n        Gets the output voltage as measured by the sense wires.\n\n        :units: As specified, or assumed to be :math:`\\\\text{V}` otherwise.\n        :type: `~pint.Quantity`\n        \"\"\"\n        return self.get_status()[\"voltage\"]\n\n    @property\n    def current_sense(self):\n        \"\"\"\n        Gets/sets the output current as measured by the sense wires.\n\n        :units: As specified, or assumed to be :math:`\\\\text{A}` otherwise.\n        :type: `~pint.Quantity`\n        \"\"\"\n        return self.get_status()[\"current\"]\n\n    @property\n    def mode(self):\n        \"\"\"\n        Gets/sets the mode for the specified channel.\n\n        The constant-voltage/constant-current modes of the power supply\n        are selected automatically depending on the load (resistance)\n        connected to the power supply. If the load greater than the set\n        V/I is connected, a voltage V is applied and the current flowing\n        is lower than I. If the load is smaller than V/I, the set current\n        I acts as a current limiter and the voltage is lower than V.\n\n        :type: `GlassmanFR.Mode`\n        \"\"\"\n        return self.get_status()[\"mode\"]\n\n    @property\n    def output(self):\n        \"\"\"\n        Gets/sets the output status.\n\n        This is a toggle setting. True will turn on the instrument output\n        while False will turn it off.\n\n        :type: `bool`\n        \"\"\"\n        return self.get_status()[\"output\"]\n\n    @output.setter\n    def output(self, newval):\n        if not isinstance(newval, bool):\n            raise TypeError(\"Output status mode must be a boolean.\")\n        self.set_status(output=newval)\n\n    @property\n    def fault(self):\n        \"\"\"\n        Gets the output status.\n\n        Returns True if the instrument has a fault.\n\n        :type: `bool`\n        \"\"\"\n        return self.get_status()[\"fault\"]\n\n    @property\n    def version(self):\n        \"\"\"\n        The software revision level of the power supply's\n        data intereface via the `V` command\n\n        :rtype: `str`\n        \"\"\"\n        return self.query(\"V\")\n\n    @property\n    def device_timeout(self):\n        \"\"\"\n        Gets/sets the timeout instrument side.\n\n        This is a toggle setting. ON will set the timeout to 1.5\n        seconds while OFF will disable it.\n\n        :type: `bool`\n        \"\"\"\n        return self._device_timeout\n\n    @device_timeout.setter\n    def device_timeout(self, newval):\n        if not isinstance(newval, bool):\n            raise TypeError(\"Device timeout mode must be a boolean.\")\n        self.query(f\"C{int(not newval)}\")  # Device acknowledges\n        self._device_timeout = newval\n\n    # METHODS ##\n\n    def sendcmd(self, cmd):\n        \"\"\"\n        Overrides the default `setcmd` by padding the front of each\n        command sent to the instrument with an SOH character and the\n        back of it with a checksum.\n\n        :param str cmd: The command message to send to the instrument\n        \"\"\"\n        checksum = self._get_checksum(cmd)\n        self._file.sendcmd(\"\\x01\" + cmd + checksum)  # Add SOH and checksum\n\n    def query(self, cmd, size=-1):\n        \"\"\"\n        Overrides the default `query` by padding the front of each\n        command sent to the instrument with an SOH character and the\n        back of it with a checksum.\n\n        This implementation also automatically check that the checksum\n        returned by the instrument is consistent with the message. If\n        the message returned is an error, it parses it and raises.\n\n        :param str cmd: The query message to send to the instrument\n        :param int size: The number of bytes to read back from the instrument\n            response.\n        :return: The instrument response to the query\n        :rtype: `str`\n        \"\"\"\n        self.sendcmd(cmd)\n        result = self._file.read(size)\n        if result[0] != getattr(self.ResponseCode, cmd[0]).value and result[0] != \"E\":\n            raise ValueError(f\"Invalid response code: {result}\")\n        if result[0] == \"A\":\n            return \"Acknowledged\"\n        if not self._verify_checksum(result):\n            raise ValueError(f\"Invalid checksum: {result}\")\n        if result[0] == \"E\":\n            error_name = self.ErrorCode(result[1]).name\n            raise ValueError(f\"Instrument responded with error: {error_name}\")\n\n        return result[1:-2]  # Remove SOH and checksum\n\n    def reset(self):\n        \"\"\"\n        Reset device to default status (HV Off, V=0.0, A=0.0)\n        \"\"\"\n        self.set_status(reset=True)\n\n    def set_status(self, voltage=None, current=None, output=None, reset=False):\n        \"\"\"\n        Sets the requested variables on the instrument.\n\n        This instrument can only set all of its variables simultaneously,\n        if some of them are omitted in this function, they will simply be\n        kept as what they were set to previously.\n        \"\"\"\n        if reset:\n            self._voltage = 0.0 * u.volt\n            self._current = 0.0 * u.amp\n            cmd = format(4, \"013d\")\n        else:\n            # The maximum value is encoded as the maximum of three hex characters (4095)\n            cmd = \"\"\n            value_max = int(0xFFF)\n\n            # If the voltage is not specified, keep it as is\n            voltage = (\n                assume_units(voltage, u.volt) if voltage is not None else self.voltage\n            )\n            ratio = float(voltage.to(u.volt) / self.voltage_max.to(u.volt))\n            voltage_int = int(round(value_max * ratio))\n            self._voltage = self.voltage_max * float(voltage_int) / value_max\n            assert 0.0 * u.volt <= self._voltage <= self.voltage_max\n            cmd += format(voltage_int, \"03X\")\n\n            # If the current is not specified, keep it as is\n            current = (\n                assume_units(current, u.amp) if current is not None else self.current\n            )\n            ratio = float(current.to(u.amp) / self.current_max.to(u.amp))\n            current_int = int(round(value_max * ratio))\n            self._current = self.current_max * float(current_int) / value_max\n            assert 0.0 * u.amp <= self._current <= self.current_max\n            cmd += format(current_int, \"03X\")\n\n            # If the output status is not specified, keep it as is\n            output = output if output is not None else self.output\n            control = f\"00{int(output)}{int(not output)}\"\n            cmd += format(int(control, 2), \"07X\")\n\n        self.query(\"S\" + cmd)  # Device acknowledges\n\n    def get_status(self):\n        \"\"\"\n        Gets and parses the response packet.\n\n        Returns a `dict` with the following keys:\n        ``{voltage,current,mode,fault,output}``\n\n        :rtype: `dict`\n        \"\"\"\n        return self._parse_response(self.query(\"Q\"))\n\n    def _parse_response(self, response):\n        \"\"\"\n        Parse the response packet returned by the power supply.\n\n        Returns a `dict` with the following keys:\n        ``{voltage,current,mode,fault,output}``\n\n        :param response: Byte string to be unpacked and parsed\n        :type: `str`\n\n        :rtype: `dict`\n        \"\"\"\n        voltage, current, monitors = unpack(\"@3s3s3x1c2x\", bytes(response, \"utf-8\"))\n\n        try:\n            voltage = self._parse_voltage(voltage)\n            current = self._parse_current(current)\n            mode, fault, output = self._parse_monitors(monitors)\n        except:\n            raise RuntimeError(\"Cannot parse response \" \"packet: {}\".format(response))\n\n        return {\n            \"voltage\": voltage,\n            \"current\": current,\n            \"mode\": mode,\n            \"fault\": fault,\n            \"output\": output,\n        }\n\n    def _parse_voltage(self, word):\n        \"\"\"\n        Converts the three-bytes voltage word returned in the\n        response packet to a single voltage quantity.\n\n        :param word: Byte string to be parsed\n        :type: `bytes`\n\n        :rtype: `~pint.Quantity`\n        \"\"\"\n        value = int(word.decode(\"utf-8\"), 16)\n        value_max = int(0x3FF)\n        return self.polarity * self.voltage_max * float(value) / value_max\n\n    def _parse_current(self, word):\n        \"\"\"\n        Converts the three-bytes current word returned in the\n        response packet to a single current quantity.\n\n        :param word: Byte string to be parsed\n        :type: `bytes`\n\n        :rtype: `~pint.Quantity`\n        \"\"\"\n        value = int(word.decode(\"utf-8\"), 16)\n        value_max = int(0x3FF)\n        return self.polarity * self.current_max * float(value) / value_max\n\n    def _parse_monitors(self, word):\n        \"\"\"\n        Converts the monitors byte returned in the response packet\n        to a mode, a fault boolean and an output boolean.\n\n        :param word: Byte to be parsed\n        :type: `byte`\n\n        :rtype: `str, bool, bool`\n        \"\"\"\n        bits = format(int(word, 16), \"04b\")\n        mode = self.Mode(bits[-1])\n        fault = bits[-2] == \"1\"\n        output = bits[-3] == \"1\"\n        return mode, fault, output\n\n    def _verify_checksum(self, word):\n        \"\"\"\n        Calculates the modulo 256 checksum of a string of characters\n        and compares it to the one returned by the instrument.\n\n        Returns True if they agree, False otherwise.\n\n        :param word: Byte string to be checked\n        :type: `str`\n\n        :rtype: `bool`\n        \"\"\"\n        data = word[1:-2]\n        inst_checksum = word[-2:]\n        calc_checksum = self._get_checksum(data)\n        return inst_checksum == calc_checksum\n\n    @staticmethod\n    def _get_checksum(data):\n        \"\"\"\n        Calculates the modulo 256 checksum of a string of characters.\n        This checksum, expressed in hexadecimal, is used in every\n        communication of this instrument, as a sanity check.\n\n        Returns a string corresponding to the hexademical value\n        of the checksum, without the `0x` prefix.\n\n        :param data: Byte string to be checksummed\n        :type: `str`\n\n        :rtype: `str`\n        \"\"\"\n        chrs = list(data)\n        total = 0\n        for c in chrs:\n            total += ord(c)\n\n        return format(total % 256, \"02X\")\n"
  },
  {
    "path": "src/instruments/hcp/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing HC Photonics instruments\n\"\"\"\n\nfrom .tc038 import TC038\nfrom .tc038d import TC038D\n"
  },
  {
    "path": "src/instruments/hcp/tc038.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the TC038 AC crystal oven by HC Photonics.\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nfrom instruments.units import ureg as u\n\nfrom instruments.abstract_instruments.instrument import Instrument\nfrom instruments.util_fns import assume_units\n\n# CLASSES #####################################################################\n\n\nclass TC038(Instrument):\n    \"\"\"\n    Communication with the HCP TC038 oven.\n\n    This is the older version with an AC power supply and AC heater.\n\n    It has parity or framing errors from time to time. Handle them in your\n    application.\n    \"\"\"\n\n    _registers = {\n        \"temperature\": \"D0002\",\n        \"setpoint\": \"D0120\",\n    }\n\n    def __init__(self, *args, **kwargs):\n        \"\"\"\n        Initialize the TC038 is a crystal oven.\n\n        Example usage:\n\n        >>> import instruments as ik\n        >>> import instruments.units as u\n        >>> inst = ik.hcp.TC038.open_serial('COM10')\n        >>> inst.setpoint = 45.3\n        >>> print(inst.temperature)\n        \"\"\"\n        super().__init__(*args, **kwargs)\n        self.terminator = \"\\r\"\n        self.addr = 1\n        self._monitored_quantity = None\n        self._file.parity = \"E\"  # serial.PARITY_EVEN\n\n    def sendcmd(self, command):\n        \"\"\"\n        Send \"command\" to the oven with \"commandData\".\n\n        Parameters\n        ----------\n        command : string, optional\n            Command to be sent. Three chars indicating the type, and data for\n            the command, if necessary.\n        \"\"\"\n        # 010 is CPU (01) and time to wait (0), which are fix\n        super().sendcmd(chr(2) + f\"{self.addr:02}\" + \"010\" + command + chr(3))\n\n    def query(self, command):\n        \"\"\"\n        Send a command to the oven and read its response.\n\n        Parameters\n        ----------\n        command : string, optional\n            Command to be sent. Three chars indicating the type, and data for\n            the command, if necessary.\n\n        Returns\n        -------\n        string\n            response of the system.\n        \"\"\"\n        return super().query(chr(2) + f\"{self.addr:02}\" + \"010\" + command + chr(3))\n\n    @property\n    def monitored_quantity(self):\n        \"\"\"The monitored quantity.\"\"\"\n        return self._monitored_quantity\n\n    @monitored_quantity.setter\n    def monitored_quantity(self, quantity=\"temperature\"):\n        \"\"\"\n        Configure the oven to monitor a certain `quantity`.\n\n        `quantity` may be any key of `_registers`. Default is the current\n        temperature in °C.\n        \"\"\"\n        assert quantity in self._registers.keys(), f\"Quantity {quantity} is unknown.\"\n        # WRS in order to setup to monitor a word\n        # monitor 1 to 16 words\n        # monitor the word in the given register\n        # Additional registers are added with a separating space or comma.\n        self.query(command=\"WRS\" + \"01\" + self._registers[quantity])\n        self._monitored_quantity = quantity\n\n    @property\n    def setpoint(self):\n        \"\"\"Read and return the current setpoint in °C.\"\"\"\n        got = self.query(command=\"WRD\" + \"D0120\" + \",01\")\n        # WRD: read words\n        # start with register D0003\n        # read a single word, separated by space or comma\n        return self._data_to_temp(got)\n\n    @setpoint.setter\n    def setpoint(self, value):\n        \"\"\"Set the setpoint to a temperature in °C.\"\"\"\n        number = assume_units(value, u.degC).to(u.degC).magnitude\n        commandData = f\"D0120,01,{int(round(number * 10)):04X}\"\n        # Temperature without decimal sign in hex representation\n        got = self.query(command=\"WWR\" + commandData)\n        assert got[5:7] == \"OK\", \"A communication error occurred.\"\n\n    @property\n    def temperature(self):\n        \"\"\"Read and return the current temperature in °C.\"\"\"\n        got = self.query(command=\"WRD\" + \"D0002\" + \",01\")\n        return self._data_to_temp(got)\n\n    @property\n    def monitored_value(self):\n        \"\"\"\n        Read and return the monitored value.\n\n        Per default it's the current temperature in °C.\n        \"\"\"\n        # returns the monitored words\n        got = self.query(command=\"WRM\")\n        return self._data_to_temp(got)\n\n    @property\n    def information(self):\n        \"\"\"Read the device information.\"\"\"\n        return self.query(\"INF6\")[7:-1]\n\n    @staticmethod\n    def _data_to_temp(data):\n        \"\"\"Convert the returned hex value \"data\" to a temperature in °C.\"\"\"\n        return u.Quantity(int(data[7:11], 16) / 10, u.degC)\n        # get the hex number, convert to int and shift the decimal sign\n"
  },
  {
    "path": "src/instruments/hcp/tc038d.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the TC038 AC crystal oven by HC Photonics.\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nfrom instruments.units import ureg as u\n\nfrom instruments.abstract_instruments.instrument import Instrument\nfrom instruments.util_fns import assume_units\n\n# CLASSES #####################################################################\n\n\nclass TC038D(Instrument):\n    \"\"\"\n    Communication with the HCP TC038D oven.\n\n    This is the newer version with DC heating.\n\n    The temperature controller is on default set to modbus communication.\n    The oven expects raw bytes written, no ascii code, and sends raw bytes.\n    For the variables are two or four-byte modes available. We use the\n    four-byte mode addresses, so do we. In that case element count has to be\n    double the variables read.\n    \"\"\"\n\n    functions = {\"read\": 0x03, \"writeMultiple\": 0x10, \"writeSingle\": 0x06, \"echo\": 0x08}\n\n    byteMode = 4\n\n    def __init__(self, *args, **kwargs):\n        \"\"\"\n        The TC038 is a crystal oven.\n\n        Example usage:\n\n        >>> import instruments as ik\n        >>> import instruments.units as u\n        >>> inst = ik.hcp.TC038.open_serial('COM10')\n        >>> inst.setpoint = 45.3\n        >>> print(inst.temperature)\n        \"\"\"\n        super().__init__(*args, **kwargs)\n        self.addr = 1\n\n    @staticmethod\n    def CRC16(data):\n        \"\"\"Calculate the CRC16 checksum for the data byte array.\"\"\"\n        CRC = 0xFFFF\n        for octet in data:\n            CRC ^= octet\n            for j in range(8):\n                lsb = CRC & 0x1  # least significant bit\n                CRC = CRC >> 1\n                if lsb:\n                    CRC ^= 0xA001\n        return [CRC & 0xFF, CRC >> 8]\n\n    def readRegister(self, address, count=1):\n        \"\"\"Read count variables from start address on.\"\"\"\n        # Count has to be double the number of elements in 4-byte-mode.\n        count *= self.byteMode // 2\n        data = [self.addr]\n        data.append(self.functions[\"read\"])  # function code\n        data += [address >> 8, address & 0xFF]  # 2B address\n        data += [count >> 8, count & 0xFF]  # 2B number of elements\n        data += self.CRC16(data)\n        self._file.write_raw(bytes(data))\n        # Slave address, function, length\n        got = self.read_raw(3)\n        if got[1] == self.functions[\"read\"]:\n            length = got[2]\n            # data length, 2 Byte CRC\n            read = self.read_raw(length + 2)\n            if read[-2:] != bytes(self.CRC16(got + read[:-2])):\n                raise ConnectionError(\"Response CRC does not match.\")\n            return read[:-2]\n        else:  # an error occurred\n            end = self.read_raw(2)  # empty the buffer\n            if got[2] == 0x02:\n                raise ValueError(\"The read start address is incorrect.\")\n            if got[2] == 0x03:\n                raise ValueError(\"The number of elements exceeds the allowed range\")\n            raise ConnectionError(f\"Unknown read error. Received: {got} {end}\")\n\n    def writeMultiple(self, address, values):\n        \"\"\"Write multiple variables.\"\"\"\n        data = [self.addr]\n        data.append(self.functions[\"writeMultiple\"])  # function code\n        data += [address >> 8, address & 0xFF]  # 2B address\n        if isinstance(values, int):\n            data += [0x0, self.byteMode // 2]  # 2B number of elements\n            data.append(self.byteMode)  # 1B number of write data\n            for i in range(self.byteMode - 1, -1, -1):\n                data.append(values >> i * 8 & 0xFF)\n        elif hasattr(values, \"__iter__\"):\n            elements = len(values) * self.byteMode // 2\n            data += [elements >> 8, elements & 0xFF]  # 2B number of elements\n            data.append(len(values) * self.byteMode)  # 1B number of write data\n            for element in values:\n                for i in range(self.byteMode - 1, -1, -1):\n                    data.append(element >> i * 8 & 0xFF)\n        else:\n            raise ValueError(\n                \"Values has to be an integer or an iterable of \"\n                f\"integers. values: {values}\"\n            )\n        data += self.CRC16(data)\n        self._file.write_raw(bytes(data))\n        got = self.read_raw(2)\n        # slave address, function\n        if got[1] == self.functions[\"writeMultiple\"]:\n            # start address, number elements, CRC; each 2 Bytes long\n            got += self.read_raw(2 + 2 + 2)\n            if got[-2:] != bytes(self.CRC16(got[:-2])):\n                raise ConnectionError(\"Response CRC does not match.\")\n        else:\n            end = self.read_raw(3)  # error code and CRC\n            errors = {\n                0x02: \"Wrong start address\",\n                0x03: \"Variable data error\",\n                0x04: \"Operation error\",\n            }\n            raise ValueError(errors[end[0]])\n\n    @property\n    def setpoint(self):\n        \"\"\"Get the current setpoint in °C.\"\"\"\n        value = int.from_bytes(self.readRegister(0x106), byteorder=\"big\") / 10\n        return u.Quantity(value, u.degC)\n\n    @setpoint.setter\n    def setpoint(self, value):\n        \"\"\"Set the setpoint in °C.\"\"\"\n        value = assume_units(value, u.degC).to(u.degC)\n        value = int(round(value.to(\"degC\").magnitude * 10, 0))\n        self.writeMultiple(0x106, int(round(value)))\n\n    @property\n    def temperature(self):\n        \"\"\"Get the current temperature in °C.\"\"\"\n        value = int.from_bytes(self.readRegister(0x0), byteorder=\"big\") / 10\n        return u.Quantity(value, u.degC)\n"
  },
  {
    "path": "src/instruments/holzworth/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing Holzworth instruments\n\"\"\"\n\nfrom .holzworth_hs9000 import HS9000\n"
  },
  {
    "path": "src/instruments/holzworth/holzworth_hs9000.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the Holzworth HS9000\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nfrom instruments.units import ureg as u\n\nfrom instruments.abstract_instruments.signal_generator import SignalGenerator, SGChannel\nfrom instruments.util_fns import (\n    ProxyList,\n    split_unit_str,\n    bounded_unitful_property,\n    bool_property,\n)\n\n# CLASSES #####################################################################\n\n\nclass HS9000(SignalGenerator):\n    \"\"\"\n    Communicates with a `Holzworth HS-9000 series`_ multi-channel frequency\n    synthesizer.\n\n    .. _Holzworth HS-9000 series: http://www.holzworth.com/synthesizers-multi.htm\n    \"\"\"\n\n    # INNER CLASSES #\n\n    class Channel(SGChannel):\n        \"\"\"\n        Class representing a physical channel on the Holzworth HS9000\n\n        .. warning:: This class should NOT be manually created by the user. It\n            is designed to be initialized by the `HS9000` class.\n        \"\"\"\n\n        def __init__(self, hs, idx_chan):\n            self._hs = hs\n            self._idx = idx_chan\n\n            # We unpacked the channel index from the string of the form \"CH1\",\n            # in order to make the API more Pythonic, but now we need to put\n            # it back.\n            # Some channel names, like \"REF\", are special and are preserved\n            # as strs.\n            self._ch_name = (\n                idx_chan if isinstance(idx_chan, str) else f\"CH{idx_chan + 1}\"\n            )\n\n        # PRIVATE METHODS #\n\n        def sendcmd(self, cmd):\n            \"\"\"\n            Function used to send a command to the instrument while wrapping\n            the command with the neccessary identifier for the channel.\n\n            :param str cmd: Command that will be sent to the instrument after\n                being prefixed with the channel identifier\n            \"\"\"\n            self._hs.sendcmd(f\":{self._ch_name}:{cmd}\")\n\n        def query(self, cmd):\n            \"\"\"\n            Function used to send a command to the instrument while wrapping\n            the command with the neccessary identifier for the channel.\n\n            :param str cmd: Command that will be sent to the instrument after\n                being prefixed with the channel identifier\n            :return: The result from the query\n            :rtype: `str`\n            \"\"\"\n            return self._hs.query(f\":{self._ch_name}:{cmd}\")\n\n        # STATE METHODS #\n\n        def reset(self):\n            \"\"\"\n            Resets the setting of the specified channel\n\n            Example usage:\n            >>> import instruments as ik\n            >>> hs = ik.holzworth.HS9000.open_tcpip(\"192.168.0.2\", 8080)\n            >>> hs.channel[0].reset()\n            \"\"\"\n            self.sendcmd(\"*RST\")\n\n        def recall_state(self):\n            \"\"\"\n            Recalls the state of the specified channel from memory.\n\n            Example usage:\n            >>> import instruments as ik\n            >>> hs = ik.holzworth.HS9000.open_tcpip(\"192.168.0.2\", 8080)\n            >>> hs.channel[0].recall_state()\n            \"\"\"\n            self.sendcmd(\"*RCL\")\n\n        def save_state(self):\n            \"\"\"\n            Saves the current state of the specified channel.\n\n            Example usage:\n            >>> import instruments as ik\n            >>> hs = ik.holzworth.HS9000.open_tcpip(\"192.168.0.2\", 8080)\n            >>> hs.channel[0].save_state()\n            \"\"\"\n            self.sendcmd(\"*SAV\")\n\n        # PROPERTIES #\n\n        @property\n        def temperature(self):\n            \"\"\"\n            Gets the current temperature of the specified channel.\n\n            :units: As specified by the instrument.\n            :rtype: `~pint.Quantity`\n            \"\"\"\n            val, units = split_unit_str(self.query(\"TEMP?\"))\n            units = f\"deg{units}\"\n            return u.Quantity(val, units)\n\n        frequency, frequency_min, frequency_max = bounded_unitful_property(\n            \"FREQ\",\n            units=u.GHz,\n            doc=\"\"\"\n            Gets/sets the frequency of the specified channel. When setting,\n            values are bounded between what is returned by `frequency_min`\n            and `frequency_max`.\n\n            Example usage:\n            >>> import instruments as ik\n            >>> hs = ik.holzworth.HS9000.open_tcpip(\"192.168.0.2\", 8080)\n            >>> print(hs.channel[0].frequency)\n            >>> print(hs.channel[0].frequency_min)\n            >>> print(hs.channel[0].frequency_max)\n\n            :type: `~pint.Quantity`\n            :units: As specified or assumed to be of units GHz\n            \"\"\",\n        )\n        power, power_min, power_max = bounded_unitful_property(\n            \"PWR\",\n            units=u.dBm,\n            doc=\"\"\"\n            Gets/sets the output power of the specified channel. When setting,\n            values are bounded between what is returned by `power_min`\n            and `power_max`.\n\n            Example usage:\n            >>> import instruments as ik\n            >>> hs = ik.holzworth.HS9000.open_tcpip(\"192.168.0.2\", 8080)\n            >>> print(hs.channel[0].power)\n            >>> print(hs.channel[0].power_min)\n            >>> print(hs.channel[0].power_max)\n\n            :type: `~pint.Quantity`\n            :units: `instruments.units.dBm`\n            \"\"\",\n        )\n        phase, phase_min, phase_max = bounded_unitful_property(\n            \"PHASE\",\n            units=u.degree,\n            doc=\"\"\"\n            Gets/sets the output phase of the specified channel. When setting,\n            values are bounded between what is returned by `phase_min`\n            and `phase_max`.\n\n            Example usage:\n            >>> import instruments as ik\n            >>> hs = ik.holzworth.HS9000.open_tcpip(\"192.168.0.2\", 8080)\n            >>> print(hs.channel[0].phase)\n            >>> print(hs.channel[0].phase_min)\n            >>> print(hs.channel[0].phase_max)\n\n            :type: `~pint.Quantity`\n            :units: As specified or assumed to be of units degrees\n            \"\"\",\n        )\n\n        output = bool_property(\n            \"PWR:RF\",\n            inst_true=\"ON\",\n            inst_false=\"OFF\",\n            set_fmt=\"{}:{}\",\n            doc=\"\"\"\n            Gets/sets the output status of the channel. Setting to `True` will\n            turn the channel's output stage on, while a value of `False` will\n            turn it off.\n\n            Example usage:\n            >>> import instruments as ik\n            >>> hs = ik.holzworth.HS9000.open_tcpip(\"192.168.0.2\", 8080)\n            >>> print(hs.channel[0].output)\n            >>> hs.channel[0].output = True\n\n            :type: `bool`\n            \"\"\",\n        )\n\n    # PROXY LIST ##\n\n    def _channel_idxs(self):\n        \"\"\"\n        Internal function used to get the list of valid channel names\n        to be used by `HS9000.channel`\n\n        :return: A list of valid channel indicies\n        :rtype: `list` of `int` and `str`\n        \"\"\"\n        # The command :ATTACH? returns a string of the form \":CH1:CH2\" to\n        # indicate what channels are attached to the internal USB bus.\n        # We convert what channel names we can to integers, and leave the\n        # rest as strings.\n        return [\n            (\n                int(ch_name.replace(\"CH\", \"\")) - 1\n                if ch_name.startswith(\"CH\")\n                else ch_name.strip()\n            )\n            for ch_name in self.query(\":ATTACH?\").split(\":\")\n            if ch_name\n        ]\n\n    @property\n    def channel(self):\n        \"\"\"\n        Gets a specific channel on the HS9000. The desired channel is accessed\n        like one would access a list.\n\n        Example usage:\n\n        >>> import instruments as ik\n        >>> hs = ik.holzworth.HS9000.open_tcpip(\"192.168.0.2\", 8080)\n        >>> print(hs.channel[0].frequency)\n\n        :return: A channel object for the HS9000\n        :rtype: `~HS9000.Channel`\n        \"\"\"\n        return ProxyList(self, self.Channel, self._channel_idxs())\n\n    # OTHER PROPERTIES #\n\n    @property\n    def name(self):\n        \"\"\"\n        Gets identification string of the HS9000\n\n        :return: The string as usually returned by ``*IDN?`` on SCPI instruments\n        :rtype: `str`\n        \"\"\"\n        # This is a weird one; the HS-9000 associates the :IDN? command\n        # with each individual channel, though we want it to be a synthesizer-\n        # wide property. To solve this, we assume that CH1 is always a channel\n        # and ask its name.\n        return self.channel[0].query(\"IDN?\")\n\n    @property\n    def ready(self):\n        \"\"\"\n        Gets the ready status of the HS9000.\n\n        :return: If the instrument is ready for operation\n        :rtype: `bool`\n        \"\"\"\n        return \"Ready\" in self.query(\":COMM:READY?\")\n"
  },
  {
    "path": "src/instruments/hp/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing HP instruments\n\"\"\"\n\nfrom .hp3325a import HP3325a\nfrom .hp3456a import HP3456a\nfrom .hp6624a import HP6624a\nfrom .hp6632b import HP6632b\nfrom .hp6652a import HP6652a\nfrom .hpe3631a import HPe3631a\n"
  },
  {
    "path": "src/instruments/hp/hp3325a.py",
    "content": "#!/usr/bin/env python\n#\n# hp3325a.py: Driver for the HP3235a/b Synthesizer/Function Generator.\n#\n# © 2023 Scott Phillips (polygonguru@gmail.com).\n#\n# This file is a part of the InstrumentKit project.\n# Licensed under the AGPL version 3.\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Affero General Public License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with this program. If not, see <http://www.gnu.org/licenses/>.\n#\n\"\"\"\nDriver for the HP3325a Synthesizer/Function Generator\n\nOriginally contributed and copyright held by Scott Phillips (polygonguru@gmail.com)\n\nAn unrestricted license has been provided to the maintainers of the Instrument\nKit project.\n\"\"\"\n\n# IMPORTS #####################################################################\nimport math\nfrom enum import Enum, IntEnum\n\nfrom instruments import Instrument\nfrom instruments.abstract_instruments import FunctionGenerator\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import enum_property, unitful_property, bool_property\n\n# CLASSES #####################################################################\n\n\ndef amplitude_parse(am_resp: str) -> float:\n    am_units = am_resp[-2:]\n    am_num = am_resp[:-2].replace(\"AM\", \"\").strip()\n    return float(am_num * HP3325a.ampl_scale[am_units])\n\n\ndef frequency_parse(fr_resp: str) -> float:\n    freq_units = fr_resp[-2:]\n    freq_num = fr_resp[:-2].replace(\"FR\", \"\").strip()\n    return float(freq_num * HP3325a.freq_scale[freq_units])\n\n\ndef offset_parse(of_resp: str) -> float:\n    of_resp = of_resp.replace(\"OF\", \"\")\n    of_units = of_resp[-2:]\n    return float(of_resp[:-2]) * (1 if of_units == \"VO\" else 1000)\n\n\nclass HP3325a(FunctionGenerator):\n    \"\"\"The `HP3325a` is a 20Mhz Synthesizer / Function Generator.\n\n    It supports sine-, square-, triangle-, ramp-waves across a wide range of frequencies. It also supports amplitude\n    and phase modulation, as well as DC-offset.\n\n    `HP3325a` is a HPIB / pre-448.2 instrument.\n    \"\"\"\n\n    def __init__(self, filelike):\n        \"\"\"\n        Initialise the instrument, and set the required eos, eoi needed for\n        communication.\n        \"\"\"\n        super().__init__(filelike)\n        self._channel_count = 1\n        self.terminator = \"\\r\\n\"\n\n    class Waveform(IntEnum):\n        \"\"\"\n        Enum with the supported math modes\n        \"\"\"\n\n        dc_only = 0\n        sine = 1\n        square = 2\n        triangle = 3\n        positive_ramp = 4\n        negative_ramp = 5\n\n    class FrequencyScale(Enum):\n        \"\"\"\n        Enum with the supported frequency scales\n        \"\"\"\n\n        hertz = 1\n        kilohertz = 1e3\n        megahertz = 1e6\n\n    class AmplitudeScale(Enum):\n        \"\"\"\n        Enum with the supported amplitude scales\n        \"\"\"\n\n        Volts = 1\n        Millivolts = 1e-3\n        Volts_RMS = math.sqrt(2.0)\n        Millivolts_RMS = 1e-3 * math.sqrt(2.0)\n\n    freq_scale = {\"HZ\": 1, \"KH\": 1e3, \"MH\": 1e6}\n    ampl_scale = {\n        \"VO\": 1,\n        \"MV\": 1e-3,\n        \"VR\": math.sqrt(2.0),\n        \"MR\": 1e-3 * math.sqrt(2.0),\n    }\n\n    # PROPERTIES ##\n\n    function = enum_property(\n        command=\"IFU\",\n        enum=Waveform,\n        set_cmd=\"FU\",\n        doc=\"\"\"\n        Gets/sets the output function of the function generator\n\n        type: `HP3325a.Waveform`\n        \"\"\",\n        input_decoration=int,\n        set_fmt=\"{}{}\",\n    )\n\n    amplitude = unitful_property(\n        command=\"IAM\",\n        units=u.volts,\n        set_cmd=\"AM\",\n        format_code=\"{}\",\n        doc=\"\"\"\n        Gets/sets the amplitude of the output waveform\n\n        :type: `float`\n        \"\"\",\n        input_decoration=amplitude_parse,\n        set_fmt=\"{}{}VO\",\n    )\n\n    frequency = unitful_property(\n        command=\"IFR\",\n        units=u.hertz,\n        set_cmd=\"FR\",\n        format_code=\"{}\",\n        doc=\"\"\"\n        Gets/sets the frequency of the output waveform\n\n        :type: `float`\n        \"\"\",\n        input_decoration=frequency_parse,\n        set_fmt=\"{}{}HZ\",\n    )\n\n    offset = unitful_property(\n        command=\"IOF\",\n        units=u.volts,\n        set_cmd=\"OF\",\n        format_code=\"{}\",\n        doc=\"\"\"\n        Gets/sets the offset of the output waveform\n\n        :type: `float`\n        \"\"\",\n        input_decoration=offset_parse,\n        set_fmt=\"{}{}VO\",\n    )\n\n    phase = unitful_property(\n        command=\"IPH\",\n        units=u.degrees,\n        set_cmd=\"PH\",\n        format_code=\"{}\",\n        doc=\"\"\"\n        Gets/sets the phase of the output waveform\n\n        :type: `float`\n        \"\"\",\n        input_decoration=lambda x: float(x.replace(\"PH\", \"\").replace(\"DE\", \"\").strip()),\n        set_fmt=\"{}{}DE\",\n    )\n\n    high_voltage = bool_property(\n        command=\"IHV\",\n        set_cmd=\"HV\",\n        inst_true=\"HV1\",\n        inst_false=\"HV0\",\n        doc=\"\"\"\n        Gets/sets the high voltage mode of the output waveform\n\n        :type: `bool`\n        \"\"\",\n        set_fmt=\"{}{}\",\n    )\n\n    amplitude_modulation = bool_property(\n        command=\"IMA\",\n        set_cmd=\"MA\",\n        inst_true=\"1\",\n        inst_false=\"0\",\n        doc=\"\"\"\n        Gets/sets the amplitude modulation mode of the output waveform\n\n        :type: `bool`\n        \"\"\",\n        set_fmt=\"{}{}\",\n    )\n\n    marker_frequency = bool_property(\n        command=\"IMA\",\n        set_cmd=\"MA\",\n        inst_true=\"1\",\n        inst_false=\"0\",\n        doc=\"\"\"\n        Gets/sets the marker frequency mode of the output waveform\n\n        :type: `bool`\n        \"\"\",\n        set_fmt=\"{}{}\",\n    )\n\n    def query(self, cmd, size=-1):\n        \"\"\"\n        Query the instrument with the given command and return the response\n        \"\"\"\n        # strip the question mark because HP3325A is too old for that\n        cmd = cmd.replace(\"?\", \"\")\n        return Instrument.query(self, cmd, size)\n\n    def amplitude_calibration(self):\n        self.sendcmd(\"AC\")\n\n    def assign_zero_phase(self):\n        self.sendcmd(\"AP\")\n\n    # TODO - Support CALM which only works on 3325B\n    # TODO - Support DCLR which only works on 3325B\n    # TODO - Support DISP which only works on 3325B\n    # TODO - Support DRCL,DSTO which only works on 3325B\n    # TODO - Support DSP which only works on 3325B\n    # TODO - Support ECHO which only works on 3325B\n    # TODO - Support ENH which only works on 3325B\n    # TODO - Support ESTB which only works on 3325B\n    # TODO - Support EXTR which only works on 3325B\n    # TODO - Support HEAD which only works on 3325B\n    # TODO - Support *IDN? which only works on 3325B\n    # TODO - Support LCL which only works on 3325B\n\n    def query_error(self) -> int:\n        # TODO - Support ERR? on HP3325B which is more specific\n        err_resp = self.query(\"IER\")\n        return int(err_resp.replace(\"E\", \"\").replace(\"R\", \"\").strip())\n"
  },
  {
    "path": "src/instruments/hp/hp3456a.py",
    "content": "#!/usr/bin/env python\n#\n# hp3456a.py: Driver for the HP3456a Digital Voltmeter.\n#\n# © 2014 Willem Dijkstra (wpd@xs4all.nl).\n#\n# This file is a part of the InstrumentKit project.\n# Licensed under the AGPL version 3.\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Affero General Public License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with this program. If not, see <http://www.gnu.org/licenses/>.\n#\n\"\"\"\nDriver for the HP3456a Digital Voltmeter\n\nOriginally contributed and copyright held by Willem Dijkstra (wpd@xs4all.nl)\n\nAn unrestricted license has been provided to the maintainers of the Instrument\nKit project.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nimport time\nfrom enum import Enum, IntEnum\n\nfrom instruments.abstract_instruments import Multimeter\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import assume_units, bool_property, enum_property\n\n# CLASSES #####################################################################\n\n\nclass HP3456a(Multimeter):\n    \"\"\"The `HP3456a` is a 6 1/2 digit bench multimeter.\n\n    It supports DCV, ACV, ACV + DCV, 2 wire Ohms, 4 wire Ohms, DCV/DCV Ratio,\n    ACV/DCV Ratio, Offset compensated 2 wire Ohms and Offset compensated 4 wire\n    Ohms measurements.\n\n    Measurements can be further extended using a system math mode that allows\n    for pass/fail, statistics, dB/dBm, null, scale and percentage readings.\n\n    `HP3456a` is a HPIB / pre-448.2 instrument.\n    \"\"\"\n\n    def __init__(self, filelike):\n        \"\"\"\n        Initialise the instrument, and set the required eos, eoi needed for\n        communication.\n        \"\"\"\n        super().__init__(filelike)\n        self.timeout = 15 * u.second\n        self.terminator = \"\\r\"\n        self.sendcmd(\"HO0T4SO1\")\n        self._null = False\n\n    # ENUMS ##\n\n    class MathMode(IntEnum):\n        \"\"\"\n        Enum with the supported math modes\n        \"\"\"\n\n        off = 0\n        pass_fail = 1\n        statistic = 2\n        null = 3\n        dbm = 4\n        thermistor_f = 5\n        thermistor_c = 6\n        scale = 7\n        percent = 8\n        db = 9\n\n    class Mode(Enum):\n        \"\"\"\n        Enum containing the supported mode codes\n        \"\"\"\n\n        #: DC voltage\n        dcv = \"S0F1\"\n        #: AC voltage\n        acv = \"S0F2\"\n        #: RMS of DC + AC voltage\n        acvdcv = \"S0F3\"\n        #: 2 wire resistance\n        resistance_2wire = \"S0F4\"\n        #: 4 wire resistance\n        resistance_4wire = \"S0F5\"\n        #: ratio DC / DC voltage\n        ratio_dcv_dcv = \"S1F1\"\n        #: ratio AC / DC voltage\n        ratio_acv_dcv = \"S1F2\"\n        #: ratio (AC + DC) / DC voltage\n        ratio_acvdcv_dcv = \"S1F3\"\n        #: offset compensated 2 wire resistance\n        oc_resistence_2wire = \"S1F4\"\n        #: offset compensated 4 wire resistance\n        oc_resistence_4wire = \"S1F5\"\n\n    class Register(Enum):\n        \"\"\"\n        Enum with the register names for all `HP3456a` internal registers.\n        \"\"\"\n\n        number_of_readings = \"N\"\n        number_of_digits = \"G\"\n        nplc = \"I\"\n        delay = \"D\"\n        mean = \"M\"\n        variance = \"V\"\n        count = \"C\"\n        lower = \"L\"\n        r = \"R\"\n        upper = \"U\"\n        y = \"Y\"\n        z = \"Z\"\n\n    class TriggerMode(IntEnum):\n        \"\"\"\n        Enum with valid trigger modes.\n        \"\"\"\n\n        internal = 1\n        external = 2\n        single = 3\n        hold = 4\n\n    class ValidRange(Enum):\n        \"\"\"\n        Enum with the valid ranges for voltage, resistance, and number of\n        powerline cycles to integrate over.\n\n        \"\"\"\n\n        voltage = (1e-1, 1e0, 1e1, 1e2, 1e3)\n        resistance = (1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9)\n        nplc = (1e-1, 1e0, 1e1, 1e2)\n\n    # PROPERTIES ##\n\n    mode = enum_property(\n        \"\",\n        Mode,\n        doc=\"\"\"Set the measurement mode.\n\n        :type: `HP3456a.Mode`\n        \"\"\",\n        writeonly=True,\n        set_fmt=\"{}{}\",\n    )\n\n    autozero = bool_property(\n        \"Z\",\n        inst_true=\"1\",\n        inst_false=\"0\",\n        doc=\"\"\"Set the autozero mode.\n\n        This is used to compensate for offsets in the dc\n        input amplifier circuit of the multimeter. If set, the amplifier\"s input\n        circuit is shorted to ground prior to actual measurement in order to\n        take an offset reading. This offset is then used to compensate for\n        drift in the next measurement. When disabled, one offset reading\n        is taken immediately and stored into memory to be used for all\n        successive measurements onwards. Disabling autozero increases the\n        `HP3456a`\"s measurement speed, and also makes the instrument more\n        suitable for high impendance measurements since no input switching is\n        done.\"\"\",\n        writeonly=True,\n        set_fmt=\"{}{}\",\n    )\n\n    filter = bool_property(\n        \"FL\",\n        inst_true=\"1\",\n        inst_false=\"0\",\n        doc=\"\"\"Set the analog filter mode.\n\n        The `HP3456a` has a 3 pole active filter with\n        greater than 60dB attenuation at frequencies of 50Hz and higher. The\n        filter is applied between the input terminals and input amplifier. When\n        in ACV or ACV+DCV functions the filter is applied to the output of the\n        ac converter and input amplifier. In these modes select the filter for\n        measurements below 400Hz.\"\"\",\n        writeonly=True,\n        set_fmt=\"{}{}\",\n    )\n\n    math_mode = enum_property(\n        \"M\",\n        MathMode,\n        doc=\"\"\"Set the math mode.\n\n        The `HP3456a` has a number of different math modes that\n        can change measurement output, or can provide additional\n        statistics. Interaction with these modes is done via the\n        `HP3456a.Register`.\n\n        :type: `HP3456a.MathMode`\n        \"\"\",\n        writeonly=True,\n        set_fmt=\"{}{}\",\n    )\n\n    trigger_mode = enum_property(\n        \"T\",\n        TriggerMode,\n        doc=\"\"\"Set the trigger mode.\n\n        Note that using `HP3456a.measure()` will override the `trigger_mode` to\n        `HP3456a.TriggerMode.single`.\n\n        :type: `HP3456a.TriggerMode`\n\n        \"\"\",\n        writeonly=True,\n        set_fmt=\"{}{}\",\n    )\n\n    @property\n    def number_of_readings(self):\n        \"\"\"Get/set the number of readings done per trigger/measurement cycle\n        using `HP3456a.Register.number_of_readings`.\n\n        :type: `float`\n        :rtype: `float`\n\n        \"\"\"\n        return self._register_read(HP3456a.Register.number_of_readings)\n\n    @number_of_readings.setter\n    def number_of_readings(self, value):\n        self._register_write(HP3456a.Register.number_of_readings, value)\n\n    @property\n    def number_of_digits(self):\n        \"\"\"Get/set the number of digits used in measurements using\n        `HP3456a.Register.number_of_digits`.\n\n        Set to higher values to increase accuracy at the cost of measurement\n        speed.\n\n        :type: `int`\n        \"\"\"\n        return int(self._register_read(HP3456a.Register.number_of_digits))\n\n    @number_of_digits.setter\n    def number_of_digits(self, newval):\n        newval = int(newval)\n        if newval not in range(3, 7):\n            raise ValueError(\n                \"Valid number_of_digits are: \" \"{}\".format(list(range(3, 7)))\n            )\n\n        self._register_write(HP3456a.Register.number_of_digits, newval)\n\n    @property\n    def nplc(self):\n        \"\"\"Get/set the number of powerline cycles to integrate per measurement\n        using `HP3456a.Register.nplc`.\n\n        Setting higher values increases accuracy at the cost of a longer\n        measurement time. The implicit assumption is that the input reading is\n        stable over the number of powerline cycles to integrate.\n\n        :type: `int`\n        \"\"\"\n        return int(self._register_read(HP3456a.Register.nplc))\n\n    @nplc.setter\n    def nplc(self, newval):\n        newval = int(newval)\n        valid = HP3456a.ValidRange[\"nplc\"].value\n        if newval in valid:\n            self._register_write(HP3456a.Register.nplc, newval)\n        else:\n            raise ValueError(\"Valid nplc settings are: \" \"{}\".format(valid))\n\n    @property\n    def delay(self):\n        \"\"\"Get/set the delay that is waited after a trigger for the input to\n        settle using `HP3456a.Register.delay`.\n\n        :type: As specified, assumed to be `~quantaties.Quantity.s` otherwise\n        :rtype: `~quantaties.Quantity.s`\n\n        \"\"\"\n        return self._register_read(HP3456a.Register.delay) * u.s\n\n    @delay.setter\n    def delay(self, value):\n        delay = assume_units(value, u.s).to(u.s).magnitude\n        self._register_write(HP3456a.Register.delay, delay)\n\n    @property\n    def mean(self):\n        \"\"\"\n        Get the mean over `HP3456a.Register.count` measurements from\n        `HP3456a.Register.mean` when in `HP3456a.MathMode.statistic`.\n\n        :rtype: `float`\n        \"\"\"\n        return self._register_read(HP3456a.Register.mean)\n\n    @property\n    def variance(self):\n        \"\"\"\n        Get the variance over `HP3456a.Register.count` measurements from\n        `HP3456a.Register.variance` when in `HP3456a.MathMode.statistic`.\n\n        :rtype: `float`\n        \"\"\"\n        return self._register_read(HP3456a.Register.variance)\n\n    @property\n    def count(self):\n        \"\"\"\n        Get the number of measurements taken from `HP3456a.Register.count` when\n        in `HP3456a.MathMode.statistic`.\n\n        :rtype: `int`\n        \"\"\"\n        return int(self._register_read(HP3456a.Register.count))\n\n    @property\n    def lower(self):\n        \"\"\"\n        Get/set the value in `HP3456a.Register.lower`, which indicates the\n        lowest value measurement made while in `HP3456a.MathMode.statistic`, or\n        the lowest value preset for `HP3456a.MathMode.pass_fail`.\n\n        :type: `float`\n        \"\"\"\n        return self._register_read(HP3456a.Register.lower)\n\n    @lower.setter\n    def lower(self, value):\n        self._register_write(HP3456a.Register.lower, value)\n\n    @property\n    def upper(self):\n        \"\"\"\n        Get/set the value in `HP3456a.Register.upper`, which indicates the\n        highest value measurement made while in `HP3456a.MathMode.statistic`,\n        or the highest value preset for `HP3456a.MathMode.pass_fail`.\n\n        :type: `float`\n        :rtype: `float`\n        \"\"\"\n        return self._register_read(HP3456a.Register.upper)\n\n    @upper.setter\n    def upper(self, value):\n        return self._register_write(HP3456a.Register.upper, value)\n\n    @property\n    def r(self):\n        \"\"\"\n        Get/set the value in `HP3456a.Register.r`, which indicates the resistor\n        value used while in `HP3456a.MathMode.dbm` or the number of recalled\n        readings in reading storage mode.\n\n        :type: `float`\n        :rtype: `float`\n        \"\"\"\n        return self._register_read(HP3456a.Register.r)\n\n    @r.setter\n    def r(self, value):\n        self._register_write(HP3456a.Register.r, value)\n\n    @property\n    def y(self):\n        \"\"\"\n        Get/set the value in `HP3456a.Register.y` to be used in calculations\n        when in `HP3456a.MathMode.scale` or `HP3456a.MathMode.percent`.\n\n        :type: `float`\n        :rtype: `float`\n        \"\"\"\n        return self._register_read(HP3456a.Register.y)\n\n    @y.setter\n    def y(self, value):\n        self._register_write(HP3456a.Register.y, value)\n\n    @property\n    def z(self):\n        \"\"\"\n        Get/set the value in `HP3456a.Register.z` to be used in calculations\n        when in `HP3456a.MathMode.scale` or the first reading when in\n        `HP3456a.MathMode.statistic`.\n\n        :type: `float`\n        :rtype: `float`\n        \"\"\"\n        return self._register_read(HP3456a.Register.z)\n\n    @z.setter\n    def z(self, value):\n        self._register_write(HP3456a.Register.z, value)\n\n    @property\n    def input_range(self):\n        \"\"\"Set the input range to be used.\n\n        The `HP3456a` has separate ranges for `ohm` and for\n        `volt`. The range value sent to the instrument depends on\n        the unit set on the input range value. `auto` selects auto ranging.\n\n        :type: `~pint.Quantity`\n        \"\"\"\n        raise NotImplementedError\n\n    @input_range.setter\n    def input_range(self, value):\n        if isinstance(value, str):\n            if value.lower() == \"auto\":\n                self.sendcmd(\"R1W\")\n            else:\n                raise ValueError(\n                    \"Only 'auto' is acceptable when specifying \"\n                    \"the input range as a string.\"\n                )\n\n        elif isinstance(value, u.Quantity):\n            if value.units == u.volt:\n                valid = HP3456a.ValidRange.voltage.value\n                value = value.to(u.volt)\n            elif value.units == u.ohm:\n                valid = HP3456a.ValidRange.resistance.value\n                value = value.to(u.ohm)\n            else:\n                raise ValueError(\n                    \"Value {} not quantity.volt or quantity.ohm\" \"\".format(value)\n                )\n\n            value = float(value.magnitude)\n            if value not in valid:\n                raise ValueError(\n                    \"Value {} outside valid ranges \" \"{}\".format(value, valid)\n                )\n            value = valid.index(value) + 2\n            self.sendcmd(f\"R{value}W\")\n        else:\n            raise TypeError(\n                \"Range setting must be specified as a float, int, \"\n                \"or the string 'auto', got {}\".format(type(value))\n            )\n\n    @property\n    def relative(self):\n        \"\"\"\n        Enable or disable `HP3456a.MathMode.Null` on the instrument.\n\n        :type: `bool`\n        \"\"\"\n        return self._null\n\n    @relative.setter\n    def relative(self, value):\n        if value is True:\n            self._null = True\n            self.sendcmd(f\"M{HP3456a.MathMode.null.value}\")\n        elif value is False:\n            self._null = False\n            self.sendcmd(f\"M{HP3456a.MathMode.off.value}\")\n        else:\n            raise TypeError(\n                \"Relative setting must be specified as a bool, \"\n                \"got {}\".format(type(value))\n            )\n\n    # METHODS ##\n\n    def auto_range(self):\n        \"\"\"\n        Set input range to auto. The `HP3456a` should upscale when a reading\n        is at 120% and downscale when it below 11% full scale. Note that auto\n        ranging can increase the measurement time.\n        \"\"\"\n        self.input_range = \"auto\"\n\n    def fetch(self, mode=None):\n        \"\"\"Retrieve n measurements after the HP3456a has been instructed to\n        perform a series of similar measurements. Typically the mode, range,\n        nplc, analog filter, autozero is set along with the number of\n        measurements to take. The series is then started at the trigger\n        command.\n\n        Example usage:\n\n        >>> dmm.number_of_digits = 6\n        >>> dmm.auto_range()\n        >>> dmm.nplc = 1\n        >>> dmm.mode = dmm.Mode.resistance_2wire\n        >>> n = 100\n        >>> dmm.number_of_readings = n\n        >>> dmm.trigger()\n        >>> time.sleep(n * 0.04)\n        >>> v = dmm.fetch(dmm.Mode.resistance_2wire)\n        >>> print len(v)\n        10\n\n        :param mode: Desired measurement mode. If not specified, the previous\n            set mode will be used, but no measurement unit will be returned.\n\n        :type mode: `HP3456a.Mode`\n\n        :return: A series of measurements from the multimeter.\n        :rtype: `~pint.Quantity`\n        \"\"\"\n        if mode is not None:\n            units = UNITS[mode]\n        else:\n            units = 1\n\n        value = self.query(\"\", size=-1)\n        values = [float(x) * units for x in value.split(\",\")]\n        return values\n\n    def measure(self, mode=None):\n        \"\"\"Instruct the HP3456a to perform a one time measurement. The\n        measurement will use the current set registers for the measurement\n        (number_of_readings, number_of_digits, nplc, delay, mean, lower, upper,\n        y and z) and will immediately take place.\n\n        Note that using `HP3456a.measure()` will override the `trigger_mode` to\n        `HP3456a.TriggerMode.single`\n\n        Example usage:\n\n        >>> dmm = ik.hp.HP3456a.open_gpibusb(\"/dev/ttyUSB0\", 22)\n        >>> dmm.number_of_digits = 6\n        >>> dmm.nplc = 1\n        >>> print dmm.measure(dmm.Mode.resistance_2wire)\n\n        :param mode: Desired measurement mode. If not specified, the previous\n            set mode will be used, but no measurement unit will be\n            returned.\n\n        :type mode: `HP3456a.Mode`\n\n        :return: A measurement from the multimeter.\n        :rtype: `~pint.Quantity`\n\n        \"\"\"\n        if mode is not None:\n            modevalue = mode.value\n            units = UNITS[mode]\n        else:\n            modevalue = \"\"\n            units = 1\n\n        self.sendcmd(f\"{modevalue}W1STNT3\")\n\n        value = self.query(\"\", size=-1)\n        return float(value) * units\n\n    def _register_read(self, name):\n        \"\"\"\n        Read a register on the HP3456a.\n\n        :param name: The name of the register to read from\n        :type name: `HP3456a.Register`\n        :rtype: `float`\n        \"\"\"\n        try:\n            name = HP3456a.Register[name]\n        except KeyError:\n            pass\n        if not isinstance(name, HP3456a.Register):\n            raise TypeError(\n                \"register must be specified as a \"\n                \"HP3456a.Register, got {} \"\n                \"instead.\".format(name)\n            )\n        self.sendcmd(f\"RE{name.value}\")\n        time.sleep(0.1)\n        return float(self.query(\"\", size=-1))\n\n    def _register_write(self, name, value):\n        \"\"\"\n        Write a register on the HP3456a.\n\n        :param name: The name of the register to write to\n        :type name: `HP3456a.Register`\n        :type value: `float`\n        \"\"\"\n        try:\n            name = HP3456a.Register[name]\n        except KeyError:\n            pass\n        if not isinstance(name, HP3456a.Register):\n            raise TypeError(\n                \"register must be specified as a \"\n                \"HP3456a.Register, got {} \"\n                \"instead.\".format(name)\n            )\n        if name in [\n            HP3456a.Register.mean,\n            HP3456a.Register.variance,\n            HP3456a.Register.count,\n        ]:\n            raise ValueError(f\"register {name} is read only\")\n        self.sendcmd(f\"W{value}ST{name.value}\")\n        time.sleep(0.1)\n\n    def trigger(self):\n        \"\"\"\n        Signal a single manual trigger event to the `HP3456a`.\n        \"\"\"\n        self.sendcmd(\"T3\")\n\n\n# UNITS #######################################################################\n\nUNITS = {\n    None: 1,\n    HP3456a.Mode.dcv: u.volt,\n    HP3456a.Mode.acv: u.volt,\n    HP3456a.Mode.acvdcv: u.volt,\n    HP3456a.Mode.resistance_2wire: u.ohm,\n    HP3456a.Mode.resistance_4wire: u.ohm,\n    HP3456a.Mode.ratio_dcv_dcv: 1,\n    HP3456a.Mode.ratio_acv_dcv: 1,\n    HP3456a.Mode.ratio_acvdcv_dcv: 1,\n    HP3456a.Mode.oc_resistence_2wire: u.ohm,\n    HP3456a.Mode.oc_resistence_4wire: u.ohm,\n}\n"
  },
  {
    "path": "src/instruments/hp/hp6624a.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the HP6624a power supply\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom enum import Enum\n\nfrom instruments.abstract_instruments import PowerSupply\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import ProxyList, unitful_property, bool_property\n\n# CLASSES #####################################################################\n\n\nclass HP6624a(PowerSupply):\n    \"\"\"\n    The HP6624a is a multi-output power supply.\n\n    This class can also be used for HP662xa, where x=1,2,3,4,7. Note that some\n    models have fewer channels than the HP6624, and it is up to the user to take\n    this into account. This can be changed with the `~HP6624a.channel_count`\n    property.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> psu = ik.hp.HP6624a.open_gpibusb('/dev/ttyUSB0', 1)\n    >>> psu.channel[0].voltage = 10 # Sets channel 1 voltage to 10V.\n    \"\"\"\n\n    def __init__(self, filelike):\n        super().__init__(filelike)\n        self._channel_count = 4\n\n    # INNER CLASSES #\n\n    class Channel(PowerSupply.Channel):\n        \"\"\"\n        Class representing a power output channel on the HP6624a.\n\n        .. warning:: This class should NOT be manually created by the user. It is\n            designed to be initialized by the `HP6624a` class.\n        \"\"\"\n\n        def __init__(self, hp, idx):\n            self._hp = hp\n            self._idx = idx + 1\n\n        # COMMUNICATION METHODS #\n\n        def _format_cmd(self, cmd):\n            cmd = cmd.split(\" \")\n            if len(cmd) == 1:\n                cmd = f\"{cmd[0]} {self._idx}\"\n            else:\n                cmd = \"{cmd} {idx},{value}\".format(\n                    cmd=cmd[0], idx=self._idx, value=cmd[1]\n                )\n            return cmd\n\n        def sendcmd(self, cmd):\n            \"\"\"\n            Function used to send a command to the instrument while wrapping\n            the command with the neccessary identifier for the channel.\n\n            :param str cmd: Command that will be sent to the instrument after\n                being prefixed with the channel identifier\n            \"\"\"\n            cmd = self._format_cmd(cmd)\n            self._hp.sendcmd(cmd)\n\n        def query(self, cmd):\n            \"\"\"\n            Function used to send a command to the instrument while wrapping\n            the command with the neccessary identifier for the channel.\n\n            :param str cmd: Command that will be sent to the instrument after\n                being prefixed with the channel identifier\n            :return: The result from the query\n            :rtype: `str`\n            \"\"\"\n            cmd = self._format_cmd(cmd)\n            return self._hp.query(cmd)\n\n        # PROPERTIES #\n\n        @property\n        def mode(self):\n            \"\"\"\n            Gets/sets the mode for the specified channel.\n            \"\"\"\n            raise NotImplementedError\n\n        @mode.setter\n        def mode(self, newval):\n            raise NotImplementedError\n\n        voltage = unitful_property(\n            \"VSET\",\n            u.volt,\n            set_fmt=\"{} {:.1f}\",\n            output_decoration=float,\n            doc=\"\"\"\n            Gets/sets the voltage of the specified channel. If the device is in\n            constant current mode, this sets the voltage limit.\n\n            Note there is no bounds checking on the value specified.\n\n            :units: As specified, or assumed to be :math:`\\\\text{V}` otherwise.\n            :type: `float` or `~pint.Quantity`\n            \"\"\",\n        )\n\n        current = unitful_property(\n            \"ISET\",\n            u.amp,\n            set_fmt=\"{} {:.1f}\",\n            output_decoration=float,\n            doc=\"\"\"\n            Gets/sets the current of the specified channel. If the device is in\n            constant voltage mode, this sets the current limit.\n\n            Note there is no bounds checking on the value specified.\n\n            :units: As specified, or assumed to be :math:`\\\\text{A}` otherwise.\n            :type: `float` or `~pint.Quantity`\n            \"\"\",\n        )\n\n        voltage_sense = unitful_property(\n            \"VOUT\",\n            u.volt,\n            readonly=True,\n            doc=\"\"\"\n            Gets the actual voltage as measured by the sense wires for the\n            specified channel.\n\n            :units: :math:`\\\\text{V}` (volts)\n            :rtype: `~pint.Quantity`\n            \"\"\",\n        )\n\n        current_sense = unitful_property(\n            \"IOUT\",\n            u.amp,\n            readonly=True,\n            doc=\"\"\"\n            Gets the actual output current as measured by the instrument for\n            the specified channel.\n\n            :units: :math:`\\\\text{A}` (amps)\n            :rtype: `~pint.Quantity`\n            \"\"\",\n        )\n\n        overvoltage = unitful_property(\n            \"OVSET\",\n            u.volt,\n            set_fmt=\"{} {:.1f}\",\n            output_decoration=float,\n            doc=\"\"\"\n            Gets/sets the overvoltage protection setting for the specified channel.\n\n            Note there is no bounds checking on the value specified.\n\n            :units: As specified, or assumed to be :math:`\\\\text{V}` otherwise.\n            :type: `float` or `~pint.Quantity`\n            \"\"\",\n        )\n\n        overcurrent = bool_property(\n            \"OVP\",\n            inst_true=\"1\",\n            inst_false=\"0\",\n            doc=\"\"\"\n            Gets/sets the overcurrent protection setting for the specified channel.\n\n            This is a toggle setting. It is either on or off.\n\n            :type: `bool`\n            \"\"\",\n        )\n\n        output = bool_property(\n            \"OUT\",\n            inst_true=\"1\",\n            inst_false=\"0\",\n            doc=\"\"\"\n            Gets/sets the outputting status of the specified channel.\n\n            This is a toggle setting. True will turn on the channel output\n            while False will turn it off.\n\n            :type: `bool`\n            \"\"\",\n        )\n\n        # METHODS ##\n\n        def reset(self):\n            \"\"\"\n            Reset overvoltage and overcurrent errors to resume operation.\n            \"\"\"\n            self.sendcmd(\"OVRST\")\n            self.sendcmd(\"OCRST\")\n\n    # ENUMS #\n\n    class Mode(Enum):\n        \"\"\"\n        Enum holding typical valid output modes for a power supply.\n\n        However, for the HP6624a I believe that it is only capable of\n        constant-voltage output, so this class current does not do anything\n        and is just a placeholder.\n        \"\"\"\n\n        voltage = 0\n        current = 0\n\n    # PROPERTIES ##\n\n    @property\n    def channel(self):\n        \"\"\"\n        Gets a specific channel object. The desired channel is specified like\n        one would access a list.\n\n        :rtype: `HP6624a.Channel`\n\n        .. seealso::\n            `HP6624a` for example using this property.\n        \"\"\"\n        return ProxyList(self, HP6624a.Channel, range(self.channel_count))\n\n    @property\n    def voltage(self):\n        \"\"\"\n        Gets/sets the voltage for all four channels.\n\n        :units: As specified (if a `~pint.Quantity`) or assumed to be\n            of units Volts.\n        :type: `tuple`[`~pint.Quantity`, ...] with units Volt\n        \"\"\"\n        return tuple(self.channel[i].voltage for i in range(self.channel_count))\n\n    @voltage.setter\n    def voltage(self, newval):\n        if isinstance(newval, (list, tuple)):\n            if len(newval) is not self.channel_count:\n                raise ValueError(\n                    \"When specifying the voltage for all channels \"\n                    \"as a list or tuple, it must be of \"\n                    \"length {}.\".format(self.channel_count)\n                )\n            for i in range(self.channel_count):\n                self.channel[i].voltage = newval[i]\n        else:\n            for i in range(self.channel_count):\n                self.channel[i].voltage = newval\n\n    @property\n    def current(self):\n        \"\"\"\n        Gets/sets the current for all four channels.\n\n        :units: As specified (if a `~pint.Quantity`) or assumed to be\n            of units Amps.\n        :type: `tuple`[`~pint.Quantity`, ...] with units Amp\n        \"\"\"\n        return tuple(self.channel[i].current for i in range(self.channel_count))\n\n    @current.setter\n    def current(self, newval):\n        if isinstance(newval, (list, tuple)):\n            if len(newval) is not self.channel_count:\n                raise ValueError(\n                    \"When specifying the current for all channels \"\n                    \"as a list or tuple, it must be of \"\n                    \"length {}.\".format(self.channel_count)\n                )\n            for i in range(self.channel_count):\n                self.channel[i].current = newval[i]\n        else:\n            for i in range(self.channel_count):\n                self.channel[i].current = newval\n\n    @property\n    def voltage_sense(self):\n        \"\"\"\n        Gets the actual voltage as measured by the sense wires for all channels.\n\n        :units: :math:`\\\\text{V}` (volts)\n        :rtype: `tuple`[`~pint.Quantity`, ...]\n        \"\"\"\n        return tuple(self.channel[i].voltage_sense for i in range(self.channel_count))\n\n    @property\n    def current_sense(self):\n        \"\"\"\n        Gets the actual current as measured by the instrument for all channels.\n\n        :units: :math:`\\\\text{A}` (amps)\n        :rtype: `tuple`[`~pint.Quantity`, ...]\n        \"\"\"\n        return tuple(self.channel[i].current_sense for i in range(self.channel_count))\n\n    @property\n    def channel_count(self):\n        \"\"\"\n        Gets/sets the number of output channels available for the connected\n        power supply.\n\n        :type: `int`\n        \"\"\"\n        return self._channel_count\n\n    @channel_count.setter\n    def channel_count(self, newval):\n        if not isinstance(newval, int):\n            raise TypeError(\"Channel count must be specified as an integer.\")\n        if newval < 1:\n            raise ValueError(\"Channel count must be >=1\")\n        self._channel_count = newval\n\n    # METHODS ##\n\n    def clear(self):\n        \"\"\"\n        Taken from the manual:\n\n        Return the power supply to its power-on state and all parameters are\n        returned to their initial power-on values except the following:\n\n        #) The store/recall registers are not cleared.\n        #) The power supply remains addressed to listen.\n        #) The PON bit in the serial poll register is cleared.\n        \"\"\"\n        self.sendcmd(\"CLR\")\n"
  },
  {
    "path": "src/instruments/hp/hp6632b.py",
    "content": "#!/usr/bin/env python\n#\n# hp6632b.py: Python class for the HP6632b power supply\n#\n# © 2014 Willem Dijkstra (wpd@xs4all.nl).\n#\n# This file is a part of the InstrumentKit project.\n# Licensed under the AGPL version 3.\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Affero General Public License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with this program. If not, see <http://www.gnu.org/licenses/>.\n#\n\"\"\"\nDriver for the HP6632b DC power supply\n\nOriginally contributed and copyright held by Willem Dijkstra (wpd@xs4all.nl)\n\nAn unrestricted license has been provided to the maintainers of the Instrument\nKit project.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom enum import Enum, IntEnum\n\nfrom instruments.generic_scpi.scpi_instrument import SCPIInstrument\nfrom instruments.hp.hp6652a import HP6652a\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import (\n    unitful_property,\n    unitless_property,\n    bool_property,\n    enum_property,\n    int_property,\n)\n\n# CLASSES #####################################################################\n\n\nclass HP6632b(SCPIInstrument, HP6652a):\n    \"\"\"\n    The HP6632b is a system dc power supply with an output rating of 0-20V/0-5A,\n    precision low current measurement and low output noise.\n\n    According to the manual this class MIGHT be usable for any HP power supply\n    with a model number\n\n    - HP663Xb with X in {1, 2, 3, 4},\n    - HP661Xc with X in {1,2, 3, 4} and\n    - HP663X2A for X in {1, 3}, without the additional measurement capabilities.\n\n    HOWEVER, it has only been tested by the author with HP6632b supplies.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> psu = ik.hp.HP6632b.open_gpibusb('/dev/ttyUSB0', 6)\n    >>> psu.voltage = 10             # Sets voltage to 10V.\n    >>> psu.output = True            # Enable output\n    >>> psu.voltage\n    array(10.0) * V\n    >>> psu.voltage_trigger = 20     # Set transient trigger voltage\n    >>> psu.init_output_trigger()    # Prime instrument to initiated state, ready for trigger\n    >>> psu.trigger()                # Send trigger\n    >>> psu.voltage\n    array(10.0) * V\n    \"\"\"\n\n    # ENUMS ##\n\n    class ALCBandwidth(IntEnum):\n        \"\"\"\n        Enum containing valid ALC bandwidth modes for the hp6632b\n        \"\"\"\n\n        normal = 1.5e4\n        fast = 6e4\n\n    class DigitalFunction(Enum):\n        \"\"\"\n        Enum containing valid digital function modes for the hp6632b\n        \"\"\"\n\n        remote_inhibit = \"RIDF\"\n        data = \"DIG\"\n\n    class DFISource(Enum):\n        \"\"\"\n        Enum containing valid DFI sources for the hp6632b\n        \"\"\"\n\n        questionable = \"QUES\"\n        operation = \"OPER\"\n        event_status_bit = \"ESB\"\n        request_service_bit = \"RQS\"\n        off = \"OFF\"\n\n    class ErrorCodes(IntEnum):\n        \"\"\"\n        Enum containing generic-SCPI error codes along with codes specific\n        to the HP6632b.\n        \"\"\"\n\n        no_error = 0\n\n        # -100 BLOCK: COMMAND ERRORS ##\n        command_error = -100\n        invalid_character = -101\n        syntax_error = -102\n        invalid_separator = -103\n        data_type_error = -104\n        get_not_allowed = -105\n        # -106 and -107 not specified.\n        parameter_not_allowed = -108\n        missing_parameter = -109\n        command_header_error = -110\n        header_separator_error = -111\n        program_mnemonic_too_long = -112\n        undefined_header = -113\n        header_suffix_out_of_range = -114\n        unexpected_number_of_parameters = -115\n        numeric_data_error = -120\n        invalid_character_in_number = -121\n        exponent_too_large = -123\n        too_many_digits = -124\n        numeric_data_not_allowed = -128\n        suffix_error = -130\n        invalid_suffix = -131\n        suffix_too_long = -134\n        suffix_not_allowed = -138\n        character_data_error = -140\n        invalid_character_data = -141\n        character_data_too_long = -144\n        character_data_not_allowed = -148\n        string_data_error = -150\n        invalid_string_data = -151\n        string_data_not_allowed = -158\n        block_data_error = -160\n        invalid_block_data = -161\n        block_data_not_allowed = -168\n        expression_error = -170\n        invalid_expression = -171\n        expression_not_allowed = -178\n        macro_error_180 = -180\n        invalid_outside_macro_definition = -181\n        invalid_inside_macro_definition = -183\n        macro_parameter_error = -184\n\n        # -200 BLOCK: EXECUTION ERRORS ##\n        # -300 BLOCK: DEVICE-SPECIFIC ERRORS ##\n        # Note that device-specific errors also include all positive numbers.\n        # -400 BLOCK: QUERY ERRORS ##\n\n        # OTHER ERRORS ##\n\n        #: Raised when the instrument detects that it has been turned from\n        #: off to on.\n        power_on = -500  # Yes, SCPI 1999 defines the instrument turning on as\n        # an error. Yes, this makes my brain hurt.\n        user_request_event = -600\n        request_control_event = -700\n        operation_complete = -800\n\n        # -200 BLOCK: EXECUTION ERRORS\n        execution_error = -200\n        data_out_of_range = -222\n        too_much_data = -223\n        illegal_parameter_value = -224\n        out_of_memory = -225\n        macro_error_270 = -270\n        macro_execution_error = -272\n        illegal_macro_label = -273\n        macro_recursion_error = -276\n        macro_redefinition_not_allowed = -277\n\n        # -300 BLOCK: DEVICE-SPECIFIC ERRORS\n        system_error = -310\n        too_many_errors = -350\n\n        # -400 BLOCK: QUERY ERRORS\n        query_error = -400\n        query_interrupted = -410\n        query_unterminated = -420\n        query_deadlocked = -430\n        query_unterminated_after_indefinite_response = -440\n\n        # DEVICE ERRORS\n        ram_rd0_checksum_failed = 1\n        ram_config_checksum_failed = 2\n        ram_cal_checksum_failed = 3\n        ram_state_checksum_failed = 4\n        ram_rst_checksum_failed = 5\n        ram_selftest = 10\n        vdac_idac_selftest1 = 11\n        vdac_idac_selftest2 = 12\n        vdac_idac_selftest3 = 13\n        vdac_idac_selftest4 = 14\n        ovdac_selftest = 15\n        digital_io_selftest = 80\n        ingrd_recv_buffer_overrun = 213\n        rs232_recv_framing_error = 216\n        rs232_recv_parity_error = 217\n        rs232_recv_overrun_error = 218\n        front_panel_uart_overrun = 220\n        front_panel_uart_framing = 221\n        front_panel_uart_parity = 222\n        front_panel_uart_buffer_overrun = 223\n        front_panel_uart_timeout = 224\n        cal_switch_prevents_cal = 401\n        cal_password_incorrect = 402\n        cal_not_enabled = 403\n        computed_readback_cal_const_incorrect = 404\n        computed_prog_cal_constants_incorrect = 405\n        incorrect_seq_cal_commands = 406\n        cv_or_cc_status_incorrect = 407\n        output_mode_must_be_normal = 408\n        too_many_sweep_points = 601\n        command_only_applic_rs232 = 602\n        curr_or_volt_fetch_incompat_with_last_acq = 603\n        measurement_overrange = 604\n\n    class RemoteInhibit(Enum):\n        \"\"\"\n        Enum containing vlaid remote inhibit modes for the hp6632b.\n        \"\"\"\n\n        latching = \"LATC\"\n        live = \"LIVE\"\n        off = \"OFF\"\n\n    class SenseWindow(Enum):\n        \"\"\"\n        Enum containing valid sense window modes for the hp6632b.\n        \"\"\"\n\n        hanning = \"HANN\"\n        rectangular = \"RECT\"\n\n    # PROPERTIES ##\n\n    voltage_alc_bandwidth = enum_property(\n        \"VOLT:ALC:BAND\",\n        ALCBandwidth,\n        input_decoration=lambda x: int(float(x)),\n        readonly=True,\n        doc=\"\"\"\n        Get the \"automatic level control bandwidth\" which for the HP66332A and\n        HP6631-6634 determines if the output capacitor is in circuit. `Normal`\n        denotes that it is, and `Fast` denotes that it is not.\n\n        :type: `~HP6632b.ALCBandwidth`\n        \"\"\",\n    )\n\n    voltage_trigger = unitful_property(\n        \"VOLT:TRIG\",\n        u.volt,\n        doc=\"\"\"\n        Gets/sets the pending triggered output voltage.\n\n        Note there is no bounds checking on the value specified.\n\n        :units: As specified, or assumed to be :math:`\\\\text{V}` otherwise.\n        :type: `float` or `~pint.Quantity`\n        \"\"\",\n    )\n\n    current_trigger = unitful_property(\n        \"CURR:TRIG\",\n        u.amp,\n        doc=\"\"\"\n        Gets/sets the pending triggered output current.\n\n        Note there is no bounds checking on the value specified.\n\n        :units: As specified, or assumed to be :math:`\\\\text{A}` otherwise.\n        :type: `float` or `~pint.Quantity`\n        \"\"\",\n    )\n\n    init_output_continuous = bool_property(\n        \"INIT:CONT:SEQ1\",\n        \"1\",\n        \"0\",\n        doc=\"\"\"\n        Get/set the continuous output trigger. In this state, the power supply\n        will remain in the initiated state, and respond continuously on new\n        incoming triggers by applying the set voltage and current trigger\n        levels.\n\n        :type: `bool`\n        \"\"\",\n    )\n\n    current_sense_range = unitful_property(\n        \"SENS:CURR:RANGE\",\n        u.ampere,\n        doc=\"\"\"\n        Get/set the sense current range by the current max value.\n\n        A current of 20mA or less selects the low-current range, a current\n        value higher than that selects the high-current range. The low current\n        range increases the low current measurement sensitivity and accuracy.\n\n        :units: As specified, or assumed to be :math:`\\\\text{A}` otherwise.\n        :type: `float` or `~pint.Quantity`\n        \"\"\",\n    )\n\n    output_dfi = bool_property(\n        \"OUTP:DFI\",\n        \"1\",\n        \"0\",\n        doc=\"\"\"\n        Get/set the discrete fault indicator (DFI) output from the dc\n        source. The DFI is an open-collector logic signal connected to the read\n        panel FLT connection, that can be used to signal external devices when\n        a fault is detected.\n\n        :type: `bool`\n        \"\"\",\n    )\n\n    output_dfi_source = enum_property(\n        \"OUTP:DFI:SOUR\",\n        DFISource,\n        doc=\"\"\"\n        Get/set the source for discrete fault indicator (DFI) events.\n\n        :type: `~HP6632b.DFISource`\n        \"\"\",\n    )\n\n    output_remote_inhibit = enum_property(\n        \"OUTP:RI:MODE\",\n        RemoteInhibit,\n        doc=\"\"\"\n        Get/set the remote inhibit signal. Remote inhibit is an external,\n        chassis-referenced logic signal routed through the rear panel INH\n        connection, which allows an external device to signal a fault.\n\n        :type: `~HP6632b.RemoteInhibit`\n        \"\"\",\n    )\n\n    digital_function = enum_property(\n        \"DIG:FUNC\",\n        DigitalFunction,\n        doc=\"\"\"\n        Get/set the inhibit+fault port to digital in+out or vice-versa.\n\n        :type: `~HP6632b.DigitalFunction`\n        \"\"\",\n    )\n\n    digital_data = int_property(\n        \"DIG:DATA\",\n        valid_set=range(0, 8),\n        doc=\"\"\"\n        Get/set digital in+out port to data. Data can be an integer from 0-7.\n\n        :type: `int`\n        \"\"\",\n    )\n\n    sense_sweep_points = unitless_property(\n        \"SENS:SWE:POIN\",\n        doc=\"\"\"\n        Get/set the number of points in a measurement sweep.\n\n        :type: `int`\n        \"\"\",\n    )\n\n    sense_sweep_interval = unitful_property(\n        \"SENS:SWE:TINT\",\n        u.second,\n        doc=\"\"\"\n        Get/set the digitizer sample spacing. Can be set from 15.6 us to 31200\n        seconds, the interval will be rounded to the nearest 15.6 us increment.\n\n        :units: As specified, or assumed to be :math:`\\\\text{s}` otherwise.\n        :type: `float` or `~pint.Quantity`\n        \"\"\",\n    )\n\n    sense_window = enum_property(\n        \"SENS:WIND\",\n        SenseWindow,\n        doc=\"\"\"\n        Get/set the measurement window function.\n\n        :type: `~HP6632b.SenseWindow`\n        \"\"\",\n    )\n\n    output_protection_delay = unitful_property(\n        \"OUTP:PROT:DEL\",\n        u.second,\n        doc=\"\"\"\n        Get/set the time between programming of an output change that produces\n        a constant current condition and the recording of that condigition in\n        the Operation Status Condition register. This command also delays over\n        current protection, but not overvoltage protection.\n\n        :units: As specified, or assumed to be :math:`\\\\text{s}` otherwise.\n        :type: `float` or `~pint.Quantity`\n        \"\"\",\n    )\n\n    # FUNCTIONS ##\n\n    def init_output_trigger(self):\n        \"\"\"\n        Set the output trigger system to the initiated state. In this state,\n        the power supply will respond to the next output trigger command.\n        \"\"\"\n        self.sendcmd(\"INIT:NAME TRAN\")\n\n    def abort_output_trigger(self):\n        \"\"\"\n        Set the output trigger system to the idle state.\n        \"\"\"\n        self.sendcmd(\"ABORT\")\n\n    # SCPIInstrument commands that need local overrides\n\n    @property\n    def line_frequency(self):\n        raise NotImplementedError\n\n    @line_frequency.setter\n    def line_frequency(self, newval):\n        raise NotImplementedError\n\n    @property\n    def display_brightness(self):\n        raise NotImplementedError\n\n    @display_brightness.setter\n    def display_brightness(self, newval):\n        raise NotImplementedError\n\n    @property\n    def display_contrast(self):\n        raise NotImplementedError\n\n    @display_contrast.setter\n    def display_contrast(self, newval):\n        raise NotImplementedError\n\n    def check_error_queue(self):\n        \"\"\"\n        Checks and clears the error queue for this device, returning a list of\n        :class:`~SCPIInstrument.ErrorCodes` or `int` elements for each error\n        reported by the connected instrument.\n        \"\"\"\n        done = False\n        result = []\n        while not done:\n            err = int(self.query(\"SYST:ERR?\").split(\",\")[0])\n            if err == self.ErrorCodes.no_error:\n                done = True\n            else:\n                result.append(\n                    self.ErrorCodes(err)\n                    if any(err == item.value for item in self.ErrorCodes)\n                    else err\n                )\n\n        return result\n"
  },
  {
    "path": "src/instruments/hp/hp6652a.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nDriver for the HP6652a single output power supply\n\nOriginally contributed by Wil Langford (wil.langford+instrumentkit@gmail.com)\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nfrom instruments.units import ureg as u\n\nfrom instruments.abstract_instruments import PowerSupply\nfrom instruments.util_fns import unitful_property, bool_property\n\n# CLASSES #####################################################################\n\n\nclass HP6652a(PowerSupply, PowerSupply.Channel):\n    \"\"\"\n    The HP6652a is a single output power supply.\n\n    Because it is a single channel output, this object inherits from both\n    PowerSupply and PowerSupply.Channel.\n\n    According to the manual, this class MIGHT be usable for any HP power supply\n    with a model number HP66XYA, where X is in {4,5,7,8,9} and Y is a digit(?).\n    (e.g. HP6652A and HP6671A)\n\n    HOWEVER, it has only been tested by the author with an HP6652A power supply.\n\n    Example usage:\n\n    >>> import time\n    >>> import instruments as ik\n    >>> psu = ik.hp.HP6652a.open_serial('/dev/ttyUSB0', 57600)\n    >>> psu.voltage = 3 # Sets output voltage to 3V.\n    >>> psu.output = True\n    >>> psu.voltage\n    array(3.0) * V\n    >>> psu.voltage_sense < 5\n    True\n    >>> psu.output = False\n    >>> psu.voltage_sense < 1\n    True\n    >>> psu.display_textmode=True\n    >>> psu.display_text(\"test GOOD\")\n    'TEST GOOD'\n    >>> time.sleep(5)\n    >>> psu.display_textmode=False\n    \"\"\"\n\n    # ENUMS ##\n\n    # I don't know of any possible enumerations supported\n    # by this instrument.\n\n    # PROPERTIES ##\n\n    voltage = unitful_property(\n        \"VOLT\",\n        u.volt,\n        doc=\"\"\"\n        Gets/sets the output voltage.\n\n        Note there is no bounds checking on the value specified.\n\n        :units: As specified, or assumed to be :math:`\\\\text{V}` otherwise.\n        :type: `float` or `~pint.Quantity`\n        \"\"\",\n    )\n\n    current = unitful_property(\n        \"CURR\",\n        u.amp,\n        doc=\"\"\"\n        Gets/sets the output current.\n\n        Note there is no bounds checking on the value specified.\n\n        :units: As specified, or assumed to be :math:`\\\\text{A}` otherwise.\n        :type: `float` or `~pint.Quantity`\n        \"\"\",\n    )\n\n    voltage_sense = unitful_property(\n        \"MEAS:VOLT\",\n        u.volt,\n        readonly=True,\n        doc=\"\"\"\n        Gets the actual output voltage as measured by the sense wires.\n\n        :units: :math:`\\\\text{V}` (volts)\n        :rtype: `~pint.Quantity`\n        \"\"\",\n    )\n\n    current_sense = unitful_property(\n        \"MEAS:CURR\",\n        u.amp,\n        readonly=True,\n        doc=\"\"\"\n        Gets the actual output current as measured by the sense wires.\n\n        :units: :math:`\\\\text{A}` (amps)\n        :rtype: `~pint.Quantity`\n        \"\"\",\n    )\n\n    overvoltage = unitful_property(\n        \"VOLT:PROT\",\n        u.volt,\n        doc=\"\"\"\n        Gets/sets the overvoltage protection setting in volts.\n\n        Note there is no bounds checking on the value specified.\n\n        :units: As specified, or assumed to be :math:`\\\\text{V}` otherwise.\n        :type: `float` or `~pint.Quantity`\n        \"\"\",\n    )\n\n    overcurrent = bool_property(\n        \"CURR:PROT:STAT\",\n        inst_true=\"1\",\n        inst_false=\"0\",\n        doc=\"\"\"\n        Gets/sets the overcurrent protection setting.\n\n        This is a toggle setting. It is either on or off.\n\n        :type: `bool`\n        \"\"\",\n    )\n\n    output = bool_property(\n        \"OUTP\",\n        inst_true=\"1\",\n        inst_false=\"0\",\n        doc=\"\"\"\n        Gets/sets the output status.\n\n        This is a toggle setting. True will turn on the instrument output\n        while False will turn it off.\n\n        :type: `bool`\n        \"\"\",\n    )\n\n    display_textmode = bool_property(\n        \"DISP:MODE\",\n        inst_true=\"TEXT\",\n        inst_false=\"NORM\",\n        doc=\"\"\"\n        Gets/sets the display mode.\n\n        This is a toggle setting. True will allow text to be sent to the\n        front-panel LCD with the display_text() method.  False returns to\n        the normal display mode.\n\n        .. seealso:: `~HP6652a.display_text()`\n\n        :type: `bool`\n        \"\"\",\n    )\n\n    @property\n    def name(self):\n        \"\"\"\n        The name of the connected instrument, as reported by the\n        standard SCPI command ``*IDN?``.\n\n        :rtype: `str`\n        \"\"\"\n        idn_string = self.query(\"*IDN?\")\n        idn_list = idn_string.split(\",\")\n        return \" \".join(idn_list[:2])\n\n    @property\n    def mode(self):\n        \"\"\"\n        Unimplemented.\n        \"\"\"\n        raise NotImplementedError(\"Setting the mode is not implemented.\")\n\n    @mode.setter\n    def mode(self, newval):\n        \"\"\"\n        Unimplemented.\n        \"\"\"\n        raise NotImplementedError(\"Setting the mode is not implemented.\")\n\n    # METHODS ##\n\n    def reset(self):\n        \"\"\"\n        Reset overvoltage and overcurrent errors to resume operation.\n        \"\"\"\n        self.sendcmd(\"OUTP:PROT:CLE\")\n\n    def display_text(self, text_to_display):\n        \"\"\"\n        Sends up to 12 (uppercase) alphanumerics to be sent to the\n        front-panel LCD display.  Some punctuation is allowed, and\n        can affect the number of characters allowed.  See the\n        programming manual for the HP6652A for more details.\n\n        Because the maximum valid number of possible characters is\n        15 (counting the possible use of punctuation), the text will\n        be truncated to 15 characters before the command is sent to\n        the instrument.\n\n        If an invalid string is sent, the command will fail silently.\n        Any lowercase letters in the text_to_display will be converted\n        to uppercase before the command is sent to the instrument.\n\n        No attempt to validate punctuation is currently made.\n\n        Because the string cannot be read back from the instrument,\n        this method returns the actual string value sent.\n\n        :param text_to_display: The text that you wish to have displayed\n            on the front-panel LCD\n        :type text_to_display: 'str'\n        :return: Returns the version of the provided string that will\n            be send to the instrument. This means it will be truncated to\n            a maximum of 15 characters and changed to all upper case.\n        :rtype: `str`\n        \"\"\"\n\n        if len(text_to_display) > 15:\n            text_to_display = text_to_display[:15]\n        text_to_display = text_to_display.upper()\n\n        self.sendcmd(f'DISP:TEXT \"{text_to_display}\"')\n\n        return text_to_display\n\n    @property\n    def channel(self):\n        \"\"\"\n        Return the channel (which in this case is the entire instrument, since\n        there is only 1 channel on the HP6652a.)\n\n        :rtype: 'tuple' of length 1 containing a reference back to the parent\n            HP6652a object.\n        \"\"\"\n        return (self,)\n"
  },
  {
    "path": "src/instruments/hp/hpe3631a.py",
    "content": "#!/usr/bin/env python\n#\n# hpe3631a.py: Driver for the HP E3631A Power Supply\n#\n# © 2019 Francois Drielsma (francois.drielsma@gmail.com).\n#\n# This file is a part of the InstrumentKit project.\n# Licensed under the AGPL version 3.\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Affero General Public License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with this program. If not, see <http://www.gnu.org/licenses/>.\n#\n\"\"\"\nDriver for the HP E3631A Power Supply\n\nOriginally contributed and copyright held by Francois Drielsma\n(francois.drielsma@gmail.com)\n\nAn unrestricted license has been provided to the maintainers of the Instrument\nKit project.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nimport time\n\nfrom instruments.units import ureg as u\n\nfrom instruments.abstract_instruments import PowerSupply\nfrom instruments.generic_scpi import SCPIInstrument\nfrom instruments.util_fns import (\n    int_property,\n    unitful_property,\n    bounded_unitful_property,\n    bool_property,\n    split_unit_str,\n    assume_units,\n)\n\n# CLASSES #####################################################################\n\n\nclass HPe3631a(PowerSupply, PowerSupply.Channel, SCPIInstrument):\n    \"\"\"\n    The HPe3631a is a three channels voltage/current supply.\n    - Channel 1 is a positive +6V/5A channel (P6V)\n    - Channel 2 is a positive +25V/1A channel (P25V)\n    - Channel 3 is a negative -25V/1A channel (N25V)\n\n    This module is designed for the power supply to be set to\n    a specific channel and remain set afterwards as this device\n    does not offer commands to set or read multiple channels\n    without calling the channel set command each time (0.5s). It is\n    possible to call a specific channel through psu.channel[idx],\n    which will automatically reset the channel id, when necessary.\n\n    This module is likely to work as is for the Agilent E3631 and\n    Keysight E3631 which seem to be rebranded but identical devices.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> psu = ik.hp.HPe3631a.open_gpibusb(\"/dev/ttyUSB0\", 10)\n    >>> psu.channelid = 2           # Sets channel to P25V\n    >>> psu.voltage = 12.5          # Sets voltage to 12.5V\n    >>> psu.voltage                 # Reads back set voltage\n    array(12.5) * V\n    >>> psu.voltage_sense           # Reads back sensed voltage\n    array(12.501) * V\n    \"\"\"\n\n    def __init__(self, filelike):\n        super().__init__(filelike)\n        self.sendcmd(\"SYST:REM\")  # Puts the device in remote operation\n        time.sleep(0.1)\n\n    # INNER CLASSES #\n\n    class Channel:\n        \"\"\"\n        Class representing a power output channel on the HPe3631a.\n\n        .. warning:: This class should NOT be manually created by the user. It is\n            designed to be initialized by the `HPe3631a` class.\n        \"\"\"\n\n        def __init__(self, parent, valid_set):\n            self._parent = parent\n            self._valid_set = valid_set\n\n        def __getitem__(self, idx):\n            # Check that the channel is available. If it is, set the\n            # channelid of the device and return the device object.\n            if self._parent.channelid != idx:\n                self._parent.channelid = idx\n                time.sleep(0.5)\n            return self._parent\n\n        def __len__(self):\n            return len(self._valid_set)\n\n    # PROPERTIES ##\n\n    @property\n    def channel(self):\n        \"\"\"\n        Gets a specific channel object. The desired channel is specified like\n        one would access a list.\n\n        :rtype: `HPe3631a.Channel`\n\n        .. seealso::\n            `HPe3631a` for example using this property.\n        \"\"\"\n        return self.Channel(self, [1, 2, 3])\n\n    @property\n    def mode(self):\n        \"\"\"\n        Gets/sets the mode for the specified channel.\n\n        The constant-voltage/constant-current modes of the power supply\n        are selected automatically depending on the load (resistance)\n        connected to the power supply. If the load greater than the set\n        V/I is connected, a voltage V is applied and the current flowing\n        is lower than I. If the load is smaller than V/I, the set current\n        I acts as a current limiter and the voltage is lower than V.\n        \"\"\"\n        raise AttributeError(\"The `HPe3631a` sets its mode automatically\")\n\n    channelid = int_property(\n        \"INST:NSEL\",\n        valid_set=[1, 2, 3],\n        doc=\"\"\"\n        Gets/Sets the active channel ID.\n\n        :type: `HPe3631a.ChannelType`\n        \"\"\",\n    )\n\n    @property\n    def voltage(self):\n        \"\"\"\n        Gets/sets the output voltage of the source.\n\n        :units: As specified, or assumed to be :math:`\\\\text{V}` otherwise.\n        :type: `float` or `~pint.Quantity`\n        \"\"\"\n        raw = self.query(\"SOUR:VOLT?\")\n        return u.Quantity(*split_unit_str(raw, u.volt)).to(u.volt)\n\n    @voltage.setter\n    def voltage(self, newval):\n        \"\"\"\n        Gets/sets the output voltage of the source.\n\n        :units: As specified, or assumed to be :math:`\\\\text{V}` otherwise.\n        :type: `float` or `~pint.Quantity`\n        \"\"\"\n        min_value, max_value = self.voltage_range\n        if newval < min_value:\n            raise ValueError(\n                \"Voltage quantity is too low. Got {}, minimum \"\n                \"value is {}\".format(newval, min_value)\n            )\n\n        if newval > max_value:\n            raise ValueError(\n                \"Voltage quantity is too high. Got {}, maximum \"\n                \"value is {}\".format(newval, max_value)\n            )\n\n        # Rescale to the correct unit before printing. This will also\n        # catch bad units.\n        strval = f\"{assume_units(newval, u.volt).to(u.volt).magnitude:e}\"\n        self.sendcmd(f\"SOUR:VOLT {strval}\")\n\n    @property\n    def voltage_min(self):\n        \"\"\"\n        Gets the minimum voltage for the current channel.\n\n        :units: :math:`\\\\text{V}`.\n        :type: `~pint.Quantity`\n        \"\"\"\n        return self.voltage_range[0]\n\n    @property\n    def voltage_max(self):\n        \"\"\"\n        Gets the maximum voltage for the current channel.\n\n        :units: :math:`\\\\text{V}`.\n        :type: `~pint.Quantity`\n        \"\"\"\n        return self.voltage_range[1]\n\n    @property\n    def voltage_range(self):\n        \"\"\"\n        Gets the voltage range for the current channel.\n\n        The MAX function SCPI command is designed in such a way\n        on this device that it always returns the largest absolute value.\n        There is no need to query MIN, as it is always 0., but one has to\n        order the values as MAX can be negative.\n\n        :units: :math:`\\\\text{V}`.\n        :type: array of `~pint.Quantity`\n        \"\"\"\n        value = u.Quantity(*split_unit_str(self.query(\"SOUR:VOLT? MAX\"), u.volt))\n        if value < 0.0:\n            return value, 0.0\n        return 0.0, value\n\n    current, current_min, current_max = bounded_unitful_property(\n        \"SOUR:CURR\",\n        u.amp,\n        min_fmt_str=\"{}? MIN\",\n        max_fmt_str=\"{}? MAX\",\n        doc=\"\"\"\n        Gets/sets the output current of the source.\n\n        :units: As specified, or assumed to be :math:`\\\\text{A}` otherwise.\n        :type: `float` or `~pint.Quantity`\n        \"\"\",\n    )\n\n    voltage_sense = unitful_property(\n        \"MEAS:VOLT\",\n        u.volt,\n        readonly=True,\n        doc=\"\"\"\n        Gets the actual output voltage as measured by the sense wires.\n\n        :units: As specified, or assumed to be :math:`\\\\text{V}` otherwise.\n        :type: `~pint.Quantity`\n        \"\"\",\n    )\n\n    current_sense = unitful_property(\n        \"MEAS:CURR\",\n        u.amp,\n        readonly=True,\n        doc=\"\"\"\n        Gets the actual output current as measured by the sense wires.\n\n        :units: As specified, or assumed to be :math:`\\\\text{A}` otherwise.\n        :type: `~pint.Quantity`\n        \"\"\",\n    )\n\n    output = bool_property(\n        \"OUTP\",\n        inst_true=\"1\",\n        inst_false=\"0\",\n        doc=\"\"\"\n        Gets/sets the outputting status of the specified channel.\n\n        This is a toggle setting. ON will turn on the channel output\n        while OFF will turn it off.\n\n        :type: `bool`\n        \"\"\",\n    )\n"
  },
  {
    "path": "src/instruments/keithley/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing Keithley instruments\n\"\"\"\n\nfrom .keithley195 import Keithley195\nfrom .keithley485 import Keithley485\nfrom .keithley580 import Keithley580\nfrom .keithley2182 import Keithley2182\nfrom .keithley6220 import Keithley6220\nfrom .keithley6514 import Keithley6514\n"
  },
  {
    "path": "src/instruments/keithley/keithley195.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nDriver for the Keithley 195 digital multimeter\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport time\nimport struct\nfrom enum import Enum, IntEnum\n\nfrom instruments.abstract_instruments import Multimeter\nfrom instruments.units import ureg as u\n\n# CLASSES #####################################################################\n\n\nclass Keithley195(Multimeter):\n    \"\"\"\n    The Keithley 195 is a 5 1/2 digit auto-ranging digital multimeter. You can\n    find the full specifications list in the `Keithley 195 user's guide`_.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> import instruments.units as u\n    >>> dmm = ik.keithley.Keithley195.open_gpibusb('/dev/ttyUSB0', 12)\n    >>> print dmm.measure(dmm.Mode.resistance)\n\n    .. _Keithley 195 user's guide: http://www.keithley.com/data?asset=803\n    \"\"\"\n\n    def __init__(self, filelike):\n        super().__init__(filelike)\n        self.sendcmd(\"YX\")  # Removes the termination CRLF\n        self.sendcmd(\"G1DX\")  # Disable returning prefix and suffix\n\n    # ENUMS ##\n\n    class Mode(IntEnum):\n        \"\"\"\n        Enum containing valid measurement modes for the Keithley 195\n        \"\"\"\n\n        voltage_dc = 0\n        voltage_ac = 1\n        resistance = 2\n        current_dc = 3\n        current_ac = 4\n\n    class TriggerMode(IntEnum):\n        \"\"\"\n        Enum containing valid trigger modes for the Keithley 195\n        \"\"\"\n\n        talk_continuous = 0\n        talk_one_shot = 1\n        get_continuous = 2\n        get_one_shot = 3\n        x_continuous = 4\n        x_one_shot = 5\n        ext_continuous = 6\n        ext_one_shot = 7\n\n    class ValidRange(Enum):\n        \"\"\"\n        Enum containing valid range settings for the Keithley 195\n        \"\"\"\n\n        voltage_dc = (20e-3, 200e-3, 2, 20, 200, 1000)\n        voltage_ac = (20e-3, 200e-3, 2, 20, 200, 700)\n        current_dc = (20e-6, 200e-6, 2e-3, 20e-3, 200e-3, 2)\n        current_ac = (20e-6, 200e-6, 2e-3, 20e-3, 200e-3, 2, 2)\n        resistance = (20, 200, 2000, 20e3, 200e3, 2e6, 20e6)\n\n    # PROPERTIES #\n\n    @property\n    def mode(self):\n        \"\"\"\n        Gets/sets the measurement mode for the Keithley 195. The base model\n        only has DC voltage and resistance measurements. In order to use AC\n        voltage, DC current, and AC current measurements your unit must be\n        equiped with option 1950.\n\n        Example use:\n\n        >>> import instruments as ik\n        >>> dmm = ik.keithley.Keithley195.open_gpibusb('/dev/ttyUSB0', 12)\n        >>> dmm.mode = dmm.Mode.resistance\n\n        :type: `Keithley195.Mode`\n        \"\"\"\n        return self.parse_status_word(self.get_status_word())[\"mode\"]\n\n    @mode.setter\n    def mode(self, newval):\n        if isinstance(newval, str):\n            newval = self.Mode[newval]\n        if not isinstance(newval, Keithley195.Mode):\n            raise TypeError(\n                \"Mode must be specified as a Keithley195.Mode \"\n                \"value, got {} instead.\".format(newval)\n            )\n        self.sendcmd(f\"F{newval.value}DX\")\n\n    @property\n    def trigger_mode(self):\n        \"\"\"\n        Gets/sets the trigger mode of the Keithley 195.\n\n        There are two different trigger settings for four different sources.\n        This means there are eight different settings for the trigger mode.\n\n        The two types are continuous and one-shot. Continuous has the instrument\n        continuously sample the resistance. One-shot performs a single\n        resistance measurement.\n\n        The three trigger sources are on talk, on GET, and on \"X\". On talk\n        refers to addressing the instrument to talk over GPIB. On GET is when\n        the instrument receives the GPIB command byte for \"group execute\n        trigger\". On \"X\" is when one sends the ASCII character \"X\" to the\n        instrument. This character is used as a general execute to confirm\n        commands send to the instrument. In InstrumentKit, \"X\" is sent after\n        each command so it is not suggested that one uses on \"X\" triggering.\n        Last, is external triggering. This is the port on the rear of the\n        instrument. Refer to the manual for electrical characteristics of this\n        port.\n\n        :type: `Keithley195.TriggerMode`\n        \"\"\"\n        return self.parse_status_word(self.get_status_word())[\"trigger\"]\n\n    @trigger_mode.setter\n    def trigger_mode(self, newval):\n        if isinstance(newval, str):\n            newval = Keithley195.TriggerMode[newval]\n        if not isinstance(newval, Keithley195.TriggerMode):\n            raise TypeError(\n                \"Drive must be specified as a \"\n                \"Keithley195.TriggerMode, got {} \"\n                \"instead.\".format(newval)\n            )\n        self.sendcmd(f\"T{newval.value}X\")\n\n    @property\n    def relative(self):\n        \"\"\"\n        Gets/sets the zero command (relative measurement) mode of the\n        Keithley 195.\n\n        As stated in the manual: The zero mode serves as a means for a baseline\n        suppression. When the correct zero command is send over the bus, the\n        instrument will enter the zero mode, as indicated by the front panel\n        ZERO indicator light. All reading displayed or send over the bus while\n        zero is enabled are the difference between the stored baseline adn the\n        actual voltage level. For example, if a 100mV baseline is stored, 100mV\n        will be subtracted from all subsequent readings as long as the zero mode\n        is enabled. The value of the stored baseline can be as little as a few\n        microvolts or as large as the selected range will permit.\n\n        See the manual for more information.\n\n        :type: `bool`\n        \"\"\"\n        return self.parse_status_word(self.get_status_word())[\"relative\"]\n\n    @relative.setter\n    def relative(self, newval):\n        if not isinstance(newval, bool):\n            raise TypeError(\"Relative mode must be a boolean.\")\n        self.sendcmd(f\"Z{int(newval)}DX\")\n\n    @property\n    def input_range(self):\n        \"\"\"\n        Gets/sets the range of the Keithley 195 input terminals. The valid range\n        settings depends on the current mode of the instrument. They are listed\n        as follows:\n\n        #) voltage_dc = ``(20e-3, 200e-3, 2, 20, 200, 1000)``\n\n        #) voltage_ac = ``(20e-3, 200e-3, 2, 20, 200, 700)``\n\n        #) current_dc = ``(20e-6, 200e-6, 2e-3, 20e-3, 200e-3, 2)``\n\n        #) current_ac = ``(20e-6, 200e-6, 2e-3, 20e-3, 200e-3, 2)``\n\n        #) resistance = ``(20, 200, 2000, 20e3, 200e3, 2e6, 20e6)``\n\n        All modes will also accept the string ``auto`` which will set the 195\n        into auto ranging mode.\n\n        :rtype: `~pint.Quantity` or `str`\n        \"\"\"\n        index = self.parse_status_word(self.get_status_word())[\"range\"]\n        if index == 0:\n            return \"auto\"\n\n        mode = self.mode\n        value = Keithley195.ValidRange[mode.name].value[index - 1]\n        units = UNITS2[mode]\n        return value * units\n\n    @input_range.setter\n    def input_range(self, newval):\n        if isinstance(newval, str):\n            if newval.lower() == \"auto\":\n                self.sendcmd(\"R0DX\")\n                return\n            else:\n                raise ValueError(\n                    'Only \"auto\" is acceptable when specifying '\n                    \"the input range as a string.\"\n                )\n        if isinstance(newval, u.Quantity):\n            newval = float(newval.magnitude)\n\n        mode = self.mode\n        valid = Keithley195.ValidRange[mode.name].value\n        if isinstance(newval, (float, int)):\n            if newval in valid:\n                newval = valid.index(newval) + 1\n            else:\n                raise ValueError(\n                    \"Valid range settings for mode {} \" \"are: {}\".format(mode, valid)\n                )\n        else:\n            raise TypeError(\n                \"Range setting must be specified as a float, int, \"\n                'or the string \"auto\", got {}'.format(type(newval))\n            )\n        self.sendcmd(f\"R{newval}DX\")\n\n    # METHODS #\n\n    def measure(self, mode=None):\n        \"\"\"\n        Instruct the Keithley 195 to perform a one time measurement. The\n        instrument will use default parameters for the requested measurement.\n        The measurement will immediately take place, and the results are\n        directly sent to the instrument's output buffer.\n\n        Method returns a Python quantity consisting of a numpy array with the\n        instrument value and appropriate units.\n\n        With the 195, it is HIGHLY recommended that you seperately set the\n        mode and let the instrument settle into the new mode. This can sometimes\n        take longer than the 2 second delay added in this method. In our testing\n        the 2 seconds seems to be sufficient but we offer no guarentee.\n\n        Example usage:\n\n        >>> import instruments as ik\n        >>> import instruments.units as u\n        >>> dmm = ik.keithley.Keithley195.open_gpibusb('/dev/ttyUSB0', 12)\n        >>> print(dmm.measure(dmm.Mode.resistance))\n\n        :param mode: Desired measurement mode. This must always be specified\n            in order to provide the correct return units.\n        :type mode: `Keithley195.Mode`\n\n        :return: A measurement from the multimeter.\n        :rtype: `~pint.Quantity`\n        \"\"\"\n        if mode is not None:\n            current_mode = self.mode\n            if mode != current_mode:\n                self.mode = mode\n                time.sleep(2)  # Gives the instrument a moment to settle\n        else:\n            mode = self.mode\n        value = self.query(\"\")\n        return float(value) * UNITS2[mode]\n\n    def get_status_word(self):\n        \"\"\"\n        Retreive the status word from the instrument. This contains information\n        regarding the various settings of the instrument.\n\n        The function `~Keithley195.parse_status_word` is designed to parse\n        the return string from this function.\n\n        :return: String containing setting information of the instrument\n        :rtype: `str`\n        \"\"\"\n        self.sendcmd(\"U0DX\")\n        return self._file.read_raw()\n\n    @staticmethod\n    def parse_status_word(statusword):  # pylint: disable=too-many-locals\n        \"\"\"\n        Parse the status word returned by the function\n        `~Keithley195.get_status_word`.\n\n        Returns a `dict` with the following keys:\n        ``{trigger,mode,range,eoi,buffer,rate,srqmode,relative,delay,multiplex,\n        selftest,dataformat,datacontrol,filter,terminator}``\n\n        :param statusword: Byte string to be unpacked and parsed\n        :type: `str`\n\n        :return: A parsed version of the status word as a Python dictionary\n        :rtype: `dict`\n        \"\"\"\n        if statusword[:3] != b\"195\":\n            raise ValueError(\n                \"Status word starts with wrong prefix, expected \"\n                \"195, got {}\".format(statusword)\n            )\n\n        (\n            trigger,\n            function,\n            input_range,\n            eoi,\n            buf,\n            rate,\n            srqmode,\n            relative,\n            delay,\n            multiplex,\n            selftest,\n            data_fmt,\n            data_ctrl,\n            filter_mode,\n            terminator,\n        ) = struct.unpack(\"@4c2s3c2s5c2s\", statusword[4:])\n\n        return {\n            \"trigger\": Keithley195.TriggerMode(int(trigger)),\n            \"mode\": Keithley195.Mode(int(function)),\n            \"range\": int(input_range),\n            \"eoi\": (eoi == b\"1\"),\n            \"buffer\": buf,\n            \"rate\": rate,\n            \"srqmode\": srqmode,\n            \"relative\": (relative == b\"1\"),\n            \"delay\": delay,\n            \"multiplex\": (multiplex == b\"1\"),\n            \"selftest\": selftest,\n            \"dataformat\": data_fmt,\n            \"datacontrol\": data_ctrl,\n            \"filter\": filter_mode,\n            \"terminator\": terminator,\n        }\n\n    def trigger(self):\n        \"\"\"\n        Tell the Keithley 195 to execute all commands that it has received.\n\n        Do note that this is different from the standard SCPI ``*TRG`` command\n        (which is not supported by the 195 anyways).\n        \"\"\"\n        self.sendcmd(\"X\")\n\n    def auto_range(self):\n        \"\"\"\n        Turn on auto range for the Keithley 195.\n\n        This is the same as calling ``Keithley195.input_range = 'auto'``\n        \"\"\"\n        self.input_range = \"auto\"\n\n\n# UNITS #######################################################################\n\nUNITS = {\n    \"DCV\": u.volt,\n    \"ACV\": u.volt,\n    \"ACA\": u.amp,\n    \"DCA\": u.amp,\n    \"OHM\": u.ohm,\n}\n\nUNITS2 = {\n    Keithley195.Mode.voltage_dc: u.volt,\n    Keithley195.Mode.voltage_ac: u.volt,\n    Keithley195.Mode.current_dc: u.amp,\n    Keithley195.Mode.current_ac: u.amp,\n    Keithley195.Mode.resistance: u.ohm,\n}\n"
  },
  {
    "path": "src/instruments/keithley/keithley2182.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nDriver for the Keithley 2182 nano-voltmeter\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom enum import Enum\n\nfrom instruments.abstract_instruments import Multimeter\nfrom instruments.generic_scpi import SCPIMultimeter\nfrom instruments.optional_dep_finder import numpy\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import ProxyList\n\n# CLASSES #####################################################################\n\n\nclass Keithley2182(SCPIMultimeter):\n    \"\"\"\n    The Keithley 2182 is a nano-voltmeter. You can find the full specifications\n    list in the `user's guide`_.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> meter = ik.keithley.Keithley2182.open_gpibusb(\"/dev/ttyUSB0\", 10)\n    >>> print(meter.measure(meter.Mode.voltage_dc))\n\n\n    \"\"\"\n\n    # INNER CLASSES #\n\n    class Channel(Multimeter):\n        \"\"\"\n        Class representing a channel on the Keithley 2182 nano-voltmeter.\n\n        .. warning:: This class should NOT be manually created by the user. It\n            is designed to be initialized by the `Keithley2182` class.\n        \"\"\"\n\n        # pylint: disable=super-init-not-called\n        def __init__(self, parent, idx):\n            self._parent = parent\n            self._idx = idx + 1\n\n        # PROPERTIES #\n\n        @property\n        def mode(self):\n            return Keithley2182.Mode(self._parent.query(\"SENS:FUNC?\"))\n\n        @mode.setter\n        def mode(self, newval):\n            raise NotImplementedError\n\n        @property\n        def trigger_mode(self):\n            raise NotImplementedError\n\n        @trigger_mode.setter\n        def trigger_mode(self, newval):\n            raise NotImplementedError\n\n        @property\n        def relative(self):\n            raise NotImplementedError\n\n        @relative.setter\n        def relative(self, newval):\n            raise NotImplementedError\n\n        @property\n        def input_range(self):\n            raise NotImplementedError\n\n        @input_range.setter\n        def input_range(self, newval):\n            raise NotImplementedError\n\n        # METHODS #\n\n        def measure(self, mode=None):\n            \"\"\"\n            Performs a measurement of the specified channel. If no mode\n            parameter is specified then the current mode is used.\n\n            :param mode: Mode that the measurement will be performed in\n            :type mode: Keithley2182.Mode\n            :return: The value of the measurement\n            :rtype: `~pint.Quantity`\n            \"\"\"\n            if mode is not None:\n                # self.mode = mode\n                raise NotImplementedError\n            self._parent.sendcmd(f\"SENS:CHAN {self._idx}\")\n            value = float(self._parent.query(\"SENS:DATA:FRES?\"))\n            unit = self._parent.units\n            return u.Quantity(value, unit)\n\n    # ENUMS #\n\n    class Mode(Enum):\n        \"\"\"\n        Enum containing valid measurement modes for the Keithley 2182\n        \"\"\"\n\n        voltage_dc = \"VOLT\"\n        temperature = \"TEMP\"\n\n    class TriggerMode(Enum):\n        \"\"\"\n        Enum containing valid trigger modes for the Keithley 2182\n        \"\"\"\n\n        immediate = \"IMM\"\n        external = \"EXT\"\n        bus = \"BUS\"\n        timer = \"TIM\"\n        manual = \"MAN\"\n\n    # PROPERTIES #\n\n    @property\n    def channel(self):\n        \"\"\"\n        Gets a specific Keithley 2182 channel object. The desired channel is\n        specified like one would access a list.\n\n        Although not default, the 2182 has up to two channels.\n\n        For example, the following would print the measurement from channel 1:\n\n        >>> meter = ik.keithley.Keithley2182.open_gpibusb(\"/dev/ttyUSB0\", 10)\n        >>> print meter.channel[0].measure()\n\n        :rtype: `Keithley2182.Channel`\n        \"\"\"\n        return ProxyList(self, Keithley2182.Channel, range(2))\n\n    @property\n    def relative(self):\n        \"\"\"\n        Gets/sets the relative measurement function of the Keithley 2182.\n\n        This is used to enable or disable the relative function for the\n        currently set mode. When enabling, the current reading is used as a\n        baseline which is subtracted from future measurements.\n\n        If relative is already on, the stored value is refreshed with the\n        currently read value.\n\n        See the manual for more information.\n\n        :type: `bool`\n        \"\"\"\n        mode = self.channel[0].mode\n        return self.query(f\"SENS:{mode.value}:CHAN1:REF:STAT?\") == \"ON\"\n\n    @relative.setter\n    def relative(self, newval):\n        if not isinstance(newval, bool):\n            raise TypeError(\"Relative mode must be a boolean.\")\n        mode = self.channel[0].mode\n        if self.relative:\n            self.sendcmd(f\"SENS:{mode.value}:CHAN1:REF:ACQ\")\n        else:\n            newval = \"ON\" if newval is True else \"OFF\"\n            self.sendcmd(f\"SENS:{mode.value}:CHAN1:REF:STAT {newval}\")\n\n    @property\n    def input_range(self):\n        raise NotImplementedError\n\n    @input_range.setter\n    def input_range(self, newval):\n        raise NotImplementedError\n\n    @property\n    def units(self):\n        \"\"\"\n        Gets the current measurement units of the instrument.\n\n        :rtype: `~pint.Unit`\n        \"\"\"\n        mode = self.channel[0].mode\n        if mode == Keithley2182.Mode.voltage_dc:\n            return u.volt\n        unit = self.query(\"UNIT:TEMP?\")\n        if unit == \"C\":\n            unit = u.celsius\n        elif unit == \"K\":\n            unit = u.kelvin\n        elif unit == \"F\":\n            unit = u.fahrenheit\n        else:\n            raise ValueError(\"Unknown temperature units.\")\n        return unit\n\n    # METHODS #\n\n    def fetch(self):\n        \"\"\"\n        Transfer readings from instrument memory to the output buffer, and thus\n        to the computer.\n        If currently taking a reading, the instrument will wait until it is\n        complete before executing this command.\n        Readings are NOT erased from memory when using fetch. Use the ``R?``\n        command to read and erase data.\n        Note that the data is transfered as ASCII, and thus it is not\n        recommended to transfer a large number of data points using GPIB.\n\n        :return: Measurement readings from the instrument output buffer.\n        :rtype: `tuple`[`~pint.Quantity`, ...]\n            or if numpy is installed, `~pint.Quantity` with `numpy.array` data\n        \"\"\"\n        data = list(map(float, self.query(\"FETC?\").split(\",\")))\n        unit = self.units\n        if numpy:\n            return data * unit\n        return tuple(d * unit for d in data)\n\n    def measure(self, mode=None):\n        \"\"\"\n        Perform and transfer a measurement of the desired type.\n\n        :param mode: Desired measurement mode. If left at default the\n            measurement will occur with the current mode.\n        :type: `Keithley2182.Mode`\n\n        :return: Returns a single shot measurement of the specified mode.\n        :rtype: `~pint.Quantity`\n        :units: Volts, Celsius, Kelvin, or Fahrenheit\n        \"\"\"\n        if mode is None:\n            mode = self.channel[0].mode\n        if not isinstance(mode, Keithley2182.Mode):\n            raise TypeError(\n                \"Mode must be specified as a Keithley2182.Mode \"\n                \"value, got {} instead.\".format(mode)\n            )\n        value = float(self.query(f\"MEAS:{mode.value}?\"))\n        unit = self.units\n        return value * unit\n"
  },
  {
    "path": "src/instruments/keithley/keithley485.py",
    "content": "#!/usr/bin/env python\n#\n# keithley485.py: Driver for the Keithley 485 picoammeter.\n#\n# © 2019 Francois Drielsma (francois.drielsma@gmail.com).\n#\n# This file is a part of the InstrumentKit project.\n# Licensed under the AGPL version 3.\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Affero General Public License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with this program. If not, see <http://www.gnu.org/licenses/>.\n#\n\"\"\"\nDriver for the Keithley 485 picoammeter.\n\nOriginally contributed and copyright held by Francois Drielsma\n(francois.drielsma@gmail.com).\n\nAn unrestricted license has been provided to the maintainers of the Instrument\nKit project.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom struct import unpack\nfrom enum import Enum\n\nfrom instruments.abstract_instruments import Instrument\nfrom instruments.units import ureg as u\n\n# CLASSES #####################################################################\n\n\nclass Keithley485(Instrument):\n    \"\"\"\n    The Keithley Model 485 is a 4 1/2 digit resolution autoranging\n    picoammeter with a +- 20000 count LCD. It is designed for low\n    current measurement requirements from 0.1pA to 2mA.\n\n    The device needs some processing time (manual reports 300-500ms) after a\n    command has been transmitted.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> inst = ik.keithley.Keithley485.open_gpibusb(\"/dev/ttyUSB0\", 22)\n    >>> inst.measure()  # Measures the current\n    array(-1.278e-10) * A\n    \"\"\"\n\n    # ENUMS #\n\n    class TriggerMode(Enum):\n        \"\"\"\n        Enum containing valid trigger modes for the Keithley 485\n        \"\"\"\n\n        #: Continuously measures current, returns on talk\n        continuous_ontalk = 0\n        #: Measures current once and returns on talk\n        oneshot_ontalk = 1\n        #: Continuously measures current, returns on `GET`\n        continuous_onget = 2\n        #: Measures current once and returns on `GET`\n        oneshot_onget = 3\n        #: Continuously measures current, returns on `X`\n        continuous_onx = 4\n        #: Measures current once and returns on `X`\n        oneshot_onx = 5\n\n    class SRQDataMask(Enum):\n        \"\"\"\n        Enum containing valid SRQ data masks for the Keithley 485\n        \"\"\"\n\n        #: Service request (SRQ) disabled\n        srq_disabled = 0\n        #: Read overflow\n        read_ovf = 1\n        #: Read done\n        read_done = 8\n        #: Read done or read overflow\n        read_done_ovf = 9\n        #: Device busy\n        busy = 16\n        #: Device busy or read overflow\n        busy_read_ovf = 17\n        #: Device busy or read overflow\n        busy_read_done = 24\n        #: Device busy, read done or read overflow\n        busy_read_done_ovf = 25\n\n    class SRQErrorMask(Enum):\n        \"\"\"\n        Enum containing valid SRQ error masks for the Keithley 485\n        \"\"\"\n\n        #: Service request (SRQ) disabled\n        srq_disabled = 0\n        #: Illegal Device-Dependent Command Option (IDDCO)\n        idcco = 1\n        #: Illegal Device-Dependent Command (IDDC)\n        idcc = 2\n        #: IDDCO or IDDC\n        idcco_idcc = 3\n        #: Device not in remote\n        not_remote = 4\n        #: Device not in remote or IDDCO\n        not_remote_idcco = 5\n        #: Device not in remote or IDDC\n        not_remote_idcc = 6\n        #: Device not in remote, IDDCO or IDDC\n        not_remote_idcco_idcc = 7\n\n    class Status(Enum):\n        \"\"\"\n        Enum containing valid status keys in the measurement string\n        \"\"\"\n\n        #: Measurement normal\n        normal = b\"N\"\n        #: Measurement zero-check\n        zerocheck = b\"C\"\n        #: Measurement overflow\n        overflow = b\"O\"\n        #: Measurement relative\n        relative = b\"Z\"\n\n    # PROPERTIES #\n\n    @property\n    def zero_check(self):\n        \"\"\"\n        Gets/sets the 'zero check' mode (C) of the Keithley 485.\n\n        Once zero check is enabled (C1 sent), the display can be\n        zeroed with the REL feature or the front panel pot.\n\n        See the Keithley 485 manual for more information.\n\n        :type: `bool`\n        \"\"\"\n        return self.get_status()[\"zerocheck\"]\n\n    @zero_check.setter\n    def zero_check(self, newval):\n        if not isinstance(newval, bool):\n            raise TypeError(\"Zero Check mode must be a boolean.\")\n        self.sendcmd(f\"C{int(newval)}X\")\n\n    @property\n    def log(self):\n        \"\"\"\n        Gets/sets the 'log' mode (D) of the Keithley 485.\n\n        Once log is enabled (D1 sent), the device will return\n        the logarithm of the current readings.\n\n        See the Keithley 485 manual for more information.\n\n        :type: `bool`\n        \"\"\"\n        return self.get_status()[\"log\"]\n\n    @log.setter\n    def log(self, newval):\n        if not isinstance(newval, bool):\n            raise TypeError(\"Log mode must be a boolean.\")\n        self.sendcmd(f\"D{int(newval)}X\")\n\n    @property\n    def input_range(self):\n        \"\"\"\n        Gets/sets the range (R) of the Keithley 485 input terminals. The valid\n        ranges are one of ``{auto|2e-9|2e-8|2e-7|2e-6|2e-5|2e-4|2e-3}``\n\n        :type: `~pint.Quantity` or `str`\n        \"\"\"\n        value = self.get_status()[\"range\"]\n        if isinstance(value, str):\n            return value\n        return value * u.amp\n\n    @input_range.setter\n    def input_range(self, newval):\n        valid = (\"auto\", 2e-9, 2e-8, 2e-7, 2e-6, 2e-5, 2e-4, 2e-3)\n        if isinstance(newval, str):\n            newval = newval.lower()\n            if newval == \"auto\":\n                self.sendcmd(\"R0X\")\n                return\n            else:\n                raise ValueError(\n                    \"Only `auto` is acceptable when specifying \"\n                    \"the range as a string.\"\n                )\n        if isinstance(newval, u.Quantity):\n            newval = float(newval.magnitude)\n\n        if isinstance(newval, (float, int)):\n            if newval in valid:\n                newval = valid.index(newval)\n            else:\n                raise ValueError(f\"Valid range settings are: {valid}\")\n        else:\n            raise TypeError(\n                \"Range setting must be specified as a float, int, \"\n                \"or the string `auto`, got {}\".format(type(newval))\n            )\n        self.sendcmd(f\"R{newval}X\")\n\n    @property\n    def relative(self):\n        \"\"\"\n        Gets/sets the relative measurement mode (Z) of the Keithley 485.\n\n        As stated in the manual: The relative function is used to establish a\n        baseline reading. This reading is subtracted from all subsequent\n        readings. The purpose of making relative measurements is to cancel test\n        lead and offset currents or to store an input as a reference level.\n\n        Once a relative level is established, it remains in effect until another\n        relative level is set. The relative value is only good for the range the\n        value was taken on and higher ranges. If a lower range is selected than\n        that on which the relative was taken, inaccurate results may occur.\n        Relative cannot be activated when \"OL\" is displayed.\n\n        See the manual for more information.\n\n        :type: `bool`\n        \"\"\"\n        return self.get_status()[\"relative\"]\n\n    @relative.setter\n    def relative(self, newval):\n        if not isinstance(newval, bool):\n            raise TypeError(\"Relative mode must be a boolean.\")\n        self.sendcmd(f\"Z{int(newval)}X\")\n\n    @property\n    def eoi_mode(self):\n        \"\"\"\n        Gets/sets the 'eoi' mode (K) of the Keithley 485.\n\n        The model 485 will normally send an end of interrupt (EOI)\n        during the last byte of its data string or status word.\n        The EOI reponse of the instrument may be included or omitted.\n        Warning: the default setting (K0) includes it.\n\n        See the Keithley 485 manual for more information.\n\n        :type: `bool`\n        \"\"\"\n        return self.get_status()[\"eoi_mode\"]\n\n    @eoi_mode.setter\n    def eoi_mode(self, newval):\n        if not isinstance(newval, bool):\n            raise TypeError(\"EOI mode must be a boolean.\")\n        self.sendcmd(f\"K{1 - int(newval)}X\")\n\n    @property\n    def trigger_mode(self):\n        \"\"\"\n        Gets/sets the trigger mode (T) of the Keithley 485.\n\n        There are two different trigger settings for three different sources.\n        This means there are six different settings for the trigger mode.\n\n        The two types are continuous and one-shot. Continuous has the instrument\n        continuously sample the current. One-shot performs a single\n        current measurement when requested to do so.\n\n        The three trigger sources are on talk, on GET, and on \"X\". On talk\n        refers to addressing the instrument to talk over GPIB. On GET is when\n        the instrument receives the GPIB command byte for \"group execute\n        trigger\". Last, on \"X\" is when one sends the ASCII character \"X\" to the\n        instrument.\n\n        It is recommended to leave it in the default mode (T0, continuous on talk),\n        and simply ignore the output when other commands are called.\n\n        :type: `Keithley485.TriggerMode`\n        \"\"\"\n        return self.get_status()[\"trigger\"]\n\n    @trigger_mode.setter\n    def trigger_mode(self, newval):\n        if isinstance(newval, str):\n            newval = Keithley485.TriggerMode[newval]\n        if not isinstance(newval, Keithley485.TriggerMode):\n            raise TypeError(\n                \"Drive must be specified as a \"\n                \"Keithley485.TriggerMode, got {} \"\n                \"instead.\".format(newval)\n            )\n        self.sendcmd(f\"T{newval.value}X\")\n\n    # METHODS #\n\n    def auto_range(self):\n        \"\"\"\n        Turn on auto range for the Keithley 485.\n\n        This is the same as calling the `Keithley485.set_current_range`\n        method and setting the parameter to \"AUTO\".\n        \"\"\"\n        self.sendcmd(\"R0X\")\n\n    def get_status(self):\n        \"\"\"\n        Gets and parses the status word.\n\n        Returns a `dict` with the following keys:\n        ``{zerocheck,log,range,relative,eoi,relative,\n        trigger,datamask,errormask,terminator}``\n\n        :rtype: `dict`\n        \"\"\"\n        return self._parse_status_word(self._get_status_word())\n\n    def _get_status_word(self):\n        \"\"\"\n        The device will not always respond with the statusword when asked. We\n        use a simple heuristic here: request it up to 5 times.\n\n        :rtype: `str`\n        \"\"\"\n        tries = 5\n        statusword = \"\"\n        while statusword[:3] != \"485\" and tries != 0:\n            statusword = self.query(\"U0X\")\n            tries -= 1\n\n        if tries == 0:\n            raise OSError(\"Could not retrieve status word\")\n\n        return statusword[:-1]\n\n    def _parse_status_word(self, statusword):\n        \"\"\"\n        Parse the status word returned by the function\n        `~Keithley485.get_status_word`.\n\n        Returns a `dict` with the following keys:\n        ``{zerocheck,log,range,relative,eoi,relative,\n        trigger,datamask,errormask,terminator}``\n\n        :param statusword: Byte string to be unpacked and parsed\n        :type: `str`\n\n        :rtype: `dict`\n        \"\"\"\n        if statusword[:3] != \"485\":\n            raise ValueError(\n                \"Status word starts with wrong \" \"prefix: {}\".format(statusword)\n            )\n\n        (\n            zerocheck,\n            log,\n            device_range,\n            relative,\n            eoi_mode,\n            trigger,\n            datamask,\n            errormask,\n        ) = unpack(\"@6c2s2s\", bytes(statusword[3:], \"utf-8\"))\n\n        valid_range = {\n            b\"0\": \"auto\",\n            b\"1\": 2e-9,\n            b\"2\": 2e-8,\n            b\"3\": 2e-7,\n            b\"4\": 2e-6,\n            b\"5\": 2e-5,\n            b\"6\": 2e-4,\n            b\"7\": 2e-3,\n        }\n\n        try:\n            device_range = valid_range[device_range]\n            trigger = self.TriggerMode(int(trigger)).name\n            datamask = self.SRQDataMask(int(datamask)).name\n            errormask = self.SRQErrorMask(int(errormask)).name\n        except:\n            raise RuntimeError(\"Cannot parse status \" \"word: {}\".format(statusword))\n\n        return {\n            \"zerocheck\": zerocheck == b\"1\",\n            \"log\": log == b\"1\",\n            \"range\": device_range,\n            \"relative\": relative == b\"1\",\n            \"eoi_mode\": eoi_mode == b\"0\",\n            \"trigger\": trigger,\n            \"datamask\": datamask,\n            \"errormask\": errormask,\n            \"terminator\": self.terminator,\n        }\n\n    def measure(self):\n        \"\"\"\n        Perform a current measurement with the Keithley 485.\n\n        :rtype: `~pint.Quantity`\n        \"\"\"\n        return self._parse_measurement(self.query(\"X\"))\n\n    def _parse_measurement(self, measurement):\n        \"\"\"\n        Parse the measurement string returned by the instrument.\n\n        Returns the current formatted as a Quantity.\n\n        :param measurement: String to be unpacked and parsed\n        :type: `str`\n\n        :rtype: `~pint.Quantity`\n        \"\"\"\n        status, function, base, current = unpack(\n            \"@1c2s1c10s\", bytes(measurement, \"utf-8\")\n        )\n\n        try:\n            status = self.Status(status)\n        except ValueError:\n            raise ValueError(f\"Invalid status word in measurement: {status}\")\n\n        if status != self.Status.normal:\n            raise ValueError(f\"Instrument not in normal mode: {status.name}\")\n\n        if function != b\"DC\":\n            raise ValueError(f\"Instrument not returning DC function: {function}\")\n\n        try:\n            current = (\n                float(current) * u.amp\n                if base == b\"A\"\n                else 10 ** (float(current)) * u.amp\n            )\n        except:\n            raise Exception(f\"Cannot parse measurement: {measurement}\")\n\n        return current\n"
  },
  {
    "path": "src/instruments/keithley/keithley580.py",
    "content": "#!/usr/bin/env python\n#\n# keithley580.py: Driver for the Keithley 580 micro-ohmmeter.\n#\n# © 2013 Willem Dijkstra (wpd@xs4all.nl).\n#   2014 Steven Casagrande (scasagrande@galvant.ca)\n#\n# This file is a part of the InstrumentKit project.\n# Licensed under the AGPL version 3.\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Affero General Public License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with this program. If not, see <http://www.gnu.org/licenses/>.\n#\n\"\"\"\nDriver for the HP6632b DC power supply\n\nOriginally contributed and copyright held by Willem Dijkstra (wpd@xs4all.nl)\n\nAn unrestricted license has been provided to the maintainers of the Instrument\nKit project.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nimport time\nimport struct\n\nfrom enum import IntEnum\n\nfrom instruments.units import ureg as u\n\nfrom instruments.abstract_instruments import Instrument\n\n# CLASSES #####################################################################\n\n\nclass Keithley580(Instrument):\n    \"\"\"\n    The Keithley Model 580 is a 4 1/2 digit resolution autoranging\n    micro-ohmmeter with a +- 20,000 count LCD. It is designed for low\n    resistance measurement requirements from 10uΩ to 200kΩ.\n\n    The device needs some processing time (manual reports 300-500ms) after a\n    command has been transmitted.\n    \"\"\"\n\n    def __init__(self, filelike):\n        \"\"\"\n        Initialise the instrument and remove CRLF line termination\n        \"\"\"\n        super().__init__(filelike)\n        self.sendcmd(\"Y:X\")  # Removes the termination CRLF characters\n\n    # ENUMS #\n\n    class Polarity(IntEnum):\n        \"\"\"\n        Enum containing valid polarity modes for the Keithley 580\n        \"\"\"\n\n        positive = 0\n        negative = 1\n\n    class Drive(IntEnum):\n        \"\"\"\n        Enum containing valid drive modes for the Keithley 580\n        \"\"\"\n\n        pulsed = 0\n        dc = 1\n\n    class TriggerMode(IntEnum):\n        \"\"\"\n        Enum containing valid trigger modes for the Keithley 580\n        \"\"\"\n\n        talk_continuous = 0\n        talk_one_shot = 1\n        get_continuous = 2\n        get_one_shot = 3\n        trigger_continuous = 4\n        trigger_one_shot = 5\n\n    # PROPERTIES #\n\n    @property\n    def polarity(self):\n        \"\"\"\n        Gets/sets instrument polarity.\n\n        Example use:\n\n        >>> import instruments as ik\n        >>> keithley = ik.keithley.Keithley580.open_gpibusb('/dev/ttyUSB0', 1)\n        >>> keithley.polarity = keithley.Polarity.positive\n\n        :type: `Keithley580.Polarity`\n        \"\"\"\n        value = self.parse_status_word(self.get_status_word())[\"polarity\"]\n        if value == \"+\":\n            return Keithley580.Polarity.positive\n        else:\n            return Keithley580.Polarity.negative\n\n    @polarity.setter\n    def polarity(self, newval):\n        if isinstance(newval, str):\n            newval = Keithley580.Polarity[newval]\n        if not isinstance(newval, Keithley580.Polarity):\n            raise TypeError(\n                \"Polarity must be specified as a \"\n                \"Keithley580.Polarity, got {} \"\n                \"instead.\".format(newval)\n            )\n\n        self.sendcmd(f\"P{newval.value}X\")\n\n    @property\n    def drive(self):\n        \"\"\"\n        Gets/sets the instrument drive to either pulsed or DC.\n\n        Example use:\n\n        >>> import instruments as ik\n        >>> keithley = ik.keithley.Keithley580.open_gpibusb('/dev/ttyUSB0', 1)\n        >>> keithley.drive = keithley.Drive.pulsed\n\n        :type: `Keithley580.Drive`\n        \"\"\"\n        value = self.parse_status_word(self.get_status_word())[\"drive\"]\n        return Keithley580.Drive[value]\n\n    @drive.setter\n    def drive(self, newval):\n        if isinstance(newval, str):\n            newval = Keithley580.Drive[newval]\n        if not isinstance(newval, Keithley580.Drive):\n            raise TypeError(\n                \"Drive must be specified as a \"\n                \"Keithley580.Drive, got {} \"\n                \"instead.\".format(newval)\n            )\n\n        self.sendcmd(f\"D{newval.value}X\")\n\n    @property\n    def dry_circuit_test(self):\n        \"\"\"\n        Gets/sets the 'dry circuit test' mode of the Keithley 580.\n\n        This mode is used to minimize any physical and electrical changes in\n        the contact junction by limiting the maximum source voltage to 20mV.\n        By limiting the voltage, the measuring circuit will leave the resistive\n        surface films built up on the contacts undisturbed. This allows for\n        measurement of the resistance of these films.\n\n        See the Keithley 580 manual for more information.\n\n        :type: `bool`\n        \"\"\"\n        return self.parse_status_word(self.get_status_word())[\"drycircuit\"]\n\n    @dry_circuit_test.setter\n    def dry_circuit_test(self, newval):\n        if not isinstance(newval, bool):\n            raise TypeError(\"DryCircuitTest mode must be a boolean.\")\n        self.sendcmd(f\"C{int(newval)}X\")\n\n    @property\n    def operate(self):\n        \"\"\"\n        Gets/sets the operating mode of the Keithley 580. If set to true, the\n        instrument will be in operate mode, while false sets the instruments\n        into standby mode.\n\n        :type: `bool`\n        \"\"\"\n        return self.parse_status_word(self.get_status_word())[\"operate\"]\n\n    @operate.setter\n    def operate(self, newval):\n        if not isinstance(newval, bool):\n            raise TypeError(\"Operate mode must be a boolean.\")\n        self.sendcmd(f\"O{int(newval)}X\")\n\n    @property\n    def relative(self):\n        \"\"\"\n        Gets/sets the relative measurement mode of the Keithley 580.\n\n        As stated in the manual: The relative function is used to establish a\n        baseline reading. This reading is subtracted from all subsequent\n        readings. The purpose of making relative measurements is to cancel test\n        lead and offset resistances or to store an input as a reference level.\n\n        Once a relative level is established, it remains in effect until another\n        relative level is set. The relative value is only good for the range the\n        value was taken on and higher ranges. If a lower range is selected than\n        that on which the relative was taken, inaccurate results may occur.\n        Relative cannot be activated when \"OL\" is displayed.\n\n        See the manual for more information.\n\n        :type: `bool`\n        \"\"\"\n        return self.parse_status_word(self.get_status_word())[\"relative\"]\n\n    @relative.setter\n    def relative(self, newval):\n        if not isinstance(newval, bool):\n            raise TypeError(\"Relative mode must be a boolean.\")\n        self.sendcmd(f\"Z{int(newval)}X\")\n\n    @property\n    def trigger_mode(self):\n        \"\"\"\n        Gets/sets the trigger mode of the Keithley 580.\n\n        There are two different trigger settings for three different sources.\n        This means there are six different settings for the trigger mode.\n\n        The two types are continuous and one-shot. Continuous has the instrument\n        continuously sample the resistance. One-shot performs a single\n        resistance measurement.\n\n        The three trigger sources are on talk, on GET, and on \"X\". On talk\n        refers to addressing the instrument to talk over GPIB. On GET is when\n        the instrument receives the GPIB command byte for \"group execute\n        trigger\". Last, on \"X\" is when one sends the ASCII character \"X\" to the\n        instrument. This character is used as a general execute to confirm\n        commands send to the instrument. In InstrumentKit, \"X\" is sent after\n        each command so it is not suggested that one uses on \"X\" triggering.\n\n        :type: `Keithley580.TriggerMode`\n        \"\"\"\n        raise NotImplementedError\n\n    @trigger_mode.setter\n    def trigger_mode(self, newval):\n        if isinstance(newval, str):\n            newval = Keithley580.TriggerMode[newval]\n        if not isinstance(newval, Keithley580.TriggerMode):\n            raise TypeError(\n                \"Drive must be specified as a \"\n                \"Keithley580.TriggerMode, got {} \"\n                \"instead.\".format(newval)\n            )\n        self.sendcmd(f\"T{newval.value}X\")\n\n    @property\n    def input_range(self):\n        \"\"\"\n        Gets/sets the range of the Keithley 580 input terminals. The valid\n        ranges are one of ``{AUTO|2e-1|2|20|200|2000|2e4|2e5}``\n\n        :type: `~pint.Quantity` or `str`\n        \"\"\"\n        value = self.parse_status_word(self.get_status_word())[\"range\"]\n        if isinstance(value, str):  # if range is 'auto'\n            return value\n        else:\n            return float(value) * u.ohm\n\n    @input_range.setter\n    def input_range(self, newval):\n        valid = (\"auto\", 2e-1, 2e0, 2e1, 2e2, 2e3, 2e4, 2e5)\n        if isinstance(newval, str):\n            newval = newval.lower()\n            if newval == \"auto\":\n                self.sendcmd(\"R0X\")\n                return\n            else:\n                raise ValueError(\n                    'Only \"auto\" is acceptable when specifying '\n                    \"the input range as a string.\"\n                )\n        if isinstance(newval, u.Quantity):\n            newval = float(newval.magnitude)\n\n        if isinstance(newval, (float, int)):\n            if newval in valid:\n                newval = valid.index(newval)\n            else:\n                raise ValueError(f\"Valid range settings are: {valid}\")\n        else:\n            raise TypeError(\n                \"Range setting must be specified as a float, int, \"\n                'or the string \"auto\", got {}'.format(type(newval))\n            )\n        self.sendcmd(f\"R{newval}X\")\n\n    # METHODS #\n\n    def trigger(self):\n        \"\"\"\n        Tell the Keithley 580 to execute all commands that it has received.\n\n        Do note that this is different from the standard SCPI ``*TRG`` command\n        (which is not supported by the 580 anyways).\n        \"\"\"\n        self.sendcmd(\"X\")\n\n    def auto_range(self):\n        \"\"\"\n        Turn on auto range for the Keithley 580.\n\n        This is the same as calling the `Keithley580.set_resistance_range`\n        method and setting the parameter to \"AUTO\".\n        \"\"\"\n        self.sendcmd(\"R0X\")\n\n    def set_calibration_value(self, value):\n        \"\"\"\n        Sets the calibration value. This is not currently implemented.\n\n        :param value: Calibration value to write\n        \"\"\"\n        # self.write('V+n.nnnnE+nn')\n        raise NotImplementedError(\"setCalibrationValue not implemented\")\n\n    def store_calibration_constants(self):\n        \"\"\"\n        Instructs the instrument to store the calibration constants. This is\n        not currently implemented.\n        \"\"\"\n        # self.write('L0X')\n        raise NotImplementedError(\"storeCalibrationConstants not implemented\")\n\n    def get_status_word(self):\n        \"\"\"\n        The keithley will not always respond with the statusword when asked. We\n        use a simple heuristic here: request it up to 5 times, using a 1s\n        delay to allow the keithley some thinking time.\n\n        :rtype: `str`\n        \"\"\"\n        tries = 5\n        statusword = \"\"\n        while statusword[:3] != b\"580\" and tries != 0:\n            tries -= 1\n            self.sendcmd(\"U0X\")\n            time.sleep(1)\n            self.sendcmd(\"\")\n            statusword = self._file.read_raw()\n\n        if tries == 0:\n            raise OSError(\"could not retrieve status word\")\n\n        return statusword[:-1]\n\n    def parse_status_word(self, statusword):\n        \"\"\"\n        Parse the status word returned by the function\n        `~Keithley580.get_status_word`.\n\n        Returns a `dict` with the following keys:\n        ``{drive,polarity,drycircuit,operate,range,relative,eoi,trigger,\n        sqrondata,sqronerror,linefreq,terminator}``\n\n        :param statusword: Byte string to be unpacked and parsed\n        :type: `str`\n\n        :rtype: `dict`\n        \"\"\"\n        if statusword[:3] != b\"580\":\n            raise ValueError(\n                \"Status word starts with wrong \" \"prefix: {}\".format(statusword)\n            )\n\n        (\n            drive,\n            polarity,\n            drycircuit,\n            operate,\n            rng,\n            relative,\n            eoi,\n            trigger,\n            sqrondata,\n            sqronerror,\n            linefreq,\n        ) = struct.unpack(\"@8c2s2sc\", statusword[3:16])\n\n        valid = {\n            \"drive\": {b\"0\": \"pulsed\", b\"1\": \"dc\"},\n            \"polarity\": {b\"0\": \"+\", b\"1\": \"-\"},\n            \"range\": {\n                b\"0\": \"auto\",\n                b\"1\": 0.2,\n                b\"2\": 2,\n                b\"3\": 20,\n                b\"4\": 2e2,\n                b\"5\": 2e3,\n                b\"6\": 2e4,\n                b\"7\": 2e5,\n            },\n            \"linefreq\": {b\"0\": \"60Hz\", b\"1\": \"50Hz\"},\n        }\n\n        try:\n            drive = valid[\"drive\"][drive]\n            polarity = valid[\"polarity\"][polarity]\n            rng = valid[\"range\"][rng]\n            linefreq = valid[\"linefreq\"][linefreq]\n        except:\n            raise RuntimeError(\"Cannot parse status \" \"word: {}\".format(statusword))\n\n        return {\n            \"drive\": drive,\n            \"polarity\": polarity,\n            \"drycircuit\": (drycircuit == b\"1\"),\n            \"operate\": (operate == b\"1\"),\n            \"range\": rng,\n            \"relative\": (relative == b\"1\"),\n            \"eoi\": eoi,\n            \"trigger\": (trigger == b\"1\"),\n            \"sqrondata\": sqrondata,\n            \"sqronerror\": sqronerror,\n            \"linefreq\": linefreq,\n            \"terminator\": self.terminator,\n        }\n\n    def measure(self):\n        \"\"\"\n        Perform a measurement with the Keithley 580.\n\n        The usual mode parameter is ignored for the Keithley 580 as the only\n        valid mode is resistance.\n\n        :rtype: `~pint.Quantity`\n        \"\"\"\n        self.trigger()\n        self.sendcmd(\"\")\n        return self.parse_measurement(self._file.read_raw()[:-1])[\"resistance\"]\n\n    @staticmethod\n    def parse_measurement(measurement):\n        \"\"\"\n        Parse the measurement string returned by the instrument.\n\n        Returns a dict with the following keys:\n        ``{status,polarity,drycircuit,drive,resistance}``\n\n        :param measurement: String to be unpacked and parsed\n        :type: `str`\n\n        :rtype: `dict`\n        \"\"\"\n        status, polarity, drycircuit, drive, resistance = struct.unpack(\n            \"@4c11s\", measurement\n        )\n\n        valid = {\n            \"status\": {\n                b\"S\": \"standby\",\n                b\"N\": \"normal\",\n                b\"O\": \"overflow\",\n                b\"Z\": \"relative\",\n            },\n            \"polarity\": {b\"+\": \"+\", b\"-\": \"-\"},\n            \"drycircuit\": {b\"N\": False, b\"D\": True},\n            \"drive\": {b\"P\": \"pulsed\", b\"D\": \"dc\"},\n        }\n        try:\n            status = valid[\"status\"][status]\n            polarity = valid[\"polarity\"][polarity]\n            drycircuit = valid[\"drycircuit\"][drycircuit]\n            drive = valid[\"drive\"][drive]\n            resistance = float(resistance) * u.ohm\n        except:\n            raise Exception(f\"Cannot parse measurement: {measurement}\")\n\n        return {\n            \"status\": status,\n            \"polarity\": polarity,\n            \"drycircuit\": drycircuit,\n            \"drive\": drive,\n            \"resistance\": resistance,\n        }\n\n    # COMMUNICATOR METHODS #\n\n    def sendcmd(self, cmd):\n        super().sendcmd(cmd + \":\")\n\n    def query(self, cmd, size=-1):\n        return super().query(cmd + \":\", size)[:-1]\n"
  },
  {
    "path": "src/instruments/keithley/keithley6220.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the Keithley 6220 constant current supply\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nfrom instruments.units import ureg as u\n\nfrom instruments.abstract_instruments import PowerSupply\nfrom instruments.generic_scpi import SCPIInstrument\nfrom instruments.util_fns import bounded_unitful_property\n\n# CLASSES #####################################################################\n\n\nclass Keithley6220(SCPIInstrument, PowerSupply):\n    \"\"\"\n    The Keithley 6220 is a single channel constant current supply.\n\n    Because this is a constant current supply, most features that a regular\n    power supply have are not present on the 6220.\n\n    Example usage:\n\n    >>> import instruments.units as u\n    >>> import instruments as ik\n    >>> ccs = ik.keithley.Keithley6220.open_gpibusb(\"/dev/ttyUSB0\", 10)\n    >>> ccs.current = 10 * u.milliamp # Sets current to 10mA\n    >>> ccs.disable() # Turns off the output and sets the current to 0A\n    \"\"\"\n\n    # PROPERTIES ##\n\n    @property\n    def channel(self):\n        \"\"\"\n        For most power supplies, this would return a channel specific object.\n        However, the 6220 only has a single channel, so this function simply\n        returns a tuple containing itself. This is for compatibility reasons\n        if a multichannel supply is replaced with the single-channel 6220.\n\n        For example, the following commands are the same and both set the\n        current to 10mA:\n\n        >>> ccs.channel[0].current = 0.01\n        >>> ccs.current = 0.01\n        \"\"\"\n        return (self,)\n\n    @property\n    def voltage(self):\n        \"\"\"\n        This property is not supported by the Keithley 6220.\n        \"\"\"\n        raise NotImplementedError(\n            \"The Keithley 6220 does not support voltage \" \"settings.\"\n        )\n\n    @voltage.setter\n    def voltage(self, newval):\n        raise NotImplementedError(\n            \"The Keithley 6220 does not support voltage \" \"settings.\"\n        )\n\n    current, current_min, current_max = bounded_unitful_property(\n        \"SOUR:CURR\",\n        u.amp,\n        valid_range=(-105 * u.milliamp, +105 * u.milliamp),\n        doc=\"\"\"\n        Gets/sets the output current of the source. Value must be between\n        -105mA and +105mA.\n\n        :units: As specified, or assumed to be :math:`\\\\text{A}` otherwise.\n        :type: `float` or `~pint.Quantity`\n        \"\"\",\n    )\n\n    # METHODS #\n\n    def disable(self):\n        \"\"\"\n        Set the output current to zero and disable the output.\n        \"\"\"\n        self.sendcmd(\"SOUR:CLE:IMM\")\n"
  },
  {
    "path": "src/instruments/keithley/keithley6514.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the Keithley 6514 electrometer\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom enum import Enum\n\nfrom instruments.abstract_instruments import Electrometer\nfrom instruments.generic_scpi import SCPIInstrument\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import bool_property, enum_property\n\n# CLASSES #####################################################################\n\n\nclass Keithley6514(SCPIInstrument, Electrometer):\n    \"\"\"\n    The `Keithley 6514`_ is an electrometer capable of doing sensitive current,\n    charge, voltage and resistance measurements.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> import instruments.units as u\n    >>> dmm = ik.keithley.Keithley6514.open_gpibusb('/dev/ttyUSB0', 12)\n    \"\"\"\n\n    # ENUMS #\n\n    class Mode(Enum):\n        \"\"\"\n        Enum containing valid measurement modes for the Keithley 6514\n        \"\"\"\n\n        voltage = \"VOLT:DC\"\n        current = \"CURR:DC\"\n        resistance = \"RES\"\n        charge = \"CHAR\"\n\n    class TriggerMode(Enum):\n        \"\"\"\n        Enum containing valid trigger modes for the Keithley 6514\n        \"\"\"\n\n        immediate = \"IMM\"\n        tlink = \"TLINK\"\n\n    class ArmSource(Enum):\n        \"\"\"\n        Enum containing valid trigger arming sources for the Keithley 6514\n        \"\"\"\n\n        immediate = \"IMM\"\n        timer = \"TIM\"\n        bus = \"BUS\"\n        tlink = \"TLIN\"\n        stest = \"STES\"\n        pstest = \"PST\"\n        nstest = \"NST\"\n        manual = \"MAN\"\n\n    class ValidRange(Enum):\n        \"\"\"\n        Enum containing valid measurement ranges for the Keithley 6514\n        \"\"\"\n\n        voltage = (2, 20, 200)\n        current = (\n            20e-12,\n            200e-12,\n            2e-9,\n            20e-9,\n            200e-9,\n            2e-6,\n            20e-6,\n            200e-6,\n            2e-3,\n            20e-3,\n        )\n        resistance = (2e3, 20e3, 200e3, 2e6, 20e6, 200e6, 2e9, 20e9, 200e9)\n        charge = (20e-9, 200e-9, 2e-6, 20e-6)\n\n    # CONSTANTS #\n\n    _MODE_UNITS = {\n        Mode.voltage: u.volt,\n        Mode.current: u.amp,\n        Mode.resistance: u.ohm,\n        Mode.charge: u.coulomb,\n    }\n\n    # PRIVATE METHODS #\n\n    def _valid_range(self, mode):\n        if mode == self.Mode.voltage:\n            return self.ValidRange.voltage\n        elif mode == self.Mode.current:\n            return self.ValidRange.current\n        elif mode == self.Mode.resistance:\n            return self.ValidRange.resistance\n        elif mode == self.Mode.charge:\n            return self.ValidRange.charge\n        else:\n            raise ValueError(\"Invalid mode.\")\n\n    def _parse_measurement(self, ascii):\n        # TODO: don't assume ASCII data format # pylint: disable=fixme\n        vals = list(map(float, ascii.split(\",\")))\n        reading = vals[0] * self.unit\n        timestamp = vals[1]\n        status = vals[2]\n        return reading, timestamp, status\n\n    # PROPERTIES #\n\n    # The mode values have quotes around them for some annoying reason.\n    mode = enum_property(\n        \"FUNCTION\",\n        Mode,\n        input_decoration=lambda val: val[1:-1],\n        # output_decoration=lambda val: '\"{}\"'.format(val),\n        set_fmt='{} \"{}\"',\n        doc=\"\"\"\n        Gets/sets the measurement mode of the Keithley 6514.\n        \"\"\",\n    )\n\n    trigger_mode = enum_property(\n        \"TRIGGER:SOURCE\",\n        TriggerMode,\n        doc=\"\"\"\n        Gets/sets the trigger mode of the Keithley 6514.\n        \"\"\",\n    )\n\n    arm_source = enum_property(\n        \"ARM:SOURCE\",\n        ArmSource,\n        doc=\"\"\"\n        Gets/sets the arm source of the Keithley 6514.\n        \"\"\",\n    )\n\n    zero_check = bool_property(\n        \"SYST:ZCH\",\n        inst_true=\"ON\",\n        inst_false=\"OFF\",\n        doc=\"\"\"\n        Gets/sets the zero checking status of the Keithley 6514.\n        \"\"\",\n    )\n\n    zero_correct = bool_property(\n        \"SYST:ZCOR\",\n        inst_true=\"ON\",\n        inst_false=\"OFF\",\n        doc=\"\"\"\n        Gets/sets the zero correcting status of the Keithley 6514.\n        \"\"\",\n    )\n\n    @property\n    def unit(self):\n        return self._MODE_UNITS[self.mode]\n\n    @property\n    def auto_range(self):\n        \"\"\"\n        Gets/sets the auto range setting\n\n        :type: `bool`\n        \"\"\"\n        # pylint: disable=no-member\n        out = self.query(f\"{self.mode.value}:RANGE:AUTO?\")\n        return True if out == \"1\" else False\n\n    @auto_range.setter\n    def auto_range(self, newval):\n        # pylint: disable=no-member\n        self.sendcmd(\"{}:RANGE:AUTO {}\".format(self.mode.value, \"1\" if newval else \"0\"))\n\n    @property\n    def input_range(self):\n        \"\"\"\n        Gets/sets the upper limit of the current range.\n\n        :type: `~pint.Quantity`\n        \"\"\"\n        # pylint: disable=no-member\n        mode = self.mode\n        out = self.query(f\"{mode.value}:RANGE:UPPER?\")\n        return float(out) * self._MODE_UNITS[mode]\n\n    @input_range.setter\n    def input_range(self, newval):\n        # pylint: disable=no-member\n        mode = self.mode\n        val = newval.to(self._MODE_UNITS[mode]).magnitude\n        if val not in self._valid_range(mode).value:\n            raise ValueError(\"Unexpected range limit for currently selected mode.\")\n        self.sendcmd(f\"{mode.value}:RANGE:UPPER {val:e}\")\n\n    # METHODS ##\n\n    def auto_config(self, mode):\n        \"\"\"\n        This command causes the device to do the following:\n            - Switch to the specified mode\n            - Reset all related controls to default values\n            - Set trigger and arm to the 'immediate' setting\n            - Set arm and trigger counts to 1\n            - Set trigger delays to 0\n            - Place unit in idle state\n            - Disable all math calculations\n            - Disable buffer operation\n            - Enable autozero\n        \"\"\"\n        self.sendcmd(f\"CONF:{mode.value}\")\n\n    def fetch(self):\n        \"\"\"\n        Request the latest post-processed readings using the current mode.\n        (So does not issue a trigger)\n        Returns a tuple of the form (reading, timestamp)\n        \"\"\"\n        raw = self.query(\"FETC?\")\n        reading, timestamp, _ = self._parse_measurement(raw)\n        return reading, timestamp\n\n    def read_measurements(self):\n        \"\"\"\n        Trigger and acquire readings using the current mode.\n        Returns a tuple of the form (reading, timestamp)\n        \"\"\"\n        raw = self.query(\"READ?\")\n        reading, timestamp, _ = self._parse_measurement(raw)\n        return reading, timestamp\n"
  },
  {
    "path": "src/instruments/lakeshore/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing Lakeshore instruments\n\"\"\"\n\nfrom instruments.lakeshore.lakeshore336 import Lakeshore336\nfrom instruments.lakeshore.lakeshore340 import Lakeshore340\nfrom instruments.lakeshore.lakeshore370 import Lakeshore370\nfrom instruments.lakeshore.lakeshore475 import Lakeshore475\n"
  },
  {
    "path": "src/instruments/lakeshore/lakeshore336.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the Lakeshore Model 336 cryogenic temperature controller.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom instruments.generic_scpi import SCPIInstrument\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import ProxyList\n\n# CLASSES #####################################################################\n\n\nclass Lakeshore336(SCPIInstrument):\n    \"\"\"\n    The Lakeshore Model 336 is a multi-sensor cryogenic temperature controller.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> import instruments.units as u\n    >>> import serial\n    >>> inst = ik.lakeshore.Lakeshore336.open_serial('/dev/ttyUSB0', baud=57600, bytesize=serial.SEVENBITS, parity=serial.PARITY_ODD, stopbits=serial.STOPBITS_ONE)\n    >>> print(inst.sensor[0].temperature)\n    >>> print(inst.sensor[1].temperature)\n    \"\"\"\n\n    # INNER CLASSES ##\n\n    class Sensor:\n        \"\"\"\n        Class representing a sensor attached to the Lakeshore Model 336.\n\n        .. warning:: This class should NOT be manually created by the user. It is\n            designed to be initialized by the `Lakeshore336` class.\n        \"\"\"\n\n        def __init__(self, parent, idx):\n            _idx_mapper = {0: \"A\", 1: \"B\", 2: \"C\", 3: \"D\"}\n            self._parent = parent\n            self._idx = _idx_mapper[idx]\n\n        # PROPERTIES ##\n\n        @property\n        def temperature(self):\n            \"\"\"\n            Gets the temperature of the specified sensor.\n\n            :units: Kelvin\n            :type: `~pint.Quantity`\n            \"\"\"\n            value = self._parent.query(f\"KRDG?{self._idx}\")\n            return u.Quantity(float(value), u.kelvin)\n\n    # PROPERTIES ##\n\n    @property\n    def sensor(self):\n        \"\"\"\n        Gets a specific sensor object. The desired sensor is specified like\n        one would access a list.\n\n        For instance, after opening the connection as described in the overview,\n        this would query the temperature of the first sensor:\n\n        >>> print(inst.sensor[0].temperature)\n\n        The Lakeshore 336 supports up to 4 sensors (index 0-3).\n\n        :rtype: `~Lakeshore336.Sensor`\n        \"\"\"\n        return ProxyList(self, Lakeshore336.Sensor, range(4))\n"
  },
  {
    "path": "src/instruments/lakeshore/lakeshore340.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the Lakeshore 340 cryogenic temperature controller.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom instruments.generic_scpi import SCPIInstrument\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import ProxyList\n\n# CLASSES #####################################################################\n\n\nclass Lakeshore340(SCPIInstrument):\n    \"\"\"\n    The Lakeshore340 is a multi-sensor cryogenic temperature controller.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> import instruments.units as u\n    >>> inst = ik.lakeshore.Lakeshore340.open_gpibusb('/dev/ttyUSB0', 1)\n    >>> print(inst.sensor[0].temperature)\n    >>> print(inst.sensor[1].temperature)\n    \"\"\"\n\n    # INNER CLASSES ##\n\n    class Sensor:\n        \"\"\"\n        Class representing a sensor attached to the Lakeshore 340.\n\n        .. warning:: This class should NOT be manually created by the user. It is\n            designed to be initialized by the `Lakeshore340` class.\n        \"\"\"\n\n        def __init__(self, parent, idx):\n            self._parent = parent\n            self._idx = idx + 1\n\n        # PROPERTIES ##\n\n        @property\n        def temperature(self):\n            \"\"\"\n            Gets the temperature of the specified sensor.\n\n            :units: Kelvin\n            :type: `~pint.Quantity`\n            \"\"\"\n            value = self._parent.query(f\"KRDG?{self._idx}\")\n            return u.Quantity(float(value), u.kelvin)\n\n    # PROPERTIES ##\n\n    @property\n    def sensor(self):\n        \"\"\"\n        Gets a specific sensor object. The desired sensor is specified like\n        one would access a list.\n\n        For instance, this would query the temperature of the first sensor::\n\n        >>> bridge = Lakeshore340.open_serial(\"COM5\")\n        >>> print(bridge.sensor[0].temperature)\n\n        The Lakeshore 340 supports up to 2 sensors (index 0-1).\n\n        :rtype: `~Lakeshore340.Sensor`\n        \"\"\"\n        return ProxyList(self, Lakeshore340.Sensor, range(2))\n"
  },
  {
    "path": "src/instruments/lakeshore/lakeshore370.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the Lakeshore 370 AC resistance bridge.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom instruments.generic_scpi import SCPIInstrument\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import ProxyList\n\n# CLASSES #####################################################################\n\n\nclass Lakeshore370(SCPIInstrument):\n    \"\"\"\n    The Lakeshore 370 is a multichannel AC resistance bridge for use in low\n    temperature dilution refridgerator setups.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> bridge = ik.lakeshore.Lakeshore370.open_gpibusb('/dev/ttyUSB0', 1)\n    >>> print(bridge.channel[0].resistance)\n    \"\"\"\n\n    def __init__(self, filelike):\n        super().__init__(filelike)\n        # Disable termination characters and enable EOI\n        self.sendcmd(\"IEEE 3,0\")\n\n    # INNER CLASSES ##\n\n    class Channel:\n        \"\"\"\n        Class representing a sensor attached to the Lakeshore 370.\n\n        .. warning:: This class should NOT be manually created by the user. It is\n            designed to be initialized by the `Lakeshore370` class.\n        \"\"\"\n\n        def __init__(self, parent, idx):\n            self._parent = parent\n            self._idx = idx + 1\n\n        # PROPERTIES ##\n\n        @property\n        def resistance(self):\n            \"\"\"\n            Gets the resistance of the specified sensor.\n\n            :units: Ohm\n            :rtype: `~pint.Quantity`\n            \"\"\"\n            value = self._parent.query(f\"RDGR? {self._idx}\")\n            return u.Quantity(float(value), u.ohm)\n\n    # PROPERTIES ##\n\n    @property\n    def channel(self):\n        \"\"\"\n        Gets a specific channel object. The desired channel is specified like\n        one would access a list.\n\n        For instance, this would query the resistance of the first channel::\n\n        >>> import instruments as ik\n        >>> bridge = ik.lakeshore.Lakeshore370.open_serial(\"COM5\")\n        >>> print(bridge.channel[0].resistance)\n\n        The Lakeshore 370 supports up to 16 channels (index 0-15).\n\n        :rtype: `~Lakeshore370.Channel`\n        \"\"\"\n        return ProxyList(self, Lakeshore370.Channel, range(16))\n"
  },
  {
    "path": "src/instruments/lakeshore/lakeshore475.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the Lakeshore 475 Gaussmeter.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom enum import IntEnum\n\nfrom instruments.generic_scpi import SCPIInstrument\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import assume_units, bool_property\n\n# CONSTANTS ###################################################################\n\nLAKESHORE_FIELD_UNITS = {1: u.gauss, 2: u.tesla, 3: u.oersted, 4: u.amp / u.meter}\n\nLAKESHORE_TEMP_UNITS = {1: u.celsius, 2: u.kelvin}\n\nLAKESHORE_FIELD_UNITS_INV = {v: k for k, v in LAKESHORE_FIELD_UNITS.items()}\nLAKESHORE_TEMP_UNITS_INV = {v: k for k, v in LAKESHORE_TEMP_UNITS.items()}\n\n# CLASSES #####################################################################\n\n\nclass Lakeshore475(SCPIInstrument):\n    \"\"\"\n    The Lakeshore475 is a DSP Gaussmeter with field ranges from 35mG to 350kG.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> import instruments.units as u\n    >>> gm = ik.lakeshore.Lakeshore475.open_gpibusb('/dev/ttyUSB0', 1)\n    >>> print(gm.field)\n    >>> gm.field_units = u.tesla\n    >>> gm.field_setpoint = 0.05 * u.tesla\n    \"\"\"\n\n    # ENUMS ##\n\n    class Mode(IntEnum):\n        \"\"\"\n        Enum containing valid measurement modes for the Lakeshore 475\n        \"\"\"\n\n        dc = 1\n        rms = 2\n        peak = 3\n\n    class Filter(IntEnum):\n        \"\"\"\n        Enum containing valid filter modes for the Lakeshore 475\n        \"\"\"\n\n        wide = 1\n        narrow = 2\n        lowpass = 3\n\n    class PeakMode(IntEnum):\n        \"\"\"\n        Enum containing valid peak modes for the Lakeshore 475\n        \"\"\"\n\n        periodic = 1\n        pulse = 2\n\n    class PeakDisplay(IntEnum):\n        \"\"\"\n        Enum containing valid peak displays for the Lakeshore 475\n        \"\"\"\n\n        positive = 1\n        negative = 2\n        both = 3\n\n    # PROPERTIES ##\n\n    @property\n    def field(self):\n        \"\"\"\n        Read field from connected probe.\n\n        :type: `~pint.Quantity`\n        \"\"\"\n        return float(self.query(\"RDGFIELD?\")) * self.field_units\n\n    @property\n    def field_units(self):\n        \"\"\"\n        Gets/sets the units of the Gaussmeter.\n\n        Acceptable units are Gauss, Tesla, Oersted, and Amp/meter.\n\n        :type: `~pint.Unit`\n        \"\"\"\n        value = int(self.query(\"UNIT?\"))\n        return LAKESHORE_FIELD_UNITS[value]\n\n    @field_units.setter\n    def field_units(self, newval):\n        if isinstance(newval, u.Unit):\n            if newval in LAKESHORE_FIELD_UNITS_INV:\n                self.sendcmd(f\"UNIT {LAKESHORE_FIELD_UNITS_INV[newval]}\")\n            else:\n                raise ValueError(\"Not an acceptable Python quantities object\")\n        else:\n            raise TypeError(\"Field units must be a Python quantity\")\n\n    @property\n    def temp_units(self):\n        \"\"\"\n        Gets/sets the temperature units of the Gaussmeter.\n\n        Acceptable units are celcius and kelvin.\n\n        :type: `~pint.Unit`\n        \"\"\"\n        value = int(self.query(\"TUNIT?\"))\n        return LAKESHORE_TEMP_UNITS[value]\n\n    @temp_units.setter\n    def temp_units(self, newval):\n        if isinstance(newval, u.Unit):\n            if newval in LAKESHORE_TEMP_UNITS_INV:\n                self.sendcmd(f\"TUNIT {LAKESHORE_TEMP_UNITS_INV[newval]}\")\n            else:\n                raise TypeError(\"Not an acceptable Python quantities object\")\n        else:\n            raise TypeError(\"Temperature units must be a Python quantity\")\n\n    @property\n    def field_setpoint(self):\n        \"\"\"\n        Gets/sets the final setpoint of the field control ramp.\n\n        :units: As specified (if a `~pint.Quantity`) or assumed to be\n            of units Gauss.\n        :type: `~pint.Quantity` with units Gauss\n        \"\"\"\n        value = self.query(\"CSETP?\").strip()\n        units = self.field_units\n        return float(value) * units\n\n    @field_setpoint.setter\n    def field_setpoint(self, newval):\n        expected_units = self.field_units\n        newval = assume_units(newval, u.gauss)\n\n        if newval.units != expected_units:\n            raise ValueError(\n                f\"Field setpoint must be specified in the same units \"\n                f\"that the field units are currently set to. Attempts units of \"\n                f\"{newval.units}, currently expecting {expected_units}.\"\n            )\n\n        self.sendcmd(f\"CSETP {newval.magnitude}\")\n\n    @property\n    def field_control_params(self):\n        \"\"\"\n        Gets/sets the parameters associated with the field control ramp.\n        These are (in this order) the P, I, ramp rate, and control slope limit.\n\n        :type: `tuple` of 2 `float` and 2 `~pint.Quantity`\n        \"\"\"\n        params = self.query(\"CPARAM?\").strip().split(\",\")\n        params = [float(x) for x in params]\n        params[2] = params[2] * self.field_units / u.minute\n        params[3] = params[3] * u.volt / u.minute\n        return tuple(params)\n\n    @field_control_params.setter\n    def field_control_params(self, newval):\n        if not isinstance(newval, tuple):\n            raise TypeError(\"Field control parameters must be specified as \" \" a tuple\")\n        p, i, ramp_rate, control_slope_lim = newval\n\n        expected_units = self.field_units / u.minute\n\n        ramp_rate = assume_units(ramp_rate, expected_units)\n        if ramp_rate.units != expected_units:\n            raise ValueError(\n                f\"Field control params ramp rate must be specified in the same units \"\n                f\"that the field units are currently set to, per minute. Attempts units of \"\n                f\"{ramp_rate.units}, currently expecting {expected_units}.\"\n            )\n        ramp_rate = float(ramp_rate.magnitude)\n\n        unit = u.volt / u.minute\n        control_slope_lim = float(\n            assume_units(control_slope_lim, unit).to(unit).magnitude\n        )\n\n        self.sendcmd(f\"CPARAM {p},{i},{ramp_rate},{control_slope_lim}\")\n\n    @property\n    def p_value(self):\n        \"\"\"\n        Gets/sets the P value for the field control ramp.\n\n        :type: `float`\n        \"\"\"\n        return self.field_control_params[0]\n\n    @p_value.setter\n    def p_value(self, newval):\n        newval = float(newval)\n        values = list(self.field_control_params)\n        values[0] = newval\n        self.field_control_params = tuple(values)\n\n    @property\n    def i_value(self):\n        \"\"\"\n        Gets/sets the I value for the field control ramp.\n\n        :type: `float`\n        \"\"\"\n        return self.field_control_params[1]\n\n    @i_value.setter\n    def i_value(self, newval):\n        newval = float(newval)\n        values = list(self.field_control_params)\n        values[1] = newval\n        self.field_control_params = tuple(values)\n\n    @property\n    def ramp_rate(self):\n        \"\"\"\n        Gets/sets the ramp rate value for the field control ramp.\n\n        :units: As specified (if a `~pint.Quantity`) or assumed to be\n            of current field units / minute.\n        :type: `~pint.Quantity`\n        \"\"\"\n        return self.field_control_params[2]\n\n    @ramp_rate.setter\n    def ramp_rate(self, newval):\n        unit = self.field_units / u.minute\n        newval = float(assume_units(newval, unit).to(unit).magnitude)\n        values = list(self.field_control_params)\n        values[2] = newval\n        self.field_control_params = tuple(values)\n\n    @property\n    def control_slope_limit(self):\n        \"\"\"\n        Gets/sets the I value for the field control ramp.\n\n        :units: As specified (if a `~pint.Quantity`) or assumed to be\n            of units volt / minute.\n        :type: `~pint.Quantity`\n        \"\"\"\n        return self.field_control_params[3]\n\n    @control_slope_limit.setter\n    def control_slope_limit(self, newval):\n        unit = u.volt / u.minute\n        newval = float(assume_units(newval, unit).to(unit).magnitude)\n        values = list(self.field_control_params)\n        values[3] = newval\n        self.field_control_params = tuple(values)\n\n    control_mode = bool_property(\n        command=\"CMODE\",\n        inst_true=\"1\",\n        inst_false=\"0\",\n        doc=\"\"\"\n        Gets/sets the control mode setting. False corresponds to the field\n        control ramp being disables, while True enables the closed loop PI\n        field control.\n\n        :type: `bool`\n        \"\"\",\n    )\n\n    # METHODS ##\n\n    # pylint: disable=too-many-arguments\n    def change_measurement_mode(\n        self, mode, resolution, filter_type, peak_mode, peak_disp\n    ):\n        \"\"\"\n        Change the measurement mode of the Gaussmeter.\n\n        :param mode: The desired measurement mode.\n        :type mode: `Lakeshore475.Mode`\n\n        :param `int` resolution: Digit resolution of the measured field. One of\n            `{3|4|5}`.\n\n        :param filter_type: Specify the signal filter\n            used by the instrument. Available types include wide band, narrow\n            band, and low pass.\n        :type filter_type: `Lakeshore475.Filter`\n\n        :param peak_mode: Peak measurement mode to be\n            used.\n        :type peak_mode: `Lakeshore475.PeakMode`\n\n        :param peak_disp: Peak display mode to be\n            used.\n        :type peak_disp: `Lakeshore475.PeakDisplay`\n        \"\"\"\n        if not isinstance(mode, Lakeshore475.Mode):\n            raise TypeError(\n                \"Mode setting must be a \"\n                \"`Lakeshore475.Mode` value, got {} \"\n                \"instead.\".format(type(mode))\n            )\n        if not isinstance(resolution, int):\n            raise TypeError('Parameter \"resolution\" must be an integer.')\n        if not isinstance(filter_type, Lakeshore475.Filter):\n            raise TypeError(\n                \"Filter type setting must be a \"\n                \"`Lakeshore475.Filter` value, got {} \"\n                \"instead.\".format(type(filter_type))\n            )\n        if not isinstance(peak_mode, Lakeshore475.PeakMode):\n            raise TypeError(\n                \"Peak measurement type setting must be a \"\n                \"`Lakeshore475.PeakMode` value, got {} \"\n                \"instead.\".format(type(peak_mode))\n            )\n        if not isinstance(peak_disp, Lakeshore475.PeakDisplay):\n            raise TypeError(\n                \"Peak display type setting must be a \"\n                \"`Lakeshore475.PeakDisplay` value, got {} \"\n                \"instead.\".format(type(peak_disp))\n            )\n\n        mode = mode.value\n        filter_type = filter_type.value\n        peak_mode = peak_mode.value\n        peak_disp = peak_disp.value\n\n        # Parse the resolution\n        if resolution in range(3, 6):\n            resolution -= 2\n        else:\n            raise ValueError(\"Only 3,4,5 are valid resolutions.\")\n\n        self.sendcmd(\n            \"RDGMODE {},{},{},{},{}\".format(\n                mode, resolution, filter_type, peak_mode, peak_disp\n            )\n        )\n"
  },
  {
    "path": "src/instruments/mettler_toledo/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing Mettler Toledo instruments\n\"\"\"\n\nfrom .mt_sics import MTSICS\n"
  },
  {
    "path": "src/instruments/mettler_toledo/mt_sics.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the Mettler Toledo balances via Standard Interface Command Set.\n\"\"\"\n\nfrom enum import Enum\nimport warnings\n\nfrom instruments.abstract_instruments import Instrument\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import assume_units\n\n\nclass MTSICS(Instrument):\n    \"\"\"\n    Instrument class to communicate with Mettler Toledo balances using the MT-SICS\n    Standared Interface Command Set.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> inst = ik.mettler_toledo.MTSICS.open_serial('/dev/ttyUSB0', 9600)\n    >>> inst.weight\n    <Quantity(120.2, 'gram')>\n    \"\"\"\n\n    class WeightMode(Enum):\n        \"\"\"\n        Enum class to select the weight mode.\n        \"\"\"\n\n        stable = False\n        immediately = True\n\n    def __init__(self, filelike, *args, **kwargs):\n        super().__init__(filelike, *args, **kwargs)\n        self.terminator = \"\\r\\n\"\n        self._weight_mode = MTSICS.WeightMode.stable\n\n    def clear_tare(self):\n        \"\"\"\n        Clear the tare value.\n\n        Example usage:\n\n        >>> import instruments as ik\n        >>> inst = ik.mettler_toledo.MTSICS.open_serial('/dev/ttyUSB0', 9600)\n        >>> inst.clear_tare()\n        \"\"\"\n        _ = self.query(\"TAC\")\n\n    def reset(self):\n        \"\"\"\n        Reset the balance.\n\n        Example usage:\n\n        >>> import instruments as ik\n        >>> inst = ik.mettler_toledo.MTSICS.open_serial('/dev/ttyUSB0', 9600)\n        >>> inst.reset()\n        \"\"\"\n        _ = self.query(\"@\")\n\n    def tare(self, immediately=None):\n        \"\"\"\n        Tare the balance.\n\n        The mode is dependent on the weight mode, however, can be overwritten with\n        the keyword `immediately`.\n\n        :param bool immediately: Tare immediately if True, otherwise wait for stable\n            weight.\n\n        Example usage:\n\n        >>> import instruments as ik\n        >>> inst = ik.mettler_toledo.MTSICS.open_serial('/dev/ttyUSB0', 9600)\n        >>> inst.tare()\n        \"\"\"\n        if immediately is None:\n            immediately = self.weight_mode.value\n        msg = \"TI\" if immediately else \"T\"\n        _ = self.query(msg)\n\n    def zero(self, immediately=None):\n        \"\"\"\n        Zero the balance after stable weight is obtained.\n\n        Terminates processes such as zero, tare, calibration and testing etc.\n        If the device is in standby mode, it is turned on. This function sets the\n        currently read and the tare value to zero.\n\n        The mode is dependent on the weight mode, however, can be overwritten with\n        the keyword `immediately`.\n\n        :param bool immediately: Zero immediately if True, otherwise wait for stable\n            weight.\n\n        Example usage:\n\n        >>> import instruments as ik\n        >>> inst = ik.mettler_toledo.MTSICS.open_serial('/dev/ttyUSB0', 9600)\n        >>> inst.zero()\n        \"\"\"\n        if immediately is None:\n            immediately = self.weight_mode.value\n        msg = \"ZI\" if immediately else \"Z\"\n        _ = self.query(msg)\n\n    def query(self, cmd, size=-1):\n        \"\"\"\n        Query the instrument for a response.\n\n        Error checking is performed on the response.\n\n        :param str cmd: The command to send to the instrument.\n        :param int size: Number of bytes to read from the instrument.\n\n        :return: The response from the instrument.\n        :rtype: str\n\n        :raises: UserWarning if the balance is in dynamic mode.\n        \"\"\"\n        self.sendcmd(cmd)\n\n        rval = self.read(size)\n        rval = rval.split()\n\n        # error checking\n        self._general_error_checking(rval[0])\n        self._cmd_error_checking(rval[1])\n\n        # raise warning if balance in dynamic mode\n        if rval[1] == \"D\":\n            warnings.warn(\"Balance in dynamic mode.\", UserWarning)\n\n        return rval[2:]\n\n    def _cmd_error_checking(self, value):\n        \"\"\"\n        Check for errors in the query response.\n\n        :param value: Command specific error code.\n        :return: None\n\n        :raises: OSError if an error in the command occurred.\n        \"\"\"\n        if value == \"I\":\n            raise OSError(\"Internal error (e.g. balance not ready yet).\")\n        elif value == \"L\":\n            raise OSError(\"Logical error (e.g. parameter not allowed).\")\n        elif value == \"+\":\n            raise OSError(\n                \"Weigh module or balance is in overload range\"\n                \"(weighing range exceeded).\"\n            )\n        elif value == \"-\":\n            raise OSError(\n                \"Weigh module or balance is in underload range\"\n                \"(e.g. weighing pan is not in place).\"\n            )\n\n    def _general_error_checking(self, value):\n        \"\"\"\n        Check for general errors in the query response.\n\n        :param value:  General error code.\n\n        :return: None\n\n        :raises: OSError if a general error occurred.\n        \"\"\"\n        if value == \"ES\":\n            raise OSError(\"Syntax Error.\")\n        elif value == \"ET\":\n            raise OSError(\"Transmission Error.\")\n        elif value == \"EL\":\n            raise OSError(\"Logical Error.\")\n\n    @property\n    def mt_sics(self):\n        \"\"\"\n        Get MT-SICS level and MT-SICS versions.\n\n        :return: Level, Version Level 0, Version Level 1, Version Level 2,\n            Version Level 3\n\n        Example usage:\n\n        >>> import instruments as ik\n        >>> inst = ik.mettler_toledo.MTSICS.open_serial('/dev/ttyUSB0', 9600)\n        >>> inst.mt_sics\n        ['1', '1.0', '1.0', '1.0']\n        \"\"\"\n        retval = [it.replace('\"', \"\") for it in self.query(\"I1\")]\n        return retval\n\n    @property\n    def mt_sics_commands(self):\n        \"\"\"\n        Get MT-SICS commands.\n\n        Please refer to manual for information on the commands. Not all of these\n        commands are currently implemented in this class!\n\n        :return: List of all implemented MT-SICS levels and commands\n        :rtype: list\n\n        Example usage:\n\n        >>> import instruments as ik\n        >>> inst = ik.mettler_toledo.MTSICS.open_serial('/dev/ttyUSB0', 9600)\n        >>> in inst.mt_sics_commands\n        [[\"0\", \"I0\"], [\"1\", \"D\"]]\n        \"\"\"\n        timeout = self.timeout\n        self.timeout = u.Quantity(0.1, u.s)\n\n        retlist = []\n        self.sendcmd(\"I0\")\n        while True:\n            try:\n                lst = self.read().split()\n                if lst == []:  # data stream was empty\n                    break\n                retlist.append(lst)\n            except OSError:  # communication timed out\n                break\n        self.timeout = timeout\n        av_cmds = [[it[2], it[3].replace('\"', \"\")] for it in retlist]\n        return av_cmds\n\n    @property\n    def name(self):\n        \"\"\"Get / Set balance name.\n\n        A maximum of 20 characters can be entered.\n\n        :raises ValueError: If name is longer than 20 characters.\n\n        Example usage:\n\n        >>> import instruments as ik\n        >>> inst = ik.mettler_toledo.MTSICS.open_serial('/dev/ttyUSB0', 9600)\n        >>> inst.name = \"My Balance\"\n        >>> inst.name\n        'My Balance'\n        \"\"\"\n        retval = \" \".join(self.query(\"I10\"))\n        return retval.replace('\"', \"\")\n\n    @name.setter\n    def name(self, value):\n        if len(value) > 20:\n            raise ValueError(\"Name must be 20 characters or less.\")\n        _ = self.query(f'I10 \"{value}\"')\n\n    @property\n    def serial_number(self):\n        \"\"\"\n        Get the serial number of the balance.\n\n        :return: The serial number of the balance.\n        :rtype: str\n\n        Example usage:\n\n        >>> import instruments as ik\n        >>> inst = ik.mettler_toledo.MTSICS.open_serial('/dev/ttyUSB0', 9600)\n        >>> inst.serial_number\n        '123456789'\n        \"\"\"\n        return self.query(\"I4\")[0].replace('\"', \"\")\n\n    @property\n    def tare_value(self):\n        \"\"\"Get / set the tare value.\n\n        If no unit is given, grams are assumed.\n\n        Example usage:\n\n        >>> import instruments as ik\n        >>> inst = ik.mettler_toledo.MTSICS.open_serial('/dev/ttyUSB0', 9600)\n        >>> inst.tare_value = 1.0\n        >>> inst.tare_value\n        <Quantity(1.0, 'gram')>\n        \"\"\"\n        retval = self.query(\"TA\")\n        return u.Quantity(float(retval[0]), retval[1])\n\n    @tare_value.setter\n    def tare_value(self, value):\n        value = assume_units(value, u.gram)\n        value = value.to(u.gram)\n        _ = self.query(f\"TA {value.magnitude} g\")\n\n    @property\n    def weight(self):\n        \"\"\"\n        Get the weight.\n\n        If you want to get the immediate (maybe unstable) weight, plese set the\n        weight mode accordingly.\n\n        :return: Weight\n        :rtype: u.Quantity\n\n        Example usage:\n\n        >>> import instruments as ik\n        >>> inst = ik.mettler_toledo.MTSICS.open_serial('/dev/ttyUSB0', 9600)\n        >>> inst.weight\n        <Quantity(1.0, 'gram')>\n        \"\"\"\n        msg = \"SI\" if self.weight_mode.value else \"S\"\n        retval = self.query(msg)\n        return u.Quantity(float(retval[0]), retval[1])\n\n    @property\n    def weight_mode(self):\n        \"\"\"Get/set the weight mode.\n\n        By default, it starts in ``MTSICS.WeightMode.stable``.\n\n        :return: Weight mode\n        :rtype: MTSICS.WeightMode\n\n        :raises TypeError: Weight mode is not of type ``MTSICS.WeightMode``\n\n        Example usage:\n\n        >>> import instruments as ik\n        >>> inst = ik.mettler_toledo.MTSICS.open_serial('/dev/ttyUSB0', 9600)\n        >>> inst.weight_mode = inst.WeightMode.immediately\n        >>> inst.weight_mode\n        <Weight.immediately>\n        \"\"\"\n        return self._weight_mode\n\n    @weight_mode.setter\n    def weight_mode(self, value):\n        if not isinstance(value, MTSICS.WeightMode):\n            raise TypeError(\"Weight mode must be of type `MTSICS.WeightMode\")\n        self._weight_mode = value\n"
  },
  {
    "path": "src/instruments/minghe/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing MingHe instruments\n\"\"\"\n\nfrom .mhs5200a import MHS5200\n"
  },
  {
    "path": "src/instruments/minghe/mhs5200a.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides the support for the MingHe low-cost function generator.\n\nClass originally contributed by Catherine Holloway.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom enum import Enum\n\nfrom instruments.abstract_instruments import FunctionGenerator\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import ProxyList, assume_units\n\n# CLASSES #####################################################################\n\n\nclass MHS5200(FunctionGenerator):\n    \"\"\"\n    The MHS5200 is a low-cost, 2 channel function generator.\n\n    There is no user manual, but Al Williams has reverse-engineered the\n    communications protocol:\n    https://github.com/wd5gnr/mhs5200a/blob/master/MHS5200AProtocol.pdf\n    \"\"\"\n\n    def __init__(self, filelike):\n        super().__init__(filelike)\n        self._channel_count = 2\n        self.terminator = \"\\r\\n\"\n\n    def _ack_expected(self, msg=\"\"):\n        if msg.find(\":r\") == 0:\n            return None\n        # most commands res\n        return \"ok\"\n\n    # INNER CLASSES #\n\n    class Channel(FunctionGenerator.Channel):\n        \"\"\"\n        Class representing a channel on the MHS52000.\n        \"\"\"\n\n        # pylint: disable=protected-access\n\n        __CHANNEL_NAMES = {1: \"1\", 2: \"2\"}\n\n        def __init__(self, mhs, idx):\n            self._mhs = mhs\n            super().__init__(parent=mhs, name=idx)\n            # Use zero-based indexing for the external API, but one-based\n            # for talking to the instrument.\n            self._idx = idx + 1\n            self._chan = self.__CHANNEL_NAMES[self._idx]\n            self._count = 0\n\n        def _get_amplitude_(self):\n            query = f\":r{self._chan}a\"\n            response = self._mhs.query(query)\n            return float(response.replace(query, \"\")) / 100.0, self._mhs.VoltageMode.rms\n\n        def _set_amplitude_(self, magnitude, units):\n            if (\n                units == self._mhs.VoltageMode.peak_to_peak\n                or units == self._mhs.VoltageMode.rms\n            ):\n                magnitude = assume_units(magnitude, \"V\").to(u.V).magnitude\n            elif units == self._mhs.VoltageMode.dBm:\n                raise NotImplementedError(\"Decibel units are not supported.\")\n            magnitude *= 100\n            query = f\":s{self._chan}a{int(magnitude)}\"\n            self._mhs.sendcmd(query)\n\n        @property\n        def duty_cycle(self):\n            \"\"\"\n            Gets/Sets the duty cycle of this channel.\n\n            :units: A fraction\n            :type: `float`\n            \"\"\"\n            query = f\":r{self._chan}d\"\n            response = self._mhs.query(query)\n            duty = float(response.replace(query, \"\")) / 10.0\n            return duty\n\n        @duty_cycle.setter\n        def duty_cycle(self, new_val):\n            query = f\":s{self._chan}d{int(100.0 * new_val)}\"\n            self._mhs.sendcmd(query)\n\n        @property\n        def enable(self):\n            \"\"\"\n            Gets/Sets the enable state of this channel.\n\n            :type: `bool`\n            \"\"\"\n            query = f\":r{self._chan}b\"\n            return int(self._mhs.query(query).replace(query, \"\").replace(\"\\r\", \"\"))\n\n        @enable.setter\n        def enable(self, newval):\n            query = f\":s{self._chan}b{int(newval)}\"\n            self._mhs.sendcmd(query)\n\n        @property\n        def frequency(self):\n            \"\"\"\n            Gets/Sets the frequency of this channel.\n\n            :units: As specified (if a `~pint.Quantity`) or assumed to be\n                of units hertz.\n            :type: `~pint.Quantity`\n            \"\"\"\n            query = f\":r{self._chan}f\"\n            response = self._mhs.query(query)\n            freq = float(response.replace(query, \"\")) * u.Hz\n            return freq / 100.0\n\n        @frequency.setter\n        def frequency(self, new_val):\n            new_val = assume_units(new_val, u.Hz).to(u.Hz).magnitude * 100.0\n            query = f\":s{self._chan}f{int(new_val)}\"\n            self._mhs.sendcmd(query)\n\n        @property\n        def offset(self):\n            \"\"\"\n            Gets/Sets the offset of this channel.\n\n            The fraction of the duty cycle to offset the function by.\n\n            :type: `float`\n            \"\"\"\n            # need to convert\n            query = f\":r{self._chan}o\"\n            response = self._mhs.query(query)\n            return int(response.replace(query, \"\")) / 100.0 - 1.20\n\n        @offset.setter\n        def offset(self, new_val):\n            new_val = int(new_val * 100) + 120\n            query = f\":s{self._chan}o{new_val}\"\n            self._mhs.sendcmd(query)\n\n        @property\n        def phase(self):\n            \"\"\"\n            Gets/Sets the phase of this channel.\n\n            :units: As specified (if a `~pint.Quantity`) or assumed to be\n                of degrees.\n            :type: `~pint.Quantity`\n            \"\"\"\n            # need to convert\n            query = f\":r{self._chan}p\"\n            response = self._mhs.query(query)\n            return int(response.replace(query, \"\")) * u.deg\n\n        @phase.setter\n        def phase(self, new_val):\n            new_val = assume_units(new_val, u.deg).to(\"deg\").magnitude\n            query = f\":s{self._chan}p{int(new_val)}\"\n            self._mhs.sendcmd(query)\n\n        @property\n        def function(self):\n            \"\"\"\n            Gets/Sets the wave type of this channel.\n\n            :type: `MHS5200.Function`\n            \"\"\"\n            query = f\":r{self._chan}w\"\n            response = self._mhs.query(query).replace(query, \"\")\n            return self._mhs.Function(int(response))\n\n        @function.setter\n        def function(self, new_val):\n            query = f\":s{self._chan}w{self._mhs.Function(new_val).value}\"\n            self._mhs.sendcmd(query)\n\n    class Function(Enum):\n        \"\"\"\n        Enum containing valid wave modes for\n        \"\"\"\n\n        sine = 0\n        square = 1\n        triangular = 2\n        sawtooth_up = 3\n        sawtooth_down = 4\n\n    @property\n    def channel(self):\n        \"\"\"\n        Gets a specific channel object. The desired channel is specified like\n        one would access a list.\n\n        For instance, this would print the counts of the first channel::\n\n            >>> import instruments as ik\n            >>> mhs = ik.minghe.MHS5200.open_serial(vid=1027, pid=24577,\n            baud=19200, timeout=1)\n            >>> print(mhs.channel[0].frequency)\n\n        :rtype: `list`[`MHS5200.Channel`]\n        \"\"\"\n        return ProxyList(self, MHS5200.Channel, range(self._channel_count))\n\n    @property\n    def serial_number(self):\n        \"\"\"\n        Get the serial number, as an int\n\n        :rtype: int\n        \"\"\"\n        query = \":r0c\"\n        response = self.query(query)\n        response = response.replace(query, \"\").replace(\"\\r\", \"\")\n        return response\n\n    def _get_amplitude_(self):\n        raise NotImplementedError()\n\n    def _set_amplitude_(self, magnitude, units):\n        raise NotImplementedError()\n"
  },
  {
    "path": "src/instruments/named_struct.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nClass for quickly defining C-like structures with named fields.\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport struct\nfrom collections import OrderedDict\n\n# DESIGN NOTES ################################################################\n\n# This class uses the Django-like strategy described at\n#     http://stackoverflow.com/a/3288988/267841\n# to assign a \"birthday\" to each Field as it's instantiated. We can thus sort\n# each Field in a NamedStruct by its birthday.\n\n# Notably, this hack is not at all required on Python 3.6:\n#     https://www.python.org/dev/peps/pep-0520/\n\n# TODO: arrays other than string arrays do not currently work.\n\n# PYLINT CONFIGURATION ########################################################\n\n# All of the classes in this module need to interact with each other rather\n# deeply, so we disable the protected-access check within this module.\n\n# pylint:disable=protected-access\n\n# CLASSES #####################################################################\n\n\nclass Field:\n    \"\"\"\n    A named field within a C-style structure.\n\n    :param str fmt: Format for the field, corresponding to the\n        documentation of the :mod:`struct` standard library package.\n    \"\"\"\n\n    __n_fields_created = 0\n    _field_birthday = None\n\n    _fmt = \"\"\n    _name = None\n    _owner_type = object\n\n    def __init__(self, fmt, strip_null=False):\n        super().__init__()\n\n        # Record our birthday so that we can sort fields later.\n        self._field_birthday = Field.__n_fields_created\n        Field.__n_fields_created += 1\n\n        self._fmt = fmt.strip()\n        self._strip_null = strip_null\n\n        # If we're given a length, check that it\n        # makes sense.\n        if self._fmt[:-1] and int(self._fmt[:-1]) < 0:\n            raise TypeError(\"Field is specified with negative length.\")\n\n    def is_significant(self):\n        return not self._fmt.endswith(\"x\")\n\n    @property\n    def fmt_char(self):\n        \"\"\"\n        Gets the format character\n        \"\"\"\n        return self._fmt[-1]\n\n    def __len__(self):\n        if self._fmt[:-1]:\n            # Although we know that length > 0, this abs ensures that static\n            # code checks are happy with __len__ always returning a positive number\n            return abs(int(self._fmt[:-1]))\n\n        raise TypeError(\"Field is scalar and has no len().\")\n\n    def __repr__(self):\n        if self._owner_type:  # pylint: disable=using-constant-test\n            return \"<Field {} of {}, fmt={}>\".format(\n                self._name, self._owner_type, self._fmt\n            )\n\n        return f\"<Unbound field, fmt={self._fmt}>\"\n\n    def __str__(self):\n        n, fmt_char = len(self), self.fmt_char\n        c_type = {\n            \"x\": \"char\",\n            \"c\": \"char\",\n            \"b\": \"char\",\n            \"B\": \"unsigned char\",\n            \"?\": \"bool\",\n            \"h\": \"short\",\n            \"H\": \"unsigned short\",\n            \"i\": \"int\",\n            \"I\": \"unsigned int\",\n            \"l\": \"long\",\n            \"L\": \"unsigned long\",\n            \"q\": \"long long\",\n            \"Q\": \"unsigned long long\",\n            \"f\": \"float\",\n            \"d\": \"double\",\n            # NB: no [], since that will be implied by n.\n            \"s\": \"char\",\n            \"p\": \"char\",\n            \"P\": \"void *\",\n        }[fmt_char]\n\n        if n:\n            c_type = f\"{c_type}[{n}]\"\n        return f\"{c_type} {self._name}\" if self.is_significant() else c_type\n\n    # DESCRIPTOR PROTOCOL #\n\n    def __get__(self, obj, type=None):\n        return obj._values[self._name]\n\n    def __set__(self, obj, value):\n        obj._values[self._name] = value\n\n\nclass StringField(Field):\n    \"\"\"\n    Represents a field that is interpreted as a Python string.\n\n    :param int length: Maximum allowed length of the field, as\n        measured in the number of bytes used by its encoding.\n        Note that if a shorter string is provided, it will\n        be padded by null bytes.\n    :param str encoding: Name of an encoding to use in serialization\n        and deserialization to Python strings.\n    :param bool strip_null: If `True`, null bytes (``'\\x00'``) will\n        be removed from the right upon deserialization.\n    \"\"\"\n\n    _strip_null = False\n    _encoding = \"ascii\"\n\n    def __init__(self, length, encoding=\"ascii\", strip_null=False):\n        super().__init__(f\"{length}s\")\n        self._strip_null = strip_null\n        self._encoding = encoding\n\n    def __set__(self, obj, value):\n        if isinstance(value, bytes):\n            value = value.decode(self._encoding)\n        if self._strip_null:\n            value = value.rstrip(\"\\x00\")\n        value = value.encode(self._encoding)\n\n        super().__set__(obj, value)\n\n    def __get__(self, obj, type=None):\n        return super().__get__(obj, type=type).decode(self._encoding)\n\n\nclass Padding(Field):\n    \"\"\"\n    Represents a field whose value is insignificant, and will not\n    be kept in serialization and deserialization.\n\n    :param int n_bytes: Number of padding bytes occupied by this field.\n    \"\"\"\n\n    def __init__(self, n_bytes=1):\n        super().__init__(f\"{n_bytes}x\")\n\n\nclass HasFields(type):\n    \"\"\"\n    Metaclass used for NamedStruct\n    \"\"\"\n\n    def __new__(mcs, name, bases, attrs):\n        # Since this is a metaclass, the __new__ method observes\n        # creation of new *classes* and not new instances.\n        # We call the superclass of HasFields, which is another\n        # metaclass, to do most of the heavy lifting of creating\n        # the new class.\n        cls = super().__new__(mcs, name, bases, attrs)\n\n        # We now sort the fields by their birthdays and store them in an\n        # ordered dict for easier look up later.\n        cls._fields = OrderedDict(\n            [\n                (field_name, field)\n                for field_name, field in sorted(\n                    (\n                        (field_name, field)\n                        for field_name, field in attrs.items()\n                        if isinstance(field, Field)\n                    ),\n                    key=lambda item: item[1]._field_birthday,\n                )\n            ]\n        )\n\n        # Assign names and owner types to each field so that they can follow\n        # the descriptor protocol.\n        for field_name, field in cls._fields.items():\n            field._name = field_name\n            field._owner_type = cls\n\n        # Associate a struct.Struct instance with the new class\n        # that defines how to pack/unpack the new type.\n        cls._struct = struct.Struct(\n            # TODO: support alignment char at start.\n            \" \".join([field._fmt for field in cls._fields.values()])\n        )\n\n        return cls\n\n\nclass NamedStruct(metaclass=HasFields):\n    \"\"\"\n    Represents a C-style struct with one or more named fields,\n    useful for packing and unpacking serialized data documented\n    in terms of C examples. For instance, consider a struct of the\n    form::\n\n        typedef struct {\n            unsigned long a = 0x1234;\n            char[12] dummy;\n            unsigned char b = 0xab;\n        } Foo;\n\n    This struct can be represented as the following NamedStruct::\n\n        class Foo(NamedStruct):\n            a = Field('L')\n            dummy = Padding(12)\n            b = Field('B')\n\n        foo = Foo(a=0x1234, b=0xab)\n    \"\"\"\n\n    # Provide reasonable defaults for the lowercase-f-fields\n    # created by HasFields. This will prevent a few edge cases,\n    # allow type inference and will prevent pylint false positives.\n    _fields = {}\n    _struct = None\n\n    def __init__(self, **kwargs):\n        super().__init__()\n        self._values = OrderedDict(\n            [\n                (field._name, None)\n                for field in filter(Field.is_significant, self._fields.values())\n            ]\n        )\n\n        for field_name, value in kwargs.items():\n            setattr(self, field_name, value)\n\n    def _to_seq(self):\n        return tuple(self._values.values())\n\n    @classmethod\n    def _from_seq(cls, new_values):\n        return cls(\n            **{\n                field._name: new_value\n                for field, new_value in zip(\n                    list(filter(Field.is_significant, cls._fields.values())), new_values\n                )\n            }\n        )\n\n    def pack(self):\n        \"\"\"\n        Packs this instance into bytes, suitable for transmitting over\n        a network or recording to disc. See :func:`struct.pack` for details.\n\n        :return bytes packed_data: A serialized representation of this\n            instance.\n        \"\"\"\n        return self._struct.pack(*self._to_seq())\n\n    @classmethod\n    def unpack(cls, buffer):\n        \"\"\"\n        Given a buffer, unpacks it into an instance of this NamedStruct.\n        See :func:`struct.unpack` for details.\n\n        :param bytes buffer: Data to use in creating a new instance.\n        :return: The new instance represented by `buffer`.\n        \"\"\"\n        return cls._from_seq(cls._struct.unpack(buffer))\n\n    def __eq__(self, other):\n        if not isinstance(other, NamedStruct):\n            return False\n\n        return self._values == other._values\n\n    def __hash__(self):\n        return hash(self._values)\n\n    def __str__(self):\n        return \"{name} {{\\n{fields}\\n}}\".format(\n            name=type(self).__name__,\n            fields=\"\\n\".join(\n                [\n                    \"    {field}{value};\".format(\n                        field=field,\n                        value=(\n                            f\" = {repr(self._values[field._name])}\"\n                            if field.is_significant()\n                            else \"\"\n                        ),\n                    )\n                    for field in self._fields.values()\n                ]\n            ),\n        )\n"
  },
  {
    "path": "src/instruments/newport/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing Newport instruments\n\"\"\"\n\nfrom .agilis import AGUC2\n\nfrom .errors import NewportError\nfrom .newportesp301 import NewportESP301\n\nfrom .newport_pmc8742 import PicoMotorController8742\n"
  },
  {
    "path": "src/instruments/newport/agilis.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the Newport Agilis Controller AG-UC2 only (currently).\n\nAgilis controllers are piezo driven motors that do not have support for units.\nAll units used in this document are given as steps.\n\nCurrently I only have a AG-PR100 rotation stage available for testing. This\ndevice does not contain a limit switch and certain routines are therefore\ncompletely untested! These are labeled in their respective docstring with:\n    `UNTESTED: SEE COMMENT ON TOP`\n\nThe governing document for the commands and implementation is:\n\nAgilis Series, Piezo Motor Driven Components, User's Manual, v2.2.x,\nby Newport, especially chapter 4.7: \"ASCII Command Set\"\nDocument number from footer: EDH0224En5022 — 10/12\n\nRoutines not implemented at all:\n- Measure current position (MA command):\n  This routine interrupts the communication and\n  restarts it afterwards. It can, according to the documentation, take up to\n  2 minutes to complete. It is furthermore only available on stages with limit\n  switches. I currently do not have the capability to implement this therefore.\n- Absolute Move (PA command):\n  Exactly the same reason as for MA command.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nimport time\n\nfrom enum import IntEnum\n\nfrom instruments.abstract_instruments import Instrument\nfrom instruments.util_fns import ProxyList\n\n# CLASSES #####################################################################\n\n\nclass AGUC2(Instrument):\n    \"\"\"\n    Handles the communication with the AGUC2 controller using the serial\n    connection.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> agl = ik.newport.AGUC2.open_serial(port='COM5', baud=921600)\n\n    This loads a controller into the instance `agl`. The two axis are\n    called 'X' (axis 1) and 'Y' (axis 2). Controller commands and settings\n    can be executed as following, as examples:\n\n    Reset the controller:\n\n    >>> agl.reset_controller()\n\n    Print the firmware version:\n\n    >>> print(agl.firmware_version)\n\n    Individual axes can be controlled and queried as following:\n\n    Relative move by 1000 steps:\n\n    >>> agl.axis[\"X\"].move_relative(1000)\n\n    Activate jogging in mode 3:\n\n    >>> agl.axis[\"X\"].jog(3)\n\n    Jogging will continue until the axis is stopped\n\n    >>> agl.axis[\"X\"].stop()\n\n    Query the step amplitude, then set the postive one to +10 and the\n    negative one to -20\n\n    >>> print(agl.axis[\"X\"].step_amplitude)\n    >>> agl.axis[\"X\"].step_amplitude = 10\n    >>> agl.axis[\"X\"].step_amplitude = -20\n    \"\"\"\n\n    def __init__(self, filelike):\n        super().__init__(filelike)\n\n        # Instrument requires '\\r\\n' line termination\n        self.terminator = \"\\r\\n\"\n\n        # Some local variables\n        self._remote_mode = False\n        self._sleep_time = 0.25\n\n    class Axis:\n        \"\"\"\n        Class representing one axis attached to a Controller. This will likely\n        work with the AG-UC8 controller as well.\n\n        .. warning:: This class should NOT be manually created by the user. It is\n            designed to be initialized by a Controller class\n        \"\"\"\n\n        def __init__(self, cont, ax):\n            if not isinstance(cont, AGUC2):\n                raise TypeError(\"Don't do that.\")\n\n            # set axis integer\n            if isinstance(ax, AGUC2.Axes):\n                self._ax = ax.value\n            else:\n                self._ax = ax\n\n            # set controller\n            self._cont = cont\n\n        # PROPERTIES #\n\n        @property\n        def axis_status(self):\n            \"\"\"\n            Returns the status of the current axis.\n            \"\"\"\n            resp = self._cont.ag_query(f\"{int(self._ax)} TS\")\n            if resp.find(\"TS\") == -1:\n                return \"Status code query failed.\"\n\n            resp = int(resp.replace(str(int(self._ax)) + \"TS\", \"\"))\n            status_message = agilis_status_message(resp)\n            return status_message\n\n        @property\n        def jog(self):\n            \"\"\"\n            Start jog motion / get jog mode\n            Defined jog steps are defined with `step_amplitude` function (default\n            16). If a jog mode is supplied, the jog motion is started. Otherwise\n            the current jog mode is queried. Valid jog modes are:\n\n            -4 — Negative direction, 666 steps/s at defined step amplitude.\n            -3 — Negative direction, 1700 steps/s at max. step amplitude.\n            -2 — Negative direction, 100 step/s at max. step amplitude.\n            -1 — Negative direction, 5 steps/s at defined step amplitude.\n            +0 — No move, go to READY state.\n            +1 — Positive direction, 5 steps/s at defined step amplitude.\n            +2 — Positive direction, 100 steps/s at max. step amplitude.\n            +3 — Positive direction, 1700 steps/s at max. step amplitude.\n            +4 — Positive direction, 666 steps/s at defined step amplitude.\n\n            :return: Jog motion set\n            :rtype: `int`\n            \"\"\"\n            resp = self._cont.ag_query(f\"{int(self._ax)} JA?\")\n            return int(resp.split(\"JA\")[1])\n\n        @jog.setter\n        def jog(self, mode):\n            mode = int(mode)\n            if mode < -4 or mode > 4:\n                raise ValueError(\"Jog mode out of range. Must be between -4 and \" \"4.\")\n\n            self._cont.ag_sendcmd(f\"{int(self._ax)} JA {mode}\")\n\n        @property\n        def number_of_steps(self):\n            \"\"\"\n            Returns the number of accumulated steps in forward direction minus\n            the number of steps in backward direction since powering the\n            controller or since the last ZP (zero position) command, whatever\n            was last.\n\n            Note:\n            The step size of the Agilis devices are not 100% repeatable and\n            vary between forward and backward direction. Furthermore, the step\n            size can be modified using the SU command. Consequently, the TP\n            command provides only limited information about the actual position\n            of the device. In particular, an Agilis device can be at very\n            different positions even though a TP command may return the same\n            result.\n\n            :return: Number of steps\n            :rtype: int\n            \"\"\"\n            resp = self._cont.ag_query(f\"{int(self._ax)} TP\")\n            return int(resp.split(\"TP\")[1])\n\n        @property\n        def move_relative(self):\n            \"\"\"\n            Moves the axis by nn steps / Queries the status of the axis.\n            Steps must be given a number that can be converted to a signed integer\n            between -2,147,483,648 and 2,147,483,647.\n            If queried, command returns the current target position. At least this\n            is the expected behaviour, never worked with the rotation stage.\n            \"\"\"\n            resp = self._cont.ag_query(f\"{int(self._ax)} PR?\")\n            return int(resp.split(\"PR\")[1])\n\n        @move_relative.setter\n        def move_relative(self, steps):\n            steps = int(steps)\n            if steps < -2147483648 or steps > 2147483647:\n                raise ValueError(\n                    \"Number of steps are out of range. They must be \"\n                    \"between -2,147,483,648 and 2,147,483,647\"\n                )\n\n            self._cont.ag_sendcmd(f\"{int(self._ax)} PR {steps}\")\n\n        @property\n        def move_to_limit(self):\n            \"\"\"\n            UNTESTED: SEE COMMENT ON TOP\n\n            The  command functions properly only with devices that feature a\n            limit switch like models AG-LS25, AG-M050L and AG-M100L.\n\n            Starts a jog motion at a defined speed to the limit and stops\n            automatically when the limit is activated. See `jog` command for\n            details on available modes.\n\n            Returns the distance of the current position to the limit in\n            1/1000th of the total travel.\n            \"\"\"\n            resp = self._cont.ag_query(f\"{int(self._ax)} MA?\")\n            return int(resp.split(\"MA\")[1])\n\n        @move_to_limit.setter\n        def move_to_limit(self, mode):\n            mode = int(mode)\n            if mode < -4 or mode > 4:\n                raise ValueError(\"Jog mode out of range. Must be between -4 and \" \"4.\")\n\n            self._cont.ag_sendcmd(f\"{int(self._ax)} MA {mode}\")\n\n        @property\n        def step_amplitude(self):\n            \"\"\"\n            Sets / Gets the step_amplitude.\n\n            Sets the step amplitude (step size) in positive and / or negative\n            direction. If the parameter is positive, it will set the step\n            amplitude in the forward direction. If the parameter is negative,\n            it will set the step amplitude in the backward direction. You can also\n            provide a tuple or list of two values (one positive, one negative),\n            which will set both values.\n            Valid values are between -50 and 50, except for 0.\n\n            :return: Tuple of first negative, then positive step amplitude\n                response.\n            :rtype: (`int`, `int`)\n            \"\"\"\n            resp_neg = self._cont.ag_query(f\"{int(self._ax)} SU-?\")\n            resp_pos = self._cont.ag_query(f\"{int(self._ax)} SU+?\")\n            return int(resp_neg.split(\"SU\")[1]), int(resp_pos.split(\"SU\")[1])\n\n        @step_amplitude.setter\n        def step_amplitude(self, nns):\n            if not isinstance(nns, tuple) and not isinstance(nns, list):\n                nns = [nns]\n\n            # check all values for validity\n            for nn in nns:\n                nn = int(nn)\n                if nn < -50 or nn > 50 or nn == 0:\n                    raise ValueError(\n                        \"Step amplitude {} outside the valid range. \"\n                        \"It must be between -50 and -1 or between \"\n                        \"1 and 50.\".format(nn)\n                    )\n\n            for nn in nns:\n                self._cont.ag_sendcmd(f\"{int(self._ax)} SU {int(nn)}\")\n\n        @property\n        def step_delay(self):\n            \"\"\"\n            Sets/gets the step delay of stepping mode. The delay applies for both\n            positive and negative directions. The delay is programmed as multiple\n            of 10µs. For example, a delay of 40 is equivalent to\n            40 x 10 µs = 400 µs. The maximum value of the parameter is equal to a\n            delay of 2 seconds between pulses. By default, after reset, the value\n            is 0.\n            Setter: value must be integer between 0 and 200000 included\n\n            :return: Step delay\n            :rtype: `int`\n            \"\"\"\n            resp = self._cont.ag_query(f\"{int(self._ax)} DL?\")\n            return int(resp.split(\"DL\")[1])\n\n        @step_delay.setter\n        def step_delay(self, nn):\n            nn = int(nn)\n            if nn < 0 or nn > 200000:\n                raise ValueError(\n                    \"Step delay is out of range. It must be between \" \"0 and 200000.\"\n                )\n\n            self._cont.ag_sendcmd(f\"{int(self._ax)} DL {nn}\")\n\n        # MODES #\n\n        def am_i_still(self, max_retries=5):\n            \"\"\"\n            Function to test if an axis stands still. It queries the status of\n            the given axis and returns True (if axis is still) or False if it is\n            moving.\n            The reason this routine is implemented is because the status messages\n            can time out. If a timeout occurs, this routine will retry the query\n            until `max_retries` is reached. If query is still not successful, an\n            IOError will be raised.\n\n            :param int max_retries: Maximum number of retries\n\n            :return: True if the axis is still, False if the axis is moving\n            :rtype: bool\n            \"\"\"\n            retries = 0\n\n            while retries < max_retries:\n                status = self.axis_status\n                if status == agilis_status_message(0):\n                    return True\n                elif (\n                    status == agilis_status_message(1)\n                    or status == agilis_status_message(2)\n                    or status == agilis_status_message(3)\n                ):\n                    return False\n                else:\n                    retries += 1\n\n            raise OSError(\n                \"The function `am_i_still` ran out of maximum retries. \"\n                \"Could not query the status of the axis.\"\n            )\n\n        def stop(self):\n            \"\"\"\n            Stops the axis. This is useful to interrupt a jogging motion.\n            \"\"\"\n            self._cont.ag_sendcmd(f\"{int(self._ax)} ST\")\n\n        def zero_position(self):\n            \"\"\"\n            Resets the step counter to zero. See `number_of_steps` for details.\n            \"\"\"\n            self._cont.ag_sendcmd(f\"{int(self._ax)} ZP\")\n\n    # ENUMS #\n\n    class Axes(IntEnum):\n        \"\"\"\n        Enumeration of valid delay channels for the AG-UC2 controller.\n        \"\"\"\n\n        X = 1\n        Y = 2\n\n    # INNER CLASSES #\n\n    # PROPERTIES #\n\n    @property\n    def axis(self):\n        \"\"\"\n        Gets a specific axis object.\n\n        The desired axis is accessed by passing an EnumValue from\n        `~AGUC2.Channels`. For example, to access the X axis (axis 1):\n\n        >>> import instruments as ik\n        >>> agl = ik.newport.AGUC2.open_serial(port='COM5', baud=921600)\n        >>> agl.axis[\"X\"].move_relative(1000)\n\n        See example in `AGUC2` for a more details\n\n        :rtype: `AGUC2.Axis`\n        \"\"\"\n        self.enable_remote_mode = True\n        return ProxyList(self, self.Axis, AGUC2.Axes)\n\n    @property\n    def enable_remote_mode(self):\n        \"\"\"\n        Gets / sets the status of remote mode.\n        \"\"\"\n        return self._remote_mode\n\n    @enable_remote_mode.setter\n    def enable_remote_mode(self, newval):\n        if newval and not self._remote_mode:\n            self._remote_mode = True\n            self.ag_sendcmd(\"MR\")\n        elif not newval and self._remote_mode:\n            self._remote_mode = False\n            self.ag_sendcmd(\"ML\")\n\n    @property\n    def error_previous_command(self):\n        \"\"\"\n        Retrieves the error of the previous command and translates it into a\n        string. The string is returned\n        \"\"\"\n        resp = self.ag_query(\"TE\")\n\n        if resp.find(\"TE\") == -1:\n            return \"Error code query failed.\"\n\n        resp = int(resp.replace(\"TE\", \"\"))\n        error_message = agilis_error_message(resp)\n        return error_message\n\n    @property\n    def firmware_version(self):\n        \"\"\"\n        Returns the firmware version of the controller\n        \"\"\"\n        resp = self.ag_query(\"VE\")\n        return resp\n\n    @property\n    def limit_status(self):\n        \"\"\"\n        PARTLY UNTESTED: SEE COMMENT ABOVE\n\n        Returns the limit switch status of the controller. Possible returns\n        are:\n\n            - PH0: No limit switch is active\n            - PH1: Limit switch of axis #1 (X) is active,\n                   limit switch of axis #2 (Y)  is not active\n            - PH2: Limit switch of axis #2 (Y) is active,\n                   limit switch of axis #1 (X) is not active\n            - PH3: Limit switches of axis #1 (X) and axis #2 (Y) are active\n\n        If device has no limit switch, this routine always returns PH0\n        \"\"\"\n        self.enable_remote_mode = True\n        resp = self.ag_query(\"PH\")\n        return resp\n\n    @property\n    def sleep_time(self):\n        \"\"\"\n        The device often times out. Therefore, a sleep time can be set. The\n        routine will wait for this amount (in seconds) every time after a\n        command or a query are sent.\n        Setting the sleep time: Give time in seconds\n        If queried: Returns the sleep time in seconds as a float\n        \"\"\"\n        return self._sleep_time\n\n    @sleep_time.setter\n    def sleep_time(self, t):\n        if t < 0:\n            raise ValueError(\"Sleep time must be >= 0.\")\n\n        self._sleep_time = float(t)\n\n    # MODES #\n\n    def reset_controller(self):\n        \"\"\"\n        Resets the controller. All temporary settings are reset to the default\n        value. Controller is put into local model.\n        \"\"\"\n        self._remote_mode = False\n        self.ag_sendcmd(\"RS\")\n\n    # SEND COMMAND AND QUERY ROUTINES AGILIS STYLE #\n\n    def ag_sendcmd(self, cmd):\n        \"\"\"\n        Sends the command, then sleeps\n        \"\"\"\n        self.sendcmd(cmd)\n        time.sleep(self._sleep_time)\n\n    def ag_query(self, cmd, size=-1):\n        \"\"\"\n        This runs the query command. However, the query command often times\n        out for this device. The response of all queries are always strings.\n        If timeout occurs, the response will be:\n        \"Query timed out.\"\n        \"\"\"\n        try:\n            resp = self.query(cmd, size=size)\n        except OSError:\n            resp = \"Query timed out.\"\n\n        # sleep\n        time.sleep(self._sleep_time)\n\n        return resp\n\n\ndef agilis_error_message(error_code):\n    \"\"\"\n    Returns a string with th error message for a given Agilis error code.\n\n    :param int error_code: error code as an integer\n\n    :return: error message\n    :rtype: string\n    \"\"\"\n    if not isinstance(error_code, int):\n        return \"Error code is not an integer.\"\n\n    error_dict = {\n        0: \"No error\",\n        -1: \"Unknown command\",\n        -2: \"Axis out of range (must be 1 or 2, or must not be specified)\",\n        -3: \"Wrong format for parameter nn (or must not be specified)\",\n        -4: \"Parameter nn out of range\",\n        -5: \"Not allowed in local mode\",\n        -6: \"Not allowed in current state\",\n    }\n\n    if error_code in error_dict.keys():\n        return error_dict[error_code]\n    else:\n        return \"An unknown error occurred.\"\n\n\ndef agilis_status_message(status_code):\n    \"\"\"\n    Returns a string with the status message for a given Agilis status\n    code.\n\n    :param int status_code: status code as returned\n\n    :return: status message\n    :rtype: string\n    \"\"\"\n    if not isinstance(status_code, int):\n        return \"Status code is not an integer.\"\n\n    status_dict = {\n        0: \"Ready (not moving).\",\n        1: \"Stepping (currently executing a `move_relative` command).\",\n        2: \"Jogging (currently executing a `jog` command with command\"\n        \"parameter different than 0).\",\n        3: \"Moving to limit (currently executing `measure_current_position`, \"\n        \"`move_to_limit`, or `move_absolute` command).\",\n    }\n\n    if status_code in status_dict.keys():\n        return status_dict[status_code]\n    else:\n        return \"An unknown status occurred.\"\n"
  },
  {
    "path": "src/instruments/newport/errors.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides common error handling for Newport devices.\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport datetime\n\n# CLASSES ####################################################################\n\n\nclass NewportError(IOError):\n    \"\"\"\n    Raised in response to an error with a Newport-brand instrument.\n    \"\"\"\n\n    start_time = datetime.datetime.now()\n\n    # Dict Containing all possible errors.\n    # Uses strings for keys in order to handle axis\n    messageDict = {\n        \"0\": \"NO ERROR DETECTED\",\n        \"1\": \"PCI COMMUNICATION TIME-OUT\",\n        \"2\": \"Reserved for future use\",\n        \"3\": \"Reserved for future use\",\n        \"4\": \"EMERGENCY SOP ACTIVATED\",\n        \"5\": \"Reserved for future use\",\n        \"6\": \"COMMAND DOES NOT EXIST\",\n        \"7\": \"PARAMETER OUT OF RANGE\",\n        \"8\": \"CABLE INTERLOCK ERROR\",\n        \"9\": \"AXIS NUMBER OUT OF RANGE\",\n        \"10\": \"Reserved for future use\",\n        \"11\": \"Reserved for future use\",\n        \"12\": \"Reserved for future use\",\n        \"13\": \"GROUP NUMBER MISSING\",\n        \"14\": \"GROUP NUMBER OUT OF RANGE\",\n        \"15\": \"GROUP NUMBER NOT ASSIGNED\",\n        \"16\": \"GROUP NUMBER ALREADY ASSIGNED\",\n        \"17\": \"GROUP AXIS OUT OF RANGE\",\n        \"18\": \"GROUP AXIS ALREADY ASSIGNED\",\n        \"19\": \"GROUP AXIS DUPLICATED\",\n        \"20\": \"DATA ACQUISITION IS BUSY\",\n        \"21\": \"DATA ACQUISITION SETUP ERROR\",\n        \"22\": \"DATA ACQUISITION NOT ENABLED\",\n        \"23\": \"SERVO CYCLE (400 µS) TICK FAILURE\",\n        \"24\": \"Reserved for future use\",\n        \"25\": \"DOWNLOAD IN PROGRESS\",\n        \"26\": \"STORED PROGRAM NOT STARTEDL\",\n        \"27\": \"COMMAND NOT ALLOWEDL\",\n        \"28\": \"STORED PROGRAM FLASH AREA FULL\",\n        \"29\": \"GROUP PARAMETER MISSING\",\n        \"30\": \"GROUP PARAMETER OUT OF RANGE\",\n        \"31\": \"GROUP MAXIMUM VELOCITY EXCEEDED\",\n        \"32\": \"GROUP MAXIMUM ACCELERATION EXCEEDED\",\n        \"33\": \"GROUP MAXIMUM DECELERATION EXCEEDED\",\n        \"34\": \" GROUP MOVE NOT ALLOWED DURING MOTION\",\n        \"35\": \"PROGRAM NOT FOUND\",\n        \"36\": \"Reserved for future use\",\n        \"37\": \"AXIS NUMBER MISSING\",\n        \"38\": \"COMMAND PARAMETER MISSING\",\n        \"39\": \"PROGRAM LABEL NOT FOUND\",\n        \"40\": \"LAST COMMAND CANNOT BE REPEATED\",\n        \"41\": \"MAX NUMBER OF LABELS PER PROGRAM EXCEEDED\",\n        \"x00\": \"MOTOR TYPE NOT DEFINED\",\n        \"x01\": \"PARAMETER OUT OF RANGE\",\n        \"x02\": \"AMPLIFIER FAULT DETECTED\",\n        \"x03\": \"FOLLOWING ERROR THRESHOLD EXCEEDED\",\n        \"x04\": \"POSITIVE HARDWARE LIMIT DETECTED\",\n        \"x05\": \"NEGATIVE HARDWARE LIMIT DETECTED\",\n        \"x06\": \"POSITIVE SOFTWARE LIMIT DETECTED\",\n        \"x07\": \"NEGATIVE SOFTWARE LIMIT DETECTED\",\n        \"x08\": \"MOTOR / STAGE NOT CONNECTED\",\n        \"x09\": \"FEEDBACK SIGNAL FAULT DETECTED\",\n        \"x10\": \"MAXIMUM VELOCITY EXCEEDED\",\n        \"x11\": \"MAXIMUM ACCELERATION EXCEEDED\",\n        \"x12\": \"Reserved for future use\",\n        \"x13\": \"MOTOR NOT ENABLED\",\n        \"x14\": \"Reserved for future use\",\n        \"x15\": \"MAXIMUM JERK EXCEEDED\",\n        \"x16\": \"MAXIMUM DAC OFFSET EXCEEDED\",\n        \"x17\": \"ESP CRITICAL SETTINGS ARE PROTECTED\",\n        \"x18\": \"ESP STAGE DEVICE ERROR\",\n        \"x19\": \"ESP STAGE DATA INVALID\",\n        \"x20\": \"HOMING ABORTED\",\n        \"x21\": \"MOTOR CURRENT NOT DEFINED\",\n        \"x22\": \"UNIDRIVE COMMUNICATIONS ERROR\",\n        \"x23\": \"UNIDRIVE NOT DETECTED\",\n        \"x24\": \"SPEED OUT OF RANGE\",\n        \"x25\": \"INVALID TRAJECTORY MASTER AXIS\",\n        \"x26\": \"PARAMETER CHARGE NOT ALLOWED\",\n        \"x27\": \"INVALID TRAJECTORY MODE FOR HOMING\",\n        \"x28\": \"INVALID ENCODER STEP RATIO\",\n        \"x29\": \"DIGITAL I/O INTERLOCK DETECTED\",\n        \"x30\": \"COMMAND NOT ALLOWED DURING HOMING\",\n        \"x31\": \"COMMAND NOT ALLOWED DUE TO GROUP\",\n        \"x32\": \"INVALID TRAJECTORY MODE FOR MOVING\",\n    }\n\n    def __init__(self, errcode=None, timestamp=None):\n        if timestamp is None:\n            self._timestamp = datetime.datetime.now() - NewportError.start_time\n        else:\n            self._timestamp = datetime.datetime.now() - timestamp\n\n        if errcode is not None:\n            # Break the error code into an axis number\n            # and the rest of the code.\n            self._errcode = int(errcode) % 100\n            self._axis = errcode // 100\n            if self._axis == 0:\n                self._axis = None\n                error_message = self.get_message(str(errcode))\n                error = \"Newport Error: {}. Error Message: {}. \" \"At time : {}\".format(\n                    str(errcode), error_message, self._timestamp\n                )\n                super().__init__(error)\n            else:\n                error_message = self.get_message(f\"x{self._errcode:02d}\")\n                error = (\n                    \"Newport Error: {}. Axis: {}. \"\n                    \"Error Message: {}. \"\n                    \"At time : {}\".format(\n                        str(self._errcode), self._axis, error_message, self._timestamp\n                    )\n                )\n                super().__init__(error)\n\n        else:\n            self._errcode = None\n            self._axis = None\n            super().__init__(\"\")\n\n    # PRIVATE METHODS ##\n\n    @staticmethod\n    def get_message(code):\n        \"\"\"\n        Returns the error string for a given error code\n\n        :param str code: Error code as returned by instrument\n        :return: Full error code string\n        :rtype: `str`\n        \"\"\"\n        return NewportError.messageDict.get(code, \"Error code not recognised\")\n\n    # PROPERTIES ##\n\n    @property\n    def timestamp(self):\n        \"\"\"\n        Geturns the timestamp reported by the device as the time\n        at which this error occured.\n\n        :type: `datetime`\n        \"\"\"\n        return self._timestamp\n\n    @property\n    def errcode(self):\n        \"\"\"\n        Gets the error code reported by the device.\n\n        :type: `int`\n        \"\"\"\n        return self._errcode\n\n    @property\n    def axis(self):\n        \"\"\"\n        Gets the axis with which this error is concerned, or\n        `None` if the error was not associated with any particlar\n        axis.\n\n        :type: `int`\n        \"\"\"\n        return self._axis\n"
  },
  {
    "path": "src/instruments/newport/newport_pmc8742.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the Newport Pico Motor Controller 8742\n\nNote that the class is currently only tested with one controller connected,\nhowever, a main controller / secondary controller setup has also been\nimplemented already. Commands are as described in the Picomotor manual.\n\nIf a connection via TCP/IP is opened, the standard port that these devices\nlisten to is 23.\n\nIf you have only one controller connected, everything should work out of\nthe box. Please only use axiss 0 through 3.\n\nIf you have multiple controllers connected (up to 31), you need to set the\naddresses of each controller. This can be done with this this class. See,\ne.g., routines for `controller_address`, `scan_controller`, and `scan`.\nAlso make sure that you set `multiple_controllers` to `True`. This is\nused for internal handling of the class only and does not communicate with\nthe instruments.\nIf you run with multiple controllers, the axiss are as following:\nCh 0 - 3 -> Motors 1 - 4 on controller with address 1\nCh 4 - 7 -> Motors 1 - 4 on controller with address 2\nCh i - i+4 -> Motors 1 - 4 on controller with address i / 4 + 1 (with i%4 = 0)\n\nAll network commands only work with the main controller (this should make\nsense).\n\nIf in multiple controller mode, you can always send controller specific\ncommands by sending them to one individual axis of that controller.\nAny axis works!\n\"\"\"\n\n# IMPORTS #\n\nfrom enum import IntEnum\n\nfrom instruments.abstract_instruments import Instrument\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import assume_units, ProxyList\n\n# pylint: disable=too-many-lines\n\n\nclass PicoMotorController8742(Instrument):\n    \"\"\"Newport Picomotor Controller 8742 Communications Class\n\n    Use this class to communicate with the picomotor controller 8742.\n    Single-controller and multi-controller setup can be used.\n\n    Device can be talked to via TCP/IP or over USB.\n    FixMe: InstrumentKit currently does not communicate correctly via USB!\n\n    Example for TCP/IP controller in single controller mode:\n        >>> import instruments as ik\n        >>> ip = \"192.168.1.2\"\n        >>> port = 23   # this is the default port\n        >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n        >>> motor1 = inst.axis[0]\n        >>> motor1.move_relative = 100\n\n    Example for communications via USB:\n        >>> import instruments as ik\n        >>> pid = 0x4000\n        >>> vid = 0x104d\n        >>> ik.newport.PicoMotorController8742.open_usb(pid=pid, vid=vid)\n        >>> motor3 = inst.axis[2]\n        >>> motor3.move_absolute = -200\n\n    Example for multicontrollers with controller addresses 1 and 2:\n        >>> import instruments as ik\n        >>> ip = \"192.168.1.2\"\n        >>> port = 23   # this is the default port\n        >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n        >>> inst.multiple_controllers = True\n        >>> contr1mot1 = inst.axis[0]\n        >>> contr2mot1 = inst.axis[4]\n        >>> contr1mot1.move_absolute = 200\n        >>> contr2mot1.move_relative = -212\n    \"\"\"\n\n    def __init__(self, filelike):\n        \"\"\"Initialize the PicoMotorController class.\"\"\"\n        super().__init__(filelike)\n\n        # terminator\n        self.terminator = \"\\r\\n\"\n\n        # setup\n        self._multiple_controllers = False\n\n    # INNER CLASSES #\n\n    class Axis:\n        \"\"\"PicoMotorController8742 Axis class for individual motors.\"\"\"\n\n        def __init__(self, parent, idx):\n            \"\"\"Initialize the axis with the parent and the number.\n\n            :raises IndexError: Axis accessed looks like a main / secondary\n                setup, but the flag for `multiple_controllers` is not set\n                appropriately. See introduction.\n            \"\"\"\n            if not isinstance(parent, PicoMotorController8742):\n                raise TypeError(\"Don't do that.\")\n\n            if idx > 3 and not parent.multiple_controllers:\n                raise IndexError(\n                    \"You requested an axis that is only \"\n                    \"available in multi controller mode, \"\n                    \"however, have not enabled it. See \"\n                    \"`multi_controllers` routine.\"\n                )\n\n            # set controller\n            self._parent = parent\n            self._idx = idx % 4 + 1\n\n            # set _address:\n            if self._parent.multiple_controllers:\n                self._address = f\"{idx // 4 + 1}>\"\n            else:\n                self._address = \"\"\n\n        # ENUMS #\n\n        class MotorType(IntEnum):\n            \"\"\"IntEnum Class containing valid MotorTypes\n\n            Use this enum to set the motor type. You can select that no or an\n            unkown motor are connected. See also `motor_check` command to set\n            these values per controller automatically.\n            \"\"\"\n\n            none = 0\n            unknown = 1\n            tiny = 2\n            standard = 3\n\n        # PROPERTIES #\n\n        @property\n        def acceleration(self):\n            \"\"\"Get / set acceleration of axis in steps / sec^2.\n\n            Valid values are between 1 and 200,000 (steps) 1 / sec^2 with the\n            default as 100,000 (steps) 1 / sec^2. If quantity is not unitful,\n            it is assumed that 1 / sec^2 is chosen.\n\n            :return: Acceleration in 1 / sec^2\n            :rtype: u.Quantity(int)\n\n            :raises ValueError: Limit is out of bound.\n\n            Example:\n                >>> import instruments as ik\n                >>> import instruments.units as u\n                >>> ip = \"192.168.1.2\"\n                >>> port = 23   # this is the default port\n                >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n                >>> ax = inst.axis[0]\n                >>> ax.acceleration = u.Quantity(500, 1/u.s**-2)\n            \"\"\"\n            return assume_units(int(self.query(\"AC?\")), u.s**-2)\n\n        @acceleration.setter\n        def acceleration(self, value):\n            value = int(assume_units(value, u.s**-2).to(u.s**-2).magnitude)\n            if not 1 <= value <= 200000:\n                raise ValueError(\n                    f\"Acceleration must be between 1 and \"\n                    f\"200,000 s^-2 but is {value}.\"\n                )\n            self.sendcmd(f\"AC{value}\")\n\n        @property\n        def home_position(self):\n            \"\"\"Get / set home position\n\n            The home position of the device is used, e.g., when moving\n            to a specific position instead of a relative move. Valid values\n            are between -2147483648 and 2147483647.\n\n            :return: Home position.\n            :rtype: int\n\n            :raises ValueError: Set value is out of range.\n\n            Example:\n                >>> import instruments as ik\n                >>> ip = \"192.168.1.2\"\n                >>> port = 23   # this is the default port\n                >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n                >>> ax = inst.axis[0]\n                >>> ax.home_position = 444\n            \"\"\"\n            return int(self.query(\"DH?\"))\n\n        @home_position.setter\n        def home_position(self, value):\n            if not -2147483648 <= value <= 2147483647:\n                raise ValueError(\n                    f\"Home position must be between -2147483648 \"\n                    f\"and 2147483647, but is {value}.\"\n                )\n            self.sendcmd(f\"DH{int(value)}\")\n\n        @property\n        def is_stopped(self):\n            \"\"\"Get if an axis is stopped (not moving).\n\n            :return: Is the axis stopped?\n            :rtype: bool\n\n            Example:\n                >>> import instruments as ik\n                >>> ip = \"192.168.1.2\"\n                >>> port = 23   # this is the default port\n                >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n                >>> ax = inst.axis[0]\n                >>> ax.is_stopped\n                True\n            \"\"\"\n            return bool(int(self.query(\"MD?\")))\n\n        @property\n        def motor_type(self):\n            \"\"\"Set / get the type of motor connected to the axis.\n\n            Use a `MotorType` IntEnum to set this motor type.\n\n            :return: Motor type set.\n            :rtype: MotorType\n\n            :raises TypeError: Set motor type is not of type `MotorType`.\n\n            Example:\n                >>> import instruments as ik\n                >>> ip = \"192.168.1.2\"\n                >>> port = 23   # this is the default port\n                >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n                >>> ax = inst.axis[0]\n                >>> ax.motor_type = ax.MotorType.tiny\n            \"\"\"\n            retval = int(self.query(\"QM?\"))\n            return self.MotorType(retval)\n\n        @motor_type.setter\n        def motor_type(self, value):\n            if not isinstance(value, self.MotorType):\n                raise TypeError(\n                    f\"Set motor type must be of type `MotorType` \"\n                    f\"but is of type {type(value)}.\"\n                )\n            self.sendcmd(f\"QM{value.value}\")\n\n        @property\n        def move_absolute(self):\n            \"\"\"Get / set the absolute target position of a motor.\n\n            Set with absolute position in steps. Valid values between\n            -2147483648 and +2147483647.\n            See also: `home_position`.\n\n            :return: Absolute motion target position.\n            :rtype: int\n\n            :raises ValueError: Requested position out of range.\n\n            Example:\n                >>> import instruments as ik\n                >>> ip = \"192.168.1.2\"\n                >>> port = 23   # this is the default port\n                >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n                >>> ax = inst.axis[0]\n                >>> ax.move_absolute = 100\n            \"\"\"\n            return int(self.query(\"PA?\"))\n\n        @move_absolute.setter\n        def move_absolute(self, value):\n            if not -2147483648 <= value <= 2147483647:\n                raise ValueError(\n                    f\"Set position must be between -2147483648 \"\n                    f\"and 2147483647, but is {value}.\"\n                )\n            self.sendcmd(f\"PA{int(value)}\")\n\n        @property\n        def move_relative(self):\n            \"\"\"Get / set the relative target position of a motor.\n\n            Set with relative motion in steps. Valid values between\n            -2147483648 and +2147483647.\n            See also: `home_position`.\n\n            :return: Relative motion target position.\n            :rtype: int\n\n            :raises ValueError: Requested motion out of range.\n\n            Example:\n                >>> import instruments as ik\n                >>> ip = \"192.168.1.2\"\n                >>> port = 23   # this is the default port\n                >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n                >>> ax = inst.axis[0]\n                >>> ax.move_relative = 100\n            \"\"\"\n            return int(self.query(\"PR?\"))\n\n        @move_relative.setter\n        def move_relative(self, value):\n            if not -2147483648 <= value <= 2147483647:\n                raise ValueError(\n                    f\"Set motion must be between -2147483648 \"\n                    f\"and 2147483647, but is {value}.\"\n                )\n            self.sendcmd(f\"PR{int(value)}\")\n\n        @property\n        def position(self):\n            \"\"\"Queries current, actual position of motor.\n\n            Positions are with respect to the home position.\n\n            :return: Current position in steps.\n            :rtype: int\n\n            Example:\n                >>> import instruments as ik\n                >>> ip = \"192.168.1.2\"\n                >>> port = 23   # this is the default port\n                >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n                >>> ax = inst.axis[0]\n                >>> ax.position\n                123\n            \"\"\"\n            return int(self.query(\"TP?\"))\n\n        @property\n        def velocity(self):\n            \"\"\"Get / set velocty of the connected motor (unitful).\n\n            Velocity is given in (steps) per second (1/s).\n            If a `MotorType.tiny` motor is connected, the maximum velocity\n            allowed is 1750 /s, otherwise 2000 /s.\n            If no units are given, 1/s are assumed.\n\n            :return: Velocity in 1/s\n            :rtype: u.Quantity(int)\n\n            :raises ValueError: Set value is out of the allowed range.\n\n            Example:\n                >>> import instruments as ik\n                >>> import instruments.units as u\n                >>> ip = \"192.168.1.2\"\n                >>> port = 23   # this is the default port\n                >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n                >>> ax = inst.axis[0]\n                >>> ax.velocity = u.Quantity(500, 1/u.s)\n            \"\"\"\n            retval = int(self.query(\"VA?\"))\n            return u.Quantity(retval, 1 / u.s)\n\n        @velocity.setter\n        def velocity(self, value):\n            if self.motor_type == self.MotorType.tiny:\n                max_velocity = 1750\n            else:\n                max_velocity = 2000\n\n            value = int(assume_units(value, 1 / u.s).to(1 / u.s).magnitude)\n            if not 0 < value <= max_velocity:\n                raise ValueError(\n                    f\"The maximum allowed velocity for the set \"\n                    f\"motor is {max_velocity}. The requested \"\n                    f\"velocity of {value} is out of range.\"\n                )\n            self.sendcmd(f\"VA{value}\")\n\n        # METHODS #\n\n        def move_indefinite(self, direction):\n            \"\"\"Move the motor indefinitely in the specific direction.\n\n            To stop motion, issue `stop_motion` or `abort_motion` command.\n            Direction is defined as a string of either \"+\" or \"-\".\n\n            :param direction: Direction in which to move the motor, \"+\" or \"-\"\n            :type direction: str\n\n            Example:\n                >>> from time import sleep\n                >>> import instruments as ik\n                >>> ip = \"192.168.1.2\"\n                >>> port = 23   # this is the default port\n                >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n                >>> ax = inst.axis[0]\n                >>> ax.move_indefinite(\"+\")\n                >>> sleep(1)   # wait a second\n                >>> ax.stop()\n            \"\"\"\n            if direction in [\"+\", \"-\"]:\n                self.sendcmd(f\"MV{direction}\")\n\n        def stop(self):\n            \"\"\"Stops the specific axis if in motion.\n\n            Example:\n                >>> from time import sleep\n                >>> import instruments as ik\n                >>> ip = \"192.168.1.2\"\n                >>> port = 23   # this is the default port\n                >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n                >>> ax = inst.axis[0]\n                >>> ax.move_indefinite(\"+\")\n                >>> sleep(1)   # wait a second\n                >>> ax.stop()\n            \"\"\"\n            self.sendcmd(\"ST\")\n\n        # CONTROLLER SPECIFIC PROPERTIES #\n\n        @property\n        def controller_address(self):\n            \"\"\"Get / set the controller address.\n\n            Valid address values are between 1 and 31. For setting up multiple\n            instruments, see `multiple_controllers`.\n\n            :return: Address of this device if secondary, otherwise `None`\n            :rtype: int\n            \"\"\"\n            retval = int(self.query(\"SA?\", axs=False))\n            return retval\n\n        @controller_address.setter\n        def controller_address(self, newval):\n            self.sendcmd(f\"SA{int(newval)}\", axs=False)\n\n        @property\n        def controller_configuration(self):\n            \"\"\"Get / set configuration of some of the controller’s features.\n\n            Configuration is given as a bit mask. If changed, please save\n            the settings afterward if you would like to do so. See\n            `save_settings`.\n\n            The bitmask to be set can be either given as a number, or as a\n            string of the mask itself. The following values are equivalent:\n            3, 0b11, \"11\"\n\n            - Bit 0:\n                - Value 0: Perform auto motor detection. Check and set motor\n                           type automatically when commanded to move.\n                - Value 1: Do not perform auto motor detection on move.\n            - Bit 1:\n                - Value 0: Do not scan for motors connected to controllers upon\n                           reboot (Performs ‘MC’ command upon power-up, reset or\n                           reboot).\n                - Value 1: Scan for motors connected to controller upon power-up\n                           or reset.\n\n            :return: Bitmask of the controller configuration.\n            :rtype: str, binary configuration\n            \"\"\"\n            return self.query(\"ZZ?\", axs=False)\n\n        @controller_configuration.setter\n        def controller_configuration(self, value):\n            if isinstance(value, str):\n                self.sendcmd(f\"ZZ{value}\", axs=False)\n            else:\n                self.sendcmd(f\"ZZ{str(bin(value))[2:]}\", axs=False)\n\n        @property\n        def error_code(self):\n            \"\"\"Get error code only.\n\n            Error code0 means no error detected.\n\n            :return: Error code.\n            :rtype: int\n            \"\"\"\n            return int(self.query(\"TE?\", axs=False))\n\n        @property\n        def error_code_and_message(self):\n            \"\"\"Get error code and message.\n\n            :return: Error code, error message\n            :rtype: int, str\n            \"\"\"\n            retval = self.query(\"TB?\", axs=False)\n            err_code, err_msg = retval.split(\",\")\n            err_code = int(err_code)\n            err_msg = err_msg.strip()\n            return err_code, err_msg\n\n        @property\n        def firmware_version(self):\n            \"\"\"Get the controller firmware version.\"\"\"\n            return self.query(\"VE?\", axs=False)\n\n        @property\n        def name(self):\n            \"\"\"Get the name of the controller.\"\"\"\n            return self.query(\"*IDN?\", axs=False)\n\n        # CONTROLLER SPECIFIC METHODS #\n\n        def abort_motion(self):\n            \"\"\"Instantaneously stops any motion in progress.\"\"\"\n            self.sendcmd(\"AB\", axs=False)\n\n        def motor_check(self):\n            \"\"\"Check what motors are connected and set parameters.\n\n            Use the save command `save_settings` if you want to save the\n            configuration to the non-volatile memory.\n            \"\"\"\n            self.sendcmd(\"MC\", axs=False)\n\n        def purge(self):\n            \"\"\"Purge the non-volatile memory of the controller.\n\n            Perform a hard reset and reset all the saved variables. The\n            following variables are reset to factory settings:\n            1. Hostname\n            2. IP Mode\n            3. IP Address\n            4. Subnet mask address\n            5. Gateway address\n            6. Configuration register\n            7. Motor type\n            8. Desired Velocity\n            9. Desired Acceleration\n            \"\"\"\n            self.sendcmd(\"XX\", axs=False)\n\n        def recall_parameters(self, value=0):\n            \"\"\"Recall parameter set.\n\n            This command restores the controller working parameters from values\n            saved in its non-volatile memory. It is useful when, for example,\n            the user has been exploring and changing parameters (e.g., velocity)\n            but then chooses to reload from previously stored, qualified\n            settings. Note that `*RCL 0` command just restores the working\n            parameters to factory default settings. It does not change the\n            settings saved in EEPROM.\n\n            :param value: 0 -> Recall factory default,\n                          1 -> Recall last saved settings\n            :type int:\n            \"\"\"\n            self.sendcmd(f\"*RCL{1 if value else 0}\", axs=False)\n\n        def reset(self):\n            \"\"\"Reset the controller.\n\n            Perform a soft reset. Saved variables are not deleted! For a\n            hard reset, see the `purge` command.\n\n            ..note:: It might take up to 30 seconds to re-establish\n                communications via TCP/IP\n            \"\"\"\n            self.sendcmd(\"*RST\", axs=False)\n\n        def save_settings(self):\n            \"\"\"Save user settings.\n\n            This command saves the controller settings in its non-volatile memory.\n            The controller restores or reloads these settings to working registers\n            automatically after system reset or it reboots. The purge\n            command is used to clear non-volatile memory and restore to factory\n            settings. Note that the SM saves parameters for all motors.\n\n            Saves the following variables:\n            1. Controller address\n            2. Hostname\n            3. IP Mode\n            4. IP Address\n            5. Subnet mask address\n            6. Gateway address\n            7. Configuration register\n            8. Motor type\n            9. Desired Velocity\n            10. Desired Acceleration\n            \"\"\"\n            self.sendcmd(\"SM\", axs=False)\n\n        # SEND AND QUERY #\n\n        def sendcmd(self, cmd, axs=True):\n            \"\"\"Send a command to an axis object.\n\n            :param cmd: Command\n            :type cmd: str\n            :param axs: Send axis address along? Not used for controller\n                commands. Defaults to `True`\n            :type axs: bool\n            \"\"\"\n            if axs:\n                command = f\"{self._address}{self._idx}{cmd}\"\n            else:\n                command = f\"{self._address}{cmd}\"\n            self._parent.sendcmd(command)\n\n        def query(self, cmd, size=-1, axs=True):\n            \"\"\"Query for an axis object.\n\n            :param cmd: Command\n            :type cmd: str\n            :param size: bytes to read, defaults to \"until terminator\" (-1)\n            :type size: int\n            :param axs: Send axis address along? Not used for controller\n                commands. Defaults to `True`\n            :type axs: bool\n\n            :raises IOError: The wrong axis answered.\n            \"\"\"\n            if axs:\n                command = f\"{self._address}{self._idx}{cmd}\"\n            else:\n                command = f\"{self._address}{cmd}\"\n\n            retval = self._parent.query(command, size=size)\n\n            if retval[: len(self._address)] != self._address:\n                raise OSError(\n                    f\"Expected to hear back from secondary \"\n                    f\"controller {self._address}, instead \"\n                    f\"controller {retval[:len(self._address)]} \"\n                    f\"answered.\"\n                )\n\n            return retval[len(self._address) :]\n\n    @property\n    def axis(self):\n        \"\"\"Return an axis object.\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> ip = \"192.168.1.2\"\n            >>> port = 23   # this is the default port\n            >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n            >>> ax = inst.axis[0]\n        \"\"\"\n        return ProxyList(self, self.Axis, range(31 * 4))\n\n    @property\n    def controller_address(self):\n        \"\"\"Get / set the controller address.\n\n        Valid address values are between 1 and 31. For setting up multiple\n        instruments, see `multiple_controllers`.\n\n        :return: Address of this device if secondary, otherwise `None`\n        :rtype: int\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> ip = \"192.168.1.2\"\n            >>> port = 23   # this is the default port\n            >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n            >>> inst.controller_address = 13\n        \"\"\"\n        return self.axis[0].controller_address\n\n    @controller_address.setter\n    def controller_address(self, newval):\n        self.axis[0].controller_address = newval\n\n    @property\n    def controller_configuration(self):\n        \"\"\"Get / set configuration of some of the controller’s features.\n\n        Configuration is given as a bit mask. If changed, please save\n        the settings afterward if you would like to do so. See\n        `save_settings`.\n\n        - Bit 0:\n            - Value 0: Perform auto motor detection. Check and set motor\n                       type automatically when commanded to move.\n            - Value 1: Do not perform auto motor detection on move.\n        - Bit 1:\n            - Value 0: Do not scan for motors connected to controllers upon\n                       reboot (Performs ‘MC’ command upon power-up, reset or\n                       reboot).\n            - Value 1: Scan for motors connected to controller upon power-up\n                       or reset.\n\n        :return: Bitmask of the controller configuration.\n        :rtype: str\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> ip = \"192.168.1.2\"\n            >>> port = 23   # this is the default port\n            >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n            >>> inst.controller_configuration = \"11\"\n        \"\"\"\n        return self.axis[0].controller_configuration\n\n    @controller_configuration.setter\n    def controller_configuration(self, value):\n        self.axis[0].controller_configuration = value\n\n    @property\n    def dhcp_mode(self):\n        \"\"\"Get / set if device is in DHCP mode.\n\n        If not in DHCP mode, a static IP address, gateway, and netmask\n        must be set.\n\n        :return: Status if DHCP mode is enabled\n        :rtype: `bool`\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> ip = \"192.168.1.2\"\n            >>> port = 23   # this is the default port\n            >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n            >>> inst.dhcp_mode = True\n        \"\"\"\n        return bool(self.query(\"IPMODE?\"))\n\n    @dhcp_mode.setter\n    def dhcp_mode(self, newval):\n        nn = 1 if newval else 0\n        self.sendcmd(f\"IPMODE{nn}\")\n\n    @property\n    def error_code(self):\n        \"\"\"Get error code only.\n\n        Error code0 means no error detected.\n\n        :return: Error code.\n        :rtype: int\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> ip = \"192.168.1.2\"\n            >>> port = 23   # this is the default port\n            >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n            >>> inst.error_code\n            0\n        \"\"\"\n        return self.axis[0].error_code\n\n    @property\n    def error_code_and_message(self):\n        \"\"\"Get error code and message.\n\n        :return: Error code, error message\n        :rtype: int, str\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> ip = \"192.168.1.2\"\n            >>> port = 23   # this is the default port\n            >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n            >>> inst.error_code\n            (0, 'NO ERROR DETECTED')\n        \"\"\"\n        return self.axis[0].error_code_and_message\n\n    @property\n    def firmware_version(self):\n        \"\"\"Get the controller firmware version.\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> ip = \"192.168.1.2\"\n            >>> port = 23   # this is the default port\n            >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n            >>> inst.firmware_version\n            '8742 Version 2.2 08/01/13'\n        \"\"\"\n        return self.axis[0].firmware_version\n\n    @property\n    def gateway(self):\n        \"\"\"Get / set the gateway of the instrument.\n\n        :return: Gateway address\n        :rtype: str\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> ip = \"192.168.1.2\"\n            >>> port = 23   # this is the default port\n            >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n            >>> inst.gateway = \"192.168.1.1\"\n        \"\"\"\n        return self.query(\"GATEWAY?\")\n\n    @gateway.setter\n    def gateway(self, value):\n        self.sendcmd(f\"GATEWAY {value}\")\n\n    @property\n    def hostname(self):\n        \"\"\"Get / set the hostname of the instrument.\n\n        :return: Hostname\n        :rtype: `str`\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> ip = \"192.168.1.2\"\n            >>> port = 23   # this is the default port\n            >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n            >>> inst.hostname = \"asdf\"\n        \"\"\"\n        return self.query(\"HOSTNAME?\")\n\n    @hostname.setter\n    def hostname(self, value):\n        self.sendcmd(f\"HOSTNAME {value}\")\n\n    @property\n    def ip_address(self):\n        \"\"\"Get / set the IP address of the instrument.\n\n        :return: IP address\n        :rtype: `str`\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> ip = \"192.168.1.2\"\n            >>> port = 23   # this is the default port\n            >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n            >>> inst.ip_address = \"192.168.1.2\"\n        \"\"\"\n        return self.query(\"IPADDR?\")\n\n    @ip_address.setter\n    def ip_address(self, value):\n        self.sendcmd(f\"IPADDR {value}\")\n\n    @property\n    def mac_address(self):\n        \"\"\"Get the MAC address of the instrument.\n\n        :return: MAC address\n        :rtype: `str`\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> ip = \"192.168.1.2\"\n            >>> port = 23   # this is the default port\n            >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n            >>> inst.mac_address\n            '5827809, 8087'\n        \"\"\"\n        return self.query(\"MACADDR?\")\n\n    @property\n    def multiple_controllers(self):\n        \"\"\"Get / set if multiple controllers are used.\n\n        By default, this is initialized as `False`. Set to `True` if you\n        have a main controller / secondary controller via RS-485 network\n        set up.\n\n        Instrument commands will always be sent to main controller.\n        Axis specific commands will be set to the axis chosen, see\n        `axis` description.\n\n        :return: Status if multiple controllers are activated\n        :rtype: bool\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> ip = \"192.168.1.2\"\n            >>> port = 23   # this is the default port\n            >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n            >>> inst.multiple_controllers = True\n        \"\"\"\n        return self._multiple_controllers\n\n    @multiple_controllers.setter\n    def multiple_controllers(self, newval):\n        self._multiple_controllers = True if newval else False\n\n    @property\n    def name(self):\n        \"\"\"Get the name of the controller.\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> ip = \"192.168.1.2\"\n            >>> port = 23   # this is the default port\n            >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n            >>> inst.name\n            'New_Focus 8742 v2.2 08/01/13 13991'\n        \"\"\"\n        return self.axis[0].name\n\n    @property\n    def netmask(self):\n        \"\"\"Get / set the Netmask of the instrument.\n\n        :return: Netmask\n        :rtype: `str`\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> ip = \"192.168.1.2\"\n            >>> port = 23   # this is the default port\n            >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n            >>> inst.netmask = \"255.255.255.0\"\n        \"\"\"\n        return self.query(\"NETMASK?\")\n\n    @netmask.setter\n    def netmask(self, value):\n        self.sendcmd(f\"NETMASK {value}\")\n\n    @property\n    def scan_controllers(self):\n        \"\"\"RS-485 controller address map query of all controllers.\n\n        32 bit string that represents the following:\n            Bit:    Value: (True: 1, False: 0)\n            0       Address conflict?\n            1:      Controller with address 1 exists?\n            <...>\n            31:      Controller with address 31 exists\n\n        Bits 1—31 are one-to-one mapped to controller addresses 1—31. The\n        bit value is set to 1 only when there are no conflicts with that\n        address. For example, if the master controller determines that there\n        are unique controllers at addresses 1,2, and 7 and more than one\n        controller at address 23, this query will return 135. The binary\n        representation of 135 is 10000111. Bit #0 = 1 implies that the scan\n        found at lease one address conflict during last scan. Bit #1,2, 7 = 1\n        implies that the scan found controllers with addresses 1,2, and 7\n        that do not conflict with any other controller.\n\n        :return: Binary representation of controller configuration bitmask.\n        :rtype: str\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> ip = \"192.168.1.2\"\n            >>> port = 23   # this is the default port\n            >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n            >>> inst.scan_controllers\n            \"10000111\"\n        \"\"\"\n        return self.query(\"SC?\")\n\n    @property\n    def scan_done(self):\n        \"\"\"Queries if a controller scan is done or not.\n\n        :return: Controller scan done?\n        :rtype: bool\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> ip = \"192.168.1.2\"\n            >>> port = 23   # this is the default port\n            >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n            >>> inst.scan_done\n            True\n        \"\"\"\n        return bool(int(self.query(\"SD?\")))\n\n    # METHODS #\n\n    def abort_motion(self):\n        \"\"\"Instantaneously stop any motion in progress.\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> ip = \"192.168.1.2\"\n            >>> port = 23   # this is the default port\n            >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n            >>> inst.abort_motion()\n        \"\"\"\n        self.axis[0].abort_motion()\n\n    def motor_check(self):\n        \"\"\"Check what motors are connected and set parameters.\n\n        Use the save command `save_settings` if you want to save the\n        configuration to the non-volatile memory.\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> ip = \"192.168.1.2\"\n            >>> port = 23   # this is the default port\n            >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n            >>> inst.motor_check()\n        \"\"\"\n        self.axis[0].motor_check()\n\n    def scan(self, value=2):\n        \"\"\"Initialize and set controller addresses automatically.\n\n        Scans the RS-485 network for connected controllers and set the\n        addresses automatically. Three possible scan modes can be\n        selected:\n\n        Mode 0:\n            Primary controller scans the network but does not resolve\n            any address conflicts.\n        Mode 1:\n            Primary controller scans the network and resolves address\n            conflicts, if any. This option preserves the non-conflicting\n            addresses and reassigns the conflicting addresses starting\n            with the lowest available address.\n        Mode 2 (default):\n            Primary controller reassigns the addresses of all\n            controllers on the network in a sequential order starting\n            with master controller set to address 1.\n\n        See also: `scan_controllers` property.\n\n        :param value: Scan mode.\n        :type: int\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> ip = \"192.168.1.2\"\n            >>> port = 23   # this is the default port\n            >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n            >>> inst.scan(2)\n        \"\"\"\n        self.sendcmd(f\"SC{value}\")\n\n    def purge(self):\n        \"\"\"Purge the non-volatile memory of the controller.\n\n        Perform a hard reset and reset all the saved variables. The\n        following variables are reset to factory settings:\n        1. Hostname\n        2. IP Mode\n        3. IP Address\n        4. Subnet mask address\n        5. Gateway address\n        6. Configuration register\n        7. Motor type\n        8. Desired Velocity\n        9. Desired Acceleration\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> ip = \"192.168.1.2\"\n            >>> port = 23   # this is the default port\n            >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n            >>> inst.purge()\n        \"\"\"\n        self.axis[0].purge()\n\n    def recall_parameters(self, value=0):\n        \"\"\"Recall parameter set.\n\n        This command restores the controller working parameters from values\n        saved in its non-volatile memory. It is useful when, for example,\n        the user has been exploring and changing parameters (e.g., velocity)\n        but then chooses to reload from previously stored, qualified\n        settings. Note that `*RCL 0` command just restores the working\n        parameters to factory default settings. It does not change the\n        settings saved in EEPROM.\n\n        :param value: 0 -> Recall factory default,\n            1 -> Recall last saved settings\n        :type value: int\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> ip = \"192.168.1.2\"\n            >>> port = 23   # this is the default port\n            >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n            >>> inst.recall_parameters(1)\n        \"\"\"\n        self.axis[0].recall_parameters(value)\n\n    def reset(self):\n        \"\"\"Reset the controller.\n\n        Perform a soft reset. Saved variables are not deleted! For a\n        hard reset, see the `purge` command.\n\n        ..note:: It might take up to 30 seconds to re-establish\n        communications via TCP/IP\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> ip = \"192.168.1.2\"\n            >>> port = 23   # this is the default port\n            >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n            >>> inst.reset()\n        \"\"\"\n        self.axis[0].reset()\n\n    def save_settings(self):\n        \"\"\"Save user settings.\n\n        This command saves the controller settings in its non-volatile memory.\n        The controller restores or reloads these settings to working registers\n        automatically after system reset or it reboots. The purge\n        command is used to clear non-volatile memory and restore to factory\n        settings. Note that the SM saves parameters for all motors.\n\n        Saves the following variables:\n        1. Controller address\n        2. Hostname\n        3. IP Mode\n        4. IP Address\n        5. Subnet mask address\n        6. Gateway address\n        7. Configuration register\n        8. Motor type\n        9. Desired Velocity\n        10. Desired Acceleration\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> ip = \"192.168.1.2\"\n            >>> port = 23   # this is the default port\n            >>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)\n            >>> inst.save_settings()\n        \"\"\"\n        self.axis[0].save_settings()\n\n    # QUERY #\n\n    def query(self, cmd, size=-1):\n        \"\"\"Query's the device and returns ASCII string.\n\n        Must be queried as a raw string with terminator line ending. This is\n        currently not implemented in instrument and therefore must be called\n        directly from file.\n\n        Sometimes, the instrument sends an undecodable 6 byte header along\n        (usually for the first query). We'll catch it with a try statement.\n        The 6 byte header was also remarked in this matlab script:\n        https://github.com/cnanders/matlab-newfocus-model-8742\n        \"\"\"\n        self.sendcmd(cmd)\n        retval = self.read_raw(size=size)\n        try:\n            retval = retval.decode(\"utf-8\")\n        except UnicodeDecodeError:\n            retval = retval[6:].decode(\"utf-8\")\n\n        return retval\n"
  },
  {
    "path": "src/instruments/newport/newportesp301.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the Newport ESP-301 motor controller.\n\nDue to the complexity of this piece of equipment, and relative lack of\ndocumentation and following of normal SCPI guidelines, this file more than\nlikely contains bugs and non-complete behaviour.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom contextlib import contextmanager\nfrom enum import IntEnum\nfrom functools import reduce\nimport time\n\nfrom instruments.abstract_instruments import Instrument\nfrom instruments.newport.errors import NewportError\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import assume_units, ProxyList\n\n# CLASSES #####################################################################\n\n\n# pylint: disable=too-many-lines\nclass NewportESP301(Instrument):\n    \"\"\"\n    Handles communication with the Newport ESP-301 multiple-axis motor\n    controller using the protocol documented in the `user's guide`_.\n\n    Due to the complexity of this piece of equipment, and relative lack of\n    documentation and following of normal SCPI guidelines, this class more than\n    likely contains bugs and non-complete behaviour.\n\n    .. _user's guide: http://assets.newport.com/webDocuments-EN/images/14294.pdf\n    \"\"\"\n\n    def __init__(self, filelike):\n        super().__init__(filelike)\n        self._execute_immediately = True\n        self._command_list = []\n        self._bulk_query_resp = \"\"\n        self.terminator = \"\\r\"\n\n    class Axis:\n        \"\"\"\n        Encapsulates communication concerning a single axis\n        of an ESP-301 controller. This class should not be\n        instantiated by the user directly, but is\n        returned by `NewportESP301.axis`.\n        \"\"\"\n\n        # quantities micro inch\n        # micro_inch = u.UnitQuantity('micro-inch', u.inch / 1e6, symbol='uin')\n        micro_inch = u.uinch\n\n        # Some more work might need to be done here to make\n        # the encoder_step and motor_step functional\n        # I really don't have a concrete idea how I'm\n        # going to do this until I have a physical device\n\n        _unit_dict = {\n            0: u.count,\n            1: u.count,\n            2: u.mm,\n            3: u.um,\n            4: u.inch,\n            5: u.mil,\n            6: micro_inch,  # compound unit for micro-inch\n            7: u.deg,\n            8: u.grad,\n            9: u.rad,\n            10: u.mrad,\n            11: u.urad,\n        }\n\n        def __init__(self, controller, axis_id):\n            if not isinstance(controller, NewportESP301):\n                raise TypeError(\n                    \"Axis must be controlled by a Newport ESP-301 \" \"motor controller.\"\n                )\n\n            self._controller = controller\n            self._axis_id = axis_id + 1\n\n            self._units = self.units\n\n        # CONTEXT MANAGERS ##\n\n        @contextmanager\n        def _units_of(self, units):\n            \"\"\"\n            Sets the units for the corresponding axis to a those given by an integer\n            label (see `NewportESP301.Units`), ensuring that the units are properly\n            reset at the completion of the context manager.\n            \"\"\"\n            old_units = self._get_units()\n            self._set_units(units)\n            yield\n            self._set_units(old_units)\n\n        # PRIVATE METHODS ##\n\n        def _get_units(self):\n            \"\"\"\n            Returns the integer label for the current units set for this axis.\n\n            .. seealso::\n                NewportESP301.Units\n            \"\"\"\n            return self._controller.Units(\n                int(self._newport_cmd(\"SN?\", target=self.axis_id))\n            )\n\n        def _set_units(self, new_units):\n            return self._newport_cmd(\"SN\", target=self.axis_id, params=[int(new_units)])\n\n        # PROPERTIES ##\n\n        @property\n        def axis_id(self):\n            \"\"\"\n            Get axis number of Newport Controller\n\n            :type: `int`\n            \"\"\"\n            return self._axis_id\n\n        @property\n        def is_motion_done(self):\n            \"\"\"\n            `True` if and only if all motion commands have\n            completed. This method can be used to wait for\n            a motion command to complete before sending the next\n            command.\n\n            :type: `bool`\n            \"\"\"\n            return bool(int(self._newport_cmd(\"MD?\", target=self.axis_id)))\n\n        @property\n        def acceleration(self):\n            \"\"\"\n            Gets/sets the axis acceleration\n\n            :units: As specified (if a `~pint.Quantity`) or assumed to be\n                of current newport unit\n            :type: `~pint.Quantity` or `float`\n            \"\"\"\n\n            return assume_units(\n                float(self._newport_cmd(\"AC?\", target=self.axis_id)),\n                self._units / (u.s**2),\n            )\n\n        @acceleration.setter\n        def acceleration(self, newval):\n            if newval is None:\n                return\n            newval = float(\n                assume_units(newval, self._units / (u.s**2))\n                .to(self._units / (u.s**2))\n                .magnitude\n            )\n            self._newport_cmd(\"AC\", target=self.axis_id, params=[newval])\n\n        @property\n        def deceleration(self):\n            \"\"\"\n            Gets/sets the axis deceleration\n\n            :units: As specified (if a `~pint.Quantity`) or assumed to be\n                of current newport :math:`\\\\frac{unit}{s^2}`\n            :type: `~pint.Quantity` or float\n            \"\"\"\n            return assume_units(\n                float(self._newport_cmd(\"AG?\", target=self.axis_id)),\n                self._units / (u.s**2),\n            )\n\n        @deceleration.setter\n        def deceleration(self, newval):\n            if newval is None:\n                return\n            newval = float(\n                assume_units(newval, self._units / (u.s**2))\n                .to(self._units / (u.s**2))\n                .magnitude\n            )\n            self._newport_cmd(\"AG\", target=self.axis_id, params=[newval])\n\n        @property\n        def estop_deceleration(self):\n            \"\"\"\n            Gets/sets the axis estop deceleration\n\n            :units: As specified (if a `~pint.Quantity`) or assumed to be\n                of current newport :math:`\\\\frac{unit}{s^2}`\n            :type: `~pint.Quantity` or float\n            \"\"\"\n            return assume_units(\n                float(self._newport_cmd(\"AE?\", target=self.axis_id)),\n                self._units / (u.s**2),\n            )\n\n        @estop_deceleration.setter\n        def estop_deceleration(self, decel):\n            decel = float(\n                assume_units(decel, self._units / (u.s**2))\n                .to(self._units / (u.s**2))\n                .magnitude\n            )\n            self._newport_cmd(\"AE\", target=self.axis_id, params=[decel])\n\n        @property\n        def jerk(self):\n            \"\"\"\n            Gets/sets the jerk rate for the controller\n\n            :units: As specified (if a `~pint.Quantity`) or assumed to be\n                of current newport unit\n            :type: `~pint.Quantity` or `float`\n            \"\"\"\n\n            return assume_units(\n                float(self._newport_cmd(\"JK?\", target=self.axis_id)),\n                self._units / (u.s**3),\n            )\n\n        @jerk.setter\n        def jerk(self, jerk):\n            jerk = float(\n                assume_units(jerk, self._units / (u.s**3))\n                .to(self._units / (u.s**3))\n                .magnitude\n            )\n            self._newport_cmd(\"JK\", target=self.axis_id, params=[jerk])\n\n        @property\n        def velocity(self):\n            \"\"\"\n            Gets/sets the axis velocity\n\n            :units: As specified (if a `~pint.Quantity`) or assumed to be\n                of current newport :math:`\\\\frac{unit}{s}`\n            :type: `~pint.Quantity` or `float`\n            \"\"\"\n            return assume_units(\n                float(self._newport_cmd(\"VA?\", target=self.axis_id)), self._units / u.s\n            )\n\n        @velocity.setter\n        def velocity(self, velocity):\n            velocity = float(\n                assume_units(velocity, self._units / (u.s))\n                .to(self._units / u.s)\n                .magnitude\n            )\n            self._newport_cmd(\"VA\", target=self.axis_id, params=[velocity])\n\n        @property\n        def max_velocity(self):\n            \"\"\"\n            Gets/sets the axis maximum velocity\n\n            :units: As specified (if a `~pint.Quantity`) or assumed to be\n                of current newport :math:`\\\\frac{unit}{s}`\n            :type: `~pint.Quantity` or `float`\n            \"\"\"\n            return assume_units(\n                float(self._newport_cmd(\"VU?\", target=self.axis_id)), self._units / u.s\n            )\n\n        @max_velocity.setter\n        def max_velocity(self, newval):\n            if newval is None:\n                return\n            newval = float(\n                assume_units(newval, self._units / u.s).to(self._units / u.s).magnitude\n            )\n            self._newport_cmd(\"VU\", target=self.axis_id, params=[newval])\n\n        @property\n        def max_base_velocity(self):\n            \"\"\"\n            Gets/sets the maximum base velocity for stepper motors\n\n            :units: As specified (if a `~pint.Quantity`) or assumed to be\n                of current newport :math:`\\\\frac{unit}{s}`\n            :type: `~pint.Quantity` or `float`\n            \"\"\"\n            return assume_units(\n                float(self._newport_cmd(\"VB?\", target=self.axis_id)), self._units / u.s\n            )\n\n        @max_base_velocity.setter\n        def max_base_velocity(self, newval):\n            if newval is None:\n                return\n            newval = float(\n                assume_units(newval, self._units / u.s).to(self._units / u.s).magnitude\n            )\n            self._newport_cmd(\"VB\", target=self.axis_id, params=[newval])\n\n        @property\n        def jog_high_velocity(self):\n            \"\"\"\n            Gets/sets the axis jog high velocity\n\n            :units: As specified (if a `~pint.Quantity`) or assumed to be\n                of current newport :math:`\\\\frac{unit}{s}`\n            :type: `~pint.Quantity` or `float`\n            \"\"\"\n            return assume_units(\n                float(self._newport_cmd(\"JH?\", target=self.axis_id)), self._units / u.s\n            )\n\n        @jog_high_velocity.setter\n        def jog_high_velocity(self, newval):\n            if newval is None:\n                return\n            newval = float(\n                assume_units(newval, self._units / u.s).to(self._units / u.s).magnitude\n            )\n            self._newport_cmd(\"JH\", target=self.axis_id, params=[newval])\n\n        @property\n        def jog_low_velocity(self):\n            \"\"\"\n            Gets/sets the axis jog low velocity\n\n            :units: As specified (if a `~pint.Quantity`) or assumed to be\n                of current newport :math:`\\\\frac{unit}{s}`\n            :type: `~pint.Quantity` or `float`\n            \"\"\"\n            return assume_units(\n                float(self._newport_cmd(\"JW?\", target=self.axis_id)), self._units / u.s\n            )\n\n        @jog_low_velocity.setter\n        def jog_low_velocity(self, newval):\n            if newval is None:\n                return\n            newval = float(\n                assume_units(newval, self._units / u.s).to(self._units / u.s).magnitude\n            )\n            self._newport_cmd(\"JW\", target=self.axis_id, params=[newval])\n\n        @property\n        def homing_velocity(self):\n            \"\"\"\n            Gets/sets the axis homing velocity\n\n            :units: As specified (if a `~pint.Quantity`) or assumed to be\n                of current newport :math:`\\\\frac{unit}{s}`\n            :type: `~pint.Quantity` or `float`\n            \"\"\"\n            return assume_units(\n                float(self._newport_cmd(\"OH?\", target=self.axis_id)), self._units / u.s\n            )\n\n        @homing_velocity.setter\n        def homing_velocity(self, newval):\n            if newval is None:\n                return\n            newval = float(\n                assume_units(newval, self._units / u.s).to(self._units / u.s).magnitude\n            )\n            self._newport_cmd(\"OH\", target=self.axis_id, params=[newval])\n\n        @property\n        def max_acceleration(self):\n            \"\"\"\n            Gets/sets the axis max acceleration\n\n            :units: As specified (if a `~pint.Quantity`) or assumed to be\n                of current newport :math:`\\\\frac{unit}{s^2}`\n            :type: `~pint.Quantity` or `float`\n            \"\"\"\n            return assume_units(\n                float(self._newport_cmd(\"AU?\", target=self.axis_id)),\n                self._units / (u.s**2),\n            )\n\n        @max_acceleration.setter\n        def max_acceleration(self, newval):\n            if newval is None:\n                return\n            newval = float(\n                assume_units(newval, self._units / (u.s**2))\n                .to(self._units / (u.s**2))\n                .magnitude\n            )\n            self._newport_cmd(\"AU\", target=self.axis_id, params=[newval])\n\n        @property\n        def max_deceleration(self):\n            \"\"\"\n            Gets/sets the axis max decceleration.\n            Max deaceleration is always the same as acceleration.\n\n            :units: As specified (if a `~pint.Quantity`) or assumed to be\n                of current newport :math:`\\\\frac{unit}{s^2}`\n            :type: `~pint.Quantity` or `float`\n            \"\"\"\n            return self.max_acceleration\n\n        @max_deceleration.setter\n        def max_deceleration(self, decel):\n            decel = float(\n                assume_units(decel, self._units / (u.s**2))\n                .to(self._units / (u.s**2))\n                .magnitude\n            )\n            self.max_acceleration = decel\n\n        @property\n        def position(self):\n            \"\"\"\n            Gets real position on axis in units\n\n            :units: As specified (if a `~pint.Quantity`) or assumed to be\n                of current newport unit\n            :type: `~pint.Quantity` or `float`\n            \"\"\"\n            return assume_units(\n                float(self._newport_cmd(\"TP?\", target=self.axis_id)), self._units\n            )\n\n        @property\n        def desired_position(self):\n            \"\"\"\n            Gets desired position on axis in units\n\n            :units: As specified (if a `~pint.Quantity`) or assumed to be\n                of current newport unit\n            :type: `~pint.Quantity` or `float`\n            \"\"\"\n            return assume_units(\n                float(self._newport_cmd(\"DP?\", target=self.axis_id)), self._units\n            )\n\n        @property\n        def desired_velocity(self):\n            \"\"\"\n            Gets the axis desired velocity in unit/s\n\n            :units: As specified (if a `~pint.Quantity`) or assumed to be\n                of current newport unit/s\n            :type: `~pint.Quantity` or `float`\n            \"\"\"\n            return assume_units(\n                float(self._newport_cmd(\"DV?\", target=self.axis_id)), self._units / u.s\n            )\n\n        @property\n        def home(self):\n            \"\"\"\n            Gets/sets the axis home position.\n            Default should be 0 as that sets current position as home\n\n            :units: As specified (if a `~pint.Quantity`) or assumed to be\n                of current newport unit\n            :type: `~pint.Quantity` or `float`\n            \"\"\"\n            return assume_units(\n                float(self._newport_cmd(\"DH?\", target=self.axis_id)), self._units\n            )\n\n        @home.setter\n        def home(self, newval):\n            if newval is None:\n                return\n            newval = float(assume_units(newval, self._units).to(self._units).magnitude)\n            self._newport_cmd(\"DH\", target=self.axis_id, params=[newval])\n\n        @property\n        def units(self):\n            \"\"\"\n            Get the units that all commands are in reference to.\n\n            :type: `~pint.Unit` corresponding to units of axis connected  or\n                int which corresponds to Newport unit number\n            \"\"\"\n            self._units = self._get_pq_unit(self._get_units())\n            return self._units\n\n        @units.setter\n        def units(self, newval):\n            if newval is None:\n                return\n            if isinstance(newval, int):\n                self._units = self._get_pq_unit(self._controller.Units(int(newval)))\n            elif isinstance(newval, u.Unit):\n                self._units = newval\n                newval = self._get_unit_num(newval)\n            self._set_units(newval)\n\n        @property\n        def encoder_resolution(self):\n            \"\"\"\n            Gets/sets the resolution of the encode. The minimum number of units\n            per step. Encoder functionality must be enabled.\n\n            :units: The number of units per encoder step\n            :type: `~pint.Quantity` or `float`\n            \"\"\"\n\n            return assume_units(\n                float(self._newport_cmd(\"SU?\", target=self.axis_id)), self._units\n            )\n\n        @encoder_resolution.setter\n        def encoder_resolution(self, newval):\n            if newval is None:\n                return\n            newval = float(assume_units(newval, self._units).to(self._units).magnitude)\n            self._newport_cmd(\"SU\", target=self.axis_id, params=[newval])\n\n        @property\n        def full_step_resolution(self):\n            \"\"\"\n            Gets/sets the axis resolution of the encode. The minimum number of\n            units per step. Encoder functionality must be enabled.\n\n            :units: The number of units per encoder step\n            :type: `~pint.Quantity` or `float`\n            \"\"\"\n\n            return assume_units(\n                float(self._newport_cmd(\"FR?\", target=self.axis_id)), self._units\n            )\n\n        @full_step_resolution.setter\n        def full_step_resolution(self, newval):\n            if newval is None:\n                return\n            newval = float(assume_units(newval, self._units).to(self._units).magnitude)\n            self._newport_cmd(\"FR\", target=self.axis_id, params=[newval])\n\n        @property\n        def left_limit(self):\n            \"\"\"\n            Gets/sets the axis left travel limit\n\n            :units: The limit in units\n            :type: `~pint.Quantity` or `float`\n            \"\"\"\n            return assume_units(\n                float(self._newport_cmd(\"SL?\", target=self.axis_id)), self._units\n            )\n\n        @left_limit.setter\n        def left_limit(self, limit):\n            limit = float(assume_units(limit, self._units).to(self._units).magnitude)\n            self._newport_cmd(\"SL\", target=self.axis_id, params=[limit])\n\n        @property\n        def right_limit(self):\n            \"\"\"\n            Gets/sets the axis right travel limit\n\n            :units: units\n            :type: `~pint.Quantity` or `float`\n            \"\"\"\n            return assume_units(\n                float(self._newport_cmd(\"SR?\", target=self.axis_id)), self._units\n            )\n\n        @right_limit.setter\n        def right_limit(self, limit):\n            limit = float(assume_units(limit, self._units).to(self._units).magnitude)\n            self._newport_cmd(\"SR\", target=self.axis_id, params=[limit])\n\n        @property\n        def error_threshold(self):\n            \"\"\"\n            Gets/sets the axis error threshold\n\n            :units: units\n            :type: `~pint.Quantity` or `float`\n            \"\"\"\n            return assume_units(\n                float(self._newport_cmd(\"FE?\", target=self.axis_id)), self._units\n            )\n\n        @error_threshold.setter\n        def error_threshold(self, newval):\n            if newval is None:\n                return\n            newval = float(assume_units(newval, self._units).to(self._units).magnitude)\n            self._newport_cmd(\"FE\", target=self.axis_id, params=[newval])\n\n        @property\n        def current(self):\n            \"\"\"\n            Gets/sets the axis current (amps)\n\n            :units: As specified (if a `~pint.Quantity`) or assumed to be\n                of current newport :math:`\\\\text{A}`\n            :type: `~pint.Quantity` or `float`\n            \"\"\"\n            return assume_units(\n                float(self._newport_cmd(\"QI?\", target=self.axis_id)), u.A\n            )\n\n        @current.setter\n        def current(self, newval):\n            if newval is None:\n                return\n            current = float(assume_units(newval, u.A).to(u.A).magnitude)\n            self._newport_cmd(\"QI\", target=self.axis_id, params=[current])\n\n        @property\n        def voltage(self):\n            \"\"\"\n            Gets/sets the axis voltage\n\n            :units: As specified (if a `~pint.Quantity`) or assumed to be\n                of current newport :math:`\\\\text{V}`\n            :type: `~pint.Quantity` or `float`\n            \"\"\"\n            return assume_units(\n                float(self._newport_cmd(\"QV?\", target=self.axis_id)), u.V\n            )\n\n        @voltage.setter\n        def voltage(self, newval):\n            if newval is None:\n                return\n            voltage = float(assume_units(newval, u.V).to(u.V).magnitude)\n            self._newport_cmd(\"QV\", target=self.axis_id, params=[voltage])\n\n        @property\n        def motor_type(self):\n            \"\"\"\n            Gets/sets the axis motor type\n            * 0 = undefined\n            * 1 = DC Servo\n            * 2 = Stepper motor\n            * 3 = commutated stepper motor\n            * 4 = commutated brushless servo motor\n\n            :type: `int`\n            :rtype: `NewportESP301.MotorType`\n            \"\"\"\n            return self._controller.MotorType(\n                int(self._newport_cmd(\"QM?\", target=self._axis_id))\n            )\n\n        @motor_type.setter\n        def motor_type(self, newval):\n            if newval is None:\n                return\n            self._newport_cmd(\"QM\", target=self._axis_id, params=[int(newval)])\n\n        @property\n        def feedback_configuration(self):\n            \"\"\"\n            Gets/sets the axis Feedback configuration\n\n            :type: `int`\n            \"\"\"\n            return int(self._newport_cmd(\"ZB?\", target=self._axis_id)[:-2], 16)\n\n        @feedback_configuration.setter\n        def feedback_configuration(self, newval):\n            if newval is None:\n                return\n            self._newport_cmd(\"ZB\", target=self._axis_id, params=[int(newval)])\n\n        @property\n        def position_display_resolution(self):\n            \"\"\"\n            Gets/sets the position display resolution\n\n            :type: `int`\n            \"\"\"\n            return int(self._newport_cmd(\"FP?\", target=self._axis_id))\n\n        @position_display_resolution.setter\n        def position_display_resolution(self, newval):\n            if newval is None:\n                return\n            self._newport_cmd(\"FP\", target=self._axis_id, params=[int(newval)])\n\n        @property\n        def trajectory(self):\n            \"\"\"\n            Gets/sets the axis trajectory\n\n            :type: `int`\n            \"\"\"\n            return int(self._newport_cmd(\"TJ?\", target=self._axis_id))\n\n        @trajectory.setter\n        def trajectory(self, newval):\n            if newval is None:\n                return\n            self._newport_cmd(\"TJ\", target=self._axis_id, params=[int(newval)])\n\n        @property\n        def microstep_factor(self):\n            \"\"\"\n            Gets/sets the axis microstep_factor\n\n            :type: `int`\n            \"\"\"\n            return int(self._newport_cmd(\"QS?\", target=self._axis_id))\n\n        @microstep_factor.setter\n        def microstep_factor(self, newval):\n            if newval is None:\n                return\n            newval = int(newval)\n            if newval < 1 or newval > 250:\n                raise ValueError(\"Microstep factor must be between 1 and 250\")\n            else:\n                self._newport_cmd(\"QS\", target=self._axis_id, params=[newval])\n\n        @property\n        def hardware_limit_configuration(self):\n            \"\"\"\n            Gets/sets the axis hardware_limit_configuration\n\n            :type: `int`\n            \"\"\"\n            return int(self._newport_cmd(\"ZH?\", target=self._axis_id)[:-2])\n\n        @hardware_limit_configuration.setter\n        def hardware_limit_configuration(self, newval):\n            if newval is None:\n                return\n            self._newport_cmd(\"ZH\", target=self._axis_id, params=[int(newval)])\n\n        @property\n        def acceleration_feed_forward(self):\n            \"\"\"\n            Gets/sets the axis acceleration_feed_forward setting\n\n            :type: `int`\n            \"\"\"\n            return float(self._newport_cmd(\"AF?\", target=self._axis_id))\n\n        @acceleration_feed_forward.setter\n        def acceleration_feed_forward(self, newval):\n            if newval is None:\n                return\n            self._newport_cmd(\"AF\", target=self._axis_id, params=[float(newval)])\n\n        @property\n        def proportional_gain(self):\n            \"\"\"\n            Gets/sets the axis proportional_gain\n\n            :type: `float`\n            \"\"\"\n            return float(self._newport_cmd(\"KP?\", target=self._axis_id)[:-1])\n\n        @proportional_gain.setter\n        def proportional_gain(self, newval):\n            if newval is None:\n                return\n            self._newport_cmd(\"KP\", target=self._axis_id, params=[float(newval)])\n\n        @property\n        def derivative_gain(self):\n            \"\"\"\n            Gets/sets the axis derivative_gain\n\n            :type: `float`\n            \"\"\"\n            return float(self._newport_cmd(\"KD?\", target=self._axis_id))\n\n        @derivative_gain.setter\n        def derivative_gain(self, newval):\n            if newval is None:\n                return\n            self._newport_cmd(\"KD\", target=self._axis_id, params=[float(newval)])\n\n        @property\n        def integral_gain(self):\n            \"\"\"\n            Gets/sets the axis integral_gain\n\n            :type: `float`\n            \"\"\"\n            return float(self._newport_cmd(\"KI?\", target=self._axis_id))\n\n        @integral_gain.setter\n        def integral_gain(self, newval):\n            if newval is None:\n                return\n            self._newport_cmd(\"KI\", target=self._axis_id, params=[float(newval)])\n\n        @property\n        def integral_saturation_gain(self):\n            \"\"\"\n            Gets/sets the axis integral_saturation_gain\n\n            :type: `float`\n            \"\"\"\n            return float(self._newport_cmd(\"KS?\", target=self._axis_id))\n\n        @integral_saturation_gain.setter\n        def integral_saturation_gain(self, newval):\n            if newval is None:\n                return\n            self._newport_cmd(\"KS\", target=self._axis_id, params=[float(newval)])\n\n        @property\n        def encoder_position(self):\n            \"\"\"\n            Gets the encoder position\n\n            :type:\n            \"\"\"\n            with self._units_of(self._controller.Units.encoder_step):\n                return self.position\n\n        # MOVEMENT METHODS #\n\n        def search_for_home(self, search_mode=None):\n            \"\"\"\n            Searches this axis only\n            for home using the method specified by ``search_mode``.\n\n            :param HomeSearchMode search_mode: Method to detect when\n                Home has been found. If None, the search mode is taken from\n                HomeSearchMode.\n            \"\"\"\n            if search_mode is None:\n                search_mode = self._controller.HomeSearchMode.zero_position_count.value\n            self._controller.search_for_home(axis=self.axis_id, search_mode=search_mode)\n\n        def move(self, position, absolute=True, wait=False, block=False):\n            \"\"\"\n            :param position: Position to set move to along this axis.\n            :type position: `float` or :class:`~pint.Quantity`\n            :param bool absolute: If `True`, the position ``pos`` is\n                interpreted as relative to the zero-point of the encoder.\n                If `False`, ``pos`` is interpreted as relative to the current\n                position of this axis.\n            :param bool wait: If True, will tell axis to not execute other\n                commands until movement is finished\n            :param bool block: If True, will block code until movement is finished\n            \"\"\"\n            position = float(\n                assume_units(position, self._units).to(self._units).magnitude\n            )\n            if absolute:\n                self._newport_cmd(\"PA\", params=[position], target=self.axis_id)\n            else:\n                self._newport_cmd(\"PR\", params=[position], target=self.axis_id)\n\n            if wait:\n                self.wait_for_position(position)\n                if block:\n                    time.sleep(0.003)\n                    mot = self.is_motion_done\n                    while not mot:\n                        mot = self.is_motion_done\n\n        def move_to_hardware_limit(self):\n            \"\"\"\n            move to hardware travel limit\n            \"\"\"\n            self._newport_cmd(\"MT\", target=self.axis_id)\n\n        def move_indefinitely(self):\n            \"\"\"\n            Move until told to stop\n            \"\"\"\n            self._newport_cmd(\"MV\", target=self.axis_id)\n\n        def abort_motion(self):\n            \"\"\"\n            Abort motion\n            \"\"\"\n            self._newport_cmd(\"AB\", target=self.axis_id)\n\n        def wait_for_stop(self):\n            \"\"\"\n            Waits for axis motion to stop before next command is executed\n            \"\"\"\n            self._newport_cmd(\"WS\", target=self.axis_id)\n\n        def stop_motion(self):\n            \"\"\"\n            Stop all motion on axis. With programmed deceleration rate\n            \"\"\"\n            self._newport_cmd(\"ST\", target=self.axis_id)\n\n        def wait_for_position(self, position):\n            \"\"\"\n            Wait for axis to reach position before executing next command\n\n            :param position: Position to wait for on axis\n\n            :type position: float or :class:`~pint.Quantity`\n            \"\"\"\n            position = float(\n                assume_units(position, self._units).to(self._units).magnitude\n            )\n            self._newport_cmd(\"WP\", target=self.axis_id, params=[position])\n\n        def wait_for_motion(self, poll_interval=0.01, max_wait=None):\n            \"\"\"\n            Blocks until all movement along this axis is complete, as reported\n            by `NewportESP301.Axis.is_motion_done`.\n\n            :param float poll_interval: How long (in seconds) to sleep between\n                checking if the motion is complete.\n            :param float max_wait: Maximum amount of time to wait before\n                raising a `IOError`. If `None`, this method will wait\n                indefinitely.\n            \"\"\"\n            # FIXME: make sure that the controller is not in\n            #        programming mode, or else this might not work.\n            #        In programming mode, the \"WS\" command should be\n            #        sent instead, and the two parameters to this method should\n            #        be ignored.\n            poll_interval = float(assume_units(poll_interval, u.s).to(u.s).magnitude)\n            if max_wait is not None:\n                max_wait = float(assume_units(max_wait, u.s).to(u.s).magnitude)\n            tic = time.time()\n            while True:\n                if self.is_motion_done:\n                    return\n                else:\n                    if max_wait is None or (time.time() - tic) < max_wait:\n                        time.sleep(poll_interval)\n                    else:\n                        raise OSError(\"Timed out waiting for motion to finish.\")\n\n        def enable(self):\n            \"\"\"\n            Turns motor axis on.\n            \"\"\"\n            self._newport_cmd(\"MO\", target=self._axis_id)\n\n        def disable(self):\n            \"\"\"\n            Turns motor axis off.\n            \"\"\"\n            self._newport_cmd(\"MF\", target=self._axis_id)\n\n        def setup_axis(self, **kwargs):\n            \"\"\"\n            Setup a non-newport DC servo motor stage. Necessary parameters are.\n\n            * 'motor_type' = type of motor see 'QM' in Newport documentation\n            * 'current' = motor maximum current (A)\n            * 'voltage' = motor voltage (V)\n            * 'units' = set units (see NewportESP301.Units)(U)\n            * 'encoder_resolution' = value of encoder step in terms of (U)\n            * 'max_velocity' = maximum velocity (U/s)\n            * 'max_base_velocity' =  maximum working velocity (U/s)\n            * 'homing_velocity' = homing speed (U/s)\n            * 'jog_high_velocity' = jog high speed (U/s)\n            * 'jog_low_velocity' = jog low speed (U/s)\n            * 'max_acceleration' = maximum acceleration (U/s^2)\n            * 'acceleration' = acceleration (U/s^2)\n            * 'velocity' = velocity (U/s)\n            * 'deceleration' = set deceleration (U/s^2)\n            * 'error_threshold' = set error threshold (U)\n            * 'estop_deceleration' = estop deceleration (U/s^2)\n            * 'jerk' = jerk rate (U/s^3)\n            * 'proportional_gain' = PID proportional gain (optional)\n            * 'derivative_gain' = PID derivative gain (optional)\n            * 'integral_gain' = PID internal gain (optional)\n            * 'integral_saturation_gain' = PID integral saturation (optional)\n            * 'trajectory' = trajectory mode (optional)\n            * 'position_display_resolution' (U per step)\n            * 'feedback_configuration'\n            * 'full_step_resolution'  = (U per step)\n            * 'home' = (U)\n            * 'acceleration_feed_forward' = between 0 to 2e9\n            * 'microstep_factor' = axis microstep factor\n            * 'reduce_motor_torque_time' =  time (ms) between 0 and 60000,\n            * 'reduce_motor_torque_percentage' = percentage between 0 and 100\n            \"\"\"\n\n            self.motor_type = kwargs.get(\"motor_type\")\n            self.feedback_configuration = kwargs.get(\"feedback_configuration\")\n            self.full_step_resolution = kwargs.get(\"full_step_resolution\")\n            self.position_display_resolution = kwargs.get(\n                \"position_display_\" \"resolution\"\n            )\n            self.current = kwargs.get(\"current\")\n            self.voltage = kwargs.get(\"voltage\")\n            self.units = int(kwargs.get(\"units\"))\n            self.encoder_resolution = kwargs.get(\"encoder_resolution\")\n            self.max_acceleration = kwargs.get(\"max_acceleration\")\n            self.max_velocity = kwargs.get(\"max_velocity\")\n            self.max_base_velocity = kwargs.get(\"max_base_velocity\")\n            self.homing_velocity = kwargs.get(\"homing_velocity\")\n            self.jog_high_velocity = kwargs.get(\"jog_high_velocity\")\n            self.jog_low_velocity = kwargs.get(\"jog_low_velocity\")\n            self.acceleration = kwargs.get(\"acceleration\")\n            self.velocity = kwargs.get(\"velocity\")\n            self.deceleration = kwargs.get(\"deceleration\")\n            self.estop_deceleration = kwargs.get(\"estop_deceleration\")\n            self.jerk = kwargs.get(\"jerk\")\n            self.error_threshold = kwargs.get(\"error_threshold\")\n            self.proportional_gain = kwargs.get(\"proportional_gain\")\n            self.derivative_gain = kwargs.get(\"derivative_gain\")\n            self.integral_gain = kwargs.get(\"integral_gain\")\n            self.integral_saturation_gain = kwargs.get(\"integral_saturation_gain\")\n            self.home = kwargs.get(\"home\")\n            self.microstep_factor = kwargs.get(\"microstep_factor\")\n            self.acceleration_feed_forward = kwargs.get(\"acceleration_feed_forward\")\n            self.trajectory = kwargs.get(\"trajectory\")\n            self.hardware_limit_configuration = kwargs.get(\n                \"hardware_limit_\" \"configuration\"\n            )\n            if (\n                \"reduce_motor_torque_time\" in kwargs\n                and \"reduce_motor_torque_percentage\" in kwargs\n            ):\n                motor_time = kwargs[\"reduce_motor_torque_time\"]\n                motor_time = int(assume_units(motor_time, u.ms).to(u.ms).magnitude)\n                if motor_time < 0 or motor_time > 60000:\n                    raise ValueError(\"Time must be between 0 and 60000 ms\")\n                percentage = kwargs[\"reduce_motor_torque_percentage\"]\n                percentage = int(\n                    assume_units(percentage, u.percent).to(u.percent).magnitude\n                )\n                if percentage < 0 or percentage > 100:\n                    raise ValueError(r\"Percentage must be between 0 and 100%\")\n                self._newport_cmd(\n                    \"QR\", target=self._axis_id, params=[motor_time, percentage]\n                )\n\n            # update motor configuration\n            self._newport_cmd(\"UF\", target=self._axis_id)\n            self._newport_cmd(\"QD\", target=self._axis_id)\n            # save configuration\n            self._newport_cmd(\"SM\")\n            return self.read_setup()\n\n        def read_setup(self):\n            \"\"\"\n            Returns dictionary containing:\n                'units'\n                'motor_type'\n                'feedback_configuration'\n                'full_step_resolution'\n                'position_display_resolution'\n                'current'\n                'max_velocity'\n                'encoder_resolution'\n                'acceleration'\n                'deceleration'\n                'velocity'\n                'max_acceleration'\n                'homing_velocity'\n                'jog_high_velocity'\n                'jog_low_velocity'\n                'estop_deceleration'\n                'jerk'\n                'proportional_gain'\n                'derivative_gain'\n                'integral_gain'\n                'integral_saturation_gain'\n                'home'\n                'microstep_factor'\n                'acceleration_feed_forward'\n                'trajectory'\n                'hardware_limit_configuration'\n\n            :rtype: dict of `pint.Quantity`, float and int\n            \"\"\"\n\n            config = dict()\n            config[\"units\"] = self.units\n            config[\"motor_type\"] = self.motor_type\n            config[\"feedback_configuration\"] = self.feedback_configuration\n            config[\"full_step_resolution\"] = self.full_step_resolution\n            config[\"position_display_resolution\"] = self.position_display_resolution\n            config[\"current\"] = self.current\n            config[\"max_velocity\"] = self.max_velocity\n            config[\"encoder_resolution\"] = self.encoder_resolution\n            config[\"acceleration\"] = self.acceleration\n            config[\"deceleration\"] = self.deceleration\n            config[\"velocity\"] = self.velocity\n            config[\"max_acceleration\"] = self.max_acceleration\n            config[\"homing_velocity\"] = self.homing_velocity\n            config[\"jog_high_velocity\"] = self.jog_high_velocity\n            config[\"jog_low_velocity\"] = self.jog_low_velocity\n            config[\"estop_deceleration\"] = self.estop_deceleration\n            config[\"jerk\"] = self.jerk\n            # config['error_threshold'] = self.error_threshold\n            config[\"proportional_gain\"] = self.proportional_gain\n            config[\"derivative_gain\"] = self.derivative_gain\n            config[\"integral_gain\"] = self.integral_gain\n            config[\"integral_saturation_gain\"] = self.integral_saturation_gain\n            config[\"home\"] = self.home\n            config[\"microstep_factor\"] = self.microstep_factor\n            config[\"acceleration_feed_forward\"] = self.acceleration_feed_forward\n            config[\"trajectory\"] = self.trajectory\n            config[\"hardware_limit_configuration\"] = self.hardware_limit_configuration\n            return config\n\n        def get_status(self):\n            \"\"\"\n            Returns Dictionary containing values:\n                'units'\n                'position'\n                'desired_position'\n                'desired_velocity'\n                'is_motion_done'\n\n            :rtype: dict\n            \"\"\"\n            status = dict()\n            status[\"units\"] = self.units\n            status[\"position\"] = self.position\n            status[\"desired_position\"] = self.desired_position\n            status[\"desired_velocity\"] = self.desired_velocity\n            status[\"is_motion_done\"] = self.is_motion_done\n\n            return status\n\n        @staticmethod\n        def _get_pq_unit(num):\n            \"\"\"\n            Gets the units for the specified axis.\n\n            :units: The units for the attached axis\n            :type num: int\n            \"\"\"\n            return NewportESP301.Axis._unit_dict[num]\n\n        def _get_unit_num(self, quantity):\n            \"\"\"\n            Gets the integer label used by the Newport ESP 301 corresponding to a\n            given `~pint.Quantity`.\n\n            :param pint.Quantity quantity: Units to return a label for.\n\n            :return int:\n            \"\"\"\n            for num, quant in self._unit_dict.items():\n                if quant == quantity:\n                    return num\n\n            raise KeyError(f\"{quantity} is not a valid unit for Newport Axis\")\n\n        # pylint: disable=protected-access\n        def _newport_cmd(self, cmd, **kwargs):\n            \"\"\"\n            Passes the newport command from the axis class to the parent controller\n\n            :param cmd:\n            :param kwargs:\n            :return:\n            \"\"\"\n            return self._controller._newport_cmd(cmd, **kwargs)\n\n    # ENUMS #\n\n    class HomeSearchMode(IntEnum):\n        \"\"\"\n        Enum containing different search modes code\n        \"\"\"\n\n        #: Search along specified axes for the +0 position.\n        zero_position_count = 0\n        #: Search for combined Home and Index signals.\n        home_index_signals = 1\n        #: Search only for the Home signal.\n        home_signal_only = 2\n        #: Search for the positive limit signal.\n        pos_limit_signal = 3\n        #: Search for the negative limit signal.\n        neg_limit_signal = 4\n        #: Search for the positive limit and Index signals.\n        pos_index_signals = 5\n        #: Search for the negative limit and Index signals.\n        neg_index_signals = 6\n\n    class MotorType(IntEnum):\n        \"\"\"\n        Enum for different motor types.\n        \"\"\"\n\n        undefined = 0\n        dc_servo = 1\n        stepper_motor = 2\n        commutated_stepper_motor = 3\n        commutated_brushless_servo = 4\n\n    class Units(IntEnum):\n        \"\"\"\n        Enum containing what `units` return means.\n        \"\"\"\n\n        encoder_step = 0\n        motor_step = 1\n        millimeter = 2\n        micrometer = 3\n        inches = 4\n        milli_inches = 5\n        micro_inches = 6\n        degree = 7\n        gradian = 8\n        radian = 9\n        milliradian = 10\n        microradian = 11\n\n    # PROPERTIES #\n\n    @property\n    def axis(self):\n        \"\"\"\n        Gets the axes of the motor controller as a sequence. For instance,\n        to move along a given axis::\n\n        >>> controller = NewportESP301.open_serial(\"COM3\")\n        >>> controller.axis[0].move(-0.001, absolute=False)\n\n        Note that the axes are numbered starting from zero, so that\n        Python idioms can be used more easily. This is not the same convention\n        used in the Newport ESP-301 user's manual, and so care must\n        be taken when converting examples.\n\n        :type: :class:`NewportESP301.Axis`\n        \"\"\"\n\n        return ProxyList(self, self.Axis, range(100))\n        # return _AxisList(self)\n\n    # LOW-LEVEL COMMAND METHODS ##\n\n    def _newport_cmd(self, cmd, params=tuple(), target=None, errcheck=True):\n        \"\"\"\n        The Newport ESP-301 command set supports checking for errors,\n        specifying different axes and allows for multiple parameters.\n        As such, it is convienent to wrap calls to the low-level\n        `~instruments.abstract_instruments.Instrument.sendcmd` method\n        in a method that is aware of the eccenticities of the controller.\n\n        This method sends a command, checks for errors on the device\n        and turns them into exceptions as needed.\n\n        :param bool errcheck: If `False`, suppresses the standard error\n            checking. Note that since error-checking is unsupported\n            during device programming, ``errcheck`` must be `False`\n            during ``PGM`` mode.\n        \"\"\"\n        query_resp = None\n        if isinstance(target, self.Axis):\n            target = target.axis_id\n        raw_cmd = \"{target}{cmd}{params}\".format(\n            target=target if target is not None else \"\",\n            cmd=cmd.upper(),\n            params=\",\".join(map(str, params)),\n        )\n\n        if self._execute_immediately:\n            query_resp = self._execute_cmd(raw_cmd, errcheck)\n        else:\n            self._command_list.append(raw_cmd)\n\n        # This works because \"return None\" is equivalent to \"return\".\n        return query_resp\n\n    def _execute_cmd(self, raw_cmd, errcheck=True):\n        \"\"\"\n        Takes a string command and executes it on Newport\n\n\n        :param str raw_cmd:\n        :param bool errcheck:\n\n\n        :return: response of device\n        :rtype: `str`\n\n        \"\"\"\n        query_resp = None\n\n        if \"?\" in raw_cmd:\n            query_resp = self.query(raw_cmd)\n        else:\n            self.sendcmd(raw_cmd)\n\n        if errcheck:\n            err_resp = self.query(\"TB?\")\n\n            # pylint: disable=unused-variable\n            code, timestamp, msg = err_resp.split(\",\")\n            code = int(code)\n            if code != 0:\n                raise NewportError(code)\n\n        return query_resp\n\n    # SPECIFIC COMMANDS ##\n\n    def _home(self, axis, search_mode, errcheck=True):\n        \"\"\"\n        Private method for searching for home \"OR\", so that\n        the methods in this class and the axis class can both\n        point to the same thing.\n        \"\"\"\n        self._newport_cmd(\"OR\", target=axis, params=[search_mode], errcheck=errcheck)\n\n    def search_for_home(\n        self,\n        axis=1,\n        search_mode=HomeSearchMode.zero_position_count.value,\n        errcheck=True,\n    ):\n        \"\"\"\n        Searches the specified axis for home using the method specified\n        by ``search_mode``.\n\n        :param int axis: Axis ID for which home should be searched for. This\n            value is 1-based indexing.\n        :param HomeSearchMode search_mode: Method to detect when\n            Home has been found.\n        :param bool errcheck: Boolean to check for errors after each command\n            that is sent to the instrument.\n        \"\"\"\n        self._home(axis=axis, search_mode=search_mode, errcheck=errcheck)\n\n    def reset(self):\n        \"\"\"\n        Causes the device to perform a hardware reset. Note that\n        this method is only effective if the watchdog timer is enabled\n        by the physical jumpers on the ESP-301. Please see the `user's guide`_\n        for more information.\n        \"\"\"\n        self._newport_cmd(\"RS\", errcheck=False)\n\n    # USER PROGRAMS ##\n\n    @contextmanager\n    def define_program(self, program_id):\n        \"\"\"\n        Erases any existing programs with a given program ID\n        and instructs the device to record the commands within this\n        ``with`` block to be saved as a program with that ID.\n\n        For instance:\n\n        >>> controller = NewportESP301.open_serial(\"COM3\")\n        >>> with controller.define_program(15):\n        ...     controller.axis[0].move(0.001, absolute=False)\n        ...\n        >>> controller.run_program(15)\n\n        :param int program_id: An integer label for the new program.\n            Must be in ``range(1, 101)``.\n        \"\"\"\n        if program_id not in range(1, 101):\n            raise ValueError(\n                \"Invalid program ID. Must be an integer from \" \"1 to 100 (inclusive).\"\n            )\n        self._newport_cmd(\"XX\", target=program_id)\n        try:\n            self._newport_cmd(\"EP\", target=program_id)\n            yield\n        finally:\n            self._newport_cmd(\"QP\")\n\n    @contextmanager\n    def execute_bulk_command(self, errcheck=True):\n        \"\"\"\n        Context manager to execute multiple commands in a single\n        communication with device\n\n        Example::\n\n            with self.execute_bulk_command():\n                execute commands as normal...\n\n        :param bool errcheck: Boolean to check for errors after each command\n            that is sent to the instrument.\n        \"\"\"\n        self._execute_immediately = False\n        yield\n        command_string = reduce(lambda x, y: x + \" ; \" + y + \" ; \", self._command_list)\n        # TODO: is _bulk_query_resp getting back to user?\n        self._bulk_query_resp = self._execute_cmd(command_string, errcheck)\n        self._command_list = []\n        self._execute_immediately = True\n\n    def run_program(self, program_id):\n        \"\"\"\n        Runs a previously defined user program with a given program ID.\n\n        :param int program_id: ID number for previously saved user program\n        \"\"\"\n        if program_id not in range(1, 101):\n            raise ValueError(\n                \"Invalid program ID. Must be an integer from \" \"1 to 100 (inclusive).\"\n            )\n        self._newport_cmd(\"EX\", target=program_id)\n"
  },
  {
    "path": "src/instruments/ondax/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing Ondax Instruments\n\"\"\"\n\nfrom .lm import LM\n"
  },
  {
    "path": "src/instruments/ondax/lm.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides the support for the Ondax LM Laser.\n\nClass originally contributed by Catherine Holloway.\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nfrom enum import IntEnum\n\nfrom instruments.units import ureg as u\n\nfrom instruments.abstract_instruments import Instrument\nfrom instruments.util_fns import convert_temperature, assume_units\n\n# CLASSES #####################################################################\n\n\nclass LM(Instrument):\n    \"\"\"\n    The LM is the Ondax SureLock VHG-stabilized laser diode.\n\n    The user manual can be found on the `Ondax website`_.\n\n    .. _Ondax website: http://www.ondax.com/Downloads/SureLock/Compact%20laser%20module%20manual.pdf\n    \"\"\"\n\n    def __init__(self, filelike):\n        super().__init__(filelike)\n        self.terminator = \"\\r\"\n        self.apc = self._AutomaticPowerControl(self)\n        self.acc = self._AutomaticCurrentControl(self)\n        self.tec = self._ThermoElectricCooler(self)\n        self.modulation = self._Modulation(self)\n        self._enabled = None\n\n    # ENUMS #\n    class Status(IntEnum):\n        \"\"\"\n        Enum containing the valid states of the laser\n        \"\"\"\n\n        normal = 1\n        inner_modulation = 2\n        power_scan = 3\n        calibrate = 4\n        shutdown_current = 5\n        shutdown_overheat = 6\n        waiting_stable_temperature = 7\n        waiting = 8\n\n    # INNER CLASSES #\n\n    class _AutomaticCurrentControl:\n        \"\"\"\n        Options and functions related to the laser diode's automatic current\n        control driver.\n\n        .. warning:: This class is not designed to be accessed directly. It\n            should be interfaced via `LM.acc`\n        \"\"\"\n\n        def __init__(self, parent):\n            self._parent = parent\n            self._enabled = False\n\n        @property\n        def target(self):\n            \"\"\"\n            Gets the automatic current control target setting.\n\n            This property is accessed via the `LM.acc` namespace.\n\n            Example usage:\n\n            >>> import instruments as ik\n            >>> laser = ik.ondax.LM.open_serial('/dev/ttyUSB0', baud=1234)\n            >>> print(laser.acc.target)\n\n            :return: Current ACC of the Laser\n            :units: mA\n            :type: `~pint.Quantity`\n            \"\"\"\n            response = float(self._parent.query(\"rstli?\"))\n            return response * u.mA\n\n        @property\n        def enabled(self):\n            \"\"\"\n            Get/Set the enabled state of the ACC driver.\n\n            This property is accessed via the `LM.acc` namespace.\n\n            Example usage:\n\n            >>> import instruments as ik\n            >>> laser = ik.ondax.LM.open_serial('/dev/ttyUSB0', baud=1234)\n            >>> print(laser.acc.enabled)\n            >>> laser.acc.enabled = True\n\n            :type: `bool`\n            \"\"\"\n            return self._enabled\n\n        @enabled.setter\n        def enabled(self, newval):\n            if not isinstance(newval, bool):\n                raise TypeError(\n                    \"ACC driver enabled property must be specified\"\n                    \"with a boolean, got {}.\".format(type(newval))\n                )\n            if newval:\n                self._parent.sendcmd(\"lcen\")\n            else:\n                self._parent.sendcmd(\"lcdis\")\n            self._enabled = newval\n\n        def on(self):\n            \"\"\"\n            Turns on the automatic current control driver.\n\n            This function is accessed via the `LM.acc` namespace.\n\n            Example usage:\n\n            >>> import instruments as ik\n            >>> laser = ik.ondax.LM.open_serial('/dev/ttyUSB0', baud=1234)\n            >>> laser.acc.on()\n            \"\"\"\n            self._parent.sendcmd(\"lcon\")\n\n        def off(self):\n            \"\"\"\n            Turn off the automatic current control driver.\n\n            This function is accessed via the `LM.acc` namespace.\n\n            Example usage:\n\n            >>> import instruments as ik\n            >>> laser = ik.ondax.LM.open_serial('/dev/ttyUSB0', baud=1234)\n            >>> laser.acc.off()\n            \"\"\"\n            self._parent.sendcmd(\"lcoff\")\n\n    class _AutomaticPowerControl:\n        \"\"\"\n        Options and functions related to the laser diode's automatic power\n        control driver.\n\n        .. warning:: This class is not designed to be accessed directly. It\n            should be interfaced via `LM.apc`\n        \"\"\"\n\n        def __init__(self, parent):\n            self._parent = parent\n            self._enabled = False\n\n        @property\n        def target(self):\n            \"\"\"\n            Gets the target laser power of the automatic power control in mW.\n\n            This property is accessed via the `LM.apc` namespace.\n\n            Example usage:\n\n            >>> import instruments as ik\n            >>> laser = ik.ondax.LM.open_serial('/dev/ttyUSB0', baud=1234)\n            >>> print(laser.apc.target)\n\n            :return: the target laser power\n            :units: mW\n            :type: `~pint.Quantity`\n            \"\"\"\n            response = self._parent.query(\"rslp?\")\n            return float(response) * u.mW\n\n        @property\n        def enabled(self):\n            \"\"\"\n            Get/Set the enabled state of the automatic power control driver.\n\n            This property is accessed via the `LM.apc` namespace.\n\n            Example usage:\n\n            >>> import instruments as ik\n            >>> laser = ik.ondax.LM.open_serial('/dev/ttyUSB0', baud=1234)\n            >>> print(laser.apc.enabled)\n            >>> laser.apc.enabled = True\n\n            :type: `bool`\n            \"\"\"\n            return self._enabled\n\n        @enabled.setter\n        def enabled(self, newval):\n            if not isinstance(newval, bool):\n                raise TypeError(\n                    \"APC driver enabled property must be specified \"\n                    \"with a boolean, got {}.\".format(type(newval))\n                )\n            if newval:\n                self._parent.sendcmd(\"len\")\n            else:\n                self._parent.sendcmd(\"ldis\")\n            self._enabled = newval\n\n        def start(self):\n            \"\"\"\n            Start the automatic power control scan.\n\n            This function is accessed via the `LM.apc` namespace.\n\n            Example usage:\n\n            >>> import instruments as ik\n            >>> laser = ik.ondax.LM.open_serial('/dev/ttyUSB0', baud=1234)\n            >>> laser.apc.start()\n            \"\"\"\n            self._parent.sendcmd(\"sps\")\n\n        def stop(self):\n            \"\"\"\n            Stop the automatic power control scan.\n\n            This function is accessed via the `LM.apc` namespace.\n\n            Example usage:\n\n            >>> import instruments as ik\n            >>> laser = ik.ondax.LM.open_serial('/dev/ttyUSB0', baud=1234)\n            >>> laser.apc.stop()\n            \"\"\"\n            self._parent.sendcmd(\"cps\")\n\n    class _Modulation:\n        \"\"\"\n        Options and functions related to the laser's optical output modulation.\n\n        .. warning:: This class is not designed to be accessed directly. It\n            should be interfaced via `LM.modulation`\n        \"\"\"\n\n        def __init__(self, parent):\n            self._parent = parent\n            self._enabled = False\n\n        @property\n        def on_time(self):\n            \"\"\"\n            Gets/sets the TTL modulation on time, in milliseconds.\n\n            This property is accessed via the `LM.modulation` namespace.\n\n            Example usage:\n\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> laser = ik.ondax.LM.open_serial('/dev/ttyUSB0', baud=1234)\n            >>> print(laser.modulation.on_time)\n            >>> laser.modulation.on_time = 1 * u.ms\n\n            :return: The TTL modulation on time\n            :units: As specified (if a `~pint.Quantity`) or assumed\n                to be of units milliseconds.\n            :type: `~pint.Quantity`\n            \"\"\"\n            response = self._parent.query(\"stsont?\")\n            return float(response) * u.ms\n\n        @on_time.setter\n        def on_time(self, newval):\n            newval = assume_units(newval, u.ms).to(u.ms).magnitude\n            self._parent.sendcmd(\"stsont:\" + str(newval))\n\n        @property\n        def off_time(self):\n            \"\"\"\n            Gets/sets the TTL modulation off time, in milliseconds.\n\n            This property is accessed via the `LM.modulation` namespace.\n\n            Example usage:\n\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> laser = ik.ondax.LM.open_serial('/dev/ttyUSB0', baud=1234)\n            >>> print(laser.modulation.on_time)\n            >>> laser.modulation.on_time = 1 * u.ms\n\n            :return: The TTL modulation off time.\n            :units: As specified (if a `~pint.Quantity`) or assumed\n                to be of units milliseconds.\n            :type: `~pint.Quantity`\n            \"\"\"\n            response = self._parent.query(\"stsofft?\")\n            return float(response) * u.ms\n\n        @off_time.setter\n        def off_time(self, newval):\n            newval = assume_units(newval, u.ms).to(u.ms).magnitude\n            self._parent.sendcmd(\"stsofft:\" + str(newval))\n\n        @property\n        def enabled(self):\n            \"\"\"\n            Get/Set the TTL modulation output state.\n\n            This property is accessed via the `LM.modulation` namespace.\n\n            Example usage:\n\n            >>> import instruments as ik\n            >>> laser = ik.ondax.LM.open_serial('/dev/ttyUSB0', baud=1234)\n            >>> print(laser.modulation.enabled)\n            >>> laser.modulation.enabled = True\n\n            :type: `bool`\n            \"\"\"\n            return self._enabled\n\n        @enabled.setter\n        def enabled(self, newval):\n            if not isinstance(newval, bool):\n                raise TypeError(\n                    \"Modulation enabled property must be specified \"\n                    \"with a boolean, got {}.\".format(type(newval))\n                )\n            if newval:\n                self._parent.sendcmd(\"stm\")\n            else:\n                self._parent.sendcmd(\"ctm\")\n            self._enabled = newval\n\n    class _ThermoElectricCooler:\n        \"\"\"\n        Options and functions relating to the laser diode's thermo electric\n        cooler.\n\n        .. warning:: This class is not designed to be accessed directly. It\n            should be interfaced via `LM.tec`\n        \"\"\"\n\n        def __init__(self, parent):\n            self._parent = parent\n            self._enabled = False\n\n        @property\n        def current(self):\n            \"\"\"\n            Gets the thermoelectric cooler current setting.\n\n            This property is accessed via the `LM.tec` namespace.\n\n            Example usage:\n\n            >>> import instruments as ik\n            >>> laser = ik.ondax.LM.open_serial('/dev/ttyUSB0', baud=1234)\n            >>> print(laser.tec.current)\n\n            :units: mA\n            :type: `~pint.Quantity`\n            \"\"\"\n            response = self._parent.query(\"rti?\")\n            return float(response) * u.mA\n\n        @property\n        def target(self):\n            \"\"\"\n            Gets the thermoelectric cooler target temperature.\n\n            This property is acccessed via the `LM.tec` namespace.\n\n            Example usage:\n\n            >>> import instruments as ik\n            >>> laser = ik.ondax.LM.open_serial('/dev/ttyUSB0', baud=1234)\n            >>> print(laser.tec.target)\n\n            :units: Degrees Celcius\n            :type: `~pint.Quantity`\n            \"\"\"\n            response = self._parent.query(\"rstt?\")\n            return u.Quantity(float(response), u.degC)\n\n        @property\n        def enabled(self):\n            \"\"\"\n            Gets/sets the enable state for the thermoelectric cooler.\n\n            This property is accessed via the `LM.tec` namespace.\n\n            Example usage:\n\n            >>> import instruments as ik\n            >>> laser = ik.ondax.LM.open_serial('/dev/ttyUSB0', baud=1234)\n            >>> print(laser.tec.enabled)\n            >>> laser.tec.enabled = True\n\n            :type: `bool`\n            \"\"\"\n            return self._enabled\n\n        @enabled.setter\n        def enabled(self, newval):\n            if not isinstance(newval, bool):\n                raise TypeError(\n                    \"TEC enabled property must be specified with \"\n                    \"a boolean, got {}.\".format(type(newval))\n                )\n            if newval:\n                self._parent.sendcmd(\"tecon\")\n            else:\n                self._parent.sendcmd(\"tecoff\")\n            self._enabled = newval\n\n    def _ack_expected(self, msg=\"\"):\n        if msg.find(\"?\") > 0:\n            return None\n\n        return \"OK\"\n\n    @property\n    def firmware(self):\n        \"\"\"\n        Gets the laser system firmware version.\n\n        :type: `str`\n        \"\"\"\n        response = self.query(\"rsv?\")\n        return response\n\n    @property\n    def current(self):\n        \"\"\"\n        Gets/sets the laser diode current, in mA.\n\n        :units: As specified (if a `~pint.Quantity`) or assumed\n                to be of units mA.\n        :type: `~pint.Quantity`\n        \"\"\"\n        response = self.query(\"rli?\")\n        return float(response) * u.mA\n\n    @current.setter\n    def current(self, newval):\n        newval = assume_units(newval, u.mA).to(u.mA).magnitude\n        self.sendcmd(\"slc:\" + str(newval))\n\n    @property\n    def maximum_current(self):\n        \"\"\"\n        Get/Set the maximum laser diode current in mA. If the current is set\n        over the limit, the laser will shut down.\n\n        :units: As specified (if a `~pint.Quantity`) or assumed\n                to be of units mA.\n        :type: `~pint.Quantity`\n        \"\"\"\n        response = self.query(\"rlcm?\")\n        return float(response) * u.mA\n\n    @maximum_current.setter\n    def maximum_current(self, newval):\n        newval = assume_units(newval, u.mA).to(\"mA\").magnitude\n        self.sendcmd(\"smlc:\" + str(newval))\n\n    @property\n    def power(self):\n        \"\"\"\n        Get/Set the laser's optical power in mW.\n\n        :units: As specified (if a `~pint.Quantity`) or assumed\n                to be of units mW.\n        :rtype: `~pint.Quantity`\n        \"\"\"\n        response = self.query(\"rlp?\")\n        return float(response) * u.mW\n\n    @power.setter\n    def power(self, newval):\n        newval = assume_units(newval, u.mW).to(u.mW).magnitude\n        self.sendcmd(\"slp:\" + str(newval))\n\n    @property\n    def serial_number(self):\n        \"\"\"\n        Gets the laser controller serial number\n\n        :type: `str`\n        \"\"\"\n        response = self.query(\"rsn?\")\n        return response\n\n    @property\n    def status(self):\n        \"\"\"\n        Read laser controller run status.\n\n        :type: `LM.Status`\n        \"\"\"\n        response = self.query(\"rlrs?\")\n        return self.Status(int(response))\n\n    @property\n    def temperature(self):\n        \"\"\"\n        Gets/sets laser diode temperature.\n\n        :units: As specified (if a `~pint.Quantity`) or assumed\n                to be of units degrees celcius.\n        :type: `~pint.Quantity`\n        \"\"\"\n        response = self.query(\"rtt?\")\n        return u.Quantity(float(response), u.degC)\n\n    @temperature.setter\n    def temperature(self, newval):\n        newval = convert_temperature(newval, u.degC).magnitude\n        self.sendcmd(\"stt:\" + str(newval))\n\n    @property\n    def enabled(self):\n        \"\"\"\n        Gets/sets the laser emission enabled status.\n\n        :type: `bool`\n        \"\"\"\n        return self._enabled\n\n    @enabled.setter\n    def enabled(self, newval):\n        if not isinstance(newval, bool):\n            raise TypeError(\n                \"Laser module enabled property must be specified \"\n                \"with a boolean, got {}.\".format(type(newval))\n            )\n        if newval:\n            self.sendcmd(\"lon\")\n        else:\n            self.sendcmd(\"loff\")\n        self._enabled = newval\n\n    def save(self):\n        \"\"\"\n        Save current settings in flash memory.\n        \"\"\"\n        self.sendcmd(\"ssc\")\n\n    def reset(self):\n        \"\"\"\n        Reset the laser controller.\n        \"\"\"\n        self.sendcmd(\"reset\")\n"
  },
  {
    "path": "src/instruments/optional_dep_finder.py",
    "content": "\"\"\"\nSmall module to obtain handles to optional dependencies\n\"\"\"\n\n# pylint: disable=unused-import\ntry:\n    import numpy\n\n    _numpy_installed = True\nexcept ImportError:\n    numpy = None\n    _numpy_installed = False\n"
  },
  {
    "path": "src/instruments/oxford/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing Oxford instruments\n\"\"\"\n\nfrom .oxforditc503 import OxfordITC503\n"
  },
  {
    "path": "src/instruments/oxford/oxforditc503.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the Oxford ITC 503 temperature controller.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom instruments.abstract_instruments import Instrument\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import ProxyList\n\n# CLASSES #####################################################################\n\n\nclass OxfordITC503(Instrument):\n    \"\"\"\n    The Oxford ITC503 is a multi-sensor temperature controller.\n\n    Example usage::\n\n    >>> import instruments as ik\n    >>> itc = ik.oxford.OxfordITC503.open_gpibusb('/dev/ttyUSB0', 1)\n    >>> print(itc.sensor[0].temperature)\n    >>> print(itc.sensor[1].temperature)\n    \"\"\"\n\n    def __init__(self, filelike):\n        super().__init__(filelike)\n        self.terminator = \"\\r\"\n        self.sendcmd(\"C3\")  # Enable remote commands\n\n    # INNER CLASSES #\n\n    class Sensor:\n        \"\"\"\n        Class representing a probe sensor on the Oxford ITC 503.\n\n        .. warning:: This class should NOT be manually created by the user. It\n            is designed to be initialized by the `OxfordITC503` class.\n        \"\"\"\n\n        def __init__(self, parent, idx):\n            self._parent = parent\n            self._idx = idx + 1\n\n        # PROPERTIES #\n\n        @property\n        def temperature(self):\n            \"\"\"\n            Read the temperature of the attached probe to the specified channel.\n\n            :units: Kelvin\n            :type: `~pint.Quantity`\n            \"\"\"\n            value = float(self._parent.query(f\"R{self._idx}\")[1:])\n            return u.Quantity(value, u.kelvin)\n\n    # PROPERTIES #\n\n    @property\n    def sensor(self):\n        \"\"\"\n        Gets a specific sensor object. The desired sensor is specified like\n        one would access a list.\n\n        For instance, this would query the temperature of the first sensor::\n\n        >>> itc = ik.oxford.OxfordITC503.open_gpibusb('/dev/ttyUSB0', 1)\n        >>> print(itc.sensor[0].temperature)\n\n        :type: `OxfordITC503.Sensor`\n        \"\"\"\n        return ProxyList(self, OxfordITC503.Sensor, range(3))\n"
  },
  {
    "path": "src/instruments/pfeiffer/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing Pfeiffer instruments\n\"\"\"\n\nfrom .tpg36x import TPG36x\n"
  },
  {
    "path": "src/instruments/pfeiffer/tpg36x.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nDriver for the Pfeiffer TPG36x vacumm gauge controller.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom enum import Enum\nimport ipaddress\n\nfrom instruments.abstract_instruments import Instrument\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import ProxyList\n\n# CLASSES #####################################################################\n\n\nclass TPG36x(Instrument):\n    \"\"\"\n    The Pfeiffer TPG361/2 is a vacuum gauge controller with one/two channels.\n\n    By default, the two channel version is intialized. If you have the one channel\n    version (TPG361), set the `number_channels` property to 1.\n\n    Example usage:\n        >>> import instruments as ik\n        >>> inst = ik.pfeiffer.TPG36x.open_serial(\"/dev/ttyUSB0\", 9600)\n        >>> ch = inst.channel[0]\n        >>> ch.pressure\n        4.7634 millibar\n    \"\"\"\n\n    def __init__(self, filelike):\n        super().__init__(filelike)\n\n        self._number_channels = 2\n\n        self._defined_cmd = {\n            \"ETX\": 3,\n            \"ENQ\": 5,\n            \"ACK\": 6,\n            \"NAK\": 21,\n        }\n\n        self.terminator = \"\\r\\n\"\n\n    class EthernetMode(Enum):\n        \"\"\"Enum go get/set the ethernet mode of the device when configuring.\"\"\"\n\n        STATIC = 0\n        DHCP = 1\n\n    class Language(Enum):\n        \"\"\"Enum to get/set the language of the device.\"\"\"\n\n        ENGLISH = 0\n        GERMAN = 1\n        FRENCH = 2\n\n    class Unit(Enum):\n        \"\"\"Enum for the pressure units.\"\"\"\n\n        MBAR = 0\n        TORR = 1\n        PASCAL = 2\n        MICRON = 3\n        HPASCAL = 4\n        VOLT = 5\n\n    class Channel:\n        \"\"\"\n        Class representing a sensor attached to the TPG 362.\n\n        .. warning:: This class should NOT be manually created by the user. It is\n            designed to be initialized by the `TPG36x` class.\n        \"\"\"\n\n        class SensorStatus(Enum):\n            \"\"\"Enum to get the status of the sensor.\"\"\"\n\n            CANNOT_TURN_ON_OFF = 0\n            OFF = 1\n            ON = 2\n\n        def __init__(self, parent, chan):\n            if not isinstance(parent, TPG36x):\n                raise TypeError(\"Don't do that.\")\n            self._chan = chan\n            self._parent = parent\n\n        @property\n        def pressure(self):\n            \"\"\"\n            The pressure measured by the channel, returned as a pint.Quantity\n            with the correct units attached (based on instrument settings).\n\n            This routine also does error checking on the pressure reading and raises\n            an IOError with adequate message if, e.g., no sensor is connected to the\n            channel.\n\n            :return: Pressure on given channel.\n            :rtype: `u.Quantity`\n\n            Example:\n                >>> import instruments as ik\n                >>> inst = ik.pfeiffer.TPG36x.open_serial(\"/dev/ttyUSB0\", 9600)\n                >>> ch = inst.channel[0]\n                >>> ch.pressure\n                4.7634 millibar\n            \"\"\"\n            status_msgs = {\n                0: \"OK\",\n                1: \"Underrange\",\n                2: \"Overrange\",\n                3: \"Sensor error\",\n                4: \"Sensor off\",\n                5: \"No sensor\",\n                6: \"Identification error\",\n            }\n\n            raw_str = self._parent.query(f\"PR{self._chan + 1}\")  # ex: \"0,+1.7377E+00\"\n            status_str, val_str = raw_str.split(\",\")\n            status = int(status_str)\n            val = float(val_str)\n\n            if status != 0:\n                raise OSError(status_msgs.get(status, \"Unknown error\"))\n\n            current_unit = self._parent.unit\n\n            return val * u.Quantity(current_unit.name.lower())\n\n        @property\n        def status(self):\n            \"\"\"\n            Get/set the status of a channel (sensor).\n\n            :return: The status of the sensor.\n            :rtype: `TPG36x.Channel.SensorStatus`\n\n            Example:\n                >>> import instruments as ik\n                >>> inst = ik.pfeiffer.TPG36x.open_serial(\"/dev/ttyUSB0\", 9600)\n                >>> ch = inst.channel[0]\n                >>> ch.status\n                SensorStatus.ON\n            \"\"\"\n            val = self._parent.query(\"SEN\")\n            val = int(val.split(\",\")[self._chan])\n            return self.SensorStatus(val)\n\n        @status.setter\n        def status(self, value):\n            if not isinstance(value, self.SensorStatus):\n                raise ValueError(\"The status must be a SensorStatus enum.\")\n            if value == self.SensorStatus.CANNOT_TURN_ON_OFF:\n                raise ValueError(\"You cannot set the status to this value.\")\n            status_to_send = [0 for _ in range(self._parent.number_channels)]\n            status_to_send[self._chan] = value.value\n            status_to_send_str = \",\".join([str(x) for x in status_to_send])\n            self._parent.sendcmd(f\"SEN,{status_to_send_str}\")\n\n    @property\n    def channel(self):\n        \"\"\"\n        Gets a specific channel object.\n\n        Note that the channel number is pythonic, i.e., the first channel is 0.\n\n        :rtype: `TPG36x.Channel`\n\n        Example:\n            >>> import instruments as ik\n            >>> inst = ik.pfeiffer.TPG36x.open_serial(\"/dev/ttyUSB0\", 9600)\n            >>> ch = inst.channel[0]\n        \"\"\"\n        return ProxyList(self, self.Channel, range(self._number_channels))\n\n    @property\n    def ethernet_configuration(self):\n        \"\"\"\n        Get / set the ethernet configuration of the TPG36x.\n\n        .. note:: If you set the configuration to DHCP, you should simply send\n            `TPG36x.EthernetMode.DHCP` as the sole value. To set it to static,\n            you must provide a list of 4 elements: `[EthernetMode, IP, Subnet, Gateway]`.\n            The types are as follows: `TPG36x.EthernetMode`, `str`, `str`, `str`.\n\n        :return: List of the current configuration:\n            0. Configuration enum `TPG36x.EthernetMode`\n            1. IP address as string (or `ipaddress.ip_address`)\n            2. Subnet mask as string (or `ipaddress.ip_address`)\n            3. Gateway as string (or `ipaddress.ip_address`)\n\n        Example:\n            >>> import instruments as ik\n            >>> inst = ik.pfeiffer.TPG36x.open_serial(\"/dev/ttyUSB0\", 9600)\n            >>> inst.ethernet_configuration = [inst.EthernetMode.STATIC, \"192.168.1.42\", \"255.255.255.0\", \"192.168.1.1\"]\n            >>> inst.ethernet_configuration\n            [<EthernetMode.STATIC: 0>, \"192.168.1.42\", \"255.255.255.0\", \"192.168.1.1\"]\n        \"\"\"\n        return_list = self.query(\"ETH\").split(\",\")\n        return_list[0] = self.EthernetMode(int(return_list[0]))\n        return return_list\n\n    @ethernet_configuration.setter\n    def ethernet_configuration(self, value):\n        if not isinstance(value, list) or len(value) != 4:  # check for correct format\n            if value == self.EthernetMode.DHCP:  # DHCP is a special case\n                self.sendcmd(f\"ETH,{value.value}\")\n                return\n            else:\n                raise ValueError(\n                    \"The ethernet configuration must be a list of 4 elements.\"\n                )\n        if not isinstance(value[0], self.EthernetMode):  # check for correct type\n            raise ValueError(\"The first element must be an EthernetMode.\")\n\n        for addr in value[1:]:\n            _ = ipaddress.ip_address(addr)  # check for valid IP address\n\n        self.sendcmd(f\"ETH,{value[0].value},{value[1]},{value[2]},{value[3]}\")\n\n    @property\n    def language(self):\n        \"\"\"\n        Get/set the language of the TPG36x.\n\n        :rtype: `TPG36x.Language`\n\n        Example:\n            >>> import instruments as ik\n            >>> inst = ik.pfeiffer.TPG36x.open_serial(\"/dev/ttyUSB0\", 9600)\n            >>> inst.language\n            Language.ENGLISH\n        \"\"\"\n        val = int(self.query(\"LNG\"))\n        return self.Language(val)\n\n    @language.setter\n    def language(self, value):\n        self.sendcmd(f\"LNG,{value.value}\")\n\n    @property\n    def mac_address(self):\n        \"\"\"\n        Get the MAC address of the TPG36x.\n\n        :return: MAC address of the TPG36x.\n        :rtype: str\n\n        Example:\n            >>> import instruments as ik\n            >>> inst = ik.pfeiffer.TPG36x.open_serial(\"/dev/ttyUSB0\", 9600)\n            >>> inst.mac_address\n            \"00:1A:2B:3C:4D:5E\"\n        \"\"\"\n        return self.query(\"MAC\")\n\n    @property\n    def name(self):\n        \"\"\"\n        Get the name from the TPG36x.\n\n        :rtype: str\n\n        Example:\n            >>> import instruments as ik\n            >>> inst = ik.pfeiffer.TPG36x.open_serial(\"/dev/ttyUSB0\", 9600)\n            >>> inst.name\n            \"TPG362\"\n        \"\"\"\n        return self.query(\"AYT\").split(\",\")[0]\n\n    @property\n    def number_channels(self):\n        \"\"\"\n        The number of channels on the TPG36x.\n\n        This defaults to two channels. Set this to 1 if you have a one gauge\n        instrument, i.e., a TPG361.\n\n        :rtype: int\n\n        Example:\n            >>> import instruments as ik\n            >>> inst = ik.pfeiffer.TPG36x.open_serial(\"/dev/ttyUSB0\", 9600)\n            >>> inst.number_channels\n            2\n        \"\"\"\n        return self._number_channels\n\n    @number_channels.setter\n    def number_channels(self, value):\n        if value not in (1, 2):\n            raise ValueError(\"The TPG36x only supports 1 or 2 channels.\")\n        self._number_channels = value\n\n    @property\n    def pressure(self):\n        \"\"\"\n        The pressure measured by the first channel.\n\n        To select the channel, get a channel first and then call the pressure\n        method on it.\n\n        :rtype: `pint.Quantity`\n\n        Example:\n            >>> import instruments as ik\n            >>> inst = ik.pfeiffer.TPG36x.open_serial(\"/dev/ttyUSB0\", 9600)\n            >>> inst.pressure\n            0.02 * u.mbar\n        \"\"\"\n        return self.channel[0].pressure\n\n    @property\n    def unit(self):\n        \"\"\"\n        Get/set the unit of the TPG36x (global to the instrument).\n\n        :return: The current unit.\n        :rtype: `TPG36x.Unit`\n\n        Example:\n            >>> import instruments as ik\n            >>> inst = ik.pfeiffer.TPG36x.open_serial(\"/dev/ttyUSB0\", 9600)\n            >>> inst.unit\n            Unit.MBAR\n        \"\"\"\n        val = self.query(\"UNI\")\n        val = int(val)\n        return self.Unit(val)\n\n    @unit.setter\n    def unit(self, new_unit):\n        if isinstance(new_unit, str):\n            new_unit = self.Unit[new_unit.upper()]\n        cmd_val = new_unit.value\n        self.sendcmd(f\"UNI,{cmd_val}\")\n\n    def query(self, cmd):\n        \"\"\"\n        Query the TPG36x with the enquire command.\n\n        :return: The response from the TPG36x.\n        \"\"\"\n        self.sendcmd(cmd)\n        self.write(chr(self._defined_cmd[\"ENQ\"]))\n        return self.read()\n\n    def _ack_expected(self, msg=\"\"):\n        return [chr(self._defined_cmd[\"ACK\"])]\n"
  },
  {
    "path": "src/instruments/phasematrix/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing Phase Matrix instruments\n\"\"\"\n\nfrom .phasematrix_fsw0020 import PhaseMatrixFSW0020\n"
  },
  {
    "path": "src/instruments/phasematrix/phasematrix_fsw0020.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the Phase Matrix FSW0020 signal generator.\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nfrom instruments.abstract_instruments.signal_generator import SingleChannelSG\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import assume_units\n\n# CLASSES #####################################################################\n\n\nclass PhaseMatrixFSW0020(SingleChannelSG):\n    \"\"\"\n    Communicates with a Phase Matrix FSW-0020 signal generator via the\n    \"Native SPI\" protocol, supported on all FSW firmware versions.\n\n    Example::\n\n        >>> import instruments as ik\n        >>> import instruments.units as u\n        >>> inst = ik.phasematrix.PhaseMatrixFSW0020.open_serial(\"/dev/ttyUSB0\", baud=115200)\n        >>> inst.frequency = 1 * u.GHz\n        >>> inst.power = 0 * ik.units.dBm  # Can omit units and will assume dBm\n        >>> inst.output = True\n    \"\"\"\n\n    def reset(self):\n        r\"\"\"\n        Causes the connected signal generator to perform a hardware reset.\n        Note that no commands will be accepted by the generator for at least\n        :math:`5 \\mu\\text{s}`.\n        \"\"\"\n        self.sendcmd(\"0E.\")\n\n    @property\n    def frequency(self):\n        \"\"\"\n        Gets/sets the output frequency of the signal generator.\n        If units are not specified, the frequency is assumed\n        to be in gigahertz (GHz).\n\n        :type: `~pint.Quantity`\n        :units: frequency, assumed to be GHz\n        \"\"\"\n        return (int(self.query(\"04.\"), 16) * u.mHz).to(u.GHz)\n\n    @frequency.setter\n    def frequency(self, newval):\n        # Rescale the input to millihertz as demanded by the signal\n        # generator, then convert to an integer.\n        newval = int(assume_units(newval, u.GHz).to(u.mHz).magnitude)\n\n        # Write the integer to the serial port in ASCII-encoded\n        # uppercase-hexadecimal format, with padding to 12 nybbles.\n        self.sendcmd(f\"0C{newval:012X}.\")\n\n        # No return data, so no readline needed.\n\n    @property\n    def power(self):\n        \"\"\"\n        Gets/sets the output power of the signal generator.\n        If units are not specified, the power is assumed to be in\n        decibel-milliwatts (dBm).\n\n        :type: `~pint.Quantity`\n        :units: log-power, assumed to be dBm\n        \"\"\"\n        return u.Quantity((int(self.query(\"0D.\"), 16)), u.cBm).to(u.dBm)\n\n    @power.setter\n    def power(self, newval):\n        # TODO: convert UnitPower Quantity instances to UnitLogPower.\n        #       That is, convert [W] to [dBm].\n\n        # The Phase Matrix unit speaks in units of centibel-milliwats,\n        # so convert and take the integer part.\n        newval = int(assume_units(newval, u.dBm).to(u.cBm).magnitude)\n\n        # Command code 0x03, parameter length 2 bytes (4 nybbles)\n        self.sendcmd(f\"03{newval:04X}.\")\n\n    @property\n    def phase(self):\n        raise NotImplementedError\n\n    @phase.setter\n    def phase(self, newval):\n        raise NotImplementedError\n\n    @property\n    def blanking(self):\n        \"\"\"\n        Gets/sets the blanking status of the FSW0020\n\n        :type: `bool`\n        \"\"\"\n        raise NotImplementedError\n\n    @blanking.setter\n    def blanking(self, newval):\n        self.sendcmd(f\"05{1 if newval else 0:02X}.\")\n\n    @property\n    def ref_output(self):\n        \"\"\"\n        Gets/sets the reference output status of the FSW0020\n\n        :type: `bool`\n        \"\"\"\n        raise NotImplementedError\n\n    @ref_output.setter\n    def ref_output(self, newval):\n        self.sendcmd(f\"08{1 if newval else 0:02X}.\")\n\n    @property\n    def output(self):\n        \"\"\"\n        Gets/sets the channel output status of the FSW0020. Setting this\n        property to `True` will turn the output on.\n\n        :type: `bool`\n        \"\"\"\n        raise NotImplementedError\n\n    @output.setter\n    def output(self, newval):\n        self.sendcmd(f\"0F{1 if newval else 0:02X}.\")\n\n    @property\n    def pulse_modulation(self):\n        \"\"\"\n        Gets/sets the pulse modulation status of the FSW0020\n\n        :type: `bool`\n        \"\"\"\n        raise NotImplementedError\n\n    @pulse_modulation.setter\n    def pulse_modulation(self, newval):\n        self.sendcmd(f\"09{1 if newval else 0:02X}.\")\n\n    @property\n    def am_modulation(self):\n        \"\"\"\n        Gets/sets the amplitude modulation status of the FSW0020\n\n        :type: `bool`\n        \"\"\"\n        raise NotImplementedError\n\n    @am_modulation.setter\n    def am_modulation(self, newval):\n        self.sendcmd(f\"0A{1 if newval else 0:02X}.\")\n"
  },
  {
    "path": "src/instruments/picowatt/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing Picowatt instruments\n\"\"\"\n\nfrom .picowattavs47 import PicowattAVS47\n"
  },
  {
    "path": "src/instruments/picowatt/picowattavs47.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the Picowatt AVS 47 resistance bridge\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom enum import IntEnum\n\nfrom instruments.generic_scpi import SCPIInstrument\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import enum_property, bool_property, int_property, ProxyList\n\n# CLASSES #####################################################################\n\n\nclass PicowattAVS47(SCPIInstrument):\n    \"\"\"\n    The Picowatt AVS 47 is a resistance bridge used to measure the resistance\n    of low-temperature sensors.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> bridge = ik.picowatt.PicowattAVS47.open_gpibusb('/dev/ttyUSB0', 1)\n    >>> print bridge.sensor[0].resistance\n    \"\"\"\n\n    def __init__(self, filelike):\n        super().__init__(filelike)\n        self.sendcmd(\"HDR 0\")  # Disables response headers from replies\n\n    # INNER CLASSES #\n\n    class Sensor:\n        \"\"\"\n        Class representing a sensor on the PicowattAVS47\n\n        .. warning:: This class should NOT be manually created by the user. It is\n            designed to be initialized by the `PicowattAVS47` class.\n        \"\"\"\n\n        def __init__(self, parent, idx):\n            self._parent = parent\n            self._idx = idx  # The AVS47 is actually zero-based indexing! Wow!\n\n        @property\n        def resistance(self):\n            \"\"\"\n            Gets the resistance. It first ensures that the next measurement\n            reading is up to date by first sending the \"ADC\" command.\n\n            :units: :math:`\\\\Omega` (ohms)\n            :rtype: `~pint.Quantity`\n            \"\"\"\n            # First make sure the mux is on the correct channel\n            if self._parent.mux_channel != self._idx:\n                self._parent.input_source = self._parent.InputSource.ground\n                self._parent.mux_channel = self._idx\n                self._parent.input_source = self._parent.InputSource.actual\n            # Next, prep a measurement with the ADC command\n            self._parent.sendcmd(\"ADC\")\n            return float(self._parent.query(\"RES?\")) * u.ohm\n\n    # ENUMS #\n\n    class InputSource(IntEnum):\n        \"\"\"\n        Enum containing valid input source modes for the AVS 47\n        \"\"\"\n\n        ground = 0\n        actual = 1\n        reference = 2\n\n    # PROPERTIES #\n\n    @property\n    def sensor(self):\n        \"\"\"\n        Gets a specific sensor object. The desired sensor is specified like\n        one would access a list.\n\n        :rtype: `~PicowattAVS47.Sensor`\n\n        .. seealso::\n            `PicowattAVS47` for an example using this property.\n        \"\"\"\n        return ProxyList(self, PicowattAVS47.Sensor, range(8))\n\n    remote = bool_property(\n        command=\"REM\",\n        inst_true=\"1\",\n        inst_false=\"0\",\n        doc=\"\"\"\n        Gets/sets the remote mode state.\n\n        Enabling the remote mode allows all settings to be changed by computer\n        interface and locks-out the front panel.\n\n        :type: `bool`\n        \"\"\",\n    )\n\n    input_source = enum_property(\n        command=\"INP\",\n        enum=InputSource,\n        input_decoration=int,\n        doc=\"\"\"\n        Gets/sets the input source.\n\n        :type: `PicowattAVS47.InputSource`\n        \"\"\",\n    )\n\n    mux_channel = int_property(\n        command=\"MUX\",\n        doc=\"\"\"\n        Gets/sets the multiplexer sensor number.\n        It is recommended that you ground the input before switching the\n        multiplexer channel.\n\n        Valid mux channel values are 0 through 7 (inclusive).\n\n        :type: `int`\n        \"\"\",\n        valid_set=range(8),\n    )\n\n    excitation = int_property(\n        command=\"EXC\",\n        doc=\"\"\"\n        Gets/sets the excitation sensor number.\n\n        Valid excitation sensor values are 0 through 7 (inclusive).\n\n        :type: `int`\n        \"\"\",\n        valid_set=range(8),\n    )\n\n    display = int_property(\n        command=\"DIS\",\n        doc=\"\"\"\n        Gets/sets the sensor that is displayed on the front panel.\n\n        Valid display sensor values are 0 through 7 (inclusive).\n\n        :type: `int`\n        \"\"\",\n        valid_set=range(8),\n    )\n"
  },
  {
    "path": "src/instruments/qubitekk/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing Qubitekk instruments\n\"\"\"\n\nfrom .cc1 import CC1\nfrom .mc1 import MC1\n"
  },
  {
    "path": "src/instruments/qubitekk/cc1.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the Qubitekk CC1 Coincidence Counter instrument.\n\nCC1 Class originally contributed by Catherine Holloway.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom enum import Enum\n\nfrom instruments.generic_scpi.scpi_instrument import SCPIInstrument\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import ProxyList, assume_units, split_unit_str\n\n# CLASSES #####################################################################\n\n\nclass CC1(SCPIInstrument):\n    \"\"\"\n    The CC1 is a hand-held coincidence counter.\n\n    It has two setting values, the dwell time and the coincidence window. The\n    coincidence window determines the amount of time (in ns) that the two\n    detections may be from each other and still be considered a coincidence.\n    The dwell time is the amount of time that passes before the counter will\n    send the clear signal.\n\n    More information can be found at :\n    http://www.qubitekk.com\n    \"\"\"\n\n    def __init__(self, filelike):\n        super().__init__(filelike)\n        self.terminator = \"\\n\"\n        self._channel_count = 3\n        self._firmware = None\n        self._ack_on = False\n        self.sendcmd(\":ACKN OF\")\n        # a readline is required because if the firmware is prior to 2.2,\n        # the cc1 will respond with 'Unknown Command'. After\n        # 2.2, it will either respond by acknowledging the command (turning\n        # acknowledgements off does not take place until after the current\n        # exchange has been completed), or not acknowledging it (if the\n        # acknowledgements are off). The try/except block is required to\n        # handle the case in which acknowledgements are off.\n        try:\n            self.read(-1)\n        except OSError:\n            pass\n        _ = self.firmware  # prime the firmware\n\n        if self.firmware[0] >= 2 and self.firmware[1] > 1:\n            self._bool = (\"ON\", \"OFF\")\n            self._set_fmt = \":{}:{}\"\n            self.TriggerMode = self._TriggerModeNew\n\n        else:\n            self._bool = (\"1\", \"0\")\n            self._set_fmt = \":{} {}\"\n            self.TriggerMode = self._TriggerModeOld\n\n    def _ack_expected(self, msg=\"\"):\n        return (\n            msg\n            if self._ack_on and self.firmware[0] >= 2 and self.firmware[1] > 1\n            else None\n        )\n\n    # ENUMS #\n\n    class _TriggerModeNew(Enum):\n        \"\"\"\n        Enum containing valid trigger modes for the CC1\n        \"\"\"\n\n        continuous = \"MODE CONT\"\n        start_stop = \"MODE STOP\"\n\n    class _TriggerModeOld(Enum):\n        \"\"\"\n        Enum containing valid trigger modes for the CC1\n        \"\"\"\n\n        continuous = \"0\"\n        start_stop = \"1\"\n\n    # INNER CLASSES #\n\n    class Channel:\n        \"\"\"\n        Class representing a channel on the Qubitekk CC1.\n        \"\"\"\n\n        __CHANNEL_NAMES = {1: \"C1\", 2: \"C2\", 3: \"CO\"}\n\n        def __init__(self, cc1, idx):\n            self._cc1 = cc1\n            # Use zero-based indexing for the external API, but one-based\n            # for talking to the instrument.\n            self._idx = idx + 1\n            self._chan = self.__CHANNEL_NAMES[self._idx]\n            self._count = 0\n\n        # PROPERTIES #\n\n        @property\n        def count(self):\n            \"\"\"\n            Gets the counts of this channel.\n\n            :rtype: `int`\n            \"\"\"\n            count = self._cc1.query(f\"COUN:{self._chan}?\")\n            tries = 5\n            try:\n                count = int(count)\n            except ValueError:\n                count = None\n                while count is None and tries > 0:\n                    # try to read again\n                    try:\n                        count = int(self._cc1.read(-1))\n                    except ValueError:\n                        count = None\n                        tries -= 1\n\n            if tries == 0:\n                raise OSError(f\"Could not read the count of channel \" f\"{self._chan}.\")\n\n            self._count = count\n            return self._count\n\n    # PROPERTIES #\n\n    @property\n    def acknowledge(self):\n        \"\"\"\n        Gets/sets the acknowledge message state. If True, the CC1 will echo\n        back every command sent, then print the response (either Unable to\n        comply, Unknown command or the response to a query). If False,\n        the CC1 will only print the response.\n\n        :units: None\n        :type: boolean\n        \"\"\"\n        return self._ack_on\n\n    @acknowledge.setter\n    def acknowledge(self, new_val):\n        if self.firmware[0] >= 2 and self.firmware[1] > 1:\n            if self._ack_on and not new_val:\n                self.sendcmd(\":ACKN OF\")\n                self._ack_on = False\n            elif not self._ack_on and new_val:\n                self.sendcmd(\":ACKN ON\")\n                self._ack_on = True\n        else:\n            raise NotImplementedError(\n                \"Acknowledge message not implemented in \" \"this version.\"\n            )\n\n    @property\n    def gate(self):\n        \"\"\"\n        Gets/sets the gate enable status\n\n        :type: `bool`\n        \"\"\"\n        return self.query(\"GATE?\").strip() == self._bool[0]\n\n    @gate.setter\n    def gate(self, newval):\n        if not isinstance(newval, bool):\n            raise TypeError(\"Bool properties must be specified with a \" \"boolean value\")\n        self.sendcmd(\n            self._set_fmt.format(\"GATE\", self._bool[0] if newval else self._bool[1])\n        )\n\n    @property\n    def subtract(self):\n        \"\"\"\n        Gets/sets the subtract enable status\n\n        :type: `bool`\n        \"\"\"\n        return self.query(\"SUBT?\").strip() == self._bool[0]\n\n    @subtract.setter\n    def subtract(self, newval):\n        if not isinstance(newval, bool):\n            raise TypeError(\"Bool properties must be specified with a \" \"boolean value\")\n        self.sendcmd(\n            self._set_fmt.format(\"SUBT\", self._bool[0] if newval else self._bool[1])\n        )\n\n    @property\n    def trigger_mode(self):\n        \"\"\"\n        Gets/sets the trigger mode setting for the CC1. This can be set to\n        ``continuous`` or ``start/stop`` modes.\n\n        :type: `CC1.TriggerMode`\n        \"\"\"\n        return self.TriggerMode(self.query(\"TRIG?\").strip())\n\n    @trigger_mode.setter\n    def trigger_mode(self, newval):\n        try:  # First assume newval is Enum.value\n            newval = self.TriggerMode[newval]\n        except KeyError:  # Check if newval is Enum.name instead\n            try:\n                newval = self.TriggerMode(newval)\n            except ValueError:\n                raise ValueError(\"Enum property new value not in enum.\")\n        self.sendcmd(self._set_fmt.format(\"TRIG\", self.TriggerMode(newval).value))\n\n    @property\n    def window(self):\n        \"\"\"\n        Gets/sets the length of the coincidence window between the two signals.\n\n        :units: As specified (if a `~pint.Quantity`) or assumed to be\n            of units nanoseconds.\n        :type: `~pint.Quantity`\n        \"\"\"\n        return u.Quantity(*split_unit_str(self.query(\"WIND?\"), \"ns\"))\n\n    @window.setter\n    def window(self, new_val):\n        new_val_mag = int(assume_units(new_val, u.ns).to(u.ns).magnitude)\n        if new_val_mag < 0 or new_val_mag > 7:\n            raise ValueError(\"Window is out of range.\")\n        # window must be an integer!\n        self.sendcmd(f\":WIND {new_val_mag}\")\n\n    @property\n    def delay(self):\n        \"\"\"\n        Get/sets the delay value (in nanoseconds) on Channel 1.\n\n        When setting, ``N`` may be ``0, 2, 4, 6, 8, 10, 12, or 14ns``.\n\n        :rtype: `~pint.Quantity`\n        :return: the delay value\n        \"\"\"\n        return u.Quantity(*split_unit_str(self.query(\"DELA?\"), \"ns\"))\n\n    @delay.setter\n    def delay(self, new_val):\n        new_val = assume_units(new_val, u.ns).to(u.ns)\n        if new_val < 0 * u.ns or new_val > 14 * u.ns:\n            raise ValueError(\"New delay value is out of bounds.\")\n        if new_val.magnitude % 2 != 0:\n            raise ValueError(\"New magnitude must be an even number\")\n        self.sendcmd(\":DELA \" + str(int(new_val.magnitude)))\n\n    @property\n    def dwell_time(self):\n        \"\"\"\n        Gets/sets the length of time before a clear signal is sent to the\n        counters.\n\n        :units: As specified (if a `~pint.Quantity`) or assumed to be\n            of units seconds.\n        :type: `~pint.Quantity`\n        \"\"\"\n        # the older versions of the firmware erroneously report the units of the\n        # dwell time as being seconds rather than ms\n        dwell_time = u.Quantity(*split_unit_str(self.query(\"DWEL?\"), \"s\"))\n        if self.firmware[0] <= 2 and self.firmware[1] <= 1:\n            return dwell_time / 1000.0\n\n        return dwell_time\n\n    @dwell_time.setter\n    def dwell_time(self, new_val):\n        new_val_mag = assume_units(new_val, u.s).to(u.s).magnitude\n        if new_val_mag < 0:\n            raise ValueError(\"Dwell time cannot be negative.\")\n        self.sendcmd(f\":DWEL {new_val_mag}\")\n\n    @property\n    def firmware(self):\n        \"\"\"\n        Gets the firmware version\n\n        :rtype: `tuple`(Major:`int`, Minor:`int`, Patch`int`)\n        \"\"\"\n        # the firmware is assumed not to change while the device is active\n        # firmware is stored locally as it will be gotten often\n        # pylint: disable=no-member\n        if self._firmware is None:\n            while self._firmware is None:\n                self._firmware = self.query(\"FIRM?\")\n                if self._firmware.find(\"Unknown\") >= 0:\n                    self._firmware = None\n                else:\n                    value = self._firmware.replace(\"Firmware v\", \"\").split(\".\")\n                    if len(value) < 3:\n                        for _ in range(3 - len(value)):\n                            value.append(0)\n                    value = tuple(map(int, value))\n                    self._firmware = value\n        return self._firmware\n\n    @property\n    def channel(self):\n        \"\"\"\n        Gets a specific channel object. The desired channel is specified like\n        one would access a list.\n\n        For instance, this would print the counts of the first channel::\n\n        >>> cc = ik.qubitekk.CC1.open_serial('COM8', 19200, timeout=1)\n        >>> print(cc.channel[0].count)\n\n        :rtype: `CC1.Channel`\n        \"\"\"\n        return ProxyList(self, CC1.Channel, range(self._channel_count))\n\n    # METHODS #\n\n    def clear_counts(self):\n        \"\"\"\n        Clears the current total counts on the counters.\n        \"\"\"\n        self.sendcmd(\"CLEA\")\n"
  },
  {
    "path": "src/instruments/qubitekk/mc1.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the Qubitekk MC1 Motor Controller.\n\nMC1 Class originally contributed by Catherine Holloway.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom enum import Enum\n\nfrom instruments.abstract_instruments import Instrument\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import (\n    int_property,\n    enum_property,\n    unitful_property,\n    assume_units,\n)\n\n# CLASSES #####################################################################\n\n\nclass MC1(Instrument):\n    \"\"\"\n    The MC1 is a controller for the qubitekk motor controller. Used with a\n    linear actuator to perform a HOM dip.\n    \"\"\"\n\n    def __init__(self, filelike):\n        super().__init__(filelike)\n        self.terminator = \"\\r\"\n        self._increment = 1 * u.ms\n        self._lower_limit = -300 * u.ms\n        self._upper_limit = 300 * u.ms\n        self._firmware = None\n        self._controller = None\n\n    # ENUMS #\n\n    class MotorType(Enum):\n        \"\"\"\n        Enum for the motor types for the MC1\n        \"\"\"\n\n        radio = \"Radio\"\n        relay = \"Relay\"\n\n    # PROPERTIES #\n\n    @property\n    def increment(self):\n        \"\"\"\n        Gets/sets the stepping increment value of the motor controller\n\n        :units: As specified, or assumed to be of units milliseconds\n        :type: `~pint.Quantity`\n        \"\"\"\n        return self._increment\n\n    @increment.setter\n    def increment(self, newval):\n        self._increment = assume_units(newval, u.ms).to(u.ms)\n\n    @property\n    def lower_limit(self):\n        \"\"\"\n        Gets/sets the stepping lower limit value of the motor controller\n\n        :units: As specified, or assumed to be of units milliseconds\n        :type: `~pint.Quantity`\n        \"\"\"\n        return self._lower_limit\n\n    @lower_limit.setter\n    def lower_limit(self, newval):\n        self._lower_limit = assume_units(newval, u.ms).to(u.ms)\n\n    @property\n    def upper_limit(self):\n        \"\"\"\n        Gets/sets the stepping upper limit value of the motor controller\n\n        :units: As specified, or assumed to be of units milliseconds\n        :type: `~pint.Quantity`\n        \"\"\"\n        return self._upper_limit\n\n    @upper_limit.setter\n    def upper_limit(self, newval):\n        self._upper_limit = assume_units(newval, u.ms).to(u.ms)\n\n    direction = unitful_property(\n        command=\"DIRE\",\n        doc=\"\"\"\n        Get the internal direction variable, which is a function of how far\n        the motor needs to go.\n\n        :type: `~pint.Quantity`\n        :units: milliseconds\n        \"\"\",\n        units=u.ms,\n        readonly=True,\n    )\n\n    inertia = unitful_property(\n        command=\"INER\",\n        doc=\"\"\"\n        Gets/Sets the amount of force required to overcome static inertia. Must\n         be between 0 and 100 milliseconds.\n\n        :type: `~pint.Quantity`\n        :units: milliseconds\n        \"\"\",\n        format_code=\"{:.0f}\",\n        units=u.ms,\n        valid_range=(0 * u.ms, 100 * u.ms),\n        set_fmt=\":{} {}\",\n    )\n\n    @property\n    def internal_position(self):\n        \"\"\"\n        Get the internal motor state position, which is equivalent to the total\n         number of milliseconds that voltage has been applied to the motor in\n         the positive direction minus the number of milliseconds that voltage\n         has been applied to the motor in the negative direction.\n\n        :type: `~pint.Quantity`\n        :units: milliseconds\n        \"\"\"\n        response = int(self.query(\"POSI?\")) * self.step_size\n        return response\n\n    metric_position = unitful_property(\n        command=\"METR\",\n        doc=\"\"\"\n        Get the estimated motor position, in millimeters.\n\n        :type: `~pint.Quantity`\n        :units: millimeters\n        \"\"\",\n        units=u.mm,\n        readonly=True,\n    )\n\n    setting = int_property(\n        command=\"OUTP\",\n        doc=\"\"\"\n        Gets/sets the output port of the optical switch. 0 means input 1 is\n        directed to output 1, and input 2 is directed to output 2. 1 means that\n        input 1 is directed to output 2 and input 2 is directed to output 1.\n\n        :type: `int`\n        \"\"\",\n        valid_set=range(2),\n        set_fmt=\":{} {}\",\n    )\n\n    step_size = unitful_property(\n        command=\"STEP\",\n        doc=\"\"\"\n        Gets/Sets the number of milliseconds per step. Must be between 1\n        and 100 milliseconds.\n\n        :type: `~pint.Quantity`\n        :units: milliseconds\n        \"\"\",\n        format_code=\"{:.0f}\",\n        units=u.ms,\n        valid_range=(1 * u.ms, 100 * u.ms),\n        set_fmt=\":{} {}\",\n    )\n\n    @property\n    def firmware(self):\n        \"\"\"\n        Gets the firmware version\n\n        :rtype: `tuple`(Major:`int`, Minor:`int`, Patch`int`)\n        \"\"\"\n        # the firmware is assumed not to change while the device is active\n        # firmware is stored locally as it will be gotten often\n        # pylint: disable=no-member\n        if self._firmware is None:\n            while self._firmware is None:\n                self._firmware = self.query(\"FIRM?\")\n                value = self._firmware.split(\".\")\n                if len(value) < 3:\n                    for _ in range(3 - len(value)):\n                        value.append(0)\n                value = tuple(map(int, value))\n                self._firmware = value\n        return self._firmware\n\n    controller = enum_property(\n        \"MOTO\",\n        MotorType,\n        doc=\"\"\"\n        Get the motor controller type.\n        \"\"\",\n        readonly=True,\n    )\n\n    @property\n    def move_timeout(self):\n        \"\"\"\n        Get the motor's timeout value, which indicates the number of\n        milliseconds before the motor can start moving again.\n\n        :type: `~pint.Quantity`\n        :units: milliseconds\n        \"\"\"\n        response = int(self.query(\"TIME?\"))\n        return response * self.step_size\n\n    # METHODS #\n\n    def is_centering(self):\n        \"\"\"\n        Query whether the motor is in its centering phase\n\n        :return: False if not centering, True if centering\n        :rtype: `bool`\n        \"\"\"\n        response = self.query(\"CENT?\")\n        return True if int(response) == 1 else False\n\n    def center(self):\n        \"\"\"\n        Commands the motor to go to the center of its travel range\n        \"\"\"\n        self.sendcmd(\":CENT\")\n\n    def reset(self):\n        \"\"\"\n        Sends the stage to the limit of one of its travel ranges\n        \"\"\"\n        self.sendcmd(\":RESE\")\n\n    def move(self, new_position):\n        \"\"\"\n        Move to a specified location. Position is unitless and is defined as\n        the number of motor steps. It varies between motors.\n\n        :param new_position: the location\n        :type new_position: `~pint.Quantity`\n        \"\"\"\n        new_position = assume_units(new_position, u.ms).to(u.ms)\n        if self.lower_limit <= new_position <= self.upper_limit:\n            clock_cycles = new_position / self.step_size\n            cmd = f\":MOVE {int(clock_cycles)}\"\n            self.sendcmd(cmd)\n        else:\n            raise ValueError(\"Location out of range\")\n"
  },
  {
    "path": "src/instruments/rigol/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing Rigol instruments\n\"\"\"\n\nfrom .rigolds1000 import RigolDS1000Series\n"
  },
  {
    "path": "src/instruments/rigol/rigolds1000.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for Rigol DS-1000 series oscilloscopes.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom enum import Enum\n\nfrom instruments.abstract_instruments import Oscilloscope\nfrom instruments.generic_scpi import SCPIInstrument\nfrom instruments.util_fns import ProxyList, bool_property, enum_property\n\n# CLASSES #####################################################################\n\n\nclass RigolDS1000Series(SCPIInstrument, Oscilloscope):\n    \"\"\"\n    The Rigol DS1000-series is a popular budget oriented oscilloscope\n    that has featured wide adoption across hobbyist circles.\n\n    .. warning:: This instrument is not complete, and probably not even\n        functional!\n    \"\"\"\n\n    # ENUMS #\n\n    class AcquisitionType(Enum):\n        \"\"\"\n        Enum containing valid acquisition types for the Rigol DS1000\n        \"\"\"\n\n        normal = \"NORM\"\n        average = \"AVER\"\n        peak_detect = \"PEAK\"\n\n    # INNER CLASSES #\n\n    class DataSource(Oscilloscope.DataSource):\n        \"\"\"\n        Class representing a data source (channel, math, or ref) on the\n        Rigol DS1000\n\n        .. warning:: This class should NOT be manually created by the user. It\n            is designed to be initialized by the `RigolDS1000Series` class.\n        \"\"\"\n\n        @property\n        def name(self):\n            return self._name\n\n        def read_waveform(self, bin_format=True):\n            # TODO: add DIG, FFT.\n            if self.name not in [\"CHAN1\", \"CHAN2\", \"DIG\", \"MATH\", \"FFT\"]:\n                raise NotImplementedError(\n                    \"Rigol DS1000 series does not \"\n                    \"supportreading waveforms from \"\n                    \"{}.\".format(self.name)\n                )\n            self._parent.sendcmd(f\":WAV:DATA? {self.name}\")\n            data = self._parent.binblockread(2)  # TODO: check width\n            return data\n\n    class Channel(DataSource, Oscilloscope.Channel):\n        \"\"\"\n        Class representing a channel on the Rigol DS1000.\n\n        This class inherits from `~RigolDS1000Series.DataSource`.\n\n        .. warning:: This class should NOT be manually created by the user. It\n            is designed to be initialized by the `RigolDS1000Series` class.\n        \"\"\"\n\n        class Coupling(Enum):\n            \"\"\"\n            Enum containing valid coupling modes for the Rigol DS1000\n            \"\"\"\n\n            ac = \"AC\"\n            dc = \"DC\"\n            ground = \"GND\"\n\n        def __init__(self, parent, idx):\n            self._parent = parent\n            self._idx = idx + 1  # Rigols are 1-based.\n\n            # Initialize as a data source with name CHAN{}.\n            super().__init__(self._parent, f\"CHAN{self._idx}\")\n\n        def sendcmd(self, cmd):\n            \"\"\"\n            Passes a command from the `Channel` class to the parent\n            `RigolDS1000Series`, appending the required channel identification.\n\n            :param str cmd: The command string to send to the instrument\n            \"\"\"\n            self._parent.sendcmd(f\":CHAN{self._idx}:{cmd}\")\n\n        def query(self, cmd):\n            \"\"\"\n            Passes a command from the `Channel` class to the parent\n            `RigolDS1000Series`, appending the required channel identification.\n\n            :param str cmd: The command string to send to the instrument\n            :return: The result as returned by the instrument\n            :rtype: `str`\n            \"\"\"\n            return self._parent.query(f\":CHAN{self._idx}:{cmd}\")\n\n        coupling = enum_property(\"COUP\", Coupling)\n\n        bw_limit = bool_property(\"BWL\", inst_true=\"ON\", inst_false=\"OFF\")\n        display = bool_property(\"DISP\", inst_true=\"ON\", inst_false=\"OFF\")\n        invert = bool_property(\"INV\", inst_true=\"ON\", inst_false=\"OFF\")\n\n        # TODO: :CHAN<n>:OFFset\n        # TODO: :CHAN<n>:PROBe\n        # TODO: :CHAN<n>:SCALe\n\n        filter = bool_property(\"FILT\", inst_true=\"ON\", inst_false=\"OFF\")\n\n        # TODO: :CHAN<n>:MEMoryDepth\n\n        vernier = bool_property(\"VERN\", inst_true=\"ON\", inst_false=\"OFF\")\n\n    # PROPERTIES #\n\n    @property\n    def channel(self):\n        # Rigol DS1000 series oscilloscopes all have two channels,\n        # according to the documentation.\n        return ProxyList(self, self.Channel, range(2))\n\n    @property\n    def math(self):\n        return self.DataSource(parent=self, name=\"MATH\")\n\n    @property\n    def ref(self):\n        return self.DataSource(parent=self, name=\"REF\")\n\n    acquire_type = enum_property(\":ACQ:TYPE\", AcquisitionType)\n    # TODO: implement :ACQ:MODE. This is confusing in the documentation,\n    # though.\n\n    @property\n    def acquire_averages(self):\n        \"\"\"\n        Gets/sets the number of averages the oscilloscope should take per\n        acquisition.\n\n        :type: `int`\n        \"\"\"\n        return int(self.query(\":ACQ:AVER?\"))\n\n    @acquire_averages.setter\n    def acquire_averages(self, newval):\n        if newval not in [2**i for i in range(1, 9)]:\n            raise ValueError(\n                \"Number of averages {} not supported by instrument; \"\n                \"must be a power of 2 from 2 to 256.\".format(newval)\n            )\n        self.sendcmd(f\":ACQ:AVER {newval}\")\n\n    # TODO: implement :ACQ:SAMP in a meaningful way. This should probably be\n    #       under Channel, and needs to be unitful.\n    # TODO: I don't understand :ACQ:MEMD yet.\n\n    # METHODS ##\n\n    def force_trigger(self):\n        self.sendcmd(\":FORC\")\n\n    # TODO: consider moving the next few methods to Oscilloscope.\n    def run(self):\n        \"\"\"\n        Starts running the oscilloscope trigger.\n        \"\"\"\n        self.sendcmd(\":RUN\")\n\n    def stop(self):\n        \"\"\"\n        Stops running the oscilloscope trigger.\n        \"\"\"\n        self.sendcmd(\":STOP\")\n\n    # TODO: unitful timebase!\n\n    # FRONT-PANEL KEY EMULATION METHODS ##\n    # These methods correspond one-to-one with physical keys on the front\n    # (local) control panel, except for release_panel, which enables the local\n    # panel and disables any remote lockouts, and for panel_locked.\n    #\n    # Many of the :KEY: commands are not yet implemented as methods.\n\n    panel_locked = bool_property(\":KEY:LOCK\", inst_true=\"ENAB\", inst_false=\"DIS\")\n\n    def release_panel(self):\n        # TODO: better name?\n        # NOTE: method may be redundant with the panel_locked property.\n        \"\"\"\n        Releases any lockout of the local control panel.\n        \"\"\"\n        self.sendcmd(\":KEY:FORC\")\n"
  },
  {
    "path": "src/instruments/srs/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing Lakeshore instruments\n\"\"\"\n\nfrom .srs345 import SRS345\nfrom .srs830 import SRS830\nfrom .srsdg645 import SRSDG645\nfrom .srsctc100 import SRSCTC100\n"
  },
  {
    "path": "src/instruments/srs/srs345.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the SRS 345 function generator.\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nfrom enum import IntEnum\n\nfrom instruments.units import ureg as u\n\nfrom instruments.abstract_instruments import FunctionGenerator\nfrom instruments.generic_scpi import SCPIInstrument\nfrom instruments.util_fns import enum_property, unitful_property\n\n# CLASSES #####################################################################\n\n\nclass SRS345(SCPIInstrument, FunctionGenerator):\n    \"\"\"\n    The SRS DS345 is a 30MHz function generator.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> import instruments.units as u\n    >>> srs = ik.srs.SRS345.open_gpib('/dev/ttyUSB0', 1)\n    >>> srs.frequency = 1 * u.MHz\n    >>> print(srs.offset)\n    >>> srs.function = srs.Function.triangle\n    \"\"\"\n\n    # FIXME: need to add OUTX 1 here, but doing so seems to cause a syntax\n    #        error on the instrument.\n\n    # CONSTANTS #\n\n    _UNIT_MNEMONICS = {\n        FunctionGenerator.VoltageMode.peak_to_peak: \"VP\",\n        FunctionGenerator.VoltageMode.rms: \"VR\",\n        FunctionGenerator.VoltageMode.dBm: \"DB\",\n    }\n\n    _MNEMONIC_UNITS = {mnem: unit for unit, mnem in _UNIT_MNEMONICS.items()}\n\n    # FunctionGenerator CONTRACT #\n\n    def _get_amplitude_(self):\n        resp = self.query(\"AMPL?\").strip()\n\n        return (float(resp[:-2]), self._MNEMONIC_UNITS[resp[-2:]])\n\n    def _set_amplitude_(self, magnitude, units):\n        self.sendcmd(f\"AMPL {magnitude}{self._UNIT_MNEMONICS[units]}\")\n\n    # ENUMS ##\n\n    class Function(IntEnum):\n        \"\"\"\n        Enum containing valid output function modes for the SRS 345\n        \"\"\"\n\n        sinusoid = 0\n        square = 1\n        triangle = 2\n        ramp = 3\n        noise = 4\n        arbitrary = 5\n\n    # PROPERTIES ##\n\n    frequency = unitful_property(\n        command=\"FREQ\",\n        units=u.Hz,\n        doc=\"\"\"\n        Gets/sets the output frequency.\n\n        :units: As specified, or assumed to be :math:`\\\\text{Hz}` otherwise.\n        :type: `float` or `~pint.Quantity`\n        \"\"\",\n    )\n\n    function = enum_property(\n        command=\"FUNC\",\n        enum=Function,\n        input_decoration=int,\n        doc=\"\"\"\n        Gets/sets the output function of the function generator.\n\n        :type: `~SRS345.Function`\n        \"\"\",\n    )\n\n    offset = unitful_property(\n        command=\"OFFS\",\n        units=u.volt,\n        doc=\"\"\"\n        Gets/sets the offset voltage for the output waveform.\n\n        :units: As specified, or assumed to be :math:`\\\\text{V}` otherwise.\n        :type: `float` or `~pint.Quantity`\n        \"\"\",\n    )\n\n    phase = unitful_property(\n        command=\"PHSE\",\n        units=u.degree,\n        doc=\"\"\"\n        Gets/sets the phase for the output waveform.\n\n        :units: As specified, or assumed to be degrees (:math:`{}^{\\\\circ}`)\n            otherwise.\n        :type: `float` or `~pint.Quantity`\n        \"\"\",\n    )\n"
  },
  {
    "path": "src/instruments/srs/srs830.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the SRS 830 lock-in amplifier.\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport math\nimport time\nimport warnings\nfrom enum import Enum, IntEnum\n\nfrom instruments.abstract_instruments.comm import (\n    GPIBCommunicator,\n    SerialCommunicator,\n    LoopbackCommunicator,\n)\nfrom instruments.generic_scpi import SCPIInstrument\nfrom instruments.optional_dep_finder import numpy\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import (\n    bool_property,\n    bounded_unitful_property,\n    enum_property,\n    unitful_property,\n)\n\n# CONSTANTS ###################################################################\n\nVALID_SAMPLE_RATES = [2.0**n for n in range(-4, 10)]\nVALID_SAMPLE_RATES += [\"trigger\"]\n\n# CLASSES #####################################################################\n\n\nclass SRS830(SCPIInstrument):\n    \"\"\"\n    Communicates with a Stanford Research Systems 830 Lock-In Amplifier.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> import instruments.units as u\n    >>> srs = ik.srs.SRS830.open_gpibusb('/dev/ttyUSB0', 1)\n    >>> srs.frequency = 1000 * u.hertz # Lock-In frequency\n    >>> data = srs.take_measurement(1, 10) # 1Hz sample rate, 10 samples total\n    \"\"\"\n\n    def __init__(self, filelike, outx_mode=None):\n        \"\"\"\n        Class initialization method.\n\n        :param int outx_mode: Manually over-ride which ``OUTX`` command to send\n            at startup. This is a command that needs to be sent as specified\n            by the SRS830 manual. If left default, the correct ``OUTX`` command\n            will be sent depending on what type of communicator self._file is.\n        \"\"\"\n        super().__init__(filelike)\n        if outx_mode == 1:\n            self.sendcmd(\"OUTX 1\")\n        elif outx_mode == 2:\n            self.sendcmd(\"OUTX 2\")\n        else:\n            if isinstance(self._file, GPIBCommunicator):\n                self.sendcmd(\"OUTX 1\")\n            elif isinstance(self._file, SerialCommunicator):\n                self.sendcmd(\"OUTX 2\")\n            elif isinstance(self._file, LoopbackCommunicator):\n                pass\n            else:\n                warnings.warn(\n                    \"OUTX command has not been set. Instrument \"\n                    \"behaviour is unknown.\",\n                    UserWarning,\n                )\n\n    # ENUMS #\n\n    class FreqSource(IntEnum):\n        \"\"\"\n        Enum for the SRS830 frequency source settings.\n        \"\"\"\n\n        external = 0\n        internal = 1\n\n    class Coupling(IntEnum):\n        \"\"\"\n        Enum for the SRS830 channel coupling settings.\n        \"\"\"\n\n        ac = 0\n        dc = 1\n\n    class BufferMode(IntEnum):\n        \"\"\"\n        Enum for the SRS830 buffer modes.\n        \"\"\"\n\n        one_shot = 0\n        loop = 1\n\n    class Mode(Enum):\n        \"\"\"\n        Enum containing valid modes for the SRS 830\n        \"\"\"\n\n        x = \"x\"\n        y = \"y\"\n        r = \"r\"\n        theta = \"theta\"\n        xnoise = \"xnoise\"\n        ynoise = \"ynoise\"\n        aux1 = \"aux1\"\n        aux2 = \"aux2\"\n        aux3 = \"aux3\"\n        aux4 = \"aux4\"\n        ref = \"ref\"\n        ch1 = \"ch1\"\n        ch2 = \"ch2\"\n        none = \"none\"\n\n    # CONSTANTS #\n\n    _XYR_MODE_MAP = {Mode.x: 1, Mode.y: 2, Mode.r: 3}\n\n    # PROPERTIES #\n\n    frequency_source = enum_property(\n        \"FMOD\",\n        FreqSource,\n        input_decoration=int,\n        doc=\"\"\"\n        Gets/sets the frequency source used. This is either an external source,\n            or uses the internal reference.\n\n        :type: `SRS830.FreqSource`\n        \"\"\",\n    )\n\n    frequency = unitful_property(\n        \"FREQ\",\n        u.hertz,\n        valid_range=(0, None),\n        doc=\"\"\"\n        Gets/sets the lock-in amplifier reference frequency.\n\n        :units: As specified (if a `~pint.Quantity`) or assumed to be\n            of units Hertz.\n        :type: `~pint.Quantity` with units Hertz.\n        \"\"\",\n    )\n\n    phase, phase_min, phase_max = bounded_unitful_property(\n        \"PHAS\",\n        u.degrees,\n        valid_range=(-360 * u.degrees, 730 * u.degrees),\n        doc=\"\"\"\n        Gets/set the phase of the internal reference signal.\n\n        Set value should be -360deg <= newval < +730deg.\n\n        :units: As specified (if a `~pint.Quantity`) or assumed to be\n            of units degrees.\n        :type: `~pint.Quantity` with units degrees.\n        \"\"\",\n    )\n\n    amplitude, amplitude_min, amplitude_max = bounded_unitful_property(\n        \"SLVL\",\n        u.volt,\n        valid_range=(0.004 * u.volt, 5 * u.volt),\n        doc=\"\"\"\n        Gets/set the amplitude of the internal reference signal.\n\n        Set value should be 0.004 <= newval <= 5.000\n\n        :units: As specified (if a `~pint.Quantity`) or assumed to be\n            of units volts. Value should be specified as peak-to-peak.\n        :type: `~pint.Quantity` with units volts peak-to-peak.\n        \"\"\",\n    )\n\n    input_shield_ground = bool_property(\n        \"IGND\",\n        inst_true=\"1\",\n        inst_false=\"0\",\n        doc=\"\"\"\n        Function sets the input shield grounding to either 'float' or 'ground'.\n\n        :type: `bool`\n        \"\"\",\n    )\n\n    coupling = enum_property(\n        \"ICPL\",\n        Coupling,\n        input_decoration=int,\n        doc=\"\"\"\n        Gets/sets the input coupling to either 'ac' or 'dc'.\n\n        :type: `SRS830.Coupling`\n        \"\"\",\n    )\n\n    @property\n    def sample_rate(self):\n        r\"\"\"\n        Gets/sets the data sampling rate of the lock-in.\n\n        Acceptable set values are :math:`2^n` where :math:`n \\in \\{-4...+9\\}` or\n        the string `trigger`.\n\n        :type: `~pint.Quantity` with units Hertz.\n        \"\"\"\n        value = int(self.query(\"SRAT?\"))\n        if value == 14:\n            return \"trigger\"\n        return u.Quantity(VALID_SAMPLE_RATES[value], u.Hz)\n\n    @sample_rate.setter\n    def sample_rate(self, newval):\n        if isinstance(newval, str):\n            newval = newval.lower()\n\n        if newval in VALID_SAMPLE_RATES:\n            self.sendcmd(f\"SRAT {VALID_SAMPLE_RATES.index(newval)}\")\n        else:\n            raise ValueError(\n                \"Valid samples rates given by {} \"\n                'and \"trigger\".'.format(VALID_SAMPLE_RATES)\n            )\n\n    buffer_mode = enum_property(\n        \"SEND\",\n        BufferMode,\n        input_decoration=int,\n        doc=\"\"\"\n        Gets/sets the end of buffer mode.\n\n        This sets the behaviour of the instrument when the data storage buffer\n        is full. Setting to `one_shot` will stop acquisition, while `loop`\n        will repeat from the start.\n\n        :type: `SRS830.BufferMode`\n        \"\"\",\n    )\n\n    @property\n    def num_data_points(self):\n        \"\"\"\n        Gets the number of data sets in the SRS830 buffer.\n\n        :type: `int`\n        \"\"\"\n        resp = None\n        i = 0\n        while not resp and i < 10:\n            resp = self.query(\"SPTS?\").strip()\n            i += 1\n        if not resp:\n            raise OSError(\n                f\"Expected integer response from instrument, got {repr(resp)}\"\n            )\n        return int(resp)\n\n    data_transfer = bool_property(\n        \"FAST\",\n        inst_true=\"2\",\n        inst_false=\"0\",\n        doc=\"\"\"\n        Gets/sets the data transfer status.\n\n        Note that this function only makes use of 2 of the 3 data transfer modes\n        supported by the SRS830. The supported modes are FAST0 and FAST2. The\n        other, FAST1, is for legacy systems which this package does not support.\n\n        :type: `bool`\n        \"\"\",\n    )\n\n    # AUTO- METHODS #\n\n    def auto_offset(self, mode):\n        \"\"\"\n        Sets a specific channel mode to auto offset. This is the same as\n        pressing the auto offset key on the display.\n\n        It sets the offset of the mode specified to zero.\n\n        :param mode: Target mode of auto_offset function. Valid inputs are\n            {X|Y|R}.\n        :type mode: `~SRS830.Mode` or `str`\n        \"\"\"\n        if isinstance(mode, str):\n            mode = mode.lower()\n            mode = SRS830.Mode[mode]\n\n        if mode not in self._XYR_MODE_MAP:\n            raise ValueError(\"Specified mode not valid for this function.\")\n\n        mode = self._XYR_MODE_MAP[mode]\n\n        self.sendcmd(f\"AOFF {mode}\")\n\n    def auto_phase(self):\n        \"\"\"\n        Sets the lock-in to auto phase.\n        This does the same thing as pushing the auto phase button.\n\n        Do not send this message again without waiting the correct amount\n        of time for the lock-in to finish.\n        \"\"\"\n        self.sendcmd(\"APHS\")\n\n    # META-METHODS #\n\n    def init(self, sample_rate, buffer_mode):\n        r\"\"\"\n        Wrapper function to prepare the SRS830 for measurement.\n        Sets both the data sampling rate and the end of buffer mode\n\n        :param sample_rate: The desired sampling\n            rate. Acceptable set values are :math:`2^n` where\n            :math:`n \\in \\{-4...+9\\}` in units Hertz or the string `trigger`.\n        :type sample_rate: `~pint.Quantity` or `str`\n\n        :param `SRS830.BufferMode` buffer_mode: This sets the behaviour of the\n            instrument when the data storage buffer is full. Setting to\n            `one_shot` will stop acquisition, while `loop` will repeat from\n            the start.\n        \"\"\"\n        self.clear_data_buffer()\n        self.sample_rate = sample_rate\n        self.buffer_mode = buffer_mode\n\n    def start_data_transfer(self):\n        \"\"\"\n        Wrapper function to start the actual data transfer.\n        Sets the transfer mode to FAST2, and triggers the data transfer\n        to start after a delay of 0.5 seconds.\n        \"\"\"\n        self.data_transfer = True\n        self.start_scan()\n\n    def take_measurement(self, sample_rate, num_samples):\n        \"\"\"\n        Wrapper function that allows you to easily take measurements with a\n        specified sample rate and number of desired samples.\n\n        Function will call time.sleep() for the required amount of time it will\n        take the instrument to complete this sampling operation.\n\n        Returns a list containing two items, each of which are lists containing\n        the channel data. The order is [[Ch1 data], [Ch2 data]].\n\n        :param `int` sample_rate: Set the desired sample rate of the\n            measurement. See `~SRS830.sample_rate` for more information.\n\n        :param `int` num_samples: Number of samples to take.\n\n        :rtype: `tuple`[`tuple`[`float`, ...], `tuple`[`float`, ...]]\n            or if numpy is installed, `numpy.array`[`numpy.array`, `numpy.array`]\n        \"\"\"\n        if num_samples > 16383:\n            raise ValueError(\"Number of samples cannot exceed 16383.\")\n\n        sample_time = math.ceil(num_samples / sample_rate)\n\n        self.init(sample_rate, SRS830.BufferMode[\"one_shot\"])\n        self.start_data_transfer()\n\n        time.sleep(sample_time + 0.1)\n\n        self.pause()\n\n        # The following should fail. We do this to force the instrument\n        # to flush its internal buffers.\n        # Note that this causes a redundant transmission, and should be fixed\n        # in future versions.\n        try:\n            self.num_data_points\n        except OSError:\n            pass\n\n        ch1 = self.read_data_buffer(\"ch1\")\n        ch2 = self.read_data_buffer(\"ch2\")\n\n        if numpy:\n            return numpy.array([ch1, ch2])\n        return ch1, ch2\n\n    # OTHER METHODS #\n\n    def set_offset_expand(self, mode, offset, expand):\n        \"\"\"\n        Sets the channel offset and expand parameters.\n        Offset is a percentage, and expand is given as a multiplication\n        factor of 1, 10, or 100.\n\n        :param mode: The channel mode that you wish to change the\n            offset and/or the expand of. Valid modes are X, Y, and R.\n        :type mode: `SRS830.Mode` or `str`\n\n        :param float offset: Offset of the mode, given as a percent.\n            offset = <-105...+105>.\n\n        :param int expand: Expansion factor for the measurement. Valid input\n            is {1|10|100}.\n        \"\"\"\n        if isinstance(mode, str):\n            mode = mode.lower()\n            mode = SRS830.Mode[mode]\n\n        if mode not in self._XYR_MODE_MAP:\n            raise ValueError(\"Specified mode not valid for this function.\")\n\n        mode = self._XYR_MODE_MAP[mode]\n\n        if not isinstance(offset, (int, float)):\n            raise TypeError(\"Offset parameter must be an integer or a float.\")\n        if not isinstance(expand, (int, float)):\n            raise TypeError(\"Expand parameter must be an integer or a float.\")\n\n        if (offset > 105) or (offset < -105):\n            raise ValueError(\"Offset mustbe -105 <= offset <= +105.\")\n\n        valid = [1, 10, 100]\n        if expand in valid:\n            expand = valid.index(expand)\n        else:\n            raise ValueError(\"Expand must be 1, 10, 100.\")\n\n        self.sendcmd(f\"OEXP {mode},{int(offset)},{expand}\")\n\n    def start_scan(self):\n        \"\"\"\n        After setting the data transfer on via the dataTransfer function,\n        this is used to start the scan. The scan starts after a delay of\n        0.5 seconds.\n        \"\"\"\n        self.sendcmd(\"STRD\")\n\n    def pause(self):\n        \"\"\"\n        Has the instrument pause data capture.\n        \"\"\"\n        self.sendcmd(\"PAUS\")\n\n    _data_snap_modes = {\n        Mode.x: 1,\n        Mode.y: 2,\n        Mode.r: 3,\n        Mode.theta: 4,\n        Mode.aux1: 5,\n        Mode.aux2: 6,\n        Mode.aux3: 7,\n        Mode.aux4: 8,\n        Mode.ref: 9,\n        Mode.ch1: 10,\n        Mode.ch2: 11,\n    }\n\n    def data_snap(self, mode1, mode2):\n        \"\"\"\n        Takes a snapshot of the current parameters are defined by variables\n        mode1 and mode2.\n\n        For combinations (X,Y) and (R,THETA), they are taken at the same\n        instant. All other combinations are done sequentially, and may\n        not represent values taken from the same timestamp.\n\n        Returns a list of floats, arranged in the order that they are\n        given in the function input parameters.\n\n        :param mode1: Mode to take data snap for channel 1. Valid inputs are\n            given by: {X|Y|R|THETA|AUX1|AUX2|AUX3|AUX4|REF|CH1|CH2}\n        :type mode1: `~SRS830.Mode` or `str`\n        :param mode2: Mode to take data snap for channel 2. Valid inputs are\n            given by: {X|Y|R|THETA|AUX1|AUX2|AUX3|AUX4|REF|CH1|CH2}\n        :type mode2: `~SRS830.Mode` or `str`\n\n        :rtype: `list`\n        \"\"\"\n        if isinstance(mode1, str):\n            mode1 = mode1.lower()\n            mode1 = SRS830.Mode[mode1]\n        if isinstance(mode2, str):\n            mode2 = mode2.lower()\n            mode2 = SRS830.Mode[mode2]\n\n        if (mode1 not in self._data_snap_modes) or (mode2 not in self._data_snap_modes):\n            raise ValueError(\"Specified mode not valid for this function.\")\n\n        mode1 = self._XYR_MODE_MAP[mode1]\n        mode2 = self._XYR_MODE_MAP[mode2]\n\n        if mode1 == mode2:\n            raise ValueError(\"Both parameters for the data snapshot are the \" \"same.\")\n\n        result = self.query(f\"SNAP? {mode1},{mode2}\")\n        return list(map(float, result.split(\",\")))\n\n    _valid_read_data_buffer = {Mode.ch1: 1, Mode.ch2: 2}\n\n    def read_data_buffer(self, channel):\n        \"\"\"\n        Reads the entire data buffer for a specific channel.\n        Transfer is done in ASCII mode. Although binary would be faster,\n        this is not currently implemented.\n\n        Returns a list of floats containing instrument's measurements.\n\n        :param channel: Channel data buffer to read from. Valid channels are\n            given by {CH1|CH2}.\n        :type channel: `SRS830.Mode` or `str`\n\n        :rtype: `tuple`[`float`, ...] or if numpy is installed, `numpy.array`\n        \"\"\"\n        if isinstance(channel, str):\n            channel = channel.lower()\n            channel = SRS830.Mode[channel]\n\n        if channel not in self._valid_read_data_buffer:\n            raise ValueError(\"Specified mode not valid for this function.\")\n\n        channel = self._valid_read_data_buffer[channel]\n\n        N = self.num_data_points  # Retrieve number of data points stored\n\n        # Query device for entire buffer, returning in ASCII, then\n        # converting to a list of floats before returning to the\n        # calling method\n        data = self.query(f\"TRCA?{channel},0,{N}\").strip()\n        if numpy:\n            return numpy.fromstring(data, sep=\",\")\n        return tuple(map(float, data.split(\",\")))\n\n    def clear_data_buffer(self):\n        \"\"\"\n        Clears the data buffer of the SRS830.\n        \"\"\"\n        self.sendcmd(\"REST\")\n\n    _valid_channel_display = [\n        {Mode.x: 0, Mode.r: 1, Mode.xnoise: 2, Mode.aux1: 3, Mode.aux2: 4},  # channel1\n        {  # channel2\n            Mode.y: 0,\n            Mode.theta: 1,\n            Mode.ynoise: 2,\n            Mode.aux3: 3,\n            Mode.aux4: 4,\n        },\n    ]\n\n    _valid_channel_ratio = [\n        {Mode.none: 0, Mode.aux1: 1, Mode.aux2: 2},  # channel1\n        {Mode.none: 0, Mode.aux3: 1, Mode.aux4: 2},  # channel2\n    ]\n\n    _valid_channel = {Mode.ch1: 1, Mode.ch2: 2}\n\n    def set_channel_display(self, channel, display, ratio):\n        \"\"\"\n        Sets the display of the two channels.\n        Channel 1 can display X, R, X Noise, Aux In 1, Aux In 2\n        Channel 2 can display Y, Theta, Y Noise, Aux In 3, Aux In 4\n\n        Channel 1 can have ratio of None, Aux In 1, Aux In 2\n        Channel 2 can have ratio of None, Aux In 3, Aux In 4\n\n        :param channel: Channel you wish to set the display of. Valid input is\n            one of {CH1|CH2}.\n        :type channel: `~SRS830.Mode` or `str`\n\n        :param display: Setting the channel will be changed to. Valid\n            input is one of {X|Y|R|THETA|XNOISE|YNOISE|AUX1|AUX2|AUX3|AUX4}\n        :type display: `~SRS830.Mode` or `str`\n\n        :param ratio: Desired ratio setting for this channel. Valid input\n            is one of {NONE|AUX1|AUX2|AUX3|AUX4}\n        :type ratio: `~SRS830.Mode` or `str`\n        \"\"\"\n        if isinstance(channel, str):\n            channel = channel.lower()\n            channel = SRS830.Mode[channel]\n        if isinstance(display, str):\n            display = display.lower()\n            display = SRS830.Mode[display]\n        if isinstance(ratio, str):\n            ratio = ratio.lower()\n            ratio = SRS830.Mode[ratio]\n\n        if channel not in self._valid_channel:\n            raise ValueError(\"Specified channel not valid for this function.\")\n\n        channel = self._valid_channel[channel]\n\n        if display not in self._valid_channel_display[channel - 1]:\n            raise ValueError(\"Specified display mode not valid for this \" \"function.\")\n        if ratio not in self._valid_channel_ratio[channel - 1]:\n            raise ValueError(\"Specified display ratio not valid for this \" \"function.\")\n\n        display = self._valid_channel_display[channel - 1][display]\n        ratio = self._valid_channel_ratio[channel - 1][ratio]\n\n        self.sendcmd(f\"DDEF {channel},{display},{ratio}\")\n"
  },
  {
    "path": "src/instruments/srs/srsctc100.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the SRS CTC-100 cryogenic temperature controller.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom contextlib import contextmanager\nfrom enum import Enum\n\nfrom instruments.generic_scpi import SCPIInstrument\nfrom instruments.optional_dep_finder import numpy\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import ProxyList\n\n# CLASSES #####################################################################\n\n\nclass SRSCTC100(SCPIInstrument):\n    \"\"\"\n    Communicates with a Stanford Research Systems CTC-100 cryogenic temperature\n    controller.\n    \"\"\"\n\n    def __init__(self, filelike):\n        super().__init__(filelike)\n        self._do_errcheck = True\n\n    # DICTIONARIES #\n\n    _BOOL_NAMES = {\"On\": True, \"Off\": False}\n\n    # Note that the SRS CTC-100 uses '\\xb0' to represent '°'.\n    _UNIT_NAMES = {\n        \"\\xb0C\": u.celsius,\n        \"W\": u.watt,\n        \"V\": u.volt,\n        \"\\xea\": u.ohm,\n        \"\": u.dimensionless,\n    }\n\n    # INNER CLASSES ##\n\n    class SensorType(Enum):\n        \"\"\"\n        Enum containing valid sensor types for the SRS CTC-100\n        \"\"\"\n\n        rtd = \"RTD\"\n        thermistor = \"Thermistor\"\n        diode = \"Diode\"\n        rox = \"ROX\"\n\n    class Channel:\n        \"\"\"\n        Represents an input or output channel on an SRS CTC-100 cryogenic\n        temperature controller.\n        \"\"\"\n\n        def __init__(self, ctc, chan_name):\n            self._ctc = ctc\n\n            # Save the pretty name that we are given.\n            self._chan_name = chan_name\n\n            # Strip spaces from the name used in remote programming, as\n            # specified on page 14 of the manual.\n            self._rem_name = chan_name.replace(\" \", \"\")\n\n        # PRIVATE METHODS #\n\n        def _get(self, prop_name):\n            return self._ctc.query(f\"{self._rem_name}.{prop_name}?\").strip()\n\n        def _set(self, prop_name, newval):\n            self._ctc.sendcmd(f'{self._rem_name}.{prop_name} = \"{newval}\"')\n\n        # DISPLAY AND PROGRAMMING #\n        # These properties control how the channel is identified in scripts\n        # and on the front-panel display.\n\n        @property\n        def name(self):\n            \"\"\"\n            Gets/sets the name of the channel that will be used by the\n            instrument to identify the channel in programming and on the\n            display.\n\n            :type: `str`\n            \"\"\"\n            return self._chan_name\n\n        @name.setter\n        def name(self, newval):\n            self._set(\"name\", newval)\n            # TODO: check for errors!\n            self._chan_name = newval\n            self._rem_name = newval.replace(\" \", \"\")\n\n        # BASICS #\n\n        @property\n        def value(self):\n            \"\"\"\n            Gets the measurement value of the channel. Units depend on what\n            kind of sensor and/or channel you have specified. Units can be one\n            of ``celsius``, ``watt``, ``volt``, ``ohm``, or ``dimensionless``.\n\n            :type: `~pint.Quantity`\n            \"\"\"\n            # WARNING: Queries all units all the time.\n            # TODO: Make an OutputChannel that subclasses this class,\n            #       and add a setter for value.\n            return u.Quantity(float(self._get(\"value\")), self.units)\n\n        @property\n        def units(self):\n            \"\"\"\n            Gets the appropriate units for the specified channel.\n\n            Units can be one of ``celsius``, ``watt``, ``volt``, ``ohm``, or\n            ``dimensionless``.\n\n            :type: `~pint.Unit`\n            \"\"\"\n            # FIXME: does not respect \"chan.d/dt\" property.\n            return self._ctc.channel_units()[self._chan_name]\n            # FIXME: the following line doesn't do what I'd expect, and so it's\n            #        commented out.\n            # return\n            # self._ctc._UNIT_NAMES[self._ctc.query('{}.units?'.format(self._rem_name)).strip()]\n\n        @property\n        def sensor_type(self):\n            \"\"\"\n            Gets the type of sensor attached to the specified channel.\n\n            :type: `SRSCTC100.SensorType`\n            \"\"\"\n            return self._ctc.SensorType(self._get(\"sensor\"))\n\n        # STATS #\n        # The following properties control and query the statistics of the\n        # channel.\n\n        @property\n        def stats_enabled(self):\n            \"\"\"\n            Gets/sets enabling the statistics for the specified channel.\n\n            :type: `bool`\n            \"\"\"\n            return True if self._get(\"stats\") == \"On\" else False\n\n        @stats_enabled.setter\n        def stats_enabled(self, newval):\n            # FIXME: replace with bool_property factory\n            self._set(\"stats\", \"On\" if newval else \"Off\")\n\n        @property\n        def stats_points(self):\n            \"\"\"\n            Gets/sets the number of sample points to use for the channel\n            statistics.\n\n            :type: `int`\n            \"\"\"\n            return int(self._get(\"points\"))\n\n        @stats_points.setter\n        def stats_points(self, newval):\n            self._set(\"points\", int(newval))\n\n        @property\n        def average(self):\n            \"\"\"\n            Gets the average measurement for the specified channel as\n            determined by the statistics gathering.\n\n            :type: `~pint.Quantity`\n            \"\"\"\n            return u.Quantity(float(self._get(\"average\")), self.units)\n\n        @property\n        def std_dev(self):\n            \"\"\"\n            Gets the standard deviation for the specified channel as determined\n            by the statistics gathering.\n\n            :type: `~pint.Quantity`\n            \"\"\"\n            return u.Quantity(float(self._get(\"SD\")), self.units)\n\n        # LOGGING #\n\n        def get_log_point(self, which=\"next\", units=None):\n            \"\"\"\n            Get a log data point from the instrument.\n\n            :param str which: Which data point you want. Valid examples\n                include ``first``, and ``next``. Consult the instrument\n                manual for the complete list\n            :param units: Units to attach to the returned data point. If left\n                with the value of `None` then the instrument will be queried\n                for the current units setting.\n            :type units: `~pint.Unit`\n            :return: The log data point with units\n            :rtype: `~pint.Quantity`\n            \"\"\"\n            if units is None:\n                units = self.units\n\n            point = [\n                s.strip()\n                for s in self._ctc.query(f\"getLog.xy {self._chan_name}, {which}\").split(\n                    \",\"\n                )\n            ]\n            return u.Quantity(float(point[0]), \"ms\"), u.Quantity(float(point[1]), units)\n\n        def get_log(self):\n            \"\"\"\n            Gets all of the log data points currently saved in the instrument\n            memory.\n\n            :return: Tuple of all the log data points. First value is time,\n                second is the measurement value.\n            :rtype: If numpy is installed, tuple of 2x `~pint.Quantity`,\n                each comprised of a numpy array (`numpy.dnarray`).\n                Else, `tuple`[`tuple`[`~pint.Quantity`, ...], `tuple`[`~pint.Quantity`, ...]]\n            \"\"\"\n            # Remember the current units.\n            units = self.units\n\n            # Find out how many points there are.\n            n_points = int(self._ctc.query(f\"getLog.xy? {self._chan_name}\"))\n\n            # Make an empty quantity that size for the times and for the channel\n            # values.\n            if numpy:\n                ts = u.Quantity(numpy.empty((n_points,)), u.ms)\n                temps = u.Quantity(numpy.empty((n_points,)), units)\n            else:\n                ts = [u.Quantity(0, u.ms)] * n_points\n                temps = [u.Quantity(0, units)] * n_points\n\n            # Reset the position to the first point, then save it.\n            # pylint: disable=protected-access\n            with self._ctc._error_checking_disabled():\n                ts[0], temps[0] = self.get_log_point(\"first\", units)\n                for idx in range(1, n_points):\n                    ts[idx], temps[idx] = self.get_log_point(\"next\", units)\n\n            # Do an actual error check now.\n            if self._ctc.error_check_toggle:\n                self._ctc.errcheck()\n\n            if not numpy:\n                ts = tuple(ts)\n                temps = tuple(temps)\n\n            return ts, temps\n\n    # PRIVATE METHODS ##\n\n    def _channel_names(self):\n        \"\"\"\n        Returns the names of valid channels, using the ``getOutput.names``\n        command, as documented in the example on page 14 of the\n        `CTC-100 manual`_.\n\n        Note that ``getOutput`` also lists input channels, confusingly enough.\n\n        .. _CTC-100 manual: http://www.thinksrs.com/downloads/PDFs/Manuals/CTC100m.pdf\n        \"\"\"\n        # We need to split apart the comma-separated list and make sure that\n        # no newlines or other whitespace gets carried along for the ride.\n        # Note that we do NOT strip spaces here, as this is done inside\n        # the Channel object. Doing things that way allows us to present\n        # the actual pretty name to users, but to use the remote-programming\n        # name in commands.\n        # As a consequence, users of this instrument MUST use spaces\n        # matching the pretty name and not the remote-programming name.\n        # CG could not think of a good way around this.\n        names = [name.strip() for name in self.query(\"getOutput.names?\").split(\",\")]\n        return names\n\n    def channel_units(self):\n        \"\"\"\n        Returns a dictionary from channel names to channel units, using the\n        ``getOutput.units`` command. Unknown units and dimensionless quantities\n        are presented the same way by the instrument, and so both are reported\n        using `u.dimensionless`.\n\n        :rtype: `dict` with channel names as keys and units as values\n        \"\"\"\n        unit_strings = [\n            unit_str.strip() for unit_str in self.query(\"getOutput.units?\").split(\",\")\n        ]\n        return {\n            chan_name: self._UNIT_NAMES[unit_str]\n            for chan_name, unit_str in zip(self._channel_names(), unit_strings)\n        }\n\n    def errcheck(self):\n        \"\"\"\n        Performs an error check query against the CTC100. This function does\n        not return anything, but will raise an `IOError` if the error code\n        received by the instrument is not zero.\n\n        :return: Nothing\n        \"\"\"\n        errs = super().query(\"geterror?\").strip()\n        err_code, err_descript = errs.split(\",\")\n        err_code = int(err_code)\n        if err_code == 0:\n            return err_code\n        else:\n            raise OSError(err_descript.strip())\n\n    @contextmanager\n    def _error_checking_disabled(self):\n        old = self._do_errcheck\n        self._do_errcheck = False\n        yield\n        self._do_errcheck = old\n\n    # PROPERTIES ##\n    @property\n    def channel(self):\n        \"\"\"\n        Gets a specific measurement channel on the SRS CTC100. This is accessed\n        like one would access a `dict`. Here you must use the actual channel\n        names to address a specific channel. This is different from most\n        other instruments in InstrumentKit because the CRC100 channel names\n        can change by the user.\n\n        The list of current valid channel names can be accessed by the\n        `SRSCTC100._channel_names()` function.\n\n        :type: `SRSCTC100.Channel`\n        \"\"\"\n        # Note that since the names can change, we need to query channel names\n        # each time. This is inefficient, but alas.\n        return ProxyList(self, self.Channel, self._channel_names())\n\n    @property\n    def display_figures(self):\n        \"\"\"\n        Gets/sets the number of significant figures to display. Valid range\n        is 0-6 inclusive.\n\n        :type: `int`\n        \"\"\"\n        return int(self.query(\"system.display.figures?\"))\n\n    @display_figures.setter\n    def display_figures(self, newval):\n        if newval not in range(7):\n            raise ValueError(\n                \"Number of display figures must be an integer \"\n                \"from 0 to 6, inclusive.\"\n            )\n        self.sendcmd(f\"system.display.figures = {newval}\")\n\n    @property\n    def error_check_toggle(self):\n        \"\"\"\n        Gets/sets if errors should be checked for after every command.\n\n        :bool:\n        \"\"\"\n        return self._do_errcheck\n\n    @error_check_toggle.setter\n    def error_check_toggle(self, newval):\n        if not isinstance(newval, bool):\n            raise TypeError\n        self._do_errcheck = newval\n\n    # OVERRIDEN METHODS #\n\n    # We override sendcmd() and query() to do error checking after each\n    # command.\n    def sendcmd(self, cmd):\n        super().sendcmd(cmd)\n        if self._do_errcheck:\n            self.errcheck()\n\n    def query(self, cmd, size=-1):\n        resp = super().query(cmd, size)\n        if self._do_errcheck:\n            self.errcheck()\n        return resp\n\n    # LOGGING COMMANDS #\n\n    def clear_log(self):\n        \"\"\"\n        Clears the SRS CTC100 log\n\n        Not sure if this works.\n        \"\"\"\n        self.sendcmd(\"System.Log.Clear yes\")\n"
  },
  {
    "path": "src/instruments/srs/srsdg645.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the SRS DG645 digital delay generator.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom enum import IntEnum\n\nfrom instruments.abstract_instruments.comm import GPIBCommunicator\nfrom instruments.generic_scpi import SCPIInstrument\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import assume_units, ProxyList\n\n# CLASSES #####################################################################\n\n\nclass SRSDG645(SCPIInstrument):\n    \"\"\"\n    Communicates with a Stanford Research Systems DG645 digital delay generator,\n    using the SCPI commands documented in the `user's guide`_.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> import instruments.units as u\n    >>> srs = ik.srs.SRSDG645.open_gpibusb('/dev/ttyUSB0', 1)\n    >>> srs.channel[\"B\"].delay = (srs.channel[\"A\"], u.Quantity(10, 'ns'))\n    >>> srs.output[\"AB\"].level_amplitude = u.Quantity(4.0, \"V\")\n\n    .. _user's guide: http://www.thinksrs.com/downloads/PDFs/Manuals/DG645m.pdf\n    \"\"\"\n\n    class Channel:\n        \"\"\"\n        Class representing a sensor attached to the SRS DG644.\n\n        .. warning:: This class should NOT be manually created by the user. It is\n            designed to be initialized by the `SRSDG644` class.\n        \"\"\"\n\n        def __init__(self, parent, chan):\n            if not isinstance(parent, SRSDG645):\n                raise TypeError(\"Don't do that.\")\n\n            if isinstance(chan, parent.Channels):\n                self._chan = chan.value\n            else:\n                self._chan = chan\n\n            self._ddg = parent\n\n        # PROPERTIES #\n\n        @property\n        def idx(self):\n            \"\"\"\n            Gets the channel identifier number as used for communication\n\n            :return: The communication identification number for the specified\n                channel\n            :rtype: `int`\n            \"\"\"\n            return self._chan\n\n        @property\n        def delay(self):\n            \"\"\"\n            Gets/sets the delay of this channel.\n            Formatted as a two-tuple of the reference and the delay time.\n            For example, ``(SRSDG644.Channels.A, u.Quantity(10, \"ps\"))``\n            indicates a delay of 9 picoseconds from delay channel A.\n\n            :units: Assume seconds if no units given.\n            \"\"\"\n            resp = self._ddg.query(f\"DLAY?{int(self._chan)}\").split(\",\")\n            return self._ddg.Channels(int(resp[0])), u.Quantity(float(resp[1]), \"s\")\n\n        @delay.setter\n        def delay(self, newval):\n            newval = (newval[0], assume_units(newval[1], u.s))\n            self._ddg.sendcmd(\n                \"DLAY {},{},{}\".format(\n                    int(self._chan), int(newval[0].idx), newval[1].to(\"s\").magnitude\n                )\n            )\n\n    def __init__(self, filelike):\n        super().__init__(filelike)\n\n        # This instrument requires stripping two characters.\n        if isinstance(filelike, GPIBCommunicator):\n            filelike.strip = 2\n\n    # ENUMS #\n\n    class LevelPolarity(IntEnum):\n        \"\"\"\n        Polarities for output levels.\n        \"\"\"\n\n        positive = 1\n        negative = 0\n\n    class Outputs(IntEnum):\n        \"\"\"\n        Enumeration of valid outputs from the DDG.\n        \"\"\"\n\n        T0 = 0\n        AB = 1\n        CD = 2\n        EF = 3\n        GH = 4\n\n    class Channels(IntEnum):\n        \"\"\"\n        Enumeration of valid delay channels for the DDG.\n        \"\"\"\n\n        T0 = 0\n        T1 = 1\n        A = 2\n        B = 3\n        C = 4\n        D = 5\n        E = 6\n        F = 7\n        G = 8\n        H = 9\n\n    class DisplayMode(IntEnum):\n        \"\"\"\n        Enumeration of possible modes for the physical front-panel display.\n        \"\"\"\n\n        trigger_rate = 0\n        trigger_threshold = 1\n        trigger_single_shot = 2\n        trigger_line = 3\n        adv_triggering_enable = 4\n        trigger_holdoff = 5\n        prescale_config = 6\n        burst_mode = 7\n        burst_delay = 8\n        burst_count = 9\n        burst_period = 10\n        channel_delay = 11\n        channel_levels = 12\n        channel_polarity = 13\n        burst_T0_config = 14\n\n    class TriggerSource(IntEnum):\n        \"\"\"\n        Enumeration of the different allowed trigger sources and modes.\n        \"\"\"\n\n        internal = 0\n        external_rising = 1\n        external_falling = 2\n        ss_external_rising = 3\n        ss_external_falling = 4\n        single_shot = 5\n        line = 6\n\n    # INNER CLASSES #\n\n    class Output:\n        \"\"\"\n        An output from the DDG.\n        \"\"\"\n\n        def __init__(self, parent, idx):\n            self._parent = parent\n            self._idx = int(idx)\n\n        @property\n        def polarity(self):\n            \"\"\"\n            Polarity of this output.\n\n            :type: :class:`SRSDG645.LevelPolarity`\n            \"\"\"\n            return self._parent.LevelPolarity(\n                int(self._parent.query(f\"LPOL? {self._idx}\"))\n            )\n\n        @polarity.setter\n        def polarity(self, newval):\n            if not isinstance(newval, self._parent.LevelPolarity):\n                raise TypeError(\n                    \"Mode must be specified as a \"\n                    \"SRSDG645.LevelPolarity value, got {} \"\n                    \"instead.\".format(type(newval))\n                )\n            self._parent.sendcmd(f\"LPOL {self._idx},{int(newval.value)}\")\n\n        @property\n        def level_amplitude(self):\n            \"\"\"\n            Amplitude (in voltage) of the output level for this output.\n\n            :type: `float` or :class:`~pint.Quantity`\n            :units: As specified, or :math:`\\\\text{V}` by default.\n            \"\"\"\n            return u.Quantity(float(self._parent.query(f\"LAMP? {self._idx}\")), \"V\")\n\n        @level_amplitude.setter\n        def level_amplitude(self, newval):\n            newval = assume_units(newval, \"V\").magnitude\n            self._parent.sendcmd(f\"LAMP {self._idx},{newval}\")\n\n        @property\n        def level_offset(self):\n            \"\"\"\n            Amplitude offset (in voltage) of the output level for this output.\n\n            :type: `float` or :class:`~pint.Quantity`\n            :units: As specified, or :math:`\\\\text{V}` by default.\n            \"\"\"\n            return u.Quantity(float(self._parent.query(f\"LOFF? {self._idx}\")), \"V\")\n\n        @level_offset.setter\n        def level_offset(self, newval):\n            newval = assume_units(newval, \"V\").magnitude\n            self._parent.sendcmd(f\"LOFF {self._idx},{newval}\")\n\n    # PROPERTIES #\n\n    @property\n    def channel(self):\n        \"\"\"\n        Gets a specific channel object.\n\n        The desired channel is accessed by passing an EnumValue from\n        `SRSDG645.Channels`. For example, to access channel A:\n\n        >>> import instruments as ik\n        >>> inst = ik.srs.SRSDG645.open_gpibusb('/dev/ttyUSB0', 1)\n        >>> inst.channel[inst.Channels.A]\n\n        See the example in `SRSDG645` for a more complete example.\n\n        :rtype: `SRSDG645.Channel`\n        \"\"\"\n        return ProxyList(self, self.Channel, SRSDG645.Channels)\n\n    @property\n    def output(self):\n        \"\"\"\n        Gets the specified output port.\n\n        :type: :class:`SRSDG645.Output`\n        \"\"\"\n        return ProxyList(self, self.Output, self.Outputs)\n\n    @property\n    def display(self):\n        \"\"\"\n        Gets/sets the front-panel display mode for the connected DDG.\n        The mode is a tuple of the display mode and the channel.\n\n        :type: `tuple` of an `SRSDG645.DisplayMode` and an `SRSDG645.Channels`\n        \"\"\"\n        disp_mode, chan = map(int, self.query(\"DISP?\").split(\",\"))\n        return SRSDG645.DisplayMode(disp_mode), SRSDG645.Channels(chan)\n\n    @display.setter\n    def display(self, newval):\n        # TODO: check types here.\n        self.sendcmd(\"DISP {},{}\".format(*map(int, newval)))\n\n    @property\n    def enable_adv_triggering(self):\n        \"\"\"\n        Gets/sets whether advanced triggering is enabled.\n\n        :type: `bool`\n        \"\"\"\n        return bool(int(self.query(\"ADVT?\")))\n\n    @enable_adv_triggering.setter\n    def enable_adv_triggering(self, newval):\n        self.sendcmd(f\"ADVT {1 if newval else 0}\")\n\n    @property\n    def trigger_rate(self):\n        \"\"\"\n        Gets/sets the rate of the internal trigger.\n\n        :type: `~pint.Quantity` or `float`\n        :units: As passed or Hz if not specified.\n        \"\"\"\n        return u.Quantity(float(self.query(\"TRAT?\")), u.Hz)\n\n    @trigger_rate.setter\n    def trigger_rate(self, newval):\n        newval = assume_units(newval, u.Hz)\n        self.sendcmd(f\"TRAT {newval.to(u.Hz).magnitude}\")\n\n    @property\n    def trigger_source(self):\n        \"\"\"\n        Gets/sets the source for the trigger.\n\n        :type: :class:`SRSDG645.TriggerSource`\n        \"\"\"\n        return SRSDG645.TriggerSource(int(self.query(\"TSRC?\")))\n\n    @trigger_source.setter\n    def trigger_source(self, newval):\n        self.sendcmd(f\"TSRC {int(newval)}\")\n\n    @property\n    def holdoff(self):\n        \"\"\"\n        Gets/sets the trigger holdoff time.\n\n        :type: `~pint.Quantity` or `float`\n        :units: As passed, or s if not specified.\n        \"\"\"\n        return u.Quantity(float(self.query(\"HOLD?\")), u.s)\n\n    @holdoff.setter\n    def holdoff(self, newval):\n        newval = assume_units(newval, u.s)\n        self.sendcmd(f\"HOLD {newval.to(u.s).magnitude}\")\n\n    @property\n    def enable_burst_mode(self):\n        \"\"\"\n        Gets/sets whether burst mode is enabled.\n\n        :type: `bool`\n        \"\"\"\n        return bool(int(self.query(\"BURM?\")))\n\n    @enable_burst_mode.setter\n    def enable_burst_mode(self, newval):\n        self.sendcmd(f\"BURM {1 if newval else 0}\")\n\n    @property\n    def enable_burst_t0_first(self):\n        \"\"\"\n        Gets/sets whether T0 output in burst mode is on first. If\n        enabled, the T0 output is enabled for first delay cycle of the\n        burst only. If disabled, the T0 output is enabled for all delay\n        cycles of the burst.\n\n        :type: `bool`\n        \"\"\"\n        return bool(int(self.query(\"BURT?\")))\n\n    @enable_burst_t0_first.setter\n    def enable_burst_t0_first(self, newval):\n        self.sendcmd(f\"BURT {1 if newval else 0}\")\n\n    @property\n    def burst_count(self):\n        \"\"\"\n        Gets/sets the burst count. When burst mode is enabled, the\n        DG645 outputs burst count delay cycles per trigger.\n        Valid numbers for burst count are between 1 and 2**32 - 1\n        \"\"\"\n        return int(self.query(\"BURC?\"))\n\n    @burst_count.setter\n    def burst_count(self, newval):\n        self.sendcmd(f\"BURC {int(newval)}\")\n\n    @property\n    def burst_period(self):\n        \"\"\"\n        Gets/sets the burst period. The burst period sets the time\n        between delay cycles during a burst. The burst period may\n        range from 100 ns to 2000 – 10 ns in 10 ns steps.\n\n        :units: Assume seconds if no units given.\n        \"\"\"\n        return u.Quantity(float(self.query(\"BURP?\")), u.s)\n\n    @burst_period.setter\n    def burst_period(self, newval):\n        newval = assume_units(newval, u.sec)\n        self.sendcmd(f\"BURP {newval.to(u.sec).magnitude}\")\n\n    @property\n    def burst_delay(self):\n        \"\"\"\n        Gets/sets the burst delay. When burst mode is enabled the DG645\n        delays the first burst pulse relative to the trigger by the\n        burst delay. The burst delay may range from 0 ps to < 2000 s\n        with a resolution of 5 ps.\n\n        :units: Assume seconds if no units given.\n        \"\"\"\n        return u.Quantity(float(self.query(\"BURD?\")), u.s)\n\n    @burst_delay.setter\n    def burst_delay(self, newval):\n        newval = assume_units(newval, u.s)\n        self.sendcmd(f\"BURD {newval.to(u.sec).magnitude}\")\n"
  },
  {
    "path": "src/instruments/sunpower/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing Sunpower instruments\n\"\"\"\n\nfrom .cryotel_gt import CryoTelGT\n"
  },
  {
    "path": "src/instruments/sunpower/cryotel_gt.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nDriver for the Sunpower CryoTel GT generation 2 cryocooler.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom collections import OrderedDict\nfrom enum import Enum\nimport warnings\n\nfrom instruments.abstract_instruments import Instrument\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import assume_units\n\n# CLASSES #####################################################################\n\n\nclass CryoTelGT(Instrument):\n    \"\"\"\n    The Sunpower CyroTel GT is a cryocooler.\n\n    This driver is for the GT generation 2. According to the Sunpower website,\n    this means for cryocoolers purchased after May 2012.\n\n    Caution: Do not use this driver to established a closed loop control of the\n    cryocooler, as this may cause malfunction and potentially damage to the\n    device (see the manual for details). You can use this driver however to adjust\n    the setpoint temperature and read the current temperature.\n\n    For communications, the default baudrate is 4800, 8 data bits, 1 stop bit,\n    and no flow control.\n\n    Example usage:\n        >>> import instruments as ik\n        >>> inst = ik.sunpower.CryoTelGT.open_serial(\"/dev/ttyACM0\", 4800)\n        >>> inst.temperature\n        82.0 Kelvin\n        >>> inst.temperature_setpoint\n        77.0 Kelvin\n    \"\"\"\n\n    class ControlMode(Enum):\n        \"\"\"\n        Control modes for the Cryocooler.\n        \"\"\"\n\n        POWER = 0\n        TEMPERATURE = 2\n\n    class ThermostatStatus(Enum):\n        \"\"\"\n        Thermostat status for the CryoTel GT.\n\n        Off means that the thermostat is open and the cryocooler is shutting down or\n        shut down.\n        \"\"\"\n\n        OFF = 0\n        ON = 1\n\n    class StopMode(Enum):\n        \"\"\"\n        Stop mode for the cryocooler. `HOST` means that the start/stop command can\n        be controlled from the host computer. `DIGIO` means that the start/stop command\n        can be set from the digital input/output pin 1 on the cryocooler.\n        \"\"\"\n\n        HOST = 0\n        DIGIO = 1\n\n    def __init__(self, filelike):\n        super().__init__(filelike)\n\n        self._error_codes = OrderedDict(\n            {\n                1: \"Over Current\",\n                2: \"Jumper Error\",\n                4: \"Serial Error\",\n                8: \"Non-volatile Memory Error\",\n                16: \"Watchdog Error\",\n                32: \"Temperature Sensor Error\",\n            }\n        )\n\n        self.terminator = \"\\r\"\n\n    @property\n    def at_temperature_band(self):\n        \"\"\"\n        Get/set the temperature band of the CryoTel GT in Kelvin.\n\n        Returns the temperature band within the green LED and \"At Temperature\" pin on\n        the I/O connector will be activated.\n\n        If no unit is provided, Kelvin are assumed.\n\n        :return: The current temperature band in Kelvin.\n        \"\"\"\n        ret_val = self.query(\"SET TBAND\")\n        return float(ret_val) * u.K\n\n    @at_temperature_band.setter\n    def at_temperature_band(self, value):\n        value = assume_units(value, u.K).to(u.K)\n        self.query(\"SET TBAND\", float(value.magnitude))\n\n    @property\n    def control_mode(self):\n        \"\"\"\n        Get/set the control mode of the CryoTel GT.\n\n        Valid options are `ControlMode.POWER` and `ControlMode.TEMPERATURE`.\n\n        .. note:: The set control mode will be reset after a power cycle unless you also\n            call the `save_control_mode()` method.\n\n        :return: The current control mode.\n        \"\"\"\n        ret_val = int(float(self.query(\"SET PID\")))\n        return self.ControlMode(ret_val)\n\n    @control_mode.setter\n    def control_mode(self, value):\n        if not isinstance(value, self.ControlMode):\n            raise ValueError(\n                \"Invalid control mode. Use ControlMode.POWER or ControlMode.TEMPERATURE.\"\n            )\n        self.query(\"SET PID\", value.value)\n\n    @property\n    def errors(self):\n        \"\"\"Get any error codes from the CryoTel GT.\n\n        Only error codes that are currently active will be added to the\n        list. If no error codes are active, an empty list is returned.\n\n        :return: List of human readable strings.\n        \"\"\"\n        ret_val = int(self.query(\"ERROR\"), 2)\n        errors = []\n        for errcode, errstr in self._error_codes.items():\n            if ret_val & errcode:\n                errors.append(errstr)\n        return errors\n\n    @property\n    def ki(self):\n        \"\"\"Set/get the integral constant of the temperature control loop.\n\n        The default integral constant is 1.0 and will be reset to this value if the\n        reset method is called.\n\n        :return: The current integral constant.\n        :rtype: float\n        \"\"\"\n        ret_val = self.query(\"SET KI\")\n        return float(ret_val)\n\n    @ki.setter\n    def ki(self, value):\n        _ = self.query(\"SET KI\", float(value))\n\n    @property\n    def kp(self):\n        \"\"\"Set/get the proportional constant of the temperature control loop.\n\n        The default proportional constant is 50.0 and will be reset to this value if the\n        reset method is called.\n\n        :return: The current proportional constant.\n        :rtype: float\n        \"\"\"\n        ret_val = self.query(\"SET KP\")\n        return float(ret_val)\n\n    @kp.setter\n    def kp(self, value):\n        _ = self.query(\"SET KP\", float(value))\n\n    @property\n    def power(self):\n        \"\"\"\n        Get the current power in Watts.\n\n        :return: The current power in Watts.\n        \"\"\"\n        ret_val = self.query(\"P\")\n        return float(ret_val) * u.W\n\n    @property\n    def power_current_and_limits(self):\n        \"\"\"\n        Get the current power and power limits in Watts.\n\n        :return: Three u.Quantity objects representing the maximum allowable power at the\n            current temperature, the minimum allowable power at the current temperature,\n            and the current power.\n        \"\"\"\n        ret_vals = self.query_multiline(\"E\", 3)\n        max_power = float(ret_vals[0]) * u.W\n        min_power = float(ret_vals[1]) * u.W\n        current_power = float(ret_vals[2]) * u.W\n        return max_power, min_power, current_power\n\n    @property\n    def power_max(self):\n        \"\"\"\n        Get/set the maximum user defined power in Watts.\n\n        The cooler will automatically limit the power to a safe value if this number\n        exceeds the maximum allowable power.\n\n        :return: The maximum user defined power in Watts.\n        \"\"\"\n        ret_val = self.query(\"SET MAX\")\n        return float(ret_val) * u.W\n\n    @power_max.setter\n    def power_max(self, value):\n        value = assume_units(value, u.W).to(u.W)\n        if value.magnitude < 0 or value.magnitude > 999.99:\n            raise ValueError(\"Maximum power must be between 0 and 999.99 Watts.\")\n        self.query(\"SET MAX\", float(value.magnitude))\n\n    @property\n    def power_min(self):\n        \"\"\"\n        Get/set the minimum user defined power in Watts.\n\n        The cooler will automatically limit the power to a safe value if this number\n        exceeds the minimum allowable power.\n\n        :return: The minimum user defined power in Watts.\n        \"\"\"\n        ret_val = self.query(\"SET MIN\")\n        return float(ret_val) * u.W\n\n    @power_min.setter\n    def power_min(self, value):\n        value = assume_units(value, u.W).to(u.W)\n        if value.magnitude < 0 or value.magnitude > 999.99:\n            raise ValueError(\"Minimum power must be between 0 and 999.99 Watts.\")\n        self.query(\"SET MIN\", float(value.magnitude))\n\n    @property\n    def power_setpoint(self):\n        \"\"\"\n        Get/set the setpoint power in Watts.\n\n        This setpoint is used when the control mode is set to `ControlMode.POWER`.\n        Setting the power is unitful. If no unit is given, it is assumed\n        to be in Watts.\n\n        While any number from 0 to 999.99 can be set, the controller will only command\n        a power that will not damage the cryocooler.\n\n        :return: The setpoint power in Watts.\n\n        :raises ValueError: If the power is set to a value outside the allowed range.\n        \"\"\"\n        ret_val = self.query(\"SET PWOUT\")\n        return float(ret_val) * u.W\n\n    @power_setpoint.setter\n    def power_setpoint(self, value):\n        value = assume_units(value, u.W).to(u.W)\n        if value.magnitude < 0 or value.magnitude > 999.99:\n            raise ValueError(\"Power setpoint must be between 0 and 999.99 Watts.\")\n        self.query(\"SET PWOUT\", float(value.magnitude))\n\n    @property\n    def serial_number(self):\n        \"\"\"\n        Get the serial number and revision of the CryoTel GT.\n\n        :return: List of serial number string and revision string.\n        \"\"\"\n        return self.query_multiline(\"SERIAL\", 2)\n\n    @property\n    def state(self):\n        \"\"\"\n        Get a list of most of the control parameters and their values.\n\n        Note: This is the direct list from the CryoTel GT controller. See the manual for\n        the meaning of the parameters.\n\n        :return: A list of strings representing the control parameters and their values.\n        \"\"\"\n        return self.query_multiline(\"STATE\", 14)\n\n    @property\n    def temperature(self):\n        \"\"\"\n        Get the current temperature in Kelvin.\n\n        :return: The current temperature in Kelvin.\n        \"\"\"\n        ret_val = self.query(\"TC\")\n        return float(ret_val) * u.K\n\n    @property\n    def temperature_setpoint(self):\n        \"\"\"\n        Get/set the setpoint temperature in Kelvin.\n\n        This setpoint is used when the control mode is set to `ControlMode.TEMPERATURE`.\n        Setting the temperature is unitful. If no unit is given, it is assumed\n        to be in Kelvin.\n\n        :return: The setpoint temperature in Kelvin.\n        \"\"\"\n        ret_val = self.query(\"SET TTARGET\")\n        return float(ret_val) * u.K\n\n    @temperature_setpoint.setter\n    def temperature_setpoint(self, value):\n        value = assume_units(value, u.K).to(u.K)\n        self.query(\"SET TTARGET\", float(value.magnitude))\n\n    @property\n    def thermostat(self):\n        \"\"\"Get/set the thermostat mode of the CryoTel GT.\n\n        Set this to `True` to enable the thermostat mode, or `False` to disable it.\n\n        :return: The current thermostat mode state.\n        :rtype: bool\n        \"\"\"\n        ret_val = int(float(self.query(\"SET TSTATM\")))\n        return bool(ret_val)\n\n    @thermostat.setter\n    def thermostat(self, value):\n        if not isinstance(value, bool):\n            raise ValueError(\"Invalid thermostat mode. Use True or False.\")\n        self.query(\"SET TSTATM\", int(value))\n\n    @property\n    def thermostat_status(self):\n        \"\"\"\n        Get the current thermostat status of the CryoTel GT.\n\n        Returns `ThermostatStatus.ON` if the thermostat is enabled, and `ThermostatStatus.OFF`\n        if it is disabled.\n\n        :return: The current thermostat status.\n        :rtype: ThermostatStatus\n        \"\"\"\n        ret_val = int(float(self.query(\"TSTAT\")))\n        return self.ThermostatStatus(ret_val)\n\n    @property\n    def stop(self):\n        \"\"\"\n        Get/set the stop state of the CryoTel GT.\n\n        Valid options are `True` (stop) and `False` (start).\n\n        :return: The current stop state.\n        \"\"\"\n        ret_val = int(float(self.query(\"SET SSTOP\")))\n        return bool(ret_val)\n\n    @stop.setter\n    def stop(self, value):\n        if not isinstance(value, bool):\n            raise ValueError(\"Invalid stop state. Use True or False.\")\n        self.query(\"SET SSTOP\", int(value))\n\n    @property\n    def stop_mode(self):\n        \"\"\"\n        Get/set the stop mode of the CryoTel GT.\n\n        Valid options are `StopMode.HOST` and `StopMode.DIGIO`.\n\n        :return: The current stop mode.\n        \"\"\"\n        ret_val = int(float(self.query(\"SET SSTOPM\")))\n        return self.StopMode(ret_val)\n\n    @stop_mode.setter\n    def stop_mode(self, value):\n        if not isinstance(value, self.StopMode):\n            raise ValueError(\"Invalid stop mode. Use StopMode.HOST or StopMode.DIGIO.\")\n        self.query(\"SET SSTOPM\", value.value)\n\n    # CryoCooler Methods\n\n    def reset(self):\n        \"\"\"\n        Reset the CryoTel GT to factory defaults.\n        \"\"\"\n        _ = self.query_multiline(\"RESET=F\", 2)\n\n    def save_control_mode(self):\n        \"\"\"\n        Save the current control mode as the default control mode.\n        \"\"\"\n        _ = self.query(\"SAVE PID\")\n\n    # Driver methods\n\n    def query(self, command, value=None):\n        \"\"\"\n        Send a query to the cooler and return the response if no value is given.\n\n        When setting a variable, the CryoTel GT will generally return the value\n        that was set. This is checked for accuracy and a warning is raised if the\n        return value is not the same as the set value.\n\n        For an actual query where we expect a result, the result is returned unchanged.\n\n        :param command: The command to send to the cooler.\n        :param value: The value to be set. If not given or None, it is assumed that\n            you want to query the cryocooler.\n\n        :return: The response from the cooler or None.\n        \"\"\"\n        if value is None:\n            self.sendcmd(command)\n            return self.read().strip()\n        else:\n            if isinstance(value, float):\n                value_to_send = f\"{value:.2f}\"\n            else:\n                value_to_send = str(value)\n            self.sendcmd(f\"{command}={value_to_send}\")\n            ret_val = self.read().strip()\n            if float(ret_val) != value:\n                warnings.warn(\n                    f\"Set value {value} does not match returned value {ret_val}.\"\n                )\n\n    def query_multiline(self, command, num_lines):\n        \"\"\"\n        Send a query to the cooler and return the response.\n\n        This is used for commands that return multiple lines of data.\n\n        :param command: The command to send to the cooler.\n        :param num_lines: The number of lines to read from the cooler.\n\n        :return: The response from the cooler as a list of lines.\n        \"\"\"\n        self.sendcmd(command)\n        ret_val = [self.read().strip() for _ in range(num_lines)]\n        return ret_val\n\n    def sendcmd(self, command):\n        \"\"\"\n        Send a command to the cooler.\n\n        :param command: The command to send to the cooler.\n        \"\"\"\n        self._file.sendcmd(command)\n        _ = self.read()  # echo\n"
  },
  {
    "path": "src/instruments/tektronix/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing Tektronix instruments\n\"\"\"\n\nfrom .tekdpo4104 import TekDPO4104\nfrom .tekdpo70000 import TekDPO70000\nfrom .tekawg2000 import TekAWG2000\nfrom .tektds224 import TekTDS224\nfrom .tektds5xx import TekTDS5xx\n"
  },
  {
    "path": "src/instruments/tektronix/tekawg2000.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the Tektronix AWG2000 series arbitrary wave generators.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom enum import Enum\n\nfrom instruments.generic_scpi import SCPIInstrument\nfrom instruments.optional_dep_finder import numpy\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import assume_units, ProxyList\n\n# CLASSES #####################################################################\n\n\nclass TekAWG2000(SCPIInstrument):\n    \"\"\"\n    Communicates with a Tektronix AWG2000 series instrument using the SCPI\n    commands documented in the user's guide.\n    \"\"\"\n\n    # INNER CLASSES #\n\n    class Channel:\n        \"\"\"\n        Class representing a physical channel on the Tektronix AWG 2000\n\n        .. warning:: This class should NOT be manually created by the user. It\n            is designed to be initialized by the `TekAWG2000` class.\n        \"\"\"\n\n        def __init__(self, tek, idx):\n            self._tek = tek\n            # Zero-based for pythonic convienence, so we need to convert to\n            # Tektronix's one-based notation here.\n            self._name = f\"CH{idx + 1}\"\n\n            # Remember what the old data source was for use as a context manager\n            self._old_dsrc = None\n\n        # PROPERTIES #\n\n        @property\n        def name(self):\n            \"\"\"\n            Gets the name of this AWG channel\n\n            :type: `str`\n            \"\"\"\n            return self._name\n\n        @property\n        def amplitude(self):\n            \"\"\"\n            Gets/sets the amplitude of the specified channel.\n\n            :units: As specified (if a `~pint.Quantity`) or assumed to be\n                of units Volts.\n            :type: `~pint.Quantity` with units Volts peak-to-peak.\n            \"\"\"\n            return u.Quantity(\n                float(self._tek.query(f\"FG:{self._name}:AMPL?\").strip()), u.V\n            )\n\n        @amplitude.setter\n        def amplitude(self, newval):\n            self._tek.sendcmd(\n                \"FG:{}:AMPL {}\".format(\n                    self._name, assume_units(newval, u.V).to(u.V).magnitude\n                )\n            )\n\n        @property\n        def offset(self):\n            \"\"\"\n            Gets/sets the offset of the specified channel.\n\n            :units: As specified (if a `~pint.Quantity`) or assumed to be\n                of units Volts.\n            :type: `~pint.Quantity` with units Volts.\n            \"\"\"\n            return u.Quantity(\n                float(self._tek.query(f\"FG:{self._name}:OFFS?\").strip()), u.V\n            )\n\n        @offset.setter\n        def offset(self, newval):\n            self._tek.sendcmd(\n                \"FG:{}:OFFS {}\".format(\n                    self._name, assume_units(newval, u.V).to(u.V).magnitude\n                )\n            )\n\n        @property\n        def frequency(self):\n            \"\"\"\n            Gets/sets the frequency of the specified channel when using the built-in\n            function generator.\n\n            :units: As specified (if a `~pint.Quantity`) or assumed to be\n                of units Hertz.\n            :type: `~pint.Quantity` with units Hertz.\n            \"\"\"\n            return u.Quantity(float(self._tek.query(\"FG:FREQ?\").strip()), u.Hz)\n\n        @frequency.setter\n        def frequency(self, newval):\n            self._tek.sendcmd(\n                f\"FG:FREQ {assume_units(newval, u.Hz).to(u.Hz).magnitude}HZ\"\n            )\n\n        @property\n        def polarity(self):\n            \"\"\"\n            Gets/sets the polarity of the specified channel.\n\n            :type: `TekAWG2000.Polarity`\n            \"\"\"\n            return TekAWG2000.Polarity(self._tek.query(f\"FG:{self._name}:POL?\").strip())\n\n        @polarity.setter\n        def polarity(self, newval):\n            if not isinstance(newval, TekAWG2000.Polarity):\n                raise TypeError(\n                    \"Polarity settings must be a \"\n                    \"`TekAWG2000.Polarity` value, got {} \"\n                    \"instead.\".format(type(newval))\n                )\n\n            self._tek.sendcmd(f\"FG:{self._name}:POL {newval.value}\")\n\n        @property\n        def shape(self):\n            \"\"\"\n            Gets/sets the waveform shape of the specified channel. The AWG will\n            use the internal function generator for these shapes.\n\n            :type: `TekAWG2000.Shape`\n            \"\"\"\n            return TekAWG2000.Shape(\n                self._tek.query(f\"FG:{self._name}:SHAP?\").strip().split(\",\")[0]\n            )\n\n        @shape.setter\n        def shape(self, newval):\n            if not isinstance(newval, TekAWG2000.Shape):\n                raise TypeError(\n                    \"Shape settings must be a `TekAWG2000.Shape` \"\n                    \"value, got {} instead.\".format(type(newval))\n                )\n            self._tek.sendcmd(f\"FG:{self._name}:SHAP {newval.value}\")\n\n    # ENUMS #\n\n    class Polarity(Enum):\n        \"\"\"\n        Enum containing valid polarity modes for the AWG2000\n        \"\"\"\n\n        normal = \"NORMAL\"\n        inverted = \"INVERTED\"\n\n    class Shape(Enum):\n        \"\"\"\n        Enum containing valid waveform shape modes for hte AWG2000\n        \"\"\"\n\n        sine = \"SINUSOID\"\n        pulse = \"PULSE\"\n        ramp = \"RAMP\"\n        square = \"SQUARE\"\n        triangle = \"TRIANGLE\"\n\n    # Properties #\n\n    @property\n    def waveform_name(self):\n        \"\"\"\n        Gets/sets the destination waveform name for upload.\n\n        This is the file name that will be used on the AWG for any following\n        waveform data that is uploaded.\n\n        :type: `str`\n        \"\"\"\n        return self.query(\"DATA:DEST?\").strip()\n\n    @waveform_name.setter\n    def waveform_name(self, newval):\n        if not isinstance(newval, str):\n            raise TypeError(\"Waveform name must be specified as a string.\")\n        self.sendcmd(f'DATA:DEST \"{newval}\"')\n\n    @property\n    def channel(self):\n        \"\"\"\n        Gets a specific channel on the AWG2000. The desired channel is accessed\n        like one would access a list.\n\n        Example usage:\n\n        >>> import instruments as ik\n        >>> inst = ik.tektronix.TekAWG2000.open_gpibusb(\"/dev/ttyUSB0\", 1)\n        >>> print(inst.channel[0].frequency)\n\n        :return: A channel object for the AWG2000\n        :rtype: `TekAWG2000.Channel`\n        \"\"\"\n        return ProxyList(self, self.Channel, range(2))\n\n    # METHODS #\n\n    def upload_waveform(self, yzero, ymult, xincr, waveform):\n        \"\"\"\n        Uploads a waveform from the PC to the instrument.\n\n        :param yzero: Y-axis origin offset\n        :type yzero: `float` or `int`\n\n        :param ymult: Y-axis data point multiplier\n        :type ymult: `float` or `int`\n\n        :param xincr: X-axis data point increment\n        :type xincr: `float` or `int`\n\n        :param `numpy.ndarray` waveform: Numpy array of values representing the\n            waveform to be uploaded. This array should be normalized. This means\n            that all absolute values contained within the array should not\n            exceed 1.\n        \"\"\"\n        if numpy is None:\n            raise ImportError(\n                \"Missing optional dependency numpy, which is required\"\n                \"for uploading waveforms.\"\n            )\n\n        if not isinstance(yzero, float) and not isinstance(yzero, int):\n            raise TypeError(\"yzero must be specified as a float or int\")\n\n        if not isinstance(ymult, float) and not isinstance(ymult, int):\n            raise TypeError(\"ymult must be specified as a float or int\")\n\n        if not isinstance(xincr, float) and not isinstance(xincr, int):\n            raise TypeError(\"xincr must be specified as a float or int\")\n\n        if not isinstance(waveform, numpy.ndarray):\n            raise TypeError(\"waveform must be specified as a numpy array\")\n\n        if numpy.max(numpy.abs(waveform)) > 1:\n            raise ValueError(\"The max value for an element in waveform is 1.\")\n\n        self.sendcmd(f\"WFMP:YZERO {yzero}\")\n        self.sendcmd(f\"WFMP:YMULT {ymult}\")\n        self.sendcmd(f\"WFMP:XINCR {xincr}\")\n\n        waveform *= 2**12 - 1\n        waveform = waveform.astype(\"<u2\").tobytes()\n        wfm_header_2 = str(len(waveform))\n        wfm_header_1 = len(wfm_header_2)\n\n        bin_str = f\"#{wfm_header_1}{wfm_header_2}{waveform}\"\n\n        self.sendcmd(f\"CURVE {bin_str}\")\n"
  },
  {
    "path": "src/instruments/tektronix/tekdpo4104.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the Tektronix DPO 4104 oscilloscope\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nfrom time import sleep\nfrom enum import Enum\n\nfrom instruments.abstract_instruments import Oscilloscope\nfrom instruments.optional_dep_finder import numpy\nfrom instruments.generic_scpi import SCPIInstrument\nfrom instruments.util_fns import ProxyList\n\n# FUNCTIONS ###################################################################\n\n\ndef _parent_property(prop_name, doc=\"\"):\n    def getter(self):  # pylint: disable=missing-docstring\n        with self:\n            # pylint: disable=protected-access\n            return getattr(self._tek, prop_name)\n\n    # pylint: disable=missing-docstring,unused-argument\n    def setter(self, newval):\n        with self:\n            # pylint: disable=protected-access\n            setattr(self._tek, prop_name, newval)\n\n    return property(getter, setter, doc=doc)\n\n\n# CLASSES #####################################################################\n\n\nclass TekDPO4104(SCPIInstrument, Oscilloscope):\n    \"\"\"\n    The Tektronix DPO4104 is a multi-channel oscilloscope with analog\n    bandwidths ranging from 100MHz to 1GHz.\n\n    This class inherits from `~instruments.generic_scpi.SCPIInstrument`.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> tek = ik.tektronix.TekDPO4104.open_tcpip(\"192.168.0.2\", 8888)\n    >>> [x, y] = tek.channel[0].read_waveform()\n    \"\"\"\n\n    class DataSource(Oscilloscope.DataSource):\n        \"\"\"\n        Class representing a data source (channel, math, or ref) on the Tektronix\n        DPO 4104.\n\n        .. warning:: This class should NOT be manually created by the user. It is\n            designed to be initialized by the `TekDPO4104` class.\n        \"\"\"\n\n        def __init__(self, tek, name):\n            super().__init__(tek, name)\n            self._tek = self._parent\n\n        @property\n        def name(self):\n            \"\"\"\n            Gets the name of this data source, as identified over SCPI.\n\n            :type: `str`\n            \"\"\"\n            return self._name\n\n        def __enter__(self):\n            self._old_dsrc = self._tek.data_source\n            if self._old_dsrc != self:\n                # Set the new data source, and let __exit__ cleanup.\n                self._tek.data_source = self\n            else:\n                # There\"s nothing to do or undo in this case.\n                self._old_dsrc = None\n\n        def __exit__(self, type, value, traceback):\n            if self._old_dsrc is not None:\n                self._tek.data_source = self._old_dsrc\n\n        def __eq__(self, other):\n            if not isinstance(other, type(self)):\n                return NotImplemented\n\n            return other.name == self.name\n\n        __hash__ = None\n\n        def read_waveform(self, bin_format=True):\n            \"\"\"\n            Read waveform from the oscilloscope.\n            This function is all inclusive. After reading the data from the\n            oscilloscope, it unpacks the data and scales it accordingly.\n            Supports both ASCII and binary waveform transfer.\n\n            Function returns a tuple (x,y), where both x and y are numpy arrays.\n\n            :param bool bin_format: If `True`, data is transfered\n                in a binary format. Otherwise, data is transferred in ASCII.\n            :rtype: `tuple`[`tuple`[`~pint.Quantity`, ...], `tuple`[`~pint.Quantity`, ...]]\n                or if numpy is installed, `tuple` of two `~pint.Quantity` with `numpy.array` data\n            \"\"\"\n\n            # Set the acquisition channel\n            with self:\n                # TODO: move this out somewhere more appropriate.\n                old_dat_stop = self._tek.query(\"DAT:STOP?\")\n                self._tek.sendcmd(f\"DAT:STOP {10 ** 7}\")\n\n                if not bin_format:\n                    # Set data encoding format to ASCII\n                    self._tek.sendcmd(\"DAT:ENC ASCI\")\n                    sleep(0.02)  # Work around issue with 2.48 firmware.\n                    raw = self._tek.query(\"CURVE?\")\n                    raw = raw.split(\",\")  # Break up comma delimited string\n                    if numpy:\n                        raw = numpy.array(raw, dtype=float)  # Convert to numpy array\n                    else:\n                        raw = map(float, raw)\n                else:\n                    # Set encoding to signed, big-endian\n                    self._tek.sendcmd(\"DAT:ENC RIB\")\n                    sleep(0.02)  # Work around issue with 2.48 firmware.\n                    data_width = self._tek.data_width\n                    self._tek.sendcmd(\"CURVE?\")\n                    # Read in the binary block, data width of 2 bytes.\n                    raw = self._tek.binblockread(data_width)\n                    # Read the new line character that is sent\n                    self._tek._file.read_raw(1)  # pylint: disable=protected-access\n\n                yoffs = self._tek.y_offset  # Retrieve Y offset\n                ymult = self._tek.query(\"WFMP:YMU?\")  # Retrieve Y multiplier\n                yzero = self._tek.query(\"WFMP:YZE?\")  # Retrieve Y zero\n\n                xzero = self._tek.query(\"WFMP:XZE?\")  # Retrieve X zero\n                xincr = self._tek.query(\"WFMP:XIN?\")  # Retrieve X incr\n                # Retrieve number of data points\n                ptcnt = self._tek.query(\"WFMP:NR_P?\")\n\n                if numpy:\n                    x = numpy.arange(float(ptcnt)) * float(xincr) + float(xzero)\n                    y = ((raw - yoffs) * float(ymult)) + float(yzero)\n                else:\n                    x = tuple(\n                        float(val) * float(xincr) + float(xzero)\n                        for val in range(int(ptcnt))\n                    )\n                    y = tuple(((x - yoffs) * float(ymult)) + float(yzero) for x in raw)\n\n                self._tek.sendcmd(f\"DAT:STOP {old_dat_stop}\")\n\n                return x, y\n\n        y_offset = _parent_property(\"y_offset\")\n\n    class Channel(DataSource, Oscilloscope.Channel):\n        \"\"\"\n        Class representing a channel on the Tektronix DPO 4104.\n\n        This class inherits from `TekDPO4104.DataSource`.\n\n        .. warning:: This class should NOT be manually created by the user. It is\n            designed to be initialized by the `TekDPO4104` class.\n        \"\"\"\n\n        def __init__(self, parent, idx):\n            super().__init__(parent, f\"CH{idx + 1}\")\n            self._idx = idx + 1\n\n        @property\n        def coupling(self):\n            \"\"\"\n            Gets/sets the coupling setting for this channel.\n\n            :type: `TekDPO4104.Coupling`\n            \"\"\"\n            return TekDPO4104.Coupling(self._tek.query(f\"CH{self._idx}:COUPL?\"))\n\n        @coupling.setter\n        def coupling(self, newval):\n            if not isinstance(newval, TekDPO4104.Coupling):\n                raise TypeError(\n                    \"Coupling setting must be a `TekDPO4104.Coupling`\"\n                    \" value, got {} instead.\".format(type(newval))\n                )\n\n            self._tek.sendcmd(f\"CH{self._idx}:COUPL {newval.value}\")\n\n    # ENUMS #\n\n    class Coupling(Enum):\n        \"\"\"\n        Enum containing valid coupling modes for the channels on the\n        Tektronix DPO 4104\n        \"\"\"\n\n        ac = \"AC\"\n        dc = \"DC\"\n        ground = \"GND\"\n\n    # PROPERTIES #\n\n    @property\n    def channel(self):\n        \"\"\"\n        Gets a specific oscilloscope channel object. The desired channel is\n        specified like one would access a list.\n\n        For instance, this would transfer the waveform from the first channel::\n\n        >>> tek = ik.tektronix.TekDPO4104.open_tcpip(\"192.168.0.2\", 8888)\n        >>> [x, y] = tek.channel[0].read_waveform()\n\n        :rtype: `TekDPO4104.Channel`\n        \"\"\"\n        return ProxyList(self, self.Channel, range(4))\n\n    @property\n    def ref(self):\n        \"\"\"\n        Gets a specific oscilloscope reference channel object. The desired\n        channel is specified like one would access a list.\n\n        For instance, this would transfer the waveform from the first channel::\n\n        >>> import instruments as ik\n        >>> tek = ik.tektronix.TekDPO4104.open_tcpip(\"192.168.0.2\", 8888)\n        >>> [x, y] = tek.ref[0].read_waveform()\n\n        :rtype: `TekDPO4104.DataSource`\n        \"\"\"\n        return ProxyList(\n            self,\n            lambda s, idx: self.DataSource(s, f\"REF{idx + 1}\"),\n            range(4),\n        )\n\n    @property\n    def math(self):\n        \"\"\"\n        Gets a data source object corresponding to the MATH channel.\n\n        :rtype: `TekDPO4104.DataSource`\n        \"\"\"\n        return self.DataSource(self, \"MATH\")\n\n    @property\n    def data_source(self):\n        \"\"\"\n        Gets/sets the the data source for waveform transfer.\n        \"\"\"\n        name = self.query(\"DAT:SOU?\")\n        if name.startswith(\"CH\"):\n            return self.Channel(self, int(name[2:]) - 1)\n\n        return self.DataSource(self, name)\n\n    @data_source.setter\n    def data_source(self, newval):\n        # TODO: clean up type-checking here.\n        if not isinstance(newval, str):\n            if hasattr(newval, \"value\"):  # Is an enum with a value.\n                newval = newval.value\n            elif hasattr(newval, \"name\"):  # Is a datasource with a name.\n                newval = newval.name\n        self.sendcmd(f\"DAT:SOU {newval}\")\n        sleep(0.01)  # Let the instrument catch up.\n\n    @property\n    def aquisition_length(self):\n        \"\"\"\n        Gets/sets the aquisition length of the oscilloscope\n\n        :type: `int`\n        \"\"\"\n        return int(self.query(\"HOR:RECO?\"))\n\n    @aquisition_length.setter\n    def aquisition_length(self, newval):\n        self.sendcmd(f\"HOR:RECO {newval}\")\n\n    @property\n    def aquisition_running(self):\n        \"\"\"\n        Gets/sets the aquisition state of the attached instrument.\n        This property is `True` if the aquisition is running,\n        and is `False` otherwise.\n\n        :type: `bool`\n        \"\"\"\n        return bool(int(self.query(\"ACQ:STATE?\").strip()))\n\n    @aquisition_running.setter\n    def aquisition_running(self, newval):\n        self.sendcmd(f\"ACQ:STATE {1 if newval else 0}\")\n\n    @property\n    def aquisition_continuous(self):\n        \"\"\"\n        Gets/sets whether the aquisition is continuous (\"run/stop mode\")\n        or whether aquisiton halts after the next sequence (\"single mode\").\n\n        :type: `bool`\n        \"\"\"\n        return self.query(\"ACQ:STOPA?\").strip().startswith(\"RUNST\")\n\n    @aquisition_continuous.setter\n    def aquisition_continuous(self, newval):\n        self.sendcmd(\"ACQ:STOPA {}\".format(\"RUNST\" if newval else \"SEQ\"))\n\n    @property\n    def data_width(self):\n        \"\"\"\n        Gets/sets the data width (number of bytes wide per data point)\n        for waveforms transfered to/from the oscilloscope.\n\n        Valid widths are 1 or 2.\n\n        :type: `int`\n        \"\"\"\n        return int(self.query(\"DATA:WIDTH?\"))\n\n    @data_width.setter\n    def data_width(self, newval):\n        if int(newval) not in [1, 2]:\n            raise ValueError(\"Only one or two byte-width is supported.\")\n\n        self.sendcmd(f\"DATA:WIDTH {newval}\")\n\n    # TODO: convert to read in unitful quantities.\n    @property\n    def y_offset(self):\n        \"\"\"\n        Gets/sets the Y offset of the currently selected data source.\n        \"\"\"\n        yoffs = float(self.query(\"WFMP:YOF?\"))\n        return yoffs\n\n    @y_offset.setter\n    def y_offset(self, newval):\n        self.sendcmd(f\"WFMP:YOF {newval}\")\n\n    # METHODS #\n\n    def force_trigger(self):\n        \"\"\"\n        Forces a trigger event to occur on the attached oscilloscope.\n        Note that this is distinct from the standard SCPI ``*TRG``\n        functionality.\n        \"\"\"\n        self.sendcmd(\"TRIG FORCE\")\n"
  },
  {
    "path": "src/instruments/tektronix/tekdpo70000.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the Tektronix DPO 70000 oscilloscope series\n\"\"\"\n\n# IMPORTS #####################################################################\n\nimport abc\nfrom enum import Enum\nimport time\n\nfrom instruments.abstract_instruments import Oscilloscope\nfrom instruments.generic_scpi import SCPIInstrument\nfrom instruments.optional_dep_finder import numpy\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import (\n    enum_property,\n    string_property,\n    int_property,\n    unitful_property,\n    unitless_property,\n    bool_property,\n    ProxyList,\n)\n\n# CLASSES #####################################################################\n\n# pylint: disable=too-many-lines\n\n\nclass TekDPO70000(SCPIInstrument, Oscilloscope):\n    \"\"\"\n    The Tektronix DPO70000 series  is a multi-channel oscilloscope with analog\n    bandwidths ranging up to 33GHz.\n\n    This class inherits from `~instruments.generic_scpi.SCPIInstrument`.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> tek = ik.tektronix.TekDPO70000.open_tcpip(\"192.168.0.2\", 8888)\n    >>> [x, y] = tek.channel[0].read_waveform()\n    \"\"\"\n\n    # CONSTANTS #\n\n    # The number of horizontal and vertical divisions.\n    HOR_DIVS = 10\n    VERT_DIVS = 10\n\n    # ENUMS #\n\n    class AcquisitionMode(Enum):\n        \"\"\"\n        Enum containing valid acquisition modes for the Tektronix 70000 series\n        oscilloscopes.\n        \"\"\"\n\n        sample = \"SAM\"\n        peak_detect = \"PEAK\"\n        hi_res = \"HIR\"\n        average = \"AVE\"\n        waveform_db = \"WFMDB\"\n        envelope = \"ENV\"\n\n    class AcquisitionState(Enum):\n        \"\"\"\n        Enum containing valid acquisition states for the Tektronix 70000 series\n        oscilloscopes.\n        \"\"\"\n\n        on = \"ON\"\n        off = \"OFF\"\n        run = \"RUN\"\n        stop = \"STOP\"\n\n    class StopAfter(Enum):\n        \"\"\"\n        Enum containing valid stop condition modes for the Tektronix 70000\n        series oscilloscopes.\n        \"\"\"\n\n        run_stop = \"RUNST\"\n        sequence = \"SEQ\"\n\n    class SamplingMode(Enum):\n        \"\"\"\n        Enum containing valid sampling modes for the Tektronix 70000\n        series oscilloscopes.\n        \"\"\"\n\n        real_time = \"RT\"\n        equivalent_time_allowed = \"ET\"\n        interpolation_allowed = \"IT\"\n\n    class HorizontalMode(Enum):\n        \"\"\"\n        Enum containing valid horizontal scan modes for the Tektronix 70000\n        series oscilloscopes.\n        \"\"\"\n\n        auto = \"AUTO\"\n        constant = \"CONST\"\n        manual = \"MAN\"\n\n    class WaveformEncoding(Enum):\n        \"\"\"\n        Enum containing valid waveform encoding modes for the Tektronix 70000\n        series oscilloscopes.\n        \"\"\"\n\n        # NOTE: For some reason, it uses the full names here instead of\n        # returning the mneonics listed in the manual.\n        ascii = \"ASCII\"\n        binary = \"BINARY\"\n\n    class BinaryFormat(Enum):\n        \"\"\"\n        Enum containing valid binary formats for the Tektronix 70000\n        series oscilloscopes (int, unsigned-int, floating-point).\n        \"\"\"\n\n        int = \"RI\"\n        uint = \"RP\"\n        float = \"FP\"  # Single-precision!\n\n    class ByteOrder(Enum):\n        \"\"\"\n        Enum containing valid byte order (big-/little-endian) for the\n        Tektronix 70000 series oscilloscopes.\n        \"\"\"\n\n        little_endian = \"LSB\"\n        big_endian = \"MSB\"\n\n    class TriggerState(Enum):\n        \"\"\"\n        Enum containing valid trigger states for the Tektronix 70000\n        series oscilloscopes.\n        \"\"\"\n\n        armed = \"ARMED\"\n        auto = \"AUTO\"\n        dpo = \"DPO\"\n        partial = \"PARTIAL\"\n        ready = \"READY\"\n\n    # STATIC METHODS #\n\n    @staticmethod\n    def _dtype(binary_format, byte_order, n_bytes):\n        return \"{}{}{}\".format(\n            {\n                TekDPO70000.ByteOrder.big_endian: \">\",\n                TekDPO70000.ByteOrder.little_endian: \"<\",\n            }[byte_order],\n            (n_bytes if n_bytes is not None else \"\"),\n            {\n                TekDPO70000.BinaryFormat.int: \"i\",\n                TekDPO70000.BinaryFormat.uint: \"u\",\n                TekDPO70000.BinaryFormat.float: \"f\",\n            }[binary_format],\n        )\n\n    # CLASSES #\n\n    class DataSource(Oscilloscope.DataSource):\n        \"\"\"\n        Class representing a data source (channel, math, or ref) on the\n        Tektronix DPO 70000.\n\n        .. warning:: This class should NOT be manually created by the user. It\n            is designed to be initialized by the `TekDPO70000` class.\n        \"\"\"\n\n        @property\n        def name(self):\n            return self._name\n\n        @abc.abstractmethod\n        def _scale_raw_data(self, data):\n            \"\"\"\n            Takes the int16 data and figures out how to make it unitful.\n            \"\"\"\n\n        # pylint: disable=protected-access\n        def read_waveform(self, bin_format=True):\n            # We want to get the data back in binary, as it's just too much\n            # otherwise.\n            with self:\n                self._parent.select_fastest_encoding()\n                n_bytes = self._parent.outgoing_n_bytes\n                dtype = self._parent._dtype(\n                    self._parent.outgoing_binary_format,\n                    self._parent.outgoing_byte_order,\n                    n_bytes=None,\n                )\n                self._parent.sendcmd(\"CURV?\")\n                raw = self._parent.binblockread(n_bytes, fmt=dtype)\n                # Clear the queue by reading the end of line character\n                self._parent._file.read_raw(1)\n\n                return self._scale_raw_data(raw)\n\n        def __enter__(self):\n            self._old_dsrc = self._parent.data_source\n            if self._old_dsrc != self:\n                # Set the new data source, and let __exit__ cleanup.\n                self._parent.data_source = self\n            else:\n                # There's nothing to do or undo in this case.\n                self._old_dsrc = None\n\n        def __exit__(self, type, value, traceback):\n            if self._old_dsrc is not None:\n                self._parent.data_source = self._old_dsrc\n\n    class Math(DataSource):\n        \"\"\"\n        Class representing a math channel on the Tektronix DPO 70000.\n\n        This class inherits from `TekDPO70000.DataSource`.\n\n        .. warning:: This class should NOT be manually created by the user. It\n            is designed to be initialized by the `TekDPO70000` class.\n        \"\"\"\n\n        def __init__(self, parent, idx):\n            self._parent = parent\n            self._idx = idx + 1  # 1-based.\n\n            # Initialize as a data source with name MATH{}.\n            super().__init__(parent, f\"MATH{self._idx}\")\n\n        def sendcmd(self, cmd):\n            \"\"\"\n            Wraps commands sent from property factories in this class with\n            identifiers for the specified math channel.\n\n            :param str cmd: Command to send to the instrument\n            \"\"\"\n            self._parent.sendcmd(f\"MATH{self._idx}:{cmd}\")\n\n        def query(self, cmd, size=-1):\n            \"\"\"\n            Wraps queries sent from property factories in this class with\n            identifiers for the specified math channel.\n\n            :param str cmd: Query command to send to the instrument\n            :param int size: Number of characters to read from the response.\n                Default value reads until a termination character is found.\n            :return: The query response\n            :rtype: `str`\n            \"\"\"\n            return self._parent.query(f\"MATH{self._idx}:{cmd}\", size)\n\n        class FilterMode(Enum):\n            \"\"\"\n            Enum containing valid filter modes for a math channel on the\n            TekDPO70000 series oscilloscope.\n            \"\"\"\n\n            centered = \"CENT\"\n            shifted = \"SHIF\"\n\n        class Mag(Enum):\n            \"\"\"\n            Enum containing valid amplitude units for a math channel on the\n            TekDPO70000 series oscilloscope.\n            \"\"\"\n\n            linear = \"LINEA\"\n            db = \"DB\"\n            dbm = \"DBM\"\n\n        class Phase(Enum):\n            \"\"\"\n            Enum containing valid phase units for a math channel on the\n            TekDPO70000 series oscilloscope.\n            \"\"\"\n\n            degrees = \"DEG\"\n            radians = \"RAD\"\n            group_delay = \"GROUPD\"\n\n        class SpectralWindow(Enum):\n            \"\"\"\n            Enum containing valid spectral windows for a math channel on the\n            TekDPO70000 series oscilloscope.\n            \"\"\"\n\n            rectangular = \"RECTANG\"\n            hamming = \"HAMM\"\n            hanning = \"HANN\"\n            kaiser_besse = \"KAISERB\"\n            blackman_harris = \"BLACKMANH\"\n            flattop2 = \"FLATTOP2\"\n            gaussian = \"GAUSS\"\n            tek_exponential = \"TEKEXP\"\n\n        define = string_property(\n            \"DEF\",\n            doc=\"\"\"\n            A text string specifying the math to do, ex. CH1+CH2\n            \"\"\",\n        )\n\n        filter_mode = enum_property(\"FILT:MOD\", FilterMode)\n\n        filter_risetime = unitful_property(\"FILT:RIS\", u.second)\n\n        label = string_property(\n            \"LAB:NAM\",\n            doc=\"\"\"\n            Just a human readable label for the channel.\n            \"\"\",\n        )\n\n        label_xpos = unitless_property(\n            \"LAB:XPOS\",\n            doc=\"\"\"\n            The x position, in divisions, to place the label.\n            \"\"\",\n        )\n\n        label_ypos = unitless_property(\n            \"LAB:YPOS\",\n            doc=\"\"\"The y position, in divisions, to place the label.\n            \"\"\",\n        )\n\n        num_avg = unitless_property(\n            \"NUMAV\",\n            doc=\"\"\"\n            The number of acquisistions over which exponential averaging is\n            performed.\n            \"\"\",\n        )\n\n        spectral_center = unitful_property(\n            \"SPEC:CENTER\",\n            u.Hz,\n            doc=\"\"\"\n            The desired frequency of the spectral analyzer output data span\n            in Hz.\n            \"\"\",\n        )\n\n        spectral_gatepos = unitful_property(\n            \"SPEC:GATEPOS\",\n            u.second,\n            doc=\"\"\"\n            The gate position. Units are represented in seconds, with respect\n            to trigger position.\n            \"\"\",\n        )\n\n        spectral_gatewidth = unitful_property(\n            \"SPEC:GATEWIDTH\",\n            u.second,\n            doc=\"\"\"\n            The time across the 10-division screen in seconds.\n            \"\"\",\n        )\n\n        spectral_lock = bool_property(\"SPEC:LOCK\", inst_true=\"ON\", inst_false=\"OFF\")\n\n        spectral_mag = enum_property(\n            \"SPEC:MAG\",\n            Mag,\n            doc=\"\"\"\n            Whether the spectral magnitude is linear, db, or dbm.\n            \"\"\",\n        )\n\n        spectral_phase = enum_property(\n            \"SPEC:PHASE\",\n            Phase,\n            doc=\"\"\"\n            Whether the spectral phase is degrees, radians, or group delay.\n            \"\"\",\n        )\n\n        spectral_reflevel = unitless_property(\n            \"SPEC:REFL\",\n            doc=\"\"\"\n            The value that represents the topmost display screen graticule.\n            The units depend on spectral_mag.\n            \"\"\",\n        )\n\n        spectral_reflevel_offset = unitless_property(\"SPEC:REFLEVELO\")\n\n        spectral_resolution_bandwidth = unitful_property(\n            \"SPEC:RESB\",\n            u.Hz,\n            doc=\"\"\"\n            The desired resolution bandwidth value. Units are represented in\n            Hertz.\n            \"\"\",\n        )\n\n        spectral_span = unitful_property(\n            \"SPEC:SPAN\",\n            u.Hz,\n            doc=\"\"\"\n            Specifies the frequency span of the output data vector from the\n            spectral analyzer.\n            \"\"\",\n        )\n\n        spectral_suppress = unitless_property(\n            \"SPEC:SUPP\",\n            doc=\"\"\"\n            The magnitude level that data with magnitude values below this\n            value are displayed as zero phase.\n            \"\"\",\n        )\n\n        spectral_unwrap = bool_property(\n            \"SPEC:UNWR\",\n            inst_true=\"ON\",\n            inst_false=\"OFF\",\n            doc=\"\"\"\n            Enables or disables phase wrapping.\n            \"\"\",\n        )\n\n        spectral_window = enum_property(\"SPEC:WIN\", SpectralWindow)\n\n        threshhold = unitful_property(\n            \"THRESH\",\n            u.volt,\n            doc=\"\"\"\n            The math threshhold in volts\n            \"\"\",\n        )\n\n        unit_string = string_property(\n            \"UNITS\",\n            doc=\"\"\"\n            Just a label for the units...doesn\"t actually change anything.\n            \"\"\",\n        )\n\n        autoscale = bool_property(\n            \"VERT:AUTOSC\",\n            inst_true=\"ON\",\n            inst_false=\"OFF\",\n            doc=\"\"\"\n            Enables or disables the auto-scaling of new math waveforms.\n            \"\"\",\n        )\n\n        position = unitless_property(\n            \"VERT:POS\",\n            doc=\"\"\"\n            The vertical position, in divisions from the center graticule.\n            \"\"\",\n        )\n\n        scale = unitful_property(\n            \"VERT:SCALE\",\n            u.volt,\n            doc=\"\"\"\n            The scale in volts per division. The range is from\n            ``100e-36`` to ``100e+36``.\n            \"\"\",\n        )\n\n        def _scale_raw_data(self, data):\n            # TODO: incorperate the unit_string somehow\n            if numpy:\n                return self.scale * (\n                    (TekDPO70000.VERT_DIVS / 2) * data.astype(float) / (2**15)\n                    - self.position\n                )\n\n            scale = self.scale\n            position = self.position\n            rval = tuple(\n                scale * ((TekDPO70000.VERT_DIVS / 2) * d / (2**15) - position)\n                for d in map(float, data)\n            )\n            return rval\n\n    class Channel(DataSource, Oscilloscope.Channel):\n        \"\"\"\n        Class representing a channel on the Tektronix DPO 70000.\n\n        This class inherits from `TekDPO70000.DataSource`.\n\n        .. warning:: This class should NOT be manually created by the user. It\n            is designed to be initialized by the `TekDPO70000` class.\n        \"\"\"\n\n        def __init__(self, parent, idx):\n            self._parent = parent\n            self._idx = idx + 1  # 1-based.\n\n            # Initialize as a data source with name CH{}.\n            super().__init__(self._parent, f\"CH{self._idx}\")\n\n        def sendcmd(self, cmd):\n            \"\"\"\n            Wraps commands sent from property factories in this class with\n            identifiers for the specified channel.\n\n            :param str cmd: Command to send to the instrument\n            \"\"\"\n            self._parent.sendcmd(f\"CH{self._idx}:{cmd}\")\n\n        def query(self, cmd, size=-1):\n            \"\"\"\n            Wraps queries sent from property factories in this class with\n            identifiers for the specified channel.\n\n            :param str cmd: Query command to send to the instrument\n            :param int size: Number of characters to read from the response.\n                Default value reads until a termination character is found.\n            :return: The query response\n            :rtype: `str`\n            \"\"\"\n            return self._parent.query(f\"CH{self._idx}:{cmd}\", size)\n\n        class Coupling(Enum):\n            \"\"\"\n            Enum containing valid coupling modes for the oscilloscope channel\n            \"\"\"\n\n            ac = \"AC\"\n            dc = \"DC\"\n            dc_reject = \"DCREJ\"\n            ground = \"GND\"\n\n        coupling = enum_property(\n            \"COUP\",\n            Coupling,\n            doc=\"\"\"\n            Gets/sets the coupling for the specified channel.\n\n            Example usage:\n\n            >>> import instruments as ik\n            >>> inst = ik.tektronix.TekDPO70000.open_tcpip(\"192.168.0.1\", 8080)\n            >>> channel = inst.channel[0]\n            >>> channel.coupling = channel.Coupling.ac\n            \"\"\",\n        )\n\n        bandwidth = unitful_property(\"BAN\", u.Hz)\n\n        deskew = unitful_property(\"DESK\", u.second)\n\n        termination = unitful_property(\"TERM\", u.ohm)\n\n        label = string_property(\n            \"LAB:NAM\",\n            doc=\"\"\"\n            Just a human readable label for the channel.\n            \"\"\",\n        )\n\n        label_xpos = unitless_property(\n            \"LAB:XPOS\",\n            doc=\"\"\"\n            The x position, in divisions, to place the label.\n            \"\"\",\n        )\n\n        label_ypos = unitless_property(\n            \"LAB:YPOS\",\n            doc=\"\"\"\n            The y position, in divisions, to place the label.\n            \"\"\",\n        )\n\n        offset = unitful_property(\n            \"OFFS\",\n            u.volt,\n            doc=\"\"\"\n            The vertical offset in units of volts. Voltage is given by\n            ``offset+scale*(5*raw/2^15 - position)``.\n            \"\"\",\n        )\n\n        position = unitless_property(\n            \"POS\",\n            doc=\"\"\"\n            The vertical position, in divisions from the center graticule,\n            ranging from ``-8`` to ``8``. Voltage is given by\n            ``offset+scale*(5*raw/2^15 - position)``.\n            \"\"\",\n        )\n\n        scale = unitful_property(\n            \"SCALE\",\n            u.volt,\n            doc=\"\"\"\n            Vertical channel scale in units volts/division. Voltage is given\n            by ``offset+scale*(5*raw/2^15 - position)``.\n            \"\"\",\n        )\n\n        def _scale_raw_data(self, data):\n            scale = self.scale\n            position = self.position\n            offset = self.offset\n\n            if numpy:\n                return (\n                    scale\n                    * (\n                        (TekDPO70000.VERT_DIVS / 2) * data.astype(float) / (2**15)\n                        - position\n                    )\n                    + offset\n                )\n\n            return tuple(\n                scale * ((TekDPO70000.VERT_DIVS / 2) * d / (2**15) - position) + offset\n                for d in map(float, data)\n            )\n\n    # PROPERTIES ##\n\n    @property\n    def channel(self):\n        return ProxyList(self, self.Channel, range(4))\n\n    @property\n    def math(self):\n        return ProxyList(self, self.Math, range(4))\n\n    @property\n    def ref(self):\n        raise NotImplementedError\n\n    # For some settings that probably won't be used that often, use\n    # string_property instead of setting up an enum property.\n    acquire_enhanced_enob = string_property(\n        \"ACQ:ENHANCEDE\",\n        bookmark_symbol=\"\",\n        doc=\"\"\"\n        Valid values are AUTO and OFF.\n        \"\"\",\n    )\n\n    acquire_enhanced_state = bool_property(\n        \"ACQ:ENHANCEDE:STATE\",\n        inst_false=\"0\",  # TODO: double check that these are correct\n        inst_true=\"1\",\n    )\n\n    acquire_interp_8bit = string_property(\n        \"ACQ:INTERPE\",\n        bookmark_symbol=\"\",\n        doc=\"\"\"\n        Valid values are AUTO, ON and OFF.\n        \"\"\",\n    )\n\n    acquire_magnivu = bool_property(\"ACQ:MAG\", inst_true=\"ON\", inst_false=\"OFF\")\n\n    acquire_mode = enum_property(\"ACQ:MOD\", AcquisitionMode)\n\n    acquire_mode_actual = enum_property(\"ACQ:MOD:ACT\", AcquisitionMode, readonly=True)\n\n    acquire_num_acquisitions = int_property(\n        \"ACQ:NUMAC\",\n        readonly=True,\n        doc=\"\"\"\n        The number of waveform acquisitions that have occurred since starting\n        acquisition with the ACQuire:STATE RUN command\n        \"\"\",\n    )\n\n    acquire_num_avgs = int_property(\n        \"ACQ:NUMAV\",\n        doc=\"\"\"\n        The number of waveform acquisitions to average.\n        \"\"\",\n    )\n\n    acquire_num_envelop = int_property(\n        \"ACQ:NUME\",\n        doc=\"\"\"\n        The number of waveform acquisitions to be enveloped\n        \"\"\",\n    )\n\n    acquire_num_frames = int_property(\n        \"ACQ:NUMFRAMESACQ\",\n        readonly=True,\n        doc=\"\"\"\n        The number of frames acquired when in FastFrame Single Sequence and\n        acquisitions are running.\n        \"\"\",\n    )\n\n    acquire_num_samples = int_property(\n        \"ACQ:NUMSAM\",\n        doc=\"\"\"\n        The minimum number of acquired samples that make up a waveform\n        database (WfmDB) waveform for single sequence mode and Mask Pass/Fail\n        Completion Test. The default value is 16,000 samples. The range is\n        5,000 to 2,147,400,000 samples.\n        \"\"\",\n    )\n\n    acquire_sampling_mode = enum_property(\"ACQ:SAMP\", SamplingMode)\n\n    acquire_state = enum_property(\n        \"ACQ:STATE\",\n        AcquisitionState,\n        doc=\"\"\"\n        This command starts or stops acquisitions.\n        \"\"\",\n    )\n\n    acquire_stop_after = enum_property(\n        \"ACQ:STOPA\",\n        StopAfter,\n        doc=\"\"\"\n        This command sets or queries whether the instrument continually\n        acquires acquisitions or acquires a single sequence.\n        \"\"\",\n    )\n\n    data_framestart = int_property(\"DAT:FRAMESTAR\")\n\n    data_framestop = int_property(\"DAT:FRAMESTOP\")\n\n    data_start = int_property(\n        \"DAT:STAR\",\n        doc=\"\"\"\n        The first data point that will be transferred, which ranges from 1 to\n        the record length.\n        \"\"\",\n    )\n\n    # TODO: Look into the following troublesome datasheet note: \"When using the\n    # CURVe command, DATa:STOP is ignored and WFMInpre:NR_Pt is used.\"\n    data_stop = int_property(\n        \"DAT:STOP\",\n        doc=\"\"\"\n        The last data point that will be transferred.\n        \"\"\",\n    )\n\n    data_sync_sources = bool_property(\"DAT:SYNCSOU\", inst_true=\"ON\", inst_false=\"OFF\")\n\n    @property\n    def data_source(self):\n        \"\"\"\n        Gets/sets the data source for the oscilloscope. This will return\n        the actual Channel/Math/DataSource object as if it was accessed\n        through the usual `TekDPO70000.channel`, `TekDPO70000.math`, or\n        `TekDPO70000.ref` properties.\n\n        :type: `TekDPO70000.Channel` or `TekDPO70000.Math`\n        \"\"\"\n        val = self.query(\"DAT:SOU?\")\n        if val[0:2] == \"CH\":\n            out = self.channel[int(val[2]) - 1]\n        elif val[0:2] == \"MA\":\n            out = self.math[int(val[4]) - 1]\n        elif val[0:2] == \"RE\":\n            out = self.ref[int(val[3]) - 1]\n        else:\n            raise NotImplementedError\n        return out\n\n    @data_source.setter\n    def data_source(self, newval):\n        if not isinstance(newval, self.DataSource):\n            raise TypeError(f\"{type(newval)} is not a valid data source.\")\n        self.sendcmd(f\"DAT:SOU {newval.name}\")\n\n        # Some Tek scopes require this after the DAT:SOU command, or else\n        # they will stop responding.\n        time.sleep(0.02)\n\n    horiz_acq_duration = unitful_property(\n        \"HOR:ACQDURATION\",\n        u.second,\n        readonly=True,\n        doc=\"\"\"\n        The duration of the acquisition.\n        \"\"\",\n    )\n\n    horiz_acq_length = int_property(\n        \"HOR:ACQLENGTH\",\n        readonly=True,\n        doc=\"\"\"\n        The record length.\n        \"\"\",\n    )\n\n    horiz_delay_mode = bool_property(\"HOR:DEL:MOD\", inst_true=\"1\", inst_false=\"0\")\n\n    horiz_delay_pos = unitful_property(\n        \"HOR:DEL:POS\",\n        u.percent,\n        doc=\"\"\"\n        The percentage of the waveform that is displayed left of the center\n        graticule.\n        \"\"\",\n    )\n\n    horiz_delay_time = unitful_property(\n        \"HOR:DEL:TIM\",\n        u.second,\n        doc=\"\"\"\n        The base trigger delay time setting.\n        \"\"\",\n    )\n\n    horiz_interp_ratio = unitless_property(\n        \"HOR:MAI:INTERPR\",\n        readonly=True,\n        doc=\"\"\"\n        The ratio of interpolated points to measured points.\n        \"\"\",\n    )\n\n    horiz_main_pos = unitful_property(\n        \"HOR:MAI:POS\",\n        u.percent,\n        doc=\"\"\"\n        The percentage of the waveform that is displayed left of the center\n        graticule.\n        \"\"\",\n    )\n\n    horiz_unit = string_property(\"HOR:MAI:UNI\")\n\n    horiz_mode = enum_property(\"HOR:MODE\", HorizontalMode)\n\n    horiz_record_length_lim = int_property(\n        \"HOR:MODE:AUTO:LIMIT\",\n        doc=\"\"\"\n        The recond length limit in samples.\n        \"\"\",\n    )\n\n    horiz_record_length = int_property(\n        \"HOR:MODE:RECO\",\n        doc=\"\"\"\n        The recond length in samples. See `horiz_mode`; manual mode lets you\n        change the record length, while the length is readonly for auto and\n        constant mode.\n        \"\"\",\n    )\n\n    horiz_sample_rate = unitful_property(\n        \"HOR:MODE:SAMPLER\",\n        u.Hz,\n        doc=\"\"\"\n        The sample rate in samples per second.\n        \"\"\",\n    )\n\n    horiz_scale = unitful_property(\n        \"HOR:MODE:SCA\",\n        u.second,\n        doc=\"\"\"\n        The horizontal scale in seconds per division. The horizontal scale is\n        readonly when `horiz_mode` is manual.\n        \"\"\",\n    )\n\n    horiz_pos = unitful_property(\n        \"HOR:POS\",\n        u.percent,\n        doc=\"\"\"\n        The position of the trigger point on the screen, left is 0%, right\n        is 100%.\n        \"\"\",\n    )\n\n    horiz_roll = string_property(\n        \"HOR:ROLL\",\n        bookmark_symbol=\"\",\n        doc=\"\"\"\n        Valid arguments are AUTO, OFF, and ON.\n        \"\"\",\n    )\n\n    trigger_state = enum_property(\"TRIG:STATE\", TriggerState)\n\n    # Waveform Transfer Properties\n    outgoing_waveform_encoding = enum_property(\n        \"WFMO:ENC\",\n        WaveformEncoding,\n        doc=\"\"\"\n        Controls the encoding used for outgoing waveforms (instrument → host).\n        \"\"\",\n    )\n\n    outgoing_binary_format = enum_property(\n        \"WFMO:BN_F\",\n        BinaryFormat,\n        doc=\"\"\"\n        Controls the data type of samples when transferring waveforms from\n        the instrument to the host using binary encoding.\n        \"\"\",\n    )\n\n    outgoing_byte_order = enum_property(\n        \"WFMO:BYT_O\",\n        ByteOrder,\n        doc=\"\"\"\n        Controls whether binary data is returned in little or big endian.\n        \"\"\",\n    )\n\n    outgoing_n_bytes = int_property(\n        \"WFMO:BYT_N\",\n        valid_set={1, 2, 4, 8},\n        doc=\"\"\"\n        The number of bytes per sample used in representing outgoing\n        waveforms in binary encodings.\n\n        Must be either 1, 2, 4 or 8.\n        \"\"\",\n    )\n\n    # METHODS #\n\n    def select_fastest_encoding(self):\n        \"\"\"\n        Sets the encoding for data returned by this instrument to be the\n        fastest encoding method consistent with the current data source.\n        \"\"\"\n        self.sendcmd(\"DAT:ENC FAS\")\n\n    def force_trigger(self):\n        \"\"\"\n        Forces a trigger event to happen for the oscilloscope.\n        \"\"\"\n        self.sendcmd(\"TRIG FORC\")\n\n    # TODO: consider moving the next few methods to Oscilloscope.\n    def run(self):\n        \"\"\"\n        Enables the trigger for the oscilloscope.\n        \"\"\"\n        self.sendcmd(\":RUN\")\n\n    def stop(self):\n        \"\"\"\n        Disables the trigger for the oscilloscope.\n        \"\"\"\n        self.sendcmd(\":STOP\")\n"
  },
  {
    "path": "src/instruments/tektronix/tektds224.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the Tektronix TDS 224 oscilloscope\n\"\"\"\n\n# IMPORTS #####################################################################\n\nimport time\n\nfrom enum import Enum\n\nfrom instruments.abstract_instruments import Oscilloscope\nfrom instruments.generic_scpi import SCPIInstrument\nfrom instruments.optional_dep_finder import numpy\nfrom instruments.util_fns import ProxyList\nfrom instruments.units import ureg as u\n\n# CLASSES #####################################################################\n\n\nclass TekTDS224(SCPIInstrument, Oscilloscope):\n    \"\"\"\n    The Tektronix TDS224 is a multi-channel oscilloscope with analog\n    bandwidths of 100MHz.\n\n    This class inherits from `~instruments.generic_scpi.SCPIInstrument`.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> tek = ik.tektronix.TekTDS224.open_gpibusb(\"/dev/ttyUSB0\", 1)\n    >>> [x, y] = tek.channel[0].read_waveform()\n    \"\"\"\n\n    def __init__(self, filelike):\n        super().__init__(filelike)\n        self._file.timeout = 3 * u.second\n\n    class DataSource(Oscilloscope.DataSource):\n        \"\"\"\n        Class representing a data source (channel, math, or ref) on the Tektronix\n        TDS 224.\n\n        .. warning:: This class should NOT be manually created by the user. It is\n            designed to be initialized by the `TekTDS224` class.\n        \"\"\"\n\n        def __init__(self, tek, name):\n            super().__init__(tek, name)\n            self._tek = self._parent\n\n        @property\n        def name(self):\n            \"\"\"\n            Gets the name of this data source, as identified over SCPI.\n\n            :type: `str`\n            \"\"\"\n            return self._name\n\n        def read_waveform(self, bin_format=True):\n            \"\"\"\n            Read waveform from the oscilloscope.\n            This function is all inclusive. After reading the data from the\n            oscilloscope, it unpacks the data and scales it accordingly.\n\n            Supports both ASCII and binary waveform transfer. For 2500 data\n            points, with a width of 2 bytes, transfer takes approx 2 seconds for\n            binary, and 7 seconds for ASCII over Galvant Industries' GPIBUSB\n            adapter.\n\n            Function returns a tuple (x,y), where both x and y are numpy arrays.\n\n            :param bool bin_format: If `True`, data is transfered\n                in a binary format. Otherwise, data is transferred in ASCII.\n\n            :rtype: `tuple`[`tuple`[`float`, ...], `tuple`[`float`, ...]]\n                or if numpy is installed, `tuple`[`numpy.array`, `numpy.array`]\n            \"\"\"\n            with self:\n                if not bin_format:\n                    self._tek.sendcmd(\"DAT:ENC ASCI\")\n                    # Set the data encoding format to ASCII\n                    raw = self._tek.query(\"CURVE?\")\n                    raw = raw.split(\",\")  # Break up comma delimited string\n                    if numpy:\n                        raw = numpy.array(raw, dtype=float)  # Convert to ndarray\n                    else:\n                        raw = tuple(map(float, raw))\n                else:\n                    self._tek.sendcmd(\"DAT:ENC RIB\")\n                    # Set encoding to signed, big-endian\n                    data_width = self._tek.data_width\n                    self._tek.sendcmd(\"CURVE?\")\n                    raw = self._tek.binblockread(\n                        data_width\n                    )  # Read in the binary block,\n                    # data width of 2 bytes\n\n                    # pylint: disable=protected-access\n                    self._tek._file.flush_input()  # Flush input buffer\n\n                yoffs = self._tek.query(f\"WFMP:{self.name}:YOF?\")  # Retrieve Y offset\n                ymult = self._tek.query(f\"WFMP:{self.name}:YMU?\")  # Retrieve Y multiply\n                yzero = self._tek.query(f\"WFMP:{self.name}:YZE?\")  # Retrieve Y zero\n\n                xzero = self._tek.query(\"WFMP:XZE?\")  # Retrieve X zero\n                xincr = self._tek.query(\"WFMP:XIN?\")  # Retrieve X incr\n                ptcnt = self._tek.query(\n                    f\"WFMP:{self.name}:NR_P?\"\n                )  # Retrieve number of data points\n\n                if numpy:\n                    x = numpy.arange(float(ptcnt)) * float(xincr) + float(xzero)\n                    y = ((raw - float(yoffs)) * float(ymult)) + float(yzero)\n                else:\n                    x = tuple(\n                        float(val) * float(xincr) + float(xzero)\n                        for val in range(int(ptcnt))\n                    )\n                    y = tuple(\n                        ((x - float(yoffs)) * float(ymult)) + float(yzero) for x in raw\n                    )\n\n                return x, y\n\n    class Channel(DataSource, Oscilloscope.Channel):\n        \"\"\"\n        Class representing a channel on the Tektronix TDS 224.\n\n        This class inherits from `TekTDS224.DataSource`.\n\n        .. warning:: This class should NOT be manually created by the user. It is\n            designed to be initialized by the `TekTDS224` class.\n        \"\"\"\n\n        def __init__(self, parent, idx):\n            super().__init__(parent, f\"CH{idx + 1}\")\n            self._idx = idx + 1\n\n        @property\n        def coupling(self):\n            \"\"\"\n            Gets/sets the coupling setting for this channel.\n\n            :type: `TekTDS224.Coupling`\n            \"\"\"\n            return TekTDS224.Coupling(self._tek.query(f\"CH{self._idx}:COUPL?\"))\n\n        @coupling.setter\n        def coupling(self, newval):\n            if not isinstance(newval, TekTDS224.Coupling):\n                raise TypeError(\n                    f\"Coupling setting must be a `TekTDS224.Coupling` value,\"\n                    f\"got {type(newval)} instead.\"\n                )\n            self._tek.sendcmd(f\"CH{self._idx}:COUPL {newval.value}\")\n\n    # ENUMS #\n\n    class Coupling(Enum):\n        \"\"\"\n        Enum containing valid coupling modes for the Tek TDS224\n        \"\"\"\n\n        ac = \"AC\"\n        dc = \"DC\"\n        ground = \"GND\"\n\n    # PROPERTIES #\n\n    @property\n    def channel(self):\n        \"\"\"\n        Gets a specific oscilloscope channel object. The desired channel is\n        specified like one would access a list.\n\n        For instance, this would transfer the waveform from the first channel::\n\n        >>> import instruments as ik\n        >>> tek = ik.tektronix.TekTDS224.open_tcpip('192.168.0.2', 8888)\n        >>> [x, y] = tek.channel[0].read_waveform()\n\n        :rtype: `TekTDS224.Channel`\n        \"\"\"\n        return ProxyList(self, self.Channel, range(4))\n\n    @property\n    def ref(self):\n        \"\"\"\n        Gets a specific oscilloscope reference channel object. The desired\n        channel is specified like one would access a list.\n\n        For instance, this would transfer the waveform from the first channel::\n\n        >>> import instruments as ik\n        >>> tek = ik.tektronix.TekTDS224.open_tcpip('192.168.0.2', 8888)\n        >>> [x, y] = tek.ref[0].read_waveform()\n\n        :rtype: `TekTDS224.DataSource`\n        \"\"\"\n        return ProxyList(\n            self, lambda s, idx: self.DataSource(s, f\"REF{idx + 1}\"), range(4)\n        )\n\n    @property\n    def math(self):\n        \"\"\"\n        Gets a data source object corresponding to the MATH channel.\n\n        :rtype: `TekTDS224.DataSource`\n        \"\"\"\n        return self.DataSource(self, \"MATH\")\n\n    @property\n    def data_source(self):\n        \"\"\"\n        Gets/sets the the data source for waveform transfer.\n        \"\"\"\n        name = self.query(\"DAT:SOU?\")\n        if name.startswith(\"CH\"):\n            return self.Channel(self, int(name[2:]) - 1)\n\n        return self.DataSource(self, name)\n\n    @data_source.setter\n    def data_source(self, newval):\n        # TODO: clean up type-checking here.\n        if not isinstance(newval, str):\n            if hasattr(newval, \"value\"):  # Is an enum with a value.\n                newval = newval.value\n            elif hasattr(newval, \"name\"):  # Is a datasource with a name.\n                newval = newval.name\n        self.sendcmd(f\"DAT:SOU {newval}\")\n        time.sleep(0.01)  # Let the instrument catch up.\n\n    @property\n    def data_width(self):\n        \"\"\"\n        Gets/sets the byte-width of the data points being returned by the\n        instrument. Valid widths are ``1`` or ``2``.\n\n        :type: `int`\n        \"\"\"\n        return int(self.query(\"DATA:WIDTH?\"))\n\n    @data_width.setter\n    def data_width(self, newval):\n        if int(newval) not in [1, 2]:\n            raise ValueError(\"Only one or two byte-width is supported.\")\n\n        self.sendcmd(f\"DATA:WIDTH {newval}\")\n\n    def force_trigger(self):\n        raise NotImplementedError\n"
  },
  {
    "path": "src/instruments/tektronix/tektds5xx.py",
    "content": "#!/usr/bin/env python\n#\n# tektds5xx.py: Driver for the Tektronix TDS 5xx series oscilloscope.\n#\n# © 2014 Chris Schimp (silverchris@gmail.com)\n#\n# Modified from tektds224.py\n# © 2013 Steven Casagrande (scasagrande@galvant.ca).\n#\n# This file is a part of the InstrumentKit project.\n# Licensed under the AGPL version 3.\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Affero General Public License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with this program. If not, see <http://www.gnu.org/licenses/>.\n#\n\"\"\"\nProvides support for the Tektronix DPO 500 oscilloscope series.\n\nOriginally contributed by Chris Schimp (silverchris@gmail.com) in 2014.\nBased off of tektds224.py written by Steven Casagrande.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom datetime import datetime\nfrom enum import Enum\nfrom functools import reduce\nimport operator\nimport struct\nimport time\n\n\nfrom instruments.abstract_instruments import Oscilloscope\nfrom instruments.generic_scpi import SCPIInstrument\nfrom instruments.optional_dep_finder import numpy\nfrom instruments.util_fns import ProxyList\n\n# CLASSES #####################################################################\n\n\nclass TekTDS5xx(SCPIInstrument, Oscilloscope):\n    \"\"\"\n    Support for the TDS5xx series of oscilloscopes\n     Implemented from:\n      | TDS Family Digitizing Oscilloscopes\n      | (TDS 410A, 420A, 460A, 520A, 524A, 540A, 544A,\n      | 620A, 640A, 644A, 684A, 744A & 784A)\n      | Tektronix Document: 070-8709-07\n    \"\"\"\n\n    class Measurement:\n        \"\"\"\n        Class representing a measurement channel on the Tektronix TDS5xx\n        \"\"\"\n\n        def __init__(self, tek, idx):\n            self._tek = tek\n            self._id = idx + 1\n            resp = self._tek.query(f\"MEASU:MEAS{self._id}?\")\n            self._data = dict(\n                zip(\n                    [\n                        \"enabled\",\n                        \"type\",\n                        \"units\",\n                        \"src1\",\n                        \"src2\",\n                        \"edge1\",\n                        \"edge2\",\n                        \"dir\",\n                    ],\n                    resp.split(\";\"),\n                )\n            )\n\n        def read(self):\n            \"\"\"\n            Gets the current measurement value of the channel, and returns a dict\n            of all relevant information\n\n            :rtype: `dict` of measurement parameters\n            \"\"\"\n            if int(self._data[\"enabled\"]):\n                resp = self._tek.query(f\"MEASU:MEAS{self._id}:VAL?\")\n                self._data[\"value\"] = float(resp)\n                return self._data\n\n            return self._data\n\n    class DataSource(Oscilloscope.DataSource):\n        \"\"\"\n        Class representing a data source (channel, math, or ref) on the Tektronix\n        TDS 5xx.\n\n        .. warning:: This class should NOT be manually created by the user. It is\n            designed to be initialized by the `TekTDS5xx` class.\n        \"\"\"\n\n        @property\n        def name(self):\n            \"\"\"\n            Gets the name of this data source, as identified over SCPI.\n\n            :type: `str`\n            \"\"\"\n            return self._name\n\n        def read_waveform(self, bin_format=True):\n            \"\"\"\n            Read waveform from the oscilloscope.\n            This function is all inclusive. After reading the data from the\n            oscilloscope, it unpacks the data and scales it accordingly.\n\n            Supports both ASCII and binary waveform transfer. For 2500 data\n            points, with a width of 2 bytes, transfer takes approx 2 seconds for\n            binary, and 7 seconds for ASCII over Galvant Industries' GPIBUSB\n            adapter.\n\n            Function returns a tuple (x,y), where both x and y are numpy arrays.\n\n            :param bool bin_format: If `True`, data is transfered\n                in a binary format. Otherwise, data is transferred in ASCII.\n\n            :rtype: `tuple`[`tuple`[`float`, ...], `tuple`[`float`, ...]]\n                or if numpy is installed, `tuple`[`numpy.array`, `numpy.array`]\n            \"\"\"\n            with self:\n                if not bin_format:\n                    # Set the data encoding format to ASCII\n                    self._parent.sendcmd(\"DAT:ENC ASCI\")\n                    raw = self._parent.query(\"CURVE?\")\n                    raw = raw.split(\",\")  # Break up comma delimited string\n                    if numpy:\n                        raw = numpy.array(raw, dtype=float)  # Convert to numpy array\n                    else:\n                        raw = map(float, raw)\n                else:\n                    # Set encoding to signed, big-endian\n                    self._parent.sendcmd(\"DAT:ENC RIB\")\n                    data_width = self._parent.data_width\n                    self._parent.sendcmd(\"CURVE?\")\n                    # Read in the binary block, data width of 2 bytes\n                    raw = self._parent.binblockread(data_width)\n\n                    # pylint: disable=protected-access\n                    # read line separation character\n                    self._parent._file.read_raw(1)\n\n                # Retrieve Y offset\n                yoffs = float(self._parent.query(f\"WFMP:{self.name}:YOF?\"))\n                # Retrieve Y multiply\n                ymult = float(self._parent.query(f\"WFMP:{self.name}:YMU?\"))\n                # Retrieve Y zero\n                yzero = float(self._parent.query(f\"WFMP:{self.name}:YZE?\"))\n\n                # Retrieve X incr\n                xincr = float(self._parent.query(f\"WFMP:{self.name}:XIN?\"))\n                # Retrieve number of data points\n                ptcnt = int(self._parent.query(f\"WFMP:{self.name}:NR_P?\"))\n\n                if numpy:\n                    x = numpy.arange(float(ptcnt)) * float(xincr)\n                    y = ((raw - yoffs) * float(ymult)) + float(yzero)\n                else:\n                    x = tuple(float(val) * float(xincr) for val in range(ptcnt))\n                    y = tuple(((x - yoffs) * float(ymult)) + float(yzero) for x in raw)\n\n                return x, y\n\n    class Channel(DataSource, Oscilloscope.Channel):\n        \"\"\"\n        Class representing a channel on the Tektronix TDS 5xx.\n\n        This class inherits from `TekTDS5xx.DataSource`.\n\n        .. warning:: This class should NOT be manually created by the user. It is\n            designed to be initialized by the `TekTDS5xx` class.\n        \"\"\"\n\n        def __init__(self, parent, idx):\n            super().__init__(parent, f\"CH{idx + 1}\")\n            self._idx = idx + 1\n\n        @property\n        def coupling(self):\n            \"\"\"\n            Gets/sets the coupling setting for this channel.\n\n            :type: `TekTDS5xx.Coupling`\n            \"\"\"\n            return TekTDS5xx.Coupling(self._parent.query(f\"CH{self._idx}:COUPL?\"))\n\n        @coupling.setter\n        def coupling(self, newval):\n            if not isinstance(newval, TekTDS5xx.Coupling):\n                raise TypeError(\n                    \"Coupling setting must be a `TekTDS5xx.Coupling`\"\n                    \" value, got {} instead.\".format(type(newval))\n                )\n\n            self._parent.sendcmd(f\"CH{self._idx}:COUPL {newval.value}\")\n\n        @property\n        def bandwidth(self):\n            \"\"\"\n            Gets/sets the Bandwidth setting for this channel.\n\n            :type: `TekTDS5xx.Bandwidth`\n            \"\"\"\n            return TekTDS5xx.Bandwidth(self._parent.query(f\"CH{self._idx}:BAND?\"))\n\n        @bandwidth.setter\n        def bandwidth(self, newval):\n            if not isinstance(newval, TekTDS5xx.Bandwidth):\n                raise TypeError(\n                    \"Bandwidth setting must be a `TekTDS5xx.Bandwidth`\"\n                    \" value, got {} instead.\".format(type(newval))\n                )\n\n            self._parent.sendcmd(f\"CH{self._idx}:BAND {newval.value}\")\n\n        @property\n        def impedance(self):\n            \"\"\"\n            Gets/sets the impedance setting for this channel.\n\n            :type: `TekTDS5xx.Impedance`\n            \"\"\"\n            return TekTDS5xx.Impedance(self._parent.query(f\"CH{self._idx}:IMP?\"))\n\n        @impedance.setter\n        def impedance(self, newval):\n            if not isinstance(newval, TekTDS5xx.Impedance):\n                raise TypeError(\n                    \"Impedance setting must be a `TekTDS5xx.Impedance`\"\n                    \" value, got {} instead.\".format(type(newval))\n                )\n\n            self._parent.sendcmd(f\"CH{self._idx}:IMP {newval.value}\")\n\n        @property\n        def probe(self):\n            \"\"\"\n            Gets the connected probe value for this channel\n\n            :type: `float`\n            \"\"\"\n            return round(1 / float(self._parent.query(f\"CH{self._idx}:PRO?\")), 0)\n\n        @property\n        def scale(self):\n            \"\"\"\n            Gets/sets the scale setting for this channel.\n\n            :type: `float`\n            \"\"\"\n            return float(self._parent.query(f\"CH{self._idx}:SCA?\"))\n\n        @scale.setter\n        def scale(self, newval):\n            self._parent.sendcmd(f\"CH{self._idx}:SCA {newval:.3E}\")\n            resp = float(self._parent.query(f\"CH{self._idx}:SCA?\"))\n            if newval != resp:\n                raise ValueError(\n                    \"Tried to set CH{} Scale to {} but got {}\"\n                    \" instead\".format(self._idx, newval, resp)\n                )\n\n    # ENUMS ##\n\n    class Coupling(Enum):\n        \"\"\"\n        Available coupling options for input sources and trigger\n        \"\"\"\n\n        ac = \"AC\"\n        dc = \"DC\"\n        ground = \"GND\"\n\n    class Bandwidth(Enum):\n        \"\"\"\n        Bandwidth in MHz\n        \"\"\"\n\n        Twenty = \"TWE\"\n        OneHundred = \"HUN\"\n        TwoHundred = \"TWO\"\n        FULL = \"FUL\"\n\n    class Impedance(Enum):\n        \"\"\"\n        Available options for input source impedance\n        \"\"\"\n\n        Fifty = \"FIF\"\n        OneMeg = \"MEG\"\n\n    class Edge(Enum):\n        \"\"\"\n        Available Options for trigger slope\n        \"\"\"\n\n        Rising = \"RIS\"\n        Falling = \"FALL\"\n\n    class Trigger(Enum):\n        \"\"\"\n        Available Trigger sources\n        (AUX not Available on TDS520A/TDS540A)\n        \"\"\"\n\n        CH1 = \"CH1\"\n        CH2 = \"CH2\"\n        CH3 = \"CH3\"\n        CH4 = \"CH4\"\n        AUX = \"AUX\"\n        LINE = \"LINE\"\n\n    class Source(Enum):\n        \"\"\"\n        Available Data sources\n        \"\"\"\n\n        CH1 = \"CH1\"\n        CH2 = \"CH2\"\n        CH3 = \"CH3\"\n        CH4 = \"CH4\"\n        Math1 = \"MATH1\"\n        Math2 = \"MATH2\"\n        Math3 = \"MATH3\"\n        Ref1 = \"REF1\"\n        Ref2 = \"REF2\"\n        Ref3 = \"REF3\"\n        Ref4 = \"REF4\"\n\n    # PROPERTIES ##\n    @property\n    def measurement(self):\n        \"\"\"\n        Gets a specific oscilloscope measurement object. The desired channel is\n        specified like one would access a list.\n\n        :rtype: `TekTDS5xx.Measurement`\n        \"\"\"\n        return ProxyList(self, self.Measurement, range(3))\n\n    @property\n    def channel(self):\n        \"\"\"\n        Gets a specific oscilloscope channel object. The desired channel is\n        specified like one would access a list.\n\n        For instance, this would transfer the waveform from the first channel::\n\n        >>> tek = ik.tektronix.TekTDS5xx.open_tcpip('192.168.0.2', 8888)\n        >>> [x, y] = tek.channel[0].read_waveform()\n\n        :rtype: `TekTDS5xx.Channel`\n        \"\"\"\n        return ProxyList(self, self.Channel, range(4))\n\n    @property\n    def ref(self):\n        \"\"\"\n        Gets a specific oscilloscope reference channel object. The desired\n        channel is specified like one would access a list.\n\n        For instance, this would transfer the waveform from the first channel::\n\n        >>> tek = ik.tektronix.TekTDS5xx.open_tcpip('192.168.0.2', 8888)\n        >>> [x, y] = tek.ref[0].read_waveform()\n\n        :rtype: `TekTDS5xx.DataSource`\n        \"\"\"\n        return ProxyList(\n            self,\n            lambda s, idx: self.DataSource(s, f\"REF{idx + 1}\"),\n            range(4),\n        )\n\n    @property\n    def math(self):\n        \"\"\"\n        Gets a data source object corresponding to the MATH channel.\n\n        :rtype: `TekTDS5xx.DataSource`\n        \"\"\"\n        return ProxyList(\n            self,\n            lambda s, idx: self.DataSource(s, f\"MATH{idx + 1}\"),\n            range(3),\n        )\n\n    @property\n    def sources(self):\n        \"\"\"\n        Returns list of all active sources\n\n        :rtype: `list`\n        \"\"\"\n        active = []\n        channels = list(map(int, self.query(\"SEL?\").split(\";\")[0:11]))\n        for idx in range(0, 4):\n            if channels[idx]:\n                active.append(self.Channel(self, idx))\n        for idx in range(4, 7):\n            if channels[idx]:\n                active.append(self.DataSource(self, f\"MATH{idx - 3}\"))\n        for idx in range(7, 11):\n            if channels[idx]:\n                active.append(self.DataSource(self, f\"REF{idx - 6}\"))\n        return active\n\n    @property\n    def data_source(self):\n        \"\"\"\n        Gets/sets the the data source for waveform transfer.\n\n        :type: `TekTDS5xx.Source` or `TekTDS5xx.DataSource`\n        :rtype: `TekTDS5xx.DataSource`\n        \"\"\"\n        name = self.query(\"DAT:SOU?\")\n        if name.startswith(\"CH\"):\n            return self.Channel(self, int(name[2:]) - 1)\n\n        return self.DataSource(self, name)\n\n    @data_source.setter\n    def data_source(self, newval):\n        if isinstance(newval, self.DataSource):\n            newval = TekTDS5xx.Source(newval.name)\n        if not isinstance(newval, TekTDS5xx.Source):\n            raise TypeError(\n                \"Source setting must be a `TekTDS5xx.Source`\"\n                \" value, got {} instead.\".format(type(newval))\n            )\n\n        self.sendcmd(f\"DAT:SOU {newval.value}\")\n        time.sleep(0.01)  # Let the instrument catch up.\n\n    @property\n    def data_width(self):\n        \"\"\"\n        Gets/Sets the data width for waveform transfers\n\n        :type: `int`\n        \"\"\"\n        return int(self.query(\"DATA:WIDTH?\"))\n\n    @data_width.setter\n    def data_width(self, newval):\n        if int(newval) not in [1, 2]:\n            raise ValueError(\"Only one or two byte-width is supported.\")\n\n        self.sendcmd(f\"DATA:WIDTH {newval}\")\n\n    def force_trigger(self):\n        raise NotImplementedError\n\n    @property\n    def horizontal_scale(self):\n        \"\"\"\n        Get/Set Horizontal Scale\n\n        :type: `float`\n        \"\"\"\n        return float(self.query(\"HOR:MAI:SCA?\"))\n\n    @horizontal_scale.setter\n    def horizontal_scale(self, newval):\n        self.sendcmd(f\"HOR:MAI:SCA {newval:.3E}\")\n        resp = float(self.query(\"HOR:MAI:SCA?\"))\n        if newval != resp:\n            raise ValueError(\n                \"Tried to set Horizontal Scale to {} but got {}\"\n                \" instead\".format(newval, resp)\n            )\n\n    @property\n    def trigger_level(self):\n        \"\"\"\n        Get/Set trigger level\n\n        :type: `float`\n        \"\"\"\n        return float(self.query(\"TRIG:MAI:LEV?\"))\n\n    @trigger_level.setter\n    def trigger_level(self, newval):\n        self.sendcmd(f\"TRIG:MAI:LEV {newval:.3E}\")\n        resp = float(self.query(\"TRIG:MAI:LEV?\"))\n        if newval != resp:\n            raise ValueError(\n                \"Tried to set trigger level to {} but got {}\"\n                \" instead\".format(newval, resp)\n            )\n\n    @property\n    def trigger_coupling(self):\n        \"\"\"\n        Get/Set trigger coupling\n\n        :type: `TekTDS5xx.Coupling`\n        \"\"\"\n        return TekTDS5xx.Coupling(self.query(\"TRIG:MAI:EDGE:COUP?\"))\n\n    @trigger_coupling.setter\n    def trigger_coupling(self, newval):\n        if not isinstance(newval, TekTDS5xx.Coupling):\n            raise TypeError(\n                \"Coupling setting must be a `TekTDS5xx.Coupling`\"\n                \" value, got {} instead.\".format(type(newval))\n            )\n\n        self.sendcmd(f\"TRIG:MAI:EDGE:COUP {newval.value}\")\n\n    @property\n    def trigger_slope(self):\n        \"\"\"\n        Get/Set trigger slope\n\n        :type: `TekTDS5xx.Edge`\n        \"\"\"\n        return TekTDS5xx.Edge(self.query(\"TRIG:MAI:EDGE:SLO?\"))\n\n    @trigger_slope.setter\n    def trigger_slope(self, newval):\n        if not isinstance(newval, TekTDS5xx.Edge):\n            raise TypeError(\n                \"Edge setting must be a `TekTDS5xx.Edge`\"\n                \" value, got {} instead.\".format(type(newval))\n            )\n\n        self.sendcmd(f\"TRIG:MAI:EDGE:SLO {newval.value}\")\n\n    @property\n    def trigger_source(self):\n        \"\"\"\n        Get/Set trigger source\n\n        :type: `TekTDS5xx.Trigger`\n        \"\"\"\n        return TekTDS5xx.Trigger(self.query(\"TRIG:MAI:EDGE:SOU?\"))\n\n    @trigger_source.setter\n    def trigger_source(self, newval):\n        if not isinstance(newval, TekTDS5xx.Trigger):\n            raise TypeError(\n                \"Trigger source setting must be a \"\n                \"`TekTDS5xx.Trigger` value, got {} \"\n                \"instead.\".format(type(newval))\n            )\n\n        self.sendcmd(f\"TRIG:MAI:EDGE:SOU {newval.value}\")\n\n    @property\n    def clock(self):\n        \"\"\"\n        Get/Set oscilloscope clock\n\n        :type: `datetime.datetime`\n        \"\"\"\n        resp = self.query(\"DATE?;:TIME?\")\n        return datetime.strptime(resp, '\"%Y-%m-%d\";\"%H:%M:%S\"')\n\n    @clock.setter\n    def clock(self, newval):\n        if not isinstance(newval, datetime):\n            raise ValueError(\n                \"Expected datetime.datetime \" \"but got {} instead\".format(type(newval))\n            )\n        self.sendcmd(newval.strftime('DATE \"%Y-%m-%d\";:TIME \"%H:%M:%S\"'))\n\n    @property\n    def display_clock(self):\n        \"\"\"\n        Get/Set the visibility of clock on the display\n\n        :type: `bool`\n        \"\"\"\n        return bool(int(self.query(\"DISPLAY:CLOCK?\")))\n\n    @display_clock.setter\n    def display_clock(self, newval):\n        if not isinstance(newval, bool):\n            raise ValueError(\"Expected bool but got \" \"{} instead\".format(type(newval)))\n        self.sendcmd(f\"DISPLAY:CLOCK {int(newval)}\")\n\n    def get_hardcopy(self):\n        \"\"\"\n        Gets a screenshot of the display\n\n        :rtype: `string`\n        \"\"\"\n        self.sendcmd(\"HARDC:PORT GPI;HARDC:LAY PORT;:HARDC:FORM BMP\")\n        self.sendcmd(\"HARDC START\")\n        time.sleep(1)\n        header = self._file.read_raw(size=54)\n        # Get BMP Length  in kilobytes from DIB header, because file header is\n        # bad\n        length = reduce(operator.mul, struct.unpack(\"<iihh\", header[18:30])) / 8\n        length = int(length) + 8  # Add 8 bytes for our monochrome colour table\n        data = header + self._file.read_raw(size=length)\n        self._file.flush_input()  # Flush input buffer\n        return data\n"
  },
  {
    "path": "src/instruments/teledyne/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing Teledyne instruments\n\"\"\"\n\nfrom .maui import MAUI\n"
  },
  {
    "path": "src/instruments/teledyne/maui.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the Teledyne-Lecroy Oscilloscopes that use the\nMAUI interface.\n\nDevelopment follows the IEEE 488.2 Command Reference from the MAUI\nOscilloscopes Remote Control and Automation Manual, document number\nmaui-remote-control-automation_10mar20.pdf\n\nWhere possible, commands are sent using the enum_property, ... that\nare usually used for SCPI classes, even though, this is not an SCPI\nclass.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom enum import Enum\n\nfrom instruments.abstract_instruments import Oscilloscope\nfrom instruments.optional_dep_finder import numpy\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import assume_units, enum_property, bool_property, ProxyList\n\n# CLASSES #####################################################################\n\n\n# pylint: disable=too-many-lines,arguments-differ\n\n\nclass MAUI(Oscilloscope):\n    \"\"\"\n    Medium to high-end Teledyne-Lecroy Oscilloscopes are shipped with\n    the MAUI user interface. This class can be used to communicate with\n    these instruments.\n\n    By default, the IEEE 488.2 commands are used. However, commands\n    based on MAUI's `app` definition can be submitted too using the\n    appropriate send / query commands.\n\n    Your scope must be set up to communicate via LXI (VXI11) to be used\n    with pyvisa. Make sure that either the pyvisa-py or the NI-VISA\n    backend is installed. Please see the pyvisa documentation for more\n    information.\n\n    This class inherits from: `Oscilloscope`\n\n    Example usage (more examples below):\n        >>> import instruments as ik\n        >>> import instruments.units as u\n        >>> inst = ik.teledyne.MAUI.open_visa(\"TCPIP0::192.168.0.10::INSTR\")\n        >>> # start the trigger in automatic mode\n        >>> inst.run()\n        >>> print(inst.trigger_state)  # print the trigger state\n        <TriggerState.auto: 'AUTO'>\n        >>> # set timebase to 20 ns per division\n        >>> inst.time_div = u.Quantity(20, u.ns)\n        >>> # call the first oscilloscope channel\n        >>> channel = inst.channel[0]\n        >>> channel.trace = True  # turn the trace on\n        >>> channel.coupling = channel.Coupling.dc50  # coupling to 50 Ohm\n        >>> channel.scale = u.Quantity(1, u.V)  # vertical scale to 1V/division\n        >>> # transfer a waveform into xdat and ydat:\n        >>> xdat, ydat = channel.read_waveform()\n    \"\"\"\n\n    # CONSTANTS #\n\n    # number of horizontal and vertical divisions on the scope\n    # HOR_DIVS = 10\n    # VERT_DIVS = 8\n\n    def __init__(self, filelike):\n        super().__init__(filelike)\n\n        # turn off command headers -> for SCPI like behavior\n        self.sendcmd(\"COMM_HEADER OFF\")\n\n        # constants\n        self._number_channels = 4\n        self._number_functions = 2\n        self._number_measurements = 6\n\n    # ENUMS #\n\n    class MeasurementParameters(Enum):\n        \"\"\"\n        Enum containing valid measurement parameters that only require\n        one or more sources. Only single source parameters are currently\n        implemented.\n        \"\"\"\n\n        amplitude = \"AMPL\"\n        area = \"AREA\"\n        base = \"BASE\"\n        delay = \"DLY\"\n        duty_cycle = \"DUTY\"\n        fall_time_80_20 = \"FALL82\"\n        fall_time_90_10 = \"FALL\"\n        frequency = \"FREQ\"\n        maximum = \"MAX\"\n        minimum = \"MIN\"\n        mean = \"MEAN\"\n        none = \"NULL\"\n        overshoot_pos = \"OVSP\"\n        overshoot_neg = \"OVSN\"\n        peak_to_peak = \"PKPK\"\n        period = \"PER\"\n        phase = \"PHASE\"\n        rise_time_20_80 = \"RISE28\"\n        rise_time_10_90 = \"RISE\"\n        rms = \"RMS\"\n        stdev = \"SDEV\"\n        top = \"TOP\"\n        width_50_pos = \"WID\"\n        width_50_neg = \"WIDN\"\n\n    class TriggerState(Enum):\n        \"\"\"\n        Enum containing valid trigger state for the oscilloscope.\n        \"\"\"\n\n        auto = \"AUTO\"\n        normal = \"NORM\"\n        single = \"SINGLE\"\n        stop = \"STOP\"\n\n    class TriggerType(Enum):\n        \"\"\"Enum containing valid trigger state.\n\n        Availability depends on oscilloscope options. Please consult\n        your manual. Only simple types are currently included.\n\n        .. warning:: Some of the trigger types are untested and might\n            need further parameters in order to be appropriately set.\n        \"\"\"\n\n        dropout = \"DROPOUT\"\n        edge = \"EDGE\"\n        glitch = \"GLIT\"\n        interval = \"INTV\"\n        pattern = \"PA\"\n        runt = \"RUNT\"\n        slew_rate = \"SLEW\"\n        width = \"WIDTH\"\n        qualified = \"TEQ\"\n        tv = \"TV\"\n\n    class TriggerSource(Enum):\n        \"\"\"Enum containing valid trigger sources.\n\n        This is an enum for the default values.\n\n        .. note:: This class is initialized like this for four channels,\n            which is the default setting. If you change the number of\n            channels, `TriggerSource` will be recreated using the\n            routine `_create_trigger_source_enum`. This will make\n            further channels available to you or remove channels that\n            are not present in your setup.\n        \"\"\"\n\n        c0 = \"C1\"\n        c1 = \"C2\"\n        c2 = \"C3\"\n        c3 = \"C4\"\n        ext = \"EX\"\n        ext5 = \"EX5\"\n        ext10 = \"EX10\"\n        etm10 = \"ETM10\"\n        line = \"LINE\"\n\n    def _create_trigger_source_enum(self):\n        \"\"\"Create an Enum for the trigger source class.\n\n        Needs to be dynamically generated, in case channel number\n        changes!\n\n        .. note:: Not all trigger sources are available on all scopes.\n            Please consult the manual for your oscilloscope.\n        \"\"\"\n        names = [\"ext\", \"ext5\", \"ext10\", \"etm10\", \"line\"]\n        values = [\"EX\", \"EX5\", \"EX10\", \"ETM10\", \"LINE\"]\n        # now add the channels\n        for it in range(self._number_channels):\n            names.append(f\"c{it}\")\n            values.append(f\"C{it + 1}\")  # to send to scope\n        # create and store the enum\n        self.TriggerSource = Enum(\"TriggerSource\", zip(names, values))\n\n    # CLASSES #\n\n    class DataSource(Oscilloscope.DataSource):\n        \"\"\"\n        Class representing a data source (channel, math, ref) on a MAUI\n        oscilloscope.\n\n        .. warning:: This class should NOT be manually created by the\n            user. It is designed to be initialized by the `MAUI` class.\n        \"\"\"\n\n        # PROPERTIES #\n\n        @property\n        def name(self):\n            return self._name\n\n        # METHODS #\n\n        def read_waveform(self, bin_format=False, single=True):\n            \"\"\"\n            Reads the waveform and returns an array of floats with the\n            data.\n\n            :param bin_format: Not implemented, always False\n            :type bin_format: bool\n            :param single: Run a single trigger? Default True. In case\n                a waveform from a channel is required, this option\n                is recommended to be set to True. This means that the\n                acquisition system is first stopped, a single trigger\n                is issued, then the waveform is transfered, and the\n                system is set back into the state it was in before.\n                If sampling math with multiple samples, set this to\n                false, otherwise the sweeps are cleared by the\n                oscilloscope prior when a single trigger command is\n                issued.\n            :type single: bool\n\n            :return: Data (time, signal) where time is in seconds and\n                signal in V\n            :rtype: `tuple`[`tuple`[`~pint.Quantity`, ...], `tuple`[`~pint.Quantity`, ...]]\n                or if numpy is installed, `tuple`[`numpy.array`, `numpy.array`]\n\n            :raises NotImplementedError: Bin format was chosen, but\n                it is not implemented.\n\n            Example usage:\n                >>> import instruments as ik\n                >>> import instruments.units as u\n                >>> inst = ik.teledyne.MAUI.open_visa(\"TCPIP0::192.168.0.10::INSTR\")\n                >>> channel = inst.channel[0]  # set up channel\n                >>> xdat, ydat = channel.read_waveform()  # read waveform\n            \"\"\"\n            if bin_format:\n                raise NotImplementedError(\n                    \"Bin format reading is currently \"\n                    \"not implemented for the MAUI \"\n                    \"routine.\"\n                )\n\n            if single:\n                # get current trigger state (to reset after read)\n                trig_state = self._parent.trigger_state\n                # trigger state to single\n                self._parent.trigger_state = self._parent.TriggerState.single\n\n            # now read the data\n            retval = self.query(\"INSPECT? 'SIMPLE'\")  # pylint: disable=E1101\n\n            # read the parameters to create time-base array\n            horiz_off = self.query(\"INSPECT? 'HORIZ_OFFSET'\")  # pylint: disable=E1101\n            horiz_int = self.query(\"INSPECT? 'HORIZ_INTERVAL'\")  # pylint: disable=E1101\n\n            if single:\n                # reset trigger\n                self._parent.trigger_state = trig_state\n\n            # format the string to appropriate data\n            retval = retval.replace('\"', \"\").split()\n            if numpy:\n                dat_val = numpy.array(retval, dtype=float)  # Convert to ndarray\n            else:\n                dat_val = tuple(map(float, retval))\n\n            # format horizontal data into floats\n            horiz_off = float(horiz_off.replace('\"', \"\").split(\":\")[1])\n            horiz_int = float(horiz_int.replace('\"', \"\").split(\":\")[1])\n\n            # create time base\n            if numpy:\n                dat_time = numpy.arange(\n                    horiz_off, horiz_off + horiz_int * (len(dat_val)), horiz_int\n                )\n            else:\n                dat_time = tuple(\n                    val * horiz_int + horiz_off for val in range(len(dat_val))\n                )\n\n            # fix length bug, sometimes dat_time is longer than dat_signal\n            if len(dat_time) > len(dat_val):\n                dat_time = dat_time[0 : len(dat_val)]\n            else:  # in case the opposite is the case\n                dat_val = dat_val[0 : len(dat_time)]\n\n            if numpy:\n                return numpy.stack((dat_time, dat_val))\n            else:\n                return dat_time, dat_val\n\n        trace = bool_property(\n            command=\"TRA\",\n            doc=\"\"\"\n            Gets/Sets if a given trace is turned on or off.\n\n            Example usage:\n\n            >>> import instruments as ik\n            >>> address = \"TCPIP0::192.168.0.10::INSTR\"\n            >>> inst = inst = ik.teledyne.MAUI.open_visa(address)\n            >>> channel = inst.channel[0]\n            >>> channel.trace = False\n            \"\"\",\n        )\n\n    class Channel(DataSource, Oscilloscope.Channel):\n        \"\"\"\n        Class representing a channel on a MAUI oscilloscope.\n\n        .. warning:: This class should NOT be manually created by the\n            user. It is designed to be initialized by the `MAUI` class.\n        \"\"\"\n\n        def __init__(self, parent, idx):\n            self._parent = parent\n            self._idx = idx + 1  # 1-based\n\n            # Initialize as a data source with name C{}.\n            super().__init__(self._parent, f\"C{self._idx}\")\n\n        # ENUMS #\n\n        class Coupling(Enum):\n            \"\"\"\n            Enum containing valid coupling modes for the oscilloscope\n            channel. 1 MOhm and 50 Ohm are included.\n            \"\"\"\n\n            ac1M = \"A1M\"\n            dc1M = \"D1M\"\n            dc50 = \"D50\"\n            ground = \"GND\"\n\n        coupling = enum_property(\n            \"CPL\",\n            Coupling,\n            doc=\"\"\"\n            Gets/sets the coupling for the specified channel.\n\n            Example usage:\n\n            >>> import instruments as ik\n            >>> address = \"TCPIP0::192.168.0.10::INSTR\"\n            >>> inst = inst = ik.teledyne.MAUI.open_visa(address)\n            >>> channel = inst.channel[0]\n            >>> channel.coupling = channel.Coupling.dc50\n            \"\"\",\n        )\n\n        # PROPERTIES #\n\n        @property\n        def offset(self):\n            \"\"\"\n            Sets/gets the vertical offset of the specified input\n            channel.\n\n            Example:\n                >>> import instruments as ik\n                >>> import instruments.units as u\n                >>> inst = ik.teledyne.MAUI.open_visa(\"TCPIP0::192.168.0.10::INSTR\")\n                >>> channel = inst.channel[0]  # set up channel\n                >>> channel.offset = u.Quantity(-1, u.V)\n            \"\"\"\n            return u.Quantity(float(self.query(\"OFST?\")), u.V)\n\n        @offset.setter\n        def offset(self, newval):\n            newval = assume_units(newval, \"V\").to(u.V).magnitude\n            self.sendcmd(f\"OFST {newval}\")\n\n        @property\n        def scale(self):\n            \"\"\"\n            Sets/Gets the vertical scale of the channel.\n\n            Example:\n                >>> import instruments as ik\n                >>> import instruments.units as u\n                >>> inst = ik.teledyne.MAUI.open_visa(\"TCPIP0::192.168.0.10::INSTR\")\n                >>> channel = inst.channel[0]  # set up channel\n                >>> channel.scale = u.Quantity(20, u.mV)\n            \"\"\"\n            return u.Quantity(float(self.query(\"VDIV?\")), u.V)\n\n        @scale.setter\n        def scale(self, newval):\n            newval = assume_units(newval, \"V\").to(u.V).magnitude\n            self.sendcmd(f\"VDIV {newval}\")\n\n        # METHODS #\n\n        def sendcmd(self, cmd):\n            \"\"\"\n            Wraps commands sent from property factories in this class\n            with identifiers for the specified channel.\n\n            :param str cmd: Command to send to the instrument\n            \"\"\"\n            self._parent.sendcmd(f\"C{self._idx}:{cmd}\")\n\n        def query(self, cmd, size=-1):\n            \"\"\"\n            Executes the given query. Wraps commands sent from property\n            factories in this class with identifiers for the specified\n            channel.\n\n            :param str cmd: String containing the query to\n                execute.\n            :param int size: Number of bytes to be read. Default is read\n                until termination character is found.\n            :return: The result of the query as returned by the\n                connected instrument.\n            :rtype: `str`\n            \"\"\"\n            return self._parent.query(f\"C{self._idx}:{cmd}\", size=size)\n\n    class Math(DataSource):\n        \"\"\"\n        Class representing a function on a MAUI oscilloscope.\n\n        .. warning:: This class should NOT be manually created by the\n            user. It is designed to be initialized by the `MAUI` class.\n        \"\"\"\n\n        def __init__(self, parent, idx):\n            self._parent = parent\n            self._idx = idx + 1  # 1-based\n\n            # Initialize as a data source with name C{}.\n            super().__init__(self._parent, f\"F{self._idx}\")\n\n        # CLASSES #\n\n        class Operators:\n            \"\"\"\n            Sets the operator for a given channel.\n            Most operators need a source `src`. If the source is given\n            as an integer, it is assumed that a signal channel is\n            requested. If you want to select another math channel for\n            example, you will need to specify the source as a tuple:\n            Example: `src=('f', 0)` would represent the first function\n            channel (called F1 in the MAUI manual). A channel could be\n            selected by calling `src=('c', 1)`, which would request the\n            second channel (oscilloscope channel 2). Please consult the\n            oscilloscope manual / the math setup itself for further\n            possibilities.\n\n            .. note:: Your oscilloscope might not have all functions\n                that are described here. Also: Not all possibilities are\n                currently implemented. However, extension of this\n                functionality should be simple when following the given\n                structure\n            \"\"\"\n\n            def __init__(self, parent):\n                self._parent = parent\n\n            # PROPERTIES #\n\n            @property\n            def current_setting(self):\n                \"\"\"\n                Gets the current setting and returns it as the full\n                command, as sent to the scope when setting an operator.\n                \"\"\"\n                return self._parent.query(\"DEF?\")\n\n            # METHODS - OPERATORS #\n\n            def absolute(self, src):\n                \"\"\"\n                Absolute of wave form.\n\n                :param int,tuple src: Source, see info above\n                \"\"\"\n                src_str = _source(src)\n                send_str = f\"'ABS({src_str})'\"\n                self._send_operator(send_str)\n\n            def average(self, src, average_type=\"summed\", sweeps=1000):\n                \"\"\"\n                Average of wave form.\n\n                :param int,tuple src: Source, see info above\n                :param str average_type: `summed` or `continuous`\n                :param int sweeps: In summed mode, how many sweeps to\n                    collect. In `continuous` mode the weight of each\n                    sweep is equal to 1/`1`sweeps`\n                \"\"\"\n                src_str = _source(src)\n\n                avgtp_str = \"SUMMED\"\n                if average_type == \"continuous\":\n                    avgtp_str = \"CONTINUOUS\"\n\n                send_str = \"'AVG({})',AVERAGETYPE,{},SWEEPS,{}\".format(\n                    src_str, avgtp_str, sweeps\n                )\n\n                self._send_operator(send_str)\n\n            def derivative(self, src, vscale=1e6, voffset=0, autoscale=True):\n                \"\"\"\n                Derivative of waveform using subtraction of adjacent\n                samples. If vscale and voffset are unitless, V/s are\n                assumed.\n\n                :param int,tuple src: Source, see info above\n                :param float vscale: vertical units to display (V/s)\n                :param float voffset: vertical offset (V/s)\n                :param bool autoscale: auto scaling of vscale, voffset?\n                \"\"\"\n                src_str = _source(src)\n\n                vscale = assume_units(vscale, u.V / u.s).to(u.V / u.s).magnitude\n\n                voffset = assume_units(voffset, u.V / u.s).to(u.V / u.s).magnitude\n\n                autoscale_str = \"OFF\"\n                if autoscale:\n                    autoscale_str = \"ON\"\n\n                send_str = (\n                    \"'DERI({})',VERSCALE,{},VEROFFSET,{},\"\n                    \"ENABLEAUTOSCALE,{}\".format(src_str, vscale, voffset, autoscale_str)\n                )\n\n                self._send_operator(send_str)\n\n            def difference(self, src1, src2, vscale_variable=False):\n                \"\"\"\n                Difference between two sources, `src1`-`src2`.\n\n                :param int,tuple src1: Source 1, see info above\n                :param int,tuple src2: Source 2, see info above\n                :param bool vscale_variable: Horizontal and vertical\n                    scale for addition and subtraction must be\n                    identical. Allow for variable vertical scale in\n                    result?\n                \"\"\"\n                src1_str = _source(src1)\n                src2_str = _source(src2)\n\n                opt_str = \"FALSE\"\n                if vscale_variable:\n                    opt_str = \"TRUE\"\n\n                send_str = \"'{}-{}',VERSCALEVARIABLE,{}\".format(\n                    src1_str, src2_str, opt_str\n                )\n\n                self._send_operator(send_str)\n\n            def envelope(self, src, sweeps=1000, limit_sweeps=True):\n                \"\"\"\n                Highest and lowest Y values at each X in N sweeps.\n\n                :param int,tuple src: Source, see info above\n                :param int sweeps: Number of sweeps\n                :param bool limit_sweeps: Limit the number of sweeps?\n                \"\"\"\n                src_str = _source(src)\n                send_str = \"'EXTR({})',SWEEPS,{},LIMITNUMSWEEPS,{}\".format(\n                    src_str, sweeps, limit_sweeps\n                )\n                self._send_operator(send_str)\n\n            def eres(self, src, bits=0.5):\n                \"\"\"\n                Smoothing function defined by extra bits of resolution.\n\n                :param int,tuple src: Source, see info above\n                :param float bits: Number of bits. Possible values are\n                    (0.5, 1.0, 1.5, 2.0, 2.5, 3.0). If not in list,\n                    default to 0.5.\n                \"\"\"\n                src_str = _source(src)\n\n                bits_possible = (0.5, 1.0, 1.5, 2.0, 2.5, 3.0)\n                if bits not in bits_possible:\n                    bits = 0.5\n\n                send_str = f\"'ERES({src_str})',BITS,{bits}\"\n\n                self._send_operator(send_str)\n\n            def fft(\n                self, src, type=\"powerspectrum\", window=\"vonhann\", suppress_dc=True\n            ):\n                \"\"\"\n                Fast Fourier Transform of signal.\n\n                :param int,tuple src: Source, see info above\n                :param str type: Type of power spectrum. Possible\n                    options are: ['real', 'imaginary', 'magnitude',\n                    'phase', 'powerspectrum', 'powerdensity'].\n                    Default: 'powerspectrum'\n                :param str window: Window. Possible options are:\n                    ['blackmanharris', 'flattop', 'hamming',\n                    'rectangular', 'vonhann']. Default: 'vonhann'\n                :param bool suppress_dc: Supress DC?\n                \"\"\"\n                src_str = _source(src)\n\n                type_possible = [\n                    \"real\",\n                    \"imaginary\",\n                    \"magnitude\",\n                    \"phase\",\n                    \"powerspectrum\",\n                    \"powerdensity\",\n                ]\n                if type not in type_possible:\n                    type = \"powerspectrum\"\n\n                window_possible = [\n                    \"blackmanharris\",\n                    \"flattop\",\n                    \"hamming\",\n                    \"rectangular\",\n                    \"vonhann\",\n                ]\n                if window not in window_possible:\n                    window = \"vonhann\"\n\n                if suppress_dc:\n                    opt = \"ON\"\n                else:\n                    opt = \"OFF\"\n\n                send_str = \"'FFT({})',TYPE,{},WINDOW,{},SUPPRESSDC,{}\".format(\n                    src_str, type, window, opt\n                )\n\n                self._send_operator(send_str)\n\n            def floor(self, src, sweeps=1000, limit_sweeps=True):\n                \"\"\"\n                Lowest vertical value at each X value in N sweeps.\n\n                :param int,tuple src: Source, see info above\n                :param int sweeps: Number of sweeps\n                :param bool limit_sweeps: Limit the number of sweeps?\n                \"\"\"\n                src_str = _source(src)\n                send_str = \"'FLOOR({})',SWEEPS,{},LIMITNUMSWEEPS,{}\".format(\n                    src_str, sweeps, limit_sweeps\n                )\n                self._send_operator(send_str)\n\n            def integral(self, src, multiplier=1, adder=0, vscale=1e-3, voffset=0):\n                \"\"\"\n                Integral of waveform.\n\n                :param int,tuple src: Source, see info above\n                :param float multiplier: 0 to 1e15\n                :param float adder: 0 to 1e15\n                :param float vscale: vertical units to display (Wb)\n                :param float voffset: vertical offset (Wb)\n                \"\"\"\n                src_str = _source(src)\n\n                vscale = assume_units(vscale, u.Wb).to(u.Wb).magnitude\n\n                voffset = assume_units(voffset, u.Wb).to(u.Wb).magnitude\n\n                send_str = (\n                    \"'INTG({}),MULTIPLIER,{},ADDER,{},VERSCALE,{},\"\n                    \"VEROFFSET,{}\".format(src_str, multiplier, adder, vscale, voffset)\n                )\n\n                self._send_operator(send_str)\n\n            def invert(self, src):\n                \"\"\"\n                Inversion of waveform (-waveform).\n\n                :param int,tuple src: Source, see info above\n                \"\"\"\n                src_str = _source(src)\n                self._send_operator(f\"'-{src_str}'\")\n\n            def product(self, src1, src2):\n                \"\"\"\n                Product of two sources, `src1`*`src2`.\n\n                :param int,tuple src1: Source 1, see info above\n                :param int,tuple src2: Source 2, see info above\n                \"\"\"\n                src1_str = _source(src1)\n                src2_str = _source(src2)\n\n                send_str = f\"'{src1_str}*{src2_str}'\"\n\n                self._send_operator(send_str)\n\n            def ratio(self, src1, src2):\n                \"\"\"\n                Ratio of two sources, `src1`/`src2`.\n\n                :param int,tuple src1: Source 1, see info above\n                :param int,tuple src2: Source 2, see info above\n                \"\"\"\n                src1_str = _source(src1)\n                src2_str = _source(src2)\n\n                send_str = f\"'{src1_str}/{src2_str}'\"\n\n                self._send_operator(send_str)\n\n            def reciprocal(self, src):\n                \"\"\"\n                Reciprocal of waveform (1/waveform).\n\n                :param int,tuple src: Source, see info above\n                \"\"\"\n                src_str = _source(src)\n                self._send_operator(f\"'1/{src_str}'\")\n\n            def rescale(self, src, multiplier=1, adder=0):\n                \"\"\"\n                Rescales the waveform (w) in the style.\n                multiplier * w + adder\n\n                :param int,tuple src: Source, see info above\n                :param float multiplier: multiplier\n                :param float adder: addition in V or assuming V\n                \"\"\"\n                src_str = _source(src)\n\n                adder = assume_units(adder, u.V).to(u.V).magnitude\n\n                send_str = \"'RESC({})',MULTIPLIER,{},ADDER,{}\".format(\n                    src_str, multiplier, adder\n                )\n\n                self._send_operator(send_str)\n\n            def sinx(self, src):\n                \"\"\"\n                Sin(x)/x interpolation to produce 10x output samples.\n\n                :param int,tuple src: Source, see info above\n                \"\"\"\n                src_str = _source(src)\n                self._send_operator(f\"'SINX({src_str})'\")\n\n            def square(self, src):\n                \"\"\"\n                Square of the input waveform.\n\n                :param int,tuple src: Source, see info above\n                \"\"\"\n                src_str = _source(src)\n                self._send_operator(f\"'SQR({src_str})'\")\n\n            def square_root(self, src):\n                \"\"\"\n                Square root of the input waveform.\n\n                :param int,tuple src: Source, see info above\n                \"\"\"\n                src_str = _source(src)\n                self._send_operator(f\"'SQRT({src_str})'\")\n\n            def sum(self, src1, src2):\n                \"\"\"\n                Product of two sources, `src1`+`src2`.\n\n                :param int,tuple src1: Source 1, see info above\n                :param int,tuple src2: Source 2, see info above\n                \"\"\"\n                src1_str = _source(src1)\n                src2_str = _source(src2)\n\n                send_str = f\"'{src1_str}+{src2_str}'\"\n\n                self._send_operator(send_str)\n\n            def trend(self, src, vscale=1, center=0, autoscale=True):\n                \"\"\"\n                Trend of the values of a paramter\n\n                :param float vscale: vertical units to display (V)\n                :param float center: center (V)\n                \"\"\"\n                src_str = _source(src)\n\n                vscale = assume_units(vscale, u.V).to(u.V).magnitude\n\n                center = assume_units(center, u.V).to(u.V).magnitude\n\n                if autoscale:\n                    auto_str = \"ON\"\n                else:\n                    auto_str = \"OFF\"\n\n                send_str = (\n                    \"'TREND({})',VERSCALE,{},CENTER,{},\"\n                    \"AUTOFINDSCALE,{}\".format(src_str, vscale, center, auto_str)\n                )\n\n                self._send_operator(send_str)\n\n            def roof(self, src, sweeps=1000, limit_sweeps=True):\n                \"\"\"\n                Highest vertical value at each X value in N sweeps.\n\n                :param int,tuple src: Source, see info above\n                :param int sweeps: Number of sweeps\n                :param bool limit_sweeps: Limit the number of sweeps?\n                \"\"\"\n                src_str = _source(src)\n                send_str = \"'ROOF({})',SWEEPS,{},LIMITNUMSWEEPS,{}\".format(\n                    src_str, sweeps, limit_sweeps\n                )\n                self._send_operator(send_str)\n\n            def _send_operator(self, cmd):\n                \"\"\"\n                Set the operator in the scope.\n                \"\"\"\n                self._parent.sendcmd(\"{},{}\".format(\"DEFINE EQN\", cmd))\n\n        # PROPERTIES #\n\n        @property\n        def operator(self):\n            \"\"\"Get an operator object to set use to do math.\n\n            Example:\n                >>> import instruments as ik\n                >>> import instruments.units as u\n                >>> inst = ik.teledyne.MAUI.open_visa(\"TCPIP0::192.168.0.10::INSTR\")\n                >>> channel = inst.channel[0]  # set up channel\n                >>> # set up the first math function\n                >>> function = inst.math[0]\n                >>> function.trace = True  # turn the trace on\n                >>> # set function to average the first oscilloscope channel\n                >>> function.operator.average(0)\n            \"\"\"\n            return self.Operators(self)\n\n        # METHODS #\n\n        def clear_sweeps(self):\n            \"\"\"Clear the sweeps in a measurement.\"\"\"\n            self._parent.clear_sweeps()  # re-implemented because handy\n\n        def sendcmd(self, cmd):\n            \"\"\"\n            Wraps commands sent from property factories in this class\n            with identifiers for the specified channel.\n\n            :param str cmd: Command to send to the instrument\n            \"\"\"\n            self._parent.sendcmd(f\"F{self._idx}:{cmd}\")\n\n        def query(self, cmd, size=-1):\n            \"\"\"\n            Executes the given query. Wraps commands sent from property\n            factories in this class with identifiers for the specified\n            channel.\n\n            :param str cmd: String containing the query to\n                execute.\n            :param int size: Number of bytes to be read. Default is read\n                until termination character is found.\n            :return: The result of the query as returned by the\n                connected instrument.\n            :rtype: `str`\n            \"\"\"\n            return self._parent.query(f\"F{self._idx}:{cmd}\", size=size)\n\n    class Measurement:\n        \"\"\"\n        Class representing a measurement on a MAUI oscilloscope.\n\n        .. warning:: This class should NOT be manually created by the\n            user. It is designed to be initialized by the `MAUI` class.\n        \"\"\"\n\n        def __init__(self, parent, idx):\n            self._parent = parent\n            self._idx = idx + 1  # 1-based\n\n        # CLASSES #\n\n        class State(Enum):\n            \"\"\"\n            Enum class for Measurement Parameters. Required to turn it\n            on or off.\n            \"\"\"\n\n            statistics = \"CUST,STAT\"\n            histogram_icon = \"CUST,HISTICON\"\n            both = \"CUST,BOTH\"\n            off = \"CUST,OFF\"\n\n        # PROPERTIES #\n\n        measurement_state = enum_property(\n            command=\"PARM\",\n            enum=State,\n            doc=\"\"\"\n                Sets / Gets the measurement state. Valid values are\n                'statistics', 'histogram_icon', 'both', 'off'.\n\n                Example:\n                    >>> import instruments as ik\n                    >>> import instruments.units as u\n                    >>> inst = ik.teledyne.MAUI.open_visa(\"TCPIP0::192.168.0.10::INSTR\")\n                    >>> msr1 = inst.measurement[0]  # set up first measurement\n                    >>> msr1.measurement_state = msr1.State.both  # set to `both`\n                \"\"\",\n        )\n\n        @property\n        def statistics(self):\n            \"\"\"\n            Gets the statistics for the selected parameter. The scope\n            must be in `My_Measure` mode.\n\n            :return tuple: (average, low, high, sigma, sweeps)\n            :return type: (float, float, float, float, float)\n            \"\"\"\n            ret_str = self.query(f\"PAST? CUST, P{self._idx}\").rstrip().split(\",\")\n            # parse the return string -> put into dictionary:\n            ret_dict = {\n                ret_str[it]: ret_str[it + 1] for it in range(0, len(ret_str), 2)\n            }\n            try:\n                stats = (\n                    float(ret_dict[\"AVG\"]),\n                    float(ret_dict[\"LOW\"]),\n                    float(ret_dict[\"HIGH\"]),\n                    float(ret_dict[\"SIGMA\"]),\n                    float(ret_dict[\"SWEEPS\"]),\n                )\n            except ValueError:  # some statistics did not return\n                raise ValueError(\n                    \"Some statistics did not return useful \"\n                    \"values. The return string is {}. Please \"\n                    \"ensure that statistics is properly turned \"\n                    \"on.\".format(ret_str)\n                )\n            return stats\n\n        # METHODS #\n\n        def delete(self):\n            \"\"\"\n            Deletes the given measurement parameter.\n            \"\"\"\n            self.sendcmd(f\"PADL {self._idx}\")\n\n        def set_parameter(self, param, src):\n            \"\"\"\n            Sets a given parameter that should be measured on this\n            given channel.\n\n            :param `inst.MeasurementParameters` param: The parameter\n                to set from the given enum list.\n            :param int,tuple src: Source, either as an integer if a\n                channel is requested (e.g., src=0 for Channel 1) or as\n                a tuple in the form, e.g., ('F', 1). Here 'F' refers\n                to a mathematical function and 1 would take the second\n                mathematical function `F2`.\n\n            :raises AttributeError: The chosen parameter is invalid.\n\n            Example:\n                >>> import instruments as ik\n                >>> import instruments.units as u\n                >>> inst = ik.teledyne.MAUI.open_visa(\"TCPIP0::192.168.0.10::INSTR\")\n                >>> msr1 = inst.measurement[0]  # set up first measurement\n                >>> # setup to measure the 10 - 90% rise time on first channel\n                >>> msr1.set_parameter(inst.MeasurementParameters.rise_time_10_90, 0)\n            \"\"\"\n            if not isinstance(param, self._parent.MeasurementParameters):\n                raise AttributeError(\n                    \"Parameter must be selected from {}.\".format(\n                        self._parent.MeasurementParameters\n                    )\n                )\n\n            send_str = f\"PACU {self._idx},{param.value},{_source(src)}\"\n\n            self.sendcmd(send_str)\n\n        def sendcmd(self, cmd):\n            \"\"\"\n            Wraps commands sent from property factories in this class\n            with identifiers for the specified channel.\n\n            :param str cmd: Command to send to the instrument\n            \"\"\"\n            self._parent.sendcmd(cmd)\n\n        def query(self, cmd, size=-1):\n            \"\"\"\n            Executes the given query. Wraps commands sent from property\n            factories in this class with identifiers for the specified\n            channel.\n\n            :param str cmd: String containing the query to\n                execute.\n            :param int size: Number of bytes to be read. Default is read\n                until termination character is found.\n            :return: The result of the query as returned by the\n                connected instrument.\n            :rtype: `str`\n            \"\"\"\n            return self._parent.query(cmd, size=size)\n\n    # PROPERTIES #\n\n    @property\n    def channel(self):\n        \"\"\"\n        Gets an iterator or list for easy Pythonic access to the various\n        channel objects on the oscilloscope instrument.\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> inst = ik.teledyne.MAUI.open_visa(\"TCPIP0::192.168.0.10::INSTR\")\n            >>> channel = inst.channel[0]  # get first channel\n        \"\"\"\n        return ProxyList(self, self.Channel, range(self.number_channels))\n\n    @property\n    def math(self):\n        \"\"\"\n        Gets an iterator or list for easy Pythonic access to the various\n        math data sources objects on the oscilloscope instrument.\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> inst = ik.teledyne.MAUI.open_visa(\"TCPIP0::192.168.0.10::INSTR\")\n            >>> math = inst.math[0]  # get first math function\n        \"\"\"\n        return ProxyList(self, self.Math, range(self.number_functions))\n\n    @property\n    def measurement(self):\n        \"\"\"\n        Gets an iterator or list for easy Pythonic access to the various\n        measurement data sources objects on the oscilloscope instrument.\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> inst = ik.teledyne.MAUI.open_visa(\"TCPIP0::192.168.0.10::INSTR\")\n            >>> msr = inst.measurement[0]  # get first measurement parameter\n        \"\"\"\n        return ProxyList(self, self.Measurement, range(self.number_measurements))\n\n    @property\n    def ref(self):\n        raise NotImplementedError\n\n    # PROPERTIES\n\n    @property\n    def number_channels(self):\n        \"\"\"\n        Sets/Gets the number of channels available on the specific\n        oscilloscope. Defaults to 4.\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> inst = ik.teledyne.MAUI.open_visa(\"TCPIP0::192.168.0.10::INSTR\")\n            >>> inst.number_channel = 2  # for a oscilloscope with 2 channels\n            >>> inst.number_channel\n            2\n        \"\"\"\n        return self._number_channels\n\n    @number_channels.setter\n    def number_channels(self, newval):\n        self._number_channels = newval\n        # create new trigger source enum\n        self._create_trigger_source_enum()\n\n    @property\n    def number_functions(self):\n        \"\"\"\n        Sets/Gets the number of functions available on the specific\n        oscilloscope. Defaults to 2.\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> inst = ik.teledyne.MAUI.open_visa(\"TCPIP0::192.168.0.10::INSTR\")\n            >>> inst.number_functions = 4  # for a oscilloscope with 4 math functions\n            >>> inst.number_functions\n            4\n        \"\"\"\n        return self._number_functions\n\n    @number_functions.setter\n    def number_functions(self, newval):\n        self._number_functions = newval\n\n    @property\n    def number_measurements(self):\n        \"\"\"\n        Sets/Gets the number of measurements available on the specific\n        oscilloscope. Defaults to 6.\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> inst = ik.teledyne.MAUI.open_visa(\"TCPIP0::192.168.0.10::INSTR\")\n            >>> inst.number_measurements = 4  # for a oscilloscope with 4 measurements\n            >>> inst.number_measurements\n            4\n        \"\"\"\n        return self._number_measurements\n\n    @number_measurements.setter\n    def number_measurements(self, newval):\n        self._number_measurements = newval\n\n    @property\n    def self_test(self):\n        \"\"\"\n        Runs an oscilloscope's internal self test and returns the\n        result. The self-test includes testing the hardware of all\n        channels, the timebase and the trigger circuits.\n        Hardware failures are identified by a unique binary code in the\n        returned <status> number. A status of 0 indicates that no\n        failures occurred.\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> inst = ik.teledyne.MAUI.open_visa(\"TCPIP0::192.168.0.10::INSTR\")\n            >>> inst.self_test()\n        \"\"\"\n        # increase timeout x 10 to allow for enough time to test\n        self.timeout *= 10\n        retval = self.query(\"*TST?\")\n        self.timeout /= 10\n        return retval\n\n    @property\n    def show_id(self):\n        \"\"\"\n        Gets the scope information and returns it. The response\n        comprises manufacturer, oscilloscope model, serial number,\n        and firmware revision level.\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> inst = ik.teledyne.MAUI.open_visa(\"TCPIP0::192.168.0.10::INSTR\")\n            >>> inst.show_id()\n        \"\"\"\n        return self.query(\"*IDN?\")\n\n    @property\n    def show_options(self):\n        \"\"\"\n        Gets and returns oscilloscope options: installed software or\n        hardware that is additional to the standard instrument\n        configuration. The response consists of a series of response\n        fields listing all the installed options.\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> inst = ik.teledyne.MAUI.open_visa(\"TCPIP0::192.168.0.10::INSTR\")\n            >>> inst.show_options()\n        \"\"\"\n        return self.query(\"*OPT?\")\n\n    @property\n    def time_div(self):\n        \"\"\"\n        Sets/Gets the time per division, modifies the timebase setting.\n        Unitful.\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> inst = ik.teledyne.MAUI.open_visa(\"TCPIP0::192.168.0.10::INSTR\")\n            >>> inst.time_div = u.Quantity(200, u.ns)\n        \"\"\"\n        return u.Quantity(float(self.query(\"TDIV?\")), u.s)\n\n    @time_div.setter\n    def time_div(self, newval):\n        newval = assume_units(newval, \"s\").to(u.s).magnitude\n        self.sendcmd(f\"TDIV {newval}\")\n\n    # TRIGGER PROPERTIES\n\n    trigger_state = enum_property(\n        command=\"TRMD\",\n        enum=TriggerState,\n        doc=\"\"\"\n            Sets / Gets the trigger state. Valid values are are defined\n            in `TriggerState` enum class.\n\n            Example:\n                >>> import instruments as ik\n                >>> import instruments.units as u\n                >>> inst = ik.teledyne.MAUI.open_visa(\"TCPIP0::192.168.0.10::INSTR\")\n                >>> inst.trigger_state = inst.TriggerState.normal\n            \"\"\",\n    )\n\n    @property\n    def trigger_delay(self):\n        \"\"\"\n        Sets/Gets the trigger offset with respect to time zero (i.e.,\n        a horizontal shift). Unitful.\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> inst = ik.teledyne.MAUI.open_visa(\"TCPIP0::192.168.0.10::INSTR\")\n            >>> inst.trigger_delay = u.Quantity(60, u.ns)\n\n        \"\"\"\n        return u.Quantity(float(self.query(\"TRDL?\")), u.s)\n\n    @trigger_delay.setter\n    def trigger_delay(self, newval):\n        newval = assume_units(newval, \"s\").to(u.s).magnitude\n        self.sendcmd(f\"TRDL {newval}\")\n\n    @property\n    def trigger_source(self):\n        \"\"\"Sets / Gets the trigger source.\n\n        .. note:: The `TriggerSource` class is dynamically generated\n            when the number of channels is switched. The above shown class\n            is only the default! Channels are added and removed, as\n            required.\n\n        .. warning:: If a trigger type is currently set on the\n            oscilloscope that is not implemented in this class,\n            setting the source will fail. The oscilloscope is set up\n            such that the trigger type and source are set at the\n            same time. However, for convenience, these two properties\n            are split apart here.\n\n        :return: Trigger source.\n        :rtype: Member of `TriggerSource` class.\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> inst = ik.teledyne.MAUI.open_visa(\"TCPIP0::192.168.0.10::INSTR\")\n            >>> inst.trigger_source = inst.TriggerSource.ext  # external trigger\n        \"\"\"\n        retval = self.query(\"TRIG_SELECT?\").split(\",\")[2]\n        return self.TriggerSource(retval)\n\n    @trigger_source.setter\n    def trigger_source(self, newval):\n        curr_trig_typ = self.trigger_type\n        cmd = f\"TRIG_SELECT {curr_trig_typ.value},SR,{newval.value}\"\n        self.sendcmd(cmd)\n\n    @property\n    def trigger_type(self):\n        \"\"\"Sets / Gets the trigger type.\n\n        .. warning:: If a trigger source is currently set on the\n            oscilloscope that is not implemented in this class,\n            setting the source will fail. The oscilloscope is set up\n            such that the the trigger type and source are set at the\n            same time. However, for convenience, these two properties\n            are split apart here.\n\n        :return: Trigger type.\n        :rtype: Member of `TriggerType` enum class.\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> inst = ik.teledyne.MAUI.open_visa(\"TCPIP0::192.168.0.10::INSTR\")\n            >>> inst.trigger_type = inst.TriggerType.edge  # trigger on edge\n        \"\"\"\n        retval = self.query(\"TRIG_SELECT?\").split(\",\")[0]\n        return self.TriggerType(retval)\n\n    @trigger_type.setter\n    def trigger_type(self, newval):\n        curr_trig_src = self.trigger_source\n        cmd = f\"TRIG_SELECT {newval.value},SR,{curr_trig_src.value}\"\n        self.sendcmd(cmd)\n\n    # METHODS #\n\n    def clear_sweeps(self):\n        \"\"\"Clears the sweeps in a measurement.\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> inst = ik.teledyne.MAUI.open_visa(\"TCPIP0::192.168.0.10::INSTR\")\n            >>> inst.clear_sweeps()\n        \"\"\"\n        self.sendcmd(\"CLEAR_SWEEPS\")\n\n    def force_trigger(self):\n        \"\"\"Forces a trigger event to occur on the attached oscilloscope.\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> inst = ik.teledyne.MAUI.open_visa(\"TCPIP0::192.168.0.10::INSTR\")\n            >>> inst.force_trigger()\n        \"\"\"\n        self.sendcmd(\"ARM\")\n\n    def run(self):\n        \"\"\"Enables the trigger for the oscilloscope and sets it to auto.\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> inst = ik.teledyne.MAUI.open_visa(\"TCPIP0::192.168.0.10::INSTR\")\n            >>> inst.run()\n        \"\"\"\n        self.trigger_state = self.TriggerState.auto\n\n    def stop(self):\n        \"\"\"Disables the trigger for the oscilloscope.\n\n        Example:\n            >>> import instruments as ik\n            >>> import instruments.units as u\n            >>> inst = ik.teledyne.MAUI.open_visa(\"TCPIP0::192.168.0.10::INSTR\")\n            >>> inst.stop()\n        \"\"\"\n        self.sendcmd(\"STOP\")\n\n\n# STATICS #\n\n\ndef _source(src):\n    \"\"\"Stich the source together properly and return it.\"\"\"\n    if isinstance(src, int):\n        return f\"C{src + 1}\"\n    elif isinstance(src, tuple) and len(src) == 2:\n        return f\"{src[0].upper()}{int(src[1]) + 1}\"\n    else:\n        raise ValueError(\n            \"An invalid source was specified. \"\n            \"Source must be an integer or a tuple of \"\n            \"length 2.\"\n        )\n"
  },
  {
    "path": "src/instruments/thorlabs/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing Thorlabs instruments\n\"\"\"\n\nfrom .thorlabsapt import (\n    ThorLabsAPT,\n    APTPiezoInertiaActuator,\n    APTPiezoStage,\n    APTStrainGaugeReader,\n    APTMotorController,\n)\nfrom .pm100usb import PM100USB\nfrom .lcc25 import LCC25\nfrom .sc10 import SC10\nfrom .tc200 import TC200\n"
  },
  {
    "path": "src/instruments/thorlabs/_abstract.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nDefines a generic Thorlabs instrument to define some common functionality.\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport time\n\nfrom instruments.units import ureg as u\n\nfrom instruments.thorlabs import _packets\nfrom instruments.abstract_instruments.instrument import Instrument\nfrom instruments.util_fns import assume_units\n\n# CLASSES #####################################################################\n\n\nclass ThorLabsInstrument(Instrument):\n    \"\"\"\n    Generic class for ThorLabs instruments which require wrapping of\n    commands and queries in packets.\n    \"\"\"\n\n    def __init__(self, filelike):\n        super().__init__(filelike)\n        self.terminator = \"\"\n\n    def sendpacket(self, packet):\n        \"\"\"\n        Sends a packet to the connected APT instrument, and waits for a packet\n        in response. Optionally, checks whether the received packet type is\n        matches that the caller expects.\n\n        :param packet: The thorlabs data packet that will be queried\n        :type packet: `ThorLabsPacket`\n        \"\"\"\n        self._file.write_raw(packet.pack())\n\n    # pylint: disable=protected-access\n    def querypacket(self, packet, expect=None, timeout=None, expect_data_len=None):\n        \"\"\"\n        Sends a packet to the connected APT instrument, and waits for a packet\n        in response. Optionally, checks whether the received packet type is\n        matches that the caller expects.\n\n        :param packet: The thorlabs data packet that will be queried\n        :type packet: `ThorLabsPacket`\n\n        :param expect: The expected message id from the response. If an\n            an incorrect id is received then an `IOError` is raised. If left\n            with the default value of `None` then no checking occurs.\n        :type expect: `str` or `None`\n\n        :param timeout: Sets a timeout to wait before returning `None`, indicating\n            no packet was received. If the timeout is set to `None`, then the\n            timeout is inherited from the underlying communicator and no additional\n            timeout is added. If timeout is set to `False`, then this method waits\n            indefinitely. If timeout is set to a unitful quantity, then it is interpreted\n            as a time and used as the timeout value. Finally, if the timeout is a unitless\n            number (e.g. `float` or `int`), then seconds are assumed.\n\n        :param int expect_data_len: Number of bytes to expect as the\n            data for the returned packet.\n\n        :return: Returns the response back from the instrument wrapped up in\n            a ThorLabs APT packet, or None if no packet was received.\n        :rtype: `ThorLabsPacket`\n        \"\"\"\n        t_start = time.time()\n\n        if timeout is not None:\n            timeout = assume_units(timeout, u.second).to(\"second\").magnitude\n\n        while True:\n            self._file.write_raw(packet.pack())\n            resp = self._file.read_raw(\n                expect_data_len + 6  # the header is six bytes.\n                if expect_data_len\n                else 6\n            )\n            if resp or timeout is None:\n                break\n            else:\n                tic = time.time()\n                if tic - t_start > timeout:\n                    break\n\n        if not resp:\n            if expect is None:\n                return None\n            else:\n                raise OSError(f\"Expected packet {expect}, got nothing instead.\")\n        pkt = _packets.ThorLabsPacket.unpack(resp)\n        if expect is not None and pkt._message_id != expect:\n            # TODO: make specialized subclass that can record the offending\n            #       packet.\n            raise OSError(\n                \"APT returned message ID {}, expected {}\".format(\n                    pkt._message_id, expect\n                )\n            )\n\n        return pkt\n"
  },
  {
    "path": "src/instruments/thorlabs/_cmds.py",
    "content": "#!/usr/bin/python\n\"\"\"\nContains command mneonics for the ThorLabs APT protocol\n\nClass originally contributed by Catherine Holloway.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom enum import IntEnum\n\n# CLASSES #####################################################################\n\n\nclass ThorLabsCommands(IntEnum):\n    \"\"\"\n    Enum containing command mneonics for the ThorLabs APT protocol\n    \"\"\"\n\n    # General System Commands\n    MOD_IDENTIFY = 0x0223\n    MOD_SET_CHANENABLESTATE = 0x0210\n    MOD_REQ_CHANENABLESTATE = 0x0211\n    MOD_GET_CHANENABLESTATE = 0x0212\n    HW_DISCONNECT = 0x0002\n    HW_RESPONSE = 0x0080\n    HW_RICHRESPONSE = 0x0081\n    HW_START_UPDATEMSGS = 0x0011\n    HW_STOP_UPDATEMSGS = 0x0012\n    HW_REQ_INFO = 0x0005\n    HW_GET_INFO = 0x0006\n    RACK_REQ_BAYUSED = 0x0060\n    RACK_GET_BAYUSED = 0x0061\n    HUB_REQ_BAYUSED = 0x0065\n    HUB_GET_BAYUSED = 0x0066\n    RACK_REQ_STATUSBITS = 0x0226\n    RACK_GET_STATUSBITS = 0x0227\n    RACK_SET_DIGOUTPUTS = 0x0228\n    RACK_REQ_DIGOUTPUTS = 0x0229\n    RACK_GET_DIGOUTPUTS = 0x0230\n    MOD_SET_DIGOUTPUTS = 0x0213\n    MOD_REQ_DIGOUTPUTS = 0x0214\n    MOD_GET_DIGOUTPUTS = 0x0215\n\n    # Motor Control Messages\n    MOT_SET_POSCOUNTER = 0x0410\n    MOT_REQ_POSCOUNTER = 0x0411\n    MOT_GET_POSCOUNTER = 0x0412\n    MOT_SET_ENCCOUNTER = 0x0409\n    MOT_REQ_ENCCOUNTER = 0x040A\n    MOT_GET_ENCCOUNTER = 0x040B\n    MOT_SET_VELPARAMS = 0x0413\n    MOT_REQ_VELPARAMS = 0x0414\n    MOT_GET_VELPARAMS = 0x0415\n    MOT_SET_JOGPARAMS = 0x0416\n    MOT_REQ_JOGPARAMS = 0x0417\n    MOT_GET_JOGPARAMS = 0x0418\n    MOT_REQ_ADCINPUTS = 0x042B\n    MOT_GET_ADCINPUTS = 0x042C\n    MOT_SET_POWERPARAMS = 0x0426\n    MOT_REQ_POWERPARAMS = 0x0427\n    MOT_GET_POWERPARAMS = 0x0428\n    MOT_SET_GENMOVEPARAMS = 0x043A\n    MOT_REQ_GENMOVEPARAMS = 0x043B\n    MOT_GET_GENMOVEPARAMS = 0x043C\n    MOT_SET_MOVERELPARAMS = 0x0445\n    MOT_REQ_MOVERELPARAMS = 0x0446\n    MOT_GET_MOVERELPARAMS = 0x0447\n    MOT_SET_MOVEABSPARAMS = 0x0450\n    MOT_REQ_MOVEABSPARAMS = 0x0451\n    MOT_GET_MOVEABSPARAMS = 0x0452\n    MOT_SET_HOMEPARAMS = 0x0440\n    MOT_REQ_HOMEPARAMS = 0x0441\n    MOT_GET_HOMEPARAMS = 0x0442\n    MOT_SET_LIMSWITCHPARAMS = 0x0423\n    MOT_REQ_LIMSWITCHPARAMS = 0x0424\n    MOT_GET_LIMSWITCHPARAMS = 0x0425\n    MOT_MOVE_HOME = 0x0443\n    MOT_MOVE_HOMED = 0x0444\n    MOT_MOVE_RELATIVE = 0x0448\n    MOT_MOVE_COMPLETED = 0x0464\n    MOT_MOVE_ABSOLUTE = 0x0453\n    MOT_MOVE_JOG = 0x046A\n    MOT_MOVE_VELOCITY = 0x0457\n    MOT_MOVE_STOP = 0x0465\n    MOT_MOVE_STOPPED = 0x0466\n    MOT_SET_DCPIDPARAMS = 0x04A0\n    MOT_REQ_DCPIDPARAMS = 0x04A1\n    MOT_GET_DCPIDPARAMS = 0x04A2\n    MOT_SET_AVMODES = 0x04B3\n    MOT_REQ_AVMODES = 0x04B4\n    MOT_GET_AVMODES = 0x04B5\n    MOT_SET_POTPARAMS = 0x04B0\n    MOT_REQ_POTPARAMS = 0x04B1\n    MOT_GET_POTPARAMS = 0x04B2\n    MOT_SET_BUTTONPARAMS = 0x04B6\n    MOT_REQ_BUTTONPARAMS = 0x04B7\n    MOT_GET_BUTTONPARAMS = 0x04B8\n    MOT_SET_EEPROMPARAMS = 0x04B9\n    MOT_SET_PMDPOSITIONLOOPPARAMS = 0x04D7\n    MOT_REQ_PMDPOSITIONLOOPPARAMS = 0x04D8\n    MOT_GET_PMDPOSITIONLOOPPARAMS = 0x04D9\n    MOT_SET_PMDMOTOROUTPUTPARAMS = 0x04DA\n    MOT_REQ_PMDMOTOROUTPUTPARAMS = 0x04DB\n    MOT_GET_PMDMOTOROUTPUTPARAMS = 0x04DC\n    MOT_SET_PMDTRACKSETTLEPARAMS = 0x04E0\n    MOT_REQ_PMDTRACKSETTLEPARAMS = 0x04E1\n    MOT_GET_PMDTRACKSETTLEPARAMS = 0x04E2\n    MOT_SET_PMDPROFILEMODEPARAMS = 0x04E3\n    MOT_REQ_PMDPROFILEMODEPARAMS = 0x04E4\n    MOT_GET_PMDPROFILEMODEPARAMS = 0x04E5\n    MOT_SET_PMDJOYSTICKPARAMS = 0x04E6\n    MOT_REQ_PMDJOYSTICKPARAMS = 0x04E7\n    MOT_GET_PMDJOYSTICKPARAMS = 0x04E8\n    MOT_SET_PMDCURRENTLOOPPARAMS = 0x04D4\n    MOT_REQ_PMDCURRENTLOOPPARAMS = 0x04D5\n    MOT_GET_PMDCURRENTLOOPPARAMS = 0x04D6\n    MOT_SET_PMDSETTLEDCURRENTLOOPPARAMS = 0x04E9\n    MOT_REQ_PMDSETTLEDCURRENTLOOPPARAMS = 0x04EA\n    MOT_GET_PMDSETTLEDCURRENTLOOPPARAMS = 0x04EB\n    MOT_SET_PMDSTAGEAXISPARAMS = 0x04F0\n    MOT_REQ_PMDSTAGEAXISPARAMS = 0x04F1\n    MOT_GET_PMDSTAGEAXISPARAMS = 0x04F2\n    MOT_GET_STATUSUPDATE = 0x0481\n    MOT_REQ_STATUSUPDATE = 0x0480\n    MOT_GET_DCSTATUSUPDATE = 0x0491\n    MOT_REQ_DCSTATUSUPDATE = 0x0490\n    MOT_ACK_DCSTATUSUPDATE = 0x0492\n    MOT_REQ_STATUSBITS = 0x0429\n    MOT_GET_STATUSBITS = 0x042A\n    MOT_SUSPEND_ENDOFMOVEMSGS = 0x046B\n    MOT_RESUME_ENDOFMOVEMSGS = 0x046C\n    MOT_SET_TRIGGER = 0x0500\n    MOT_REQ_TRIGGER = 0x0501\n    MOT_GET_TRIGGER = 0x0502\n\n    # Solenoid Control Messages\n    MOT_SET_SOL_OPERATINGMODE = 0x04C0\n    MOT_REQ_SOL_OPERATINGMODE = 0x04C1\n    MOT_GET_SOL_OPERATINGMODE = 0x04C2\n    MOT_SET_SOL_CYCLEPARAMS = 0x04C3\n    MOT_REQ_SOL_CYCLEPARAMS = 0x04C4\n    MOT_GET_SOL_CYCLEPARAMS = 0x04C5\n    MOT_SET_SOL_INTERLOCKMODE = 0x04C6\n    MOT_REQ_SOL_INTERLOCKMODE = 0x04C7\n    MOT_GET_SOL_INTERLOCKMODE = 0x04C8\n    MOT_SET_SOL_STATE = 0x04CB\n    MOT_REQ_SOL_STATE = 0x04CC\n    MOT_GET_SOL_STATE = 0x04CD\n\n    # Piezo Control Messages\n    PZ_SET_POSCONTROLMODE = 0x0640\n    PZ_REQ_POSCONTROLMODE = 0x0641\n    PZ_GET_POSCONTROLMODE = 0x0642\n    PZ_SET_OUTPUTVOLTS = 0x0643\n    PZ_REQ_OUTPUTVOLTS = 0x0644\n    PZ_GET_OUTPUTVOLTS = 0x0645\n    PZ_SET_OUTPUTPOS = 0x0646\n    PZ_REQ_OUTPUTPOS = 0x0647\n    PZ_GET_OUTPUTPOS = 0x0648\n    PZ_SET_INPUTVOLTSSRC = 0x0652\n    PZ_REQ_INPUTVOLTSSRC = 0x0653\n    PZ_GET_INPUTVOLTSSRC = 0x0654\n    PZ_SET_PICONSTS = 0x0655\n    PZ_REQ_PICONSTS = 0x0656\n    PZ_GET_PICONSTS = 0x0657\n    PZ_REQ_PZSTATUSBITS = 0x065B\n    PZ_GET_PZSTATUSBITS = 0x065C\n    PZ_GET_PZSTATUSUPDATE = 0x0661\n    PZ_ACK_PZSTATUSUPDATE = 0x0662\n    PZ_SET_OUTPUTLUT = 0x0700\n    PZ_REQ_OUTPUTLUT = 0x0701\n    PZ_GET_OUTPUTLUT = 0x0702\n    PZ_SET_OUTPUTLUTPARAMS = 0x0703\n    PZ_REQ_OUTPUTLUTPARAMS = 0x0704\n    PZ_GET_OUTPUTLUTPARAMS = 0x0705\n    PZ_START_LUTOUTPUT = 0x0706\n    PZ_STOP_LUTOUTPUT = 0x0707\n    PZ_SET_EEPROMPARAMS = 0x07D0\n    PZ_SET_TPZ_DISPSETTINGS = 0x07D1\n    PZ_REQ_TPZ_DISPSETTINGS = 0x07D2\n    PZ_GET_TPZ_DISPSETTINGS = 0x07D3\n    PZ_SET_TPZ_IOSETTINGS = 0x07D4\n    PZ_REQ_TPZ_IOSETTINGS = 0x07D5\n    PZ_GET_TPZ_IOSETTINGS = 0x07D6\n    PZ_SET_ZERO = 0x0658\n    PZ_REQ_MAXTRAVEL = 0x0650\n    PZ_GET_MAXTRAVEL = 0x0651\n    PZ_SET_IOSETTINGS = 0x0670\n    PZ_REQ_IOSETTINGS = 0x0671\n    PZ_GET_IOSETTINGS = 0x06723\n    PZ_SET_OUTPUTMAXVOLTS = 0x0680\n    PZ_REQ_OUTPUTMAXVOLTS = 0x0681\n    PZ_GET_OUTPUTMAXVOLTS = 0x0682\n    PZ_SET_TPZ_SLEWRATES = 0x0683\n    PZ_REQ_TPZ_SLEWRATES = 0x0684\n    PZ_GET_TPZ_SLEWRATES = 0x068\n    MOT_SET_PZSTAGEPARAMDEFAULTS = 0x0686\n    PZ_SET_LUTVALUETYPE = 0x0708\n    PZ_SET_TSG_IOSETTINGS = 0x07DA\n    PZ_REQ_TSG_IOSETTINGS = 0x07DB\n    PZ_GET_TSG_IOSETTINGS = 0x07DC\n    PZ_REQ_TSG_READING = 0x07DD\n    PZ_GET_TSG_READING = 0x07DE\n\n    # NanoTrak Control Messages\n    PZ_SET_NTMODE = 0x0603\n    PZ_REQ_NTMODE = 0x0604\n    PZ_GET_NTMODE = 0x0605\n    PZ_SET_NTTRACKTHRESHOLD = 0x0606\n    PZ_REQ_NTTRACKTHRESHOLD = 0x0607\n    PZ_GET_NTTRACKTHRESHOLD = 0x0608\n    PZ_SET_NTCIRCHOMEPOS = 0x0609\n    PZ_REQ_NTCIRCHOMEPOS = 0x0610\n    PZ_GET_NTCIRCHOMEPOS = 0x0611\n    PZ_MOVE_NTCIRCTOHOMEPOS = 0x0612\n    PZ_REQ_NTCIRCCENTREPOS = 0x0613\n    PZ_GET_NTCIRCCENTREPOS = 0x0614\n    PZ_SET_NTCIRCPARAMS = 0x0618\n    PZ_REQ_NTCIRCPARAMS = 0x0619\n    PZ_GET_NTCIRCPARAMS = 0x0620\n    PZ_SET_NTCIRCDIA = 0x061A\n    PZ_SET_NTCIRCDIALUT = 0x0621\n    PZ_REQ_NTCIRCDIALUT = 0x0622\n    PZ_GET_NTCIRCDIALUT = 0x0623\n    PZ_SET_NTPHASECOMPPARAMS = 0x0626\n    PZ_REQ_NTPHASECOMPPARAMS = 0x0627\n    PZ_GET_NTPHASECOMPPARAMS = 0x0628\n    PZ_SET_NTTIARANGEPARAMS = 0x0630\n    PZ_REQ_NTTIARANGEPARAMS = 0x0631\n    PZ_GET_NTTIARANGEPARAMS = 0x0632\n    PZ_SET_NTGAINPARAMS = 0x0633\n    PZ_REQ_NTGAINPARAMS = 0x0634\n    PZ_GET_NTGAINPARAMS = 0x0635\n    PZ_SET_NTTIALPFILTERPARAMS = 0x0636\n    PZ_REQ_NTTIALPFILTERPARAMS = 0x0637\n    PZ_GET_NTTIALPFILTERPARAMS = 0x0638\n    PZ_REQ_NTTIAREADING = 0x0639\n    PZ_GET_NTTIAREADING = 0x063A\n    PZ_SET_NTFEEDBACKSRC = 0x063B\n    PZ_REQ_NTFEEDBACKSRC = 0x063C\n    PZ_GET_NTFEEDBACKSRC = 0x063D\n    PZ_REQ_NTSTATUSBITS = 0x063E\n    PZ_GET_NTSTATUSBITS = 0x063F\n    PZ_REQ_NTSTATUSUPDATE = 0x0664\n    PZ_GET_NTSTATUSUPDATE = 0x0665\n    PZ_ACK_NTSTATUSUPDATE = 0x0666\n    NT_SET_EEPROMPARAMS = 0x07E7\n    NT_SET_TNA_DISPSETTINGS = 0x07E8\n    NT_REQ_TNA_DISPSETTINGS = 0x07E9\n    NT_GET_TNA_DISPSETTINGS = 0x07EA\n    NT_SET_TNAIOSETTINGS = 0x07EB\n    NT_REQ_TNAIOSETTINGS = 0x07EC\n    NT_GET_TNAIOSETTINGS = 0x07ED\n\n    # Laser Control Messages  181\n    LA_SET_PARAMS = 0x0800\n    LA_REQ_PARAMS = 0x0801\n    LA_GET_PARAMS = 0x0802\n    LA_ENABLEOUTPUT = 0x0811\n    LA_DISABLEOUTPUT = 0x0812\n    LA_REQ_STATUSUPDATE = 0x0820\n    LA_GET_STATUSUPDATE = 0x0821\n    LA_ACK_STATUSUPDATE = 0x0822\n\n    # Additional messages for TIM101 and KIM101\n    PZMOT_SET_PARAMS = 0x08C0\n    PZMOT_REQ_PARAMS = 0x08C1\n    PZMOT_GET_PARAMS = 0x08C2\n    PZMOT_MOVE_ABSOLUTE = 0x08D4\n    PZMOT_MOVE_COMPLETED = 0x08D6\n    PZMOT_MOVE_JOG = 0x08D9\n    PZMOT_GET_STATUSUPDATE = 0x08E1\n"
  },
  {
    "path": "src/instruments/thorlabs/_packets.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule for working with ThorLabs packets.\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport struct\n\n# STRUCTS #####################################################################\n\nmessage_header_nopacket = struct.Struct(\"<HBBBB\")\nmessage_header_wpacket = struct.Struct(\"<HHBB\")\nhw_info_data = struct.Struct(\n    \"<\"  # Declare endianness.\n    \"4s\"  # serial_number\n    \"8s\"  # model_number\n    \"H\"  # hw_type_int\n    \"BBBx\"  # fw_version\n    \"48s\"  # notes\n    \"12x\"  # padding\n    \"H\"  # hw_version\n    \"H\"  # mod_state\n    \"H\"  # n_channels\n)\n\n# CLASSES #####################################################################\n\n\nclass ThorLabsPacket:\n    \"\"\"\n    This class is used to wrap data to-/from- the instrument. Because of the\n    command protocol for some ThorLabs instruments, this helps get all the\n    data formatted and organized correctly.\n    \"\"\"\n\n    # pylint: disable=too-many-arguments\n    def __init__(\n        self, message_id, param1=None, param2=None, dest=0x50, source=0x01, data=None\n    ):\n        if param1 is not None or param2 is not None:\n            has_data = False\n        elif data is not None:\n            has_data = True\n        else:\n            raise ValueError(\"Must specify either parameters or data.\")\n\n        if not has_data and (data is not None):\n            raise ValueError(\n                \"A ThorLabs packet can either have parameters \" \"or data, but not both.\"\n            )\n\n        self._message_id = message_id\n        self._param1 = param1\n        self._param2 = param2\n        self._data = data\n        self._has_data = has_data\n        self._dest = dest\n        self._source = source\n\n    def __str__(self):\n        return \"\"\"\nThorLabs APT packet:\n    Message ID      0x{0._message_id:x}\n    Parameter 1     {1}\n    Parameter 2     {2}\n    Destination     0x{0._dest:x}\n    Source          0x{0._source:x}\n    Data            {3}\n\"\"\".format(\n            self,\n            f\"0x{self._param1:x}\" if not self._has_data else \"None\",\n            f\"0x{self._param2:x}\" if not self._has_data else \"None\",\n            f\"{self._data}\" if self._has_data else \"None\",\n        )\n\n    @property\n    def message_id(self):\n        \"\"\"\n        Gets/sets the message ID for the packet\n\n        :type: `str`\n        \"\"\"\n        return self._message_id\n\n    @message_id.setter\n    def message_id(self, newval):\n        self._message_id = newval\n\n    @property\n    def parameters(self):\n        \"\"\"\n        Gets/sets both parameters for the packet\n\n        :type: `tuple`\n        \"\"\"\n        return self._param1, self._param2\n\n    @parameters.setter\n    def parameters(self, newval):\n        self._param1, self._param2 = newval\n\n    @property\n    def destination(self):\n        \"\"\"\n        Gets/sets the destination for the packet\n\n        :type: `str`\n        \"\"\"\n        return self._dest\n\n    @destination.setter\n    def destination(self, newval):\n        self._dest = newval\n\n    @property\n    def source(self):\n        \"\"\"\n        Gets/sets the source for the packet\n\n        :type: `str`\n        \"\"\"\n        return self._source\n\n    @source.setter\n    def source(self, newval):\n        self._source = newval\n\n    @property\n    def data(self):\n        \"\"\"\n        Gets/sets the data for the packet\n\n        :type: `str`\n        \"\"\"\n        return self._data\n\n    @data.setter\n    def data(self, newval):\n        self._data = newval\n\n    def pack(self):\n        \"\"\"\n        Pack this `ThorLabsPacket` object into the byte string that can then\n        be sent to the instrument.\n        \"\"\"\n        if self._has_data:\n            return (\n                message_header_wpacket.pack(\n                    self._message_id, len(self._data), 0x80 | self._dest, self._source\n                )\n                + self._data\n            )\n\n        return message_header_nopacket.pack(\n            self._message_id, self._param1, self._param2, self._dest, self._source\n        )\n\n    @classmethod\n    def unpack(cls, bytes):\n        \"\"\"\n        Classmethod used to unpack the response from an instrument into\n        a new `ThorLabsPacket` object.\n\n        :param bytes: Data from the instrument that will be unpacked into\n            the packet object\n        :type bytes: `str`\n\n        :return: The unpacked data in a new packet object\n        :rtype: `ThorLabsPacket`\n        \"\"\"\n        if not bytes:\n            raise ValueError(\"Expected a packet, got an empty string instead.\")\n        if len(bytes) < 6:\n            raise ValueError(\"Packet must be at least 6 bytes long.\")\n\n        header = bytes[:6]\n\n        # Check if 0x80 is set on header byte 4. If so, then this packet\n        # has data.\n        if struct.unpack(\"B\", header[4:5])[0] & 0x80:\n            msg_id, length, dest, source = message_header_wpacket.unpack(header)\n            dest = dest ^ 0x80  # Turn off 0x80.\n            param1 = None\n            param2 = None\n            data = bytes[6 : 6 + length]  # Count on this to raise an index\n            # error if the length doesn't match\n            # up.\n        else:\n            msg_id, param1, param2, dest, source = message_header_nopacket.unpack(\n                header\n            )\n            data = None\n\n        return cls(\n            message_id=msg_id,\n            param1=param1,\n            param2=param2,\n            data=data,\n            dest=dest,\n            source=source,\n        )\n"
  },
  {
    "path": "src/instruments/thorlabs/lcc25.py",
    "content": "#!/usr/bin/python\n\"\"\"\nProvides the support for the Thorlabs LCC25 liquid crystal controller.\n\nClass originally contributed by Catherine Holloway.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom enum import IntEnum\n\nfrom instruments.thorlabs.thorlabs_utils import check_cmd\n\nfrom instruments.abstract_instruments import Instrument\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import enum_property, bool_property, unitful_property\n\n# CLASSES #####################################################################\n\n\nclass LCC25(Instrument):\n    \"\"\"\n    The LCC25 is a controller for the thorlabs liquid crystal modules.\n    it can set two voltages and then oscillate between them at a specific\n    repetition rate.\n\n    The user manual can be found here:\n    http://www.thorlabs.com/thorcat/18800/LCC25-Manual.pdf\n    \"\"\"\n\n    def __init__(self, filelike):\n        super().__init__(filelike)\n        self.terminator = \"\\r\"\n        self.prompt = \"> \"\n\n    def _ack_expected(self, msg=\"\"):\n        return msg\n\n    # ENUMS #\n\n    class Mode(IntEnum):\n        \"\"\"\n        Enum containing valid output modes of the LCC25\n        \"\"\"\n\n        normal = 0\n        voltage1 = 1\n        voltage2 = 2\n\n    # PROPERTIES #\n\n    @property\n    def name(self):\n        \"\"\"\n        Gets the name and version number of the device\n\n        :rtype: `str`\n        \"\"\"\n        return self.query(\"*idn?\")\n\n    frequency = unitful_property(\n        \"freq\",\n        u.Hz,\n        format_code=\"{:.1f}\",\n        set_fmt=\"{}={}\",\n        valid_range=(5, 150),\n        doc=\"\"\"\n        Gets/sets the frequency at which the LCC oscillates between the\n        two voltages.\n\n        :units: As specified (if a `~pint.Quantity`) or assumed\n            to be of units Hertz.\n        :rtype: `~pint.Quantity`\n        \"\"\",\n    )\n\n    mode = enum_property(\n        \"mode\",\n        Mode,\n        input_decoration=int,\n        set_fmt=\"{}={}\",\n        doc=\"\"\"\n        Gets/sets the output mode of the LCC25\n\n        :rtype: `LCC25.Mode`\n        \"\"\",\n    )\n\n    enable = bool_property(\n        \"enable\",\n        inst_true=\"1\",\n        inst_false=\"0\",\n        set_fmt=\"{}={}\",\n        doc=\"\"\"\n        Gets/sets the output enable status.\n\n        If output enable is on (`True`), there is a voltage on the output.\n\n        :rtype: `bool`\n        \"\"\",\n    )\n\n    extern = bool_property(\n        \"extern\",\n        inst_true=\"1\",\n        inst_false=\"0\",\n        set_fmt=\"{}={}\",\n        doc=\"\"\"\n        Gets/sets the use of the external TTL modulation.\n\n        Value is `True` for external TTL modulation and `False` for internal\n        modulation.\n\n        :rtype: `bool`\n        \"\"\",\n    )\n\n    remote = bool_property(\n        \"remote\",\n        inst_true=\"1\",\n        inst_false=\"0\",\n        set_fmt=\"{}={}\",\n        doc=\"\"\"\n        Gets/sets front panel lockout status for remote instrument operation.\n\n        Value is `False` for normal operation and `True` to lock out the front\n        panel buttons.\n\n        :rtype: `bool`\n        \"\"\",\n    )\n\n    voltage1 = unitful_property(\n        \"volt1\",\n        u.V,\n        format_code=\"{:.3f}\",\n        set_fmt=\"{}={}\",\n        valid_range=(0, 25),\n        doc=\"\"\"\n        Gets/sets the voltage value for output 1.\n\n        :units: As specified (if a `~pint.Quantity`) or\n            assumed to be of units Volts.\n        :rtype: `~pint.Quantity`\n        \"\"\",\n    )\n\n    voltage2 = unitful_property(\n        \"volt2\",\n        u.V,\n        format_code=\"{:.3f}\",\n        set_fmt=\"{}={}\",\n        valid_range=(0, 25),\n        doc=\"\"\"\n        Gets/sets the voltage value for output 2.\n\n        :units: As specified (if a `~pint.Quantity`) or\n            assumed to be of units Volts.\n        :rtype: `~pint.Quantity`\n        \"\"\",\n    )\n\n    min_voltage = unitful_property(\n        \"min\",\n        u.V,\n        format_code=\"{:.3f}\",\n        set_fmt=\"{}={}\",\n        valid_range=(0, 25),\n        doc=\"\"\"\n        Gets/sets the minimum voltage value for the test mode.\n\n        :units: As specified (if a `~pint.Quantity`) or assumed\n            to be of units Volts.\n        :rtype: `~pint.Quantity`\n        \"\"\",\n    )\n\n    max_voltage = unitful_property(\n        \"max\",\n        u.V,\n        format_code=\"{:.3f}\",\n        set_fmt=\"{}={}\",\n        valid_range=(0, 25),\n        doc=\"\"\"\n        Gets/sets the maximum voltage value for the test mode. If the maximum\n        voltage is less than the minimum voltage, nothing happens.\n\n        :units: As specified (if a `~pint.Quantity`) or assumed\n            to be of units Volts.\n        :rtype: `~pint.Quantity`\n        \"\"\",\n    )\n\n    dwell = unitful_property(\n        \"dwell\",\n        units=u.ms,\n        format_code=\"{:n}\",\n        set_fmt=\"{}={}\",\n        valid_range=(0, None),\n        doc=\"\"\"\n        Gets/sets the dwell time for voltages for the test mode.\n\n        :units: As specified (if a `~pint.Quantity`) or assumed\n            to be of units milliseconds.\n        :rtype: `~pint.Quantity`\n        \"\"\",\n    )\n\n    increment = unitful_property(\n        \"increment\",\n        units=u.V,\n        format_code=\"{:.3f}\",\n        set_fmt=\"{}={}\",\n        valid_range=(0, None),\n        doc=\"\"\"\n        Gets/sets the voltage increment for voltages for the test mode.\n\n        :units: As specified (if a `~pint.Quantity`) or assumed\n            to be of units Volts.\n        :rtype: `~pint.Quantity`\n        \"\"\",\n    )\n\n    # METHODS #\n\n    def default(self):\n        \"\"\"\n        Restores instrument to factory settings.\n\n        Returns 1 if successful, 0 otherwise\n\n        :rtype: `int`\n        \"\"\"\n        response = self.query(\"default\")\n        return check_cmd(response)\n\n    def save(self):\n        \"\"\"\n        Stores the parameters in static memory\n\n        Returns 1 if successful, zero otherwise.\n\n        :rtype: `int`\n        \"\"\"\n        response = self.query(\"save\")\n        return check_cmd(response)\n\n    def set_settings(self, slot):\n        \"\"\"\n        Saves the current settings to memory.\n\n        Returns 1 if successful, zero otherwise.\n\n        :param slot: Memory slot to use, valid range `[1,4]`\n        :type slot: `int`\n        :rtype: `int`\n        \"\"\"\n        if slot not in range(1, 5):\n            raise ValueError(\"Cannot set memory out of `[1,4]` range\")\n        response = self.query(f\"set={slot}\")\n        return check_cmd(response)\n\n    def get_settings(self, slot):\n        \"\"\"\n        Gets the current settings to memory.\n\n        Returns 1 if successful, zero otherwise.\n\n        :param slot: Memory slot to use, valid range `[1,4]`\n        :type slot: `int`\n        :rtype: `int`\n        \"\"\"\n        if slot not in range(1, 5):\n            raise ValueError(\"Cannot set memory out of `[1,4]` range\")\n        response = self.query(f\"get={slot}\")\n        return check_cmd(response)\n\n    def test_mode(self):\n        \"\"\"\n        Puts the LCC in test mode - meaning it will increment the output\n        voltage from the minimum value to the maximum value, in increments,\n        waiting for the dwell time\n\n        Returns 1 if successful, zero otherwise.\n\n        :rtype: `int`\n        \"\"\"\n        response = self.query(\"test\")\n        return check_cmd(response)\n"
  },
  {
    "path": "src/instruments/thorlabs/pm100usb.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides the support for the Thorlabs PM100USB power meter.\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport logging\nfrom collections import defaultdict, namedtuple\n\nfrom enum import Enum, IntEnum\n\nfrom instruments.units import ureg as u\n\nfrom instruments.generic_scpi import SCPIInstrument\nfrom instruments.util_fns import enum_property\n\n# LOGGING #####################################################################\n\nlogger = logging.getLogger(__name__)\nlogger.addHandler(logging.NullHandler())\n\n# CLASSES #####################################################################\n\n\nclass PM100USB(SCPIInstrument):\n    \"\"\"\n    Instrument class for the `ThorLabs PM100USB`_ power meter.\n    Note that as this is an SCPI-compliant instrument, the properties and\n    methods of :class:`~instruments.generic_scpi.SCPIInstrument` may be used\n    as well.\n\n    .. _ThorLabs PM100USB: http://www.thorlabs.com/thorproduct.cfm?partnumber=PM100USB\n    \"\"\"\n\n    # ENUMS #\n\n    class SensorFlags(IntEnum):\n        \"\"\"\n        Enum containing valid sensor flags for the PM100USB\n        \"\"\"\n\n        is_power_sensor = 1\n        is_energy_sensor = 2\n        response_settable = 16\n        wavelength_settable = 32\n        tau_settable = 64\n        has_temperature_sensor = 256\n\n    class MeasurementConfiguration(Enum):\n        \"\"\"\n        Enum containing valid measurement modes for the PM100USB\n        \"\"\"\n\n        current = \"CURR\"\n        power = \"POW\"\n        voltage = \"VOLT\"\n        energy = \"ENER\"\n        frequency = \"FREQ\"\n        power_density = \"PDEN\"\n        energy_density = \"EDEN\"\n        resistance = \"RES\"\n        temperature = \"TEMP\"\n\n    # We will cheat and also represent things by a named tuple over bools.\n    # TODO: make a flagtuple into a new type in util_fns, copying this out\n    #       as a starting point.\n    _SensorFlags = namedtuple(\n        \"SensorFlags\",\n        [flag.name for flag in SensorFlags],  # pylint: disable=not-an-iterable\n    )\n\n    # INNER CLASSES #\n\n    class Sensor:\n        \"\"\"\n        Class representing a sensor on the ThorLabs PM100USB\n\n        .. warning:: This class should NOT be manually created by the user. It\n            is designed to be initialized by the `PM100USB` class.\n        \"\"\"\n\n        def __init__(self, parent):\n            self._parent = parent\n\n            # Pull details about the sensor from SYST:SENSOR:IDN?\n            sensor_idn = parent.query(\"SYST:SENSOR:IDN?\")\n            (\n                self._name,\n                self._serial_number,\n                self._calibration_message,\n                self._sensor_type,\n                self._sensor_subtype,\n                self._flags,\n            ) = sensor_idn.split(\",\")\n\n            # Normalize things to enums as appropriate.\n            # We want flags to be a named tuple over bools.\n            # pylint: disable=protected-access\n            self._flags = parent._SensorFlags(\n                **{\n                    e.name: bool(e & int(self._flags))\n                    for e in PM100USB.SensorFlags  # pylint: disable=not-an-iterable\n                }\n            )\n\n        @property\n        def name(self):\n            \"\"\"\n            Gets the name associated with the sensor channel\n\n            :type: `str`\n            \"\"\"\n            return self._name\n\n        @property\n        def serial_number(self):\n            \"\"\"\n            Gets the serial number of the sensor channel\n\n            :type: `str`\n            \"\"\"\n            return self._serial_number\n\n        @property\n        def calibration_message(self):\n            \"\"\"\n            Gets the calibration message of the sensor channel\n\n            :type: `str`\n            \"\"\"\n            return self._calibration_message\n\n        @property\n        def type(self):\n            \"\"\"\n            Gets the sensor type of the sensor channel\n\n            :type: `str`\n            \"\"\"\n            return self._sensor_type, self._sensor_subtype\n\n        @property\n        def flags(self):\n            \"\"\"\n            Gets any sensor flags set on the sensor channel\n\n            :type: `collections.namedtuple`\n            \"\"\"\n            return self._flags\n\n    # PRIVATE ATTRIBUTES #\n\n    _cache_units = False\n\n    # UNIT CACHING #\n\n    @property\n    def cache_units(self):\n        \"\"\"\n        If enabled, then units are not checked every time a measurement is\n        made, reducing by half the number of round-trips to the device.\n\n        .. warning::\n\n            Setting this to `True` may cause incorrect values to be returned,\n            if any commands are sent to the device either by its local panel,\n            or by software other than InstrumentKit.\n\n        :type: `bool`\n        \"\"\"\n        return bool(self._cache_units)\n\n    @cache_units.setter\n    def cache_units(self, newval):\n        self._cache_units = (\n            self._READ_UNITS[self.measurement_configuration] if newval else False\n        )\n\n    # SENSOR PROPERTIES #\n\n    @property\n    def sensor(self):\n        \"\"\"\n        Returns information about the currently connected sensor.\n\n        :type: :class:`PM100USB.Sensor`\n        \"\"\"\n        return self.Sensor(self)\n\n    # SENSING CONFIGURATION PROPERTIES #\n\n    # TODO: make a setting of this refresh cache_units.\n    measurement_configuration = enum_property(\n        \"CONF\",\n        MeasurementConfiguration,\n        doc=\"\"\"\n        Returns the current measurement configuration.\n\n        :rtype: :class:`PM100USB.MeasurementConfiguration`\n        \"\"\",\n    )\n\n    @property\n    def averaging_count(self):\n        \"\"\"\n        Integer specifying how many samples to collect and average over for\n        each measurement, with each sample taking approximately 3 ms.\n        \"\"\"\n        return int(self.query(\"SENS:AVER:COUN?\"))\n\n    @averaging_count.setter\n    def averaging_count(self, newval):\n        if newval < 1:\n            raise ValueError(\"Must count at least one time.\")\n        self.sendcmd(f\"SENS:AVER:COUN {newval}\")\n\n    # METHODS ##\n\n    _READ_UNITS = defaultdict(lambda: u.dimensionless)\n    _READ_UNITS.update(\n        {\n            MeasurementConfiguration.power: u.W,\n            MeasurementConfiguration.current: u.A,\n            MeasurementConfiguration.frequency: u.Hz,\n            MeasurementConfiguration.voltage: u.V,\n        }\n    )\n\n    def read(self, size=-1, encoding=\"utf-8\"):\n        \"\"\"\n        Reads a measurement from this instrument, according to its current\n        configuration mode.\n\n        :param int size: Number of bytes to read from the instrument. Default\n            of ``-1`` reads until a termination character is found.\n\n        :units: As specified by :attr:`~PM100USB.measurement_configuration`.\n        :rtype: :class:`~pint.Quantity`\n        \"\"\"\n        # Get the current configuration to find out the units we need to\n        # attach.\n        units = (\n            self._READ_UNITS[self.measurement_configuration]\n            if not self._cache_units\n            else self._cache_units\n        )\n        return float(self.query(\"READ?\", size)) * units\n"
  },
  {
    "path": "src/instruments/thorlabs/sc10.py",
    "content": "#!/usr/bin/python\n\"\"\"\nProvides the support for the Thorlabs SC10 optical beam shutter controller.\n\nClass originally contributed by Catherine Holloway.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom enum import IntEnum\n\nfrom instruments.abstract_instruments import Instrument\nfrom instruments.thorlabs.thorlabs_utils import check_cmd\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import (\n    bool_property,\n    enum_property,\n    int_property,\n    unitful_property,\n)\n\n# CLASSES #####################################################################\n\n\nclass SC10(Instrument):\n    \"\"\"\n    The SC10 is a shutter controller, to be used with the Thorlabs SH05 and SH1.\n    The user manual can be found here:\n    http://www.thorlabs.com/thorcat/8600/SC10-Manual.pdf\n    \"\"\"\n\n    def __init__(self, filelike):\n        super().__init__(filelike)\n        self.terminator = \"\\r\"\n        self.prompt = \"> \"\n\n    def _ack_expected(self, msg=\"\"):\n        return msg\n\n    # ENUMS #\n\n    class Mode(IntEnum):\n        \"\"\"\n        Enum containing valid output modes of the SC10\n        \"\"\"\n\n        manual = 1\n        auto = 2\n        single = 3\n        repeat = 4\n        external = 5\n\n    # PROPERTIES #\n\n    @property\n    def name(self):\n        \"\"\"\n        Gets the name and version number of the device.\n\n        :return: Name and verison number of the device\n        :rtype: `str`\n        \"\"\"\n        return self.query(\"id?\")\n\n    @property\n    def enable(self):\n        \"\"\"\n        Gets/sets the shutter enable status, False for disabled, True if\n        enabled\n\n        If output enable is on (`True`), there is a voltage on the output.\n        :return: Status of the switch.\n        :rtype: `bool`\n\n        :raises TypeError: Unexpected type given when trying to enable.\n        \"\"\"\n        return bool(int(self.query(\"ens?\")))\n\n    @enable.setter\n    def enable(self, value):\n        if not isinstance(value, bool):\n            raise TypeError(f\"Expected bool, got type {type(value)} instead.\")\n        curr_status = self.enable\n        if curr_status is not value:\n            self.sendcmd(\"ens\")\n\n    repeat = int_property(\n        \"rep\",\n        valid_set=range(1, 100),\n        set_fmt=\"{}={}\",\n        doc=\"\"\"\n        Gets/sets the repeat count for repeat mode. Valid range is [1,99]\n        inclusive.\n\n        :type: `int`\n        \"\"\",\n    )\n\n    mode = enum_property(\n        \"mode\",\n        Mode,\n        input_decoration=int,\n        set_fmt=\"{}={}\",\n        doc=\"\"\"\n        Gets/sets the output mode of the SC10\n\n        :rtype: `SC10.Mode`\n        \"\"\",\n    )\n\n    trigger = int_property(\n        \"trig\",\n        valid_set=range(0, 2),\n        set_fmt=\"{}={}\",\n        doc=\"\"\"\n        Gets/sets the trigger source.\n\n        0 for internal trigger, 1 for external trigger\n\n        :type: `int`\n        \"\"\",\n    )\n\n    out_trigger = int_property(\n        \"xto\",\n        valid_set=range(0, 2),\n        set_fmt=\"{}={}\",\n        doc=\"\"\"\n        Gets/sets the out trigger source.\n\n        0 trigger out follows shutter output, 1 trigger out follows\n        controller output\n\n        :type: `int`\n        \"\"\",\n    )\n\n    open_time = unitful_property(\n        \"open\",\n        u.ms,\n        format_code=\"{:.0f}\",\n        set_fmt=\"{}={}\",\n        valid_range=(0, 999999),\n        doc=\"\"\"\n        Gets/sets the amount of time that the shutter is open, in ms\n\n        :units: As specified (if a `~pint.Quantity`) or assumed\n            to be of units milliseconds.\n        :type: `~pint.Quantity`\n        \"\"\",\n    )\n\n    shut_time = unitful_property(\n        \"shut\",\n        u.ms,\n        format_code=\"{:.0f}\",\n        set_fmt=\"{}={}\",\n        valid_range=(0, 999999),\n        doc=\"\"\"\n        Gets/sets the amount of time that the shutter is closed, in ms\n\n        :units: As specified (if a `~pint.Quantity`) or assumed\n            to be of units milliseconds.\n        :type: `~pint.Quantity`\n        \"\"\",\n    )\n\n    @property\n    def baud_rate(self):\n        \"\"\"\n        Gets/sets the instrument baud rate.\n\n        Valid baud rates are 9600 and 115200.\n\n        :type: `int`\n        \"\"\"\n        response = self.query(\"baud?\")\n        return 115200 if int(response) else 9600\n\n    @baud_rate.setter\n    def baud_rate(self, newval):\n        if newval != 9600 and newval != 115200:\n            raise ValueError(\"Invalid baud rate mode\")\n        else:\n            self.sendcmd(f\"baud={0 if newval == 9600 else 1}\")\n\n    closed = bool_property(\n        \"closed\",\n        inst_true=\"1\",\n        inst_false=\"0\",\n        readonly=True,\n        doc=\"\"\"\n        Gets the shutter closed status.\n\n        `True` represents the shutter is closed, and `False` for the shutter is\n        open.\n\n        :rtype: `bool`\n        \"\"\",\n    )\n\n    interlock = bool_property(\n        \"interlock\",\n        inst_true=\"1\",\n        inst_false=\"0\",\n        readonly=True,\n        doc=\"\"\"\n        Gets the interlock tripped status.\n\n        Returns `True` if the interlock is tripped, and `False` otherwise.\n\n        :rtype: `bool`\n        \"\"\",\n    )\n\n    # Methods #\n\n    def default(self):\n        \"\"\"\n        Restores instrument to factory settings.\n\n        Returns 1 if successful, zero otherwise.\n\n        :rtype: `int`\n        \"\"\"\n        response = self.query(\"default\")\n        return check_cmd(response)\n\n    def save(self):\n        \"\"\"\n        Stores the parameters in static memory\n\n        Returns 1 if successful, zero otherwise.\n\n        :rtype: `int`\n        \"\"\"\n        response = self.query(\"savp\")\n        return check_cmd(response)\n\n    def save_mode(self):\n        \"\"\"\n        Stores output trigger mode and baud rate settings in memory.\n\n        Returns 1 if successful, zero otherwise.\n\n        :rtype: `int`\n        \"\"\"\n        response = self.query(\"save\")\n        return check_cmd(response)\n\n    def restore(self):\n        \"\"\"\n        Loads the settings from memory.\n\n        Returns 1 if successful, zero otherwise.\n\n        :rtype: `int`\n        \"\"\"\n        response = self.query(\"resp\")\n        return check_cmd(response)\n"
  },
  {
    "path": "src/instruments/thorlabs/tc200.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides the support for the Thorlabs TC200 temperature controller.\n\nClass originally contributed by Catherine Holloway.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom enum import IntEnum, Enum\n\nfrom instruments.abstract_instruments import Instrument\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import (\n    convert_temperature,\n    enum_property,\n    unitful_property,\n    int_property,\n)\n\n# CLASSES #####################################################################\n\n\nclass TC200(Instrument):\n    \"\"\"\n    The TC200 is is a controller for the voltage across a heating element.\n    It can also read in the temperature off of a thermistor and implements\n    a PID control to keep the temperature at a set value.\n\n    The user manual can be found here:\n    http://www.thorlabs.com/thorcat/12500/TC200-Manual.pdf\n    \"\"\"\n\n    def __init__(self, filelike):\n        super().__init__(filelike)\n        self.terminator = \"\\r\"\n        self.prompt = \"> \"\n\n    def _ack_expected(self, msg=\"\"):\n        return msg\n\n    # ENUMS #\n\n    class Mode(IntEnum):\n        \"\"\"\n        Enum containing valid output modes of the TC200.\n        \"\"\"\n\n        normal = 0\n        cycle = 1\n\n    class Sensor(Enum):\n        \"\"\"\n        Enum containing valid temperature sensor types for the TC200.\n        \"\"\"\n\n        ptc100 = \"ptc100\"\n        ptc1000 = \"ptc1000\"\n        th10k = \"th10k\"\n        ntc10k = \"ntc10k\"\n\n    # PROPERTIES #\n\n    def name(self):\n        \"\"\"\n        Gets the name and version number of the device\n\n        :return: the name string of the device\n        :rtype: str\n        \"\"\"\n        response = self.query(\"*idn?\")\n        return response\n\n    @property\n    def mode(self):\n        \"\"\"\n        Gets/sets the output mode of the TC200\n\n        :type: `TC200.Mode`\n        \"\"\"\n        response = self.status\n        response_code = (int(response) >> 1) % 2\n        return TC200.Mode(response_code)\n\n    @mode.setter\n    def mode(self, newval):\n        if not isinstance(newval, TC200.Mode):\n            raise TypeError(\n                \"Mode setting must be a `TC200.Mode` value, \"\n                \"got {} instead.\".format(type(newval))\n            )\n        out_query = f\"mode={newval.name}\"\n        # there is an issue with the TC200; it responds with a spurious\n        # Command Error on mode=normal. Thus, the sendcmd() method cannot\n        # be used.\n        if newval == TC200.Mode.normal:\n            self.prompt = \"Command error CMD_ARG_RANGE_ERR\\n\\r> \"\n            self.sendcmd(out_query)\n            self.prompt = \"> \"\n        else:\n            self.sendcmd(out_query)\n\n    @property\n    def enable(self):\n        \"\"\"\n        Gets/sets the heater enable status.\n\n        If output enable is on (`True`), there is a voltage on the output.\n\n        :type: `bool`\n        \"\"\"\n        response = self.status\n        return True if int(response) % 2 == 1 else False\n\n    @enable.setter\n    def enable(self, newval):\n        if not isinstance(newval, bool):\n            raise TypeError(\n                \"TC200 enable property must be specified with a \" \"boolean.\"\n            )\n        # the \"ens\" command is a toggle, we need to track two different cases,\n        # when it should be on and it is off, and when it is off and\n        # should be on\n\n        # if no sensor is attached, the unit will respond with an error.\n        # There is no current error handling in the way that thorlabs\n        # responds with errors\n        if newval and not self.enable:\n            response1 = self._file.query(\"ens\")\n            while response1 != \">\":\n                response1 = self._file.read(1)\n            self._file.read(1)\n\n        elif not newval and self.enable:\n            response1 = self._file.query(\"ens\")\n            while response1 != \">\":\n                response1 = self._file.read(1)\n            self._file.read(1)\n\n    @property\n    def status(self):\n        \"\"\"\n        Gets the the status code of the TC200\n\n        :rtype: `int`\n        \"\"\"\n        _ = self._file.query(\"stat?\")\n        response = self.read(5)\n        return int(response.split(\" \")[0])\n\n    temperature = unitful_property(\n        \"tact\",\n        units=u.degC,\n        readonly=True,\n        input_decoration=lambda x: x.replace(\" C\", \"\")\n        .replace(\" F\", \"\")\n        .replace(\" K\", \"\"),\n        doc=\"\"\"\n        Gets the actual temperature of the sensor\n\n        :units: As specified (if a `~pint.Quantity`) or assumed\n            to be of units degrees C.\n        :type: `~pint.Quantity` or `int`\n        :return: the temperature (in degrees C)\n        :rtype: `~pint.Quantity`\n        \"\"\",\n    )\n\n    max_temperature = unitful_property(\n        \"tmax\",\n        units=u.degC,\n        format_code=\"{:.1f}\",\n        set_fmt=\"{}={}\",\n        valid_range=(u.Quantity(20, u.degC), u.Quantity(205, u.degC)),\n        doc=\"\"\"\n        Gets/sets the maximum temperature\n\n        :return: the maximum temperature (in deg C)\n        :units: As specified or assumed to be degree Celsius. Returns with\n            units degC.\n        :rtype: `~pint.Quantity`\n        \"\"\",\n    )\n\n    @property\n    def temperature_set(self):\n        \"\"\"\n        Gets/sets the actual temperature of the sensor\n\n        :units: As specified (if a `~pint.Quantity`) or assumed\n            to be of units degrees C.\n        :type: `~pint.Quantity` or `int`\n        :return: the temperature (in degrees C)\n        :rtype: `~pint.Quantity`\n        \"\"\"\n        response = (\n            self.query(\"tset?\")\n            .replace(\" Celsius\", \"\")\n            .replace(\" C\", \"\")\n            .replace(\" F\", \"\")\n            .replace(\" K\", \"\")\n        )\n        return u.Quantity(float(response), u.degC)\n\n    @temperature_set.setter\n    def temperature_set(self, newval):\n        # the set temperature is always in celsius\n        newval = convert_temperature(newval, u.degC)\n        if newval < u.Quantity(20.0, u.degC) or newval > self.max_temperature:\n            raise ValueError(\"Temperature set is out of range.\")\n        out_query = f\"tset={newval.magnitude}\"\n        self.sendcmd(out_query)\n\n    @property\n    def p(self):\n        \"\"\"\n        Gets/sets the p-gain. Valid numbers are [1,250].\n\n        :return: the p-gain (in nnn)\n        :rtype: `int`\n        \"\"\"\n        return self.pid[0]\n\n    @p.setter\n    def p(self, newval):\n        if newval not in range(1, 251):\n            raise ValueError(\"P-value not in [1, 250]\")\n        self.sendcmd(f\"pgain={newval}\")\n\n    @property\n    def i(self):\n        \"\"\"\n        Gets/sets the i-gain. Valid numbers are [1,250]\n\n        :return: the i-gain (in nnn)\n        :rtype: `int`\n        \"\"\"\n        return self.pid[1]\n\n    @i.setter\n    def i(self, newval):\n        if newval not in range(0, 251):\n            raise ValueError(\"I-value not in [0, 250]\")\n        self.sendcmd(f\"igain={newval}\")\n\n    @property\n    def d(self):\n        \"\"\"\n        Gets/sets the d-gain. Valid numbers are [0, 250]\n\n        :return: the d-gain (in nnn)\n        :type: `int`\n        \"\"\"\n        return self.pid[2]\n\n    @d.setter\n    def d(self, newval):\n        if newval not in range(0, 251):\n            raise ValueError(\"D-value not in [0, 250]\")\n        self.sendcmd(f\"dgain={newval}\")\n\n    @property\n    def pid(self):\n        \"\"\"\n        Gets/sets all three PID values at the same time. See `TC200.p`,\n        `TC200.i`, and `TC200.d` for individual restrictions.\n\n        If `None` is specified then the corresponding PID value is not changed.\n\n        :return: List of integers of PID values. In order [P, I, D].\n        :type: `list` or `tuple`\n        :rtype: `list`\n        \"\"\"\n        return list(map(int, self.query(\"pid?\").split()))\n\n    @pid.setter\n    def pid(self, newval):\n        if not isinstance(newval, (list, tuple)):\n            raise TypeError(\"Setting PID must be specified as a list or tuple\")\n        if newval[0] is not None:\n            self.p = newval[0]\n        if newval[1] is not None:\n            self.i = newval[1]\n        if newval[2] is not None:\n            self.d = newval[2]\n\n    @property\n    def degrees(self):\n        \"\"\"\n        Gets/sets the units of the temperature measurement.\n\n        :return: The temperature units (degC/F/K) the TC200 is measuring in\n        :type: `~pint.Unit`\n        \"\"\"\n        response = self.status\n        if (response >> 4) % 2 and (response >> 5) % 2:\n            return u.degC\n        elif (response >> 5) % 2:\n            return u.degK\n\n        return u.degF\n\n    @degrees.setter\n    def degrees(self, newval):\n        if newval == u.degC:\n            self.sendcmd(\"unit=c\")\n        elif newval == u.degF:\n            self.sendcmd(\"unit=f\")\n        elif newval == u.degK:\n            self.sendcmd(\"unit=k\")\n        else:\n            raise TypeError(\"Invalid temperature type\")\n\n    sensor = enum_property(\n        \"sns\",\n        Sensor,\n        input_decoration=lambda x: x.split(\",\")[0].split(\"=\")[1].strip().lower(),\n        set_fmt=\"{}={}\",\n        doc=\"\"\"\n        Gets/sets the current thermistor type. Used for converting resistances\n        to temperatures.\n\n        :return: The thermistor type\n        :type: `TC200.Sensor`\n        \"\"\",\n    )\n\n    beta = int_property(\n        \"beta\",\n        valid_set=range(2000, 6001),\n        set_fmt=\"{}={}\",\n        doc=\"\"\"\n        Gets/sets the beta value of the thermistor curve.\n\n        Value within [2000, 6000]\n\n        :return: the gain (in nnn)\n        :type: `int`\n        \"\"\",\n    )\n\n    max_power = unitful_property(\n        \"pmax\",\n        units=u.W,\n        format_code=\"{:.1f}\",\n        set_fmt=\"{}={}\",\n        valid_range=(0.1 * u.W, 18.0 * u.W),\n        doc=\"\"\"\n        Gets/sets the maximum power\n\n        :return: The maximum power\n        :units: Watts (linear units)\n        :type: `~pint.Quantity`\n        \"\"\",\n    )\n"
  },
  {
    "path": "src/instruments/thorlabs/thorlabs_utils.py",
    "content": "#!/usr/bin/python\n\"\"\"\nContains common utility functions for Thorlabs-brand instruments\n\"\"\"\n\n\ndef check_cmd(response):\n    \"\"\"\n    Checks the for the two common Thorlabs error messages; CMD_NOT_DEFINED and\n    CMD_ARG_INVALID\n\n    :param response: the response from the device\n    :return: 1 if not found, 0 otherwise\n    :rtype: int\n    \"\"\"\n    return 1 if response != \"CMD_NOT_DEFINED\" and response != \"CMD_ARG_INVALID\" else 0\n"
  },
  {
    "path": "src/instruments/thorlabs/thorlabsapt.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides the support for the Thorlabs APT Controller.\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport re\nimport struct\nimport logging\nimport codecs\nimport warnings\n\nfrom instruments.thorlabs import _abstract, _packets, _cmds\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import assume_units\n\n# LOGGING #####################################################################\n\nlogger = logging.getLogger(__name__)\nlogger.addHandler(logging.NullHandler())\n\n# CLASSES #####################################################################\n\n# pylint: disable=too-many-lines\n\n\nclass ThorLabsAPT(_abstract.ThorLabsInstrument):\n    \"\"\"\n    Generic ThorLabs APT hardware device controller. Communicates using the\n    ThorLabs APT communications protocol, whose documentation is found in the\n    thorlabs source folder.\n    \"\"\"\n\n    class APTChannel:\n        \"\"\"\n        Represents a channel within the hardware device. One device can have\n        many channels, each labeled by an index.\n        \"\"\"\n\n        def __init__(self, apt, idx_chan):\n            self._apt = apt\n            # APT is 1-based, but we want the Python representation to be\n            # 0-based.\n            self._idx_chan = idx_chan + 1\n\n        @property\n        def enabled(self):\n            \"\"\"\n            Gets/sets the enabled status for the specified APT channel\n\n            :type: `bool`\n\n            :raises TypeError: If controller is not supported\n            \"\"\"\n            if self._apt.model_number[0:3] == \"KIM\":\n                raise TypeError(\n                    \"For KIM controllers, use the \"\n                    \"`enabled_single` function to enable \"\n                    \"one axis. For KIM101 controllers, \"\n                    \"multiple axes can be enabled using \"\n                    \"the `enabled_multi` function from the \"\n                    \"controller level.\"\n                )\n\n            pkt = _packets.ThorLabsPacket(\n                message_id=_cmds.ThorLabsCommands.MOD_REQ_CHANENABLESTATE,\n                param1=self._idx_chan,\n                param2=0x00,\n                dest=self._apt.destination,\n                source=0x01,\n                data=None,\n            )\n            resp = self._apt.querypacket(\n                pkt, expect=_cmds.ThorLabsCommands.MOD_GET_CHANENABLESTATE\n            )\n            return not bool(resp.parameters[1] - 1)\n\n        @enabled.setter\n        def enabled(self, newval):\n            pkt = _packets.ThorLabsPacket(\n                message_id=_cmds.ThorLabsCommands.MOD_SET_CHANENABLESTATE,\n                param1=self._idx_chan,\n                param2=0x01 if newval else 0x02,\n                dest=self._apt.destination,\n                source=0x01,\n                data=None,\n            )\n            self._apt.sendpacket(pkt)\n\n    _channel_type = APTChannel\n\n    def __init__(self, filelike):\n        super().__init__(filelike)\n        self._dest = 0x50  # Generic USB device; make this configurable later.\n\n        # Provide defaults in case an exception occurs below.\n        self._serial_number = None\n        self._model_number = None\n        self._hw_type = None\n        self._fw_version = None\n        self._notes = \"\"\n        self._hw_version = None\n        self._mod_state = None\n        self._n_channels = 0\n        self._channel = ()\n\n        # Perform a HW_REQ_INFO to figure out the model number, serial number,\n        # etc.\n        try:\n            req_packet = _packets.ThorLabsPacket(\n                message_id=_cmds.ThorLabsCommands.HW_REQ_INFO,\n                param1=0x00,\n                param2=0x00,\n                dest=self._dest,\n                source=0x01,\n                data=None,\n            )\n            hw_info = self.querypacket(\n                req_packet,\n                expect=_cmds.ThorLabsCommands.HW_GET_INFO,\n                expect_data_len=84,\n            )\n\n            self._serial_number = codecs.encode(hw_info.data[0:4], \"hex\").decode(\n                \"ascii\"\n            )\n            self._model_number = (\n                hw_info.data[4:12].decode(\"ascii\").replace(\"\\x00\", \"\").strip()\n            )\n\n            hw_type_int = struct.unpack(\"<H\", hw_info.data[12:14])[0]\n            if hw_type_int == 45:\n                self._hw_type = \"Multi-channel controller motherboard\"\n            elif hw_type_int == 44:\n                self._hw_type = \"Brushless DC controller\"\n            else:\n                self._hw_type = f\"Unknown type: {hw_type_int}\"\n\n            # Note that the fourth byte is padding, so we strip out the first\n            # three bytes and format them.\n            # pylint: disable=invalid-format-index\n            self._fw_version = \"{0[0]:x}.{0[1]:x}.{0[2]:x}\".format(hw_info.data[14:18])\n            self._notes = (\n                hw_info.data[18:66].replace(b\"\\x00\", b\"\").decode(\"ascii\").strip()\n            )\n\n            self._hw_version = struct.unpack(\"<H\", hw_info.data[78:80])[0]\n            self._mod_state = struct.unpack(\"<H\", hw_info.data[80:82])[0]\n            self._n_channels = struct.unpack(\"<H\", hw_info.data[82:84])[0]\n        except OSError as e:\n            logger.error(\"Exception occured while fetching hardware info: %s\", e)\n\n        # Create a tuple of channels of length _n_channel_type\n        if self._n_channels > 0:\n            self._channel = tuple(\n                self._channel_type(self, chan_idx)\n                for chan_idx in range(self._n_channels)\n            )\n\n    @property\n    def serial_number(self):\n        \"\"\"\n        Gets the serial number for the APT controller\n\n        :type: `str`\n        \"\"\"\n        return self._serial_number\n\n    @property\n    def model_number(self):\n        \"\"\"\n        Gets the model number for the APT controller\n\n        :type: `str`\n        \"\"\"\n        return self._model_number\n\n    @property\n    def name(self):\n        \"\"\"\n        Gets the name of the APT controller. This is a human readable string\n        containing the model, serial number, hardware version, and firmware\n        version.\n\n        :type: `str`\n        \"\"\"\n        return (\n            \"ThorLabs APT Instrument model {model}, serial {serial} \"\n            \"(HW version {hw_ver}, FW version {fw_ver})\".format(\n                hw_ver=self._hw_version,\n                serial=self.serial_number,\n                fw_ver=self._fw_version,\n                model=self.model_number,\n            )\n        )\n\n    @property\n    def channel(self):\n        \"\"\"\n        Gets the list of channel objects attached to the APT controller.\n\n        A specific channel object can then be accessed like one would access\n        a list.\n\n        :type: `tuple` of `APTChannel`\n        \"\"\"\n        return self._channel\n\n    @property\n    def n_channels(self):\n        \"\"\"\n        Gets/sets the number of channels attached to the APT controller\n\n        :type: `int`\n        \"\"\"\n        return self._n_channels\n\n    @n_channels.setter\n    def n_channels(self, nch):\n        # Change the number of channels so as not to modify those instances\n        # already existing:\n        # If we add more channels, append them to the list,\n        # If we remove channels, remove them from the end of the list.\n        if nch > self._n_channels:\n            self._channel = list(self._channel) + list(\n                self._channel_type(self, chan_idx)\n                for chan_idx in range(self._n_channels, nch)\n            )\n        elif nch < self._n_channels:\n            self._channel = self._channel[:nch]\n        self._n_channels = nch\n\n    def identify(self):\n        \"\"\"\n        Causes a light on the APT instrument to blink, so that it can be\n        identified.\n        \"\"\"\n        pkt = _packets.ThorLabsPacket(\n            message_id=_cmds.ThorLabsCommands.MOD_IDENTIFY,\n            param1=0x00,\n            param2=0x00,\n            dest=self._dest,\n            source=0x01,\n            data=None,\n        )\n        self.sendpacket(pkt)\n\n    @property\n    def destination(self):\n        \"\"\"\n        Gets the destination for the APT controller\n\n        :type: `int`\n        \"\"\"\n        return self._dest\n\n\nclass APTPiezoDevice(ThorLabsAPT):\n    \"\"\"\n    Generic ThorLabs APT piezo device, superclass of more specific piezo\n    devices.\n    \"\"\"\n\n    class PiezoDeviceChannel(ThorLabsAPT.APTChannel):\n        \"\"\"\n        Represents a channel within the hardware device. One device can have\n        many channels, each labeled by an index.\n\n        This class represents piezo stage channels.\n        \"\"\"\n\n        # PIEZO COMMANDS #\n\n        @property\n        def max_travel(self):\n            \"\"\"\n            Gets the maximum travel for the specified piezo channel.\n\n            :type: `~pint.Quantity`\n            :units: Nanometers\n            \"\"\"\n            pkt = _packets.ThorLabsPacket(\n                message_id=_cmds.ThorLabsCommands.PZ_REQ_MAXTRAVEL,\n                param1=self._idx_chan,\n                param2=0x00,\n                dest=self._apt.destination,\n                source=0x01,\n                data=None,\n            )\n            resp = self._apt.querypacket(pkt, expect_data_len=4)\n\n            # Not all APT piezo devices support querying the maximum travel\n            # distance. Those that do not simply ignore the PZ_REQ_MAXTRAVEL\n            # packet, so that the response is empty.\n            if resp is None:\n                return NotImplemented\n\n            # chan, int_maxtrav\n            _, int_maxtrav = struct.unpack(\"<HH\", resp.data)\n            return int_maxtrav * u.Quantity(100, \"nm\")\n\n    @property\n    def led_intensity(self):\n        \"\"\"\n        Gets/sets the output intensity of the LED display.\n\n        :type: `float` between 0 and 1.\n        \"\"\"\n        pkt = _packets.ThorLabsPacket(\n            message_id=_cmds.ThorLabsCommands.PZ_REQ_TPZ_DISPSETTINGS,\n            param1=0x01,\n            param2=0x00,\n            dest=self._dest,\n            source=0x01,\n            data=None,\n        )\n        resp = self.querypacket(pkt, expect_data_len=2)\n\n        # Not all APT piezo devices support querying the LED intenstiy\n        # distance, e.g., TIM, KIM. Those that do not simply ignore the\n        # PZ_REQ_TPZ_DISPSETTINGS packet, so that the response is empty.\n        # Setting will be ignored as well.\n        if resp is None:\n            return NotImplemented\n        else:\n            return float(struct.unpack(\"<H\", resp.data)[0]) / 255\n\n    @led_intensity.setter\n    def led_intensity(self, intensity):\n        # pylint: disable=round-builtin\n        pkt = _packets.ThorLabsPacket(\n            message_id=_cmds.ThorLabsCommands.PZ_SET_TPZ_DISPSETTINGS,\n            param1=None,\n            param2=None,\n            dest=self._dest,\n            source=0x01,\n            data=struct.pack(\"<H\", int(round(255 * intensity))),\n        )\n        self.sendpacket(pkt)\n\n    _channel_type = PiezoDeviceChannel\n\n\nclass APTPiezoInertiaActuator(APTPiezoDevice):\n    \"\"\"Represent a Thorlabs APT piezo inertia actuator.\n\n    Currently only the KIM piezo inertia actuator is implemented.\n    Some routines will work with the TIM actuator as well. Routines\n    that are specific for the KIM101 controller will raise a TypeError\n    if not implemented for this controller. Unfortunately, handling all\n    these controller specific functions is fairly messy, but necessary.\n\n    Example for a KIM101 controller:\n        >>> import instruments as ik\n        >>> import instruments.units as u\n        >>> # call the controller\n        >>> kim = ik.thorlabs.APTPiezoInertiaActuator.open_serial(\"/dev/ttyUSB0\", baud=115200)\n        >>> # set first channel to enabled\n        >>> ch = kim.channel[0]\n        >>> ch.enabled_single = True\n        >>> # define and set drive parameters\n        >>> max_volts = u.Quantity(110, u.V)\n        >>> step_rate = u.Quantity(1000, 1/u.s)\n        >>> acceleration = u.Quantity(10000, 1/u.s**2)\n        >>> ch.drive_op_parameters = [max_volts, step_rate, acceleration]\n        >>> # aboslute move to 1000 steps\n        >>> ch.move_abs(1000)\n    \"\"\"\n\n    class PiezoChannel(APTPiezoDevice.PiezoDeviceChannel):\n        \"\"\"\n        Class representing a single piezo channel within a piezo stage\n        on the Thorlabs APT controller.\n        \"\"\"\n\n        # PROPERTIES #\n\n        @property\n        def drive_op_parameters(self):\n            \"\"\"Get / Set various drive parameters for move motion.\n\n            Defines the speed and acceleration of moves initiated in\n            the following ways:\n            - by clicking in the position display\n            - via the top panel controls when ‘Go To Position’ mode is\n            selected (in the Set_TIM_JogParameters (09) or\n            Set_KCubeMMIParams (15) sub‐messages).\n            - via software using the MoveVelocity, MoveAbsoluteStepsEx\n            or MoveRelativeStepsEx methods.\n\n            :setter: The setter must be be given as a list of 3\n                entries. The three entries are:\n                -  Maximum Voltage:\n                The maximum piezo drive voltage, in the range 85V\n                to 125V. Unitful, if no unit given, V are assumed.\n                - Step Rate:\n                The piezo motor moves by ramping up the drive\n                voltage to the value set in the MaxVoltage parameter\n                and then dropping quickly to zero, then repeating.\n                One cycle is termed a step. This parameter specifies\n                the velocity to move when a command is initiated.\n                The step rate is specified in steps/sec, in the range 1\n                to 2,000. Unitful, if no unit given, 1 / sec assumed.\n                - Step Acceleration:\n                This parameter specifies the acceleration up to the\n                step rate, in the range 1 to 100,000 cycles/sec/sec.\n                Unitful, if no unit given, 1/sec**2 assumed.\n\n            :return: List with the drive parameters, unitful.\n\n            :raises TypeError: The setter was not a list or tuple.\n            :raises ValueError: The setter was not given a tuple with\n                three values.\n            :raises ValueError: One of the parameters was out of range.\n\n            Example:\n                >>> import instruments as ik\n                >>> import instruments.units as u\n                >>> # call the controller\n                >>> kim = ik.thorlabs.APTPiezoInertiaActuator.open_serial(\"/dev/ttyUSB0\", baud=115200)\n                >>> # grab channel 0\n                >>> ch = kim.channel[0]\n                >>> # change the step rate to 2000 /s\n                >>> drive_params = ch.drive_op_parameters\n                >>> drive_params[1] = 2000\n                >>> ch.drive_op_parameters = drive_params\n            \"\"\"\n            pkt = _packets.ThorLabsPacket(\n                message_id=_cmds.ThorLabsCommands.PZMOT_REQ_PARAMS,\n                param1=0x07,\n                param2=self._idx_chan,\n                dest=self._apt.destination,\n                source=0x01,\n                data=None,\n            )\n\n            resp = self._apt.querypacket(\n                pkt, expect=_cmds.ThorLabsCommands.PZMOT_GET_PARAMS, expect_data_len=14\n            )\n\n            # unpack\n            ret_val = struct.unpack(\"<HHHll\", resp.data)\n            ret_val = [ret_val[2], ret_val[3], ret_val[4]]\n\n            # set units and formats\n            ret_val = [\n                u.Quantity(int(ret_val[0]), u.V),\n                u.Quantity(int(ret_val[1]), 1 / u.s),\n                u.Quantity(int(ret_val[2]), 1 / u.s**2),\n            ]\n            return ret_val\n\n        @drive_op_parameters.setter\n        def drive_op_parameters(self, params):\n            if not isinstance(params, tuple) and not isinstance(params, list):\n                raise TypeError(\"Parameters must be given as list or tuple.\")\n            if len(params) != 3:\n                raise ValueError(\"Parameters must be a list or tuple with \" \"length 3.\")\n\n            # ensure units\n            volt = int(assume_units(params[0], u.V).to(u.V).magnitude)\n            rate = int(assume_units(params[1], 1 / u.s).to(1 / u.s).magnitude)\n            accl = int(assume_units(params[2], 1 / u.s**2).to(1 / u.s**2).magnitude)\n\n            # check parameters\n            if volt < 85 or volt > 125:\n                raise ValueError(\n                    \"The voltage ({} V) is out of range. It must \"\n                    \"be between 85 V and 125 V.\".format(volt)\n                )\n            if rate < 1 or rate > 2000:\n                raise ValueError(\n                    \"The step rate ({} /s) is out of range. It \"\n                    \"must be between 1 /s and 2,000 /s.\".format(rate)\n                )\n\n            if accl < 1 or accl > 100000:\n                raise ValueError(\n                    \"The acceleration ({} /s/s) is out of range. \"\n                    \"It must be between 1 /s/s and 100,000 /s/s.\".format(accl)\n                )\n\n            pkt = _packets.ThorLabsPacket(\n                message_id=_cmds.ThorLabsCommands.PZMOT_SET_PARAMS,\n                param1=None,\n                param2=None,\n                dest=self._apt.destination,\n                source=0x01,\n                data=struct.pack(\"<HHHll\", 0x07, self._idx_chan, volt, rate, accl),\n            )\n            self._apt.sendpacket(pkt)\n\n        @property\n        def enabled_single(self):\n            \"\"\"Get / Set single axis enabled.\n\n            .. note:: Enabling multi channels for KIM101 is defined in\n                the controller class.\n\n            :return: Axis status enabled.\n            :rtype: bool\n\n            :raises TypeError: Invalid controller for this command.\n\n            Example for a KIM101 controller:\n                >>> import instruments as ik\n                >>> # call the controller\n                >>> kim = ik.thorlabs.APTPiezoInertiaActuator.open_serial(\"/dev/ttyUSB0\", baud=115200)\n                >>> # grab channel 0\n                >>> ch = kim.channel[0]\n                >>> # enable channel 0\n                >>> ch.enabled_single = True\n            \"\"\"\n            if self._apt.model_number[0:3] != \"KIM\":\n                raise (\n                    \"This command is only valid with KIM001 and \"\n                    \"KIM101 controllers. Your controller is a {}.\".format(\n                        self._apt.model_number\n                    )\n                )\n\n            pkt = _packets.ThorLabsPacket(\n                message_id=_cmds.ThorLabsCommands.PZMOT_REQ_PARAMS,\n                param1=0x2B,\n                param2=self._idx_chan,\n                dest=self._apt.destination,\n                source=0x01,\n                data=None,\n            )\n\n            resp = self._apt.querypacket(\n                pkt, expect=_cmds.ThorLabsCommands.PZMOT_GET_PARAMS, expect_data_len=4\n            )\n\n            ret_val = struct.unpack(\"<HH\", resp.data)[1] == self._idx_chan\n\n            return ret_val\n\n        @enabled_single.setter\n        def enabled_single(self, newval):\n            if self._apt.model_number[0:3] != \"KIM\":\n                raise TypeError(\n                    \"This command is only valid with \"\n                    \"KIM001 and KIM101 controllers. Your \"\n                    \"controller is a {}.\".format(self._apt.model_number)\n                )\n\n            param = self._idx_chan if newval else 0x00\n            pkt = _packets.ThorLabsPacket(\n                message_id=_cmds.ThorLabsCommands.PZMOT_SET_PARAMS,\n                param1=None,\n                param2=None,\n                dest=self._apt.destination,\n                source=0x01,\n                data=struct.pack(\"<HH\", 0x2B, param),\n            )\n            self._apt.sendpacket(pkt)\n\n        @property\n        def jog_parameters(self):\n            \"\"\"Get / Set the jog parameters.\n\n            Define the speed and acceleration of moves initiated in the\n            following ways:\n            - By clicking the jog buttons on the GUI panel\n            - By moving the joystick on the unit when ‘Jog Mode’ is\n            selected.\n            - via software using the MoveJog method.\n\n            It differs from the normal motor jog message in that there\n            are two jog step sizes, one for forward and one for reverse.\n            The reason for this is that due to the inherent nature of\n            the PIA actuators going further in one direction as\n            compared with another this will allow the user to\n            potentially make adjustments to get fore and aft movement\n            the same or similar.\n\n            :setter: The setter must be be given as a list of 5\n                entries. The three entries are:\n                - Jog Mode (1 for continuus, i.e., until stop command\n                is issued, or 2 jog by the number of steps defined)\n                - Jog Step Size Forward: Range 1 - 2000\n                - Jog Step Size Backward: Range 1 - 2000\n                The piezo motor moves by ramping up the drive\n                voltage to the value set in the MaxVoltage parameter\n                and then dropping quickly to zero, then repeating.\n                One cycle is termed a step. This parameter specifies\n                the velocity to move when a command is initiated.\n                The step rate is specified in steps/sec, in the range 1\n                to 2,000. Unitful, if no unit given, 1 / sec assumed.\n                - Jog Step Acceleration:\n                This parameter specifies the acceleration up to the\n                step rate, in the range 1 to 100,000 cycles/sec/sec.\n                Unitful, if no unit given, 1/sec**2 assumed.\n\n            :return: List with the jog parameters.\n\n            :raises TypeError: The setter was not a list or tuple.\n            :raises ValueError: The setter was not given a tuple with\n                three values.\n            :raises ValueError: One of the parameters was out of range.\n            :raises TypeError: Invalid controller for this command.\n\n            Example for a KIM101 controller:\n                >>> import instruments as ik\n                >>> import instruments.units as u\n                >>> # call the controller\n                >>> kim = ik.thorlabs.APTPiezoInertiaActuator.open_serial(\"/dev/ttyUSB0\", baud=115200)\n                >>> # grab channel 0\n                >>> ch = kim.channel[0]\n                >>> # set jog parameters\n                >>> mode = 2  # only move by set step size\n                >>> step = 100  # step size\n                >>> rate = u.Quantity(1000, 1/u.s)  # step rate\n                >>> # if no quantity given, SI units assumed\n                >>> accl = 10000\n                >>> ch.jog_parameters = [mode, step, step, rate, accl]\n                >>> ch.jog_parameters\n                [2, 100, 100, array(1000) * 1/s, array(10000) * 1/s**2]\n            \"\"\"\n            if self._apt.model_number[0:3] != \"KIM\":\n                raise TypeError(\n                    \"This command is only valid with \"\n                    \"KIM001 and KIM101 controllers. Your \"\n                    \"controller is a {}.\".format(self._apt.model_number)\n                )\n\n            pkt = _packets.ThorLabsPacket(\n                message_id=_cmds.ThorLabsCommands.PZMOT_REQ_PARAMS,\n                param1=0x2D,\n                param2=self._idx_chan,\n                dest=self._apt.destination,\n                source=0x01,\n                data=None,\n            )\n\n            resp = self._apt.querypacket(\n                pkt, expect=_cmds.ThorLabsCommands.PZMOT_GET_PARAMS, expect_data_len=22\n            )\n\n            # unpack response\n            ret_val = struct.unpack(\"<HHHllll\", resp.data)\n            ret_val = [ret_val[2], ret_val[3], ret_val[4], ret_val[5], ret_val[6]]\n\n            # assign the appropriate units, forms\n            ret_val = [\n                int(ret_val[0]),\n                int(ret_val[1]),\n                int(ret_val[2]),\n                u.Quantity(int(ret_val[3]), 1 / u.s),\n                u.Quantity(int(ret_val[4]), 1 / u.s**2),\n            ]\n\n            return ret_val\n\n        @jog_parameters.setter\n        def jog_parameters(self, params):\n            if self._apt.model_number[0:3] != \"KIM\":\n                raise TypeError(\n                    \"This command is only valid with \"\n                    \"KIM001 and KIM101 controllers. Your \"\n                    \"controller is a {}.\".format(self._apt.model_number)\n                )\n\n            if not isinstance(params, tuple) and not isinstance(params, list):\n                raise TypeError(\"Parameters must be given as list or tuple.\")\n            if len(params) != 5:\n                raise ValueError(\"Parameters must be a list or tuple with \" \"length 5.\")\n\n            # ensure units\n            mode = int(params[0])\n            steps_fwd = int(params[1])\n            steps_bkw = int(params[2])\n            rate = int(assume_units(params[3], 1 / u.s).to(1 / u.s).magnitude)\n            accl = int(assume_units(params[4], 1 / u.s**2).to(1 / u.s**2).magnitude)\n\n            # check parameters\n            if mode != 1 and mode != 2:\n                raise ValueError(\n                    \"The mode ({}) must be either set to 1 \"\n                    \"(continuus) or 2 (steps).\".format(mode)\n                )\n            if steps_fwd < 1 or steps_fwd > 2000:\n                raise ValueError(\n                    \"The steps forward ({}) are out of range. It \"\n                    \"must be between 1 and 2,000.\".format(steps_fwd)\n                )\n            if steps_bkw < 1 or steps_bkw > 2000:\n                raise ValueError(\n                    \"The steps backward ({}) are out of range. \"\n                    \"It must be between 1 and 2,000.\".format(steps_bkw)\n                )\n            if rate < 1 or rate > 2000:\n                raise ValueError(\n                    \"The step rate ({} /s) is out of range. It \"\n                    \"must be between 1 /s and 2,000 /s.\".format(rate)\n                )\n            if accl < 1 or accl > 100000:\n                raise ValueError(\n                    \"The acceleration ({} /s/s) is out of range. \"\n                    \"It must be between 1 /s/s and 100,000 /s/s.\".format(accl)\n                )\n\n            pkt = _packets.ThorLabsPacket(\n                message_id=_cmds.ThorLabsCommands.PZMOT_SET_PARAMS,\n                param1=None,\n                param2=None,\n                dest=self._apt.destination,\n                source=0x01,\n                data=struct.pack(\n                    \"<HHHllll\",\n                    0x2D,\n                    self._idx_chan,\n                    mode,\n                    steps_fwd,\n                    steps_bkw,\n                    rate,\n                    accl,\n                ),\n            )\n            self._apt.sendpacket(pkt)\n\n        @property\n        def position_count(self):\n            \"\"\"Get/Set the position count of a given channel.\n\n            :setter pos: Position (steps) of axis.\n            :type pos: int\n\n            :return: Position (steps) of axis.\n            :rtype: int\n\n            Example:\n                >>> import instruments as ik\n                >>> # call the controller\n                >>> kim = ik.thorlabs.APTPiezoInertiaActuator.open_serial(\"/dev/ttyUSB0\", baud=115200)\n                >>> # grab channel 0\n                >>> ch = kim.channel[0]\n                >>> # set position count to zero\n                >>> ch.position_count = 0\n                >>> ch.position_count\n                0\n            \"\"\"\n            pkt = _packets.ThorLabsPacket(\n                message_id=_cmds.ThorLabsCommands.PZMOT_REQ_PARAMS,\n                param1=0x05,\n                param2=self._idx_chan,\n                dest=self._apt.destination,\n                source=0x01,\n                data=None,\n            )\n\n            resp = self._apt.querypacket(\n                pkt, expect=_cmds.ThorLabsCommands.PZMOT_GET_PARAMS, expect_data_len=12\n            )\n\n            ret_val = int(struct.unpack(\"<HHll\", resp.data)[2])\n\n            return ret_val\n\n        @position_count.setter\n        def position_count(self, pos):\n            pkt = _packets.ThorLabsPacket(\n                message_id=_cmds.ThorLabsCommands.PZMOT_SET_PARAMS,\n                param1=None,\n                param2=None,\n                dest=self._apt.destination,\n                source=0x01,\n                data=struct.pack(\"<HHll\", 0x05, self._idx_chan, pos, 0x00),\n            )\n            self._apt.sendpacket(pkt)\n\n        # METHODS #\n\n        def move_abs(self, pos):\n            \"\"\"\n            Moves the axis to a position specified as the number of\n            steps away from the zero position.\n\n            To set the moving parameters, use the setter for\n            `drive_op_parameters`.\n\n            :param pos: Position to move to, in steps.\n            :type pos: int\n\n            Example:\n                >>> import instruments as ik\n                >>> # call the controller\n                >>> kim = ik.thorlabs.APTPiezoInertiaActuator.open_serial(\"/dev/ttyUSB0\", baud=115200)\n                >>> # grab channel 0\n                >>> ch = kim.channel[0]\n                >>> # move to 314 steps\n                >>> ch.move_abs(314)\n            \"\"\"\n            pkt = _packets.ThorLabsPacket(\n                message_id=_cmds.ThorLabsCommands.PZMOT_MOVE_ABSOLUTE,\n                param1=None,\n                param2=None,\n                dest=self._apt.destination,\n                source=0x01,\n                data=struct.pack(\"<Hl\", self._idx_chan, pos),\n            )\n            self._apt.sendpacket(pkt)\n\n        def move_jog(self, direction=\"fwd\"):\n            \"\"\"\n            Jogs the axis in forward or backward direction by the number\n            of steps that are stored in the controller.\n\n            To set the moving parameters, use the setter for\n            `jog_parameters`.\n\n            :param str direction: Direction of jog. 'fwd' for forward,\n                'rev' for backward. 'fwd' if invalid argument given\n\n            Example:\n                >>> import instruments as ik\n                >>> # call the controller\n                >>> kim = ik.thorlabs.APTPiezoInertiaActuator.open_serial(\"/dev/ttyUSB0\", baud=115200)\n                >>> # grab channel 0\n                >>> ch = kim.channel[0]\n                >>> # set jog parameters\n                >>> params = ch.jog_parameters\n                >>> params[0] = 2  # move by number of steps\n                >>> params[1] = 100  # step size forward\n                >>> params[2] = 200  # step size reverse\n                >>> ch.jog_parameters = params  # set parameters\n                >>> # jog forward (default)\n                >>> ch.move_jog()\n                >>> # jog reverse\n                >>> ch.move_jog('rev')\n            \"\"\"\n            if direction == \"rev\":\n                param2 = 0x02\n            else:\n                param2 = 0x01\n\n            pkt = _packets.ThorLabsPacket(\n                message_id=_cmds.ThorLabsCommands.PZMOT_MOVE_JOG,\n                param1=self._idx_chan,\n                param2=param2,\n                dest=self._apt.destination,\n                source=0x01,\n                data=None,\n            )\n            self._apt.sendpacket(pkt)\n\n        def move_jog_stop(self):\n            \"\"\"Stops the current motor movement.\n\n            Stop a jog command. The regular motor move stop command does\n            not work for jogging. This command somehow does...\n\n            .. note:: This information is quite empirical. It would\n                only be really needed if jogging parameters are set to\n                continuous. The safer method is to set the step range.\n            \"\"\"\n            pkt = _packets.ThorLabsPacket(\n                message_id=_cmds.ThorLabsCommands.PZMOT_MOVE_JOG,\n                param1=self._idx_chan,\n                param2=0x00,\n                dest=self._apt.destination,\n                source=0x01,\n                data=None,\n            )\n\n            self._apt.sendpacket(pkt)\n\n    _channel_type = PiezoChannel\n\n    # PROPERTIES #\n\n    @property\n    def enabled_multi(self):\n        \"\"\"Enable / Query mulitple channel mode.\n\n        For KIM101 controller, where multiple axes can be selected\n        simultaneously (i. e., for a mirror mount).\n\n        :setter mode: Channel pair to be activated.\n            0:  All channels deactivated\n            1:  First channel pair activated (channel 0 & 1)\n            2:  Second channel pair activated (channel 2 & 3)\n        :type mode: int\n\n        :return: The selected mode:\n            0 - multi-channel selection disabled\n            1 - Channel 0 & 1 enabled\n            2 - Channel 2 & 3 enabled\n        :rtype: int\n\n        :raises ValueError: No valid channel pair selected\n        :raises TypeError: Invalid controller for this command.\n\n        Example:\n            >>> import instruments as ik\n            >>> kim = ik.thorlabs.APTPiezoInertiaActuator.open_serial(\"/dev/ttyUSB0\", baud=115200)\n            >>> # activate the first two channels\n            >>> kim.enabled_multi = 1\n            >>> # read back\n            >>> kim.enabled_multi\n            1\n        \"\"\"\n        if self.model_number != \"KIM101\":\n            raise TypeError(\n                \"This command is only valid with \"\n                \"a KIM101 controller. Your \"\n                \"controller is a {}.\".format(self.model_number)\n            )\n\n        pkt = _packets.ThorLabsPacket(\n            message_id=_cmds.ThorLabsCommands.PZMOT_REQ_PARAMS,\n            param1=0x2B,\n            param2=0x00,\n            dest=self.destination,\n            source=0x01,\n            data=None,\n        )\n\n        resp = self.querypacket(\n            pkt, expect=_cmds.ThorLabsCommands.PZMOT_GET_PARAMS, expect_data_len=4\n        )\n\n        ret_val = int(struct.unpack(\"<HH\", resp.data)[1])\n\n        if ret_val == 5:\n            return 1\n        elif ret_val == 6:\n            return 2\n        else:\n            return 0\n\n    @enabled_multi.setter\n    def enabled_multi(self, mode):\n        if self.model_number != \"KIM101\":\n            raise TypeError(\n                \"This command is only valid with \"\n                \"a KIM101 controller. Your \"\n                \"controller is a {}.\".format(self.model_number)\n            )\n\n        if mode == 0:\n            param = 0x00\n        elif mode == 1:\n            param = 0x05\n        elif mode == 2:\n            param = 0x06\n        else:\n            raise ValueError(\n                \"Please select a valid mode: 0 - all \"\n                \"disabled, 1 - Channel 1 & 2 enabled, \"\n                \"2 - Channel 3 & 4 enabled.\"\n            )\n\n        pkt = _packets.ThorLabsPacket(\n            message_id=_cmds.ThorLabsCommands.PZMOT_SET_PARAMS,\n            param1=None,\n            param2=None,\n            dest=self.destination,\n            source=0x01,\n            data=struct.pack(\"<HH\", 0x2B, param),\n        )\n\n        self.sendpacket(pkt)\n\n\nclass APTPiezoStage(APTPiezoDevice):\n    \"\"\"\n    Class representing a Thorlabs APT piezo stage\n    \"\"\"\n\n    class PiezoChannel(APTPiezoDevice.PiezoDeviceChannel):\n        \"\"\"\n        Class representing a single piezo channel within a piezo stage\n        on the Thorlabs APT controller.\n        \"\"\"\n\n        # PIEZO COMMANDS #\n\n        @property\n        def position_control_closed(self):\n            \"\"\"\n            Gets the status if the position control is closed or not.\n\n            `True` means that the position control is closed, `False` otherwise\n\n            :type: `bool`\n            \"\"\"\n            pkt = _packets.ThorLabsPacket(\n                message_id=_cmds.ThorLabsCommands.PZ_REQ_POSCONTROLMODE,\n                param1=self._idx_chan,\n                param2=0x00,\n                dest=self._apt.destination,\n                source=0x01,\n                data=None,\n            )\n            resp = self._apt.querypacket(\n                pkt, expect=_cmds.ThorLabsCommands.PZ_GET_POSCONTROLMODE\n            )\n            return bool((resp.parameters[1] - 1) & 1)\n\n        def change_position_control_mode(self, closed, smooth=True):\n            \"\"\"\n            Changes the position control mode of the piezo channel\n\n            :param bool closed: `True` for closed, `False` for open\n            :param bool smooth: `True` for smooth, `False` for otherwise.\n                Default is `True`.\n            \"\"\"\n            mode = 1 + (int(closed) | int(smooth) << 1)\n            pkt = _packets.ThorLabsPacket(\n                message_id=_cmds.ThorLabsCommands.PZ_SET_POSCONTROLMODE,\n                param1=self._idx_chan,\n                param2=mode,\n                dest=self._apt.destination,\n                source=0x01,\n                data=None,\n            )\n            self._apt.sendpacket(pkt)\n\n        @property\n        def output_position(self):\n            \"\"\"\n            Gets/sets the output position for the piezo channel.\n\n            :type: `str`\n            \"\"\"\n            pkt = _packets.ThorLabsPacket(\n                message_id=_cmds.ThorLabsCommands.PZ_REQ_OUTPUTPOS,\n                param1=self._idx_chan,\n                param2=0x00,\n                dest=self._apt.destination,\n                source=0x01,\n                data=None,\n            )\n            resp = self._apt.querypacket(\n                pkt, expect=_cmds.ThorLabsCommands.PZ_GET_OUTPUTPOS, expect_data_len=4\n            )\n            # chan, pos\n            _, pos = struct.unpack(\"<HH\", resp.data)\n            return pos\n\n        @output_position.setter\n        def output_position(self, pos):\n            pkt = _packets.ThorLabsPacket(\n                message_id=_cmds.ThorLabsCommands.PZ_SET_OUTPUTPOS,\n                param1=None,\n                param2=None,\n                dest=self._apt.destination,\n                source=0x01,\n                data=struct.pack(\"<HH\", self._idx_chan, pos),\n            )\n            self._apt.sendpacket(pkt)\n\n    _channel_type = PiezoChannel\n\n\nclass APTStrainGaugeReader(APTPiezoDevice):\n    \"\"\"\n    Class representing a Thorlabs APT strain gauge reader.\n\n    .. warning:: This is not currently implemented\n    \"\"\"\n\n    class StrainGaugeChannel(APTPiezoDevice.PiezoDeviceChannel):\n        \"\"\"\n        Class representing a single strain gauge channel attached to a\n        `APTStrainGaugeReader` on the Thorlabs APT controller.\n\n        .. warning:: This is not currently implemented\n        \"\"\"\n\n    _channel_type = StrainGaugeChannel\n\n\nclass APTMotorController(ThorLabsAPT):\n    \"\"\"\n    Class representing a Thorlabs APT motor controller.\n\n    .. note:: A motor model must be selected in order to use unitful\n        distances.\n\n    Example:\n        >>> import instruments as ik\n        >>> import instruments.units as u\n\n        >>> # load the controller, a KDC101 cube\n        >>> kdc = ik.thorlabs.APTMotorController.open_serial(\"/dev/ttyUSB0\", baud=115200)\n        >>> # assign a channel to `ch`\n        >>> ch = kdc.channel[0]\n        >>> # select the stage that is connected to the controller\n        >>> ch.motor_model = 'PRM1-Z8'  # a rotation stage\n\n        >>> # home the stage\n        >>> ch.go_home()\n        >>> # move to 52 degrees absolute position\n        >>> ch.move(u.Quantity(52, u.deg))\n        >>> # move 10 degrees back from current position\n        >>> ch.move(u.Quantity(-10, u.deg), absolute=False)\n    \"\"\"\n\n    class MotorChannel(ThorLabsAPT.APTChannel):\n        \"\"\"\n        Class representing a single motor attached to a Thorlabs APT motor\n        controller (`APTMotorController`).\n        \"\"\"\n\n        # INSTANCE VARIABLES #\n\n        _motor_model = None\n\n        #: Sets the scale between the encoder counts and physical units\n        #: for the position, velocity and acceleration parameters of this\n        #: channel. By default, set to dimensionless, indicating that the proper\n        #: scale is not known.\n        #:\n        #: In keeping with the APT protocol documentation, the scale factor\n        #: is multiplied by the physical quantity to get the encoder count,\n        #: such that scale factors should have units similar to microsteps/mm,\n        #: in the example of a linear motor.\n        #:\n        #: Encoder counts are represented by the quantities package unit\n        #: \"ct\", which is considered dimensionally equivalent to dimensionless.\n        #: Finally, note that the \"/s\" and \"/s**2\" are not included in scale\n        #: factors, so as to produce quantities of dimension \"ct/s\" and\n        #: \"ct/s**2\"\n        #: from dimensionful input.\n        #:\n        #: For more details, see the APT protocol documentation.\n        scale_factors = (u.Quantity(1, \"dimensionless\"),) * 3\n\n        _motion_timeout = u.Quantity(10, \"second\")\n\n        __SCALE_FACTORS_BY_MODEL = {\n            # TODO: add other tables here.\n            re.compile(\"TST001|BSC00.|BSC10.|MST601\"): {\n                # Note that for these drivers, the scale factors are identical\n                # for position, velcoity and acceleration. This is not true for\n                # all drivers!\n                \"DRV001\": (u.Quantity(51200, \"count/mm\"),) * 3,\n                \"DRV013\": (u.Quantity(25600, \"count/mm\"),) * 3,\n                \"DRV014\": (u.Quantity(25600, \"count/mm\"),) * 3,\n                \"DRV113\": (u.Quantity(20480, \"count/mm\"),) * 3,\n                \"DRV114\": (u.Quantity(20480, \"count/mm\"),) * 3,\n                \"FW103\": (u.Quantity(25600 / 360, \"count/deg\"),) * 3,\n                \"NR360\": (u.Quantity(25600 / 5.4546, \"count/deg\"),) * 3,\n            },\n            re.compile(\"TDC001|KDC101\"): {\n                \"MTS25-Z8\": (\n                    1 / u.Quantity(34304, \"mm/count\"),\n                    NotImplemented,\n                    NotImplemented,\n                ),\n                \"MTS50-Z8\": (\n                    1 / u.Quantity(34304, \"mm/count\"),\n                    NotImplemented,\n                    NotImplemented,\n                ),\n                # TODO: Z8xx and Z6xx models. Need to add regex support to motor models, too.\n                \"PRM1-Z8\": (\n                    u.Quantity(1919.64, \"count/deg\"),\n                    u.Quantity(42941.66, u.sec / u.deg),\n                    u.Quantity(14.66, u.sec**2 / u.deg),\n                ),\n            },\n        }\n\n        __STATUS_BIT_MASK = {\n            \"CW_HARD_LIM\": 0x00000001,\n            \"CCW_HARD_LIM\": 0x00000002,\n            \"CW_SOFT_LIM\": 0x00000004,\n            \"CCW_SOFT_LIM\": 0x00000008,\n            \"CW_MOVE_IN_MOTION\": 0x00000010,\n            \"CCW_MOVE_IN_MOTION\": 0x00000020,\n            \"CW_JOG_IN_MOTION\": 0x00000040,\n            \"CCW_JOG_IN_MOTION\": 0x00000080,\n            \"MOTOR_CONNECTED\": 0x00000100,\n            \"HOMING_IN_MOTION\": 0x00000200,\n            \"HOMING_COMPLETE\": 0x00000400,\n            \"INTERLOCK_STATE\": 0x00001000,\n        }\n\n        # IK-SPECIFIC PROPERTIES #\n        # These properties don't correspond to any particular functionality\n        # of the underlying device, but control how we interact with it.\n\n        @property\n        def motion_timeout(self):\n            \"\"\"\n            Gets/sets the motor channel motion timeout.\n\n            :units: Seconds\n            :type: `~pint.Quantity`\n            \"\"\"\n            return self._motion_timeout\n\n        @motion_timeout.setter\n        def motion_timeout(self, newval):\n            self._motion_timeout = assume_units(newval, u.second)\n\n        # UNIT CONVERSION METHODS #\n\n        def _set_scale(self, motor_model):\n            \"\"\"\n            Sets the scale factors for this motor channel, based on the model\n            of the attached motor and the specifications of the driver of which\n            this is a channel.\n\n            :param str motor_model: Name of the model of the attached motor,\n                as indicated in the APT protocol documentation (page 14, v9).\n            \"\"\"\n            for driver_re, motor_dict in self.__SCALE_FACTORS_BY_MODEL.items():\n                if driver_re.match(self._apt.model_number) is not None:\n                    if motor_model in motor_dict:\n                        self.scale_factors = motor_dict[motor_model]\n                        return\n                    else:\n                        break\n            # If we've made it down here, emit a warning that we didn't find the\n            # model.\n            logger.warning(\n                \"Scale factors for controller %s and motor %s are \" \"unknown\",\n                self._apt.model_number,\n                motor_model,\n            )\n\n        # We copy the docstring below, so it's OK for this method\n        # to not have a docstring of its own.\n        # pylint: disable=missing-docstring\n        def set_scale(self, motor_model):\n            warnings.warn(\n                \"The set_scale method has been deprecated in favor \"\n                \"of the motor_model property.\",\n                DeprecationWarning,\n            )\n            return self._set_scale(motor_model)\n\n        set_scale.__doc__ = _set_scale.__doc__\n\n        @property\n        def motor_model(self):\n            \"\"\"\n            Gets or sets the model name of the attached motor.\n            Note that the scale factors for this motor channel are based on the model\n            of the attached motor and the specifications of the driver of which\n            this is a channel, such that setting a new motor model will update\n            the scale factors accordingly.\n\n            :type: `str` or `None`\n            \"\"\"\n            return self._motor_model\n\n        @motor_model.setter\n        def motor_model(self, newval):\n            self._set_scale(newval)\n            self._motor_model = newval\n\n        # MOTOR COMMANDS #\n\n        @property\n        def backlash_correction(self):\n            \"\"\"Get / set backlash correctionf or given stage.\n\n            If no units are given, ``u.counts`` are assumed. If you have\n            the stage defined (see example below), unitful values can be\n            used for setting the backlash correction, e.g., ``u.mm`` or\n            ``u.deg``.\n\n            :return: Unitful quantity of backlash correction.\n\n            Example:\n                >>> import instruments as ik\n                >>> import instruments.units as u\n\n                >>> # load the controller, a KDC101 cube\n                >>> kdc = ik.thorlabs.APTMotorController.open_serial(\"/dev/ttyUSB0\", baud=115200)\n                >>> # assign a channel to `ch`\n                >>> ch = kdc.channel[0]\n                >>> ch.motor_model = 'PRM1-Z8'  # select rotation stage\n\n                >>> ch.backlash_correction = 4 * u.deg  # set it to 4 degrees\n                >>> ch.backlash_correction  # read it back\n                <Quantity(4, 'degree')>\n            \"\"\"\n            pkt = _packets.ThorLabsPacket(\n                message_id=_cmds.ThorLabsCommands.MOT_REQ_GENMOVEPARAMS,\n                param1=self._idx_chan,\n                param2=0x00,\n                dest=self._apt.destination,\n                source=0x01,\n                data=None,\n            )\n            response = self._apt.querypacket(\n                pkt,\n                expect=_cmds.ThorLabsCommands.MOT_GET_GENMOVEPARAMS,\n                expect_data_len=6,\n            )\n            # chan, pos\n            _, pos = struct.unpack(\"<Hl\", response.data)\n            return u.Quantity(pos, \"counts\") / self.scale_factors[0]\n\n        @backlash_correction.setter\n        def backlash_correction(self, pos):\n            if not isinstance(pos, u.Quantity):\n                pos_ec = int(pos)\n            else:\n                if pos.units == u.counts:\n                    pos_ec = int(pos.magnitude)\n                else:\n                    scaled_pos = pos * self.scale_factors[0]\n                    # Force a unit error.\n                    try:\n                        pos_ec = int(scaled_pos.to(u.counts).magnitude)\n                    except:\n                        raise ValueError(\n                            \"Provided units are not compatible \"\n                            \"with current motor scale factor.\"\n                        )\n            # create package to send\n            pkt = _packets.ThorLabsPacket(\n                message_id=_cmds.ThorLabsCommands.MOT_SET_GENMOVEPARAMS,\n                param1=None,\n                param2=None,\n                dest=self._apt.destination,\n                source=0x01,\n                data=struct.pack(\"<Hl\", self._idx_chan, pos_ec),\n            )\n            self._apt.sendpacket(pkt)\n\n        @property\n        def home_parameters(self):\n            \"\"\"Get the home parameters for the motor channel.\n\n            Parameters are stage specific and not all parameters can be set\n            for every stage. For example, the MLS203 stage only allows the\n            homing velocity to be changed.\n\n            .. note:: When setting the quantity, pass `None` to values\n                that you want to leave unchanged (see example below).\n\n            .. note:: After changing the offset, the stage must be homed\n                in order to show the new offset in its values.\n\n            :return: Home Direction (1: forward/positive, 2 reverse/negative),\n                Limit Switch (1: hardware reverse, 4: hardware forward),\n                Home Velocity,\n                Offset distance\n            :rtype: Tuple[int, int, u.Quantity, u.Quantity]\n\n\n            Example:\n                >>> import instruments as ik\n                >>> import instruments.units as u\n\n                >>> # load the controller, a KDC101 cube\n                >>> kdc = ik.thorlabs.APTMotorController.open_serial(\"/dev/ttyUSB0\", baud=115200)\n                >>> # assign a channel to `ch`\n                >>> ch = kdc.channel[0]\n                >>> ch.motor_model = 'PRM1-Z8'  # select rotation stage\n\n                >>> # set offset distance to 4 degrees, leave other values\n                >>> ch.home_parameters = None, None, None, 4 * u.deg\n                >>> ch.home_parameters  # read it back\n                (2, 1, <Quantity(9.99, 'degree / second')>, <Quantity(3.99, 'degree')>)\n            \"\"\"\n            pkt = _packets.ThorLabsPacket(\n                message_id=_cmds.ThorLabsCommands.MOT_REQ_HOMEPARAMS,\n                param1=self._idx_chan,\n                param2=0x00,\n                dest=self._apt.destination,\n                source=0x01,\n                data=None,\n            )\n            response = self._apt.querypacket(\n                pkt,\n                expect=_cmds.ThorLabsCommands.MOT_GET_HOMEPARAMS,\n                expect_data_len=14,\n            )\n            # chan, home_dir, limit_switch, velocity, ,offset_dist\n            _, home_dir, lim_sw, vel, offset = struct.unpack(\"<HHHll\", response.data)\n            return (\n                int(home_dir),\n                int(lim_sw),\n                u.Quantity(vel) / self.scale_factors[1],\n                u.Quantity(offset, \"counts\") / self.scale_factors[0],\n            )\n\n        @home_parameters.setter\n        def home_parameters(self, values):\n            if len(values) != 4:\n                raise ValueError(\n                    \"Home parameters muust be set with four values: \"\n                    \"Home direction, limit switch settings, velocity, and offset. \"\n                    \"For settings you want to leave untouched, pass `None`.\"\n                )\n\n            # replace values that are `None`\n            if None in values:\n                set_params = self.home_parameters\n                values = [x if x is not None else y for x, y in zip(values, set_params)]\n\n            home_dir, lim_sw, velocity, offset = values\n            if isinstance(velocity, u.Quantity):\n                velocity = (velocity * self.scale_factors[1]).to_reduced_units()\n                if velocity.dimensionless:\n                    velocity = int(velocity.magnitude)\n                else:\n                    raise ValueError(\n                        \"Provided units for velocity are not compatible \"\n                        \"with current motor scale factor.\"\n                    )\n            if isinstance(offset, u.Quantity):\n                if offset.units == u.counts:\n                    offset = int(offset.magnitude)\n                else:\n                    scaled_vel = offset * self.scale_factors[0]\n                    try:\n                        offset = int(scaled_vel.to(u.counts).magnitude)\n                    except:\n                        raise ValueError(\n                            \"Provided units for offset are not compatible \"\n                            \"with current motor scale factor.\"\n                        )\n\n                # create package to send\n            pkt = _packets.ThorLabsPacket(\n                message_id=_cmds.ThorLabsCommands.MOT_SET_HOMEPARAMS,\n                param1=None,\n                param2=None,\n                dest=self._apt.destination,\n                source=0x01,\n                data=struct.pack(\n                    \"<HHHll\",\n                    self._idx_chan,\n                    int(home_dir),\n                    int(lim_sw),\n                    int(velocity),\n                    int(offset),\n                ),\n            )\n            self._apt.sendpacket(pkt)\n\n        @property\n        def status_bits(self):\n            \"\"\"\n            Gets the status bits for the specified motor channel.\n\n            .. note:: This command, as currently implemented, is only\n                available for certain devices and will result in an\n                ``OSError`` otherwise. Devices that work according to the\n                manual are: TSC001, KSC101, BSC10x, BSC20x, LTS150, LTS300,\n                MLJ050, MLJ150, TIM101, KIM101.\n\n            :type: `dict`\n            \"\"\"\n            # NOTE: the difference between MOT_REQ_STATUSUPDATE and\n            # MOT_REQ_DCSTATUSUPDATE confuses me\n            pkt = _packets.ThorLabsPacket(\n                message_id=_cmds.ThorLabsCommands.MOT_REQ_STATUSUPDATE,\n                param1=self._idx_chan,\n                param2=0x00,\n                dest=self._apt.destination,\n                source=0x01,\n                data=None,\n            )\n            # The documentation claims there are 14 data bytes, but it seems\n            # there are sometimes some extra random ones...\n            # fixme: wrong expected datatype? MOT_GET_STATUSUPDATE expected\n            resp_data = self._apt.querypacket(\n                pkt,\n                expect=_cmds.ThorLabsCommands.MOT_GET_POSCOUNTER,\n                expect_data_len=14,\n            ).data[:14]\n            # ch_ident, position, enc_count, status_bits\n            _, _, _, status_bits = struct.unpack(\"<HLLL\", resp_data)\n\n            status_dict = {\n                key: (status_bits & bit_mask > 0)\n                for key, bit_mask in self.__STATUS_BIT_MASK.items()\n            }\n\n            return status_dict\n\n        @property\n        def position(self):\n            \"\"\"\n            Gets the current position of the specified motor channel\n\n            :type: `~pint.Quantity`\n            \"\"\"\n            pkt = _packets.ThorLabsPacket(\n                message_id=_cmds.ThorLabsCommands.MOT_REQ_POSCOUNTER,\n                param1=self._idx_chan,\n                param2=0x00,\n                dest=self._apt.destination,\n                source=0x01,\n                data=None,\n            )\n            response = self._apt.querypacket(\n                pkt, expect=_cmds.ThorLabsCommands.MOT_GET_POSCOUNTER, expect_data_len=6\n            )\n            # chan, pos\n            _, pos = struct.unpack(\"<Hl\", response.data)\n            return u.Quantity(pos, \"counts\") / self.scale_factors[0]\n\n        @property\n        def position_encoder(self):\n            \"\"\"\n            Gets the position of the encoder of the specified motor channel\n\n            :type: `~pint.Quantity`\n            :units: Encoder ``counts``\n            \"\"\"\n            pkt = _packets.ThorLabsPacket(\n                message_id=_cmds.ThorLabsCommands.MOT_REQ_ENCCOUNTER,\n                param1=self._idx_chan,\n                param2=0x00,\n                dest=self._apt.destination,\n                source=0x01,\n                data=None,\n            )\n            response = self._apt.querypacket(\n                pkt, expect=_cmds.ThorLabsCommands.MOT_GET_ENCCOUNTER, expect_data_len=6\n            )\n            # chan, pos\n            _, pos = struct.unpack(\"<Hl\", response.data)\n            return u.Quantity(pos, \"counts\")\n\n        def go_home(self):\n            \"\"\"\n            Instructs the specified motor channel to return to its home\n            position\n            \"\"\"\n            pkt = _packets.ThorLabsPacket(\n                message_id=_cmds.ThorLabsCommands.MOT_MOVE_HOME,\n                param1=self._idx_chan,\n                param2=0x00,\n                dest=self._apt.destination,\n                source=0x01,\n                data=None,\n            )\n            _ = self._apt.querypacket(\n                pkt,\n                expect=_cmds.ThorLabsCommands.MOT_MOVE_HOMED,\n                timeout=self.motion_timeout,\n            )\n\n        def move(self, pos, absolute=True):\n            \"\"\"\n            Instructs the specified motor channel to move to a specific\n            location. The provided position can be either an absolute or\n            relative position.\n\n            :param pos: The position to move to. Provided value will be\n                converted to encoder counts.\n            :type pos: `~pint.Quantity`\n            :units pos: As specified, or assumed to of units encoder counts\n\n            :param bool absolute: Specify if the position is a relative or\n                absolute position. ``True`` means absolute, while ``False``\n                is for a relative move.\n\n            Example:\n                >>> import instruments as ik\n                >>> import instruments.units as u\n\n                >>> # load the controller, a KDC101 cube\n                >>> kdc = ik.thorlabs.APTMotorController.open_serial(\"/dev/ttyUSB0\", baud=115200)\n                >>> # assign a channel to `ch`\n                >>> ch = kdc.channel[0]\n                >>> # select the stage that is connected to the controller\n                >>> ch.motor_model = 'PRM1-Z8'  # a rotation stage\n\n                >>> # move to 32 degrees absolute position\n                >>> ch.move(u.Quantity(32, u.deg))\n\n                >>> # move 10 degrees forward from current position\n                >>> ch.move(u.Quantity(10, u.deg), absolute=False)\n            \"\"\"\n            # Handle units as follows:\n            # 1. Treat raw numbers as encoder counts.\n            # 2. If units are provided (as a Quantity), check if they're encoder\n            #    counts. If they aren't, apply scale factor.\n            if not isinstance(pos, u.Quantity):\n                pos_ec = int(pos)\n            else:\n                if pos.units == u.counts:\n                    pos_ec = int(pos.magnitude)\n                else:\n                    scaled_pos = pos * self.scale_factors[0]\n                    # Force a unit error.\n                    try:\n                        pos_ec = int(scaled_pos.to(u.counts).magnitude)\n                    except:\n                        raise ValueError(\n                            \"Provided units are not compatible \"\n                            \"with current motor scale factor.\"\n                        )\n\n            # Now that we have our position as an integer number of encoder\n            # counts, we're good to move.\n            pkt = _packets.ThorLabsPacket(\n                message_id=(\n                    _cmds.ThorLabsCommands.MOT_MOVE_ABSOLUTE\n                    if absolute\n                    else _cmds.ThorLabsCommands.MOT_MOVE_RELATIVE\n                ),\n                param1=None,\n                param2=None,\n                dest=self._apt.destination,\n                source=0x01,\n                data=struct.pack(\"<Hl\", self._idx_chan, pos_ec),\n            )\n\n            _ = self._apt.querypacket(\n                pkt,\n                expect=_cmds.ThorLabsCommands.MOT_MOVE_COMPLETED,\n                timeout=self.motion_timeout,\n                expect_data_len=14,\n            )\n\n    _channel_type = MotorChannel\n\n    # CONTROLLER PROPERTIES AND METHODS #\n"
  },
  {
    "path": "src/instruments/toptica/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing Toptica instruments\n\"\"\"\n\nfrom .topmode import TopMode\n"
  },
  {
    "path": "src/instruments/toptica/topmode.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides the support for the Toptica Topmode diode laser.\n\nClass originally contributed by Catherine Holloway.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom enum import IntEnum\n\nfrom instruments.abstract_instruments import Instrument\nfrom instruments.toptica.toptica_utils import convert_toptica_boolean as ctbool\nfrom instruments.toptica.toptica_utils import convert_toptica_datetime as ctdate\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import ProxyList\n\n# CLASSES #####################################################################\n\n\nclass TopMode(Instrument):\n    \"\"\"\n    Communicates with a `Toptica Topmode`_ instrument.\n\n    The TopMode is a diode laser with active stabilization, produced by Toptica.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> tm = ik.toptica.TopMode.open_serial('/dev/ttyUSB0', 115200)\n    >>> print(tm.laser[0].wavelength)\n    \"\"\"\n\n    def __init__(self, filelike):\n        super().__init__(filelike)\n        self.prompt = \"> \"\n        self.terminator = \"\\r\\n\"\n\n    def _ack_expected(self, msg=\"\"):\n        if \"reboot\" in msg:\n            return [msg, \"reboot process started.\"]\n        elif \"start-correction\" in msg:\n            return [msg, \"()\"]\n\n        return msg\n\n    # ENUMS #\n\n    class CharmStatus(IntEnum):\n        \"\"\"\n        Enum containing valid charm statuses for the lasers\n        \"\"\"\n\n        un_initialized = 0\n        in_progress = 1\n        success = 2\n        failure = 3\n\n    # INNER CLASSES #\n\n    class Laser:\n        \"\"\"\n        Class representing a laser on the Toptica Topmode.\n\n        .. warning:: This class should NOT be manually created by the user. It\n            is designed to be initialized by the `Topmode` class.\n        \"\"\"\n\n        def __init__(self, parent, idx):\n            self.parent = parent\n            self.name = f\"laser{idx + 1}\"\n\n        # PROPERTIES #\n\n        @property\n        def serial_number(self):\n            \"\"\"\n            Gets the serial number of the laser\n\n            :return: The serial number of the specified laser\n            :type: `str`\n            \"\"\"\n            return self.parent.reference(self.name + \":serial-number\")\n\n        @property\n        def model(self):\n            \"\"\"\n            Gets the model type of the laser\n\n            :return: The model of the specified laser\n            :type: `str`\n            \"\"\"\n            return self.parent.reference(self.name + \":model\")\n\n        @property\n        def wavelength(self):\n            \"\"\"\n            Gets the wavelength of the laser\n\n            :return: The wavelength of the specified laser\n            :units: Nanometers (nm)\n            :type: `~pint.Quantity`\n            \"\"\"\n            return float(self.parent.reference(self.name + \":wavelength\")) * u.nm\n\n        @property\n        def production_date(self):\n            \"\"\"\n            Gets the production date of the laser\n\n            :return: The production date of the specified laser\n            :type: `str`\n            \"\"\"\n            return self.parent.reference(self.name + \":production-date\")\n\n        @property\n        def enable(self):\n            \"\"\"\n            Gets/sets the enable/disable status of the laser. Value of `True`\n            is for enabled, and `False` for disabled.\n\n            :return: Enable status of the specified laser\n            :type: `bool`\n            \"\"\"\n            return ctbool(self.parent.reference(self.name + \":emission\"))\n\n        @enable.setter\n        def enable(self, newval):\n            if not isinstance(newval, bool):\n                raise TypeError(\n                    \"Emission status must be a boolean, got: \" \"{}\".format(type(newval))\n                )\n            if not self.is_connected:\n                raise RuntimeError(\n                    \"Laser was not recognized by charm \" \"controller. Is it plugged in?\"\n                )\n            self.parent.set(self.name + \":enable-emission\", newval)\n\n        @property\n        def is_connected(self):\n            \"\"\"\n            Check whether a laser is connected.\n\n            :return: Whether the controller successfully connected to a laser\n            :type: `bool`\n            \"\"\"\n            if self.serial_number == \"unknown\":\n                return False\n            return True\n\n        @property\n        def on_time(self):\n            \"\"\"\n            Gets the 'on time' value for the laser\n\n            :return: The 'on time' value for the specified laser\n            :units: Seconds (s)\n            :type: `~pint.Quantity`\n            \"\"\"\n            return float(self.parent.reference(self.name + \":ontime\")) * u.s\n\n        @property\n        def charm_status(self):\n            \"\"\"\n            Gets the 'charm status' of the laser\n\n            :return: The 'charm status' of the specified laser\n            :type: `bool`\n            \"\"\"\n            response = int(self.parent.reference(self.name + \":health\"))\n            return (response >> 7) % 2 == 1\n\n        @property\n        def temperature_control_status(self):\n            \"\"\"\n            Gets the temperature control status of the laser\n\n            :return: The temperature control status of the specified laser\n            :type: `bool`\n            \"\"\"\n            response = int(self.parent.reference(self.name + \":health\"))\n            return (response >> 5) % 2 == 1\n\n        @property\n        def current_control_status(self):\n            \"\"\"\n            Gets the current control status of the laser\n\n            :return: The current control status of the specified laser\n            :type: `bool`\n            \"\"\"\n            response = int(self.parent.reference(self.name + \":health\"))\n            return (response >> 6) % 2 == 1\n\n        @property\n        def tec_status(self):\n            \"\"\"\n            Gets the TEC status of the laser\n\n            :return: The TEC status of the specified laser\n            :type: `bool`\n            \"\"\"\n            return ctbool(self.parent.reference(self.name + \":tec:ready\"))\n\n        @property\n        def intensity(self):\n            \"\"\"\n            Gets the intensity of the laser. This property is unitless.\n\n            :return: the intensity of the specified laser\n            :units: Unitless\n            :type: `float`\n            \"\"\"\n            return float(self.parent.reference(self.name + \":intensity\"))\n\n        @property\n        def mode_hop(self):\n            \"\"\"\n            Gets whether the laser has mode-hopped\n\n            :return: Mode-hop status of the specified laser\n            :type: `bool`\n            \"\"\"\n            response = self.parent.reference(self.name + \":charm:reg:mh-occurred\")\n            return ctbool(response)\n\n        @property\n        def lock_start(self):\n            \"\"\"\n            Gets the date and time of the start of mode-locking\n\n            :return: The datetime of start of mode-locking for specified laser\n            :type: `datetime`\n            \"\"\"\n            # if mode locking has not started yet, the device will respond with\n            # an empty date string. This causes a problem with ctdate.\n            _corr_stat = self.correction_status\n            if (\n                _corr_stat == TopMode.CharmStatus.un_initialized\n                or _corr_stat == TopMode.CharmStatus.failure\n            ):\n                raise RuntimeError(\"Laser has not yet successfully locked\")\n\n            response = self.parent.reference(self.name + \":charm:reg:started\")\n            return ctdate(response)\n\n        @property\n        def first_mode_hop_time(self):\n            \"\"\"\n            Gets the date and time of the first mode hop\n\n            :return: The datetime of the first mode hop for the specified laser\n            :type: `datetime`\n            \"\"\"\n            # if the mode has not hopped, the device will respond with an empty\n            # date string. This causes a problem with ctdate.\n            if not self.mode_hop:\n                raise RuntimeError(\"Mode hop not detected\")\n            response = self.parent.reference(self.name + \":charm:reg:first-mh\")\n\n            return ctdate(response)\n\n        @property\n        def latest_mode_hop_time(self):\n            \"\"\"\n            Gets the date and time of the latest mode hop\n\n            :return: The datetime of the latest mode hop for the\n                specified laser\n            :type: `datetime`\n            \"\"\"\n            # if the mode has not hopped, the device will respond with an empty\n            # date string. This causes a problem with ctdate.\n            if not self.mode_hop:\n                raise RuntimeError(\"Mode hop not detected\")\n            response = self.parent.reference(self.name + \":charm:reg:latest-mh\")\n            return ctdate(response)\n\n        @property\n        def correction_status(self):\n            \"\"\"\n            Gets the correction status of the laser\n\n            :return: The correction status of the specified laser\n            :type: `~TopMode.CharmStatus`\n            \"\"\"\n            value = self.parent.reference(self.name + \":charm:correction-status\")\n            return TopMode.CharmStatus(int(value))\n\n        # METHODS #\n\n        def correction(self):\n            \"\"\"\n            Run the correction against the specified laser\n            \"\"\"\n            if self.correction_status == TopMode.CharmStatus.un_initialized:\n                self.parent.execute(self.name + \":charm:start-correction-initial\")\n            else:\n                self.parent.execute(self.name + \":charm:start-correction\")\n\n    # TOPMODE CONTROL LANGUAGE #\n\n    def execute(self, command):\n        \"\"\"\n        Sends an execute command to the Topmode. This is used to automatically\n        append (exec ' + command + ) to your command.\n\n        :param str command: The command to be executed.\n        \"\"\"\n        self.sendcmd(\"(exec '\" + command + \")\")\n\n    def set(self, param, value):\n        \"\"\"\n        Sends a param-set command to the Topmode. This is used to automatically\n        handle appending \"param-set!\" and the rest of the param-set message\n        structure to your message.\n\n        :param str param: Parameter that will be set\n        :param value: Value that the parameter will be set to\n        :type value: `str`, `tuple`, `list`, or `bool`\n        \"\"\"\n\n        if isinstance(value, str):\n            self.query(f'(param-set! \\'{param} \"{value}\")')\n        elif isinstance(value, (tuple, list)):\n            self.query(\"(param-set! '{} '({}))\".format(param, \" \".join(value)))\n        elif isinstance(value, bool):\n            value = \"t\" if value else \"f\"\n            self.query(f\"(param-set! '{param} #{value})\")\n\n    def reference(self, param):\n        \"\"\"\n        Sends a reference commands to the Topmode. This is effectively a query\n        request. It will append the required (param-ref ' + param + ).\n\n        :param str param: Parameter that should be queried\n        :return: Response to the reference request\n        :rtype: `str`\n        \"\"\"\n        response = self.query(f\"(param-ref '{param})\").replace('\"', \"\")\n        return response\n\n    def display(self, param):\n        \"\"\"\n        Sends a display command to the Topmode.\n\n        :param str param: Parameter that will be sent with a display request\n        :return: Response to the display request\n        \"\"\"\n        return self.query(f\"(param-disp '{param})\")\n\n    # PROPERTIES #\n\n    @property\n    def laser(self):\n        \"\"\"\n        Gets a specific Topmode laser object. The desired laser is\n        specified like one would access a list.\n\n        For example, the following would print the wavelength from laser 1:\n\n        >>> import instruments as ik\n        >>> import instruments.units as u\n        >>> tm = ik.toptica.TopMode.open_serial('/dev/ttyUSB0', 115200)\n        >>> print(tm.laser[0].wavelength)\n\n        :rtype: `~TopMode.Laser`\n        \"\"\"\n        return ProxyList(self, self.Laser, range(2))\n\n    @property\n    def enable(self):\n        \"\"\"\n        is the laser lasing?\n        :return:\n        \"\"\"\n        return ctbool(self.reference(\"emission\"))\n\n    @enable.setter\n    def enable(self, newval):\n        if not isinstance(newval, bool):\n            raise TypeError(\n                \"Emission status must be a boolean, \" \"got: {}\".format(type(newval))\n            )\n        self.set(\"enable-emission\", newval)\n\n    @property\n    def locked(self):\n        \"\"\"\n        Gets the key switch lock status\n\n        :return: `True` if key switch is locked, `False` otherwise\n        :type: `bool`\n        \"\"\"\n        return ctbool(self.reference(\"front-key-locked\"))\n\n    @property\n    def interlock(self):\n        \"\"\"\n        Gets the interlock switch open state\n\n        :return: `True` if interlock switch is open, `False` otherwise\n        :type: `bool`\n        \"\"\"\n        return ctbool(self.reference(\"interlock-open\"))\n\n    @property\n    def firmware(self):\n        \"\"\"\n        Gets the firmware version of the charm controller\n\n        :return: The firmware version of the charm controller\n        :type: `tuple`\n        \"\"\"\n        firmware = tuple(map(int, self.reference(\"fw-ver\").split(\".\")))\n        return firmware\n\n    @property\n    def fpga_status(self):\n        \"\"\"\n        Gets the FPGA health status\n\n        :return: `False` if there has been a failure for the FPGA,\n            `True` otherwise\n        :type: `bool`\n        \"\"\"\n        response = self.reference(\"system-health\")\n        if response.find(\"#f\") >= 0:\n            return False\n        response = int(response)\n        return False if response % 2 else True\n\n    @property\n    def serial_number(self):\n        \"\"\"\n        Gets the serial number of the charm controller\n\n        :return: The serial number of the charm controller\n        :type: `str`\n        \"\"\"\n        return self.reference(\"serial-number\")\n\n    @property\n    def temperature_status(self):\n        \"\"\"\n        Gets the temperature controller board health status\n\n        :return: `False` if there has been a failure for the temperature\n            controller board, `True` otherwise\n        :type: `bool`\n        \"\"\"\n        response = int(self.reference(\"system-health\"))\n        return False if (response >> 1) % 2 else True\n\n    @property\n    def current_status(self):\n        \"\"\"\n        Gets the current controller board health status\n\n        :return: `False` if there has been a failure for the current controller\n            board, `True` otherwise\n        :type: `bool`\n        \"\"\"\n        response = int(self.reference(\"system-health\"))\n        return False if (response >> 2) % 2 else True\n\n    # METHODS #\n\n    def reboot(self):\n        \"\"\"\n        Reboots the system (note that the serial connect might have to be\n        re-opened after this)\n        \"\"\"\n        self.execute(\"reboot-system\")\n"
  },
  {
    "path": "src/instruments/toptica/toptica_utils.py",
    "content": "#!/usr/bin/env python\n\n\"\"\"\nContains common utility functions for Toptica-brand instruments\n\"\"\"\n\nfrom datetime import datetime\n\n\ndef convert_toptica_boolean(response):\n    \"\"\"\n    Converts the toptica boolean expression to a boolean\n    :param response: response string\n    :type response: str\n    :return: the converted boolean\n    :rtype: bool\n    \"\"\"\n    if response.find(\"Error: -3\") > -1:\n        return None\n    elif response.find(\"f\") > -1:\n        return False\n    elif response.find(\"t\") > -1:\n        return True\n    else:\n        raise ValueError(\"cannot convert: \" + str(response) + \" to boolean\")\n\n\ndef convert_toptica_datetime(response):\n    \"\"\"\n    Converts the toptical date format to a python time date\n    :param response: the string from the topmode\n    :type response: str\n    :return: the converted date\n    :rtype: 'datetime.datetime'\n    \"\"\"\n    if response.find('\"\"') >= 0:\n        return None\n\n    return datetime.strptime(response, \"%Y-%m-%d %H:%M:%S\")\n"
  },
  {
    "path": "src/instruments/units.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing custom units used by various instruments.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nimport pint\n\n# UNITS #######################################################################\n\nureg = pint.get_application_registry()\nureg.define(\"centibelmilliwatt = 1e-3 watt; logbase: 10; logfactor: 100 = cBm\")\n"
  },
  {
    "path": "src/instruments/util_fns.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing various utility functions\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport re\n\nfrom enum import Enum, IntEnum\nfrom instruments.units import ureg as u\n\n# CONSTANTS ###################################################################\n\n_IDX_REGEX = re.compile(r\"([a-zA-Z_][a-zA-Z0-9_]*)\\[(-?[0-9]*)\\]\")\n\n# FUNCTIONS ###################################################################\n\n\n# pylint: disable=too-many-arguments\ndef assume_units(value, units):\n    \"\"\"\n    If units are not provided for ``value`` (that is, if it is a raw\n    `float`), then returns a `~pint.Quantity` with magnitude\n    given by ``value`` and units given by ``units``.\n\n    :param value: A value that may or may not be unitful.\n    :param units: Units to be assumed for ``value`` if it does not already\n        have units.\n\n    :return: A unitful quantity that has either the units of ``value`` or\n        ``units``, depending on if ``value`` is unitful.\n    :rtype: `Quantity`\n    \"\"\"\n    if isinstance(value, u.Quantity):\n        return value\n    elif isinstance(value, str):\n        value = u.Quantity(value)\n        if value.dimensionless:\n            return u.Quantity(value.magnitude, units)\n        return value\n    return u.Quantity(value, units)\n\n\ndef setattr_expression(target, name_expr, value):\n    \"\"\"\n    Recursively calls getattr/setattr for attribute\n    names that are miniature expressions with subscripting.\n    For instance, of the form ``a[0].b``.\n    \"\"\"\n    # Allow \".\" in attribute names so that we can set attributes\n    # recursively.\n    if \".\" in name_expr:\n        # Recursion: We have to strip off a level of getattr.\n        head, name_expr = name_expr.split(\".\", 1)\n        match = _IDX_REGEX.match(head)\n        if match:\n            head_name, head_idx = match.groups()\n            target = getattr(target, head_name)[int(head_idx)]\n        else:\n            target = getattr(target, head)\n\n        setattr_expression(target, name_expr, value)\n    else:\n        # Base case: We're in the last part of a dot-expression.\n        match = _IDX_REGEX.match(name_expr)\n        if match:\n            name, idx = match.groups()\n            getattr(target, name)[int(idx)] = value\n        else:\n            setattr(target, name_expr, value)\n\n\ndef convert_temperature(temperature, base):\n    \"\"\"\n    Obsolete with the transition to Pint from Quantities.\n\n    :param temperature: A quantity with units of Kelvin, Celsius, or Fahrenheit\n    :type temperature: `pint.Quantity`\n    :param base: A temperature unit to convert to\n    :type base: `pint.Quantity`\n\n    :return: The converted temperature\n    :rtype: `pint.Quantity`\n    \"\"\"\n    newval = assume_units(temperature, u.degC)\n    return newval.to(base)\n\n\ndef split_unit_str(s, default_units=u.dimensionless, lookup=None):\n    \"\"\"\n    Given a string of the form \"12 C\" or \"14.7 GHz\", returns a tuple of the\n    numeric part and the unit part, irrespective of how many (if any) whitespace\n    characters appear between.\n\n    By design, the tuple should be such that it can be unpacked into\n    :func:`u.Quantity`::\n\n        >>> u.Quantity(*split_unit_str(\"1 s\"))\n        array(1) * s\n\n    For this reason, the second element of the tuple may be a unit or\n    a string, depending, since the quantity constructor takes either.\n\n    :param str s: Input string that will be split up\n    :param default_units: If no units are specified, this argument is given\n        as the units.\n    :param callable lookup: If specified, this function is called on the\n        units part of the input string. If `None`, no lookup is performed.\n        Lookups are never performed on the default units.\n    :rtype: `tuple` of a `float` and a `str` or `u.Quantity`\n    \"\"\"\n    if lookup is None:\n        lookup = lambda x: x\n\n    # Borrowed from:\n    # http://stackoverflow.com/questions/430079/how-to-split-strings-into-text-and-number\n    # Reg exp tweaked on May 30, 2015 by scasagrande to match on input with\n    # scientific notation. General flow borrowed from:\n    # http://www.regular-expressions.info/floatingpoint.html\n    regex = r\"([-+]?[0-9]*\\.?[0-9]+)([eE][-+]?[0-9]+)?\\s*([a-z]+)?\"\n    match = re.match(regex, str(s).strip(), re.I)\n    if match:\n        if match.groups()[1] is None:\n            val, _, units = match.groups()\n        else:\n            val = float(match.groups()[0]) * 10 ** float(match.groups()[1][1:])\n            units = match.groups()[2]\n\n        if units is None:\n            return float(val), default_units\n\n        return float(val), lookup(units)\n\n    try:\n        return float(s), default_units\n    except ValueError:\n        raise ValueError(f\"Could not split '{repr(s)}' into value and units.\")\n\n\ndef rproperty(fget=None, fset=None, doc=None, readonly=False, writeonly=False):\n    \"\"\"\n    Creates and returns a new property based on the input parameters.\n\n    :param function fget: Function to be called for the new property's getter\n    :param function fset: Function to be called for the new property's setter\n    :param str doc: Docstring for the new property\n    :param bool readonly: If `True`, the returned property does not have a\n        setter.\n    :param bool writeonly: If `True`, the returned property does not have a\n        getter. Both readonly and writeonly cannot both be `True`.\n    \"\"\"\n    if readonly and writeonly:\n        raise ValueError(\"Properties cannot be both read- and write-only.\")\n    if readonly:\n        return property(fget=fget, fset=None, doc=doc)\n    elif writeonly:\n        return property(fget=None, fset=fset, doc=doc)\n\n    return property(fget=fget, fset=fset, doc=doc)\n\n\ndef bool_property(\n    command,\n    set_cmd=None,\n    inst_true=\"ON\",\n    inst_false=\"OFF\",\n    doc=None,\n    readonly=False,\n    writeonly=False,\n    set_fmt=\"{} {}\",\n):\n    \"\"\"\n    Called inside of SCPI classes to instantiate boolean properties\n    of the device cleanly.\n    For example:\n\n    >>> my_property = bool_property(\n    ...     \"BEST:PROPERTY\",\n    ...     inst_true=\"ON\",\n    ...     inst_false=\"OFF\"\n    ... ) # doctest: +SKIP\n\n    This will result in \"BEST:PROPERTY ON\" or \"BEST:PROPERTY OFF\" being sent\n    when setting, and \"BEST:PROPERTY?\" being sent when getting.\n\n    :param str command: Name of the SCPI command corresponding to this property.\n        If parameter set_cmd is not specified, then this parameter is also used\n        for both getting and setting.\n    :param str set_cmd: If not `None`, this parameter sets the command string\n        to be used when sending commands with no return values to the\n        instrument. This allows for non-symmetric properties that have different\n        strings for getting vs setting a property.\n    :param str inst_true: String returned and accepted by the instrument for\n        `True` values.\n    :param str inst_false: String returned and accepted by the instrument for\n        `False` values.\n    :param str doc: Docstring to be associated with the new property.\n    :param bool readonly: If `True`, the returned property does not have a\n        setter.\n    :param bool writeonly: If `True`, the returned property does not have a\n        getter. Both readonly and writeonly cannot both be `True`.\n    :param str set_fmt: Specify the string format to use when sending a\n        non-query to the instrument. The default is \"{} {}\" which places a\n        space between the SCPI command the associated parameter. By switching\n        to \"{}={}\" an equals sign would instead be used as the separator.\n    \"\"\"\n\n    def _getter(self):\n        return self.query(command + \"?\").strip() == inst_true\n\n    def _setter(self, newval):\n        if not isinstance(newval, bool):\n            raise TypeError(\"Bool properties must be specified with a \" \"boolean value\")\n        self.sendcmd(\n            set_fmt.format(\n                command if set_cmd is None else set_cmd,\n                inst_true if newval else inst_false,\n            )\n        )\n\n    return rproperty(\n        fget=_getter, fset=_setter, doc=doc, readonly=readonly, writeonly=writeonly\n    )\n\n\ndef enum_property(\n    command,\n    enum,\n    set_cmd=None,\n    doc=None,\n    input_decoration=None,\n    output_decoration=None,\n    readonly=False,\n    writeonly=False,\n    set_fmt=\"{} {}\",\n):\n    \"\"\"\n    Called inside of SCPI classes to instantiate Enum properties\n    of the device cleanly.\n    The decorations can be functions which modify the incoming and outgoing\n    values for dumb instruments that do stuff like include superfluous quotes\n    that you might not want in your enum.\n    Example:\n    my_property = bool_property(\"BEST:PROPERTY\", enum_class)\n\n    :param str command: Name of the SCPI command corresponding to this property.\n        If parameter set_cmd is not specified, then this parameter is also used\n        for both getting and setting.\n    :param str set_cmd: If not `None`, this parameter sets the command string\n        to be used when sending commands with no return values to the\n        instrument. This allows for non-symmetric properties that have different\n        strings for getting vs setting a property.\n    :param type enum: Class derived from `Enum` representing valid values.\n    :param callable input_decoration: Function called on responses from\n        the instrument before passing to user code.\n    :param callable output_decoration: Function called on commands to the\n        instrument.\n    :param str doc: Docstring to be associated with the new property.\n    :param bool readonly: If `True`, the returned property does not have a\n        setter.\n    :param bool writeonly: If `True`, the returned property does not have a\n        getter. Both readonly and writeonly cannot both be `True`.\n    :param str set_fmt: Specify the string format to use when sending a\n        non-query to the instrument. The default is \"{} {}\" which places a\n        space between the SCPI command the associated parameter. By switching\n        to \"{}={}\" an equals sign would instead be used as the separator.\n    :param str get_cmd: If not `None`, this parameter sets the command string\n        to be used when reading/querying from the instrument. If used, the name\n        parameter is still used to set the command for pure-write commands to\n        the instrument.\n    \"\"\"\n\n    def _in_decor_fcn(val):\n        if input_decoration is None:\n            return val\n        elif hasattr(input_decoration, \"__get__\"):\n            return input_decoration.__get__(None, object)(val)\n        return input_decoration(val)\n\n    def _out_decor_fcn(val):\n        if output_decoration is None:\n            return val\n        elif hasattr(output_decoration, \"__get__\"):\n            return output_decoration.__get__(None, object)(val)\n        return output_decoration(val)\n\n    def _getter(self):\n        return enum(_in_decor_fcn(self.query(f\"{command}?\").strip()))\n\n    def _setter(self, newval):\n        try:  # First assume newval is Enum.value\n            newval = enum[newval]\n        except KeyError:  # Check if newval is Enum.name instead\n            try:\n                newval = enum(newval)\n            except ValueError:\n                raise ValueError(\"Enum property new value not in enum.\")\n        self.sendcmd(\n            set_fmt.format(\n                command if set_cmd is None else set_cmd,\n                _out_decor_fcn(enum(newval).value),\n            )\n        )\n\n    return rproperty(\n        fget=_getter, fset=_setter, doc=doc, readonly=readonly, writeonly=writeonly\n    )\n\n\ndef unitless_property(\n    command,\n    set_cmd=None,\n    format_code=\"{:e}\",\n    doc=None,\n    readonly=False,\n    writeonly=False,\n    set_fmt=\"{} {}\",\n):\n    \"\"\"\n    Called inside of SCPI classes to instantiate properties with unitless\n    numeric values.\n\n    :param str command: Name of the SCPI command corresponding to this property.\n        If parameter set_cmd is not specified, then this parameter is also used\n        for both getting and setting.\n    :param str set_cmd: If not `None`, this parameter sets the command string\n        to be used when sending commands with no return values to the\n        instrument. This allows for non-symmetric properties that have different\n        strings for getting vs setting a property.\n    :param str format_code: Argument to `str.format` used in sending values\n        to the instrument.\n    :param str doc: Docstring to be associated with the new property.\n    :param bool readonly: If `True`, the returned property does not have a\n        setter.\n    :param bool writeonly: If `True`, the returned property does not have a\n        getter. Both readonly and writeonly cannot both be `True`.\n    :param str set_fmt: Specify the string format to use when sending a\n        non-query to the instrument. The default is \"{} {}\" which places a\n        space between the SCPI command the associated parameter. By switching\n        to \"{}={}\" an equals sign would instead be used as the separator.\n    \"\"\"\n\n    def _getter(self):\n        raw = self.query(f\"{command}?\")\n        return float(raw)\n\n    def _setter(self, newval):\n        if isinstance(newval, u.Quantity):\n            if newval.units == u.dimensionless:\n                newval = float(newval.magnitude)\n            else:\n                raise ValueError\n        strval = format_code.format(newval)\n        self.sendcmd(set_fmt.format(command if set_cmd is None else set_cmd, strval))\n\n    return rproperty(\n        fget=_getter, fset=_setter, doc=doc, readonly=readonly, writeonly=writeonly\n    )\n\n\ndef int_property(\n    command,\n    set_cmd=None,\n    format_code=\"{:d}\",\n    doc=None,\n    readonly=False,\n    writeonly=False,\n    valid_set=None,\n    set_fmt=\"{} {}\",\n):\n    \"\"\"\n    Called inside of SCPI classes to instantiate properties with unitless\n    numeric values.\n\n    :param str command: Name of the SCPI command corresponding to this property.\n        If parameter set_cmd is not specified, then this parameter is also used\n        for both getting and setting.\n    :param str set_cmd: If not `None`, this parameter sets the command string\n        to be used when sending commands with no return values to the\n        instrument. This allows for non-symmetric properties that have different\n        strings for getting vs setting a property.\n    :param str format_code: Argument to `str.format` used in sending values\n        to the instrument.\n    :param str doc: Docstring to be associated with the new property.\n    :param bool readonly: If `True`, the returned property does not have a\n        setter.\n    :param bool writeonly: If `True`, the returned property does not have a\n        getter. Both readonly and writeonly cannot both be `True`.\n    :param valid_set: Set of valid values for the property, or `None` if all\n        `int` values are valid.\n    :param str set_fmt: Specify the string format to use when sending a\n        non-query to the instrument. The default is \"{} {}\" which places a\n        space between the SCPI command the associated parameter. By switching\n        to \"{}={}\" an equals sign would instead be used as the separator.\n    \"\"\"\n\n    def _getter(self):\n        raw = self.query(f\"{command}?\")\n        return int(raw)\n\n    if valid_set is None:\n\n        def _setter(self, newval):\n            strval = format_code.format(newval)\n            self.sendcmd(\n                set_fmt.format(command if set_cmd is None else set_cmd, strval)\n            )\n\n    else:\n\n        def _setter(self, newval):\n            if newval not in valid_set:\n                raise ValueError(\n                    \"{} is not an allowed value for this property; \"\n                    \"must be one of {}.\".format(newval, valid_set)\n                )\n            strval = format_code.format(newval)\n            self.sendcmd(\n                set_fmt.format(command if set_cmd is None else set_cmd, strval)\n            )\n\n    return rproperty(\n        fget=_getter, fset=_setter, doc=doc, readonly=readonly, writeonly=writeonly\n    )\n\n\ndef unitful_property(\n    command,\n    units,\n    set_cmd=None,\n    format_code=\"{:e}\",\n    doc=None,\n    input_decoration=None,\n    output_decoration=None,\n    readonly=False,\n    writeonly=False,\n    set_fmt=\"{} {}\",\n    valid_range=(None, None),\n):\n    \"\"\"\n    Called inside of SCPI classes to instantiate properties with unitful numeric\n    values. This function assumes that the instrument only accepts\n    and returns magnitudes without unit annotations, such that all unit\n    information is provided by the ``units`` argument. This is not suitable\n    for instruments where the units can change dynamically due to front-panel\n    interaction or due to remote commands.\n\n    :param str command: Name of the SCPI command corresponding to this property.\n        If parameter set_cmd is not specified, then this parameter is also used\n        for both getting and setting.\n    :param str set_cmd: If not `None`, this parameter sets the command string\n        to be used when sending commands with no return values to the\n        instrument. This allows for non-symmetric properties that have different\n        strings for getting vs setting a property.\n    :param units: Units to assume in sending and receiving magnitudes to and\n        from the instrument.\n    :param str format_code: Argument to `str.format` used in sending the\n        magnitude of values to the instrument.\n    :param str doc: Docstring to be associated with the new property.\n    :param callable input_decoration: Function called on responses from\n        the instrument before passing to user code.\n    :param callable output_decoration: Function called on commands to the\n        instrument.\n    :param bool readonly: If `True`, the returned property does not have a\n        setter.\n    :param bool writeonly: If `True`, the returned property does not have a\n        getter. Both readonly and writeonly cannot both be `True`.\n    :param str set_fmt: Specify the string format to use when sending a\n        non-query to the instrument. The default is \"{} {}\" which places a\n        space between the SCPI command the associated parameter. By switching\n        to \"{}={}\" an equals sign would instead be used as the separator.\n    :param valid_range: Tuple containing min & max values when setting\n        the property. Index 0 is minimum value, index 1 is maximum value.\n        Setting `None` in either disables bounds checking for that end of the\n        range. The default of `(None, None)` has no min or max constraints.\n        The valid set is inclusive of the values provided.\n    :type valid_range: `tuple` or `list` of `int` or `float`\n    \"\"\"\n\n    def _in_decor_fcn(val):\n        if input_decoration is None:\n            return val\n        elif hasattr(input_decoration, \"__get__\"):\n            return input_decoration.__get__(None, object)(val)\n        return input_decoration(val)\n\n    def _out_decor_fcn(val):\n        if output_decoration is None:\n            return val\n        elif hasattr(output_decoration, \"__get__\"):\n            return output_decoration.__get__(None, object)(val)\n        return output_decoration(val)\n\n    def _getter(self):\n        raw = _in_decor_fcn(self.query(f\"{command}?\"))\n        return u.Quantity(*split_unit_str(raw, units)).to(units)\n\n    def _setter(self, newval):\n        newval = assume_units(newval, units).to(units)\n        min_value, max_value = valid_range\n        if min_value is not None:\n            if callable(min_value):\n                min_value = min_value(self)  # pylint: disable=not-callable\n            else:\n                min_value = assume_units(min_value, units)\n            if newval < min_value:\n                raise ValueError(\n                    f\"Unitful quantity is too low. Got {newval}, \"\n                    f\"minimum value is {min_value}\"\n                )\n        if max_value is not None:\n            if callable(max_value):\n                max_value = max_value(self)  # pylint: disable=not-callable\n            else:\n                max_value = assume_units(max_value, units)\n            if newval > max_value:\n                raise ValueError(\n                    f\"Unitful quantity is too high. Got {newval}, \"\n                    f\"maximum value is {max_value}\"\n                )\n        # Rescale to the correct unit before printing. This will also\n        # catch bad units.\n        strval = format_code.format(newval.magnitude)\n        self.sendcmd(\n            set_fmt.format(\n                command if set_cmd is None else set_cmd, _out_decor_fcn(strval)\n            )\n        )\n\n    return rproperty(\n        fget=_getter, fset=_setter, doc=doc, readonly=readonly, writeonly=writeonly\n    )\n\n\ndef bounded_unitful_property(\n    command,\n    units,\n    min_fmt_str=\"{}:MIN?\",\n    max_fmt_str=\"{}:MAX?\",\n    valid_range=(\"query\", \"query\"),\n    **kwargs,\n):\n    \"\"\"\n    Called inside of SCPI classes to instantiate properties with unitful numeric\n    values which have upper and lower bounds. This function in turn calls\n    `unitful_property` where all kwargs for this function are passed on to.\n    See `unitful_property` documentation for information about additional\n    parameters that will be passed on.\n\n    Compared to `unitful_property`, this function will return 3 properties:\n    the one created by `unitful_property`, one for the minimum value, and one\n    for the maximum value.\n\n    :param str command: Name of the SCPI command corresponding to this property.\n        If parameter set_cmd is not specified, then this parameter is also used\n        for both getting and setting.\n    :param str set_cmd: If not `None`, this parameter sets the command string\n        to be used when sending commands with no return values to the\n        instrument. This allows for non-symmetric properties that have different\n        strings for getting vs setting a property.\n    :param units: Units to assume in sending and receiving magnitudes to and\n        from the instrument.\n    :param str min_fmt_str: Specify the string format to use when sending a\n        minimum value query. The default is ``\"{}:MIN?\"`` which will place\n        the property name in before the colon. Eg: ``\"MOCK:MIN?\"``\n    :param str max_fmt_str: Specify the string format to use when sending a\n        maximum value query. The default is ``\"{}:MAX?\"`` which will place\n        the property name in before the colon. Eg: ``\"MOCK:MAX?\"``\n    :param valid_range: Tuple containing min & max values when setting\n        the property. Index 0 is minimum value, index 1 is maximum value.\n        Setting `None` in either disables bounds checking for that end of the\n        range. The default of ``(\"query\", \"query\")`` will query the instrument\n        for min and max parameter values. The valid set is inclusive of\n        the values provided.\n    :type valid_range: `list` or `tuple` of `int`, `float`, `None`, or the\n        string ``\"query\"``.\n    :param kwargs: All other keyword arguments are passed onto\n        `unitful_property`\n    :return: Returns a `tuple` of 3 properties: first is as returned by\n        `unitful_property`, second is a property representing the minimum\n        value, and third is a property representing the maximum value\n    \"\"\"\n\n    def _min_getter(self):\n        if valid_range[0] == \"query\":\n            return u.Quantity(\n                *split_unit_str(self.query(min_fmt_str.format(command)), units)\n            )\n\n        return assume_units(valid_range[0], units).to(units)\n\n    def _max_getter(self):\n        if valid_range[1] == \"query\":\n            return u.Quantity(\n                *split_unit_str(self.query(max_fmt_str.format(command)), units)\n            )\n\n        return assume_units(valid_range[1], units).to(units)\n\n    new_range = (\n        None if valid_range[0] is None else _min_getter,\n        None if valid_range[1] is None else _max_getter,\n    )\n\n    return (\n        unitful_property(command, units, valid_range=new_range, **kwargs),\n        property(_min_getter) if valid_range[0] is not None else None,\n        property(_max_getter) if valid_range[1] is not None else None,\n    )\n\n\ndef string_property(\n    command,\n    set_cmd=None,\n    bookmark_symbol='\"',\n    doc=None,\n    readonly=False,\n    writeonly=False,\n    set_fmt=\"{} {}{}{}\",\n):\n    \"\"\"\n    Called inside of SCPI classes to instantiate properties with a string value.\n\n    :param str command: Name of the SCPI command corresponding to this property.\n        If parameter set_cmd is not specified, then this parameter is also used\n        for both getting and setting.\n    :param str set_cmd: If not `None`, this parameter sets the command string\n        to be used when sending commands with no return values to the\n        instrument. This allows for non-symmetric properties that have different\n        strings for getting vs setting a property.\n    :param str doc: Docstring to be associated with the new property.\n    :param bool readonly: If `True`, the returned property does not have a\n        setter.\n    :param bool writeonly: If `True`, the returned property does not have a\n        getter. Both readonly and writeonly cannot both be `True`.\n    :param str set_fmt: Specify the string format to use when sending a\n        non-query to the instrument. The default is \"{} {}{}{}\" which places a\n        space between the SCPI command the associated parameter, and places\n        the bookmark symbols on either side of the parameter.\n    :param str bookmark_symbol: The symbol that will flank both sides of the\n        parameter to be sent to the instrument. By default this is ``\"``.\n    \"\"\"\n    bookmark_length = len(bookmark_symbol)\n\n    def _getter(self):\n        string = self.query(f\"{command}?\")\n        string = (\n            string[bookmark_length:-bookmark_length] if bookmark_length > 0 else string\n        )\n        return string\n\n    def _setter(self, newval):\n        self.sendcmd(\n            set_fmt.format(\n                command if set_cmd is None else set_cmd,\n                bookmark_symbol,\n                newval,\n                bookmark_symbol,\n            )\n        )\n\n    return rproperty(\n        fget=_getter, fset=_setter, doc=doc, readonly=readonly, writeonly=writeonly\n    )\n\n\n# CLASSES #####################################################################\n\n\nclass ProxyList:\n    \"\"\"\n    This is a special class used to generate lists of objects where the valid\n    keys are defined by the `valid_set` init parameter. This allows an\n    instrument to have a single property through which all of its various\n    identical input/ouput channels can be accessed.\n\n    Search the code base of existing examples of how this is used for plenty\n    of different examples.\n\n    :param parent: The \"parent\" or \"owner\" of the of the proxy classes. In\n        dev work, this is typically ``self``.\n    :param proxy_cls: The child class that will be returned when the returned\n        object is iterated through. These are usually objects that represent\n        an entire channel/sensor/input/output, of which an instrument might\n        have more than one but each are individually addressed. An example is\n        an oscilloscope channel.\n    :param valid_set: The set of valid keys by which the proxy class objects\n        are accessed. Typically this is something like `range`, but can be\n        any generator, list, enum, etc.\n    \"\"\"\n\n    def __init__(self, parent, proxy_cls, valid_set):\n        self._parent = parent\n        self._proxy_cls = proxy_cls\n        self._valid_set = valid_set\n\n        # FIXME: This only checks the next level up the chain!\n        if hasattr(valid_set, \"__bases__\"):\n            self._isenum = (Enum in valid_set.__bases__) or (\n                IntEnum in valid_set.__bases__\n            )\n        else:\n            self._isenum = False\n\n    def __iter__(self):\n        for idx in self._valid_set:\n            yield self._proxy_cls(self._parent, idx)\n\n    def __getitem__(self, idx):\n        # If we have an enum, try to normalize by using getitem. This will\n        # allow for things like 'x' to be used instead of enum.x.\n        if self._isenum:\n            try:\n                idx = self._valid_set[idx]\n            except KeyError:\n                try:\n                    idx = self._valid_set(idx)\n                except ValueError:\n                    pass\n            if not isinstance(idx, self._valid_set):\n                raise IndexError(\n                    \"Index out of range. Must be \" \"in {}.\".format(self._valid_set)\n                )\n            idx = idx.value\n        else:\n            if idx not in self._valid_set:\n                raise IndexError(\n                    \"Index out of range. Must be \" \"in {}.\".format(self._valid_set)\n                )\n        return self._proxy_cls(self._parent, idx)\n\n    def __len__(self):\n        return len(self._valid_set)\n"
  },
  {
    "path": "src/instruments/yokogawa/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing Yokogawa instruments\n\"\"\"\n\nfrom .yokogawa6370 import Yokogawa6370\nfrom .yokogawa7651 import Yokogawa7651\n"
  },
  {
    "path": "src/instruments/yokogawa/yokogawa6370.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the Yokogawa 6370 optical spectrum analyzer.\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nfrom enum import IntEnum, Enum\n\nfrom instruments.units import ureg as u\n\nfrom instruments.abstract_instruments import OpticalSpectrumAnalyzer\nfrom instruments.abstract_instruments.comm import SocketCommunicator\nfrom instruments.util_fns import (\n    enum_property,\n    unitful_property,\n    unitless_property,\n    bounded_unitful_property,\n    ProxyList,\n    string_property,\n)\n\n# CLASSES #####################################################################\n\n\nclass Yokogawa6370(OpticalSpectrumAnalyzer):\n    \"\"\"\n    The Yokogawa 6370 is an optical spectrum analyzer.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> import instruments.units as u\n    >>> inst = ik.yokogawa.Yokogawa6370.open_visa('TCPIP0:192.168.0.35')\n    >>> inst.start_wl = 1030e-9 * u.m\n\n    Example usage with TCP/IP connection and user authentication:\n\n    >>> import instruments as ik\n    >>> auth = (\"username\", \"password\")\n    >>> inst = ik.yokogawa.Yokogawa6370.open_tcpip(\"192.168.0.35\", 10001, auth=auth)\n    \"\"\"\n\n    def __init__(self, filelike, auth=None):\n        super().__init__(filelike)\n        self._channel_count = len(self.Traces)\n\n        if isinstance(self._file, SocketCommunicator):\n            self.terminator = \"\\r\\n\"  # TCP IP connection terminator\n\n        # Authenticate with `auth`\n        if auth is not None:\n            self._authenticate(auth)\n\n        # Set data Format to binary\n        self.sendcmd(\":FORMat:DATA REAL,64\")  # TODO: Find out where we want this\n\n    def _authenticate(self, auth):\n        \"\"\"Authenticate with the instrument.\n\n        :param auth: Authentication tuple of (username, password)\n        \"\"\"\n        username, password = auth\n        _ = self.query(f'OPEN \"{username}\"')\n        resp = self.query(f'\"{password}\"')\n\n        if \"ready\" not in resp.lower():\n            raise ConnectionError(\"Could not authenticate with username / password\")\n\n    # INNER CLASSES #\n\n    class Channel(OpticalSpectrumAnalyzer.Channel):\n        \"\"\"\n        Class representing the channels on the Yokogawa 6370.\n        This class inherits from `OpticalSpectrumAnalyzer.Channel`.\n\n        .. warning:: This class should NOT be manually created by the user. It\n            is designed to be initialized by the `Yokogawa6370` class.\n        \"\"\"\n\n        def __init__(self, parent, idx):\n            self._parent = parent\n            self._name = idx\n\n        # METHODS #\n\n        def _data(self, axis, limits=None, bin_format=True):\n            \"\"\"Get data of `axis`.\n\n            :param axis: Axis to get the data of, \"X\" or \"Y\"\n            :param limits: Range of samples to transfer as a tuple of min and\n                max value, e.g. (5, 100) transfers data from the fifth to the\n                100th sample. The possible values are from 0 to 50000.\n            \"\"\"\n            if limits is None:\n                cmd = f\":TRAC:{axis}? {self._name}\"\n            elif isinstance(limits, (tuple, list)) and len(limits) == 2:\n                cmd = f\":TRAC:{axis}? {self._name},{limits[0]+1},{limits[1]+1}\"\n            else:\n                raise ValueError(\"limits has to be a list or tuple with two members\")\n            self._parent.sendcmd(cmd)\n            data = self._parent.binblockread(data_width=8, fmt=\"<d\")\n            self._parent._file.read_raw(1)  # pylint: disable=protected-access\n            return data\n\n        def data(self, limits=None, bin_format=True):\n            \"\"\"\n            Return the trace's level data.\n\n            :param limits: Range of samples to transfer as a tuple of min and\n                max value, e.g. (5, 100) transfers data from the fifth to the\n                100th sample. The possible values are from 0 to 50000.\n            \"\"\"\n            return self._data(\"Y\", limits=limits, bin_format=bin_format)\n\n        def wavelength(self, limits=None, bin_format=True):\n            \"\"\"\n            Return the trace's wavelength data.\n\n            :param limits: Range of samples to transfer as a tuple of min and\n                max value, e.g. (5, 100) transfers data from the fifth to the\n                100th sample. The possible values are from 0 to 50000.\n            \"\"\"\n            return self._data(\"X\", limits=limits, bin_format=bin_format)\n\n    # ENUMS #\n\n    class SweepModes(IntEnum):\n        \"\"\"\n        Enum containing valid output modes for the Yokogawa 6370\n        \"\"\"\n\n        SINGLE = 1\n        REPEAT = 2\n        AUTO = 3\n\n    class Traces(Enum):\n        \"\"\"\n        Enum containing valid Traces for the Yokogawa 6370\n        \"\"\"\n\n        A = \"TRA\"\n        B = \"TRB\"\n        C = \"TRC\"\n        D = \"TRD\"\n        E = \"TRE\"\n        F = \"TRF\"\n        G = \"TRG\"\n\n    # PROPERTIES #\n\n    # General\n\n    id = string_property(\n        \"*IDN\",\n        doc=\"\"\"\n            Get the identification of the device.\n            Output: 'Manufacturer,Product,SerialNumber,FirmwareVersion'\n            Sample: 'YOKOGAWA,AQ6370D,90Y403996,02.08'\n            \"\"\",\n        readonly=True,\n    )\n\n    status = unitless_property(\n        \"*STB\",\n        doc=\"\"\"The status byte of the device.\n        Bit 7: Summary bit of operation status\n        Bit 5: Summary bit of standard event status register\n        Bit 4: “1” if the output buffer contains data\n        Bit 3: Summary bit of questionable status\n        \"\"\",\n        readonly=True,\n    )\n\n    operation_event = unitless_property(\n        \":status:operation:event\",\n        doc=\"\"\"\n            All changes after the last readout. Readout clears the operation_event\n            Bit 4: Autosweep\n            Bit 3: Calibration/Alignment\n            Bit 2: Copy/File\n            Bit 1: Program\n            Bit 0: Sweep finished.\n        \"\"\",\n        readonly=True,\n    )\n\n    @property\n    def channel(self):\n        \"\"\"\n        Gets the specific channel object.\n        This channel is accessed as a list in the following manner::\n\n        >>> import instruments as ik\n        >>> osa = ik.yokogawa.Yokogawa6370.open_gpibusb('/dev/ttyUSB0')\n        >>> dat = osa.channel[\"A\"].data # Gets the data of channel 0\n\n        :rtype: `list`[`~Yokogawa6370.Channel`]\n        \"\"\"\n        return ProxyList(self, Yokogawa6370.Channel, Yokogawa6370.Traces)\n\n    # Sweep\n\n    start_wl, start_wl_min, start_wl_max = bounded_unitful_property(\n        \":SENS:WAV:STAR\",\n        u.meter,\n        doc=\"\"\"\n        The start wavelength in m.\n        \"\"\",\n        valid_range=(600e-9, 1700e-9),\n    )\n\n    stop_wl, stop_wl_min, stop_wl_max = bounded_unitful_property(\n        \":SENS:WAV:STOP\",\n        u.meter,\n        doc=\"\"\"\n        The stop wavelength in m.\n        \"\"\",\n        valid_range=(600e-9, 1700e-9),\n    )\n\n    bandwidth = unitful_property(\n        \":SENS:BAND:RES\",\n        u.meter,\n        doc=\"\"\"\n        The bandwidth in m.\n        \"\"\",\n    )\n\n    span = unitful_property(\n        \":SENS:WAV:SPAN\",\n        u.meter,\n        doc=\"\"\"\n        A floating point property that controls the wavelength span in m.\n        \"\"\",\n    )\n\n    center_wl = unitful_property(\n        \":SENS:WAV:CENT\",\n        u.meter,\n        doc=\"\"\"\n         A floating point property that controls the center wavelength m.\n        \"\"\",\n    )\n\n    points = unitless_property(\n        \":SENS:SWE:POIN\",\n        doc=\"\"\"\n        An integer property that controls the number of points in a trace.\n        \"\"\",\n    )\n\n    sweep_mode = enum_property(\n        \":INIT:SMOD\",\n        SweepModes,\n        input_decoration=int,\n        doc=\"\"\"\n        A property to control the Sweep Mode as one of Yokogawa6370.SweepMode.\n        Effective only after a self.start_sweep().\"\"\",\n    )\n\n    # Analysis\n\n    # Traces\n\n    active_trace = enum_property(\n        \":TRAC:ACTIVE\",\n        Traces,\n        doc=\"\"\"\n        The active trace of the OSA of enum Yokogawa6370.Traces. Determines the\n        result of Yokogawa6370.data() and Yokogawa6370.wavelength().\"\"\",\n    )\n\n    # METHODS #\n\n    def data(self, limits=None):\n        \"\"\"\n        Function to query the active Trace data of the OSA.\n\n        :param limits: Range of samples to transfer as a tuple of min and\n            max value, e.g. (5, 100) transfers data from the fifth to the\n            100th sample. The possible values are from 0 to 50000.\n        \"\"\"\n        return self.channel[self.active_trace].data(limits=limits)\n\n    def wavelength(self, limits=None):\n        \"\"\"\n        Query the wavelength axis of the active trace.\n\n        :param limits: Range of samples to transfer as a tuple of min and\n            max value, e.g. (5, 100) transfers data from the fifth to the\n            100th sample. The possible values are from 0 to 50000.\n        \"\"\"\n        return self.channel[self.active_trace].wavelength(limits=limits)\n\n    def analysis(self):\n        \"\"\"Get the analysis data.\"\"\"\n        return [float(x) for x in self.query(\":CALC:DATA?\").split(\",\")]\n\n    def start_sweep(self):\n        \"\"\"\n        Triggering function for the Yokogawa 6370.\n        After changing the sweep mode, the device needs to be triggered before\n        it will update.\n        \"\"\"\n        self.sendcmd(\"*CLS;:init\")\n\n    def abort(self):\n        \"\"\"Abort a running sweep or calibration etc.\"\"\"\n        self.sendcmd(\":ABORT\")\n\n    def clear(self):\n        \"\"\"Clear status registers.\"\"\"\n        self.sendcmd(\"*CLS\")\n\n    def query(self, cmd, size=-1):\n        \"\"\"todo: remove\"\"\"\n        print(f\"CMD: {cmd}\")\n        retval = super().query(cmd, size=size)\n        print(f\"RESP: {retval}\")\n        return retval\n"
  },
  {
    "path": "src/instruments/yokogawa/yokogawa7651.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nProvides support for the Yokogawa 7651 power supply.\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nfrom enum import IntEnum\n\nfrom instruments.units import ureg as u\n\nfrom instruments.abstract_instruments import PowerSupply\nfrom instruments.abstract_instruments import Instrument\nfrom instruments.util_fns import assume_units, ProxyList\n\n# CLASSES #####################################################################\n\n\nclass Yokogawa7651(PowerSupply, Instrument):\n    \"\"\"\n    The Yokogawa 7651 is a single channel DC power supply.\n\n    Example usage:\n\n    >>> import instruments as ik\n    >>> import instruments.units as u\n    >>> inst = ik.yokogawa.Yokogawa7651.open_gpibusb(\"/dev/ttyUSB0\", 1)\n    >>> inst.voltage = 10 * u.V\n    \"\"\"\n\n    # INNER CLASSES #\n\n    class Channel(PowerSupply.Channel):\n        \"\"\"\n        Class representing the only channel on the Yokogawa 7651.\n\n        This class inherits from `PowerSupply.Channel`.\n\n        .. warning:: This class should NOT be manually created by the user. It\n            is designed to be initialized by the `Yokogawa7651` class.\n        \"\"\"\n\n        def __init__(self, parent, name):\n            self._parent = parent\n            self._name = name\n\n        # PROPERTIES #\n\n        @property\n        def mode(self):\n            \"\"\"\n            Sets the output mode for the power supply channel.\n            This is either constant voltage or constant current.\n\n            Querying the mode is not supported by this instrument.\n\n            :type: `Yokogawa7651.Mode`\n            \"\"\"\n            raise NotImplementedError(\n                \"This instrument does not support \" \"querying the operation mode.\"\n            )\n\n        @mode.setter\n        def mode(self, newval):\n            if not isinstance(newval, Yokogawa7651.Mode):\n                raise TypeError(\n                    \"Mode setting must be a `Yokogawa7651.Mode` \"\n                    \"value, got {} instead.\".format(type(newval))\n                )\n            self._parent.sendcmd(f\"F{newval.value};\")\n            self._parent.trigger()\n\n        @property\n        def voltage(self):\n            \"\"\"\n            Sets the voltage of the specified channel. This device has a voltage\n            range of 0V to +30V.\n\n            Querying the voltage is not supported by this instrument.\n\n            :units: As specified (if a `~pint.Quantity`) or\n                assumed to be of units Volts.\n            :type: `~pint.Quantity` with units Volt\n            \"\"\"\n            raise NotImplementedError(\n                \"This instrument does not support \"\n                \"querying the output voltage setting.\"\n            )\n\n        @voltage.setter\n        def voltage(self, newval):\n            newval = assume_units(newval, u.volt).to(u.volt).magnitude\n            self.mode = self._parent.Mode.voltage\n            self._parent.sendcmd(f\"SA{newval};\")\n            self._parent.trigger()\n\n        @property\n        def current(self):\n            \"\"\"\n            Sets the current of the specified channel. This device has an max\n            setting of 100mA.\n\n            Querying the current is not supported by this instrument.\n\n            :units: As specified (if a `~pint.Quantity`) or\n                assumed to be of units Amps.\n            :type: `~pint.Quantity` with units Amp\n            \"\"\"\n            raise NotImplementedError(\n                \"This instrument does not support \"\n                \"querying the output current setting.\"\n            )\n\n        @current.setter\n        def current(self, newval):\n            newval = assume_units(newval, u.amp).to(u.amp).magnitude\n            self.mode = self._parent.Mode.current\n            self._parent.sendcmd(f\"SA{newval};\")\n            self._parent.trigger()\n\n        @property\n        def output(self):\n            \"\"\"\n            Sets the output status of the specified channel. This either enables\n            or disables the output.\n\n            Querying the output status is not supported by this instrument.\n\n            :type: `bool`\n            \"\"\"\n            raise NotImplementedError(\n                \"This instrument does not support \" \"querying the output status.\"\n            )\n\n        @output.setter\n        def output(self, newval):\n            if newval is True:\n                self._parent.sendcmd(\"O1;\")\n                self._parent.trigger()\n            else:\n                self._parent.sendcmd(\"O0;\")\n                self._parent.trigger()\n\n    # ENUMS #\n\n    class Mode(IntEnum):\n        \"\"\"\n        Enum containing valid output modes for the Yokogawa 7651\n        \"\"\"\n\n        voltage = 1\n        current = 5\n\n    # PROPERTIES #\n\n    @property\n    def channel(self):\n        \"\"\"\n        Gets the specific power supply channel object. Since the Yokogawa7651\n        is only equiped with a single channel, a list with a single element\n        will be returned.\n\n        This (single) channel is accessed as a list in the following manner::\n\n        >>> import instruments as ik\n        >>> yoko = ik.yokogawa.Yokogawa7651.open_gpibusb('/dev/ttyUSB0', 10)\n        >>> yoko.channel[0].voltage = 1 # Sets output voltage to 1V\n\n        :rtype: `~Yokogawa7651.Channel`\n        \"\"\"\n        return ProxyList(self, Yokogawa7651.Channel, [0])\n\n    @property\n    def voltage(self):\n        \"\"\"\n        Sets the voltage. This device has a voltage range of 0V to +30V.\n\n        Querying the voltage is not supported by this instrument.\n\n        :units: As specified (if a `~pint.Quantity`) or assumed\n            to be of units Volts.\n        :type: `~pint.Quantity` with units Volt\n        \"\"\"\n        raise NotImplementedError(\n            \"This instrument does not support querying \" \"the output voltage setting.\"\n        )\n\n    @voltage.setter\n    def voltage(self, newval):\n        self.channel[0].voltage = newval\n\n    @property\n    def current(self):\n        \"\"\"\n        Sets the current. This device has an max setting of 100mA.\n\n        Querying the current is not supported by this instrument.\n\n        :units: As specified (if a `~pint.Quantity`) or assumed\n            to be of units Amps.\n        :type: `~pint.Quantity` with units Amp\n        \"\"\"\n        raise NotImplementedError(\n            \"This instrument does not support querying \" \"the output current setting.\"\n        )\n\n    @current.setter\n    def current(self, newval):\n        self.channel[0].current = newval\n\n    # METHODS #\n\n    def trigger(self):\n        \"\"\"\n        Triggering function for the Yokogawa 7651.\n\n        After changing any parameters of the instrument (for example, output\n        voltage), the device needs to be triggered before it will update.\n        \"\"\"\n        self.sendcmd(\"E;\")\n"
  },
  {
    "path": "tests/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing InstrumentKit unit tests\n\nThis file hosts a few utility functions to assist with creating and running\nunit tests.\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport contextlib\nfrom io import BytesIO\nfrom unittest import mock\n\nimport pytest\n\nfrom instruments.optional_dep_finder import numpy\nfrom instruments.units import ureg as u\n\n# FUNCTIONS ##################################################################\n\n\n@contextlib.contextmanager\ndef expected_protocol(ins_class, host_to_ins, ins_to_host, sep=\"\\n\", repeat=1):\n    \"\"\"\n    Given an instrument class, expected output from the host and expected input\n    from the instrument, asserts that the protocol in a context block proceeds\n    according to that expectation.\n\n    For an example of how to write tests using this context manager, see\n    the ``make_name_test`` function below.\n\n    :param ins_class: Instrument class to use for the protocol assertion.\n    :type ins_class: `~instruments.Instrument`\n    :param host_to_ins: Data to be sent by the host to the instrument;\n        this is checked against the actual data sent by the instrument class\n        during the execution of this context manager.\n    :type host_to_ins: ``str`` or ``list``; if ``list``, each line is\n        concatenated with the separator given by ``sep``.\n    :param ins_to_host: Data to be sent by the instrument; this is played\n        back during the execution of this context manager, and should\n        be used to assert correct behaviour within the context.\n    :type ins_to_host: ``str`` or ``list``; if ``list``, each line is\n        concatenated with the separator given by ``sep``.\n    :param str sep: Character to be inserted after each string in both\n        host_to_ins and ins_to_host parameters. This is typically the\n        termination character you would like to have inserted.\n    :param int repeat: The number of times the host_to_ins and\n        ins_to_host data sets should be duplicated. Typically the default\n        value of 1 is sufficient, but increasing this is useful when\n        testing multiple calls in the same test that should have the same\n        command transactions.\n    \"\"\"\n    if isinstance(sep, bytes):\n        sep = sep.decode(\"utf-8\")\n\n    # Normalize assertion and playback strings.\n    if isinstance(ins_to_host, list):\n        ins_to_host = [\n            item.encode(\"utf-8\") if isinstance(item, str) else item\n            for item in ins_to_host\n        ]\n        ins_to_host = sep.encode(\"utf-8\").join(ins_to_host) + (\n            sep.encode(\"utf-8\") if ins_to_host else b\"\"\n        )\n    elif isinstance(ins_to_host, str):\n        ins_to_host = ins_to_host.encode(\"utf-8\")\n    ins_to_host *= repeat\n\n    if isinstance(host_to_ins, list):\n        host_to_ins = [\n            item.encode(\"utf-8\") if isinstance(item, str) else item\n            for item in host_to_ins\n        ]\n        host_to_ins = sep.encode(\"utf-8\").join(host_to_ins) + (\n            sep.encode(\"utf-8\") if host_to_ins else b\"\"\n        )\n    elif isinstance(host_to_ins, str):\n        host_to_ins = host_to_ins.encode(\"utf-8\")\n    host_to_ins *= repeat\n\n    stdin = BytesIO(ins_to_host)\n    stdout = BytesIO()\n\n    yield ins_class.open_test(stdin, stdout)\n\n    assert stdout.getvalue() == host_to_ins, \"\"\"Expected:\n\n{}\n\nGot:\n\n{}\"\"\".format(repr(host_to_ins), repr(stdout.getvalue()))\n\n    # current = stdin.tell()\n    # stdin.seek(0, 2)\n    # end = stdin.tell()\n    #\n    # assert current == end, \\\n    #     \"\"\"Only read {} bytes out of {}\"\"\".format(current, end)\n\n\ndef unit_eq(a, b, **kwargs):\n    \"\"\"\n    Asserts that two unitful quantites ``a`` and ``b`` are equal up to a small numerical\n    threshold. Keyword arguments ``kwargs`` are passed on to ``pytest.approx()``.\n\n    :param a: First quantity to compare to second.\n    :type a: `~pint.Quantity`\n    :param b: Second quantity to compare to first.\n    :type b: `~pint.Quantity`\n    :param kwargs: Keyword arguments, passed on to ``pytest.approx()``.\n    \"\"\"\n    assert a.magnitude == pytest.approx(b.magnitude, **kwargs)\n    assert a.units == b.units, f\"{a} and {b} have different units\"\n\n\ndef make_name_test(ins_class, name_cmd=\"*IDN?\"):\n    \"\"\"\n    Given an instrument class, produces a test which asserts that the instrument\n    correctly reports its name in response to a standard command.\n    \"\"\"\n\n    def test():\n        with expected_protocol(ins_class, name_cmd + \"\\n\", \"NAME\\n\") as ins:\n            assert ins.name == \"NAME\"\n\n    return test\n\n\ndef iterable_eq(a, b, **kwargs):\n    \"\"\"\n    Asserts that the contents of two iterables are the same. Keyword arguments\n    ``kwargs`` are passed on ``unit_eq``.\n\n    :param a: First iterable to compare to second.\n    :param b: Second iterable to compare to first.\n    :param kwargs: Keyword arguments, passed on to ``pytest.approx()``\n    \"\"\"\n    if numpy and (isinstance(a, numpy.ndarray) or isinstance(b, numpy.ndarray)):\n        # pylint: disable=unidiomatic-typecheck\n        assert type(a) == type(\n            b\n        ), f\"Expected two numpy arrays, got {type(a)}, {type(b)}\"\n        assert len(a) == len(\n            b\n        ), f\"Length of iterables is not the same, got {len(a)} and {len(b)}\"\n        assert (a == b).all()\n    elif isinstance(a, u.Quantity) and isinstance(b, u.Quantity):\n        unit_eq(a, b, **kwargs)\n    else:\n        assert a == b\n"
  },
  {
    "path": "tests/test_abstract_inst/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_abstract_inst/test_electrometer.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the abstract electrometer class\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport pytest\n\nimport instruments as ik\nfrom tests import expected_protocol\n\n# TESTS ######################################################################\n\n\n@pytest.fixture\ndef em(monkeypatch):\n    \"\"\"Patch and return electrometer class for direct access of metaclass.\"\"\"\n    inst = ik.abstract_instruments.Electrometer\n    monkeypatch.setattr(inst, \"__abstractmethods__\", set())\n    return inst\n\n\ndef test_electrometer_mode(em):\n    \"\"\"Get / set mode to ensure the abstract property exists.\"\"\"\n    with expected_protocol(em, [], []) as inst:\n        _ = inst.mode\n        inst.mode = 42\n\n\ndef test_electrometer_unit(em):\n    \"\"\"Get unit to ensure the abstract property exists.\"\"\"\n    with expected_protocol(em, [], []) as inst:\n        _ = inst.unit\n\n\ndef test_electrometer_trigger_mode(em):\n    \"\"\"Get / set trigger mode to ensure the abstract property exists.\"\"\"\n    with expected_protocol(em, [], []) as inst:\n        _ = inst.trigger_mode\n        inst.trigger_mode = 42\n\n\ndef test_electrometer_input_range(em):\n    \"\"\"Get / set input range to ensure the abstract property exists.\"\"\"\n    with expected_protocol(em, [], []) as inst:\n        _ = inst.input_range\n        inst.input_range = 42\n\n\ndef test_electrometer_zero_check(em):\n    \"\"\"Get / set zero check to ensure the abstract property exists.\"\"\"\n    with expected_protocol(em, [], []) as inst:\n        _ = inst.zero_check\n        inst.zero_check = 42\n\n\ndef test_electrometer_zero_correct(em):\n    \"\"\"Get / set zero correct to ensure the abstract property exists.\"\"\"\n    with expected_protocol(em, [], []) as inst:\n        _ = inst.zero_correct\n        inst.zero_correct = 42\n\n\ndef test_electrometer_fetch(em):\n    \"\"\"Raise NotImplementedError for fetch method.\"\"\"\n    with expected_protocol(em, [], []) as inst:\n        with pytest.raises(NotImplementedError):\n            inst.fetch()\n\n\ndef test_electrometer_read_measurements(em):\n    \"\"\"Raise NotImplementedError for read_measurements method.\"\"\"\n    with expected_protocol(em, [], []) as inst:\n        with pytest.raises(NotImplementedError):\n            inst.read_measurements()\n"
  },
  {
    "path": "tests/test_abstract_inst/test_function_generator.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the abstract function generator class\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport pytest\nfrom instruments.units import ureg as u\n\nimport instruments as ik\nfrom tests import expected_protocol, unit_eq\n\n# TESTS ######################################################################\n\n# pylint: disable=missing-function-docstring,redefined-outer-name,protected-access\n\n\n@pytest.fixture\ndef fg():\n    return ik.abstract_instruments.FunctionGenerator.open_test()\n\n\ndef test_func_gen_default_channel_count(fg):\n    assert fg._channel_count == 1\n\n\ndef test_func_gen_raises_not_implemented_error_one_channel_getting(fg):\n    fg._channel_count = 1\n    with pytest.raises(NotImplementedError):\n        _ = fg.amplitude\n    with pytest.raises(NotImplementedError):\n        _ = fg.frequency\n    with pytest.raises(NotImplementedError):\n        _ = fg.function\n    with pytest.raises(NotImplementedError):\n        _ = fg.offset\n    with pytest.raises(NotImplementedError):\n        _ = fg.phase\n\n\ndef test_func_gen_raises_not_implemented_error_one_channel_setting(fg):\n    fg._channel_count = 1\n    with pytest.raises(NotImplementedError):\n        fg.amplitude = 1\n    with pytest.raises(NotImplementedError):\n        fg.frequency = 1\n    with pytest.raises(NotImplementedError):\n        fg.function = 1\n    with pytest.raises(NotImplementedError):\n        fg.offset = 1\n    with pytest.raises(NotImplementedError):\n        fg.phase = 1\n\n\ndef test_func_gen_raises_not_implemented_error_two_channel_getting(fg):\n    fg._channel_count = 2\n    with pytest.raises(NotImplementedError):\n        _ = fg.channel[0].amplitude\n    with pytest.raises(NotImplementedError):\n        _ = fg.channel[0].frequency\n    with pytest.raises(NotImplementedError):\n        _ = fg.channel[0].function\n    with pytest.raises(NotImplementedError):\n        _ = fg.channel[0].offset\n    with pytest.raises(NotImplementedError):\n        _ = fg.channel[0].phase\n\n\ndef test_func_gen_raises_not_implemented_error_two_channel_setting(fg):\n    fg._channel_count = 2\n    with pytest.raises(NotImplementedError):\n        fg.channel[0].amplitude = 1\n    with pytest.raises(NotImplementedError):\n        fg.channel[0].frequency = 1\n    with pytest.raises(NotImplementedError):\n        fg.channel[0].function = 1\n    with pytest.raises(NotImplementedError):\n        fg.channel[0].offset = 1\n    with pytest.raises(NotImplementedError):\n        fg.channel[0].phase = 1\n\n\ndef test_func_gen_two_channel_passes_thru_call_getter(fg, mocker):\n    mock_channel = mocker.MagicMock()\n    mock_properties = [mocker.PropertyMock(return_value=1) for _ in range(5)]\n\n    mocker.patch(\n        \"instruments.abstract_instruments.FunctionGenerator.Channel\", new=mock_channel\n    )\n    type(mock_channel()).amplitude = mock_properties[0]\n    type(mock_channel()).frequency = mock_properties[1]\n    type(mock_channel()).function = mock_properties[2]\n    type(mock_channel()).offset = mock_properties[3]\n    type(mock_channel()).phase = mock_properties[4]\n\n    fg._channel_count = 2\n    _ = fg.amplitude\n    _ = fg.frequency\n    _ = fg.function\n    _ = fg.offset\n    _ = fg.phase\n\n    for mock_property in mock_properties:\n        mock_property.assert_called_once_with()\n\n\ndef test_func_gen_one_channel_passes_thru_call_getter(fg, mocker):\n    mock_properties = [mocker.PropertyMock(return_value=1) for _ in range(4)]\n    mock_method = mocker.MagicMock(return_value=(1, u.V))\n\n    mocker.patch(\n        \"instruments.abstract_instruments.FunctionGenerator.frequency\",\n        new=mock_properties[0],\n    )\n    mocker.patch(\n        \"instruments.abstract_instruments.FunctionGenerator.function\",\n        new=mock_properties[1],\n    )\n    mocker.patch(\n        \"instruments.abstract_instruments.FunctionGenerator.offset\",\n        new=mock_properties[2],\n    )\n    mocker.patch(\n        \"instruments.abstract_instruments.FunctionGenerator.phase\",\n        new=mock_properties[3],\n    )\n    mocker.patch(\n        \"instruments.abstract_instruments.FunctionGenerator._get_amplitude_\",\n        new=mock_method,\n    )\n\n    fg._channel_count = 1\n    _ = fg.channel[0].amplitude\n    _ = fg.channel[0].frequency\n    _ = fg.channel[0].function\n    _ = fg.channel[0].offset\n    _ = fg.channel[0].phase\n\n    for mock_property in mock_properties:\n        mock_property.assert_called_once_with()\n\n    mock_method.assert_called_once_with()\n\n\ndef test_func_gen_two_channel_passes_thru_call_setter(fg, mocker):\n    mock_channel = mocker.MagicMock()\n    mock_properties = [mocker.PropertyMock() for _ in range(5)]\n\n    mocker.patch(\n        \"instruments.abstract_instruments.FunctionGenerator.Channel\", new=mock_channel\n    )\n    type(mock_channel()).amplitude = mock_properties[0]\n    type(mock_channel()).frequency = mock_properties[1]\n    type(mock_channel()).function = mock_properties[2]\n    type(mock_channel()).offset = mock_properties[3]\n    type(mock_channel()).phase = mock_properties[4]\n\n    fg._channel_count = 2\n    fg.amplitude = 1\n    fg.frequency = 1\n    fg.function = 1\n    fg.offset = 1\n    fg.phase = 1\n\n    for mock_property in mock_properties:\n        mock_property.assert_called_once_with(1)\n\n\ndef test_func_gen_one_channel_passes_thru_call_setter(fg, mocker):\n    mock_properties = [mocker.PropertyMock() for _ in range(4)]\n    mock_method = mocker.MagicMock()\n\n    mocker.patch(\n        \"instruments.abstract_instruments.FunctionGenerator.frequency\",\n        new=mock_properties[0],\n    )\n    mocker.patch(\n        \"instruments.abstract_instruments.FunctionGenerator.function\",\n        new=mock_properties[1],\n    )\n    mocker.patch(\n        \"instruments.abstract_instruments.FunctionGenerator.offset\",\n        new=mock_properties[2],\n    )\n    mocker.patch(\n        \"instruments.abstract_instruments.FunctionGenerator.phase\",\n        new=mock_properties[3],\n    )\n    mocker.patch(\n        \"instruments.abstract_instruments.FunctionGenerator._set_amplitude_\",\n        new=mock_method,\n    )\n\n    fg._channel_count = 1\n    fg.channel[0].amplitude = 1\n    fg.channel[0].frequency = 1\n    fg.channel[0].function = 1\n    fg.channel[0].offset = 1\n    fg.channel[0].phase = 1\n\n    for mock_property in mock_properties:\n        mock_property.assert_called_once_with(1)\n\n    mock_method.assert_called_once_with(magnitude=1, units=fg.VoltageMode.peak_to_peak)\n\n\ndef test_func_gen_channel_set_amplitude_dbm(mocker):\n    \"\"\"Get amplitude of channel when units are in dBm.\"\"\"\n    with expected_protocol(ik.abstract_instruments.FunctionGenerator, [], []) as inst:\n        value = 3.14\n        # mock out the _get_amplitude of parent to return value in dBm\n        mocker.patch.object(\n            inst,\n            \"_get_amplitude_\",\n            return_value=(\n                value,\n                ik.abstract_instruments.FunctionGenerator.VoltageMode.dBm,\n            ),\n        )\n\n        channel = inst.channel[0]\n        unit_eq(channel.amplitude, u.Quantity(value, u.dBm))\n\n\ndef test_func_gen_channel_sendcmd(mocker):\n    \"\"\"Send a command via parent class function.\"\"\"\n    with expected_protocol(ik.abstract_instruments.FunctionGenerator, [], []) as inst:\n        cmd = \"COMMAND\"\n        # mock out parent's send command\n        mock_sendcmd = mocker.patch.object(inst, \"sendcmd\")\n        channel = inst.channel[0]\n        channel.sendcmd(cmd)\n        mock_sendcmd.assert_called_with(cmd)\n\n\ndef test_func_gen__channel_sendcmd(mocker):\n    \"\"\"Send a query via parent class function.\"\"\"\n    with expected_protocol(ik.abstract_instruments.FunctionGenerator, [], []) as inst:\n        cmd = \"QUERY\"\n        size = 13\n        retval = \"ANSWER\"\n        # mock out parent's query command\n        mock_query = mocker.patch.object(inst, \"query\", return_value=retval)\n        channel = inst.channel[0]\n        assert channel.query(cmd, size=size) == retval\n        mock_query.assert_called_with(cmd, size)\n"
  },
  {
    "path": "tests/test_abstract_inst/test_multimeter.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the abstract multimeter class\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport pytest\n\nimport instruments as ik\nfrom tests import expected_protocol\n\n# TESTS ######################################################################\n\n\n@pytest.fixture\ndef mul(monkeypatch):\n    \"\"\"Patch and return Multimeter class for access.\"\"\"\n    inst = ik.abstract_instruments.Multimeter\n    monkeypatch.setattr(inst, \"__abstractmethods__\", set())\n    return inst\n\n\ndef test_multimeter_mode(mul):\n    \"\"\"Get / set mode: ensure existence.\"\"\"\n    with expected_protocol(mul, [], []) as inst:\n        _ = inst.mode\n        inst.mode = 42\n\n\ndef test_multimeter_trigger_mode(mul):\n    \"\"\"Get / set trigger mode: ensure existence.\"\"\"\n    with expected_protocol(mul, [], []) as inst:\n        _ = inst.trigger_mode\n        inst.trigger_mode = 42\n\n\ndef test_multimeter_relative(mul):\n    \"\"\"Get / set relative: ensure existence.\"\"\"\n    with expected_protocol(mul, [], []) as inst:\n        _ = inst.relative\n        inst.relative = 42\n\n\ndef test_multimeter_input_range(mul):\n    \"\"\"Get / set input range: ensure existence.\"\"\"\n    with expected_protocol(mul, [], []) as inst:\n        _ = inst.input_range\n        inst.input_range = 42\n\n\ndef test_multimeter_measure(mul):\n    \"\"\"Measure: ensure existence.\"\"\"\n    with expected_protocol(mul, [], []) as inst:\n        inst.measure(\"mode\")\n"
  },
  {
    "path": "tests/test_abstract_inst/test_optical_spectrum_analyzer.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the abstract optical spectrum analyzer class\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport pytest\n\nimport instruments as ik\nfrom tests import expected_protocol\n\n# TESTS ######################################################################\n\n\n@pytest.fixture\ndef osa(monkeypatch):\n    \"\"\"Patch and return Optical Spectrum Analyzer class for access.\"\"\"\n    inst = ik.abstract_instruments.OpticalSpectrumAnalyzer\n    chan = ik.abstract_instruments.OpticalSpectrumAnalyzer.Channel\n    monkeypatch.setattr(inst, \"__abstractmethods__\", set())\n    monkeypatch.setattr(chan, \"__abstractmethods__\", set())\n    return inst\n\n\n# OPTICAL SPECTRUM ANALYZER CLASS #\n\n\ndef test_osa_channel(osa):\n    \"\"\"Get channel: ensure existence.\"\"\"\n    with expected_protocol(osa, [], []) as inst:\n        ch = inst.channel[0]\n        assert isinstance(ch, ik.abstract_instruments.OpticalSpectrumAnalyzer.Channel)\n\n\ndef test_osa_start_wl(osa):\n    \"\"\"Get / set start wavelength: ensure existence.\"\"\"\n    with expected_protocol(osa, [], []) as inst:\n        with pytest.raises(NotImplementedError):\n            _ = inst.start_wl\n        with pytest.raises(NotImplementedError):\n            inst.start_wl = 42\n\n\ndef test_osa_stop_wl(osa):\n    \"\"\"Get / set stop wavelength: ensure existence.\"\"\"\n    with expected_protocol(osa, [], []) as inst:\n        with pytest.raises(NotImplementedError):\n            _ = inst.stop_wl\n        with pytest.raises(NotImplementedError):\n            inst.stop_wl = 42\n\n\ndef test_osa_bandwidth(osa):\n    \"\"\"Get / set bandwidth: ensure existence.\"\"\"\n    with expected_protocol(osa, [], []) as inst:\n        with pytest.raises(NotImplementedError):\n            _ = inst.bandwidth\n        with pytest.raises(NotImplementedError):\n            inst.bandwidth = 42\n\n\ndef test_osa_start_sweep(osa):\n    \"\"\"Start sweep: ensure existence.\"\"\"\n    with expected_protocol(osa, [], []) as inst:\n        with pytest.raises(NotImplementedError):\n            inst.start_sweep()\n\n\n# OSAChannel #\n\n\n@pytest.mark.parametrize(\"num_ch\", [1, 5])\ndef test_osa_channel_wavelength(osa, num_ch):\n    \"\"\"Channel wavelength method: ensure existence.\"\"\"\n    with expected_protocol(osa, [], []) as inst:\n        inst._channel_count = num_ch\n        ch = inst.channel[0]\n        with pytest.raises(NotImplementedError):\n            ch.wavelength()\n        with pytest.raises(NotImplementedError):\n            inst.wavelength()  # single channel instrument\n\n\n@pytest.mark.parametrize(\"num_ch\", [1, 5])\ndef test_osa_channel_data(osa, num_ch):\n    \"\"\"Channel data method: ensure existence.\"\"\"\n    with expected_protocol(osa, [], []) as inst:\n        inst._channel_count = num_ch\n        ch = inst.channel[0]\n        with pytest.raises(NotImplementedError):\n            ch.data()\n        with pytest.raises(NotImplementedError):\n            inst.data()  # single channel instrument\n"
  },
  {
    "path": "tests/test_abstract_inst/test_oscilloscope.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the abstract oscilloscope class\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport pytest\n\nimport instruments as ik\nfrom tests import expected_protocol\n\n# TESTS ######################################################################\n\n\n@pytest.fixture\ndef osc(monkeypatch):\n    \"\"\"Patch and return Oscilloscope class for access.\"\"\"\n    inst = ik.abstract_instruments.Oscilloscope\n    monkeypatch.setattr(inst, \"__abstractmethods__\", set())\n    return inst\n\n\n@pytest.fixture\ndef osc_ch(monkeypatch):\n    \"\"\"Patch and return OscilloscopeChannel class for access.\"\"\"\n    inst = ik.abstract_instruments.Oscilloscope.Channel\n    monkeypatch.setattr(inst, \"__abstractmethods__\", set())\n    return inst\n\n\n@pytest.fixture\ndef osc_ds(monkeypatch):\n    \"\"\"Patch and return OscilloscopeDataSource class for access.\"\"\"\n    inst = ik.abstract_instruments.Oscilloscope.DataSource\n    monkeypatch.setattr(inst, \"__abstractmethods__\", set())\n    return inst\n\n\n# OSCILLOSCOPE #\n\n\ndef test_oscilloscope_channel(osc):\n    \"\"\"Get channel: ensure existence.\"\"\"\n    with expected_protocol(osc, [], []) as inst:\n        with pytest.raises(NotImplementedError):\n            _ = inst.channel\n\n\ndef test_oscilloscope_ref(osc):\n    \"\"\"Get ref: ensure existence.\"\"\"\n    with expected_protocol(osc, [], []) as inst:\n        with pytest.raises(NotImplementedError):\n            _ = inst.ref\n\n\ndef test_oscilloscope_math(osc):\n    \"\"\"Get math: ensure existence.\"\"\"\n    with expected_protocol(osc, [], []) as inst:\n        with pytest.raises(NotImplementedError):\n            _ = inst.math\n\n\ndef test_oscilloscope_force_trigger(osc):\n    \"\"\"Force a trigger: ensure existence.\"\"\"\n    with expected_protocol(osc, [], []) as inst:\n        with pytest.raises(NotImplementedError):\n            inst.force_trigger()\n\n\n# OSCILLOSCOPE CHANNEL #\n\n\ndef test_oscilloscope_channel_coupling(osc_ch):\n    \"\"\"Get / set channel coupling: ensure existence.\"\"\"\n    inst = osc_ch()\n    with pytest.raises(NotImplementedError):\n        _ = inst.coupling\n    with pytest.raises(NotImplementedError):\n        inst.coupling = 42\n\n\n# OSCILLOSCOPE DATA SOURCE #\n\n\ndef test_oscilloscope_data_source_init(osc_ds):\n    \"\"\"Initialize Oscilloscope Data Source.\"\"\"\n    parent = \"parent\"\n    name = \"name\"\n    inst = osc_ds(parent, name)\n    assert inst._parent == parent\n    assert inst._name == name\n    assert inst._old_dsrc is None\n\n\ndef test_oscilloscope_data_source_name(osc_ds):\n    \"\"\"Get data source name: ensure existence.\"\"\"\n    parent = \"parent\"\n    name = \"name\"\n    inst = osc_ds(parent, name)\n    with pytest.raises(NotImplementedError):\n        _ = inst.name\n\n\ndef test_oscilloscope_data_source_read_waveform(osc_ds):\n    \"\"\"Read data source waveform: ensure existence.\"\"\"\n    parent = \"parent\"\n    name = \"name\"\n    inst = osc_ds(parent, name)\n    with pytest.raises(NotImplementedError):\n        inst.read_waveform()\n"
  },
  {
    "path": "tests/test_abstract_inst/test_power_supply.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the abstract power supply class\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport pytest\n\nimport instruments as ik\nfrom tests import expected_protocol\n\n# TESTS ######################################################################\n\n\n@pytest.fixture\ndef ps(monkeypatch):\n    \"\"\"Patch and return Power Supply class for access.\"\"\"\n    inst = ik.abstract_instruments.PowerSupply\n    monkeypatch.setattr(inst, \"__abstractmethods__\", set())\n    return inst\n\n\n@pytest.fixture\ndef ps_ch(monkeypatch):\n    \"\"\"Patch and return Power Supply Channel class for access.\"\"\"\n    inst = ik.abstract_instruments.PowerSupply.Channel\n    monkeypatch.setattr(inst, \"__abstractmethods__\", set())\n    return inst\n\n\n# POWER SUPPLY #\n\n\ndef test_power_supply_channel(ps):\n    \"\"\"Get channel: ensure existence.\"\"\"\n    with expected_protocol(ps, [], []) as inst:\n        with pytest.raises(NotImplementedError):\n            _ = inst.channel\n\n\ndef test_power_supply_voltage(ps):\n    \"\"\"Get / set voltage: ensure existence.\"\"\"\n    with expected_protocol(ps, [], []) as inst:\n        _ = inst.voltage\n        inst.voltage = 42\n\n\ndef test_power_supply_current(ps):\n    \"\"\"Get / set current: ensure existence.\"\"\"\n    with expected_protocol(ps, [], []) as inst:\n        _ = inst.current\n        inst.current = 42\n\n\n# POWER SUPPLY CHANNEL #\n\n\ndef test_power_supply_channel_mode(ps_ch):\n    \"\"\"Get / set channel mode: ensure existence.\"\"\"\n    inst = ps_ch()\n    _ = inst.mode\n    inst.mode = 42\n\n\ndef test_power_supply_channel_voltage(ps_ch):\n    \"\"\"Get / set channel voltage: ensure existence.\"\"\"\n    inst = ps_ch()\n    _ = inst.voltage\n    inst.voltage = 42\n\n\ndef test_power_supply_channel_current(ps_ch):\n    \"\"\"Get / set channel current: ensure existence.\"\"\"\n    inst = ps_ch()\n    _ = inst.current\n    inst.current = 42\n\n\ndef test_power_supply_channel_output(ps_ch):\n    \"\"\"Get / set channel output: ensure existence.\"\"\"\n    inst = ps_ch()\n    _ = inst.output\n    inst.output = 42\n"
  },
  {
    "path": "tests/test_abstract_inst/test_signal_generator/test_channel.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the abstract signal generator channel class\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport pytest\n\nimport instruments as ik\n\n# TESTS ######################################################################\n\n\n@pytest.fixture\ndef sgc(monkeypatch):\n    \"\"\"Patch and return SGChannel for direct access of metaclass.\"\"\"\n    inst = ik.abstract_instruments.signal_generator.SGChannel\n    monkeypatch.setattr(inst, \"__abstractmethods__\", set())\n    return inst\n\n\ndef test_sg_channel_frequency(sgc):\n    \"\"\"Get / set frequency: Ensure existence.\"\"\"\n    inst = sgc()\n    _ = inst.frequency\n    inst.frequency = 42\n\n\ndef test_sg_channel_power(sgc):\n    \"\"\"Get / set power: Ensure existence.\"\"\"\n    inst = sgc()\n    _ = inst.power\n    inst.power = 42\n\n\ndef test_sg_channel_phase(sgc):\n    \"\"\"Get / set phase: Ensure existence.\"\"\"\n    inst = sgc()\n    _ = inst.phase\n    inst.phase = 42\n\n\ndef test_sg_channel_output(sgc):\n    \"\"\"Get / set output: Ensure existence.\"\"\"\n    inst = sgc()\n    _ = inst.output\n    inst.output = 4\n"
  },
  {
    "path": "tests/test_abstract_inst/test_signal_generator/test_signal_generator.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the abstract signal generator class\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport pytest\n\nimport instruments as ik\nfrom tests import expected_protocol\n\n# TESTS ######################################################################\n\n\n@pytest.fixture\ndef sg(monkeypatch):\n    \"\"\"Patch and return signal generator for direct access of metaclass.\"\"\"\n    inst = ik.abstract_instruments.signal_generator.SignalGenerator\n    monkeypatch.setattr(inst, \"__abstractmethods__\", set())\n    return inst\n\n\ndef test_signal_generator_channel(sg):\n    \"\"\"Get channel: Ensure existence.\"\"\"\n    with expected_protocol(sg, [], []) as inst:\n        with pytest.raises(NotImplementedError):\n            _ = inst.channel\n"
  },
  {
    "path": "tests/test_abstract_inst/test_signal_generator/test_single_channel_sg.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the abstract signal generator class\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport pytest\n\nimport instruments as ik\nfrom tests import expected_protocol\n\n# TESTS ######################################################################\n\n\n@pytest.fixture\ndef scsg(monkeypatch):\n    \"\"\"Patch and return signal generator for direct access of metaclass.\"\"\"\n    inst = ik.abstract_instruments.signal_generator.SingleChannelSG\n    monkeypatch.setattr(inst, \"__abstractmethods__\", set())\n    return inst\n\n\ndef test_signal_generator_channel(scsg):\n    \"\"\"Get channel: Ensure existence.\"\"\"\n    with expected_protocol(scsg, [], []) as inst:\n        assert inst.channel[0] == inst\n"
  },
  {
    "path": "tests/test_agilent/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_agilent/test_agilent_33220a.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for generic SCPI function generator instruments\n\"\"\"\n\n# IMPORTS ####################################################################\n\nfrom hypothesis import given, strategies as st\nimport pytest\n\nfrom instruments.units import ureg as u\n\nimport instruments as ik\nfrom tests import expected_protocol, make_name_test\n\n# TESTS ######################################################################\n\ntest_scpi_func_gen_name = make_name_test(ik.agilent.Agilent33220a)\n\n\ndef test_agilent33220a_amplitude():\n    with expected_protocol(\n        ik.agilent.Agilent33220a,\n        [\n            \"VOLT:UNIT?\",\n            \"VOLT?\",\n            \"VOLT:UNIT VPP\",\n            \"VOLT 2.0\",\n            \"VOLT:UNIT DBM\",\n            \"VOLT 1.5\",\n        ],\n        [\"VPP\", \"+1.000000E+00\"],\n    ) as fg:\n        assert fg.amplitude == (1 * u.V, fg.VoltageMode.peak_to_peak)\n        fg.amplitude = 2 * u.V\n        fg.amplitude = (1.5 * u.V, fg.VoltageMode.dBm)\n\n\ndef test_agilent33220a_frequency():\n    with expected_protocol(\n        ik.agilent.Agilent33220a, [\"FREQ?\", \"FREQ 1.005000e+02\"], [\"+1.234000E+03\"]\n    ) as fg:\n        assert fg.frequency == 1234 * u.Hz\n        fg.frequency = 100.5 * u.Hz\n\n\ndef test_agilent33220a_function():\n    with expected_protocol(\n        ik.agilent.Agilent33220a, [\"FUNC?\", \"FUNC:SQU\"], [\"SIN\"]\n    ) as fg:\n        assert fg.function == fg.Function.sinusoid\n        fg.function = fg.Function.square\n\n\ndef test_agilent33220a_offset():\n    with expected_protocol(\n        ik.agilent.Agilent33220a,\n        [\"VOLT:OFFS?\", \"VOLT:OFFS 4.321000e-01\"],\n        [\n            \"+1.234000E+01\",\n        ],\n    ) as fg:\n        assert fg.offset == 12.34 * u.V\n        fg.offset = 0.4321 * u.V\n\n\ndef test_agilent33220a_duty_cycle():\n    with expected_protocol(\n        ik.agilent.Agilent33220a,\n        [\"FUNC:SQU:DCYC?\", \"FUNC:SQU:DCYC 75\"],\n        [\n            \"53\",\n        ],\n    ) as fg:\n        assert fg.duty_cycle == 53\n        fg.duty_cycle = 75\n\n\ndef test_agilent33220a_ramp_symmetry():\n    with expected_protocol(\n        ik.agilent.Agilent33220a,\n        [\"FUNC:RAMP:SYMM?\", \"FUNC:RAMP:SYMM 75\"],\n        [\n            \"53\",\n        ],\n    ) as fg:\n        assert fg.ramp_symmetry == 53\n        fg.ramp_symmetry = 75\n\n\ndef test_agilent33220a_output():\n    with expected_protocol(\n        ik.agilent.Agilent33220a,\n        [\"OUTP?\", \"OUTP OFF\"],\n        [\n            \"ON\",\n        ],\n    ) as fg:\n        assert fg.output is True\n        fg.output = False\n\n\ndef test_agilent33220a_output_sync():\n    with expected_protocol(\n        ik.agilent.Agilent33220a,\n        [\"OUTP:SYNC?\", \"OUTP:SYNC OFF\"],\n        [\n            \"ON\",\n        ],\n    ) as fg:\n        assert fg.output_sync is True\n        fg.output_sync = False\n\n\ndef test_agilent33220a_output_polarity():\n    with expected_protocol(\n        ik.agilent.Agilent33220a,\n        [\"OUTP:POL?\", \"OUTP:POL NORM\"],\n        [\n            \"INV\",\n        ],\n    ) as fg:\n        assert fg.output_polarity == fg.OutputPolarity.inverted\n        fg.output_polarity = fg.OutputPolarity.normal\n\n\ndef test_agilent33220a_load_resistance():\n    with expected_protocol(\n        ik.agilent.Agilent33220a,\n        [\"OUTP:LOAD?\", \"OUTP:LOAD?\", \"OUTP:LOAD 100\", \"OUTP:LOAD MAX\"],\n        [\"50\", \"INF\"],\n    ) as fg:\n        assert fg.load_resistance == 50 * u.ohm\n        assert fg.load_resistance == fg.LoadResistance.high_impedance\n        fg.load_resistance = 100 * u.ohm\n        fg.load_resistance = fg.LoadResistance.maximum\n\n\n@given(value=st.floats().filter(lambda x: x < 0 or x > 10000))\ndef test_agilent33220a_load_resistance_value_invalid(value):\n    \"\"\"Raise ValueError when resistance value loaded is out of range.\"\"\"\n    with expected_protocol(ik.agilent.Agilent33220a, [], []) as fg:\n        with pytest.raises(ValueError) as err_info:\n            fg.load_resistance = value\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"Load resistance must be between 0 and 10,000\"\n\n\ndef test_phase_not_implemented_error():\n    \"\"\"Raise a NotImplementedError when getting / setting the phase.\"\"\"\n    with expected_protocol(ik.agilent.Agilent33220a, [], []) as fg:\n        with pytest.raises(NotImplementedError):\n            _ = fg.phase()\n        with pytest.raises(NotImplementedError):\n            fg.phase = 42\n"
  },
  {
    "path": "tests/test_agilent/test_agilent_34410a.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for Agilent 34410a\n\"\"\"\n\n# IMPORTS ####################################################################\n\nimport pytest\n\nimport instruments as ik\nfrom instruments.optional_dep_finder import numpy\nfrom tests import expected_protocol, iterable_eq, make_name_test, unit_eq\nfrom instruments.units import ureg as u\n\n# TESTS ######################################################################\n\ntest_agilent_34410a_name = make_name_test(ik.agilent.Agilent34410a)\n\n\ndef test_agilent34410a_read():\n    with expected_protocol(\n        ik.agilent.Agilent34410a,\n        [\"CONF?\", \"READ?\"],\n        [\"VOLT +1.000000E+01,+3.000000E-06\", \"+1.86850000E-03\"],\n    ) as dmm:\n        unit_eq(dmm.read_meter(), +1.86850000e-03 * u.volt)\n\n\ndef test_agilent34410a_data_point_count():\n    with expected_protocol(\n        ik.agilent.Agilent34410a,\n        [\n            \"DATA:POIN?\",\n        ],\n        [\n            \"+215\",\n        ],\n    ) as dmm:\n        assert dmm.data_point_count == 215\n\n\ndef test_agilent34410a_init():\n    \"\"\"Switch device from `idle` to `wait-for-trigger state`.\"\"\"\n    with expected_protocol(ik.agilent.Agilent34410a, [\"INIT\"], []) as dmm:\n        dmm.init()\n\n\ndef test_agilent34410a_abort():\n    \"\"\"Abort all current measurements.\"\"\"\n    with expected_protocol(ik.agilent.Agilent34410a, [\"ABOR\"], []) as dmm:\n        dmm.abort()\n\n\ndef test_agilent34410a_clear_memory():\n    \"\"\"Clear non-volatile memory.\"\"\"\n    with expected_protocol(ik.agilent.Agilent34410a, [\"DATA:DEL NVMEM\"], []) as dmm:\n        dmm.clear_memory()\n\n\ndef test_agilent34410a_r():\n    with expected_protocol(\n        ik.agilent.Agilent34410a,\n        [\"CONF?\", \"FORM:DATA REAL,64\", \"R? 1\"],\n        [\n            \"VOLT +1.000000E+01,+3.000000E-06\",\n            # pylint: disable=no-member\n            b\"#18\" + bytes.fromhex(\"3FF0000000000000\"),\n        ],\n    ) as dmm:\n        expected = (u.Quantity(1, u.volt),)\n        if numpy:\n            expected = numpy.array([1]) * u.volt\n        actual = dmm.r(1)\n        iterable_eq(actual, expected)\n\n\ndef test_agilent34410a_r_count_zero():\n    \"\"\"Read measurements with count set to zero.\"\"\"\n    with expected_protocol(\n        ik.agilent.Agilent34410a,\n        [\"CONF?\", \"FORM:DATA REAL,64\", \"R?\"],\n        [\n            \"VOLT +1.000000E+01,+3.000000E-06\",\n            # pylint: disable=no-member\n            b\"#18\" + bytes.fromhex(\"3FF0000000000000\"),\n        ],\n    ) as dmm:\n        expected = (u.Quantity(1, u.volt),)\n        if numpy:\n            expected = numpy.array([1]) * u.volt\n        actual = dmm.r(0)\n        iterable_eq(actual, expected)\n\n\ndef test_agilent34410a_r_type_error():\n    \"\"\"Raise TypeError if count is not a integer.\"\"\"\n    wrong_type = \"42\"\n    with expected_protocol(\n        ik.agilent.Agilent34410a,\n        [\n            \"CONF?\",\n        ],\n        [\n            \"VOLT +1.000000E+01,+3.000000E-06\",\n        ],\n    ) as dmm:\n        with pytest.raises(TypeError) as err_info:\n            dmm.r(wrong_type)\n        err_msg = err_info.value.args[0]\n        assert err_msg == 'Parameter \"count\" must be an integer'\n\n\ndef test_agilent34410a_fetch():\n    with expected_protocol(\n        ik.agilent.Agilent34410a,\n        [\"CONF?\", \"FETC?\"],\n        [\"VOLT +1.000000E+01,+3.000000E-06\", \"+4.27150000E-03,5.27150000E-03\"],\n    ) as dmm:\n        data = dmm.fetch()\n        expected = (4.27150000e-03 * u.volt, 5.27150000e-03 * u.volt)\n        if numpy:\n            expected = (4.27150000e-03, 5.27150000e-03) * u.volt\n        iterable_eq(data, expected)\n\n\ndef test_agilent34410a_read_data():\n    with expected_protocol(\n        ik.agilent.Agilent34410a,\n        [\"CONF?\", \"FORM:DATA ASC\", \"DATA:REM? 2\"],\n        [\"VOLT +1.000000E+01,+3.000000E-06\", \"+4.27150000E-03,5.27150000E-03\"],\n    ) as dmm:\n        data = dmm.read_data(2)\n        unit_eq(data[0], 4.27150000e-03 * u.volt)\n        unit_eq(data[1], 5.27150000e-03 * u.volt)\n\n\ndef test_agilent34410a_read_data_count_minus_one():\n    \"\"\"Read data for all data points available.\"\"\"\n    sample_count = 100\n    with expected_protocol(\n        ik.agilent.Agilent34410a,\n        [\"DATA:POIN?\", \"CONF?\", \"FORM:DATA ASC\", f\"DATA:REM? {sample_count}\"],\n        [\n            f\"{sample_count}\",\n            \"VOLT +1.000000E+01,+3.000000E-06\",\n            \"+4.27150000E-03,5.27150000E-03\",\n        ],\n    ) as dmm:\n        data = dmm.read_data(-1)\n        unit_eq(data[0], 4.27150000e-03 * u.volt)\n        unit_eq(data[1], 5.27150000e-03 * u.volt)\n\n\ndef test_agilent34410a_read_data_type_error():\n    \"\"\"Raise Type error if count is not an integer.\"\"\"\n    wrong_type = \"42\"\n    with expected_protocol(ik.agilent.Agilent34410a, [], []) as dmm:\n        with pytest.raises(TypeError) as err_info:\n            dmm.read_data(wrong_type)\n        err_msg = err_info.value.args[0]\n        assert err_msg == 'Parameter \"sample_count\" must be an integer.'\n\n\ndef test_agilent34410a_read_data_nvmem():\n    with expected_protocol(\n        ik.agilent.Agilent34410a,\n        [\n            \"CONF?\",\n            \"DATA:DATA? NVMEM\",\n        ],\n        [\"VOLT +1.000000E+01,+3.000000E-06\", \"+4.27150000E-03,5.27150000E-03\"],\n    ) as dmm:\n        data = dmm.read_data_nvmem()\n        unit_eq(data[0], 4.27150000e-03 * u.volt)\n        unit_eq(data[1], 5.27150000e-03 * u.volt)\n\n\ndef test_agilent34410a_read_last_data():\n    with expected_protocol(\n        ik.agilent.Agilent34410a,\n        [\n            \"DATA:LAST?\",\n        ],\n        [\n            \"+1.73730000E-03 VDC\",\n        ],\n    ) as dmm:\n        unit_eq(dmm.read_last_data(), 1.73730000e-03 * u.volt)\n\n\ndef test_agilent34410a_read_last_data_na():\n    \"\"\"Return 9.91e37 if no data are available to read.\"\"\"\n    na_value_str = \"9.91000000E+37\"\n    with expected_protocol(\n        ik.agilent.Agilent34410a, [\"DATA:LAST?\"], [na_value_str]\n    ) as dmm:\n        assert dmm.read_last_data() == float(na_value_str)\n"
  },
  {
    "path": "tests/test_aimtti/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_aimtti/test_aimttiel302p.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the Aim-TTI EL302P single output power supply\n\"\"\"\n\n# IMPORTS #####################################################################\n\nimport pytest\n\nimport instruments as ik\nfrom instruments.units import ureg as u\nfrom tests import expected_protocol, unit_eq\n\n# TESTS #######################################################################\n\n\ndef test_channel():\n    with expected_protocol(ik.aimtti.AimTTiEL302P, [], [], sep=\"\\n\") as psu:\n        assert psu.channel[0] == psu\n        assert len(psu.channel) == 1\n\n\ndef test_current():\n    with expected_protocol(\n        ik.aimtti.AimTTiEL302P, [\"I 1.0\", \"I?\"], [\"I 1.00\"], sep=\"\\n\"\n    ) as psu:\n        psu.current = 1.0 * u.amp\n        assert psu.current == 1.0 * u.amp\n\n\ndef test_current_sense():\n    with expected_protocol(\n        ik.aimtti.AimTTiEL302P, [\"IO?\"], [\"I 1.00\"], sep=\"\\n\"\n    ) as psu:\n        assert psu.current_sense == 1.00 * u.amp\n\n\ndef test_error():\n    with expected_protocol(\n        ik.aimtti.AimTTiEL302P, [\"ERR?\"], [\"ERR 0\"], sep=\"\\n\"\n    ) as psu:\n        assert psu.error == ik.aimtti.AimTTiEL302P.Error.error_none\n\n\ndef test_mode():\n    with expected_protocol(ik.aimtti.AimTTiEL302P, [\"M?\"], [\"M CV\"], sep=\"\\n\") as psu:\n        assert psu.mode == ik.aimtti.AimTTiEL302P.Mode.voltage\n\n\ndef test_name():\n    with expected_protocol(\n        ik.aimtti.AimTTiEL302P, [\"*IDN?\"], [\"Thurlby Thandar,EL302P,0,v2.00\"], sep=\"\\n\"\n    ) as psu:\n        assert psu.name == \"Thurlby Thandar EL302P\"\n\n\ndef test_off():\n    with expected_protocol(\n        ik.aimtti.AimTTiEL302P, [\"OFF\", \"OUT?\"], [\"OUT OFF\"], sep=\"\\n\"\n    ) as psu:\n        psu.output = False\n        assert not psu.output\n\n\ndef test_on():\n    with expected_protocol(\n        ik.aimtti.AimTTiEL302P, [\"ON\", \"OUT?\"], [\"OUT ON\"], sep=\"\\n\"\n    ) as psu:\n        psu.output = True\n        assert psu.output\n\n\ndef test_reset():\n    with expected_protocol(ik.aimtti.AimTTiEL302P, [\"*RST\"], [], sep=\"\\n\") as psu:\n        psu.reset()\n\n\ndef test_voltage():\n    with expected_protocol(\n        ik.aimtti.AimTTiEL302P, [\"V 10.0\", \"V?\"], [\"V 10.00\"], sep=\"\\n\"\n    ) as psu:\n        psu.voltage = 10.0 * u.volt\n        assert psu.voltage == 10.0 * u.volt\n\n\ndef test_voltage_sense():\n    with expected_protocol(\n        ik.aimtti.AimTTiEL302P, [\"VO?\"], [\"V 24.00\"], sep=\"\\n\"\n    ) as psu:\n        assert psu.voltage_sense == 24.00 * u.volt\n"
  },
  {
    "path": "tests/test_base_instrument.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the base Instrument class\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport socket\nimport io\nimport serial\nimport usb.core\nfrom serial.tools.list_ports_common import ListPortInfo\n\nimport pytest\n\nimport instruments as ik\nfrom instruments.optional_dep_finder import numpy\nfrom tests import expected_protocol\n\n# pylint: disable=unused-import\nfrom instruments.abstract_instruments.comm import (\n    SocketCommunicator,\n    USBCommunicator,\n    VisaCommunicator,\n    FileCommunicator,\n    LoopbackCommunicator,\n    GPIBCommunicator,\n    AbstractCommunicator,\n    USBTMCCommunicator,\n    VXI11Communicator,\n    SerialCommunicator,\n)\nfrom instruments.errors import AcknowledgementError, PromptError\nfrom tests import iterable_eq\n\nfrom . import mock\n\n# TESTS ######################################################################\n\n# pylint: disable=no-member,protected-access\n\n\n# BINBLOCKREAD TESTS\n\n\ndef test_instrument_binblockread():\n    with expected_protocol(\n        ik.Instrument,\n        [],\n        [\n            b\"#210\" + bytes.fromhex(\"00000001000200030004\") + b\"0\",\n        ],\n        sep=\"\\n\",\n    ) as inst:\n        actual_data = inst.binblockread(2)\n        expected = (0, 1, 2, 3, 4)\n        if numpy:\n            expected = numpy.array(expected)\n        iterable_eq(actual_data, expected)\n\n\ndef test_instrument_binblockread_two_reads():\n    inst = ik.Instrument.open_test()\n    data = bytes.fromhex(\"00000001000200030004\")\n    inst._file.read_raw = mock.MagicMock(\n        side_effect=[b\"#\", b\"2\", b\"10\", data[:6], data[6:]]\n    )\n\n    expected = (0, 1, 2, 3, 4)\n    if numpy:\n        expected = numpy.array((0, 1, 2, 3, 4))\n    iterable_eq(inst.binblockread(2), expected)\n\n    calls_expected = [1, 1, 2, 10, 4]\n    calls_actual = [call[0][0] for call in inst._file.read_raw.call_args_list]\n    iterable_eq(calls_actual, calls_expected)\n\n\ndef test_instrument_binblockread_too_many_reads():\n    with pytest.raises(IOError):\n        inst = ik.Instrument.open_test()\n        data = bytes.fromhex(\"00000001000200030004\")\n        inst._file.read_raw = mock.MagicMock(\n            side_effect=[b\"#\", b\"2\", b\"10\", data[:6], b\"\", b\"\", b\"\"]\n        )\n\n        _ = inst.binblockread(2)\n\n\ndef test_instrument_binblockread_bad_block_start():\n    with pytest.raises(IOError):\n        inst = ik.Instrument.open_test()\n        inst._file.read_raw = mock.MagicMock(return_value=b\"@\")\n\n        _ = inst.binblockread(2)\n\n\n# OPEN CONNECTION TESTS\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.SocketCommunicator\")\n@mock.patch(\"instruments.abstract_instruments.instrument.socket\")\ndef test_instrument_open_tcpip(mock_socket, mock_socket_comm):\n    mock_socket.socket.return_value.__class__ = socket.socket\n    mock_socket_comm.return_value.__class__ = SocketCommunicator\n\n    inst = ik.Instrument.open_tcpip(\"127.0.0.1\", 1234)\n\n    assert isinstance(inst._file, SocketCommunicator) is True\n\n    # Check for call: SocketCommunicator(socket.socket())\n    mock_socket_comm.assert_called_with(mock_socket.socket.return_value)\n\n\ndef test_instrument_open_tcpip_auth_not_implemented():\n    \"\"\"Ensure `_authenticate` exists and raises NotImplemented error if hit here.\"\"\"\n    inst = ik.Instrument.open_test()\n    with pytest.raises(NotImplementedError):\n        inst._authenticate(auth=(\"user\", \"pwd\"))\n\n\n@pytest.mark.parametrize(\"auth\", [None, (\"user\", \"pwd\")])\n@mock.patch(\"instruments.abstract_instruments.instrument.SocketCommunicator\")\n@mock.patch(\"instruments.abstract_instruments.instrument.socket\")\ndef test_instrument_open_tcpip_passing_on_auth(\n    mock_socket, mock_socket_comm, auth, mocker\n):\n    \"\"\"Ensure auth only passed on if not None, see issue #439.\"\"\"\n    mock_socket.socket.return_value.__class__ = socket.socket\n    mock_socket_comm.return_value.__class__ = SocketCommunicator\n\n    # spy on the __init__ method of the Instrument class\n    inst_spy = mocker.spy(ik.abstract_instruments.instrument.Instrument, \"__init__\")\n\n    _ = ik.Instrument.open_tcpip(\"127.0.0.1\", 1234, auth=auth)\n\n    call_list = inst_spy.mock_calls\n    auth_kwarg = {\"auth\": auth}\n\n    if auth is not None:\n        assert auth_kwarg in call_list[0]\n    else:\n        assert auth_kwarg not in call_list[0]\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.serial_manager\")\ndef test_instrument_open_serial(mock_serial_manager):\n    mock_serial_manager.new_serial_connection.return_value.__class__ = (\n        SerialCommunicator\n    )\n\n    inst = ik.Instrument.open_serial(\"/dev/port\", baud=1234)\n\n    assert isinstance(inst._file, SerialCommunicator) is True\n\n    mock_serial_manager.new_serial_connection.assert_called_with(\n        \"/dev/port\", baud=1234, timeout=3, write_timeout=3\n    )\n\n\nclass fake_serial:\n    \"\"\"\n    Create a fake serial.Serial() object so that tests can be run without\n    accessing a non-existant port.\n    \"\"\"\n\n    # pylint: disable=unused-variable, unused-argument, no-self-use\n    def __init__(self, device, baudrate=None, timeout=None, writeTimeout=None):\n        self.device = device\n\n    def isOpen(self):\n        \"\"\"\n        Pretends that the serial connection is open.\n        \"\"\"\n        return True\n\n\n# TEST OPEN_SERIAL WITH USB IDENTIFIERS ######################################\n\n\ndef fake_comports():\n    \"\"\"\n    Generate a fake list of comports to compare against.\n    \"\"\"\n    fake_device = ListPortInfo(device=\"COM1\")\n    fake_device.vid = 0\n    fake_device.pid = 1000\n    fake_device.serial_number = \"a1\"\n\n    fake_device2 = ListPortInfo(device=\"COM2\")\n    fake_device2.vid = 1\n    fake_device2.pid = 1010\n    fake_device2.serial_number = \"c0\"\n    return [fake_device, fake_device2]\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.comports\", new=fake_comports)\n@mock.patch(\"instruments.abstract_instruments.instrument.serial_manager\")\ndef test_instrument_open_serial_by_usb_ids(mock_serial_manager):\n    mock_serial_manager.new_serial_connection.return_value.__class__ = (\n        SerialCommunicator\n    )\n\n    inst = ik.Instrument.open_serial(baud=1234, vid=1, pid=1010)\n    assert isinstance(inst._file, SerialCommunicator) is True\n    mock_serial_manager.new_serial_connection.assert_called_with(\n        \"COM2\", baud=1234, timeout=3, write_timeout=3\n    )\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.comports\", new=fake_comports)\n@mock.patch(\"instruments.abstract_instruments.instrument.serial_manager\")\ndef test_instrument_open_serial_by_usb_ids_and_serial_number(mock_serial_manager):\n    mock_serial_manager.new_serial_connection.return_value.__class__ = (\n        SerialCommunicator\n    )\n\n    inst = ik.Instrument.open_serial(baud=1234, vid=0, pid=1000, serial_number=\"a1\")\n    assert isinstance(inst._file, SerialCommunicator) is True\n    mock_serial_manager.new_serial_connection.assert_called_with(\n        \"COM1\", baud=1234, timeout=3, write_timeout=3\n    )\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.comports\")\n@mock.patch(\"instruments.abstract_instruments.instrument.serial_manager\")\ndef test_instrument_open_serial_by_usb_ids_multiple_matches(_, mock_comports):\n    with pytest.raises(serial.SerialException):\n        fake_device = ListPortInfo(device=\"COM1\")\n        fake_device.vid = 0\n        fake_device.pid = 1000\n        fake_device.serial_number = \"a1\"\n\n        fake_device2 = ListPortInfo(device=\"COM2\")\n        fake_device2.vid = 0\n        fake_device2.pid = 1000\n        fake_device2.serial_number = \"b2\"\n\n        mock_comports.return_value = [fake_device, fake_device2]\n\n        _ = ik.Instrument.open_serial(baud=1234, vid=0, pid=1000)\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.comports\", new=fake_comports)\n@mock.patch(\"instruments.abstract_instruments.instrument.serial_manager\")\ndef test_instrument_open_serial_by_usb_ids_incorrect_serial_num(mock_serial_manager):\n    with pytest.raises(ValueError):\n        mock_serial_manager.new_serial_connection.return_value.__class__ = (\n            SerialCommunicator\n        )\n        _ = ik.Instrument.open_serial(baud=1234, vid=0, pid=1000, serial_number=\"xyz\")\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.comports\", new=fake_comports)\n@mock.patch(\"instruments.abstract_instruments.instrument.serial_manager\")\ndef test_instrument_open_serial_by_usb_ids_cant_find(mock_serial_manager):\n    with pytest.raises(ValueError):\n        mock_serial_manager.new_serial_connection.return_value.__class__ = (\n            SerialCommunicator\n        )\n        _ = ik.Instrument.open_serial(baud=1234, vid=1234, pid=1000)\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.comports\", new=fake_comports)\n@mock.patch(\"instruments.abstract_instruments.instrument.serial_manager\")\ndef test_instrument_open_serial_no_port(mock_serial_manager):\n    with pytest.raises(ValueError):\n        mock_serial_manager.new_serial_connection.return_value.__class__ = (\n            SerialCommunicator\n        )\n        _ = ik.Instrument.open_serial(baud=1234)\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.comports\", new=fake_comports)\n@mock.patch(\"instruments.abstract_instruments.instrument.serial_manager\")\ndef test_instrument_open_serial_by_usb_ids_and_port(mock_serial_manager):\n    with pytest.raises(ValueError):\n        mock_serial_manager.new_serial_connection.return_value.__class__ = (\n            SerialCommunicator\n        )\n        _ = ik.Instrument.open_serial(port=\"COM1\", baud=1234, vid=1234, pid=1000)\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.comports\", new=fake_comports)\n@mock.patch(\"instruments.abstract_instruments.instrument.serial_manager\")\ndef test_instrument_open_serial_by_usb_vid_no_pid(mock_serial_manager):\n    with pytest.raises(ValueError):\n        mock_serial_manager.new_serial_connection.return_value.__class__ = (\n            SerialCommunicator\n        )\n        _ = ik.Instrument.open_serial(baud=1234, vid=1234)\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.comports\", new=fake_comports)\n@mock.patch(\"instruments.abstract_instruments.instrument.serial_manager\")\ndef test_instrument_open_serial_by_usb_pid_no_vid(mock_serial_manager):\n    with pytest.raises(ValueError):\n        mock_serial_manager.new_serial_connection.return_value.__class__ = (\n            SerialCommunicator\n        )\n        _ = ik.Instrument.open_serial(baud=1234, pid=1234)\n\n\n# TEST OPEN_GPIBUSB ##########################################################\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.GPIBCommunicator\")\n@mock.patch(\"instruments.abstract_instruments.instrument.serial_manager\")\ndef test_instrument_open_gpibusb(mock_serial_manager, mock_gpib_comm):\n    mock_serial_manager.new_serial_connection.return_value.__class__ = (\n        SerialCommunicator\n    )\n    mock_gpib_comm.return_value.__class__ = GPIBCommunicator\n\n    inst = ik.Instrument.open_gpibusb(\"/dev/port\", gpib_address=1, model=\"gi\")\n\n    assert isinstance(inst._file, GPIBCommunicator) is True\n\n    mock_serial_manager.new_serial_connection.assert_called_with(\n        \"/dev/port\", baud=460800, timeout=3, write_timeout=3\n    )\n\n    mock_gpib_comm.assert_called_with(\n        mock_serial_manager.new_serial_connection.return_value, 1, \"gi\"\n    )\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.GPIBCommunicator\")\n@mock.patch(\"instruments.abstract_instruments.instrument.socket\")\ndef test_instrument_open_gpibethernet(mock_socket_manager, mock_gpib_comm):\n    mock_gpib_comm.return_value.__class__ = GPIBCommunicator\n\n    host = \"192.168.1.13\"\n    port = 1818\n\n    inst = ik.Instrument.open_gpibethernet(host, port, gpib_address=1, model=\"pl\")\n\n    mock_socket_manager.socket.assert_called()\n    mock_socket_manager.socket().connect.assert_called_with((host, port))\n    assert isinstance(inst._file, GPIBCommunicator) is True\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.VisaCommunicator\")\n@mock.patch(\"instruments.abstract_instruments.instrument.pyvisa\")\ndef test_instrument_open_visa_new_version(mock_visa, mock_visa_comm):\n    mock_visa_comm.return_value.__class__ = VisaCommunicator\n    mock_visa.__version__ = \"1.8\"\n    visa_open_resource = mock_visa.ResourceManager.return_value.open_resource\n\n    inst = ik.Instrument.open_visa(\"abc123\")\n\n    assert isinstance(inst._file, VisaCommunicator) is True\n\n    visa_open_resource.assert_called_with(\"abc123\")\n    mock_visa_comm.assert_called_with(visa_open_resource(\"abc123\"))\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.VisaCommunicator\")\n@mock.patch(\"instruments.abstract_instruments.instrument.pyvisa\")\ndef test_instrument_open_visa_old_version(mock_visa, mock_visa_comm):\n    mock_visa_comm.return_value.__class__ = VisaCommunicator\n    mock_visa.__version__ = \"1.5\"\n\n    inst = ik.Instrument.open_visa(\"abc123\")\n\n    assert isinstance(inst._file, VisaCommunicator) is True\n\n    mock_visa.instrument.assert_called_with(\"abc123\")\n\n\ndef test_instrument_open_test():\n    a = mock.MagicMock()\n    b = mock.MagicMock()\n    a.__class__ = io.BytesIO\n    b.__class__ = io.BytesIO\n\n    inst = ik.Instrument.open_test(stdin=a, stdout=b)\n\n    assert isinstance(inst._file, LoopbackCommunicator)\n    assert inst._file._stdin == a\n    assert inst._file._stdout == b\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.VXI11Communicator\")\ndef test_instrument_open_vxi11(mock_vxi11_comm):\n    mock_vxi11_comm.return_value.__class__ = VXI11Communicator\n\n    inst = ik.Instrument.open_vxi11(\"string\", 1, key1=\"value\")\n\n    assert isinstance(inst._file, VXI11Communicator) is True\n\n    mock_vxi11_comm.assert_called_with(\"string\", 1, key1=\"value\")\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.USBCommunicator\")\n@mock.patch(\"instruments.abstract_instruments.instrument.usb\")\ndef test_instrument_open_usb(mock_usb, mock_usb_comm):\n    \"\"\"Open USB device.\"\"\"\n    mock_usb.core.find.return_value.__class__ = usb.core.Device\n    mock_usb_comm.return_value.__class__ = USBCommunicator\n\n    # fake instrument\n    vid = \"0x1000\"\n    pid = \"0x1000\"\n    dev = mock_usb.core.find(idVendor=vid, idProduct=pid)\n\n    # call instrument\n    inst = ik.Instrument.open_usb(vid, pid)\n\n    assert isinstance(inst._file, USBCommunicator)\n    mock_usb_comm.assert_called_with(dev)\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.usb\")\ndef test_instrument_open_usb_no_device(mock_usb):\n    \"\"\"Open USB, no device found.\"\"\"\n    mock_usb.core.find.return_value = None  # mock no instrument found\n    with pytest.raises(IOError) as err:\n        _ = ik.Instrument.open_usb(0x1000, 0x1000)\n    err_msg = err.value.args[0]\n    assert err_msg == \"No such device found.\"\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.USBTMCCommunicator\")\ndef test_instrument_open_usbtmc(mock_usbtmc_comm):\n    mock_usbtmc_comm.return_value.__class__ = USBTMCCommunicator\n\n    inst = ik.Instrument.open_usbtmc(\"string\", 1, key1=\"value\")\n\n    assert isinstance(inst._file, USBTMCCommunicator) is True\n\n    mock_usbtmc_comm.assert_called_with(\"string\", 1, key1=\"value\")\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.FileCommunicator\")\ndef test_instrument_open_file(mock_file_comm):\n    mock_file_comm.return_value.__class__ = FileCommunicator\n\n    inst = ik.Instrument.open_file(\"filename\")\n\n    assert isinstance(inst._file, FileCommunicator) is True\n\n    mock_file_comm.assert_called_with(\"filename\")\n\n\n# OPEN URI TESTS\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.Instrument.open_serial\")\ndef test_instrument_open_from_uri_serial(mock_open_conn):\n    _ = ik.Instrument.open_from_uri(\"serial:///dev/foobar\")\n\n    mock_open_conn.assert_called_with(\"/dev/foobar\", baud=115200)\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.Instrument.open_serial\")\ndef test_instrument_open_from_uri_serial_with_baud(mock_open_conn):\n    _ = ik.Instrument.open_from_uri(\"serial:///dev/foobar?baud=230400\")\n\n    mock_open_conn.assert_called_with(\"/dev/foobar\", baud=230400)\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.Instrument.open_tcpip\")\ndef test_instrument_open_from_uri_tcpip(mock_open_conn):\n    _ = ik.Instrument.open_from_uri(\"tcpip://192.169.0.1:8080\")\n\n    mock_open_conn.assert_called_with(\"192.169.0.1\", 8080)\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.Instrument.open_gpibusb\")\ndef test_instrument_open_from_uri_gpibusb(mock_open_conn):\n    _ = ik.Instrument.open_from_uri(\"gpib+usb:///dev/foobar/15\")\n\n    mock_open_conn.assert_called_with(\"/dev/foobar\", 15)\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.Instrument.open_gpibusb\")\ndef test_instrument_open_from_uri_gpibserial(mock_open_conn):\n    _ = ik.Instrument.open_from_uri(\"gpib+serial:///dev/foobar/7\")\n\n    mock_open_conn.assert_called_with(\"/dev/foobar\", 7)\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.Instrument.open_visa\")\ndef test_instrument_open_from_uri_visa(mock_open_conn):\n    _ = ik.Instrument.open_from_uri(\"visa://USB::0x1234::0xFF12::0x7421::0::INSTR\")\n\n    mock_open_conn.assert_called_with(\"USB::0x1234::0xFF12::0x7421::0::INSTR\")\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.Instrument.open_usbtmc\")\ndef test_instrument_open_from_uri_usbtmc(mock_open_conn):\n    _ = ik.Instrument.open_from_uri(\"usbtmc://USB::0x1234::0xFF12::0x7421::0::INSTR\")\n\n    mock_open_conn.assert_called_with(\"USB::0x1234::0xFF12::0x7421::0::INSTR\")\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.Instrument.open_file\")\ndef test_instrument_open_from_uri_file(mock_open_conn):\n    _ = ik.Instrument.open_from_uri(\"file:///dev/filename\")\n\n    mock_open_conn.assert_called_with(\"/dev/filename\")\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.Instrument.open_vxi11\")\ndef test_instrument_open_from_uri_vxi11(mock_open_conn):\n    _ = ik.Instrument.open_from_uri(\"vxi11://TCPIP::192.168.1.105::gpib,5::INSTR\")\n\n    mock_open_conn.assert_called_with(\"TCPIP::192.168.1.105::gpib,5::INSTR\")\n\n\ndef test_instrument_open_from_uri_invalid_scheme():\n    with pytest.raises(NotImplementedError):\n        _ = ik.Instrument.open_from_uri(\"foo://bar\")\n\n\n@mock.patch(\"instruments.abstract_instruments.comm.LoopbackCommunicator.close\")\ndef test_instrument_context_manager(mock_close: mock.Mock):\n    with ik.Instrument.open_test():\n        pass\n    mock_close.assert_called()\n\n\n# INIT TESTS\n\n\ndef test_instrument_init_bad_filelike():\n    with pytest.raises(TypeError):\n        _ = ik.Instrument(mock.MagicMock())\n\n\ndef test_instrument_init():\n    mock_filelike = mock.MagicMock()\n    mock_filelike.__class__ = AbstractCommunicator\n    inst = ik.Instrument(mock_filelike)\n\n    assert inst._testing is False\n    assert inst._prompt is None\n    assert inst._terminator == \"\\n\"\n    assert inst._file == mock_filelike\n\n\ndef test_instrument_init_loopbackcomm():\n    mock_filelike = mock.MagicMock()\n    mock_filelike.__class__ = LoopbackCommunicator\n    inst = ik.Instrument(mock_filelike)\n\n    assert inst._testing is True\n\n\n# COMM TESTS\n\n\ndef test_instrument_default_ack_expected():\n    mock_filelike = mock.MagicMock()\n    mock_filelike.__class__ = AbstractCommunicator\n    inst = ik.Instrument(mock_filelike)\n\n    assert inst._ack_expected() is None\n    assert inst._ack_expected(\"foobar\") is None\n\n\ndef test_instrument_sendcmd_noack_noprompt():\n    mock_filelike = mock.MagicMock()\n    mock_filelike.__class__ = AbstractCommunicator\n    inst = ik.Instrument(mock_filelike)\n\n    inst.sendcmd(\"foobar\")\n\n    inst._file.sendcmd.assert_called_with(\"foobar\")\n\n\ndef test_instrument_sendcmd_noprompt():\n    mock_filelike = mock.MagicMock()\n    mock_filelike.__class__ = AbstractCommunicator\n    inst = ik.Instrument(mock_filelike)\n\n    def new_ack(msg):\n        return msg\n\n    inst._ack_expected = new_ack\n    inst.read = mock.MagicMock(return_value=\"foobar\")\n\n    inst.sendcmd(\"foobar\")\n    inst.read.assert_called_with()\n    inst._file.sendcmd.assert_called_with(\"foobar\")\n\n\ndef test_instrument_sendcmd_noprompt_multiple_ack():\n    mock_filelike = mock.MagicMock()\n    mock_filelike.__class__ = AbstractCommunicator\n    inst = ik.Instrument(mock_filelike)\n\n    def new_ack(msg):\n        return [msg, \"second ack\"]\n\n    inst._ack_expected = new_ack\n    inst.read = mock.MagicMock(side_effect=[\"foobar\", \"second ack\"])\n\n    inst.sendcmd(\"foobar\")\n    inst.read.assert_called_with()\n    inst._file.sendcmd.assert_called_with(\"foobar\")\n\n\ndef test_instrument_sendcmd_bad_ack():\n    with pytest.raises(AcknowledgementError):\n        mock_filelike = mock.MagicMock()\n        mock_filelike.__class__ = AbstractCommunicator\n        inst = ik.Instrument(mock_filelike)\n\n        def new_ack(msg):\n            return msg\n\n        inst._ack_expected = new_ack\n        inst.read = mock.MagicMock(return_value=\"derp\")\n\n        inst.sendcmd(\"foobar\")\n\n\ndef test_instrument_sendcmd_noack():\n    mock_filelike = mock.MagicMock()\n    mock_filelike.__class__ = AbstractCommunicator\n    inst = ik.Instrument(mock_filelike)\n\n    inst.prompt = \"> \"\n    inst.read = mock.MagicMock(return_value=\"> \")\n\n    inst.sendcmd(\"foobar\")\n    inst.read.assert_called_with(2)\n    inst._file.sendcmd.assert_called_with(\"foobar\")\n\n\ndef test_instrument_sendcmd_noack_bad_prompt():\n    with pytest.raises(PromptError):\n        mock_filelike = mock.MagicMock()\n        mock_filelike.__class__ = AbstractCommunicator\n        inst = ik.Instrument(mock_filelike)\n\n        inst.prompt = \"> \"\n        inst.read = mock.MagicMock(return_value=\"* \")\n\n        inst.sendcmd(\"foobar\")\n\n\ndef test_instrument_sendcmd():\n    mock_filelike = mock.MagicMock()\n    mock_filelike.__class__ = AbstractCommunicator\n    inst = ik.Instrument(mock_filelike)\n\n    def new_ack(msg):\n        return msg\n\n    inst._ack_expected = new_ack\n    inst.prompt = \"> \"\n    inst.read = mock.MagicMock(side_effect=[\"foobar\", \"> \"])\n\n    inst.sendcmd(\"foobar\")\n    inst.read.assert_any_call()\n    inst.read.assert_any_call(2)\n    inst._file.sendcmd.assert_called_with(\"foobar\")\n    assert inst.read.call_count == 2\n\n\ndef test_instrument_query_noack_noprompt():\n    mock_filelike = mock.MagicMock()\n    mock_filelike.__class__ = AbstractCommunicator\n    inst = ik.Instrument(mock_filelike)\n    inst._file.query.return_value = \"datas\"\n\n    assert inst.query(\"foobar?\") == \"datas\"\n\n    inst._file.query.assert_called_with(\"foobar?\", -1)\n\n\ndef test_instrument_query_noprompt():\n    \"\"\"\n    Expected order of operations:\n\n    - IK sends command to instrument\n    - Instrument sends ACK, commonly an echo of the command\n    - ACK is verified with _ack_expected function\n    - If ACK is good, do another read which contains our return data\n    \"\"\"\n    mock_filelike = mock.MagicMock()\n    mock_filelike.__class__ = AbstractCommunicator\n    inst = ik.Instrument(mock_filelike)\n    inst.read = mock.MagicMock(side_effect=[\"foobar?\", \"datas\"])\n\n    def new_ack(msg):\n        return msg\n\n    inst._ack_expected = new_ack\n\n    assert inst.query(\"foobar?\") == \"datas\"\n    inst._file.query.assert_called_with(\"foobar?\", size=0)\n    inst.read.assert_called_with(-1)\n\n\ndef test_instrument_query_noprompt_multiple_ack():\n    \"\"\"\n    Expected order of operations:\n\n    - IK sends command to instrument\n    - Instrument sends ACK, commonly an echo of the command\n    - ACK is verified with _ack_expected function\n    - Loop through each ACK that is expected\n    - If ACK is good, do another read which contains our return data\n    \"\"\"\n    mock_filelike = mock.MagicMock()\n    mock_filelike.__class__ = AbstractCommunicator\n    inst = ik.Instrument(mock_filelike)\n    inst.read = mock.MagicMock(side_effect=[\"foobar?\", \"second ack\", \"datas\"])\n\n    def new_ack(msg):\n        return [msg, \"second ack\"]\n\n    inst._ack_expected = new_ack\n\n    assert inst.query(\"foobar?\") == \"datas\"\n    inst._file.query.assert_called_with(\"foobar?\", size=0)\n    inst.read.assert_called_with(-1)\n\n\ndef test_instrument_query_bad_ack():\n    with pytest.raises(AcknowledgementError):\n        mock_filelike = mock.MagicMock()\n        mock_filelike.__class__ = AbstractCommunicator\n        inst = ik.Instrument(mock_filelike)\n        inst.read = mock.MagicMock(return_value=\"derp\")\n\n        def new_ack(msg):\n            return msg\n\n        inst._ack_expected = new_ack\n\n        _ = inst.query(\"foobar?\")\n\n\ndef test_instrument_query_noack():\n    \"\"\"\n    Expected order of operations:\n\n    - IK sends command to instrument and gets responce containing our data\n    - Another read is done to capture the prompt characters sent by the\n        instrument. Read should be equal to the length of the expected prompt\n    - Exception is raised is prompt is not correct\n    \"\"\"\n    mock_filelike = mock.MagicMock()\n    mock_filelike.__class__ = AbstractCommunicator\n    inst = ik.Instrument(mock_filelike)\n    inst._file.query.return_value = \"datas\"\n\n    inst.prompt = \"> \"\n    inst.read = mock.MagicMock(return_value=\"> \")\n\n    assert inst.query(\"foobar?\") == \"datas\"\n    inst._file.query.assert_called_with(\"foobar?\", -1)\n    inst.read.assert_called_with(2)\n\n\ndef test_instrument_query_noack_bad_prompt():\n    with pytest.raises(PromptError):\n        mock_filelike = mock.MagicMock()\n        mock_filelike.__class__ = AbstractCommunicator\n        inst = ik.Instrument(mock_filelike)\n        inst._file.query.return_value = \"datas\"\n\n        inst.prompt = \"> \"\n        inst.read = mock.MagicMock(return_value=\"* \")\n\n        _ = inst.query(\"foobar?\")\n\n\ndef test_instrument_query():\n    \"\"\"\n    Expected order of operations:\n\n    - IK sends command to instrument\n    - Instrument sends ACK, commonly an echo of the command\n    - ACK is verified with _ack_expected function\n    - If ACK is good, do another read which contains our return data\n    - Another read is done to capture the prompt characters sent by the\n        instrument. Read should be equal to the length of the expected prompt\n    - Exception is raised is prompt is not correct\n    \"\"\"\n    mock_filelike = mock.MagicMock()\n    mock_filelike.__class__ = AbstractCommunicator\n    inst = ik.Instrument(mock_filelike)\n    inst._file.query.return_value = \"foobar?\"\n    inst.read = mock.MagicMock(side_effect=[\"foobar?\", \"datas\", \"> \"])\n\n    def new_ack(msg):\n        return msg\n\n    inst._ack_expected = new_ack\n    inst.prompt = \"> \"\n\n    assert inst.query(\"foobar?\") == \"datas\"\n    inst.read.assert_any_call(-1)\n    inst.read.assert_any_call(2)\n    inst._file.query.assert_called_with(\"foobar?\", size=0)\n    assert inst.read.call_count == 3\n\n\ndef test_instrument_read():\n    mock_filelike = mock.MagicMock()\n    mock_filelike.__class__ = AbstractCommunicator\n    inst = ik.Instrument(mock_filelike)\n    inst._file.read.return_value = \"foobar\"\n\n    assert inst.read() == \"foobar\"\n    inst._file.read.assert_called_with(-1, \"utf-8\")\n\n    inst._file = mock.MagicMock()\n    inst._file.read.return_value = \"foobar\"\n\n    assert inst.read(6) == \"foobar\"\n    inst._file.read.assert_called_with(6, \"utf-8\")\n\n\ndef test_instrument_write():\n    mock_filelike = mock.MagicMock()\n    mock_filelike.__class__ = AbstractCommunicator\n    inst = ik.Instrument(mock_filelike)\n\n    inst.write(\"foobar\")\n    inst._file.write.assert_called_with(\"foobar\")\n\n\n# PROPERTIES #\n\n\ndef test_instrument_timeout():\n    mock_filelike = mock.MagicMock()\n    mock_filelike.__class__ = AbstractCommunicator\n    inst = ik.Instrument(mock_filelike)\n    timeout = mock.PropertyMock(return_value=1)\n    type(inst._file).timeout = timeout\n\n    assert inst.timeout == 1\n    timeout.assert_called_with()\n\n    inst.timeout = 5\n    timeout.assert_called_with(5)\n\n\ndef test_instrument_address():\n    mock_filelike = mock.MagicMock()\n    mock_filelike.__class__ = AbstractCommunicator\n    inst = ik.Instrument(mock_filelike)\n    address = mock.PropertyMock(return_value=\"/dev/foobar\")\n    type(inst._file).address = address\n\n    assert inst.address == \"/dev/foobar\"\n    address.assert_called_with()\n\n    inst.address = \"COM1\"\n    address.assert_called_with(\"COM1\")\n\n\ndef test_instrument_terminator():\n    mock_filelike = mock.MagicMock()\n    mock_filelike.__class__ = AbstractCommunicator\n    inst = ik.Instrument(mock_filelike)\n    terminator = mock.PropertyMock(return_value=\"\\n\")\n    type(inst._file).terminator = terminator\n\n    assert inst.terminator == \"\\n\"\n    terminator.assert_called_with()\n\n    inst.terminator = \"*\"\n    terminator.assert_called_with(\"*\")\n\n\ndef test_instrument_prompt():\n    mock_filelike = mock.MagicMock()\n    mock_filelike.__class__ = AbstractCommunicator\n    inst = ik.Instrument(mock_filelike)\n\n    assert inst.prompt is None\n\n    inst.prompt = \"> \"\n    assert inst.prompt == \"> \"\n\n    inst.prompt = None\n    assert inst.prompt is None\n"
  },
  {
    "path": "tests/test_comet/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_comet/test_cito_plus_1310.py",
    "content": "#!/usr/bin/env python\n\"\"\"Test the Comet Cito Plus 1310 instrument.\"\"\"\n\nfrom hypothesis import given, strategies as st\nimport pytest\n\nimport instruments as ik\nfrom instruments.comet.cito_plus_1310 import _crc16 as crc16\nfrom instruments.units import ureg as u\nfrom tests import expected_protocol\n\n\ndef add_checksum(data: bytes) -> bytes:\n    \"\"\"Add a CRC-16 checksum to the data.\"\"\"\n    checksum = crc16(data)\n    return data + checksum.to_bytes(2, \"little\")\n\n\n# TEST CLASS PROPERTIES #\n\n\ndef test_name():\n    \"\"\"Get the instrument label as string.\"\"\"\n    label_exp = \"Comet Cito Plus 1310\"\n    lbl_bytes = label_exp.encode(\"utf_8\")\n\n    cmd = bytes([0x0A, 0x41, 0x00, 0x0A, 0x00, 0x01])\n    cmd = add_checksum(cmd)\n    answ = bytes([0x0A, 0x41, len(lbl_bytes)]) + lbl_bytes\n    answ = add_checksum(answ)\n\n    with expected_protocol(\n        ik.comet.CitoPlus1310,\n        [cmd],\n        [answ],\n        sep=\"\",\n    ) as cito:\n        assert cito.name == label_exp\n\n\ndef test_forward_power():\n    \"\"\"Read forward power from instrument.\"\"\"\n    cmd = bytes([0x0A, 0x41, 0x1F, 0x55, 0x00, 0x01])\n    cmd = add_checksum(cmd)\n    answ = bytes([0x0A, 0x41, 0x04, 0x00, 0x00, 0x03, 0xE8])  # 1000 W\n    answ = add_checksum(answ)\n    with expected_protocol(\n        ik.comet.CitoPlus1310,\n        [cmd],\n        [answ],\n        sep=\"\",\n    ) as cito:\n        assert cito.forward_power == 1000 * u.mW\n\n\ndef test_load_power():\n    \"\"\"Read forward power from instrument.\"\"\"\n    cmd = bytes([0x0A, 0x41, 0x1F, 0x57, 0x00, 0x01])\n    cmd = add_checksum(cmd)\n    answ = bytes([0x0A, 0x41, 0x04, 0x00, 0x00, 0x03, 0xE8])  # 1000 W\n    answ = add_checksum(answ)\n    with expected_protocol(\n        ik.comet.CitoPlus1310,\n        [cmd],\n        [answ],\n        sep=\"\",\n    ) as cito:\n        assert cito.load_power == 1000 * u.mW\n\n\ndef test_output_power():\n    \"\"\"Get/set output power.\"\"\"\n    cmd_set_1kW = bytes([0x0A, 0x42, 0x04, 0xB6, 0x00, 0x0F, 0x42, 0x40])\n    cmd_set_1kW = add_checksum(cmd_set_1kW)\n    cmd_query = bytes([0x0A, 0x41, 0x04, 0xB6, 0x00, 0x01])\n    cmd_query = add_checksum(cmd_query)\n    answ_1kW = bytes([0x0A, 0x41, 0x04, 0x00, 0x0F, 0x42, 0x40])\n    answ_1kW = add_checksum(answ_1kW)\n    with expected_protocol(\n        ik.comet.CitoPlus1310,\n        [\n            cmd_set_1kW,\n            cmd_query,\n        ],\n        [\n            cmd_set_1kW,\n            answ_1kW,\n        ],\n        sep=\"\",\n    ) as cito:\n        cito.output_power = 1 * u.kW\n        assert cito.output_power == 1 * u.kW\n\n\n@given(pow=st.floats(min_value=0, max_value=1.0, exclude_max=True))\ndef test_output_power_smaller_one(pow):\n    \"\"\"Set output power values smaller than 1 W are set to zero.\"\"\"\n    cmd_set_0W = bytes([0x0A, 0x42, 0x04, 0xB6, 0x00, 0x00, 0x00, 0x00])\n    cmd_set_0W = add_checksum(cmd_set_0W)\n    with expected_protocol(\n        ik.comet.CitoPlus1310,\n        [cmd_set_0W],\n        [cmd_set_0W],\n        sep=\"\",\n    ) as cito:\n        cito.output_power = pow * u.W\n\n\ndef test_reflected_power():\n    \"\"\"Read reflected power from instrument.\"\"\"\n    cmd = bytes([0x0A, 0x41, 0x1F, 0x56, 0x00, 0x01])\n    cmd = add_checksum(cmd)\n    answ = bytes([0x0A, 0x41, 0x04, 0x00, 0x00, 0x03, 0xE8])  # 1000 W\n    answ = add_checksum(answ)\n    with expected_protocol(\n        ik.comet.CitoPlus1310,\n        [cmd],\n        [answ],\n        sep=\"\",\n    ) as cito:\n        assert cito.reflected_power == 1000 * u.mW\n\n\ndef test_regulation_mode():\n    \"\"\"Set/get the regulation mode.\"\"\"\n    cmd_forward_power = bytes([0x0A, 0x42, 0x04, 0xB1, 0x00, 0x00, 0x00, 0x00])\n    cmd_forward_power = add_checksum(cmd_forward_power)\n    cmd_load_power = bytes([0x0A, 0x42, 0x04, 0xB1, 0x00, 0x00, 0x00, 0x01])\n    cmd_load_power = add_checksum(cmd_load_power)\n    cmd_read_mode = bytes([0x0A, 0x41, 0x04, 0xB1, 0x00, 0x01])\n    cmd_read_mode = add_checksum(cmd_read_mode)\n    answ_forward_power = bytes([0x0A, 0x41, 0x04, 0x00, 0x00, 0x00, 0x00])\n    answ_forward_power = add_checksum(answ_forward_power)\n    answ_load_power = bytes([0x0A, 0x41, 0x04, 0x00, 0x00, 0x00, 0x01])\n    answ_load_power = add_checksum(answ_load_power)\n\n    with expected_protocol(\n        ik.comet.CitoPlus1310,\n        [\n            cmd_forward_power,\n            cmd_read_mode,\n            cmd_load_power,\n            cmd_read_mode,\n        ],\n        [\n            cmd_forward_power,\n            answ_forward_power,\n            cmd_load_power,\n            answ_load_power,\n        ],\n        sep=\"\",\n    ) as cito:\n        cito.regulation_mode = cito.RegulationMode.ForwardPower\n        assert cito.regulation_mode == cito.RegulationMode.ForwardPower\n        cito.regulation_mode = cito.RegulationMode.LoadPower\n        assert cito.regulation_mode == cito.RegulationMode.LoadPower\n\n\ndef test_rf():\n    \"\"\"Set/get the RF state.\"\"\"\n    cmd_rf_on = bytes([0x0A, 0x42, 0x03, 0xE9, 0x00, 0x00, 0x00, 0x01])\n    cmd_rf_on = add_checksum(cmd_rf_on)\n    cmd_rf_off = bytes([0x0A, 0x42, 0x03, 0xE9, 0x00, 0x00, 0x00, 0x00])\n    cmd_rf_off = add_checksum(cmd_rf_off)\n    query_rf = bytes([0x0A, 0x41, 0x1F, 0x40, 0x00, 0x01])\n    query_rf = add_checksum(query_rf)\n    answ_rf_on = bytes([0x0A, 0x41, 0x04, 0x00, 0x00, 0x00, 0x00])\n    answ_rf_on = add_checksum(answ_rf_on)\n    answ_rf_off = bytes([0x0A, 0x41, 0x04, 0x00, 0x00, 0x00, 0x01])\n    answ_rf_off = add_checksum(answ_rf_off)\n    with expected_protocol(\n        ik.comet.CitoPlus1310,\n        [\n            cmd_rf_on,\n            query_rf,\n            cmd_rf_off,\n            query_rf,\n        ],\n        [\n            cmd_rf_on,\n            answ_rf_on,\n            cmd_rf_off,\n            answ_rf_off,\n        ],\n        sep=\"\",\n    ) as rf:\n        rf.rf = True\n        assert rf.rf\n        rf.rf = False\n        assert not rf.rf\n\n\ndef test_checksum_error_return_package():\n    \"\"\"Raise an OSError if the checksum of returned package is invalid.\"\"\"\n    query_rf = bytes([0x0A, 0x41, 0x1F, 0x40, 0x00, 0x01])\n    query_rf = add_checksum(query_rf)\n    answ_rf_on = bytes([0x0A, 0x41, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])\n\n    with expected_protocol(\n        ik.comet.CitoPlus1310,\n        [query_rf],\n        [answ_rf_on],\n        sep=\"\",\n    ) as rf:\n        with pytest.raises(OSError) as err:\n            _ = rf.rf\n        assert \"CRC-16 checksum of returned package does not match\" in err.value.args[0]\n\n\ndef test_unknown_parameter():\n    \"\"\"Raise an excpetion if illegal function code used.\"\"\"\n    cmd = bytes([0x0A, 0x41, 0x00, 0x00])\n    cmd = add_checksum(cmd)\n    answ = bytes([0x0A, 0xC1, 0x01])\n    answ = add_checksum(answ)\n    with expected_protocol(\n        ik.comet.CitoPlus1310,\n        [cmd],\n        [answ],\n        sep=\"\",\n    ) as cito:\n        with pytest.raises(OSError) as err:\n            cito.query(cmd)\n        assert \"Unknown parameter or illegal function code\" in err.value.args[0]\n\n\ndef test_write_answer_package_no_match():\n    \"\"\"Raise exception if answer package of a write command does not match.\"\"\"\n    cmd_rf_on = bytes([0x0A, 0x42, 0x03, 0xE9, 0x00, 0x00, 0x00, 0x01])\n    cmd_rf_on = add_checksum(cmd_rf_on)\n    wrong_return = bytes([0x0A, 0x42, 0x04, 0xE9, 0x00, 0x00, 0x00, 0x01])\n    wrong_return = add_checksum(wrong_return)\n\n    with expected_protocol(\n        ik.comet.CitoPlus1310,\n        [cmd_rf_on],\n        [wrong_return],\n        sep=\"\",\n    ) as rf:\n        with pytest.raises(OSError) as err:\n            rf.rf = True\n        assert \"Received package does not match sent package\" in err.value.args[0]\n\n\n### TEST CHECKSUM FUNCTION ###\n\n\n@pytest.mark.parametrize(\n    \"inp_out\", [[bytes([0x00]), 0x0000], [bytes([0x31, 0xAE]), 0x2C94]]\n)\ndef test_crc16(inp_out):\n    \"\"\"Test CRC16 calculation with some hand-calculated examples.\"\"\"\n    input, expected = inp_out\n    assert crc16(input) == expected\n"
  },
  {
    "path": "tests/test_comm/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_comm/test_file.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the file communication layer\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport pytest\n\nfrom instruments.abstract_instruments.comm import FileCommunicator\nfrom .. import mock\n\n# TEST CASES #################################################################\n\n# pylint: disable=protected-access,unused-argument\n\npatch_path = \"instruments.abstract_instruments.comm.file_communicator.usbtmc\"\n\n\ndef test_filecomm_init():\n    mock_file = mock.MagicMock()\n    comm = FileCommunicator(mock_file)\n    assert comm._filelike is mock_file\n\n\ndef test_filecomm_address_getter():\n    mock_file = mock.MagicMock()\n    comm = FileCommunicator(mock_file)\n\n    mock_name = mock.PropertyMock(return_value=\"/home/user/file\")\n    type(comm._filelike).name = mock_name\n\n    assert comm.address == \"/home/user/file\"\n    mock_name.assert_called_with()\n\n\ndef test_filecomm_address_getter_no_name():\n    mock_file = mock.MagicMock()\n    comm = FileCommunicator(mock_file)\n\n    del comm._filelike.name\n\n    assert comm.address is None\n\n\ndef test_filecomm_address_setter():\n    with pytest.raises(NotImplementedError):\n        comm = FileCommunicator(mock.MagicMock())\n        comm.address = \"abc123\"\n\n\ndef test_filecomm_terminator():\n    comm = FileCommunicator(mock.MagicMock())\n\n    assert comm.terminator == \"\\n\"\n\n    comm.terminator = \"*\"\n    assert comm._terminator == \"*\"\n\n    comm.terminator = b\"*\"\n    assert comm._terminator == \"*\"\n\n\ndef test_filecomm_timeout_getter():\n    with pytest.raises(NotImplementedError):\n        comm = FileCommunicator(mock.MagicMock())\n        _ = comm.timeout\n\n\ndef test_filecomm_timeout_setter():\n    with pytest.raises(NotImplementedError):\n        comm = FileCommunicator(mock.MagicMock())\n        comm.timeout = 1\n\n\ndef test_filecomm_close():\n    comm = FileCommunicator(mock.MagicMock())\n\n    comm.close()\n    comm._filelike.close.assert_called_with()\n\n\ndef test_filecomm_read_raw():\n    comm = FileCommunicator(mock.MagicMock())\n    comm._filelike.read = mock.MagicMock(side_effect=[b\"a\", b\"b\", b\"c\", b\"\\n\"])\n\n    assert comm.read_raw() == b\"abc\"\n    comm._filelike.read.assert_has_calls([mock.call(1)] * 4)\n    assert comm._filelike.read.call_count == 4\n\n    comm._filelike.read = mock.MagicMock()\n    comm.read_raw(10)\n    comm._filelike.read.assert_called_with(10)\n\n\ndef test_filecomm_write_raw():\n    comm = FileCommunicator(mock.MagicMock())\n\n    comm.write_raw(b\"mock\")\n    comm._filelike.write.assert_called_with(b\"mock\")\n\n\ndef test_filecomm_sendcmd():\n    comm = FileCommunicator(mock.MagicMock())\n\n    comm._sendcmd(\"mock\")\n    comm._filelike.write.assert_called_with(b\"mock\\n\")\n\n\ndef test_filecomm_query():\n    comm = FileCommunicator(mock.MagicMock())\n    comm._testing = True  # to disable the delay in the _query function\n    comm._filelike.read = mock.MagicMock(side_effect=[b\"a\", b\"b\", b\"c\", b\"\\n\"])\n\n    assert comm._query(\"mock\") == \"abc\"\n\n\ndef test_filecomm_seek():\n    comm = FileCommunicator(mock.MagicMock())\n    comm.seek(1)\n    comm._filelike.seek.assert_called_with(1)\n\n\ndef test_filecomm_tell():\n    comm = FileCommunicator(mock.MagicMock())\n    comm._filelike.tell.return_value = 5\n\n    assert comm.tell() == 5\n    comm._filelike.tell.assert_called_with()\n\n\ndef test_filecomm_flush_input():\n    comm = FileCommunicator(mock.MagicMock())\n    comm.flush_input()\n    comm._filelike.flush.assert_called_with()\n"
  },
  {
    "path": "tests/test_comm/test_gpibusb.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the GPIBUSB communication layer\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport pytest\nimport serial\nfrom instruments.units import ureg as u\n\nfrom instruments.abstract_instruments.comm import GPIBCommunicator, SerialCommunicator\nfrom tests import unit_eq\nfrom .. import mock\n\n# TEST CASES #################################################################\n\n# pylint: disable=protected-access,unused-argument\n\n\ndef test_gpibusbcomm_init():\n    serial_comm = SerialCommunicator(serial.Serial())\n    serial_comm._conn = mock.MagicMock()\n    serial_comm._query = mock.MagicMock(return_value=\"1\")\n    comm = GPIBCommunicator(serial_comm, 1)\n    assert isinstance(comm._file, SerialCommunicator)\n\n\ndef test_gpibusbcomm_init_correct_values_new_firmware():\n    mock_gpib = mock.MagicMock()\n    mock_gpib.query.return_value = \"5\"\n    comm = GPIBCommunicator(mock_gpib, 1)\n\n    assert comm._terminator == \"\\n\"\n    assert comm._version == 5\n    assert comm._eos == \"\\n\"\n    assert comm._eoi is True\n    unit_eq(comm._timeout, 1000 * u.millisecond)\n\n\ndef test_gpibusbcomm_init_correct_values_old_firmware():\n    # This test just has the differences between the new and old firmware\n    mock_gpib = mock.MagicMock()\n    mock_gpib.query.return_value = \"4\"\n    comm = GPIBCommunicator(mock_gpib, 1)\n\n    assert comm._eos == 10\n\n\ndef test_gpibusbcomm_address():\n    # Create our communicator\n    comm = GPIBCommunicator(mock.MagicMock(), 1)\n\n    port_name = mock.PropertyMock(return_value=\"/dev/address\")\n    type(comm._file).address = port_name\n\n    # Check that our address function is working\n    assert comm.address == (1, \"/dev/address\")\n    port_name.assert_called_with()\n\n    # Able to set GPIB address\n    comm.address = 5\n    assert comm._gpib_address == 5\n\n    # Able to set address with a list\n    comm.address = [6, \"/dev/foobar\"]\n    assert comm._gpib_address == 6\n    port_name.assert_called_with(\"/dev/foobar\")\n\n\ndef test_gpibusbcomm_address_out_of_range():\n    with pytest.raises(ValueError):\n        comm = GPIBCommunicator(mock.MagicMock(), 1)\n\n        comm.address = 31\n\n\ndef test_gpibusbcomm_address_wrong_type():\n    with pytest.raises(TypeError):\n        comm = GPIBCommunicator(mock.MagicMock(), 1)\n        comm.address = \"derp\"\n\n\ndef test_gpibusbcomm_eoi():\n    comm = GPIBCommunicator(mock.MagicMock(), 1)\n    comm._version = 5\n\n    comm._file.sendcmd = mock.MagicMock()\n    comm.eoi = True\n    assert comm.eoi is True\n    assert comm._eoi is True\n    comm._file.sendcmd.assert_called_with(\"++eoi 1\")\n\n    comm._file.sendcmd = mock.MagicMock()\n    comm.eoi = False\n    assert comm.eoi is False\n    assert comm._eoi is False\n    comm._file.sendcmd.assert_called_with(\"++eoi 0\")\n\n\ndef test_gpibusbcomm_eoi_old_firmware():\n    comm = GPIBCommunicator(mock.MagicMock(), 1)\n    comm._version = 4\n\n    comm._file.sendcmd = mock.MagicMock()\n    comm.eoi = True\n    assert comm.eoi is True\n    assert comm._eoi is True\n    comm._file.sendcmd.assert_called_with(\"+eoi:1\")\n\n    comm._file.sendcmd = mock.MagicMock()\n    comm.eoi = False\n    assert comm.eoi is False\n    assert comm._eoi is False\n    comm._file.sendcmd.assert_called_with(\"+eoi:0\")\n\n\ndef test_gpibusbcomm_eoi_bad_type():\n    with pytest.raises(TypeError):\n        comm = GPIBCommunicator(mock.MagicMock(), 1)\n        comm._version = 5\n        comm.eoi = \"abc\"\n\n\ndef test_gpibusbcomm_eos_rn():\n    comm = GPIBCommunicator(mock.MagicMock(), 1)\n    comm._version = 5\n\n    comm._file.sendcmd = mock.MagicMock()\n    comm.eos = \"\\r\\n\"\n    assert comm.eos == \"\\r\\n\"\n    assert comm._eos == \"\\r\\n\"\n    comm._file.sendcmd.assert_called_with(\"++eos 0\")\n\n\ndef test_gpibusbcomm_eos_r():\n    comm = GPIBCommunicator(mock.MagicMock(), 1)\n    comm._version = 5\n\n    comm._file.sendcmd = mock.MagicMock()\n    comm.eos = \"\\r\"\n    assert comm.eos == \"\\r\"\n    assert comm._eos == \"\\r\"\n    comm._file.sendcmd.assert_called_with(\"++eos 1\")\n\n\ndef test_gpibusbcomm_eos_n():\n    comm = GPIBCommunicator(mock.MagicMock(), 1)\n    comm._version = 5\n\n    comm._file.sendcmd = mock.MagicMock()\n    comm.eos = \"\\n\"\n    assert comm.eos == \"\\n\"\n    assert comm._eos == \"\\n\"\n    comm._file.sendcmd.assert_called_with(\"++eos 2\")\n\n\ndef test_gpibusbcomm_eos_invalid():\n    with pytest.raises(ValueError):\n        comm = GPIBCommunicator(mock.MagicMock(), 1)\n        comm._version = 5\n        comm.eos = \"*\"\n\n\ndef test_gpibusbcomm_eos_old_firmware():\n    comm = GPIBCommunicator(mock.MagicMock(), 1)\n    comm._version = 4\n\n    comm._file.sendcmd = mock.MagicMock()\n    comm.eos = \"\\n\"\n    assert comm._eos == 10\n    comm._file.sendcmd.assert_called_with(\"+eos:10\")\n\n\ndef test_gpibusbcomm_terminator():\n    comm = GPIBCommunicator(mock.MagicMock(), 1)\n    comm._version = 5\n\n    # Default terminator should be eoi\n    assert comm.terminator == \"eoi\"\n    assert comm._eoi is True\n\n    comm.terminator = \"\\n\"\n    assert comm.terminator == \"\\n\"\n    assert comm._eoi is False\n\n    comm.terminator = \"eoi\"\n    assert comm.terminator == \"eoi\"\n    assert comm._eoi is True\n\n\ndef test_gpibusbcomm_timeout():\n    comm = GPIBCommunicator(mock.MagicMock(), 1)\n    comm._version = 5\n\n    unit_eq(comm.timeout, 1000 * u.millisecond)\n\n    comm.timeout = 5000 * u.millisecond\n    comm._file.sendcmd.assert_called_with(\"++read_tmo_ms 5000\")\n\n\ndef test_gpibusbcomm_close():\n    comm = GPIBCommunicator(mock.MagicMock(), 1)\n    comm._version = 5\n\n    comm.close()\n    comm._file.close.assert_called_with()\n\n\ndef test_gpibusbcomm_read_raw():\n    comm = GPIBCommunicator(mock.MagicMock(), 1)\n    comm._version = 5\n    comm._file.read_raw = mock.MagicMock(return_value=b\"abc\")\n\n    assert comm.read_raw(3) == b\"abc\"\n    comm._file.read_raw.assert_called_with(3)\n\n\ndef test_gpibusbcomm_write_raw():\n    comm = GPIBCommunicator(mock.MagicMock(), 1)\n    comm._version = 5\n\n    comm.write_raw(b\"mock\")\n    comm._file.write_raw.assert_called_with(b\"mock\")\n\n\ndef test_gpibusbcomm_sendcmd():\n    comm = GPIBCommunicator(mock.MagicMock(), 1)\n    comm._version = 5\n\n    comm._sendcmd(\"mock\")\n    comm._file.sendcmd.assert_has_calls(\n        [\n            mock.call(\"+a:1\"),\n            mock.call(\"++eoi 1\"),\n            mock.call(\"++read_tmo_ms 1000\"),\n            mock.call(\"++eos 2\"),\n            mock.call(\"mock\"),\n        ]\n    )\n\n\ndef test_gpibusbcomm_sendcmd_empty_string():\n    comm = GPIBCommunicator(mock.MagicMock(), 1)\n    comm._version = 5\n    comm._file.sendcmd = mock.MagicMock()  # Refreshed because init makes calls\n\n    comm._sendcmd(\"\")\n    comm._file.sendcmd.assert_not_called()\n\n\ndef test_gpibusbcomm_query():\n    comm = GPIBCommunicator(mock.MagicMock(), 1)\n    comm._version = 5\n\n    comm._file.read = mock.MagicMock(return_value=\"answer\")\n    comm.sendcmd = mock.MagicMock()\n\n    assert comm._query(\"mock?\") == \"answer\"\n    comm.sendcmd.assert_called_with(\"mock?\")\n    comm._file.read.assert_called_with(-1)\n\n    comm._query(\"mock?\", size=10)\n    comm._file.read.assert_called_with(10)\n\n\ndef test_gpibusbcomm_query_no_question_mark():\n    comm = GPIBCommunicator(mock.MagicMock(), 1)\n    comm._version = 5\n    comm._file.sendcmd = mock.MagicMock()  # Refreshed because init makes calls\n\n    comm._file.read = mock.MagicMock(return_value=\"answer\")\n    comm.sendcmd = mock.MagicMock()\n\n    assert comm._query(\"mock\") == \"answer\"\n    comm.sendcmd.assert_called_with(\"mock\")\n    comm._file.read.assert_called_with(-1)\n    comm._file.sendcmd.assert_has_calls([mock.call(\"+read\")])\n\n\ndef test_serialcomm_flush_input():\n    comm = GPIBCommunicator(mock.MagicMock(), 1)\n    comm._version = 5\n\n    comm.flush_input()\n    comm._file.flush_input.assert_called_with()\n"
  },
  {
    "path": "tests/test_comm/test_loopback.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the loopback communication layer\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport pytest\n\nfrom instruments.abstract_instruments.comm import LoopbackCommunicator\nfrom .. import mock\n\n# TEST CASES #################################################################\n\n# pylint: disable=protected-access,unused-argument\n\n\ndef test_loopbackcomm_init():\n    var1 = \"abc\"\n    var2 = \"123\"\n    comm = LoopbackCommunicator(stdin=var1, stdout=var2)\n    assert comm._stdin is var1\n    assert comm._stdout is var2\n\n\n@mock.patch(\"instruments.abstract_instruments.comm.loopback_communicator.sys\")\ndef test_loopbackcomm_address(mock_sys):\n    mock_name = mock.PropertyMock(return_value=\"address\")\n    type(mock_sys.stdin).name = mock_name\n    comm = LoopbackCommunicator()\n    comm._conn = mock.MagicMock()\n\n    # Check that our address function is working\n    assert comm.address == \"address\"\n    mock_name.assert_called_with()\n\n\ndef test_loopbackcomm_terminator():\n    comm = LoopbackCommunicator()\n\n    # Default terminator should be \\n\n    assert comm.terminator == \"\\n\"\n\n    comm.terminator = b\"*\"\n    assert comm.terminator == \"*\"\n    assert comm._terminator == \"*\"\n\n    comm.terminator = \"\\r\"\n    assert comm.terminator == \"\\r\"\n    assert comm._terminator == \"\\r\"\n\n    comm.terminator = \"\\r\\n\"\n    assert comm.terminator == \"\\r\\n\"\n    assert comm._terminator == \"\\r\\n\"\n\n\ndef test_loopbackcomm_timeout():\n    comm = LoopbackCommunicator()\n\n    assert comm.timeout == 0\n\n    comm.timeout = 10\n    assert comm.timeout == 0  # setting should be ignored\n\n\ndef test_loopbackcomm_close():\n    mock_stdin = mock.MagicMock()\n    comm = LoopbackCommunicator(stdin=mock_stdin)\n\n    comm.close()\n    mock_stdin.close.assert_called_with()\n\n\ndef test_loopbackcomm_read_raw():\n    mock_stdin = mock.MagicMock()\n    mock_stdin.read.side_effect = [b\"a\", b\"b\", b\"c\", b\"\\n\"]\n    comm = LoopbackCommunicator(stdin=mock_stdin)\n\n    assert comm.read_raw() == b\"abc\"\n    mock_stdin.read.assert_has_calls([mock.call(1)] * 4)\n    assert mock_stdin.read.call_count == 4\n\n    mock_stdin.read = mock.MagicMock()\n    comm.read_raw(10)\n    mock_stdin.read.assert_called_with(10)\n\n\ndef test_loopbackcomm_read_raw_2char_terminator():\n    mock_stdin = mock.MagicMock()\n    mock_stdin.read.side_effect = [b\"a\", b\"b\", b\"c\", b\"\\r\", b\"\\n\"]\n    comm = LoopbackCommunicator(stdin=mock_stdin)\n    comm._terminator = \"\\r\\n\"\n\n    assert comm.read_raw() == b\"abc\"\n    mock_stdin.read.assert_has_calls([mock.call(1)] * 5)\n    assert mock_stdin.read.call_count == 5\n\n\ndef test_loopbackcomm_read_raw_terminator_is_empty_string():\n    mock_stdin = mock.MagicMock()\n    mock_stdin.read.side_effect = [b\"abc\"]\n    comm = LoopbackCommunicator(stdin=mock_stdin)\n    comm._terminator = \"\"\n\n    assert comm.read_raw() == b\"abc\"\n    mock_stdin.read.assert_has_calls([mock.call(-1)])\n    assert mock_stdin.read.call_count == 1\n\n\ndef test_loopbackcomm_read_raw_size_invalid():\n    with pytest.raises(ValueError):\n        mock_stdin = mock.MagicMock()\n        mock_stdin.read.side_effect = [b\"abc\"]\n        comm = LoopbackCommunicator(stdin=mock_stdin)\n        comm.read_raw(size=-2)\n\n\n@mock.patch(\"builtins.input\")\ndef test_loopbackcomm_read_raw_stdin(mock_input):\n    mock_input.return_value = \"Returned string.\"\n    comm = LoopbackCommunicator()\n    assert comm.read_raw() == b\"Returned string.\"\n\n\ndef test_loopbackcomm_write_raw():\n    mock_stdout = mock.MagicMock()\n    comm = LoopbackCommunicator(stdout=mock_stdout)\n    comm.write_raw(b\"mock\")\n    mock_stdout.write.assert_called_with(b\"mock\")\n\n\ndef test_loopbackcomm_sendcmd():\n    mock_stdout = mock.MagicMock()\n    comm = LoopbackCommunicator(stdout=mock_stdout)\n\n    comm._sendcmd(\"mock\")\n    mock_stdout.write.assert_called_with(b\"mock\\n\")\n\n    comm.write = mock.MagicMock()\n    comm._sendcmd(\"mock\")\n    comm.write.assert_called_with(\"mock\\n\")\n\n\ndef test_loopbackcomm_query():\n    comm = LoopbackCommunicator()\n    comm.read = mock.MagicMock(return_value=\"answer\")\n    comm.sendcmd = mock.MagicMock()\n\n    assert comm._query(\"mock\") == \"answer\"\n    comm.sendcmd.assert_called_with(\"mock\")\n    comm.read.assert_called_with(-1)\n\n    comm._query(\"mock\", size=10)\n    comm.read.assert_called_with(10)\n\n\ndef test_loopbackcomm_seek():\n    with pytest.raises(NotImplementedError):\n        comm = LoopbackCommunicator()\n        comm.seek(1)\n\n\ndef test_loopbackcomm_tell():\n    with pytest.raises(NotImplementedError):\n        comm = LoopbackCommunicator()\n        comm.tell()\n\n\ndef test_loopbackcomm_flush_input():\n    comm = LoopbackCommunicator()\n    comm.flush_input()\n"
  },
  {
    "path": "tests/test_comm/test_serial.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the serial communication layer\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport pytest\nimport serial\nfrom instruments.units import ureg as u\n\nfrom instruments.abstract_instruments.comm import SerialCommunicator\nfrom tests import unit_eq\nfrom .. import mock\n\n# TEST CASES #################################################################\n\n# pylint: disable=protected-access,unused-argument\n\n\ndef test_serialcomm_init():\n    comm = SerialCommunicator(serial.Serial())\n    assert isinstance(comm._conn, serial.Serial) is True\n\n\ndef test_serialcomm_init_wrong_filelike():\n    with pytest.raises(TypeError):\n        _ = SerialCommunicator(\"derp\")\n\n\ndef test_serialcomm_address():\n    # Create our communicator\n    comm = SerialCommunicator(serial.Serial())\n    comm._conn = mock.MagicMock()\n\n    port_name = mock.PropertyMock(return_value=\"/dev/address\")\n    type(comm._conn).port = port_name\n\n    # Check that our address function is working\n    assert comm.address == \"/dev/address\"\n    port_name.assert_called_with()\n\n\ndef test_serialcomm_terminator():\n    comm = SerialCommunicator(serial.Serial())\n\n    # Default terminator should be \\n\n    assert comm.terminator == \"\\n\"\n\n    comm.terminator = \"*\"\n    assert comm.terminator == \"*\"\n\n    comm.terminator = \"\\r\\n\"\n    assert comm.terminator == \"\\r\\n\"\n    assert comm._terminator == \"\\r\\n\"\n\n\ndef test_serialcomm_timeout():\n    comm = SerialCommunicator(serial.Serial())\n    comm._conn = mock.MagicMock()\n\n    timeout = mock.PropertyMock(return_value=30)\n    type(comm._conn).timeout = timeout\n\n    unit_eq(comm.timeout, 30 * u.second)\n    timeout.assert_called_with()\n\n    comm.timeout = 10\n    timeout.assert_called_with(10)\n\n    comm.timeout = 1000 * u.millisecond\n    timeout.assert_called_with(1)\n\n\ndef test_serialcomm_parity():\n    comm = SerialCommunicator(serial.Serial())\n\n    # Default parity should be NONE\n    assert comm.parity == serial.PARITY_NONE\n\n    comm.parity = serial.PARITY_EVEN\n    assert comm.parity == serial.PARITY_EVEN\n\n\ndef test_serialcomm_close():\n    comm = SerialCommunicator(serial.Serial())\n    comm._conn = mock.MagicMock()\n\n    comm.close()\n    comm._conn.shutdown.assert_called_with()\n    comm._conn.close.assert_called_with()\n\n\ndef test_serialcomm_read_raw():\n    comm = SerialCommunicator(serial.Serial())\n    comm._conn = mock.MagicMock()\n    comm._conn.read = mock.MagicMock(side_effect=[b\"a\", b\"b\", b\"c\", b\"\\n\"])\n\n    assert comm.read_raw() == b\"abc\"\n    comm._conn.read.assert_has_calls([mock.call(1)] * 4)\n    assert comm._conn.read.call_count == 4\n\n    comm._conn.read = mock.MagicMock()\n    comm.read_raw(10)\n    comm._conn.read.assert_called_with(10)\n\n\ndef test_loopbackcomm_read_raw_2char_terminator():\n    comm = SerialCommunicator(serial.Serial())\n    comm._conn = mock.MagicMock()\n    comm._conn.read = mock.MagicMock(side_effect=[b\"a\", b\"b\", b\"c\", b\"\\r\", b\"\\n\"])\n    comm._terminator = \"\\r\\n\"\n\n    assert comm.read_raw() == b\"abc\"\n    comm._conn.read.assert_has_calls([mock.call(1)] * 5)\n    assert comm._conn.read.call_count == 5\n\n\ndef test_serialcomm_read_raw_timeout():\n    with pytest.raises(IOError):\n        comm = SerialCommunicator(serial.Serial())\n        comm._conn = mock.MagicMock()\n        comm._conn.read = mock.MagicMock(side_effect=[b\"a\", b\"b\", b\"\"])\n\n        _ = comm.read_raw(-1)\n\n\ndef test_serialcomm_write_raw():\n    comm = SerialCommunicator(serial.Serial())\n    comm._conn = mock.MagicMock()\n\n    comm.write_raw(b\"mock\")\n    comm._conn.write.assert_called_with(b\"mock\")\n\n\ndef test_serialcomm_sendcmd():\n    comm = SerialCommunicator(serial.Serial())\n    comm._conn = mock.MagicMock()\n\n    comm._sendcmd(\"mock\")\n    comm._conn.write.assert_called_with(b\"mock\\n\")\n\n\ndef test_serialcomm_query():\n    comm = SerialCommunicator(serial.Serial())\n    comm._conn = mock.MagicMock()\n    comm.read = mock.MagicMock(return_value=\"answer\")\n    comm.sendcmd = mock.MagicMock()\n\n    assert comm._query(\"mock\") == \"answer\"\n    comm.sendcmd.assert_called_with(\"mock\")\n    comm.read.assert_called_with(-1)\n\n    comm._query(\"mock\", size=10)\n    comm.read.assert_called_with(10)\n\n\ndef test_serialcomm_seek():\n    with pytest.raises(NotImplementedError):\n        comm = SerialCommunicator(serial.Serial())\n        comm.seek(1)\n\n\ndef test_serialcomm_tell():\n    with pytest.raises(NotImplementedError):\n        comm = SerialCommunicator(serial.Serial())\n        comm.tell()\n\n\ndef test_serialcomm_flush_input():\n    comm = SerialCommunicator(serial.Serial())\n    comm._conn = mock.MagicMock()\n    comm.flush_input()\n\n    comm._conn.flushInput.assert_called_with()\n"
  },
  {
    "path": "tests/test_comm/test_socket.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the socket communication layer\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport socket\n\nimport pytest\nfrom instruments.units import ureg as u\n\nfrom instruments.abstract_instruments.comm import SocketCommunicator\nfrom tests import unit_eq\nfrom .. import mock\n\n# TEST CASES #################################################################\n\n# pylint: disable=protected-access,unused-argument\n\n\ndef test_socketcomm_init():\n    socket_object = socket.socket()\n    comm = SocketCommunicator(socket_object)\n    assert isinstance(comm._conn, socket.socket) is True\n    assert comm._conn == socket_object\n\n\ndef test_socketcomm_init_wrong_filelike():\n    with pytest.raises(TypeError):\n        _ = SocketCommunicator(\"derp\")\n\n\ndef test_socketcomm_address():\n    comm = SocketCommunicator(socket.socket())\n    comm._conn = mock.MagicMock()\n    comm._conn.getpeername.return_value = \"127.0.0.1\", 1234\n\n    assert comm.address == (\"127.0.0.1\", 1234)\n    comm._conn.getpeername.assert_called_with()\n\n\ndef test_socketcomm_address_setting():\n    with pytest.raises(NotImplementedError):\n        comm = SocketCommunicator(socket.socket())\n        comm.address = \"foobar\"\n\n\ndef test_socketcomm_terminator():\n    comm = SocketCommunicator(socket.socket())\n\n    # Default terminator should be \\n\n    assert comm.terminator == \"\\n\"\n\n    comm.terminator = b\"*\"\n    assert comm.terminator == \"*\"\n    assert comm._terminator == \"*\"\n\n    comm.terminator = \"\\r\"\n    assert comm.terminator == \"\\r\"\n    assert comm._terminator == \"\\r\"\n\n    comm.terminator = \"\\r\\n\"\n    assert comm.terminator == \"\\r\\n\"\n    assert comm._terminator == \"\\r\\n\"\n\n\ndef test_socketcomm_timeout():\n    comm = SocketCommunicator(socket.socket())\n    comm._conn = mock.MagicMock()\n    comm._conn.gettimeout.return_value = 1.234\n\n    unit_eq(comm.timeout, 1.234 * u.second)\n    comm._conn.gettimeout.assert_called_with()\n\n    comm.timeout = 10\n    comm._conn.settimeout.assert_called_with(10)\n\n    comm.timeout = 1000 * u.millisecond\n    comm._conn.settimeout.assert_called_with(1)\n\n\ndef test_socketcomm_close():\n    comm = SocketCommunicator(socket.socket())\n    comm._conn = mock.MagicMock()\n\n    comm.close()\n    comm._conn.shutdown.assert_called_with(socket.SHUT_RDWR)\n    comm._conn.close.assert_called_with()\n\n\ndef test_socketcomm_read_raw():\n    comm = SocketCommunicator(socket.socket())\n    comm._conn = mock.MagicMock()\n    comm._conn.recv = mock.MagicMock(side_effect=[b\"a\", b\"b\", b\"c\", b\"\\n\"])\n\n    assert comm.read_raw() == b\"abc\"\n    comm._conn.recv.assert_has_calls([mock.call(1)] * 4)\n    assert comm._conn.recv.call_count == 4\n\n    comm._conn.recv = mock.MagicMock()\n    comm.read_raw(10)\n    comm._conn.recv.assert_called_with(10)\n\n\ndef test_loopbackcomm_read_raw_2char_terminator():\n    comm = SocketCommunicator(socket.socket())\n    comm._conn = mock.MagicMock()\n    comm._conn.recv = mock.MagicMock(side_effect=[b\"a\", b\"b\", b\"c\", b\"\\r\", b\"\\n\"])\n    comm._terminator = \"\\r\\n\"\n\n    assert comm.read_raw() == b\"abc\"\n    comm._conn.recv.assert_has_calls([mock.call(1)] * 5)\n    assert comm._conn.recv.call_count == 5\n\n\ndef test_serialcomm_read_raw_timeout():\n    with pytest.raises(IOError):\n        comm = SocketCommunicator(socket.socket())\n        comm._conn = mock.MagicMock()\n        comm._conn.recv = mock.MagicMock(side_effect=[b\"a\", b\"b\", b\"\"])\n\n        _ = comm.read_raw(-1)\n\n\ndef test_socketcomm_write_raw():\n    comm = SocketCommunicator(socket.socket())\n    comm._conn = mock.MagicMock()\n\n    comm.write_raw(b\"mock\")\n    comm._conn.sendall.assert_called_with(b\"mock\")\n\n\ndef test_socketcomm_sendcmd():\n    comm = SocketCommunicator(socket.socket())\n    comm._conn = mock.MagicMock()\n\n    comm._sendcmd(\"mock\")\n    comm._conn.sendall.assert_called_with(b\"mock\\n\")\n\n\ndef test_socketcomm_query():\n    comm = SocketCommunicator(socket.socket())\n    comm._conn = mock.MagicMock()\n    comm.read = mock.MagicMock(return_value=\"answer\")\n    comm.sendcmd = mock.MagicMock()\n\n    assert comm._query(\"mock\") == \"answer\"\n    comm.sendcmd.assert_called_with(\"mock\")\n    comm.read.assert_called_with(-1)\n\n    comm._query(\"mock\", size=10)\n    comm.read.assert_called_with(10)\n\n\ndef test_socketcomm_seek():\n    with pytest.raises(NotImplementedError):\n        comm = SocketCommunicator(socket.socket())\n        comm.seek(1)\n\n\ndef test_socketcomm_tell():\n    with pytest.raises(NotImplementedError):\n        comm = SocketCommunicator(socket.socket())\n        comm.tell()\n\n\ndef test_socketcomm_flush_input():\n    comm = SocketCommunicator(socket.socket())\n    comm._conn = mock.MagicMock()\n    comm.read = mock.MagicMock()\n    comm.flush_input()\n\n    comm.read.assert_called_with(-1)\n"
  },
  {
    "path": "tests/test_comm/test_usb_communicator.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the USB communicator.\n\"\"\"\n\n# IMPORTS ####################################################################\n\nimport math\n\nimport pytest\n\nimport usb.core\nimport usb.util\n\nfrom instruments.abstract_instruments.comm import USBCommunicator\nfrom instruments.units import ureg as u\nfrom .. import mock\n\n# TEST CASES #################################################################\n\n# pylint: disable=protected-access,unused-argument, redefined-outer-name\n\npatch_util = \"instruments.abstract_instruments.comm.usb_communicator.usb.util\"\n\n\n@pytest.fixture()\ndef dev():\n    \"\"\"Return a usb core device for initialization.\"\"\"\n    dev = mock.MagicMock()\n    dev.__class__ = usb.core.Device\n    return dev\n\n\n@pytest.fixture()\n@mock.patch(patch_util)\ndef inst(patch_util, dev):\n    \"\"\"Return a USB Communicator instrument.\"\"\"\n    return USBCommunicator(dev)\n\n\n@mock.patch(patch_util)\ndef test_init(usb_util, dev):\n    \"\"\"Initialize usb communicator.\"\"\"\n    # mock some behavior of the device required for initializing\n    dev.find.return_value.__class__ = usb.core.Device  # dev\n    # shortcuts for asserting calls\n    cfg = dev.get_active_configuration()\n    interface_number = cfg[(0, 0)].bInterfaceNumber\n    _ = dev.control.get_interface(dev, cfg[(0, 0)].bInterfaceNumber)\n\n    inst = USBCommunicator(dev)\n\n    # # assert calls according to manual\n    dev.set_configuration.assert_called()  # check default configuration\n    dev.get_active_configuration.assert_called()  # get active configuration\n    dev.control.get_interface.assert_called_with(dev, interface_number)\n    usb_util.find_descriptor.assert_has_calls(cfg)\n\n    assert isinstance(inst, USBCommunicator)\n\n    assert inst._dev == dev\n\n\ndef test_init_wrong_type():\n    \"\"\"Raise TypeError if initialized with wrong device.\"\"\"\n    with pytest.raises(TypeError) as err:\n        _ = USBCommunicator(42)\n    err_msg = err.value.args[0]\n    assert err_msg == \"USBCommunicator must wrap a usb.core.Device object.\"\n\n\ndef test_init_no_endpoints(dev):\n    \"\"\"Initialize usb communicator without endpoints.\"\"\"\n    # mock some behavior of the device required for initializing\n    dev.find.return_value.__class__ = usb.core.Device  # dev\n\n    with pytest.raises(IOError) as err:\n        _ = USBCommunicator(dev)\n    err_msg = err.value.args[0]\n    assert err_msg == \"USB endpoint not found.\"\n\n\ndef test_address(inst):\n    \"\"\"Address of device can not be read, nor written.\"\"\"\n    with pytest.raises(NotImplementedError):\n        _ = inst.address\n\n    with pytest.raises(ValueError) as err:\n        inst.address = 42\n\n    msg = err.value.args[0]\n    assert msg == \"Unable to change USB target address.\"\n\n\ndef test_terminator(inst):\n    \"\"\"Get / set terminator of instrument.\"\"\"\n    assert inst.terminator == \"\\n\"\n    inst.terminator = \"\\r\\n\"\n    assert inst.terminator == \"\\r\\n\"\n\n\ndef test_terminator_wrong_type(inst):\n    \"\"\"Raise TypeError when setting bad terminator.\"\"\"\n    with pytest.raises(TypeError) as err:\n        inst.terminator = 42\n    msg = err.value.args[0]\n    assert (\n        msg == \"Terminator for USBCommunicator must be specified as a \"\n        \"character string.\"\n    )\n\n\n@pytest.mark.parametrize(\"val\", [1, 1000, math.inf])\ndef test_timeout_get(val, inst):\n    \"\"\"Get a timeout from device (ms) and turn into s.\"\"\"\n    # mock timeout value of device\n    inst._dev.default_timeout = val\n\n    ret_val = inst.timeout\n    assert ret_val == u.Quantity(val, u.ms).to(u.s)\n\n\ndef test_timeout_set_unitless(inst):\n    \"\"\"Set a timeout value from device unitless (s).\"\"\"\n    val = 1000\n    inst.timeout = val\n    set_val = inst._dev.default_timeout\n    exp_val = 1000 * val\n    assert set_val == exp_val\n\n\ndef test_timeout_set_minutes(inst):\n    \"\"\"Set a timeout value from device in minutes.\"\"\"\n    val = 10\n    val_to_set = u.Quantity(val, u.min)\n    inst.timeout = val_to_set\n    set_val = inst._dev.default_timeout\n    exp_val = 1000 * 60 * val\n    assert set_val == exp_val\n\n\n@mock.patch(patch_util)\ndef test_close(usb_util, inst):\n    \"\"\"Close the connection, release instrument.\"\"\"\n    inst.close()\n    inst._dev.reset.assert_called()\n    usb_util.dispose_resources.assert_called_with(inst._dev)\n\n\ndef test_read_raw(inst):\n    \"\"\"Read raw information from instrument.\"\"\"\n    msg = b\"message\\n\"\n    msg_exp = b\"message\"\n\n    inst._ep_in.read.return_value = msg\n\n    assert inst.read_raw() == msg_exp\n\n\ndef test_read_raw_size(inst):\n    \"\"\"If size is -1, read 1000 bytes.\"\"\"\n    msg = b\"message\\n\"\n    inst._ep_in.read.return_value = msg\n\n    # set max package size\n    max_size = 256\n    inst._max_packet_size = max_size\n\n    _ = inst.read_raw(size=-1)\n    inst._ep_in.read.assert_called_with(max_size)\n\n\ndef test_read_raw_termination_char_not_found(inst):\n    \"\"\"Raise IOError if termination character not found.\"\"\"\n    msg = b\"message\"\n    inst._ep_in.read.return_value = msg\n    default_read_size = 1000\n\n    inst._max_packet_size = default_read_size\n\n    with pytest.raises(IOError) as err:\n        _ = inst.read_raw()\n    err_msg = err.value.args[0]\n    assert (\n        err_msg == f\"Did not find the terminator in the returned \"\n        f\"string. Total size of {default_read_size} might \"\n        f\"not be enough.\"\n    )\n\n\ndef test_write_raw(inst):\n    \"\"\"Write a message to the instrument.\"\"\"\n    msg = b\"message\\n\"\n    inst.write_raw(msg)\n    inst._ep_out.write.assert_called_with(msg)\n\n\ndef test_seek(inst):\n    \"\"\"Raise NotImplementedError if `seek` is called.\"\"\"\n    with pytest.raises(NotImplementedError):\n        inst.seek(42)\n\n\ndef test_tell(inst):\n    \"\"\"Raise NotImplementedError if `tell` is called.\"\"\"\n    with pytest.raises(NotImplementedError):\n        inst.tell()\n\n\ndef test_flush_input(inst):\n    \"\"\"Flush the input out by trying to read until no more available.\"\"\"\n    inst._ep_in.read.side_effect = [b\"message\\n\", usb.core.USBTimeoutError]\n    inst.flush_input()\n    inst._ep_in.read.assert_called()\n\n\ndef test_sendcmd(inst):\n    \"\"\"Send a command.\"\"\"\n    msg = \"msg\"\n    msg_to_send = f\"msg{inst._terminator}\"\n\n    inst.write = mock.MagicMock()\n\n    inst._sendcmd(msg)\n    inst.write.assert_called_with(msg_to_send)\n\n\ndef test_query(inst):\n    \"\"\"Query the instrument.\"\"\"\n    msg = \"msg\"\n    size = 1000\n\n    inst.sendcmd = mock.MagicMock()\n    inst.read = mock.MagicMock()\n\n    inst._query(msg, size=size)\n    inst.sendcmd.assert_called_with(msg)\n    inst.read.assert_called_with(size)\n"
  },
  {
    "path": "tests/test_comm/test_usbtmc.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the USBTMC communication layer\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport pytest\n\nfrom instruments.abstract_instruments.comm import USBTMCCommunicator\nfrom tests import unit_eq\nfrom instruments.units import ureg as u\nfrom .. import mock\n\n# TEST CASES #################################################################\n\n# pylint: disable=protected-access,unused-argument,no-member\n\npatch_path = \"instruments.abstract_instruments.comm.usbtmc_communicator.usbtmc\"\n\n\n@mock.patch(patch_path)\ndef test_usbtmccomm_init(mock_usbtmc):\n    _ = USBTMCCommunicator(\"foobar\", var1=123)\n    mock_usbtmc.Instrument.assert_called_with(\"foobar\", var1=123)\n\n\n@mock.patch(patch_path, new=None)\ndef test_usbtmccomm_init_missing_module():\n    with pytest.raises(ImportError):\n        _ = USBTMCCommunicator()\n\n\n@mock.patch(patch_path)\ndef test_usbtmccomm_terminator_getter(mock_usbtmc):\n    comm = USBTMCCommunicator()\n\n    term_char = mock.PropertyMock(return_value=10)\n    type(comm._filelike).term_char = term_char\n\n    assert comm.terminator == \"\\n\"\n    term_char.assert_called_with()\n\n\n@mock.patch(patch_path)\ndef test_usbtmccomm_terminator_setter(mock_usbtmc):\n    comm = USBTMCCommunicator()\n\n    term_char = mock.PropertyMock(return_value=\"\\n\")\n    type(comm._filelike).term_char = term_char\n\n    comm.terminator = \"*\"\n    assert comm._terminator == \"*\"\n    term_char.assert_called_with(42)\n\n    comm.terminator = b\"*\"\n    assert comm._terminator == \"*\"\n    term_char.assert_called_with(42)\n\n\n@mock.patch(patch_path)\ndef test_usbtmccomm_timeout(mock_usbtmc):\n    comm = USBTMCCommunicator()\n\n    timeout = mock.PropertyMock(return_value=1)\n    type(comm._filelike).timeout = timeout\n\n    unit_eq(comm.timeout, 1 * u.second)\n    timeout.assert_called_with()\n\n    comm.timeout = 10\n    timeout.assert_called_with(10.0)\n\n    comm.timeout = 1000 * u.millisecond\n    timeout.assert_called_with(1.0)\n\n\n@mock.patch(patch_path)\ndef test_usbtmccomm_close(mock_usbtmc):\n    comm = USBTMCCommunicator()\n\n    comm.close()\n    comm._filelike.close.assert_called_with()\n\n\n@mock.patch(patch_path)\ndef test_usbtmccomm_read_raw(mock_usbtmc):\n    comm = USBTMCCommunicator()\n    comm._filelike.read_raw = mock.MagicMock(return_value=b\"abc\")\n\n    assert comm.read_raw() == b\"abc\"\n    comm._filelike.read_raw.assert_called_with(num=-1)\n    assert comm._filelike.read_raw.call_count == 1\n\n    comm._filelike.read_raw = mock.MagicMock()\n    comm.read_raw(10)\n    comm._filelike.read_raw.assert_called_with(num=10)\n\n\n@mock.patch(patch_path)\ndef test_usbtmccomm_write_raw(mock_usbtmc):\n    comm = USBTMCCommunicator()\n\n    comm.write_raw(b\"mock\")\n    comm._filelike.write_raw.assert_called_with(b\"mock\")\n\n\n@mock.patch(patch_path)\ndef test_usbtmccomm_sendcmd(mock_usbtmc):\n    comm = USBTMCCommunicator()\n    comm.write = mock.MagicMock()\n\n    comm._sendcmd(\"mock\")\n    comm.write.assert_called_with(\"mock\")\n\n\n@mock.patch(patch_path)\ndef test_usbtmccomm_query(mock_usbtmc):\n    comm = USBTMCCommunicator()\n    comm._filelike.ask = mock.MagicMock(return_value=\"answer\")\n\n    assert comm._query(\"mock\") == \"answer\"\n    comm._filelike.ask.assert_called_with(\"mock\", num=-1, encoding=\"utf-8\")\n\n    comm._query(\"mock\", size=10)\n    comm._filelike.ask.assert_called_with(\"mock\", num=10, encoding=\"utf-8\")\n\n\n@mock.patch(patch_path)\ndef test_usbtmccomm_seek(mock_usbtmc):\n    with pytest.raises(NotImplementedError):\n        comm = USBTMCCommunicator()\n        comm.seek(1)\n\n\n@mock.patch(patch_path)\ndef test_usbtmccomm_tell(mock_usbtmc):\n    with pytest.raises(NotImplementedError):\n        comm = USBTMCCommunicator()\n        comm.tell()\n\n\n@mock.patch(patch_path)\ndef test_usbtmccomm_flush_input(mock_usbtmc):\n    comm = USBTMCCommunicator()\n    comm.flush_input()\n"
  },
  {
    "path": "tests/test_comm/test_visa_communicator.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the VISA communication layer\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport pytest\nimport pyvisa\n\nfrom instruments.units import ureg as u\nfrom instruments.abstract_instruments.comm import VisaCommunicator\n\n# TEST CASES #################################################################\n\n# pylint: disable=protected-access,redefined-outer-name\n\n\n# create a visa instrument\n@pytest.fixture()\ndef visa_inst():\n    \"\"\"Create a default visa-sim instrument and return it.\"\"\"\n    inst = pyvisa.ResourceManager(\"@sim\").open_resource(\"ASRL1::INSTR\")\n    return inst\n\n\ndef test_visacomm_init(visa_inst):\n    \"\"\"Initialize visa communicator.\"\"\"\n    comm = VisaCommunicator(visa_inst)\n    assert comm._conn == visa_inst\n    assert comm._terminator == \"\\n\"\n    assert comm._buf == bytearray()\n\n\ndef test_visacomm_init_wrong_type():\n    \"\"\"Raise TypeError if not a VISA instrument.\"\"\"\n    with pytest.raises(TypeError) as err:\n        VisaCommunicator(42)\n    err_msg = err.value.args[0]\n    assert err_msg == \"VisaCommunicator must wrap a VISA Instrument.\"\n\n\ndef test_visacomm_address(visa_inst):\n    \"\"\"Get / Set instrument address.\"\"\"\n    comm = VisaCommunicator(visa_inst)\n    assert comm.address == visa_inst.resource_name\n    with pytest.raises(NotImplementedError) as err:\n        comm.address = \"new address\"\n    err_msg = err.value.args[0]\n    assert err_msg == (\"Changing addresses of a VISA Instrument is not supported.\")\n\n\ndef test_visacomm_read_termination_not_string(visa_inst):\n    \"\"\"Raise TypeError if read termination is set with non-string character.\"\"\"\n    comm = VisaCommunicator(visa_inst)\n    with pytest.raises(TypeError):\n        comm.read_termination = 42\n\n\ndef test_visacomm_terminator(visa_inst):\n    \"\"\"Get / Set terminator and ensure pyvisa takes the right communicator.\"\"\"\n    comm = VisaCommunicator(visa_inst)\n    comm.terminator = \"\\r\"\n    assert comm.terminator == \"\\r\"\n\n    assert comm.read_termination == \"\\r\"\n    assert comm.write_termination == \"\\r\"\n\n\ndef test_visacomm_terminator_not_string(visa_inst):\n    \"\"\"Raise TypeError if terminator is set with non-string character.\"\"\"\n    comm = VisaCommunicator(visa_inst)\n    with pytest.raises(TypeError) as err:\n        comm.terminator = 42\n    err_msg = err.value.args[0]\n    assert err_msg == (\"Terminator for VisaCommunicator must be specified as a string.\")\n\n\ndef test_visacomm_timeout(visa_inst):\n    \"\"\"Set / Get timeout of VISA communicator.\"\"\"\n    comm = VisaCommunicator(visa_inst)\n    comm.timeout = 3\n    assert comm.timeout == u.Quantity(3, u.s)\n    comm.timeout = u.Quantity(40000, u.ms)\n    assert comm.timeout == u.Quantity(40, u.s)\n\n\ndef test_visacomm_write_termination_not_string(visa_inst):\n    \"\"\"Raise TypeError if write termination is set with non-string character.\"\"\"\n    comm = VisaCommunicator(visa_inst)\n    with pytest.raises(TypeError):\n        comm.write_termination = 42\n\n\ndef test_visacomm_close(visa_inst, mocker):\n    \"\"\"Raise an IOError if comms cannot be closed.\"\"\"\n    io_error_mock = mocker.Mock()\n    io_error_mock.side_effect = IOError\n    mock_close = mocker.patch.object(visa_inst, \"close\", io_error_mock)\n    comm = VisaCommunicator(visa_inst)\n    comm.close()\n    mock_close.assert_called()  # but error will just pass!\n\n\ndef test_visacomm_read_raw(visa_inst, mocker):\n    \"\"\"Read raw data from instrument without size specification.\"\"\"\n    comm = VisaCommunicator(visa_inst)\n    mock_read_raw = mocker.patch.object(visa_inst, \"read_raw\", return_value=b\"asdf\")\n    comm.read_raw()\n    mock_read_raw.assert_called()\n    assert comm._buf == bytearray()\n\n\ndef test_visacomm_read_raw_size(visa_inst, mocker):\n    \"\"\"Read raw data from instrument with size specification.\"\"\"\n    comm = VisaCommunicator(visa_inst)\n    size = 3\n    mock_read_bytes = mocker.patch.object(visa_inst, \"read_bytes\", return_value=b\"123\")\n    ret_val = comm.read_raw(size=size)\n    assert ret_val == b\"123\"\n    mock_read_bytes.assert_called()\n    assert comm._buf == bytearray()\n\n\ndef test_visacomm_read_raw_wrong_size(visa_inst):\n    \"\"\"Raise ValueError if size is invalid.\"\"\"\n    comm = VisaCommunicator(visa_inst)\n    with pytest.raises(ValueError) as err:\n        comm.read_raw(size=-3)\n    err_msg = err.value.args[0]\n    assert err_msg == (\n        \"Must read a positive value of characters, or -1 for all characters.\"\n    )\n\n\ndef test_visacomm_write_raw(visa_inst, mocker):\n    \"\"\"Write raw message to instrument.\"\"\"\n    mock_write = mocker.patch.object(visa_inst, \"write_raw\")\n    comm = VisaCommunicator(visa_inst)\n    msg = b\"12345\"\n    comm.write_raw(msg)\n    mock_write.assert_called_with(msg)\n\n\ndef test_visacomm_seek_not_implemented(visa_inst):\n    \"\"\"Raise NotImplementedError when calling seek.\"\"\"\n    comm = VisaCommunicator(visa_inst)\n    with pytest.raises(NotImplementedError):\n        comm.seek(42)\n\n\ndef test_visacomm_tell_not_implemented(visa_inst):\n    \"\"\"Raise NotImplementedError when calling tell.\"\"\"\n    comm = VisaCommunicator(visa_inst)\n    with pytest.raises(NotImplementedError):\n        comm.tell()\n\n\ndef test_visacomm_sendcmd(visa_inst, mocker):\n    \"\"\"Write to device.\"\"\"\n    mock_write = mocker.patch.object(VisaCommunicator, \"write\")\n    comm = VisaCommunicator(visa_inst)\n    msg = \"asdf\"\n    comm._sendcmd(msg)\n    mock_write.assert_called_with(msg)\n\n\ndef test_visacomm_query(visa_inst, mocker):\n    \"\"\"Query device.\"\"\"\n    mock_query = mocker.patch.object(visa_inst, \"query\")\n    comm = VisaCommunicator(visa_inst)\n    msg = \"asdf\"\n    comm._query(msg)\n    mock_query.assert_called_with(msg)\n"
  },
  {
    "path": "tests/test_comm/test_vxi11.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the VXI11 communication layer\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport pytest\n\nfrom instruments.abstract_instruments.comm import VXI11Communicator\nfrom .. import mock\n\n# TEST CASES #################################################################\n\n# pylint: disable=protected-access,unused-argument,no-member\n\nimport_base = \"instruments.abstract_instruments.comm.vxi11_communicator.vxi11\"\n\n\n@mock.patch(import_base)\ndef test_vxi11comm_init(mock_vxi11):\n    _ = VXI11Communicator(\"host\")\n    mock_vxi11.Instrument.assert_called_with(\"host\")\n\n\n@mock.patch(import_base, new=None)\ndef test_vxi11comm_init_no_vxi11():\n    with pytest.raises(ImportError):\n        _ = VXI11Communicator(\"host\")\n\n\n@mock.patch(import_base)\ndef test_vxi11comm_address(mock_vxi11):\n    # Create our communicator\n    comm = VXI11Communicator()\n\n    # Add in the host and name properties which are usually\n    # done in vxi11.Instrument.__init__\n    host = mock.PropertyMock(return_value=\"host\")\n    name = mock.PropertyMock(return_value=\"name\")\n    type(comm._inst).host = host\n    type(comm._inst).name = name\n\n    # Check that our address function is working\n    assert comm.address == [\"host\", \"name\"]\n    host.assert_called_with()\n    name.assert_called_with()\n\n\n@mock.patch(import_base)\ndef test_vxi11comm_terminator(mock_vxi11):\n    comm = VXI11Communicator()\n\n    term_char = mock.PropertyMock(return_value=\"\\n\")\n    type(comm._inst).term_char = term_char\n\n    assert comm.terminator == \"\\n\"\n    term_char.assert_called_with()\n\n    comm.terminator = \"*\"\n    term_char.assert_called_with(\"*\")\n\n\n@mock.patch(import_base)\ndef test_vxi11comm_timeout(mock_vxi11):\n    comm = VXI11Communicator()\n\n    timeout = mock.PropertyMock(return_value=30)\n    type(comm._inst).timeout = timeout\n\n    assert comm.timeout == 30\n    timeout.assert_called_with()\n\n    comm.timeout = 10\n    timeout.assert_called_with(10)\n\n\n@mock.patch(import_base)\ndef test_vxi11comm_close(mock_vxi11):\n    comm = VXI11Communicator()\n\n    comm.close()\n    comm._inst.close.assert_called_with()\n\n\n@mock.patch(import_base)\ndef test_vxi11comm_close_fail(mock_vxi11):\n    comm = VXI11Communicator()\n\n    comm._inst.close.return_value = Exception\n    comm.close()\n    comm._inst.close.assert_called_once_with()\n\n\n@mock.patch(import_base)\ndef test_vxi11comm_read(mock_vxi11):\n    comm = VXI11Communicator()\n    comm._inst.read_raw.return_value = b\"mock\"\n\n    assert comm.read_raw() == b\"mock\"\n    comm._inst.read_raw.assert_called_with(num=-1)\n\n    comm.read(10)\n    comm._inst.read_raw.assert_called_with(num=10)\n\n\n@mock.patch(import_base)\ndef test_vxi11comm_write(mock_vxi11):\n    comm = VXI11Communicator()\n\n    comm.write_raw(b\"mock\")\n    comm._inst.write_raw.assert_called_with(b\"mock\")\n\n\n@mock.patch(import_base)\ndef test_vxi11comm_sendcmd(mock_vxi11):\n    comm = VXI11Communicator()\n\n    comm._sendcmd(\"mock\")\n    comm._inst.write_raw.assert_called_with(b\"mock\")\n\n\n@mock.patch(import_base)\ndef test_vxi11comm_query(mock_vxi11):\n    comm = VXI11Communicator()\n    comm._inst.ask.return_value = \"answer\"\n\n    assert comm._query(\"mock\") == \"answer\"\n    comm._inst.ask.assert_called_with(\"mock\", num=-1)\n\n    comm._query(\"mock\", size=10)\n    comm._inst.ask.assert_called_with(\"mock\", num=10)\n\n\n@mock.patch(import_base)\ndef test_vxi11comm_seek(mock_vxi11):\n    with pytest.raises(NotImplementedError):\n        comm = VXI11Communicator()\n        comm.seek(1)\n\n\n@mock.patch(import_base)\ndef test_vxi11comm_tell(mock_vxi11):\n    with pytest.raises(NotImplementedError):\n        comm = VXI11Communicator()\n        comm.tell()\n\n\n@mock.patch(import_base)\ndef test_vxi11comm_flush(mock_vxi11):\n    with pytest.raises(NotImplementedError):\n        comm = VXI11Communicator()\n        comm.flush_input()\n"
  },
  {
    "path": "tests/test_config.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for util_fns.py\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nfrom io import StringIO\n\nimport pytest\n\nfrom instruments.units import ureg as u\n\nimport instruments as ik\nfrom instruments import Instrument\nfrom instruments.config import load_instruments, yaml\n\n# TEST CASES #################################################################\n\n# pylint: disable=protected-access,missing-docstring\n\n\ndef test_load_test_instrument():\n    config_data = StringIO(\"\"\"\ntest:\n    class: !!python/name:instruments.Instrument\n    uri: test://\n\"\"\")\n    insts = load_instruments(config_data)\n    assert isinstance(insts[\"test\"], Instrument)\n\n\ndef test_load_test_instrument_from_file(tmp_path):\n    \"\"\"Load an instrument from a `.yml` file with filename as string.\"\"\"\n    conf_file = tmp_path.joinpath(\"config.yml\")\n    conf_file.write_text(\"\"\"\ntest:\n    class: !!python/name:instruments.Instrument\n    uri: test://\n\"\"\")\n    insts = load_instruments(str(conf_file.absolute()))\n    assert isinstance(insts[\"test\"], Instrument)\n\n\ndef test_load_test_instrument_subtree():\n    config_data = StringIO(\"\"\"\ninstruments:\n    test:\n        class: !!python/name:instruments.Instrument\n        uri: test://\n\"\"\")\n    insts = load_instruments(config_data, conf_path=\"/instruments\")\n    assert isinstance(insts[\"test\"], Instrument)\n\n\ndef test_yaml_quantity_tag():\n    yaml_data = StringIO(\"\"\"\na:\n    b: !Q 37 tesla\n    c: !Q 41.2 inches\n    d: !Q 98\n\"\"\")\n    data = yaml.load(yaml_data)\n    assert data[\"a\"][\"b\"] == u.Quantity(37, \"tesla\")\n    assert data[\"a\"][\"c\"] == u.Quantity(41.2, \"inches\")\n    assert data[\"a\"][\"d\"] == 98\n\n\ndef test_load_test_instrument_setattr():\n    config_data = StringIO(\"\"\"\ntest:\n    class: !!python/name:instruments.Instrument\n    uri: test://\n    attrs:\n        foo: !Q 111 GHz\n\"\"\")\n    insts = load_instruments(config_data)\n    assert insts[\"test\"].foo == u.Quantity(111, \"GHz\")\n\n\ndef test_load_test_instrument_oserror(mocker):\n    \"\"\"Raise warning and continue in case loading test instrument fails with OSError.\"\"\"\n    config_data = StringIO(\"\"\"\ntest:\n    class: !!python/name:instruments.Instrument\n    uri: test://\n\"\"\")\n\n    mocker.patch.object(Instrument, \"open_from_uri\", side_effect=OSError)\n\n    with pytest.warns(RuntimeWarning):\n        _ = load_instruments(config_data)\n"
  },
  {
    "path": "tests/test_delta_elektronika/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_delta_elektronika/test_psc_eth.py",
    "content": "#!/usr/bin/env python\n\"\"\"Tests for the Delta Elektronika PSC-ETH interface.\"\"\"\n\nfrom hypothesis import given, strategies as st\nimport pytest\n\nimport instruments as ik\nfrom instruments.units import ureg as u\nfrom tests import expected_protocol, make_name_test, unit_eq\n\n# TEST CLASS PROPERTIES #\n\n\ntest_psc_eth_device_name = make_name_test(ik.delta_elektronika.PscEth)\n\n\ndef test_current_limit():\n    \"\"\"Get the current limit of the instrument.\"\"\"\n    with expected_protocol(\n        ik.delta_elektronika.PscEth,\n        [\"SYST:LIM:CUR?\", \"SYST:LIM:CUR?\"],\n        [\"0.0,OFF\", \"0.2,ON\"],\n        sep=\"\\n\",\n    ) as rf:\n        status, value = rf.current_limit\n        assert status == ik.delta_elektronika.PscEth.LimitStatus.OFF\n        unit_eq(value, 0.0 * u.A)\n\n        status, value = rf.current_limit\n        assert status == ik.delta_elektronika.PscEth.LimitStatus.ON\n        unit_eq(value, 0.2 * u.A)\n\n\ndef test_voltage_limit():\n    \"\"\"Get the voltage limit of the instrument.\"\"\"\n    with expected_protocol(\n        ik.delta_elektronika.PscEth,\n        [\"SYST:LIM:VOL?\", \"SYST:LIM:VOL?\"],\n        [\"0.0,OFF\", \"20.0,ON\"],\n        sep=\"\\n\",\n    ) as rf:\n        status, value = rf.voltage_limit\n        assert status == ik.delta_elektronika.PscEth.LimitStatus.OFF\n        unit_eq(value, 0.0 * u.V)\n\n        status, value = rf.voltage_limit\n        assert status == ik.delta_elektronika.PscEth.LimitStatus.ON\n        unit_eq(value, 20.0 * u.V)\n\n\ndef test_current():\n    \"\"\"Get/set the output current of the instrument.\"\"\"\n    with expected_protocol(\n        ik.delta_elektronika.PscEth,\n        [\"SOUR:CURR?\", f\"SOUR:CURR {0.1:.15f}\", \"SOUR:CURR?\"],\n        [\"0.0\", \"0.1\"],\n        sep=\"\\n\",\n    ) as rf:\n        unit_eq(rf.current, 0.0 * u.A)\n        rf.current = 0.1 * u.A\n        unit_eq(rf.current, 0.1 * u.A)\n\n\ndef test_current_max():\n    \"\"\"Get/set the maximum output current of the instrument.\"\"\"\n    with expected_protocol(\n        ik.delta_elektronika.PscEth,\n        [\"SOUR:CURR:MAX?\", f\"SOUR:CURR:MAX {0.2:.15f}\", \"SOUR:CURR:MAX?\"],\n        [\"0.1\", \"0.2\"],\n        sep=\"\\n\",\n    ) as rf:\n        unit_eq(rf.current_max, 0.1 * u.A)\n        rf.current_max = 0.2 * u.A\n        unit_eq(rf.current_max, 0.2 * u.A)\n\n\ndef test_current_measure():\n    \"\"\"Get the measured output current of the instrument.\"\"\"\n    with expected_protocol(\n        ik.delta_elektronika.PscEth,\n        [\"MEAS:CURR?\", \"MEAS:CURR?\"],\n        [\"0.0\", \"0.1\"],\n        sep=\"\\n\",\n    ) as rf:\n        unit_eq(rf.current_measure, 0.0 * u.A)\n        unit_eq(rf.current_measure, 0.1 * u.A)\n\n\ndef test_current_stepsize():\n    \"\"\"Get the current stepsize of the instrument.\"\"\"\n    with expected_protocol(\n        ik.delta_elektronika.PscEth,\n        [\"SOUR:CUR:STE?\", \"SOUR:CUR:STE?\"],\n        [\"0.001\", \"0.01\"],\n        sep=\"\\n\",\n    ) as rf:\n        unit_eq(rf.current_stepsize, 0.001 * u.A)\n        unit_eq(rf.current_stepsize, 0.01 * u.A)\n\n\ndef test_voltage():\n    \"\"\"Get/set the output voltage of the instrument.\"\"\"\n    with expected_protocol(\n        ik.delta_elektronika.PscEth,\n        [\"SOUR:VOL?\", f\"SOUR:VOL {10.0:.15f}\", \"SOUR:VOL?\"],\n        [\"0.0\", \"10.0\"],\n        sep=\"\\n\",\n    ) as rf:\n        unit_eq(rf.voltage, 0.0 * u.V)\n        rf.voltage = 10.0 * u.V\n        unit_eq(rf.voltage, 10.0 * u.V)\n\n\ndef test_voltage_max():\n    \"\"\"Get/set the maximum output voltage of the instrument.\"\"\"\n    with expected_protocol(\n        ik.delta_elektronika.PscEth,\n        [\"SOUR:VOLT:MAX?\", f\"SOUR:VOLT:MAX {20.0:.15f}\", \"SOUR:VOLT:MAX?\"],\n        [\"10.0\", \"20.0\"],\n        sep=\"\\n\",\n    ) as rf:\n        unit_eq(rf.voltage_max, 10.0 * u.V)\n        rf.voltage_max = 20.0 * u.V\n        unit_eq(rf.voltage_max, 20.0 * u.V)\n\n\ndef test_voltage_measure():\n    \"\"\"Get the measured output voltage of the instrument.\"\"\"\n    with expected_protocol(\n        ik.delta_elektronika.PscEth,\n        [\"MEAS:VOLT?\", \"MEAS:VOLT?\"],\n        [\"0.0\", \"10.0\"],\n        sep=\"\\n\",\n    ) as rf:\n        unit_eq(rf.voltage_measure, 0.0 * u.V)\n        unit_eq(rf.voltage_measure, 10.0 * u.V)\n\n\ndef test_voltage_stepsize():\n    \"\"\"Get the voltage stepsize of the instrument.\"\"\"\n    with expected_protocol(\n        ik.delta_elektronika.PscEth,\n        [\"SOUR:VOL:STE?\", \"SOUR:VOL:STE?\"],\n        [\"0.01\", \"0.1\"],\n        sep=\"\\n\",\n    ) as rf:\n        unit_eq(rf.voltage_stepsize, 0.01 * u.V)\n        unit_eq(rf.voltage_stepsize, 0.1 * u.V)\n\n\n# TEST CLASS METHODS #\n\n\ndef test_recall():\n    \"\"\"Recall a stored setting from non-volatile memory.\"\"\"\n    with expected_protocol(\n        ik.delta_elektronika.PscEth,\n        [\"*RCL\"],\n        [],\n        sep=\"\\n\",\n    ) as rf:\n        rf.recall()\n\n\ndef test_reset():\n    \"\"\"Reset the instrument to default settings.\"\"\"\n    with expected_protocol(\n        ik.delta_elektronika.PscEth,\n        [\"*RST\"],\n        [],\n        sep=\"\\n\",\n    ) as rf:\n        rf.reset()\n\n\ndef test_save():\n    \"\"\"Save the current settings to non-volatile memory.\"\"\"\n    with expected_protocol(\n        ik.delta_elektronika.PscEth,\n        [\"*SAV\"],\n        [],\n        sep=\"\\n\",\n    ) as rf:\n        rf.save()\n\n\ndef test_set_current_limit():\n    \"\"\"Set the current limit of the instrument.\"\"\"\n    with expected_protocol(\n        ik.delta_elektronika.PscEth,\n        [f\"SYST:LIM:CUR {0.0:.15f},OFF\", f\"SYST:LIM:CUR {0.2:.15f},ON\"],\n        [],\n        sep=\"\\n\",\n    ) as rf:\n        rf.set_current_limit(ik.delta_elektronika.PscEth.LimitStatus.OFF)\n        rf.set_current_limit(ik.delta_elektronika.PscEth.LimitStatus.ON, 0.2 * u.A)\n\n\ndef test_set_current_limit_invalid_type():\n    \"\"\"Setting current limit with invalid type raises TypeError.\"\"\"\n    with expected_protocol(\n        ik.delta_elektronika.PscEth,\n        [],\n        [],\n        sep=\"\\n\",\n    ) as rf:\n        with pytest.raises(TypeError):\n            rf.set_current_limit(\"ON\", 0.2 * u.A)\n\n\ndef test_set_voltage_limit():\n    \"\"\"Set the voltage limit of the instrument.\"\"\"\n    with expected_protocol(\n        ik.delta_elektronika.PscEth,\n        [f\"SYST:LIM:VOL {0.0:.15f},OFF\", f\"SYST:LIM:VOL {20.0:.15f},ON\"],\n        [],\n        sep=\"\\n\",\n    ) as rf:\n        rf.set_voltage_limit(ik.delta_elektronika.PscEth.LimitStatus.OFF)\n        rf.set_voltage_limit(ik.delta_elektronika.PscEth.LimitStatus.ON, 20.0 * u.V)\n\n\ndef test_set_voltage_limit_invalid_type():\n    \"\"\"Setting voltage limit with invalid type raises TypeError.\"\"\"\n    with expected_protocol(\n        ik.delta_elektronika.PscEth,\n        [],\n        [],\n        sep=\"\\n\",\n    ) as rf:\n        with pytest.raises(TypeError):\n            rf.set_voltage_limit(\"ON\", 20.0 * u.V)\n"
  },
  {
    "path": "tests/test_dressler/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_dressler/test_cesar_1312.py",
    "content": "#!/usr/bin/env python\n\"\"\"Tests for the Dressler Cesar 1312 RF generator.\"\"\"\n\nfrom hypothesis import given, strategies as st\nimport pytest\n\nimport instruments as ik\nfrom instruments.units import ureg as u\nfrom tests import expected_protocol\n\n# CONSTANTS #\n\n\nACK = bytes([0x06])\nNAK = bytes([0x15])\n\n\n# TEST CLASS PROPERTIES #\n\n\ndef test_address():\n    \"\"\"Set/get the address of the instrument.\"\"\"\n    with expected_protocol(\n        ik.dressler.Cesar1312,\n        [],\n        [],\n        sep=\"\",\n    ) as rf:\n        assert rf.address == 0x01\n        rf.address = 5\n        assert rf.address == 5\n\n        for addr in [-1, 32]:\n            with pytest.raises(ValueError):\n                rf.address = addr\n\n\ndef test_retries():\n    \"\"\"Set/get the number of retries.\"\"\"\n    with expected_protocol(\n        ik.dressler.Cesar1312,\n        [],\n        [],\n        sep=\"\",\n    ) as rf:\n        assert rf.retries == 3\n        rf.retries = 5\n        assert rf.retries == 5\n\n        with pytest.raises(ValueError):\n            rf.retries = -1\n\n\n# TEST INSTRUMENT PROPERTIES #\n\n\ndef test_control_mode():\n    \"\"\"Get/set the control model.\"\"\"\n    read_mode = bytes([0x08, 0x9B, 0x93])\n    read_mode_answ_front_panel = bytes([0x09, 0x9B, 0x06, 0x94])\n    set_mode_host = bytes([0x09, 0x0E, 0x02, 0x05])\n    set_mode_answ = bytes([0x09, 0x0E, 0x00, 0x07])\n    read_mode_answ_host = bytes([0x09, 0x9B, 0x02, 0x90])\n    with expected_protocol(\n        ik.dressler.Cesar1312,\n        [\n            read_mode,\n            ACK,\n            set_mode_host,\n            ACK,\n            read_mode,\n            ACK,\n        ],\n        [\n            ACK,\n            read_mode_answ_front_panel,\n            ACK,\n            set_mode_answ,\n            ACK,\n            read_mode_answ_host,\n        ],\n        sep=\"\",\n    ) as rf:\n        assert rf.control_mode == rf.ControlMode.FrontPanel\n        rf.control_mode = rf.ControlMode.Host\n        assert rf.control_mode == rf.ControlMode.Host\n\n\ndef test_name():\n    \"\"\"Get the supply type and size.\"\"\"\n    cmd_type = bytes([0x08, 0x80, 0x88])\n    ascii_type = b\"CESAR\"\n    ans_type = bytes([0x0D, 0x80]) + ascii_type\n    ans_type += checksum(ans_type)\n    cmd_size = bytes([0x08, 0x81, 0x89])\n    ascii_size = b\"_1312\"\n    ans_size = bytes([0x0D, 0x81]) + ascii_size\n    ans_size += checksum(ans_size)\n    with expected_protocol(\n        ik.dressler.Cesar1312,\n        [\n            cmd_type,\n            ACK,\n            cmd_size,\n            ACK,\n        ],\n        [\n            ACK,\n            ans_type,\n            ACK,\n            ans_size,\n        ],\n        sep=\"\",\n    ) as rf:\n        assert rf.name == \"CESAR_1312\"\n\n\ndef test_output_power():\n    \"\"\"Get/set the output power of the RF generator.\"\"\"\n    set_power_1kW = bytes([0x0A, 0x08, 0xE8, 0x03, 0xE9])\n    set_power_answ = bytes([0x09, 0x08, 0x00, 0x01])\n    read_power = bytes([0x08, 0xA4, 0xAC])\n    read_answ = bytes([0x0B, 0xA4, 0xE8, 0x03, 0x06, 0x42])\n    with expected_protocol(\n        ik.dressler.Cesar1312,\n        [\n            set_power_1kW,\n            ACK,\n            set_power_1kW,\n            ACK,\n            read_power,\n            ACK,\n        ],\n        [\n            ACK,\n            set_power_answ,\n            ACK,\n            set_power_answ,\n            ACK,\n            read_answ,\n        ],\n        sep=\"\",\n    ) as rf:\n        rf.output_power = 1000\n        rf.output_power = u.Quantity(1, u.kW)\n        assert rf.output_power == u.Quantity(1000, u.W)\n\n\ndef test_regulation_mode():\n    \"\"\"Get/set regulation mode.\"\"\"\n    read_mode = bytes([0x08, 0x9A, 0x92])\n    read_answ_load = bytes([0x09, 0x9A, 0x08, 0x9B])\n    set_mode_fwd = bytes([0x09, 0x03, 0x06, 0x0C])\n    set_mode_answ = bytes([0x09, 0x03, 0x00, 0x0A])\n    read_answ_fwd = bytes([0x09, 0x9A, 0x06, 0x95])\n    with expected_protocol(\n        ik.dressler.Cesar1312,\n        [\n            read_mode,\n            ACK,\n            set_mode_fwd,\n            ACK,\n            read_mode,\n            ACK,\n        ],\n        [\n            ACK,\n            read_answ_load,\n            ACK,\n            set_mode_answ,\n            ACK,\n            read_answ_fwd,\n        ],\n        sep=\"\",\n    ) as rf:\n        assert rf.regulation_mode == rf.RegulationMode.ExternalPower\n        rf.regulation_mode = rf.RegulationMode.ForwardPower\n        assert rf.regulation_mode == rf.RegulationMode.ForwardPower\n\n\ndef test_reflected_power():\n    \"\"\"Get the reflected power.\"\"\"\n    read_send = bytes([0x08, 0xA6, 0xAE])\n    read_answ = bytes([0x0A, 0xA6, 0x01, 0x00, 0xAD])\n    with expected_protocol(\n        ik.dressler.Cesar1312,\n        [\n            read_send,\n            ACK,\n        ],\n        [ACK, read_answ],\n        sep=\"\",\n    ) as rf:\n        assert rf.reflected_power == u.Quantity(1, u.W)\n\n\ndef test_rf():\n    \"\"\"Set/get the RF output state.\"\"\"\n    rf_read = bytes([0x08, 0xA2, 0xAA])\n    rf_read_answ_on = bytes([0x0C, 0xA2, 0x20, 0x00, 0x00, 0x00, 0x8E])\n    rf_read_answ_off = bytes([0x0C, 0xA2, 0x00, 0x00, 0x00, 0x00, 0xAE])\n    rf_on = bytes([0x08, 0x02, 0x0A])\n    rf_on_answ = bytes([0x09, 0x02, 0x00, 0x0B])\n    rf_off = bytes([0x08, 0x01, 0x09])\n    rf_off_answ = bytes([0x09, 0x01, 0x00, 0x08])\n    with expected_protocol(\n        ik.dressler.Cesar1312,\n        [\n            rf_read,\n            ACK,\n            rf_off,\n            ACK,\n            rf_on,\n            ACK,\n            rf_read,\n            ACK,\n        ],\n        [\n            ACK,\n            rf_read_answ_off,\n            ACK,\n            rf_off_answ,\n            ACK,\n            rf_on_answ,\n            ACK,\n            rf_read_answ_on,\n        ],\n        sep=\"\",\n    ) as rf:\n        assert not rf.rf\n        rf.rf = False\n        rf.rf = True\n        assert rf.rf\n\n\ndef test_rf_cmd_invalid():\n    \"\"\"Raise OSError if acknowledgement of cmd fails after retries.\"\"\"\n    rf_read = bytes([0x08, 0xA2, 0xAA])\n    with expected_protocol(\n        ik.dressler.Cesar1312,\n        [\n            rf_read,\n            rf_read,\n        ],\n        [\n            NAK,\n            NAK,\n        ],\n        sep=\"\",\n    ) as rf:\n        rf.retries = 1\n        with pytest.raises(OSError):\n            rf.rf\n\n\ndef test_rf_reply_invalid():\n    \"\"\"Raise OSError if acknowledgement of reply fails after retries.\"\"\"\n    rf_read = bytes([0x08, 0xA2, 0xAA])\n    rf_read_answ_on = bytes([0x0C, 0xA2, 0x20, 0x00, 0x00, 0x00, 0xFF])  # bad checksum\n    with expected_protocol(\n        ik.dressler.Cesar1312,\n        [\n            rf_read,\n            NAK,\n            NAK,\n        ],\n        [\n            ACK,\n            rf_read_answ_on,\n            rf_read_answ_on,\n            rf_read_answ_on,\n        ],\n        sep=\"\",\n    ) as rf:\n        rf.retries = 2\n        with pytest.raises(OSError):\n            rf.rf\n\n\ndef test_unknown_command():\n    \"\"\"Raise OSError if an unknown command is sent.\"\"\"\n    pkg_send = bytes([0x08, 0x00, 0x08])\n    pkg_rec = bytes([0x09, 0x80, 0x63, 0xEA])\n    with expected_protocol(\n        ik.dressler.Cesar1312,\n        [\n            pkg_send,\n            ACK,\n        ],\n        [\n            ACK,\n            pkg_rec,\n        ],\n        sep=\"\",\n    ) as rf:\n        with pytest.raises(OSError) as err:\n            rf.sendcmd(rf._make_pkg(0))\n        assert \"Command not implemented\" in err.value.args[0]\n\n\ndef test_device_returns_no_data():\n    \"\"\"Raise ValueError if device returned no data.\"\"\"\n    pkg_send = bytes([0x08, 0x00, 0x08])\n    pkg_rec = bytes([0x08, 0x80, 0x88])\n    with expected_protocol(\n        ik.dressler.Cesar1312,\n        [\n            pkg_send,\n            ACK,\n        ],\n        [\n            ACK,\n            pkg_rec,\n        ],\n        sep=\"\",\n    ) as rf:\n        with pytest.raises(ValueError) as err:\n            rf.sendcmd(rf._make_pkg(0))\n        assert \"No data received from the device\" in err.value.args[0]\n\n\ndef test_answer_longer_six_bytes():\n    \"\"\"Ensure that answers with data >6 bytes are handled correctly.\n\n    Note: While the protocol describes this scenario and it is implemented\n    into the query, the whole command book does not offer a single command\n    where this case occurs. However, this might be different for other\n    models.\n    \"\"\"\n    pkg_send = bytes([0x08, 0x00, 0x08])\n    rec_data = bytes([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08])\n    pkg_rec = bytes([0x0F, 0x00, 0x08]) + rec_data\n    pkg_rec += checksum(pkg_rec)\n    with expected_protocol(\n        ik.dressler.Cesar1312,\n        [\n            pkg_send,\n            ACK,\n        ],\n        [\n            ACK,\n            pkg_rec,\n        ],\n        sep=\"\",\n    ) as rf:\n        data = rf.query(rf._make_pkg(0))\n        assert data == rec_data\n\n\n# TEST PRIVATE METHODS #\n\n\ndef test_make_pkg():\n    \"\"\"Create a package that can be sent to the instrument.\"\"\"\n    with expected_protocol(\n        ik.dressler.Cesar1312,\n        [],\n        [],\n        sep=\"\",\n    ) as rf:\n        # simple query package with command 1\n        pkg_exp = bytes([0b00001000, 0x01])\n        pkg_exp += rf._calculate_checksum(pkg_exp)\n\n        pkg = rf._make_pkg(1, None)\n        assert pkg == pkg_exp\n\n        pkg = rf._make_pkg(1)  # should be same as above\n        assert pkg == pkg_exp\n\n        # change address to 3 and send 4 bytes of 1024 to command 2\n        pkg_exp = bytes([0b00011100, 0x02, 0x00, 0x04, 0x00, 0x00])\n        pkg_exp += rf._calculate_checksum(pkg_exp)\n\n        rf.address = 3\n        pkg = rf._make_pkg(2, (1024).to_bytes(4, \"little\"))\n\n        assert pkg == pkg_exp\n\n        # some long command with lots of data, using the make data pkg\n        rf.address = 1\n        pkg_exp = bytes(\n            [\n                0b00001111,\n                0x01,\n                0x09,\n                0x01,\n                0x02,\n                0x03,\n                0x04,\n                0x05,\n                0x00,\n                0x04,\n                0x00,\n                0x00,\n            ]\n        )\n        data = rf._make_data([1, 1, 1, 1, 1, 4], [1, 2, 3, 4, 5, 1024])\n        pkg_exp += rf._calculate_checksum(pkg_exp)\n\n        pkg = rf._make_pkg(1, data)\n        assert pkg == pkg_exp\n\n\ndef test_make_pkg_error():\n    \"\"\"Raise error if cmd is too large or data is too long.\"\"\"\n    with expected_protocol(\n        ik.dressler.Cesar1312,\n        [],\n        [],\n        sep=\"\",\n    ) as rf:\n        with pytest.raises(ValueError):\n            rf._make_pkg(256, None)\n\n        with pytest.raises(ValueError):\n            rf._make_pkg(1, (1).to_bytes(256, \"little\"))\n\n\n@given(values=st.lists(st.integers(min_value=0, max_value=255), min_size=1))\ndef test_checksum(values):\n    \"\"\"Assure that exclusive or of all bytes plus checksum is 0.\"\"\"\n    bts = bytes(values)\n    with expected_protocol(\n        ik.dressler.Cesar1312,\n        [],\n        [],\n        sep=\"\",\n    ) as rf:\n        checksum = rf._calculate_checksum(bts)[0]\n        for val in bts:\n            checksum ^= val\n        assert checksum == 0\n\n\n@given(\n    addr=st.integers(min_value=0, max_value=31),\n    data_length=st.integers(min_value=0, max_value=255),\n)\ndef test_pack_header(addr, data_length):\n    \"\"\"Pack the header of the package.\"\"\"\n    with expected_protocol(\n        ik.dressler.Cesar1312,\n        [],\n        [],\n        sep=\"\",\n    ) as rf:\n        rf.address = addr\n        header = rf._pack_header(data_length)\n        dl = 7 if data_length > 6 else data_length\n        header_exp = (addr << 3) + dl\n        assert header == header_exp\n\n\n@given(hdr_int=st.integers(min_value=0, max_value=255))\ndef test_unpack_header(hdr_int):\n    \"\"\"Unpack a header to return address and data length.\"\"\"\n    hdr = hdr_int.to_bytes(1, \"little\")\n    with expected_protocol(\n        ik.dressler.Cesar1312,\n        [],\n        [],\n        sep=\"\",\n    ) as rf:\n        addr, dl = rf._unpack_header(hdr)\n        assert addr == hdr_int >> 3\n        assert dl == hdr_int & 0b00000111\n\n\ndef checksum(values: bytes) -> bytes:\n    \"\"\"Calculate the checksum of the given values.\"\"\"\n    checksum = 0x00\n    for val in values:\n        checksum ^= val\n    return bytes([checksum])\n"
  },
  {
    "path": "tests/test_fluke/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_fluke/test_fluke3000.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the Fluke 3000 FC multimeter\n\"\"\"\n\n# IMPORTS ####################################################################\n\nimport pytest\n\nfrom instruments.units import ureg as u\n\nimport instruments as ik\nfrom tests import expected_protocol\n\n# TESTS ######################################################################\n\n\n# pylint: disable=protected-access\n\n\n# Empty initialization sequence (scan function) that does not uncover\n# any available Fluke 3000 FC device.\nnone_sequence = [\n    \"rfebd 01 0\",\n    \"rfebd 02 0\",\n    \"rfebd 03 0\",\n    \"rfebd 04 0\",\n    \"rfebd 05 0\",\n    \"rfebd 06 0\",\n]\nnone_response = [\"CR:Ack=2\", \"CR:Ack=2\", \"CR:Ack=2\", \"CR:Ack=2\", \"CR:Ack=2\", \"CR:Ack=2\"]\n\n# Default initialization sequence (scan function) that binds a multimeter\n# to port 1 and a temperature module to port 2.\ninit_sequence = [\n    \"rfebd 01 0\",  # 1\n    \"rfgus 01\",  # 2\n    \"rfebd 02 0\",  # 3\n    \"rfgus 02\",  # 4\n    \"rfebd 03 0\",  # 5\n    \"rfebd 04 0\",  # 6\n    \"rfebd 05 0\",  # 7\n    \"rfebd 06 0\",  # 8\n]\ninit_response = [\n    \"CR:Ack=0:RFEBD\",  # 1.1\n    \"ME:R:S#=01:DCC=012:PH=64\",  # 1.2\n    \"CR:Ack=0:RFGUS\",  # 2.1\n    \"ME:R:S#=01:DCC=004:PH=46333030304643\",  # 2.2\n    \"CR:Ack=0:RFEBD\",  # 3.1\n    \"ME:R:S#=01:DCC=012:PH=64\",  # 3.2\n    \"CR:Ack=0:RFGUS\",  # 4.1\n    \"ME:R:S#=02:DCC=004:PH=54333030304643\",  # 4.2\n    \"CR:Ack=2\",  # 5\n    \"CR:Ack=2\",  # 6\n    \"CR:Ack=2\",  # 7\n    \"CR:Ack=2\",  # 8\n]\n\n\n# Default initialization sequence (scan function) that binds a multimeter\n# to port 1. Adopted from `init_sequence` and `init_response`, thus\n# counting does not contain 4.\ninit_sequence_mm_only = [\n    \"rfebd 01 0\",  # 1\n    \"rfgus 01\",  # 2\n    \"rfebd 02 0\",  # 3\n    \"rfebd 03 0\",  # 5\n    \"rfebd 04 0\",  # 6\n    \"rfebd 05 0\",  # 7\n    \"rfebd 06 0\",  # 8\n]\ninit_response_mm_only = [\n    \"CR:Ack=0:RFEBD\",  # 1.1\n    \"ME:R:S#=01:DCC=012:PH=64\",  # 1.2\n    \"CR:Ack=0:RFGUS\",  # 2.1\n    \"ME:R:S#=01:DCC=004:PH=46333030304643\",  # 2.2\n    \"CR:Ack=2\",  # 3\n    \"CR:Ack=2\",  # 5\n    \"CR:Ack=2\",  # 6\n    \"CR:Ack=2\",  # 7\n    \"CR:Ack=2\",  # 8\n]\n\n\ndef test_mode():\n    with expected_protocol(\n        ik.fluke.Fluke3000,\n        init_sequence + [\"rfemd 01 1\", \"rfemd 01 2\"],  # 1  # 2\n        init_response\n        + [\n            \"CR:Ack=0:RFEMD\",  # 1.1\n            \"ME:R:S#=01:DCC=010:PH=00000006020C0600\",  # 1.2\n            \"CR:Ack=0:RFEMD\",  # 2\n        ],\n        \"\\r\",\n    ) as inst:\n        assert inst.mode == inst.Mode.voltage_dc\n\n\ndef test_mode_key_error():\n    \"\"\"Raise KeyError if the Module is not available.\"\"\"\n    with expected_protocol(\n        ik.fluke.Fluke3000, init_sequence, init_response, \"\\r\"\n    ) as inst:\n        # kill positions to trigger error\n        inst.positions = {}\n        with pytest.raises(KeyError) as err_info:\n            _ = inst.mode\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"No `Fluke3000` FC multimeter is bound\"\n\n\ndef test_trigger_mode_attribute_error():\n    \"\"\"Raise AttributeError since trigger mode not supported.\"\"\"\n    with expected_protocol(\n        ik.fluke.Fluke3000, init_sequence, init_response, \"\\r\"\n    ) as inst:\n        with pytest.raises(AttributeError) as err_info:\n            _ = inst.trigger_mode\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"The `Fluke3000` only supports single trigger when \" \"queried\"\n\n\ndef test_relative_attribute_error():\n    \"\"\"Raise AttributeError since relative measurement mode not supported.\"\"\"\n    with expected_protocol(\n        ik.fluke.Fluke3000, init_sequence, init_response, \"\\r\"\n    ) as inst:\n        with pytest.raises(AttributeError) as err_info:\n            _ = inst.relative\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"The `Fluke3000` FC does not support relative \" \"measurements\"\n\n\ndef test_input_range_attribute_error():\n    \"\"\"\n    Raise AttributeError since instrument is an auto ranging only\n    multimeter.\n    \"\"\"\n    with expected_protocol(\n        ik.fluke.Fluke3000, init_sequence, init_response, \"\\r\"\n    ) as inst:\n        with pytest.raises(AttributeError) as err_info:\n            _ = inst.input_range\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"The `Fluke3000` FC is an autoranging only \" \"multimeter\"\n\n\ndef test_connect():\n    with expected_protocol(\n        ik.fluke.Fluke3000,\n        none_sequence\n        + [\n            \"ri\",  # 1\n            \"rfsm 1\",  # 2\n            \"rfdis\",  # 3\n        ]\n        + init_sequence,\n        none_response\n        + [\n            \"CR:Ack=0:RI\",  # 1.1\n            \"SI:PON=Power On\",  # 1.2\n            \"RE:O\",  # 1.3\n            \"CR:Ack=0:RFSM:Radio On Master\",  # 2.1\n            \"RE:M\",  # 2.2\n            \"CR:Ack=0:RFDIS\",  # 3.1\n            \"ME:S\",  # 3.2\n            \"ME:D:010200000000\",  # 3.3\n        ]\n        + init_response,\n        \"\\r\",\n    ) as inst:\n        assert inst.positions[ik.fluke.Fluke3000.Module.m3000] == 1\n        assert inst.positions[ik.fluke.Fluke3000.Module.t3000] == 2\n\n\ndef test_connect_no_modules_available():\n    \"\"\"Raise ValueError if no modules are avilable.\"\"\"\n    with pytest.raises(ValueError) as err_info:\n        with expected_protocol(\n            ik.fluke.Fluke3000, none_sequence, none_response, \"\\r\"\n        ) as inst:\n            _ = inst\n    err_msg = err_info.value.args[0]\n    assert err_msg == \"No `Fluke3000` modules available\"\n\n\ndef test_scan():\n    with expected_protocol(\n        ik.fluke.Fluke3000, init_sequence, init_response, \"\\r\"\n    ) as inst:\n        assert inst.positions[ik.fluke.Fluke3000.Module.m3000] == 1\n        assert inst.positions[ik.fluke.Fluke3000.Module.t3000] == 2\n\n\ndef test_scan_module_not_implemented():\n    \"\"\"Raise NotImplementedError if a module with wrong ID is found.\"\"\"\n    # modify response to contain unknown module\n    module_id = 42\n    mod_response = list(init_response)\n    mod_response[3] = f\"ME:R:S#=01:DCC=004:PH={module_id}\"  # new module id\n    with pytest.raises(NotImplementedError) as err_info:\n        with expected_protocol(\n            ik.fluke.Fluke3000, init_sequence, mod_response, \"\\r\"\n        ) as inst:\n            _ = inst\n    err_msg = err_info.value.args[0]\n    assert err_msg == f\"Module ID {module_id} not implemented\"\n\n\ndef test_reset():\n    with expected_protocol(\n        ik.fluke.Fluke3000,\n        init_sequence + [\"ri\", \"rfsm 1\"],  # 1  # 2\n        init_response\n        + [\n            \"CR:Ack=0:RI\",  # 1.1\n            \"SI:PON=Power On\",  # 1.2\n            \"RE:O\",  # 1.3\n            \"CR:Ack=0:RFSM:Radio On Master\",  # 2.1\n            \"RE:M\",  # 2.2\n        ],\n        \"\\r\",\n    ) as inst:\n        inst.reset()\n\n\ndef test_flush(mocker):\n    \"\"\"Test flushing the reads, which raises an OSError here.\n\n    Mocking `read()` to generate the error.\n    \"\"\"\n    with expected_protocol(\n        ik.fluke.Fluke3000, init_sequence, init_response, \"\\r\"\n    ) as inst:\n        # mock read to raise OSError\n        os_error_mock = mocker.Mock()\n        os_error_mock.side_effect = OSError\n        read_mock = mocker.patch.object(inst, \"read\", os_error_mock)\n        # now flush\n        inst.flush()\n        read_mock.assert_called()\n\n\ndef test_measure():\n    with expected_protocol(\n        ik.fluke.Fluke3000,\n        init_sequence + [\"rfemd 01 1\", \"rfemd 01 2\", \"rfemd 02 0\"],  # 1  # 2  # 3\n        init_response\n        + [\n            \"CR:Ack=0:RFEMD\",  # 1.1\n            \"ME:R:S#=01:DCC=010:PH=FD010006020C0600\",  # 1.2\n            \"CR:Ack=0:RFEMD\",  # 2\n            \"CR:Ack=0:RFEMD\",  # 3.1\n            \"ME:R:S#=02:DCC=010:PH=FD00C08207220000\",  # 3.2\n        ],\n        \"\\r\",\n    ) as inst:\n        assert inst.measure(inst.Mode.voltage_dc) == 0.509 * u.volt\n        assert inst.measure(inst.Mode.temperature) == u.Quantity(-25.3, u.degC)\n\n\ndef test_measure_invalid_mode():\n    \"\"\"Raise ValueError if measurement mode is not supported.\"\"\"\n    with expected_protocol(\n        ik.fluke.Fluke3000, init_sequence, init_response, \"\\r\"\n    ) as inst:\n        wrong_mode = 42\n        with pytest.raises(ValueError) as err_info:\n            inst.measure(wrong_mode)\n        err_msg = err_info.value.args[0]\n        assert err_msg == f\"Mode {wrong_mode} is not supported\"\n\n\ndef test_measure_no_module_with_mode():\n    \"\"\"\n    Raise ValueError if not sensor that supports the requested mode is\n    connected.\n    \"\"\"\n    mode_not_available = ik.fluke.Fluke3000.Mode.temperature\n    with expected_protocol(\n        ik.fluke.Fluke3000, init_sequence_mm_only, init_response_mm_only, \"\\r\"\n    ) as inst:\n        with pytest.raises(ValueError) as err_info:\n            inst.measure(mode=mode_not_available)\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Device necessary to measure {mode_not_available} \"\n            f\"is not available\"\n        )\n\n\ndef test_measure_inconsistent_answer(mocker):\n    \"\"\"Measurement test with inconsistent answer.\n\n    The first time around in this measurement an inconsistent answer is\n    returend. This would usually call a `flush` routine, which reads\n    until no more terminators are found. Here, `flush` is mocked out\n    such that the `expected_protocol` can actually be used.\n    \"\"\"\n    mode_issue = 42  # expect 02, answer something different - unexpected\n    with expected_protocol(\n        ik.fluke.Fluke3000,\n        init_sequence\n        + [\n            # bad query\n            \"rfemd 01 1\",  # 1\n            \"rfemd 01 2\",  # 2\n            \"rfemd 01 2\",  # 2\n            # try again\n            \"rfemd 01 1\",  # 1\n            \"rfemd 01 2\",  # 2\n        ],\n        init_response\n        + [\n            # bad response\n            \"CR:Ack=0:RFEMD\",  # 1.1\n            f\"ME:R:S#=01:DCC=010:PH=FD010006{mode_issue}0C0600\",  # 1.2\n            \"CR:Ack=0:RFEMD\",  # 2\n            \"CR:Ack=0:RFEMD\",  # 2\n            # \"\",  # something to flush\n            # try again\n            \"CR:Ack=0:RFEMD\",  # 1.1\n            \"ME:R:S#=01:DCC=010:PH=FD010006020C0600\",  # 1.2\n            \"CR:Ack=0:RFEMD\",  # 2\n        ],\n        \"\\r\",\n    ) as inst:\n        # mock out flush\n        flush_mock = mocker.patch.object(inst, \"flush\", return_value=None)\n        assert inst.measure(inst.Mode.voltage_dc) == 0.509 * u.volt\n        # assert that flush was called once\n        flush_mock.assert_called_once()\n\n\ndef test_parse_ph_not_in_result():\n    \"\"\"Raise ValueError if 'PH' is not in `result`.\"\"\"\n    with expected_protocol(\n        ik.fluke.Fluke3000, init_sequence, init_response, \"\\r\"\n    ) as inst:\n        mode = inst.Mode.temperature\n        bad_result = \"42\"\n        with pytest.raises(ValueError) as err_info:\n            inst._parse(bad_result, mode)\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == \"Cannot parse a string that does not contain a \" \"return value\"\n        )\n\n\ndef test_parse_wrong_mode():\n    \"\"\"Raise ValueError if multimeter not in the right mode.\"\"\"\n    with expected_protocol(\n        ik.fluke.Fluke3000, init_sequence, init_response, \"\\r\"\n    ) as inst:\n        mode_requested = inst.Mode.temperature\n        result = \"ME:R:S#=01:DCC=010:PH=FD010006020C0600\"\n        mode_result = inst.Mode(result.split(\"PH=\")[-1][8:10])\n        with pytest.raises(ValueError) as err_info:\n            inst._parse(result, mode_requested)\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Mode {mode_requested.name} was requested but \"\n            f\"the Fluke 3000FC Multimeter is in mode \"\n            f\"{mode_result.name} instead. Could not read the \"\n            f\"requested quantity.\"\n        )\n\n\ndef test_parse_factor_wrong_code():\n    \"\"\"Raise ValueError if code not in prefixes.\"\"\"\n    data = \"00000012\"\n    byte = format(int(data[6:8], 16), \"08b\")\n    code = int(byte[1:4], 2)\n    with expected_protocol(\n        ik.fluke.Fluke3000, init_sequence, init_response, \"\\r\"\n    ) as inst:\n        with pytest.raises(ValueError) as err_info:\n            inst._parse_factor(data)\n        err_msg = err_info.value.args[0]\n        assert err_msg == f\"Metric prefix not recognized: {code}\"\n"
  },
  {
    "path": "tests/test_generic_scpi/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_generic_scpi/test_scpi_function_generator.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for generic SCPI function generator instruments\n\"\"\"\n\n# IMPORTS ####################################################################\n\nimport pytest\n\nfrom instruments.units import ureg as u\n\nimport instruments as ik\nfrom tests import expected_protocol, make_name_test\n\n# TESTS ######################################################################\n\ntest_scpi_func_gen_name = make_name_test(ik.generic_scpi.SCPIFunctionGenerator)\n\n\ndef test_scpi_func_gen_amplitude():\n    with expected_protocol(\n        ik.generic_scpi.SCPIFunctionGenerator,\n        [\n            \"VOLT:UNIT?\",\n            \"VOLT?\",\n            \"VOLT:UNIT VPP\",\n            \"VOLT 2.0\",\n            \"VOLT:UNIT DBM\",\n            \"VOLT 1.5\",\n        ],\n        [\"VPP\", \"+1.000000E+00\"],\n        repeat=2,\n    ) as fg:\n        assert fg.amplitude == (1 * u.V, fg.VoltageMode.peak_to_peak)\n        fg.amplitude = 2 * u.V\n        fg.amplitude = (1.5 * u.V, fg.VoltageMode.dBm)\n\n        assert fg.channel[0].amplitude == (1 * u.V, fg.VoltageMode.peak_to_peak)\n        fg.channel[0].amplitude = 2 * u.V\n        fg.channel[0].amplitude = (1.5 * u.V, fg.VoltageMode.dBm)\n\n\ndef test_scpi_func_gen_frequency():\n    with expected_protocol(\n        ik.generic_scpi.SCPIFunctionGenerator,\n        [\"FREQ?\", \"FREQ 1.005000e+02\"],\n        [\"+1.234000E+03\"],\n        repeat=2,\n    ) as fg:\n        assert fg.frequency == 1234 * u.Hz\n        fg.frequency = 100.5 * u.Hz\n\n        assert fg.channel[0].frequency == 1234 * u.Hz\n        fg.channel[0].frequency = 100.5 * u.Hz\n\n\ndef test_scpi_func_gen_function():\n    with expected_protocol(\n        ik.generic_scpi.SCPIFunctionGenerator, [\"FUNC?\", \"FUNC SQU\"], [\"SIN\"], repeat=2\n    ) as fg:\n        assert fg.function == fg.Function.sinusoid\n        fg.function = fg.Function.square\n\n        assert fg.channel[0].function == fg.Function.sinusoid\n        fg.channel[0].function = fg.Function.square\n\n\ndef test_scpi_func_gen_offset():\n    with expected_protocol(\n        ik.generic_scpi.SCPIFunctionGenerator,\n        [\"VOLT:OFFS?\", \"VOLT:OFFS 4.321000e-01\"],\n        [\n            \"+1.234000E+01\",\n        ],\n        repeat=2,\n    ) as fg:\n        assert fg.offset == 12.34 * u.V\n        fg.offset = 0.4321 * u.V\n\n        assert fg.channel[0].offset == 12.34 * u.V\n        fg.channel[0].offset = 0.4321 * u.V\n\n\ndef test_scpi_func_gen_phase():\n    \"\"\"Raise NotImplementedError when set / get phase.\"\"\"\n    with expected_protocol(\n        ik.generic_scpi.SCPIFunctionGenerator,\n        [],\n        [],\n    ) as fg:\n        with pytest.raises(NotImplementedError):\n            _ = fg.phase\n        with pytest.raises(NotImplementedError):\n            fg.phase = 42\n"
  },
  {
    "path": "tests/test_generic_scpi/test_scpi_instrument.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for generic SCPI instruments\n\"\"\"\n\n# IMPORTS ####################################################################\n\nfrom hypothesis import given, strategies as st\nimport pytest\n\nfrom instruments.units import ureg as u\n\nimport instruments as ik\nfrom tests import expected_protocol, make_name_test, unit_eq\n\n# TESTS ######################################################################\n\n\ntest_scpi_multimeter_name = make_name_test(ik.generic_scpi.SCPIInstrument)\n\n\ndef test_scpi_instrument_scpi_version():\n    \"\"\"Get name of instrument.\"\"\"\n    retval = \"12345\"\n    with expected_protocol(\n        ik.generic_scpi.SCPIInstrument, [\"SYST:VERS?\"], [f\"{retval}\"]\n    ) as inst:\n        assert inst.scpi_version == retval\n\n\n@pytest.mark.parametrize(\"retval\", (\"0\", \"1\"))\ndef test_scpi_instrument_op_complete(retval):\n    \"\"\"Check if operation is completed.\"\"\"\n    with expected_protocol(\n        ik.generic_scpi.SCPIInstrument, [\"*OPC?\"], [f\"{retval}\"]\n    ) as inst:\n        assert inst.op_complete == bool(int(retval))\n\n\n@pytest.mark.parametrize(\"retval\", (\"off\", \"0\", 0, False))\ndef test_scpi_instrument_power_on_status_off(retval):\n    \"\"\"Get / set power on status for instrument to on.\"\"\"\n    with expected_protocol(\n        ik.generic_scpi.SCPIInstrument, [\"*PSC 0\", \"*PSC?\"], [\"0\"]\n    ) as inst:\n        inst.power_on_status = retval\n        assert not inst.power_on_status\n\n\n@pytest.mark.parametrize(\"retval\", (\"on\", \"1\", 1, True))\ndef test_scpi_instrument_power_on_status_on(retval):\n    \"\"\"Get / set power on status for instrument to on.\"\"\"\n    with expected_protocol(\n        ik.generic_scpi.SCPIInstrument, [\"*PSC 1\", \"*PSC?\"], [\"1\"]\n    ) as inst:\n        inst.power_on_status = retval\n        assert inst.power_on_status\n\n\ndef test_scpi_instrument_power_on_status_value_error():\n    \"\"\"Raise ValueError if power on status set with invalid value.\"\"\"\n    with expected_protocol(ik.generic_scpi.SCPIInstrument, [], []) as inst:\n        with pytest.raises(ValueError):\n            inst.power_on_status = 42\n\n\ndef test_scpi_instrument_self_test_ok():\n    \"\"\"Check if self test returns okay.\"\"\"\n    with expected_protocol(\n        ik.generic_scpi.SCPIInstrument, [\"*TST?\", \"*TST?\"], [\"0\", \"not ok\"]  # ok\n    ) as inst:\n        assert inst.self_test_ok\n        assert not inst.self_test_ok\n\n\ndef test_scpi_instrument_reset():\n    \"\"\"Reset the instrument.\"\"\"\n    with expected_protocol(ik.generic_scpi.SCPIInstrument, [\"*RST\"], []) as inst:\n        inst.reset()\n\n\ndef test_scpi_instrument_clear():\n    \"\"\"Clear the instrument.\"\"\"\n    with expected_protocol(ik.generic_scpi.SCPIInstrument, [\"*CLS\"], []) as inst:\n        inst.clear()\n\n\ndef test_scpi_instrument_trigger():\n    \"\"\"Trigger the instrument.\"\"\"\n    with expected_protocol(ik.generic_scpi.SCPIInstrument, [\"*TRG\"], []) as inst:\n        inst.trigger()\n\n\ndef test_scpi_instrument_wait_to_continue():\n    \"\"\"Wait to continue the instrument.\"\"\"\n    with expected_protocol(ik.generic_scpi.SCPIInstrument, [\"*WAI\"], []) as inst:\n        inst.wait_to_continue()\n\n\ndef test_scpi_instrument_line_frequency():\n    \"\"\"Get / set line frequency.\"\"\"\n    freq_hz = 100\n    freq_mhz = u.Quantity(100000, u.mHz)\n    with expected_protocol(\n        ik.generic_scpi.SCPIInstrument,\n        [\n            f\"SYST:LFR {freq_hz}\",\n            \"SYST:LFR?\",\n            f\"SYST:LFR {freq_mhz.to('Hz').magnitude}\",\n        ],\n        [\n            f\"{freq_hz}\",\n        ],\n    ) as inst:\n        inst.line_frequency = freq_hz\n        unit_eq(inst.line_frequency, freq_hz * u.hertz)\n        # send a value as mHz\n        inst.line_frequency = freq_mhz\n\n\ndef test_scpi_instrument_check_error_queue():\n    \"\"\"Check and clear error queue.\"\"\"\n    ErrorCodes = ik.generic_scpi.SCPIInstrument.ErrorCodes\n    err1 = ErrorCodes.no_error  # is skipped\n    err2 = ErrorCodes.invalid_separator\n    err3 = 13  # invalid error number\n    with expected_protocol(\n        ik.generic_scpi.SCPIInstrument,\n        [f\"SYST:ERR:CODE:ALL?\"],\n        [\n            f\"{err1.value},{err2.value},{err3}\",\n        ],\n    ) as inst:\n        assert inst.check_error_queue() == [err2, err3]\n\n\n@given(val=st.floats(min_value=0, max_value=1))\ndef test_scpi_instrument_display_brightness(val):\n    \"\"\"Get / set display brightness.\"\"\"\n    with expected_protocol(\n        ik.generic_scpi.SCPIInstrument,\n        [f\"DISP:BRIG {val}\", f\"DISP:BRIG?\"],\n        [\n            f\"{val}\",\n        ],\n    ) as inst:\n        inst.display_brightness = val\n        assert inst.display_brightness == val\n\n\n@given(\n    val=st.floats(allow_nan=False, allow_infinity=False).filter(\n        lambda x: x < 0 or x > 1\n    )\n)\ndef test_scpi_instrument_display_brightness_invalid_value(val):\n    \"\"\"Raise ValueError if display brightness set with invalid value.\"\"\"\n    with expected_protocol(ik.generic_scpi.SCPIInstrument, [], []) as inst:\n        with pytest.raises(ValueError) as err_info:\n            inst.display_brightness = val\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"Display brightness must be a number between 0 \" \"and 1.\"\n\n\n@given(val=st.floats(min_value=0, max_value=1))\ndef test_scpi_instrument_display_contrast(val):\n    \"\"\"Get / set display contrast.\"\"\"\n    with expected_protocol(\n        ik.generic_scpi.SCPIInstrument,\n        [f\"DISP:CONT {val}\", f\"DISP:CONT?\"],\n        [\n            f\"{val}\",\n        ],\n    ) as inst:\n        inst.display_contrast = val\n        assert inst.display_contrast == val\n\n\n@given(\n    val=st.floats(allow_nan=False, allow_infinity=False).filter(\n        lambda x: x < 0 or x > 1\n    )\n)\ndef test_scpi_instrument_display_contrast_invalid_value(val):\n    \"\"\"Raise ValueError if display contrast set with invalid value.\"\"\"\n    with expected_protocol(ik.generic_scpi.SCPIInstrument, [], []) as inst:\n        with pytest.raises(ValueError) as err_info:\n            inst.display_contrast = val\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"Display contrast must be a number between 0 \" \"and 1.\"\n"
  },
  {
    "path": "tests/test_generic_scpi/test_scpi_multimeter.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for generic SCPI multimeter instruments\n\"\"\"\n\n# IMPORTS ####################################################################\n\nimport pytest\n\nfrom instruments.units import ureg as u\n\nimport instruments as ik\nfrom tests import expected_protocol, make_name_test, unit_eq\n\n# TESTS ######################################################################\n\ntest_scpi_multimeter_name = make_name_test(ik.generic_scpi.SCPIMultimeter)\n\n\ndef test_scpi_multimeter_mode():\n    with expected_protocol(\n        ik.generic_scpi.SCPIMultimeter,\n        [\"CONF?\", \"CONF:CURR:AC\"],\n        [\"FRES +1.000000E+01,+3.000000E-06\"],\n    ) as dmm:\n        assert dmm.mode == dmm.Mode.fourpt_resistance\n        dmm.mode = dmm.Mode.current_ac\n\n\ndef test_scpi_multimeter_trigger_mode():\n    with expected_protocol(\n        ik.generic_scpi.SCPIMultimeter, [\"TRIG:SOUR?\", \"TRIG:SOUR EXT\"], [\"BUS\"]\n    ) as dmm:\n        assert dmm.trigger_mode == dmm.TriggerMode.bus\n        dmm.trigger_mode = dmm.TriggerMode.external\n\n\ndef test_scpi_multimeter_input_range():\n    with expected_protocol(\n        ik.generic_scpi.SCPIMultimeter,\n        [\n            \"CONF?\",  # 1\n            \"CONF?\",  # 2\n            \"CONF?\",  # 3.1\n            \"CONF:FRES MIN\",  # 3.2\n            \"CONF?\",  # 4.1\n            \"CONF:CURR:DC 1\",  # 4.2\n        ],\n        [\n            \"CURR:AC +1.000000E+01,+3.000000E-06\",  # 1\n            \"CURR:AC AUTO,+3.000000E-06\",  # 2\n            \"FRES +1.000000E+01,+3.000000E-06\",  # 3\n            \"CURR:DC +1.000000E+01,+3.000000E-06\",  # 4\n        ],\n    ) as dmm:\n        unit_eq(dmm.input_range, 1e1 * u.amp)\n        assert dmm.input_range == dmm.InputRange.automatic\n        dmm.input_range = dmm.InputRange.minimum\n        dmm.input_range = 1 * u.amp\n\n\ndef test_scpi_multimeter_resolution():\n    with expected_protocol(\n        ik.generic_scpi.SCPIMultimeter,\n        [\n            \"CONF?\",  # 1\n            \"CONF?\",  # 2\n            \"CONF?\",  # 3.1\n            \"CONF:FRES +1.000000E+01,MIN\",  # 3.2\n            \"CONF?\",  # 4.1\n            \"CONF:CURR:DC +1.000000E+01,3e-06\",  # 4.2\n        ],\n        [\n            \"VOLT +1.000000E+01,+3.000000E-06\",  # 1\n            \"VOLT +1.000000E+01,MAX\",  # 2\n            \"FRES +1.000000E+01,+3.000000E-06\",  # 3\n            \"CURR:DC +1.000000E+01,+3.000000E-06\",  # 4\n        ],\n    ) as dmm:\n        assert dmm.resolution == 3e-06\n        assert dmm.resolution == dmm.Resolution.maximum\n        dmm.resolution = dmm.Resolution.minimum\n        dmm.resolution = 3e-06\n\n\ndef test_scpi_multimeter_resolution_type_error():\n    \"\"\"Raise TypeError if resolution value has the wrong type.\"\"\"\n    with expected_protocol(\n        ik.generic_scpi.SCPIMultimeter, [\"CONF?\"], [\"VOLT +1.000000E+01,+3.000000E-06\"]\n    ) as dmm:\n        wrong_type = \"42\"\n        with pytest.raises(TypeError) as err_info:\n            dmm.resolution = wrong_type\n        err_msg = err_info.value.args[0]\n        assert err_msg == (\n            \"Resolution must be specified as an int, float, \"\n            \"or SCPIMultimeter.Resolution value.\"\n        )\n\n\ndef test_scpi_multimeter_trigger_count():\n    with expected_protocol(\n        ik.generic_scpi.SCPIMultimeter,\n        [\"TRIG:COUN?\", \"TRIG:COUN?\", \"TRIG:COUN MIN\", \"TRIG:COUN 10\"],\n        [\n            \"+10\",\n            \"INF\",\n        ],\n    ) as dmm:\n        assert dmm.trigger_count == 10\n        assert dmm.trigger_count == dmm.TriggerCount.infinity\n        dmm.trigger_count = dmm.TriggerCount.minimum\n        dmm.trigger_count = 10\n\n\ndef test_scpi_multimeter_trigger_count_type_error():\n    \"\"\"Raise TypeError if trigger count value has the wrong type.\"\"\"\n    with expected_protocol(ik.generic_scpi.SCPIMultimeter, [], []) as dmm:\n        wrong_type = \"42\"\n        with pytest.raises(TypeError) as err_info:\n            dmm.trigger_count = wrong_type\n        err_msg = err_info.value.args[0]\n        assert err_msg == (\n            \"Trigger count must be specified as an int \"\n            \"or SCPIMultimeter.TriggerCount value.\"\n        )\n\n\ndef test_scpi_multimeter_sample_count():\n    with expected_protocol(\n        ik.generic_scpi.SCPIMultimeter,\n        [\"SAMP:COUN?\", \"SAMP:COUN?\", \"SAMP:COUN MIN\", \"SAMP:COUN 10\"],\n        [\n            \"+10\",\n            \"MAX\",\n        ],\n    ) as dmm:\n        assert dmm.sample_count == 10\n        assert dmm.sample_count == dmm.SampleCount.maximum\n        dmm.sample_count = dmm.SampleCount.minimum\n        dmm.sample_count = 10\n\n\ndef test_scpi_multimeter_sample_count_type_error():\n    \"\"\"Raise TypeError if sample count is of invalid type.\"\"\"\n    with expected_protocol(ik.generic_scpi.SCPIMultimeter, [], []) as dmm:\n        wrong_type = \"42\"\n        with pytest.raises(TypeError) as err_info:\n            dmm.sample_count = wrong_type\n        err_msg = err_info.value.args[0]\n        assert err_msg == (\n            \"Sample count must be specified as an int \"\n            \"or SCPIMultimeter.SampleCount value.\"\n        )\n\n\ndef test_scpi_multimeter_trigger_delay():\n    with expected_protocol(\n        ik.generic_scpi.SCPIMultimeter,\n        [\n            \"TRIG:DEL?\",\n            f\"TRIG:DEL {1:e}\",\n        ],\n        [\n            \"+1\",\n        ],\n    ) as dmm:\n        unit_eq(dmm.trigger_delay, 1 * u.second)\n        dmm.trigger_delay = 1000 * u.millisecond\n\n\ndef test_scpi_multimeter_sample_source():\n    with expected_protocol(\n        ik.generic_scpi.SCPIMultimeter,\n        [\n            \"SAMP:SOUR?\",\n            \"SAMP:SOUR TIM\",\n        ],\n        [\n            \"IMM\",\n        ],\n    ) as dmm:\n        assert dmm.sample_source == dmm.SampleSource.immediate\n        dmm.sample_source = dmm.SampleSource.timer\n\n\ndef test_scpi_multimeter_sample_timer():\n    with expected_protocol(\n        ik.generic_scpi.SCPIMultimeter,\n        [\n            \"SAMP:TIM?\",\n            f\"SAMP:TIM {1:e}\",\n        ],\n        [\n            \"+1\",\n        ],\n    ) as dmm:\n        unit_eq(dmm.sample_timer, 1 * u.second)\n        dmm.sample_timer = 1000 * u.millisecond\n\n\ndef test_scpi_multimeter_relative_not_implemented():\n    \"\"\"Raise NotImplementedError when set / get relative.\"\"\"\n    with expected_protocol(ik.generic_scpi.SCPIMultimeter, [], []) as dmm:\n        with pytest.raises(NotImplementedError):\n            _ = dmm.relative\n        with pytest.raises(NotImplementedError):\n            dmm.relative = 42\n\n\ndef test_scpi_multimeter_measure():\n    with expected_protocol(\n        ik.generic_scpi.SCPIMultimeter,\n        [\n            \"MEAS:VOLT:DC?\",\n        ],\n        [\n            \"+4.23450000E-03\",\n        ],\n    ) as dmm:\n        unit_eq(dmm.measure(dmm.Mode.voltage_dc), 4.2345e-03 * u.volt)\n\n\ndef test_scpi_multimeter_measure_mode_none():\n    \"\"\"Read current mode if not specified, test with volt, DC mode.\"\"\"\n    with expected_protocol(\n        ik.generic_scpi.SCPIMultimeter,\n        [\n            \"CONF?\",\n            \"MEAS:VOLT:DC?\",\n        ],\n        [\n            \"VOLT:DC\",\n            \"+4.23450000E-03\",\n        ],\n    ) as dmm:\n        unit_eq(dmm.measure(), 4.2345e-03 * u.volt)\n\n\ndef test_scpi_multimeter_measure_invalid_mode():\n    \"\"\"Raise TypeError if mode is not of type SCPIMultimeter.Mode.\"\"\"\n    with expected_protocol(ik.generic_scpi.SCPIMultimeter, [], []) as dmm:\n        wrong_type = 42\n        with pytest.raises(TypeError) as err_info:\n            dmm.measure(mode=wrong_type)\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Mode must be specified as a SCPIMultimeter.Mode \"\n            f\"value, got {type(wrong_type)} instead.\"\n        )\n"
  },
  {
    "path": "tests/test_gentec_eo/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_gentec_eo/test_blu.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the Gentec-eo Blu\n\"\"\"\n\n# IMPORTS ####################################################################\n\nfrom hypothesis import given\nfrom hypothesis import strategies as st\nimport pytest\n\nimport instruments as ik\nfrom instruments.units import ureg as u\nfrom tests import expected_protocol\n\n# TESTS ######################################################################\n\n# pylint: disable=protected-access\n\n\n# TESTS FOR Blu #\n\n\ndef test_blu_initialization():\n    \"\"\"Initialize the device.\"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [],\n        [],\n        sep=\"\\r\\n\",\n    ) as blu:\n        assert blu.terminator == \"\\r\\n\"\n        assert blu._power_mode is None\n\n\n# TEST PROPERTIES #\n\n\ndef test_blu_anticipation():\n    \"\"\"Get / Set the instrument into anticipation mode.\"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [\"*GAN\", \"*ANT0\"],\n        [\"Anticipation: 1\", \"ACK\"],\n        sep=\"\\r\\n\",\n    ) as blu:\n        assert blu.anticipation\n        blu.anticipation = False\n\n\ndef test_blu_auto_scale():\n    \"\"\"Get / Set the instrument into automatic scaling mode.\"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [\"*GAS\", \"*SAS0\"],\n        [\"Autoscale: 1\", \"ACK\"],\n        sep=\"\\r\\n\",\n    ) as blu:\n        assert blu.auto_scale\n        blu.auto_scale = False\n\n\ndef test_blu_available_scales():\n    \"\"\"Get the available scales that are on teh blue device.\n\n    Note that the routine tested here will temporarily overwrite the\n    terminator and the timeout. The function here is special in the\n    sense that it returns a list of parameters, all individual entries\n    are separated by the terminator. There is no clear end to when this\n    should be finished. It is assumed that 1 second is enough time to\n    send all the data.\n    \"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [\"*DVS\"],\n        [\n            \"[22]: 100.0 m\\r\\n\"\n            \"[23]: 300.0 m\\r\\n\"\n            \"[24]: 1.000\\r\\n\"\n            \"[25]: 3.000\\r\\n\"\n            \"[26]: 10.00\\r\\n\"\n            \"[27]: 30.00\\r\\n\"\n            \"[28]: 100.0\\r\\n\"\n        ],\n        sep=\"\",\n    ) as blu:\n        ret_scale = [\n            blu.Scale.max100milli,\n            blu.Scale.max300milli,\n            blu.Scale.max1,\n            blu.Scale.max3,\n            blu.Scale.max10,\n            blu.Scale.max30,\n            blu.Scale.max100,\n        ]\n        assert blu.available_scales == ret_scale\n\n\ndef test_blu_available_scales_error():\n    \"\"\"Ensure that temporary variables are reset if read errors.\n\n    Return a `bogus` value, which is not an available scale, and ensure\n    that the temporary variables are reset afterwards. This specific\n    case raises a ValueError.\n    \"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [\"*DVS\"],\n        [\"bogus\"],\n        sep=\"\",\n    ) as blu:\n        _terminator = blu.terminator\n        _timeout = blu.timeout\n        with pytest.raises(ValueError):\n            _ = blu.available_scales\n        assert blu.terminator == _terminator\n        assert blu.timeout == _timeout\n\n\ndef test_blu_battery_state():\n    \"\"\"Get the battery state of the instrument in percent.\"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [\"*QSO\"],\n        [\"98\"],\n        sep=\"\\r\\n\",\n    ) as blu:\n        assert blu.battery_state == u.Quantity(98, u.percent)\n\n\ndef test_blu_current_value_watts():\n    \"\"\"Get the current value in Watt mode.\"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [\"*GMD\", \"*CVU\"],\n        [\"Mode: 0\", \"42\"],\n        sep=\"\\r\\n\",\n    ) as blu:\n        assert blu.current_value == u.Quantity(42, u.W)\n\n\ndef test_blu_current_value_joules():\n    \"\"\"Get the current value in Watt mode.\"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [\"*GMD\", \"*CVU\"],\n        [\"Mode: 2\", \"42\"],\n        sep=\"\\r\\n\",\n    ) as blu:\n        assert blu.current_value == u.Quantity(42, u.J)\n\n\ndef test_blu_head_type():\n    \"\"\"Get information on the connected power meter head.\n\n    Here, an example head is returned.\n    \"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [\"*GFW\"],\n        [\"NIG : 104552, Wattmeter, V1.95\"],\n        sep=\"\\r\\n\",\n    ) as blu:\n        example_head = \"NIG : 104552, Wattmeter, V1.95\"\n        assert blu.head_type == example_head\n\n\ndef test_blu_measure_mode():\n    \"\"\"Get the measure mode the head is in.\n\n    This routine is also run when a unitful response is returned from\n    another routine and the measurement mode has not been determined\n    before.\n    \"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [\"*GMD\", \"*GMD\"],\n        [\"Mode: 0\", \"Mode: 2\"],\n        sep=\"\\r\\n\",\n    ) as blu:\n        # power mode\n        assert blu.measure_mode == \"power\"\n        assert blu._power_mode\n\n        # single shot energy mode (J)\n        assert blu.measure_mode == \"sse\"\n        assert not blu._power_mode\n\n\ndef test_blu_new_value_ready():\n    \"\"\"Query if a new value is ready for reading.\"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [\"*NVU\", \"*NVU\"],\n        [\"New Data Not Available\", \"New Data Available\"],\n        sep=\"\\r\\n\",\n    ) as blu:\n        assert not blu.new_value_ready\n        assert blu.new_value_ready\n\n\n@pytest.mark.parametrize(\"scale\", ik.gentec_eo.Blu.Scale)\ndef test_blu_scale(scale):\n    \"\"\"Get / set the instrument scale manually.\"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [f\"*SCS{scale.value}\", \"*GCR\"],\n        [\"ACK\", f\"Range: {scale.value}\"],\n        sep=\"\\r\\n\",\n    ) as blu:\n        blu.scale = scale\n        assert blu.scale == scale\n\n\ndef test_blu_single_shot_energy_mode():\n    \"\"\"Get / set the single shot energy mode.\"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [\"*GSE\", \"*SSE1\"],\n        [\"SSE: 0\", \"ACK\"],\n        sep=\"\\r\\n\",\n    ) as blu:\n        assert not blu.single_shot_energy_mode\n        assert blu._power_mode\n        blu.single_shot_energy_mode = True\n        assert not blu._power_mode\n\n\ndef test_blu_trigger_level():\n    \"\"\"Get / set the trigger level.\"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [\"*GTL\", \"*STL53.4\", \"*STL01.2\", \"*STL1.23\"],\n        [\n            \"Trigger level: 15.4% (4.6 Watts) of max power: 30 Watts\",\n            \"ACK\",\n            \"ACK\",\n            \"ACK\",\n        ],\n        sep=\"\\r\\n\",\n    ) as blu:\n        assert blu.trigger_level == 0.154\n        blu.trigger_level = 0.534\n        blu.trigger_level = 0.012\n        blu.trigger_level = 0.0123\n\n\ndef test_blu_trigger_level_invalid_value():\n    \"\"\"Raise error when trigger level value set is out of bound.\"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [],\n        [],\n        sep=\"\\r\\n\",\n    ) as blu:\n        with pytest.raises(ValueError):\n            blu.trigger_level = -0.3\n        with pytest.raises(ValueError):\n            blu.trigger_level = 1.1\n\n\ndef test_blu_usb_state():\n    \"\"\"Get the status if USB cable is plugged in.\"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [\"*USB\"],\n        [\"USB: 1\"],\n        sep=\"\\r\\n\",\n    ) as blu:\n        assert blu.usb_state\n\n\ndef test_blu_user_multiplier():\n    \"\"\"Get / set user multiplier.\"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [\"*GUM\", \"*MUL435.6666\"],\n        [\"User Multiplier: 3.3000000e+01\", \"ACK\"],\n        sep=\"\\r\\n\",\n    ) as blu:\n        assert blu.user_multiplier == 33.0\n        blu.user_multiplier = 435.6666\n\n\ndef test_blu_user_offset_watts():\n    \"\"\"Get / set user offset in watts.\"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [\"*GMD\", \"*GUO\", \"*OFF00000042\"],  # get power mode\n        [\"Mode: 0\", \"User Offset : 1.500e-3\", \"ACK\"],  # power mode watts\n        sep=\"\\r\\n\",\n    ) as blu:\n        assert blu.user_offset == u.Quantity(1.5, u.mW)\n        blu.user_offset = u.Quantity(42.0, u.W)\n\n\ndef test_blu_user_offset_joules():\n    \"\"\"Get / set user offset in joules.\"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [\"*GMD\", \"*GUO\", \"*OFF00000042\"],  # get power mode\n        [\"Mode: 2\", \"User Offset : 1.500e-3\", \"ACK\"],  # power mode joules\n        sep=\"\\r\\n\",\n    ) as blu:\n        assert blu.user_offset == u.Quantity(0.0015, u.J)\n        blu.user_offset = u.Quantity(42.0, u.J)\n\n\ndef test_blu_user_offset_unitless():\n    \"\"\"Set user offset unitless.\"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [\"*OFF00000042\"],\n        [\"ACK\"],\n        sep=\"\\r\\n\",\n    ) as blu:\n        blu.user_offset = 42.0\n\n\ndef test_blu_user_offset_unit_error():\n    \"\"\"Raise ValueError if unit is invalid.\"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [],\n        [],\n        sep=\"\\r\\n\",\n    ) as blu:\n        with pytest.raises(ValueError):\n            blu.user_offset = u.Quantity(42, u.mm)\n\n\ndef test_blu_version():\n    \"\"\"Query version of device.\"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [\"*VER\"],\n        [\"Blu firmware Version 1.95\"],\n        sep=\"\\r\\n\",\n    ) as blu:\n        version = \"Blu firmware Version 1.95\"\n        assert blu.version == version\n\n\ndef test_blu_wavelength():\n    \"\"\"Get / set the wavelength.\"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [\"*GWL\", \"*PWC00527\", \"*PWC00527\"],\n        [\"PWC: 1064\", \"ACK\", \"ACK\"],\n        sep=\"\\r\\n\",\n    ) as blu:\n        assert blu.wavelength == u.Quantity(1064, u.nm)\n        blu.wavelength = u.Quantity(0.527, u.um)\n        blu.wavelength = 527\n\n\ndef test_blu_wavelength_out_of_bound():\n    \"\"\"Get / set the wavelength when value is out of bound.\"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [\"*PWC00000\", \"*PWC00000\"],\n        [\"ACK\", \"ACK\"],\n        sep=\"\\r\\n\",\n    ) as blu:\n        blu.wavelength = u.Quantity(1000, u.um)\n        blu.wavelength = -3\n\n\ndef test_blu_zero_offset():\n    \"\"\"Get / set the zero offset.\"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [\"*GZO\", \"*SOU\", \"*COU\"],\n        [\"Zero: 1\", \"ACK\", \"ACK\"],\n        sep=\"\\r\\n\",\n    ) as blu:\n        assert blu.zero_offset\n        blu.zero_offset = True\n        blu.zero_offset = False\n\n\n# TEST METHODS #\n\n\ndef test_blu_confirm_connection():\n    \"\"\"Confirm a bluetooth connection.\"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [\"*RDY\"],\n        [\"ACK\"],\n        sep=\"\\r\\n\",\n    ) as blu:\n        blu.confirm_connection()\n\n\ndef test_blu_disconnect():\n    \"\"\"Disconnect bluetooth connection.\"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [\"*BTD\"],\n        [\"ACK\"],\n        sep=\"\\r\\n\",\n    ) as blu:\n        blu.disconnect()\n\n\ndef test_blu_scale_down():\n    \"\"\"Set the scale one level lower.\"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [\"*SSD\"],\n        [\"ACK\"],\n        sep=\"\\r\\n\",\n    ) as blu:\n        blu.scale_down()\n\n\ndef test_blu_scale_up():\n    \"\"\"Set the scale one level higher.\"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [\"*SSU\"],\n        [\"ACK\"],\n        sep=\"\\r\\n\",\n    ) as blu:\n        blu.scale_up()\n\n\ndef test_no_ack_query_error(mocker):\n    \"\"\"Ensure temporary variables reset if `_no_ack_query` errors.\n\n    Mocking query here in order to raise an error on query.\n    \"\"\"\n    with expected_protocol(\n        ik.gentec_eo.Blu,\n        [],\n        [],\n        sep=\"\\r\\n\",\n    ) as blu:\n        # mock query w/ IOError\n        io_error_mock = mocker.Mock()\n        io_error_mock.side_effect = IOError\n        mocker.patch.object(blu, \"query\", io_error_mock)\n        # do the query\n        with pytest.raises(IOError):\n            _ = blu._no_ack_query(\"QUERY\")\n        assert blu._ack_message == \"ACK\"\n\n\n# NON-Blu ROUTINES #\n\n\ndef test_format_eight_type():\n    \"\"\"Ensure type returned is string.\"\"\"\n    assert isinstance(ik.gentec_eo.blu._format_eight(3.0), str)\n\n\n@given(\n    value=st.floats(\n        min_value=-1e100, max_value=1e100, exclude_min=True, exclude_max=True\n    )\n)\ndef test_format_eight_length_values(value):\n    \"\"\"Ensure format eight routine works.\n\n    This is a helper routine for the blu device to cut any number to\n    eight characters. Make sure this is the case with various numbers\n    and that it is correct to 1% with given number.\n    \"\"\"\n    value_read = ik.gentec_eo.blu._format_eight(value)\n    if value > 0:\n        assert value == pytest.approx(float(value_read), rel=0.01)\n    else:\n        assert value == pytest.approx(float(value_read), rel=0.05)\n    assert len(value_read) == 8\n"
  },
  {
    "path": "tests/test_glassman/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_glassman/test_glassmanfr.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the Glassman FR power supply\n\"\"\"\n\n# IMPORTS ####################################################################\n\nimport pytest\n\nimport instruments as ik\nfrom tests import expected_protocol\nfrom instruments.units import ureg as u\n\n# TESTS ######################################################################\n\n# pylint: disable=protected-access\n\n\ndef set_defaults(inst):\n    \"\"\"\n    Sets default values for the voltage and current range of the Glassman FR\n    to be used to test the voltage and current property getters/setters.\n    \"\"\"\n    inst.voltage_max = 50.0 * u.kilovolt\n    inst.current_max = 6.0 * u.milliamp\n    inst.polarity = +1\n\n\ndef test_channel():\n    with expected_protocol(ik.glassman.GlassmanFR, [], [], \"\\r\") as inst:\n        assert len(inst.channel) == 1\n        assert inst.channel[0] == inst\n\n\ndef test_voltage():\n    with expected_protocol(\n        ik.glassman.GlassmanFR,\n        [\"\\x01Q51\", \"\\x01S3330000000001CD\"],\n        [\"R00000000000040\", \"A\"],\n        \"\\r\",\n    ) as inst:\n        set_defaults(inst)\n        inst.voltage = 10.0 * u.kilovolt\n        assert inst.voltage == 10.0 * u.kilovolt\n\n\ndef test_current():\n    with expected_protocol(\n        ik.glassman.GlassmanFR,\n        [\"\\x01Q51\", \"\\x01S0003330000001CD\"],\n        [\"R00000000000040\", \"A\"],\n        \"\\r\",\n    ) as inst:\n        set_defaults(inst)\n        inst.current = 1.2 * u.milliamp\n        assert inst.current == 1.2 * u.milliamp\n\n\ndef test_voltage_sense():\n    with expected_protocol(\n        ik.glassman.GlassmanFR, [\"\\x01Q51\"], [\"R10A00000010053\"], \"\\r\"\n    ) as inst:\n        set_defaults(inst)\n        assert round(inst.voltage_sense) == 13.0 * u.kilovolt\n\n\ndef test_current_sense():\n    with expected_protocol(\n        ik.glassman.GlassmanFR, [\"\\x01Q51\"], [\"R0001550001004C\"], \"\\r\"\n    ) as inst:\n        set_defaults(inst)\n        assert inst.current_sense == 2.0 * u.milliamp\n\n\ndef test_mode():\n    with expected_protocol(\n        ik.glassman.GlassmanFR,\n        [\"\\x01Q51\", \"\\x01Q51\"],\n        [\"R00000000000040\", \"R00000000010041\"],\n        \"\\r\",\n    ) as inst:\n        assert inst.mode == inst.Mode.voltage\n        assert inst.mode == inst.Mode.current\n\n\ndef test_output():\n    with expected_protocol(\n        ik.glassman.GlassmanFR,\n        [\"\\x01S0000000000001C4\", \"\\x01Q51\", \"\\x01S0000000000002C5\", \"\\x01Q51\"],\n        [\"A\", \"R00000000000040\", \"A\", \"R00000000040044\"],\n        \"\\r\",\n    ) as inst:\n        inst.output = False\n        assert not inst.output\n        inst.output = True\n        assert inst.output\n\n\ndef test_output_type_error():\n    \"\"\"Raise TypeError when setting output w non-boolean value.\"\"\"\n    with expected_protocol(ik.glassman.GlassmanFR, [], [], \"\\r\") as inst:\n        with pytest.raises(TypeError) as err_info:\n            inst.output = 42\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"Output status mode must be a boolean.\"\n\n\n@pytest.mark.parametrize(\"value\", [0, 2])\ndef test_fault(value):\n    \"\"\"Get the instrument status: True if fault.\"\"\"\n    with expected_protocol(\n        ik.glassman.GlassmanFR,\n        [\"\\x01Q51\"],\n        [\n            f\"R000000000{value}004{value}\",\n        ],\n        \"\\r\",\n    ) as inst:\n        assert inst.fault == bool(value)\n\n\ndef test_version():\n    with expected_protocol(\n        ik.glassman.GlassmanFR, [\"\\x01V56\"], [\"B1465\"], \"\\r\"\n    ) as inst:\n        assert inst.version == \"14\"\n\n\ndef test_device_timeout():\n    with expected_protocol(\n        ik.glassman.GlassmanFR, [\"\\x01C073\", \"\\x01C174\"], [\"A\", \"A\"], \"\\r\"\n    ) as inst:\n        inst.device_timeout = True\n        assert inst.device_timeout\n        inst.device_timeout = False\n        assert not inst.device_timeout\n\n\ndef test_device_timeout_type_error():\n    \"\"\"Raise TypeError if device timeout mode not set with boolean.\"\"\"\n    with expected_protocol(ik.glassman.GlassmanFR, [], [], \"\\r\") as inst:\n        with pytest.raises(TypeError) as err_info:\n            inst.device_timeout = 42\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"Device timeout mode must be a boolean.\"\n\n\ndef test_sendcmd():\n    with expected_protocol(ik.glassman.GlassmanFR, [\"\\x01123ABC5C\"], [], \"\\r\") as inst:\n        inst.sendcmd(\"123ABC\")\n\n\ndef test_query():\n    \"\"\"Query the instrument.\"\"\"\n    response = \"R123ABC5C\"\n    with expected_protocol(\n        ik.glassman.GlassmanFR, [\"\\x01Q123ABCAD\"], [response], \"\\r\"\n    ) as inst:\n        assert inst.query(\"Q123ABC\") == response[1:-2]\n\n\ndef test_query_invalid_response_code():\n    \"\"\"Raise ValueError when query receives an invalid response code.\"\"\"\n    response = \"A123ABC5C\"\n    with expected_protocol(\n        ik.glassman.GlassmanFR, [\"\\x01Q123ABCAD\"], [response], \"\\r\"\n    ) as inst:\n        with pytest.raises(ValueError) as err_info:\n            inst.query(\"Q123ABC\")\n        err_msg = err_info.value.args[0]\n        assert err_msg == f\"Invalid response code: {response}\"\n\n\ndef test_query_invalid_checksum():\n    \"\"\"Raise ValueError if query returns with invalid checksum.\"\"\"\n    response = \"R123ABC5A\"\n    with expected_protocol(\n        ik.glassman.GlassmanFR, [\"\\x01Q123ABCAD\"], [response], \"\\r\"\n    ) as inst:\n        with pytest.raises(ValueError) as err_info:\n            inst.query(\"Q123ABC\")\n        err_msg = err_info.value.args[0]\n        assert err_msg == f\"Invalid checksum: {response}\"\n\n\n@pytest.mark.parametrize(\"err\", ik.glassman.GlassmanFR.ErrorCode)\ndef test_query_error(err):\n    \"\"\"Raise ValueError if query returns with error.\"\"\"\n    err_code = err.value\n    check_sum = ord(err_code) % 256\n    response = f\"E{err_code}{format(check_sum, '02X')}\"\n    with expected_protocol(\n        ik.glassman.GlassmanFR, [\"\\x01Q123ABCAD\"], [response], \"\\r\"\n    ) as inst:\n        with pytest.raises(ValueError) as err_info:\n            inst.query(\"Q123ABC\")\n        err_msg = err_info.value.args[0]\n        assert err_msg == f\"Instrument responded with error: {err.name}\"\n\n\ndef test_reset():\n    with expected_protocol(\n        ik.glassman.GlassmanFR, [\"\\x01S0000000000004C7\"], [\"A\"], \"\\r\"\n    ) as inst:\n        inst.reset()\n\n\ndef test_set_status():\n    with expected_protocol(\n        ik.glassman.GlassmanFR,\n        [\"\\x01S3333330000002D7\", \"\\x01Q51\"],\n        [\"A\", \"R00000000040044\"],\n        \"\\r\",\n    ) as inst:\n        set_defaults(inst)\n        inst.set_status(voltage=10 * u.kilovolt, current=1.2 * u.milliamp, output=True)\n        assert inst.output\n        assert inst.voltage == 10 * u.kilovolt\n        assert inst.current == 1.2 * u.milliamp\n\n\ndef test_parse_invalid_response():\n    \"\"\"Raise a RunTime error if response cannot be parsed.\"\"\"\n    response = \"000000000X00\"  # invalid monitors\n    with expected_protocol(ik.glassman.GlassmanFR, [], [], \"\\r\") as inst:\n        with pytest.raises(RuntimeError) as err_info:\n            inst._parse_response(response)\n        err_msg = err_info.value.args[0]\n        assert err_msg == f\"Cannot parse response packet: {response}\"\n"
  },
  {
    "path": "tests/test_hcp/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_hcp/test_tc038.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the HCP TC038\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nfrom tests import expected_protocol, unit_eq, pytest\nfrom instruments.units import ureg as u\n\n\nfrom instruments.hcp import TC038\n\n\ndef test_sendcmd():\n    with expected_protocol(TC038, [\"\\x0201010x\\x03\"], [], sep=\"\\r\") as inst:\n        inst.sendcmd(\"x\")\n\n\ndef test_query():\n    with expected_protocol(TC038, [\"\\x0201010x\\x03\"], [\"y\"], sep=\"\\r\") as inst:\n        assert inst.query(\"x\") == \"y\"\n\n\ndef test_setpoint():\n    with expected_protocol(\n        TC038, [\"\\x0201010WRDD0120,01\\x03\"], [\"\\x020101OK00C8\\x03\"], sep=\"\\r\"\n    ) as inst:\n        value = inst.setpoint\n        unit_eq(value, u.Quantity(20, u.degC))\n\n\ndef test_setpoint_setter():\n    # Communication from manual.\n    with expected_protocol(\n        TC038, [\"\\x0201010WWRD0120,01,00C8\\x03\"], [\"\\x020101OK\\x03\"], sep=\"\\r\"\n    ) as inst:\n        inst.setpoint = 20\n\n\ndef test_temperature():\n    # Communication from manual.\n    with expected_protocol(\n        TC038, [\"\\x0201010WRDD0002,01\\x03\"], [\"\\x020101OK00C8\\x03\"], sep=\"\\r\"\n    ) as inst:\n        value = inst.temperature\n        unit_eq(value, u.Quantity(20, u.degC))\n\n\ndef test_monitored():\n    # Communication from manual.\n    with expected_protocol(\n        TC038, [\"\\x0201010WRM\\x03\"], [\"\\x020101OK00C8\\x03\"], sep=\"\\r\"\n    ) as inst:\n        value = inst.monitored_value\n        unit_eq(value, u.Quantity(20, u.degC))\n\n\ndef test_set_monitored():\n    # Communication from manual.\n    with expected_protocol(\n        TC038, [\"\\x0201010WRS01D0002\\x03\"], [\"\\x020101OK\\x03\"], sep=\"\\r\"\n    ) as inst:\n        inst.monitored_quantity = \"temperature\"\n        assert inst.monitored_quantity == \"temperature\"\n\n\ndef test_set_monitored_wrong_input():\n    with expected_protocol(TC038, [], [], sep=\"\\r\") as inst:\n        with pytest.raises(AssertionError):\n            inst.monitored_quantity = \"temper\"\n\n\ndef test_information():\n    # Communication from manual.\n    with expected_protocol(\n        TC038,\n        [\"\\x0201010INF6\\x03\"],\n        [\"\\x020101OKUT150333 V01.R001111222233334444\\x03\"],\n        sep=\"\\r\",\n    ) as inst:\n        value = inst.information\n        assert value == \"UT150333 V01.R001111222233334444\"\n"
  },
  {
    "path": "tests/test_hcp/test_tc038d.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the HCP TC038D\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nfrom tests import expected_protocol, unit_eq, pytest\n\nfrom instruments.units import ureg as u\n\n\nfrom instruments.hcp import TC038D\n\n\ndef test_write_multiple():\n    # Communication from manual.\n    with expected_protocol(\n        TC038D,\n        [b\"\\x01\\x10\\x01\\x0a\\x00\\x04\\x08\\x00\\x00\\x03\\xe8\\xff\\xff\\xfc\\x18\\x8d\\xe9\"],\n        [b\"\\x01\\x10\\x01\\x0a\\x00\\x04\\xe0\\x34\"],\n        sep=\"\",\n    ) as inst:\n        inst.writeMultiple(0x010A, [1000, -1000])\n\n\ndef test_write_multiple_CRC_error():\n    with expected_protocol(\n        TC038D,\n        [b\"\\x01\\x10\\x01\\x06\\x00\\x02\\x04\\x00\\x00\\x01A\\xbf\\xb5\"],\n        [b\"\\x01\\x10\\x01\\x06\\x00\\x02\\x01\\x02\"],\n        sep=\"\",\n    ) as inst:\n        with pytest.raises(ConnectionError):\n            inst.setpoint = u.Quantity(32.1, u.degC)\n\n\ndef test_write_multiple_wrong_values():\n    with expected_protocol(\n        TC038D,\n        [],\n        [],\n        sep=\"\",\n    ) as inst:\n        with pytest.raises(ValueError):\n            inst.writeMultiple(0x010A, 5.5)\n\n\ndef test_write_multiple_Value_error():\n    with expected_protocol(\n        TC038D,\n        [b\"\\x01\\x10\\x01\\x06\\x00\\x02\\x04\\x00\\x00\\x01A\\xbf\\xb5\"],\n        [b\"\\x01\\x90\\x02\\x06\\x00\"],\n        sep=\"\",\n    ) as inst:\n        with pytest.raises(ValueError) as exc:\n            inst.setpoint = u.Quantity(32.1, u.degC)\n            assert str(exc) == \"Wrong start address\"\n\n\ndef test_read_CRC_error():\n    with expected_protocol(\n        TC038D,\n        [b\"\\x01\\x03\\x00\\x00\\x00\\x02\\xc4\\x0b\"],\n        [b\"\\x01\\x03\\x04\\x00\\x00\\x03\\xe8\\x01\\x02\"],\n        sep=\"\",\n    ) as inst:\n        with pytest.raises(ConnectionError):\n            inst.temperature\n\n\ndef test_read_address_error():\n    with expected_protocol(\n        TC038D,\n        [b\"\\x01\\x03\\x00\\x00\\x00\\x02\\xc4\\x0b\"],\n        [b\"\\x01\\x83\\x02\\01\\02\"],\n        sep=\"\",\n    ) as inst:\n        with pytest.raises(ValueError):\n            inst.temperature\n\n\ndef test_read_elements_error():\n    with expected_protocol(\n        TC038D,\n        [b\"\\x01\\x03\\x00\\x00\\x00\\x02\\xc4\\x0b\"],\n        [b\"\\x01\\x83\\x03\\01\\02\"],\n        sep=\"\",\n    ) as inst:\n        with pytest.raises(ValueError):\n            inst.temperature\n\n\ndef test_read_any_error():\n    with expected_protocol(\n        TC038D,\n        [b\"\\x01\\x03\\x00\\x00\\x00\\x02\\xc4\\x0b\"],\n        [b\"\\x01\\x43\\x05\\01\\02\"],\n        sep=\"\",\n    ) as inst:\n        with pytest.raises(ConnectionError):\n            inst.temperature\n\n\ndef test_setpoint():\n    with expected_protocol(\n        TC038D,\n        [b\"\\x01\\x03\\x01\\x06\\x00\\x02\\x25\\xf6\"],\n        [b\"\\x01\\x03\\x04\\x00\\x00\\x00\\x99:Y\"],\n        sep=\"\",\n    ) as inst:\n        value = inst.setpoint\n        unit_eq(value, u.Quantity(15.3, u.degC))\n\n\ndef test_setpoint_setter():\n    with expected_protocol(\n        TC038D,\n        [b\"\\x01\\x10\\x01\\x06\\x00\\x02\\x04\\x00\\x00\\x01A\\xbf\\xb5\"],\n        [b\"\\x01\\x10\\x01\\x06\\x00\\x02\\xa0\\x35\"],\n        sep=\"\",\n    ) as inst:\n        inst.setpoint = u.Quantity(32.1, u.degC)\n\n\ndef test_temperature():\n    # Communication from manual.\n    # Tests readRegister as well.\n    with expected_protocol(\n        TC038D,\n        [b\"\\x01\\x03\\x00\\x00\\x00\\x02\\xc4\\x0b\"],\n        [b\"\\x01\\x03\\x04\\x00\\x00\\x03\\xe8\\xfa\\x8d\"],\n        sep=\"\",\n    ) as inst:\n        value = inst.temperature\n        unit_eq(value, u.Quantity(100, u.degC))\n"
  },
  {
    "path": "tests/test_holzworth/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_holzworth/test_holzworth_hs9000.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the Holzworth HS9000\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nfrom instruments.units import ureg as u\n\nimport instruments as ik\nfrom tests import expected_protocol\nfrom .. import mock\n\n# TEST CLASSES ################################################################\n\n# pylint: disable=protected-access\n\n\ndef test_hs9000_name():\n    with expected_protocol(\n        ik.holzworth.HS9000,\n        [\":ATTACH?\", \":CH1:IDN?\"],\n        [\":CH1:CH2:FOO\", \"Foobar name\"],\n        sep=\"\\n\",\n    ) as hs:\n        assert hs.name == \"Foobar name\"\n\n\ndef test_channel_idx_list():\n    with expected_protocol(\n        ik.holzworth.HS9000,\n        [\n            \":ATTACH?\",\n        ],\n        [\":CH1:CH2:FOO\"],\n        sep=\"\\n\",\n    ) as hs:\n        assert hs._channel_idxs() == [0, 1, \"FOO\"]\n\n\ndef test_channel_returns_inner_class():\n    with expected_protocol(\n        ik.holzworth.HS9000,\n        [\n            \":ATTACH?\",\n        ],\n        [\":CH1:CH2:FOO\"],\n        sep=\"\\n\",\n    ) as hs:\n        channel = hs.channel[0]\n        assert isinstance(channel, hs.Channel) is True\n        assert channel._ch_name == \"CH1\"\n\n\ndef test_channel_sendcmd():\n    channel = ik.holzworth.HS9000.Channel(mock.MagicMock(), 0)\n\n    channel.sendcmd(\"FOO\")\n\n    channel._hs.sendcmd.assert_called_with(\":CH1:FOO\")\n\n\ndef test_channel_query():\n    channel = ik.holzworth.HS9000.Channel(mock.MagicMock(), 0)\n    channel._hs.query.return_value = \"FOO\"\n\n    value = channel.query(\"BAR\")\n\n    channel._hs.query.assert_called_with(\":CH1:BAR\")\n    assert value == \"FOO\"\n\n\ndef test_channel_reset():\n    channel = ik.holzworth.HS9000.Channel(mock.MagicMock(), 0)\n    channel.reset()\n\n    channel._hs.sendcmd.assert_called_with(\":CH1:*RST\")\n\n\ndef test_channel_recall_state():\n    channel = ik.holzworth.HS9000.Channel(mock.MagicMock(), 0)\n    channel.recall_state()\n\n    channel._hs.sendcmd.assert_called_with(\":CH1:*RCL\")\n\n\ndef test_channel_save_state():\n    channel = ik.holzworth.HS9000.Channel(mock.MagicMock(), 0)\n    channel.save_state()\n\n    channel._hs.sendcmd.assert_called_with(\":CH1:*SAV\")\n\n\ndef test_channel_temperature():\n    with expected_protocol(\n        ik.holzworth.HS9000,\n        [\":ATTACH?\", \":CH1:TEMP?\"],\n        [\":CH1:CH2:FOO\", \"10 C\"],\n        sep=\"\\n\",\n    ) as hs:\n        channel = hs.channel[0]\n        assert channel.temperature == u.Quantity(10, u.degC)\n\n\ndef test_channel_frequency_getter():\n    with expected_protocol(\n        ik.holzworth.HS9000,\n        [\":ATTACH?\", \":CH1:FREQ?\", \":CH1:FREQ:MIN?\", \":CH1:FREQ:MAX?\"],\n        [\":CH1:CH2:FOO\", \"1000 MHz\", \"100 MHz\", \"10 GHz\"],\n        sep=\"\\n\",\n    ) as hs:\n        channel = hs.channel[0]\n        assert channel.frequency == 1 * u.GHz\n        assert channel.frequency_min == 100 * u.MHz\n        assert channel.frequency_max == 10 * u.GHz\n\n\ndef test_channel_frequency_setter():\n    with expected_protocol(\n        ik.holzworth.HS9000,\n        [\":ATTACH?\", \":CH1:FREQ:MIN?\", \":CH1:FREQ:MAX?\", f\":CH1:FREQ {1:e}\"],\n        [\":CH1:CH2:FOO\", \"100 MHz\", \"10 GHz\"],\n        sep=\"\\n\",\n    ) as hs:\n        channel = hs.channel[0]\n        channel.frequency = 1 * u.GHz\n\n\ndef test_channel_power_getter():\n    with expected_protocol(\n        ik.holzworth.HS9000,\n        [\":ATTACH?\", \":CH1:PWR?\", \":CH1:PWR:MIN?\", \":CH1:PWR:MAX?\"],\n        [\":CH1:CH2:FOO\", \"0\", \"-100\", \"20\"],\n        sep=\"\\n\",\n    ) as hs:\n        channel = hs.channel[0]\n        assert channel.power == u.Quantity(0, u.dBm)\n        assert channel.power_min == u.Quantity(-100, u.dBm)\n        assert channel.power_max == u.Quantity(20, u.dBm)\n\n\ndef test_channel_power_setter():\n    with expected_protocol(\n        ik.holzworth.HS9000,\n        [\":ATTACH?\", \":CH1:PWR:MIN?\", \":CH1:PWR:MAX?\", f\":CH1:PWR {0:e}\"],\n        [\":CH1:CH2:FOO\", \"-100\", \"20\"],\n        sep=\"\\n\",\n    ) as hs:\n        channel = hs.channel[0]\n        channel.power = u.Quantity(0, u.dBm)\n\n\ndef test_channel_phase_getter():\n    with expected_protocol(\n        ik.holzworth.HS9000,\n        [\":ATTACH?\", \":CH1:PHASE?\", \":CH1:PHASE:MIN?\", \":CH1:PHASE:MAX?\"],\n        [\":CH1:CH2:FOO\", \"0\", \"-180\", \"+180\"],\n        sep=\"\\n\",\n    ) as hs:\n        channel = hs.channel[0]\n        assert channel.phase == 0 * u.degree\n        assert channel.phase_min == -180 * u.degree\n        assert channel.phase_max == 180 * u.degree\n\n\ndef test_channel_phase_setter():\n    with expected_protocol(\n        ik.holzworth.HS9000,\n        [\":ATTACH?\", \":CH1:PHASE:MIN?\", \":CH1:PHASE:MAX?\", f\":CH1:PHASE {0:e}\"],\n        [\":CH1:CH2:FOO\", \"-180\", \"+180\"],\n        sep=\"\\n\",\n    ) as hs:\n        channel = hs.channel[0]\n        channel.phase = 0 * u.degree\n\n\ndef test_channel_output():\n    with expected_protocol(\n        ik.holzworth.HS9000,\n        [\":ATTACH?\", \":CH1:PWR:RF?\", \":CH1:PWR:RF:ON\", \":CH1:PWR:RF:OFF\"],\n        [\":CH1:CH2:FOO\", \"OFF\"],\n        sep=\"\\n\",\n    ) as hs:\n        channel = hs.channel[0]\n        assert channel.output is False\n        channel.output = True\n        channel.output = False\n\n\ndef test_hs9000_is_ready():\n    with expected_protocol(\n        ik.holzworth.HS9000,\n        [\":COMM:READY?\", \":COMM:READY?\"],\n        [\"Ready\", \"DANGER DANGER\"],\n        sep=\"\\n\",\n    ) as hs:\n        assert hs.ready is True\n        assert hs.ready is False\n"
  },
  {
    "path": "tests/test_hp/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_hp/test_hp3325a.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the HP 3325a function generator\n\"\"\"\n\n# IMPORTS #####################################################################\n\nimport time\n\nimport pytest\nfrom instruments.units import ureg as u\nimport instruments as ik\nfrom tests import expected_protocol\n\n# TESTS #######################################################################\n\n# pylint: disable=protected-access\n\n\n@pytest.fixture(autouse=True)\ndef time_mock(mocker):\n    \"\"\"Mock out time to speed up.\"\"\"\n    return mocker.patch.object(time, \"sleep\", return_value=None)\n\n\ndef test_hp3325a_high_voltage():\n    with expected_protocol(\n        ik.hp.hp3325a.HP3325a,\n        [\n            \"IHV\",\n        ],\n        [\"HV0\"],\n        sep=\"\\r\\n\",\n    ) as fcngen:\n        assert not fcngen.high_voltage\n\n    with expected_protocol(\n        ik.hp.hp3325a.HP3325a,\n        [\n            \"IHV\",\n        ],\n        [\"HV1\"],\n        sep=\"\\r\\n\",\n    ) as fcngen:\n        assert fcngen.high_voltage\n\n\ndef test_hp3325a_phase():\n    with expected_protocol(\n        ik.hp.hp3325a.HP3325a,\n        [\n            \"IPH\",\n        ],\n        [\"PH10DE\"],\n        sep=\"\\r\\n\",\n    ) as fcngen:\n        assert fcngen.phase == u.Quantity(10, \"deg\")\n\n\ndef test_hp3325a_amplitude():\n    with expected_protocol(\n        ik.hp.hp3325a.HP3325a,\n        [\n            \"IAM\",\n        ],\n        [\"AM1.2VO\"],\n        sep=\"\\r\\n\",\n    ) as fcngen:\n        assert fcngen.amplitude == u.Quantity(1.2, \"V\")\n\n\ndef test_hp3325a_frequency():\n    with expected_protocol(\n        ik.hp.hp3325a.HP3325a,\n        [\n            \"IFR\",\n        ],\n        [\"FR1000.0HZ\"],\n        sep=\"\\r\\n\",\n    ) as fcngen:\n        assert fcngen.frequency == u.Quantity(1000, \"Hz\")\n\n\ndef test_hp3325a_offset():\n    with expected_protocol(\n        ik.hp.hp3325a.HP3325a,\n        [\n            \"IOF\",\n        ],\n        [\"OF0.123VO\"],\n        sep=\"\\r\\n\",\n    ) as fcngen:\n        assert fcngen.offset == u.Quantity(0.123, \"V\")\n\n\ndef test_hp3325a_commands():\n    with expected_protocol(\n        ik.hp.hp3325a.HP3325a,\n        [\"AC\", \"AP\", \"IER\"],\n        [\"ER0\"],\n        sep=\"\\r\\n\",\n    ) as fcngen:\n        fcngen.amplitude_calibration()\n        fcngen.assign_zero_phase()\n        fcngen.query_error()\n"
  },
  {
    "path": "tests/test_hp/test_hp3456a.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the HP 3456a digital voltmeter\n\"\"\"\n\n# IMPORTS #####################################################################\n\nimport time\n\nimport pytest\n\nimport instruments as ik\nfrom tests import expected_protocol\nfrom instruments.units import ureg as u\n\n# TESTS #######################################################################\n\n# pylint: disable=protected-access\n\n\n@pytest.fixture(autouse=True)\ndef time_mock(mocker):\n    \"\"\"Mock out time to speed up.\"\"\"\n    return mocker.patch.object(time, \"sleep\", return_value=None)\n\n\ndef test_hp3456a_trigger_mode():\n    with expected_protocol(\n        ik.hp.HP3456a,\n        [\n            \"HO0T4SO1\",\n            \"T4\",\n        ],\n        [\"\"],\n        sep=\"\\r\",\n    ) as dmm:\n        dmm.trigger_mode = dmm.TriggerMode.hold\n\n\ndef test_hp3456a_number_of_digits():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.hp.HP3456a, [\"HO0T4SO1\", \"W6STG\", \"REG\"], [\"+06.00000E+0\"], sep=\"\\r\"\n    ) as dmm:\n        dmm.number_of_digits = 7\n\n\ndef test_hp3456a_number_of_digits_invalid():\n    with expected_protocol(\n        ik.hp.HP3456a, [\"HO0T4SO1\", \"W6STG\", \"REG\"], [\"+06.00000E+0\"], sep=\"\\r\"\n    ) as dmm:\n        dmm.number_of_digits = 6\n        assert dmm.number_of_digits == 6\n\n\ndef test_hp3456a_auto_range():\n    with expected_protocol(\n        ik.hp.HP3456a,\n        [\n            \"HO0T4SO1\",\n            \"R1W\",\n        ],\n        [\"\"],\n        sep=\"\\r\",\n    ) as dmm:\n        dmm.auto_range()\n\n\ndef test_hp3456a_number_of_readings():\n    with expected_protocol(\n        ik.hp.HP3456a, [\"HO0T4SO1\", \"W10STN\", \"REN\"], [\"+10.00000E+0\"], sep=\"\\r\"\n    ) as dmm:\n        dmm.number_of_readings = 10\n        assert dmm.number_of_readings == 10\n\n\ndef test_hp3456a_nplc():\n    with expected_protocol(\n        ik.hp.HP3456a, [\"HO0T4SO1\", \"W1STI\", \"REI\"], [\"+1.00000E+0\"], sep=\"\\r\"\n    ) as dmm:\n        dmm.nplc = 1\n        assert dmm.nplc == 1\n\n\ndef test_hp3456a_nplc_invalid():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.hp.HP3456a, [\"HO0T4SO1\", \"W1STI\", \"REI\"], [\"+1.00000E+0\"], sep=\"\\r\"\n    ) as dmm:\n        dmm.nplc = 0\n\n\ndef test_hp3456a_mode():\n    with expected_protocol(\n        ik.hp.HP3456a,\n        [\n            \"HO0T4SO1\",\n            \"S0F4\",\n        ],\n        [\"\"],\n        sep=\"\\r\",\n    ) as dmm:\n        dmm.mode = dmm.Mode.resistance_2wire\n\n\ndef test_hp3456a_math_mode():\n    with expected_protocol(\n        ik.hp.HP3456a,\n        [\n            \"HO0T4SO1\",\n            \"M2\",\n        ],\n        [\"\"],\n        sep=\"\\r\",\n    ) as dmm:\n        dmm.math_mode = dmm.MathMode.statistic\n\n\ndef test_hp3456a_trigger():\n    with expected_protocol(\n        ik.hp.HP3456a,\n        [\n            \"HO0T4SO1\",\n            \"T3\",\n        ],\n        [\"\"],\n        sep=\"\\r\",\n    ) as dmm:\n        dmm.trigger()\n\n\ndef test_hp3456a_fetch():\n    with expected_protocol(\n        ik.hp.HP3456a,\n        [\"HO0T4SO1\"],\n        [\n            \"+000.1055E+0,+000.1043E+0,+000.1005E+0,+000.1014E+0\",\n            \"+000.1055E+0,+000.1043E+0,+000.1005E+0,+000.1014E+0\",\n        ],\n        sep=\"\\r\",\n    ) as dmm:\n        v = dmm.fetch(dmm.Mode.resistance_2wire)\n        assert v == [0.1055 * u.ohm, 0.1043 * u.ohm, 0.1005 * u.ohm, 0.1014 * u.ohm]\n        v = dmm.fetch()\n        assert v == [0.1055, 0.1043, 0.1005, 0.1014]\n\n\ndef test_hp3456a_variance():\n    with expected_protocol(\n        ik.hp.HP3456a,\n        [\n            \"HO0T4SO1\",\n            \"REV\",\n        ],\n        [\"+04.93111E-6\"],\n        sep=\"\\r\",\n    ) as dmm:\n        assert dmm.variance == +04.93111e-6\n\n\ndef test_hp3456a_count():\n    with expected_protocol(\n        ik.hp.HP3456a,\n        [\n            \"HO0T4SO1\",\n            \"REC\",\n        ],\n        [\"+10.00000E+0\"],\n        sep=\"\\r\",\n    ) as dmm:\n        assert dmm.count == +10\n\n\ndef test_hp3456a_mean():\n    with expected_protocol(\n        ik.hp.HP3456a,\n        [\n            \"HO0T4SO1\",\n            \"REM\",\n        ],\n        [\"+102.1000E-3\"],\n        sep=\"\\r\",\n    ) as dmm:\n        assert dmm.mean == +102.1000e-3\n\n\ndef test_hp3456a_delay():\n    with expected_protocol(\n        ik.hp.HP3456a, [\"HO0T4SO1\", \"RED\", \"W1STD\"], [\"-000.0000E+0\"], sep=\"\\r\"\n    ) as dmm:\n        assert dmm.delay == 0\n        dmm.delay = 1 * u.sec\n\n\ndef test_hp3456a_lower():\n    with expected_protocol(\n        ik.hp.HP3456a, [\"HO0T4SO1\", \"REL\", \"W0.0993STL\"], [\"+099.3000E-3\"], sep=\"\\r\"\n    ) as dmm:\n        assert dmm.lower == +099.3000e-3\n        dmm.lower = +099.3000e-3\n\n\ndef test_hp3456a_upper():\n    with expected_protocol(\n        ik.hp.HP3456a, [\"HO0T4SO1\", \"REU\", \"W0.1055STU\"], [\"+105.5000E-3\"], sep=\"\\r\"\n    ) as dmm:\n        assert dmm.upper == +105.5000e-3\n        dmm.upper = +105.5000e-3\n\n\ndef test_hp3456a_ryz():\n    with expected_protocol(\n        ik.hp.HP3456a,\n        [\"HO0T4SO1\", \"RER\", \"REY\", \"REZ\", \"W600.0STR\", \"W1.0STY\", \"W0.1055STZ\"],\n        [\"+0600.000E+0\", \"+1.000000E+0\", \"+105.5000E-3\"],\n        sep=\"\\r\",\n    ) as dmm:\n        assert dmm.r == +0600.000e0\n        assert dmm.y == +1.000000e0\n        assert dmm.z == +105.5000e-3\n        dmm.r = +0600.000e0\n        dmm.y = +1.000000e0\n        dmm.z = +105.5000e-3\n\n\ndef test_hp3456a_measure():\n    with expected_protocol(\n        ik.hp.HP3456a,\n        [\"HO0T4SO1\", \"S1F1W1STNT3\", \"S0F4W1STNT3\", \"S0F1W1STNT3\", \"W1STNT3\"],\n        [\"+00.00000E-3\", \"+000.1010E+0\", \"+000.0002E-3\", \"+000.0002E-3\"],\n        sep=\"\\r\",\n    ) as dmm:\n        assert dmm.measure(dmm.Mode.ratio_dcv_dcv) == 0\n        assert dmm.measure(dmm.Mode.resistance_2wire) == +000.1010e0 * u.ohm\n        assert dmm.measure(dmm.Mode.dcv) == +000.0002e-3 * u.volt\n        assert dmm.measure() == +000.0002e-3\n\n\ndef test_hp3456a_input_range():\n    with expected_protocol(\n        ik.hp.HP3456a, [\"HO0T4SO1\", \"R2W\", \"R3W\"], [\"\"], sep=\"\\r\"\n    ) as dmm:\n        dmm.input_range = 10**-1 * u.volt\n        dmm.input_range = 1e3 * u.ohm\n        with pytest.raises(NotImplementedError):\n            _ = dmm.input_range\n\n\ndef test_hp3456a_input_range_invalid_str():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.hp.HP3456a, [], [], sep=\"\\r\"\n    ) as dmm:\n        dmm.input_range = \"derp\"\n\n\ndef test_hp3456a_input_range_invalid_range():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.hp.HP3456a, [], [], sep=\"\\r\"\n    ) as dmm:\n        dmm.input_range = 1 * u.ohm\n\n\ndef test_hp3456a_input_range_bad_type():\n    with pytest.raises(TypeError), expected_protocol(\n        ik.hp.HP3456a, [], [], sep=\"\\r\"\n    ) as dmm:\n        dmm.input_range = True\n\n\ndef test_hp3456a_input_range_bad_units():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.hp.HP3456a, [], [], sep=\"\\r\"\n    ) as dmm:\n        dmm.input_range = 1 * u.amp\n\n\ndef test_hp3456a_relative():\n    with expected_protocol(\n        ik.hp.HP3456a,\n        [\n            \"HO0T4SO1\",\n            \"M0\",\n            \"M3\",\n        ],\n        [\n            \"\",\n        ],\n        sep=\"\\r\",\n    ) as dmm:\n        dmm.relative = False\n        dmm.relative = True\n        assert dmm.relative is True\n\n\ndef test_hp3456a_relative_bad_type():\n    with pytest.raises(TypeError), expected_protocol(\n        ik.hp.HP3456a, [], [], sep=\"\\r\"\n    ) as dmm:\n        dmm.relative = \"derp\"\n\n\ndef test_hp3456a_auto_zero():\n    with expected_protocol(\n        ik.hp.HP3456a,\n        [\n            \"HO0T4SO1\",\n            \"Z0\",\n            \"Z1\",\n        ],\n        [\n            \"\",\n        ],\n        sep=\"\\r\",\n    ) as dmm:\n        dmm.autozero = False\n        dmm.autozero = True\n\n\ndef test_hp3456a_filter():\n    with expected_protocol(\n        ik.hp.HP3456a,\n        [\n            \"HO0T4SO1\",\n            \"FL0\",\n            \"FL1\",\n        ],\n        [\n            \"\",\n        ],\n        sep=\"\\r\",\n    ) as dmm:\n        dmm.filter = False\n        dmm.filter = True\n\n\ndef test_hp3456a_register_read_bad_name():\n    with pytest.raises(TypeError), expected_protocol(\n        ik.hp.HP3456a, [], [], sep=\"\\r\"\n    ) as dmm:\n        dmm._register_read(\"foobar\")\n\n\ndef test_hp3456a_register_write_bad_name():\n    with pytest.raises(TypeError), expected_protocol(\n        ik.hp.HP3456a, [], [], sep=\"\\r\"\n    ) as dmm:\n        dmm._register_write(\"foobar\", 1)\n\n\ndef test_hp3456a_register_write_bad_register():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.hp.HP3456a, [], [], sep=\"\\r\"\n    ) as dmm:\n        dmm._register_write(dmm.Register.mean, 1)\n"
  },
  {
    "path": "tests/test_hp/test_hp6624a.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the HP 6624a power supply\n\"\"\"\n\n# IMPORTS #####################################################################\n\nimport pytest\n\nimport instruments as ik\nfrom tests import (\n    expected_protocol,\n    iterable_eq,\n)\nfrom instruments.units import ureg as u\nfrom .. import mock\n\n# TESTS #######################################################################\n\n# pylint: disable=protected-access\n\n\ndef test_channel_returns_inner_class():\n    with expected_protocol(ik.hp.HP6624a, [], [], sep=\"\\n\") as hp:\n        channel = hp.channel[0]\n        assert isinstance(channel, hp.Channel) is True\n        assert channel._idx == 1\n\n\ndef test_channel_sendcmd():\n    channel = ik.hp.HP6624a.Channel(mock.MagicMock(), 0)\n\n    channel.sendcmd(\"FOO\")\n\n    channel._hp.sendcmd.assert_called_with(\"FOO 1\")\n\n\ndef test_channel_sendcmd_2():\n    channel = ik.hp.HP6624a.Channel(mock.MagicMock(), 0)\n\n    channel.sendcmd(\"FOO 5\")\n\n    channel._hp.sendcmd.assert_called_with(\"FOO 1,5\")\n\n\ndef test_channel_query():\n    channel = ik.hp.HP6624a.Channel(mock.MagicMock(), 0)\n    channel._hp.query.return_value = \"FOO\"\n\n    value = channel.query(\"BAR?\")\n\n    channel._hp.query.assert_called_with(\"BAR? 1\")\n    assert value == \"FOO\"\n\n\ndef test_mode():\n    \"\"\"Raise NotImplementedError when mode is called.\"\"\"\n    with expected_protocol(ik.hp.HP6624a, [], [], sep=\"\\n\") as hp:\n        channel = hp.channel[0]\n        with pytest.raises(NotImplementedError):\n            _ = channel.mode\n        with pytest.raises(NotImplementedError):\n            channel.mode = 42\n\n\ndef test_channel_voltage():\n    with expected_protocol(\n        ik.hp.HP6624a, [\"VSET? 1\", f\"VSET 1,{5:.1f}\"], [\"2\"], sep=\"\\n\"\n    ) as hp:\n        assert hp.channel[0].voltage == 2 * u.V\n        hp.channel[0].voltage = 5 * u.V\n\n\ndef test_channel_current():\n    with expected_protocol(\n        ik.hp.HP6624a, [\"ISET? 1\", f\"ISET 1,{5:.1f}\"], [\"2\"], sep=\"\\n\"\n    ) as hp:\n        assert hp.channel[0].current == 2 * u.amp\n        hp.channel[0].current = 5 * u.amp\n\n\ndef test_channel_voltage_sense():\n    with expected_protocol(ik.hp.HP6624a, [\"VOUT? 1\"], [\"2\"], sep=\"\\n\") as hp:\n        assert hp.channel[0].voltage_sense == 2 * u.V\n\n\ndef test_channel_current_sense():\n    with expected_protocol(\n        ik.hp.HP6624a,\n        [\n            \"IOUT? 1\",\n        ],\n        [\"2\"],\n        sep=\"\\n\",\n    ) as hp:\n        assert hp.channel[0].current_sense == 2 * u.A\n\n\ndef test_channel_overvoltage():\n    with expected_protocol(\n        ik.hp.HP6624a, [\"OVSET? 1\", f\"OVSET 1,{5:.1f}\"], [\"2\"], sep=\"\\n\"\n    ) as hp:\n        assert hp.channel[0].overvoltage == 2 * u.V\n        hp.channel[0].overvoltage = 5 * u.V\n\n\ndef test_channel_overcurrent():\n    with expected_protocol(ik.hp.HP6624a, [\"OVP? 1\", \"OVP 1,1\"], [\"1\"], sep=\"\\n\") as hp:\n        assert hp.channel[0].overcurrent is True\n        hp.channel[0].overcurrent = True\n\n\ndef test_channel_output():\n    with expected_protocol(ik.hp.HP6624a, [\"OUT? 1\", \"OUT 1,1\"], [\"1\"], sep=\"\\n\") as hp:\n        assert hp.channel[0].output is True\n        hp.channel[0].output = True\n\n\ndef test_channel_reset():\n    channel = ik.hp.HP6624a.Channel(mock.MagicMock(), 0)\n    channel.reset()\n\n    calls = [mock.call(\"OVRST 1\"), mock.call(\"OCRST 1\")]\n    channel._hp.sendcmd.assert_has_calls(calls)\n\n\ndef test_all_voltage():\n    with expected_protocol(\n        ik.hp.HP6624a,\n        [\n            \"VSET? 1\",\n            \"VSET? 2\",\n            \"VSET? 3\",\n            \"VSET? 4\",\n            f\"VSET 1,{5:.1f}\",\n            f\"VSET 2,{5:.1f}\",\n            f\"VSET 3,{5:.1f}\",\n            f\"VSET 4,{5:.1f}\",\n            f\"VSET 1,{1:.1f}\",\n            f\"VSET 2,{2:.1f}\",\n            f\"VSET 3,{3:.1f}\",\n            f\"VSET 4,{4:.1f}\",\n        ],\n        [\"2\", \"3\", \"4\", \"5\"],\n        sep=\"\\n\",\n    ) as hp:\n        expected = (2 * u.V, 3 * u.V, 4 * u.V, 5 * u.V)\n        iterable_eq(hp.voltage, expected)\n        hp.voltage = 5 * u.V\n        hp.voltage = (1 * u.V, 2 * u.V, 3 * u.V, 4 * u.V)\n\n\ndef test_all_voltage_wrong_length():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.hp.HP6624a, [], [], sep=\"\\n\"\n    ) as hp:\n        hp.voltage = (1 * u.volt, 2 * u.volt)\n\n\ndef test_all_current():\n    with expected_protocol(\n        ik.hp.HP6624a,\n        [\n            \"ISET? 1\",\n            \"ISET? 2\",\n            \"ISET? 3\",\n            \"ISET? 4\",\n            f\"ISET 1,{5:.1f}\",\n            f\"ISET 2,{5:.1f}\",\n            f\"ISET 3,{5:.1f}\",\n            f\"ISET 4,{5:.1f}\",\n            f\"ISET 1,{1:.1f}\",\n            f\"ISET 2,{2:.1f}\",\n            f\"ISET 3,{3:.1f}\",\n            f\"ISET 4,{4:.1f}\",\n        ],\n        [\"2\", \"3\", \"4\", \"5\"],\n        sep=\"\\n\",\n    ) as hp:\n        expected = (2 * u.A, 3 * u.A, 4 * u.A, 5 * u.A)\n        iterable_eq(hp.current, expected)\n        hp.current = 5 * u.A\n        hp.current = (1 * u.A, 2 * u.A, 3 * u.A, 4 * u.A)\n\n\ndef test_all_current_wrong_length():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.hp.HP6624a, [], [], sep=\"\\n\"\n    ) as hp:\n        hp.current = (1 * u.amp, 2 * u.amp)\n\n\ndef test_all_voltage_sense():\n    with expected_protocol(\n        ik.hp.HP6624a,\n        [\"VOUT? 1\", \"VOUT? 2\", \"VOUT? 3\", \"VOUT? 4\"],\n        [\"2\", \"3\", \"4\", \"5\"],\n        sep=\"\\n\",\n    ) as hp:\n        expected = (2 * u.V, 3 * u.V, 4 * u.V, 5 * u.V)\n        iterable_eq(hp.voltage_sense, expected)\n\n\ndef test_all_current_sense():\n    with expected_protocol(\n        ik.hp.HP6624a,\n        [\"IOUT? 1\", \"IOUT? 2\", \"IOUT? 3\", \"IOUT? 4\"],\n        [\"2\", \"3\", \"4\", \"5\"],\n        sep=\"\\n\",\n    ) as hp:\n        expected = (2 * u.A, 3 * u.A, 4 * u.A, 5 * u.A)\n        iterable_eq(hp.current_sense, expected)\n\n\ndef test_clear():\n    with expected_protocol(ik.hp.HP6624a, [\"CLR\"], [], sep=\"\\n\") as hp:\n        hp.clear()\n\n\ndef test_channel_count():\n    with expected_protocol(ik.hp.HP6624a, [], [], sep=\"\\n\") as hp:\n        assert hp.channel_count == 4\n        hp.channel_count = 3\n\n\ndef test_channel_count_wrong_type():\n    with pytest.raises(TypeError), expected_protocol(\n        ik.hp.HP6624a, [], [], sep=\"\\n\"\n    ) as hp:\n        hp.channel_count = \"foobar\"\n\n\ndef test_channel_count_too_small():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.hp.HP6624a, [], [], sep=\"\\n\"\n    ) as hp:\n        hp.channel_count = 0\n"
  },
  {
    "path": "tests/test_hp/test_hp6632b.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the HP 6632b power supply\n\"\"\"\n\n# IMPORTS #####################################################################\n\nimport pytest\n\nfrom instruments.units import ureg as u\n\nimport instruments as ik\nfrom tests import expected_protocol, make_name_test, unit_eq\n\n# TESTS #######################################################################\n\ntest_scpi_multimeter_name = make_name_test(ik.hp.HP6632b)\n\n\ndef test_hp6632b_display_textmode():\n    with expected_protocol(\n        ik.hp.HP6632b, [\"DISP:MODE?\", \"DISP:MODE TEXT\"], [\"NORM\"]\n    ) as psu:\n        assert psu.display_textmode is False\n        psu.display_textmode = True\n\n\ndef test_hp6632b_display_text():\n    with expected_protocol(\n        ik.hp.HP6632b, ['DISP:TEXT \"TEST\"', 'DISP:TEXT \"TEST AAAAAAAAAA\"'], []\n    ) as psu:\n        assert psu.display_text(\"TEST\") == \"TEST\"\n        assert psu.display_text(\"TEST AAAAAAAAAAAAAAAA\") == \"TEST AAAAAAAAAA\"\n\n\ndef test_hp6632b_output():\n    with expected_protocol(ik.hp.HP6632b, [\"OUTP?\", \"OUTP 1\"], [\"0\"]) as psu:\n        assert psu.output is False\n        psu.output = True\n\n\ndef test_hp6632b_voltage():\n    with expected_protocol(ik.hp.HP6632b, [\"VOLT?\", f\"VOLT {1:e}\"], [\"10.0\"]) as psu:\n        unit_eq(psu.voltage, 10 * u.volt)\n        psu.voltage = 1.0 * u.volt\n\n\ndef test_hp6632b_voltage_sense():\n    with expected_protocol(\n        ik.hp.HP6632b,\n        [\n            \"MEAS:VOLT?\",\n        ],\n        [\"10.0\"],\n    ) as psu:\n        unit_eq(psu.voltage_sense, 10 * u.volt)\n\n\ndef test_hp6632b_overvoltage():\n    with expected_protocol(\n        ik.hp.HP6632b, [\"VOLT:PROT?\", f\"VOLT:PROT {1:e}\"], [\"10.0\"]\n    ) as psu:\n        unit_eq(psu.overvoltage, 10 * u.volt)\n        psu.overvoltage = 1.0 * u.volt\n\n\ndef test_hp6632b_current():\n    with expected_protocol(ik.hp.HP6632b, [\"CURR?\", f\"CURR {1:e}\"], [\"10.0\"]) as psu:\n        unit_eq(psu.current, 10 * u.amp)\n        psu.current = 1.0 * u.amp\n\n\ndef test_hp6632b_current_sense():\n    with expected_protocol(\n        ik.hp.HP6632b,\n        [\n            \"MEAS:CURR?\",\n        ],\n        [\"10.0\"],\n    ) as psu:\n        unit_eq(psu.current_sense, 10 * u.amp)\n\n\ndef test_hp6632b_overcurrent():\n    with expected_protocol(\n        ik.hp.HP6632b, [\"CURR:PROT:STAT?\", \"CURR:PROT:STAT 1\"], [\"0\"]\n    ) as psu:\n        assert psu.overcurrent is False\n        psu.overcurrent = True\n\n\ndef test_hp6632b_current_sense_range():\n    with expected_protocol(\n        ik.hp.HP6632b, [\"SENS:CURR:RANGE?\", f\"SENS:CURR:RANGE {1:e}\"], [\"0.05\"]\n    ) as psu:\n        unit_eq(psu.current_sense_range, 0.05 * u.amp)\n        psu.current_sense_range = 1 * u.amp\n\n\ndef test_hp6632b_output_dfi_source():\n    with expected_protocol(\n        ik.hp.HP6632b, [\"OUTP:DFI:SOUR?\", \"OUTP:DFI:SOUR QUES\"], [\"OPER\"]\n    ) as psu:\n        assert psu.output_dfi_source == psu.DFISource.operation\n        psu.output_dfi_source = psu.DFISource.questionable\n\n\ndef test_hp6632b_output_remote_inhibit():\n    with expected_protocol(\n        ik.hp.HP6632b, [\"OUTP:RI:MODE?\", \"OUTP:RI:MODE LATC\"], [\"LIVE\"]\n    ) as psu:\n        assert psu.output_remote_inhibit == psu.RemoteInhibit.live\n        psu.output_remote_inhibit = psu.RemoteInhibit.latching\n\n\ndef test_hp6632b_digital_function():\n    with expected_protocol(\n        ik.hp.HP6632b, [\"DIG:FUNC?\", \"DIG:FUNC DIG\"], [\"RIDF\"]\n    ) as psu:\n        assert psu.digital_function == psu.DigitalFunction.remote_inhibit\n        psu.digital_function = psu.DigitalFunction.data\n\n\ndef test_hp6632b_digital_data():\n    with expected_protocol(ik.hp.HP6632b, [\"DIG:DATA?\", \"DIG:DATA 1\"], [\"5\"]) as psu:\n        assert psu.digital_data == 5\n        psu.digital_data = 1\n\n\ndef test_hp6632b_sense_sweep_points():\n    with expected_protocol(\n        ik.hp.HP6632b, [\"SENS:SWE:POIN?\", f\"SENS:SWE:POIN {2048:e}\"], [\"5\"]\n    ) as psu:\n        assert psu.sense_sweep_points == 5\n        psu.sense_sweep_points = 2048\n\n\ndef test_hp6632b_sense_sweep_interval():\n    with expected_protocol(\n        ik.hp.HP6632b,\n        [\"SENS:SWE:TINT?\", f\"SENS:SWE:TINT {1e-05:e}\"],\n        [\"1.56e-05\"],\n    ) as psu:\n        unit_eq(psu.sense_sweep_interval, 1.56e-05 * u.second)\n        psu.sense_sweep_interval = 1e-05 * u.second\n\n\ndef test_hp6632b_sense_window():\n    with expected_protocol(\n        ik.hp.HP6632b, [\"SENS:WIND?\", \"SENS:WIND RECT\"], [\"HANN\"]\n    ) as psu:\n        assert psu.sense_window == psu.SenseWindow.hanning\n        psu.sense_window = psu.SenseWindow.rectangular\n\n\ndef test_hp6632b_output_protection_delay():\n    with expected_protocol(\n        ik.hp.HP6632b, [\"OUTP:PROT:DEL?\", f\"OUTP:PROT:DEL {5e-02:e}\"], [\"8e-02\"]\n    ) as psu:\n        unit_eq(psu.output_protection_delay, 8e-02 * u.second)\n        psu.output_protection_delay = 5e-02 * u.second\n\n\ndef test_hp6632b_voltage_alc_bandwidth():\n    with expected_protocol(\n        ik.hp.HP6632b,\n        [\n            \"VOLT:ALC:BAND?\",\n        ],\n        [\"6e4\"],\n    ) as psu:\n        assert psu.voltage_alc_bandwidth == psu.ALCBandwidth.fast\n\n\ndef test_hp6632b_voltage_trigger():\n    with expected_protocol(\n        ik.hp.HP6632b, [\"VOLT:TRIG?\", f\"VOLT:TRIG {1:e}\"], [\"1e+0\"]\n    ) as psu:\n        unit_eq(psu.voltage_trigger, 1 * u.volt)\n        psu.voltage_trigger = 1 * u.volt\n\n\ndef test_hp6632b_current_trigger():\n    with expected_protocol(\n        ik.hp.HP6632b, [\"CURR:TRIG?\", f\"CURR:TRIG {0.1:e}\"], [\"1e-01\"]\n    ) as psu:\n        unit_eq(psu.current_trigger, 0.1 * u.amp)\n        psu.current_trigger = 0.1 * u.amp\n\n\ndef test_hp6632b_init_output_trigger():\n    with expected_protocol(\n        ik.hp.HP6632b,\n        [\n            \"INIT:NAME TRAN\",\n        ],\n        [],\n    ) as psu:\n        psu.init_output_trigger()\n\n\ndef test_hp6632b_abort_output_trigger():\n    with expected_protocol(\n        ik.hp.HP6632b,\n        [\n            \"ABORT\",\n        ],\n        [],\n    ) as psu:\n        psu.abort_output_trigger()\n\n\ndef test_line_frequency():\n    \"\"\"Raise NotImplemented error when called.\"\"\"\n    with expected_protocol(ik.hp.HP6632b, [], []) as psu:\n        with pytest.raises(NotImplementedError):\n            psu.line_frequency = 42\n        with pytest.raises(NotImplementedError):\n            _ = psu.line_frequency\n\n\ndef test_display_brightness():\n    \"\"\"Raise NotImplemented error when called.\"\"\"\n    with expected_protocol(ik.hp.HP6632b, [], []) as psu:\n        with pytest.raises(NotImplementedError):\n            psu.display_brightness = 42\n        with pytest.raises(NotImplementedError):\n            _ = psu.display_brightness\n\n\ndef test_display_contrast():\n    \"\"\"Raise NotImplemented error when called.\"\"\"\n    with expected_protocol(ik.hp.HP6632b, [], []) as psu:\n        with pytest.raises(NotImplementedError):\n            psu.display_contrast = 42\n        with pytest.raises(NotImplementedError):\n            _ = psu.display_contrast\n\n\ndef test_hp6632b_check_error_queue():\n    with expected_protocol(\n        ik.hp.HP6632b,\n        [\n            \"SYST:ERR?\",\n            \"SYST:ERR?\",\n        ],\n        ['-222,\"Data out of range\"', '+0,\"No error\"'],\n    ) as psu:\n        err_queue = psu.check_error_queue()\n        assert err_queue == [psu.ErrorCodes.data_out_of_range], f\"got {err_queue}\"\n"
  },
  {
    "path": "tests/test_hp/test_hp6652a.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the HP 6652a single output power supply\n\"\"\"\n\n# IMPORTS #####################################################################\n\nimport pytest\n\nimport instruments as ik\nfrom tests import expected_protocol\n\n# TESTS #######################################################################\n\n\ndef test_name():\n    with expected_protocol(\n        ik.hp.HP6652a, [\"*IDN?\"], [\"FOO,BAR,AAA,BBBB\"], sep=\"\\n\"\n    ) as hp:\n        assert hp.name == \"FOO BAR\"\n\n\ndef test_mode():\n    \"\"\"Raise NotImplementedError when called.\"\"\"\n    with expected_protocol(ik.hp.HP6652a, [], [], sep=\"\\n\") as hp:\n        with pytest.raises(NotImplementedError):\n            _ = hp.mode\n        with pytest.raises(NotImplementedError):\n            hp.mode = 42\n\n\ndef test_reset():\n    with expected_protocol(ik.hp.HP6652a, [\"OUTP:PROT:CLE\"], [], sep=\"\\n\") as hp:\n        hp.reset()\n\n\ndef test_display_text():\n    with expected_protocol(\n        ik.hp.HP6652a, ['DISP:TEXT \"TEST\"', 'DISP:TEXT \"TEST AAAAAAAAAA\"'], []\n    ) as psu:\n        assert psu.display_text(\"TEST\") == \"TEST\"\n        assert psu.display_text(\"TEST AAAAAAAAAAAAAAAA\") == \"TEST AAAAAAAAAA\"\n\n\ndef test_channel():\n    with expected_protocol(ik.hp.HP6652a, [], [], sep=\"\\n\") as hp:\n        assert hp.channel[0] == hp\n        assert len(hp.channel) == 1\n"
  },
  {
    "path": "tests/test_hp/test_hpe3631a.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the HP E3631A power supply\n\"\"\"\n\n# IMPORTS #####################################################################\n\nimport time\n\nimport pytest\n\nfrom instruments.units import ureg as u\n\nimport instruments as ik\nfrom tests import expected_protocol\n\n# TESTS #######################################################################\n\n\n@pytest.fixture(autouse=True)\ndef time_mock(mocker):\n    \"\"\"Mock out time such that the tests go faster.\"\"\"\n    return mocker.patch.object(time, \"sleep\", return_value=None)\n\n\ndef test_channel():\n    with expected_protocol(\n        ik.hp.HPe3631a,\n        [\"SYST:REM\", \"INST:NSEL?\", \"INST:NSEL?\", \"INST:NSEL 2\", \"INST:NSEL?\"],\n        [\"1\", \"1\", \"2\"],\n    ) as inst:\n        assert inst.channelid == 1\n        assert inst.channel[2] == inst\n        assert inst.channelid == 2\n        assert inst.channel.__len__() == len([1, 2, 3])  # len of valild set\n\n\ndef test_channelid():\n    with expected_protocol(\n        ik.hp.HPe3631a,\n        [\"SYST:REM\", \"INST:NSEL?\", \"INST:NSEL 2\", \"INST:NSEL?\"],  # 0  # 1  # 2  # 3\n        [\"1\", \"2\"],  # 1  # 3\n    ) as inst:\n        assert inst.channelid == 1\n        inst.channelid = 2\n        assert inst.channelid == 2\n\n\ndef test_mode():\n    \"\"\"Raise AttributeError since instrument sets mode automatically.\"\"\"\n    with expected_protocol(ik.hp.HPe3631a, [\"SYST:REM\"], []) as inst:\n        with pytest.raises(AttributeError) as err_info:\n            _ = inst.mode()\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"The `HPe3631a` sets its mode automatically\"\n\n\ndef test_voltage():\n    with expected_protocol(\n        ik.hp.HPe3631a,\n        [\n            \"SYST:REM\",  # 0\n            \"SOUR:VOLT? MAX\",  # 1\n            \"SOUR:VOLT? MAX\",  # 2\n            \"SOUR:VOLT? MAX\",  # 3.1\n            \"SOUR:VOLT 3.000000e+00\",  # 3.2\n            \"SOUR:VOLT?\",  # 4\n            \"SOUR:VOLT? MAX\",  # 5\n            \"SOUR:VOLT? MAX\",  # 6\n        ],\n        [\"6.0\", \"6.0\", \"6.0\", \"3.0\", \"6.0\", \"6.0\"],  # 1  # 2  # 3.1  # 4  # 5  # 6\n    ) as inst:\n        assert inst.voltage_min == 0.0 * u.volt\n        assert inst.voltage_max == 6.0 * u.volt\n        inst.voltage = 3.0 * u.volt\n        assert inst.voltage == 3.0 * u.volt\n        with pytest.raises(ValueError) as err_info:\n            newval = -1.0 * u.volt\n            inst.voltage = newval\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Voltage quantity is too low. Got {newval}, \"\n            f\"minimum value is {0.}\"\n        )\n        with pytest.raises(ValueError) as err_info:\n            newval = 7.0 * u.volt\n            inst.voltage = newval\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Voltage quantity is too high. Got {newval}, \"\n            f\"maximum value is {u.Quantity(6.0, u.V)}\"\n        )\n\n\ndef test_voltage_range_negative():\n    \"\"\"Get voltage max if negative.\"\"\"\n    max_volts = -6.0\n    with expected_protocol(\n        ik.hp.HPe3631a,\n        [\"SYST:REM\", \"SOUR:VOLT? MAX\"],  # 0  # 1\n        [\n            f\"{max_volts}\",  # 1\n        ],\n    ) as inst:\n        expected_value = u.Quantity(max_volts, u.V), 0.0\n        received_value = inst.voltage_range\n        assert expected_value == received_value\n\n\ndef test_current():\n    with expected_protocol(\n        ik.hp.HPe3631a,\n        [\n            \"SYST:REM\",  # 0\n            \"SOUR:CURR? MIN\",  # 1.1\n            \"SOUR:CURR? MAX\",  # 1.2\n            \"SOUR:CURR? MIN\",  # 2.1\n            \"SOUR:CURR? MAX\",  # 2.2\n            \"SOUR:CURR 2.000000e+00\",  # 3\n            \"SOUR:CURR?\",  # 4\n            \"SOUR:CURR? MIN\",  # 5\n            \"SOUR:CURR? MIN\",  # 6.1\n            \"SOUR:CURR? MAX\",  # 6.2\n        ],\n        [\n            \"0.0\",  # 1.1\n            \"5.0\",  # 1.2\n            \"0.0\",  # 2.1\n            \"5.0\",  # 2.2\n            \"2.0\",  # 4\n            \"0.0\",  # 5\n            \"0.0\",  # 6.1\n            \"5.0\",  # 6.2\n        ],\n    ) as inst:\n        assert inst.current_min == 0.0 * u.amp\n        assert inst.current_max == 5.0 * u.amp\n        inst.current = 2.0 * u.amp\n        assert inst.current == 2.0 * u.amp\n        try:\n            inst.current = -1.0 * u.amp\n        except ValueError:\n            pass\n        try:\n            inst.current = 6.0 * u.amp\n        except ValueError:\n            pass\n\n\ndef test_voltage_sense():\n    with expected_protocol(\n        ik.hp.HPe3631a, [\"SYST:REM\", \"MEAS:VOLT?\"], [\"1.234\"]  # 0  # 1  # 1\n    ) as inst:\n        assert inst.voltage_sense == 1.234 * u.volt\n\n\ndef test_current_sense():\n    with expected_protocol(\n        ik.hp.HPe3631a, [\"SYST:REM\", \"MEAS:CURR?\"], [\"1.234\"]  # 0  # 1  # 1\n    ) as inst:\n        assert inst.current_sense == 1.234 * u.amp\n"
  },
  {
    "path": "tests/test_keithley/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_keithley/test_keithley195.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the Keithley 195 digital multimeter.\n\"\"\"\n\n# IMPORTS ####################################################################\n\nimport struct\nimport time\n\nfrom hypothesis import (\n    given,\n    strategies as st,\n)\nimport pytest\n\nimport instruments as ik\nfrom tests import expected_protocol\nfrom instruments.units import ureg as u\n\n# TESTS ######################################################################\n\n\n# pylint: disable=redefined-outer-name\n\n\n# PYTEST FIXTURES FOR INITIALIZATION #\n\n\n@pytest.fixture(scope=\"session\")\ndef init():\n    \"\"\"Returns the initialization command that is sent to instrument.\"\"\"\n    return \"YX\\nG1DX\"\n\n\n@pytest.fixture(scope=\"session\")\ndef statusword():\n    \"\"\"Return a standard statusword for the status of the instrument.\"\"\"\n    trigger = b\"1\"  # talk_one_shot\n    mode = b\"2\"  # resistance\n    range = b\"3\"  # 2kOhm in resistance mode\n    eoi = b\"1\"  # disabled\n    buffer = b\"3\"  # reading done, currently unused\n    rate = b\"5\"  # Line cycle integration\n    srqmode = b\"0\"  # disabled\n    relative = b\"1\"  # relative mode is activated\n    delay = b\"0\"  # no delay, currently unused\n    multiplex = b\"0\"  # multiplex enabled\n    selftest = b\"2\"  # self test successful, currently unused\n    dataformat = b\"1\"  # Readings without prefix/suffix.\n    datacontrol = b\"0\"  # Readings without prefix/suffix.\n    filter = b\"0\"  # filter disabled\n    terminator = b\"1\"\n\n    statusword_p1 = b\"195 \"  # sends a space after 195!\n    statusword_p2 = struct.pack(\n        \"@4c2s3c2s5c2s\",\n        trigger,\n        mode,\n        range,\n        eoi,\n        buffer,\n        rate,\n        srqmode,\n        relative,\n        delay,\n        multiplex,\n        selftest,\n        dataformat,\n        datacontrol,\n        filter,\n        terminator,\n    )\n    return statusword_p1 + statusword_p2\n\n\n# TEST INSTRUMENT #\n\n\ndef test_keithley195_mode(init, statusword):\n    \"\"\"Get / set the measurement mode.\"\"\"\n    with expected_protocol(\n        ik.keithley.Keithley195, [init, \"F2DX\", \"U0DX\"], [statusword], sep=\"\\n\"\n    ) as mul:\n        mul.mode = mul.Mode.resistance\n        assert mul.mode == mul.Mode.resistance\n\n\ndef test_keithley195_mode_string(init, statusword):\n    \"\"\"Get / set the measurement mode using a string.\"\"\"\n    with expected_protocol(\n        ik.keithley.Keithley195, [init, \"F2DX\", \"U0DX\"], [statusword], sep=\"\\n\"\n    ) as mul:\n        mul.mode = \"resistance\"\n        assert mul.mode == mul.Mode.resistance\n\n\ndef test_keithley195_mode_type_error(init):\n    \"\"\"Raise type error when setting the mode with the wrong type.\"\"\"\n    wrong_type = 42\n    with expected_protocol(ik.keithley.Keithley195, [init], [], sep=\"\\n\") as mul:\n        with pytest.raises(TypeError) as err_info:\n            mul.mode = wrong_type\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Mode must be specified as a Keithley195.Mode \"\n            f\"value, got {wrong_type} instead.\"\n        )\n\n\ndef test_keithley195_trigger_mode(init, statusword):\n    \"\"\"Get / set the trigger mode.\"\"\"\n    with expected_protocol(\n        ik.keithley.Keithley195, [init, \"T1X\", \"U0DX\"], [statusword], sep=\"\\n\"\n    ) as mul:\n        mul.trigger_mode = mul.TriggerMode.talk_one_shot\n        assert mul.trigger_mode == mul.TriggerMode.talk_one_shot\n\n\ndef test_keithley195_trigger_mode_string(init, statusword):\n    \"\"\"Get / set the trigger using a string.\"\"\"\n    with expected_protocol(\n        ik.keithley.Keithley195, [init, \"T1X\", \"U0DX\"], [statusword], sep=\"\\n\"\n    ) as mul:\n        mul.trigger_mode = \"talk_one_shot\"\n        assert mul.trigger_mode == mul.TriggerMode.talk_one_shot\n\n\ndef test_keithley195_trigger_mode_type_error(init):\n    \"\"\"Raise type error when setting the trigger mode with the wrong type.\"\"\"\n    wrong_type = 42\n    with expected_protocol(ik.keithley.Keithley195, [init], [], sep=\"\\n\") as mul:\n        with pytest.raises(TypeError) as err_info:\n            mul.trigger_mode = wrong_type\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Drive must be specified as a \"\n            f\"Keithley195.TriggerMode, got {wrong_type} instead.\"\n        )\n\n\ndef test_keithley195_relative(init, statusword):\n    \"\"\"Get / set the relative mode\"\"\"\n    with expected_protocol(\n        ik.keithley.Keithley195, [init, \"Z0DX\", \"Z1DX\", \"U0DX\"], [statusword], sep=\"\\n\"\n    ) as mul:\n        mul.relative = False\n        mul.relative = True\n        assert mul.relative\n\n\ndef test_keithley195_relative_type_error(init):\n    \"\"\"Raise type error when setting relative non-bool.\"\"\"\n    wrong_type = 42\n    with expected_protocol(\n        ik.keithley.Keithley195,\n        [\n            init,\n        ],\n        [],\n        sep=\"\\n\",\n    ) as mul:\n        with pytest.raises(TypeError) as err_info:\n            mul.relative = wrong_type\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"Relative mode must be a boolean.\"\n\n\n@pytest.mark.parametrize(\"range\", ik.keithley.Keithley195.ValidRange.resistance.value)\ndef test_keithley195_input_range(init, statusword, range):\n    \"\"\"Get / set input range.\n\n    Set unitful and w/o units.\n    \"\"\"\n    mode = ik.keithley.Keithley195.Mode(int(statusword.decode()[5]))\n    index = ik.keithley.Keithley195.ValidRange[mode.name].value.index(range)\n    # new statusword\n    new_statusword = list(statusword.decode())\n    new_statusword[6] = str(index + 1)\n    new_statusword = \"\".join(new_statusword)\n    # units\n    units = ik.keithley.keithley195.UNITS2[mode]\n    with expected_protocol(\n        ik.keithley.Keithley195,\n        [\n            init,\n            \"U0DX\",\n            f\"R{index + 1}DX\",\n            \"U0DX\",\n            f\"R{index + 1}DX\",\n            \"U0DX\",  # query\n            \"U0DX\",\n        ],\n        [statusword, statusword, new_statusword, new_statusword],\n        sep=\"\\n\",\n    ) as mul:\n        mul.input_range = range\n        mul.input_range = u.Quantity(range, units)\n        assert mul.input_range == range * units\n\n\ndef test_keithley195_input_range_auto(init, statusword):\n    \"\"\"Get / set input range auto.\"\"\"\n    # new statusword\n    new_statusword = list(statusword.decode())\n    new_statusword[6] = \"0\"\n    new_statusword = \"\".join(new_statusword)\n    with expected_protocol(\n        ik.keithley.Keithley195, [init, \"R0DX\", \"U0DX\"], [new_statusword], sep=\"\\n\"\n    ) as mul:\n        mul.input_range = \"Auto\"\n        assert mul.input_range == \"auto\"\n\n\ndef test_keithley195_input_range_set_wrong_string(init):\n    \"\"\"Raise Value error if input range set w/ string other than 'auto'.\"\"\"\n    bad_string = \"forty-two\"\n    with expected_protocol(ik.keithley.Keithley195, [init], [], sep=\"\\n\") as mul:\n        with pytest.raises(ValueError) as err_info:\n            mul.input_range = bad_string\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == 'Only \"auto\" is acceptable when specifying the '\n            \"input range as a string.\"\n        )\n\n\ndef test_keithley195_input_range_set_wrong_range(init, statusword):\n    \"\"\"Raise Value error if input range set w/ out of range value.\"\"\"\n    mode = ik.keithley.Keithley195.Mode(int(statusword.decode()[5]))\n    valid = ik.keithley.Keithley195.ValidRange[mode.name].value\n    out_of_range_value = 42\n    with expected_protocol(\n        ik.keithley.Keithley195, [init, \"U0DX\"], [statusword], sep=\"\\n\"\n    ) as mul:\n        with pytest.raises(ValueError) as err_info:\n            mul.input_range = out_of_range_value\n        err_msg = err_info.value.args[0]\n        assert err_msg == f\"Valid range settings for mode {mode} are: {valid}\"\n\n\ndef test_keithley195_input_range_set_wrong_type(init, statusword):\n    \"\"\"Raise TypeError  if input range set w/ wrong type.\"\"\"\n    wrong_type = {\"The Answer\": 42}\n    with expected_protocol(\n        ik.keithley.Keithley195, [init, \"U0DX\"], [statusword], sep=\"\\n\"\n    ) as mul:\n        with pytest.raises(TypeError) as err_info:\n            mul.input_range = wrong_type\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Range setting must be specified as a float, \"\n            f'int, or the string \"auto\", got '\n            f\"{type(wrong_type)}\"\n        )\n\n\n@given(value=st.floats(allow_infinity=False, allow_nan=False))\ndef test_measure_mode_is_none(init, statusword, value):\n    \"\"\"Get a measurement in current measure mode.\"\"\"\n    mode = ik.keithley.Keithley195.Mode(int(statusword.decode()[5]))\n    units = ik.keithley.keithley195.UNITS2[mode]\n    with expected_protocol(\n        ik.keithley.Keithley195, [init, \"U0DX\"], [statusword, f\"{value}\"], sep=\"\\n\"\n    ) as mul:\n        assert mul.measure() == value * units\n\n\ndef test_measure_mode_is_current(init, statusword):\n    \"\"\"Get a measurement with given mode, which is already set.\"\"\"\n    mode = ik.keithley.Keithley195.Mode(int(statusword.decode()[5]))\n    units = ik.keithley.keithley195.UNITS2[mode]\n    value = 3.14\n    with expected_protocol(\n        ik.keithley.Keithley195, [init, \"U0DX\"], [statusword, f\"{value}\"], sep=\"\\n\"\n    ) as mul:\n        assert mul.measure(mode=mode) == value * units\n\n\ndef test_measure_new_mode(init, statusword, mocker):\n    \"\"\"Get a measurement with given mode, which is already set.\n\n    Mock time.sleep() call and assert it is called with 2 seconds.\n    \"\"\"\n    # patch call to time.sleep with mock\n    mock_time = mocker.patch.object(time, \"sleep\", return_value=None)\n\n    # new modes\n    new_mode = ik.keithley.Keithley195.Mode(0)\n    units = ik.keithley.keithley195.UNITS2[new_mode]\n    value = 3.14\n    with expected_protocol(\n        ik.keithley.Keithley195,\n        [init, \"U0DX\", \"F0DX\"],  # send new mode\n        [statusword, f\"{value}\"],\n        sep=\"\\n\",\n    ) as mul:\n        assert mul.measure(mode=new_mode) == value * units\n\n        # assert time.sleep is called with 2 second argument\n        mock_time.assert_called_with(2)\n\n\ndef test_parse_status_word_value_error(init):\n    \"\"\"Raise ValueError if status word does not start with '195'.\"\"\"\n    wrong_statusword = \"42 314\"\n    with expected_protocol(\n        ik.keithley.Keithley195,\n        [\n            init,\n        ],\n        [],\n        sep=\"\\n\",\n    ) as mul:\n        with pytest.raises(ValueError) as err_info:\n            mul.parse_status_word(wrong_statusword)\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Status word starts with wrong prefix, expected \"\n            f\"195, got {wrong_statusword}\"\n        )\n\n\ndef test_trigger(init):\n    \"\"\"Send a trigger command.\"\"\"\n    with expected_protocol(ik.keithley.Keithley195, [init, \"X\"], [], sep=\"\\n\") as mul:\n        mul.trigger()\n\n\ndef test_auto_range(init):\n    \"\"\"Set input range to 'auto'.\"\"\"\n    with expected_protocol(\n        ik.keithley.Keithley195,\n        [\n            init,\n            \"R0DX\",\n        ],\n        [],\n        sep=\"\\n\",\n    ) as mul:\n        mul.auto_range()\n"
  },
  {
    "path": "tests/test_keithley/test_keithley2182.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the Keithley 2182 nano-voltmeter\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport pytest\n\nimport instruments as ik\nfrom instruments.optional_dep_finder import numpy\nfrom tests import (\n    expected_protocol,\n    iterable_eq,\n)\nfrom instruments.units import ureg as u\n\n# TESTS #######################################################################\n\n\ndef test_channel():\n    inst = ik.keithley.Keithley2182.open_test()\n    assert isinstance(inst.channel[0], inst.Channel) is True\n\n\ndef test_channel_mode():\n    with expected_protocol(\n        ik.keithley.Keithley2182,\n        [\n            \"SENS:FUNC?\",\n        ],\n        [\n            \"VOLT\",\n        ],\n    ) as inst:\n        channel = inst.channel[0]\n        assert channel.mode == inst.Mode.voltage_dc\n        with pytest.raises(NotImplementedError):\n            channel.mode = 42\n\n\ndef test_channel_trigger_mode():\n    \"\"\"Raise NotImplementedError when getting / setting trigger mode.\"\"\"\n    with expected_protocol(ik.keithley.Keithley2182, [], []) as inst:\n        channel = inst.channel[0]\n        with pytest.raises(NotImplementedError):\n            _ = channel.trigger_mode\n        with pytest.raises(NotImplementedError):\n            channel.trigger_mode = 42\n\n\ndef test_channel_relative():\n    \"\"\"Raise NotImplementedError when getting / setting relative.\"\"\"\n    with expected_protocol(ik.keithley.Keithley2182, [], []) as inst:\n        channel = inst.channel[0]\n        with pytest.raises(NotImplementedError):\n            _ = channel.relative\n        with pytest.raises(NotImplementedError):\n            channel.relative = 42\n\n\ndef test_channel_input_range():\n    \"\"\"Raise NotImplementedError when getting / setting input range.\"\"\"\n    with expected_protocol(ik.keithley.Keithley2182, [], []) as inst:\n        channel = inst.channel[0]\n        with pytest.raises(NotImplementedError):\n            _ = channel.input_range\n        with pytest.raises(NotImplementedError):\n            channel.input_range = 42\n\n\ndef test_channel_measure_mode_not_none():\n    \"\"\"Raise NotImplementedError measuring with non-None mode.\"\"\"\n    with expected_protocol(ik.keithley.Keithley2182, [], []) as inst:\n        channel = inst.channel[0]\n        with pytest.raises(NotImplementedError):\n            channel.measure(mode=\"Some Mode\")\n\n\ndef test_channel_measure_voltage():\n    with expected_protocol(\n        ik.keithley.Keithley2182,\n        [\"SENS:CHAN 1\", \"SENS:DATA:FRES?\", \"SENS:FUNC?\"],\n        [\n            \"1.234\",\n            \"VOLT\",\n        ],\n    ) as inst:\n        channel = inst.channel[0]\n        assert channel.measure() == 1.234 * u.volt\n\n\ndef test_channel_measure_temperature():\n    with expected_protocol(\n        ik.keithley.Keithley2182,\n        [\"SENS:CHAN 1\", \"SENS:DATA:FRES?\", \"SENS:FUNC?\", \"UNIT:TEMP?\"],\n        [\"1.234\", \"TEMP\", \"C\"],\n    ) as inst:\n        channel = inst.channel[0]\n        assert channel.measure() == u.Quantity(1.234, u.degC)\n\n\ndef test_channel_measure_unknown_temperature_units():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.keithley.Keithley2182,\n        [\"SENS:CHAN 1\", \"SENS:DATA:FRES?\", \"SENS:FUNC?\", \"UNIT:TEMP?\"],\n        [\"1.234\", \"TEMP\", \"Z\"],\n    ) as inst:\n        inst.channel[0].measure()\n\n\ndef test_units():\n    with expected_protocol(\n        ik.keithley.Keithley2182,\n        [\n            \"SENS:FUNC?\",\n            \"UNIT:TEMP?\",\n            \"SENS:FUNC?\",\n            \"UNIT:TEMP?\",\n            \"SENS:FUNC?\",\n            \"UNIT:TEMP?\",\n            \"SENS:FUNC?\",\n        ],\n        [\"TEMP\", \"C\", \"TEMP\", \"F\", \"TEMP\", \"K\", \"VOLT\"],\n    ) as inst:\n        assert inst.units == u.degC\n        assert inst.units == u.degF\n        assert inst.units == u.kelvin\n        assert inst.units == u.volt\n\n\ndef test_fetch():\n    with expected_protocol(\n        ik.keithley.Keithley2182,\n        [\"FETC?\", \"SENS:FUNC?\"],\n        [\n            \"1.234,1,5.678\",\n            \"VOLT\",\n        ],\n    ) as inst:\n        data = inst.fetch()\n        vals = [1.234, 1, 5.678]\n        expected_data = tuple(v * u.volt for v in vals)\n        if numpy:\n            expected_data = vals * u.volt\n        iterable_eq(data, expected_data)\n\n\ndef test_measure():\n    with expected_protocol(\n        ik.keithley.Keithley2182,\n        [\n            \"SENS:FUNC?\",\n            \"MEAS:VOLT?\",\n            \"SENS:FUNC?\",\n        ],\n        [\"VOLT\", \"1.234\", \"VOLT\"],\n    ) as inst:\n        assert inst.measure() == 1.234 * u.volt\n\n\ndef test_measure_invalid_mode():\n    with pytest.raises(TypeError), expected_protocol(\n        ik.keithley.Keithley2182, [], []\n    ) as inst:\n        inst.measure(\"derp\")\n\n\ndef test_relative_get():\n    with expected_protocol(\n        ik.keithley.Keithley2182,\n        [\"SENS:FUNC?\", \"SENS:VOLT:CHAN1:REF:STAT?\"],\n        [\"VOLT\", \"ON\"],\n    ) as inst:\n        assert inst.relative is True\n\n\ndef test_relative_set_already_enabled():\n    with expected_protocol(\n        ik.keithley.Keithley2182,\n        [\n            \"SENS:FUNC?\",\n            \"SENS:FUNC?\",\n            \"SENS:VOLT:CHAN1:REF:STAT?\",\n            \"SENS:VOLT:CHAN1:REF:ACQ\",\n        ],\n        [\n            \"VOLT\",\n            \"VOLT\",\n            \"ON\",\n        ],\n    ) as inst:\n        inst.relative = True\n\n\ndef test_relative_set_start_disabled():\n    with expected_protocol(\n        ik.keithley.Keithley2182,\n        [\n            \"SENS:FUNC?\",\n            \"SENS:FUNC?\",\n            \"SENS:VOLT:CHAN1:REF:STAT?\",\n            \"SENS:VOLT:CHAN1:REF:STAT ON\",\n        ],\n        [\n            \"VOLT\",\n            \"VOLT\",\n            \"OFF\",\n        ],\n    ) as inst:\n        inst.relative = True\n\n\ndef test_relative_set_wrong_type():\n    with pytest.raises(TypeError), expected_protocol(\n        ik.keithley.Keithley2182, [], []\n    ) as inst:\n        inst.relative = \"derp\"\n\n\ndef test_input_range():\n    \"\"\"Raise NotImplementedError when getting / setting input range.\"\"\"\n    with expected_protocol(ik.keithley.Keithley2182, [], []) as inst:\n        with pytest.raises(NotImplementedError):\n            _ = inst.input_range\n        with pytest.raises(NotImplementedError):\n            inst.input_range = 42\n"
  },
  {
    "path": "tests/test_keithley/test_keithley485.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the Keithley 485 picoammeter\n\"\"\"\n\n# IMPORTS ####################################################################\n\nimport pytest\n\nfrom instruments.units import ureg as u\n\nimport instruments as ik\nfrom tests import expected_protocol\n\n# TESTS ######################################################################\n\n# pylint: disable=protected-access\n\n\ndef test_zero_check():\n    with expected_protocol(\n        ik.keithley.Keithley485, [\"C0X\", \"C1X\", \"U0X\"], [\"4851000000000:\"]\n    ) as inst:\n        inst.zero_check = False\n        inst.zero_check = True\n        assert inst.zero_check\n        with pytest.raises(TypeError) as err_info:\n            inst.zero_check = 42\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"Zero Check mode must be a boolean.\"\n\n\ndef test_log():\n    with expected_protocol(\n        ik.keithley.Keithley485, [\"D0X\", \"D1X\", \"U0X\"], [\"4850100000000:\"]\n    ) as inst:\n        inst.log = False\n        inst.log = True\n        assert inst.log\n        with pytest.raises(TypeError) as err_info:\n            inst.log = 42\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"Log mode must be a boolean.\"\n\n\ndef test_input_range():\n    with expected_protocol(\n        ik.keithley.Keithley485, [\"R0X\", \"R7X\", \"U0X\"], [\"4850070000000:\"]\n    ) as inst:\n        inst.input_range = \"auto\"\n        inst.input_range = 2e-3\n        assert inst.input_range == 2.0 * u.milliamp\n\n\ndef test_relative():\n    with expected_protocol(\n        ik.keithley.Keithley485, [\"Z0X\", \"Z1X\", \"U0X\"], [\"4850001000000:\"]\n    ) as inst:\n        inst.relative = False\n        inst.relative = True\n        assert inst.relative\n        with pytest.raises(TypeError) as err_info:\n            inst.relative = 42\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"Relative mode must be a boolean.\"\n\n\ndef test_eoi_mode():\n    with expected_protocol(\n        ik.keithley.Keithley485, [\"K0X\", \"K1X\", \"U0X\"], [\"4850000100000:\"]\n    ) as inst:\n        inst.eoi_mode = True\n        inst.eoi_mode = False\n        assert not inst.eoi_mode\n        with pytest.raises(TypeError) as err_info:\n            inst.eoi_mode = 42\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"EOI mode must be a boolean.\"\n\n\ndef test_trigger_mode():\n    with expected_protocol(\n        ik.keithley.Keithley485, [\"T0X\", \"T5X\", \"U0X\"], [\"4850000050000:\"]\n    ) as inst:\n        inst.trigger_mode = \"continuous_ontalk\"\n        inst.trigger_mode = \"oneshot_onx\"\n        assert inst.trigger_mode == \"oneshot_onx\"\n        with pytest.raises(TypeError) as err_info:\n            newval = 42\n            inst.trigger_mode = newval\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Drive must be specified as a \"\n            f\"Keithley485.TriggerMode, got {newval} instead.\"\n        )\n\n\ndef test_auto_range():\n    with expected_protocol(\n        ik.keithley.Keithley485, [\"R0X\", \"U0X\"], [\"4850000000000:\"]\n    ) as inst:\n        inst.auto_range()\n        assert inst.input_range == \"auto\"\n\n\n@pytest.mark.parametrize(\"newval\", (2e-9, 2e-8, 2e-7, 2e-6, 2e-5, 2e-4, 2e-3))\ndef test_input_range_value(newval):\n    \"\"\"Set input range with a given value from list.\"\"\"\n    valid = (\"auto\", 2e-9, 2e-8, 2e-7, 2e-6, 2e-5, 2e-4, 2e-3)\n    with expected_protocol(\n        ik.keithley.Keithley485, [f\"R{valid.index(newval)}X\"], []\n    ) as inst:\n        inst.input_range = newval\n\n\ndef test_input_range_quantity():\n    \"\"\"Set input range with a given value from list.\"\"\"\n    valid = (\"auto\", 2e-9, 2e-8, 2e-7, 2e-6, 2e-5, 2e-4, 2e-3)\n    newval = 2e-9\n    quant = u.Quantity(newval, u.A)\n    with expected_protocol(\n        ik.keithley.Keithley485, [f\"R{valid.index(newval)}X\"], []\n    ) as inst:\n        inst.input_range = quant\n\n\ndef test_input_range_invalid_value():\n    \"\"\"Raise ValueError if invalid value is given.\"\"\"\n    valid = (\"auto\", 2e-9, 2e-8, 2e-7, 2e-6, 2e-5, 2e-4, 2e-3)\n    with expected_protocol(ik.keithley.Keithley485, [], []) as inst:\n        with pytest.raises(ValueError) as err_info:\n            inst.input_range = 42\n        err_msg = err_info.value.args[0]\n        assert err_msg == f\"Valid range settings are: {valid}\"\n\n\ndef test_input_range_invalid_type():\n    \"\"\"Raise TypeError if invalid type is given.\"\"\"\n    invalid_type = [42]\n    with expected_protocol(ik.keithley.Keithley485, [], []) as inst:\n        with pytest.raises(TypeError) as err_info:\n            inst.input_range = invalid_type\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Range setting must be specified as a float, \"\n            f\"int, or the string `auto`, got \"\n            f\"{type(invalid_type)}\"\n        )\n\n\ndef test_input_range_invalid_string():\n    \"\"\"Raise ValueError if input range set with invalid string.\"\"\"\n    with expected_protocol(ik.keithley.Keithley485, [], []) as inst:\n        with pytest.raises(ValueError) as err_info:\n            inst.input_range = \"2e-9\"\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == \"Only `auto` is acceptable when specifying the \"\n            \"range as a string.\"\n        )\n\n\ndef test_get_status():\n    with expected_protocol(\n        ik.keithley.Keithley485, [\"U0X\"], [\"4850000000000:\"]\n    ) as inst:\n        inst.get_status()\n\n\ndef test_measure():\n    with expected_protocol(\n        ik.keithley.Keithley485, [\"X\", \"X\"], [\"NDCA+1.2345E-9\", \"NDCL-9.0000E+0\"]\n    ) as inst:\n        assert 1.2345 * u.nanoamp == inst.measure()\n        assert 1 * u.nanoamp == inst.measure()\n\n\ndef test_get_status_word_fails():\n    \"\"\"Raise IOError if status word query fails > 5 times.\"\"\"\n    with expected_protocol(\n        ik.keithley.Keithley485,\n        [\"U0X\", \"U0X\", \"U0X\", \"U0X\", \"U0X\"],\n        [\"\", \"\", \"\", \"\", \"\"],\n    ) as inst:\n        with pytest.raises(IOError) as err_info:\n            inst._get_status_word()\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"Could not retrieve status word\"\n\n\ndef test_parse_status_word_wrong_prefix():\n    \"\"\"Raise ValueError if statusword has wrong prefix.\"\"\"\n    wrong_statusword = \"wrong statusword\"\n    with expected_protocol(ik.keithley.Keithley485, [], []) as inst:\n        with pytest.raises(ValueError) as err_info:\n            inst._parse_status_word(wrong_statusword)\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Status word starts with wrong prefix: \" f\"{wrong_statusword}\"\n        )\n\n\ndef test_parse_status_word_cannot_parse():\n    \"\"\"Raise RuntimeError if statusword cannot be parsed.\"\"\"\n    bad_statusword = \"485FFFFFFFFFF\"\n    with expected_protocol(ik.keithley.Keithley485, [], []) as inst:\n        with pytest.raises(RuntimeError) as err_info:\n            inst._parse_status_word(bad_statusword)\n        err_msg = err_info.value.args[0]\n        assert err_msg == f\"Cannot parse status word: {bad_statusword}\"\n\n\ndef test_parse_measurement_invalid_status():\n    \"\"\"Raise ValueError if invalild status encountered.\"\"\"\n    status = \"L\"\n    bad_measurement = f\"{status}DCA+1.2345E-9\"\n    with expected_protocol(ik.keithley.Keithley485, [], []) as inst:\n        with pytest.raises(ValueError) as err_info:\n            inst._parse_measurement(bad_measurement)\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Invalid status word in measurement: \"\n            f\"{bytes(status, 'utf-8')}\"\n        )\n\n\ndef test_parse_measurement_bad_status():\n    \"\"\"Raise ValueError if non-normal status encountered.\"\"\"\n    status = ik.keithley.Keithley485.Status.overflow\n    bad_measurement = f\"{status.value.decode('utf-8')}DCA+1.2345E-9\"\n    with expected_protocol(ik.keithley.Keithley485, [], []) as inst:\n        with pytest.raises(ValueError) as err_info:\n            inst._parse_measurement(bad_measurement)\n        err_msg = err_info.value.args[0]\n        assert err_msg == f\"Instrument not in normal mode: {status.name}\"\n\n\ndef test_parse_measurement_bad_function():\n    \"\"\"Raise ValueError if non-normal function encountered.\"\"\"\n    function = \"XX\"\n    bad_measurement = f\"N{function}A+1.2345E-9\"\n    with expected_protocol(ik.keithley.Keithley485, [], []) as inst:\n        with pytest.raises(ValueError) as err_info:\n            inst._parse_measurement(bad_measurement)\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Instrument not returning DC function: \"\n            f\"{bytes(function, 'utf-8')}\"\n        )\n\n\ndef test_parse_measurement_bad_measurement():\n    \"\"\"Raise ValueError if non-normal function encountered.\"\"\"\n    bad_measurement = f\"NDCA+1.23X5E-9\"\n    with expected_protocol(ik.keithley.Keithley485, [], []) as inst:\n        with pytest.raises(Exception) as err_info:\n            inst._parse_measurement(bad_measurement)\n        err_msg = err_info.value.args[0]\n        assert err_msg == f\"Cannot parse measurement: {bad_measurement}\"\n"
  },
  {
    "path": "tests/test_keithley/test_keithley580.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the Keithley 580 digital multimeter.\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport struct\nimport time\n\nfrom hypothesis import (\n    given,\n    strategies as st,\n)\nimport pytest\n\nimport instruments as ik\nfrom tests import expected_protocol\nfrom instruments.units import ureg as u\n\n# TESTS ######################################################################\n\n\n# pylint: disable=redefined-outer-name\n\n\n# PYTEST FIXTURES FOR INITIALIZATION #\n\n\n@pytest.fixture(scope=\"session\")\ndef init():\n    \"\"\"Returns the initialization command that is sent to instrument.\"\"\"\n    return \"Y:X:\"\n\n\n@pytest.fixture(scope=\"session\")\ndef create_statusword():\n    \"\"\"Create a function that can create a status word.\n\n    Variables used in tests can be set manually, but useful default\n    values are set as well. Note: The terminator is not created, since\n    it is already sent by `expected_protocol`.\n\n    :return: Method to make a status word.\n    :rtype: `method`\n    \"\"\"\n\n    def make_statusword(\n        drive=b\"1\",\n        polarity=b\"0\",\n        drycircuit=b\"0\",\n        operate=b\"0\",\n        rng=b\"0\",\n        relative=b\"0\",\n        trigger=b\"1\",\n        linefreq=b\"0\",\n    ):\n        \"\"\"Create the status word.\"\"\"\n        # other variables\n        eoi = b\"0\"\n        sqrondata = b\"0\"\n        sqronerror = b\"0\"\n\n        status_word = struct.pack(\n            \"@8c2s2sc\",\n            drive,\n            polarity,\n            drycircuit,\n            operate,\n            rng,\n            relative,\n            eoi,\n            trigger,\n            sqrondata,\n            sqronerror,\n            linefreq,\n        )\n\n        return b\"580\" + status_word\n\n    return make_statusword\n\n\n@pytest.fixture(scope=\"session\")\ndef create_measurement():\n    \"\"\"Create a function that can create a measurement.\n\n    Variables used in tests can be set manually, but useful default\n    values are set as well.\n\n    :return: Method to make a measurement.\n    :rtype: `method`\n    \"\"\"\n\n    def make_measurement(\n        status=b\"N\", polarity=b\"+\", drycircuit=b\"D\", drive=b\"P\", resistance=b\"42\"\n    ):\n        \"\"\"Create a measurement.\"\"\"\n        resistance = bytes(resistance.decode().zfill(11), \"utf-8\")\n        measurement = struct.pack(\n            \"@4c11s\", status, polarity, drycircuit, drive, resistance\n        )\n\n        return measurement\n\n    return make_measurement\n\n\n@pytest.fixture(autouse=True)\ndef mock_time(mocker):\n    \"\"\"Mock the time.sleep object for use.\n\n    Use by default, such that getting status word is fast in tests.\n    \"\"\"\n    return mocker.patch.object(time, \"sleep\", return_value=None)\n\n\n# PROPERTIES #\n\n\n@pytest.mark.parametrize(\"newval\", ik.keithley.Keithley580.Polarity)\ndef test_polarity(init, create_statusword, newval):\n    \"\"\"Get / set instrument polarity.\"\"\"\n    status_word = create_statusword(polarity=bytes(str(newval.value), \"utf-8\"))\n    with expected_protocol(\n        ik.keithley.Keithley580,\n        [init, f\"P{newval.value}X\" + \":\", \"U0X:\", \":\"],\n        [status_word + b\":\"],\n        sep=\"\\n\",\n    ) as inst:\n        inst.polarity = newval\n        assert inst.polarity == newval\n\n\n@pytest.mark.parametrize(\n    \"newval_str\", [it.name for it in ik.keithley.Keithley580.Polarity]\n)\ndef test_polarity_string(init, newval_str):\n    \"\"\"Set polarity with a string.\"\"\"\n    newval = ik.keithley.Keithley580.Polarity[newval_str]\n    with expected_protocol(\n        ik.keithley.Keithley580,\n        [\n            init,\n            f\"P{newval.value}X\" + \":\",\n        ],\n        [],\n        sep=\"\\n\",\n    ) as inst:\n        inst.polarity = newval_str\n\n\ndef test_polarity_wrong_type(init):\n    \"\"\"Raise TypeError if setting polarity with wrong type.\"\"\"\n    wrong_type = 42\n    with expected_protocol(\n        ik.keithley.Keithley580,\n        [\n            init,\n        ],\n        [],\n        sep=\"\\n\",\n    ) as inst:\n        with pytest.raises(TypeError) as err_info:\n            inst.polarity = wrong_type\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Polarity must be specified as a \"\n            f\"Keithley580.Polarity, got {wrong_type} \"\n            f\"instead.\"\n        )\n\n\n@pytest.mark.parametrize(\"newval\", ik.keithley.Keithley580.Drive)\ndef test_drive(init, create_statusword, newval):\n    \"\"\"Get / set instrument drive.\"\"\"\n    status_word = create_statusword(drive=bytes(str(newval.value), \"utf-8\"))\n    with expected_protocol(\n        ik.keithley.Keithley580,\n        [init, f\"D{newval.value}X\" + \":\", \"U0X:\", \":\"],\n        [status_word + b\":\"],\n        sep=\"\\n\",\n    ) as inst:\n        inst.drive = newval\n        assert inst.drive == newval\n\n\n@pytest.mark.parametrize(\n    \"newval_str\", [it.name for it in ik.keithley.Keithley580.Drive]\n)\ndef test_drive_string(init, newval_str):\n    \"\"\"Set drive with a string.\"\"\"\n    newval = ik.keithley.Keithley580.Drive[newval_str]\n    with expected_protocol(\n        ik.keithley.Keithley580,\n        [\n            init,\n            f\"D{newval.value}X\" + \":\",\n        ],\n        [],\n        sep=\"\\n\",\n    ) as inst:\n        inst.drive = newval_str\n\n\ndef test_drive_wrong_type(init):\n    \"\"\"Raise TypeError if setting drive with wrong type.\"\"\"\n    wrong_type = 42\n    with expected_protocol(\n        ik.keithley.Keithley580,\n        [\n            init,\n        ],\n        [],\n        sep=\"\\n\",\n    ) as inst:\n        with pytest.raises(TypeError) as err_info:\n            inst.drive = wrong_type\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Drive must be specified as a \"\n            f\"Keithley580.Drive, got {wrong_type} \"\n            f\"instead.\"\n        )\n\n\n@pytest.mark.parametrize(\"newval\", (True, False))\ndef test_dry_circuit_test(init, create_statusword, newval):\n    \"\"\"Get / set dry circuit test.\"\"\"\n    status_word = create_statusword(drycircuit=bytes(str(int(newval)), \"utf-8\"))\n    with expected_protocol(\n        ik.keithley.Keithley580,\n        [init, f\"C{int(newval)}X\" + \":\", \"U0X:\", \":\"],\n        [status_word + b\":\"],\n        sep=\"\\n\",\n    ) as inst:\n        inst.dry_circuit_test = newval\n        assert inst.dry_circuit_test == newval\n\n\ndef test_dry_circuit_test_wrong_type(init):\n    \"\"\"Raise TypeError if setting dry circuit test with wrong type.\"\"\"\n    wrong_type = 42\n    with expected_protocol(\n        ik.keithley.Keithley580,\n        [\n            init,\n        ],\n        [],\n        sep=\"\\n\",\n    ) as inst:\n        with pytest.raises(TypeError) as err_info:\n            inst.dry_circuit_test = wrong_type\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"DryCircuitTest mode must be a boolean.\"\n\n\n@pytest.mark.parametrize(\"newval\", (True, False))\ndef test_operate(init, create_statusword, newval):\n    \"\"\"Get / set operate.\"\"\"\n    status_word = create_statusword(operate=bytes(str(int(newval)), \"utf-8\"))\n    with expected_protocol(\n        ik.keithley.Keithley580,\n        [init, f\"O{int(newval)}X\" + \":\", \"U0X:\", \":\"],\n        [status_word + b\":\"],\n        sep=\"\\n\",\n    ) as inst:\n        inst.operate = newval\n        assert inst.operate == newval\n\n\ndef test_operate_wrong_type(init):\n    \"\"\"Raise TypeError if setting operate with wrong type.\"\"\"\n    wrong_type = 42\n    with expected_protocol(\n        ik.keithley.Keithley580,\n        [\n            init,\n        ],\n        [],\n        sep=\"\\n\",\n    ) as inst:\n        with pytest.raises(TypeError) as err_info:\n            inst.operate = wrong_type\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"Operate mode must be a boolean.\"\n\n\n@pytest.mark.parametrize(\"newval\", (True, False))\ndef test_relative(init, create_statusword, newval):\n    \"\"\"Get / set relative.\"\"\"\n    status_word = create_statusword(relative=bytes(str(int(newval)), \"utf-8\"))\n    with expected_protocol(\n        ik.keithley.Keithley580,\n        [init, f\"Z{int(newval)}X\" + \":\", \"U0X:\", \":\"],\n        [status_word + b\":\"],\n        sep=\"\\n\",\n    ) as inst:\n        inst.relative = newval\n        assert inst.relative == newval\n\n\ndef test_relative_wrong_type(init):\n    \"\"\"Raise TypeError if setting relative with wrong type.\"\"\"\n    wrong_type = 42\n    with expected_protocol(\n        ik.keithley.Keithley580,\n        [\n            init,\n        ],\n        [],\n        sep=\"\\n\",\n    ) as inst:\n        with pytest.raises(TypeError) as err_info:\n            inst.relative = wrong_type\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"Relative mode must be a boolean.\"\n\n\ndef test_trigger_mode_get(init):\n    \"\"\"Getting trigger mode raises NotImplementedError.\n\n    Unclear why this is not implemented.\n    \"\"\"\n    with expected_protocol(\n        ik.keithley.Keithley580,\n        [\n            init,\n        ],\n        [],\n        sep=\"\\n\",\n    ) as inst:\n        with pytest.raises(NotImplementedError):\n            assert inst.trigger_mode\n\n\n@pytest.mark.parametrize(\"newval\", ik.keithley.Keithley580.TriggerMode)\ndef test_trigger_mode_set(init, newval):\n    \"\"\"Set instrument trigger mode.\"\"\"\n    with expected_protocol(\n        ik.keithley.Keithley580, [init, f\"T{newval.value}X\" + \":\"], [], sep=\"\\n\"\n    ) as inst:\n        inst.trigger_mode = newval\n\n\n@pytest.mark.parametrize(\"newval\", ik.keithley.Keithley580.TriggerMode)\ndef test_trigger_mode_set_string(init, newval):\n    \"\"\"Set instrument trigger mode as a string.\"\"\"\n    newval_str = newval.name\n    with expected_protocol(\n        ik.keithley.Keithley580, [init, f\"T{newval.value}X\" + \":\"], [], sep=\"\\n\"\n    ) as inst:\n        inst.trigger_mode = newval_str\n\n\ndef test_trigger_mode_set_type_error(init):\n    \"\"\"Raise TypeError when setting trigger mode with wrong type.\"\"\"\n    wrong_type = 42\n    with expected_protocol(ik.keithley.Keithley580, [init], [], sep=\"\\n\") as inst:\n        with pytest.raises(TypeError) as err_info:\n            inst.trigger_mode = wrong_type\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Drive must be specified as a \"\n            f\"Keithley580.TriggerMode, got \"\n            f\"{wrong_type} instead.\"\n        )\n\n\n@pytest.mark.parametrize(\"newval\", (2e-1, 2e0, 2e1, 2e2, 2e3, 2e4, 2e5))\ndef test_input_range_float(init, create_statusword, newval):\n    \"\"\"Get / set input range with a float, unitful and unitless.\"\"\"\n    valid = (\"auto\", 2e-1, 2e0, 2e1, 2e2, 2e3, 2e4, 2e5)\n    newval_unitful = newval * u.ohm\n    newval_index = valid.index(newval)\n\n    status_word = create_statusword(rng=bytes(str(newval_index), \"utf-8\"))\n\n    with expected_protocol(\n        ik.keithley.Keithley580,\n        [init, f\"R{newval_index}X\" + \":\", f\"R{newval_index}X\" + \":\", \"U0X:\", \":\"],\n        [status_word + b\":\"],\n        sep=\"\\n\",\n    ) as inst:\n        inst.input_range = newval\n        inst.input_range = newval_unitful\n        assert inst.input_range == newval_unitful\n\n\ndef test_input_range_auto(init, create_statusword):\n    \"\"\"Get / set input range auto.\"\"\"\n    newval = \"auto\"\n    newval_index = 0\n\n    status_word = create_statusword(rng=bytes(str(newval_index), \"utf-8\"))\n\n    with expected_protocol(\n        ik.keithley.Keithley580,\n        [init, f\"R{newval_index}X\" + \":\", \"U0X:\", \":\"],\n        [status_word + b\":\"],\n        sep=\"\\n\",\n    ) as inst:\n        inst.input_range = newval\n        assert inst.input_range == newval\n\n\n@given(\n    newval=st.floats().filter(lambda x: x not in (2e-1, 2e0, 2e1, 2e2, 2e3, 2e4, 2e5))\n)\ndef test_input_range_float_value_error(init, newval):\n    \"\"\"Raise ValueError if input range set to invalid value.\"\"\"\n    valid = (\"auto\", 2e-1, 2e0, 2e1, 2e2, 2e3, 2e4, 2e5)\n    with expected_protocol(ik.keithley.Keithley580, [init], [], sep=\"\\n\") as inst:\n        with pytest.raises(ValueError) as err_info:\n            inst.input_range = newval\n        err_msg = err_info.value.args[0]\n        assert err_msg == f\"Valid range settings are: {valid}\"\n\n\ndef test_input_range_auto_value_error(init):\n    \"\"\"Raise ValueError if string set as input range is not 'auto'.\"\"\"\n    newval = \"automatic\"\n\n    with expected_protocol(ik.keithley.Keithley580, [init], [], sep=\"\\n\") as inst:\n        with pytest.raises(ValueError) as err_info:\n            inst.input_range = newval\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == 'Only \"auto\" is acceptable when specifying the '\n            \"input range as a string.\"\n        )\n\n\ndef test_input_range_type_error(init):\n    \"\"\"Raise TypeError if input range is set with wrong type.\"\"\"\n    wrong_type = {\"The Answer\": 42}\n\n    with expected_protocol(ik.keithley.Keithley580, [init], [], sep=\"\\n\") as inst:\n        with pytest.raises(TypeError) as err_info:\n            inst.input_range = wrong_type\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Range setting must be specified as a float, \"\n            f'int, or the string \"auto\", got '\n            f\"{type(wrong_type)}\"\n        )\n\n\n# METHODS #\n\n\ndef test_trigger(init):\n    \"\"\"Send a trigger to instrument.\"\"\"\n    with expected_protocol(ik.keithley.Keithley580, [init, \"X:\"], [], sep=\"\\n\") as inst:\n        inst.trigger()\n\n\ndef test_auto_range(init):\n    \"\"\"Put instrument into auto range mode.\"\"\"\n    with expected_protocol(\n        ik.keithley.Keithley580, [init, \"R0X:\"], [], sep=\"\\n\"\n    ) as inst:\n        inst.auto_range()\n\n\ndef test_set_calibration_value(init):\n    \"\"\"Raise NotImplementedError when trying to set calibration value.\"\"\"\n    value = None\n    with expected_protocol(ik.keithley.Keithley580, [init], [], sep=\"\\n\") as inst:\n        with pytest.raises(NotImplementedError) as err_info:\n            inst.set_calibration_value(value)\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"setCalibrationValue not implemented\"\n\n\ndef test_store_calibration_constants(init):\n    \"\"\"Raise NotImplementedError when trying to store calibration constants.\"\"\"\n    with expected_protocol(ik.keithley.Keithley580, [init], [], sep=\"\\n\") as inst:\n        with pytest.raises(NotImplementedError) as err_info:\n            inst.store_calibration_constants()\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"storeCalibrationConstants not implemented\"\n\n\n# STATUS WORD #\n\n\ndef test_get_status_word(init, create_statusword, mock_time):\n    \"\"\"Test getting a default status word.\"\"\"\n    status_word = create_statusword()\n\n    with expected_protocol(\n        ik.keithley.Keithley580, [init, \"U0X:\", \":\"], [status_word + b\":\"], sep=\"\\n\"\n    ) as inst:\n        assert inst.get_status_word() == status_word\n        mock_time.assert_called_with(1)\n\n\ndef test_get_status_word_fails(init, mock_time):\n    \"\"\"Raise IOError after 5 reads with bad returns.\"\"\"\n    wrong_status_word = b\"195 12345\"\n\n    with expected_protocol(\n        ik.keithley.Keithley580,\n        [init, \"U0X:\", \":\", \"U0X:\", \":\", \"U0X:\", \":\", \"U0X:\", \":\", \"U0X:\", \":\"],\n        [\n            wrong_status_word,\n            wrong_status_word,\n            wrong_status_word,\n            wrong_status_word,\n            wrong_status_word,\n        ],\n        sep=\"\\n\",\n    ) as inst:\n        with pytest.raises(IOError) as err_info:\n            inst.get_status_word()\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"could not retrieve status word\"\n\n        mock_time.assert_called_with(1)\n\n\n@pytest.mark.parametrize(\"line_frequency\", ((\"0\", \"60Hz\"), (\"1\", \"50Hz\")))\ndef test_parse_status_word(init, create_statusword, line_frequency):\n    \"\"\"Parse a given status word.\n\n    Note: full range of parameters explored in individual routines.\n    Here, we thus just use the default status word created by the\n    fixture and only parametrize where other routines do not.\n    \"\"\"\n    status_word = create_statusword(linefreq=bytes(line_frequency[0], \"utf-8\"))\n    # create the dictionary to compare to\n    expected_dict = {\n        \"drive\": \"dc\",\n        \"polarity\": \"+\",\n        \"drycircuit\": False,\n        \"operate\": False,\n        \"range\": \"auto\",\n        \"relative\": False,\n        \"eoi\": b\"0\",\n        \"trigger\": True,\n        \"sqrondata\": struct.pack(\"@2s\", b\"0\"),\n        \"sqronerror\": struct.pack(\"@2s\", b\"0\"),\n        \"linefreq\": line_frequency[1],\n    }\n    with expected_protocol(ik.keithley.Keithley580, [init], [], sep=\"\\n\") as inst:\n        # add terminator to expected dict:\n        expected_dict[\"terminator\"] = inst.terminator\n        assert inst.parse_status_word(status_word) == expected_dict\n\n\n@given(\n    drive=st.integers(min_value=2, max_value=9),\n    polarity=st.integers(min_value=2, max_value=9),\n    rng=st.integers(min_value=8, max_value=9),\n    linefreq=st.integers(min_value=2, max_value=9),\n)\ndef test_parse_status_word_invalid_values(\n    init, create_statusword, drive, polarity, rng, linefreq\n):\n    \"\"\"Raise RuntimeError if status word contains invalid values.\"\"\"\n    status_word = create_statusword(\n        drive=bytes(str(drive), \"utf-8\"),\n        polarity=bytes(str(polarity), \"utf-8\"),\n        rng=bytes(str(rng), \"utf-8\"),\n        linefreq=bytes(str(linefreq), \"utf-8\"),\n    )\n    with expected_protocol(ik.keithley.Keithley580, [init], [], sep=\"\\n\") as inst:\n        with pytest.raises(RuntimeError) as err_info:\n            inst.parse_status_word(status_word)\n        err_msg = err_info.value.args[0]\n        assert err_msg == f\"Cannot parse status word: {status_word}\"\n\n\ndef test_parse_status_word_invalid_prefix(init):\n    \"\"\"Raise ValueError if status word has invalid prefix.\"\"\"\n    invalid_status_word = b\"314 424242\"\n    with expected_protocol(ik.keithley.Keithley580, [init], [], sep=\"\\n\") as inst:\n        with pytest.raises(ValueError) as err_info:\n            inst.parse_status_word(invalid_status_word)\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Status word starts with wrong prefix: \"\n            f\"{invalid_status_word}\"\n        )\n\n\n# MEASUREMENT #\n\n\n@given(resistance=st.floats(min_value=0.001, max_value=1000000))\ndef test_measure(init, create_measurement, resistance):\n    \"\"\"Perform a resistance measurement.\"\"\"\n    # cap resistance at max of 11 character with given max_value\n    resistance_byte = bytes(f\"{resistance:.6f}\", \"utf-8\")\n    measurement = create_measurement(resistance=resistance_byte)\n    with expected_protocol(\n        ik.keithley.Keithley580,\n        [init, \"X:\", \":\"],  # trigger\n        [measurement + b\":\"],\n        sep=\"\\n\",\n    ) as inst:\n        read_value = inst.measure()\n        assert read_value.magnitude == pytest.approx(resistance, rel=1e-3)\n        assert read_value.units == u.ohm\n\n\n@pytest.mark.parametrize(\"status\", (b\"S\", b\"N\", b\"O\", b\"Z\"))\n@pytest.mark.parametrize(\"polarity\", (b\"+\", b\"-\"))\n@pytest.mark.parametrize(\"drycircuit\", (b\"N\", b\"D\"))\n@pytest.mark.parametrize(\"drive\", (b\"P\", b\"D\"))\ndef test_parse_measurement(\n    init, create_measurement, status, polarity, drycircuit, drive\n):\n    \"\"\"Parse a given measurement.\"\"\"\n    resistance = b\"42\"\n    measurement = create_measurement(\n        status=status,\n        polarity=polarity,\n        drycircuit=drycircuit,\n        drive=drive,\n        resistance=resistance,\n    )\n\n    # valid states\n    valid = {\n        \"status\": {b\"S\": \"standby\", b\"N\": \"normal\", b\"O\": \"overflow\", b\"Z\": \"relative\"},\n        \"polarity\": {b\"+\": \"+\", b\"-\": \"-\"},\n        \"drycircuit\": {b\"N\": False, b\"D\": True},\n        \"drive\": {b\"P\": \"pulsed\", b\"D\": \"dc\"},\n    }\n\n    # create expected dictionary\n    dict_expected = {\n        \"status\": valid[\"status\"][status],\n        \"polarity\": valid[\"polarity\"][polarity],\n        \"drycircuit\": valid[\"drycircuit\"][drycircuit],\n        \"drive\": valid[\"drive\"][drive],\n        \"resistance\": float(resistance.decode()) * u.ohm,\n    }\n\n    with expected_protocol(\n        ik.keithley.Keithley580,\n        [\n            init,\n        ],\n        [],\n        sep=\"\\n\",\n    ) as inst:\n        assert inst.parse_measurement(measurement) == dict_expected\n\n\ndef test_parse_measurement_invalid(init, create_measurement):\n    \"\"\"Raise an exception if the status contains invalid character.\"\"\"\n    measurement = create_measurement(status=bytes(\"V\", \"utf-8\"))\n\n    with expected_protocol(\n        ik.keithley.Keithley580,\n        [\n            init,\n        ],\n        [],\n        sep=\"\\n\",\n    ) as inst:\n        with pytest.raises(Exception) as exc_info:\n            inst.parse_measurement(measurement)\n        err_msg = exc_info.value.args[0]\n        assert err_msg == f\"Cannot parse measurement: {measurement}\"\n\n\n# COMMUNICATION METHODS #\n\n\ndef test_sendcmd(init):\n    \"\"\"Send a command to the instrument.\"\"\"\n    cmd = \"COMMAND\"\n    with expected_protocol(\n        ik.keithley.Keithley580, [init, cmd + \":\"], [], sep=\"\\n\"\n    ) as inst:\n        inst.sendcmd(cmd)\n\n\ndef test_query(init):\n    \"\"\"Query the instrument.\"\"\"\n    cmd = \"COMMAND\"\n    answer = \"ANSWER\"\n    with expected_protocol(\n        ik.keithley.Keithley580, [init, cmd + \":\"], [answer + \":\"], sep=\"\\n\"\n    ) as inst:\n        assert inst.query(cmd) == answer\n"
  },
  {
    "path": "tests/test_keithley/test_keithley6220.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the Keithley 6220 constant current supply\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport pytest\n\nfrom instruments.units import ureg as u\n\nimport instruments as ik\nfrom tests import expected_protocol\n\n# TESTS #######################################################################\n\n\ndef test_channel():\n    inst = ik.keithley.Keithley6220.open_test()\n    assert inst.channel[0] == inst\n\n\ndef test_voltage():\n    \"\"\"Raise NotImplementedError when getting / setting voltage.\"\"\"\n    with expected_protocol(ik.keithley.Keithley6220, [], []) as inst:\n        with pytest.raises(NotImplementedError) as err_info:\n            _ = inst.voltage\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"The Keithley 6220 does not support voltage \" \"settings.\"\n        with pytest.raises(NotImplementedError) as err_info:\n            inst.voltage = 42\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"The Keithley 6220 does not support voltage \" \"settings.\"\n\n\ndef test_current():\n    with expected_protocol(\n        ik.keithley.Keithley6220,\n        [\"SOUR:CURR?\", f\"SOUR:CURR {0.05:e}\"],\n        [\n            \"0.1\",\n        ],\n    ) as inst:\n        assert inst.current == 100 * u.milliamp\n        assert inst.current_min == -105 * u.milliamp\n        assert inst.current_max == +105 * u.milliamp\n        inst.current = 50 * u.milliamp\n\n\ndef test_disable():\n    with expected_protocol(ik.keithley.Keithley6220, [\"SOUR:CLE:IMM\"], []) as inst:\n        inst.disable()\n"
  },
  {
    "path": "tests/test_keithley/test_keithley6514.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the Keithley 6514 electrometer\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport pytest\n\nimport instruments as ik\nfrom tests import expected_protocol\nfrom instruments.units import ureg as u\n\n# TESTS #######################################################################\n\n# pylint: disable=protected-access\n\n\ndef test_valid_range():\n    inst = ik.keithley.Keithley6514.open_test()\n    assert inst._valid_range(inst.Mode.voltage) == inst.ValidRange.voltage\n    assert inst._valid_range(inst.Mode.current) == inst.ValidRange.current\n    assert inst._valid_range(inst.Mode.resistance) == inst.ValidRange.resistance\n    assert inst._valid_range(inst.Mode.charge) == inst.ValidRange.charge\n\n\ndef test_valid_range_invalid():\n    with pytest.raises(ValueError):\n        inst = ik.keithley.Keithley6514.open_test()\n        inst._valid_range(inst.TriggerMode.immediate)\n\n\ndef test_parse_measurement():\n    with expected_protocol(\n        ik.keithley.Keithley6514,\n        [\n            \"FUNCTION?\",\n        ],\n        ['\"VOLT:DC\"'],\n    ) as inst:\n        reading, timestamp, status = inst._parse_measurement(\"1.0,1234,5678\")\n        assert reading == 1.0 * u.volt\n        assert timestamp == 1234\n        assert status == 5678\n\n\ndef test_mode():\n    with expected_protocol(\n        ik.keithley.Keithley6514, [\"FUNCTION?\", 'FUNCTION \"VOLT:DC\"'], ['\"VOLT:DC\"']\n    ) as inst:\n        assert inst.mode == inst.Mode.voltage\n        inst.mode = inst.Mode.voltage\n\n\ndef test_trigger_source():\n    with expected_protocol(\n        ik.keithley.Keithley6514, [\"TRIGGER:SOURCE?\", \"TRIGGER:SOURCE IMM\"], [\"TLINK\"]\n    ) as inst:\n        assert inst.trigger_mode == inst.TriggerMode.tlink\n        inst.trigger_mode = inst.TriggerMode.immediate\n\n\ndef test_arm_source():\n    with expected_protocol(\n        ik.keithley.Keithley6514, [\"ARM:SOURCE?\", \"ARM:SOURCE IMM\"], [\"TIM\"]\n    ) as inst:\n        assert inst.arm_source == inst.ArmSource.timer\n        inst.arm_source = inst.ArmSource.immediate\n\n\ndef test_zero_check():\n    with expected_protocol(\n        ik.keithley.Keithley6514, [\"SYST:ZCH?\", \"SYST:ZCH ON\"], [\"OFF\"]\n    ) as inst:\n        assert inst.zero_check is False\n        inst.zero_check = True\n\n\ndef test_zero_correct():\n    with expected_protocol(\n        ik.keithley.Keithley6514, [\"SYST:ZCOR?\", \"SYST:ZCOR ON\"], [\"OFF\"]\n    ) as inst:\n        assert inst.zero_correct is False\n        inst.zero_correct = True\n\n\ndef test_unit():\n    with expected_protocol(\n        ik.keithley.Keithley6514,\n        [\n            \"FUNCTION?\",\n        ],\n        ['\"VOLT:DC\"'],\n    ) as inst:\n        assert inst.unit == u.volt\n\n\ndef test_auto_range():\n    with expected_protocol(\n        ik.keithley.Keithley6514,\n        [\"FUNCTION?\", \"VOLT:DC:RANGE:AUTO?\", \"FUNCTION?\", \"VOLT:DC:RANGE:AUTO 1\"],\n        ['\"VOLT:DC\"', \"0\", '\"VOLT:DC\"'],\n    ) as inst:\n        assert inst.auto_range is False\n        inst.auto_range = True\n\n\ndef test_input_range():\n    with expected_protocol(\n        ik.keithley.Keithley6514,\n        [\n            \"FUNCTION?\",\n            \"VOLT:DC:RANGE:UPPER?\",\n            \"FUNCTION?\",\n            f\"VOLT:DC:RANGE:UPPER {20:e}\",\n        ],\n        ['\"VOLT:DC\"', \"10\", '\"VOLT:DC\"'],\n    ) as inst:\n        assert inst.input_range == 10 * u.volt\n        inst.input_range = 20 * u.volt\n\n\ndef test_input_range_invalid():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.keithley.Keithley6514, [\"FUNCTION?\"], ['\"VOLT:DC\"']\n    ) as inst:\n        inst.input_range = 10 * u.volt\n\n\ndef test_auto_config():\n    with expected_protocol(\n        ik.keithley.Keithley6514,\n        [\n            \"CONF:VOLT:DC\",\n        ],\n        [],\n    ) as inst:\n        inst.auto_config(inst.Mode.voltage)\n\n\ndef test_fetch():\n    with expected_protocol(\n        ik.keithley.Keithley6514,\n        [\n            \"FETC?\",\n            \"FUNCTION?\",\n        ],\n        [\"1.0,1234,5678\", '\"VOLT:DC\"'],\n    ) as inst:\n        reading, timestamp = inst.fetch()\n        assert reading == 1.0 * u.volt\n        assert timestamp == 1234\n\n\ndef test_read():\n    with expected_protocol(\n        ik.keithley.Keithley6514,\n        [\n            \"READ?\",\n            \"FUNCTION?\",\n        ],\n        [\"1.0,1234,5678\", '\"VOLT:DC\"'],\n    ) as inst:\n        reading, timestamp = inst.read_measurements()\n        assert reading == 1.0 * u.volt\n        assert timestamp == 1234\n"
  },
  {
    "path": "tests/test_lakeshore/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_lakeshore/test_lakeshore336.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the Lakeshore 336\n\"\"\"\n\n# IMPORTS ####################################################################\n\nimport pytest\n\nimport instruments as ik\nfrom instruments.units import ureg as u\nfrom tests import expected_protocol\n\n# TESTS ######################################################################\n\n# pylint: disable=protected-access\n\n# TEST SENSOR CLASS #\n\n\ndef test_lakeshore336_sensor_init():\n    \"\"\"\n    Test initialization of sensor class.\n    \"\"\"\n    with expected_protocol(\n        ik.lakeshore.Lakeshore336,\n        [],\n        [],\n    ) as cryo:\n        sensor = cryo.sensor[0]\n        assert sensor._parent is cryo\n        assert sensor._idx == \"A\"\n\n\n@pytest.mark.parametrize(\"idx_ch\", [(0, \"A\"), (1, \"B\"), (2, \"C\"), (3, \"D\")])\ndef test_lakeshore336_sensor_temperature(idx_ch):\n    \"\"\"\n    Receive a unitful temperature from a sensor.\n    \"\"\"\n    idx, ch = idx_ch\n    with expected_protocol(\n        ik.lakeshore.Lakeshore336,\n        [f\"KRDG?{ch}\"],\n        [\"77\"],\n    ) as cryo:\n        assert cryo.sensor[idx].temperature == u.Quantity(77, u.K)\n"
  },
  {
    "path": "tests/test_lakeshore/test_lakeshore340.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the Lakeshore 340\n\"\"\"\n\n# IMPORTS ####################################################################\n\nimport instruments as ik\nfrom instruments.units import ureg as u\nfrom tests import expected_protocol\n\n# TESTS ######################################################################\n\n# pylint: disable=protected-access\n\n# TEST SENSOR CLASS #\n\n\ndef test_lakeshore340_sensor_init():\n    \"\"\"\n    Test initialization of sensor class.\n    \"\"\"\n    with expected_protocol(\n        ik.lakeshore.Lakeshore340,\n        [],\n        [],\n    ) as cryo:\n        sensor = cryo.sensor[0]\n        assert sensor._parent is cryo\n        assert sensor._idx == 1\n\n\ndef test_lakeshore340_sensor_temperature():\n    \"\"\"\n    Receive a unitful temperature from a sensor.\n    \"\"\"\n    with expected_protocol(\n        ik.lakeshore.Lakeshore340,\n        [\"KRDG?1\"],\n        [\"77\"],\n    ) as cryo:\n        assert cryo.sensor[0].temperature == u.Quantity(77, u.K)\n"
  },
  {
    "path": "tests/test_lakeshore/test_lakeshore370.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the Lakeshore 370\n\"\"\"\n\n# IMPORTS ####################################################################\n\nimport pytest\n\nimport instruments as ik\nfrom instruments.units import ureg as u\nfrom tests import expected_protocol\n\n# TESTS ######################################################################\n\n# pylint: disable=redefined-outer-name,protected-access\n\n# PYTEST FIXTURES FOR INITIALIZATION #\n\n\n@pytest.fixture\ndef init():\n    \"\"\"Returns the command the instrument sends at initaliation.\"\"\"\n    return \"IEEE 3,0\"\n\n\n# TEST SENSOR CLASS #\n\n\ndef test_lakeshore370_channel_init(init):\n    \"\"\"\n    Test initialization of channel class.\n    \"\"\"\n    with expected_protocol(\n        ik.lakeshore.Lakeshore370,\n        [init],\n        [],\n    ) as lsh:\n        channel = lsh.channel[7]\n        assert channel._parent is lsh\n        assert channel._idx == 8\n\n\ndef test_lakeshore370_channel_resistance(init):\n    \"\"\"\n    Receive a unitful resistance from a channel.\n    \"\"\"\n    with expected_protocol(\n        ik.lakeshore.Lakeshore370,\n        [init, \"RDGR? 1\"],\n        [\"100.\"],\n    ) as lsh:\n        assert lsh.channel[0].resistance == u.Quantity(100, u.ohm)\n"
  },
  {
    "path": "tests/test_lakeshore/test_lakeshore475.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the Lakeshore 475 Gaussmeter\n\"\"\"\n\n# IMPORTS ####################################################################\n\nimport pytest\n\nimport instruments as ik\nfrom instruments.units import ureg as u\nfrom tests import expected_protocol\n\n# TESTS ######################################################################\n\n\n# TEST LAKESHORE475 CLASS PROPERTIES #\n\n\ndef test_lakeshore475_field():\n    \"\"\"\n    Get field from connected probe unitful.\n    \"\"\"\n    with expected_protocol(\n        ik.lakeshore.Lakeshore475,\n        [\"RDGFIELD?\", \"UNIT?\"],\n        [\"200.\", \"2\"],\n    ) as lsh:\n        assert lsh.field == u.Quantity(200.0, u.tesla)\n\n\ndef test_lakeshore475_field_units():\n    \"\"\"\n    Get / set field unit on device.\n    \"\"\"\n    with expected_protocol(\n        ik.lakeshore.Lakeshore475,\n        [\"UNIT?\", \"UNIT 2\"],\n        [\"3\"],\n    ) as lsh:\n        assert lsh.field_units == u.oersted\n        lsh.field_units = u.tesla\n\n\ndef test_lakeshore475_field_units_invalid_unit():\n    \"\"\"\n    Raise a ValueError if an invalid unit is given.\n    \"\"\"\n    with expected_protocol(\n        ik.lakeshore.Lakeshore475,\n        [],\n        [],\n    ) as lsh:\n        with pytest.raises(ValueError) as exc_info:\n            lsh.field_units = u.m\n        exc_msg = exc_info.value.args[0]\n        assert exc_msg == \"Not an acceptable Python quantities object\"\n\n\ndef test_lakeshore475_field_units_not_a_unit():\n    \"\"\"\n    Raise a ValueError if something else than a quantity is given.\n    \"\"\"\n    with expected_protocol(\n        ik.lakeshore.Lakeshore475,\n        [],\n        [],\n    ) as lsh:\n        with pytest.raises(TypeError) as exc_info:\n            lsh.field_units = 42\n        exc_msg = exc_info.value.args[0]\n        assert exc_msg == \"Field units must be a Python quantity\"\n\n\ndef test_lakeshore475_temp_units():\n    \"\"\"\n    Get / set temperature unit on device.\n    \"\"\"\n    with expected_protocol(\n        ik.lakeshore.Lakeshore475,\n        [\"TUNIT?\", \"TUNIT 2\"],\n        [\"1\"],\n    ) as lsh:\n        assert lsh.temp_units == u.celsius\n        lsh.temp_units = u.kelvin\n\n\ndef test_lakeshore475_temp_units_invalid_unit():\n    \"\"\"\n    Raise a ValueError if an invalid unit is given.\n    \"\"\"\n    with expected_protocol(\n        ik.lakeshore.Lakeshore475,\n        [],\n        [],\n    ) as lsh:\n        with pytest.raises(TypeError) as exc_info:\n            lsh.temp_units = u.fahrenheit\n        exc_msg = exc_info.value.args[0]\n        assert exc_msg == \"Not an acceptable Python quantities object\"\n\n\ndef test_lakeshore475_temp_units_not_a_unit():\n    \"\"\"\n    Raise a ValueError if something else than a quantity is given.\n    \"\"\"\n    with expected_protocol(\n        ik.lakeshore.Lakeshore475,\n        [],\n        [],\n    ) as lsh:\n        with pytest.raises(TypeError) as exc_info:\n            lsh.temp_units = 42\n        exc_msg = exc_info.value.args[0]\n        assert exc_msg == \"Temperature units must be a Python quantity\"\n\n\ndef test_lakeshore475_field_setpoint():\n    \"\"\"\n    Get / set field set point.\n    \"\"\"\n    with expected_protocol(\n        ik.lakeshore.Lakeshore475,\n        [\n            \"CSETP?\",\n            \"UNIT?\",\n            \"UNIT?\",\n            \"CSETP 1.0\",  # send 1 tesla\n            \"UNIT?\",\n            \"CSETP 23.0\",  # send 23 unitless (equals gauss)\n        ],\n        [\"10.\", \"1\", \"2\", \"1\"],\n    ) as lsh:\n        assert lsh.field_setpoint == u.Quantity(10, u.gauss)\n\n        lsh.field_setpoint = u.Quantity(1.0, u.tesla)\n        lsh.field_setpoint = 23.0\n\n\ndef test_lakeshore475_field_setpoint_wrong_units():\n    \"\"\"\n    Setting the field setpoint with the wrong units\n    \"\"\"\n    with expected_protocol(\n        ik.lakeshore.Lakeshore475,\n        [\n            \"UNIT?\",\n        ],\n        [\"1\"],\n    ) as lsh:\n        with pytest.raises(ValueError) as exc_info:\n            lsh.field_setpoint = u.Quantity(1.0, u.tesla)\n        exc_msg = exc_info.value.args[0]\n        assert \"Field setpoint must be specified in the same units\" in exc_msg\n\n\ndef test_lakeshore475_field_get_control_params():\n    \"\"\"\n    Get field control parameters.\n    \"\"\"\n    with expected_protocol(\n        ik.lakeshore.Lakeshore475,\n        [\"CPARAM?\", \"UNIT?\"],\n        [\"+1.0E+0,+1.0E+1,+4.2E+1,+1.0E+2\", \"2\"],  # teslas\n    ) as lsh:\n        current_params = lsh.field_control_params\n        assert current_params == (\n            1.0,\n            10.0,\n            u.Quantity(42.0, u.tesla / u.min),\n            u.Quantity(100.0, u.volt / u.min),\n        )\n\n\ndef test_lakeshore475_field_set_control_params():\n    \"\"\"\n    Set field control parameters, unitful and using assumed units.\n    \"\"\"\n    with expected_protocol(\n        ik.lakeshore.Lakeshore475,\n        [\"UNIT?\", \"CPARAM 5.0,50.0,120.0,60.0\", \"UNIT?\", \"CPARAM 5.0,50.0,120.0,60.0\"],\n        [\"2\", \"2\"],  # teslas  # teslas\n    ) as lsh:\n        # currently set units are used\n        lsh.field_control_params = (\n            5.0,\n            50.0,\n            u.Quantity(120.0, u.tesla / u.min),\n            u.Quantity(60.0, u.volt / u.min),\n        )\n        # no units are used\n        lsh.field_control_params = (5.0, 50.0, 120.0, 60.0)\n\n\ndef test_lakeshore475_field_set_control_params_not_a_tuple():\n    \"\"\"\n    Set field control parameters with wrong type.\n    \"\"\"\n    with expected_protocol(\n        ik.lakeshore.Lakeshore475,\n        [],\n        [],\n    ) as lsh:\n        with pytest.raises(TypeError) as exc_info:\n            lsh.field_control_params = 42\n        exc_msg = exc_info.value.args[0]\n        assert exc_msg == \"Field control parameters must be specified as \" \" a tuple\"\n\n\ndef test_lakeshore475_field_set_control_params_wrong_units():\n    \"\"\"\n    Set field control parameters with the wrong units\n    \"\"\"\n    with expected_protocol(\n        ik.lakeshore.Lakeshore475,\n        [\n            \"UNIT?\",\n        ],\n        [\n            \"1\",  # gauss\n        ],\n    ) as lsh:\n        with pytest.raises(ValueError) as exc_info:\n            lsh.field_control_params = (\n                5.0,\n                50.0,\n                u.Quantity(120.0, u.tesla / u.min),\n                u.Quantity(60.0, u.volt / u.min),\n            )\n        exc_msg = exc_info.value.args[0]\n        assert (\n            \"Field control params ramp rate must be specified in the same units\"\n            in exc_msg\n        )\n\n\ndef test_lakeshore475_p_value():\n    \"\"\"\n    Get / set p-value.\n    \"\"\"\n    with expected_protocol(\n        ik.lakeshore.Lakeshore475,\n        [\n            \"CPARAM?\",\n            \"UNIT?\",\n            \"CPARAM?\",\n            \"UNIT?\",\n            \"UNIT?\",\n            \"CPARAM 5.0,10.0,42.0,100.0\",\n        ],\n        [\n            \"+1.0E+0,+1.0E+1,+4.2E+1,+1.0E+2\",\n            \"2\",  # teslas\n            \"+1.0E+0,+1.0E+1,+4.2E+1,+1.0E+2\",\n            \"2\",  # teslas\n            \"2\",\n        ],\n    ) as lsh:\n        assert lsh.p_value == 1.0\n        lsh.p_value = 5.0\n\n\ndef test_lakeshore475_i_value():\n    \"\"\"\n    Get / set i-value.\n    \"\"\"\n    with expected_protocol(\n        ik.lakeshore.Lakeshore475,\n        [\n            \"CPARAM?\",\n            \"UNIT?\",\n            \"CPARAM?\",\n            \"UNIT?\",\n            \"UNIT?\",\n            \"CPARAM 1.0,5.0,42.0,100.0\",\n        ],\n        [\n            \"+1.0E+0,+1.0E+1,+4.2E+1,+1.0E+2\",\n            \"2\",  # teslas\n            \"+1.0E+0,+1.0E+1,+4.2E+1,+1.0E+2\",\n            \"2\",  # teslas\n            \"2\",\n        ],\n    ) as lsh:\n        assert lsh.i_value == 10.0\n        lsh.i_value = 5.0\n\n\ndef test_lakeshore475_ramp_rate():\n    \"\"\"\n    Get / set ramp rate, unitful and not.\n    \"\"\"\n    with expected_protocol(\n        ik.lakeshore.Lakeshore475,\n        [\n            \"CPARAM?\",\n            \"UNIT?\",\n            \"UNIT?\",\n            \"CPARAM?\",\n            \"UNIT?\",\n            \"UNIT?\",\n            \"CPARAM 1.0,10.0,420.0,100.0\",\n            \"UNIT?\",\n            \"CPARAM?\",\n            \"UNIT?\",\n            \"UNIT?\",\n            \"CPARAM 1.0,10.0,420.0,100.0\",\n        ],\n        [\n            \"+1.0E+0,+1.0E+1,+4.2E+1,+1.0E+2\",\n            \"2\",  # teslas\n            \"2\",\n            \"+1.0E+0,+1.0E+1,+4.2E+1,+1.0E+2\",\n            \"2\",  # teslas\n            \"2\",\n            \"2\",\n            \"+1.0E+0,+1.0E+1,+4.2E+1,+1.0E+2\",\n            \"2\",\n            \"2\",\n        ],\n    ) as lsh:\n        assert lsh.ramp_rate == u.Quantity(42.0, u.tesla / u.min)\n        lsh.ramp_rate = u.Quantity(420.0, u.tesla / u.min)\n        lsh.ramp_rate = 420.0\n\n\ndef test_lakeshore475_control_slope_limit():\n    \"\"\"\n    Get / set slope limit, unitful and not.\n    \"\"\"\n    with expected_protocol(\n        ik.lakeshore.Lakeshore475,\n        [\n            \"CPARAM?\",\n            \"UNIT?\",\n            \"CPARAM?\",\n            \"UNIT?\",\n            \"UNIT?\",\n            \"CPARAM 1.0,10.0,42.0,42.0\",\n            \"CPARAM?\",\n            \"UNIT?\",\n            \"UNIT?\",\n            \"CPARAM 1.0,10.0,42.0,42.0\",\n        ],\n        [\n            \"+1.0E+0,+1.0E+1,+4.2E+1,+1.0E+2\",\n            \"2\",  # teslas\n            \"+1.0E+0,+1.0E+1,+4.2E+1,+1.0E+2\",\n            \"2\",  # teslas\n            \"2\",\n            \"+1.0E+0,+1.0E+1,+4.2E+1,+1.0E+2\",\n            \"2\",\n            \"2\",\n        ],\n    ) as lsh:\n        assert lsh.control_slope_limit == u.Quantity(100.0, u.V / u.min)\n        lsh.control_slope_limit = u.Quantity(42000.0, u.mV / u.min)\n        lsh.control_slope_limit = 42.0\n\n\ndef test_lakeshore475_control_mode():\n    \"\"\"\n    Get / set control mode.\n    \"\"\"\n    with expected_protocol(\n        ik.lakeshore.Lakeshore475,\n        [\"CMODE?\", \"CMODE 1\"],\n        [\"0\"],\n    ) as lsh:\n        assert not lsh.control_mode\n        lsh.control_mode = True\n\n\n# TEST LAKESHORE475 CLASS METHODS #\n\n\ndef test_lakeshore475_change_measurement_mode():\n    \"\"\"\n    Change the measurement mode with valid values and ensure properly\n    sent to device.\n    \"\"\"\n    with expected_protocol(\n        ik.lakeshore.Lakeshore475,\n        [\"RDGMODE 1,2,3,2,1\"],\n        [],\n    ) as lsh:\n        # parameters to send\n        mode = lsh.Mode.dc\n        resolution = 4\n        filter_type = lsh.Filter.lowpass\n        peak_mode = lsh.PeakMode.pulse\n        peak_disp = lsh.PeakDisplay.positive\n        # send them\n        lsh.change_measurement_mode(mode, resolution, filter_type, peak_mode, peak_disp)\n\n\ndef test_lakeshore475_change_measurement_mode_mismatched_type():\n    \"\"\"\n    Ensure that mismatched input type raises a TypeError.\n    \"\"\"\n    with expected_protocol(\n        ik.lakeshore.Lakeshore475,\n        [],\n        [],\n    ) as lsh:\n        # parameters to send\n        mode = lsh.Mode.dc\n        resolution = 4\n        filter_type = lsh.Filter.lowpass\n        peak_mode = lsh.PeakMode.pulse\n        peak_disp = lsh.PeakDisplay.positive\n        # check mode\n        with pytest.raises(TypeError) as exc_info:\n            lsh.change_measurement_mode(\n                42, resolution, filter_type, peak_mode, peak_disp\n            )\n        exc_msg = exc_info.value.args[0]\n        assert (\n            exc_msg == f\"Mode setting must be a `Lakeshore475.Mode` \"\n            f\"value, got {int} instead.\"\n        )\n        # check resolution\n        with pytest.raises(TypeError) as exc_info:\n            lsh.change_measurement_mode(mode, 3.14, filter_type, peak_mode, peak_disp)\n        exc_msg = exc_info.value.args[0]\n        assert exc_msg == 'Parameter \"resolution\" must be an integer.'\n        # check filter_type\n        with pytest.raises(TypeError) as exc_info:\n            lsh.change_measurement_mode(mode, resolution, 42, peak_mode, peak_disp)\n        exc_msg = exc_info.value.args[0]\n        assert (\n            exc_msg == f\"Filter type setting must be a \"\n            f\"`Lakeshore475.Filter` value, \"\n            f\"got {int} instead.\"\n        )\n        # check peak_mode\n        with pytest.raises(TypeError) as exc_info:\n            lsh.change_measurement_mode(mode, resolution, filter_type, 42, peak_disp)\n        exc_msg = exc_info.value.args[0]\n        assert (\n            exc_msg == f\"Peak measurement type setting must be a \"\n            f\"`Lakeshore475.PeakMode` value, \"\n            f\"got {int} instead.\"\n        )\n        # check peak_display\n        with pytest.raises(TypeError) as exc_info:\n            lsh.change_measurement_mode(mode, resolution, filter_type, peak_mode, 42)\n        exc_msg = exc_info.value.args[0]\n        assert (\n            exc_msg == f\"Peak display type setting must be a \"\n            f\"`Lakeshore475.PeakDisplay` value, \"\n            f\"got {int} instead.\"\n        )\n\n\ndef test_lakeshore475_change_measurement_mode_invalid_resolution():\n    \"\"\"\n    Ensure that mismatched input type raises a TypeError.\n    \"\"\"\n    with expected_protocol(\n        ik.lakeshore.Lakeshore475,\n        [],\n        [],\n    ) as lsh:\n        # parameters to send\n        mode = lsh.Mode.dc\n        filter_type = lsh.Filter.lowpass\n        peak_mode = lsh.PeakMode.pulse\n        peak_disp = lsh.PeakDisplay.positive\n        # check resolution too low\n        with pytest.raises(ValueError) as exc_info:\n            lsh.change_measurement_mode(mode, 2, filter_type, peak_mode, peak_disp)\n        exc_msg = exc_info.value.args[0]\n        assert exc_msg == \"Only 3,4,5 are valid resolutions.\"\n        # check resolution too high\n        with pytest.raises(ValueError) as exc_info:\n            lsh.change_measurement_mode(mode, 6, filter_type, peak_mode, peak_disp)\n        exc_msg = exc_info.value.args[0]\n        assert exc_msg == \"Only 3,4,5 are valid resolutions.\"\n"
  },
  {
    "path": "tests/test_mettler_toledo/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_mettler_toledo/test_mt_sics.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nTests for the Mettler Toledo Standard Interface Command Set (SICS).\n\"\"\"\n\n# IMPORTS #####################################################################\n\nimport pytest\n\nimport instruments as ik\nfrom instruments.units import ureg as u\nfrom tests import expected_protocol\n\n# TESTS #######################################################################\n\n\ndef test_clear_tare():\n    \"\"\"Clear the tare value.\"\"\"\n    with expected_protocol(\n        ik.mettler_toledo.MTSICS, [\"TAC\"], [\"TAC A\"], \"\\r\\n\"\n    ) as inst:\n        inst.clear_tare()\n\n\ndef test_reset():\n    \"\"\"Reset the balance.\"\"\"\n    with expected_protocol(\n        ik.mettler_toledo.MTSICS, [\"@\"], ['@ A \"123456789\"'], \"\\r\\n\"\n    ) as inst:\n        inst.reset()\n\n\n@pytest.mark.parametrize(\"mode\", ik.mettler_toledo.MTSICS.WeightMode)\ndef test_tare(mode):\n    \"\"\"Tare the balance.\"\"\"\n    msg = \"TI\" if mode.value else \"T\"\n    with expected_protocol(\n        ik.mettler_toledo.MTSICS,\n        [f\"{msg}\", \"T\", \"TI\"],\n        [f\"{msg} A 2.486 g\", \"T A 2.486 g\", \"TI A 2.486 g\"],\n        \"\\r\\n\",\n    ) as inst:\n        inst.weight_mode = mode\n        inst.tare()\n        inst.tare(immediately=False)\n        inst.tare(immediately=True)\n\n\n@pytest.mark.parametrize(\"mode\", ik.mettler_toledo.MTSICS.WeightMode)\ndef test_zero(mode):\n    \"\"\"Zero the balance.\"\"\"\n    msg = \"ZI\" if mode.value else \"Z\"\n    with expected_protocol(\n        ik.mettler_toledo.MTSICS,\n        [f\"{msg}\", \"Z\", \"ZI\"],\n        [f\"{msg} A\", \"Z A\", \"ZI A\"],\n        \"\\r\\n\",\n    ) as inst:\n        inst.weight_mode = mode\n        inst.zero()\n        inst.zero(immediately=False)\n        inst.zero(immediately=True)\n\n\n@pytest.mark.parametrize(\"err\", [\"I\", \"L\", \"+\", \"-\"])\ndef test_command_error_checking(err):\n    \"\"\"Raise OSError if command encounters an error.\"\"\"\n    with expected_protocol(\n        ik.mettler_toledo.MTSICS, [\"S\"], [f\"S {err}\"], \"\\r\\n\"\n    ) as inst:\n        with pytest.raises(OSError):\n            inst.weight\n\n\n@pytest.mark.parametrize(\"err\", [\"ES\", \"ET\", \"EL\"])\ndef test_general_error_checking(err):\n    \"\"\"Raise OSError if general error encountered.\"\"\"\n    with expected_protocol(ik.mettler_toledo.MTSICS, [\"S\"], [f\"{err}\"], \"\\r\\n\") as inst:\n        with pytest.raises(OSError):\n            inst.weight\n\n\n# PROPERTIES #\n\n\ndef test_mt_sics():\n    \"\"\"Get MT-SICS level and MT-SICS versions.\"\"\"\n    with expected_protocol(\n        ik.mettler_toledo.MTSICS, [\"I1\"], [\"I1 A 01 2.00 2.00 2.00 1.0\"], \"\\r\\n\"\n    ) as inst:\n        assert inst.mt_sics == [\"01\", \"2.00\", \"2.00\", \"2.00\", \"1.0\"]\n\n\ndef test_mt_sics_commands():\n    \"\"\"Get all available MT-SICS implemented commands.\"\"\"\n    with expected_protocol(\n        ik.mettler_toledo.MTSICS, [\"I0\"], ['0 B 0 \"I0\"\\r\\nI0 B 1 \"D\"'], \"\\r\\n\"\n    ) as inst:\n        assert inst.mt_sics_commands == [[\"0\", \"I0\"], [\"1\", \"D\"]]\n\n\ndef test_mt_sics_commands_timeout(mocker):\n    \"\"\"Ensure that timeout error is caught appropriately.\"\"\"\n    inst_class = ik.mettler_toledo.MTSICS\n    # mock reading raises timeout error\n    os_error_mock = mocker.Mock()\n    os_error_mock.side_effect = OSError\n    mocker.patch.object(inst_class, \"read\", os_error_mock)\n\n    with expected_protocol(inst_class, [\"I0\"], [], \"\\r\\n\") as inst:\n        timeout = inst.timeout\n        assert inst.mt_sics_commands == []\n        assert inst.timeout == timeout\n\n\ndef test_name():\n    \"\"\"Get / Set balance name.\"\"\"\n    with expected_protocol(\n        ik.mettler_toledo.MTSICS,\n        ['I10 \"My Balance\"', \"I10\"],\n        ['I10 A \"Balance\"', 'I10 A \"My Balance\"'],\n        \"\\r\\n\",\n    ) as inst:\n        inst.name = \"My Balance\"\n        assert inst.name == \"My Balance\"\n\n\ndef test_name_too_long():\n    \"\"\"Raise ValueError if name is too long.\"\"\"\n    with expected_protocol(ik.mettler_toledo.MTSICS, [], [], \"\\r\\n\") as inst:\n        with pytest.raises(ValueError):\n            inst.name = \"My Balance is too long\"\n\n\ndef test_serial_number():\n    \"\"\"Get the serial number of the instrument.\"\"\"\n    with expected_protocol(\n        ik.mettler_toledo.MTSICS, [\"I4\"], [\"I4 A 0123456789\"], \"\\r\\n\"\n    ) as inst:\n        assert inst.serial_number == \"0123456789\"\n\n\ndef test_tare_value():\n    \"\"\"Set / get the tare value.\"\"\"\n    with expected_protocol(\n        ik.mettler_toledo.MTSICS,\n        [\"TA 2.486 g\", \"TA\"],\n        [\"TA A 2.486 g\", \"TA A 2.486 g\"],\n        \"\\r\\n\",\n    ) as inst:\n        inst.tare_value = u.Quantity(2.486, u.gram)\n        assert inst.tare_value == u.Quantity(2.486, u.gram)\n\n\n@pytest.mark.parametrize(\"mode\", ik.mettler_toledo.MTSICS.WeightMode)\ndef test_weight(mode):\n    \"\"\"Get the stable weight.\"\"\"\n    msg = \"SI\" if mode.value else \"S\"\n    with expected_protocol(\n        ik.mettler_toledo.MTSICS, [f\"{msg}\"], [f\"{msg} A 1.234 g\"], \"\\r\\n\"\n    ) as inst:\n        inst.weight_mode = mode\n        assert inst.weight == u.Quantity(1.234, u.gram)\n\n\ndef test_weight_immediately_dynamic_mode():\n    \"\"\"Raise UserWarning if balance is in dynamic mode.\"\"\"\n    with expected_protocol(\n        ik.mettler_toledo.MTSICS, [\"SI\"], [\"S D 1.234 g\"], \"\\r\\n\"\n    ) as inst:\n        inst.weight_mode = inst.WeightMode.immediately\n        with pytest.warns(UserWarning):\n            _ = inst.weight\n\n\ndef test_weight_mode_type_error():\n    \"\"\"Raise TypeError if weight mode is set with wrong type.\"\"\"\n    with expected_protocol(ik.mettler_toledo.MTSICS, [], [], \"\\r\\n\") as inst:\n        with pytest.raises(TypeError):\n            inst.weight_mode = True\n"
  },
  {
    "path": "tests/test_minghe/test_minghe_mhs5200a.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the MingHe MHS52000a\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport pytest\nfrom instruments.units import ureg as u\n\nimport instruments as ik\nfrom tests import expected_protocol\n\n# TESTS ######################################################################\n\n\ndef test_mhs_amplitude():\n    with expected_protocol(\n        ik.minghe.MHS5200,\n        [\":r1a\", \":r2a\", \":s1a660\", \":s2a800\"],\n        [\":r1a330\", \":r2a500\", \"ok\", \"ok\"],\n        sep=\"\\r\\n\",\n    ) as mhs:\n        assert mhs.channel[0].amplitude[0] == 3.3 * u.V\n        assert mhs.channel[1].amplitude[0] == 5.0 * u.V\n        mhs.channel[0].amplitude = 6.6 * u.V\n        mhs.channel[1].amplitude = 8.0 * u.V\n\n\ndef test_mhs_amplitude_dbm_notimplemented():\n    with expected_protocol(ik.minghe.MHS5200, [], [], sep=\"\\r\\n\") as mhs:\n        with pytest.raises(NotImplementedError):\n            mhs.channel[0].amplitude = u.Quantity(6.6, u.dBm)\n\n\ndef test_mhs_duty_cycle():\n    with expected_protocol(\n        ik.minghe.MHS5200,\n        [\":r1d\", \":r2d\", \":s1d6\", \":s2d80\"],\n        [\":r1d010\", \":r2d100\", \"ok\", \"ok\"],\n        sep=\"\\r\\n\",\n    ) as mhs:\n        assert mhs.channel[0].duty_cycle == 1.0\n        assert mhs.channel[1].duty_cycle == 10.0\n        mhs.channel[0].duty_cycle = 0.06\n        mhs.channel[1].duty_cycle = 0.8\n\n\ndef test_mhs_enable():\n    with expected_protocol(\n        ik.minghe.MHS5200,\n        [\":r1b\", \":r2b\", \":s1b0\", \":s2b1\"],\n        [\":r1b1\", \":r2b0\", \"ok\", \"ok\"],\n        sep=\"\\r\\n\",\n    ) as mhs:\n        assert mhs.channel[0].enable\n        assert not mhs.channel[1].enable\n        mhs.channel[0].enable = False\n        mhs.channel[1].enable = True\n\n\ndef test_mhs_frequency():\n    with expected_protocol(\n        ik.minghe.MHS5200,\n        [\":r1f\", \":r2f\", \":s1f600000\", \":s2f800000\"],\n        [\":r1f3300000\", \":r2f50000000\", \"ok\", \"ok\"],\n        sep=\"\\r\\n\",\n    ) as mhs:\n        assert mhs.channel[0].frequency == 33.0 * u.kHz\n        assert mhs.channel[1].frequency == 500.0 * u.kHz\n        mhs.channel[0].frequency = 6 * u.kHz\n        mhs.channel[1].frequency = 8 * u.kHz\n\n\ndef test_mhs_offset():\n    with expected_protocol(\n        ik.minghe.MHS5200,\n        [\":r1o\", \":r2o\", \":s1o60\", \":s2o180\"],\n        [\":r1o120\", \":r2o0\", \"ok\", \"ok\"],\n        sep=\"\\r\\n\",\n    ) as mhs:\n        assert mhs.channel[0].offset == 0\n        assert mhs.channel[1].offset == -1.2\n        mhs.channel[0].offset = -0.6\n        mhs.channel[1].offset = 0.6\n\n\ndef test_mhs_phase():\n    with expected_protocol(\n        ik.minghe.MHS5200,\n        [\":r1p\", \":r2p\", \":s1p60\", \":s2p180\"],\n        [\":r1p120\", \":r2p0\", \"ok\", \"ok\"],\n        sep=\"\\r\\n\",\n    ) as mhs:\n        assert mhs.channel[0].phase == 120 * u.degree\n        assert mhs.channel[1].phase == 0 * u.degree\n        mhs.channel[0].phase = 60\n        mhs.channel[1].phase = 180\n\n\ndef test_mhs_wave_type():\n    with expected_protocol(\n        ik.minghe.MHS5200,\n        [\":r1w\", \":r2w\", \":s1w2\", \":s2w3\"],\n        [\":r1w0\", \":r2w1\", \"ok\", \"ok\"],\n        sep=\"\\r\\n\",\n    ) as mhs:\n        assert mhs.channel[0].function == mhs.Function.sine\n        assert mhs.channel[1].function == mhs.Function.square\n        mhs.channel[0].function = mhs.Function.triangular\n        mhs.channel[1].function = mhs.Function.sawtooth_up\n\n\ndef test_mhs_serial_number():\n    with expected_protocol(\n        ik.minghe.MHS5200,\n        [\":r0c\"],\n        [\n            \":r0c5225A1\",\n        ],\n        sep=\"\\r\\n\",\n    ) as mhs:\n        assert mhs.serial_number == \"5225A1\"\n\n\ndef test_mhs_get_amplitude():\n    \"\"\"Raise NotImplementedError when trying to get amplitude\"\"\"\n    with expected_protocol(ik.minghe.MHS5200, [], [], sep=\"\\r\\n\") as mhs:\n        with pytest.raises(NotImplementedError):\n            mhs._get_amplitude_()\n\n\ndef test_mhs_set_amplitude():\n    \"\"\"Raise NotImplementedError when trying to set amplitude\"\"\"\n    with expected_protocol(ik.minghe.MHS5200, [], [], sep=\"\\r\\n\") as mhs:\n        with pytest.raises(NotImplementedError):\n            mhs._set_amplitude_(1, 2)\n"
  },
  {
    "path": "tests/test_named_struct.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for named structures.\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nfrom unittest import TestCase\n\nfrom hypothesis import given\nimport hypothesis.strategies as st\n\nfrom instruments.named_struct import Field, StringField, Padding, NamedStruct\n\n# TESTS ######################################################################\n\n# We disable pylint warnings that are not as applicable for unit tests.\n# pylint: disable=no-member,protected-access,blacklisted-name,missing-docstring,no-self-use\n\n\nclass TestNamedStruct(TestCase):\n    @given(\n        st.integers(min_value=0, max_value=0x7FFF * 2 + 1),\n        st.integers(min_value=0, max_value=0xFF),\n    )\n    def test_roundtrip(self, var1, var2):\n        class Foo(NamedStruct):\n            a = Field(\"H\")\n            padding = Padding(12)\n            b = Field(\"B\")\n\n        foo = Foo(a=var1, b=var2)\n        assert Foo.unpack(foo.pack()) == foo\n\n    def test_str(self):\n        class Foo(NamedStruct):\n            a = StringField(8, strip_null=False)\n            b = StringField(9, strip_null=True)\n            c = StringField(2, encoding=\"utf-8\")\n\n        foo = Foo(a=\"0123456\\x00\", b=\"abc\", c=\"α\")\n        assert Foo.unpack(foo.pack()) == foo\n\n        # Also check that we can get fields out directly.\n        self.assertEqual(foo.a, \"0123456\\x00\")\n        self.assertEqual(foo.b, \"abc\")\n        self.assertEqual(foo.c, \"α\")\n\n    def test_negative_len(self):\n        \"\"\"\n        Checks whether negative field lengths correctly raise.\n        \"\"\"\n        with self.assertRaises(TypeError):\n\n            class Foo(NamedStruct):  # pylint: disable=unused-variable\n                a = StringField(-1)\n\n    def test_equality(self):\n        class Foo(NamedStruct):\n            a = Field(\"H\")\n            b = Field(\"B\")\n            c = StringField(5, encoding=\"utf8\", strip_null=True)\n\n        foo1 = Foo(a=0x1234, b=0x56, c=\"ω\")\n        foo2 = Foo(a=0xABCD, b=0xEF, c=\"α\")\n\n        assert foo1 == foo1\n        assert foo1 != foo2\n"
  },
  {
    "path": "tests/test_newport/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_newport/test_agilis.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the Agilis Controller\n\"\"\"\n\n# IMPORTS #####################################################################\n\nimport time\n\nimport pytest\n\nimport instruments as ik\nfrom tests import expected_protocol\n\n# TESTS #######################################################################\n\n\n# pylint: disable=protected-access\n\n\n# FIXTURES #\n\n\n@pytest.fixture(autouse=True)\ndef mock_time(mocker):\n    \"\"\"Mock `time.sleep` for and set to zero as autouse fixture.\"\"\"\n    return mocker.patch.object(time, \"sleep\", return_value=None)\n\n\n# CONTROLLER TESTS #\n\n\ndef test_aguc2_enable_remote_mode():\n    \"\"\"\n    Check enabling of remote mode.\n    \"\"\"\n    with expected_protocol(ik.newport.AGUC2, [\"MR\", \"ML\"], [], sep=\"\\r\\n\") as agl:\n        agl.enable_remote_mode = True\n        assert agl.enable_remote_mode is True\n        agl.enable_remote_mode = False\n        assert agl.enable_remote_mode is False\n\n\ndef test_aguc2_error_previous_command_no_error():\n    \"\"\"Test return of an error value (`No Error`) from previous command.\"\"\"\n    with expected_protocol(ik.newport.AGUC2, [\"TE\"], [\"TE0\"], sep=\"\\r\\n\") as agl:\n        assert agl.error_previous_command == \"No error\"\n\n\ndef test_aguc2_error_previous_command():\n    \"\"\"\n    Check the call error of previous command routine. Note that the test will\n    return \"Error code must be given as an integer.\" will be returned because\n    no actual error code is fed to the error message checker.\n    \"\"\"\n    with expected_protocol(ik.newport.AGUC2, [\"TE\"], [], sep=\"\\r\\n\") as agl:\n        assert agl.error_previous_command == \"Error code query failed.\"\n\n\ndef test_aguc2_firmware_version():\n    \"\"\"\n    Check firmware version\n    AG-UC2 v2.2.1\n    \"\"\"\n    with expected_protocol(\n        ik.newport.AGUC2, [\"VE\"], [\"AG-UC2 v2.2.1\"], sep=\"\\r\\n\"\n    ) as agl:\n        assert agl.firmware_version == \"AG-UC2 v2.2.1\"\n\n\ndef test_aguc2_limit_status():\n    \"\"\"\n    Check the limit status routine.\n    \"\"\"\n    with expected_protocol(\n        ik.newport.AGUC2, [\"MR\", \"PH\"], [\"PH0\"], sep=\"\\r\\n\"  # initialize remote mode\n    ) as agl:\n        assert agl.limit_status == \"PH0\"\n\n\ndef test_aguc2_sleep_time():\n    \"\"\"\n    Check setting, getting the sleep time.\n    \"\"\"\n    with expected_protocol(ik.newport.AGUC2, [], [], sep=\"\\r\\n\") as agl:\n        agl.sleep_time = 3\n        assert agl.sleep_time == 3\n        with pytest.raises(ValueError):\n            agl.sleep_time = -3.14\n\n\ndef test_aguc2_reset_controller():\n    \"\"\"\n    Check reset controller function.\n    \"\"\"\n    with expected_protocol(ik.newport.AGUC2, [\"RS\"], [], sep=\"\\r\\n\") as agl:\n        agl.reset_controller()\n        assert agl.enable_remote_mode is False\n\n\ndef test_aguc2_ag_sendcmd():\n    \"\"\"\n    Check agilis sendcommand wrapper.\n    \"\"\"\n    with expected_protocol(\n        ik.newport.AGUC2, [\"MR\"], [], sep=\"\\r\\n\"  # some command, here remote mode\n    ) as agl:\n        agl.ag_sendcmd(\"MR\")\n\n\ndef test_aguc2_ag_query():\n    \"\"\"\n    Check agilis query wrapper.\n    \"\"\"\n    with expected_protocol(\n        ik.newport.AGUC2, [\"VE\"], [\"AG-UC2 v2.2.1\"], sep=\"\\r\\n\"\n    ) as agl:\n        assert agl.ag_query(\"VE\") == \"AG-UC2 v2.2.1\"\n\n\ndef test_aguc2_ag_query_io_error(mocker):\n    \"\"\"Respond with `Query timed out.` if IOError occurs.\"\"\"\n    # mock the query to raise an IOError\n    io_error_mock = mocker.Mock()\n    io_error_mock.side_effect = IOError\n    mocker.patch.object(ik.newport.AGUC2, \"query\", io_error_mock)\n\n    with expected_protocol(ik.newport.AGUC2, [], [], sep=\"\\r\\n\") as agl:\n        assert agl.ag_query(\"VE\") == \"Query timed out.\"\n\n\n# AXIS TESTS #\n\n\n@pytest.mark.parametrize(\"axis\", ik.newport.AGUC2.Axes)\ndef test_aguc2_axis_init_enum(axis):\n    \"\"\"Initialize an axis externally with an enum.\"\"\"\n    with expected_protocol(ik.newport.AGUC2, [], [], sep=\"\\r\\n\") as agl:\n        ax = ik.newport.agilis.AGUC2.Axis(agl, axis)\n        assert ax._ax == axis.value\n\n\ndef test_aguc2_axis_init_wrong_type():\n    \"\"\"Raise TypeError when not initialized from AGUC2 parent class.\"\"\"\n    with pytest.raises(TypeError) as err_info:\n        ik.newport.agilis.AGUC2.Axis(42, ik.newport.AGUC2.Axes.X)\n    err_msg = err_info.value.args[0]\n    assert err_msg == \"Don't do that.\"\n\n\n@pytest.mark.parametrize(\"axis\", ik.newport.AGUC2.Axes)\n@pytest.mark.parametrize(\"still\", (True, False))\ndef test_aguc2_axis_am_i_still(axis, still):\n    \"\"\"Check if axis is still or not.\"\"\"\n    with expected_protocol(\n        ik.newport.AGUC2,\n        [\n            \"MR\",  # initialize remote mode\n            f\"{axis.value} TS\",\n        ],\n        [f\"{axis.value}TS {int(not still)}\"],\n        sep=\"\\r\\n\",\n    ) as agl:\n        assert agl.axis[axis].am_i_still() == still\n\n\ndef test_aguc2_axis_am_i_still_io_error():\n    \"\"\"Raise IOError if max retries achieved.\"\"\"\n    with expected_protocol(\n        ik.newport.AGUC2,\n        [\"MR\", \"1 TS\", \"2 TS\", \"2 TS\", \"2 TS\"],  # initialize remote mode\n        [],\n        sep=\"\\r\\n\",\n    ) as agl:\n        with pytest.raises(IOError):\n            agl.axis[\"X\"].am_i_still(max_retries=1)\n        with pytest.raises(IOError):\n            agl.axis[\"Y\"].am_i_still(max_retries=3)\n\n\n@pytest.mark.parametrize(\"axis\", ik.newport.AGUC2.Axes)\ndef test_aguc2_axis_axis_status_not_moving(axis):\n    \"\"\"Check status of axis and return axis not moving.\"\"\"\n    with expected_protocol(\n        ik.newport.AGUC2,\n        [\n            \"MR\",  # initialize remote mode\n            f\"{axis.value} TS\",\n        ],\n        [f\"{axis.value}TS0\"],\n        sep=\"\\r\\n\",\n    ) as agl:\n        assert agl.axis[axis].axis_status == \"Ready (not moving).\"\n\n\ndef test_aguc2_axis_axis_status():\n    \"\"\"\n    Check the status of the axis. Note that the test will return\n    \"Status code query failed.\" since no instrument is connected.\n    \"\"\"\n    with expected_protocol(\n        ik.newport.AGUC2,\n        [\"MR\", \"1 TS\", \"2 TS\"],  # initialize remote mode\n        [],\n        sep=\"\\r\\n\",\n    ) as agl:\n        assert agl.axis[\"X\"].axis_status == \"Status code query failed.\"\n        assert agl.axis[\"Y\"].axis_status == \"Status code query failed.\"\n\n\ndef test_aguc2_axis_jog():\n    \"\"\"Get / set jog function.\"\"\"\n    with expected_protocol(\n        ik.newport.AGUC2,\n        [\"MR\", \"1 JA 3\", \"1 JA?\", \"2 JA -4\", \"2 JA?\"],  # initialize remote mode\n        [\"1JA3\", \"2JA-4\"],\n        sep=\"\\r\\n\",\n    ) as agl:\n        agl.axis[\"X\"].jog = 3\n        assert agl.axis[\"X\"].jog == 3\n        agl.axis[\"Y\"].jog = -4\n        assert agl.axis[\"Y\"].jog == -4\n        with pytest.raises(ValueError):\n            agl.axis[\"X\"].jog = -5\n        with pytest.raises(ValueError):\n            agl.axis[\"Y\"].jog = 5\n\n\ndef test_aguc2_axis_number_of_steps():\n    \"\"\"\n    Check the number of steps function.\n    \"\"\"\n    with expected_protocol(\n        ik.newport.AGUC2,\n        [\n            \"MR\",  # initialize remote mode\n            \"1 TP\",\n        ],\n        [\"1TP0\"],\n        sep=\"\\r\\n\",\n    ) as agl:\n        assert agl.axis[\"X\"].number_of_steps == 0\n\n\ndef test_aguc2_axis_move_relative():\n    \"\"\"\n    Check the move relative function.\n    \"\"\"\n    with expected_protocol(\n        ik.newport.AGUC2,\n        [\"MR\", \"1 PR 1000\", \"1 PR?\", \"2 PR -340\", \"2 PR?\"],  # initialize remote mode\n        [\"1PR1000\", \"2PR-340\"],\n        sep=\"\\r\\n\",\n    ) as agl:\n        agl.axis[\"X\"].move_relative = 1000\n        assert agl.axis[\"X\"].move_relative == 1000\n        agl.axis[\"Y\"].move_relative = -340\n        assert agl.axis[\"Y\"].move_relative == -340\n        with pytest.raises(ValueError):\n            agl.axis[\"X\"].move_relative = 2147483648\n        with pytest.raises(ValueError):\n            agl.axis[\"Y\"].move_relative = -2147483649\n\n\ndef test_aguc2_axis_move_to_limit():\n    \"\"\"\n    Check for move to limit function.\n    This function is UNTESTED to work, here simply command sending is checked\n    \"\"\"\n    with expected_protocol(\n        ik.newport.AGUC2,\n        [\"MR\", \"2 MA 3\", \"2 MA?\"],  # initialize remote mode\n        [\"2MA42\"],\n        sep=\"\\r\\n\",\n    ) as agl:\n        agl.axis[\"Y\"].move_to_limit = 3\n        assert agl.axis[\"Y\"].move_to_limit == 42\n        with pytest.raises(ValueError):\n            agl.axis[\"Y\"].move_to_limit = -5\n        with pytest.raises(ValueError):\n            agl.axis[\"X\"].move_to_limit = 5\n\n\ndef test_aguc2_axis_step_amplitude():\n    \"\"\"\n    Check for step amplitude function\n    \"\"\"\n    with expected_protocol(\n        ik.newport.AGUC2,\n        [\n            \"MR\",  # initialize remote mode\n            \"1 SU-?\",\n            \"1 SU+?\",\n            \"1 SU -35\",\n            \"1 SU 47\",\n            \"1 SU -23\",\n            \"1 SU 13\",\n        ],\n        [\"1SU-35\", \"1SU+35\"],\n        sep=\"\\r\\n\",\n    ) as agl:\n        assert agl.axis[\"X\"].step_amplitude == (-35, 35)\n        agl.axis[\"X\"].step_amplitude = -35\n        agl.axis[\"X\"].step_amplitude = 47\n        agl.axis[\"X\"].step_amplitude = (-23, 13)\n        with pytest.raises(ValueError):\n            agl.axis[\"X\"].step_amplitude = 0\n        with pytest.raises(ValueError):\n            agl.axis[\"Y\"].step_amplitude = -51\n        with pytest.raises(ValueError):\n            agl.axis[\"Y\"].step_amplitude = 51\n\n\ndef test_aguc2_axis_step_delay():\n    \"\"\"\n    Check the step delay function.\n    \"\"\"\n    with expected_protocol(\n        ik.newport.AGUC2,\n        [\"MR\", \"2 DL?\", \"1 DL 1000\", \"1 DL 200\"],  # initialize remote mode\n        [\"2DL0\"],\n        sep=\"\\r\\n\",\n    ) as agl:\n        assert agl.axis[\"Y\"].step_delay == 0\n        agl.axis[\"X\"].step_delay = 1000\n        agl.axis[\"X\"].step_delay = 200\n        with pytest.raises(ValueError):\n            agl.axis[\"X\"].step_delay = -1\n        with pytest.raises(ValueError):\n            agl.axis[\"Y\"].step_delay = 2000001\n\n\ndef test_aguc2_axis_stop():\n    \"\"\"\n    Check the stop function.\n    \"\"\"\n    with expected_protocol(\n        ik.newport.AGUC2,\n        [\"MR\", \"1 ST\", \"2 ST\"],  # initialize remote mode\n        [],\n        sep=\"\\r\\n\",\n    ) as agl:\n        agl.axis[\"X\"].stop()\n        agl.axis[\"Y\"].stop()\n\n\ndef test_aguc2_axis_zero_position():\n    \"\"\"\n    Check the stop function.\n    \"\"\"\n    with expected_protocol(\n        ik.newport.AGUC2,\n        [\"MR\", \"1 ZP\", \"2 ZP\"],  # initialize remote mode\n        [],\n        sep=\"\\r\\n\",\n    ) as agl:\n        agl.axis[\"X\"].zero_position()\n        agl.axis[\"Y\"].zero_position()\n\n\n# FUNCTION TESTS #\n\n\ndef test_agilis_error_message():\n    # regular error messages\n    assert ik.newport.agilis.agilis_error_message(0) == \"No error\"\n    assert (\n        ik.newport.agilis.agilis_error_message(-6) == \"Not allowed in \" \"current state\"\n    )\n    # out of range integers\n    assert ik.newport.agilis.agilis_error_message(1) == \"An unknown error \" \"occurred.\"\n    assert ik.newport.agilis.agilis_error_message(-7) == \"An unknown error \" \"occurred.\"\n    # non-integers\n    assert (\n        ik.newport.agilis.agilis_error_message(-7.5) == \"Error code is \"\n        \"not an integer.\"\n    )\n    assert (\n        ik.newport.agilis.agilis_error_message(\"TE0\") == \"Error code is \"\n        \"not an integer.\"\n    )\n\n\ndef test_agilis_status_message():\n    # regular status messages\n    assert ik.newport.agilis.agilis_status_message(0) == \"Ready (not moving).\"\n    assert (\n        ik.newport.agilis.agilis_status_message(3)\n        == \"Moving to limit (currently executing \"\n        \"`measure_current_position`, `move_to_limit`, or \"\n        \"`move_absolute` command).\"\n    )\n    # out of range integers\n    assert (\n        ik.newport.agilis.agilis_status_message(4) == \"An unknown \" \"status occurred.\"\n    )\n    assert (\n        ik.newport.agilis.agilis_status_message(-1) == \"An unknown \" \"status occurred.\"\n    )\n    # non integers\n    assert (\n        ik.newport.agilis.agilis_status_message(3.14) == \"Status code is \"\n        \"not an integer.\"\n    )\n    assert (\n        ik.newport.agilis.agilis_status_message(\"1TS0\") == \"Status code \"\n        \"is not an \"\n        \"integer.\"\n    )\n"
  },
  {
    "path": "tests/test_newport/test_errors.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for NewportError class\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport datetime\n\nfrom instruments.newport.errors import NewportError\n\n# TESTS ######################################################################\n\n\n# pylint: disable=protected-access\n\n\ndef test_init_none():\n    \"\"\"Initialized with both arguments as `None`.\"\"\"\n    cls = NewportError()\n    assert isinstance(cls._timestamp, datetime.timedelta)\n    assert cls._errcode is None\n    assert cls._axis is None\n\n\ndef test_init_with_timestamp():\n    \"\"\"Initialized with a time stamp.\"\"\"\n    timestamp = datetime.datetime.now()\n    cls = NewportError(timestamp=timestamp)\n    assert isinstance(cls._timestamp, datetime.timedelta)\n\n\ndef test_init_with_error_code():\n    \"\"\"Initialize with non-axis specific error code.\"\"\"\n    err_code = 7  # parameter out of range\n    cls = NewportError(errcode=err_code)\n    assert cls._axis is None\n    assert cls._errcode == 7\n\n\ndef test_init_with_error_code_axis():\n    \"\"\"Initialize with axis-specific error code.\"\"\"\n    err_code = 313  # ax 3 not enabled\n    cls = NewportError(errcode=err_code)\n    assert cls._axis == 3\n    assert cls._errcode == 13\n\n\ndef test_get_message():\n    \"\"\"Get the message for a given error code.\"\"\"\n    err_code = \"7\"\n    cls = NewportError()\n    assert cls.get_message(err_code) == cls.messageDict[err_code]\n\n\ndef test_timestamp():\n    \"\"\"Get the timestamp for a given error.\"\"\"\n    cls = NewportError()\n    assert cls.timestamp == cls._timestamp\n\n\ndef test_errcode():\n    \"\"\"Get the error code reported by device.\"\"\"\n    cls = NewportError(errcode=7)\n    assert cls.errcode == cls._errcode\n\n\ndef test_axis():\n    \"\"\"Get axis for given error code.\"\"\"\n    cls = NewportError(errcode=313)\n    assert cls.axis == cls._axis\n"
  },
  {
    "path": "tests/test_newport/test_newport_pmc8742.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nTests for the Newport Picomotor Controller 8742.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom hypothesis import given, strategies as st\nimport pytest\n\nimport instruments as ik\nfrom instruments.units import ureg as u\nfrom tests import expected_protocol\n\n# pylint: disable=protected-access\n\n\n# INSTRUMENT #\n\n\ndef test_init():\n    \"\"\"Initialize a new Picomotor PMC8742 instrument.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [], [], sep=\"\\r\\n\"\n    ) as inst:\n        assert inst.terminator == \"\\r\\n\"\n        assert not inst.multiple_controllers\n\n\ndef test_controller_address():\n    \"\"\"Set and get controller address.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [\"SA2\", \"SA?\"], [\"2\"], sep=\"\\r\\n\"\n    ) as inst:\n        inst.controller_address = 2\n        assert inst.controller_address == 2\n\n\ndef test_controller_configuration():\n    \"\"\"Set and get controller configuration.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742,\n        [\"ZZ11\", \"ZZ11\", \"ZZ11\", \"ZZ?\"],\n        [\"11\"],\n        sep=\"\\r\\n\",\n    ) as inst:\n        inst.controller_configuration = 3\n        inst.controller_configuration = 0b11\n        inst.controller_configuration = \"11\"\n        assert inst.controller_configuration == \"11\"\n\n\ndef test_dhcp_mode():\n    \"\"\"Set and get DHCP mode.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742,\n        [\"IPMODE0\", \"IPMODE1\", \"IPMODE?\"],\n        [\"1\"],\n        sep=\"\\r\\n\",\n    ) as inst:\n        inst.dhcp_mode = False\n        inst.dhcp_mode = True\n        assert inst.dhcp_mode\n\n\ndef test_error_code():\n    \"\"\"Get error code.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [\"TE?\"], [\"0\"], sep=\"\\r\\n\"\n    ) as inst:\n        assert inst.error_code == 0\n\n\ndef test_error_code_and_message():\n    \"\"\"Get error code and message as tuple.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742,\n        [\"TB?\"],\n        [\"0, NO ERROR DETECTED\"],\n        sep=\"\\r\\n\",\n    ) as inst:\n        err_expected = (0, \"NO ERROR DETECTED\")\n        err_received = inst.error_code_and_message\n        assert err_received == err_expected\n        assert isinstance(err_received, tuple)\n\n\ndef test_firmware_version():\n    \"\"\"Get firmware version.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [\"VE?\"], [\"0123456789\"], sep=\"\\r\\n\"\n    ) as inst:\n        assert inst.firmware_version == \"0123456789\"\n\n\ndef test_gateway():\n    \"\"\"Set / get gateway.\"\"\"\n    ip_addr = \"192.168.1.1\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742,\n        [f\"GATEWAY {ip_addr}\", \"GATEWAY?\"],\n        [f\"{ip_addr}\"],\n        sep=\"\\r\\n\",\n    ) as inst:\n        inst.gateway = ip_addr\n        assert inst.gateway == ip_addr\n\n\ndef test_hostname():\n    \"\"\"Set / get hostname.\"\"\"\n    host = \"192.168.1.1\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742,\n        [f\"HOSTNAME {host}\", \"HOSTNAME?\"],\n        [f\"{host}\"],\n        sep=\"\\r\\n\",\n    ) as inst:\n        inst.hostname = host\n        assert inst.hostname == host\n\n\ndef test_ip_address():\n    \"\"\"Set / get ip address.\"\"\"\n    ip_addr = \"192.168.1.1\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742,\n        [f\"IPADDR {ip_addr}\", \"IPADDR?\"],\n        [f\"{ip_addr}\"],\n        sep=\"\\r\\n\",\n    ) as inst:\n        inst.ip_address = ip_addr\n        assert inst.ip_address == ip_addr\n\n\ndef test_mac_address():\n    \"\"\"Set / get mac address.\"\"\"\n    mac_addr = \"5827809, 8087\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [\"MACADDR?\"], [f\"{mac_addr}\"], sep=\"\\r\\n\"\n    ) as inst:\n        assert inst.mac_address == mac_addr\n\n\ndef test_name():\n    \"\"\"Get name of the current instrument.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [\"*IDN?\"], [\"NAME\"], sep=\"\\r\\n\"\n    ) as inst:\n        assert inst.name == \"NAME\"\n\n\ndef test_netmask():\n    \"\"\"Set / get netmask.\"\"\"\n    ip_addr = \"192.168.1.1\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742,\n        [f\"NETMASK {ip_addr}\", \"NETMASK?\"],\n        [f\"{ip_addr}\"],\n        sep=\"\\r\\n\",\n    ) as inst:\n        inst.netmask = ip_addr\n        assert inst.netmask == ip_addr\n\n\ndef test_scan_controller():\n    \"\"\"Scan connected controllers.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [\"SC?\"], [\"11\"], sep=\"\\r\\n\"\n    ) as inst:\n        assert inst.scan_controllers == \"11\"\n\n\ndef test_scan_done():\n    \"\"\"Query if a controller scan is completed.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [\"SD?\", \"SD?\"], [\"1\", \"0\"], sep=\"\\r\\n\"\n    ) as inst:\n        assert inst.scan_done\n        assert not inst.scan_done\n\n\ndef test_abort_motion():\n    \"\"\"Abort all motion.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [\"AB\"], [], sep=\"\\r\\n\"\n    ) as inst:\n        inst.abort_motion()\n\n\ndef test_motor_check():\n    \"\"\"Check the connected motors.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [\"MC\"], [], sep=\"\\r\\n\"\n    ) as inst:\n        inst.motor_check()\n\n\n@pytest.mark.parametrize(\"mode\", [0, 1, 2])\ndef test_scan(mode):\n    \"\"\"Scan address configuration of motors for default and other modes.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [\"SC2\", f\"SC{mode}\"], [], sep=\"\\r\\n\"\n    ) as inst:\n        inst.scan()\n        inst.scan(mode)\n\n\ndef test_purge():\n    \"\"\"Purge the memory.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [\"XX\"], [], sep=\"\\r\\n\"\n    ) as inst:\n        inst.purge()\n\n\n@pytest.mark.parametrize(\"mode\", [0, 1])\ndef test_recall_parameters(mode):\n    \"\"\"Recall parameters, by default the factory set values.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [\"*RCL0\", f\"*RCL{mode}\"], [], sep=\"\\r\\n\"\n    ) as inst:\n        inst.recall_parameters()\n        inst.recall_parameters(mode)\n\n\ndef test_reset():\n    \"\"\"Soft reset of the controller.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [\"*RST\"], [], sep=\"\\r\\n\"\n    ) as inst:\n        inst.reset()\n\n\ndef test_save_settings():\n    \"\"\"Save settings of the controller.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [\"SM\"], [], sep=\"\\r\\n\"\n    ) as inst:\n        inst.save_settings()\n\n\ndef test_query_bad_header():\n    \"\"\"Ensure stripping of bad header if present, see comment in query.\"\"\"\n    retval = b\"\\xff\\xfd\\x03\\xff\\xfb\\x01192.168.2.161\"\n    val_expected = \"192.168.2.161\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [\"IPADDR?\"], [retval], sep=\"\\r\\n\"\n    ) as inst:\n        assert inst.ip_address == val_expected\n\n\n# AXIS SPECIFIC COMMANDS - CONTROLLER COMMANDS PER AXIS TESTED ABOVE #\n\n\n@given(ax=st.integers(min_value=0, max_value=3))\ndef test_axis_returns(ax):\n    \"\"\"Return axis with given axis number testing all valid axes.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [], [], sep=\"\\r\\n\"\n    ) as inst:\n        axis = inst.axis[ax]\n        assert isinstance(axis, ik.newport.PicoMotorController8742.Axis)\n        assert axis._parent == inst\n        assert axis._idx == ax + 1\n        assert axis._address == \"\"\n\n\ndef test_axis_returns_type_error():\n    \"\"\"Raise TypeError if parent class is not PicoMotorController8742.\"\"\"\n    with pytest.raises(TypeError):\n        _ = ik.newport.PicoMotorController8742.Axis(0, 0)\n\n\n@given(ax=st.integers(min_value=4))\ndef test_axis_return_index_error(ax):\n    \"\"\"Raise IndexError if axis out of bounds and in one controller mode.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [], [], sep=\"\\r\\n\"\n    ) as inst:\n        with pytest.raises(IndexError):\n            _ = inst.axis[ax]\n\n\n@given(val=st.integers(min_value=1, max_value=200000))\ndef test_axis_acceleration(val):\n    \"\"\"Set / get axis acceleration unitful and without units.\"\"\"\n    val_unit = u.Quantity(val, u.s**-2)\n    val_unit_other = val_unit.to(u.min**-2)\n    with expected_protocol(\n        ik.newport.PicoMotorController8742,\n        [f\"1AC{val}\", f\"1AC{val}\", \"1AC?\"],\n        [f\"{val}\"],\n        sep=\"\\r\\n\",\n    ) as inst:\n        axis = inst.axis[0]\n        axis.acceleration = val\n        axis.acceleration = val_unit_other\n        assert axis.acceleration == val_unit\n\n\n@given(val=st.integers().filter(lambda x: not 1 <= x <= 200000))\ndef test_axis_acceleration_value_error(val):\n    \"\"\"Raise ValueError if acceleration out of range.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [], [], sep=\"\\r\\n\"\n    ) as inst:\n        axis = inst.axis[0]\n        with pytest.raises(ValueError):\n            axis.acceleration = val\n\n\n@given(val=st.integers(min_value=-2147483648, max_value=2147483647))\ndef test_axis_home_position(val):\n    \"\"\"Set / get axis home position.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742,\n        [f\"1DH{val}\", \"1DH?\"],\n        [f\"{val}\"],\n        sep=\"\\r\\n\",\n    ) as inst:\n        axis = inst.axis[0]\n        axis.home_position = val\n        assert axis.home_position == val\n\n\n@pytest.mark.parametrize(\"val\", [-2147483649, 2147483648])\ndef test_axis_home_position_value_error(val):\n    \"\"\"Raise ValueError if home position out of range.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [], [], sep=\"\\r\\n\"\n    ) as inst:\n        axis = inst.axis[0]\n        with pytest.raises(ValueError):\n            axis.home_position = val\n\n\n@pytest.mark.parametrize(\"val\", [\"0\", \"1\"])\ndef test_axis_is_stopped(val):\n    \"\"\"Query if axis is stopped.\"\"\"\n    exp_result = True if val == \"1\" else False\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [\"1MD?\"], [f\"{val}\"], sep=\"\\r\\n\"\n    ) as inst:\n        axis = inst.axis[0]\n        assert axis.is_stopped == exp_result\n\n\n@pytest.mark.parametrize(\"val\", ik.newport.PicoMotorController8742.Axis.MotorType)\ndef test_axis_motor_type(val):\n    \"\"\"Set / get motor type.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742,\n        [f\"1QM{val.value}\", \"1QM?\"],\n        [f\"{val.value}\"],\n        sep=\"\\r\\n\",\n    ) as inst:\n        axis = inst.axis[0]\n        axis.motor_type = val\n        assert axis.motor_type == val\n\n\ndef test_axis_motor_type_wrong_type():\n    \"\"\"Raise TypeError if not appropriate motor type.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [], [], sep=\"\\r\\n\"\n    ) as inst:\n        axis = inst.axis[0]\n        with pytest.raises(TypeError):\n            axis.motor_type = 2\n\n\n@given(val=st.integers(min_value=-2147483648, max_value=2147483647))\ndef test_axis_move_absolute(val):\n    \"\"\"Set / get axis move absolute.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742,\n        [f\"1PA{val}\", \"1PA?\"],\n        [f\"{val}\"],\n        sep=\"\\r\\n\",\n    ) as inst:\n        axis = inst.axis[0]\n        axis.move_absolute = val\n        assert axis.move_absolute == val\n\n\n@pytest.mark.parametrize(\"val\", [-2147483649, 2147483648])\ndef test_axis_move_absolute_value_error(val):\n    \"\"\"Raise ValueError if move absolute out of range.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [], [], sep=\"\\r\\n\"\n    ) as inst:\n        axis = inst.axis[0]\n        with pytest.raises(ValueError):\n            axis.move_absolute = val\n\n\n@given(val=st.integers(min_value=-2147483648, max_value=2147483647))\ndef test_axis_move_relative(val):\n    \"\"\"Set / get axis move relative.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742,\n        [f\"1PR{val}\", \"1PR?\"],\n        [f\"{val}\"],\n        sep=\"\\r\\n\",\n    ) as inst:\n        axis = inst.axis[0]\n        axis.move_relative = val\n        assert axis.move_relative == val\n\n\n@pytest.mark.parametrize(\"val\", [-2147483649, 2147483648])\ndef test_axis_move_relative_value_error(val):\n    \"\"\"Raise ValueError if move relative out of range.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [], [], sep=\"\\r\\n\"\n    ) as inst:\n        axis = inst.axis[0]\n        with pytest.raises(ValueError):\n            axis.move_relative = val\n\n\ndef test_axis_position():\n    \"\"\"Query position of an axis.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [\"1TP?\"], [\"42\"], sep=\"\\r\\n\"\n    ) as inst:\n        axis = inst.axis[0]\n        assert axis.position == 42\n\n\n@given(val=st.integers(min_value=1, max_value=2000))\ndef test_axis_velocity(val):\n    \"\"\"Set / get axis velocity, unitful and unitless.\"\"\"\n    val_unit = u.Quantity(val, 1 / u.s)\n    val_unit_other = val_unit.to(1 / u.hour)\n    with expected_protocol(\n        ik.newport.PicoMotorController8742,\n        [f\"1QM?\", f\"1VA{val}\", f\"1QM?\", f\"1VA{val}\", \"1VA?\"],\n        [\"3\", \"3\", f\"{val}\"],\n        sep=\"\\r\\n\",\n    ) as inst:\n        axis = inst.axis[0]\n        axis.velocity = val\n        axis.velocity = val_unit_other\n        assert axis.velocity == val_unit\n\n\n@given(val=st.integers().filter(lambda x: not 1 <= x <= 2000))\n@pytest.mark.parametrize(\"motor\", [0, 1, 3])\ndef test_axis_velocity_value_error_regular(val, motor):\n    \"\"\"Raise ValueError if velocity is out of range for non-tiny motor.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [\"1QM?\"], [f\"{motor}\"], sep=\"\\r\\n\"\n    ) as inst:\n        axis = inst.axis[0]\n        with pytest.raises(ValueError):\n            axis.velocity = val\n\n\n@given(val=st.integers().filter(lambda x: not 1 <= x <= 1750))\ndef test_axis_velocity_value_error_tiny(val):\n    \"\"\"Raise ValueError if velocity is out of range for tiny motor.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [\"1QM?\"], [\"2\"], sep=\"\\r\\n\"\n    ) as inst:\n        axis = inst.axis[0]\n        with pytest.raises(ValueError):\n            axis.velocity = val\n\n\n@pytest.mark.parametrize(\"direction\", [\"+\", \"-\"])\ndef test_axis_move_indefinite(direction):\n    \"\"\"Move axis indefinitely.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [f\"1MV{direction}\"], [], sep=\"\\r\\n\"\n    ) as inst:\n        axis = inst.axis[0]\n        axis.move_indefinite(direction)\n\n\ndef test_axis_stop():\n    \"\"\"Stop axis.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [f\"1ST\"], [], sep=\"\\r\\n\"\n    ) as inst:\n        axis = inst.axis[0]\n        axis.stop()\n\n\n# SOME ADDITIONAL TESTS FOR MAIN / SECONDARY CONTROLLER SETUP #\n\n\ndef test_multi_controllers():\n    \"\"\"Enable and disable multiple controllers.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [], [], sep=\"\\r\\n\"\n    ) as inst:\n        inst.multiple_controllers = True\n        assert inst.multiple_controllers\n        inst.multiple_controllers = False\n        assert not inst.multiple_controllers\n\n\n@given(ax=st.integers(min_value=0, max_value=31 * 4 - 1))\ndef test_axis_return_multi(ax):\n    \"\"\"Return axis properly for multi-controller setup.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [], [], sep=\"\\r\\n\"\n    ) as inst:\n        inst.multiple_controllers = True\n        axis = inst.axis[ax]\n        assert isinstance(axis, ik.newport.PicoMotorController8742.Axis)\n        assert axis._parent == inst\n        assert axis._idx == ax % 4 + 1\n        assert axis._address == f\"{ax // 4 + 1}>\"\n\n\n@given(ax=st.integers(min_value=124))\ndef test_axis_return_multi_index_error(ax):\n    \"\"\"Raise IndexError if axis out of bounds and in multi controller mode.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [], [], sep=\"\\r\\n\"\n    ) as inst:\n        inst.multiple_controllers = True\n        with pytest.raises(IndexError):\n            _ = inst.axis[ax]\n\n\n@given(ax=st.integers(min_value=0, max_value=31 * 4 - 1))\ndef test_axis_sendcmd_multi(ax):\n    \"\"\"Send correct command in multiple axis mode.\"\"\"\n    address = ax // 4 + 1\n    axis = ax % 4 + 1\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [f\"{address}>{axis}CMD\"], [], sep=\"\\r\\n\"\n    ) as inst:\n        inst.multiple_controllers = True\n        axis = inst.axis[ax]\n        axis.sendcmd(\"CMD\")\n\n\n@given(ax=st.integers(min_value=0, max_value=31 * 4 - 1))\ndef test_axis_query_multi(ax):\n    \"\"\"Query command in multiple axis mode and strip address routing.\"\"\"\n    address = ax // 4 + 1\n    axis = ax % 4 + 1\n    answer_expected = f\"{axis}ANSWER\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742,\n        [f\"{address}>{axis}CMD\"],\n        [f\"{address}>{answer_expected}\"],\n        sep=\"\\r\\n\",\n    ) as inst:\n        inst.multiple_controllers = True\n        axis = inst.axis[ax]\n        assert axis.query(\"CMD\") == answer_expected\n\n\ndef test_axis_query_multi_io_error():\n    \"\"\"Raise IOError if query response from wrong controller.\"\"\"\n    with expected_protocol(\n        ik.newport.PicoMotorController8742, [f\"1>1CMD\"], [f\"4>1ANSWER\"], sep=\"\\r\\n\"\n    ) as inst:\n        inst.multiple_controllers = True\n        axis = inst.axis[0]\n        with pytest.raises(IOError):\n            axis.query(\"CMD\")\n"
  },
  {
    "path": "tests/test_newport/test_newportesp301.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the Newport ESP 301 axis controller\n\"\"\"\n\n# IMPORTS #####################################################################\n\nimport time\nfrom unittest import mock\n\nfrom hypothesis import given, strategies as st\nimport pytest\n\nimport instruments as ik\nfrom instruments.units import ureg as u\nfrom tests import expected_protocol\n\n# TESTS #######################################################################\n\n\n# pylint: disable=protected-access,too-many-lines\n\n\n# INSTRUMENT #\n\n\ndef test_init():\n    \"\"\"Initialize a Newport ESP301 instrument.\"\"\"\n    with expected_protocol(ik.newport.NewportESP301, [], [], sep=\"\\r\") as inst:\n        assert inst._execute_immediately\n        assert inst._command_list == []\n        assert inst._bulk_query_resp == \"\"\n        assert inst.terminator == \"\\r\"\n\n\n@given(ax=st.integers(min_value=0, max_value=99))\ndef test_axis_returns_axis_class(ax):\n    \"\"\"Return axis class with given axis number.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301,\n        [f\"{ax+1}SN?\", \"TB?\"],  # error check query\n        [\"1\", \"0,0,0\"],\n        sep=\"\\r\",\n    ) as inst:\n        axis = inst.axis[ax]\n        assert isinstance(axis, ik.newport.NewportESP301.Axis)\n\n\ndef test_newport_cmd(mocker):\n    \"\"\"Send a low level command to some randomly chosen target.\n\n    Execute command immediately (default), but no error check.\n    \"\"\"\n    target = \"TARG\"\n    cmd = \"COMMAND\"\n    params = (1, 2, 3)\n    # stitch together raw command to send\n    raw_cmd = f\"{target}{cmd}{','.join(map(str, params))}\"\n    with expected_protocol(ik.newport.NewportESP301, [raw_cmd], [], sep=\"\\r\") as inst:\n        execute_spy = mocker.spy(inst, \"_execute_cmd\")\n        resp = inst._newport_cmd(cmd, params=params, target=target, errcheck=False)\n        assert resp is None\n        execute_spy.assert_called_with(raw_cmd, False)\n\n\ndef test_newport_cmd_add_to_list():\n    \"\"\"Send a low level command to some randomly chosen target.\n\n    Do not execute, just add command to list.\n    \"\"\"\n    target = \"TARG\"\n    cmd = \"COMMAND\"\n    params = (1, 2, 3)\n    # stitch together raw command to send\n    raw_cmd = f\"{target}{cmd}{','.join(map(str, params))}\"\n    with expected_protocol(ik.newport.NewportESP301, [], [], sep=\"\\r\") as inst:\n        inst._execute_immediately = False\n        resp = inst._newport_cmd(cmd, params=params, target=target)\n        assert resp is None\n        assert inst._command_list == [raw_cmd]\n\n\ndef test_newport_cmd_with_axis():\n    \"\"\"Send a low level command for a given axis.\"\"\"\n    ax = 42\n    cmd = \"COMMAND\"\n    params = (1, 2, 3)\n    # stitch together raw command to send\n    raw_cmd = f\"{ax+1}{cmd}{','.join(map(str, params))}\"\n\n    with expected_protocol(\n        ik.newport.NewportESP301,\n        [f\"{ax+1}SN?\", \"TB?\", raw_cmd],  # error check query\n        [\"1\", \"0,0,0\"],\n        sep=\"\\r\",\n    ) as inst:\n        axis = inst.axis[ax]\n        resp = inst._newport_cmd(cmd, params=params, target=axis, errcheck=False)\n        assert resp is None\n\n\ndef test_execute_cmd_query():\n    \"\"\"Execute a query.\"\"\"\n    query = \"QUERY?\"\n    response = \"RESPONSE\"\n\n    with expected_protocol(\n        ik.newport.NewportESP301,\n        [query, \"TB?\"],\n        [response, \"0,0,0\"],  # no error\n        sep=\"\\r\",\n    ) as inst:\n        assert inst._execute_cmd(query) == response\n\n\ndef test_execute_cmd_query_error():\n    \"\"\"Raise an error while sending a command to the instrument.\n\n    Only check for the context of the specific error message, since\n    timestamp is not frozen.\n    \"\"\"\n    cmd = \"COMMAND\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [cmd, \"TB?\"], [\"13,0,0\"], sep=\"\\r\"  # no error\n    ) as inst:\n        with pytest.raises(ik.newport.errors.NewportError) as err_info:\n            inst._execute_cmd(cmd)\n        err_msg = err_info.value.args[0]\n        assert \"GROUP NUMBER MISSING\" in err_msg\n\n\ndef test_home(mocker):\n    \"\"\"Search for home.\n\n    Mock `_newport_cmd`, this routine is already tested. Just assert\n    that it is called with correct arguments.\n    \"\"\"\n    axis = \"ax\"\n    params = 1, 2, 3\n    errcheck = False\n    with expected_protocol(ik.newport.NewportESP301, [], [], sep=\"\\r\") as inst:\n        mock_cmd = mocker.patch.object(inst, \"_newport_cmd\")\n        inst._home(axis, params, errcheck)\n        mock_cmd.assert_called_with(\n            \"OR\", target=axis, params=[params], errcheck=errcheck\n        )\n\n\n@pytest.mark.parametrize(\"search_mode\", ik.newport.NewportESP301.HomeSearchMode)\ndef test_search_for_home(mocker, search_mode):\n    \"\"\"Search for home with specific method.\n\n    Mock `_home` routine (already tested) and just assert that called\n    with the correct arguments.\n    \"\"\"\n    axis = 3\n    errcheck = True\n    with expected_protocol(ik.newport.NewportESP301, [], [], sep=\"\\r\") as inst:\n        mock_cmd = mocker.patch.object(inst, \"_home\")\n        inst.search_for_home(axis, search_mode, errcheck)\n        mock_cmd.assert_called_with(\n            axis=axis, search_mode=search_mode, errcheck=errcheck\n        )\n\n\ndef test_reset(mocker):\n    \"\"\"Reset the device.\n\n    Mock `_newport_cmd`, this routine is already tested. Just assert\n    that it is called with correct arguments.\n    \"\"\"\n    with expected_protocol(ik.newport.NewportESP301, [], [], sep=\"\\r\") as inst:\n        mock_cmd = mocker.patch.object(inst, \"_newport_cmd\")\n        inst.reset()\n        mock_cmd.assert_called_with(\"RS\", errcheck=False)\n\n\n@given(prg_id=st.integers(min_value=1, max_value=100))\ndef test_define_program(prg_id):\n    \"\"\"Define an empty program.\n\n    Mock out the `_newport_cmd` routine. Already tested and not\n    required.\n    \"\"\"\n    with expected_protocol(ik.newport.NewportESP301, [], [], sep=\"\\r\") as inst:\n        with mock.patch.object(inst, \"_newport_cmd\") as mock_cmd:\n            with inst.define_program(prg_id):\n                pass\n            calls = (\n                mock.call(\"XX\", target=prg_id),\n                mock.call(\"EP\", target=prg_id),\n                mock.call(\"QP\"),\n            )\n            mock_cmd.assert_has_calls(calls)\n\n\n@given(prg_id=st.integers().filter(lambda x: x < 1 or x > 100))\ndef test_define_program_value_error(prg_id):\n    \"\"\"Raise ValueError when defining program with invalid ID.\"\"\"\n    with expected_protocol(ik.newport.NewportESP301, [], [], sep=\"\\r\") as inst:\n        with pytest.raises(ValueError) as err_info:\n            with inst.define_program(prg_id):\n                pass\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == \"Invalid program ID. Must be an integer from 1 to \"\n            \"100 (inclusive).\"\n        )\n\n\n@pytest.mark.parametrize(\"errcheck\", (True, False))\ndef test_execute_bulk_command(mocker, errcheck):\n    \"\"\"Execute bulk commands.\n\n    Mock out the `_execute_cmd` call and simply assert that calls are\n    in correct order.\n\n    We will just do three move commands, one with steps of 1, 10, and\n    11.\n    \"\"\"\n    ax = 0\n    move_commands_sent = \"1PA1.0 ; 1PA10.0 ;  ; 1PA11.0 ; \"\n    resp = \"Response\"\n    with expected_protocol(\n        ik.newport.NewportESP301,\n        [\n            f\"{ax+1}SN?\",\n            \"TB?\",  # error check query\n        ],\n        [\"1\", \"0,0,0\"],\n        sep=\"\\r\",\n    ) as inst:\n        axis = inst.axis[ax]\n        mock_exec = mocker.patch.object(inst, \"_execute_cmd\", return_value=resp)\n        with inst.execute_bulk_command(errcheck=errcheck):\n            assert not inst._execute_immediately\n            # some move commands\n            axis.move(1.0)\n            axis.move(10.0)\n            axis.move(11.0)\n        mock_exec.assert_called_with(move_commands_sent, errcheck)\n        assert inst._bulk_query_resp == resp\n        assert inst._command_list == []\n        assert inst._execute_immediately\n\n\n@given(prg_id=st.integers(min_value=1, max_value=100))\ndef test_run_program(prg_id):\n    \"\"\"Run a program.\n\n    Mock out the `_newport_cmd` routine. Already tested and not\n    required.\n    \"\"\"\n    with expected_protocol(ik.newport.NewportESP301, [], [], sep=\"\\r\") as inst:\n        with mock.patch.object(inst, \"_newport_cmd\") as mock_cmd:\n            inst.run_program(prg_id)\n            mock_cmd.assert_called_with(\"EX\", target=prg_id)\n\n\n@given(prg_id=st.integers().filter(lambda x: x < 1 or x > 100))\ndef test_run_program_value_error(prg_id):\n    \"\"\"Raise ValueError when defining program with invalid ID.\"\"\"\n    with expected_protocol(ik.newport.NewportESP301, [], [], sep=\"\\r\") as inst:\n        with pytest.raises(ValueError) as err_info:\n            inst.run_program(prg_id)\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == \"Invalid program ID. Must be an integer from 1 to \"\n            \"100 (inclusive).\"\n        )\n\n\n# AXIS #\n\n\n# commands to send, return when initializing axis zero\nax_init = \"1SN?\\rTB?\", \"1\\r0,0,0\"\n\n\ndef test_axis_init():\n    \"\"\"Initialize a new axis.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        assert axis._controller == inst\n        assert axis._axis_id == 1\n        assert axis._units == u.Quantity(1, u.count)\n\n\ndef test_axis_init_type_error():\n    \"\"\"Raise TypeError when axis initialized from wrong parent.\"\"\"\n    with pytest.raises(TypeError) as err_info:\n        _ = ik.newport.newportesp301.NewportESP301.Axis(42, 0)\n    err_msg = err_info.value.args[0]\n    assert (\n        err_msg == \"Axis must be controlled by a Newport ESP-301 motor \" \"controller.\"\n    )\n\n\ndef test_axis_units_of(mocker):\n    \"\"\"Context manager with reset of units after usage.\n\n    Mock out the getting and setting the units. These two routines are\n    tested separately, thus only assert that the correct calls are\n    issued.\n    \"\"\"\n    get_unit = ik.newport.newportesp301.NewportESP301.Units.millimeter\n    set_unit = ik.newport.newportesp301.NewportESP301.Units.inches\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_get = mocker.patch.object(axis, \"_get_units\", return_value=get_unit)\n        mock_set = mocker.patch.object(axis, \"_set_units\", return_value=None)\n        with axis._units_of(set_unit):\n            mock_get.assert_called()\n            mock_set.assert_called_with(set_unit)\n        mock_set.assert_called_with(get_unit)\n\n\ndef test_axis_get_units(mocker):\n    \"\"\"Get units from the axis.\n\n    Mock out the command sending and receiving.\n    \"\"\"\n    resp = \"2\"\n    unit = ik.newport.newportesp301.NewportESP301.Units(int(resp))\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=resp)\n        assert unit == axis._get_units()\n        mock_cmd.assert_called_with(\"SN?\", target=1)\n\n\ndef test_axis_set_units(mocker):\n    \"\"\"Set units for a given axis.\n\n    Mock out the actual command sending for simplicity, but assert it\n    has been called.\n    \"\"\"\n    unit = ik.newport.newportesp301.NewportESP301.Units.radian  # just pick one\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=None)\n        assert axis._set_units(unit) is None\n        mock_cmd.assert_called_with(\"SN\", target=1, params=[int(unit)])\n\n\ndef test_axis_id():\n    \"\"\"Get axis ID.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        assert axis.axis_id == 1\n\n\n@pytest.mark.parametrize(\"resp\", (\"0\", \"1\"))\ndef test_axis_is_motion_done(mocker, resp):\n    \"\"\"Get if motion is done.\n\n    Mock out the command sending, as above.\n    \"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=resp)\n        assert axis.is_motion_done is bool(int(resp))\n        mock_cmd.assert_called_with(\"MD?\", target=1)\n\n\ndef test_axis_acceleration(mocker):\n    \"\"\"Set / get axis acceleration.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value = 42\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value)\n        axis.acceleration = value\n        mock_cmd.assert_called_with(\"AC\", target=1, params=[float(value)])\n        assert axis.acceleration == u.Quantity(value, axis._units / u.s**2)\n        mock_cmd.assert_called_with(\"AC?\", target=1)\n\n\ndef test_axis_acceleration_none():\n    \"\"\"Set axis acceleration with `None` does nothing.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        axis.acceleration = None\n\n\ndef test_axis_deceleration(mocker):\n    \"\"\"Set / get axis deceleration.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value = 42\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value)\n        axis.deceleration = value\n        mock_cmd.assert_called_with(\"AG\", target=1, params=[float(value)])\n        assert axis.deceleration == u.Quantity(value, axis._units / u.s**2)\n        mock_cmd.assert_called_with(\"AG?\", target=1)\n\n\ndef test_axis_deceleration_none():\n    \"\"\"Set axis deceleration with `None` does nothing.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        axis.deceleration = None\n\n\ndef test_axis_estop_deceleration(mocker):\n    \"\"\"Set / get axis estop deceleration.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value = 42\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value)\n        axis.estop_deceleration = value\n        mock_cmd.assert_called_with(\"AE\", target=1, params=[float(value)])\n        assert axis.estop_deceleration == u.Quantity(value, axis._units / u.s**2)\n        mock_cmd.assert_called_with(\"AE?\", target=1)\n\n\ndef test_axis_jerk(mocker):\n    \"\"\"Set / get axis jerk rate.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value = 42\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value)\n        axis.jerk = value\n        mock_cmd.assert_called_with(\"JK\", target=1, params=[float(value)])\n        assert axis.jerk == u.Quantity(value, axis._units / u.s**3)\n        mock_cmd.assert_called_with(\"JK?\", target=1)\n\n\ndef test_axis_velocity(mocker):\n    \"\"\"Set / get axis velocity.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value = 42\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value)\n        axis.velocity = value\n        mock_cmd.assert_called_with(\"VA\", target=1, params=[float(value)])\n        assert axis.velocity == u.Quantity(value, axis._units / u.s)\n        mock_cmd.assert_called_with(\"VA?\", target=1)\n\n\ndef test_axis_max_velocity(mocker):\n    \"\"\"Set / get axis maximum velocity.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value = 42\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value)\n        axis.max_velocity = value\n        mock_cmd.assert_called_with(\"VU\", target=1, params=[float(value)])\n        assert axis.max_velocity == u.Quantity(value, axis._units / u.s)\n        mock_cmd.assert_called_with(\"VU?\", target=1)\n\n\ndef test_axis_max_velocity_none():\n    \"\"\"Set axis maximum velocity with `None` does nothing.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        axis.max_velocity = None\n\n\ndef test_axis_max_base_velocity(mocker):\n    \"\"\"Set / get axis maximum base velocity.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value = 42\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value)\n        axis.max_base_velocity = value\n        mock_cmd.assert_called_with(\"VB\", target=1, params=[float(value)])\n        assert axis.max_base_velocity == u.Quantity(value, axis._units / u.s)\n        mock_cmd.assert_called_with(\"VB?\", target=1)\n\n\ndef test_axis_max_base_velocity_none():\n    \"\"\"Set axis maximum base velocity with `None` does nothing.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        axis.max_base_velocity = None\n\n\ndef test_axis_jog_high_velocity(mocker):\n    \"\"\"Set / get axis jog high velocity.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value = 42\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value)\n        axis.jog_high_velocity = value\n        mock_cmd.assert_called_with(\"JH\", target=1, params=[float(value)])\n        assert axis.jog_high_velocity == u.Quantity(value, axis._units / u.s)\n        mock_cmd.assert_called_with(\"JH?\", target=1)\n\n\ndef test_axis_jog_high_velocity_none():\n    \"\"\"Set axis jog high velocity with `None` does nothing.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        axis.jog_high_velocity = None\n\n\ndef test_axis_jog_low_velocity(mocker):\n    \"\"\"Set / get axis jog low velocity.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value = 42\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value)\n        axis.jog_low_velocity = value\n        mock_cmd.assert_called_with(\"JW\", target=1, params=[float(value)])\n        assert axis.jog_low_velocity == u.Quantity(value, axis._units / u.s)\n        mock_cmd.assert_called_with(\"JW?\", target=1)\n\n\ndef test_axis_jog_low_velocity_none():\n    \"\"\"Set axis jog low velocity with `None` does nothing.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        axis.jog_low_velocity = None\n\n\ndef test_axis_homing_velocity(mocker):\n    \"\"\"Set / get axis homing velocity.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value = 42\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value)\n        axis.homing_velocity = value\n        mock_cmd.assert_called_with(\"OH\", target=1, params=[float(value)])\n        assert axis.homing_velocity == u.Quantity(value, axis._units / u.s)\n        mock_cmd.assert_called_with(\"OH?\", target=1)\n\n\ndef test_axis_homing_velocity_none():\n    \"\"\"Set axis homing velocity with `None` does nothing.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        axis.homing_velocity = None\n\n\ndef test_axis_max_acceleration(mocker):\n    \"\"\"Set / get axis maximum acceleration.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value = 42\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value)\n        axis.max_acceleration = value\n        mock_cmd.assert_called_with(\"AU\", target=1, params=[float(value)])\n        assert axis.max_acceleration == u.Quantity(value, axis._units / u.s**2)\n        mock_cmd.assert_called_with(\"AU?\", target=1)\n\n\ndef test_axis_max_acceleration_none():\n    \"\"\"Set axis maximum acceleration with `None` does nothing.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        axis.max_acceleration = None\n\n\ndef test_axis_max_deceleration(mocker):\n    \"\"\"Set / get axis maximum deceleration.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value = 42\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value)\n        axis.max_deceleration = value\n        mock_cmd.assert_called_with(\"AU\", target=1, params=[float(value)])\n        assert axis.max_deceleration == u.Quantity(value, axis._units / u.s**2)\n        mock_cmd.assert_called_with(\"AU?\", target=1)\n\n\ndef test_axis_position(mocker):\n    \"\"\"Get axis position.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    retval = \"42\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=retval)\n        assert axis.position == u.Quantity(float(retval), axis._units)\n        mock_cmd.assert_called_with(\"TP?\", target=1)\n\n\ndef test_axis_desired_position(mocker):\n    \"\"\"Get axis desired position.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    retval = \"42\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=retval)\n        assert axis.desired_position == u.Quantity(float(retval), axis._units)\n        mock_cmd.assert_called_with(\"DP?\", target=1)\n\n\ndef test_axis_desired_velocity(mocker):\n    \"\"\"Get axis desired velocity.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    retval = \"42\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=retval)\n        assert axis.desired_velocity == u.Quantity(float(retval), axis._units / u.s)\n        mock_cmd.assert_called_with(\"DV?\", target=1)\n\n\ndef test_axis_home(mocker):\n    \"\"\"Set / get axis home position.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value = 42\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value)\n        axis.home = value\n        mock_cmd.assert_called_with(\"DH\", target=1, params=[float(value)])\n        assert axis.home == u.Quantity(value, axis._units)\n        mock_cmd.assert_called_with(\"DH?\", target=1)\n\n\ndef test_axis_home_none():\n    \"\"\"Set axis home with `None` does nothing.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        axis.home = None\n\n\ndef test_axis_units(mocker):\n    \"\"\"Get / set units.\n\n    Mock out `_newport_cmd` since tested elsewhere. Returns u.counts\n    \"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=\"0\")\n        assert axis.units == u.counts\n        mock_cmd.reset_mock()\n        # set units with None\n        axis.units = None\n        mock_cmd.assert_not_called()\n        # set units with um as number (num 3)\n        axis.units = 3\n        assert axis._units == u.um\n        mock_cmd.assert_called_with(\"SN\", target=1, params=[3])\n        # set units with millimeters as quantity (num 2)\n        axis.units = u.mm\n        assert axis._units == u.mm\n        mock_cmd.assert_called_with(\"SN\", target=1, params=[2])\n\n\ndef test_axis_encoder_resolution(mocker):\n    \"\"\"Set / get axis encoder resolution.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value = 42\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value)\n        axis.encoder_resolution = value\n        mock_cmd.assert_called_with(\"SU\", target=1, params=[float(value)])\n        assert axis.encoder_resolution == u.Quantity(value, axis._units)\n        mock_cmd.assert_called_with(\"SU?\", target=1)\n\n\ndef test_axis_encoder_resolution_none():\n    \"\"\"Set axis encoder resolution with `None` does nothing.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        axis.encoder_resolution = None\n\n\ndef test_axis_full_step_resolution(mocker):\n    \"\"\"Set / get axis full step resolution.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value = 42\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value)\n        axis.full_step_resolution = value\n        mock_cmd.assert_called_with(\"FR\", target=1, params=[float(value)])\n        assert axis.full_step_resolution == u.Quantity(value, axis._units)\n        mock_cmd.assert_called_with(\"FR?\", target=1)\n\n\ndef test_axis_full_step_resolution_none():\n    \"\"\"Set axis full step resolution with `None` does nothing.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        axis.full_step_resolution = None\n\n\ndef test_axis_left_limit(mocker):\n    \"\"\"Set / get axis left limit.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value = 42\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value)\n        axis.left_limit = value\n        mock_cmd.assert_called_with(\"SL\", target=1, params=[float(value)])\n        assert axis.left_limit == u.Quantity(value, axis._units)\n        mock_cmd.assert_called_with(\"SL?\", target=1)\n\n\ndef test_axis_right_limit(mocker):\n    \"\"\"Set / get axis right limit.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value = 42\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value)\n        axis.right_limit = value\n        mock_cmd.assert_called_with(\"SR\", target=1, params=[float(value)])\n        assert axis.right_limit == u.Quantity(value, axis._units)\n        mock_cmd.assert_called_with(\"SR?\", target=1)\n\n\ndef test_axis_error_threshold(mocker):\n    \"\"\"Set / get axis error threshold.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value = 42\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value)\n        axis.error_threshold = value\n        mock_cmd.assert_called_with(\"FE\", target=1, params=[float(value)])\n        assert axis.error_threshold == u.Quantity(value, axis._units)\n        mock_cmd.assert_called_with(\"FE?\", target=1)\n\n\ndef test_axis_error_threshold_none():\n    \"\"\"Set axis error threshold with `None` does nothing.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        axis.error_threshold = None\n\n\ndef test_axis_current(mocker):\n    \"\"\"Set / get axis current.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value = 42\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value)\n        axis.current = value\n        mock_cmd.assert_called_with(\"QI\", target=1, params=[float(value)])\n        assert axis.current == u.Quantity(value, u.A)\n        mock_cmd.assert_called_with(\"QI?\", target=1)\n\n\ndef test_axis_current_none():\n    \"\"\"Set axis current with `None` does nothing.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        axis.current = None\n\n\ndef test_axis_voltage(mocker):\n    \"\"\"Set / get axis voltage.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value = 42\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value)\n        axis.voltage = value\n        mock_cmd.assert_called_with(\"QV\", target=1, params=[float(value)])\n        assert axis.voltage == u.Quantity(value, u.V)\n        mock_cmd.assert_called_with(\"QV?\", target=1)\n\n\ndef test_axis_voltage_none():\n    \"\"\"Set axis voltage with `None` does nothing.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        axis.voltage = None\n\n\ndef test_axis_motor_type(mocker):\n    \"\"\"Set / get axis motor type.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value = 1  # DC Servo\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value)\n        axis.motor_type = value\n        mock_cmd.assert_called_with(\"QM\", target=1, params=[float(value)])\n        assert axis.motor_type == value\n        mock_cmd.assert_called_with(\"QM?\", target=1)\n\n\ndef test_axis_motor_type_none():\n    \"\"\"Set axis motor type with `None` does nothing.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        axis.motor_type = None\n\n\ndef test_axis_feedback_configuration(mocker):\n    \"\"\"Set / get axis feedback configuration.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value_ret = \"A13\\r\\n\"  # 2 additional characters that will be cancelled\n    value = int(value_ret[:-2], 16)\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value_ret)\n        axis.feedback_configuration = value\n        mock_cmd.assert_called_with(\"ZB\", target=1, params=[float(value)])\n        assert axis.feedback_configuration == value\n        mock_cmd.assert_called_with(\"ZB?\", target=1)\n\n\ndef test_axis_feedback_configuration_none():\n    \"\"\"Set axis feedback configuration with `None` does nothing.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        axis.feedback_configuration = None\n\n\ndef test_axis_position_display_resolution(mocker):\n    \"\"\"Set / get axis position display resolution.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value = 42\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value)\n        axis.position_display_resolution = value\n        mock_cmd.assert_called_with(\"FP\", target=1, params=[float(value)])\n        assert axis.position_display_resolution == value\n        mock_cmd.assert_called_with(\"FP?\", target=1)\n\n\ndef test_axis_position_display_resolution_none():\n    \"\"\"Set axis position display resolution with `None` does nothing.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        axis.position_display_resolution = None\n\n\ndef test_axis_trajectory(mocker):\n    \"\"\"Set / get axis trajectory.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value = 42\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value)\n        axis.trajectory = value\n        mock_cmd.assert_called_with(\"TJ\", target=1, params=[float(value)])\n        assert axis.trajectory == value\n        mock_cmd.assert_called_with(\"TJ?\", target=1)\n\n\ndef test_axis_trajectory_none():\n    \"\"\"Set axis trajectory with `None` does nothing.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        axis.trajectory = None\n\n\ndef test_axis_microstep_factor(mocker):\n    \"\"\"Set / get axis microstep factor.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value = 42\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value)\n        axis.microstep_factor = value\n        mock_cmd.assert_called_with(\"QS\", target=1, params=[float(value)])\n        assert axis.microstep_factor == value\n        mock_cmd.assert_called_with(\"QS?\", target=1)\n\n\ndef test_axis_microstep_factor_none():\n    \"\"\"Set axis microstep factor with `None` does nothing.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        axis.microstep_factor = None\n\n\n@given(fct=st.integers().filter(lambda x: x < 1 or x > 250))\ndef test_axis_microstep_factor_out_of_range(fct):\n    \"\"\"Raise ValueError when microstep factor is out of range.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        with pytest.raises(ValueError) as err_info:\n            axis.microstep_factor = fct\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"Microstep factor must be between 1 and 250\"\n\n\ndef test_axis_hardware_limit_configuration(mocker):\n    \"\"\"Set / get axis hardware limit configuration.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value_ret = \"42\\r\\n\"  # add two characters to delete later\n    value = int(value_ret[:-2])\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value_ret)\n        axis.hardware_limit_configuration = value\n        mock_cmd.assert_called_with(\"ZH\", target=1, params=[float(value)])\n        assert axis.hardware_limit_configuration == value\n        mock_cmd.assert_called_with(\"ZH?\", target=1)\n\n\ndef test_axis_hardware_limit_configuration_none():\n    \"\"\"Set axis hardware limit configuration with `None` does nothing.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        axis.hardware_limit_configuration = None\n\n\ndef test_axis_acceleration_feed_forward(mocker):\n    \"\"\"Set / get axis acceleration feed forward.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value = 42\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value)\n        axis.acceleration_feed_forward = value\n        mock_cmd.assert_called_with(\"AF\", target=1, params=[float(value)])\n        assert axis.acceleration_feed_forward == value\n        mock_cmd.assert_called_with(\"AF?\", target=1)\n\n\ndef test_axis_acceleration_feed_forward_none():\n    \"\"\"Set axis acceleration feed forward with `None` does nothing.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        axis.acceleration_feed_forward = None\n\n\ndef test_axis_proportional_gain(mocker):\n    \"\"\"Set / get axis proportional gain.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value_ret = \"42\\r\"\n    value = float(value_ret[:-1])\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value_ret)\n        axis.proportional_gain = value\n        mock_cmd.assert_called_with(\"KP\", target=1, params=[float(value)])\n        assert axis.proportional_gain == float(value)\n        mock_cmd.assert_called_with(\"KP?\", target=1)\n\n\ndef test_axis_proportional_gain_none():\n    \"\"\"Set axis proportional gain with `None` does nothing.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        axis.proportional_gain = None\n\n\ndef test_axis_derivative_gain(mocker):\n    \"\"\"Set / get axis derivative gain.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value_ret = \"42\"\n    value = float(value_ret)\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value_ret)\n        axis.derivative_gain = value\n        mock_cmd.assert_called_with(\"KD\", target=1, params=[float(value)])\n        assert axis.derivative_gain == float(value)\n        mock_cmd.assert_called_with(\"KD?\", target=1)\n\n\ndef test_axis_derivative_gain_none():\n    \"\"\"Set axis derivative gain with `None` does nothing.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        axis.derivative_gain = None\n\n\ndef test_axis_integral_gain(mocker):\n    \"\"\"Set / get axis integral gain.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value_ret = \"42\"\n    value = float(value_ret)\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value_ret)\n        axis.integral_gain = value\n        mock_cmd.assert_called_with(\"KI\", target=1, params=[float(value)])\n        assert axis.integral_gain == float(value)\n        mock_cmd.assert_called_with(\"KI?\", target=1)\n\n\ndef test_axis_integral_gain_none():\n    \"\"\"Set axis integral gain with `None` does nothing.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        axis.integral_gain = None\n\n\ndef test_axis_integral_saturation_gain(mocker):\n    \"\"\"Set / get axis integral saturation gain.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value_ret = \"42\"\n    value = float(value_ret)\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value_ret)\n        axis.integral_saturation_gain = value\n        mock_cmd.assert_called_with(\"KS\", target=1, params=[float(value)])\n        assert axis.integral_saturation_gain == float(value)\n        mock_cmd.assert_called_with(\"KS?\", target=1)\n\n\ndef test_axis_integral_saturation_gain_none():\n    \"\"\"Set axis integral saturation gain with `None` does nothing.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        axis.integral_saturation_gain = None\n\n\ndef test_axis_encoder_position(mocker):\n    \"\"\"Get encoder position.\n\n    Mock out the getting and setting the units. These two routines are\n    tested separately, thus only assert that the correct calls are\n    issued.\n    Also mock out `_newport_cmd`.\n    \"\"\"\n    value = 42\n    get_unit = ik.newport.newportesp301.NewportESP301.Units.millimeter\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_get = mocker.patch.object(axis, \"_get_units\", return_value=get_unit)\n        mock_set = mocker.patch.object(axis, \"_set_units\", return_value=None)\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\", return_value=value)\n        assert axis.encoder_position == u.Quantity(value, u.count)\n        mock_get.assert_called()\n        mock_set.assert_called_with(get_unit)\n        mock_cmd.assert_called_with(\"TP?\", target=1)\n\n\n# AXIS METHODS #\n\n\n@pytest.mark.parametrize(\"mode\", ik.newport.newportesp301.NewportESP301.HomeSearchMode)\ndef test_axis_search_for_home(mocker, mode):\n    \"\"\"Search for home.\n\n    Mock out `search_for_home` of controller since already tested.\n    \"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_search = mocker.patch.object(axis._controller, \"search_for_home\")\n        axis.search_for_home(search_mode=mode.value)\n        mock_search.assert_called_with(axis=1, search_mode=mode.value)\n\n\ndef test_axis_search_for_home_default(mocker):\n    \"\"\"Search for home without a specified search mode.\n\n    Mock out `search_for_home` of controller since already tested.\n    \"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_search = mocker.patch.object(axis._controller, \"search_for_home\")\n        axis.search_for_home()\n\n        default_mode = axis._controller.HomeSearchMode.zero_position_count.value\n        mock_search.assert_called_with(axis=1, search_mode=default_mode)\n\n\ndef test_axis_move_absolute(mocker):\n    \"\"\"Make an absolute move (default) on the axis.\n\n    No wait, no block.\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    position = 42\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\")\n        axis.move(position)\n        mock_cmd.assert_called_with(\"PA\", params=[position], target=1)\n\n\ndef test_axis_move_relative_wait(mocker):\n    \"\"\"Make an relative move on the axis and wait.\n\n    Do a wait but no block.\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    position = 42\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\")\n        axis.move(position, absolute=False, wait=True)\n        calls = (\n            mocker.call(\"PR\", params=[position], target=1),\n            mocker.call(\"WP\", target=1, params=[float(position)]),\n        )\n        mock_cmd.assert_has_calls(calls)\n\n\ndef test_axis_move_relative_wait_block(mocker):\n    \"\"\"Make an relative move on the axis and wait.\n\n    Do a wait and lock, go once into while loop.\n    Mock out `_newport_cmd`, `time.sleep`, and `is_motion_done` since\n    tested elsewhere.\n    \"\"\"\n    position = 42\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\")\n        mock_cmd.side_effect = [None, None, False, True]\n        axis.move(position, absolute=False, wait=True, block=True)\n        calls = (\n            mocker.call(\"PR\", params=[position], target=1),\n            mocker.call(\"WP\", target=1, params=[float(position)]),\n            mocker.call(\"MD?\", target=1),\n            mocker.call(\"MD?\", target=1),\n        )\n        mock_cmd.assert_has_calls(calls)\n\n\ndef test_axis_move_to_hardware_limit(mocker):\n    \"\"\"Move to hardware limit.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\")\n        axis.move_to_hardware_limit()\n        mock_cmd.assert_called_with(\"MT\", target=1)\n\n\ndef test_axis_move_indefinitely(mocker):\n    \"\"\"Move indefinitely\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\")\n        axis.move_indefinitely()\n        mock_cmd.assert_called_with(\"MV\", target=1)\n\n\ndef test_axis_abort_motion(mocker):\n    \"\"\"Abort motion.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\")\n        axis.abort_motion()\n        mock_cmd.assert_called_with(\"AB\", target=1)\n\n\ndef test_axis_wait_for_stop(mocker):\n    \"\"\"Wait for stop.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\")\n        axis.wait_for_stop()\n        mock_cmd.assert_called_with(\"WS\", target=1)\n\n\ndef test_axis_stop_motion(mocker):\n    \"\"\"Stop motion.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\")\n        axis.stop_motion()\n        mock_cmd.assert_called_with(\"ST\", target=1)\n\n\ndef test_axis_wait_for_position(mocker):\n    \"\"\"Wait for position.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    value = 42\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\")\n        axis.wait_for_position(value)\n        mock_cmd.assert_called_with(\"WP\", target=1, params=[float(value)])\n\n\ndef test_axis_wait_for_motion_max_wait_zero(mocker):\n    \"\"\"Wait for motion to finish.\n\n    Motion is not stopped (mock that part) but maximum wait time is\n    zero.\n    \"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mocker.patch.object(axis, \"_newport_cmd\", return_value=\"0\")\n\n        with pytest.raises(IOError) as err_info:\n            axis.wait_for_motion(max_wait=0.0)\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"Timed out waiting for motion to finish.\"\n\n\ndef test_axis_wait_for_motion_max_wait_some_time(mocker):\n    \"\"\"Wait for motion to finish.\n\n    Motion is stopped after several queries that first return `False`.\n    Mocking `time.time`, `time.sleep`, and `_newport_cmd`. Using\n    generators to create the appropriate times..\n    \"\"\"\n    interval = 42.0\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        # patch time and sleep\n        mock_time = mocker.patch.object(time, \"time\", return_value=None)\n        mock_time.side_effect = [0.0, 0.0, 0.1]\n        mock_sleep = mocker.patch.object(time, \"sleep\", return_value=None)\n        # get axis\n        axis = inst.axis[0]\n        # patch status\n        mock_status = mocker.patch.object(axis, \"_newport_cmd\", return_value=None)\n        mock_status.side_effect = [\"0\", \"0\", \"1\"]\n        assert axis.wait_for_motion(poll_interval=interval) is None\n        # make sure the routine has called sleep\n        mock_sleep.assert_called_with(interval)\n\n\ndef test_axis_enable(mocker):\n    \"\"\"Enable axis.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\")\n        axis.enable()\n        mock_cmd.assert_called_with(\"MO\", target=1)\n\n\ndef test_axis_disable(mocker):\n    \"\"\"Disable axis.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\")\n        axis.disable()\n        mock_cmd.assert_called_with(\"MF\", target=1)\n\n\ndef test_axis_setup_axis(mocker):\n    \"\"\"Set up non-newport motor.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    motor_type = 2  # stepper motor\n    current = 1\n    voltage = 2\n    units = ik.newport.newportesp301.NewportESP301.Units.radian\n    encoder_resolution = 3.0\n    max_velocity = 4\n    max_base_velocity = 5\n    homing_velocity = 6\n    jog_high_velocity = 7\n    jog_low_velocity = 8\n    max_acceleration = 9\n    acceleration = 10\n    velocity = 11\n    deceleration = 12\n    estop_deceleration = 13\n    jerk = 14\n    error_threshold = 15\n    proportional_gain = 16\n    derivative_gain = 17\n    integral_gain = 18\n    integral_saturation_gain = 19\n    trajectory = 20\n    position_display_resolution = 21\n    feedback_configuration = 22\n    full_step_resolution = 23\n    home = 24\n    microstep_factor = 25\n    acceleration_feed_forward = 26\n    hardware_limit_configuration = 27\n\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\")\n        mocker.patch.object(axis, \"read_setup\", return_value=True)\n        ax_setup = axis.setup_axis(\n            motor_type=motor_type,\n            current=current,\n            voltage=voltage,\n            units=units,\n            encoder_resolution=encoder_resolution,\n            max_velocity=max_velocity,\n            max_base_velocity=max_base_velocity,\n            homing_velocity=homing_velocity,\n            jog_high_velocity=jog_high_velocity,\n            jog_low_velocity=jog_low_velocity,\n            max_acceleration=max_acceleration,\n            acceleration=acceleration,\n            velocity=velocity,\n            deceleration=deceleration,\n            estop_deceleration=estop_deceleration,\n            jerk=jerk,\n            error_threshold=error_threshold,\n            proportional_gain=proportional_gain,\n            derivative_gain=derivative_gain,\n            integral_gain=integral_gain,\n            integral_saturation_gain=integral_saturation_gain,\n            trajectory=trajectory,\n            position_display_resolution=position_display_resolution,\n            feedback_configuration=feedback_configuration,\n            full_step_resolution=full_step_resolution,\n            home=home,\n            microstep_factor=microstep_factor,\n            acceleration_feed_forward=acceleration_feed_forward,\n            hardware_limit_configuration=hardware_limit_configuration,\n        )\n        assert ax_setup\n\n        # assert mandatory calls in any order\n        calls_params = (\n            mocker.call(\"QM\", target=1, params=[int(motor_type)]),\n            mocker.call(\"ZB\", target=1, params=[int(feedback_configuration)]),\n            mocker.call(\"FR\", target=1, params=[float(full_step_resolution)]),\n            mocker.call(\"FP\", target=1, params=[int(position_display_resolution)]),\n            mocker.call(\"QI\", target=1, params=[float(current)]),\n            mocker.call(\"QV\", target=1, params=[float(voltage)]),\n            mocker.call(\"SN\", target=1, params=[units.value]),\n            mocker.call(\"SU\", target=1, params=[float(encoder_resolution)]),\n            mocker.call(\"AU\", target=1, params=[float(max_acceleration)]),\n            mocker.call(\"VU\", target=1, params=[float(max_velocity)]),\n            mocker.call(\"VB\", target=1, params=[float(max_base_velocity)]),\n            mocker.call(\"OH\", target=1, params=[float(homing_velocity)]),\n            mocker.call(\"JH\", target=1, params=[float(jog_high_velocity)]),\n            mocker.call(\"JW\", target=1, params=[float(jog_low_velocity)]),\n            mocker.call(\"AC\", target=1, params=[float(acceleration)]),\n            mocker.call(\"VA\", target=1, params=[float(velocity)]),\n            mocker.call(\"AG\", target=1, params=[float(deceleration)]),\n            mocker.call(\"AE\", target=1, params=[float(estop_deceleration)]),\n            mocker.call(\"JK\", target=1, params=[float(jerk)]),\n            mocker.call(\"FE\", target=1, params=[float(error_threshold)]),\n            mocker.call(\"KP\", target=1, params=[float(proportional_gain)]),\n            mocker.call(\"KD\", target=1, params=[float(derivative_gain)]),\n            mocker.call(\"KI\", target=1, params=[float(integral_gain)]),\n            mocker.call(\"KS\", target=1, params=[float(integral_saturation_gain)]),\n            mocker.call(\"DH\", target=1, params=[float(home)]),\n            mocker.call(\"QS\", target=1, params=[float(microstep_factor)]),\n            mocker.call(\"AF\", target=1, params=[float(acceleration_feed_forward)]),\n            mocker.call(\"TJ\", target=1, params=[int(trajectory)]),\n            mocker.call(\"ZH\", target=1, params=[int(hardware_limit_configuration)]),\n        )\n        mock_cmd.assert_has_calls(calls_params, any_order=True)\n\n        # assert final calls - in order\n        calls_final = (\n            mocker.call(\"UF\", target=1),\n            mocker.call(\"QD\", target=1),\n            mocker.call(\"SM\"),\n        )\n        mock_cmd.assert_has_calls(calls_final)\n        mock_cmd.assert_called_with(\"SM\")\n\n\ndef test_axis_setup_axis_torque(mocker):\n    \"\"\"Set up non-newport motor with torque specifications.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    motor_type = 2  # stepper motor\n    current = 1\n    voltage = 2\n    units = ik.newport.newportesp301.NewportESP301.Units.radian\n    encoder_resolution = 3.0\n    max_velocity = 4\n    max_base_velocity = 5\n    homing_velocity = 6\n    jog_high_velocity = 7\n    jog_low_velocity = 8\n    max_acceleration = 9\n    acceleration = 10\n    velocity = 11\n    deceleration = 12\n    estop_deceleration = 13\n    jerk = 14\n    error_threshold = 15\n    proportional_gain = 16\n    derivative_gain = 17\n    integral_gain = 18\n    integral_saturation_gain = 19\n    trajectory = 20\n    position_display_resolution = 21\n    feedback_configuration = 22\n    full_step_resolution = 23\n    home = 24\n    microstep_factor = 25\n    acceleration_feed_forward = 26\n    hardware_limit_configuration = 27\n    # special configs\n    rmt_time = 42\n    rmt_perc = 13\n\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\")\n        mocker.patch.object(axis, \"read_setup\", return_value=True)\n        axis.setup_axis(\n            motor_type=motor_type,\n            current=current,\n            voltage=voltage,\n            units=units,\n            encoder_resolution=encoder_resolution,\n            max_velocity=max_velocity,\n            max_base_velocity=max_base_velocity,\n            homing_velocity=homing_velocity,\n            jog_high_velocity=jog_high_velocity,\n            jog_low_velocity=jog_low_velocity,\n            max_acceleration=max_acceleration,\n            acceleration=acceleration,\n            velocity=velocity,\n            deceleration=deceleration,\n            estop_deceleration=estop_deceleration,\n            jerk=jerk,\n            error_threshold=error_threshold,\n            proportional_gain=proportional_gain,\n            derivative_gain=derivative_gain,\n            integral_gain=integral_gain,\n            integral_saturation_gain=integral_saturation_gain,\n            trajectory=trajectory,\n            position_display_resolution=position_display_resolution,\n            feedback_configuration=feedback_configuration,\n            full_step_resolution=full_step_resolution,\n            home=home,\n            microstep_factor=microstep_factor,\n            acceleration_feed_forward=acceleration_feed_forward,\n            hardware_limit_configuration=hardware_limit_configuration,\n            reduce_motor_torque_time=rmt_time,\n            reduce_motor_torque_percentage=rmt_perc,\n        )\n        # ensure the torque settings are set\n        call_torque = (mocker.call(\"QR\", target=1, params=[rmt_time, rmt_perc]),)\n\n        mock_cmd.assert_has_calls(call_torque)\n\n\n@given(rmt_time=st.integers().filter(lambda x: x < 0 or x > 60000))\ndef test_axis_setup_axis_torque_time_out_of_range(rmt_time):\n    \"\"\"Raise ValueError when time is out of range.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    motor_type = 2  # stepper motor\n    current = 1\n    voltage = 2\n    units = ik.newport.newportesp301.NewportESP301.Units.radian\n    encoder_resolution = 3.0\n    max_velocity = 4\n    max_base_velocity = 5\n    homing_velocity = 6\n    jog_high_velocity = 7\n    jog_low_velocity = 8\n    max_acceleration = 9\n    acceleration = 10\n    velocity = 11\n    deceleration = 12\n    estop_deceleration = 13\n    jerk = 14\n    error_threshold = 15\n    proportional_gain = 16\n    derivative_gain = 17\n    integral_gain = 18\n    integral_saturation_gain = 19\n    trajectory = 20\n    position_display_resolution = 21\n    feedback_configuration = 22\n    full_step_resolution = 23\n    home = 24\n    microstep_factor = 25\n    acceleration_feed_forward = 26\n    hardware_limit_configuration = 27\n    # special configs\n    rmt_perc = 13\n\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        with mock.patch.object(axis, \"_newport_cmd\"), mock.patch.object(\n            axis, \"read_setup\", return_value=True\n        ), pytest.raises(ValueError) as err_info:\n            axis.setup_axis(\n                motor_type=motor_type,\n                current=current,\n                voltage=voltage,\n                units=units,\n                encoder_resolution=encoder_resolution,\n                max_velocity=max_velocity,\n                max_base_velocity=max_base_velocity,\n                homing_velocity=homing_velocity,\n                jog_high_velocity=jog_high_velocity,\n                jog_low_velocity=jog_low_velocity,\n                max_acceleration=max_acceleration,\n                acceleration=acceleration,\n                velocity=velocity,\n                deceleration=deceleration,\n                estop_deceleration=estop_deceleration,\n                jerk=jerk,\n                error_threshold=error_threshold,\n                proportional_gain=proportional_gain,\n                derivative_gain=derivative_gain,\n                integral_gain=integral_gain,\n                integral_saturation_gain=integral_saturation_gain,\n                trajectory=trajectory,\n                position_display_resolution=position_display_resolution,\n                feedback_configuration=feedback_configuration,\n                full_step_resolution=full_step_resolution,\n                home=home,\n                microstep_factor=microstep_factor,\n                acceleration_feed_forward=acceleration_feed_forward,\n                hardware_limit_configuration=hardware_limit_configuration,\n                reduce_motor_torque_time=rmt_time,\n                reduce_motor_torque_percentage=rmt_perc,\n            )\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"Time must be between 0 and 60000 ms\"\n\n\n@given(rmt_perc=st.integers().filter(lambda x: x < 0 or x > 100))\ndef test_axis_setup_axis_torque_percentage_out_of_range(rmt_perc):\n    \"\"\"Raise ValueError when time is out of range.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    motor_type = 2  # stepper motor\n    current = 1\n    voltage = 2\n    units = ik.newport.newportesp301.NewportESP301.Units.radian\n    encoder_resolution = 3.0\n    max_velocity = 4\n    max_base_velocity = 5\n    homing_velocity = 6\n    jog_high_velocity = 7\n    jog_low_velocity = 8\n    max_acceleration = 9\n    acceleration = 10\n    velocity = 11\n    deceleration = 12\n    estop_deceleration = 13\n    jerk = 14\n    error_threshold = 15\n    proportional_gain = 16\n    derivative_gain = 17\n    integral_gain = 18\n    integral_saturation_gain = 19\n    trajectory = 20\n    position_display_resolution = 21\n    feedback_configuration = 22\n    full_step_resolution = 23\n    home = 24\n    microstep_factor = 25\n    acceleration_feed_forward = 26\n    hardware_limit_configuration = 27\n    # special configs\n    rmt_time = 42\n\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        with mock.patch.object(axis, \"_newport_cmd\"), mock.patch.object(\n            axis, \"read_setup\", return_value=True\n        ), pytest.raises(ValueError) as err_info:\n            axis.setup_axis(\n                motor_type=motor_type,\n                current=current,\n                voltage=voltage,\n                units=units,\n                encoder_resolution=encoder_resolution,\n                max_velocity=max_velocity,\n                max_base_velocity=max_base_velocity,\n                homing_velocity=homing_velocity,\n                jog_high_velocity=jog_high_velocity,\n                jog_low_velocity=jog_low_velocity,\n                max_acceleration=max_acceleration,\n                acceleration=acceleration,\n                velocity=velocity,\n                deceleration=deceleration,\n                estop_deceleration=estop_deceleration,\n                jerk=jerk,\n                error_threshold=error_threshold,\n                proportional_gain=proportional_gain,\n                derivative_gain=derivative_gain,\n                integral_gain=integral_gain,\n                integral_saturation_gain=integral_saturation_gain,\n                trajectory=trajectory,\n                position_display_resolution=position_display_resolution,\n                feedback_configuration=feedback_configuration,\n                full_step_resolution=full_step_resolution,\n                home=home,\n                microstep_factor=microstep_factor,\n                acceleration_feed_forward=acceleration_feed_forward,\n                hardware_limit_configuration=hardware_limit_configuration,\n                reduce_motor_torque_time=rmt_time,\n                reduce_motor_torque_percentage=rmt_perc,\n            )\n        err_msg = err_info.value.args[0]\n        assert err_msg == r\"Percentage must be between 0 and 100%\"\n\n\ndef test_axis_read_setup(mocker):\n    \"\"\"Read the axis setup and return it.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    config = {\n        \"units\": u.mm,\n        \"motor_type\": ik.newport.newportesp301.NewportESP301.MotorType.dc_servo,\n        \"feedback_configuration\": 1,  # last 2 removed at return\n        \"full_step_resolution\": u.Quantity(2.0, u.mm),\n        \"position_display_resolution\": 3,\n        \"current\": u.Quantity(4.0, u.A),\n        \"max_velocity\": u.Quantity(5.0, u.mm / u.s),\n        \"encoder_resolution\": u.Quantity(6.0, u.mm),\n        \"acceleration\": u.Quantity(7.0, u.mm / u.s**2),\n        \"deceleration\": u.Quantity(8.0, u.mm / u.s**2),\n        \"velocity\": u.Quantity(9.0, u.mm / u.s),\n        \"max_acceleration\": u.Quantity(10.0, u.mm / u.s**2.0),\n        \"homing_velocity\": u.Quantity(11.0, u.mm / u.s),\n        \"jog_high_velocity\": u.Quantity(12.0, u.mm / u.s),\n        \"jog_low_velocity\": u.Quantity(13.0, u.mm / u.s),\n        \"estop_deceleration\": u.Quantity(14.0, u.mm / u.s**2.0),\n        \"jerk\": u.Quantity(14.0, u.mm / u.s**3.0),\n        \"proportional_gain\": 15.0,  # last 1 removed at return\n        \"derivative_gain\": 16.0,\n        \"integral_gain\": 17.0,\n        \"integral_saturation_gain\": 18.0,\n        \"home\": u.Quantity(19.0, u.mm),\n        \"microstep_factor\": 20,\n        \"acceleration_feed_forward\": 21.0,\n        \"trajectory\": 22,\n        \"hardware_limit_configuration\": 23,  # last 2 removed\n    }\n\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\")\n        mock_cmd.side_effect = [\n            ik.newport.newportesp301.NewportESP301.Units.millimeter.value,\n            config[\"motor_type\"].value,\n            f\"{config['feedback_configuration']}**\",  # 2 extra\n            config[\"full_step_resolution\"].magnitude,\n            config[\"position_display_resolution\"],\n            config[\"current\"].magnitude,\n            config[\"max_velocity\"].magnitude,\n            config[\"encoder_resolution\"].magnitude,\n            config[\"acceleration\"].magnitude,\n            config[\"deceleration\"].magnitude,\n            config[\"velocity\"].magnitude,\n            config[\"max_acceleration\"].magnitude,\n            config[\"homing_velocity\"].magnitude,\n            config[\"jog_high_velocity\"].magnitude,\n            config[\"jog_low_velocity\"].magnitude,\n            config[\"estop_deceleration\"].magnitude,\n            config[\"jerk\"].magnitude,\n            f\"{config['proportional_gain']}*\",  # 1 extra\n            config[\"derivative_gain\"],\n            config[\"integral_gain\"],\n            config[\"integral_saturation_gain\"],\n            config[\"home\"].magnitude,\n            config[\"microstep_factor\"],\n            config[\"acceleration_feed_forward\"],\n            config[\"trajectory\"],\n            f\"{config['hardware_limit_configuration']}**\",\n        ]\n        assert axis.read_setup() == config\n\n\ndef test_axis_get_status(mocker):\n    \"\"\"Get an axis status.\n\n    Mock out `_newport_cmd` since tested elsewhere.\n    \"\"\"\n    status = {\n        \"units\": u.mm,\n        \"position\": u.Quantity(1.0, u.mm),\n        \"desired_position\": u.Quantity(2.0, u.mm),\n        \"desired_velocity\": u.Quantity(3.0, u.mm / u.s),\n        \"is_motion_done\": True,\n    }\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis, \"_newport_cmd\")\n        mock_cmd.side_effect = [\n            \"2\",\n            status[\"position\"].magnitude,\n            status[\"desired_position\"].magnitude,\n            status[\"desired_velocity\"].magnitude,\n            \"1\",\n        ]\n        assert axis.get_status() == status\n\n\n@pytest.mark.parametrize(\"num\", ik.newport.NewportESP301.Axis._unit_dict)\ndef test_axis_get_pq_unit(num):\n    \"\"\"Get units for specified axis.\"\"\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        assert axis._get_pq_unit(num) == axis._unit_dict[num]\n\n\n@pytest.mark.parametrize(\"num\", ik.newport.NewportESP301.Axis._unit_dict)\ndef test_axis_get_unit_num(num):\n    \"\"\"Get unit number from dictionary.\n\n    Skip number 1, since u.count appears twice in dictionary!\n    \"\"\"\n    if num == 1:\n        num = 0  # u.count twice\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        quant = axis._unit_dict[num]\n        print(quant)\n        assert axis._get_unit_num(quant) == num\n\n\ndef test_axis_get_unit_num_invalid_unit():\n    \"\"\"Raise KeyError if unit not valid.\"\"\"\n    invalid_unit = u.ly\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        with pytest.raises(KeyError) as err_info:\n            axis._get_unit_num(invalid_unit)\n        err_msg = err_info.value.args[0]\n        assert err_msg == f\"{invalid_unit} is not a valid unit for Newport \" f\"Axis\"\n\n\ndef test_axis_newport_cmd(mocker):\n    \"\"\"Send command to parent class.\n\n    Mock out parent classes `_newport_cmd` and assert call.\n    \"\"\"\n    cmd = 123\n    some_keyword = \"keyword\"\n    with expected_protocol(\n        ik.newport.NewportESP301, [ax_init[0]], [ax_init[1]], sep=\"\\r\"\n    ) as inst:\n        axis = inst.axis[0]\n        mock_cmd = mocker.patch.object(axis._controller, \"_newport_cmd\")\n        axis._newport_cmd(cmd, some_keyword=some_keyword)\n        mock_cmd.assert_called_with(cmd, some_keyword=some_keyword)\n"
  },
  {
    "path": "tests/test_ondax/test_lm.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the Ondax Laser Module\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport pytest\n\nfrom instruments import ondax\nfrom tests import expected_protocol\nfrom instruments.units import ureg as u\n\n# TESTS #######################################################################\n\n\ndef test_acc_target():\n    with expected_protocol(ondax.LM, [\"rstli?\"], [\"100\"], sep=\"\\r\") as lm:\n        assert lm.acc.target == 100 * u.mA\n\n\ndef test_acc_enable():\n    with expected_protocol(ondax.LM, [\"lcen\"], [\"OK\"], sep=\"\\r\") as lm:\n        lm.acc.enabled = True\n        assert lm.acc.enabled\n\n\ndef test_acc_disable():\n    with expected_protocol(ondax.LM, [\"lcdis\"], [\"OK\"], sep=\"\\r\") as lm:\n        lm.acc.enabled = False\n        assert not lm.acc.enabled\n\n\ndef test_acc_enable_not_boolean():\n    with pytest.raises(TypeError):\n        with expected_protocol(ondax.LM, [], [], sep=\"\\r\") as lm:\n            lm.acc.enabled = \"foobar\"\n\n\ndef test_acc_on():\n    with expected_protocol(ondax.LM, [\"lcon\"], [\"OK\"], sep=\"\\r\") as lm:\n        lm.acc.on()\n\n\ndef test_acc_off():\n    with expected_protocol(ondax.LM, [\"lcoff\"], [\"OK\"], sep=\"\\r\") as lm:\n        lm.acc.off()\n\n\ndef test_apc_target():\n    with expected_protocol(ondax.LM, [\"rslp?\"], [\"100\"], sep=\"\\r\") as lm:\n        assert lm.apc.target == 100 * u.mW\n\n\ndef test_apc_enable():\n    with expected_protocol(ondax.LM, [\"len\"], [\"OK\"], sep=\"\\r\") as lm:\n        lm.apc.enabled = True\n        assert lm.apc.enabled\n\n\ndef test_apc_disable():\n    with expected_protocol(ondax.LM, [\"ldis\"], [\"OK\"], sep=\"\\r\") as lm:\n        lm.apc.enabled = False\n        assert not lm.apc.enabled\n\n\ndef test_apc_enable_not_boolean():\n    with pytest.raises(TypeError):\n        with expected_protocol(ondax.LM, [], [], sep=\"\\r\") as lm:\n            lm.apc.enabled = \"foobar\"\n\n\ndef test_apc_start():\n    with expected_protocol(ondax.LM, [\"sps\"], [\"OK\"], sep=\"\\r\") as lm:\n        lm.apc.start()\n\n\ndef test_apc_stop():\n    with expected_protocol(ondax.LM, [\"cps\"], [\"OK\"], sep=\"\\r\") as lm:\n        lm.apc.stop()\n\n\ndef test_modulation_on_time():\n    with expected_protocol(\n        ondax.LM, [\"stsont?\", \"stsont:20\"], [\"10\", \"OK\"], sep=\"\\r\"\n    ) as lm:\n        assert lm.modulation.on_time == 10 * u.ms\n        lm.modulation.on_time = 20 * u.ms\n\n\ndef test_modulation_off_time():\n    with expected_protocol(\n        ondax.LM, [\"stsofft?\", \"stsofft:20\"], [\"10\", \"OK\"], sep=\"\\r\"\n    ) as lm:\n        assert lm.modulation.off_time == 10 * u.ms\n        lm.modulation.off_time = 20 * u.ms\n\n\ndef test_modulation_enabled():\n    with expected_protocol(ondax.LM, [\"stm\"], [\"OK\"], sep=\"\\r\") as lm:\n        lm.modulation.enabled = True\n        assert lm.modulation.enabled\n\n\ndef test_modulation_disabled():\n    with expected_protocol(ondax.LM, [\"ctm\"], [\"OK\"], sep=\"\\r\") as lm:\n        lm.modulation.enabled = False\n        assert not lm.modulation.enabled\n\n\ndef test_modulation_enable_not_boolean():\n    with pytest.raises(TypeError):\n        with expected_protocol(ondax.LM, [], [], sep=\"\\r\") as lm:\n            lm.modulation.enabled = \"foobar\"\n\n\ndef test_tec_current():\n    with expected_protocol(ondax.LM, [\"rti?\"], [\"100\"], sep=\"\\r\") as lm:\n        assert lm.tec.current == 100 * u.mA\n\n\ndef test_tec_target():\n    with expected_protocol(ondax.LM, [\"rstt?\"], [\"22\"], sep=\"\\r\") as lm:\n        assert lm.tec.target == u.Quantity(22, u.degC)\n\n\ndef test_tec_enable():\n    with expected_protocol(ondax.LM, [\"tecon\"], [\"OK\"], sep=\"\\r\") as lm:\n        lm.tec.enabled = True\n        assert lm.tec.enabled\n\n\ndef test_tec_disable():\n    with expected_protocol(ondax.LM, [\"tecoff\"], [\"OK\"], sep=\"\\r\") as lm:\n        lm.tec.enabled = False\n        assert not lm.tec.enabled\n\n\ndef test_tec_enable_not_boolean():\n    with pytest.raises(TypeError):\n        with expected_protocol(ondax.LM, [], [], sep=\"\\r\") as lm:\n            lm.tec.enabled = \"foobar\"\n\n\ndef test_firmware():\n    with expected_protocol(ondax.LM, [\"rsv?\"], [\"3.27\"], sep=\"\\r\") as lm:\n        assert lm.firmware == \"3.27\"\n\n\ndef test_current():\n    with expected_protocol(\n        ondax.LM, [\"rli?\", \"slc:100\"], [\"120\", \"OK\"], sep=\"\\r\"\n    ) as lm:\n        assert lm.current == 120 * u.mA\n        lm.current = 100 * u.mA\n\n\ndef test_maximum_current():\n    with expected_protocol(\n        ondax.LM, [\"rlcm?\", \"smlc:100\"], [\"120\", \"OK\"], sep=\"\\r\"\n    ) as lm:\n        assert lm.maximum_current == 120 * u.mA\n        lm.maximum_current = 100 * u.mA\n\n\ndef test_power():\n    with expected_protocol(\n        ondax.LM, [\"rlp?\", \"slp:100\"], [\"120\", \"OK\"], sep=\"\\r\"\n    ) as lm:\n        assert lm.power == 120 * u.mW\n        lm.power = 100 * u.mW\n\n\ndef test_serial_number():\n    with expected_protocol(ondax.LM, [\"rsn?\"], [\"B099999\"], sep=\"\\r\") as lm:\n        assert lm.serial_number == \"B099999\"\n\n\ndef test_status():\n    with expected_protocol(ondax.LM, [\"rlrs?\"], [\"1\"], sep=\"\\r\") as lm:\n        assert lm.status == lm.Status(1)\n\n\ndef test_temperature():\n    with expected_protocol(ondax.LM, [\"rtt?\", \"stt:40\"], [\"35\", \"OK\"], sep=\"\\r\") as lm:\n        assert lm.temperature == u.Quantity(35, u.degC)\n        lm.temperature = u.Quantity(40, u.degC)\n\n\ndef test_enable():\n    with expected_protocol(ondax.LM, [\"lon\"], [\"OK\"], sep=\"\\r\") as lm:\n        lm.enabled = True\n        assert lm.enabled\n\n\ndef test_disable():\n    with expected_protocol(ondax.LM, [\"loff\"], [\"OK\"], sep=\"\\r\") as lm:\n        lm.enabled = False\n        assert not lm.enabled\n\n\ndef test_enable_not_boolean():\n    with pytest.raises(TypeError):\n        with expected_protocol(ondax.LM, [], [], sep=\"\\r\") as lm:\n            lm.enabled = \"foobar\"\n\n\ndef test_save():\n    with expected_protocol(ondax.LM, [\"ssc\"], [\"OK\"], sep=\"\\r\") as lm:\n        lm.save()\n\n\ndef test_reset():\n    with expected_protocol(ondax.LM, [\"reset\"], [\"OK\"], sep=\"\\r\") as lm:\n        lm.reset()\n"
  },
  {
    "path": "tests/test_oxford/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_oxford/test_oxforditc503.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the Oxford ITC 503 temperature controller\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport instruments as ik\nfrom tests import expected_protocol\nfrom instruments.units import ureg as u\n\n# TESTS #######################################################################\n\n\ndef test_sensor_returns_sensor_class():\n    with expected_protocol(ik.oxford.OxfordITC503, [\"C3\"], [], sep=\"\\r\") as inst:\n        sensor = inst.sensor[0]\n        assert isinstance(sensor, inst.Sensor) is True\n\n\ndef test_sensor_temperature():\n    with expected_protocol(\n        ik.oxford.OxfordITC503, [\"C3\", \"R1\"], [\"R123\"], sep=\"\\r\"\n    ) as inst:\n        sensor = inst.sensor[0]\n        assert sensor.temperature == u.Quantity(123, u.kelvin)\n"
  },
  {
    "path": "tests/test_package.py",
    "content": "\"\"\"\nModule containing tests for the base instruments package\n\"\"\"\n\n# IMPORTS ####################################################################\n\nimport instruments._version as ik_version_file\n\n# TEST CASES #################################################################\n\n\ndef test_package_has_version():\n    assert hasattr(ik_version_file, \"version\")\n    assert hasattr(ik_version_file, \"version_tuple\")\n"
  },
  {
    "path": "tests/test_pfeiffer/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_pfeiffer/test_tpg36x.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the TPG 36x gauge controller\n\"\"\"\n\n# IMPORTS ####################################################################\n\nfrom ipaddress import ip_address\n\nimport pytest\n\nimport instruments as ik\nfrom instruments.units import ureg as u\nfrom tests import expected_protocol, unit_eq\n\n# TESTS ######################################################################\n\n# pylint: disable=no-member,protected-access\n\nSEP = \"\\r\\n\"\nENQ = chr(5)\nACK = chr(6)\nNAK = chr(21)\n\n# CHANNELS #\n\n\ndef test_tpg36x_channel_init():\n    \"\"\"Ensure an error is raised when not coming from correct class.\"\"\"\n    with pytest.raises(TypeError):\n        ik.pfeiffer.TPG36x.Channel(42, 0)\n\n\ndef test_tpg36x_channel_pressure():\n    \"\"\"Get the pressure from the device.\"\"\"\n    with expected_protocol(\n        ik.pfeiffer.TPG36x,\n        [\"PR1\", SEP, ENQ, \"UNI\", SEP, ENQ],\n        [ACK, SEP, \"0,2.0000E-2\", SEP, ACK, SEP, \"0\", SEP],\n        sep=\"\",\n    ) as tpg:\n        ch = tpg.channel[0]\n        assert ch.pressure == 0.02 * u.mbar\n\n\n@pytest.mark.parametrize(\n    \"status\",\n    [\n        [1, \"Underrange\"],\n        [2, \"Overrange\"],\n        [3, \"Sensor error\"],\n        [4, \"Sensor off\"],\n        [5, \"No sensor\"],\n        [6, \"Identification error\"],\n        [42, \"Unknown error\"],\n    ],\n)\ndef test_tpg36x_channel_pressure_error(status):\n    \"\"\"Raise correct error if statos is not zero.\"\"\"\n    err_code, err_meaning = status\n    with expected_protocol(\n        ik.pfeiffer.TPG36x,\n        [\"PR1\", SEP, ENQ],\n        [ACK, SEP, f\"{err_code},2.0000E-2\", SEP],\n        sep=\"\",\n    ) as tpg:\n        ch = tpg.channel[0]\n        with pytest.raises(OSError) as err:\n            ch.pressure\n            assert err.value.args[0].contains(err_meaning)\n\n\ndef test_tpg36x_channel_status():\n    \"\"\"Set/get the status of a channel.\"\"\"\n    with expected_protocol(\n        ik.pfeiffer.TPG36x,\n        [\"SEN\", SEP, ENQ, \"SEN,0,2\", SEP, \"SEN\", SEP, ENQ],\n        [ACK, SEP, \"0,1\", SEP, ACK, SEP, ACK, SEP, \"0,2\", SEP],\n        sep=\"\",\n    ) as tpg:\n        ch0 = tpg.channel[0]\n        assert ch0.status == ch0.SensorStatus.CANNOT_TURN_ON_OFF\n        ch1 = tpg.channel[1]\n        ch1.status = ch1.SensorStatus.ON\n        assert ch1.status == ch1.SensorStatus.ON\n\n\ndef test_tpg36x_channel_status_error():\n    \"\"\"Raise ValueErrors if bad statuses are given.\"\"\"\n    with expected_protocol(\n        ik.pfeiffer.TPG36x,\n        [],\n        [],\n        sep=\"\",\n    ) as tpg:\n        ch = tpg.channel[0]\n        with pytest.raises(ValueError):\n            ch.status = 42\n        with pytest.raises(ValueError):\n            ch.status = ch.SensorStatus.CANNOT_TURN_ON_OFF\n\n\n# TPG36x #1\n\n\n@pytest.mark.parametrize(\n    \"addrs\",\n    [\n        [\"192.168.1.10\", \"255.255.255.0\", \"192.168.1.1\"],\n        [\n            ip_address(\"192.168.1.10\"),\n            ip_address(\"255.255.255.0\"),\n            ip_address(\"192.168.1.1\"),\n        ],\n    ],\n)\ndef test_tpg36x_ethernet_configuration_static(addrs):\n    \"\"\"Set/get the ethernet configuration (static).\"\"\"\n    ip, subnet, gateway = addrs\n    with expected_protocol(\n        ik.pfeiffer.TPG36x,\n        [f\"ETH,0,{ip},{subnet},{gateway}\", SEP, \"ETH\", SEP, ENQ],\n        [ACK, SEP, ACK, SEP, f\"0,{ip},{subnet},{gateway}\", SEP],\n        sep=\"\",\n    ) as tpg:\n        tpg.ethernet_configuration = [\n            tpg.EthernetMode.STATIC,\n            \"192.168.1.10\",\n            \"255.255.255.0\",\n            \"192.168.1.1\",\n        ]\n        ret_val = tpg.ethernet_configuration\n        assert isinstance(ret_val, list)\n\n        mode, ip_rec, subnet_rec, gateway_rec = ret_val\n        assert mode == tpg.EthernetMode.STATIC\n        assert str(ip) == ip_rec\n        assert str(subnet) == subnet_rec\n        assert str(gateway) == gateway_rec\n\n\ndef test_tpg36x_ethernet_configuration_dhcp():\n    \"\"\"Set/get the ethernet configuration (dhcp).\"\"\"\n    with expected_protocol(\n        ik.pfeiffer.TPG36x,\n        [\"ETH,1\", SEP],\n        [ACK, SEP],\n        sep=\"\",\n    ) as tpg:\n        tpg.ethernet_configuration = tpg.EthernetMode.DHCP\n\n\ndef test_tpg36x_ethernet_configuration_errors():\n    \"\"\"Raise appropriate errors for bad settings.\"\"\"\n    good_addr = \"192.168.1.1\"\n    with expected_protocol(\n        ik.pfeiffer.TPG36x,\n        [],\n        [],\n        sep=\"\",\n    ) as tpg:\n        with pytest.raises(ValueError):  # invalid list\n            tpg.ethernet_configuration = [tpg.EthernetMode.STATIC, 42]\n        with pytest.raises(ValueError):  # first value not an EthernetMode\n            tpg.ethernet_configuration = [42, good_addr, good_addr, good_addr]\n\n\ndef test_tpg36x_language():\n    \"\"\"Set/get the language of the TPG36x.\"\"\"\n    with expected_protocol(\n        ik.pfeiffer.TPG36x,\n        [\"LNG,0\", SEP, \"LNG\", SEP, ENQ],\n        [ACK, SEP, ACK, SEP, \"0\", SEP],\n        sep=\"\",\n    ) as tpg:\n        tpg.language = tpg.Language.ENGLISH\n        assert tpg.language == tpg.Language.ENGLISH\n\n\ndef test_tpg36x_mac_address():\n    \"\"\"Get the MAC address of the TPG36x.\"\"\"\n    with expected_protocol(\n        ik.pfeiffer.TPG36x,\n        [\"MAC\", SEP, ENQ],\n        [ACK, SEP, \"00:00:00:00:00:00\", SEP],\n        sep=\"\",\n    ) as tpg:\n        assert tpg.mac_address == \"00:00:00:00:00:00\"\n\n\ndef test_tpg36x_mac_address_name():\n    \"\"\"Get the name from the TPG36x.\"\"\"\n    with expected_protocol(\n        ik.pfeiffer.TPG36x,\n        [\"AYT\", SEP, ENQ],\n        [ACK, SEP, \"TPG 362,PTG28290,44990000,010100,010100\", SEP],\n        sep=\"\",\n    ) as tpg:\n        assert tpg.name == \"TPG 362\"\n\n\ndef test_tpg36x_number_channels():\n    \"\"\"Set/get the number of channels.\"\"\"\n    with expected_protocol(\n        ik.pfeiffer.TPG36x,\n        [],\n        [],\n        sep=\"\",\n    ) as tpg:\n        tpg.number_channels = 2\n        assert tpg.number_channels == 2\n\n    with expected_protocol(\n        ik.pfeiffer.TPG36x,\n        [],\n        [],\n        sep=\"\",\n    ) as tpg:\n        tpg.number_channels = 1\n        assert tpg.number_channels == 1\n\n\n@pytest.mark.parametrize(\"bad_num\", [3, 0])\ndef test_tpg36x_number_channels_error(bad_num):\n    \"\"\"Raise ValueErrors if bad number of channels are given.\"\"\"\n    with expected_protocol(\n        ik.pfeiffer.TPG36x,\n        [],\n        [],\n        sep=\"\",\n    ) as tpg:\n        with pytest.raises(ValueError):\n            tpg.number_channels = bad_num\n\n\ndef test_tpg36x_pressure():\n    \"\"\"Get the pressure from a one-channel device.\"\"\"\n    with expected_protocol(\n        ik.pfeiffer.TPG36x,\n        [\"PR1\", SEP, ENQ, \"UNI\", SEP, ENQ],\n        [ACK, SEP, \"0,2.0000E-2\", SEP, ACK, SEP, \"0\", SEP],\n        sep=\"\",\n    ) as tpg:\n        assert tpg.pressure == 0.02 * u.mbar\n\n\n@pytest.mark.parametrize(\"ret_val\", [0, 1, 2, 3, 4, 5])\ndef test_tpg36x_unit(ret_val):\n    \"\"\"Get the unit from the device.\"\"\"\n    with expected_protocol(\n        ik.pfeiffer.TPG36x,\n        [\"UNI\", SEP, ENQ],\n        [ACK, SEP, f\"{int(ret_val)}\", SEP],\n        sep=\"\",\n    ) as tpg:\n        assert tpg.unit == tpg.Unit(ret_val)\n\n\ndef test_tpg36x_unit_string():\n    \"\"\"Set a unit from a string.\"\"\"\n    with expected_protocol(\n        ik.pfeiffer.TPG36x,\n        [\"UNI,0\", SEP, \"UNI\", SEP, ENQ],\n        [ACK, SEP, ACK, SEP, \"0\", SEP],\n        sep=\"\",\n    ) as tpg:\n        tpg.unit = \"mbar\"\n        assert tpg.unit == tpg.Unit.MBAR\n"
  },
  {
    "path": "tests/test_phasematrix/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_phasematrix/test_phasematrix_fsw0020.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the Phasematrix FSW0020\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport pytest\n\nfrom instruments.units import ureg as u\n\nimport instruments as ik\nfrom tests import expected_protocol\n\n# TESTS #######################################################################\n\n\ndef test_reset():\n    with expected_protocol(ik.phasematrix.PhaseMatrixFSW0020, [\"0E.\"], []) as inst:\n        inst.reset()\n\n\ndef test_frequency():\n    with expected_protocol(\n        ik.phasematrix.PhaseMatrixFSW0020,\n        [\"04.\", f\"0C{int((10 * u.GHz).to(u.mHz).magnitude):012X}.\"],\n        [\"00E8D4A51000\"],\n    ) as inst:\n        assert inst.frequency == 1.0000000000000002 * u.GHz\n        inst.frequency = 10 * u.GHz\n\n\ndef test_power():\n    with expected_protocol(\n        ik.phasematrix.PhaseMatrixFSW0020,\n        [\"0D.\", f\"03{int(u.Quantity(10, u.dBm).to(u.cBm).magnitude):04X}.\"],\n        [\"-064\"],\n    ) as inst:\n        assert inst.power == u.Quantity(-10, u.dBm)\n        inst.power = u.Quantity(10, u.dBm)\n\n\ndef test_phase():\n    \"\"\"Raise NotImplementedError when phase is set / got.\"\"\"\n    with expected_protocol(ik.phasematrix.PhaseMatrixFSW0020, [], []) as inst:\n        with pytest.raises(NotImplementedError):\n            _ = inst.phase\n        with pytest.raises(NotImplementedError):\n            inst.phase = 42\n\n\ndef test_blanking():\n    with expected_protocol(\n        ik.phasematrix.PhaseMatrixFSW0020,\n        [f\"05{1:02X}.\", f\"05{0:02X}.\"],\n        [],\n    ) as inst:\n        inst.blanking = True\n        inst.blanking = False\n        with pytest.raises(NotImplementedError):\n            _ = inst.blanking\n\n\ndef test_ref_output():\n    with expected_protocol(\n        ik.phasematrix.PhaseMatrixFSW0020,\n        [f\"08{1:02X}.\", f\"08{0:02X}.\"],\n        [],\n    ) as inst:\n        inst.ref_output = True\n        inst.ref_output = False\n        with pytest.raises(NotImplementedError):\n            _ = inst.ref_output\n\n\ndef test_output():\n    with expected_protocol(\n        ik.phasematrix.PhaseMatrixFSW0020,\n        [f\"0F{1:02X}.\", f\"0F{0:02X}.\"],\n        [],\n    ) as inst:\n        inst.output = True\n        inst.output = False\n        with pytest.raises(NotImplementedError):\n            _ = inst.output\n\n\ndef test_pulse_modulation():\n    with expected_protocol(\n        ik.phasematrix.PhaseMatrixFSW0020,\n        [f\"09{1:02X}.\", f\"09{0:02X}.\"],\n        [],\n    ) as inst:\n        inst.pulse_modulation = True\n        inst.pulse_modulation = False\n        with pytest.raises(NotImplementedError):\n            _ = inst.pulse_modulation\n\n\ndef test_am_modulation():\n    with expected_protocol(\n        ik.phasematrix.PhaseMatrixFSW0020,\n        [f\"0A{1:02X}.\", f\"0A{0:02X}.\"],\n        [],\n    ) as inst:\n        inst.am_modulation = True\n        inst.am_modulation = False\n        with pytest.raises(NotImplementedError):\n            _ = inst.am_modulation\n"
  },
  {
    "path": "tests/test_picowatt/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_picowatt/test_picowatt_avs47.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the Picowatt AVS47\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nfrom instruments.units import ureg as u\n\nimport instruments as ik\nfrom tests import expected_protocol\n\n# TESTS #######################################################################\n\n\ndef test_sensor_is_sensor_class():\n    inst = ik.picowatt.PicowattAVS47.open_test()\n    assert isinstance(inst.sensor[0], inst.Sensor) is True\n\n\ndef test_init():\n    with expected_protocol(ik.picowatt.PicowattAVS47, [\"HDR 0\"], []):\n        pass\n\n\ndef test_sensor_resistance_same_channel():\n    with expected_protocol(\n        ik.picowatt.PicowattAVS47, [\"HDR 0\", \"MUX?\", \"ADC\", \"RES?\"], [\"0\", \"123\"]\n    ) as inst:\n        assert inst.sensor[0].resistance == 123 * u.ohm\n\n\ndef test_sensor_resistance_different_channel():\n    with expected_protocol(\n        ik.picowatt.PicowattAVS47,\n        [\"HDR 0\", \"MUX?\", \"INP 0\", \"MUX 0\", \"INP 1\", \"ADC\", \"RES?\"],\n        [\"1\", \"123\"],\n    ) as inst:\n        assert inst.sensor[0].resistance == 123 * u.ohm\n\n\ndef test_remote():\n    with expected_protocol(\n        ik.picowatt.PicowattAVS47,\n        [\"HDR 0\", \"REM?\", \"REM?\", \"REM 1\", \"REM 0\"],\n        [\"0\", \"1\"],\n    ) as inst:\n        assert inst.remote is False\n        assert inst.remote is True\n        inst.remote = True\n        inst.remote = False\n\n\ndef test_input_source():\n    with expected_protocol(\n        ik.picowatt.PicowattAVS47,\n        [\"HDR 0\", \"INP?\", \"INP 1\"],\n        [\n            \"0\",\n        ],\n    ) as inst:\n        assert inst.input_source == inst.InputSource.ground\n        inst.input_source = inst.InputSource.actual\n\n\ndef test_mux_channel():\n    with expected_protocol(\n        ik.picowatt.PicowattAVS47,\n        [\"HDR 0\", \"MUX?\", \"MUX 1\"],\n        [\n            \"3\",\n        ],\n    ) as inst:\n        assert inst.mux_channel == 3\n        inst.mux_channel = 1\n\n\ndef test_excitation():\n    with expected_protocol(\n        ik.picowatt.PicowattAVS47,\n        [\"HDR 0\", \"EXC?\", \"EXC 1\"],\n        [\n            \"3\",\n        ],\n    ) as inst:\n        assert inst.excitation == 3\n        inst.excitation = 1\n\n\ndef test_display():\n    with expected_protocol(\n        ik.picowatt.PicowattAVS47,\n        [\"HDR 0\", \"DIS?\", \"DIS 1\"],\n        [\n            \"3\",\n        ],\n    ) as inst:\n        assert inst.display == 3\n        inst.display = 1\n"
  },
  {
    "path": "tests/test_property_factories/__init__.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing common code for testing the property factories\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nfrom io import StringIO\n\n# CLASSES ####################################################################\n\n# pylint: disable=missing-docstring\n\n\nclass MockInstrument:\n    \"\"\"\n    Mock class that admits sendcmd/query but little else such that property\n    factories can be tested by deriving from the class.\n    \"\"\"\n\n    def __init__(self, responses=None):\n        self._buf = StringIO()\n        self._responses = responses if responses is not None else {}\n\n    @property\n    def value(self):\n        return self._buf.getvalue()\n\n    def sendcmd(self, cmd):\n        self._buf.write(f\"{cmd}\\n\")\n\n    def query(self, cmd):\n        self.sendcmd(cmd)\n        return self._responses[cmd.strip()]\n"
  },
  {
    "path": "tests/test_property_factories/test_bool_property.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the bool property factories\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport pytest\n\nfrom instruments.util_fns import bool_property\nfrom . import MockInstrument\n\n# TEST CASES #################################################################\n\n# pylint: disable=missing-docstring\n\n\ndef test_bool_property_basics():\n    class BoolMock(MockInstrument):\n        mock1 = bool_property(\"MOCK1\")\n        mock2 = bool_property(\"MOCK2\", inst_true=\"YES\", inst_false=\"NO\")\n\n    mock_inst = BoolMock({\"MOCK1?\": \"OFF\", \"MOCK2?\": \"YES\"})\n\n    assert mock_inst.mock1 is False\n    assert mock_inst.mock2 is True\n\n    mock_inst.mock1 = True\n    mock_inst.mock2 = False\n\n    assert mock_inst.value == \"MOCK1?\\nMOCK2?\\nMOCK1 ON\\nMOCK2 NO\\n\"\n\n\ndef test_bool_property_set_fmt():\n    class BoolMock(MockInstrument):\n        mock1 = bool_property(\"MOCK1\", set_fmt=\"{}={}\")\n\n    mock_instrument = BoolMock({\"MOCK1?\": \"OFF\"})\n\n    mock_instrument.mock1 = True\n\n    assert mock_instrument.value == \"MOCK1=ON\\n\"\n\n\ndef test_bool_property_readonly_writing_fails():\n    with pytest.raises(AttributeError):\n\n        class BoolMock(MockInstrument):\n            mock1 = bool_property(\"MOCK1\", readonly=True)\n\n        mock_instrument = BoolMock({\"MOCK1?\": \"OFF\"})\n\n        mock_instrument.mock1 = True\n\n\ndef test_bool_property_readonly_reading_passes():\n    class BoolMock(MockInstrument):\n        mock1 = bool_property(\"MOCK1\", readonly=True)\n\n    mock_instrument = BoolMock({\"MOCK1?\": \"OFF\"})\n\n    assert mock_instrument.mock1 is False\n\n\ndef test_bool_property_writeonly_reading_fails():\n    with pytest.raises(AttributeError):\n\n        class BoolMock(MockInstrument):\n            mock1 = bool_property(\"MOCK1\", writeonly=True)\n\n        mock_instrument = BoolMock({\"MOCK1?\": \"OFF\"})\n\n        _ = mock_instrument.mock1\n\n\ndef test_bool_property_writeonly_writing_passes():\n    class BoolMock(MockInstrument):\n        mock1 = bool_property(\"MOCK1\", writeonly=True)\n\n    mock_instrument = BoolMock({\"MOCK1?\": \"OFF\"})\n\n    mock_instrument.mock1 = False\n\n\ndef test_bool_property_set_cmd():\n    class BoolMock(MockInstrument):\n        mock1 = bool_property(\"MOCK1\", set_cmd=\"FOOBAR\")\n\n    mock_inst = BoolMock({\"MOCK1?\": \"OFF\"})\n\n    assert mock_inst.mock1 is False\n    mock_inst.mock1 = True\n\n    assert mock_inst.value == \"MOCK1?\\nFOOBAR ON\\n\"\n"
  },
  {
    "path": "tests/test_property_factories/test_bounded_unitful_property.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the bounded unitful property factories\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport pytest\nfrom instruments.units import ureg as u\n\nfrom instruments.util_fns import bounded_unitful_property\nfrom . import MockInstrument\nfrom .. import mock\n\n# TEST CASES #################################################################\n\n# pylint: disable=missing-docstring\n\n\ndef test_bounded_unitful_property_basics():\n    class BoundedUnitfulMock(MockInstrument):\n        property, property_min, property_max = bounded_unitful_property(\n            \"MOCK\", units=u.hertz\n        )\n\n    mock_inst = BoundedUnitfulMock(\n        {\"MOCK?\": \"1000\", \"MOCK:MIN?\": \"10\", \"MOCK:MAX?\": \"9999\"}\n    )\n\n    assert mock_inst.property == 1000 * u.hertz\n    assert mock_inst.property_min == 10 * u.hertz\n    assert mock_inst.property_max == 9999 * u.hertz\n\n    mock_inst.property = 1000 * u.hertz\n\n\ndef test_bounded_unitful_property_set_outside_max():\n    with pytest.raises(ValueError):\n\n        class BoundedUnitfulMock(MockInstrument):\n            property, property_min, property_max = bounded_unitful_property(\n                \"MOCK\", units=u.hertz\n            )\n\n        mock_inst = BoundedUnitfulMock(\n            {\"MOCK?\": \"1000\", \"MOCK:MIN?\": \"10\", \"MOCK:MAX?\": \"9999\"}\n        )\n\n        mock_inst.property = 10000 * u.hertz  # Should raise ValueError\n\n\ndef test_bounded_unitful_property_set_outside_min():\n    with pytest.raises(ValueError):\n\n        class BoundedUnitfulMock(MockInstrument):\n            property, property_min, property_max = bounded_unitful_property(\n                \"MOCK\", units=u.hertz\n            )\n\n        mock_inst = BoundedUnitfulMock(\n            {\"MOCK?\": \"1000\", \"MOCK:MIN?\": \"10\", \"MOCK:MAX?\": \"9999\"}\n        )\n\n        mock_inst.property = 1 * u.hertz  # Should raise ValueError\n\n\ndef test_bounded_unitful_property_min_fmt_str():\n    class BoundedUnitfulMock(MockInstrument):\n        property, property_min, property_max = bounded_unitful_property(\n            \"MOCK\", units=u.hertz, min_fmt_str=\"{} MIN?\"\n        )\n\n    mock_inst = BoundedUnitfulMock({\"MOCK MIN?\": \"10\"})\n\n    assert mock_inst.property_min == 10 * u.Hz\n    assert mock_inst.value == \"MOCK MIN?\\n\"\n\n\ndef test_bounded_unitful_property_max_fmt_str():\n    class BoundedUnitfulMock(MockInstrument):\n        property, property_min, property_max = bounded_unitful_property(\n            \"MOCK\", units=u.hertz, max_fmt_str=\"{} MAX?\"\n        )\n\n    mock_inst = BoundedUnitfulMock({\"MOCK MAX?\": \"9999\"})\n\n    assert mock_inst.property_max == 9999 * u.Hz\n    assert mock_inst.value == \"MOCK MAX?\\n\"\n\n\ndef test_bounded_unitful_property_static_range():\n    class BoundedUnitfulMock(MockInstrument):\n        property, property_min, property_max = bounded_unitful_property(\n            \"MOCK\", units=u.hertz, valid_range=(10, 9999)\n        )\n\n    mock_inst = BoundedUnitfulMock()\n\n    assert mock_inst.property_min == 10 * u.Hz\n    assert mock_inst.property_max == 9999 * u.Hz\n\n\ndef test_bounded_unitful_property_static_range_with_units():\n    class BoundedUnitfulMock(MockInstrument):\n        property, property_min, property_max = bounded_unitful_property(\n            \"MOCK\", units=u.hertz, valid_range=(10 * u.kilohertz, 9999 * u.kilohertz)\n        )\n\n    mock_inst = BoundedUnitfulMock()\n\n    assert mock_inst.property_min == 10 * 1000 * u.Hz\n    assert mock_inst.property_max == 9999 * 1000 * u.Hz\n\n\n@mock.patch(\"instruments.util_fns.unitful_property\")\ndef test_bounded_unitful_property_passes_kwargs(mock_unitful_property):\n    bounded_unitful_property(command=\"MOCK\", units=u.Hz, derp=\"foobar\")\n    mock_unitful_property.assert_called_with(\n        \"MOCK\", u.Hz, derp=\"foobar\", valid_range=(mock.ANY, mock.ANY)\n    )\n\n\n@mock.patch(\"instruments.util_fns.unitful_property\")\ndef test_bounded_unitful_property_valid_range_none(mock_unitful_property):\n    bounded_unitful_property(command=\"MOCK\", units=u.Hz, valid_range=(None, None))\n    mock_unitful_property.assert_called_with(\"MOCK\", u.Hz, valid_range=(None, None))\n\n\ndef test_bounded_unitful_property_returns_none():\n    class BoundedUnitfulMock(MockInstrument):\n        property, property_min, property_max = bounded_unitful_property(\n            \"MOCK\", units=u.hertz, valid_range=(None, None)\n        )\n\n    mock_inst = BoundedUnitfulMock()\n\n    assert mock_inst.property_min is None\n    assert mock_inst.property_max is None\n"
  },
  {
    "path": "tests/test_property_factories/test_enum_property.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the enum property factories\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nfrom enum import Enum, IntEnum\nimport pytest\n\nfrom instruments.util_fns import enum_property\nfrom . import MockInstrument\n\n# TEST CASES #################################################################\n\n# pylint: disable=missing-docstring\n\n\ndef test_enum_property():\n    class SillyEnum(Enum):\n        a = \"aa\"\n        b = \"bb\"\n\n    class EnumMock(MockInstrument):\n        a = enum_property(\"MOCK:A\", SillyEnum)\n        b = enum_property(\"MOCK:B\", SillyEnum)\n\n    mock_inst = EnumMock({\"MOCK:A?\": \"aa\", \"MOCK:B?\": \"bb\"})\n\n    assert mock_inst.a == SillyEnum.a\n    assert mock_inst.b == SillyEnum.b\n\n    # Test EnumValues, string values and string names.\n    mock_inst.a = SillyEnum.b\n    mock_inst.b = \"a\"\n    mock_inst.b = \"bb\"\n\n    assert mock_inst.value == \"MOCK:A?\\nMOCK:B?\\nMOCK:A bb\\nMOCK:B aa\\nMOCK:B bb\\n\"\n\n\ndef test_enum_property_invalid():\n    with pytest.raises(ValueError):\n\n        class SillyEnum(Enum):\n            a = \"aa\"\n            b = \"bb\"\n\n        class EnumMock(MockInstrument):\n            a = enum_property(\"MOCK:A\", SillyEnum)\n\n        mock_inst = EnumMock({\"MOCK:A?\": \"aa\", \"MOCK:B?\": \"bb\"})\n\n        mock_inst.a = \"c\"\n\n\ndef test_enum_property_set_fmt():\n    class SillyEnum(Enum):\n        a = \"aa\"\n\n    class EnumMock(MockInstrument):\n        a = enum_property(\"MOCK:A\", SillyEnum, set_fmt=\"{}={}\")\n\n    mock_instrument = EnumMock()\n\n    mock_instrument.a = \"aa\"\n    assert mock_instrument.value == \"MOCK:A=aa\\n\"\n\n\ndef test_enum_property_input_decoration():\n    class SillyEnum(Enum):\n        a = \"aa\"\n\n    class EnumMock(MockInstrument):\n        @staticmethod\n        def _input_decorator(_):\n            return \"aa\"\n\n        a = enum_property(\"MOCK:A\", SillyEnum, input_decoration=_input_decorator)\n\n    mock_instrument = EnumMock({\"MOCK:A?\": \"garbage\"})\n\n    assert mock_instrument.a == SillyEnum.a\n\n\ndef test_enum_property_input_decoration_not_a_function():\n    class SillyEnum(IntEnum):\n        a = 1\n\n    class EnumMock(MockInstrument):\n        a = enum_property(\"MOCK:A\", SillyEnum, input_decoration=int)\n\n    mock_instrument = EnumMock({\"MOCK:A?\": \"1\"})\n\n    assert mock_instrument.a == SillyEnum.a\n\n\ndef test_enum_property_output_decoration():\n    class SillyEnum(Enum):\n        a = \"aa\"\n\n    class EnumMock(MockInstrument):\n        @staticmethod\n        def _output_decorator(_):\n            return \"foobar\"\n\n        a = enum_property(\"MOCK:A\", SillyEnum, output_decoration=_output_decorator)\n\n    mock_instrument = EnumMock()\n\n    mock_instrument.a = SillyEnum.a\n\n    assert mock_instrument.value == \"MOCK:A foobar\\n\"\n\n\ndef test_enum_property_output_decoration_not_a_function():\n    class SillyEnum(Enum):\n        a = \".23\"\n\n    class EnumMock(MockInstrument):\n        a = enum_property(\"MOCK:A\", SillyEnum, output_decoration=float)\n\n    mock_instrument = EnumMock()\n\n    mock_instrument.a = SillyEnum.a\n\n    assert mock_instrument.value == \"MOCK:A 0.23\\n\"\n\n\ndef test_enum_property_writeonly_reading_fails():\n    with pytest.raises(AttributeError):\n\n        class SillyEnum(Enum):\n            a = \"aa\"\n\n        class EnumMock(MockInstrument):\n            a = enum_property(\"MOCK:A\", SillyEnum, writeonly=True)\n\n        mock_instrument = EnumMock()\n\n        _ = mock_instrument.a\n\n\ndef test_enum_property_writeonly_writing_passes():\n    class SillyEnum(Enum):\n        a = \"aa\"\n\n    class EnumMock(MockInstrument):\n        a = enum_property(\"MOCK:A\", SillyEnum, writeonly=True)\n\n    mock_instrument = EnumMock()\n\n    mock_instrument.a = SillyEnum.a\n    assert mock_instrument.value == \"MOCK:A aa\\n\"\n\n\ndef test_enum_property_readonly_writing_fails():\n    with pytest.raises(AttributeError):\n\n        class SillyEnum(Enum):\n            a = \"aa\"\n\n        class EnumMock(MockInstrument):\n            a = enum_property(\"MOCK:A\", SillyEnum, readonly=True)\n\n        mock_instrument = EnumMock({\"MOCK:A?\": \"aa\"})\n\n        mock_instrument.a = SillyEnum.a\n\n\ndef test_enum_property_readonly_reading_passes():\n    class SillyEnum(Enum):\n        a = \"aa\"\n\n    class EnumMock(MockInstrument):\n        a = enum_property(\"MOCK:A\", SillyEnum, readonly=True)\n\n    mock_instrument = EnumMock({\"MOCK:A?\": \"aa\"})\n\n    assert mock_instrument.a == SillyEnum.a\n    assert mock_instrument.value == \"MOCK:A?\\n\"\n\n\ndef test_enum_property_set_cmd():\n    class SillyEnum(Enum):\n        a = \"aa\"\n\n    class EnumMock(MockInstrument):\n        a = enum_property(\"MOCK:A\", SillyEnum, set_cmd=\"FOOBAR:A\")\n\n    mock_inst = EnumMock({\"MOCK:A?\": \"aa\"})\n\n    assert mock_inst.a == SillyEnum.a\n    mock_inst.a = SillyEnum.a\n\n    assert mock_inst.value == \"MOCK:A?\\nFOOBAR:A aa\\n\"\n"
  },
  {
    "path": "tests/test_property_factories/test_int_property.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the int property factories\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport pytest\n\nfrom instruments.util_fns import int_property\nfrom . import MockInstrument\n\n# TEST CASES #################################################################\n\n# pylint: disable=missing-docstring\n\n\ndef test_int_property_outside_valid_set():\n    with pytest.raises(ValueError):\n\n        class IntMock(MockInstrument):\n            mock_property = int_property(\"MOCK\", valid_set={1, 2})\n\n        mock_inst = IntMock()\n        mock_inst.mock_property = 3\n\n\ndef test_int_property_valid_set():\n    class IntMock(MockInstrument):\n        int_property = int_property(\"MOCK\", valid_set={1, 2})\n\n    mock_inst = IntMock({\"MOCK?\": \"1\"})\n\n    assert mock_inst.int_property == 1\n\n    mock_inst.int_property = 2\n    assert mock_inst.value == \"MOCK?\\nMOCK 2\\n\"\n\n\ndef test_int_property_no_set():\n    class IntMock(MockInstrument):\n        int_property = int_property(\"MOCK\")\n\n    mock_inst = IntMock()\n\n    mock_inst.int_property = 1\n\n    assert mock_inst.value == \"MOCK 1\\n\"\n\n\ndef test_int_property_writeonly_reading_fails():\n    with pytest.raises(AttributeError):\n\n        class IntMock(MockInstrument):\n            int_property = int_property(\"MOCK\", writeonly=True)\n\n        mock_inst = IntMock()\n\n        _ = mock_inst.int_property\n\n\ndef test_int_property_writeonly_writing_passes():\n    class IntMock(MockInstrument):\n        int_property = int_property(\"MOCK\", writeonly=True)\n\n    mock_inst = IntMock()\n\n    mock_inst.int_property = 1\n    assert mock_inst.value == f\"MOCK {1:d}\\n\"\n\n\ndef test_int_property_readonly_writing_fails():\n    with pytest.raises(AttributeError):\n\n        class IntMock(MockInstrument):\n            int_property = int_property(\"MOCK\", readonly=True)\n\n        mock_inst = IntMock({\"MOCK?\": \"1\"})\n\n        mock_inst.int_property = 1\n\n\ndef test_int_property_readonly_reading_passes():\n    class IntMock(MockInstrument):\n        int_property = int_property(\"MOCK\", readonly=True)\n\n    mock_inst = IntMock({\"MOCK?\": \"1\"})\n\n    assert mock_inst.int_property == 1\n\n\ndef test_int_property_format_code():\n    class IntMock(MockInstrument):\n        int_property = int_property(\"MOCK\", format_code=\"{:e}\")\n\n    mock_inst = IntMock()\n\n    mock_inst.int_property = 1\n    assert mock_inst.value == f\"MOCK {1:e}\\n\"\n\n\ndef test_int_property_set_cmd():\n    class IntMock(MockInstrument):\n        int_property = int_property(\"MOCK\", set_cmd=\"FOOBAR\")\n\n    mock_inst = IntMock({\"MOCK?\": \"1\"})\n\n    assert mock_inst.int_property == 1\n    mock_inst.int_property = 1\n\n    assert mock_inst.value == \"MOCK?\\nFOOBAR 1\\n\"\n"
  },
  {
    "path": "tests/test_property_factories/test_rproperty.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the property factories\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport pytest\n\nfrom instruments.util_fns import rproperty\nfrom . import MockInstrument\n\n# TEST CASES #################################################################\n\n# pylint: disable=missing-docstring\n\n\ndef test_rproperty_basic():\n    class Mock(MockInstrument):\n        def __init__(self):\n            super().__init__()\n            self._value = 0\n\n        def mockget(self):\n            return self._value\n\n        def mockset(self, newval):\n            self._value = newval\n\n        mockproperty = rproperty(fget=mockget, fset=mockset)\n\n    mock_inst = Mock()\n    mock_inst.mockproperty = 1\n    assert mock_inst.mockproperty == 1\n\n\ndef test_rproperty_readonly_writing_fails():\n    with pytest.raises(AttributeError):\n\n        class Mock(MockInstrument):\n            def __init__(self):\n                super().__init__()\n                self._value = 0\n\n            def mockset(self, newval):  # pragma: no cover\n                self._value = newval\n\n            mockproperty = rproperty(fget=None, fset=mockset, readonly=True)\n\n        mock_inst = Mock()\n        mock_inst.mockproperty = 1\n\n\ndef test_rproperty_readonly_reading_passes():\n    class Mock(MockInstrument):\n        def __init__(self):\n            super().__init__()\n            self._value = 0\n\n        def mockget(self):\n            return self._value\n\n        mockproperty = rproperty(fget=mockget, fset=None, readonly=True)\n\n    mock_inst = Mock()\n    assert mock_inst.mockproperty == 0\n\n\ndef test_rproperty_writeonly_reading_fails():\n    with pytest.raises(AttributeError):\n\n        class Mock(MockInstrument):\n            def __init__(self):\n                super().__init__()\n                self._value = 0\n\n            def mockget(self):  # pragma: no cover\n                return self._value\n\n            mockproperty = rproperty(fget=mockget, fset=None, writeonly=True)\n\n        mock_inst = Mock()\n        assert mock_inst.mockproperty == 0\n\n\ndef test_rproperty_writeonly_writing_passes():\n    class Mock(MockInstrument):\n        def __init__(self):\n            super().__init__()\n            self._value = 0\n\n        def mockset(self, newval):\n            self._value = newval\n\n        mockproperty = rproperty(fget=None, fset=mockset, writeonly=True)\n\n    mock_inst = Mock()\n    mock_inst.mockproperty = 1\n\n\ndef test_rproperty_readonly_and_writeonly():\n    with pytest.raises(ValueError):\n        _ = rproperty(readonly=True, writeonly=True)\n"
  },
  {
    "path": "tests/test_property_factories/test_string_property.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the string property factories\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nfrom instruments.util_fns import string_property\nfrom . import MockInstrument\n\n# TEST CASES #################################################################\n\n# pylint: disable=missing-docstring\n\n\ndef test_string_property_basics():\n    class StringMock(MockInstrument):\n        mock_property = string_property(\"MOCK\")\n\n    mock_inst = StringMock({\"MOCK?\": '\"foobar\"'})\n\n    assert mock_inst.mock_property == \"foobar\"\n\n    mock_inst.mock_property = \"foo\"\n    assert mock_inst.value == 'MOCK?\\nMOCK \"foo\"\\n'\n\n\ndef test_string_property_different_bookmark_symbol():\n    class StringMock(MockInstrument):\n        mock_property = string_property(\"MOCK\", bookmark_symbol=\"%^\")\n\n    mock_inst = StringMock({\"MOCK?\": \"%^foobar%^\"})\n\n    assert mock_inst.mock_property == \"foobar\"\n\n    mock_inst.mock_property = \"foo\"\n    assert mock_inst.value == \"MOCK?\\nMOCK %^foo%^\\n\"\n\n\ndef test_string_property_no_bookmark_symbol():\n    class StringMock(MockInstrument):\n        mock_property = string_property(\"MOCK\", bookmark_symbol=\"\")\n\n    mock_inst = StringMock({\"MOCK?\": \"foobar\"})\n\n    assert mock_inst.mock_property == \"foobar\"\n\n    mock_inst.mock_property = \"foo\"\n    assert mock_inst.value == \"MOCK?\\nMOCK foo\\n\"\n\n\ndef test_string_property_set_cmd():\n    class StringMock(MockInstrument):\n        mock_property = string_property(\"MOCK\", set_cmd=\"FOOBAR\")\n\n    mock_inst = StringMock({\"MOCK?\": '\"derp\"'})\n\n    assert mock_inst.mock_property == \"derp\"\n\n    mock_inst.mock_property = \"qwerty\"\n    assert mock_inst.value == 'MOCK?\\nFOOBAR \"qwerty\"\\n'\n"
  },
  {
    "path": "tests/test_property_factories/test_unitful_property.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the unitful property factories\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport pytest\nimport pint\n\nfrom instruments.util_fns import unitful_property\nfrom instruments.units import ureg as u\nfrom . import MockInstrument\n\n# TEST CASES #################################################################\n\n# pylint: disable=missing-docstring,no-self-use\n\n\ndef test_unitful_property_basics():\n    class UnitfulMock(MockInstrument):\n        unitful_property = unitful_property(\"MOCK\", units=u.hertz)\n\n    mock_inst = UnitfulMock({\"MOCK?\": \"1000\"})\n\n    assert mock_inst.unitful_property == 1000 * u.hertz\n\n    mock_inst.unitful_property = 1000 * u.hertz\n    assert mock_inst.value == f\"MOCK?\\nMOCK {1000:e}\\n\"\n\n\ndef test_unitful_property_format_code():\n    class UnitfulMock(MockInstrument):\n        unitful_property = unitful_property(\"MOCK\", u.hertz, format_code=\"{:f}\")\n\n    mock_inst = UnitfulMock()\n\n    mock_inst.unitful_property = 1000 * u.hertz\n    assert mock_inst.value == f\"MOCK {1000:f}\\n\"\n\n\ndef test_unitful_property_rescale_units():\n    class UnitfulMock(MockInstrument):\n        unitful_property = unitful_property(\"MOCK\", u.hertz)\n\n    mock_inst = UnitfulMock()\n\n    mock_inst.unitful_property = 1 * u.kilohertz\n    assert mock_inst.value == f\"MOCK {1000:e}\\n\"\n\n\ndef test_unitful_property_no_units_on_set():\n    class UnitfulMock(MockInstrument):\n        unitful_property = unitful_property(\"MOCK\", u.hertz)\n\n    mock_inst = UnitfulMock()\n\n    mock_inst.unitful_property = 1000\n    assert mock_inst.value == f\"MOCK {1000:e}\\n\"\n\n\ndef test_unitful_property_wrong_units():\n    with pytest.raises(pint.errors.DimensionalityError):\n\n        class UnitfulMock(MockInstrument):\n            unitful_property = unitful_property(\"MOCK\", u.hertz)\n\n        mock_inst = UnitfulMock()\n\n        mock_inst.unitful_property = 1 * u.volt\n\n\ndef test_unitful_property_writeonly_reading_fails():\n    with pytest.raises(AttributeError):\n\n        class UnitfulMock(MockInstrument):\n            unitful_property = unitful_property(\"MOCK\", u.hertz, writeonly=True)\n\n        mock_inst = UnitfulMock()\n\n        _ = mock_inst.unitful_property\n\n\ndef test_unitful_property_writeonly_writing_passes():\n    class UnitfulMock(MockInstrument):\n        unitful_property = unitful_property(\"MOCK\", u.hertz, writeonly=True)\n\n    mock_inst = UnitfulMock()\n\n    mock_inst.unitful_property = 1 * u.hertz\n    assert mock_inst.value == f\"MOCK {1:e}\\n\"\n\n\ndef test_unitful_property_readonly_writing_fails():\n    with pytest.raises(AttributeError):\n\n        class UnitfulMock(MockInstrument):\n            unitful_property = unitful_property(\"MOCK\", u.hertz, readonly=True)\n\n        mock_inst = UnitfulMock({\"MOCK?\": \"1\"})\n\n        mock_inst.unitful_property = 1 * u.hertz\n\n\ndef test_unitful_property_readonly_reading_passes():\n    class UnitfulMock(MockInstrument):\n        unitful_property = unitful_property(\"MOCK\", u.hertz, readonly=True)\n\n    mock_inst = UnitfulMock({\"MOCK?\": \"1\"})\n\n    assert mock_inst.unitful_property == 1 * u.hertz\n\n\ndef test_unitful_property_valid_range():\n    class UnitfulMock(MockInstrument):\n        unitful_property = unitful_property(\"MOCK\", u.hertz, valid_range=(0, 10))\n\n    mock_inst = UnitfulMock()\n\n    mock_inst.unitful_property = 0\n    mock_inst.unitful_property = 10\n\n    assert mock_inst.value == f\"MOCK {0:e}\\nMOCK {10:e}\\n\"\n\n\ndef test_unitful_property_valid_range_functions():\n    class UnitfulMock(MockInstrument):\n        def min_value(self):\n            return 0 * u.Hz\n\n        def max_value(self):\n            return 10 * u.Hz\n\n        unitful_property = unitful_property(\n            \"MOCK\", u.hertz, valid_range=(min_value, max_value)\n        )\n\n    mock_inst = UnitfulMock()\n\n    mock_inst.unitful_property = 0\n    mock_inst.unitful_property = 10\n\n    assert mock_inst.value == f\"MOCK {0:e}\\nMOCK {10:e}\\n\"\n\n\ndef test_unitful_property_minimum_value():\n    with pytest.raises(ValueError):\n\n        class UnitfulMock(MockInstrument):\n            unitful_property = unitful_property(\"MOCK\", u.hertz, valid_range=(0, 10))\n\n        mock_inst = UnitfulMock()\n\n        mock_inst.unitful_property = -1\n\n\ndef test_unitful_property_maximum_value():\n    with pytest.raises(ValueError):\n\n        class UnitfulMock(MockInstrument):\n            unitful_property = unitful_property(\"MOCK\", u.hertz, valid_range=(0, 10))\n\n        mock_inst = UnitfulMock()\n\n        mock_inst.unitful_property = 11\n\n\ndef test_unitful_property_input_decoration():\n    class UnitfulMock(MockInstrument):\n        @staticmethod\n        def _input_decorator(_):\n            return \"1\"\n\n        a = unitful_property(\"MOCK:A\", u.hertz, input_decoration=_input_decorator)\n\n    mock_instrument = UnitfulMock({\"MOCK:A?\": \"garbage\"})\n\n    assert mock_instrument.a == 1 * u.Hz\n\n\ndef test_unitful_property_input_decoration_not_a_function():\n    class UnitfulMock(MockInstrument):\n        a = unitful_property(\"MOCK:A\", u.hertz, input_decoration=float)\n\n    mock_instrument = UnitfulMock({\"MOCK:A?\": \".123\"})\n\n    assert mock_instrument.a == 0.123 * u.Hz\n\n\ndef test_unitful_property_output_decoration():\n    class UnitfulMock(MockInstrument):\n        @staticmethod\n        def _output_decorator(_):\n            return \"1\"\n\n        a = unitful_property(\"MOCK:A\", u.hertz, output_decoration=_output_decorator)\n\n    mock_instrument = UnitfulMock()\n\n    mock_instrument.a = 345 * u.hertz\n\n    assert mock_instrument.value == \"MOCK:A 1\\n\"\n\n\ndef test_unitful_property_output_decoration_not_a_function():\n    class UnitfulMock(MockInstrument):\n        a = unitful_property(\"MOCK:A\", u.hertz, output_decoration=bool)\n\n    mock_instrument = UnitfulMock()\n\n    mock_instrument.a = 1 * u.hertz\n\n    assert mock_instrument.value == \"MOCK:A True\\n\"\n\n\ndef test_unitful_property_split_str():\n    class UnitfulMock(MockInstrument):\n        unitful_property = unitful_property(\"MOCK\", u.hertz, valid_range=(0, 10))\n\n    mock_inst = UnitfulMock({\"MOCK?\": \"1 kHz\"})\n\n    value = mock_inst.unitful_property\n    assert value.magnitude == 1000\n    assert value.units == u.hertz\n\n\ndef test_unitful_property_name_read_not_none():\n    class UnitfulMock(MockInstrument):\n        a = unitful_property(\"MOCK\", units=u.hertz, set_cmd=\"FOOBAR\")\n\n    mock_inst = UnitfulMock({\"MOCK?\": \"1000\"})\n    assert mock_inst.a == 1000 * u.hertz\n    mock_inst.a = 1000 * u.hertz\n\n    assert mock_inst.value == f\"MOCK?\\nFOOBAR {1000:e}\\n\"\n"
  },
  {
    "path": "tests/test_property_factories/test_unitless_property.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the unitless property factory\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport pytest\nfrom instruments.units import ureg as u\n\nfrom instruments.util_fns import unitless_property\nfrom . import MockInstrument\n\n# TEST CASES #################################################################\n\n# pylint: disable=missing-docstring\n\n\ndef test_unitless_property_basics():\n    class UnitlessMock(MockInstrument):\n        mock_property = unitless_property(\"MOCK\")\n\n    mock_inst = UnitlessMock({\"MOCK?\": \"1\"})\n\n    assert mock_inst.mock_property == 1\n\n    mock_inst.mock_property = 1\n    assert mock_inst.value == f\"MOCK?\\nMOCK {1:e}\\n\"\n\n\ndef test_unitless_property_units():\n    with pytest.raises(ValueError):\n\n        class UnitlessMock(MockInstrument):\n            mock_property = unitless_property(\"MOCK\")\n\n        mock_inst = UnitlessMock({\"MOCK?\": \"1\"})\n\n        mock_inst.mock_property = 1 * u.volt\n\n\ndef test_unitless_property_format_code():\n    class UnitlessMock(MockInstrument):\n        mock_property = unitless_property(\"MOCK\", format_code=\"{:f}\")\n\n    mock_inst = UnitlessMock()\n\n    mock_inst.mock_property = 1\n    assert mock_inst.value == f\"MOCK {1:f}\\n\"\n\n\ndef test_unitless_property_writeonly_reading_fails():\n    with pytest.raises(AttributeError):\n\n        class UnitlessMock(MockInstrument):\n            mock_property = unitless_property(\"MOCK\", writeonly=True)\n\n        mock_inst = UnitlessMock()\n\n        _ = mock_inst.mock_property\n\n\ndef test_unitless_property_writeonly_writing_passes():\n    class UnitlessMock(MockInstrument):\n        mock_property = unitless_property(\"MOCK\", writeonly=True)\n\n    mock_inst = UnitlessMock()\n\n    mock_inst.mock_property = 1\n    assert mock_inst.value == f\"MOCK {1:e}\\n\"\n\n\ndef test_unitless_property_readonly_writing_fails():\n    with pytest.raises(AttributeError):\n\n        class UnitlessMock(MockInstrument):\n            mock_property = unitless_property(\"MOCK\", readonly=True)\n\n        mock_inst = UnitlessMock({\"MOCK?\": \"1\"})\n\n        mock_inst.mock_property = 1\n\n\ndef test_unitless_property_readonly_reading_passes():\n    class UnitlessMock(MockInstrument):\n        mock_property = unitless_property(\"MOCK\", readonly=True)\n\n    mock_inst = UnitlessMock({\"MOCK?\": \"1\"})\n\n    assert mock_inst.mock_property == 1\n\n\ndef test_unitless_property_set_cmd():\n    class UnitlessMock(MockInstrument):\n        mock_property = unitless_property(\"MOCK\", set_cmd=\"FOOBAR\")\n\n    mock_inst = UnitlessMock({\"MOCK?\": \"1\"})\n\n    assert mock_inst.mock_property == 1\n    mock_inst.mock_property = 1\n\n    assert mock_inst.value == f\"MOCK?\\nFOOBAR {1:e}\\n\"\n"
  },
  {
    "path": "tests/test_qubitekk/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_qubitekk/test_qubitekk_cc1.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the Qubitekk CC1\n\"\"\"\n\n# IMPORTS ####################################################################\n\nfrom io import BytesIO\nimport pytest\nfrom instruments.units import ureg as u\n\nimport instruments as ik\nfrom tests import expected_protocol, unit_eq\n\n# TESTS ######################################################################\n\n\ndef test_init_os_error(mocker):\n    \"\"\"Initialize with acknowledgements already turned off.\n\n    This raises an OSError in the read which must pass without an issue.\n    \"\"\"\n    stdout = BytesIO(b\":ACKN OF\\nFIRM?\\n\")\n    stdin = BytesIO(b\"Firmware v2.010\\n\")\n    mock_read = mocker.patch.object(ik.qubitekk.CC1, \"read\")\n    mock_read.side_effect = OSError\n    _ = ik.qubitekk.CC1.open_test(stdin, stdout)\n    mock_read.assert_called_with(-1)\n\n\ndef test_cc1_count():\n    with expected_protocol(\n        ik.qubitekk.CC1,\n        [\":ACKN OF\", \"FIRM?\", \"COUN:C1?\"],\n        [\"\", \"Firmware v2.010\", \"20\"],\n        sep=\"\\n\",\n    ) as cc:\n        assert cc.channel[0].count == 20.0\n\n\ndef test_cc1_count_valule_error():\n    with expected_protocol(\n        ik.qubitekk.CC1,\n        [\":ACKN OF\", \"FIRM?\", \"COUN:C1?\"],\n        [\"\", \"Firmware v2.010\", \"bad_count\", \"try1\" \"try2\" \"try3\" \"try4\" \"try5\"],\n        sep=\"\\n\",\n    ) as cc:\n        with pytest.raises(IOError) as err_info:\n            _ = cc.channel[0].count\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"Could not read the count of channel C1.\"\n\n\ndef test_cc1_window():\n    with expected_protocol(\n        ik.qubitekk.CC1,\n        [\":ACKN OF\", \"FIRM?\", \"WIND?\", \":WIND 7\"],\n        [\n            \"\",\n            \"Firmware v2.010\",\n            \"2\",\n        ],\n        sep=\"\\n\",\n    ) as cc:\n        unit_eq(cc.window, u.Quantity(2, \"ns\"))\n        cc.window = 7\n\n\ndef test_cc1_window_error():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.qubitekk.CC1,\n        [\":ACKN OF\", \"FIRM?\", \":WIND 10\"],\n        [\"\", \"Firmware v2.010\"],\n        sep=\"\\n\",\n    ) as cc:\n        cc.window = 10\n\n\ndef test_cc1_delay():\n    with expected_protocol(\n        ik.qubitekk.CC1,\n        [\":ACKN OF\", \"FIRM?\", \"DELA?\", \":DELA 2\"],\n        [\"\", \"Firmware v2.010\", \"8\", \"\"],\n        sep=\"\\n\",\n    ) as cc:\n        unit_eq(cc.delay, u.Quantity(8, \"ns\"))\n        cc.delay = 2\n\n\ndef test_cc1_delay_error1():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.qubitekk.CC1,\n        [\":ACKN OF\", \"FIRM?\", \":DELA -1\"],\n        [\"\", \"Firmware v2.010\"],\n        sep=\"\\n\",\n    ) as cc:\n        cc.delay = -1\n\n\ndef test_cc1_delay_error2():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.qubitekk.CC1,\n        [\":ACKN OF\", \"FIRM?\", \":DELA 1\"],\n        [\"\", \"Firmware v2.010\"],\n        sep=\"\\n\",\n    ) as cc:\n        cc.delay = 1\n\n\ndef test_cc1_dwell_old_firmware():\n    with expected_protocol(\n        ik.qubitekk.CC1,\n        [\":ACKN OF\", \"FIRM?\", \"DWEL?\", \":DWEL 2\"],\n        [\"Unknown Command\", \"Firmware v2.001\", \"8000\", \"\"],\n        sep=\"\\n\",\n    ) as cc:\n        unit_eq(cc.dwell_time, u.Quantity(8, \"s\"))\n        cc.dwell_time = 2\n\n\ndef test_cc1_dwell_new_firmware():\n    with expected_protocol(\n        ik.qubitekk.CC1,\n        [\":ACKN OF\", \"FIRM?\", \"DWEL?\", \":DWEL 2\"],\n        [\"\", \"Firmware v2.010\", \"8\"],\n        sep=\"\\n\",\n    ) as cc:\n        unit_eq(cc.dwell_time, u.Quantity(8, \"s\"))\n        cc.dwell_time = 2\n\n\ndef test_cc1_dwell_time_error():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.qubitekk.CC1,\n        [\":ACKN OF\", \"FIRM?\", \":DWEL -1\"],\n        [\"\", \"Firmware v2.010\"],\n        sep=\"\\n\",\n    ) as cc:\n        cc.dwell_time = -1\n\n\ndef test_cc1_firmware():\n    with expected_protocol(\n        ik.qubitekk.CC1, [\":ACKN OF\", \"FIRM?\"], [\"\", \"Firmware v2.010\"], sep=\"\\n\"\n    ) as cc:\n        assert cc.firmware == (2, 10, 0)\n\n\ndef test_cc1_firmware_2():\n    with expected_protocol(\n        ik.qubitekk.CC1,\n        [\":ACKN OF\", \"FIRM?\"],\n        [\"Unknown Command\", \"Firmware v2\"],\n        sep=\"\\n\",\n    ) as cc:\n        assert cc.firmware == (2, 0, 0)\n\n\ndef test_cc1_firmware_3():\n    with expected_protocol(\n        ik.qubitekk.CC1,\n        [\":ACKN OF\", \"FIRM?\"],\n        [\"Unknown Command\", \"Firmware v2.010.1\"],\n        sep=\"\\n\",\n    ) as cc:\n        assert cc.firmware == (2, 10, 1)\n\n\ndef test_cc1_firmware_repeat_query():\n    with expected_protocol(\n        ik.qubitekk.CC1,\n        [\":ACKN OF\", \"FIRM?\", \"FIRM?\"],\n        [\"Unknown Command\", \"Unknown\", \"Firmware v2.010\"],\n        sep=\"\\n\",\n    ) as cc:\n        assert cc.firmware == (2, 10, 0)\n\n\ndef test_cc1_gate_new_firmware():\n    with expected_protocol(\n        ik.qubitekk.CC1,\n        [\":ACKN OF\", \"FIRM?\", \"GATE?\", \":GATE:ON\", \":GATE:OFF\"],\n        [\"\", \"Firmware v2.010\", \"ON\"],\n    ) as cc:\n        assert cc.gate is True\n        cc.gate = True\n        cc.gate = False\n\n\ndef test_cc1_gate_old_firmware():\n    with expected_protocol(\n        ik.qubitekk.CC1,\n        [\":ACKN OF\", \"FIRM?\", \"GATE?\", \":GATE 1\", \":GATE 0\"],\n        [\"Unknown Command\", \"Firmware v2.001\", \"1\", \"\", \"\"],\n        sep=\"\\n\",\n    ) as cc:\n        assert cc.gate is True\n        cc.gate = True\n        cc.gate = False\n\n\ndef test_cc1_gate_error():\n    with pytest.raises(TypeError), expected_protocol(\n        ik.qubitekk.CC1,\n        [\":ACKN OF\", \"FIRM?\", \":GATE blo\"],\n        [\"\", \"Firmware v2.010\"],\n        sep=\"\\n\",\n    ) as cc:\n        cc.gate = \"blo\"\n\n\ndef test_cc1_subtract_new_firmware():\n    with expected_protocol(\n        ik.qubitekk.CC1,\n        [\":ACKN OF\", \"FIRM?\", \"SUBT?\", \":SUBT:ON\", \":SUBT:OFF\"],\n        [\"\", \"Firmware v2.010\", \"ON\", \":SUBT:OFF\"],\n        sep=\"\\n\",\n    ) as cc:\n        assert cc.subtract is True\n        cc.subtract = True\n        cc.subtract = False\n\n\ndef test_cc1_subtract_error():\n    with pytest.raises(TypeError), expected_protocol(\n        ik.qubitekk.CC1,\n        [\":ACKN OF\", \"FIRM?\", \":SUBT blo\"],\n        [\"\", \"Firmware v2.010\"],\n        sep=\"\\n\",\n    ) as cc:\n        cc.subtract = \"blo\"\n\n\ndef test_cc1_trigger_mode():\n    with expected_protocol(\n        ik.qubitekk.CC1,\n        [\":ACKN OF\", \"FIRM?\", \"TRIG?\", \":TRIG:MODE CONT\", \":TRIG:MODE STOP\"],\n        [\"\", \"Firmware v2.010\", \"MODE STOP\"],\n        sep=\"\\n\",\n    ) as cc:\n        assert cc.trigger_mode is cc.TriggerMode.start_stop\n        cc.trigger_mode = cc.TriggerMode.continuous\n        cc.trigger_mode = cc.TriggerMode.start_stop\n\n\ndef test_cc1_trigger_mode_old_firmware():\n    with expected_protocol(\n        ik.qubitekk.CC1,\n        [\":ACKN OF\", \"FIRM?\", \"TRIG?\", \":TRIG 0\", \":TRIG 1\"],\n        [\"Unknown Command\", \"Firmware v2.001\", \"1\", \"\", \"\"],\n        sep=\"\\n\",\n    ) as cc:\n        assert cc.trigger_mode == cc.TriggerMode.start_stop\n        cc.trigger_mode = cc.TriggerMode.continuous\n        cc.trigger_mode = cc.TriggerMode.start_stop\n\n\ndef test_cc1_trigger_mode_error():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.qubitekk.CC1, [\":ACKN OF\", \"FIRM?\"], [\"\", \"Firmware v2.010\"], sep=\"\\n\"\n    ) as cc:\n        cc.trigger_mode = \"blo\"\n\n\ndef test_cc1_clear():\n    with expected_protocol(\n        ik.qubitekk.CC1,\n        [\":ACKN OF\", \"FIRM?\", \"CLEA\"],\n        [\"\", \"Firmware v2.010\"],\n        sep=\"\\n\",\n    ) as cc:\n        cc.clear_counts()\n\n\ndef test_acknowledge():\n    with expected_protocol(\n        ik.qubitekk.CC1,\n        [\":ACKN OF\", \"FIRM?\", \":ACKN ON\", \"CLEA\", \":ACKN OF\", \"CLEA\"],\n        [\"\", \"Firmware v2.010\", \"CLEA\", \":ACKN OF\"],\n        sep=\"\\n\",\n    ) as cc:\n        assert not cc.acknowledge\n        cc.acknowledge = True\n        assert cc.acknowledge\n        cc.clear_counts()\n        cc.acknowledge = False\n        assert not cc.acknowledge\n        cc.clear_counts()\n\n\ndef test_acknowledge_notimplementederror():\n    with pytest.raises(NotImplementedError), expected_protocol(\n        ik.qubitekk.CC1,\n        [\":ACKN OF\", \"FIRM?\"],\n        [\"Unknown Command\", \"Firmware v2.001\"],\n        sep=\"\\n\",\n    ) as cc:\n        cc.acknowledge = True\n\n\ndef test_acknowledge_not_implemented_error():  # pylint: disable=protected-access\n    with pytest.raises(NotImplementedError), expected_protocol(\n        ik.qubitekk.CC1,\n        [\":ACKN OF\", \"FIRM?\"],\n        [\"Unknown Command\", \"Firmware v2.001\"],\n        sep=\"\\n\",\n    ) as cc:\n        cc.acknowledge = True\n"
  },
  {
    "path": "tests/test_qubitekk/test_qubitekk_mc1.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the Qubitekk MC1\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport pytest\n\nfrom instruments.units import ureg as u\nimport instruments as ik\nfrom tests import expected_protocol\n\n# TESTS ######################################################################\n\n\ndef test_mc1_increment():\n    with expected_protocol(ik.qubitekk.MC1, [], [], sep=\"\\r\") as mc:\n        assert mc.increment == 1 * u.ms\n        mc.increment = 3 * u.ms\n        assert mc.increment == 3 * u.ms\n\n\ndef test_mc1_lower_limit():\n    with expected_protocol(ik.qubitekk.MC1, [], [], sep=\"\\r\") as mc:\n        assert mc.lower_limit == -300 * u.ms\n        mc.lower_limit = -400 * u.ms\n        assert mc.lower_limit == -400 * u.ms\n\n\ndef test_mc1_upper_limit():\n    with expected_protocol(ik.qubitekk.MC1, [], [], sep=\"\\r\") as mc:\n        assert mc.upper_limit == 300 * u.ms\n        mc.upper_limit = 400 * u.ms\n        assert mc.upper_limit == 400 * u.ms\n\n\ndef test_mc1_setting():\n    with expected_protocol(\n        ik.qubitekk.MC1, [\"OUTP?\", \":OUTP 0\"], [\"1\"], sep=\"\\r\"\n    ) as mc:\n        assert mc.setting == 1\n        mc.setting = 0\n\n\ndef test_mc1_internal_position():\n    with expected_protocol(\n        ik.qubitekk.MC1, [\"POSI?\", \"STEP?\"], [\"-100\", \"1\"], sep=\"\\r\"\n    ) as mc:\n        assert mc.internal_position == -100 * u.ms\n\n\ndef test_mc1_metric_position():\n    with expected_protocol(ik.qubitekk.MC1, [\"METR?\"], [\"-3.14159\"], sep=\"\\r\") as mc:\n        assert mc.metric_position == -3.14159 * u.mm\n\n\ndef test_mc1_direction():\n    with expected_protocol(ik.qubitekk.MC1, [\"DIRE?\"], [\"-100\"], sep=\"\\r\") as mc:\n        assert mc.direction == -100 * u.ms\n\n\ndef test_mc1_firmware():\n    with expected_protocol(ik.qubitekk.MC1, [\"FIRM?\"], [\"1.0.1\"], sep=\"\\r\") as mc:\n        assert mc.firmware == (1, 0, 1)\n\n\ndef test_mc1_firmware_no_patch_info():\n    with expected_protocol(ik.qubitekk.MC1, [\"FIRM?\"], [\"1.0\"], sep=\"\\r\") as mc:\n        assert mc.firmware == (1, 0, 0)\n\n\ndef test_mc1_inertia():\n    with expected_protocol(ik.qubitekk.MC1, [\"INER?\"], [\"20\"], sep=\"\\r\") as mc:\n        assert mc.inertia == 20 * u.ms\n\n\ndef test_mc1_step():\n    with expected_protocol(ik.qubitekk.MC1, [\"STEP?\"], [\"20\"], sep=\"\\r\") as mc:\n        assert mc.step_size == 20 * u.ms\n\n\ndef test_mc1_motor():\n    with expected_protocol(ik.qubitekk.MC1, [\"MOTO?\"], [\"Radio\"], sep=\"\\r\") as mc:\n        assert mc.controller == mc.MotorType.radio\n\n\ndef test_mc1_move_timeout():\n    with expected_protocol(\n        ik.qubitekk.MC1, [\"TIME?\", \"STEP?\"], [\"200\", \"1\"], sep=\"\\r\"\n    ) as mc:\n        assert mc.move_timeout == 200 * u.ms\n\n\ndef test_mc1_is_centering():\n    with expected_protocol(ik.qubitekk.MC1, [\"CENT?\"], [\"1\"], sep=\"\\r\") as mc:\n        assert mc.is_centering() is True\n\n\ndef test_mc1_is_centering_false():\n    with expected_protocol(ik.qubitekk.MC1, [\"CENT?\"], [\"0\"], sep=\"\\r\") as mc:\n        assert mc.is_centering() is False\n\n\ndef test_mc1_center():\n    with expected_protocol(ik.qubitekk.MC1, [\":CENT\"], [\"\"], sep=\"\\r\") as mc:\n        mc.center()\n\n\ndef test_mc1_reset():\n    with expected_protocol(ik.qubitekk.MC1, [\":RESE\"], [\"\"], sep=\"\\r\") as mc:\n        mc.reset()\n\n\ndef test_mc1_move():\n    with expected_protocol(\n        ik.qubitekk.MC1, [\"STEP?\", \":MOVE 0\"], [\"1\"], sep=\"\\r\"\n    ) as mc:\n        mc.move(0)\n\n\ndef test_mc1_move_value_error():\n    with pytest.raises(ValueError) as exc_info, expected_protocol(\n        ik.qubitekk.MC1, [], [], sep=\"\\r\"\n    ) as mc:\n        mc.move(-1000)\n    exc_msg = exc_info.value.args[0]\n    assert exc_msg == \"Location out of range\"\n"
  },
  {
    "path": "tests/test_rigol/test_rigolds1000.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the Rigol DS1000\n\"\"\"\n\n# IMPORTS ####################################################################\n\nimport pytest\n\nimport instruments as ik\nfrom instruments.optional_dep_finder import numpy\nfrom tests import (\n    expected_protocol,\n    iterable_eq,\n    make_name_test,\n)\n\n# TESTS ######################################################################\n\n# pylint: disable=protected-access\n\n\ntest_rigolds1000_name = make_name_test(ik.rigol.RigolDS1000Series)\n\n\n# TEST CHANNEL #\n\n\ndef test_channel_initialization():\n    \"\"\"Ensure correct initialization of channel object.\"\"\"\n    with expected_protocol(ik.rigol.RigolDS1000Series, [], []) as osc:\n        channel = osc.channel[0]\n        assert channel._parent is osc\n        assert channel._idx == 1\n\n\ndef test_channel_coupling():\n    \"\"\"Get / set channel coupling.\"\"\"\n    with expected_protocol(\n        ik.rigol.RigolDS1000Series, [\":CHAN1:COUP?\", \":CHAN2:COUP DC\"], [\"AC\"]\n    ) as osc:\n        assert osc.channel[0].coupling == osc.channel[0].Coupling.ac\n        osc.channel[1].coupling = osc.channel[1].Coupling.dc\n\n\ndef test_channel_bw_limit():\n    \"\"\"Get / set instrument bw limit.\"\"\"\n    with expected_protocol(\n        ik.rigol.RigolDS1000Series, [\":CHAN2:BWL?\", \":CHAN1:BWL ON\"], [\"OFF\"]\n    ) as osc:\n        assert not osc.channel[1].bw_limit\n        osc.channel[0].bw_limit = True\n\n\ndef test_channel_display():\n    \"\"\"Get / set instrument display.\"\"\"\n    with expected_protocol(\n        ik.rigol.RigolDS1000Series, [\":CHAN2:DISP?\", \":CHAN1:DISP ON\"], [\"OFF\"]\n    ) as osc:\n        assert not osc.channel[1].display\n        osc.channel[0].display = True\n\n\ndef test_channel_invert():\n    \"\"\"Get / set instrument invert.\"\"\"\n    with expected_protocol(\n        ik.rigol.RigolDS1000Series, [\":CHAN2:INV?\", \":CHAN1:INV ON\"], [\"OFF\"]\n    ) as osc:\n        assert not osc.channel[1].invert\n        osc.channel[0].invert = True\n\n\ndef test_channel_filter():\n    \"\"\"Get / set instrument filter.\"\"\"\n    with expected_protocol(\n        ik.rigol.RigolDS1000Series, [\":CHAN2:FILT?\", \":CHAN1:FILT ON\"], [\"OFF\"]\n    ) as osc:\n        assert not osc.channel[1].filter\n        osc.channel[0].filter = True\n\n\ndef test_channel_vernier():\n    \"\"\"Get / set instrument vernier.\"\"\"\n    with expected_protocol(\n        ik.rigol.RigolDS1000Series, [\":CHAN2:VERN?\", \":CHAN1:VERN ON\"], [\"OFF\"]\n    ) as osc:\n        assert not osc.channel[1].vernier\n        osc.channel[0].vernier = True\n\n\ndef test_channel_name():\n    \"\"\"Get channel name - DataSource property.\"\"\"\n    with expected_protocol(ik.rigol.RigolDS1000Series, [], []) as osc:\n        assert osc.channel[0].name == \"CHAN1\"\n\n\ndef test_channel_read_waveform():\n    \"\"\"Read waveform of channel object.\"\"\"\n    with expected_protocol(\n        ik.rigol.RigolDS1000Series,\n        [\":WAV:DATA? CHAN2\"],\n        [b\"#210\" + bytes.fromhex(\"00000001000200030004\") + b\"0\"],\n    ) as osc:\n        expected = (0, 1, 2, 3, 4)\n        if numpy:\n            expected = numpy.array(expected)\n        iterable_eq(osc.channel[1].read_waveform(), expected)\n\n\n# TEST MATH #\n\n\ndef test_math_name():\n    \"\"\"Ensure correct naming of math object.\"\"\"\n    with expected_protocol(ik.rigol.RigolDS1000Series, [], []) as osc:\n        assert osc.math.name == \"MATH\"\n\n\ndef test_math_read_waveform():\n    \"\"\"Read waveform of of math object.\"\"\"\n    with expected_protocol(\n        ik.rigol.RigolDS1000Series,\n        [\":WAV:DATA? MATH\"],\n        [b\"#210\" + bytes.fromhex(\"00000001000200030004\") + b\"0\"],\n    ) as osc:\n        expected = (0, 1, 2, 3, 4)\n        if numpy:\n            expected = numpy.array(expected)\n        iterable_eq(osc.math.read_waveform(), expected)\n\n\n# TEST REF DATASOURCE #\n\n\ndef test_ref_name():\n    \"\"\"Ensure correct naming of ref object.\"\"\"\n    with expected_protocol(ik.rigol.RigolDS1000Series, [], []) as osc:\n        assert osc.ref.name == \"REF\"\n\n\ndef test_ref_read_waveform_raises_error():\n    \"\"\"Ensure error raising when reading waveform of REF channel.\"\"\"\n    with expected_protocol(ik.rigol.RigolDS1000Series, [], []) as osc:\n        with pytest.raises(NotImplementedError):\n            osc.ref.read_waveform()\n\n\n# TEST FURTHER PROPERTIES AND METHODS #\n\n\ndef test_acquire_type():\n    \"\"\"Get / Set acquire type.\"\"\"\n    with expected_protocol(\n        ik.rigol.RigolDS1000Series, [\":ACQ:TYPE?\", \":ACQ:TYPE PEAK\"], [\"NORM\"]\n    ) as osc:\n        assert osc.acquire_type == osc.AcquisitionType.normal\n        osc.acquire_type = osc.AcquisitionType.peak_detect\n\n\ndef test_acquire_averages():\n    \"\"\"Get / Set acquire averages.\"\"\"\n    with expected_protocol(\n        ik.rigol.RigolDS1000Series, [\":ACQ:AVER?\", \":ACQ:AVER 128\"], [\"16\"]\n    ) as osc:\n        assert osc.acquire_averages == 16\n        osc.acquire_averages = 128\n\n\ndef test_acquire_averages_bad_values():\n    \"\"\"Raise error when bad values encountered.\"\"\"\n    with expected_protocol(ik.rigol.RigolDS1000Series, [], []) as osc:\n        with pytest.raises(ValueError):\n            osc.acquire_averages = 0\n        with pytest.raises(ValueError):\n            osc.acquire_averages = 1\n        with pytest.raises(ValueError):\n            osc.acquire_averages = 42\n        with pytest.raises(ValueError):\n            osc.acquire_averages = 257\n        with pytest.raises(ValueError):\n            osc.acquire_averages = 512\n\n\ndef test_force_trigger():\n    \"\"\"Force a trigger.\"\"\"\n    with expected_protocol(ik.rigol.RigolDS1000Series, [\":FORC\"], []) as osc:\n        osc.force_trigger()\n\n\ndef test_run():\n    \"\"\"Run the instrument.\"\"\"\n    with expected_protocol(ik.rigol.RigolDS1000Series, [\":RUN\"], []) as osc:\n        osc.run()\n\n\ndef test_stop():\n    \"\"\"Stop the instrument.\"\"\"\n    with expected_protocol(ik.rigol.RigolDS1000Series, [\":STOP\"], []) as osc:\n        osc.stop()\n\n\ndef test_panel_locked():\n    \"\"\"Get / set the panel_locked bool property.\"\"\"\n    with expected_protocol(\n        ik.rigol.RigolDS1000Series, [\":KEY:LOCK?\", \":KEY:LOCK DIS\"], [\"ENAB\"]\n    ) as osc:\n        assert osc.panel_locked\n        osc.panel_locked = False\n\n\ndef test_release_panel():\n    \"\"\"Get / set the panel_locked bool property.\"\"\"\n    with expected_protocol(ik.rigol.RigolDS1000Series, [\":KEY:FORC\"], []) as osc:\n        osc.release_panel()\n"
  },
  {
    "path": "tests/test_split_str.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the util_fns.split_unit_str utility function\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport pytest\n\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import split_unit_str\n\n# TEST CASES #################################################################\n\n\ndef test_split_unit_str_magnitude_and_units():\n    \"\"\"\n    split_unit_str: Given the input \"42 foobars\" I expect the output\n    to be (42, \"foobars\").\n\n    This checks that \"[val] [units]\" works where val is a non-scientific number\n    \"\"\"\n    mag, units = split_unit_str(\"42 foobars\")\n    assert mag == 42\n    assert units == \"foobars\"\n\n\ndef test_split_unit_str_magnitude_and_default_units():\n    \"\"\"\n    split_unit_str: Given the input \"42\" and default_units=\"foobars\"\n    I expect output to be (42, \"foobars\").\n\n    This checks that when given a string without units, the function returns\n    default_units as the units.\n    \"\"\"\n    mag, units = split_unit_str(\"42\", default_units=\"foobars\")\n    assert mag == 42\n    assert units == \"foobars\"\n\n\ndef test_split_unit_str_ignore_default_units():\n    \"\"\"\n    split_unit_str: Given the input \"42 snafus\" and default_units=\"foobars\"\n    I expect the output to be (42, \"snafus\").\n\n    This verifies that if the input has units, then any specified default_units\n    are ignored.\n    \"\"\"\n    mag, units = split_unit_str(\"42 snafus\", default_units=\"foobars\")\n    assert mag == 42\n    assert units == \"snafus\"\n\n\ndef test_split_unit_str_lookups():\n    \"\"\"\n    split_unit_str: Given the input \"42 FOO\" and a dictionary for our units\n    lookup, I expect the output to be (42, \"foobars\").\n\n    This checks that the unit lookup parameter is correctly called, which can be\n    used to map between units as string and their pyquantities equivalent.\n    \"\"\"\n    unit_dict = {\"FOO\": \"foobars\", \"SNA\": \"snafus\"}\n    mag, units = split_unit_str(\"42 FOO\", lookup=unit_dict.__getitem__)\n    assert mag == 42\n    assert units == \"foobars\"\n\n\ndef test_split_unit_str_scientific_notation():\n    \"\"\"\n    split_unit_str: Given inputs of scientific notation, I expect the output\n    to correctly represent the inputted magnitude.\n\n    This checks that inputs with scientific notation are correctly converted\n    to floats.\n    \"\"\"\n    # No signs, no units\n    mag, units = split_unit_str(\"123E1\")\n    assert mag == 1230\n    assert units == u.dimensionless\n    # Negative exponential, no units\n    mag, units = split_unit_str(\"123E-1\")\n    assert mag == 12.3\n    assert units == u.dimensionless\n    # Negative magnitude, no units\n    mag, units = split_unit_str(\"-123E1\")\n    assert mag == -1230\n    assert units == u.dimensionless\n    # No signs, with units\n    mag, units = split_unit_str(\"123E1 foobars\")\n    assert mag == 1230\n    assert units == \"foobars\"\n    # Signs everywhere, with units\n    mag, units = split_unit_str(\"-123E-1 foobars\")\n    assert mag == -12.3\n    assert units == \"foobars\"\n    # Lower case e\n    mag, units = split_unit_str(\"123e1\")\n    assert mag == 1230\n    assert units == u.dimensionless\n\n\ndef test_split_unit_str_empty_string():\n    \"\"\"\n    split_unit_str: Given an empty string, I expect the function to raise\n    a ValueError.\n    \"\"\"\n    with pytest.raises(ValueError):\n        _ = split_unit_str(\"\")\n\n\ndef test_split_unit_str_only_exponential():\n    \"\"\"\n    split_unit_str: Given a string with only an exponential, I expect the\n    function to raise a ValueError.\n    \"\"\"\n    with pytest.raises(ValueError):\n        _ = split_unit_str(\"E3\")\n\n\ndef test_split_unit_str_magnitude_with_decimal():\n    \"\"\"\n    split_unit_str: Given a string with magnitude containing a decimal, I\n    expect the function to correctly parse the magnitude.\n    \"\"\"\n    # Decimal and units\n    mag, units = split_unit_str(\"123.4 foobars\")\n    assert mag == 123.4\n    assert units == \"foobars\"\n    # Decimal, units, and exponential\n    mag, units = split_unit_str(\"123.4E1 foobars\")\n    assert mag == 1234\n    assert units == \"foobars\"\n\n\ndef test_split_unit_str_only_units():\n    \"\"\"\n    split_unit_str: Given a bad string containing only units (ie, no numbers),\n    I expect the function to raise a ValueError.\n    \"\"\"\n    with pytest.raises(ValueError):\n        _ = split_unit_str(\"foobars\")\n"
  },
  {
    "path": "tests/test_srs/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_srs/test_srs345.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the SRS 345 function generator\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport instruments as ik\nfrom tests import (\n    expected_protocol,\n    iterable_eq,\n)\nfrom instruments.units import ureg as u\n\n# TESTS #######################################################################\n\n\ndef test_amplitude():\n    with expected_protocol(\n        ik.srs.SRS345,\n        [\"AMPL?\", \"AMPL 0.1VP\", \"AMPL 0.1VR\"],\n        [\n            \"1.234VP\",\n        ],\n    ) as inst:\n        iterable_eq(inst.amplitude, (1.234 * u.V, inst.VoltageMode.peak_to_peak))\n        inst.amplitude = 0.1 * u.V\n        inst.amplitude = (0.1 * u.V, inst.VoltageMode.rms)\n\n\ndef test_frequency():\n    with expected_protocol(\n        ik.srs.SRS345,\n        [\n            \"FREQ?\",\n            f\"FREQ {0.1:e}\",\n        ],\n        [\n            \"1.234\",\n        ],\n    ) as inst:\n        assert inst.frequency == 1.234 * u.Hz\n        inst.frequency = 0.1 * u.Hz\n\n\ndef test_function():\n    with expected_protocol(\n        ik.srs.SRS345,\n        [\"FUNC?\", \"FUNC 0\"],\n        [\n            \"1\",\n        ],\n    ) as inst:\n        assert inst.function == inst.Function.square\n        inst.function = inst.Function.sinusoid\n\n\ndef test_offset():\n    with expected_protocol(\n        ik.srs.SRS345,\n        [\n            \"OFFS?\",\n            f\"OFFS {0.1:e}\",\n        ],\n        [\n            \"1.234\",\n        ],\n    ) as inst:\n        assert inst.offset == 1.234 * u.V\n        inst.offset = 0.1 * u.V\n\n\ndef test_phase():\n    with expected_protocol(\n        ik.srs.SRS345,\n        [\n            \"PHSE?\",\n            f\"PHSE {0.1:e}\",\n        ],\n        [\n            \"1.234\",\n        ],\n    ) as inst:\n        assert inst.phase == 1.234 * u.degree\n        inst.phase = 0.1 * u.degree\n"
  },
  {
    "path": "tests/test_srs/test_srs830.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the SRS 830 lock-in amplifier\n\"\"\"\n\n# IMPORTS #####################################################################\n\nimport time\n\nimport pytest\nimport serial\n\nimport instruments as ik\nfrom instruments.abstract_instruments.comm import (\n    FileCommunicator,\n    GPIBCommunicator,\n    LoopbackCommunicator,\n    SerialCommunicator,\n)\nfrom instruments.optional_dep_finder import numpy\nfrom tests import (\n    expected_protocol,\n    iterable_eq,\n)\nfrom instruments.units import ureg as u\n\n# TESTS #######################################################################\n\n\n@pytest.fixture(autouse=True)\ndef time_mock(mocker):\n    \"\"\"Mock out sleep such that test runs fast.\"\"\"\n    return mocker.patch.object(time, \"sleep\", return_value=None)\n\n\n@pytest.mark.parametrize(\"mode\", (1, 2))\ndef test_init_mode_given(mocker, mode):\n    \"\"\"Test initialization with a given mode.\"\"\"\n    comm = LoopbackCommunicator()\n    send_spy = mocker.spy(comm, \"sendcmd\")\n    ik.srs.SRS830(comm, outx_mode=mode)\n    send_spy.assert_called_with(f\"OUTX {mode}\")\n\n\ndef test_init_mode_gpibcomm(mocker):\n    \"\"\"Test initialization with GPIBCommunicator\"\"\"\n    mock_gpib = mocker.MagicMock()\n    comm = GPIBCommunicator(mock_gpib, 1)\n    mock_send = mocker.patch.object(comm, \"sendcmd\")\n    ik.srs.SRS830(comm)\n    mock_send.assert_called_with(\"OUTX 1\")\n\n\ndef test_init_mode_serial_comm(mocker):\n    \"\"\"Test initialization with SerialCommunicator\"\"\"\n    comm = SerialCommunicator(serial.Serial())\n    mock_send = mocker.patch.object(comm, \"sendcmd\")\n    ik.srs.SRS830(comm)\n    mock_send.assert_called_with(\"OUTX 2\")\n\n\ndef test_init_mode_invalid():\n    \"\"\"Test initialization with invalid communicator.\"\"\"\n    comm = FileCommunicator(None)\n    with pytest.warns(UserWarning) as wrn_info:\n        ik.srs.SRS830(comm)\n    wrn_msg = wrn_info[0].message.args[0]\n    assert (\n        wrn_msg == \"OUTX command has not been set. Instrument behaviour \" \"is unknown.\"\n    )\n\n\ndef test_frequency_source():\n    with expected_protocol(\n        ik.srs.SRS830,\n        [\"FMOD?\", \"FMOD 0\"],\n        [\n            \"1\",\n        ],\n    ) as inst:\n        assert inst.frequency_source == inst.FreqSource.internal\n        inst.frequency_source = inst.FreqSource.external\n\n\ndef test_frequency():\n    with expected_protocol(\n        ik.srs.SRS830,\n        [\"FREQ?\", f\"FREQ {1000:e}\"],\n        [\n            \"12.34\",\n        ],\n    ) as inst:\n        assert inst.frequency == 12.34 * u.Hz\n        inst.frequency = 1 * u.kHz\n\n\ndef test_phase():\n    with expected_protocol(\n        ik.srs.SRS830,\n        [\"PHAS?\", f\"PHAS {10:e}\"],\n        [\n            \"-45\",\n        ],\n    ) as inst:\n        assert inst.phase == -45 * u.degrees\n        inst.phase = 10 * u.degrees\n\n\ndef test_amplitude():\n    with expected_protocol(\n        ik.srs.SRS830,\n        [\"SLVL?\", f\"SLVL {1:e}\"],\n        [\n            \"0.1\",\n        ],\n    ) as inst:\n        assert inst.amplitude == 0.1 * u.V\n        inst.amplitude = 1 * u.V\n\n\ndef test_input_shield_ground():\n    with expected_protocol(\n        ik.srs.SRS830,\n        [\"IGND?\", \"IGND 1\"],\n        [\n            \"0\",\n        ],\n    ) as inst:\n        assert inst.input_shield_ground is False\n        inst.input_shield_ground = True\n\n\ndef test_coupling():\n    with expected_protocol(\n        ik.srs.SRS830,\n        [\"ICPL?\", \"ICPL 0\"],\n        [\n            \"1\",\n        ],\n    ) as inst:\n        assert inst.coupling == inst.Coupling.dc\n        inst.coupling = inst.Coupling.ac\n\n\ndef test_sample_rate():  # sends index of VALID_SAMPLE_RATES\n    with expected_protocol(\n        ik.srs.SRS830, [\"SRAT?\", \"SRAT?\", f\"SRAT {5:d}\", \"SRAT 14\"], [\"8\", \"14\"]\n    ) as inst:\n        assert inst.sample_rate == 16 * u.Hz\n        assert inst.sample_rate == \"trigger\"\n        inst.sample_rate = 2\n        inst.sample_rate = \"trigger\"\n\n\ndef test_sample_rate_invalid():\n    with pytest.raises(ValueError), expected_protocol(ik.srs.SRS830, [], []) as inst:\n        inst.sample_rate = \"foobar\"\n\n\ndef test_buffer_mode():\n    with expected_protocol(\n        ik.srs.SRS830,\n        [\"SEND?\", \"SEND 1\"],\n        [\n            \"0\",\n        ],\n    ) as inst:\n        assert inst.buffer_mode == inst.BufferMode.one_shot\n        inst.buffer_mode = inst.BufferMode.loop\n\n\ndef test_num_data_points():\n    with expected_protocol(\n        ik.srs.SRS830,\n        [\"SPTS?\"],\n        [\n            \"5\",\n        ],\n    ) as inst:\n        assert inst.num_data_points == 5\n\n\ndef test_num_data_points_no_answer():\n    \"\"\"Raise IOError after no answer 10 times.\"\"\"\n    answer = \"\"\n    with expected_protocol(ik.srs.SRS830, [\"SPTS?\"] * 10, [answer] * 10) as inst:\n        with pytest.raises(IOError) as err_info:\n            _ = inst.num_data_points\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Expected integer response from instrument, got \"\n            f\"{repr(answer)}\"\n        )\n\n\ndef test_data_transfer():\n    with expected_protocol(\n        ik.srs.SRS830,\n        [\"FAST?\", \"FAST 2\"],\n        [\n            \"0\",\n        ],\n    ) as inst:\n        assert inst.data_transfer is False\n        inst.data_transfer = True\n\n\ndef test_auto_offset():\n    with expected_protocol(ik.srs.SRS830, [\"AOFF 1\", \"AOFF 1\"], []) as inst:\n        inst.auto_offset(inst.Mode.x)\n        inst.auto_offset(\"x\")\n\n\ndef test_auto_offset_invalid():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.srs.SRS830,\n        [\n            \"AOFF 1\",\n        ],\n        [],\n    ) as inst:\n        inst.auto_offset(inst.Mode.theta)\n\n\ndef test_auto_phase():\n    with expected_protocol(ik.srs.SRS830, [\"APHS\"], []) as inst:\n        inst.auto_phase()\n\n\ndef test_init():\n    with expected_protocol(ik.srs.SRS830, [\"REST\", \"SRAT 5\", \"SEND 1\"], []) as inst:\n        inst.init(sample_rate=2, buffer_mode=inst.BufferMode.loop)\n\n\ndef test_start_data_transfer():\n    with expected_protocol(ik.srs.SRS830, [\"FAST 2\", \"STRD\"], []) as inst:\n        inst.start_data_transfer()\n\n\ndef test_take_measurement():\n    with expected_protocol(\n        ik.srs.SRS830,\n        [\n            \"REST\",\n            \"SRAT 4\",\n            \"SEND 0\",\n            \"FAST 2\",\n            \"STRD\",\n            \"PAUS\",\n            \"SPTS?\",\n            \"SPTS?\",\n            \"TRCA?1,0,2\",\n            \"SPTS?\",\n            \"TRCA?2,0,2\",\n        ],\n        [\"2\", \"2\", \"1.234,5.678\", \"2\", \"0.456,5.321\"],\n    ) as inst:\n        resp = inst.take_measurement(sample_rate=1, num_samples=2)\n        expected = ((1.234, 5.678), (0.456, 5.321))\n        if numpy:\n            expected = numpy.array(expected)\n        iterable_eq(resp, expected)\n\n\ndef test_take_measurement_num_dat_points_fails():\n    \"\"\"Simulate the failure of num_data_points.\n\n    This is the way it is currently implemented.\n    \"\"\"\n    with expected_protocol(\n        ik.srs.SRS830,\n        [\"REST\", \"SRAT 4\", \"SEND 0\", \"FAST 2\", \"STRD\", \"PAUS\"]\n        + [\"SPTS?\"] * 11\n        + [\"TRCA?1,0,2\", \"SPTS?\", \"TRCA?2,0,2\"],\n        [\n            \"\",\n        ]\n        * 10\n        + [\"2\", \"1.234,5.678\", \"2\", \"0.456,5.321\"],\n    ) as inst:\n        resp = inst.take_measurement(sample_rate=1, num_samples=2)\n        expected = ((1.234, 5.678), (0.456, 5.321))\n        if numpy:\n            expected = numpy.array(expected)\n        iterable_eq(resp, expected)\n\n\ndef test_take_measurement_invalid_num_samples():\n    with pytest.raises(ValueError), expected_protocol(ik.srs.SRS830, [], []) as inst:\n        _ = inst.take_measurement(sample_rate=1, num_samples=16384)\n\n\ndef test_set_offset_expand():\n    with expected_protocol(ik.srs.SRS830, [\"OEXP 1,0,0\"], []) as inst:\n        inst.set_offset_expand(mode=inst.Mode.x, offset=0, expand=1)\n\n\ndef test_set_offset_expand_mode_as_str():\n    with expected_protocol(ik.srs.SRS830, [\"OEXP 1,0,0\"], []) as inst:\n        inst.set_offset_expand(mode=\"x\", offset=0, expand=1)\n\n\ndef test_set_offset_expand_invalid_mode():\n    with pytest.raises(ValueError), expected_protocol(ik.srs.SRS830, [], []) as inst:\n        inst.set_offset_expand(mode=inst.Mode.theta, offset=0, expand=1)\n\n\ndef test_set_offset_expand_invalid_offset():\n    with pytest.raises(ValueError), expected_protocol(ik.srs.SRS830, [], []) as inst:\n        inst.set_offset_expand(mode=inst.Mode.x, offset=106, expand=1)\n\n\ndef test_set_offset_expand_invalid_expand():\n    with pytest.raises(ValueError), expected_protocol(ik.srs.SRS830, [], []) as inst:\n        inst.set_offset_expand(mode=inst.Mode.x, offset=0, expand=5)\n\n\ndef test_set_offset_expand_invalid_type_offset():\n    with pytest.raises(TypeError), expected_protocol(ik.srs.SRS830, [], []) as inst:\n        inst.set_offset_expand(mode=inst.Mode.x, offset=\"derp\", expand=1)\n\n\ndef test_set_offset_expand_invalid_type_expand():\n    with pytest.raises(TypeError), expected_protocol(ik.srs.SRS830, [], []) as inst:\n        inst.set_offset_expand(mode=inst.Mode.x, offset=0, expand=\"derp\")\n\n\ndef test_start_scan():\n    with expected_protocol(ik.srs.SRS830, [\"STRD\"], []) as inst:\n        inst.start_scan()\n\n\ndef test_pause():\n    with expected_protocol(ik.srs.SRS830, [\"PAUS\"], []) as inst:\n        inst.pause()\n\n\ndef test_data_snap():\n    with expected_protocol(ik.srs.SRS830, [\"SNAP? 1,2\"], [\"1.234,9.876\"]) as inst:\n        data = inst.data_snap(mode1=inst.Mode.x, mode2=inst.Mode.y)\n        expected = [1.234, 9.876]\n        iterable_eq(data, expected)\n\n\ndef test_data_snap_mode_as_str():\n    with expected_protocol(ik.srs.SRS830, [\"SNAP? 1,2\"], [\"1.234,9.876\"]) as inst:\n        data = inst.data_snap(mode1=\"x\", mode2=\"y\")\n        expected = [1.234, 9.876]\n        iterable_eq(data, expected)\n\n\ndef test_data_snap_invalid_snap_mode1():\n    with pytest.raises(ValueError), expected_protocol(ik.srs.SRS830, [], []) as inst:\n        _ = inst.data_snap(mode1=inst.Mode.xnoise, mode2=inst.Mode.y)\n\n\ndef test_data_snap_invalid_snap_mode2():\n    with pytest.raises(ValueError), expected_protocol(ik.srs.SRS830, [], []) as inst:\n        _ = inst.data_snap(mode1=inst.Mode.x, mode2=inst.Mode.ynoise)\n\n\ndef test_data_snap_identical_modes():\n    with pytest.raises(ValueError), expected_protocol(ik.srs.SRS830, [], []) as inst:\n        _ = inst.data_snap(mode1=inst.Mode.x, mode2=inst.Mode.x)\n\n\ndef test_read_data_buffer():\n    with expected_protocol(\n        ik.srs.SRS830, [\"SPTS?\", \"TRCA?1,0,2\"], [\"2\", \"1.234,9.876\"]\n    ) as inst:\n        data = inst.read_data_buffer(channel=inst.Mode.ch1)\n        expected = (1.234, 9.876)\n        if numpy:\n            expected = numpy.array(expected)\n        iterable_eq(data, expected)\n\n\ndef test_read_data_buffer_mode_as_str():\n    with expected_protocol(\n        ik.srs.SRS830, [\"SPTS?\", \"TRCA?1,0,2\"], [\"2\", \"1.234,9.876\"]\n    ) as inst:\n        data = inst.read_data_buffer(channel=\"ch1\")\n        expected = (1.234, 9.876)\n        if numpy:\n            expected = numpy.array(expected)\n        iterable_eq(data, expected)\n\n\ndef test_read_data_buffer_invalid_mode():\n    with pytest.raises(ValueError), expected_protocol(ik.srs.SRS830, [], []) as inst:\n        _ = inst.read_data_buffer(channel=inst.Mode.x)\n\n\ndef test_clear_data_buffer():\n    with expected_protocol(ik.srs.SRS830, [\"REST\"], []) as inst:\n        inst.clear_data_buffer()\n\n\ndef test_set_channel_display():\n    with expected_protocol(ik.srs.SRS830, [\"DDEF 1,0,0\"], []) as inst:\n        inst.set_channel_display(\n            channel=inst.Mode.ch1, display=inst.Mode.x, ratio=inst.Mode.none\n        )\n\n\ndef test_set_channel_display_params_as_str():\n    with expected_protocol(ik.srs.SRS830, [\"DDEF 1,0,0\"], []) as inst:\n        inst.set_channel_display(channel=\"ch1\", display=\"x\", ratio=\"none\")\n\n\ndef test_set_channel_display_invalid_channel():\n    with pytest.raises(ValueError), expected_protocol(ik.srs.SRS830, [], []) as inst:\n        inst.set_channel_display(\n            channel=inst.Mode.x, display=inst.Mode.x, ratio=inst.Mode.none\n        )\n\n\ndef test_set_channel_display_invalid_display():\n    with pytest.raises(ValueError), expected_protocol(ik.srs.SRS830, [], []) as inst:\n        inst.set_channel_display(\n            channel=inst.Mode.ch1,\n            display=inst.Mode.y,  # y is only valid for ch2, not ch1!\n            ratio=inst.Mode.none,\n        )\n\n\ndef test_set_channel_display_invalid_ratio():\n    with pytest.raises(ValueError), expected_protocol(ik.srs.SRS830, [], []) as inst:\n        inst.set_channel_display(\n            channel=inst.Mode.ch1, display=inst.Mode.x, ratio=inst.Mode.xnoise\n        )\n"
  },
  {
    "path": "tests/test_srs/test_srsctc100.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the SRS CTC-100\n\"\"\"\n\n# IMPORTS ####################################################################\n\nfrom hypothesis import (\n    given,\n    strategies as st,\n)\nimport pytest\n\nimport instruments as ik\nfrom instruments.optional_dep_finder import numpy\nfrom tests import (\n    expected_protocol,\n    iterable_eq,\n)\nfrom instruments.units import ureg as u\n\n# TESTS ######################################################################\n\n\n# pylint: disable=protected-access\n\n\n# SETUP #\n\n\n# Create one channel name for every possible unit for parametrized testing\nch_units = list(ik.srs.SRSCTC100._UNIT_NAMES.keys())\nch_names = [f\"CH {it}\" for it in range(len(ch_units))]\nch_name_unit_dict = dict(zip(ch_names, ch_units))\n\n\n# string that is returned when initializing channels:\nch_names_query = \"getOutput.names?\"\nch_names_str = \",\".join(ch_names)\n\n\n# CHANNELS #\n\n\n@pytest.mark.parametrize(\"channel\", ch_names)\ndef test_srsctc100_channel_init(channel):\n    \"\"\"Initialize a channel.\"\"\"\n    with expected_protocol(ik.srs.SRSCTC100, [ch_names_query], [ch_names_str]) as inst:\n        with inst._error_checking_disabled():\n            ch = inst.channel[channel]\n            assert ch._ctc is inst\n            assert ch._chan_name == channel\n            assert ch._rem_name == channel.replace(\" \", \"\")\n\n\ndef test_srsctc100_channel_name():\n    \"\"\"Get / set the channel name.\"\"\"\n    old_name = ch_names[0]\n    new_name = \"New channel\"\n    with expected_protocol(\n        ik.srs.SRSCTC100,\n        [ch_names_query, f\"{old_name.replace(' ', '')}.name = \\\"{new_name}\\\"\"],\n        [ch_names_str],\n    ) as inst:\n        with inst._error_checking_disabled():\n            ch = inst.channel[ch_names[0]]\n            # assert old name is set\n            assert ch.name == ch_names[0]\n            # set a new name\n            ch.name = new_name\n            assert ch.name == new_name\n            assert ch._rem_name == new_name.replace(\" \", \"\")\n\n\n@pytest.mark.parametrize(\"channel\", ch_names)\ndef test_srsctc100_channel_get(channel):\n    \"\"\"Query a given channel.\n\n    Ensure proper functionality for all available channels.\n    \"\"\"\n    cmd = \"COMMAND\"\n    answ = \"ANSWER\"\n    with expected_protocol(\n        ik.srs.SRSCTC100,\n        [ch_names_query, f\"{channel.replace(' ', '')}.{cmd}?\"],\n        [ch_names_str, answ],\n    ) as inst:\n        with inst._error_checking_disabled():\n            assert inst.channel[channel]._get(cmd) == answ\n\n\n@pytest.mark.parametrize(\"channel\", ch_names)\ndef test_srsctc100_channel_set(channel):\n    \"\"\"Send a command to a given channel.\n\n    Ensure proper functionality for all available channels.\n    \"\"\"\n    cmd = \"COMMAND\"\n    newval = \"NEWVAL\"\n    with expected_protocol(\n        ik.srs.SRSCTC100,\n        [ch_names_query, f\"{channel.replace(' ', '')}.{cmd} = \\\"{newval}\\\"\"],\n        [ch_names_str],\n    ) as inst:\n        with inst._error_checking_disabled():\n            inst.channel[channel]._set(cmd, newval)\n\n\ndef test_srsctc100_channel_value():\n    \"\"\"Get value and unit from a given channel.\"\"\"\n    channel = ch_names[0]\n    unit = ik.srs.SRSCTC100._UNIT_NAMES[ch_units[0]]\n    value = 42\n\n    with expected_protocol(\n        ik.srs.SRSCTC100,\n        [\n            ch_names_query,\n            f\"{channel.replace(' ', '')}.value?\",\n            \"getOutput.units?\",\n            ch_names_query,\n        ],\n        [\n            ch_names_str,\n            f\"{value}\",\n            \",\".join(ch_units),\n            ch_names_str,\n        ],\n    ) as inst:\n        with inst._error_checking_disabled():\n            assert inst.channel[channel].value == u.Quantity(value, unit)\n\n\ndef test_srsctc100_channel_units_single():\n    \"\"\"Get unit for one given channel.\"\"\"\n    channel = ch_names[0]\n    unit = ik.srs.SRSCTC100._UNIT_NAMES[ch_units[0]]\n    with expected_protocol(\n        ik.srs.SRSCTC100,\n        [ch_names_query, \"getOutput.units?\", ch_names_query],\n        [\n            ch_names_str,\n            \",\".join(ch_units),\n            ch_names_str,\n        ],\n    ) as inst:\n        with inst._error_checking_disabled():\n            ch = inst.channel[channel]\n            assert ch.units == unit\n\n\n@pytest.mark.parametrize(\"sensor\", ik.srs.SRSCTC100.SensorType)\ndef test_srsctc100_channel_sensor_type(sensor):\n    \"\"\"Get type of sensor attached to specified channel.\"\"\"\n    channel = ch_names[0]\n    with expected_protocol(\n        ik.srs.SRSCTC100,\n        [\n            ch_names_query,\n            f\"{channel.replace(' ', '')}.sensor?\",\n        ],\n        [ch_names_str, f\"{sensor.value}\"],\n    ) as inst:\n        with inst._error_checking_disabled():\n            assert inst.channel[channel].sensor_type == sensor\n\n\n@pytest.mark.parametrize(\"newval\", (True, False))\ndef test_srsctc100_channel_stats_enabled(newval):\n    \"\"\"Get / set enabling statistics for specified channel.\"\"\"\n    channel = ch_names[0]\n    value_inst = \"On\" if newval else \"Off\"\n    with expected_protocol(\n        ik.srs.SRSCTC100,\n        [\n            ch_names_query,\n            f\"{channel.replace(' ', '')}.stats = \\\"{value_inst}\\\"\",\n            f\"{channel.replace(' ', '')}.stats?\",\n        ],\n        [ch_names_str, f\"{value_inst}\"],\n    ) as inst:\n        with inst._error_checking_disabled():\n            ch = inst.channel[channel]\n            ch.stats_enabled = newval\n            assert ch.stats_enabled == newval\n\n\n@given(points=st.integers(min_value=2, max_value=6000))\ndef test_srsctc100_channel_stats_points(points):\n    \"\"\"Get / set stats points in valid range.\"\"\"\n    channel = ch_names[0]\n    with expected_protocol(\n        ik.srs.SRSCTC100,\n        [\n            ch_names_query,\n            f\"{channel.replace(' ', '')}.points = \\\"{points}\\\"\",\n            f\"{channel.replace(' ', '')}.points?\",\n        ],\n        [ch_names_str, f\"{points}\"],\n    ) as inst:\n        with inst._error_checking_disabled():\n            ch = inst.channel[channel]\n            ch.stats_points = points\n            assert ch.stats_points == points\n\n\ndef test_srsctc100_channel_average():\n    \"\"\"Get average measurement for given channel, unitful.\"\"\"\n    channel = ch_names[0]\n    unit = ik.srs.SRSCTC100._UNIT_NAMES[ch_units[0]]\n    value = 42\n    with expected_protocol(\n        ik.srs.SRSCTC100,\n        [\n            ch_names_query,\n            f\"{channel.replace(' ', '')}.average?\",\n            \"getOutput.units?\",\n            ch_names_query,\n        ],\n        [\n            ch_names_str,\n            f\"{value}\",\n            \",\".join(ch_units),\n            ch_names_str,\n        ],\n    ) as inst:\n        with inst._error_checking_disabled():\n            assert inst.channel[channel].average == u.Quantity(value, unit)\n\n\ndef test_srsctc100_channel_std_dev():\n    \"\"\"Get standard deviation for given channel, unitful.\"\"\"\n    channel = ch_names[0]\n    unit = ik.srs.SRSCTC100._UNIT_NAMES[ch_units[0]]\n    value = 42\n    with expected_protocol(\n        ik.srs.SRSCTC100,\n        [\n            ch_names_query,\n            f\"{channel.replace(' ', '')}.SD?\",\n            \"getOutput.units?\",\n            ch_names_query,\n        ],\n        [\n            ch_names_str,\n            f\"{value}\",\n            \",\".join(ch_units),\n            ch_names_str,\n        ],\n    ) as inst:\n        with inst._error_checking_disabled():\n            assert inst.channel[channel].std_dev == u.Quantity(value, unit)\n\n\n@pytest.mark.parametrize(\"channel\", ch_names)\ndef test_get_log_point(channel):\n    \"\"\"Get a log point and include a unit query.\"\"\"\n    channel = ch_names[0]\n    unit = ik.srs.SRSCTC100._UNIT_NAMES[ch_name_unit_dict[channel]]\n    values = (13, 42)\n    which = \"first\"\n    values_out = (\n        u.Quantity(float(values[0]), u.ms),\n        u.Quantity(float(values[1]), unit),\n    )\n    with expected_protocol(\n        ik.srs.SRSCTC100,\n        [\n            ch_names_query,\n            \"getOutput.units?\",\n            ch_names_query,\n            f\"getLog.xy {channel}, {which}\",\n        ],\n        [ch_names_str, \",\".join(ch_units), ch_names_str, f\"{values[0]},{values[1]}\"],\n    ) as inst:\n        with inst._error_checking_disabled():\n            assert inst.channel[channel].get_log_point(which=which) == values_out\n\n\ndef test_get_log_point_with_unit():\n    \"\"\"Get a log point and include a unit query.\"\"\"\n    channel = ch_names[0]\n    unit = ik.srs.SRSCTC100._UNIT_NAMES[ch_units[0]]\n    values = (13, 42)\n    which = \"first\"\n    values_out = (\n        u.Quantity(float(values[0]), u.ms),\n        u.Quantity(float(values[1]), unit),\n    )\n    with expected_protocol(\n        ik.srs.SRSCTC100,\n        [ch_names_query, f\"getLog.xy {channel}, {which}\"],\n        [ch_names_str, f\"{values[0]},{values[1]}\"],\n    ) as inst:\n        with inst._error_checking_disabled():\n            assert (\n                inst.channel[channel].get_log_point(which=which, units=unit)\n                == values_out\n            )\n\n\n@pytest.mark.parametrize(\"channel\", ch_names)\ndef test_channel_get_log(channel):\n    \"\"\"Get the full log of a channel.\n\n    Leave error checking activated, because it is run at the end.\n    \"\"\"\n    # make some data\n    times = [0, 1, 2, 3]\n    values = [1.3, 2.4, 3.5, 4.6]\n\n    # variables\n    units = ik.srs.SRSCTC100._UNIT_NAMES[ch_name_unit_dict[channel]]\n    n_points = len(values)\n\n    # strings for error checking, sending and receiving\n    err_check_send = \"geterror?\"\n    err_check_reci = \"0,NO ERROR\"\n\n    # stich together strings to read all the values\n    str_log_next_send = \"\\n\".join(\n        [f\"getLog.xy {channel}, next\" for it in range(1, n_points)]\n    )\n    str_log_next_reci = \"\\n\".join(\n        [f\"{times[it]},{values[it]}\" for it in range(1, n_points)]\n    )\n\n    # make data to compare with\n    if numpy:\n        ts = u.Quantity(numpy.empty((n_points,)), u.ms)\n        temps = u.Quantity(numpy.empty((n_points,)), units)\n    else:\n        ts = [u.Quantity(0, u.ms)] * n_points\n        temps = [u.Quantity(0, units)] * n_points\n    for it, time in enumerate(times):\n        ts[it] = u.Quantity(time, u.ms)\n        temps[it] = u.Quantity(values[it], units)\n\n    if not numpy:\n        ts = tuple(ts)\n        temps = tuple(temps)\n\n    with expected_protocol(\n        ik.srs.SRSCTC100,\n        [\n            ch_names_query,\n            err_check_send,\n            \"getOutput.units?\",\n            err_check_send,\n            ch_names_query,\n            err_check_send,\n            f\"getLog.xy? {channel}\",\n            err_check_send,\n            f\"getLog.xy {channel}, first\",  # query first point\n            str_log_next_send,\n            err_check_send,\n        ],\n        [\n            ch_names_str,\n            err_check_reci,\n            \",\".join(ch_units),\n            err_check_reci,\n            ch_names_str,\n            err_check_reci,\n            f\"{n_points}\",\n            err_check_reci,\n            f\"{times[0]},{values[0]}\",\n            str_log_next_reci,\n            err_check_reci,\n        ],\n    ) as inst:\n        ch = inst.channel[channel]\n        ts_read, temps_read = ch.get_log()\n        # assert the data is correct\n        iterable_eq(ts, ts_read)\n        iterable_eq(temps, temps_read)\n\n\n# INSTRUMENT #\n\n\ndef test_srsctc100_init():\n    \"\"\"Initialize the SRS CTC-100 instrument.\"\"\"\n    with expected_protocol(ik.srs.SRSCTC100, [], []) as inst:\n        assert inst._do_errcheck\n\n\ndef test_srsctc100_channel_names():\n    \"\"\"Get current channel names from instrument.\"\"\"\n    with expected_protocol(ik.srs.SRSCTC100, [ch_names_query], [ch_names_str]) as inst:\n        with inst._error_checking_disabled():\n            assert inst._channel_names() == ch_names\n\n\ndef test_srsctc100_channel_units_all():\n    \"\"\"Get units for all channels.\"\"\"\n    with expected_protocol(\n        ik.srs.SRSCTC100,\n        [\"getOutput.units?\", ch_names_query],\n        [\",\".join(ch_units), ch_names_str],\n    ) as inst:\n        with inst._error_checking_disabled():\n            # create a unit dictionary to compare the return to\n            unit_dict = {\n                chan_name: ik.srs.SRSCTC100._UNIT_NAMES[unit_str]\n                for chan_name, unit_str in zip(ch_names, ch_units)\n            }\n            assert inst.channel_units() == unit_dict\n\n\ndef test_srsctc100_errcheck():\n    \"\"\"Error check - no error returned.\"\"\"\n    with expected_protocol(ik.srs.SRSCTC100, [\"geterror?\"], [\"0,NO ERROR\"]) as inst:\n        assert inst.errcheck() == 0\n\n\ndef test_srsctc100_errcheck_error_raised():\n    \"\"\"Error check - error raises.\"\"\"\n    with expected_protocol(ik.srs.SRSCTC100, [\"geterror?\"], [\"42,THE ANSWER\"]) as inst:\n        with pytest.raises(IOError) as exc_info:\n            inst.errcheck()\n        exc_msg = exc_info.value.args[0]\n        assert exc_msg == \"THE ANSWER\"\n\n\ndef test_srsctc100_error_checking_disabled_context():\n    \"\"\"Context dialogue to disable error checking.\"\"\"\n    with expected_protocol(ik.srs.SRSCTC100, [], []) as inst:\n        # by default, error checking enabled\n        with inst._error_checking_disabled():\n            assert not inst._do_errcheck\n\n        # default enabled again\n        assert inst._do_errcheck\n\n\n@given(figures=st.integers(min_value=0, max_value=6))\ndef test_srsctc100_display_figures(figures):\n    \"\"\"Get / set significant figures of display.\"\"\"\n    with expected_protocol(\n        ik.srs.SRSCTC100,\n        [f\"system.display.figures = {figures}\", \"system.display.figures?\"],\n        [f\"{figures}\"],\n    ) as inst:\n        with inst._error_checking_disabled():\n            inst.display_figures = figures\n            assert inst.display_figures == figures\n\n\n@given(figures=st.integers().filter(lambda x: x < 0 or x > 6))\ndef test_srsctc100_display_figures_value_error(figures):\n    \"\"\"Raise ValueError when setting an invalid number of figures.\"\"\"\n    with expected_protocol(ik.srs.SRSCTC100, [], []) as inst:\n        with inst._error_checking_disabled():\n            with pytest.raises(ValueError) as exc_info:\n                inst.display_figures = figures\n            exc_msg = exc_info.value.args[0]\n            assert (\n                exc_msg == \"Number of display figures must be an \"\n                \"integer from 0 to 6, inclusive.\"\n            )\n\n\n@pytest.mark.parametrize(\"newval\", (True, False))\ndef test_srsctc100_error_check_toggle(newval):\n    \"\"\"Get / set error check bool.\"\"\"\n    with expected_protocol(ik.srs.SRSCTC100, [], []) as inst:\n        inst.error_check_toggle = newval\n        assert inst.error_check_toggle == newval\n\n\ndef test_srsctc100_error_check_toggle_type_error():\n    \"\"\"Raise type error when error check toggle set with non-bool.\"\"\"\n    newval = 42\n    with expected_protocol(ik.srs.SRSCTC100, [], []) as inst:\n        with pytest.raises(TypeError):\n            inst.error_check_toggle = newval\n\n\ndef test_srsctc100_sendcmd():\n    \"\"\"Send a command and error check.\"\"\"\n    cmd = \"COMMAND\"\n    with expected_protocol(\n        ik.srs.SRSCTC100, [cmd, \"geterror?\"], [\"0,NO ERROR\"]\n    ) as inst:\n        inst.sendcmd(\"COMMAND\")\n\n\ndef test_srsctc100_query():\n    \"\"\"Send a query and error check.\"\"\"\n    cmd = \"COMMAND\"\n    answ = \"ANSWER\"\n    with expected_protocol(\n        ik.srs.SRSCTC100, [cmd, \"geterror?\"], [answ, \"0,NO ERROR\"]\n    ) as inst:\n        assert inst.query(\"COMMAND\") == answ\n\n\ndef test_srsctc100_clear_log():\n    \"\"\"Clear the log.\"\"\"\n    with expected_protocol(ik.srs.SRSCTC100, [\"System.Log.Clear yes\"], []) as inst:\n        with inst._error_checking_disabled():\n            inst.clear_log()\n"
  },
  {
    "path": "tests/test_srs/test_srsdg645.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the SRS DG645\n\"\"\"\n\n# IMPORTS ####################################################################\n\nimport pytest\n\nimport instruments as ik\nfrom instruments.abstract_instruments.comm import GPIBCommunicator\nfrom instruments.units import ureg as u\nfrom tests import expected_protocol, make_name_test, unit_eq\n\n# TESTS ######################################################################\n\n# pylint: disable=no-member,protected-access\n\ntest_srsdg645_name = make_name_test(ik.srs.SRSDG645)\n\n\n# CHANNELS #\n\n\ndef test_srsdg645_channel_init():\n    \"\"\"\n    _SRSDG645Channel: Ensure correct errors are raised during\n    initialization if not coming from a DG class.\n    \"\"\"\n    with pytest.raises(TypeError):\n        ik.srs.srsdg645.SRSDG645.Channel(42, 0)\n\n\ndef test_srsdg645_channel_init_channel_value():\n    \"\"\"\n    _SRSDG645Channel: Ensure the correct channel value is used when\n    passing on a SRSDG645.Channels instance as the `chan` value.\n    \"\"\"\n    ddg = ik.srs.SRSDG645.open_test()  # test connection\n    chan = ik.srs.srsdg645.SRSDG645.Channels.B  # select a channel manually\n    assert ik.srs.srsdg645.SRSDG645.Channel(ddg, chan)._chan == 3\n\n\ndef test_srsdg645_channel_delay():\n    \"\"\"\n    SRSDG645: Get / set delay.\n    \"\"\"\n    with expected_protocol(\n        ik.srs.SRSDG645,\n        [\"DLAY?2\", \"DLAY 3,2,60\", \"DLAY 5,4,10\"],\n        [\"0,42\"],\n    ) as ddg:\n        ref, t = ddg.channel[\"A\"].delay\n        assert ref == ddg.Channels.T0\n        assert abs((t - u.Quantity(42, \"s\")).magnitude) < 1e5\n        ddg.channel[\"B\"].delay = (ddg.channel[\"A\"], u.Quantity(1, \"minute\"))\n        ddg.channel[\"D\"].delay = (ddg.channel[\"C\"], 10)\n\n\n# DG645 #\n\n\ndef test_srsdg645_init_gpib(mocker):\n    \"\"\"Initialize SRSDG645 with GPIB Communicator.\"\"\"\n    mock_gpib = mocker.MagicMock()\n    comm = GPIBCommunicator(mock_gpib, 1)\n    ik.srs.SRSDG645(comm)\n    assert comm.strip == 2\n\n\ndef test_srsdg645_output_level():\n    \"\"\"\n    SRSDG645: Checks getting/setting unitful output level.\n    \"\"\"\n    with expected_protocol(\n        ik.srs.SRSDG645,\n        [\n            \"LAMP? 1\",\n            \"LAMP 1,4.0\",\n        ],\n        [\"3.2\"],\n    ) as ddg:\n        unit_eq(ddg.output[\"AB\"].level_amplitude, u.Quantity(3.2, \"V\"))\n        ddg.output[\"AB\"].level_amplitude = 4.0\n\n\ndef test_srsdg645_output_offset():\n    \"\"\"\n    SRSDG645: Checks getting/setting unitful output offset.\n    \"\"\"\n    with expected_protocol(\n        ik.srs.SRSDG645,\n        [\n            \"LOFF? 1\",\n            \"LOFF 1,2.0\",\n        ],\n        [\"1.2\"],\n    ) as ddg:\n        unit_eq(ddg.output[\"AB\"].level_offset, u.Quantity(1.2, \"V\"))\n        ddg.output[\"AB\"].level_offset = 2.0\n\n\ndef test_srsdg645_output_polarity():\n    \"\"\"\n    SRSDG645: Checks getting/setting\n    \"\"\"\n    with expected_protocol(ik.srs.SRSDG645, [\"LPOL? 1\", \"LPOL 2,0\"], [\"1\"]) as ddg:\n        assert ddg.output[\"AB\"].polarity == ddg.LevelPolarity.positive\n        ddg.output[\"CD\"].polarity = ddg.LevelPolarity.negative\n\n\ndef test_srsdg645_output_polarity_raise_type_error():\n    \"\"\"\n    SRSDG645: Polarity setter with wrong input - raise type error.\n    \"\"\"\n    with expected_protocol(ik.srs.SRSDG645, [], []) as ddg:\n        with pytest.raises(TypeError):\n            ddg.output[\"AB\"].polarity = 1\n\n\ndef test_srsdg645_display():\n    \"\"\"\n    SRSDG645: Set / get display mode.\n    \"\"\"\n    with expected_protocol(ik.srs.SRSDG645, [\"DISP?\", \"DISP 0,0\"], [\"12,3\"]) as ddg:\n        assert ddg.display == (ddg.DisplayMode.channel_levels, ddg.Channels.B)\n        ddg.display = (ddg.DisplayMode.trigger_rate, ddg.Channels.T0)\n\n\ndef test_srsdg645_enable_adv_triggering():\n    \"\"\"\n    SRSDG645: Set / get if advanced triggering is enabled.\n    \"\"\"\n    with expected_protocol(ik.srs.SRSDG645, [\"ADVT?\", \"ADVT 1\"], [\"0\"]) as ddg:\n        assert not ddg.enable_adv_triggering\n        ddg.enable_adv_triggering = True\n\n\ndef test_srsdg645_trigger_rate():\n    \"\"\"\n    SRSDG645: Set / get trigger rate.\n    \"\"\"\n    with expected_protocol(\n        ik.srs.SRSDG645, [\"TRAT?\", \"TRAT 10000\", \"TRAT 1000\"], [\"+1000.000000\"]\n    ) as ddg:\n        assert ddg.trigger_rate == u.Quantity(1000, u.Hz)\n        ddg.trigger_rate = 10000\n        ddg.trigger_rate = u.Quantity(1000, u.Hz)  # unitful send\n\n\ndef test_srsdg645_trigger_source():\n    \"\"\"\n    SRSDG645: Set / get trigger source.\n    \"\"\"\n    with expected_protocol(ik.srs.SRSDG645, [\"TSRC?\", \"TSRC 1\"], [\"0\"]) as ddg:\n        assert ddg.trigger_source == ddg.TriggerSource.internal\n        ddg.trigger_source = ddg.TriggerSource.external_rising\n\n\ndef test_srsdg645_holdoff():\n    \"\"\"\n    SRSDG645: Set / get hold off.\n    \"\"\"\n    with expected_protocol(\n        ik.srs.SRSDG645, [\"HOLD?\", \"HOLD 0\", \"HOLD 0.01\"], [\"+0.001001000000\"]\n    ) as ddg:\n        assert u.Quantity(1001, u.us) == ddg.holdoff\n        ddg.holdoff = 0\n        ddg.holdoff = u.Quantity(10, u.ms)  # unitful hold off\n\n\ndef test_srsdg645_enable_burst_mode():\n    \"\"\"\n    SRSDG645: Checks getting/setting of enabling burst mode.\n    \"\"\"\n    with expected_protocol(ik.srs.SRSDG645, [\"BURM?\", \"BURM 1\"], [\"0\"]) as ddg:\n        assert ddg.enable_burst_mode is False\n        ddg.enable_burst_mode = True\n\n\ndef test_srsdg645_enable_burst_t0_first():\n    \"\"\"\n    SRSDG645: Checks getting/setting of enabling T0 output on first\n    in burst mode.\n    \"\"\"\n    with expected_protocol(ik.srs.SRSDG645, [\"BURT?\", \"BURT 1\"], [\"0\"]) as ddg:\n        assert ddg.enable_burst_t0_first is False\n        ddg.enable_burst_t0_first = True\n\n\ndef test_srsdg645_burst_count():\n    \"\"\"\n    SRSDG645: Checks getting/setting of enabling T0 output on first\n    in burst mode.\n    \"\"\"\n    with expected_protocol(ik.srs.SRSDG645, [\"BURC?\", \"BURC 42\"], [\"10\"]) as ddg:\n        assert ddg.burst_count == 10\n        ddg.burst_count = 42\n\n\ndef test_srsdg645_burst_period():\n    \"\"\"\n    SRSDG645: Checks getting/setting of enabling T0 output on first\n    in burst mode.\n    \"\"\"\n    with expected_protocol(\n        ik.srs.SRSDG645, [\"BURP?\", \"BURP 13\", \"BURP 0.1\"], [\"100E-9\"]\n    ) as ddg:\n        unit_eq(ddg.burst_period, u.Quantity(100, \"ns\").to(u.sec))\n        ddg.burst_period = u.Quantity(13, \"s\")\n        ddg.burst_period = 0.1\n\n\ndef test_srsdg645_burst_delay():\n    \"\"\"\n    SRSDG645: Checks getting/setting of enabling T0 output on first\n    in burst mode.\n    \"\"\"\n    with expected_protocol(\n        ik.srs.SRSDG645, [\"BURD?\", \"BURD 42\", \"BURD 0.1\"], [\"0\"]\n    ) as ddg:\n        unit_eq(ddg.burst_delay, u.Quantity(0, \"s\"))\n        ddg.burst_delay = u.Quantity(42, \"s\")\n        ddg.burst_delay = 0.1\n"
  },
  {
    "path": "tests/test_sunpower/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_sunpower/test_cryotel_gt.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the Sunpower CryoTel GT\n\"\"\"\n\n# IMPORTS ####################################################################\n\nimport pytest\n\nimport instruments as ik\nfrom instruments.abstract_instruments.comm import GPIBCommunicator\nfrom instruments.units import ureg as u\nfrom tests import expected_protocol, make_name_test, unit_eq\n\n# TESTS ######################################################################\n\n# PROPERTIES #\n\n\ndef test_at_temperature_band():\n    \"\"\"Set/ get the at_temperature_band property of the CryoTel GT.\"\"\"\n    with expected_protocol(\n        ik.sunpower.CryoTelGT,\n        [\"SET TBAND\", \"SET TBAND=0.07\", \"SET TBAND\"],\n        [\"SET TBAND\", \"0.500\", \"SET TBAND=0.07\", \"0.07\", \"SET TBAND\", \"0.07\"],\n        sep=\"\\r\",\n    ) as ct:\n        assert ct.at_temperature_band == 0.5 * u.K\n        ct.at_temperature_band = 0.07 * u.K\n        assert ct.at_temperature_band == 0.07 * u.K\n\n\ndef test_control_mode():\n    \"\"\"Set/ get the control_mode property of the CryoTel GT.\"\"\"\n    with expected_protocol(\n        ik.sunpower.CryoTelGT,\n        [\"SET PID\", \"SET PID=0\", \"SET PID\"],\n        [\"SET PID\", \"2\", \"SET PID=0\", \"0\", \"SET PID\", \"0\"],\n        sep=\"\\r\",\n    ) as ct:\n        assert ct.control_mode == ct.ControlMode.TEMPERATURE\n        ct.control_mode = ct.ControlMode.POWER\n        assert ct.control_mode == ct.ControlMode.POWER\n\n        with pytest.raises(ValueError):\n            ct.control_mode = \"invalid_mode\"\n\n\ndef test_errors():\n    \"\"\"Get the error codes of the CryoTel GT and return error strings.\"\"\"\n    with expected_protocol(\n        ik.sunpower.CryoTelGT,\n        [\"ERROR\", \"ERROR\", \"ERROR\"],\n        [\"ERROR\", \"100000\", \"ERROR\", \"000000\", \"ERROR\", \"011001\"],\n        sep=\"\\r\",\n    ) as ct:\n        assert ct.errors == [\"Temperature Sensor Error\"]\n        assert ct.errors == []\n        assert ct.errors == [\n            \"Over Current\",\n            \"Non-volatile Memory Error\",\n            \"Watchdog Error\",\n        ]\n\n\ndef test_ki():\n    \"\"\"Set/get the ki property of the CryoTel GT.\"\"\"\n    with expected_protocol(\n        ik.sunpower.CryoTelGT,\n        [\"SET KI\", \"SET KI=0.10\", \"SET KI\"],\n        [\"SET KI\", \"5.0\", \"SET KI=0.10\", \"0.1\", \"SET KI\", \"0.1\"],\n        sep=\"\\r\",\n    ) as ct:\n        assert ct.ki == 5.0\n        ct.ki = 0.1\n        assert ct.ki == 0.1\n\n\ndef test_kp():\n    \"\"\"Set/get the kp property of the CryoTel GT.\"\"\"\n    with expected_protocol(\n        ik.sunpower.CryoTelGT,\n        [\"SET KP\", \"SET KP=0.10\", \"SET KP\"],\n        [\"SET KP\", \"0.5\", \"SET KP=0.10\", \"0.10\", \"SET KP\", \"0.10\"],\n        sep=\"\\r\",\n    ) as ct:\n        assert ct.kp == 0.5\n        ct.kp = 0.1\n        assert ct.kp == 0.1\n\n\ndef test_power():\n    \"\"\"Get the current power of the CryoTel GT.\"\"\"\n    with expected_protocol(\n        ik.sunpower.CryoTelGT,\n        [\"P\", \"P\"],\n        [\"P\", \"0.00\", \"P\", \"500.0\"],\n        sep=\"\\r\",\n    ) as ct:\n        assert ct.power == 0.0 * u.W\n        assert ct.power == 500 * u.W\n\n\ndef test_power_current_and_limits():\n    \"\"\"Get the current power and power limits of the CryoTel GT.\"\"\"\n    with expected_protocol(\n        ik.sunpower.CryoTelGT,\n        [\"E\"],\n        [\"P\", \"0.00\", \"500.00\", \"1000.00\"],\n        sep=\"\\r\",\n    ) as ct:\n        assert ct.power_current_and_limits == (0.0 * u.W, 500 * u.W, 1000 * u.W)\n\n\ndef test_power_max():\n    \"\"\"Get/set the maximum user power.\"\"\"\n    with expected_protocol(\n        ik.sunpower.CryoTelGT,\n        [\"SET MAX\", \"SET MAX=100.00\", \"SET MAX\"],\n        [\"SET MAX\", \"10.0\", \"SET MAX=100.00\", \"100.0\", \"SET MAX\", \"100.0\"],\n        sep=\"\\r\",\n    ) as ct:\n        assert ct.power_max == 10 * u.W\n        ct.power_max = 100 * u.W\n        assert ct.power_max == 100 * u.W\n\n        with pytest.raises(ValueError):\n            ct.power_max = 1000 * u.W\n        with pytest.raises(ValueError):\n            ct.power_max = -10 * u.W\n\n\ndef test_power_min():\n    \"\"\"Get/set the minimum user power.\"\"\"\n    with expected_protocol(\n        ik.sunpower.CryoTelGT,\n        [\"SET MIN\", \"SET MIN=10.00\", \"SET MIN\"],\n        [\"SET MIN\", \"0.0\", \"SET MIN=10.00\", \"10.0\", \"SET MIN\", \"10.0\"],\n        sep=\"\\r\",\n    ) as ct:\n        assert ct.power_min == 0 * u.W\n        ct.power_min = 10 * u.W\n        assert ct.power_min == 10 * u.W\n\n        with pytest.raises(ValueError):\n            ct.power_min = 1000 * u.W\n        with pytest.raises(ValueError):\n            ct.power_min = -10 * u.W\n\n\ndef test_power_setpoint():\n    \"\"\"Get/set the power setpoint of the CryoTel GT.\"\"\"\n    with expected_protocol(\n        ik.sunpower.CryoTelGT,\n        [\"SET PWOUT\", \"SET PWOUT=100.00\", \"SET PWOUT\"],\n        [\"SET PWOUT\", \"0.00\", \"SET PWOUT=100.00\", \"100.00\", \"SET PWOUT\", \"100.00\"],\n        sep=\"\\r\",\n    ) as ct:\n        assert ct.power_setpoint == 0 * u.W\n        ct.power_setpoint = 100 * u.W\n        assert ct.power_setpoint == 100 * u.W\n\n        with pytest.raises(ValueError):\n            ct.power_setpoint = 1000 * u.W\n        with pytest.raises(ValueError):\n            ct.power_setpoint = -10 * u.W\n\n\ndef test_serial_number():\n    \"\"\"Get the serial number of the CryoTel GT.\"\"\"\n    with expected_protocol(\n        ik.sunpower.CryoTelGT,\n        [\"SERIAL\"],\n        [\"SERIAL\", \"serial\", \"revision\"],\n        sep=\"\\r\",\n    ) as ct:\n        assert ct.serial_number == [\"serial\", \"revision\"]\n\n\ndef test_state():\n    \"\"\"Get the state of the CryoTel GT.\"\"\"\n    STATE_EXAMPLE = [\n        \"MODE = 002.00\",\n        \"TSTATM = 000.00\",\n        \"TSTAT = 000.00\",\n        \"SSTOPM = 000.00\",\n        \"SSTOP = 000.00\",\n        \"PID = 002.00\",\n        \"LOCK = 000.00\",\n        \"MAX = 300.00\",\n        \"MIN = 000.00\",\n        \"PWOUT = 000.00\",\n        \"TTARGET = 000.00\",\n        \"TBAND = 000.50\",\n        \"KI = 000.50\",\n        \"KP = 050.00000\",\n    ]\n    with expected_protocol(\n        ik.sunpower.CryoTelGT,\n        [\"STATE\"],\n        [\"STATE\"] + STATE_EXAMPLE,\n        sep=\"\\r\",\n    ) as ct:\n        assert ct.state == STATE_EXAMPLE\n\n\ndef test_temperature():\n    \"\"\"Get the temperature of the CryoTel GT.\"\"\"\n    with expected_protocol(\n        ik.sunpower.CryoTelGT,\n        [\"TC\", \"TC\"],\n        [\"TC\", \"77.00\", \"TC\", \"72.89\"],\n        sep=\"\\r\",\n    ) as ct:\n        assert ct.temperature == 77.0 * u.K\n        assert ct.temperature == 72.89 * u.K\n\n\ndef test_temperature_setpoint():\n    \"\"\"Get/set the temperature setpoint of the CryoTel GT.\"\"\"\n    with expected_protocol(\n        ik.sunpower.CryoTelGT,\n        [\"SET TTARGET\", \"SET TTARGET=100.00\", \"SET TTARGET\"],\n        [\n            \"SET TTARGET\",\n            \"0.00\",\n            \"SET TTARGET=100.00\",\n            \"100.00\",\n            \"SET TTARGET\",\n            \"100.00\",\n        ],\n        sep=\"\\r\",\n    ) as ct:\n        assert ct.temperature_setpoint == 0 * u.K\n        ct.temperature_setpoint = 100 * u.K\n        assert ct.temperature_setpoint == 100 * u.K\n\n\ndef test_thermostat():\n    \"\"\"Get/set the thermostat property of the CryoTel GT.\"\"\"\n    with expected_protocol(\n        ik.sunpower.CryoTelGT,\n        [\"SET TSTATM\", \"SET TSTATM=1\", \"SET TSTATM\"],\n        [\"SET TSTATM\", \"0\", \"SET TSTATM=1\", \"1\", \"SET TSTATM\", \"1\"],\n        sep=\"\\r\",\n    ) as ct:\n        assert ct.thermostat is False\n        ct.thermostat = True\n        assert ct.thermostat is True\n\n        with pytest.raises(ValueError):\n            ct.thermostat = \"invalid_value\"\n\n\ndef test_thermostat_status():\n    \"\"\"Get the thermostat status of the CryoTel GT.\"\"\"\n    with expected_protocol(\n        ik.sunpower.CryoTelGT,\n        [\"TSTAT\", \"TSTAT\"],\n        [\"TSTAT\", \"0.00\", \"TSTAT\", \"1.00\"],\n        sep=\"\\r\",\n    ) as ct:\n        assert ct.thermostat_status == ct.ThermostatStatus.OFF\n        assert ct.thermostat_status == ct.ThermostatStatus.ON\n\n\ndef test_stop():\n    \"\"\"Get/set the soft stop property of the CryoTel GT to turn it off.\"\"\"\n    with expected_protocol(\n        ik.sunpower.CryoTelGT,\n        [\"SET SSTOP\", \"SET SSTOP=1\", \"SET SSTOP\"],\n        [\"SET SSTOP\", \"0\", \"SET SSTOP=1\", \"1\", \"SET SSTOP\", \"1\"],\n        sep=\"\\r\",\n    ) as ct:\n        assert ct.stop is False\n        ct.stop = True\n        assert ct.stop is True\n\n        with pytest.raises(ValueError):\n            ct.stop = \"invalid_value\"\n\n\ndef test_stop_mode():\n    \"\"\"Get/set the stop mode of the CryoTel GT.\"\"\"\n    with expected_protocol(\n        ik.sunpower.CryoTelGT,\n        [\"SET SSTOPM\", \"SET SSTOPM=1\", \"SET SSTOPM\"],\n        [\"SET SSTOPM\", \"0\", \"SET SSTOPM=1\", \"1\", \"SET SSTOPM\", \"1\"],\n        sep=\"\\r\",\n    ) as ct:\n        assert ct.stop_mode == ct.StopMode.HOST\n        ct.stop_mode = ct.StopMode.DIGIO\n        assert ct.stop_mode == ct.StopMode.DIGIO\n\n        with pytest.raises(ValueError):\n            ct.stop_mode = \"invalid_mode\"\n\n\n# METHODS #\n\n\ndef test_reset():\n    \"\"\"Reset the CryoTel GT.\"\"\"\n    with expected_protocol(\n        ik.sunpower.CryoTelGT,\n        [\"RESET=F\"],\n        [\"RESET=F\", \"RESETTING TO FACTORY DEFAULT...\", \"FACTORY RESET COMPLETE!\"],\n        sep=\"\\r\",\n    ) as ct:\n        ct.reset()\n\n\ndef test_save_control_mode():\n    \"\"\"Save the current control mode of the CryoTel GT.\"\"\"\n    with expected_protocol(\n        ik.sunpower.CryoTelGT,\n        [\"SAVE PID\"],\n        [\"SAVE PID\"],\n        sep=\"\\r\",\n    ) as ct:\n        ct.save_control_mode()\n\n\ndef test_query_warning():\n    \"\"\"Raise a warning if a value was not set because not accepted.\"\"\"\n    with expected_protocol(\n        ik.sunpower.CryoTelGT,\n        [\"SET TTARGET=0.00\"],\n        [\"SET TTARGET=0.00\", \"77.00\"],\n        sep=\"\\r\",\n    ) as ct:\n        with pytest.warns(UserWarning) as warn:\n            ct.temperature_setpoint = 0 * u.K\n            assert \"Set value 0\" in warn[0].message.args[0]\n            assert \"returned value 77\" in warn[0].message.args[0]\n"
  },
  {
    "path": "tests/test_tektronix/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_tektronix/test_tekawg2000.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the Tektronix AGG2000 arbitrary wave generators.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom hypothesis import (\n    given,\n    strategies as st,\n)\nimport pytest\n\nimport instruments as ik\nfrom instruments.optional_dep_finder import numpy\nfrom tests import expected_protocol, make_name_test\nfrom instruments.units import ureg as u\n\n# TESTS #######################################################################\n\n# pylint: disable=protected-access\n\n\ntest_tekawg2000_name = make_name_test(ik.tektronix.TekAWG2000)\n\n\n# CHANNEL #\n\n\nchannels_to_try = range(2)\nchannels_to_try_id = [f\"CH{it}\" for it in channels_to_try]\n\n\n@pytest.mark.parametrize(\"channel\", channels_to_try, ids=channels_to_try_id)\ndef test_channel_init(channel):\n    \"\"\"Channel initialization.\"\"\"\n    with expected_protocol(ik.tektronix.TekAWG2000, [], []) as inst:\n        assert inst.channel[channel]._tek is inst\n        assert inst.channel[channel]._name == f\"CH{channel + 1}\"\n        assert inst.channel[channel]._old_dsrc is None\n\n\n@pytest.mark.parametrize(\"channel\", channels_to_try, ids=channels_to_try_id)\ndef test_channel_name(channel):\n    \"\"\"Get the name of the channel.\"\"\"\n    with expected_protocol(ik.tektronix.TekAWG2000, [], []) as inst:\n        assert inst.channel[channel].name == f\"CH{channel + 1}\"\n\n\n@pytest.mark.parametrize(\"channel\", channels_to_try, ids=channels_to_try_id)\n@given(\n    val_read=st.floats(min_value=0.02, max_value=2),\n    val_unitless=st.floats(min_value=0.02, max_value=2),\n    val_millivolt=st.floats(min_value=0.02, max_value=2000),\n)\ndef test_channel_amplitude(channel, val_read, val_unitless, val_millivolt):\n    \"\"\"Get / set amplitude.\"\"\"\n    val_read = u.Quantity(val_read, u.V)\n    val_unitful = u.Quantity(val_millivolt, u.mV)\n    with expected_protocol(\n        ik.tektronix.TekAWG2000,\n        [\n            f\"FG:CH{channel+1}:AMPL?\",\n            f\"FG:CH{channel+1}:AMPL {val_unitless}\",\n            f\"FG:CH{channel+1}:AMPL {val_unitful.to(u.V).magnitude}\",\n        ],\n        [f\"{val_read.magnitude}\"],\n    ) as inst:\n        assert inst.channel[channel].amplitude == val_read\n        inst.channel[channel].amplitude = val_unitless\n        inst.channel[channel].amplitude = val_unitful\n\n\n@pytest.mark.parametrize(\"channel\", channels_to_try, ids=channels_to_try_id)\n@given(\n    val_read=st.floats(min_value=0.02, max_value=2),\n    val_unitless=st.floats(min_value=0.02, max_value=2),\n    val_millivolt=st.floats(min_value=0.02, max_value=2000),\n)\ndef test_channel_offset(channel, val_read, val_unitless, val_millivolt):\n    \"\"\"Get / set offset.\"\"\"\n    val_read = u.Quantity(val_read, u.V)\n    val_unitful = u.Quantity(val_millivolt, u.mV)\n    with expected_protocol(\n        ik.tektronix.TekAWG2000,\n        [\n            f\"FG:CH{channel+1}:OFFS?\",\n            f\"FG:CH{channel+1}:OFFS {val_unitless}\",\n            f\"FG:CH{channel+1}:OFFS {val_unitful.to(u.V).magnitude}\",\n        ],\n        [f\"{val_read.magnitude}\"],\n    ) as inst:\n        assert inst.channel[channel].offset == val_read\n        inst.channel[channel].offset = val_unitless\n        inst.channel[channel].offset = val_unitful\n\n\n@pytest.mark.parametrize(\"channel\", channels_to_try, ids=channels_to_try_id)\n@given(\n    val_read=st.floats(min_value=1, max_value=200000),\n    val_unitless=st.floats(min_value=1, max_value=200000),\n    val_kilohertz=st.floats(min_value=1, max_value=200),\n)\ndef test_channel_frequency(channel, val_read, val_unitless, val_kilohertz):\n    \"\"\"Get / set offset.\"\"\"\n    val_read = u.Quantity(val_read, u.Hz)\n    val_unitful = u.Quantity(val_kilohertz, u.kHz)\n    with expected_protocol(\n        ik.tektronix.TekAWG2000,\n        [\n            f\"FG:FREQ?\",\n            f\"FG:FREQ {val_unitless}HZ\",\n            f\"FG:FREQ {val_unitful.to(u.Hz).magnitude}HZ\",\n        ],\n        [f\"{val_read.magnitude}\"],\n    ) as inst:\n        assert inst.channel[channel].frequency == val_read\n        inst.channel[channel].frequency = val_unitless\n        inst.channel[channel].frequency = val_unitful\n\n\n@pytest.mark.parametrize(\"channel\", channels_to_try, ids=channels_to_try_id)\n@given(polarity=st.sampled_from(ik.tektronix.TekAWG2000.Polarity))\ndef test_channel_polarity(channel, polarity):\n    \"\"\"Get / set polarity.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekAWG2000,\n        [f\"FG:CH{channel+1}:POL?\", f\"FG:CH{channel+1}:POL {polarity.value}\"],\n        [f\"{polarity.value}\"],\n    ) as inst:\n        assert inst.channel[channel].polarity == polarity\n        inst.channel[channel].polarity = polarity\n\n\n@pytest.mark.parametrize(\"channel\", channels_to_try, ids=channels_to_try_id)\ndef test_channel_polarity_type_mismatch(channel):\n    \"\"\"Raise a TypeError if a wrong type is selected as the polarity.\"\"\"\n    wrong_type = 42\n    with expected_protocol(ik.tektronix.TekAWG2000, [], []) as inst:\n        with pytest.raises(TypeError) as exc_info:\n            inst.channel[channel].polarity = wrong_type\n        exc_msg = exc_info.value.args[0]\n        assert (\n            exc_msg == f\"Polarity settings must be a `TekAWG2000.Polarity` \"\n            f\"value, got {type(wrong_type)} instead.\"\n        )\n\n\n@pytest.mark.parametrize(\"channel\", channels_to_try, ids=channels_to_try_id)\n@given(shape=st.sampled_from(ik.tektronix.TekAWG2000.Shape))\ndef test_channel_shape(channel, shape):\n    \"\"\"Get / set shape.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekAWG2000,\n        [f\"FG:CH{channel+1}:SHAP?\", f\"FG:CH{channel+1}:SHAP {shape.value}\"],\n        [f\"{shape.value}, 0\"],  # pulse duty cycle\n    ) as inst:\n        assert inst.channel[channel].shape == shape\n        inst.channel[channel].shape = shape\n\n\n@pytest.mark.parametrize(\"channel\", channels_to_try, ids=channels_to_try_id)\ndef test_channel_shape_type_mismatch(channel):\n    \"\"\"Raise a TypeError if a wrong type is selected as the shape.\"\"\"\n    wrong_type = 42\n    with expected_protocol(ik.tektronix.TekAWG2000, [], []) as inst:\n        with pytest.raises(TypeError) as exc_info:\n            inst.channel[channel].shape = wrong_type\n        exc_msg = exc_info.value.args[0]\n        assert (\n            exc_msg == f\"Shape settings must be a `TekAWG2000.Shape` \"\n            f\"value, got {type(wrong_type)} instead.\"\n        )\n\n\n# INSTRUMENT #\n\n\ndef test_waveform_name():\n    \"\"\"Get / set the waveform name.\"\"\"\n    file_name = \"test_file\"\n    with expected_protocol(\n        ik.tektronix.TekAWG2000,\n        [\"DATA:DEST?\", f'DATA:DEST \"{file_name}\"'],\n        [f\"{file_name}\"],\n    ) as inst:\n        assert inst.waveform_name == file_name\n        inst.waveform_name = file_name\n\n\ndef test_waveform_name_type_mismatch():\n    \"\"\"Raise a TypeError when something else than a string is given.\"\"\"\n    wrong_type = 42\n    with expected_protocol(ik.tektronix.TekAWG2000, [], []) as inst:\n        with pytest.raises(TypeError) as exc_info:\n            inst.waveform_name = wrong_type\n        exc_msg = exc_info.value.args[0]\n        assert exc_msg == \"Waveform name must be specified as a string.\"\n\n\n@pytest.mark.skipif(numpy is None, reason=\"Numpy required for this test\")\n@given(\n    yzero=st.floats(min_value=-5, max_value=5),\n    ymult=st.floats(min_value=0.00001),\n    xincr=st.floats(min_value=5e-8, max_value=1e-1),\n    waveform=st.lists(st.floats(min_value=0, max_value=1), min_size=1),\n)\ndef test_upload_waveform(yzero, ymult, xincr, waveform):\n    \"\"\"Upload a waveform from the PC to the instrument.\"\"\"\n    # prep waveform\n    waveform = numpy.array(waveform)\n    waveform_send = waveform * (2**12 - 1)\n    waveform_send = waveform_send.astype(\"<u2\").tobytes()\n    wfm_header_2 = str(len(waveform_send))\n    wfm_header_1 = len(wfm_header_2)\n    bin_str = f\"#{wfm_header_1}{wfm_header_2}{waveform_send}\"\n    with expected_protocol(\n        ik.tektronix.TekAWG2000,\n        [\n            f\"WFMP:YZERO {yzero}\",\n            f\"WFMP:YMULT {ymult}\",\n            f\"WFMP:XINCR {xincr}\",\n            f\"CURVE {bin_str}\",\n        ],\n        [],\n    ) as inst:\n        inst.upload_waveform(yzero, ymult, xincr, waveform)\n\n\n@pytest.mark.skipif(numpy is None, reason=\"Numpy required for this test\")\n@given(\n    yzero=st.floats(min_value=-5, max_value=5),\n    ymult=st.floats(min_value=0.00001),\n    xincr=st.floats(min_value=5e-8, max_value=1e-1),\n    waveform=st.lists(st.floats(min_value=0, max_value=1), min_size=1),\n)\ndef test_upload_waveform_type_mismatch(yzero, ymult, xincr, waveform):\n    \"\"\"Raise type error when types for method mismatched.\"\"\"\n    wrong_type_yzero = \"42\"\n    wrong_type_ymult = \"42\"\n    wrong_type_xincr = \"42\"\n    waveform_ndarray = numpy.array(waveform)\n    with expected_protocol(ik.tektronix.TekAWG2000, [], []) as inst:\n        # wrong yzero type\n        with pytest.raises(TypeError) as exc_info:\n            inst.upload_waveform(wrong_type_yzero, ymult, xincr, waveform_ndarray)\n        exc_msg = exc_info.value.args[0]\n        assert exc_msg == \"yzero must be specified as a float or int\"\n        # wrong ymult type\n        with pytest.raises(TypeError) as exc_info:\n            inst.upload_waveform(yzero, wrong_type_ymult, xincr, waveform_ndarray)\n        exc_msg = exc_info.value.args[0]\n        assert exc_msg == \"ymult must be specified as a float or int\"\n        # wrong xincr type\n        with pytest.raises(TypeError) as exc_info:\n            inst.upload_waveform(yzero, ymult, wrong_type_xincr, waveform_ndarray)\n        exc_msg = exc_info.value.args[0]\n        assert exc_msg == \"xincr must be specified as a float or int\"\n        # wrong waveform type\n        with pytest.raises(TypeError) as exc_info:\n            inst.upload_waveform(yzero, ymult, xincr, waveform)\n        exc_msg = exc_info.value.args[0]\n        assert exc_msg == \"waveform must be specified as a numpy array\"\n\n\n@pytest.mark.skipif(numpy is None, reason=\"Numpy required for this test\")\n@given(\n    yzero=st.floats(min_value=-5, max_value=5),\n    ymult=st.floats(min_value=0.00001),\n    xincr=st.floats(min_value=5e-8, max_value=1e-1),\n    waveform=st.lists(st.floats(min_value=0, max_value=1), min_size=1),\n)\ndef test_upload_waveform_wrong_max(yzero, ymult, xincr, waveform):\n    \"\"\"Raise ValueError when waveform maximum is too large.\"\"\"\n    waveform_wrong_max = numpy.array(waveform)\n    waveform_wrong_max[0] = 42.0\n    with expected_protocol(ik.tektronix.TekAWG2000, [], []) as inst:\n        with pytest.raises(ValueError) as exc_info:\n            inst.upload_waveform(yzero, ymult, xincr, waveform_wrong_max)\n        exc_msg = exc_info.value.args[0]\n        assert exc_msg == \"The max value for an element in waveform is 1.\"\n\n\n@pytest.mark.skipif(numpy is not None, reason=\"Numpy missing is required for this test\")\ndef test_upload_waveform_missing_numpy_raises_exception():\n    with expected_protocol(ik.tektronix.TekAWG2000, [], []) as inst:\n        with pytest.raises(ImportError):\n            inst.upload_waveform(0, 0, 0, [0])\n"
  },
  {
    "path": "tests/test_tektronix/test_tekdpo4104.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nTests for the Tektronix DPO 4104 oscilloscope.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom enum import Enum\nimport struct\n\nfrom hypothesis import (\n    given,\n    strategies as st,\n)\nimport pytest\n\nimport instruments as ik\nfrom instruments.optional_dep_finder import numpy\nfrom tests import (\n    expected_protocol,\n    iterable_eq,\n    make_name_test,\n)\n\n# TESTS #######################################################################\n\n# pylint: disable=protected-access\n\n\ntest_tekdpo4104_name = make_name_test(ik.tektronix.TekDPO4104)\n\n\n# INSTRUMENT #\n\n\ndef test_data_source():\n    \"\"\"Get / set data source for waveform transfer.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO4104,\n        [\n            \"DAT:SOU CH1\",  # set a string\n            \"DAT:SOU?\",\n            \"DAT:SOU REF2\",  # set value of an enum\n            \"DAT:SOU?\",\n            \"DAT:SOU MATH\",  # set a math channel\n            \"DAT:SOU?\",\n        ],\n        [\"CH1\", \"REF2\", \"MATH\"],\n    ) as inst:\n        # Channel as string\n        inst.data_source = \"CH1\"\n        assert inst.data_source == ik.tektronix.tekdpo4104.TekDPO4104.Channel(inst, 0)\n\n        # Reference channel as enum\n        class RefChannel(Enum):\n            \"\"\"Temporary reference channel enum.\"\"\"\n\n            channel = \"REF2\"\n\n        channel = RefChannel.channel.value\n        inst.data_source = RefChannel.channel\n        assert inst.data_source == ik.tektronix.tekdpo4104.TekDPO4104.DataSource(\n            inst, channel\n        )\n\n        # Set a math channel\n        math_ch = inst.math\n        inst.data_source = math_ch\n        assert inst.data_source == ik.tektronix.tekdpo4104.TekDPO4104.DataSource(\n            inst, math_ch.name\n        )\n\n\nh_record_lengths_possible = (1000, 10000, 100000, 1000000, 10000000)\n\n\n@pytest.mark.parametrize(\"aqu_length\", h_record_lengths_possible)\ndef test_aquisition_length(aqu_length):\n    \"\"\"Get / set acquisition length with valid values.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO4104,\n        [f\"HOR:RECO {aqu_length}\", \"HOR:RECO?\"],\n        [f\"{aqu_length}\"],\n    ) as inst:\n        inst.aquisition_length = aqu_length\n        assert inst.aquisition_length == aqu_length\n\n\ndef test_aquisition_running():\n    \"\"\"Get / set status of aquisition running.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO4104,\n        [\"ACQ:STATE?\", \"ACQ:STATE 0\", \"ACQ:STATE?\", \"ACQ:STATE 1\"],\n        [\"1\", \"0\"],\n    ) as inst:\n        assert inst.aquisition_running\n        inst.aquisition_running = False\n        assert not inst.aquisition_running\n        inst.aquisition_running = True\n\n\ndef test_aquisition_continuous():\n    \"\"\"Get / set status of aquisition continuous.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO4104,\n        [\"ACQ:STOPA?\", \"ACQ:STOPA SEQ\", \"ACQ:STOPA?\", \"ACQ:STOPA RUNST\"],\n        [\"RUNST\", \"SEQ\"],\n    ) as inst:\n        assert inst.aquisition_continuous\n        inst.aquisition_continuous = False\n        assert not inst.aquisition_continuous\n        inst.aquisition_continuous = True\n\n\n@pytest.mark.parametrize(\"data_width\", (1, 2))\ndef test_data_width(data_width):\n    \"\"\"Get / set data width with valid values.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO4104,\n        [\n            f\"DATA:WIDTH {data_width}\",\n            \"DATA:WIDTH?\",\n        ],\n        [f\"{data_width}\"],\n    ) as inst:\n        inst.data_width = data_width\n        assert inst.data_width == data_width\n\n\ndef test_data_width_out_of_range():\n    \"\"\"Raise Value Error if input value is out of range.\"\"\"\n    with expected_protocol(ik.tektronix.TekDPO4104, [], []) as inst:\n        with pytest.raises(ValueError) as exc_info:\n            inst.data_width = 42\n        exc_msg = exc_info.value.args[0]\n        assert exc_msg == \"Only one or two byte-width is supported.\"\n\n\n@given(offset=st.floats(min_value=-100, max_value=100))\ndef test_y_offset(offset):\n    \"\"\"Get / set Y offset of currently selected data source.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO4104, [f\"WFMP:YOF {offset}\", \"WFMP:YOF?\"], [f\"{offset}\"]\n    ) as inst:\n        inst.y_offset = offset\n        assert inst.y_offset == offset\n\n\ndef test_force_trigger():\n    \"\"\"Force a trigger event to occur.\"\"\"\n    with expected_protocol(ik.tektronix.TekDPO4104, [\"TRIG FORCE\"], []) as inst:\n        inst.force_trigger()\n\n\n# CHANNELS #\n\n\nchannels_to_try = range(4)\nchannels_to_try_ids = [f\"CH{it}\" for it in channels_to_try]\n\n\n@pytest.mark.parametrize(\"channel\", channels_to_try, ids=channels_to_try_ids)\ndef test_channel_init(channel):\n    \"\"\"Initialize a channel.\"\"\"\n    with expected_protocol(ik.tektronix.TekDPO4104, [], []) as inst:\n        assert inst.channel[channel]._idx == channel + 1\n\n\n@pytest.mark.parametrize(\"channel\", channels_to_try, ids=channels_to_try_ids)\n@pytest.mark.parametrize(\"coupling\", ik.tektronix.TekDPO4104.Coupling)\ndef test_channel_coupling(channel, coupling):\n    \"\"\"Initialize a channel.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO4104,\n        [f\"CH{channel + 1}:COUPL {coupling.value}\", f\"CH{channel + 1}:COUPL?\"],\n        [f\"{coupling.value}\"],\n    ) as inst:\n        inst.channel[channel].coupling = coupling\n        assert inst.channel[channel].coupling == coupling\n\n\ndef test_channel_coupling_invalid_value():\n    \"\"\"Raise Type Error when trying to set coupling with wrong value.\"\"\"\n    with expected_protocol(ik.tektronix.TekDPO4104, [], []) as inst:\n        wrong_type = \"DC\"\n        with pytest.raises(TypeError) as exc_info:\n            inst.channel[0].coupling = wrong_type\n        exc_msg = exc_info.value.args[0]\n        assert (\n            exc_msg == f\"Coupling setting must be a `TekDPO4104.Coupling`\"\n            f\" value, got {type(wrong_type)} instead.\"\n        )\n\n\n# DATA SOURCE #\n\n\nreference_sources_to_try = range(4)\nreference_sources_to_try_ids = [f\"REF{it}\" for it in reference_sources_to_try]\n\n\n@pytest.mark.parametrize(\n    \"ref\", reference_sources_to_try, ids=reference_sources_to_try_ids\n)\ndef test_data_source_ref_initialize(ref):\n    \"\"\"Initialize a ref data source.\"\"\"\n    with expected_protocol(ik.tektronix.TekDPO4104, [], []) as inst:\n        ref_source = inst.ref[ref]\n\n        # test instance\n        assert isinstance(ref_source, ik.tektronix.tekdpo4104.TekDPO4104.DataSource)\n\n        # test for parent\n        assert ref_source._tek is inst\n\n\ndef test_data_source_math_initialize():\n    \"\"\"Initialize a ref data source.\"\"\"\n    with expected_protocol(ik.tektronix.TekDPO4104, [], []) as inst:\n        math_source = inst.math\n\n        # test instance\n        assert isinstance(math_source, ik.tektronix.tekdpo4104.TekDPO4104.DataSource)\n\n        # test for parent\n        assert math_source._tek is inst\n\n\ndef test_data_source_name():\n    \"\"\"Get the name of the data source.\"\"\"\n    with expected_protocol(ik.tektronix.TekDPO4104, [], []) as inst:\n        assert inst.math.name == \"MATH\"\n\n\ndef test_data_source_equality_not_implemented():\n    \"\"\"Raise NotImplemented when comparing different types\"\"\"\n    with expected_protocol(ik.tektronix.TekDPO4104, [], []) as inst:\n        assert inst.math.__eq__(42) == NotImplemented\n\n\n@given(\n    values=st.lists(st.integers(min_value=-32768, max_value=32767), min_size=1),\n    ymult=st.integers(min_value=1, max_value=65536),\n    yzero=st.floats(min_value=-100, max_value=100),\n    xzero=st.floats(min_value=-10, max_value=10),\n    xincr=st.floats(min_value=1e-6, max_value=1),\n)\ndef test_data_source_read_waveform_bin(values, ymult, yzero, xzero, xincr):\n    \"\"\"Read the waveform of a data trace in bin format.\"\"\"\n    old_dat_source = 3\n    old_dat_stop = 100  # \"previous\" setting\n    # new values\n    channel = 0\n    data_width = 2  # use format '>h' for decoding\n    yoffs = 0  # already tested with hypothesis\n    # values packing\n    ptcnt = len(values)\n    values_packed = b\"\".join(struct.pack(\">h\", value) for value in values)\n    values_len = str(len(values_packed)).encode()\n    values_len_of_len = str(len(values_len)).encode()\n    with expected_protocol(\n        ik.tektronix.TekDPO4104,\n        [\n            \"DAT:SOU?\",  # old data source\n            f\"DAT:SOU CH{channel+1}\",\n            \"DAT:STOP?\",\n            f\"DAT:STOP {10**7}\",\n            \"DAT:ENC RIB\",  # set encoding\n            \"DATA:WIDTH?\",  # query data width\n            \"CURVE?\",  # get the data (in bin format)\n            \"WFMP:YOF?\",  # query yoffs\n            \"WFMP:YMU?\",  # query ymult\n            \"WFMP:YZE?\",  # query yzero\n            \"WFMP:XZE?\",  # query x zero\n            \"WFMP:XIN?\",  # retrieve x increments\n            \"WFMP:NR_P?\",  # retrieve number of points\n            f\"DAT:STOP {old_dat_stop}\",\n            f\"DAT:SOU CH{old_dat_source + 1}\",  # set back old data source\n        ],\n        [\n            f\"CH{old_dat_source+1}\",\n            f\"{old_dat_stop}\",\n            f\"{data_width}\",\n            b\"#\" + values_len_of_len + values_len + values_packed,\n            f\"{yoffs}\",\n            f\"{ymult}\",\n            f\"{yzero}\",\n            f\"{xzero}\",\n            f\"{xincr}\",\n            f\"{ptcnt}\",\n        ],\n    ) as inst:\n        x_read, y_read = inst.channel[channel].read_waveform()\n        if numpy:\n            x_calc = numpy.arange(ptcnt) * xincr + xzero\n            y_calc = ((numpy.array(values) - yoffs) * ymult) + yzero\n        else:\n            x_calc = tuple(float(val) * xincr + xzero for val in range(ptcnt))\n            y_calc = tuple(((float(val) - yoffs) * ymult) + yzero for val in values)\n        iterable_eq(x_read, x_calc)\n        iterable_eq(y_read, y_calc)\n\n\n@given(\n    values=st.lists(st.integers(min_value=-32768, max_value=32767), min_size=1),\n    ymult=st.integers(min_value=1, max_value=65536),\n    yzero=st.floats(min_value=-100, max_value=100),\n    xzero=st.floats(min_value=-10, max_value=10),\n    xincr=st.floats(min_value=1e-9, max_value=1),\n)\ndef test_data_source_read_waveform_ascii(values, ymult, yzero, xzero, xincr):\n    \"\"\"Read waveform back in ASCII format.\"\"\"\n    old_dat_source = 3\n    old_dat_stop = 100  # \"previous\" setting\n    # new values\n    channel = 0\n    yoffs = 0  # already tested with hypothesis\n    # transform values to strings\n    values_str = \",\".join([str(value) for value in values])\n    # calculated values\n    ptcnt = len(values)\n    with expected_protocol(\n        ik.tektronix.TekDPO4104,\n        [\n            \"DAT:SOU?\",  # old data source\n            f\"DAT:SOU CH{channel + 1}\",\n            \"DAT:STOP?\",\n            f\"DAT:STOP {10**7}\",\n            \"DAT:ENC ASCI\",  # set encoding\n            \"CURVE?\",  # get the data (in bin format)\n            \"WFMP:YOF?\",\n            \"WFMP:YMU?\",  # query y-offset\n            \"WFMP:YZE?\",  # query y zero\n            \"WFMP:XZE?\",  # query x zero\n            \"WFMP:XIN?\",  # retrieve x increments\n            \"WFMP:NR_P?\",  # retrieve number of points\n            f\"DAT:STOP {old_dat_stop}\",\n            f\"DAT:SOU CH{old_dat_source + 1}\",  # set back old data source\n        ],\n        [\n            f\"CH{old_dat_source + 1}\",\n            f\"{old_dat_stop}\",\n            f\"{values_str}\",\n            f\"{yoffs}\",\n            f\"{ymult}\",\n            f\"{yzero}\",\n            f\"{xzero}\",\n            f\"{xincr}\",\n            f\"{ptcnt}\",\n        ],\n    ) as inst:\n        # get the values from the instrument\n        x_read, y_read = inst.channel[channel].read_waveform(bin_format=False)\n\n        # manually calculate the values\n        if numpy:\n            raw = numpy.array(values_str.split(\",\"), dtype=float)\n            x_calc = numpy.arange(ptcnt) * xincr + xzero\n            y_calc = (raw - yoffs) * ymult + yzero\n        else:\n            x_calc = tuple(float(val) * xincr + xzero for val in range(ptcnt))\n            y_calc = tuple(((float(val) - yoffs) * ymult) + yzero for val in values)\n\n        # assert arrays are equal\n        iterable_eq(x_read, x_calc)\n        iterable_eq(y_read, y_calc)\n\n\n@given(offset=st.floats(min_value=-100, max_value=100))\ndef test_data_source_y_offset_get(offset):\n    \"\"\"Get y-offset from parent property.\"\"\"\n    old_dat_source = 2\n    channel = 0\n    with expected_protocol(\n        ik.tektronix.TekDPO4104,\n        [\n            \"DAT:SOU?\",  # old data source\n            f\"DAT:SOU CH{channel + 1}\",\n            \"WFMP:YOF?\",\n            f\"DAT:SOU CH{old_dat_source + 1}\",  # set back old data source\n        ],\n        [f\"CH{old_dat_source + 1}\", f\"{offset}\"],\n    ) as inst:\n        assert inst.channel[channel].y_offset == offset\n\n\n@given(offset=st.floats(min_value=-100, max_value=100))\ndef test_data_source_y_offset_set(offset):\n    \"\"\"Set y-offset from parent property.\"\"\"\n    old_dat_source = 2\n    channel = 0\n    with expected_protocol(\n        ik.tektronix.TekDPO4104,\n        [\n            \"DAT:SOU?\",  # old data source\n            f\"DAT:SOU CH{channel + 1}\",\n            f\"WFMP:YOF {offset}\",\n            f\"DAT:SOU CH{old_dat_source + 1}\",  # set back old data source\n        ],\n        [\n            f\"CH{old_dat_source + 1}\",\n        ],\n    ) as inst:\n        inst.channel[channel].y_offset = offset\n\n\ndef test_data_source_y_offset_set_old_data_source_same():\n    \"\"\"Set y-offset from parent property, old data source same.\n\n    Test one case of setting a data source where the old data source\n    and the new one is the same. Use y_offset for this test.\n    \"\"\"\n    offset = 0\n    old_dat_source = 0\n    channel = 0\n    with expected_protocol(\n        ik.tektronix.TekDPO4104,\n        [\n            \"DAT:SOU?\",  # old data source\n            f\"WFMP:YOF {offset}\",\n        ],\n        [\n            f\"CH{old_dat_source + 1}\",\n        ],\n    ) as inst:\n        inst.channel[channel].y_offset = offset\n"
  },
  {
    "path": "tests/test_tektronix/test_tekdpo70000.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nTests for the Tektronix DPO 70000 oscilloscope.\n\"\"\"\n\n# IMPORTS #####################################################################\n\nimport struct\nimport time\n\nfrom hypothesis import (\n    given,\n    strategies as st,\n)\nimport pytest\n\nimport instruments as ik\nfrom instruments.optional_dep_finder import numpy\nfrom tests import (\n    expected_protocol,\n    iterable_eq,\n    make_name_test,\n    unit_eq,\n)\nfrom instruments.units import ureg as u\n\n# TESTS #######################################################################\n\n# pylint: disable=too-many-lines,protected-access\n\n\ntest_tekdpo70000_name = make_name_test(ik.tektronix.TekDPO70000)\n\n\n# STATIC METHOD #\n\n\n@pytest.mark.parametrize(\"binary_format\", ik.tektronix.TekDPO70000.BinaryFormat)\n@pytest.mark.parametrize(\"byte_order\", ik.tektronix.TekDPO70000.ByteOrder)\n@pytest.mark.parametrize(\"n_bytes\", (1, 2, 4, 8))\ndef test_dtype(binary_format, byte_order, n_bytes):\n    \"\"\"Return the formatted format name, depending on settings.\"\"\"\n    binary_format_dict = {\n        ik.tektronix.TekDPO70000.BinaryFormat.int: \"i\",\n        ik.tektronix.TekDPO70000.BinaryFormat.uint: \"u\",\n        ik.tektronix.TekDPO70000.BinaryFormat.float: \"f\",\n    }\n    byte_order_dict = {\n        ik.tektronix.TekDPO70000.ByteOrder.big_endian: \">\",\n        ik.tektronix.TekDPO70000.ByteOrder.little_endian: \"<\",\n    }\n    value_expected = (\n        f\"{byte_order_dict[byte_order]}\"\n        f\"{n_bytes}\"\n        f\"{binary_format_dict[binary_format]}\"\n    )\n    with expected_protocol(ik.tektronix.TekDPO70000, [], []) as inst:\n        assert inst._dtype(binary_format, byte_order, n_bytes) == value_expected\n\n\n# DATA SOURCE - TESTED WITH CHANNELS #\n\n\ndef test_data_source_name():\n    \"\"\"Query the name of a data source.\"\"\"\n    channel = 0\n    with expected_protocol(ik.tektronix.TekDPO70000, [], []) as inst:\n        assert inst.channel[channel].name == f\"CH{channel+1}\"\n\n\n@pytest.mark.parametrize(\"channel\", [it for it in range(4)])\n@given(\n    values=st.lists(\n        st.integers(min_value=-2147483648, max_value=2147483647), min_size=1\n    )\n)\ndef test_data_source_read_waveform(channel, values):\n    \"\"\"Read waveform from data source, binary format only!\"\"\"\n    # select one set to test for:\n    binary_format = ik.tektronix.TekDPO70000.BinaryFormat.int  # go w/ values\n    byte_order = ik.tektronix.TekDPO70000.ByteOrder.big_endian\n    n_bytes = 4\n    # get the dtype\n    dtype_set = ik.tektronix.TekDPO70000._dtype(binary_format, byte_order, n_bytes=None)\n\n    # pack the values\n    values_packed = b\"\".join(struct.pack(dtype_set, value) for value in values)\n    values_len = str(len(values_packed)).encode()\n    values_len_of_len = str(len(values_len)).encode()\n    # scale the values\n    scale = 1.0\n    position = 0.0\n    offset = 0.0\n    scaled_values = [\n        scale\n        * ((ik.tektronix.TekDPO70000.VERT_DIVS / 2) * float(v) / (2**15) - position)\n        + offset\n        for v in values\n    ]\n    if numpy:\n        values = numpy.array(values)\n        scaled_values = (\n            scale\n            * (\n                (ik.tektronix.TekDPO70000.VERT_DIVS / 2)\n                * values.astype(float)\n                / (2**15)\n                - position\n            )\n            + offset\n        )\n\n    # run through the instrument\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [\n            \"DAT:SOU?\",  # query data source\n            \"DAT:ENC FAS\",  # fastest encoding\n            \"WFMO:BYT_N?\",  # get n_bytes\n            \"WFMO:BN_F?\",  # outgoing binary format\n            \"WFMO:BYT_O?\",  # outgoing byte order\n            \"CURV?\",  # query data\n            f\"CH{channel + 1}:SCALE?\",  # scale raw data\n            f\"CH{channel + 1}:POS?\",\n            f\"CH{channel + 1}:OFFS?\",\n        ],\n        [\n            f\"CH{channel+1}\",\n            f\"{n_bytes}\",\n            f\"{binary_format.value}\",\n            f\"{byte_order.value}\",\n            b\"#\" + values_len_of_len + values_len + values_packed,\n            f\"{scale}\",\n            f\"{position}\",\n            f\"{offset}\",\n        ],\n    ) as inst:\n        # query waveform\n        actual_waveform = inst.channel[channel].read_waveform()\n        expected_waveform = tuple(v * u.V for v in scaled_values)\n        if numpy:\n            expected_waveform = scaled_values * u.V\n        iterable_eq(actual_waveform, expected_waveform)\n\n\ndef test_data_source_read_waveform_with_old_data_source():\n    \"\"\"Read waveform from data, old data source present!\"\"\"\n    channel = 0  # multiple channels already tested above\n    # select one set to test for:\n    binary_format = ik.tektronix.TekDPO70000.BinaryFormat.int  # go w/ values\n    byte_order = ik.tektronix.TekDPO70000.ByteOrder.big_endian\n    n_bytes = 4\n    # get the dtype\n    dtype_set = ik.tektronix.TekDPO70000._dtype(binary_format, byte_order, n_bytes=None)\n\n    # pack the values\n    values = range(10)\n    if numpy:\n        values = numpy.arange(10)\n    values_packed = b\"\".join(struct.pack(dtype_set, value) for value in values)\n    values_len = str(len(values_packed)).encode()\n    values_len_of_len = str(len(values_len)).encode()\n    # scale the values\n    scale = 1.0\n    position = 0.0\n    offset = 0.0\n    scaled_values = [\n        scale\n        * ((ik.tektronix.TekDPO70000.VERT_DIVS / 2) * float(v) / (2**15) - position)\n        + offset\n        for v in values\n    ]\n    if numpy:\n        scaled_values = (\n            scale\n            * (\n                (ik.tektronix.TekDPO70000.VERT_DIVS / 2)\n                * values.astype(float)\n                / (2**15)\n                - position\n            )\n            + offset\n        )\n\n    # old data source to set manually - ensure it is set back later\n    old_dsrc = \"MATH1\"\n\n    # run through the instrument\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [\n            \"DAT:SOU?\",  # query data source\n            f\"DAT:SOU CH{channel + 1}\",  # set current data source\n            \"DAT:ENC FAS\",  # fastest encoding\n            \"WFMO:BYT_N?\",  # get n_bytes\n            \"WFMO:BN_F?\",  # outgoing binary format\n            \"WFMO:BYT_O?\",  # outgoing byte order\n            \"CURV?\",  # query data\n            f\"CH{channel + 1}:SCALE?\",  # scale raw data\n            f\"CH{channel + 1}:POS?\",\n            f\"CH{channel + 1}:OFFS?\",\n            f\"DAT:SOU {old_dsrc}\",\n        ],\n        [\n            old_dsrc,\n            f\"{n_bytes}\",\n            f\"{binary_format.value}\",\n            f\"{byte_order.value}\",\n            b\"#\" + values_len_of_len + values_len + values_packed,\n            f\"{scale}\",\n            f\"{position}\",\n            f\"{offset}\",\n        ],\n    ) as inst:\n        # query waveform\n        actual_waveform = inst.channel[channel].read_waveform()\n        expected_waveform = tuple(v * u.V for v in scaled_values)\n        if numpy:\n            expected_waveform = scaled_values * u.V\n        iterable_eq(actual_waveform, expected_waveform)\n\n\n# MATH #\n\n\n@pytest.mark.parametrize(\"math\", [it for it in range(4)])\ndef test_math_init(math):\n    \"\"\"Initialize a math channel.\"\"\"\n    with expected_protocol(ik.tektronix.TekDPO70000, [], []) as inst:\n        assert inst.math[math]._parent is inst\n        assert inst.math[math]._idx == math + 1\n\n\n@pytest.mark.parametrize(\"math\", [it for it in range(4)])\ndef test_math_sendcmd(math):\n    \"\"\"Send a command from a math channel.\"\"\"\n    cmd = \"TEST\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000, [f\"MATH{math+1}:{cmd}\"], []\n    ) as inst:\n        inst.math[math].sendcmd(cmd)\n\n\n@pytest.mark.parametrize(\"math\", [it for it in range(4)])\ndef test_math_query(math):\n    \"\"\"Query from a math channel.\"\"\"\n    cmd = \"TEST\"\n    answ = \"ANSWER\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000, [f\"MATH{math+1}:{cmd}\"], [answ]\n    ) as inst:\n        assert inst.math[math].query(cmd) == answ\n\n\n@given(\n    value=st.text(\n        alphabet=st.characters(blacklist_characters=\"\\n\", blacklist_categories=(\"Cs\",))\n    )\n)\ndef test_math_define(value):\n    \"\"\"Get / set a string operation from the Math mode.\"\"\"\n    math = 0\n    cmd = \"DEF\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f'MATH{math+1}:{cmd} \"{value}\"', f\"MATH{math+1}:{cmd}?\"],\n        [f'\"{value}\"'],\n    ) as inst:\n        inst.math[math].define = value\n        assert inst.math[math].define == value\n\n\n@pytest.mark.parametrize(\"value\", ik.tektronix.TekDPO70000.Math.FilterMode)\ndef test_math_filter_mode(value):\n    \"\"\"Get / set filter mode.\"\"\"\n    math = 0\n    cmd = \"FILT:MOD\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"MATH{math + 1}:{cmd} {value.value}\", f\"MATH{math + 1}:{cmd}?\"],\n        [f\"{value.value}\"],\n    ) as inst:\n        inst.math[math].filter_mode = value\n        assert inst.math[math].filter_mode == value\n\n\n@given(value=st.floats(min_value=0))\ndef test_math_filter_risetime(value):\n    \"\"\"Get / set filter risetime.\"\"\"\n    math = 0\n    cmd = \"FILT:RIS\"\n    value_unitful = u.Quantity(value, u.s)\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [\n            f\"MATH{math + 1}:{cmd} {value:e}\",\n            f\"MATH{math + 1}:{cmd} {value:e}\",\n            f\"MATH{math + 1}:{cmd}?\",\n        ],\n        [f\"{value}\"],\n    ) as inst:\n        inst.math[math].filter_risetime = value\n        inst.math[math].filter_risetime = value_unitful\n        unit_eq(inst.math[math].filter_risetime, value_unitful)\n\n\n@given(\n    value=st.text(\n        alphabet=st.characters(blacklist_characters=\"\\n\", blacklist_categories=(\"Cs\",))\n    )\n)\ndef test_math_label(value):\n    \"\"\"Get / set a label for the math channel.\"\"\"\n    math = 0\n    cmd = \"LAB:NAM\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f'MATH{math+1}:{cmd} \"{value}\"', f\"MATH{math+1}:{cmd}?\"],\n        [f'\"{value}\"'],\n    ) as inst:\n        inst.math[math].label = value\n        assert inst.math[math].label == value\n\n\n@given(\n    value=st.floats(\n        min_value=-ik.tektronix.TekDPO70000.HOR_DIVS,\n        max_value=ik.tektronix.TekDPO70000.HOR_DIVS,\n    )\n)\ndef test_math_label_xpos(value):\n    \"\"\"Get / set x position for label.\"\"\"\n    math = 0\n    cmd = \"LAB:XPOS\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"MATH{math + 1}:{cmd} {value:e}\", f\"MATH{math + 1}:{cmd}?\"],\n        [f\"{value}\"],\n    ) as inst:\n        inst.math[math].label_xpos = value\n        assert inst.math[math].label_xpos == value\n\n\n@given(\n    value=st.floats(\n        min_value=-ik.tektronix.TekDPO70000.VERT_DIVS,\n        max_value=ik.tektronix.TekDPO70000.VERT_DIVS,\n    )\n)\ndef test_math_label_ypos(value):\n    \"\"\"Get / set y position for label.\"\"\"\n    math = 0\n    cmd = \"LAB:YPOS\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"MATH{math + 1}:{cmd} {value:e}\", f\"MATH{math + 1}:{cmd}?\"],\n        [f\"{value}\"],\n    ) as inst:\n        inst.math[math].label_ypos = value\n        assert inst.math[math].label_ypos == value\n\n\n@given(value=st.integers(min_value=0))\ndef test_math_num_avg(value):\n    \"\"\"Get / set number of averages.\"\"\"\n    math = 0\n    cmd = \"NUMAV\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"MATH{math + 1}:{cmd} {value:e}\", f\"MATH{math + 1}:{cmd}?\"],\n        [f\"{value}\"],\n    ) as inst:\n        inst.math[math].num_avg = value\n        assert inst.math[math].num_avg == pytest.approx(value)\n\n\n@given(value=st.floats(min_value=0))\ndef test_math_spectral_center(value):\n    \"\"\"Get / set spectral center.\"\"\"\n    math = 0\n    cmd = \"SPEC:CENTER\"\n    value_unitful = u.Quantity(value, u.Hz)\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [\n            f\"MATH{math + 1}:{cmd} {value:e}\",\n            f\"MATH{math + 1}:{cmd} {value:e}\",\n            f\"MATH{math + 1}:{cmd}?\",\n        ],\n        [f\"{value}\"],\n    ) as inst:\n        inst.math[math].spectral_center = value\n        inst.math[math].spectral_center = value_unitful\n        unit_eq(inst.math[math].spectral_center, value_unitful)\n\n\n@given(value=st.floats(allow_nan=False))\ndef test_math_spectral_gatepos(value):\n    \"\"\"Get / set gate position.\"\"\"\n    math = 0\n    cmd = \"SPEC:GATEPOS\"\n    value_unitful = u.Quantity(value, u.s)\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [\n            f\"MATH{math + 1}:{cmd} {value:e}\",\n            f\"MATH{math + 1}:{cmd} {value:e}\",\n            f\"MATH{math + 1}:{cmd}?\",\n        ],\n        [f\"{value}\"],\n    ) as inst:\n        inst.math[math].spectral_gatepos = value\n        inst.math[math].spectral_gatepos = value_unitful\n        unit_eq(inst.math[math].spectral_gatepos, value_unitful)\n\n\n@given(value=st.floats(allow_nan=False))\ndef test_math_spectral_gatewidth(value):\n    \"\"\"Get / set gate width.\"\"\"\n    math = 0\n    cmd = \"SPEC:GATEWIDTH\"\n    value_unitful = u.Quantity(value, u.s)\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [\n            f\"MATH{math + 1}:{cmd} {value:e}\",\n            f\"MATH{math + 1}:{cmd} {value:e}\",\n            f\"MATH{math + 1}:{cmd}?\",\n        ],\n        [f\"{value}\"],\n    ) as inst:\n        inst.math[math].spectral_gatewidth = value\n        inst.math[math].spectral_gatewidth = value_unitful\n        unit_eq(inst.math[math].spectral_gatewidth, value_unitful)\n\n\n@pytest.mark.parametrize(\"value\", [True, False])\ndef test_math_spectral_lock(value):\n    \"\"\"Get / set spectral lock.\"\"\"\n    math = 0\n    cmd = \"SPEC:LOCK\"\n    value_io = \"ON\" if value else \"OFF\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"MATH{math + 1}:{cmd} {value_io}\", f\"MATH{math + 1}:{cmd}?\"],\n        [f\"{value_io}\"],\n    ) as inst:\n        inst.math[math].spectral_lock = value\n        assert inst.math[math].spectral_lock == value\n\n\n@pytest.mark.parametrize(\"value\", ik.tektronix.TekDPO70000.Math.Mag)\ndef test_math_spectral_mag(value):\n    \"\"\"Get / set spectral magnitude scaling.\"\"\"\n    math = 0\n    cmd = \"SPEC:MAG\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"MATH{math + 1}:{cmd} {value.value}\", f\"MATH{math + 1}:{cmd}?\"],\n        [f\"{value.value}\"],\n    ) as inst:\n        inst.math[math].spectral_mag = value\n        assert inst.math[math].spectral_mag == value\n\n\n@pytest.mark.parametrize(\"value\", ik.tektronix.TekDPO70000.Math.Phase)\ndef test_math_spectral_phase(value):\n    \"\"\"Get / set spectral phase unit.\"\"\"\n    math = 0\n    cmd = \"SPEC:PHASE\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"MATH{math + 1}:{cmd} {value.value}\", f\"MATH{math + 1}:{cmd}?\"],\n        [f\"{value.value}\"],\n    ) as inst:\n        inst.math[math].spectral_phase = value\n        assert inst.math[math].spectral_phase == value\n\n\n@given(value=st.floats(allow_nan=False))\ndef test_math_spectral_reflevel(value):\n    \"\"\"Get / set spectral reference level.\"\"\"\n    math = 0\n    cmd = \"SPEC:REFL\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"MATH{math + 1}:{cmd} {value:e}\", f\"MATH{math + 1}:{cmd}?\"],\n        [f\"{value}\"],\n    ) as inst:\n        inst.math[math].spectral_reflevel = value\n        assert inst.math[math].spectral_reflevel == value\n\n\n@given(value=st.floats(allow_nan=False))\ndef test_math_spectral_reflevel_offset(value):\n    \"\"\"Get / set spectral reference level offset.\"\"\"\n    math = 0\n    cmd = \"SPEC:REFLEVELO\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"MATH{math + 1}:{cmd} {value:e}\", f\"MATH{math + 1}:{cmd}?\"],\n        [f\"{value}\"],\n    ) as inst:\n        inst.math[math].spectral_reflevel_offset = value\n        assert inst.math[math].spectral_reflevel_offset == value\n\n\n@given(value=st.floats(min_value=0))\ndef test_math_spectral_resolution_bandwidth(value):\n    \"\"\"Get / set spectral resolution bandwidth.\"\"\"\n    math = 0\n    cmd = \"SPEC:RESB\"\n    value_unitful = u.Quantity(value, u.Hz)\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [\n            f\"MATH{math + 1}:{cmd} {value:e}\",\n            f\"MATH{math + 1}:{cmd} {value:e}\",\n            f\"MATH{math + 1}:{cmd}?\",\n        ],\n        [f\"{value}\"],\n    ) as inst:\n        inst.math[math].spectral_resolution_bandwidth = value\n        inst.math[math].spectral_resolution_bandwidth = value_unitful\n        unit_eq(inst.math[math].spectral_resolution_bandwidth, value_unitful)\n\n\n@given(value=st.floats(min_value=0))\ndef test_math_spectral_span(value):\n    \"\"\"Get / set frequency span of output data vector.\"\"\"\n    math = 0\n    cmd = \"SPEC:SPAN\"\n    value_unitful = u.Quantity(value, u.Hz)\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [\n            f\"MATH{math + 1}:{cmd} {value:e}\",\n            f\"MATH{math + 1}:{cmd} {value:e}\",\n            f\"MATH{math + 1}:{cmd}?\",\n        ],\n        [f\"{value}\"],\n    ) as inst:\n        inst.math[math].spectral_span = value\n        inst.math[math].spectral_span = value_unitful\n        unit_eq(inst.math[math].spectral_span, value_unitful)\n\n\n@given(value=st.floats(allow_nan=False))\ndef test_math_spectral_suppress(value):\n    \"\"\"Get / set spectral suppression value.\"\"\"\n    math = 0\n    cmd = \"SPEC:SUPP\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"MATH{math + 1}:{cmd} {value:e}\", f\"MATH{math + 1}:{cmd}?\"],\n        [f\"{value}\"],\n    ) as inst:\n        inst.math[math].spectral_suppress = value\n        assert inst.math[math].spectral_suppress == value\n\n\n@pytest.mark.parametrize(\"value\", [True, False])\ndef test_math_spectral_unwrap(value):\n    \"\"\"Get / set phase wrapping.\"\"\"\n    math = 0\n    cmd = \"SPEC:UNWR\"\n    value_io = \"ON\" if value else \"OFF\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"MATH{math + 1}:{cmd} {value_io}\", f\"MATH{math + 1}:{cmd}?\"],\n        [f\"{value_io}\"],\n    ) as inst:\n        inst.math[math].spectral_unwrap = value\n        assert inst.math[math].spectral_unwrap == value\n\n\n@pytest.mark.parametrize(\"value\", ik.tektronix.TekDPO70000.Math.SpectralWindow)\ndef test_math_spectral_window(value):\n    \"\"\"Get / set spectral window.\"\"\"\n    math = 0\n    cmd = \"SPEC:WIN\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"MATH{math + 1}:{cmd} {value.value}\", f\"MATH{math + 1}:{cmd}?\"],\n        [f\"{value.value}\"],\n    ) as inst:\n        inst.math[math].spectral_window = value\n        assert inst.math[math].spectral_window == value\n\n\n@given(value=st.floats(min_value=0))\ndef test_math_threshold(value):\n    \"\"\"Get / set threshold of math channel.\"\"\"\n    math = 0\n    cmd = \"THRESH\"\n    value_unitful = u.Quantity(value, u.V)\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [\n            f\"MATH{math + 1}:{cmd} {value:e}\",\n            f\"MATH{math + 1}:{cmd} {value:e}\",\n            f\"MATH{math + 1}:{cmd}?\",\n        ],\n        [f\"{value}\"],\n    ) as inst:\n        inst.math[math].threshhold = value\n        inst.math[math].threshhold = value_unitful\n        unit_eq(inst.math[math].threshhold, value_unitful)\n\n\n@given(\n    value=st.text(\n        alphabet=st.characters(blacklist_characters=\"\\n\", blacklist_categories=(\"Cs\",))\n    )\n)\ndef test_math_units(value):\n    \"\"\"Get / set a label for the units.\"\"\"\n    math = 0\n    cmd = \"UNITS\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f'MATH{math+1}:{cmd} \"{value}\"', f\"MATH{math+1}:{cmd}?\"],\n        [f'\"{value}\"'],\n    ) as inst:\n        inst.math[math].unit_string = value\n        assert inst.math[math].unit_string == value\n\n\n@pytest.mark.parametrize(\"value\", [True, False])\ndef test_math_autoscale(value):\n    \"\"\"Get / set if autoscale is enabled.\"\"\"\n    math = 0\n    cmd = \"VERT:AUTOSC\"\n    value_io = \"ON\" if value else \"OFF\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"MATH{math + 1}:{cmd} {value_io}\", f\"MATH{math + 1}:{cmd}?\"],\n        [f\"{value_io}\"],\n    ) as inst:\n        inst.math[math].autoscale = value\n        assert inst.math[math].autoscale == value\n\n\n@given(\n    value=st.floats(\n        min_value=-ik.tektronix.TekDPO70000.VERT_DIVS / 2,\n        max_value=ik.tektronix.TekDPO70000.VERT_DIVS / 2,\n    )\n)\ndef test_math_position(value):\n    \"\"\"Get / set spectral vertical position from center.\"\"\"\n    math = 0\n    cmd = \"VERT:POS\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"MATH{math + 1}:{cmd} {value:e}\", f\"MATH{math + 1}:{cmd}?\"],\n        [f\"{value}\"],\n    ) as inst:\n        inst.math[math].position = value\n        assert inst.math[math].position == value\n\n\n@given(value=st.floats(min_value=0))\ndef test_math_scale(value):\n    \"\"\"Get / set scale in volts per division.\"\"\"\n    math = 0\n    cmd = \"VERT:SCALE\"\n    value_unitful = u.Quantity(value, u.V)\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [\n            f\"MATH{math + 1}:{cmd} {value:e}\",\n            f\"MATH{math + 1}:{cmd} {value:e}\",\n            f\"MATH{math + 1}:{cmd}?\",\n        ],\n        [f\"{value}\"],\n    ) as inst:\n        inst.math[math].scale = value\n        inst.math[math].scale = value_unitful\n        unit_eq(inst.math[math].scale, value_unitful)\n\n\n@given(\n    values=st.lists(st.floats(min_value=-2147483648, max_value=2147483647), min_size=1)\n)\ndef test_math_scale_raw_data(values):\n    \"\"\"Return scaled raw data according to current settings.\"\"\"\n    math = 0\n    scale = 1.0 * u.V\n    position = -2.3\n    expected_value = tuple(\n        scale\n        * ((ik.tektronix.TekDPO70000.VERT_DIVS / 2) * float(v) / (2**15) - position)\n        for v in values\n    )\n    if numpy:\n        values = numpy.array(values)\n        expected_value = scale * (\n            (ik.tektronix.TekDPO70000.VERT_DIVS / 2) * values.astype(float) / (2**15)\n            - position\n        )\n\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"MATH{math + 1}:VERT:SCALE?\", f\"MATH{math + 1}:VERT:POS?\"],\n        [f\"{scale}\", f\"{position}\"],\n    ) as inst:\n        iterable_eq(inst.math[math]._scale_raw_data(values), expected_value)\n\n\n# CHANNEL #\n\n\n@pytest.mark.parametrize(\"channel\", [it for it in range(4)])\ndef test_channel_init(channel):\n    \"\"\"Initialize a channel.\"\"\"\n    with expected_protocol(ik.tektronix.TekDPO70000, [], []) as inst:\n        assert inst.channel[channel]._parent is inst\n        assert inst.channel[channel]._idx == channel + 1\n\n\n@pytest.mark.parametrize(\"channel\", [it for it in range(4)])\ndef test_channel_sendcmd(channel):\n    \"\"\"Send a command from a channel.\"\"\"\n    cmd = \"TEST\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000, [f\"CH{channel+1}:{cmd}\"], []\n    ) as inst:\n        inst.channel[channel].sendcmd(cmd)\n\n\n@pytest.mark.parametrize(\"channel\", [it for it in range(4)])\ndef test_channel_query(channel):\n    \"\"\"Send a query from a channel.\"\"\"\n    cmd = \"TEST\"\n    answ = \"ANSWER\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000, [f\"CH{channel+1}:{cmd}\"], [answ]\n    ) as inst:\n        assert inst.channel[channel].query(cmd) == answ\n\n\n@pytest.mark.parametrize(\"value\", ik.tektronix.TekDPO70000.Channel.Coupling)\ndef test_channel_coupling(value):\n    \"\"\"Get / set channel coupling.\"\"\"\n    channel = 0\n    cmd = \"COUP\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"CH{channel+1}:{cmd} {value.value}\", f\"CH{channel+1}:{cmd}?\"],\n        [f\"{value.value}\"],\n    ) as inst:\n        inst.channel[channel].coupling = value\n        assert inst.channel[channel].coupling == value\n\n\n@given(value=st.floats(min_value=0, max_value=30e9))\ndef test_channel_bandwidth(value):\n    \"\"\"Get / set bandwidth of a channel.\n\n    Test unitful and unitless setting.\n    \"\"\"\n    channel = 0\n    cmd = \"BAN\"\n    value_unitful = u.Quantity(value, u.Hz)\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [\n            f\"CH{channel + 1}:{cmd} {value:e}\",\n            f\"CH{channel + 1}:{cmd} {value:e}\",\n            f\"CH{channel + 1}:{cmd}?\",\n        ],\n        [f\"{value}\"],\n    ) as inst:\n        inst.channel[channel].bandwidth = value\n        inst.channel[channel].bandwidth = value_unitful\n        unit_eq(inst.channel[channel].bandwidth, value_unitful)\n\n\n@given(value=st.floats(min_value=-25e-9, max_value=25e-9))\ndef test_channel_deskew(value):\n    \"\"\"Get / set deskew time.\n\n    Test unitful and unitless setting.\n    \"\"\"\n    channel = 0\n    cmd = \"DESK\"\n    value_unitful = u.Quantity(value, u.s)\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [\n            f\"CH{channel + 1}:{cmd} {value:e}\",\n            f\"CH{channel + 1}:{cmd} {value:e}\",\n            f\"CH{channel + 1}:{cmd}?\",\n        ],\n        [f\"{value}\"],\n    ) as inst:\n        inst.channel[channel].deskew = value\n        inst.channel[channel].deskew = value_unitful\n        unit_eq(inst.channel[channel].deskew, value_unitful)\n\n\n@pytest.mark.parametrize(\"value\", [50, 1000000])\ndef test_channel_termination(value):\n    \"\"\"Get / set termination of channel.\n\n    Valid values are 50 Ohm or 1 MOhm. Try setting unitful and\n    unitless.\n    \"\"\"\n    channel = 0\n    cmd = \"TERM\"\n    value_unitful = u.Quantity(value, u.ohm)\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [\n            f\"CH{channel + 1}:{cmd} {value:e}\",\n            f\"CH{channel + 1}:{cmd} {value:e}\",\n            f\"CH{channel + 1}:{cmd}?\",\n        ],\n        [f\"{value}\"],\n    ) as inst:\n        inst.channel[channel].termination = value\n        inst.channel[channel].termination = value_unitful\n        unit_eq(inst.channel[channel].termination, value_unitful)\n\n\n@given(\n    value=st.text(\n        alphabet=st.characters(blacklist_characters=\"\\n\", blacklist_categories=(\"Cs\",))\n    )\n)\ndef test_channel_label(value):\n    \"\"\"Get / set human readable label for channel.\"\"\"\n    channel = 0\n    cmd = \"LAB:NAM\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f'CH{channel+1}:{cmd} \"{value}\"', f\"CH{channel+1}:{cmd}?\"],\n        [f'\"{value}\"'],\n    ) as inst:\n        inst.channel[channel].label = value\n        assert inst.channel[channel].label == value\n\n\n@given(\n    value=st.floats(\n        min_value=-ik.tektronix.TekDPO70000.HOR_DIVS,\n        max_value=ik.tektronix.TekDPO70000.HOR_DIVS,\n    )\n)\ndef test_channel_label_xpos(value):\n    \"\"\"Get / set x position for label.\"\"\"\n    channel = 0\n    cmd = \"LAB:XPOS\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"CH{channel+1}:{cmd} {value:e}\", f\"CH{channel+1}:{cmd}?\"],\n        [f\"{value}\"],\n    ) as inst:\n        inst.channel[channel].label_xpos = value\n        assert inst.channel[channel].label_xpos == value\n\n\n@given(\n    value=st.floats(\n        min_value=-ik.tektronix.TekDPO70000.VERT_DIVS,\n        max_value=ik.tektronix.TekDPO70000.VERT_DIVS,\n    )\n)\ndef test_channel_label_ypos(value):\n    \"\"\"Get / set y position for label.\"\"\"\n    channel = 0\n    cmd = \"LAB:YPOS\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"CH{channel+1}:{cmd} {value:e}\", f\"CH{channel+1}:{cmd}?\"],\n        [f\"{value}\"],\n    ) as inst:\n        inst.channel[channel].label_ypos = value\n        assert inst.channel[channel].label_ypos == value\n\n\n@given(value=st.floats(allow_nan=False))\ndef test_channel_offset(value):\n    \"\"\"Get / set offset, unitful in V and unitless.\"\"\"\n    channel = 0\n    cmd = \"OFFS\"\n    value_unitful = u.Quantity(value, u.V)\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [\n            f\"CH{channel + 1}:{cmd} {value:e}\",\n            f\"CH{channel + 1}:{cmd} {value:e}\",\n            f\"CH{channel + 1}:{cmd}?\",\n        ],\n        [f\"{value}\"],\n    ) as inst:\n        inst.channel[channel].offset = value\n        inst.channel[channel].offset = value_unitful\n        unit_eq(inst.channel[channel].offset, value_unitful)\n\n\n@given(\n    value=st.floats(\n        min_value=-ik.tektronix.TekDPO70000.VERT_DIVS,\n        max_value=ik.tektronix.TekDPO70000.VERT_DIVS,\n    )\n)\ndef test_channel_position(value):\n    \"\"\"Get / set vertical position.\"\"\"\n    channel = 0\n    cmd = \"POS\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"CH{channel+1}:{cmd} {value:e}\", f\"CH{channel+1}:{cmd}?\"],\n        [f\"{value}\"],\n    ) as inst:\n        inst.channel[channel].position = value\n        assert inst.channel[channel].position == value\n\n\n@given(value=st.floats(min_value=0))\ndef test_channel_scale(value):\n    \"\"\"Get / set scale.\"\"\"\n    channel = 0\n    cmd = \"SCALE\"\n    value_unitful = u.Quantity(value, u.V)\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [\n            f\"CH{channel + 1}:{cmd} {value:e}\",\n            f\"CH{channel + 1}:{cmd} {value:e}\",\n            f\"CH{channel + 1}:{cmd}?\",\n        ],\n        [f\"{value}\"],\n    ) as inst:\n        inst.channel[channel].scale = value\n        inst.channel[channel].scale = value_unitful\n        unit_eq(inst.channel[channel].scale, value_unitful)\n\n\n@given(\n    values=st.lists(st.floats(min_value=-2147483648, max_value=2147483647), min_size=1)\n)\ndef test_channel_scale_raw_data(values):\n    \"\"\"Return scaled raw data according to current settings.\"\"\"\n    channel = 0\n    scale = u.Quantity(1.0, u.V)\n    position = -1.0\n    offset = u.Quantity(0.0, u.V)\n    expected_value = tuple(\n        scale\n        * ((ik.tektronix.TekDPO70000.VERT_DIVS / 2) * float(v) / (2**15) - position)\n        for v in values\n    )\n    if numpy:\n        values = numpy.array(values)\n        expected_value = (\n            scale\n            * (\n                (ik.tektronix.TekDPO70000.VERT_DIVS / 2)\n                * values.astype(float)\n                / (2**15)\n                - position\n            )\n            + offset\n        )\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"CH{channel + 1}:SCALE?\", f\"CH{channel + 1}:POS?\", f\"CH{channel + 1}:OFFS?\"],\n        [f\"{scale}\", f\"{position}\", f\"{offset}\"],\n    ) as inst:\n        actual_data = inst.channel[channel]._scale_raw_data(values)\n        iterable_eq(actual_data, expected_value)\n\n\n# INSTRUMENT #\n\n\n@pytest.mark.parametrize(\"value\", [\"AUTO\", \"OFF\"])\ndef test_acquire_enhanced_enob(value):\n    \"\"\"Get / set enhanced effective number of bits.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"ACQ:ENHANCEDE {value}\", \"ACQ:ENHANCEDE?\"],\n        [f\"{value}\"],\n    ) as inst:\n        inst.acquire_enhanced_enob = value\n        assert inst.acquire_enhanced_enob == value\n\n\n@pytest.mark.parametrize(\"value\", [True, False])\ndef test_acquire_enhanced_state(value):\n    \"\"\"Get / set state of enhanced effective number of bits.\"\"\"\n    value_io = \"1\" if value else \"0\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"ACQ:ENHANCEDE:STATE {value_io}\", \"ACQ:ENHANCEDE:STATE?\"],\n        [f\"{value_io}\"],\n    ) as inst:\n        inst.acquire_enhanced_state = value\n        assert inst.acquire_enhanced_state == value\n\n\n@pytest.mark.parametrize(\"value\", [\"AUTO\", \"ON\", \"OFF\"])\ndef test_acquire_interp_8bit(value):\n    \"\"\"Get / set interpolation method of instrument.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000, [f\"ACQ:INTERPE {value}\", \"ACQ:INTERPE?\"], [f\"{value}\"]\n    ) as inst:\n        inst.acquire_interp_8bit = value\n        assert inst.acquire_interp_8bit == value\n\n\n@pytest.mark.parametrize(\"value\", [True, False])\ndef test_acquire_magnivu(value):\n    \"\"\"Get / set MagniVu feature.\"\"\"\n    value_io = \"ON\" if value else \"OFF\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000, [f\"ACQ:MAG {value_io}\", \"ACQ:MAG?\"], [f\"{value_io}\"]\n    ) as inst:\n        inst.acquire_magnivu = value\n        assert inst.acquire_magnivu == value\n\n\n@pytest.mark.parametrize(\"value\", ik.tektronix.TekDPO70000.AcquisitionMode)\ndef test_acquire_mode(value):\n    \"\"\"Get / set acquisition mode.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"ACQ:MOD {value.value}\", \"ACQ:MOD?\"],\n        [f\"{value.value}\"],\n    ) as inst:\n        inst.acquire_mode = value\n        assert inst.acquire_mode == value\n\n\n@pytest.mark.parametrize(\"value\", ik.tektronix.TekDPO70000.AcquisitionMode)\ndef test_acquire_mode_actual(value):\n    \"\"\"Get actually used acquisition mode (query only).\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000, [\"ACQ:MOD:ACT?\"], [f\"{value.value}\"]\n    ) as inst:\n        assert inst.acquire_mode_actual == value\n\n\n@given(value=st.integers(min_value=0, max_value=2**30 - 1))\ndef test_acquire_num_acquisitions(value):\n    \"\"\"Get number of waveform acquisitions since start (query only).\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000, [\"ACQ:NUMAC?\"], [f\"{value}\"]\n    ) as inst:\n        assert inst.acquire_num_acquisitions == value\n\n\n@given(value=st.integers(min_value=0))\ndef test_acquire_num_avgs(value):\n    \"\"\"Get / set number of waveform acquisitions to average.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000, [f\"ACQ:NUMAV {value}\", \"ACQ:NUMAV?\"], [f\"{value}\"]\n    ) as inst:\n        inst.acquire_num_avgs = value\n        assert inst.acquire_num_avgs == value\n\n\n@given(value=st.integers(min_value=0))\ndef test_acquire_num_envelop(value):\n    \"\"\"Get / set number of waveform acquisitions to envelope.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000, [f\"ACQ:NUME {value}\", \"ACQ:NUME?\"], [f\"{value}\"]\n    ) as inst:\n        inst.acquire_num_envelop = value\n        assert inst.acquire_num_envelop == value\n\n\n@given(value=st.integers(min_value=0))\ndef test_acquire_num_frames(value):\n    \"\"\"Get / set number of frames in FastFrame Single Sequence mode.\n\n    Query only.\n    \"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000, [\"ACQ:NUMFRAMESACQ?\"], [f\"{value}\"]\n    ) as inst:\n        assert inst.acquire_num_frames == value\n\n\n@given(value=st.integers(min_value=5000, max_value=2147400000))\ndef test_acquire_num_samples(value):\n    \"\"\"Get / set number of acquired samples to make up waveform database.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000, [f\"ACQ:NUMSAM {value}\", \"ACQ:NUMSAM?\"], [f\"{value}\"]\n    ) as inst:\n        inst.acquire_num_samples = value\n        assert inst.acquire_num_samples == value\n\n\n@pytest.mark.parametrize(\"value\", ik.tektronix.TekDPO70000.SamplingMode)\ndef test_acquire_sampling_mode(value):\n    \"\"\"Get / set sampling mode.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"ACQ:SAMP {value.value}\", \"ACQ:SAMP?\"],\n        [f\"{value.value}\"],\n    ) as inst:\n        inst.acquire_sampling_mode = value\n        assert inst.acquire_sampling_mode == value\n\n\n@pytest.mark.parametrize(\"value\", ik.tektronix.TekDPO70000.AcquisitionState)\ndef test_acquire_state(value):\n    \"\"\"Get / set acquisition state.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"ACQ:STATE {value.value}\", \"ACQ:STATE?\"],\n        [f\"{value.value}\"],\n    ) as inst:\n        inst.acquire_state = value\n        assert inst.acquire_state == value\n\n\n@pytest.mark.parametrize(\"value\", ik.tektronix.TekDPO70000.StopAfter)\ndef test_acquire_stop_after(value):\n    \"\"\"Get / set whether acquisition is continuous.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"ACQ:STOPA {value.value}\", \"ACQ:STOPA?\"],\n        [f\"{value.value}\"],\n    ) as inst:\n        inst.acquire_stop_after = value\n        assert inst.acquire_stop_after == value\n\n\n@given(value=st.integers(min_value=0))\ndef test_data_framestart(value):\n    \"\"\"Get / set start frame for waveform transfer.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"DAT:FRAMESTAR {value}\", \"DAT:FRAMESTAR?\"],\n        [f\"{value}\"],\n    ) as inst:\n        inst.data_framestart = value\n        assert inst.data_framestart == value\n\n\n@given(value=st.integers(min_value=0))\ndef test_data_framestop(value):\n    \"\"\"Get / set stop frame for waveform transfer.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"DAT:FRAMESTOP {value}\", \"DAT:FRAMESTOP?\"],\n        [f\"{value}\"],\n    ) as inst:\n        inst.data_framestop = value\n        assert inst.data_framestop == value\n\n\n@given(value=st.integers(min_value=0))\ndef test_data_start(value):\n    \"\"\"Get / set start data point for waveform transfer.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000, [f\"DAT:STAR {value}\", \"DAT:STAR?\"], [f\"{value}\"]\n    ) as inst:\n        inst.data_start = value\n        assert inst.data_start == value\n\n\n@given(value=st.integers(min_value=0))\ndef test_data_stop(value):\n    \"\"\"Get / set stop data point for waveform transfer.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000, [f\"DAT:STOP {value}\", \"DAT:STOP?\"], [f\"{value}\"]\n    ) as inst:\n        inst.data_stop = value\n        assert inst.data_stop == value\n\n\n@pytest.mark.parametrize(\"value\", [True, False])\ndef test_data_sync_sources(value):\n    \"\"\"Get / set if data sync sources are on or off.\"\"\"\n    value_io = \"ON\" if value else \"OFF\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"DAT:SYNCSOU {value_io}\", \"DAT:SYNCSOU?\"],\n        [f\"{value_io}\"],\n    ) as inst:\n        inst.data_sync_sources = value\n        assert inst.data_sync_sources == value\n\n\nvalid_channel_range = [it for it in range(4)]\n\n\n@pytest.mark.parametrize(\"no\", valid_channel_range)\ndef test_data_source_channel(no):\n    \"\"\"Get / set channel as data source.\"\"\"\n    channel_name = f\"CH{no + 1}\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"DAT:SOU {channel_name}\", f\"DAT:SOU?\"],\n        [channel_name],\n    ) as inst:\n        channel = inst.channel[no]\n        inst.data_source = channel\n        assert inst.data_source == channel\n\n\nvalid_math_range = [it for it in range(4)]\n\n\n@pytest.mark.parametrize(\"no\", valid_math_range)\ndef test_data_source_math(no, mocker):\n    \"\"\"Get / set math as data source.\"\"\"\n    math_name = f\"MATH{no + 1}\"\n\n    # patch call to time.sleep with mock\n    mock_time = mocker.patch.object(time, \"sleep\", return_value=None)\n\n    with expected_protocol(\n        ik.tektronix.TekDPO70000, [f\"DAT:SOU {math_name}\", f\"DAT:SOU?\"], [math_name]\n    ) as inst:\n        math = inst.math[no]\n        inst.data_source = math\n        assert inst.data_source == math\n\n        # assert that time.sleep has been called\n        mock_time.assert_called()\n\n\ndef test_data_source_ref_not_implemented_error():\n    \"\"\"Get / set a reference channel raises a NotImplemented error.\"\"\"\n    ref_name = \"REF1\"  # example, range not important\n    with expected_protocol(ik.tektronix.TekDPO70000, [f\"DAT:SOU?\"], [ref_name]) as inst:\n        # getter\n        with pytest.raises(NotImplementedError):\n            print(inst.data_source)\n        # setter\n        with pytest.raises(NotImplementedError):\n            inst.data_source = inst.ref[0]\n\n\ndef test_data_source_not_implemented_error():\n    \"\"\"Get a data source that is currently not implemented.\"\"\"\n    ds_name = \"HHG29\"  # example, range not important\n    with expected_protocol(ik.tektronix.TekDPO70000, [f\"DAT:SOU?\"], [ds_name]) as inst:\n        with pytest.raises(NotImplementedError):\n            print(inst.data_source)\n\n\ndef test_data_source_invalid_type():\n    \"\"\"Raise TypeError when a wrong type is set for data source.\"\"\"\n    invalid_data_source = 42\n    with expected_protocol(ik.tektronix.TekDPO70000, [], []) as inst:\n        with pytest.raises(TypeError) as exc_info:\n            inst.data_source = invalid_data_source\n        exc_msg = exc_info.value.args[0]\n        assert exc_msg == f\"{type(invalid_data_source)} is not a valid data \" f\"source.\"\n\n\n@given(value=st.floats(min_value=0, max_value=1000))\ndef test_horiz_acq_duration(value):\n    \"\"\"Get horizontal acquisition duration (query only).\"\"\"\n    value_unitful = u.Quantity(value, u.s)\n    with expected_protocol(\n        ik.tektronix.TekDPO70000, [\"HOR:ACQDURATION?\"], [f\"{value}\"]\n    ) as inst:\n        unit_eq(inst.horiz_acq_duration, value_unitful)\n\n\n@given(value=st.integers(min_value=0))\ndef test_horiz_acq_length(value):\n    \"\"\"Get horizontal acquisition length (query only).\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000, [\"HOR:ACQLENGTH?\"], [f\"{value}\"]\n    ) as inst:\n        assert inst.horiz_acq_length == value\n\n\n@pytest.mark.parametrize(\"value\", [True, False])\ndef test_horiz_delay_mode(value):\n    \"\"\"Get / set state of horizontal delay mode.\"\"\"\n    value_io = \"1\" if value else \"0\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"HOR:DEL:MOD {value_io}\", \"HOR:DEL:MOD?\"],\n        [f\"{value_io}\"],\n    ) as inst:\n        inst.horiz_delay_mode = value\n        assert inst.horiz_delay_mode == value\n\n\n@given(value=st.floats(min_value=0, max_value=100))\ndef test_horiz_delay_pos(value):\n    \"\"\"Get / set horizontal time base if delay mode is on.\n\n    Test setting unitful and without units.\"\"\"\n    value_unitful = u.Quantity(value, u.percent)\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"HOR:DEL:POS {value:e}\", f\"HOR:DEL:POS {value:e}\", \"HOR:DEL:POS?\"],\n        [f\"{value}\"],\n    ) as inst:\n        inst.horiz_delay_pos = value\n        inst.horiz_delay_pos = value_unitful\n        unit_eq(inst.horiz_delay_pos, value_unitful)\n\n\n@given(value=st.floats(min_value=0))\ndef test_horiz_delay_time(value):\n    \"\"\"Get / set horizontal delay time.\"\"\"\n    value_unitful = u.Quantity(value, u.s)\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"HOR:DEL:TIM {value:e}\", f\"HOR:DEL:TIM {value:e}\", \"HOR:DEL:TIM?\"],\n        [f\"{value}\"],\n    ) as inst:\n        inst.horiz_delay_time = value\n        inst.horiz_delay_time = value_unitful\n        unit_eq(inst.horiz_delay_time, value_unitful)\n\n\n@given(value=st.floats(min_value=0))\ndef test_horiz_interp_ratio(value):\n    \"\"\"Get horizontal interpolation ratio (query only).\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000, [\"HOR:MAI:INTERPR?\"], [f\"{value}\"]\n    ) as inst:\n        assert inst.horiz_interp_ratio == value\n\n\n@given(value=st.floats(min_value=0))\ndef test_horiz_main_pos(value):\n    \"\"\"Get / set horizontal main position.\n\n    Test setting unitful and without units.\"\"\"\n    value_unitful = u.Quantity(value, u.percent)\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"HOR:MAI:POS {value:e}\", f\"HOR:MAI:POS {value:e}\", \"HOR:MAI:POS?\"],\n        [f\"{value}\"],\n    ) as inst:\n        inst.horiz_main_pos = value\n        inst.horiz_main_pos = value_unitful\n        unit_eq(inst.horiz_main_pos, value_unitful)\n\n\ndef test_horiz_unit():\n    \"\"\"Get / set horizontal unit string.\"\"\"\n    unit_string = \"LUM\"  # as example in manual\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f'HOR:MAI:UNI \"{unit_string}\"', \"HOR:MAI:UNI?\"],\n        [f'\"{unit_string}\"'],\n    ) as inst:\n        inst.horiz_unit = unit_string\n        assert inst.horiz_unit == unit_string\n\n\n@pytest.mark.parametrize(\"value\", ik.tektronix.TekDPO70000.HorizontalMode)\ndef test_horiz_mode(value):\n    \"\"\"Get / set horizontal mode.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"HOR:MODE {value.value}\", \"HOR:MODE?\"],\n        [f\"{value.value}\"],\n    ) as inst:\n        inst.horiz_mode = value\n        assert inst.horiz_mode == value\n\n\n@given(value=st.integers(min_value=0))\ndef test_horiz_record_length_lim(value):\n    \"\"\"Get / set horizontal record length limit.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"HOR:MODE:AUTO:LIMIT {value}\", \"HOR:MODE:AUTO:LIMIT?\"],\n        [f\"{value}\"],\n    ) as inst:\n        inst.horiz_record_length_lim = value\n        assert inst.horiz_record_length_lim == value\n\n\n@given(value=st.integers(min_value=0))\ndef test_horiz_record_length(value):\n    \"\"\"Get / set horizontal record length.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"HOR:MODE:RECO {value}\", \"HOR:MODE:RECO?\"],\n        [f\"{value}\"],\n    ) as inst:\n        inst.horiz_record_length = value\n        assert inst.horiz_record_length == value\n\n\n@given(value=st.floats(min_value=0, max_value=30e9))\ndef test_horiz_sample_rate(value):\n    \"\"\"Get / set horizontal sampling rate.\n\n    Set with and without units.\"\"\"\n    value_unitful = u.Quantity(value, u.Hz)\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [\n            f\"HOR:MODE:SAMPLER {value:e}\",\n            f\"HOR:MODE:SAMPLER {value:e}\",\n            f\"HOR:MODE:SAMPLER?\",\n        ],\n        [f\"{value}\"],\n    ) as inst:\n        inst.horiz_sample_rate = value_unitful\n        inst.horiz_sample_rate = value\n        unit_eq(inst.horiz_sample_rate, value_unitful)\n\n\n@given(value=st.floats(min_value=0))\ndef test_horiz_scale(value):\n    \"\"\"Get / set horizontal scale in seconds per division.\n\n    Set with and without units.\"\"\"\n    value_unitful = u.Quantity(value, u.s)\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"HOR:MODE:SCA {value:e}\", f\"HOR:MODE:SCA {value:e}\", f\"HOR:MODE:SCA?\"],\n        [f\"{value}\"],\n    ) as inst:\n        inst.horiz_scale = value_unitful\n        inst.horiz_scale = value\n        unit_eq(inst.horiz_scale, value_unitful)\n\n\n@given(value=st.floats(min_value=0))\ndef test_horiz_pos(value):\n    \"\"\"Get / set position of trigger point on the screen.\n\n    Set with and without units.\n    \"\"\"\n    value_unitful = u.Quantity(value, u.percent)\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"HOR:POS {value:e}\", f\"HOR:POS {value:e}\", f\"HOR:POS?\"],\n        [f\"{value}\"],\n    ) as inst:\n        inst.horiz_pos = value_unitful\n        inst.horiz_pos = value\n        unit_eq(inst.horiz_pos, value_unitful)\n\n\n@pytest.mark.parametrize(\"value\", [\"AUTO\", \"OFF\", \"ON\"])\ndef test_horiz_roll(value):\n    \"\"\"Get / set roll mode status.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000, [f\"HOR:ROLL {value}\", f\"HOR:ROLL?\"], [f\"{value}\"]\n    ) as inst:\n        inst.horiz_roll = value\n        assert inst.horiz_roll == value\n\n\n@pytest.mark.parametrize(\"value\", ik.tektronix.TekDPO70000.TriggerState)\ndef test_trigger_state(value):\n    \"\"\"Get / set the trigger state.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"TRIG:STATE {value.value}\", \"TRIG:STATE?\"],\n        [f\"{value.value}\"],\n    ) as inst:\n        inst.trigger_state = value\n        assert inst.trigger_state == value\n\n\n@pytest.mark.parametrize(\"value\", ik.tektronix.TekDPO70000.WaveformEncoding)\ndef test_outgoing_waveform_encoding(value):\n    \"\"\"Get / set the encoding used for outgoing waveforms.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"WFMO:ENC {value.value}\", \"WFMO:ENC?\"],\n        [f\"{value.value}\"],\n    ) as inst:\n        inst.outgoing_waveform_encoding = value\n        assert inst.outgoing_waveform_encoding == value\n\n\n@pytest.mark.parametrize(\"value\", ik.tektronix.TekDPO70000.BinaryFormat)\ndef test_outgoing_byte_format(value):\n    \"\"\"Get / set the binary format for outgoing waveforms.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"WFMO:BN_F {value.value}\", \"WFMO:BN_F?\"],\n        [f\"{value.value}\"],\n    ) as inst:\n        inst.outgoing_binary_format = value\n        assert inst.outgoing_binary_format == value\n\n\n@pytest.mark.parametrize(\"value\", ik.tektronix.TekDPO70000.ByteOrder)\ndef test_outgoing_byte_order(value):\n    \"\"\"Get / set the binary data endianness for outgoing waveforms.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000,\n        [f\"WFMO:BYT_O {value.value}\", \"WFMO:BYT_O?\"],\n        [f\"{value.value}\"],\n    ) as inst:\n        inst.outgoing_byte_order = value\n        assert inst.outgoing_byte_order == value\n\n\n@pytest.mark.parametrize(\"value\", (1, 2, 4, 8))\ndef test_outgoing_n_bytes(value):\n    \"\"\"Get / set the number of bytes sampled in waveforms binary encoding.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekDPO70000, [f\"WFMO:BYT_N {value}\", \"WFMO:BYT_N?\"], [f\"{value}\"]\n    ) as inst:\n        inst.outgoing_n_bytes = value\n        assert inst.outgoing_n_bytes == value\n\n\n# METHODS #\n\n\ndef test_select_fastest_encoding():\n    \"\"\"Sets encoding to fastest methods.\"\"\"\n    with expected_protocol(ik.tektronix.TekDPO70000, [\"DAT:ENC FAS\"], []) as inst:\n        inst.select_fastest_encoding()\n\n\ndef test_force_trigger():\n    \"\"\"Force a trivver event.\"\"\"\n    with expected_protocol(ik.tektronix.TekDPO70000, [\"TRIG FORC\"], []) as inst:\n        inst.force_trigger()\n\n\ndef test_run():\n    \"\"\"Enables the trigger for the oscilloscope.\"\"\"\n    with expected_protocol(ik.tektronix.TekDPO70000, [\":RUN\"], []) as inst:\n        inst.run()\n\n\ndef test_stop():\n    \"\"\"Disables the trigger for the oscilloscope.\"\"\"\n    with expected_protocol(ik.tektronix.TekDPO70000, [\":STOP\"], []) as inst:\n        inst.stop()\n"
  },
  {
    "path": "tests/test_tektronix/test_tektronix_tds224.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the Tektronix TDS224\n\"\"\"\n\n# IMPORTS ####################################################################\n\nfrom enum import Enum\nimport time\n\nfrom hypothesis import given, strategies as st\nimport pytest\n\nimport instruments as ik\nfrom instruments.optional_dep_finder import numpy\nfrom tests import (\n    expected_protocol,\n    iterable_eq,\n    make_name_test,\n)\n\n# TESTS ######################################################################\n\n# pylint: disable=protected-access,redefined-outer-name\n\n\n# FIXTURES #\n\n\n@pytest.fixture(autouse=True)\ndef mock_time(mocker):\n    \"\"\"Mock time to replace time.sleep.\"\"\"\n    return mocker.patch.object(time, \"sleep\", return_value=None)\n\n\ntest_tektds224_name = make_name_test(ik.tektronix.TekTDS224)\n\n\ndef test_ref_init():\n    \"\"\"Initialize a reference channel.\"\"\"\n    with expected_protocol(ik.tektronix.TekTDS224, [], []) as tek:\n        assert tek.ref[0]._tek is tek\n\n\ndef test_data_source_name():\n    \"\"\"Get name of data source.\"\"\"\n    with expected_protocol(ik.tektronix.TekTDS224, [], []) as tek:\n        assert tek.math.name == \"MATH\"\n\n\ndef test_tektds224_data_width():\n    with expected_protocol(\n        ik.tektronix.TekTDS224, [\"DATA:WIDTH?\", \"DATA:WIDTH 1\"], [\"2\"]\n    ) as tek:\n        assert tek.data_width == 2\n        tek.data_width = 1\n\n\n@given(width=st.integers().filter(lambda x: x > 2 or x < 1))\ndef test_tektds224_data_width_value_error(width):\n    \"\"\"Raise value error if data_width is out of range.\"\"\"\n    with expected_protocol(ik.tektronix.TekTDS224, [], []) as tek:\n        with pytest.raises(ValueError) as err_info:\n            tek.data_width = width\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"Only one or two byte-width is supported.\"\n\n\ndef test_tektds224_data_source(mock_time):\n    with expected_protocol(\n        ik.tektronix.TekTDS224,\n        [\"DAT:SOU?\", \"DAT:SOU?\", \"DAT:SOU MATH\"],\n        [\"MATH\", \"CH1\"],\n    ) as tek:\n        assert tek.data_source == tek.math\n        assert tek.data_source == ik.tektronix.tektds224.TekTDS224.Channel(tek, 0)\n        tek.data_source = tek.math\n\n        # assert that time.sleep is called\n        mock_time.assert_called()\n\n\ndef test_tektds224_data_source_with_enum():\n    \"\"\"Set data source from an enum.\"\"\"\n\n    class Channel(Enum):\n        \"\"\"Fake class to init data_source with enum.\"\"\"\n\n        channel = \"MATH\"\n\n    with expected_protocol(ik.tektronix.TekTDS224, [\"DAT:SOU MATH\"], []) as tek:\n        tek.data_source = Channel.channel\n\n\ndef test_tektds224_channel():\n    with expected_protocol(ik.tektronix.TekTDS224, [], []) as tek:\n        assert tek.channel[0] == ik.tektronix.tektds224.TekTDS224.Channel(tek, 0)\n\n\ndef test_tektds224_channel_coupling():\n    with expected_protocol(\n        ik.tektronix.TekTDS224, [\"CH1:COUPL?\", \"CH2:COUPL AC\"], [\"DC\"]\n    ) as tek:\n        assert tek.channel[0].coupling == tek.Coupling.dc\n        tek.channel[1].coupling = tek.Coupling.ac\n\n\ndef test_tektds224_channel_coupling_type_error():\n    \"\"\"Raise TypeError if coupling setting is wrong type.\"\"\"\n    wrong_type = 42\n    with expected_protocol(ik.tektronix.TekTDS224, [], []) as tek:\n        with pytest.raises(TypeError) as err_info:\n            tek.channel[1].coupling = wrong_type\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Coupling setting must be a `TekTDS224.Coupling` \"\n            f\"value,got {type(wrong_type)} instead.\"\n        )\n\n\ndef test_tektds224_data_source_read_waveform():\n    with expected_protocol(\n        ik.tektronix.TekTDS224,\n        [\n            \"DAT:SOU?\",\n            \"DAT:SOU CH2\",\n            \"DAT:ENC RIB\",\n            \"DATA:WIDTH?\",\n            \"CURVE?\",\n            \"WFMP:CH2:YOF?\",\n            \"WFMP:CH2:YMU?\",\n            \"WFMP:CH2:YZE?\",\n            \"WFMP:XZE?\",\n            \"WFMP:XIN?\",\n            \"WFMP:CH2:NR_P?\",\n            \"DAT:SOU CH1\",\n        ],\n        [\n            \"CH1\",\n            \"2\",\n            # pylint: disable=no-member\n            \"#210\" + bytes.fromhex(\"00000001000200030004\").decode(\"utf-8\") + \"0\",\n            \"1\",\n            \"0\",\n            \"0\",\n            \"1\",\n            \"5\",\n        ],\n    ) as tek:\n        data = tuple(range(5))\n        if numpy:\n            data = numpy.array([0, 1, 2, 3, 4])\n        x, y = tek.channel[1].read_waveform()\n        iterable_eq(x, data)\n        iterable_eq(y, data)\n\n\n@given(values=st.lists(st.floats(allow_infinity=False, allow_nan=False), min_size=1))\ndef test_tektds224_data_source_read_waveform_ascii(values):\n    \"\"\"Read waveform as ASCII\"\"\"\n    # values\n    values_str = \",\".join([str(value) for value in values])\n\n    # parameters\n    yoffs = 1\n    ymult = 1\n    yzero = 0\n    xzero = 0\n    xincr = 1\n    ptcnt = len(values)\n\n    with expected_protocol(\n        ik.tektronix.TekTDS224,\n        [\n            \"DAT:SOU?\",\n            \"DAT:SOU CH2\",\n            \"DAT:ENC ASCI\",\n            \"CURVE?\",\n            \"WFMP:CH2:YOF?\",\n            \"WFMP:CH2:YMU?\",\n            \"WFMP:CH2:YZE?\",\n            \"WFMP:XZE?\",\n            \"WFMP:XIN?\",\n            \"WFMP:CH2:NR_P?\",\n            \"DAT:SOU CH1\",\n        ],\n        [\n            \"CH1\",\n            values_str,\n            f\"{yoffs}\",\n            f\"{ymult}\",\n            f\"{yzero}\",\n            f\"{xzero}\",\n            f\"{xincr}\",\n            f\"{ptcnt}\",\n        ],\n    ) as tek:\n        if numpy:\n            x_expected = numpy.arange(float(ptcnt)) * float(xincr) + float(xzero)\n            y_expected = ((numpy.array(values) - float(yoffs)) * float(ymult)) + float(\n                yzero\n            )\n        else:\n            x_expected = tuple(\n                float(val) * float(xincr) + float(xzero) for val in range(ptcnt)\n            )\n            y_expected = tuple(\n                ((val - float(yoffs)) * float(ymult)) + float(yzero) for val in values\n            )\n        x_read, y_read = tek.channel[1].read_waveform(bin_format=False)\n        iterable_eq(x_read, x_expected)\n        iterable_eq(y_read, y_expected)\n\n\ndef test_force_trigger():\n    \"\"\"Raise NotImplementedError when trying to force a trigger.\"\"\"\n    with expected_protocol(ik.tektronix.TekTDS224, [], []) as tek:\n        with pytest.raises(NotImplementedError):\n            tek.force_trigger()\n"
  },
  {
    "path": "tests/test_tektronix/test_tktds5xx.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nTests for the Tektronix TDS 5xx series oscilloscope.\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nfrom datetime import datetime\nimport struct\nimport time\nfrom unittest import mock\n\nfrom hypothesis import (\n    given,\n    strategies as st,\n)\nimport pytest\n\nimport instruments as ik\nfrom instruments.optional_dep_finder import numpy\nfrom tests import (\n    expected_protocol,\n    iterable_eq,\n    make_name_test,\n)\n\n# TESTS #######################################################################\n\n\n# pylint: disable=protected-access\n\n\ntest_tektds5xx_name = make_name_test(ik.tektronix.TekTDS5xx)\n\n\n# MEASUREMENT #\n\n\n@pytest.mark.parametrize(\"msr\", [it for it in range(3)])\ndef test_measurement_init(msr):\n    \"\"\"Initialize a new measurement.\"\"\"\n    meas_categories = [\n        \"enabled\",\n        \"type\",\n        \"units\",\n        \"src1\",\n        \"src2\",\n        \"edge1\",\n        \"edge2\",\n        \"dir\",\n    ]\n    meas_return = '0;UNDEFINED;\"V\",CH1,CH2,RISE,RISE,FORWARDS'\n    data_expected = dict(zip(meas_categories, meas_return.split(\";\")))\n    with expected_protocol(\n        ik.tektronix.TekTDS5xx, [f\"MEASU:MEAS{msr+1}?\"], [meas_return]\n    ) as inst:\n        measurement = inst.measurement[msr]\n        assert measurement._tek is inst\n        assert measurement._id == msr + 1\n        assert measurement._data == data_expected\n\n\n@pytest.mark.parametrize(\"msr\", [it for it in range(3)])\n@given(value=st.floats(allow_nan=False))\ndef test_measurement_read_enabled_true(msr, value):\n    \"\"\"Read a new measurement value since enabled is true.\"\"\"\n    enabled = 1\n    # initialization dictionary\n    meas_categories = [\n        \"enabled\",\n        \"type\",\n        \"units\",\n        \"src1\",\n        \"src2\",\n        \"edge1\",\n        \"edge2\",\n        \"dir\",\n    ]\n    meas_return = f'{enabled};UNDEFINED;\"V\",CH1,CH2,RISE,RISE,FORWARDS'\n    data_expected = dict(zip(meas_categories, meas_return.split(\";\")))\n\n    # extended dictionary\n    data_expected[\"value\"] = value\n\n    with expected_protocol(\n        ik.tektronix.TekTDS5xx,\n        [f\"MEASU:MEAS{msr+1}?\", f\"MEASU:MEAS{msr+1}:VAL?\"],\n        [meas_return, f\"{value}\"],\n    ) as inst:\n        measurement = inst.measurement[msr]\n        assert measurement.read() == data_expected\n\n\ndef test_measurement_read_enabled_false():\n    \"\"\"Do not read a new measurement value since enabled is false.\"\"\"\n    msr = 0\n    enabled = 0\n    # initialization dictionary\n    meas_categories = [\n        \"enabled\",\n        \"type\",\n        \"units\",\n        \"src1\",\n        \"src2\",\n        \"edge1\",\n        \"edge2\",\n        \"dir\",\n    ]\n    meas_return = f'{enabled};UNDEFINED;\"V\",CH1,CH2,RISE,RISE,FORWARDS'\n    data_expected = dict(zip(meas_categories, meas_return.split(\";\")))\n    with expected_protocol(\n        ik.tektronix.TekTDS5xx, [f\"MEASU:MEAS{msr+1}?\"], [meas_return]\n    ) as inst:\n        measurement = inst.measurement[msr]\n        assert measurement.read() == data_expected\n\n\n# DATA SOURCE #\n\n\n@given(values=st.lists(st.integers(min_value=-32768, max_value=32767), min_size=1))\ndef test_data_source_read_waveform_binary(values):\n    \"\"\"Read waveform from data source as binary.\"\"\"\n    # constants - to not overkill it with hypothesis\n    channel_no = 0\n    data_width = 2\n    yoffs = 1.0\n    ymult = 1.0\n    yzero = 0.3\n    xincr = 0.001\n    # make values to compare with\n    ptcnt = len(values)\n    values_arr = values\n    if numpy:\n        values_arr = numpy.array(values)\n    values_packed = b\"\".join(struct.pack(\">h\", value) for value in values)\n    values_len = str(len(values_packed)).encode()\n    values_len_of_len = str(len(values_len)).encode()\n\n    # calculations\n    if numpy:\n        x_calc = numpy.arange(float(ptcnt)) * xincr\n        y_calc = ((values_arr - yoffs) * ymult) + yzero\n    else:\n        x_calc = tuple(float(val) * float(xincr) for val in range(ptcnt))\n        y_calc = tuple(((val - yoffs) * float(ymult)) + float(yzero) for val in values)\n\n    with expected_protocol(\n        ik.tektronix.TekTDS5xx,\n        [\n            \"DAT:SOU?\",\n            \"DAT:ENC RIB\",\n            \"DATA:WIDTH?\",\n            \"CURVE?\",\n            f\"WFMP:CH{channel_no+1}:YOF?\",\n            f\"WFMP:CH{channel_no+1}:YMU?\",\n            f\"WFMP:CH{channel_no+1}:YZE?\",\n            f\"WFMP:CH{channel_no+1}:XIN?\",\n            f\"WFMP:CH{channel_no+1}:NR_P?\",\n        ],\n        [\n            f\"CH{channel_no+1}\",\n            f\"{data_width}\",\n            b\"#\" + values_len_of_len + values_len + values_packed,\n            f\"{yoffs}\",\n            f\"{ymult}\",\n            f\"{yzero}\",\n            f\"{xincr}\",\n            f\"{ptcnt}\",\n        ],\n    ) as inst:\n        channel = inst.channel[channel_no]\n        x_read, y_read = channel.read_waveform(bin_format=True)\n        iterable_eq(x_read, x_calc)\n        iterable_eq(y_read, y_calc)\n\n\n@given(values=st.lists(st.floats(min_value=0), min_size=1))\ndef test_data_source_read_waveform_ascii(values):\n    \"\"\"Read waveform from data source as ASCII.\"\"\"\n    # constants - to not overkill it with hypothesis\n    channel_no = 0\n    yoffs = 1.0\n    ymult = 1.0\n    yzero = 0.3\n    xincr = 0.001\n    # make values to compare with\n    values_str = \",\".join([str(value) for value in values])\n    values_arr = values\n    if numpy:\n        values_arr = numpy.array(values)\n\n    # calculations\n    ptcnt = len(values)\n    if numpy:\n        x_calc = numpy.arange(float(ptcnt)) * xincr\n        y_calc = ((values_arr - yoffs) * ymult) + yzero\n    else:\n        x_calc = tuple(float(val) * float(xincr) for val in range(ptcnt))\n        y_calc = tuple(((val - yoffs) * float(ymult)) + float(yzero) for val in values)\n\n    with expected_protocol(\n        ik.tektronix.TekTDS5xx,\n        [\n            \"DAT:SOU?\",\n            \"DAT:ENC ASCI\",\n            \"CURVE?\",\n            f\"WFMP:CH{channel_no+1}:YOF?\",\n            f\"WFMP:CH{channel_no+1}:YMU?\",\n            f\"WFMP:CH{channel_no+1}:YZE?\",\n            f\"WFMP:CH{channel_no+1}:XIN?\",\n            f\"WFMP:CH{channel_no+1}:NR_P?\",\n        ],\n        [\n            f\"CH{channel_no+1}\",\n            values_str,\n            f\"{yoffs}\",\n            f\"{ymult}\",\n            f\"{yzero}\",\n            f\"{xincr}\",\n            f\"{ptcnt}\",\n        ],\n    ) as inst:\n        channel = inst.channel[channel_no]\n        x_read, y_read = channel.read_waveform(bin_format=False)\n        iterable_eq(x_read, x_calc)\n        iterable_eq(y_read, y_calc)\n\n\n# CHANNEL #\n\n\n@pytest.mark.parametrize(\"channel\", [it for it in range(4)])\ndef test_channel_init(channel):\n    \"\"\"Initialize a new channel.\"\"\"\n    with expected_protocol(ik.tektronix.TekTDS5xx, [], []) as inst:\n        assert inst.channel[channel]._parent is inst\n        assert inst.channel[channel]._idx == channel + 1\n\n\n@pytest.mark.parametrize(\"coupl\", ik.tektronix.TekTDS5xx.Coupling)\ndef test_channel_coupling(coupl):\n    \"\"\"Get / set channel coupling.\"\"\"\n    channel = 0\n    with expected_protocol(\n        ik.tektronix.TekTDS5xx,\n        [f\"CH{channel+1}:COUPL {coupl.value}\", f\"CH{channel+1}:COUPL?\"],\n        [f\"{coupl.value}\"],\n    ) as inst:\n        inst.channel[channel].coupling = coupl\n        assert inst.channel[channel].coupling == coupl\n\n\ndef test_channel_coupling_type_error():\n    \"\"\"Raise type error if channel coupling is set with wrong type.\"\"\"\n    wrong_type = 42\n    channel = 0\n    with expected_protocol(ik.tektronix.TekTDS5xx, [], []) as inst:\n        with pytest.raises(TypeError) as err_info:\n            inst.channel[channel].coupling = wrong_type\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Coupling setting must be a `TekTDS5xx.Coupling` \"\n            f\"value, got {type(wrong_type)} instead.\"\n        )\n\n\n@pytest.mark.parametrize(\"bandw\", ik.tektronix.TekTDS5xx.Bandwidth)\ndef test_channel_bandwidth(bandw):\n    \"\"\"Get / set channel bandwidth.\"\"\"\n    channel = 0\n    with expected_protocol(\n        ik.tektronix.TekTDS5xx,\n        [f\"CH{channel+1}:BAND {bandw.value}\", f\"CH{channel+1}:BAND?\"],\n        [f\"{bandw.value}\"],\n    ) as inst:\n        inst.channel[channel].bandwidth = bandw\n        assert inst.channel[channel].bandwidth == bandw\n\n\ndef test_channel_bandwidth_type_error():\n    \"\"\"Raise type error if channel bandwidth is set with wrong type.\"\"\"\n    wrong_type = 42\n    channel = 0\n    with expected_protocol(ik.tektronix.TekTDS5xx, [], []) as inst:\n        with pytest.raises(TypeError) as err_info:\n            inst.channel[channel].bandwidth = wrong_type\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Bandwidth setting must be a \"\n            f\"`TekTDS5xx.Bandwidth` value, got \"\n            f\"{type(wrong_type)} instead.\"\n        )\n\n\n@pytest.mark.parametrize(\"imped\", ik.tektronix.TekTDS5xx.Impedance)\ndef test_channel_impedance(imped):\n    \"\"\"Get / set channel impedance.\"\"\"\n    channel = 0\n    with expected_protocol(\n        ik.tektronix.TekTDS5xx,\n        [f\"CH{channel+1}:IMP {imped.value}\", f\"CH{channel+1}:IMP?\"],\n        [f\"{imped.value}\"],\n    ) as inst:\n        inst.channel[channel].impedance = imped\n        assert inst.channel[channel].impedance == imped\n\n\ndef test_channel_impedance_type_error():\n    \"\"\"Raise type error if channel impedance is set with wrong type.\"\"\"\n    wrong_type = 42\n    channel = 0\n    with expected_protocol(ik.tektronix.TekTDS5xx, [], []) as inst:\n        with pytest.raises(TypeError) as err_info:\n            inst.channel[channel].impedance = wrong_type\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Impedance setting must be a \"\n            f\"`TekTDS5xx.Impedance` value, got \"\n            f\"{type(wrong_type)} instead.\"\n        )\n\n\n@given(value=st.floats(min_value=0, exclude_min=True))\ndef test_channel_probe(value):\n    \"\"\"Get connected probe value.\"\"\"\n    channel = 0\n    with expected_protocol(\n        ik.tektronix.TekTDS5xx, [f\"CH{channel+1}:PRO?\"], [f\"{value}\"]\n    ) as inst:\n        value_expected = round(1 / value, 0)\n        assert inst.channel[channel].probe == value_expected\n\n\n@given(value=st.floats(min_value=0))\ndef test_channel_scale(value):\n    \"\"\"Get / set scale setting.\"\"\"\n    channel = 0\n    with expected_protocol(\n        ik.tektronix.TekTDS5xx,\n        [\n            f\"CH{channel + 1}:SCA {value:.3E}\",\n            f\"CH{channel + 1}:SCA?\",\n            f\"CH{channel + 1}:SCA?\",\n        ],\n        [f\"{value}\", f\"{value}\"],\n    ) as inst:\n        inst.channel[channel].scale = value\n        print(f\"\\n>>>{value}\")\n        assert inst.channel[channel].scale == value\n\n\ndef test_channel_scale_value_error():\n    \"\"\"Raise ValueError if scale was not set properly.\"\"\"\n    scale_set = 42\n    scale_rec = 13\n    channel = 0\n    with expected_protocol(\n        ik.tektronix.TekTDS5xx,\n        [f\"CH{channel + 1}:SCA {scale_set:.3E}\", f\"CH{channel + 1}:SCA?\"],\n        [f\"{scale_rec}\"],\n    ) as inst:\n        with pytest.raises(ValueError) as err_info:\n            inst.channel[channel].scale = scale_set\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Tried to set CH{channel+1} Scale to {scale_set} \"\n            f\"but got {float(scale_rec)} instead\"\n        )\n\n\n# INSTRUMENT #\n\n\n@given(states=st.lists(st.integers(min_value=0, max_value=1), min_size=11, max_size=11))\ndef test_sources(states):\n    \"\"\"Get list of all active sources.\"\"\"\n    active_sources = []\n    with expected_protocol(\n        ik.tektronix.TekTDS5xx, [\"SEL?\"], [\";\".join([str(state) for state in states])]\n    ) as inst:\n        # create active_sources\n        for idx in range(4):\n            if states[idx]:\n                active_sources.append(\n                    ik.tektronix.tektds5xx.TekTDS5xx.Channel(inst, idx)\n                )\n        for idx in range(4, 7):\n            if states[idx]:\n                active_sources.append(\n                    ik.tektronix.tektds5xx.TekTDS5xx.DataSource(inst, f\"MATH{idx - 3}\")\n                )\n        for idx in range(7, 11):\n            if states[idx]:\n                active_sources.append(\n                    ik.tektronix.tektds5xx.TekTDS5xx.DataSource(inst, f\"REF{idx - 6}\")\n                )\n        # read active sources\n        active_read = inst.sources\n\n        assert active_read == active_sources\n\n\n@pytest.mark.parametrize(\"channel\", [it for it in range(4)])\ndef test_data_source_channel(channel):\n    \"\"\"Get / set channel data source for waveform transfer.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekTDS5xx,\n        [f\"DAT:SOU CH{channel+1}\", f\"DAT:SOU CH{channel+1}\", \"DAT:SOU?\"],\n        [f\"CH{channel+1}\"],\n    ) as inst:\n        # set as Source enum\n        inst.data_source = ik.tektronix.TekTDS5xx.Source[f\"CH{channel + 1}\"]\n        # set as channel object\n        data_source = inst.channel[channel]\n        inst.data_source = data_source\n        assert inst.data_source == data_source\n\n\n@pytest.mark.parametrize(\"channel\", [it for it in range(3)])\ndef test_data_source_math(channel):\n    \"\"\"Get / set math data source for waveform transfer.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekTDS5xx,\n        [f\"DAT:SOU MATH{channel+1}\", f\"DAT:SOU MATH{channel+1}\", \"DAT:SOU?\"],\n        [f\"MATH{channel+1}\"],\n    ) as inst:\n        # set as Source enum\n        inst.data_source = ik.tektronix.TekTDS5xx.Source[f\"Math{channel + 1}\"]\n        # set as channel object\n        data_source = inst.math[channel]\n        inst.data_source = data_source\n        assert inst.data_source == data_source\n\n\n@pytest.mark.parametrize(\"channel\", [it for it in range(3)])\ndef test_data_source_ref(channel):\n    \"\"\"Get / set ref data source for waveform transfer.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekTDS5xx,\n        [f\"DAT:SOU REF{channel+1}\", f\"DAT:SOU REF{channel+1}\", \"DAT:SOU?\"],\n        [f\"REF{channel+1}\"],\n    ) as inst:\n        # set as Source enum\n        inst.data_source = ik.tektronix.TekTDS5xx.Source[f\"Ref{channel + 1}\"]\n        # set as channel object\n        data_source = inst.ref[channel]\n        inst.data_source = data_source\n        assert inst.data_source == data_source\n\n\ndef test_data_source_raise_type_error():\n    \"\"\"Raise TypeError when setting data source with wrong type.\"\"\"\n    wrong_type = 42\n    with expected_protocol(ik.tektronix.TekTDS5xx, [], []) as inst:\n        with pytest.raises(TypeError) as err_info:\n            inst.data_source = wrong_type\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Source setting must be a `TekTDS5xx.Source` \"\n            f\"value, got {type(wrong_type)} instead.\"\n        )\n\n\n@pytest.mark.parametrize(\"width\", (1, 2))\ndef test_data_width(width):\n    \"\"\"Get / set data width.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekTDS5xx, [f\"DATA:WIDTH {width}\", \"DATA:WIDTH?\"], [f\"{width}\"]\n    ) as inst:\n        inst.data_width = width\n        assert inst.data_width == width\n\n\n@given(width=st.integers().filter(lambda x: x < 1 or x > 2))\ndef test_data_width_value_error(width):\n    \"\"\"Raise ValueError when setting a wrong data width.\"\"\"\n    with expected_protocol(ik.tektronix.TekTDS5xx, [], []) as inst:\n        with pytest.raises(ValueError) as err_info:\n            inst.data_width = width\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"Only one or two byte-width is supported.\"\n\n\ndef test_force_trigger():\n    \"\"\"Raise NotImplementedError when forcing a trigger.\"\"\"\n    with expected_protocol(ik.tektronix.TekTDS5xx, [], []) as inst:\n        with pytest.raises(NotImplementedError):\n            inst.force_trigger()\n\n\n@given(value=st.floats(min_value=0))\ndef test_horizontal_scale(value):\n    \"\"\"Get / set horizontal scale.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekTDS5xx,\n        [f\"HOR:MAI:SCA {value:.3E}\", \"HOR:MAI:SCA?\", \"HOR:MAI:SCA?\"],\n        [f\"{value}\", f\"{value}\"],\n    ) as inst:\n        inst.horizontal_scale = value\n        assert inst.horizontal_scale == value\n\n\ndef test_horizontal_scale_value_error():\n    \"\"\"Raise ValueError if setting horizontal scale does not work.\"\"\"\n    set_value = 42\n    get_value = 13\n    with expected_protocol(\n        ik.tektronix.TekTDS5xx,\n        [f\"HOR:MAI:SCA {set_value:.3E}\", \"HOR:MAI:SCA?\"],\n        [\n            f\"{get_value}\",\n        ],\n    ) as inst:\n        with pytest.raises(ValueError) as err_info:\n            inst.horizontal_scale = set_value\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Tried to set Horizontal Scale to {set_value} \"\n            f\"but got {float(get_value)} instead\"\n        )\n\n\n@given(value=st.floats(min_value=0))\ndef test_trigger_level(value):\n    \"\"\"Get / set trigger level.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekTDS5xx,\n        [f\"TRIG:MAI:LEV {value:.3E}\", \"TRIG:MAI:LEV?\", \"TRIG:MAI:LEV?\"],\n        [f\"{value}\", f\"{value}\"],\n    ) as inst:\n        inst.trigger_level = value\n        assert inst.trigger_level == value\n\n\ndef test_trigger_level_value_error():\n    \"\"\"Raise ValueError if setting trigger level does not work.\"\"\"\n    set_value = 42\n    get_value = 13\n    with expected_protocol(\n        ik.tektronix.TekTDS5xx,\n        [f\"TRIG:MAI:LEV {set_value:.3E}\", \"TRIG:MAI:LEV?\"],\n        [f\"{get_value}\"],\n    ) as inst:\n        with pytest.raises(ValueError) as err_info:\n            inst.trigger_level = set_value\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Tried to set trigger level to {set_value} \"\n            f\"but got {float(get_value)} instead\"\n        )\n\n\n@pytest.mark.parametrize(\"coupl\", ik.tektronix.TekTDS5xx.Coupling)\ndef test_trigger_coupling(coupl):\n    \"\"\"Get / set trigger coupling.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekTDS5xx,\n        [f\"TRIG:MAI:EDGE:COUP {coupl.value}\", \"TRIG:MAI:EDGE:COUP?\"],\n        [f\"{coupl.value}\"],\n    ) as inst:\n        inst.trigger_coupling = coupl\n        assert inst.trigger_coupling == coupl\n\n\ndef test_trigger_coupling_type_error():\n    \"\"\"Raise type error when coupling is not a `Coupling` enum.\"\"\"\n    wrong_type = 42\n    with expected_protocol(ik.tektronix.TekTDS5xx, [], []) as inst:\n        with pytest.raises(TypeError) as err_info:\n            inst.trigger_coupling = wrong_type\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Coupling setting must be a `TekTDS5xx.Coupling` \"\n            f\"value, got {type(wrong_type)} instead.\"\n        )\n\n\n@pytest.mark.parametrize(\"edge\", ik.tektronix.TekTDS5xx.Edge)\ndef test_trigger_slope(edge):\n    \"\"\"Get / set trigger slope.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekTDS5xx,\n        [f\"TRIG:MAI:EDGE:SLO {edge.value}\", \"TRIG:MAI:EDGE:SLO?\"],\n        [f\"{edge.value}\"],\n    ) as inst:\n        inst.trigger_slope = edge\n        assert inst.trigger_slope == edge\n\n\ndef test_trigger_slope_type_error():\n    \"\"\"Raise type error when edge is not an `Edge` enum.\"\"\"\n    wrong_type = 42\n    with expected_protocol(ik.tektronix.TekTDS5xx, [], []) as inst:\n        with pytest.raises(TypeError) as err_info:\n            inst.trigger_slope = wrong_type\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Edge setting must be a `TekTDS5xx.Edge` \"\n            f\"value, got {type(wrong_type)} instead.\"\n        )\n\n\n@pytest.mark.parametrize(\"source\", ik.tektronix.TekTDS5xx.Trigger)\ndef test_trigger_source(source):\n    \"\"\"Get / set trigger source.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekTDS5xx,\n        [f\"TRIG:MAI:EDGE:SOU {source.value}\", \"TRIG:MAI:EDGE:SOU?\"],\n        [f\"{source.value}\"],\n    ) as inst:\n        inst.trigger_source = source\n        assert inst.trigger_source == source\n\n\ndef test_trigger_source_type_error():\n    \"\"\"Raise type error when source is not an `source` enum.\"\"\"\n    wrong_type = 42\n    with expected_protocol(ik.tektronix.TekTDS5xx, [], []) as inst:\n        with pytest.raises(TypeError) as err_info:\n            inst.trigger_source = wrong_type\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Trigger source setting must be a \"\n            f\"`TekTDS5xx.Trigger` value, got \"\n            f\"{type(wrong_type)} instead.\"\n        )\n\n\n@given(dt=st.datetimes(min_value=datetime(1000, 1, 1)))\ndef test_clock(dt):\n    \"\"\"Get / set oscilloscope clock.\"\"\"\n    # create a date and time\n    dt_fmt_receive = '\"%Y-%m-%d\";\"%H:%M:%S\"'\n    dt_fmt_send = 'DATE \"%Y-%m-%d\";:TIME \"%H:%M:%S\"'\n    with expected_protocol(\n        ik.tektronix.TekTDS5xx,\n        [dt.strftime(dt_fmt_send), \"DATE?;:TIME?\"],\n        [dt.strftime(dt_fmt_receive)],\n    ) as inst:\n        inst.clock = dt\n        assert inst.clock == dt.replace(microsecond=0)\n\n\ndef test_clock_value_error():\n    \"\"\"Raise ValueError when not set with datetime object.\"\"\"\n    wrong_type = 42\n    with expected_protocol(ik.tektronix.TekTDS5xx, [], []) as inst:\n        with pytest.raises(ValueError) as err_info:\n            inst.clock = wrong_type\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"Expected datetime.datetime but got \"\n            f\"{type(wrong_type)} instead\"\n        )\n\n\n@pytest.mark.parametrize(\"newval\", (True, False))\ndef test_display_clock(newval):\n    \"\"\"Get / set if clock is displayed on screen.\"\"\"\n    with expected_protocol(\n        ik.tektronix.TekTDS5xx,\n        [f\"DISPLAY:CLOCK {int(newval)}\", \"DISPLAY:CLOCK?\"],\n        [f\"{int(newval)}\"],\n    ) as inst:\n        inst.display_clock = newval\n        assert inst.display_clock == newval\n\n\ndef test_display_clock_value_error():\n    \"\"\"Raise ValueError when display_clock is called w/o a bool.\"\"\"\n    wrong_type = 42\n    with expected_protocol(ik.tektronix.TekTDS5xx, [], []) as inst:\n        with pytest.raises(ValueError) as err_info:\n            inst.display_clock = wrong_type\n        err_msg = err_info.value.args[0]\n        assert err_msg == f\"Expected bool but got {type(wrong_type)} instead\"\n\n\n@given(data=st.binary(min_size=1, max_size=2147483647))\n@mock.patch.object(time, \"sleep\", return_value=None)\ndef test_get_hardcopy(mocker, data):\n    \"\"\"Transfer data in binary from the instrument.\n\n    Data is at least 1 byte long, then we need to add 8 for the\n    color table.\n    Fake the header of the data such that in byte 18:30 are 4 factorial\n    packed as '<iihh' that multiplied together result in the length of\n    the total data. Limit maximum size of binary such that we don't have\n    to factor the length and such that it simply fits into 4 bytes\n    unsigned.\n    Take some random data, then stick a header to it. Unchecked entries\n    in header are filled with zeros.\n    Mocking out sleep to do nothing.\n    \"\"\"\n\n    # make data\n    length_data = (len(data) - 8) * 8  # subtract header and color table\n    # make a fake header\n    header = struct.pack(\"<ddhiihhddd\", 0, 0, 0, length_data, 1, 1, 1, 0, 0, 0)\n    # stick header and data together\n    data_expected = header + data\n\n    with expected_protocol(\n        ik.tektronix.TekTDS5xx,\n        [\n            \"HARDC:PORT GPI;HARDC:LAY PORT;:HARDC:FORM BMP\",\n            \"HARDC START\",\n        ],\n        [header + data],\n    ) as inst:\n        data_read = inst.get_hardcopy()\n        assert data_read == data_expected\n"
  },
  {
    "path": "tests/test_teledyne/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_teledyne/test_maui.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the Thorlabs TC200\n\"\"\"\n\n# IMPORTS ####################################################################\n\nimport pytest\n\nimport instruments as ik\nfrom instruments.optional_dep_finder import numpy\nfrom tests import (\n    expected_protocol,\n    iterable_eq,\n)\nfrom instruments.units import ureg as u\n\n# TESTS ######################################################################\n\n\n# pylint: disable=too-many-lines,redefined-outer-name,protected-access\n\n\n# PYTEST FIXTURES FOR INITIALIZATION #\n\n\n@pytest.fixture\ndef init():\n    \"\"\"Returns the initialization command that is sent to MAUI Scope.\"\"\"\n    return \"COMM_HEADER OFF\"\n\n\n# TEST ENUM GENERATION #\n\n\ndef test_create_trigger_source_enum(init):\n    \"\"\"Generate trigger source enum when number of channels changes.\"\"\"\n    with expected_protocol(ik.teledyne.MAUI, [init], [], sep=\"\\n\") as osc:\n        osc.number_channels = 42\n\n        # existence of new channels, but not more\n        assert \"c41\" in osc.TriggerSource.__members__\n        assert \"c42\" not in osc.TriggerSource.__members__\n\n        # proper name generation\n        assert osc.TriggerSource[\"c41\"].value == \"C42\"\n\n\n# TEST DATA SOURCE CLASS #\n\n\ndef test_maui_data_source_name(init):\n    \"\"\"Return the name of the channel in the data source.\"\"\"\n    with expected_protocol(ik.teledyne.MAUI, [init], [], sep=\"\\n\") as osc:\n        assert osc.math[0].name == \"F1\"\n        assert osc.channel[1].name == \"C2\"\n\n\ndef test_maui_data_source_read_waveform(init):\n    \"\"\"Return a numpy array of a waveform.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI,\n        [\n            init,\n            \"TRMD?\",\n            \"TRMD SINGLE\",\n            \"C1:INSPECT? 'SIMPLE'\",\n            \"C1:INSPECT? 'HORIZ_OFFSET'\",\n            \"C1:INSPECT? 'HORIZ_INTERVAL'\",\n            \"TRMD AUTO\",\n        ],\n        [\n            \"AUTO\",\n            '\"  1.   2.   3.   4.  \"',\n            \"HORIZ_OFFSET       : 0.   \",\n            \"HORIZ_INTERVAL     : 2.5        \",\n        ],\n        sep=\"\\n\",\n    ) as osc:\n        if numpy:\n            expected_wf = numpy.array([[0.0, 2.5, 5.0, 7.5], [1.0, 2.0, 3.0, 4.0]])\n        else:\n            expected_wf = ((0.0, 2.5, 5.0, 7.5), (1.0, 2.0, 3.0, 4.0))\n        actual_wf = osc.channel[0].read_waveform()\n        iterable_eq(actual_wf, expected_wf)\n\n\ndef test_maui_data_source_read_waveform_different_length(init):\n    \"\"\"BF: Stacking return arrays with different length.\n\n    Depending on rounding issues, time and data arrays can have\n    different lengths. Shorten to the shorter one by cutting the longer\n    one from the end.\n    \"\"\"\n    faulty_dataset_str = []\n    faulty_dataset_int = []\n    for it in range(402):  # 402 datapoints will make the error\n        faulty_dataset_str.append(str(it))\n        faulty_dataset_int.append(it)\n    return_data_string = '\"   ' + \"   \".join(faulty_dataset_str) + '   \"'\n\n    with expected_protocol(\n        ik.teledyne.MAUI,\n        [\n            init,\n            \"TRMD?\",\n            \"TRMD SINGLE\",\n            \"C1:INSPECT? 'SIMPLE'\",\n            \"C1:INSPECT? 'HORIZ_OFFSET'\",\n            \"C1:INSPECT? 'HORIZ_INTERVAL'\",\n            \"TRMD AUTO\",\n        ],\n        [\n            \"AUTO\",\n            return_data_string,\n            \"HORIZ_OFFSET       : 9.8895e-06   \",\n            \"HORIZ_INTERVAL     : 5e-10        \",\n        ],\n        sep=\"\\n\",\n    ) as osc:\n        h_offset = 9.8895e-06\n        h_interval = 5e-10\n\n        if numpy:\n            # create the signal that we want to get returned\n            signal = numpy.array(faulty_dataset_int)\n            timebase = numpy.arange(\n                h_offset, h_offset + h_interval * (len(signal)), h_interval\n            )\n\n            # now cut timebase to the length of the signal\n            timebase = timebase[0 : len(signal)]\n            # create return dataset\n            dataset_return = numpy.stack((timebase, signal))\n        else:\n            signal = tuple(faulty_dataset_int)\n            timebase = tuple(\n                float(val) * h_interval + h_offset for val in range(len(signal))\n            )\n            timebase = timebase[0 : len(signal)]\n            dataset_return = timebase, signal\n\n        actual_wf = osc.channel[0].read_waveform()\n        iterable_eq(actual_wf, dataset_return)\n\n\ndef test_maui_data_source_read_waveform_math(init):\n    \"\"\"Return a numpy array of a waveform for multiple sweeps.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI,\n        [\n            init,\n            \"C1:INSPECT? 'SIMPLE'\",\n            \"C1:INSPECT? 'HORIZ_OFFSET'\",\n            \"C1:INSPECT? 'HORIZ_INTERVAL'\",\n        ],\n        [\n            '\"  1.   2.   3.   4.  \"',\n            \"HORIZ_OFFSET       : 0.   \",\n            \"HORIZ_INTERVAL     : 2.5        \",\n        ],\n        sep=\"\\n\",\n    ) as osc:\n        if numpy:\n            expected_wf = numpy.array([[0.0, 2.5, 5.0, 7.5], [1.0, 2.0, 3.0, 4.0]])\n        else:\n            expected_wf = ((0.0, 2.5, 5.0, 7.5), (1.0, 2.0, 3.0, 4.0))\n        actual_wf = osc.channel[0].read_waveform(single=False)\n        iterable_eq(actual_wf, expected_wf)\n\n\ndef test_maui_data_source_read_waveform_bin_format(init):\n    \"\"\"Raise a not implemented error.\"\"\"\n    with expected_protocol(ik.teledyne.MAUI, [init], [], sep=\"\\n\") as osc:\n        with pytest.raises(NotImplementedError):\n            osc.channel[0].read_waveform(bin_format=True)\n\n\ndef test_maui_data_source_trace(init):\n    \"\"\"Get / Set the on/off status of a trace.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI, [init, \"F1:TRA?\", \"C1:TRA ON\", \"C3:TRA OFF\"], [\"ON\"], sep=\"\\n\"\n    ) as osc:\n        assert osc.math[0].trace\n        osc.channel[0].trace = True\n        osc.channel[2].trace = False\n\n\n# TEST CHANNEL CLASS #\n\n\ndef test_maui_channel_init(init):\n    \"\"\"Initialize a MAUI Channel.\"\"\"\n    with expected_protocol(ik.teledyne.MAUI, [init], [], sep=\"\\n\") as osc:\n        assert osc.channel[0]._idx == 1\n        assert isinstance(osc.channel[0]._parent, ik.teledyne.MAUI)\n\n\ndef test_maui_channel_coupling(init):\n    \"\"\"Get / Set MAUI Channel coupling.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI, [init, \"C3:CPL?\", \"C3:CPL A1M\"], [\"D50\"], sep=\"\\n\"\n    ) as osc:\n        assert osc.channel[2].coupling == osc.channel[2].Coupling.dc50\n        osc.channel[2].coupling = osc.channel[2].Coupling.ac1M\n\n\ndef test_maui_channel_offset(init):\n    \"\"\"Get / Set MAUI Channel offset.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI,\n        [init, \"C1:OFST?\", \"C1:OFST 0.2\", \"C3:OFST 2\"],\n        [\"1\"],\n        sep=\"\\n\",\n    ) as osc:\n        assert osc.channel[0].offset == u.Quantity(1, u.V)\n        osc.channel[0].offset = u.Quantity(200, u.mV)\n        osc.channel[2].offset = 2\n\n\ndef test_maui_channel_scale(init):\n    \"\"\"Get / Set MAUI Channel scale.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI,\n        [init, \"C2:VDIV?\", \"C1:VDIV 2.0\", \"C2:VDIV 0.4\"],\n        [\"1\"],\n        sep=\"\\n\",\n    ) as osc:\n        assert osc.channel[1].scale == u.Quantity(1, u.V)\n        osc.channel[0].scale = u.Quantity(2000, u.mV)\n        osc.channel[1].scale = 0.4\n\n\n# TEST MATH CLASS #\n\n\ndef test_maui_math_init(init):\n    \"\"\"Initialize math channel.\"\"\"\n    with expected_protocol(ik.teledyne.MAUI, [init], [], sep=\"\\n\") as osc:\n        assert osc.math[0]._idx == 1\n        assert isinstance(osc.math[0]._parent, ik.teledyne.MAUI)\n\n\ndef test_maui_math_clear_sweeps(init):\n    \"\"\"Clears math channel sweeps.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI, [init, \"CLEAR_SWEEPS\"], [], sep=\"\\n\"\n    ) as osc:\n        osc.math[0].clear_sweeps()\n\n\n# TEST MATH CLASS OPERATORS #\n\n\ndef test_maui_math_op_current_settings(init):\n    \"\"\"Return the current settings as oscilloscope string.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI,\n        [init, \"F1:DEF?\"],\n        [\"bla bla bla\"],  # answer unimportant\n        sep=\"\\n\",\n    ) as osc:\n        assert osc.math[0].operator.current_setting == \"bla bla bla\"\n\n\ndef test_maui_math_op_absolute(init):\n    \"\"\"Set math channel, absolute operator.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI, [init, \"F1:DEFINE EQN,'ABS(C1)'\"], [], sep=\"\\n\"\n    ) as osc:\n        osc.math[0].operator.absolute(0)\n\n\ndef test_maui_math_op_average(init):\n    \"\"\"Set math channel, average operator.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI,\n        [\n            init,\n            \"F1:DEFINE EQN,'AVG(C1)',AVERAGETYPE,SUMMED,SWEEPS,1000\",\n            \"F1:DEFINE EQN,'AVG(C2)',AVERAGETYPE,CONTINUOUS,SWEEPS,100\",\n        ],\n        [],\n        sep=\"\\n\",\n    ) as osc:\n        osc.math[0].operator.average(0)\n        osc.math[0].operator.average(1, average_type=\"continuous\", sweeps=100)\n\n\ndef test_maui_math_op_derivative(init):\n    \"\"\"Set math channel, derivative operator.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI,\n        [\n            init,\n            \"F1:DEFINE EQN,'DERI(C1)',VERSCALE,1000000.0,\"\n            \"VEROFFSET,0,ENABLEAUTOSCALE,ON\",\n            \"F1:DEFINE EQN,'DERI(C3)',VERSCALE,5.0,\"\n            \"VEROFFSET,1.0,ENABLEAUTOSCALE,OFF\",\n        ],\n        [],\n        sep=\"\\n\",\n    ) as osc:\n        osc.math[0].operator.derivative(0)\n        osc.math[0].operator.derivative(\n            2,\n            vscale=u.Quantity(5000, u.mV / u.s),\n            voffset=u.Quantity(60, u.V / u.min),\n            autoscale=False,\n        )\n\n\ndef test_maui_math_op_difference(init):\n    \"\"\"Set math channel, difference operator.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI,\n        [\n            init,\n            \"F1:DEFINE EQN,'C1-C3',VERSCALEVARIABLE,FALSE\",\n            \"F1:DEFINE EQN,'C1-C3',VERSCALEVARIABLE,TRUE\",\n        ],\n        [],\n        sep=\"\\n\",\n    ) as osc:\n        osc.math[0].operator.difference(0, 2)\n        osc.math[0].operator.difference(0, 2, vscale_variable=True)\n\n\ndef test_maui_math_op_envelope(init):\n    \"\"\"Set math channel, envelope operator.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI,\n        [\n            init,\n            \"F1:DEFINE EQN,'EXTR(C1)',SWEEPS,1000,LIMITNUMSWEEPS,True\",\n            \"F1:DEFINE EQN,'EXTR(F2)',SWEEPS,10,LIMITNUMSWEEPS,False\",\n        ],\n        [],\n        sep=\"\\n\",\n    ) as osc:\n        osc.math[0].operator.envelope(0)\n        osc.math[0].operator.envelope((\"f\", 1), sweeps=10, limit_sweeps=False)\n\n\ndef test_maui_math_op_eres(init):\n    \"\"\"Set math channel, eres operator.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI,\n        [\n            init,\n            \"F1:DEFINE EQN,'ERES(C1)',BITS,0.5\",\n            \"F1:DEFINE EQN,'ERES(C1)',BITS,3\",\n            \"F1:DEFINE EQN,'ERES(C1)',BITS,0.5\",\n        ],\n        [],\n        sep=\"\\n\",\n    ) as osc:\n        osc.math[0].operator.eres(0)\n        osc.math[0].operator.eres(0, 3)\n        osc.math[0].operator.eres(0, 28)\n\n\ndef test_maui_math_op_fft(init):\n    \"\"\"Set math channel, fft operator.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI,\n        [\n            init,\n            \"F1:DEFINE EQN,'FFT(C1)',TYPE,powerspectrum,\"\n            \"WINDOW,vonhann,SUPPRESSDC,ON\",\n            \"F1:DEFINE EQN,'FFT(C1)',TYPE,real,\" \"WINDOW,flattop,SUPPRESSDC,OFF\",\n            \"F1:DEFINE EQN,'FFT(C1)',TYPE,powerspectrum,\"\n            \"WINDOW,vonhann,SUPPRESSDC,ON\",\n        ],\n        [],\n        sep=\"\\n\",\n    ) as osc:\n        osc.math[0].operator.fft(0)\n        osc.math[0].operator.fft(0, type=\"real\", window=\"flattop\", suppress_dc=False)\n        osc.math[0].operator.fft(0, type=\"inv str\", window=\"inv str\", suppress_dc=1)\n\n\ndef test_maui_math_op_floor(init):\n    \"\"\"Set math channel, floor operator.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI,\n        [\n            init,\n            \"F1:DEFINE EQN,'FLOOR(C1)',SWEEPS,1000,LIMITNUMSWEEPS,True\",\n            \"F1:DEFINE EQN,'FLOOR(C1)',SWEEPS,10,LIMITNUMSWEEPS,False\",\n        ],\n        [],\n        sep=\"\\n\",\n    ) as osc:\n        osc.math[0].operator.floor(0)\n        osc.math[0].operator.floor(0, sweeps=10, limit_sweeps=False)\n\n\ndef test_maui_math_op_integral(init):\n    \"\"\"Set math channel, integral operator.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI,\n        [\n            init,\n            \"F1:DEFINE EQN,'INTG(C1),MULTIPLIER,1,ADDER,0,\"\n            \"VERSCALE,0.001,VEROFFSET,0\",\n            \"F1:DEFINE EQN,'INTG(C4),MULTIPLIER,10,ADDER,0.5,\"\n            \"VERSCALE,1,VEROFFSET,0.001\",\n            \"F1:DEFINE EQN,'INTG(C4),MULTIPLIER,10,ADDER,0.5,\"\n            \"VERSCALE,1,VEROFFSET,0.001\",\n        ],\n        [],\n        sep=\"\\n\",\n    ) as osc:\n        osc.math[0].operator.integral(0)\n        osc.math[0].operator.integral(\n            3,\n            multiplier=10,\n            adder=0.5,\n            vscale=u.Quantity(1, u.Wb),\n            voffset=u.Quantity(0.001, u.Wb),\n        )\n        osc.math[0].operator.integral(\n            3, multiplier=10, adder=0.5, vscale=1, voffset=0.001\n        )\n\n\ndef test_maui_math_op_invert(init):\n    \"\"\"Set math channel, invert operator.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI, [init, \"F1:DEFINE EQN,'-C1'\"], [], sep=\"\\n\"\n    ) as osc:\n        osc.math[0].operator.invert(0)\n\n\ndef test_maui_math_op_product(init):\n    \"\"\"Set math channel, product operator.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI, [init, \"F1:DEFINE EQN,'C1*C3'\"], [], sep=\"\\n\"\n    ) as osc:\n        osc.math[0].operator.product(0, 2)\n\n\ndef test_maui_math_op_ratio(init):\n    \"\"\"Set math channel, ratio operator.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI, [init, \"F1:DEFINE EQN,'C1/C3'\"], [], sep=\"\\n\"\n    ) as osc:\n        osc.math[0].operator.ratio(0, 2)\n\n\ndef test_maui_math_op_reciprocal(init):\n    \"\"\"Set math channel, reciprocal operator.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI, [init, \"F1:DEFINE EQN,'1/C3'\"], [], sep=\"\\n\"\n    ) as osc:\n        osc.math[0].operator.reciprocal(2)\n\n\ndef test_maui_math_op_rescale(init):\n    \"\"\"Set math channel, rescale operator.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI,\n        [\n            init,\n            \"F1:DEFINE EQN,'RESC(C3)',MULTIPLIER,1,ADDER,0\",\n            \"F1:DEFINE EQN,'RESC(C3)',MULTIPLIER,10.3,ADDER,1.3\",\n        ],\n        [],\n        sep=\"\\n\",\n    ) as osc:\n        osc.math[0].operator.rescale(2)\n        osc.math[0].operator.rescale(2, multiplier=10.3, adder=1.3)\n\n\ndef test_maui_math_op_sinx(init):\n    \"\"\"Set math channel, sin(x)/x operator.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI, [init, \"F1:DEFINE EQN,'SINX(C3)'\"], [], sep=\"\\n\"\n    ) as osc:\n        osc.math[0].operator.sinx(2)\n\n\ndef test_maui_math_op_square(init):\n    \"\"\"Set math channel, square operator.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI, [init, \"F1:DEFINE EQN,'SQR(C3)'\"], [], sep=\"\\n\"\n    ) as osc:\n        osc.math[0].operator.square(2)\n\n\ndef test_maui_math_op_square_root(init):\n    \"\"\"Set math channel, square root operator.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI, [init, \"F1:DEFINE EQN,'SQRT(C3)'\"], [], sep=\"\\n\"\n    ) as osc:\n        osc.math[0].operator.square_root(2)\n\n\ndef test_maui_math_op_sum(init):\n    \"\"\"Set math channel, sum operator.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI, [init, \"F1:DEFINE EQN,'C3+C1'\"], [], sep=\"\\n\"\n    ) as osc:\n        osc.math[0].operator.sum(2, 0)\n\n\ndef test_maui_math_op_trend(init):\n    \"\"\"Set math channel, trend operator.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI,\n        [\n            init,\n            \"F1:DEFINE EQN,'TREND(C1)',VERSCALE,1,CENTER,0,\" \"AUTOFINDSCALE,ON\",\n            \"F1:DEFINE EQN,'TREND(C2)',VERSCALE,2,CENTER,1,\" \"AUTOFINDSCALE,OFF\",\n            \"F1:DEFINE EQN,'TREND(C2)',VERSCALE,2,CENTER,1,\" \"AUTOFINDSCALE,ON\",\n        ],\n        [],\n        sep=\"\\n\",\n    ) as osc:\n        osc.math[0].operator.trend(0)\n        osc.math[0].operator.trend(\n            1, vscale=u.Quantity(2, u.V), center=u.Quantity(1, u.V), autoscale=False\n        )\n        osc.math[0].operator.trend(1, vscale=2, center=1, autoscale=True)\n\n\ndef test_maui_math_op_roof(init):\n    \"\"\"Set math channel, roof operator.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI,\n        [\n            init,\n            \"F1:DEFINE EQN,'ROOF(C1)',SWEEPS,1000,LIMITNUMSWEEPS,True\",\n            \"F1:DEFINE EQN,'ROOF(C1)',SWEEPS,10,LIMITNUMSWEEPS,False\",\n        ],\n        [],\n        sep=\"\\n\",\n    ) as osc:\n        osc.math[0].operator.roof(0)\n        osc.math[0].operator.roof(0, sweeps=10, limit_sweeps=False)\n\n\n# TEST MEASUREMENT CLASS #\n\n\ndef test_maui_measurement_init(init):\n    \"\"\"Initialize measurement class.\"\"\"\n    with expected_protocol(ik.teledyne.MAUI, [init], [], sep=\"\\n\") as osc:\n        assert osc.measurement[0]._idx == 1\n        assert isinstance(osc.measurement[0]._parent, ik.teledyne.MAUI)\n\n\ndef test_maui_measurement_state(init):\n    \"\"\"Get / Set measurement state.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI, [init, \"PARM?\", \"PARM CUST,HISTICON\"], [\"CUST,OFF\"], sep=\"\\n\"\n    ) as osc:\n        assert osc.measurement[0].measurement_state == osc.measurement[0].State.off\n        osc.measurement[0].measurement_state = osc.measurement[0].State.histogram_icon\n\n\ndef test_maui_measurement_statistics_error(init):\n    \"\"\"Raise ValueError if statistics cannot be gathered.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI,\n        [init, \"PAST? CUST, P1\"],\n        [\n            \"CUST,P1,NULL,C3,AVG,UNDEF,HIGH,UNDEF,LAST,UNDEF,LOW,UNDEF,\"\n            \"SIGMA,UNDEF,SWEEPS,0\"\n        ],\n        sep=\"\\n\",\n    ) as osc:\n        with pytest.raises(ValueError):\n            print(osc.measurement[0].statistics)\n\n\ndef test_maui_measurement_statistics(init):\n    \"\"\"Get statistics for given measurement.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI,\n        [init, \"PAST? CUST, P1\"],\n        [\"CUST,P1,MEAN,C3,AVG,10,HIGH,20,LAST,30,LOW,40,SIGMA,50,\" \"SWEEPS,42\"],\n        sep=\"\\n\",\n    ) as osc:\n        assert osc.measurement[0].statistics == (10.0, 40.0, 20.0, 50.0, 42.0)\n\n\ndef test_maui_measurement_delete(init):\n    \"\"\"Delete a given measurement.\"\"\"\n    with expected_protocol(ik.teledyne.MAUI, [init, \"PADL 1\"], [], sep=\"\\n\") as osc:\n        osc.measurement[0].delete()\n\n\ndef test_maui_measurement_set_parameter(init):\n    \"\"\"Set a specific parameter.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI, [init, \"PACU 1,AMPL,C3\"], [], sep=\"\\n\"\n    ) as osc:\n        osc.measurement[0].set_parameter(osc.MeasurementParameters.amplitude, 2)\n\n\ndef test_maui_measurement_set_invalid_parameter(init):\n    \"\"\"Set a specific parameter.\"\"\"\n    with expected_protocol(ik.teledyne.MAUI, [init], [], sep=\"\\n\") as osc:\n        with pytest.raises(AttributeError):\n            osc.measurement[0].set_parameter(\"amplitude\", 2)\n\n\n# TEST CLASS PROPERTIES AND METHODS #\n\n\ndef test_maui_ref(init):\n    \"\"\"Reference class in oscillioscope is not implemented.\"\"\"\n    with expected_protocol(ik.teledyne.MAUI, [init], [], sep=\"\\n\") as osc:\n        with pytest.raises(NotImplementedError):\n            assert osc.ref\n\n\ndef test_maui_number_channels(init):\n    \"\"\"Set / Get number of channels.\"\"\"\n    with expected_protocol(ik.teledyne.MAUI, [init], [], sep=\"\\n\") as osc:\n        osc.number_channels = 42\n        assert osc.number_channels == 42\n\n\ndef test_maui_number_functions(init):\n    \"\"\"Set / Get number of functions.\"\"\"\n    with expected_protocol(ik.teledyne.MAUI, [init], [], sep=\"\\n\") as osc:\n        osc.number_functions = 42\n        assert osc.number_functions == 42\n\n\ndef test_maui_number_measurements(init):\n    \"\"\"Set / Get number of measurements.\"\"\"\n    with expected_protocol(ik.teledyne.MAUI, [init], [], sep=\"\\n\") as osc:\n        osc.number_measurements = 42\n        assert osc.number_measurements == 42\n\n\ndef test_maui_self_test(init):\n    \"\"\"Runs an oscilloscope self test.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI, [init, \"*TST?\"], [\"*TST 0\"], sep=\"\\n\"\n    ) as osc:\n        assert osc.self_test == \"*TST 0\"  # Status: OK\n\n\ndef test_maui_show_id(init):\n    \"\"\"Displays oscilloscope ID.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI,\n        [init, \"*IDN?\"],\n        [\"*IDN LECROY,WAVEMASTER,WM01000,3.3.0\"],\n        sep=\"\\n\",\n    ) as osc:\n        assert osc.show_id == \"*IDN LECROY,WAVEMASTER,WM01000,3.3.0\"\n\n\ndef test_maui_show_options(init):\n    \"\"\"Displays oscilloscope options.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI, [init, \"*OPT?\"], [\"*OPT 0\"], sep=\"\\n\"\n    ) as osc:\n        assert osc.show_options == \"*OPT 0\"  # no options installed\n\n\ndef test_maui_time_div(init):\n    \"\"\"Get / Set time per division.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI, [init, \"TDIV?\", \"TDIV 0.001\"], [\"1\"], sep=\"\\n\"\n    ) as osc:\n        assert osc.time_div == u.Quantity(1, u.s)\n        osc.time_div = u.Quantity(1, u.ms)\n\n\ndef test_maui_trigger_state(init):\n    \"\"\"Get / Set trigger state.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI, [init, \"TRMD?\", \"TRMD SINGLE\"], [\"AUTO\"], sep=\"\\n\"\n    ) as osc:\n        assert osc.trigger_state == osc.TriggerState.auto\n        osc.trigger_state = osc.TriggerState.single\n\n\ndef test_maui_trigger_delay(init):\n    \"\"\"Get / Set trigger delay.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI, [init, \"TRDL?\", \"TRDL 0.001\", \"TRDL 1\"], [\"0.001\"], sep=\"\\n\"\n    ) as osc:\n        assert osc.trigger_delay == u.Quantity(1, u.ms)\n        osc.trigger_delay = u.Quantity(1, u.ms)\n        osc.trigger_delay = 1\n\n\ndef test_maui_trigger_source(init):\n    \"\"\"Get / Set trigger source.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI,\n        [init, \"TRIG_SELECT?\", \"TRIG_SELECT?\", \"TRIG_SELECT EDGE,SR,EX\"],\n        [\"EDGE,SR,C1,HT,OFF\", \"EDGE,SR,C1,HT,OFF\"],\n        sep=\"\\n\",\n    ) as osc:\n        assert osc.trigger_source == osc.TriggerSource.c0\n        osc.trigger_source = osc.TriggerSource.ext\n\n\ndef test_maui_trigger_type(init):\n    \"\"\"Get / Set trigger type.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI,\n        [init, \"TRIG_SELECT?\", \"TRIG_SELECT?\", \"TRIG_SELECT EDGE,SR,C3\"],\n        [\"RUNT,SR,C3,HT,OFF\", \"EDGE,SR,C3,HT,OFF\"],\n        sep=\"\\n\",\n    ) as osc:\n        assert osc.trigger_type == osc.TriggerType.runt\n        osc.trigger_type = osc.TriggerType.edge\n\n\ndef test_maui_clear_sweeps(init):\n    \"\"\"Clear the sweeps.\"\"\"\n    with expected_protocol(\n        ik.teledyne.MAUI, [init, \"CLEAR_SWEEPS\"], [], sep=\"\\n\"\n    ) as osc:\n        osc.clear_sweeps()\n\n\ndef test_maui_force_trigger(init):\n    \"\"\"Force a trigger.\"\"\"\n    with expected_protocol(ik.teledyne.MAUI, [init, \"ARM\"], [], sep=\"\\n\") as osc:\n        osc.force_trigger()\n\n\ndef test_maui_run(init):\n    \"\"\"Run the measurement in automatic trigger state.\"\"\"\n    with expected_protocol(ik.teledyne.MAUI, [init, \"TRMD AUTO\"], [], sep=\"\\n\") as osc:\n        osc.run()\n\n\ndef test_maui_stop(init):\n    \"\"\"Stop the acquisition.\"\"\"\n    with expected_protocol(ik.teledyne.MAUI, [init, \"STOP\"], [], sep=\"\\n\") as osc:\n        osc.stop()\n\n\n# STATIC FUNCTIONS #\n\n\ndef test_source_stichting_integer():\n    \"\"\"Source stiching when an integer is given.\"\"\"\n    assert ik.teledyne.maui._source(3) == \"C4\"\n\n\ndef test_source_stiching_tuple():\n    \"\"\"Source stiching when a tuple is given.\"\"\"\n    assert ik.teledyne.maui._source((\"p\", 0)) == \"P1\"\n    assert ik.teledyne.maui._source((\"P\", 0)) == \"P1\"\n\n\ndef test_source_stiching_value_error():\n    \"\"\"Raise a value error if anything else.\"\"\"\n    with pytest.raises(ValueError):\n        ik.teledyne.maui._source(3.14)\n"
  },
  {
    "path": "tests/test_test_utils.py",
    "content": "\"\"\"\nModule containing tests for test-related utility functions\n\"\"\"\n\nfrom hypothesis import (\n    given,\n    strategies as st,\n)\nimport pytest\n\nfrom instruments.optional_dep_finder import numpy\nfrom tests import iterable_eq\nfrom instruments.units import ureg as u\n\n# TESTS #######################################################################\n\n\n@given(a=st.lists(st.floats()))\ndef test_iterable_eq_passes_basic(a):\n    \"\"\"\n    Test that two identical lists and tuples always pass\n    \"\"\"\n    b = a[:]\n    iterable_eq(a, b)\n    iterable_eq(tuple(a), tuple(b))\n\n\n@pytest.mark.parametrize(\"b\", [(0, 1, 2), (0,)])\ndef test_iterable_eq_fails(b):\n    \"\"\"\n    Test failure on equal and mismatched lengths\n    \"\"\"\n    a = (1, 2, 3)\n    with pytest.raises(AssertionError):\n        iterable_eq(a, b)\n\n\ndef test_iterable_eq_fails_type_mismatch():\n    \"\"\"\n    Test failure on type mismatch\n    \"\"\"\n    a = [1, 2, 3]\n    with pytest.raises(AssertionError):\n        iterable_eq(a, tuple(a))\n\n\ndef test_iterable_eq_passes_sequence_quantity():\n    \"\"\"\n    Test passes on equal sequences with unitful values\n    \"\"\"\n    a = (1 * u.V, 2 * u.A)\n    iterable_eq(a, a[:])\n\n\ndef test_iterable_eq_fails_sequence_quantity():\n    \"\"\"\n    Test failure on unitful sequences with wrong units, and wrong magnitudes\n    \"\"\"\n    a = (1 * u.V, 2 * u.A)\n    b = (1 * u.A, 2 * u.A)  # Different units\n    with pytest.raises(AssertionError):\n        iterable_eq(a, b)\n\n    b = (1 * u.V, 3 * u.A)  # Different magnitude\n    with pytest.raises(AssertionError):\n        iterable_eq(a, b)\n\n\ndef test_iterable_eq_passes_singular_quantity():\n    \"\"\"\n    Test passes on singular unitful quantity\n    \"\"\"\n    iterable_eq(1 * u.V, 1 * u.V)\n\n\ndef test_iterable_eq_fails_singular_quantity():\n    \"\"\"\n    Test failure on singular unitful quantity with wrong units\n    \"\"\"\n    a = 1 * u.V\n    b = 1 * u.A\n    with pytest.raises(AssertionError):\n        iterable_eq(a, b)\n\n\n@pytest.mark.skipif(numpy is None, reason=\"Only run if numpy installed\")\ndef test_iterable_eq_passes_two_numpy_array():\n    \"\"\"\n    Test pases for two identical numpy arrays\n    \"\"\"\n    a = numpy.array([1, 2, 3])\n    iterable_eq(a, a.copy())\n\n\n@pytest.mark.skipif(numpy is None, reason=\"Only run if numpy installed\")\ndef test_iterable_eq_fails_one_numpy_array_equal_values():\n    \"\"\"\n    Test failure for one is numpy array, other is not\n    \"\"\"\n    a = numpy.array([1, 2, 3])\n    b = (1, 2, 3)\n    with pytest.raises(AssertionError):\n        iterable_eq(a, b)\n\n\n@pytest.mark.skipif(numpy is None, reason=\"Only run if numpy installed\")\n@pytest.mark.parametrize(\"b\", [(1, 6, 3), (1, 2)])\ndef test_iterable_eq_fails_one_numpy_array(b):\n    \"\"\"\n    Test that different value and different length\n    comparisons against numpy arrays fail\n    \"\"\"\n    a = numpy.array([1, 2, 3])\n    with pytest.raises(AssertionError):\n        iterable_eq(a, b)\n\n\n@pytest.mark.skipif(numpy is None, reason=\"Only run if numpy installed\")\n@pytest.mark.parametrize(\"b\", [(1, 6, 3), (1, 2)])\ndef test_iterable_eq_fails_two_numpy_array(b):\n    \"\"\"\n    Test that different value and different length\n    comparisons against numpy arrays fail\n    \"\"\"\n    a = numpy.array([1, 2, 3])\n    b = numpy.array(b)\n    with pytest.raises(AssertionError):\n        iterable_eq(a, b)\n\n\n@pytest.mark.skipif(numpy is None, reason=\"Only run if numpy installed\")\ndef test_iterable_eq_passes_two_numpy_array_quantities():\n    \"\"\"\n    Test that two unitful quantities with numpy array data\n    will equal data will pass\n    \"\"\"\n    values = [1, 2, 3]\n    a = numpy.array(values) * u.V\n    b = numpy.array(values) * u.V\n    iterable_eq(a, b)\n"
  },
  {
    "path": "tests/test_thorlabs/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_thorlabs/test_abstract.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the abstract ThorlabsInstrument class.\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport struct\nimport time\n\nimport pytest\n\nimport instruments as ik\nfrom instruments.thorlabs import _packets\nfrom tests import expected_protocol\n\n# TESTS ######################################################################\n\n\nexample_packet = _packets.ThorLabsPacket(\n    message_id=0x0000, param1=0x00, param2=0x00, dest=0x50, source=0x01, data=None\n)\n\n\nexample_packet_with_data = _packets.ThorLabsPacket(\n    message_id=0x0000,\n    param1=None,\n    param2=None,\n    dest=0x50,\n    source=0x01,\n    data=struct.pack(\"<HH\", 0, 1),\n)\n\n\n# pylint: disable=protected-access\n\n\ndef test_init():\n    \"\"\"Initialize the instrument and set terminator.\"\"\"\n    with expected_protocol(\n        ik.thorlabs._abstract.ThorLabsInstrument, [], [], sep=\"\"\n    ) as inst:\n        assert inst.terminator == \"\"\n\n\ndef test_send_packet():\n    \"\"\"Send a packet using write_raw.\"\"\"\n    with expected_protocol(\n        ik.thorlabs._abstract.ThorLabsInstrument, [example_packet.pack()], [], sep=\"\"\n    ) as inst:\n        inst.sendpacket(example_packet)\n\n\ndef test_query_packet(mocker):\n    \"\"\"Query the simple example packet without data.\n\n    For simplicity, the query and response packet are the same.\n    \"\"\"\n    with expected_protocol(\n        ik.thorlabs._abstract.ThorLabsInstrument,\n        [example_packet.pack()],\n        [example_packet.pack()],\n        sep=\"\",\n    ) as inst:\n        read_raw_spy = mocker.spy(inst._file, \"read_raw\")\n        packet_return = inst.querypacket(example_packet)\n        assert packet_return.message_id == example_packet.message_id\n        assert packet_return.parameters == example_packet.parameters\n        assert packet_return.destination == example_packet.destination\n        assert packet_return.source == example_packet.source\n        assert packet_return.data == example_packet.data\n        # assert read_raw is called with proper length\n        read_raw_spy.assert_called_with(6)\n\n\ndef test_query_packet_with_data(mocker):\n    \"\"\"Query the simple example packet with data.\"\"\"\n    data_length = 4\n    with expected_protocol(\n        ik.thorlabs._abstract.ThorLabsInstrument,\n        [example_packet.pack()],\n        [example_packet_with_data.pack()],\n        sep=\"\",\n    ) as inst:\n        read_raw_spy = mocker.spy(inst._file, \"read_raw\")\n        packet_return = inst.querypacket(example_packet, expect_data_len=data_length)\n        assert packet_return.data == example_packet_with_data.data\n        # assert read_raw is called with proper length\n        read_raw_spy.assert_called_with(data_length + 6)\n\n\ndef test_query_packet_expect_none_no_respnse():\n    \"\"\"Query a packet but no response, none expected.\"\"\"\n    with expected_protocol(\n        ik.thorlabs._abstract.ThorLabsInstrument, [example_packet.pack()], [], sep=\"\"\n    ) as inst:\n        assert inst.querypacket(example_packet, expect=None) is None\n\n\ndef test_query_packet_expect_but_no_respnse():\n    \"\"\"Raise IO error when expecting a package but none returned.\"\"\"\n    expect = 0x0000\n    with expected_protocol(\n        ik.thorlabs._abstract.ThorLabsInstrument, [example_packet.pack()], [], sep=\"\"\n    ) as inst:\n        with pytest.raises(IOError) as err_info:\n            inst.querypacket(example_packet, expect=expect)\n        err_msg = err_info.value.args[0]\n        assert err_msg == f\"Expected packet {expect}, got nothing instead.\"\n\n\ndef test_query_packet_wrong_package_received():\n    \"\"\"Raise IOError if an unexpected package is received.\"\"\"\n    expect = 0x002A\n    with expected_protocol(\n        ik.thorlabs._abstract.ThorLabsInstrument,\n        [example_packet.pack()],\n        [example_packet.pack()],\n        sep=\"\",\n    ) as inst:\n        with pytest.raises(IOError) as err_info:\n            inst.querypacket(example_packet, expect=expect)\n        err_msg = err_info.value.args[0]\n        assert (\n            err_msg == f\"APT returned message ID \"\n            f\"{example_packet.message_id}, expected {expect}\"\n        )\n\n\ndef test_query_packet_no_response_with_timeout(mocker):\n    \"\"\"Query a packet but no response, timeout is set.\"\"\"\n    with expected_protocol(\n        ik.thorlabs._abstract.ThorLabsInstrument, [example_packet.pack()], [], sep=\"\"\n    ) as inst:\n        time_spy = mocker.spy(time, \"time\")\n        assert inst.querypacket(example_packet, timeout=0) is None\n        # timeout set to zero, assert `time.time()`  called twice\n        assert time_spy.call_count >= 2\n"
  },
  {
    "path": "tests/test_thorlabs/test_packets.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the ThorLabsPacket class.\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport struct\n\nimport pytest\n\nfrom instruments.thorlabs import _packets\n\n# TESTS ######################################################################\n\n\n# pylint: disable=protected-access,redefined-outer-name\n\n\n# variable to parametrize parameters or data: param1, param2, data, has_data\nparams_or_data = (\n    (0x00, 0x00, None, False),\n    (None, None, struct.pack(\"<HH\", 0x00, 0x01), True),\n)\n\n\n@pytest.mark.parametrize(\"params_or_data\", params_or_data)\ndef test_init(params_or_data):\n    \"\"\"Initialize a Thorlabs packet without data.\"\"\"\n    message_id = 0x0000\n    param1 = params_or_data[0]\n    param2 = params_or_data[1]\n    dest = 0x50\n    source = 0x01\n    data = params_or_data[2]\n    pkt = _packets.ThorLabsPacket(\n        message_id=message_id,\n        param1=param1,\n        param2=param2,\n        dest=dest,\n        source=source,\n        data=data,\n    )\n    # assert that variables are set correctly\n    assert pkt._message_id == message_id\n    assert pkt._param1 == param1\n    assert pkt._param2 == param2\n    assert pkt._data == data\n    assert pkt._dest == dest\n    assert pkt._source == source\n    assert pkt._has_data is params_or_data[3]\n\n\ndef test_init_no_params_no_data():\n    \"\"\"Raise ValueError if package initialized without parameters or data.\"\"\"\n    message_id = 0x0000\n    param1 = None\n    param2 = None\n    dest = 0x50\n    source = 0x01\n    data = None\n    with pytest.raises(ValueError) as err_info:\n        _ = _packets.ThorLabsPacket(\n            message_id=message_id,\n            param1=param1,\n            param2=param2,\n            dest=dest,\n            source=source,\n            data=data,\n        )\n    err_msg = err_info.value.args[0]\n    assert err_msg == \"Must specify either parameters or data.\"\n\n\ndef test_init_params_and_data():\n    \"\"\"Raise ValueError if package initialized with parameters and data.\"\"\"\n    message_id = 0x0000\n    param1 = 0x00\n    param2 = 0x00\n    dest = 0x50\n    source = 0x01\n    data = struct.pack(\"<HH\", 0x00, 0x01)\n    with pytest.raises(ValueError) as err_info:\n        _ = _packets.ThorLabsPacket(\n            message_id=message_id,\n            param1=param1,\n            param2=param2,\n            dest=dest,\n            source=source,\n            data=data,\n        )\n    err_msg = err_info.value.args[0]\n    assert (\n        err_msg == \"A ThorLabs packet can either have parameters or \"\n        \"data, but not both.\"\n    )\n\n\n@pytest.mark.parametrize(\"params_or_data\", params_or_data)\ndef test_string(params_or_data):\n    \"\"\"Return a string of the class.\"\"\"\n    message_id = 0x0000\n    param1 = params_or_data[0]\n    param2 = params_or_data[1]\n    dest = 0x50\n    source = 0x01\n    data = params_or_data[2]\n    pkt = _packets.ThorLabsPacket(\n        message_id=message_id,\n        param1=param1,\n        param2=param2,\n        dest=dest,\n        source=source,\n        data=data,\n    )\n    string_expected = \"\"\"\nThorLabs APT packet:\n    Message ID      {}\n    Parameter 1     {}\n    Parameter 2     {}\n    Destination     {}\n    Source          {}\n    Data            {}\n\"\"\".format(\n        f\"0x{message_id:x}\",\n        f\"0x{param1:x}\" if not params_or_data[3] else \"None\",\n        f\"0x{param2:x}\" if not params_or_data[3] else \"None\",\n        f\"0x{dest:x}\",\n        f\"0x{source:x}\",\n        f\"{data}\" if params_or_data[3] else \"None\",\n    )\n    assert pkt.__str__() == string_expected\n\n\ndef test_message_id():\n    \"\"\"Get / set message ID.\"\"\"\n    pkt = _packets.ThorLabsPacket(\n        message_id=0x000, param1=0x01, param2=0x02, dest=0x50, source=0x01, data=None\n    )\n    pkt.message_id = 0x002A\n    assert pkt.message_id == 0x002A\n\n\ndef test_parameters():\n    \"\"\"Get / set both parameters.\"\"\"\n    pkt = _packets.ThorLabsPacket(\n        message_id=0x000, param1=0x01, param2=0x02, dest=0x50, source=0x01, data=None\n    )\n    pkt.parameters = (0x0D, 0x2A)\n    assert pkt.parameters == (0x0D, 0x2A)\n\n\ndef test_destination():\n    \"\"\"Get / set destination.\"\"\"\n    pkt = _packets.ThorLabsPacket(\n        message_id=0x000, param1=0x01, param2=0x02, dest=0x50, source=0x01, data=None\n    )\n    pkt.destination = 0x2A\n    assert pkt.destination == 0x2A\n\n\ndef test_source():\n    \"\"\"Get / set source.\"\"\"\n    pkt = _packets.ThorLabsPacket(\n        message_id=0x000, param1=0x01, param2=0x02, dest=0x50, source=0x01, data=None\n    )\n    pkt.source = 0x2A\n    assert pkt.source == 0x2A\n\n\ndef test_data():\n    \"\"\"Get / set data.\"\"\"\n    pkt = _packets.ThorLabsPacket(\n        message_id=0x000, param1=None, param2=None, dest=0x50, source=0x01, data=0x00\n    )\n    data = struct.pack(\"<H\", 0x2A)\n    pkt.data = data\n    assert pkt.data == data\n\n\n@pytest.mark.parametrize(\"params_or_data\", params_or_data)\ndef test_pack_with_data(params_or_data):\n    \"\"\"Pack a package with data.\"\"\"\n    message_id = 0x0000\n    param1 = params_or_data[0]\n    param2 = params_or_data[1]\n    dest = 0x50\n    source = 0x01\n    data = params_or_data[2]\n    pkt = _packets.ThorLabsPacket(\n        message_id=message_id,\n        param1=param1,\n        param2=param2,\n        dest=dest,\n        source=source,\n        data=data,\n    )\n\n    if params_or_data[3]:\n        message_header = struct.Struct(\"<HHBB\")\n        pkt_expected = (\n            message_header.pack(message_id, len(data), 0x80 | dest, source) + data\n        )\n    else:\n        message_header = struct.Struct(\"<HBBBB\")\n        pkt_expected = message_header.pack(message_id, param1, param2, dest, source)\n    assert pkt.pack() == pkt_expected\n\n\n@pytest.mark.parametrize(\"params_or_data\", params_or_data)\ndef test_unpack(params_or_data):\n    \"\"\"Unpack data - the reverse operation of packing it!\"\"\"\n    message_id = 0x0000\n    param1 = params_or_data[0]\n    param2 = params_or_data[1]\n    dest = 0x50\n    source = 0x01\n    data = params_or_data[2]\n    pkt = _packets.ThorLabsPacket(\n        message_id=message_id,\n        param1=param1,\n        param2=param2,\n        dest=dest,\n        source=source,\n        data=data,\n    )\n    packed_package = pkt.pack()\n    unpacked_package = pkt.unpack(packed_package)\n    assert unpacked_package.message_id == message_id\n    assert unpacked_package.parameters == (param1, param2)\n    assert unpacked_package.data == data\n    assert unpacked_package.destination == dest\n    assert unpacked_package.source == source\n\n\ndef test_unpack_empty_packet():\n    \"\"\"Raise ValueError if trying to unpack empty string.\"\"\"\n    pkt = _packets.ThorLabsPacket(\n        message_id=0x000, param1=0x01, param2=0x02, dest=0x50, source=0x01, data=None\n    )\n    with pytest.raises(ValueError) as err_info:\n        pkt.unpack(\"\")\n    err_msg = err_info.value.args[0]\n    assert err_msg == \"Expected a packet, got an empty string instead.\"\n\n\ndef test_unpack_too_short():\n    \"\"\"Raise ValueError if trying to unpack to short value.\"\"\"\n    pkt = _packets.ThorLabsPacket(\n        message_id=0x000, param1=0x01, param2=0x02, dest=0x50, source=0x01, data=None\n    )\n    too_short_package = struct.pack(\"<HH\", 0x01, 0x02)\n    with pytest.raises(ValueError) as err_info:\n        pkt.unpack(too_short_package)\n    err_msg = err_info.value.args[0]\n    assert err_msg == \"Packet must be at least 6 bytes long.\"\n"
  },
  {
    "path": "tests/test_thorlabs/test_thorlabs_apt.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the Thorlabs TC200\n\"\"\"\n\n# IMPORTS ####################################################################\n\n# pylint: disable=unused-import,too-many-lines\n\n\nimport struct\nimport warnings\n\nfrom hypothesis import given, strategies as st\nimport pytest\nfrom instruments.units import ureg as u\n\nimport instruments as ik\nfrom instruments.thorlabs._packets import ThorLabsPacket, hw_info_data\nfrom instruments.thorlabs._cmds import ThorLabsCommands\nfrom tests import expected_protocol, unit_eq\n\n# TESTS ######################################################################\n\n# pylint: disable=protected-access,unused-argument\n\n\nhw_types_setup = (\n    (45, \"Multi-channel controller motherboard\"),\n    (44, \"Brushless DC controller\"),\n    (3, \"Unknown type: 3\"),\n)\n\n\n@pytest.mark.parametrize(\"hw_type\", hw_types_setup)\ndef test_apt_hw_info(hw_type):\n    with expected_protocol(\n        ik.thorlabs.ThorLabsAPT,\n        [\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.HW_REQ_INFO,\n                param1=0x00,\n                param2=0x00,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack()\n        ],\n        [\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.HW_GET_INFO,\n                dest=0x01,\n                source=0x50,\n                data=hw_info_data.pack(\n                    # Serial number\n                    b\"\\x01\\x02\\x03\\x04\",\n                    # Model number\n                    b\"ABC-123\",\n                    # HW type\n                    hw_type[0],\n                    # FW version,\n                    0xA1,\n                    0xA2,\n                    0xA3,\n                    # Notes\n                    b\"abcdefg\",\n                    # HW version\n                    42,\n                    # Mod state\n                    43,\n                    # Number of channels\n                    2,\n                ),\n            ).pack()\n        ],\n        sep=\"\",\n    ) as apt:\n        # Check internal representations.\n        # NB: we shouldn't do this in some sense, but these fields\n        #     act as an API to the APT subclasses.\n        assert apt._hw_type == hw_type[1]\n        assert apt._fw_version == \"a1.a2.a3\"\n        assert apt._notes == \"abcdefg\"\n        assert apt._hw_version == 42\n        assert apt._mod_state == 43\n\n        # Check external API.\n        assert apt.serial_number == \"01020304\"\n        assert apt.model_number == \"ABC-123\"\n        assert apt.name == (\n            \"ThorLabs APT Instrument model ABC-123, \"\n            \"serial 01020304 (HW version 42, FW version a1.a2.a3)\"\n        )\n\n\ndef test_apt_hw_info_io_error(mocker):\n    inst_class = ik.thorlabs.ThorLabsAPT\n\n    # mock querying a packet and raise an IOError\n    io_error_mock = mocker.Mock()\n    io_error_mock.side_effect = IOError\n    mocker.patch.object(inst_class, \"querypacket\", io_error_mock)\n\n    with expected_protocol(inst_class, [], [], sep=\"\") as apt:\n        # IOError was raised, assert that defaults are still present\n        assert apt._serial_number is None\n        assert apt._model_number is None\n        assert apt._hw_type is None\n        assert apt._fw_version is None\n        assert apt._notes == \"\"\n        assert apt._hw_version is None\n        assert apt._mod_state is None\n        assert apt._n_channels == 0\n        assert apt._channel == ()\n\n\n# FIXTURES FOR APT TEST SUITE #\n\n\n@pytest.fixture\ndef init_kdc101():\n    \"\"\"Return the send, receive value to initialize a KIM101 unit.\"\"\"\n    stdin = ThorLabsPacket(\n        message_id=ThorLabsCommands.HW_REQ_INFO,\n        param1=0x00,\n        param2=0x00,\n        dest=0x50,\n        source=0x01,\n        data=None,\n    ).pack()\n    stdout = ThorLabsPacket(\n        message_id=ThorLabsCommands.HW_GET_INFO,\n        dest=0x01,\n        source=0x50,\n        data=hw_info_data.pack(\n            # Serial number\n            b\"\\x01\\x02\\x03\\x04\",\n            # Model number\n            b\"KDC101\",\n            # HW type\n            16,\n            # FW version,\n            0xA1,\n            0xA2,\n            0xA3,\n            # Notes\n            b\"abcdefg\",\n            # HW version\n            42,\n            # Mod state\n            43,\n            # Number of channels\n            1,\n        ),\n    ).pack()\n    return stdin, stdout\n\n\n@pytest.fixture\ndef init_kim101():\n    \"\"\"Return the send, receive value to initialize a KIM101 unit.\"\"\"\n    stdin = ThorLabsPacket(\n        message_id=ThorLabsCommands.HW_REQ_INFO,\n        param1=0x00,\n        param2=0x00,\n        dest=0x50,\n        source=0x01,\n        data=None,\n    ).pack()\n    stdout = ThorLabsPacket(\n        message_id=ThorLabsCommands.HW_GET_INFO,\n        dest=0x01,\n        source=0x50,\n        data=hw_info_data.pack(\n            # Serial number\n            b\"\\x01\\x02\\x03\\x04\",\n            # Model number\n            b\"KIM101\",\n            # HW type\n            16,\n            # FW version,\n            0xA1,\n            0xA2,\n            0xA3,\n            # Notes\n            b\"abcdefg\",\n            # HW version\n            42,\n            # Mod state\n            43,\n            # Number of channels\n            4,\n        ),\n    ).pack()\n    return stdin, stdout\n\n\n@pytest.fixture\ndef init_tim101():\n    \"\"\"Return the send, receive value to initialize a TIM101 unit.\n\n    Currently only used to test failure modes.\n    \"\"\"\n    stdin = ThorLabsPacket(\n        message_id=ThorLabsCommands.HW_REQ_INFO,\n        param1=0x00,\n        param2=0x00,\n        dest=0x50,\n        source=0x01,\n        data=None,\n    ).pack()\n    stdout = ThorLabsPacket(\n        message_id=ThorLabsCommands.HW_GET_INFO,\n        dest=0x01,\n        source=0x50,\n        data=hw_info_data.pack(\n            # Serial number\n            b\"\\x01\\x02\\x03\\x04\",\n            # Model number\n            b\"TIM101\",\n            # HW type\n            16,\n            # FW version,\n            0xA1,\n            0xA2,\n            0xA3,\n            # Notes\n            b\"abcdefg\",\n            # HW version\n            42,\n            # Mod state\n            43,\n            # Number of channels\n            4,\n        ),\n    ).pack()\n    return stdin, stdout\n\n\n@pytest.fixture\ndef init_ksg101():\n    \"\"\"Return the send, receive value to initialize a KSG101 unit.\"\"\"\n    stdin = ThorLabsPacket(\n        message_id=ThorLabsCommands.HW_REQ_INFO,\n        param1=0x00,\n        param2=0x00,\n        dest=0x50,\n        source=0x01,\n        data=None,\n    ).pack()\n    stdout = ThorLabsPacket(\n        message_id=ThorLabsCommands.HW_GET_INFO,\n        dest=0x01,\n        source=0x50,\n        data=hw_info_data.pack(\n            # Serial number\n            b\"\\x01\\x02\\x03\\x04\",\n            # Model number\n            b\"KSG101\",\n            # HW type\n            3,\n            # FW version,\n            0xA1,\n            0xA2,\n            0xA3,\n            # Notes\n            b\"abcdefg\",\n            # HW version\n            42,\n            # Mod state\n            43,\n            # Number of channels\n            1,\n        ),\n    ).pack()\n    return stdin, stdout\n\n\n@pytest.fixture(scope=\"session\")\ndef init_kpz001():\n    \"\"\"Return the send, receive value to initialize a KPZ001 unit.\"\"\"\n    stdin = ThorLabsPacket(\n        message_id=ThorLabsCommands.HW_REQ_INFO,\n        param1=0x00,\n        param2=0x00,\n        dest=0x50,\n        source=0x01,\n        data=None,\n    ).pack()\n    stdout = ThorLabsPacket(\n        message_id=ThorLabsCommands.HW_GET_INFO,\n        dest=0x01,\n        source=0x50,\n        data=hw_info_data.pack(\n            # Serial number\n            b\"\\x01\\x02\\x03\\x04\",\n            # Model number\n            b\"KPZ101\",\n            # HW type\n            3,\n            # FW version,\n            0xA1,\n            0xA2,\n            0xA3,\n            # Notes\n            b\"abcdefg\",\n            # HW version\n            42,\n            # Mod state\n            43,\n            # Number of channels\n            1,\n        ),\n    ).pack()\n    return stdin, stdout\n\n\n# pylint: disable=redefined-outer-name\n\n\n# TESTS FOR APT PIEZO INERTIA ACTUATOR CLASS (APT_PIA) #\n\n\n# CHANNELS #\n\n\ndef test_apt_pia_channel_drive_op_parameters(init_kim101):\n    \"\"\"Test the drive op parameters for the APT Piezo Inertia Actuator.\n\n    Tested with KIM101 driver connected to PIM1 mirror mount.\n    \"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTPiezoInertiaActuator,\n        [\n            init_kim101[0],\n            ThorLabsPacket(  # receive a package\n                message_id=ThorLabsCommands.PZMOT_REQ_PARAMS,\n                param1=0x07,\n                param2=0x01,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n            ThorLabsPacket(  # send a packet\n                message_id=ThorLabsCommands.PZMOT_SET_PARAMS,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<HHHll\", 0x07, 0x01, 100, 1000, 10000),\n            ).pack(),\n        ],\n        [\n            init_kim101[1],\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.PZMOT_GET_PARAMS,\n                dest=0x01,\n                source=0x50,\n                data=struct.pack(\"<HHHll\", 0x07, 0x01, 90, 500, 5000),\n            ).pack(),\n        ],\n        sep=\"\",\n    ) as apt:\n        assert apt.channel[0].drive_op_parameters == [\n            u.Quantity(90, u.V),\n            u.Quantity(500, 1 / u.s),\n            u.Quantity(5000, 1 / u.s**2),\n        ]\n        apt.channel[0].drive_op_parameters = [\n            u.Quantity(100, u.V),\n            u.Quantity(1000, 1 / u.s),\n            u.Quantity(10000, 1 / u.s**2),\n        ]\n\n\ndef test_apt_pia_channel_drive_op_parameters_exceptions(init_kim101):\n    \"\"\"Raise exceptions in drive op parameters: APT Piezo Inertia Actuator.\"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTPiezoInertiaActuator, [init_kim101[0]], [init_kim101[1]], sep=\"\"\n    ) as apt:\n        # wrong type of parameters\n        with pytest.raises(TypeError):\n            apt.channel[0].drive_op_parameters = 42\n\n        # wrong list length\n        with pytest.raises(ValueError):\n            apt.channel[0].drive_op_parameters = [42, 42]\n\n        # parameters out of range\n        with pytest.raises(ValueError):\n            apt.channel[0].drive_op_parameters = [1, 1000, 10000]\n        with pytest.raises(ValueError):\n            apt.channel[0].drive_op_parameters = [100, 2500, 10000]\n        with pytest.raises(ValueError):\n            apt.channel[0].drive_op_parameters = [100, 1000, 100001]\n\n\ndef test_apt_pia_channel_enabled_single(init_kim101):\n    \"\"\"Enable a single channel for the APT Piezo Inertia Actuator.\n\n    Tested with KIM101 driver connected to PIM1 mirror mount.\n    \"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTPiezoInertiaActuator,\n        [\n            init_kim101[0],\n            ThorLabsPacket(  # read state\n                message_id=ThorLabsCommands.PZMOT_REQ_PARAMS,\n                param1=0x2B,\n                param2=0x01,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n            ThorLabsPacket(  # disable all channels\n                message_id=ThorLabsCommands.PZMOT_SET_PARAMS,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<HH\", 0x2B, 0x00),\n            ).pack(),\n        ],\n        [\n            init_kim101[1],\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.PZMOT_GET_PARAMS,\n                param1=None,\n                param2=None,\n                dest=0x01,\n                source=0x50,\n                data=struct.pack(\"<HH\", 0x2B, 0x01),\n            ).pack(),\n        ],\n        sep=\"\",\n    ) as apt:\n        assert apt.channel[0].enabled_single\n        apt.channel[0].enabled_single = False\n\n\ndef test_apt_pia_channel_enabled_single_wrong_controller(init_tim101):\n    \"\"\"Raise TypeError when called with from non-KIM controller.\"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTPiezoInertiaActuator, [init_tim101[0]], [init_tim101[1]], sep=\"\"\n    ) as apt:\n        with pytest.raises(TypeError):\n            assert apt.channel[0].enabled_single\n        with pytest.raises(TypeError):\n            apt.channel[0].enabled_single = True\n\n\ndef test_apt_pia_channel_jog_parameters(init_kim101):\n    \"\"\"Test the jog parameters for the APT Piezo Inertia Actuator.\n\n    Tested with KIM101 driver connected to PIM1 mirror mount.\n    \"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTPiezoInertiaActuator,\n        [\n            init_kim101[0],\n            ThorLabsPacket(  # receive a package\n                message_id=ThorLabsCommands.PZMOT_REQ_PARAMS,\n                param1=0x2D,\n                param2=0x01,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n            ThorLabsPacket(  # send a packet\n                message_id=ThorLabsCommands.PZMOT_SET_PARAMS,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<HHHllll\", 0x2D, 0x01, 2, 100, 100, 1000, 10000),\n            ).pack(),\n        ],\n        [\n            init_kim101[1],\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.PZMOT_GET_PARAMS,\n                dest=0x01,\n                source=0x50,\n                data=struct.pack(\"<HHHllll\", 0x2D, 0x01, 1, 500, 1000, 400, 5000),\n            ).pack(),\n        ],\n        sep=\"\",\n    ) as apt:\n        assert apt.channel[0].jog_parameters == [\n            1,\n            500,\n            1000,\n            u.Quantity(400, 1 / u.s),\n            u.Quantity(5000, 1 / u.s**2),\n        ]\n        apt.channel[0].jog_parameters = [\n            2,\n            100,\n            100,\n            u.Quantity(1000, 1 / u.s),\n            u.Quantity(10000, 1 / u.s**2),\n        ]\n\n        # invalid setter\n        with pytest.raises(TypeError):\n            apt.channel[0].jog_parameters = 3.14\n        # list out of range\n        with pytest.raises(ValueError):\n            apt.channel[0].jog_parameters = [42, 42]\n        # invalid parameters\n        with pytest.raises(ValueError):\n            apt.channel[0].jog_parameters = [0, 100, 100, 1000, 10000]\n        with pytest.raises(ValueError):\n            apt.channel[0].jog_parameters = [2, 0, 100, 1000, 10000]\n        with pytest.raises(ValueError):\n            apt.channel[0].jog_parameters = [2, 100, 2500, 1000, 10000]\n        with pytest.raises(ValueError):\n            apt.channel[0].jog_parameters = [2, 100, 100, 2500, 10000]\n        with pytest.raises(ValueError):\n            apt.channel[0].jog_parameters = [2, 100, 100, 1000, 100001]\n\n\ndef test_apt_pia_channel_jog_parameters_invalid_controller(init_tim101):\n    \"\"\"Throw error when trying to set jog parameters for wrong controller.\"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTPiezoInertiaActuator, [init_tim101[0]], [init_tim101[1]], sep=\"\"\n    ) as apt:\n        with pytest.raises(TypeError):\n            assert apt.channel[0].jog_parameters == [2, 100, 100, 1000, 1000]\n        with pytest.raises(TypeError):\n            apt.channel[0].jog_parameters = [2, 100, 100, 1000, 1000]\n\n\ndef test_apt_pia_channel_position_count(init_kim101):\n    \"\"\"Get / Set position count for APT Piezo Inertia Actuator.\n\n    Tested with KIM101 driver connected to PIM1 mirror mount.\n    \"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTPiezoInertiaActuator,\n        [\n            init_kim101[0],\n            ThorLabsPacket(  # receive a package\n                message_id=ThorLabsCommands.PZMOT_REQ_PARAMS,\n                param1=0x05,\n                param2=0x01,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n            ThorLabsPacket(  # send a packet\n                message_id=ThorLabsCommands.PZMOT_SET_PARAMS,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<HHll\", 0x05, 0x03, 100, 0x00),\n            ).pack(),\n        ],\n        [\n            init_kim101[1],\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.PZMOT_GET_PARAMS,\n                dest=0x01,\n                source=0x50,\n                data=struct.pack(\"<HHll\", 0x05, 0x01, 0, 0),\n            ).pack(),\n        ],\n        sep=\"\",\n    ) as apt:\n        assert apt.channel[0].position_count == 0\n        apt.channel[2].position_count = 100\n\n\ndef test_apt_pia_channel_move_abs(init_kim101):\n    \"\"\"Absolute movement of APT Piezo Inertia Actuator.\n\n    Tested with KIM101 driver connected to PIM1 mirror mount.\n    \"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTPiezoInertiaActuator,\n        [\n            init_kim101[0],\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.PZMOT_MOVE_ABSOLUTE,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<Hl\", 0x01, 100),\n            ).pack(),\n        ],\n        [init_kim101[1]],\n        sep=\"\",\n    ) as apt:\n        apt.channel[0].move_abs(100)\n\n\ndef test_apt_pia_channel_move_jog(init_kim101):\n    \"\"\"Jog forward and reverse with APT Piezo Inertia Actuator.\n\n    Tested with KIM101 driver connected to PIM1 mirror mount.\n    \"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTPiezoInertiaActuator,\n        [\n            init_kim101[0],\n            ThorLabsPacket(  # forward\n                message_id=ThorLabsCommands.PZMOT_MOVE_JOG,\n                param1=0x01,\n                param2=0x01,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n            ThorLabsPacket(  # reverse\n                message_id=ThorLabsCommands.PZMOT_MOVE_JOG,\n                param1=0x01,\n                param2=0x02,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n        ],\n        [init_kim101[1]],\n        sep=\"\",\n    ) as apt:\n        apt.channel[0].move_jog()\n        apt.channel[0].move_jog(\"rev\")\n\n\ndef test_apt_pia_channel_move_jog_stop(init_kim101):\n    \"\"\"Jog forward and reverse with APT Piezo Inertia Actuator.\n\n    Tested with KIM101 driver connected to PIM1 mirror mount.\n    \"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTPiezoInertiaActuator,\n        [\n            init_kim101[0],\n            ThorLabsPacket(  # no direction -> stop\n                message_id=ThorLabsCommands.PZMOT_MOVE_JOG,\n                param1=0x01,\n                param2=0x00,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n        ],\n        [init_kim101[1]],\n        sep=\"\",\n    ) as apt:\n        apt.channel[0].move_jog_stop()\n\n\n# CONTROLLER #\n\n\ndef test_apt_pia_enabled_multi(init_kim101):\n    \"\"\"Multi-channel enabling APT Piezo Inertia Actuator KIM101.\n\n    Tested with KIM101 driver connected to PIM1 mirror mount.\n    \"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTPiezoInertiaActuator,\n        [\n            init_kim101[0],\n            ThorLabsPacket(  # all off\n                message_id=ThorLabsCommands.PZMOT_REQ_PARAMS,\n                param1=0x2B,\n                param2=0x00,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n            ThorLabsPacket(  # read channel 0 & 1\n                message_id=ThorLabsCommands.PZMOT_REQ_PARAMS,\n                param1=0x2B,\n                param2=0x00,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n            ThorLabsPacket(  # read channel 2 & 3\n                message_id=ThorLabsCommands.PZMOT_REQ_PARAMS,\n                param1=0x2B,\n                param2=0x00,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n            ThorLabsPacket(  # send off\n                message_id=ThorLabsCommands.PZMOT_SET_PARAMS,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<HH\", 0x2B, 0x00),\n            ).pack(),\n            ThorLabsPacket(  # send ch 0 & 1\n                message_id=ThorLabsCommands.PZMOT_SET_PARAMS,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<HH\", 0x2B, 0x05),\n            ).pack(),\n            ThorLabsPacket(  # send ch 2 & 3\n                message_id=ThorLabsCommands.PZMOT_SET_PARAMS,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<HH\", 0x2B, 0x06),\n            ).pack(),\n        ],\n        [\n            init_kim101[1],\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.PZMOT_GET_PARAMS,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<HH\", 0x2B, 0x00),\n            ).pack(),\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.PZMOT_GET_PARAMS,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<HH\", 0x2B, 0x05),\n            ).pack(),\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.PZMOT_GET_PARAMS,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<HH\", 0x2B, 0x06),\n            ).pack(),\n        ],\n        sep=\"\",\n    ) as apt:\n        assert apt.enabled_multi == 0\n        assert apt.enabled_multi == 1\n        assert apt.enabled_multi == 2\n        apt.enabled_multi = 0\n        apt.enabled_multi = 1\n        apt.enabled_multi = 2\n\n        # Invalid controller chosen\n        with pytest.raises(ValueError):\n            apt.enabled_multi = 42\n\n\ndef test_apt_pia_enabled_multi_wrong_controller(init_tim101):\n    \"\"\"Multi-channel enabling APT Piezo Inertia Actuator TIM101.\n\n    Tested with KIM101 driver connected to PIM1 mirror mount.\n    \"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTPiezoInertiaActuator, [init_tim101[0]], [init_tim101[1]], sep=\"\"\n    ) as apt:\n        with pytest.raises(TypeError):\n            apt.enabled_multi = 42\n        with pytest.raises(TypeError):\n            assert apt.enabled_multi == 1\n\n\ndef test_apt_pia_enabled_type_error(init_kim101):\n    \"\"\"Raise Type Error and give feedback when trying to use enabled.\n\n    Implemented at parent class level and KIM101 should respond to it,\n    however, it does not. Enabling channels is implmented. Unclear if\n    this is an error in the manual.\n    \"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTPiezoInertiaActuator, [init_kim101[0]], [init_kim101[1]], sep=\"\"\n    ) as apt:\n        with pytest.raises(TypeError):\n            assert apt.channel[0].enabled\n\n\n# APT PIEZO STAGE (APT_PS) #\n\n\ndef test_apt_ps_max_travel_no_response(init_kpz001):\n    with expected_protocol(\n        ik.thorlabs.APTPiezoStage,\n        [\n            init_kpz001[0],\n            ThorLabsPacket(  # read state\n                message_id=ThorLabsCommands.PZ_REQ_MAXTRAVEL,\n                param1=0x01,\n                param2=0x00,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n        ],\n        [init_kpz001[1]],\n        sep=\"\",\n    ) as apt:\n        assert apt.channel[0].max_travel == NotImplemented\n\n\ndef test_apt_ps_led_intensity(init_kpz001):\n    \"\"\"Get / set LED intensity between zero and 1.\"\"\"\n    led_intensity = 0.73\n    with expected_protocol(\n        ik.thorlabs.APTPiezoStage,\n        [\n            init_kpz001[0],\n            ThorLabsPacket(  # set state\n                message_id=ThorLabsCommands.PZ_SET_TPZ_DISPSETTINGS,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<H\", int(round(255 * led_intensity))),\n            ).pack(),\n            ThorLabsPacket(  # read state\n                message_id=ThorLabsCommands.PZ_REQ_TPZ_DISPSETTINGS,\n                param1=0x01,\n                param2=0x00,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n        ],\n        [\n            init_kpz001[1],\n            ThorLabsPacket(  # get state\n                message_id=ThorLabsCommands.PZ_GET_TPZ_DISPSETTINGS,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<H\", int(round(255 * led_intensity))),\n            ).pack(),\n        ],\n        sep=\"\",\n    ) as apt:\n        apt.led_intensity = led_intensity\n        assert apt.led_intensity == pytest.approx(led_intensity, 1.0 / 255)\n\n\ndef test_apt_ps_led_intensity_no_response(init_kpz001):\n    \"\"\"No response when setting the display.\"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTPiezoStage,\n        [\n            init_kpz001[0],\n            ThorLabsPacket(  # read state\n                message_id=ThorLabsCommands.PZ_REQ_TPZ_DISPSETTINGS,\n                param1=0x01,\n                param2=0x00,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n        ],\n        [init_kpz001[1]],\n        sep=\"\",\n    ) as apt:\n        assert apt.led_intensity == NotImplemented\n\n\n@pytest.mark.parametrize(\"value\", (0x01, 0x02, 0x03, 0x04))\ndef test_apt_ps_position_control_closed(init_kpz001, value):\n    \"\"\"Get the status if the position control is closed or not.\"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTPiezoStage,\n        [\n            init_kpz001[0],\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.PZ_REQ_POSCONTROLMODE,\n                param1=0x01,\n                param2=0x00,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n        ],\n        [\n            init_kpz001[1],\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.PZ_GET_POSCONTROLMODE,\n                param1=0x01,\n                param2=value,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n        ],\n        sep=\"\",\n    ) as apt:\n        assert apt.channel[0].position_control_closed == bool(value - 1 & 1)\n\n\n@pytest.mark.parametrize(\"closed\", (True, False))\n@pytest.mark.parametrize(\"smooth\", (True, False))\ndef test_apt_ps_change_position_control_mode(init_kpz001, closed, smooth):\n    \"\"\"Set the position control mode.\"\"\"\n    mode = 1 + (int(closed) | int(smooth) << 1)\n    with expected_protocol(\n        ik.thorlabs.APTPiezoStage,\n        [\n            init_kpz001[0],\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.PZ_SET_POSCONTROLMODE,\n                param1=0x01,\n                param2=mode,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n        ],\n        [init_kpz001[1]],\n        sep=\"\",\n    ) as apt:\n        apt.channel[0].change_position_control_mode(closed, smooth=smooth)\n\n\n@given(position=st.integers(min_value=0, max_value=32767))\ndef test_apt_ps_output_position(init_kpz001, position):\n    \"\"\"Get / set output position for piezo channel.\"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTPiezoStage,\n        [\n            init_kpz001[0],\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.PZ_SET_OUTPUTPOS,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<HH\", 0x01, position),\n            ).pack(),\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.PZ_REQ_OUTPUTPOS,\n                param1=0x01,\n                param2=0x00,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n        ],\n        [\n            init_kpz001[1],\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.PZ_GET_OUTPUTPOS,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<HH\", 0x01, position),\n            ).pack(),\n        ],\n        sep=\"\",\n    ) as apt:\n        apt.channel[0].output_position = position\n        assert apt.channel[0].output_position == position\n\n\n# APT STRAIN GAUGE READER (APT SGR) #\n\n\ndef test_apt_sgr_max_travel(init_ksg101):\n    value = 10000\n    with expected_protocol(\n        ik.thorlabs.APTPiezoStage,\n        [\n            init_ksg101[0],\n            ThorLabsPacket(  # read state\n                message_id=ThorLabsCommands.PZ_REQ_MAXTRAVEL,\n                param1=0x01,\n                param2=0x00,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n        ],\n        [\n            init_ksg101[1],\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.PZ_GET_MAXTRAVEL,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<HH\", 0x01, value),\n            ).pack(),\n        ],\n        sep=\"\",\n    ) as apt:\n        assert apt.channel[0].max_travel == value * u.Quantity(100, \"nm\")\n\n\n# APT MOTOR CONTROLLER (APT_MC) #\n\n\ndef test_apt_mc_motion_timeout(init_kdc101):\n    \"\"\"Set and get motion timeout.\"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTMotorController, [init_kdc101[0]], [init_kdc101[1]], sep=\"\"\n    ) as apt:\n        apt.channel[0].motion_timeout = u.Quantity(100, u.s)\n        assert apt.channel[0].motion_timeout == u.Quantity(100, u.s)\n\n\ndef test_apt_mc_enabled(init_kdc101):\n    \"\"\"Enable the channel and read status back.\"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTMotorController,\n        [\n            init_kdc101[0],\n            ThorLabsPacket(  # read state\n                message_id=ThorLabsCommands.MOD_REQ_CHANENABLESTATE,\n                param1=0x01,\n                param2=0x00,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n            ThorLabsPacket(  # write state\n                message_id=ThorLabsCommands.MOD_SET_CHANENABLESTATE,\n                param1=0x01,\n                param2=0x01,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n        ],\n        [\n            init_kdc101[1],\n            ThorLabsPacket(  # return False\n                message_id=ThorLabsCommands.MOD_GET_CHANENABLESTATE,\n                param1=0x01,\n                param2=0x02,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n        ],\n        sep=\"\",\n    ) as apt:\n        assert not apt.channel[0].enabled\n        apt.channel[0].enabled = True\n\n\ndef test_apt_mc_set_scale(init_kdc101, mocker):\n    \"\"\"Set the scale using the depreciated set scale routine.\n\n    Assert that a warning was raised.\n    \"\"\"\n    mock_warning = mocker.patch.object(warnings, \"warn\")\n    with expected_protocol(\n        ik.thorlabs.APTMotorController, [init_kdc101[0]], [init_kdc101[1]], sep=\"\"\n    ) as apt:\n        apt.channel[0].set_scale(\"PRM1-Z8\")\n        mock_warning.assert_called_with(\n            \"The set_scale method has been deprecated in favor \"\n            \"of the motor_model property.\",\n            DeprecationWarning,\n        )\n\n\ndef test_apt_mc_motor_model(init_kdc101):\n    \"\"\"Set / Get the motor model.\"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTMotorController, [init_kdc101[0]], [init_kdc101[1]], sep=\"\"\n    ) as apt:\n        apt.channel[0].motor_model = \"PRM1-Z8\"\n        assert apt.channel[0].motor_model == \"PRM1-Z8\"\n\n\ndef test_apt_mc_motor_model_invalid_model(init_kdc101):\n    \"\"\"Try setting an invalid motor model.\"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTMotorController, [init_kdc101[0]], [init_kdc101[1]], sep=\"\"\n    ) as apt:\n        apt.scale_factors = 42  # set to some value\n        apt.channel[0].motor_model = \"INVALID\"\n        assert apt.scale_factors == 42  # assert it hasn't changed\n\n\napt_mc_channel_status_bit_mask = {\n    \"CW_HARD_LIM\": 0x00000001,\n    \"CCW_HARD_LIM\": 0x00000002,\n    \"CW_SOFT_LIM\": 0x00000004,\n    \"CCW_SOFT_LIM\": 0x00000008,\n    \"CW_MOVE_IN_MOTION\": 0x00000010,\n    \"CCW_MOVE_IN_MOTION\": 0x00000020,\n    \"CW_JOG_IN_MOTION\": 0x00000040,\n    \"CCW_JOG_IN_MOTION\": 0x00000080,\n    \"MOTOR_CONNECTED\": 0x00000100,\n    \"HOMING_IN_MOTION\": 0x00000200,\n    \"HOMING_COMPLETE\": 0x00000400,\n    \"INTERLOCK_STATE\": 0x00001000,\n}\n\n\n@pytest.mark.parametrize(\"status_bits\", apt_mc_channel_status_bit_mask.values())\ndef test_apt_mc_status_bits(init_kdc101, status_bits):\n    \"\"\"Get status bits.\"\"\"\n    status_dict_expected = {\n        key: (status_bits & bit_mask > 0)\n        for key, bit_mask in apt_mc_channel_status_bit_mask.items()\n    }\n\n    with expected_protocol(\n        ik.thorlabs.APTMotorController,\n        [\n            init_kdc101[0],\n            ThorLabsPacket(  # read position\n                message_id=ThorLabsCommands.MOT_REQ_STATUSUPDATE,\n                param1=0x01,\n                param2=0x00,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n        ],\n        [\n            init_kdc101[1],\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.MOT_GET_POSCOUNTER,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<HLLL\", 0x01, 0x01, 0x01, status_bits),\n            ).pack(),\n        ],\n        sep=\"\",\n    ) as apt:\n        assert apt.channel[0].status_bits == status_dict_expected\n\n\ndef test_apt_mc_position(init_kdc101):\n    \"\"\"Get unitful position of controller.\"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTMotorController,\n        [\n            init_kdc101[0],\n            ThorLabsPacket(  # read position\n                message_id=ThorLabsCommands.MOT_REQ_POSCOUNTER,\n                param1=0x01,\n                param2=0x00,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n        ],\n        [\n            init_kdc101[1],\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.MOT_GET_POSCOUNTER,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<Hl\", 0x01, -20000),\n            ).pack(),\n        ],\n        sep=\"\",\n    ) as apt:\n        assert (\n            apt.channel[0].position\n            == u.Quantity(-20000, \"counts\") / apt.channel[0].scale_factors[0]\n        )\n\n\ndef test_apt_mc_backlash_correction_no_units(init_kdc101):\n    \"\"\"Get / set backlash correction without units or as counts.\"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTMotorController,\n        [\n            init_kdc101[0],\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.MOT_SET_GENMOVEPARAMS,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<Hl\", 0x01, 1000),\n            ).pack(),\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.MOT_SET_GENMOVEPARAMS,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<Hl\", 0x01, -20000),\n            ).pack(),\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.MOT_REQ_GENMOVEPARAMS,\n                param1=0x01,\n                param2=0x00,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n        ],\n        [\n            init_kdc101[1],\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.MOT_GET_GENMOVEPARAMS,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<Hl\", 0x01, -20000),\n            ).pack(),\n        ],\n        sep=\"\",\n    ) as apt:\n        apt.channel[0].backlash_correction = 1000\n        apt.channel[0].backlash_correction = -20000 * u.counts\n        assert apt.channel[0].backlash_correction == u.Quantity(-20000, \"counts\")\n\n\ndef test_apt_mc_backlash_correction_unitful(init_kdc101):\n    \"\"\"Get / set backlash correction unitful.\"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTMotorController,\n        [\n            init_kdc101[0],\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.MOT_SET_GENMOVEPARAMS,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<Hl\", 0x01, 1919),\n            ).pack(),\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.MOT_REQ_GENMOVEPARAMS,\n                param1=0x01,\n                param2=0x00,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n        ],\n        [\n            init_kdc101[1],\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.MOT_GET_GENMOVEPARAMS,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<Hl\", 0x01, 1919),\n            ).pack(),\n        ],\n        sep=\"\",\n    ) as apt:\n        apt.channel[0].motor_model = \"PRM1-Z8\"\n        corr = 1 * u.deg\n        apt.channel[0].backlash_correction = corr\n\n        corr_received = apt.channel[0].backlash_correction\n        unit_eq(corr_received, corr, abs=1e-3)\n\n\ndef test_apt_mc_backlash_correction_bad_units(init_kdc101):\n    \"\"\"Raise ValueError if incompatible units are used for backlash corr.\"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTMotorController,\n        [init_kdc101[0]],\n        [init_kdc101[1]],\n        sep=\"\",\n    ) as apt:\n        with pytest.raises(ValueError):\n            apt.channel[0].backlash_correction = 10 * u.mm\n\n\ndef test_apt_mc_home_parameters_no_units(init_kdc101):\n    \"\"\"Get / set home_parameters without units or as counts.\"\"\"\n    home_direction = 1\n    limit_switch = 1\n    velocity = 100\n    offset = 7000\n    with expected_protocol(\n        ik.thorlabs.APTMotorController,\n        [\n            init_kdc101[0],\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.MOT_SET_HOMEPARAMS,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\n                    \"<HHHll\", 0x01, home_direction, limit_switch, velocity, offset\n                ),\n            ).pack(),\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.MOT_SET_HOMEPARAMS,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\n                    \"<HHHll\", 0x01, home_direction, limit_switch, velocity, offset\n                ),\n            ).pack(),\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.MOT_REQ_HOMEPARAMS,\n                param1=0x01,\n                param2=0x00,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n        ],\n        [\n            init_kdc101[1],\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.MOT_GET_HOMEPARAMS,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\n                    \"<HHHll\", 0x01, home_direction, limit_switch, velocity, offset\n                ),\n            ).pack(),\n        ],\n        sep=\"\",\n    ) as apt:\n        apt.channel[0].home_parameters = home_direction, limit_switch, velocity, offset\n        apt.channel[0].home_parameters = (\n            home_direction,\n            limit_switch,\n            velocity,\n            offset * u.counts,\n        )\n        params_rec = apt.channel[0].home_parameters\n        assert params_rec[0] == home_direction\n        assert params_rec[1] == limit_switch\n        assert params_rec[2] == velocity\n        unit_eq(params_rec[3], offset * u.count)\n\n\ndef test_apt_mc_home_parameters_set_with_none(init_kdc101):\n    \"\"\"Set home parameters with all `None` sends read back values.\"\"\"\n    home_direction = 1\n    limit_switch = 1\n    velocity = 1000\n    offset = 1250\n    with expected_protocol(\n        ik.thorlabs.APTMotorController,\n        [\n            init_kdc101[0],\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.MOT_REQ_HOMEPARAMS,\n                param1=0x01,\n                param2=0x00,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.MOT_SET_HOMEPARAMS,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\n                    \"<HHHll\",\n                    0x01,\n                    home_direction,\n                    limit_switch,\n                    velocity,\n                    offset,\n                ),\n            ).pack(),\n        ],\n        [\n            init_kdc101[1],\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.MOT_GET_HOMEPARAMS,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\n                    \"<HHHll\",\n                    0x01,\n                    home_direction,\n                    limit_switch,\n                    velocity,\n                    offset,\n                ),\n            ).pack(),\n        ],\n        sep=\"\",\n    ) as apt:\n        apt.channel[0].home_parameters = None, None, None, None\n\n\ndef test_apt_mc_home_parameters_set_offset_to_zero(init_kdc101):\n    \"\"\"Ensure setting offset to zero is not interpreted as `None`.\"\"\"\n    home_direction = 1\n    limit_switch = 1\n    velocity = 1000\n    offset = 1250\n    offset_new = 0\n    with expected_protocol(\n        ik.thorlabs.APTMotorController,\n        [\n            init_kdc101[0],\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.MOT_REQ_HOMEPARAMS,\n                param1=0x01,\n                param2=0x00,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.MOT_SET_HOMEPARAMS,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\n                    \"<HHHll\",\n                    0x01,\n                    home_direction,\n                    limit_switch,\n                    velocity,\n                    offset_new,\n                ),\n            ).pack(),\n        ],\n        [\n            init_kdc101[1],\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.MOT_GET_HOMEPARAMS,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\n                    \"<HHHll\",\n                    0x01,\n                    home_direction,\n                    limit_switch,\n                    velocity,\n                    offset,\n                ),\n            ).pack(),\n        ],\n        sep=\"\",\n    ) as apt:\n        apt.channel[0].home_parameters = None, None, None, offset_new\n\n\ndef test_apt_mc_home_parameters(init_kdc101):\n    \"\"\"Get / set home_parameters in unitful fashion.\"\"\"\n    home_direction = 1\n    limit_switch = 1\n    velocity = 1 * u.deg / u.s\n    velocity_enc = 42941\n    offset = 1 * u.deg\n    offset_enc = 1919\n    with expected_protocol(\n        ik.thorlabs.APTMotorController,\n        [\n            init_kdc101[0],\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.MOT_SET_HOMEPARAMS,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\n                    \"<HHHll\",\n                    0x01,\n                    home_direction,\n                    limit_switch,\n                    velocity_enc,\n                    offset_enc,\n                ),\n            ).pack(),\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.MOT_REQ_HOMEPARAMS,\n                param1=0x01,\n                param2=0x00,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n        ],\n        [\n            init_kdc101[1],\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.MOT_GET_HOMEPARAMS,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\n                    \"<HHHll\",\n                    0x01,\n                    home_direction,\n                    limit_switch,\n                    velocity_enc,\n                    offset_enc,\n                ),\n            ).pack(),\n        ],\n        sep=\"\",\n    ) as apt:\n        apt.channel[0].motor_model = \"PRM1-Z8\"\n        apt.channel[0].home_parameters = home_direction, limit_switch, velocity, offset\n\n        params_rec = apt.channel[0].home_parameters\n\n        assert params_rec[0] == home_direction\n        assert params_rec[1] == limit_switch\n        unit_eq(params_rec[2], velocity, abs=0.001)\n        unit_eq(params_rec[3], offset, abs=0.001)\n\n\ndef test_apt_mc_home_parameters_wrong_values(init_kdc101):\n    \"\"\"Raise a ValueError if wrong number of values are provided.\"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTMotorController,\n        [init_kdc101[0]],\n        [init_kdc101[1]],\n        sep=\"\",\n    ) as apt:\n        with pytest.raises(ValueError):\n            apt.channel[0].home_parameters = 1, 1, 1\n\n\ndef test_apt_mc_home_parameters_bad_units(init_kdc101):\n    \"\"\"Raise a ValueError if incompatible units are provided.\"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTMotorController,\n        [init_kdc101[0]],\n        [init_kdc101[1]],\n        sep=\"\",\n    ) as apt:\n        # velocity\n        with pytest.raises(ValueError):\n            apt.channel[0].home_parameters = 1, 1, 1 * u.deg / u.sec, 1919\n\n        # offset\n        with pytest.raises(ValueError):\n            apt.channel[0].home_parameters = 1, 1, 42000, 1 * u.Hz\n\n\ndef test_apt_mc_position_encoder(init_kdc101):\n    \"\"\"Get unitful position of encoder, in counts.\"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTMotorController,\n        [\n            init_kdc101[0],\n            ThorLabsPacket(  # read position\n                message_id=ThorLabsCommands.MOT_REQ_ENCCOUNTER,\n                param1=0x01,\n                param2=0x00,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n        ],\n        [\n            init_kdc101[1],\n            ThorLabsPacket(\n                message_id=ThorLabsCommands.MOT_GET_ENCCOUNTER,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<Hl\", 0x01, -20000),\n            ).pack(),\n        ],\n        sep=\"\",\n    ) as apt:\n        assert apt.channel[0].position_encoder == u.Quantity(-20000, \"counts\")\n\n\ndef test_apt_mc_go_home(init_kdc101):\n    \"\"\"Homing routine.\"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTMotorController,\n        [\n            init_kdc101[0],\n            ThorLabsPacket(  # read position\n                message_id=ThorLabsCommands.MOT_MOVE_HOME,\n                param1=0x01,\n                param2=0x00,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n        ],\n        [\n            init_kdc101[1],\n            ThorLabsPacket(  # homing complete package\n                message_id=ThorLabsCommands.MOT_MOVE_HOMED,\n                param1=0x01,\n                param2=0x00,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n        ],\n        sep=\"\",\n    ) as apt:\n        apt.channel[0].go_home()\n\n\ndef test_apt_mc_move(init_kdc101):\n    \"\"\"Move the stage.\"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTMotorController,\n        [\n            init_kdc101[0],\n            ThorLabsPacket(  # encoder count, absolute move\n                message_id=ThorLabsCommands.MOT_MOVE_ABSOLUTE,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<Hl\", 0x01, 1000),\n            ).pack(),\n            ThorLabsPacket(  # encoder count, relative move\n                message_id=ThorLabsCommands.MOT_MOVE_RELATIVE,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<Hl\", 0x01, -1000),\n            ).pack(),\n            ThorLabsPacket(  # encoder count, absolute move\n                message_id=ThorLabsCommands.MOT_MOVE_ABSOLUTE,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<Hl\", 0x01, 1919),\n            ).pack(),\n        ],\n        [\n            init_kdc101[1],\n            ThorLabsPacket(  # move complete message\n                message_id=ThorLabsCommands.MOT_MOVE_COMPLETED,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<HlHHL\", 0x01, 1000, 205, 0, 0),\n            ).pack(),\n            ThorLabsPacket(  # move complete message\n                message_id=ThorLabsCommands.MOT_MOVE_COMPLETED,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<HlHHL\", 0x01, -1000, 205, 0, 0),\n            ).pack(),\n            ThorLabsPacket(  # move complete message\n                message_id=ThorLabsCommands.MOT_MOVE_COMPLETED,\n                param1=None,\n                param2=None,\n                dest=0x50,\n                source=0x01,\n                data=struct.pack(\"<HlHHL\", 0x01, 1919, 205, 0, 0),\n            ).pack(),\n        ],\n        sep=\"\",\n    ) as apt:\n        apt.channel[0].move(1000)\n        apt.channel[0].move(u.Quantity(-1000, \"counts\"), absolute=False)\n\n        # unitful motion: requires motor to be initialized\n        apt.channel[0].motor_model = \"PRM1-Z8\"\n        apt.channel[0].move(u.Quantity(1, u.deg))\n\n        # raise error if units are wrong\n        with pytest.raises(ValueError):\n            apt.channel[0].move(u.Quantity(5, u.mm))\n\n\n# CONTROLLER #\n\n\ndef test_apt_mc_identify(init_kdc101):\n    \"\"\"Identify the controller by blinking its LEDs.\"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTMotorController,\n        [\n            init_kdc101[0],\n            ThorLabsPacket(  # write state\n                message_id=ThorLabsCommands.MOD_IDENTIFY,\n                param1=0x00,\n                param2=0x00,\n                dest=0x50,\n                source=0x01,\n                data=None,\n            ).pack(),\n        ],\n        [init_kdc101[1]],\n        sep=\"\",\n    ) as apt:\n        apt.identify()\n\n\n@pytest.mark.parametrize(\"n_ch\", (0, 1, 2))\ndef test_apt_mc_n_channels(init_kdc101, n_ch):\n    \"\"\"Get / Set the number of channels.\"\"\"\n    with expected_protocol(\n        ik.thorlabs.APTMotorController, [init_kdc101[0]], [init_kdc101[1]], sep=\"\"\n    ) as apt:\n        # print(type(apt._channel))\n        apt.n_channels = n_ch\n        assert apt.n_channels == n_ch\n"
  },
  {
    "path": "tests/test_thorlabs/test_thorlabs_lcc25.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the Thorlabs LCC25\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport pytest\nfrom instruments.units import ureg as u\n\nimport instruments as ik\nfrom tests import expected_protocol, unit_eq\n\n# TESTS ######################################################################\n\n\ndef test_lcc25_name():\n    with expected_protocol(\n        ik.thorlabs.LCC25, [\"*idn?\"], [\"*idn?\", \"bloopbloop\", \"> \"], sep=\"\\r\"\n    ) as lcc:\n        name = lcc.name\n        assert name == \"bloopbloop\", f\"got {name} expected bloopbloop\"\n\n\ndef test_lcc25_frequency():\n    with expected_protocol(\n        ik.thorlabs.LCC25,\n        [\"freq?\", \"freq=10.0\"],\n        [\"freq?\", \"20\", \"> freq=10.0\", \"> \"],\n        sep=\"\\r\",\n    ) as lcc:\n        unit_eq(lcc.frequency, u.Quantity(20, \"Hz\"))\n        lcc.frequency = 10.0\n\n\ndef test_lcc25_frequency_lowlimit():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.thorlabs.LCC25, [\"freq=0.0\"], [\"freq=0.0\", \"> \"], sep=\"\\r\"\n    ) as lcc:\n        lcc.frequency = 0.0\n\n\ndef test_lcc25_frequency_highlimit():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.thorlabs.LCC25, [\"freq=160.0\"], [\"freq=160.0\", \"> \"], sep=\"\\r\"\n    ) as lcc:\n        lcc.frequency = 160.0\n\n\ndef test_lcc25_mode():\n    with expected_protocol(\n        ik.thorlabs.LCC25,\n        [\"mode?\", \"mode=1\"],\n        [\"mode?\", \"2\", \"> mode=1\", \"> \"],\n        sep=\"\\r\",\n    ) as lcc:\n        assert lcc.mode == ik.thorlabs.LCC25.Mode.voltage2\n        lcc.mode = ik.thorlabs.LCC25.Mode.voltage1\n\n\ndef test_lcc25_mode_invalid():\n    with pytest.raises(ValueError), expected_protocol(ik.thorlabs.LCC25, [], []) as lcc:\n        lcc.mode = \"blo\"\n\n\ndef test_lcc25_enable():\n    with expected_protocol(\n        ik.thorlabs.LCC25,\n        [\"enable?\", \"enable=1\"],\n        [\"enable?\", \"0\", \"> enable=1\", \"> \"],\n        sep=\"\\r\",\n    ) as lcc:\n        assert lcc.enable is False\n        lcc.enable = True\n\n\ndef test_lcc25_enable_invalid_type():\n    with pytest.raises(TypeError), expected_protocol(ik.thorlabs.LCC25, [], []) as lcc:\n        lcc.enable = \"blo\"\n\n\ndef test_lcc25_extern():\n    with expected_protocol(\n        ik.thorlabs.LCC25,\n        [\"extern?\", \"extern=1\"],\n        [\"extern?\", \"0\", \"> extern=1\", \"> \"],\n        sep=\"\\r\",\n    ) as lcc:\n        assert lcc.extern is False\n        lcc.extern = True\n\n\ndef test_tc200_extern_invalid_type():\n    with pytest.raises(TypeError), expected_protocol(ik.thorlabs.LCC25, [], []) as tc:\n        tc.extern = \"blo\"\n\n\ndef test_lcc25_remote():\n    with expected_protocol(\n        ik.thorlabs.LCC25,\n        [\"remote?\", \"remote=1\"],\n        [\"remote?\", \"0\", \"> remote=1\", \"> \"],\n        sep=\"\\r\",\n    ) as lcc:\n        assert lcc.remote is False\n        lcc.remote = True\n\n\ndef test_tc200_remote_invalid_type():\n    with pytest.raises(TypeError), expected_protocol(ik.thorlabs.LCC25, [], []) as tc:\n        tc.remote = \"blo\"\n\n\ndef test_lcc25_voltage1():\n    with expected_protocol(\n        ik.thorlabs.LCC25,\n        [\"volt1?\", \"volt1=10.000\"],\n        [\"volt1?\", \"20\", \"> volt1=10.000\", \"> \"],\n        sep=\"\\r\",\n    ) as lcc:\n        unit_eq(lcc.voltage1, u.Quantity(20, \"V\"))\n        lcc.voltage1 = 10.0\n\n\ndef test_check_cmd():\n    assert ik.thorlabs.thorlabs_utils.check_cmd(\"blo\") == 1\n    assert ik.thorlabs.thorlabs_utils.check_cmd(\"CMD_NOT_DEFINED\") == 0\n    assert ik.thorlabs.thorlabs_utils.check_cmd(\"CMD_ARG_INVALID\") == 0\n\n\ndef test_lcc25_voltage2():\n    with expected_protocol(\n        ik.thorlabs.LCC25,\n        [\n            \"volt2?\",\n            \"volt2=10.000\",\n        ],\n        [\"volt2?\", \"20\", \"> volt2=10.000\", \"> \"],\n        sep=\"\\r\",\n    ) as lcc:\n        unit_eq(lcc.voltage2, u.Quantity(20, \"V\"))\n        lcc.voltage2 = 10.0\n\n\ndef test_lcc25_minvoltage():\n    with expected_protocol(\n        ik.thorlabs.LCC25,\n        [\"min?\", \"min=10.000\"],\n        [\"min?\", \"20\", \"> min=10.000\", \"> \"],\n        sep=\"\\r\",\n    ) as lcc:\n        unit_eq(lcc.min_voltage, u.Quantity(20, \"V\"))\n        lcc.min_voltage = 10.0\n\n\ndef test_lcc25_maxvoltage():\n    with expected_protocol(\n        ik.thorlabs.LCC25,\n        [\"max?\", \"max=10.000\"],\n        [\"max?\", \"20\", \"> max=10.000\", \"> \"],\n        sep=\"\\r\",\n    ) as lcc:\n        unit_eq(lcc.max_voltage, u.Quantity(20, \"V\"))\n        lcc.max_voltage = 10.0\n\n\ndef test_lcc25_dwell():\n    with expected_protocol(\n        ik.thorlabs.LCC25,\n        [\"dwell?\", \"dwell=10\"],\n        [\"dwell?\", \"20\", \"> dwell=10\", \"> \"],\n        sep=\"\\r\",\n    ) as lcc:\n        unit_eq(lcc.dwell, u.Quantity(20, \"ms\"))\n        lcc.dwell = 10\n\n\ndef test_lcc25_dwell_positive():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.thorlabs.LCC25, [\"dwell=-10\"], [\"dwell=-10\", \"> \"], sep=\"\\r\"\n    ) as lcc:\n        lcc.dwell = -10\n\n\ndef test_lcc25_increment():\n    with expected_protocol(\n        ik.thorlabs.LCC25,\n        [\"increment?\", \"increment=10.000\"],\n        [\"increment?\", \"20\", \"> increment=10.000\", \"> \"],\n        sep=\"\\r\",\n    ) as lcc:\n        unit_eq(lcc.increment, u.Quantity(20, \"V\"))\n        lcc.increment = 10.0\n\n\ndef test_lcc25_increment_positive():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.thorlabs.LCC25, [\"increment=-10\"], [\"increment=-10\", \"> \"], sep=\"\\r\"\n    ) as lcc:\n        lcc.increment = -10\n\n\ndef test_lcc25_default():\n    with expected_protocol(\n        ik.thorlabs.LCC25, [\"default\"], [\"default\", \"1\", \"> \"], sep=\"\\r\"\n    ) as lcc:\n        lcc.default()\n\n\ndef test_lcc25_save():\n    with expected_protocol(\n        ik.thorlabs.LCC25, [\"save\"], [\"save\", \"1\", \"> \"], sep=\"\\r\"\n    ) as lcc:\n        lcc.save()\n\n\ndef test_lcc25_set_settings():\n    with expected_protocol(\n        ik.thorlabs.LCC25, [\"set=2\"], [\"set=2\", \"1\", \"> \"], sep=\"\\r\"\n    ) as lcc:\n        lcc.set_settings(2)\n\n\ndef test_lcc25_set_settings_invalid():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.thorlabs.LCC25, [], [], sep=\"\\r\"\n    ) as lcc:\n        lcc.set_settings(5)\n\n\ndef test_lcc25_get_settings():\n    with expected_protocol(\n        ik.thorlabs.LCC25, [\"get=2\"], [\"get=2\", \"1\", \"> \"], sep=\"\\r\"\n    ) as lcc:\n        lcc.get_settings(2)\n\n\ndef test_lcc25_get_settings_invalid():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.thorlabs.LCC25, [], [], sep=\"\\r\"\n    ) as lcc:\n        lcc.get_settings(5)\n\n\ndef test_lcc25_test_mode():\n    with expected_protocol(\n        ik.thorlabs.LCC25, [\"test\"], [\"test\", \"1\", \"> \"], sep=\"\\r\"\n    ) as lcc:\n        lcc.test_mode()\n\n\ndef test_lcc25_remote_invalid_type():\n    with pytest.raises(TypeError), expected_protocol(ik.thorlabs.LCC25, [], []) as lcc:\n        lcc.remote = \"blo\"\n\n\ndef test_lcc25_extern_invalid_type():\n    with pytest.raises(TypeError), expected_protocol(ik.thorlabs.LCC25, [], []) as lcc:\n        lcc.extern = \"blo\"\n"
  },
  {
    "path": "tests/test_thorlabs/test_thorlabs_pm100usb.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the Thorlabs PM100USB\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nfrom hypothesis import (\n    given,\n    strategies as st,\n)\nimport pytest\n\nimport instruments as ik\nfrom tests import expected_protocol\n\n# TESTS ######################################################################\n\n\n# pylint: disable=protected-access,redefined-outer-name\n\n\n# FIXTURES #\n\n\n@pytest.fixture\ndef init_sensor():\n    \"\"\"Initialize a sensor - return initialized sensor class.\"\"\"\n\n    class Sensor:\n        \"\"\"Initialize a sensor class\"\"\"\n\n        NAME = \"SENSOR\"\n        SERIAL_NUMBER = \"123456\"\n        CALIBRATION_MESSAGE = \"OK\"\n        SENSOR_TYPE = \"TEMPERATURE\"\n        SENSOR_SUBTYPE = \"KDP\"\n        FLAGS = \"256\"\n\n        def sendmsg(self):\n            return \"SYST:SENSOR:IDN?\"\n\n        def message(self):\n            return \",\".join(\n                [\n                    self.NAME,\n                    self.SERIAL_NUMBER,\n                    self.CALIBRATION_MESSAGE,\n                    self.SENSOR_TYPE,\n                    self.SENSOR_SUBTYPE,\n                    self.FLAGS,\n                ]\n            )\n\n    return Sensor()\n\n\n# SENSOR CLASS #\n\n\ndef test_sensor_init(init_sensor):\n    \"\"\"Initialize a sensor object from the parent class.\"\"\"\n    with expected_protocol(\n        ik.thorlabs.PM100USB, [init_sensor.sendmsg()], [init_sensor.message()]\n    ) as inst:\n        assert inst.sensor._parent is inst\n\n\ndef test_sensor_name(init_sensor):\n    \"\"\"Get name of the sensor.\"\"\"\n    with expected_protocol(\n        ik.thorlabs.PM100USB, [init_sensor.sendmsg()], [init_sensor.message()]\n    ) as inst:\n        assert inst.sensor.name == init_sensor.NAME\n\n\ndef test_sensor_serial_number(init_sensor):\n    \"\"\"Get serial number of the sensor.\"\"\"\n    with expected_protocol(\n        ik.thorlabs.PM100USB, [init_sensor.sendmsg()], [init_sensor.message()]\n    ) as inst:\n        assert inst.sensor.serial_number == init_sensor.SERIAL_NUMBER\n\n\ndef test_sensor_calibration_message(init_sensor):\n    \"\"\"Get calibration message of the sensor.\"\"\"\n    with expected_protocol(\n        ik.thorlabs.PM100USB, [init_sensor.sendmsg()], [init_sensor.message()]\n    ) as inst:\n        assert inst.sensor.calibration_message == init_sensor.CALIBRATION_MESSAGE\n\n\ndef test_sensor_type(init_sensor):\n    \"\"\"Get type of the sensor.\"\"\"\n    with expected_protocol(\n        ik.thorlabs.PM100USB, [init_sensor.sendmsg()], [init_sensor.message()]\n    ) as inst:\n        assert inst.sensor.type == (init_sensor.SENSOR_TYPE, init_sensor.SENSOR_SUBTYPE)\n\n\ndef test_sensor_flags(init_sensor):\n    \"\"\"Get flags of the sensor.\"\"\"\n    flag_read = init_sensor.FLAGS\n    flags = ik.thorlabs.PM100USB._SensorFlags(\n        **{e.name: bool(e & int(flag_read)) for e in ik.thorlabs.PM100USB.SensorFlags}\n    )\n    with expected_protocol(\n        ik.thorlabs.PM100USB, [init_sensor.sendmsg()], [init_sensor.message()]\n    ) as inst:\n        assert inst.sensor.flags == flags\n\n\n# INSTRUMENT #\n\n\ndef test_cache_units():\n    \"\"\"Get, set cache units bool.\"\"\"\n    msr_conf = ik.thorlabs.PM100USB.MeasurementConfiguration.current\n    with expected_protocol(\n        ik.thorlabs.PM100USB,\n        [\"CONF?\"],\n        [f\"{msr_conf.value}\"],  # measurement configuration temperature\n    ) as inst:\n        inst.cache_units = True\n        assert inst._cache_units == inst._READ_UNITS[msr_conf]\n        inst.cache_units = False\n        assert not inst.cache_units\n\n\n@pytest.mark.parametrize(\"msr_conf\", ik.thorlabs.PM100USB.MeasurementConfiguration)\ndef test_measurement_configuration(msr_conf):\n    \"\"\"Get / set measurement configuration.\"\"\"\n    with expected_protocol(\n        ik.thorlabs.PM100USB,\n        [f\"CONF {msr_conf.value}\", \"CONF?\"],\n        [f\"{msr_conf.value}\"],  # measurement configuration temperature\n    ) as inst:\n        inst.measurement_configuration = msr_conf\n        assert inst.measurement_configuration == msr_conf\n\n\n@given(value=st.integers(min_value=1))\ndef test_averaging_count(value):\n    \"\"\"Get / set averaging count.\"\"\"\n    with expected_protocol(\n        ik.thorlabs.PM100USB,\n        [f\"SENS:AVER:COUN {value}\", \"SENS:AVER:COUN?\"],\n        [f\"{value}\"],  # measurement configuration temperature\n    ) as inst:\n        inst.averaging_count = value\n        assert inst.averaging_count == value\n\n\n@given(value=st.integers(max_value=0))\ndef test_averaging_count_value_error(value):\n    \"\"\"Raise a ValueError if the averaging count is wrong.\"\"\"\n    with expected_protocol(ik.thorlabs.PM100USB, [], []) as inst:\n        with pytest.raises(ValueError) as err_info:\n            inst.averaging_count = value\n        err_msg = err_info.value.args[0]\n        assert err_msg == \"Must count at least one time.\"\n\n\n@given(value=st.floats(min_value=0))\ndef test_read(value):\n    \"\"\"Read instrument and grab the units.\"\"\"\n    msr_conf = ik.thorlabs.PM100USB.MeasurementConfiguration.current\n    with expected_protocol(\n        ik.thorlabs.PM100USB,\n        [\"CONF?\", \"READ?\"],\n        [f\"{msr_conf.value}\", f\"{value}\"],  # measurement configuration temperature\n    ) as inst:\n        units = inst._READ_UNITS[msr_conf]  # cache units is False at init\n        assert inst.read() == value * units\n\n\ndef test_read_cached_units():\n    \"\"\"Read instrument and grab the units.\"\"\"\n    msr_conf = ik.thorlabs.PM100USB.MeasurementConfiguration.current\n    value = 42\n    with expected_protocol(\n        ik.thorlabs.PM100USB,\n        [\"CONF?\", \"READ?\"],\n        [f\"{msr_conf.value}\", f\"{value}\"],  # measurement configuration temperature\n    ) as inst:\n        units = inst._READ_UNITS[msr_conf]  # cache units is False at init\n        inst.cache_units = True\n        assert inst.read() == value * units\n"
  },
  {
    "path": "tests/test_thorlabs/test_thorlabs_sc10.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the Thorlabs SC10\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport pytest\nfrom instruments.units import ureg as u\n\nimport instruments as ik\nfrom tests import expected_protocol, unit_eq\n\n# TESTS ######################################################################\n\n\ndef test_sc10_name():\n    with expected_protocol(\n        ik.thorlabs.SC10, [\"id?\"], [\"id?\", \"bloopbloop\", \"> \"], sep=\"\\r\"\n    ) as sc:\n        assert sc.name == \"bloopbloop\"\n\n\ndef test_sc10_enable_query():\n    with expected_protocol(\n        ik.thorlabs.SC10, [\"ens?\"], [\"ens?\", \"0\", \"> \"], sep=\"\\r\"\n    ) as sc:\n        assert sc.enable is False\n\n\n@pytest.mark.parametrize(\"status\", [0, 1])\n@pytest.mark.parametrize(\"value\", [0, 1])\ndef test_sc10_enable_send(status, value):\n    host_to_ins = [\"ens?\"]\n    ins_to_host = [\"ens?\", f\"{status}\"]\n    if value != status:\n        host_to_ins.append(\"ens\")\n        ins_to_host += [\"> ens\", \"> \"]\n    else:\n        ins_to_host.append(\"> \")\n    with expected_protocol(ik.thorlabs.SC10, host_to_ins, ins_to_host, sep=\"\\r\") as sc:\n        sc.enable = bool(value)\n\n\ndef test_sc10_enable_invalid():\n    with pytest.raises(TypeError), expected_protocol(\n        ik.thorlabs.SC10, [], [], sep=\"\\r\"\n    ) as sc:\n        sc.enable = 10\n\n\ndef test_sc10_repeat():\n    with expected_protocol(\n        ik.thorlabs.SC10, [\"rep?\", \"rep=10\"], [\"rep?\", \"20\", \"> rep=10\", \"> \"], sep=\"\\r\"\n    ) as sc:\n        assert sc.repeat == 20\n        sc.repeat = 10\n\n\ndef test_sc10_repeat_invalid():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.thorlabs.SC10, [], [], sep=\"\\r\"\n    ) as sc:\n        sc.repeat = -1\n\n\ndef test_sc10_mode():\n    with expected_protocol(\n        ik.thorlabs.SC10,\n        [\"mode?\", \"mode=2\"],\n        [\"mode?\", \"1\", \"> mode=2\", \"> \"],\n        sep=\"\\r\",\n    ) as sc:\n        assert sc.mode == ik.thorlabs.SC10.Mode.manual\n        sc.mode = ik.thorlabs.SC10.Mode.auto\n\n\ndef test_sc10_mode_invalid():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.thorlabs.SC10, [], [], sep=\"\\r\"\n    ) as sc:\n        sc.mode = \"blo\"\n\n\ndef test_sc10_trigger():\n    with expected_protocol(\n        ik.thorlabs.SC10,\n        [\"trig?\", \"trig=1\"],\n        [\"trig?\", \"0\", \"> trig=1\", \"> \"],\n        sep=\"\\r\",\n    ) as sc:\n        assert sc.trigger == 0\n        sc.trigger = 1\n\n\ndef test_sc10_out_trigger():\n    with expected_protocol(\n        ik.thorlabs.SC10, [\"xto?\", \"xto=1\"], [\"xto?\", \"0\", \"> xto=1\", \"> \"], sep=\"\\r\"\n    ) as sc:\n        assert sc.out_trigger == 0\n        sc.out_trigger = 1\n\n\ndef test_sc10_open_time():\n    with expected_protocol(\n        ik.thorlabs.SC10,\n        [\"open?\", \"open=10\"],\n        [\"open?\", \"20\", \"> open=10\", \"> \"],\n        sep=\"\\r\",\n    ) as sc:\n        unit_eq(sc.open_time, u.Quantity(20, \"ms\"))\n        sc.open_time = 10\n\n\ndef test_sc10_shut_time():\n    with expected_protocol(\n        ik.thorlabs.SC10,\n        [\"shut?\", \"shut=10\"],\n        [\"shut?\", \"20\", \"> shut=10\", \"> \"],\n        sep=\"\\r\",\n    ) as sc:\n        unit_eq(sc.shut_time, u.Quantity(20, \"ms\"))\n        sc.shut_time = 10.0\n\n\ndef test_sc10_baud_rate():\n    with expected_protocol(\n        ik.thorlabs.SC10,\n        [\"baud?\", \"baud=1\"],\n        [\"baud?\", \"0\", \"> baud=1\", \"> \"],\n        sep=\"\\r\",\n    ) as sc:\n        assert sc.baud_rate == 9600\n        sc.baud_rate = 115200\n\n\ndef test_sc10_baud_rate_error():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.thorlabs.SC10, [], [], sep=\"\\r\"\n    ) as sc:\n        sc.baud_rate = 115201\n\n\ndef test_sc10_closed():\n    with expected_protocol(\n        ik.thorlabs.SC10, [\"closed?\"], [\"closed?\", \"1\", \"> \"], sep=\"\\r\"\n    ) as sc:\n        assert sc.closed\n\n\ndef test_sc10_interlock():\n    with expected_protocol(\n        ik.thorlabs.SC10, [\"interlock?\"], [\"interlock?\", \"1\", \"> \"], sep=\"\\r\"\n    ) as sc:\n        assert sc.interlock\n\n\ndef test_sc10_default():\n    with expected_protocol(\n        ik.thorlabs.SC10, [\"default\"], [\"default\", \"1\", \"> \"], sep=\"\\r\"\n    ) as sc:\n        assert sc.default()\n\n\ndef test_sc10_save():\n    with expected_protocol(\n        ik.thorlabs.SC10, [\"savp\"], [\"savp\", \"1\", \"> \"], sep=\"\\r\"\n    ) as sc:\n        assert sc.save()\n\n\ndef test_sc10_save_mode():\n    with expected_protocol(\n        ik.thorlabs.SC10, [\"save\"], [\"save\", \"1\", \"> \"], sep=\"\\r\"\n    ) as sc:\n        assert sc.save_mode()\n\n\ndef test_sc10_restore():\n    with expected_protocol(\n        ik.thorlabs.SC10, [\"resp\"], [\"resp\", \"1\", \"> \"], sep=\"\\r\"\n    ) as sc:\n        assert sc.restore()\n"
  },
  {
    "path": "tests/test_thorlabs/test_thorlabs_tc200.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the Thorlabs TC200\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nfrom enum import IntEnum\nimport pytest\nfrom instruments.units import ureg as u\n\nimport instruments as ik\nfrom tests import expected_protocol\n\n# TESTS ######################################################################\n\n\ndef test_tc200_name():\n    with expected_protocol(\n        ik.thorlabs.TC200, [\"*idn?\"], [\"*idn?\", \"bloopbloop\", \"> \"], sep=\"\\r\"\n    ) as tc:\n        assert tc.name() == \"bloopbloop\"\n\n\ndef test_tc200_mode():\n    with expected_protocol(\n        ik.thorlabs.TC200,\n        [\"stat?\", \"stat?\", \"mode=cycle\"],\n        [\"stat?\", \"0 > stat?\", \"2 >  mode=cycle\", \"> \"],\n        sep=\"\\r\",\n    ) as tc:\n        assert tc.mode == tc.Mode.normal\n        assert tc.mode == tc.Mode.cycle\n        tc.mode = ik.thorlabs.TC200.Mode.cycle\n\n\ndef test_tc200_mode_2():\n    with expected_protocol(\n        ik.thorlabs.TC200,\n        [\"mode=normal\"],\n        [\"mode=normal\", \"Command error CMD_ARG_RANGE_ERR\\n\", \"> \"],\n        sep=\"\\r\",\n    ) as tc:\n        tc.mode = ik.thorlabs.TC200.Mode.normal\n\n\ndef test_tc200_mode_error():\n    with pytest.raises(TypeError), expected_protocol(\n        ik.thorlabs.TC200, [], [], sep=\"\\r\"\n    ) as tc:\n        tc.mode = \"blo\"\n\n\ndef test_tc200_mode_error2():\n    with pytest.raises(TypeError), expected_protocol(\n        ik.thorlabs.TC200, [], [], sep=\"\\r\"\n    ) as tc:\n\n        class TestEnum(IntEnum):\n            blo = 1\n            beep = 2\n\n        tc.mode = TestEnum.blo\n\n\ndef test_tc200_enable():\n    with expected_protocol(\n        ik.thorlabs.TC200,\n        [\"stat?\", \"stat?\", \"ens\", \"stat?\", \"ens\"],\n        [\"stat?\", \"54 > stat?\", \"54 > ens\", \"> stat?\", \"55 > ens\", \"> \"],\n        sep=\"\\r\",\n    ) as tc:\n        assert tc.enable == 0\n        tc.enable = True\n        tc.enable = False\n\n\ndef test_tc200_enable_type():\n    with pytest.raises(TypeError), expected_protocol(\n        ik.thorlabs.TC200, [], [], sep=\"\\r\"\n    ) as tc:\n        tc.enable = \"blo\"\n\n\ndef test_tc200_temperature():\n    with expected_protocol(\n        ik.thorlabs.TC200,\n        [\n            \"tact?\",\n        ],\n        [\n            \"tact?\",\n            \"30 C\",\n            \"> \",\n        ],\n        sep=\"\\r\",\n    ) as tc:\n        assert tc.temperature == u.Quantity(30.0, u.degC)\n\n\ndef test_tc200_temperature_set():\n    with expected_protocol(\n        ik.thorlabs.TC200,\n        [\"tset?\", \"tmax?\", \"tset=40\"],\n        [\"tset?\", \"30 C\", \"> tmax?\", \"250\", \"> tset=40\", \"> \"],\n        sep=\"\\r\",\n    ) as tc:\n        assert tc.temperature_set == u.Quantity(30.0, u.degC)\n        tc.temperature_set = u.Quantity(40, u.degC)\n\n\ndef test_tc200_temperature_set_celsius():\n    \"\"\"Ensure celsius is stripped if returned by instrument, see issue #331\"\"\"\n    with expected_protocol(\n        ik.thorlabs.TC200, [\"tset?\"], [\"tset?\", \"30 Celsius\", \"> \"], sep=\"\\r\"\n    ) as tc:\n        assert tc.temperature_set == u.Quantity(30.0, u.degC)\n\n\ndef test_tc200_temperature_range():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.thorlabs.TC200, [\"tmax?\"], [\"tmax?\", \"40\", \"> \"], sep=\"\\r\"\n    ) as tc:\n        tc.temperature_set = u.Quantity(50, u.degC)\n\n\ndef test_tc200_pid():\n    with expected_protocol(\n        ik.thorlabs.TC200,\n        [\"pid?\", \"pgain=2\"],\n        [\"pid?\", \"2 0 220\", \"> pgain=2\", \"> \"],\n        sep=\"\\r\",\n    ) as tc:\n        assert tc.p == 2\n        tc.p = 2\n\n    with expected_protocol(\n        ik.thorlabs.TC200,\n        [\"pid?\", \"igain=0\"],\n        [\"pid?\", \"2 0 220\", \"> igain=0\", \"> \"],\n        sep=\"\\r\",\n    ) as tc:\n        assert tc.i == 0\n        tc.i = 0\n\n    with expected_protocol(\n        ik.thorlabs.TC200,\n        [\"pid?\", \"dgain=220\"],\n        [\"pid?\", \"2 0 220\", \"> dgain=220\", \"> \"],\n        sep=\"\\r\",\n    ) as tc:\n        assert tc.d == 220\n        tc.d = 220\n\n    with expected_protocol(\n        ik.thorlabs.TC200,\n        [\"pid?\", \"pgain=2\", \"igain=0\", \"dgain=220\"],\n        [\"pid?\", \"2 0 220\", \"> pgain=2\", \"> igain=0\", \"> dgain=220\", \"> \"],\n        sep=\"\\r\",\n    ) as tc:\n        assert tc.pid == [2, 0, 220]\n        tc.pid = (2, 0, 220)\n\n\ndef test_tc200_pid_invalid_type():\n    with pytest.raises(TypeError), expected_protocol(\n        ik.thorlabs.TC200, [], [], sep=\"\\r\"\n    ) as tc:\n        tc.pid = \"foo\"\n\n\ndef test_tc200_pmin():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.thorlabs.TC200, [\"pgain=-1\"], [\"pgain=-1\", \"> \"], sep=\"\\r\"\n    ) as tc:\n        tc.p = -1\n\n\ndef test_tc200_pmax():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.thorlabs.TC200, [\"pgain=260\"], [\"pgain=260\", \"> \"], sep=\"\\r\"\n    ) as tc:\n        tc.p = 260\n\n\ndef test_tc200_imin():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.thorlabs.TC200, [\"igain=-1\"], [\"igain=-1\", \"> \"], sep=\"\\r\"\n    ) as tc:\n        tc.i = -1\n\n\ndef test_tc200_imax():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.thorlabs.TC200, [\"igain=260\"], [\"igain=260\", \"> \"], sep=\"\\r\"\n    ) as tc:\n        tc.i = 260\n\n\ndef test_tc200_dmin():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.thorlabs.TC200, [\"dgain=-1\"], [\"dgain=-1\", \"> \"], sep=\"\\r\"\n    ) as tc:\n        tc.d = -1\n\n\ndef test_tc200_dmax():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.thorlabs.TC200, [\"dgain=260\"], [\"dgain=260\", \"> \"], sep=\"\\r\"\n    ) as tc:\n        tc.d = 260\n\n\ndef test_tc200_degrees():\n    with expected_protocol(\n        ik.thorlabs.TC200,\n        [\"stat?\", \"stat?\", \"stat?\", \"unit=c\", \"unit=f\", \"unit=k\"],\n        [\n            \"stat?\",\n            \"44 > stat?\",\n            \"54 > stat?\",\n            \"0 >  unit=c\",\n            \"> unit=f\",\n            \"> unit=k\",\n            \"> \",\n        ],\n        sep=\"\\r\",\n    ) as tc:\n        assert tc.degrees == u.degK\n        assert tc.degrees == u.degC\n        assert tc.degrees == u.degF\n\n        tc.degrees = u.degC\n        tc.degrees = u.degF\n        tc.degrees = u.degK\n\n\ndef test_tc200_degrees_invalid():\n    with pytest.raises(TypeError), expected_protocol(\n        ik.thorlabs.TC200, [], [], sep=\"\\r\"\n    ) as tc:\n        tc.degrees = \"blo\"\n\n\ndef test_tc200_sensor():\n    with expected_protocol(\n        ik.thorlabs.TC200,\n        [\"sns?\", \"sns=ptc100\"],\n        [\"sns?\", \"Sensor = NTC10K, Beta = 5600\", \"> sns=ptc100\", \"> \"],\n        sep=\"\\r\",\n    ) as tc:\n        assert tc.sensor == tc.Sensor.ntc10k\n        tc.sensor = tc.Sensor.ptc100\n\n\ndef test_tc200_sensor_error():\n    with pytest.raises(ValueError), expected_protocol(ik.thorlabs.TC200, [], []) as tc:\n        tc.sensor = \"blo\"\n\n\ndef test_tc200_sensor_error2():\n    with pytest.raises(ValueError), expected_protocol(ik.thorlabs.TC200, [], []) as tc:\n\n        class TestEnum(IntEnum):\n            blo = 1\n            beep = 2\n\n        tc.sensor = TestEnum.blo\n\n\ndef test_tc200_beta():\n    with expected_protocol(\n        ik.thorlabs.TC200,\n        [\"beta?\", \"beta=2000\"],\n        [\"beta?\", \"5600\", \"> beta=2000\", \"> \"],\n        sep=\"\\r\",\n    ) as tc:\n        assert tc.beta == 5600\n        tc.beta = 2000\n\n\ndef test_tc200_beta_min():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.thorlabs.TC200, [\"beta=200\"], [\"beta=200\", \"> \"], sep=\"\\r\"\n    ) as tc:\n        tc.beta = 200\n\n\ndef test_tc200_beta_max():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.thorlabs.TC200, [\"beta=20000\"], [\"beta=20000\", \"> \"], sep=\"\\r\"\n    ) as tc:\n        tc.beta = 20000\n\n\ndef test_tc200_max_power():\n    with expected_protocol(\n        ik.thorlabs.TC200,\n        [\"pmax?\", \"pmax=12.0\"],\n        [\"pmax?\", \"15.0\", \"> pmax=12.0\", \"> \"],\n        sep=\"\\r\",\n    ) as tc:\n        assert tc.max_power == 15.0 * u.W\n        tc.max_power = 12 * u.W\n\n\ndef test_tc200_power_min():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.thorlabs.TC200, [\"PMAX=-2\"], [\"PMAX=-2\", \"> \"], sep=\"\\r\"\n    ) as tc:\n        tc.max_power = -1\n\n\ndef test_tc200_power_max():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.thorlabs.TC200, [\"PMAX=20000\"], [\"PMAX=20000\", \"> \"], sep=\"\\r\"\n    ) as tc:\n        tc.max_power = 20000\n\n\ndef test_tc200_max_temperature():\n    with expected_protocol(\n        ik.thorlabs.TC200,\n        [\"tmax?\", \"tmax=180.0\"],\n        [\"tmax?\", \"200.0\", \"> tmax=180.0\", \"> \"],\n        sep=\"\\r\",\n    ) as tc:\n        assert tc.max_temperature == u.Quantity(200.0, u.degC)\n        tc.max_temperature = u.Quantity(180, u.degC)\n\n\ndef test_tc200_temp_min():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.thorlabs.TC200, [\"TMAX=-2\"], [\"TMAX=-2\", \">\"], sep=\"\\r\"\n    ) as tc:\n        tc.max_temperature = -1\n\n\ndef test_tc200_temp_max():\n    with pytest.raises(ValueError), expected_protocol(\n        ik.thorlabs.TC200, [\"TMAX=20000\"], [\"TMAX=20000\", \">\"], sep=\"\\r\"\n    ) as tc:\n        tc.max_temperature = 20000\n"
  },
  {
    "path": "tests/test_thorlabs/test_utils.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the Thorlabs util functions\n\"\"\"\n\n# IMPORTS ####################################################################\n\n\nimport instruments as ik\n\n# TESTS ######################################################################\n\n\ndef test_check_cmd():\n    assert ik.thorlabs.thorlabs_utils.check_cmd(\"blo\") == 1\n    assert ik.thorlabs.thorlabs_utils.check_cmd(\"CMD_NOT_DEFINED\") == 0\n    assert ik.thorlabs.thorlabs_utils.check_cmd(\"CMD_ARG_INVALID\") == 0\n"
  },
  {
    "path": "tests/test_toptica/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_toptica/test_toptica_topmode.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for the Toptica Topmode\n\"\"\"\n\n# IMPORTS #####################################################################\n\nfrom datetime import datetime\nimport pytest\nfrom instruments.units import ureg as u\n\n\nimport instruments as ik\nfrom tests import expected_protocol\n\n# TESTS #######################################################################\n\n\ndef test_laser_serial_number():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        [\"(param-ref 'laser1:serial-number)\", \"(param-ref 'laser2:serial-number)\"],\n        [\n            \"(param-ref 'laser1:serial-number)\",\n            \"bloop1\",\n            \"> (param-ref 'laser2:serial-number)\",\n            \"bloop2\",\n            \"> \",\n        ],\n        sep=\"\\r\\n\",\n    ) as tm:\n        assert tm.laser[0].serial_number == \"bloop1\"\n        assert tm.laser[1].serial_number == \"bloop2\"\n\n\ndef test_model():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        [\"(param-ref 'laser1:model)\", \"(param-ref 'laser2:model)\"],\n        [\n            \"(param-ref 'laser1:model)\",\n            \"bloop1\",\n            \"> (param-ref 'laser2:model)\",\n            \"bloop2\",\n            \"> \",\n        ],\n        sep=\"\\r\\n\",\n    ) as tm:\n        assert tm.laser[0].model == \"bloop1\"\n        assert tm.laser[1].model == \"bloop2\"\n\n\ndef test_wavelength():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        [\"(param-ref 'laser1:wavelength)\", \"(param-ref 'laser2:wavelength)\"],\n        [\n            \"(param-ref 'laser1:wavelength)\",\n            \"640\",\n            \"> (param-ref 'laser2:wavelength)\",\n            \"405.3\",\n            \"> \",\n        ],\n        sep=\"\\r\\n\",\n    ) as tm:\n        assert tm.laser[0].wavelength == 640 * u.nm\n        assert tm.laser[1].wavelength == 405.3 * u.nm\n\n\ndef test_laser_enable():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        [\n            \"(param-ref 'laser1:emission)\",\n            \"(param-ref 'laser1:serial-number)\",\n            \"(param-set! 'laser1:enable-emission #t)\",\n        ],\n        [\n            \"(param-ref 'laser1:emission)\",\n            \"#f\",\n            \"> (param-ref 'laser1:serial-number)\",\n            \"bloop1\",\n            \"> (param-set! 'laser1:enable-emission #t)\",\n            \"0\",\n            \"> \",\n        ],\n        sep=\"\\r\\n\",\n    ) as tm:\n        assert tm.laser[0].enable is False\n        tm.laser[0].enable = True\n\n\ndef test_laser_enable_no_laser():\n    with pytest.raises(RuntimeError), expected_protocol(\n        ik.toptica.TopMode,\n        [\n            \"(param-ref 'laser1:serial-number)\",\n            \"(param-set! 'laser1:enable-emission #t)\",\n        ],\n        [\n            \"(param-ref 'laser1:serial-number)\",\n            \"unknown\",\n            \"> (param-set! 'laser1:enable-emission #t)\",\n            \"0\",\n            \"> \",\n        ],\n        sep=\"\\r\\n\",\n    ) as tm:\n        tm.laser[0].enable = True\n\n\ndef test_laser_enable_error():\n    with pytest.raises(TypeError), expected_protocol(\n        ik.toptica.TopMode,\n        [\n            \"(param-ref 'laser1:serial-number)\",\n            \"(param-set! 'laser1:enable-emission #t)\",\n        ],\n        [\n            \"(param-ref 'laser1:serial-number)\",\n            \"bloop1\",\n            \"> (param-set! 'laser1:enable-emission #t)\",\n            \"0\",\n            \"> \",\n        ],\n        sep=\"\\n\",\n    ) as tm:\n        tm.laser[0].enable = \"True\"\n\n\ndef test_laser_tec_status():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        [\"(param-ref 'laser1:tec:ready)\"],\n        [\"(param-ref 'laser1:tec:ready)\", \"#f\", \"> \"],\n        sep=\"\\r\\n\",\n    ) as tm:\n        assert tm.laser[0].tec_status is False\n\n\ndef test_laser_intensity():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        [\"(param-ref 'laser1:intensity)\"],\n        [\"(param-ref 'laser1:intensity)\", \"0.666\", \"> \"],\n        sep=\"\\r\\n\",\n    ) as tm:\n        assert tm.laser[0].intensity == 0.666\n\n\ndef test_laser_mode_hop():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        [\"(param-ref 'laser1:charm:reg:mh-occurred)\"],\n        [\"(param-ref 'laser1:charm:reg:mh-occurred)\", \"#f\", \"> \"],\n        sep=\"\\r\\n\",\n    ) as tm:\n        assert tm.laser[0].mode_hop is False\n\n\ndef test_laser_lock_start():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        [\n            \"(param-ref 'laser1:charm:correction-status)\",\n            \"(param-ref 'laser1:charm:reg:started)\",\n        ],\n        [\n            \"(param-ref 'laser1:charm:correction-status)\",\n            \"2\",\n            \"> (param-ref 'laser1:charm:reg:started)\",\n            '\"2012-12-01 01:02:01\"',\n            \"> \",\n        ],\n        sep=\"\\r\\n\",\n    ) as tm:\n        _date = datetime(2012, 12, 1, 1, 2, 1)\n        assert tm.laser[0].lock_start == _date\n\n\ndef test_laser_lock_start_runtime_error():\n    with pytest.raises(RuntimeError), expected_protocol(\n        ik.toptica.TopMode,\n        [\n            \"(param-ref 'laser1:charm:correction-status)\",\n            \"(param-ref 'laser1:charm:reg:started)\",\n        ],\n        [\n            \"(param-ref 'laser1:charm:correction-status)\",\n            \"0\",\n            \"> (param-ref 'laser1:charm:reg:started)\",\n            '\"\"',\n            \"> \",\n        ],\n        sep=\"\\r\\n\",\n    ) as tm:\n        _date = datetime(2012, 12, 1, 1, 2, 1)\n        assert tm.laser[0].lock_start == _date\n\n\ndef test_laser_first_mode_hop_time_runtime_error():\n    with pytest.raises(RuntimeError), expected_protocol(\n        ik.toptica.TopMode,\n        [\n            \"(param-ref 'laser1:charm:reg:mh-occurred)\",\n            \"(param-ref 'laser1:charm:reg:first-mh)\",\n        ],\n        [\n            \"(param-ref 'laser1:charm:reg:mh-occurred)\",\n            \"#f\",\n            \"> (param-ref 'laser1:charm:reg:first-mh)\",\n            '\"\"',\n            \"> \",\n        ],\n        sep=\"\\r\\n\",\n    ) as tm:\n        assert tm.laser[0].first_mode_hop_time is None\n\n\ndef test_laser_first_mode_hop_time():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        [\n            \"(param-ref 'laser1:charm:reg:mh-occurred)\",\n            \"(param-ref 'laser1:charm:reg:first-mh)\",\n        ],\n        [\n            \"(param-ref 'laser1:charm:reg:mh-occurred)\",\n            \"#t\",\n            \"> (param-ref 'laser1:charm:reg:first-mh)\",\n            '\"2012-12-01 01:02:01\"',\n            \"> \",\n        ],\n        sep=\"\\r\\n\",\n    ) as tm:\n        _date = datetime(2012, 12, 1, 1, 2, 1)\n        assert tm.laser[0].first_mode_hop_time == _date\n\n\ndef test_laser_latest_mode_hop_time_none():\n    with pytest.raises(RuntimeError), expected_protocol(\n        ik.toptica.TopMode,\n        [\n            \"(param-ref 'laser1:charm:reg:mh-occurred)\",\n            \"(param-ref 'laser1:charm:reg:latest-mh)\",\n        ],\n        [\n            \"(param-ref 'laser1:charm:reg:mh-occurred)\",\n            \"#f\",\n            \"> (param-ref 'laser1:charm:reg:latest-mh)\",\n            '\"\"',\n            \"> \",\n        ],\n        sep=\"\\r\\n\",\n    ) as tm:\n        assert tm.laser[0].latest_mode_hop_time is None\n\n\ndef test_laser_latest_mode_hop_time():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        [\n            \"(param-ref 'laser1:charm:reg:mh-occurred)\",\n            \"(param-ref 'laser1:charm:reg:latest-mh)\",\n        ],\n        [\n            \"(param-ref 'laser1:charm:reg:mh-occurred)\",\n            \"#t\",\n            \"> (param-ref 'laser1:charm:reg:latest-mh)\",\n            '\"2012-12-01 01:02:01\"',\n            \"> \",\n        ],\n        sep=\"\\r\\n\",\n    ) as tm:\n        _date = datetime(2012, 12, 1, 1, 2, 1)\n        assert tm.laser[0].latest_mode_hop_time == _date\n\n\ndef test_laser_correction_status():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        [\"(param-ref 'laser1:charm:correction-status)\"],\n        [\"(param-ref 'laser1:charm:correction-status)\", \"0\", \"> \"],\n        sep=\"\\r\\n\",\n    ) as tm:\n        assert (\n            tm.laser[0].correction_status\n            == ik.toptica.TopMode.CharmStatus.un_initialized\n        )\n\n\ndef test_laser_correction():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        [\n            \"(param-ref 'laser1:charm:correction-status)\",  # 1st\n            \"(exec 'laser1:charm:start-correction-initial)\",\n            \"(param-ref 'laser1:charm:correction-status)\",  # 2nd\n            \"(exec 'laser1:charm:start-correction)\",\n            \"(param-ref 'laser1:charm:correction-status)\",  # 3rd\n            \"(param-ref 'laser1:charm:correction-status)\",  # 4th\n            \"(exec 'laser1:charm:start-correction)\",\n        ],\n        [\n            \"(param-ref 'laser1:charm:correction-status)\",  # 1st\n            \"0\",\n            \"> (exec 'laser1:charm:start-correction-initial)\",\n            \"()\",\n            \"> (param-ref 'laser1:charm:correction-status)\",  # 3nd\n            \"1\",\n            \"> (exec 'laser1:charm:start-correction)\",\n            \"()\",\n            \"> (param-ref 'laser1:charm:correction-status)\",  # 3rd\n            \"3\",\n            \"> (param-ref 'laser1:charm:correction-status)\",  # 4th\n            \"2\",\n            \"> (exec 'laser1:charm:start-correction)\",\n            \"()\",\n            \"> \",\n        ],\n        sep=\"\\r\\n\",\n    ) as tm:\n        tm.laser[0].correction()\n        tm.laser[0].correction()\n        _ = tm.laser[0].correction_status\n        tm.laser[0].correction()\n\n\ndef test_reboot_system():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        [\"(exec 'reboot-system)\"],\n        [\"(exec 'reboot-system)\", \"reboot process started.\", \"> \"],\n        sep=\"\\r\\n\",\n    ) as tm:\n        tm.reboot()\n\n\ndef test_laser_ontime():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        [\"(param-ref 'laser1:ontime)\"],\n        [\"(param-ref 'laser1:ontime)\", \"10000\", \"> \"],\n        sep=\"\\r\\n\",\n    ) as tm:\n        assert tm.laser[0].on_time == 10000 * u.s\n\n\ndef test_laser_charm_status():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        [\"(param-ref 'laser1:health)\"],\n        [\"(param-ref 'laser1:health)\", \"230\", \"> \"],\n        sep=\"\\r\\n\",\n    ) as tm:\n        assert tm.laser[0].charm_status == 1\n\n\ndef test_laser_temperature_control_status():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        [\"(param-ref 'laser1:health)\"],\n        [\"(param-ref 'laser1:health)\", \"230\", \"> \"],\n        sep=\"\\r\\n\",\n    ) as tm:\n        assert tm.laser[0].temperature_control_status == 1\n\n\ndef test_laser_current_control_status():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        [\"(param-ref 'laser1:health)\"],\n        [\"(param-ref 'laser1:health)\", \"230\", \"> \"],\n        sep=\"\\r\\n\",\n    ) as tm:\n        assert tm.laser[0].current_control_status == 1\n\n\ndef test_laser_production_date():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        [\"(param-ref 'laser1:production-date)\"],\n        [\"(param-ref 'laser1:production-date)\", \"2016-01-16\", \"> \"],\n        sep=\"\\r\\n\",\n    ) as tm:\n        assert tm.laser[0].production_date == \"2016-01-16\"\n\n\ndef test_set_str():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        ['(param-set! \\'blo \"blee\")'],\n        ['(param-set! \\'blo \"blee\")', \"0\", \"> \"],\n        sep=\"\\r\\n\",\n    ) as tm:\n        tm.set(\"blo\", \"blee\")\n\n\ndef test_set_list():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        [\"(param-set! 'blo '(blee blo))\"],\n        [\"(param-set! 'blo '(blee blo))\", \"0\", \"> \"],\n        sep=\"\\r\\n\",\n    ) as tm:\n        tm.set(\"blo\", [\"blee\", \"blo\"])\n\n\ndef test_display():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        [\"(param-disp 'blo)\"],\n        [\"(param-disp 'blo)\", \"bloop\", \"> \"],\n        sep=\"\\r\\n\",\n    ) as tm:\n        assert tm.display(\"blo\") == \"bloop\"\n\n\ndef test_enable():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        [\"(param-ref 'emission)\", \"(param-set! 'enable-emission #f)\"],\n        [\n            \"(param-ref 'emission)\",\n            \"#f\",\n            \"> (param-set! 'enable-emission #f)\",\n            \"0\",\n            \"> \",\n        ],\n        sep=\"\\r\\n\",\n    ) as tm:\n        assert tm.enable is False\n        tm.enable = False\n\n\ndef test_firmware():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        [\"(param-ref 'fw-ver)\"],\n        [\"(param-ref 'fw-ver)\", \"1.02.01\", \"> \"],\n        sep=\"\\r\\n\",\n    ) as tm:\n        assert tm.firmware == (1, 2, 1)\n\n\ndef test_serial_number():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        [\"(param-ref 'serial-number)\"],\n        [\"(param-ref 'serial-number)\", \"010101\", \"> \"],\n        sep=\"\\r\\n\",\n    ) as tm:\n        assert tm.serial_number == \"010101\"\n\n\ndef test_enable_error():\n    with pytest.raises(TypeError):\n        with expected_protocol(\n            ik.toptica.TopMode,\n            [\"(param-set! 'enable-emission #f)\"],\n            [\"(param-set! 'enable-emission #f)\", \">\"],\n            sep=\"\\r\\n\",\n        ) as tm:\n            tm.enable = \"False\"\n\n\ndef test_front_key():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        [\"(param-ref 'front-key-locked)\"],\n        [\"(param-ref 'front-key-locked)\", \"#f\", \"> \"],\n        sep=\"\\r\\n\",\n    ) as tm:\n        assert tm.locked is False\n\n\ndef test_interlock():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        [\"(param-ref 'interlock-open)\"],\n        [\"(param-ref 'interlock-open)\", \"#f\", \"> \"],\n        sep=\"\\r\\n\",\n    ) as tm:\n        assert tm.interlock is False\n\n\ndef test_fpga_status():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        [\"(param-ref 'system-health)\"],\n        [\"(param-ref 'system-health)\", \"0\", \"> \"],\n        sep=\"\\r\\n\",\n    ) as tm:\n        assert tm.fpga_status is True\n\n\ndef test_fpga_status_false():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        [\"(param-ref 'system-health)\"],\n        [\"(param-ref 'system-health)\", \"#f\", \"> \"],\n        sep=\"\\r\\n\",\n    ) as tm:\n        assert tm.fpga_status is False\n\n\ndef test_temperature_status():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        [\"(param-ref 'system-health)\"],\n        [\"(param-ref 'system-health)\", \"2\", \"> \"],\n        sep=\"\\r\\n\",\n    ) as tm:\n        assert tm.temperature_status is False\n\n\ndef test_current_status():\n    with expected_protocol(\n        ik.toptica.TopMode,\n        [\"(param-ref 'system-health)\"],\n        [\"(param-ref 'system-health)\", \"4\", \"> \"],\n        sep=\"\\r\\n\",\n    ) as tm:\n        assert tm.current_status is False\n"
  },
  {
    "path": "tests/test_toptica/test_toptica_utils.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for Topical util functions\n\"\"\"\n\n# IMPORTS #####################################################################\n\nimport datetime\n\nimport pytest\n\nfrom instruments.toptica import toptica_utils\n\n# TESTS #######################################################################\n\n\ndef test_convert_boolean():\n    assert toptica_utils.convert_toptica_boolean(\"bloof\") is False\n    assert toptica_utils.convert_toptica_boolean(\"boot\") is True\n    assert toptica_utils.convert_toptica_boolean(\"Error: -3\") is None\n\n\ndef test_convert_boolean_value():\n    with pytest.raises(ValueError):\n        toptica_utils.convert_toptica_boolean(\"blo\")\n\n\ndef test_convert_toptica_datetime():\n    blo = datetime.datetime.now()\n    blo_str = datetime.datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\")\n    assert toptica_utils.convert_toptica_datetime('\"\"\\r') is None\n    blo2 = toptica_utils.convert_toptica_datetime(blo_str)\n    diff = blo - blo2\n    assert diff.seconds < 60\n"
  },
  {
    "path": "tests/test_util_fns.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nModule containing tests for util_fns.py\n\"\"\"\n\n# IMPORTS ####################################################################\n\nfrom enum import Enum\n\nimport pint\nimport pytest\n\nfrom instruments.units import ureg as u\nfrom instruments.util_fns import (\n    assume_units,\n    bool_property,\n    enum_property,\n    int_property,\n    ProxyList,\n    setattr_expression,\n    string_property,\n    unitful_property,\n    unitless_property,\n)\n\nfrom tests import unit_eq\n\n# FIXTURES ###################################################################\n\n\n@pytest.fixture\ndef mock_inst(mocker):\n    \"\"\"Intialize a mock instrument to test property factories.\n\n    Include a call to each property factory to be tested. The command\n    given to the property factory must be a valid argument returned by\n    query. This argument can be asserted later. Also set up are mocker\n    spies to assert `query` and `sendcmd` have actually been called.\n\n    :return: Fake instrument class.\n    \"\"\"\n\n    class Inst:\n        \"\"\"Mock instrument class.\"\"\"\n\n        def __init__(self):\n            \"\"\"Set up the mocker spies and send command placeholder.\"\"\"\n            # spies\n            self.spy_query = mocker.spy(self, \"query\")\n            self.spy_sendcmd = mocker.spy(self, \"sendcmd\")\n\n            # variable to set with send command\n            self._sendcmd = None\n\n        def query(self, cmd):\n            \"\"\"Return the command minus the ? which is sent along.\"\"\"\n            return f\"{cmd[:-1]}\"\n\n        def sendcmd(self, cmd):\n            \"\"\"Sets the command to `self._sendcmd`.\"\"\"\n            self._sendcmd = cmd\n\n        class SomeEnum(Enum):\n            test = \"enum\"\n\n        bool_property = bool_property(\"ON\")  # return True\n\n        enum_property = enum_property(\"enum\", SomeEnum)\n\n        unitless_property = unitless_property(\"42\")\n\n        int_property = int_property(\"42\")\n\n        unitful_property_limited = unitful_property(\n            \"42\", u.m, valid_range=(1 * u.m, 100 * u.m)\n        )\n\n        unitful_property_limited_numbers = unitful_property(\n            \"42\", u.m, valid_range=(1, 100.0)\n        )\n\n        unitful_property = unitful_property(\"42\", u.m)\n\n        string_property = string_property(\"'STRING'\")\n\n    return Inst()\n\n\n# TEST CASES #################################################################\n\n# pylint: disable=protected-access,missing-docstring,redefined-outer-name\n\n\ndef test_ProxyList_basics():\n    class ProxyChild:\n        def __init__(self, parent, name):\n            self._parent = parent\n            self._name = name\n\n    parent = object()\n\n    proxy_list = ProxyList(parent, ProxyChild, range(10))\n\n    child = proxy_list[0]\n    assert child._parent is parent\n    assert child._name == 0\n\n\ndef test_ProxyList_valid_range_is_enum():\n    class ProxyChild:\n        def __init__(self, parent, name):\n            self._parent = parent\n            self._name = name\n\n    class MockEnum(Enum):\n        a = \"aa\"\n        b = \"bb\"\n\n    parent = object()\n\n    proxy_list = ProxyList(parent, ProxyChild, MockEnum)\n    assert proxy_list[\"aa\"]._name == MockEnum.a.value\n    assert proxy_list[\"b\"]._name == MockEnum.b.value\n    assert proxy_list[MockEnum.a]._name == MockEnum.a.value\n\n\ndef test_ProxyList_length():\n    class ProxyChild:\n        def __init__(self, parent, name):\n            self._parent = parent\n            self._name = name\n\n    parent = object()\n\n    proxy_list = ProxyList(parent, ProxyChild, range(10))\n\n    assert len(proxy_list) == 10\n\n\ndef test_ProxyList_iterator():\n    class ProxyChild:\n        def __init__(self, parent, name):\n            self._parent = parent\n            self._name = name\n\n    parent = object()\n\n    proxy_list = ProxyList(parent, ProxyChild, range(10))\n\n    i = 0\n    for item in proxy_list:\n        assert item._name == i\n        i = i + 1\n\n\ndef test_ProxyList_invalid_idx_enum():\n    with pytest.raises(IndexError):\n\n        class ProxyChild:\n            def __init__(self, parent, name):\n                self._parent = parent\n                self._name = name\n\n        class MockEnum(Enum):\n            a = \"aa\"\n            b = \"bb\"\n\n        parent = object()\n\n        proxy_list = ProxyList(parent, ProxyChild, MockEnum)\n\n        _ = proxy_list[\"c\"]  # Should raise IndexError\n\n\ndef test_ProxyList_invalid_idx():\n    with pytest.raises(IndexError):\n\n        class ProxyChild:\n            def __init__(self, parent, name):\n                self._parent = parent\n                self._name = name\n\n        parent = object()\n\n        proxy_list = ProxyList(parent, ProxyChild, range(5))\n\n        _ = proxy_list[10]  # Should raise IndexError\n\n\n@pytest.mark.parametrize(\n    \"input, out\",\n    (\n        (1, u.Quantity(1, \"m\")),\n        (5 * u.mm, u.Quantity(5, \"mm\")),\n        (\"7.3 km\", u.Quantity(7.3, \"km\")),\n        (\"7.5\", u.Quantity(7.5, u.m)),\n        (u.Quantity(9, \"nm\"), 9 * u.nm),\n    ),\n)\ndef test_assume_units_correct(input, out):\n    unit_eq(assume_units(input, \"m\"), out)\n\n\ndef test_setattr_expression_simple():\n    class A:\n        x = \"x\"\n        y = \"y\"\n        z = \"z\"\n\n    a = A()\n    setattr_expression(a, \"x\", \"foo\")\n    assert a.x == \"foo\"\n\n\ndef test_setattr_expression_index():\n    class A:\n        x = [\"x\", \"y\", \"z\"]\n\n    a = A()\n    setattr_expression(a, \"x[1]\", \"foo\")\n    assert a.x[1] == \"foo\"\n\n\ndef test_setattr_expression_nested():\n    class B:\n        x = \"x\"\n\n    class A:\n        b = None\n\n        def __init__(self):\n            self.b = B()\n\n    a = A()\n    setattr_expression(a, \"b.x\", \"foo\")\n    assert a.b.x == \"foo\"\n\n\ndef test_setattr_expression_both():\n    class B:\n        x = \"x\"\n\n    class A:\n        b = None\n\n        def __init__(self):\n            self.b = [B()]\n\n    a = A()\n    setattr_expression(a, \"b[0].x\", \"foo\")\n    assert a.b[0].x == \"foo\"\n\n\ndef test_bool_property_sendcmd_query(mock_inst):\n    \"\"\"Assert that bool_property calls sendcmd, query of parent class.\"\"\"\n    # fixture query should return \"On\" -> True\n    assert mock_inst.bool_property\n    mock_inst.spy_query.assert_called()\n    # setter\n    mock_inst.bool_property = True\n    assert mock_inst._sendcmd == \"ON ON\"\n    mock_inst.spy_sendcmd.assert_called()\n\n\ndef test_enum_property_sendcmd_query(mock_inst):\n    \"\"\"Assert that enum_property calls sendcmd, query of parent class.\"\"\"\n    # test getter\n    assert mock_inst.enum_property == mock_inst.SomeEnum.test\n    mock_inst.spy_query.assert_called()\n    # setter\n    mock_inst.enum_property = mock_inst.SomeEnum.test\n    assert mock_inst._sendcmd == \"enum enum\"\n    mock_inst.spy_sendcmd.assert_called()\n\n\ndef test_unitless_property_sendcmd_query(mock_inst):\n    \"\"\"Assert that unitless_property calls sendcmd, query of parent class.\"\"\"\n    # getter\n    assert mock_inst.unitless_property == 42\n    mock_inst.spy_query.assert_called()\n    # setter\n    value = 13\n    mock_inst.unitless_property = value\n    assert mock_inst._sendcmd == f\"42 {value:e}\"\n    mock_inst.spy_sendcmd.assert_called()\n\n\ndef test_int_property_sendcmd_query(mock_inst):\n    \"\"\"Assert that int_property calls sendcmd, query of parent class.\"\"\"\n    # getter\n    assert mock_inst.int_property == 42\n    mock_inst.spy_query.assert_called()\n    # setter\n    value = 13\n    mock_inst.int_property = value\n    assert mock_inst._sendcmd == f\"42 {value}\"\n    mock_inst.spy_sendcmd.assert_called()\n\n\nclass Test_unitful_property:\n    def test_unitful_property_sendcmd_query(self, mock_inst):\n        \"\"\"Assert that unitful_property calls sendcmd, query of parent class.\"\"\"\n        # getter\n        assert mock_inst.unitful_property == u.Quantity(42, u.m)\n        mock_inst.spy_query.assert_called()\n        # setter\n        value = 13\n        mock_inst.unitful_property = u.Quantity(value, u.m)\n        assert mock_inst._sendcmd == f\"42 {value:e}\"\n        mock_inst.spy_sendcmd.assert_called()\n\n    def test_unitful_property_sendcmd_query_unitless(self, mock_inst):\n        \"\"\"Assert that unitful_property calls sendcmd, query of parent class.\n        Here for a unitless input\n        \"\"\"\n        # getter\n        assert mock_inst.unitful_property == u.Quantity(42, u.m)\n        mock_inst.spy_query.assert_called()\n        # setter\n        value = 13\n        mock_inst.unitful_property = value\n        assert mock_inst._sendcmd == f\"42 {value:e}\"\n        mock_inst.spy_sendcmd.assert_called()\n\n    @pytest.mark.parametrize(\"value\", (0.1, 200, 0.1 * u.m, 200 * u.m))\n    def test_unitful_property_sendcmd_limited_unfit(self, mock_inst, value):\n        \"\"\"Assert that unitful_property calls sendcmd, query of parent class.\n        Here an input out of bounds for quantity limited property.\"\"\"\n        # setter\n        with pytest.raises(ValueError):\n            mock_inst.unitful_property_limited = value\n\n    @pytest.mark.parametrize(\"value\", (13 * u.m, 17 * u.m, 55 * u.m))\n    def test_unitful_property_sendcmd_limited_pass_un(self, mock_inst, value):\n        \"\"\"Assert that unitful_property calls sendcmd, query of parent class.\n        Here a quantity input fit for quantity limited property.\"\"\"\n        # setter\n        mock_inst.unitful_property_limited = value\n        assert mock_inst._sendcmd == f\"42 {value.magnitude:e}\"\n        mock_inst.spy_sendcmd.assert_called()\n\n    @pytest.mark.parametrize(\"value\", (13, 17.0, 55.5, 99))\n    def test_unitful_property_sendcmd_limited_pass_ul(self, mock_inst, value):\n        \"\"\"Assert that unitful_property calls sendcmd, query of parent class.\n        Here a numbers input fit for quantity limited property.\"\"\"\n        # setter\n        mock_inst.unitful_property_limited = value\n        assert mock_inst._sendcmd == f\"42 {value:e}\"\n        mock_inst.spy_sendcmd.assert_called()\n\n    @pytest.mark.parametrize(\"value\", (0.1, 200, 0.1 * u.m, 200 * u.m))\n    def test_unitful_property_sendcmd_limited_unfit2(self, mock_inst, value):\n        \"\"\"Assert that unitful_property calls sendcmd, query of parent class.\n        Here an input out of numbered bounds for limited property.\"\"\"\n        # setter\n        with pytest.raises(ValueError):\n            mock_inst.unitful_property_limited_numbers = value\n\n    @pytest.mark.parametrize(\"value\", (13 * u.m, 17 * u.m, 55 * u.m))\n    def test_unitful_property_sendcmd_limited_pass_un2(self, mock_inst, value):\n        \"\"\"Assert that unitful_property calls sendcmd, query of parent class.\n        Here a quantity input fit for numbers limited property.\"\"\"\n        # setter\n        mock_inst.unitful_property_limited_numbers = value\n        assert mock_inst._sendcmd == f\"42 {value.magnitude:e}\"\n        mock_inst.spy_sendcmd.assert_called()\n\n    @pytest.mark.parametrize(\"value\", (13, 17.0, 55.5, 99))\n    def test_unitful_property_sendcmd_limited_pass_ul2(self, mock_inst, value):\n        \"\"\"Assert that unitful_property calls sendcmd, query of parent class.\n        Here a numbers input fit for numbers limited property.\"\"\"\n        # setter\n        mock_inst.unitful_property_limited_numbers = value\n        assert mock_inst._sendcmd == f\"42 {value:e}\"\n        mock_inst.spy_sendcmd.assert_called()\n\n\ndef test_string_property_sendcmd_query(mock_inst):\n    \"\"\"Assert that string_property calls sendcmd, query of parent class.\"\"\"\n    # getter\n    assert mock_inst.string_property == \"STRING\"\n    mock_inst.spy_query.assert_called()\n    # setter\n    value = \"forty-two\"\n    mock_inst.string_property = value\n    assert mock_inst._sendcmd == f\"'STRING' \\\"{value}\\\"\"\n    mock_inst.spy_sendcmd.assert_called()\n"
  },
  {
    "path": "tests/test_yokogawa/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_yokogawa/test_yokogawa7651.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the Yokogawa 7651 power supply\n\"\"\"\n\n# IMPORTS #####################################################################\n\n\nimport pytest\n\nimport instruments as ik\nfrom instruments.units import ureg as u\nfrom tests import expected_protocol\n\n# TESTS #######################################################################\n\n# pylint: disable=protected-access\n\n# TEST CHANNEL #\n\n\ndef test_channel_init():\n    \"\"\"Initialize of channel class.\"\"\"\n    with expected_protocol(ik.yokogawa.Yokogawa7651, [], []) as yok:\n        assert yok.channel[0]._parent is yok\n        assert yok.channel[0]._name == 0\n\n\ndef test_channel_mode():\n    \"\"\"Get / Set mode of the channel.\"\"\"\n    with expected_protocol(\n        ik.yokogawa.Yokogawa7651, [\"F5;\", \"E;\", \"F1;\", \"E;\"], []  # trigger  # trigger\n    ) as yok:\n        # query\n        with pytest.raises(NotImplementedError) as exc_info:\n            print(f\"Mode is: {yok.channel[0].mode}\")\n        exc_msg = exc_info.value.args[0]\n        assert (\n            exc_msg == \"This instrument does not support querying the \"\n            \"operation mode.\"\n        )\n\n        # set first current, then voltage mode\n        yok.channel[0].mode = yok.Mode.current\n        yok.channel[0].mode = yok.Mode.voltage\n\n\ndef test_channel_invalid_mode_set():\n    \"\"\"Set mode to invalid value.\"\"\"\n    with expected_protocol(ik.yokogawa.Yokogawa7651, [], []) as yok:\n        wrong_mode = 42\n        with pytest.raises(TypeError) as exc_info:\n            yok.channel[0].mode = wrong_mode\n        exc_msg = exc_info.value.args[0]\n        assert (\n            exc_msg == \"Mode setting must be a `Yokogawa7651.Mode` \"\n            \"value, got {} instead.\".format(type(wrong_mode))\n        )\n\n\ndef test_channel_voltage():\n    \"\"\"Get / Set voltage of channel.\"\"\"\n\n    # values to set for test\n    value_unitless = 5.0\n    value_unitful = u.Quantity(500, u.mV)\n\n    with expected_protocol(\n        ik.yokogawa.Yokogawa7651,\n        [\n            \"F1;\\nE;\",  # set voltage mode\n            f\"SA{value_unitless};\",\n            \"E;\",  # trigger\n            \"F1;\\nE;\",  # set voltage mode\n            f\"SA{value_unitful.to(u.volt).magnitude};\",\n            \"E;\",  # trigger\n        ],\n        [],\n    ) as yok:\n        # query\n        with pytest.raises(NotImplementedError) as exc_info:\n            print(f\"Voltage is: {yok.channel[0].voltage}\")\n        exc_msg = exc_info.value.args[0]\n        assert (\n            exc_msg == \"This instrument does not support querying the \"\n            \"output voltage setting.\"\n        )\n\n        # set first current, then voltage mode\n        yok.channel[0].voltage = value_unitless\n        yok.channel[0].voltage = value_unitful\n\n\ndef test_channel_current():\n    \"\"\"Get / Set current of channel.\"\"\"\n\n    # values to set for test\n    value_unitless = 0.8\n    value_unitful = u.Quantity(50, u.mA)\n\n    with expected_protocol(\n        ik.yokogawa.Yokogawa7651,\n        [\n            \"F5;\\nE;\",  # set voltage mode\n            f\"SA{value_unitless};\",\n            \"E;\",  # trigger\n            \"F5;\\nE;\",  # set voltage mode\n            f\"SA{value_unitful.to(u.A).magnitude};\",\n            \"E;\",  # trigger\n        ],\n        [],\n    ) as yok:\n        # query\n        with pytest.raises(NotImplementedError) as exc_info:\n            print(f\"Current is: {yok.channel[0].current}\")\n        exc_msg = exc_info.value.args[0]\n        assert (\n            exc_msg == \"This instrument does not support querying the \"\n            \"output current setting.\"\n        )\n\n        # set first current, then current mode\n        yok.channel[0].current = value_unitless\n        yok.channel[0].current = value_unitful\n\n\ndef test_channel_output():\n    \"\"\"Get / Set output of channel.\"\"\"\n    with expected_protocol(\n        ik.yokogawa.Yokogawa7651,\n        [\"O1;\", \"E;\", \"O0;\", \"E;\"],  # turn output on  # turn output off\n        [],\n    ) as yok:\n        # query\n        with pytest.raises(NotImplementedError) as exc_info:\n            print(f\"Output is: {yok.channel[0].output}\")\n        exc_msg = exc_info.value.args[0]\n        assert (\n            exc_msg == \"This instrument does not support querying the \" \"output status.\"\n        )\n\n        # set first current, then current mode\n        yok.channel[0].output = True\n        yok.channel[0].output = False\n\n\n# CLASS PROPERTIES #\n\n\ndef test_voltage():\n    \"\"\"Get / Set voltage of instrument.\"\"\"\n\n    # values to set for test\n    value_unitless = 5.0\n    value_unitful = u.Quantity(500, u.mV)\n\n    with expected_protocol(\n        ik.yokogawa.Yokogawa7651,\n        [\n            \"F1;\\nE;\",  # set voltage mode\n            f\"SA{value_unitless};\",\n            \"E;\",  # trigger\n            \"F1;\\nE;\",  # set voltage mode\n            f\"SA{value_unitful.to(u.volt).magnitude};\",\n            \"E;\",  # trigger\n        ],\n        [],\n    ) as yok:\n        # query\n        with pytest.raises(NotImplementedError) as exc_info:\n            print(f\"Voltage is: {yok.voltage}\")\n        exc_msg = exc_info.value.args[0]\n        assert (\n            exc_msg == \"This instrument does not support querying the \"\n            \"output voltage setting.\"\n        )\n\n        # set first current, then voltage mode\n        yok.voltage = value_unitless\n        yok.voltage = value_unitful\n\n\ndef test_current():\n    \"\"\"Get / Set current of instrument.\"\"\"\n\n    # values to set for test\n    value_unitless = 0.8\n    value_unitful = u.Quantity(50, u.mA)\n\n    with expected_protocol(\n        ik.yokogawa.Yokogawa7651,\n        [\n            \"F5;\\nE;\",  # set current mode\n            f\"SA{value_unitless};\",\n            \"E;\",  # trigger\n            \"F5;\\nE;\",  # set current mode\n            f\"SA{value_unitful.to(u.A).magnitude};\",\n            \"E;\",  # trigger\n        ],\n        [],\n    ) as yok:\n        # query\n        with pytest.raises(NotImplementedError) as exc_info:\n            print(f\"current is: {yok.current}\")\n        exc_msg = exc_info.value.args[0]\n        assert (\n            exc_msg == \"This instrument does not support querying the \"\n            \"output current setting.\"\n        )\n\n        # set first current, then current mode\n        yok.current = value_unitless\n        yok.current = value_unitful\n"
  },
  {
    "path": "tests/test_yokogawa/test_yokogawa_6370.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nUnit tests for the Yokogawa 6370\n\"\"\"\n\n# IMPORTS #####################################################################\n\nimport struct\n\nfrom hypothesis import (\n    given,\n    strategies as st,\n)\nimport socket\n\nimport instruments as ik\nfrom instruments.optional_dep_finder import numpy\nfrom tests import (\n    expected_protocol,\n    iterable_eq,\n    pytest,\n)\nfrom instruments.units import ureg as u\nfrom .. import mock\n\n# TESTS #######################################################################\n\n\ndef test_channel_is_channel_class():\n    inst = ik.yokogawa.Yokogawa6370.open_test()\n    assert inst._channel_count == len(inst.Traces)\n    assert isinstance(inst.channel[\"A\"], inst.Channel) is True\n\n\ndef test_init():\n    with expected_protocol(ik.yokogawa.Yokogawa6370, [\":FORMat:DATA REAL,64\"], []) as _:\n        pass\n\n\ndef test_id():\n    with expected_protocol(\n        ik.yokogawa.Yokogawa6370,\n        [\":FORMat:DATA REAL,64\", \"*IDN?\"],\n        [\"'YOKOGAWA,AQ6370D,x,02.08'\"],\n    ) as inst:\n        assert inst.id == \"YOKOGAWA,AQ6370D,x,02.08\"\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.socket\")\ndef test_tcpip_connection_terminator(mock_socket):\n    \"\"\"Ensure terminator is `\\r\\n` if connected via TCP-IP (issue #386).\"\"\"\n    mock_socket.socket.return_value.__class__ = socket.socket\n    inst = ik.yokogawa.Yokogawa6370.open_tcpip(\"127.0.0.1\", port=1001)\n    assert inst.terminator == \"\\r\\n\"\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.socket\")\ndef test_tcpip_authentication(mock_socket, mocker):\n    mock_socket.socket.return_value.__class__ = socket.socket\n\n    call_order = []\n\n    mock_query = mocker.patch(\"instruments.yokogawa.Yokogawa6370.query\")\n    mock_sendcmd = mocker.patch(\"instruments.yokogawa.Yokogawa6370.sendcmd\")\n\n    def query_return(*args, **kwargs):\n        \"\"\"Return results and add to `call_order`.\"\"\"\n        call_order.append(mock_query)\n        return \"ready\"\n\n    mock_query.side_effect = query_return\n    mock_sendcmd.side_effect = lambda *a, **kw: call_order.append(mock_sendcmd)\n\n    username = \"user\"\n    password = \"my_password\"\n\n    _ = ik.yokogawa.Yokogawa6370.open_tcpip(\n        \"127.0.0.1\", 1234, auth=(username, password)\n    )\n\n    calls = [\n        mocker.call(f'OPEN \"{username}\"'),\n        mocker.call(f'\"{password}\"'),\n    ]\n    mock_query.assert_has_calls(calls, any_order=False)\n\n    assert call_order == [mock_query, mock_query, mock_sendcmd]\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.socket\")\ndef test_tcpip_authentication_anonymous(mock_socket, mocker):\n    \"\"\"Authenticate as anonymous user (any password accepted).\"\"\"\n    mock_socket.socket.return_value.__class__ = socket.socket\n\n    call_order = []\n\n    mock_query = mocker.patch(\"instruments.yokogawa.Yokogawa6370.query\")\n    mock_sendcmd = mocker.patch(\"instruments.yokogawa.Yokogawa6370.sendcmd\")\n\n    def query_return(*args, **kwargs):\n        \"\"\"Return results and add to `call_order`.\"\"\"\n        call_order.append(mock_query)\n        return \"ready\"\n\n    mock_query.side_effect = query_return\n    mock_sendcmd.side_effect = lambda *a, **kw: call_order.append(mock_sendcmd)\n\n    username = \"anonymous\"\n    password = \"my_password\"\n\n    _ = ik.yokogawa.Yokogawa6370.open_tcpip(\n        \"127.0.0.1\", 1234, auth=(username, password)\n    )\n\n    calls = [\n        mocker.call(f'OPEN \"{username}\"'),\n        mocker.call(f'\"{password}\"'),  # this is the password since any is accepted\n    ]\n    mock_query.assert_has_calls(calls, any_order=False)\n\n    assert call_order == [mock_query, mock_query, mock_sendcmd]\n\n\n@mock.patch(\"instruments.abstract_instruments.instrument.socket\")\ndef test_tcpip_authentication_error(mock_socket, mocker):\n    mock_socket.socket.return_value.__class__ = socket.socket\n\n    mock_query = mocker.patch(\"instruments.yokogawa.Yokogawa6370.query\")\n\n    mock_query.side_effect = [\"asdf\", \"asdf\", \"error\"]  # three calls total\n\n    username = \"user\"\n    password = \"my_password\"\n\n    with pytest.raises(ConnectionError):\n        _ = ik.yokogawa.Yokogawa6370.open_tcpip(\n            \"127.0.0.1\", 1234, auth=(username, password)\n        )\n\n\ndef test_status():\n    with expected_protocol(\n        ik.yokogawa.Yokogawa6370, [\":FORMat:DATA REAL,64\", \"*STB?\"], [\"7\"]\n    ) as inst:\n        assert inst.status == 7\n\n\ndef test_operation_event():\n    with expected_protocol(\n        ik.yokogawa.Yokogawa6370,\n        [\":FORMat:DATA REAL,64\", \":status:operation:event?\"],\n        [\"7\"],\n    ) as inst:\n        assert inst.operation_event == 7\n\n\n@pytest.mark.parametrize(\"axis\", (\"X\", \"Y\"))\n@given(\n    values=st.lists(st.floats(allow_infinity=False, allow_nan=False), min_size=1),\n    channel=st.sampled_from(ik.yokogawa.Yokogawa6370.Traces),\n)\ndef test_channel_private_data_wo_limits(values, channel, axis):\n    values_packed = b\"\".join(struct.pack(\"<d\", value) for value in values)\n    values_len = str(len(values_packed)).encode()\n    values_len_of_len = str(len(values_len)).encode()\n    with expected_protocol(\n        ik.yokogawa.Yokogawa6370,\n        [\n            \":FORMat:DATA REAL,64\",\n            f\":TRAC:{axis}? {channel.value}\",\n        ],\n        [b\"#\" + values_len_of_len + values_len + values_packed],\n    ) as inst:\n        values = tuple(values)\n        if numpy:\n            values = numpy.array(values, dtype=\"<d\")\n        iterable_eq(inst.channel[channel]._data(axis), values)\n\n\n@pytest.mark.parametrize(\"axis\", (\"X\", \"Y\"))\n@given(\n    values=st.lists(st.floats(allow_infinity=False, allow_nan=False), min_size=1),\n    channel=st.sampled_from(ik.yokogawa.Yokogawa6370.Traces),\n    start=st.integers(0, 25000),\n    length=st.integers(0, 25000),\n)\ndef test_channel_private_data_with_limits(values, channel, axis, start, length):\n    values_packed = b\"\".join(struct.pack(\"<d\", value) for value in values)\n    values_len = str(len(values_packed)).encode()\n    values_len_of_len = str(len(values_len)).encode()\n    with expected_protocol(\n        ik.yokogawa.Yokogawa6370,\n        [\n            \":FORMat:DATA REAL,64\",\n            f\":TRAC:{axis}? {channel.value},{start+1},{start+1+length}\",\n        ],\n        [b\"#\" + values_len_of_len + values_len + values_packed],\n    ) as inst:\n        values = tuple(values)\n        if numpy:\n            values = numpy.array(values, dtype=\"<d\")\n        iterable_eq(inst.channel[channel]._data(axis, (start, start + length)), values)\n\n\n@pytest.mark.parametrize(\"limits\", ([5], \"abc\", (7,), 3))\ndef test_channel_private_data_limit_error(limits):\n    with expected_protocol(\n        ik.yokogawa.Yokogawa6370, [\":FORMat:DATA REAL,64\"], []\n    ) as inst:\n        with pytest.raises(ValueError):\n            inst.channel[\"A\"]._data(\"X\", limits)\n\n\n@given(\n    values=st.lists(st.floats(allow_infinity=False, allow_nan=False), min_size=1),\n    channel=st.sampled_from(ik.yokogawa.Yokogawa6370.Traces),\n)\ndef test_channel_data(values, channel):\n    values_packed = b\"\".join(struct.pack(\"<d\", value) for value in values)\n    values_len = str(len(values_packed)).encode()\n    values_len_of_len = str(len(values_len)).encode()\n    with expected_protocol(\n        ik.yokogawa.Yokogawa6370,\n        [\n            \":FORMat:DATA REAL,64\",\n            f\":TRAC:Y? {channel.value}\",\n        ],\n        [b\"#\" + values_len_of_len + values_len + values_packed],\n    ) as inst:\n        values = tuple(values)\n        if numpy:\n            values = numpy.array(values, dtype=\"<d\")\n        iterable_eq(inst.channel[channel].data(), values)\n\n\n@given(\n    values=st.lists(st.floats(allow_infinity=False, allow_nan=False), min_size=1),\n    channel=st.sampled_from(ik.yokogawa.Yokogawa6370.Traces),\n)\ndef test_channel_wavelength(values, channel):\n    values_packed = b\"\".join(struct.pack(\"<d\", value) for value in values)\n    values_len = str(len(values_packed)).encode()\n    values_len_of_len = str(len(values_len)).encode()\n    with expected_protocol(\n        ik.yokogawa.Yokogawa6370,\n        [\n            \":FORMat:DATA REAL,64\",\n            f\":TRAC:X? {channel.value},1,501\",\n        ],\n        [b\"#\" + values_len_of_len + values_len + values_packed],\n    ) as inst:\n        values = tuple(values)\n        if numpy:\n            values = numpy.array(values, dtype=\"<d\")\n        iterable_eq(inst.channel[channel].wavelength((0, 500)), values)\n\n\n@given(value=st.floats(min_value=600e-9, max_value=1700e-9))\ndef test_start_wavelength(value):\n    with expected_protocol(\n        ik.yokogawa.Yokogawa6370,\n        [\n            \":FORMat:DATA REAL,64\",\n            \":SENS:WAV:STAR?\",\n            f\":SENS:WAV:STAR {value:e}\",\n        ],\n        [\"6.000000e-06\"],\n    ) as inst:\n        assert inst.start_wl == 6e-6 * u.meter\n        inst.start_wl = value * u.meter\n\n\n@given(value=st.floats(min_value=600e-9, max_value=1700e-9))\ndef test_end_wavelength(value):\n    with expected_protocol(\n        ik.yokogawa.Yokogawa6370,\n        [\n            \":FORMat:DATA REAL,64\",\n            \":SENS:WAV:STOP?\",\n            f\":SENS:WAV:STOP {value:e}\",\n        ],\n        [\"6.000000e-06\"],\n    ) as inst:\n        assert inst.stop_wl == 6e-6 * u.meter\n        inst.stop_wl = value * u.meter\n\n\ndef test_bandwidth():\n    with expected_protocol(\n        ik.yokogawa.Yokogawa6370,\n        [\":FORMat:DATA REAL,64\", \":SENS:BAND:RES?\", \":SENS:BAND:RES 1.000000e-06\"],\n        [\"6.000000e-06\"],\n    ) as inst:\n        assert inst.bandwidth == 6e-6 * u.meter\n        inst.bandwidth = 1e-6 * u.meter\n\n\ndef test_span():\n    with expected_protocol(\n        ik.yokogawa.Yokogawa6370,\n        [\":FORMat:DATA REAL,64\", \":SENS:WAV:SPAN?\", \":SENS:WAV:SPAN 1.000000e-06\"],\n        [\"6.000000e-06\"],\n    ) as inst:\n        assert inst.span == 6e-6 * u.meter\n        inst.span = 1e-6 * u.meter\n\n\ndef test_center_wl():\n    with expected_protocol(\n        ik.yokogawa.Yokogawa6370,\n        [\":FORMat:DATA REAL,64\", \":SENS:WAV:CENT?\", \":SENS:WAV:CENT 1.000000e-06\"],\n        [\"6.000000e-06\"],\n    ) as inst:\n        assert inst.center_wl == 6e-6 * u.meter\n        inst.center_wl = 1e-6 * u.meter\n\n\ndef test_points():\n    with expected_protocol(\n        ik.yokogawa.Yokogawa6370,\n        [\":FORMat:DATA REAL,64\", \":SENS:SWE:POIN?\", \":SENS:SWE:POIN 1.000000e+00\"],\n        [\"6\"],\n    ) as inst:\n        assert inst.points == 6\n        inst.points = 1\n\n\ndef test_sweep_mode():\n    with expected_protocol(\n        ik.yokogawa.Yokogawa6370,\n        [\n            \":FORMat:DATA REAL,64\",\n            \":INIT:SMOD 1\",\n            \":INIT:SMOD 2\",\n            \":INIT:SMOD 3\",\n            \":INIT:SMOD?\",\n            \":INIT:SMOD?\",\n            \":INIT:SMOD?\",\n        ],\n        [\n            \"1\",\n            \"2\",\n            \"3\",\n        ],\n    ) as inst:\n        for mode in inst.SweepModes:\n            inst.sweep_mode = mode\n        for mode in inst.SweepModes:\n            assert inst.sweep_mode == mode\n\n\ndef test_active_trace():\n    with expected_protocol(\n        ik.yokogawa.Yokogawa6370,\n        [\n            \":FORMat:DATA REAL,64\",\n            \":TRAC:ACTIVE TRA\",\n            \":TRAC:ACTIVE TRD\",\n            \":TRAC:ACTIVE?\",\n            \":TRAC:ACTIVE?\",\n        ],\n        [\n            \"TRB\",\n            \"TRG\",\n        ],\n    ) as inst:\n        inst.active_trace = inst.Traces.A\n        inst.active_trace = inst.Traces.D\n        assert inst.active_trace == inst.Traces.B\n        assert inst.active_trace == inst.Traces.G\n\n\n# METHODS #\n\n\n@given(values=st.lists(st.decimals(allow_infinity=False, allow_nan=False), min_size=1))\ndef test_data_active_trace(values):\n    \"\"\"Get data from active trace - method.\"\"\"\n    values_packed = b\"\".join(struct.pack(\"<d\", value) for value in values)\n    values_len = str(len(values_packed)).encode()\n    values_len_of_len = str(len(values_len)).encode()\n    channel = \"TRA\"  # active trace\n    with expected_protocol(\n        ik.yokogawa.Yokogawa6370,\n        [\n            \":FORMat:DATA REAL,64\",\n            f\":TRAC:Y? {channel}\",\n            \":TRAC:ACTIVE?\",\n            f\":TRAC:Y? {channel}\",\n        ],\n        [\n            b\"#\" + values_len_of_len + values_len + values_packed,\n            channel,\n            b\"#\" + values_len_of_len + values_len + values_packed,\n        ],\n    ) as inst:\n        # data by channel\n        data_call_by_trace = inst.channel[channel].data()\n        # call active trace data\n        data_active_trace = inst.data()\n        iterable_eq(data_call_by_trace, data_active_trace)\n\n\n@given(values=st.lists(st.decimals(allow_infinity=False, allow_nan=False), min_size=1))\ndef test_wavelength_active_trace(values):\n    \"\"\"Get wavelength from active trace - method.\"\"\"\n    values_packed = b\"\".join(struct.pack(\"<d\", value) for value in values)\n    values_len = str(len(values_packed)).encode()\n    values_len_of_len = str(len(values_len)).encode()\n    channel = \"TRA\"  # active trace\n    with expected_protocol(\n        ik.yokogawa.Yokogawa6370,\n        [\n            \":FORMat:DATA REAL,64\",\n            f\":TRAC:X? {channel}\",\n            \":TRAC:ACTIVE?\",\n            f\":TRAC:X? {channel}\",\n        ],\n        [\n            b\"#\" + values_len_of_len + values_len + values_packed,\n            channel,\n            b\"#\" + values_len_of_len + values_len + values_packed,\n        ],\n    ) as inst:\n        # data by channel\n        data_call_by_trace = inst.channel[channel].wavelength()\n        # call active trace data\n        data_active_trace = inst.wavelength()\n        iterable_eq(data_call_by_trace, data_active_trace)\n\n\ndef test_start_sweep():\n    with expected_protocol(\n        ik.yokogawa.Yokogawa6370,\n        [\n            \":FORMat:DATA REAL,64\",\n            \"*CLS;:init\",\n        ],\n        [],\n    ) as inst:\n        inst.start_sweep()\n\n\ndef test_analysis():\n    with expected_protocol(\n        ik.yokogawa.Yokogawa6370,\n        [\n            \":FORMat:DATA REAL,64\",\n            \":CALC:DATA?\",\n        ],\n        [\"1,2,3,7.3,3.12314,.2345\"],\n    ) as inst:\n        assert inst.analysis() == [1, 2, 3, 7.3, 3.12314, 0.2345]\n\n\ndef test_abort():\n    with expected_protocol(\n        ik.yokogawa.Yokogawa6370,\n        [\n            \":FORMat:DATA REAL,64\",\n            \":ABORT\",\n        ],\n        [],\n    ) as inst:\n        inst.abort()\n\n\ndef test_clear():\n    with expected_protocol(\n        ik.yokogawa.Yokogawa6370,\n        [\n            \":FORMat:DATA REAL,64\",\n            \"*CLS\",\n        ],\n        [],\n    ) as inst:\n        inst.clear()\n"
  },
  {
    "path": "tox.ini",
    "content": "[tox]\nenvlist = py{38,39,310,311,312},py{38,39,310,311,312}-numpy,pylint\nisolated_build = true\n\n[testenv]\nextras = dev\ndeps =\n    numpy: numpy\ncommands = pytest -n auto --cov --cov-report=xml {toxinidir}/tests/ {posargs:}\n\n[testenv:precommit]\nbasepython = python3\ndeps =\n    pre-commit\ncommands =\n    pre-commit run --all --show-diff-on-failure\n"
  }
]