diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 000000000..2049cc7bf --- /dev/null +++ b/.coveragerc @@ -0,0 +1,2 @@ +[report] +show_missing = True \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5ba77b1e9..3998172c3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,19 @@ build/ dist/ pymodbus.egg-info/ .coverage +.vscode +.idea +.noseids + +.idea/ +.tox/ +doc/api/epydoc/html/ +.vscode/ + +__pycache__/ +pymodbus/__pycache__/ +pymodbus/client/__pycache__/ +pymodbus/datastore/__pycache__/ +pymodbus/internal/__pycache__/ +pymodbus/server/__pycache__/ +test/__pycache__/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..6817288a6 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,32 @@ +sudo: false +language: python +matrix: + include: + - os: linux + python: "2.7" + - os: linux + python: "3.4" + - os: linux + python: "3.5" + - os: linux + python: "3.6" + - os: osx + language: generic +before_install: + - if [ $TRAVIS_OS_NAME = osx ]; then brew update; fi + - if [ $TRAVIS_OS_NAME = osx ]; then brew install openssl; fi + +install: +# - scripts/travis.sh pip install pip-accel + - scripts/travis.sh pip install coveralls + - scripts/travis.sh pip install --requirement=requirements-checks.txt + - scripts/travis.sh pip install --requirement=requirements-tests.txt + - scripts/travis.sh LC_ALL=C pip install . +script: +# - scripts/travis.sh make check + - scripts/travis.sh make test +after_success: + - scripts/travis.sh coveralls +branches: + except: + - /^[0-9]/ diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6734f8280..b2eab1798 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,35 @@ -Version 1.2.0 +Version 1.3.2 +------------------------------------------------------------ +* Fix response length calculation for ModbusASCII protocol +* Fix response length calculation ReportSlaveIdResponse, DiagnosticStatusResponse +* Fix never ending transaction case when response is recieved without header and CRC +* Fix tests + +Version 1.3.1 ------------------------------------------------------------ +* Recall socket recv until get a complete response +* Register_write_message.py: Observe skip_encode option when encoding a single register request +* Fix wrong expected response length for coils and discrete inputs +* Fix decode errors with ReadDeviceInformationRequest and ReportSlaveIdRequest on Python3 +* Move MaskWriteRegisterRequest/MaskWriteRegisterResponse to register_write_message.py from file_message.py +* Python3 compatible examples [WIP] +* Misc updates with examples +Version 1.3.0.rc2 +------------------------------------------------------------ +* Fix encoding problem for ReadDeviceInformationRequest method on python3 +* Fix problem with the usage of ord in python3 while cleaning up receive buffer +* Fix struct unpack errors with BinaryPayloadDecoder on python3 - string vs bytestring error +* Calculate expected response size for ReadWriteMultipleRegistersRequest +* Enhancement for ModbusTcpClient, ModbusTcpClient can now accept connection timeout as one of the parameter +* Misc updates + +Version 1.3.0.rc1 +------------------------------------------------------------ +* Timing improvements over MODBUS Serial interface +* Modbus RTU use 3.5 char silence before and after transactions +* Bug fix on FifoTransactionManager , flush stray data before transaction +* Update repository information * Added ability to ignore missing slaves * Added ability to revert to ZeroMode * Passed a number of extra options through the stack diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..e9f7d9ef5 --- /dev/null +++ b/Makefile @@ -0,0 +1,64 @@ +# Makefile for the `pymodbus' package. + +WORKON_HOME ?= $(HOME)/.virtualenvs +VIRTUAL_ENV ?= $(WORKON_HOME)/pymodbus +PATH := $(VIRTUAL_ENV)/bin:$(PATH) +MAKE := $(MAKE) --no-print-directory +SHELL = bash + +default: + @echo 'Makefile for pymodbus' + @echo + @echo 'Usage:' + @echo + @echo ' make install install the package in a virtual environment' + @echo ' make reset recreate the virtual environment' + @echo ' make check check coding style (PEP-8, PEP-257)' + @echo ' make test run the test suite, report coverage' + @echo ' make tox run the tests on all Python versions' + @echo ' make clean cleanup all temporary files' + @echo + +install: + @test -d "$(VIRTUAL_ENV)" || mkdir -p "$(VIRTUAL_ENV)" + @test -x "$(VIRTUAL_ENV)/bin/python" || virtualenv --quiet "$(VIRTUAL_ENV)" + @test -x "$(VIRTUAL_ENV)/bin/pip" || easy_install pip + @pip install --quiet --requirement=requirements.txt + @pip uninstall --yes pymodbus &>/dev/null || true + @pip install --quiet --no-deps --ignore-installed . + +reset: + $(MAKE) clean + rm -Rf "$(VIRTUAL_ENV)" + $(MAKE) install + +check: install + @pip install --upgrade --quiet --requirement=requirements-checks.txt + @flake8 + +test: install + @pip install --quiet --requirement=requirements-tests.txt + @nosetests --with-coverage --cover-html + @coverage report --fail-under=90 + +tox: install + @pip install --quiet tox && tox + +docs: install + @pip install --quiet sphinx + @cd doc/sphinx && sphinx-build -nb html -d doctrees . html + +publish: install + git push origin && git push --tags origin + $(MAKE) clean + pip install --quiet twine wheel + python setup.py sdist bdist_wheel + twine upload dist/* + $(MAKE) clean + +clean: + @rm -Rf *.egg .cache .coverage .tox build dist docs/build htmlcov + @find -depth -type d -name __pycache__ -exec rm -Rf {} \; + @find -type f -name '*.pyc' -delete + +.PHONY: default install reset check test tox docs publish clean diff --git a/README.rst b/README.rst index 55297215e..afb4e0730 100644 --- a/README.rst +++ b/README.rst @@ -1,3 +1,13 @@ +.. image:: https://travis-ci.org/riptideio/pymodbus.svg?branch=master + :target: https://travis-ci.org/riptideio/pymodbus + +.. image:: https://badges.gitter.im/Join%20Chat.svg + :target: https://gitter.im/pymodbus_dev/Lobby + +.. image:: https://readthedocs.org/projects/pymodbus-n/badge/?version=latest + :target: http://pymodbus-n.readthedocs.io/en/latest/?badge=latest + :alt: Documentation Status + ============================================================ Summary ============================================================ @@ -49,6 +59,9 @@ a user to test as many devices as their base operating system will allow (*allow in this case means how many Virtual IP addresses are allowed). For more information please browse the project documentation: + +http://riptideio.github.io/pymodbus/ +or http://readthedocs.org/docs/pymodbus/en/latest/index.html ------------------------------------------------------------ @@ -72,7 +85,8 @@ need, feel free to submit them so others can benefit. Also, if you have questions, please ask them on the mailing list so that others can benefit from the results and so that I can trace them. I get a lot of email and sometimes these requests -get lost in the noise: http://groups.google.com/group/pymodbus +get lost in the noise: http://groups.google.com/group/pymodbus or +at gitter: https://gitter.im/pymodbus_dev/Lobby ------------------------------------------------------------ Installing @@ -111,11 +125,34 @@ like your device tested, I accept devices via mail or by IP address. That said, the current work mainly involves polishing the library as I get time doing such tasks as: + * Add CI support + * Make PEP-8 compatible and flake8 ready * Fixing bugs/feature requests * Architecture documentation * Functional testing against any reference I can find * The remaining edges of the protocol (that I think no one uses) - + +------------------------------------------------------------ +Development Instructions +------------------------------------------------------------ +The current code base is compatible with both py2 and py3. +Use make to perform a range of activities + +:: + + $ make + Makefile for pymodbus + + Usage: + + make install install the package in a virtual environment + make reset recreate the virtual environment + make check check coding style (PEP-8, PEP-257) + make test run the test suite, report coverage + make tox run the tests on all Python versions + make clean cleanup all temporary files + + ------------------------------------------------------------ License Information ------------------------------------------------------------ @@ -123,7 +160,8 @@ License Information Pymodbus is built on top of code developed from/by: * Copyright (c) 2001-2005 S.W.A.C. GmbH, Germany. * Copyright (c) 2001-2005 S.W.A.C. Bohemia s.r.o., Czech Republic. - * Hynek Petrak + + * Hynek Petrak, https://github.com/HynekPetrak * Twisted Matrix Released under the BSD License diff --git a/doc/sphinx/conf.py b/doc/sphinx/conf.py index e223ad565..3faaccc37 100644 --- a/doc/sphinx/conf.py +++ b/doc/sphinx/conf.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- # -# PyModbus documentation build configuration file, created by -# sphinx-quickstart on Tue Apr 14 19:11:16 2009. +# pymodbus documentation build configuration file, created by +# sphinx-quickstart on Fri May 26 10:10:53 2017. # -# This file is execfile()d with the current directory set to its containing dir. +# This file is execfile()d with the current directory set to its +# containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. @@ -11,188 +12,98 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os - # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.append(os.path.abspath('.')) +# +import os +import sys + +sys.path.insert(0, os.path.abspath(os.pardir)) -# -- General configuration ----------------------------------------------------- +from pymodbus import __version__, __author__, __maintainer__ -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage'] +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +# Sphinx extension module names. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.viewcode', + 'humanfriendly.sphinx', +] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] +# Sort members by the source order instead of alphabetically. +autodoc_member_order = 'bysource' + +# Paths that contain templates, relative to this directory. +templates_path = ['templates'] + # The suffix of source filenames. source_suffix = '.rst' -# The encoding of source files. -#source_encoding = 'utf-8' - # The master toctree document. master_doc = 'index' # General information about the project. -project = u'Pymodbus' -copyright = u'2009, Galen Collins' +project = u'pymodbus' +copyright = u'2017, {}, {}'.format(__author__, __maintainer__) +author = u'{}, {}'.format(__author__, __maintainer__) # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '1.0' +version = __version__ # The full version, including alpha/beta/rc tags. -release = '1.0' +release = __version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of documents that shouldn't be included in the build. -#unused_docs = [] - -# List of directories, relative to source directory, that shouldn't be searched -# for source files. -exclude_trees = ['build'] +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = 'en' -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['build', '_build', 'Thumbs.db', '.DS_Store'] # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -show_authors = True +add_function_parentheses = True # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# Refer to the Python standard library. +# From: http://twistedmatrix.com/trac/ticket/4582. +intersphinx_mapping = dict( + python=('https://docs.python.org/2', None), + capturer=('https://capturer.readthedocs.io/en/latest', None), + humanfriendly=('https://humanfriendly.readthedocs.io/en/latest', None), +) +# -- Options for HTML output -------------------------------------------------- -# -- Options for extensions --------------------------------------------------- -autodoc_default_flags = ['members', 'inherited-members', 'show-inheritance'] -autoclass_content = 'both' - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. Major themes that come with -# Sphinx are currently 'default' and 'sphinxdoc'. +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. html_theme = 'default' -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None +# Output file base name for HTML help builder. +htmlhelp_basename = 'pymodbusdoc' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_use_modindex = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = '' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'Pymodbus' - - -# -- Options for LaTeX output -------------------------------------------------- - -# The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' - -# The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'Pymodbus.tex', ur'Pymodbus Documentation', - ur'Galen Collins', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# Additional stuff for the LaTeX preamble. -#latex_preamble = '' - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_use_modindex = True diff --git a/doc/sphinx/doctrees/environment.pickle b/doc/sphinx/doctrees/environment.pickle new file mode 100644 index 000000000..97c03334b Binary files /dev/null and b/doc/sphinx/doctrees/environment.pickle differ diff --git a/doc/sphinx/doctrees/examples/asynchronous-client.doctree b/doc/sphinx/doctrees/examples/asynchronous-client.doctree new file mode 100644 index 000000000..bba09eeb2 Binary files /dev/null and b/doc/sphinx/doctrees/examples/asynchronous-client.doctree differ diff --git a/doc/sphinx/doctrees/examples/asynchronous-processor.doctree b/doc/sphinx/doctrees/examples/asynchronous-processor.doctree new file mode 100644 index 000000000..72c7735d1 Binary files /dev/null and b/doc/sphinx/doctrees/examples/asynchronous-processor.doctree differ diff --git a/doc/sphinx/doctrees/examples/asynchronous-server.doctree b/doc/sphinx/doctrees/examples/asynchronous-server.doctree new file mode 100644 index 000000000..f960e7245 Binary files /dev/null and b/doc/sphinx/doctrees/examples/asynchronous-server.doctree differ diff --git a/doc/sphinx/doctrees/examples/bcd-payload.doctree b/doc/sphinx/doctrees/examples/bcd-payload.doctree new file mode 100644 index 000000000..6f70e2287 Binary files /dev/null and b/doc/sphinx/doctrees/examples/bcd-payload.doctree differ diff --git a/doc/sphinx/doctrees/examples/bottle-frontend.doctree b/doc/sphinx/doctrees/examples/bottle-frontend.doctree new file mode 100644 index 000000000..239d7da26 Binary files /dev/null and b/doc/sphinx/doctrees/examples/bottle-frontend.doctree differ diff --git a/doc/sphinx/doctrees/examples/callback-server.doctree b/doc/sphinx/doctrees/examples/callback-server.doctree new file mode 100644 index 000000000..f0b5ac74a Binary files /dev/null and b/doc/sphinx/doctrees/examples/callback-server.doctree differ diff --git a/doc/sphinx/doctrees/examples/changing-framers.doctree b/doc/sphinx/doctrees/examples/changing-framers.doctree new file mode 100644 index 000000000..c7b4a142b Binary files /dev/null and b/doc/sphinx/doctrees/examples/changing-framers.doctree differ diff --git a/doc/sphinx/doctrees/examples/concurrent-client.doctree b/doc/sphinx/doctrees/examples/concurrent-client.doctree new file mode 100644 index 000000000..37ba5671d Binary files /dev/null and b/doc/sphinx/doctrees/examples/concurrent-client.doctree differ diff --git a/doc/sphinx/doctrees/examples/custom-datablock.doctree b/doc/sphinx/doctrees/examples/custom-datablock.doctree new file mode 100644 index 000000000..6342b4628 Binary files /dev/null and b/doc/sphinx/doctrees/examples/custom-datablock.doctree differ diff --git a/doc/sphinx/doctrees/examples/custom-message.doctree b/doc/sphinx/doctrees/examples/custom-message.doctree new file mode 100644 index 000000000..8779f1c1f Binary files /dev/null and b/doc/sphinx/doctrees/examples/custom-message.doctree differ diff --git a/doc/sphinx/doctrees/examples/database-datastore.doctree b/doc/sphinx/doctrees/examples/database-datastore.doctree new file mode 100644 index 000000000..b02a1f3c0 Binary files /dev/null and b/doc/sphinx/doctrees/examples/database-datastore.doctree differ diff --git a/doc/sphinx/doctrees/examples/gtk-frontend.doctree b/doc/sphinx/doctrees/examples/gtk-frontend.doctree new file mode 100644 index 000000000..d18a345c0 Binary files /dev/null and b/doc/sphinx/doctrees/examples/gtk-frontend.doctree differ diff --git a/doc/sphinx/doctrees/examples/index.doctree b/doc/sphinx/doctrees/examples/index.doctree new file mode 100644 index 000000000..8e0a735b2 Binary files /dev/null and b/doc/sphinx/doctrees/examples/index.doctree differ diff --git a/doc/sphinx/doctrees/examples/libmodbus-client.doctree b/doc/sphinx/doctrees/examples/libmodbus-client.doctree new file mode 100644 index 000000000..7ac317fa2 Binary files /dev/null and b/doc/sphinx/doctrees/examples/libmodbus-client.doctree differ diff --git a/doc/sphinx/doctrees/examples/message-generator.doctree b/doc/sphinx/doctrees/examples/message-generator.doctree new file mode 100644 index 000000000..f1c8e740c Binary files /dev/null and b/doc/sphinx/doctrees/examples/message-generator.doctree differ diff --git a/doc/sphinx/doctrees/examples/message-parser.doctree b/doc/sphinx/doctrees/examples/message-parser.doctree new file mode 100644 index 000000000..136037c4c Binary files /dev/null and b/doc/sphinx/doctrees/examples/message-parser.doctree differ diff --git a/doc/sphinx/doctrees/examples/modbus-logging.doctree b/doc/sphinx/doctrees/examples/modbus-logging.doctree new file mode 100644 index 000000000..c22c357c5 Binary files /dev/null and b/doc/sphinx/doctrees/examples/modbus-logging.doctree differ diff --git a/doc/sphinx/doctrees/examples/modbus-payload-server.doctree b/doc/sphinx/doctrees/examples/modbus-payload-server.doctree new file mode 100644 index 000000000..e91de84c1 Binary files /dev/null and b/doc/sphinx/doctrees/examples/modbus-payload-server.doctree differ diff --git a/doc/sphinx/doctrees/examples/modbus-payload.doctree b/doc/sphinx/doctrees/examples/modbus-payload.doctree new file mode 100644 index 000000000..37ad8a6ec Binary files /dev/null and b/doc/sphinx/doctrees/examples/modbus-payload.doctree differ diff --git a/doc/sphinx/doctrees/examples/modbus-scraper.doctree b/doc/sphinx/doctrees/examples/modbus-scraper.doctree new file mode 100644 index 000000000..c21e58e59 Binary files /dev/null and b/doc/sphinx/doctrees/examples/modbus-scraper.doctree differ diff --git a/doc/sphinx/doctrees/examples/modbus-simulator.doctree b/doc/sphinx/doctrees/examples/modbus-simulator.doctree new file mode 100644 index 000000000..3d5b66c9a Binary files /dev/null and b/doc/sphinx/doctrees/examples/modbus-simulator.doctree differ diff --git a/doc/sphinx/doctrees/examples/modicon-payload.doctree b/doc/sphinx/doctrees/examples/modicon-payload.doctree new file mode 100644 index 000000000..f8dbe9511 Binary files /dev/null and b/doc/sphinx/doctrees/examples/modicon-payload.doctree differ diff --git a/doc/sphinx/doctrees/examples/performance.doctree b/doc/sphinx/doctrees/examples/performance.doctree new file mode 100644 index 000000000..414dbca74 Binary files /dev/null and b/doc/sphinx/doctrees/examples/performance.doctree differ diff --git a/doc/sphinx/doctrees/examples/redis-datastore.doctree b/doc/sphinx/doctrees/examples/redis-datastore.doctree new file mode 100644 index 000000000..e5d0f9925 Binary files /dev/null and b/doc/sphinx/doctrees/examples/redis-datastore.doctree differ diff --git a/doc/sphinx/doctrees/examples/remote-server-context.doctree b/doc/sphinx/doctrees/examples/remote-server-context.doctree new file mode 100644 index 000000000..0d823732d Binary files /dev/null and b/doc/sphinx/doctrees/examples/remote-server-context.doctree differ diff --git a/doc/sphinx/doctrees/examples/serial-forwarder.doctree b/doc/sphinx/doctrees/examples/serial-forwarder.doctree new file mode 100644 index 000000000..9f3e19026 Binary files /dev/null and b/doc/sphinx/doctrees/examples/serial-forwarder.doctree differ diff --git a/doc/sphinx/doctrees/examples/synchronous-client-ext.doctree b/doc/sphinx/doctrees/examples/synchronous-client-ext.doctree new file mode 100644 index 000000000..91674e5f6 Binary files /dev/null and b/doc/sphinx/doctrees/examples/synchronous-client-ext.doctree differ diff --git a/doc/sphinx/doctrees/examples/synchronous-client.doctree b/doc/sphinx/doctrees/examples/synchronous-client.doctree new file mode 100644 index 000000000..f7f7b1691 Binary files /dev/null and b/doc/sphinx/doctrees/examples/synchronous-client.doctree differ diff --git a/doc/sphinx/doctrees/examples/synchronous-server.doctree b/doc/sphinx/doctrees/examples/synchronous-server.doctree new file mode 100644 index 000000000..7959497a8 Binary files /dev/null and b/doc/sphinx/doctrees/examples/synchronous-server.doctree differ diff --git a/doc/sphinx/doctrees/examples/thread-safe-datastore.doctree b/doc/sphinx/doctrees/examples/thread-safe-datastore.doctree new file mode 100644 index 000000000..606ce7966 Binary files /dev/null and b/doc/sphinx/doctrees/examples/thread-safe-datastore.doctree differ diff --git a/doc/sphinx/doctrees/examples/tk-frontend.doctree b/doc/sphinx/doctrees/examples/tk-frontend.doctree new file mode 100644 index 000000000..c7f6716b3 Binary files /dev/null and b/doc/sphinx/doctrees/examples/tk-frontend.doctree differ diff --git a/doc/sphinx/doctrees/examples/updating-server.doctree b/doc/sphinx/doctrees/examples/updating-server.doctree new file mode 100644 index 000000000..3d6b43252 Binary files /dev/null and b/doc/sphinx/doctrees/examples/updating-server.doctree differ diff --git a/doc/sphinx/doctrees/examples/wx-frontend.doctree b/doc/sphinx/doctrees/examples/wx-frontend.doctree new file mode 100644 index 000000000..e3f93f075 Binary files /dev/null and b/doc/sphinx/doctrees/examples/wx-frontend.doctree differ diff --git a/doc/sphinx/doctrees/index.doctree b/doc/sphinx/doctrees/index.doctree new file mode 100644 index 000000000..1bac3bb45 Binary files /dev/null and b/doc/sphinx/doctrees/index.doctree differ diff --git a/doc/sphinx/doctrees/library/async-client.doctree b/doc/sphinx/doctrees/library/async-client.doctree new file mode 100644 index 000000000..549c6a58e Binary files /dev/null and b/doc/sphinx/doctrees/library/async-client.doctree differ diff --git a/doc/sphinx/doctrees/library/async-server.doctree b/doc/sphinx/doctrees/library/async-server.doctree new file mode 100644 index 000000000..0c5f143c0 Binary files /dev/null and b/doc/sphinx/doctrees/library/async-server.doctree differ diff --git a/doc/sphinx/doctrees/library/bit-read-message.doctree b/doc/sphinx/doctrees/library/bit-read-message.doctree new file mode 100644 index 000000000..c850597c5 Binary files /dev/null and b/doc/sphinx/doctrees/library/bit-read-message.doctree differ diff --git a/doc/sphinx/doctrees/library/bit-write-message.doctree b/doc/sphinx/doctrees/library/bit-write-message.doctree new file mode 100644 index 000000000..41ee0ee23 Binary files /dev/null and b/doc/sphinx/doctrees/library/bit-write-message.doctree differ diff --git a/doc/sphinx/doctrees/library/client-common.doctree b/doc/sphinx/doctrees/library/client-common.doctree new file mode 100644 index 000000000..56437d3ea Binary files /dev/null and b/doc/sphinx/doctrees/library/client-common.doctree differ diff --git a/doc/sphinx/doctrees/library/constants.doctree b/doc/sphinx/doctrees/library/constants.doctree new file mode 100644 index 000000000..b606376fb Binary files /dev/null and b/doc/sphinx/doctrees/library/constants.doctree differ diff --git a/doc/sphinx/doctrees/library/datastore/context.doctree b/doc/sphinx/doctrees/library/datastore/context.doctree new file mode 100644 index 000000000..9c8c53ff7 Binary files /dev/null and b/doc/sphinx/doctrees/library/datastore/context.doctree differ diff --git a/doc/sphinx/doctrees/library/datastore/index.doctree b/doc/sphinx/doctrees/library/datastore/index.doctree new file mode 100644 index 000000000..9159561b4 Binary files /dev/null and b/doc/sphinx/doctrees/library/datastore/index.doctree differ diff --git a/doc/sphinx/doctrees/library/datastore/remote.doctree b/doc/sphinx/doctrees/library/datastore/remote.doctree new file mode 100644 index 000000000..acae54db8 Binary files /dev/null and b/doc/sphinx/doctrees/library/datastore/remote.doctree differ diff --git a/doc/sphinx/doctrees/library/datastore/store.doctree b/doc/sphinx/doctrees/library/datastore/store.doctree new file mode 100644 index 000000000..1ebacf7de Binary files /dev/null and b/doc/sphinx/doctrees/library/datastore/store.doctree differ diff --git a/doc/sphinx/doctrees/library/device.doctree b/doc/sphinx/doctrees/library/device.doctree new file mode 100644 index 000000000..b8952ec2c Binary files /dev/null and b/doc/sphinx/doctrees/library/device.doctree differ diff --git a/doc/sphinx/doctrees/library/diag-message.doctree b/doc/sphinx/doctrees/library/diag-message.doctree new file mode 100644 index 000000000..e21c8c32a Binary files /dev/null and b/doc/sphinx/doctrees/library/diag-message.doctree differ diff --git a/doc/sphinx/doctrees/library/events.doctree b/doc/sphinx/doctrees/library/events.doctree new file mode 100644 index 000000000..b8773d81f Binary files /dev/null and b/doc/sphinx/doctrees/library/events.doctree differ diff --git a/doc/sphinx/doctrees/library/exceptions.doctree b/doc/sphinx/doctrees/library/exceptions.doctree new file mode 100644 index 000000000..ae59f9a71 Binary files /dev/null and b/doc/sphinx/doctrees/library/exceptions.doctree differ diff --git a/doc/sphinx/doctrees/library/factory.doctree b/doc/sphinx/doctrees/library/factory.doctree new file mode 100644 index 000000000..6f955affc Binary files /dev/null and b/doc/sphinx/doctrees/library/factory.doctree differ diff --git a/doc/sphinx/doctrees/library/file-message.doctree b/doc/sphinx/doctrees/library/file-message.doctree new file mode 100644 index 000000000..01b9070c4 Binary files /dev/null and b/doc/sphinx/doctrees/library/file-message.doctree differ diff --git a/doc/sphinx/doctrees/library/index.doctree b/doc/sphinx/doctrees/library/index.doctree new file mode 100644 index 000000000..5afbde909 Binary files /dev/null and b/doc/sphinx/doctrees/library/index.doctree differ diff --git a/doc/sphinx/doctrees/library/interfaces.doctree b/doc/sphinx/doctrees/library/interfaces.doctree new file mode 100644 index 000000000..4ac498043 Binary files /dev/null and b/doc/sphinx/doctrees/library/interfaces.doctree differ diff --git a/doc/sphinx/doctrees/library/mei-message.doctree b/doc/sphinx/doctrees/library/mei-message.doctree new file mode 100644 index 000000000..5689b22ad Binary files /dev/null and b/doc/sphinx/doctrees/library/mei-message.doctree differ diff --git a/doc/sphinx/doctrees/library/other-message.doctree b/doc/sphinx/doctrees/library/other-message.doctree new file mode 100644 index 000000000..707b782c9 Binary files /dev/null and b/doc/sphinx/doctrees/library/other-message.doctree differ diff --git a/doc/sphinx/doctrees/library/payload.doctree b/doc/sphinx/doctrees/library/payload.doctree new file mode 100644 index 000000000..35bce27dd Binary files /dev/null and b/doc/sphinx/doctrees/library/payload.doctree differ diff --git a/doc/sphinx/doctrees/library/pdu.doctree b/doc/sphinx/doctrees/library/pdu.doctree new file mode 100644 index 000000000..b148c8e71 Binary files /dev/null and b/doc/sphinx/doctrees/library/pdu.doctree differ diff --git a/doc/sphinx/doctrees/library/pymodbus.doctree b/doc/sphinx/doctrees/library/pymodbus.doctree new file mode 100644 index 000000000..340baffdd Binary files /dev/null and b/doc/sphinx/doctrees/library/pymodbus.doctree differ diff --git a/doc/sphinx/doctrees/library/register-read-message.doctree b/doc/sphinx/doctrees/library/register-read-message.doctree new file mode 100644 index 000000000..d0ca283c4 Binary files /dev/null and b/doc/sphinx/doctrees/library/register-read-message.doctree differ diff --git a/doc/sphinx/doctrees/library/register-write-message.doctree b/doc/sphinx/doctrees/library/register-write-message.doctree new file mode 100644 index 000000000..b28e508a3 Binary files /dev/null and b/doc/sphinx/doctrees/library/register-write-message.doctree differ diff --git a/doc/sphinx/doctrees/library/sync-client.doctree b/doc/sphinx/doctrees/library/sync-client.doctree new file mode 100644 index 000000000..1bd7de384 Binary files /dev/null and b/doc/sphinx/doctrees/library/sync-client.doctree differ diff --git a/doc/sphinx/doctrees/library/sync-server.doctree b/doc/sphinx/doctrees/library/sync-server.doctree new file mode 100644 index 000000000..e8946211e Binary files /dev/null and b/doc/sphinx/doctrees/library/sync-server.doctree differ diff --git a/doc/sphinx/doctrees/library/transaction.doctree b/doc/sphinx/doctrees/library/transaction.doctree new file mode 100644 index 000000000..f1388e7f5 Binary files /dev/null and b/doc/sphinx/doctrees/library/transaction.doctree differ diff --git a/doc/sphinx/doctrees/library/utilities.doctree b/doc/sphinx/doctrees/library/utilities.doctree new file mode 100644 index 000000000..0749c3e1d Binary files /dev/null and b/doc/sphinx/doctrees/library/utilities.doctree differ diff --git a/doc/sphinx/examples/bottle-frontend.rst b/doc/sphinx/examples/bottle-frontend.rst index 662f2a7a0..95507c5a8 100644 --- a/doc/sphinx/examples/bottle-frontend.rst +++ b/doc/sphinx/examples/bottle-frontend.rst @@ -18,5 +18,5 @@ run behind gunicorn, cherrypi, etc wsgi containers. Main Program -------------------------------------------------- -.. literalinclude:: ../../../examples/gui/web/frontend.py +.. literalinclude:: ../../../examples/gui/bottle/frontend.py diff --git a/doc/sphinx/examples/index.rst b/doc/sphinx/examples/index.rst index 10f265ecf..a41f01129 100644 --- a/doc/sphinx/examples/index.rst +++ b/doc/sphinx/examples/index.rst @@ -15,8 +15,10 @@ Example Library Code asynchronous-server asynchronous-processor custom-message + custom-datablock modbus-logging modbus-payload + modbus-payload-server synchronous-client synchronous-client-ext synchronous-server diff --git a/doc/sphinx/examples/modbus-payload-server.rst b/doc/sphinx/examples/modbus-payload-server.rst new file mode 100644 index 000000000..9144f0f53 --- /dev/null +++ b/doc/sphinx/examples/modbus-payload-server.rst @@ -0,0 +1,6 @@ +================================================== +Modbus Payload Server Context Building Example +================================================== + +.. literalinclude:: ../../../examples/common/modbus-payload-server.py + diff --git a/doc/sphinx/html/.buildinfo b/doc/sphinx/html/.buildinfo new file mode 100644 index 000000000..72a1539e3 --- /dev/null +++ b/doc/sphinx/html/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: a869ccec57cd788c41a89393439ff025 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/doc/sphinx/html/_modules/index.html b/doc/sphinx/html/_modules/index.html new file mode 100644 index 000000000..1133838f3 --- /dev/null +++ b/doc/sphinx/html/_modules/index.html @@ -0,0 +1,114 @@ + + + + + + + + Overview: module code — pymodbus 1.3.0.rc2 documentation + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/sphinx/html/_modules/pymodbus/bit_read_message.html b/doc/sphinx/html/_modules/pymodbus/bit_read_message.html new file mode 100644 index 000000000..53fb041ba --- /dev/null +++ b/doc/sphinx/html/_modules/pymodbus/bit_read_message.html @@ -0,0 +1,331 @@ + + + + + + + + pymodbus.bit_read_message — pymodbus 1.3.0.rc2 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for pymodbus.bit_read_message

+"""
+Bit Reading Request/Response messages
+--------------------------------------
+
+"""
+import struct
+from pymodbus.pdu import ModbusRequest
+from pymodbus.pdu import ModbusResponse
+from pymodbus.pdu import ModbusExceptions as merror
+from pymodbus.utilities import pack_bitstring, unpack_bitstring
+from pymodbus.compat import byte2int
+
+
+
[docs]class ReadBitsRequestBase(ModbusRequest): + ''' Base class for Messages Requesting bit values ''' + + _rtu_frame_size = 8 +
[docs] def __init__(self, address, count, **kwargs): + ''' Initializes the read request data + + :param address: The start address to read from + :param count: The number of bits after 'address' to read + ''' + ModbusRequest.__init__(self, **kwargs) + self.address = address + self.count = count
+ +
[docs] def encode(self): + ''' Encodes a request pdu + + :returns: The encoded pdu + ''' + return struct.pack('>HH', self.address, self.count)
+ +
[docs] def decode(self, data): + ''' Decodes a request pdu + + :param data: The packet data to decode + ''' + self.address, self.count = struct.unpack('>HH', data)
+ +
[docs] def get_response_pdu_size(self): + """ + Func_code (1 byte) + Byte Count(1 byte) + Quantity of Coils (n Bytes) + :return: + """ + return 1 + 1 + self.count
+ +
[docs] def __str__(self): + ''' Returns a string representation of the instance + + :returns: A string representation of the instance + ''' + return "ReadBitRequest(%d,%d)" % (self.address, self.count)
+ + +
[docs]class ReadBitsResponseBase(ModbusResponse): + ''' Base class for Messages responding to bit-reading values ''' + + _rtu_byte_count_pos = 2 + +
[docs] def __init__(self, values, **kwargs): + ''' Initializes a new instance + + :param values: The requested values to be returned + ''' + ModbusResponse.__init__(self, **kwargs) + self.bits = values or []
+ +
[docs] def encode(self): + ''' Encodes response pdu + + :returns: The encoded packet message + ''' + result = pack_bitstring(self.bits) + packet = struct.pack(">B", len(result)) + result + return packet
+ +
[docs] def decode(self, data): + ''' Decodes response pdu + + :param data: The packet data to decode + ''' + self.byte_count = byte2int(data[0]) + self.bits = unpack_bitstring(data[1:])
+ +
[docs] def setBit(self, address, value=1): + ''' Helper function to set the specified bit + + :param address: The bit to set + :param value: The value to set the bit to + ''' + self.bits[address] = (value != 0)
+ +
[docs] def resetBit(self, address): + ''' Helper function to set the specified bit to 0 + + :param address: The bit to reset + ''' + self.setBit(address, 0)
+ +
[docs] def getBit(self, address): + ''' Helper function to get the specified bit's value + + :param address: The bit to query + :returns: The value of the requested bit + ''' + return self.bits[address]
+ +
[docs] def __str__(self): + ''' Returns a string representation of the instance + + :returns: A string representation of the instance + ''' + return "ReadBitResponse(%d)" % len(self.bits)
+ + +
[docs]class ReadCoilsRequest(ReadBitsRequestBase): + ''' + This function code is used to read from 1 to 2000(0x7d0) contiguous status + of coils in a remote device. The Request PDU specifies the starting + address, ie the address of the first coil specified, and the number of + coils. In the PDU Coils are addressed starting at zero. Therefore coils + numbered 1-16 are addressed as 0-15. + ''' + function_code = 1 + +
[docs] def __init__(self, address=None, count=None, **kwargs): + ''' Initializes a new instance + + :param address: The address to start reading from + :param count: The number of bits to read + ''' + ReadBitsRequestBase.__init__(self, address, count, **kwargs)
+ +
[docs] def execute(self, context): + ''' Run a read coils request against a datastore + + Before running the request, we make sure that the request is in + the max valid range (0x001-0x7d0). Next we make sure that the + request is valid against the current datastore. + + :param context: The datastore to request from + :returns: The initializes response message, exception message otherwise + ''' + if not (1 <= self.count <= 0x7d0): + return self.doException(merror.IllegalValue) + if not context.validate(self.function_code, self.address, self.count): + return self.doException(merror.IllegalAddress) + values = context.getValues(self.function_code, self.address, self.count) + return ReadCoilsResponse(values)
+ + +
[docs]class ReadCoilsResponse(ReadBitsResponseBase): + ''' + The coils in the response message are packed as one coil per bit of + the data field. Status is indicated as 1= ON and 0= OFF. The LSB of the + first data byte contains the output addressed in the query. The other + coils follow toward the high order end of this byte, and from low order + to high order in subsequent bytes. + + If the returned output quantity is not a multiple of eight, the + remaining bits in the final data byte will be padded with zeros + (toward the high order end of the byte). The Byte Count field specifies + the quantity of complete bytes of data. + ''' + function_code = 1 + +
[docs] def __init__(self, values=None, **kwargs): + ''' Intializes a new instance + + :param values: The request values to respond with + ''' + ReadBitsResponseBase.__init__(self, values, **kwargs)
+ + +
[docs]class ReadDiscreteInputsRequest(ReadBitsRequestBase): + ''' + This function code is used to read from 1 to 2000(0x7d0) contiguous status + of discrete inputs in a remote device. The Request PDU specifies the + starting address, ie the address of the first input specified, and the + number of inputs. In the PDU Discrete Inputs are addressed starting at + zero. Therefore Discrete inputs numbered 1-16 are addressed as 0-15. + ''' + function_code = 2 + +
[docs] def __init__(self, address=None, count=None, **kwargs): + ''' Intializes a new instance + + :param address: The address to start reading from + :param count: The number of bits to read + ''' + ReadBitsRequestBase.__init__(self, address, count, **kwargs)
+ +
[docs] def execute(self, context): + ''' Run a read discrete input request against a datastore + + Before running the request, we make sure that the request is in + the max valid range (0x001-0x7d0). Next we make sure that the + request is valid against the current datastore. + + :param context: The datastore to request from + :returns: The initializes response message, exception message otherwise + ''' + if not (1 <= self.count <= 0x7d0): + return self.doException(merror.IllegalValue) + if not context.validate(self.function_code, self.address, self.count): + return self.doException(merror.IllegalAddress) + values = context.getValues(self.function_code, self.address, self.count) + return ReadDiscreteInputsResponse(values)
+ + +
[docs]class ReadDiscreteInputsResponse(ReadBitsResponseBase): + ''' + The discrete inputs in the response message are packed as one input per + bit of the data field. Status is indicated as 1= ON; 0= OFF. The LSB of + the first data byte contains the input addressed in the query. The other + inputs follow toward the high order end of this byte, and from low order + to high order in subsequent bytes. + + If the returned input quantity is not a multiple of eight, the + remaining bits in the final data byte will be padded with zeros + (toward the high order end of the byte). The Byte Count field specifies + the quantity of complete bytes of data. + ''' + function_code = 2 + +
[docs] def __init__(self, values=None, **kwargs): + ''' Intializes a new instance + + :param values: The request values to respond with + ''' + ReadBitsResponseBase.__init__(self, values, **kwargs)
+ +#---------------------------------------------------------------------------# +# Exported symbols +#---------------------------------------------------------------------------# +__all__ = [ + "ReadCoilsRequest", "ReadCoilsResponse", + "ReadDiscreteInputsRequest", "ReadDiscreteInputsResponse", +] +
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/doc/sphinx/html/_modules/pymodbus/bit_write_message.html b/doc/sphinx/html/_modules/pymodbus/bit_write_message.html new file mode 100644 index 000000000..c1a379119 --- /dev/null +++ b/doc/sphinx/html/_modules/pymodbus/bit_write_message.html @@ -0,0 +1,353 @@ + + + + + + + + pymodbus.bit_write_message — pymodbus 1.3.0.rc2 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for pymodbus.bit_write_message

+"""
+Bit Writing Request/Response
+------------------------------
+
+TODO write mask request/response
+"""
+import struct
+from pymodbus.constants import ModbusStatus
+from pymodbus.pdu import ModbusRequest
+from pymodbus.pdu import ModbusResponse
+from pymodbus.pdu import ModbusExceptions as merror
+from pymodbus.utilities import pack_bitstring, unpack_bitstring
+
+#---------------------------------------------------------------------------#
+# Local Constants
+#---------------------------------------------------------------------------#
+# These are defined in the spec to turn a coil on/off
+#---------------------------------------------------------------------------#
+_turn_coil_on  = struct.pack(">H", ModbusStatus.On)
+_turn_coil_off = struct.pack(">H", ModbusStatus.Off)
+
+
+
[docs]class WriteSingleCoilRequest(ModbusRequest): + ''' + This function code is used to write a single output to either ON or OFF + in a remote device. + + The requested ON/OFF state is specified by a constant in the request + data field. A value of FF 00 hex requests the output to be ON. A value + of 00 00 requests it to be OFF. All other values are illegal and will + not affect the output. + + The Request PDU specifies the address of the coil to be forced. Coils + are addressed starting at zero. Therefore coil numbered 1 is addressed + as 0. The requested ON/OFF state is specified by a constant in the Coil + Value field. A value of 0XFF00 requests the coil to be ON. A value of + 0X0000 requests the coil to be off. All other values are illegal and + will not affect the coil. + ''' + function_code = 5 + _rtu_frame_size = 8 + +
[docs] def __init__(self, address=None, value=None, **kwargs): + ''' Initializes a new instance + + :param address: The variable address to write + :param value: The value to write at address + ''' + ModbusRequest.__init__(self, **kwargs) + self.address = address + self.value = bool(value)
+ +
[docs] def encode(self): + ''' Encodes write coil request + + :returns: The byte encoded message + ''' + result = struct.pack('>H', self.address) + if self.value: result += _turn_coil_on + else: result += _turn_coil_off + return result
+ +
[docs] def decode(self, data): + ''' Decodes a write coil request + + :param data: The packet data to decode + ''' + self.address, value = struct.unpack('>HH', data) + self.value = (value == ModbusStatus.On)
+ +
[docs] def execute(self, context): + ''' Run a write coil request against a datastore + + :param context: The datastore to request from + :returns: The populated response or exception message + ''' + #if self.value not in [ModbusStatus.Off, ModbusStatus.On]: + # return self.doException(merror.IllegalValue) + if not context.validate(self.function_code, self.address, 1): + return self.doException(merror.IllegalAddress) + + context.setValues(self.function_code, self.address, [self.value]) + values = context.getValues(self.function_code, self.address, 1) + return WriteSingleCoilResponse(self.address, values[0])
+ +
[docs] def get_response_pdu_size(self): + """ + Func_code (1 byte) + Output Address (2 byte) + Output Value (2 Bytes) + :return: + """ + return 1 + 2 + 2
+ +
[docs] def __str__(self): + ''' Returns a string representation of the instance + + :return: A string representation of the instance + ''' + return "WriteCoilRequest(%d, %s) => " % (self.address, self.value)
+ + +
[docs]class WriteSingleCoilResponse(ModbusResponse): + ''' + The normal response is an echo of the request, returned after the coil + state has been written. + ''' + function_code = 5 + _rtu_frame_size = 8 + +
[docs] def __init__(self, address=None, value=None, **kwargs): + ''' Initializes a new instance + + :param address: The variable address written to + :param value: The value written at address + ''' + ModbusResponse.__init__(self, **kwargs) + self.address = address + self.value = value
+ +
[docs] def encode(self): + ''' Encodes write coil response + + :return: The byte encoded message + ''' + result = struct.pack('>H', self.address) + if self.value: result += _turn_coil_on + else: result += _turn_coil_off + return result
+ +
[docs] def decode(self, data): + ''' Decodes a write coil response + + :param data: The packet data to decode + ''' + self.address, value = struct.unpack('>HH', data) + self.value = (value == ModbusStatus.On)
+ +
[docs] def __str__(self): + ''' Returns a string representation of the instance + + :returns: A string representation of the instance + ''' + return "WriteCoilResponse(%d) => %d" % (self.address, self.value)
+ + +
[docs]class WriteMultipleCoilsRequest(ModbusRequest): + ''' + "This function code is used to force each coil in a sequence of coils to + either ON or OFF in a remote device. The Request PDU specifies the coil + references to be forced. Coils are addressed starting at zero. Therefore + coil numbered 1 is addressed as 0. + + The requested ON/OFF states are specified by contents of the request + data field. A logical '1' in a bit position of the field requests the + corresponding output to be ON. A logical '0' requests it to be OFF." + ''' + function_code = 15 + _rtu_byte_count_pos = 6 + +
[docs] def __init__(self, address=None, values=None, **kwargs): + ''' Initializes a new instance + + :param address: The starting request address + :param values: The values to write + ''' + ModbusRequest.__init__(self, **kwargs) + self.address = address + if not values: values = [] + elif not hasattr(values, '__iter__'): values = [values] + self.values = values + self.byte_count = (len(self.values) + 7) // 8
+ +
[docs] def encode(self): + ''' Encodes write coils request + + :returns: The byte encoded message + ''' + count = len(self.values) + self.byte_count = (count + 7) // 8 + packet = struct.pack('>HHB', self.address, count, self.byte_count) + packet += pack_bitstring(self.values) + return packet
+ +
[docs] def decode(self, data): + ''' Decodes a write coils request + + :param data: The packet data to decode + ''' + self.address, count, self.byte_count = struct.unpack('>HHB', data[0:5]) + values = unpack_bitstring(data[5:]) + self.values = values[:count]
+ +
[docs] def execute(self, context): + ''' Run a write coils request against a datastore + + :param context: The datastore to request from + :returns: The populated response or exception message + ''' + count = len(self.values) + if not (1 <= count <= 0x07b0): + return self.doException(merror.IllegalValue) + if (self.byte_count != (count + 7) // 8): + return self.doException(merror.IllegalValue) + if not context.validate(self.function_code, self.address, count): + return self.doException(merror.IllegalAddress) + + context.setValues(self.function_code, self.address, self.values) + return WriteMultipleCoilsResponse(self.address, count)
+ +
[docs] def __str__(self): + ''' Returns a string representation of the instance + + :returns: A string representation of the instance + ''' + params = (self.address, len(self.values)) + return "WriteNCoilRequest (%d) => %d " % params
+ + +
[docs]class WriteMultipleCoilsResponse(ModbusResponse): + ''' + The normal response returns the function code, starting address, and + quantity of coils forced. + ''' + function_code = 15 + _rtu_frame_size = 8 + +
[docs] def __init__(self, address=None, count=None, **kwargs): + ''' Initializes a new instance + + :param address: The starting variable address written to + :param count: The number of values written + ''' + ModbusResponse.__init__(self, **kwargs) + self.address = address + self.count = count
+ +
[docs] def encode(self): + ''' Encodes write coils response + + :returns: The byte encoded message + ''' + return struct.pack('>HH', self.address, self.count)
+ +
[docs] def decode(self, data): + ''' Decodes a write coils response + + :param data: The packet data to decode + ''' + self.address, self.count = struct.unpack('>HH', data)
+ +
[docs] def __str__(self): + ''' Returns a string representation of the instance + + :returns: A string representation of the instance + ''' + return "WriteNCoilResponse(%d, %d)" % (self.address, self.count)
+ +#---------------------------------------------------------------------------# +# Exported symbols +#---------------------------------------------------------------------------# +__all__ = [ + "WriteSingleCoilRequest", "WriteSingleCoilResponse", + "WriteMultipleCoilsRequest", "WriteMultipleCoilsResponse", +] +
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/doc/sphinx/html/_modules/pymodbus/client/async.html b/doc/sphinx/html/_modules/pymodbus/client/async.html new file mode 100644 index 000000000..51c879075 --- /dev/null +++ b/doc/sphinx/html/_modules/pymodbus/client/async.html @@ -0,0 +1,305 @@ + + + + + + + + pymodbus.client.async — pymodbus 1.3.0.rc2 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for pymodbus.client.async

+"""
+Implementation of a Modbus Client Using Twisted
+--------------------------------------------------
+
+Example run::
+
+    from twisted.internet import reactor, protocol
+    from pymodbus.client.async import ModbusClientProtocol
+
+    def printResult(result):
+        print "Result: %d" % result.bits[0]
+
+    def process(client):
+        result = client.write_coil(1, True)
+        result.addCallback(printResult)
+        reactor.callLater(1, reactor.stop)
+
+    defer = protocol.ClientCreator(reactor, ModbusClientProtocol
+            ).connectTCP("localhost", 502)
+    defer.addCallback(process)
+
+Another example::
+
+    from twisted.internet import reactor
+    from pymodbus.client.async import ModbusClientFactory
+
+    def process():
+        factory = reactor.connectTCP("localhost", 502, ModbusClientFactory())
+        reactor.stop()
+
+    if __name__ == "__main__":
+       reactor.callLater(1, process)
+       reactor.run()
+"""
+from twisted.internet import defer, protocol
+from pymodbus.factory import ClientDecoder
+from pymodbus.exceptions import ConnectionException
+from pymodbus.transaction import ModbusSocketFramer
+from pymodbus.transaction import FifoTransactionManager
+from pymodbus.transaction import DictTransactionManager
+from pymodbus.client.common import ModbusClientMixin
+from twisted.python.failure import Failure
+
+#---------------------------------------------------------------------------#
+# Logging
+#---------------------------------------------------------------------------#
+import logging
+_logger = logging.getLogger(__name__)
+
+
+#---------------------------------------------------------------------------#
+# Connected Client Protocols
+#---------------------------------------------------------------------------#
+
[docs]class ModbusClientProtocol(protocol.Protocol, ModbusClientMixin): + ''' + This represents the base modbus client protocol. All the application + layer code is deferred to a higher level wrapper. + ''' + +
[docs] def __init__(self, framer=None, **kwargs): + ''' Initializes the framer module + + :param framer: The framer to use for the protocol + ''' + self._connected = False + self.framer = framer or ModbusSocketFramer(ClientDecoder()) + if isinstance(self.framer, ModbusSocketFramer): + self.transaction = DictTransactionManager(self, **kwargs) + else: self.transaction = FifoTransactionManager(self, **kwargs)
+ +
[docs] def connectionMade(self): + ''' Called upon a successful client connection. + ''' + _logger.debug("Client connected to modbus server") + self._connected = True
+ +
[docs] def connectionLost(self, reason): + ''' Called upon a client disconnect + + :param reason: The reason for the disconnect + ''' + _logger.debug("Client disconnected from modbus server: %s" % reason) + self._connected = False + for tid in list(self.transaction): + self.transaction.getTransaction(tid).errback(Failure( + ConnectionException('Connection lost during request')))
+ +
[docs] def dataReceived(self, data): + ''' Get response, check for valid message, decode result + + :param data: The data returned from the server + ''' + self.framer.processIncomingPacket(data, self._handleResponse)
+ +
[docs] def execute(self, request): + ''' Starts the producer to send the next request to + consumer.write(Frame(request)) + ''' + request.transaction_id = self.transaction.getNextTID() + packet = self.framer.buildPacket(request) + self.transport.write(packet) + return self._buildResponse(request.transaction_id)
+ +
[docs] def _handleResponse(self, reply): + ''' Handle the processed response and link to correct deferred + + :param reply: The reply to process + ''' + if reply is not None: + tid = reply.transaction_id + handler = self.transaction.getTransaction(tid) + if handler: + handler.callback(reply) + else: _logger.debug("Unrequested message: " + str(reply))
+ +
[docs] def _buildResponse(self, tid): + ''' Helper method to return a deferred response + for the current request. + + :param tid: The transaction identifier for this response + :returns: A defer linked to the latest request + ''' + if not self._connected: + return defer.fail(Failure( + ConnectionException('Client is not connected'))) + + d = defer.Deferred() + self.transaction.addTransaction(d, tid) + return d
+ + #----------------------------------------------------------------------# + # Extra Functions + #----------------------------------------------------------------------# + #if send_failed: + # if self.retry > 0: + # deferLater(clock, self.delay, send, message) + # self.retry -= 1 + + +#---------------------------------------------------------------------------# +# Not Connected Client Protocol +#---------------------------------------------------------------------------# +class ModbusUdpClientProtocol(protocol.DatagramProtocol, ModbusClientMixin): + ''' + This represents the base modbus client protocol. All the application + layer code is deferred to a higher level wrapper. + ''' + + def __init__(self, framer=None, **kwargs): + ''' Initializes the framer module + + :param framer: The framer to use for the protocol + ''' + self.framer = framer or ModbusSocketFramer(ClientDecoder()) + if isinstance(self.framer, ModbusSocketFramer): + self.transaction = DictTransactionManager(self, **kwargs) + else: self.transaction = FifoTransactionManager(self, **kwargs) + + def datagramReceived(self, data, params): + ''' Get response, check for valid message, decode result + + :param data: The data returned from the server + :param params: The host parameters sending the datagram + ''' + _logger.debug("Datagram from: %s:%d" % params) + self.framer.processIncomingPacket(data, self._handleResponse) + + def execute(self, request): + ''' Starts the producer to send the next request to + consumer.write(Frame(request)) + ''' + request.transaction_id = self.transaction.getNextTID() + packet = self.framer.buildPacket(request) + self.transport.write(packet) + return self._buildResponse(request.transaction_id) + + def _handleResponse(self, reply): + ''' Handle the processed response and link to correct deferred + + :param reply: The reply to process + ''' + if reply is not None: + tid = reply.transaction_id + handler = self.transaction.getTransaction(tid) + if handler: + handler.callback(reply) + else: _logger.debug("Unrequested message: " + str(reply)) + + def _buildResponse(self, tid): + ''' Helper method to return a deferred response + for the current request. + + :param tid: The transaction identifier for this response + :returns: A defer linked to the latest request + ''' + d = defer.Deferred() + self.transaction.addTransaction(d, tid) + return d + + +#---------------------------------------------------------------------------# +# Client Factories +#---------------------------------------------------------------------------# +
[docs]class ModbusClientFactory(protocol.ReconnectingClientFactory): + ''' Simple client protocol factory ''' + + protocol = ModbusClientProtocol
+ +#---------------------------------------------------------------------------# +# Exported symbols +#---------------------------------------------------------------------------# +__all__ = [ + "ModbusClientProtocol", "ModbusUdpClientProtocol", + "ModbusClientFactory", +] +
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/doc/sphinx/html/_modules/pymodbus/client/common.html b/doc/sphinx/html/_modules/pymodbus/client/common.html new file mode 100644 index 000000000..5eec0cabb --- /dev/null +++ b/doc/sphinx/html/_modules/pymodbus/client/common.html @@ -0,0 +1,240 @@ + + + + + + + + pymodbus.client.common — pymodbus 1.3.0.rc2 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for pymodbus.client.common

+'''
+Modbus Client Common
+----------------------------------
+
+This is a common client mixin that can be used by
+both the synchronous and asynchronous clients to
+simplify the interface.
+'''
+from pymodbus.bit_read_message import *
+from pymodbus.bit_write_message import *
+from pymodbus.register_read_message import *
+from pymodbus.register_write_message import *
+from pymodbus.diag_message import *
+from pymodbus.file_message import *
+from pymodbus.other_message import *
+
+
+
[docs]class ModbusClientMixin(object): + ''' + This is a modbus client mixin that provides additional factory + methods for all the current modbus methods. This can be used + instead of the normal pattern of:: + + # instead of this + client = ModbusClient(...) + request = ReadCoilsRequest(1,10) + response = client.execute(request) + + # now like this + client = ModbusClient(...) + response = client.read_coils(1, 10) + ''' + +
[docs] def read_coils(self, address, count=1, **kwargs): + ''' + + :param address: The starting address to read from + :param count: The number of coils to read + :param unit: The slave unit this request is targeting + :returns: A deferred response handle + ''' + request = ReadCoilsRequest(address, count, **kwargs) + return self.execute(request)
+ +
[docs] def read_discrete_inputs(self, address, count=1, **kwargs): + ''' + + :param address: The starting address to read from + :param count: The number of discretes to read + :param unit: The slave unit this request is targeting + :returns: A deferred response handle + ''' + request = ReadDiscreteInputsRequest(address, count, **kwargs) + return self.execute(request)
+ +
[docs] def write_coil(self, address, value, **kwargs): + ''' + + :param address: The starting address to write to + :param value: The value to write to the specified address + :param unit: The slave unit this request is targeting + :returns: A deferred response handle + ''' + request = WriteSingleCoilRequest(address, value, **kwargs) + return self.execute(request)
+ +
[docs] def write_coils(self, address, values, **kwargs): + ''' + + :param address: The starting address to write to + :param values: The values to write to the specified address + :param unit: The slave unit this request is targeting + :returns: A deferred response handle + ''' + request = WriteMultipleCoilsRequest(address, values, **kwargs) + return self.execute(request)
+ +
[docs] def write_register(self, address, value, **kwargs): + ''' + + :param address: The starting address to write to + :param value: The value to write to the specified address + :param unit: The slave unit this request is targeting + :returns: A deferred response handle + ''' + request = WriteSingleRegisterRequest(address, value, **kwargs) + return self.execute(request)
+ +
[docs] def write_registers(self, address, values, **kwargs): + ''' + + :param address: The starting address to write to + :param values: The values to write to the specified address + :param unit: The slave unit this request is targeting + :returns: A deferred response handle + ''' + request = WriteMultipleRegistersRequest(address, values, **kwargs) + return self.execute(request)
+ +
[docs] def read_holding_registers(self, address, count=1, **kwargs): + ''' + + :param address: The starting address to read from + :param count: The number of registers to read + :param unit: The slave unit this request is targeting + :returns: A deferred response handle + ''' + request = ReadHoldingRegistersRequest(address, count, **kwargs) + return self.execute(request)
+ +
[docs] def read_input_registers(self, address, count=1, **kwargs): + ''' + + :param address: The starting address to read from + :param count: The number of registers to read + :param unit: The slave unit this request is targeting + :returns: A deferred response handle + ''' + request = ReadInputRegistersRequest(address, count, **kwargs) + return self.execute(request)
+ +
[docs] def readwrite_registers(self, *args, **kwargs): + ''' + + :param read_address: The address to start reading from + :param read_count: The number of registers to read from address + :param write_address: The address to start writing to + :param write_registers: The registers to write to the specified address + :param unit: The slave unit this request is targeting + :returns: A deferred response handle + ''' + request = ReadWriteMultipleRegistersRequest(*args, **kwargs) + return self.execute(request)
+ +
[docs] def mask_write_register(self, *args, **kwargs): + ''' + + :param address: The address of the register to write + :param and_mask: The and bitmask to apply to the register address + :param or_mask: The or bitmask to apply to the register address + :param unit: The slave unit this request is targeting + :returns: A deferred response handle + ''' + request = MaskWriteRegisterRequest(*args, **kwargs) + return self.execute(request)
+ +#---------------------------------------------------------------------------# +# Exported symbols +#---------------------------------------------------------------------------# +__all__ = [ 'ModbusClientMixin' ] +
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/doc/sphinx/html/_modules/pymodbus/client/sync.html b/doc/sphinx/html/_modules/pymodbus/client/sync.html new file mode 100644 index 000000000..04e0ce562 --- /dev/null +++ b/doc/sphinx/html/_modules/pymodbus/client/sync.html @@ -0,0 +1,506 @@ + + + + + + + + pymodbus.client.sync — pymodbus 1.3.0.rc2 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for pymodbus.client.sync

+import socket
+import serial
+import time
+
+from pymodbus.constants import Defaults
+from pymodbus.factory import ClientDecoder
+from pymodbus.compat import byte2int
+from pymodbus.exceptions import NotImplementedException, ParameterException
+from pymodbus.exceptions import ConnectionException
+from pymodbus.transaction import FifoTransactionManager
+from pymodbus.transaction import DictTransactionManager
+from pymodbus.transaction import ModbusSocketFramer, ModbusBinaryFramer
+from pymodbus.transaction import ModbusAsciiFramer, ModbusRtuFramer
+from pymodbus.client.common import ModbusClientMixin
+
+#---------------------------------------------------------------------------#
+# Logging
+#---------------------------------------------------------------------------#
+import logging
+_logger = logging.getLogger(__name__)
+
+
+#---------------------------------------------------------------------------#
+# The Synchronous Clients
+#---------------------------------------------------------------------------#
+
[docs]class BaseModbusClient(ModbusClientMixin): + ''' + Inteface for a modbus synchronous client. Defined here are all the + methods for performing the related request methods. Derived classes + simply need to implement the transport methods and set the correct + framer. + ''' + +
[docs] def __init__(self, framer, **kwargs): + ''' Initialize a client instance + + :param framer: The modbus framer implementation to use + ''' + self.framer = framer + if isinstance(self.framer, ModbusSocketFramer): + self.transaction = DictTransactionManager(self, **kwargs) + else: self.transaction = FifoTransactionManager(self, **kwargs)
+ + #-----------------------------------------------------------------------# + # Client interface + #-----------------------------------------------------------------------# +
[docs] def connect(self): + ''' Connect to the modbus remote host + + :returns: True if connection succeeded, False otherwise + ''' + raise NotImplementedException("Method not implemented by derived class")
+ +
[docs] def close(self): + ''' Closes the underlying socket connection + ''' + pass
+ +
[docs] def _send(self, request): + ''' Sends data on the underlying socket + + :param request: The encoded request to send + :return: The number of bytes written + ''' + raise NotImplementedException("Method not implemented by derived class")
+ +
[docs] def _recv(self, size): + ''' Reads data from the underlying descriptor + + :param size: The number of bytes to read + :return: The bytes read + ''' + raise NotImplementedException("Method not implemented by derived class")
+ + #-----------------------------------------------------------------------# + # Modbus client methods + #-----------------------------------------------------------------------# +
[docs] def execute(self, request=None): + ''' + :param request: The request to process + :returns: The result of the request execution + ''' + if not self.connect(): + raise ConnectionException("Failed to connect[%s]" % (self.__str__())) + return self.transaction.execute(request)
+ + #-----------------------------------------------------------------------# + # The magic methods + #-----------------------------------------------------------------------# +
[docs] def __enter__(self): + ''' Implement the client with enter block + + :returns: The current instance of the client + ''' + if not self.connect(): + raise ConnectionException("Failed to connect[%s]" % (self.__str__())) + return self
+ +
[docs] def __exit__(self, klass, value, traceback): + ''' Implement the client with exit block ''' + self.close()
+ +
[docs] def __str__(self): + ''' Builds a string representation of the connection + + :returns: The string representation + ''' + return "Null Transport"
+ + +#---------------------------------------------------------------------------# +# Modbus TCP Client Transport Implementation +#---------------------------------------------------------------------------# +
[docs]class ModbusTcpClient(BaseModbusClient): + ''' Implementation of a modbus tcp client + ''' + +
[docs] def __init__(self, host='127.0.0.1', port=Defaults.Port, + framer=ModbusSocketFramer, **kwargs): + ''' Initialize a client instance + + :param host: The host to connect to (default 127.0.0.1) + :param port: The modbus port to connect to (default 502) + :param source_address: The source address tuple to bind to (default ('', 0)) + :param timeout: The timeout to use for this socket (default Defaults.Timeout) + :param framer: The modbus framer to use (default ModbusSocketFramer) + + .. note:: The host argument will accept ipv4 and ipv6 hosts + ''' + self.host = host + self.port = port + self.source_address = kwargs.get('source_address', ('', 0)) + self.socket = None + self.timeout = kwargs.get('timeout', Defaults.Timeout) + BaseModbusClient.__init__(self, framer(ClientDecoder()), **kwargs)
+ +
[docs] def connect(self): + ''' Connect to the modbus tcp server + + :returns: True if connection succeeded, False otherwise + ''' + if self.socket: return True + try: + address = (self.host, self.port) + self.socket = socket.create_connection((self.host, self.port), + timeout=self.timeout, source_address=self.source_address) + except socket.error as msg: + _logger.error('Connection to (%s, %s) failed: %s' % \ + (self.host, self.port, msg)) + self.close() + return self.socket != None
+ +
[docs] def close(self): + ''' Closes the underlying socket connection + ''' + if self.socket: + self.socket.close() + self.socket = None
+ +
[docs] def _send(self, request): + ''' Sends data on the underlying socket + + :param request: The encoded request to send + :return: The number of bytes written + ''' + if not self.socket: + raise ConnectionException(self.__str__()) + if request: + return self.socket.send(request) + return 0
+ +
[docs] def _recv(self, size): + ''' Reads data from the underlying descriptor + + :param size: The number of bytes to read + :return: The bytes read + ''' + if not self.socket: + raise ConnectionException(self.__str__()) + return self.socket.recv(size)
+ +
[docs] def __str__(self): + ''' Builds a string representation of the connection + + :returns: The string representation + ''' + return "%s:%s" % (self.host, self.port)
+ + +#---------------------------------------------------------------------------# +# Modbus UDP Client Transport Implementation +#---------------------------------------------------------------------------# +
[docs]class ModbusUdpClient(BaseModbusClient): + ''' Implementation of a modbus udp client + ''' + +
[docs] def __init__(self, host='127.0.0.1', port=Defaults.Port, + framer=ModbusSocketFramer, **kwargs): + ''' Initialize a client instance + + :param host: The host to connect to (default 127.0.0.1) + :param port: The modbus port to connect to (default 502) + :param framer: The modbus framer to use (default ModbusSocketFramer) + :param timeout: The timeout to use for this socket (default None) + ''' + self.host = host + self.port = port + self.socket = None + self.timeout = kwargs.get('timeout', None) + BaseModbusClient.__init__(self, framer(ClientDecoder()), **kwargs)
+ + @classmethod +
[docs] def _get_address_family(cls, address): + ''' A helper method to get the correct address family + for a given address. + + :param address: The address to get the af for + :returns: AF_INET for ipv4 and AF_INET6 for ipv6 + ''' + try: + _ = socket.inet_pton(socket.AF_INET6, address) + except socket.error: # not a valid ipv6 address + return socket.AF_INET + return socket.AF_INET6
+ +
[docs] def connect(self): + ''' Connect to the modbus tcp server + + :returns: True if connection succeeded, False otherwise + ''' + if self.socket: return True + try: + family = ModbusUdpClient._get_address_family(self.host) + self.socket = socket.socket(family, socket.SOCK_DGRAM) + self.socket.settimeout(self.timeout) + except socket.error as ex: + _logger.error('Unable to create udp socket %s' % ex) + self.close() + return self.socket != None
+ +
[docs] def close(self): + ''' Closes the underlying socket connection + ''' + self.socket = None
+ +
[docs] def _send(self, request): + ''' Sends data on the underlying socket + + :param request: The encoded request to send + :return: The number of bytes written + ''' + if not self.socket: + raise ConnectionException(self.__str__()) + if request: + return self.socket.sendto(request, (self.host, self.port)) + return 0
+ +
[docs] def _recv(self, size): + ''' Reads data from the underlying descriptor + + :param size: The number of bytes to read + :return: The bytes read + ''' + if not self.socket: + raise ConnectionException(self.__str__()) + return self.socket.recvfrom(size)[0]
+ +
[docs] def __str__(self): + ''' Builds a string representation of the connection + + :returns: The string representation + ''' + return "%s:%s" % (self.host, self.port)
+ + +#---------------------------------------------------------------------------# +# Modbus Serial Client Transport Implementation +#---------------------------------------------------------------------------# +
[docs]class ModbusSerialClient(BaseModbusClient): + ''' Implementation of a modbus serial client + ''' + +
[docs] def __init__(self, method='ascii', **kwargs): + ''' Initialize a serial client instance + + The methods to connect are:: + + - ascii + - rtu + - binary + + :param method: The method to use for connection + :param port: The serial port to attach to + :param stopbits: The number of stop bits to use + :param bytesize: The bytesize of the serial messages + :param parity: Which kind of parity to use + :param baudrate: The baud rate to use for the serial device + :param timeout: The timeout between serial requests (default 3s) + ''' + self.method = method + self.socket = None + BaseModbusClient.__init__(self, self.__implementation(method), **kwargs) + + self.port = kwargs.get('port', 0) + self.stopbits = kwargs.get('stopbits', Defaults.Stopbits) + self.bytesize = kwargs.get('bytesize', Defaults.Bytesize) + self.parity = kwargs.get('parity', Defaults.Parity) + self.baudrate = kwargs.get('baudrate', Defaults.Baudrate) + self.timeout = kwargs.get('timeout', Defaults.Timeout) + if self.method == "rtu": + self._last_frame_end = 0.0 + self._silent_interval = 3.5 * (1 + 8 + 2) / self.baudrate
+ + @staticmethod + def __implementation(method): + ''' Returns the requested framer + + :method: The serial framer to instantiate + :returns: The requested serial framer + ''' + method = method.lower() + if method == 'ascii': return ModbusAsciiFramer(ClientDecoder()) + elif method == 'rtu': return ModbusRtuFramer(ClientDecoder()) + elif method == 'binary': return ModbusBinaryFramer(ClientDecoder()) + elif method == 'socket': return ModbusSocketFramer(ClientDecoder()) + raise ParameterException("Invalid framer method requested") + +
[docs] def connect(self): + ''' Connect to the modbus serial server + + :returns: True if connection succeeded, False otherwise + ''' + if self.socket: return True + try: + self.socket = serial.Serial(port=self.port, timeout=self.timeout, + bytesize=self.bytesize, stopbits=self.stopbits, + baudrate=self.baudrate, parity=self.parity) + except serial.SerialException as msg: + _logger.error(msg) + self.close() + if self.method == "rtu": + self._last_frame_end = time.time() + return self.socket != None
+ +
[docs] def close(self): + ''' Closes the underlying socket connection + ''' + if self.socket: + self.socket.close() + self.socket = None
+ +
[docs] def _send(self, request): + ''' Sends data on the underlying socket + + If receive buffer still holds some data then flush it. + + Sleep if last send finished less than 3.5 character + times ago. + + :param request: The encoded request to send + :return: The number of bytes written + ''' + if not self.socket: + raise ConnectionException(self.__str__()) + if request: + ts = time.time() + if self.method == "rtu": + if ts < self._last_frame_end + self._silent_interval: + _logger.debug("will sleep to wait for 3.5 char") + time.sleep(self._last_frame_end + self._silent_interval - ts) + + try: + in_waiting = "in_waiting" if hasattr(self.socket, "in_waiting") else "inWaiting" + if in_waiting == "in_waiting": + waitingbytes = getattr(self.socket, in_waiting) + else: + waitingbytes = getattr(self.socket, in_waiting)() + if waitingbytes: + result = self.socket.read(waitingbytes) + if _logger.isEnabledFor(logging.WARNING): + _logger.warning("cleanup recv buffer before send: " + " ".join([hex(byte2int(x)) for x in result])) + except NotImplementedError: + pass + + size = self.socket.write(request) + if self.method == "rtu": + self._last_frame_end = time.time() + return size + return 0
+ +
[docs] def _recv(self, size): + ''' Reads data from the underlying descriptor + + :param size: The number of bytes to read + :return: The bytes read + ''' + if not self.socket: + raise ConnectionException(self.__str__()) + result = self.socket.read(size) + if self.method == "rtu": + self._last_frame_end = time.time() + return result
+ +
[docs] def __str__(self): + ''' Builds a string representation of the connection + + :returns: The string representation + ''' + return "%s baud[%s]" % (self.method, self.baudrate)
+ +#---------------------------------------------------------------------------# +# Exported symbols +#---------------------------------------------------------------------------# +__all__ = [ + "ModbusTcpClient", "ModbusUdpClient", "ModbusSerialClient" +] +
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/doc/sphinx/html/_modules/pymodbus/constants.html b/doc/sphinx/html/_modules/pymodbus/constants.html new file mode 100644 index 000000000..adfc5a0b0 --- /dev/null +++ b/doc/sphinx/html/_modules/pymodbus/constants.html @@ -0,0 +1,331 @@ + + + + + + + + pymodbus.constants — pymodbus 1.3.0.rc2 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for pymodbus.constants

+'''
+Constants For Modbus Server/Client
+----------------------------------
+
+This is the single location for storing default
+values for the servers and clients.
+'''
+from pymodbus.interfaces import Singleton
+
+
+
[docs]class Defaults(Singleton): + ''' A collection of modbus default values + + .. attribute:: Port + + The default modbus tcp server port (502) + + .. attribute:: Retries + + The default number of times a client should retry the given + request before failing (3) + + .. attribute:: RetryOnEmpty + + A flag indicating if a transaction should be retried in the + case that an empty response is received. This is useful for + slow clients that may need more time to process a requst. + + .. attribute:: Timeout + + The default amount of time a client should wait for a request + to be processed (3 seconds) + + .. attribute:: Reconnects + + The default number of times a client should attempt to reconnect + before deciding the server is down (0) + + .. attribute:: TransactionId + + The starting transaction identifier number (0) + + .. attribute:: ProtocolId + + The modbus protocol id. Currently this is set to 0 in all + but proprietary implementations. + + .. attribute:: UnitId + + The modbus slave addrss. Currently this is set to 0x00 which + means this request should be broadcast to all the slave devices + (really means that all the devices should respons). + + .. attribute:: Baudrate + + The speed at which the data is transmitted over the serial line. + This defaults to 19200. + + .. attribute:: Parity + + The type of checksum to use to verify data integrity. This can be + on of the following:: + + - (E)ven - 1 0 1 0 | P(0) + - (O)dd - 1 0 1 0 | P(1) + - (N)one - 1 0 1 0 | no parity + + This defaults to (N)one. + + .. attribute:: Bytesize + + The number of bits in a byte of serial data. This can be one of + 5, 6, 7, or 8. This defaults to 8. + + .. attribute:: Stopbits + + The number of bits sent after each character in a message to + indicate the end of the byte. This defaults to 1. + + .. attribute:: ZeroMode + + Indicates if the slave datastore should use indexing at 0 or 1. + More about this can be read in section 4.4 of the modbus specification. + + .. attribute:: IgnoreMissingSlaves + + In case a request is made to a missing slave, this defines if an error + should be returned or simply ignored. This is useful for the case of a + serial server emulater where a request to a non-existant slave on a bus + will never respond. The client in this case will simply timeout. + ''' + Port = 502 + Retries = 3 + RetryOnEmpty = False + Timeout = 3 + Reconnects = 0 + TransactionId = 0 + ProtocolId = 0 + UnitId = 0x00 + Baudrate = 19200 + Parity = 'N' + Bytesize = 8 + Stopbits = 1 + ZeroMode = False + IgnoreMissingSlaves = False
+ + +
[docs]class ModbusStatus(Singleton): + ''' + These represent various status codes in the modbus + protocol. + + .. attribute:: Waiting + + This indicates that a modbus device is currently + waiting for a given request to finish some running task. + + .. attribute:: Ready + + This indicates that a modbus device is currently + free to perform the next request task. + + .. attribute:: On + + This indicates that the given modbus entity is on + + .. attribute:: Off + + This indicates that the given modbus entity is off + + .. attribute:: SlaveOn + + This indicates that the given modbus slave is running + + .. attribute:: SlaveOff + + This indicates that the given modbus slave is not running + ''' + Waiting = 0xffff + Ready = 0x0000 + On = 0xff00 + Off = 0x0000 + SlaveOn = 0xff + SlaveOff = 0x00
+ + +
[docs]class Endian(Singleton): + ''' An enumeration representing the various byte endianess. + + .. attribute:: Auto + + This indicates that the byte order is chosen by the + current native environment. + + .. attribute:: Big + + This indicates that the bytes are in little endian format + + .. attribute:: Little + + This indicates that the bytes are in big endian format + + .. note:: I am simply borrowing the format strings from the + python struct module for my convenience. + ''' + Auto = '@' + Big = '>' + Little = '<'
+ + +
[docs]class ModbusPlusOperation(Singleton): + ''' Represents the type of modbus plus request + + .. attribute:: GetStatistics + + Operation requesting that the current modbus plus statistics + be returned in the response. + + .. attribute:: ClearStatistics + + Operation requesting that the current modbus plus statistics + be cleared and not returned in the response. + ''' + GetStatistics = 0x0003 + ClearStatistics = 0x0004
+ + +
[docs]class DeviceInformation(Singleton): + ''' Represents what type of device information to read + + .. attribute:: Basic + + This is the basic (required) device information to be returned. + This includes VendorName, ProductCode, and MajorMinorRevision + code. + + .. attribute:: Regular + + In addition to basic data objects, the device provides additional + and optinoal identification and description data objects. All of + the objects of this category are defined in the standard but their + implementation is optional. + + .. attribute:: Extended + + In addition to regular data objects, the device provides additional + and optional identification and description private data about the + physical device itself. All of these data are device dependent. + + .. attribute:: Specific + + Request to return a single data object. + ''' + Basic = 0x01 + Regular = 0x02 + Extended = 0x03 + Specific = 0x04
+ + +
[docs]class MoreData(Singleton): + ''' Represents the more follows condition + + .. attribute:: Nothing + + This indiates that no more objects are going to be returned. + + .. attribute:: KeepReading + + This indicates that there are more objects to be returned. + ''' + Nothing = 0x00 + KeepReading = 0xFF
+ +#---------------------------------------------------------------------------# +# Exported Identifiers +#---------------------------------------------------------------------------# +__all__ = [ + "Defaults", "ModbusStatus", "Endian", + "ModbusPlusOperation", + "DeviceInformation", "MoreData", +] +
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/doc/sphinx/html/_modules/pymodbus/datastore/context.html b/doc/sphinx/html/_modules/pymodbus/datastore/context.html new file mode 100644 index 000000000..306a20e72 --- /dev/null +++ b/doc/sphinx/html/_modules/pymodbus/datastore/context.html @@ -0,0 +1,246 @@ + + + + + + + + pymodbus.datastore.context — pymodbus 1.3.0.rc2 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for pymodbus.datastore.context

+from pymodbus.exceptions import ParameterException, NoSuchSlaveException
+from pymodbus.interfaces import IModbusSlaveContext
+from pymodbus.datastore.store import ModbusSequentialDataBlock
+from pymodbus.constants import Defaults
+from pymodbus.compat import iteritems, itervalues
+
+#---------------------------------------------------------------------------#
+# Logging
+#---------------------------------------------------------------------------#
+import logging;
+_logger = logging.getLogger(__name__)
+
+
+#---------------------------------------------------------------------------#
+# Slave Contexts
+#---------------------------------------------------------------------------#
+
[docs]class ModbusSlaveContext(IModbusSlaveContext): + ''' + This creates a modbus data model with each data access + stored in its own personal block + ''' + +
[docs] def __init__(self, *args, **kwargs): + ''' Initializes the datastores, defaults to fully populated + sequential data blocks if none are passed in. + + :param kwargs: Each element is a ModbusDataBlock + + 'di' - Discrete Inputs initializer + 'co' - Coils initializer + 'hr' - Holding Register initializer + 'ir' - Input Registers iniatializer + ''' + self.store = {} + self.store['d'] = kwargs.get('di', ModbusSequentialDataBlock.create()) + self.store['c'] = kwargs.get('co', ModbusSequentialDataBlock.create()) + self.store['i'] = kwargs.get('ir', ModbusSequentialDataBlock.create()) + self.store['h'] = kwargs.get('hr', ModbusSequentialDataBlock.create()) + self.zero_mode = kwargs.get('zero_mode', Defaults.ZeroMode)
+ +
[docs] def __str__(self): + ''' Returns a string representation of the context + + :returns: A string representation of the context + ''' + return "Modbus Slave Context"
+ +
[docs] def reset(self): + ''' Resets all the datastores to their default values ''' + for datastore in itervalues(self.store): + datastore.reset()
+ +
[docs] def validate(self, fx, address, count=1): + ''' Validates the request to make sure it is in range + + :param fx: The function we are working with + :param address: The starting address + :param count: The number of values to test + :returns: True if the request in within range, False otherwise + ''' + if not self.zero_mode: address = address + 1 + _logger.debug("validate[%d] %d:%d" % (fx, address, count)) + return self.store[self.decode(fx)].validate(address, count)
+ +
[docs] def getValues(self, fx, address, count=1): + ''' Validates the request to make sure it is in range + + :param fx: The function we are working with + :param address: The starting address + :param count: The number of values to retrieve + :returns: The requested values from a:a+c + ''' + if not self.zero_mode: address = address + 1 + _logger.debug("getValues[%d] %d:%d" % (fx, address, count)) + return self.store[self.decode(fx)].getValues(address, count)
+ +
[docs] def setValues(self, fx, address, values): + ''' Sets the datastore with the supplied values + + :param fx: The function we are working with + :param address: The starting address + :param values: The new values to be set + ''' + if not self.zero_mode: address = address + 1 + _logger.debug("setValues[%d] %d:%d" % (fx, address, len(values))) + self.store[self.decode(fx)].setValues(address, values)
+ + +
[docs]class ModbusServerContext(object): + ''' This represents a master collection of slave contexts. + If single is set to true, it will be treated as a single + context so every unit-id returns the same context. If single + is set to false, it will be interpreted as a collection of + slave contexts. + ''' + +
[docs] def __init__(self, slaves=None, single=True): + ''' Initializes a new instance of a modbus server context. + + :param slaves: A dictionary of client contexts + :param single: Set to true to treat this as a single context + ''' + self.single = single + self.__slaves = slaves or {} + if self.single: + self.__slaves = {Defaults.UnitId: self.__slaves}
+ +
[docs] def __iter__(self): + ''' Iterater over the current collection of slave + contexts. + + :returns: An iterator over the slave contexts + ''' + return iteritems(self.__slaves)
+ +
[docs] def __contains__(self, slave): + ''' Check if the given slave is in this list + + :param slave: slave The slave to check for existance + :returns: True if the slave exists, False otherwise + ''' + return slave in self.__slaves
+ +
[docs] def __setitem__(self, slave, context): + ''' Used to set a new slave context + + :param slave: The slave context to set + :param context: The new context to set for this slave + ''' + if self.single: slave = Defaults.UnitId + if 0xf7 >= slave >= 0x00: + self.__slaves[slave] = context + else: + raise NoSuchSlaveException('slave index :{} out of range'.format(slave))
+ +
[docs] def __delitem__(self, slave): + ''' Wrapper used to access the slave context + + :param slave: The slave context to remove + ''' + if not self.single and (0xf7 >= slave >= 0x00): + del self.__slaves[slave] + else: + raise NoSuchSlaveException('slave index: {} out of range'.format(slave))
+ +
[docs] def __getitem__(self, slave): + ''' Used to get access to a slave context + + :param slave: The slave context to get + :returns: The requested slave context + ''' + if self.single: slave = Defaults.UnitId + if slave in self.__slaves: + return self.__slaves.get(slave) + else: + raise NoSuchSlaveException("slave - {} does not exist, or is out of range".format(slave))
+
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/doc/sphinx/html/_modules/pymodbus/datastore/remote.html b/doc/sphinx/html/_modules/pymodbus/datastore/remote.html new file mode 100644 index 000000000..05a888254 --- /dev/null +++ b/doc/sphinx/html/_modules/pymodbus/datastore/remote.html @@ -0,0 +1,190 @@ + + + + + + + + pymodbus.datastore.remote — pymodbus 1.3.0.rc2 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for pymodbus.datastore.remote

+from pymodbus.exceptions import NotImplementedException
+from pymodbus.interfaces import IModbusSlaveContext
+
+#---------------------------------------------------------------------------#
+# Logging
+#---------------------------------------------------------------------------#
+import logging
+_logger = logging.getLogger(__name__)
+
+
+#---------------------------------------------------------------------------#
+# Context
+#---------------------------------------------------------------------------#
+
[docs]class RemoteSlaveContext(IModbusSlaveContext): + ''' TODO + This creates a modbus data model that connects to + a remote device (depending on the client used) + ''' + +
[docs] def __init__(self, client): + ''' Initializes the datastores + + :param client: The client to retrieve values with + ''' + self._client = client + self.__build_mapping()
+ +
[docs] def reset(self): + ''' Resets all the datastores to their default values ''' + raise NotImplementedException()
+ +
[docs] def validate(self, fx, address, count=1): + ''' Validates the request to make sure it is in range + + :param fx: The function we are working with + :param address: The starting address + :param count: The number of values to test + :returns: True if the request in within range, False otherwise + ''' + _logger.debug("validate[%d] %d:%d" % (fx, address, count)) + result = self.__get_callbacks[self.decode(fx)](address, count) + return result.function_code < 0x80
+ +
[docs] def getValues(self, fx, address, count=1): + ''' Validates the request to make sure it is in range + + :param fx: The function we are working with + :param address: The starting address + :param count: The number of values to retrieve + :returns: The requested values from a:a+c + ''' + # TODO deal with deferreds + _logger.debug("get values[%d] %d:%d" % (fx, address, count)) + result = self.__get_callbacks[self.decode(fx)](address, count) + return self.__extract_result(self.decode(fx), result)
+ +
[docs] def setValues(self, fx, address, values): + ''' Sets the datastore with the supplied values + + :param fx: The function we are working with + :param address: The starting address + :param values: The new values to be set + ''' + # TODO deal with deferreds + _logger.debug("set values[%d] %d:%d" % (fx, address, len(values))) + self.__set_callbacks[self.decode(fx)](address, values)
+ +
[docs] def __str__(self): + ''' Returns a string representation of the context + + :returns: A string representation of the context + ''' + return "Remote Slave Context(%s)" % self._client
+ + def __build_mapping(self): + ''' + A quick helper method to build the function + code mapper. + ''' + self.__get_callbacks = { + 'd': lambda a, c: self._client.read_discrete_inputs(a, c), + 'c': lambda a, c: self._client.read_coils(a, c), + 'h': lambda a, c: self._client.read_holding_registers(a, c), + 'i': lambda a, c: self._client.read_input_registers(a, c), + } + self.__set_callbacks = { + 'd': lambda a, v: self._client.write_coils(a, v), + 'c': lambda a, v: self._client.write_coils(a, v), + 'h': lambda a, v: self._client.write_registers(a, v), + 'i': lambda a, v: self._client.write_registers(a, v), + } + + def __extract_result(self, fx, result): + ''' A helper method to extract the values out of + a response. TODO make this consistent (values?) + ''' + if result.function_code < 0x80: + if fx in ['d', 'c']: return result.bits + if fx in ['h', 'i']: return result.registers + else: return result
+
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/doc/sphinx/html/_modules/pymodbus/datastore/store.html b/doc/sphinx/html/_modules/pymodbus/datastore/store.html new file mode 100644 index 000000000..54b6b4f02 --- /dev/null +++ b/doc/sphinx/html/_modules/pymodbus/datastore/store.html @@ -0,0 +1,343 @@ + + + + + + + + pymodbus.datastore.store — pymodbus 1.3.0.rc2 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for pymodbus.datastore.store

+"""
+Modbus Server Datastore
+-------------------------
+
+For each server, you will create a ModbusServerContext and pass
+in the default address space for each data access.  The class
+will create and manage the data.
+
+Further modification of said data accesses should be performed
+with [get,set][access]Values(address, count)
+
+Datastore Implementation
+-------------------------
+
+There are two ways that the server datastore can be implemented.
+The first is a complete range from 'address' start to 'count'
+number of indecies.  This can be thought of as a straight array::
+
+    data = range(1, 1 + count)
+    [1,2,3,...,count]
+
+The other way that the datastore can be implemented (and how
+many devices implement it) is a associate-array::
+
+    data = {1:'1', 3:'3', ..., count:'count'}
+    [1,3,...,count]
+
+The difference between the two is that the latter will allow
+arbitrary gaps in its datastore while the former will not.
+This is seen quite commonly in some modbus implementations.
+What follows is a clear example from the field:
+
+Say a company makes two devices to monitor power usage on a rack.
+One works with three-phase and the other with a single phase. The
+company will dictate a modbus data mapping such that registers::
+
+    n:      phase 1 power
+    n+1:    phase 2 power
+    n+2:    phase 3 power
+
+Using this, layout, the first device will implement n, n+1, and n+2,
+however, the second device may set the latter two values to 0 or
+will simply not implmented the registers thus causing a single read
+or a range read to fail.
+
+I have both methods implemented, and leave it up to the user to change
+based on their preference.
+"""
+from pymodbus.exceptions import NotImplementedException, ParameterException
+from pymodbus.compat import iteritems, iterkeys, itervalues, get_next
+
+#---------------------------------------------------------------------------#
+# Logging
+#---------------------------------------------------------------------------#
+import logging
+_logger = logging.getLogger(__name__)
+
+
+#---------------------------------------------------------------------------#
+# Datablock Storage
+#---------------------------------------------------------------------------#
+
[docs]class BaseModbusDataBlock(object): + ''' + Base class for a modbus datastore + + Derived classes must create the following fields: + @address The starting address point + @defult_value The default value of the datastore + @values The actual datastore values + + Derived classes must implemented the following methods: + validate(self, address, count=1) + getValues(self, address, count=1) + setValues(self, address, values) + ''' + +
[docs] def default(self, count, value=False): + ''' Used to initialize a store to one value + + :param count: The number of fields to set + :param value: The default value to set to the fields + ''' + self.default_value = value + self.values = [self.default_value] * count + self.address = 0x00
+ +
[docs] def reset(self): + ''' Resets the datastore to the initialized default value ''' + self.values = [self.default_value] * len(self.values)
+ +
[docs] def validate(self, address, count=1): + ''' Checks to see if the request is in range + + :param address: The starting address + :param count: The number of values to test for + :returns: True if the request in within range, False otherwise + ''' + raise NotImplementedException("Datastore Address Check")
+ +
[docs] def getValues(self, address, count=1): + ''' Returns the requested values from the datastore + + :param address: The starting address + :param count: The number of values to retrieve + :returns: The requested values from a:a+c + ''' + raise NotImplementedException("Datastore Value Retrieve")
+ +
[docs] def setValues(self, address, values): + ''' Returns the requested values from the datastore + + :param address: The starting address + :param values: The values to store + ''' + raise NotImplementedException("Datastore Value Retrieve")
+ +
[docs] def __str__(self): + ''' Build a representation of the datastore + + :returns: A string representation of the datastore + ''' + return "DataStore(%d, %d)" % (len(self.values), self.default_value)
+ +
[docs] def __iter__(self): + ''' Iterater over the data block data + + :returns: An iterator of the data block data + ''' + if isinstance(self.values, dict): + return iteritems(self.values) + return enumerate(self.values, self.address)
+ + +
[docs]class ModbusSequentialDataBlock(BaseModbusDataBlock): + ''' Creates a sequential modbus datastore ''' + +
[docs] def __init__(self, address, values): + ''' Initializes the datastore + + :param address: The starting address of the datastore + :param values: Either a list or a dictionary of values + ''' + self.address = address + if hasattr(values, '__iter__'): + self.values = list(values) + else: self.values = [values] + self.default_value = self.values[0].__class__()
+ + @classmethod +
[docs] def create(klass): + ''' Factory method to create a datastore with the + full address space initialized to 0x00 + + :returns: An initialized datastore + ''' + return klass(0x00, [0x00] * 65536)
+ +
[docs] def validate(self, address, count=1): + ''' Checks to see if the request is in range + + :param address: The starting address + :param count: The number of values to test for + :returns: True if the request in within range, False otherwise + ''' + result = (self.address <= address) + result &= ((self.address + len(self.values)) >= (address + count)) + return result
+ +
[docs] def getValues(self, address, count=1): + ''' Returns the requested values of the datastore + + :param address: The starting address + :param count: The number of values to retrieve + :returns: The requested values from a:a+c + ''' + start = address - self.address + return self.values[start:start + count]
+ +
[docs] def setValues(self, address, values): + ''' Sets the requested values of the datastore + + :param address: The starting address + :param values: The new values to be set + ''' + if not isinstance(values, list): + values = [values] + start = address - self.address + self.values[start:start + len(values)] = values
+ + +
[docs]class ModbusSparseDataBlock(BaseModbusDataBlock): + ''' Creates a sparse modbus datastore ''' + +
[docs] def __init__(self, values): + ''' Initializes the datastore + + Using the input values we create the default + datastore value and the starting address + + :param values: Either a list or a dictionary of values + ''' + if isinstance(values, dict): + self.values = values + elif hasattr(values, '__iter__'): + self.values = dict(enumerate(values)) + else: raise ParameterException( + "Values for datastore must be a list or dictionary") + self.default_value = get_next(itervalues(self.values)).__class__() + self.address = get_next(iterkeys(self.values))
+ + @classmethod +
[docs] def create(klass): + ''' Factory method to create a datastore with the + full address space initialized to 0x00 + + :returns: An initialized datastore + ''' + return klass([0x00] * 65536)
+ +
[docs] def validate(self, address, count=1): + ''' Checks to see if the request is in range + + :param address: The starting address + :param count: The number of values to test for + :returns: True if the request in within range, False otherwise + ''' + if count == 0: return False + handle = set(range(address, address + count)) + return handle.issubset(set(iterkeys(self.values)))
+ +
[docs] def getValues(self, address, count=1): + ''' Returns the requested values of the datastore + + :param address: The starting address + :param count: The number of values to retrieve + :returns: The requested values from a:a+c + ''' + return [self.values[i] for i in range(address, address + count)]
+ +
[docs] def setValues(self, address, values): + ''' Sets the requested values of the datastore + + :param address: The starting address + :param values: The new values to be set + ''' + if isinstance(values, dict): + for idx, val in iteritems(values): + self.values[idx] = val + else: + if not isinstance(values, list): + values = [values] + for idx, val in enumerate(values): + self.values[address + idx] = val
+
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/doc/sphinx/html/_modules/pymodbus/device.html b/doc/sphinx/html/_modules/pymodbus/device.html new file mode 100644 index 000000000..ce0d93f53 --- /dev/null +++ b/doc/sphinx/html/_modules/pymodbus/device.html @@ -0,0 +1,712 @@ + + + + + + + + pymodbus.device — pymodbus 1.3.0.rc2 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for pymodbus.device

+"""
+Modbus Device Controller
+-------------------------
+
+These are the device management handlers.  They should be
+maintained in the server context and the various methods
+should be inserted in the correct locations.
+"""
+from pymodbus.constants import DeviceInformation
+from pymodbus.interfaces import Singleton
+from pymodbus.utilities import dict_property
+from pymodbus.compat import iteritems, itervalues, izip, int2byte
+
+from collections import OrderedDict
+
+#---------------------------------------------------------------------------#
+# Network Access Control
+#---------------------------------------------------------------------------#
+
[docs]class ModbusAccessControl(Singleton): + ''' + This is a simple implementation of a Network Management System table. + Its purpose is to control access to the server (if it is used). + We assume that if an entry is in the table, it is allowed accesses to + resources. However, if the host does not appear in the table (all + unknown hosts) its connection will simply be closed. + + Since it is a singleton, only one version can possible exist and all + instances pull from here. + ''' + __nmstable = [ + "127.0.0.1", + ] + +
[docs] def __iter__(self): + ''' Iterater over the network access table + + :returns: An iterator of the network access table + ''' + return self.__nmstable.__iter__()
+ +
[docs] def __contains__(self, host): + ''' Check if a host is allowed to access resources + + :param host: The host to check + ''' + return host in self.__nmstable
+ +
[docs] def add(self, host): + ''' Add allowed host(s) from the NMS table + + :param host: The host to add + ''' + if not isinstance(host, list): + host = [host] + for entry in host: + if entry not in self.__nmstable: + self.__nmstable.append(entry)
+ +
[docs] def remove(self, host): + ''' Remove allowed host(s) from the NMS table + + :param host: The host to remove + ''' + if not isinstance(host, list): + host = [host] + for entry in host: + if entry in self.__nmstable: + self.__nmstable.remove(entry)
+ +
[docs] def check(self, host): + ''' Check if a host is allowed to access resources + + :param host: The host to check + ''' + return host in self.__nmstable
+ + +#---------------------------------------------------------------------------# +# Modbus Plus Statistics +#---------------------------------------------------------------------------# +
[docs]class ModbusPlusStatistics(object): + ''' + This is used to maintain the current modbus plus statistics count. As of + right now this is simply a stub to complete the modbus implementation. + For more information, see the modbus implementation guide page 87. + ''' + + __data = OrderedDict({ + 'node_type_id' : [0x00] * 2, # 00 + 'software_version_number' : [0x00] * 2, # 01 + 'network_address' : [0x00] * 2, # 02 + 'mac_state_variable' : [0x00] * 2, # 03 + 'peer_status_code' : [0x00] * 2, # 04 + 'token_pass_counter' : [0x00] * 2, # 05 + 'token_rotation_time' : [0x00] * 2, # 06 + + 'program_master_token_failed' : [0x00], # 07 hi + 'data_master_token_failed' : [0x00], # 07 lo + 'program_master_token_owner' : [0x00], # 08 hi + 'data_master_token_owner' : [0x00], # 08 lo + 'program_slave_token_owner' : [0x00], # 09 hi + 'data_slave_token_owner' : [0x00], # 09 lo + 'data_slave_command_transfer' : [0x00], # 10 hi + '__unused_10_lowbit' : [0x00], # 10 lo + + 'program_slave_command_transfer' : [0x00], # 11 hi + 'program_master_rsp_transfer' : [0x00], # 11 lo + 'program_slave_auto_logout' : [0x00], # 12 hi + 'program_master_connect_status' : [0x00], # 12 lo + 'receive_buffer_dma_overrun' : [0x00], # 13 hi + 'pretransmit_deferral_error' : [0x00], # 13 lo + 'frame_size_error' : [0x00], # 14 hi + 'repeated_command_received' : [0x00], # 14 lo + 'receiver_alignment_error' : [0x00], # 15 hi + 'receiver_collision_abort_error' : [0x00], # 15 lo + 'bad_packet_length_error' : [0x00], # 16 hi + 'receiver_crc_error' : [0x00], # 16 lo + 'transmit_buffer_dma_underrun' : [0x00], # 17 hi + 'bad_link_address_error' : [0x00], # 17 lo + + 'bad_mac_function_code_error' : [0x00], # 18 hi + 'internal_packet_length_error' : [0x00], # 18 lo + 'communication_failed_error' : [0x00], # 19 hi + 'communication_retries' : [0x00], # 19 lo + 'no_response_error' : [0x00], # 20 hi + 'good_receive_packet' : [0x00], # 20 lo + 'unexpected_path_error' : [0x00], # 21 hi + 'exception_response_error' : [0x00], # 21 lo + 'forgotten_transaction_error' : [0x00], # 22 hi + 'unexpected_response_error' : [0x00], # 22 lo + + 'active_station_bit_map' : [0x00] * 8, # 23-26 + 'token_station_bit_map' : [0x00] * 8, # 27-30 + 'global_data_bit_map' : [0x00] * 8, # 31-34 + 'receive_buffer_use_bit_map' : [0x00] * 8, # 35-37 + 'data_master_output_path' : [0x00] * 8, # 38-41 + 'data_slave_input_path' : [0x00] * 8, # 42-45 + 'program_master_outptu_path' : [0x00] * 8, # 46-49 + 'program_slave_input_path' : [0x00] * 8, # 50-53 + }) + +
[docs] def __init__(self): + ''' + Initialize the modbus plus statistics with the default + information. + ''' + self.reset()
+ +
[docs] def __iter__(self): + ''' Iterater over the statistics + + :returns: An iterator of the modbus plus statistics + ''' + return iteritems(self.__data)
+ +
[docs] def reset(self): + ''' This clears all of the modbus plus statistics + ''' + for key in self.__data: + self.__data[key] = [0x00] * len(self.__data[key])
+ +
[docs] def summary(self): + ''' Returns a summary of the modbus plus statistics + + :returns: 54 16-bit words representing the status + ''' + return itervalues(self.__data)
+ +
[docs] def encode(self): + ''' Returns a summary of the modbus plus statistics + + :returns: 54 16-bit words representing the status + ''' + total, values = [], sum(self.__data.values(), []) + for c in range(0, len(values), 2): + total.append((values[c] << 8) | values[c+1]) + return total
+ + +#---------------------------------------------------------------------------# +# Device Information Control +#---------------------------------------------------------------------------# +
[docs]class ModbusDeviceIdentification(object): + ''' + This is used to supply the device identification + for the readDeviceIdentification function + + For more information read section 6.21 of the modbus + application protocol. + ''' + __data = { + 0x00: '', # VendorName + 0x01: '', # ProductCode + 0x02: '', # MajorMinorRevision + 0x03: '', # VendorUrl + 0x04: '', # ProductName + 0x05: '', # ModelName + 0x06: '', # UserApplicationName + 0x07: '', # reserved + 0x08: '', # reserved + # 0x80 -> 0xFF are private + } + + __names = [ + 'VendorName', + 'ProductCode', + 'MajorMinorRevision', + 'VendorUrl', + 'ProductName', + 'ModelName', + 'UserApplicationName', + ] + +
[docs] def __init__(self, info=None): + ''' + Initialize the datastore with the elements you need. + (note acceptable range is [0x00-0x06,0x80-0xFF] inclusive) + + :param information: A dictionary of {int:string} of values + ''' + if isinstance(info, dict): + for key in info: + if (0x06 >= key >= 0x00) or (0x80 > key > 0x08): + self.__data[key] = info[key]
+ +
[docs] def __iter__(self): + ''' Iterater over the device information + + :returns: An iterator of the device information + ''' + return iteritems(self.__data)
+ +
[docs] def summary(self): + ''' Return a summary of the main items + + :returns: An dictionary of the main items + ''' + return dict(zip(self.__names, itervalues(self.__data)))
+ +
[docs] def update(self, value): + ''' Update the values of this identity + using another identify as the value + + :param value: The value to copy values from + ''' + self.__data.update(value)
+ +
[docs] def __setitem__(self, key, value): + ''' Wrapper used to access the device information + + :param key: The register to set + :param value: The new value for referenced register + ''' + if key not in [0x07, 0x08]: + self.__data[key] = value
+ +
[docs] def __getitem__(self, key): + ''' Wrapper used to access the device information + + :param key: The register to read + ''' + return self.__data.setdefault(key, '')
+ +
[docs] def __str__(self): + ''' Build a representation of the device + + :returns: A string representation of the device + ''' + return "DeviceIdentity"
+ + #-------------------------------------------------------------------------# + # Properties + #-------------------------------------------------------------------------# + VendorName = dict_property(lambda s: s.__data, 0) + ProductCode = dict_property(lambda s: s.__data, 1) + MajorMinorRevision = dict_property(lambda s: s.__data, 2) + VendorUrl = dict_property(lambda s: s.__data, 3) + ProductName = dict_property(lambda s: s.__data, 4) + ModelName = dict_property(lambda s: s.__data, 5) + UserApplicationName = dict_property(lambda s: s.__data, 6)
+ + +
[docs]class DeviceInformationFactory(Singleton): + ''' This is a helper factory that really just hides + some of the complexity of processing the device information + requests (function code 0x2b 0x0e). + ''' + + __lookup = { + DeviceInformation.Basic: lambda c,r,i: c.__gets(r, list(range(0x00, 0x03))), + DeviceInformation.Regular: lambda c,r,i: c.__gets(r, list(range(0x00, 0x08))), + DeviceInformation.Extended: lambda c,r,i: c.__gets(r, list(range(0x80, i))), + DeviceInformation.Specific: lambda c,r,i: c.__get(r, i), + } + + @classmethod +
[docs] def get(cls, control, read_code=DeviceInformation.Basic, object_id=0x00): + ''' Get the requested device data from the system + + :param control: The control block to pull data from + :param read_code: The read code to process + :param object_id: The specific object_id to read + :returns: The requested data (id, length, value) + ''' + identity = control.Identity + return cls.__lookup[read_code](cls, identity, object_id)
+ + @classmethod + def __get(cls, identity, object_id): + ''' Read a single object_id from the device information + + :param identity: The identity block to pull data from + :param object_id: The specific object id to read + :returns: The requested data (id, length, value) + ''' + return { object_id:identity[object_id] } + + @classmethod + def __gets(cls, identity, object_ids): + ''' Read multiple object_ids from the device information + + :param identity: The identity block to pull data from + :param object_ids: The specific object ids to read + :returns: The requested data (id, length, value) + ''' + return dict((oid, identity[oid]) for oid in object_ids)
+ + +#---------------------------------------------------------------------------# +# Counters Handler +#---------------------------------------------------------------------------# +class ModbusCountersHandler(object): + ''' + This is a helper class to simplify the properties for the counters:: + + 0x0B 1 Return Bus Message Count + + Quantity of messages that the remote + device has detected on the communications system since its + last restart, clear counters operation, or power-up. Messages + with bad CRC are not taken into account. + + 0x0C 2 Return Bus Communication Error Count + + Quantity of CRC errors encountered by the remote device since its + last restart, clear counters operation, or power-up. In case of + an error detected on the character level, (overrun, parity error), + or in case of a message length < 3 bytes, the receiving device is + not able to calculate the CRC. In such cases, this counter is + also incremented. + + 0x0D 3 Return Slave Exception Error Count + + Quantity of MODBUS exception error detected by the remote device + since its last restart, clear counters operation, or power-up. It + comprises also the error detected in broadcast messages even if an + exception message is not returned in this case. + Exception errors are described and listed in "MODBUS Application + Protocol Specification" document. + + 0xOE 4 Return Slave Message Count + + Quantity of messages addressed to the remote device, including + broadcast messages, that the remote device has processed since its + last restart, clear counters operation, or power-up. + + 0x0F 5 Return Slave No Response Count + + Quantity of messages received by the remote device for which it + returned no response (neither a normal response nor an exception + response), since its last restart, clear counters operation, or + power-up. Then, this counter counts the number of broadcast + messages it has received. + + 0x10 6 Return Slave NAK Count + + Quantity of messages addressed to the remote device for which it + returned a Negative Acknowledge (NAK) exception response, since + its last restart, clear counters operation, or power-up. Exception + responses are described and listed in "MODBUS Application Protocol + Specification" document. + + 0x11 7 Return Slave Busy Count + + Quantity of messages addressed to the remote device for which it + returned a Slave Device Busy exception response, since its last + restart, clear counters operation, or power-up. Exception + responses are described and listed in "MODBUS Application + Protocol Specification" document. + + 0x12 8 Return Bus Character Overrun Count + + Quantity of messages addressed to the remote device that it could + not handle due to a character overrun condition, since its last + restart, clear counters operation, or power-up. A character + overrun is caused by data characters arriving at the port faster + than they can. + + .. note:: I threw the event counter in here for convinience + ''' + __data = dict([(i, 0x0000) for i in range(9)]) + __names = [ + 'BusMessage', + 'BusCommunicationError', + 'SlaveExceptionError', + 'SlaveMessage', + 'SlaveNoResponse', + 'SlaveNAK', + 'SlaveBusy', + 'BusCharacterOverrun' + 'Event ' + ] + + def __iter__(self): + ''' Iterater over the device counters + + :returns: An iterator of the device counters + ''' + return izip(self.__names, itervalues(self.__data)) + + def update(self, values): + ''' Update the values of this identity + using another identify as the value + + :param values: The value to copy values from + ''' + for k, v in iteritems(values): + v += self.__getattribute__(k) + self.__setattr__(k, v) + + def reset(self): + ''' This clears all of the system counters + ''' + self.__data = dict([(i, 0x0000) for i in range(9)]) + + def summary(self): + ''' Returns a summary of the counters current status + + :returns: A byte with each bit representing each counter + ''' + count, result = 0x01, 0x00 + for i in itervalues(self.__data): + if i != 0x00: result |= count + count <<= 1 + return result + + #-------------------------------------------------------------------------# + # Properties + #-------------------------------------------------------------------------# + BusMessage = dict_property(lambda s: s.__data, 0) + BusCommunicationError = dict_property(lambda s: s.__data, 1) + BusExceptionError = dict_property(lambda s: s.__data, 2) + SlaveMessage = dict_property(lambda s: s.__data, 3) + SlaveNoResponse = dict_property(lambda s: s.__data, 4) + SlaveNAK = dict_property(lambda s: s.__data, 5) + SlaveBusy = dict_property(lambda s: s.__data, 6) + BusCharacterOverrun = dict_property(lambda s: s.__data, 7) + Event = dict_property(lambda s: s.__data, 8) + + +#---------------------------------------------------------------------------# +# Main server controll block +#---------------------------------------------------------------------------# +
[docs]class ModbusControlBlock(Singleton): + ''' + This is a global singleotn that controls all system information + + All activity should be logged here and all diagnostic requests + should come from here. + ''' + + __mode = 'ASCII' + __diagnostic = [False] * 16 + __instance = None + __listen_only = False + __delimiter = '\r' + __counters = ModbusCountersHandler() + __identity = ModbusDeviceIdentification() + __plus = ModbusPlusStatistics() + __events = [] + + #-------------------------------------------------------------------------# + # Magic + #-------------------------------------------------------------------------# +
[docs] def __str__(self): + ''' Build a representation of the control block + + :returns: A string representation of the control block + ''' + return "ModbusControl"
+ +
[docs] def __iter__(self): + ''' Iterater over the device counters + + :returns: An iterator of the device counters + ''' + return self.__counters.__iter__()
+ + #-------------------------------------------------------------------------# + # Events + #-------------------------------------------------------------------------# +
[docs] def addEvent(self, event): + ''' Adds a new event to the event log + + :param event: A new event to add to the log + ''' + self.__events.insert(0, event) + self.__events = self.__events[0:64] # chomp to 64 entries + self.Counter.Event += 1
+ +
[docs] def getEvents(self): + ''' Returns an encoded collection of the event log. + + :returns: The encoded events packet + ''' + events = [event.encode() for event in self.__events] + return b''.join(events)
+ +
[docs] def clearEvents(self): + ''' Clears the current list of events + ''' + self.__events = []
+ + #-------------------------------------------------------------------------# + # Other Properties + #-------------------------------------------------------------------------# + Identity = property(lambda s: s.__identity) + Counter = property(lambda s: s.__counters) + Events = property(lambda s: s.__events) + Plus = property(lambda s: s.__plus) + +
[docs] def reset(self): + ''' This clears all of the system counters and the + diagnostic register + ''' + self.__events = [] + self.__counters.reset() + self.__diagnostic = [False] * 16
+ + #-------------------------------------------------------------------------# + # Listen Properties + #-------------------------------------------------------------------------# +
[docs] def _setListenOnly(self, value): + ''' This toggles the listen only status + + :param value: The value to set the listen status to + ''' + self.__listen_only = bool(value)
+ + ListenOnly = property(lambda s: s.__listen_only, _setListenOnly) + + #-------------------------------------------------------------------------# + # Mode Properties + #-------------------------------------------------------------------------# +
[docs] def _setMode(self, mode): + ''' This toggles the current serial mode + + :param mode: The data transfer method in (RTU, ASCII) + ''' + if mode in ['ASCII', 'RTU']: + self.__mode = mode
+ + Mode = property(lambda s: s.__mode, _setMode) + + #-------------------------------------------------------------------------# + # Delimiter Properties + #-------------------------------------------------------------------------# +
[docs] def _setDelimiter(self, char): + ''' This changes the serial delimiter character + + :param char: The new serial delimiter character + ''' + if isinstance(char, str): + self.__delimiter = char.encode() + if isinstance(char, bytes): + self.__delimiter = char + elif isinstance(char, int): + self.__delimiter = int2byte(char)
+ + Delimiter = property(lambda s: s.__delimiter, _setDelimiter) + + #-------------------------------------------------------------------------# + # Diagnostic Properties + #-------------------------------------------------------------------------# +
[docs] def setDiagnostic(self, mapping): + ''' This sets the value in the diagnostic register + + :param mapping: Dictionary of key:value pairs to set + ''' + for entry in iteritems(mapping): + if entry[0] >= 0 and entry[0] < len(self.__diagnostic): + self.__diagnostic[entry[0]] = (entry[1] != 0)
+ +
[docs] def getDiagnostic(self, bit): + ''' This gets the value in the diagnostic register + + :param bit: The bit to get + :returns: The current value of the requested bit + ''' + try: + if bit and bit >= 0 and bit < len(self.__diagnostic): + return self.__diagnostic[bit] + except Exception: + return None
+ +
[docs] def getDiagnosticRegister(self): + ''' This gets the entire diagnostic register + + :returns: The diagnostic register collection + ''' + return self.__diagnostic
+ +#---------------------------------------------------------------------------# +# Exported Identifiers +#---------------------------------------------------------------------------# +__all__ = [ + "ModbusAccessControl", + "ModbusPlusStatistics", + "ModbusDeviceIdentification", + "DeviceInformationFactory", + "ModbusControlBlock" +] +
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/doc/sphinx/html/_modules/pymodbus/diag_message.html b/doc/sphinx/html/_modules/pymodbus/diag_message.html new file mode 100644 index 000000000..b706258af --- /dev/null +++ b/doc/sphinx/html/_modules/pymodbus/diag_message.html @@ -0,0 +1,845 @@ + + + + + + + + pymodbus.diag_message — pymodbus 1.3.0.rc2 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for pymodbus.diag_message

+'''
+Diagnostic Record Read/Write
+------------------------------
+
+These need to be tied into a the current server context
+or linked to the appropriate data
+'''
+import struct
+
+from pymodbus.constants import ModbusStatus, ModbusPlusOperation
+from pymodbus.pdu import ModbusRequest
+from pymodbus.pdu import ModbusResponse
+from pymodbus.device import ModbusControlBlock
+from pymodbus.exceptions import NotImplementedException
+from pymodbus.utilities import pack_bitstring
+
+_MCB = ModbusControlBlock()
+
+
+#---------------------------------------------------------------------------#
+# Diagnostic Function Codes Base Classes
+# diagnostic 08, 00-18,20
+#---------------------------------------------------------------------------#
+# TODO Make sure all the data is decoded from the response
+#---------------------------------------------------------------------------#
+
[docs]class DiagnosticStatusRequest(ModbusRequest): + ''' + This is a base class for all of the diagnostic request functions + ''' + function_code = 0x08 + _rtu_frame_size = 8 + +
[docs] def __init__(self, **kwargs): + ''' + Base initializer for a diagnostic request + ''' + ModbusRequest.__init__(self, **kwargs) + self.message = None
+ +
[docs] def encode(self): + ''' + Base encoder for a diagnostic response + we encode the data set in self.message + + :returns: The encoded packet + ''' + packet = struct.pack('>H', self.sub_function_code) + if self.message is not None: + if isinstance(self.message, str): + packet += self.message.encode() + elif isinstance(self.message, bytes): + packet += self.message + elif isinstance(self.message, list): + for piece in self.message: + packet += struct.pack('>H', piece) + elif isinstance(self.message, int): + packet += struct.pack('>H', self.message) + return packet
+ +
[docs] def decode(self, data): + ''' Base decoder for a diagnostic request + + :param data: The data to decode into the function code + ''' + self.sub_function_code, self.message = struct.unpack('>HH', data)
+ +
[docs] def get_response_pdu_size(self): + """ + Func_code (1 byte) + Sub function code (2 byte) + Data (2 * N bytes) + :return: + """ + if not isinstance(self.message,list): + self.message = [self.message] + return 1 + 2 + 2 * len(self.message)
+ + + +
[docs]class DiagnosticStatusResponse(ModbusResponse): + ''' + This is a base class for all of the diagnostic response functions + + It works by performing all of the encoding and decoding of variable + data and lets the higher classes define what extra data to append + and how to execute a request + ''' + function_code = 0x08 + _rtu_frame_size = 8 + +
[docs] def __init__(self, **kwargs): + ''' + Base initializer for a diagnostic response + ''' + ModbusResponse.__init__(self, **kwargs) + self.message = None
+ +
[docs] def encode(self): + ''' + Base encoder for a diagnostic response + we encode the data set in self.message + + :returns: The encoded packet + ''' + packet = struct.pack('>H', self.sub_function_code) + if self.message is not None: + if isinstance(self.message, str): + packet += self.message.encode() + elif isinstance(self.message, bytes): + packet += self.message + elif isinstance(self.message, list): + for piece in self.message: + packet += struct.pack('>H', piece) + elif isinstance(self.message, int): + packet += struct.pack('>H', self.message) + return packet
+ +
[docs] def decode(self, data): + ''' Base decoder for a diagnostic response + + :param data: The data to decode into the function code + ''' + self.sub_function_code, self.message = struct.unpack('>HH', data)
+ + +
[docs]class DiagnosticStatusSimpleRequest(DiagnosticStatusRequest): + ''' + A large majority of the diagnostic functions are simple + status request functions. They work by sending 0x0000 + as data and their function code and they are returned + 2 bytes of data. + + If a function inherits this, they only need to implement + the execute method + ''' + +
[docs] def __init__(self, data=0x0000, **kwargs): + ''' + General initializer for a simple diagnostic request + + The data defaults to 0x0000 if not provided as over half + of the functions require it. + + :param data: The data to send along with the request + ''' + DiagnosticStatusRequest.__init__(self, **kwargs) + self.message = data
+ +
[docs] def execute(self, *args): + ''' Base function to raise if not implemented ''' + raise NotImplementedException("Diagnostic Message Has No Execute Method")
+ + +
[docs]class DiagnosticStatusSimpleResponse(DiagnosticStatusResponse): + ''' + A large majority of the diagnostic functions are simple + status request functions. They work by sending 0x0000 + as data and their function code and they are returned + 2 bytes of data. + ''' + +
[docs] def __init__(self, data=0x0000, **kwargs): + ''' General initializer for a simple diagnostic response + + :param data: The resulting data to return to the client + ''' + DiagnosticStatusResponse.__init__(self, **kwargs) + self.message = data
+ + +#---------------------------------------------------------------------------# +# Diagnostic Sub Code 00 +#---------------------------------------------------------------------------# +
[docs]class ReturnQueryDataRequest(DiagnosticStatusRequest): + ''' + The data passed in the request data field is to be returned (looped back) + in the response. The entire response message should be identical to the + request. + ''' + sub_function_code = 0x0000 + +
[docs] def __init__(self, message=0x0000, **kwargs): + ''' Initializes a new instance of the request + + :param message: The message to send to loopback + ''' + DiagnosticStatusRequest.__init__(self, **kwargs) + if isinstance(message, list): + self.message = message + else: + self.message = [message]
+ +
[docs] def execute(self, *args): + ''' Executes the loopback request (builds the response) + + :returns: The populated loopback response message + ''' + return ReturnQueryDataResponse(self.message)
+ + +
[docs]class ReturnQueryDataResponse(DiagnosticStatusResponse): + ''' + The data passed in the request data field is to be returned (looped back) + in the response. The entire response message should be identical to the + request. + ''' + sub_function_code = 0x0000 + +
[docs] def __init__(self, message=0x0000, **kwargs): + ''' Initializes a new instance of the response + + :param message: The message to loopback + ''' + DiagnosticStatusResponse.__init__(self, **kwargs) + if isinstance(message, list): + self.message = message + else: self.message = [message]
+ + +#---------------------------------------------------------------------------# +# Diagnostic Sub Code 01 +#---------------------------------------------------------------------------# +
[docs]class RestartCommunicationsOptionRequest(DiagnosticStatusRequest): + ''' + The remote device serial line port must be initialized and restarted, and + all of its communications event counters are cleared. If the port is + currently in Listen Only Mode, no response is returned. This function is + the only one that brings the port out of Listen Only Mode. If the port is + not currently in Listen Only Mode, a normal response is returned. This + occurs before the restart is executed. + ''' + sub_function_code = 0x0001 + +
[docs] def __init__(self, toggle=False, **kwargs): + ''' Initializes a new request + + :param toggle: Set to True to toggle, False otherwise + ''' + DiagnosticStatusRequest.__init__(self, **kwargs) + if toggle: + self.message = [ModbusStatus.On] + else: self.message = [ModbusStatus.Off]
+ +
[docs] def execute(self, *args): + ''' Clear event log and restart + + :returns: The initialized response message + ''' + #if _MCB.ListenOnly: + return RestartCommunicationsOptionResponse(self.message)
+ +
[docs]class RestartCommunicationsOptionResponse(DiagnosticStatusResponse): + ''' + The remote device serial line port must be initialized and restarted, and + all of its communications event counters are cleared. If the port is + currently in Listen Only Mode, no response is returned. This function is + the only one that brings the port out of Listen Only Mode. If the port is + not currently in Listen Only Mode, a normal response is returned. This + occurs before the restart is executed. + ''' + sub_function_code = 0x0001 + +
[docs] def __init__(self, toggle=False, **kwargs): + ''' Initializes a new response + + :param toggle: Set to True if we toggled, False otherwise + ''' + DiagnosticStatusResponse.__init__(self, **kwargs) + if toggle: + self.message = [ModbusStatus.On] + else: self.message = [ModbusStatus.Off]
+ + +#---------------------------------------------------------------------------# +# Diagnostic Sub Code 02 +#---------------------------------------------------------------------------# +
[docs]class ReturnDiagnosticRegisterRequest(DiagnosticStatusSimpleRequest): + ''' + The contents of the remote device's 16-bit diagnostic register are + returned in the response + ''' + sub_function_code = 0x0002 + +
[docs] def execute(self, *args): + ''' Execute the diagnostic request on the given device + + :returns: The initialized response message + ''' + #if _MCB.isListenOnly(): + register = pack_bitstring(_MCB.getDiagnosticRegister()) + return ReturnDiagnosticRegisterResponse(register)
+ + +
[docs]class ReturnDiagnosticRegisterResponse(DiagnosticStatusSimpleResponse): + ''' + The contents of the remote device's 16-bit diagnostic register are + returned in the response + ''' + sub_function_code = 0x0002
+ + +#---------------------------------------------------------------------------# +# Diagnostic Sub Code 03 +#---------------------------------------------------------------------------# +
[docs]class ChangeAsciiInputDelimiterRequest(DiagnosticStatusSimpleRequest): + ''' + The character 'CHAR' passed in the request data field becomes the end of + message delimiter for future messages (replacing the default LF + character). This function is useful in cases of a Line Feed is not + required at the end of ASCII messages. + ''' + sub_function_code = 0x0003 + +
[docs] def execute(self, *args): + ''' Execute the diagnostic request on the given device + + :returns: The initialized response message + ''' + char = (self.message & 0xff00) >> 8 + _MCB.Delimiter = char + return ChangeAsciiInputDelimiterResponse(self.message)
+ + +
[docs]class ChangeAsciiInputDelimiterResponse(DiagnosticStatusSimpleResponse): + ''' + The character 'CHAR' passed in the request data field becomes the end of + message delimiter for future messages (replacing the default LF + character). This function is useful in cases of a Line Feed is not + required at the end of ASCII messages. + ''' + sub_function_code = 0x0003
+ + +#---------------------------------------------------------------------------# +# Diagnostic Sub Code 04 +#---------------------------------------------------------------------------# +
[docs]class ForceListenOnlyModeRequest(DiagnosticStatusSimpleRequest): + ''' + Forces the addressed remote device to its Listen Only Mode for MODBUS + communications. This isolates it from the other devices on the network, + allowing them to continue communicating without interruption from the + addressed remote device. No response is returned. + ''' + sub_function_code = 0x0004 + +
[docs] def execute(self, *args): + ''' Execute the diagnostic request on the given device + + :returns: The initialized response message + ''' + _MCB.ListenOnly = True + return ForceListenOnlyModeResponse()
+ + +
[docs]class ForceListenOnlyModeResponse(DiagnosticStatusResponse): + ''' + Forces the addressed remote device to its Listen Only Mode for MODBUS + communications. This isolates it from the other devices on the network, + allowing them to continue communicating without interruption from the + addressed remote device. No response is returned. + + This does not send a response + ''' + sub_function_code = 0x0004 + should_respond = False + +
[docs] def __init__(self, **kwargs): + ''' Initializer to block a return response + ''' + DiagnosticStatusResponse.__init__(self, **kwargs) + self.message = []
+ + +#---------------------------------------------------------------------------# +# Diagnostic Sub Code 10 +#---------------------------------------------------------------------------# +
[docs]class ClearCountersRequest(DiagnosticStatusSimpleRequest): + ''' + The goal is to clear ll counters and the diagnostic register. + Also, counters are cleared upon power-up + ''' + sub_function_code = 0x000A + +
[docs] def execute(self, *args): + ''' Execute the diagnostic request on the given device + + :returns: The initialized response message + ''' + _MCB.reset() + return ClearCountersResponse(self.message)
+ + +
[docs]class ClearCountersResponse(DiagnosticStatusSimpleResponse): + ''' + The goal is to clear ll counters and the diagnostic register. + Also, counters are cleared upon power-up + ''' + sub_function_code = 0x000A
+ + +#---------------------------------------------------------------------------# +# Diagnostic Sub Code 11 +#---------------------------------------------------------------------------# +
[docs]class ReturnBusMessageCountRequest(DiagnosticStatusSimpleRequest): + ''' + The response data field returns the quantity of messages that the + remote device has detected on the communications systems since its last + restart, clear counters operation, or power-up + ''' + sub_function_code = 0x000B + +
[docs] def execute(self, *args): + ''' Execute the diagnostic request on the given device + + :returns: The initialized response message + ''' + count = _MCB.Counter.BusMessage + return ReturnBusMessageCountResponse(count)
+ + +
[docs]class ReturnBusMessageCountResponse(DiagnosticStatusSimpleResponse): + ''' + The response data field returns the quantity of messages that the + remote device has detected on the communications systems since its last + restart, clear counters operation, or power-up + ''' + sub_function_code = 0x000B
+ + +#---------------------------------------------------------------------------# +# Diagnostic Sub Code 12 +#---------------------------------------------------------------------------# +
[docs]class ReturnBusCommunicationErrorCountRequest(DiagnosticStatusSimpleRequest): + ''' + The response data field returns the quantity of CRC errors encountered + by the remote device since its last restart, clear counter operation, or + power-up + ''' + sub_function_code = 0x000C + +
[docs] def execute(self, *args): + ''' Execute the diagnostic request on the given device + + :returns: The initialized response message + ''' + count = _MCB.Counter.BusCommunicationError + return ReturnBusCommunicationErrorCountResponse(count)
+ + +
[docs]class ReturnBusCommunicationErrorCountResponse(DiagnosticStatusSimpleResponse): + ''' + The response data field returns the quantity of CRC errors encountered + by the remote device since its last restart, clear counter operation, or + power-up + ''' + sub_function_code = 0x000C
+ + +#---------------------------------------------------------------------------# +# Diagnostic Sub Code 13 +#---------------------------------------------------------------------------# +
[docs]class ReturnBusExceptionErrorCountRequest(DiagnosticStatusSimpleRequest): + ''' + The response data field returns the quantity of modbus exception + responses returned by the remote device since its last restart, + clear counters operation, or power-up + ''' + sub_function_code = 0x000D + +
[docs] def execute(self, *args): + ''' Execute the diagnostic request on the given device + + :returns: The initialized response message + ''' + count = _MCB.Counter.BusExceptionError + return ReturnBusExceptionErrorCountResponse(count)
+ + +
[docs]class ReturnBusExceptionErrorCountResponse(DiagnosticStatusSimpleResponse): + ''' + The response data field returns the quantity of modbus exception + responses returned by the remote device since its last restart, + clear counters operation, or power-up + ''' + sub_function_code = 0x000D
+ + +#---------------------------------------------------------------------------# +# Diagnostic Sub Code 14 +#---------------------------------------------------------------------------# +
[docs]class ReturnSlaveMessageCountRequest(DiagnosticStatusSimpleRequest): + ''' + The response data field returns the quantity of messages addressed to the + remote device, or broadcast, that the remote device has processed since + its last restart, clear counters operation, or power-up + ''' + sub_function_code = 0x000E + +
[docs] def execute(self, *args): + ''' Execute the diagnostic request on the given device + + :returns: The initialized response message + ''' + count = _MCB.Counter.SlaveMessage + return ReturnSlaveMessageCountResponse(count)
+ + +
[docs]class ReturnSlaveMessageCountResponse(DiagnosticStatusSimpleResponse): + ''' + The response data field returns the quantity of messages addressed to the + remote device, or broadcast, that the remote device has processed since + its last restart, clear counters operation, or power-up + ''' + sub_function_code = 0x000E
+ + +#---------------------------------------------------------------------------# +# Diagnostic Sub Code 15 +#---------------------------------------------------------------------------# +
[docs]class ReturnSlaveNoResponseCountRequest(DiagnosticStatusSimpleRequest): + ''' + The response data field returns the quantity of messages addressed to the + remote device, or broadcast, that the remote device has processed since + its last restart, clear counters operation, or power-up + ''' + sub_function_code = 0x000F + +
[docs] def execute(self, *args): + ''' Execute the diagnostic request on the given device + + :returns: The initialized response message + ''' + count = _MCB.Counter.SlaveNoResponse + return ReturnSlaveNoReponseCountResponse(count)
+ + +
[docs]class ReturnSlaveNoReponseCountResponse(DiagnosticStatusSimpleResponse): + ''' + The response data field returns the quantity of messages addressed to the + remote device, or broadcast, that the remote device has processed since + its last restart, clear counters operation, or power-up + ''' + sub_function_code = 0x000F
+ + +#---------------------------------------------------------------------------# +# Diagnostic Sub Code 16 +#---------------------------------------------------------------------------# +
[docs]class ReturnSlaveNAKCountRequest(DiagnosticStatusSimpleRequest): + ''' + The response data field returns the quantity of messages addressed to the + remote device for which it returned a Negative Acknowledge (NAK) exception + response, since its last restart, clear counters operation, or power-up. + Exception responses are described and listed in section 7 . + ''' + sub_function_code = 0x0010 + +
[docs] def execute(self, *args): + ''' Execute the diagnostic request on the given device + + :returns: The initialized response message + ''' + count = _MCB.Counter.SlaveNAK + return ReturnSlaveNAKCountResponse(count)
+ + +
[docs]class ReturnSlaveNAKCountResponse(DiagnosticStatusSimpleResponse): + ''' + The response data field returns the quantity of messages addressed to the + remote device for which it returned a Negative Acknowledge (NAK) exception + response, since its last restart, clear counters operation, or power-up. + Exception responses are described and listed in section 7. + ''' + sub_function_code = 0x0010
+ + +#---------------------------------------------------------------------------# +# Diagnostic Sub Code 17 +#---------------------------------------------------------------------------# +
[docs]class ReturnSlaveBusyCountRequest(DiagnosticStatusSimpleRequest): + ''' + The response data field returns the quantity of messages addressed to the + remote device for which it returned a Slave Device Busy exception response, + since its last restart, clear counters operation, or power-up. + ''' + sub_function_code = 0x0011 + +
[docs] def execute(self, *args): + ''' Execute the diagnostic request on the given device + + :returns: The initialized response message + ''' + count = _MCB.Counter.SlaveBusy + return ReturnSlaveBusyCountResponse(count)
+ + +
[docs]class ReturnSlaveBusyCountResponse(DiagnosticStatusSimpleResponse): + ''' + The response data field returns the quantity of messages addressed to the + remote device for which it returned a Slave Device Busy exception response, + since its last restart, clear counters operation, or power-up. + ''' + sub_function_code = 0x0011
+ + +#---------------------------------------------------------------------------# +# Diagnostic Sub Code 18 +#---------------------------------------------------------------------------# +
[docs]class ReturnSlaveBusCharacterOverrunCountRequest(DiagnosticStatusSimpleRequest): + ''' + The response data field returns the quantity of messages addressed to the + remote device that it could not handle due to a character overrun condition, + since its last restart, clear counters operation, or power-up. A character + overrun is caused by data characters arriving at the port faster than they + can be stored, or by the loss of a character due to a hardware malfunction. + ''' + sub_function_code = 0x0012 + +
[docs] def execute(self, *args): + ''' Execute the diagnostic request on the given device + + :returns: The initialized response message + ''' + count = _MCB.Counter.BusCharacterOverrun + return ReturnSlaveBusCharacterOverrunCountResponse(count)
+ + +
[docs]class ReturnSlaveBusCharacterOverrunCountResponse(DiagnosticStatusSimpleResponse): + ''' + The response data field returns the quantity of messages addressed to the + remote device that it could not handle due to a character overrun condition, + since its last restart, clear counters operation, or power-up. A character + overrun is caused by data characters arriving at the port faster than they + can be stored, or by the loss of a character due to a hardware malfunction. + ''' + sub_function_code = 0x0012
+ + +#---------------------------------------------------------------------------# +# Diagnostic Sub Code 19 +#---------------------------------------------------------------------------# +
[docs]class ReturnIopOverrunCountRequest(DiagnosticStatusSimpleRequest): + ''' + An IOP overrun is caused by data characters arriving at the port + faster than they can be stored, or by the loss of a character due + to a hardware malfunction. This function is specific to the 884. + ''' + sub_function_code = 0x0013 + +
[docs] def execute(self, *args): + ''' Execute the diagnostic request on the given device + + :returns: The initialized response message + ''' + count = _MCB.Counter.BusCharacterOverrun + return ReturnIopOverrunCountResponse(count)
+ + +
[docs]class ReturnIopOverrunCountResponse(DiagnosticStatusSimpleResponse): + ''' + The response data field returns the quantity of messages + addressed to the slave that it could not handle due to an 884 + IOP overrun condition, since its last restart, clear counters + operation, or power-up. + ''' + sub_function_code = 0x0013
+ + +#---------------------------------------------------------------------------# +# Diagnostic Sub Code 20 +#---------------------------------------------------------------------------# +
[docs]class ClearOverrunCountRequest(DiagnosticStatusSimpleRequest): + ''' + Clears the overrun error counter and reset the error flag + + An error flag should be cleared, but nothing else in the + specification mentions is, so it is ignored. + ''' + sub_function_code = 0x0014 + +
[docs] def execute(self, *args): + ''' Execute the diagnostic request on the given device + + :returns: The initialized response message + ''' + _MCB.Counter.BusCharacterOverrun = 0x0000 + return ClearOverrunCountResponse(self.message)
+ + +
[docs]class ClearOverrunCountResponse(DiagnosticStatusSimpleResponse): + ''' + Clears the overrun error counter and reset the error flag + ''' + sub_function_code = 0x0014
+ + +#---------------------------------------------------------------------------# +# Diagnostic Sub Code 21 +#---------------------------------------------------------------------------# +
[docs]class GetClearModbusPlusRequest(DiagnosticStatusSimpleRequest): + ''' + In addition to the Function code (08) and Subfunction code + (00 15 hex) in the query, a two-byte Operation field is used + to specify either a 'Get Statistics' or a 'Clear Statistics' + operation. The two operations are exclusive - the 'Get' + operation cannot clear the statistics, and the 'Clear' + operation does not return statistics prior to clearing + them. Statistics are also cleared on power-up of the slave + device. + ''' + sub_function_code = 0x0015 + +
[docs] def execute(self, *args): + ''' Execute the diagnostic request on the given device + + :returns: The initialized response message + ''' + message = None # the clear operation does not return info + if self.message == ModbusPlusOperation.ClearStatistics: + _MCB.Plus.reset() + else: message = _MCB.Plus.encode() + return GetClearModbusPlusResponse(message)
+ + +
[docs]class GetClearModbusPlusResponse(DiagnosticStatusSimpleResponse): + ''' + Returns a series of 54 16-bit words (108 bytes) in the data field + of the response (this function differs from the usual two-byte + length of the data field). The data contains the statistics for + the Modbus Plus peer processor in the slave device. + ''' + sub_function_code = 0x0015
+ + +#---------------------------------------------------------------------------# +# Exported symbols +#---------------------------------------------------------------------------# +__all__ = [ + "DiagnosticStatusRequest", "DiagnosticStatusResponse", + "ReturnQueryDataRequest", "ReturnQueryDataResponse", + "RestartCommunicationsOptionRequest", "RestartCommunicationsOptionResponse", + "ReturnDiagnosticRegisterRequest", "ReturnDiagnosticRegisterResponse", + "ChangeAsciiInputDelimiterRequest", "ChangeAsciiInputDelimiterResponse", + "ForceListenOnlyModeRequest", "ForceListenOnlyModeResponse", + "ClearCountersRequest", "ClearCountersResponse", + "ReturnBusMessageCountRequest", "ReturnBusMessageCountResponse", + "ReturnBusCommunicationErrorCountRequest", "ReturnBusCommunicationErrorCountResponse", + "ReturnBusExceptionErrorCountRequest", "ReturnBusExceptionErrorCountResponse", + "ReturnSlaveMessageCountRequest", "ReturnSlaveMessageCountResponse", + "ReturnSlaveNoResponseCountRequest", "ReturnSlaveNoReponseCountResponse", + "ReturnSlaveNAKCountRequest", "ReturnSlaveNAKCountResponse", + "ReturnSlaveBusyCountRequest", "ReturnSlaveBusyCountResponse", + "ReturnSlaveBusCharacterOverrunCountRequest", "ReturnSlaveBusCharacterOverrunCountResponse", + "ReturnIopOverrunCountRequest", "ReturnIopOverrunCountResponse", + "ClearOverrunCountRequest", "ClearOverrunCountResponse", + "GetClearModbusPlusRequest", "GetClearModbusPlusResponse", +] +
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/doc/sphinx/html/_modules/pymodbus/events.html b/doc/sphinx/html/_modules/pymodbus/events.html new file mode 100644 index 000000000..1b3a1f593 --- /dev/null +++ b/doc/sphinx/html/_modules/pymodbus/events.html @@ -0,0 +1,287 @@ + + + + + + + + pymodbus.events — pymodbus 1.3.0.rc2 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for pymodbus.events

+'''
+Modbus Remote Events
+------------------------------------------------------------
+
+An event byte returned by the Get Communications Event Log function
+can be any one of four types. The type is defined by bit 7
+(the high-order bit) in each byte. It may be further defined by bit 6.
+'''
+from pymodbus.exceptions import NotImplementedException
+from pymodbus.exceptions import ParameterException
+from pymodbus.utilities import pack_bitstring, unpack_bitstring
+
+
+
[docs]class ModbusEvent(object): + +
[docs] def encode(self): + ''' Encodes the status bits to an event message + + :returns: The encoded event message + ''' + raise NotImplementedException()
+ +
[docs] def decode(self, event): + ''' Decodes the event message to its status bits + + :param event: The event to decode + ''' + raise NotImplementedException()
+ + +
[docs]class RemoteReceiveEvent(ModbusEvent): + ''' Remote device MODBUS Receive Event + + The remote device stores this type of event byte when a query message + is received. It is stored before the remote device processes the message. + This event is defined by bit 7 set to logic '1'. The other bits will be + set to a logic '1' if the corresponding condition is TRUE. The bit layout + is:: + + Bit Contents + ---------------------------------- + 0 Not Used + 2 Not Used + 3 Not Used + 4 Character Overrun + 5 Currently in Listen Only Mode + 6 Broadcast Receive + 7 1 + ''' + +
[docs] def __init__(self, **kwargs): + ''' Initialize a new event instance + ''' + self.overrun = kwargs.get('overrun', False) + self.listen = kwargs.get('listen', False) + self.broadcast = kwargs.get('broadcast', False)
+ +
[docs] def encode(self): + ''' Encodes the status bits to an event message + + :returns: The encoded event message + ''' + bits = [False] * 3 + bits += [self.overrun, self.listen, self.broadcast, True] + packet = pack_bitstring(bits) + return packet
+ +
[docs] def decode(self, event): + ''' Decodes the event message to its status bits + + :param event: The event to decode + ''' + bits = unpack_bitstring(event) + self.overrun = bits[4] + self.listen = bits[5] + self.broadcast = bits[6]
+ + +
[docs]class RemoteSendEvent(ModbusEvent): + ''' Remote device MODBUS Send Event + + The remote device stores this type of event byte when it finishes + processing a request message. It is stored if the remote device + returned a normal or exception response, or no response. + + This event is defined by bit 7 set to a logic '0', with bit 6 set to a '1'. + The other bits will be set to a logic '1' if the corresponding + condition is TRUE. The bit layout is:: + + Bit Contents + ----------------------------------------------------------- + 0 Read Exception Sent (Exception Codes 1-3) + 1 Slave Abort Exception Sent (Exception Code 4) + 2 Slave Busy Exception Sent (Exception Codes 5-6) + 3 Slave Program NAK Exception Sent (Exception Code 7) + 4 Write Timeout Error Occurred + 5 Currently in Listen Only Mode + 6 1 + 7 0 + ''' + +
[docs] def __init__(self, **kwargs): + ''' Initialize a new event instance + ''' + self.read = kwargs.get('read', False) + self.slave_abort = kwargs.get('slave_abort', False) + self.slave_busy = kwargs.get('slave_busy', False) + self.slave_nak = kwargs.get('slave_nak', False) + self.write_timeout = kwargs.get('write_timeout', False) + self.listen = kwargs.get('listen', False)
+ +
[docs] def encode(self): + ''' Encodes the status bits to an event message + + :returns: The encoded event message + ''' + bits = [self.read, self.slave_abort, self.slave_busy, + self.slave_nak, self.write_timeout, self.listen] + bits += [True, False] + packet = pack_bitstring(bits) + return packet
+ +
[docs] def decode(self, event): + ''' Decodes the event message to its status bits + + :param event: The event to decode + ''' + # todo fix the start byte count + bits = unpack_bitstring(event) + self.read = bits[0] + self.slave_abort = bits[1] + self.slave_busy = bits[2] + self.slave_nak = bits[3] + self.write_timeout = bits[4] + self.listen = bits[5]
+ + +
[docs]class EnteredListenModeEvent(ModbusEvent): + ''' Remote device Entered Listen Only Mode + + The remote device stores this type of event byte when it enters + the Listen Only Mode. The event is defined by a content of 04 hex. + ''' + + value = 0x04 + __encoded = b'\x04' + +
[docs] def encode(self): + ''' Encodes the status bits to an event message + + :returns: The encoded event message + ''' + return self.__encoded
+ +
[docs] def decode(self, event): + ''' Decodes the event message to its status bits + + :param event: The event to decode + ''' + if event != self.__encoded: + raise ParameterException('Invalid decoded value')
+ + +
[docs]class CommunicationRestartEvent(ModbusEvent): + ''' Remote device Initiated Communication Restart + + The remote device stores this type of event byte when its communications + port is restarted. The remote device can be restarted by the Diagnostics + function (code 08), with sub-function Restart Communications Option + (code 00 01). + + That function also places the remote device into a 'Continue on Error' + or 'Stop on Error' mode. If the remote device is placed into 'Continue on + Error' mode, the event byte is added to the existing event log. If the + remote device is placed into 'Stop on Error' mode, the byte is added to + the log and the rest of the log is cleared to zeros. + + The event is defined by a content of zero. + ''' + + value = 0x00 + __encoded = b'\x00' + +
[docs] def encode(self): + ''' Encodes the status bits to an event message + + :returns: The encoded event message + ''' + return self.__encoded
+ +
[docs] def decode(self, event): + ''' Decodes the event message to its status bits + + :param event: The event to decode + ''' + if event != self.__encoded: + raise ParameterException('Invalid decoded value')
+
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/doc/sphinx/html/_modules/pymodbus/exceptions.html b/doc/sphinx/html/_modules/pymodbus/exceptions.html new file mode 100644 index 000000000..eafc77a48 --- /dev/null +++ b/doc/sphinx/html/_modules/pymodbus/exceptions.html @@ -0,0 +1,177 @@ + + + + + + + + pymodbus.exceptions — pymodbus 1.3.0.rc2 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for pymodbus.exceptions

+'''
+Pymodbus Exceptions
+--------------------
+
+Custom exceptions to be used in the Modbus code.
+'''
+
+
+
[docs]class ModbusException(Exception): + ''' Base modbus exception ''' + +
[docs] def __init__(self, string): + ''' Initialize the exception + :param string: The message to append to the error + ''' + self.string = string
+ + def __str__(self): + return 'Modbus Error: %s' % self.string
+ + +
[docs]class ModbusIOException(ModbusException): + ''' Error resulting from data i/o ''' + +
[docs] def __init__(self, string=""): + ''' Initialize the exception + :param string: The message to append to the error + ''' + message = "[Input/Output] %s" % string + ModbusException.__init__(self, message)
+ + +
[docs]class ParameterException(ModbusException): + ''' Error resulting from invalid parameter ''' + +
[docs] def __init__(self, string=""): + ''' Initialize the exception + + :param string: The message to append to the error + ''' + message = "[Invalid Parameter] %s" % string + ModbusException.__init__(self, message)
+ + +class NoSuchSlaveException(ModbusException): + ''' Error resulting from making a request to a slave + that does not exist ''' + + def __init__(self, string=""): + ''' Initialize the exception + + :param string: The message to append to the error + ''' + message = "[No Such Slave] %s" % string + ModbusException.__init__(self, message) + + +
[docs]class NotImplementedException(ModbusException): + ''' Error resulting from not implemented function ''' + +
[docs] def __init__(self, string=""): + ''' Initialize the exception + :param string: The message to append to the error + ''' + message = "[Not Implemented] %s" % string + ModbusException.__init__(self, message)
+ + +class ConnectionException(ModbusException): + ''' Error resulting from a bad connection ''' + + def __init__(self, string=""): + ''' Initialize the exception + + :param string: The message to append to the error + ''' + message = "[Connection] %s" % string + ModbusException.__init__(self, message) + +#---------------------------------------------------------------------------# +# Exported symbols +#---------------------------------------------------------------------------# +__all__ = [ + "ModbusException", "ModbusIOException", + "ParameterException", "NotImplementedException", + "ConnectionException", "NoSuchSlaveException", +] +
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/doc/sphinx/html/_modules/pymodbus/factory.html b/doc/sphinx/html/_modules/pymodbus/factory.html new file mode 100644 index 000000000..cfffdf5d4 --- /dev/null +++ b/doc/sphinx/html/_modules/pymodbus/factory.html @@ -0,0 +1,347 @@ + + + + + + + + pymodbus.factory — pymodbus 1.3.0.rc2 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for pymodbus.factory

+"""
+Modbus Request/Response Decoder Factories
+-------------------------------------------
+
+The following factories make it easy to decode request/response messages.
+To add a new request/response pair to be decodeable by the library, simply
+add them to the respective function lookup table (order doesn't matter, but
+it does help keep things organized).
+
+Regardless of how many functions are added to the lookup, O(1) behavior is
+kept as a result of a pre-computed lookup dictionary.
+"""
+
+from pymodbus.pdu import IllegalFunctionRequest
+from pymodbus.pdu import ExceptionResponse
+from pymodbus.pdu import ModbusExceptions as ecode
+from pymodbus.interfaces import IModbusDecoder
+from pymodbus.exceptions import ModbusException
+from pymodbus.bit_read_message import *
+from pymodbus.bit_write_message import *
+from pymodbus.diag_message import *
+from pymodbus.file_message import *
+from pymodbus.other_message import *
+from pymodbus.mei_message import *
+from pymodbus.register_read_message import *
+from pymodbus.register_write_message import *
+from pymodbus.compat import byte2int
+
+#---------------------------------------------------------------------------#
+# Logging
+#---------------------------------------------------------------------------#
+import logging
+_logger = logging.getLogger(__name__)
+
+
+#---------------------------------------------------------------------------#
+# Server Decoder
+#---------------------------------------------------------------------------#
+
[docs]class ServerDecoder(IModbusDecoder): + ''' Request Message Factory (Server) + + To add more implemented functions, simply add them to the list + ''' + __function_table = [ + ReadHoldingRegistersRequest, + ReadDiscreteInputsRequest, + ReadInputRegistersRequest, + ReadCoilsRequest, + WriteMultipleCoilsRequest, + WriteMultipleRegistersRequest, + WriteSingleRegisterRequest, + WriteSingleCoilRequest, + ReadWriteMultipleRegistersRequest, + + DiagnosticStatusRequest, + + ReadExceptionStatusRequest, + GetCommEventCounterRequest, + GetCommEventLogRequest, + ReportSlaveIdRequest, + + ReadFileRecordRequest, + WriteFileRecordRequest, + MaskWriteRegisterRequest, + ReadFifoQueueRequest, + + ReadDeviceInformationRequest, + ] + __sub_function_table = [ + ReturnQueryDataRequest, + RestartCommunicationsOptionRequest, + ReturnDiagnosticRegisterRequest, + ChangeAsciiInputDelimiterRequest, + ForceListenOnlyModeRequest, + ClearCountersRequest, + ReturnBusMessageCountRequest, + ReturnBusCommunicationErrorCountRequest, + ReturnBusExceptionErrorCountRequest, + ReturnSlaveMessageCountRequest, + ReturnSlaveNoResponseCountRequest, + ReturnSlaveNAKCountRequest, + ReturnSlaveBusyCountRequest, + ReturnSlaveBusCharacterOverrunCountRequest, + ReturnIopOverrunCountRequest, + ClearOverrunCountRequest, + GetClearModbusPlusRequest, + + ReadDeviceInformationRequest, + ] + +
[docs] def __init__(self): + ''' Initializes the client lookup tables + ''' + functions = set(f.function_code for f in self.__function_table) + self.__lookup = dict([(f.function_code, f) for f in self.__function_table]) + self.__sub_lookup = dict((f, {}) for f in functions) + for f in self.__sub_function_table: + self.__sub_lookup[f.function_code][f.sub_function_code] = f
+ +
[docs] def decode(self, message): + ''' Wrapper to decode a request packet + + :param message: The raw modbus request packet + :return: The decoded modbus message or None if error + ''' + try: + return self._helper(message) + except ModbusException as er: + _logger.warning("Unable to decode request %s" % er) + return None
+ +
[docs] def lookupPduClass(self, function_code): + ''' Use `function_code` to determine the class of the PDU. + + :param function_code: The function code specified in a frame. + :returns: The class of the PDU that has a matching `function_code`. + ''' + return self.__lookup.get(function_code, ExceptionResponse)
+ +
[docs] def _helper(self, data): + ''' + This factory is used to generate the correct request object + from a valid request packet. This decodes from a list of the + currently implemented request types. + + :param data: The request packet to decode + :returns: The decoded request or illegal function request object + ''' + function_code = byte2int(data[0]) + _logger.debug("Factory Request[%d]" % function_code) + request = self.__lookup.get(function_code, lambda: None)() + if not request: + request = IllegalFunctionRequest(function_code) + request.decode(data[1:]) + + if hasattr(request, 'sub_function_code'): + lookup = self.__sub_lookup.get(request.function_code, {}) + subtype = lookup.get(request.sub_function_code, None) + if subtype: request.__class__ = subtype + + return request
+ + +#---------------------------------------------------------------------------# +# Client Decoder +#---------------------------------------------------------------------------# +
[docs]class ClientDecoder(IModbusDecoder): + ''' Response Message Factory (Client) + + To add more implemented functions, simply add them to the list + ''' + __function_table = [ + ReadHoldingRegistersResponse, + ReadDiscreteInputsResponse, + ReadInputRegistersResponse, + ReadCoilsResponse, + WriteMultipleCoilsResponse, + WriteMultipleRegistersResponse, + WriteSingleRegisterResponse, + WriteSingleCoilResponse, + ReadWriteMultipleRegistersResponse, + + DiagnosticStatusResponse, + + ReadExceptionStatusResponse, + GetCommEventCounterResponse, + GetCommEventLogResponse, + ReportSlaveIdResponse, + + ReadFileRecordResponse, + WriteFileRecordResponse, + MaskWriteRegisterResponse, + ReadFifoQueueResponse, + + ReadDeviceInformationResponse, + ] + __sub_function_table = [ + ReturnQueryDataResponse, + RestartCommunicationsOptionResponse, + ReturnDiagnosticRegisterResponse, + ChangeAsciiInputDelimiterResponse, + ForceListenOnlyModeResponse, + ClearCountersResponse, + ReturnBusMessageCountResponse, + ReturnBusCommunicationErrorCountResponse, + ReturnBusExceptionErrorCountResponse, + ReturnSlaveMessageCountResponse, + ReturnSlaveNoReponseCountResponse, + ReturnSlaveNAKCountResponse, + ReturnSlaveBusyCountResponse, + ReturnSlaveBusCharacterOverrunCountResponse, + ReturnIopOverrunCountResponse, + ClearOverrunCountResponse, + GetClearModbusPlusResponse, + + ReadDeviceInformationResponse, + ] + +
[docs] def __init__(self): + ''' Initializes the client lookup tables + ''' + functions = set(f.function_code for f in self.__function_table) + self.__lookup = dict([(f.function_code, f) for f in self.__function_table]) + self.__sub_lookup = dict((f, {}) for f in functions) + for f in self.__sub_function_table: + self.__sub_lookup[f.function_code][f.sub_function_code] = f
+ +
[docs] def lookupPduClass(self, function_code): + ''' Use `function_code` to determine the class of the PDU. + + :param function_code: The function code specified in a frame. + :returns: The class of the PDU that has a matching `function_code`. + ''' + return self.__lookup.get(function_code, ExceptionResponse)
+ +
[docs] def decode(self, message): + ''' Wrapper to decode a response packet + + :param message: The raw packet to decode + :return: The decoded modbus message or None if error + ''' + try: + return self._helper(message) + except ModbusException as er: + _logger.error("Unable to decode response %s" % er) + return None
+ +
[docs] def _helper(self, data): + ''' + This factory is used to generate the correct response object + from a valid response packet. This decodes from a list of the + currently implemented request types. + + :param data: The response packet to decode + :returns: The decoded request or an exception response object + ''' + function_code = byte2int(data[0]) + _logger.debug("Factory Response[%d]" % function_code) + response = self.__lookup.get(function_code, lambda: None)() + if function_code > 0x80: + code = function_code & 0x7f # strip error portion + response = ExceptionResponse(code, ecode.IllegalFunction) + if not response: + raise ModbusException("Unknown response %d" % function_code) + response.decode(data[1:]) + + if hasattr(response, 'sub_function_code'): + lookup = self.__sub_lookup.get(response.function_code, {}) + subtype = lookup.get(response.sub_function_code, None) + if subtype: response.__class__ = subtype + + return response
+ +#---------------------------------------------------------------------------# +# Exported symbols +#---------------------------------------------------------------------------# +__all__ = ['ServerDecoder', 'ClientDecoder'] +
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/doc/sphinx/html/_modules/pymodbus/file_message.html b/doc/sphinx/html/_modules/pymodbus/file_message.html new file mode 100644 index 000000000..fcc06fca1 --- /dev/null +++ b/doc/sphinx/html/_modules/pymodbus/file_message.html @@ -0,0 +1,576 @@ + + + + + + + + pymodbus.file_message — pymodbus 1.3.0.rc2 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for pymodbus.file_message

+'''
+File Record Read/Write Messages
+-------------------------------
+
+Currently none of these messages are implemented
+'''
+import struct
+from pymodbus.pdu import ModbusRequest
+from pymodbus.pdu import ModbusResponse
+from pymodbus.pdu import ModbusExceptions as merror
+from pymodbus.compat import byte2int
+
+
+#---------------------------------------------------------------------------#
+# File Record Types
+#---------------------------------------------------------------------------#
+
[docs]class FileRecord(object): + ''' Represents a file record and its relevant data. + ''' + +
[docs] def __init__(self, **kwargs): + ''' Initializes a new instance + + :params reference_type: Defaults to 0x06 (must be) + :params file_number: Indicates which file number we are reading + :params record_number: Indicates which record in the file + :params record_data: The actual data of the record + :params record_length: The length in registers of the record + :params response_length: The length in bytes of the record + ''' + self.reference_type = kwargs.get('reference_type', 0x06) + self.file_number = kwargs.get('file_number', 0x00) + self.record_number = kwargs.get('record_number', 0x00) + self.record_data = kwargs.get('record_data', '') + + self.record_length = kwargs.get('record_length', len(self.record_data) // 2) + self.response_length = kwargs.get('response_length', len(self.record_data) + 1)
+ +
[docs] def __eq__(self, relf): + ''' Compares the left object to the right + ''' + return self.reference_type == relf.reference_type \ + and self.file_number == relf.file_number \ + and self.record_number == relf.record_number \ + and self.record_length == relf.record_length \ + and self.record_data == relf.record_data
+ +
[docs] def __ne__(self, relf): + ''' Compares the left object to the right + ''' + return not self.__eq__(relf)
+ +
[docs] def __repr__(self): + ''' Gives a representation of the file record + ''' + params = (self.file_number, self.record_number, self.record_length) + return 'FileRecord(file=%d, record=%d, length=%d)' % params
+ + +#---------------------------------------------------------------------------# +# File Requests/Responses +#---------------------------------------------------------------------------# +
[docs]class ReadFileRecordRequest(ModbusRequest): + ''' + This function code is used to perform a file record read. All request + data lengths are provided in terms of number of bytes and all record + lengths are provided in terms of registers. + + A file is an organization of records. Each file contains 10000 records, + addressed 0000 to 9999 decimal or 0x0000 to 0x270f. For example, record + 12 is addressed as 12. The function can read multiple groups of + references. The groups can be separating (non-contiguous), but the + references within each group must be sequential. Each group is defined + in a seperate 'sub-request' field that contains seven bytes:: + + The reference type: 1 byte (must be 0x06) + The file number: 2 bytes + The starting record number within the file: 2 bytes + The length of the record to be read: 2 bytes + + The quantity of registers to be read, combined with all other fields + in the expected response, must not exceed the allowable length of the + MODBUS PDU: 235 bytes. + ''' + function_code = 0x14 + _rtu_byte_count_pos = 2 + +
[docs] def __init__(self, records=None, **kwargs): + ''' Initializes a new instance + + :param records: The file record requests to be read + ''' + ModbusRequest.__init__(self, **kwargs) + self.records = records or []
+ +
[docs] def encode(self): + ''' Encodes the request packet + + :returns: The byte encoded packet + ''' + packet = struct.pack('B', len(self.records) * 7) + for record in self.records: + packet += struct.pack('>BHHH', 0x06, record.file_number, + record.record_number, record.record_length) + return packet
+ +
[docs] def decode(self, data): + ''' Decodes the incoming request + + :param data: The data to decode into the address + ''' + self.records = [] + byte_count = byte2int(data[0]) + for count in range(1, byte_count, 7): + decoded = struct.unpack('>BHHH', data[count:count+7]) + record = FileRecord(file_number=decoded[1], + record_number=decoded[2], record_length=decoded[3]) + if decoded[0] == 0x06: self.records.append(record)
+ +
[docs] def execute(self, context): + ''' Run a read exeception status request against the store + + :param context: The datastore to request from + :returns: The populated response + ''' + # TODO do some new context operation here + # if file number, record number, or address + length + # is too big, return an error. + files = [] + return ReadFileRecordResponse(files)
+ + +
[docs]class ReadFileRecordResponse(ModbusResponse): + ''' + The normal response is a series of 'sub-responses,' one for each + 'sub-request.' The byte count field is the total combined count of + bytes in all 'sub-responses.' In addition, each 'sub-response' + contains a field that shows its own byte count. + ''' + function_code = 0x14 + _rtu_byte_count_pos = 2 + +
[docs] def __init__(self, records=None, **kwargs): + ''' Initializes a new instance + + :param records: The requested file records + ''' + ModbusResponse.__init__(self, **kwargs) + self.records = records or []
+ +
[docs] def encode(self): + ''' Encodes the response + + :returns: The byte encoded message + ''' + total = sum(record.response_length + 1 for record in self.records) + packet = struct.pack('B', total) + for record in self.records: + packet += struct.pack('>BB', 0x06, record.record_length) + packet += record.record_data + return packet
+ +
[docs] def decode(self, data): + ''' Decodes a the response + + :param data: The packet data to decode + ''' + count, self.records = 1, [] + byte_count = byte2int(data[0]) + while count < byte_count: + response_length, reference_type = struct.unpack('>BB', data[count:count+2]) + count += response_length + 1 # the count is not included + record = FileRecord(response_length=response_length, + record_data=data[count - response_length + 1:count]) + if reference_type == 0x06: self.records.append(record)
+ + +
[docs]class WriteFileRecordRequest(ModbusRequest): + ''' + This function code is used to perform a file record write. All + request data lengths are provided in terms of number of bytes + and all record lengths are provided in terms of the number of 16 + bit words. + ''' + function_code = 0x15 + _rtu_byte_count_pos = 2 + +
[docs] def __init__(self, records=None, **kwargs): + ''' Initializes a new instance + + :param records: The file record requests to be read + ''' + ModbusRequest.__init__(self, **kwargs) + self.records = records or []
+ +
[docs] def encode(self): + ''' Encodes the request packet + + :returns: The byte encoded packet + ''' + total_length = sum((record.record_length * 2) + 7 for record in self.records) + packet = struct.pack('B', total_length) + for record in self.records: + packet += struct.pack('>BHHH', 0x06, record.file_number, + record.record_number, record.record_length) + packet += record.record_data + return packet
+ +
[docs] def decode(self, data): + ''' Decodes the incoming request + + :param data: The data to decode into the address + ''' + count, self.records = 1, [] + byte_count = byte2int(data[0]) + while count < byte_count: + decoded = struct.unpack('>BHHH', data[count:count+7]) + response_length = decoded[3] * 2 + count += response_length + 7 + record = FileRecord(record_length=decoded[3], + file_number=decoded[1], record_number=decoded[2], + record_data=data[count - response_length:count]) + if decoded[0] == 0x06: self.records.append(record)
+ +
[docs] def execute(self, context): + ''' Run the write file record request against the context + + :param context: The datastore to request from + :returns: The populated response + ''' + # TODO do some new context operation here + # if file number, record number, or address + length + # is too big, return an error. + return WriteFileRecordResponse(self.records)
+ + +
[docs]class WriteFileRecordResponse(ModbusResponse): + ''' + The normal response is an echo of the request. + ''' + function_code = 0x15 + _rtu_byte_count_pos = 2 + +
[docs] def __init__(self, records=None, **kwargs): + ''' Initializes a new instance + + :param records: The file record requests to be read + ''' + ModbusResponse.__init__(self, **kwargs) + self.records = records or []
+ +
[docs] def encode(self): + ''' Encodes the response + + :returns: The byte encoded message + ''' + total_length = sum((record.record_length * 2) + 7 for record in self.records) + packet = struct.pack('B', total_length) + for record in self.records: + packet += struct.pack('>BHHH', 0x06, record.file_number, + record.record_number, record.record_length) + packet += record.record_data + return packet
+ +
[docs] def decode(self, data): + ''' Decodes the incoming request + + :param data: The data to decode into the address + ''' + count, self.records = 1, [] + byte_count = byte2int(data[0]) + while count < byte_count: + decoded = struct.unpack('>BHHH', data[count:count+7]) + response_length = decoded[3] * 2 + count += response_length + 7 + record = FileRecord(record_length=decoded[3], + file_number=decoded[1], record_number=decoded[2], + record_data=data[count - response_length:count]) + if decoded[0] == 0x06: self.records.append(record)
+ + +class MaskWriteRegisterRequest(ModbusRequest): + ''' + This function code is used to modify the contents of a specified holding + register using a combination of an AND mask, an OR mask, and the + register's current contents. The function can be used to set or clear + individual bits in the register. + ''' + function_code = 0x16 + _rtu_frame_size = 10 + + + def __init__(self, address=0x0000, and_mask=0xffff, or_mask=0x0000, **kwargs): + ''' Initializes a new instance + + :param address: The mask pointer address (0x0000 to 0xffff) + :param and_mask: The and bitmask to apply to the register address + :param or_mask: The or bitmask to apply to the register address + ''' + ModbusRequest.__init__(self, **kwargs) + self.address = address + self.and_mask = and_mask + self.or_mask = or_mask + + def encode(self): + ''' Encodes the request packet + + :returns: The byte encoded packet + ''' + return struct.pack('>HHH', self.address, self.and_mask, self.or_mask) + + def decode(self, data): + ''' Decodes the incoming request + + :param data: The data to decode into the address + ''' + self.address, self.and_mask, self.or_mask = struct.unpack('>HHH', data) + + def execute(self, context): + ''' Run a mask write register request against the store + + :param context: The datastore to request from + :returns: The populated response + ''' + if not (0x0000 <= self.and_mask <= 0xffff): + return self.doException(merror.IllegalValue) + if not (0x0000 <= self.or_mask <= 0xffff): + return self.doException(merror.IllegalValue) + if not context.validate(self.function_code, self.address, 1): + return self.doException(merror.IllegalAddress) + values = context.getValues(self.function_code, self.address, 1)[0] + values = ((values & self.and_mask) | self.or_mask) + context.setValues(self.function_code, self.address, [values]) + return MaskWriteRegisterResponse(self.address, self.and_mask, self.or_mask) + + +class MaskWriteRegisterResponse(ModbusResponse): + ''' + The normal response is an echo of the request. The response is returned + after the register has been written. + ''' + function_code = 0x16 + _rtu_frame_size = 10 + + def __init__(self, address=0x0000, and_mask=0xffff, or_mask=0x0000, **kwargs): + ''' Initializes a new instance + + :param address: The mask pointer address (0x0000 to 0xffff) + :param and_mask: The and bitmask applied to the register address + :param or_mask: The or bitmask applied to the register address + ''' + ModbusResponse.__init__(self, **kwargs) + self.address = address + self.and_mask = and_mask + self.or_mask = or_mask + + def encode(self): + ''' Encodes the response + + :returns: The byte encoded message + ''' + return struct.pack('>HHH', self.address, self.and_mask, self.or_mask) + + def decode(self, data): + ''' Decodes a the response + + :param data: The packet data to decode + ''' + self.address, self.and_mask, self.or_mask = struct.unpack('>HHH', data) + + +
[docs]class ReadFifoQueueRequest(ModbusRequest): + ''' + This function code allows to read the contents of a First-In-First-Out + (FIFO) queue of register in a remote device. The function returns a + count of the registers in the queue, followed by the queued data. + Up to 32 registers can be read: the count, plus up to 31 queued data + registers. + + The queue count register is returned first, followed by the queued data + registers. The function reads the queue contents, but does not clear + them. + ''' + function_code = 0x18 + _rtu_frame_size = 6 + +
[docs] def __init__(self, address=0x0000, **kwargs): + ''' Initializes a new instance + + :param address: The fifo pointer address (0x0000 to 0xffff) + ''' + ModbusRequest.__init__(self, **kwargs) + self.address = address + self.values = [] # this should be added to the context
+ +
[docs] def encode(self): + ''' Encodes the request packet + + :returns: The byte encoded packet + ''' + return struct.pack('>H', self.address)
+ +
[docs] def decode(self, data): + ''' Decodes the incoming request + + :param data: The data to decode into the address + ''' + self.address = struct.unpack('>H', data)[0]
+ +
[docs] def execute(self, context): + ''' Run a read exeception status request against the store + + :param context: The datastore to request from + :returns: The populated response + ''' + if not (0x0000 <= self.address <= 0xffff): + return self.doException(merror.IllegalValue) + if len(self.values) > 31: + return self.doException(merror.IllegalValue) + # TODO pull the values from some context + return ReadFifoQueueResponse(self.values)
+ + +
[docs]class ReadFifoQueueResponse(ModbusResponse): + ''' + In a normal response, the byte count shows the quantity of bytes to + follow, including the queue count bytes and value register bytes + (but not including the error check field). The queue count is the + quantity of data registers in the queue (not including the count register). + + If the queue count exceeds 31, an exception response is returned with an + error code of 03 (Illegal Data Value). + ''' + function_code = 0x18 + + @classmethod +
[docs] def calculateRtuFrameSize(cls, buffer): + ''' Calculates the size of the message + + :param buffer: A buffer containing the data that have been received. + :returns: The number of bytes in the response. + ''' + hi_byte = byte2int(buffer[2]) + lo_byte = byte2int(buffer[3]) + return (hi_byte << 16) + lo_byte + 6
+ +
[docs] def __init__(self, values=None, **kwargs): + ''' Initializes a new instance + + :param values: The list of values of the fifo to return + ''' + ModbusResponse.__init__(self, **kwargs) + self.values = values or []
+ +
[docs] def encode(self): + ''' Encodes the response + + :returns: The byte encoded message + ''' + length = len(self.values) * 2 + packet = struct.pack('>HH', 2 + length, length) + for value in self.values: + packet += struct.pack('>H', value) + return packet
+ +
[docs] def decode(self, data): + ''' Decodes a the response + + :param data: The packet data to decode + ''' + self.values = [] + _, count = struct.unpack('>HH', data[0:4]) + for index in range(0, count - 4): + idx = 4 + index * 2 + self.values.append(struct.unpack('>H', data[idx:idx + 2])[0])
+ +#---------------------------------------------------------------------------# +# Exported symbols +#---------------------------------------------------------------------------# +__all__ = [ + "FileRecord", + "ReadFileRecordRequest", "ReadFileRecordResponse", + "WriteFileRecordRequest", "WriteFileRecordResponse", + "MaskWriteRegisterRequest", "MaskWriteRegisterResponse", + "ReadFifoQueueRequest", "ReadFifoQueueResponse", +] +
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/doc/sphinx/html/_modules/pymodbus/interfaces.html b/doc/sphinx/html/_modules/pymodbus/interfaces.html new file mode 100644 index 000000000..46190819f --- /dev/null +++ b/doc/sphinx/html/_modules/pymodbus/interfaces.html @@ -0,0 +1,327 @@ + + + + + + + + pymodbus.interfaces — pymodbus 1.3.0.rc2 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for pymodbus.interfaces

+'''
+Pymodbus Interfaces
+---------------------
+
+A collection of base classes that are used throughout
+the pymodbus library.
+'''
+from pymodbus.exceptions import NotImplementedException
+
+
+#---------------------------------------------------------------------------#
+# Generic
+#---------------------------------------------------------------------------#
+
[docs]class Singleton(object): + ''' + Singleton base class + http://mail.python.org/pipermail/python-list/2007-July/450681.html + ''' +
[docs] def __new__(cls, *args, **kwargs): + ''' Create a new instance + ''' + if '_inst' not in vars(cls): + cls._inst = object.__new__(cls) + return cls._inst
+ + +#---------------------------------------------------------------------------# +# Project Specific +#---------------------------------------------------------------------------# +
[docs]class IModbusDecoder(object): + ''' Modbus Decoder Base Class + + This interface must be implemented by a modbus message + decoder factory. These factories are responsible for + abstracting away converting a raw packet into a request / response + message object. + ''' + +
[docs] def decode(self, message): + ''' Wrapper to decode a given packet + + :param message: The raw modbus request packet + :return: The decoded modbus message or None if error + ''' + raise NotImplementedException( + "Method not implemented by derived class")
+ +
[docs] def lookupPduClass(self, function_code): + ''' Use `function_code` to determine the class of the PDU. + + :param function_code: The function code specified in a frame. + :returns: The class of the PDU that has a matching `function_code`. + ''' + raise NotImplementedException( + "Method not implemented by derived class")
+ + +
[docs]class IModbusFramer(object): + ''' + A framer strategy interface. The idea is that we abstract away all the + detail about how to detect if a current message frame exists, decoding + it, sending it, etc so that we can plug in a new Framer object (tcp, + rtu, ascii). + ''' + +
[docs] def checkFrame(self): + ''' Check and decode the next frame + + :returns: True if we successful, False otherwise + ''' + raise NotImplementedException( + "Method not implemented by derived class")
+ +
[docs] def advanceFrame(self): + ''' Skip over the current framed message + This allows us to skip over the current message after we have processed + it or determined that it contains an error. It also has to reset the + current frame header handle + ''' + raise NotImplementedException( + "Method not implemented by derived class")
+ +
[docs] def addToFrame(self, message): + ''' Add the next message to the frame buffer + + This should be used before the decoding while loop to add the received + data to the buffer handle. + + :param message: The most recent packet + ''' + raise NotImplementedException( + "Method not implemented by derived class")
+ +
[docs] def isFrameReady(self): + ''' Check if we should continue decode logic + + This is meant to be used in a while loop in the decoding phase to let + the decoder know that there is still data in the buffer. + + :returns: True if ready, False otherwise + ''' + raise NotImplementedException( + "Method not implemented by derived class")
+ +
[docs] def getFrame(self): + ''' Get the next frame from the buffer + + :returns: The frame data or '' + ''' + raise NotImplementedException( + "Method not implemented by derived class")
+ +
[docs] def populateResult(self, result): + ''' Populates the modbus result with current frame header + + We basically copy the data back over from the current header + to the result header. This may not be needed for serial messages. + + :param result: The response packet + ''' + raise NotImplementedException( + "Method not implemented by derived class")
+ +
[docs] def processIncomingPacket(self, data, callback): + ''' The new packet processing pattern + + This takes in a new request packet, adds it to the current + packet stream, and performs framing on it. That is, checks + for complete messages, and once found, will process all that + exist. This handles the case when we read N + 1 or 1 / N + messages at a time instead of 1. + + The processed and decoded messages are pushed to the callback + function to process and send. + + :param data: The new packet data + :param callback: The function to send results to + ''' + raise NotImplementedException( + "Method not implemented by derived class")
+ +
[docs] def buildPacket(self, message): + ''' Creates a ready to send modbus packet + + The raw packet is built off of a fully populated modbus + request / response message. + + :param message: The request/response to send + :returns: The built packet + ''' + raise NotImplementedException( + "Method not implemented by derived class")
+ + +
[docs]class IModbusSlaveContext(object): + ''' + Interface for a modbus slave data context + + Derived classes must implemented the following methods: + reset(self) + validate(self, fx, address, count=1) + getValues(self, fx, address, count=1) + setValues(self, fx, address, values) + ''' + __fx_mapper = {2: 'd', 4: 'i'} + __fx_mapper.update([(i, 'h') for i in [3, 6, 16, 22, 23]]) + __fx_mapper.update([(i, 'c') for i in [1, 5, 15]]) + +
[docs] def decode(self, fx): + ''' Converts the function code to the datastore to + + :param fx: The function we are working with + :returns: one of [d(iscretes),i(inputs),h(oliding),c(oils) + ''' + return self.__fx_mapper[fx]
+ +
[docs] def reset(self): + ''' Resets all the datastores to their default values + ''' + raise NotImplementedException("Context Reset")
+ +
[docs] def validate(self, fx, address, count=1): + ''' Validates the request to make sure it is in range + + :param fx: The function we are working with + :param address: The starting address + :param count: The number of values to test + :returns: True if the request in within range, False otherwise + ''' + raise NotImplementedException("validate context values")
+ +
[docs] def getValues(self, fx, address, count=1): + ''' Validates the request to make sure it is in range + + :param fx: The function we are working with + :param address: The starting address + :param count: The number of values to retrieve + :returns: The requested values from a:a+c + ''' + raise NotImplementedException("get context values")
+ +
[docs] def setValues(self, fx, address, values): + ''' Sets the datastore with the supplied values + + :param fx: The function we are working with + :param address: The starting address + :param values: The new values to be set + ''' + raise NotImplementedException("set context values")
+ + +
[docs]class IPayloadBuilder(object): + ''' + This is an interface to a class that can build a payload + for a modbus register write command. It should abstract + the codec for encoding data to the required format + (bcd, binary, char, etc). + ''' + +
[docs] def build(self): + ''' Return the payload buffer as a list + + This list is two bytes per element and can + thus be treated as a list of registers. + + :returns: The payload buffer as a list + ''' + raise NotImplementedException("set context values")
+ +#---------------------------------------------------------------------------# +# Exported symbols +#---------------------------------------------------------------------------# +__all__ = [ + 'Singleton', + 'IModbusDecoder', 'IModbusFramer', 'IModbusSlaveContext', + 'IPayloadBuilder', +] +
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/doc/sphinx/html/_modules/pymodbus/mei_message.html b/doc/sphinx/html/_modules/pymodbus/mei_message.html new file mode 100644 index 000000000..0e3336359 --- /dev/null +++ b/doc/sphinx/html/_modules/pymodbus/mei_message.html @@ -0,0 +1,256 @@ + + + + + + + + pymodbus.mei_message — pymodbus 1.3.0.rc2 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for pymodbus.mei_message

+'''
+Encapsulated Interface (MEI) Transport Messages
+-----------------------------------------------
+
+'''
+import struct
+from pymodbus.constants import DeviceInformation, MoreData
+from pymodbus.pdu import ModbusRequest
+from pymodbus.pdu import ModbusResponse
+from pymodbus.device import ModbusControlBlock
+from pymodbus.device import DeviceInformationFactory
+from pymodbus.pdu import ModbusExceptions as merror
+from pymodbus.compat import iteritems, byte2int
+
+_MCB = ModbusControlBlock()
+
+
+#---------------------------------------------------------------------------#
+# Read Device Information
+#---------------------------------------------------------------------------#
+
[docs]class ReadDeviceInformationRequest(ModbusRequest): + ''' + This function code allows reading the identification and additional + information relative to the physical and functional description of a + remote device, only. + + The Read Device Identification interface is modeled as an address space + composed of a set of addressable data elements. The data elements are + called objects and an object Id identifies them. + ''' + function_code = 0x2b + sub_function_code = 0x0e + _rtu_frame_size = 7 + +
[docs] def __init__(self, read_code=None, object_id=0x00, **kwargs): + ''' Initializes a new instance + + :param read_code: The device information read code + :param object_id: The object to read from + ''' + ModbusRequest.__init__(self, **kwargs) + self.read_code = read_code or DeviceInformation.Basic + self.object_id = object_id
+ +
[docs] def encode(self): + ''' Encodes the request packet + + :returns: The byte encoded packet + ''' + packet = struct.pack('>BBB', self.sub_function_code, + self.read_code, self.object_id) + return packet
+ +
[docs] def decode(self, data): + ''' Decodes data part of the message. + + :param data: The incoming data + ''' + params = struct.unpack('>BBB', data) + self.sub_function_code, self.read_code, self.object_id = params
+ +
[docs] def execute(self, context): + ''' Run a read exeception status request against the store + + :param context: The datastore to request from + :returns: The populated response + ''' + if not (0x00 <= self.object_id <= 0xff): + return self.doException(merror.IllegalValue) + if not (0x00 <= self.read_code <= 0x04): + return self.doException(merror.IllegalValue) + + information = DeviceInformationFactory.get(_MCB, + self.read_code, self.object_id) + return ReadDeviceInformationResponse(self.read_code, information)
+ +
[docs] def __str__(self): + ''' Builds a representation of the request + + :returns: The string representation of the request + ''' + params = (self.read_code, self.object_id) + return "ReadDeviceInformationRequest(%d,%d)" % params
+ + +
[docs]class ReadDeviceInformationResponse(ModbusResponse): + ''' + ''' + function_code = 0x2b + sub_function_code = 0x0e + + @classmethod +
[docs] def calculateRtuFrameSize(cls, buffer): + ''' Calculates the size of the message + + :param buffer: A buffer containing the data that have been received. + :returns: The number of bytes in the response. + ''' + size = 8 # skip the header information + count = byte2int(buffer[7]) + + while count > 0: + _, object_length = struct.unpack('>BB', buffer[size:size+2]) + size += object_length + 2 + count -= 1 + return size + 2
+ +
[docs] def __init__(self, read_code=None, information=None, **kwargs): + ''' Initializes a new instance + + :param read_code: The device information read code + :param information: The requested information request + ''' + ModbusResponse.__init__(self, **kwargs) + self.read_code = read_code or DeviceInformation.Basic + self.information = information or {} + self.number_of_objects = len(self.information) + self.conformity = 0x83 # I support everything right now + + # TODO calculate + self.next_object_id = 0x00 # self.information[-1](0) + self.more_follows = MoreData.Nothing
+ +
[docs] def encode(self): + ''' Encodes the response + + :returns: The byte encoded message + ''' + packet = struct.pack('>BBBBBB', self.sub_function_code, + self.read_code, self.conformity, self.more_follows, + self.next_object_id, self.number_of_objects) + + for (object_id, data) in iteritems(self.information): + packet += struct.pack('>BB', object_id, len(data)) + packet += data.encode() + return packet
+ +
[docs] def decode(self, data): + ''' Decodes a the response + + :param data: The packet data to decode + ''' + params = struct.unpack('>BBBBBB', data[0:6]) + self.sub_function_code, self.read_code = params[0:2] + self.conformity, self.more_follows = params[2:4] + self.next_object_id, self.number_of_objects = params[4:6] + self.information, count = {}, 6 # skip the header information + + while count < len(data): + object_id, object_length = struct.unpack('>BB', data[count:count+2]) + count += object_length + 2 + self.information[object_id] = data[count-object_length:count]
+ +
[docs] def __str__(self): + ''' Builds a representation of the response + + :returns: The string representation of the response + ''' + return "ReadDeviceInformationResponse(%d)" % self.read_code
+ +#---------------------------------------------------------------------------# +# Exported symbols +#---------------------------------------------------------------------------# +__all__ = [ + "ReadDeviceInformationRequest", "ReadDeviceInformationResponse", +] +
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/doc/sphinx/html/_modules/pymodbus/other_message.html b/doc/sphinx/html/_modules/pymodbus/other_message.html new file mode 100644 index 000000000..cf1081c62 --- /dev/null +++ b/doc/sphinx/html/_modules/pymodbus/other_message.html @@ -0,0 +1,533 @@ + + + + + + + + pymodbus.other_message — pymodbus 1.3.0.rc2 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for pymodbus.other_message

+'''
+Diagnostic record read/write
+
+Currently not all implemented
+'''
+import struct
+from pymodbus.constants import ModbusStatus
+from pymodbus.pdu import ModbusRequest
+from pymodbus.pdu import ModbusResponse
+from pymodbus.device import ModbusControlBlock
+from pymodbus.compat import byte2int, int2byte
+
+_MCB = ModbusControlBlock()
+
+
+#---------------------------------------------------------------------------#
+# TODO Make these only work on serial
+#---------------------------------------------------------------------------#
+
[docs]class ReadExceptionStatusRequest(ModbusRequest): + ''' + This function code is used to read the contents of eight Exception Status + outputs in a remote device. The function provides a simple method for + accessing this information, because the Exception Output references are + known (no output reference is needed in the function). + ''' + function_code = 0x07 + _rtu_frame_size = 4 + +
[docs] def __init__(self, **kwargs): + ''' Initializes a new instance + ''' + ModbusRequest.__init__(self, **kwargs)
+ +
[docs] def encode(self): + ''' Encodes the message + ''' + return b''
+ +
[docs] def decode(self, data): + ''' Decodes data part of the message. + + :param data: The incoming data + ''' + pass
+ +
[docs] def execute(self): + ''' Run a read exeception status request against the store + + :returns: The populated response + ''' + status = _MCB.Counter.summary() + return ReadExceptionStatusResponse(status)
+ +
[docs] def __str__(self): + ''' Builds a representation of the request + + :returns: The string representation of the request + ''' + return "ReadExceptionStatusRequest(%d)" % (self.function_code)
+ + +
[docs]class ReadExceptionStatusResponse(ModbusResponse): + ''' + The normal response contains the status of the eight Exception Status + outputs. The outputs are packed into one data byte, with one bit + per output. The status of the lowest output reference is contained + in the least significant bit of the byte. The contents of the eight + Exception Status outputs are device specific. + ''' + function_code = 0x07 + _rtu_frame_size = 5 + +
[docs] def __init__(self, status=0x00, **kwargs): + ''' Initializes a new instance + + :param status: The status response to report + ''' + ModbusResponse.__init__(self, **kwargs) + self.status = status
+ +
[docs] def encode(self): + ''' Encodes the response + + :returns: The byte encoded message + ''' + return struct.pack('>B', self.status)
+ +
[docs] def decode(self, data): + ''' Decodes a the response + + :param data: The packet data to decode + ''' + self.status = byte2int(data[0])
+ +
[docs] def __str__(self): + ''' Builds a representation of the response + + :returns: The string representation of the response + ''' + arguments = (self.function_code, self.status) + return "ReadExceptionStatusResponse(%d, %s)" % arguments
+ +# Encapsulate interface transport 43, 14 +# CANopen general reference 43, 13 + + +#---------------------------------------------------------------------------# +# TODO Make these only work on serial +#---------------------------------------------------------------------------# +
[docs]class GetCommEventCounterRequest(ModbusRequest): + ''' + This function code is used to get a status word and an event count from + the remote device's communication event counter. + + By fetching the current count before and after a series of messages, a + client can determine whether the messages were handled normally by the + remote device. + + The device's event counter is incremented once for each successful + message completion. It is not incremented for exception responses, + poll commands, or fetch event counter commands. + + The event counter can be reset by means of the Diagnostics function + (code 08), with a subfunction of Restart Communications Option + (code 00 01) or Clear Counters and Diagnostic Register (code 00 0A). + ''' + function_code = 0x0b + _rtu_frame_size = 4 + +
[docs] def __init__(self, **kwargs): + ''' Initializes a new instance + ''' + ModbusRequest.__init__(self, **kwargs)
+ +
[docs] def encode(self): + ''' Encodes the message + ''' + return b''
+ +
[docs] def decode(self, data): + ''' Decodes data part of the message. + + :param data: The incoming data + ''' + pass
+ +
[docs] def execute(self): + ''' Run a read exeception status request against the store + + :returns: The populated response + ''' + status = _MCB.Counter.Event + return GetCommEventCounterResponse(status)
+ +
[docs] def __str__(self): + ''' Builds a representation of the request + + :returns: The string representation of the request + ''' + return "GetCommEventCounterRequest(%d)" % (self.function_code)
+ + +
[docs]class GetCommEventCounterResponse(ModbusResponse): + ''' + The normal response contains a two-byte status word, and a two-byte + event count. The status word will be all ones (FF FF hex) if a + previously-issued program command is still being processed by the + remote device (a busy condition exists). Otherwise, the status word + will be all zeros. + ''' + function_code = 0x0b + _rtu_frame_size = 8 + +
[docs] def __init__(self, count=0x0000, **kwargs): + ''' Initializes a new instance + + :param count: The current event counter value + ''' + ModbusResponse.__init__(self, **kwargs) + self.count = count + self.status = True # this means we are ready, not waiting
+ +
[docs] def encode(self): + ''' Encodes the response + + :returns: The byte encoded message + ''' + if self.status: ready = ModbusStatus.Ready + else: ready = ModbusStatus.Waiting + return struct.pack('>HH', ready, self.count)
+ +
[docs] def decode(self, data): + ''' Decodes a the response + + :param data: The packet data to decode + ''' + ready, self.count = struct.unpack('>HH', data) + self.status = (ready == ModbusStatus.Ready)
+ +
[docs] def __str__(self): + ''' Builds a representation of the response + + :returns: The string representation of the response + ''' + arguments = (self.function_code, self.count, self.status) + return "GetCommEventCounterResponse(%d, %d, %d)" % arguments
+ + +#---------------------------------------------------------------------------# +# TODO Make these only work on serial +#---------------------------------------------------------------------------# +class GetCommEventLogRequest(ModbusRequest): + ''' + This function code is used to get a status word, event count, message + count, and a field of event bytes from the remote device. + + The status word and event counts are identical to that returned by + the Get Communications Event Counter function (11, 0B hex). + + The message counter contains the quantity of messages processed by the + remote device since its last restart, clear counters operation, or + power-up. This count is identical to that returned by the Diagnostic + function (code 08), sub-function Return Bus Message Count (code 11, + 0B hex). + + The event bytes field contains 0-64 bytes, with each byte corresponding + to the status of one MODBUS send or receive operation for the remote + device. The remote device enters the events into the field in + chronological order. Byte 0 is the most recent event. Each new byte + flushes the oldest byte from the field. + ''' + function_code = 0x0c + _rtu_frame_size = 4 + + def __init__(self, **kwargs): + ''' Initializes a new instance + ''' + ModbusRequest.__init__(self, **kwargs) + + def encode(self): + ''' Encodes the message + ''' + return b'' + + def decode(self, data): + ''' Decodes data part of the message. + + :param data: The incoming data + ''' + pass + + def execute(self): + ''' Run a read exeception status request against the store + + :returns: The populated response + ''' + results = { + 'status' : True, + 'message_count' : _MCB.Counter.BusMessage, + 'event_count' : _MCB.Counter.Event, + 'events' : _MCB.getEvents(), + } + return GetCommEventLogResponse(**results) + + def __str__(self): + ''' Builds a representation of the request + + :returns: The string representation of the request + ''' + return "GetCommEventLogRequest(%d)" % self.function_code + + +class GetCommEventLogResponse(ModbusResponse): + ''' + The normal response contains a two-byte status word field, + a two-byte event count field, a two-byte message count field, + and a field containing 0-64 bytes of events. A byte count + field defines the total length of the data in these four field + ''' + function_code = 0x0c + _rtu_byte_count_pos = 3 + + def __init__(self, **kwargs): + ''' Initializes a new instance + + :param status: The status response to report + :param message_count: The current message count + :param event_count: The current event count + :param events: The collection of events to send + ''' + ModbusResponse.__init__(self, **kwargs) + self.status = kwargs.get('status', True) + self.message_count = kwargs.get('message_count', 0) + self.event_count = kwargs.get('event_count', 0) + self.events = kwargs.get('events', []) + + def encode(self): + ''' Encodes the response + + :returns: The byte encoded message + ''' + if self.status: ready = ModbusStatus.Ready + else: ready = ModbusStatus.Waiting + packet = struct.pack('>B', 6 + len(self.events)) + packet += struct.pack('>H', ready) + packet += struct.pack('>HH', self.event_count, self.message_count) + packet += b''.join(struct.pack('>B', e) for e in self.events) + return packet + + def decode(self, data): + ''' Decodes a the response + + :param data: The packet data to decode + ''' + length = byte2int(data[0]) + status = struct.unpack('>H', data[1:3])[0] + self.status = (status == ModbusStatus.Ready) + self.event_count = struct.unpack('>H', data[3:5])[0] + self.message_count = struct.unpack('>H', data[5:7])[0] + + self.events = [] + for e in range(7, length + 1): + self.events.append(byte2int(data[e])) + + def __str__(self): + ''' Builds a representation of the response + + :returns: The string representation of the response + ''' + arguments = (self.function_code, self.status, self.message_count, self.event_count) + return "GetCommEventLogResponse(%d, %d, %d, %d)" % arguments + + +#---------------------------------------------------------------------------# +# TODO Make these only work on serial +#---------------------------------------------------------------------------# +
[docs]class ReportSlaveIdRequest(ModbusRequest): + ''' + This function code is used to read the description of the type, the + current status, and other information specific to a remote device. + ''' + function_code = 0x11 + _rtu_frame_size = 4 + +
[docs] def __init__(self, **kwargs): + ''' Initializes a new instance + ''' + ModbusRequest.__init__(self, **kwargs)
+ +
[docs] def encode(self): + ''' Encodes the message + ''' + return b''
+ +
[docs] def decode(self, data): + ''' Decodes data part of the message. + + :param data: The incoming data + ''' + pass
+ +
[docs] def execute(self): + ''' Run a read exeception status request against the store + + :returns: The populated response + ''' + identifier = b'\x70\x79\x6d\x6f\x64\x62\x75\x73' + return ReportSlaveIdResponse(identifier)
+ +
[docs] def __str__(self): + ''' Builds a representation of the request + + :returns: The string representation of the request + ''' + return "ResportSlaveIdRequest(%d)" % self.function_code
+ + +
[docs]class ReportSlaveIdResponse(ModbusResponse): + ''' + The format of a normal response is shown in the following example. + The data contents are specific to each type of device. + ''' + function_code = 0x11 + _rtu_byte_count_pos = 2 + +
[docs] def __init__(self, identifier=b'\x00', status=True, **kwargs): + ''' Initializes a new instance + + :param identifier: The identifier of the slave + :param status: The status response to report + ''' + ModbusResponse.__init__(self, **kwargs) + self.identifier = identifier + self.status = status
+ +
[docs] def encode(self): + ''' Encodes the response + + :returns: The byte encoded message + ''' + if self.status: status = ModbusStatus.SlaveOn + else: status = ModbusStatus.SlaveOff + length = len(self.identifier) + 2 + packet = int2byte(length) + packet += self.identifier # we assume it is already encoded + packet += int2byte(status) + return packet
+ +
[docs] def decode(self, data): + ''' Decodes a the response + + Since the identifier is device dependent, we just return the + raw value that a user can decode to whatever it should be. + + :param data: The packet data to decode + ''' + length = byte2int(data[0]) + self.identifier = data[1:length + 1] + status = byte2int(data[-1]) + self.status = status == ModbusStatus.SlaveOn
+ +
[docs] def __str__(self): + ''' Builds a representation of the response + + :returns: The string representation of the response + ''' + arguments = (self.function_code, self.identifier, self.status) + return "ResportSlaveIdResponse(%d, %d, %d)" % arguments
+ +#---------------------------------------------------------------------------# +# TODO Make these only work on serial +#---------------------------------------------------------------------------# +# report device identification 43, 14 + +#---------------------------------------------------------------------------# +# Exported symbols +#---------------------------------------------------------------------------# +__all__ = [ + "ReadExceptionStatusRequest", "ReadExceptionStatusResponse", + "GetCommEventCounterRequest", "GetCommEventCounterResponse", + "GetCommEventLogRequest", "GetCommEventLogResponse", + "ReportSlaveIdRequest", "ReportSlaveIdResponse", +] +
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/doc/sphinx/html/_modules/pymodbus/payload.html b/doc/sphinx/html/_modules/pymodbus/payload.html new file mode 100644 index 000000000..67fb0efeb --- /dev/null +++ b/doc/sphinx/html/_modules/pymodbus/payload.html @@ -0,0 +1,432 @@ + + + + + + + + pymodbus.payload — pymodbus 1.3.0.rc2 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for pymodbus.payload

+'''
+Modbus Payload Builders
+------------------------
+
+A collection of utilities for building and decoding
+modbus messages payloads.
+'''
+from struct import pack, unpack
+from pymodbus.interfaces import IPayloadBuilder
+from pymodbus.constants import Endian
+from pymodbus.utilities import pack_bitstring
+from pymodbus.utilities import unpack_bitstring
+from pymodbus.exceptions import ParameterException
+
+
+
[docs]class BinaryPayloadBuilder(IPayloadBuilder): + ''' + A utility that helps build payload messages to be + written with the various modbus messages. It really is just + a simple wrapper around the struct module, however it saves + time looking up the format strings. What follows is a simple + example:: + + builder = BinaryPayloadBuilder(endian=Endian.Little) + builder.add_8bit_uint(1) + builder.add_16bit_uint(2) + payload = builder.build() + ''' + +
[docs] def __init__(self, payload=None, endian=Endian.Little): + ''' Initialize a new instance of the payload builder + + :param payload: Raw payload data to initialize with + :param endian: The endianess of the payload + ''' + self._payload = payload or [] + self._endian = endian
+ +
[docs] def to_string(self): + ''' Return the payload buffer as a string + + :returns: The payload buffer as a string + ''' + return b''.join(self._payload)
+ +
[docs] def __str__(self): + ''' Return the payload buffer as a string + + :returns: The payload buffer as a string + ''' + return self.to_string().decode('utf-8')
+ +
[docs] def reset(self): + ''' Reset the payload buffer + ''' + self._payload = []
+ +
[docs] def to_registers(self): + ''' Convert the payload buffer into a register + layout that can be used as a context block. + + :returns: The register layout to use as a block + ''' + fstring = self._endian + 'H' + payload = self.build() + return [unpack(fstring, value)[0] for value in payload]
+ +
[docs] def build(self): + ''' Return the payload buffer as a list + + This list is two bytes per element and can + thus be treated as a list of registers. + + :returns: The payload buffer as a list + ''' + string = self.to_string() + length = len(string) + string = string + (b'\x00' * (length % 2)) + return [string[i:i+2] for i in range(0, length, 2)]
+ +
[docs] def add_bits(self, values): + ''' Adds a collection of bits to be encoded + + If these are less than a multiple of eight, + they will be left padded with 0 bits to make + it so. + + :param value: The value to add to the buffer + ''' + value = pack_bitstring(values) + self._payload.append(value)
+ +
[docs] def add_8bit_uint(self, value): + ''' Adds a 8 bit unsigned int to the buffer + + :param value: The value to add to the buffer + ''' + fstring = self._endian + 'B' + self._payload.append(pack(fstring, value))
+ +
[docs] def add_16bit_uint(self, value): + ''' Adds a 16 bit unsigned int to the buffer + + :param value: The value to add to the buffer + ''' + fstring = self._endian + 'H' + self._payload.append(pack(fstring, value))
+ +
[docs] def add_32bit_uint(self, value): + ''' Adds a 32 bit unsigned int to the buffer + + :param value: The value to add to the buffer + ''' + fstring = self._endian + 'I' + self._payload.append(pack(fstring, value))
+ +
[docs] def add_64bit_uint(self, value): + ''' Adds a 64 bit unsigned int to the buffer + + :param value: The value to add to the buffer + ''' + fstring = self._endian + 'Q' + self._payload.append(pack(fstring, value))
+ +
[docs] def add_8bit_int(self, value): + ''' Adds a 8 bit signed int to the buffer + + :param value: The value to add to the buffer + ''' + fstring = self._endian + 'b' + self._payload.append(pack(fstring, value))
+ +
[docs] def add_16bit_int(self, value): + ''' Adds a 16 bit signed int to the buffer + + :param value: The value to add to the buffer + ''' + fstring = self._endian + 'h' + self._payload.append(pack(fstring, value))
+ +
[docs] def add_32bit_int(self, value): + ''' Adds a 32 bit signed int to the buffer + + :param value: The value to add to the buffer + ''' + fstring = self._endian + 'i' + self._payload.append(pack(fstring, value))
+ +
[docs] def add_64bit_int(self, value): + ''' Adds a 64 bit signed int to the buffer + + :param value: The value to add to the buffer + ''' + fstring = self._endian + 'q' + self._payload.append(pack(fstring, value))
+ +
[docs] def add_32bit_float(self, value): + ''' Adds a 32 bit float to the buffer + + :param value: The value to add to the buffer + ''' + fstring = self._endian + 'f' + self._payload.append(pack(fstring, value))
+ +
[docs] def add_64bit_float(self, value): + ''' Adds a 64 bit float(double) to the buffer + + :param value: The value to add to the buffer + ''' + fstring = self._endian + 'd' + self._payload.append(pack(fstring, value))
+ +
[docs] def add_string(self, value): + ''' Adds a string to the buffer + + :param value: The value to add to the buffer + ''' + fstring = self._endian + str(len(value)) + 's' + self._payload.append(pack(fstring, value))
+ + +
[docs]class BinaryPayloadDecoder(object): + ''' + A utility that helps decode payload messages from a modbus + reponse message. It really is just a simple wrapper around + the struct module, however it saves time looking up the format + strings. What follows is a simple example:: + + decoder = BinaryPayloadDecoder(payload) + first = decoder.decode_8bit_uint() + second = decoder.decode_16bit_uint() + ''' + +
[docs] def __init__(self, payload, endian=Endian.Little): + ''' Initialize a new payload decoder + + :param payload: The payload to decode with + :param endian: The endianess of the payload + ''' + self._payload = payload + self._pointer = 0x00 + self._endian = endian
+ + @classmethod +
[docs] def fromRegisters(klass, registers, endian=Endian.Little): + ''' Initialize a payload decoder with the result of + reading a collection of registers from a modbus device. + + The registers are treated as a list of 2 byte values. + We have to do this because of how the data has already + been decoded by the rest of the library. + + :param registers: The register results to initialize with + :param endian: The endianess of the payload + :returns: An initialized PayloadDecoder + ''' + if isinstance(registers, list): # repack into flat binary + payload = b''.join(pack(endian + 'H', x) for x in registers) + return klass(payload, endian) + raise ParameterException('Invalid collection of registers supplied')
+ + @classmethod +
[docs] def fromCoils(klass, coils, endian=Endian.Little): + ''' Initialize a payload decoder with the result of + reading a collection of coils from a modbus device. + + The coils are treated as a list of bit(boolean) values. + + :param coils: The coil results to initialize with + :param endian: The endianess of the payload + :returns: An initialized PayloadDecoder + ''' + if isinstance(coils, list): + payload = pack_bitstring(coils) + return klass(payload, endian) + raise ParameterException('Invalid collection of coils supplied')
+ +
[docs] def reset(self): + ''' Reset the decoder pointer back to the start + ''' + self._pointer = 0x00
+ +
[docs] def decode_8bit_uint(self): + ''' Decodes a 8 bit unsigned int from the buffer + ''' + self._pointer += 1 + fstring = self._endian + 'B' + handle = self._payload[self._pointer - 1:self._pointer] + return unpack(fstring, handle)[0]
+ +
[docs] def decode_bits(self): + ''' Decodes a byte worth of bits from the buffer + ''' + self._pointer += 1 + fstring = self._endian + 'B' + handle = self._payload[self._pointer - 1:self._pointer] + return unpack_bitstring(handle)
+ +
[docs] def decode_16bit_uint(self): + ''' Decodes a 16 bit unsigned int from the buffer + ''' + self._pointer += 2 + fstring = self._endian + 'H' + handle = self._payload[self._pointer - 2:self._pointer] + return unpack(fstring, handle)[0]
+ +
[docs] def decode_32bit_uint(self): + ''' Decodes a 32 bit unsigned int from the buffer + ''' + self._pointer += 4 + fstring = self._endian + 'I' + handle = self._payload[self._pointer - 4:self._pointer] + return unpack(fstring, handle)[0]
+ +
[docs] def decode_64bit_uint(self): + ''' Decodes a 64 bit unsigned int from the buffer + ''' + self._pointer += 8 + fstring = self._endian + 'Q' + handle = self._payload[self._pointer - 8:self._pointer] + return unpack(fstring, handle)[0]
+ +
[docs] def decode_8bit_int(self): + ''' Decodes a 8 bit signed int from the buffer + ''' + self._pointer += 1 + fstring = self._endian + 'b' + handle = self._payload[self._pointer - 1:self._pointer] + return unpack(fstring, handle)[0]
+ +
[docs] def decode_16bit_int(self): + ''' Decodes a 16 bit signed int from the buffer + ''' + self._pointer += 2 + fstring = self._endian + 'h' + handle = self._payload[self._pointer - 2:self._pointer] + return unpack(fstring, handle)[0]
+ +
[docs] def decode_32bit_int(self): + ''' Decodes a 32 bit signed int from the buffer + ''' + self._pointer += 4 + fstring = self._endian + 'i' + handle = self._payload[self._pointer - 4:self._pointer] + return unpack(fstring, handle)[0]
+ +
[docs] def decode_64bit_int(self): + ''' Decodes a 64 bit signed int from the buffer + ''' + self._pointer += 8 + fstring = self._endian + 'q' + handle = self._payload[self._pointer - 8:self._pointer] + return unpack(fstring, handle)[0]
+ +
[docs] def decode_32bit_float(self): + ''' Decodes a 32 bit float from the buffer + ''' + self._pointer += 4 + fstring = self._endian + 'f' + handle = self._payload[self._pointer - 4:self._pointer] + return unpack(fstring, handle)[0]
+ +
[docs] def decode_64bit_float(self): + ''' Decodes a 64 bit float(double) from the buffer + ''' + self._pointer += 8 + fstring = self._endian + 'd' + handle = self._payload[self._pointer - 8:self._pointer] + return unpack(fstring, handle)[0]
+ +
[docs] def decode_string(self, size=1): + ''' Decodes a string from the buffer + + :param size: The size of the string to decode + ''' + self._pointer += size + return self._payload[self._pointer - size:self._pointer]
+ +#---------------------------------------------------------------------------# +# Exported Identifiers +#---------------------------------------------------------------------------# +__all__ = ["BinaryPayloadBuilder", "BinaryPayloadDecoder"] +
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/doc/sphinx/html/_modules/pymodbus/pdu.html b/doc/sphinx/html/_modules/pymodbus/pdu.html new file mode 100644 index 000000000..afcda7a14 --- /dev/null +++ b/doc/sphinx/html/_modules/pymodbus/pdu.html @@ -0,0 +1,330 @@ + + + + + + + + pymodbus.pdu — pymodbus 1.3.0.rc2 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for pymodbus.pdu

+'''
+Contains base classes for modbus request/response/error packets
+'''
+from pymodbus.interfaces import Singleton
+from pymodbus.exceptions import NotImplementedException
+from pymodbus.constants import Defaults
+from pymodbus.utilities import rtuFrameSize
+from pymodbus.compat import iteritems, int2byte, byte2int
+
+#---------------------------------------------------------------------------#
+# Logging
+#---------------------------------------------------------------------------#
+import logging
+_logger = logging.getLogger(__name__)
+
+
+#---------------------------------------------------------------------------#
+# Base PDU's
+#---------------------------------------------------------------------------#
+
[docs]class ModbusPDU(object): + ''' + Base class for all Modbus mesages + + .. attribute:: transaction_id + + This value is used to uniquely identify a request + response pair. It can be implemented as a simple counter + + .. attribute:: protocol_id + + This is a constant set at 0 to indicate Modbus. It is + put here for ease of expansion. + + .. attribute:: unit_id + + This is used to route the request to the correct child. In + the TCP modbus, it is used for routing (or not used at all. However, + for the serial versions, it is used to specify which child to perform + the requests against. The value 0x00 represents the broadcast address + (also 0xff). + + .. attribute:: check + + This is used for LRC/CRC in the serial modbus protocols + + .. attribute:: skip_encode + + This is used when the message payload has already been encoded. + Generally this will occur when the PayloadBuilder is being used + to create a complicated message. By setting this to True, the + request will pass the currently encoded message through instead + of encoding it again. + ''' + +
[docs] def __init__(self, **kwargs): + ''' Initializes the base data for a modbus request ''' + self.transaction_id = kwargs.get('transaction', Defaults.TransactionId) + self.protocol_id = kwargs.get('protocol', Defaults.ProtocolId) + self.unit_id = kwargs.get('unit', Defaults.UnitId) + self.skip_encode = kwargs.get('skip_encode', False) + self.check = 0x0000
+ +
[docs] def encode(self): + ''' Encodes the message + + :raises: A not implemented exception + ''' + raise NotImplementedException()
+ +
[docs] def decode(self, data): + ''' Decodes data part of the message. + + :param data: is a string object + :raises: A not implemented exception + ''' + raise NotImplementedException()
+ + @classmethod +
[docs] def calculateRtuFrameSize(cls, buffer): + ''' Calculates the size of a PDU. + + :param buffer: A buffer containing the data that have been received. + :returns: The number of bytes in the PDU. + ''' + if hasattr(cls, '_rtu_frame_size'): + return cls._rtu_frame_size + elif hasattr(cls, '_rtu_byte_count_pos'): + return rtuFrameSize(buffer, cls._rtu_byte_count_pos) + else: raise NotImplementedException( + "Cannot determine RTU frame size for %s" % cls.__name__)
+ + +
[docs]class ModbusRequest(ModbusPDU): + ''' Base class for a modbus request PDU ''' + +
[docs] def __init__(self, **kwargs): + ''' Proxy to the lower level initializer ''' + ModbusPDU.__init__(self, **kwargs)
+ +
[docs] def doException(self, exception): + ''' Builds an error response based on the function + + :param exception: The exception to return + :raises: An exception response + ''' + _logger.error("Exception Response F(%d) E(%d)" % + (self.function_code, exception)) + return ExceptionResponse(self.function_code, exception)
+ + +
[docs]class ModbusResponse(ModbusPDU): + ''' Base class for a modbus response PDU + + .. attribute:: should_respond + + A flag that indicates if this response returns a result back + to the client issuing the request + + .. attribute:: _rtu_frame_size + + Indicates the size of the modbus rtu response used for + calculating how much to read. + ''' + + should_respond = True + +
[docs] def __init__(self, **kwargs): + ''' Proxy to the lower level initializer ''' + ModbusPDU.__init__(self, **kwargs)
+ + +#---------------------------------------------------------------------------# +# Exception PDU's +#---------------------------------------------------------------------------# +
[docs]class ModbusExceptions(Singleton): + ''' + An enumeration of the valid modbus exceptions + ''' + IllegalFunction = 0x01 + IllegalAddress = 0x02 + IllegalValue = 0x03 + SlaveFailure = 0x04 + Acknowledge = 0x05 + SlaveBusy = 0x06 + MemoryParityError = 0x08 + GatewayPathUnavailable = 0x0A + GatewayNoResponse = 0x0B + + @classmethod +
[docs] def decode(cls, code): + ''' Given an error code, translate it to a + string error name. + + :param code: The code number to translate + ''' + values = dict((v, k) for k, v in iteritems(cls.__dict__) + if not k.startswith('__') and not callable(v)) + return values.get(code, None)
+ + +
[docs]class ExceptionResponse(ModbusResponse): + ''' Base class for a modbus exception PDU ''' + ExceptionOffset = 0x80 + _rtu_frame_size = 5 + +
[docs] def __init__(self, function_code, exception_code=None, **kwargs): + ''' Initializes the modbus exception response + + :param function_code: The function to build an exception response for + :param exception_code: The specific modbus exception to return + ''' + ModbusResponse.__init__(self, **kwargs) + self.original_code = function_code + self.function_code = function_code | self.ExceptionOffset + self.exception_code = exception_code
+ +
[docs] def encode(self): + ''' Encodes a modbus exception response + + :returns: The encoded exception packet + ''' + return int2byte(self.exception_code)
+ +
[docs] def decode(self, data): + ''' Decodes a modbus exception response + + :param data: The packet data to decode + ''' + self.exception_code = byte2int(data[0])
+ +
[docs] def __str__(self): + ''' Builds a representation of an exception response + + :returns: The string representation of an exception response + ''' + message = ModbusExceptions.decode(self.exception_code) + parameters = (self.function_code, self.original_code, message) + return "Exception Response(%d, %d, %s)" % parameters
+ + +
[docs]class IllegalFunctionRequest(ModbusRequest): + ''' + Defines the Modbus slave exception type 'Illegal Function' + This exception code is returned if the slave:: + + - does not implement the function code **or** + - is not in a state that allows it to process the function + ''' + ErrorCode = 1 + +
[docs] def __init__(self, function_code, **kwargs): + ''' Initializes a IllegalFunctionRequest + + :param function_code: The function we are erroring on + ''' + ModbusRequest.__init__(self, **kwargs) + self.function_code = function_code
+ +
[docs] def decode(self, data): + ''' This is here so this failure will run correctly + + :param data: Not used + ''' + pass
+ +
[docs] def execute(self, context): + ''' Builds an illegal function request error response + + :param context: The current context for the message + :returns: The error response packet + ''' + return ExceptionResponse(self.function_code, self.ErrorCode)
+ +#---------------------------------------------------------------------------# +# Exported symbols +#---------------------------------------------------------------------------# +__all__ = [ + 'ModbusRequest', 'ModbusResponse', 'ModbusExceptions', + 'ExceptionResponse', 'IllegalFunctionRequest', +] +
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/doc/sphinx/html/_modules/pymodbus/register_read_message.html b/doc/sphinx/html/_modules/pymodbus/register_read_message.html new file mode 100644 index 000000000..5ecb331d7 --- /dev/null +++ b/doc/sphinx/html/_modules/pymodbus/register_read_message.html @@ -0,0 +1,449 @@ + + + + + + + + pymodbus.register_read_message — pymodbus 1.3.0.rc2 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for pymodbus.register_read_message

+'''
+Register Reading Request/Response
+---------------------------------
+'''
+import struct
+from pymodbus.pdu import ModbusRequest
+from pymodbus.pdu import ModbusResponse
+from pymodbus.pdu import ModbusExceptions as merror
+from pymodbus.compat import int2byte, byte2int
+
+
+
[docs]class ReadRegistersRequestBase(ModbusRequest): + ''' + Base class for reading a modbus register + ''' + _rtu_frame_size = 8 + +
[docs] def __init__(self, address, count, **kwargs): + ''' Initializes a new instance + + :param address: The address to start the read from + :param count: The number of registers to read + ''' + ModbusRequest.__init__(self, **kwargs) + self.address = address + self.count = count
+ +
[docs] def encode(self): + ''' Encodes the request packet + + :return: The encoded packet + ''' + return struct.pack('>HH', self.address, self.count)
+ +
[docs] def decode(self, data): + ''' Decode a register request packet + + :param data: The request to decode + ''' + self.address, self.count = struct.unpack('>HH', data)
+ +
[docs] def get_response_pdu_size(self): + """ + Func_code (1 byte) + Byte Count(1 byte) + 2 * Quantity of Coils (n Bytes) + :return: + """ + return 1 + 1 + 2 * self.count
+ +
[docs] def __str__(self): + ''' Returns a string representation of the instance + + :returns: A string representation of the instance + ''' + return "ReadRegisterRequest (%d,%d)" % (self.address, self.count)
+ + +
[docs]class ReadRegistersResponseBase(ModbusResponse): + ''' + Base class for responsing to a modbus register read + ''' + + _rtu_byte_count_pos = 2 + +
[docs] def __init__(self, values, **kwargs): + ''' Initializes a new instance + + :param values: The values to write to + ''' + ModbusResponse.__init__(self, **kwargs) + self.registers = values or []
+ +
[docs] def encode(self): + ''' Encodes the response packet + + :returns: The encoded packet + ''' + result = int2byte(len(self.registers) * 2) + for register in self.registers: + result += struct.pack('>H', register) + return result
+ +
[docs] def decode(self, data): + ''' Decode a register response packet + + :param data: The request to decode + ''' + byte_count = byte2int(data[0]) + self.registers = [] + for i in range(1, byte_count + 1, 2): + self.registers.append(struct.unpack('>H', data[i:i + 2])[0])
+ +
[docs] def getRegister(self, index): + ''' Get the requested register + + :param index: The indexed register to retrieve + :returns: The request register + ''' + return self.registers[index]
+ +
[docs] def __str__(self): + ''' Returns a string representation of the instance + + :returns: A string representation of the instance + ''' + return "ReadRegisterResponse (%d)" % len(self.registers)
+ + +
[docs]class ReadHoldingRegistersRequest(ReadRegistersRequestBase): + ''' + This function code is used to read the contents of a contiguous block + of holding registers in a remote device. The Request PDU specifies the + starting register address and the number of registers. In the PDU + Registers are addressed starting at zero. Therefore registers numbered + 1-16 are addressed as 0-15. + ''' + function_code = 3 + +
[docs] def __init__(self, address=None, count=None, **kwargs): + ''' Initializes a new instance of the request + + :param address: The starting address to read from + :param count: The number of registers to read from address + ''' + ReadRegistersRequestBase.__init__(self, address, count, **kwargs)
+ +
[docs] def execute(self, context): + ''' Run a read holding request against a datastore + + :param context: The datastore to request from + :returns: An initialized response, exception message otherwise + ''' + if not (1 <= self.count <= 0x7d): + return self.doException(merror.IllegalValue) + if not context.validate(self.function_code, self.address, self.count): + return self.doException(merror.IllegalAddress) + values = context.getValues(self.function_code, self.address, self.count) + return ReadHoldingRegistersResponse(values)
+ + +
[docs]class ReadHoldingRegistersResponse(ReadRegistersResponseBase): + ''' + This function code is used to read the contents of a contiguous block + of holding registers in a remote device. The Request PDU specifies the + starting register address and the number of registers. In the PDU + Registers are addressed starting at zero. Therefore registers numbered + 1-16 are addressed as 0-15. + ''' + function_code = 3 + +
[docs] def __init__(self, values=None, **kwargs): + ''' Initializes a new response instance + + :param values: The resulting register values + ''' + ReadRegistersResponseBase.__init__(self, values, **kwargs)
+ + +
[docs]class ReadInputRegistersRequest(ReadRegistersRequestBase): + ''' + This function code is used to read from 1 to approx. 125 contiguous + input registers in a remote device. The Request PDU specifies the + starting register address and the number of registers. In the PDU + Registers are addressed starting at zero. Therefore input registers + numbered 1-16 are addressed as 0-15. + ''' + function_code = 4 + +
[docs] def __init__(self, address=None, count=None, **kwargs): + ''' Initializes a new instance of the request + + :param address: The starting address to read from + :param count: The number of registers to read from address + ''' + ReadRegistersRequestBase.__init__(self, address, count, **kwargs)
+ +
[docs] def execute(self, context): + ''' Run a read input request against a datastore + + :param context: The datastore to request from + :returns: An initialized response, exception message otherwise + ''' + if not (1 <= self.count <= 0x7d): + return self.doException(merror.IllegalValue) + if not context.validate(self.function_code, self.address, self.count): + return self.doException(merror.IllegalAddress) + values = context.getValues(self.function_code, self.address, self.count) + return ReadInputRegistersResponse(values)
+ + +
[docs]class ReadInputRegistersResponse(ReadRegistersResponseBase): + ''' + This function code is used to read from 1 to approx. 125 contiguous + input registers in a remote device. The Request PDU specifies the + starting register address and the number of registers. In the PDU + Registers are addressed starting at zero. Therefore input registers + numbered 1-16 are addressed as 0-15. + ''' + function_code = 4 + +
[docs] def __init__(self, values=None, **kwargs): + ''' Initializes a new response instance + + :param values: The resulting register values + ''' + ReadRegistersResponseBase.__init__(self, values, **kwargs)
+ + +
[docs]class ReadWriteMultipleRegistersRequest(ModbusRequest): + ''' + This function code performs a combination of one read operation and one + write operation in a single MODBUS transaction. The write + operation is performed before the read. + + Holding registers are addressed starting at zero. Therefore holding + registers 1-16 are addressed in the PDU as 0-15. + + The request specifies the starting address and number of holding + registers to be read as well as the starting address, number of holding + registers, and the data to be written. The byte count specifies the + number of bytes to follow in the write data field." + ''' + function_code = 23 + _rtu_byte_count_pos = 10 + +
[docs] def __init__(self, **kwargs): + ''' Initializes a new request message + + :param read_address: The address to start reading from + :param read_count: The number of registers to read from address + :param write_address: The address to start writing to + :param write_registers: The registers to write to the specified address + ''' + ModbusRequest.__init__(self, **kwargs) + self.read_address = kwargs.get('read_address', 0x00) + self.read_count = kwargs.get('read_count', 0) + self.write_address = kwargs.get('write_address', 0x00) + self.write_registers = kwargs.get('write_registers', None) + if not hasattr(self.write_registers, '__iter__'): + self.write_registers = [self.write_registers] + self.write_count = len(self.write_registers) + self.write_byte_count = self.write_count * 2
+ +
[docs] def encode(self): + ''' Encodes the request packet + + :returns: The encoded packet + ''' + result = struct.pack('>HHHHB', + self.read_address, self.read_count, \ + self.write_address, self.write_count, self.write_byte_count) + for register in self.write_registers: + result += struct.pack('>H', register) + return result
+ +
[docs] def decode(self, data): + ''' Decode the register request packet + + :param data: The request to decode + ''' + self.read_address, self.read_count, \ + self.write_address, self.write_count, \ + self.write_byte_count = struct.unpack('>HHHHB', data[:9]) + self.write_registers = [] + for i in range(9, self.write_byte_count + 9, 2): + register = struct.unpack('>H', data[i:i + 2])[0] + self.write_registers.append(register)
+ +
[docs] def execute(self, context): + ''' Run a write single register request against a datastore + + :param context: The datastore to request from + :returns: An initialized response, exception message otherwise + ''' + if not (1 <= self.read_count <= 0x07d): + return self.doException(merror.IllegalValue) + if not (1 <= self.write_count <= 0x079): + return self.doException(merror.IllegalValue) + if (self.write_byte_count != self.write_count * 2): + return self.doException(merror.IllegalValue) + if not context.validate(self.function_code, self.write_address, + self.write_count): + return self.doException(merror.IllegalAddress) + if not context.validate(self.function_code, self.read_address, + self.read_count): + return self.doException(merror.IllegalAddress) + context.setValues(self.function_code, self.write_address, + self.write_registers) + registers = context.getValues(self.function_code, self.read_address, + self.read_count) + return ReadWriteMultipleRegistersResponse(registers)
+ +
[docs] def get_response_pdu_size(self): + """ + Func_code (1 byte) + Byte Count(1 byte) + 2 * Quantity of Coils (n Bytes) + :return: + """ + return 1 + 1 + 2 * self.read_count
+ +
[docs] def __str__(self): + ''' Returns a string representation of the instance + + :returns: A string representation of the instance + ''' + params = (self.read_address, self.read_count, self.write_address, + self.write_count) + return "ReadWriteNRegisterRequest R(%d,%d) W(%d,%d)" % params
+ + +
[docs]class ReadWriteMultipleRegistersResponse(ModbusResponse): + ''' + The normal response contains the data from the group of registers that + were read. The byte count field specifies the quantity of bytes to + follow in the read data field. + ''' + function_code = 23 + _rtu_byte_count_pos = 2 + +
[docs] def __init__(self, values=None, **kwargs): + ''' Initializes a new instance + + :param values: The register values to write + ''' + ModbusResponse.__init__(self, **kwargs) + self.registers = values or []
+ +
[docs] def encode(self): + ''' Encodes the response packet + + :returns: The encoded packet + ''' + result = int2byte(len(self.registers) * 2) + for register in self.registers: + result += struct.pack('>H', register) + return result
+ +
[docs] def decode(self, data): + ''' Decode the register response packet + + :param data: The response to decode + ''' + bytecount = byte2int(data[0]) + for i in range(1, bytecount, 2): + self.registers.append(struct.unpack('>H', data[i:i + 2])[0])
+ +
[docs] def __str__(self): + ''' Returns a string representation of the instance + + :returns: A string representation of the instance + ''' + return "ReadWriteNRegisterResponse (%d)" % len(self.registers)
+ +#---------------------------------------------------------------------------# +# Exported symbols +#---------------------------------------------------------------------------# +__all__ = [ + "ReadHoldingRegistersRequest", "ReadHoldingRegistersResponse", + "ReadInputRegistersRequest", "ReadInputRegistersResponse", + "ReadWriteMultipleRegistersRequest", "ReadWriteMultipleRegistersResponse", +] +
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/doc/sphinx/html/_modules/pymodbus/register_write_message.html b/doc/sphinx/html/_modules/pymodbus/register_write_message.html new file mode 100644 index 000000000..f2990db88 --- /dev/null +++ b/doc/sphinx/html/_modules/pymodbus/register_write_message.html @@ -0,0 +1,337 @@ + + + + + + + + pymodbus.register_write_message — pymodbus 1.3.0.rc2 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for pymodbus.register_write_message

+'''
+Register Writing Request/Response Messages
+-------------------------------------------
+'''
+import struct
+from pymodbus.pdu import ModbusRequest
+from pymodbus.pdu import ModbusResponse
+from pymodbus.pdu import ModbusExceptions as merror
+
+
+
[docs]class WriteSingleRegisterRequest(ModbusRequest): + ''' + This function code is used to write a single holding register in a + remote device. + + The Request PDU specifies the address of the register to + be written. Registers are addressed starting at zero. Therefore register + numbered 1 is addressed as 0. + ''' + function_code = 6 + _rtu_frame_size = 8 + +
[docs] def __init__(self, address=None, value=None, **kwargs): + ''' Initializes a new instance + + :param address: The address to start writing add + :param value: The values to write + ''' + ModbusRequest.__init__(self, **kwargs) + self.address = address + self.value = value
+ +
[docs] def encode(self): + ''' Encode a write single register packet packet request + + :returns: The encoded packet + ''' + if self.skip_encode: + return self.value + return struct.pack('>HH', self.address, self.value)
+ +
[docs] def decode(self, data): + ''' Decode a write single register packet packet request + + :param data: The request to decode + ''' + self.address, self.value = struct.unpack('>HH', data)
+ +
[docs] def execute(self, context): + ''' Run a write single register request against a datastore + + :param context: The datastore to request from + :returns: An initialized response, exception message otherwise + ''' + if not (0 <= self.value <= 0xffff): + return self.doException(merror.IllegalValue) + if not context.validate(self.function_code, self.address, 1): + return self.doException(merror.IllegalAddress) + + context.setValues(self.function_code, self.address, [self.value]) + values = context.getValues(self.function_code, self.address, 1) + return WriteSingleRegisterResponse(self.address, values[0])
+ +
[docs] def get_response_pdu_size(self): + """ + Func_code (1 byte) + Register Address(2 byte) + Register Value (2 bytes) + :return: + """ + return 1 + 2 + 2
+ +
[docs] def __str__(self): + ''' Returns a string representation of the instance + + :returns: A string representation of the instance + ''' + return "WriteRegisterRequest %d => %d" % (self.address, self.value)
+ + +
[docs]class WriteSingleRegisterResponse(ModbusResponse): + ''' + The normal response is an echo of the request, returned after the + register contents have been written. + ''' + function_code = 6 + _rtu_frame_size = 8 + +
[docs] def __init__(self, address=None, value=None, **kwargs): + ''' Initializes a new instance + + :param address: The address to start writing add + :param value: The values to write + ''' + ModbusResponse.__init__(self, **kwargs) + self.address = address + self.value = value
+ +
[docs] def encode(self): + ''' Encode a write single register packet packet request + + :returns: The encoded packet + ''' + return struct.pack('>HH', self.address, self.value)
+ +
[docs] def decode(self, data): + ''' Decode a write single register packet packet request + + :param data: The request to decode + ''' + self.address, self.value = struct.unpack('>HH', data)
+ +
[docs] def __str__(self): + ''' Returns a string representation of the instance + + :returns: A string representation of the instance + ''' + params = (self.address, self.value) + return "WriteRegisterResponse %d => %d" % params
+ + +#---------------------------------------------------------------------------# +# Write Multiple Registers +#---------------------------------------------------------------------------# +
[docs]class WriteMultipleRegistersRequest(ModbusRequest): + ''' + This function code is used to write a block of contiguous registers (1 + to approx. 120 registers) in a remote device. + + The requested written values are specified in the request data field. + Data is packed as two bytes per register. + ''' + function_code = 16 + _rtu_byte_count_pos = 6 + _pdu_length = 5 #func + adress1 + adress2 + outputQuant1 + outputQuant2 + +
[docs] def __init__(self, address=None, values=None, **kwargs): + ''' Initializes a new instance + + :param address: The address to start writing to + :param values: The values to write + ''' + ModbusRequest.__init__(self, **kwargs) + self.address = address + if values is None: + values = [] + elif not hasattr(values, '__iter__'): + values = [values] + self.values = values + self.count = len(self.values) + self.byte_count = self.count * 2
+ +
[docs] def encode(self): + ''' Encode a write single register packet packet request + + :returns: The encoded packet + ''' + packet = struct.pack('>HHB', self.address, self.count, self.byte_count) + if self.skip_encode: + return packet + b''.join(self.values) + + for value in self.values: + packet += struct.pack('>H', value) + + return packet
+ +
[docs] def decode(self, data): + ''' Decode a write single register packet packet request + + :param data: The request to decode + ''' + self.address, self.count, \ + self.byte_count = struct.unpack('>HHB', data[:5]) + self.values = [] # reset + for idx in range(5, (self.count * 2) + 5, 2): + self.values.append(struct.unpack('>H', data[idx:idx + 2])[0])
+ +
[docs] def execute(self, context): + ''' Run a write single register request against a datastore + + :param context: The datastore to request from + :returns: An initialized response, exception message otherwise + ''' + if not (1 <= self.count <= 0x07b): + return self.doException(merror.IllegalValue) + if (self.byte_count != self.count * 2): + return self.doException(merror.IllegalValue) + if not context.validate(self.function_code, self.address, self.count): + return self.doException(merror.IllegalAddress) + + context.setValues(self.function_code, self.address, self.values) + return WriteMultipleRegistersResponse(self.address, self.count)
+ +
[docs] def __str__(self): + ''' Returns a string representation of the instance + + :returns: A string representation of the instance + ''' + params = (self.address, self.count) + return "WriteMultipleRegisterRequest %d => %d" % params
+ + +
[docs]class WriteMultipleRegistersResponse(ModbusResponse): + ''' + "The normal response returns the function code, starting address, and + quantity of registers written. + ''' + function_code = 16 + _rtu_frame_size = 8 + +
[docs] def __init__(self, address=None, count=None, **kwargs): + ''' Initializes a new instance + + :param address: The address to start writing to + :param count: The number of registers to write to + ''' + ModbusResponse.__init__(self, **kwargs) + self.address = address + self.count = count
+ +
[docs] def encode(self): + ''' Encode a write single register packet packet request + + :returns: The encoded packet + ''' + return struct.pack('>HH', self.address, self.count)
+ +
[docs] def decode(self, data): + ''' Decode a write single register packet packet request + + :param data: The request to decode + ''' + self.address, self.count = struct.unpack('>HH', data)
+ +
[docs] def __str__(self): + ''' Returns a string representation of the instance + + :returns: A string representation of the instance + ''' + params = (self.address, self.count) + return "WriteMultipleRegisterResponse (%d,%d)" % params
+ +#---------------------------------------------------------------------------# +# Exported symbols +#---------------------------------------------------------------------------# +__all__ = [ + "WriteSingleRegisterRequest", "WriteSingleRegisterResponse", + "WriteMultipleRegistersRequest", "WriteMultipleRegistersResponse", +] +
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/doc/sphinx/html/_modules/pymodbus/server/async.html b/doc/sphinx/html/_modules/pymodbus/server/async.html new file mode 100644 index 000000000..dbbe5cea1 --- /dev/null +++ b/doc/sphinx/html/_modules/pymodbus/server/async.html @@ -0,0 +1,370 @@ + + + + + + + + pymodbus.server.async — pymodbus 1.3.0.rc2 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for pymodbus.server.async

+'''
+Implementation of a Twisted Modbus Server
+------------------------------------------
+
+'''
+from binascii import b2a_hex
+from twisted.internet import protocol
+from twisted.internet.protocol import ServerFactory
+
+from pymodbus.constants import Defaults
+from pymodbus.factory import ServerDecoder
+from pymodbus.datastore import ModbusServerContext
+from pymodbus.device import ModbusControlBlock
+from pymodbus.device import ModbusAccessControl
+from pymodbus.device import ModbusDeviceIdentification
+from pymodbus.exceptions import NoSuchSlaveException
+from pymodbus.transaction import ModbusSocketFramer, ModbusAsciiFramer
+from pymodbus.pdu import ModbusExceptions as merror
+from pymodbus.internal.ptwisted import InstallManagementConsole
+from pymodbus.compat import byte2int
+from twisted.internet import reactor
+
+#---------------------------------------------------------------------------#
+# Logging
+#---------------------------------------------------------------------------#
+import logging
+_logger = logging.getLogger(__name__)
+
+
+#---------------------------------------------------------------------------#
+# Modbus TCP Server
+#---------------------------------------------------------------------------#
+
[docs]class ModbusTcpProtocol(protocol.Protocol): + ''' Implements a modbus server in twisted ''' + +
[docs] def connectionMade(self): + ''' Callback for when a client connects + + ..note:: since the protocol factory cannot be accessed from the + protocol __init__, the client connection made is essentially + our __init__ method. + ''' + _logger.debug("Client Connected [%s]" % self.transport.getHost()) + self.framer = self.factory.framer(decoder=self.factory.decoder)
+ +
[docs] def connectionLost(self, reason): + ''' Callback for when a client disconnects + + :param reason: The client's reason for disconnecting + ''' + _logger.debug("Client Disconnected: %s" % reason)
+ +
[docs] def dataReceived(self, data): + ''' Callback when we receive any data + + :param data: The data sent by the client + ''' + if _logger.isEnabledFor(logging.DEBUG): + _logger.debug(' '.join([hex(byte2int(x)) for x in data])) + if not self.factory.control.ListenOnly: + self.framer.processIncomingPacket(data, self._execute)
+ +
[docs] def _execute(self, request): + ''' Executes the request and returns the result + + :param request: The decoded request message + ''' + try: + context = self.factory.store[request.unit_id] + response = request.execute(context) + except NoSuchSlaveException as ex: + _logger.debug("requested slave does not exist: %s" % request.unit_id ) + if self.factory.ignore_missing_slaves: + return # the client will simply timeout waiting for a response + response = request.doException(merror.GatewayNoResponse) + except Exception as ex: + _logger.debug("Datastore unable to fulfill request: %s" % ex) + response = request.doException(merror.SlaveFailure) + #self.framer.populateResult(response) + response.transaction_id = request.transaction_id + response.unit_id = request.unit_id + self._send(response)
+ +
[docs] def _send(self, message): + ''' Send a request (string) to the network + + :param message: The unencoded modbus response + ''' + if message.should_respond: + self.factory.control.Counter.BusMessage += 1 + pdu = self.framer.buildPacket(message) + if _logger.isEnabledFor(logging.DEBUG): + _logger.debug('send: %s' % b2a_hex(pdu)) + return self.transport.write(pdu)
+ + +
[docs]class ModbusServerFactory(ServerFactory): + ''' + Builder class for a modbus server + + This also holds the server datastore so that it is + persisted between connections + ''' + + protocol = ModbusTcpProtocol + +
[docs] def __init__(self, store, framer=None, identity=None, **kwargs): + ''' Overloaded initializer for the modbus factory + + If the identify structure is not passed in, the ModbusControlBlock + uses its own empty structure. + + :param store: The ModbusServerContext datastore + :param framer: The framer strategy to use + :param identity: An optional identify structure + :param ignore_missing_slaves: True to not send errors on a request to a missing slave + ''' + self.decoder = ServerDecoder() + self.framer = framer or ModbusSocketFramer + self.store = store or ModbusServerContext() + self.control = ModbusControlBlock() + self.access = ModbusAccessControl() + self.ignore_missing_slaves = kwargs.get('ignore_missing_slaves', Defaults.IgnoreMissingSlaves) + + if isinstance(identity, ModbusDeviceIdentification): + self.control.Identity.update(identity)
+ + +#---------------------------------------------------------------------------# +# Modbus UDP Server +#---------------------------------------------------------------------------# +
[docs]class ModbusUdpProtocol(protocol.DatagramProtocol): + ''' Implements a modbus udp server in twisted ''' + +
[docs] def __init__(self, store, framer=None, identity=None, **kwargs): + ''' Overloaded initializer for the modbus factory + + If the identify structure is not passed in, the ModbusControlBlock + uses its own empty structure. + + :param store: The ModbusServerContext datastore + :param framer: The framer strategy to use + :param identity: An optional identify structure + :param ignore_missing_slaves: True to not send errors on a request to a missing slave + ''' + framer = framer or ModbusSocketFramer + self.framer = framer(decoder=ServerDecoder()) + self.store = store or ModbusServerContext() + self.control = ModbusControlBlock() + self.access = ModbusAccessControl() + self.ignore_missing_slaves = kwargs.get('ignore_missing_slaves', Defaults.IgnoreMissingSlaves) + + if isinstance(identity, ModbusDeviceIdentification): + self.control.Identity.update(identity)
+ +
[docs] def datagramReceived(self, data, addr): + ''' Callback when we receive any data + + :param data: The data sent by the client + ''' + _logger.debug("Client Connected [%s:%s]" % addr) + if _logger.isEnabledFor(logging.DEBUG): + _logger.debug(" ".join([hex(byte2int(x)) for x in data])) + if not self.control.ListenOnly: + continuation = lambda request: self._execute(request, addr) + self.framer.processIncomingPacket(data, continuation)
+ +
[docs] def _execute(self, request, addr): + ''' Executes the request and returns the result + + :param request: The decoded request message + ''' + try: + context = self.store[request.unit_id] + response = request.execute(context) + except NoSuchSlaveException as ex: + _logger.debug("requested slave does not exist: %s" % request.unit_id ) + if self.ignore_missing_slaves: + return # the client will simply timeout waiting for a response + response = request.doException(merror.GatewayNoResponse) + except Exception as ex: + _logger.debug("Datastore unable to fulfill request: %s" % ex) + response = request.doException(merror.SlaveFailure) + #self.framer.populateResult(response) + response.transaction_id = request.transaction_id + response.unit_id = request.unit_id + self._send(response, addr)
+ +
[docs] def _send(self, message, addr): + ''' Send a request (string) to the network + + :param message: The unencoded modbus response + :param addr: The (host, port) to send the message to + ''' + self.control.Counter.BusMessage += 1 + pdu = self.framer.buildPacket(message) + if _logger.isEnabledFor(logging.DEBUG): + _logger.debug('send: %s' % b2a_hex(pdu)) + return self.transport.write(pdu, addr)
+ + +#---------------------------------------------------------------------------# +# Starting Factories +#---------------------------------------------------------------------------# +
[docs]def StartTcpServer(context, identity=None, address=None, console=False, **kwargs): + ''' Helper method to start the Modbus Async TCP server + + :param context: The server data context + :param identify: The server identity to use (default empty) + :param address: An optional (interface, port) to bind to. + :param console: A flag indicating if you want the debug console + :param ignore_missing_slaves: True to not send errors on a request to a missing slave + ''' + from twisted.internet import reactor + + address = address or ("", Defaults.Port) + framer = ModbusSocketFramer + factory = ModbusServerFactory(context, framer, identity, **kwargs) + if console: + InstallManagementConsole({'factory': factory}) + + _logger.info("Starting Modbus TCP Server on %s:%s" % address) + reactor.listenTCP(address[1], factory, interface=address[0]) + reactor.run()
+ + +
[docs]def StartUdpServer(context, identity=None, address=None, **kwargs): + ''' Helper method to start the Modbus Async Udp server + + :param context: The server data context + :param identify: The server identity to use (default empty) + :param address: An optional (interface, port) to bind to. + :param ignore_missing_slaves: True to not send errors on a request to a missing slave + ''' + from twisted.internet import reactor + + address = address or ("", Defaults.Port) + framer = ModbusSocketFramer + server = ModbusUdpProtocol(context, framer, identity, **kwargs) + + _logger.info("Starting Modbus UDP Server on %s:%s" % address) + reactor.listenUDP(address[1], server, interface=address[0]) + reactor.run()
+ + +
[docs]def StartSerialServer(context, identity=None, + framer=ModbusAsciiFramer, **kwargs): + ''' Helper method to start the Modbus Async Serial server + + :param context: The server data context + :param identify: The server identity to use (default empty) + :param framer: The framer to use (default ModbusAsciiFramer) + :param port: The serial port to attach to + :param baudrate: The baud rate to use for the serial device + :param console: A flag indicating if you want the debug console + :param ignore_missing_slaves: True to not send errors on a request to a missing slave + ''' + from twisted.internet import reactor + from twisted.internet.serialport import SerialPort + + port = kwargs.get('port', '/dev/ttyS0') + baudrate = kwargs.get('baudrate', Defaults.Baudrate) + console = kwargs.get('console', False) + + _logger.info("Starting Modbus Serial Server on %s" % port) + factory = ModbusServerFactory(context, framer, identity, **kwargs) + if console: + InstallManagementConsole({'factory': factory}) + + protocol = factory.buildProtocol(None) + SerialPort.getHost = lambda self: port # hack for logging + SerialPort(protocol, port, reactor, baudrate) + reactor.run()
+ +#---------------------------------------------------------------------------# +# Exported symbols +#---------------------------------------------------------------------------# +__all__ = [ + "StartTcpServer", "StartUdpServer", "StartSerialServer", +] +
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/doc/sphinx/html/_modules/pymodbus/server/sync.html b/doc/sphinx/html/_modules/pymodbus/server/sync.html new file mode 100644 index 000000000..cd97b92e7 --- /dev/null +++ b/doc/sphinx/html/_modules/pymodbus/server/sync.html @@ -0,0 +1,597 @@ + + + + + + + + pymodbus.server.sync — pymodbus 1.3.0.rc2 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for pymodbus.server.sync

+'''
+Implementation of a Threaded Modbus Server
+------------------------------------------
+
+'''
+from binascii import b2a_hex
+import serial
+import socket
+import traceback
+
+from pymodbus.constants import Defaults
+from pymodbus.factory import ServerDecoder
+from pymodbus.datastore import ModbusServerContext
+from pymodbus.device import ModbusControlBlock
+from pymodbus.device import ModbusDeviceIdentification
+from pymodbus.transaction import *
+from pymodbus.exceptions import NotImplementedException, NoSuchSlaveException
+from pymodbus.pdu import ModbusExceptions as merror
+from pymodbus.compat import socketserver, byte2int
+
+#---------------------------------------------------------------------------#
+# Logging
+#---------------------------------------------------------------------------#
+import logging
+_logger = logging.getLogger(__name__)
+
+
+#---------------------------------------------------------------------------#
+# Protocol Handlers
+#---------------------------------------------------------------------------#
+
[docs]class ModbusBaseRequestHandler(socketserver.BaseRequestHandler): + ''' Implements the modbus server protocol + + This uses the socketserver.BaseRequestHandler to implement + the client handler. + ''' + +
[docs] def setup(self): + ''' Callback for when a client connects + ''' + _logger.debug("Client Connected [%s:%s]" % self.client_address) + self.running = True + self.framer = self.server.framer(self.server.decoder) + self.server.threads.append(self)
+ +
[docs] def finish(self): + ''' Callback for when a client disconnects + ''' + _logger.debug("Client Disconnected [%s:%s]" % self.client_address) + self.server.threads.remove(self)
+ +
[docs] def execute(self, request): + ''' The callback to call with the resulting message + + :param request: The decoded request message + ''' + try: + context = self.server.context[request.unit_id] + response = request.execute(context) + except NoSuchSlaveException as ex: + _logger.debug("requested slave does not exist: %s" % request.unit_id ) + if self.server.ignore_missing_slaves: + return # the client will simply timeout waiting for a response + response = request.doException(merror.GatewayNoResponse) + except Exception as ex: + _logger.debug("Datastore unable to fulfill request: %s; %s", ex, traceback.format_exc() ) + response = request.doException(merror.SlaveFailure) + response.transaction_id = request.transaction_id + response.unit_id = request.unit_id + self.send(response)
+ + #---------------------------------------------------------------------------# + # Base class implementations + #---------------------------------------------------------------------------# +
[docs] def handle(self): + ''' Callback when we receive any data + ''' + raise NotImplementedException("Method not implemented by derived class")
+ +
[docs] def send(self, message): + ''' Send a request (string) to the network + + :param message: The unencoded modbus response + ''' + raise NotImplementedException("Method not implemented by derived class")
+ + +# class ModbusSingleRequestHandler(ModbusBaseRequestHandler): +# ''' Implements the modbus server protocol +# +# This uses the socketserver.BaseRequestHandler to implement +# the client handler for a single client(serial clients) +# ''' +# +# def handle(self): +# ''' Callback when we receive any data +# ''' +# while self.running: +# try: +# data = self.request.recv(1024) +# if data: +# if _logger.isEnabledFor(logging.DEBUG): +# _logger.debug(' '.join([hex(byte2int(x)) for x in data])) +# self.framer.processIncomingPacket(data, self.execute) +# except Exception as msg: +# # since we only have a single socket, we cannot exit +# _logger.error("Socket error occurred %s" % msg) +# +# def send(self, message): +# ''' Send a request (string) to the network +# +# :param message: The unencoded modbus response +# ''' + + +
[docs]class ModbusSingleRequestHandler(ModbusBaseRequestHandler): + ''' Implements the modbus server protocol + + This uses the socketserver.BaseRequestHandler to implement + the client handler for a single client(serial clients) + ''' + +
[docs] def handle(self): + ''' Callback when we receive any data + ''' + while self.running: + try: + data = self.request.recv(1024) + if data: + if _logger.isEnabledFor(logging.DEBUG): + _logger.debug(" ".join([hex(byte2int(x)) for x in data])) + self.framer.processIncomingPacket(data, self.execute) + except Exception as msg: + # since we only have a single socket, we cannot exit + _logger.error("Socket error occurred %s" % msg)
+ +
[docs] def send(self, message): + ''' Send a request (string) to the network + + :param message: The unencoded modbus response + ''' + if message.should_respond: + #self.server.control.Counter.BusMessage += 1 + pdu = self.framer.buildPacket(message) + if _logger.isEnabledFor(logging.DEBUG): + _logger.debug('send: %s' % b2a_hex(pdu)) + return self.request.send(pdu)
+ + +
[docs]class ModbusConnectedRequestHandler(ModbusBaseRequestHandler): + ''' Implements the modbus server protocol + + This uses the socketserver.BaseRequestHandler to implement + the client handler for a connected protocol (TCP). + ''' + +
[docs] def handle(self): + '''Callback when we receive any data, until self.running becomes not True. Blocks indefinitely + awaiting data. If shutdown is required, then the global socket.settimeout(<seconds>) may be + used, to allow timely checking of self.running. However, since this also affects socket + connects, if there are outgoing socket connections used in the same program, then these will + be prevented, if the specfied timeout is too short. Hence, this is unreliable. + + To respond to Modbus...Server.server_close() (which clears each handler's self.running), + derive from this class to provide an alternative handler that awakens from time to time when + no input is available and checks self.running. Use Modbus...Server( handler=... ) keyword + to supply the alternative request handler class. + + ''' + while self.running: + try: + data = self.request.recv(1024) + if not data: self.running = False + if _logger.isEnabledFor(logging.DEBUG): + _logger.debug(' '.join([hex(byte2int(x)) for x in data])) + # if not self.server.control.ListenOnly: + self.framer.processIncomingPacket(data, self.execute) + except socket.timeout as msg: + if _logger.isEnabledFor(logging.DEBUG): + _logger.debug("Socket timeout occurred %s", msg) + pass + except socket.error as msg: + _logger.error("Socket error occurred %s" % msg) + self.running = False + except: + _logger.error("Socket exception occurred %s" % traceback.format_exc() ) + self.running = False
+ +
[docs] def send(self, message): + ''' Send a request (string) to the network + + :param message: The unencoded modbus response + ''' + if message.should_respond: + #self.server.control.Counter.BusMessage += 1 + pdu = self.framer.buildPacket(message) + if _logger.isEnabledFor(logging.DEBUG): + _logger.debug('send: %s' % b2a_hex(pdu)) + return self.request.send(pdu)
+ + +
[docs]class ModbusDisconnectedRequestHandler(ModbusBaseRequestHandler): + ''' Implements the modbus server protocol + + This uses the socketserver.BaseRequestHandler to implement + the client handler for a disconnected protocol (UDP). The + only difference is that we have to specify who to send the + resulting packet data to. + ''' + +
[docs] def handle(self): + ''' Callback when we receive any data + ''' + while self.running: + try: + data, self.request = self.request + if not data: self.running = False + if _logger.isEnabledFor(logging.DEBUG): + _logger.debug(' '.join([hex(byte2int(x)) for x in data])) + # if not self.server.control.ListenOnly: + self.framer.processIncomingPacket(data, self.execute) + except socket.timeout: pass + except socket.error as msg: + _logger.error("Socket error occurred %s" % msg) + self.running = False + except: self.running = False
+ +
[docs] def send(self, message): + ''' Send a request (string) to the network + + :param message: The unencoded modbus response + ''' + if message.should_respond: + #self.server.control.Counter.BusMessage += 1 + pdu = self.framer.buildPacket(message) + if _logger.isEnabledFor(logging.DEBUG): + _logger.debug('send: %s' % b2a_hex(pdu)) + return self.request.sendto(pdu, self.client_address)
+ + +#---------------------------------------------------------------------------# +# Server Implementations +#---------------------------------------------------------------------------# +
[docs]class ModbusTcpServer(socketserver.ThreadingTCPServer): + ''' + A modbus threaded tcp socket server + + We inherit and overload the socket server so that we + can control the client threads as well as have a single + server context instance. + ''' + +
[docs] def __init__(self, context, framer=None, identity=None, address=None, handler=None, **kwargs): + ''' Overloaded initializer for the socket server + + If the identify structure is not passed in, the ModbusControlBlock + uses its own empty structure. + + :param context: The ModbusServerContext datastore + :param framer: The framer strategy to use + :param identity: An optional identify structure + :param address: An optional (interface, port) to bind to. + :param handler: A handler for each client session; default is ModbusConnectedRequestHandler + :param ignore_missing_slaves: True to not send errors on a request to a missing slave + ''' + self.threads = [] + self.decoder = ServerDecoder() + self.framer = framer or ModbusSocketFramer + self.context = context or ModbusServerContext() + self.control = ModbusControlBlock() + self.address = address or ("", Defaults.Port) + self.ignore_missing_slaves = kwargs.get('ignore_missing_slaves', Defaults.IgnoreMissingSlaves) + + if isinstance(identity, ModbusDeviceIdentification): + self.control.Identity.update(identity) + + socketserver.ThreadingTCPServer.__init__(self, + self.address, ModbusConnectedRequestHandler)
+ +
[docs] def process_request(self, request, client): + ''' Callback for connecting a new client thread + + :param request: The request to handle + :param client: The address of the client + ''' + _logger.debug("Started thread to serve client at " + str(client)) + socketserver.ThreadingTCPServer.process_request(self, request, client)
+ +
[docs] def shutdown(self): + ''' Stops the serve_forever loop. + + Overridden to signal handlers to stop. + ''' + for thread in self.threads: + thread.running = False + socketserver.ThreadingTCPServer.shutdown(self)
+ +
[docs] def server_close(self): + ''' Callback for stopping the running server + ''' + _logger.debug("Modbus server stopped") + self.socket.close() + for thread in self.threads: + thread.running = False
+ + +
[docs]class ModbusUdpServer(socketserver.ThreadingUDPServer): + ''' + A modbus threaded udp socket server + + We inherit and overload the socket server so that we + can control the client threads as well as have a single + server context instance. + ''' + +
[docs] def __init__(self, context, framer=None, identity=None, address=None, handler=None, **kwargs): + ''' Overloaded initializer for the socket server + + If the identify structure is not passed in, the ModbusControlBlock + uses its own empty structure. + + :param context: The ModbusServerContext datastore + :param framer: The framer strategy to use + :param identity: An optional identify structure + :param address: An optional (interface, port) to bind to. + :param handler: A handler for each client session; default is ModbusDisonnectedRequestHandler + :param ignore_missing_slaves: True to not send errors on a request to a missing slave + ''' + self.threads = [] + self.decoder = ServerDecoder() + self.framer = framer or ModbusSocketFramer + self.context = context or ModbusServerContext() + self.control = ModbusControlBlock() + self.address = address or ("", Defaults.Port) + self.ignore_missing_slaves = kwargs.get('ignore_missing_slaves', Defaults.IgnoreMissingSlaves) + + if isinstance(identity, ModbusDeviceIdentification): + self.control.Identity.update(identity) + + socketserver.ThreadingUDPServer.__init__(self, + self.address, ModbusDisconnectedRequestHandler)
+ +
[docs] def process_request(self, request, client): + ''' Callback for connecting a new client thread + + :param request: The request to handle + :param client: The address of the client + ''' + packet, socket = request # TODO I might have to rewrite + _logger.debug("Started thread to serve client at " + str(client)) + socketserver.ThreadingUDPServer.process_request(self, request, client)
+ +
[docs] def server_close(self): + ''' Callback for stopping the running server + ''' + _logger.debug("Modbus server stopped") + self.socket.close() + for thread in self.threads: + thread.running = False
+ + +
[docs]class ModbusSerialServer(object): + ''' + A modbus threaded serial socket server + + We inherit and overload the socket server so that we + can control the client threads as well as have a single + server context instance. + ''' + +
[docs] def __init__(self, context, framer=None, identity=None, **kwargs): + ''' Overloaded initializer for the socket server + + If the identify structure is not passed in, the ModbusControlBlock + uses its own empty structure. + + :param context: The ModbusServerContext datastore + :param framer: The framer strategy to use + :param identity: An optional identify structure + :param port: The serial port to attach to + :param stopbits: The number of stop bits to use + :param bytesize: The bytesize of the serial messages + :param parity: Which kind of parity to use + :param baudrate: The baud rate to use for the serial device + :param timeout: The timeout to use for the serial device + :param ignore_missing_slaves: True to not send errors on a request to a missing slave + ''' + self.threads = [] + self.decoder = ServerDecoder() + self.framer = framer or ModbusAsciiFramer + self.context = context or ModbusServerContext() + self.control = ModbusControlBlock() + + if isinstance(identity, ModbusDeviceIdentification): + self.control.Identity.update(identity) + + self.device = kwargs.get('port', 0) + self.stopbits = kwargs.get('stopbits', Defaults.Stopbits) + self.bytesize = kwargs.get('bytesize', Defaults.Bytesize) + self.parity = kwargs.get('parity', Defaults.Parity) + self.baudrate = kwargs.get('baudrate', Defaults.Baudrate) + self.timeout = kwargs.get('timeout', Defaults.Timeout) + self.ignore_missing_slaves = kwargs.get('ignore_missing_slaves', Defaults.IgnoreMissingSlaves) + self.socket = None + self._connect() + self.is_running = True
+ +
[docs] def _connect(self): + ''' Connect to the serial server + + :returns: True if connection succeeded, False otherwise + ''' + if self.socket: return True + try: + self.socket = serial.Serial(port=self.device, timeout=self.timeout, + bytesize=self.bytesize, stopbits=self.stopbits, + baudrate=self.baudrate, parity=self.parity) + except serial.SerialException as msg: + _logger.error(msg) + return self.socket != None
+ +
[docs] def _build_handler(self): + ''' A helper method to create and monkeypatch + a serial handler. + + :returns: A patched handler + ''' + request = self.socket + request.send = request.write + request.recv = request.read + handler = ModbusSingleRequestHandler(request, + (self.device, self.device), self) + return handler
+ +
[docs] def serve_forever(self): + ''' Callback for connecting a new client thread + + :param request: The request to handle + :param client: The address of the client + ''' + _logger.debug("Started thread to serve client") + handler = self._build_handler() + while self.is_running: + handler.handle()
+ +
[docs] def server_close(self): + ''' Callback for stopping the running server + ''' + _logger.debug("Modbus server stopped") + self.is_running = False + self.socket.close()
+ + +#---------------------------------------------------------------------------# +# Creation Factories +#---------------------------------------------------------------------------# +
[docs]def StartTcpServer(context=None, identity=None, address=None, **kwargs): + ''' A factory to start and run a tcp modbus server + + :param context: The ModbusServerContext datastore + :param identity: An optional identify structure + :param address: An optional (interface, port) to bind to. + :param ignore_missing_slaves: True to not send errors on a request to a missing slave + ''' + framer = ModbusSocketFramer + server = ModbusTcpServer(context, framer, identity, address, **kwargs) + server.serve_forever()
+ + +
[docs]def StartUdpServer(context=None, identity=None, address=None, **kwargs): + ''' A factory to start and run a udp modbus server + + :param context: The ModbusServerContext datastore + :param identity: An optional identify structure + :param address: An optional (interface, port) to bind to. + :param framer: The framer to operate with (default ModbusSocketFramer) + :param ignore_missing_slaves: True to not send errors on a request to a missing slave + ''' + framer = kwargs.pop('framer', ModbusSocketFramer) + server = ModbusUdpServer(context, framer, identity, address, **kwargs) + server.serve_forever()
+ + +
[docs]def StartSerialServer(context=None, identity=None, **kwargs): + ''' A factory to start and run a serial modbus server + + :param context: The ModbusServerContext datastore + :param identity: An optional identify structure + :param framer: The framer to operate with (default ModbusAsciiFramer) + :param port: The serial port to attach to + :param stopbits: The number of stop bits to use + :param bytesize: The bytesize of the serial messages + :param parity: Which kind of parity to use + :param baudrate: The baud rate to use for the serial device + :param timeout: The timeout to use for the serial device + :param ignore_missing_slaves: True to not send errors on a request to a missing slave + ''' + framer = kwargs.pop('framer', ModbusAsciiFramer) + server = ModbusSerialServer(context, framer, identity, **kwargs) + server.serve_forever()
+ +#---------------------------------------------------------------------------# +# Exported symbols +#---------------------------------------------------------------------------# +__all__ = [ + "StartTcpServer", "StartUdpServer", "StartSerialServer" +] +
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/doc/sphinx/html/_modules/pymodbus/transaction.html b/doc/sphinx/html/_modules/pymodbus/transaction.html new file mode 100644 index 000000000..c7d53ea22 --- /dev/null +++ b/doc/sphinx/html/_modules/pymodbus/transaction.html @@ -0,0 +1,1064 @@ + + + + + + + + pymodbus.transaction — pymodbus 1.3.0.rc2 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for pymodbus.transaction

+'''
+Collection of transaction based abstractions
+'''
+import sys
+import struct
+import socket
+from binascii import b2a_hex, a2b_hex
+
+from pymodbus.exceptions import ModbusIOException, NotImplementedException
+from pymodbus.constants  import Defaults
+from pymodbus.interfaces import IModbusFramer
+from pymodbus.utilities  import checkCRC, computeCRC
+from pymodbus.utilities  import checkLRC, computeLRC
+from pymodbus.compat import iterkeys, imap, byte2int
+
+#---------------------------------------------------------------------------#
+# Logging
+#---------------------------------------------------------------------------#
+import logging
+_logger = logging.getLogger(__name__)
+
+
+#---------------------------------------------------------------------------#
+# The Global Transaction Manager
+#---------------------------------------------------------------------------#
+class ModbusTransactionManager(object):
+    ''' Impelements a transaction for a manager
+
+    The transaction protocol can be represented by the following pseudo code::
+
+        count = 0
+        do
+          result = send(message)
+          if (timeout or result == bad)
+             count++
+          else break
+        while (count < 3)
+
+    This module helps to abstract this away from the framer and protocol.
+    '''
+
+    def __init__(self, client, **kwargs):
+        ''' Initializes an instance of the ModbusTransactionManager
+
+        :param client: The client socket wrapper
+        :param retry_on_empty: Should the client retry on empty
+        :param retries: The number of retries to allow
+        '''
+        self.tid = Defaults.TransactionId
+        self.client = client
+        self.retry_on_empty = kwargs.get('retry_on_empty', Defaults.RetryOnEmpty)
+        self.retries = kwargs.get('retries', Defaults.Retries)
+        if client:
+            self._set_adu_size()
+
+    def _set_adu_size(self):
+        # base ADU size of modbus frame in bytes
+        if isinstance(self.client.framer, ModbusSocketFramer):
+            self.base_adu_size = 7 # tid(2), pid(2), length(2), uid(1)
+        elif isinstance(self.client.framer, ModbusRtuFramer):
+            self.base_adu_size = 3 # address(1), CRC(2)
+        elif isinstance(self.client.framer, ModbusAsciiFramer):
+            self.base_adu_size = 4 # Address(2), LRC(2)
+        elif isinstance(self.client.framer, ModbusBinaryFramer):
+            self.base_adu_size = 3 #, Address(1), CRC(2)
+        else:
+            self.base_adu_size = -1
+
+    def _calculate_response_length(self, expected_pdu_size):
+        if self.base_adu_size == -1:
+            return 1024
+        else:
+            return self.base_adu_size + expected_pdu_size
+
+    def execute(self, request):
+        ''' Starts the producer to send the next request to
+        consumer.write(Frame(request))
+        '''
+        retries = self.retries
+        request.transaction_id = self.getNextTID()
+        _logger.debug("Running transaction %d" % request.transaction_id)
+        if hasattr(request, "get_response_pdu_size"):
+            response_pdu_size = request.get_response_pdu_size()
+            expected_response_length = self._calculate_response_length(response_pdu_size)
+        else:
+            expected_response_length = 1024
+
+        while retries > 0:
+            try:
+                self.client.connect()
+                self.client._send(self.client.framer.buildPacket(request))
+                result = self.client._recv(expected_response_length)
+
+                if not result and self.retry_on_empty:
+                    retries -= 1
+                    continue
+                if _logger.isEnabledFor(logging.DEBUG):
+                    _logger.debug("recv: " + " ".join([hex(byte2int(x)) for x in result]))
+                self.client.framer.processIncomingPacket(result, self.addTransaction)
+                break;
+            except socket.error as msg:
+                self.client.close()
+                _logger.debug("Transaction failed. (%s) " % msg)
+                retries -= 1
+        return self.getTransaction(request.transaction_id)
+
+
+    def addTransaction(self, request, tid=None):
+        ''' Adds a transaction to the handler
+
+        This holds the requets in case it needs to be resent.
+        After being sent, the request is removed.
+
+        :param request: The request to hold on to
+        :param tid: The overloaded transaction id to use
+        '''
+        raise NotImplementedException("addTransaction")
+
+    def getTransaction(self, tid):
+        ''' Returns a transaction matching the referenced tid
+
+        If the transaction does not exist, None is returned
+
+        :param tid: The transaction to retrieve
+        '''
+        raise NotImplementedException("getTransaction")
+
+    def delTransaction(self, tid):
+        ''' Removes a transaction matching the referenced tid
+
+        :param tid: The transaction to remove
+        '''
+        raise NotImplementedException("delTransaction")
+
+    def getNextTID(self):
+        ''' Retrieve the next unique transaction identifier
+
+        This handles incrementing the identifier after
+        retrieval
+
+        :returns: The next unique transaction identifier
+        '''
+        self.tid = (self.tid + 1) & 0xffff
+        return self.tid
+
+    def reset(self):
+        ''' Resets the transaction identifier '''
+        self.tid = Defaults.TransactionId
+        self.transactions = type(self.transactions)()
+
+
+
[docs]class DictTransactionManager(ModbusTransactionManager): + ''' Impelements a transaction for a manager where the + results are keyed based on the supplied transaction id. + ''' + +
[docs] def __init__(self, client, **kwargs): + ''' Initializes an instance of the ModbusTransactionManager + + :param client: The client socket wrapper + ''' + self.transactions = {} + super(DictTransactionManager, self).__init__(client, **kwargs)
+ +
[docs] def __iter__(self): + ''' Iterater over the current managed transactions + + :returns: An iterator of the managed transactions + ''' + return iterkeys(self.transactions)
+ +
[docs] def addTransaction(self, request, tid=None): + ''' Adds a transaction to the handler + + This holds the requets in case it needs to be resent. + After being sent, the request is removed. + + :param request: The request to hold on to + :param tid: The overloaded transaction id to use + ''' + tid = tid if tid != None else request.transaction_id + _logger.debug("adding transaction %d" % tid) + self.transactions[tid] = request
+ +
[docs] def getTransaction(self, tid): + ''' Returns a transaction matching the referenced tid + + If the transaction does not exist, None is returned + + :param tid: The transaction to retrieve + ''' + _logger.debug("getting transaction %d" % tid) + return self.transactions.pop(tid, None)
+ +
[docs] def delTransaction(self, tid): + ''' Removes a transaction matching the referenced tid + + :param tid: The transaction to remove + ''' + _logger.debug("deleting transaction %d" % tid) + self.transactions.pop(tid, None)
+ + +
[docs]class FifoTransactionManager(ModbusTransactionManager): + ''' Impelements a transaction for a manager where the + results are returned in a FIFO manner. + ''' + +
[docs] def __init__(self, client, **kwargs): + ''' Initializes an instance of the ModbusTransactionManager + + :param client: The client socket wrapper + ''' + super(FifoTransactionManager, self).__init__(client, **kwargs) + self.transactions = []
+ +
[docs] def __iter__(self): + ''' Iterater over the current managed transactions + + :returns: An iterator of the managed transactions + ''' + return iter(self.transactions)
+ +
[docs] def addTransaction(self, request, tid=None): + ''' Adds a transaction to the handler + + This holds the requets in case it needs to be resent. + After being sent, the request is removed. + + :param request: The request to hold on to + :param tid: The overloaded transaction id to use + ''' + tid = tid if tid != None else request.transaction_id + _logger.debug("adding transaction %d" % tid) + self.transactions.append(request)
+ +
[docs] def getTransaction(self, tid): + ''' Returns a transaction matching the referenced tid + + If the transaction does not exist, None is returned + + :param tid: The transaction to retrieve + ''' + _logger.debug("getting transaction %s" % str(tid)) + return self.transactions.pop(0) if self.transactions else None
+ +
[docs] def delTransaction(self, tid): + ''' Removes a transaction matching the referenced tid + + :param tid: The transaction to remove + ''' + _logger.debug("deleting transaction %d" % tid) + if self.transactions: self.transactions.pop(0)
+ + +#---------------------------------------------------------------------------# +# Modbus TCP Message +#---------------------------------------------------------------------------# +
[docs]class ModbusSocketFramer(IModbusFramer): + ''' Modbus Socket Frame controller + + Before each modbus TCP message is an MBAP header which is used as a + message frame. It allows us to easily separate messages as follows:: + + [ MBAP Header ] [ Function Code] [ Data ] + [ tid ][ pid ][ length ][ uid ] + 2b 2b 2b 1b 1b Nb + + while len(message) > 0: + tid, pid, length`, uid = struct.unpack(">HHHB", message) + request = message[0:7 + length - 1`] + message = [7 + length - 1:] + + * length = uid + function code + data + * The -1 is to account for the uid byte + ''' + +
[docs] def __init__(self, decoder): + ''' Initializes a new instance of the framer + + :param decoder: The decoder factory implementation to use + ''' + self.__buffer = b'' + self.__header = {'tid':0, 'pid':0, 'len':0, 'uid':0} + self.__hsize = 0x07 + self.decoder = decoder
+ + #-----------------------------------------------------------------------# + # Private Helper Functions + #-----------------------------------------------------------------------# +
[docs] def checkFrame(self): + ''' + Check and decode the next frame Return true if we were successful + ''' + if len(self.__buffer) > self.__hsize: + self.__header['tid'], self.__header['pid'], \ + self.__header['len'], self.__header['uid'] = struct.unpack( + '>HHHB', self.__buffer[0:self.__hsize]) + + # someone sent us an error? ignore it + if self.__header['len'] < 2: + self.advanceFrame() + # we have at least a complete message, continue + elif len(self.__buffer) - self.__hsize + 1 >= self.__header['len']: + return True + # we don't have enough of a message yet, wait + return False
+ +
[docs] def advanceFrame(self): + ''' Skip over the current framed message + This allows us to skip over the current message after we have processed + it or determined that it contains an error. It also has to reset the + current frame header handle + ''' + length = self.__hsize + self.__header['len'] - 1 + self.__buffer = self.__buffer[length:] + self.__header = {'tid':0, 'pid':0, 'len':0, 'uid':0}
+ +
[docs] def isFrameReady(self): + ''' Check if we should continue decode logic + This is meant to be used in a while loop in the decoding phase to let + the decoder factory know that there is still data in the buffer. + + :returns: True if ready, False otherwise + ''' + return len(self.__buffer) > self.__hsize
+ +
[docs] def addToFrame(self, message): + ''' Adds new packet data to the current frame buffer + + :param message: The most recent packet + ''' + self.__buffer += message
+ +
[docs] def getFrame(self): + ''' Return the next frame from the buffered data + + :returns: The next full frame buffer + ''' + length = self.__hsize + self.__header['len'] - 1 + return self.__buffer[self.__hsize:length]
+ +
[docs] def populateResult(self, result): + ''' + Populates the modbus result with the transport specific header + information (pid, tid, uid, checksum, etc) + + :param result: The response packet + ''' + result.transaction_id = self.__header['tid'] + result.protocol_id = self.__header['pid'] + result.unit_id = self.__header['uid']
+ + #-----------------------------------------------------------------------# + # Public Member Functions + #-----------------------------------------------------------------------# +
[docs] def processIncomingPacket(self, data, callback): + ''' The new packet processing pattern + + This takes in a new request packet, adds it to the current + packet stream, and performs framing on it. That is, checks + for complete messages, and once found, will process all that + exist. This handles the case when we read N + 1 or 1 / N + messages at a time instead of 1. + + The processed and decoded messages are pushed to the callback + function to process and send. + + :param data: The new packet data + :param callback: The function to send results to + ''' + _logger.debug(' '.join([hex(byte2int(x)) for x in data])) + self.addToFrame(data) + while True: + if self.isFrameReady(): + if self.checkFrame(): + self._process(callback) + else: self.resetFrame() + else: + if len(self.__buffer): + # Possible error ??? + if self.__header['len'] < 2: + self._process(callback, error=True) + break
+ +
[docs] def _process(self, callback, error=False): + """ + Process incoming packets irrespective error condition + """ + data = self.getRawFrame() if error else self.getFrame() + result = self.decoder.decode(data) + if result is None: + raise ModbusIOException("Unable to decode request") + self.populateResult(result) + self.advanceFrame() + callback(result) # defer or push to a thread?
+ +
[docs] def resetFrame(self): + ''' Reset the entire message frame. + This allows us to skip ovver errors that may be in the stream. + It is hard to know if we are simply out of sync or if there is + an error in the stream as we have no way to check the start or + end of the message (python just doesn't have the resolution to + check for millisecond delays). + ''' + self.__buffer = '' + self.__header = {}
+ +
[docs] def getRawFrame(self): + """ + Returns the complete buffer + """ + return self.__buffer
+ +
[docs] def buildPacket(self, message): + ''' Creates a ready to send modbus packet + + :param message: The populated request/response to send + ''' + data = message.encode() + packet = struct.pack('>HHHBB', + message.transaction_id, + message.protocol_id, + len(data) + 2, + message.unit_id, + message.function_code) + data + return packet
+ + +#---------------------------------------------------------------------------# +# Modbus RTU Message +#---------------------------------------------------------------------------# +
[docs]class ModbusRtuFramer(IModbusFramer): + ''' + Modbus RTU Frame controller:: + + [ Start Wait ] [Address ][ Function Code] [ Data ][ CRC ][ End Wait ] + 3.5 chars 1b 1b Nb 2b 3.5 chars + + Wait refers to the amount of time required to transmist at least x many + characters. In this case it is 3.5 characters. Also, if we recieve a + wait of 1.5 characters at any point, we must trigger an error message. + Also, it appears as though this message is little endian. The logic is + simplified as the following:: + + block-on-read: + read until 3.5 delay + check for errors + decode + + The following table is a listing of the baud wait times for the specified + baud rates:: + + ------------------------------------------------------------------ + Baud 1.5c (18 bits) 3.5c (38 bits) + ------------------------------------------------------------------ + 1200 13333.3 us 31666.7 us + 4800 3333.3 us 7916.7 us + 9600 1666.7 us 3958.3 us + 19200 833.3 us 1979.2 us + 38400 416.7 us 989.6 us + ------------------------------------------------------------------ + 1 Byte = start + 8 bits + parity + stop = 11 bits + (1/Baud)(bits) = delay seconds + ''' + +
[docs] def __init__(self, decoder): + ''' Initializes a new instance of the framer + + :param decoder: The decoder factory implementation to use + ''' + self.__buffer = b'' + self.__header = {} + self.__hsize = 0x01 + self.__end = b'\x0d\x0a' + self.__min_frame_size = 4 + self.decoder = decoder
+ + #-----------------------------------------------------------------------# + # Private Helper Functions + #-----------------------------------------------------------------------# +
[docs] def checkFrame(self): + ''' + Check if the next frame is available. Return True if we were + successful. + ''' + try: + self.populateHeader() + frame_size = self.__header['len'] + data = self.__buffer[:frame_size - 2] + crc = self.__buffer[frame_size - 2:frame_size] + crc_val = (byte2int(crc[0]) << 8) + byte2int(crc[1]) + return checkCRC(data, crc_val) + except (IndexError, KeyError): + return False
+ +
[docs] def advanceFrame(self): + ''' Skip over the current framed message + This allows us to skip over the current message after we have processed + it or determined that it contains an error. It also has to reset the + current frame header handle + ''' + try: + self.__buffer = self.__buffer[self.__header['len']:] + except KeyError: + # Error response, no header len found + self.resetFrame() + self.__header = {}
+ +
[docs] def resetFrame(self): + ''' Reset the entire message frame. + This allows us to skip ovver errors that may be in the stream. + It is hard to know if we are simply out of sync or if there is + an error in the stream as we have no way to check the start or + end of the message (python just doesn't have the resolution to + check for millisecond delays). + ''' + self.__buffer = b'' + self.__header = {}
+ +
[docs] def isFrameReady(self): + ''' Check if we should continue decode logic + This is meant to be used in a while loop in the decoding phase to let + the decoder know that there is still data in the buffer. + + :returns: True if ready, False otherwise + ''' + return len(self.__buffer) > self.__hsize
+ +
[docs] def populateHeader(self): + ''' Try to set the headers `uid`, `len` and `crc`. + + This method examines `self.__buffer` and writes meta + information into `self.__header`. It calculates only the + values for headers that are not already in the dictionary. + + Beware that this method will raise an IndexError if + `self.__buffer` is not yet long enough. + ''' + self.__header['uid'] = byte2int(self.__buffer[0]) + func_code = byte2int(self.__buffer[1]) + pdu_class = self.decoder.lookupPduClass(func_code) + size = pdu_class.calculateRtuFrameSize(self.__buffer) + self.__header['len'] = size + self.__header['crc'] = self.__buffer[size - 2:size]
+ +
[docs] def addToFrame(self, message): + ''' + This should be used before the decoding while loop to add the received + data to the buffer handle. + + :param message: The most recent packet + ''' + self.__buffer += message
+ +
[docs] def getFrame(self): + ''' Get the next frame from the buffer + + :returns: The frame data or '' + ''' + start = self.__hsize + end = self.__header['len'] - 2 + buffer = self.__buffer[start:end] + if end > 0: return buffer + return ''
+ +
[docs] def populateResult(self, result): + ''' Populates the modbus result header + + The serial packets do not have any header information + that is copied. + + :param result: The response packet + ''' + result.unit_id = self.__header['uid']
+ + #-----------------------------------------------------------------------# + # Public Member Functions + #-----------------------------------------------------------------------# +
[docs] def processIncomingPacket(self, data, callback): + ''' The new packet processing pattern + + This takes in a new request packet, adds it to the current + packet stream, and performs framing on it. That is, checks + for complete messages, and once found, will process all that + exist. This handles the case when we read N + 1 or 1 / N + messages at a time instead of 1. + + The processed and decoded messages are pushed to the callback + function to process and send. + + :param data: The new packet data + :param callback: The function to send results to + ''' + self.addToFrame(data) + while True: + if self.isFrameReady(): + if self.checkFrame(): + self._process(callback) + else: + # Could be an error response + if len(self.__buffer): + # Possible error ??? + self._process(callback, error=True) + else: + if len(self.__buffer): + # Possible error ??? + if self.__header.get('len', 0) < 2: + self._process(callback, error=True) + break
+ +
[docs] def buildPacket(self, message): + ''' Creates a ready to send modbus packet + + :param message: The populated request/response to send + ''' + data = message.encode() + packet = struct.pack('>BB', + message.unit_id, + message.function_code) + data + packet += struct.pack(">H", computeCRC(packet)) + return packet
+ +
[docs] def _process(self, callback, error=False): + """ + Process incoming packets irrespective error condition + """ + data = self.getRawFrame() if error else self.getFrame() + result = self.decoder.decode(data) + if result is None: + raise ModbusIOException("Unable to decode request") + self.populateResult(result) + self.advanceFrame() + callback(result) # defer or push to a thread?
+ +
[docs] def getRawFrame(self): + """ + Returns the complete buffer + """ + return self.__buffer
+ + + +#---------------------------------------------------------------------------# +# Modbus ASCII Message +#---------------------------------------------------------------------------# +
[docs]class ModbusAsciiFramer(IModbusFramer): + ''' + Modbus ASCII Frame Controller:: + + [ Start ][Address ][ Function ][ Data ][ LRC ][ End ] + 1c 2c 2c Nc 2c 2c + + * data can be 0 - 2x252 chars + * end is '\\r\\n' (Carriage return line feed), however the line feed + character can be changed via a special command + * start is ':' + + This framer is used for serial transmission. Unlike the RTU protocol, + the data in this framer is transferred in plain text ascii. + ''' + +
[docs] def __init__(self, decoder): + ''' Initializes a new instance of the framer + + :param decoder: The decoder implementation to use + ''' + self.__buffer = b'' + self.__header = {'lrc':'0000', 'len':0, 'uid':0x00} + self.__hsize = 0x02 + self.__start = b':' + self.__end = b"\r\n" + self.decoder = decoder
+ + #-----------------------------------------------------------------------# + # Private Helper Functions + #-----------------------------------------------------------------------# +
[docs] def checkFrame(self): + ''' Check and decode the next frame + + :returns: True if we successful, False otherwise + ''' + start = self.__buffer.find(self.__start) + if start == -1: return False + if start > 0 : # go ahead and skip old bad data + self.__buffer = self.__buffer[start:] + start = 0 + + end = self.__buffer.find(self.__end) + if (end != -1): + self.__header['len'] = end + self.__header['uid'] = int(self.__buffer[1:3], 16) + self.__header['lrc'] = int(self.__buffer[end - 2:end], 16) + data = a2b_hex(self.__buffer[start + 1:end - 2]) + return checkLRC(data, self.__header['lrc']) + return False
+ +
[docs] def advanceFrame(self): + ''' Skip over the current framed message + This allows us to skip over the current message after we have processed + it or determined that it contains an error. It also has to reset the + current frame header handle + ''' + self.__buffer = self.__buffer[self.__header['len'] + 2:] + self.__header = {'lrc':'0000', 'len':0, 'uid':0x00}
+ +
[docs] def isFrameReady(self): + ''' Check if we should continue decode logic + This is meant to be used in a while loop in the decoding phase to let + the decoder know that there is still data in the buffer. + + :returns: True if ready, False otherwise + ''' + return len(self.__buffer) > 1
+ +
[docs] def addToFrame(self, message): + ''' Add the next message to the frame buffer + This should be used before the decoding while loop to add the received + data to the buffer handle. + + :param message: The most recent packet + ''' + self.__buffer += message
+ +
[docs] def getFrame(self): + ''' Get the next frame from the buffer + + :returns: The frame data or '' + ''' + start = self.__hsize + 1 + end = self.__header['len'] - 2 + buffer = self.__buffer[start:end] + if end > 0: return a2b_hex(buffer) + return b''
+ +
[docs] def populateResult(self, result): + ''' Populates the modbus result header + + The serial packets do not have any header information + that is copied. + + :param result: The response packet + ''' + result.unit_id = self.__header['uid']
+ + #-----------------------------------------------------------------------# + # Public Member Functions + #-----------------------------------------------------------------------# +
[docs] def processIncomingPacket(self, data, callback): + ''' The new packet processing pattern + + This takes in a new request packet, adds it to the current + packet stream, and performs framing on it. That is, checks + for complete messages, and once found, will process all that + exist. This handles the case when we read N + 1 or 1 / N + messages at a time instead of 1. + + The processed and decoded messages are pushed to the callback + function to process and send. + + :param data: The new packet data + :param callback: The function to send results to + ''' + self.addToFrame(data) + while self.isFrameReady(): + if self.checkFrame(): + result = self.decoder.decode(self.getFrame()) + if result is None: + raise ModbusIOException("Unable to decode response") + self.populateResult(result) + self.advanceFrame() + callback(result) # defer this + else: break
+ +
[docs] def buildPacket(self, message): + ''' Creates a ready to send modbus packet + Built off of a modbus request/response + + :param message: The request/response to send + :return: The encoded packet + ''' + encoded = message.encode() + buffer = struct.pack('>BB', message.unit_id, message.function_code) + checksum = computeLRC(encoded + buffer) + + packet = bytearray() + params = (message.unit_id, message.function_code) + packet.extend(self.__start) + packet.extend(('%02x%02x' % params).encode()) + packet.extend(b2a_hex(encoded)) + packet.extend(('%02x' % checksum).encode()) + packet.extend(self.__end) + return bytes(packet).upper()
+ + +#---------------------------------------------------------------------------# +# Modbus Binary Message +#---------------------------------------------------------------------------# +
[docs]class ModbusBinaryFramer(IModbusFramer): + ''' + Modbus Binary Frame Controller:: + + [ Start ][Address ][ Function ][ Data ][ CRC ][ End ] + 1b 1b 1b Nb 2b 1b + + * data can be 0 - 2x252 chars + * end is '}' + * start is '{' + + The idea here is that we implement the RTU protocol, however, + instead of using timing for message delimiting, we use start + and end of message characters (in this case { and }). Basically, + this is a binary framer. + + The only case we have to watch out for is when a message contains + the { or } characters. If we encounter these characters, we + simply duplicate them. Hopefully we will not encounter those + characters that often and will save a little bit of bandwitch + without a real-time system. + + Protocol defined by jamod.sourceforge.net. + ''' + +
[docs] def __init__(self, decoder): + ''' Initializes a new instance of the framer + + :param decoder: The decoder implementation to use + ''' + self.__buffer = b'' + self.__header = {'crc':0x0000, 'len':0, 'uid':0x00} + self.__hsize = 0x02 + self.__start = b'\x7b' # { + self.__end = b'\x7d' # } + self.__repeat = [b'}'[0], b'{'[0]] # python3 hack + self.decoder = decoder
+ + #-----------------------------------------------------------------------# + # Private Helper Functions + #-----------------------------------------------------------------------# +
[docs] def checkFrame(self): + ''' Check and decode the next frame + + :returns: True if we are successful, False otherwise + ''' + start = self.__buffer.find(self.__start) + if start == -1: return False + if start > 0 : # go ahead and skip old bad data + self.__buffer = self.__buffer[start:] + + end = self.__buffer.find(self.__end) + if (end != -1): + self.__header['len'] = end + self.__header['uid'] = struct.unpack('>B', self.__buffer[1:2]) + self.__header['crc'] = struct.unpack('>H', self.__buffer[end - 2:end])[0] + data = self.__buffer[start + 1:end - 2] + return checkCRC(data, self.__header['crc']) + return False
+ +
[docs] def advanceFrame(self): + ''' Skip over the current framed message + This allows us to skip over the current message after we have processed + it or determined that it contains an error. It also has to reset the + current frame header handle + ''' + self.__buffer = self.__buffer[self.__header['len'] + 2:] + self.__header = {'crc':0x0000, 'len':0, 'uid':0x00}
+ +
[docs] def isFrameReady(self): + ''' Check if we should continue decode logic + This is meant to be used in a while loop in the decoding phase to let + the decoder know that there is still data in the buffer. + + :returns: True if ready, False otherwise + ''' + return len(self.__buffer) > 1
+ +
[docs] def addToFrame(self, message): + ''' Add the next message to the frame buffer + This should be used before the decoding while loop to add the received + data to the buffer handle. + + :param message: The most recent packet + ''' + self.__buffer += message
+ +
[docs] def getFrame(self): + ''' Get the next frame from the buffer + + :returns: The frame data or '' + ''' + start = self.__hsize + 1 + end = self.__header['len'] - 2 + buffer = self.__buffer[start:end] + if end > 0: return buffer + return b''
+ +
[docs] def populateResult(self, result): + ''' Populates the modbus result header + + The serial packets do not have any header information + that is copied. + + :param result: The response packet + ''' + result.unit_id = self.__header['uid']
+ + #-----------------------------------------------------------------------# + # Public Member Functions + #-----------------------------------------------------------------------# +
[docs] def processIncomingPacket(self, data, callback): + ''' The new packet processing pattern + + This takes in a new request packet, adds it to the current + packet stream, and performs framing on it. That is, checks + for complete messages, and once found, will process all that + exist. This handles the case when we read N + 1 or 1 / N + messages at a time instead of 1. + + The processed and decoded messages are pushed to the callback + function to process and send. + + :param data: The new packet data + :param callback: The function to send results to + ''' + self.addToFrame(data) + while self.isFrameReady(): + if self.checkFrame(): + result = self.decoder.decode(self.getFrame()) + if result is None: + raise ModbusIOException("Unable to decode response") + self.populateResult(result) + self.advanceFrame() + callback(result) # defer or push to a thread? + else: break
+ +
[docs] def buildPacket(self, message): + ''' Creates a ready to send modbus packet + + :param message: The request/response to send + :returns: The encoded packet + ''' + data = self._preflight(message.encode()) + packet = struct.pack('>BB', + message.unit_id, + message.function_code) + data + packet += struct.pack(">H", computeCRC(packet)) + packet = self.__start + packet + self.__end + return packet
+ +
[docs] def _preflight(self, data): + ''' Preflight buffer test + + This basically scans the buffer for start and end + tags and if found, escapes them. + + :param data: The message to escape + :returns: the escaped packet + ''' + array = bytearray() + for d in data: + if d in self.__repeat: + array.append(d) + array.append(d) + return bytes(array)
+ +#---------------------------------------------------------------------------# +# Exported symbols +#---------------------------------------------------------------------------# +__all__ = [ + "FifoTransactionManager", + "DictTransactionManager", + "ModbusSocketFramer", "ModbusRtuFramer", + "ModbusAsciiFramer", "ModbusBinaryFramer", +] +
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/doc/sphinx/html/_modules/pymodbus/utilities.html b/doc/sphinx/html/_modules/pymodbus/utilities.html new file mode 100644 index 000000000..a81af94bf --- /dev/null +++ b/doc/sphinx/html/_modules/pymodbus/utilities.html @@ -0,0 +1,290 @@ + + + + + + + + pymodbus.utilities — pymodbus 1.3.0.rc2 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for pymodbus.utilities

+'''
+Modbus Utilities
+-----------------
+
+A collection of utilities for packing data, unpacking
+data computing checksums, and decode checksums.
+'''
+from pymodbus.compat import int2byte, byte2int
+
+
+#---------------------------------------------------------------------------#
+# Helpers
+#---------------------------------------------------------------------------#
+
[docs]def default(value): + ''' + Given a python object, return the default value + of that object. + + :param value: The value to get the default of + :returns: The default value + ''' + return type(value)()
+ + +
[docs]def dict_property(store, index): + ''' Helper to create class properties from a dictionary. + Basically this allows you to remove a lot of possible + boilerplate code. + + :param store: The store store to pull from + :param index: The index into the store to close over + :returns: An initialized property set + ''' + if hasattr(store, '__call__'): + getter = lambda self: store(self)[index] + setter = lambda self, value: store(self).__setitem__(index, value) + elif isinstance(store, str): + getter = lambda self: self.__getattribute__(store)[index] + setter = lambda self, value: self.__getattribute__(store).__setitem__( + index, value) + else: + getter = lambda self: store[index] + setter = lambda self, value: store.__setitem__(index, value) + + return property(getter, setter)
+ + +#---------------------------------------------------------------------------# +# Bit packing functions +#---------------------------------------------------------------------------# +
[docs]def pack_bitstring(bits): + ''' Creates a string out of an array of bits + + :param bits: A bit array + + example:: + + bits = [False, True, False, True] + result = pack_bitstring(bits) + ''' + ret = b'' + i = packed = 0 + for bit in bits: + if bit: packed += 128 + i += 1 + if i == 8: + ret += int2byte(packed) + i = packed = 0 + else: packed >>= 1 + if i > 0 and i < 8: + packed >>= (7 - i) + ret += int2byte(packed) + return ret
+ + +
[docs]def unpack_bitstring(string): + ''' Creates bit array out of a string + + :param string: The modbus data packet to decode + + example:: + + bytes = 'bytes to decode' + result = unpack_bitstring(bytes) + ''' + byte_count = len(string) + bits = [] + for byte in range(byte_count): + value = byte2int(string[byte]) + for _ in range(8): + bits.append((value & 1) == 1) + value >>= 1 + return bits
+ + +#---------------------------------------------------------------------------# +# Error Detection Functions +#---------------------------------------------------------------------------# +
[docs]def __generate_crc16_table(): + ''' Generates a crc16 lookup table + + .. note:: This will only be generated once + ''' + result = [] + for byte in range(256): + crc = 0x0000 + for _ in range(8): + if (byte ^ crc) & 0x0001: + crc = (crc >> 1) ^ 0xa001 + else: crc >>= 1 + byte >>= 1 + result.append(crc) + return result
+ +__crc16_table = __generate_crc16_table() + + +
[docs]def computeCRC(data): + ''' Computes a crc16 on the passed in string. For modbus, + this is only used on the binary serial protocols (in this + case RTU). + + The difference between modbus's crc16 and a normal crc16 + is that modbus starts the crc value out at 0xffff. + + :param data: The data to create a crc16 of + :returns: The calculated CRC + ''' + crc = 0xffff + for a in data: + idx = __crc16_table[(crc ^ byte2int(a)) & 0xff]; + crc = ((crc >> 8) & 0xff) ^ idx + swapped = ((crc << 8) & 0xff00) | ((crc >> 8) & 0x00ff) + return swapped
+ + +
[docs]def checkCRC(data, check): + ''' Checks if the data matches the passed in CRC + + :param data: The data to create a crc16 of + :param check: The CRC to validate + :returns: True if matched, False otherwise + ''' + return computeCRC(data) == check
+ + +
[docs]def computeLRC(data): + ''' Used to compute the longitudinal redundancy check + against a string. This is only used on the serial ASCII + modbus protocol. A full description of this implementation + can be found in appendex B of the serial line modbus description. + + :param data: The data to apply a lrc to + :returns: The calculated LRC + + ''' + lrc = sum(byte2int(a) for a in data) & 0xff + lrc = (lrc ^ 0xff) + 1 + return lrc & 0xff
+ + +
[docs]def checkLRC(data, check): + ''' Checks if the passed in data matches the LRC + + :param data: The data to calculate + :param check: The LRC to validate + :returns: True if matched, False otherwise + ''' + return computeLRC(data) == check
+ + +
[docs]def rtuFrameSize(data, byte_count_pos): + ''' Calculates the size of the frame based on the byte count. + + :param data: The buffer containing the frame. + :param byte_count_pos: The index of the byte count in the buffer. + :returns: The size of the frame. + + The structure of frames with a byte count field is always the + same: + + - first, there are some header fields + - then the byte count field + - then as many data bytes as indicated by the byte count, + - finally the CRC (two bytes). + + To calculate the frame size, it is therefore sufficient to extract + the contents of the byte count field, add the position of this + field, and finally increment the sum by three (one byte for the + byte count field, two for the CRC). + ''' + return byte2int(data[byte_count_pos]) + byte_count_pos + 3
+ +#---------------------------------------------------------------------------# +# Exported symbols +#---------------------------------------------------------------------------# +__all__ = [ + 'pack_bitstring', 'unpack_bitstring', 'default', + 'computeCRC', 'checkCRC', 'computeLRC', 'checkLRC', 'rtuFrameSize' +] +
+ +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/doc/sphinx/html/_sources/examples/asynchronous-client.rst.txt b/doc/sphinx/html/_sources/examples/asynchronous-client.rst.txt new file mode 100644 index 000000000..7f5b7d3c3 --- /dev/null +++ b/doc/sphinx/html/_sources/examples/asynchronous-client.rst.txt @@ -0,0 +1,16 @@ +================================================== +Asynchronous Client Example +================================================== + +The asynchronous client functions in the same way as the synchronous +client, however, the asynchronous client uses twisted to return deferreds +for the response result. Just like the synchronous version, it works against +TCP, UDP, serial ASCII, and serial RTU devices. + +Below an asynchronous tcp client is demonstrated running against a +reference server. If you do not have a device to test with, feel free +to run a pymodbus server instance or start the reference tester in +the tools directory. + +.. literalinclude:: ../../../examples/common/asynchronous-client.py + diff --git a/doc/sphinx/html/_sources/examples/asynchronous-processor.rst.txt b/doc/sphinx/html/_sources/examples/asynchronous-processor.rst.txt new file mode 100644 index 000000000..4afd8504e --- /dev/null +++ b/doc/sphinx/html/_sources/examples/asynchronous-processor.rst.txt @@ -0,0 +1,15 @@ +================================================== +Asynchronous Processor Example +================================================== + +Below is a simplified asynchronous client skeleton that was +submitted by a user of the library. It can be used as a guide +for implementing more complex pollers or state machines. + +Feel free to test it against whatever device you currently have +available. If you do not have a device to test with, feel free +to run a pymodbus server instance or start the reference tester in +the tools directory. + +.. literalinclude:: ../../../examples/common/asynchronous-processor.py + diff --git a/doc/sphinx/html/_sources/examples/asynchronous-server.rst.txt b/doc/sphinx/html/_sources/examples/asynchronous-server.rst.txt new file mode 100644 index 000000000..14aeef298 --- /dev/null +++ b/doc/sphinx/html/_sources/examples/asynchronous-server.rst.txt @@ -0,0 +1,6 @@ +================================================== +Asynchronous Server Example +================================================== + +.. literalinclude:: ../../../examples/common/asynchronous-server.py + diff --git a/doc/sphinx/html/_sources/examples/bcd-payload.rst.txt b/doc/sphinx/html/_sources/examples/bcd-payload.rst.txt new file mode 100644 index 000000000..a95c5e7a6 --- /dev/null +++ b/doc/sphinx/html/_sources/examples/bcd-payload.rst.txt @@ -0,0 +1,6 @@ +================================================== +Binary Coded Decimal Example +================================================== + +.. literalinclude:: ../../../examples/contrib/bcd-payload.py + diff --git a/doc/sphinx/html/_sources/examples/bottle-frontend.rst.txt b/doc/sphinx/html/_sources/examples/bottle-frontend.rst.txt new file mode 100644 index 000000000..95507c5a8 --- /dev/null +++ b/doc/sphinx/html/_sources/examples/bottle-frontend.rst.txt @@ -0,0 +1,22 @@ +================================================== +Bottle Web Frontend Example +================================================== + +-------------------------------------------------- +Summary +-------------------------------------------------- + +This is a simple example of adding a live REST api +on top of a running pymodbus server. This uses the +bottle microframework to achieve this. + +The example can be hosted under twisted as well as +the bottle internal server and can furthermore be +run behind gunicorn, cherrypi, etc wsgi containers. + +-------------------------------------------------- +Main Program +-------------------------------------------------- + +.. literalinclude:: ../../../examples/gui/bottle/frontend.py + diff --git a/doc/sphinx/html/_sources/examples/callback-server.rst.txt b/doc/sphinx/html/_sources/examples/callback-server.rst.txt new file mode 100644 index 000000000..e57475216 --- /dev/null +++ b/doc/sphinx/html/_sources/examples/callback-server.rst.txt @@ -0,0 +1,6 @@ +================================================== +Callback Server Example +================================================== + +.. literalinclude:: ../../../examples/common/callback-server.py + diff --git a/doc/sphinx/html/_sources/examples/changing-framers.rst.txt b/doc/sphinx/html/_sources/examples/changing-framers.rst.txt new file mode 100644 index 000000000..3679aa43f --- /dev/null +++ b/doc/sphinx/html/_sources/examples/changing-framers.rst.txt @@ -0,0 +1,6 @@ +================================================== +Changing Default Framers +================================================== + +.. literalinclude:: ../../../examples/common/changing-framers.py + diff --git a/doc/sphinx/html/_sources/examples/concurrent-client.rst.txt b/doc/sphinx/html/_sources/examples/concurrent-client.rst.txt new file mode 100644 index 000000000..1a3799ac9 --- /dev/null +++ b/doc/sphinx/html/_sources/examples/concurrent-client.rst.txt @@ -0,0 +1,6 @@ +================================================== +Modbus Concurrent Client Example +================================================== + +.. literalinclude:: ../../../examples/contrib/concurrent-client.py + diff --git a/doc/sphinx/html/_sources/examples/custom-datablock.rst.txt b/doc/sphinx/html/_sources/examples/custom-datablock.rst.txt new file mode 100644 index 000000000..7139c0f08 --- /dev/null +++ b/doc/sphinx/html/_sources/examples/custom-datablock.rst.txt @@ -0,0 +1,6 @@ +================================================== +Custom Datablock Example +================================================== + +.. literalinclude:: ../../../examples/common/custom-datablock.py + diff --git a/doc/sphinx/html/_sources/examples/custom-message.rst.txt b/doc/sphinx/html/_sources/examples/custom-message.rst.txt new file mode 100644 index 000000000..2ced10cbc --- /dev/null +++ b/doc/sphinx/html/_sources/examples/custom-message.rst.txt @@ -0,0 +1,6 @@ +================================================== +Custom Message Example +================================================== + +.. literalinclude:: ../../../examples/common/custom-message.py + diff --git a/doc/sphinx/html/_sources/examples/database-datastore.rst.txt b/doc/sphinx/html/_sources/examples/database-datastore.rst.txt new file mode 100644 index 000000000..9186be812 --- /dev/null +++ b/doc/sphinx/html/_sources/examples/database-datastore.rst.txt @@ -0,0 +1,6 @@ +================================================== +Database Datastore Example +================================================== + +.. literalinclude:: ../../../examples/contrib/database-datastore.py + diff --git a/doc/sphinx/html/_sources/examples/gtk-frontend.rst.txt b/doc/sphinx/html/_sources/examples/gtk-frontend.rst.txt new file mode 100644 index 000000000..3efc72644 --- /dev/null +++ b/doc/sphinx/html/_sources/examples/gtk-frontend.rst.txt @@ -0,0 +1,26 @@ +================================================== +Glade/GTK Frontend Example +================================================== + +Main Program +-------------------------------------------------- + +This is an example simulator that is written using the pygtk +bindings. Although it currently does not have a frontend for +modifying the context values, it does allow one to expose N +virtual modbus devices to a network which is useful for testing +data center monitoring tools. + +.. note:: The virtual networking code will only work on linux + +.. literalinclude:: ../../../examples/gui/gtk/simulator.py + :language: python + +Glade Layout File +-------------------------------------------------- + +The following is the glade layout file that is used by this script: + +.. literalinclude:: ../../../examples/gui/gtk/simulator.glade + :language: xml + diff --git a/doc/sphinx/html/_sources/examples/index.rst.txt b/doc/sphinx/html/_sources/examples/index.rst.txt new file mode 100644 index 000000000..a41f01129 --- /dev/null +++ b/doc/sphinx/html/_sources/examples/index.rst.txt @@ -0,0 +1,60 @@ + +Pymodbus Library Examples +==================================== + +*What follows is a collection of examples using the pymodbus +library in various ways* + +Example Library Code +-------------------------------------------------- + +.. toctree:: + :maxdepth: 2 + + asynchronous-client + asynchronous-server + asynchronous-processor + custom-message + custom-datablock + modbus-logging + modbus-payload + modbus-payload-server + synchronous-client + synchronous-client-ext + synchronous-server + performance + updating-server + callback-server + changing-framers + thread-safe-datastore + +Custom Pymodbus Code +-------------------------------------------------- + +.. toctree:: + :maxdepth: 2 + + redis-datastore + database-datastore + bcd-payload + modicon-payload + message-generator + message-parser + serial-forwarder + modbus-scraper + modbus-simulator + concurrent-client + libmodbus-client + remote-server-context + +Example Frontend Code +-------------------------------------------------- + +.. toctree:: + :maxdepth: 2 + + gtk-frontend + tk-frontend + wx-frontend + bottle-frontend + diff --git a/doc/sphinx/html/_sources/examples/libmodbus-client.rst.txt b/doc/sphinx/html/_sources/examples/libmodbus-client.rst.txt new file mode 100644 index 000000000..17ac8e2cf --- /dev/null +++ b/doc/sphinx/html/_sources/examples/libmodbus-client.rst.txt @@ -0,0 +1,6 @@ +================================================== +Libmodbus Client Facade +================================================== + +.. literalinclude:: ../../../examples/contrib/libmodbus-client.py + diff --git a/doc/sphinx/html/_sources/examples/message-generator.rst.txt b/doc/sphinx/html/_sources/examples/message-generator.rst.txt new file mode 100644 index 000000000..605554c45 --- /dev/null +++ b/doc/sphinx/html/_sources/examples/message-generator.rst.txt @@ -0,0 +1,26 @@ +================================================== +Modbus Message Generator Example +================================================== + +This is an example of a utility that will build +examples of modbus messages in all the available +formats in the pymodbus package. + +-------------------------------------------------- +Program Source +-------------------------------------------------- + +.. literalinclude:: ../../../examples/contrib/message-generator.py + +-------------------------------------------------- +Example Request Messages +-------------------------------------------------- + +.. literalinclude:: ../../../examples/contrib/tx-messages + +-------------------------------------------------- +Example Response Messages +-------------------------------------------------- + +.. literalinclude:: ../../../examples/contrib/rx-messages + diff --git a/doc/sphinx/html/_sources/examples/message-parser.rst.txt b/doc/sphinx/html/_sources/examples/message-parser.rst.txt new file mode 100644 index 000000000..0bbaee9fc --- /dev/null +++ b/doc/sphinx/html/_sources/examples/message-parser.rst.txt @@ -0,0 +1,55 @@ +================================================== +Modbus Message Parsing Example +================================================== + +This is an example of a parser to decode raw messages +to a readable description. It will attempt to decode +a message to the request and response version of a +message if possible. Here is an example output:: + + $./message-parser.py -b -m 000112340006ff076d + ================================================================================ + Decoding Message 000112340006ff076d + ================================================================================ + ServerDecoder + -------------------------------------------------------------------------------- + name = ReadExceptionStatusRequest + check = 0x0 + unit_id = 0xff + transaction_id = 0x1 + protocol_id = 0x1234 + documentation = + This function code is used to read the contents of eight Exception Status + outputs in a remote device. The function provides a simple method for + accessing this information, because the Exception Output references are + known (no output reference is needed in the function). + + ClientDecoder + -------------------------------------------------------------------------------- + name = ReadExceptionStatusResponse + check = 0x0 + status = 0x6d + unit_id = 0xff + transaction_id = 0x1 + protocol_id = 0x1234 + documentation = + The normal response contains the status of the eight Exception Status + outputs. The outputs are packed into one data byte, with one bit + per output. The status of the lowest output reference is contained + in the least significant bit of the byte. The contents of the eight + Exception Status outputs are device specific. + +-------------------------------------------------- +Program Source +-------------------------------------------------- + +.. literalinclude:: ../../../examples/contrib/message-parser.py + +-------------------------------------------------- +Example Messages +-------------------------------------------------- + +See the documentation for the message generator +for a collection of messages that can be parsed +by this utility. + diff --git a/doc/sphinx/html/_sources/examples/modbus-logging.rst.txt b/doc/sphinx/html/_sources/examples/modbus-logging.rst.txt new file mode 100644 index 000000000..710e5bd4d --- /dev/null +++ b/doc/sphinx/html/_sources/examples/modbus-logging.rst.txt @@ -0,0 +1,6 @@ +================================================== +Modbus Logging Example +================================================== + +.. literalinclude:: ../../../examples/common/modbus-logging.py + diff --git a/doc/sphinx/html/_sources/examples/modbus-payload-server.rst.txt b/doc/sphinx/html/_sources/examples/modbus-payload-server.rst.txt new file mode 100644 index 000000000..9144f0f53 --- /dev/null +++ b/doc/sphinx/html/_sources/examples/modbus-payload-server.rst.txt @@ -0,0 +1,6 @@ +================================================== +Modbus Payload Server Context Building Example +================================================== + +.. literalinclude:: ../../../examples/common/modbus-payload-server.py + diff --git a/doc/sphinx/html/_sources/examples/modbus-payload.rst.txt b/doc/sphinx/html/_sources/examples/modbus-payload.rst.txt new file mode 100644 index 000000000..79e46dfdb --- /dev/null +++ b/doc/sphinx/html/_sources/examples/modbus-payload.rst.txt @@ -0,0 +1,6 @@ +================================================== +Modbus Payload Building/Decoding Example +================================================== + +.. literalinclude:: ../../../examples/common/modbus-payload.py + diff --git a/doc/sphinx/html/_sources/examples/modbus-scraper.rst.txt b/doc/sphinx/html/_sources/examples/modbus-scraper.rst.txt new file mode 100644 index 000000000..9931c4a62 --- /dev/null +++ b/doc/sphinx/html/_sources/examples/modbus-scraper.rst.txt @@ -0,0 +1,6 @@ +================================================== +Modbus Scraper Example +================================================== + +.. literalinclude:: ../../../examples/contrib/modbus-scraper.py + diff --git a/doc/sphinx/html/_sources/examples/modbus-simulator.rst.txt b/doc/sphinx/html/_sources/examples/modbus-simulator.rst.txt new file mode 100644 index 000000000..5adcee5ee --- /dev/null +++ b/doc/sphinx/html/_sources/examples/modbus-simulator.rst.txt @@ -0,0 +1,5 @@ +================================================== +Modbus Simulator Example +================================================== + +.. literalinclude:: ../../../examples/contrib/modbus-simulator.py diff --git a/doc/sphinx/html/_sources/examples/modicon-payload.rst.txt b/doc/sphinx/html/_sources/examples/modicon-payload.rst.txt new file mode 100644 index 000000000..997a12f50 --- /dev/null +++ b/doc/sphinx/html/_sources/examples/modicon-payload.rst.txt @@ -0,0 +1,6 @@ +================================================== +Modicon Encoded Example +================================================== + +.. literalinclude:: ../../../examples/contrib/modicon-payload.py + diff --git a/doc/sphinx/html/_sources/examples/performance.rst.txt b/doc/sphinx/html/_sources/examples/performance.rst.txt new file mode 100644 index 000000000..93185bdea --- /dev/null +++ b/doc/sphinx/html/_sources/examples/performance.rst.txt @@ -0,0 +1,11 @@ +================================================== +Synchronous Client Performance Check +================================================== + +Below is a quick example of how to test the performance of a tcp modbus +device using the synchronous tcp client. If you do not have a device +to test with, feel free to run a pymodbus server instance or start +the reference tester in the tools directory. + +.. literalinclude:: ../../../examples/common/performance.py + diff --git a/doc/sphinx/html/_sources/examples/redis-datastore.rst.txt b/doc/sphinx/html/_sources/examples/redis-datastore.rst.txt new file mode 100644 index 000000000..bb5554e04 --- /dev/null +++ b/doc/sphinx/html/_sources/examples/redis-datastore.rst.txt @@ -0,0 +1,6 @@ +================================================== +Redis Datastore Example +================================================== + +.. literalinclude:: ../../../examples/contrib/redis-datastore.py + diff --git a/doc/sphinx/html/_sources/examples/remote-server-context.rst.txt b/doc/sphinx/html/_sources/examples/remote-server-context.rst.txt new file mode 100644 index 000000000..2a2ac3c05 --- /dev/null +++ b/doc/sphinx/html/_sources/examples/remote-server-context.rst.txt @@ -0,0 +1,6 @@ +================================================== +Remote Single Server Context +================================================== + +.. literalinclude:: ../../../examples/contrib/remote_server_context.py + diff --git a/doc/sphinx/html/_sources/examples/serial-forwarder.rst.txt b/doc/sphinx/html/_sources/examples/serial-forwarder.rst.txt new file mode 100644 index 000000000..87f6e0a0c --- /dev/null +++ b/doc/sphinx/html/_sources/examples/serial-forwarder.rst.txt @@ -0,0 +1,6 @@ +================================================== +Synchronous Serial Forwarder +================================================== + +.. literalinclude:: ../../../examples/contrib/serial-forwarder.py + diff --git a/doc/sphinx/html/_sources/examples/synchronous-client-ext.rst.txt b/doc/sphinx/html/_sources/examples/synchronous-client-ext.rst.txt new file mode 100644 index 000000000..5012ec8b0 --- /dev/null +++ b/doc/sphinx/html/_sources/examples/synchronous-client-ext.rst.txt @@ -0,0 +1,6 @@ +================================================== +Synchronous Client Extended Example +================================================== + +.. literalinclude:: ../../../examples/common/synchronous-client-ext.py + diff --git a/doc/sphinx/html/_sources/examples/synchronous-client.rst.txt b/doc/sphinx/html/_sources/examples/synchronous-client.rst.txt new file mode 100644 index 000000000..b90563402 --- /dev/null +++ b/doc/sphinx/html/_sources/examples/synchronous-client.rst.txt @@ -0,0 +1,19 @@ +================================================== +Synchronous Client Example +================================================== + +It should be noted that each request will block waiting for the result. If asynchronous +behaviour is required, please use the asynchronous client implementations. +The synchronous client, works against TCP, UDP, serial ASCII, and serial RTU devices. + +The synchronous client exposes the most popular methods of the modbus protocol, +however, if you want to execute other methods against the device, +simple create a request instance and pass it to the execute method. + +Below an synchronous tcp client is demonstrated running against a +reference server. If you do not have a device to test with, feel free +to run a pymodbus server instance or start the reference tester in +the tools directory. + +.. literalinclude:: ../../../examples/common/synchronous-client.py + diff --git a/doc/sphinx/html/_sources/examples/synchronous-server.rst.txt b/doc/sphinx/html/_sources/examples/synchronous-server.rst.txt new file mode 100644 index 000000000..1db715d7f --- /dev/null +++ b/doc/sphinx/html/_sources/examples/synchronous-server.rst.txt @@ -0,0 +1,6 @@ +================================================== +Synchronous Server Example +================================================== + +.. literalinclude:: ../../../examples/common/synchronous-server.py + diff --git a/doc/sphinx/html/_sources/examples/thread-safe-datastore.rst.txt b/doc/sphinx/html/_sources/examples/thread-safe-datastore.rst.txt new file mode 100644 index 000000000..7a965a3f4 --- /dev/null +++ b/doc/sphinx/html/_sources/examples/thread-safe-datastore.rst.txt @@ -0,0 +1,6 @@ +================================================== +Thread Safe Datastore Example +================================================== + +.. literalinclude:: ../../../examples/contrib/thread_safe_datastore.py + diff --git a/doc/sphinx/html/_sources/examples/tk-frontend.rst.txt b/doc/sphinx/html/_sources/examples/tk-frontend.rst.txt new file mode 100644 index 000000000..1849d0abc --- /dev/null +++ b/doc/sphinx/html/_sources/examples/tk-frontend.rst.txt @@ -0,0 +1,17 @@ +================================================== +TK Frontend Example +================================================== + +Main Program +-------------------------------------------------- + +This is an example simulator that is written using the native tk +toolkit. Although it currently does not have a frontend for +modifying the context values, it does allow one to expose N +virtual modbus devices to a network which is useful for testing +data center monitoring tools. + +.. note:: The virtual networking code will only work on linux + +.. literalinclude:: ../../../examples/gui/tk/simulator.py + diff --git a/doc/sphinx/html/_sources/examples/updating-server.rst.txt b/doc/sphinx/html/_sources/examples/updating-server.rst.txt new file mode 100644 index 000000000..07d1baac0 --- /dev/null +++ b/doc/sphinx/html/_sources/examples/updating-server.rst.txt @@ -0,0 +1,6 @@ +================================================== +Updating Server Example +================================================== + +.. literalinclude:: ../../../examples/common/updating-server.py + diff --git a/doc/sphinx/html/_sources/examples/wx-frontend.rst.txt b/doc/sphinx/html/_sources/examples/wx-frontend.rst.txt new file mode 100644 index 000000000..5d68142c0 --- /dev/null +++ b/doc/sphinx/html/_sources/examples/wx-frontend.rst.txt @@ -0,0 +1,17 @@ +================================================== +WX Frontend Example +================================================== + +Main Program +-------------------------------------------------- + +This is an example simulator that is written using the python wx +bindings. Although it currently does not have a frontend for +modifying the context values, it does allow one to expose N +virtual modbus devices to a network which is useful for testing +data center monitoring tools. + +.. note:: The virtual networking code will only work on linux + +.. literalinclude:: ../../../examples/gui/wx/simulator.py + diff --git a/doc/sphinx/html/_sources/index.rst.txt b/doc/sphinx/html/_sources/index.rst.txt new file mode 100644 index 000000000..7017018f2 --- /dev/null +++ b/doc/sphinx/html/_sources/index.rst.txt @@ -0,0 +1,23 @@ +.. PyModbus documentation master file, created by + sphinx-quickstart on Tue Apr 14 19:11:16 2009. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to PyModbus's documentation! +==================================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + examples/index.rst + library/index.rst + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/doc/sphinx/html/_sources/library/async-client.rst.txt b/doc/sphinx/html/_sources/library/async-client.rst.txt new file mode 100644 index 000000000..c325706dc --- /dev/null +++ b/doc/sphinx/html/_sources/library/async-client.rst.txt @@ -0,0 +1,19 @@ +:mod:`client.async` --- Twisted Async Modbus Client +==================================================== + +.. module:: client.async + :synopsis: Twisted Asynchronous Modbus Client + +.. moduleauthor:: Galen Collins +.. sectionauthor:: Galen Collins + +API Documentation +------------------ + +.. automodule:: pymodbus.client.async + +.. autoclass:: ModbusClientProtocol + :members: + +.. autoclass:: ModbusClientFactory + :members: diff --git a/doc/sphinx/html/_sources/library/async-server.rst.txt b/doc/sphinx/html/_sources/library/async-server.rst.txt new file mode 100644 index 000000000..8f2dcdb7b --- /dev/null +++ b/doc/sphinx/html/_sources/library/async-server.rst.txt @@ -0,0 +1,29 @@ +:mod:`server.async` --- Twisted Asynchronous Modbus Server +============================================================ + +.. module:: server.async + :synopsis: Twisted Asynchronous Modbus Server + +.. moduleauthor:: Galen Collins +.. sectionauthor:: Galen Collins + +API Documentation +------------------- + +.. automodule:: pymodbus.server.async + +.. autoclass:: ModbusTcpProtocol + :members: + +.. autoclass:: ModbusUdpProtocol + :members: + +.. autoclass:: ModbusServerFactory + :members: + +.. autofunction:: StartTcpServer + +.. autofunction:: StartUdpServer + +.. autofunction:: StartSerialServer + diff --git a/doc/sphinx/html/_sources/library/bit-read-message.rst.txt b/doc/sphinx/html/_sources/library/bit-read-message.rst.txt new file mode 100644 index 000000000..9ea4b0cfa --- /dev/null +++ b/doc/sphinx/html/_sources/library/bit-read-message.rst.txt @@ -0,0 +1,32 @@ +:mod:`bit_read_message` --- Bit Read Modbus Messages +============================================================ + +.. module:: bit_read_message + :synopsis: Bit Read Modbus Messages + +.. moduleauthor:: Galen Collins +.. sectionauthor:: Galen Collins + +API Documentation +------------------- + +.. automodule:: pymodbus.bit_read_message + +.. autoclass:: ReadBitsRequestBase + :members: + +.. autoclass:: ReadBitsResponseBase + :members: + +.. autoclass:: ReadCoilsRequest + :members: + +.. autoclass:: ReadCoilsResponse + :members: + +.. autoclass:: ReadDiscreteInputsRequest + :members: + +.. autoclass:: ReadDiscreteInputsResponse + :members: + diff --git a/doc/sphinx/html/_sources/library/bit-write-message.rst.txt b/doc/sphinx/html/_sources/library/bit-write-message.rst.txt new file mode 100644 index 000000000..6a73f9a46 --- /dev/null +++ b/doc/sphinx/html/_sources/library/bit-write-message.rst.txt @@ -0,0 +1,26 @@ +:mod:`bit_write_message` --- Bit Write Modbus Messages +============================================================ + +.. module:: bit_write_message + :synopsis: Bit Write Modbus Messages + +.. moduleauthor:: Galen Collins +.. sectionauthor:: Galen Collins + +API Documentation +------------------- + +.. automodule:: pymodbus.bit_write_message + +.. autoclass:: WriteSingleCoilRequest + :members: + +.. autoclass:: WriteSingleCoilResponse + :members: + +.. autoclass:: WriteMultipleCoilsRequest + :members: + +.. autoclass:: WriteMultipleCoilsResponse + :members: + diff --git a/doc/sphinx/html/_sources/library/client-common.rst.txt b/doc/sphinx/html/_sources/library/client-common.rst.txt new file mode 100644 index 000000000..5cca7ada3 --- /dev/null +++ b/doc/sphinx/html/_sources/library/client-common.rst.txt @@ -0,0 +1,16 @@ +:mod:`client.common` --- Twisted Async Modbus Client +==================================================== + +.. module:: client.common + :synopsis: Modbus common client clode + +.. moduleauthor:: Galen Collins +.. sectionauthor:: Galen Collins + +API Documentation +------------------ + +.. automodule:: pymodbus.client.common + +.. autoclass:: ModbusClientMixin + :members: diff --git a/doc/sphinx/html/_sources/library/constants.rst.txt b/doc/sphinx/html/_sources/library/constants.rst.txt new file mode 100644 index 000000000..a99e207c6 --- /dev/null +++ b/doc/sphinx/html/_sources/library/constants.rst.txt @@ -0,0 +1,31 @@ +:mod:`constants` --- Modbus Default Values +============================================================ + +.. module:: constants + :synopsis: Modbus Default Values + +.. moduleauthor:: Galen Collins +.. sectionauthor:: Galen Collins + +API Documentation +------------------- + +.. automodule:: pymodbus.constants + +.. autoclass:: Defaults + :members: + +.. autoclass:: ModbusStatus + :members: + +.. autoclass:: Endian + :members: + +.. autoclass:: ModbusPlusOperation + :members: + +.. autoclass:: DeviceInformation + :members: + +.. autoclass:: MoreData + :members: diff --git a/doc/sphinx/html/_sources/library/datastore/context.rst.txt b/doc/sphinx/html/_sources/library/datastore/context.rst.txt new file mode 100644 index 000000000..16a62e2e1 --- /dev/null +++ b/doc/sphinx/html/_sources/library/datastore/context.rst.txt @@ -0,0 +1,20 @@ +:mod:`context` --- Modbus Server Contexts +============================================================ + +.. module:: context + :synopsis: Modbus Server Contexts + +.. moduleauthor:: Galen Collins +.. sectionauthor:: Galen Collins + +API Documentation +------------------- + +.. automodule:: pymodbus.datastore.context + +.. autoclass:: ModbusSlaveContext + :members: + +.. autoclass:: ModbusServerContext + :members: + diff --git a/doc/sphinx/html/_sources/library/datastore/index.rst.txt b/doc/sphinx/html/_sources/library/datastore/index.rst.txt new file mode 100644 index 000000000..6ea14b24e --- /dev/null +++ b/doc/sphinx/html/_sources/library/datastore/index.rst.txt @@ -0,0 +1,13 @@ + +Server Datastores and Contexts +==================================== + +*The following are the API documentation strings taken +from the sourcecode* + +.. toctree:: + :maxdepth: 2 + + store.rst + context.rst + remote.rst diff --git a/doc/sphinx/html/_sources/library/datastore/remote.rst.txt b/doc/sphinx/html/_sources/library/datastore/remote.rst.txt new file mode 100644 index 000000000..551f0b17b --- /dev/null +++ b/doc/sphinx/html/_sources/library/datastore/remote.rst.txt @@ -0,0 +1,16 @@ +:mod:`remote` --- Remote Slave Context +============================================================ + +.. module:: remote + :synopsis: Remote Slave Context + +.. moduleauthor:: Galen Collins +.. sectionauthor:: Galen Collins + +API Documentation +------------------- + +.. automodule:: pymodbus.datastore.remote + +.. autoclass:: RemoteSlaveContext + :members: diff --git a/doc/sphinx/html/_sources/library/datastore/store.rst.txt b/doc/sphinx/html/_sources/library/datastore/store.rst.txt new file mode 100644 index 000000000..1a5ff2d67 --- /dev/null +++ b/doc/sphinx/html/_sources/library/datastore/store.rst.txt @@ -0,0 +1,23 @@ +:mod:`store` --- Datastore for Modbus Server Context +============================================================ + +.. module:: store + :synopsis: Datastore for Modbus Server Context + +.. moduleauthor:: Galen Collins +.. sectionauthor:: Galen Collins + +API Documentation +------------------- + +.. automodule:: pymodbus.datastore.store + +.. autoclass:: BaseModbusDataBlock + :members: + +.. autoclass:: ModbusSequentialDataBlock + :members: + +.. autoclass:: ModbusSparseDataBlock + :members: + diff --git a/doc/sphinx/html/_sources/library/device.rst.txt b/doc/sphinx/html/_sources/library/device.rst.txt new file mode 100644 index 000000000..59b996de4 --- /dev/null +++ b/doc/sphinx/html/_sources/library/device.rst.txt @@ -0,0 +1,28 @@ +:mod:`device` --- Modbus Device Representation +============================================================ + +.. module:: device + :synopsis: Modbus Device Representation + +.. moduleauthor:: Galen Collins +.. sectionauthor:: Galen Collins + +API Documentation +------------------- + +.. automodule:: pymodbus.device + +.. autoclass:: ModbusAccessControl + :members: + +.. autoclass:: ModbusPlusStatistics + :members: + +.. autoclass:: ModbusDeviceIdentification + :members: + +.. autoclass:: DeviceInformationFactory + :members: + +.. autoclass:: ModbusControlBlock + :members: diff --git a/doc/sphinx/html/_sources/library/diag-message.rst.txt b/doc/sphinx/html/_sources/library/diag-message.rst.txt new file mode 100644 index 000000000..5500a1cbc --- /dev/null +++ b/doc/sphinx/html/_sources/library/diag-message.rst.txt @@ -0,0 +1,128 @@ +:mod:`diag_message` --- Diagnostic Modbus Messages +============================================================ + +.. module:: diag_message + :synopsis: Diagnostic Modbus Messages + +.. moduleauthor:: Galen Collins +.. sectionauthor:: Galen Collins + +API Documentation +------------------- + +.. automodule:: pymodbus.diag_message + +.. autoclass:: DiagnosticStatusRequest + :members: + +.. autoclass:: DiagnosticStatusResponse + :members: + +.. autoclass:: DiagnosticStatusSimpleRequest + :members: + +.. autoclass:: DiagnosticStatusSimpleResponse + :members: + +.. autoclass:: ReturnQueryDataRequest + :members: + +.. autoclass:: ReturnQueryDataResponse + :members: + +.. autoclass:: RestartCommunicationsOptionRequest + :members: + +.. autoclass:: RestartCommunicationsOptionResponse + :members: + +.. autoclass:: ReturnDiagnosticRegisterRequest + :members: + +.. autoclass:: ReturnDiagnosticRegisterResponse + :members: + +.. autoclass:: ChangeAsciiInputDelimiterRequest + :members: + +.. autoclass:: ChangeAsciiInputDelimiterResponse + :members: + +.. autoclass:: ForceListenOnlyModeRequest + :members: + +.. autoclass:: ForceListenOnlyModeResponse + :members: + +.. autoclass:: ClearCountersRequest + :members: + +.. autoclass:: ClearCountersResponse + :members: + +.. autoclass:: ReturnBusMessageCountRequest + :members: + +.. autoclass:: ReturnBusMessageCountResponse + :members: + +.. autoclass:: ReturnBusCommunicationErrorCountRequest + :members: + +.. autoclass:: ReturnBusCommunicationErrorCountResponse + :members: + +.. autoclass:: ReturnBusExceptionErrorCountRequest + :members: + +.. autoclass:: ReturnBusExceptionErrorCountResponse + :members: + +.. autoclass:: ReturnSlaveMessageCountRequest + :members: + +.. autoclass:: ReturnSlaveMessageCountResponse + :members: + +.. autoclass:: ReturnSlaveNoResponseCountRequest + :members: + +.. autoclass:: ReturnSlaveNoReponseCountResponse + :members: + +.. autoclass:: ReturnSlaveNAKCountRequest + :members: + +.. autoclass:: ReturnSlaveNAKCountResponse + :members: + +.. autoclass:: ReturnSlaveBusyCountRequest + :members: + +.. autoclass:: ReturnSlaveBusyCountResponse + :members: + +.. autoclass:: ReturnSlaveBusCharacterOverrunCountRequest + :members: + +.. autoclass:: ReturnSlaveBusCharacterOverrunCountResponse + :members: + +.. autoclass:: ReturnIopOverrunCountRequest + :members: + +.. autoclass:: ReturnIopOverrunCountResponse + :members: + +.. autoclass:: ClearOverrunCountRequest + :members: + +.. autoclass:: ClearOverrunCountResponse + :members: + +.. autoclass:: GetClearModbusPlusRequest + :members: + +.. autoclass:: GetClearModbusPlusResponse + :members: + diff --git a/doc/sphinx/html/_sources/library/events.rst.txt b/doc/sphinx/html/_sources/library/events.rst.txt new file mode 100644 index 000000000..5bc4d466e --- /dev/null +++ b/doc/sphinx/html/_sources/library/events.rst.txt @@ -0,0 +1,28 @@ +:mod:`events` --- Events Used in PyModbus +============================================================ + +.. module:: events + :synopsis: Events Used in PyModbus + +.. moduleauthor:: Galen Collins +.. sectionauthor:: Galen Collins + +API Documentation +------------------- + +.. automodule:: pymodbus.events + +.. autoclass:: ModbusEvent + :members: + +.. autoclass:: RemoteReceiveEvent + :members: + +.. autoclass:: RemoteSendEvent + :members: + +.. autoclass:: EnteredListenModeEvent + :members: + +.. autoclass:: CommunicationRestartEvent + :members: diff --git a/doc/sphinx/html/_sources/library/exceptions.rst.txt b/doc/sphinx/html/_sources/library/exceptions.rst.txt new file mode 100644 index 000000000..03c05ea71 --- /dev/null +++ b/doc/sphinx/html/_sources/library/exceptions.rst.txt @@ -0,0 +1,26 @@ +:mod:`exceptions` --- Exceptions Used in PyModbus +============================================================ + +.. module:: exceptions + :synopsis: Exceptions Used in PyModbus + +.. moduleauthor:: Galen Collins +.. sectionauthor:: Galen Collins + +API Documentation +------------------- + +.. automodule:: pymodbus.exceptions + +.. autoclass:: ModbusException + :members: + +.. autoclass:: ModbusIOException + :members: + +.. autoclass:: ParameterException + :members: + +.. autoclass:: NotImplementedException + :members: + diff --git a/doc/sphinx/html/_sources/library/factory.rst.txt b/doc/sphinx/html/_sources/library/factory.rst.txt new file mode 100644 index 000000000..5f0fcc83a --- /dev/null +++ b/doc/sphinx/html/_sources/library/factory.rst.txt @@ -0,0 +1,19 @@ +:mod:`factory` --- Request/Response Decoders +============================================================ + +.. module:: factory + :synopsis: Request/Response Decoders + +.. moduleauthor:: Galen Collins +.. sectionauthor:: Galen Collins + +API Documentation +------------------- + +.. automodule:: pymodbus.factory + +.. autoclass:: ServerDecoder + :members: + +.. autoclass:: ClientDecoder + :members: diff --git a/doc/sphinx/html/_sources/library/file-message.rst.txt b/doc/sphinx/html/_sources/library/file-message.rst.txt new file mode 100644 index 000000000..e5d0b666d --- /dev/null +++ b/doc/sphinx/html/_sources/library/file-message.rst.txt @@ -0,0 +1,34 @@ +:mod:`file_message` --- File Modbus Messages +============================================================ + +.. module:: file_message + :synopsis: File Modbus Messages + +.. moduleauthor:: Galen Collins +.. sectionauthor:: Galen Collins + +API Documentation +------------------- + +.. automodule:: pymodbus.file_message + +.. autoclass:: FileRecord + :members: + +.. autoclass:: ReadFileRecordRequest + :members: + +.. autoclass:: ReadFileRecordResponse + :members: + +.. autoclass:: WriteFileRecordRequest + :members: + +.. autoclass:: WriteFileRecordResponse + :members: + +.. autoclass:: ReadFifoQueueRequest + :members: + +.. autoclass:: ReadFifoQueueResponse + :members: diff --git a/doc/sphinx/html/_sources/library/index.rst.txt b/doc/sphinx/html/_sources/library/index.rst.txt new file mode 100644 index 000000000..f21e8e4a7 --- /dev/null +++ b/doc/sphinx/html/_sources/library/index.rst.txt @@ -0,0 +1,35 @@ + +Pymodbus Library API Documentation +==================================== + +*The following are the API documentation strings taken +from the sourcecode* + +.. toctree:: + :maxdepth: 2 + + bit-read-message.rst + bit-write-message.rst + client-common.rst + sync-client.rst + async-client.rst + constants.rst + datastore/index.rst + diag-message.rst + device.rst + factory.rst + interfaces.rst + exceptions.rst + other-message.rst + mei-message.rst + file-message.rst + events.rst + payload.rst + pdu.rst + pymodbus.rst + register-read-message.rst + register-write-message.rst + sync-server.rst + async-server.rst + transaction.rst + utilities.rst diff --git a/doc/sphinx/html/_sources/library/interfaces.rst.txt b/doc/sphinx/html/_sources/library/interfaces.rst.txt new file mode 100644 index 000000000..cb10d0c62 --- /dev/null +++ b/doc/sphinx/html/_sources/library/interfaces.rst.txt @@ -0,0 +1,28 @@ +:mod:`interfaces` --- System Interfaces +============================================================ + +.. module:: interfaces + :synopsis: System Interfaces + +.. moduleauthor:: Galen Collins +.. sectionauthor:: Galen Collins + +API Documentation +------------------- + +.. automodule:: pymodbus.interfaces + +.. autoclass:: Singleton + :members: + +.. autoclass:: IModbusDecoder + :members: + +.. autoclass:: IModbusFramer + :members: + +.. autoclass:: IModbusSlaveContext + :members: + +.. autoclass:: IPayloadBuilder + :members: diff --git a/doc/sphinx/html/_sources/library/mei-message.rst.txt b/doc/sphinx/html/_sources/library/mei-message.rst.txt new file mode 100644 index 000000000..d75809445 --- /dev/null +++ b/doc/sphinx/html/_sources/library/mei-message.rst.txt @@ -0,0 +1,19 @@ +:mod:`mei_message` --- MEI Modbus Messages +============================================================ + +.. module:: mei_message + :synopsis: MEI Modbus Messages + +.. moduleauthor:: Galen Collins +.. sectionauthor:: Galen Collins + +API Documentation +------------------- + +.. automodule:: pymodbus.mei_message + +.. autoclass:: ReadDeviceInformationRequest + :members: + +.. autoclass:: ReadDeviceInformationResponse + :members: diff --git a/doc/sphinx/html/_sources/library/other-message.rst.txt b/doc/sphinx/html/_sources/library/other-message.rst.txt new file mode 100644 index 000000000..3baf24888 --- /dev/null +++ b/doc/sphinx/html/_sources/library/other-message.rst.txt @@ -0,0 +1,31 @@ +:mod:`other_message` --- Other Modbus Messages +============================================================ + +.. module:: other_message + :synopsis: Other Modbus Messages + +.. moduleauthor:: Galen Collins +.. sectionauthor:: Galen Collins + +API Documentation +------------------- + +.. automodule:: pymodbus.other_message + +.. autoclass:: ReadExceptionStatusRequest + :members: + +.. autoclass:: ReadExceptionStatusResponse + :members: + +.. autoclass:: GetCommEventCounterRequest + :members: + +.. autoclass:: GetCommEventCounterResponse + :members: + +.. autoclass:: ReportSlaveIdRequest + :members: + +.. autoclass:: ReportSlaveIdResponse + :members: diff --git a/doc/sphinx/html/_sources/library/payload.rst.txt b/doc/sphinx/html/_sources/library/payload.rst.txt new file mode 100644 index 000000000..4083aa2ca --- /dev/null +++ b/doc/sphinx/html/_sources/library/payload.rst.txt @@ -0,0 +1,19 @@ +:mod:`payload` --- Modbus Payload Utilities +============================================================ + +.. module:: payload + :synopsis: Modbus Payload Utilities + +.. moduleauthor:: Galen Collins +.. sectionauthor:: Galen Collins + +API Documentation +------------------- + +.. automodule:: pymodbus.payload + +.. autoclass:: BinaryPayloadBuilder + :members: + +.. autoclass:: BinaryPayloadDecoder + :members: diff --git a/doc/sphinx/html/_sources/library/pdu.rst.txt b/doc/sphinx/html/_sources/library/pdu.rst.txt new file mode 100644 index 000000000..46b8fd456 --- /dev/null +++ b/doc/sphinx/html/_sources/library/pdu.rst.txt @@ -0,0 +1,32 @@ +:mod:`pdu` --- Base Structures +============================================================ + +.. module:: pdu + :synopsis: Base Structures + +.. moduleauthor:: Galen Collins +.. sectionauthor:: Galen Collins + +API Documentation +------------------- + +.. automodule:: pymodbus.pdu + +.. autoclass:: ModbusPDU + :members: + +.. autoclass:: ModbusRequest + :members: + +.. autoclass:: ModbusResponse + :members: + +.. autoclass:: ModbusExceptions + :members: + +.. autoclass:: ExceptionResponse + :members: + +.. autoclass:: IllegalFunctionRequest + :members: + diff --git a/doc/sphinx/html/_sources/library/pymodbus.rst.txt b/doc/sphinx/html/_sources/library/pymodbus.rst.txt new file mode 100644 index 000000000..54719825d --- /dev/null +++ b/doc/sphinx/html/_sources/library/pymodbus.rst.txt @@ -0,0 +1,7 @@ +:mod:`pymodbus` --- Pymodbus Library +============================================================ + +.. moduleauthor:: Galen Collins +.. sectionauthor:: Galen Collins + +.. automodule:: pymodbus diff --git a/doc/sphinx/html/_sources/library/register-read-message.rst.txt b/doc/sphinx/html/_sources/library/register-read-message.rst.txt new file mode 100644 index 000000000..aeb4c3402 --- /dev/null +++ b/doc/sphinx/html/_sources/library/register-read-message.rst.txt @@ -0,0 +1,38 @@ +:mod:`register_read_message` --- Register Read Messages +============================================================ + +.. module:: register_read_message + :synopsis: Register Read Messages + +.. moduleauthor:: Galen Collins +.. sectionauthor:: Galen Collins + +API Documentation +------------------- + +.. automodule:: pymodbus.register_read_message + +.. autoclass:: ReadRegistersRequestBase + :members: + +.. autoclass:: ReadRegistersResponseBase + :members: + +.. autoclass:: ReadHoldingRegistersRequest + :members: + +.. autoclass:: ReadHoldingRegistersResponse + :members: + +.. autoclass:: ReadInputRegistersRequest + :members: + +.. autoclass:: ReadInputRegistersResponse + :members: + +.. autoclass:: ReadWriteMultipleRegistersRequest + :members: + +.. autoclass:: ReadWriteMultipleRegistersResponse + :members: + diff --git a/doc/sphinx/html/_sources/library/register-write-message.rst.txt b/doc/sphinx/html/_sources/library/register-write-message.rst.txt new file mode 100644 index 000000000..71a917646 --- /dev/null +++ b/doc/sphinx/html/_sources/library/register-write-message.rst.txt @@ -0,0 +1,26 @@ +:mod:`register_write_message` --- Register Write Messages +============================================================ + +.. module:: register_write_message + :synopsis: Register Write Messages + +.. moduleauthor:: Galen Collins +.. sectionauthor:: Galen Collins + +API Documentation +------------------- + +.. automodule:: pymodbus.register_write_message + +.. autoclass:: WriteSingleRegisterRequest + :members: + +.. autoclass:: WriteSingleRegisterResponse + :members: + +.. autoclass:: WriteMultipleRegistersRequest + :members: + +.. autoclass:: WriteMultipleRegistersResponse + :members: + diff --git a/doc/sphinx/html/_sources/library/sync-client.rst.txt b/doc/sphinx/html/_sources/library/sync-client.rst.txt new file mode 100644 index 000000000..5f60a3a50 --- /dev/null +++ b/doc/sphinx/html/_sources/library/sync-client.rst.txt @@ -0,0 +1,26 @@ +:mod:`client.sync` --- Twisted Synchronous Modbus Client +========================================================= + +.. module:: client.sync + :synopsis: Twisted Synchronous Modbus Client + +.. moduleauthor:: Galen Collins +.. sectionauthor:: Galen Collins + +API Documentation +------------------ + +.. automodule:: pymodbus.client.sync + +.. autoclass:: BaseModbusClient + :members: + +.. autoclass:: ModbusTcpClient + :members: + +.. autoclass:: ModbusUdpClient + :members: + +.. autoclass:: ModbusSerialClient + :members: + diff --git a/doc/sphinx/html/_sources/library/sync-server.rst.txt b/doc/sphinx/html/_sources/library/sync-server.rst.txt new file mode 100644 index 000000000..3dcf9e04d --- /dev/null +++ b/doc/sphinx/html/_sources/library/sync-server.rst.txt @@ -0,0 +1,40 @@ +:mod:`server.sync` --- Twisted Synchronous Modbus Server +============================================================ + +.. module:: server.sync + :synopsis: Twisted Synchronous Modbus Server + +.. moduleauthor:: Galen Collins +.. sectionauthor:: Galen Collins + +API Documentation +------------------- + +.. automodule:: pymodbus.server.sync + +.. autoclass:: ModbusBaseRequestHandler + :members: + +.. autoclass:: ModbusSingleRequestHandler + :members: + +.. autoclass:: ModbusConnectedRequestHandler + :members: + +.. autoclass:: ModbusDisconnectedRequestHandler + :members: + +.. autoclass:: ModbusTcpServer + :members: + +.. autoclass:: ModbusUdpServer + :members: + +.. autoclass:: ModbusSerialServer + :members: + +.. autofunction:: StartTcpServer + +.. autofunction:: StartUdpServer + +.. autofunction:: StartSerialServer diff --git a/doc/sphinx/html/_sources/library/transaction.rst.txt b/doc/sphinx/html/_sources/library/transaction.rst.txt new file mode 100644 index 000000000..b6ee89a8a --- /dev/null +++ b/doc/sphinx/html/_sources/library/transaction.rst.txt @@ -0,0 +1,31 @@ +:mod:`transaction` --- Transaction Controllers for Pymodbus +============================================================ + +.. module:: transaction + :synopsis: Transaction controllers for pymodbus + +.. moduleauthor:: Galen Collins +.. sectionauthor:: Galen Collins + +API Documentation +------------------- + +.. automodule:: pymodbus.transaction + +.. autoclass:: DictTransactionManager + :members: + +.. autoclass:: FifoTransactionManager + :members: + +.. autoclass:: ModbusSocketFramer + :members: + +.. autoclass:: ModbusRtuFramer + :members: + +.. autoclass:: ModbusAsciiFramer + :members: + +.. autoclass:: ModbusBinaryFramer + :members: diff --git a/doc/sphinx/html/_sources/library/utilities.rst.txt b/doc/sphinx/html/_sources/library/utilities.rst.txt new file mode 100644 index 000000000..d8e78527e --- /dev/null +++ b/doc/sphinx/html/_sources/library/utilities.rst.txt @@ -0,0 +1,33 @@ +:mod:`utilities` --- Extra Modbus Helpers +========================================== + +.. module:: utilities + :synopsis: Extra Modbus Helpers + +.. moduleauthor:: Galen Collins +.. sectionauthor:: Galen Collins + +API Documentation +------------------- + +.. automodule:: pymodbus.utilities + +.. autofunction:: default + +.. autofunction:: dict_property + +.. autofunction:: pack_bitstring + +.. autofunction:: unpack_bitstring + +.. autofunction:: __generate_crc16_table + +.. autofunction:: computeCRC + +.. autofunction:: checkCRC + +.. autofunction:: computeLRC + +.. autofunction:: checkLRC + +.. autofunction:: rtuFrameSize diff --git a/doc/sphinx/html/_static/README b/doc/sphinx/html/_static/README new file mode 100644 index 000000000..06016c7ce --- /dev/null +++ b/doc/sphinx/html/_static/README @@ -0,0 +1 @@ +include any html static content here diff --git a/doc/sphinx/html/_static/ajax-loader.gif b/doc/sphinx/html/_static/ajax-loader.gif new file mode 100644 index 000000000..61faf8cab Binary files /dev/null and b/doc/sphinx/html/_static/ajax-loader.gif differ diff --git a/doc/sphinx/html/_static/basic.css b/doc/sphinx/html/_static/basic.css new file mode 100644 index 000000000..dc88b5a2d --- /dev/null +++ b/doc/sphinx/html/_static/basic.css @@ -0,0 +1,632 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox input[type="text"] { + width: 170px; +} + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px 7px 0 7px; + background-color: #ffe; + width: 40%; + float: right; +} + +p.sidebar-title { + font-weight: bold; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px 7px 0 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +div.admonition dl { + margin-bottom: 0; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + border: 0; + border-collapse: collapse; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +dl { + margin-bottom: 15px; +} + +dd p { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dt:target, .highlighted { + background-color: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; +} + +td.linenos pre { + padding: 5px 0px; + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + margin-left: 0.5em; +} + +table.highlighttable td { + padding: 0 0.5em 0 0.5em; +} + +div.code-block-caption { + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +div.code-block-caption + div > div.highlight > pre { + margin-top: 0; +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + padding: 1em 1em 0; +} + +div.literal-block-wrapper div.highlight { + margin: 0; +} + +code.descname { + background-color: transparent; + font-weight: bold; + font-size: 1.2em; +} + +code.descclassname { + background-color: transparent; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: relative; + left: 0px; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/doc/sphinx/html/_static/classic.css b/doc/sphinx/html/_static/classic.css new file mode 100644 index 000000000..22fa0bde7 --- /dev/null +++ b/doc/sphinx/html/_static/classic.css @@ -0,0 +1,261 @@ +/* + * classic.css_t + * ~~~~~~~~~~~~~ + * + * Sphinx stylesheet -- classic theme. + * + * :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +body { + font-family: sans-serif; + font-size: 100%; + background-color: #11303d; + color: #000; + margin: 0; + padding: 0; +} + +div.document { + background-color: #1c4e63; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 230px; +} + +div.body { + background-color: #ffffff; + color: #000000; + padding: 0 20px 30px 20px; +} + +div.footer { + color: #ffffff; + width: 100%; + padding: 9px 0 9px 0; + text-align: center; + font-size: 75%; +} + +div.footer a { + color: #ffffff; + text-decoration: underline; +} + +div.related { + background-color: #133f52; + line-height: 30px; + color: #ffffff; +} + +div.related a { + color: #ffffff; +} + +div.sphinxsidebar { +} + +div.sphinxsidebar h3 { + font-family: 'Trebuchet MS', sans-serif; + color: #ffffff; + font-size: 1.4em; + font-weight: normal; + margin: 0; + padding: 0; +} + +div.sphinxsidebar h3 a { + color: #ffffff; +} + +div.sphinxsidebar h4 { + font-family: 'Trebuchet MS', sans-serif; + color: #ffffff; + font-size: 1.3em; + font-weight: normal; + margin: 5px 0 0 0; + padding: 0; +} + +div.sphinxsidebar p { + color: #ffffff; +} + +div.sphinxsidebar p.topless { + margin: 5px 10px 10px 10px; +} + +div.sphinxsidebar ul { + margin: 10px; + padding: 0; + color: #ffffff; +} + +div.sphinxsidebar a { + color: #98dbcc; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + + + +/* -- hyperlink styles ------------------------------------------------------ */ + +a { + color: #355f7c; + text-decoration: none; +} + +a:visited { + color: #355f7c; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + + + +/* -- body styles ----------------------------------------------------------- */ + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: 'Trebuchet MS', sans-serif; + background-color: #f2f2f2; + font-weight: normal; + color: #20435c; + border-bottom: 1px solid #ccc; + margin: 20px -20px 10px -20px; + padding: 3px 0 3px 10px; +} + +div.body h1 { margin-top: 0; font-size: 200%; } +div.body h2 { font-size: 160%; } +div.body h3 { font-size: 140%; } +div.body h4 { font-size: 120%; } +div.body h5 { font-size: 110%; } +div.body h6 { font-size: 100%; } + +a.headerlink { + color: #c60f0f; + font-size: 0.8em; + padding: 0 4px 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + background-color: #c60f0f; + color: white; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + text-align: justify; + line-height: 130%; +} + +div.admonition p.admonition-title + p { + display: inline; +} + +div.admonition p { + margin-bottom: 5px; +} + +div.admonition pre { + margin-bottom: 5px; +} + +div.admonition ul, div.admonition ol { + margin-bottom: 5px; +} + +div.note { + background-color: #eee; + border: 1px solid #ccc; +} + +div.seealso { + background-color: #ffc; + border: 1px solid #ff6; +} + +div.topic { + background-color: #eee; +} + +div.warning { + background-color: #ffe4e4; + border: 1px solid #f66; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre { + padding: 5px; + background-color: #eeffcc; + color: #333333; + line-height: 120%; + border: 1px solid #ac9; + border-left: none; + border-right: none; +} + +code { + background-color: #ecf0f3; + padding: 0 1px 0 1px; + font-size: 0.95em; +} + +th { + background-color: #ede; +} + +.warning code { + background: #efc2c2; +} + +.note code { + background: #d6d6d6; +} + +.viewcode-back { + font-family: sans-serif; +} + +div.viewcode-block:target { + background-color: #f4debf; + border-top: 1px solid #ac9; + border-bottom: 1px solid #ac9; +} + +div.code-block-caption { + color: #efefef; + background-color: #1c4e63; +} \ No newline at end of file diff --git a/doc/sphinx/html/_static/comment-bright.png b/doc/sphinx/html/_static/comment-bright.png new file mode 100644 index 000000000..15e27edb1 Binary files /dev/null and b/doc/sphinx/html/_static/comment-bright.png differ diff --git a/doc/sphinx/html/_static/comment-close.png b/doc/sphinx/html/_static/comment-close.png new file mode 100644 index 000000000..4d91bcf57 Binary files /dev/null and b/doc/sphinx/html/_static/comment-close.png differ diff --git a/doc/sphinx/html/_static/comment.png b/doc/sphinx/html/_static/comment.png new file mode 100644 index 000000000..dfbc0cbd5 Binary files /dev/null and b/doc/sphinx/html/_static/comment.png differ diff --git a/doc/sphinx/html/_static/default.css b/doc/sphinx/html/_static/default.css new file mode 100644 index 000000000..81b936363 --- /dev/null +++ b/doc/sphinx/html/_static/default.css @@ -0,0 +1 @@ +@import url("classic.css"); diff --git a/doc/sphinx/html/_static/doctools.js b/doc/sphinx/html/_static/doctools.js new file mode 100644 index 000000000..565497723 --- /dev/null +++ b/doc/sphinx/html/_static/doctools.js @@ -0,0 +1,287 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for all documentation. + * + * :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + +/** + * make the code below compatible with browsers without + * an installed firebug like debugger +if (!window.console || !console.firebug) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", + "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", + "profile", "profileEnd"]; + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; +} + */ + +/** + * small helper function to urldecode strings + */ +jQuery.urldecode = function(x) { + return decodeURIComponent(x).replace(/\+/g, ' '); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s == 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node) { + if (node.nodeType == 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { + var span = document.createElement("span"); + span.className = className; + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this); + }); + } + } + return this.each(function() { + highlight(this); + }); +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated == 'undefined') + return string; + return (typeof translated == 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated == 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + if (!body.length) { + body = $('body'); + } + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) == 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this == '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + }, + + initOnKeyListeners: function() { + $(document).keyup(function(event) { + var activeElementType = document.activeElement.tagName; + // don't navigate when in search box or textarea + if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT') { + switch (event.keyCode) { + case 37: // left + var prevHref = $('link[rel="prev"]').prop('href'); + if (prevHref) { + window.location.href = prevHref; + return false; + } + case 39: // right + var nextHref = $('link[rel="next"]').prop('href'); + if (nextHref) { + window.location.href = nextHref; + return false; + } + } + } + }); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); \ No newline at end of file diff --git a/doc/sphinx/html/_static/down-pressed.png b/doc/sphinx/html/_static/down-pressed.png new file mode 100644 index 000000000..5756c8cad Binary files /dev/null and b/doc/sphinx/html/_static/down-pressed.png differ diff --git a/doc/sphinx/html/_static/down.png b/doc/sphinx/html/_static/down.png new file mode 100644 index 000000000..1b3bdad2c Binary files /dev/null and b/doc/sphinx/html/_static/down.png differ diff --git a/doc/sphinx/html/_static/file.png b/doc/sphinx/html/_static/file.png new file mode 100644 index 000000000..a858a410e Binary files /dev/null and b/doc/sphinx/html/_static/file.png differ diff --git a/doc/sphinx/html/_static/jquery-3.1.0.js b/doc/sphinx/html/_static/jquery-3.1.0.js new file mode 100644 index 000000000..f2fc27478 --- /dev/null +++ b/doc/sphinx/html/_static/jquery-3.1.0.js @@ -0,0 +1,10074 @@ +/*eslint-disable no-unused-vars*/ +/*! + * jQuery JavaScript Library v3.1.0 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2016-07-07T21:44Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var document = window.document; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var concat = arr.concat; + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + + + + function DOMEval( code, doc ) { + doc = doc || document; + + var script = doc.createElement( "script" ); + + script.text = code; + doc.head.appendChild( script ).parentNode.removeChild( script ); + } +/* global Symbol */ +// Defining this global in .eslintrc would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.1.0", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }, + + // Support: Android <=4.0 only + // Make sure we trim BOM and NBSP + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num != null ? + + // Return just the one element from the set + ( num < 0 ? this[ num + this.length ] : this[ num ] ) : + + // Return all the elements in a clean array + slice.call( this ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = jQuery.isArray( copy ) ) ) ) { + + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray( src ) ? src : []; + + } else { + clone = src && jQuery.isPlainObject( src ) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isFunction: function( obj ) { + return jQuery.type( obj ) === "function"; + }, + + isArray: Array.isArray, + + isWindow: function( obj ) { + return obj != null && obj === obj.window; + }, + + isNumeric: function( obj ) { + + // As of jQuery 3.0, isNumeric is limited to + // strings and numbers (primitives or objects) + // that can be coerced to finite numbers (gh-2662) + var type = jQuery.type( obj ); + return ( type === "number" || type === "string" ) && + + // parseFloat NaNs numeric-cast false positives ("") + // ...but misinterprets leading-number strings, particularly hex literals ("0x...") + // subtraction forces infinities to NaN + !isNaN( obj - parseFloat( obj ) ); + }, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + + /* eslint-disable no-unused-vars */ + // See https://github.com/eslint/eslint/issues/6125 + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + type: function( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; + }, + + // Evaluates a script in a global context + globalEval: function( code ) { + DOMEval( code ); + }, + + // Convert dashed to camelCase; used by the css and data modules + // Support: IE <=9 - 11, Edge 12 - 13 + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // Support: Android <=4.0 only + trim: function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var tmp, args, proxy; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; + }, + + now: Date.now, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), +function( i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +} ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = jQuery.type( obj ); + + if ( type === "function" || jQuery.isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.0 + * https://sizzlejs.com/ + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2016-01-04 + */ +(function( window ) { + +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[i] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + + rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), + funescape = function( _, escaped, escapedWhitespace ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + // Support: Firefox<24 + // Workaround erroneous numeric interpretation of +"0x" + return high !== high || escapedWhitespace ? + escaped : + high < 0 ? + // BMP codepoint + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + disabledAncestor = addCombinator( + function( elem ) { + return elem.disabled === true; + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { + + // ID selector + if ( (m = match[1]) ) { + + // Document context + if ( nodeType === 9 ) { + if ( (elem = context.getElementById( m )) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && (elem = newContext.getElementById( m )) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[2] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( (m = match[3]) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !compilerCache[ selector + " " ] && + (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { + + if ( nodeType !== 1 ) { + newContext = context; + newSelector = selector; + + // qSA looks outside Element context, which is not what we want + // Thanks to Andrew Dupont for this workaround technique + // Support: IE <=8 + // Exclude object elements + } else if ( context.nodeName.toLowerCase() !== "object" ) { + + // Capture the context ID, setting it first if necessary + if ( (nid = context.getAttribute( "id" )) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", (nid = expando) ); + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[i] = "#" + nid + " " + toSelector( groups[i] ); + } + newSelector = groups.join( "," ); + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + } + + if ( newSelector ) { + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key + " " ] = value); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement("fieldset"); + + try { + return !!fn( el ); + } catch (e) { + return false; + } finally { + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split("|"), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[i] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + // Known :disabled false positives: + // IE: *[disabled]:not(button, input, select, textarea, optgroup, option, menuitem, fieldset) + // not IE: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Check form elements and option elements for explicit disabling + return "label" in elem && elem.disabled === disabled || + "form" in elem && elem.disabled === disabled || + + // Check non-disabled form elements for fieldset[disabled] ancestors + "form" in elem && elem.disabled === false && ( + // Support: IE6-11+ + // Ancestry is covered for us + elem.isDisabled === disabled || + + // Otherwise, assume any non-