diff --git a/.gitignore b/.gitignore
index e5913b7..7ec6d1b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@ _past*
*.pyc
.DS_Store
docs/_build/
+docs/_snippets/
docs/drivers
.idea
_test*
diff --git a/CHANGES b/CHANGES
index 1d1ddd4..1ed61e8 100644
--- a/CHANGES
+++ b/CHANGES
@@ -2,8 +2,47 @@ Lantz Changelog
===============
+0.4 (unreleased)
+----------------
-Version 0.2 (2013-01-01)
---------------------------
+- Nothing changed yet.
+
+
+0.3 (2015-02-05)
+----------------
+
+- Introduced MessageBasedDriver, a class to rule them all.
+- Moved drivers
+- Moved old base classes (serial, usb, tcp, visa) to legacy package.
+- Moved instrument drivers based on legacy classes to legacy package.
+- Migrated instrument classes to MessageBasedDriver.
+- Improved logging to avoid duplication and useless info.
+- Frontend and Backend for application development.
+- Implemented Chart block.
+- Implemented Scan block.
+- Implemented FeatScan block.
+- Implemented Loop block.
+- Implemented Layout block.
+- Online documentation now shows the drivers classes.
+- Stub classes to enable documentation building without required packages.
+- Moved simulator from script to console-script.
+- Changed sphinx theme to read the docs.
+- Introduced `start_gui` helper function to start an app with a ui file
+ and an instrument or instruments.
+- Introduced `start_test_app` helper function to start the test panel.
+- Introduced `start_gui_app` takes a backend and fronted and shows the app.
+- Moved get-lantz script to a gist.
+- Removed lantz-shell, equivalent functionality was contributed to PyVISA.
+- Support simultaneous values and units in Feat.
+ (Issue #25)
+- Use Qt import scheme from IPython.
+- Helper functions for multiple initialization: initialize_many and finalize_many.
+- GUI support for initialize_many and finalize_many.
+- New instrument drivers.
+
+
+
+0.2 (2013-01-01)
+----------------
- first public release.
diff --git a/MANIFEST.in b/MANIFEST.in
index cdb4481..fae4715 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,10 +1,16 @@
include LICENSE AUTHORS README CHANGES
-recursive-include tests *
+recursive-include lantz/testsuite *
recursive-include examples *
recursive-include docs *
recursive-include lantz/drivers *
recursive-include lantz/simulators *
recursive-include lantz/ui *
+
+include *.txt
+include .travis.yml
+recursive-include lantz *.c
+recursive-include lantz *.h
+
global-exclude *.pyc *.pyo .DS_Store
prune docs/_build
prune docs/_themes/.git
diff --git a/docs/_static/blocks/chart.png b/docs/_static/blocks/chart.png
new file mode 100644
index 0000000..97e8913
Binary files /dev/null and b/docs/_static/blocks/chart.png differ
diff --git a/docs/_static/blocks/loop.png b/docs/_static/blocks/loop.png
new file mode 100644
index 0000000..0409cb7
Binary files /dev/null and b/docs/_static/blocks/loop.png differ
diff --git a/docs/_static/blocks/scan.png b/docs/_static/blocks/scan.png
new file mode 100644
index 0000000..d402e6a
Binary files /dev/null and b/docs/_static/blocks/scan.png differ
diff --git a/docs/_static/blocks/scanfeat.png b/docs/_static/blocks/scanfeat.png
new file mode 100644
index 0000000..54e6cc3
Binary files /dev/null and b/docs/_static/blocks/scanfeat.png differ
diff --git a/docs/_static/guides/scanfrequency.png b/docs/_static/guides/scanfrequency.png
new file mode 100644
index 0000000..328a81f
Binary files /dev/null and b/docs/_static/guides/scanfrequency.png differ
diff --git a/docs/_static/tutorial/scanfrequency.ui b/docs/_static/guides/scanfrequency.ui
similarity index 100%
rename from docs/_static/tutorial/scanfrequency.ui
rename to docs/_static/guides/scanfrequency.ui
diff --git a/docs/_static/tutorial/fungen.ui b/docs/_static/tutorial/fungen.ui
new file mode 100644
index 0000000..43179d5
--- /dev/null
+++ b/docs/_static/tutorial/fungen.ui
@@ -0,0 +1,90 @@
+
+
+ MainWindow
+
+
+
+ 0
+ 0
+ 328
+ 231
+
+
+
+ Function Generator
+
+
+
+ -
+
+
+ Configuration
+
+
+
-
+
+
+ QFormLayout::FieldsStayAtSizeHint
+
+
-
+
+
+ Amplitude
+
+
+
+ -
+
+
+ -
+
+
+ Offset
+
+
+
+ -
+
+
+ -
+
+
+ Waveform
+
+
+
+ -
+
+
+ -
+
+
+ Frequency
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/_static/tutorial/gui-app.png b/docs/_static/tutorial/gui-app.png
index 328a81f..f0d1b80 100644
Binary files a/docs/_static/tutorial/gui-app.png and b/docs/_static/tutorial/gui-app.png differ
diff --git a/docs/_static/tutorial/rich-gui-app.png b/docs/_static/tutorial/rich-gui-app.png
new file mode 100644
index 0000000..d8ff7e6
Binary files /dev/null and b/docs/_static/tutorial/rich-gui-app.png differ
diff --git a/docs/about.rst b/docs/about.rst
index 9c5fd73..5688f55 100644
--- a/docs/about.rst
+++ b/docs/about.rst
@@ -14,7 +14,7 @@ Lantz explains itself by contextually displaying documentation. Core functionali
Lantz works well with Linux, Mac and Windows. It is written in Python and Qt4 for the user interface.
-Lantz profits from Python's batteries-included philosophy and it's extensive library in many different fields from text parsing to database communication.
+Lantz profits from Python's batteries-included philosophy and its extensive library in many different fields from text parsing to database communication.
Lantz builds on the giant shoulders. By using state-of-the art libraries, it delivers tested robustness and performance.
diff --git a/docs/agreement.rst b/docs/agreement.rst
index efca127..cf3241b 100644
--- a/docs/agreement.rst
+++ b/docs/agreement.rst
@@ -1,5 +1,9 @@
.. _agreement:
+
+Agreeement
+----------
+
.. raw:: html
diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst
index e1f343b..fe2982d 100644
--- a/docs/api/interfaces.rst
+++ b/docs/api/interfaces.rst
@@ -2,18 +2,10 @@
Interfacing to instruments
==========================
-.. automodule:: lantz.serial
- :members:
- :show-inheritance:
-
-.. automodule:: lantz.network
+.. automodule:: lantz.messagebased
:members:
:show-inheritance:
.. automodule:: lantz.foreign
:members:
:show-inheritance:
-
-.. automodule:: lantz.visa
- :members:
- :show-inheritance:
diff --git a/docs/api/support.rst b/docs/api/support.rst
index e65be85..7671cd0 100644
--- a/docs/api/support.rst
+++ b/docs/api/support.rst
@@ -7,6 +7,5 @@ Support
stats
processors
- visalib
stringparser
diff --git a/docs/api/visalib.rst b/docs/api/visalib.rst
deleted file mode 100644
index 979659d..0000000
--- a/docs/api/visalib.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-.. automodule:: lantz.visalib
- :members:
diff --git a/docs/conf.py b/docs/conf.py
index 6d2e638..19f853c 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -15,6 +15,20 @@
import pkg_resources
import datetime
+os.environ['QT_API'] = 'mock'
+os.environ['LANTZ_BUILDING_DOCS'] = 'True'
+
+from unittest.mock import MagicMock
+
+class Mock(MagicMock):
+
+ @classmethod
+ def __getattr__(cls, name):
+ return Mock()
+
+MOCK_MODULES = ['numpy', ]
+sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES)
+
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
@@ -29,10 +43,12 @@
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.viewcode', 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx',
+ 'sphinxcontrib.images',
]#'autodriver']
# 'sphinxcontrib.spelling']
-intersphinx_mapping = {'python': ('http://docs.python.org/3.2', None)}
+intersphinx_mapping = {'python': ('http://docs.python.org/3.4', None),
+ 'pyvisa': ('http://pyvisa.readthedocs.org/en/latest', None)}
autodoc_member_order = 'groupwise'
autoclass_content = 'both'
@@ -57,7 +73,11 @@
# |version| and |release|, also used in various other places throughout the
# built documents.
-version = pkg_resources.get_distribution(project).version
+try:
+ version = pkg_resources.get_distribution(project).version
+except:
+ version = 'unknown'
+
release = version
this_year = datetime.date.today().year
copyright = '%s, %s' % (this_year, author)
@@ -96,24 +116,29 @@
# A list of ignored prefixes for module index sorting.
modindex_common_prefix = ['lantz.']
-
-import driversdoc
-driversdoc.main()
-
# -- Options for HTML output ---------------------------------------------------
-sys.path.append(os.path.abspath('_themes'))
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
-html_theme = 'lantz'
+html_theme = 'sphinx_rtd_theme'
+
+on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
+
+if not on_rtd: # only import and set the theme if we're building docs locally
+ try:
+ import sphinx_rtd_theme
+ except ImportError:
+ print('\n\nTheme not found. Please install Sphinx Read The Docs Themes using:\n\n'
+ ' pip install sphinx_rtd_theme\n')
+ sys.exit(1)
+ html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
+
+import driversdoc
+driversdoc.main()
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
-#html_theme_options = {}
-html_theme_options = {'collapsiblesidebar': True}
-
-# Add any paths that contain custom themes here, relative to this directory.
-html_theme_path = ['_themes']
+html_theme_options = {}
# The name for this set of Sphinx documents. If None, it defaults to
# " v documentation".
diff --git a/docs/contributing-core.rst b/docs/contributing-core.rst
index 94df427..0c8f823 100644
--- a/docs/contributing-core.rst
+++ b/docs/contributing-core.rst
@@ -16,7 +16,7 @@ There are always at least two branches:
* master: appropriate for users. It must always be in a working state.
* develop: appropriate for developers. Might not be in a working state.
-The master branch only accepts atomic, small commits. Larger changes that might break the master branch should happen in the develop branch. The develop branch will be merged into the master after deep testing. If you want to refactor major parts of the code or try new ideas, create a dedicated branch. This branch will merged into develop once tested.
+The master branch only accepts atomic, small commits. Larger changes that might break the master branch should happen in the develop branch . The develop branch will be merged into the master after deep testing. If you want to refactor major parts of the code or try new ideas, create a dedicated branch. This will merged into develop once tested.
The easiest way to start hacking Lantz codebase is using a virtual environment
and cloning an editable package.
@@ -24,25 +24,21 @@ and cloning an editable package.
Assuming that you have installed all the requirements described in
:ref:`tutorial-installing`, in OSX/Linux::
- $ pip-3.2 install virtualenv
+ $ pip-3.4 install virtualenv
$ cd ~
- $ virtualenv -p python3.2 --system-site-packages lantzenv
+ $ virtualenv -p python3.4 --system-site-packages lantzenv
$ cd lantzenv
$ source bin/activate
and in Windows::
- C:\Python3.2\Scripts\pip install virtualenv
+ C:\Python34\Scripts\pip install virtualenv
cd %USERPROFILE%\Desktop
- C:\Python32\Scripts\virtualenv --system-site-packages lantzenv
+ C:\Python34\Scripts\virtualenv --system-site-packages lantzenv
cd lantzenv\Scripts
activate
-and then install an editable package::
-
- $ pip install -e git+gitolite@glugcen.dc.uba.ar:lantz.git#egg=lantz
-
-or from `Lantz at Github`_::
+and then install an editable package from `Lantz at Github`_::
$ pip install -e git+git://github.com/hgrecco/lantz.git#egg=lantz
@@ -133,7 +129,7 @@ module, a small description and the copyright message. For example:
Submitting your changes
-----------------------
-Changes must be submitted for merging as patches or pull requests.
+Changes must be submitted for merging as pull requests.
Before doing so, please check that:
* The new code is functional.
@@ -143,14 +139,7 @@ Before doing so, please check that:
* Any new file contains an appropriate header.
* You commit to the head of the appropriate branch (usually develop).
-Commits must include a one-line description of the intended change followed, if necessary, by an empty line and detailed description. You can send your patch by e-mail to `lantz.contributor@gmail.com`::
-
- $ git format-patch origin/develop..develop
- 0001-Changed-Driver-class-to-enable-inheritance-of-Action.patch
- 0002-Added-RECV_CHUNK-to-TextualMixin.patch
-
-
-or send a pull request.
+Commits must include a one-line description of the intended change followed, if necessary, by an empty line and detailed description..
Copyright
@@ -183,3 +172,4 @@ Finally, we have a small Zen
.. _git: http://git-scm.com/
.. _reStructuredText: http://docutils.sf.net/rst.html
.. _Sphinx: http://sphinx.pocoo.org/
+.. _`Lantz at Github`: https://github.com/hgrecco/lantz/
diff --git a/docs/contributing-drivers.rst b/docs/contributing-drivers.rst
index 761eb79..82d0bc5 100644
--- a/docs/contributing-drivers.rst
+++ b/docs/contributing-drivers.rst
@@ -5,14 +5,15 @@ Contributing Drivers
====================
The most straightforward way to contribute to Lantz is by submitting instrument
-drivers. You do not need to clone or understand the whole structure of `Lantz`
-for this purpose and you can do it directly from you own projects.
+drivers as you do not need to understand the whole structure of `Lantz`.
-If you have installed Lantz using the tutorial (:ref:`installing`), you
+There are two ways:
- $ lantz-contribute
+- Clone the repo in github and do a pull request
+- Upload your code to a gist_,
Please be sure that you have documented the code properly before submission. You
can also use this tool if you want some feedback about your code.
+.. _gist: https://gist.github.com/
diff --git a/docs/driversdoc.py b/docs/driversdoc.py
index f0d60bd..f6d2367 100644
--- a/docs/driversdoc.py
+++ b/docs/driversdoc.py
@@ -306,6 +306,7 @@ def list_drivers(key, module):
def list_packages(root_package):
packages = {}
+ failed_packages = []
path, prefix = root_package.__path__, root_package.__name__ + "."
for importer, modname, ispkg in pkgutil.iter_modules(path, prefix):
if not ispkg:
@@ -315,12 +316,13 @@ def list_packages(root_package):
packages[package.__name__] = package
print('+ Imported {}'.format(modname))
except Exception as e:
+ failed_packages.append(modname)
print('- Cannot import {}: {}'.format(modname, e))
- return packages
+ return packages, failed_packages
def main():
print('\nGenerating documentation for drivers ...')
- packages = list_packages(drivers)
+ packages, failed_packages = list_packages(drivers)
class opts:
pass
opts.dryrun = False
@@ -341,6 +343,8 @@ class opts:
fp.write(' *\n')
last = ''
for key in sorted(packages.keys()):
+ if key == 'legacy':
+ continue
try:
company = company_parser(packages[key].__doc__).strip()
except Exception as e:
@@ -355,5 +359,11 @@ class opts:
module = packages[key]
fp.write(list_drivers(key, module))
+ if failed_packages:
+ fp.write(format_heading(1, 'Failed to generate the docs for the following subpackages'))
+ fp.write('\n')
+ for key in sorted(failed_packages):
+ fp.write('- {}\n'.format(key))
+
if __name__ == '__main__':
main()
diff --git a/docs/extract_snippets.py b/docs/extract_snippets.py
new file mode 100644
index 0000000..245ed1a
--- /dev/null
+++ b/docs/extract_snippets.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+
+import os
+
+from docutils.core import publish_doctree
+from docutils.nodes import literal_block
+
+
+def extract_snippets(filename):
+ with open(filename, 'r') as fp:
+ doctree = publish_doctree(fp.read())
+
+ for part in doctree.traverse(literal_block):
+ if not part.attributes.get('classes', None) == ['code', 'python']:
+ continue
+ yield part.rawsource
+
+
+def main(doc_folder=None):
+ if doc_folder is None:
+ doc_folder = os.getcwd()
+
+ join = os.path.join
+
+ snippet_folder = join(doc_folder, '_snippets')
+
+ print('Scanning %s' % doc_folder)
+ print('Extracting snippets to %s' % snippet_folder)
+
+ for root, dirs, files in os.walk(doc_folder, topdown=True):
+ dirs[:] = [d for d in dirs
+ if d[0] != '_' and d not in ('drivers', 'api')]
+
+ dst_folder = join(snippet_folder, root[(len(doc_folder)+1):])
+
+ try:
+ os.mkdir(dst_folder)
+ except:
+ pass
+
+ for name in files:
+ if not name.endswith('.rst'):
+ continue
+
+ for ndx, snippet in enumerate(extract_snippets(join(root, name))):
+ with open(join(dst_folder, '%s_%02d.py' % (name, ndx)), 'w', encoding='utf-8') as fo:
+ fo.write(snippet)
+
+if __name__ == '__main__':
+ main()
diff --git a/docs/faq.rst b/docs/faq.rst
index 1fe1000..4139e05 100644
--- a/docs/faq.rst
+++ b/docs/faq.rst
@@ -62,7 +62,7 @@ Slow operations such as numerical calculations are done using libraries such as
How do I start?
---------------
-The :ref:`tutorial` is a good place.
+The :ref:`tutorials` is a good place.
I want to help. What can I do?
diff --git a/docs/guides/blocks.rst b/docs/guides/blocks.rst
new file mode 100644
index 0000000..3d628bb
--- /dev/null
+++ b/docs/guides/blocks.rst
@@ -0,0 +1,38 @@
+.. _blocks:
+
+
+==================
+Knowing the Blocks
+==================
+
+
+Loop
+----
+
+.. image:: ../_static/blocks/loop.png
+
+TBD
+
+
+Scan
+----
+
+.. image:: ../_static/blocks/scan.png
+
+TBD
+
+
+FeatScan
+--------
+
+.. image:: ../_static/blocks/scanfeat.png
+
+TBD
+
+
+Chart
+-----
+
+.. image:: ../_static/blocks/chart.png
+
+TBD
diff --git a/docs/guides/build-backend.rst b/docs/guides/build-backend.rst
new file mode 100644
index 0000000..a0c16e5
--- /dev/null
+++ b/docs/guides/build-backend.rst
@@ -0,0 +1,9 @@
+.. _build-backend:
+
+
+==================
+Building a Backend
+==================
+
+
+TBD
diff --git a/docs/guides/build-frontend.rst b/docs/guides/build-frontend.rst
new file mode 100644
index 0000000..69ea111
--- /dev/null
+++ b/docs/guides/build-frontend.rst
@@ -0,0 +1,9 @@
+.. _build-frontend:
+
+
+===================
+Building a Frontend
+===================
+
+
+TBD
diff --git a/docs/guides/defaults.rst b/docs/guides/defaults.rst
new file mode 100644
index 0000000..d283ac8
--- /dev/null
+++ b/docs/guides/defaults.rst
@@ -0,0 +1,93 @@
+.. _defaults_dictionary:
+
+=======================
+The DEFAULTS dictionary
+=======================
+
+
+Different instruments require different communication settings such as baud rate, end of a message characters, etc. The :attribute::`DEFAULTS` dictionary provides a way to customize resource initialization at the :class::MessageBasedDriver level, avoiding tedious customization in all instances.
+
+It is easier to see it with an example. Let's start with simple case::
+
+ class MyDriver(MessageBasedDriver):
+
+ DEFAULTS = {
+ 'COMMON': {'write_termination': '\n'}
+ }
+
+The 'COMMON' key is used to tells MessageBasedDriver that 'write_termination' should be set to '\n' for all type of interface types (USB, GPIB, etc).
+
+But in certain cases, different resource types might require different settings::
+
+ DEFAULTS = {
+ 'ASRL': {'write_termination': '\n',
+ 'read_termination': '\r',
+ 'baud_rate': 9600},
+
+ 'USB': {'write_termination': '\n',
+ 'read_termination': \n'}
+ }
+
+This specifies a dictionary of settings for an ASRL (serial) resource and a different for USB. We might make this more concise::
+
+ DEFAULTS = {
+ 'ASRL': {'read_termination': '\r',
+ 'baud_rate': 9600},
+
+ 'USB': {'read_termination': \n'},
+
+ 'COMMON': {'write_termination': '\n'}
+ }
+
+
+When you require a USB resource, Lantz will combine the USB and COMMON settings.
+
+The interface type is not the only thing that defines the resource. For example TCPIP device can be a INSTR or SOCKET. You can also specify this in a tuple::
+
+ DEFAULTS = {
+ 'INSTR': {'read_termination': '\r'},
+
+ 'SOCKET': {'read_termination': \n'},
+
+ 'COMMON': {'write_termination': '\n'}
+ }
+
+This will specify that 'read_termination' will be set '\r' to for al INSTR. If you want to specify only for TCPIP, use a tuple like this::
+
+ DEFAULTS = {
+ ('TCPIP, 'INSTR'): {'read_termination': '\r'},
+
+ 'SOCKET': {'read_termination': \n'},
+
+ 'COMMON': {'write_termination': '\n'}
+ }
+
+
+Overriding on initialization
+----------------------------
+
+You can override the defaults when you instantiate the instrument by passing these values a command line arguments::
+
+ inst = MyDriver('TCPIP::localhost::5678::INSTR', read_termination='\t')
+
+
+Colliding values
+----------------
+
+When multiple values are given for the same setting (for example 'read_termination' is in USB And COMMON) and a USB resource is requested, the following order is used to define the precedence:
+
+ - user provided keyword arguments.
+ - settings for (instrument_type, resource_type).
+ - settings for instrument_type: ASRL, USB, GPIB, TCPIP
+ - settings for resource_type: SOCKET, INSTR, RAW
+ - settings for COMMON
+
+The rule is: more specific has precedence.
+
+
+Valid settings
+--------------
+
+If you provide an invalid setting, you will get an Exception upon initalization. The valid settings are defined by `Attributes per resource in PyVISA`_
+
+.. _Attributes per resource in PyVISA: http://pyvisa.readthedocs.org/en/master/api/resources.html
diff --git a/docs/guides/index.rst b/docs/guides/index.rst
index b968d0b..b1bce40 100644
--- a/docs/guides/index.rst
+++ b/docs/guides/index.rst
@@ -4,11 +4,57 @@
Guides
======
+This are tasks oriented guides:
+
+
+Using drivers
+=============
+
.. toctree::
:maxdepth: 1
+ via-methods
initializing-setup
+
+
+GUI (high-level)
+================
+
+.. toctree::
+ :maxdepth: 1
+
+ blocks
ui-driver
ui-feat-two-widgets
ui-two-drivers
ui-initializing
+ build-backend
+ build-frontend
+
+
+Building drivers
+================
+
+.. toctree::
+ :maxdepth: 1
+
+ defaults
+
+
+GUI (low-level)
+===============
+
+.. toctree::
+ :maxdepth: 1
+
+ ui-no-frontend
+
+
+
+Other guides
+============
+
+.. toctree::
+ :maxdepth: 1
+
+ upgrading
diff --git a/docs/guides/initializing-setup.rst b/docs/guides/initializing-setup.rst
index 5227447..3f9eb22 100644
--- a/docs/guides/initializing-setup.rst
+++ b/docs/guides/initializing-setup.rst
@@ -11,7 +11,7 @@ the program finishes it can lead to deadlocks.
Lantz provides context managers to ensure that that these methods are called.
For example::
- with A2023aSerial('COM1') as fungen:
+ with A2023a.via_serial(1) as fungen:
print(fungen.idn)
fungen.frequency = Q_(20, 'MHz')
@@ -20,14 +20,14 @@ For example::
will call the initializer in the first line and the finalizer when the program
-exits the block even in the case of an unhandled exception as explained in :ref:`using`.
+exits the block even in the case of an unhandled exception as explained in :ref:`Safely-releasing-resources`.
This approach is very useful but inconvenient if the number of instruments
is large. For three instruments is still fine::
- with FrequenceMeter('COM1') as fmeter, \
- A2023aSerial('COM2') as fungen, \
- SR844('COM3') as lockin:
+ with FrequenceMeter.via_serial(1) as fmeter, \
+ A2023a.from_serial_port(2) as fungen, \
+ SR844.from_serial_port(3) as lockin:
freq = fmeter.frequency
@@ -42,7 +42,7 @@ Lantz provides `initialize_many` and `finalize_many` to solve this problem.
The previous example will look like this::
- drivers = (FrequenceMeter('COM1'), A2023aSerial('COM2'), SR844('COM3'))
+ drivers = (FrequenceMeter.via_serial(1), A2023a.via_serial(2), SR844.via_serial(3))
initialize_many(drivers)
@@ -77,8 +77,8 @@ will print (if we assume for each instrument certain initialization times)::
Initializing FrequenceMeter1
Initialized FrequenceMeter1 in 3.2 secs
- Initializing A2023aSerial1
- Initialized A2023aSerial1 in 4.1 secs
+ Initializing A2023a1
+ Initialized A2023a1 in 4.1 secs
Initializing SR8441
Initialized SR8441 in 3.5 secs
Done in 10.8 seconds.
@@ -102,10 +102,10 @@ The output will no be::
Initializing FrequenceMeter1
Initializing SR8441
- Initializing A2023aSerial1
+ Initializing A2023a1
Initialized FrequenceMeter1 in 3.2 secs
Initialized SR8441 in 3.5 secs
- Initialized A2023aSerial1 in 4.1 secs
+ Initialized A2023a1 in 4.1 secs
Done in 4.1 seconds.
Initialization is now done concurrently yielding 2x speed up. For a larger number
@@ -121,11 +121,11 @@ If a particular order in the initialization is required, you can order the list
(or tuple) and do a serial (concurrent=False) initialization. But is slow again.
You can specify a hierarchy of initialization using the `dependencies` argument.
-If the A2023aSerial1 requires that SR8441 and FrequenceMeter1 are initialized
+If the A2023a1 requires that SR8441 and FrequenceMeter1 are initialized
before, the call will be::
initialize_many(drivers, on_initializing=initializing, on_initialized=initialized,
- concurrent=True, dependencies={'A2023aSerial1': ('SR8441', 'FrequenceMeter1')})
+ concurrent=True, dependencies={'A2023a1': ('SR8441', 'FrequenceMeter1')})
and the result will be::
@@ -133,8 +133,8 @@ and the result will be::
Initializing SR8441
Initialized FrequenceMeter1 in 3.2 secs
Initialized SR8441 in 3.5 secs
- Initializing A2023aSerial1
- Initialized A2023aSerial1 in 4.1 secs
+ Initializing A2023a1
+ Initialized A2023a1 in 4.1 secs
Done in 7.6 seconds.
The `dependencies` argument takes a dictionary where each key is a driver name
diff --git a/docs/guides/ui-driver.rst b/docs/guides/ui-driver.rst
index ec269f4..8a27fdc 100644
--- a/docs/guides/ui-driver.rst
+++ b/docs/guides/ui-driver.rst
@@ -12,37 +12,37 @@ While the test widget is very convenient is not good enough for visually attract
You can set the frequency and amplitude using sliders. The sliders are named `frequency` and `amplitude`.
+For *educational* purposes, we show you three ways to do this. You will certainly use only the last and shortest way but showing you how it is done allows you to understand what is going on.
+
The long way
------------
-You can connect each relevant driver Feat to the corresponding widget::
+You can connect each relevant driver Feat to the corresponding widget:
- import sys
+.. code-block:: python
- # Import lantz.ui register an import hook that will replace calls to Qt by PyQt4 or PySide ...
- import lantz.ui
+ import sys
- # and here we just use Qt and will work with both bindings!
- from Qt.QtGui import QApplication, QWidget
- from Qt.uic import loadUi
+ # Import Qt related from lantz so it worsk with PyQt4 or PySide ...
+ from lantz.utils.qt import QtGui
# From lantz we import the driver ...
- from lantz.drivers.examples.fungen import LantzSignalGeneratorTCP
+ from lantz.drivers.examples.fungen import LantzSignalGenerator
# and a function named connect_feat that does the work.
from lantz.ui.widgets import connect_feat
- app = QApplication(sys.argv)
+ app = QtGui.QApplication(sys.argv)
# We load the UI from the QtDesigner file. You can also use pyuic4 to generate a class.
- main = loadUi('connect_test.ui')
+ main = QtGui.loadUi('connect_test.ui')
# We get a reference to each of the widgets.
- frequency_widget = main.findChild((QWidget, ), 'frequency')
- amplitude_widget = main.findChild((QWidget, ), 'amplitude')
+ frequency_widget = main.findChild((QtGui.QWidget, ), 'frequency')
+ amplitude_widget = main.findChild((QtGui.QWidget, ), 'amplitude')
- with LantzSignalGeneratorTCP('localhost', 5678) as inst:
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst:
# We connect each widget to each feature
# The syntax arguments are widget, target (driver), Feat name
@@ -67,29 +67,27 @@ and that is all. Under the hood `connect_feat` is:
The short way
-------------
-If you have named the widgets according to the Feat name as we have done, you can save some typing (not so much here but a lot in big interfaces)::
+If you have named the widgets according to the Feat name as we have done, you can save some typing (not so much here but a lot in big interfaces):
- import sys
+.. code-block:: python
- # Import lantz.ui register an import hook that will replace calls to Qt by PyQt4 or PySide ...
- import lantz.ui
+ import sys
- # and here we just use Qt and will work with both bindings!
- from Qt.QtGui import QApplication, QWidget
- from Qt.uic import loadUi
+ # Import Qt related from lantz so it worsk with PyQt4 or PySide ...
+ from lantz.utils.qt import QtGui
# From lantz we import the driver ...
- from lantz.drivers.examples.fungen import LantzSignalGeneratorTCP
+ from lantz.drivers.examples.fungen import LantzSignalGenerator
# and a function named connect_driver that does the work.
from lantz.ui.widgets import connect_driver
- app = QApplication(sys.argv)
+ app = QtGui.QApplication(sys.argv)
# We load the UI from the QtDesigner file. You can also use pyuic4 to generate a class.
- main = loadUi('connect_test.ui')
+ main = QtGui.loadUi('connect_test.ui')
- with LantzSignalGeneratorTCP('localhost', 5678) as inst:
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst:
# We connect the parent widget (main) to the instrument.
connect_driver(main, inst)
@@ -100,6 +98,24 @@ If you have named the widgets according to the Feat name as we have done, you ca
Notice that now we do not need a reference to the widgets (only to the parent widget, here named main). And we call `connect_driver` (instead of `connect_feat`) without specifying the feat name. Under the hood, `connect_driver` is iterating over all widgets and checking if the driver contains a Feat with the widget name. If it does, it executes `connect_feat`.
+The shortest way
+----------------
+
+As this is a commont pattern, we have a useful function for that:
+
+.. code-block:: python
+
+ import sys
+
+ # From lantz we import the driver ...
+ from lantz.drivers.examples.fungen import LantzSignalGenerator
+
+ from lantz.ui.app import start_gui
+
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst:
+ start_gui('connect_test.ui', inst, sys.argv)
+
+
.. seealso::
:ref:`ui-feat-two-widgets`
diff --git a/docs/guides/ui-feat-two-widgets.rst b/docs/guides/ui-feat-two-widgets.rst
index 2bba60f..574831e 100644
--- a/docs/guides/ui-feat-two-widgets.rst
+++ b/docs/guides/ui-feat-two-widgets.rst
@@ -11,6 +11,8 @@ In many cases you want to have multiple widgets (e.g. different kind) connected
You can set the frequency using the slider or the double spin box. The slider is named `frequency__slider` and the spin is named `frequency`.
+For *educational* purposes, we show you three ways to do this. You will certainly use only the last and shortest way but showing you how it is done allows you to understand what is going on.
+
The long way
------------
@@ -19,12 +21,8 @@ You can connect each relevant driver Feat to the corresponding widget::
import sys
- # Import lantz.ui register an import hook that will replace calls to Qt by PyQt4 or PySide ...
- import lantz.ui
-
- # and here we just use Qt and will work with both bindings!
- from Qt.QtGui import QApplication, QWidget
- from Qt.uic import loadUi
+ # Import Qt related from lantz so it worsk with PyQt4 or PySide ...
+ from lantz.utils.qt import QtGui
# From lantz we import the driver ...
from lantz.drivers.examples.fungen import LantzSignalGeneratorTCP
@@ -32,14 +30,14 @@ You can connect each relevant driver Feat to the corresponding widget::
# and a function named connect_feat that does the work.
from lantz.ui.widgets import connect_feat
- app = QApplication(sys.argv)
+ app = QtGui.QApplication(sys.argv)
# We load the UI from the QtDesigner file. You can also use pyuic4 to generate a class.
- main = loadUi('connect_test.ui')
+ main = QtGui.loadUi('connect_test.ui')
# We get a reference to each of the widgets.
- slider = main.findChild((QWidget, ), 'frequency__slider')
- spin = main.findChild((QWidget, ), 'frequency')
+ slider = main.findChild((QtGui.QWidget, ), 'frequency__slider')
+ spin = main.findChild((QtGui.QWidget, ), 'frequency')
with LantzSignalGeneratorTCP('localhost', 5678) as inst:
@@ -62,12 +60,8 @@ If you have named the widgets according to the Feat and you have use a suffix in
import sys
- # Import lantz.ui register an import hook that will replace calls to Qt by PyQt4 or PySide ...
- import lantz.ui
-
- # and here we just use Qt and will work with both bindings!
- from Qt.QtGui import QApplication, QWidget
- from Qt.uic import loadUi
+ # Import Qt related from lantz so it worsk with PyQt4 or PySide ...
+ from lantz.utils.qt import QtGui
# From lantz we import the driver ...
from lantz.drivers.examples.fungen import LantzSignalGeneratorTCP
@@ -75,10 +69,10 @@ If you have named the widgets according to the Feat and you have use a suffix in
# and a function named connect_feat that does the work.
from lantz.ui.widgets import connect_feat
- app = QApplication(sys.argv)
+ app = QtGui.QApplication(sys.argv)
# We load the UI from the QtDesigner file. You can also use pyuic4 to generate a class.
- main = loadUi('connect_test.ui')
+ main = QtGui.loadUi('connect_test.ui')
with LantzSignalGeneratorTCP('localhost', 5678) as inst:
@@ -97,6 +91,21 @@ In this example, we have use the double underscore `__` to separate the suffix.
There is no limit in the number of widgets that you can connect to the same feat.
+The shortest way
+----------------
+
+As this is a commont pattern, we have a useful function for that::
+
+ import sys
+
+ # Import Qt related from lantz so it worsk with PyQt4 or PySide ...
+ from lantz.ui.app import start_gui
+
+ with LantzSignalGeneratorTCP('localhost', 5678) as inst:
+ start_gui('connect_test.ui', inst, sys.argv)
+
+
+
.. seealso::
:ref:`ui-driver`
diff --git a/docs/guides/ui-no-frontend.rst b/docs/guides/ui-no-frontend.rst
new file mode 100644
index 0000000..6f694f5
--- /dev/null
+++ b/docs/guides/ui-no-frontend.rst
@@ -0,0 +1,245 @@
+.. _ui-no-frontend:
+
+=======================================
+Building a GUI without Backend/Frontend
+=======================================
+
+If you are a long time PyQt user, you might have asked yourself: Do I really need to use the :class:Backend and :class:Frontend classes? The answer is **No**. Lantz helps you, but only if you want it. You can do everything yourself and this guide shows you how.
+
+
+Start the simulated instrument running the following command::
+
+ $ lantz-sim fungen tcp
+
+Using Qt Designer, create a window like this:
+
+.. image:: ../_static/guides/scanfrequency.png
+
+and save it as `fungen.ui` in the folder in which you have created
+the driver (:ref:`tutorial-building`). For the example, we have labeled
+each control as corresponding label in lower caps (amplitude, offset,
+waveform, start, stop, step, wait). The button is named `scan`.
+You can also download
+:download:`the ui file <../_static/guides/scanfrequency.ui>` if you prefer.
+
+We will now add the code to do the actual frequency scan. We will reuse the
+function from the last tutorial::
+
+ import sys
+
+ from lantz import Q_
+
+ # Import Qt related from lantz so it worsk with PyQt4 or PySide ...
+ from lantz.utils.qt import QtGui
+
+ # These imports are from your own project
+ from mydriver import LantzSignalGenerator
+ from scanfrequency import scan_frequency
+
+ app = QtGui.QApplication(sys.argv)
+
+ # Load the UI from the QtDesigner file. You can also use pyuic4 to generate a class.
+ main = QtGui.loadUi('scanfrequency.ui')
+
+ Hz = Q_(1, 'Hz')
+ sec = Q_(1, 'second')
+
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst:
+
+ # Obtain a reference to the widgets controlling the scan parameters
+ start = main.findChild((QtGui.QWidget, ), 'start')
+ stop = main.findChild((QtGui.QWidget, ), 'stop')
+ step = main.findChild((QtGui.QWidget, ), 'step')
+ wait = main.findChild((QtGui.QWidget, ), 'wait')
+ scan = main.findChild((QtGui.QWidget, ), 'scan')
+
+ # Define a function to read the values from the widget and call scan_frequency
+ def scan_clicked():
+ scan_frequency(inst, start.value() * Hz, stop.value() * Hz,
+ step.value() * Hz, wait.value() * sec)
+
+ # Connect the clicked signal of the scan button to the function
+ scan.clicked.connect(scan_clicked)
+
+ # Scan the app
+ main.show()
+ exit(app.exec_())
+
+When the button is clicked, Qt will emit a signal which is connected to the
+function we have defined the application should scan the frequency. You will
+not see anything happening in the Window, but if you look in the simulator
+console you will see the frequency changing.
+
+
+Connecting widgets to Feats
+---------------------------
+
+To allow the user to change the amplitude, offset, shape and frequency, we will
+connect the configuration widgets::
+
+ import sys
+
+ from lantz import Q_
+
+ # Import Qt related from lantz so it worsk with PyQt4 or PySide ...
+ from lantz.utils.qt import QtGui
+
+ # Import from lantz a function to connect drivers to UI <--- NEW
+ from lantz.ui.widgets import connect_driver
+
+ # These imports are from your own project
+ from mydriver import LantzSignalGenerator
+ from scanfrequency import scan_frequency
+
+ app = QtGui.QApplication(sys.argv)
+
+ # Load the UI from the QtDesigner file. You can also use pyuic4 to generate a class.
+ main = QtGui.loadUi('scanfrequency.ui')
+
+ Hz = Q_(1, 'Hz')
+ sec = Q_(1, 'second')
+
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst:
+
+ # Obtain a reference to the widgets controlling the scan parameters
+ start = main.findChild((QtGui.QWidget, ), 'start')
+ stop = main.findChild((QtGui.QWidget, ), 'stop')
+ step = main.findChild((QtGui.QWidget, ), 'step')
+ wait = main.findChild((QtGui.QWidget, ), 'wait')
+ scan = main.findChild((QtGui.QWidget, ), 'scan')
+
+ # <--------- This is new --------->
+ connect_driver(main, inst)
+
+ progress = main.findChild((QtGui.QWidget, ), 'progress')
+
+ def update_progress_bar(new, old):
+ fraction = (new.magnitude - start.value()) / (stop.value() - start.value())
+ progress.setValue(fraction * 100)
+
+ inst.frequency_changed.connect(update_progress_bar)
+
+
+ # Define a function to read the values from the widget and call scan_frequency
+ def scan_clicked():
+ scan_frequency(inst, start.value() * Hz, stop.value() * Hz,
+ step.value() * Hz, wait.value() * sec)
+
+ # Connect the clicked signal of the scan button to the function
+ scan.clicked.connect(scan_clicked)
+
+ # Scan the app
+ main.show()
+ exit(app.exec_())
+
+The function `connect_driver` matches by name Widgets to Feats and then connects
+them. Under the hood, for each match it:
+
+ 1.- Wraps the widget to make it Lantz compatible.
+
+ 2.- If applicable, configures minimum, maximum, steps and units.
+
+ 3.- Add a handler such as when the widget value is changed, the Feat is updated.
+
+ 4.- Add a handler such as when the Feat value is changed, the widget is updated.
+
+You can learn more and some alternatives in :ref:`ui-driver`.
+
+To update the progress bar, we connected the `frequency_changed` signal to a
+function that updates the progress bar.
+
+Run this example and test how you can change the amplitude, offset and waveform::
+
+ $ python scanfrequency-gui.py
+
+However, you will see that the frequency and the progress bar are not updated
+during the scan.
+
+Using a background thread
+-------------------------
+
+The drawback of the previous (simple) approach is that the scan is executed in the same
+thread as the GUI, effectively locking the main window and making the application
+unresponsive. Qt Multithreading programming is out of the scope of this tutorial
+(checkout `Threads in Qt`_ for more info), but we will provide some examples
+how to do it::
+
+ import sys
+
+ # Import Qt related from lantz so it worsk with PyQt4 or PySide ...
+ from lantz.utils.qt import QtGui, QtCore
+
+ from lantz import Q_
+
+ # Import from lantz a function to connect drivers to UI
+ from lantz.ui.widgets import connect_driver
+
+ # These imports are from your own project
+ from mydriver import LantzSignalGenerator
+ from scanfrequency import scan_frequency
+
+ app = QtGui.QApplication(sys.argv)
+
+ # Load the UI from the QtDesigner file. You can also use pyuic4 to generate a class.
+ main = QtGui.loadUi('scanfrequency.ui')
+
+ Hz = Q_(1, 'Hz')
+ sec = Q_(1, 'second')
+
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') a as inst:
+
+ # Connect the main panel widgets to the instruments Feats,
+ # matching by name
+ connect_driver(main, inst)
+
+ # Obtain a reference to the widgets controlling the scan parameters
+ start = main.findChild((QtGui.QWidget, ), 'start')
+ stop = main.findChild((QtGui.QWidget, ), 'stop')
+ step = main.findChild((QtGui.QWidget, ), 'step')
+ wait = main.findChild((QtGui.QWidget, ), 'wait')
+ scan = main.findChild((QtGui.QWidget, ), 'scan')
+ progress = main.findChild((QtGui.QWidget, ), 'progress')
+
+ def update_progress_bar(new, old):
+ fraction = (new.magnitude - start.value()) / (stop.value() - start.value())
+ progress.setValue(fraction * 100)
+
+ inst.frequency_changed.connect(update_progress_bar)
+
+ # <--------- New code--------->
+ # Define a function to read the values from the widget and call scan_frequency
+ class Scanner(QtCore.QObject):
+
+ def scan(self):
+ # Call the scan frequency
+ scan_frequency(inst, start.value() * Hz, stop.value() * Hz,
+ step.value() * Hz, wait.value() * sec)
+ # When it finishes, set the progress to 100%
+ progress.setValue(100)
+
+ thread = QtCore.QThread()
+ scanner = Scanner()
+ scanner.moveToThread(thread)
+ thread.start()
+
+ # Connect the clicked signal of the scan button to the function
+ scan.clicked.connect(scanner.scan)
+
+ app.aboutToQuit.connect(thread.quit)
+ # <--------- End of new code --------->
+
+ main.show()
+ exit(app.exec_())
+
+In Qt, when a signal is connected to a slot (a function of a QObject),
+the execution occurs in the Thread of the receiver (not the emitter).
+That is why we moved the QObject to the new thread.
+
+.. note::
+ On a production app it would be good to add a lock to prevent the application
+ from exiting or calling the scanner while a scanning is running.
+
+
+:class:Backend and :class:Frontend provides an easier way to deal with all of these, allowing you to focus in your code not in how to connect everything.
+
+.. _`Threads in Qt`: http://doc.qt.digia.com/4.7/threads.html
diff --git a/docs/guides/ui-two-drivers.rst b/docs/guides/ui-two-drivers.rst
index 3ca31f0..0916c61 100644
--- a/docs/guides/ui-two-drivers.rst
+++ b/docs/guides/ui-two-drivers.rst
@@ -13,6 +13,8 @@ Real application consists not only of a single instrument but many. In a custom
The widgets are named `fungen1__frequency` and `fungen2__frequency`.
+For *educational* purposes, we show you four ways to do this. You will certainly use only the last and shortest way but showing you how it is done allows you to understand what is going on.
+
The long way
------------
@@ -21,30 +23,26 @@ Get a reference to each widget and connect them manually::
import sys
- # Import lantz.ui register an import hook that will replace calls to Qt by PyQt4 or PySide ...
- import lantz.ui
-
- # and here we just use Qt and will work with both bindings!
- from Qt.QtGui import QApplication, QWidget
- from Qt.uic import loadUi
+ # Import Qt related from lantz so it worsk with PyQt4 or PySide ...
+ from lantz.utils.qt import QtGui
# From lantz we import the driver ...
- from lantz.drivers.examples.fungen import LantzSignalGeneratorTCP
+ from lantz.drivers.examples.fungen import LantzSignalGenerator
# and a function named connect_feat that does the work.
from lantz.ui.widgets import connect_feat
- app = QApplication(sys.argv)
+ app = QtGui.QApplication(sys.argv)
# We load the UI from the QtDesigner file. You can also use pyuic4 to generate a class.
- main = loadUi('ui-two-drivers.ui')
+ main = QtGui.loadUi('ui-two-drivers.ui')
# We get a reference to each of the widgets.
- freq1 = main.findChild((QWidget, ), 'fungen1__frequency')
- freq2 = main.findChild((QWidget, ), 'fungen2__frequency')
+ freq1 = main.findChild((QtGui.QWidget, ), 'fungen1__frequency')
+ freq2 = main.findChild((QtGui.QWidget, ), 'fungen2__frequency')
- with LantzSignalGeneratorTCP('localhost', 5678) as inst1, \
- LantzSignalGeneratorTCP('localhost', 5679) as inst2:
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst1, \
+ LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst2:
# We connect each widget to each feature
# The syntax arguments are widget, target (driver), Feat name
@@ -62,26 +60,22 @@ If you have use a prefix to solve the name collision you can use it and connect
import sys
- # Import lantz.ui register an import hook that will replace calls to Qt by PyQt4 or PySide ...
- import lantz.ui
-
- # and here we just use Qt and will work with both bindings!
- from Qt.QtGui import QApplication, QWidget
- from Qt.uic import loadUi
+ # Import Qt related from lantz so it worsk with PyQt4 or PySide ...
+ from lantz.utils.qt import QtGui
# From lantz we import the driver ...
- from lantz.drivers.examples.fungen import LantzSignalGeneratorTCP
+ from lantz.drivers.examples.fungen import LantzSignalGenerator
# and a function named connect_feat that does the work.
from lantz.ui.widgets import connect_feat
- app = QApplication(sys.argv)
+ app = QtGui.QApplication(sys.argv)
# We load the UI from the QtDesigner file. You can also use pyuic4 to generate a class.
- main = loadUi('ui-two-drivers.ui')
+ main = QtGui.loadUi('ui-two-drivers.ui')
- with LantzSignalGeneratorTCP('localhost', 5678) as inst1, \
- LantzSignalGeneratorTCP('localhost', 5679) as inst2:
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst1, \
+ LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst2:
# We connect each widget to each feature
# The syntax arguments are widget, target (driver), Feat name
@@ -100,12 +94,8 @@ If you have named the widgets according to the Feat name and added a prefix corr
import sys
- # Import lantz.ui register an import hook that will replace calls to Qt by PyQt4 or PySide ...
- import lantz.ui
-
- # and here we just use Qt and will work with both bindings!
- from Qt.QtGui import QApplication, QWidget
- from Qt.uic import loadUi
+ # Import Qt related from lantz so it worsk with PyQt4 or PySide ...
+ from lantz.utils.qt import QtGui
# From lantz we import the driver ...
from lantz.drivers.examples.fungen import LantzSignalGeneratorTCP
@@ -113,14 +103,14 @@ If you have named the widgets according to the Feat name and added a prefix corr
# and a function named connect_feat that does the work.
from lantz.ui.widgets import connect_feat
- app = QApplication(sys.argv)
+ app = QtGui.QApplication(sys.argv)
# We load the UI from the QtDesigner file. You can also use pyuic4 to generate a class.
- main = loadUi('ui-two-drivers.ui')
+ main = QtGui.loadUi('ui-two-drivers.ui')
# Notice that now we specify the instrument name!
- with LantzSignalGeneratorTCP('localhost', 5678, name='fungen1') as inst1, \
- LantzSignalGeneratorTCP('localhost', 5679, name='fungen2') as inst2:
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET', name='fungen1') as inst1, \
+ LantzSignalGenerator('TCPIP::localhost::5679::SOCKET', name='fungen2') as inst2:
# We connect the whole main widget, and we give a list of drivers.
connect_setup(main, [inst1, inst2])
@@ -131,6 +121,28 @@ If you have named the widgets according to the Feat name and added a prefix corr
Under the hood, `connect_setup` iterates over all drivers in the second argument and executes `connect_driver` using the driver name.
+The shortest way
+----------------
+
+As this is a commont pattern, we have a useful function for that::
+
+ import sys
+
+
+ # From lantz we import the driver ...
+ from lantz.drivers.examples.fungen import LantzSignalGeneratorTCP
+
+ # Import Qt related from lantz so it worsk with PyQt4 or PySide ...
+ from lantz.ui.app import start_gui
+
+ # Notice that now we specify the instrument name!
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET', name='fungen1') as inst1, \
+ LantzSignalGenerator('TCPIP::localhost::5679::SOCKET', name='fungen2') as inst2:
+
+ start_gui('connect_test.ui', [inst1, inst2], sys.argv)
+
+
+
.. seealso::
:ref:`ui-driver`
diff --git a/docs/guides/upgrading.rst b/docs/guides/upgrading.rst
new file mode 100644
index 0000000..0e2c848
--- /dev/null
+++ b/docs/guides/upgrading.rst
@@ -0,0 +1,44 @@
+.. _upgrading:
+
+===========================
+Upgrading to newer releases
+===========================
+
+Like any other package, Lantz is changing over time. Most of the changes are backwards compatible and you don’t have to change anything in your code to enjoy the new release.
+
+But every once in a while we to introduce some disruptive changes. We might realize that something error prone or the API was confusing. Or we might introduce a better way to do things and you might need to change your code to take advantage of it.
+
+This section of the documentation enumerates all the backwards incompatible changes in Lantz, helping you to transition your code from release to release.
+
+You can upgrade to the latest version of Lantz using pip::
+
+ pip install -U lantz
+
+
+Upgrading to 0.3
+----------------
+
+We introduced the :class:`lantz.messagebased.MessageBasedDriver`, a class to rule them all. Replaces :class:`SerialDriver`, :class:`TCPDriver`, :class:`VisaDriver`, :class:`USBDriver`, :class:`USBTMCDriver`, :class:`SerialVisaDriver`, :class:`GPIBVisaDriver`, :class:`USBVisaDriver`.
+
+Migrating your driver to use `MessageBasedDriver` is easy:
+
+ 1. Add the necessary imports::
+
+ from lantz.messagebased import MessageBasedDriver
+
+ 2. Change the base class of your driver::
+
+ class MyDriver(MessageBasedDriver):
+
+ # Your code
+
+ 3. In the class methods, change `self.send` by `self.write`; and `self.recv` by `self.read`.
+ This was done to homogenize the API with PyVISA
+
+ 4. Change the class defaults to use the :ref:`defaults_dictionary`.
+
+You are **NOT** forced to migrate you classes. The old base classes are still available under :mod:`lantz.drivers.legacy`. So you can just change the import.
+
+While all instrument drivers have been migrated to the new :class:`MessageBasedDriver` you can still used the drivers based on the legacy classes from :mod:`lantz.drivers.legacy`.
+
+The only backwards incompatib
diff --git a/docs/guides/via-methods.rst b/docs/guides/via-methods.rst
new file mode 100644
index 0000000..4f052f4
--- /dev/null
+++ b/docs/guides/via-methods.rst
@@ -0,0 +1,124 @@
+.. _via-methods:
+
+==========================================
+Avoiding Resource Names: the *via* methods
+==========================================
+
+While resource names provide a comprehensive way to address devices in many cases you want a simpler and succint way to do it. :class::MessageBasedDriver provide special methods for each interface type. Under the hood, these methods build the resource name for you based on minimum information.
+
+
+Via Serial
+----------
+
+For serial instruments, you just need to provide the serial port. Instead of doing::
+
+ with MyDriver('ASRL1::INSTR') as instrument:
+
+ print(instrument.idn)
+
+you can do::
+
+ with MyDriver.via_serial(1) as instrument:
+
+ print(instrument.idn)
+
+Just like with the standard constructor you can specify the name of the instrument (for logging purposes)::
+
+ with MyDriver.via_serial(1, name='mydevice') as instrument:
+
+ print(instrument.idn)
+
+
+And you can also specify the initialization settings::
+
+ with MyDriver.via_serial(1, name='mydevice', 'read_termination'='\n') as instrument:
+
+ print(instrument.idn)
+
+Names and setting are available for all constructor methods so we will ignore them from now on.
+
+
+Via TCPIP
+---------
+
+For TCPIP instruments, you just need to provide the hostname (ip address) and port. Instead of doing::
+
+ with MyDriver('TCPIP::localhost::5678::INSTR') as instrument:
+
+ print(instrument.idn)
+
+you can do::
+
+ with MyDriver.via_tcpip('localhost', 5678) as instrument:
+
+ print(instrument.idn)
+
+
+The same is true for TCIP Sockets.
+
+Instead of doing::
+
+ with MyDriver('TCPIP::localhost::5678::SOCKET') as instrument:
+
+ print(instrument.idn)
+
+you can do::
+
+ with MyDriver.via_tcpip_socket('localhost', 5678) as instrument:
+
+ print(instrument.idn)
+
+
+Via GPIB
+--------
+
+For TCPIP instruments, you just need to provide the gpib address::
+
+ with MyDriver('GPIB::9::INSTR') as instrument:
+
+ print(instrument.idn)
+
+you can do::
+
+ with MyDriver.via_gpib(9) as instrument:
+
+ print(instrument.idn)
+
+
+Via USB
+-------
+
+For USB instruments the thing becomes really useful. USB devices *announce* themselves to the computer indicating the manufacturer id, model code and serial number. Many classes have hardcoded the manufacturer id and model code (or codes) of the instruments they can control.
+
+So instead of doing::
+
+ with MyDriver('USB0::0x1AB1::0x0588::DS1K00005888::INSTR') as instrument
+
+ print(instrument.idn)
+
+you can just do::
+
+ with MyDriver.via_usb() as instrument:
+
+ print(instrument.idn)
+
+If you have multiple identical usb instuments connected, this will fail. In this case you need to specify the serial number of the instrument you want::
+
+ with MyDriver.via_usb('DS1K00005888') as instrument:
+
+ print(instrument.idn)
+
+
+If you want to try if a driver can control another instrument you can override the model code and/or the manufacturer id::
+
+ with MyDriver.via_usb(manufacturer_id='0x1AB2', model_code='0x0589') as instrument:
+
+ print(instrument.idn)
+
+You can also specify the serial code if you want. The rule is simple you need to specify it in a way that there is only one.
+
+The same arguments are valid to create a USB SOCKET::
+
+ with MyDriver.via_usb_socket() as instrument:
+
+ print(instrument.idn)
diff --git a/docs/index.rst b/docs/index.rst
index e8b7c58..3bb40bc 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,5 +1,5 @@
-Welcome to Lantz
-----------------
+Lantz: Simple yet powerful instrumentation in Python
+----------------------------------------------------
.. image:: _static/lantz_logo.png
:alt: Lantz
@@ -10,8 +10,33 @@ and consistent interface. It provides a core of commonly used functionalities
for building applications that communicate with scientific instruments allowing
rapid application prototyping, development and testing. Lantz benefits from
Python's extensive library flexibility as a glue language to wrap existing
-drivers and DLLs. Lantz aims to provide a library of curated and well documented
-instruments drivers.
+drivers and DLLs.
+
+When you use Lantz you get:
+
+ - A comprehensive and growing library of curated and well documented
+ instruments drivers.
+
+ - A really easy way write your own drivers. In less than an hour
+ you can write a full driver with bounds checks, useful logging,
+ async capabilities and much more.
+
+ - On-the-fly GUI for testing purposes.
+ Without a line of code you get for **any** driver something like this
+ (click to enlarge):
+
+ .. thumbnail:: _static/ui-fungen.png
+ :width: 20%
+
+ - Tools to quickly build beautiful, responsive and composable applications.
+
+ - Rapid application development primitives.
+
+ - An awesome and supporting community.
+
+
+More information
+----------------
| :ref:`about`
| Lantz Philosophy and design principles.
diff --git a/docs/overview.rst b/docs/overview.rst
index 2606d7a..145ae9e 100644
--- a/docs/overview.rst
+++ b/docs/overview.rst
@@ -8,9 +8,9 @@ A minimal script to control a function generator using Lantz might look like thi
from lantz import Q_
- from lantz.drivers.aeroflex import A2023aSerial
+ from lantz.drivers.aeroflex import A2023a
- fungen = A2023aSerial('COM1')
+ fungen = A2023a('COM1')
fungen.initialize()
print(fungen.idn)
@@ -40,11 +40,11 @@ You will see the instance initializing and how and when each property is accesse
which for this case becomes::
- lantz.A2023aSerial.A2023aSerial0
+ lantz.A2023a.A2023a0
because no name was given. If you want to specify a name, do it at object creation::
- fungen = A2023aSerial('COM1', name='white')
+ fungen = A2023a('COM1', name='white')
Separation into multiple loggers makes finding problems easier and enables fine grained control over log output.
@@ -190,7 +190,7 @@ Context manager
If you want to send a command to an instrument only once during a particular script, you might want to make use of the context manager syntax. In the following example, the driver will be created and initialized in the first line and finalized when the `with` clause finishes even when an unhandled exception is raised::
- with A2023aSerial('COM1') as fungen:
+ with A2023a('COM1') as fungen:
print(fungen.idn)
fungen.frequency = Q_(20, 'MHz')
@@ -239,11 +239,11 @@ You might want to use the value obtained in one instrument to set another. Or yo
from lantz import Q_
from lantz.drivers.example import FrequenceMeter
- from lantz.drivers.aeroflex import A2023aSerial
+ from lantz.drivers.aeroflex import A2023a
from lantz.drivers.standford import SR844
with FrequenceMeter('COM1') as fmeter, \
- A2023aSerial('COM2') as fungen, \
+ A2023a('COM2') as fungen, \
SR844('COM3') as lockin:
freq = fmeter.frequency
diff --git a/docs/tutorial/building.rst b/docs/tutorial/building.rst
index e9b78dc..1b6bd4c 100644
--- a/docs/tutorial/building.rst
+++ b/docs/tutorial/building.rst
@@ -79,20 +79,20 @@ A basic driver
--------------
Having look at the instrument, we will now create the driver. Open the project folder that you created in the previous tutorial (:ref:`tutorial-using`). Create a python file named `mydriver.py` (I know is a bad name but it is just to stress that
- it is yours) and change it to look like this::
+ it is yours) and change it to look like this:
+.. code-block:: python
+
from lantz import Feat
- from lantz.network import TCPDriver
+ from lantz.messagebased import MessageBasedDriver
- class LantzSignalGeneratorTCP(TCPDriver):
+ class LantzSignalGenerator(MessageBasedDriver):
"""Lantz Signal Generator.
"""
- ENCODING = 'ascii'
-
- RECV_TERMINATION = '\n'
- SEND_TERMINATION = '\n'
+ DEFAULTS = {'COMMON': {'write_termination': '\n',
+ 'read_termination': '\n'}}
@Feat()
def idn(self):
@@ -100,18 +100,18 @@ Having look at the instrument, we will now create the driver. Open the project f
if __name__ == '__main__':
- with LantzSignalGeneratorTCP('localhost', 5678) as inst:
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst:
print('The identification of this instrument is : ' + inst.idn)
-The code is straight forward. We first import TCPDriver from lantz.network (the Lantz module for network related functions).
-TCPDriver is a base class (derived from Driver) that implements methods to communicate via TCP protocol. Our driver will derive from this.
+The code is straight forward. We first import :class:MessageBasedDriver from :mod:lantz.messagebased (the Lantz module for message based instruments).
+MessageBasedDriver is a base class (derived from :class:Driver) that implements methods to communicate via different protocols. Our driver will derive from this.
-We also import Feat from lantz. Feat is the Lantz pimped property and you use Feat just like you use `property`.
+We also import Feat from lantz. Feat is the Lantz pimped property and you use Feat just like you use :py:class:`property`.
By convention Feats are named using nouns or adjectives.
-Inside the method (in this case is a getter) goes the code to communicate with the instrument. In this case we use `query`, a function present in all based classes for message drivers (TCPDriver, SerialDriver, etc). `query` sends a message to the instrument, waits for a response and returns it. The argument is the command to be sent to the instrument. Lantz takes care of formatting (encoding, endings) and transmitting the command appropriately. That's why we define ENCODING, RECV_TERMINATION, SEND_TERMINATION at the beginning of the class.
+Inside the method (in this case is a getter) goes the code to communicate with the instrument. In this case we use `query`, a method present in :class:MessageBasedDrivers. `query` sends a message to the instrument, waits for a response and returns it. The argument is the command to be sent to the instrument. Lantz takes care of formatting (encoding, endings) and transmitting the command appropriately. That's why we define :ref:_defaults_dictionary at the beginning of the class. You can find more information about in the guides, but for now on, we will just point out that the key **COMMON** is used to tell Lantz that the following keyword arguments are for all instrument types (USB, GPIB, etc). In particular we specify that the read and write termination are `'\n'`.
-Finally, inside the `__name__ == '__main__'` we instantiate the SignalGenerator specifying host and port (these are arguments of the TCPDriver constructor, more on this later) and we print the identification.
+Finally, inside the `__name__ == '__main__'` we instantiate the SignalGenerator (as we have seen in :ref:using and we print the identification.
If you have the simulator running, you can test your new driver. From the command line, cd into the project directory and then run the following command::
@@ -121,51 +121,21 @@ If you have the simulator running, you can test your new driver. From the comman
the one in which you have installed Lantz. You might need to use
`python3` instead of `python`.
-You should see `LSG Serial #1234`.
+You should see `LSG Serial #1234` in the console.
-Let's see what's its going on under the hood by logging to screen in debug mode::
+Let's allow our driver to control the instruments amplitude:
- from lantz.log import log_to_screen, DEBUG # <-- This is new
+.. code-block:: python
from lantz import Feat
- from lantz.network import TCPDriver
+ from lantz.network import MessageBasedDriver
- class LantzSignalGeneratorTCP(TCPDriver):
+ class LantzSignalGenerator(MessageBasedDriver):
"""Lantz Signal Generator.
"""
- ENCODING = 'ascii'
-
- RECV_TERMINATION = '\n'
- SEND_TERMINATION = '\n'
-
- @Feat()
- def idn(self):
- """Identification.
- """
- return self.query('?IDN')
-
-
- if __name__ == '__main__':
- log_to_screen(DEBUG)
- with LantzSignalGeneratorTCP('localhost', 5678) as inst:
- print('The identification of this instrument is : ' + inst.idn)
-
-You can adjust the level of information provided by changing the LOGGING_LEVEL. You can also display the logging in another window to avoid cluttering but this comes later.
-
-Let's allow our driver to control the instruments amplitude::
-
- from lantz import Feat
- from lantz.network import TCPDriver
-
- class LantzSignalGeneratorTCP(TCPDriver):
- """Lantz Signal Generator.
- """
-
- ENCODING = 'ascii'
-
- RECV_TERMINATION = '\n'
- SEND_TERMINATION = '\n'
+ DEFAULTS = {'COMMON': {'write_termination': '\n',
+ 'read_termination': '\n'}}
@Feat()
def idn(self):
@@ -190,7 +160,7 @@ Let's allow our driver to control the instruments amplitude::
from lantz.log import log_to_screen, DEBUG
log_to_screen(DEBUG)
- with LantzSignalGeneratorTCP('localhost', 5678) as inst:
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst:
print('The identification of this instrument is : ' + inst.idn)
print('Setting amplitude to 3')
inst.amplitude = 3
@@ -199,13 +169,13 @@ Let's allow our driver to control the instruments amplitude::
print('Current amplitude: {}'.format(inst.amplitude))
-We have defined another Feat, now with a getter and a setter. The getter sends `?AMP` and waits for the answer which is converted to float and returned to the caller. The setter send `!AMP` concatenated with the float formatted to string with two decimals. Run the script. Check also the window running `sim-fungen.py`. You should see the amplitude changing!.
+We have defined another Feat, now with a getter and a setter. The getter sends `?AMP` and waits for the answer which is converted to float and returned to the caller. The setter send `!AMP` concatenated with the float formatted to string with two decimals. Run the script. Check also the window running `lantz-sim`. You should see the amplitude changing!.
In the current version of this driver, if we try to set the amplitude to 20 V the command will fill in the instrument but the driver will not know. Lets add some error checking::
# import ...
- class LantzSignalGeneratorTCP(TCPDriver):
+ class LantzSignalGenerator(MessageBasedDriver):
# Code from previous example
# ...
@@ -227,7 +197,7 @@ Because all commands should be checked for `ERROR`, we will override query to do
# import ...
from lantz.errors import InstrumentError
- class LantzSignalGeneratorTCP(TCPDriver):
+ class LantzSignalGenerator(MessageBasedDriver):
# Code from previous example
# ...
@@ -250,20 +220,20 @@ for errors. In this way we have added error checking for all queries!.
Putting units to work
---------------------
-Hoping that the Mars Orbiter story convinced you that using units is worth it, let's modify the driver to use them::
+Hoping that the Mars Orbiter story convinced you that using units is worth it, let's modify the driver to use them:
+
+.. code-block:: python
from lantz import Feat
- from lantz.network import TCPDriver
+ from lantz.network import MessageBasedDriver
from lantz.errors import InstrumentError
- class LantzSignalGeneratorTCP(TCPDriver):
+ class LantzSignalGenerator(MessageBasedDriver):
"""Lantz Signal Generator.
"""
- ENCODING = 'ascii'
-
- RECV_TERMINATION = '\n'
- SEND_TERMINATION = '\n'
+ DEFAULTS = {'COMMON': {'write_termination': '\n',
+ 'read_termination': '\n'}}
def query(self, command, *, send_args=(None, None), recv_args=(None, None)):
answer = super().query(command, send_args=send_args, recv_args=recv_args)
@@ -296,7 +266,7 @@ Hoping that the Mars Orbiter story convinced you that using units is worth it, l
milivolt = Q_(1, 'mV')
log_to_screen(DEBUG)
- with LantzSignalGeneratorTCP('localhost', 5678) as inst:
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst:
print('The identification of this instrument is : ' + inst.idn)
print('Setting amplitude to 3')
inst.amplitude = 3 * volt
@@ -322,20 +292,22 @@ When the communication round-trip to the instrument is too long, you might want
If you provide a value outside the valid range, Lantz will raise a ValueError.
If the steps parameter is set but you provide a value not compatible with it,
-it will be silently rounded. Let's put this to work for amplitude, frequency and offset::
+it will be silently rounded. Let's put this to work for amplitude, frequency and offset:
+
+
+.. code-block:: python
from lantz import Feat
- from lantz.network import TCPDriver
+ from lantz.network import MessageBasedDriver
from lantz.errors import InstrumentError
- class LantzSignalGeneratorTCP(TCPDriver):
+ class LantzSignalGenerator(MessageBasedDriver):
"""Lantz Signal Generator
"""
- ENCODING = 'ascii'
+ DEFAULTS = {'COMMON': {'write_termination': '\n',
+ 'read_termination': '\n'}}
- RECV_TERMINATION = '\n'
- SEND_TERMINATION = '\n'
def query(self, command, *, send_args=(None, None), recv_args=(None, None)):
answer = super().query(command, send_args=send_args, recv_args=recv_args)
@@ -388,20 +360,21 @@ Automatic rounding::
Mapping values
--------------
-We will define offset and frequency like we did with amplitude, and we will also define output enabled and waveform::
+We will define offset and frequency like we did with amplitude, and we will also define output enabled and waveform:
+
+.. code-block:: python
from lantz import Feat, DictFeat
- from lantz.network import TCPDriver
+ from lantz.network import MessageBasedDriver
from lantz.errors import InstrumentError
- class LantzSignalGeneratorTCP(TCPDriver):
+ class LantzSignalGenerator(MessageBasedDriver):
"""Lantz Signal Generator
"""
- ENCODING = 'ascii'
+ DEFAULTS = {'COMMON': {'write_termination': '\n',
+ 'read_termination': '\n'}}
- RECV_TERMINATION = '\n'
- SEND_TERMINATION = '\n'
def query(self, command, *, send_args=(None, None), recv_args=(None, None)):
answer = super().query(command, send_args=send_args, recv_args=recv_args)
@@ -471,7 +444,7 @@ We will define offset and frequency like we did with amplitude, and we will also
Hz = Q_(1, 'Hz')
log_to_screen(DEBUG)
- with LantzSignalGeneratorTCP('localhost', 5678) as inst:
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst:
print('The identification of this instrument is : ' + inst.idn)
print('Setting amplitude to 3')
inst.amplitude = 3 * volt
@@ -489,6 +462,35 @@ We have provided `output_enabled` a mapping table through the `values` argument.
This means that we can write the body of the getter/setter expecting a instrument compatible value (1 or 0) but the user actually sees a much more friendly interface (True or False). The same happens with `waveform`. Instead of asking the user to memorize which number corresponds to 'sine' or implement his own mapping, we provide this within the feat.
+Beautiful Testing
+-----------------
+
+Testing in the command line is extermely useful but sometimes it is desirable to have simple GUI to be used as a test panel. Lantz gives you that with no effort. Just change the main part to this::
+
+ if __name__ == '__main__':
+ from lantz.ui.qtwidgets import start_test_app
+
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst:
+ start_test_app(inst)
+
+
+and you will get something like this:
+
+ .. image:: ../_static/ui-fungen.png
+ :width: 20%
+
+Cool, right? Using Python amazing introspection capabilites together with Feat annotations inside the driver, Lantz was able to build **on-the-fly** a Graphical User Interface for testing purposes.
+
+Among other things, you get:
+
+ - The right widget, for the right datatype: Check-box for boolean, Combo-box for options, etc.
+ - When a Feat has units, the suffix is displayed.
+ You can also press the `u` key to change the displayed units.
+ - Minimum and Maximum values when a Feat has limits
+
+... and much more! All of this without an extra line of code.
+
+
Properties with items: DictFeat
-------------------------------
@@ -497,7 +499,7 @@ It is quite common that scientific equipment has many of certain features (such
# import ...
- class LantzSignalGeneratorTCP(TCPDriver):
+ class LantzSignalGenerator(MessageBasedDriver):
# Code from previous example
# ...
@@ -523,7 +525,7 @@ By default, any key (in this case, channel) is valid and Lantz leaves to the und
# import ...
- class LantzSignalGeneratorTCP(TCPDriver):
+ class LantzSignalGenerator(MessageBasedDriver):
# Code from previous example
# ...
@@ -551,7 +553,7 @@ We will create now a read-read only DictFeat for the digital input::
# import ...
- class LantzSignalGeneratorTCP(TCPDriver):
+ class LantzSignalGenerator(MessageBasedDriver):
# Code from previous example
# ...
@@ -579,7 +581,7 @@ and within the class we will add::
self.query('!CAL')
-.. TODO: expand this section and add !CAL to the driver. Add section `Interactive`
+.. TODO: expand this section and add !CAL to the driver.
.. rubric::
diff --git a/docs/tutorial/cli-app.rst b/docs/tutorial/cli-app.rst
index ff664b1..d93242f 100644
--- a/docs/tutorial/cli-app.rst
+++ b/docs/tutorial/cli-app.rst
@@ -13,13 +13,16 @@ Start the simulated instrument running the following command::
Open the folder in which you have created the driver (:ref:`tutorial-building`)
-and create a python file named `scanfrequency.py`::
+and create a python file named `scanfrequency.py`:
+
+
+.. code-block:: python
import time
from lantz import Q_
- from mydriver import LantzSignalGeneratorTCP
+ from mydriver import LantzSignalGenerator
Hz = Q_(1, 'Hz')
start = 1 * Hz
@@ -27,7 +30,7 @@ and create a python file named `scanfrequency.py`::
step = 1 * Hz
wait = .5
- with LantzSignalGeneratorTCP('localhost', 5678) as inst:
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst:
print(inst.idn)
current = start
@@ -64,7 +67,7 @@ the `argparse` module and create a parser object::
from lantz import Q_
- from mydriver import LantzSignalGeneratorTCP
+ from mydriver import LantzSignalGenerator
parser = argparse.ArgumentParser()
parser.add_argument('start', type=float,
@@ -84,7 +87,7 @@ the `argparse` module and create a parser object::
step = args.step * Hz
wait = args.wait
- with LantzSignalGeneratorTCP('localhost', 5678) as inst:
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst:
print(inst.idn)
current = start
@@ -107,93 +110,5 @@ Try it again specifying the start, stop, step and waiting time::
$ python scanfrequency.py 2 8 2 .1
-Refactoring for reusability
----------------------------
-
-Finally we will add a couple of lines to allow the user to define the host
-and port number of the TCP function generator. We will also refactor the
-code to extract the function that perform the actual frequency scan apart::
-
- import time
-
- def scan_frequency(inst, start, stop, step, wait):
- """Scan frequency in an instrument.
-
- :param start: Start frequency.
- :type start: Quantity
- :param stop: Stop frequency.
- :type stop: Quantity
- :param step: Step frequency.
- :type step: Quantity
- :param wait: Waiting time.
- :type wait: Quantity
-
- """
- in_secs = wait.to('seconds').magnitude
- current = start
- while current < stop:
- inst.frequency = current
- time.sleep(in_secs)
- current += step
-
-
- if __name__ == '__main__':
- import argparse
-
- from lantz import Q_
-
- from mydriver import LantzSignalGeneratorTCP
-
- parser = argparse.ArgumentParser()
-
- # Configure
- parser.add_argument('-H', '--host', type=str, default='localhost',
- help='TCP hostname')
- parser.add_argument('-p', '--port', type=int, default=5678,
- help='TCP port')
-
- parser.add_argument('start', type=float,
- help='Start frequency [Hz]')
- parser.add_argument('stop', type=float,
- help='Stop frequency [Hz]')
- parser.add_argument('step', type=float,
- help='Step frequency [Hz]')
- parser.add_argument('wait', type=float,
- help='Waiting time at each step [s]')
-
- args = parser.parse_args()
-
- Hz = Q_(1, 'Hz')
- sec = Q_(1, 'sec')
-
- def print_change(new, old):
- print('Changed from {} to {}'.format(old, new))
-
- with LantzSignalGeneratorTCP(args.host, args.port) as inst:
- print(inst.idn)
-
- inst.frequency_changed.connect(print_change)
-
- scan_frequency(inst, args.start * Hz, args.stop * Hz,
- args.step * Hz, args.wait * sec)
-
-
-The first change you will notice is that we have now used a Quantity for the
-time. It might be meaningless as the script ask for the waiting time in
-seconds and the function used to wait (`time.sleep`) expects the time in
-seconds. But using a Quantity allows the caller of the function how the
-waiting is implemented.
-
-Also notice that we have removed the print statement from inside the function
-to be able to reuse it in other applications. For example, we might want to use
-it in a silent command line application or in a GUI application.
-To know that the frequency has changed we have connected a reporting function
-(`print_change`) to a signal (`frequency_changed`). Lantz will call the
-function every time that the frequency changes. Every Feat has an associated
-signal that can be accessed by appending `_changed` to the name.
-
-
.. rubric::
- If you have installed PyQt4 (or PySide) you can use Lantz helpers to build
- a GUI app.
Learn how in the next part of the tutorial: :ref:`tutorial-gui-app`.
diff --git a/docs/tutorial/gui-app.rst b/docs/tutorial/gui-app.rst
index e426eb6..7ef803e 100644
--- a/docs/tutorial/gui-app.rst
+++ b/docs/tutorial/gui-app.rst
@@ -4,9 +4,8 @@
A simple GUI app
================
-In this part of the tutorial you will build an application to do a frequency
-scan like in the previous tutorial (:ref:`tutorial-cli-app`) but with a
-graphical frontend.
+In this part of the tutorial you will build an application to custom function
+generator GUI:
Start the simulated instrument running the following command::
@@ -15,165 +14,42 @@ Start the simulated instrument running the following command::
Using Qt Designer, create a window like this:
.. image:: ../_static/tutorial/gui-app.png
- :alt: Scan frequency window.
-and save it as `scanfrequency.ui` in the folder in which you have created
+and save it as `fungen.ui` in the folder in which you have created
the driver (:ref:`tutorial-building`). For the example, we have labeled
each control as corresponding label in lower caps (amplitude, offset,
-waveform, start, stop, step, wait). The button is named `scan`.
+waveform). The button is named `scan`.
You can also download
-:download:`the ui file <../_static/tutorial/scanfrequency.ui>` if you prefer.
+:download:`the ui file <../_static/tutorial/fungen.ui>` if you prefer.
Notice that the `amplitude` and `offset` don't show units and that the `waveform`
combobox is not populated. These widgets will be connected to the corresponding
Feats of the drivers and Lantz will take care of setting the right values, items,
etc.
-Create a python file named `scanfrequency-gui.py` with the following content::
+Create a python file named `fungen-gui.py` with the following content:
- import sys
-
- # Import lantz.ui register an import hook that will replace calls to Qt by PyQt4 or PySide ...
- import lantz.ui
-
- # and here we just use Qt and will work with both bindings!
- from Qt.QtGui import QApplication, QWidget
- from Qt.uic import loadUi
+.. code-block:: python
- app = QApplication(sys.argv)
+ import sys
- # Load the UI from the QtDesigner file. You can also use pyuic4 to generate a class.
- main = loadUi('scanfrequency.ui')
+ # Import from a lantz the start_gui helper function
+ from lantz.ui.app import start_gui
- # Start the app
- main.show()
- exit(app.exec_())
+ #
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst:
+ start_gui('fungen.ui', inst)
-Run this stub should display the window::
+Run it and enjoy::
$ python scanfrequency-gui.py
.. note:: In Windows, you can use `pythonw` instead of `python` to suppress the
terminal window.
-We will now add the code to do the actual frequency scan. We will reuse the
-function from the last tutorial::
-
- import sys
-
- # Import lantz.ui register an import hook that will replace calls to Qt by PyQt4 or PySide ...
- import lantz.ui
- from lantz import Q_
-
- # and here we just use Qt and will work with both bindings!
- from Qt.QtGui import QApplication, QWidget
- from Qt.uic import loadUi
-
- # These imports are from your own project
- from mydriver import LantzSignalGeneratorTCP
- from scanfrequency import scan_frequency
-
- app = QApplication(sys.argv)
-
- # Load the UI from the QtDesigner file. You can also use pyuic4 to generate a class.
- main = loadUi('scanfrequency.ui')
-
- Hz = Q_(1, 'Hz')
- sec = Q_(1, 'second')
-
- with LantzSignalGeneratorTCP('localhost', 5678) as inst:
-
- # Obtain a reference to the widgets controlling the scan parameters
- start = main.findChild((QWidget, ), 'start')
- stop = main.findChild((QWidget, ), 'stop')
- step = main.findChild((QWidget, ), 'step')
- wait = main.findChild((QWidget, ), 'wait')
- scan = main.findChild((QWidget, ), 'scan')
-
- # Define a function to read the values from the widget and call scan_frequency
- def scan_clicked():
- scan_frequency(inst, start.value() * Hz, stop.value() * Hz,
- step.value() * Hz, wait.value() * sec)
-
- # Connect the clicked signal of the scan button to the function
- scan.clicked.connect(scan_clicked)
-
- # Scan the app
- main.show()
- exit(app.exec_())
-
-When the button is clicked, Qt will emit a signal which is connected to the
-function we have defined the application should scan the frequency. You will
-not see anything happening in the Window, but if you look in the simulator
-console you will see the frequency changing.
-
-
-Connecting widgets to Feats
----------------------------
-
-To allow the user to change the amplitude, offset, shape and frequency, we will
-connect the configuration widgets::
-
- import sys
-
- # Import lantz.ui register an import hook that will replace calls to Qt by PyQt4 or PySide ...
- import lantz.ui
- from lantz import Q_
-
- # and here we just use Qt and will work with both bindings!
- from Qt.QtGui import QApplication, QWidget
- from Qt.uic import loadUi
-
- # Import from lantz a function to connect drivers to UI <--- NEW
- from lantz.ui.widgets import connect_driver
-
- # These imports are from your own project
- from mydriver import LantzSignalGeneratorTCP
- from scanfrequency import scan_frequency
-
- app = QApplication(sys.argv)
-
- # Load the UI from the QtDesigner file. You can also use pyuic4 to generate a class.
- main = loadUi('scanfrequency.ui')
-
- Hz = Q_(1, 'Hz')
- sec = Q_(1, 'second')
-
- with LantzSignalGeneratorTCP('localhost', 5678) as inst:
-
- # Obtain a reference to the widgets controlling the scan parameters
- start = main.findChild((QWidget, ), 'start')
- stop = main.findChild((QWidget, ), 'stop')
- step = main.findChild((QWidget, ), 'step')
- wait = main.findChild((QWidget, ), 'wait')
- scan = main.findChild((QWidget, ), 'scan')
-
- # <--------- This is new --------->
- connect_driver(main, inst)
-
- progress = main.findChild((QWidget, ), 'progress')
-
- def update_progress_bar(new, old):
- fraction = (new.magnitude - start.value()) / (stop.value() - start.value())
- progress.setValue(fraction * 100)
-
- inst.frequency_changed.connect(update_progress_bar)
+:func:start_gui take at leas two arguments. First, the fullpath of an QtDesigner ui file. As a second argument, an instrument instance.
-
- # Define a function to read the values from the widget and call scan_frequency
- def scan_clicked():
- scan_frequency(inst, start.value() * Hz, stop.value() * Hz,
- step.value() * Hz, wait.value() * sec)
-
- # Connect the clicked signal of the scan button to the function
- scan.clicked.connect(scan_clicked)
-
- # Scan the app
- main.show()
- exit(app.exec_())
-
-The function `connect_driver` matches by name Widgets to Feats and then connects
-them. Under the hood, for each match it:
+Under the hood, `start_gui` is creating a Qt Application and loading the ui file. Then it matches by name Widgets to Feats and then connects them. Under the hood, for each match it:
1.- Wraps the widget to make it Lantz compatible.
@@ -183,109 +59,8 @@ them. Under the hood, for each match it:
4.- Add a handler such as when the Feat value is changed, the widget is updated.
-You can learn more and some alternatives in :ref:`ui-driver`.
-
-To update the progress bar, we connected the `frequency_changed` signal to a
-function that updates the progress bar.
-
-Run this example and test how you can change the amplitude, offset and waveform::
-
- $ python scanfrequency-gui.py
-
-However, you will see that the frequency and the progress bar are not updated
-during the scan.
-
-Using a background thread
--------------------------
-
-The drawback of the previous (simple) approach is that the scan is executed in the same
-thread as the GUI, effectively locking the main window and making the application
-unresponsive. Qt Multithreading programming is out of the scope of this tutorial
-(checkout `Threads in Qt`_ for more info), but we will provide some examples
-how to do it::
-
- import sys
-
- # Import lantz.ui register an import hook that will replace calls to Qt by PyQt4 or PySide ...
- import lantz.ui
- from lantz import Q_
-
- # Import from lantz a function to connect drivers to UI
- from lantz.ui.widgets import connect_driver
-
- # and here we just use Qt and will work with both bindings!
- from Qt.QtGui import QApplication, QWidget
- from Qt.uic import loadUi
-
- # We import
- from Qt.QtCore import QThread, QObject
-
- # These imports are from your own project
- from mydriver import LantzSignalGeneratorTCP
- from scanfrequency import scan_frequency
-
- app = QApplication(sys.argv)
-
- # Load the UI from the QtDesigner file. You can also use pyuic4 to generate a class.
- main = loadUi('scanfrequency.ui')
-
- Hz = Q_(1, 'Hz')
- sec = Q_(1, 'second')
-
- with LantzSignalGeneratorTCP('localhost', 5678) as inst:
-
- # Connect the main panel widgets to the instruments Feats,
- # matching by name
- connect_driver(main, inst)
-
- # Obtain a reference to the widgets controlling the scan parameters
- start = main.findChild((QWidget, ), 'start')
- stop = main.findChild((QWidget, ), 'stop')
- step = main.findChild((QWidget, ), 'step')
- wait = main.findChild((QWidget, ), 'wait')
- scan = main.findChild((QWidget, ), 'scan')
- progress = main.findChild((QWidget, ), 'progress')
-
- def update_progress_bar(new, old):
- fraction = (new.magnitude - start.value()) / (stop.value() - start.value())
- progress.setValue(fraction * 100)
-
- inst.frequency_changed.connect(update_progress_bar)
-
- # <--------- New code--------->
- # Define a function to read the values from the widget and call scan_frequency
- class Scanner(QObject):
-
- def scan(self):
- # Call the scan frequency
- scan_frequency(inst, start.value() * Hz, stop.value() * Hz,
- step.value() * Hz, wait.value() * sec)
- # When it finishes, set the progress to 100%
- progress.setValue(100)
-
- thread = QThread()
- scanner = Scanner()
- scanner.moveToThread(thread)
- thread.start()
-
- # Connect the clicked signal of the scan button to the function
- scan.clicked.connect(scanner.scan)
-
- app.aboutToQuit.connect(thread.quit)
- # <--------- End of new code --------->
-
- main.show()
- exit(app.exec_())
-
-In Qt, when a signal is connected to a slot (a function of a QObject),
-the execution occurs in the Thread of the receiver (not the emitter).
-That is why we moved the QObject to the new thread.
-
-.. note::
- On a production app it would be good to add a lock to prevent the application
- from exiting or calling the scanner while a scanning is running.
-
-
+You can learn more fine grained alternatives in :ref:`ui-driver`.
-.. _`Thread in Qt`: http://doc.qt.digia.com/4.7/threads.html
+.. rubric::
+ Learn how in the next part of the tutorial: :ref:`tutorial-gui-app`.
diff --git a/docs/tutorial/index.rst b/docs/tutorial/index.rst
index 53d2134..7524d9f 100644
--- a/docs/tutorial/index.rst
+++ b/docs/tutorial/index.rst
@@ -13,4 +13,5 @@ Tutorials
building
cli-app
gui-app
+ rich-app
diff --git a/docs/tutorial/installing.rst b/docs/tutorial/installing.rst
index f30aaaf..9741a5f 100644
--- a/docs/tutorial/installing.rst
+++ b/docs/tutorial/installing.rst
@@ -4,13 +4,20 @@ Installation guide
==================
This guide describes Lantz requirements and provides platform specific
-installation guides. Examples are given for Python 3.2 installing all
+installation guides. Examples are given for Python 3.4 installing all
optional requirements as site-packages.
Requirements
------------
-Lantz core requires only `Python`_ 3.2+.
+Lantz core requires `Python`_ 3.4+ and:
+
+ - `PyVISA`_ Python package that enables you to control all kinds of measurement
+ devices independently of the interface (e.g. GPIB, RS232, USB, Ethernet) using
+ different backends.
+
+ - `Qt4`_ is used to generate the graphical user interfaces. Due to a license issue there
+ are two python bindings for Qt: `PyQt`_ and `PySide`_.
Optional requirements
@@ -34,9 +41,6 @@ packages, a link to the binary distribution is given. Specifi
- `pySerial`_ it is to communicate via serial port.
It is optional and only needed if you are using a driver that uses lantz.serial.
- - `Qt4`_ is used to generate the graphical user interfaces. Due to a license issue there
- are two python bindings for Qt: `PyQt`_ and `PySide`_.
-
- `NumPy`_ is used by many drivers to perform numerical calculations.
- `VISA`_ National Instruments Library for communicating via GPIB, VXI, PXI,
@@ -47,12 +51,16 @@ packages, a link to the binary distribution is given. Specifi
- :ref:`mac`
- :ref:`windows`
+
+
+.. note:: A really simple way that works in all OS is using Anaconda Python Distribution (see below)
+
.. _linux:
Linux
-----
-Most linux distributions provide packages for Python 3.2, NumPy, PyQt (or PySide).
+Most linux distributions provide packages for Python 3.4, NumPy, PyQt (or PySide).
There might be some other useful packages. For some distributions, you will find
specific instructions below.
@@ -91,17 +99,17 @@ and continue to to step 5 in OSX
OSX
---
-1. Install Python 3.2
+1. Install Python 3.4
2. (optionally) Install PyQt_, NumPy_
3. (optionally) Install VISA_
4. Open a terminal to install pip::
- $ curl http://python-distribute.org/distribute_setup.py | python3.2
- $ curl https://raw.github.com/pypa/pip/master/contrib/get-pip.py | python3.2
+ $ curl http://python-distribute.org/distribute_setup.py | python3.4
+ $ curl https://raw.github.com/pypa/pip/master/contrib/get-pip.py | python3.4
5. Using pip, install Lantz and its dependencies other optional dependencies::
- $ pip-3.2 install sphinx pyserial colorama lantz
+ $ pip-3.4 install sphinx pyserial colorama lantz
.. _windows:
@@ -114,7 +122,7 @@ Windows
We provide a simple script to run all the steps provided below. Download
`get-lantz`_ to the folder in which you want to create the virtual environment.
- The run the script using a 32 bit version of `Python`_ 3.2+.
+ The run the script using a 32 bit version of `Python`_ 3.4+.
In some of the steps, an installer application will pop-up. Just select all
default options.
@@ -125,14 +133,14 @@ Windows
Install `Python`_, `NumPy binaries`_, `PyQt binaries`_ (or `PySide binaries`), `VISA`_.
-Download and run with Python 3.2::
+Download and run with Python 3.4::
- http://python-distribute.org/distribute_setup.py
- https://raw.github.com/pypa/pip/master/contrib/get-pip.py
In the command prompt install using pip all other optional dependencies::
- $ C:\Python3.2\Scripts\pip install sphinx pyserial colorama lantz
+ $ C:\Python3.4\Scripts\pip install sphinx pyserial colorama lantz
.. _anaconda:
@@ -140,6 +148,10 @@ In the command prompt install using pip all other optional dependencies::
Anaconda
--------
+Anaconda is a Scientific Oriented Python Distribution. It can be installed in Linux, OSX and Windows; without administrator privileges. It has a binary package manager that makes it really easy to install all the packages that you need to use Lantz (and much more!)
+
+It comes in two flavors: miniconda and anaconda, which is is just miniconda with a lot of predefine packages. Here we show you how to do it with miniconda.
+
In any OS you can use Anaconda Python Distribution
1. Download and install the apropriate miniconda3_ for your OS.
@@ -151,16 +163,12 @@ In any OS you can use Anaconda Python Distribution
2. If you want a minimal environment::
- $ conda install pip numpy sphinx
+ $ conda install pip numpy sphinx pyqt
or if you want everything::
$ conda install anaconda
- 3. Install PyQt::
-
- $ conda install -c asmeurer pyqt
-
4. Install Lantz::
$ pip install colorama pyserial pyusb lantz
@@ -178,6 +186,7 @@ In any OS you can use Anaconda Python Distribution
.. _Sphinx: http://sphinx.pocoo.org/
.. _Docutils: http://docutils.sourceforge.net/
.. _pySerial: http://pyserial.sourceforge.net/
+.. _PyVISA: http://pyvisa.readthedocs.org/
.. _pySerial binaries: http://pyserial.sourceforge.net/pyserial.html#packages
.. _Qt4: http://qt.nokia.com/products/
.. _PyQt: http://www.riverbankcomputing.co.uk/software/pyqt
@@ -187,7 +196,7 @@ In any OS you can use Anaconda Python Distribution
.. _NumPy: http://numpy.scipy.org/
.. _NumPy binaries: http://sourceforge.net/projects/numpy/files/
.. _Lantz at Github: https://github.com/hgrecco/lantz
-.. _get-lantz: https://raw.github.com/hgrecco/lantz/master/scripts/get-lantz.py
+.. _get-lantz: https://gist.github.com/hgrecco/bd1dc8560c01359a28ed
.. _Python: http://www.python.org/getit/
.. _VISA: http://www.ni.com/visa/
.. _git: http://git-scm.com/
diff --git a/docs/tutorial/rich-app.rst b/docs/tutorial/rich-app.rst
new file mode 100644
index 0000000..1944987
--- /dev/null
+++ b/docs/tutorial/rich-app.rst
@@ -0,0 +1,248 @@
+.. _tutorial-gui-rich-app:
+
+
+A rich GUI app
+==============
+
+Building a rich, responsive and reusable app is tough. In this part of the tutorial you learn how Lantz makes it simpler by build an application using Blocks and :class:Backend and :class:Frontend classes.
+
+This is the wish list for our application:
+
+ - Should measure with a voltmeter as we scan the frequency in a function generator.
+ - The user should be able to change the range and step of the frequency scan.
+ - The user should be able to cancel the scan at any moment.
+ - Plot the results live
+
+
+First, as we have done before start the simulated function generator running the following command::
+
+ $ lantz-sim fungen tcp
+
+We will also start simulated instrument running the following command in another terminal::
+
+ $ lantz-sim voltmeter tcp -p 5679
+
+Let's test our two instruments before start:
+
+.. code-block:: python
+
+ from lantz import Q_
+ from lantz.drivers.examples import LantzSignalGenerator, LantzVoltmeter
+
+ # We change the frequency of the function generator
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as fungen:
+ print(fungen.idn)
+ fungen.frequency = Q_(3.14, 'Hz')
+
+ # We measure the voltage in channel 0 of the voltmeter
+ with LantzVoltmeter('TCPIP::localhost::5679::SOCKET') as voltmeter:
+ print(voltmeter.idn)
+ print(voltmeter.voltage[0])
+
+
+Now that everything works, let's make the app!
+
+
+The scanner
+-----------
+
+Scanning (a frequency, a voltage, etc.) is a very common task in instrumentation applications. That is why lantz provide a building block (usually called just blocks) for this. It is called :class:FeatScan
+
+.. code-block:: python
+
+ # We import a helper function to start the app
+ from lantz.ui.app import start_gui_app
+
+ # The block consists of two parts the backend and the frontend
+ from lantz.ui.blocks import FeatScan, FeatScanUi
+
+ # An this you know already
+ from lantz.drivers.examples import LantzSignalGenerator
+
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as fungen:
+
+ # Here we instantiate the backend setting 'frequency' as the Feat to scan
+ # and specifying in which instrument
+ app = FeatScan('frequency', instrument=fungen)
+
+ # Now we use the helper to start the app.
+ # It takes a Backend instance and a FrontEnd class
+ start_gui_app(app, FeatScanUi)
+
+
+Save it, run it and how the Feat is scanned in the simulator. In no time you build a responsive and interactive application in which you can choose the start and stop value, the number of steps or the step size, the interval between calls like the one you see below:
+
+.. image:: ../_static/blocks/scanfeat.png
+ :alt: Feat Scanner User Interface
+
+Blocks are small, reusable, composable and easy to use. They do one thing, and they do it right. Combining multiple blocks you can build a complex and rich application. There are many available blocks and you can build your own. We will not go into details right now but it is only important that you know that blocks can derive from one of two classes: :class:Backend and :class:Frontend. The first contains the logic of your application and the second the GUI. This keeps things easier to test and develop. It also allows you to call you application without any GUI (for example from the command line) or with a different GUI (for example a debug GUI with more information).
+
+
+Measuring
+---------
+
+Now we need to measure in the voltmeter in each step. There is a really simple way to do it. A :class:FeatScan object exposes an attribute (`body`) in which you can hook a function. The function should take three arguments:
+
+ - counter: the step number from 0..N-1
+ - new_value: the feat value that was used
+ - overrun: a boolean indicating that executing the body is taking longer than the interval that you have allocated.
+
+
+.. code-block:: python
+
+ # We import a helper function to start the app
+ from lantz.ui.app import start_gui_app
+
+ # The block consists of two parts the backend and the frontend
+ from lantz.ui.blocks import FeatScan, FeatScanUi
+
+ # An this you know already
+ from lantz.drivers.examples import LantzSignalGenerator, LantzVoltmeter
+
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as fungen, \
+ LantzVoltmeter('TCPIP::localhost::5679::SOCKET') as voltmeter:
+
+ def measure(counter, new_value, overrun):
+ print(new_value, voltmeter.voltage[0])
+
+ app = FeatScan('frequency', instrument=fungen)
+
+ # Here we are telling the FeatScan backend to call the measure function
+ # in each scan step. It will build a
+ app.body = measure
+
+ # Now we use the helper to start the app.
+ # It takes a Backend instance and a FrontEnd class
+ start_gui_app(app, FeatScanUi)
+
+
+That's it! You can put in the body anything that you like: waiting, changing the scale, etc. The only rule is that the backend is not aware of the Frontend. So ... how are we going to plot?
+
+
+Composing an application
+------------------------
+
+We are going to create an application embedding the FeatScan. In the backend we will add an InstrumentSlot for the voltmeter: this tells Lantz that an instrument is necessary. We will also add a Signal to tell the GUI to plot new data.
+
+.. Note:: Why a signal? Qt Signals is a way of async communication between objects. It is a way in which one object can inform others that something has happend. A signal is emitted by an object and received by another in an slot (a function). You need connect signals and slots for this to happen. Why we cannot just call the frontend? If you call the frontend, the backend will not be able to do anything until the frontend finishes. Emitting a signal tells the frontend to do something but without disturbing the backend job.
+
+In the frontend, we will connect this signal to a Plot function (you will need pyqtgraph for this). Try installing it with::
+
+ $ pip install pyqtgraph
+
+
+.. code-block:: python
+
+ # We import a helper function to start the app
+ from lantz.ui.app import start_gui_app
+
+ # Import Qt modules from lantz (pyside and pyqt compatible)
+ from lantz.utils.qt import QtCore
+
+ # We import the FeatScan backend and
+ # the ChartUi a frontend with a chart.
+ # You require pyqtgraph for this.
+ from lantz.ui.blocks import FeatScan, FeatScanUi, ChartUi, HorizonalUi
+
+ from lantz.ui.app import Backend, start_gui_app, InstrumentSlot
+
+ # We first create our backend
+ class MyApp(Backend):
+
+ # We embed the FeatScan app.
+ # Notice that we put the class, not an instance of it.
+ scanner = FeatScan
+
+ # The app needs an instrument that will be available in the voltmeter attribute
+ # It also needs another instrument to scan, but this is included in FeatScan
+ voltmeter = InstrumentSlot
+
+ # This signal will be emitted when new data is available.
+ # The two values are the x and y values
+ new_data = QtCore.Signal(object, object)
+
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ # We assign the scanner body to our function.
+ self.scanner.body = self.measure
+
+ def measure(self, counter, new_value, overrun):
+
+ # We measure and we emit a signal.
+ # Remember that these values have units!!
+ self.new_data.emit(new_value, self.voltmeter.voltage[0])
+
+ # This will be our Frontend
+ # We inherite from HorizonalUi, which organizes the widgets automatically horizontally
+ class MyGui(HorizonalUi):
+
+ # We embed two existing Frontends. Notice that agian
+
+ # The FeatScanUi, which you know alredy.
+ # But we say that it will be using the scanner backend
+ # Notice that we put the class, not an instance of it.
+ scanui = FeatScanUi.using('scanner')
+
+ # The ChartUi, which plot a dataset point-by-point using pyqtgraph
+ chartui = ChartUi
+
+ # Here we tell HorizonalUi how we want to organize the widgets
+ # Notice that we need to put the names of the attributes as strings.
+
+ parts = ('scanui', # The FeatScanUi will be in the first colum
+ # and connected to the embedded scanner backend
+ 'chartui') # The ChartUI will be in the second column.
+
+ def connect_backend(self):
+
+ # This method is called after gui has been loaded (referenced in self.widget)
+ # and the backend is connected to the frontend (referenced in self.backend).
+ # In this case, we use it to connect the new_data signal of the backend
+ # with the plot function in ChartUi
+ self.backend.new_data.connect(self.chartui.plot)
+
+ # To clear the chart every time we start a new scan
+ # we connect the request start signal of the user interface
+ # to the clear method of the chart ui
+ self.scanui.request_start.connect(self.chartui.clear)
+
+ super().connect_backend()
+
+ # We define the labels and the units to use
+
+ # For the y axis, it is fixed.
+ self.chartui.ylabel = 'voltage'
+ self.chartui.yunits = 'V'
+
+ # For the x axis, depends on the feat selected by the user.
+ self.chartui.xlabel = self.backend.scanner.feat_name
+ self.chartui.xunits = self.backend.scanner.feat_units
+
+ # Notice that the units is not just a change to the label,
+ # it rescales the values that are shown in the plot.
+
+
+ if __name__ == '__main__':
+ # An this you know already
+ from lantz.drivers.examples import LantzSignalGenerator, LantzVoltmeter
+
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as fungen, \
+ LantzVoltmeter('TCPIP::localhost::5679::SOCKET') as voltmeter:
+
+ app = MyApp(instrument=fungen, voltmeter=voltmeter,
+ scanner={'feat_name': 'frequency'})
+
+ # Now we use the helper to start the app.
+ # It takes a Backend instance and a FrontEnd class
+ start_gui_app(app, MyGui)
+
+
+Run it and you will see something like this:
+
+.. image:: ../_static/tutorial/rich-gui-app.png
+ :alt: Feat Scanner User Interface with plot
+
+
+There is much more to know. Hopefully this tutorial get's you started.
diff --git a/docs/tutorial/using-feats.rst b/docs/tutorial/using-feats.rst
index 5c4688c..fed36b2 100644
--- a/docs/tutorial/using-feats.rst
+++ b/docs/tutorial/using-feats.rst
@@ -3,11 +3,13 @@
Using Feats
===========
-Let's query all parameters and print their state in a nice format::
+Let's query all parameters and print their state in a nice format:
- from lantz.drivers.examples import LantzSignalGeneratorTCP
+.. code-block:: python
- with LantzSignalGeneratorTCP('localhost', 5678) as inst:
+ from lantz.drivers.examples import LantzSignalGenerator
+
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst:
print('idn: {}'.format(inst.idn))
print('frequency: {}'.format(inst.frequency))
print('amplitude: {}'.format(inst.amplitude))
@@ -48,21 +50,23 @@ If you run the program you will get something like::
Valid values
------------
-You can set property like `output_enabled`::
+You can set property like `output_enabled`:
+
+.. code-block:: python
- from lantz.drivers.examples import LantzSignalGeneratorTCP
+ from lantz.drivers.examples import LantzSignalGenerator
- with LantzSignalGeneratorTCP('localhost', 5678) as inst:
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst:
print('output_enabled: {}'.format(inst.output_enabled))
inst.output_enabled = True
print('output_enabled: {}'.format(inst.output_enabled))
-If you check the documentation for lantz.drivers.examples.LantzSignalGeneratorTCP),
+If you check the documentation for lantz.drivers.examples.LantzSignalGenerator),
`output_enabled` accepts only `True` or `False`. If your provide a different value::
- from lantz.drivers.examples import LantzSignalGeneratorTCP
+ from lantz.drivers.examples import LantzSignalGenerator
- with LantzSignalGeneratorTCP('localhost', 5678) as inst:
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst:
inst.output_enabled = 'Yes'
you will get an error message::
@@ -77,23 +81,27 @@ Units
-----
Feats corresponding to physical quantities (magnitude and units), are declared
-with a default unit. If try to set a number to them::
+with a default unit. If try to set a number to them:
+
+.. code-block:: python
- from lantz.drivers.examples import LantzSignalGeneratorTCP
+ from lantz.drivers.examples import LantzSignalGenerator
- with LantzSignalGeneratorTCP('localhost', 5678) as inst:
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst:
inst.amplitude = 1
Lantz will issue a warning::
DimensionalityWarning: Assuming units `volt` for 1
-Lantz uses the Pint_ package to declare units::
+Lantz uses the Pint_ package to declare units:
- from lantz.drivers.examples import LantzSignalGeneratorTCP
+.. code-block:: python
+
+ from lantz.drivers.examples import LantzSignalGenerator
from lantz import Q_
- with LantzSignalGeneratorTCP('localhost', 5678) as inst:
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst:
inst.amplitude = Q_(1, 'Volts')
print('amplitude: {}'.format(inst.amplitude))
@@ -103,12 +111,14 @@ the output is::
The nice thing is that this will work even if the instruments and you program
opeate in different units. The conversion is done internally, minimizing errors
-and allowing better interoperability::
+and allowing better interoperability:
+
+.. code-block:: python
- from lantz.drivers.examples import LantzSignalGeneratorTCP
+ from lantz.drivers.examples import LantzSignalGenerator
from lantz import Q_
- with LantzSignalGeneratorTCP('localhost', 5678) as inst:
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst:
inst.amplitude = Q_(.1, 'decivolt')
print('amplitude: {}'.format(inst.amplitude))
diff --git a/docs/tutorial/using.rst b/docs/tutorial/using.rst
index c7aaa12..cc945a4 100644
--- a/docs/tutorial/using.rst
+++ b/docs/tutorial/using.rst
@@ -12,47 +12,57 @@ Following a tutorial about using a driver to communicate with an instrument that
.. note::
If you are using Windows, it is likely that `lantz-sim` script is not be in
- the path. You will have to change directory to `C:\\Python32\\Scripts` or
+ the path. You will have to change directory to `C:\\Python34\\Scripts` or
something similar.
This will start an application (i.e. your instrument) that listens for incoming TCP packages (commands) on port 5678 from `localhost`. In the screen you will see the commands received and sent by the instrument.
Your program and the instrument will communicate by exchanging text commands via TCP. But having a Lantz driver already built for your particular instrument releases you for the burden of sending and receiving the messages. Let's start by finding the driver. Lantz drivers are organized inside packages, each package named after the manufacturer. So the `Coherent Argon Laser Innova` 300C driver is in `lantz.drivers.coherent` under the name `ArgonInnova300C`. We follow Python style guide (PEP8) to name packages and modules (lowercase) and classes (CamelCase).
-Make a new folder for your project and create inside a python script named `test_fungen.py`. Copy the following code inside the file::
+Make a new folder for your project and create inside a python script named `test_fungen.py`. Copy the following code inside the file:
- from lantz.drivers.examples import LantzSignalGeneratorTCP
+.. code-block:: python
- inst = LantzSignalGeneratorTCP('localhost', 5678)
+ from lantz.drivers.examples import LantzSignalGenerator
+
+ inst = LantzSignalGenerator('TCPIP::localhost::5678::SOCKET')
inst.initialize()
print(inst.idn)
inst.finalize()
Let's look at the code line-by-line. First we import the class into our script::
- from lantz.drivers.examples import LantzSignalGeneratorTCP
+ from lantz.drivers.examples import LantzSignalGenerator
+
+Instead of memorizing lot of text based commands and fighting with formatting and parsing, Lantz provides an **object oriented layer** to instrumentation. In this case, the driver for our simulated device is under the company `examples` and is named `LantzSignalGenerator`.
+
+Then we create an instance of the class::
-The driver for our simulated device is under the company `examples` and is named `LantzSignalGeneratorTCP`.
-Then we create an instance of the class, setting the address to localhost and port to 5678::
+ inst = LantzSignalGenerator('TCPIP::localhost::5678::SOCKET')
- inst = LantzSignalGeneratorTCP('localhost', 5678)
+The string 'TCPIP::localhost::5678::SOCKET' is called the **Resource Name** and is specified by the `VISA specification`_. It specifies the how you connect to your device. Lantz uses the power of VISA through PyVISA_ where you can also find a description of the :ref:`pyvisa:resource_names`.
-This does not connects to the device. To do so, you call the `initialize` method::
+Notice that is the resource name, not the driver what specifies the connectivity. This allows easy programming
+of instruments supporting multiple protocols.
+
+We then connect to the device by calling the :meth:`initialize` method::
inst.initialize()
-All Lantz drivers have an `initialize` method. Drivers that communicate through a port (e.g. a Serial port) will open the port in this call. Then we query the instrument for it's identification and we print it::
+All Lantz drivers have an :meth:`initialize` method. Drivers that communicate through a port (e.g. a Serial port) will open the port within this call. Then we query the instrument for it's identification and we print it::
print(inst.idn)
-At the end, we call the `finalize` method to clean up all resources (e.g. close ports)::
+At the end, we call the :meth:`finalize` method to clean up all resources (e.g. close ports)::
inst.finalize()
-Just like the `initialize` method, all Lantz drivers have a `finalize`. Save the python script and run it by::
+Just like the :meth:`initialize` method, all Lantz drivers have a :meth:`finalize`. Save the python script and run it by::
$ python test_fungen.py
+(You can also run it in the python console)
+
.. note:: If you have different versions of python installed, remember to use
the one in which you have installed Lantz. You might need to use
`python3` instead of `python`.
@@ -61,27 +71,31 @@ and you will get the following output::
FunctionGenerator Serial #12345
-In the window where `sim-fungen.py` is running you will see the message exchange. You normally don't see this in real instruments. Having a simulated instrument allow us to peek into it and understand what is going on: when we called `inst.idn`, the driver sent message (`?IDN`) to the instrument and it answered back (`FunctionGenerator Serial #12345`). Notice that end of line characters were stripped by the driver.
+In the window where `lantz-sim` is running you will see the message exchange. You normally don't see this in real instruments. Having a simulated instrument allow us to peek into it and understand what is going on: when we called `inst.idn`, the driver sent message (`?IDN`) to the instrument and it answered back (`FunctionGenerator Serial #12345`). Notice that end of line characters were stripped by the driver.
-To find out which other properties and methods are available checkout the documentation. A nice feature of Lantz (thanks to sphinx) is that useful documentation is generated from the driver itself. `idn` is a `Feat` of the driver. Think of a `Feat` as a pimped property. It works just like python properties but it wraps its call with some utilities (more on this later). `idn` is a read-only and as the documentation states it gets the identification information from the device.
+To find out which other properties and methods are available checkout the documentation. A nice feature of Lantz (thanks to sphinx) is that useful documentation is generated from the driver itself. `idn` is a `Feat` of the driver. Think of a `Feat` as a pimped :py:class:`property `. It works just like python properties but it wraps its call with some utilities (more on this later). `idn` is a read-only and as the documentation states it gets the identification information from the device. We will see more about this later on when we start :ref:`tutorial-building`
+
+.. _Safely-releasing-resources:
Safely releasing resources
--------------------------
As `idn` is read-only, the following code will raise an exception::
- from lantz.drivers.examples import LantzSignalGeneratorTCP
+ from lantz.drivers.examples import LantzSignalGenerator
- inst = LantzSignalGeneratorTCP('localhost', 5678)
+ inst = LantzSignalGenerator('TCPIP::localhost::5678::SOCKET')
inst.initialize()
inst.idn = 'A new identification' # <- This will fail as idn is read-only
inst.finalize()
-The problem is that finalize will never be called possibly leaving resources open. You need to wrap your possible failing code into a try-except-finally structure::
+The problem is that finalize will never be called possibly leaving resources open. You need to wrap your possible failing code into a try-except-finally structure:
+
+.. code-block:: python
- from lantz.drivers.examples import LantzSignalGeneratorTCP
+ from lantz.drivers.examples import LantzSignalGenerator
- inst = LantzSignalGeneratorTCP('localhost', 5678)
+ inst = LantzSignalGenerator('TCPIP::localhost::5678::SOCKET')
inst.initialize()
try:
inst.idn = 'A new identification' # <- This will fail as idn is read-only
@@ -90,11 +104,13 @@ The problem is that finalize will never be called possibly leaving resources ope
finally:
inst.finalize()
-All lantz drivers are also context managers and there fore you can write this in a much more compact way::
+All lantz drivers are also context managers and there fore you can write this in a much more compact way:
- from lantz.drivers.examples import LantzSignalGeneratorTCP
+.. code-block:: python
- with LantzSignalGeneratorTCP('localhost', 5678) as inst:
+ from lantz.drivers.examples import LantzSignalGenerator
+
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst:
# inst.initialize is called as soon as you enter this block
inst.idn = 'A new identification' # <- This will fail as idn is read-only
# inst.finalize is called as soon as you leave this block,
@@ -111,40 +127,48 @@ At any point in your code you can obtain the root Lantz logger::
from lantz import LOGGER
But additionally, Lantz has some convenience functions to display the log
-output in a nice format::
+output in a nice format:
+
+.. code-block:: python
from lantz.log import log_to_screen, DEBUG, INFO, CRITICAL
- from lantz.drivers.examples import LantzSignalGeneratorTCP
+ from lantz.drivers.examples import LantzSignalGenerator
# This directs the lantz logger to the console.
log_to_screen(DEBUG)
- with LantzSignalGeneratorTCP('localhost', 5678) as inst:
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst:
print(inst.idn)
print(inst.waveform)
Run this script to see the generated log information (it should be colorized
in your screen)::
- 16:25:03 INFO Created LantzSignalGeneratorTCP0
- 16:25:03 DEBUG Opening port ('localhost', 5678)
- 16:25:03 INFO Getting idn
- 16:25:03 DEBUG Sending b'?IDN\n'
- 16:25:03 DEBUG Received 'FunctionGenerator Serial #12345\n' (len=32)
- 16:25:03 DEBUG (raw) Got FunctionGenerator Serial #12345 for idn
- 16:25:03 INFO Got FunctionGenerator Serial #12345 for idn
+ 14:38:43 INFO Created LantzSignalGenerator0
+ 14:38:43 DEBUG Using MessageBasedDriver for TCPIP::localhost::5678::SOCKET
+ 14:38:43 INFO Calling initialize
+ 14:38:43 INFO initialize returned None
+ 14:38:43 DEBUG Opening resource TCPIP::localhost::5678::SOCKET
+ 14:38:43 DEBUG Setting [('write_termination', '\n'), ('read_termination', '\n')]
+ 14:38:43 INFO Getting idn
+ 14:38:43 DEBUG Writing '?IDN'
+ 14:38:43 DEBUG Read 'FunctionGenerator Serial #12345'
+ 14:38:43 DEBUG (raw) Got FunctionGenerator Serial #12345 for idn
+ 14:38:43 INFO Got FunctionGenerator Serial #12345 for idn
+ 14:38:43 INFO Getting waveform
+ 14:38:43 DEBUG Writing '?WVF'
+ 14:38:43 DEBUG Read '0'
+ 14:38:43 DEBUG (raw) Got 0 for waveform
+ 14:38:43 INFO Got sine for waveform
+ 14:38:43 DEBUG Closing resource TCPIP::localhost::5678::SOCKET
+ 14:38:43 INFO Calling finalize
+ 14:38:43 INFO finalize returned None
FunctionGenerator Serial #12345
- 16:25:03 INFO Getting waveform
- 16:25:03 DEBUG Sending b'?WVF\n'
- 16:25:03 DEBUG Received '0\n' (len=2)
- 16:25:03 DEBUG (raw) Got 0 for waveform
- 16:25:03 INFO Got sine for waveform
sine
- 16:25:03 DEBUG Closing port ('localhost', 5678)
The first line shows the creation of the driver instance. As no name was
-provided, Lantz assigns one (`LantzSignalGeneratorTCP0`). Line 2 shows that
+provided, Lantz assigns one (`LantzSignalGenerator0`). Line 2 shows that
the port was opened (in the implicit call to initialize in the `with` statement).
We then request the `idn` (line 3), which is done by sending the command via
the TCP port (line 4). 32 bytes are received from the instrument (line 5)
@@ -160,19 +184,20 @@ the `with` block).
The lines without the time are the result of the print function.
-Change `INFO` to `DEBUG` or to `CRITICAL` and run it again to see the different
+You can change the name of the instrument when you instantiate it.
+Also change `DEBUG` to `INFO` run it again to see the different
levels of information you can get.
-You can change the name of the instrument when you instantiate it::
+.. code-block:: python
from lantz.log import log_to_screen, DEBUG, INFO, CRITICAL
- from lantz.drivers.examples import LantzSignalGeneratorTCP
+ from lantz.drivers.examples import LantzSignalGenerator
# This directs the lantz logger to the console.
- log_to_screen(DEBUG)
+ log_to_screen(INFO)
- with LantzSignalGeneratorTCP('localhost', 5678) as inst:
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET', name='my-device') as inst:
print(inst.idn)
print(inst.waveform)
@@ -181,16 +206,18 @@ The cache
---------
As you have seen before, logging provides a look into the Lantz internals.
-Let's duplicate some code::
+Let's duplicate some code:
+
+.. code-block:: python
from lantz.log import log_to_screen, DEBUG
- from lantz.drivers.examples import LantzSignalGeneratorTCP
+ from lantz.drivers.examples import LantzSignalGenerator
# This directs the lantz logger to the console.
log_to_screen(DEBUG)
- with LantzSignalGeneratorTCP('localhost', 5678) as inst:
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst:
print(inst.idn)
print(inst.idn)
print(inst.waveform)
@@ -198,90 +225,112 @@ Let's duplicate some code::
If you see the log output::
- 16:34:40 INFO Created LantzSignalGeneratorTCP0
- 16:34:40 DEBUG Opening port ('localhost', 5678)
- 16:34:40 INFO Getting idn
- 16:34:40 DEBUG Sending b'?IDN\n'
- 16:34:40 DEBUG Received 'FunctionGenerator Serial #12345\n' (len=32)
- 16:34:40 DEBUG (raw) Got FunctionGenerator Serial #12345 for idn
- 16:34:40 INFO Got FunctionGenerator Serial #12345 for idn
+ 14:42:32 INFO Created LantzSignalGenerator0
+ 14:42:32 DEBUG Using MessageBasedDriver for TCPIP::localhost::5678::SOCKET
+ 14:42:32 INFO Calling initialize
+ 14:42:32 INFO initialize returned None
+ 14:42:32 DEBUG Opening resource TCPIP::localhost::5678::SOCKET
+ 14:42:32 DEBUG Setting [('read_termination', '\n'), ('write_termination', '\n')]
+ 14:42:32 INFO Getting idn
+ 14:42:32 DEBUG Writing '?IDN'
+ 14:42:32 DEBUG Read 'FunctionGenerator Serial #12345'
+ 14:42:32 DEBUG (raw) Got FunctionGenerator Serial #12345 for idn
+ 14:42:32 INFO Got FunctionGenerator Serial #12345 for idn
+ 14:42:32 INFO Getting waveform
+ 14:42:32 DEBUG Writing '?WVF'
+ 14:42:32 DEBUG Read '0'
+ 14:42:32 DEBUG (raw) Got 0 for waveform
+ 14:42:32 INFO Got sine for waveform
+ 14:42:32 INFO Getting waveform
+ 14:42:32 DEBUG Writing '?WVF'
+ 14:42:32 DEBUG Read '0'
+ 14:42:32 DEBUG (raw) Got 0 for waveform
+ 14:42:32 INFO Got sine for waveform
+ 14:42:32 DEBUG Closing resource TCPIP::localhost::5678::SOCKET
+ 14:42:32 INFO Calling finalize
+ 14:42:32 INFO finalize returned None
FunctionGenerator Serial #12345
FunctionGenerator Serial #12345
- 16:34:40 INFO Getting waveform
- 16:34:40 DEBUG Sending b'?WVF\n'
- 16:34:40 DEBUG Received '0\n' (len=2)
- 16:34:40 DEBUG (raw) Got 0 for waveform
- 16:34:40 INFO Got sine for waveform
sine
- 16:34:40 INFO Getting waveform
- 16:34:40 DEBUG Sending b'?WVF\n'
- 16:34:40 DEBUG Received '0\n' (len=2)
- 16:34:40 DEBUG (raw) Got 0 for waveform
- 16:34:40 INFO Got sine for waveform
sine
- 16:34:40 DEBUG Closing port ('localhost', 5678)
`idn` is only requested once, but waveform twice as you except. The reason
is that `idn` is marked `read_once` in the driver as it does not change.
The value is cached, preventing unnecessary communication with the instrument.
-The cache is specially useful with setters::
+The cache is specially useful with setters:
+
+.. code-block:: python
from lantz.log import log_to_screen, DEBUG
- from lantz.drivers.examples import LantzSignalGeneratorTCP
+ from lantz.drivers.examples import LantzSignalGenerator
# This directs the lantz logger to the console.
log_to_screen(DEBUG)
- with LantzSignalGeneratorTCP('localhost', 5678) as inst:
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst:
inst.waveform = 'sine'
inst.waveform = 'sine'
the log output::
- 16:40:08 INFO Created LantzSignalGeneratorTCP0
- 16:40:08 DEBUG Opening port ('localhost', 5678)
- 16:40:08 INFO Setting waveform = sine (current=MISSING, force=False)
- 16:40:08 DEBUG (raw) Setting waveform = 0
- 16:40:08 DEBUG Sending b'!WVF 0\n'
- 16:40:08 DEBUG Received 'OK\n' (len=3)
- 16:40:08 INFO waveform was set to sine
- 16:40:08 INFO No need to set waveform = sine (current=sine, force=False)
- 16:40:08 DEBUG Closing port ('localhost', 5678)
+ 14:44:09 INFO Created LantzSignalGenerator0
+ 14:44:09 DEBUG Using MessageBasedDriver for TCPIP::localhost::5678::SOCKET
+ 14:44:09 INFO Calling initialize
+ 14:44:09 INFO initialize returned None
+ 14:44:09 DEBUG Opening resource TCPIP::localhost::5678::SOCKET
+ 14:44:09 DEBUG Setting [('write_termination', '\n'), ('read_termination', '\n')]
+ 14:44:09 INFO Setting waveform = sine (current=MISSING, force=False)
+ 14:44:09 DEBUG (raw) Setting waveform = 0
+ 14:44:09 DEBUG Writing '!WVF 0'
+ 14:44:09 DEBUG Read 'OK'
+ 14:44:09 INFO waveform was set to sine
+ 14:44:09 INFO No need to set waveform = sine (current=sine, force=False)
+ 14:44:09 DEBUG Closing resource TCPIP::localhost::5678::SOCKET
+ 14:44:09 INFO Calling finalize
+ 14:44:09 INFO finalize returned None
Lantz prevents setting the waveform to the same value, a useful feature to speed
up communication with instruments in programs build upon decoupled parts.
If you have a good reason to force the change of the value, you can do it with
-the `update` method::
+the `update` method:
+
+.. code-block:: python
from lantz.log import log_to_screen, DEBUG, INFO, CRITICAL
- from lantz.drivers.examples import LantzSignalGeneratorTCP
+ from lantz.drivers.examples import LantzSignalGenerator
# This directs the lantz logger to the console.
log_to_screen(DEBUG)
- with LantzSignalGeneratorTCP('localhost', 5678) as inst:
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst:
inst.waveform = 'sine'
inst.update(waveform='sine', force=True)
the log output (notice `force=True`)::
-
- 16:41:03 INFO Created LantzSignalGeneratorTCP0
- 16:41:03 DEBUG Opening port ('localhost', 5678)
- 16:41:03 INFO Setting waveform = sine (current=MISSING, force=False)
- 16:41:03 DEBUG (raw) Setting waveform = 0
- 16:41:03 DEBUG Sending b'!WVF 0\n'
- 16:41:03 DEBUG Received 'OK\n' (len=3)
- 16:41:03 INFO waveform was set to sine
- 16:41:03 INFO Setting waveform = sine (current=sine, force=True)
- 16:41:03 DEBUG (raw) Setting waveform = 0
- 16:41:03 DEBUG Sending b'!WVF 0\n'
- 16:41:03 DEBUG Received 'OK\n' (len=3)
- 16:41:03 INFO waveform was set to sine
- 16:41:03 DEBUG Closing port ('localhost', 5678)
+
+ 14:44:44 INFO Created LantzSignalGenerator0
+ 14:44:44 DEBUG Using MessageBasedDriver for TCPIP::localhost::5678::SOCKET
+ 14:44:44 INFO Calling initialize
+ 14:44:44 INFO initialize returned None
+ 14:44:44 DEBUG Opening resource TCPIP::localhost::5678::SOCKET
+ 14:44:44 DEBUG Setting [('read_termination', '\n'), ('write_termination', '\n')]
+ 14:44:44 INFO Setting waveform = sine (current=MISSING, force=False)
+ 14:44:44 DEBUG (raw) Setting waveform = 0
+ 14:44:44 DEBUG Writing '!WVF 0'
+ 14:44:44 DEBUG Read 'OK'
+ 14:44:44 INFO waveform was set to sine
+ 14:44:44 INFO Setting waveform = sine (current=sine, force=True)
+ 14:44:44 DEBUG (raw) Setting waveform = 0
+ 14:44:44 DEBUG Writing '!WVF 0'
+ 14:44:44 DEBUG Read 'OK'
+ 14:44:44 INFO waveform was set to sine
+ 14:44:44 DEBUG Closing resource TCPIP::localhost::5678::SOCKET
+ 14:44:44 INFO Calling finalize
+ 14:44:44 INFO finalize returned None
Cache related methods: update, refresh and recall
@@ -333,16 +382,18 @@ to get all values.
In some cases you need the value of some attribute of the instrument that
you have not changed since the last time you got/set. The `recall` method returns
-the value stored in the cache::
+the value stored in the cache:
+
+.. code-block:: python
from lantz.log import log_to_screen, DEBUG
- from lantz.drivers.examples import LantzSignalGeneratorTCP
+ from lantz.drivers.examples import LantzSignalGenerator
# This directs the lantz logger to the console.
log_to_screen(DEBUG)
- with LantzSignalGeneratorTCP('localhost', 5678) as inst:
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst:
print(inst.waveform)
print(inst.recall('waveform'))
@@ -350,3 +401,8 @@ the value stored in the cache::
.. rubric::
You can use the the driver that you have created in you projects.
Learn more in the next part of the tutorial: :ref:`tutorial-using-feats`.
+
+
+.. _`VISA specification`:
+ http://www.ivifoundation.org/Downloads/Specifications.htm
+.. _`PyVISA`: http://pyvisa.readthedocs.org/
diff --git a/examples/backend_frontend/example1-cli.py b/examples/backend_frontend/example1-cli.py
index ed6bd38..93f538b 100644
--- a/examples/backend_frontend/example1-cli.py
+++ b/examples/backend_frontend/example1-cli.py
@@ -11,7 +11,7 @@
- :copyright: 2014 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/examples/backend_frontend/example2-gui.py b/examples/backend_frontend/example2-gui.py
index 2ac54db..f04cb67 100644
--- a/examples/backend_frontend/example2-gui.py
+++ b/examples/backend_frontend/example2-gui.py
@@ -13,7 +13,7 @@
The User interface has two widgets connected to feats of the function generator
(amplitude and frequency) and a few controls to control the scan.
- :copyright: 2014 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/examples/backend_frontend/example3-cli2.py b/examples/backend_frontend/example3-cli2.py
index b27d750..738a68d 100644
--- a/examples/backend_frontend/example3-cli2.py
+++ b/examples/backend_frontend/example3-cli2.py
@@ -6,7 +6,7 @@
This example shows how to use a Backend with an embedded Backend.
(and apps WITHOUT graphical user interface).
- :copyright: 2014 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/examples/backend_frontend/example4-gui2.py b/examples/backend_frontend/example4-gui2.py
index bc854d1..e3d5da2 100644
--- a/examples/backend_frontend/example4-gui2.py
+++ b/examples/backend_frontend/example4-gui2.py
@@ -6,7 +6,7 @@
This example shows how to use a Frontend (with an embedded Frontend)
(and apps WITHOUT graphical user interface).
- :copyright: 2014 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/examples/backend_frontend/example5-config.py b/examples/backend_frontend/example5-config.py
index b3c1a91..76081c5 100644
--- a/examples/backend_frontend/example5-config.py
+++ b/examples/backend_frontend/example5-config.py
@@ -7,7 +7,7 @@
from an external file and
- :copyright: 2014 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/examples/backend_frontend/myapps.py b/examples/backend_frontend/myapps.py
index 38604e9..74f75de 100644
--- a/examples/backend_frontend/myapps.py
+++ b/examples/backend_frontend/myapps.py
@@ -5,7 +5,7 @@
Example backend and frontend for 2 different applications.
- :copyright: 2013 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/examples/scanfrequency-gui.py b/examples/scanfrequency-gui.py
index 301fbef..99cee00 100644
--- a/examples/scanfrequency-gui.py
+++ b/examples/scanfrequency-gui.py
@@ -6,7 +6,7 @@
This example shows how to program a GUI using Qt and lantz drivers, but
without the backend-frontend classes that simplifies app development.
- :copyright: 2014 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/examples/scanfrequency.py b/examples/scanfrequency.py
index bed5288..ed6482d 100644
--- a/examples/scanfrequency.py
+++ b/examples/scanfrequency.py
@@ -6,7 +6,7 @@
This example shows how to program a CLI using a lantz drivers, but
without the backend-frontend classes that simplifies app development.
- :copyright: 2014 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/examples/using_blocks/example1-simpleloop.py b/examples/using_blocks/example1-simpleloop.py
index 9f6d9f1..a161cb6 100644
--- a/examples/using_blocks/example1-simpleloop.py
+++ b/examples/using_blocks/example1-simpleloop.py
@@ -5,7 +5,7 @@
This example shows how to use the loop block backend and frontend.
- :copyright: 2014 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/examples/using_blocks/example2-plot.py b/examples/using_blocks/example2-plot.py
index a91e748..b65bf94 100644
--- a/examples/using_blocks/example2-plot.py
+++ b/examples/using_blocks/example2-plot.py
@@ -8,7 +8,7 @@
The gui incorporates a plot.
- :copyright: 2014 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/examples/using_blocks/myapps.py b/examples/using_blocks/myapps.py
index 0ee4dfa..5a62831 100644
--- a/examples/using_blocks/myapps.py
+++ b/examples/using_blocks/myapps.py
@@ -5,7 +5,7 @@
Example backend and frontend for 2 different applications.
- :copyright: 2013 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/lantz/__init__.py b/lantz/__init__.py
index 447464e..ec9046f 100644
--- a/lantz/__init__.py
+++ b/lantz/__init__.py
@@ -6,25 +6,16 @@
An automation and instrumentation toolkit with a clean, well-designed and
consistent interface.
- :copyright: 2012 by The Lantz Authors
+ :copyright: 2015 by The Lantz Authors
:license: BSD, see LICENSE for more details.
"""
-import os
-import subprocess
import pkg_resources
-__version__ = "unknown"
-try: # try to grab the commit version of our package
- __version__ = (subprocess.check_output(["git", "describe"],
- stderr=subprocess.STDOUT,
- cwd=os.path.dirname(os.path.abspath(__file__)))).strip()
-except: # on any error just try to grab the version that is installed on the system
- try:
- __version__ = pkg_resources.get_distribution('lantz').version
- except:
- pass # we seem to have a local copy without any repository control or installed without setuptools
- # so the reported version will be __unknown__
+try:
+ __version__ = pkg_resources.get_distribution('lantz').version
+except:
+ __version__ = "unknown"
from pint import UnitRegistry
ureg = UnitRegistry()
@@ -36,7 +27,9 @@
__all__ = ['Driver', 'Action', 'Feat', 'DictFeat', 'Q_']
-def run_pyroma(data):
+def _run_pyroma(data): # pragma: no cover
+ """Run pyroma (used to perform checks before releasing a new version).
+ """
import sys
from zest.releaser.utils import ask
if not ask("Run pyroma on the package before uploading?"):
@@ -50,3 +43,12 @@ def run_pyroma(data):
except ImportError:
if not ask("pyroma not available. Continue?"):
sys.exit(1)
+
+
+def test():
+ """Run all tests.
+
+ :return: a :class:`unittest.TestResult` object
+ """
+ from .testsuite import run
+ return run()
diff --git a/lantz/action.py b/lantz/action.py
index ba4b4bd..a86c74c 100644
--- a/lantz/action.py
+++ b/lantz/action.py
@@ -6,7 +6,7 @@
Implements the Action class to wrap driver bound methods with Lantz's
data handling, logging, timing.
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
@@ -77,7 +77,7 @@ def __init__(self, func=None, *, values=None, units=None, limits=None, procs=Non
'limits': limits,
'processors': procs}
self.func = func
- self.args = None
+ self.args = ()
def __call__(self, func):
self.func = func
@@ -102,7 +102,10 @@ def call(self, instance, *args, **kwargs):
# This part calls to the underlying function wrapping
# and timing, logging and error handling
with instance._lock:
- instance.log_info('Calling {} with ({}, {}))', name, args, kwargs)
+ if args or kwargs:
+ instance.log_info('Calling {} with ({}, {}))', name, args, kwargs)
+ else:
+ instance.log_info('Calling {}', name)
try:
values = inspect.getcallargs(self.func, *(instance, ) + args, **kwargs)
@@ -115,21 +118,21 @@ def call(self, instance, *args, **kwargs):
except Exception as e:
instance.log_error('While pre-processing ({}, {}) for {}: {}', args, kwargs, name, e)
raise e
- instance.log_debug('(raw) Setting {} = {}', name, t_values)
+
+ if args or kwargs:
+ instance.log_debug('(raw) Calling {} with {}', name, t_values)
try:
tic = time.time()
out = self.func(instance, *t_values)
+ instance.timing.add(name, time.time() - tic)
+ instance.log_info('{} returned {}', name, out)
+
+ return out
except Exception as e:
instance.log_error('While calling {} with {}. {}', name, t_values, e)
raise e
- instance.timing.add(name, time.time() - tic)
-
- instance.log_info('{} returned {}', name, out)
-
- return out
-
def pre_action(self, value, instance=None):
procs = _dget(self.action_processors, instance)
for processor in procs:
diff --git a/lantz/driver.py b/lantz/driver.py
index 56de466..803e9c6 100644
--- a/lantz/driver.py
+++ b/lantz/driver.py
@@ -5,26 +5,21 @@
Implements the Driver base class.
- :copyright: 2013 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
-import time
import copy
import atexit
import logging
import threading
-
from functools import wraps
from concurrent import futures
from collections import defaultdict
from .utils.qt import MetaQObject, SuperQObject, QtCore
-
from .feat import Feat, DictFeat, MISSING, FeatProxy
from .action import Action, ActionProxy
from .stats import RunningStats
-from .errors import LantzTimeoutError
-from .processors import ParseProcessor
from .log import get_logger
logger = get_logger('lantz.driver', False)
@@ -155,7 +150,7 @@ def __new__(cls, classname, bases, class_dict):
return super().__new__(cls, classname, bases, class_dict)
- def __init__(cls, classname, bases, class_dict):
+ def __init__(self, classname, bases, class_dict):
super().__init__(classname, bases, class_dict)
feats = {}
@@ -176,10 +171,10 @@ def __init__(cls, classname, bases, class_dict):
# We create async versions of each Action if it does not exists.
for key, action in actions.items():
- if not hasattr(cls, key + '_async'):
+ if not hasattr(self, key + '_async'):
async_action = repartial_submit(key)
async_action.__doc__ = '(Async) ' + action.__doc__ if action.__doc__ else ''
- setattr(cls, key + '_async', async_action)
+ setattr(self, key + '_async', async_action)
# We update the feat an actions dictionaries with the ones
# from the base clases
@@ -192,8 +187,8 @@ def __init__(cls, classname, bases, class_dict):
if isinstance(value, Action) and key not in actions:
actions[key] = value
- cls._lantz_features = feats
- cls._lantz_actions = actions
+ self._lantz_features = feats
+ self._lantz_actions = actions
_REGISTERED = defaultdict(int)
@@ -220,6 +215,8 @@ class Driver(SuperQObject, metaclass=_DriverType):
_lantz_features = {}
_lantz_actions = {}
+ __name = ''
+
def __new__(cls, *args, **kwargs):
inst = SuperQObject.__new__(cls)
name = kwargs.pop('name', None)
@@ -229,7 +226,7 @@ def __new__(cls, *args, **kwargs):
inst.__unfinished_tasks = 0
inst.timing = RunningStats()
- if hasattr(inst, 'name'):
+ if hasattr(inst, 'name') and inst.name:
pass
elif name:
inst.name = name
@@ -470,151 +467,6 @@ def actions(self):
return Proxy(self, self._lantz_actions, ActionProxy)
-class TextualMixin(object):
- """Mixin class for classes that communicate with instruments
- exchanging text messages.
-
- Ideally, transport classes should provide receive methods
- that support:
- 1. query the number of available bytes
- 2. read chunks of bytes (with and without timeout)
- 3. read until certain character is found (with and without timeout)
-
- Most transport layers support 1 and 2 but not all support 3 (or only
- for a defined set of characters) and TextualMixin provides fallback
- a method.
- """
-
- #: Encoding to transform string to bytes and back as defined in
- #: http://docs.python.org/py3k/library/codecs.html#standard-encodings
- ENCODING = 'ascii'
- #: Termination characters for receiving data, if not given RECV_CHUNK
- #: number of bytes will be read.
- RECV_TERMINATION = ''
- #: Termination characters for sending data
- SEND_TERMINATION = ''
- #: Timeout in seconds of the complete read operation.
- TIMEOUT = 1
- #: Parsers
- PARSERS = {}
- #: Size in bytes of the receive chunk (-1 means all bytes in buffer)
- RECV_CHUNK = 1
-
- #: String containing the part of the message after RECV_TERMINATION
- #: Used in software based finding of termination character when
- #: RECV_CHUNK > 1
- _received = ''
-
- def raw_recv(self, size):
- """Receive raw bytes from the instrument. No encoding or termination
- character should be applied.
-
- This method must be implemented by base classes.
-
- :param size: number of bytes to receive.
- :return: received bytes, eom
- :rtype: bytes, bool
- """
- raise NotImplemented
-
- def raw_send(self, data):
- """Send raw bytes to the instrument. No encoding or termination
- character should be applied.
-
- This method must be implemented by base classes.
-
- :param data: bytes to be sent to the instrument.
- :param data: bytes.
- """
- raise NotImplemented
-
- def send(self, command, termination=None, encoding=None):
- """Send command to the instrument.
-
- :param command: command to be sent to the instrument.
- :type command: string.
-
- :param termination: termination character to override class defined
- default.
- :param encoding: encoding to transform string to bytes to override class
- defined default.
-
- :return: number of bytes sent.
-
- """
- if termination is None:
- termination = self.SEND_TERMINATION
- if encoding is None:
- encoding = self.ENCODING
-
- message = bytes(command + termination, encoding)
- self.log_debug('Sending {}', message)
- return self.raw_send(message)
-
- def recv(self, termination=None, encoding=None, recv_chunk=None):
- """Receive string from instrument.
-
- :param termination: termination character (overrides class default)
- :type termination: str
- :param encoding: encoding to transform bytes to string (overrides class default)
- :param recv_chunk: number of bytes to receive (overrides class default)
- :return: string encoded from received bytes
- """
-
- termination = termination or self.RECV_TERMINATION
- encoding = encoding or self.ENCODING
- recv_chunk = recv_chunk or self.RECV_CHUNK
-
- if not termination:
- return str(self.raw_recv(recv_chunk), encoding)
-
- if self.TIMEOUT is None or self.TIMEOUT < 0:
- stop = float('+inf')
- else:
- stop = time.time() + self.TIMEOUT
-
- received = self._received
- eom = False
- while not (termination in received or eom):
- if time.time() > stop:
- raise LantzTimeoutError
- raw_received = self.raw_recv(recv_chunk)
- received += str(raw_received, encoding)
-
- self.log_debug('Received {!r} (len={})', received, len(received))
-
- received, self._received = received.split(termination, 1)
-
- return received
-
- def query(self, command, *, send_args=(None, None), recv_args=(None, None)):
- """Send query to the instrument and return the answer
-
- :param command: command to be sent to the instrument
- :type command: string
-
- :param send_args: (termination, encoding) to override class defaults
- :param recv_args: (termination, encoding) to override class defaults
- """
-
- self.send(command, *send_args)
- return self.recv(*recv_args)
-
- def parse_query(self, command, *,
- send_args=(None, None), recv_args=(None, None),
- format=None):
- """Send query to the instrument, parse the output using format
- and return the answer.
-
- .. seealso:: TextualMixin.query and stringparser
- """
- ans = self.query(command, send_args=send_args, recv_args=recv_args)
- if format:
- parser = self.PARSERS.setdefault(format, ParseProcessor(format))
- ans = parser(ans)
- return ans
-
-
def _solve_dependencies(dependencies, all_members=None):
"""Solve a dependency graph.
diff --git a/lantz/drivers/__init__.py b/lantz/drivers/__init__.py
index e69de29..6897adb 100644
--- a/lantz/drivers/__init__.py
+++ b/lantz/drivers/__init__.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers
+ ~~~~~~~~~~~~~
+
+ Legacy drivers for different companies.
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
diff --git a/lantz/drivers/aa/__init__.py b/lantz/drivers/aa/__init__.py
index 8a0922e..9a2aa03 100644
--- a/lantz/drivers/aa/__init__.py
+++ b/lantz/drivers/aa/__init__.py
@@ -9,7 +9,7 @@
----
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/lantz/drivers/aa/aotf.py b/lantz/drivers/aa/aotf.py
index 7a831d0..73d0d87 100644
--- a/lantz/drivers/aa/aotf.py
+++ b/lantz/drivers/aa/aotf.py
@@ -16,16 +16,17 @@
- MDSnC Manual
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
#TODO: Implement calibrated power.
from lantz import Feat, DictFeat
-from lantz.serial import SerialDriver
+from lantz.messagebased import MessageBasedDriver
-class MDSnC(SerialDriver):
+
+class MDSnC(MessageBasedDriver):
"""MDSnC synthesizer for AOTF.nC
"""
@@ -35,31 +36,31 @@ class MDSnC(SerialDriver):
def main_enabled(self, value):
"""Enable the
"""
- self.send("I{}".format(value))
+ self.write("I{}".format(value))
@DictFeat(None, keys=CHANNELS)
def enabled(self, channel, value):
"""Enable single channels.
"""
- self.send("L{}O{}".format(channel, value))
+ self.write("L{}O{}".format(channel, value))
@DictFeat(None, keys=CHANNELS)
def frequency(self, channel, value):
"""RF frequency for a given channel.
"""
- self.send("L{}F{}".format(channel, value))
+ self.write("L{}F{}".format(channel, value))
@DictFeat(None, keys=CHANNELS)
def powerdb(self, channel, value):
"""Power for a given channel (in db).
"""
- self.send("L{}D{}".format(channel, value))
+ self.write("L{}D{}".format(channel, value))
@DictFeat(None, keys=CHANNELS, limits=(0, 1023, 1))
def power(self, channel, value):
"""Power for a given channel (in digital units).
"""
- self.send("L{}P{:04d}".format(channel, value))
+ self.write("L{}P{:04d}".format(channel, value))
if __name__ == '__main__':
@@ -74,7 +75,7 @@ def power(self, channel, value):
args = parser.parse_args()
lantz.log.log_to_socket(lantz.log.DEBUG)
- with MDSnC(args.port) as inst:
+ with MDSnC.from_serial_port(args.port) as inst:
if args.interactive:
from lantz.ui.app import start_test_app
start_test_app(inst)
diff --git a/lantz/drivers/aeroflex/__init__.py b/lantz/drivers/aeroflex/__init__.py
index ca766dd..225161c 100644
--- a/lantz/drivers/aeroflex/__init__.py
+++ b/lantz/drivers/aeroflex/__init__.py
@@ -9,7 +9,7 @@
----
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/lantz/drivers/aeroflex/a2023a.py b/lantz/drivers/aeroflex/a2023a.py
index 3395083..cb1b1c0 100644
--- a/lantz/drivers/aeroflex/a2023a.py
+++ b/lantz/drivers/aeroflex/a2023a.py
@@ -10,20 +10,20 @@
- Aeroflex 2023a Manual.
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from lantz import Feat, Action
-from lantz.serial import SerialDriver
-from lantz.processors import ParseProcessor
+from lantz.messagebased import MessageBasedDriver
-class A2023a(SerialDriver):
+
+class A2023a(MessageBasedDriver):
"""Aeroflex Test Solutions 2023A 9 kHz to 1.2 GHz Signal Generator.
"""
- RECV_TERMINATION = 256
- SEND_TERMINATION = '\n'
+ DEFAULTS = {'ASRL': {'write_termination': '\n',
+ 'read_termination': chr(256)}}
@Feat(read_once=True)
def idn(self):
@@ -42,7 +42,7 @@ def fitted_options(self):
def reset(self):
"""Set the instrument functions to the factory default power up state.
"""
- self.send('*RST')
+ self.write('*RST')
@Action()
def self_test(self):
@@ -55,13 +55,13 @@ def wait(self):
"""Inhibit execution of an overlapped command until the execution of
the preceding operation has been completed.
"""
- self.send('*WAI')
+ self.write('*WAI')
@Action()
def trigger(self):
"""Equivalent to Group Execute Trigger.
"""
- self.send('*TRG')
+ self.write('*TRG')
@Feat()
def status_byte(self):
@@ -77,7 +77,7 @@ def service_request_enabled(self):
@service_request_enabled.setter
def service_request_enabled(self, value):
- return self.query('*SRE {0:d}', value)
+ self.query('*SRE {0:d}'.format(value))
@Feat()
def event_status_reg(self):
@@ -87,7 +87,7 @@ def event_status_reg(self):
@event_status_reg.setter
def event_status_reg(self, value):
- self.query('*ESR {0:d}', value)
+ self.query('*ESR {0:d}'.format(value))
@Feat()
def event_status_enabled(self):
@@ -97,11 +97,11 @@ def event_status_enabled(self):
@event_status_enabled.setter
def event_status_enabled(self, value):
- self.query('*ESR {0:d}', value)
+ self.query('*ESR {0:d}'.format(value))
@Action()
def clear_status(self):
- self.send('*CLS')
+ self.write('*CLS')
@Feat(units='Hz')
def frequency(self):
@@ -112,7 +112,7 @@ def frequency(self):
@frequency.setter
def frequency(self, value):
- self.send('CFRQ:VALUE {0:f}HZ'.format(value))
+ self.write('CFRQ:VALUE {0:f}HZ'.format(value))
@Feat(units='V')
@@ -124,15 +124,15 @@ def amplitude(self):
@amplitude.setter
def amplitude(self, value):
- self.query('RFLV:VALUE {0:f}V', value)
+ self.query('RFLV:VALUE {0:f}V'.format(value))
@Feat(units='V')
def offset(self):
"""Offset amplitude.
"""
- return self.query('RFLV:OFFS?',
- format=':RFLV:OFFS:VALUE {0:f};{_}')
+ return self.parse_query('RFLV:OFFS?',
+ format=':RFLV:OFFS:VALUE {0:f};{_}')
@offset.setter
def offset(self, value):
@@ -159,7 +159,7 @@ def phase(self):
def phase(self, value):
"""Adjust phase offset of carrier in degrees.
"""
- self.send('CFRQ:PHASE {}'.format(value))
+ self.write('CFRQ:PHASE {}'.format(value))
@Feat(values={'INT', 'EXT10DIR', 'EXTIND', 'EXT10IND', 'INT10OUT'})
def frequency_standard(self):
@@ -191,34 +191,35 @@ def rflimit_enabled(self, value):
def remote(self, value):
if value:
- self.send('^A')
+ self.write('^A')
else:
- self.send('^D')
+ self.write('^D')
@Action(units='ms')
def expose(self, exposure_time=1):
- self.send('EXPOSE {}'.format(exposure_time))
+ self.write('EXPOSE {}'.format(exposure_time))
@Feat(values={True: 'on', False: 'off'})
def time(self):
- self.send()
- return self.recv()
+ # TODO: ??
+ self.write('')
+ return self.read()
@time.setter
def time(self, value):
- self.send("vlal ".format(value))
+ self.write("vlal ".format(value))
def local_lockout(self, value):
if value:
- self.send('^R')
+ self.write('^R')
else:
- self.send('^P')
+ self.write('^P')
def software_handshake(self, value):
if value:
- self.send('^Q')
+ self.write('^Q')
else:
- self.send('^S')
+ self.write('^S')
if __name__ == '__main__':
@@ -233,7 +234,7 @@ def software_handshake(self, value):
args = parser.parse_args()
lantz.log.log_to_socket(lantz.log.DEBUG)
- with A2023a(args.port) as inst:
+ with A2023a.from_serial_port(args.port) as inst:
if args.interactive:
from lantz.ui.app import start_test_app
start_test_app(inst)
diff --git a/lantz/drivers/andor/__init__.py b/lantz/drivers/andor/__init__.py
index 6acb422..cb43748 100644
--- a/lantz/drivers/andor/__init__.py
+++ b/lantz/drivers/andor/__init__.py
@@ -9,7 +9,7 @@
----
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/lantz/drivers/andor/andor.py b/lantz/drivers/andor/andor.py
index 93676a5..c34151c 100644
--- a/lantz/drivers/andor/andor.py
+++ b/lantz/drivers/andor/andor.py
@@ -10,7 +10,7 @@
- Andor Manual
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
@@ -23,7 +23,7 @@
_ERRORS = {
0: 'SUCCESS',
1: 'AT_ERR_NOTINITIALISED',
- 1: 'AT_HANDLE_SYSTEM', # TODO: Check twice the same key!
+ #1: 'AT_HANDLE_SYSTEM', # TODO: Check twice the same key!
2: 'AT_ERR_NOTIMPLEMENTED',
3: 'AT_ERR_READONLY',
4: 'AT_ERR_NOTREADABLE',
diff --git a/lantz/drivers/andor/ccd.py b/lantz/drivers/andor/ccd.py
index ca39a87..c91339d 100644
--- a/lantz/drivers/andor/ccd.py
+++ b/lantz/drivers/andor/ccd.py
@@ -3,7 +3,7 @@
"""
lantz.drivers.andor.ccd
- ~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~~~~~~~~~~~~~~~~~~~~~~~
Low level driver wrapping library for CCD and Intensified CCD cameras.
Only functions for iXon EMCCD cameras were tested.
@@ -17,7 +17,7 @@
- Andor SDK 2.96 Manual
- :copyright: 2014 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
import numpy as np
@@ -114,7 +114,6 @@
20121: 'DRV_ERROR_NOHANDLE',
20130: 'DRV_GATING_NOT_AVAILABLE',
20131: 'DRV_FPGA_VOLTAGE_ERROR',
- 20099: 'DRV_BINNING_ERROR',
20100: 'DRV_INVALID_AMPLIFIER',
20101: 'DRV_INVALID_COUNTCONVERT_MODE'
}
@@ -239,10 +238,10 @@ def camera_handle(self, index):
SetCurrentCamera function.
The number of cameras can be obtained using the GetAvailableCameras
function.
- Parameters
- long cameraIndex: index of any of the installed cameras. Valid values:
- 0 to NumberCameras-1 where NumberCameras is the value returned by
- the GetAvailableCameras function.
+
+ :param index: index of any of the installed cameras.
+ Valid values: 0 to NumberCameras-1 where NumberCameras
+ is the value returned by the GetAvailableCameras function.
"""
index = ct.c_long(index)
handle = ct.c_long()
@@ -338,7 +337,8 @@ def controller_card(self):
"""This function will retrieve the type of PCI controller card included
in your system. This function is not applicable for USB systems. The
maximum number of characters that can be returned from this function is
- 10."""
+ 10.
+ """
model = ct.c_wchar_p()
self.lib.GetControllerCardModel(ct.pointer(model))
@@ -363,7 +363,7 @@ def detector_shape(self):
@Feat(read_once=True)
def px_size(self):
- """ This function returns the dimension of the pixels in the detector
+ """This function returns the dimension of the pixels in the detector
in microns.
"""
xp, yp = ct.c_float(), ct.c_float()
@@ -373,7 +373,7 @@ def px_size(self):
return (xp.value, yp.value)
def QE(self, wl):
- """ Returns the percentage QE for a particular head model at a user
+ """Returns the percentage QE for a particular head model at a user
specified wavelength.
"""
hname = (ct.c_char * 100)()
@@ -387,14 +387,16 @@ def QE(self, wl):
return qe.value
def sensitivity(self, ad, amp, i, pa):
- """ This function returns the sensitivity for a particular speed."""
+ """This function returns the sensitivity for a particular speed.
+ """
+
sens = ct.c_float()
ad, amp, i, pa = ct.c_int(ad), ct.c_int(amp), ct.c_int(i), ct.c_int(pa)
self.lib.GetSensitivity(ad, amp, i, pa, ct.pointer(sens))
return sens.value
def count_convert_available(self, mode):
- """ This function checks if the hardware and current settings permit
+ """This function checks if the hardware and current settings permit
the use of the specified Count Convert mode.
"""
mode = ct.c_int(mode)
@@ -407,7 +409,7 @@ def count_convert_available(self, mode):
### SHUTTER # I couldn't find a better way to do this... sorry
@Action()
def shutter(self, typ, mode, ext_closing, ext_opening, ext_mode):
- """ This function expands the control offered by SetShutter to allow an
+ """This function expands the control offered by SetShutter to allow an
external shutter and internal shutter to be controlled independently
(only available on some cameras – please consult your Camera User
Guide). The typ parameter allows the user to control the TTL signal
@@ -423,18 +425,14 @@ def shutter(self, typ, mode, ext_closing, ext_opening, ext_mode):
“Open” and set the mode parameter to “Auto”.
To not use any shutter in the experiment, set both shutter modes to
permanently open.
- Parameters
- Int typ:
- 0 Output TTL low signal to open shutter
- 1 Output TTL high signal to open shutter
- int mode:
- 0 Fully Auto
- 1 Permanently Open
- 2 Permanently Closed
- 4 Open for FVB series
- 5 Open for any series
- int closingtime: time shutter takes to close (milliseconds)
- int openingtime: Time shutter takes to open (milliseconds)
+
+ :param typ: 0 (or 1) Output TTL low (or high) signal to open shutter.
+ :param mode: Internal shutter: 0 Fully Auto, 1 Permanently Open,
+ 2 Permanently Closed, 4 Open for FVB series, 5 Open for any series.
+ :param ext_closing: Time shutter takes to close (milliseconds)
+ :param ext_opening: Time shutter takes to open (milliseconds)
+ :param ext_mode: External shutter: 0 Fully Auto, 1 Permanently Open,
+ 2 Permanently Closed, 4 Open for FVB series, 5 Open for any series.
"""
self.lib.SetShutterEx(ct.c_int(typ), ct.c_int(mode),
ct.c_int(ext_closing), ct.c_int(ext_opening),
@@ -459,7 +457,7 @@ def has_mechanical_shutter(self):
@Feat(read_once=True, units='degC')
def min_temperature(self):
- """ This function returns the valid range of temperatures in centigrads
+ """This function returns the valid range of temperatures in centigrads
to which the detector can be cooled.
"""
mini, maxi = ct.c_int(), ct.c_int()
@@ -468,7 +466,7 @@ def min_temperature(self):
@Feat(read_once=True, units='degC')
def max_temperature(self):
- """ This function returns the valid range of temperatures in centigrads
+ """This function returns the valid range of temperatures in centigrads
to which the detector can be cooled.
"""
mini, maxi = ct.c_int(), ct.c_int()
@@ -477,7 +475,7 @@ def max_temperature(self):
@Feat()
def temperature_status(self):
- """ This function returns the temperature of the detector to the
+ """This function returns the temperature of the detector to the
nearest degree. It also gives the status of cooling process.
"""
temp = ct.c_float()
@@ -486,7 +484,7 @@ def temperature_status(self):
@Feat(units='degC')
def temperature(self):
- """ This function returns the temperature of the detector to the
+ """This function returns the temperature of the detector to the
nearest degree. It also gives the status of cooling process.
"""
temp = ct.c_float()
@@ -518,7 +516,7 @@ def cooler_on(self, value):
@Feat(values={True: 1, False: 0})
def cooled_on_shutdown(self):
- """ This function determines whether the cooler is switched off when
+ """This function determines whether the cooler is switched off when
the camera is shut down.
"""
return self.cooled_on_shutdown_value
@@ -531,7 +529,7 @@ def cooled_on_shutdown(self, state):
@Feat(values={'onfull': 0, 'onlow': 1, 'off': 2})
def fan_mode(self):
- """ Allows the user to control the mode of the camera fan. If the
+ """Allows the user to control the mode of the camera fan. If the
system is cooled, the fan should only be turned off for short periods
of time. During this time the body of the camera will warm up which
could compromise cooling capabilities.
@@ -551,7 +549,7 @@ def fan_mode(self, mode):
@Feat()
def averaging_factor(self):
- """ Averaging factor to be used with the recursive filter. For
+ """Averaging factor to be used with the recursive filter. For
information on the various data averaging filters available see
DATA AVERAGING FILTERS in the Special Guides section of the manual.
"""
@@ -565,7 +563,7 @@ def averaging_factor(self, value):
@Feat()
def averaging_frame_count(self):
- """ Number of frames to be used when using the frame averaging filter.
+ """Number of frames to be used when using the frame averaging filter.
"""
fc = ct.c_uint()
self.lib.Filter_GetAveragingFrameCount(ct.pointer(fc))
@@ -577,7 +575,7 @@ def averaging_frame_count(self, value):
@Feat(values={'NAF': 0, 'RAF': 5, 'FAF': 6})
def averaging_mode(self):
- """ Current averaging mode.
+ """Current averaging mode.
Valid options are:
0 – No Averaging Filter
5 – Recursive Averaging Filter
@@ -593,7 +591,7 @@ def averaging_mode(self, value):
@Feat(values={'NF': 0, 'MF': 1, 'LAF': 2, 'IRF': 3, 'NTF': 4})
def noise_filter_mode(self):
- """ Set the Noise Filter to use; For information on the various
+ """Set the Noise Filter to use; For information on the various
spurious noise filters available see SPURIOUS NOISE FILTERS in the
Special Guides section of the manual.
Valid options are:
@@ -613,7 +611,7 @@ def noise_filter_mode(self, value):
@Feat()
def filter_threshold(self):
- """ Sets the threshold value for the Noise Filter. For information on
+ """Sets the threshold value for the Noise Filter. For information on
the various spurious noise filters available see SPURIOUS NOISE FILTERS
in the Special Guides section of the manual.
Valid values are:
@@ -630,7 +628,7 @@ def filter_threshold(self, value):
@Feat(values={True: 2, False: 0})
def cr_filter_enabled(self):
- """ This function will set the state of the cosmic ray filter mode for
+ """This function will set the state of the cosmic ray filter mode for
future acquisitions. If the filter mode is on, consecutive scans in an
accumulation will be compared and any cosmic ray-like features that are
only present in one scan will be replaced with a scaled version of the
@@ -648,7 +646,7 @@ def cr_filter_enabled(self, value):
@Feat(values={True: 1, False: 0}) # FIXME: untested
def photon_counting_mode(self):
- """ This function activates the photon counting option.
+ """This function activates the photon counting option.
"""
return self.photon_counting_mode_state
@@ -660,7 +658,7 @@ def photon_counting_mode(self, state):
@Feat(read_once=True)
def n_photon_counting_div(self):
- """ Available in some systems is photon counting mode. This function
+ """Available in some systems is photon counting mode. This function
gets the number of photon counting divisions available. The functions
SetPhotonCounting and SetPhotonCountingThreshold can be used to specify
which of these divisions is to be used.
@@ -671,14 +669,14 @@ def n_photon_counting_div(self):
@Action() # untested
def set_photon_counting_divs(self, n, thres):
- """ This function sets the thresholds for the photon counting option.
+ """This function sets the thresholds for the photon counting option.
"""
thres = ct.c_long(thres)
self.lib.SetPhotonCountingDivisions(ct.c_ulong(n), ct.pointer(thres))
@Action()
def set_photon_counting_thres(self, mini, maxi):
- """ This function sets the minimum and maximum threshold in counts
+ """This function sets the minimum and maximum threshold in counts
(1-65535) for the photon counting option.
"""
self.lib.SetPhotonCountingThreshold(ct.c_long(mini), ct.c_long(maxi))
@@ -702,7 +700,7 @@ def FK_exposure_time(self):
@Feat(values={'Single Scan': 1, 'Accumulate': 2, 'Kinetics': 3,
'Fast Kinetics': 4, 'Run till abort': 5})
def acquisition_mode(self):
- """ This function will set the acquisition mode to be used on the next
+ """This function will set the acquisition mode to be used on the next
StartAcquisition.
NOTE: In Mode 5 the system uses a “Run Till Abort” acquisition mode. In
Mode 5 only, the camera continually acquires data until the
@@ -719,7 +717,7 @@ def acquisition_mode(self, mode):
@Action()
def prepare_acquisition(self):
- """ This function reads the current acquisition setup and allocates and
+ """This function reads the current acquisition setup and allocates and
configures any memory that will be used during the acquisition. The
function call is not required as it will be called automatically by the
StartAcquisition function if it has not already been called externally.
@@ -735,7 +733,7 @@ def prepare_acquisition(self):
@Action()
def start_acquisition(self):
- """ This function starts an acquisition. The status of the acquisition
+ """This function starts an acquisition. The status of the acquisition
can be monitored via GetStatus().
"""
self.lib.StartAcquisition()
@@ -748,7 +746,7 @@ def abort_acquisition(self):
@Action()
def wait_for_acquisition(self):
- """ WaitForAcquisition can be called after an acquisition is started
+ """WaitForAcquisition can be called after an acquisition is started
using StartAcquisition to put the calling thread to sleep until an
Acquisition Event occurs. This can be used as a simple alternative to
the functionality provided by the SetDriverEvent function, as all Event
@@ -776,7 +774,7 @@ def cancel_wait(self):
@Feat()
def acquisition_progress(self):
- """ This function will return information on the progress of the
+ """This function will return information on the progress of the
current acquisition. It can be called at any time but is best used in
conjunction with SetDriverEvent.
The values returned show the number of completed scans in the current
@@ -797,7 +795,7 @@ def acquisition_progress(self):
@Feat()
def status(self):
- """ This function will return the current status of the Andor SDK
+ """This function will return the current status of the Andor SDK
system. This function should be called before an acquisition is started
to ensure that it is IDLE and during an acquisition to monitor the
process.
@@ -824,14 +822,14 @@ def status(self):
@Feat()
def n_exposures_in_ring(self):
- """ Gets the number of exposures in the ring at this moment."""
+ """Gets the number of exposures in the ring at this moment."""
n = ct.c_int()
self.lib.GetNumberRingExposureTimes(ct.pointer(n))
return n.value
@Feat()
def buffer_size(self):
- """ This function will return the maximum number of images the circular
+ """This function will return the maximum number of images the circular
buffer can store based on the current acquisition settings.
"""
n = ct.c_long()
@@ -840,7 +838,7 @@ def buffer_size(self):
@Feat(values={True: 1, False: 0})
def exposing(self):
- """ This function will return if the system is exposing or not. The
+ """This function will return if the system is exposing or not. The
status of the firepulse will be returned.
NOTE This is only supported by the CCI23 card.
"""
@@ -850,7 +848,7 @@ def exposing(self):
@Feat()
def n_images_acquired(self):
- """ This function will return the total number of images acquired since
+ """This function will return the total number of images acquired since
the current acquisition started. If the camera is idle the value
returned is the number of images acquired during the last acquisition.
"""
@@ -860,15 +858,15 @@ def n_images_acquired(self):
@Action()
def set_image(self, shape=None, binned=(1, 1), p_0=(1, 1)):
- """ This function will set the horizontal and vertical binning to be
+ """This function will set the horizontal and vertical binning to be
used when taking a full resolution image.
- Parameters
- int hbin: number of pixels to bin horizontally.
- int vbin: number of pixels to bin vertically.
- int hstart: Start column (inclusive).
- int hend: End column (inclusive).
- int vstart: Start row (inclusive).
- int vend: End row (inclusive).
+
+ :param hbin: number of pixels to bin horizontally.
+ :param vbin: number of pixels to bin vertically.
+ :param hstart: Start column (inclusive).
+ :param hend: End column (inclusive).
+ :param vstart: Start row (inclusive).
+ :param vend: End row (inclusive).
"""
if shape is None:
@@ -885,7 +883,7 @@ def set_image(self, shape=None, binned=(1, 1), p_0=(1, 1)):
@Feat(values={'FVB': 0, 'Multi-Track': 1, 'Random-Track': 2,
'Single-Track': 3, 'Image': 4})
def readout_mode(self):
- """ This function will set the readout mode to be used on the subsequent
+ """This function will set the readout mode to be used on the subsequent
acquisitions.
"""
return self.readout_mode_mode
@@ -898,7 +896,7 @@ def readout_mode(self, mode):
@Feat(values={True: 1, False: 0})
def readout_packing(self):
- """ This function will configure whether data is packed into the readout
+ """This function will configure whether data is packed into the readout
register to improve frame rates for sub-images.
Note: It is important to ensure that no light falls outside of the
sub-image area otherwise the acquired data will be corrupted. Only
@@ -916,14 +914,13 @@ def readout_packing(self, state):
@Feat(read_once=True)
def min_image_length(self):
- """ This function will return the minimum number of pixels that can be
+ """This function will return the minimum number of pixels that can be
read out from the chip at each exposure. This minimum value arises due
the way in which the chip is read out and will limit the possible sub
image dimensions and binning sizes that can be applied.
- Parameters
- int* MinImageLength: Will contain the minimum number of super
- pixels on return.
"""
+
+ # Will contain the minimum number of super pixels on return.
px = ct.c_int()
self.lib.GetMinimumImageLength(ct.pointer(px))
@@ -939,7 +936,7 @@ def free_int_mem(self):
self.lib.FreeInternalMemory()
def acquired_data(self, shape):
- """ This function will return the data from the last acquisition. The
+ """This function will return the data from the last acquisition. The
data are returned as long integers (32-bit signed integers). The
“array” must be large enough to hold the complete data set.
"""
@@ -951,7 +948,7 @@ def acquired_data(self, shape):
return arr
def acquired_data16(self, shape):
- """ 16-bit version of the GetAcquiredData function. The “array” must be
+ """16-bit version of the GetAcquiredData function. The “array” must be
large enough to hold the complete data set.
"""
size = np.array(shape).prod()
@@ -961,7 +958,7 @@ def acquired_data16(self, shape):
return arr.reshape(shape)
def oldest_image(self, shape):
- """ This function will update the data array with the oldest image in
+ """This function will update the data array with the oldest image in
the circular buffer. Once the oldest image has been retrieved it no
longer is available. The data are returned as long integers (32-bit
signed integers). The "array" must be exactly the same size as the full
@@ -974,7 +971,7 @@ def oldest_image(self, shape):
return array.reshape(shape)
def oldest_image16(self, shape):
- """ 16-bit version of the GetOldestImage function.
+ """16-bit version of the GetOldestImage function.
"""
size = np.array(shape).prod()
array = np.ascontiguousarray(np.zeros(size, dtype=np.int16))
@@ -983,7 +980,7 @@ def oldest_image16(self, shape):
return array.reshape(shape)
def most_recent_image(self, shape):
- """ This function will update the data array with the most recently
+ """This function will update the data array with the most recently
acquired image in any acquisition mode. The data are returned as long
integers (32-bit signed integers). The "array" must be exactly the same
size as the complete image.
@@ -995,7 +992,7 @@ def most_recent_image(self, shape):
return arr.reshape(shape)
def most_recent_image16(self, shape):
- """ 16-bit version of the GetMostRecentImage function.
+ """16-bit version of the GetMostRecentImage function.
"""
size = np.array(shape).prod()
arr = np.ascontiguousarray(np.zeros(size, dtype=np.int16))
@@ -1004,17 +1001,17 @@ def most_recent_image16(self, shape):
return arr.reshape(shape)
def images(self, first, last, shape, validfirst, validlast):
- """ This function will update the data array with the specified series
+ """This function will update the data array with the specified series
of images from the circular buffer. If the specified series is out of
range (i.e. the images have been overwritten or have not yet been
acquired) then an error will be returned.
- Parameters:
- long first: index of first image in buffer to retrieve.
- long last: index of last image in buffer to retrieve.
- at_32* arr: pointer to data storage allocated by the user.
- unsigned long size: total number of pixels.
- long* validfirst: index of the first valid image.
- long* validlast: index of the last valid image.
+
+ :param first: index of first image in buffer to retrieve.
+ :param flast: index of last image in buffer to retrieve.
+ :param farr: pointer to data storage allocated by the user.
+ :param size: total number of pixels.
+ :param fvalidfirst: index of the first valid image.
+ :param fvalidlast: index of the last valid image.
"""
size = shape[0] * shape[1] * (1 + last - first)
array = np.ascontiguousarray(np.zeros(size, dtype=np.int32))
@@ -1026,7 +1023,7 @@ def images(self, first, last, shape, validfirst, validlast):
return array.reshape(-1, shape[0], shape[1])
def images16(self, first, last, shape, validfirst, validlast):
- """ 16-bit version of the GetImages function.
+ """16-bit version of the GetImages function.
"""
size = shape[0] * shape[1] * (1 + last - first)
array = np.ascontiguousarray(np.zeros(size, dtype=np.int16))
@@ -1040,7 +1037,7 @@ def images16(self, first, last, shape, validfirst, validlast):
@Feat()
def new_images_index(self):
- """ This function will return information on the number of new images
+ """This function will return information on the number of new images
(i.e. images which have not yet been retrieved) in the circular buffer.
This information can be used with GetImages to retrieve a series of the
latest images. If any images are overwritten in the circular buffer
@@ -1055,7 +1052,7 @@ def new_images_index(self):
@Feat() # TODO: test this
def available_images_index(self):
- """ This function will return information on the number of available
+ """This function will return information on the number of available
images in the circular buffer. This information can be used with
GetImages to retrieve a series of images. If any images are overwritten
in the circular buffer they no longer can be retrieved and the
@@ -1068,7 +1065,7 @@ def available_images_index(self):
return (first.value, last.value)
def set_dma_parameters(self, n_max_images, s_per_dma):
- """ In order to facilitate high image readout rates the controller card
+ """In order to facilitate high image readout rates the controller card
may wait for multiple images to be acquired before notifying the SDK
that new data is available. Without this facility, there is a chance
that hardware interrupts may be lost as the operating system does not
@@ -1078,33 +1075,36 @@ def set_dma_parameters(self, n_max_images, s_per_dma):
There are 3 settings involved in determining how many images will be
acquired for each notification (DMA Interrupt) of the controller card
and they are as follows:
- 1. The size of the DMA buffer gives an upper limit on the number of
- images that can be stored within it and is usually set to the size
- of one full image when installing the software. This will usually
- mean that if you acquire full frames there will never be more than
- one image per DMA.
- 2. A second setting that is used is the minimum amount of time
- (SecondsPerDMA) that should expire between interrupts. This can be
- used to give an indication of the reponsiveness of the operating
- system to interrupts. Decreasing this value will allow more
- interrupts per second and should only be done for faster pcs. The
- default value is 0.03s (30ms), finding the optimal value for your
- pc can only be done through experimentation.
- 3. The third setting is an overide to the number of images
- calculated using the previous settings. If the number of images per
- dma is calculated to be greater than MaxImagesPerDMA then it will
- be reduced to MaxImagesPerDMA. This can be used to, for example,
- ensure that there is never more than 1 image per DMA by setting
- MaxImagesPerDMA to 1. Setting MaxImagesPerDMA to zero removes this
- limit. Care should be taken when modifying these parameters as
- missed interrupts may prevent the acquisition from completing.
+
+ 1. The size of the DMA buffer gives an upper limit on the number of
+ images that can be stored within it and is usually set to the size
+ of one full image when installing the software. This will usually
+ mean that if you acquire full frames there will never be more than
+ one image per DMA.
+
+ 2. A second setting that is used is the minimum amount of time
+ (SecondsPerDMA) that should expire between interrupts. This can be
+ used to give an indication of the reponsiveness of the operating
+ system to interrupts. Decreasing this value will allow more
+ interrupts per second and should only be done for faster pcs. The
+ default value is 0.03s (30ms), finding the optimal value for your
+ pc can only be done through experimentation.
+
+ 3. The third setting is an overide to the number of images
+ calculated using the previous settings. If the number of images per
+ dma is calculated to be greater than MaxImagesPerDMA then it will
+ be reduced to MaxImagesPerDMA. This can be used to, for example,
+ ensure that there is never more than 1 image per DMA by setting
+ MaxImagesPerDMA to 1. Setting MaxImagesPerDMA to zero removes this
+ limit. Care should be taken when modifying these parameters as
+ missed interrupts may prevent the acquisition from completing.
"""
self.lib.SetDMAParameters(ct.c_int(n_max_images),
ct.c_float(s_per_dma))
@Feat()
def max_images_per_dma(self):
- """ This function will return the maximum number of images that can be
+ """This function will return the maximum number of images that can be
transferred during a single DMA transaction.
"""
n = ct.c_ulong()
@@ -1113,7 +1113,7 @@ def max_images_per_dma(self):
@Action()
def save_raw(self, filename, typ):
- """ This function saves the last acquisition as a raw data file.
+ """This function saves the last acquisition as a raw data file.
See self.savetypes for the file type keys.
"""
self.lib.SaveAsRaw(ct.c_char_p(str.encode(filename)),
@@ -1123,7 +1123,7 @@ def save_raw(self, filename, typ):
@Feat()
def acquisition_timings(self):
- """ This function will return the current “valid” acquisition timing
+ """This function will return the current “valid” acquisition timing
information. This function should be used after all the acquisitions
settings have been set, e.g. SetExposureTime, SetKineticCycleTime and
SetReadMode etc. The values returned are the actual times used in
@@ -1143,7 +1143,7 @@ def acquisition_timings(self):
@Action()
def set_exposure_time(self, time):
- """ This function will set the exposure time to the nearest valid value
+ """This function will set the exposure time to the nearest valid value
not less than the given value, in seconds. The actual exposure time
used is obtained by GetAcquisitionTimings. Please refer to
SECTION 5 – ACQUISITION MODES for further information.
@@ -1157,7 +1157,7 @@ def set_exposure_time(self, time):
@Action()
def set_accum_time(self, time):
- """ This function will set the accumulation cycle time to the nearest
+ """This function will set the accumulation cycle time to the nearest
valid value not less than the given value. The actual cycle time used
is obtained by GetAcquisitionTimings. Please refer to
SECTION 5 – ACQUISITION MODES for further information.
@@ -1171,7 +1171,7 @@ def set_accum_time(self, time):
@Action()
def set_kinetic_cycle_time(self, time):
- """ This function will set the kinetic cycle time to the nearest valid
+ """This function will set the kinetic cycle time to the nearest valid
value not less than the given value. The actual time used is obtained
by GetAcquisitionTimings. . Please refer to
SECTION 5 – ACQUISITION MODES for further information.
@@ -1186,7 +1186,7 @@ def set_kinetic_cycle_time(self, time):
@Action()
def set_n_kinetics(self, n):
- """ This function will set the number of scans (possibly accumulated
+ """This function will set the number of scans (possibly accumulated
scans) to be taken during a single acquisition sequence. This will only
take effect if the acquisition mode is Kinetic Series.
"""
@@ -1194,7 +1194,7 @@ def set_n_kinetics(self, n):
@Action()
def set_n_accum(self, n):
- """ This function will set the number of scans accumulated in memory.
+ """This function will set the number of scans accumulated in memory.
This will only take effect if the acquisition mode is either Accumulate
or Kinetic Series.
"""
@@ -1202,7 +1202,7 @@ def set_n_accum(self, n):
@Feat(units='s')
def keep_clean_time(self):
- """ This function will return the time to perform a keep clean cycle.
+ """This function will return the time to perform a keep clean cycle.
This function should be used after all the acquisitions settings have
been set, e.g. SetExposureTime, SetKineticCycleTime and SetReadMode
etc. The value returned is the actual times used in subsequent
@@ -1214,7 +1214,7 @@ def keep_clean_time(self):
@Feat(units='s')
def readout_time(self):
- """ This function will return the time to readout data from a sensor.
+ """This function will return the time to readout data from a sensor.
This function should be used after all the acquisitions settings have
been set, e.g. SetExposureTime, SetKineticCycleTime and SetReadMode
etc. The value returned is the actual times used in subsequent
@@ -1226,7 +1226,7 @@ def readout_time(self):
@Feat(read_once=True, units='s')
def max_exposure(self):
- """ This function will return the maximum Exposure Time in seconds that
+ """This function will return the maximum Exposure Time in seconds that
is settable by the SetExposureTime function.
"""
exp = ct.c_float()
@@ -1235,7 +1235,7 @@ def max_exposure(self):
@Feat(read_once=True)
def n_max_nexposure(self):
- """ This function will return the maximum number of exposures that can
+ """This function will return the maximum number of exposures that can
be configured in the SetRingExposureTimes SDK function.
"""
n = ct.c_int()
@@ -1243,7 +1243,7 @@ def n_max_nexposure(self):
return n.value
def true_exposure_times(self, n): # FIXME: bit order? something
- """ This function will return the actual exposure times that the camera
+ """This function will return the actual exposure times that the camera
will use. There may be differences between requested exposures and the
actual exposures.
ntimes: Numbers of times requested.
@@ -1261,7 +1261,7 @@ def exposure_times(self, value):
@Feat(values={True: 1, False: 0})
def frame_transfer_mode(self):
- """ This function will set whether an acquisition will readout in Frame
+ """This function will set whether an acquisition will readout in Frame
Transfer Mode. If the acquisition mode is Single Scan or Fast Kinetics
this call will have no affect.
"""
@@ -1277,7 +1277,7 @@ def frame_transfer_mode(self, state):
@Feat(read_once=True)
def n_preamps(self):
- """ Available in some systems are a number of pre amp gains that can be
+ """Available in some systems are a number of pre amp gains that can be
applied to the data as it is read out. This function gets the number of
these pre amp gains available. The functions GetPreAmpGain and
SetPreAmpGain can be used to specify which of these gains is to be
@@ -1288,7 +1288,7 @@ def n_preamps(self):
return n.value
def preamp_available(self, channel, amp, index, preamp):
- """ This function checks that the AD channel exists, and that the
+ """This function checks that the AD channel exists, and that the
amplifier, speed and gain are available for the AD channel.
"""
channel = ct.c_int(channel)
@@ -1302,7 +1302,7 @@ def preamp_available(self, channel, amp, index, preamp):
return bool(status.value)
def preamp_descr(self, index):
- """ This function will return a string with a pre amp gain description.
+ """This function will return a string with a pre amp gain description.
The pre amp gain is selected using the index. The SDK has a string
associated with each of its pre amp gains. The maximum number of
characters needed to store the pre amp gain descriptions is 30. The
@@ -1316,7 +1316,7 @@ def preamp_descr(self, index):
return str(descr.value)[2:-1]
def true_preamp(self, index):
- """ For those systems that provide a number of pre amp gains to apply
+ """For those systems that provide a number of pre amp gains to apply
to the data as it is read out; this function retrieves the amount of
gain that is stored for a particular index. The number of gains
available can be obtained by calling the GetNumberPreAmpGains function
@@ -1329,7 +1329,7 @@ def true_preamp(self, index):
@Feat()
def preamp(self):
- """ This function will set the pre amp gain to be used for subsequent
+ """This function will set the pre amp gain to be used for subsequent
acquisitions. The actual gain factor that will be applied can be found
through a call to the GetPreAmpGain function.
The number of Pre Amp Gains available is found by calling the
@@ -1364,7 +1364,7 @@ def EM_advanced_enabled(self, value):
@Feat(values={'DAC255': 0, 'DAC4095': 1, 'Linear': 2, 'RealGain': 3})
def EM_gain_mode(self):
- """ Set the EM Gain mode to one of the following possible settings.
+ """Set the EM Gain mode to one of the following possible settings.
Mode 0: The EM Gain is controlled by DAC settings in the range
0-255. Default mode.
1: The EM Gain is controlled by DAC settings in the range 0-4095.
@@ -1418,7 +1418,7 @@ def n_amps(self):
return n.value
def amp_available(self, iamp):
- """ This function checks if the hardware and current settings permit
+ """This function checks if the hardware and current settings permit
the use of the specified amplifier."""
ans = self.lib.IsAmplifierAvailable(ct.c_int(iamp))
if ans == 20002:
@@ -1427,7 +1427,7 @@ def amp_available(self, iamp):
return False
def amp_descr(self, index):
- """ This function will return a string with an amplifier description.
+ """This function will return a string with an amplifier description.
The amplifier is selected using the index. The SDK has a string
associated with each of its amplifiers. The maximum number of
characters needed to store the amplifier descriptions is 21. The user
@@ -1441,7 +1441,7 @@ def amp_descr(self, index):
return str(descr.value)[2:-1]
def readout_flipped(self, iamp):
- """ On cameras with multiple amplifiers the frame readout may be
+ """On cameras with multiple amplifiers the frame readout may be
flipped. This function can be used to determine if this is the case.
"""
flipped = ct.c_int()
@@ -1450,7 +1450,7 @@ def readout_flipped(self, iamp):
return bool(flipped.value)
def amp_max_hspeed(self, index):
- """ This function will return the maximum available horizontal shift
+ """This function will return the maximum available horizontal shift
speed for the amplifier selected by the index parameter.
"""
hspeed = ct.c_float()
@@ -1458,15 +1458,12 @@ def amp_max_hspeed(self, index):
return hspeed.value
def n_horiz_shift_speeds(self, channel=0, typ=None):
- """ As your Andor SDK system is capable of operating at more than one
+ """As your Andor SDK system is capable of operating at more than one
horizontal shift speed this function will return the actual number of
speeds available.
- Parameters
- int channel: the AD channel.
- int typ: output amplification.
- Valid values: 0 electron multiplication.
- 1 conventional.
- int* speeds: number of allowed horizontal speeds
+
+ :param channel: the AD channel.
+ :param typ: output amplification. 0 electron multiplication. 1 conventional.
"""
if typ is None:
typ = self.amp_typ
@@ -1485,20 +1482,13 @@ def true_horiz_shift_speed(self, index=0, typ=None, ad=0):
GetHSSpeed(int channel, int typ, int index, float* speed)
- Parameters
- int channel: the AD channel.
-
- int typ: output amplification.
- Valid values:
+ :param typ: output amplification.
0 electron multiplication/Conventional(clara)
1 conventional/Extended NIR Mode(clara).
-
- int index: speed required
- Valid values
- 0 to NumberSpeeds-1 where NumberSpeeds is value returned in
- first parameter after a call to GetNumberHSSpeeds().
-
- float* speed: speed in in MHz.
+ :param index: speed required
+ 0 to NumberSpeeds-1 where NumberSpeeds is value returned in
+ first parameter after a call to GetNumberHSSpeeds().
+ :param ad: the AD channel.
"""
if typ is None:
@@ -1517,19 +1507,17 @@ def horiz_shift_speed(self):
@horiz_shift_speed.setter
def horiz_shift_speed(self, index):
- """ This function will set the speed at which the pixels are shifted
+ """This function will set the speed at which the pixels are shifted
into the output node during the readout phase of an acquisition.
Typically your camera will be capable of operating at several
horizontal shift speeds. To get the actual speed that an index
corresponds to use the GetHSSpeed function.
- Parameters
- int typ: output amplification.
- Valid values:
+
+ :param typ: output amplification.
0 electron multiplication/Conventional(clara).
1 conventional/Extended NIR mode(clara).
- int index: the horizontal speed to be used
- Valid values
- 0 to GetNumberHSSpeeds() - 1
+ :param index: the horizontal speed to be used
+ 0 to GetNumberHSSpeeds() - 1
"""
ans = self.lib.SetHSSpeed(ct.c_int(self.amp_typ), ct.c_int(index))
if ans == 20002:
@@ -1537,7 +1525,7 @@ def horiz_shift_speed(self, index):
@Feat()
def fastest_recommended_vsspeed(self):
- """ As your Andor SDK system may be capable of operating at more than
+ """As your Andor SDK system may be capable of operating at more than
one vertical shift speed this function will return the fastest
recommended speed available. The very high readout speeds, may require
an increase in the amplitude of the Vertical Clock Voltage using
@@ -1552,7 +1540,7 @@ def fastest_recommended_vsspeed(self):
@Feat(read_once=True)
def n_vert_clock_amps(self):
- """ This function will normally return the number of vertical clock
+ """This function will normally return the number of vertical clock
voltage amplitudes that the camera has.
"""
n = ct.c_int()
@@ -1560,11 +1548,10 @@ def n_vert_clock_amps(self):
return n.value
def vert_amp_index(self, string):
- """ This Function is used to get the index of the Vertical Clock
+ """This Function is used to get the index of the Vertical Clock
Amplitude that corresponds to the string passed in.
- Parameters
- char* text: String to test
- Valid values: "Normal" , "+1" , "+2" , "+3" , "+4"
+
+ :param string: "Normal" , "+1" , "+2" , "+3" , "+4"
"""
index = ct.c_int()
string = ct.c_char_p(str.encode(string))
@@ -1572,11 +1559,11 @@ def vert_amp_index(self, string):
return index.value
def vert_amp_string(self, index):
- """ This Function is used to get the Vertical Clock Amplitude string
+ """This Function is used to get the Vertical Clock Amplitude string
that corresponds to the index passed in.
- Parameters
- int index: Index of VS amplitude required
- Valid values 0 to GetNumberVSAmplitudes() - 1
+
+ :param index: Index of VS amplitude required
+ Valid values 0 to GetNumberVSAmplitudes() - 1
"""
index = ct.c_int(index)
string = (ct.c_char * 6)()
@@ -1584,11 +1571,11 @@ def vert_amp_string(self, index):
return str(string.value)[2:-1]
def true_vert_amp(self, index):
- """ This Function is used to get the value of the Vertical Clock
+ """This Function is used to get the value of the Vertical Clock
Amplitude found at the index passed in.
- Parameters
- int index: Index of VS amplitude required
- Valid values 0 to GetNumberVSAmplitudes() - 1
+
+ :param index: Index of VS amplitude required
+ Valid values 0 to GetNumberVSAmplitudes() - 1
"""
index = ct.c_int(index)
amp = ct.c_int()
@@ -1597,15 +1584,11 @@ def true_vert_amp(self, index):
@Action()
def set_vert_clock(self, index):
- """ If you choose a high readout speed (a low readout time), then you
+ """If you choose a high readout speed (a low readout time), then you
should also consider increasing the amplitude of the Vertical Clock
Voltage.
There are five levels of amplitude available for you to choose from:
- ď‚· Normal
- ď‚· +1
- ď‚· +2
- ď‚· +3
- ď‚· +4
+ - Normal, +1, +2, +3, +4
Exercise caution when increasing the amplitude of the vertical clock
voltage, since higher clocking voltages may result in increased
clock-induced charge (noise) in your signal. In general, only the very
@@ -1616,7 +1599,7 @@ def set_vert_clock(self, index):
@Feat(read_once=True)
def n_vert_shift_speeds(self):
- """ As your Andor system may be capable of operating at more than one
+ """As your Andor system may be capable of operating at more than one
vertical shift speed this function will return the actual number of
speeds available.
"""
@@ -1639,7 +1622,7 @@ def vert_shift_speed(self):
@vert_shift_speed.setter
def vert_shift_speed(self, index):
- """ This function will set the vertical speed to be used for subsequent
+ """This function will set the vertical speed to be used for subsequent
acquisitions.
"""
self.vert_shift_speed_index = index
@@ -1649,7 +1632,7 @@ def vert_shift_speed(self, index):
@Feat(values={True: 1, False: 0})
def baseline_clamp(self):
- """ This function returns the status of the baseline clamp
+ """This function returns the status of the baseline clamp
functionality. With this feature enabled the baseline level of each
scan in a kinetic series will be more consistent across the sequence.
"""
@@ -1664,7 +1647,7 @@ def baseline_clamp(self, value):
@Feat(limits=(-1000, 1100, 100))
def baseline_offset(self):
- """ This function allows the user to move the baseline level by the
+ """This function allows the user to move the baseline level by the
amount selected. For example “+100” will add approximately 100 counts
to the default baseline value. The value entered should be a multiple
of 100 between -1000 and +1000 inclusively.
@@ -1680,7 +1663,7 @@ def baseline_offset(self, value):
### BIT DEPTH
def bit_depth(self, ch):
- """ This function will retrieve the size in bits of the dynamic range
+ """This function will retrieve the size in bits of the dynamic range
for any available AD channel.
"""
ch = ct.c_int(ch)
@@ -1692,7 +1675,7 @@ def bit_depth(self, ch):
@Feat(values={True: 1, False: 0})
def adv_trigger_mode(self):
- """ This function will set the state for the iCam functionality that
+ """This function will set the state for the iCam functionality that
some cameras are capable of. There may be some cases where we wish to
prevent the software using the new functionality and just do it the way
it was previously done.
@@ -1706,7 +1689,7 @@ def adv_trigger_mode(self, state):
self.adv_trigger_mode_state = state
def trigger_mode_available(self, modestr):
- """ This function checks if the hardware and current settings permit
+ """This function checks if the hardware and current settings permit
the use of the specified trigger mode.
"""
index = self.triggers[modestr]
@@ -1720,7 +1703,7 @@ def trigger_mode_available(self, modestr):
'External Exposure': 7, 'External FVB EM': 9,
'Software Trigger': 10, 'External Charge Shifting': 12})
def trigger_mode(self):
- """ This function will set the trigger mode that the camera will
+ """This function will set the trigger mode that the camera will
operate in.
"""
return self.trigger_mode_index
@@ -1733,24 +1716,25 @@ def trigger_mode(self, mode):
@Action()
def send_software_trigger(self):
- """ This function sends an event to the camera to take an acquisition
+ """This function sends an event to the camera to take an acquisition
when in Software Trigger mode. Not all cameras have this mode available
to them. To check if your camera can operate in this mode check the
GetCapabilities function for the Trigger Mode
AC_TRIGGERMODE_CONTINUOUS. If this mode is physically possible and
other settings are suitable (IsTriggerModeAvailable) and the camera is
acquiring then this command will take an acquisition.
+
NOTES:
- The settings of the camera must be as follows:
- ReadOut mode is full image
- RunMode is Run Till Abort
- TriggerMode is 10
+ The settings of the camera must be as follows:
+ - ReadOut mode is full image
+ - RunMode is Run Till Abort
+ - TriggerMode is 10
"""
self.lib.SendSoftwareTrigger()
@Action()
def trigger_level(self, value):
- """ This function sets the trigger voltage which the system will use.
+ """This function sets the trigger voltage which the system will use.
"""
self.lib.SetTriggerLevel(ct.c_float(value))
@@ -1758,7 +1742,7 @@ def trigger_level(self, value):
@DictFeat(values={True: not(0), False: 0}, keys=list(range(1, 5)))
def in_aux_port(self, port):
- """ This function returns the state of the TTL Auxiliary Input Port on
+ """This function returns the state of the TTL Auxiliary Input Port on
the Andor plug-in card.
"""
port = ct.c_int(port)
@@ -1768,7 +1752,7 @@ def in_aux_port(self, port):
@DictFeat(values={True: 1, False: 0}, keys=list(range(1, 5)))
def out_aux_port(self, port):
- """ This function sets the TTL Auxiliary Output port (P) on the Andor
+ """This function sets the TTL Auxiliary Output port (P) on the Andor
plug-in card to either ON/HIGH or OFF/LOW.
"""
return self.auxout[port - 1]
diff --git a/lantz/drivers/andor/neo.py b/lantz/drivers/andor/neo.py
index 9c3030a..9526b49 100644
--- a/lantz/drivers/andor/neo.py
+++ b/lantz/drivers/andor/neo.py
@@ -10,7 +10,7 @@
- Andor Neo Manual
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/lantz/drivers/cobolt/__init__.py b/lantz/drivers/cobolt/__init__.py
index d30c957..e53a75a 100644
--- a/lantz/drivers/cobolt/__init__.py
+++ b/lantz/drivers/cobolt/__init__.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""
lantz.drivers.cobolt
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~~~~~~~~~~~~~~~~~~~~
:company: Cobolt.
:description: DPSS lasers, diode laser modules, fiber pigtailed lasers.
@@ -9,7 +9,7 @@
----
- :copyright: 2014 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/lantz/drivers/cobolt/cobolt0601.py b/lantz/drivers/cobolt/cobolt0601.py
index 053b418..95b62df 100644
--- a/lantz/drivers/cobolt/cobolt0601.py
+++ b/lantz/drivers/cobolt/cobolt0601.py
@@ -1,35 +1,36 @@
# -*- coding: utf-8 -*-
"""
lantz.drivers.cobolt.cobolt0601
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- :copyright: 2014 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
-from lantz import Action, Feat, DictFeat
-from lantz.serial import SerialDriver
-from lantz.errors import InstrumentError
+from pyvisa import constants
+from lantz import Action, Feat
+from lantz.messagebased import MessageBasedDriver
-class Cobolt0601(SerialDriver):
+
+class Cobolt0601(MessageBasedDriver):
"""Driver for any Cobolt 06-01 Series laser.
"""
- ENCODING = 'ascii'
-
- RECV_TERMINATION = '\r'
- SEND_TERMINATION = '\r'
-
- BAUDRATE = 115200
- BYTESIZE = 8
- PARITY = 'none'
- STOPBITS = 1
-
- #: flow control flags
- RTSCTS = False
- DSRDTR = False
- XONXOFF = False
+ DEFAULTS = {'ASRL': {'write_termination': '\r',
+ 'read_termination': '\r',
+ 'baud_rate': 115200,
+ 'bytesize': 8,
+ 'parity': constants.Parity.none,
+ 'stop_bits': constants.StopBits.one,
+ 'encoding': 'ascii',
+ }}
+
+ #TODO: add this in PyVISA
+ # flow control flags
+ #RTSCTS = False
+ #DSRDTR = False
+ #XONXOFF = False
@Feat(read_once=True)
def idn(self):
@@ -214,7 +215,7 @@ def mod_mode(self):
args = parser.parse_args()
lantz.log.log_to_screen(lantz.log.DEBUG)
- with Cobolt0601(args.port) as inst:
+ with Cobolt0601.from_serial_port(args.port) as inst:
if args.interactive:
from lantz.ui.qtwidgets import start_test_app
start_test_app(inst)
diff --git a/lantz/drivers/coherent/__init__.py b/lantz/drivers/coherent/__init__.py
index e69d758..a108297 100644
--- a/lantz/drivers/coherent/__init__.py
+++ b/lantz/drivers/coherent/__init__.py
@@ -9,7 +9,7 @@
----
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/lantz/drivers/coherent/innova.py b/lantz/drivers/coherent/innova.py
index c990d2e..703d789 100644
--- a/lantz/drivers/coherent/innova.py
+++ b/lantz/drivers/coherent/innova.py
@@ -17,14 +17,17 @@
- Innova 300C Manual
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
-from ... import Q_, Feat, DictFeat, Action
-from ...serial import SerialDriver
-from ...errors import InvalidCommand
+from pyvisa import constants
+
+from lantz import Feat, DictFeat, Action
+from lantz.errors import InvalidCommand
+
+from lantz.messagebased import MessageBasedDriver
def make_feat(command, **kwargs):
@@ -43,20 +46,21 @@ def set(self, value):
return Feat(get, set, **kwargs)
-class Innova300C(SerialDriver):
+class Innova300C(MessageBasedDriver):
"""Innova300 C Series.
"""
- ENCODING = 'ascii'
- SEND_TERMINATION = '\r\n'
- RECV_TERMINATION = '\r\n'
+ DEFAULTS = {'ASRL': {'write_termination': '\r\n',
+ 'read_termination': '\r\n',
+ 'baud_rate': 1200,
+ 'bytesize': 8,
+ 'parity': constants.Parity.none,
+ 'stop_bits': constants.StopBits.one,
+ 'encoding': 'ascii',
+ }}
- def __init__(self, port=1, baudrate=1200, **kwargs):
- super().__init__(port, baudrate, bytesize=8, parity='None',
- stopbits=1, **kwargs)
-
def initialize(self):
super().initialize()
self.echo_enabled = False
@@ -67,9 +71,6 @@ def query(self, command, *, send_args=(None, None), recv_args=(None, None)):
:param command: command to be sent to the instrument
:type command: string
-
- :param send_args: (termination, encoding) to override class defaults
- :param recv_args: (termination, encoding) to override class defaults
"""
ans = super().query(command, send_args=send_args, recv_args=recv_args)
# TODO: Echo handling
@@ -377,7 +378,7 @@ class KryptonInnova300C(Innova300C):
args = parser.parse_args()
lantz.log.log_to_socket(lantz.log.DEBUG)
- with Innova300C(args.port) as inst:
+ with Innova300C.from_serial_port(args.port) as inst:
if args.interactive:
from lantz.ui.app import start_test_app
start_test_app(inst)
diff --git a/lantz/drivers/examples/__init__.py b/lantz/drivers/examples/__init__.py
index 7c803d7..1a0dbd1 100644
--- a/lantz/drivers/examples/__init__.py
+++ b/lantz/drivers/examples/__init__.py
@@ -9,13 +9,11 @@
----
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
-from .fungen import (LantzSignalGenerator, LantzSignalGeneratorTCP, LantzSignalGeneratorSerial,
- LantzSignalGeneratorSerialVisa)
-from .voltmeter import LantzVoltmeterTCP
+from .fungen import LantzSignalGenerator
+from .voltmeter import LantzVoltmeter
-__all__ = ['LantzSignalGenerator', 'LantzSignalGeneratorTCP', 'LantzSignalGeneratorSerial',
- 'LantzSignalGeneratorSerialVisa', 'LantzVoltmeterTCP']
+__all__ = ['LantzSignalGenerator', 'LantzVoltmeter']
diff --git a/lantz/drivers/examples/dummydrivers.py b/lantz/drivers/examples/dummydrivers.py
index 6e5bdd5..3551e88 100644
--- a/lantz/drivers/examples/dummydrivers.py
+++ b/lantz/drivers/examples/dummydrivers.py
@@ -5,7 +5,7 @@
Just some fake drivers to show how the backend, frontend works.
- :copyright: 2014 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/lantz/drivers/examples/foreign_example.py b/lantz/drivers/examples/foreign_example.py
index b626dfa..682536c 100644
--- a/lantz/drivers/examples/foreign_example.py
+++ b/lantz/drivers/examples/foreign_example.py
@@ -1,11 +1,11 @@
# -*- coding: utf-8 -*-
"""
- lantz.drivers.example.foreign
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ lantz.drivers.example.foreign_example
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Foreign library example.
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
@@ -68,7 +68,7 @@ def do_something(self):
args = parser.parse_args()
lantz.log.log_to_socket(lantz.log.DEBUG)
- with ForeignExample() as inst:
+ with ForeignTemplate() as inst:
if args.interactive:
from lantz.ui.app import start_test_app
start_test_app(inst)
diff --git a/lantz/drivers/examples/fungen.py b/lantz/drivers/examples/fungen.py
index da43c39..a288238 100644
--- a/lantz/drivers/examples/fungen.py
+++ b/lantz/drivers/examples/fungen.py
@@ -5,25 +5,22 @@
Implements the Signal Generator described in the Lantz tutorial.
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
-from lantz import Feat, DictFeat, Q_, Action
-from lantz.serial import SerialDriver
-from lantz.network import TCPDriver
+from lantz import Feat, DictFeat, Action
from lantz.errors import InstrumentError
-from lantz.visa import SerialVisaDriver
+from lantz.messagebased import MessageBasedDriver
-class LantzSignalGenerator(object):
+class LantzSignalGenerator(MessageBasedDriver):
"""Lantz Signal Generator
"""
- ENCODING = 'ascii'
+ DEFAULTS = {'COMMON': {'write_termination': '\n',
+ 'read_termination': '\n'}}
- RECV_TERMINATION = '\n'
- SEND_TERMINATION = '\n'
def query(self, command, *, send_args=(None, None), recv_args=(None, None)):
answer = super().query(command, send_args=send_args, recv_args=recv_args)
@@ -79,7 +76,7 @@ def output_enabled(self, value):
def waveform(self):
return self.query('?WVF')
- @waveform
+ @waveform.setter
def waveform(self, value):
self.query('!WVF {}'.format(value))
@@ -110,17 +107,3 @@ def self_test(self, level=1, repetitions=3):
"""Reset to .
"""
self.query('!TES {} {}'.format(level, repetitions))
-
-
-class LantzSignalGeneratorTCP(LantzSignalGenerator, TCPDriver):
- pass
-
-
-class LantzSignalGeneratorSerial(LantzSignalGenerator, SerialDriver):
- pass
-
-
-class LantzSignalGeneratorSerialVisa(LantzSignalGenerator, SerialVisaDriver):
- pass
-
-
diff --git a/lantz/drivers/examples/serial_example.py b/lantz/drivers/examples/serial_example.py
index a57ba8f..698584c 100644
--- a/lantz/drivers/examples/serial_example.py
+++ b/lantz/drivers/examples/serial_example.py
@@ -1,25 +1,25 @@
# -*- coding: utf-8 -*-
"""
- lantz.drivers.example.serial_template
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ lantz.drivers.example.serial_example
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Serial example.
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from lantz import Action, Feat, DictFeat
-from lantz.serial import SerialDriver
+from lantz.messagebased import MessageBasedDriver
-class SerialTemplate(SerialDriver):
+
+class SerialTemplate(MessageBasedDriver):
"""Template for drivers connecting via serial port.
"""
- ENCODING = 'ascii'
- RECV_TERMINATION = '\n'
- SEND_TERMINATION = '\n'
+ DEFAULTS = {'ASRL': {'write_termination': '\n',
+ 'read_termination': '\n'}}
@Feat()
def a_read_only_property(self):
@@ -34,7 +34,7 @@ def a_read_write_property(self):
return float(self.query('?AMP'))
@a_read_write_property.setter
- def amplitude(self, value):
+ def a_read_write_property(self, value):
self.query('!AMP {:.1f}'.format(value))
@DictFeat(values={True: '1', False: '0'}, keys=list(range(1,9)))
diff --git a/lantz/drivers/examples/voltmeter.py b/lantz/drivers/examples/voltmeter.py
index 002c271..acbdfc1 100644
--- a/lantz/drivers/examples/voltmeter.py
+++ b/lantz/drivers/examples/voltmeter.py
@@ -1,26 +1,25 @@
# -*- coding: utf-8 -*-
"""
lantz.drivers.examples.voltmeter
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Implements the Simple Voltmeter described in the Lantz tutorial.
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
-from lantz import Feat, DictFeat, Q_, Action
-from lantz.network import TCPDriver
+from lantz import Feat, DictFeat, Action
from lantz.errors import InstrumentError
+from lantz.messagebased import MessageBasedDriver
-class LantzVoltmeterTCP(TCPDriver):
+
+class LantzVoltmeter(MessageBasedDriver):
"""Lantz Signal Generator
"""
- ENCODING = 'ascii'
-
- RECV_TERMINATION = '\n'
- SEND_TERMINATION = '\n'
+ DEFAULTS = {'COMMON': {'write_termination': '\n',
+ 'read_termination': '\n'}}
def query(self, command, *, send_args=(None, None), recv_args=(None, None)):
answer = super().query(command, send_args=send_args, recv_args=recv_args)
diff --git a/lantz/drivers/ieee4882.py b/lantz/drivers/ieee4882.py
index e2fdc2f..1d98e17 100644
--- a/lantz/drivers/ieee4882.py
+++ b/lantz/drivers/ieee4882.py
@@ -5,7 +5,7 @@
Implements a IEEE-488.2 commands.
- :copyright: 2013 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
@@ -85,7 +85,7 @@ def service_request_enabled(self):
@service_request_enabled.setter
def service_request_enabled(self, value):
- return self.query('*SRE {0:d}', value)
+ self.query('*SRE {0:d}'.format(value))
event_status_reg = Feat()
diff --git a/lantz/drivers/kentech/__init__.py b/lantz/drivers/kentech/__init__.py
index 9dd1490..6b2df92 100644
--- a/lantz/drivers/kentech/__init__.py
+++ b/lantz/drivers/kentech/__init__.py
@@ -9,7 +9,7 @@
----
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/lantz/drivers/kentech/hri.py b/lantz/drivers/kentech/hri.py
index 6875c6f..805e958 100644
--- a/lantz/drivers/kentech/hri.py
+++ b/lantz/drivers/kentech/hri.py
@@ -22,14 +22,14 @@
- Lantz reverse engineering team
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from lantz import Feat, Action
-from lantz.serial import SerialDriver
from lantz.errors import InstrumentError
+from lantz.messagebased import MessageBasedDriver
def between(s, before, after):
ndx1 = s.index(before)
@@ -37,13 +37,12 @@ def between(s, before, after):
return s[ndx1+len(before):ndx2]
-class HRI(SerialDriver):
+class HRI(MessageBasedDriver):
"""Kentech High Repetition Rate Image Intensifier.
"""
- SEND_TERMINATION = '\r'
- RECV_TERMINATION = '\n'
- ENCODING = 'ascii'
+ DEFAULTS = {'COMMON': {'write_termination': '\r',
+ 'read_termination': '\n'}}
def query(self, command, *, send_args=(None, None), recv_args=(None, None)):
"""Send query to the instrument and return the answer.
@@ -54,8 +53,18 @@ def query(self, command, *, send_args=(None, None), recv_args=(None, None)):
self.remote = True
return super().query(command, send_args=send_args, recv_args=recv_args)
- def query_expect(self, command, recv_termination=None, expected='ok'):
- ans = self.query(command, recv_args=(recv_termination, HRI.ENCODING))
+ def query_expect(self, command, read_termination=None, expected='ok'):
+ """Send a query and check that the answer contains the string.
+
+ :type command: str
+ :type read_termination: str | None
+ :type expected: str | None
+ """
+ if command and not self.recall('remote'):
+ self.log_info('Setting Remote.')
+ self.remote = True
+ self.resource.write(command)
+ ans = self.read(read_termination)
if expected and not expected in ans:
raise InstrumentError("'{}' not in '{}'".format(expected, ans))
return ans
@@ -64,7 +73,7 @@ def query_expect(self, command, recv_termination=None, expected='ok'):
def clear(self):
"""Clear the buffer.
"""
- self.send('\r\r')
+ self.write('\r\r')
@Feat(None, values={True, False})
def remote(self, value):
@@ -73,7 +82,7 @@ def remote(self, value):
if value:
#self.query_expect('', None, None)
self.query_expect('\r', expected=None)
- self.recv()
+ self.read()
else:
return self.query_expect('LOCAL', chr(0), None)
@@ -86,7 +95,7 @@ def revision(self):
if 'UNDEFINED' in ans:
ans = '1.0'
else:
- ans = self.recv()
+ ans = self.read()
ans = ans.split()[1]
return ans
@@ -264,7 +273,7 @@ def enabled(self, value):
args = parser.parse_args()
lantz.log.log_to_socket(lantz.log.DEBUG)
- with HRI(args.port, baudrate=9600) as inst:
+ with HRI.from_serial_port(args.port, baudrate=9600) as inst:
if args.interactive:
from lantz.ui.app import start_test_app
start_test_app(inst)
diff --git a/lantz/drivers/labjack/__init__.py b/lantz/drivers/labjack/__init__.py
index dc7f8aa..73feaeb 100644
--- a/lantz/drivers/labjack/__init__.py
+++ b/lantz/drivers/labjack/__init__.py
@@ -4,15 +4,14 @@
~~~~~~~~~~~~~~~~~~~~~
:company: LabJack
- :description: LabJacks are USB/Ethernet based measurement and automation
- devices which provide analog inputs/outputs, digital inputs/outputs, and more.
- They serve as an inexpensive and easy to use interface between computers and
- the physical world.
+ :description: LabJacks are USB/Ethernet based measurement and automation
+ devices which provide analog inputs/outputs, digital inputs/outputs, and more.
+
:website: http://www.labjack.com/
----
- :copyright: 2013 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/lantz/drivers/labjack/_internal/__init__.py b/lantz/drivers/labjack/_internal/__init__.py
new file mode 100644
index 0000000..f64d5d3
--- /dev/null
+++ b/lantz/drivers/labjack/_internal/__init__.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.labjack._internal
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Python drivers provided by LabJack.
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
diff --git a/lantz/drivers/labjack/_internal/u12.py b/lantz/drivers/labjack/_internal/u12.py
index e4326ec..64435d1 100644
--- a/lantz/drivers/labjack/_internal/u12.py
+++ b/lantz/drivers/labjack/_internal/u12.py
@@ -1,28 +1,36 @@
+# -*- coding: utf-8 -*-
"""
-Name: u12.py
-Desc: Defines the U12 class, which makes working with a U12 much easier. The
- functions of the U12 class are divided into two categories: UW and
- low-level.
-
- Most of the UW functions are exposed as functions of the U12 class. With
- the exception of the "e" functions, UW functions are Windows only. The "e"
- functions will work with both the UW and the Exodriver. Therefore, people
- wishing to write cross-platform code should restrict themselves to using
- only the "e" functions. The UW functions are described in Section 4 of the
- U12 User's Guide:
-
- http://labjack.com/support/u12/users-guide/4
-
- All low-level functions of the U12 class begin with the word
- raw. For example, the low-level function Counter can be called with
- U12.rawCounter(). Currently, low-level functions are limited to the
- Exodriver (Linux and Mac OS X). You can find descriptions of the low-level
- functions in Section 5 of the U12 User's Guide:
-
- http://labjack.com/support/u12/users-guide/5
-
+ lantz.drivers.labjack._internal.u12
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Python drivers for U12 provided by LabJack.
+
+ Defines the U12 class, which makes working with a U12 much easier. The
+ functions of the U12 class are divided into two categories: UW and
+ low-level.
+
+ Most of the UW functions are exposed as functions of the U12 class. With
+ the exception of the "e" functions, UW functions are Windows only. The "e"
+ functions will work with both the UW and the Exodriver. Therefore, people
+ wishing to write cross-platform code should restrict themselves to using
+ only the "e" functions. The UW functions are described in Section 4 of the
+ U12 User's Guide:
+
+ http://labjack.com/support/u12/users-guide/4
+
+ All low-level functions of the U12 class begin with the word
+ raw. For example, the low-level function Counter can be called with
+ U12.rawCounter(). Currently, low-level functions are limited to the
+ Exodriver (Linux and Mac OS X). You can find descriptions of the low-level
+ functions in Section 5 of the U12 User's Guide:
+
+ http://labjack.com/support/u12/users-guide/5
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
"""
+
import platform
import ctypes
import os, atexit
@@ -2529,7 +2537,7 @@ def getWinVersion(self):
>>> dev = U12()
>>> dev.getWinVersion()
- >>> {'majorVersion': 5L, 'minorVersion': 1L, 'platformID': 2L, 'buildNumber': 2600L, 'servicePackMajor': 2L, 'servicePackMinor': 0L}
+ >>> {'majorVersion': 5, 'minorVersion': 1, 'platformID': 2, 'buildNumber': 2600, 'servicePackMajor': 2, 'servicePackMinor': 0}
"""
# Create ctypes
diff --git a/lantz/drivers/labjack/u12.py b/lantz/drivers/labjack/u12.py
index 2ea5635..0c3bcec 100644
--- a/lantz/drivers/labjack/u12.py
+++ b/lantz/drivers/labjack/u12.py
@@ -1,10 +1,19 @@
# -*- coding: utf-8 -*-
"""
-
-
+ lantz.drivers.labjack
+ ~~~~~~~~~~~~~~~~~~~~~
+
+ :company: LabJack
+ :description: LabJacks are USB/Ethernet based measurement and automation
+ devices which provide analog inputs/outputs, digital inputs/outputs, and more.
+ They serve as an inexpensive and easy to use interface between computers and
+ the physical world.
+ :website: http://www.labjack.com/
+
+ ----
- :copyright: 2013 by Lantz Authors, see AUTHORS for more details.
- :license: BSD, see LICENSE for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
"""
@@ -12,7 +21,19 @@
from lantz import Driver
from lantz.errors import InstrumentError
-from _internal import u12 as _u12
+try:
+ from ._internal import u12 as _u12
+except Exception as ex:
+ from lantz.utils import is_building_docs
+ if not is_building_docs:
+ raise ex
+
+ class _u12:
+
+ @staticmethod
+ def U12(board_id):
+ raise ex
+
class U12(Driver):
"""
@@ -21,6 +42,7 @@ class U12(Driver):
For details about the commands, refer to the users guide.
"""
def __init__(self, board_id):
+ super().__init__()
self._internal = _u12.U12(board_id)
def initialize(self):
diff --git a/lantz/drivers/legacy/__init__.py b/lantz/drivers/legacy/__init__.py
new file mode 100644
index 0000000..a50c1d7
--- /dev/null
+++ b/lantz/drivers/legacy/__init__.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy
+ ~~~~~~~~~~~~~~~~~~~~
+
+ Legacy drivers based on old instrument classes. Do not use for new code.
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
diff --git a/lantz/drivers/legacy/aa/__init__.py b/lantz/drivers/legacy/aa/__init__.py
new file mode 100644
index 0000000..a3d64cd
--- /dev/null
+++ b/lantz/drivers/legacy/aa/__init__.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.aa
+ ~~~~~~~~~~~~~~~~~~~~~~~
+
+ :company: AA Opto Electronic.
+ :description: Radio frequency and acousto-optic devices, Laser based sub-systems.
+ :website: http://opto.braggcell.com/
+
+ ----
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from .aotf import MDSnC
+
+__all__ = ['MDSnC', ]
+
diff --git a/lantz/drivers/legacy/aa/aotf.py b/lantz/drivers/legacy/aa/aotf.py
new file mode 100644
index 0000000..da2e8ae
--- /dev/null
+++ b/lantz/drivers/legacy/aa/aotf.py
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.aa.aotf
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Implements the drivers for an AOTF Controller
+
+
+ Implementation Notes
+ --------------------
+
+ There are currently two (disconnected) ways of setting the power for each
+ line: powerdb and power.
+
+ Sources::
+
+ - MDSnC Manual
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+#TODO: Implement calibrated power.
+
+from lantz import Feat, DictFeat
+from lantz.drivers.legacy.serial import SerialDriver
+
+class MDSnC(SerialDriver):
+ """MDSnC synthesizer for AOTF.nC
+ """
+
+ CHANNELS = list(range(8))
+
+ @Feat(None, values={True: 1, False: 0})
+ def main_enabled(self, value):
+ """Enable the
+ """
+ self.send("I{}".format(value))
+
+ @DictFeat(None, keys=CHANNELS)
+ def enabled(self, channel, value):
+ """Enable single channels.
+ """
+ self.send("L{}O{}".format(channel, value))
+
+ @DictFeat(None, keys=CHANNELS)
+ def frequency(self, channel, value):
+ """RF frequency for a given channel.
+ """
+ self.send("L{}F{}".format(channel, value))
+
+ @DictFeat(None, keys=CHANNELS)
+ def powerdb(self, channel, value):
+ """Power for a given channel (in db).
+ """
+ self.send("L{}D{}".format(channel, value))
+
+ @DictFeat(None, keys=CHANNELS, limits=(0, 1023, 1))
+ def power(self, channel, value):
+ """Power for a given channel (in digital units).
+ """
+ self.send("L{}P{:04d}".format(channel, value))
+
+
+if __name__ == '__main__':
+ import argparse
+ import lantz.log
+
+ parser = argparse.ArgumentParser(description='Test Kentech HRI')
+ parser.add_argument('-i', '--interactive', action='store_true',
+ default=False, help='Show interactive GUI')
+ parser.add_argument('-p', '--port', type=str, default='17',
+ help='Serial port to connect to')
+
+ args = parser.parse_args()
+ lantz.log.log_to_socket(lantz.log.DEBUG)
+ with MDSnC(args.port) as inst:
+ if args.interactive:
+ from lantz.ui.app import start_test_app
+ start_test_app(inst)
+ else:
+ from time import sleep
+ print("init")
+ freq = 130
+ inst.power(4,10)
+ sleep(0.2)
+ inst.enabled(4,1)
+ sleep(1.2)
+ inst.enabled(4,0)
+ sleep(1.2)
+ inst.enabled(4,1)
+
+
diff --git a/lantz/drivers/legacy/aeroflex/__init__.py b/lantz/drivers/legacy/aeroflex/__init__.py
new file mode 100644
index 0000000..7227364
--- /dev/null
+++ b/lantz/drivers/legacy/aeroflex/__init__.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.aeroflex
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ :company: Aeroflex
+ :description: Test and measurement equipment and microelectronic solutions.
+ :website: http://www.aeroflex.com/
+
+ ----
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from .a2023a import A2023a
+
+__all__ = ['A2023a', ]
+
diff --git a/lantz/drivers/legacy/aeroflex/a2023a.py b/lantz/drivers/legacy/aeroflex/a2023a.py
new file mode 100644
index 0000000..583f0df
--- /dev/null
+++ b/lantz/drivers/legacy/aeroflex/a2023a.py
@@ -0,0 +1,255 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.aeroflex.a2023a
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Implements the drivers for an signal generator.
+
+
+ Sources::
+
+ - Aeroflex 2023a Manual.
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from lantz import Feat, Action
+from lantz.drivers.legacy.serial import SerialDriver
+
+
+class A2023a(SerialDriver):
+ """Aeroflex Test Solutions 2023A 9 kHz to 1.2 GHz Signal Generator.
+ """
+
+ RECV_TERMINATION = 256
+ SEND_TERMINATION = '\n'
+
+ @Feat(read_once=True)
+ def idn(self):
+ """Instrument identification.
+ """
+ return self.parse_query('*IDN?',
+ format='{manufacturer:s},{model:s},{serialno:s},{softno:s}')
+
+ @Feat(read_once=True)
+ def fitted_options(self):
+ """Fitted options.
+ """
+ return self.query('*OPT?').split(',')
+
+ @Action()
+ def reset(self):
+ """Set the instrument functions to the factory default power up state.
+ """
+ self.send('*RST')
+
+ @Action()
+ def self_test(self):
+ """Is the interface and processor are operating?
+ """
+ return self.query('*TST?') == '0'
+
+ @Action()
+ def wait(self):
+ """Inhibit execution of an overlapped command until the execution of
+ the preceding operation has been completed.
+ """
+ self.send('*WAI')
+
+ @Action()
+ def trigger(self):
+ """Equivalent to Group Execute Trigger.
+ """
+ self.send('*TRG')
+
+ @Feat()
+ def status_byte(self):
+ """Status byte, a number between 0-255.
+ """
+ return int(self.query('*STB?'))
+
+ @Feat()
+ def service_request_enabled(self):
+ """Service request enable register.
+ """
+ return int(self.query('*SRE?'))
+
+ @service_request_enabled.setter
+ def service_request_enabled(self, value):
+ self.query('*SRE {0:d}'.format(value))
+
+ @Feat()
+ def event_status_reg(self):
+ """Standard event enable register.
+ """
+ return int(self.query('*ESR?'))
+
+ @event_status_reg.setter
+ def event_status_reg(self, value):
+ self.query('*ESR {0:d}'.format(value))
+
+ @Feat()
+ def event_status_enabled(self):
+ """Standard event enable register.
+ """
+ return int(self.query('*ESR?'))
+
+ @event_status_enabled.setter
+ def event_status_enabled(self, value):
+ self.query('*ESR {0:d}'.format(value))
+
+ @Action()
+ def clear_status(self):
+ self.send('*CLS')
+
+ @Feat(units='Hz')
+ def frequency(self):
+ """Carrier frequency.
+ """
+ return self.parse_query('CFRQ?',
+ format=':CFRQ:VALUE {0:f};{_}')
+
+ @frequency.setter
+ def frequency(self, value):
+ self.send('CFRQ:VALUE {0:f}HZ'.format(value))
+
+
+ @Feat(units='V')
+ def amplitude(self):
+ """RF amplitude.
+ """
+ return self.parse_query('RFLV?',
+ format=':RFLV:UNITS {_};TYPE {_};VALUE {0:f};INC {_};')
+
+ @amplitude.setter
+ def amplitude(self, value):
+ self.query('RFLV:VALUE {0:f}V'.format(value))
+
+
+ @Feat(units='V')
+ def offset(self):
+ """Offset amplitude.
+ """
+ return self.parse_query('RFLV:OFFS?',
+ format=':RFLV:OFFS:VALUE {0:f};{_}')
+
+ @offset.setter
+ def offset(self, value):
+ self.query('RFLV:OFFS:VALUE {0:f}'.format(value))
+
+ @Feat(values={True: 'ENABLED', False: 'DISABLED'})
+ def output_enabled(self):
+ """Enable or disable the RF output
+ """
+ return self.query('OUTPUT?')
+
+ @output_enabled.setter
+ def output_enabled(self, value):
+ self.query('OUTPUT:{}'.format(value))
+
+ @Feat(units='deg')
+ def phase(self):
+ """Phase offset
+ """
+ return self.parse_query('CFRQ?',
+ format=':CFRQ:VALUE {:f}; INC {_};MODE {_}')
+
+ @phase.setter
+ def phase(self, value):
+ """Adjust phase offset of carrier in degrees.
+ """
+ self.send('CFRQ:PHASE {}'.format(value))
+
+ @Feat(values={'INT', 'EXT10DIR', 'EXTIND', 'EXT10IND', 'INT10OUT'})
+ def frequency_standard(self):
+ """Get internal or external frequency standard.
+ """
+ return self.query('FSTD?')
+
+ @frequency_standard.setter
+ def frequency_standard(self, value):
+ self.query('FSTD {}'.format(value))
+
+ @Feat()
+ def rflimit(self):
+ """Set RF output level max.
+ """
+ return self.query('*RFLV:LIMIT?')
+
+ @rflimit.setter
+ def rflimit(self, value):
+ self.query('RFLV:LIMIT {}'.format(value))
+
+ @Feat(values={True: 'ENABLED', False: 'DISABLED'})
+ def rflimit_enabled(self):
+ return self.query('*RFLV:LIMIT?')
+
+ @rflimit_enabled.setter
+ def rflimit_enabled(self, value):
+ self.query('RFLV:LIMIT:{}'.format(value))
+
+ def remote(self, value):
+ if value:
+ self.send('^A')
+ else:
+ self.send('^D')
+
+ @Action(units='ms')
+ def expose(self, exposure_time=1):
+ self.send('EXPOSE {}'.format(exposure_time))
+
+ @Feat(values={True: 'on', False: 'off'})
+ def time(self):
+ #TODO: Command??
+ self.send('')
+ return self.recv()
+
+ @time.setter
+ def time(self, value):
+ self.send("vlal ".format(value))
+
+ def local_lockout(self, value):
+ if value:
+ self.send('^R')
+ else:
+ self.send('^P')
+
+ def software_handshake(self, value):
+ if value:
+ self.send('^Q')
+ else:
+ self.send('^S')
+
+
+if __name__ == '__main__':
+ import argparse
+ import lantz.log
+
+ parser = argparse.ArgumentParser(description='Test Kentech HRI')
+ parser.add_argument('-i', '--interactive', action='store_true',
+ default=False, help='Show interactive GUI')
+ parser.add_argument('-p', '--port', type=str, default='17',
+ help='Serial port to connect to')
+
+ args = parser.parse_args()
+ lantz.log.log_to_socket(lantz.log.DEBUG)
+ with A2023a(args.port) as inst:
+ if args.interactive:
+ from lantz.ui.app import start_test_app
+ start_test_app(inst)
+ else:
+ print(inst.idn)
+ inst.fstd = "EXT10DIR"
+ print(inst.fstd)
+ print(inst.freq)
+ inst.freq = 41.006
+ print(inst.rflevel)
+ inst.rflevel = -13
+ inst.phase=0
+ print(inst.phase)
+ inst.phase=30
+ print(inst.phase)
+ inst.phase=60
+ print(inst.phase)
+
diff --git a/lantz/drivers/legacy/andor/__init__.py b/lantz/drivers/legacy/andor/__init__.py
new file mode 100644
index 0000000..0c92c36
--- /dev/null
+++ b/lantz/drivers/legacy/andor/__init__.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.andor
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ :company: Andor
+ :description: Scientific cameras.
+ :website: http://www.andor.com/
+
+ ----
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from .andor import Andor
+from .neo import Neo
+from .ccd import CCD
+
+__all__ = ['Andor', 'Neo', 'CCD']
+
diff --git a/lantz/drivers/legacy/andor/andor.py b/lantz/drivers/legacy/andor/andor.py
new file mode 100644
index 0000000..a84724b
--- /dev/null
+++ b/lantz/drivers/legacy/andor/andor.py
@@ -0,0 +1,269 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.andor.andor
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Low level driver wrapping atcore andor library.
+
+
+ Sources::
+
+ - Andor Manual
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+import ctypes as ct
+
+from lantz import Driver, Feat, Action
+from lantz.errors import InstrumentError
+from lantz.foreign import LibraryDriver
+
+_ERRORS = {
+ 0: 'SUCCESS',
+ 1: 'AT_ERR_NOTINITIALISED',
+ #1: 'AT_HANDLE_SYSTEM', # TODO: Check twice the same key!
+ 2: 'AT_ERR_NOTIMPLEMENTED',
+ 3: 'AT_ERR_READONLY',
+ 4: 'AT_ERR_NOTREADABLE',
+ 5: 'AT_ERR_NOTWRITABLE',
+ 6: 'AT_ERR_OUTOFRANGE',
+ 7: 'AT_ERR_INDEXNOTAVAILABLE',
+ 8: 'AT_ERR_INDEXNOTIMPLEMENTED',
+ 9: 'AT_ERR_EXCEEDEDMAXSTRINGLENGTH',
+ 10: 'AT_ERR_CONNECTION',
+ 11: 'AT_ERR_NODATA',
+ 12: 'AT_ERR_INVALIDHANDLE',
+ 13: 'AT_ERR_TIMEDOUT',
+ 14: 'AT_ERR_BUFFERFULL',
+ 15: 'AT_ERR_INVALIDSIZE',
+ 16: 'AT_ERR_INVALIDALIGNMENT',
+ 17: 'AT_ERR_COMM',
+ 18: 'AT_ERR_STRINGNOTAVAILABLE',
+ 19: 'AT_ERR_STRINGNOTIMPLEMENTED',
+ 20: 'AT_ERR_NULL_FEATURE',
+ 21: 'AT_ERR_NULL_HANDLE',
+ 22: 'AT_ERR_NULL_IMPLEMENTED_VAR',
+ 23: 'AT_ERR_NULL_READABLE_VAR',
+ 24: 'AT_ERR_NULL_READONLY_VAR',
+ 25: 'AT_ERR_NULL_WRITABLE_VAR',
+ 26: 'AT_ERR_NULL_MINVALUE',
+ 27: 'AT_ERR_NULL_MAXVALUE',
+ 28: 'AT_ERR_NULL_VALUE',
+ 29: 'AT_ERR_NULL_STRING',
+ 30: 'AT_ERR_NULL_COUNT_VAR',
+ 31: 'AT_ERR_NULL_ISAVAILABLE_VAR',
+ 32: 'AT_ERR_NULL_MAXSTRINGLENGTH',
+ 33: 'AT_ERR_NULL_EVCALLBACK',
+ 34: 'AT_ERR_NULL_QUEUE_PTR',
+ 35: 'AT_ERR_NULL_WAIT_PTR',
+ 36: 'AT_ERR_NULL_PTRSIZE',
+ 37: 'AT_ERR_NOMEMORY',
+ 100: 'AT_ERR_HARDWARE_OVERFLOW',
+ -1: 'AT_HANDLE_UNINITIALISED'
+}
+
+class Andor(LibraryDriver):
+
+ LIBRARY_NAME = 'atcore.dll'
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.AT_H = ct.c_int()
+ self.AT_U8 = ct.c_ubyte()
+ self.cameraIndex = ct.c_int(0)
+
+ def _patch_functions(self):
+ internal = self.lib.internal
+ internal.AT_Command.argtypes = [ct.c_int, ct.c_wchar_p, ]
+
+ internal.AT_GetInt.argtypes = [ct.c_int, ct.c_wchar_p, ct.addressof(ct.c_longlong)]
+ internal.AT_SetInt.argtypes = [ct.c_int, ct.c_wchar_p, ct.c_longlong]
+
+ internal.AT_GetFloat.argtypes = [ct.c_int, ct.c_wchar_p, ct.addressof(ct.c_double)]
+ internal.AT_SetFloat.argtypes = [ct.c_int, ct.c_wchar_p, ct.c_double]
+
+ internal.AT_GetBool.argtypes = [ct.c_int, ct.c_wchar_p, ct.addressof(ct.c_bool)]
+ internal.AT_SetBool.argtypes = [ct.c_int, ct.c_wchar_p, ct.c_bool]
+
+ internal.AT_GetEnumerated.argtypes = [ct.c_int, ct.c_wchar_p, ct.addressof(ct.c_int)]
+ internal.AT_SetEnumerated.argtypes = [ct.c_int, ct.c_wchar_p, ct.c_int]
+
+ internal.AT_SetEnumString.argtypes = [ct.c_int, ct.c_wchar_p, ct.c_wchar_p]
+
+ def _return_handler(self, func_name, ret_value):
+ if ret_value != 0:
+ raise InstrumentError('{} ({})'.format(ret_value, _ERRORS[ret_value]))
+ return ret_value
+
+ def initialize(self):
+ """Initialize Library.
+ """
+ self.lib.AT_InitialiseLibrary()
+ self.open()
+
+ def finalize(self):
+ """Finalize Library. Concluding function.
+ """
+ self.close()
+ self.lib.AT_FinaliseLibrary()
+
+ @Action()
+ def open(self):
+ """Open camera self.AT_H.
+ """
+ camidx = ct.c_int(0)
+ self.lib.AT_Open(camidx, ct.addressof(self.AT_H))
+ return self.AT_H
+
+ @Action()
+ def close(self):
+ """Close camera self.AT_H.
+ """
+ self.lib.AT_Close(self.AT_H)
+
+ def is_implemented(self, strcommand):
+ """Checks if command is implemented.
+ """
+ result = ct.c_bool()
+ command = ct.c_wchar_p(strcommand)
+ self.lib.AT_IsImplemented(self.AT_H, command, ct.addressof(result))
+ return result.value
+
+ def is_writable(self, strcommand):
+ """Checks if command is writable.
+ """
+ result = ct.c_bool()
+ command = ct.c_wchar_p(strcommand)
+ self.lib.AT_IsWritable(self.AT_H, command, ct.addressof(result))
+ return result.value
+
+ def queuebuffer(self, bufptr, value):
+ """Put buffer in queue.
+ """
+ value = ct.c_int(value)
+ self.lib.AT_QueueBuffer(self.AT_H, ct.byref(bufptr), value)
+
+ def waitbuffer(self, ptr, bufsize):
+ """Wait for next buffer ready.
+ """
+ timeout = ct.c_int(20000)
+ self.lib.AT_WaitBuffer(self.AT_H, ct.byref(ptr), ct.byref(bufsize), timeout)
+
+ def command(self, strcommand):
+ """Run command.
+ """
+ command = ct.c_wchar_p(strcommand)
+ self.lib.AT_Command(self.AT_H, command)
+
+ def getint(self, strcommand):
+ """Run command and get Int return value.
+ """
+ result = ct.c_longlong()
+ command = ct.c_wchar_p(strcommand)
+ self.lib.AT_GetInt(self.AT_H, command, ct.addressof(result))
+ return result.value
+
+ def setint(self, strcommand, value):
+ """SetInt function.
+ """
+ command = ct.c_wchar_p(strcommand)
+ value = ct.c_longlong(value)
+ self.lib.AT_SetInt(self.AT_H, command, value)
+
+ def getfloat(self, strcommand):
+ """Run command and get Int return value.
+ """
+ result = ct.c_double()
+ command = ct.c_wchar_p(strcommand)
+ self.lib.AT_GetFloat(self.AT_H, command, ct.addressof(result))
+ return result.value
+
+ def setfloat(self, strcommand, value):
+ """Set command with Float value parameter.
+ """
+ command = ct.c_wchar_p(strcommand)
+ value = ct.c_double(value)
+ self.lib.AT_SetFloat(self.AT_H, command, value)
+
+ def getbool(self, strcommand):
+ """Run command and get Bool return value.
+ """
+ result = ct.c_bool()
+ command = ct.c_wchar_p(strcommand)
+ self.lib.AT_GetBool(self.AT_H, command, ct.addressof(result))
+ return result.value
+
+ def setbool(self, strcommand, value):
+ """Set command with Bool value parameter.
+ """
+ command = ct.c_wchar_p(strcommand)
+ value = ct.c_bool(value)
+ self.lib.AT_SetBool(self.AT_H, command, value)
+
+ def getenumerated(self, strcommand):
+ """Run command and set Enumerated return value.
+ """
+ result = ct.c_int()
+ command = ct.c_wchar_p(strcommand)
+ self.lib.AT_GetEnumerated(self.AT_H, command, ct.addressof(result))
+
+ def setenumerated(self, strcommand, value):
+ """Set command with Enumerated value parameter.
+ """
+ command = ct.c_wchar_p(strcommand)
+ value = ct.c_bool(value)
+ self.lib.AT_SetEnumerated(self.AT_H, command, value) #TODO: IS THIS CORRECT
+
+ def setenumstring(self, strcommand, item):
+ """Set command with EnumeratedString value parameter.
+ """
+ command = ct.c_wchar_p(strcommand)
+ item = ct.c_wchar_p(item)
+ self.lib.AT_SetEnumString(self.AT_H, command, item)
+
+ def flush(self):
+ self.lib.AT_Flush(self.AT_H)
+
+if __name__ == '__main__':
+ import numpy as np
+ import ctypes as ct
+ from andor import Andor
+ from matplotlib import pyplot as plt
+
+ with Andor() as andor:
+ andor.flush()
+ width = andor.getint("SensorWidth")
+ height = andor.getint("SensorHeight")
+ length = width * height
+
+ #andor.setenumerated("FanSpeed", 2)
+ andor.getfloat("SensorTemperature")
+ andor.setfloat("ExposureTime", 0.001)
+ andor.setenumstring("PixelReadoutRate", "100 MHz")
+ andor.setenumstring("PixelEncoding", "Mono32")
+ #andor.setenumstring("PixelEncoding", "Mono16")
+
+ imagesizebytes = andor.getint("ImageSizeBytes")
+
+ userbuffer = ct.create_string_buffer(' ' * imagesizebytes)
+ andor.queuebuffer(userbuffer, imagesizebytes)
+
+ imsize = ct.c_int(1)
+ ubuffer = ct.create_string_buffer(" " * 1)
+
+ andor.command("AcquisitionStart")
+ andor.waitbuffer(ubuffer, imsize)
+ andor.command("AcquisitionStop")
+ andor.flush()
+
+ image = np.fromstring(userbuffer, dtype=np.uint32, count=length)
+ #image = np.fromstring(userbuffer, dtype=np.uint16, count=length)
+ image.shape = (height, width)
+
+ im = plt.imshow(image, cmap = 'gray')
+ plt.show()
+
+ print(image.min(), image.max(), image.mean())
+
diff --git a/lantz/drivers/legacy/andor/ccd.py b/lantz/drivers/legacy/andor/ccd.py
new file mode 100644
index 0000000..0f1e675
--- /dev/null
+++ b/lantz/drivers/legacy/andor/ccd.py
@@ -0,0 +1,1939 @@
+# -*- coding: utf-8 -*-
+# pylint: disable=E265
+
+"""
+ lantz.drivers.legacy.andor.ccd
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Low level driver wrapping library for CCD and Intensified CCD cameras.
+ Only functions for iXon EMCCD cameras were tested.
+ Only tested in Windows OS.
+
+ The driver was written for the single-camera scenario. If more than one
+ camera is present, some 'read_once=True' should be erased but it
+ shouldn't be necessary to make any more changes.
+
+ Sources::
+
+ - Andor SDK 2.96 Manual
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+import numpy as np
+import ctypes as ct
+from collections import namedtuple
+
+from lantz import Driver, Feat, Action, DictFeat
+from lantz.errors import InstrumentError
+from lantz.foreign import LibraryDriver
+from lantz import Q_
+
+degC = Q_(1, 'degC')
+us = Q_(1, 'us')
+MHz = Q_(1, 'MHz')
+seg = Q_(1, 's')
+
+_ERRORS = {
+ 20002: 'DRV_SUCCESS',
+ 20003: 'DRV_VXDNOTINSTALLED',
+ 20004: 'DRV_ERROR_SCAN',
+ 20005: 'DRV_ERROR_CHECK_SUM',
+ 20006: 'DRV_ERROR_FILELOAD',
+ 20007: 'DRV_UNKNOWN_FUNCTION',
+ 20008: 'DRV_ERROR_VXD_INIT',
+ 20009: 'DRV_ERROR_ADDRESS',
+ 20010: 'DRV_ERROR_PAGELOCK',
+ 20011: 'DRV_ERROR_PAGE_UNLOCK',
+ 20012: 'DRV_ERROR_BOARDTEST',
+ 20013: 'Unable to communicate with card.',
+ 20014: 'DRV_ERROR_UP_FIFO',
+ 20015: 'DRV_ERROR_PATTERN',
+ 20017: 'DRV_ACQUISITION_ERRORS',
+ 20018: 'Computer unable to read the data via the ISA slot at the required rate.',
+ 20019: 'DRV_ACQ_DOWNFIFO_FULL',
+ 20020: 'RV_PROC_UNKNOWN_INSTRUCTION',
+ 20021: 'DRV_ILLEGAL_OP_CODE',
+ 20022: 'Unable to meet Kinetic cycle time.',
+ 20023: 'Unable to meet Accumulate cycle time.',
+ 20024: 'No acquisition has taken place',
+ 20026: 'Overflow of the spool buffer.',
+ 20027: 'DRV_SPOOLSETUPERROR',
+ 20033: 'DRV_TEMPERATURE_CODES',
+ 20034: 'Temperature is OFF.',
+ 20035: 'Temperature reached but not stabilized.',
+ 20036: 'Temperature has stabilized at set point.',
+ 20037: 'Temperature has not reached set point.',
+ 20038: 'DRV_TEMPERATURE_OUT_RANGE',
+ 20039: 'DRV_TEMPERATURE_NOT_SUPPORTED',
+ 20040: 'Temperature had stabilized but has since drifted.',
+ 20049: 'DRV_GENERAL_ERRORS',
+ 20050: 'DRV_INVALID_AUX',
+ 20051: 'DRV_COF_NOTLOADED',
+ 20052: 'DRV_FPGAPROG',
+ 20053: 'DRV_FLEXERROR',
+ 20054: 'DRV_GPIBERROR',
+ 20064: 'DRV_DATATYPE',
+ 20065: 'DRV_DRIVER_ERRORS',
+ 20066: 'Invalid parameter 1',
+ 20067: 'Invalid parameter 2',
+ 20068: 'Invalid parameter 3',
+ 20069: 'Invalid parameter 4',
+ 20070: 'DRV_INIERROR',
+ 20071: 'DRV_COFERROR',
+ 20072: 'Acquisition in progress',
+ 20073: 'The system is not currently acquiring',
+ 20074: 'DRV_TEMPCYCLE',
+ 20075: 'System not initialized',
+ 20076: 'DRV_P5INVALID',
+ 20077: 'DRV_P6INVALID',
+ 20078: 'Not a valid mode',
+ 20079: 'DRV_INVALID_FILTER',
+ 20080: 'DRV_I2CERRORS',
+ 20081: 'DRV_DRV_I2CDEVNOTFOUND',
+ 20082: 'DRV_I2CTIMEOUT',
+ 20083: 'DRV_P7INVALID',
+ 20089: 'DRV_USBERROR',
+ 20090: 'DRV_IOCERROR',
+ 20091: 'DRV_VRMVERSIONERROR',
+ 20093: 'DRV_USB_INTERRUPT_ENDPOINT_ERROR',
+ 20094: 'DRV_RANDOM_TRACK_ERROR',
+ 20095: 'DRV_INVALID_TRIGGER_MODE',
+ 20096: 'DRV_LOAD_FIRMWARE_ERROR',
+ 20097: 'DRV_DIVIDE_BY_ZERO_ERROR',
+ 20098: 'DRV_INVALID_RINGEXPOSURES',
+ 20099: 'DRV_BINNING_ERROR',
+ 20990: 'No camera present',
+ 20991: 'Feature not supported on this camera.',
+ 20992: 'Feature is not available at the moment.',
+ 20115: 'DRV_ERROR_MAP',
+ 20116: 'DRV_ERROR_UNMAP',
+ 20117: 'DRV_ERROR_MDL',
+ 20118: 'DRV_ERROR_UNMDL',
+ 20119: 'DRV_ERROR_BUFFSIZE',
+ 20121: 'DRV_ERROR_NOHANDLE',
+ 20130: 'DRV_GATING_NOT_AVAILABLE',
+ 20131: 'DRV_FPGA_VOLTAGE_ERROR',
+ 20100: 'DRV_INVALID_AMPLIFIER',
+ 20101: 'DRV_INVALID_COUNTCONVERT_MODE'
+}
+
+
+class CCD(LibraryDriver):
+
+ LIBRARY_NAME = 'atmcd64d.dll'
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.cameraIndex = ct.c_int(0)
+
+ def _patch_functions(self):
+ internal = self.lib.internal
+ internal.GetCameraSerialNumber.argtypes = [ct.pointer(ct.c_uint)]
+ internal.Filter_SetAveragingFactor.argtypes = [ct.c_int]
+ internal.Filter_SetThreshold.argtypes = ct.c_float
+ internal.Filter_GetThreshold.argtypes = ct.c_float
+
+ def _return_handler(self, func_name, ret_value):
+ excl_func = ['GetTemperatureF', 'IsCountConvertModeAvailable',
+ 'IsAmplifierAvailable', 'IsTriggerModeAvailable']
+ if ret_value != 20002 and func_name not in excl_func:
+ raise InstrumentError('{}'.format(_ERRORS[ret_value]))
+ return ret_value
+
+ def initialize(self):
+ """ This function will initialize the Andor SDK System. As part of the
+ initialization procedure on some cameras (i.e. Classic, iStar and
+ earlier iXion) the DLL will need access to a DETECTOR.INI which
+ contains information relating to the detector head, number pixels,
+ readout speeds etc. If your system has multiple cameras then see the
+ section Controlling multiple cameras.
+ """
+ self.lib.Initialize()
+
+ self.triggers = {'Internal': 0, 'External': 1, 'External Start': 6,
+ 'External Exposure': 7, 'External FVB EM': 9,
+ 'Software Trigger': 10,
+ 'External Charge Shifting': 12}
+ self.savetypes = {'Signed16bits': 1, 'Signed32bits': 2, 'Float': 3}
+
+ # Initial values
+
+ self.readout_packing_state = False
+ self.readout_packing = self.readout_packing_state
+
+ self.readout_mode_mode = 'Image'
+ self.readout_mode = self.readout_mode_mode
+
+ self.photon_counting_mode_state = False
+ self.photon_counting_mode = self.photon_counting_mode_state
+
+ self.frame_transfer_mode_state = False
+ self.frame_transfer_mode = self.frame_transfer_mode_state
+
+ self.fan_mode_index = 'onfull'
+ self.fan_mode = self.fan_mode_index
+
+ self.EM_gain_mode_index = 'DAC255'
+ self.EM_gain_mode = self.EM_gain_mode_index
+
+ self.cooled_on_shutdown_value = False
+ self.cooled_on_shutdown = self.cooled_on_shutdown_value
+
+ self.baseline_offset_value = 100
+ self.baseline_offset = self.baseline_offset_value
+
+ self.adv_trigger_mode_state = True
+ self.adv_trigger_mode = self.adv_trigger_mode_state
+
+ self.acq_mode = 'Single Scan'
+ self.acquisition_mode = self.acq_mode
+
+ self.amp_typ = 0
+
+ self.horiz_shift_speed_index = 0
+ self.horiz_shift_speed = self.horiz_shift_speed_index
+
+ self.vert_shift_speed_index = 0
+ self.vert_shift_speed = self.vert_shift_speed_index
+
+ self.preamp_index = 0
+ self.preamp = self.preamp_index
+
+ self.temperature_sp = 0 * degC
+ self.temperature_setpoint = self.temperature_sp
+
+ self.auxout = np.zeros(4, dtype=bool)
+ for i in np.arange(1, 5):
+ self.out_aux_port[i] = False
+
+ self.trigger_mode_index = 'Internal'
+ self.trigger_mode = self.trigger_mode_index
+
+ def finalize(self):
+ """Finalize Library. Concluding function.
+ """
+ if self.status != 'Camera is idle, waiting for instructions.':
+ self.abort_acquisition()
+ self.cooler_on = False
+ self.free_int_mem()
+ self.lib.ShutDown()
+
+ ### SYSTEM INFORMATION
+
+ @Feat(read_once=True)
+ def ncameras(self):
+ """This function returns the total number of Andor cameras currently
+ installed. It is possible to call this function before any of the
+ cameras are initialized.
+ """
+ n = ct.c_long()
+ self.lib.GetAvailableCameras(ct.pointer(n))
+ return n.value
+
+ def camera_handle(self, index):
+ """This function returns the handle for the camera specified by
+ cameraIndex. When multiple Andor cameras are installed the handle of
+ each camera must be retrieved in order to select a camera using the
+ SetCurrentCamera function.
+ The number of cameras can be obtained using the GetAvailableCameras
+ function.
+ Parameters
+ long cameraIndex: index of any of the installed cameras. Valid values:
+ 0 to NumberCameras-1 where NumberCameras is the value returned by
+ the GetAvailableCameras function.
+ """
+ index = ct.c_long(index)
+ handle = ct.c_long()
+ self.lib.GetCameraHandle(index, ct.pointer(handle))
+ return handle.value
+
+ @Feat()
+ def current_camera(self):
+ """When multiple Andor cameras are installed this function allows the
+ user to select which camera is currently active. Once a camera has been
+ selected the other functions can be called as normal but they will only
+ apply to the selected camera. If only 1 camera is installed calling
+ this function is not required since that camera will be selected by
+ default.
+ """
+ n = ct.c_long() # current camera handler
+ self.lib.GetCurrentCamera(ct.pointer(n))
+ return n.value
+
+ @current_camera.setter
+ def current_camera(self, value):
+ value = ct.c_long(value)
+ self.lib.SetCurrentCamera(value.value) # needs camera handler
+
+ @Feat(read_once=True)
+ def idn(self):
+ """Identification of the device
+ """
+ hname = (ct.c_char * 100)()
+ self.lib.GetHeadModel(ct.pointer(hname))
+ hname = str(hname.value)[2:-1]
+ sn = ct.c_uint()
+ self.lib.GetCameraSerialNumber(ct.pointer(sn))
+ return 'Andor ' + hname + ', serial number ' + str(sn.value)
+
+ @Feat(read_once=True)
+ def hardware_version(self):
+ pcb, decode = ct.c_uint(), ct.c_uint()
+ dummy1, dummy2 = ct.c_uint(), ct.c_uint()
+ firmware_ver, firmware_build = ct.c_uint(), ct.c_uint()
+ self.lib.GetHardwareVersion(ct.pointer(pcb), ct.pointer(decode),
+ ct.pointer(dummy1), ct.pointer(dummy2),
+ ct.pointer(firmware_ver),
+ ct.pointer(firmware_build))
+ results = namedtuple('hardware_versions',
+ 'PCB Flex10K CameraFirmware CameraFirmwareBuild')
+ return results(pcb.value, decode.value, firmware_ver.value,
+ firmware_build.value)
+
+ @Feat(read_once=True)
+ def software_version(self):
+ eprom, coffile, vxdrev = ct.c_uint(), ct.c_uint(), ct.c_uint()
+ vxdver, dllrev, dllver = ct.c_uint(), ct.c_uint(), ct.c_uint()
+ self.lib.GetSoftwareVersion(ct.pointer(eprom), ct.pointer(coffile),
+ ct.pointer(vxdrev), ct.pointer(vxdver),
+ ct.pointer(dllrev), ct.pointer(dllver))
+ results = namedtuple('software_versions',
+ 'EPROM COF DriverRev DriverVer DLLRev DLLVer')
+ return results(eprom.value, coffile.value, vxdrev.value,
+ vxdver.value, dllrev.value, dllver.value)
+
+ # TODO: Make sense of this:
+ @Feat(read_once=True)
+ def capabilities(self):
+ """This function will fill in an AndorCapabilities structure with the
+ capabilities associated with the connected camera. Individual
+ capabilities are determined by examining certain bits and combinations
+ of bits in the member variables of the AndorCapabilites structure.
+ """
+
+ class Capabilities(ct.Structure):
+ _fields_ = [("Size", ct.c_ulong),
+ ("AcqModes", ct.c_ulong),
+ ("ReadModes", ct.c_ulong),
+ ("FTReadModes", ct.c_ulong),
+ ("TriggerModes", ct.c_ulong),
+ ("CameraType", ct.c_ulong),
+ ("PixelModes", ct.c_ulong),
+ ("SetFunctions", ct.c_ulong),
+ ("GetFunctions", ct.c_ulong),
+ ("Features", ct.c_ulong),
+ ("PCICard", ct.c_ulong),
+ ("EMGainCapability", ct.c_ulong)]
+
+ stru = Capabilities()
+ stru.Size = ct.sizeof(stru)
+ self.lib.GetCapabilities(ct.pointer(stru))
+
+ return stru
+
+ @Feat(read_once=True)
+ def controller_card(self):
+ """This function will retrieve the type of PCI controller card included
+ in your system. This function is not applicable for USB systems. The
+ maximum number of characters that can be returned from this function is
+ 10."""
+
+ model = ct.c_wchar_p()
+ self.lib.GetControllerCardModel(ct.pointer(model))
+
+ return model.value
+
+ @Feat(read_once=True)
+ def count_convert_wavelength_range(self):
+ """This function returns the valid wavelength range available in Count
+ Convert mode."""
+ mini = ct.c_float()
+ maxi = ct.c_float()
+ self.lib.GetCountConvertWavelengthRange(ct.pointer(mini),
+ ct.pointer(maxi))
+ return (mini.value, maxi.value)
+
+ @Feat(read_once=True)
+ def detector_shape(self):
+ xp, yp = ct.c_int(), ct.c_int()
+ self.lib.GetDetector(ct.pointer(xp), ct.pointer(yp))
+ return (xp.value, yp.value)
+
+ @Feat(read_once=True)
+ def px_size(self):
+ """ This function returns the dimension of the pixels in the detector
+ in microns.
+ """
+ xp, yp = ct.c_float(), ct.c_float()
+
+ self.lib.GetPixelSize(ct.pointer(xp), ct.pointer(yp))
+
+ return (xp.value, yp.value)
+
+ def QE(self, wl):
+ """ Returns the percentage QE for a particular head model at a user
+ specified wavelength.
+ """
+ hname = (ct.c_char * 100)()
+ self.lib.GetHeadModel(ct.pointer(hname))
+
+ wl = ct.c_float(wl)
+ qe = ct.c_float()
+
+ self.lib.GetQE(ct.pointer(hname), wl, ct.c_uint(0), ct.pointer(qe))
+
+ return qe.value
+
+ def sensitivity(self, ad, amp, i, pa):
+ """ This function returns the sensitivity for a particular speed."""
+ sens = ct.c_float()
+ ad, amp, i, pa = ct.c_int(ad), ct.c_int(amp), ct.c_int(i), ct.c_int(pa)
+ self.lib.GetSensitivity(ad, amp, i, pa, ct.pointer(sens))
+ return sens.value
+
+ def count_convert_available(self, mode):
+ """ This function checks if the hardware and current settings permit
+ the use of the specified Count Convert mode.
+ """
+ mode = ct.c_int(mode)
+ ans = self.lib.IsCountConvertModeAvailable(mode)
+ if ans == 20002:
+ return True
+ else:
+ return False
+
+ ### SHUTTER # I couldn't find a better way to do this... sorry
+ @Action()
+ def shutter(self, typ, mode, ext_closing, ext_opening, ext_mode):
+ """ This function expands the control offered by SetShutter to allow an
+ external shutter and internal shutter to be controlled independently
+ (only available on some cameras – please consult your Camera User
+ Guide). The typ parameter allows the user to control the TTL signal
+ output to an external shutter. The opening and closing times specify
+ the length of time required to open and close the shutter (this
+ information is required for calculating acquisition timings – see
+ SHUTTER TRANSFER TIME).
+ The mode and extmode parameters control the behaviour of the internal
+ and external shutters. To have an external shutter open and close
+ automatically in an experiment, set the mode parameter to “Open” and
+ set the extmode parameter to “Auto”. To have an internal shutter open
+ and close automatically in an experiment, set the extmode parameter to
+ “Open” and set the mode parameter to “Auto”.
+ To not use any shutter in the experiment, set both shutter modes to
+ permanently open.
+ Parameters
+ Int typ:
+ 0 Output TTL low signal to open shutter
+ 1 Output TTL high signal to open shutter
+ int mode:
+ 0 Fully Auto
+ 1 Permanently Open
+ 2 Permanently Closed
+ 4 Open for FVB series
+ 5 Open for any series
+ int closingtime: time shutter takes to close (milliseconds)
+ int openingtime: Time shutter takes to open (milliseconds)
+ int mode:
+ 0 Fully Auto
+ 1 Permanently Open
+ 2 Permanently Closed
+ 4 Open for FVB series
+ 5 Open for any series
+ """
+ self.lib.SetShutterEx(ct.c_int(typ), ct.c_int(mode),
+ ct.c_int(ext_closing), ct.c_int(ext_opening),
+ ct.c_int(ext_mode))
+
+ @Feat(read_once=True)
+ def shutter_min_times(self):
+ """ This function will return the minimum opening and closing times in
+ milliseconds for the shutter on the current camera.
+ """
+ otime, ctime = ct.c_int(), ct.c_int()
+ self.lib.GetShutterMinTimes(ct.pointer(ctime), ct.pointer(otime))
+ return (otime.value, ctime.value)
+
+ @Feat(read_once=True)
+ def has_mechanical_shutter(self):
+ state = ct.c_int()
+ self.lib.IsInternalMechanicalShutter(ct.pointer(state))
+ return bool(state.value)
+
+ ### TEMPERATURE
+
+ @Feat(read_once=True, units='degC')
+ def min_temperature(self):
+ """ This function returns the valid range of temperatures in centigrads
+ to which the detector can be cooled.
+ """
+ mini, maxi = ct.c_int(), ct.c_int()
+ self.lib.GetTemperatureRange(ct.pointer(mini), ct.pointer(maxi))
+ return mini.value
+
+ @Feat(read_once=True, units='degC')
+ def max_temperature(self):
+ """ This function returns the valid range of temperatures in centigrads
+ to which the detector can be cooled.
+ """
+ mini, maxi = ct.c_int(), ct.c_int()
+ self.lib.GetTemperatureRange(ct.pointer(mini), ct.pointer(maxi))
+ return maxi.value
+
+ @Feat()
+ def temperature_status(self):
+ """ This function returns the temperature of the detector to the
+ nearest degree. It also gives the status of cooling process.
+ """
+ temp = ct.c_float()
+ ans = self.lib.GetTemperatureF(ct.pointer(temp))
+ return _ERRORS[ans]
+
+ @Feat(units='degC')
+ def temperature(self):
+ """ This function returns the temperature of the detector to the
+ nearest degree. It also gives the status of cooling process.
+ """
+ temp = ct.c_float()
+ self.lib.GetTemperatureF(ct.pointer(temp))
+ return temp.value
+
+ @Feat(units='degC')
+ def temperature_setpoint(self):
+ return self.temperature_sp
+
+ @temperature_setpoint.setter
+ def temperature_setpoint(self, value):
+ self.temperature_sp = value
+ value = ct.c_int(int(value))
+ self.lib.SetTemperature(value)
+
+ @Feat(values={True: 1, False: 0})
+ def cooler_on(self):
+ state = ct.c_int()
+ self.lib.IsCoolerOn(ct.pointer(state))
+ return state.value
+
+ @cooler_on.setter
+ def cooler_on(self, value):
+ if value:
+ self.lib.CoolerON()
+ else:
+ self.lib.CoolerOFF()
+
+ @Feat(values={True: 1, False: 0})
+ def cooled_on_shutdown(self):
+ """ This function determines whether the cooler is switched off when
+ the camera is shut down.
+ """
+ return self.cooled_on_shutdown_value
+
+ @cooled_on_shutdown.setter
+ def cooled_on_shutdown(self, state):
+ ans = self.lib.SetCoolerMode(ct.c_int(state))
+ if ans == 20002:
+ self.cooled_on_shutdown_value = state
+
+ @Feat(values={'onfull': 0, 'onlow': 1, 'off': 2})
+ def fan_mode(self):
+ """ Allows the user to control the mode of the camera fan. If the
+ system is cooled, the fan should only be turned off for short periods
+ of time. During this time the body of the camera will warm up which
+ could compromise cooling capabilities.
+ If the camera body reaches too high a temperature, depends on camera,
+ the buzzer will sound. If this happens, turn off the external power
+ supply and allow the system to stabilize before continuing.
+ """
+ return self.fan_mode_index
+
+ @fan_mode.setter
+ def fan_mode(self, mode):
+ ans = self.lib.SetFanMode(ct.c_int(mode))
+ if ans == 20002:
+ self.fan_mode_index = mode
+
+ ### FILTERS
+
+ @Feat()
+ def averaging_factor(self):
+ """ Averaging factor to be used with the recursive filter. For
+ information on the various data averaging filters available see
+ DATA AVERAGING FILTERS in the Special Guides section of the manual.
+ """
+ af = ct.c_uint()
+ self.lib.Filter_GetAveragingFactor(ct.pointer(af))
+ return af.value
+
+ @averaging_factor.setter
+ def averaging_factor(self, value):
+ self.lib.Filter_SetAveragingFactor(ct.c_uint(value))
+
+ @Feat()
+ def averaging_frame_count(self):
+ """ Number of frames to be used when using the frame averaging filter.
+ """
+ fc = ct.c_uint()
+ self.lib.Filter_GetAveragingFrameCount(ct.pointer(fc))
+ return fc.value
+
+ @averaging_frame_count.setter
+ def averaging_frame_count(self, value):
+ self.lib.Filter_SetAveragingFrameCount(ct.c_uint(value))
+
+ @Feat(values={'NAF': 0, 'RAF': 5, 'FAF': 6})
+ def averaging_mode(self):
+ """ Current averaging mode.
+ Valid options are:
+ 0 – No Averaging Filter
+ 5 – Recursive Averaging Filter
+ 6 – Frame Averaging Filter
+ """
+ i = ct.c_int()
+ self.lib.Filter_GetDataAveragingMode(ct.pointer(i))
+ return i.value
+
+ @averaging_mode.setter
+ def averaging_mode(self, value):
+ self.lib.Filter_SetDataAveragingMode(ct.c_int(value))
+
+ @Feat(values={'NF': 0, 'MF': 1, 'LAF': 2, 'IRF': 3, 'NTF': 4})
+ def noise_filter_mode(self):
+ """ Set the Noise Filter to use; For information on the various
+ spurious noise filters available see SPURIOUS NOISE FILTERS in the
+ Special Guides section of the manual.
+ Valid options are:
+ 0 – No Averaging Filter
+ 1 – Median Filter
+ 2 – Level Above Filter
+ 3 – Interquartile Range Filter
+ 4 – Noise Threshold Filter
+ """
+ i = ct.c_uint()
+ self.lib.Filter_GetMode(ct.pointer(i))
+ return i.value
+
+ @noise_filter_mode.setter
+ def noise_filter_mode(self, value):
+ self.lib.Filter_SetMode(ct.c_uint(value))
+
+ @Feat()
+ def filter_threshold(self):
+ """ Sets the threshold value for the Noise Filter. For information on
+ the various spurious noise filters available see SPURIOUS NOISE FILTERS
+ in the Special Guides section of the manual.
+ Valid values are:
+ 0 – 65535 for Level Above filte
+ 0 – 10 for all other filters.
+ """
+ f = ct.c_float()
+ self.lib.Filter_GetThreshold(ct.pointer(f))
+ return f.value
+
+ @filter_threshold.setter
+ def filter_threshold(self, value):
+ self.lib.Filter_SetThreshold(ct.c_float(value))
+
+ @Feat(values={True: 2, False: 0})
+ def cr_filter_enabled(self):
+ """ This function will set the state of the cosmic ray filter mode for
+ future acquisitions. If the filter mode is on, consecutive scans in an
+ accumulation will be compared and any cosmic ray-like features that are
+ only present in one scan will be replaced with a scaled version of the
+ corresponding pixel value in the correct scan.
+ """
+ i = ct.c_int()
+ self.lib.GetFilterMode(ct.pointer(i))
+ return i.value
+
+ @cr_filter_enabled.setter
+ def cr_filter_enabled(self, value):
+ self.lib.SetFilterMode(ct.c_int(value))
+
+ ### PHOTON COUNTING MODE
+
+ @Feat(values={True: 1, False: 0}) # FIXME: untested
+ def photon_counting_mode(self):
+ """ This function activates the photon counting option.
+ """
+ return self.photon_counting_mode_state
+
+ @photon_counting_mode.setter
+ def photon_counting_mode(self, state):
+ ans = self.lib.SetPhotonCounting(ct.c_int(state))
+ if ans == 20002:
+ self.photon_counting_mode_state = state
+
+ @Feat(read_once=True)
+ def n_photon_counting_div(self):
+ """ Available in some systems is photon counting mode. This function
+ gets the number of photon counting divisions available. The functions
+ SetPhotonCounting and SetPhotonCountingThreshold can be used to specify
+ which of these divisions is to be used.
+ """
+ inti = ct.c_ulong()
+ self.lib.GetNumberPhotonCountingDivisions(ct.pointer(inti))
+ return inti.value
+
+ @Action() # untested
+ def set_photon_counting_divs(self, n, thres):
+ """ This function sets the thresholds for the photon counting option.
+ """
+ thres = ct.c_long(thres)
+ self.lib.SetPhotonCountingDivisions(ct.c_ulong(n), ct.pointer(thres))
+
+ @Action()
+ def set_photon_counting_thres(self, mini, maxi):
+ """ This function sets the minimum and maximum threshold in counts
+ (1-65535) for the photon counting option.
+ """
+ self.lib.SetPhotonCountingThreshold(ct.c_long(mini), ct.c_long(maxi))
+
+ ### FAST KINETICS MODE
+
+ @Feat(units='s')
+ def FK_exposure_time(self):
+ """This function will return the current “valid” exposure time for a
+ fast kinetics acquisition. This function should be used after all the
+ acquisitions settings have been set, i.e. SetFastKinetics and
+ SetFKVShiftSpeed. The value returned is the actual time used in
+ subsequent acquisitions.
+ """
+ f = ct.c_float()
+ self.lib.GetFKExposureTime(ct.pointer(f))
+ return f.value
+
+ ### ACQUISITION HANDLING
+
+ @Feat(values={'Single Scan': 1, 'Accumulate': 2, 'Kinetics': 3,
+ 'Fast Kinetics': 4, 'Run till abort': 5})
+ def acquisition_mode(self):
+ """ This function will set the acquisition mode to be used on the next
+ StartAcquisition.
+ NOTE: In Mode 5 the system uses a “Run Till Abort” acquisition mode. In
+ Mode 5 only, the camera continually acquires data until the
+ AbortAcquisition function is called. By using the SetDriverEvent
+ function you will be notified as each acquisition is completed.
+ """
+ return self.acq_mode
+
+ @acquisition_mode.setter
+ def acquisition_mode(self, mode):
+ ans = self.lib.SetAcquisitionMode(ct.c_int(mode))
+ if ans == 20002:
+ self.acq_mode = mode
+
+ @Action()
+ def prepare_acquisition(self):
+ """ This function reads the current acquisition setup and allocates and
+ configures any memory that will be used during the acquisition. The
+ function call is not required as it will be called automatically by the
+ StartAcquisition function if it has not already been called externally.
+ However for long kinetic series acquisitions the time to allocate and
+ configure any memory can be quite long which can result in a long delay
+ between calling StartAcquisition and the acquisition actually
+ commencing. For iDus, there is an additional delay caused by the camera
+ being set-up with any new acquisition parameters. Calling
+ PrepareAcquisition first will reduce this delay in the StartAcquisition
+ call.
+ """
+ self.lib.PrepareAcquisition()
+
+ @Action()
+ def start_acquisition(self):
+ """ This function starts an acquisition. The status of the acquisition
+ can be monitored via GetStatus().
+ """
+ self.lib.StartAcquisition()
+
+ @Action()
+ def abort_acquisition(self):
+ """This function aborts the current acquisition if one is active
+ """
+ self.lib.AbortAcquisition()
+
+ @Action()
+ def wait_for_acquisition(self):
+ """ WaitForAcquisition can be called after an acquisition is started
+ using StartAcquisition to put the calling thread to sleep until an
+ Acquisition Event occurs. This can be used as a simple alternative to
+ the functionality provided by the SetDriverEvent function, as all Event
+ creation and handling is performed internally by the SDK library.
+ Like the SetDriverEvent functionality it will use less processor
+ resources than continuously polling with the GetStatus function. If you
+ wish to restart the calling thread without waiting for an Acquisition
+ event, call the function CancelWait.
+ An Acquisition Event occurs each time a new image is acquired during an
+ Accumulation, Kinetic Series or Run-Till-Abort acquisition or at the
+ end of a Single Scan Acquisition.
+ If a second event occurs before the first one has been acknowledged,
+ the first one will be ignored. Care should be taken in this case, as
+ you may have to use CancelWait to exit the function.
+ """
+ self.lib.WaitForAcquisition()
+
+ @Action()
+ def cancel_wait(self):
+ """This function restarts a thread which is sleeping within the
+ WaitForAcquisition function. The sleeping thread will return from
+ WaitForAcquisition with a value not equal to DRV_SUCCESS.
+ """
+ self.lib.CancelWait()
+
+ @Feat()
+ def acquisition_progress(self):
+ """ This function will return information on the progress of the
+ current acquisition. It can be called at any time but is best used in
+ conjunction with SetDriverEvent.
+ The values returned show the number of completed scans in the current
+ acquisition. If 0 is returned for both accum and series then either:
+ - No acquisition is currently running
+ - The acquisition has just completed
+ - The very first scan of an acquisition has just started and not yet
+ completed.
+ GetStatus can be used to confirm if the first scan has just started,
+ returning DRV_ACQUIRING, otherwise it will return DRV_IDLE.
+ For example, if accum=2 and series=3 then the acquisition has completed
+ 3 in the series and 2 accumulations in the 4 scan of the series
+ """
+ acc = ct.c_long()
+ series = ct.c_long()
+ self.lib.GetAcquisitionProgress(ct.pointer(acc), ct.pointer(series))
+ return acc.value, series.value
+
+ @Feat()
+ def status(self):
+ """ This function will return the current status of the Andor SDK
+ system. This function should be called before an acquisition is started
+ to ensure that it is IDLE and during an acquisition to monitor the
+ process.
+ """
+ st = ct.c_int()
+ self.lib.GetStatus(ct.pointer(st))
+ if st.value == 20073:
+ return 'Camera is idle, waiting for instructions.'
+ elif st.value == 20074:
+ return 'Camera is executing the temperature cycle.'
+ elif st.value == 20072:
+ return 'Acquisition in progress.'
+ elif st.value == 20023:
+ return 'Unable to meet accumulate cycle time.'
+ elif st.value == 20022:
+ return 'Unable to meet kinetic cycle time.'
+ elif st.value == 20013:
+ return 'Unable to communicate with card.'
+ elif st.value == 20018:
+ return ('Computer unable to read the data via the ISA slot at the '
+ 'required rate.')
+ elif st.value == 20026:
+ return 'Overflow of the spool buffer.'
+
+ @Feat()
+ def n_exposures_in_ring(self):
+ """ Gets the number of exposures in the ring at this moment."""
+ n = ct.c_int()
+ self.lib.GetNumberRingExposureTimes(ct.pointer(n))
+ return n.value
+
+ @Feat()
+ def buffer_size(self):
+ """ This function will return the maximum number of images the circular
+ buffer can store based on the current acquisition settings.
+ """
+ n = ct.c_long()
+ self.lib.GetSizeOfCircularBuffer(ct.pointer(n))
+ return n.value
+
+ @Feat(values={True: 1, False: 0})
+ def exposing(self):
+ """ This function will return if the system is exposing or not. The
+ status of the firepulse will be returned.
+ NOTE This is only supported by the CCI23 card.
+ """
+ i = ct.c_int()
+ self.lib.GetCameraEventStatus(ct.pointer(i))
+ return i.value
+
+ @Feat()
+ def n_images_acquired(self):
+ """ This function will return the total number of images acquired since
+ the current acquisition started. If the camera is idle the value
+ returned is the number of images acquired during the last acquisition.
+ """
+ n = ct.c_long()
+ self.lib.GetTotalNumberImagesAcquired(ct.pointer(n))
+ return n.value
+
+ @Action()
+ def set_image(self, shape=None, binned=(1, 1), p_0=(1, 1)):
+ """ This function will set the horizontal and vertical binning to be
+ used when taking a full resolution image.
+ Parameters
+ int hbin: number of pixels to bin horizontally.
+ int vbin: number of pixels to bin vertically.
+ int hstart: Start column (inclusive).
+ int hend: End column (inclusive).
+ int vstart: Start row (inclusive).
+ int vend: End row (inclusive).
+ """
+
+ if shape is None:
+ shape = self.detector_shape
+
+ (hbin, vbin) = binned
+ (hstart, vstart) = p_0
+ (hend, vend) = (p_0[0] + shape[0] - 1, p_0[1] + shape[1] - 1)
+
+ self.lib.SetImage(ct.c_int(hbin), ct.c_int(vbin),
+ ct.c_int(hstart), ct.c_int(hend),
+ ct.c_int(vstart), ct.c_int(vend))
+
+ @Feat(values={'FVB': 0, 'Multi-Track': 1, 'Random-Track': 2,
+ 'Single-Track': 3, 'Image': 4})
+ def readout_mode(self):
+ """ This function will set the readout mode to be used on the subsequent
+ acquisitions.
+ """
+ return self.readout_mode_mode
+
+ @readout_mode.setter
+ def readout_mode(self, mode):
+ ans = self.lib.SetReadMode(ct.c_int(mode))
+ if ans == 20002:
+ self.readout_mode_mode = mode
+
+ @Feat(values={True: 1, False: 0})
+ def readout_packing(self):
+ """ This function will configure whether data is packed into the readout
+ register to improve frame rates for sub-images.
+ Note: It is important to ensure that no light falls outside of the
+ sub-image area otherwise the acquired data will be corrupted. Only
+ currently available on iXon+ and iXon3.
+ """
+ return self.readout_packing_state
+
+ @readout_packing.setter
+ def readout_packing(self, state):
+ ans = self.lib.SetReadoutRegisterPacking(ct.c_int(state))
+ if ans == 20002:
+ self.readout_packing_state = state
+
+ ### DATA HANDLING
+
+ @Feat(read_once=True)
+ def min_image_length(self):
+ """ This function will return the minimum number of pixels that can be
+ read out from the chip at each exposure. This minimum value arises due
+ the way in which the chip is read out and will limit the possible sub
+ image dimensions and binning sizes that can be applied.
+ Parameters
+ int* MinImageLength: Will contain the minimum number of super
+ pixels on return.
+ """
+ px = ct.c_int()
+ self.lib.GetMinimumImageLength(ct.pointer(px))
+
+ return px.value
+
+ @Action()
+ def free_int_mem(self):
+ """The FreeInternalMemory function will deallocate any memory used
+ internally to store the previously acquired data. Note that once this
+ function has been called, data from last acquisition cannot be
+ retrived.
+ """
+ self.lib.FreeInternalMemory()
+
+ def acquired_data(self, shape):
+ """ This function will return the data from the last acquisition. The
+ data are returned as long integers (32-bit signed integers). The
+ “array” must be large enough to hold the complete data set.
+ """
+ size = np.array(shape).prod()
+ arr = np.ascontiguousarray(np.zeros(size, dtype=np.int32))
+ self.lib.GetAcquiredData(arr.ctypes.data_as(ct.POINTER(ct.c_int32)),
+ ct.c_ulong(size))
+ arr = arr.reshape(shape)
+ return arr
+
+ def acquired_data16(self, shape):
+ """ 16-bit version of the GetAcquiredData function. The “array” must be
+ large enough to hold the complete data set.
+ """
+ size = np.array(shape).prod()
+ arr = np.ascontiguousarray(np.zeros(size, dtype=np.int16))
+ self.lib.GetAcquiredData16(arr.ctypes.data_as(ct.POINTER(ct.c_int16)),
+ ct.c_ulong(size))
+ return arr.reshape(shape)
+
+ def oldest_image(self, shape):
+ """ This function will update the data array with the oldest image in
+ the circular buffer. Once the oldest image has been retrieved it no
+ longer is available. The data are returned as long integers (32-bit
+ signed integers). The "array" must be exactly the same size as the full
+ image.
+ """
+ size = np.array(shape).prod()
+ array = np.ascontiguousarray(np.zeros(size, dtype=np.int32))
+ self.lib.GetOldestImage(array.ctypes.data_as(ct.POINTER(ct.c_int32)),
+ ct.c_ulong(size))
+ return array.reshape(shape)
+
+ def oldest_image16(self, shape):
+ """ 16-bit version of the GetOldestImage function.
+ """
+ size = np.array(shape).prod()
+ array = np.ascontiguousarray(np.zeros(size, dtype=np.int16))
+ self.lib.GetOldestImage16(array.ctypes.data_as(ct.POINTER(ct.c_int16)),
+ ct.c_ulong(size))
+ return array.reshape(shape)
+
+ def most_recent_image(self, shape):
+ """ This function will update the data array with the most recently
+ acquired image in any acquisition mode. The data are returned as long
+ integers (32-bit signed integers). The "array" must be exactly the same
+ size as the complete image.
+ """
+ size = np.array(shape).prod()
+ arr = np.ascontiguousarray(np.zeros(size, dtype=np.int32))
+ self.lib.GetMostRecentImage(arr.ctypes.data_as(ct.POINTER(ct.c_int32)),
+ ct.c_ulong(size))
+ return arr.reshape(shape)
+
+ def most_recent_image16(self, shape):
+ """ 16-bit version of the GetMostRecentImage function.
+ """
+ size = np.array(shape).prod()
+ arr = np.ascontiguousarray(np.zeros(size, dtype=np.int16))
+ pt = ct.POINTER(ct.c_int16)
+ self.lib.GetMostRecentImage16(arr.ctypes.data_as(pt), ct.c_ulong(size))
+ return arr.reshape(shape)
+
+ def images(self, first, last, shape, validfirst, validlast):
+ """ This function will update the data array with the specified series
+ of images from the circular buffer. If the specified series is out of
+ range (i.e. the images have been overwritten or have not yet been
+ acquired) then an error will be returned.
+ Parameters:
+ long first: index of first image in buffer to retrieve.
+ long last: index of last image in buffer to retrieve.
+ at_32* arr: pointer to data storage allocated by the user.
+ unsigned long size: total number of pixels.
+ long* validfirst: index of the first valid image.
+ long* validlast: index of the last valid image.
+ """
+ size = shape[0] * shape[1] * (1 + last - first)
+ array = np.ascontiguousarray(np.zeros(size, dtype=np.int32))
+ self.lib.GetImages(ct.c_long(first), ct.c_long(last),
+ array.ctypes.data_as(ct.POINTER(ct.c_int32)),
+ ct.c_ulong(size), ct.pointer(ct.c_long(validfirst)),
+ ct.pointer(ct.c_long(validlast)))
+
+ return array.reshape(-1, shape[0], shape[1])
+
+ def images16(self, first, last, shape, validfirst, validlast):
+ """ 16-bit version of the GetImages function.
+ """
+ size = shape[0] * shape[1] * (1 + last - first)
+ array = np.ascontiguousarray(np.zeros(size, dtype=np.int16))
+ self.lib.GetImages16(ct.c_long(first), ct.c_long(last),
+ array.ctypes.data_as(ct.POINTER(ct.c_int16)),
+ ct.c_ulong(size),
+ ct.pointer(ct.c_long(validfirst)),
+ ct.pointer(ct.c_long(validlast)))
+
+ return array.reshape(-1, shape[0], shape[1])
+
+ @Feat()
+ def new_images_index(self):
+ """ This function will return information on the number of new images
+ (i.e. images which have not yet been retrieved) in the circular buffer.
+ This information can be used with GetImages to retrieve a series of the
+ latest images. If any images are overwritten in the circular buffer
+ they can no longer be retrieved and the information returned will treat
+ overwritten images as having been retrieved.
+ """
+ first = ct.c_long()
+ last = ct.c_long()
+ self.lib.GetNumberNewImages(ct.pointer(first), ct.pointer(last))
+
+ return (first.value, last.value)
+
+ @Feat() # TODO: test this
+ def available_images_index(self):
+ """ This function will return information on the number of available
+ images in the circular buffer. This information can be used with
+ GetImages to retrieve a series of images. If any images are overwritten
+ in the circular buffer they no longer can be retrieved and the
+ information returned will treat overwritten images as not available.
+ """
+ first = ct.c_long()
+ last = ct.c_long()
+ self.lib.GetNumberAvailableImages(ct.pointer(first), ct.pointer(last))
+
+ return (first.value, last.value)
+
+ def set_dma_parameters(self, n_max_images, s_per_dma):
+ """ In order to facilitate high image readout rates the controller card
+ may wait for multiple images to be acquired before notifying the SDK
+ that new data is available. Without this facility, there is a chance
+ that hardware interrupts may be lost as the operating system does not
+ have enough time to respond to each interrupt. The drawback to this is
+ that you will not get the data for an image until all images for that
+ interrupt have been acquired.
+ There are 3 settings involved in determining how many images will be
+ acquired for each notification (DMA Interrupt) of the controller card
+ and they are as follows:
+ 1. The size of the DMA buffer gives an upper limit on the number of
+ images that can be stored within it and is usually set to the size
+ of one full image when installing the software. This will usually
+ mean that if you acquire full frames there will never be more than
+ one image per DMA.
+ 2. A second setting that is used is the minimum amount of time
+ (SecondsPerDMA) that should expire between interrupts. This can be
+ used to give an indication of the reponsiveness of the operating
+ system to interrupts. Decreasing this value will allow more
+ interrupts per second and should only be done for faster pcs. The
+ default value is 0.03s (30ms), finding the optimal value for your
+ pc can only be done through experimentation.
+ 3. The third setting is an overide to the number of images
+ calculated using the previous settings. If the number of images per
+ dma is calculated to be greater than MaxImagesPerDMA then it will
+ be reduced to MaxImagesPerDMA. This can be used to, for example,
+ ensure that there is never more than 1 image per DMA by setting
+ MaxImagesPerDMA to 1. Setting MaxImagesPerDMA to zero removes this
+ limit. Care should be taken when modifying these parameters as
+ missed interrupts may prevent the acquisition from completing.
+ """
+ self.lib.SetDMAParameters(ct.c_int(n_max_images),
+ ct.c_float(s_per_dma))
+
+ @Feat()
+ def max_images_per_dma(self):
+ """ This function will return the maximum number of images that can be
+ transferred during a single DMA transaction.
+ """
+ n = ct.c_ulong()
+ self.lib.GetImagesPerDMA(ct.pointer(n))
+ return n.value
+
+ @Action()
+ def save_raw(self, filename, typ):
+ """ This function saves the last acquisition as a raw data file.
+ See self.savetypes for the file type keys.
+ """
+ self.lib.SaveAsRaw(ct.c_char_p(str.encode(filename)),
+ ct.c_int(self.savetypes[typ]))
+
+ ### EXPOSURE SETTINGS
+
+ @Feat()
+ def acquisition_timings(self):
+ """ This function will return the current “valid” acquisition timing
+ information. This function should be used after all the acquisitions
+ settings have been set, e.g. SetExposureTime, SetKineticCycleTime and
+ SetReadMode etc. The values returned are the actual times used in
+ subsequent acquisitions.
+ This function is required as it is possible to set the exposure time to
+ 20ms, accumulate cycle time to 30ms and then set the readout mode to
+ full image. As it can take 250ms to read out an image it is not
+ possible to have a cycle time of 30ms.
+ All data is measured in seconds.
+ """
+ exp = ct.c_float()
+ accum = ct.c_float()
+ kine = ct.c_float()
+ self.lib.GetAcquisitionTimings(ct.pointer(exp), ct.pointer(accum),
+ ct.pointer(kine))
+ return exp.value, accum.value, kine.value
+
+ @Action()
+ def set_exposure_time(self, time):
+ """ This function will set the exposure time to the nearest valid value
+ not less than the given value, in seconds. The actual exposure time
+ used is obtained by GetAcquisitionTimings. Please refer to
+ SECTION 5 – ACQUISITION MODES for further information.
+ """
+ try:
+ time.magnitude
+ except AttributeError:
+ time = time * seg
+
+ self.lib.SetExposureTime(ct.c_float(time.magnitude))
+
+ @Action()
+ def set_accum_time(self, time):
+ """ This function will set the accumulation cycle time to the nearest
+ valid value not less than the given value. The actual cycle time used
+ is obtained by GetAcquisitionTimings. Please refer to
+ SECTION 5 – ACQUISITION MODES for further information.
+ """
+ try:
+ time.magnitude
+ except AttributeError:
+ time = time * seg
+
+ self.lib.SetAccumulationCycleTime(ct.c_float(time.magnitude))
+
+ @Action()
+ def set_kinetic_cycle_time(self, time):
+ """ This function will set the kinetic cycle time to the nearest valid
+ value not less than the given value. The actual time used is obtained
+ by GetAcquisitionTimings. . Please refer to
+ SECTION 5 – ACQUISITION MODES for further information.
+ float time: the kinetic cycle time in seconds.
+ """
+ try:
+ time.magnitude
+ except AttributeError:
+ time = time * seg
+
+ self.lib.SetKineticCycleTime(ct.c_float(time.magnitude))
+
+ @Action()
+ def set_n_kinetics(self, n):
+ """ This function will set the number of scans (possibly accumulated
+ scans) to be taken during a single acquisition sequence. This will only
+ take effect if the acquisition mode is Kinetic Series.
+ """
+ self.lib.SetNumberKinetics(ct.c_int(n))
+
+ @Action()
+ def set_n_accum(self, n):
+ """ This function will set the number of scans accumulated in memory.
+ This will only take effect if the acquisition mode is either Accumulate
+ or Kinetic Series.
+ """
+ self.lib.SetNumberAccumulations(ct.c_int(n))
+
+ @Feat(units='s')
+ def keep_clean_time(self):
+ """ This function will return the time to perform a keep clean cycle.
+ This function should be used after all the acquisitions settings have
+ been set, e.g. SetExposureTime, SetKineticCycleTime and SetReadMode
+ etc. The value returned is the actual times used in subsequent
+ acquisitions.
+ """
+ time = ct.c_float()
+ self.lib.GetKeepCleanTime(ct.pointer(time))
+ return time.value
+
+ @Feat(units='s')
+ def readout_time(self):
+ """ This function will return the time to readout data from a sensor.
+ This function should be used after all the acquisitions settings have
+ been set, e.g. SetExposureTime, SetKineticCycleTime and SetReadMode
+ etc. The value returned is the actual times used in subsequent
+ acquisitions.
+ """
+ time = ct.c_float()
+ self.lib.GetReadOutTime(ct.pointer(time))
+ return time.value
+
+ @Feat(read_once=True, units='s')
+ def max_exposure(self):
+ """ This function will return the maximum Exposure Time in seconds that
+ is settable by the SetExposureTime function.
+ """
+ exp = ct.c_float()
+ self.lib.GetMaximumExposure(ct.pointer(exp))
+ return exp.value
+
+ @Feat(read_once=True)
+ def n_max_nexposure(self):
+ """ This function will return the maximum number of exposures that can
+ be configured in the SetRingExposureTimes SDK function.
+ """
+ n = ct.c_int()
+ self.lib.GetMaximumNumberRingExposureTimes(ct.pointer(n))
+ return n.value
+
+ def true_exposure_times(self, n): # FIXME: bit order? something
+ """ This function will return the actual exposure times that the camera
+ will use. There may be differences between requested exposures and the
+ actual exposures.
+ ntimes: Numbers of times requested.
+ """
+ times = np.ascontiguousarray(np.zeros(n, dtype=np.float))
+ outtimes = times.ctypes.data_as(ct.POINTER(ct.c_float))
+ self.lib.GetAdjustedRingExposureTimes(ct.c_int(n), outtimes)
+ return times
+
+ def exposure_times(self, value):
+ n = ct.c_int(len(value))
+ value = np.ascontiguousarray(value.astype(np.float))
+ outvalue = value.ctypes.data_as(ct.POINTER(ct.c_float))
+ self.lib.SetRingExposureTimes(n, outvalue)
+
+ @Feat(values={True: 1, False: 0})
+ def frame_transfer_mode(self):
+ """ This function will set whether an acquisition will readout in Frame
+ Transfer Mode. If the acquisition mode is Single Scan or Fast Kinetics
+ this call will have no affect.
+ """
+ return self.frame_transfer_mode_state
+
+ @frame_transfer_mode.setter
+ def frame_transfer_mode(self, state):
+ ans = self.lib.SetFrameTransferMode(ct.c_int(state))
+ if ans == 20002:
+ self.frame_transfer_mode_state = state
+
+ ### AMPLIFIERS, GAIN, SPEEDS
+
+ @Feat(read_once=True)
+ def n_preamps(self):
+ """ Available in some systems are a number of pre amp gains that can be
+ applied to the data as it is read out. This function gets the number of
+ these pre amp gains available. The functions GetPreAmpGain and
+ SetPreAmpGain can be used to specify which of these gains is to be
+ used.
+ """
+ n = ct.c_int()
+ self.lib.GetNumberPreAmpGains(ct.pointer(n))
+ return n.value
+
+ def preamp_available(self, channel, amp, index, preamp):
+ """ This function checks that the AD channel exists, and that the
+ amplifier, speed and gain are available for the AD channel.
+ """
+ channel = ct.c_int(channel)
+ amp = ct.c_int(amp)
+ index = ct.c_int(index)
+ preamp = ct.c_int(preamp)
+ status = ct.c_int()
+ self.lib.IsPreAmpGainAvailable(channel, amp, index, preamp,
+ ct.pointer(status))
+
+ return bool(status.value)
+
+ def preamp_descr(self, index):
+ """ This function will return a string with a pre amp gain description.
+ The pre amp gain is selected using the index. The SDK has a string
+ associated with each of its pre amp gains. The maximum number of
+ characters needed to store the pre amp gain descriptions is 30. The
+ user has to specify the number of characters they wish to have returned
+ to them from this function.
+ """
+ index = ct.c_int(index)
+ descr = (ct.c_char * 30)()
+ leng = ct.c_int(30)
+ self.lib.GetAmpDesc(index, ct.pointer(descr), leng)
+ return str(descr.value)[2:-1]
+
+ def true_preamp(self, index):
+ """ For those systems that provide a number of pre amp gains to apply
+ to the data as it is read out; this function retrieves the amount of
+ gain that is stored for a particular index. The number of gains
+ available can be obtained by calling the GetNumberPreAmpGains function
+ and a specific Gain can be selected using the function SetPreAmpGain.
+ """
+ index = ct.c_int(index)
+ gain = ct.c_float()
+ self.lib.GetPreAmpGain(index, ct.pointer(gain))
+ return gain
+
+ @Feat()
+ def preamp(self):
+ """ This function will set the pre amp gain to be used for subsequent
+ acquisitions. The actual gain factor that will be applied can be found
+ through a call to the GetPreAmpGain function.
+ The number of Pre Amp Gains available is found by calling the
+ GetNumberPreAmpGains function.
+ """
+ return self.preamp_index
+
+ @preamp.setter
+ def preamp(self, index):
+ self.preamp_index = index
+ self.lib.SetPreAmpGain(ct.c_int(index))
+
+ @Feat(values={True: 1, False: 0})
+ def EM_advanced_enabled(self):
+ """This function turns on and off access to higher EM gain levels
+ within the SDK. Typically, optimal signal to noise ratio and dynamic
+ range is achieved between x1 to x300 EM Gain.
+ Higher gains of > x300 are recommended for single photon counting only.
+ Before using higher levels, you should ensure that light levels do not
+ exceed the regime of tens of photons per pixel, otherwise accelerated
+ ageing of the sensor can occur.
+ This is set to False upon initialization of the camera.
+ """
+
+ state = ct.c_int()
+ self.lib.GetEMAdvanced(ct.pointer(state))
+ return state.value
+
+ @EM_advanced_enabled.setter
+ def EM_advanced_enabled(self, value):
+ self.lib.SetEMAdvanced(ct.c_int(value))
+
+ @Feat(values={'DAC255': 0, 'DAC4095': 1, 'Linear': 2, 'RealGain': 3})
+ def EM_gain_mode(self):
+ """ Set the EM Gain mode to one of the following possible settings.
+ Mode 0: The EM Gain is controlled by DAC settings in the range
+ 0-255. Default mode.
+ 1: The EM Gain is controlled by DAC settings in the range 0-4095.
+ 2: Linear mode.
+ 3: Real EM gain
+ """
+ return self.EM_gain_mode_index
+
+ @EM_gain_mode.setter
+ def EM_gain_mode(self, mode):
+ ans = self.lib.SetEMGainMode(ct.c_int(mode))
+ if ans == 20002:
+ self.EM_gain_mode_index = mode
+
+ @Feat()
+ def EM_gain(self):
+ """Allows the user to change the gain value. The valid range for the
+ gain depends on what gain mode the camera is operating in. See
+ SetEMGainMode to set the mode and GetEMGainRange to get the valid range
+ to work with. To access higher gain values (>x300) see SetEMAdvanced.
+ """
+ gain = ct.c_int()
+ self.lib.GetEMCCDGain(ct.pointer(gain))
+
+ return gain.value
+
+ @EM_gain.setter
+ def EM_gain(self, value):
+ self.lib.SetEMCCDGain(ct.c_int(value))
+
+ @Feat()
+ def EM_gain_range(self):
+ """Returns the minimum and maximum values of the current selected EM
+ Gain mode and temperature of the sensor.
+ """
+ mini, maxi = ct.c_int(), ct.c_int()
+ self.lib.GetEMGainRange(ct.pointer(mini), ct.pointer(maxi))
+
+ return (mini.value, maxi.value)
+
+ @Feat(read_once=True)
+ def n_ad_channels(self):
+ n = ct.c_int()
+ self.lib.GetNumberADChannels(ct.pointer(n))
+ return n.value
+
+ @Feat(read_once=True)
+ def n_amps(self):
+ n = ct.c_int()
+ self.lib.GetNumberAmp(ct.pointer(n))
+ return n.value
+
+ def amp_available(self, iamp):
+ """ This function checks if the hardware and current settings permit
+ the use of the specified amplifier."""
+ ans = self.lib.IsAmplifierAvailable(ct.c_int(iamp))
+ if ans == 20002:
+ return True
+ else:
+ return False
+
+ def amp_descr(self, index):
+ """ This function will return a string with an amplifier description.
+ The amplifier is selected using the index. The SDK has a string
+ associated with each of its amplifiers. The maximum number of
+ characters needed to store the amplifier descriptions is 21. The user
+ has to specify the number of characters they wish to have returned to
+ them from this function.
+ """
+ index = ct.c_int(index)
+ descr = (ct.c_char * 21)()
+ leng = ct.c_int(21)
+ self.lib.GetAmpDesc(index, ct.pointer(descr), leng)
+ return str(descr.value)[2:-1]
+
+ def readout_flipped(self, iamp):
+ """ On cameras with multiple amplifiers the frame readout may be
+ flipped. This function can be used to determine if this is the case.
+ """
+ flipped = ct.c_int()
+ self.lib.IsReadoutFlippedByAmplifier(ct.c_int(iamp),
+ ct.pointer(flipped))
+ return bool(flipped.value)
+
+ def amp_max_hspeed(self, index):
+ """ This function will return the maximum available horizontal shift
+ speed for the amplifier selected by the index parameter.
+ """
+ hspeed = ct.c_float()
+ self.lib.GetAmpMaxSpeed(ct.c_int(index), ct.pointer(hspeed))
+ return hspeed.value
+
+ def n_horiz_shift_speeds(self, channel=0, typ=None):
+ """ As your Andor SDK system is capable of operating at more than one
+ horizontal shift speed this function will return the actual number of
+ speeds available.
+ Parameters
+ int channel: the AD channel.
+ int typ: output amplification.
+ Valid values: 0 electron multiplication.
+ 1 conventional.
+ int* speeds: number of allowed horizontal speeds
+ """
+ if typ is None:
+ typ = self.amp_typ
+
+ n = ct.c_int()
+
+ self.lib.GetNumberHSSpeeds(ct.c_int(channel),
+ ct.c_int(typ), ct.pointer(n))
+
+ return n.value
+
+ def true_horiz_shift_speed(self, index=0, typ=None, ad=0):
+ """As your Andor system is capable of operating at more than one
+ horizontal shift speed this function will return the actual speeds
+ available. The value returned is in MHz.
+
+ GetHSSpeed(int channel, int typ, int index, float* speed)
+
+ Parameters
+ int channel: the AD channel.
+
+ int typ: output amplification.
+ Valid values:
+ 0 electron multiplication/Conventional(clara)
+ 1 conventional/Extended NIR Mode(clara).
+
+ int index: speed required
+ Valid values
+ 0 to NumberSpeeds-1 where NumberSpeeds is value returned in
+ first parameter after a call to GetNumberHSSpeeds().
+
+ float* speed: speed in in MHz.
+ """
+
+ if typ is None:
+ typ = self.amp_typ
+
+ speed = ct.c_float()
+
+ self.lib.GetHSSpeed(ct.c_int(ad), ct.c_int(typ), ct.c_int(index),
+ ct.pointer(speed))
+
+ return speed.value * MHz
+
+ @Feat()
+ def horiz_shift_speed(self):
+ return self.horiz_shift_speed_index
+
+ @horiz_shift_speed.setter
+ def horiz_shift_speed(self, index):
+ """ This function will set the speed at which the pixels are shifted
+ into the output node during the readout phase of an acquisition.
+ Typically your camera will be capable of operating at several
+ horizontal shift speeds. To get the actual speed that an index
+ corresponds to use the GetHSSpeed function.
+ Parameters
+ int typ: output amplification.
+ Valid values:
+ 0 electron multiplication/Conventional(clara).
+ 1 conventional/Extended NIR mode(clara).
+ int index: the horizontal speed to be used
+ Valid values
+ 0 to GetNumberHSSpeeds() - 1
+ """
+ ans = self.lib.SetHSSpeed(ct.c_int(self.amp_typ), ct.c_int(index))
+ if ans == 20002:
+ self.horiz_shift_speed_index = index
+
+ @Feat()
+ def fastest_recommended_vsspeed(self):
+ """ As your Andor SDK system may be capable of operating at more than
+ one vertical shift speed this function will return the fastest
+ recommended speed available. The very high readout speeds, may require
+ an increase in the amplitude of the Vertical Clock Voltage using
+ SetVSAmplitude. This function returns the fastest speed which does not
+ require the Vertical Clock Voltage to be adjusted. The values returned
+ are the vertical shift speed index and the actual speed in microseconds
+ per pixel shift.
+ """
+ inti, f2 = ct.c_int(), ct.c_float()
+ self.lib.GetFastestRecommendedVSSpeed(ct.pointer(inti), ct.pointer(f2))
+ return (inti.value, f2.value)
+
+ @Feat(read_once=True)
+ def n_vert_clock_amps(self):
+ """ This function will normally return the number of vertical clock
+ voltage amplitudes that the camera has.
+ """
+ n = ct.c_int()
+ self.lib.GetNumberVSAmplitudes(ct.pointer(n))
+ return n.value
+
+ def vert_amp_index(self, string):
+ """ This Function is used to get the index of the Vertical Clock
+ Amplitude that corresponds to the string passed in.
+ Parameters
+ char* text: String to test
+ Valid values: "Normal" , "+1" , "+2" , "+3" , "+4"
+ """
+ index = ct.c_int()
+ string = ct.c_char_p(str.encode(string))
+ self.lib.GetVSAmplitudeFromString(string, ct.pointer(index))
+ return index.value
+
+ def vert_amp_string(self, index):
+ """ This Function is used to get the Vertical Clock Amplitude string
+ that corresponds to the index passed in.
+ Parameters
+ int index: Index of VS amplitude required
+ Valid values 0 to GetNumberVSAmplitudes() - 1
+ """
+ index = ct.c_int(index)
+ string = (ct.c_char * 6)()
+ self.lib.GetVSAmplitudeString(index, ct.pointer(string))
+ return str(string.value)[2:-1]
+
+ def true_vert_amp(self, index):
+ """ This Function is used to get the value of the Vertical Clock
+ Amplitude found at the index passed in.
+ Parameters
+ int index: Index of VS amplitude required
+ Valid values 0 to GetNumberVSAmplitudes() - 1
+ """
+ index = ct.c_int(index)
+ amp = ct.c_int()
+ self.lib.GetVSAmplitudeValue(index, ct.pointer(amp))
+ return amp.value
+
+ @Action()
+ def set_vert_clock(self, index):
+ """ If you choose a high readout speed (a low readout time), then you
+ should also consider increasing the amplitude of the Vertical Clock
+ Voltage.
+ There are five levels of amplitude available for you to choose from:
+ ď‚· Normal
+ ď‚· +1
+ ď‚· +2
+ ď‚· +3
+ ď‚· +4
+ Exercise caution when increasing the amplitude of the vertical clock
+ voltage, since higher clocking voltages may result in increased
+ clock-induced charge (noise) in your signal. In general, only the very
+ highest vertical clocking speeds are likely to benefit from an
+ increased vertical clock voltage amplitude.
+ """
+ self.lib.SetVSAmplitude(ct.c_int(index))
+
+ @Feat(read_once=True)
+ def n_vert_shift_speeds(self):
+ """ As your Andor system may be capable of operating at more than one
+ vertical shift speed this function will return the actual number of
+ speeds available.
+ """
+ n = ct.c_int()
+ self.lib.GetNumberVSSpeeds(ct.pointer(n))
+ return n.value
+
+ def true_vert_shift_speed(self, index=0):
+ """As your Andor SDK system may be capable of operating at more than
+ one vertical shift speed this function will return the actual speeds
+ available. The value returned is in microseconds.
+ """
+ speed = ct.c_float()
+ self.lib.GetVSSpeed(ct.c_int(index), ct.pointer(speed))
+ return speed.value * us
+
+ @Feat()
+ def vert_shift_speed(self):
+ return self.vert_shift_speed_index
+
+ @vert_shift_speed.setter
+ def vert_shift_speed(self, index):
+ """ This function will set the vertical speed to be used for subsequent
+ acquisitions.
+ """
+ self.vert_shift_speed_index = index
+ self.lib.SetVSSpeed(ct.c_int(index))
+
+ ### BASELINE
+
+ @Feat(values={True: 1, False: 0})
+ def baseline_clamp(self):
+ """ This function returns the status of the baseline clamp
+ functionality. With this feature enabled the baseline level of each
+ scan in a kinetic series will be more consistent across the sequence.
+ """
+ i = ct.c_int()
+ self.lib.GetBaselineClamp(ct.pointer(i))
+ return i.value
+
+ @baseline_clamp.setter
+ def baseline_clamp(self, value):
+ value = ct.c_int(value)
+ self.lib.SetBaselineClamp(value)
+
+ @Feat(limits=(-1000, 1100, 100))
+ def baseline_offset(self):
+ """ This function allows the user to move the baseline level by the
+ amount selected. For example “+100” will add approximately 100 counts
+ to the default baseline value. The value entered should be a multiple
+ of 100 between -1000 and +1000 inclusively.
+ """
+ return self.baseline_offset_value
+
+ @baseline_offset.setter
+ def baseline_offset(self, value):
+ ans = self.lib.SetBaselineOffset(ct.c_int(value))
+ if ans == 20002:
+ self.baseline_offset_value = value
+
+ ### BIT DEPTH
+
+ def bit_depth(self, ch):
+ """ This function will retrieve the size in bits of the dynamic range
+ for any available AD channel.
+ """
+ ch = ct.c_int(ch)
+ depth = ct.c_uint()
+ self.lib.GetBitDepth(ch, ct.pointer(depth))
+ return depth.value
+
+ ### TRIGGER
+
+ @Feat(values={True: 1, False: 0})
+ def adv_trigger_mode(self):
+ """ This function will set the state for the iCam functionality that
+ some cameras are capable of. There may be some cases where we wish to
+ prevent the software using the new functionality and just do it the way
+ it was previously done.
+ """
+ return self.adv_trigger_mode_state
+
+ @adv_trigger_mode.setter
+ def adv_trigger_mode(self, state):
+ ans = self.lib.SetAdvancedTriggerModeState(ct.c_int(state))
+ if ans == 20002:
+ self.adv_trigger_mode_state = state
+
+ def trigger_mode_available(self, modestr):
+ """ This function checks if the hardware and current settings permit
+ the use of the specified trigger mode.
+ """
+ index = self.triggers[modestr]
+ ans = self.lib.IsTriggerModeAvailable(ct.c_int(index))
+ if ans == 20002:
+ return True
+ else:
+ return False
+
+ @Feat(values={'Internal': 0, 'External': 1, 'External Start': 6,
+ 'External Exposure': 7, 'External FVB EM': 9,
+ 'Software Trigger': 10, 'External Charge Shifting': 12})
+ def trigger_mode(self):
+ """ This function will set the trigger mode that the camera will
+ operate in.
+ """
+ return self.trigger_mode_index
+
+ @trigger_mode.setter
+ def trigger_mode(self, mode):
+ ans = self.lib.SetTriggerMode(ct.c_int(mode))
+ if ans == 20002:
+ self.trigger_mode_index = mode
+
+ @Action()
+ def send_software_trigger(self):
+ """ This function sends an event to the camera to take an acquisition
+ when in Software Trigger mode. Not all cameras have this mode available
+ to them. To check if your camera can operate in this mode check the
+ GetCapabilities function for the Trigger Mode
+ AC_TRIGGERMODE_CONTINUOUS. If this mode is physically possible and
+ other settings are suitable (IsTriggerModeAvailable) and the camera is
+ acquiring then this command will take an acquisition.
+ NOTES:
+ The settings of the camera must be as follows:
+ ReadOut mode is full image
+ RunMode is Run Till Abort
+ TriggerMode is 10
+ """
+ self.lib.SendSoftwareTrigger()
+
+ @Action()
+ def trigger_level(self, value):
+ """ This function sets the trigger voltage which the system will use.
+ """
+ self.lib.SetTriggerLevel(ct.c_float(value))
+
+ ### AUXPORT
+
+ @DictFeat(values={True: not(0), False: 0}, keys=list(range(1, 5)))
+ def in_aux_port(self, port):
+ """ This function returns the state of the TTL Auxiliary Input Port on
+ the Andor plug-in card.
+ """
+ port = ct.c_int(port)
+ state = ct.c_int()
+ self.lib.InAuxPort(port, ct.pointer(state))
+ return state.value
+
+ @DictFeat(values={True: 1, False: 0}, keys=list(range(1, 5)))
+ def out_aux_port(self, port):
+ """ This function sets the TTL Auxiliary Output port (P) on the Andor
+ plug-in card to either ON/HIGH or OFF/LOW.
+ """
+ return self.auxout[port - 1]
+
+ @out_aux_port.setter
+ def out_aux_port(self, port, state):
+ self.auxout[port - 1] = bool(state)
+ port = ct.c_int(port)
+ state = ct.c_int(state)
+ self.lib.OutAuxPort(port, ct.pointer(state))
+
+ def is_implemented(self, strcommand):
+ """Checks if command is implemented.
+ """
+ result = ct.c_bool()
+ command = ct.c_wchar_p(strcommand)
+ self.lib.AT_IsImplemented(self.AT_H, command, ct.addressof(result))
+ return result.value
+
+ def is_writable(self, strcommand):
+ """Checks if command is writable.
+ """
+ result = ct.c_bool()
+ command = ct.c_wchar_p(strcommand)
+ self.lib.AT_IsWritable(self.AT_H, command, ct.addressof(result))
+ return result.value
+
+ def queuebuffer(self, bufptr, value):
+ """Put buffer in queue.
+ """
+ value = ct.c_int(value)
+ self.lib.AT_QueueBuffer(self.AT_H, ct.byref(bufptr), value)
+
+ def waitbuffer(self, ptr, bufsize):
+ """Wait for next buffer ready.
+ """
+ timeout = ct.c_int(20000)
+ self.lib.AT_WaitBuffer(self.AT_H, ct.byref(ptr), ct.byref(bufsize),
+ timeout)
+
+ def command(self, strcommand):
+ """Run command.
+ """
+ command = ct.c_wchar_p(strcommand)
+ self.lib.AT_Command(self.AT_H, command)
+
+ def getint(self, strcommand):
+ """Run command and get Int return value.
+ """
+ result = ct.c_longlong()
+ command = ct.c_wchar_p(strcommand)
+ self.lib.AT_GetInt(self.AT_H, command, ct.addressof(result))
+ return result.value
+
+ def setint(self, strcommand, value):
+ """SetInt function.
+ """
+ command = ct.c_wchar_p(strcommand)
+ value = ct.c_longlong(value)
+ self.lib.AT_SetInt(self.AT_H, command, value)
+
+ def getfloat(self, strcommand):
+ """Run command and get Int return value.
+ """
+ result = ct.c_double()
+ command = ct.c_wchar_p(strcommand)
+ self.lib.AT_GetFloat(self.AT_H, command, ct.addressof(result))
+ return result.value
+
+ def setfloat(self, strcommand, value):
+ """Set command with Float value parameter.
+ """
+ command = ct.c_wchar_p(strcommand)
+ value = ct.c_double(value)
+ self.lib.AT_SetFloat(self.AT_H, command, value)
+
+ def getbool(self, strcommand):
+ """Run command and get Bool return value.
+ """
+ result = ct.c_bool()
+ command = ct.c_wchar_p(strcommand)
+ self.lib.AT_GetBool(self.AT_H, command, ct.addressof(result))
+ return result.value
+
+ def setbool(self, strcommand, value):
+ """Set command with Bool value parameter.
+ """
+ command = ct.c_wchar_p(strcommand)
+ value = ct.c_bool(value)
+ self.lib.AT_SetBool(self.AT_H, command, value)
+
+ def getenumerated(self, strcommand):
+ """Run command and set Enumerated return value.
+ """
+ result = ct.c_int()
+ command = ct.c_wchar_p(strcommand)
+ self.lib.AT_GetEnumerated(self.AT_H, command, ct.addressof(result))
+
+ def setenumerated(self, strcommand, value):
+ """Set command with Enumerated value parameter.
+ """
+ command = ct.c_wchar_p(strcommand)
+ value = ct.c_bool(value)
+ self.lib.AT_SetEnumerated(self.AT_H, command, value)
+
+ def setenumstring(self, strcommand, item):
+ """Set command with EnumeratedString value parameter.
+ """
+ command = ct.c_wchar_p(strcommand)
+ item = ct.c_wchar_p(item)
+ self.lib.AT_SetEnumString(self.AT_H, command, item)
+
+ def flush(self):
+ self.lib.AT_Flush(self.AT_H)
+
+if __name__ == '__main__':
+ from matplotlib import pyplot as plt
+ from lantz import Q_
+ import time
+
+ degC = Q_(1, 'degC')
+ us = Q_(1, 'us')
+ MHz = Q_(1, 'MHz')
+ s = Q_(1, 's')
+
+ with CCD() as andor:
+
+ print(andor.idn)
+ andor.free_int_mem()
+
+ # Acquisition settings
+ andor.readout_mode = 'Image'
+ andor.set_image()
+# andor.acquisition_mode = 'Single Scan'
+ andor.acquisition_mode = 'Run till abort'
+ andor.set_exposure_time(0.03 * s)
+ andor.trigger_mode = 'Internal'
+ andor.amp_typ = 0
+ andor.horiz_shift_speed = 0
+ andor.vert_shift_speed = 0
+ andor.shutter(0, 0, 0, 0, 0)
+
+# # Temperature stabilization
+# andor.temperature_setpoint = -30 * degC
+# andor.cooler_on = True
+# stable = 'Temperature has stabilized at set point.'
+# print('Temperature set point =', andor.temperature_setpoint)
+# while andor.temperature_status != stable:
+# print("Current temperature:", np.round(andor.temperature, 1))
+# time.sleep(30)
+# print('Temperature has stabilized at set point')
+
+ # Acquisition
+ andor.start_acquisition()
+ time.sleep(2)
+ data = andor.most_recent_image(shape=andor.detector_shape)
+ andor.abort_acquisition()
+
+ plt.imshow(data, cmap='gray', interpolation='None')
+ plt.colorbar()
+ plt.show()
+
+ print(data.min(), data.max(), data.mean())
diff --git a/lantz/drivers/legacy/andor/neo.py b/lantz/drivers/legacy/andor/neo.py
new file mode 100644
index 0000000..4cc1f7f
--- /dev/null
+++ b/lantz/drivers/legacy/andor/neo.py
@@ -0,0 +1,122 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.andor.neo
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Implements a high level driver for the Andor Neo CMOS Camera
+
+
+ Sources::
+
+ - Andor Neo Manual
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+
+import ctypes as ct
+
+import numpy as np
+
+from lantz import Feat, Action, Q_
+from lantz.foreign import RetStr, RetTuple
+
+from .andor import Andor
+
+class Neo(Andor):
+ """Neo Andor CMOS Camera
+ """
+
+ def initialize(self):
+ super().initialize()
+ self.flush()
+ self.fan_speed = 1
+ self.width ,self.height = self.sensor_size
+ self.length = self.width * self.height
+ self.clock_rate = 100
+ self.pixel_encoding = 32
+ self.imagesizebytes = self.getint("ImageSizeBytes")
+ self.userbuffer = ct.create_string_buffer(' ' * self.imagesizebytes)
+
+ @Feat(None, values={32: 'Mono32', 64: 'Mono64'})
+ def pixel_encoding(self, value):
+ """Pixel encoding.
+ """
+ self.setenumstring("PixelEncoding", value)
+
+ @Feat()
+ def sensor_size(self):
+ width = self.getint("SensorWidth")
+ height = self.getint("SensorHeight")
+ return width, height
+
+ @Feat(None, values={100: '100 MHz', 200: '200 MHz', 280: '280 MHz'})
+ def clock_rate(self, value):
+ """Pixel clock rate
+ """
+ self.setenumstring("PixelReadoutRate", value)
+
+ @Feat(None)
+ def fan_peed(self, value = 1):
+ """Fan speed.
+ """
+ self.setenumerated("FanSpeed", value)
+
+ @Feat()
+ def sensor_temp(self):
+ """Sensor temperature.
+ """
+ return self.getfloat("SensorTemperature")
+
+ @Feat()
+ def exposure_time(self):
+ """Get exposure time.
+ """
+ return self.getfloat("ExposureTime")
+
+ @exposure_time.setter
+ def exposure_time(self, exposure):
+ self.setfloat("ExposureTime", exposure)
+
+ @Feat(None)
+ def roi(self, width_height_top_left):
+ """Set region of interest
+ """
+ width, height, top, left = width_height_top_left
+ self.setint("AOIWidth", width)
+ self.setint("AOILeft", left)
+ self.setint("AOIHeight", height)
+ self.setint("AOITop", top)
+
+ @Action()
+ def take_image(self):
+ """Image acquisition.
+ """
+ self.queuebuffer(self.userbuffer, self.imagesizebytes)
+ self.command("AcquisitionStart")
+ self.waitbuffer(*RetStr(1))
+ self.command("AcquisitionStop")
+ self.flush()
+ image = np.fromstring(self.userbuffer, dtype=np.uint32, count=self.length)
+ image.shape = (self.height, self.width)
+ return image
+
+ @Action()
+ def take_image(self, numbuff, numframes):
+ """Image acquisition with circular buffer.
+ """
+ imagesizebytes = self.getint("ImageSizeBytes")
+ userbuffer = []
+ for i in range(numbuff):
+ userbuffer.append(ct.create_string_buffer(' ' * imagesizebytes))
+ self.queuebuffer(userbuffer, imagesizebytes)
+ self.command("AcquisitionStart")
+ for i in range(numbuff):
+ self.waitbuffer(*RetStr(1))
+ self.queuebuffer(userbuffer[i], imagesizebytes)
+ self.command("AcquisitionStop")
+ self.flush()
+ image = np.fromstring(userbuffer[0], dtype=np.uint32, count=self.length)
+ image.shape = (self.height, self.width)
+ return image
diff --git a/lantz/drivers/legacy/cobolt/__init__.py b/lantz/drivers/legacy/cobolt/__init__.py
new file mode 100644
index 0000000..ab4b59a
--- /dev/null
+++ b/lantz/drivers/legacy/cobolt/__init__.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.cobolt
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ :company: Cobolt.
+ :description: DPSS lasers, diode laser modules, fiber pigtailed lasers.
+ :website: http://www.cobolt.se/
+
+ ----
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from .cobolt0601 import Cobolt0601
+
+__all__ = ['Cobolt0601']
diff --git a/lantz/drivers/legacy/cobolt/cobolt0601.py b/lantz/drivers/legacy/cobolt/cobolt0601.py
new file mode 100644
index 0000000..2facf23
--- /dev/null
+++ b/lantz/drivers/legacy/cobolt/cobolt0601.py
@@ -0,0 +1,224 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.cobolt.cobolt0601
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from lantz import Action, Feat
+from lantz.drivers.legacy.serial import SerialDriver
+
+
+class Cobolt0601(SerialDriver):
+ """Driver for any Cobolt 06-01 Series laser.
+ """
+
+ ENCODING = 'ascii'
+
+ RECV_TERMINATION = '\r'
+ SEND_TERMINATION = '\r'
+
+ BAUDRATE = 115200
+ BYTESIZE = 8
+ PARITY = 'none'
+ STOPBITS = 1
+
+ #: flow control flags
+ RTSCTS = False
+ DSRDTR = False
+ XONXOFF = False
+
+ @Feat(read_once=True)
+ def idn(self):
+ """Get serial number
+ """
+ ans = self.query('gsn?')[1:]
+ wavel = ans[:3]
+ sn = ans[3:]
+ return 'Cobolt ' + wavel + 'nm 06-01 Series, serial number ' + sn
+
+ def initialize(self):
+ super().initialize()
+ self.mode = 'APC'
+ self.ctl_mode = self.mode
+
+ # ENABLE LASER METHODS
+
+ @Feat(values={True: '1', False: '0'})
+ def ksw_enabled(self):
+ """Handling Key Switch enable state
+ """
+ ans = self.query('@cobasky?')
+ return ans[1:]
+
+ @ksw_enabled.setter
+ def ksw_enabled(self, value):
+ self.query('@cobasky ' + value)
+
+ @Feat(values={True: '1', False: '0'})
+ def enabled(self):
+ """Method for turning on the laser. Requires autostart disabled.
+ """
+ ans = self.query('l?')
+ return ans[-1]
+
+ @enabled.setter
+ def enabled(self, value):
+ self.query('l' + value)
+
+ @Feat(values={True: '1', False: '0'})
+ def autostart(self):
+ """Autostart handling
+ """
+ ans = self.query('@cobas?')
+ return ans[-1]
+
+ @autostart.setter
+ def autostart(self, value):
+ self.query('@cobas ' + value)
+
+ @Action()
+ def restart(self):
+ """Forces the laser on without checking if autostart is enabled.
+ """
+ self.query('@cob1')
+
+ # LASER INFORMATION METHODS
+ @Feat()
+ def operating_hours(self):
+ """Get Laser Head operating hours
+ """
+ return self.query('hrs?')[1:]
+
+ @Feat(values={'Interlock open': '1', 'OK': '0'})
+ def interlock(self):
+ """Get interlock state
+ """
+ return self.query('ilk?')[1:]
+
+ # LASER'S CONTROL MODE AND SET POINT
+
+ @Feat(values={'APC', 'ACC'})
+ def ctl_mode(self):
+ """To handle laser control modes
+ """
+ return self.mode
+
+ @ctl_mode.setter
+ def ctl_mode(self, value):
+ if value == 'ACC':
+ self.query('ci')
+ self.mode = 'ACC'
+ elif value == 'APC':
+ self.mode = 'APC'
+ self.query('cp')
+
+ @Feat(units='mA')
+ def current_sp(self):
+ """Get drive current
+ """
+ return float(self.query('i?'))
+
+ @current_sp.setter
+ def current_sp(self, value):
+ self.query('slc {:.1f}'.format(value))
+
+ @Feat(units='mW')
+ def power_sp(self):
+ """To handle output power set point (mW) in constant power mode
+ """
+ return 1000 * float(self.query('p?'))
+
+ @power_sp.setter
+ def power_sp(self, value):
+ self.query('p {:.5f}'.format(value / 1000))
+
+ # LASER'S CURRENT STATUS
+
+ @Feat(units='mW')
+ def power(self):
+ """Read output power
+ """
+ return 1000 * float(self.query('pa?'))
+
+ @Feat(values={'Temperature error': '1', 'No errors': '0',
+ 'Interlock error': '3', 'Constant power time out': '4'})
+ def status(self):
+ """Get operating fault
+ """
+ return self.query('f?')[1:]
+
+ @Action()
+ def clear_fault(self):
+ """Clear fault
+ """
+ self.query('cf')
+
+ # MODULATION MODES
+ @Action()
+ def enter_mod_mode(self):
+ """Enter modulation mode
+ """
+ self.query('em')
+
+ @Feat(values={True: '1', False: '0'})
+ def digital_mod(self):
+ """digital modulation enable state
+ """
+ return self.query('gdmes?')[1:]
+
+ @digital_mod.setter
+ def digital_mod(self, value):
+ self.query('gdmes ' + value)
+
+ @Feat(values={True: '1', False: '0'})
+ def analog_mod(self):
+ """analog modulation enable state
+ """
+ return self.query('games?')[1:]
+
+ @analog_mod.setter
+ def analog_mod(self, value):
+ self.query('sames ' + value)
+
+ @Feat(values={True: '1', False: '0'})
+ def analogli_mod(self):
+ """analog modulation enable state
+ """
+ return self.query('galis?')[1:]
+
+ @analogli_mod.setter
+ def analogli_mod(self, value):
+ self.query('salis ' + value)
+
+ @Feat(values={'Waiting for key': '1', 'Off': '0', 'Continuous': '2',
+ 'On/Off Modulation': '3', 'Modulation': '4', 'Fault': '5',
+ 'Aborted': '6'})
+ def mod_mode(self):
+ """Returns the current operating mode
+ """
+ return self.query('gom?')[1:]
+
+if __name__ == '__main__':
+ import argparse
+ import lantz.log
+
+ parser = argparse.ArgumentParser(description='Test Kentech HRI')
+ parser.add_argument('-i', '--interactive', action='store_true',
+ default=False, help='Show interactive GUI')
+ parser.add_argument('-p', '--port', type=str, default='COM4',
+ help='Serial port to connect to')
+
+ args = parser.parse_args()
+ lantz.log.log_to_screen(lantz.log.DEBUG)
+ with Cobolt0601(args.port) as inst:
+ if args.interactive:
+ from lantz.ui.qtwidgets import start_test_app
+ start_test_app(inst)
+ else:
+ # Add your test code here
+ print('Non interactive mode')
+ print(inst.idn)
+ print(inst.shg_tuning)
diff --git a/lantz/drivers/legacy/coherent/__init__.py b/lantz/drivers/legacy/coherent/__init__.py
new file mode 100644
index 0000000..ad15d73
--- /dev/null
+++ b/lantz/drivers/legacy/coherent/__init__.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.coherent
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ :company: Coherent Inc.
+ :description: Lasers and Lasers Systems.
+ :website: http://www.coherent.com/
+
+ ----
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from .innova import Innova300C, ArgonInnova300C, KryptonInnova300C
+
+__all__ = ['Innova300C', 'ArgonInnova300C', 'KryptonInnova300C']
diff --git a/lantz/drivers/legacy/coherent/innova.py b/lantz/drivers/legacy/coherent/innova.py
new file mode 100644
index 0000000..af4e4e5
--- /dev/null
+++ b/lantz/drivers/legacy/coherent/innova.py
@@ -0,0 +1,388 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.coherent.innova
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Implements the drivers for Innova 300 Series gas lasers.
+
+
+ Implementation Notes
+ --------------------
+
+ There are currently 3 drivers implemented Innova300C, ArgonInnova300C
+ and KryptonInnova300C. The last two only add to the first the
+ corresponding wavelength selection.
+
+ Sources::
+
+ - Innova 300C Manual
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+
+"""
+
+
+from lantz import Q_, Action, Feat, DictFeat
+from lantz.errors import InvalidCommand
+from lantz.drivers.legacy.serial import SerialDriver
+
+
+def make_feat(command, **kwargs):
+
+ def get(self):
+ return self.query('PRINT {}'.format(command))
+
+ def set(self, value):
+ return self.query('{}={}'.format(command, value))
+
+ if kwargs.pop('readonly', None):
+ return Feat(fget=get, **kwargs)
+ elif kwargs.pop('writeonly', None):
+ return Feat(fset=set, **kwargs)
+
+ return Feat(get, set, **kwargs)
+
+
+class Innova300C(SerialDriver):
+ """Innova300 C Series.
+ """
+
+ ENCODING = 'ascii'
+
+ SEND_TERMINATION = '\r\n'
+ RECV_TERMINATION = '\r\n'
+
+
+ def __init__(self, port=1, baudrate=1200, **kwargs):
+ super().__init__(port, baudrate, bytesize=8, parity='None',
+ stopbits=1, **kwargs)
+
+ def initialize(self):
+ super().initialize()
+ self.echo_enabled = False
+
+ def query(self, command, *, send_args=(None, None), recv_args=(None, None)):
+ """Send query to the laser and return the answer, after handling
+ possible errors.
+
+ :param command: command to be sent to the instrument
+ :type command: string
+
+ :param send_args: (termination, encoding) to override class defaults
+ :param recv_args: (termination, encoding) to override class defaults
+ """
+ ans = super().query(command, send_args=send_args, recv_args=recv_args)
+ # TODO: Echo handling
+ if ans == 'Out of Range':
+ raise ValueError()
+ elif ans.startswith('Syntax Error'):
+ raise InvalidCommand()
+ elif ans == 'Laser must be off':
+ raise Exception('Laser must be off')
+
+ return ans
+
+
+ # General information and communication
+
+ idn = make_feat('ID',
+ readonly=True,
+ doc='Laser identification, should be I300.',
+ read_once=True)
+
+ software_rev = make_feat('SOFTWARE',
+ readonly=True,
+ doc='Software revision level in the power supply.',
+ read_once=True)
+
+ head_software_rev = make_feat('HEAD SOFTWARE',
+ readonly=True,
+ doc='Software revision level in the laser head board.',
+ read_once=True)
+
+ echo_enabled = make_feat('ECHO',
+ writeonly=True,
+ doc='Echo mode of the serial interface.',
+ values={True: 1, False: 0})
+
+ baudrate = Feat(values={110, 300, 1200, 2400, 4800, 9600, 19200})
+
+ @baudrate.setter
+ def baudrate(self, value):
+ """RS-232/422 baud rate, the serial connection will be reset after.
+ """
+ self.query('BAUDRATE={}'.format(value))
+ #TODO: RESET Connection
+
+
+ # Interface
+
+ analog_relative = make_feat('ANALOG MODE',
+ doc='Analog Interface input mode.',
+ values={True: 1, False: 0})
+
+ analog_enabled = make_feat('ANALOGINT',
+ doc='Analog Interface input state.',
+ values={True: 1, False: 0})
+
+ current_range = make_feat('CURRENT RANGE',
+ doc='Current corresponding to 5 Volts at the input'\
+ ' or output lines of the Analog Interface.',
+ units='A',
+ limits=(10, 100, 1))
+
+ control_pin_high = make_feat('CONTROL',
+ readonly=True,
+ doc='State of the input pin 10 of the Analog Interface.',
+ values={True: 1, False: 0})
+
+ output_pin_high = make_feat('STATUS',
+ doc='State of the output pin 24 and 25 of the Analog Interface.',
+ values={(False, False): 0, (True, False): 1,
+ (False, True): 2, (True, True): 3})
+
+ # Diagnostics
+
+ @Feat()
+ def faults(self):
+ """List of all active faults.
+ """
+ return self.query('PRINT FAULT').split('&')
+
+ autofill_delta = make_feat('AUTOFILL DELTA',
+ readonly=True,
+ doc='Tube voltage minus the autofill setting.',
+ units='V')
+
+ autofill_needed = make_feat('AUTOFILL STATUS',
+ readonly=True,
+ doc='Is the autofill needed (wheter fill is enabled or not)',
+ values={True: 1, False: 0})
+
+ remaining_time = make_feat('HRSTILSHUTDOWN',
+ readonly=True,
+ doc='Number of hours remaining before the laser '\
+ 'will shut down automatically.',
+ units='hour')
+
+ cathode_current = make_feat('CATHODE CURRENT',
+ readonly=True,
+ doc='Laser cathode current (AC).',
+ units='A')
+
+ cathode_voltage = make_feat('CATHODE VOLTAGE',
+ readonly=True,
+ doc='Laser cathode voltage (AC).',
+ units='V')
+
+ time_to_start = make_feat('START',
+ readonly=True,
+ doc='Timer countdown during the start delay cycle.',
+ units='second')
+
+ @Feat()
+ def is_in_start_delay(self):
+ """Laser is in start delay (tube not ionized)
+ """
+ return self.query('LASER') == '1'
+
+
+ tube_time = make_feat('HOURS',
+ readonly=True,
+ doc='Number of operating hours on the plasma tube.',
+ units='hour')
+
+ tube_voltage = make_feat('TUBE VOLTAGE',
+ readonly=True,
+ doc='Laser tube voltage.',
+ units='V')
+
+ water_flow = make_feat('FLOW',
+ readonly=True,
+ doc='Water flow.',
+ units='gallons/minute')
+
+ water_resistivity = make_feat('WATER RESISTIVITY',
+ readonly=True,
+ doc='Resistivity of the incoming water to the power supply.',
+ units='kohm*cm')
+
+ water_temperature = make_feat('WATER TEMPERATURE',
+ doc='Temperature of the incoming water to the power supply.')
+
+
+ # Other
+
+ autofill_mode = make_feat('AUTOFILL',
+ doc='Autofill mode.',
+ values={'disabled': 0, 'enabled': 1,
+ 'enabled until next autofill': 2})
+
+ laser_enabled = make_feat('LASER',
+ doc='Energize the power supply.',
+ values={True: 2, False: 0})
+
+ magnet_current = make_feat('MAGNET CURRENT',
+ readonly=True,
+ doc='Laser magnet current.',
+ units='A')
+
+ operating_mode = make_feat('MODE',
+ readonly=True,
+ doc='Laser operating mode.',
+ values={'current regulation': 0,
+ 'reduced bandwidth light regulation': 1,
+ 'standard light regulation': 2,
+ 'current regulation, light regulation out of range': 3})
+
+ # Etalon
+
+ etalon_mode = make_feat('EMODE',
+ doc='Etalon mode.',
+ values={'manual': 0, 'modetrack': 1, 'modetune': 2})
+
+ etalon_temperature = make_feat('ETALON',
+ readonly=True,
+ doc='Etalon temperature.',
+ units='degC')
+
+ @Feat(units='degC', limits=(51.5, 54, 0.001))
+ def etalon_temperature_setpoint(self):
+ """Setpoint for the etalon temperature.
+ """
+ return self.query('PRINT SET ETALON')
+
+ @etalon_temperature_setpoint.setter
+ def etalon_temperature_setpoint(self, value):
+ self.query('ETALON={}'.format(value))
+
+
+ # Magnetic field
+
+ magnetic_field_high = make_feat('FIELD',
+ doc='Magnetic field.',
+ values={True: 1, False: 0})
+
+ @Feat(values={True: 1, False: 0})
+ def magnetic_field_setpoint_high(self):
+ """Setpoint for magnetic field setting.
+ """
+ return self.query('PRINT SET FIELD')
+
+ @magnetic_field_setpoint_high.setter
+ def magnetic_field_setpoint_high(self, value):
+ self.query('FIELD={}'.format(value))
+
+
+ # Light and current regulation
+
+ powertrack_mode_enabled = make_feat('PT',
+ doc='PowerTrack.',
+ values={True: 1, False: 0})
+
+ @DictFeat(keys=('A', 'B'), limits=(0, 255))
+ def powertrack_position(self, key):
+ """Relative position of the PowerTrack solenoids.
+ """
+ return self.query('PRINT PTDAC{}'.format(key))
+
+ @powertrack_position.setter
+ def powertrack_position(self, key, value):
+ self.query('PTDAC{}={}'.format(key, value))
+
+ @Action()
+ def recalibrate_powertrack(self):
+ """Recalibrate PowerTrack. This will only execute if PowerTrack is on
+ and light regulation is off
+ """
+ self.query('PT=2')
+
+ @Action()
+ def center_powertrack(self):
+ """Center PowerTrack and turn it off.
+ """
+ self.query('PT=3')
+
+ current = make_feat('CURRENT',
+ readonly=True,
+ doc='Current regulation mode.',
+ units='A')
+
+ @Feat(units='A', limits=(0, 50, 0.01))
+ def current_setpoint(self):
+ """Current setpoint when using the current regulation mode.
+ """
+ return self.query('PRINT SET CURRENT')
+
+ @current_setpoint.setter
+ def current_setpoint(self, value):
+ self.query('CURRENT={}'.format(value))
+
+
+ power = make_feat('LIGHT 3',
+ readonly=True,
+ doc='Current power output.',
+ units='A')
+
+ @Feat(units='W', limits=(0, 50, 0.0001))
+ def power_setpoint(self):
+ """Setpoint for the light regulation.
+ """
+ return self.query('PRINT SET LIGHT')
+
+ @power_setpoint.setter
+ def power_setpoint(self, value):
+ self.query('LIGHT={}'.format(value))
+
+
+ auto_light_cal_enabled = make_feat('AUTOLTCAL',
+ doc='Automatic light regulation calibration flag.',
+ values={True: 1, False: 0})
+
+ current_change_limit = make_feat('PCTCHGTILRECAL',
+ doc='Percent tube change before an automatic '\
+ 'light regulation recalibration becomes '\
+ 'necessary.',
+ units='', #TODO: %
+ limits=(5, 100, 1))
+
+
+class ArgonInnova300C(Innova300C):
+ """Argon Innova 300C.
+ """
+
+ wavelength = make_feat('WAVELENGTH',
+ doc='Wavelength for the internal power meter calibration',
+ values={351, 364, 454, 457, 465, 472, 476, 488, 496, 501,
+ 514, 528, 1090, 'MLVS', 'MLUV', 'MLDUV'})
+
+
+class KryptonInnova300C(Innova300C):
+ """Krypton Innova 300C.
+ """
+ wavelength = make_feat('WAVELENGTH',
+ doc='Wavelength for the internal power meter calibration',
+ values={476, 482, 520, 530, 568, 647, 676, 752, 'MLVS',
+ 'MLUV', 'MLVI', 'MLBG', 'MLRD', 'MLIR'})
+
+if __name__ == '__main__':
+ import argparse
+ import lantz.log
+
+ parser = argparse.ArgumentParser(description='Test Kentech HRI')
+ parser.add_argument('-i', '--interactive', action='store_true',
+ default=False, help='Show interactive GUI')
+ parser.add_argument('-p', '--port', type=str, default='17',
+ help='Serial port to connect to')
+
+ args = parser.parse_args()
+ lantz.log.log_to_socket(lantz.log.DEBUG)
+ with Innova300C(args.port) as inst:
+ if args.interactive:
+ from lantz.ui.app import start_test_app
+ start_test_app(inst)
+ else:
+ print(inst.idn)
+ print(inst.software_rev)
+ print(inst.head_software_rev)
diff --git a/lantz/drivers/legacy/ieee4882.py b/lantz/drivers/legacy/ieee4882.py
new file mode 100644
index 0000000..2512ad4
--- /dev/null
+++ b/lantz/drivers/legacy/ieee4882.py
@@ -0,0 +1,172 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.ieee4882
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Implements a IEEE-488.2 commands.
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from lantz import Action, Feat, Driver
+
+
+class IEEE4882Driver(Driver):
+ """Implements mandatory functions for a IEE488.2 device.
+
+ You can use it as a mixin class.
+ """
+
+ @Feat(read_once=True)
+ def idn(self):
+ """Instrument identification.
+ """
+ return self.parse_query('*IDN?',
+ format='{manufacturer:s},{model:s},{serialno:s},{softno:s}')
+
+ @Feat(read_once=True)
+ def fitted_options(self):
+ """Fitted options.
+ """
+ return self.query('*OPT?').split(',')
+
+ @Action()
+ def reset(self):
+ """Set the instrument functions to the factory default power up state.
+ """
+ self.send('*RST')
+
+ @Action()
+ def self_test(self):
+ """Performs a complete instrument self-test.
+
+ If test fails, one or more error messages will provide additional information.
+ When available, Use SYSTem:ERRor? to read error queue.
+ """
+ return self.query('*TST?') == '0'
+
+ @Action()
+ def wait(self):
+ """Inhibit execution of an overlapped command until the execution of
+ the preceding operation has been completed.
+ """
+ self.send('*WAI')
+
+ @Action()
+ def trigger(self):
+ """Equivalent to Group Execute Trigger.
+ """
+ self.send('*TRG')
+
+ @Feat()
+ def status_byte(self):
+ """Status byte, a number between 0-255.
+
+ Decimal sum of the bits in the register.
+ Bit #6: Master Summary Status Bit (MSS)
+ This bit is set, if one of the bits in STB becomes true
+ and the corresponding bit in the SRE is enabled.
+ Bit #5: Event Summary Bit (ESB)
+ This bit is set, if one of the bits in ESR becomes true
+ and the corresponding bit in the ESE is enabled.
+ Bit #4: Message Available Bit (MAV)
+ This bit is set, if there is a message in the output buffer available.
+ """
+ return int(self.query('*STB?'))
+
+ @Feat()
+ def service_request_enabled(self):
+ """Service request enable register.
+
+ Decimal sum of the bits in the register.
+ """
+ return int(self.query('*SRE?'))
+
+ @service_request_enabled.setter
+ def service_request_enabled(self, value):
+ return self.query('*SRE {0:d}'.format(value))
+
+ event_status_reg = Feat()
+
+ @event_status_reg.setter
+ def event_status_reg(self):
+ """Queries the event register for the Standard Event Register group.
+ Register is read-only; bits not cleared when read.
+ """
+ return int(self.query('*ESR?'))
+
+ @Feat()
+ def event_status_enabled(self):
+ """Enables bits in the enable register for the Standard Event Register group.
+ The selected bits are then reported to bit 5 of the Status Byte Register.
+
+ Decimal sum of the bits in the register.
+ Bit #7: Enable ESB when Power on or restart
+ Bit #5: Enable ESB when a Command Error occur
+ Bit #3: Enable ESB when a Device Dependent Error occur
+ Bit #0: Enable ESB when Operation complete
+
+ Others are not used.
+ """
+ return int(self.query('*ESE?'))
+
+ @event_status_enabled.setter
+ def event_status_enabled(self, value):
+ self.query('*ESE {0:d}', value)
+
+ @Action()
+ def clear_status(self):
+ """Clears the event registers in all register groups.
+ Also clears the error queue.
+ """
+ self.send('*CLS')
+
+ @Action()
+ def wait_operation_complete_bit(self):
+ """Returns 1 to the output buffer after all pending commands complete.
+
+ Other commands cannot be executed until this command completes.
+ """
+ return self.query('*OPC?')
+
+ @Action()
+ def set_operation_complete_bit(self):
+ """Sets "Operation Complete" (bit 0) in the Standard Event register at
+ the completion of the current operation.
+
+ The purpose of this command is to synchronize your application with the instrument.
+ Other commands may be executed before Operation Complete bit is set.
+ """
+ return self.query('*OPC')
+
+ @Feat(values={True: 0, False: 1})
+ def poweron_status_clear_enabled(self):
+ """Enables or disables clearing of two specific registers at power on:
+ - Standard Event enable register
+ - Status Byte condition register
+ - Questionable Data Register
+ - Standard Operation Register
+ """
+ return self.query('*PSC?')
+
+ @poweron_status_clear_enabled.setter
+ def poweron_status_clear_enabled(self, value):
+ self.query('*PSC {}'.format(value))
+
+ @Action()
+ def recall_state(self, location):
+ """Recalls (*RCL) instrument state in specified non-volatile location.
+
+ :param location: non-volatile storage location.
+ """
+ self.send('*RCL {}'.format(location))
+
+ @Action()
+ def save_state(self, location):
+ """Saves instrument state in specified non-volatile location.
+
+ Previously stored state in location is overwritten (no error is generated).
+ :param location: non-volatile storage location.
+ """
+ self.send('*SAV'.format(location))
diff --git a/lantz/drivers/legacy/kentech/__init__.py b/lantz/drivers/legacy/kentech/__init__.py
new file mode 100644
index 0000000..ac8ac66
--- /dev/null
+++ b/lantz/drivers/legacy/kentech/__init__.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.kentech
+ ~~~~~~~~~~~~~~~~~~~~~
+
+ :company: Kentech Instruments Ltd.
+ :description: Manufacturers of specialised and custom built electronics and imaging equipment.
+ :website: http://www.kentech.co.uk/
+
+ ----
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from .hri import HRI
+
+__all__ = ['HRI', ]
diff --git a/lantz/drivers/legacy/kentech/hri.py b/lantz/drivers/legacy/kentech/hri.py
new file mode 100644
index 0000000..3d65fd1
--- /dev/null
+++ b/lantz/drivers/legacy/kentech/hri.py
@@ -0,0 +1,281 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.kentech.hri
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Implements the driver for Kentech High Repetition Rate Image Intensifier
+ revisions 1 and 2.
+
+
+ Implementation Notes
+ --------------------
+
+ The set of commands is cumbersome and inconsistent. Moreover, each revision
+ introduces backward incompatible changes. The Lantz driver abstracts those
+ differences.
+
+ Sources::
+
+ - LaVision PicoStar HR12
+ - HRI Commands obtained from Kentech
+ - HRI.cl from DaVis
+ - Lantz reverse engineering team
+
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+
+"""
+
+from lantz import Feat, Action
+from lantz.drivers.legacy.serial import SerialDriver
+from lantz.errors import InstrumentError
+
+def between(s, before, after):
+ ndx1 = s.index(before)
+ ndx2 = s.index(after)
+ return s[ndx1+len(before):ndx2]
+
+
+class HRI(SerialDriver):
+ """Kentech High Repetition Rate Image Intensifier.
+ """
+
+ SEND_TERMINATION = '\r'
+ RECV_TERMINATION = '\n'
+ ENCODING = 'ascii'
+
+ def query(self, command, *, send_args=(None, None), recv_args=(None, None)):
+ """Send query to the instrument and return the answer.
+ Set remote mode if needed.
+ """
+ if command and not self.recall('remote'):
+ self.log_info('Setting Remote.')
+ self.remote = True
+ return super().query(command, send_args=send_args, recv_args=recv_args)
+
+ def query_expect(self, command, recv_termination=None, expected='ok'):
+ ans = self.query(command, recv_args=(recv_termination, HRI.ENCODING))
+ if expected and not expected in ans:
+ raise InstrumentError("'{}' not in '{}'".format(expected, ans))
+ return ans
+
+ @Action()
+ def clear(self):
+ """Clear the buffer.
+ """
+ self.send('\r\r')
+
+ @Feat(None, values={True, False})
+ def remote(self, value):
+ """Remote or local.
+ """
+ if value:
+ #self.query_expect('', None, None)
+ self.query_expect('\r', expected=None)
+ self.recv()
+ else:
+ return self.query_expect('LOCAL', chr(0), None)
+
+ @Feat(read_once=True)
+ def revision(self):
+ """Revision.
+ """
+ ans = self.query_expect('.REV', expected=None)
+ print(ans)
+ if 'UNDEFINED' in ans:
+ ans = '1.0'
+ else:
+ ans = self.recv()
+ ans = ans.split()[1]
+ return ans
+
+ @Feat(None, values={'ecl': 'ECLTRIG', 'ttl': 'TTLTRIG'})
+ def trigger_logic(self, value):
+ """Trigger logic.
+ """
+ self.query_expect(value)
+
+ @Feat(None, values={'high': 'HITRIG', '50ohm': '50TRIG}'})
+ def trigger_ttl_termination(self, value):
+ """Trigger termination for TTL logic (for ECL is fixed to 50 ohm).
+ """
+ if self.recall('trigger_type') == 'ecl':
+ raise InstrumentError('Level triggering only with ECL')
+ self.query_expect(value)
+
+ @Feat(None, values={'rising': '+VETRIG', 'falling': '-VETRIG}'})
+ def trigger_edge(self, value):
+ """Trigger on rising or falling edge.
+ """
+ self.query_expect(value)
+
+ @Feat(None, values={'level': 'LVLTRIG', 'log': 'LOGTRIG}'})
+ def trigger_ecl_mode(self, value):
+ """Trigger mode for ECL logic.
+ """
+ if self.recall('trigger_type') == 'ttl':
+ raise InstrumentError('Level triggering only with ECL')
+
+ self.query_expect(value)
+
+ @Feat(units='centivolt', limits=(-40, 40, 1))
+ def trigger_ecl_level(self):
+ """Trigger level for ECL logic, mode level.
+ """
+ if self.revision >= 2.0:
+ ans = self.query_expect('THRESHV ?')
+ ans = between(ans, 'THRESHV ?', 'ok')
+ return float(ans.strip())
+ else:
+ ans = self.query_expect('THRESHV @ .')[8:]
+ try:
+ pos = ans.index('.')
+ except ValueError:
+ raise InstrumentError('Unsupported operation.')
+ return float(ans[pos+2:pos+7])
+
+ @trigger_ecl_level.setter
+ def trigger_ecl_level(self, value):
+ if self.revision >= 2.0:
+ self.query_expect('{:d} !THRESH'.format(value))
+ else:
+ value = 40 * value + 2000.0
+ self.query_expect('{:d} THRESH ! TRIG+RF>HW'.format(value))
+
+ @Feat(units='volt', limits=(-50, 50))
+ def clamp_voltage(self):
+ """Most negative value of the gate pulse.
+ """
+ if self.revision >= 2.0:
+ ans = self.query_expect('CLAMP ?')
+ ans = between(ans, 'CLAMP ?', 'ok').strip()
+ return float(ans)
+ else:
+ ans = self.query_expect('CLAMP @ .')
+ try:
+ pos = ans.index('.')
+ except ValueError:
+ raise InstrumentError('Unsupported operation.')
+ return float(ans[pos+2:pos+7]) / 10.0
+
+ @clamp_voltage.setter
+ def clamp_voltage(self, value):
+ average = self.recall('average_voltage')
+ mn, mx = average - Q_(60, volt), average
+ if mn < value < mx:
+ raise ValueError('Invalid clamp voltage. Not in range {}-{}'.format(mn, mx))
+ self.query_expect('{:d} CLAMP ! CLAMP>HW'.format(value * 10))
+
+ @Feat(units='volt', limits=(-50, 50))
+ def average_voltage(self):
+ """Cathode potential bias with respect of MCP.
+ """
+ if self.revision >= 2.0:
+ ans = self.query_expect('AVE ?')
+ ans = between(ans, 'AVE ?', 'ok')
+ return float(ans.strip()) / 10.
+ else:
+ ans = self.query_expect('THRESHV @ .')[8:]
+ try:
+ pos = ans.index('.')
+ except ValueError:
+ raise InstrumentError('Unsupported operation.')
+ return float(ans[pos+2:pos+7]) / 10.
+
+ @average_voltage.setter
+ def average_voltage(self, value):
+ self.query_expect('{:d} AVE ! AVE>HW'.format(value * 10))
+
+ @Feat()
+ def status(self):
+ """Get status.
+ """
+ return self.query_expect(".STATUS", chr(0))
+
+ @Feat(None, units='volt', limits=(0, 1700))
+ def mcp(self, value):
+ """MCP Voltage.
+ """
+ if self.revision >= '2.0':
+ return self.query_expect('{} !MCP'.format(value))
+ else:
+ return self.query_expect('{} !MCPVOLTS'.format(value))
+
+ @Feat(None, values={'inhibit': 0, 'rf': 21, 'ldc': 22, 'hdc': 23, 'dc': 24,
+ 'user1': 25, 'user2': 26, 'user3': 27, 'user4': 28})
+ def mode(self, mode):
+ """Gain modulation mode.
+
+ HRI Machine Modes and Mode Indices
+ None Mode
+ 0 INHIBIT
+ 2-10 COMB modes 200 ps to 1 ns inclusive (High rate operation)
+ 11-20 COMB modes 100 ps to 3 ns inclusive (Low rate (+GOI) operation)
+ 21 RF
+ 22 logic low duty cycle (LDC)
+ 23 logic high duty cycle
+ 24 DC
+ 25-28 user modes 1 to 4
+ """
+ #TODO: Modes [11-20] not available in rev < 2.0
+ return self.query_expect("{} !MODE".format(mode))
+
+ @Feat(None)
+ def rfgain(self, value):
+ """RF Gain.
+ """
+ return self.query("{} !RFGAIN".format(value))
+
+ @Feat()
+ def temperature(self):
+ """Temperature.
+ """
+ if self.revision == 2.0:
+ return self.query("@TEMP .")
+ return 0
+
+ @Feat(None, values={True, False})
+ def enabled(self, value):
+ """MCP Enabled
+ """
+ if self.revision < 2:
+ if value:
+ self.query_expect('+M')
+ else:
+ self.query_expect('-M')
+ else:
+ if value:
+ self.mode = self.__dict__.get('_last_mode', 21)
+ else:
+ self._last_mode = self.recall('mode')
+ self.mode = 0
+
+
+if __name__ == '__main__':
+ import argparse
+ import lantz.log
+
+ parser = argparse.ArgumentParser(description='Test Kentech HRI')
+ parser.add_argument('-i', '--interactive', action='store_true',
+ default=False, help='Show interactive GUI')
+ parser.add_argument('-p', '--port', type=str, default='17',
+ help='Serial port to connect to')
+
+ args = parser.parse_args()
+ lantz.log.log_to_socket(lantz.log.DEBUG)
+ with HRI(args.port, baudrate=9600) as inst:
+ if args.interactive:
+ from lantz.ui.app import start_test_app
+ start_test_app(inst)
+ else:
+ #inst.clear()
+ inst.remote = True
+ print(inst.revision)
+ inst.mode = "inhibit"
+ inst.mcp = 350
+ inst.rfgain = 99
+ #print(inst.status)
+ inst.mode = "rf"
+ #print(inst.status)
+ inst.remote = False
diff --git a/lantz/drivers/legacy/labjack/__init__.py b/lantz/drivers/legacy/labjack/__init__.py
new file mode 100644
index 0000000..42bb30f
--- /dev/null
+++ b/lantz/drivers/legacy/labjack/__init__.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.labjack
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ :company: LabJack
+ :description: LabJacks are USB/Ethernet based measurement and automation
+ devices which provide analog inputs/outputs, digital inputs/outputs, and more.
+ They serve as an inexpensive and easy to use interface between computers and
+ the physical world.
+ :website: http://www.labjack.com/
+
+ ----
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from .u12 import U12
+
+__all__ = ['U12']
+
diff --git a/lantz/drivers/legacy/labjack/_internal/u12.py b/lantz/drivers/legacy/labjack/_internal/u12.py
new file mode 100644
index 0000000..1725c51
--- /dev/null
+++ b/lantz/drivers/legacy/labjack/_internal/u12.py
@@ -0,0 +1,2983 @@
+# -*- coding: utf-8 -*-
+"""
+Name: u12.py
+Desc: Defines the U12 class, which makes working with a U12 much easier. The
+ functions of the U12 class are divided into two categories: UW and
+ low-level.
+
+ Most of the UW functions are exposed as functions of the U12 class. With
+ the exception of the "e" functions, UW functions are Windows only. The "e"
+ functions will work with both the UW and the Exodriver. Therefore, people
+ wishing to write cross-platform code should restrict themselves to using
+ only the "e" functions. The UW functions are described in Section 4 of the
+ U12 User's Guide:
+
+ http://labjack.com/support/u12/users-guide/4
+
+ All low-level functions of the U12 class begin with the word
+ raw. For example, the low-level function Counter can be called with
+ U12.rawCounter(). Currently, low-level functions are limited to the
+ Exodriver (Linux and Mac OS X). You can find descriptions of the low-level
+ functions in Section 5 of the U12 User's Guide:
+
+ http://labjack.com/support/u12/users-guide/5
+
+"""
+
+import platform
+import ctypes
+import os, atexit
+import math
+from time import time
+import struct
+
+WINDOWS = "Windows"
+ON_WINDOWS = (os.name == 'nt')
+
+class U12Exception(Exception):
+ """Custom Exception meant for dealing specifically with U12 Exceptions.
+
+ Error codes are either going to be a LabJackUD error code or a -1. The -1 implies
+ a python wrapper specific error.
+ def __init__(self, ec = 0, errorString = ''):
+ self.errorCode = ec
+ self.errorString = errorString
+
+ if not self.errorString:
+ #try:
+ self.errorString = getErrorString(ec)
+ #except:
+ # self.errorString = str(self.errorCode)
+
+ def __str__(self):
+ return self.errorString
+
+ """
+ pass
+
+class BitField(object):
+ """
+ Provides a method for working with bit fields.
+
+ >>> bf = BitField()
+ >>> print bf
+ [ bit7 = 0, bit6 = 0, bit5 = 0, bit4 = 0, bit3 = 0, bit2 = 0, bit1 = 0, bit0 = 0 ]
+
+ You can use attribute accessing for easy bit flipping:
+ >>> bf.bit4 = 1
+ >>> bf.bit7 = 1
+ >>> print bf
+ [ bit7 = 1, bit6 = 0, bit5 = 0, bit4 = 1, bit3 = 0, bit2 = 0, bit1 = 0, bit0 = 0 ]
+
+ You can also use list-style accessing. Counting starts on the left:
+ >>> print bf[0] # List index 0 is bit7
+ 1
+ >>> print bf[3] # List index 3 is bit4
+ 1
+
+ List-style slicing:
+ >>> print bf[3:]
+ [1, 0, 0, 0, 0]
+
+ List-style setting bits works as you would expect:
+ >>> bf[1] = 1
+ >>> print bf
+ [ bit7 = 1, bit6 = 1, bit5 = 0, bit4 = 1, bit3 = 0, bit2 = 0, bit1 = 0, bit0 = 0 ]
+
+ It provides methods for going to and from bytes:
+
+ >>> bf = BitField(123)
+ >>> print bf
+ [ bit7 = 0, bit6 = 1, bit5 = 1, bit4 = 1, bit3 = 1, bit2 = 0, bit1 = 1, bit0 = 1 ]
+
+ >>> bf = BitField()
+ >>> bf.fromByte(123) # Modifies bf in place
+ >>> print bf
+ [ bit7 = 0, bit6 = 1, bit5 = 1, bit4 = 1, bit3 = 1, bit2 = 0, bit1 = 1, bit0 = 1 ]
+
+ >>> bf.bit4 = 0
+ >>> print bf.asByte()
+ 107
+
+ You can iterate of the raw bits ( 1 and 0 Vs. '1' and '0') easily:
+ >>> for i in bf:
+ ... print i
+ 0
+ 1
+ 1
+ 0
+ 1
+ 0
+ 1
+ 1
+
+ You can also iterate over the labels and their data values using items():
+ >>> for label, data in bf.items():
+ ... print label, data
+ bit7 0
+ bit6 1
+ bit5 1
+ bit4 0
+ bit3 1
+ bit2 0
+ bit1 1
+ bit0 1
+
+ As an added bonus, it can also be cast as an int or hex:
+ >>> int(bf)
+ 107
+
+ >>> hex(bf)
+ '0x6b'
+
+ See the description of the __init__ method for setting the label parameters. """
+ def __init__(self, rawByte = None, labelPrefix = "bit", labelList = None, zeroLabel = "0", oneLabel = "1"):
+ """
+ Name: BitField.__init__(rawByte = None, labelPrefix = "bit",
+ labelList = None, zeroLabel = "0",
+ oneLabel = "1")
+ Args: rawByte, a value to set the bit field values to.
+ labelPrefix, what should go before the labels in labelList
+ labelList, a list of labels to apply to each bit. If None, it
+ gets set to range(7,-1,-1).
+ zeroLabel, bits with a value of 0 will have this label
+ oneLabel, bits with a value of 1 will have this label
+ Desc: Creates a new bitfield and sets up the labels.
+
+ With out any arguments, you get a bit field that looks like this:
+ >>> bf = BitField()
+ >>> print bf
+ [ bit7 = 0, bit6 = 0, bit5 = 0, bit4 = 0, bit3 = 0, bit2 = 0, bit1 = 0,
+ bit0 = 0 ]
+
+ To make the labels, it iterates over all the labelList and adds the
+ labelPrefix to them. If you have less than 8 labels, then your bit field
+ will only work up to that many bits.
+
+ To make a BitField with labels for FIO0-7 you can do the following:
+ >>> bf = BitField(labelPrefix = "FIO")
+ >>> print bf
+ [ FIO7 = 0, FIO6 = 0, FIO5 = 0, FIO4 = 0, FIO3 = 0, FIO2 = 0, FIO1 = 0,
+ FIO0 = 0 ]
+
+
+ The labels don't have to be numbers, for example:
+ >>> names = [ "Goodreau", "Jerri", "Selena", "Allan", "Tania",
+ "Kathrine", "Jessie", "Zelma" ]
+ >>> bf = BitField( labelPrefix = "", labelList = names)
+ >>> print bf
+ [ Goodreau = 0, Jerri = 0, Selena = 0, Allan = 0, Tania = 0,
+ Kathrine = 0, Jessie = 0, Zelma = 0 ]
+
+ You can change the display value of zero and one to be whatever you
+ want. For example, if you have a BitField that represents FIO0-7
+ directions:
+ >>> dirs = BitField(rawByte = 5, labelPrefix = "FIO",
+ zeroLabel = "Output", oneLabel = "Input")
+ >>> print dirs
+ [ FIO7 = Output, FIO6 = Output, FIO5 = Output, FIO4 = Output,
+ FIO3 = Output, FIO2 = Input, FIO1 = Output, FIO0 = Input ]
+
+ Note, that when you access the value, you will get 1 or 0, not "Input"
+ or "Output. For example:
+ >>> print dirs.FIO3
+ 0
+ """
+ # Do labels first, so that self.something = something works.
+ self.__dict__['labels'] = []
+
+ self.labelPrefix = labelPrefix
+
+ if labelList is None:
+ self.labelList = list(range(8))
+ else:
+ self.labelList = list(reversed(labelList))
+
+ self.zeroLabel = zeroLabel
+ self.oneLabel = oneLabel
+
+ self.rawValue = 0
+ self.rawBits = [ 0 ] * 8
+ self.data = [ self.zeroLabel ] * 8
+
+ items = min(8, len(self.labelList))
+ for i in reversed(list(range(items))):
+ self.labels.append("%s%s" % (self.labelPrefix, self.labelList[i]))
+
+ if rawByte is not None:
+ self.fromByte(rawByte)
+
+ def fromByte(self, raw):
+ """
+ Name: BitField.fromByte(raw)
+ Args: raw, the raw byte to make the BitField.
+ Desc: Takes a byte, and modifies self to match.
+
+ >>> bf = BitField()
+ >>> bf.fromByte(123) # Modifies bf in place
+ >>> print bf
+ [ bit7 = 0, bit6 = 1, bit5 = 1, bit4 = 1, bit3 = 1, bit2 = 0, bit1 = 1,
+ bit0 = 1 ]
+ """
+ self.rawValue = raw
+ self.rawBits = []
+ self.data = []
+
+ items = min(8, len(self.labelList))
+ for i in reversed(list(range(items))):
+ self.rawBits.append( ((raw >> (i)) & 1) )
+ self.data.append(self.oneLabel if bool(((raw >> (i)) & 1)) else self.zeroLabel)
+
+ def asByte(self):
+ """
+ Name: BitField.asByte()
+ Args: None
+ Desc: Returns the value of the bitfield as a byte.
+
+ >>> bf = BitField()
+ >>> bf.fromByte(123) # Modifies bf in place
+ >>> bf.bit4 = 0
+ >>> print bf.asByte()
+ 107
+ """
+ byteVal = 0
+ for i, v in enumerate(reversed(self.rawBits)):
+ byteVal += ( 1 << i ) * v
+
+ return byteVal
+
+ def asBin(self):
+ result = "0b"
+ for i in self.rawBits:
+ result += "%s" % i
+
+ return result
+
+ def __len__(self):
+ return len(self.data)
+
+ def __repr__(self):
+ result = "["
+ for i in range(len(self.data)):
+ result += " %s = %s (%s)," % (self.labels[i], self.data[i], self.rawBits[i])
+ result = result.rstrip(',')
+ result += " ]"
+ return "" % result
+
+ def __str__(self):
+ result = "["
+ for i in range(len(self.data)):
+ result += " %s = %s," % (self.labels[i], self.data[i])
+ result = result.rstrip(',')
+ result += " ]"
+ return result
+
+ def __getattr__(self, label):
+ try:
+ i = self.labels.index(label)
+ return self.rawBits[i]
+ except ValueError:
+ raise AttributeError(label)
+
+ def __setattr__(self, label, value):
+ try:
+ i = self.labels.index(label)
+ self.rawBits[i] = int(bool(value))
+ self.data[i] = self.oneLabel if bool(value) else self.zeroLabel
+ except ValueError:
+ self.__dict__[label] = value
+
+ def __getitem__(self, key):
+ return self.rawBits[key]
+
+ def __setitem__(self, key, value):
+ self.rawBits[key] = int(bool(value))
+ self.data[key] = self.oneLabel if bool(value) else self.zeroLabel
+
+ def __iter__(self):
+ return iter(self.rawBits)
+
+ def items(self):
+ """
+ Name: BitField.items()
+ Args: None
+ Desc: Returns a list of tuples where the first item is the label and the
+ second is the string value, like "High" or "Input"
+
+ >>> dirs = BitField(rawByte = 5, labelPrefix = "FIO",
+ zeroLabel = "Output", oneLabel = "Input")
+ >>> print dirs
+ [ FIO7 = Output, FIO6 = Output, FIO5 = Output, FIO4 = Output,
+ FIO3 = Output, FIO2 = Input, FIO1 = Output, FIO0 = Input ]
+ >>> for label, data in dirs.items():
+ ... print label, data
+ ...
+ FIO7 Output
+ FIO6 Output
+ FIO5 Output
+ FIO4 Output
+ FIO3 Output
+ FIO2 Input
+ FIO1 Output
+ FIO0 Input
+ """
+ return list(zip(self.labels, self.data))
+
+ def __int__(self):
+ return self.asByte()
+
+ def __hex__(self):
+ return hex(self.asByte())
+
+ def __add__(self, other):
+ """
+ A helper to prevent having to test if a variable is a bitfield or int.
+ """
+ return other + self.asByte()
+
+def errcheck(ret, func, args):
+ if ret == -1:
+ try:
+ ec = ctypes.get_errno()
+ raise U12Exception("Exodriver returned error number %s" % ec)
+ except AttributeError:
+ raise U12Exception("Exodriver returned an error, but LabJackPython is unable to read the error code. Upgrade to Python 2.6 for this functionality.")
+ else:
+ return ret
+
+def _loadLinuxSo():
+ try:
+ l = ctypes.CDLL("liblabjackusb.so", use_errno=True)
+ except TypeError:
+ l = ctypes.CDLL("liblabjackusb.so")
+ l.LJUSB_Stream.errcheck = errcheck
+ l.LJUSB_Read.errcheck = errcheck
+ return l
+
+def _loadMacDylib():
+ try:
+ l = ctypes.CDLL("liblabjackusb.dylib", use_errno=True)
+ except TypeError:
+ l = ctypes.CDLL("liblabjackusb.dylib")
+ l.LJUSB_Stream.errcheck = errcheck
+ l.LJUSB_Read.errcheck = errcheck
+ return l
+
+staticLib = None
+if os.name == 'posix':
+ try:
+ staticLib = _loadLinuxSo()
+ except OSError as e:
+ pass # We may be on Mac.
+ except Exception as e:
+ raise U12Exception("Could not load the Linux SO for some reason other than it not being installed. Ethernet connectivity only.\n\n The error was: %s" % e)
+
+ try:
+ if staticLib is None:
+ staticLib = _loadMacDylib()
+ except OSError as e:
+ raise U12Exception("Could not load the Exodriver driver. Ethernet connectivity only.\n\nCheck that the Exodriver is installed, and the permissions are set correctly.\nThe error message was: %s" % e)
+ except Exception as e:
+ raise U12Exception("Could not load the Mac Dylib for some reason other than it not being installed. Ethernet connectivity only.\n\n The error was: %s" % e)
+else:
+ try:
+ staticLib = ctypes.windll.LoadLibrary("ljackuw")
+ except:
+ raise Exception("Could not load LabJack UW driver.")
+
+class U12(object):
+ """
+ U12 Class for all U12 specific commands.
+
+ u12 = U12()
+
+ """
+ def __init__(self, id = -1, serialNumber = None, debug = False):
+ self.id = id
+ self.serialNumber = serialNumber
+ self.deviceName = "U12"
+ self.streaming = False
+ self.handle = None
+ self.debug = debug
+ self._autoCloseSetup = False
+
+ if not ON_WINDOWS:
+ # Save some variables to save state.
+ self.pwmAVoltage = 0
+ self.pwmBVoltage = 0
+
+ self.open(id, serialNumber)
+
+ def open(self, id = -1, serialNumber = None):
+ """
+ Opens the U12.
+
+ The Windows UW driver opens the device every time a function is called.
+ The Exodriver, however, works like the UD family of devices and returns
+ a handle. On Windows, this method does nothing. On Mac OS X and Linux,
+ this method acquires a device handle and saves it to the U12 object.
+ """
+ if ON_WINDOWS:
+ pass
+ else:
+ if self.debug: print("open called")
+ devType = ctypes.c_ulong(1)
+ openDev = staticLib.LJUSB_OpenDevice
+ openDev.restype = ctypes.c_void_p
+
+
+ if serialNumber is not None:
+ numDevices = staticLib.LJUSB_GetDevCount(devType)
+
+ for i in range(numDevices):
+ handle = openDev(i+1, 0, devType)
+
+ if handle != 0 and handle is not None:
+ self.handle = ctypes.c_void_p(handle)
+
+ try:
+ serial = self.rawReadSerial()
+ except Exception:
+ serial = self.rawReadSerial()
+
+ if serial == int(serialNumber):
+ break
+ else:
+ self.close()
+
+ if self.handle is None:
+ raise U12Exception("Couldn't find a U12 with a serial number matching %s" % serialNumber)
+
+ elif id != -1:
+ numDevices = staticLib.LJUSB_GetDevCount(devType)
+
+ for i in range(numDevices):
+ handle = openDev(i+1, 0, devType)
+
+ if handle != 0 and handle is not None:
+ self.handle = ctypes.c_void_p(handle)
+
+ try:
+ unitId = self.rawReadLocalId()
+ except Exception:
+ unitId = self.rawReadLocalId()
+
+ if unitId == int(id):
+ break
+ else:
+ self.close()
+
+ if self.handle is None:
+ raise U12Exception("Couldn't find a U12 with a local ID matching %s" % id)
+ elif id == -1:
+ handle = openDev(1, 0, devType)
+
+ if handle == 0 or handle is None:
+ raise Exception("Couldn't open a U12. Check that one is connected and try again.")
+ else:
+ self.handle = ctypes.c_void_p(handle)
+
+ # U12 ignores first command, so let's write a command.
+ command = [ 0 ] * 8
+ command[5] = 0x57 # 0b01010111
+
+ try:
+ self.write(command)
+ self.read()
+ except:
+ pass
+
+ self.id = self.rawReadLocalId()
+
+ else:
+ raise Exception("Invalid combination of parameters.")
+
+
+ if not self._autoCloseSetup:
+ # Only need to register auto-close once per device.
+ atexit.register(self.close)
+ self._autoCloseSetup = True
+
+ def close(self):
+ if ON_WINDOWS:
+ pass
+ else:
+ staticLib.LJUSB_CloseDevice(self.handle)
+ self.handle = None
+
+ def write(self, writeBuffer):
+ if ON_WINDOWS:
+ pass
+ else:
+ if self.handle is None:
+ raise U12Exception("The U12's handle is None. Please open a U12 with open()")
+
+ if self.debug: print("Writing:", hexWithoutQuotes(writeBuffer))
+ newA = (ctypes.c_byte*len(writeBuffer))(0)
+ for i in range(len(writeBuffer)):
+ newA[i] = ctypes.c_byte(writeBuffer[i])
+
+ writeBytes = staticLib.LJUSB_Write(self.handle, ctypes.byref(newA), len(writeBuffer))
+
+ if(writeBytes != len(writeBuffer)):
+ raise U12Exception( "Could only write %s of %s bytes." % (writeBytes, len(writeBuffer) ) )
+
+ return writeBuffer
+
+ def read(self, numBytes = 8):
+ if ON_WINDOWS:
+ pass
+ else:
+ if self.handle is None:
+ raise U12Exception("The U12's handle is None. Please open a U12 with open()")
+ newA = (ctypes.c_byte*numBytes)()
+ readBytes = staticLib.LJUSB_Read(self.handle, ctypes.byref(newA), numBytes)
+ # return a list of integers in command/response mode
+ result = [(newA[i] & 0xff) for i in range(readBytes)]
+ if self.debug: print("Received:", hexWithoutQuotes(result))
+ return result
+
+
+ # Low-level helpers
+ def rawReadSerial(self):
+ """
+ Name: U12.rawReadSerial()
+
+ Args: None
+
+ Desc: Reads the serial number from internal memory.
+
+ Returns: The U12's serial number as an integer.
+
+ Example:
+ >>> import u12
+ >>> d = u12.U12()
+ >>> print d.rawReadSerial()
+ 10004XXXX
+ """
+ results = self.rawReadRAM()
+ return struct.unpack(">I", struct.pack("BBBB", results['DataByte3'], results['DataByte2'], results['DataByte1'], results['DataByte0']))[0]
+
+ def rawReadLocalId(self):
+ """
+ Name: U12.rawReadLocalId()
+
+ Args: None
+
+ Desc: Reads the Local ID from internal memory.
+
+ Returns: The U12's Local ID as an integer.
+
+ Example:
+ >>> import u12
+ >>> d = u12.U12()
+ >>> print d.rawReadLocalId()
+ 0
+ """
+ results = self.rawReadRAM(0x08)
+ return results['DataByte0']
+
+
+ # Begin Section 5 Functions
+
+ def rawAISample(self, channel0PGAMUX = 8, channel1PGAMUX = 9, channel2PGAMUX = 10, channel3PGAMUX = 11, UpdateIO = False, LEDState = True, IO3toIO0States = 0, EchoValue = 0):
+ """
+ Name: U12.rawAISample(channel0PGAMUX = 8, channel1PGAMUX = 9,
+ channel2PGAMUX = 10, channel3PGAMUX = 11,
+ UpdateIO = False, LEDState = True,
+ IO3toIO0States = 0, EchoValue = 0)
+
+ Args: channel0PGAMUX, A byte that contains channel0 information
+ channel1PGAMUX, A byte that contains channel1 information
+ channel2PGAMUX, A byte that contains channel2 information
+ channel3PGAMUX, A byte that contains channel3 information
+ IO3toIO0States, A byte that represents the states of IO0 to IO3
+ UpdateIO, If true, set IO0 to IO 3 to match IO3toIO0States
+ LEDState, Turns the status LED on or off.
+ EchoValue, Sometimes, you want what you put in.
+
+ Desc: Collects readings from 4 analog inputs. It can also toggle the
+ status LED and update the state of the IOs. See Section 5.1 of
+ the User's Guide.
+
+ By default it will read AI0-3 (single-ended).
+
+ Returns: A dictionary with the following keys:
+ PGAOvervoltage, A bool representing if the U12 detected overvoltage
+ IO3toIO0States, a BitField representing the state of IO0 to IO3
+ Channel0-3, the analog voltage for the channel
+ EchoValue, a repeat of the value passed in.
+
+ Example:
+ >>> import u12
+ >>> d = u12.U12()
+ >>> d.rawAISample()
+ {
+ 'IO3toIO0States':
+ ,
+ 'Channel0': 1.46484375,
+ 'Channel1': 1.4501953125,
+ 'Channel2': 1.4599609375,
+ 'Channel3': 1.4306640625,
+ 'PGAOvervoltage': False,
+ 'EchoValue': 0
+ }
+
+ """
+ command = [ 0 ] * 8
+
+ # Bits 6-4: PGA for 1st Channel
+ # Bits 3-0: MUX command for 1st Channel
+ command[0] = int(channel0PGAMUX)
+
+ tempNum = command[0] & 7 # 7 = 0b111
+ channel0Number = tempNum if (command[0] & 0xf) > 7 else tempNum+8
+ channel0Gain = (command[0] >> 4) & 7 # 7 = 0b111
+
+ command[1] = int(channel1PGAMUX)
+
+ tempNum = command[1] & 7 # 7 = 0b111
+ channel1Number = tempNum if (command[1] & 0xf) > 7 else tempNum+8
+ channel1Gain = (command[1] >> 4) & 7 # 7 = 0b111
+
+ command[2] = int(channel2PGAMUX)
+
+ tempNum = command[2] & 7 # 7 = 0b111
+ channel2Number = tempNum if (command[2] & 0xf) > 7 else tempNum+8
+ channel2Gain = (command[2] >> 4) & 7 # 7 = 0b111
+
+ command[3] = int(channel3PGAMUX)
+
+ tempNum = command[3] & 7 # 7 = 0b111
+ channel3Number = tempNum if (command[3] & 0xf) > 7 else tempNum+8
+ channel3Gain = (command[3] >> 4) & 7 # 7 = 0b111
+
+ # Bit 1: Update IO
+ # Bit 0: LED State
+ bf = BitField()
+ bf.bit1 = int(UpdateIO)
+ bf.bit0 = int(LEDState)
+ command[4] = int(bf)
+
+ # Bit 7-4: 1100 (Command/Response)
+ # Bit 3-0: Bits for IO3 through IO0 States
+ bf.fromByte(0)
+ bf.bit7 = 1
+ bf.bit6 = 1
+
+ bf.fromByte( int(bf) | int(IO3toIO0States) )
+ command[5] = int(bf)
+
+ command[7] = EchoValue
+
+ self.write(command)
+ results = self.read()
+
+ bf = BitField()
+
+ bf.fromByte(results[0])
+
+ if bf.bit7 != 1 or bf.bit6 != 0:
+ raise U12Exception("Expected a AIStream response, got %s instead." % results[0])
+
+ returnDict = {}
+ returnDict['EchoValue'] = results[1]
+ returnDict['PGAOvervoltage'] = bool(bf.bit4)
+ returnDict['IO3toIO0States'] = BitField(results[0], "IO", list(range(3, -1, -1)), "Low", "High")
+
+ channel0 = (results[2] >> 4) & 0xf
+ channel1 = (results[2] & 0xf)
+ channel2 = (results[5] >> 4) & 0xf
+ channel3 = (results[5] & 0xf)
+
+ channel0 = (channel0 << 8) + results[3]
+ returnDict['Channel0'] = self.bitsToVolts(channel0Number, channel0Gain, channel0)
+
+ channel1 = (channel1 << 8) + results[4]
+ returnDict['Channel1'] = self.bitsToVolts(channel1Number, channel1Gain, channel1)
+
+ channel2 = (channel2 << 8) + results[6]
+ returnDict['Channel2'] = self.bitsToVolts(channel2Number, channel2Gain, channel2)
+
+ channel3 = (channel3 << 8) + results[7]
+ returnDict['Channel3'] = self.bitsToVolts(channel3Number, channel3Gain, channel3)
+
+ return returnDict
+
+ def rawDIO(self, D15toD8Directions = 0, D7toD0Directions = 0, D15toD8States = 0, D7toD0States = 0, IO3toIO0DirectionsAndStates = 0, UpdateDigital = False):
+ """
+ Name: U12.rawDIO(D15toD8Directions = 0, D7toD0Directions = 0,
+ D15toD8States = 0, D7toD0States = 0,
+ IO3toIO0DirectionsAndStates = 0, UpdateDigital = 1)
+
+ Args: D15toD8Directions, A byte where 0 = Output, 1 = Input for D15-8
+ D7toD0Directions, A byte where 0 = Output, 1 = Input for D7-0
+ D15toD8States, A byte where 0 = Low, 1 = High for D15-8
+ D7toD0States, A byte where 0 = Low, 1 = High for D7-0
+ IO3toIO0DirectionsAndStates, Bits 7-4: Direction, 3-0: State
+ UpdateDigital, True if you want to update the IO/D line. False to
+ False to just read their values.
+
+ Desc: This commands reads the direction and state of all the digital
+ I/O. See Section 5.2 of the U12 User's Guide.
+
+ By default, it just reads the directions and states.
+
+ Returns: A dictionary with the following keys:
+ D15toD8Directions, a BitField representing the directions of D15-D8
+ D7toD0Directions, a BitField representing the directions of D7-D0.
+ D15toD8States, a BitField representing the states of D15-D8.
+ D7toD0States, a BitField representing the states of D7-D0.
+ IO3toIO0States, a BitField representing the states of IO3-IO0.
+ D15toD8OutputLatchStates, BitField of output latch states for D15-8
+ D7toD0OutputLatchStates, BitField of output latch states for D7-0
+
+ Example:
+ >>> import u12
+ >>> d = u12.U12()
+ >>> d.rawDIO()
+ {
+
+ 'D15toD8Directions':
+ ,
+
+ 'D7toD0Directions':
+ ,
+
+ 'D15toD8States':
+ ,
+
+ 'D7toD0States':
+ ,
+
+ 'IO3toIO0States':
+ ,
+
+ 'D15toD8OutputLatchStates':
+ ,
+
+ 'D7toD0OutputLatchStates':
+
+ }
+
+ """
+ command = [ 0 ] * 8
+
+ # Bits for D15 through D8 Direction
+ command[0] = int(D15toD8Directions)
+
+ # Bits for D7 through D0 Direction ( 0 = Output, 1 = Input)
+ command[1] = int(D7toD0Directions)
+
+ # Bits for D15 through D8 State ( 0 = Low, 1 = High)
+ command[2] = int(D15toD8States)
+
+ # Bits for D7 through D0 State ( 0 = Low, 1 = High)
+ command[3] = int(D7toD0States)
+
+ # Bits 7-4: Bits for IO3 through IO0 Direction
+ # Bits 3-0: Bits for IO3 through IO0 State
+ command[4] = int(IO3toIO0DirectionsAndStates)
+
+ # 01X10111 (DIO)
+ command[5] = 0x57 # 0b01010111
+
+ # Bit 0: Update Digital
+ command[6] = int(bool(UpdateDigital))
+
+ #XXXXXXXX
+ # command[7] = XXXXXXXX
+
+ self.write(command)
+ results = self.read()
+
+ returnDict = {}
+
+ if results[0] != 87:
+ raise U12Exception("Expected a DIO response, got %s instead." % results[0])
+
+ returnDict['D15toD8States'] = BitField(results[1], "D", list(range(15, 7, -1)), "Low", "High")
+ returnDict['D7toD0States'] = BitField(results[2], "D", list(range(7, -1, -1)), "Low", "High")
+
+ returnDict['D15toD8Directions'] = BitField(results[4], "D", list(range(15, 7, -1)), "Output", "Input")
+ returnDict['D7toD0Directions'] = BitField(results[5], "D", list(range(7, -1, -1)), "Output", "Input")
+
+ returnDict['D15toD8OutputLatchStates'] = BitField(results[6], "D", list(range(15, 7, -1)))
+ returnDict['D7toD0OutputLatchStates'] = BitField(results[7], "D", list(range(7, -1, -1)))
+
+ returnDict['IO3toIO0States'] = BitField((results[3] >> 4), "IO", list(range(3, -1, -1)), "Low", "High")
+
+ return returnDict
+
+ def rawCounter(self, StrobeEnabled = False, ResetCounter = False):
+ """
+ Name: U12.rawCounter(StrobeEnabled = False, ResetCounter = False)
+ Args: StrobeEnable, set to True to enable strobe.
+ ResetCounter, set to True to reset the counter AFTER reading.
+ Desc: This command controls and reads the 32-bit counter. See
+ Section 5.3 of the User's Guide.
+
+ Returns: A dictionary with the following keys:
+ D15toD8States, a BitField representing the states of D15-D8.
+ D7toD0States, a BitField representing the states of D7-D0.
+ IO3toIO0States, a BitField representing the states of IO3-IO0.
+ Counter, the value of the counter
+
+ Example:
+ >>> import u12
+ >>> d = u12.U12()
+ >>> d.rawCounter()
+ {
+ 'D15toD8States':
+ ,
+
+ 'D7toD0States':
+ ,
+
+ 'IO3toIO0States':
+ ,
+
+ 'Counter': 0
+ }
+
+
+ """
+ command = [ 0 ] * 8
+
+ bf = BitField()
+ bf.bit1 = int(StrobeEnabled)
+ bf.bit0 = int(ResetCounter)
+
+ command[0] = int(bf)
+
+ bf.fromByte(0)
+ bf.bit6 = 1
+ bf.bit4 = 1
+ bf.bit1 = 1
+ command[5] = int(bf)
+
+ self.write(command)
+ results = self.read()
+
+ returnDict = {}
+
+ if results[0] != command[5]:
+ raise U12Exception("Expected a Counter response, got %s instead." % results[0])
+
+ returnDict['D15toD8States'] = BitField(results[1], "D", list(range(15, 7, -1)), "Low", "High")
+ returnDict['D7toD0States'] = BitField(results[2], "D", list(range(7, -1, -1)), "Low", "High")
+ returnDict['IO3toIO0States'] = BitField((results[3] >> 4), "IO", list(range(3, -1, -1)), "Low", "High")
+
+ counter = results[7]
+ counter += results[6] << 8
+ counter += results[5] << 16
+ counter += results[4] << 24
+ returnDict['Counter'] = counter
+
+ return returnDict
+
+ def rawCounterPWMDIO(self, D15toD8Directions = 0, D7toD0Directions = 0, D15toD8States = 0, D7toD0States = 0, IO3toIO0DirectionsAndStates = 0, ResetCounter = False, UpdateDigital = 0, PWMA = 0, PWMB = 0):
+ """
+ Name: U12.rawCounterPWMDIO( D15toD8Directions = 0, D7toD0Directions = 0,
+ D15toD8States = 0, D7toD0States = 0,
+ IO3toIO0DirectionsAndStates = 0,
+ ResetCounter = False, UpdateDigital = 0,
+ PWMA = 0, PWMB = 0)
+
+ Args: D15toD8Directions, A byte where 0 = Output, 1 = Input for D15-8
+ D7toD0Directions, A byte where 0 = Output, 1 = Input for D7-0
+ D15toD8States, A byte where 0 = Low, 1 = High for D15-8
+ D7toD0States, A byte where 0 = Low, 1 = High for D7-0
+ IO3toIO0DirectionsAndStates, Bits 7-4: Direction, 3-0: State
+ ResetCounter, If True, reset the counter after reading.
+ UpdateDigital, True if you want to update the IO/D line. False to
+ False to just read their values.
+ PWMA, Voltage to set AO0 to output.
+ PWMB, Voltage to set AO1 to output.
+
+ Desc: This command controls all 20 digital I/O, and the 2 PWM outputs.
+ The response provides the state of all I/O and the current count.
+ See Section 5.4 of the User's Guide.
+
+ By default, sets the AOs to 0 and reads the states and counters.
+
+ Returns: A dictionary with the following keys:
+ D15toD8States, a BitField representing the states of D15-D8.
+ D7toD0States, a BitField representing the states of D7-D0.
+ IO3toIO0States, a BitField representing the states of IO3-IO0.
+ Counter, the value of the counter
+
+ Example:
+ >>> import u12
+ >>> d = u12.U12()
+ >>> d.rawCounterPWMDIO()
+ {
+ 'D15toD8States':
+ ,
+
+ 'D7toD0States':
+ ,
+
+ 'IO3toIO0States':
+ ,
+
+ 'Counter': 0
+ }
+ """
+ command = [ 0 ] * 8
+
+ # Bits for D15 through D8 Direction
+ command[0] = int(D15toD8Directions)
+
+ # Bits for D7 through D0 Direction ( 0 = Output, 1 = Input)
+ command[1] = int(D7toD0Directions)
+
+ # Bits for D15 through D8 State ( 0 = Low, 1 = High)
+ command[2] = int(D15toD8States)
+
+ # Bits for D7 through D0 State ( 0 = Low, 1 = High)
+ command[3] = int(D7toD0States)
+
+ # Bits 7-4: Bits for IO3 through IO0 Direction
+ # Bits 3-0: Bits for IO3 through IO0 State
+ command[4] = int(IO3toIO0DirectionsAndStates)
+
+ bf = BitField()
+ bf.bit5 = int(ResetCounter)
+ bf.bit4 = int(UpdateDigital)
+
+ binPWMA = int((1023 * (float(PWMA)/5.0)))
+ binPWMB = int((1023 * (float(PWMB)/5.0)))
+
+ bf2 = BitField()
+ bf2.fromByte( binPWMA & 3 ) # 3 = 0b11
+ bf.bit3 = bf2.bit1
+ bf.bit2 = bf2.bit0
+
+ bf2.fromByte( binPWMB & 3 ) # 3 = 0b11
+ bf.bit1 = bf2.bit1
+ bf.bit0 = bf2.bit0
+
+ command[5] = int(bf)
+
+ command[6] = (binPWMA >> 2) & 0xff
+ command[7] = (binPWMB >> 2) & 0xff
+
+ self.write(command)
+ results = self.read()
+
+ returnDict = {}
+
+ returnDict['D15toD8States'] = BitField(results[1], "D", list(range(15, 7, -1)), "Low", "High")
+ returnDict['D7toD0States'] = BitField(results[2], "D", list(range(7, -1, -1)), "Low", "High")
+ returnDict['IO3toIO0States'] = BitField((results[3] >> 4), "IO", list(range(3, -1, -1)), "Low", "High")
+
+ counter = results[7]
+ counter += results[6] << 8
+ counter += results[5] << 16
+ counter += results[4] << 24
+ returnDict['Counter'] = counter
+
+ return returnDict
+
+ def rawAIBurst(self, channel0PGAMUX = 8, channel1PGAMUX = 9, channel2PGAMUX = 10, channel3PGAMUX = 11, NumberOfScans = 8, TriggerIONum = 0, TriggerState = 0, UpdateIO = False, LEDState = True, IO3ToIO0States = 0, FeatureReports = False, TriggerOn = False, SampleInterval = 15000):
+ """
+ Name: U12.rawAIBurst( channel0PGAMUX = 8, channel1PGAMUX = 9,
+ channel2PGAMUX = 10, channel3PGAMUX = 11,
+ NumberOfScans = 8, TriggerIONum = 0,
+ TriggerState = 0, UpdateIO = False,
+ LEDState = True, IO3ToIO0States = 0,
+ FeatureReports = False, TriggerOn = False,
+ SampleInterval = 15000 )
+
+ Args: channel0PGAMUX, A byte that contains channel0 information
+ channel1PGAMUX, A byte that contains channel1 information
+ channel2PGAMUX, A byte that contains channel2 information
+ channel3PGAMUX, A byte that contains channel3 information
+ NumberOfScans, The number of scans you wish to take. Rounded up
+ to a power of 2.
+ TriggerIONum, IO to trigger burst on.
+ TriggerState, State to trigger on.
+ UpdateIO, True if you want to update the IO/D line. False to
+ False to just read their values.
+ LEDState, Turns the status LED on or off.
+ IO3ToIO0States, 4 bits for IO3-0 states
+ FeatureReports, Use feature reports, or not.
+ TriggerOn, Use trigger to start acquisition.
+ SampleInterval, = int(6000000.0/(ScanRate * NumberOfChannels))
+ must be greater than (or equal to) 733.
+
+ Desc: After receiving a AIBurst command, the LabJack collects 4
+ channels at the specified data rate, and puts data in the buffer.
+ This continues until the buffer is full, at which time the
+ LabJack starts sending the data to the host. Data is sent to the
+ host 1 scan at a time while checking for a command from the host.
+ If a command is received the burst operation is canceled and the
+ command is executed normally. If the LED is enabled, it blinks at
+ 4 Hz while waiting for a trigger, is off during acquisition,
+ blinks at about 8 Hz during data delivery, and is set on when
+ done or stopped. See Section 5.5 of the User's Guide.
+
+ This function sends the AIBurst command, then reads all the
+ responses. Separating the write and read is not currently
+ supported (like in the UW driver).
+
+ By default, it does single-ended readings on AI0-4 at 100Hz for 8
+ scans.
+
+ Returns: A dictionary with the following keys:
+ Channel0-3, A list of the readings on the channels
+ PGAOvervoltages, A list of the over-voltage flags
+ IO3toIO0State, A list of the IO states
+ IterationCounters, A list of the values of the iteration counter
+ Backlogs, value*256 = number of packets in the backlog.
+ BufferOverflowOrChecksumErrors, If True and Backlog = 31,
+ then a buffer overflow occurred. If
+ True and Backlog = 0, then Checksum
+ error occurred.
+
+ Example:
+ >>> import u12
+ >>> d = u12.U12()
+ >>> d.rawAIBurst()
+ {
+ 'Channel0': [1.484375, 1.513671875, ... , 1.46484375],
+
+ 'Channel1': [1.455078125, 1.455078125, ... , 1.455078125],
+
+ 'Channel2': [1.46484375, 1.474609375, ... , 1.46484375],
+
+ 'Channel3': [1.435546875, 1.42578125, ... , 1.435546875],
+
+ 'PGAOvervoltages': [False, False, ..., False],
+
+ 'IO3toIO0States':
+ [, ... ],
+
+ 'IterationCounters': [0, 1, 2, 3, 4, 5, 6, 0],
+
+ 'Backlogs': [0, 0, 0, 0, 0, 0, 0, 0],
+
+ 'BufferOverflowOrChecksumErrors': [False, False, ... , False]
+ }
+
+
+ """
+ command = [ 0 ] * 8
+
+ # Bits 6-4: PGA for 1st Channel
+ # Bits 3-0: MUX command for 1st Channel
+ command[0] = int(channel0PGAMUX)
+
+ tempNum = command[0] & 7 # 7 = 0b111
+ channel0Number = tempNum if (command[0] & 0xf) > 7 else tempNum+8
+ channel0Gain = (command[0] >> 4) & 7 # 7 = 0b111
+
+ command[1] = int(channel1PGAMUX)
+
+ tempNum = command[1] & 7 # 7 = 0b111
+ channel1Number = tempNum if (command[1] & 0xf) > 7 else tempNum+8
+ channel1Gain = (command[1] >> 4) & 7 # 7 = 0b111
+
+ command[2] = int(channel2PGAMUX)
+
+ tempNum = command[2] & 7 # 7 = 0b111
+ channel2Number = tempNum if (command[2] & 0xf) > 7 else tempNum+8
+ channel2Gain = (command[2] >> 4) & 7 # 7 = 0b111
+
+ command[3] = int(channel3PGAMUX)
+
+ tempNum = command[3] & 7 # 7 = 0b111
+ channel3Number = tempNum if (command[3] & 0xf) > 7 else tempNum+8
+ channel3Gain = (command[3] >> 4) & 7 # 7 = 0b111
+
+ if NumberOfScans > 1024 or NumberOfScans < 8:
+ raise U12Exception("The number of scans must be between 1024 and 8 (inclusive)")
+
+ NumScansExponentMod = 10 - int(math.ceil(math.log(NumberOfScans, 2)))
+ NumScans = 2 ** (10 - NumScansExponentMod)
+
+ bf = BitField( rawByte = (NumScansExponentMod << 5) )
+ # bits 4-3: IO to Trigger on
+ bf.bit2 = 0
+ bf.bit1 = int(bool(UpdateIO))
+ bf.bit0 = int(bool(LEDState))
+ command[4] = int(bf)
+
+ bf2 = BitField(rawByte = int(IO3ToIO0States))
+ #Bits 7-4: 1010 (Start Burst)
+ bf2.bit7 = 1
+ bf2.bit5 = 1
+ command[5] = int(bf2)
+
+ if SampleInterval < 733:
+ raise U12Exception("SampleInterval must be greater than 733.")
+
+ bf3 = BitField( rawByte = ((SampleInterval >> 8) & 0xf) )
+ bf3.bit7 = int(bool(FeatureReports))
+ bf3.bit6 = int(bool(TriggerOn))
+ command[6] = int(bf3)
+
+ command[7] = SampleInterval & 0xff
+
+ self.write(command)
+
+ resultsList = []
+ for i in range(NumScans):
+ resultsList.append(self.read())
+
+
+ returnDict = {}
+
+ returnDict['BufferOverflowOrChecksumErrors'] = list()
+ returnDict['PGAOvervoltages'] = list()
+ returnDict['IO3toIO0States'] = list()
+
+ returnDict['IterationCounters'] = list()
+ returnDict['Backlogs'] = list()
+
+ returnDict['Channel0'] = list()
+
+ returnDict['Channel1'] = list()
+
+ returnDict['Channel2'] = list()
+
+ returnDict['Channel3'] = list()
+
+ for results in resultsList:
+ bf = BitField(rawByte = results[0])
+
+ if bf.bit7 != 1 or bf.bit6 != 0:
+ raise U12Exception("Expected a AIBurst response, got %s instead." % results[0])
+
+ returnDict['BufferOverflowOrChecksumErrors'].append(bool(bf.bit5))
+ returnDict['PGAOvervoltages'].append(bool(bf.bit4))
+ returnDict['IO3toIO0States'].append(BitField(results[0], "IO", list(range(3, -1, -1)), "Low", "High"))
+
+ returnDict['IterationCounters'].append((results[1] >> 5))
+ returnDict['Backlogs'].append(results[1] & 0xf)
+
+ channel0 = (results[2] >> 4) & 0xf
+ channel1 = (results[2] & 0xf)
+ channel2 = (results[5] >> 4) & 0xf
+ channel3 = (results[5] & 0xf)
+
+ channel0 = (channel0 << 8) + results[3]
+ returnDict['Channel0'].append(self.bitsToVolts(channel0Number, channel0Gain, channel0))
+
+ channel1 = (channel1 << 8) + results[4]
+ returnDict['Channel1'].append(self.bitsToVolts(channel1Number, channel1Gain, channel1))
+
+ channel2 = (channel2 << 8) + results[6]
+ returnDict['Channel2'].append(self.bitsToVolts(channel2Number, channel2Gain, channel2))
+
+ channel3 = (channel3 << 8) + results[7]
+ returnDict['Channel3'].append(self.bitsToVolts(channel3Number, channel3Gain, channel3))
+
+ return returnDict
+
+
+
+ def rawAIContinuous(self, channel0PGAMUX = 8, channel1PGAMUX = 9, channel2PGAMUX = 10, channel3PGAMUX = 11, FeatureReports = False, CounterRead = False, UpdateIO = False, LEDState = True, IO3ToIO0States = 0, SampleInterval = 15000):
+ """
+ Currently in development.
+
+ The function is mostly implemented, but is currently too slow to be
+ useful.
+ """
+ command = [ 0 ] * 8
+
+ # Bits 6-4: PGA for 1st Channel
+ # Bits 3-0: MUX command for 1st Channel
+ command[0] = int(channel0PGAMUX)
+
+ tempNum = command[0] & 7 # 7 = 0b111
+ channel0Number = tempNum if (command[0] & 0xf) > 7 else tempNum+8
+ channel0Gain = (command[0] >> 4) & 7 # 7 = 0b111
+
+ command[1] = int(channel1PGAMUX)
+
+ tempNum = command[1] & 7 # 7 = 0b111
+ channel1Number = tempNum if (command[1] & 0xf) > 7 else tempNum+8
+ channel1Gain = (command[1] >> 4) & 7 # 7 = 0b111
+
+ command[2] = int(channel2PGAMUX)
+
+ tempNum = command[2] & 7 # 7 = 0b111
+ channel2Number = tempNum if (command[2] & 0xf) > 7 else tempNum+8
+ channel2Gain = (command[2] >> 4) & 7 # 7 = 0b111
+
+ command[3] = int(channel3PGAMUX)
+
+ tempNum = command[3] & 7 # 7 = 0b111
+ channel3Number = tempNum if (command[3] & 0xf) > 7 else tempNum+8
+ channel3Gain = (command[3] >> 4) & 7 # 7 = 0b111
+
+ bf = BitField()
+ bf.bit7 = int(bool(FeatureReports))
+ bf.bit6 = int(bool(CounterRead))
+ bf.bit1 = int(bool(UpdateIO))
+ bf.bit0 = int(bool(LEDState))
+
+ command[4] = int(bf)
+
+ # Bits 7-4: 1001 (Start Continuous)
+ bf2 = BitField( rawByte = int(IO3ToIO0States) )
+ bf2.bit7 = 1
+ bf2.bit4 = 1
+
+ command[5] = int(bf2)
+
+ command[6] = ( SampleInterval >> 8)
+ command[7] = SampleInterval & 0xff
+
+ byte0bf = BitField()
+ returnDict = dict()
+
+ self.write(command)
+ while True:
+ results = self.read()
+
+ byte0bf.fromByte(results[0])
+
+ returnDict['Byte0'] = byte0bf
+ returnDict['IterationCounter'] = (results[1] >> 5)
+ returnDict['Backlog'] = results[1] & 0xf
+
+
+ yield returnDict
+
+
+ def rawPulseout(self, B1 = 10, C1 = 2, B2 = 10, C2 = 2, D7ToD0PulseSelection = 1, ClearFirst = False, NumberOfPulses = 5):
+ """
+ Name: U12.rawPulseout( B1 = 10, C1 = 2, B2 = 10, C2 = 2,
+ D7ToD0PulseSelection = 1, ClearFirst = False,
+ NumberOfPulses = 5)
+
+ Args: B1, the B component of the first half cycle
+ C1, the C component of the first half cycle
+ B2, the B component of the second half cycle
+ C2, the C component of the second half cycle
+ D7ToD0PulseSelection, which D lines to pulse.
+ ClearFirst, True = Start Low.
+ NumberOfPulses, the number of pulses
+
+ Desc: This command creates pulses on any, or all, of D0-D7. The desired
+ D lines must be set to output with some other function. See
+ Section 5.7 of the User's Guide.
+
+ By default, pulses D0 5 times at 400us high, then 400 us low.
+
+ Returns: None
+
+ Example:
+ Have a jumper wire connected from D0 to CNT.
+
+ >>> import u12
+ >>> d = u12.U12()
+ >>> d.rawDIO(D7toD0Directions = 0, UpdateDigital = True)
+ >>> d.rawCounter(ResetCounter = True)
+ >>> d.rawPulseout(ClearFirst = True)
+ >>> print d.rawCounter()
+ { 'IO3toIO0States': ... ,
+ 'Counter': 5,
+ 'D7toD0States': ... ,
+ 'D15toD8States': ...
+ }
+ """
+ command = [ 0 ] * 8
+
+ command[0] = B1
+ command[1] = C1
+ command[2] = B2
+ command[3] = C2
+ command[4] = int(D7ToD0PulseSelection)
+
+ # 01100100 (Pulseout)
+ bf = BitField()
+ bf.bit6 = 1
+ bf.bit5 = 1
+ bf.bit2 = 1
+
+ command[5] = int(bf)
+
+ bf2 = BitField( rawByte = ( NumberOfPulses >> 8 ) )
+ bf2.bit7 = int(bool(ClearFirst))
+
+ command[6] = int(bf2)
+ command[7] = NumberOfPulses & 0xff
+
+ self.write(command)
+ results = self.read()
+
+ if command[5] != results[5]:
+ raise U12Exception("Expected Pulseout response, got %s instead." % results[5])
+
+ if results[4] != 0:
+ errors = BitField(rawByte = command[4], labelPrefix = "D", zeroLabel = "Ok", oneLabel = "Error")
+ raise U12Exception("D7-D0 Direction error detected: %s" % errors)
+
+ return None
+
+
+ def rawReset(self):
+ """
+ Name: U12.rawReset()
+
+ Desc: Sits in an infinite loop until micro watchdog timeout after about
+ 2 seconds. See Section 5.8 of the User's Guide.
+
+ Note: The function will close the device after it has written the
+ command.
+
+ Returns: None
+
+ Example:
+ >>> import u12
+ >>> d = u12.U12()
+ >>> d.rawReset()
+ """
+ command = [ 0 ] * 8
+
+ # 0b01011111 ( Reset )
+ bf = BitField()
+ bf.bit6 = 1
+ bf.bit4 = 1
+ bf.bit3 = 1
+ bf.bit2 = 1
+ bf.bit1 = 1
+ bf.bit0 = 1
+
+ command[5] = int(bf)
+ self.write(command)
+ self.close()
+
+ def rawReenumerate(self):
+ """
+ Name: U12.rawReenumerate()
+
+ Desc: Detaches from the USB, reloads config parameters, and then
+ reattaches so the device can be re-enumerated. See Section 5.9 of
+ the User's Guide.
+
+ Note: The function will close the device after it has written the
+ command.
+
+ Returns: None
+
+ Example:
+ >>> import u12
+ >>> d = u12.U12()
+ >>> d.rawReenumerate()
+ """
+ command = [ 0 ] * 8
+
+ # 0b01000000 (Re-Enumerate)
+ bf = BitField()
+ bf.bit6 = 1
+ command[5] = int(bf)
+ self.write(command)
+ self.close()
+
+ def rawWatchdog(self, IgnoreCommands = False, D0Active = False, D0State = False, D1Active = False, D1State = False, D8Active = False, D8State = False, ResetOnTimeout = False, WatchdogActive = False, Timeout = 60):
+ """
+ Name: U12.rawWatchdog( IgnoreCommands = False, D0Active = False,
+ D0State = False, D1Active = False,
+ D1State = False, D8Active = False,
+ D8State = False, ResetOnTimeout = False,
+ WatchdogActive = False, Timeout = 60)
+
+ Desc: Sets the settings for the watchdog, or just reads the firmware
+ version of the U12. See section 5.10 of the User's Guide.
+
+ By defaults, just reads the firmware version.
+
+ Returns: A dictionary with the following keys:
+ FirmwareVersion, the firmware version of the U12.
+
+ Example:
+ >>> import u12
+ >>> d = u12.U12()
+ >>> print d.rawWatchdog()
+ {'FirmwareVersion': '1.10'}
+ """
+ command = [ 0 ] * 8
+
+ command[0] = int(bool(IgnoreCommands))
+
+ bf = BitField()
+ bf.bit7 = int(D0Active)
+ bf.bit6 = int(D0State)
+ bf.bit5 = int(D1Active)
+ bf.bit4 = int(D1State)
+ bf.bit3 = int(D8Active)
+ bf.bit2 = int(D8State)
+ bf.bit1 = int(ResetOnTimeout)
+ bf.bit0 = int(WatchdogActive)
+
+ command[4] = int(bf)
+
+ # 01X1X011 (Watchdog)
+ bf2 = BitField()
+ bf2.bit6 = 1
+ bf2.bit4 = 1
+ bf2.bit1 = 1
+ bf2.bit0 = 1
+ command[5] = int(bf2)
+
+ # Timeout is increments of 2^16 cycles.
+ # 2^16 cycles is about 0.01 seconds.
+ binTimeout = int((float(Timeout) / 0.01))
+ command[6] = ( binTimeout >> 8 ) & 0xff
+ command[7] = binTimeout & 0xff
+
+ self.write(command)
+ results = self.read()
+
+ returnDict = dict()
+
+ returnDict['FirmwareVersion'] = "%s.%.2d" % (results[0], results[1])
+
+ return returnDict
+
+ def rawReadRAM(self, Address = 0):
+ """
+ Name: U12.rawReadRAM(Address = 0)
+
+ Args: Address, the starting address to read from
+
+ Desc: Reads 4 bytes out of the U12's internal memory. See section 5.11
+ of the User's Guide.
+
+ By default, reads the bytes that make up the serial number.
+
+ Returns: A dictionary with the following keys:
+ DataByte0, the data byte at Address - 0
+ DataByte1, the data byte at Address - 1
+ DataByte2, the data byte at Address - 2
+ DataByte3, the data byte at Address - 3
+
+ Example:
+ >>> import u12, struct
+ >>> d = u12.U12()
+ >>> r = d.rawReadRAM()
+ >>> print r
+ {'DataByte3': 5, 'DataByte2': 246, 'DataByte1': 139, 'DataByte0': 170}
+ >>> bytes = [ r['DataByte3'], r['DataByte2'], r['DataByte1'], r['DataByte0'] ]
+ >>> print struct.unpack(">I", struct.pack("BBBB", *bytes))[0]
+ 100043690
+ """
+ command = [ 0 ] * 8
+
+ # 01010000 (Read RAM)
+ bf = BitField()
+ bf.bit6 = 1
+ bf.bit4 = 1
+ command[5] = int(bf)
+
+ command[6] = (Address >> 8) & 0xff
+ command[7] = Address & 0xff
+
+ self.write(command)
+ results = self.read()
+
+ if results[0] != int(bf):
+ raise U12Exception("Expected ReadRAM response, got %s" % results[0])
+
+ if (results[6] != command[6]) or (results[7] != command[7]):
+ receivedAddress = (results[6] << 8) + results[7]
+ raise U12Exception("Wanted address %s got address %s" % (Address, receivedAddress))
+
+ returnDict = dict()
+
+ returnDict['DataByte3'] = results[1]
+ returnDict['DataByte2'] = results[2]
+ returnDict['DataByte1'] = results[3]
+ returnDict['DataByte0'] = results[4]
+
+ return returnDict
+
+ def rawWriteRAM(self, Data, Address):
+ """
+ Name: U12.rawWriteRAM(Data, Address)
+
+ Args: Data, a list of 4 bytes to write to memory.
+ Address, the starting address to write to.
+
+ Desc: Writes 4 bytes to the U12's internal memory. See section 5.13 of
+ the User's Guide.
+
+ No default behavior, you must pass Data and Address.
+
+ Returns: A dictionary with the following keys:
+ DataByte0, the data byte at Address - 0
+ DataByte1, the data byte at Address - 1
+ DataByte2, the data byte at Address - 2
+ DataByte3, the data byte at Address - 3
+
+ Example:
+ >>> import u12
+ >>> d = u12.U12()
+ >>> print d.rawWriteRAM([1, 2, 3, 4], 0x200)
+ {'DataByte3': 4, 'DataByte2': 3, 'DataByte1': 2, 'DataByte0': 1}
+ """
+ command = [ 0 ] * 8
+
+ if not isinstance(Data, list) or len(Data) > 4:
+ raise U12Exception("Data wasn't a list, or was too long.")
+
+ Data.reverse()
+
+ command[:len(Data)] = Data
+
+ # 01010001 (Write RAM)
+ bf = BitField()
+ bf.bit6 = 1
+ bf.bit4 = 1
+ bf.bit0 = 1
+ command[5] = int(bf)
+
+ command[6] = (Address >> 8) & 0xff
+ command[7] = Address & 0xff
+
+ self.write(command)
+ results = self.read()
+
+ if results[0] != int(bf):
+ raise U12Exception("Expected ReadRAM response, got %s" % results[0])
+
+ if (results[6] != command[6]) or (results[7] != command[7]):
+ receivedAddress = (results[6] << 8) + results[7]
+ raise U12Exception("Wanted address %s got address %s" % (Address, receivedAddress))
+
+ returnDict = dict()
+
+ returnDict['DataByte3'] = results[1]
+ returnDict['DataByte2'] = results[2]
+ returnDict['DataByte1'] = results[3]
+ returnDict['DataByte0'] = results[4]
+
+ return returnDict
+
+ def rawAsynch(self, Data, AddDelay = False, TimeoutActive = False, SetTransmitEnable = False, PortB = False, NumberOfBytesToWrite = 0, NumberOfBytesToRead = 0):
+ """
+ Name: U12.rawAsynch(Data, AddDelay = False, TimeoutActive = False,
+ SetTransmitEnable = False, PortB = False,
+ NumberOfBytesToWrite = 0, NumberOfBytesToRead = 0)
+
+ Args: Data, A list of bytes to write.
+ AddDelay, True to add a 1 bit delay between each transmit byte.
+ TimeoutActive, True to enable timeout for the receive phase.
+ SetTransmitEnable, True to set Transmit Enable to high during
+ transmit and low during receive.
+ PortB, True to use PortB instead of PortA.
+ NumberOfBytesToWrite, Number of bytes to write.
+ NumberOfBytesToRead, Number of bytes to read.
+
+ Desc: Requires firmware V1.1 or higher. This function writes and then
+ reads half-duplex asynchronous data on 1 of two pairs of D lines.
+ See section 5.13 of the User's Guide.
+
+ Returns: A dictionary with the following keys,
+ DataByte0-3, the first four data bytes read over the RX line
+ ErrorFlags, a BitField representing the error flags.
+
+ Example:
+ >>> import u12
+ >>> d = u12.U12()
+ >>> # Set the full and half A,B,C to 9600
+ >>> d.rawWriteRAM([0, 1, 1, 200], 0x073)
+ >>> d.rawWriteRAM([5, 1, 2, 48], 0x076)
+ >>> print d.rawAsynch([1, 2, 3, 4], NumberOfBytesToWrite = 4, NumberOfBytesToRead = 4)
+ {
+ 'DataByte3': 4,
+ 'DataByte2': 3,
+ 'DataByte1': 2,
+ 'DataByte0': 1,
+ 'ErrorFlags':
+ }
+
+ """
+ command = [ 0 ] * 8
+
+ if not isinstance(Data, list) or len(Data) > 4:
+ raise U12Exception("Data wasn't a list, or was too long.")
+
+ NumberOfBytesToWrite = NumberOfBytesToRead & 0xff
+ NumberOfBytesToRead = NumberOfBytesToRead & 0xff
+ if NumberOfBytesToWrite > 18:
+ raise U12Exception("Can only write 18 or fewer bytes at a time.")
+ if NumberOfBytesToRead > 18:
+ raise U12Exception("Can only read 18 or fewer bytes at a time.")
+
+ Data.reverse()
+
+ command[:len(Data)] = Data
+
+ bf = BitField()
+ bf.bit3 = int(bool(AddDelay))
+ bf.bit2 = int(bool(TimeoutActive))
+ bf.bit1 = int(bool(SetTransmitEnable))
+ bf.bit0 = int(bool(PortB))
+
+ command[4] = int(bf)
+
+ #01100001 (Asynch)
+ bf2 = BitField()
+ bf2.bit6 = 1
+ bf2.bit5 = 1
+ bf2.bit0 = 1
+
+ command[5] = int(bf2)
+ command[6] = NumberOfBytesToWrite
+ command[7] = NumberOfBytesToRead
+
+ self.write(command)
+ results = self.read()
+
+ if command[5] != results[5]:
+ raise U12Exception("Expected Asynch response, got %s instead." % results[5])
+
+ returnDict = dict()
+ returnDict['DataByte3'] = results[0]
+ returnDict['DataByte2'] = results[1]
+ returnDict['DataByte1'] = results[2]
+ returnDict['DataByte0'] = results[3]
+
+ bfLabels = ["Timeout Error Flag", "STRT Error Flag", "FRM Error Flag", "RXTris Error Flag", "TETris Error Flag", "TXTris Error Flag"]
+ bf = BitField( rawByte = results[4], labelPrefix = "", labelList = bfLabels )
+
+ returnDict["ErrorFlags"] = bf
+
+ return returnDict
+
+ SPIModes = ['A', 'B', 'C', 'D']
+ def rawSPI(self, Data, AddMsDelay = False, AddHundredUsDelay = False, SPIMode = 'A', NumberOfBytesToWriteRead = 0, ControlCS = False, StateOfActiveCS = False, CSLineNumber = 0):
+ """
+ Name: U12.rawSPI( Data, AddMsDelay = False, AddHundredUsDelay = False,
+ SPIMode = 'A', NumberOfBytesToWriteRead = 0,
+ ControlCS = False, StateOfActiveCS = False,
+ CSLineNumber = 0)
+
+ Args: Data, A list of four bytes to write using SPI
+ AddMsDelay, If True, a 1 ms delay is added between each bit
+ AddHundredUsDelay, if True, 100us delay is added
+ SPIMode, 'A', 'B', 'C', or 'D'
+ NumberOfBytesToWriteRead, number of bytes to write and read.
+ ControlCS, D0-D7 is automatically controlled as CS. The state and
+ direction of CS is only tested if control is enabled.
+ StateOfActiveCS, Active state for CS line.
+ CSLineNumber, D line to use as CS if enabled (0-7).
+
+ Desc: This function performs SPI communication. See Section 5.14 of the
+ User's Guide.
+
+ Returns: A dictionary with the following keys,
+ DataByte0-3, the first four data bytes read
+ ErrorFlags, a BitField representing the error flags.
+
+ Example:
+ >>> import u12
+ >>> d = u12.U12()
+ >>> d.rawSPI([1,2,3,4], NumberOfBytesToWriteRead = 4)
+ {
+ 'DataByte3': 4,
+ 'DataByte2': 3,
+ 'DataByte1': 2,
+ 'DataByte0': 1,
+ 'ErrorFlags':
+
+ }
+
+ """
+ command = [ 0 ] * 8
+
+ if not isinstance(Data, list) or len(Data) > 4:
+ raise U12Exception("Data wasn't a list, or was too long.")
+
+ NumberOfBytesToWriteRead = NumberOfBytesToWriteRead & 0xff
+
+ if NumberOfBytesToWriteRead == 0:
+ NumberOfBytesToWriteRead = len(Data)
+
+ if NumberOfBytesToWriteRead > 18 or NumberOfBytesToWriteRead < 1:
+ raise U12Exception("Can only read/write 1 to 18 bytes at a time.")
+
+ Data.reverse()
+ command[:len(Data)] = Data
+
+ bf = BitField()
+ bf.bit7 = int(bool(AddMsDelay))
+ bf.bit6 = int(bool(AddHundredUsDelay))
+
+ modeIndex = self.SPIModes.index(SPIMode)
+ bf[7-modeIndex] = 1
+
+ command[4] = int(bf)
+
+ # 01100010 (SPI)
+ bf2 = BitField()
+ bf2.bit6 = 1
+ bf2.bit5 = 1
+ bf2.bit1 = 1
+
+ command[5] = int(bf2)
+ command[6] = NumberOfBytesToWriteRead
+
+ bf3 = BitField(rawByte = CSLineNumber)
+ bf3.bit7 = int(bool(ControlCS))
+ bf3.bit6 = int(bool(StateOfActiveCS))
+
+ command[7] = int(bf3)
+
+ self.write(command)
+ results = self.read()
+
+ if results[5] != command[5]:
+ raise U12Exception("Expected SPI response, got %s instead." % results[5])
+
+ returnDict = dict()
+ returnDict['DataByte3'] = results[0]
+ returnDict['DataByte2'] = results[1]
+ returnDict['DataByte1'] = results[2]
+ returnDict['DataByte0'] = results[3]
+
+ bfLabels = ["CSStateTris Error Flag", "SCKTris Error Flag", "MISOTris Error Flag", "MOSITris Error Flag"]
+ bf = BitField( rawByte = results[4], labelPrefix = "", labelList = bfLabels )
+
+ returnDict["ErrorFlags"] = bf
+
+ return returnDict
+
+ def rawSHT1X(self, Data = [3,0,0,0], WaitForMeasurementReady = True, IssueSerialReset = False, Add1MsDelay = False, Add300UsDelay = False, IO3State = 1, IO2State = 1, IO3Direction = 1, IO2Direction = 1, NumberOfBytesToWrite = 1, NumberOfBytesToRead = 3):
+ """
+ Name: U12.rawSHT1X( Data = [3, 0, 0, 0],
+ WaitForMeasurementReady = True,
+ IssueSerialReset = False, Add1MsDelay = False,
+ Add300UsDelay = False, IO3State = 1, IO2State = 1,
+ IO3Direction = 1, IO2Direction = 1,
+ NumberOfBytesToWrite = 1, NumberOfBytesToRead = 3)
+
+ Args: Data, a list of bytes to write to the SHT.
+ WaitForMeasurementReady, Wait for the measurement ready signal.
+ IssueSerialReset, perform a serial reset
+ Add1MsDelay, adds 1ms delay
+ Add300UsDelay, adds a 300us delay
+ IO3State, sets the state of IO3
+ IO2State, sets the state of IO2
+ IO3Direction, sets the direction of IO3 ( 1 = Output )
+ IO2Direction, sets the direction of IO3 ( 1 = Output )
+ NumberOfBytesToWrite, how many bytes to write
+ NumberOfBytesToRead, how may bytes to read back
+
+ Desc: Sends and receives data from a SHT1X T/RH sensor from Sensirion.
+ See Section 5.15 of the User's Guide.
+
+ By default, reads the temperature from the SHT.
+
+ Returns: A dictionary with the following keys,
+ DataByte0-3, the four data bytes read
+ ErrorFlags, a BitField representing the error flags.
+
+ Example:
+ Uses an EI-1050 Temp/Humidity probe wired as follows:
+ Data ( Green ) -> IO0
+ Clock ( White ) -> IO1
+ Ground ( Black ) -> GND
+ Power ( Red ) -> +5V
+ Enable ( Brown ) -> IO2
+
+ >>> import u12
+ >>> d = u12.U12()
+ >>> results = d.rawSHT1X()
+ >>> print results
+ {
+ 'DataByte3': 0,
+ 'DataByte2': 69,
+ 'DataByte1': 48,
+ 'DataByte0': 25,
+ 'ErrorFlags':
+
+ }
+ >>> tempC = (results['DataByte0'] * 256 ) + results['DataByte1']
+ >>> tempC = (tempC * 0.01) - 40
+ >>> print tempC
+ 24.48
+ >>> results = d.rawSHT1X(Data = [5,0,0,0])
+ >>> print results
+ {
+ 'DataByte3': 0,
+ 'DataByte2': 200,
+ 'DataByte1': 90,
+ 'DataByte0': 2,
+ 'ErrorFlags':
+
+ }
+ >>> sorh = (results['DataByte0'] * 256 ) + results['DataByte1']
+ >>> rhlinear = (-0.0000028*sorh*sorh)+(0.0405*sorh)-4.0
+ >>> rh = ((tempC-25.0)*(0.01+(0.00008*sorh)))+rhlinear
+ >>> print rh
+ 19.3360256
+ """
+ command = [ 0 ] * 8
+
+ if NumberOfBytesToWrite != 0:
+ if not isinstance(Data, list) or len(Data) > 4:
+ raise U12Exception("Data wasn't a list, or was too long.")
+
+ Data.reverse()
+ command[:len(Data)] = Data
+
+ if max(NumberOfBytesToWrite, NumberOfBytesToRead) > 4:
+ raise U12Exception("Can only read/write up to 4 bytes at a time.")
+
+ bf = BitField()
+ bf.bit7 = int(bool(WaitForMeasurementReady))
+ bf.bit6 = int(bool(IssueSerialReset))
+ bf.bit5 = int(bool(Add1MsDelay))
+ bf.bit4 = int(bool(Add300UsDelay))
+ bf.bit3 = int(bool(IO3State))
+ bf.bit2 = int(bool(IO2State))
+ bf.bit1 = int(bool(IO3Direction))
+ bf.bit0 = int(bool(IO2Direction))
+
+ command[4] = int(bf)
+
+ # 01101000 (SHT1X)
+ bf2 = BitField()
+ bf2.bit6 = 1
+ bf2.bit5 = 1
+ bf2.bit3 = 1
+ command[5] = int(bf2)
+
+ command[6] = NumberOfBytesToWrite
+ command[7] = NumberOfBytesToRead
+
+ self.write(command)
+ results = self.read()
+
+ if results[5] != command[5]:
+ raise U12Exception("Expected SHT1x response, got %s instead." % results[5])
+
+ returnDict = dict()
+ returnDict['DataByte3'] = results[0]
+ returnDict['DataByte2'] = results[1]
+ returnDict['DataByte1'] = results[2]
+ returnDict['DataByte0'] = results[3]
+
+ bfLabels = ["Serial Reset Error Flag", "Measurement Ready Error Flag", "Ack Error Flag"]
+ bf = BitField( rawByte = results[4], labelPrefix = "", labelList = bfLabels )
+
+ returnDict["ErrorFlags"] = bf
+
+ return returnDict
+
+ def eAnalogIn(self, channel, idNum = None, demo=0, gain=0):
+ """
+ Name: U12.eAnalogIn(channel, idNum = None, demo=0, gain=0)
+ Args: See section 4.1 of the User's Guide
+ Desc: This is a simplified version of AISample. Reads the voltage from 1 analog input
+
+ >>> import u12
+ >>> d = u12.U12()
+ >>> d.eAnalogIn(0)
+ {'overVoltage': 0, 'idnum': 1, 'voltage': 1.435546875}
+ """
+ if idNum is None:
+ idNum = self.id
+
+ if ON_WINDOWS:
+ ljid = ctypes.c_long(idNum)
+ ad0 = ctypes.c_long(999)
+ ad1 = ctypes.c_float(999)
+
+ ecode = staticLib.EAnalogIn(ctypes.byref(ljid), demo, channel, gain, ctypes.byref(ad0), ctypes.byref(ad1))
+
+ if ecode != 0: raise U12Exception(ecode)
+
+ return {"idnum":ljid.value, "overVoltage":ad0.value, "voltage":ad1.value}
+ else:
+ # Bits 6-4: PGA for 1st Channel
+ # Bits 3-0: MUX command for 1st Channel
+ channel0PGAMUX = ( ( gain & 7 ) << 4)
+ channel0PGAMUX += channel-8 if channel > 7 else channel+8
+
+ results = self.rawAISample(channel0PGAMUX = channel0PGAMUX)
+
+ return {"idnum" : self.id, "overVoltage" : int(results['PGAOvervoltage']), 'voltage' : results['Channel0']}
+
+ def eAnalogOut(self, analogOut0, analogOut1, idNum = None, demo=0):
+ """
+ Name: U12.eAnalogOut(analogOut0, analogOut1, idNum = None, demo=0)
+ Args: See section 4.2 of the User's Guide
+ Desc: This is a simplified version of AOUpdate. Sets the voltage of both analog outputs.
+
+ >>> import u12
+ >>> d = u12.U12()
+ >>> d.eAnalogOut(2, 2)
+ {'idnum': 1}
+ """
+ if idNum is None:
+ idNum = self.id
+
+ if ON_WINDOWS:
+ ljid = ctypes.c_long(idNum)
+ ecode = staticLib.EAnalogOut(ctypes.byref(ljid), demo, ctypes.c_float(analogOut0), ctypes.c_float(analogOut1))
+
+ if ecode != 0: raise U12Exception(ecode)
+
+ return {"idnum":ljid.value}
+ else:
+ if analogOut0 < 0:
+ analogOut0 = self.pwmAVoltage
+
+ if analogOut1 < 0:
+ analogOut1 = self.pwmBVoltage
+
+ self.rawCounterPWMDIO(PWMA = analogOut0, PWMB = analogOut1)
+
+ self.pwmAVoltage = analogOut0
+ self.pwmBVoltage = analogOut1
+
+ return {"idnum": self.id}
+
+ def eCount(self, idNum = None, demo = 0, resetCounter = 0):
+ """
+ Name: U12.eCount(idNum = None, demo = 0, resetCounter = 0)
+ Args: See section 4.3 of the User's Guide
+ Desc: This is a simplified version of Counter. Reads & resets the counter (CNT).
+
+ >>> import u12
+ >>> d = u12.U12()
+ >>> d.eCount()
+ {'count': 1383596032.0, 'ms': 251487257.0}
+ """
+
+ # Check id num
+ if idNum is None:
+ idNum = self.id
+
+ if ON_WINDOWS:
+ ljid = ctypes.c_long(idNum)
+ count = ctypes.c_double()
+ ms = ctypes.c_double()
+
+ ecode = staticLib.ECount(ctypes.byref(ljid), demo, resetCounter, ctypes.byref(count), ctypes.byref(ms))
+
+ if ecode != 0: raise U12Exception(ecode)
+
+ return {"idnum":ljid.value, "count":count.value, "ms":ms.value}
+ else:
+ results = self.rawCounter( ResetCounter = resetCounter)
+
+ return {"idnum":self.id, "count":results['Counter'], "ms": (time() * 1000)}
+
+
+ def eDigitalIn(self, channel, idNum = None, demo = 0, readD=0):
+ """
+ Name: U12.eDigitalIn(channel, idNum = None, demo = 0, readD=0)
+ Args: See section 4.4 of the User's Guide
+ Desc: This is a simplified version of DigitalIO that reads the state of
+ one digital input. Also configures the requested pin to input and
+ leaves it that way.
+
+ >>> import u12
+ >>> d = u12.U12()
+ >>> d.eDigitalIn(0)
+ {'state': 0, 'idnum': 1}
+ """
+
+ # Check id num
+ if idNum is None:
+ idNum = self.id
+
+ if ON_WINDOWS:
+ ljid = ctypes.c_long(idNum)
+ state = ctypes.c_long(999)
+
+ ecode = staticLib.EDigitalIn(ctypes.byref(ljid), demo, channel, readD, ctypes.byref(state))
+
+ if ecode != 0: raise U12Exception(ecode)
+
+ return {"idnum":ljid.value, "state":state.value}
+ else:
+ oldstate = self.rawDIO()
+
+ if readD:
+ if channel > 7:
+ channel = channel-8
+ direction = BitField(rawByte = int(oldstate['D15toD8Directions']))
+ direction[7-channel] = 1
+
+ results = self.rawDIO(D15toD8Directions = direction, UpdateDigital = True)
+
+ state = results['D15toD8States'][7-channel]
+
+ else:
+ direction = BitField(rawByte = int(oldstate['D7toD0Directions']))
+ direction[7-channel] = 1
+ results = self.rawDIO(D7toD0Directions = direction, UpdateDigital = True)
+
+ state = results['D7toD0States'][7-channel]
+ else:
+ results = self.rawDIO(IO3toIO0DirectionsAndStates = 255, UpdateDigital = True)
+ state = results['IO3toIO0States'][3-channel]
+
+ return {"idnum" : self.id, "state" : state}
+
+ def eDigitalOut(self, channel, state, idNum = None, demo = 0, writeD=0):
+ """
+ Name: U12.eDigitalOut(channel, state, idNum = None, demo = 0, writeD=0)
+ Args: See section 4.5 of the User's Guide
+ Desc: This is a simplified version of DigitalIO that sets/clears the
+ state of one digital output. Also configures the requested pin to
+ output and leaves it that way.
+
+ >>> import u12
+ >>> d = u12.U12()
+ >>> d.eDigitalOut(0, 1)
+ {idnum': 1}
+ """
+
+ # Check id num
+ if idNum is None:
+ idNum = self.id
+
+ if ON_WINDOWS:
+ ljid = ctypes.c_long(idNum)
+
+ ecode = staticLib.EDigitalOut(ctypes.byref(ljid), demo, channel, writeD, state)
+
+ if ecode != 0: raise U12Exception(ecode)
+
+ return {"idnum":ljid.value}
+ else:
+ oldstate = self.rawDIO()
+
+ if writeD:
+ if channel > 7:
+ channel = channel-8
+ direction = BitField(rawByte = int(oldstate['D15toD8Directions']))
+ direction[7-channel] = 0
+
+ states = BitField(rawByte = int(oldstate['D15toD8States']))
+ states[7-channel] = state
+
+ self.rawDIO(D15toD8Directions = direction, D15toD8States = states, UpdateDigital = True)
+
+ else:
+ direction = BitField(rawByte = int(oldstate['D7toD0Directions']))
+ direction[7-channel] = 0
+
+ states = BitField(rawByte = int(oldstate['D7toD0States']))
+ states[7-channel] = state
+
+ self.rawDIO(D7toD0Directions = direction, D7toD0States = states, UpdateDigital = True)
+
+ else:
+ bf = BitField()
+ bf[7-(channel+4)] = 0
+ bf[7-channel] = state
+ self.rawDIO(IO3toIO0DirectionsAndStates = bf, UpdateDigital = True)
+
+ return {"idnum" : self.id}
+
+ def aiSample(self, numChannels, channels, idNum=None, demo=0, stateIOin=0, updateIO=0, ledOn=0, gains=[0, 0, 0, 0], disableCal=0):
+ """
+ Name: U12.aiSample(channels, idNum=None, demo=0, stateIOin=0, updateIO=0, ledOn=0, gains=[0, 0, 0, 0], disableCal=0)
+ Args: See section 4.6 of the User's Guide
+ Desc: Reads the voltages from 1,2, or 4 analog inputs. Also controls/reads the 4 IO ports.
+
+ >>> dev = U12()
+ >>> dev.aiSample(2, [0, 1])
+ {'stateIO': [0, 0, 0, 0], 'overVoltage': 0, 'idnum': 1, 'voltages': [1.4208984375, 1.4306640625]}
+ """
+
+ # Check id num
+ if idNum is None:
+ idNum = self.id
+ idNum = ctypes.c_long(idNum)
+
+ # Check to make sure that everything is checked
+ if not isIterable(channels): raise TypeError("channels must be iterable")
+ if not isIterable(gains): raise TypeError("gains must be iterable")
+ if len(channels) < numChannels: raise ValueError("channels must have atleast numChannels elements")
+ if len(gains) < numChannels: raise ValueError("gains must have atleast numChannels elements")
+
+ # Convert lists to arrays and create other ctypes
+ channelsArray = listToCArray(channels, ctypes.c_long)
+ gainsArray = listToCArray(gains, ctypes.c_long)
+ overVoltage = ctypes.c_long(999)
+ longArrayType = (ctypes.c_long * 4)
+ floatArrayType = (ctypes.c_float * 4)
+ voltages = floatArrayType(0, 0, 0, 0)
+ stateIOin = ctypes.c_long(stateIOin)
+
+ ecode = staticLib.AISample(ctypes.byref(idNum), demo, ctypes.byref(stateIOin), updateIO, ledOn, numChannels, ctypes.byref(channelsArray), ctypes.byref(gainsArray), disableCal, ctypes.byref(overVoltage), ctypes.byref(voltages))
+
+ if ecode != 0: raise U12Exception(ecode)
+
+ return {"idnum":idNum.value, "stateIO":stateIOin.value, "overVoltage":overVoltage.value, "voltages":voltages[0:numChannels]}
+
+ def aiBurst(self, numChannels, channels, scanRate, numScans, idNum=None, demo=0, stateIOin=0, updateIO=0, ledOn=0, gains=[0, 0, 0, 0], disableCal=0, triggerIO=0, triggerState=0, timeout=1, transferMode=0):
+ """
+ Name: U12.aiBurst(numChannels, channels, scanRate, numScans, idNum=None, demo=0, stateIOin=[0, 0, 0, 0], updateIO=0, ledOn=0, gains=[0, 0, 0, 0], disableCal=0, triggerIO=0, triggerState=0, timeout=1, transferMode=0)
+ Args: See section 4.7 of the User's Guide
+ Desc: Reads a specified number of scans (up to 4096) at a specified scan rate (up to 8192 Hz) from 1,2, or 4 analog inputs
+
+ >>> dev = U12()
+ >>> dev.aiBurst(1, [0], 400, 10)
+ {'overVoltage': 0, 'scanRate': 400.0, 'stateIOout': , 'idnum': 1, 'voltages': }
+ """
+
+ # Check id number
+ if idNum is None:
+ idNum = self.id
+ idNum = ctypes.c_long(idNum)
+
+ # check list sizes
+ if len(channels) < numChannels: raise ValueError("channels must have atleast numChannels elements")
+ if len(gains) < numChannels: raise ValueError("gains must have atleast numChannels elements")
+
+ # Convert lists to arrays and create other ctypes
+ channelsArray = listToCArray(channels, ctypes.c_long)
+ gainsArray = listToCArray(gains, ctypes.c_long)
+ scanRate = ctypes.c_float(scanRate)
+ pointerArray = (ctypes.c_void_p * 4)
+ arr4096_type = ctypes.c_float * 4096
+ voltages_type = arr4096_type * 4
+ voltages = voltages_type()
+ stateIOout = (ctypes.c_long * 4096)()
+ overVoltage = ctypes.c_long(999)
+
+ ecode = staticLib.AIBurst(ctypes.byref(idNum), demo, stateIOin, updateIO, ledOn, numChannels, ctypes.byref(channelsArray), ctypes.byref(gainsArray), ctypes.byref(scanRate), disableCal, triggerIO, triggerState, numScans, timeout, ctypes.byref(voltages), ctypes.byref(stateIOout), ctypes.byref(overVoltage), transferMode)
+
+ if ecode != 0: raise U12Exception(ecode)
+
+ return {"idnum":idNum.value, "scanRate":scanRate.value, "voltages":voltages, "stateIOout":stateIOout, "overVoltage":overVoltage.value}
+
+ def aiStreamStart(self, numChannels, channels, scanRate, idNum=None, demo=0, stateIOin=0, updateIO=0, ledOn=0, gains=[0, 0, 0, 0], disableCal=0, readCount=0):
+ """
+ Name: U12.aiStreamStart(numChannels, channels, scanRate, idNum=None, demo=0, stateIOin=0, updateIO=0, ledOn=0, gains=[0, 0, 0, 0], disableCal=0, readCount=0)
+ Args: See section 4.8 of the User's Guide
+ Desc: Starts a hardware timed continuous acquisition
+
+ >>> dev = U12()
+ >>> dev.aiStreamStart(1, [0], 200)
+ {'scanRate': 200.0, 'idnum': 1}
+ """
+
+ # Configure return type
+ staticLib.AIStreamStart.restype = ctypes.c_long
+
+ # check list sizes
+ if len(channels) < numChannels: raise ValueError("channels must have atleast numChannels elements")
+ if len(gains) < numChannels: raise ValueError("gains must have atleast numChannels elements")
+ #if len(stateIOin) < 4: raise ValueError("stateIOin must have atleast 4 elements")
+
+ # Check id number
+ if idNum is None:
+ idNum = self.id
+ idNum = ctypes.c_long(idNum)
+
+ # Convert lists to arrays and create other ctypes
+ channelsArray = listToCArray(channels, ctypes.c_long)
+ gainsArray = listToCArray(gains, ctypes.c_long)
+ scanRate = ctypes.c_float(scanRate)
+
+ ecode = staticLib.AIStreamStart(ctypes.byref(idNum), demo, stateIOin, updateIO, ledOn, numChannels, ctypes.byref(channelsArray), ctypes.byref(gainsArray), ctypes.byref(scanRate), disableCal, 0, readCount)
+
+ if ecode != 0: raise U12Exception(ecode) # TODO: Switch this out for exception
+
+ # The ID number must be saved for AIStream
+ self.id = idNum.value
+
+ self.streaming = True
+
+ return {"idnum":idNum.value, "scanRate":scanRate.value}
+
+ def aiStreamRead(self, numScans, localID=None, timeout=1):
+ """
+ Name: U12.aiStreamRead(numScans, localID=None, timeout=1)
+ Args: See section 4.9 of the User's Guide
+ Desc: Waits for a specified number of scans to be available and reads them.
+
+ >>> dev = U12()
+ >>> dev.aiStreamStart(1, [0], 200)
+ >>> dev.aiStreamRead(10)
+ {'overVoltage': 0, 'ljScanBacklog': 0, 'stateIOout': , 'reserved': 0, 'voltages': }
+ """
+
+ # Check to make sure that we are streaming
+ if not self.streaming:
+ raise U12Exception(-1, "Streaming has not started")
+
+ # Check id number
+ if localID is None:
+ localID = self.id
+
+ # Create arrays and other ctypes
+ arr4096_type = ctypes.c_float * 4096
+ voltages_type = arr4096_type * 4
+ voltages = voltages_type()
+ stateIOout = (ctypes.c_long * 4096)()
+ reserved = ctypes.c_long(0)
+ ljScanBacklog = ctypes.c_long(99999)
+ overVoltage = ctypes.c_long(999)
+
+ ecode = staticLib.AIStreamRead(localID, numScans, timeout, ctypes.byref(voltages), ctypes.byref(stateIOout), ctypes.byref(reserved), ctypes.byref(ljScanBacklog), ctypes.byref(overVoltage))
+
+ if ecode != 0: raise U12Exception(ecode) # TODO: Switch this out for exception
+
+ return {"voltages":voltages, "stateIOout":stateIOout, "reserved":reserved.value, "ljScanBacklog":ljScanBacklog.value, "overVoltage":overVoltage.value}
+
+ def aiStreamClear(self, localID=None):
+ """
+ Name: U12.aiClear()
+ Args: See section 4.10 of the User's Guide
+ Desc: This function stops the continuous acquisition. It should be called once when finished with the stream.
+
+ >>> dev = U12()
+ >>> dev.aiStreamStart(1, [0], 200)
+ >>> dev.aiStreamRead(10)
+ >>> dev.aiStreamClear()
+ """
+
+ # Check to make sure that we are streaming
+ if not self.streaming:
+ raise U12Exception(-1, "Streaming has not started")
+
+ # Check id number
+ if localID is None:
+ localID = self.id
+
+ ecode = staticLib.AIStreamClear(localID)
+
+ if ecode != 0: raise U12Exception(ecode) # TODO: Switch this out for exception
+
+ def aoUpdate(self, idNum=None, demo=0, trisD=None, trisIO=None, stateD=None, stateIO=None, updateDigital=0, resetCounter=0, analogOut0=0, analogOut1=0):
+ """
+ Name: U12.aoUpdate()
+ Args: See section 4.11 of the User's Guide
+ Desc: Sets the voltages of the analog outputs. Also controls/reads all 20 digital I/O and the counter.
+
+ >>> dev = U12()
+ >>> dev.aoUpdate()
+ >>> {'count': 2, 'stateIO': 3, 'idnum': 1, 'stateD': 0}
+ """
+
+ # Check id number
+ if idNum is None:
+ idNum = self.id
+ idNum = ctypes.c_long(idNum)
+
+ # Check tris and state arguments
+ if updateDigital > 0:
+ if trisD is None: raise ValueError("keyword argument trisD must be set")
+ if trisIO is None: raise ValueError("keyword argument trisIO must be set")
+ if stateD is None: raise ValueError("keyword argument stateD must be set")
+ if stateIO is None: raise ValueError("keyword argument stateIO must be set")
+
+ # Create ctypes
+ if stateD is None: stateD = ctypes.c_long(0)
+ else: stateD = ctypes.c_long(stateD)
+ if stateIO is None: stateIO = ctypes.c_long(0)
+ else: stateIO = ctypes.c_long(stateIO)
+ count = ctypes.c_ushort(999)
+
+ # Create arrays and other ctypes
+ ecode = staticLib.AOUpdate(ctypes.byref(idNum), demo, trisD, trisIO, ctypes.byref(stateD), ctypes.byref(stateIO), updateDigital, resetCounter, ctypes.byref(count), ctypes.c_float(analogOut0), ctypes.c_float(analogOut1))
+
+ if ecode != 0: raise U12Exception(ecode) # TODO: Switch this out for exception
+
+ return {"idnum":idNum.value, "stateD":stateD.value, "stateIO":stateIO.value, "count":count.value}
+
+ def asynchConfig(self, fullA, fullB, fullC, halfA, halfB, halfC, idNum=None, demo=None, timeoutMult=1, configA=0, configB=0, configTE=0):
+ """
+ Name: U12.asynchConfig(fullA, fullB, fullC, halfA, halfB, halfC, idNum=None, demo=None, timeoutMult=1, configA=0, configB=0, configTE=0)
+ Args: See section 4.12 of the User's Guide
+ Desc: Requires firmware V1.1 or higher. This function writes to the asynch registers and sets the direction of the D lines (input/output) as needed.
+
+ >>> dev = U12()
+ >>> dev.asynchConfig(96,1,1,22,2,1)
+ >>> {'idNum': 1}
+ """
+
+ #Check id number
+ if idNum is None:
+ idNum = self.id
+ idNum = ctypes.c_long(idNum)
+
+ ecode = staticLib.AsynchConfig(ctypes.byref(idNum), demo, timeoutMult, configA, configB, configTE, fullA, fullB, fullC, halfA, halfB, halfC)
+
+ if ecode != 0: raise U12Exception(ecode) # TODO: Switch this out for exception
+
+ return {"idNum":idNum.value}
+
+ def asynch(self, baudrate, data, idNum=None, demo=0, portB=0, enableTE=0, enableTO=0, enableDel=0, numWrite=0, numRead=0):
+ """
+ Name: U12.asynchConfig(fullA, fullB, fullC, halfA, halfB, halfC, idNum=None, demo=None, timeoutMult=1, configA=0, configB=0, configTE=0)
+ Args: See section 4.13 of the User's Guide
+ Desc: Requires firmware V1.1 or higher. This function writes to the asynch registers and sets the direction of the D lines (input/output) as needed.
+
+ >>> dev = U12()
+ >>> dev.asynch(96,1,1,22,2,1)
+ >>> dev.asynch(19200, [0, 0])
+ >>> {'data': , 'idnum': }
+ """
+
+ #Check id number
+ if idNum is None:
+ idNum = self.id
+ idNum = ctypes.c_long(idNum)
+
+ # Check size of data
+ if len(data) > 18: raise ValueError("data can not be larger than 18 elements")
+
+ # Make data 18 elements large
+ dataArray = [0] * 18
+ for i in range(0, len(data)):
+ dataArray[i] = data[i]
+ print(dataArray)
+ dataArray = listToCArray(dataArray, ctypes.c_long)
+
+ ecode = staticLib.Asynch(ctypes.byref(idNum), demo, portB, enableTE, enableTO, enableDel, baudrate, numWrite, numRead, ctypes.byref(dataArray))
+
+ if ecode != 0: raise U12Exception(ecode) # TODO: Switch this out for exception
+
+ return {"idnum":int, "data":dataArray}
+
+ GainMapping = [ 1.0, 2.0, 4.0, 5.0, 8.0, 10.0, 16.0, 20.0 ]
+ def bitsToVolts(self, chnum, chgain, bits):
+ """
+ Name: U12.bitsToVolts(chnum, chgain, bits)
+ Args: See section 4.14 of the User's Guide
+ Desc: Converts a 12-bit (0-4095) binary value into a LabJack voltage. No hardware communication is involved.
+
+ >>> dev = U12()
+ >>> dev.bitsToVolts(0, 0, 2662)
+ >>> {'volts': 2.998046875}
+ """
+ if ON_WINDOWS:
+ volts = ctypes.c_float()
+ ecode = staticLib.BitsToVolts(chnum, chgain, bits, ctypes.byref(volts))
+
+ if ecode != 0: print(ecode)
+
+ return volts.value
+ else:
+ if chnum < 8:
+ return ( float(bits) * 20.0 / 4096.0 ) - 10.0
+ else:
+ volts = ( float(bits) * 40.0 / 4096.0 ) - 20.0
+ return volts / self.GainMapping[chgain]
+
+ def voltsToBits(self, chnum, chgain, volts):
+ """
+ Name: U12.voltsToBits(chnum, chgain, bits)
+ Args: See section 4.15 of the User's Guide
+ Desc: Converts a voltage to it's 12-bit (0-4095) binary representation. No hardware communication is involved.
+
+ >>> dev = U12()
+ >>> dev.voltsToBits(0, 0, 3)
+ >>> {'bits': 2662}
+ """
+ if ON_WINDOWS:
+ bits = ctypes.c_long(999)
+ ecode = staticLib.VoltsToBits(chnum, chgain, ctypes.c_float(volts), ctypes.byref(bits))
+
+ if ecode != 0: raise U12Exception(ecode)
+
+ return bits.value
+ else:
+ pass
+ #*bits = RoundFL((volts+10.0F)/(20.0F/4096.0F));
+
+ def counter(self, idNum=None, demo=0, resetCounter=0, enableSTB=1):
+ """
+ Name: U12.counter(idNum=None, demo=0, resetCounter=0, enableSTB=1)
+ Args: See section 4.15 of the User's Guide
+ Desc: Converts a voltage to it's 12-bit (0-4095) binary representation. No hardware communication is involved.
+
+ >>> dev = U12()
+ >>> dev.counter(0, 0, 3)
+ >>> {'bits': 2662}
+ """
+
+ #Check id number
+ if idNum is None:
+ idNum = self.id
+ idNum = ctypes.c_long(idNum)
+
+ # Create ctypes
+ stateD = ctypes.c_long(999)
+ stateIO = ctypes.c_long(999)
+ count = ctypes.c_ulong(999)
+
+ print(idNum)
+ ecode = staticLib.Counter(ctypes.byref(idNum), demo, ctypes.byref(stateD), ctypes.byref(stateIO), resetCounter, enableSTB, ctypes.byref(count))
+
+ if ecode != 0: raise U12Exception(ecode)
+
+ return {"idnum":idNum.value, "stateD": stateD.value, "stateIO":stateIO.value, "count":count.value}
+
+ def digitalIO(self, idNum=None, demo=0, trisD=None, trisIO=None, stateD=None, stateIO=None, updateDigital=0):
+ """
+ Name: U12.digitalIO(idNum=None, demo=0, trisD=None, trisIO=None, stateD=None, stateIO=None, updateDigital=0)
+ Args: See section 4.17 of the User's Guide
+ Desc: Reads and writes to all 20 digital I/O.
+
+ >>> dev = U12()
+ >>> dev.digitalIO()
+ >>> {'stateIO': 0, 'stateD': 0, 'idnum': 1, 'outputD': 0, 'trisD': 0}
+ """
+
+ #Check id number
+ if idNum is None:
+ idNum = self.id
+ idNum = ctypes.c_long(idNum)
+
+ # Check tris and state parameters
+ if updateDigital > 0:
+ if trisD is None: raise ValueError("keyword argument trisD must be set")
+ if trisIO is None: raise ValueError("keyword argument trisIO must be set")
+ if stateD is None: raise ValueError("keyword argument stateD must be set")
+ if stateIO is None: raise ValueError("keyword argument stateIO must be set")
+
+ # Create ctypes
+ if trisD is None: trisD = ctypes.c_long(999)
+ else:trisD = ctypes.c_long(trisD)
+ if stateD is None:stateD = ctypes.c_long(999)
+ else: stateD = ctypes.c_long(stateD)
+ if stateIO is None: stateIO = ctypes.c_long(0)
+ else: stateIO = ctypes.c_long(stateIO)
+ outputD = ctypes.c_long(999)
+
+ # Check trisIO
+ if trisIO is None: trisIO = 0
+
+ ecode = staticLib.DigitalIO(ctypes.byref(idNum), demo, ctypes.byref(trisD), trisIO, ctypes.byref(stateD), ctypes.byref(stateIO), updateDigital, ctypes.byref(outputD))
+ if ecode != 0: raise U12Exception(ecode)
+
+ return {"idnum":idNum.value, "trisD":trisD.value, "stateD":stateD.value, "stateIO":stateIO.value, "outputD":outputD.value}
+
+ def getDriverVersion(self):
+ """
+ Name: U12.getDriverVersion()
+ Args: See section 4.18 of the User's Guide
+ Desc: Returns the version number of ljackuw.dll. No hardware communication is involved.
+
+ >>> dev = U12()
+ >>> dev.getDriverVersion()
+ >>> 1.21000003815
+ """
+ staticLib.GetDriverVersion.restype = ctypes.c_float
+ return staticLib.GetDriverVersion()
+
+ def getFirmwareVersion(self, idNum=None):
+ """
+ Name: U12.getErrorString(idnum=None)
+ Args: See section 4.20 of the User's Guide
+ Desc: Retrieves the firmware version from the LabJack's processor
+
+ >>> dev = U12()
+ >>> dev.getFirmwareVersion()
+ >>> Unkown error
+ """
+
+ # Check ID number
+ if idNum is None: idNum = self.id
+ idNum = ctypes.c_long(idNum)
+
+ staticLib.GetFirmwareVersion.restype = ctypes.c_float
+ firmware = staticLib.GetFirmwareVersion(ctypes.byref(idNum))
+
+ if firmware > 512: raise U12Exception(firmware-512)
+
+ return {"idnum" : idNum.value, "firmware" : firmware}
+
+ def getWinVersion(self):
+ """
+ Name: U12.getErrorString()
+ Args: See section 4.21 of the User's Guide
+ Desc: Uses a Windows API function to get the OS version
+
+ >>> dev = U12()
+ >>> dev.getWinVersion()
+ >>> {'majorVersion': 5, 'minorVersion': 1, 'platformID': 2, 'buildNumber': 2600, 'servicePackMajor': 2, 'servicePackMinor': 0}
+ """
+
+ # Create ctypes
+ majorVersion = ctypes.c_ulong()
+ minorVersion = ctypes.c_ulong()
+ buildNumber = ctypes.c_ulong()
+ platformID = ctypes.c_ulong()
+ servicePackMajor = ctypes.c_ulong()
+ servicePackMinor = ctypes.c_ulong()
+
+ ecode = staticLib.GetWinVersion(ctypes.byref(majorVersion), ctypes.byref(minorVersion), ctypes.byref(buildNumber), ctypes.byref(platformID), ctypes.byref(servicePackMajor), ctypes.byref(servicePackMinor))
+
+ if ecode != 0: raise U12Exception(ecode)
+
+ return {"majorVersion":majorVersion.value, "minorVersion":minorVersion.value, "buildNumber":buildNumber.value, "platformID":platformID.value, "servicePackMajor":servicePackMajor.value, "servicePackMinor":servicePackMinor.value}
+
+ def listAll(self):
+ """
+ Name: U12.listAll()
+ Args: See section 4.22 of the User's Guide
+ Desc: Searches the USB for all LabJacks, and returns the serial number and local ID for each
+
+ >>> dev = U12()
+ >>> dev.listAll()
+ >>> {'serialnumList': , 'numberFound': 1, 'localIDList': }
+ """
+
+ # Create arrays and ctypes
+ productIDList = listToCArray([0]*127, ctypes.c_long)
+ serialnumList = listToCArray([0]*127, ctypes.c_long)
+ localIDList = listToCArray([0]*127, ctypes.c_long)
+ powerList = listToCArray([0]*127, ctypes.c_long)
+ arr127_type = ctypes.c_long * 127
+ calMatrix_type = arr127_type * 20
+ calMatrix = calMatrix_type()
+ reserved = ctypes.c_long()
+ numberFound = ctypes.c_long()
+
+ ecode = staticLib.ListAll(ctypes.byref(productIDList), ctypes.byref(serialnumList), ctypes.byref(localIDList), ctypes.byref(powerList), ctypes.byref(calMatrix), ctypes.byref(numberFound), ctypes.byref(reserved), ctypes.byref(reserved))
+ if ecode != 0: raise U12Exception(ecode)
+
+ return {"serialnumList": serialnumList, "localIDList":localIDList, "numberFound":numberFound.value}
+
+ def localID(self, localID, idNum=None):
+ """
+ Name: U12.localID(localID, idNum=None)
+ Args: See section 4.23 of the User's Guide
+ Desc: Changes the local ID of a specified LabJack
+
+ >>> dev = U12()
+ >>> dev.localID(1)
+ >>> {'idnum':1}
+ """
+ #Check id number
+ if idNum is None:
+ idNum = self.id
+ idNum = ctypes.c_long(idNum)
+
+ ecode = staticLib.LocalID(ctypes.byref(idNum), localID)
+ if ecode != 0: raise U12Exception(ecode)
+
+ return {"idnum":idNum.value}
+
+ def noThread(self, noThread, idNum=None):
+ """
+ Name: U12.localID(noThread, idNum=None)
+ Args: See section 4.24 of the User's Guide
+ Desc: This function is needed when interfacing TestPoint to the LabJack DLL on Windows 98/ME
+
+ >>> dev = U12()
+ >>> dev.noThread(1)
+ >>> {'idnum':1}
+ """
+ #Check id number
+ if idNum is None:
+ idNum = self.id
+ idNum = ctypes.c_long(idNum)
+
+ ecode = staticLib.NoThread(ctypes.byref(idNum), noThread)
+ if ecode != 0: raise U12Exception(ecode)
+
+ return {"idnum":idNum.value}
+
+ def pulseOut(self, bitSelect, numPulses, timeB1, timeC1, timeB2, timeC2, idNum=None, demo=0, lowFirst=0):
+ """
+ Name: U12.pulseOut(bitSelect, numPulses, timeB1, timeC1, timeB2, timeC2, idNum=None, demo=0, lowFirst=0)
+ Args: See section 4.25 of the User's Guide
+ Desc: This command creates pulses on any/all of D0-D7
+
+ >>> dev = U12()
+ >>> dev.pulseOut(0, 1, 1, 1, 1, 1)
+ >>> {'idnum':1}
+ """
+
+ #Check id number
+ if idNum is None:
+ idNum = self.id
+ idNum = ctypes.c_long(idNum)
+
+ ecode = staticLib.PulseOut(ctypes.byref(idNum), demo, lowFirst, bitSelect, numPulses, timeB1, timeC1, timeB2, timeC2)
+ if ecode != 0: raise U12Exception(ecode)
+
+ return {"idnum":idNum.value}
+
+ def pulseOutStart(self, bitSelect, numPulses, timeB1, timeC1, timeB2, timeC2, idNum=None, demo=0, lowFirst=0):
+ """
+ Name: U12.pulseOutStart(bitSelect, numPulses, timeB1, timeC1, timeB2, timeC2, idNum=None, demo=0, lowFirst=0)
+ Args: See section 4.26 of the User's Guide
+ Desc: PulseOutStart and PulseOutFinish are used as an alternative to PulseOut (See PulseOut for more information)
+
+ >>> dev = U12()
+ >>> dev.pulseOutStart(0, 1, 1, 1, 1, 1)
+ >>> {'idnum':1}
+ """
+
+ #Check id number
+ if idNum is None:
+ idNum = self.id
+ idNum = ctypes.c_long(idNum)
+
+ ecode = staticLib.PulseOutStart(ctypes.byref(idNum), demo, lowFirst, bitSelect, numPulses, timeB1, timeC1, timeB2, timeC2)
+ if ecode != 0: raise U12Exception(ecode)
+
+ return {"idnum":idNum.value}
+
+ def pulseOutFinish(self, timeoutMS, idNum=None, demo=0):
+ """
+ Name: U12.pulseOutFinish(timeoutMS, idNum=None, demo=0)
+ Args: See section 4.27 of the User's Guide
+ Desc: See PulseOutStart for more information
+
+ >>> dev = U12()
+ >>> dev.pulseOutStart(0, 1, 1, 1, 1, 1)
+ >>> dev.pulseOutFinish(100)
+ >>> {'idnum':1}
+ """
+ #Check id number
+ if idNum is None:
+ idNum = self.id
+ idNum = ctypes.c_long(idNum)
+
+ ecode = staticLib.PulseOutFinish(ctypes.byref(idNum), demo, timeoutMS)
+ if ecode != 0: raise U12Exception(ecode)
+
+ return {"idnum":idNum.value}
+
+ def pulseOutCalc(self, frequency):
+ """
+ Name: U12.pulseOutFinish(frequency)
+ Args: See section 4.28 of the User's Guide
+ Desc: This function can be used to calculate the cycle times for PulseOut or PulseOutStart.
+
+ >>> dev = U12()
+ >>> dev.pulseOutCalc(100)
+ >>> {'frequency': 100.07672882080078, 'timeB': 247, 'timeC': 1}
+ """
+
+ # Create ctypes
+ frequency = ctypes.c_float(frequency)
+ timeB = ctypes.c_long(0)
+ timeC = ctypes.c_long(0)
+
+ ecode = staticLib.PulseOutCalc(ctypes.byref(frequency), ctypes.byref(timeB), ctypes.byref(timeC))
+ if ecode != 0: raise U12Exception(ecode)
+
+ return {"frequency":frequency.value, "timeB":timeB.value, "timeC":timeC.value}
+
+ def reEnum(self, idNum=None):
+ """
+ Name: U12.reEnum(idNum=None)
+ Args: See section 4.29 of the User's Guide
+ Desc: Causes the LabJack to electrically detach from and re-attach to the USB so it will re-enumerate
+
+ >>> dev = U12()
+ >>> dev.reEnum()
+ >>> {'idnum': 1}
+ """
+
+ #Check id number
+ if idNum is None:
+ idNum = self.id
+ idNum = ctypes.c_long(idNum)
+
+ ecode = staticLib.ReEnum(ctypes.byref(idNum))
+ if ecode != 0: raise U12Exception(ecode)
+
+ return {"idnum":idNum.value}
+
+ def reset(self, idNum=None):
+ """
+ Name: U12.reset(idNum=None)
+ Args: See section 4.30 of the User's Guide
+ Desc: Causes the LabJack to reset after about 2 seconds
+
+ >>> dev = U12()
+ >>> dev.reset()
+ >>> {'idnum': 1}
+ """
+
+ #Check id number
+ if idNum is None:
+ idNum = self.id
+ idNum = ctypes.c_long(idNum)
+
+ ecode = staticLib.Reset(ctypes.byref(idNum))
+ if ecode != 0: raise U12Exception(ecode)
+
+ return {"idnum":idNum.value}
+
+ def resetLJ(self, idNum=None):
+ """
+ Name: U12.resetLJ(idNum=None)
+ Args: See section 4.30 of the User's Guide
+ Desc: Causes the LabJack to reset after about 2 seconds
+
+ >>> dev = U12()
+ >>> dev.resetLJ()
+ >>> {'idnum': 1}
+ """
+ return reset(idNum)
+
+ def sht1X(self, idNum=None, demo=0, softComm=0, mode=0, statusReg=0):
+ """
+ Name: U12.sht1X(idNum=None, demo=0, softComm=0, mode=0, statusReg=0)
+ Args: See section 4.31 of the User's Guide
+ Desc: This function retrieves temperature and/or humidity readings from an SHT1X sensor.
+
+ >>> dev = U12()
+ >>> dev.sht1X()
+ >>> {'tempC': 24.69999885559082, 'rh': 39.724445343017578, 'idnum': 1, 'tempF': 76.459999084472656}
+ """
+ #Check id number
+ if idNum is None:
+ idNum = self.id
+ idNum = ctypes.c_long(idNum)
+
+ # Create ctypes
+ tempC = ctypes.c_float(0)
+ tempF = ctypes.c_float(0)
+ rh = ctypes.c_float(0)
+
+ ecode = staticLib.SHT1X(ctypes.byref(idNum), demo, softComm, mode, statusReg, ctypes.byref(tempC), ctypes.byref(tempF), ctypes.byref(rh))
+ if ecode != 0: raise U12Exception(ecode)
+
+ return {"idnum":idNum.value, "tempC":tempC.value, "tempF":tempF.value, "rh":rh.value}
+
+ def shtComm(self, numWrite, numRead, datatx, idNum=None, softComm=0, waitMeas=0, serialReset=0, dataRate=0):
+ """
+ Name: U12.shtComm(numWrite, numRead, datatx, idNum=None, softComm=0, waitMeas=0, serialReset=0, dataRate=0)
+ Args: See section 4.32 of the User's Guide
+ Desc: Low-level public function to send and receive up to 4 bytes to from an SHT1X sensor
+ """
+
+ #Check id number
+ if idNum is None:
+ idNum = self.id
+ idNum = ctypes.c_long(idNum)
+
+ # Check size of datatx
+ if len(datatx) != 4: raise ValueError("datatx must have exactly 4 elements")
+
+ # Create ctypes
+ datatx = listToCArray(datatx, ctypes.c_ubyte)
+ datarx = (ctypes.c_ubyte * 4)((0) * 4)
+
+ ecode = staticLib.SHTComm(ctypes.byref(idNum), softComm, waitMeas, serialReset, dataRate, numWrite, numRead, ctypes.byref(datatx), ctypes.byref(datarx))
+ if ecode != 0: raise U12Exception(ecode)
+
+ return {"idnum":idNum.value, "datarx":datarx}
+
+ def shtCRC(self, numWrite, numRead, datatx, datarx, statusReg=0):
+ """
+ Name: U12.shtCRC(numWrite, numRead, datatx, datarx, statusReg=0)
+ Args: See section 4.33 of the User's Guide
+ Desc: Checks the CRC on an SHT1X communication
+ """
+ # Create ctypes
+ datatx = listToCArray(datatx, ctypes.c_ubyte)
+ datarx = listToCArray(datarx, ctypes.c_ubyte)
+
+ return staticLib.SHTCRC(statusReg, numWrite, numRead, ctypes.byref(datatx), ctypes.byref(datarx))
+
+ def synch(self, mode, numWriteRead, data, idNum=None, demo=0, msDelay=0, husDelay=0, controlCS=0, csLine=None, csState=0, configD=0):
+ """
+ Name: U12.synch(mode, numWriteRead, data, idNum=None, demo=0, msDelay=0, husDelay=0, controlCS=0, csLine=None, csState=0, configD=0)
+ Args: See section 4.35 of the User's Guide
+ Desc: This function retrieves temperature and/or humidity readings from an SHT1X sensor.
+ """
+ #Check id number
+ if idNum is None:
+ idNum = self.id
+ idNum = ctypes.c_long(idNum)
+
+ if controlCS > 0 and csLine is None: raise ValueError("csLine must be specified")
+
+ # Make sure data is 18 elements
+ cData = [0] * 18
+ for i in range(0, len(data)):
+ cData[i] = data[i]
+ cData = listToCArray(cData, ctypes.c_long)
+
+ ecode = staticLib.Synch(ctypes.byref(idNum), demo, mode, msDelay, husDelay, controlCS, csLine, csState, configD, numWriteRead, ctypes.byref(cData))
+ if ecode != 0: raise U12Exception(ecode)
+
+ return {"idnum":idNum.value, "data":cData}
+
+ def watchdog(self, active, timeout, activeDn, stateDn, idNum=None, demo=0, reset=0):
+ """
+ Name: U12.watchdog(active, timeout, activeDn, stateDn, idNum=None, demo=0, reset=0)
+ Args: See section 4.35 of the User's Guide
+ Desc: Controls the LabJack watchdog function.
+
+ >>> dev = U12()
+ >>> dev.watchdog(1, 1, [0, 0, 0], [0, 0, 0])
+ >>> {'idnum': 1}
+ """
+
+ #Check id number
+ if idNum is None:
+ idNum = self.id
+ idNum = ctypes.c_long(idNum)
+
+ if len(activeDn) is not 3: raise ValueError("activeDn must have 3 elements")
+ if len(stateDn) is not 3: raise Value("stateDn must have 3 elements")
+
+ ecode = staticLib.Watchdog(ctypes.byref(idNum), demo, active, timeout, reset, activeDn[0], activeDn[1], activeDn[2], stateDn[0], stateDn[1], stateDn[2])
+ if ecode != 0: raise U12Exception(ecode)
+
+ return {"idnum":idNum.value}
+
+ def readMem(self, address, idnum = None):
+ """
+ Name: U12.readMem(address, idnum=None)
+ Args: See section 4.36 of the User's Guide
+ Desc: Reads 4 bytes from a specified address in the LabJack's nonvolatile memory
+
+ >>> dev = U12()
+ >>> dev.readMem(0)
+ >>> [5, 246, 16, 59]
+ """
+
+ if address is None:
+ raise Exception("Must give an Address.")
+
+ if idnum is None:
+ idnum = self.id
+
+ ljid = ctypes.c_ulong(idnum)
+ ad0 = ctypes.c_ulong()
+ ad1 = ctypes.c_ulong()
+ ad2 = ctypes.c_ulong()
+ ad3 = ctypes.c_ulong()
+
+ ec = staticLib.ReadMem(ctypes.byref(ljid), ctypes.c_long(address), ctypes.byref(ad3), ctypes.byref(ad2), ctypes.byref(ad1), ctypes.byref(ad0))
+ if ec != 0: raise U12Exception(ec)
+
+ addr = [0] * 4
+ addr[0] = int(ad3.value & 0xff)
+ addr[1] = int(ad2.value & 0xff)
+ addr[2] = int(ad1.value & 0xff)
+ addr[3] = int(ad0.value & 0xff)
+
+ return addr
+
+ def writeMem(self, address, data, idnum=None, unlocked=False):
+ """
+ Name: U12.writeMem(self, address, data, idnum=None, unlocked=False)
+ Args: See section 4.37 of the User's Guide
+ Desc: Writes 4 bytes to the LabJack's 8,192 byte nonvolatile memory at a specified address.
+
+ >>> dev = U12()
+ >>> dev.writeMem(0, [5, 246, 16, 59])
+ >>> 1
+ """
+ if address is None or data is None:
+ raise Exception("Must give both an Address and data.")
+ if type(data) is not list or len(data) != 4:
+ raise Exception("Data must be a list and have a length of 4")
+
+ if idnum is None:
+ idnum = self.id
+
+ ljid = ctypes.c_ulong(idnum)
+ ec = staticLib.WriteMem(ctypes.byref(ljid), int(unlocked), address, data[3] & 0xff, data[2] & 0xff, data[1] & 0xff, data[0] & 0xff)
+ if ec != 0: raise U12Exception(ec)
+
+ return ljid.value
+
+ def LJHash(self, hashStr, size):
+ outBuff = (ctypes.c_char * 16)()
+ retBuff = ''
+
+ staticLib = ctypes.windll.LoadLibrary("ljackuw")
+
+ ec = staticLib.LJHash(ctypes.cast(hashStr, ctypes.POINTER(ctypes.c_char)),
+ size,
+ ctypes.cast(outBuff, ctypes.POINTER(ctypes.c_char)),
+ 0)
+ if ec != 0: raise U12Exception(ec)
+
+ for i in range(16):
+ retBuff += outBuff[i]
+
+ return retBuff
+
+def isIterable(var):
+ try:
+ iter(var)
+ return True
+ except:
+ return False
+
+def listToCArray(list, dataType):
+ arrayType = dataType * len(list)
+ array = arrayType()
+ for i in range(0,len(list)):
+ array[i] = list[i]
+
+ return array
+
+def cArrayToList(array):
+ list = []
+ for item in array:
+ list.append(item)
+
+ return list
+
+def getErrorString(errorcode):
+ """
+ Name: U12.getErrorString(errorcode)
+ Args: See section 4.19 of the User's Guide
+ Desc: Converts a LabJack errorcode, returned by another function, into a string describing the error. No hardware communication is involved.
+
+ >>> dev = U12()
+ >>> dev.getErrorString(1)
+ >>> Unkown error
+ """
+ errorString = ctypes.c_char_p(" "*50)
+ staticLib.GetErrorString(errorcode, errorString)
+ return errorString.value
+
+def hexWithoutQuotes(l):
+ """ Return a string listing hex without all the single quotes.
+
+ >>> l = range(10)
+ >>> print hexWithoutQuotes(l)
+ [0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9]
+
+ """
+ return str([hex (i) for i in l]).replace("'", "")
diff --git a/lantz/drivers/legacy/labjack/u12.py b/lantz/drivers/legacy/labjack/u12.py
new file mode 100644
index 0000000..0ebbb98
--- /dev/null
+++ b/lantz/drivers/legacy/labjack/u12.py
@@ -0,0 +1,106 @@
+# -*- coding: utf-8 -*-
+"""
+
+
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+
+from lantz import Feat, Action, DictFeat
+from lantz import Driver
+from lantz.errors import InstrumentError
+
+from _internal import u12 as _u12
+
+class U12(Driver):
+ """
+ Driver for the Labjack U12 data acquisition device.
+ http://labjack.com/support/u12/users-guide
+ For details about the commands, refer to the users guide.
+ """
+ def __init__(self, board_id):
+ self._internal = _u12.U12(board_id)
+
+ def initialize(self):
+ super().initialize()
+ self._internal.open()
+
+ def finalize(self):
+ self._internal.close()
+
+
+ # ANALOG INPUT METHODS
+ '''
+ The LabJack U12 has 8 screw terminals for analog input signals (AI0-7). These can be configured individually and on-the-fly as 8 single-
+ ended channels, 4 differential channels, or combinations in between. Each input has a 12-bit resolution and an input bias current of
+ ±90 μA.
+ '''
+
+ '''
+ EAnalogIn is a simplified (E is for easy) function that returns a single reading from 1 analog input channel. Execution time is up to
+ 20 ms.
+ '''
+ @DictFeat(units='volts', keys=list(range(0,8)))
+ def analog_in(self, key):
+ return self._internal.eAnalogIn(channel=key)['voltage']
+
+ @DictFeat(units='volts', keys=list(range(0,4)))
+ def analog_dif_in(self, key, gain = 1):
+ '''
+ Differential channels can make use of the low noise precision PGA to provide gains up to 20. In differential mode, the voltage of each AI with respect to ground must be between +20 and -10 volts, but the range of voltage difference between the 2 AI is a function of gain (G) as follows:
+ G=1 ±20 volts
+ G=2 ±10 volts
+ G=4 ±5 volts
+ G=5 ±4 volts
+ G=8 ±2.5 volts
+ G=10 ±2 volts
+ G=16 ±1.25 volts
+ G=20 ±1 volt
+ The reason the range is ±20 volts at G=1 is that, for example, AI0 could be +10 volts and AI1 could be -10 volts giving a difference of +20 volts, or AI0 could be -10 volts and AI1 could be +10 volts giving a difference of -20 volts. The PGA (programmable gain amplifier, available on differential channels only) amplifies the AI voltage before it is digitized by the A/D converter. The high level drivers then divide the reading by the gain and return the actual measured voltage.
+ '''
+ gain_list = [1,2,4,5,8,10,16,20]
+ gain_value = gain
+ if gain_value not in gain_list:
+ raise InstrumentError('Gain value not permitted, check driver code or Labjack user guide')
+ else:
+ return self._internal.eAnalogIn(channel = key + 8, gain = gain_value)['voltage']
+
+ # ANALOG OUTPUT METHOD
+ analog_out = DictFeat(units = 'volts', keys=list(range(0,2)))
+ @analog_out.setter
+ def analog_out(self, key, value):
+ '''
+ Easy function. This is a simplified version of AOUpdate. Sets the voltage of both analog outputs.
+ Execution time for this function is 20 milliseconds or less (typically 16 milliseconds in Windows).
+ If either passed voltage is less than zero, the DLL uses the last set voltage. This provides a way to update 1 output without changing the other.
+ '''
+ if key == 0:
+ self._internal.eAnalogOut(analogOut0 = value, analogOut1 = -1)
+ else:
+ if key == 1:
+ self._internal.eAnalogOut(analogOut0 = -1, analogOut1 = value)
+ return
+
+ # DIGITAL INPUT/OUTPUT METHOD
+ @DictFeat(values={True: 1, False: 0}, keys=list(range(0,16)))
+ def digital_in_out(self, key):
+ '''
+ Easy function. This is a simplified version of DigitalIO that reads the state of one digital input. Also configures the requested pin to input and leaves it that way.
+ Execution time for this function is 20 milliseconds or less (typically 16 milliseconds in Windows).
+ channel – Line to read. 0-3 for IO or 0-15 for D.
+ '''
+ return self._internal.eDigitalIn(key)['state']
+
+ @digital_in_out.setter
+ def digital_in_out(self, key, value):
+ '''
+ Easy function. This is a simplified version of DigitalIO that sets/clears the state of one digital output. Also configures the requested pin to output and leaves it that way.
+ Execution time for this function is 20 milliseconds or less (typically 16 milliseconds in Windows).
+ channel – Line to read. 0-3 for IO or 0-15 for D.
+ '''
+ if key > 3:
+ self._internal.eDigitalOut(channel=key, writeD = 1, state = value)
+ else:
+ self._internal.eDigitalOut(channel=key, writeD = 0, state = value)
diff --git a/lantz/drivers/legacy/mpb/__init__.py b/lantz/drivers/legacy/mpb/__init__.py
new file mode 100644
index 0000000..e0606ce
--- /dev/null
+++ b/lantz/drivers/legacy/mpb/__init__.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.mpb
+ ~~~~~~~~~~~~~~~~~~~~~~~~
+
+ :company: MPB Communications Inc.
+ :description: Laser products.
+ :website: http://www.mpbc.ca/
+
+ ----
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from .vfl import VFL
+
+__all__ = ['VFL']
diff --git a/lantz/drivers/legacy/mpb/vfl.py b/lantz/drivers/legacy/mpb/vfl.py
new file mode 100644
index 0000000..0714361
--- /dev/null
+++ b/lantz/drivers/legacy/mpb/vfl.py
@@ -0,0 +1,209 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.mpb.vfl
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from lantz import Action, Feat
+from lantz.drivers.legacy.serial import SerialDriver
+
+
+class VFL(SerialDriver):
+ """Driver for any VFL MPB Communications laser.
+ """
+
+ ENCODING = 'ascii'
+
+ RECV_TERMINATION = '\rD >'
+ SEND_TERMINATION = '\r'
+
+ BAUDRATE = 9600
+ BYTESIZE = 8
+ PARITY = 'none'
+ STOPBITS = 1
+
+ #: flow control flags
+ RTSCTS = False
+ DSRDTR = False
+ XONXOFF = False
+
+ @Feat(read_once=True)
+ def idn(self):
+ """Identification of the device
+ """
+ return self.query('GETMODEL')
+
+ @Feat()
+ def status(self):
+ """Current device status
+ """
+ ans = self.query('shlaser')
+ return ans.split('\r')
+
+ # ENABLE LASER
+ @Feat(values={True: '1', False: '0'})
+ def enabled(self):
+ """Method for turning on the laser
+ """
+ return self.query('GETLDENABLE')
+
+ @enabled.setter
+ def enabled(self, value):
+ self.query('SETLDENABLE ' + value)
+
+ # LASER'S CONTROL MODE AND SET POINT
+
+ @Feat(values={'APC': '1', 'ACC': '0'})
+ def ctl_mode(self):
+ """To handle laser diode current (mA) in Active Current Control Mode
+ """
+ return self.query('GETPOWERENABLE')
+
+ @ctl_mode.setter
+ def ctl_mode(self, value):
+ self.query('POWERENABLE {}'.format(value))
+
+ @Feat(units='mA')
+ def current_sp(self):
+ """To handle laser diode current (mA) in Active Current Control Mode
+ """
+ return float(self.query('GETLDCUR 1'))
+
+ @current_sp.setter
+ def current_sp(self, value):
+ self.query('SETLDCUR 1 {:.1f}'.format(value))
+
+ @Feat(units='mW')
+ def power_sp(self):
+ """To handle output power set point (mW) in APC Mode
+ """
+ return float(self.query('GETPOWER 0'))
+
+ @power_sp.setter
+ def power_sp(self, value):
+ self.query('SETPOWER 0 {:.0f}'.format(value))
+
+ # LASER'S CURRENT STATUS
+
+ @Feat(units='mW')
+ def power(self):
+ """To get the laser emission power (mW)
+ """
+ return float(self.query('POWER 0'))
+
+ @Feat(units='mA')
+ def ld_current(self):
+ """To get the laser diode current (mA)
+ """
+ return float(self.query('LDCURRENT 1'))
+
+ @Feat(units='degC')
+ def ld_temp(self):
+ """To get the laser diode temperature (ÂşC)
+ """
+ return float(self.query('LDTEMP 1'))
+
+ @Feat(units='mA')
+ def tec_current(self):
+ """To get the thermoelectric cooler (TEC) current (mA)
+ """
+ return float(self.query('TECCURRENT 1'))
+
+ @Feat(units='degC')
+ def tec_temp(self):
+ """To get the thermoelectric cooler (TEC) temperature (ÂşC)
+ """
+ return float(self.query('TECTEMP 1'))
+
+ # SECOND HARMONIC GENERATOR METHODS
+
+ @Feat(units='degC')
+ def shg_temp_sp(self):
+ """To handle the SHG temperature set point
+ """
+ return float(self.query('GETSHGTEMP'))
+
+ @shg_temp_sp.setter
+ def shg_temp_sp(self, value):
+ self.query('GETSHGTEMP {:.2f}'.format(value))
+
+ @Feat(units='degC')
+ def shg_temp(self):
+ """To get the SHG temperature
+ """
+ return float(self.query('SHGTEMP'))
+
+ @Feat()
+ def shg_tune_info(self):
+ """Getting information about laser ready for SHG tuning
+ """
+ info = self.query('GETSHGTUNERDY').split()
+ if info[0] == '0':
+ ready = 'Laser not ready for SHG tuning. '
+ else:
+ ready = 'Laser ready for SHG tuning. '
+
+ schedule = 'Next SHG tuning scheduled in {} '.format(info[1])
+ schedule += 'hours of operation. '
+ warm = 'Warm-up period expires in {} seconds.'.format(info[2])
+
+ ans = ready + schedule + warm
+ return ans
+
+ @Feat()
+ def shg_tuning(self):
+ """Initiating SHG tuning
+ """
+ state = self.query('GETSHGTUNESTATE').split()
+ tuning = error = ''
+
+ if state[0] == '0':
+ tuning = 'No SHG tuning performed since last reset. '
+ elif state[0] == '3':
+ tuning = 'SHG tuning in progress. '
+ elif state[0] == '1':
+ tuning = 'SHG tuning completed successfully. '
+ elif state[0] == '2':
+ tuning = 'SHG tuning aborted. '
+
+ if state[1] == '0':
+ error = 'No error detected.'
+ elif state[1] == '1':
+ error = 'Error: Laser not running in APC.'
+ elif state[1] == '8':
+ error = 'Error: Output Power not stabilized.'
+
+ return tuning + error
+
+ @Action()
+ def tune_shg(self):
+ self.query('SETSHGCMD 1')
+
+ @Action()
+ def tune_shg_stop(self):
+ self.query('SETSHGCMD 2')
+
+if __name__ == '__main__':
+ import argparse
+ import lantz.log
+
+ parser = argparse.ArgumentParser(description='Test Kentech HRI')
+ parser.add_argument('-i', '--interactive', action='store_true',
+ default=False, help='Show interactive GUI')
+ parser.add_argument('-p', '--port', type=str, default='COM3',
+ help='Serial port to connect to')
+
+ args = parser.parse_args()
+ lantz.log.log_to_screen(lantz.log.DEBUG)
+ with VFL(args.port) as inst:
+ if args.interactive:
+ from lantz.ui.qtwidgets import start_test_app
+ start_test_app(inst)
+ else:
+ # Add your test code here
+ print('Non interactive mode')
+ print(inst.idn)
+ print(inst.shg_tuning)
diff --git a/lantz/network.py b/lantz/drivers/legacy/network.py
similarity index 88%
rename from lantz/network.py
rename to lantz/drivers/legacy/network.py
index d61dc35..4aa719c 100644
--- a/lantz/network.py
+++ b/lantz/drivers/legacy/network.py
@@ -1,19 +1,19 @@
# -*- coding: utf-8 -*-
"""
- lantz.network
- ~~~~~~~~~~~~~
+ lantz.drivers.legacy.network
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Implements a base class for drivers that communicate with instruments via TCP.
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
import socket
-from . import Driver
-from .driver import TextualMixin
-from .errors import LantzTimeoutError
+from lantz import Driver
+from lantz.drivers.legacy.textual import TextualMixin
+from lantz.errors import LantzTimeoutError
class LantzSocketTimeoutError(socket.timeout, LantzTimeoutError):
diff --git a/lantz/drivers/legacy/newport/__init__.py b/lantz/drivers/legacy/newport/__init__.py
new file mode 100644
index 0000000..d616da6
--- /dev/null
+++ b/lantz/drivers/legacy/newport/__init__.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.newport
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ :company: Newport.
+ :description: Test and Measurement Equipment.
+ :website: http://www.newport.com/
+
+ ---
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD,
+
+"""
+
+from .powermeter1830c import powermeter1830c
+
+__all__ = ['powermeter1830c']
diff --git a/lantz/drivers/legacy/newport/powermeter1830c.py b/lantz/drivers/legacy/newport/powermeter1830c.py
new file mode 100644
index 0000000..01a0e79
--- /dev/null
+++ b/lantz/drivers/legacy/newport/powermeter1830c.py
@@ -0,0 +1,198 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.newport.powermeter1830c
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Implements the drivers to control an Optical Power Meter.
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+
+ Source: Instruction Manual (Newport)
+"""
+
+from lantz.feat import Feat
+from lantz.action import Action
+from lantz.drivers.legacy.serial import SerialDriver
+
+
+class powermeter1830c(SerialDriver):
+ """ Newport 1830c Power Meter
+ """
+
+ ENCODING = 'ascii'
+ RECV_TERMINATION = '\n'
+ SEND_TERMINATION = '\n'
+ TIMEOUT = 2000
+
+
+ def __init__(self, port=1,baudrate=9600):
+ super().__init__(port, baudrate, bytesize=8, parity='None', stopbits=1)
+
+ def initialize(self):
+ super().initialize()
+
+ @Feat(values={True: 1, False: 0})
+ def attenuator(self):
+ """ Attenuator.
+ 1: Attenuator present
+ 0: Attenuator not present
+ """
+ return int(self.query('A?'))
+
+ @attenuator.setter
+ def attenuator(self, value):
+ self.send('A{}'.format(value))
+
+ @Feat(values={True: 1, False: 0})
+ def beeper(self):
+ """ Checks whether the audio output is on or off.
+ """
+ return int(self.query('B?'))
+
+ @beeper.setter
+ def beeper(self,value):
+ self.send('B{}'.format(value))
+
+ @Feat
+ def data(self):
+ """ Retrieves the value from the power meter.
+ """
+ return float(self.query('D?'))
+
+ @Feat(values={True: 1, False: 0})
+ def echo(self):
+ """ Returns echo mode. Only applied to RS232 communication
+ """
+ return int(self.query('E?'))
+
+ @echo.setter
+ def echo(self,value):
+ self.send('E{}'.format(value))
+
+ @Feat(values={'Slow': 1, 'Medium': 2, 'Fast': 3})
+ def filter(self):
+ """ How many measurements are averaged for the displayed reading.
+ slow: 16 measurements
+ medium: 4 measurements
+ fast: 1 measurement.
+ """
+ return int(self.query('F?'))
+
+ @filter.setter
+ def filter(self,value):
+ self.send('F{}'.format(value))
+
+ @Feat(values={True: 1, False: 0})
+ def go(self):
+ """ Enable or disable the power meter from taking new measurements.
+ """
+ return int(self.query('G?'))
+
+ @go.setter
+ def go(self,value):
+ self.send('G{}'.format(value))
+
+ @Feat(values={'Off': 0, 'Medium': 1, 'High': 2})
+ def keypad(self):
+ """ Keypad/Display backlight intensity levels.
+ """
+ return int(self.query('K?'))
+
+ @keypad.setter
+ def keypad(self,value):
+ self.send('K{}'.format(value))
+
+ @Feat(values={True: 1, False: 0})
+ def lockout(self):
+ """ Enable/Disable the lockout. When the lockout is enabled, any front panel key presses would have no effect on system operation.
+ """
+ return int(self.query('L?'))
+
+ @lockout.setter
+ def lockout(self,value):
+ self.send('L{}'.format(value))
+
+ @Action()
+ def autocalibration(self):
+ """ Autocalibration of the power meter. This procedure disconnects the input signal.
+ It should be performed at least 60 minutes after warm-up.
+ """
+ self.send('O')
+
+ @Feat(values=set(range(0,9)))
+ def range(self):
+ """ Set the signal range for the input signal.
+ 0 means auto setting the range. 1 is the lowest signal range and 8 the highest.
+ """
+ return int(self.query('R?'))
+
+ @range.setter
+ def range(self,value):
+ self.send('R{}'.format(value))
+
+ @Action()
+ def store_reference(self):
+ """ Sets the current input signal power level as the power reference level.
+ Each time the S command is sent, the current input signal becomes the new reference level.
+ """
+ self.send('S')
+
+ @Feat(values={'Watts': 1, 'dB': 2, 'dBm': 3, 'REL': 4})
+ def units(self):
+ """ Sets and gets the units of the measurements.
+ """
+ return int(self.query('U?'))
+
+ @units.setter
+ def units(self,value):
+ self.send('U{}'.format(value))
+
+ @Feat(limits=(1,10000,1))
+ def wavelength(self):
+ """ Sets and gets the wavelength of the input signal.
+ """
+ return int(self.query('W?'))
+
+ @wavelength.setter
+ def wavelength(self,value):
+ self.send('W{}'.format(int(value)))
+
+ @Feat(values={True: 1, False: 0})
+ def zero(self):
+ """ Turn the zero function on/off. Zero function is used for subtracting any background power levels in future measurements.
+ """
+ return int(self.query('Z?'))
+
+ @zero.setter
+ def zero(self,value):
+ self.send('Z{}'.format(value))
+
+if __name__ == '__main__':
+ import argparse
+ import lantz.log
+
+ parser = argparse.ArgumentParser(description='Test Kentech HRI')
+ parser.add_argument('-p', '--port', type=str, default='1',
+ help='Serial port to connect to')
+
+ args = parser.parse_args()
+ lantz.log.log_to_socket(lantz.log.DEBUG)
+
+ with powermeter1830c(args.port) as inst:
+
+ inst.initialize() # Initialize the communication with the power meter
+
+ inst.lockout = True # Blocks the front panel
+ inst.keypad = 'Off' # Switches the keypad off
+ inst.attenuator = True # The attenuator is on
+ inst.wavelength = 633 # Sets the wavelength to 633nm
+ inst.units = "Watts" # Sets the units to Watts
+ inst.filter = 'Slow' # Averages 16 measurements
+
+ if not inst.go:
+ inst.go = True # If the instrument is not running, enables it
+
+ inst.range = 0 # Auto-sets the range
+
+ print('The measured power is {} Watts'.format(inst.data))
diff --git a/lantz/drivers/legacy/ni/__init__.py b/lantz/drivers/legacy/ni/__init__.py
new file mode 100644
index 0000000..2e93133
--- /dev/null
+++ b/lantz/drivers/legacy/ni/__init__.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.ni
+ ~~~~~~~~~~~~~~~~~~~~~~~
+
+ :company: National Instruments
+ :description:
+ :website: http://www.ni.com/
+
+ ----
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from . import daqmx
+
diff --git a/lantz/drivers/legacy/ni/daqmx/__init__.py b/lantz/drivers/legacy/ni/daqmx/__init__.py
new file mode 100644
index 0000000..45dcbd4
--- /dev/null
+++ b/lantz/drivers/legacy/ni/daqmx/__init__.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.ni.daqmx
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Implements bindings to the DAQmx (windows) National Instruments libraries.
+
+ Sources::
+
+ - DAQmx Reference manual
+ - DAQmx Base Reference manual
+ - pylibnidaqmx
+ http://pylibnidaqmx.googlecode.com
+
+
+ :company: National Instruments
+ :description:
+ :website: http://www.ni.com/
+
+ ----
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from .base import System, Task, Channel, Device
+from .channels import *
+from .tasks import *
+from .constants import Constants, Types
+
+
diff --git a/lantz/drivers/legacy/ni/daqmx/base.py b/lantz/drivers/legacy/ni/daqmx/base.py
new file mode 100644
index 0000000..35649cb
--- /dev/null
+++ b/lantz/drivers/legacy/ni/daqmx/base.py
@@ -0,0 +1,1862 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.ni.daqmx.base
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Implementation of base classes for Channels, Tasks and Devices
+
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from lantz import Feat, Action
+from lantz.errors import InstrumentError
+from lantz.foreign import LibraryDriver, RetValue, RetStr
+
+from .constants import Constants, Types
+
+default_buf_size = 2048
+
+_SAMPLE_MODES = {'finite': Constants.Val_FiniteSamps,
+ 'continuous': Constants.Val_ContSamps,
+ 'hwtimed': Constants.Val_HWTimedSinglePoint}
+
+_BUS_TYPES = {'PCI': Constants.Val_PCI, 'PCIe': Constants.Val_PCIe,
+ 'PXI': Constants.Val_PXI, 'SCXI': Constants.Val_SCXI,
+ 'PCCard': Constants.Val_PCCard, 'USB': Constants.Val_USB,
+ 'UNKNOWN': Constants.Val_Unknown}
+
+_SIGNAL_TYPES = {'sample_clock': Constants.Val_SampleClock,
+ 'sample_complete': Constants.Val_SampleCompleteEvent,
+ 'change_direction': Constants.Val_ChangeDetectionEvent,
+ 'counter_output': Constants.Val_CounterOutputEvent}
+
+_EDGE_TYPES = {'rising': Constants.Val_Rising, 'falling': Constants.Val_Falling}
+
+_SLOPE_TYPES = {'rising': Constants.Val_RisingSlope, 'falling': Constants.Val_FallingSlope}
+
+_WHEN_WINDOW = {'entering': Constants.Val_EnteringWin, 'leaving': Constants.Val_LeavingWin}
+
+_WHEN_TRIGGER_DIG = {'high': Constants.Val_High, 'low': Constants.Val_Low}
+
+_WHEN_TRIGGER_ALVL = {'above': Constants.Val_AboveLvl, 'below': Constants.Val_BelowLvl}
+
+_WHEN_TRIGGER_AWIN = {'inside': Constants.Val_InsideWin, 'outside': Constants.Val_OutsideWin}
+
+_WHEN_MATCH = {True: Constants.Val_PatternMatches, False: Constants.Val_PatternDoesNotMatch}
+
+_TRIGGER_TYPES = {'digital_level': Constants.Val_DigLvl,
+ 'analog_level': Constants.Val_AnlgLvl,
+ 'analog_window': Constants.Val_AnlgWin}
+
+_CHANNEL_TYPES = {'AI': Constants.Val_AI, 'AO': Constants.Val_AO,
+ 'DI': Constants.Val_DI, 'DO': Constants.Val_DO,
+ 'CI': Constants.Val_CI, 'CO': Constants.Val_CO}
+
+class _Base(LibraryDriver):
+ """Base class for NIDAQmx
+ """
+
+ LIBRARY_NAME = 'nicaiu', 'nidaqmx'
+ LIBRARY_PREFIX = 'DAQmx'
+
+ _DEVICES = {}
+ _TASKS = {}
+ _CHANNELS = {}
+
+ def _get_error_string(self, error_code):
+ size = self.lib.GetErrorString(error_code, None, 0)
+ if size <= 0:
+ raise InstrumentError('Could not retrieve error string.')
+ err, msg = self.lib.GetErrorString(error_code, *RetStr(size))
+ if err < 0:
+ raise InstrumentError('Could not retrieve error string.')
+ return msg
+
+ def _get_error_extended_error_info(self):
+ size = self.lib.GetExtendedErrorInfo(None, 0)
+ if size <= 0:
+ raise InstrumentError('Could not retrieve extended error info.')
+ err, msg = self.lib.GetExtendedErrorInfo(*RetStr(size))
+ if err < 0:
+ raise InstrumentError('Could not retrieve extended error info.')
+ return msg
+
+ def _return_handler(self, func_name, ret_value):
+ if ret_value < 0 and func_name not in ('GetErrorString', 'GetExtendedErrorInfo'):
+ msg = self._get_error_string(ret_value)
+ raise InstrumentError(msg)
+ return ret_value
+
+ def __get_fun(self, name):
+ return getattr(self.lib, name.format(self.operation_direction.title()))
+
+ def _add_types(self):
+
+ super()._add_types()
+ T = Types
+ self.lib.CreateAIVoltageChan.argtypes = [T.TaskHandle, T.string, T.string, T.int32, T.float64, T.float64, T.int32, T.string]
+ self.lib.ReadAnalogScalarF64.argtypes = [T.TaskHandle, T.float64, T._, T._]
+
+class _ObjectDict(object):
+
+ def __init__(self, key_fun, obj_creator, dictionary=None):
+ self.key_fun = key_fun
+ self.obj_creator = obj_creator
+ self._internal = {} if dictionary is None else dictionary
+
+ def __getitem__(self, item):
+
+ if item in self._internal:
+ return self._internal[item]
+
+ if item not in self:
+ raise KeyError('{} not found'.format(item))
+
+ value = self.obj_creator(item)
+
+ self._internal[item] = value
+
+ return value
+
+ def __len__(self):
+ return sum((1 for key in self.keys()))
+
+ def __contains__(self, item):
+ return item in self.key_fun()
+
+ def keys(self):
+ for key in self.key_fun():
+ yield key
+
+ def values(self):
+ for key in self.keys():
+ yield self[key]
+
+ def items(self):
+ for key in self.keys():
+ yield key, self[key]
+
+
+class System(_Base):
+ """NI-DAQmx System
+ """
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.devices = _ObjectDict(self._device_names, Device, self._DEVICES)
+ self.tasks = _ObjectDict(self._task_names, Task, self._TASKS)
+ self.channels = _ObjectDict(self._channel_names, Channel, self._CHANNELS)
+
+ @Feat(read_once=True)
+ def version(self):
+ """Version of installed NI-DAQ library.
+ """
+ err, major = self.lib.GetSysNIDAQMajorVersion(RetValue('u32'))
+ err, minor = self.lib.GetSysNIDAQMinorVersion(RetValue('u32'))
+ return major, minor
+
+ def _device_names(self):
+ """Return a tuple containing the names of all global devices installed in the system.
+ """
+ err, buf = self.lib.GetSysDevNames(*RetStr(default_buf_size))
+ names = tuple(n.strip() for n in buf.split(',') if n.strip())
+ return names
+
+ def _task_names(self):
+ """Return a tuple containing the names of all global tasks saved in the system.
+ """
+ err, buf = self.lib.GetSysTasks(*RetStr(default_buf_size))
+ names = tuple(n.strip() for n in buf.split(',') if n.strip())
+ return names
+
+ def _channel_names(self):
+ """Return a tuple containing the names of all global channels saved in the system.
+ """
+ err, buf = self.lib.GetSysGlobalChans(*RetStr(default_buf_size))
+ names = tuple(n.strip() for n in buf.split(',') if n.strip())
+ return names
+
+
+class Device(_Base):
+ """Device
+ """
+
+ def __init__(self, device_name, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.device_name = device_name
+
+ def _preprocess_args(self, name, *args):
+ """Injects device_name to all call to the library
+ """
+ if name in ('GetErrorString', 'GetExtendedErrorInfo'):
+ return super()._preprocess_args(name, *args)
+ else:
+ return super()._preprocess_args(name, *((self.device_name, ) + args))
+
+ @Feat(read_once=True)
+ def is_simulated(self):
+ """Return True if it is a simulated device
+ """
+ err, value = self.lib.GetDevIsSimulated(RetValue('u32'))
+ return value != 0
+
+ @Feat(read_once=True)
+ def product_type(self):
+ """Return the product name of the device.
+ """
+ err, buf = self.lib.GetDevProductType(*RetStr(default_buf_size))
+ return buf
+
+ @Feat(read_once=True)
+ def product_number(self):
+ """Return the unique hardware identification number for the device.
+ """
+ err, value = self.lib.GetDevProductNum(RetValue('u32'))
+ return value
+
+ @Feat(read_once=True)
+ def serial_number (self):
+ """Return the serial number of the device. This value is zero
+ if the device does not have a serial number.
+ """
+ err, value = self.lib.GetDevSerialNum(RetValue('u32'))
+ return value
+
+ @Feat(read_once=True)
+ def analog_input_channels(self):
+ """Return a tuple with the names of the analog input
+ physical channels available on the device.
+ """
+
+ err, buf = self.lib.GetDevAIPhysicalChans(*RetStr(default_buf_size))
+ names = tuple(n.strip() for n in buf.split(',') if n.strip())
+ return names
+
+ @Feat(read_once=True)
+ def analog_output_channels(self):
+ """Return a tuple with the names of the analog output
+ physical channels available on the device.
+ """
+ err, buf = self.lib.GetDevAOPhysicalChans(*RetStr(default_buf_size))
+ names = tuple(n.strip() for n in buf.split(',') if n.strip())
+ return names
+
+ @Feat(read_once=True)
+ def digital_input_lines(self):
+ """Return a tuple with the names of the digital input lines
+ physical channels available on the device.
+ """
+ err, buf = self.lib.GetDevDILines(*RetStr(default_buf_size))
+ names = tuple(n.strip() for n in buf.split(',') if n.strip())
+ return names
+
+ @Feat(read_once=True)
+ def digital_output_lines(self):
+ """Return a tuple with the names of the digital lines
+ ports available on the device.
+ """
+ err, buf = self.lib.GetDevDOLines(*RetStr(default_buf_size))
+ names = tuple(n.strip() for n in buf.split(',') if n.strip())
+ return names
+
+ @Feat(read_once=True)
+ def digital_input_ports(self):
+ """Return a tuple with the names of the digital input
+ ports available on the device.
+ """
+ err, buf = self.lib.GetDevDIPorts(*RetStr(default_buf_size))
+ names = tuple(n.strip() for n in buf.split(',') if n.strip())
+ return names
+
+ @Feat(read_once=True)
+ def digital_output_ports(self):
+ """Return a tuple with the names of the digital output
+ ports available on the device.
+ """
+ err, buf = self.lib.GetDevDOPorts(*RetStr(default_buf_size))
+ names = tuple(n.strip() for n in buf.split(',') if n.strip())
+ return names
+
+ @Feat(read_once=True)
+ def counter_input_channels(self):
+ """Return a tuple with the names of the counter input
+ physical channels available on the device.
+ """
+ err, buf = self.lib.GetDevCIPhysicalChans(*RetStr(default_buf_size))
+ names = tuple(n.strip() for n in buf.split(',') if n.strip())
+ return names
+
+ @Feat(read_once=True)
+ def counter_output_channels(self):
+ """Return a tuple with the names of the counter input
+ physical channels available on the device.
+ """
+ err, buf = self.lib.GetDevCOPhysicalChans(*RetStr(default_buf_size))
+ names = tuple(n.strip() for n in buf.split(',') if n.strip())
+ return names
+
+ @Feat(read_once=True, values=_BUS_TYPES)
+ def bus_type(self):
+ """Return the bus type of the device.
+ """
+ err, value = self.lib.GetDevBusType(RetValue('i32'))
+ return value
+
+ @Feat(read_once=True)
+ def pci_bus_number (self):
+ """Return the PCI bus number of the device.
+ """
+ err, value = self.lib.GetDevPCIBusNum(RetValue('i32'))
+ return value
+
+ @Feat(read_once=True)
+ def pci_device_number (self):
+ """Return the PCI slot number of the device.
+ """
+ err, value = self.lib.GetDevPCIDevNum(RetValue('i32'))
+ return value
+
+ @Feat(read_once=True)
+ def pxi_slot_number(self):
+ """Return the PXI slot number of the device.
+ """
+ err, value = self.lib.GetDevPXISlotNum(RetValue('u32'))
+ return value
+
+ @Feat(read_once=True)
+ def pxi_chassis_number(self):
+ """Return the PXI chassis number of the device, as identified
+ in MAX.
+ """
+ err, value = self.lib.GetDevPXIChassisNum(RetValue('u32'))
+ return value
+
+ @Feat(read_once=True)
+ def bus_info(self):
+ t = self.bus_type
+ if t in ('PCI', 'PCIe'):
+ return '%s (bus=%s, device=%s)' % (t, self.pci_bus_number, self.pci_device_number)
+ if t == 'PXI':
+ return '%s (chassis=%s, slot=%s)' % (t, self.pxi_chassis_number, self.pxi_slot_number)
+ return t
+
+ @Action()
+ def reset(self):
+ """Stops and deletes all tasks on a device and rests outputs to their defaults
+ """
+ return self.lib.ResetDevice()
+
+
+class Task(_Base):
+ """A task is a collection of one or more virtual channels with timing,
+ triggering, and other properties. Conceptually, a task represents a
+ measurement or generation you want to perform. All channels in a task
+ must be of the same I/O type, such as analog input or counter output.
+
+ However, a task can include channels of different measurement types,
+ such as an analog input temperature channel and an analog input voltage
+ channel. For most devices, only one task per subsystem can run at once,
+ but some devices can run multiple tasks simultaneously. With some devices,
+ you can include channels from multiple devices in a task.
+
+ :param name: Name assigned to the task (This can be changed by the
+ library. The final name will be stored in name attribute)
+
+ """
+
+ _REGISTRY = {}
+
+ @classmethod
+ def register_class(cls, klass):
+ cls._REGISTRY[klass.IO_TYPE] = klass
+
+ @classmethod
+ def typed_task(cls, io_type):
+ return cls._REGISTRY[io_type]
+
+ def _load_task(self, name):
+ err, self.__task_handle = self.lib.LoadTask(name, RetValue('u32'))
+ self.name = name
+ self.log_debug('Loaded task with {} ({})'.format(self.name, self.__task_handle))
+
+ def _create_task(self, name):
+ err, self.__task_handle = self.lib.CreateTask(name, RetValue('u32'))
+ err, self.name = self.lib.GetTaskName(*RetStr(default_buf_size))
+ self.log_debug('Created task with {} ({})'.format(self.name, self.__task_handle))
+
+ def __init__(self, name='', *args, **kwargs):
+ super().__init__(name, *args, **kwargs)
+ if not name:
+ self._create_task(name)
+ else:
+ try:
+ self._load_task(name)
+ except Exception as e:
+ self._create_task(name)
+
+ self.sample_mode = None
+ self.channels = _ObjectDict(self._channel_names, self._create_channel_from_name, self._CHANNELS)
+ self.devices = _ObjectDict(self._device_names, Device, self._DEVICES)
+
+ @property
+ def task_handle(self):
+ return self.__task_handle
+
+ def _preprocess_args(self, name, *args):
+ """Injects device_name to all call to the library
+ """
+ if name in ('GetErrorString', 'GetExtendedErrorInfo', 'LoadTask', 'CreateTask'):
+ return super()._preprocess_args(name, *args)
+ else:
+ return super()._preprocess_args(name, *((self.task_handle, ) + args))
+
+ def _create_channel_from_name(self, name):
+ return Channel(self, name=name)
+
+ def _channel_names(self):
+ """Return a tuple with the names of all virtual channels in the task.
+ """
+ err, buf = self.lib.GetTaskChannels(*RetStr(default_buf_size))
+ names = tuple(n.strip() for n in buf.split(',') if n.strip())
+ return names
+
+ def _device_names(self):
+ """Return a tuple with the names of all devices in the task.
+ """
+ err, buf = self.lib.GetTaskDevices(*RetStr(default_buf_size))
+ names = tuple(n.strip() for n in buf.split(',') if n.strip())
+ return names
+
+ @Feat()
+ def io_type(self):
+ for name, channel in self.channels.items():
+ return channel.io_type
+ else:
+ return None
+
+ def add_channel(self, channel):
+ if not isinstance(channel, Channel):
+ raise TypeError('Only channels may be added to a task.')
+
+ if channel.task is self.channels:
+ return
+ elif channel.task is None:
+ channel.task = self
+ else:
+ raise ValueError('Cannot add a channel that is already in another task')
+
+ def execute_fun(self, func_name, *args):
+ return getattr(self.lib, func_name)(*args)
+
+ def clear(self):
+ """Clear the task.
+
+ Before clearing, this function stops the task, if necessary,
+ and releases any resources reserved by the task. You cannot
+ use a task once you clear the task without recreating or
+ reloading the task.
+
+ If you use the DAQmxCreateTask function or any of the NI-DAQmx
+ Create Channel functions within a loop, use this function
+ within the loop after you finish with the task to avoid
+ allocating unnecessary memory.
+ """
+ if self.task_handle:
+ self.lib.ClearTask()
+ self.__task_handle = None
+
+ __del__ = clear
+
+ @Feat()
+ def is_done(self):
+ """Queries the status of the task and indicates if it completed
+ execution. Use this function to ensure that the specified
+ operation is complete before you stop the task.
+ """
+ err, value = self.lib.IsTaskDone(RetValue('u32'))
+ return value != 0
+
+ # States
+
+ @Action()
+ def start(self):
+ """Start the task
+
+ Transitions the task from the committed state to the running
+ state, which begins measurement or generation. Using this
+ function is required for some applications and optional for
+ others.
+
+ If you do not use this function, a measurement task starts
+ automatically when a read operation begins. The autoStart
+ parameter of the NI-DAQmx Write functions determines if a
+ generation task starts automatically when you use an NI-DAQmx
+ Write function.
+
+ If you do not call StartTask and StopTask when you
+ call NI-DAQmx Read functions or NI-DAQmx Write functions
+ multiple times, such as in a loop, the task starts and stops
+ repeatedly. Starting and stopping a task repeatedly reduces
+ the performance of the application.
+ """
+
+ self.lib.StartTask()
+
+ @Action()
+ def stop(self):
+ """Stop the task.
+
+ Stop the task and returns it to the state it was in before
+ you called StartTask or called an NI-DAQmx Write function with
+ autoStart set to TRUE.
+
+ If you do not call StartTask and StopTask when you call
+ NI-DAQmx Read functions or NI-DAQmx Write functions multiple
+ times, such as in a loop, the task starts and stops
+ repeatedly. Starting and stopping a task repeatedly reduces
+ the performance of the application.
+
+ """
+ self.lib.StopTask()
+
+ @Action()
+ def verify(self):
+ """Verifies that all task parameters are valid for the hardware.
+ """
+ self.alter_state('verify')
+
+ @Action()
+ def commit(self):
+ """Programs the hardware as much as possible according
+ to the task configuration.
+ """
+ self.alter_state('commit')
+
+ @Action()
+ def reserve(self):
+ """Reserves the hardware resources needed for the
+ task. No other tasks can reserve these same resources.
+ """
+ self.alter_state('reserve')
+
+ @Action()
+ def unreserve(self):
+ """Release all reserved resources.
+ """
+ self.alter_state('unreserve')
+
+ @Action()
+ def abort(self):
+ """Abort an operation, such as Read or Write, that is currently active.
+
+ The task is put into an unstable but recoverable state. To recover the
+ task, call Start to restart the task or call Stop to reset the task
+ without starting it.
+ """
+ self.alter_state('abort')
+
+ @Action(values={'start': Constants.Val_Task_Start, 'stop': Constants.Val_Task_Stop,
+ 'verify': Constants.Val_Task_Verify, 'commit': Constants.Val_Task_Commit,
+ 'reserve': Constants.Val_Task_Reserve, 'unreserve': Constants.Val_Task_Unreserve,
+ 'abort': Constants.Val_Task_Abort})
+ def alter_state(self, new_state):
+ """Alters the state of a task according to the action you
+ specify. To minimize the time required to start a task, for
+ example, DAQmxTaskControl can commit the task prior to
+ starting.
+
+ :param new_state:
+ """
+ self.lib.TaskControl(new_state)
+
+ _register_every_n_samples_event_cache = None
+
+ def register_every_n_samples_event(self, func, samples=1, options=0, cb_data=None):
+ """Register a callback function to receive an event when the
+ specified number of samples is written from the device to the
+ buffer or from the buffer to the device. This function only
+ works with devices that support buffered tasks.
+
+ When you stop a task explicitly any pending events are
+ discarded. For example, if you call DAQmxStopTask then you do
+ not receive any pending events.
+
+ :param func: The function that you want DAQmx to call when the event
+ occurs. The function you pass in this parameter must have
+ the following prototype::
+
+ def func(task, event_type, samples, cb_data):
+ ...
+ return 0
+
+ Upon entry to the callback, the task parameter contains the
+ handle to the task on which the event occurred. The
+ event_type parameter contains the value you passed in the
+ event_type parameter of this function. The samples parameter
+ contains the value you passed in the samples parameter of
+ this function. The cb_data parameter contains the value you
+ passed in the cb_data parameter of this function.
+
+ :param samples: The number of samples after which each event should occur.
+
+ :param options:
+
+ :param cb_data:
+
+ See also: register_signal_event, register_done_event
+ """
+
+ if self.operation_direction == 'input':
+ event_type = Constants.Val_Acquired_Into_Buffer
+ else:
+ event_type = Constants.Val_Transferred_From_Buffer
+
+ if options == 'sync':
+ options = Constants.Val_SynchronousEventCallbacks
+
+ if func is None:
+ c_func = None # to unregister func
+ else:
+ if self._register_every_n_samples_event_cache is not None:
+ # unregister:
+ self.register_every_n_samples_event(None, samples=samples, options=options, cb_data=cb_data)
+ # TODO: check the validity of func signature
+ # TODO: use wrapper function that converts cb_data argument to given Python object
+ c_func = EveryNSamplesEventCallback_map[self.CHANNEL_TYPE](func)
+
+ self._register_every_n_samples_event_cache = c_func
+
+ self.lib.RegisterEveryNSamplesEvent(event_type, uInt32(samples), uInt32(options), c_func, cb_data)
+
+ _register_done_event_cache = None
+
+ def register_done_event(self, func, options=0, cb_data=None):
+ """Register a callback function to receive an event when a task
+ stops due to an error or when a finite acquisition task or
+ finite generation task completes execution. A Done event does
+ not occur when a task is stopped explicitly, such as by
+ calling DAQmxStopTask.
+
+
+ :param func:
+
+ The function that you want DAQmx to call when the event
+ occurs. The function you pass in this parameter must have
+ the following prototype::
+
+ def func(task, status, cb_data = None):
+ ...
+ return 0
+
+ Upon entry to the callback, the taskHandle parameter
+ contains the handle to the task on which the event
+ occurred. The status parameter contains the status of the
+ task when the event occurred. If the status value is
+ negative, it indicates an error. If the status value is
+ zero, it indicates no error. If the status value is
+ positive, it indicates a warning. The callbackData parameter
+ contains the value you passed in the callbackData parameter
+ of this function.
+
+ :param options : {int, 'sync'}
+
+ Use this parameter to set certain options. You can
+ combine flags with the bitwise-OR operator ('|') to set
+ multiple options. Pass a value of zero if no options need to
+ be set.
+
+ 'sync' - The callback function is called in the thread which
+ registered the event. In order for the callback to occur,
+ you must be processing messages. If you do not set this
+ flag, the callback function is called in a DAQmx thread by
+ default.
+
+ Note: If you are receiving synchronous events faster than
+ you are processing them, then the user interface of your
+ application might become unresponsive.
+
+ :param cb_data:
+
+ A value that you want DAQmx to pass to the callback function
+ as the function data parameter. Do not pass the address of a
+ local variable or any other variable that might not be valid
+ when the function is executed.
+
+ See also
+
+ register_signal_event, register_every_n_samples_event
+ """
+ if options=='sync':
+ options = Constants.Val_SynchronousEventCallbacks
+
+ if func is None:
+ c_func = None
+ else:
+ if self._register_done_event_cache is not None:
+ self.register_done_event(None, options=options, cb_data=cb_data)
+ # TODO: check the validity of func signature
+ c_func = DoneEventCallback_map[self.CHANNEL_TYPE](func)
+ self._register_done_event_cache = c_func
+
+ self.lib.RegisterDoneEvent(uInt32(options), c_func, cb_data)
+
+ def operation_direction(self):
+ return 'input' if self.CHANNEL_TYPE else 'output'
+
+ _register_signal_event_cache = None
+
+ @Action(values=(None, _SIGNAL_TYPES, None, None))
+ def register_signal_event(self, func, signal, options=0, cb_data=None):
+ """Registers a callback function to receive an event when the
+ specified hardware event occurs.
+
+ When you stop a task explicitly any pending events are
+ discarded. For example, if you call DAQmxStopTask then you do
+ not receive any pending events.
+
+ :param func:
+
+ The function that you want DAQmx to call when the event
+ occurs. The function you pass in this parameter must have the
+ following prototype::
+
+ def func(task, signalID, cb_data):
+ ...
+ return 0
+
+ Upon entry to the callback, the task parameter contains the
+ handle to the task on which the event occurred. The signalID
+ parameter contains the value you passed in the signal
+ parameter of this function. The cb_data parameter contains
+ the value you passed in the cb_data parameter of this
+ function.
+
+ :param signal: {'sample_clock', 'sample_complete', 'change_detection', 'counter_output'}
+
+ The signal for which you want to receive results:
+
+ 'sample_clock' - Sample clock
+ 'sample_complete' - Sample complete event
+ 'change_detection' - Change detection event
+ 'counter_output' - Counter output event
+
+ :param options:
+
+ :param cb_data:
+
+ See also: register_done_event, register_every_n_samples_event
+ """
+
+ if options == 'sync':
+ options = Constants.Val_SynchronousEventCallbacks
+
+ if func is None:
+ c_func = None
+ else:
+ if self._register_signal_event_cache is not None:
+ self._register_signal_event(None, signal=signal, options=options, cb_data=cb_data)
+ # TODO: check the validity of func signature
+ c_func = SignalEventCallback_map[self.CHANNEL_TYPE](func)
+ self._register_signal_event_cache = c_func
+ self.lib.RegisterSignalEvent(signal, uInt32(options), c_func, cb_data)
+
+
+ @Action(values=(str, str, _SAMPLE_MODES, None))
+ def configure_timing_change_detection(self, rising_edge_channel='', falling_edge_channel='',
+ sample_mode='continuous', samples_per_channel=1000):
+ """Configures the task to acquire samples on the rising and/or
+ falling edges of the lines or ports you specify.
+ """
+
+ self.lib.CfgChangeDetectionTiming(rising_edge_channel, falling_edge_channel,
+ sample_mode, samples_per_channel)
+
+
+ @Action(values=(_SAMPLE_MODES, None))
+ def configure_timing_handshaking(self, sample_mode='continuous', samples_per_channel=1000):
+ """Determines the number of digital samples to acquire or
+ generate using digital handshaking between the device and a
+ peripheral device.
+ """
+ self.samples_per_channel = samples_per_channel
+ self.sample_mode = sample_mode
+ self.lib.CfgHandshakingTiming(sample_mode, samples_per_channel)
+
+ @Action(values=(_SAMPLE_MODES, None))
+ def configure_timing_implicit(self, sample_mode='continuous', samples_per_channel=1000):
+ """Sets only the number of samples to acquire or generate without
+ specifying timing. Typically, you should use this function
+ when the task does not require sample timing, such as tasks
+ that use counters for buffered frequency measurement, buffered
+ period measurement, or pulse train generation.
+ """
+ self.samples_per_channel = samples_per_channel
+ self.sample_mode = sample_mode
+ self.lib.CfgImplicitTiming(self, sample_mode, samples_per_channel)
+
+ @Action(values=(str, None, _EDGE_TYPES, _SAMPLE_MODES, None))
+ def configure_timing_sample_clock(self, source='on_board_clock', rate=1, active_edge='rising',
+ sample_mode='continuous', samples_per_channel=1000):
+ """Set the source of the Sample Clock, the rate of the Sample
+ Clock, and the number of samples to acquire or generate.
+
+ :param source:
+
+ The source terminal of the Sample Clock. To use the
+ internal clock of the device, use None or use
+ 'OnboardClock'.
+
+ :param rate:
+
+ The sampling rate in samples per second. If you use an
+ external source for the Sample Clock, set this value to
+ the maximum expected rate of that clock.
+
+ :param active_edge:
+
+ Specifies on which edge of the clock to
+ acquire or generate samples:
+
+ 'rising' - Acquire or generate samples on the rising edges
+ of the Sample Clock.
+
+ 'falling' - Acquire or generate samples on the falling
+ edges of the Sample Clock.
+
+ :param sample_mode: {'finite', 'continuous', 'hwtimed'}
+
+ Specifies whether the task acquires or
+ generates samples continuously or if it acquires or
+ generates a finite number of samples:
+
+ 'finite' - Acquire or generate a finite number of samples.
+
+ 'continuous' - Acquire or generate samples until you stop the task.
+
+ 'hwtimed' - Acquire or generate samples continuously
+ using hardware timing without a buffer. Hardware timed
+ single point sample mode is supported only for the
+ sample clock and change detection timing types.
+
+ :param samples_per_channel:
+
+ The number of samples to acquire or generate for each
+ channel in the task if `sample_mode` is 'finite'. If
+ sample_mode is 'continuous', NI-DAQmx uses this value to
+ determine the buffer size.
+ """
+ if source == 'on_board_clock':
+ source = None
+ self.samples_per_channel = samples_per_channel
+ self.sample_mode = sample_mode
+ self.lib.CfgSampClkTiming(source, float64(rate), active_edge, sample_mode, samples_per_channel)
+
+ def configure_timing_burst_handshaking_export_clock(self, *args, **kws):
+ """
+ Configures when the DAQ device transfers data to a peripheral
+ device, using the DAQ device's onboard sample clock to control
+ burst handshaking timing.
+ """
+ raise NotImplementedError
+
+ def configure_timing_burst_handshaking_import_clock(self, *args, **kws):
+ """
+ Configures when the DAQ device transfers data to a peripheral
+ device, using an imported sample clock to control burst
+ handshaking timing.
+ """
+ raise NotImplementedError
+
+ @Action(values=(str, _SLOPE_TYPES, None))
+ def configure_trigger_analog_edge_start(self, source, slope='rising', level=1.0):
+ """
+ Configures the task to start acquiring or generating samples
+ when an analog signal crosses the level you specify.
+
+ :param source:
+
+ The name of a channel or terminal where there is an analog
+ signal to use as the source of the trigger. For E Series
+ devices, if you use a channel name, the channel must be the
+ first channel in the task. The only terminal you can use for
+ E Series devices is PFI0.
+
+ :param slope:
+
+ Specifies on which slope of the signal to start acquiring or
+ generating samples when the signal crosses trigger level:
+
+ 'rising' - Trigger on the rising slope of the signal.
+
+ 'falling' - Trigger on the falling slope of the signal.
+
+ :param level:
+
+ The threshold at which to start acquiring or generating
+ samples. Specify this value in the units of the measurement
+ or generation. Use trigger slope to specify on which slope
+ to trigger at this threshold.
+ """
+
+ self.lib.CfgAnlgEdgeStartTrig(source, slope, level)
+
+ @Action(values=(str, _WHEN_WINDOW, None, None))
+ def configure_trigger_analog_window_start(self, source, when='entering', top=1.0, bottom=-1.0):
+ """Configure the task to start acquiring or generating samples
+ when an analog signal enters or leaves a range you specify.
+
+ :param source:
+
+ The name of a virtual channel or terminal where there
+ is an analog signal to use as the source of the trigger.
+
+ For E Series devices, if you use a virtual channel, it must
+ be the first channel in the task. The only terminal you can
+ use for E Series devices is PFI0.
+
+ :param when : {'entering', 'leaving'}
+
+ Specifies whether the task starts measuring or generating
+ samples when the signal enters the window or when it leaves
+ the window. Use `bottom` and `top` to specify the limits of
+ the window.
+
+ :param top : float
+
+ The upper limit of the window. Specify this value in the
+ units of the measurement or generation.
+
+ :param bottom : float
+
+ The lower limit of the window. Specify this value in the
+ units of the measurement or generation.
+ """
+ self.lib.CfgAnlgWindowStartTrig(self, source, when, top, bottom)
+
+ @Action(values=(str, _EDGE_TYPES))
+ def configure_trigger_digital_edge_start(self, source, edge='rising'):
+ """Configure the task to start acquiring or generating samples
+ on a rising or falling edge of a digital signal.
+
+ :param source:
+
+ The name of a terminal where there is a digital signal to
+ use as the source of the trigger.
+
+ :param edge: {'rising', 'falling'}
+
+ Specifies on which edge of a digital signal to start
+ acquiring or generating samples: rising or falling edge(s).
+ """
+
+ self.lib.CfgDigEdgeStartTrig(self, source, edge)
+
+ @Action(values=(str, str, _WHEN_MATCH))
+ def configure_trigger_digital_pattern_start(self, source, pattern, match=True):
+ """Configure a task to start acquiring or generating samples
+ when a digital pattern is matched.
+
+ :param source:
+
+ Specifies the physical channels to use for pattern
+ matching. The order of the physical channels determines the
+ order of the pattern. If a port is included, the order of
+ the physical channels within the port is in ascending order.
+
+ :param pattern:
+
+ Specifies the digital pattern that must be met for the
+ trigger to occur.
+
+ :param when: {'matches', 'does_not_match'}
+
+ Specifies the conditions under which the trigger
+ occurs: pattern matches or not.
+ """
+ self.lib.CfgDigPatternStartTrig(self, source, pattern, match)
+
+ def configure_trigger_disable_start(self):
+ """
+ Configures the task to start acquiring or generating samples
+ immediately upon starting the task.
+
+ Returns
+ -------
+
+ success_status : bool
+ """
+ self.lib.DisableStartTrig()
+
+ @Action(values=(str, _SLOPE_TYPES, None, None))
+ def configure_analog_edge_reference_trigger(self, source, slope='rising',level=1.0, pre_trigger_samps=0):
+ """Configure the task to stop the acquisition when the device
+ acquires all pretrigger samples, an analog signal reaches the
+ level you specify, and the device acquires all post-trigger samples.
+
+ :param source:
+
+ The name of a channel or terminal where there is an analog
+ signal to use as the source of the trigger. For E Series
+ devices, if you use a channel name, the channel must be the
+ first channel in the task. The only terminal you can use for
+ E Series devices is PFI0.
+
+ :param slope:
+
+ Specifies on which slope of the signal to start acquiring or
+ generating samples when the signal crosses trigger level:
+
+ 'rising' - Trigger on the rising slope of the signal.
+
+ 'falling' - Trigger on the falling slope of the signal.
+
+ :param level:
+ The threshold at which to start acquiring or generating
+ samples. Specify this value in the units of the measurement
+ or generation. Use trigger slope to specify on which slope
+ to trigger at this threshold.
+
+ :param pre_trigger_samps:
+
+ The minimum number of samples per channel to acquire before
+ recognizing the Reference Trigger. The number of posttrigger
+ samples per channel is equal to number of samples per channel
+ in the NI-DAQmx Timing functions minus pretriggerSamples.
+ """
+
+ self.lib.CfgAnlgEdgeRefTrig(source, slope, level, pre_trigger_samps)
+
+
+ @Feat(values=(str, _WHEN_WINDOW, None, None, None))
+ def configure_analog_window_reference_trigger(self, source, when='entering',top=1.0, bottom=1.0, pre_trigger_samps=0):
+ """Configure the task to stop the acquisition when the device
+ acquires all pretrigger samples, an analog signal enters or
+ leaves a range you specify, and the device acquires all
+ post-trigger samples.
+
+
+ :param source : str
+
+ The name of a channel or terminal where there is an analog
+ signal to use as the source of the trigger. For E Series
+ devices, if you use a channel name, the channel must be the
+ first channel in the task. The only terminal you can use for
+ E Series devices is PFI0.
+
+ :param when : {'entering', 'leaving'}
+
+ Specifies whether the Reference Trigger occurs when the signal
+ enters the window or when it leaves the window. Use
+ bottom and top to specify the limits of the window.
+
+ 'entering' - Trigger when the signal enters the window.
+
+ 'leaving' - Trigger when the signal leaves the window.
+
+ :param top : float
+
+ The upper limit of the window. Specify this value in the
+ units of the measurement or generation.
+
+ :param bottom : float
+
+ The lower limit of the window. Specify this value in the
+ units of the measurement or generation.
+
+ :param pre_trigger_samps : uint32
+
+ The minimum number of samples per channel to acquire before
+ recognizing the Reference Trigger. The number of posttrigger
+ samples per channel is equal to number of samples per channel
+ in the NI-DAQmx Timing functions minus pretriggerSamples.
+ """
+
+ self.lib.CfgAnlgWindowRefTrig(source, when, top, bottom, pre_trigger_samps)
+
+ @Action(values=(str, _SLOPE_TYPES, None))
+ def configure_digital_edge_reference_trigger(self, source, slope='rising', pre_trigger_samps=0):
+ """Configures the task to stop the acquisition when the device
+ acquires all pretrigger samples, detects a rising or falling
+ edge of a digital signal, and acquires all posttrigger samples.
+
+ :param source:
+
+ The name of a channel or terminal where there is an analog
+ signal to use as the source of the trigger. For E Series
+ devices, if you use a channel name, the channel must be the
+ first channel in the task. The only terminal you can use for
+ E Series devices is PFI0.
+
+ :param slope:
+
+ Specifies on which slope of the signal to start acquiring or
+ generating samples when the signal crosses trigger level:
+
+ 'rising' - Trigger on the rising slope of the signal.
+
+ 'falling' - Trigger on the falling slope of the signal.
+
+ :param pre_trigger_samps:
+
+ The minimum number of samples per channel to acquire before
+ recognizing the Reference Trigger. The number of posttrigger
+ samples per channel is equal to number of samples per channel
+ in the NI-DAQmx Timing functions minus pretriggerSamples.
+ """
+ if not source.startswith('/'): # source needs to start with a '/' TODO WHY?
+ source = '/' + source
+ self.lib.CfgDigEdgeRefTrig(source, slope, pre_trigger_samps)
+
+
+ @Action(values=(str, str, _WHEN_MATCH, None))
+ def configure_digital_pattern_reference_trigger(self, source, pattern, match=True, pre_trigger_samps=0):
+ """Configure the task to stop the acquisition when the device
+ acquires all pretrigger samples, matches or does not match
+ a digital pattern, and acquires all posttrigger samples.
+
+ :param source:
+
+ The name of a channel or terminal where there is an analog
+ signal to use as the source of the trigger. For E Series
+ devices, if you use a channel name, the channel must be the
+ first channel in the task. The only terminal you can use for
+ E Series devices is PFI0.
+
+ :param pattern:
+
+ Specifies the digital pattern that must be met for the trigger to occur.
+
+ :param match: Specifies if the conditions under which the trigger occurs
+
+ 'match' - Trigger when the signal matches the pattern
+
+ 'nomatch' - Trigger when the signal does NOT match the pattern
+
+ :param pre_trigger_samps : uint32
+
+ The minimum number of samples per channel to acquire before
+ recognizing the Reference Trigger. The number of posttrigger
+ samples per channel is equal to number of samples per channel
+ in the NI-DAQmx Timing functions minus pretriggerSamples.
+ """
+
+ if not source.startswith('/'): # source needs to start with a '/'
+ source = '/' + source
+
+ self.lib.CfgDigPatternRefTrig(self, source, pattern, match, pre_trigger_samps)
+
+ @Action()
+ def disable_reference_trigger(self):
+ """
+ Disables reference triggering for the measurement or generation.
+
+ Returns
+ -------
+
+ success_status : bool
+ """
+ return self.lib.DisableRefTrig(self) == 0
+
+
+ #TODO CHECK
+ def set_buffer(self, samples_per_channel):
+ """
+ Overrides the automatic I/O buffer allocation that NI-DAQmx performs.
+
+ Parameters
+ ----------
+
+ samples_per_channel : int
+
+ The number of samples the buffer can hold for each channel
+ in the task. Zero indicates no buffer should be
+ allocated. Use a buffer size of 0 to perform a
+ hardware-timed operation without using a buffer.
+
+ Returns
+ -------
+
+ success_status : bool
+ """
+ #channel_io_type = self.channel_io_type
+ #return CALL('Cfg%sBuffer' % (channel_io_type.title()), self, uInt32(samples_per_channel)) == 0
+ pass
+
+ @Feat(units='Hz')
+ def sample_clock_rate(self):
+ """Sample clock rate.
+
+ Set to None to reset.
+ """
+
+ err, value = self.lib.GetSampClkRate(self, RetValue('f64'))
+ return value
+
+ @sample_clock_rate.setter
+ def sample_clock_rate(self, value):
+ if value is None:
+ self.lib.ResetSampClkRate()
+ else:
+ self.lib.SetSampClkRate(value)
+
+ @Feat()
+ def convert_clock_rate(self):
+ """Convert clock rate.
+
+ The rate at which to clock the analog-to-digital
+ converter. This clock is specific to the analog input section
+ of multiplexed devices.
+
+ By default, NI-DAQmx selects the maximum convert rate
+ supported by the device, plus 10 microseconds per channel
+ settling time. Other task settings, such as high channel
+ counts or setting Delay, can result in a faster default
+ convert rate.
+
+ Set to None to reset
+ """
+ err, value = self.lib.GetAIConvRate(RetValue('f64'))
+ return value
+
+ @convert_clock_rate.setter
+ def convert_clock_rate(self, value):
+ if value is None:
+ self.lib.ResetAIConvRate()
+ else:
+ self.lib.SetAIConvRate(value)
+
+ def sample_clock_max_rate(self):
+ """Maximum Sample Clock rate supported by the task,
+ based on other timing settings. For output tasks, the maximum
+ Sample Clock rate is the maximum rate of the DAC. For input
+ tasks, NI-DAQmx calculates the maximum sampling rate
+ differently for multiplexed devices than simultaneous sampling
+ devices.
+
+ For multiplexed devices, NI-DAQmx calculates the maximum
+ sample clock rate based on the maximum AI Convert Clock rate
+ unless you set Rate. If you set that property, NI-DAQmx
+ calculates the maximum sample clock rate based on that
+ setting. Use Maximum Rate to query the maximum AI Convert
+ Clock rate. NI-DAQmx also uses the minimum sample clock delay
+ to calculate the maximum sample clock rate unless you set
+ Delay.
+
+ For simultaneous sampling devices, the maximum Sample Clock
+ rate is the maximum rate of the ADC.
+ """
+ err, value = self.lib.GetSampClkMaxRate(RetValue('f64'))
+ return value
+
+ # Not implemented:
+ # DAQmxReadBinary*, DAQmxReadCounter*, DAQmxReadDigital*
+ # DAQmxGetNthTaskReadChannel, DAQmxReadRaw
+ # DAQmxWrite*
+ # DAQmxExportSignal
+ # DAQmxCalculateReversePolyCoeff, DAQmxCreateLinScale
+ # DAQmxWaitForNextSampleClock
+ # DAQmxSwitch*
+ # DAQmxConnectTerms, DAQmxDisconnectTerms, DAQmxTristateOutputTerm
+ # DAQmxResetDevice
+ # DAQmxControlWatchdog*
+
+ # DAQmxAOSeriesCalAdjust, DAQmxESeriesCalAdjust, DAQmxGet*,
+ # DAQmxMSeriesCalAdjust, DAQmxPerformBridgeOffsetNullingCal, DAQmxRestoreLastExtCalConst
+ # DAQmxSelfCal, DAQmxSetAIChanCalCalDate, DAQmxSetAIChanCalExpDate, DAQmxSSeriesCalAdjust
+ # External Calibration, DSA Calibration, PXI-42xx Calibration, SCXI Calibration
+ # Storage, TEDS
+ # DAQmxSetAnalogPowerUpStates, DAQmxSetDigitalPowerUpStates
+ # DAQmxGetExtendedErrorInfo
+
+ @Feat(values={True: Constants.Val_AllowRegen, False: Constants.Val_DoNotAllowRegen})
+ def regeneration_enabled(self):
+ """Generating the same data more than once is allowed.
+
+ Set to None to reset.
+ """
+ err, value = self.lib.GetWriteRegenMode(RetValue('i32'))
+ return value
+
+ @regeneration_enabled.setter
+ def regeneration_enabled(self, value):
+ if value is None:
+ self.lib.ResetWriteRegenMode()
+ else:
+ self.lib.SetWriteRegenMode(value)
+
+ #TODO CHECK
+ @Feat(values={'digital_edge': Constants.Val_DigEdge, 'disable': Constants.Val_None, None: None})
+ def arm_start_trigger_type(self):
+ """the type of trigger to use to arm the task for a
+ Start Trigger. If you configure an Arm Start Trigger, the task
+ does not respond to a Start Trigger until the device receives
+ the Arm Start Trigger.
+
+ Use None to reset
+ """
+
+ err, value = self.lib.GetArmStartTrigType(RetValue('i32'))
+ return value
+
+ @arm_start_trigger_type.setter
+ def arm_start_trigger_type(self, trigger_type):
+ if trigger_type is None:
+ self.lib.ResetArmStartTrigType()
+ else:
+ self.lib.SetArmStartTrigType(trigger_type)
+
+
+ @Feat()
+ def arm_start_trigger_source(self):
+ """Rhe name of a terminal where there is a digital
+ signal to use as the source of the Arm Start Trigger
+
+ Use None to Reset
+ """
+ err, value = self.lib.GetDigEdgeArmStartTrigSrc(RetStr(default_buf_size))
+ return value
+
+ @arm_start_trigger_source.setter
+ @arm_start_trigger_source.setter
+ def arm_start_trigger_source(self, source):
+ source = str (source)
+ if source is None:
+ self.lib.ResetDigEdgeArmStartTrigSrc()
+ else:
+ self.lib.SetDigEdgeArmStartTrigSrc(source)
+
+ @Feat(values={None: None}.update(_EDGE_TYPES))
+ def arm_start_trigger_edge(self):
+ """on which edge of a digital signal to arm the task
+ for a Start Trigger
+
+ Set to None to reset
+ """
+ err, value = self.lib.GetDigEdgeArmStartTrigEdge(RetValue('i32'))
+
+ @arm_start_trigger_edge.setter
+ def arm_start_trigger_edge(self, edge):
+ if edge is None:
+ self.lib.ResetDigEdgeArmStartTrigEdge()
+ else:
+ self.lib.SetDigEdgeArmStartTrigEdge(edge)
+
+ @Feat(values={None: None}.update(_TRIGGER_TYPES))
+ def pause_trigger_type(self):
+ """The type of trigger to use to pause a task.
+
+ Set to None to Reset
+ """
+ err, value = self.lib.GetPauseTrigType(RetValue('i32'))
+ return value
+
+ @pause_trigger_type.setter
+ def pause_trigger_type(self, trigger_type):
+ if trigger_type is None:
+ self.lib.ResetPauseTrigType()
+ else:
+ self.lib.SetPauseTrigType(trigger_type)
+
+ @Feat()
+ def pause_trigger_source(self):
+ """The name of a virtual channel or terminal where
+ there is an analog signal to use as the source of the trigger.
+
+ For E Series devices, if you use a channel name, the channel
+ must be the only channel in the task. The only terminal you
+ can use for E Series devices is PFI0.
+ """
+
+ type = self.pause_trigger_type
+ if type == 'digital_level':
+ fun = self.lib.GetDigLvlPauseTrigSrc
+ elif type == 'analog_level':
+ fun = self.lib.GetAnlgLvlPauseTrigSrc
+ elif type == 'analog_window':
+ fun = self.lib.GetAnlgWinPauseTrigSrc
+ else:
+ raise InstrumentError('Pause trigger type is not specified')
+
+ err, value = fun(*RetStr(default_buf_size))
+ return value
+
+ @pause_trigger_source.setter
+ def pause_trigger_source(self, source):
+
+ type = self.pause_trigger_type
+ if type == 'digital_level':
+ fun = self.lib.SetDigLvlPauseTrigSrc
+ elif type == 'analog_level':
+ fun = self.lib.SetAnlgLvlPauseTrigSrc
+ elif type == 'analog_window':
+ fun = self.lib.SetAnlgWinPauseTrigSrc
+ else:
+ raise InstrumentError('Pause trigger type is not specified')
+
+ fun(source)
+
+ @Feat()
+ def pause_trigger_when(self):
+ """
+ Specifies whether the task pauses above or below the threshold
+ you specify with Level.
+
+ Specifies whether the task pauses while the trigger signal is
+ inside or outside the window you specify with Bottom and Top.
+
+ Specifies whether the task pauses while the signal is high or
+ low.
+
+ Set To None to reset
+ """
+ type = self.pause_trigger_type
+ if type == 'digital_level':
+ fun = self.lib.SetDigLvlPauseTrigWhen
+ convert = _WHEN_TRIGGER_DIG
+ elif type == 'analog_level':
+ fun = self.lib.SetAnlgLvlPauseTrigWhen
+ convert = _WHEN_TRIGGER_ALVL
+ elif type == 'analog_window':
+ fun = self.lib.SetAnlgWinPauseTrigWhen
+ convert = _WHEN_TRIGGER_AWIN
+ else:
+ raise InstrumentError('Pause trigger type is not specified')
+
+ err, val = fun(RetValue('i32'))
+ for key, value in convert.items():
+ if key == val:
+ return val
+ else:
+ raise ValueError(val)
+
+ @pause_trigger_when.setter
+ def pause_trigger_when (self, when=None):
+
+ if when is None:
+ self.lib.ResetDigLvlPauseTrigWhen()
+ return
+
+ type = self.pause_trigger_type
+ if type == 'digital_level':
+ fun = self.lib.SetDigLvlPauseTrigWhen
+ convert = _WHEN_TRIGGER_DIG
+ elif type == 'analog_level':
+ fun = self.lib.SetAnlgLvlPauseTrigWhen
+ convert = _WHEN_TRIGGER_ALVL
+ elif type == 'analog_window':
+ fun = self.lib.SetAnlgWinPauseTrigWhen
+ convert = _WHEN_TRIGGER_AWIN
+ else:
+ raise InstrumentError('Pause trigger type is not specified')
+
+ fun(convert[when])
+
+ def read_current_position (self):
+ """Samples per channel the current position in the buffer.
+ """
+ err, value = self.lib.GetReadCurrReadPos(*RetValue('u64'))
+ return value
+
+ def samples_per_channel_available(self):
+ """The number of samples available to read per channel.
+
+ This value is the same for all channels in the task.
+ """
+ err, value = self.lib.GetReadAvailSampPerChan(*RetValue('u32'))
+ return value
+
+ def samples_per_channel_acquired(self):
+ """The total number of samples acquired by each channel.
+
+ NI-DAQmx returns a single value because this value is
+ the same for all channels.
+ """
+ err, value = self.lib.GetReadTotalSampPerChanAcquired(*RetValue('u32'))
+ return value
+
+ @Action(units='seconds')
+ def wait_until_done(self, timeout=-1):
+ """Wait for the measurement or generation to complete. Use this
+ function to ensure that the specified operation is complete
+ before you stop the task.
+
+ :param timeout: The maximum amount of time, in seconds, to wait for the
+ measurement or generation to complete. The function returns
+ an error if the time elapses before the measurement or
+ generation is complete.
+
+ A value of -1 (Constants.Val_WaitInfinitely) means to wait
+ indefinitely.
+
+ If you set timeout to 0, the function checks once and
+ returns an error if the measurement or generation is not
+ done.
+ """
+ if timeout < 0:
+ timeout = Constants.Val_WaitInfinitely
+ return self.lib.WaitUntilTaskDone(timeout)
+
+
+class Channel(_Base):
+ """A virtual channel is a collection of settings such as a name,
+ a physical channel, input terminal connections, the type of measurement
+ or generation, and can include scaling information.
+ """
+
+ IO_TYPE = None
+
+ def __init__(self, task=None, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self._task = None
+ if task == 'create':
+ task = Task.typed_task(self.IO_TYPE)()
+ print(task)
+ self.task = task
+
+ @property
+ def task(self):
+ return self._task
+
+ @task.setter
+ def task(self, value):
+ if not self._task is None:
+ raise Exception
+ self._task = value
+ if value is not None:
+ self.log_debug('Creating channel {} with {}'.format(self.CREATE_FUN, self._create_args))
+ value.execute_fun(self.CREATE_FUN, *self._create_args)
+
+ def _preprocess_args(self, name, *args):
+ """Injects device_name to all call to the library
+ """
+ return super()._preprocess_args(name, *((self.task.task_handle, self.name) + args))
+
+ @Feat(read_once=True, values=_CHANNEL_TYPES)
+ def io_type(self):
+ """The type of the virtual channel.
+ """
+ err, value = self.lib.GetChanType(RetValue('i32'))
+ return value
+
+ @Feat(read_once=True)
+ def physical_channel_name(self,):
+ """Name of the physical channel upon which this virtual channel is based.
+ """
+ err, value = self.lib.GetPhysicalChanName(*RetStr(default_buf_size))
+ return value
+
+ def operation_direction(self):
+ return 'input' if self.CHANNEL_TYPE else 'output'
+
+ @Feat(read_once=True)
+ def is_global(self):
+ """Indicates whether the channel is a global channel.
+ """
+ err, value = self.lib.GetChanIsGlobal(RetValue('u32'))
+ return value
+
+ # TODO: DAQmx*ChanDescr
+
+ @Feat()
+ def buffer_size_on_board(self):
+ """The number of samples the I/O buffer can hold for each
+ channel in the task.
+
+ If on_board is True then specifies in samples per channel the
+ size of the onboard I/O buffer of the device.
+
+ See also
+ --------
+ set_buffer_size, reset_buffer_size
+ """
+ fun = self.__get_fun('GetBuf{}OnbrdBufSize')
+ err, value = fun(RetValue('u32'))
+ return value
+
+ @Feat()
+ def buffer_size(self):
+ """The number of samples the I/O buffer can hold for each
+ channel in the task.
+
+ Set to 0 to perform a hardware-timed operation without
+ using a buffer. Setting this property overrides the automatic I/O
+ buffer allocation that NI-DAQmx performs.
+
+ Set to None to reset.
+ """
+ fun = self.__get_fun('GetBuf{}BufSize')
+ err, value = fun(RetValue('u32'))
+ return value
+
+ @buffer_size.setter
+ def buffer_size(self, size):
+ if size is None:
+ fun = self.__get_fun('ResetBuf{}BufSize')
+ err = fun()
+ else:
+ fun = self.__get_fun('SetBuf{}BufSize')
+ err = fun(size)
+
+ @Feat()
+ def max(self):
+ """Maximum value value.
+
+ Set to None to reset.
+ """
+ fun = self.__get_fun('Get{}Max')
+ err, value = fun(RetValue('f64'))
+ return value
+
+ @max.setter
+ def max(self, value):
+ if value is None:
+ fun = self.__get_fun('Reset{}Max')
+ err = fun()
+ else:
+ fun = self.__get_fun('Set{}Max')
+ err = fun(value)
+
+ @Feat()
+ def min(self):
+ """Minimum value value.
+
+ Set to None to reset.
+ """
+ fun = self.__get_fun('Get{}Min')
+ err, value = fun(RetValue('f64'))
+ return value
+
+ @min.setter
+ def min(self, value):
+ if value is None:
+ fun = self.__get_fun('Reset{}Min')
+ err = fun()
+ else:
+ fun = self.__get_fun('Set{}Min')
+ err = fun(value)
+
+ @Feat()
+ def range_high(self):
+ """The upper limit of the input range of the
+ device. This value is in the native units of the device. On E
+ Series devices, for example, the native units is volts.
+
+ Set to None to reset.
+ """
+ fun = self.__get_fun('Get{}RngHigh')
+ err, value = fun(RetValue('f64'))
+ return value
+
+ @range_high.setter
+ def range_high(self, value):
+ if value is None:
+ fun = self.__get_fun('Reset{}RngHigh')
+ err = fun()
+ else:
+ fun = self.__get_fun('Set{}RngHigh')
+ err = fun(value)
+
+ @Feat()
+ def range_low(self):
+ """The lower limit of the input range of the
+ device. This value is in the native units of the device. On E
+ Series devices, for example, the native units is volts.
+
+ Set to None to reset.
+ """
+ fun = self.__get_fun('Get{}RngLow')
+ err, value = fun(RetValue('f64'))
+ return value
+
+ @range_low.setter
+ def range_low(self, value):
+ if value is None:
+ fun = self.__get_fun('Reset{}RngLow')
+ err = fun()
+ else:
+ fun = self.__get_fun('Set{}RngLow')
+ err = fun(value)
+
+ @Feat()
+ def gain(self):
+ """Gain factor to apply to the channel.
+
+ Set to None to reset.
+ """
+ fun = self.__get_fun('Get{}Gain')
+ err, value = fun(RetValue('f64'))
+ return value
+
+ @gain.setter
+ def gain(self, value):
+ if value is None:
+ fun = self.__get_fun('Reset{}Gain')
+ err = fun()
+ else:
+ fun = self.__get_fun('Set{}Gain')
+ err = fun(value)
+
+ @Feat()
+ def number_of_lines(self):
+ """Number of digital lines in the channel.
+ """
+ err, value = self.__get_fun('Get{}NumLines')(RetValue('u32'))
+ return value
+
+
+ def get_units(self, channel_name):
+ """
+ Specifies in what units to generate voltage on the
+ channel. Write data to the channel in the units you select.
+
+ Specifies in what units to generate current on the
+ channel. Write data to the channel is in the units you select.
+
+ See also
+ --------
+ set_units, reset_units
+ """
+ channel_name = str(channel_name)
+ mt = self.get_measurment_type(channel_name)
+ channel_type = self.channel_type
+ if mt=='voltage':
+ d = int32(0)
+ CALL('Get%sVoltageUnits' % (channel_type), self, channel_name, ctypes.byref(d))
+ units_map = {Contants.Val_Volts:'volts',
+ #Constants.Val_FromCustomScale:'custom_scale',
+ #Constants.Val_FromTEDS:'teds',
+ }
+ return units_map[d.value]
+ raise NotImplementedError('{} {}'.format(channel_name, mt))
+
+
+ @Feat(values={'none': Constants.Val_None, 'once': Constants.Val_Once,
+ 'every_sample': Constants.Val_EverySample})
+ def auto_zero_mode(self):
+ """When to measure ground. NI-DAQmx subtracts the
+ measured ground voltage from every sample.
+
+ Set to None to reset.
+ """
+ fun = self.__get_fun('Get{}AutoZeroMode')
+ err, value = fun(RetValue('i32'))
+ return value
+
+ @auto_zero_mode.setter
+ def auto_zero_mode(self, value):
+ if value is None:
+ fun = self.__get_fun('Reset{}AutoZeroMode')
+ err = fun()
+ else:
+ fun = self.__get_fun('Set{}AutoZeroMode')
+ err = fun(value)
+
+ @Feat(values={'dma': Constants.Val_DMA, 'interrupts': Constants.Val_Interrupts,
+ 'programmed_io': Constants.Val_ProgrammedIO,
+ 'usb': Constants.Val_USBbulk})
+ def data_transfer_mode(self):
+ """When to measure ground. NI-DAQmx subtracts the
+ measured ground voltage from every sample.
+
+ Set to None to reset.
+ """
+ fun = self.__get_fun('Get{}DataXferMech')
+ err, value = fun(RetValue('i32'))
+ return value
+
+ @data_transfer_mode.setter
+ def data_transfer_mode(self, value):
+ if value is None:
+ fun = self.__get_fun('Reset{}DataXferMech')
+ err = fun()
+ else:
+ fun = self.__get_fun('Set{}DataXferMech')
+ err = fun(value)
+
+ @Feat(values={True: 1, False: 0, None: None})
+ def duplicate_count_prevention_enabled(self):
+ """Duplicate count prevention enabled.
+
+ Set to None to Reset
+ """
+ err, value = self.lib.GetCIDupCountPrevent(RetValue('u32'))
+ return value != 0
+
+ @duplicate_count_prevention_enabled.setter
+ def duplicate_count_prevention_enabled(self, value):
+ if value is None:
+ err = self.lib.ResetCIDupCountPrevent()
+ else:
+ err = self.lib.SetCIDupCountPrevent(value)
+
+ @Feat()
+ def timebase_rate(self):
+ """Frequency of the counter timebase (Hz)
+
+ TODO Can I put units and still None
+
+ Set to None to reset.
+ See also
+ --------
+ set_timebase_rate, reset_timebase_rate
+ """
+ err, value = self.lib.GetCICtrTimebaseRate(RetValue('f64'))
+ return value
+
+ @timebase_rate.setter
+ def timebase_rate(self, value):
+ if value is None:
+ err = self.lib.ResetCICtrTimebaseRate()
+ else:
+ err = self.lib.SetCICtrTimebaseRate(value)
+
+
+ @Feat(None)
+ def terminal_pulse (self, terminal):
+ """Terminal to generate pulses.
+ """
+ self.lib.SetCOPulseTerm(terminal)
+
+
+ @Feat(None)
+ def terminal_count_edges(self, channel, terminal):
+ """Input terminal of the signal to measure.
+ """
+ self.lib.SetCICountEdgesTerm(terminal)
+"""
+
+DoneEventCallback_map = dict(AI=ctypes.CFUNCTYPE (int32, AnalogInputTask, int32, void_p),
+ AO=ctypes.CFUNCTYPE (int32, AnalogOutputTask, int32, void_p),
+ DI=ctypes.CFUNCTYPE (int32, DigitalInputTask, int32, void_p),
+ DO=ctypes.CFUNCTYPE (int32, DigitalOutputTask, int32, void_p),
+ CI=ctypes.CFUNCTYPE (int32, CounterInputTask, int32, void_p),
+ CO=ctypes.CFUNCTYPE (int32, CounterOutputTask, int32, void_p),
+ )
+EveryNSamplesEventCallback_map = dict(AI=ctypes.CFUNCTYPE (int32, AnalogInputTask, int32, uInt32, void_p),
+ AO=ctypes.CFUNCTYPE (int32, AnalogOutputTask, int32, uInt32, void_p),
+ DI=ctypes.CFUNCTYPE (int32, DigitalInputTask, int32, uInt32, void_p),
+ DO=ctypes.CFUNCTYPE (int32, DigitalOutputTask, int32, uInt32, void_p),
+ CI=ctypes.CFUNCTYPE (int32, CounterInputTask, int32, uInt32, void_p),
+ CO=ctypes.CFUNCTYPE (int32, CounterOutputTask, int32, uInt32, void_p),
+ )
+SignalEventCallback_map = dict(AI=ctypes.CFUNCTYPE (int32, AnalogInputTask, int32, void_p),
+ AO=ctypes.CFUNCTYPE (int32, AnalogOutputTask, int32, void_p),
+ DI=ctypes.CFUNCTYPE (int32, DigitalInputTask, int32, void_p),
+ DO=ctypes.CFUNCTYPE (int32, DigitalOutputTask, int32, void_p),
+ CI=ctypes.CFUNCTYPE (int32, CounterInputTask, int32, void_p),
+ CO=ctypes.CFUNCTYPE (int32, CounterOutputTask, int32, void_p),
+ )
+
+"""
+
+if __name__ == '__main__':
+
+ import lantz.log
+
+ lantz.log.log_to_screen(lantz.log.DEBUG)
+
+ inst = System()
+ print(inst.version)
+ print(inst.device_names)
diff --git a/lantz/drivers/legacy/ni/daqmx/channels.py b/lantz/drivers/legacy/ni/daqmx/channels.py
new file mode 100644
index 0000000..c2aaddc
--- /dev/null
+++ b/lantz/drivers/legacy/ni/daqmx/channels.py
@@ -0,0 +1,741 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.ni.daqmx.channels
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Implementation of specialized channel classes.
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from .base import Channel, Task
+from .constants import Constants
+
+
+class VoltageInputChannel(Channel):
+ """Creates channel(s) to measure voltage and adds the channel(s)
+ to the task you specify with taskHandle.
+
+ If your measurement requires the use of internal excitation or you need
+ the voltage to be scaled by excitation, call DAQmxCreateAIVoltageChanWithExcit.
+
+ :param phys_channel: The names of the physical channels to use
+ to create virtual channels. You can specify
+ a list or range of physical channels.
+ :param channel_name: The name(s) to assign to the created virtual channel(s).
+ If you do not specify a name, NI-DAQmx uses the physical
+ channel name as the virtual channel name. If you specify
+ your own names for nameToAssignToChannel, you must use the
+ names when you refer to these channels in other NI-DAQmx
+ functions.
+
+ If you create multiple virtual channels with one call to
+ this function, you can specify a list of names separated by
+ commas. If you provide fewer names than the number of
+ virtual channels you create, NI-DAQmx automatically assigns
+ names to the virtual channels.
+
+ :param terminal: {'default', 'rse', 'nrse', 'diff', 'pseudodiff'}
+ The input terminal configuration for the channel:
+
+ 'default'
+ At run time, NI-DAQmx chooses the default terminal
+ configuration for the channel.
+
+ 'rse'
+ Referenced single-ended mode
+
+ 'nrse'
+ Nonreferenced single-ended mode
+
+ 'diff'
+ Differential mode
+
+ 'pseudodiff'
+ Pseudodifferential mode
+
+ :param min_val: The minimum value, in units, that you expect to measure.
+ :param max_val: The maximum value, in units, that you expect to measure.
+ :param units: units to use to return the voltage measurements
+ """
+
+ IO_TYPE = 'AI'
+
+ CREATE_FUN = 'CreateAIVoltageChan'
+
+ terminal_map = dict (default = Constants.Val_Cfg_Default,
+ rse = Constants.Val_RSE,
+ nrse = Constants.Val_NRSE,
+ diff = Constants.Val_Diff,
+ pseudodiff = Constants.Val_PseudoDiff)
+
+ def __init__(self, phys_channel, name='', terminal='default',
+ min_max=(-10., 10.), units='volts', task=None):
+
+ if not name:
+ name = ''#phys_channel
+
+ terminal_val = self.terminal_map[terminal]
+
+ if units != 'volts':
+ custom_scale_name = units
+ units = Constants.Val_FromCustomScale
+ else:
+ custom_scale_name = None
+ units = Constants.Val_Volts
+
+ self._create_args = (phys_channel, name, terminal_val,
+ min_max[0], min_max[1], units, custom_scale_name)
+
+ super().__init__(task=task, name=name)
+
+
+class VoltageOutputChannel(Channel):
+ """Creates channel(s) to generate voltage and adds the channel(s)
+ to the task you specify with taskHandle.
+
+ See VoltageOutputChannel
+ """
+
+ CHANNEL_TYPE = 'AO'
+
+ def __init__(self, phys_channel, channel_name='', terminal='default', min_max=(-1, -1), units='volts'):
+
+ terminal_val = self.terminal_map[terminal]
+
+ if units != 'volts':
+ custom_scale_name = units
+ units = Constants.FROM_CUSTOM_SCALE
+ else:
+ custom_scale_name = None
+ units = Constants.VOLTS
+
+ err = self.lib.CreateAOVoltageChan(phys_channel, channel_name,
+ min_max[0], min_max[1], units, custom_scale_name)
+
+# Not implemented:
+# DAQmxCreateAIAccelChan, DAQmxCreateAICurrentChan, DAQmxCreateAIFreqVoltageChan,
+# DAQmxCreateAIMicrophoneChan, DAQmxCreateAIResistanceChan, DAQmxCreateAIRTDChan,
+# DAQmxCreateAIStrainGageChan, DAQmxCreateAITempBuiltInSensorChan,
+# DAQmxCreateAIThrmcplChan, DAQmxCreateAIThrmstrChanIex, DAQmxCreateAIThrmstrChanVex,
+# DAQmxCreateAIVoltageChanWithExcit
+# DAQmxCreateAIPosLVDTChan, DAQmxCreateAIPosRVDTChan/
+# DAQmxCreateTEDSAI*
+
+# Not implemented: DAQmxCreateAOCurrentChan
+# DAQmxCreateDIChan, DAQmxCreateDOChan
+# DAQmxCreateCI*, DAQmxCreateCO*
+
+
+class DigitalInputChannel(Channel):
+ """
+ Creates channel(s) to measure digital signals and adds the
+ channel(s) to the task you specify with taskHandle. You can
+ group digital lines into one digital channel or separate them
+ into multiple digital channels. If you specify one or more
+ entire ports in lines by using port physical channel names,
+ you cannot separate the ports into multiple channels. To
+ separate ports into multiple channels, use this function
+ multiple times with a different port each time.
+
+ Parameters
+ ----------
+
+ lines : str
+
+ The names of the digital lines used to create a virtual
+ channel. You can specify a list or range of lines.
+
+ name : str
+
+ The name of the created virtual channel(s). If you create
+ multiple virtual channels with one call to this function,
+ you can specify a list of names separated by commas. If you
+ do not specify a name, NI-DAQmx uses the physical channel
+ name as the virtual channel name. If you specify your own
+ names for name, you must use the names when you refer to
+ these channels in other NI-DAQmx functions.
+
+ group_by : {'line', 'all_lines'}
+
+ Specifies whether to group digital lines into one or more
+ virtual channels. If you specify one or more entire ports in
+ lines, you must set grouping to 'for_all_lines':
+
+ 'line' - One channel for each line
+
+ 'all_lines' - One channel for all lines
+ """
+
+
+ def __init__(self, lines, name='', group_by='line'):
+
+ if group_by == 'line':
+ grouping_val = Constants.ChanPerLine
+ self.one_channel_for_all_lines = False
+ else:
+ grouping_val = Constants.ChanForAllLines
+ self.one_channel_for_all_lines = True
+
+ self.lib.CreateDIChan(lines, name, grouping_val)
+
+
+class DigitalOutputChannel(Channel):
+ """
+ Creates channel(s) to generate digital signals and adds the
+ channel(s) to the task you specify with taskHandle. You can
+ group digital lines into one digital channel or separate them
+ into multiple digital channels. If you specify one or more
+ entire ports in lines by using port physical channel names,
+ you cannot separate the ports into multiple channels. To
+ separate ports into multiple channels, use this function
+ multiple times with a different port each time.
+
+ See DigitalInputChannel
+ """
+
+ def __init__(self, lines, name='', group_by='line'):
+
+ if group_by == 'line':
+ grouping_val = Constants.ChanPerLine
+ self.one_channel_for_all_lines = False
+ else:
+ grouping_val = Constants.ChanForAllLines
+ self.one_channel_for_all_lines = True
+
+ self.lib.CreateDOChan(lines, name, grouping_val)
+
+
+class CountEdgesChannel(Channel):
+ """
+ Creates a channel to count the number of rising or falling
+ edges of a digital signal and adds the channel to the task you
+ specify with taskHandle. You can create only one counter input
+ channel at a time with this function because a task can
+ include only one counter input channel. To read from multiple
+ counters simultaneously, use a separate task for each
+ counter. Connect the input signal to the default input
+ terminal of the counter unless you select a different input
+ terminal.
+
+ Parameters
+ ----------
+
+ counter : str
+
+ The name of the counter to use to create virtual channels.
+
+ name : str
+
+ The name(s) to assign to the created virtual channel(s). If
+ you do not specify a name, NI-DAQmx uses the physical
+ channel name as the virtual channel name. If you specify
+ your own names for nameToAssignToChannel, you must use the
+ names when you refer to these channels in other NI-DAQmx
+ functions.
+
+ If you create multiple virtual channels with one call to
+ this function, you can specify a list of names separated by
+ commas. If you provide fewer names than the number of
+ virtual channels you create, NI-DAQmx automatically assigns
+ names to the virtual channels.
+
+ edge : {'rising', 'falling'}
+
+ Specifies on which edges of the input signal to increment or
+ decrement the count, rising or falling edge(s).
+
+ init : int
+
+ The value from which to start counting.
+
+ direction : {'up', 'down', 'ext'}
+
+ Specifies whether to increment or decrement the
+ counter on each edge:
+
+ 'up' - Increment the count register on each edge.
+
+ 'down' - Decrement the count register on each edge.
+
+ 'ext' - The state of a digital line controls the count
+ direction. Each counter has a default count direction
+ terminal.
+ """
+
+ CHANNEL_TYPE = 'CI'
+
+
+ def __init__ (self, counter, name="", edge='rising', init=0, direction='up'):
+
+ if edge == 'rising':
+ edge_val = Constants.RISING
+ else:
+ edge_val = Constants.FALLING
+
+ if direction == 'up':
+ direction_val = Constants.COUNT_UP
+ else:
+ direction_val = Constants.COUNT_DOWN
+
+ self.lib.CreateCICountEdgesChan(counter, name, edge_val, direction_val)
+
+
+class LinearEncoderChannel(Channel):
+ """
+ Creates a channel that uses a linear encoder to measure linear position.
+ You can create only one counter input channel at a time with this function
+ because a task can include only one counter input channel. To read from
+ multiple counters simultaneously, use a separate task for each counter.
+ Connect the input signals to the default input terminals of the counter
+ unless you select different input terminals.
+
+ Parameters
+ ----------
+
+ counter : str
+
+ The name of the counter to use to create virtual channels.
+
+ name : str
+
+ The name(s) to assign to the created virtual channel(s). If
+ you do not specify a name, NI-DAQmx uses the physical
+ channel name as the virtual channel name. If you specify
+ your own names for nameToAssignToChannel, you must use the
+ names when you refer to these channels in other NI-DAQmx
+ functions.
+
+ If you create multiple virtual channels with one call to
+ this function, you can specify a list of names separated by
+ commas. If you provide fewer names than the number of
+ virtual channels you create, NI-DAQmx automatically assigns
+ names to the virtual channels.
+
+ decodingType : {'X1', 'X2', 'X4', 'TwoPulseCounting'}
+
+ Specifies how to count and interpret the pulses that the encoder
+ generates on signal A and signal B. X1, X2, and X4 are valid for
+ quadrature encoders only. TwoPulseCounting is valid only for
+ two-pulse encoders.
+
+ X2 and X4 decoding are more sensitive to smaller changes in position
+ than X1 encoding, with X4 being the most sensitive. However, more
+ sensitive decoding is more likely to produce erroneous measurements
+ if there is vibration in the encoder or other noise in the signals.
+
+ ZidxEnable : bool
+
+ Specifies whether to enable z indexing for the measurement.
+
+ ZidxVal : float
+
+ The value, in units, to which to reset the measurement when signal Z
+ is high and signal A and signal B are at the states you specify with
+ ZidxPhase.
+
+ ZidxPhase : {'AHighBHigh', 'AHighBLow', 'ALowBHigh', 'ALowBLow'}
+
+ The states at which signal A and signal B must be while signal Z is high
+ for NI-DAQmx to reset the measurement. If signal Z is never high while
+ the signal A and signal B are high, for example, you must choose a phase
+ other than Constants.Val_AHighBHigh.
+
+ When signal Z goes high and how long it stays high varies from encoder to
+ encoder. Refer to the documentation for the encoder to determine the
+ timing of signal Z with respect to signal A and signal B.
+
+ units : {'Meters', 'Inches', 'Ticks', 'FromCustomScale'}
+
+ The units to use to return linear position measurements from the channel.
+
+ distPerPulse : float
+
+ The distance measured for each pulse the encoder generates. Specify this
+ value in units.
+
+ init : float
+
+ The position of the encoder when the measurement begins. This value is
+ in units.
+
+ customScaleName : str
+
+ The name of a custom scale to apply to the channel. To use this parameter,
+ you must set units to Constants.Val_FromCustomScale. If you do not set units
+ to FromCustomScale, you must set customScaleName to NULL.
+ """
+
+ def __init__(
+ self,
+ counter,
+ name="",
+ decodingType='X1',
+ ZidxEnable=False,
+ ZidxVal=0.0,
+ ZidxPhase='AHighBHigh',
+ units='Ticks',
+ distPerPulse=1.0,
+ init=0.0,
+ customScaleName=None
+ ):
+ counter = str(counter)
+ name = str(name)
+
+ decodingType_map = dict(X1=Constants.Val_X1, X2=Constants.Val_X2, X4=Constants.Val_X4,
+ TwoPulseCounting=Constants.Val_TwoPulseCounting)
+ ZidxPhase_map = dict(AHighBHigh=Constants.Val_AHighBHigh, AHighBLow=Constants.Val_AHighBLow,
+ ALowBHigh=Constants.Val_ALowBHigh, ALowBLow=Constants.Val_ALowBLow)
+ units_map = dict(Meters=Constants.Val_Meters, Inches=Constants.Val_Inches,
+ Ticks=Constants.Val_Ticks, FromCustomScale=Constants.Val_FromCustomScale)
+
+ decodingType_val = self._get_map_value ('decodingType', decodingType_map, decodingType)
+ ZidxPhase_val = self._get_map_value ('ZidxPhase', ZidxPhase_map, ZidxPhase)
+ units_val = self._get_map_value ('units', units_map, units)
+
+ if units_val != Constants.Val_FromCustomScale:
+ customScaleName = None
+
+ CALL(
+ 'CreateCILinEncoderChan',
+ self,
+ counter,
+ name,
+ decodingType_val,
+ bool32(ZidxEnable),
+ float64(ZidxVal),
+ ZidxPhase_val,
+ units_val,
+ float64(distPerPulse),
+ float64(init),
+ customScaleName
+ )==0
+
+
+class MeasureFrequencyChannel(Channel):
+ """
+ Creates a channel to measure the frequency of a digital signal
+ and adds the channel to the task. You can create only one
+ counter input channel at a time with this function because a
+ task can include only one counter input channel. To read from
+ multiple counters simultaneously, use a separate task for each
+ counter. Connect the input signal to the default input
+ terminal of the counter unless you select a different input
+ terminal.
+
+ Parameters
+ ----------
+
+ counter : str
+ The name of the counter to use to create virtual channels.
+
+ name : str
+ The name(s) to assign to the created virtual channel(s). If
+ you do not specify a name, NI-DAQmx uses the physical
+ channel name as the virtual channel name. If you specify
+ your own names for nameToAssignToChannel, you must use the
+ names when you refer to these channels in other NI-DAQmx
+ functions.
+
+ If you create multiple virtual channels with one call to
+ this function, you can specify a list of names separated by
+ commas. If you provide fewer names than the number of
+ virtual channels you create, NI-DAQmx automatically assigns
+ names to the virtual channels.
+
+ min_val : float
+ The minimum value, in units, that you expect to measure.
+
+ max_val : float
+ The maximum value, in units, that you expect to measure.
+
+ units : {'hertz', 'ticks', 'custom'}
+ Units to use to return the measurement and to specify the
+ min/max expected value.
+
+ 'hertz' - Hertz, cycles per second
+ 'ticks' - timebase ticks
+ 'custom' - use custom_scale_name to specify units
+
+ edge : {'rising', 'falling'}
+ Specifies which edges to measure the frequency or period of the signal.
+
+ method : {'low_freq', 'high_freq', 'large_range'}
+ The method used to calculate the period or frequency of the
+ signal. See the M series DAQ User Manual (371022K-01), page
+ 7-9 for more information.
+
+ 'low_freq'
+ Use one counter that uses a constant timebase to measure
+ the input signal.
+
+ 'high_freq'
+ Use two counters, one of which counts pulses of the
+ signal to measure during the specified measurement time.
+
+ 'large_range'
+ Use one counter to divide the frequency of the input
+ signal to create a lower frequency signal that the
+ second counter can more easily measure.
+
+ meas_time : float
+ The length of time to measure the frequency or period of the
+ signal, when meas_method is 'high_freq'. Measurement accuracy
+ increases with increased meas_time and with increased signal
+ frequency. Ensure that the meas_time is low enough to prevent
+ the counter register from overflowing.
+
+ divisor : int
+ The value by which to divide the input signal, when
+ meas_method is 'large_range'. The larger this value, the more
+ accurate the measurement, but too large a value can cause the
+ count register to roll over, resulting in an incorrect
+ measurement.
+
+ custom_scale_name : str
+ The name of a custom scale to apply to the channel. To use
+ this parameter, you must set units to 'custom'. If you do
+ not set units to 'custom', you must set custom_scale_name to
+ None.
+ """
+
+ def __init__(self, counter, name='', min_val=1e2, max_val=1e3,
+ units="hertz", edge="rising", method="low_freq",
+ meas_time=1.0, divisor=1, custom_scale_name=None):
+
+ self.data_type = float
+
+ assert divisor > 0
+
+ if method == 'low_freq':
+ meas_meth_val = Constants.LOW_FREQ1_CTR
+ elif method == 'high_freq':
+ meas_meth_val = Constants.HIGH_FREQ2_CTR
+ elif method == 'large_range':
+ meas_meth_val = Constants.LARGE_RANGE2_CTR
+
+
+ if units != ('hertz', 'ticks'):
+ custom_scale_name = units
+ units = Constants.FROM_CUSTOM_SCALE
+ else:
+ custom_scale_name = None
+ if units == 'hertz':
+ units = Constants.HZ
+ else:
+ units = Contstants.TICKS
+
+ self.lib.CreateCIFreqChan(counter, name, min_max[0], min_max[1],
+ units_val, edge_val, meas_meth_val,
+ meas_time, divisor, custom_scale_name)
+
+
+
+def create_channel_frequency(self, counter, name="", units='hertz', idle_state='low',
+ delay=0.0, freq=1.0, duty_cycle=0.5):
+ """
+ Creates channel(s) to generate digital pulses that freq and
+ duty_cycle define and adds the channel to the task. The
+ pulses appear on the default output terminal of the counter
+ unless you select a different output terminal.
+
+ Parameters
+ ----------
+
+ counter : str
+
+ The name of the counter to use to create virtual
+ channels. You can specify a list or range of physical
+ channels.
+
+ name : str
+
+ The name(s) to assign to the created virtual channel(s). If
+ you do not specify a name, NI-DAQmx uses the physical
+ channel name as the virtual channel name. If you specify
+ your own names for nameToAssignToChannel, you must use the
+ names when you refer to these channels in other NI-DAQmx
+ functions.
+
+ If you create multiple virtual channels with one call to
+ this function, you can specify a list of names separated by
+ commas. If you provide fewer names than the number of
+ virtual channels you create, NI-DAQmx automatically assigns
+ names to the virtual channels.
+
+ units : {'hertz'}
+
+ The units in which to specify freq:
+
+ 'hertz' - hertz
+
+ idle_state : {'low', 'high'}
+
+ The resting state of the output terminal.
+
+ delay : float
+
+ The amount of time in seconds to wait before generating the
+ first pulse.
+
+ freq : float
+
+ The frequency at which to generate pulses.
+
+ duty_cycle : float
+
+ The width of the pulse divided by the pulse period. NI-DAQmx
+ uses this ratio, combined with frequency, to determine pulse
+ width and the interval between pulses.
+
+ Returns
+ -------
+
+ success_status : bool
+ """
+ counter = str(counter)
+ name = str(name)
+ units_map = dict (hertz = Constants.Val_Hz)
+ idle_state_map = dict (low=Constants.Val_Low, high=Constants.Val_High)
+ units_val = self._get_map_value('units', units_map, units)
+ idle_state_val = self._get_map_value('idle_state', idle_state_map, idle_state)
+ self.lib.CreateCOPulseChanFreq(counter, name, units_val, idle_state_val,
+ delay, freq, (duty_cycle))
+
+def create_channel_ticks(self, counter, name="", source="", idle_state='low',
+ delay = 0, low_ticks=1, high_ticks=1):
+ """
+ Creates channel(s) to generate digital pulses defined by the
+ number of timebase ticks that the pulse is at a high state and
+ the number of timebase ticks that the pulse is at a low state
+ and also adds the channel to the task. The pulses appear on
+ the default output terminal of the counter unless you select a
+ different output terminal.
+
+ Parameters
+ ----------
+
+ counter : str
+
+ The name of the counter to use to create virtual
+ channels. You can specify a list or range of physical
+ channels.
+
+ name : str
+
+ The name(s) to assign to the created virtual channel(s). If
+ you do not specify a name, NI-DAQmx uses the physical
+ channel name as the virtual channel name. If you specify
+ your own names for nameToAssignToChannel, you must use the
+ names when you refer to these channels in other NI-DAQmx
+ functions.
+
+ If you create multiple virtual channels with one call to
+ this function, you can specify a list of names separated by
+ commas. If you provide fewer names than the number of
+ virtual channels you create, NI-DAQmx automatically assigns
+ names to the virtual channels.
+
+ source : str
+
+ The terminal to which you connect an external timebase. You
+ also can specify a source terminal by using a terminal name.
+
+ idle_state : {'low', 'high'}
+
+ The resting state of the output terminal.
+
+ delay : int
+
+ The number of timebase ticks to wait before generating the
+ first pulse.
+
+ low_ticks : int
+
+ The number of timebase ticks that the pulse is low.
+
+ high_ticks : int
+
+ The number of timebase ticks that the pulse is high.
+
+ Returns
+ -------
+
+ success_status : bool
+ """
+ counter = str(counter)
+ name = str(name)
+ idle_state_map = dict (low=Constants.Val_Low, high=Constants.Val_High)
+ idle_state_val = self._get_map_value('idle_state', idle_state_map, idle_state)
+ return CALL('CreateCOPulseChanTicks', self, counter, name, source, idle_state_val,
+ int32 (delay), int32 (low_ticks), int32 (high_ticks))==0
+
+def create_channel_time(self, counter, name="", units="seconds", idle_state='low',
+ delay = 0, low_time=1, high_time=1):
+ """
+ Creates channel(s) to generate digital pulses defined by the
+ number of timebase ticks that the pulse is at a high state and
+ the number of timebase ticks that the pulse is at a low state
+ and also adds the channel to the task. The pulses appear on
+ the default output terminal of the counter unless you select a
+ different output terminal.
+
+ Parameters
+ ----------
+
+ counter : str
+
+ The name of the counter to use to create virtual
+ channels. You can specify a list or range of physical
+ channels.
+
+ name : str
+
+ The name(s) to assign to the created virtual channel(s). If
+ you do not specify a name, NI-DAQmx uses the physical
+ channel name as the virtual channel name. If you specify
+ your own names for nameToAssignToChannel, you must use the
+ names when you refer to these channels in other NI-DAQmx
+ functions.
+
+ If you create multiple virtual channels with one call to
+ this function, you can specify a list of names separated by
+ commas. If you provide fewer names than the number of
+ virtual channels you create, NI-DAQmx automatically assigns
+ names to the virtual channels.
+
+ units : {'seconds'}
+
+ The units in which to specify high and low time.
+
+ idle_state : {'low', 'high'}
+
+ The resting state of the output terminal.
+
+ delay : float
+
+ The amount of time in seconds to wait before generating the
+ first pulse.
+
+ low_time : float
+
+ The amount of time the pulse is low, in seconds.
+
+ high_time : float
+
+ The amount of time the pulse is high, in seconds.
+
+ Returns
+ -------
+
+ success_status : bool
+ """
+ counter = str(counter)
+ name = str(name)
+ units_map = dict (seconds = Constants.Val_Seconds)
+ idle_state_map = dict (low=Constants.Val_Low, high=Constants.Val_High)
+ units_val = self._get_map_value('units', units_map, units)
+ idle_state_val = self._get_map_value('idle_state', idle_state_map, idle_state)
+ return CALL('CreateCOPulseChanTime', self, counter, name, units_val, idle_state_val,
+ float64 (delay), float64(low_time), float64(high_time))==0
diff --git a/lantz/drivers/legacy/ni/daqmx/constants.py b/lantz/drivers/legacy/ni/daqmx/constants.py
new file mode 100644
index 0000000..c1ffe73
--- /dev/null
+++ b/lantz/drivers/legacy/ni/daqmx/constants.py
@@ -0,0 +1,1541 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.ni.daqmx.constants
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Constants and types for DAQmx
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from lantz.drivers.legacy.visalib import RichEnum
+
+class Constants(metaclass=RichEnum):
+
+ _PREFIX = 'DAQMX_'
+
+ Buf_Input_BufSize = 0x186C
+ Buf_Input_OnbrdBufSize = 0x230A
+ Buf_Output_BufSize = 0x186D
+ Buf_Output_OnbrdBufSize = 0x230B
+ SelfCal_Supported = 0x1860
+ SelfCal_LastTemp = 0x1864
+ ExtCal_RecommendedInterval = 0x1868
+ ExtCal_LastTemp = 0x1867
+ Cal_UserDefinedInfo = 0x1861
+ Cal_UserDefinedInfo_MaxSize = 0x191C
+ Cal_DevTemp = 0x223B
+ Cal_AccConnectionCount = 0x2FEB
+ Cal_RecommendedAccConnectionCountLimit = 0x2FEC
+
+ AI_Max = 0x17DD
+ AI_Min = 0x17DE
+ AI_CustomScaleName = 0x17E0
+ AI_MeasType = 0x0695
+ AI_Voltage_Units = 0x1094
+ AI_Voltage_dBRef = 0x29B0
+ AI_Voltage_ACRMS_Units = 0x17E2
+ AI_Temp_Units = 0x1033
+ AI_Thrmcpl_Type = 0x1050
+ AI_Thrmcpl_ScaleType = 0x29D0
+ AI_Thrmcpl_CJCSrc = 0x1035
+ AI_Thrmcpl_CJCVal = 0x1036
+ AI_Thrmcpl_CJCChan = 0x1034
+ AI_RTD_Type = 0x1032
+ AI_RTD_R0 = 0x1030
+ AI_RTD_A = 0x1010
+ AI_RTD_B = 0x1011
+ AI_RTD_C = 0x1013
+ AI_Thrmstr_A = 0x18C9
+ AI_Thrmstr_B = 0x18CB
+ AI_Thrmstr_C = 0x18CA
+ AI_Thrmstr_R1 = 0x1061
+ AI_ForceReadFromChan = 0x18F8
+ AI_Current_Units = 0x0701
+ AI_Current_ACRMS_Units = 0x17E3
+ AI_Strain_Units = 0x0981
+ AI_StrainGage_GageFactor = 0x0994
+ AI_StrainGage_PoissonRatio = 0x0998
+ AI_StrainGage_Cfg = 0x0982
+ AI_Resistance_Units = 0x0955
+ AI_Freq_Units = 0x0806
+ AI_Freq_ThreshVoltage = 0x0815
+ AI_Freq_Hyst = 0x0814
+ AI_LVDT_Units = 0x0910
+ AI_LVDT_Sensitivity = 0x0939
+ AI_LVDT_SensitivityUnits = 0x219A
+ AI_RVDT_Units = 0x0877
+ AI_RVDT_Sensitivity = 0x0903
+ AI_RVDT_SensitivityUnits = 0x219B
+ AI_EddyCurrentProxProbe_Units = 0x2AC0
+ AI_EddyCurrentProxProbe_Sensitivity = 0x2ABE
+ AI_EddyCurrentProxProbe_SensitivityUnits = 0x2ABF
+ AI_SoundPressure_MaxSoundPressureLvl = 0x223A
+ AI_SoundPressure_Units = 0x1528
+ AI_SoundPressure_dBRef = 0x29B1
+ AI_Microphone_Sensitivity = 0x1536
+ AI_Accel_Units = 0x0673
+ AI_Accel_dBRef = 0x29B2
+ AI_Accel_Sensitivity = 0x0692
+ AI_Accel_SensitivityUnits = 0x219C
+ AI_Force_Units = 0x2F75
+ AI_Force_IEPESensor_Sensitivity = 0x2F81
+ AI_Force_IEPESensor_SensitivityUnits = 0x2F82
+ AI_Pressure_Units = 0x2F76
+ AI_Torque_Units = 0x2F77
+ AI_Bridge_Units = 0x2F92
+ AI_Bridge_ElectricalUnits = 0x2F87
+ AI_Bridge_PhysicalUnits = 0x2F88
+ AI_Bridge_ScaleType = 0x2F89
+ AI_Bridge_TwoPointLin_First_ElectricalVal = 0x2F8A
+ AI_Bridge_TwoPointLin_First_PhysicalVal = 0x2F8B
+ AI_Bridge_TwoPointLin_Second_ElectricalVal = 0x2F8C
+ AI_Bridge_TwoPointLin_Second_PhysicalVal = 0x2F8D
+ AI_Bridge_Table_ElectricalVals = 0x2F8E
+ AI_Bridge_Table_PhysicalVals = 0x2F8F
+ AI_Bridge_Poly_ForwardCoeff = 0x2F90
+ AI_Bridge_Poly_ReverseCoeff = 0x2F91
+ AI_Is_TEDS = 0x2983
+ AI_TEDS_Units = 0x21E0
+ AI_Coupling = 0x0064
+ AI_Impedance = 0x0062
+ AI_TermCfg = 0x1097
+ AI_InputSrc = 0x2198
+ AI_ResistanceCfg = 0x1881
+ AI_LeadWireResistance = 0x17EE
+ AI_Bridge_Cfg = 0x0087
+ AI_Bridge_NomResistance = 0x17EC
+ AI_Bridge_InitialVoltage = 0x17ED
+ AI_Bridge_InitialRatio = 0x2F86
+ AI_Bridge_ShuntCal_Enable = 0x0094
+ AI_Bridge_ShuntCal_Select = 0x21D5
+ AI_Bridge_ShuntCal_GainAdjust = 0x193F
+ AI_Bridge_ShuntCal_ShuntCalAResistance = 0x2F78
+ AI_Bridge_ShuntCal_ShuntCalAActualResistance = 0x2F79
+ AI_Bridge_Balance_CoarsePot = 0x17F1
+ AI_Bridge_Balance_FinePot = 0x18F4
+ AI_CurrentShunt_Loc = 0x17F2
+ AI_CurrentShunt_Resistance = 0x17F3
+ AI_Excit_Src = 0x17F4
+ AI_Excit_Val = 0x17F5
+ AI_Excit_UseForScaling = 0x17FC
+ AI_Excit_UseMultiplexed = 0x2180
+ AI_Excit_ActualVal = 0x1883
+ AI_Excit_DCorAC = 0x17FB
+ AI_Excit_VoltageOrCurrent = 0x17F6
+ AI_ACExcit_Freq = 0x0101
+ AI_ACExcit_SyncEnable = 0x0102
+ AI_ACExcit_WireMode = 0x18CD
+ AI_OpenThrmcplDetectEnable = 0x2F72
+ AI_Thrmcpl_LeadOffsetVoltage = 0x2FB8
+ AI_Atten = 0x1801
+ AI_ProbeAtten = 0x2A88
+ AI_Lowpass_Enable = 0x1802
+ AI_Lowpass_CutoffFreq = 0x1803
+ AI_Lowpass_SwitchCap_ClkSrc = 0x1884
+ AI_Lowpass_SwitchCap_ExtClkFreq = 0x1885
+ AI_Lowpass_SwitchCap_ExtClkDiv = 0x1886
+ AI_Lowpass_SwitchCap_OutClkDiv = 0x1887
+ AI_FilterDelay = 0x2FED
+ AI_RemoveFilterDelay = 0x2FBD
+ AI_AveragingWinSize = 0x2FEE
+ AI_ResolutionUnits = 0x1764
+ AI_Resolution = 0x1765
+ AI_RawSampSize = 0x22DA
+ AI_RawSampJustification = 0x0050
+ AI_ADCTimingMode = 0x29F9
+ AI_ADCCustomTimingMode = 0x2F6B
+ AI_Dither_Enable = 0x0068
+ AI_ChanCal_HasValidCalInfo = 0x2297
+ AI_ChanCal_EnableCal = 0x2298
+ AI_ChanCal_ApplyCalIfExp = 0x2299
+ AI_ChanCal_ScaleType = 0x229C
+ AI_ChanCal_Table_PreScaledVals = 0x229D
+ AI_ChanCal_Table_ScaledVals = 0x229E
+ AI_ChanCal_Poly_ForwardCoeff = 0x229F
+ AI_ChanCal_Poly_ReverseCoeff = 0x22A0
+ AI_ChanCal_OperatorName = 0x22A3
+ AI_ChanCal_Desc = 0x22A4
+ AI_ChanCal_Verif_RefVals = 0x22A1
+ AI_ChanCal_Verif_AcqVals = 0x22A2
+ AI_Rng_High = 0x1815
+ AI_Rng_Low = 0x1816
+ AI_DCOffset = 0x2A89
+ AI_Gain = 0x1818
+ AI_SampAndHold_Enable = 0x181A
+ AI_AutoZeroMode = 0x1760
+ AI_DataXferMech = 0x1821
+ AI_DataXferReqCond = 0x188B
+ AI_DataXferCustomThreshold = 0x230C
+ AI_UsbXferReqSize = 0x2A8E
+ AI_MemMapEnable = 0x188C
+ AI_RawDataCompressionType = 0x22D8
+ AI_LossyLSBRemoval_CompressedSampSize = 0x22D9
+ AI_DevScalingCoeff = 0x1930
+ AI_EnhancedAliasRejectionEnable = 0x2294
+ AO_Max = 0x1186
+ AO_Min = 0x1187
+ AO_CustomScaleName = 0x1188
+ AO_OutputType = 0x1108
+ AO_Voltage_Units = 0x1184
+ AO_Voltage_CurrentLimit = 0x2A1D
+ AO_Current_Units = 0x1109
+ AO_FuncGen_Type = 0x2A18
+ AO_FuncGen_Freq = 0x2A19
+ AO_FuncGen_Amplitude = 0x2A1A
+ AO_FuncGen_Offset = 0x2A1B
+ AO_FuncGen_Square_DutyCycle = 0x2A1C
+ AO_FuncGen_ModulationType = 0x2A22
+ AO_FuncGen_FMDeviation = 0x2A23
+ AO_OutputImpedance = 0x1490
+ AO_LoadImpedance = 0x0121
+ AO_IdleOutputBehavior = 0x2240
+ AO_TermCfg = 0x188E
+ AO_ResolutionUnits = 0x182B
+ AO_Resolution = 0x182C
+ AO_DAC_Rng_High = 0x182E
+ AO_DAC_Rng_Low = 0x182D
+ AO_DAC_Ref_ConnToGnd = 0x0130
+ AO_DAC_Ref_AllowConnToGnd = 0x1830
+ AO_DAC_Ref_Src = 0x0132
+ AO_DAC_Ref_ExtSrc = 0x2252
+ AO_DAC_Ref_Val = 0x1832
+ AO_DAC_Offset_Src = 0x2253
+ AO_DAC_Offset_ExtSrc = 0x2254
+ AO_DAC_Offset_Val = 0x2255
+ AO_ReglitchEnable = 0x0133
+ AO_Gain = 0x0118
+ AO_UseOnlyOnBrdMem = 0x183A
+ AO_DataXferMech = 0x0134
+ AO_DataXferReqCond = 0x183C
+ AO_UsbXferReqSize = 0x2A8F
+ AO_MemMapEnable = 0x188F
+ AO_DevScalingCoeff = 0x1931
+ AO_EnhancedImageRejectionEnable = 0x2241
+
+ DI_InvertLines = 0x0793
+ DI_NumLines = 0x2178
+ DI_DigFltr_Enable = 0x21D6
+ DI_DigFltr_MinPulseWidth = 0x21D7
+ DI_DigFltr_EnableBusMode = 0x2EFE
+ DI_DigFltr_TimebaseSrc = 0x2ED4
+ DI_DigFltr_TimebaseRate = 0x2ED5
+ DI_DigSync_Enable = 0x2ED6
+ DI_Tristate = 0x1890
+ DI_LogicFamily = 0x296D
+ DI_DataXferMech = 0x2263
+ DI_DataXferReqCond = 0x2264
+ DI_UsbXferReqSize = 0x2A90
+ DI_MemMapEnable = 0x296A
+ DI_AcquireOn = 0x2966
+ DO_OutputDriveType = 0x1137
+ DO_InvertLines = 0x1133
+ DO_NumLines = 0x2179
+ DO_Tristate = 0x18F3
+ DO_LineStates_StartState = 0x2972
+ DO_LineStates_PausedState = 0x2967
+ DO_LineStates_DoneState = 0x2968
+ DO_LogicFamily = 0x296E
+ DO_Overcurrent_Limit = 0x2A85
+ DO_Overcurrent_AutoReenable = 0x2A86
+ DO_Overcurrent_ReenablePeriod = 0x2A87
+ DO_UseOnlyOnBrdMem = 0x2265
+ DO_DataXferMech = 0x2266
+ DO_DataXferReqCond = 0x2267
+ DO_UsbXferReqSize = 0x2A91
+ DO_MemMapEnable = 0x296B
+ DO_GenerateOn = 0x2969
+
+ CI_Max = 0x189C
+ CI_Min = 0x189D
+ CI_CustomScaleName = 0x189E
+ CI_MeasType = 0x18A0
+ CI_Freq_Units = 0x18A1
+ CI_Freq_Term = 0x18A2
+ CI_Freq_StartingEdge = 0x0799
+ CI_Freq_MeasMeth = 0x0144
+ CI_Freq_EnableAveraging = 0x2ED0
+ CI_Freq_MeasTime = 0x0145
+ CI_Freq_Div = 0x0147
+ CI_Freq_DigFltr_Enable = 0x21E7
+ CI_Freq_DigFltr_MinPulseWidth = 0x21E8
+ CI_Freq_DigFltr_TimebaseSrc = 0x21E9
+ CI_Freq_DigFltr_TimebaseRate = 0x21EA
+ CI_Freq_DigSync_Enable = 0x21EB
+ CI_Period_Units = 0x18A3
+ CI_Period_Term = 0x18A4
+ CI_Period_StartingEdge = 0x0852
+ CI_Period_MeasMeth = 0x192C
+ CI_Period_EnableAveraging = 0x2ED1
+ CI_Period_MeasTime = 0x192D
+ CI_Period_Div = 0x192E
+ CI_Period_DigFltr_Enable = 0x21EC
+ CI_Period_DigFltr_MinPulseWidth = 0x21ED
+ CI_Period_DigFltr_TimebaseSrc = 0x21EE
+ CI_Period_DigFltr_TimebaseRate = 0x21EF
+ CI_Period_DigSync_Enable = 0x21F0
+ CI_CountEdges_Term = 0x18C7
+ CI_CountEdges_Dir = 0x0696
+ CI_CountEdges_DirTerm = 0x21E1
+ CI_CountEdges_CountDir_DigFltr_Enable = 0x21F1
+ CI_CountEdges_CountDir_DigFltr_MinPulseWidth = 0x21F2
+ CI_CountEdges_CountDir_DigFltr_TimebaseSrc = 0x21F3
+ CI_CountEdges_CountDir_DigFltr_TimebaseRate = 0x21F4
+ CI_CountEdges_CountDir_DigSync_Enable = 0x21F5
+ CI_CountEdges_InitialCnt = 0x0698
+ CI_CountEdges_ActiveEdge = 0x0697
+ CI_CountEdges_CountReset_Enable = 0x2FAF
+ CI_CountEdges_CountReset_ResetCount = 0x2FB0
+ CI_CountEdges_CountReset_Term = 0x2FB1
+ CI_CountEdges_CountReset_ActiveEdge = 0x2FB2
+ CI_CountEdges_CountReset_DigFltr_Enable = 0x2FB3
+ CI_CountEdges_CountReset_DigFltr_MinPulseWidth = 0x2FB4
+ CI_CountEdges_CountReset_DigFltr_TimebaseSrc = 0x2FB5
+ CI_CountEdges_CountReset_DigFltr_TimebaseRate = 0x2FB6
+ CI_CountEdges_CountReset_DigSync_Enable = 0x2FB7
+ CI_CountEdges_DigFltr_Enable = 0x21F6
+ CI_CountEdges_DigFltr_MinPulseWidth = 0x21F7
+ CI_CountEdges_DigFltr_TimebaseSrc = 0x21F8
+ CI_CountEdges_DigFltr_TimebaseRate = 0x21F9
+ CI_CountEdges_DigSync_Enable = 0x21FA
+ CI_AngEncoder_Units = 0x18A6
+ CI_AngEncoder_PulsesPerRev = 0x0875
+ CI_AngEncoder_InitialAngle = 0x0881
+ CI_LinEncoder_Units = 0x18A9
+ CI_LinEncoder_DistPerPulse = 0x0911
+ CI_LinEncoder_InitialPos = 0x0915
+ CI_Encoder_DecodingType = 0x21E6
+ CI_Encoder_AInputTerm = 0x219D
+ CI_Encoder_AInput_DigFltr_Enable = 0x21FB
+ CI_Encoder_AInput_DigFltr_MinPulseWidth = 0x21FC
+ CI_Encoder_AInput_DigFltr_TimebaseSrc = 0x21FD
+ CI_Encoder_AInput_DigFltr_TimebaseRate = 0x21FE
+ CI_Encoder_AInput_DigSync_Enable = 0x21FF
+ CI_Encoder_BInputTerm = 0x219E
+ CI_Encoder_BInput_DigFltr_Enable = 0x2200
+ CI_Encoder_BInput_DigFltr_MinPulseWidth = 0x2201
+ CI_Encoder_BInput_DigFltr_TimebaseSrc = 0x2202
+ CI_Encoder_BInput_DigFltr_TimebaseRate = 0x2203
+ CI_Encoder_BInput_DigSync_Enable = 0x2204
+ CI_Encoder_ZInputTerm = 0x219F
+ CI_Encoder_ZInput_DigFltr_Enable = 0x2205
+ CI_Encoder_ZInput_DigFltr_MinPulseWidth = 0x2206
+ CI_Encoder_ZInput_DigFltr_TimebaseSrc = 0x2207
+ CI_Encoder_ZInput_DigFltr_TimebaseRate = 0x2208
+ CI_Encoder_ZInput_DigSync_Enable = 0x2209
+ CI_Encoder_ZIndexEnable = 0x0890
+ CI_Encoder_ZIndexVal = 0x0888
+ CI_Encoder_ZIndexPhase = 0x0889
+ CI_PulseWidth_Units = 0x0823
+ CI_PulseWidth_Term = 0x18AA
+ CI_PulseWidth_StartingEdge = 0x0825
+ CI_PulseWidth_DigFltr_Enable = 0x220A
+ CI_PulseWidth_DigFltr_MinPulseWidth = 0x220B
+ CI_PulseWidth_DigFltr_TimebaseSrc = 0x220C
+ CI_PulseWidth_DigFltr_TimebaseRate = 0x220D
+ CI_PulseWidth_DigSync_Enable = 0x220E
+ CI_TwoEdgeSep_Units = 0x18AC
+ CI_TwoEdgeSep_FirstTerm = 0x18AD
+ CI_TwoEdgeSep_FirstEdge = 0x0833
+ CI_TwoEdgeSep_First_DigFltr_Enable = 0x220F
+ CI_TwoEdgeSep_First_DigFltr_MinPulseWidth = 0x2210
+ CI_TwoEdgeSep_First_DigFltr_TimebaseSrc = 0x2211
+ CI_TwoEdgeSep_First_DigFltr_TimebaseRate = 0x2212
+ CI_TwoEdgeSep_First_DigSync_Enable = 0x2213
+ CI_TwoEdgeSep_SecondTerm = 0x18AE
+ CI_TwoEdgeSep_SecondEdge = 0x0834
+ CI_TwoEdgeSep_Second_DigFltr_Enable = 0x2214
+ CI_TwoEdgeSep_Second_DigFltr_MinPulseWidth = 0x2215
+ CI_TwoEdgeSep_Second_DigFltr_TimebaseSrc = 0x2216
+ CI_TwoEdgeSep_Second_DigFltr_TimebaseRate = 0x2217
+ CI_TwoEdgeSep_Second_DigSync_Enable = 0x2218
+ CI_SemiPeriod_Units = 0x18AF
+ CI_SemiPeriod_Term = 0x18B0
+ CI_SemiPeriod_StartingEdge = 0x22FE
+ CI_SemiPeriod_DigFltr_Enable = 0x2219
+ CI_SemiPeriod_DigFltr_MinPulseWidth = 0x221A
+ CI_SemiPeriod_DigFltr_TimebaseSrc = 0x221B
+ CI_SemiPeriod_DigFltr_TimebaseRate = 0x221C
+ CI_SemiPeriod_DigSync_Enable = 0x221D
+ CI_Pulse_Freq_Units = 0x2F0B
+ CI_Pulse_Freq_Term = 0x2F04
+ CI_Pulse_Freq_Start_Edge = 0x2F05
+ CI_Pulse_Freq_DigFltr_Enable = 0x2F06
+ CI_Pulse_Freq_DigFltr_MinPulseWidth = 0x2F07
+ CI_Pulse_Freq_DigFltr_TimebaseSrc = 0x2F08
+ CI_Pulse_Freq_DigFltr_TimebaseRate = 0x2F09
+ CI_Pulse_Freq_DigSync_Enable = 0x2F0A
+ CI_Pulse_Time_Units = 0x2F13
+ CI_Pulse_Time_Term = 0x2F0C
+ CI_Pulse_Time_StartEdge = 0x2F0D
+ CI_Pulse_Time_DigFltr_Enable = 0x2F0E
+ CI_Pulse_Time_DigFltr_MinPulseWidth = 0x2F0F
+ CI_Pulse_Time_DigFltr_TimebaseSrc = 0x2F10
+ CI_Pulse_Time_DigFltr_TimebaseRate = 0x2F11
+ CI_Pulse_Time_DigSync_Enable = 0x2F12
+ CI_Pulse_Ticks_Term = 0x2F14
+ CI_Pulse_Ticks_StartEdge = 0x2F15
+ CI_Pulse_Ticks_DigFltr_Enable = 0x2F16
+ CI_Pulse_Ticks_DigFltr_MinPulseWidth = 0x2F17
+ CI_Pulse_Ticks_DigFltr_TimebaseSrc = 0x2F18
+ CI_Pulse_Ticks_DigFltr_TimebaseRate = 0x2F19
+ CI_Pulse_Ticks_DigSync_Enable = 0x2F1A
+ CI_Timestamp_Units = 0x22B3
+ CI_Timestamp_InitialSeconds = 0x22B4
+ CI_GPS_SyncMethod = 0x1092
+ CI_GPS_SyncSrc = 0x1093
+ CI_CtrTimebaseSrc = 0x0143
+ CI_CtrTimebaseRate = 0x18B2
+ CI_CtrTimebaseActiveEdge = 0x0142
+ CI_CtrTimebase_DigFltr_Enable = 0x2271
+ CI_CtrTimebase_DigFltr_MinPulseWidth = 0x2272
+ CI_CtrTimebase_DigFltr_TimebaseSrc = 0x2273
+ CI_CtrTimebase_DigFltr_TimebaseRate = 0x2274
+ CI_CtrTimebase_DigSync_Enable = 0x2275
+ CI_Count = 0x0148
+ CI_OutputState = 0x0149
+ CI_TCReached = 0x0150
+ CI_CtrTimebaseMasterTimebaseDiv = 0x18B3
+ CI_DataXferMech = 0x0200
+ CI_DataXferReqCond = 0x2EFB
+ CI_UsbXferReqSize = 0x2A92
+ CI_MemMapEnable = 0x2ED2
+ CI_NumPossiblyInvalidSamps = 0x193C
+ CI_DupCountPrevent = 0x21AC
+ CI_Prescaler = 0x2239
+
+ CO_OutputType = 0x18B5
+ CO_Pulse_IdleState = 0x1170
+ CO_Pulse_Term = 0x18E1
+ CO_Pulse_Time_Units = 0x18D6
+ CO_Pulse_HighTime = 0x18BA
+ CO_Pulse_LowTime = 0x18BB
+ CO_Pulse_Time_InitialDelay = 0x18BC
+ CO_Pulse_DutyCyc = 0x1176
+ CO_Pulse_Freq_Units = 0x18D5
+ CO_Pulse_Freq = 0x1178
+ CO_Pulse_Freq_InitialDelay = 0x0299
+ CO_Pulse_HighTicks = 0x1169
+ CO_Pulse_LowTicks = 0x1171
+ CO_Pulse_Ticks_InitialDelay = 0x0298
+ CO_CtrTimebaseSrc = 0x0339
+ CO_CtrTimebaseRate = 0x18C2
+ CO_CtrTimebaseActiveEdge = 0x0341
+ CO_CtrTimebase_DigFltr_Enable = 0x2276
+ CO_CtrTimebase_DigFltr_MinPulseWidth = 0x2277
+ CO_CtrTimebase_DigFltr_TimebaseSrc = 0x2278
+ CO_CtrTimebase_DigFltr_TimebaseRate = 0x2279
+ CO_CtrTimebase_DigSync_Enable = 0x227A
+ CO_Count = 0x0293
+ CO_OutputState = 0x0294
+ CO_AutoIncrCnt = 0x0295
+ CO_CtrTimebaseMasterTimebaseDiv = 0x18C3
+ CO_PulseDone = 0x190E
+ CO_EnableInitialDelayOnRetrigger = 0x2EC9
+ CO_ConstrainedGenMode = 0x29F2
+ CO_UseOnlyOnBrdMem = 0x2ECB
+ CO_DataXferMech = 0x2ECC
+ CO_DataXferReqCond = 0x2ECD
+ CO_UsbXferReqSize = 0x2A93
+ CO_MemMapEnable = 0x2ED3
+ CO_Prescaler = 0x226D
+ CO_RdyForNewVal = 0x22FF
+
+ ChanType = 0x187F
+ PhysicalChanName = 0x18F5
+ ChanDescr = 0x1926
+ ChanIsGlobal = 0x2304
+ Exported_AIConvClk_OutputTerm = 0x1687
+ Exported_AIConvClk_Pulse_Polarity = 0x1688
+ Exported_10MHzRefClk_OutputTerm = 0x226E
+ Exported_20MHzTimebase_OutputTerm = 0x1657
+ Exported_SampClk_OutputBehavior = 0x186B
+ Exported_SampClk_OutputTerm = 0x1663
+ Exported_SampClk_DelayOffset = 0x21C4
+ Exported_SampClk_Pulse_Polarity = 0x1664
+ Exported_SampClkTimebase_OutputTerm = 0x18F9
+ Exported_DividedSampClkTimebase_OutputTerm = 0x21A1
+ Exported_AdvTrig_OutputTerm = 0x1645
+ Exported_AdvTrig_Pulse_Polarity = 0x1646
+ Exported_AdvTrig_Pulse_WidthUnits = 0x1647
+ Exported_AdvTrig_Pulse_Width = 0x1648
+ Exported_PauseTrig_OutputTerm = 0x1615
+ Exported_PauseTrig_Lvl_ActiveLvl = 0x1616
+ Exported_RefTrig_OutputTerm = 0x0590
+ Exported_RefTrig_Pulse_Polarity = 0x0591
+ Exported_StartTrig_OutputTerm = 0x0584
+ Exported_StartTrig_Pulse_Polarity = 0x0585
+ Exported_AdvCmpltEvent_OutputTerm = 0x1651
+ Exported_AdvCmpltEvent_Delay = 0x1757
+ Exported_AdvCmpltEvent_Pulse_Polarity = 0x1652
+ Exported_AdvCmpltEvent_Pulse_Width = 0x1654
+ Exported_AIHoldCmpltEvent_OutputTerm = 0x18ED
+ Exported_AIHoldCmpltEvent_PulsePolarity = 0x18EE
+ Exported_ChangeDetectEvent_OutputTerm = 0x2197
+ Exported_ChangeDetectEvent_Pulse_Polarity = 0x2303
+ Exported_CtrOutEvent_OutputTerm = 0x1717
+ Exported_CtrOutEvent_OutputBehavior = 0x174F
+ Exported_CtrOutEvent_Pulse_Polarity = 0x1718
+ Exported_CtrOutEvent_Toggle_IdleState = 0x186A
+ Exported_HshkEvent_OutputTerm = 0x22BA
+ Exported_HshkEvent_OutputBehavior = 0x22BB
+ Exported_HshkEvent_Delay = 0x22BC
+ Exported_HshkEvent_Interlocked_AssertedLvl = 0x22BD
+ Exported_HshkEvent_Interlocked_AssertOnStart = 0x22BE
+ Exported_HshkEvent_Interlocked_DeassertDelay = 0x22BF
+ Exported_HshkEvent_Pulse_Polarity = 0x22C0
+ Exported_HshkEvent_Pulse_Width = 0x22C1
+ Exported_RdyForXferEvent_OutputTerm = 0x22B5
+ Exported_RdyForXferEvent_Lvl_ActiveLvl = 0x22B6
+ Exported_RdyForXferEvent_DeassertCond = 0x2963
+ Exported_RdyForXferEvent_DeassertCondCustomThreshold = 0x2964
+ Exported_DataActiveEvent_OutputTerm = 0x1633
+ Exported_DataActiveEvent_Lvl_ActiveLvl = 0x1634
+ Exported_RdyForStartEvent_OutputTerm = 0x1609
+ Exported_RdyForStartEvent_Lvl_ActiveLvl = 0x1751
+ Exported_SyncPulseEvent_OutputTerm = 0x223C
+ Exported_WatchdogExpiredEvent_OutputTerm = 0x21AA
+
+ Dev_IsSimulated = 0x22CA
+ Dev_ProductCategory = 0x29A9
+ Dev_ProductType = 0x0631
+ Dev_ProductNum = 0x231D
+ Dev_SerialNum = 0x0632
+ Dev_Accessory_ProductTypes = 0x2F6D
+ Dev_Accessory_ProductNums = 0x2F6E
+ Dev_Accessory_SerialNums = 0x2F6F
+ Carrier_SerialNum = 0x2A8A
+ Dev_Chassis_ModuleDevNames = 0x29B6
+ Dev_AnlgTrigSupported = 0x2984
+ Dev_DigTrigSupported = 0x2985
+ Dev_AI_PhysicalChans = 0x231E
+ Dev_AI_SupportedMeasTypes = 0x2FD2
+ Dev_AI_MaxSingleChanRate = 0x298C
+ Dev_AI_MaxMultiChanRate = 0x298D
+ Dev_AI_MinRate = 0x298E
+ Dev_AI_SimultaneousSamplingSupported = 0x298F
+ Dev_AI_SampModes = 0x2FDC
+ Dev_AI_TrigUsage = 0x2986
+ Dev_AI_VoltageRngs = 0x2990
+ Dev_AI_VoltageIntExcitDiscreteVals = 0x29C9
+ Dev_AI_VoltageIntExcitRangeVals = 0x29CA
+ Dev_AI_CurrentRngs = 0x2991
+ Dev_AI_CurrentIntExcitDiscreteVals = 0x29CB
+ Dev_AI_BridgeRngs = 0x2FD0
+ Dev_AI_ResistanceRngs = 0x2A15
+ Dev_AI_FreqRngs = 0x2992
+ Dev_AI_Gains = 0x2993
+ Dev_AI_Couplings = 0x2994
+ Dev_AI_LowpassCutoffFreqDiscreteVals = 0x2995
+ Dev_AI_LowpassCutoffFreqRangeVals = 0x29CF
+ Dev_AO_PhysicalChans = 0x231F
+ Dev_AO_SupportedOutputTypes = 0x2FD3
+ Dev_AO_SampClkSupported = 0x2996
+ Dev_AO_SampModes = 0x2FDD
+ Dev_AO_MaxRate = 0x2997
+ Dev_AO_MinRate = 0x2998
+ Dev_AO_TrigUsage = 0x2987
+ Dev_AO_VoltageRngs = 0x299B
+ Dev_AO_CurrentRngs = 0x299C
+ Dev_AO_Gains = 0x299D
+ Dev_DI_Lines = 0x2320
+ Dev_DI_Ports = 0x2321
+ Dev_DI_MaxRate = 0x2999
+ Dev_DI_TrigUsage = 0x2988
+ Dev_DO_Lines = 0x2322
+ Dev_DO_Ports = 0x2323
+ Dev_DO_MaxRate = 0x299A
+ Dev_DO_TrigUsage = 0x2989
+ Dev_CI_PhysicalChans = 0x2324
+ Dev_CI_SupportedMeasTypes = 0x2FD4
+ Dev_CI_TrigUsage = 0x298A
+ Dev_CI_SampClkSupported = 0x299E
+ Dev_CI_SampModes = 0x2FDE
+ Dev_CI_MaxSize = 0x299F
+ Dev_CI_MaxTimebase = 0x29A0
+ Dev_CO_PhysicalChans = 0x2325
+ Dev_CO_SupportedOutputTypes = 0x2FD5
+ Dev_CO_SampClkSupported = 0x2F5B
+ Dev_CO_SampModes = 0x2FDF
+ Dev_CO_TrigUsage = 0x298B
+ Dev_CO_MaxSize = 0x29A1
+ Dev_CO_MaxTimebase = 0x29A2
+ Dev_TEDS_HWTEDSSupported = 0x2FD6
+ Dev_NumDMAChans = 0x233C
+ Dev_BusType = 0x2326
+ Dev_PCI_BusNum = 0x2327
+ Dev_PCI_DevNum = 0x2328
+ Dev_PXI_ChassisNum = 0x2329
+ Dev_PXI_SlotNum = 0x232A
+ Dev_CompactDAQ_ChassisDevName = 0x29B7
+ Dev_CompactDAQ_SlotNum = 0x29B8
+ Dev_TCPIP_Hostname = 0x2A8B
+ Dev_TCPIP_EthernetIP = 0x2A8C
+ Dev_TCPIP_WirelessIP = 0x2A8D
+ Dev_Terminals = 0x2A40
+
+ Read_RelativeTo = 0x190A
+ Read_Offset = 0x190B
+ Read_ChannelsToRead = 0x1823
+ Read_ReadAllAvailSamp = 0x1215
+ Read_AutoStart = 0x1826
+ Read_OverWrite = 0x1211
+ Read_CurrReadPos = 0x1221
+ Read_AvailSampPerChan = 0x1223
+ Logging_FilePath = 0x2EC4
+ Logging_Mode = 0x2EC5
+ Logging_TDMS_GroupName = 0x2EC6
+ Logging_TDMS_Operation = 0x2EC7
+ Logging_Pause = 0x2FE3
+ Logging_SampsPerFile = 0x2FE4
+ Logging_FileWriteSize = 0x2FC3
+ Logging_FilePreallocationSize = 0x2FC6
+ Read_TotalSampPerChanAcquired = 0x192A
+ Read_CommonModeRangeErrorChansExist = 0x2A98
+ Read_CommonModeRangeErrorChans = 0x2A99
+ Read_OvercurrentChansExist = 0x29E6
+ Read_OvercurrentChans = 0x29E7
+ Read_OpenCurrentLoopChansExist = 0x2A09
+ Read_OpenCurrentLoopChans = 0x2A0A
+ Read_OpenThrmcplChansExist = 0x2A96
+ Read_OpenThrmcplChans = 0x2A97
+ Read_OverloadedChansExist = 0x2174
+ Read_OverloadedChans = 0x2175
+ Read_AccessoryInsertionOrRemovalDetected = 0x2F70
+ Read_DevsWithInsertedOrRemovedAccessories = 0x2F71
+ Read_ChangeDetect_HasOverflowed = 0x2194
+ Read_RawDataWidth = 0x217A
+ Read_NumChans = 0x217B
+ Read_DigitalLines_BytesPerChan = 0x217C
+ Read_WaitMode = 0x2232
+ Read_SleepTime = 0x22B0
+ RealTime_ConvLateErrorsToWarnings = 0x22EE
+ RealTime_NumOfWarmupIters = 0x22ED
+ RealTime_WaitForNextSampClkWaitMode = 0x22EF
+ RealTime_ReportMissedSamp = 0x2319
+ RealTime_WriteRecoveryMode = 0x231A
+ SwitchChan_Usage = 0x18E4
+ SwitchChan_AnlgBusSharingEnable = 0x2F9E
+ SwitchChan_MaxACCarryCurrent = 0x0648
+ SwitchChan_MaxACSwitchCurrent = 0x0646
+ SwitchChan_MaxACCarryPwr = 0x0642
+ SwitchChan_MaxACSwitchPwr = 0x0644
+ SwitchChan_MaxDCCarryCurrent = 0x0647
+ SwitchChan_MaxDCSwitchCurrent = 0x0645
+ SwitchChan_MaxDCCarryPwr = 0x0643
+ SwitchChan_MaxDCSwitchPwr = 0x0649
+ SwitchChan_MaxACVoltage = 0x0651
+ SwitchChan_MaxDCVoltage = 0x0650
+ SwitchChan_WireMode = 0x18E5
+ SwitchChan_Bandwidth = 0x0640
+ SwitchChan_Impedance = 0x0641
+ SwitchDev_SettlingTime = 0x1244
+ SwitchDev_AutoConnAnlgBus = 0x17DA
+ SwitchDev_PwrDownLatchRelaysAfterSettling = 0x22DB
+ SwitchDev_Settled = 0x1243
+ SwitchDev_RelayList = 0x17DC
+ SwitchDev_NumRelays = 0x18E6
+ SwitchDev_SwitchChanList = 0x18E7
+ SwitchDev_NumSwitchChans = 0x18E8
+ SwitchDev_NumRows = 0x18E9
+ SwitchDev_NumColumns = 0x18EA
+ SwitchDev_Topology = 0x193D
+ SwitchScan_BreakMode = 0x1247
+ SwitchScan_RepeatMode = 0x1248
+ SwitchScan_WaitingForAdv = 0x17D9
+ Scale_Descr = 0x1226
+ Scale_ScaledUnits = 0x191B
+ Scale_PreScaledUnits = 0x18F7
+ Scale_Type = 0x1929
+ Scale_Lin_Slope = 0x1227
+ Scale_Lin_YIntercept = 0x1228
+ Scale_Map_ScaledMax = 0x1229
+ Scale_Map_PreScaledMax = 0x1231
+ Scale_Map_ScaledMin = 0x1230
+ Scale_Map_PreScaledMin = 0x1232
+ Scale_Poly_ForwardCoeff = 0x1234
+ Scale_Poly_ReverseCoeff = 0x1235
+ Scale_Table_ScaledVals = 0x1236
+ Scale_Table_PreScaledVals = 0x1237
+ Sys_GlobalChans = 0x1265
+ Sys_Scales = 0x1266
+ Sys_Tasks = 0x1267
+ Sys_DevNames = 0x193B
+ Sys_NIDAQMajorVersion = 0x1272
+ Sys_NIDAQMinorVersion = 0x1923
+ Sys_NIDAQUpdateVersion = 0x2F22
+ Task_Name = 0x1276
+ Task_Channels = 0x1273
+ Task_NumChans = 0x2181
+ Task_Devices = 0x230E
+ Task_NumDevices = 0x29BA
+ Task_Complete = 0x1274
+ SampQuant_SampMode = 0x1300
+ SampQuant_SampPerChan = 0x1310
+ SampTimingType = 0x1347
+ SampClk_Rate = 0x1344
+ SampClk_MaxRate = 0x22C8
+ SampClk_Src = 0x1852
+ SampClk_ActiveEdge = 0x1301
+ SampClk_OverrunBehavior = 0x2EFC
+ SampClk_UnderflowBehavior = 0x2961
+ SampClk_TimebaseDiv = 0x18EB
+ SampClk_Term = 0x2F1B
+ SampClk_Timebase_Rate = 0x1303
+ SampClk_Timebase_Src = 0x1308
+ SampClk_Timebase_ActiveEdge = 0x18EC
+ SampClk_Timebase_MasterTimebaseDiv = 0x1305
+ SampClkTimebase_Term = 0x2F1C
+ SampClk_DigFltr_Enable = 0x221E
+ SampClk_DigFltr_MinPulseWidth = 0x221F
+ SampClk_DigFltr_TimebaseSrc = 0x2220
+ SampClk_DigFltr_TimebaseRate = 0x2221
+ SampClk_DigSync_Enable = 0x2222
+ Hshk_DelayAfterXfer = 0x22C2
+ Hshk_StartCond = 0x22C3
+ Hshk_SampleInputDataWhen = 0x22C4
+ ChangeDetect_DI_RisingEdgePhysicalChans = 0x2195
+ ChangeDetect_DI_FallingEdgePhysicalChans = 0x2196
+ ChangeDetect_DI_Tristate = 0x2EFA
+ OnDemand_SimultaneousAOEnable = 0x21A0
+ Implicit_UnderflowBehavior = 0x2EFD
+ AIConv_Rate = 0x1848
+ AIConv_MaxRate = 0x22C9
+ AIConv_Src = 0x1502
+ AIConv_ActiveEdge = 0x1853
+ AIConv_TimebaseDiv = 0x1335
+ AIConv_Timebase_Src = 0x1339
+ DelayFromSampClk_DelayUnits = 0x1304
+ DelayFromSampClk_Delay = 0x1317
+ AIConv_DigFltr_Enable = 0x2EDC
+ AIConv_DigFltr_MinPulseWidth = 0x2EDD
+ AIConv_DigFltr_TimebaseSrc = 0x2EDE
+ AIConv_DigFltr_TimebaseRate = 0x2EDF
+ AIConv_DigSync_Enable = 0x2EE0
+ MasterTimebase_Rate = 0x1495
+ MasterTimebase_Src = 0x1343
+ RefClk_Rate = 0x1315
+ RefClk_Src = 0x1316
+ SyncPulse_Src = 0x223D
+ SyncPulse_SyncTime = 0x223E
+ SyncPulse_MinDelayToStart = 0x223F
+ SyncPulse_ResetTime = 0x2F7C
+ SyncPulse_ResetDelay = 0x2F7D
+ SyncPulse_Term = 0x2F85
+ SyncClk_Interval = 0x2F7E
+ SampTimingEngine = 0x2A26
+ StartTrig_Type = 0x1393
+ StartTrig_Term = 0x2F1E
+ DigEdge_StartTrig_Src = 0x1407
+ DigEdge_StartTrig_Edge = 0x1404
+ DigEdge_StartTrig_DigFltr_Enable = 0x2223
+ DigEdge_StartTrig_DigFltr_MinPulseWidth = 0x2224
+ DigEdge_StartTrig_DigFltr_TimebaseSrc = 0x2225
+ DigEdge_StartTrig_DigFltr_TimebaseRate = 0x2226
+ DigEdge_StartTrig_DigSync_Enable = 0x2227
+ DigPattern_StartTrig_Src = 0x1410
+ DigPattern_StartTrig_Pattern = 0x2186
+ DigPattern_StartTrig_When = 0x1411
+ AnlgEdge_StartTrig_Src = 0x1398
+ AnlgEdge_StartTrig_Slope = 0x1397
+ AnlgEdge_StartTrig_Lvl = 0x1396
+ AnlgEdge_StartTrig_Hyst = 0x1395
+ AnlgEdge_StartTrig_Coupling = 0x2233
+ AnlgEdge_StartTrig_DigFltr_Enable = 0x2EE1
+ AnlgEdge_StartTrig_DigFltr_MinPulseWidth = 0x2EE2
+ AnlgEdge_StartTrig_DigFltr_TimebaseSrc = 0x2EE3
+ AnlgEdge_StartTrig_DigFltr_TimebaseRate = 0x2EE4
+ AnlgEdge_StartTrig_DigSync_Enable = 0x2EE5
+ AnlgWin_StartTrig_Src = 0x1400
+ AnlgWin_StartTrig_When = 0x1401
+ AnlgWin_StartTrig_Top = 0x1403
+ AnlgWin_StartTrig_Btm = 0x1402
+ AnlgWin_StartTrig_Coupling = 0x2234
+ AnlgWin_StartTrig_DigFltr_Enable = 0x2EFF
+ AnlgWin_StartTrig_DigFltr_MinPulseWidth = 0x2F00
+ AnlgWin_StartTrig_DigFltr_TimebaseSrc = 0x2F01
+ AnlgWin_StartTrig_DigFltr_TimebaseRate = 0x2F02
+ AnlgWin_StartTrig_DigSync_Enable = 0x2F03
+ StartTrig_Delay = 0x1856
+ StartTrig_DelayUnits = 0x18C8
+ StartTrig_Retriggerable = 0x190F
+ RefTrig_Type = 0x1419
+ RefTrig_PretrigSamples = 0x1445
+ RefTrig_Term = 0x2F1F
+ DigEdge_RefTrig_Src = 0x1434
+ DigEdge_RefTrig_Edge = 0x1430
+ DigEdge_RefTrig_DigFltr_Enable = 0x2ED7
+ DigEdge_RefTrig_DigFltr_MinPulseWidth = 0x2ED8
+ DigEdge_RefTrig_DigFltr_TimebaseSrc = 0x2ED9
+ DigEdge_RefTrig_DigFltr_TimebaseRate = 0x2EDA
+ DigEdge_RefTrig_DigSync_Enable = 0x2EDB
+ DigPattern_RefTrig_Src = 0x1437
+ DigPattern_RefTrig_Pattern = 0x2187
+ DigPattern_RefTrig_When = 0x1438
+ AnlgEdge_RefTrig_Src = 0x1424
+ AnlgEdge_RefTrig_Slope = 0x1423
+ AnlgEdge_RefTrig_Lvl = 0x1422
+ AnlgEdge_RefTrig_Hyst = 0x1421
+ AnlgEdge_RefTrig_Coupling = 0x2235
+ AnlgEdge_RefTrig_DigFltr_Enable = 0x2EE6
+ AnlgEdge_RefTrig_DigFltr_MinPulseWidth = 0x2EE7
+ AnlgEdge_RefTrig_DigFltr_TimebaseSrc = 0x2EE8
+ AnlgEdge_RefTrig_DigFltr_TimebaseRate = 0x2EE9
+ AnlgEdge_RefTrig_DigSync_Enable = 0x2EEA
+ AnlgWin_RefTrig_Src = 0x1426
+ AnlgWin_RefTrig_When = 0x1427
+ AnlgWin_RefTrig_Top = 0x1429
+ AnlgWin_RefTrig_Btm = 0x1428
+ AnlgWin_RefTrig_Coupling = 0x1857
+ AnlgWin_RefTrig_DigFltr_Enable = 0x2EEB
+ AnlgWin_RefTrig_DigFltr_MinPulseWidth = 0x2EEC
+ AnlgWin_RefTrig_DigFltr_TimebaseSrc = 0x2EED
+ AnlgWin_RefTrig_DigFltr_TimebaseRate = 0x2EEE
+ AnlgWin_RefTrig_DigSync_Enable = 0x2EEF
+ RefTrig_AutoTrigEnable = 0x2EC1
+ RefTrig_AutoTriggered = 0x2EC2
+ RefTrig_Delay = 0x1483
+ AdvTrig_Type = 0x1365
+ DigEdge_AdvTrig_Src = 0x1362
+ DigEdge_AdvTrig_Edge = 0x1360
+ DigEdge_AdvTrig_DigFltr_Enable = 0x2238
+ HshkTrig_Type = 0x22B7
+ Interlocked_HshkTrig_Src = 0x22B8
+ Interlocked_HshkTrig_AssertedLvl = 0x22B9
+ PauseTrig_Type = 0x1366
+ PauseTrig_Term = 0x2F20
+ AnlgLvl_PauseTrig_Src = 0x1370
+ AnlgLvl_PauseTrig_When = 0x1371
+ AnlgLvl_PauseTrig_Lvl = 0x1369
+ AnlgLvl_PauseTrig_Hyst = 0x1368
+ AnlgLvl_PauseTrig_Coupling = 0x2236
+ AnlgLvl_PauseTrig_DigFltr_Enable = 0x2EF0
+ AnlgLvl_PauseTrig_DigFltr_MinPulseWidth = 0x2EF1
+ AnlgLvl_PauseTrig_DigFltr_TimebaseSrc = 0x2EF2
+ AnlgLvl_PauseTrig_DigFltr_TimebaseRate = 0x2EF3
+ AnlgLvl_PauseTrig_DigSync_Enable = 0x2EF4
+ AnlgWin_PauseTrig_Src = 0x1373
+ AnlgWin_PauseTrig_When = 0x1374
+ AnlgWin_PauseTrig_Top = 0x1376
+ AnlgWin_PauseTrig_Btm = 0x1375
+ AnlgWin_PauseTrig_Coupling = 0x2237
+ AnlgWin_PauseTrig_DigFltr_Enable = 0x2EF5
+ AnlgWin_PauseTrig_DigFltr_MinPulseWidth = 0x2EF6
+ AnlgWin_PauseTrig_DigFltr_TimebaseSrc = 0x2EF7
+ AnlgWin_PauseTrig_DigFltr_TimebaseRate = 0x2EF8
+ AnlgWin_PauseTrig_DigSync_Enable = 0x2EF9
+ DigLvl_PauseTrig_Src = 0x1379
+ DigLvl_PauseTrig_When = 0x1380
+ DigLvl_PauseTrig_DigFltr_Enable = 0x2228
+ DigLvl_PauseTrig_DigFltr_MinPulseWidth = 0x2229
+ DigLvl_PauseTrig_DigFltr_TimebaseSrc = 0x222A
+ DigLvl_PauseTrig_DigFltr_TimebaseRate = 0x222B
+ DigLvl_PauseTrig_DigSync_Enable = 0x222C
+ DigPattern_PauseTrig_Src = 0x216F
+ DigPattern_PauseTrig_Pattern = 0x2188
+ DigPattern_PauseTrig_When = 0x2170
+ ArmStartTrig_Type = 0x1414
+ ArmStart_Term = 0x2F7F
+ DigEdge_ArmStartTrig_Src = 0x1417
+ DigEdge_ArmStartTrig_Edge = 0x1415
+ DigEdge_ArmStartTrig_DigFltr_Enable = 0x222D
+ DigEdge_ArmStartTrig_DigFltr_MinPulseWidth = 0x222E
+ DigEdge_ArmStartTrig_DigFltr_TimebaseSrc = 0x222F
+ DigEdge_ArmStartTrig_DigFltr_TimebaseRate = 0x2230
+ DigEdge_ArmStartTrig_DigSync_Enable = 0x2231
+ Trigger_SyncType = 0x2F80
+ Watchdog_Timeout = 0x21A9
+ WatchdogExpirTrig_Type = 0x21A3
+ DigEdge_WatchdogExpirTrig_Src = 0x21A4
+ DigEdge_WatchdogExpirTrig_Edge = 0x21A5
+ Watchdog_DO_ExpirState = 0x21A7
+ Watchdog_HasExpired = 0x21A8
+ Write_RelativeTo = 0x190C
+ Write_Offset = 0x190D
+ Write_RegenMode = 0x1453
+ Write_CurrWritePos = 0x1458
+ Write_OvercurrentChansExist = 0x29E8
+ Write_OvercurrentChans = 0x29E9
+ Write_OvertemperatureChansExist = 0x2A84
+ Write_OpenCurrentLoopChansExist = 0x29EA
+ Write_OpenCurrentLoopChans = 0x29EB
+ Write_PowerSupplyFaultChansExist = 0x29EC
+ Write_PowerSupplyFaultChans = 0x29ED
+ Write_SpaceAvail = 0x1460
+ Write_TotalSampPerChanGenerated = 0x192B
+ Write_RawDataWidth = 0x217D
+ Write_NumChans = 0x217E
+ Write_WaitMode = 0x22B1
+ Write_SleepTime = 0x22B2
+ Write_NextWriteIsLast = 0x296C
+ Write_DigitalLines_BytesPerChan = 0x217F
+ PhysicalChan_AI_SupportedMeasTypes = 0x2FD7
+ PhysicalChan_AI_TermCfgs = 0x2342
+ PhysicalChan_AI_InputSrcs = 0x2FD8
+ PhysicalChan_AO_SupportedOutputTypes = 0x2FD9
+ PhysicalChan_AO_TermCfgs = 0x29A3
+ PhysicalChan_AO_ManualControlEnable = 0x2A1E
+ PhysicalChan_AO_ManualControl_ShortDetected = 0x2EC3
+ PhysicalChan_AO_ManualControlAmplitude = 0x2A1F
+ PhysicalChan_AO_ManualControlFreq = 0x2A20
+ PhysicalChan_DI_PortWidth = 0x29A4
+ PhysicalChan_DI_SampClkSupported = 0x29A5
+ PhysicalChan_DI_SampModes = 0x2FE0
+ PhysicalChan_DI_ChangeDetectSupported = 0x29A6
+ PhysicalChan_DO_PortWidth = 0x29A7
+ PhysicalChan_DO_SampClkSupported = 0x29A8
+ PhysicalChan_DO_SampModes = 0x2FE1
+ PhysicalChan_CI_SupportedMeasTypes = 0x2FDA
+ PhysicalChan_CO_SupportedOutputTypes = 0x2FDB
+ PhysicalChan_TEDS_MfgID = 0x21DA
+ PhysicalChan_TEDS_ModelNum = 0x21DB
+ PhysicalChan_TEDS_SerialNum = 0x21DC
+ PhysicalChan_TEDS_VersionNum = 0x21DD
+ PhysicalChan_TEDS_VersionLetter = 0x21DE
+ PhysicalChan_TEDS_BitStream = 0x21DF
+ PhysicalChan_TEDS_TemplateIDs = 0x228F
+ PersistedTask_Author = 0x22CC
+ PersistedTask_AllowInteractiveEditing = 0x22CD
+ PersistedTask_AllowInteractiveDeletion = 0x22CE
+ PersistedChan_Author = 0x22D0
+ PersistedChan_AllowInteractiveEditing = 0x22D1
+ PersistedChan_AllowInteractiveDeletion = 0x22D2
+ PersistedScale_Author = 0x22D4
+ PersistedScale_AllowInteractiveEditing = 0x22D5
+ PersistedScale_AllowInteractiveDeletion = 0x22D6
+ ReadWaitMode = Read_WaitMode
+
+ Val_Task_Start = 0
+ Val_Task_Stop = 1
+ Val_Task_Verify = 2
+ Val_Task_Commit = 3
+ Val_Task_Reserve = 4
+ Val_Task_Unreserve = 5
+ Val_Task_Abort = 6
+ Val_SynchronousEventCallbacks = (1<<0)
+ Val_Acquired_Into_Buffer = 1
+ Val_Transferred_From_Buffer = 2
+ Val_ResetTimer = 0
+ Val_ClearExpiration = 1
+ Val_ChanPerLine = 0
+ Val_ChanForAllLines = 1
+ Val_GroupByChannel = 0
+ Val_GroupByScanNumber = 1
+ Val_DoNotInvertPolarity = 0
+ Val_InvertPolarity = 1
+ Val_Action_Commit = 0
+ Val_Action_Cancel = 1
+ Val_AdvanceTrigger = 12488
+ Val_Rising = 10280
+ Val_Falling = 10171
+ Val_PathStatus_Available = 10431
+ Val_PathStatus_AlreadyExists = 10432
+ Val_PathStatus_Unsupported = 10433
+ Val_PathStatus_ChannelInUse = 10434
+ Val_PathStatus_SourceChannelConflict = 10435
+ Val_PathStatus_ChannelReservedForRouting = 10436
+ Val_DegC = 10143
+ Val_DegF = 10144
+ Val_Kelvins = 10325
+ Val_DegR = 10145
+ Val_High = 10192
+ Val_Low = 10214
+ Val_Tristate = 10310
+ Val_PullUp = 15950
+ Val_PullDown = 15951
+ Val_ChannelVoltage = 0
+ Val_ChannelCurrent = 1
+ Val_Open = 10437
+ Val_Closed = 10438
+ Val_Loopback0 = 0
+ Val_Loopback180 = 1
+ Val_Ground = 2
+ Val_Cfg_Default = -1
+ Val_Default = -1
+ Val_WaitInfinitely = -1.0
+ Val_Auto = -1
+ Val_Save_Overwrite = (1<<0)
+ Val_Save_AllowInteractiveEditing = (1<<1)
+ Val_Save_AllowInteractiveDeletion = (1<<2)
+ Val_Bit_TriggerUsageTypes_Advance = (1<<0)
+ Val_Bit_TriggerUsageTypes_Pause = (1<<1)
+ Val_Bit_TriggerUsageTypes_Reference = (1<<2)
+ Val_Bit_TriggerUsageTypes_Start = (1<<3)
+ Val_Bit_TriggerUsageTypes_Handshake = (1<<4)
+ Val_Bit_TriggerUsageTypes_ArmStart = (1<<5)
+ Val_Bit_CouplingTypes_AC = (1<<0)
+ Val_Bit_CouplingTypes_DC = (1<<1)
+ Val_Bit_CouplingTypes_Ground = (1<<2)
+ Val_Bit_CouplingTypes_HFReject = (1<<3)
+ Val_Bit_CouplingTypes_LFReject = (1<<4)
+ Val_Bit_CouplingTypes_NoiseReject = (1<<5)
+ Val_Bit_TermCfg_RSE = (1<<0)
+ Val_Bit_TermCfg_NRSE = (1<<1)
+ Val_Bit_TermCfg_Diff = (1<<2)
+ Val_Bit_TermCfg_PseudoDIFF = (1<<3)
+ Val_4Wire = 4
+ Val_5Wire = 5
+ Val_HighResolution = 10195
+ Val_HighSpeed = 14712
+ Val_Best50HzRejection = 14713
+ Val_Best60HzRejection = 14714
+ Val_Custom = 10137
+ Val_Voltage = 10322
+ Val_VoltageRMS = 10350
+ Val_Current = 10134
+ Val_CurrentRMS = 10351
+ Val_Voltage_CustomWithExcitation = 10323
+ Val_Bridge = 15908
+ Val_Freq_Voltage = 10181
+ Val_Resistance = 10278
+ Val_Temp_TC = 10303
+ Val_Temp_Thrmstr = 10302
+ Val_Temp_RTD = 10301
+ Val_Temp_BuiltInSensor = 10311
+ Val_Strain_Gage = 10300
+ Val_Position_LVDT = 10352
+ Val_Position_RVDT = 10353
+ Val_Position_EddyCurrentProximityProbe = 14835
+ Val_Accelerometer = 10356
+ Val_Force_Bridge = 15899
+ Val_Force_IEPESensor = 15895
+ Val_Pressure_Bridge = 15902
+ Val_SoundPressure_Microphone = 10354
+ Val_Torque_Bridge = 15905
+ Val_TEDS_Sensor = 12531
+ Val_ZeroVolts = 12526
+ Val_HighImpedance = 12527
+ Val_MaintainExistingValue = 12528
+ Val_Voltage = 10322
+ Val_Current = 10134
+ Val_FuncGen = 14750
+ Val_mVoltsPerG = 12509
+ Val_VoltsPerG = 12510
+ Val_AccelUnit_g = 10186
+ Val_MetersPerSecondSquared = 12470
+ Val_InchesPerSecondSquared = 12471
+ Val_FromCustomScale = 10065
+ Val_FiniteSamps = 10178
+ Val_ContSamps = 10123
+ Val_HWTimedSinglePoint = 12522
+ Val_AboveLvl = 10093
+ Val_BelowLvl = 10107
+ Val_Degrees = 10146
+ Val_Radians = 10273
+ Val_FromCustomScale = 10065
+ Val_Degrees = 10146
+ Val_Radians = 10273
+ Val_Ticks = 10304
+ Val_FromCustomScale = 10065
+ Val_None = 10230
+ Val_Once = 10244
+ Val_EverySample = 10164
+ Val_NoAction = 10227
+ Val_BreakBeforeMake = 10110
+ Val_FullBridge = 10182
+ Val_HalfBridge = 10187
+ Val_QuarterBridge = 10270
+ Val_NoBridge = 10228
+ Val_VoltsPerVolt = 15896
+ Val_mVoltsPerVolt = 15897
+ Val_Newtons = 15875
+ Val_Pounds = 15876
+ Val_KilogramForce = 15877
+ Val_Pascals = 10081
+ Val_PoundsPerSquareInch = 15879
+ Val_Bar = 15880
+ Val_NewtonMeters = 15881
+ Val_InchOunces = 15882
+ Val_InchPounds = 15883
+ Val_FootPounds = 15884
+ Val_VoltsPerVolt = 15896
+ Val_mVoltsPerVolt = 15897
+ Val_FromCustomScale = 10065
+ Val_FromTEDS = 12516
+ Val_PCI = 12582
+ Val_PCIe = 13612
+ Val_PXI = 12583
+ Val_PXIe = 14706
+ Val_SCXI = 12584
+ Val_SCC = 14707
+ Val_PCCard = 12585
+ Val_USB = 12586
+ Val_CompactDAQ = 14637
+ Val_TCPIP = 14828
+ Val_Unknown = 12588
+ Val_SwitchBlock = 15870
+ Val_CountEdges = 10125
+ Val_Freq = 10179
+ Val_Period = 10256
+ Val_PulseWidth = 10359
+ Val_SemiPeriod = 10289
+ Val_PulseFrequency = 15864
+ Val_PulseTime = 15865
+ Val_PulseTicks = 15866
+ Val_Position_AngEncoder = 10360
+ Val_Position_LinEncoder = 10361
+ Val_TwoEdgeSep = 10267
+ Val_GPS_Timestamp = 10362
+ Val_BuiltIn = 10200
+ Val_ConstVal = 10116
+ Val_Chan = 10113
+ Val_Pulse_Time = 10269
+ Val_Pulse_Freq = 10119
+ Val_Pulse_Ticks = 10268
+ Val_AI = 10100
+ Val_AO = 10102
+ Val_DI = 10151
+ Val_DO = 10153
+ Val_CI = 10131
+ Val_CO = 10132
+ Val_Unconstrained = 14708
+ Val_FixedHighFreq = 14709
+ Val_FixedLowFreq = 14710
+ Val_Fixed50PercentDutyCycle = 14711
+ Val_CountUp = 10128
+ Val_CountDown = 10124
+ Val_ExtControlled = 10326
+ Val_LowFreq1Ctr = 10105
+ Val_HighFreq2Ctr = 10157
+ Val_LargeRng2Ctr = 10205
+ Val_AC = 10045
+ Val_DC = 10050
+ Val_GND = 10066
+ Val_AC = 10045
+ Val_DC = 10050
+ Val_Internal = 10200
+ Val_External = 10167
+ Val_Amps = 10342
+ Val_FromCustomScale = 10065
+ Val_FromTEDS = 12516
+ Val_Amps = 10342
+ Val_FromCustomScale = 10065
+ Val_RightJustified = 10279
+ Val_LeftJustified = 10209
+ Val_DMA = 10054
+ Val_Interrupts = 10204
+ Val_ProgrammedIO = 10264
+ Val_USBbulk = 12590
+ Val_OnbrdMemMoreThanHalfFull = 10237
+ Val_OnbrdMemFull = 10236
+ Val_OnbrdMemCustomThreshold = 12577
+ Val_ActiveDrive = 12573
+ Val_OpenCollector = 12574
+ Val_High = 10192
+ Val_Low = 10214
+ Val_Tristate = 10310
+ Val_NoChange = 10160
+ Val_PatternMatches = 10254
+ Val_PatternDoesNotMatch = 10253
+ Val_SampClkPeriods = 10286
+ Val_Seconds = 10364
+ Val_Ticks = 10304
+ Val_Seconds = 10364
+ Val_Ticks = 10304
+ Val_Seconds = 10364
+ Val_mVoltsPerMil = 14836
+ Val_VoltsPerMil = 14837
+ Val_mVoltsPerMillimeter = 14838
+ Val_VoltsPerMillimeter = 14839
+ Val_mVoltsPerMicron = 14840
+ Val_Rising = 10280
+ Val_Falling = 10171
+ Val_X1 = 10090
+ Val_X2 = 10091
+ Val_X4 = 10092
+ Val_TwoPulseCounting = 10313
+ Val_AHighBHigh = 10040
+ Val_AHighBLow = 10041
+ Val_ALowBHigh = 10042
+ Val_ALowBLow = 10043
+ Val_DC = 10050
+ Val_AC = 10045
+ Val_Internal = 10200
+ Val_External = 10167
+ Val_None = 10230
+ Val_Voltage = 10322
+ Val_Current = 10134
+ Val_Pulse = 10265
+ Val_Toggle = 10307
+ Val_Pulse = 10265
+ Val_Lvl = 10210
+ Val_Interlocked = 12549
+ Val_Pulse = 10265
+ Val_mVoltsPerNewton = 15891
+ Val_mVoltsPerPound = 15892
+ Val_Newtons = 15875
+ Val_Pounds = 15876
+ Val_KilogramForce = 15877
+ Val_FromCustomScale = 10065
+ Val_Hz = 10373
+ Val_FromCustomScale = 10065
+ Val_Hz = 10373
+ Val_Hz = 10373
+ Val_Ticks = 10304
+ Val_FromCustomScale = 10065
+ Val_Sine = 14751
+ Val_Triangle = 14752
+ Val_Square = 14753
+ Val_Sawtooth = 14754
+ Val_IRIGB = 10070
+ Val_PPS = 10080
+ Val_None = 10230
+ Val_Immediate = 10198
+ Val_WaitForHandshakeTriggerAssert = 12550
+ Val_WaitForHandshakeTriggerDeassert = 12551
+ Val_OnBrdMemMoreThanHalfFull = 10237
+ Val_OnBrdMemNotEmpty = 10241
+ Val_OnbrdMemCustomThreshold = 12577
+ Val_WhenAcqComplete = 12546
+ Val_RSE = 10083
+ Val_NRSE = 10078
+ Val_Diff = 10106
+ Val_PseudoDiff = 12529
+ Val_mVoltsPerVoltPerMillimeter = 12506
+ Val_mVoltsPerVoltPerMilliInch = 12505
+ Val_Meters = 10219
+ Val_Inches = 10379
+ Val_FromCustomScale = 10065
+ Val_Meters = 10219
+ Val_Inches = 10379
+ Val_Ticks = 10304
+ Val_FromCustomScale = 10065
+ Val_High = 10192
+ Val_Low = 10214
+ Val_Off = 10231
+ Val_Log = 15844
+ Val_LogAndRead = 15842
+ Val_Open = 10437
+ Val_OpenOrCreate = 15846
+ Val_CreateOrReplace = 15847
+ Val_Create = 15848
+ Val_2point5V = 14620
+ Val_3point3V = 14621
+ Val_5V = 14619
+ Val_SameAsSampTimebase = 10284
+ Val_100MHzTimebase = 15857
+ Val_SameAsMasterTimebase = 10282
+ Val_20MHzTimebase = 12537
+ Val_80MHzTimebase = 14636
+ Val_AM = 14756
+ Val_FM = 14757
+ Val_None = 10230
+ Val_OnBrdMemEmpty = 10235
+ Val_OnBrdMemHalfFullOrLess = 10239
+ Val_OnBrdMemNotFull = 10242
+ Val_RSE = 10083
+ Val_Diff = 10106
+ Val_PseudoDiff = 12529
+ Val_StopTaskAndError = 15862
+ Val_IgnoreOverruns = 15863
+ Val_OverwriteUnreadSamps = 10252
+ Val_DoNotOverwriteUnreadSamps = 10159
+ Val_ActiveHigh = 10095
+ Val_ActiveLow = 10096
+ Val_Pascals = 10081
+ Val_PoundsPerSquareInch = 15879
+ Val_Bar = 15880
+ Val_FromCustomScale = 10065
+ Val_MSeriesDAQ = 14643
+ Val_XSeriesDAQ = 15858
+ Val_ESeriesDAQ = 14642
+ Val_SSeriesDAQ = 14644
+ Val_BSeriesDAQ = 14662
+ Val_SCSeriesDAQ = 14645
+ Val_USBDAQ = 14646
+ Val_AOSeries = 14647
+ Val_DigitalIO = 14648
+ Val_TIOSeries = 14661
+ Val_DynamicSignalAcquisition = 14649
+ Val_Switches = 14650
+ Val_CompactDAQChassis = 14658
+ Val_CSeriesModule = 14659
+ Val_SCXIModule = 14660
+ Val_SCCConnectorBlock = 14704
+ Val_SCCModule = 14705
+ Val_NIELVIS = 14755
+ Val_NetworkDAQ = 14829
+ Val_SCExpress = 15886
+ Val_Unknown = 12588
+ Val_Pt3750 = 12481
+ Val_Pt3851 = 10071
+ Val_Pt3911 = 12482
+ Val_Pt3916 = 10069
+ Val_Pt3920 = 10053
+ Val_Pt3928 = 12483
+ Val_Custom = 10137
+ Val_mVoltsPerVoltPerDegree = 12507
+ Val_mVoltsPerVoltPerRadian = 12508
+ Val_None = 10230
+ Val_LosslessPacking = 12555
+ Val_LossyLSBRemoval = 12556
+ Val_FirstSample = 10424
+ Val_CurrReadPos = 10425
+ Val_RefTrig = 10426
+ Val_FirstPretrigSamp = 10427
+ Val_MostRecentSamp = 10428
+ Val_AllowRegen = 10097
+ Val_DoNotAllowRegen = 10158
+ Val_2Wire = 2
+ Val_3Wire = 3
+ Val_4Wire = 4
+ Val_Ohms = 10384
+ Val_FromCustomScale = 10065
+ Val_FromTEDS = 12516
+ Val_Ohms = 10384
+ Val_FromCustomScale = 10065
+ Val_Bits = 10109
+ Val_SCXI1124Range0to1V = 14629
+ Val_SCXI1124Range0to5V = 14630
+ Val_SCXI1124Range0to10V = 14631
+ Val_SCXI1124RangeNeg1to1V = 14632
+ Val_SCXI1124RangeNeg5to5V = 14633
+ Val_SCXI1124RangeNeg10to10V = 14634
+ Val_SCXI1124Range0to20mA = 14635
+ Val_SampClkActiveEdge = 14617
+ Val_SampClkInactiveEdge = 14618
+ Val_HandshakeTriggerAsserts = 12552
+ Val_HandshakeTriggerDeasserts = 12553
+ Val_SampClk = 10388
+ Val_BurstHandshake = 12548
+ Val_Handshake = 10389
+ Val_Implicit = 10451
+ Val_OnDemand = 10390
+ Val_ChangeDetection = 12504
+ Val_PipelinedSampClk = 14668
+ Val_Linear = 10447
+ Val_MapRanges = 10448
+ Val_Polynomial = 10449
+ Val_Table = 10450
+ Val_Polynomial = 10449
+ Val_Table = 10450
+ Val_Polynomial = 10449
+ Val_Table = 10450
+ Val_None = 10230
+ Val_None = 10230
+ Val_TwoPointLinear = 15898
+ Val_Table = 10450
+ Val_Polynomial = 10449
+ Val_A = 12513
+ Val_B = 12514
+ Val_AandB = 12515
+ Val_R1 = 12465
+ Val_R2 = 12466
+ Val_R3 = 12467
+ Val_R4 = 14813
+ Val_None = 10230
+ Val_AIConvertClock = 12484
+ Val_10MHzRefClock = 12536
+ Val_20MHzTimebaseClock = 12486
+ Val_SampleClock = 12487
+ Val_AdvanceTrigger = 12488
+ Val_ReferenceTrigger = 12490
+ Val_StartTrigger = 12491
+ Val_AdvCmpltEvent = 12492
+ Val_AIHoldCmpltEvent = 12493
+ Val_CounterOutputEvent = 12494
+ Val_ChangeDetectionEvent = 12511
+ Val_WDTExpiredEvent = 12512
+ Val_SampleCompleteEvent = 12530
+ Val_CounterOutputEvent = 12494
+ Val_ChangeDetectionEvent = 12511
+ Val_SampleClock = 12487
+ Val_RisingSlope = 10280
+ Val_FallingSlope = 10171
+ Val_Pascals = 10081
+ Val_FromCustomScale = 10065
+ Val_Internal = 10200
+ Val_External = 10167
+ Val_FullBridgeI = 10183
+ Val_FullBridgeII = 10184
+ Val_FullBridgeIII = 10185
+ Val_HalfBridgeI = 10188
+ Val_HalfBridgeII = 10189
+ Val_QuarterBridgeI = 10271
+ Val_QuarterBridgeII = 10272
+ Val_Strain = 10299
+ Val_FromCustomScale = 10065
+ Val_Finite = 10172
+ Val_Cont = 10117
+ Val_Source = 10439
+ Val_Load = 10440
+ Val_ReservedForRouting = 10441
+ Val_None = 10230
+ Val_Master = 15888
+ Val_Slave = 15889
+ Val_FromCustomScale = 10065
+ Val_FromTEDS = 12516
+ Val_DegC = 10143
+ Val_DegF = 10144
+ Val_Kelvins = 10325
+ Val_DegR = 10145
+ Val_FromCustomScale = 10065
+ Val_J_Type_TC = 10072
+ Val_K_Type_TC = 10073
+ Val_N_Type_TC = 10077
+ Val_R_Type_TC = 10082
+ Val_S_Type_TC = 10085
+ Val_T_Type_TC = 10086
+ Val_B_Type_TC = 10047
+ Val_E_Type_TC = 10055
+ Val_Seconds = 10364
+ Val_FromCustomScale = 10065
+ Val_Seconds = 10364
+ Val_Seconds = 10364
+ Val_Ticks = 10304
+ Val_FromCustomScale = 10065
+ Val_SingleCycle = 14613
+ Val_Multicycle = 14614
+ Val_NewtonMeters = 15881
+ Val_InchOunces = 15882
+ Val_InchPounds = 15883
+ Val_FootPounds = 15884
+ Val_FromCustomScale = 10065
+ Val_DigEdge = 10150
+ Val_None = 10230
+ Val_DigEdge = 10150
+ Val_Software = 10292
+ Val_None = 10230
+ Val_AnlgLvl = 10101
+ Val_AnlgWin = 10103
+ Val_DigLvl = 10152
+ Val_DigPattern = 10398
+ Val_None = 10230
+ Val_AnlgEdge = 10099
+ Val_DigEdge = 10150
+ Val_DigPattern = 10398
+ Val_AnlgWin = 10103
+ Val_None = 10230
+ Val_Interlocked = 12549
+ Val_None = 10230
+ Val_HaltOutputAndError = 14615
+ Val_PauseUntilDataAvailable = 14616
+ Val_Volts = 10348
+ Val_Amps = 10342
+ Val_DegF = 10144
+ Val_DegC = 10143
+ Val_DegR = 10145
+ Val_Kelvins = 10325
+ Val_Strain = 10299
+ Val_Ohms = 10384
+ Val_Hz = 10373
+ Val_Seconds = 10364
+ Val_Meters = 10219
+ Val_Inches = 10379
+ Val_Degrees = 10146
+ Val_Radians = 10273
+ Val_Ticks = 10304
+ Val_g = 10186
+ Val_MetersPerSecondSquared = 12470
+ Val_Pascals = 10081
+ Val_Newtons = 15875
+ Val_Pounds = 15876
+ Val_KilogramForce = 15877
+ Val_PoundsPerSquareInch = 15879
+ Val_Bar = 15880
+ Val_NewtonMeters = 15881
+ Val_InchOunces = 15882
+ Val_InchPounds = 15883
+ Val_FootPounds = 15884
+ Val_VoltsPerVolt = 15896
+ Val_mVoltsPerVolt = 15897
+ Val_FromTEDS = 12516
+ Val_Volts = 10348
+ Val_FromCustomScale = 10065
+ Val_FromTEDS = 12516
+ Val_Volts = 10348
+ Val_FromCustomScale = 10065
+ Val_WaitForInterrupt = 12523
+ Val_Poll = 12524
+ Val_Yield = 12525
+ Val_Sleep = 12547
+ Val_Poll = 12524
+ Val_Yield = 12525
+ Val_Sleep = 12547
+ Val_WaitForInterrupt = 12523
+ Val_Poll = 12524
+ Val_WaitForInterrupt = 12523
+ Val_Poll = 12524
+ Val_EnteringWin = 10163
+ Val_LeavingWin = 10208
+ Val_InsideWin = 10199
+ Val_OutsideWin = 10251
+ Val_WriteToEEPROM = 12538
+ Val_WriteToPROM = 12539
+ Val_DoNotWrite = 12540
+ Val_FirstSample = 10424
+ Val_CurrWritePos = 10430
+ Val_Switch_Topology_1127_Independent = "1127/Independent"
+ Val_Switch_Topology_1128_Independent = "1128/Independent"
+ Val_Switch_Topology_1130_Independent = "1130/Independent"
+ Val_Switch_Topology_1160_16_SPDT = "1160/16-SPDT"
+ Val_Switch_Topology_1161_8_SPDT = "1161/8-SPDT"
+ Val_Switch_Topology_1166_32_SPDT = "1166/32-SPDT"
+ Val_Switch_Topology_1166_16_DPDT = "1166/16-DPDT"
+ Val_Switch_Topology_1167_Independent = "1167/Independent"
+ Val_Switch_Topology_1169_100_SPST = "1169/100-SPST"
+ Val_Switch_Topology_1169_50_DPST = "1169/50-DPST"
+ Val_Switch_Topology_1192_8_SPDT = "1192/8-SPDT"
+ Val_Switch_Topology_1193_Independent = "1193/Independent"
+ Val_Switch_Topology_2510_Independent = "2510/Independent"
+ Val_Switch_Topology_2512_Independent = "2512/Independent"
+ Val_Switch_Topology_2514_Independent = "2514/Independent"
+ Val_Switch_Topology_2515_Independent = "2515/Independent"
+ Val_Switch_Topology_2527_Independent = "2527/Independent"
+ Val_Switch_Topology_2530_Independent = "2530/Independent"
+ Val_Switch_Topology_2548_4_SPDT = "2548/4-SPDT"
+ Val_Switch_Topology_2558_4_SPDT = "2558/4-SPDT"
+ Val_Switch_Topology_2564_16_SPST = "2564/16-SPST"
+ Val_Switch_Topology_2564_8_DPST = "2564/8-DPST"
+ Val_Switch_Topology_2565_16_SPST = "2565/16-SPST"
+ Val_Switch_Topology_2566_16_SPDT = "2566/16-SPDT"
+ Val_Switch_Topology_2566_8_DPDT = "2566/8-DPDT"
+ Val_Switch_Topology_2567_Independent = "2567/Independent"
+ Val_Switch_Topology_2568_31_SPST = "2568/31-SPST"
+ Val_Switch_Topology_2568_15_DPST = "2568/15-DPST"
+ Val_Switch_Topology_2569_100_SPST = "2569/100-SPST"
+ Val_Switch_Topology_2569_50_DPST = "2569/50-DPST"
+ Val_Switch_Topology_2570_40_SPDT = "2570/40-SPDT"
+ Val_Switch_Topology_2570_20_DPDT = "2570/20-DPDT"
+ Val_Switch_Topology_2571_66_SPDT = "2571/66-SPDT"
+ Val_Switch_Topology_2576_Independent = "2576/Independent"
+ Val_Switch_Topology_2584_Independent = "2584/Independent"
+ Val_Switch_Topology_2586_10_SPST = "2586/10-SPST"
+ Val_Switch_Topology_2586_5_DPST = "2586/5-DPST"
+ Val_Switch_Topology_2593_Independent = "2593/Independent"
+ Val_Switch_Topology_2599_2_SPDT = "2599/2-SPDT"
+
+import ctypes as ct
+
+class DoNothing(object):
+
+ def from_param(self, param):
+ return param
+
+class Types(metaclass=RichEnum):
+
+ _ = DoNothing()
+
+ void_p = ct.c_void_p
+
+ TaskHandle = ct.c_void_p
+ #TaskHandle = ct.c_uint32
+ bool32 = ct.c_uint32
+
+ string = ct.c_char_p
+
+ int8 = ct.c_int8
+ uInt8 = ct.c_uint8
+
+ int16 = ct.c_int16
+ uInt16 = ct.c_uint16
+
+ int32 = ct.c_int32
+ uInt32 = ct.c_uint32
+
+ int64 = ct.c_int64
+ uInt64 = ct.c_uint64
+
+ float32 = ct.c_float
+ float64 = ct.c_double
diff --git a/lantz/drivers/legacy/ni/daqmx/tasks.py b/lantz/drivers/legacy/ni/daqmx/tasks.py
new file mode 100644
index 0000000..2a437b4
--- /dev/null
+++ b/lantz/drivers/legacy/ni/daqmx/tasks.py
@@ -0,0 +1,525 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.ni.daqmx.tasks
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Implementation of specialized tasks clases.
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+import numpy as np
+
+from lantz import Feat, Action
+from lantz.foreign import RetStr, RetTuple, RetValue
+
+from .base import Task, Channel
+from .constants import Constants
+
+_GROUP_BY = {'scan': Constants.Val_GroupByScanNumber,
+ 'channel': Constants.Val_GroupByChannel}
+
+
+
+class AnalogInputTask(Task):
+ """Analog Input Task
+ """
+
+ IO_TYPE = 'AI'
+
+ @Feat()
+ def max_convert_rate(self):
+ """Maximum convert rate supported by the task, given the current
+ devices and channel count.
+
+ This rate is generally faster than the default AI Convert
+ Clock rate selected by NI-DAQmx, because NI-DAQmx adds in an
+ additional 10 microseconds per channel settling time to
+ compensate for most potential system settling constraints.
+
+ For single channel tasks, the maximum AI Convert Clock rate is the
+ maximum rate of the ADC. For multiple channel tasks, the maximum
+ AI Convert Clock rate is the maximum convert rate of the analog
+ hardware, including the ADC, filters, multiplexers, and amplifiers.
+ Signal conditioning accessories can further constrain the maximum AI
+ Convert Clock based on timing and settling requirements.
+ """
+ err, value = self.lib.GetAIConvMaxRate(RetValue('f64'))
+ return value
+
+ def read_scalar(self, timeout=10.0):
+ """Return a single floating-point sample from a task that
+ contains a single analog input channel.
+
+ :param timeout: The amount of time, in seconds, to wait for the function to
+ read the sample(s). The default value is 10.0 seconds. To
+ specify an infinite wait, pass -1 (DAQmx_Val_WaitInfinitely).
+ This function returns an error if the timeout elapses.
+
+ A value of 0 indicates to try once to read the requested
+ samples. If all the requested samples are read, the function
+ is successful. Otherwise, the function returns a timeout error
+ and returns the samples that were actually read.
+
+ :rtype: float
+ """
+
+ err, value = self.lib.ReadAnalogScalarF64(timeout, RetValue('f64'), None)
+ return value
+
+ @Action(units=(None, 'seconds', None), values=(None, None, _GROUP_BY))
+ def read(self, samples_per_channel=None, timeout=10.0, group_by='channel'):
+ """Reads multiple floating-point samples from a task that
+ contains one or more analog input channels.
+
+ :param samples_per_channel:
+ The number of samples, per channel, to read. The default
+ value of -1 (DAQmx_Val_Auto) reads all available samples. If
+ readArray does not contain enough space, this function
+ returns as many samples as fit in readArray.
+
+ NI-DAQmx determines how many samples to read based on
+ whether the task acquires samples continuously or acquires a
+ finite number of samples.
+
+ If the task acquires samples continuously and you set this
+ parameter to -1, this function reads all the samples
+ currently available in the buffer.
+
+ If the task acquires a finite number of samples and you set
+ this parameter to -1, the function waits for the task to
+ acquire all requested samples, then reads those samples. If
+ you set the Read All Available Samples property to TRUE, the
+ function reads the samples currently available in the buffer
+ and does not wait for the task to acquire all requested
+ samples.
+
+ :param timeout: float
+ The amount of time, in seconds, to wait for the function to
+ read the sample(s). The default value is 10.0 seconds. To
+ specify an infinite wait, pass -1
+ (DAQmx_Val_WaitInfinitely). This function returns an error
+ if the timeout elapses.
+
+ A value of 0 indicates to try once to read the requested
+ samples. If all the requested samples are read, the function
+ is successful. Otherwise, the function returns a timeout
+ error and returns the samples that were actually read.
+
+ :param group_by:
+
+ 'channel'
+ Group by channel (non-interleaved)::
+
+ ch0:s1, ch0:s2, ..., ch1:s1, ch1:s2,..., ch2:s1,..
+
+ 'scan'
+ Group by scan number (interleaved)::
+
+ ch0:s1, ch1:s1, ch2:s1, ch0:s2, ch1:s2, ch2:s2,...
+
+ :rtype: numpy.ndarray
+ """
+
+ if samples_per_channel is None:
+ samples_per_channel = self.samples_per_channel_available()
+
+ number_of_channels = self.number_of_channels()
+ if group_by == Constants.Val_GroupByScanNumber:
+ data = np.zeros((samples_per_channel, number_of_channels), dtype=np.float64)
+ else:
+ data = np.zeros((number_of_channels, samples_per_channel), dtype=np.float64)
+
+ err, data, count = self.lib.ReadAnalogF64(samples_per_channel, timeout, group_by,
+ data.ctypes.data, data.size, RetValue('i32'), None)
+
+ if samples_per_channel < count:
+ if group_by == 'scan':
+ return data[:count]
+ else:
+ return data[:,:count]
+
+ return data
+
+
+class AnalogOutputTask(Task):
+ """Analog Output Task
+ """
+
+ CHANNEL_TYPE = 'AO'
+
+ @Action(units=(None, None, 'seconds', None), values=(None, None, None, _GROUP_BY))
+ def write(self, data, auto_start=True, timeout=10.0, group_by='scan'):
+ """Write multiple floating-point samples or a scalar to a task
+ that contains one or more analog output channels.
+
+ Note: If you configured timing for your task, your write is
+ considered a buffered write. Buffered writes require a minimum
+ buffer size of 2 samples. If you do not configure the buffer
+ size using DAQmxCfgOutputBuffer, NI-DAQmx automatically
+ configures the buffer when you configure sample timing. If you
+ attempt to write one sample for a buffered write without
+ configuring the buffer, you will receive an error.
+
+ :param data: The array of 64-bit samples to write to the task
+ or a scalar.
+
+ :param auto_start: Whether or not this function automatically starts
+ the task if you do not start it.
+
+ :param timeout: The amount of time, in seconds, to wait for this
+ function to write all the samples. The default value is 10.0
+ seconds. To specify an infinite wait, pass -1
+ (DAQmx_Val_WaitInfinitely). This function returns an error
+ if the timeout elapses.
+
+ A value of 0 indicates to try once to write the submitted
+ samples. If this function successfully writes all submitted
+ samples, it does not return an error. Otherwise, the
+ function returns a timeout error and returns the number of
+ samples actually written.
+
+ :param group_by: how the samples are arranged, either interleaved or noninterleaved
+
+ 'channel' - Group by channel (non-interleaved).
+
+ 'scan' - Group by scan number (interleaved).
+
+ :return: The actual number of samples per channel successfully
+ written to the buffer.
+
+ """
+ if np.isscalar(data):
+ err = self.lib.WriteAnalogScalarF64(bool32(auto_start),
+ float64(timeout),
+ float64(data), None)
+ return 1
+
+ data = np.asarray(data, dtype = np.float64)
+
+ number_of_channels = self.number_of_channels()
+
+ if data.ndims == 1:
+ if number_of_channels == 1:
+ samples_per_channel = data.shape[0]
+ shape = (samples_per_channel, 1)
+ else:
+ samples_per_channel = data.size / number_of_channels
+ shape = (samples_per_channel, number_of_channels)
+
+ if not group_by == Constants.Val_GroupByScanNumber:
+ shape = tuple(reversed(shape))
+
+ data.reshape(shape)
+ else:
+ if group_by == Constants.Val_GroupByScanNumber:
+ samples_per_channel = data.shape[0]
+ else:
+ samples_per_channel = data.shape[-1]
+
+ err, count = self.lib.WriteAnalogF64(samples_per_channel, auto_start,
+ timeout, group_by,
+ data.ctypes.data, RetValue('i32'),
+ None)
+
+ return count
+
+
+class DigitalTask(Task):
+
+ @Action(units=(None, 'seconds', None), values=(None, None, _GROUP_BY))
+ def read(self, samples_per_channel=None, timeout=10.0, group_by='scan'):
+ """
+ Reads multiple samples from each digital line in a task. Each
+ line in a channel gets one byte per sample.
+
+ :param samples_per_channel: int or None
+
+ The number of samples, per channel, to
+ read. The default value of -1 (DAQmx_Val_Auto) reads all
+ available samples. If readArray does not contain enough
+ space, this function returns as many samples as fit in
+ readArray.
+
+ NI-DAQmx determines how many samples to read based on
+ whether the task acquires samples continuously or acquires a
+ finite number of samples.
+
+ If the task acquires samples continuously and you set this
+ parameter to -1, this function reads all the samples
+ currently available in the buffer.
+
+ If the task acquires a finite number of samples and you set
+ this parameter to -1, the function waits for the task to
+ acquire all requested samples, then reads those samples. If
+ you set the Read All Available Data property to TRUE, the
+ function reads the samples currently available in the buffer
+ and does not wait for the task to acquire all requested
+ samples.
+
+ :param timeout: float
+
+ The amount of time, in seconds, to wait for the function to
+ read the sample(s). The default value is 10.0 seconds. To
+ specify an infinite wait, pass -1
+ (DAQmx_Val_WaitInfinitely). This function returns an error
+ if the timeout elapses.
+
+ A value of 0 indicates to try once to read the requested
+ samples. If all the requested samples are read, the function
+ is successful. Otherwise, the function returns a timeout
+ error and returns the samples that were actually read.
+
+ :param group_by: {'group', 'scan'}
+
+ Specifies whether or not the samples are interleaved:
+
+ 'channel' - Group by channel (non-interleaved).
+
+ 'scan' - Group by scan number (interleaved).
+
+ Returns
+ -------
+
+ data : array
+
+ The array to read samples into. Each `bytes_per_sample`
+ corresponds to one sample per channel, with each element
+ in that grouping corresponding to a line in that channel,
+ up to the number of lines contained in the channel.
+
+ bytes_per_sample : int
+
+ The number of elements in returned `data` that constitutes
+ a sample per channel. For each sample per channel,
+ `bytes_per_sample` is the number of bytes that channel
+ consists of.
+
+ """
+
+ if samples_per_channel in (None, -1):
+ samples_per_channel = self.samples_per_channel_available()
+
+ if self.one_channel_for_all_lines:
+ nof_lines = []
+ for channel in self.names_of_channels():
+ nof_lines.append(self.number_of_lines (channel))
+ c = int (max (nof_lines))
+ dtype = getattr(np, 'uint%s' % (8 * c))
+ else:
+ c = 1
+ dtype = np.uint8
+
+ number_of_channels = self.number_of_channels()
+
+ if group_by == Constants.Val_GroupByScanNumber:
+ data = np.zeros((samples_per_channel, number_of_channels),dtype=dtype)
+ else:
+ data = np.zeros((number_of_channels, samples_per_channel),dtype=dtype)
+
+ err, count, bps = self.lib.ReadDigitalLines(samples_per_channel, float64 (timeout),
+ group_by, data.ctypes.data, uInt32 (data.size * c),
+ RetValue('i32'), RetValue('i32'),
+ None
+ )
+ if count < samples_per_channel:
+ if group_by == 'scan':
+ return data[:count], bps
+ else:
+ return data[:,:count], bps
+ return data, bps
+
+
+class DigitalInputTask(DigitalTask):
+ """Exposes NI-DAQmx digital input task to Python.
+ """
+
+ CHANNEL_TYPE = 'DI'
+
+
+class DigitalOutputTask(DigitalTask):
+ """Exposes NI-DAQmx digital output task to Python.
+ """
+
+ CHANNEL_TYPE = 'DO'
+
+ @Action(units=(None, None, 'seconds', None), values=(None, {True, False}, None, _GROUP_BY))
+ def write(self, data, auto_start=True, timeout=10.0, group_by='scan'):
+ """
+ Writes multiple samples to each digital line in a task. When
+ you create your write array, each sample per channel must
+ contain the number of bytes returned by the
+ DAQmx_Read_DigitalLines_BytesPerChan property.
+
+ Note: If you configured timing for your task, your write is
+ considered a buffered write. Buffered writes require a minimum
+ buffer size of 2 samples. If you do not configure the buffer
+ size using DAQmxCfgOutputBuffer, NI-DAQmx automatically
+ configures the buffer when you configure sample timing. If you
+ attempt to write one sample for a buffered write without
+ configuring the buffer, you will receive an error.
+
+ Parameters
+ ----------
+
+ data : array
+
+ The samples to write to the task.
+
+ auto_start : bool
+
+ Specifies whether or not this function automatically starts
+ the task if you do not start it.
+
+ timeout : float
+
+ The amount of time, in seconds, to wait for this function to
+ write all the samples. The default value is 10.0 seconds. To
+ specify an infinite wait, pass -1
+ (DAQmx_Val_WaitInfinitely). This function returns an error
+ if the timeout elapses.
+
+ A value of 0 indicates to try once to write the submitted
+ samples. If this function successfully writes all submitted
+ samples, it does not return an error. Otherwise, the
+ function returns a timeout error and returns the number of
+ samples actually written.
+
+ layout : {'group_by_channel', 'group_by_scan_number'}
+
+ Specifies how the samples are arranged, either interleaved
+ or noninterleaved:
+
+ 'group_by_channel' - Group by channel (non-interleaved).
+
+ 'group_by_scan_number' - Group by scan number (interleaved).
+ """
+
+ number_of_channels = self.get_number_of_channels()
+
+ if np.isscalar(data):
+ data = np.array([data]*number_of_channels, dtype = np.uint8)
+ else:
+ data = np.asarray(data, dtype = np.uint8)
+
+ if data.ndims == 1:
+ if number_of_channels == 1:
+ samples_per_channel = data.shape[0]
+ shape = (samples_per_channel, 1)
+ else:
+ samples_per_channel = data.size / number_of_channels
+ shape = (samples_per_channel, number_of_channels)
+
+ if not group_by == Constants.Val_GroupByScanNumber:
+ shape = tuple(reversed(shape))
+
+ data.reshape(shape)
+ else:
+ if group_by == Constants.Val_GroupByScanNumber:
+ samples_per_channel = data.shape[0]
+ else:
+ samples_per_channel = data.shape[-1]
+
+ err, count = self.lib.WriteDigitalLines(samples_per_channel,
+ bool32(auto_start),
+ float64(timeout), group_by,
+ data.ctypes.data, RetValue('u32'), None)
+
+ return count
+
+ # NotImplemented: WriteDigitalU8, WriteDigitalU16, WriteDigitalU32, WriteDigitalScalarU32
+
+class CounterInputTask(Task):
+ """Exposes NI-DAQmx counter input task to Python.
+ """
+
+ CHANNEL_TYPE = 'CI'
+
+ def read_scalar(self, timeout=10.0):
+ """Read a single floating-point sample from a counter task. Use
+ this function when the counter sample is scaled to a
+ floating-point value, such as for frequency and period
+ measurement.
+
+ :param float:
+ The amount of time, in seconds, to wait for the function to
+ read the sample(s). The default value is 10.0 seconds. To
+ specify an infinite wait, pass -1
+ (DAQmx_Val_WaitInfinitely). This function returns an error if
+ the timeout elapses.
+
+ A value of 0 indicates to try once to read the requested
+ samples. If all the requested samples are read, the function
+ is successful. Otherwise, the function returns a timeout error
+ and returns the samples that were actually read.
+
+ :return: The sample read from the task.
+ """
+
+ err, value = self.lib.ReadCounterScalarF64(timeout, RetValue('f64'), None)
+ return value
+
+ def read(self, samples_per_channel=None, timeout=10.0):
+ """Read multiple 32-bit integer samples from a counter task.
+ Use this function when counter samples are returned unscaled,
+ such as for edge counting.
+
+ :param samples_per_channel:
+ The number of samples, per channel, to read. The default
+ value of -1 (DAQmx_Val_Auto) reads all available samples. If
+ readArray does not contain enough space, this function
+ returns as many samples as fit in readArray.
+
+ NI-DAQmx determines how many samples to read based on
+ whether the task acquires samples continuously or acquires a
+ finite number of samples.
+
+ If the task acquires samples continuously and you set this
+ parameter to -1, this function reads all the samples
+ currently available in the buffer.
+
+ If the task acquires a finite number of samples and you set
+ this parameter to -1, the function waits for the task to
+ acquire all requested samples, then reads those samples. If
+ you set the Read All Available Samples property to TRUE, the
+ function reads the samples currently available in the buffer
+ and does not wait for the task to acquire all requested
+ samples.
+
+ :param timeout:
+ The amount of time, in seconds, to wait for the function to
+ read the sample(s). The default value is 10.0 seconds. To
+ specify an infinite wait, pass -1
+ (DAQmx_Val_WaitInfinitely). This function returns an error
+ if the timeout elapses.
+
+ A value of 0 indicates to try once to read the requested
+ samples. If all the requested samples are read, the function
+ is successful. Otherwise, the function returns a timeout
+ error and returns the samples that were actually read.
+
+
+ :return: The array of samples read.
+ """
+
+ if samples_per_channel is None:
+ samples_per_channel = self.samples_per_channel_available()
+
+ data = np.zeros((samples_per_channel,),dtype=np.int32)
+
+ err, count = self.lib.ReadCounterU32(samples_per_channel, float64(timeout),
+ data.ctypes.data, data.size, RetValue('i32'), None)
+
+ return data[:count]
+
+
+class CounterOutputTask(Task):
+
+ """Exposes NI-DAQmx counter output task to Python.
+ """
+
+ channel_type = 'CO'
+
+
+Task.register_class(AnalogInputTask)
diff --git a/lantz/drivers/legacy/olympus/__init__.py b/lantz/drivers/legacy/olympus/__init__.py
new file mode 100644
index 0000000..cf5d39c
--- /dev/null
+++ b/lantz/drivers/legacy/olympus/__init__.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.olympus
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ :company: Olympus.
+ :description: Research and clinical microscopes.
+ :website: http://www.microscopy.olympus.eu/microscopes/
+
+ ---
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from .ixbx import IX2, BX2A, IXBX
+
+__all__ = ['IX2', 'BX2A', 'IXBX']
diff --git a/lantz/drivers/legacy/olympus/ixbx.py b/lantz/drivers/legacy/olympus/ixbx.py
new file mode 100644
index 0000000..30bf51f
--- /dev/null
+++ b/lantz/drivers/legacy/olympus/ixbx.py
@@ -0,0 +1,227 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.olympus.ixbx
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ When talking about the z-axis of a microscope, use "near" and "far" instead of "up" and "down." "Nearer" always means the objective ends closer to the sample; "farther" means the objective ends farther away. On an inverted microscope, "near" is up and "far" is down; on an upright microscope it is exactly the reverse. Better to use "near" and "far" to avoid confusion.
+
+ You can always get the current state of the system by sending the command you would use to change that state followed by ?. For example, to get the current objective position, send 1OB?. The microscope returns 1OB 3, say, if the current objective is position 3 on the nosepiece.
+
+ The microscope only understands positive integers, no negative numbers, no floating point. All distances are sent as positive integers measured in hundredths of a micron. All voltages are sent as tenths of a volt. Where negative numbers are needed, such as to specify relative motion, an extra argument is used to tell the microscope the sign of the number.
+
+
+ Sources::
+
+ - Olympus IX-81 Chassis Commands `link `_
+ - Labview IX BX Series Driver `link `_
+ - Lantz reverse engineering
+
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+
+"""
+
+from lantz import Feat, Action, Q_
+from lantz.errors import InstrumentError
+from lantz.drivers.legacy.serial import SerialDriver
+
+# Physical units used by the IX/BX microscopes
+DECIVOLT = Q_(0.1, 'V')
+ZSTEP = Q_(0.01, 'micrometer')
+
+# Booleans mappings used by the IX/BX microscopes
+ON_OFF = {True: 'ON', False: 'OFF'}
+IN_OUT = {True: 'IN', False: 'OUT'}
+CLOSED_OPEN = {True: 'IN', False: 'OUT'}
+ONE_ZERO = {True: '1', False: '0'}
+ONE_TWO = {True: '1', False: '2'}
+FH_FRM = {True: 'FH', False: 'FRM'}
+EPI_DIA = {True: 'EPI', False: 'DIA'}
+
+INTSTR = (int, str)
+
+def ofeat(command, doc, **kwargs):
+ """Build Feat
+
+ :param command: command root (without ?)
+ :param doc: docstring to be applied to the feature
+ """
+
+ def _get(self):
+ response = self.query(command + '?')
+ return response
+
+ def _set(self, value):
+ self.query('{} {}'.format(command, value))
+
+ return Feat(_get, _set, doc=doc, **kwargs)
+
+
+class IXBX(SerialDriver):
+ """ IX or BX Olympus microscope body.
+ """
+
+ RECV_TERMINATION = '\r\n'
+ SEND_TERMINATION = '\r\n'
+
+
+ def __init__(self, port=1, baudrate=19200, bytesize=8, parity='Even',
+ stopbits=1, flow=0, timeout=None, write_timeout=None):
+ super().__init__(port, timeout=timeout, write_timeout=write_timeout,
+ baudrate=baudrate, bytesize=bytesize, parity=parity,
+ stopbits=stopbits, flow=flow)
+ self.send('1LOG IN\n')
+ self.send('2LOG IN')
+
+
+ def query(self, command, *, send_args=(None, None), recv_args=(None, None)):
+ """Query the instrument and parse the response.
+
+ :raises: InstrumentError
+ """
+ response = super().query(command, send_args=recv_args, recv_args=recv_args)
+ command = command.strip()[0]
+ if response in ('1x', '2x'):
+ raise InstrumentError("Unknown command: '{}'".format(command))
+ if not response.startswith(command):
+ raise InstrumentError("Unknown response: '{}'".format(response))
+ if response == 'X' or 'x':
+ raise InstrumentError('Unable to set')
+ elif not response == '+':
+ raise InstrumentError("Unknown response: '{}'".format(response))
+ return response
+
+
+ @Feat(read_once=True)
+ def idn(self):
+ """Microscope identification
+ """
+ return parse_response(self.query('1UNIT?'))
+
+ fluo_shutter = ofeat('1LED',
+ 'External shutter for the fluorescent light source',
+ values=ONE_ZERO)
+
+ lamp_epi_enabled = ofeat('1LMPSEL',
+ 'Illumination source lamp.',
+ values=EPI_DIA)
+
+ lamp_enabled = ofeat('1LMPSW',
+ 'Turn the currently selected lamp onf and off',
+ values=ON_OFF)
+
+ lamp_intensity = ofeat('1LMP',
+ 'Transmitted light intensity',
+ procs=(INTSTR, ))
+
+ def lamp_status(self):
+ #LMPSTS OK, X
+ pass
+
+ objective = ofeat('1OB',
+ 'Objective nosepiece position',
+ procs=(INTSTR, ))
+
+ body_locked = ofeat('1LOG',
+ 'Turn the currently selected lamp on and off',
+ values=ON_OFF)
+ focus_locked = ofeat('2LOG',
+ 'Turn the currently selected lamp on and off',
+ values=ON_OFF)
+
+ @Feat(units=(ZSTEP, ZSTEP))
+ def soft_limits(self):
+ near = self.query('2NEARLMT?')
+ far = self.query('2FARLMT?')
+ return near, far
+
+ @soft_limits.setter
+ def soft_limits(self, near, far):
+ self.query('2NEARLMT {:d}'.format(near))
+ self.query('2FARLMT {:d}'.format(far))
+
+
+ move_to_start_enabled = ofeat('INITRET',
+ 'Sets / cancels returning operation to the start '
+ 'position after initializing the origin.',
+ values=ON_OFF)
+
+ jog_enabled = ofeat('JOG', 'Jog enabled', values=ON_OFF)
+ jog_sensitivity = ofeat('JOGSNS',' Jog sensitivity', procs=(INTSTR, ))
+ jog_dial = ofeat('JOGSEL', 'Jog selection (Handle/BLA) ???', values=FH_FRM)
+ jog_limit_enabled = ofeat('joglmt', 'Jog limit enabled', values=ON_OFF)
+
+ @Feat()
+ def movement_status(self):
+ return self.query('ZDRV?')
+
+ @Action(units=ZSTEP)
+ def move_relative(self, distance):
+ if distance == 0:
+ return
+ elif distance < 0:
+ distance = -distance
+ direction = 'N'
+ else:
+ direction = 'F'
+
+ self.query('2MOV {:s} {:d}'.format(distance, direction))
+
+ @Feat(units=ZSTEP)
+ def z(self):
+ """Position of the objective.
+ """
+
+ # OPTIMAL?? start accel, speed tenth of microns/s, end accel
+ return int(self.query('2POS'))
+
+ @z.setter
+ def z(self, value):
+
+ # OPTIMAL?? start accel, speed tenth of microns/s, end accel
+ self.query('2MOV D {:d}'.format(value))
+
+ def stop(self):
+ """Stop any currently executing motion
+ """
+
+ # Stop any currently executing motion. Always responds with 2STOP +.
+ # If there is a 2MOV command in progress,
+ # it also aborts and returns an error condition with 2MOV !,E02133.
+ self.query('2STOP')
+
+ def init_origin(self):
+ """Init origin
+ """
+ #INITORG
+ pass
+
+
+class IX2(IXBX):
+ """ Olympus IX2 Body
+ """
+
+ bottom_port_closed = ofeat('1BPORT', 'Bottom port', values=CLOSED_OPEN)
+
+ shutter1_closed = ofeat('SHUT1', 'Shutter', values=IN_OUT)
+ shutter2_closed = ofeat('SHUT2', 'Shutter', values=IN_OUT)
+
+ filter_wheel = ofeat('FW', 'Filter wheel position', procs=(INTSTR, ))
+ condensor = ofeat('CD', 'Condensor position', procs=(INTSTR, ))
+ mirror_unit = ofeat('MU', 'Mirror unit position', procs=(INTSTR, ))
+ camera_port_enabled= ofeat('PRISM', 'Prism position', values=ONE_TWO)
+
+
+class BX2A(IXBX):
+ """ Olympus BX2A Body
+ """
+
+ shutter_closed = ofeat('SHUTTER', 'Shutter RFAA', values=IN_OUT)
+ aperture_stop_diameter = ofeat('EAS', 'Aperture stop diameter (EPI AS RLAA)', procs=(INTSTR, ))
+ aperture_stop_diameter = ofeat('DAS', 'Aperture stop diameter (DIA AS UCD)', procs=(INTSTR, ))
+ condenser_top_lens_enabled = ofeat('CDTOP', 'Condenser top lens (UCD)', values=IN_OUT)
+ turret = ofeat('TURRET', 'Turret position (UCD)', procs=(INTSTR, ))
+ cube = ofeat('CUBE', 'Cube position (RFAA/RLAA)', procs=(INTSTR, ))
+ configure_filterwheel = ofeat('FW', 'Configure filterwheel', procs=(INTSTR, ))
+
diff --git a/lantz/drivers/legacy/pco/__init__.py b/lantz/drivers/legacy/pco/__init__.py
new file mode 100644
index 0000000..c94156e
--- /dev/null
+++ b/lantz/drivers/legacy/pco/__init__.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.pco
+ ~~~~~~~~~~~~~~~~~~~~~~~~
+
+ :company: PCO.
+ :description: High performance CCD-, CMOS- and sCMOS camera systems.
+ :website: http://www.pco.de
+
+ ---
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from .sensicam import Sensicam
+
+__all__ = ['Sensicam', ]
diff --git a/lantz/drivers/legacy/pco/sensicam.py b/lantz/drivers/legacy/pco/sensicam.py
new file mode 100644
index 0000000..897ebd3
--- /dev/null
+++ b/lantz/drivers/legacy/pco/sensicam.py
@@ -0,0 +1,305 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.pco.sensicam
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Implements Sensicam driver
+
+
+ Implementation Notes
+ --------------------
+
+ The Sensicam operates using a COC = Command Operation Code
+
+ ---
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from time import sleep
+from collections import namedtuple
+
+import ctypes as ct
+import numpy as np
+
+from lantz import Action, Feat
+from lantz.errors import InstrumentError
+from lantz.foreign import LibraryDriver, RetTuple
+
+class Status(object):
+ READ_BUSY = 1
+ BUFFER_EMPTY = 2
+ COC_RUNNING = 4
+ BUFFERS_FULL = 8
+
+_ERRORS = {
+ 0: "Success",
+ -1: "No camera connected",
+ -2: "Timeout",
+ -3: "Wrong parameter",
+ -4: "cannot locate card",
+ -5: "cannot allocate DMA buffer",
+ -7: "DMA timeout",
+ -8: "Invalid camera mode",
+ -9: "No driver installed",
+ -10: "No PCI bios found",
+ -11: "Device is hold by another process",
+ -12: "Error in reading or writing data to board",
+ -13: "Wrong driver function",
+ -20: "Load COC error",
+ -21: "Too many values in COC",
+ -22: "Temperatur out of range",
+ -23: "Buffer allocate error",
+ -24: "Read image error",
+ -25: "Set/reset buffer flags is failed",
+ -26: "Buffer is used",
+ -27: "Call to a windows function is failed",
+ -28: "DMA error",
+ -29: "Cannot open file",
+ -30: "Registry error",
+ -31: "Open dialog error",
+ -32: "Needs newer called vxd or dll",
+ 100: 'no image in PCI buffer',
+ 101: 'picture too dark',
+ 102: 'picture too bright',
+ 103: 'one or more values changed',
+ 104: 'buffer for builded string too short'}
+
+#: Command Operation Code tuple
+COC = namedtuple('COC', 'mode trigger roi binning table')
+
+
+class Sensicam(LibraryDriver):
+ """PCO Sensicam
+ """
+
+ LIBRARY_NAME = 'senntcam.dll'
+
+ def __init__(self, board, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.board = board
+
+ def _return_handler(self, func_name, ret_value):
+ if ret_value < 0:
+ raise InstrumentError(_ERRORS[ret_value])
+ elif (ret_value > 0 and
+ func_name not in ('GET_COCTIME', 'GET_DELTIME', 'GET_BELTIME', 'GET_EXPTIME')):
+ self.log_warning('While calling {}: {}', func_name, _ERRORS[ret_value])
+
+ return ret_value
+
+ def initialize(self):
+ self.lib.SET_BOARD(_boardnr)
+ err = self.lib.SET_INIT(1)
+ self.stop_coc()
+
+ def finalize(self):
+ self.stop_coc()
+ self.lib.SET_INIT(0)
+
+ @Feat()
+ def status(self):
+ # Get camera type, temperature of electronics and temp. of CCD
+ cam_type = ct.pointer(ct.c_int())
+ temp_ele = ct.pointer(ct.c_int())
+ temp_ccd = ct.pointer(ct.c_int())
+ err = self.lib.GET_STATUS(cam_type, temp_ele, temp_ccd)
+ #return "{}".format(_ERRORS[err])
+ return cam_type, temp_ele, temp_ccd
+
+ @Feat()
+ def mode(self):
+ """Imaging mode as a 3-element tuple (type, gain and submode).
+ """
+ _mode = self.coc.mode
+ return _mode & 0xFF, (_mode >> 8) & 0xFF, (_mode >> 16) & 0xFF
+
+ @mode.setter
+ def mode(self, typ_gain_submode):
+ typ, gain, submode = typ_gain_submode
+ _mode = (typ & 0xFF) | ((gain & 0xFF) << 8) | ((submode & 0xFF) << 16)
+ self.coc = self.recall('coc')._replace(mode=_mode)
+
+ @Feat()
+ def trigger(self):
+ """Triger mode.
+ """
+ return self.coc.trigger
+
+ @trigger.setter
+ def trigger(self, value):
+ self.coc = self.recall('coc')._replace(trigger=value)
+
+ @Feat()
+ def roi(self):
+ """Region of interest in pixels as a 4-element tuple (x1, x2, y1, y2).
+ """
+ return self.coc.roi
+
+ @roi.setter
+ def roi(self, roi):
+ roix1, roix2, roiy1, roiy2 = roi
+ self.coc = self.recall('coc')._replace(roi=(roix1, roix2, roiy1, roiy2))
+
+ @Feat()
+ def binning(self):
+ """Binning in pixels as a 2-element tuple (horizontal, vertical).
+ """
+ return self.coc.binning
+
+ @binning.setter
+ def binning(self, hbin_vbin):
+ hbin, vbin = hbin_vbin
+ self.coc = self.recall('coc')._replace('binning', (hbin, vbin))
+
+ @Feat(units='ms')
+ def exposure_time(self):
+ """Exposure time.
+ """
+ return float(self.coc.table.split(',')[1])
+
+ @exposure_time.setter
+ def exposure_time(self, value):
+ self.table = '0,{},-1,-1'.format(value)
+
+ @Feat()
+ def table(self):
+ """COC table
+ """
+ return self.coc.table
+
+ @Action()
+ def stop_coc(self):
+ # Stop camera
+ self.lib.STOP_COC(0)
+
+ @Action()
+ def run_coc(self, runmode = 4):
+ # 0 continuous - 4 single
+ self.lib.RUN_COC(runmode)
+
+ @Feat()
+ def coc(self):
+ """Command Operation Code
+ """
+
+ mode = ct.pointer(ct.c_int())
+ trig = ct.pointer(ct.c_int())
+ roix1 = ct.pointer(ct.c_int())
+ roix2 = ct.pointer(ct.c_int())
+ roiy1 = ct.pointer(ct.c_int())
+ roiy2 = ct.pointer(ct.c_int())
+ hbin = ct.pointer(ct.c_int())
+ vbin = ct.pointer(ct.c_int())
+ table = ct.create_string_buffer(40)
+ self.lib.GET_SETTINGS(mode, trig, roix1, roix2, roiy1, roiy2,
+ hbin, vbin, table) #Todo: Try RetTuple
+
+ return COC(mode[0], trig[0], (roix1[0], roix2[0], roiy1[0], roiy2[0]),
+ (hbin[0], vbin[0]), table[0].decode('ascii'))
+
+ @coc.setter
+ def coc(self, value):
+ newcoc = (value.mode, value.trigger) + value.roi + \
+ value.binning + (value.table, )
+ self.lib.SET_COC(*newcoc)
+
+ @Feat()
+ def image_status(self):
+ """Image status
+ """
+ stat = ct.pointer(ct.c_int())
+ self.lib.GET_IMAGE_STATUS(stat)
+ return stat
+
+ @Feat()
+ def image_size(self):
+ """Image size in pixels (width, height).
+ """
+ width = ct.pointer(ct.c_int())
+ height = ct.pointer(ct.c_int())
+ self.lib.GET_IMAGE_SIZE(width, height)
+
+ return width[0], height[0]
+
+ @Feat(units='microseconds') #TODO: check units
+ def coc_time(self):
+ return self.lib.GET_COCTIME()
+
+ @Feat(units='microseconds')
+ def delay_time(self):
+ return self.lib.GET_DELTIME()
+
+ @Feat(units='microseconds')
+ def exp_time(self):
+ return self.lib.GET_EXPTIME()
+
+ @Feat(units='microseconds')
+ def bel_time(self):
+ return self.lib.GET_BELTIME()
+
+ @Action(units='ms')
+ def expose(self, exposure = 1):
+ """Expose.
+
+ :param exposure: exposure time.
+
+ """
+ self.exposure_time = exposure
+ delay = self.coc_time()
+ self.run_coc()
+ sleep(delay / 1000)
+ while not (self.image_status & Status.COC_RUNNING):
+ sleep(delay / 1000 * .1)
+
+ @Action()
+ def read_out(self):
+ """Readout image from the CCD.
+
+ :rtype: NumPy array
+ """
+ width, height = self.image_size
+ imagearray = np.zeros((width,height), dtype=np.dtype(np.ushort))
+ image = np.ascontiguousarray(imagearray)
+ ptrimage = image.ctypes.data_as(ct.POINTER(ct.c_ushort))
+ self.lib.READ_IMAGE_12BIT(0, width, height, ptrimage)
+ return image
+
+
+ @Action(units='ms')
+ def take_image(self, exposure=1):
+ """Take image.
+
+ :param exposure: exposure time.
+ :rtype: NumPy array
+ """
+ self.expose(exposure)
+ return self.read_out()
+
+
+if __name__ == '__main__':
+ import argparse
+ import lantz.log
+
+ parser = argparse.ArgumentParser(description='Test Kentech HRI')
+ parser.add_argument('-i', '--interactive', action='store_true',
+ default=False, help='Show interactive GUI')
+ parser.add_argument('-b', '--board', type=int, default='0',
+ help='Board number')
+
+ args = parser.parse_args()
+ lantz.log.log_to_socket(lantz.log.DEBUG)
+ with Sensicam(args.board) as inst:
+ if args.interactive:
+ from lantz.ui.app import start_test_app
+ start_test_app(inst)
+ else:
+ import matplotlib.pyplot as plt
+
+ image = inst.take_image(10)
+
+ print(np.min(image), np.max(image), np.mean(image))
+
+ plt.imshow(image, cmap='gray')
+ plt.show()
diff --git a/lantz/drivers/legacy/prior/__init__.py b/lantz/drivers/legacy/prior/__init__.py
new file mode 100644
index 0000000..93571e1
--- /dev/null
+++ b/lantz/drivers/legacy/prior/__init__.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.prior
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ :company: PRIOR Scientific
+ :description: Microscope Automation & Custom Solutions
+ :website: http://www.prior.com/
+
+ ----
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from .nanoscanz import NanoScanZ
+
+__all__ = ['NanoScanZ']
diff --git a/lantz/drivers/legacy/prior/nanoscanz.py b/lantz/drivers/legacy/prior/nanoscanz.py
new file mode 100644
index 0000000..d3a100a
--- /dev/null
+++ b/lantz/drivers/legacy/prior/nanoscanz.py
@@ -0,0 +1,206 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.prior.nanoscanz
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from lantz import Action, Feat
+from lantz.drivers.legacy.serial import SerialDriver
+from lantz.errors import InstrumentError
+
+class NanoScanZ(SerialDriver):
+ """Driver for the NanoScanZ Nano Focusing Piezo Stage from Prior.
+ """
+
+ ENCODING = 'ascii'
+
+ RECV_TERMINATION = '\r'
+ SEND_TERMINATION = '\r'
+
+ BAUDRATE = 9600
+ BYTESIZE = 8
+ PARITY = 'none'
+ STOPBITS = 1
+
+ #: flow control flags
+ RTSCTS = False
+ DSRDTR = False
+ XONXOFF = False
+
+
+ def query(self, command, *, send_args=(None, None), recv_args=(None, None)):
+ """Send query to the stage and return the answer, after handling
+ possible errors.
+
+ :param command: command to be sent to the instrument
+ :type command: string
+
+ :param send_args: (termination, encoding) to override class defaults
+ :param recv_args: (termination, encoding) to override class defaults
+ """
+ ans = super().query(command, send_args=send_args, recv_args=recv_args)
+ if ans[0] == 'E':
+ code = ans[2]
+ if code == '8':
+ raise InstrumentError('Value out of range')
+ elif code == '4':
+ raise InstrumentError('Command parse error, ie wrong number of parameters')
+ elif code == '5':
+ raise InstrumentError('Unknown command')
+ elif code == '2':
+ raise InstrumentError('Invalid checksum')
+
+ return ans
+
+ @Feat(values={9600,19200,38400})
+ def baudrate(self):
+ """Reports and sets the baud rate.
+ NOTE: DO NOT change the baud rate of the Piezo controller when daisy chained to ProScan.
+ """
+ return self.query('BAUD')
+
+ @baudrate.setter
+ def baudrate(self, value):
+ self.query('BAUD {}'.format(value))
+
+
+ @Feat(values={True: '4', False: '00000'})
+ def moving(self):
+ """Returns the movement status, 0 stationary, 4 moving
+ """
+ return self.query('$')
+
+ @Feat(read_once=True)
+ def idn(self):
+ """Identification of the device
+ """
+ return self.query('DATE') +' '+ self.query('SERIAL')
+
+ @Feat(units = 'micrometer')
+ def position(self):
+ """Gets and sets current position.
+ If the value is set to z = 0, the display changes to REL 0 (relative display mode). To return to ABS mode use inst.move_absolute(0) and then inst.position = 0. Thus, the stage will return to 0 micrometers and the display screen will switch to ABS mode.
+ """
+ return self.query('PZ')
+
+ @position.setter
+ def position(self, value):
+ self.query('PZ {}'.format(value))
+
+ @Action()
+ def zero_position(self):
+ """Move to zero including any position redefinitions done by the position Feat
+ """
+ self.query('M')
+
+
+ @Action(units = 'micrometer', limits=(100,))
+ def move_absolute(self, value):
+ """Move to absolute position n, range (0,100).
+ This is a "real" absolute position and is independent of any relative offset added by the position Feat.
+ """
+ self.query('V {}'.format())
+
+
+ @Action()
+ def move_relative(self, value):
+ """
+ Move the stage position relative to the current position by an amount determined by 'value'.
+ If value is given in micrometer, thats the amount the stage is going to move, in microns.
+ If value is given in steps, the stage will move a distance value.magnitude * step. The step is defined by the step Feat
+ """
+ """
+ try:
+ u = value.units
+ if value.magnitude > 0:
+ self.query('U {}'.format(value.magnitude))
+ if value.magnitude < 0:
+ self.query('D {}'.format(-value.magnitude))
+ except:
+ if isinstance(value, int):
+ if value > 0:
+ for x in range(0,value):
+ self.query('U')
+ elif value < 0:
+ for x in range(0,value):
+ self.query('D')
+ else:
+ raise ValueError('Specify the translation distance in micrometer unit')
+ """
+ try:
+ if value.units == 'micrometer':
+ if value.magnitude > 0:
+ self.query('U {}'.format(value.magnitude))
+ elif value.magnitude < 0:
+ self.query('D {}'.format(-value.magnitude))
+ elif value.units == 'steps':
+ if value.magnitude > 0:
+ for x in range(0,value.magnitude):
+ self.query('U')
+ elif value.magnitude < 0:
+ for x in range(0,-value.magnitude):
+ self.query('D')
+ except:
+ raise ValueError('Specify the translation distance in micrometers or steps')
+
+
+ @Feat(units='micrometer')
+ def step(self):
+ """Report and set the default step size, in microns
+ """
+ return self.query('C')
+
+ @step.setter
+ def step (self, value):
+ self.query('C {}'.format(value))
+
+
+
+ @Feat(read_once=True)
+ def software_version(self):
+ """Software version
+ """
+ return self.query('VER')
+
+class NanoScanZ_chained(NanoScanZ):
+ """This is needed when the NanoScanZ controller is connected to a ProScanII controller through its RS-232-2 input
+ """
+ def write(self, command, termination=None, encoding=None):
+ super().write('<' + command, termination=termination, encoding=encoding)
+
+ def read(self, termination=None, encoding=None, recv_chunk=None):
+ return super().read(termination=termination, encoding=encoding)[1:]
+
+
+if __name__ == '__main__':
+ import argparse
+ import lantz.log
+
+ parser = argparse.ArgumentParser(description='Test Prior NanoScanZ')
+ parser.add_argument('-i', '--interactive', action='store_true',
+ default=False, help='Show interactive GUI')
+ parser.add_argument('-p', '--port', type=str, default='17',
+ help='Serial port to connect to')
+
+ args = parser.parse_args()
+ lantz.log.log_to_screen(lantz.log.DEBUG)
+ with NanoScanZ(args.port) as inst:
+ if args.interactive:
+ from lantz.ui.app import start_test_app
+ start_test_app(inst)
+ else:
+ from lantz import Q_
+ # Add your test code here
+ print('Non interactive mode')
+
+ um = Q_(1, 'micrometer')
+
+ print(inst.idn)
+ print(inst.moving)
+ print(inst.position)
+ print(inst.step)
+ inst.step = 1 * um
+ print(inst.step)
diff --git a/lantz/drivers/legacy/rgblasersystems/__init__.py b/lantz/drivers/legacy/rgblasersystems/__init__.py
new file mode 100644
index 0000000..370dfea
--- /dev/null
+++ b/lantz/drivers/legacy/rgblasersystems/__init__.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.rgblasersystems
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ :company: RGB Lasersysteme GmbH.
+ :description: Lasers and Lasers Systems.
+ :website: http://www.rgb-laser.com/
+
+ ----
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from .minilasevo import MiniLasEvo
+
+__all__ = ['MiniLasEvo']
diff --git a/lantz/drivers/legacy/rgblasersystems/minilasevo.py b/lantz/drivers/legacy/rgblasersystems/minilasevo.py
new file mode 100644
index 0000000..f7e2d94
--- /dev/null
+++ b/lantz/drivers/legacy/rgblasersystems/minilasevo.py
@@ -0,0 +1,227 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.rgblasersystems.minilasevo
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from lantz import Feat
+from lantz.drivers.legacy.serial import SerialDriver
+from lantz.errors import InstrumentError
+
+class MiniLasEvo(SerialDriver):
+ """Driver for any RGB Lasersystems MiniLas Evo laser.
+ """
+
+ ENCODING = 'ascii'
+
+ RECV_TERMINATION = '\r\n'
+ SEND_TERMINATION = '\r\n'
+
+ BAUDRATE = 57600
+ BYTESIZE = 8
+ PARITY = 'none'
+ STOPBITS = 1
+
+ #: flow control flags
+ RTSCTS = False
+ DSRDTR = False
+ XONXOFF = False
+
+
+ def query(self, command, *, send_args=(None, None), recv_args=(None, None)):
+ """Send query to the laser and return the answer, after handling
+ possible errors.
+
+ :param command: command to be sent to the instrument
+ :type command: string
+
+ :param send_args: (termination, encoding) to override class defaults
+ :param recv_args: (termination, encoding) to override class defaults
+ """
+ ans = super().query(command, send_args=send_args, recv_args=recv_args)
+ # TODO: Echo handling
+ code = ans[0]
+ if code !=0:
+ if code == '1':
+ raise InstrumentError('Command invalid')
+ elif code == '2':
+ raise InstrumentError('Wrong number of parameters')
+ elif code == '3':
+ raise InstrumentError('Parameter value is out of range')
+ elif code == '4':
+ raise InstrumentError('Unlocking code is wrong')
+ elif code == '5':
+ raise InstrumentError('Device is locked for this command')
+ elif code == '6':
+ raise InstrumentError('This function is not supported')
+ elif code == '7':
+ raise InstrumentError('Timeout while reading command (60 s)')
+ elif code == '8':
+ raise InstrumentError('This value is currently not available')
+
+ ans = ans[2:]
+ # TODO: Code reporting?
+ return ans
+
+ @Feat(read_once=True)
+ def idn(self):
+ """Identification of the device
+ """
+ manufacturer = self.query('DM?')
+ device = self.query('DT?')
+ serial = self.query('DS?')
+ ans = manufacturer + ', ' + device + ', serial number ' + serial
+ return ans
+
+ @Feat()
+ def status(self):
+ """Current device status
+ """
+ ans = self.query('S?')
+ if ans == '0x10':
+ ans = 'Temperature of laser head is ok'
+ elif ans == '0x01':
+ ans = 'Laser system is active, radiation can be emitted'
+ elif ans == '0x02':
+ ans = '(reserved)'
+ elif ans == '0x04':
+ ans = 'The interlock is open'
+ elif ans == '0x08':
+ ans = self.query('E?')
+ if ans == '0x01':
+ ans = 'Temperature of laser head is too high'
+ elif ans == '0x02':
+ ans = 'Temperature of laser head is too low'
+ elif ans == '0x04':
+ ans = 'Temperature-sensor connection is broken'
+ elif ans == '0x08':
+ ans = 'Temperature sensor cable is shortened'
+ elif ans == '0x40':
+ ans = 'Current for laser head is too high'
+ elif ans == '0x80':
+ ans = 'Internal error (laser system cannot be activated)'
+ return ans
+
+ @Feat()
+ def operating_hours(self):
+ """Total operating hours [hhhh:mm]
+ """
+ return self.query('R?')
+
+ @Feat()
+ def software_version(self):
+ """Software version
+ """
+ return self.query('DO?')
+
+ @Feat(read_once=True)
+ def emission_wavelenght(self):
+ """Emission wavelenght in nm
+ """
+ return self.query('DW?')
+
+ @Feat()
+ def available_features(self):
+ """Available features (reserved for future use)
+ """
+ return self.query('DF?')
+
+ @Feat()
+ def control_mode(self):
+ """Active current (power) control
+ """
+ ans = self.query('DC?')
+ if ans == 'ACC':
+ ans = 'Active current control'
+ else:
+ ans = 'Active power control'
+ return ans
+
+ # TEMPERATURE
+
+ @Feat()
+ def temperature(self):
+ """Current temperature in ÂşC
+ """
+ return self.query('T?')
+
+ @Feat(read_once=True)
+ def temperature_min(self):
+ """Lowest operating temperature in ÂşC
+ """
+ return self.query('LTN?')
+
+ @Feat(read_once=True)
+ def temperature_max(self):
+ """Highest operating temperature in ÂşC
+ """
+ return self.query('LTP?')
+
+ # ENABLED REQUEST
+
+ @Feat(values={True: '1', False: '0'})
+ def enabled(self):
+ """Method for turning on the laser
+ """
+ return self.query('O?')
+
+ @enabled.setter
+ def enabled(self, value):
+ self.query('O=' + value)
+
+ # LASER POWER
+
+ def initialize(self):
+ super().initialize()
+ self.enabled = True
+ self.power = 0
+ self.feats.power.limits = (0, self.maximum_power.magnitude)
+
+ @Feat(units='mW')
+ def maximum_power(self):
+ """Gets the maximum emission power of the laser
+ """
+ return float(self.query('LP?'))
+
+ @Feat(units='mW')
+ def power(self):
+ """Gets and sets the emission power
+ """
+ return float(self.query('P?'))
+
+ @power.setter
+ def power(self, value):
+ self.query('P={:.1f}'.format(value))
+
+if __name__ == '__main__':
+ import argparse
+ import lantz.log
+
+ parser = argparse.ArgumentParser(description='Test Kentech HRI')
+ parser.add_argument('-i', '--interactive', action='store_true',
+ default=False, help='Show interactive GUI')
+ parser.add_argument('-p', '--port', type=str, default='17',
+ help='Serial port to connect to')
+
+ args = parser.parse_args()
+ lantz.log.log_to_screen(lantz.log.DEBUG)
+ with MiniLasEvo(args.port) as inst:
+ if args.interactive:
+ from lantz.ui.app import start_test_app
+ start_test_app(inst)
+ else:
+ # Add your test code here
+ print('Non interactive mode')
+ """inst.power = 0
+ inst.temperature
+ print(inst.power)
+ inst.power = 0
+ print(inst.idn)
+ """
+
+
+
+
diff --git a/lantz/utils/rpc.py b/lantz/drivers/legacy/rpc.py
similarity index 99%
rename from lantz/utils/rpc.py
rename to lantz/drivers/legacy/rpc.py
index becb373..c528566 100644
--- a/lantz/utils/rpc.py
+++ b/lantz/drivers/legacy/rpc.py
@@ -17,7 +17,7 @@
Original source: http://svn.python.org/projects/python/trunk/Demo/rpc/rpc.py
- :copyright: 2013 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/lantz/serial.py b/lantz/drivers/legacy/serial.py
similarity index 94%
rename from lantz/serial.py
rename to lantz/drivers/legacy/serial.py
index 5f2fdc8..cba7e15 100644
--- a/lantz/serial.py
+++ b/lantz/drivers/legacy/serial.py
@@ -1,20 +1,20 @@
# -*- coding: utf-8 -*-
"""
- lantz.serial
- ~~~~~~~~~~~~
+ lantz.drivers.legacy.serial
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
Implements base classes for drivers that communicate with instruments
via serial or parallel port.
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
import serial
-from . import Driver
-from .driver import TextualMixin
-from .errors import LantzTimeoutError
+from lantz import Driver
+from lantz.drivers.legacy.textual import TextualMixin
+from lantz.errors import LantzTimeoutError
from serial import SerialTimeoutException
diff --git a/lantz/drivers/legacy/stanford/__init__.py b/lantz/drivers/legacy/stanford/__init__.py
new file mode 100644
index 0000000..e2eb6cf
--- /dev/null
+++ b/lantz/drivers/legacy/stanford/__init__.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.stanford
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ :company: Standford Research Systems.
+ :description: Manufactures test instruments for research and industrial applications
+ :website: http://www.thinksrs.com/
+
+ ----
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from .sr830 import SR830Serial, SR830GPIB
+
+__all__ = ['SR830Serial', 'SR830GPIB']
diff --git a/lantz/drivers/legacy/stanford/sr830.py b/lantz/drivers/legacy/stanford/sr830.py
new file mode 100644
index 0000000..40ccb1b
--- /dev/null
+++ b/lantz/drivers/legacy/stanford/sr830.py
@@ -0,0 +1,516 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.stanford.sr830
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from collections import OrderedDict
+
+import numpy as np
+from lantz import Action, Feat, DictFeat, ureg
+from lantz.drivers.legacy.serial import SerialDriver
+from lantz.drivers.legacy.visa import GPIBVisaDriver
+
+SENS = OrderedDict([
+ ('2 nV/fA', 0),
+ ('5 nV/fA', 1),
+ ('10 nV/fA', 2),
+ ('20 nV/fA', 3),
+ ('50 nV/fA', 4),
+ ('100 nV/fA', 5),
+ ('200 nV/fA', 6),
+ ('500 nV/fA', 7),
+ ('1 uV/pA', 8),
+ ('2 uV/pA', 9),
+ ('5 uV/pA', 10),
+ ('10 uV/pA', 11),
+ ('20 uV/pA', 12),
+
+ ('50 uV/pA', 13),
+ ('100 uV/pA', 14),
+ ('200 uV/pA', 15),
+ ('500 uV/pA', 16),
+ ('1 mV/nA', 17),
+ ('2 mV/nA', 18),
+ ('5 mV/nA', 19),
+ ('10 mV/nA', 20),
+ ('20 mV/nA', 21),
+ ('50 mV/nA', 22),
+ ('100 mV/nA', 23),
+ ('200 mV/nA', 24),
+ ('500 mV/nA', 25),
+ ('1 V/uA', 26)
+ ])
+
+TCONSTANTS = OrderedDict([
+ ('10 us', 0),
+ ('30 us', 1),
+ ('100 us', 2),
+ ('300 us', 3),
+ ('1 ms', 4),
+ ('3 ms', 5),
+ ('10 ms', 6),
+ ('30 ms', 7),
+ ('100 ms', 8),
+ ('300 ms', 9),
+ ('1 s', 10),
+ ('3 s', 11),
+ ('10 s', 12),
+ ('30 s', 13),
+ ('100 s', 14),
+ ('300 s', 15),
+ ('1 ks', 16),
+ ('3 ks', 17),
+ ('10 ks', 18),
+ ('30 ks', 19),
+])
+
+SAMPLE_RATES = OrderedDict([
+ ('62.5 mHz', 0),
+ ('125 mHz', 1),
+ ('250 mHz', 2),
+ ('500 mHz', 3),
+ ('1 Hz', 4),
+ ('2 Hz', 5),
+ ('4 Hz', 6),
+ ('8 Hz', 7),
+ ('16 Hz', 8),
+ ('32 Hz', 9),
+ ('64 Hz', 10),
+ ('128 Hz', 11),
+ ('256 Hz', 12),
+ ('512 Hz', 13),
+ ('trigger', 14)
+])
+
+class _SR830(object):
+
+ @Feat(units='degrees', limits=(-360, 729.99, 0.01))
+ def reference_phase_shift(self):
+ """Phase shift of the reference.
+ """
+ return self.query('PHAS?')
+
+ @reference_phase_shift.setter
+ def reference_phase_shift(self, value):
+ self.send('PHAS{:.2f}'.format(value))
+
+ @Feat(values={True: 1, False: 0})
+ def reference_internal(self):
+ """Reference source.
+ """
+ return self.query('FMOD?')
+
+ @reference_internal.setter
+ def reference_internal(self, value):
+ self.send('FMOD {}'.format(value))
+
+ @Feat(units='Hz', limits=(0.001, 102000, 0.00001))
+ def frequency(self):
+ """Reference frequency.
+ """
+ return self.query('FREQ?')
+
+ @frequency.setter
+ def frequency(self, value):
+ self.send('FREQ{:.5f}'.format(value))
+
+ @Feat(values={'zero_crossing': 0, 'rising_edge': 1})
+ def reference_trigger(self):
+ """Reference trigger when using the external reference mode.
+ """
+
+ @reference_trigger.setter
+ def reference_trigger(self, value):
+ self.send('RSLP {}'.format(value))
+
+ @Feat(limits=(1, 19999, 1))
+ def harmonic(self):
+ """Detection harmonic.
+ """
+ return self.query('HARM?')
+
+ @harmonic.setter
+ def harmonic(self, value):
+ self.send('HARM {}'.format(value))
+
+ @Feat(units='volt', limits=(0.004, 5., 0.002))
+ def sine_output_amplitude(self):
+ """Amplitude of the sine output.
+ """
+ return self.query('SLVL?')
+
+ @sine_output_amplitude.setter
+ def sine_output_amplitude(self, value):
+ self.send('SLVL{:.2f}'.format(value))
+
+ @Feat(values={'A': 0, 'A-B': 1, 'I1': 2, 'I100': 3})
+ def input_configuration(self):
+ """Configuration of the Input.
+ """
+ return self.query('ISRC?')
+
+ @input_configuration.setter
+ def input_configuration(self, value):
+ self.send('ISRC {}'.format(value))
+
+ @Feat(values={'float': 0, 'ground': 1})
+ def input_shield(self):
+ """Input shield grounding.
+ """
+ return self.query('IGND?')
+
+ @input_shield.setter
+ def input_shield(self, value):
+ self.send('IGND {}'.format(value))
+
+ @Feat(values={'AC': 0, 'DC': 1})
+ def input_coupling(self):
+ """Input coupling.
+ """
+ return self.query('ICPL?')
+
+ @input_coupling.setter
+ def input_coupling(self, value):
+ self.send('ICPL {}'.format(value))
+
+ @Feat(values={(False, False): 0, (True, False): 1, (False, True): 2, (True, True): 3})
+ def input_filter(self):
+ """Input line notch filters (1x, 2x).
+ """
+ return self.query('ILIN?')
+
+ @input_filter.setter
+ def input_filter(self, value):
+ self.send('ILIN {}'.format(value))
+
+
+ # GAIN and TIME CONSTANT COMMANDS.
+
+ @Feat(values=SENS)
+ def sensitivity(self):
+ """Sensitivity.
+ """
+ return self.query('SENS?')
+
+ @sensitivity.setter
+ def sensitivity(self, value):
+ self.send('SENS {}'.format(value))
+
+ @Feat(values={'high': 0, 'normal': 1, 'low': 2})
+ def reserve_mode(self):
+ """Reserve mode.
+ """
+ return self.query('RMOD?')
+
+ @reserve_mode.setter
+ def reserve_mode(self, value):
+ self.send('RMOD {}'.format(value))
+
+ @Feat(values=TCONSTANTS)
+ def time_constants(self):
+ """Time constant.
+ """
+ return self.query('OFLT?')
+
+ @time_constants.setter
+ def time_constants(self, value):
+ self.send('OFLT {}'.format(value))
+
+ @Feat(values={6, 12, 18, 24})
+ def filter_db_per_oct(self):
+ """Time constant.
+ """
+ return self.query('OFSL?')
+
+ @filter_db_per_oct.setter
+ def filter_db_per_oct(self, value):
+ self.send('OFSL {}'.format(value))
+
+ @Feat(values={False: 0, True: 1})
+ def sync_filter(self):
+ """Synchronous filter status.
+ """
+ return self.query('SYNC?')
+
+ @sync_filter.setter
+ def sync_filter(self, value):
+ self.send('SYNC {}'.format(value))
+
+
+ ## DISPLAY and OUTPUT COMMANDS
+
+ @DictFeat(keys={1, 2})
+ def display(self, channel):
+ """Front panel output source.
+ """
+ return self.query('DDEF? {}'.format(channel))
+
+ @display.setter
+ def display(self, channel, value):
+ value, normalization = value
+ self.send('DDEF {}, {}, {}'.format(channel, value, normalization))
+
+ @DictFeat(keys={1, 2}, values={'display': 0, 'xy': 1})
+ def front_output(self, channel):
+ """Front panel output source.
+ """
+ return self.query('FPOP? {}'.format(channel))
+
+ @front_output.setter
+ def front_output(self, channel, value):
+ self.send('FPOP {}, {}'.format(channel, value))
+
+ # OEXP
+
+ # AOFF is below.
+
+ ## AUX INPUT and OUTPUT COMMANDS
+
+ @DictFeat(keys={1, 2, 3, 4}, units='volt')
+ def analog_input(self, key):
+ """Input voltage in the auxiliary analog input.
+ """
+ self.query('AOUX? {}'.format(key))
+
+ @DictFeat(None, keys={1, 2, 3, 4}, units='volt', limits=(-10.5, 10.5, 0.001))
+ def analog_output(self, key):
+ """Ouput voltage in the auxiliary analog output.
+ """
+ self.query('AUXV? {}'.format(key))
+
+ @analog_output.setter
+ def analog_output(self, key, value):
+ self.query('AUXV {}, {}'.format(key, value))
+
+
+ ## SETUP COMMANDS
+
+ remote = Feat(None, values={True: 0, False: 1})
+
+ @remote.setter
+ def remote(self, value):
+ """Lock Front panel.
+ """
+ self.query('OVRM {}'.format(value))
+
+ @Feat(values={True: 1, False: 0})
+ def key_click_enabled(self):
+ """Key click
+ """
+ return self.query('KCLK?')
+
+ @key_click_enabled.setter
+ def key_click_enabled(self, value):
+ self.send('KCLK {}'.format(value))
+
+ @Feat(values={True: 1, False: 0})
+ def alarm_enabled(self):
+ """Key click
+ """
+ return self.query('ALRM?')
+
+ @alarm_enabled.setter
+ def alarm_enabled(self, value):
+ self.send('ALRM {}'.format(value))
+
+ @Action(limits=(1, 9))
+ def recall_state(self, location):
+ """Recalls instrument state in specified non-volatile location.
+
+ :param location: non-volatile storage location.
+ """
+ self.send('RSET {}'.format(location))
+
+ @Action()
+ def save_state(self, location):
+ """Saves instrument state in specified non-volatile location.
+
+ Previously stored state in location is overwritten (no error is generated).
+ :param location: non-volatile storage location.
+ """
+ self.send('SSET'.format(location))
+
+
+ ## AUTO FUNCTIONS
+
+ def wait_bit1(self):
+ pass
+
+ @Action()
+ def auto_gain_async(self):
+ """Equivalent to press the Auto Gain key in the front panel.
+ Might take some time if the time constant is long.
+ Does nothing if the constant is greater than 1 second.
+ """
+ self.send('AGAN')
+
+ @Action()
+ def auto_gain(self):
+ self.auto_gain_async()
+ self.wait_bit1()
+
+ @Action()
+ def auto_reserve_async(self):
+ """Equivalent to press the Auto Reserve key in the front panel.
+ Might take some time if the time constant is long.
+ """
+ self.send('ARSV')
+
+ @Action()
+ def auto_reserve(self):
+ self.auto_reserve_async()
+ self.wait_bit1()
+
+ @Action()
+ def auto_phase_async(self):
+ """Equivalent to press the Auto Phase key in the front panel.
+ Might take some time if the time constant is long.
+ Does nothing if the phase is unstable.
+ """
+ self.send('ARSV')
+
+ @Action()
+ def auto_phase(self):
+ self.auto_phase_async()
+ self.wait_bit1()
+
+ @Action(values={'x': 1, 'y': 2, 'r': 3})
+ def auto_offset_async(self, channel_name):
+ """Automatically offset a given channel to zero.
+ Is equivalent to press the Auto Offset Key in the front panel.
+
+ :param channel_name: the name of the channel.
+ """
+ self.send('AOFF {}'.format(channel_name))
+
+ @Action()
+ def auto_offset(self):
+ self.auto_offset_async()
+ self.wait_bit1()
+
+
+ ## DATA STORAGE COMMANDS
+
+ @Feat(values=SAMPLE_RATES)
+ def sample_rate(self):
+ """Sample rate.
+ """
+ return self.query('SRAT?')
+
+ @sample_rate.setter
+ def sample_rate(self, value):
+ self.send('SRAT {}'.format(value))
+
+ @Feat(values={True: 0, False: 1})
+ def single_shot(self):
+ """End of buffer mode.
+
+ If loop mode (single_shot = False), make sure to pause data storage
+ before reading the data to avoid confusion about which point is the
+ most recent.
+ """
+ return self.query('SEND?')
+
+ @single_shot.setter
+ def single_shot(self, value):
+ self.send('SEND {}'.format(value))
+
+ @Action()
+ def trigger(self):
+ """Software trigger.
+ """
+ self.send('TRIG')
+
+ @Feat()
+ def trigger_start_mode(self):
+ self.query('TSTR?')
+
+ @trigger_start_mode.setter
+ def trigger_start_mode(self, value):
+ self.send('TSTR {}'.format(value))
+
+ @Action()
+ def start_data_storage(self):
+ """Start or resume data storage
+ """
+ self.send('STRT')
+
+ @Action()
+ def pause_data_storage(self):
+ """Pause data storage
+ """
+ self.send('PAUS')
+
+ @Action()
+ def reset_data_storage(self):
+ """Reset data buffers. The command can be sent at any time -
+ any storage in progress, paused or not. will be reset. The command
+ will erase the data buffer.
+ """
+ self.send('REST')
+
+
+ ## DATA TRANSFER COMMANDS
+
+ @DictFeat(keys={'x', 'y', 'r', 't', 1, 2}, units='volt')
+ def analog_value(self, key):
+ if key in 'xyrt':
+ return self.query('OUTP? {}'.format(key))
+ else:
+ return self.query('OUTR? {}'.format(key))
+
+ @Action()
+ def measure(self, channels):
+ d = {'x': '1', 'y': '2', 'r': '3', 't': '4',
+ '1': '5', '2': '6', '3': '7', '4': '8',
+ 'f': '9'} # '': 10, '': 11} TODO: How to deal with these?
+ channels = ','.join(d[ch] for ch in channels)
+ self.query('SNAP? {}'.format(channels))
+
+ # OAUX See above
+
+ @Feat()
+ def buffer_length(self):
+ return self.query('SPTS?')
+
+ @Action()
+ def read_buffer(self, channel, start=0, length=None, format='A'):
+ """Queries points stored in the Channel buffer
+
+ :param channel: Number of the channel (1, 2).
+ :param start: Index of the buffer to start.
+ :param length: Number of points to read.
+ Defaults to the number of points in the buffer.
+ :param format: Transfer format
+ 'a': ASCII (slow)
+ 'b': IEEE Binary (fast) - NOT IMPLEMENTED
+ 'c': Non-IEEE Binary (fastest) - NOT IMPLEMENTED
+ """
+
+ cmd = 'TRCA'
+ if not length:
+ length = self.buffer_length
+ self.send('{}? {},{},{}'.format(cmd, channel, start, length))
+ if cmd == 'TRCA':
+ data = self.recv()
+ return np.fromstring(data, sep=',') * ureg.volt
+ else:
+ raise ValueError('{} transfer format is not implemented'.format(format))
+
+ # Fast
+ # STRD
+
+class SR830GPIB(_SR830, GPIBVisaDriver):
+
+ RECV_TERMINATION = '\n'
+ SEND_TERMINATION = '\n'
+
+
+class SR830Serial(_SR830, SerialDriver):
+
+ RECV_TERMINATION = '\n'
+ SEND_TERMINATION = '\n'
diff --git a/lantz/drivers/legacy/sutter/__init__.py b/lantz/drivers/legacy/sutter/__init__.py
new file mode 100644
index 0000000..65f486b
--- /dev/null
+++ b/lantz/drivers/legacy/sutter/__init__.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.sutter
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ :company: Sutter Instrument.
+ :description: Biomedical and scientific instrumentation.
+ :website: http://www.sutter.com/
+
+ ---
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from .lambda103 import Lambda103
+
+__all__ = ['Lambda103', ]
diff --git a/lantz/drivers/legacy/sutter/lambda103.py b/lantz/drivers/legacy/sutter/lambda103.py
new file mode 100644
index 0000000..6221bf3
--- /dev/null
+++ b/lantz/drivers/legacy/sutter/lambda103.py
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.sutter.lambda103
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Implements the drivers to control a filter wheel.
+
+ Sources::
+
+ - Sutter Instruments manual.
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from lantz import Feat, DictFeat, Action
+from lantz.drivers.legacy.serial import SerialDriver
+
+def logged(func):
+ return func
+
+
+class Lambda103(SerialDriver):
+ """High performance, microprocessor-controlled multi-filter wheel system
+ for imaging applications requiring up to 3 filter wheels.
+ """
+
+ RECV_TERMINATION = ''
+ SEND_TERMINATION = ''
+
+ def __init__(self, port=11, baudrate=9600, timeout=1, **kwargs):
+ super().__init__(port, timeout, baudrate=baudrate, **kwargs)
+
+ self.speed = 1
+
+ @Feat(None, values={True: chr(170), False: chr(172)})
+ def open_A(self, value):
+ """Open shutter A.
+ """
+ self.send(value)
+
+ @logged
+ def flush(self):
+ """Flush.
+ """
+ self.serial.flushInput()
+ self.serial.flushOutput()
+ self.serial.flush()
+
+ # TODO: WTF 2 values for the same wheel
+ @DictFeat(None, keys={'A': 0, 'B': 1})
+ def position(self, key, value):
+ """Set filter wheel position and speed.
+
+ w = 0 -> Filter wheels A and C
+ w = 1 -> Fliter wheel B
+ """
+ command = chr( key * 128 + self.speed * 14 + value)
+ self.send(command)
+
+ @Action()
+ def motorsON(self):
+ """Power on all motors."""
+ self.send(chr(206))
+ return "Motors ON"
+
+ @Action()
+ def status(self):
+ return "Status {}".format(self.query(chr(204)))
+
+ @Feat(None, values={True: chr(238), False: chr(239)})
+ def remote(self, value):
+ """Set Local-Mode."""
+ self.send(value)
+
+ @Action()
+ def reset(self):
+ """Reset the controller."""
+ self.send(chr(251))
+
+
+if __name__ == '__main__':
+ import argparse
+ import lantz.log
+
+ parser = argparse.ArgumentParser(description='Test PI E-662')
+ parser.add_argument('-i', '--interactive', action='store_true',
+ default=False, help='Show interactive GUI')
+ parser.add_argument('-p', '--port', type=str, default='17',
+ help='Serial port to connect to')
+
+ args = parser.parse_args()
+ lantz.log.log_to_socket(lantz.log.DEBUG)
+ with Lambda103(args.port) as inst:
+ if args.interactive:
+ from lantz.ui.app import start_test_app
+ start_test_app(inst)
+ else:
+ from time import sleep
+ inst.remote = True
+
+ inst.open_A = True
+ sleep(5)
+ inst.open_A = False
+
+ sleep(1)
+ for i in range(9):
+ fw.position['A']= i
+ sleep(1)
+
+ sleep(1)
+ inst.remote = False
+
+ fw.open_A = False
+
diff --git a/lantz/drivers/legacy/tektronix/__init__.py b/lantz/drivers/legacy/tektronix/__init__.py
new file mode 100644
index 0000000..ccb180e
--- /dev/null
+++ b/lantz/drivers/legacy/tektronix/__init__.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.tektronix
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ :company: Tektronix.
+ :description: Test and Measurement Equipment.
+ :website: http://www.tek.com/
+
+ ---
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD,
+
+"""
+
+from .tds2024b import TDS2024
+from .tds1012 import TDS1012
+from .tds1002b import TDS1002b
+
+__all__ = ['TDS2024', 'TDS1002b', 'TDS1012']
+
diff --git a/lantz/drivers/legacy/tektronix/tds1002b.py b/lantz/drivers/legacy/tektronix/tds1002b.py
new file mode 100644
index 0000000..c6677e9
--- /dev/null
+++ b/lantz/drivers/legacy/tektronix/tds1002b.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.tektronix.tds1002b
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Implements the drivers to control an oscilloscope.
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+
+ Source: Tektronix Manual
+"""
+
+from lantz import Feat
+from lantz.drivers.usbtmc import USBTMCDriver
+
+
+class TDS1002b(USBTMCDriver):
+
+ @classmethod
+ def usb_from_serial(cls, serial_number):
+ # find resource and get resource name
+
+ #super().__init__(1689, 867, serial_number, **kwargs)
+
+ return cls(resource_name='bla')
+
+ @Feat(read_once=True)
+ def idn(self):
+ return self.query('*IDN?')
diff --git a/lantz/drivers/legacy/tektronix/tds1012.py b/lantz/drivers/legacy/tektronix/tds1012.py
new file mode 100644
index 0000000..4312979
--- /dev/null
+++ b/lantz/drivers/legacy/tektronix/tds1012.py
@@ -0,0 +1,240 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.tektronix.tds1012
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Implements the drivers to control an oscilloscope.
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+
+ Source: Tektronix Manual
+"""
+
+import numpy as np
+
+from lantz.feat import Feat
+from lantz.action import Action
+from lantz.drivers.legacy.serial import SerialDriver
+from lantz.errors import InvalidCommand
+
+class TDS1012(SerialDriver):
+ """Tektronix TDS1012 100MHz 2 Channel Digital Storage Oscilloscope
+ """
+ ENCODING = 'ascii'
+
+ RECV_TERMINATION = '\n'
+ SEND_TERMINATION = '\n'
+ TIMEOUT = -1 # Avoids timeout while acquiring a curve. May not be the
+ # best option.
+
+ def __init__(self, port):
+ # super().TIMEOUT = 20
+ super().__init__(port)
+ super().initialize() # Automatically open the port
+
+ @Action()
+ def initiate(self):
+ """ Initiates the acquisition in the osciloscope.
+ """
+ self.send(':ACQ:STATE ON')
+
+ @Action()
+ def idn(self):
+ """ Identify the Osciloscope
+ """
+ return self.query('*IDN?')
+
+ @Action()
+ def autoset(self):
+ """ Adjust the vertical, horizontal and trigger controls to display a
+ stable waveform.
+ """
+ self.send('AUTOS EXEC')
+
+ @Action()
+ def autocal(self):
+ """ Autocalibration of osciloscope. It may take several minutes to
+ complete
+ """
+ return self.send('*CAL')
+
+ @Feat(limits=(1,2))
+ def datasource(self):
+ """ Retrieves the data source from which data is going to be taken.
+ TDS1012 has 2 channels
+ """
+ return self.query('DAT:SOU?')
+
+ @datasource.setter
+ def datasource(self,value):
+ """ Sets the data source for the acquisition of data.
+ """
+ self.send('DAT:SOU CH{}'.format(value))
+
+ @Action()
+ def acquire_parameters(self):
+ """ Acquire parameters of the osciloscope.
+ It is intended for adjusting the values obtained in acquire_curve
+ """
+ values = 'XZE?;XIN?;PT_OF?;YZE?;YMU?;YOF?;'
+ answer = self.query('WFMP:{}'.format(values))
+ parameters = {}
+ for v, j in zip(values.split('?;'),answer.split(';')):
+ parameters[v] = float(j)
+ return parameters
+
+ @Action()
+ def data_setup(self):
+ """ Sets the way data is going to be encoded for sending.
+ """
+ self.send('DAT:ENC ASCI;WID 2') #ASCII is the least efficient way, but
+ # couldn't make the binary mode to work
+
+ @Action()
+ def acquire_curve(self,start=1,stop=2500):
+ """ Gets data from the oscilloscope. It accepts setting the start and
+ stop points of the acquisition (by default the entire range).
+ """
+ parameters = self.acquire_parameters()
+ self.data_setup()
+ self.send('DAT:STAR {}'.format(start))
+ self.send('DAT:STOP {}'.format(stop))
+ data = self.query('CURV?')
+ data = data.split(',')
+ data = np.array(list(map(float,data)))
+ ydata = (data - parameters['YOF']) * parameters['YMU']\
+ + parameters['YZE']
+ xdata = np.arange(len(data))*parameters['XIN'] + parameters['XZE']
+ return list(xdata), list(ydata)
+
+
+ @Action()
+ def forcetrigger(self):
+ """ Creates a trigger event.
+ """
+ self.send('TRIG:FORC')
+ return
+
+ @Action()
+ def triggerlevel(self):
+ """ Sets the trigger level to 50% of the minimum and maximum values of
+ the signal.
+ """
+ self.send('TRIG:MAI SETL')
+
+ @Feat(values={'AUTO', 'NORMAL'})
+ def trigger(self):
+ """ Retrieves trigger state.
+ """
+ return self.query('TRIG:MAIN:MODE?')
+
+ @trigger.setter
+ def trigger(self,state):
+ """ Sets the trigger state.
+ """
+ self.send('TRIG:MAI:MOD {}'.format(state))
+ return
+
+ @Feat()
+ def horizontal_division(self):
+ """ Horizontal time base division.
+ """
+ return float(self.query('HOR:MAI:SCA?'))
+
+ @horizontal_division.setter
+ def horizontal_division(self,value):
+ """ Sets the horizontal time base division.
+ """
+ self.send('HOR:MAI:SCA {}'.format(value))
+ return
+
+ @Feat(values={0, 4, 16, 64, 128})
+ def number_averages(self):
+ """ Number of averages
+ """
+ answer = self.query('ACQ?')
+ answer = answer.split(';')
+ if answer[0] == 'SAMPLE':
+ return 0
+ elif answer[0] == 'AVERAGE':
+ return int(self.query('ACQ:NUMAV?'))
+ else:
+ raise InvalidCommand
+
+ @number_averages.setter
+ def number_averages(self,value):
+ """ Sets the number of averages. If 0, the it is a continous sample.
+ """
+ if value == 0:
+ self.send('ACQ:MOD SAMPLE')
+ else:
+ self.send('ACQ:MOD AVE;NUMAV {}'.format(value))
+
+ @Action(values={'FREQ', 'MINI', 'MAXI', 'MEAN'})
+ def _measure(self, mode):
+ """ Measures the Frequency, Minimum, Maximum or Mean of a signal.
+ """
+ self.send('MEASU:IMM:TYP {}'.format(mode))
+ return float(self.query('MEASU:IMM:VAL?'))
+
+ def measure_mean(self):
+ """ Gets the mean of the signal.
+ """
+ answer = self._measure('MEAN')
+ return answer
+
+ def measure_frequency(self):
+ """ Gets the frequency of the signal.
+ """
+ answer = self._measure('FREQ')
+ return answer
+
+ def measure_minimum(self):
+ """ Gets the minimum of the signal.
+ """
+ answer = self._measure('MINI')
+ return answer
+
+ def measure_maximum(self):
+ """ Gets the mean of the signal.
+ """
+ answer = self._measure('MAXI')
+ return answer
+
+if __name__ == '__main__':
+ import argparse
+
+ parser = argparse.ArgumentParser(description='Measure using TDS1012 and dump to screen')
+ parser.add_argument('-p', '--port', default='/dev/ttyS0',
+ help='Serial port')
+ parser.add_argument('-v', '--view', action='store_true', default=True,
+ help='View ')
+ parser.add_argument('-c', '--channel', default=1, type=int,
+ help='Channel to use')
+
+
+ args = parser.parse_args()
+
+ osc = TDS1012(args.port)
+ osc.initiate()
+ print('Osciloscope Identification: {}'.format(osc.idn))
+ print(osc.trigger)
+ osc.forcetrigger()
+ osc.triggerlevel()
+ osc.trigger = "AUTO"
+ print(osc.trigger)
+
+ params = osc.acquire_parameters()
+
+ if args.view:
+ import matplotlib.pyplot as plt
+
+ if args.view:
+ osc.datasource = args.channel
+ x, y = osc.acquire_curve()
+ x = np.array(x)
+ x = x - x.min()
+ y = np.array(y)
+ plt.plot(x, y)
+ plt.show()
diff --git a/lantz/drivers/legacy/tektronix/tds2024b.py b/lantz/drivers/legacy/tektronix/tds2024b.py
new file mode 100644
index 0000000..2a6d11c
--- /dev/null
+++ b/lantz/drivers/legacy/tektronix/tds2024b.py
@@ -0,0 +1,199 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.tektronix.tds2024b
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Implements the drivers to control an oscilloscope.
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+import struct
+
+import numpy as np
+
+from lantz.feat import Feat
+from lantz.action import Action
+from lantz.drivers.legacy.visa import VisaDriver
+
+class TDS2024(VisaDriver):
+ """Tektronix TDS2024 200 MHz 4 Channel Digital Real-Time Oscilloscope
+ """
+
+ def __init__(self, port):
+ super().__init__(port)
+ timeout=10
+
+ @Action()
+ def autoconf(self):
+ """Autoconfig oscilloscope.
+ """
+ self.send(':AUTOS EXEC')
+
+ def initialize(self):
+ """initiate.
+ """
+ self.send(':ACQ:STATE ON')
+ return "Init"
+
+ @Feat()
+ def idn(self):
+ """IDN.
+ """
+ return self.query('ID?')
+
+ @Feat()
+ def trigger(self):
+ """Trigger state.
+ """
+ return self.query(':TRIG:STATE?')
+
+ @trigger.setter
+ def trigger(self, mode):
+ """Set trigger state.
+ """
+ self.query('TRIG:MAIN:MODE {}'.format(mode))
+
+ @Action()
+ def triggerlevel(self):
+ """Set trigger level to 50% of the minimum adn maximum
+ values of the signal.
+ """
+ self.send('TRIG:MAIn SATLevel')
+
+ @Action()
+ def forcetrigger(self):
+ """Force trigger event.
+ """
+ self.send('TRIG FORCe')
+
+ @Action()
+ def datasource(self, chn):
+ """Selects channel.
+ """
+ self.send(':DATA:SOURCE CH{}'.format(chn))
+
+ @Action()
+ def acqparams(self):
+ """ X/Y Increment Origin and Offset.
+ """
+ commands = 'XZE?;XIN?;YZE?;YMU?;YOFF?'
+ #params = self.query(":WFMPRE:XZE?;XIN?;YZE?;YMU?;YOFF?;")
+ params = self.query(':WFMPRE:{}'.format(commands))
+ params = {k: float(v) for k, v in zip(commands.split(';'), params.split(';'))}
+ return params
+
+ @Action()
+ def dataencoding(self):
+ """Set data encoding.
+ """
+ self.send(':DAT:ENC RPB;WID 2;')
+ return "Set data encoding"
+
+ @Action()
+ def curv(self):
+ """Get data.
+
+ Returns:
+ xdata, data as list
+ """
+ self.dataencoding()
+ self.send('CURV?')
+ answer = self.recv()
+ numdigs = int(answer[1])
+ bytecount = int(answer[2:2+numdigs])
+ data = answer[2+numdigs:]
+ length = bytecount / 2
+ data = struct.unpack("{}H".format(length), data[0:2*length])
+ params = self.acqparams()
+ data = np.array(list(map(float, data)))
+ yoff = params['YOFF?']
+ ymu = params['YMU?']
+ yze = params['YZE?']
+ xin = params['XIN?']
+ xze = params['XZE?']
+ ydata = ( data - yoff) * ymu + yze
+ xdata = np.arange(len(data)) * xin + xze
+ return list(xdata), list(data)
+
+ def _measure(self, type, source):
+ self.send('MEASUrement:IMMed:TYPe {}'.format(type))
+ self.send('MEASUrement:IMMed:SOUrce1 CH{}'.format(source))
+ self.send('MEASUrement:IMMed:VALue?')
+ return self.recv()
+
+ @Action()
+ def measure_frequency(self, channel):
+ """Get immediate measurement result.
+ """
+ return self._measure('FREQuency', channel)
+
+ @Action()
+ def measure_min(self, channel):
+ """Get immediate measurement result.
+ """
+ return self._measure('MINImum', channel)
+
+ @Action()
+ def measure_max(self, chn):
+ """Get immediate measurement result.
+ """
+ return self._measure('MAXImum', channel)
+
+ @Action()
+ def measure_mean(self, chn):
+ """Get immediate measurement result.
+ """
+ return self._measure('MEAN', channel)
+
+
+if __name__ == '__main__':
+ import argparse
+ import csv
+
+ parser = argparse.ArgumentParser(description='Measure using TDS2024 and dump to screen')
+ parser.add_argument('-p', '--port', default='USB0::0x0699::0x036A::C048617',
+ help='USB port')
+ parser.add_argument('-v', '--view', action='store_true', default=False,
+ help='View ')
+ parser.add_argument('Channels', metavar='channels', type=int, nargs='*',
+ help='Channels to use')
+ parser.add_argument('--output', type=argparse.FileType('wb', 0), default='-')
+
+ args = parser.parse_args()
+
+ osc = TDS2024(args.port)
+ osc.initialize()
+ print(osc.idn)
+ print(osc.trigger)
+ osc.forcetrigger()
+ osc.triggerlevel()
+ osc.trigger = "AUTO"
+ print(osc.trigger)
+ #osc.autoconf()
+ params = osc.acqparams()
+
+ if args.view:
+ import matplotlib.pyplot as plt
+ import numpy as np
+
+ with args.output as fp:
+ writer = csv.writer(fp)
+ writer.write_row(('Channel', 'Freq', 'Max', 'Min', 'Mean'))
+ for channel in args.channels or range(1, 4):
+ osc.datasource(channel)
+ writer.write_row(([osc.measure_frequency(channel),
+ osc.measure_max(channel),
+ osc.measure_min(channel),
+ osc.measure_mean(channel)]))
+
+ if args.view:
+ x, y = osc.curv()
+ x = np.array(x)
+ x = x - x.min()
+ y = np.array(y)
+ plt.plot(x, y)
+
+ if args.view:
+ plt.show()
diff --git a/lantz/drivers/legacy/textual.py b/lantz/drivers/legacy/textual.py
new file mode 100644
index 0000000..3d736d5
--- /dev/null
+++ b/lantz/drivers/legacy/textual.py
@@ -0,0 +1,159 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.legacy.textual
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Implements a Mixin class for message based instruments,
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+import time
+from lantz.errors import LantzTimeoutError
+from lantz.processors import ParseProcessor
+
+
+class TextualMixin(object):
+ """Mixin class for classes that communicate with instruments
+ exchanging text messages.
+
+ Ideally, transport classes should provide receive methods
+ that support:
+ 1. query the number of available bytes
+ 2. read chunks of bytes (with and without timeout)
+ 3. read until certain character is found (with and without timeout)
+
+ Most transport layers support 1 and 2 but not all support 3 (or only
+ for a defined set of characters) and TextualMixin provides fallback
+ a method.
+ """
+
+ #: Encoding to transform string to bytes and back as defined in
+ #: http://docs.python.org/py3k/library/codecs.html#standard-encodings
+ ENCODING = 'ascii'
+ #: Termination characters for receiving data, if not given RECV_CHUNK
+ #: number of bytes will be read.
+ RECV_TERMINATION = ''
+ #: Termination characters for sending data
+ SEND_TERMINATION = ''
+ #: Timeout in seconds of the complete read operation.
+ TIMEOUT = 1
+ #: Parsers
+ PARSERS = {}
+ #: Size in bytes of the receive chunk (-1 means all bytes in buffer)
+ RECV_CHUNK = 1
+
+ #: String containing the part of the message after RECV_TERMINATION
+ #: Used in software based finding of termination character when
+ #: RECV_CHUNK > 1
+ _received = ''
+
+ def raw_recv(self, size):
+ """Receive raw bytes from the instrument. No encoding or termination
+ character should be applied.
+
+ This method must be implemented by base classes.
+
+ :param size: number of bytes to receive.
+ :return: received bytes, eom
+ :rtype: bytes, bool
+ """
+ raise NotImplemented
+
+ def raw_send(self, data):
+ """Send raw bytes to the instrument. No encoding or termination
+ character should be applied.
+
+ This method must be implemented by base classes.
+
+ :param data: bytes to be sent to the instrument.
+ :param data: bytes.
+ """
+ raise NotImplemented
+
+ def send(self, command, termination=None, encoding=None):
+ """Send command to the instrument.
+
+ :param command: command to be sent to the instrument.
+ :type command: string.
+
+ :param termination: termination character to override class defined
+ default.
+ :param encoding: encoding to transform string to bytes to override class
+ defined default.
+
+ :return: number of bytes sent.
+
+ """
+ if termination is None:
+ termination = self.SEND_TERMINATION
+ if encoding is None:
+ encoding = self.ENCODING
+
+ message = bytes(command + termination, encoding)
+ self.log_debug('Sending {}', message)
+ return self.raw_send(message)
+
+ def recv(self, termination=None, encoding=None, recv_chunk=None):
+ """Receive string from instrument.
+
+ :param termination: termination character (overrides class default)
+ :type termination: str
+ :param encoding: encoding to transform bytes to string (overrides class default)
+ :param recv_chunk: number of bytes to receive (overrides class default)
+ :return: string encoded from received bytes
+ """
+
+ termination = termination or self.RECV_TERMINATION
+ encoding = encoding or self.ENCODING
+ recv_chunk = recv_chunk or self.RECV_CHUNK
+
+ if not termination:
+ return str(self.raw_recv(recv_chunk), encoding)
+
+ if self.TIMEOUT is None or self.TIMEOUT < 0:
+ stop = float('+inf')
+ else:
+ stop = time.time() + self.TIMEOUT
+
+ received = self._received
+ eom = False
+ while not (termination in received or eom):
+ if time.time() > stop:
+ raise LantzTimeoutError
+ raw_received = self.raw_recv(recv_chunk)
+ received += str(raw_received, encoding)
+
+ self.log_debug('Received {!r} (len={})', received, len(received))
+
+ received, self._received = received.split(termination, 1)
+
+ return received
+
+ def query(self, command, *, send_args=(None, None), recv_args=(None, None)):
+ """Send query to the instrument and return the answer
+
+ :param command: command to be sent to the instrument
+ :type command: string
+
+ :param send_args: (termination, encoding) to override class defaults
+ :param recv_args: (termination, encoding) to override class defaults
+ """
+
+ self.send(command, *send_args)
+ return self.recv(*recv_args)
+
+ def parse_query(self, command, *,
+ send_args=(None, None), recv_args=(None, None),
+ format=None):
+ """Send query to the instrument, parse the output using format
+ and return the answer.
+
+ .. seealso:: TextualMixin.query and stringparser
+ """
+ ans = self.query(command, send_args=send_args, recv_args=recv_args)
+ if format:
+ parser = self.PARSERS.setdefault(format, ParseProcessor(format))
+ ans = parser(ans)
+ return ans
diff --git a/lantz/usb.py b/lantz/drivers/legacy/usb.py
similarity index 98%
rename from lantz/usb.py
rename to lantz/drivers/legacy/usb.py
index 4e3ca75..56e4cd4 100644
--- a/lantz/usb.py
+++ b/lantz/drivers/legacy/usb.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""
- lantz.usb
- ~~~~~~~~~
+ lantz.drivers.legacy.usb
+ ~~~~~~~~~~~~~~~~~~~~~~~~
Implements base classes for drivers that communicate with instruments
via usb using PyUSB
@@ -10,7 +10,7 @@
http://www.beyondlogic.org/usbnutshell/usb5.shtml
- :copyright: 2013 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
@@ -21,8 +21,8 @@
from usb.util import (get_string as usb_get_string,
find_descriptor as usb_find_desc)
-from . import Driver
-from .errors import LantzTimeoutError, InstrumentError
+from lantz import Driver
+from lantz.errors import LantzTimeoutError, InstrumentError
ClassCodes = {
diff --git a/lantz/drivers/usbtmc.py b/lantz/drivers/legacy/usbtmc.py
similarity index 95%
rename from lantz/drivers/usbtmc.py
rename to lantz/drivers/legacy/usbtmc.py
index feb2389..b334365 100644
--- a/lantz/drivers/usbtmc.py
+++ b/lantz/drivers/legacy/usbtmc.py
@@ -1,14 +1,14 @@
# -*- coding: utf-8 -*-
"""
- lantz.drivers.usbtmc
- ~~~~~~~~~~~~~~~~~~~~
+ lantz.drivers.legacy.usbtmc
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
Implements a USBDriver based class to control USBTMC instruments
Loosely based on PyUSBTMC:python module to handle USB-TMC(Test and Measurement class) devices.
by Noboru Yamamot, Accl. Lab, KEK, JAPAN
- :copyright: 2013 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
@@ -18,10 +18,9 @@
from collections import namedtuple
import usb
-
-from lantz.driver import TextualMixin
+from lantz.drivers.legacy.textual import TextualMixin
from lantz.errors import InstrumentError
-from lantz.usb import find_devices, find_interfaces, find_endpoint, USBDriver
+from lantz.drivers.legacy.usb import find_devices, find_interfaces, find_endpoint, USBDriver
class MSGID(enum.IntEnum):
diff --git a/lantz/visa.py b/lantz/drivers/legacy/visa.py
similarity index 91%
rename from lantz/visa.py
rename to lantz/drivers/legacy/visa.py
index 69bc24a..6bcd75d 100644
--- a/lantz/visa.py
+++ b/lantz/drivers/legacy/visa.py
@@ -1,17 +1,17 @@
# -*- coding: utf-8 -*-
"""
- lantz.visa
- ~~~~~~~~~~
+ lantz.drivers.legacy.visa
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
Implements base classes for drivers that communicate with instruments using visalib.
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
-from . import Driver
-from .driver import TextualMixin
-from .errors import LantzTimeoutError
+from lantz import Driver
+from lantz.drivers.legacy.textual import TextualMixin
+from lantz.errors import LantzTimeoutError
import visa
@@ -35,7 +35,10 @@ class VisaDriver(object):
def __new__(cls, resource_name, *args, **kwargs):
library_path = kwargs.get('library_path', None)
- manager = visa.ResourceManager(library_path)
+ if library_path:
+ manager = visa.ResourceManager(library_path)
+ else:
+ manager = visa.ResourceManager()
name = manager.resource_info(resource_name).resource_name
if name.startswith('GPIB'):
return GPIBVisaDriver(resource_name, *args, **kwargs)
@@ -69,7 +72,10 @@ def __init__(self, resource_name, *args, **kwargs):
self._init_attributes = {}
library_path = kwargs.get('library_path', None)
- self.resource_manager = visa.ResourceManager(library_path)
+ if library_path:
+ self.resource_manager = visa.ResourceManager(library_path)
+ else:
+ self.resource_manager = visa.ResourceManager()
self.resource = None
diff --git a/lantz/visalib.py b/lantz/drivers/legacy/visalib.py
similarity index 99%
rename from lantz/visalib.py
rename to lantz/drivers/legacy/visalib.py
index acb6257..be2b60b 100644
--- a/lantz/visalib.py
+++ b/lantz/drivers/legacy/visalib.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""
- lantz.visalib
- ~~~~~~~~~~~~~
+ lantz.drivers.legacy.visalib
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Wraps Visa Library in a Python friendly way.
@@ -27,7 +27,7 @@
still usable.
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/lantz/drivers/vxi11.py b/lantz/drivers/legacy/vxi11.py
similarity index 98%
rename from lantz/drivers/vxi11.py
rename to lantz/drivers/legacy/vxi11.py
index 269cab5..f84f987 100644
--- a/lantz/drivers/vxi11.py
+++ b/lantz/drivers/legacy/vxi11.py
@@ -1,13 +1,13 @@
# -*- coding: utf-8 -*-
"""
- lantz.drivers.usbtmc
- ~~~~~~~~~~~~~~~~~~~~
+ lantz.drivers.legacy.vx11
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
Implements a VXI11Driver based class to control VXI11 instruments
Loosely based on Python Sun RPC Demo and Alex Forencich python-vx11
- :copyright: 2013 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
@@ -15,9 +15,10 @@
import socket
from lantz.errors import InstrumentError
-from lantz.utils import rpc
+from lantz.drivers.legacy import rpc
from lantz import Driver
+
# VXI-11 RPC constants
# Device async
diff --git a/lantz/drivers/mpb/__init__.py b/lantz/drivers/mpb/__init__.py
index 71cbe32..9adfe51 100644
--- a/lantz/drivers/mpb/__init__.py
+++ b/lantz/drivers/mpb/__init__.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""
lantz.drivers.mpb
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~~~~~~~~~~~~~~~~~
:company: MPB Communications Inc.
:description: Laser products.
@@ -9,7 +9,7 @@
----
- :copyright: 2014 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/lantz/drivers/mpb/vfl.py b/lantz/drivers/mpb/vfl.py
index 4365005..523a268 100644
--- a/lantz/drivers/mpb/vfl.py
+++ b/lantz/drivers/mpb/vfl.py
@@ -1,35 +1,35 @@
# -*- coding: utf-8 -*-
"""
lantz.drivers.mpb.vfl
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~~~~~~~~~~~~~~~~~~~~~
- :copyright: 2014 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
-from lantz import Action, Feat, DictFeat
-from lantz.serial import SerialDriver
-from lantz.errors import InstrumentError
+from pyvisa import constants
+from lantz import Action, Feat
+from lantz.messagebased import MessageBasedDriver
-class VFL(SerialDriver):
+
+class VFL(MessageBasedDriver):
"""Driver for any VFL MPB Communications laser.
"""
- ENCODING = 'ascii'
-
- RECV_TERMINATION = '\rD >'
- SEND_TERMINATION = '\r'
-
- BAUDRATE = 9600
- BYTESIZE = 8
- PARITY = 'none'
- STOPBITS = 1
+ DEFAULTS = {'ASRL': {'write_termination': '\rD >',
+ 'read_termination': '\r\n',
+ 'baud_rate': 1200,
+ 'bytesize': 8,
+ 'parity': constants.Parity.none,
+ 'stop_bits': constants.StopBits.one,
+ 'encoding': 'ascii',
+ }}
#: flow control flags
- RTSCTS = False
- DSRDTR = False
- XONXOFF = False
+ #RTSCTS = False
+ #DSRDTR = False
+ #XONXOFF = False
@Feat(read_once=True)
def idn(self):
@@ -197,7 +197,7 @@ def tune_shg_stop(self):
args = parser.parse_args()
lantz.log.log_to_screen(lantz.log.DEBUG)
- with VFL(args.port) as inst:
+ with VFL.from_serial_port(args.port) as inst:
if args.interactive:
from lantz.ui.qtwidgets import start_test_app
start_test_app(inst)
diff --git a/lantz/drivers/newport/__init__.py b/lantz/drivers/newport/__init__.py
index 9ee120b..3f0424a 100644
--- a/lantz/drivers/newport/__init__.py
+++ b/lantz/drivers/newport/__init__.py
@@ -9,11 +9,11 @@
---
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD,
"""
-from .powermeter1830c import powermeter1830c
+from .powermeter1830c import PowerMeter1830c
-__all__ = ['powermeter1830c']
+__all__ = ['PowerMeter1830c']
diff --git a/lantz/drivers/newport/powermeter1830c.py b/lantz/drivers/newport/powermeter1830c.py
index 8e8c787..8b37fb0 100644
--- a/lantz/drivers/newport/powermeter1830c.py
+++ b/lantz/drivers/newport/powermeter1830c.py
@@ -1,37 +1,38 @@
# -*- coding: utf-8 -*-
"""
lantz.drivers.newport.powermeter1830c
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Implements the drivers to control an Optical Power Meter.
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
Source: Instruction Manual (Newport)
"""
+from pyvisa import constants
+
from lantz.feat import Feat
from lantz.action import Action
-from lantz.serial import SerialDriver
-from lantz.errors import InvalidCommand
+from lantz.messagebased import MessageBasedDriver
+
-class powermeter1830c(SerialDriver):
+class PowerMeter1830c(MessageBasedDriver):
""" Newport 1830c Power Meter
"""
-
- ENCODING = 'ascii'
- RECV_TERMINATION = '\n'
- SEND_TERMINATION = '\n'
- TIMEOUT = 2000
-
- def __init__(self, port=1,baudrate=9600):
- super().__init__(port, baudrate, bytesize=8, parity='None', stopbits=1)
-
- def initialize(self):
- super().initialize()
-
+ DEFAULTS = {'ASRL': {'write_termination': '\n',
+ 'read_termination': '\n',
+ 'baud_rate': 9600,
+ 'bytesize': 8,
+ 'parity': constants.Parity.none,
+ 'stop_bits': constants.StopBits.one,
+ 'encoding': 'ascii',
+ 'timeout': 2000
+ }}
+
+
@Feat(values={True: 1, False: 0})
def attenuator(self):
""" Attenuator.
@@ -179,7 +180,7 @@ def zero(self,value):
args = parser.parse_args()
lantz.log.log_to_socket(lantz.log.DEBUG)
- with powermeter1830c(args.port) as inst:
+ with PowerMeter1830c.from_serial_port(args.port) as inst:
inst.initialize() # Initialize the communication with the power meter
@@ -195,4 +196,4 @@ def zero(self,value):
inst.range = 0 # Auto-sets the range
- print('The measured power is {} Watts'.format(inst.data))
\ No newline at end of file
+ print('The measured power is {} Watts'.format(inst.data))
diff --git a/lantz/drivers/ni/__init__.py b/lantz/drivers/ni/__init__.py
index 5ac6e68..d981148 100644
--- a/lantz/drivers/ni/__init__.py
+++ b/lantz/drivers/ni/__init__.py
@@ -9,7 +9,7 @@
----
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/lantz/drivers/ni/daqmx/__init__.py b/lantz/drivers/ni/daqmx/__init__.py
index 67bc617..a749b22 100644
--- a/lantz/drivers/ni/daqmx/__init__.py
+++ b/lantz/drivers/ni/daqmx/__init__.py
@@ -19,7 +19,7 @@
----
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/lantz/drivers/ni/daqmx/base.py b/lantz/drivers/ni/daqmx/base.py
index 4e5a04a..50be24f 100644
--- a/lantz/drivers/ni/daqmx/base.py
+++ b/lantz/drivers/ni/daqmx/base.py
@@ -6,7 +6,7 @@
Implementation of base classes for Channels, Tasks and Devices
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
@@ -1199,7 +1199,7 @@ def sample_clock_rate(self, value):
if value is None:
self.lib.ResetSampClkRate()
else:
- return self.lib.SetSampClkRate(value)
+ self.lib.SetSampClkRate(value)
@Feat()
def convert_clock_rate(self):
@@ -1225,7 +1225,7 @@ def convert_clock_rate(self, value):
if value is None:
self.lib.ResetAIConvRate()
else:
- return self.lib.SetAIConvRate(value)
+ self.lib.SetAIConvRate(value)
def sample_clock_max_rate(self):
"""Maximum Sample Clock rate supported by the task,
diff --git a/lantz/drivers/ni/daqmx/channels.py b/lantz/drivers/ni/daqmx/channels.py
index c4f34cd..3a19073 100644
--- a/lantz/drivers/ni/daqmx/channels.py
+++ b/lantz/drivers/ni/daqmx/channels.py
@@ -5,7 +5,7 @@
Implementation of specialized channel classes.
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
@@ -397,7 +397,7 @@ def __init__(
if units_val != Constants.Val_FromCustomScale:
customScaleName = None
- return CALL(
+ CALL(
'CreateCILinEncoderChan',
self,
counter,
diff --git a/lantz/drivers/ni/daqmx/constants.py b/lantz/drivers/ni/daqmx/constants.py
index 8e2e7ae..3fa2c11 100644
--- a/lantz/drivers/ni/daqmx/constants.py
+++ b/lantz/drivers/ni/daqmx/constants.py
@@ -5,11 +5,11 @@
Constants and types for DAQmx
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
-from lantz.visalib import RichEnum
+from lantz.drivers.legacy.visalib import RichEnum
class Constants(metaclass=RichEnum):
diff --git a/lantz/drivers/ni/daqmx/tasks.py b/lantz/drivers/ni/daqmx/tasks.py
index c3e451d..eddfc73 100644
--- a/lantz/drivers/ni/daqmx/tasks.py
+++ b/lantz/drivers/ni/daqmx/tasks.py
@@ -5,7 +5,7 @@
Implementation of specialized tasks clases.
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/lantz/drivers/oceanoptics/usb4000.py b/lantz/drivers/oceanoptics/usb4000.py
new file mode 100644
index 0000000..0bbdcff
--- /dev/null
+++ b/lantz/drivers/oceanoptics/usb4000.py
@@ -0,0 +1,198 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.oceanoptics.usb4000
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Driver for spectrograph.
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+
+import struct
+import numpy as np
+
+from pyvisa.errors import VisaIOError
+
+from lantz import Feat, DictFeat
+from lantz.drivers.legacy.usb import USBDriver, usb_find_desc
+
+__all__ = ['USB4000']
+
+commands = {
+ 0x01: ('init', 'initialize USB4000'),
+ 0x02: ('set_i', 'set integration time in uS'),
+ 0x03: ('set_strobe', 'set strobe enable status'),
+ 0x05: ('ask', 'query information'),
+ 0x06: ('write', 'write information'),
+ 0x09: ('get_spectra', 'request spectra'),
+ 0x0A: ('set_trigger', 'set trigger mode'),
+ 0x0B: ('num_plugins', 'query number of plug-in accessories present'),
+ 0x0C: ('plugin_ids', 'query plug-in identifiers'),
+ 0x0D: ('detect_plugins', 'detect plug-ins'),
+ 0x60: ('read_i2c', 'general I2C read'),
+ 0x61: ('write_i2c', 'general I2C write'),
+ 0x62: ('spi_io', 'general spi I/O'),
+ 0x68: ('read_psoc', 'PSOC read'),
+ 0x69: ('write_psoc', 'PSOC write'),
+ 0x6A: ('write_reg', 'write register information'),
+ 0x6B: ('read_reg', 'read register information'),
+ 0x6C: ('read_temp', 'read PCB temperature'),
+ 0x6D: ('read_calib', 'read irradiance calibration factors'),
+ 0x6E: ('write_calib', 'write irradiance calibration factors'),
+ 0xFE: ('ask_2', 'query information')
+}
+
+config_regs = {
+ 0: 'serial_number',
+ 1: '0_order_wavelength_coeff',
+ 2: '1_order_wavelength_coeff',
+ 3: '2_order_wavelength_coeff',
+ 4: '3_order_wavelength_coeff',
+ 5: 'stray_light_constant',
+ 6: '0_order_nonlinear_coeff',
+ 7: '1_order_nonlinear_coeff',
+ 8: '2_order_nonlinear_coeff',
+ 9: '3_order_nonlinear_coeff',
+ 10: '4_order_nonlinear_coeff',
+ 11: '5_order_nonlinear_coeff',
+ 12: '6_order_nonlinear_coeff',
+ 13: '7_order_nonlinear_coeff',
+ 14: 'polynomial_order',
+ 15: 'bench_configuration',
+ 16: 'USB4000_config',
+ 17: 'autonull',
+ 18: 'baud_rate'
+}
+
+
+class USB4000(USBDriver):
+ """Ocean Optics spectrometer
+ """
+
+ def __init__(self, serial_number=None, **kwargs):
+ super().__init__(vendor=0x2457, product=0x1022, serial_number=serial_number, **kwargs)
+
+ self._spec_hi = usb_find_desc(self.usb_inf, bEndpointAddress=0x82)
+ self._spec_lo = usb_find_desc(self.usb_inf, bEndpointAddress=0x86)
+
+ # initialize spectrometer
+ self.initialize()
+
+ def initialize(self):
+ """Ends the initialization command to the USB4000 spectrometer
+
+ This command initializes certain parameters on the USB4000 and sets internal variables
+ based on the USB communication speed. This command is called at object instantiation.
+ """
+ self.usb_send_ep.write(struct.pack('H', resp[1:3])[0]
+ log.info('firmware is {:d}'.format(vers))
+ return vers
+
+ trigger = Feat(values={'Freerun': 0, 'Software': 1, 'ExternalSync': 2, 'ExternalHard': 3})
+
+ @trigger.setter
+ def trigger(self, mode):
+ cmd = struct.pack(' 0:
@@ -129,7 +131,7 @@ def move_relative(self, value):
self.query('D')
else:
raise ValueError('Specify the translation distance in micrometer unit')
- '''
+ """
try:
if value.units == 'micrometer':
if value.magnitude > 0:
@@ -149,8 +151,8 @@ def move_relative(self, value):
@Feat(units='micrometer')
def step(self):
- '''Report and set the default step size, in microns
- '''
+ """Report and set the default step size, in microns
+ """
return self.query('C')
@step.setter
@@ -161,17 +163,20 @@ def step (self, value):
@Feat(read_once=True)
def software_version(self):
- '''Software version
- '''
+ """Software version
+ """
return self.query('VER')
class NanoScanZ_chained(NanoScanZ):
- '''This is needed when the NanoScanZ controller is connected to a ProScanII controller through its RS-232-2 input
- '''
- def send(self, command, termination=None, encoding=None):
- '<' + super.send(command, termination=termination, encoding=encoding)
- def recv(self, termination=None, encoding=None, recv_chunk=None):
- super.recv(termination=termination, encoding=encoding, recv_chunk=recv_chunk)[1:]
+ """This is needed when the NanoScanZ controller is connected
+ to a ProScanII controller through its RS-232-2 input
+ """
+ def write(self, command, termination=None, encoding=None):
+ super().send('<' + command, termination=termination, encoding=encoding)
+
+ def read(self, termination=None, encoding=None):
+ return super().read(termination=termination, encoding=encoding)[1:]
+
if __name__ == '__main__':
import argparse
@@ -185,7 +190,7 @@ def recv(self, termination=None, encoding=None, recv_chunk=None):
args = parser.parse_args()
lantz.log.log_to_screen(lantz.log.DEBUG)
- with NanoScanZ(args.port) as inst:
+ with NanoScanZ.from_serial_port(args.port) as inst:
if args.interactive:
from lantz.ui.app import start_test_app
start_test_app(inst)
diff --git a/lantz/drivers/rgblasersystems/__init__.py b/lantz/drivers/rgblasersystems/__init__.py
index 9762e00..519a16f 100644
--- a/lantz/drivers/rgblasersystems/__init__.py
+++ b/lantz/drivers/rgblasersystems/__init__.py
@@ -9,7 +9,7 @@
----
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/lantz/drivers/rgblasersystems/minilasevo.py b/lantz/drivers/rgblasersystems/minilasevo.py
index ae5fd94..759681c 100644
--- a/lantz/drivers/rgblasersystems/minilasevo.py
+++ b/lantz/drivers/rgblasersystems/minilasevo.py
@@ -3,33 +3,34 @@
lantz.drivers.rgblasersystems.minilasevo
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
-from lantz import Action, Feat, DictFeat
-from lantz.serial import SerialDriver
+from pyvisa import constants
+
+from lantz import Feat
+from lantz.messagebased import MessageBasedDriver
from lantz.errors import InstrumentError
-class MiniLasEvo(SerialDriver):
+class MiniLasEvo(MessageBasedDriver):
"""Driver for any RGB Lasersystems MiniLas Evo laser.
"""
- ENCODING = 'ascii'
-
- RECV_TERMINATION = '\r\n'
- SEND_TERMINATION = '\r\n'
-
- BAUDRATE = 57600
- BYTESIZE = 8
- PARITY = 'none'
- STOPBITS = 1
+ DEFAULTS = {'ASRL': {'write_termination': '\r\n',
+ 'read_termination': '\r\n',
+ 'baud_rate': 57600,
+ 'bytesize': 8,
+ 'parity': constants.Parity.none,
+ 'stop_bits': constants.StopBits.one,
+ 'encoding': 'ascii'
+ }}
#: flow control flags
- RTSCTS = False
- DSRDTR = False
- XONXOFF = False
+ #RTSCTS = False
+ #DSRDTR = False
+ #XONXOFF = False
def query(self, command, *, send_args=(None, None),
recv_args=(None, None)):
diff --git a/lantz/drivers/rigol/__init__.py b/lantz/drivers/rigol/__init__.py
new file mode 100644
index 0000000..452ff47
--- /dev/null
+++ b/lantz/drivers/rigol/__init__.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.rigol
+ ~~~~~~~~~~~~~~~~~~~
+
+ :company: Rigol.
+ :description: Acquisition devices
+ :website: http://www.rigolna.com/
+
+ ----
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from .ds1052e import DS1052e
+
+__all__ = ['DS1052e']
diff --git a/lantz/drivers/rigol/ds1052e.py b/lantz/drivers/rigol/ds1052e.py
new file mode 100644
index 0000000..ac6e4c3
--- /dev/null
+++ b/lantz/drivers/rigol/ds1052e.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.rigol.ds1052e
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Implements the drivers to control an oscilloscope.
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+
+ Source: DS1052e manual
+"""
+
+
+from lantz.messagebased import MessageBasedDriver
+
+
+class DS1052e(MessageBasedDriver):
+ pass
+
+
+
+if __name__ == '__main__':
+ with DS1052e('USB0::0x1AB1::0x0588::DS1K00005888::INSTR') as inst:
+ print(inst.query('*IDN?'))
diff --git a/lantz/drivers/scpi.py b/lantz/drivers/scpi.py
index a5309d6..0356bb9 100644
--- a/lantz/drivers/scpi.py
+++ b/lantz/drivers/scpi.py
@@ -7,7 +7,7 @@
Standard Commands for Programmable Instruments
- :copyright: 2014 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/lantz/drivers/stanford/__init__.py b/lantz/drivers/stanford/__init__.py
index c027163..b60f300 100644
--- a/lantz/drivers/stanford/__init__.py
+++ b/lantz/drivers/stanford/__init__.py
@@ -9,10 +9,10 @@
----
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
-from .sr830 import SR830Serial, SR830GPIB
+from .sr830 import SR830
-__all__ = ['SR830Serial', 'SR830GPIB']
+__all__ = ['SR830', ]
diff --git a/lantz/drivers/stanford/sr830.py b/lantz/drivers/stanford/sr830.py
index 3fff1ff..074ce6b 100644
--- a/lantz/drivers/stanford/sr830.py
+++ b/lantz/drivers/stanford/sr830.py
@@ -3,18 +3,16 @@
lantz.drivers.stanford.sr830
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
-import numpy as np
+from collections import OrderedDict
+import numpy as np
from lantz import Action, Feat, DictFeat, ureg
-from lantz.serial import SerialDriver
-from lantz.visa import GPIBVisaDriver
-from lantz.errors import InstrumentError
+from lantz.messagebased import MessageBasedDriver
-from collections import OrderedDict
SENS = OrderedDict([
('2 nV/fA', 0),
@@ -88,7 +86,13 @@
('trigger', 14)
])
-class _SR830(object):
+
+class SR830(MessageBasedDriver):
+
+ DEFAULTS = {'COMMON': {'write_termination': '\n',
+ 'read_termination': '\n',
+ }}
+
@Feat(units='degrees', limits=(-360, 729.99, 0.01))
def reference_phase_shift(self):
@@ -307,7 +311,7 @@ def key_click_enabled(self):
@key_click_enabled.setter
def key_click_enabled(self, value):
- return self.send('KCLK {}'.format(value))
+ self.send('KCLK {}'.format(value))
@Feat(values={True: 1, False: 0})
def alarm_enabled(self):
@@ -317,7 +321,7 @@ def alarm_enabled(self):
@alarm_enabled.setter
def alarm_enabled(self, value):
- return self.send('ALRM {}'.format(value))
+ self.send('ALRM {}'.format(value))
@Action(limits=(1, 9))
def recall_state(self, location):
@@ -469,7 +473,7 @@ def analog_value(self, key):
def measure(self, channels):
d = {'x': '1', 'y': '2', 'r': '3', 't': '4',
'1': '5', '2': '6', '3': '7', '4': '8',
- 'f': '9', '': 10, '': 11}
+ 'f': '9'}#, '': 10, '': 11} TODO: how to deal with these?
channels = ','.join(d[ch] for ch in channels)
self.query('SNAP? {}'.format(channels))
@@ -506,13 +510,3 @@ def read_buffer(self, channel, start=0, length=None, format='A'):
# Fast
# STRD
-class SR830GPIB(_SR830, GPIBVisaDriver):
-
- RECV_TERMINATION = '\n'
- SEND_TERMINATION = '\n'
-
-
-class SR830Serial(_SR830, SerialDriver):
-
- RECV_TERMINATION = '\n'
- SEND_TERMINATION = '\n'
diff --git a/lantz/drivers/sutter/__init__.py b/lantz/drivers/sutter/__init__.py
index f55a414..f709816 100644
--- a/lantz/drivers/sutter/__init__.py
+++ b/lantz/drivers/sutter/__init__.py
@@ -9,7 +9,7 @@
---
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/lantz/drivers/sutter/lambda103.py b/lantz/drivers/sutter/lambda103.py
index 43264dc..a36f4aa 100644
--- a/lantz/drivers/sutter/lambda103.py
+++ b/lantz/drivers/sutter/lambda103.py
@@ -9,28 +9,30 @@
- Sutter Instruments manual.
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from lantz import Feat, DictFeat, Action
-from lantz.serial import SerialDriver
+from lantz.messagebased import MessageBasedDriver
+
def logged(func):
return func
-class Lambda103(SerialDriver):
+class Lambda103(MessageBasedDriver):
"""High performance, microprocessor-controlled multi-filter wheel system
for imaging applications requiring up to 3 filter wheels.
"""
- RECV_TERMINATION = ''
- SEND_TERMINATION = ''
+ DEFAULTS = {'ASRL': {'write_termination': '',
+ 'read_termination': '',
+ }}
- def __init__(self, port=11, baudrate=9600, timeout=1, *args, **kwargs):
- super().__init__(port, baudrate, timeout, *args, **kwargs)
+ def initialize(self):
+ super().initialize()
self.speed = 1
@Feat(None, values={True: chr(170), False: chr(172)})
diff --git a/lantz/drivers/tektronix/__init__.py b/lantz/drivers/tektronix/__init__.py
index 428ad1f..f14ff2e 100644
--- a/lantz/drivers/tektronix/__init__.py
+++ b/lantz/drivers/tektronix/__init__.py
@@ -9,7 +9,7 @@
---
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD,
"""
diff --git a/lantz/drivers/tektronix/afg3021b.py b/lantz/drivers/tektronix/afg3021b.py
new file mode 100644
index 0000000..0087ec9
--- /dev/null
+++ b/lantz/drivers/tektronix/afg3021b.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.tektronix.afg3021b
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Implements the drivers to control a signal generator.
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from lantz import Feat
+
+from lantz.messagebased import MessageBasedDriver
+
+
+
+class AFG3021b(MessageBasedDriver):
+
+ MANUFACTURER_ID = '0x0699'
+ MODEL_CODE = '0x0346'
+
+ @Feat()
+ def idn(self):
+ return inst.query('*IDN?')
+
+
+if __name__ == '__main__':
+ import argparse
+ import lantz.log
+
+ parser = argparse.ArgumentParser(description='Test Kentech HRI')
+ parser.add_argument('-i', '--interactive', action='store_true',
+ default=False, help='Show interactive GUI')
+ parser.add_argument('-p', '--port', type=str, default='17',
+ help='Serial port to connect to')
+
+ args = parser.parse_args()
+ lantz.log.log_to_socket(lantz.log.DEBUG)
+
+ with AFG3021b('USB0::0x0699::0x0346::C033250::INSTR') as inst:
+ print(inst.idn)
+
+ with AFG3021b.from_hostname('192.168.0.1') as inst:
+ print(inst.idn)
+
+ with AFG3021b.from_usbtmc(serial_number='C033250') as inst:
+ print(inst.idn)
diff --git a/lantz/drivers/tektronix/tds1002b.py b/lantz/drivers/tektronix/tds1002b.py
index 537a174..285b5d8 100644
--- a/lantz/drivers/tektronix/tds1002b.py
+++ b/lantz/drivers/tektronix/tds1002b.py
@@ -1,13 +1,22 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.drivers.tektronix.tds1012
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ Implements the drivers to control an oscilloscope.
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
from lantz import Feat
-from lantz.drivers.usbtmc import USBTMCDriver
+from lantz.messagebased import MessageBasedDriver
-class TDS1002b(USBTMCDriver):
+class TDS1002b(MessageBasedDriver):
- def __init__(self, serial_number=None, **kwargs):
- super().__init__(1689, 867, serial_number, **kwargs)
+ MANUFACTURER_ID = '0x699'
+ MODEL_CODE = '0x363'
@Feat(read_once=True)
def idn(self):
diff --git a/lantz/drivers/tektronix/tds1012.py b/lantz/drivers/tektronix/tds1012.py
index 9b1d65e..b771f3f 100644
--- a/lantz/drivers/tektronix/tds1012.py
+++ b/lantz/drivers/tektronix/tds1012.py
@@ -5,34 +5,26 @@
Implements the drivers to control an oscilloscope.
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
Source: Tektronix Manual
"""
-from numpy import array, arange
+import numpy as np
from lantz.feat import Feat
from lantz.action import Action
-from lantz.serial import SerialDriver
+from lantz.messagebased import MessageBasedDriver
from lantz.errors import InvalidCommand
-class TDS1012(SerialDriver):
+
+class TDS1012(MessageBasedDriver):
"""Tektronix TDS1012 100MHz 2 Channel Digital Storage Oscilloscope
"""
- ENCODING = 'ascii'
- RECV_TERMINATION = '\n'
- SEND_TERMINATION = '\n'
- TIMEOUT = -1 # Avoids timeout while acquiring a curve. May not be the
- # best option.
-
- def __init__(self, port):
- # super().TIMEOUT = 20
- super().__init__(port)
- super().initialize() # Automatically open the port
-
+ MANUFACTURER_ID = '0x699'
+
@Action()
def initiate(self):
""" Initiates the acquisition in the osciloscope.
@@ -102,10 +94,10 @@ def acquire_curve(self,start=1,stop=2500):
self.send('DAT:STOP {}'.format(stop))
data = self.query('CURV?')
data = data.split(',')
- data = array(list(map(float,data)))
+ data = np.array(list(map(float,data)))
ydata = (data - parameters['YOF']) * parameters['YMU']\
+ parameters['YZE']
- xdata = arange(len(data))*parameters['XIN'] + parameters['XZE']
+ xdata = np.arange(len(data))*parameters['XIN'] + parameters['XZE']
return list(xdata), list(ydata)
diff --git a/lantz/drivers/tektronix/tds2024b.py b/lantz/drivers/tektronix/tds2024b.py
index e4aba20..0c9dc42 100644
--- a/lantz/drivers/tektronix/tds2024b.py
+++ b/lantz/drivers/tektronix/tds2024b.py
@@ -5,23 +5,24 @@
Implements the drivers to control an oscilloscope.
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
-from numpy import array, arange
+import struct
+
+import numpy as np
from lantz.feat import Feat
from lantz.action import Action
-from lantz.visa import VisaDriver
+from lantz.messagebased import MessageBasedDriver
+
-class TDS2024(VisaDriver):
+class TDS2024(MessageBasedDriver):
"""Tektronix TDS2024 200 MHz 4 Channel Digital Real-Time Oscilloscope
"""
- def __init__(self, port):
- super().__init__(port)
- timeout=10
+ MANUFACTURER_ID = '0x699'
@Action()
def autoconf(self):
@@ -105,14 +106,14 @@ def curv(self):
length = bytecount / 2
data = struct.unpack("{}H".format(length), data[0:2*length])
params = self.acqparams()
- data = array(list(map(float, data)))
+ data = np.array(list(map(float, data)))
yoff = params['YOFF?']
ymu = params['YMU?']
yze = params['YZE?']
xin = params['XIN?']
xze = params['XZE?']
ydata = ( data - yoff) * ymu + yze
- xdata = arange(len(data)) * xin + xze
+ xdata = np.arange(len(data)) * xin + xze
return list(xdata), list(data)
def _measure(self, type, source):
diff --git a/lantz/errors.py b/lantz/errors.py
index d9fe210..6e1a635 100644
--- a/lantz/errors.py
+++ b/lantz/errors.py
@@ -8,7 +8,7 @@
therefore allowing code to catch them via lantz excepts without
breaking specific ones.
- :copyright: 2012 by The Lantz Authors
+ :copyright: 2015 by The Lantz Authors
:license: BSD, see LICENSE for more details.
"""
@@ -21,3 +21,5 @@ class LantzTimeoutError(Exception):
class InstrumentError(Exception):
pass
+class NotSupportedError(Exception):
+ pass
diff --git a/lantz/feat.py b/lantz/feat.py
index b735957..46f3c33 100644
--- a/lantz/feat.py
+++ b/lantz/feat.py
@@ -6,7 +6,7 @@
Implements Feat and DictFeat property-like classes with data handling,
logging, timing, cache and notification.
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
@@ -90,6 +90,7 @@ class Feat(object):
"""
+ __original_doc__ = ''
def __init__(self, fget=MISSING, fset=None, doc=None, *,
values=None, units=None, limits=None, procs=None,
diff --git a/lantz/foreign.py b/lantz/foreign.py
index f004a84..28b094c 100644
--- a/lantz/foreign.py
+++ b/lantz/foreign.py
@@ -5,7 +5,7 @@
Implements classes and methods to interface to foreign functions.
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
@@ -70,7 +70,7 @@ def __get_func(self, name):
if self.prefix:
try:
return getattr(self.internal, self.prefix + name)
- except Exception:
+ except:
pass
try:
diff --git a/lantz/hub.py b/lantz/hub.py
deleted file mode 100644
index ed36e6f..0000000
--- a/lantz/hub.py
+++ /dev/null
@@ -1,31 +0,0 @@
-__author__ = 'grecco'
-
-
-
-
-class Hub(object):
-
-
- def __init__(self):
-
- #: name to instantiated device driver or Proxy
- self.devices = {}
-
-
- def add_device(self, name, device_class, args, kwargs, depends_on=None, run_on=''):
- """
-
- :param name:
- :param device_class:
- :param args:
- :param kwargs:
- :param depends_on:
- :param run_on: 'current' or 'thread' or 'process' or remote address
- :return:
- """
-
- def initialize_devices(self):
- pass
-
- def finalize_devices(self):
- pass
diff --git a/lantz/log.py b/lantz/log.py
index 3cf9015..aec47e3 100644
--- a/lantz/log.py
+++ b/lantz/log.py
@@ -1,15 +1,14 @@
+# -*- coding: utf-8 -*-
"""
lantz.log
~~~~~~~~~
Implements logging support for Lantz.
- :copyright: 2012 by The Lantz Authors
+ :copyright: 2015 by The Lantz Authors
:license: BSD, see LICENSE for more details.
"""
-import types
-
import pickle
import select
@@ -72,16 +71,6 @@ def get_logger(name, add_NullHandler=True, patch_makeRecord=True):
LOGGER = get_logger('lantz')
-try:
- from colorama import Fore, Back, Style, init as colorama_init
- colorama_init()
- colorama = True
- DEFAULT_FMT = Style.NORMAL + '{asctime} {levelname:8s}' + Style.RESET_ALL + ' {message}'
-except Exception as e:
- LOGGER.info('Log will not be colorized. Could not import colorama: {}', e)
- colorama = False
- DEFAULT_FMT = '{asctime} {levelname:8s} {message}'
-
class ColorizingFormatter(logging.Formatter):
"""Color capable logging formatter.
@@ -100,31 +89,31 @@ class ColorizingFormatter(logging.Formatter):
}
@classmethod
- def add_color_schemes(cls):
+ def add_color_schemes(cls, style, fore, back):
cls.format = cls.color_format
- cls.SCHEME.update(bright={DEBUG: Style.NORMAL,
- INFO: Style.NORMAL,
- WARNING: Style.BRIGHT,
- ERROR: Style.BRIGHT,
- CRITICAL: Style.BRIGHT},
- simple={DEBUG: Fore.BLUE + Style.BRIGHT,
- INFO: Back.WHITE + Fore.BLACK,
- WARNING: Fore.YELLOW + Style.BRIGHT,
- ERROR: Fore.RED + Style.BRIGHT,
- CRITICAL: Back.RED + Fore.WHITE + Style.BRIGHT},
- whitebg={DEBUG: Fore.BLUE + Style.BRIGHT,
- INFO: Back.WHITE + Fore.BLACK,
- WARNING: Fore.YELLOW + Style.BRIGHT,
- ERROR: Fore.RED + Style.BRIGHT,
- CRITICAL: Back.RED + Fore.WHITE + Style.BRIGHT},
- blackbg={DEBUG: Fore.BLUE + Style.BRIGHT,
- INFO: Fore.GREEN,
- WARNING: Fore.YELLOW + Style.BRIGHT,
- ERROR: Fore.RED + Style.BRIGHT,
- CRITICAL: Back.RED + Fore.WHITE + Style.BRIGHT}
+ cls.SCHEME.update(bright={DEBUG: style.NORMAL,
+ INFO: style.NORMAL,
+ WARNING: style.BRIGHT,
+ ERROR: style.BRIGHT,
+ CRITICAL: style.BRIGHT},
+ simple={DEBUG: fore.BLUE + style.BRIGHT,
+ INFO: back.WHITE + fore.BLACK,
+ WARNING: fore.YELLOW + style.BRIGHT,
+ ERROR: fore.RED + style.BRIGHT,
+ CRITICAL: back.RED + fore.WHITE + style.BRIGHT},
+ whitebg={DEBUG: fore.BLUE + style.BRIGHT,
+ INFO: back.WHITE + fore.BLACK,
+ WARNING: fore.YELLOW + style.BRIGHT,
+ ERROR: fore.RED + style.BRIGHT,
+ CRITICAL: back.RED + fore.WHITE + style.BRIGHT},
+ blackbg={DEBUG: fore.BLUE + style.BRIGHT,
+ INFO: fore.GREEN,
+ WARNING: fore.YELLOW + style.BRIGHT,
+ ERROR: fore.RED + style.BRIGHT,
+ CRITICAL: back.RED + fore.WHITE + style.BRIGHT}
)
- def __init__(self, fmt=DEFAULT_FMT, datefmt='%H:%M:%S', style='%', scheme='bw'):
+ def __init__(self, fmt, datefmt='%H:%M:%S', style='%', scheme='bw'):
super().__init__(fmt, datefmt, style)
self.scheme = scheme
@@ -161,8 +150,20 @@ def color_format(self, record):
return message
-if colorama:
- ColorizingFormatter.add_color_schemes()
+def init_colorama():
+ try:
+ from colorama import Fore, Back, Style, init as colorama_init
+ colorama_init()
+ colorama = True
+ DEFAULT_FMT = Style.NORMAL + '{asctime} {levelname:8s}' + Style.RESET_ALL + ' {message}'
+ ColorizingFormatter.add_color_schemes(Style, Fore, Back)
+ except Exception as e:
+ LOGGER.info('Log will not be colorized. Could not import colorama: {}', e)
+ colorama = False
+ DEFAULT_FMT = '{asctime} {levelname:8s} {message}'
+ return colorama, DEFAULT_FMT
+
+colorama, DEFAULT_FMT = init_colorama()
class BaseServer(object):
@@ -278,13 +279,13 @@ def __init__(self, tcphost, udphost):
def start(self):
self._lock = threading.RLock()
- s = LoggingTCPServer(self.tcp_addr, self.on_record, 0.5)
+ s = LoggingTCPServer(self.tcp_addr, self.on_record, 1)
self.tcp_server = s
self.tcp_thread = t = threading.Thread(target=s.serve_until_stopped)
t.setDaemon(True)
t.start()
- s = LoggingUDPServer(self.udp_addr, self.on_record, 0.5)
+ s = LoggingUDPServer(self.udp_addr, self.on_record, 1)
self.udp_server = s
self.udp_thread = t = threading.Thread(target=s.serve_until_stopped)
t.setDaemon(True)
@@ -329,7 +330,7 @@ def log_to_screen(level=logging.INFO, scheme='blackbg'):
handler.setLevel(level)
if not colorama:
scheme = 'bw'
- handler.setFormatter(ColorizingFormatter(scheme=scheme, style='{'))
+ handler.setFormatter(ColorizingFormatter(fmt=DEFAULT_FMT, scheme=scheme, style='{'))
LOGGER.addHandler(handler)
if LOGGER.getEffectiveLevel() > level:
LOGGER.setLevel(level)
diff --git a/lantz/messagebased.py b/lantz/messagebased.py
new file mode 100644
index 0000000..d29176f
--- /dev/null
+++ b/lantz/messagebased.py
@@ -0,0 +1,388 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.messagebased
+ ~~~~~~~~~~~~~~~~~~
+
+ Implementes base class for message based drivers using PyVISA under the hood.
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from collections import ChainMap
+import types
+
+import visa
+
+from .errors import NotSupportedError
+from .driver import Driver
+from .log import LOGGER
+from .processors import ParseProcessor
+
+
+#: Cache of parsing functions.
+#: :type: dict[str, ParseProcessor]
+_PARSERS_CACHE = {}
+
+#: PyVISA Resource Manager used in Lantz
+#: :type: visa.ResourceManager
+_resource_manager = None
+
+
+def get_resource_manager():
+ """Return the PyVISA Resource Manager, creating an instance if necessary.
+
+ :rtype: visa.ResourceManager
+ """
+ global _resource_manager
+ if _resource_manager is None:
+ _resource_manager = visa.ResourceManager()
+ return _resource_manager
+
+
+class MessageBasedDriver(Driver):
+ """Base class for message based drivers using PyVISA as underlying library.
+
+ Notice that PyVISA can communicate using different backends. For example:
+ - @ni: Using NI-VISA for communication. Backend bundled with PyVISA.
+ - @py: Using PySerial, PyUSB and linux-gpib for communication. Available with PyVISA-py package.
+ - @sim: Simulated devices. Available with PyVISA-sim package.
+ """
+
+ #: Default arguments passed to the Resource constructor on initialize.
+ #: It should be specified in two layers, the first indicating the
+ #: interface type and the second the corresponding arguments.
+ #: The key COMMON is used to indicate keywords for all interfaces.
+ #: For example::
+ #:
+ #: {'ASRL': {'read_termination': '\n',
+ #: 'baud_rate': 9600},
+ #: 'USB': {'read_termination': \r'},
+ #: 'COMMON': {'write_termination': '\n'}
+ #: }
+ #:
+ #: :type: dict[str, dict[str, str]]
+ DEFAULTS = None
+
+ #: The identification number of the manufacturer as hex code.
+ #: :type: str | None
+ MANUFACTURER_ID = None
+
+ #: The code number of the model as hex code.
+ #: Can provide a tuple/list to indicate multiple models.
+ #: :type: str | list | tuple | None
+ MODEL_CODE = None
+
+ #: Stores a reference to a PyVISA ResourceManager.
+ #: :type: visa.ResourceManager
+ __resource_manager = None
+
+ @classmethod
+ def _get_defaults_kwargs(cls, instrument_type, resource_type, **user_kwargs):
+ """Compute the default keyword arguments combining:
+ - user provided keyword arguments.
+ - (instrument_type, resource_type) keyword arguments.
+ - instrument_type keyword arguments.
+ - resource_type keyword arguments.
+ - common keyword arguments.
+
+ (the first ones have precedence)
+
+ :param instrument_type: ASRL, USB, TCPIP, GPIB
+ :type instrument_type: str
+ :param resource_type: INSTR, SOCKET, RAW
+ :type resource_type: str
+
+ :rtype: dict
+ """
+
+ if cls.DEFAULTS:
+
+ maps = [user_kwargs] if user_kwargs else []
+
+ for key in ((instrument_type, resource_type), instrument_type, resource_type, 'COMMON'):
+ if key not in cls.DEFAULTS:
+ continue
+ value = cls.DEFAULTS[key]
+ if value is None:
+ raise NotSupportedError('An %s instrument is not supported by the driver %s',
+ key, cls.__name__)
+ if value:
+ maps.append(value)
+
+ return dict(ChainMap(*maps))
+ else:
+ return user_kwargs
+
+ @classmethod
+ def _via_usb(cls, resource_type='INSTR', serial_number=None, manufacturer_id=None,
+ model_code=None, name=None, board=0, **kwargs):
+ """Return a Driver with an underlying USB resource.
+
+ A connected USBTMC instrument with the specified serial_number, manufacturer_id,
+ and model_code is returned. If any of these is missing, the first USBTMC driver
+ matching any of the provided values is returned.
+
+ To specify the manufacturer id and/or the model code override the following class attributes::
+
+ class RigolDS1052E(MessageBasedDriver):
+
+ MANUFACTURER_ID = '0x1AB1'
+ MODEL_CODE = '0x0588'
+
+ :param serial_number: The serial number of the instrument.
+ :param manufacturer_id: The unique identification number of the manufacturer.
+ :param model_code: The unique identification number of the product.
+ :param name: Unique name given within Lantz to the instrument for logging purposes.
+ Defaults to one generated based on the class name if not provided.
+ :param board: USB Board to use
+ :param kwargs: keyword arguments passed to the Resource constructor on initialize.
+
+ :rtype: MessageBasedDriver
+ """
+
+ manufacturer_id = manufacturer_id or cls.MANUFACTURER_ID
+ model_code = model_code or cls.MODEL_CODE
+
+ if isinstance(model_code, (list, tuple)):
+ _models = model_code
+ model_code = '?*'
+ else:
+ _models = None
+
+ query = 'USB%d::%s::%s::%s::%s' % (board, manufacturer_id or '?*',
+ model_code or '?*',
+ serial_number or '?*',
+ resource_type)
+
+ rm = get_resource_manager()
+ try:
+ resource_names = rm.list_resources(query)
+ except:
+ raise ValueError('No USBTMC devices found for %s' % query)
+
+ if _models:
+ # There are more than 1 model compatible with
+ resource_names = [r for r in resource_names
+ if r.split('::')[2] in _models]
+
+ if not resource_names:
+ raise ValueError('No USBTMC devices found for %s '
+ 'with model in %s' % (query, _models))
+
+ if len(resource_names) > 1:
+ raise ValueError('%d USBTMC devices found for %s. '
+ 'Please specify the serial number' % (len(resource_names), query))
+
+ return cls(resource_names[0], name, **kwargs)
+
+
+ @classmethod
+ def via_usb(cls, serial_number=None, manufacturer_id=None,
+ model_code=None, name=None, board=0, **kwargs):
+ """Return a Driver with an underlying USB Instrument resource.
+
+ A connected USBTMC instrument with the specified serial_number, manufacturer_id,
+ and model_code is returned. If any of these is missing, the first USBTMC driver
+ matching any of the provided values is returned.
+
+ To specify the manufacturer id and/or the model code override the following class attributes::
+
+ class RigolDS1052E(MessageBasedDriver):
+
+ MANUFACTURER_ID = '0x1AB1'
+ MODEL_CODE = '0x0588'
+
+
+ :param serial_number: The serial number of the instrument.
+ :param manufacturer_id: The unique identification number of the manufacturer.
+ :param model_code: The unique identification number of the product.
+ :param name: Unique name given within Lantz to the instrument for logging purposes.
+ Defaults to one generated based on the class name if not provided.
+ :param board: USB Board to use
+ :param kwargs: keyword arguments passed to the Resource constructor on initialize.
+
+ :rtype: MessageBasedDriver
+ """
+
+ return cls._via_usb('INSTR', serial_number, manufacturer_id, model_code, name, board, **kwargs)
+
+
+ @classmethod
+ def via_usb_raw(cls, serial_number=None, manufacturer_id=None, model_code=None, name=None, board=0, **kwargs):
+ """Return a Driver with an underlying USB RAW resource.
+
+ :param serial_number: The serial number of the instrument.
+ :param manufacturer_id: The unique identification number of the manufacturer.
+ :param model_code: The unique identification number of the product.
+ :param name: Unique name given within Lantz to the instrument for logging purposes.
+ Defaults to one generated based on the class name if not provided.
+ :param board: USB Board to use
+ :param kwargs: keyword arguments passed to the Resource constructor on initialize.
+
+ :rtype: MessageBasedDriver
+ """
+
+ return cls._via_usb('RAW', serial_number, manufacturer_id, model_code, name, board, **kwargs)
+
+ @classmethod
+ def via_serial(cls, port, name=None, **kwargs):
+ """Return a Driver with an underlying ASRL (Serial) Instrument resource.
+
+ :param port: The serial port to which the instrument is connected.
+ :param name: Unique name given within Lantz to the instrument for logging purposes.
+ Defaults to one generated based on the class name if not provided.
+ :param kwargs: keyword arguments passed to the Resource constructor on initialize.
+
+ :rtype: MessageBasedDriver
+ """
+ resource_name = 'ASRL%s::INSTR' % port
+ return cls(resource_name, name, **kwargs)
+
+ @classmethod
+ def via_tcpip(cls, hostname, port, name=None, **kwargs):
+ """Return a Driver with an underlying TCP Instrument resource.
+
+
+ :param hostname: The ip address or hostname of the instrument.
+ :param port: the port of the instrument.
+ :param name: Unique name given within Lantz to the instrument for logging purposes.
+ Defaults to one generated based on the class name if not provided.
+ :param kwargs: keyword arguments passed to the Resource constructor on initialize.
+
+ :rtype: MessageBasedDriver
+ """
+ resource_name = 'TCPIP::%s::%s::INSTR' % (hostname, port)
+ return cls(resource_name, name, **kwargs)
+
+ @classmethod
+ def via_tcpip_socket(cls, hostname, port, name=None, **kwargs):
+ """Return a Driver with an underlying TCP Socket resource.
+
+ :param hostname: The ip address or hostname of the instrument.
+ :param port: the port of the instrument.
+ :param name: Unique name given within Lantz to the instrument for logging purposes.
+ Defaults to one generated based on the class name if not provided.
+ :param kwargs: keyword arguments passed to the Resource constructor on initialize.
+
+ :rtype: MessageBasedDriver
+ """
+ resource_name = 'TCPIP::%s::%s::SOCKET' % (hostname, port)
+ return cls(resource_name, name, **kwargs)
+
+ @classmethod
+ def via_gpib(cls, address, name=None, **kwargs):
+ """Return a Driver with an underlying GPIB Instrument resource.
+
+ :param address: The gpib address of the instrument.
+ :param name: Unique name given within Lantz to the instrument for logging purposes.
+ Defaults to one generated based on the class name if not provided.
+ :param kwargs: keyword arguments passed to the Resource constructor on initialize.
+
+ :rtype: MessageBasedDriver
+ """
+ resource_name = 'GPIB::%s::INSTR' % address
+ return cls(resource_name, name, **kwargs)
+
+ def __init__(self, resource_name, name=None, **kwargs):
+ """
+ :param resource_name: The resource name
+ :type resource_name: str
+ :params name: easy to remember identifier given to the instance for logging
+ purposes.
+ :param kwargs: keyword arguments passed to the resource during initialization.
+ """
+
+ self.__resource_manager = get_resource_manager()
+ try:
+ resource_info = self.__resource_manager.resource_info(resource_name)
+ except visa.VisaIOError:
+ raise ValueError('The resource name is invalid')
+
+ super().__init__(name=name)
+
+ # This is to avoid accidental modifications of the class value by an instance.
+ self.DEFAULTS = types.MappingProxyType(self.DEFAULTS or {})
+
+ #: The resource name
+ #: :type: str
+ self.resource_name = resource_name
+
+ #: keyword arguments passed to the resource during initialization.
+ #: :type: dict
+ self.resource_kwargs = self._get_defaults_kwargs(resource_info.interface_type.name.upper(),
+ resource_info.resource_class,
+ **kwargs)
+
+ # The resource will be created when the driver is initialized.
+ #: :type: pyvisa.resources.MessageBasedResource
+ self.resource = None
+
+ self.log_debug('Using MessageBasedDriver for {}', self.resource_name)
+
+ def initialize(self):
+ super().initialize()
+ self.log_debug('Opening resource {}', self.resource_name)
+ self.log_debug('Setting {}', list(self.resource_kwargs.items()))
+ self.resource = get_resource_manager().open_resource(self.resource_name, **self.resource_kwargs)
+
+ def finalize(self):
+ self.log_debug('Closing resource {}', self.resource_name)
+ self.resource.close()
+ super().finalize()
+
+ def query(self, command, *, send_args=(None, None), recv_args=(None, None)):
+ """Send query to the instrument and return the answer
+
+ :param command: command to be sent to the instrument
+ :type command: string
+
+ :param send_args: (termination, encoding) to override class defaults
+ :param recv_args: (termination, encoding) to override class defaults
+ """
+
+ self.write(command, *send_args)
+ return self.read(*recv_args)
+
+ def parse_query(self, command, *,
+ send_args=(None, None), recv_args=(None, None),
+ format=None):
+ """Send query to the instrument, parse the output using format
+ and return the answer.
+
+ .. seealso:: TextualMixin.query and stringparser
+ """
+ ans = self.query(command, send_args=send_args, recv_args=recv_args)
+ if format:
+ parser = _PARSERS_CACHE.setdefault(format, ParseProcessor(format))
+ ans = parser(ans)
+ return ans
+
+ def write(self, command, termination=None, encoding=None):
+ """Send command to the instrument.
+
+ :param command: command to be sent to the instrument.
+ :type command: string.
+
+ :param termination: termination character to override class defined
+ default.
+ :param encoding: encoding to transform string to bytes to override class
+ defined default.
+
+ :return: number of bytes sent.
+
+ """
+ self.log_debug('Writing {!r}', command)
+ return self.resource.write(command, termination, encoding)
+
+ def read(self, termination=None, encoding=None):
+ """Receive string from instrument.
+
+ :param termination: termination character (overrides class default)
+ :type termination: str
+ :param encoding: encoding to transform bytes to string (overrides class default)
+ :return: string encoded from received bytes
+ """
+ ret = self.resource.read(termination, encoding)
+ self.log_debug('Read {!r}', ret)
+ return ret
diff --git a/lantz/processors.py b/lantz/processors.py
index 3faa185..65ee831 100644
--- a/lantz/processors.py
+++ b/lantz/processors.py
@@ -3,16 +3,12 @@
lantz.processors
~~~~~~~~~~~~~~~~
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
-import inspect
-import logging
import warnings
-from functools import wraps, partial
-
from . import Q_
from .log import LOGGER as _LOG
from stringparser import Parser
@@ -391,7 +387,7 @@ def get_mapping(container):
>>> getter = get_mapping({'A': 42, 'B': 43})
>>> getter('A')
42
- >>> checker(0)
+ >>> getter(0)
Traceback (most recent call last):
...
ValueError: 0 not in ('A', 'B')
diff --git a/lantz/simulators/__init__.py b/lantz/simulators/__init__.py
index 33d2da6..cfc48bd 100644
--- a/lantz/simulators/__init__.py
+++ b/lantz/simulators/__init__.py
@@ -5,7 +5,7 @@
Instrument simulators for testing.
- :copyright: 2012 by The Lantz Authors
+ :copyright: 2015 by The Lantz Authors
:license: BSD, see LICENSE for more details.
"""
@@ -13,3 +13,15 @@
SIMULATORS = {}
from . import fungen, experiment, voltmeter
+
+
+def main(args=None):
+ """Run simulators.
+ """
+ import argparse
+
+ parser = argparse.ArgumentParser(description='Run Lantz simulators.')
+ parser.add_argument('simulator', choices=list(SIMULATORS.keys()))
+ args, pending = parser.parse_known_args(args)
+ print('Dispatching ' + args.simulator)
+ SIMULATORS[args.simulator](pending)
diff --git a/lantz/simulators/experiment.py b/lantz/simulators/experiment.py
index ae75534..dfe4970 100644
--- a/lantz/simulators/experiment.py
+++ b/lantz/simulators/experiment.py
@@ -5,7 +5,7 @@
An experiment connecting an actuator and a sensor.
- :copyright: 2012 by The Lantz Authors
+ :copyright: 2015 by The Lantz Authors
:license: BSD, see LICENSE for more details.
"""
diff --git a/lantz/simulators/fungen.py b/lantz/simulators/fungen.py
index fc20c35..d2fc750 100644
--- a/lantz/simulators/fungen.py
+++ b/lantz/simulators/fungen.py
@@ -6,7 +6,7 @@
A simulated function generator.
See specification in the Lantz documentation.
- :copyright: 2012 by The Lantz Authors
+ :copyright: 2015 by The Lantz Authors
:license: BSD, see LICENSE for more details.
"""
@@ -16,7 +16,7 @@
from . import SIMULATORS
-from .instrument import SimError, InstrumentHandler, main_tcp, main_serial
+from .instrument import SimError, InstrumentHandler, main_tcp, main_serial, main_generic
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(message)s',
datefmt='%Y-%d-%m %H:%M:%S')
@@ -84,33 +84,9 @@ def generator_output(self):
def main(args=None):
- import argparse
- parser = argparse.ArgumentParser(description='Function Generator Simulator')
- subparsers = parser.add_subparsers()
-
- subparser = subparsers.add_parser('serial')
- subparser.add_argument('-p', '--port', type=str, default='1',
- help='Serial port')
- subparser.set_defaults(func=main_serial)
-
- subparser = subparsers.add_parser('tcp')
- subparser.add_argument('-H', '--host', type=str, default='localhost',
- help='TCP hostname')
- subparser.add_argument('-p', '--port', type=int, default=5678,
- help='TCP port')
- subparser.set_defaults(func=main_tcp)
-
- instrument = SimFunctionGenerator()
- args = parser.parse_args(args)
- server = args.func(instrument, args)
-
- logging.info('interrupt the program with Ctrl-C')
- try:
- server.serve_forever()
- except KeyboardInterrupt:
- logging.info('Ending')
- finally:
- server.shutdown()
+
+ return main_generic(args, SimFunctionGenerator)
+
SIMULATORS['fungen'] = main
diff --git a/lantz/simulators/instrument.py b/lantz/simulators/instrument.py
index c46f390..43f871d 100644
--- a/lantz/simulators/instrument.py
+++ b/lantz/simulators/instrument.py
@@ -6,17 +6,22 @@
An simple framework to wrap a simulated instrument into
a Serial or TCP receiver.
- :copyright: 2012 by The Lantz Authors
+ :copyright: 2015 by The Lantz Authors
:license: BSD, see LICENSE for more details.
"""
import logging
import socket
import socketserver
+
try:
- from lantz.serial import SerialDriver
+ from lantz.drivers.legacy.serial import SerialDriver
except ImportError:
- pass
+ class SerialDriver:
+
+ def __init__(self, *args, **kwargs):
+ raise Exception('Please install PySerial to use the Serial Simulator.')
+
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(message)s',
datefmt='%Y-%d-%m %H:%M:%S')
@@ -146,3 +151,37 @@ def main_tcp(instrument, args):
server = socketserver.TCPServer((args.host, args.port), Handler)
server.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
return server
+
+
+def main_generic(args, instrument, instrument_args=(), instrument_kwargs=None):
+ import argparse
+ parser = argparse.ArgumentParser()
+
+ parser.add_argument('connection', choices=['serial', 'tcp'])
+ args, pending = parser.parse_known_args(args)
+
+ if args.connection == 'serial':
+ subparser = argparse.ArgumentParser()
+ subparser.add_argument('-p', '--port', type=str, default='1',
+ help='Serial port')
+ func = main_serial
+
+ else:
+ subparser = argparse.ArgumentParser()
+ subparser.add_argument('-H', '--host', type=str, default='localhost',
+ help='TCP hostname')
+ subparser.add_argument('-p', '--port', type=int, default=5678,
+ help='TCP port')
+ func = main_tcp
+
+ args2 = subparser.parse_args(pending)
+
+ server = func(instrument(*instrument_args, **(instrument_kwargs or {})), args2)
+
+ logging.info('interrupt the program with Ctrl-C')
+ try:
+ server.serve_forever()
+ except KeyboardInterrupt:
+ logging.info('Ending')
+ finally:
+ server.shutdown()
diff --git a/lantz/simulators/voltmeter.py b/lantz/simulators/voltmeter.py
index 58acebc..cbf9721 100644
--- a/lantz/simulators/voltmeter.py
+++ b/lantz/simulators/voltmeter.py
@@ -5,14 +5,14 @@
A simulated voltmeter.
- :copyright: 2012 by The Lantz Authors
+ :copyright: 2015 by The Lantz Authors
:license: BSD, see LICENSE for more details.
"""
import time
import logging
-from .instrument import SimError, InstrumentHandler, main_tcp, main_serial
+from .instrument import SimError, InstrumentHandler, main_tcp, main_serial, main_generic
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(message)s',
datefmt='%Y-%d-%m %H:%M:%S')
@@ -55,9 +55,6 @@ def meas(self, ain):
if ain not in (0, 1):
raise SimError
value = self._get_func[ain]()
- print(self.vranges)
- print(self.range)
- print(ain)
max_val = self.vranges[self.range[ain]] * 1.2
if value > max_val or value < -max_val:
value = max_val
@@ -65,37 +62,13 @@ def meas(self, ain):
def main(args=None):
- import argparse
- parser = argparse.ArgumentParser()
- subparsers = parser.add_subparsers()
-
- subparser = subparsers.add_parser('serial')
- subparser.add_argument('-p', '--port', type=str, default='1',
- help='Serial port')
- subparser.set_defaults(func=main_serial)
-
- subparser = subparsers.add_parser('tcp')
- subparser.add_argument('-H', '--host', type=str, default='localhost',
- help='TCP hostname')
- subparser.add_argument('-p', '--port', type=int, default=5679,
- help='TCP port')
- subparser.set_defaults(func=main_tcp)
import random
def measure():
- return random.random * 10 - 5
-
- instrument = SimVoltmeter(measure, measure)
- args = parser.parse_args(args)
- server = args.func(instrument, args)
-
- logging.info('interrupt the program with Ctrl-C')
- try:
- server.serve_forever()
- except KeyboardInterrupt:
- logging.info('Ending')
- finally:
- server.shutdown()
+ return random.random() * 10 - 5
+
+ return main_generic(args, SimVoltmeter, (measure, measure))
+
from . import SIMULATORS
diff --git a/lantz/stats.py b/lantz/stats.py
index ea7afd1..9f3a59e 100644
--- a/lantz/stats.py
+++ b/lantz/stats.py
@@ -5,7 +5,7 @@
Implements an statistical accumulator
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/lantz/testsuite/__init__.py b/lantz/testsuite/__init__.py
index ef6d41c..255f5b8 100644
--- a/lantz/testsuite/__init__.py
+++ b/lantz/testsuite/__init__.py
@@ -1,8 +1,6 @@
# -*- coding: utf-8 -*-
import os
-import sys
-import logging
import unittest
@@ -13,8 +11,18 @@ def testsuite():
def main():
- """Runs the testsuite as command line application."""
+ """Runs the testsuite as command line application.
+ """
try:
unittest.main()
except Exception as e:
print('Error: %s' % e)
+
+
+def run():
+ """Run all tests.
+
+ :return: a :class:`unittest.TestResult` object
+ """
+ test_runner = unittest.TextTestRunner()
+ return test_runner.run(testsuite())
diff --git a/lantz/testsuite/simplelib.dylib b/lantz/testsuite/simplelib.dylib
deleted file mode 100755
index 5fca290..0000000
Binary files a/lantz/testsuite/simplelib.dylib and /dev/null differ
diff --git a/lantz/testsuite/simplelib.h b/lantz/testsuite/simplelib.h
new file mode 100644
index 0000000..96b47e0
--- /dev/null
+++ b/lantz/testsuite/simplelib.h
@@ -0,0 +1,15 @@
+#include
+#include
+
+int returni10();
+float returnf10();
+double returnd10();
+int sumi13(int x);
+int use_atoi(const char * here, const int maxlen);
+int write_in_charp(char * here, const int maxlen);
+int double_param (double *p);
+int double_array_param(double * pdValarray);
+int double_array_length_param(double * pdValarray, const int maxlen);
+double sum_double_array(double * pdValarray);
+double sum_double_array_length(double * pdValarray, const int maxlen);
+
diff --git a/lantz/testsuite/test_action.py b/lantz/testsuite/test_action.py
index d99047f..e461f02 100644
--- a/lantz/testsuite/test_action.py
+++ b/lantz/testsuite/test_action.py
@@ -1,8 +1,8 @@
+# -*- coding: utf-8 -*-
+
import unittest
-from time import sleep
+from lantz import Driver, Action, Q_
-from lantz import Driver, Feat, DictFeat, Action, Q_
-from lantz.feat import MISSING
class aDriver(Driver):
diff --git a/lantz/testsuite/test_dictfeat.py b/lantz/testsuite/test_dictfeat.py
index 67ef8de..17ab163 100644
--- a/lantz/testsuite/test_dictfeat.py
+++ b/lantz/testsuite/test_dictfeat.py
@@ -1,11 +1,13 @@
+# -*- coding: utf-8 -*-
+
+
import logging
import unittest
from lantz import Driver, DictFeat, Q_
-
from lantz.log import get_logger
-from lantz.feat import MISSING
+
class MemHandler(logging.Handler):
@@ -57,6 +59,7 @@ def eggs(self_, key):
def test_writeonly(self):
+ # noinspection PyPropertyDefinition
class Spam(Driver):
_eggs = {'answer': 42}
@@ -87,6 +90,7 @@ def eggs2(self_, key, value):
def test_readwrite(self):
+ # noinspection PyPropertyDefinition
class Spam(Driver):
_eggs = {'answer': 42}
diff --git a/lantz/testsuite/test_driver.py b/lantz/testsuite/test_driver.py
index 9d90820..32cd937 100644
--- a/lantz/testsuite/test_driver.py
+++ b/lantz/testsuite/test_driver.py
@@ -1,8 +1,9 @@
+# -*- coding: utf-8 -*-
+
import unittest
from time import sleep
-from lantz import Driver, Feat, DictFeat, Action, Q_
-from lantz.feat import MISSING
+from lantz import Driver, Feat, Action, Q_
from lantz.driver import Self
SLEEP = .1
diff --git a/lantz/testsuite/test_feat.py b/lantz/testsuite/test_feat.py
index cf09b67..752a68b 100644
--- a/lantz/testsuite/test_feat.py
+++ b/lantz/testsuite/test_feat.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
import time
import logging
@@ -88,6 +89,7 @@ def eggs2(self_, value):
def test_readwrite(self):
+ # noinspection PyPropertyDefinition
class Spam(Driver):
_eggs = 8
@@ -172,7 +174,7 @@ class Spam(Driver):
_eggs = 8
- @Feat(values=set((1, 2.2, 10)), units='second')
+ @Feat(values={1, 2.2, 10}, units='second')
def eggs(self_):
return self_._eggs
@@ -379,6 +381,7 @@ def eggs(self_, values):
def test_of_instance(self):
+ # noinspection PyPropertyDefinition
class Spam(Driver):
def __init__(self_):
diff --git a/lantz/testsuite/test_foreign.py b/lantz/testsuite/test_foreign.py
index a83c633..3957d1a 100644
--- a/lantz/testsuite/test_foreign.py
+++ b/lantz/testsuite/test_foreign.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
import ctypes
import unittest
diff --git a/lantz/testsuite/test_processors.py b/lantz/testsuite/test_processors.py
index 9b45870..f5db133 100644
--- a/lantz/testsuite/test_processors.py
+++ b/lantz/testsuite/test_processors.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
import unittest
import doctest
diff --git a/lantz/testsuite/test_stats.py b/lantz/testsuite/test_stats.py
index 5a5d984..cee6a43 100644
--- a/lantz/testsuite/test_stats.py
+++ b/lantz/testsuite/test_stats.py
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+
import unittest
import numpy as np
diff --git a/lantz/ui/__init__.py b/lantz/ui/__init__.py
index d3bf99e..b68cd30 100644
--- a/lantz/ui/__init__.py
+++ b/lantz/ui/__init__.py
@@ -5,7 +5,7 @@
Implements UI functionality for lantz using Qt.
- :copyright: 2013 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/lantz/ui/app.py b/lantz/ui/app.py
index 38e89d7..50ce75d 100644
--- a/lantz/ui/app.py
+++ b/lantz/ui/app.py
@@ -5,17 +5,18 @@
Implements base class for graphical applications.
- :copyright: 2013 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
import os
import sys
import inspect
+import collections
from ..log import get_logger
from ..driver import Driver, initialize_many, finalize_many
-from ..ui.widgets import connect_setup, DriverTestWidget, SetupTestWidget
+from ..ui.widgets import connect_setup, DriverTestWidget, SetupTestWidget, connect_driver
from ..utils.qt import QtCore, QtGui, SuperQObject, MetaQObject
logger = get_logger('lantz.ui.app', False)
@@ -23,6 +24,9 @@
class InstrumentSlot(object):
+ def __str__(self):
+ return ''
+
def initialize(self):
logger.warning('The Instrument slot {} has not been assigned '
'to an actual instrument', self._slot_name )
@@ -31,7 +35,10 @@ def finalize(self):
pass
-class _AppType(MetaQObject):
+Front2Back = collections.namedtuple('Front2Back', 'frontend_class backend_name settings')
+
+
+class _BackendType(MetaQObject):
def create_instrument_property(cls, key):
def getter(self):
@@ -42,11 +49,11 @@ def setter(self, instrument):
return property(getter, setter)
- def create_app_property(cls, key):
+ def create_backend_property(cls, key):
def getter(self):
- return self.apps[key]
- def setter(self, app):
- self.apps[key] = app
+ return self.backends[key]
+ def setter(self, backend):
+ self.backends[key] = backend
return property(getter, setter)
@@ -55,27 +62,58 @@ def __init__(cls, classname, bases, class_dict):
super().__init__(classname, bases, class_dict)
cls.instruments = dict()
- cls.apps = dict()
+ cls.backends = dict()
for key, value in class_dict.items():
if isinstance(value, InstrumentSlot):
cls.instruments[key] = value
cls.instruments[key]._slot_name = key
setattr(cls, key, cls.create_instrument_property(key))
- logger.debug('{}: creating instrument named {} of type {}'.format(cls, key, value))
+ logger.debug('{}, adding instrument named {} of type {}'.format(cls, key, value))
elif value is InstrumentSlot:
value = value()
cls.instruments[key] = value
cls.instruments[key]._slot_name = key
setattr(cls, key, cls.create_instrument_property(key))
- logger.debug('{} creating instrument named {} of type {}'.format(cls, key, value))
- elif hasattr(value, 'apps') and hasattr(value, 'instruments'):
- cls.apps[key] = value
- setattr(cls, key, cls.create_app_property(key))
- logger.debug('{}: creating app named {} of type {}'.format(cls, key, value))
+ logger.debug('In {}, adding instrument named {} of type {}'.format(cls, key, value))
+ elif hasattr(value, 'backends') and hasattr(value, 'instruments'):
+ cls.backends[key] = value
+ setattr(cls, key, cls.create_backend_property(key))
+ logger.debug('In {}, adding backend named {} of type {}'.format(cls, key, value))
+
+ def __str__(cls):
+ return cls.__name__
+
+
+class _FrontendType(MetaQObject):
+
+ def create_frontend_property(cls, key):
+ def getter(self):
+ return self.frontends[key]
+ def setter(self, frontend):
+ self.frontends[key] = frontend
+
+ return property(getter, setter)
+
+ def __init__(cls, classname, bases, class_dict):
+ super().__init__(classname, bases, class_dict)
+
+ cls.frontends = dict()
+
+ for key, value in class_dict.items():
+ if isinstance(value, Front2Back) or hasattr(value, 'frontends'):
+ cls.frontends[key] = value
+ setattr(cls, key, cls.create_frontend_property(key))
+ logger.debug('{}, adding frontend named {} of type {}'.format(cls, key, value))
+
+ def __str__(cls):
+ return cls.__name__
-class Frontend(QtGui.QMainWindow):
+
+class Frontend(QtGui.QMainWindow, metaclass=_FrontendType):
+
+ frontends = {}
# a declarative way to indicate the user interface file to use.
gui = None
@@ -89,15 +127,31 @@ def __init__(self, parent=None, backend=None):
self._backend = None
if self.gui:
- filename = os.path.dirname(inspect.getfile(self.__class__))
- filename = os.path.join(filename, self.gui)
- logger.debug('{}: loading gui file {}'.format(self, filename))
- self.widget = QtGui.loadUi(filename)
- self.setCentralWidget(self.widget)
+ for cls in self.__class__.__mro__:
+ filename = os.path.dirname(inspect.getfile(cls))
+ filename = os.path.join(filename, self.gui)
+ if os.path.exists(filename):
+ logger.debug('{}: loading gui file {}'.format(self, filename))
+ self.widget = QtGui.loadUi(filename)
+ self.setCentralWidget(self.widget)
+ break
+ else:
+ raise ValueError('{}: loading gui file {}'.format(self, self.gui))
+
+ for name, frontend in self.frontends.items():
+ if isinstance(frontend, Front2Back):
+ widget = frontend.frontend_class(backend=getattr(backend, frontend.backend_name))
+ else:
+ widget = frontend(backend=backend)
+ widget.setParent(self)
+ setattr(self, name, widget)
self.setupUi()
self.backend = backend
+ def __str__(self):
+ return self.__class__.__name__
+
def setupUi(self):
pass
@@ -123,34 +177,50 @@ def backend(self, backend):
self.connect_backend()
+ @classmethod
+ def using(cls, backend_name=None, settings=None):
+ return Front2Back(cls, backend_name, settings)
-class Backend(SuperQObject, metaclass=_AppType):
- apps = {}
+class Backend(SuperQObject, metaclass=_BackendType):
+
+ backends = {}
instruments = {}
- def __init__(self, parent=None, **instruments):
+ def __init__(self, parent=None, **instruments_and_backends):
super().__init__(parent)
- for name, app in self.apps.items():
- d = {key: inst for key, inst in instruments.items()
+
+ for name, app in self.backends.items():
+ if not name in instruments_and_backends:
+ continue
+
+ d = {key: inst for key, inst in instruments_and_backends.items()
if key in app.instruments.keys()}
logger.debug('{}: creating sub-backend named {} with {}'.format(self, name, app))
+ if name in instruments_and_backends:
+ d.update(instruments_and_backends[name])
setattr(self, name, app(parent=self, **d))
- for name, inst in instruments.items():
+ for name in self.instruments.keys():
+ if not name in instruments_and_backends:
+ continue
+
+ inst = instruments_and_backends[name]
logger.debug('{}: relating instrument named {} with {}'.format(self, name, inst))
setattr(self, name, inst)
inst.setParent(self)
# TODO: Check for all instruments exists
+ def __str__(self):
+ return self.__class__.__name__
+
def initialize(self, register_finalizer=False):
initialize_many(self.instruments.values(), register_finalizer=register_finalizer)
def finalize(self):
finalize_many(self.instruments.values())
-
def __enter__(self):
self.initialize(register_finalizer=False)
return self
@@ -186,7 +256,7 @@ def start_test_app(target, width=500, qapp_or_args=None):
:param target: a driver object or a collection of drivers.
:param width: to be used as minimum width of the window.
- :param args: arguments to be passed to QApplication.
+ :param qapp_or_args: arguments to be passed to QApplication.
"""
if isinstance(qapp_or_args, QtGui.QApplication):
@@ -204,3 +274,30 @@ def start_test_app(target, width=500, qapp_or_args=None):
if sys.platform.startswith('darwin'):
main.raise_()
qapp.exec_()
+
+
+def start_gui(ui_filename, drivers, qapp_or_args=None):
+ """Start a single window application with a form generated from
+ a designer file.
+
+ :param ui_filename: the full path of a file generated with QtDesigner.
+ :param drivers: a driver object or a collection of drivers.
+ :param qapp_or_args: arguments to be passed to QApplication.
+ """
+
+ if isinstance(qapp_or_args, QtGui.QApplication):
+ qapp = qapp_or_args
+ else:
+ qapp = QtGui.QApplication(qapp_or_args or [''])
+
+ main = QtGui.loadUi(ui_filename)
+
+ if isinstance(drivers, Driver):
+ connect_driver(main, drivers)
+ else:
+ connect_setup(main, drivers)
+
+ main.show()
+ if sys.platform.startswith('darwin'):
+ main.raise_()
+ qapp.exec_()
diff --git a/lantz/ui/blocks/__init__.py b/lantz/ui/blocks/__init__.py
index 88bb049..1eb0e3b 100644
--- a/lantz/ui/blocks/__init__.py
+++ b/lantz/ui/blocks/__init__.py
@@ -3,10 +3,14 @@
lantz.ui.blocks
~~~~~~~~~~~~~~~
- Just some fake drivers to show how the backend, frontend works.
+ Building blocks for rich applications.
- :copyright: 2014 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from .loop import Loop, LoopUi
+from .scan import Scan, ScanUi
+from .featscan import FeatScan, FeatScanUi
+from .chart import ChartUi
+from .layouts import HorizonalUi, VerticalUi
diff --git a/lantz/ui/blocks/chart.py b/lantz/ui/blocks/chart.py
new file mode 100644
index 0000000..a65e4f6
--- /dev/null
+++ b/lantz/ui/blocks/chart.py
@@ -0,0 +1,133 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.ui.chart
+ ~~~~~~~~~~~~~~
+
+ A chart frontend.
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from lantz import Q_
+
+# Import Qt modules from lantz (pyside and pyqt compatible)
+from lantz.utils.qt import QtGui
+
+# These classes simplify application development
+from lantz.ui.app import Frontend
+
+
+class ChartUi(Frontend):
+ """A frontend with a x,y plot (powered by pyqtgraph)
+ """
+
+ # a declarative way to indicate the user interface file to use.
+ # The file must be located next to the python file where this class
+ # is defined.
+ gui = 'placeholder.ui'
+
+ # connect widgets to instruments using connect_setup automatically.
+ auto_connect = True
+
+ _axis = {'x': 'bottom',
+ 'y': 'left'}
+
+ def __init__(self, xlabel='', xunits='', ylabel='', yunits='', *args, **kwargs):
+ self._labels = {'x': xlabel, 'y': ylabel}
+ self._units = {'x': xunits, 'y': yunits}
+
+ self._x = []
+ self._y = []
+
+ super().__init__(*args, **kwargs)
+
+ @property
+ def xlabel(self):
+ """x-axis label.
+ """
+ return self._labels['x']
+
+ @xlabel.setter
+ def xlabel(self, value):
+ self._labels['x'] = value
+ self._relabel('x')
+
+ @property
+ def ylabel(self):
+ """y-axis label.
+ """
+ return self._labels['y']
+
+ @ylabel.setter
+ def ylabel(self, value):
+ self._labels['y'] = value
+ self._relabel('y')
+
+ @property
+ def xunits(self):
+ """x-axis units as a string.
+ """
+ return self._units['x']
+
+ @xunits.setter
+ def xunits(self, value):
+ self._units['x'] = value
+ self._relabel('x')
+
+ @property
+ def yunits(self):
+ """y-axis units as a string.
+ """
+ return self._units['y']
+
+ @yunits.setter
+ def yunits(self, value):
+ self._units['y'] = value
+ self._relabel('y')
+
+ def _relabel(self, axis):
+ """Builds the actual label using the label and units for a given axis.
+ Also builds a quantity to be used to normalized the data.
+
+ :param axis: 'x' or 'y'
+ """
+ label = self._labels[axis]
+ units = self._units[axis]
+ if label and units:
+ label = '%s [%s]' % (label, units)
+ elif units:
+ label = '[%s]' % units
+
+ self.pw.setLabel(self._axis[axis], label)
+
+ if units:
+ setattr(self, '_q' + axis, Q_(1, units))
+
+ def setupUi(self):
+ import pyqtgraph as pg
+
+ pg.setConfigOptions(antialias=True)
+ # This method is called after gui has been loaded (referenced in self.widget)
+ # to customize gui building. In this case, we are adding a plot widget.
+ self.pw = pg.PlotWidget()
+
+ self.curve = self.pw.plot(pen='y')
+ layout = QtGui.QVBoxLayout()
+ layout.addWidget(self.pw)
+ self.widget.placeholder.setLayout(layout)
+
+ def plot(self, x, y):
+ """Add a pair of points to the plot.
+ """
+ x = float(x / self._qx)
+ y = float(y / self._qy)
+ self._x.append(x)
+ self._y.append(y)
+ self.curve.setData(self._x, self._y)
+
+ def clear(self, *args):
+ """Clear the plot.
+ """
+ self._x.clear()
+ self._y.clear()
diff --git a/lantz/ui/blocks/featscan.py b/lantz/ui/blocks/featscan.py
new file mode 100644
index 0000000..f82b7d0
--- /dev/null
+++ b/lantz/ui/blocks/featscan.py
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.ui.featscan
+ ~~~~~~~~~~~~~~~~~
+
+ A Feat Scan frontend and Backend. Builds upon Scan.
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from lantz.utils.qt import QtCore
+from lantz.ui.widgets import WidgetMixin
+from lantz.ui.app import start_gui_app, InstrumentSlot
+
+from lantz.ui.blocks import Scan, ScanUi
+
+
+class FeatScan(Scan):
+ """A backend to scan a feat for a given instrument.
+ """
+
+ #: Signal emitted before starting a new iteration
+ #: Parameters: loop counter, step value, overrun
+ iteration = QtCore.Signal(int, int, bool)
+
+ #: Signal emitted when the loop finished.
+ #: The parameter is used to inform if the loop was canceled.
+ loop_done = QtCore.Signal(bool)
+
+ instrument = InstrumentSlot
+
+ #: Name of the scanned feat
+ #: :type: str
+
+ def __init__(self, feat_name, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.feat_name = feat_name
+
+ def _pre_body(self, counter, new_value, overrun):
+ setattr(self.instrument, self.feat_name, new_value)
+
+ @property
+ def feat_units(self):
+ """Units of the scanned feat.
+ """
+ target = self.instrument
+ feat_name = self.feat_name
+ feat = target.feats[feat_name]
+ return str(feat.units)
+
+
+class FeatScanUi(ScanUi):
+ """A Frontend displaying scan parameters with appropriate units.
+ """
+
+ def connect_backend(self):
+ target = self.backend.instrument
+ feat_name = self.backend.feat_name
+
+ feat = target.feats[feat_name]
+
+ def _pimp(widget):
+ WidgetMixin.wrap(widget)
+ widget.bind_feat(feat)
+
+ for name in 'start stop step_size'.split():
+ _pimp(getattr(self.widget, name))
+
+ if feat.limits and len(feat.limits) == 3:
+ self.widget.step_size.setMinimum(feat.limits[2])
+ else:
+ self.widget.step_size.setMinimum(0)
+ super().connect_backend()
+
+
+if __name__ == '__main__':
+ from lantz.drivers.examples import LantzSignalGenerator
+
+ with LantzSignalGenerator('TCPIP::localhost::5678::SOCKET') as inst:
+ app = FeatScan('frequency', fungen=inst)
+ start_gui_app(app, FeatScanUi)
diff --git a/lantz/ui/blocks/layouts.py b/lantz/ui/blocks/layouts.py
new file mode 100644
index 0000000..192f199
--- /dev/null
+++ b/lantz/ui/blocks/layouts.py
@@ -0,0 +1,78 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.ui.layouts
+ ~~~~~~~~~~~~~~~~
+
+ Frontends to automatically locate widgets.
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+
+from lantz.utils.qt import QtGui
+from lantz.ui.app import Frontend
+
+
+class _PanelsUi(Frontend):
+ """The Loop frontend provides a GUI for the Rich Backend
+ """
+
+ gui = 'placeholder.ui'
+
+ auto_connect = False
+
+ #: Tuple with the columns
+ #: Each element can be:
+ #: - Frontend class: will be connected to the default backend.
+ #: - Front2Back(Frontend class, backend name): will be connect to a specific backend.
+ #: - tuple: will be iterated to obtain the rows.
+ parts = ()
+
+ _inner, _outer = None, None
+
+ def _add(self, layout, parts):
+ """Add widgets in parts to layout.
+ """
+ for part_name in parts:
+ part = getattr(self, part_name)
+
+ if isinstance(part, Frontend):
+ layout.addWidget(part)
+
+ elif isinstance(part, tuple):
+ # A tuple found in parts is considered nesting
+ if isinstance(layout, self._inner):
+ sublayout = self._outer()
+ elif isinstance(layout, self._outer):
+ sublayout = self._inner()
+ else:
+ raise ValueError('Unknown parent layout %s' % layout)
+
+ self._add(sublayout, part)
+
+ layout.setLayout(sublayout)
+
+ else:
+ raise ValueError('Only Frontend or tuple are valid values '
+ 'valid for parts not %s (%s)' % (part, type(part)))
+
+ def setupUi(self):
+ super().setupUi()
+ layout = self._outer()
+ self._add(layout, self.parts)
+ self.widget.placeholder.setLayout(layout)
+
+
+class VerticalUi(_PanelsUi):
+ """Uses a vertical box layout to locate widgets.
+ """
+
+ _inner, _outer = QtGui.QHBoxLayout, QtGui.QVBoxLayout
+
+
+class HorizonalUi(_PanelsUi):
+ """Uses a horizontal box layout to locate widgets.
+ """
+
+ _inner, _outer = QtGui.QVBoxLayout, QtGui.QHBoxLayout
diff --git a/lantz/ui/blocks/loop.py b/lantz/ui/blocks/loop.py
index a99552e..6239c16 100644
--- a/lantz/ui/blocks/loop.py
+++ b/lantz/ui/blocks/loop.py
@@ -1,4 +1,13 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.ui.loop
+ ~~~~~~~~~~~~~
+ A Loop backend and frontend.
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
import time
import math
@@ -16,6 +25,24 @@ class StopMode(IntEnum):
class Loop(Backend):
+ """The Loop backend allows you to execute task periodically.
+
+ Usage:
+
+ from lantz.ui.blocks import Loop, LoopUi
+
+ def measure(counter, iterations, overrun):
+ print(counter, iterations, overrun)
+ data = osci.measure()
+ print(data)
+
+ app = Loop()
+
+ app.body = measure
+
+ start_gui_app(app, LoopUi)
+
+ """
#: Signal emitted before starting a new iteration
#: Parameters: loop counter, iterations, overrun
@@ -25,16 +52,38 @@ class Loop(Backend):
#: The parameter is used to inform if the loop was canceled.
loop_done = QtCore.Signal(bool)
+ #: The function to be called. It requires three parameters.
+ #: counter - the iteration number
+ #: iterations - total number of iterations
+ #: overrun - a boolean indicating if the time required for the operation
+ #: is longer than the interval.
+ #: :type: (int, int, bool) -> None
+ body = None
+
def __init__(self, **kwargs):
super().__init__(**kwargs)
- self.body = None
self._active = False
self._internal_func = None
def stop(self):
+ """Request the scanning to be stop.
+ Will stop when the current iteration is finished.
+ """
self._active = False
def start(self, body, interval=0, iterations=0, timeout=0):
+ """Request the scanning to be started.
+
+ :param body: function to be called at each iteration.
+ If None, the class body will be used.
+ :param interval: interval between starts of the iteration.
+ If the body takes too long, the iteration will
+ be as fast as possible and the overrun flag will be True
+ :param iterations: number of iterations
+ :param timeout: total time in seconds that the scanning will take.
+ If overdue, the scanning will be stopped.
+ If 0, there is no timeout.
+ """
self._active = True
body = body or self.body
@@ -48,6 +97,7 @@ def internal(counter, overrun=False, schedule=QtCore.QTimer.singleShot):
body(counter, iterations, overrun)
if iterations and counter + 1 == iterations:
+ self._active = False
self.loop_done.emit(False)
return
elif not self._active:
@@ -65,12 +115,18 @@ def internal(counter, overrun=False, schedule=QtCore.QTimer.singleShot):
class LoopUi(Frontend):
+ """The Loop frontend provides a GUI for the Rich Backend
+ """
gui = 'loop.ui'
auto_connect = False
+ #: Signal emitted when a start is requested.
+ #: The parameters are None, interval, iterations, duration
request_start = QtCore.Signal(object, object, object, object)
+
+ #: Signal emitted when a stop is requested.
request_stop = QtCore.Signal()
def connect_backend(self):
diff --git a/lantz/ui/blocks/placeholder.ui b/lantz/ui/blocks/placeholder.ui
new file mode 100644
index 0000000..be20048
--- /dev/null
+++ b/lantz/ui/blocks/placeholder.ui
@@ -0,0 +1,24 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 704
+ 447
+
+
+
+ Form
+
+
+ -
+
+
+
+
+
+
+
diff --git a/lantz/ui/blocks/scan.py b/lantz/ui/blocks/scan.py
new file mode 100644
index 0000000..bfa3dde
--- /dev/null
+++ b/lantz/ui/blocks/scan.py
@@ -0,0 +1,263 @@
+# -*- coding: utf-8 -*-
+"""
+ lantz.ui.scan
+ ~~~~~~~~~~~~~
+
+ A Scan frontend and Backend. Requires scan.ui
+
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+
+import time
+import math
+from enum import IntEnum
+
+from lantz.utils.qt import QtCore, QtGui
+from lantz.ui.app import Frontend, Backend, start_gui_app
+
+
+def _linspace_args(start, stop, step_size=None, length=None):
+ """Return the step_size and length for a given linspace
+ where step_size OR length is defined.
+ """
+
+ if step_size is None:
+ if length is None:
+ length = 10
+ step_size = (stop - start) / (length + 1)
+ else:
+ if length is not None:
+ raise ValueError('step_size and length cannot be both different from None')
+ length = math.floor((stop - start) / step_size) + 1
+
+ return step_size, length
+
+
+def _linspace(start, stop, step_size=None, length=None):
+ """Yield a linear spacing from start to stop
+ with defined step_size OR length.
+ """
+
+ step_size, length = _linspace_args(start, stop, step_size, length)
+
+ for i in range(length):
+ yield start + i * step_size
+
+
+class StepsMode(IntEnum):
+ """Step calculation modes.
+ """
+
+ #: fixed step size.
+ step_size = 0
+
+ #: fixed step count.
+ step_count = 1
+
+
+class Scan(Backend):
+ """A backend that iterates over an list of values,
+ calling a `body` function in each step.
+ """
+
+ #: Signal emitted before starting a new iteration
+ #: Parameters: loop counter, step value, overrun
+ iteration = QtCore.Signal(int, int, bool)
+
+ #: Signal emitted when the loop finished.
+ #: The parameter is used to inform if the loop was canceled.
+ loop_done = QtCore.Signal(bool)
+
+ #: The function to be called. It requires three parameters.
+ #: counter - the iteration number.
+ #: current value - the current value of the scan.
+ #: overrun - a boolean indicating if the time required for the operation
+ #: is longer than the interval.
+ #: :type: (int, int, bool) -> None
+ body = None
+
+ #: To be called before the body. Same signature as body
+ _pre_body = None
+
+ #: To be called after the body. Same signature as body
+ _post_body = None
+
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+ self._active = False
+ self._internal_func = None
+
+ def stop(self):
+ """Request the scanning to be stop.
+ Will stop when the current iteration is finished.
+ """
+ self._active = False
+
+ def start(self, body, interval=0, steps=(), timeout=0):
+ """Request the scanning to be started.
+
+ :param body: function to be called at each iteration.
+ If None, the class body will be used.
+ :param interval: interval between starts of the iteration.
+ If the body takes too long, the iteration will
+ be as fast as possible and the overrun flag will be True
+ :param steps: iterable
+ :param timeout: total time in seconds that the scanning will take.
+ If overdue, the scanning will be stopped.
+ If 0, there is no timeout.
+ """
+ self._active = True
+ body = body or self.body
+
+ iterations = len(steps)
+
+ def internal(counter, overrun=False, schedule=QtCore.QTimer.singleShot):
+ if not self._active:
+ self.loop_done.emit(True)
+ return
+
+ st = time.time()
+ self.iteration.emit(counter, iterations, overrun)
+ if self._pre_body is not None:
+ self._pre_body(counter, steps[counter], overrun)
+ if body is not None:
+ body(counter, steps[counter], overrun)
+ if self._post_body is not None:
+ self._post_body(counter, steps[counter], overrun)
+
+ if iterations and counter + 1 == iterations:
+ self._active = False
+ self.loop_done.emit(False)
+ return
+ elif not self._active:
+ self.loop_done.emit(True)
+ return
+
+ sleep = interval - (time.time() - st)
+ schedule(sleep * 1000 if sleep > 0 else 0,
+ lambda: self._internal_func(counter + 1, sleep < 0))
+
+ self._internal_func = internal
+ if timeout:
+ QtCore.QTimer.singleShot(timeout * 1000, self.stop)
+ QtCore.QTimer.singleShot(0, lambda: self._internal_func(0))
+
+
+class ScanUi(Frontend):
+ """A frontend to the Scan backend.
+
+ Allows you to create linear sequence of steps between a start a stop,
+ with selectable step size or number of steps.
+ """
+
+ gui = 'scan.ui'
+
+ auto_connect = False
+
+ #: Signal emitted when a start is requested.
+ #: The parameters are None, interval, vector of steps
+ request_start = QtCore.Signal(object, object, object)
+
+ #: Signal emitted when a stop is requested.
+ request_stop = QtCore.Signal()
+
+ def connect_backend(self):
+ super().connect_backend()
+
+ self.widget.start_stop.clicked.connect(self.on_start_stop_clicked)
+ self.widget.mode.currentIndexChanged.connect(self.on_mode_changed)
+
+ self.widget.step_count.valueChanged.connect(self.recalculate)
+ self.widget.start.valueChanged.connect(self.recalculate)
+ self.widget.stop.valueChanged.connect(self.recalculate)
+ self.widget.step_size.valueChanged.connect(self.recalculate)
+
+ self.widget.progress_bar.setValue(0)
+
+ self._ok_palette = QtGui.QPalette(self.widget.progress_bar.palette())
+ self._overrun_palette = QtGui.QPalette(self.widget.progress_bar.palette())
+ self._overrun_palette.setColor(QtGui.QPalette.Highlight,
+ QtGui.QColor(QtCore.Qt.red))
+
+ self.backend.iteration.connect(self.on_iteration)
+ self.backend.loop_done.connect(self.on_loop_done)
+
+ self.request_start.connect(self.backend.start)
+ self.request_stop.connect(self.backend.stop)
+
+ def on_start_stop_clicked(self, value=None):
+ if self.backend._active:
+ self.widget.start_stop.setText('...')
+ self.widget.start_stop.setEnabled(False)
+ self.request_stop.emit()
+ return
+
+ self.widget.start_stop.setText('Stop')
+ self.widget.start_stop.setChecked(True)
+
+ vals = [getattr(self.widget, name).value()
+ for name in 'start stop step_size step_count wait'.split()]
+ start, stop, step_size, step_count, interval = vals
+
+ steps = list(_linspace(start, stop, step_size))
+
+ self.request_start.emit(None, interval, steps)
+
+ def recalculate(self, *args):
+ mode = self.widget.mode.currentIndex()
+ if mode == StepsMode.step_size:
+ step_size, length = _linspace_args(self.widget.start.value(),
+ self.widget.stop.value(),
+ self.widget.step_size.value())
+ self.widget.step_count.setValue(length)
+ elif mode == StepsMode.step_count:
+ step_size, length = _linspace_args(self.widget.start.value(),
+ self.widget.stop.value(),
+ length=self.widget.step_count.value())
+ self.widget.step_size.setValue(step_size)
+
+ def on_iteration(self, counter, iterations, overrun):
+ pbar = self.widget.progress_bar
+
+ if not counter:
+ if iterations:
+ pbar.setMaximum(iterations + 1)
+ else:
+ pbar.setMaximum(0)
+
+ if iterations:
+ pbar.setValue(counter + 1)
+
+ if overrun:
+ pbar.setPalette(self._overrun_palette)
+ else:
+ pbar.setPalette(self._ok_palette)
+
+ def on_mode_changed(self, new_index):
+ if new_index == StepsMode.step_size:
+ self.widget.step_count.setEnabled(False)
+ self.widget.step_size.setEnabled(True)
+ elif new_index == StepsMode.step_count:
+ self.widget.step_count.setEnabled(True)
+ self.widget.step_size.setEnabled(False)
+
+ self.recalculate()
+
+ def on_loop_done(self, cancelled):
+ self.widget.start_stop.setText('Start')
+ self.widget.start_stop.setEnabled(True)
+ self.widget.start_stop.setChecked(False)
+ if self.widget.progress_bar.maximum():
+ self.widget.progress_bar.setValue(self.widget.progress_bar.maximum())
+ else:
+ self.widget.progress_bar.setMaximum(1)
+
+
+if __name__ == '__main__':
+ def func(current, total, overrun):
+ print('func', current, total, overrun)
+ app = Scan()
+ app.body = func
+ start_gui_app(app, ScanUi)
diff --git a/lantz/ui/blocks/scan.ui b/lantz/ui/blocks/scan.ui
index 13760e3..f93fa82 100644
--- a/lantz/ui/blocks/scan.ui
+++ b/lantz/ui/blocks/scan.ui
@@ -6,25 +6,72 @@
0
0
- 219
- 253
+ 218
+ 258
Form
+
+ QFormLayout::FieldsStayAtSizeHint
+
+ -
+
+
+ Mode
+
+
+
+ -
+
+
-
+
+ Step size
+
+
+ -
+
+ Step count
+
+
+
+
-
-
+
- Start
+ Steps:
-
-
+
+
+ false
+
+
+ 1
+
+
+ 100000000
+
+
+ 10
+
+
+
+ -
+
+
+ Start:
+
+
+
+ -
+
- Hz
+
100000.000000000000000
@@ -34,17 +81,17 @@
- -
+
-
- Stop
+ Stop:
- -
-
+
-
+
- Hz
+
100000.000000000000000
@@ -54,17 +101,17 @@
- -
+
-
- Step
+ Step size:
- -
-
+
-
+
- Hz
+
100000.000000000000000
@@ -74,15 +121,15 @@
- -
+
-
- Wait
+ Wait:
- -
-
+
-
+
s
@@ -97,8 +144,8 @@
- -
-
+
-
+
Start
@@ -107,7 +154,7 @@
- -
+
-
100
@@ -117,37 +164,6 @@
- -
-
-
- Points
-
-
-
- -
-
-
- -
-
-
- Mode
-
-
-
- -
-
-
-
-
- Points
-
-
- -
-
- Steps
-
-
-
-
diff --git a/lantz/ui/qtlog.py b/lantz/ui/qtlog.py
index 19b6683..88dda44 100644
--- a/lantz/ui/qtlog.py
+++ b/lantz/ui/qtlog.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
import logging
from PyQt4 import QtCore
@@ -158,7 +159,7 @@ def data(self, index, role=Qt.DisplayRole):
col = viscols[index.column()]
try:
v = getattr(record, col.name)
- except AttributeError as e:
+ except AttributeError:
try:
v = ("%(" + col.name + ")s") % record
except:
diff --git a/lantz/ui/shell.py b/lantz/ui/shell.py
deleted file mode 100644
index 1896bdb..0000000
--- a/lantz/ui/shell.py
+++ /dev/null
@@ -1,312 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- lantz-visa-shell
- ~~~~~~~~~~~~~~~~
-
- VISA shell for interactive testing.
-
- :copyright: (c) 2012 by Lantz Authors, see AUTHORS for more details.
- :license: BSD, see LICENSE for more details.
-"""
-
-import sys
-import cmd
-
-class PatchedCmd(cmd.Cmd):
-
- use_rawinput = True
-
- # This has been patched to enable autocompletion on Mac OSX
- def cmdloop(self, intro=None):
- """Repeatedly issue a prompt, accept input, parse an initial prefix
- off the received input, and dispatch to action methods, passing them
- the remainder of the line as argument.
- """
-
- self.preloop()
- if self.use_rawinput and self.completekey:
- try:
- import readline
- self.old_completer = readline.get_completer()
- readline.set_completer(self.complete)
-
- if 'libedit' in readline.__doc__:
- # readline linked to BSD libedit
- if self.completekey == 'tab':
- key = '^I'
- else:
- key = self.completekey
- readline.parse_and_bind('bind %s rl_complete' % (key,))
- else:
- # readline linked to the real readline
- readline.parse_and_bind(self.completekey + ': complete')
-
- except ImportError:
- pass
- try:
- if intro is not None:
- self.intro = intro
- if self.intro:
- self.stdout.write(str(self.intro)+"\n")
- stop = None
- while not stop:
- if self.cmdqueue:
- line = self.cmdqueue.pop(0)
- else:
- if self.use_rawinput:
- try:
- line = input(self.prompt)
- except EOFError:
- line = 'EOF'
- else:
- self.stdout.write(self.prompt)
- self.stdout.flush()
- line = self.stdin.readline()
- if not len(line):
- line = 'EOF'
- else:
- line = line.rstrip('\r\n')
- line = self.precmd(line)
- stop = self.onecmd(line)
- stop = self.postcmd(stop, line)
- self.postloop()
- finally:
- if self.use_rawinput and self.completekey:
- try:
- import readline
- readline.set_completer(self.old_completer)
- except ImportError:
- pass
-
-
-class LantzShell(PatchedCmd):
- """Base Shell shell for interactive testing.
- """
-
- intro = '\nWelcome to the Lantz shell. Type help or ? to list commands.\n'
- prompt = '(lantz) '
-
- def postcmd(self, stop, line):
- print()
- return stop
-
- def do_visa(self, args):
- """Open the visa shell."""
-
- VisaShell().cmdloop()
-
- def do_usbtmc(self, args):
- """Open resource by number, resource name or alias: open 3"""
-
- USBTMCShell().cmdloop()
-
- def do_exit(self, arg):
- """Exit the shell session."""
-
- print('Bye!\n')
- sys.exit(0)
-
-
-class InterfaceShell(PatchedCmd):
- """Base Shell shell for interactive testing.
- """
-
- intro = '\nWelcome to the Lantz shell. Type help or ? to list commands.\n'
- prompt = '(lantz) '
-
- use_rawinput = True
-
- def __init__(self, library_path=None):
- super().__init__()
- self.default_prompt = self.prompt
- self.resources = []
- self.current = None
-
- def do_list(self, args):
- """List all connected resources."""
- raise NotImplemented
-
- def do_open(self, args):
- raise NotImplemented
-
- def complete_open(self, text, line, begidx, endidx):
- if not self.resources:
- self.do_list('do not print')
- return [item[0] for item in self.resources if item[0].startswith(text)] + \
- [item[1] for item in self.resources if item[1] and item[1].startswith(text)]
-
- def do_close(self, args):
- """Close resource in use."""
-
- if not self.current:
- print('There are no resources in use. Use the command "open".')
- return
-
- self.current.finalize()
- print('The resource has been closed.')
- self.current = None
- self.prompt = self.default_prompt
-
- def do_query(self, args):
- """Query resource in use: query *IDN? """
-
- if not self.current:
- print('There are no resources in use. Use the command "open".')
- return
-
- try:
- print('Response: {}'.format(self.current.query(args)))
- except Exception as e:
- print(e)
-
- def do_recv(self, args):
- """Receive from the resource in use."""
-
- if not self.current:
- print('There are no resources in use. Use the command "open".')
- return
-
- try:
- print(self.current.recv())
- except Exception as e:
- print(e)
-
- def do_send(self, args):
- """Send to the resource in use: send *IDN? """
-
- if not self.current:
- print('There are no resources in use. Use the command "open".')
- return
-
- try:
- self.current.send(args)
- except Exception as e:
- print(e)
-
- def do_exit(self, arg):
- """Exit the shell session."""
-
- if self.current:
- self.current.finalize()
-
- return True
-
- def do_EOF(self, arg):
- """.
- """
- return True
-
-
-class VisaShell(InterfaceShell):
- """VISA shell for interactive testing.
- """
-
- from lantz import visa
-
- intro = '\nWelcome to the VISA shell. Type help or ? to list commands.\n'
- prompt = '(visa) '
-
-
- def __init__(self, library_path=None):
- super().__init__()
- self.resource_manager = self.visa.ResourceManager(library_path)
-
- def do_list(self, args):
- """List all connected resources."""
-
- resources = self.resource_manager.list_resources_info('?*')
-
- self.resources = []
- for ndx, (resource, value) in enumerate(resources.items()):
- if not args:
- print('({:2d}) {}'.format(ndx, resource))
- if value.alias:
- print(' alias: {}'.format(value.alias))
-
- self.resources.append((resource, value.alias or None))
-
- def do_open(self, args):
- """Open resource by number, resource name or alias: open 3"""
-
- if not args:
- print('A resource name must be specified.')
- return
-
- if self.current:
- print('You can only open one resource at a time. Please close the current one first.')
- return
-
- if args.isdigit():
- try:
- args = self.resources[int(args)][1]
- except IndexError:
- print('Not a valid resource number. Use the command "list".')
- return
-
- try:
- self.current = self.visa.VisaDriver(args)
- self.current.initialize()
- print('{} has been opened.\n'
- 'You can talk to the device using "send", "recv" or "query.\n'
- '\\n is added and expected at the end of each message'.format(args))
- self.prompt = '(open) '
- except Exception as e:
- print(e)
-
- def do_exit(self, arg):
- super().do_exit(arg)
- del self.resource_manager
- return True
-
-
-class USBTMCShell(InterfaceShell):
- """VISA shell for interactive testing.
- """
-
- from lantz.usb import DeviceInfo
- from lantz.drivers import usbtmc
-
- intro = '\nWelcome to the USBTMC shell. Type help or ? to list commands.\n'
- prompt = '(usbtmc) '
-
- def do_list(self, args):
- """List all connected resources."""
-
- self.resources = [str(self.DeviceInfo.from_device(dev))
- for dev in self.usbtmc.find_tmc_devices()]
- for ndx, resource in enumerate(self.resources):
- print('({:2d}) {}'.format(ndx, resource))
-
- def do_open(self, args):
- """Open resource by number, resource name or alias: open 3"""
-
- if not args:
- print('A resource name must be specified.')
- return
-
- if self.current:
- print('You can only open one resource at a time. Please close the current one first.')
- return
-
- if args.isdigit():
- try:
- args = self.resources[int(args)]
- except IndexError:
- print('Not a valid resource number. Use the command "list".')
- return
-
- try:
- self.current = self.usbtmc.USBTMCDriver(serial_number=args.serial_number)
- self.current.initialize()
- print('{} has been opened.\n'
- 'You can talk to the device using "send", "recv" or "query.\n'
- '\\n is added and expected at the end of each message'.format(args))
- self.prompt = '(open) '
- except Exception as e:
- print(e)
-
-
-def main():
- LantzShell().cmdloop()
-
diff --git a/lantz/ui/widgets.py b/lantz/ui/widgets.py
index cafaaa5..049e989 100644
--- a/lantz/ui/widgets.py
+++ b/lantz/ui/widgets.py
@@ -6,7 +6,7 @@
Implements UI widgets based on Qt widgets. To achieve functionality,
instances of QtWidgets are patched.
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
@@ -14,6 +14,7 @@
import json
import inspect
+from lantz.utils import is_building_docs
from lantz.utils.qt import QtCore, QtGui
__PRINT_TRACEBACK__ = True
@@ -75,8 +76,13 @@ def register_wrapper(cls):
for wrapped in cls._WRAPPED:
if wrapped in cls._WRAPPERS:
logger.warn('{} is already registered to {}.'.format(wrapped, cls._WRAPPERS[wrapped]))
- cls._WRAPPERS[wrapped] = type(wrapped.__name__ + 'Wrapped',
- (cls, wrapped), {'_IS_LANTZ_WRAPPER': True})
+
+ if is_building_docs:
+ cls._WRAPPERS[wrapped] = type(wrapped.__name__ + 'Wrapped',
+ (cls, ), {'_IS_LANTZ_WRAPPER': True})
+ else:
+ cls._WRAPPERS[wrapped] = type(wrapped.__name__ + 'Wrapped',
+ (cls, wrapped), {'_IS_LANTZ_WRAPPER': True})
return cls
@@ -285,13 +291,14 @@ def from_feat(cls, feat, parent=None):
class FeatWidget(object):
"""Widget to show a Feat.
-
- :param parent: parent widget.
- :param target: driver object to connect.
- :param feat: Feat to connect.
"""
def __new__(cls, parent, target, feat):
+ """
+ :param parent: parent widget.
+ :param target: driver object to connect.
+ :param feat: Feat to connect.
+ """
widget = WidgetMixin.from_feat(feat, parent)
widget.bind_feat(feat)
widget.lanz_target = target
@@ -715,11 +722,11 @@ def __iter__(self):
pending = [self.parent, ]
qualname = {self.parent: self.parent.objectName()}
while pending:
- object = pending.pop()
- for child in object.children():
+ obj = pending.pop()
+ for child in obj.children():
if not isinstance(child, QtGui.QWidget):
continue
- qualname[child] = qualname[object] + '.' + child.objectName()
+ qualname[child] = qualname[obj] + '.' + child.objectName()
pending.append(child)
yield child.objectName(), qualname[child], child
diff --git a/lantz/utils/__init__.py b/lantz/utils/__init__.py
index c3ff101..800a854 100644
--- a/lantz/utils/__init__.py
+++ b/lantz/utils/__init__.py
@@ -5,6 +5,12 @@
A package with utility modules.
- :copyright: 2013 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
+
+import os
+
+# Indicates if the documentation is being built in the current process.
+# :type: bool
+is_building_docs = os.environ.get('LANTZ_BUILDING_DOCS', 'False') == 'True'
diff --git a/lantz/utils/qt.py b/lantz/utils/qt.py
index e0b28b9..4804aee 100644
--- a/lantz/utils/qt.py
+++ b/lantz/utils/qt.py
@@ -19,12 +19,12 @@
import os
-from lantz.utils.qt_loaders import (load_qt, QT_API_PYSIDE, QT_API_PYQT)
+from lantz.utils.qt_loaders import (load_qt, QT_API_PYSIDE, QT_API_PYQT, QT_MOCK)
QT_API = os.environ.get('QT_API', None)
-if QT_API not in [QT_API_PYSIDE, QT_API_PYQT, None]:
- raise RuntimeError("Invalid Qt API %r, valid values are: %r, %r" %
- (QT_API, QT_API_PYSIDE, QT_API_PYQT))
+if QT_API not in [QT_API_PYSIDE, QT_API_PYQT, QT_MOCK, None]:
+ raise RuntimeError("Invalid Qt API %r, valid values are: %r, %r, %r" %
+ (QT_API, QT_API_PYSIDE, QT_API_PYQT, QT_MOCK))
if QT_API is None:
api_opts = [QT_API_PYSIDE, QT_API_PYQT]
else:
diff --git a/lantz/utils/qt_loaders.py b/lantz/utils/qt_loaders.py
index 9872493..88c608e 100644
--- a/lantz/utils/qt_loaders.py
+++ b/lantz/utils/qt_loaders.py
@@ -29,17 +29,21 @@
QT_API_PYQTv1 = 'pyqtv1'
QT_API_PYQT_DEFAULT = 'pyqtdefault' # don't set SIP explicitly
QT_API_PYSIDE = 'pyside'
+QT_MOCK = 'mock'
-def check_version(v, check):
- """check version string v >= check
+def check_version(version, minimum_version):
+ """check version string version >= minimum_version
+
+ :param version: Version of the package.
+ :param minimum_version: Minimum version required.
If dev/prerelease tags result in TypeError for string-number comparison,
it is assumed that the dependency is satisfied.
Users on dev branches are responsible for keeping their own packages up to date.
"""
try:
- return LooseVersion(v) >= LooseVersion(check)
+ return LooseVersion(version) >= LooseVersion(minimum_version)
except TypeError:
return True
@@ -119,20 +123,23 @@ def has_binding(api):
# we can't import an incomplete pyside and pyqt4
# this will cause a crash in sip (#1431)
# check for complete presence before importing
+
+ if api == QT_MOCK:
+ return True
+
module_name = {QT_API_PYSIDE: 'PySide',
QT_API_PYQT: 'PyQt4',
QT_API_PYQTv1: 'PyQt4',
QT_API_PYQT_DEFAULT: 'PyQt4'}
module_name = module_name[api]
- import imp
+ import importlib
try:
#importing top level PyQt4/PySide module is ok...
- mod = __import__(module_name)
#...importing submodules is not
- imp.find_module('QtCore', mod.__path__)
- imp.find_module('QtGui', mod.__path__)
- imp.find_module('QtSvg', mod.__path__)
+ mod = importlib.import_module(module_name + '.QtCore')
+ mod = importlib.import_module(module_name + '.QtGui')
+ mod = importlib.import_module(module_name + '.QtSvg')
#we can also safely check PySide version
if api == QT_API_PYSIDE:
@@ -176,11 +183,8 @@ def import_pyqt4(version=2):
"""
Import PyQt4
- Parameters
- ----------
- version : 1, 2, or None
- Which QString/QVariant API to use. Set to None to use the system
- default
+ :param version: 1, 2, or None. Which QString/QVariant API to use.
+ Set to None to use the system default
ImportErrors raised within this function are non-recoverable
"""
@@ -266,9 +270,8 @@ def loadUi(uifile, baseinstance=None):
"""
Dynamically load a user interface from the given ``uifile``.
- ``uifile`` is a string containing a file name of the UI file to load.
-
- If ``baseinstance`` is ``None``, the a new instance of the top-level widget
+ :param uifile: a string containing a file name of the UI file to load.
+ :param baseinstance: If ``None``, the a new instance of the top-level widget
will be created. Otherwise, the user interface is created within the given
``baseinstance``. In this case ``baseinstance`` must be an instance of the
top-level widget class in the UI file to load, or a subclass thereof. In
@@ -294,6 +297,31 @@ def loadUi(uifile, baseinstance=None):
return QtCore, QtGui, QtSvg, QT_API_PYSIDE
+def import_qtmock():
+ """
+ Return Mock Modules
+ """
+ # The new-style string API (version=2) automatically
+ # converts QStrings to Unicode Python strings. Also, automatically unpacks
+ # QVariants to their underlying objects.
+
+ from unittest.mock import MagicMock
+
+ class Mock(MagicMock):
+
+ @classmethod
+ def __getattr__(cls, name):
+ m = Mock()
+ m.__name__ = name
+ return m
+
+ QObject = object
+
+ QtGui, QtCore, QtSvg = Mock(), Mock(), Mock()
+
+ return QtCore, QtGui, QtSvg, 'mock'
+
+
def load_qt(api_options):
"""
Attempt to import Qt, given a preference list
@@ -301,9 +329,7 @@ def load_qt(api_options):
It is safe to call this function multiple times.
- Parameters
- ----------
- api_options: List of strings
+ :param api_options: List of strings
The order of APIs to try. Valid items are 'pyside',
'pyqt', and 'pyqtv1'
@@ -323,16 +349,17 @@ def load_qt(api_options):
loaders = {QT_API_PYSIDE: import_pyside,
QT_API_PYQT: import_pyqt4,
QT_API_PYQTv1: partial(import_pyqt4, version=1),
- QT_API_PYQT_DEFAULT: partial(import_pyqt4, version=None)
+ QT_API_PYQT_DEFAULT: partial(import_pyqt4, version=None),
+ QT_MOCK: import_qtmock
}
for api in api_options:
if api not in loaders:
raise RuntimeError(
- "Invalid Qt API %r, valid values are: %r, %r, %r, %r" %
+ "Invalid Qt API %r, valid values are: %r, %r, %r, %r, %r" %
(api, QT_API_PYSIDE, QT_API_PYQT,
- QT_API_PYQTv1, QT_API_PYQT_DEFAULT))
+ QT_API_PYQTv1, QT_API_PYQT_DEFAULT, QT_MOCK))
if not can_import(api):
continue
diff --git a/requirements-doc.txt b/requirements-doc.txt
new file mode 100644
index 0000000..4010562
--- /dev/null
+++ b/requirements-doc.txt
@@ -0,0 +1,3 @@
+sphinx
+sphinx_rtd_theme
+-r requirements-rtd.txt
diff --git a/requirements-full.txt b/requirements-full.txt
index b67e567..3e98d8e 100644
--- a/requirements-full.txt
+++ b/requirements-full.txt
@@ -1,6 +1,5 @@
colorama
-sphinx
pyserial
pyusb
numpy
-lantz
+-r requirements-doc.txt
diff --git a/requirements-rtd.txt b/requirements-rtd.txt
new file mode 100644
index 0000000..89c0e00
--- /dev/null
+++ b/requirements-rtd.txt
@@ -0,0 +1,3 @@
+pyvisa
+sphinxcontrib-images
+lantz
diff --git a/scripts/get-lantz.py b/scripts/get-lantz.py
deleted file mode 100644
index ace2ffa..0000000
--- a/scripts/get-lantz.py
+++ /dev/null
@@ -1,106 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-import sys
-
-if not sys.version_info >= (3, 2, 1):
- print('Lantz requires Python >= 3.2.1')
- sys.exit(1)
-
-import os
-import time
-import platform
-import argparse
-import subprocess
-import urllib.request
-import concurrent.futures
-
-if platform.architecture()[0] != '32bit' or not sys.platform.startswith('win'):
- print('Only 32bit Python running on Windows is currently supported by get-lantz.py')
- sys.exit(2)
-
-parser = argparse.ArgumentParser('Get Lantz!')
-parser.add_argument('-e', '--editable', action='store_true',
- help='Install Lantz as an editable package')
-args = parser.parse_args()
-
-
-
-URLS = {'setuptools': ('distribute_setup.py', 'http://python-distribute.org/{}'),
- 'pip': ('get-pip.py', 'https://raw.github.com/pypa/pip/master/contrib/{}'),
- 'PyQt4': ('PyQt-Py3.2-x86-gpl-4.9.4-1.exe', 'http://www.riverbankcomputing.co.uk/static/Downloads/PyQt4/{}'),
- 'numpy': ('numpy-1.6.2-win32-superpack-python3.2.exe', 'http://sourceforge.net/projects/numpy/files/NumPy/1.6.2/{}/download'),
- 'scipy': ('scipy-0.10.1-win32-superpack-python3.2.exe', 'http://sourceforge.net/projects/scipy/files/scipy/0.10.1/{}/download'),
- 'git': ('Git-1.7.11-preview20120710.exe', 'http://msysgit.googlecode.com/files/{}'),
- 'matplotlib': ('', ''),
- 'visa': ('visa520full.exe', 'http://ftp.ni.com/support/softlib/visa/NI-VISA/5.2/win/{}')}
-
-if not args.editable:
- del URLS['git']
-
-def download(filename, url):
-
- if os.path.exists(filename):
- print('File found {}'.format(filename))
- return
-
- if '{}' in url:
- url = url.format(filename)
-
- start = time.time()
- print('Downloading {}'.format(filename))
- urllib.request.urlretrieve(url, filename)
- print('Downloaded {} in {:.2f} secs'.format(filename, time.time() - start))
-
-
-print(' Checking '.center(20, '-'))
-INSTALL = []
-for check in ('setuptools', 'pip', 'PyQt4', 'numpy', 'scipy'):
- try:
- __import__(check)
- print('No need to install {}'.format(check))
- except ImportError:
- INSTALL.append(check)
- print('Adding {} to install list'.format(check))
-
-if args.editable:
- try:
- subprocess.call(['git', '--version'])
- print('No need to install git')
- except Exception as e:
- print('Adding git to install list')
- INSTALL.append('git')
-
-
-INSTALL.append('visa')
-
-os.chdir(os.path.dirname(os.path.abspath(__file__)))
-print('Working directory: {}'.format(os.getcwd()))
-
-
-print(' Downloading '.center(20, '-'))
-with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
- futs = [executor.submit(download, *filename_url)
- for filename, filename_url in URLS.items()
- if filename in INSTALL]
- concurrent.futures.wait(futs)
-
-
-print(' Installing '.center(20, '-'))
-for key in ('setuptools', 'pip', ):
- if key in INSTALL:
- subprocess.call([sys.executable, URLS[key][0]])
-
-for key in ('PyQt4', 'numpy', 'scipy', 'git', 'visa'):
- if key in INSTALL:
- subprocess.call([URLS[key][0], ])
-
-PIP = os.path.join(os.path.dirname(sys.executable), 'Scripts', 'pip')
-
-REQS = ['colorama', 'pyserial', 'sphinx', 'pyyaml']
-
-if args.editable:
- subprocess.call([PIP, 'install', ] + REQS)
- subprocess.call([PIP, 'install', '-e', 'lantz'])
-else:
- subprocess.call([PIP, 'install', ] + REQS + ['lantz'])
diff --git a/scripts/lantz-monitor b/scripts/lantz-monitor
index 6435995..40d2aa3 100644
--- a/scripts/lantz-monitor
+++ b/scripts/lantz-monitor
@@ -7,7 +7,7 @@
Text based tool to monitor Lantz messages logged to a socket.
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
+ :copyright: 2015 by Lantz Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
diff --git a/scripts/lantz-scan b/scripts/lantz-scan
deleted file mode 100644
index b7a8bef..0000000
--- a/scripts/lantz-scan
+++ /dev/null
@@ -1,75 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- lantz-scan
- ~~~~~~~~~~
-
- Serial port scanner.
-
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
- :license: BSD, see LICENSE for more details.
-"""
-
-import serial
-
-def scan(ports=None, verbose=False):
- """Scan for available ports.
-
- :param ports: an iterable of device names or port number numbers.
- if None, ports 0 to 9 is given.
- :param verbose: print status.
- :return: return a list of tuples (identification, name)
- """
-
- if not ports:
- ports = range(10)
-
- if verbose:
- _print = print
- else:
- def _print(*args, **kwargs):
- pass
-
- for port in ports:
- try:
- _print('Trying {} ... '.format(port), end='')
- s = serial.Serial(port)
- yield port, s.portstr
- s.close()
- _print('success (port string: {}'.format(s.portstr))
- except serial.SerialException:
- _print('failed!')
- pass
-
-if __name__=='__main__':
- import sys
- import argparse
-
- parser = argparse.ArgumentParser(description='Tries to open serial ports and print the valid ones.')
- parser.add_argument('ports', metavar='ports', type=str, nargs='*', default=None,
- help='Ports to open. Ranges (e.g. 0-3, meaning 0, 1, 2, 3 are also possible.')
- args = parser.parse_args()
-
- if args.ports:
- try:
- ports = set()
- for port in args.ports:
- if '-' in port:
- fr, to = port.split('-')
- ports.update(range(int(fr), int(to)+1))
- else:
- try:
- ports.add(int(port))
- except ValueError:
- ports.add(port)
- except Exception as e:
- print('Could no parse input {}: {}'.format(port, e))
- sys.exit(1)
- else:
- ports = list(range(0, 10))
-
- print("Testing ports ...")
-
- number = len(tuple(scan(ports, verbose=True)))
-
- print('{} ports found'.format(number + 1))
diff --git a/scripts/lantz-sim b/scripts/lantz-sim
deleted file mode 100644
index 24093bb..0000000
--- a/scripts/lantz-sim
+++ /dev/null
@@ -1,23 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- lantz-sim
- ~~~~~~~~~
-
- Run simulators
-
- :copyright: 2012 by Lantz Authors, see AUTHORS for more details.
- :license: BSD, see LICENSE for more details.
-"""
-
-from lantz.simulators import SIMULATORS
-
-if __name__=='__main__':
- import argparse
-
- parser = argparse.ArgumentParser(description='Run Lantz simulators.')
- parser.add_argument('simulator', choices=list(SIMULATORS.keys()))
- args, pending = parser.parse_known_args()
- print('Dispatching ' + args.simulator)
- SIMULATORS[args.simulator](pending)
-
diff --git a/setup.py b/setup.py
index abd523f..2091f50 100644
--- a/setup.py
+++ b/setup.py
@@ -4,7 +4,9 @@
try:
from setuptools import setup
except ImportError:
- from distutils.core import setup
+ print('Please install or upgrade setuptools or pip to continue')
+ sys.exit(1)
+
import os
import sys
@@ -20,20 +22,32 @@ def read(filename):
read('CHANGES')])
__doc__ = long_description
-folder = os.path.dirname(os.path.abspath(__file__))
-folder = os.path.join(folder, 'lantz', 'drivers')
-paths = os.listdir(folder)
requirements = []
+
if sys.version_info < (3, 4):
requirements.append('enum34')
+root_folder = os.path.dirname(os.path.abspath(__file__))
+
+# Compile a list of companies with drivers.
+folder = os.path.join(root_folder, 'lantz', 'drivers')
+paths = os.listdir(folder)
companies = [path for path in paths
if os.path.isdir(os.path.join(folder, path))
and os.path.exists(os.path.join(folder, path, '__init__.py'))]
+
+# Compile a list of companies with legacy drivers.
+folder = os.path.join(root_folder, 'lantz', 'drivers', 'legacy')
+paths = os.listdir(folder)
+legacy_companies = [path for path in paths
+ if os.path.isdir(os.path.join(folder, path))
+ and os.path.exists(os.path.join(folder, path, '__init__.py'))]
+
+
setup(name='Lantz',
- version='0.3.dev1',
+ version='0.4.dev0',
license='BSD',
description='Instrumentation framework',
long_description=long_description,
@@ -43,27 +57,26 @@ def read(filename):
url='http://lantz.glugcen.dc.uba.ar/',
packages=['lantz',
'lantz.ui',
+ 'lantz.ui.blocks',
'lantz.simulators',
'lantz.utils',
'lantz.drivers'] +
- ['lantz.drivers.' + company for company in companies],
+ ['lantz.drivers.' + company for company in companies] +
+ ['lantz.drivers.legacy.' + company for company in legacy_companies],
test_suite='lantz.testsuite.testsuite',
- install_requires=['pint>=0.3.1',
+ install_requires=['pint>=0.6',
+ 'pyvisa>=1.6.2',
'stringparser',
] + requirements,
zip_safe=False,
platforms='any',
- extra_require={'colorama': ['colorama'],
- 'numpy': ['numpy'],
- 'ui': ['sip', 'pyqt4']
- },
entry_points={
'zest.releaser.releaser.after_checkout': [
'pyroma = lantz:run_pyroma',
],
'console_scripts': [
- 'lantz-shell = lantz.ui.shell:main',
- ]
+ 'lantz-sim = lantz.simulators:main',
+ ]
},
classifiers=[
'Development Status :: 4 - Beta',
@@ -81,6 +94,5 @@ def read(filename):
'Topic :: Software Development :: Libraries'
],
scripts=['scripts/lantz-monitor',
- 'scripts/lantz-scan',
- 'scripts/lantz-sim'],
+ ],
)
diff --git a/tox.ini b/tox.ini
deleted file mode 100644
index 967b38f..0000000
--- a/tox.ini
+++ /dev/null
@@ -1,8 +0,0 @@
-[tox]
-envlist = py32,py33
-
-[testenv]
-commands = {envpython} -m unittest discover
-deps =
- pint
- stringparser