pymodbus-2.1.0/000077500000000000000000000000001335513467700133725ustar00rootroot00000000000000pymodbus-2.1.0/.coveragerc000066400000000000000000000000401335513467700155050ustar00rootroot00000000000000[run] omit = pymodbus/repl/*pymodbus-2.1.0/.github/000077500000000000000000000000001335513467700147325ustar00rootroot00000000000000pymodbus-2.1.0/.github/ISSUE_TEMPLATE.md000066400000000000000000000014321335513467700174370ustar00rootroot00000000000000 ### Versions * Python: * OS: * Pymodbus: * Modbus Hardware (if used): ### Pymodbus Specific * Server: tcp/rtu/ascii - sync/async * Client: tcp/rtu/ascii - sync/async ### Description What were you trying, what has happened, what went wrong, and what did you expect? ### Code and Logs ```python # code and logs here. ``` pymodbus-2.1.0/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000001161335513467700205310ustar00rootroot00000000000000 pymodbus-2.1.0/.gitignore000066400000000000000000000010361335513467700153620ustar00rootroot00000000000000*.pyc *.swp build/ dist/ pymodbus.egg-info/ .coverage .vscode .idea .noseids *.db .idea/ .tox/ doc/api/epydoc/html/ .vscode/ .venv __pycache__/ pymodbus/__pycache__/ pymodbus/client/__pycache__/ pymodbus/datastore/__pycache__/ pymodbus/internal/__pycache__/ pymodbus/server/__pycache__/ test/__pycache__/ **/pymodbus.db /.eggs/ /test/bin/ /test/include/ /test/lib/ /test/pip-selfcheck.json /test/.Python /.cache/ /doc/sphinx/doctrees/ /doc_new/ /doc/quality/ /doc/pymodbus.pdf /doc/sphinx/ /doc/html/ /doc/_build/ .pytest_cache/ **/.pymodhis pymodbus-2.1.0/.project000066400000000000000000000005521335513467700150430ustar00rootroot00000000000000 pymodbus org.python.pydev.PyDevBuilder org.python.pydev.pythonNature pymodbus-2.1.0/.pydevproject000066400000000000000000000006511335513467700161130ustar00rootroot00000000000000 /pymodbus python 2.7 Pymodbus Environment pymodbus-2.1.0/.readthedocs.yml000066400000000000000000000003031335513467700164540ustar00rootroot00000000000000# Build PDF & ePub formats: - epub - pdf requirements_file: requirements-docs.txt python: extra_requirements: - twisted - tornado - documents version: 3.5 pymodbus-2.1.0/.travis.yml000066400000000000000000000015341335513467700155060ustar00rootroot00000000000000sudo: 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 osx_image: xcode8.3 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 -U setuptools - 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]/ pymodbus-2.1.0/CHANGELOG.rst000066400000000000000000000177701335513467700154270ustar00rootroot00000000000000Version 2.1.0 ----------------------------------------------------------- * Fix Issues with Serial client where in partial data was read when the response size is unknown. * Fix Infinite sleep loop in RTU Framer. * Add pygments as extra requirement for repl. * Add support to modify modbus client attributes via repl. * Update modbus repl documentation. * More verbose logs for repl. Version 2.0.1 ----------------------------------------------------------- * Fix unicode decoder error with BinaryPayloadDecoder in some platforms * Avoid unnecessary import of deprecated modules with dependencies on twisted Version 2.0.0 ----------------------------------------------------------- **Note This is a Major release and might affect your existing Async client implementation. Refer examples on how to use the latest async clients.** * Async client implementation based on Tornado, Twisted and asyncio with backward compatibility support for twisted client. * Allow reusing existing[running] asyncio loop when creating async client based on asyncio. * Allow reusing address for Modbus TCP sync server. * Add support to install tornado as extra requirement while installing pymodbus. * Support Pymodbus REPL * Add support to python 3.7. * Bug fix and enhancements in examples. Version 2.0.0rc1 ----------------------------------------------------------- **Note This is a Major release and might affect your existing Async client implementation. Refer examples on how to use the latest async clients.** * Async client implementation based on Tornado, Twisted and asyncio Version 1.5.2 ------------------------------------------------------------ * Fix serial client `is_socket_open` method Version 1.5.1 ------------------------------------------------------------ * Fix device information selectors * Fixed behaviour of the MEI device information command as a server when an invalid object_id is provided by an external client. * Add support for repeated MEI device information Object IDs (client/server) * Added support for encoding device information when it requires more than one PDU to pack. * Added REPR statements for all syncchronous clients * Added `isError` method to exceptions, Any response received can be tested for success before proceeding. .. code-block:: python res = client.read_holding_registers(...) if not res.isError(): # proceed else: # handle error or raise * Add examples for MEI read device information request Version 1.5.0 ------------------------------------------------------------ * Improve transaction speeds for sync clients (RTU/ASCII), now retry on empty happens only when retry_on_empty kwarg is passed to client during intialization `client = Client(..., retry_on_empty=True)` * Fix tcp servers (sync/async) not processing requests with transaction id > 255 * Introduce new api to check if the received response is an error or not (response.isError()) * Move timing logic to framers so that irrespective of client, correct timing logics are followed. * Move framers from transaction.py to respective modules * Fix modbus payload builder and decoder * Async servers can now have an option to defer `reactor.run()` when using `StartServer(...,defer_reactor_run=True)` * Fix UDP client issue while handling MEI messages (ReadDeviceInformationRequest) * Add expected response lengths for WriteMultipleCoilRequest and WriteMultipleRegisterRequest * Fix _rtu_byte_count_pos for GetCommEventLogResponse * Add support for repeated MEI device information Object IDs * Fix struct errors while decoding stray response * Modbus read retries works only when empty/no message is received * Change test runner from nosetest to pytest * Fix Misc examples Version 1.4.0 ------------------------------------------------------------ * Bug fix Modbus TCP client reading incomplete data * Check for slave unit id before processing the request for serial clients * Bug fix serial servers with Modbus Binary Framer * Bug fix header size for ModbusBinaryFramer * Bug fix payload decoder with endian Little * Payload builder and decoder can now deal with the wordorder as well of 32/64 bit data. * Support Database slave contexts (SqlStore and RedisStore) * Custom handlers could be passed to Modbus TCP servers * Asynchronous Server could now be stopped when running on a seperate thread (StopServer) * Signal handlers on Asyncronous servers are now handled based on current thread * Registers in Database datastore could now be read from remote clients * Fix examples in contrib (message_parser.py/message_generator.py/remote_server_context) * Add new example for SqlStore and RedisStore (db store slave context) * Fix minor comaptibility issues with utilities. * Update test requirements * Update/Add new unit tests * Move twisted requirements to extra so that it is not installed by default on pymodbus installtion Version 1.3.2 ------------------------------------------------------------ * ModbusSerialServer could now be stopped when running on a seperate thread. * Fix issue with server and client where in the frame buffer had values from previous unsuccesful transaction * 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 * Fixed documenation and added a number of examples Version 1.2.0 ------------------------------------------------------------ * Reworking the transaction managers to be more explicit and to handle modbus RTU over TCP. * Adding examples for a number of unique requested use cases * Allow RTU framers to fail fast instead of staying at fault * Working on datastore saving and loading Version 1.1.0 ------------------------------------------------------------ * Fixing memory leak in clients and servers (removed __del__) * Adding the ability to override the client framers * Working on web page api and GUI * Moving examples and extra code to contrib sections * Adding more documentation Version 1.0.0 ------------------------------------------------------------ * Adding support for payload builders to form complex encoding and decoding of messages. * Adding BCD and binary payload builders * Adding support for pydev * Cleaning up the build tools * Adding a message encoding generator for testing. * Now passing kwargs to base of PDU so arguments can be used correctly at all levels of the protocol. * A number of bug fixes (see bug tracker and commit messages) Version 0.9.0 ------------------------------------------------------------ Please view the git commit log pymodbus-2.1.0/MANIFEST.in000066400000000000000000000001011335513467700151200ustar00rootroot00000000000000include requirements.txt include README.rst include CHANGELOG.rstpymodbus-2.1.0/Makefile000066400000000000000000000040521335513467700150330ustar00rootroot00000000000000# 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 docs creates sphinx documentation in html' @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 @pytest --cov=pymodbus/ --cov-report term-missing @coverage report --fail-under=90 tox: install @pip install --quiet tox && tox docs: install @pip install --quiet --requirement=requirements-docs.txt @cd doc && make clean && make 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 .eggs *.egg-info *.db .cache .coverage .tox build dist docs/build htmlcov doc/_build test/.Python test/pip-selfcheck.json test/lib/ test/include/ test/bin/ @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 pymodbus-2.1.0/README.rst000066400000000000000000000174361335513467700150740ustar00rootroot00000000000000================================ PyModbus - A Python Modbus Stack ================================ .. 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/badge/?version=latest :target: http://pymodbus.readthedocs.io/en/async/?badge=latest :alt: Documentation Status .. image:: http://pepy.tech/badge/pymodbus :target: http://pepy.tech/project/pymodbus :alt: Downloads .. important:: **Note This is a Major release and might affect your existing Async client implementation. Refer examples on how to use the latest async clients.** ------------------------------------------------------------ Summary ------------------------------------------------------------ Pymodbus is a full Modbus protocol implementation using twisted for its asynchronous communications core. It can also be used without any third party dependencies (aside from pyserial) if a more lightweight project is needed. Furthermore, it should work fine under any python version > 2.7 (including python 3+) ------------------------------------------------------------ Features ------------------------------------------------------------ ~~~~~~~~~~~~~~~~~~~~ Client Features ~~~~~~~~~~~~~~~~~~~~ * Full read/write protocol on discrete and register * Most of the extended protocol (diagnostic/file/pipe/setting/information) * TCP, UDP, Serial ASCII, Serial RTU, and Serial Binary * asynchronous(powered by twisted/tornado/asyncio) and synchronous versions * Payload builder/decoder utilities * Pymodbus REPL for quick tests ~~~~~~~~~~~~~~~~~~~~ Server Features ~~~~~~~~~~~~~~~~~~~~ * Can function as a fully implemented modbus server * TCP, UDP, Serial ASCII, Serial RTU, and Serial Binary * asynchronous(powered by twisted) and synchronous versions * Full server control context (device information, counters, etc) * A number of backing contexts (database, redis, sqlite, a slave device) ------------------------------------------------------------ Use Cases ------------------------------------------------------------ Although most system administrators will find little need for a Modbus server on any modern hardware, they may find the need to query devices on their network for status (PDU, PDR, UPS, etc). Since the library is written in python, it allows for easy scripting and/or integration into their existing solutions. Continuing, most monitoring software needs to be stress tested against hundreds or even thousands of devices (why this was originally written), but getting access to that many is unwieldy at best. The pymodbus server will allow 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 ------------------------------------------------------------ Example Code ------------------------------------------------------------ For those of you that just want to get started fast, here you go:: from pymodbus.client.sync import ModbusTcpClient client = ModbusTcpClient('127.0.0.1') client.write_coil(1, True) result = client.read_coils(1,1) print(result.bits[0]) client.close() For more advanced examples, check out the examples included in the respository. If you have created any utilities that meet a specific 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 or at gitter: https://gitter.im/pymodbus_dev/Lobby ------------------------------------------------------------ Pymodbus REPL (Read Evaluate Procee Loop) ------------------------------------------------------------ Starting with Pymodbus 2.x, pymodbus library comes with handy Pymodbus REPL to quickly run the modbus clients in tcp/rtu modes. Pymodbus REPL comes with many handy features such as payload decoder to directly retrieve the values in desired format and supports all the diagnostic function codes directly . For more info on REPL refer `Pymodbus REPL `_ .. image:: https://asciinema.org/a/y1xOk7lm59U1bRBE2N1pDIj2o.png :target: https://asciinema.org/a/y1xOk7lm59U1bRBE2N1pDIj2o ------------------------------------------------------------ Installing ------------------------------------------------------------ You can install using pip or easy install by issuing the following commands in a terminal window (make sure you have correct permissions or a virtualenv currently running):: easy_install -U pymodbus pip install -U pymodbus To Install pymodbus with twisted support run:: pip install -U pymodbus[twisted] To Install pymodbus with tornado support run:: pip install -U pymodbus[tornado] To Install pymodbus REPL:: pip install -U pymodbus[repl] Otherwise you can pull the trunk source and install from there:: git clone git://github.com/bashwork/pymodbus.git cd pymodbus python setup.py install Either method will install all the required dependencies (at their appropriate versions) for your current python distribution. If you would like to install pymodbus without the twisted dependency, simply edit the setup.py file before running easy_install and comment out all mentions of twisted. It should be noted that without twisted, one will only be able to run the synchronized version as the asynchronous versions uses twisted for its event loop. ------------------------------------------------------------ Current Work In Progress ------------------------------------------------------------ Since I don't have access to any live modbus devices anymore it is a bit hard to test on live hardware. However, if you would 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: * 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) * Asynchronous clients with support to tornado , asyncio ------------------------------------------------------------ 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 ------------------------------------------------------------ Contributing ------------------------------------------------------------ Just fork the repo and raise your PR against `dev` branch. ------------------------------------------------------------ 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, https://github.com/HynekPetrak * Twisted Matrix Released under the BSD License pymodbus-2.1.0/doc/000077500000000000000000000000001335513467700141375ustar00rootroot00000000000000pymodbus-2.1.0/doc/INSTALL000066400000000000000000000031221335513467700151660ustar00rootroot00000000000000Requirements ------------- * Python 2.3 or later. * Python Twisted * Pyserial On Windows pywin32 is recommended (this is built in to ActivePython, so no need to reinstall if you use it instead of standard Python): http://sourceforge.net/project/showfiles.php?group_id=78018 The Windows IOCP reactor requires pywin32 build 205 or later. Installation ------------- To install the package from pypi, use either easy_install or pip:: pip install -U pymodbus easy_install -U pymodbus As with other Python packages, the standard way of installing from source is (as root or administrator):: python setup.py install Running Tests -------------- The tests can be run with the built in unittest module, however, it is much easier to run with the nose package. With that installed, you can use either of the following:: python setup.py test nosetests Building Documentation ---------------------- The documentation is written in restructured text using the sphinx module. Building it is as simple as:: python setup build_sphinx The API documents can be generated using one of four programs: * epydoc * pydoc * pydoctor * doxygen To bulid these, simply run the following command and the available packages will sipmly be built:: python setup.py build_apidocs Quality Tests ---------------------- There are a number of quality tests that can be run against the code base aside from unit tests:: python setup.py scan_2to3 # run a python3 compatability test python setup.py pep8 # run a pop8 standards test python setup.py lint # run a lint test pymodbus-2.1.0/doc/LICENSE000066400000000000000000000025611335513467700151500ustar00rootroot00000000000000Copyright (c) 2011 Galen Collins All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pymodbus-2.1.0/doc/Makefile000066400000000000000000000011351335513467700155770ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXPROJ = PyModbus SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)pymodbus-2.1.0/doc/TODO000066400000000000000000000035201335513467700146270ustar00rootroot00000000000000--------------------------------------------------------------------------- General --------------------------------------------------------------------------- - reorganize code into folder namespaces - put protocol code in protocol namespace - make framer read header->read header.length - maybe just for sync - finish clients (and interface) - add all modbus control into server - add a frontend plugin system - web frontend (bottle) - twisted trial / twisted logging (for functional async tests) - twisted serial server - add daemonize code / init.d / config (or just use twisted) - add correct transaction handling (retry, fail, etc) - finish remaining functions --------------------------------------------------------------------------- Protocols --------------------------------------------------------------------------- - Serial RTU -> just use sleep wait - Test serial against devices (and virtual tty) --------------------------------------------------------------------------- Utilities --------------------------------------------------------------------------- - (tcp/serial) forwarder - (udp/serial) forwarder --------------------------------------------------------------------------- Client --------------------------------------------------------------------------- - Rework transaction flow and response data --------------------------------------------------------------------------- Tools --------------------------------------------------------------------------- - add functional tests - add tk and wx gui frontdends (with editable data tables) - rpm and deb packages (documentation) --------------------------------------------------------------------------- Scratch --------------------------------------------------------------------------- from twisted.python import log observer = log.PythonLoggingObserver() observer.start() pymodbus-2.1.0/doc/api/000077500000000000000000000000001335513467700147105ustar00rootroot00000000000000pymodbus-2.1.0/doc/api/doxygen/000077500000000000000000000000001335513467700163655ustar00rootroot00000000000000pymodbus-2.1.0/doc/api/doxygen/.doxygen000066400000000000000000001737141335513467700200600ustar00rootroot00000000000000# Doxyfile 1.5.8 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project # # All text after a hash (#) is considered a comment and will be ignored # The format is: # TAG = value [value, ...] # For lists items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (" ") #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all # text before the first occurrence of this tag. Doxygen uses libiconv (or the # iconv built into libc) for the transcoding. See # http://www.gnu.org/software/libiconv for the list of possible encodings. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded # by quotes) that should identify the project. PROJECT_NAME = Pymodbus # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or # if some version control system is used. PROJECT_NUMBER = 0.5 # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. # If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. OUTPUT_DIRECTORY = # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create # 4096 sub-directories (in 2 levels) under the output directory of each output # format and will distribute the generated files over these directories. # Enabling this option can be useful when feeding doxygen a huge amount of # source files, where putting all generated files in the same directory would # otherwise cause performance problems for the file system. CREATE_SUBDIRS = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # The default language is English, other supported languages are: # Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, # Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek, # Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages), # Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish, # Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, Slovene, # Spanish, Swedish, and Ukrainian. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will # include brief member descriptions after the members that are listed in # the file and class documentation (similar to JavaDoc). # Set to NO to disable this. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend # the brief description of a member or function before the detailed description. # Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator # that is used to form the text in various listings. Each string # in this list, if found as the leading text of the brief description, will be # stripped from the text and the result after processing the whole list, is # used as the annotated text. Otherwise, the brief description is used as-is. # If left blank, the following values are used ("$name" is automatically # replaced with the name of the entity): "The $name class" "The $name widget" # "The $name file" "is" "provides" "specifies" "contains" # "represents" "a" "an" "the" ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full # path before files name in the file list and in the header files. If set # to NO the shortest path that makes the file name unique will be used. FULL_PATH_NAMES = YES # If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag # can be used to strip a user-defined part of the path. Stripping is # only done if one of the specified strings matches the left-hand part of # the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the # path to strip. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of # the path mentioned in the documentation of a class, which tells # the reader which header file to include in order to use a class. # If left blank only the name of the header file containing the class # definition is used. Otherwise one should specify the include paths that # are normally passed to the compiler using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter # (but less readable) file names. This can be useful is your file systems # doesn't support long names like on DOS, Mac, or CD-ROM. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen # will interpret the first line (until the first dot) of a JavaDoc-style # comment as the brief description. If set to NO, the JavaDoc # comments will behave just like regular Qt-style comments # (thus requiring an explicit @brief command for a brief description.) JAVADOC_AUTOBRIEF = NO # If the QT_AUTOBRIEF tag is set to YES then Doxygen will # interpret the first line (until the first dot) of a Qt-style # comment as the brief description. If set to NO, the comments # will behave just like regular Qt-style comments (thus requiring # an explicit \brief command for a brief description.) QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen # treat a multi-line C++ special comment block (i.e. a block of //! or /// # comments) as a brief description. This used to be the default behaviour. # The new default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it # re-implements. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce # a new page for each member. If set to NO, the documentation of a member will # be part of the file/class/namespace that contains it. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. TAB_SIZE = 4 # This tag can be used to specify a number of aliases that acts # as commands in the documentation. An alias has the form "name=value". # For example adding "sideeffect=\par Side Effects:\n" will allow you to # put the command \sideeffect (or @sideeffect) in the documentation, which # will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. ALIASES = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C # sources only. Doxygen will then generate output that is more tailored for C. # For instance, some of the names that are used will be different. The list # of all members will be omitted, etc. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java # sources only. Doxygen will then generate output that is more tailored for # Java. For instance, namespaces will be presented as packages, qualified # scopes will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources only. Doxygen will then generate output that is more tailored for # Fortran. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for # VHDL. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it parses. # With this tag you can assign which parser to use for a given extension. # Doxygen has a built-in mapping, but you can override or extend it using this tag. # The format is ext=language, where ext is a file extension, and language is one of # the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, # Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat # .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), # use: inc=Fortran f=C EXTENSION_MAPPING = # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should # set this tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); v.s. # func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. # Doxygen will parse them like normal C++ but will assume all classes use public # instead of private inheritance when no explicit protection keyword is present. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate getter # and setter methods for a property. Setting this option to YES (the default) # will make doxygen to replace the get and set methods by a property in the # documentation. This will only work if the methods are indeed getting or # setting a simple type. If this is not the case, or you want to show the # methods anyway, you should set this option to NO. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES (the default) to allow class member groups of # the same type (for instance a group of public functions) to be put as a # subgroup of that type (e.g. under the Public Functions section). Set it to # NO to prevent subgrouping. Alternatively, this can be done per class using # the \nosubgrouping command. SUBGROUPING = YES # When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum # is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically # be useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. TYPEDEF_HIDES_STRUCT = NO # The SYMBOL_CACHE_SIZE determines the size of the internal cache use to # determine which symbols to keep in memory and which to flush to disk. # When the cache is full, less often used symbols will be written to disk. # For small to medium size projects (<1000 input files) the default value is # probably good enough. For larger projects a too small cache size can cause # doxygen to be busy swapping symbols to and from disk most of the time # causing a significant performance penality. # If the system has enough physical memory increasing the cache will improve the # performance by keeping more symbols in memory. Note that the value works on # a logarithmic scale so increasing the size by one will rougly double the # memory usage. The cache size is given by this formula: # 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, # corresponding to a cache size of 2^16 = 65536 symbols SYMBOL_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. # Private class members and static file members will be hidden unless # the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES all private members of a class # will be included in the documentation. EXTRACT_PRIVATE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. EXTRACT_STATIC = NO # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) # defined locally in source files will be included in the documentation. # If set to NO only classes defined in header files are included. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. When set to YES local # methods, which are defined in the implementation section but not in # the interface are included in the documentation. # If set to NO (the default) only methods in the interface are included. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base # name of the file that contains the anonymous namespace. By default # anonymous namespace are hidden. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members of documented classes, files or namespaces. # If set to NO (the default) these members will be included in the # various overviews, but no documentation section is generated. # This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. # If set to NO (the default) these classes will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all # friend (class|struct|union) declarations. # If set to NO (the default) these declarations will be included in the # documentation. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. # If set to NO (the default) these blocks will be appended to the # function's detailed documentation block. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation # that is typed after a \internal command is included. If the tag is set # to NO (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate # file names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen # will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen # will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = YES # If the INLINE_INFO tag is set to YES (the default) then a tag [inline] # is inserted in the documentation for inline members. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen # will sort the (detailed) documentation of file and class members # alphabetically by member name. If set to NO the members will appear in # declaration order. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the # brief documentation of file, namespace and class members alphabetically # by member name. If set to NO (the default) the members will appear in # declaration order. SORT_BRIEF_DOCS = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the # hierarchy of group names into alphabetical order. If set to NO (the default) # the group names will appear in their defined order. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be # sorted by fully-qualified names, including namespaces. If set to # NO (the default), the class list will be sorted only by class name, # not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the # alphabetical list. SORT_BY_SCOPE_NAME = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or # disable (NO) the todo list. This list is created by putting \todo # commands in the documentation. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or # disable (NO) the test list. This list is created by putting \test # commands in the documentation. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or # disable (NO) the bug list. This list is created by putting \bug # commands in the documentation. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or # disable (NO) the deprecated list. This list is created by putting # \deprecated commands in the documentation. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional # documentation sections, marked by \if sectionname ... \endif. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines # the initial value of a variable or define consists of for it to appear in # the documentation. If the initializer consists of more lines than specified # here it will be hidden. Use a value of 0 to hide initializers completely. # The appearance of the initializer of individual variables and defines in the # documentation can be controlled using \showinitializer or \hideinitializer # command in the documentation regardless of this setting. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated # at the bottom of the documentation of classes and structs. If set to YES the # list will mention the files that were used to generate the documentation. SHOW_USED_FILES = YES # If the sources in your project are distributed over multiple directories # then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy # in the documentation. The default is NO. SHOW_DIRECTORIES = NO # Set the SHOW_FILES tag to NO to disable the generation of the Files page. # This will remove the Files entry from the Quick Index and from the # Folder Tree View (if specified). The default is YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the # Namespaces page. # This will remove the Namespaces entry from the Quick Index # and from the Folder Tree View (if specified). The default is YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command , where is the value of # the FILE_VERSION_FILTER tag, and is the name of an input file # provided by doxygen. Whatever the program writes to standard output # is used as the file version. See the manual for examples. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by # doxygen. The layout file controls the global structure of the generated output files # in an output format independent way. The create the layout file that represents # doxygen's defaults, run doxygen with the -l option. You can optionally specify a # file name after the option, if omitted DoxygenLayout.xml will be used as the name # of the layout file. LAYOUT_FILE = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = YES # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = NO # If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings # for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. WARN_IF_UNDOCUMENTED = NO # If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some # parameters in a documented function, or documenting parameters that # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = NO # This WARN_NO_PARAMDOC option can be abled to get warnings for # functions that are documented, but have no documentation for their parameters # or return value. If set to NO (the default) doxygen will only warn about # wrong or incomplete parameter documentation, but not about the absence of # documentation. WARN_NO_PARAMDOC = NO # The WARN_FORMAT tag determines the format of the warning messages that # doxygen can produce. The string should contain the $file, $line, and $text # tags, which will be replaced by the file and line number from which the # warning originated and the warning text. Optionally the format may contain # $version, which will be replaced by the version of the file (if it could # be obtained via FILE_VERSION_FILTER) WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning # and error messages should be written. If left blank the output is written # to stderr. WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag can be used to specify the files and/or directories that contain # documented source files. You may enter file names like "myfile.cpp" or # directories like "/usr/src/myproject". Separate the files or directories # with spaces. INPUT = ../../../pymodbus # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is # also the default input encoding. Doxygen uses libiconv (or the iconv built # into libc) for the transcoding. See http://www.gnu.org/software/libiconv for # the list of possible encodings. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank the following patterns are tested: # *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx # *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 FILE_PATTERNS = *.py # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used select whether or not files or # directories that are symbolic links (a Unix filesystem feature) are excluded # from the input. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. Note that the wildcards are matched # against the file with absolute path, so to exclude all test directories # for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or # directories that contain example code fragments that are included (see # the \include command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank all files are included. EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude # commands irrespective of the value of the RECURSIVE tag. # Possible values are YES and NO. If left blank NO is used. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or # directories that contain image that are included in the documentation (see # the \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command , where # is the value of the INPUT_FILTER tag, and is the name of an # input file. Doxygen will then use the output that the filter program writes # to standard output. # If FILTER_PATTERNS is specified, this tag will be # ignored. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. # Doxygen will compare the file name with each pattern and apply the # filter if there is a match. # The filters are a list of the form: # pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further # info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER # is applied to all files. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will be used to filter the input files when producing source # files to browse (i.e. when SOURCE_BROWSER is set to YES). FILTER_SOURCE_FILES = NO #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will # be generated. Documented entities will be cross-referenced with these sources. # Note: To get rid of all source code in the generated output, make sure also # VERBATIM_HEADERS is set to NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct # doxygen to hide any special comment blocks from generated source code # fragments. Normal C and C++ comments will always remain visible. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES # then for each documented function all documented # functions referencing it will be listed. REFERENCED_BY_RELATION = YES # If the REFERENCES_RELATION tag is set to YES # then for each documented function all documented entities # called/used by that function will be listed. REFERENCES_RELATION = YES # If the REFERENCES_LINK_SOURCE tag is set to YES (the default) # and SOURCE_BROWSER tag is set to YES, then the hyperlinks from # functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will # link to the source code. # Otherwise they will link to the documentation. REFERENCES_LINK_SOURCE = YES # If the USE_HTAGS tag is set to YES then the references to source code # will point to the HTML generated by the htags(1) tool instead of doxygen # built-in source browser. The htags tool is part of GNU's global source # tagging system (see http://www.gnu.org/software/global/global.html). You # will need version 4.8.6 or higher. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen # will generate a verbatim copy of the header file for each class for # which an include is specified. Set to NO to disable this. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index # of all compounds will be generated. Enable this if the project # contains a lot of classes, structs, unions or interfaces. ALPHABETICAL_INDEX = NO # If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then # the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns # in which this list will be split (can be a number in the range [1..20]) COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all # classes will be put under the same header in the alphabetical index. # The IGNORE_PREFIX tag can be used to specify one or more prefixes that # should be ignored while generating the index headers. IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `html' will be used as the default path. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for # each generated HTML page (for example: .htm,.php,.asp). If it is left blank # doxygen will generate files with .html extension. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a personal HTML header for # each generated HTML page. If it is left blank doxygen will generate a # standard header. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a personal HTML footer for # each generated HTML page. If it is left blank doxygen will generate a # standard footer. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading # style sheet that is used by each HTML page. It can be used to # fine-tune the look of the HTML output. If the tag is left blank doxygen # will generate a default style sheet. Note that doxygen will try to copy # the style sheet file to the HTML output directory, so don't put your own # stylesheet in the HTML output directory as well, or it will be erased! HTML_STYLESHEET = # If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, # files or namespaces will be aligned in HTML using tables. If set to # NO a bullet list will be used. HTML_ALIGN_MEMBERS = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. For this to work a browser that supports # JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox # Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). HTML_DYNAMIC_SECTIONS = NO # If the GENERATE_DOCSET tag is set to YES, additional index files # will be generated that can be used as input for Apple's Xcode 3 # integrated development environment, introduced with OSX 10.5 (Leopard). # To create a documentation set, doxygen will generate a Makefile in the # HTML output directory. Running make will produce the docset in that # directory and running "make install" will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find # it at startup. # See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. GENERATE_DOCSET = NO # When GENERATE_DOCSET tag is set to YES, this tag determines the name of the # feed. A documentation feed provides an umbrella under which multiple # documentation sets from a single provider (such as a company or product suite) # can be grouped. DOCSET_FEEDNAME = "Doxygen generated docs" # When GENERATE_DOCSET tag is set to YES, this tag specifies a string that # should uniquely identify the documentation set bundle. This should be a # reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen # will append .docset to the name. DOCSET_BUNDLE_ID = org.doxygen.Project # If the GENERATE_HTMLHELP tag is set to YES, additional index files # will be generated that can be used as input for tools like the # Microsoft HTML help workshop to generate a compiled HTML help file (.chm) # of the generated HTML documentation. GENERATE_HTMLHELP = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can # be used to specify the file name of the resulting .chm file. You # can add a path in front of the file if the result should not be # written to the html output directory. CHM_FILE = # If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can # be used to specify the location (absolute path including file name) of # the HTML help compiler (hhc.exe). If non-empty doxygen will try to run # the HTML help compiler on the generated index.hhp. HHC_LOCATION = # If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag # controls if a separate .chi index file is generated (YES) or that # it should be included in the master .chm file (NO). GENERATE_CHI = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING # is used to encode HtmlHelp index (hhk), content (hhc) and project file # content. CHM_INDEX_ENCODING = # If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag # controls whether a binary table of contents is generated (YES) or a # normal table of contents (NO) in the .chm file. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the HTML help documentation and to the tree view. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER # are set, an additional index file will be generated that can be used as input for # Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated # HTML documentation. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can # be used to specify the file name of the resulting .qch file. # The path specified is relative to the HTML output folder. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # http://doc.trolltech.com/qthelpproject.html#namespace QHP_NAMESPACE = # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # http://doc.trolltech.com/qthelpproject.html#virtual-folders QHP_VIRTUAL_FOLDER = doc # If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. # For more information please see # http://doc.trolltech.com/qthelpproject.html#custom-filters QHP_CUST_FILTER_NAME = # The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see # Qt Help Project / Custom Filters. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's # filter section matches. # Qt Help Project / Filter Attributes. QHP_SECT_FILTER_ATTRS = # If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can # be used to specify the location of Qt's qhelpgenerator. # If non-empty doxygen will try to run qhelpgenerator on the generated # .qhp file. QHG_LOCATION = # The DISABLE_INDEX tag can be used to turn on/off the condensed index at # top of each HTML page. The value NO (the default) enables the index and # the value YES disables it. DISABLE_INDEX = NO # This tag can be used to set the number of enum values (range [1..20]) # that doxygen will group on one line in the generated HTML documentation. ENUM_VALUES_PER_LINE = 4 # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. # If the tag value is set to FRAME, a side panel will be generated # containing a tree-like index structure (just like the one that # is generated for HTML Help). For this to work a browser that supports # JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, # Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are # probably better off using the HTML help feature. Other possible values # for this tag are: HIERARCHIES, which will generate the Groups, Directories, # and Class Hierarchy pages using a tree view instead of an ordered list; # ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which # disables this behavior completely. For backwards compatibility with previous # releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE # respectively. GENERATE_TREEVIEW = NO # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be # used to set the initial width (in pixels) of the frame in which the tree # is shown. TREEVIEW_WIDTH = 250 # Use this tag to change the font size of Latex formulas included # as images in the HTML documentation. The default is 10. Note that # when you change the font size after a successful doxygen run you need # to manually remove any form_*.png images from the HTML output directory # to force them to be regenerated. FORMULA_FONTSIZE = 10 #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- # If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. GENERATE_LATEX = NO # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `latex' will be used as the default path. LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. If left blank `latex' will be used as the default command name. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to # generate index for LaTeX. If left blank `makeindex' will be used as the # default command name. MAKEINDEX_CMD_NAME = makeindex # If the COMPACT_LATEX tag is set to YES Doxygen generates more compact # LaTeX documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_LATEX = NO # The PAPER_TYPE tag can be used to set the paper type that is used # by the printer. Possible values are: a4, a4wide, letter, legal and # executive. If left blank a4wide will be used. PAPER_TYPE = a4wide # The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for # the generated latex document. The header should contain everything until # the first chapter. If it is left blank doxygen will generate a # standard header. Notice: only use this tag if you know what you are doing! LATEX_HEADER = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated # is prepared for conversion to pdf (using ps2pdf). The pdf file will # contain links (just like the HTML output) instead of page references # This makes the output suitable for online browsing using a pdf viewer. PDF_HYPERLINKS = NO # If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of # plain latex in the generated Makefile. Set this option to YES to get a # higher quality PDF documentation. USE_PDFLATEX = NO # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. # command to the generated LaTeX files. This will instruct LaTeX to keep # running if errors occur, instead of asking the user for help. # This option is also used when generating formulas in HTML. LATEX_BATCHMODE = NO # If LATEX_HIDE_INDICES is set to YES then doxygen will not # include the index chapters (such as File Index, Compound Index, etc.) # in the output. LATEX_HIDE_INDICES = NO #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- # If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output # The RTF output is optimized for Word 97 and may not look very pretty with # other RTF readers or editors. GENERATE_RTF = NO # The RTF_OUTPUT tag is used to specify where the RTF docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `rtf' will be used as the default path. RTF_OUTPUT = rtf # If the COMPACT_RTF tag is set to YES Doxygen generates more compact # RTF documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_RTF = NO # If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated # will contain hyperlink fields. The RTF file will # contain links (just like the HTML output) instead of page references. # This makes the output suitable for online browsing using WORD or other # programs which support those fields. # Note: wordpad (write) and others do not support links. RTF_HYPERLINKS = NO # Load stylesheet definitions from file. Syntax is similar to doxygen's # config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an rtf document. # Syntax is similar to doxygen's config file. RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- # If the GENERATE_MAN tag is set to YES (the default) Doxygen will # generate man pages GENERATE_MAN = NO # The MAN_OUTPUT tag is used to specify where the man pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `man' will be used as the default path. MAN_OUTPUT = man # The MAN_EXTENSION tag determines the extension that is added to # the generated man pages (default is the subroutine's section .3) MAN_EXTENSION = .3 # If the MAN_LINKS tag is set to YES and Doxygen generates man output, # then it will generate one additional man file for each entity # documented in the real man page(s). These additional files # only source the real man page, but without them the man command # would be unable to find the correct page. The default is NO. MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- # If the GENERATE_XML tag is set to YES Doxygen will # generate an XML file that captures the structure of # the code including all documentation. GENERATE_XML = NO # The XML_OUTPUT tag is used to specify where the XML pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `xml' will be used as the default path. XML_OUTPUT = xml # The XML_SCHEMA tag can be used to specify an XML schema, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_SCHEMA = # The XML_DTD tag can be used to specify an XML DTD, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_DTD = # If the XML_PROGRAMLISTING tag is set to YES Doxygen will # dump the program listings (including syntax highlighting # and cross-referencing information) to the XML output. Note that # enabling this will significantly increase the size of the XML output. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will # generate an AutoGen Definitions (see autogen.sf.net) file # that captures the structure of the code including all # documentation. Note that this feature is still experimental # and incomplete at the moment. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- # If the GENERATE_PERLMOD tag is set to YES Doxygen will # generate a Perl module file that captures the structure of # the code including all documentation. Note that this # feature is still experimental and incomplete at the # moment. GENERATE_PERLMOD = NO # If the PERLMOD_LATEX tag is set to YES Doxygen will generate # the necessary Makefile rules, Perl scripts and LaTeX code to be able # to generate PDF and DVI output from the Perl module output. PERLMOD_LATEX = NO # If the PERLMOD_PRETTY tag is set to YES the Perl module output will be # nicely formatted so it can be parsed by a human reader. # This is useful # if you want to understand what is going on. # On the other hand, if this # tag is set to NO the size of the Perl module output will be much smaller # and Perl will parse it just the same. PERLMOD_PRETTY = YES # The names of the make variables in the generated doxyrules.make file # are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. # This is useful so different doxyrules.make files included by the same # Makefile don't overwrite each other's variables. PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will # evaluate all C-preprocessor directives found in the sources and include # files. ENABLE_PREPROCESSING = YES # If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro # names in the source code. If set to NO (the default) only conditional # compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. MACRO_EXPANSION = NO # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES # then the macro expansion is limited to the macros specified with the # PREDEFINED and EXPAND_AS_DEFINED tags. EXPAND_ONLY_PREDEF = NO # If the SEARCH_INCLUDES tag is set to YES (the default) the includes files # in the INCLUDE_PATH (see below) will be search if a #include is found. SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by # the preprocessor. INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the # directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that # are defined before the preprocessor is started (similar to the -D option of # gcc). The argument of the tag is a list of macros of the form: name # or name=definition (no spaces). If the definition and the = are # omitted =1 is assumed. To prevent a macro definition from being # undefined via #undef or recursively expanded use the := operator # instead of the = operator. PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. # The macro definition that is found in the sources will be used. # Use the PREDEFINED tag if you want to use a different macro definition. EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then # doxygen's preprocessor will remove all function-like macros that are alone # on a line, have an all uppercase name, and do not end with a semicolon. Such # function macros are typically used for boiler-plate code, and will confuse # the parser if not removed. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- # The TAGFILES option can be used to specify one or more tagfiles. # Optionally an initial location of the external documentation # can be added for each tagfile. The format of a tag file without # this location is as follows: # # TAGFILES = file1 file2 ... # Adding location for the tag files is done as follows: # # TAGFILES = file1=loc1 "file2 = loc2" ... # where "loc1" and "loc2" can be relative or absolute paths or # URLs. If a location is present for each tag, the installdox tool # does not have to be run to correct the links. # Note that each tag file must have a unique name # (where the name does NOT include the path) # If a tag file is not located in the directory in which doxygen # is run, you must also specify the path to the tagfile here. TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create # a tag file that is based on the input files it reads. GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES all external classes will be listed # in the class index. If set to NO only the inherited external classes # will be listed. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed # in the modules index. If set to NO, only the current project's groups will # be listed. EXTERNAL_GROUPS = YES # The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will # generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base # or super classes. Setting the tag to NO turns the diagrams off. Note that # this option is superseded by the HAVE_DOT option below. This is only a # fallback. It is recommended to install and use dot, since it yields more # powerful graphs. CLASS_DIAGRAMS = YES # You can define message sequence charts within doxygen comments using the \msc # command. Doxygen will then run the mscgen tool (see # http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the # documentation. The MSCGEN_PATH tag allows you to specify the directory where # the mscgen tool resides. If left empty the tool is assumed to be found in the # default search path. MSCGEN_PATH = # If set to YES, the inheritance and collaboration graphs will hide # inheritance and usage relations if the target is undocumented # or is not a class. HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz, a graph visualization # toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) HAVE_DOT = YES # By default doxygen will write a font called FreeSans.ttf to the output # directory and reference it in all dot files that doxygen generates. This # font does not include all possible unicode characters however, so when you need # these (or just want a differently looking font) you can specify the font name # using DOT_FONTNAME. You need need to make sure dot is able to find the font, # which can be done by putting it in a standard location or by setting the # DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory # containing the font. DOT_FONTNAME = FreeSans # The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. # The default size is 10pt. DOT_FONTSIZE = 10 # By default doxygen will tell dot to use the output directory to look for the # FreeSans.ttf font (which doxygen will put there itself). If you specify a # different font using DOT_FONTNAME you can set the path where dot # can find it using this tag. DOT_FONTPATH = # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect inheritance relations. Setting this tag to YES will force the # the CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect implementation dependencies (inheritance, containment, and # class references variables) of the class with other documented classes. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = NO # If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. TEMPLATE_RELATIONS = NO # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT # tags are set to YES then doxygen will generate a graph for each documented # file showing the direct and indirect include dependencies of the file with # other documented files. INCLUDE_GRAPH = YES # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and # HAVE_DOT tags are set to YES then doxygen will generate a graph for each # documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = YES # If the CALL_GRAPH and HAVE_DOT options are set to YES then # doxygen will generate a call dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable call graphs # for selected functions only using the \callgraph command. CALL_GRAPH = NO # If the CALLER_GRAPH and HAVE_DOT tags are set to YES then # doxygen will generate a caller dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable caller # graphs for selected functions only using the \callergraph command. CALLER_GRAPH = NO # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES # then doxygen will show the dependencies a directory has on other directories # in a graphical way. The dependency relations are determined by the #include # relations between the files in the directories. DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. Possible values are png, jpg, or gif # If left blank png will be used. DOT_IMAGE_FORMAT = png # The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the # \dotfile command). DOTFILE_DIRS = # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of # nodes that will be shown in the graph. If the number of nodes in a graph # becomes larger than this value, doxygen will truncate the graph, which is # visualized by representing a node as a red box. Note that doxygen if the # number of direct children of the root node in a graph is already larger than # DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note # that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. DOT_GRAPH_MAX_NODES = 50 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the # graphs generated by dot. A depth value of 3 means that only nodes reachable # from the root by following a path via at most 3 edges will be shown. Nodes # that lay further from the root node will be omitted. Note that setting this # option to 1 or 2 may greatly reduce the computation time needed for large # code bases. Also note that the size of a graph can be further restricted by # DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. MAX_DOT_GRAPH_DEPTH = 0 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent # background. This is disabled by default, because dot on Windows does not # seem to support this out of the box. Warning: Depending on the platform used, # enabling this option may lead to badly anti-aliased labels on the edges of # a graph (i.e. they become hard to read). DOT_TRANSPARENT = NO # Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) # support this, this feature is disabled by default. DOT_MULTI_TARGETS = NO # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES (the default) Doxygen will # remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES #--------------------------------------------------------------------------- # Options related to the search engine #--------------------------------------------------------------------------- # The SEARCHENGINE tag specifies whether or not a search engine should be # used. If set to NO the values of all tags below this one will be ignored. SEARCHENGINE = YES pymodbus-2.1.0/doc/api/doxygen/build.py000066400000000000000000000020451335513467700200370ustar00rootroot00000000000000#!/usr/bin/env python ''' Doxygen API Builder --------------------- ''' import os, shutil def is_exe(path): ''' Returns if the program is executable :param path: The path to the file :return: True if it is, False otherwise ''' return os.path.exists(path) and os.access(path, os.X_OK) def which(program): ''' Check to see if an executable exists :param program: The program to check for :return: The full path of the executable or None if not found ''' fpath, name = os.path.split(program) if fpath: if is_exe(program): return program else: for path in os.environ["PATH"].split(os.pathsep): exe_file = os.path.join(path, program) if is_exe(exe_file): return exe_file return None if which('doxygen') is not None: print "Building Doxygen API Documentation" os.system("doxygen .doxygen") if os.path.exists('../../../build'): shutil.move("html", "../../../build/doxygen") else: print "Doxygen not available...not building" pymodbus-2.1.0/doc/api/epydoc/000077500000000000000000000000001335513467700161735ustar00rootroot00000000000000pymodbus-2.1.0/doc/api/epydoc/build.py000077500000000000000000000017021335513467700176470ustar00rootroot00000000000000#!/usr/bin/env python ''' Epydoc API Runner ------------------ Using pkg_resources, we attempt to see if epydoc is installed, if so, we use its cli program to compile the documents ''' try: import sys, os, shutil import pkg_resources pkg_resources.require("epydoc") from epydoc.cli import cli sys.argv = '''epydoc.py pymodbus --html --simple-term --quiet --include-log --graph=all --docformat=plaintext --debug --exclude=._ --exclude=tests --output=html/ '''.split() #bugs in trunk for --docformat=restructuredtext if not os.path.exists("./html"): os.mkdir("./html") print "Building Epydoc API Documentation" cli() if os.path.exists('../../../build'): shutil.move("html", "../../../build/epydoc") except Exception, ex: import traceback,sys traceback.print_exc(file=sys.stdout) print "Epydoc not avaliable...not building" pymodbus-2.1.0/doc/api/pydoc/000077500000000000000000000000001335513467700160265ustar00rootroot00000000000000pymodbus-2.1.0/doc/api/pydoc/build.py000066400000000000000000000363741335513467700175140ustar00rootroot00000000000000#!/usr/bin/env python """ Pydoc sub-class for generating documentation for entire packages. Taken from: http://pyopengl.sourceforge.net/pydoc/OpenGLContext.pydoc.pydoc2.html Author: Mike Fletcher """ import logging import pydoc, inspect, os, string, shutil import sys, imp, os, stat, re, types, inspect from repr import Repr from string import expandtabs, find, join, lower, split, strip, rfind, rstrip _log = logging.getLogger(__name__) def classify_class_attrs(cls): """Return list of attribute-descriptor tuples. For each name in dir(cls), the return list contains a 4-tuple with these elements: 0. The name (a string). 1. The kind of attribute this is, one of these strings: 'class method' created via classmethod() 'static method' created via staticmethod() 'property' created via property() 'method' any other flavor of method 'data' not a method 2. The class which defined this attribute (a class). 3. The object as obtained directly from the defining class's __dict__, not via getattr. This is especially important for data attributes: C.data is just a data object, but C.__dict__['data'] may be a data descriptor with additional info, like a __doc__ string. Note: This version is patched to work with Zope Interface-bearing objects """ mro = inspect.getmro(cls) names = dir(cls) result = [] for name in names: # Get the object associated with the name. # Getting an obj from the __dict__ sometimes reveals more than # using getattr. Static and class methods are dramatic examples. if name in cls.__dict__: obj = cls.__dict__[name] else: try: obj = getattr(cls, name) except AttributeError, err: continue # Figure out where it was defined. homecls = getattr(obj, "__objclass__", None) if homecls is None: # search the dicts. for base in mro: if name in base.__dict__: homecls = base break # Get the object again, in order to get it from the defining # __dict__ instead of via getattr (if possible). if homecls is not None and name in homecls.__dict__: obj = homecls.__dict__[name] # Also get the object via getattr. obj_via_getattr = getattr(cls, name) # Classify the object. if isinstance(obj, staticmethod): kind = "static method" elif isinstance(obj, classmethod): kind = "class method" elif isinstance(obj, property): kind = "property" elif (inspect.ismethod(obj_via_getattr) or inspect.ismethoddescriptor(obj_via_getattr)): kind = "method" else: kind = "data" result.append((name, kind, homecls, obj)) return result inspect.classify_class_attrs = classify_class_attrs class DefaultFormatter(pydoc.HTMLDoc): def docmodule(self, object, name=None, mod=None, packageContext = None, *ignored): """Produce HTML documentation for a module object.""" name = object.__name__ # ignore the passed-in name parts = split(name, '.') links = [] for i in range(len(parts)-1): links.append( '%s' % (join(parts[:i+1], '.'), parts[i])) linkedname = join(links + parts[-1:], '.') head = '%s' % linkedname try: path = inspect.getabsfile(object) url = path if sys.platform == 'win32': import nturl2path url = nturl2path.pathname2url(path) filelink = '%s' % (url, path) except TypeError: filelink = '(built-in)' info = [] if hasattr(object, '__version__'): version = str(object.__version__) if version[:11] == '$' + 'Revision: ' and version[-1:] == '$': version = strip(version[11:-1]) info.append('version %s' % self.escape(version)) if hasattr(object, '__date__'): info.append(self.escape(str(object.__date__))) if info: head = head + ' (%s)' % join(info, ', ') result = self.heading( head, '#ffffff', '#7799ee', 'index
' + filelink) modules = inspect.getmembers(object, inspect.ismodule) classes, cdict = [], {} for key, value in inspect.getmembers(object, inspect.isclass): if (inspect.getmodule(value) or object) is object: classes.append((key, value)) cdict[key] = cdict[value] = '#' + key for key, value in classes: for base in value.__bases__: key, modname = base.__name__, base.__module__ module = sys.modules.get(modname) if modname != name and module and hasattr(module, key): if getattr(module, key) is base: if not cdict.has_key(key): cdict[key] = cdict[base] = modname + '.html#' + key funcs, fdict = [], {} for key, value in inspect.getmembers(object, inspect.isroutine): if inspect.isbuiltin(value) or inspect.getmodule(value) is object: funcs.append((key, value)) fdict[key] = '#-' + key if inspect.isfunction(value): fdict[value] = fdict[key] data = [] for key, value in inspect.getmembers(object, pydoc.isdata): if key not in ['__builtins__', '__doc__']: data.append((key, value)) doc = self.markup(pydoc.getdoc(object), self.preformat, fdict, cdict) doc = doc and '%s' % doc result = result + '

%s

\n' % doc packageContext.clean ( classes, object ) packageContext.clean ( funcs, object ) packageContext.clean ( data, object ) if hasattr(object, '__path__'): modpkgs = [] modnames = [] for file in os.listdir(object.__path__[0]): path = os.path.join(object.__path__[0], file) modname = inspect.getmodulename(file) if modname and modname not in modnames: modpkgs.append((modname, name, 0, 0)) modnames.append(modname) elif pydoc.ispackage(path): modpkgs.append((file, name, 1, 0)) modpkgs.sort() contents = self.multicolumn(modpkgs, self.modpkglink) ## result = result + self.bigsection( ## 'Package Contents', '#ffffff', '#aa55cc', contents) result = result + self.moduleSection( object, packageContext) elif modules: contents = self.multicolumn( modules, lambda (key, value), s=self: s.modulelink(value)) result = result + self.bigsection( 'Modules', '#fffff', '#aa55cc', contents) if classes: classlist = map(lambda (key, value): value, classes) contents = [ self.formattree(inspect.getclasstree(classlist, 1), name)] for key, value in classes: contents.append(self.document(value, key, name, fdict, cdict)) result = result + self.bigsection( 'Classes', '#ffffff', '#ee77aa', join(contents)) if funcs: contents = [] for key, value in funcs: contents.append(self.document(value, key, name, fdict, cdict)) result = result + self.bigsection( 'Functions', '#ffffff', '#eeaa77', join(contents)) if data: contents = [] for key, value in data: try: contents.append(self.document(value, key)) except Exception, err: pass result = result + self.bigsection( 'Data', '#ffffff', '#55aa55', join(contents, '
\n')) if hasattr(object, '__author__'): contents = self.markup(str(object.__author__), self.preformat) result = result + self.bigsection( 'Author', '#ffffff', '#7799ee', contents) if hasattr(object, '__credits__'): contents = self.markup(str(object.__credits__), self.preformat) result = result + self.bigsection( 'Credits', '#ffffff', '#7799ee', contents) return result def classlink(self, object, modname): """Make a link for a class.""" name, module = object.__name__, sys.modules.get(object.__module__) if hasattr(module, name) and getattr(module, name) is object: return '%s' % ( module.__name__, name, name ) return pydoc.classname(object, modname) def moduleSection( self, object, packageContext ): """Create a module-links section for the given object (module)""" modules = inspect.getmembers(object, inspect.ismodule) packageContext.clean ( modules, object ) packageContext.recurseScan( modules ) if hasattr(object, '__path__'): modpkgs = [] modnames = [] for file in os.listdir(object.__path__[0]): path = os.path.join(object.__path__[0], file) modname = inspect.getmodulename(file) if modname and modname not in modnames: modpkgs.append((modname, object.__name__, 0, 0)) modnames.append(modname) elif pydoc.ispackage(path): modpkgs.append((file, object.__name__, 1, 0)) modpkgs.sort() # do more recursion here... for (modname, name, ya,yo) in modpkgs: packageContext.addInteresting( join( (object.__name__, modname), '.')) items = [] for (modname, name, ispackage,isshadowed) in modpkgs: try: # get the actual module object... ## if modname == "events": ## import pdb ## pdb.set_trace() module = pydoc.safeimport( "%s.%s"%(name,modname) ) description, documentation = pydoc.splitdoc( inspect.getdoc( module )) if description: items.append( """%s -- %s"""% ( self.modpkglink( (modname, name, ispackage, isshadowed) ), description, ) ) else: items.append( self.modpkglink( (modname, name, ispackage, isshadowed) ) ) except: items.append( self.modpkglink( (modname, name, ispackage, isshadowed) ) ) contents = string.join( items, '
') result = self.bigsection( 'Package Contents', '#ffffff', '#aa55cc', contents) elif modules: contents = self.multicolumn( modules, lambda (key, value), s=self: s.modulelink(value)) result = self.bigsection( 'Modules', '#fffff', '#aa55cc', contents) else: result = "" return result class AlreadyDone(Exception): pass class PackageDocumentationGenerator: """A package document generator creates documentation for an entire package using pydoc's machinery. baseModules -- modules which will be included and whose included and children modules will be considered fair game for documentation destinationDirectory -- the directory into which the HTML documentation will be written recursion -- whether to add modules which are referenced by and/or children of base modules exclusions -- a list of modules whose contents will not be shown in any other module, commonly such modules as OpenGL.GL, wxPython.wx etc. recursionStops -- a list of modules which will explicitly stop recursion (i.e. they will never be included), even if they are children of base modules. formatter -- allows for passing in a custom formatter see DefaultFormatter for sample implementation. """ def __init__ ( self, baseModules, destinationDirectory = ".", recursion = 1, exclusions = (), recursionStops = (), formatter = None ): self.destinationDirectory = os.path.abspath( destinationDirectory) self.exclusions = {} self.warnings = [] self.baseSpecifiers = {} self.completed = {} self.recursionStops = {} self.recursion = recursion for stop in recursionStops: self.recursionStops[ stop ] = 1 self.pending = [] for exclusion in exclusions: try: self.exclusions[ exclusion ]= pydoc.locate ( exclusion) except pydoc.ErrorDuringImport, value: self.warn( """Unable to import the module %s which was specified as an exclusion module"""% (repr(exclusion))) self.formatter = formatter or DefaultFormatter() for base in baseModules: self.addBase( base ) def warn( self, message ): """Warnings are used for recoverable, but not necessarily ignorable conditions""" self.warnings.append (message) def info (self, message): """Information/status report""" _log.debug(message) def addBase(self, specifier): """Set the base of the documentation set, only children of these modules will be documented""" try: self.baseSpecifiers [specifier] = pydoc.locate ( specifier) self.pending.append (specifier) except pydoc.ErrorDuringImport, value: self.warn( """Unable to import the module %s which was specified as a base module"""% (repr(specifier))) def addInteresting( self, specifier): """Add a module to the list of interesting modules""" if self.checkScope( specifier): self.pending.append (specifier) else: self.completed[ specifier] = 1 def checkScope (self, specifier): """Check that the specifier is "in scope" for the recursion""" if not self.recursion: return 0 items = string.split (specifier, ".") stopCheck = items [:] while stopCheck: name = string.join(items, ".") if self.recursionStops.get( name): return 0 elif self.completed.get (name): return 0 del stopCheck[-1] while items: if self.baseSpecifiers.get( string.join(items, ".")): return 1 del items[-1] # was not within any given scope return 0 def process( self ): """Having added all of the base and/or interesting modules, proceed to generate the appropriate documentation for each module in the appropriate directory, doing the recursion as we go.""" try: while self.pending: try: if self.completed.has_key( self.pending[0] ): raise AlreadyDone( self.pending[0] ) self.info( """Start %s"""% (repr(self.pending[0]))) object = pydoc.locate ( self.pending[0] ) self.info( """ ... found %s"""% (repr(object.__name__))) except AlreadyDone: pass except pydoc.ErrorDuringImport, value: self.info( """ ... FAILED %s"""% (repr( value))) self.warn( """Unable to import the module %s"""% (repr(self.pending[0]))) except (SystemError, SystemExit), value: self.info( """ ... FAILED %s"""% (repr( value))) self.warn( """Unable to import the module %s"""% (repr(self.pending[0]))) except Exception, value: self.info( """ ... FAILED %s"""% (repr( value))) self.warn( """Unable to import the module %s"""% (repr(self.pending[0]))) else: page = self.formatter.page( pydoc.describe(object), self.formatter.docmodule( object, object.__name__, packageContext = self, ) ) file = open ( os.path.join( self.destinationDirectory, self.pending[0] + ".html", ), 'w', ) file.write(page) file.close() self.completed[ self.pending[0]] = object del self.pending[0] finally: for item in self.warnings: _log.info(item) def clean (self, objectList, object): """callback from the formatter object asking us to remove those items in the key, value pairs where the object is imported from one of the excluded modules""" for key, value in objectList[:]: for excludeObject in self.exclusions.values(): if hasattr( excludeObject, key ) and excludeObject is not object: if ( getattr( excludeObject, key) is value or (hasattr( excludeObject, '__name__') and excludeObject.__name__ == "Numeric" ) ): objectList[:] = [ (k,o) for k,o in objectList if k != key ] def recurseScan(self, objectList): """Process the list of modules trying to add each to the list of interesting modules""" for key, value in objectList: self.addInteresting( value.__name__ ) #---------------------------------------------------------------------------# # Main Runner #---------------------------------------------------------------------------# if __name__ == "__main__": if not os.path.exists("./html"): os.mkdir("./html") print "Building Pydoc API Documentation" PackageDocumentationGenerator( baseModules = ['pymodbus', '__builtin__'], destinationDirectory = "./html/", exclusions = ['math', 'string', 'twisted'], recursionStops = [], ).process () if os.path.exists('../../../build'): shutil.move("html", "../../../build/pydoc") pymodbus-2.1.0/doc/api/pydoctor/000077500000000000000000000000001335513467700165535ustar00rootroot00000000000000pymodbus-2.1.0/doc/api/pydoctor/build.py000077500000000000000000000014401335513467700202260ustar00rootroot00000000000000#!/usr/bin/env python ''' Pydoctor API Runner --------------------- Using pkg_resources, we attempt to see if pydoctor is installed, if so, we use its cli program to compile the documents ''' try: import sys, os, shutil import pkg_resources pkg_resources.require("pydoctor") from pydoctor.driver import main sys.argv = '''pydoctor.py --quiet --project-name=Pymodbus --project-url=http://code.google.com/p/pymodbus/ --add-package=../../../pymodbus --html-output=html --html-write-function-pages --make-html'''.split() print "Building Pydoctor API Documentation" main(sys.argv[1:]) if os.path.exists('../../../build'): shutil.move("html", "../../../build/pydoctor") except: print "Pydoctor unavailable...not building" pymodbus-2.1.0/doc/changelog.rst000066400000000000000000000001031335513467700166120ustar00rootroot00000000000000============ CHANGELOGS ============ .. include:: ../CHANGELOG.rstpymodbus-2.1.0/doc/conf.py000066400000000000000000000137321335513467700154440ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # PyModbus documentation build configuration file, created by # sphinx-quickstart on Wed Dec 20 12:31:10 2017. # # 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. # # All configuration values have a default; values that are commented out # serve to show the default. # 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. # import os import sys import recommonmark from recommonmark.parser import CommonMarkParser from recommonmark.transform import AutoStructify from pymodbus import __version__ parent_dir = os.path.abspath(os.pardir) # examples = os.path.join(parent_dir, "examples") example_contrib = os.path.join(parent_dir, "examples/contrib") example_common = os.path.join(parent_dir, "examples/common") example_gui = os.path.join(parent_dir, "examples/gui") sys.path.insert(0, os.path.abspath(os.pardir)) sys.path.append(example_common) sys.path.append(example_contrib) sys.path.append(example_gui) # sys.path.extend([examples, example_common, example_contrib, example_gui]) # sys.path.insert(0, os.path.abspath('../')) github_doc_root = 'https://github.com/riptideio/pymodbus/tree/master/doc/' # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = ['sphinx.ext.autodoc', 'recommonmark'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_parsers = { '.md': CommonMarkParser, } source_suffix = ['.rst', '.md'] # source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = u'PyModbus' copyright = u'2017, Sanjay' author = u'Sanjay' # 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 = __version__ # The full version, including alpha/beta/rc tags. release = __version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'sphinx_rtd_theme' # 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 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'] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # This is required for the alabaster theme # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars html_sidebars = { '**': [ 'relations.html', # needs 'show_related': True theme option to display 'searchbox.html', ] } # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. htmlhelp_basename = 'PyModbusdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'PyModbus.tex', u'PyModbus Documentation', u'Sanjay', 'manual'), ] # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'pymodbus', u'PyModbus Documentation', [author], 1) ] # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'PyModbus', u'PyModbus Documentation', author, 'PyModbus', 'One line description of project.', 'Miscellaneous'), ] def setup(app): app.add_config_value('recommonmark_config', { 'url_resolver': lambda url: github_doc_root + url, 'auto_toc_tree_section': 'Contents', }, True) app.add_transform(AutoStructify) pymodbus-2.1.0/doc/index.rst000066400000000000000000000010521335513467700157760ustar00rootroot00000000000000.. PyModbus documentation master file, created by sphinx-quickstart on Wed Dec 20 12:31:10 2017. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to PyModbus's documentation! ==================================== .. toctree:: :maxdepth: 2 :caption: Contents: readme.rst changelog.rst source/library/REPL source/example/modules.rst source/library/modules.rst Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` pymodbus-2.1.0/doc/readme.rst000066400000000000000000000000321335513467700161210ustar00rootroot00000000000000.. include:: ../README.rstpymodbus-2.1.0/doc/source/000077500000000000000000000000001335513467700154375ustar00rootroot00000000000000pymodbus-2.1.0/doc/source/example/000077500000000000000000000000001335513467700170725ustar00rootroot00000000000000pymodbus-2.1.0/doc/source/example/async_asyncio_client.rst000066400000000000000000000003071335513467700240240ustar00rootroot00000000000000================================================== Async Asyncio Client Example ================================================== .. literalinclude:: ../../../examples/common/async_asyncio_client.pypymodbus-2.1.0/doc/source/example/async_asyncio_serial_client.rst000066400000000000000000000003251335513467700253630ustar00rootroot00000000000000================================================== Async Asyncio Serial Client Example ================================================== .. literalinclude:: ../../../examples/common/async_asyncio_serial_client.pypymodbus-2.1.0/doc/source/example/async_tornado_client.rst000066400000000000000000000003071335513467700240250ustar00rootroot00000000000000================================================== Async Tornado Client Example ================================================== .. literalinclude:: ../../../examples/common/async_tornado_client.pypymodbus-2.1.0/doc/source/example/async_tornado_client_serial.rst000066400000000000000000000003251335513467700253640ustar00rootroot00000000000000================================================== Async Tornado Client Serial Example ================================================== .. literalinclude:: ../../../examples/common/async_tornado_client_serial.pypymodbus-2.1.0/doc/source/example/async_twisted_client.rst000066400000000000000000000003071335513467700240420ustar00rootroot00000000000000================================================== Async Twisted Client Example ================================================== .. literalinclude:: ../../../examples/common/async_twisted_client.pypymodbus-2.1.0/doc/source/example/async_twisted_client_serial.rst000066400000000000000000000003251335513467700254010ustar00rootroot00000000000000================================================== Async Twisted Client Serial Example ================================================== .. literalinclude:: ../../../examples/common/async_twisted_client_serial.pypymodbus-2.1.0/doc/source/example/asynchronous_asyncio_serial_client.rst000066400000000000000000000003441335513467700270020ustar00rootroot00000000000000================================================== Asynchronous Asyncio Serial Client Example ================================================== .. literalinclude:: ../../../examples/contrib/asynchronous_asyncio_serial_client.pypymodbus-2.1.0/doc/source/example/asynchronous_processor.rst000066400000000000000000000003131335513467700244530ustar00rootroot00000000000000================================================== Asynchronous Processor Example ================================================== .. literalinclude:: ../../../examples/common/asynchronous_processor.pypymodbus-2.1.0/doc/source/example/asynchronous_server.rst000066400000000000000000000002301335513467700237400ustar00rootroot00000000000000=========================== Asynchronous Server Example =========================== .. literalinclude:: ../../../examples/common/asynchronous_server.py pymodbus-2.1.0/doc/source/example/bcd_payload.rst000066400000000000000000000002661335513467700220710ustar00rootroot00000000000000================================================== Bcd Payload Example ================================================== .. literalinclude:: ../../../examples/contrib/bcd_payload.pypymodbus-2.1.0/doc/source/example/callback_server.rst000066400000000000000000000002171335513467700227460ustar00rootroot00000000000000=========================== Callback Server Example =========================== .. literalinclude:: ../../../examples/common/callback_server.pypymodbus-2.1.0/doc/source/example/changing_framers.rst000066400000000000000000000002771335513467700231270ustar00rootroot00000000000000================================================== Changing Framers Example ================================================== .. literalinclude:: ../../../examples/common/changing_framers.pypymodbus-2.1.0/doc/source/example/concurrent_client.rst000066400000000000000000000003021335513467700233370ustar00rootroot00000000000000================================================== Concurrent Client Example ================================================== .. literalinclude:: ../../../examples/contrib/concurrent_client.pypymodbus-2.1.0/doc/source/example/custom_datablock.rst000066400000000000000000000002771335513467700231500ustar00rootroot00000000000000================================================== Custom Datablock Example ================================================== .. literalinclude:: ../../../examples/common/custom_datablock.pypymodbus-2.1.0/doc/source/example/custom_message.rst000066400000000000000000000002731335513467700226440ustar00rootroot00000000000000================================================== Custom Message Example ================================================== .. literalinclude:: ../../../examples/common/custom_message.pypymodbus-2.1.0/doc/source/example/dbstore_update_server.rst000066400000000000000000000003111335513467700242110ustar00rootroot00000000000000================================================== Dbstore Update Server Example ================================================== .. literalinclude:: ../../../examples/common/dbstore_update_server.pypymodbus-2.1.0/doc/source/example/gui_common.rst000066400000000000000000000002601335513467700217560ustar00rootroot00000000000000================================================== Gui Common Example ================================================== .. literalinclude:: ../../../examples/gui/gui_common.pypymodbus-2.1.0/doc/source/example/libmodbus_client.rst000066400000000000000000000003001335513467700231330ustar00rootroot00000000000000================================================== Libmodbus Client Example ================================================== .. literalinclude:: ../../../examples/contrib/libmodbus_client.pypymodbus-2.1.0/doc/source/example/message_generator.rst000066400000000000000000000003021335513467700233110ustar00rootroot00000000000000================================================== Message Generator Example ================================================== .. literalinclude:: ../../../examples/contrib/message_generator.pypymodbus-2.1.0/doc/source/example/message_parser.rst000066400000000000000000000002741335513467700226270ustar00rootroot00000000000000================================================== Message Parser Example ================================================== .. literalinclude:: ../../../examples/contrib/message_parser.pypymodbus-2.1.0/doc/source/example/modbus_logging.rst000066400000000000000000000002731335513467700226250ustar00rootroot00000000000000================================================== Modbus Logging Example ================================================== .. literalinclude:: ../../../examples/common/modbus_logging.pypymodbus-2.1.0/doc/source/example/modbus_mapper.rst000066400000000000000000000002721335513467700224620ustar00rootroot00000000000000================================================== Modbus Mapper Example ================================================== .. literalinclude:: ../../../examples/contrib/modbus_mapper.pypymodbus-2.1.0/doc/source/example/modbus_payload.rst000066400000000000000000000002731335513467700226300ustar00rootroot00000000000000================================================== Modbus Payload Example ================================================== .. literalinclude:: ../../../examples/common/modbus_payload.pypymodbus-2.1.0/doc/source/example/modbus_payload_server.rst000066400000000000000000000003111335513467700242070ustar00rootroot00000000000000================================================== Modbus Payload Server Example ================================================== .. literalinclude:: ../../../examples/common/modbus_payload_server.pypymodbus-2.1.0/doc/source/example/modbus_saver.rst000066400000000000000000000002701335513467700223140ustar00rootroot00000000000000================================================== Modbus Saver Example ================================================== .. literalinclude:: ../../../examples/contrib/modbus_saver.pypymodbus-2.1.0/doc/source/example/modbus_scraper.rst000066400000000000000000000002741335513467700226370ustar00rootroot00000000000000================================================== Modbus Scraper Example ================================================== .. literalinclude:: ../../../examples/contrib/modbus_scraper.pypymodbus-2.1.0/doc/source/example/modbus_simulator.rst000066400000000000000000000003001335513467700232050ustar00rootroot00000000000000================================================== Modbus Simulator Example ================================================== .. literalinclude:: ../../../examples/contrib/modbus_simulator.pypymodbus-2.1.0/doc/source/example/modicon_payload.rst000066400000000000000000000002761335513467700227720ustar00rootroot00000000000000================================================== Modicon Payload Example ================================================== .. literalinclude:: ../../../examples/contrib/modicon_payload.pypymodbus-2.1.0/doc/source/example/modules.rst000066400000000000000000000015721335513467700213010ustar00rootroot00000000000000=================== Examples =================== === .. toctree:: :maxdepth: 4 async_asyncio_client async_asyncio_serial_client async_tornado_client async_tornado_client_serial async_twisted_client async_twisted_client_serial asynchronous_processor asynchronous_server callback_server changing_framers custom_datablock custom_message dbstore_update_server modbus_logging modbus_payload modbus_payload_server performance synchronous_client synchronous_client_ext synchronous_server updating_server asynchronous_asyncio_serial_client bcd_payload concurrent_client libmodbus_client message_generator message_parser modbus_mapper modbus_saver modbus_scraper modbus_simulator modicon_payload remote_server_context serial_forwarder sunspec_client thread_safe_datastore gui_common pymodbus-2.1.0/doc/source/example/performance.rst000066400000000000000000000001661335513467700221300ustar00rootroot00000000000000================== performance module ================== .. literalinclude:: ../../../examples/common/performance.py pymodbus-2.1.0/doc/source/example/remote_server_context.rst000066400000000000000000000003121335513467700242450ustar00rootroot00000000000000================================================== Remote Server Context Example ================================================== .. literalinclude:: ../../../examples/contrib/remote_server_context.pypymodbus-2.1.0/doc/source/example/serial_forwarder.rst000066400000000000000000000003001335513467700231470ustar00rootroot00000000000000================================================== Serial Forwarder Example ================================================== .. literalinclude:: ../../../examples/contrib/serial_forwarder.pypymodbus-2.1.0/doc/source/example/sunspec_client.rst000066400000000000000000000002741335513467700226450ustar00rootroot00000000000000================================================== Sunspec Client Example ================================================== .. literalinclude:: ../../../examples/contrib/sunspec_client.pypymodbus-2.1.0/doc/source/example/synchronous_client.rst000066400000000000000000000003031335513467700235500ustar00rootroot00000000000000================================================== Synchronous Client Example ================================================== .. literalinclude:: ../../../examples/common/synchronous_client.pypymodbus-2.1.0/doc/source/example/synchronous_client_ext.rst000066400000000000000000000003131335513467700244310ustar00rootroot00000000000000================================================== Synchronous Client Ext Example ================================================== .. literalinclude:: ../../../examples/common/synchronous_client_ext.pypymodbus-2.1.0/doc/source/example/synchronous_server.rst000066400000000000000000000003031335513467700236000ustar00rootroot00000000000000================================================== Synchronous Server Example ================================================== .. literalinclude:: ../../../examples/common/synchronous_server.pypymodbus-2.1.0/doc/source/example/thread_safe_datastore.rst000066400000000000000000000003121335513467700241330ustar00rootroot00000000000000================================================== Thread Safe Datastore Example ================================================== .. literalinclude:: ../../../examples/contrib/thread_safe_datastore.pypymodbus-2.1.0/doc/source/example/updating_server.rst000066400000000000000000000002751335513467700230310ustar00rootroot00000000000000================================================== Updating Server Example ================================================== .. literalinclude:: ../../../examples/common/updating_server.pypymodbus-2.1.0/doc/source/library/000077500000000000000000000000001335513467700171035ustar00rootroot00000000000000pymodbus-2.1.0/doc/source/library/REPL.md000066400000000000000000000347151335513467700202010ustar00rootroot00000000000000# Pymodbus REPL ## Dependencies Depends on [prompt_toolkit](https://python-prompt-toolkit.readthedocs.io/en/stable/index.html) and [click](http://click.pocoo.org/6/quickstart/) Install dependencies ``` $ pip install click prompt_toolkit --upgarde ``` Or Install pymodbus with repl support ``` $ pip install pymodbus[repl] --upgrade ``` ## Usage Instructions RTU and TCP are supported as of now ``` bash-3.2$ pymodbus.console Usage: pymodbus.console [OPTIONS] COMMAND [ARGS]... Options: --version Show the version and exit. --verbose Verbose logs --support-diag Support Diagnostic messages --help Show this message and exit. Commands: serial tcp ``` TCP Options ``` bash-3.2$ pymodbus.console tcp --help Usage: pymodbus.console tcp [OPTIONS] Options: --host TEXT Modbus TCP IP --port INTEGER Modbus TCP port --help Show this message and exit. ``` SERIAL Options ``` bash-3.2$ pymodbus.console serial --help Usage: pymodbus.console serial [OPTIONS] Options: --method TEXT Modbus Serial Mode (rtu/ascii) --port TEXT Modbus RTU port --baudrate INTEGER Modbus RTU serial baudrate to use. Defaults to 9600 --bytesize [5|6|7|8] Modbus RTU serial Number of data bits. Possible values: FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS. Defaults to 8 --parity [N|E|O|M|S] Modbus RTU serial parity. Enable parity checking. Possible values: PARITY_NONE, PARITY_EVEN, PARITY_ODD PARITY_MARK, PARITY_SPACE. Default to 'N' --stopbits [1|1.5|2] Modbus RTU serial stop bits. Number of stop bits. Possible values: STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO. Default to '1' --xonxoff INTEGER Modbus RTU serial xonxoff. Enable software flow control.Defaults to 0 --rtscts INTEGER Modbus RTU serial rtscts. Enable hardware (RTS/CTS) flow control. Defaults to 0 --dsrdtr INTEGER Modbus RTU serial dsrdtr. Enable hardware (DSR/DTR) flow control. Defaults to 0 --timeout FLOAT Modbus RTU serial read timeout. Defaults to 0.025 sec --write-timeout FLOAT Modbus RTU serial write timeout. Defaults to 2 sec --help Show this message and exit. ``` To view all available commands type `help` TCP ``` $ pymodbus.console tcp --host 192.168.128.126 --port 5020 > help Available commands: client.change_ascii_input_delimiter Diagnostic sub command, Change message delimiter for future requests. client.clear_counters Diagnostic sub command, Clear all counters and diag registers. client.clear_overrun_count Diagnostic sub command, Clear over run counter. client.close Closes the underlying socket connection client.connect Connect to the modbus tcp server client.debug_enabled Returns a boolean indicating if debug is enabled. client.force_listen_only_mode Diagnostic sub command, Forces the addressed remote device to its Listen Only Mode. client.get_clear_modbus_plus Diagnostic sub command, Get or clear stats of remote modbus plus device. client.get_com_event_counter Read status word and an event count from the remote device's communication event counter. client.get_com_event_log Read status word, event count, message count, and a field of event bytes from the remote device. client.host Read Only! client.idle_time Bus Idle Time to initiate next transaction client.is_socket_open Check whether the underlying socket/serial is open or not. client.last_frame_end Read Only! client.mask_write_register Mask content of holding register at `address` with `and_mask` and `or_mask`. client.port Read Only! client.read_coils Reads `count` coils from a given slave starting at `address`. client.read_device_information Read the identification and additional information of remote slave. client.read_discrete_inputs Reads `count` number of discrete inputs starting at offset `address`. client.read_exception_status Read the contents of eight Exception Status outputs in a remote device. client.read_holding_registers Read `count` number of holding registers starting at `address`. client.read_input_registers Read `count` number of input registers starting at `address`. client.readwrite_registers Read `read_count` number of holding registers starting at `read_address` and write `write_registers` starting at `write_address`. client.report_slave_id Report information about remote slave ID. client.restart_comm_option Diagnostic sub command, initialize and restart remote devices serial interface and clear all of its communications event counters . client.return_bus_com_error_count Diagnostic sub command, Return count of CRC errors received by remote slave. client.return_bus_exception_error_count Diagnostic sub command, Return count of Modbus exceptions returned by remote slave. client.return_bus_message_count Diagnostic sub command, Return count of message detected on bus by remote slave. client.return_diagnostic_register Diagnostic sub command, Read 16-bit diagnostic register. client.return_iop_overrun_count Diagnostic sub command, Return count of iop overrun errors by remote slave. client.return_query_data Diagnostic sub command , Loop back data sent in response. client.return_slave_bus_char_overrun_count Diagnostic sub command, Return count of messages not handled by remote slave due to character overrun condition. client.return_slave_busy_count Diagnostic sub command, Return count of server busy exceptions sent by remote slave. client.return_slave_message_count Diagnostic sub command, Return count of messages addressed to remote slave. client.return_slave_no_ack_count Diagnostic sub command, Return count of NO ACK exceptions sent by remote slave. client.return_slave_no_response_count Diagnostic sub command, Return count of No responses by remote slave. client.silent_interval Read Only! client.state Read Only! client.timeout Read Only! client.write_coil Write `value` to coil at `address`. client.write_coils Write `value` to coil at `address`. client.write_register Write `value` to register at `address`. client.write_registers Write list of `values` to registers starting at `address`. ``` SERIAL ``` $ pymodbus.console serial --port /dev/ttyUSB0 --baudrate 19200 --timeout 2 > help Available commands: client.baudrate Read Only! client.bytesize Read Only! client.change_ascii_input_delimiter Diagnostic sub command, Change message delimiter for future requests. client.clear_counters Diagnostic sub command, Clear all counters and diag registers. client.clear_overrun_count Diagnostic sub command, Clear over run counter. client.close Closes the underlying socket connection client.connect Connect to the modbus serial server client.debug_enabled Returns a boolean indicating if debug is enabled. client.force_listen_only_mode Diagnostic sub command, Forces the addressed remote device to its Listen Only Mode. client.get_baudrate Serial Port baudrate. client.get_bytesize Number of data bits. client.get_clear_modbus_plus Diagnostic sub command, Get or clear stats of remote modbus plus device. client.get_com_event_counter Read status word and an event count from the remote device's communication event counter. client.get_com_event_log Read status word, event count, message count, and a field of event bytes from the remote device. client.get_parity Enable Parity Checking. client.get_port Serial Port. client.get_serial_settings Gets Current Serial port settings. client.get_stopbits Number of stop bits. client.get_timeout Serial Port Read timeout. client.idle_time Bus Idle Time to initiate next transaction client.inter_char_timeout Read Only! client.is_socket_open c l i e n t . i s s o c k e t o p e n client.mask_write_register Mask content of holding register at `address` with `and_mask` and `or_mask`. client.method Read Only! client.parity Read Only! client.port Read Only! client.read_coils Reads `count` coils from a given slave starting at `address`. client.read_device_information Read the identification and additional information of remote slave. client.read_discrete_inputs Reads `count` number of discrete inputs starting at offset `address`. client.read_exception_status Read the contents of eight Exception Status outputs in a remote device. client.read_holding_registers Read `count` number of holding registers starting at `address`. client.read_input_registers Read `count` number of input registers starting at `address`. client.readwrite_registers Read `read_count` number of holding registers starting at `read_address` and write `write_registers` starting at `write_address`. client.report_slave_id Report information about remote slave ID. client.restart_comm_option Diagnostic sub command, initialize and restart remote devices serial interface and clear all of its communications event counters . client.return_bus_com_error_count Diagnostic sub command, Return count of CRC errors received by remote slave. client.return_bus_exception_error_count Diagnostic sub command, Return count of Modbus exceptions returned by remote slave. client.return_bus_message_count Diagnostic sub command, Return count of message detected on bus by remote slave. client.return_diagnostic_register Diagnostic sub command, Read 16-bit diagnostic register. client.return_iop_overrun_count Diagnostic sub command, Return count of iop overrun errors by remote slave. client.return_query_data Diagnostic sub command , Loop back data sent in response. client.return_slave_bus_char_overrun_count Diagnostic sub command, Return count of messages not handled by remote slave due to character overrun condition. client.return_slave_busy_count Diagnostic sub command, Return count of server busy exceptions sent by remote slave. client.return_slave_message_count Diagnostic sub command, Return count of messages addressed to remote slave. client.return_slave_no_ack_count Diagnostic sub command, Return count of NO ACK exceptions sent by remote slave. client.return_slave_no_response_count Diagnostic sub command, Return count of No responses by remote slave. client.set_baudrate Baudrate setter. client.set_bytesize Byte size setter. client.set_parity Parity Setter. client.set_port Serial Port setter. client.set_stopbits Stop bit setter. client.set_timeout Read timeout setter. client.silent_interval Read Only! client.state Read Only! client.stopbits Read Only! client.timeout Read Only! client.write_coil Write `value` to coil at `address`. client.write_coils Write `value` to coil at `address`. client.write_register Write `value` to register at `address`. client.write_registers Write list of `values` to registers starting at `address`. result.decode Decode the register response to known formatters. result.raw Return raw result dict. ``` Every command has auto suggetion on the arguments supported , supply arg and value are to be supplied in `arg=val` format. ``` > client.read_holding_registers count=4 address=9 unit=1 { "registers": [ 60497, 47134, 34091, 15424 ] } ``` The last result could be accessed with `result.raw` command ``` > result.raw { "registers": [ 15626, 55203, 28733, 18368 ] } ``` For Holding and Input register reads, the decoded value could be viewed with `result.decode` ``` > result.decode word_order=little byte_order=little formatters=float64 28.17 > ``` Client settings could be retrieved and altered as well. ``` > # For serial settings > # Check the serial mode > client.method "rtu" > client.get_serial_settings { "t1.5": 0.00171875, "baudrate": 9600, "read timeout": 0.5, "port": "/dev/ptyp0", "t3.5": 0.00401, "bytesize": 8, "parity": "N", "stopbits": 1.0 } > client.set_timeout value=1 null > client.get_timeout 1.0 > client.get_serial_settings { "t1.5": 0.00171875, "baudrate": 9600, "read timeout": 1.0, "port": "/dev/ptyp0", "t3.5": 0.00401, "bytesize": 8, "parity": "N", "stopbits": 1.0 } ``` ## DEMO [![asciicast](https://asciinema.org/a/y1xOk7lm59U1bRBE2N1pDIj2o.png)](https://asciinema.org/a/y1xOk7lm59U1bRBE2N1pDIj2o) [![asciicast](https://asciinema.org/a/edUqZN77fdjxL2toisiilJNwI.png)](https://asciinema.org/a/edUqZN77fdjxL2toisiilJNwI) pymodbus-2.1.0/doc/source/library/modules.rst000066400000000000000000000000751335513467700213070ustar00rootroot00000000000000Pymodbus ======== .. toctree:: :maxdepth: 4 pymodbus pymodbus-2.1.0/doc/source/library/pymodbus.client.async.asyncio.rst000066400000000000000000000002731335513467700255360ustar00rootroot00000000000000pymodbus\.client\.async\.asyncio package ======================================== .. automodule:: pymodbus.client.async.asyncio :members: :undoc-members: :show-inheritance: pymodbus-2.1.0/doc/source/library/pymodbus.client.async.factory.rst000066400000000000000000000014611335513467700255400ustar00rootroot00000000000000pymodbus\.client\.async\.factory package ======================================== .. automodule:: pymodbus.client.async.factory :members: :undoc-members: :show-inheritance: Submodules ---------- pymodbus\.client\.async\.factory\.serial module ----------------------------------------------- .. automodule:: pymodbus.client.async.factory.serial :members: :undoc-members: :show-inheritance: pymodbus\.client\.async\.factory\.tcp module -------------------------------------------- .. automodule:: pymodbus.client.async.factory.tcp :members: :undoc-members: :show-inheritance: pymodbus\.client\.async\.factory\.udp module -------------------------------------------- .. automodule:: pymodbus.client.async.factory.udp :members: :undoc-members: :show-inheritance: pymodbus-2.1.0/doc/source/library/pymodbus.client.async.rst000066400000000000000000000021241335513467700240670ustar00rootroot00000000000000pymodbus\.client\.async package =============================== .. automodule:: pymodbus.client.async :members: :undoc-members: :show-inheritance: Subpackages ----------- .. toctree:: pymodbus.client.async.asyncio pymodbus.client.async.factory pymodbus.client.async.schedulers pymodbus.client.async.tornado pymodbus.client.async.twisted Submodules ---------- pymodbus\.client\.async\.serial module -------------------------------------- .. automodule:: pymodbus.client.async.serial :members: :undoc-members: :show-inheritance: pymodbus\.client\.async\.tcp module ----------------------------------- .. automodule:: pymodbus.client.async.tcp :members: :undoc-members: :show-inheritance: pymodbus\.client\.async\.thread module -------------------------------------- .. automodule:: pymodbus.client.async.thread :members: :undoc-members: :show-inheritance: pymodbus\.client\.async\.udp module ----------------------------------- .. automodule:: pymodbus.client.async.udp :members: :undoc-members: :show-inheritance: pymodbus-2.1.0/doc/source/library/pymodbus.client.async.schedulers.rst000066400000000000000000000003041335513467700262250ustar00rootroot00000000000000pymodbus\.client\.async\.schedulers package =========================================== .. automodule:: pymodbus.client.async.schedulers :members: :undoc-members: :show-inheritance: pymodbus-2.1.0/doc/source/library/pymodbus.client.async.tornado.rst000066400000000000000000000002731335513467700255370ustar00rootroot00000000000000pymodbus\.client\.async\.tornado package ======================================== .. automodule:: pymodbus.client.async.tornado :members: :undoc-members: :show-inheritance: pymodbus-2.1.0/doc/source/library/pymodbus.client.async.twisted.rst000066400000000000000000000002731335513467700255540ustar00rootroot00000000000000pymodbus\.client\.async\.twisted package ======================================== .. automodule:: pymodbus.client.async.twisted :members: :undoc-members: :show-inheritance: pymodbus-2.1.0/doc/source/library/pymodbus.client.rst000066400000000000000000000010451335513467700227540ustar00rootroot00000000000000pymodbus\.client package ======================== .. automodule:: pymodbus.client :members: :undoc-members: :show-inheritance: Subpackages ----------- .. toctree:: pymodbus.client.async Submodules ---------- pymodbus\.client\.common module ------------------------------- .. automodule:: pymodbus.client.common :members: :undoc-members: :show-inheritance: pymodbus\.client\.sync module ----------------------------- .. automodule:: pymodbus.client.sync :members: :undoc-members: :show-inheritance: pymodbus-2.1.0/doc/source/library/pymodbus.datastore.database.rst000066400000000000000000000012171335513467700252300ustar00rootroot00000000000000pymodbus\.datastore\.database package ===================================== .. automodule:: pymodbus.datastore.database :members: :undoc-members: :show-inheritance: Submodules ---------- pymodbus\.datastore\.database\.redis\_datastore module ------------------------------------------------------ .. automodule:: pymodbus.datastore.database.redis_datastore :members: :undoc-members: :show-inheritance: pymodbus\.datastore\.database\.sql\_datastore module ---------------------------------------------------- .. automodule:: pymodbus.datastore.database.sql_datastore :members: :undoc-members: :show-inheritance: pymodbus-2.1.0/doc/source/library/pymodbus.datastore.rst000066400000000000000000000013671335513467700234730ustar00rootroot00000000000000pymodbus\.datastore package =========================== .. automodule:: pymodbus.datastore :members: :undoc-members: :show-inheritance: Subpackages ----------- .. toctree:: pymodbus.datastore.database Submodules ---------- pymodbus\.datastore\.context module ----------------------------------- .. automodule:: pymodbus.datastore.context :members: :undoc-members: :show-inheritance: pymodbus\.datastore\.remote module ---------------------------------- .. automodule:: pymodbus.datastore.remote :members: :undoc-members: :show-inheritance: pymodbus\.datastore\.store module --------------------------------- .. automodule:: pymodbus.datastore.store :members: :undoc-members: :show-inheritance: pymodbus-2.1.0/doc/source/library/pymodbus.framer.rst000066400000000000000000000016261335513467700227570ustar00rootroot00000000000000pymodbus\.framer package ======================== Submodules ---------- pymodbus\.framer\.ascii_framer module ------------------------------------- .. automodule:: pymodbus.framer.ascii_framer :members: :undoc-members: :show-inheritance: pymodbus\.framer\.binary_framer module -------------------------------------- .. automodule:: pymodbus.framer.binary_framer :members: :undoc-members: :show-inheritance: pymodbus\.framer\.rtu_framer module ----------------------------------- .. automodule:: pymodbus.framer.rtu_framer :members: :undoc-members: :show-inheritance: pymodbus\.framer\.socket_framer module -------------------------------------- .. automodule:: pymodbus.framer.socket_framer :members: :undoc-members: :show-inheritance: Module contents --------------- .. automodule:: pymodbus.framer :members: :undoc-members: :show-inheritance: pymodbus-2.1.0/doc/source/library/pymodbus.internal.rst000066400000000000000000000005311335513467700233110ustar00rootroot00000000000000pymodbus\.internal package ========================== .. automodule:: pymodbus.internal :members: :undoc-members: :show-inheritance: Submodules ---------- pymodbus\.internal\.ptwisted module ----------------------------------- .. automodule:: pymodbus.internal.ptwisted :members: :undoc-members: :show-inheritance: pymodbus-2.1.0/doc/source/library/pymodbus.repl.rst000066400000000000000000000014551335513467700224450ustar00rootroot00000000000000pymodbus\.repl package ========================== .. automodule:: pymodbus.repl :members: :undoc-members: :show-inheritance: Submodules ---------- pymodbus\.repl\.client module ----------------------------------- .. automodule:: pymodbus.repl.client :members: :undoc-members: :show-inheritance: pymodbus\.repl\.completer module ----------------------------------- .. automodule:: pymodbus.repl.completer :members: :undoc-members: :show-inheritance: pymodbus\.repl\.helper module ----------------------------------- .. automodule:: pymodbus.repl.helper :members: :undoc-members: :show-inheritance: pymodbus\.repl\.main module ----------------------------------- .. automodule:: pymodbus.repl.main :members: :undoc-members: :show-inheritance: pymodbus-2.1.0/doc/source/library/pymodbus.rst000066400000000000000000000065031335513467700215030ustar00rootroot00000000000000pymodbus package ================ .. automodule:: pymodbus :members: :undoc-members: :show-inheritance: Subpackages ----------- .. toctree:: pymodbus.client pymodbus.datastore pymodbus.framer pymodbus.internal pymodbus.server pymodbus.repl Submodules ---------- pymodbus\.bit\_read\_message module ----------------------------------- .. automodule:: pymodbus.bit_read_message :members: :undoc-members: :show-inheritance: pymodbus\.bit\_write\_message module ------------------------------------ .. automodule:: pymodbus.bit_write_message :members: :undoc-members: :show-inheritance: pymodbus\.compat module ----------------------- .. automodule:: pymodbus.compat :members: :undoc-members: :show-inheritance: pymodbus\.constants module -------------------------- .. automodule:: pymodbus.constants :members: :undoc-members: :show-inheritance: pymodbus\.device module ----------------------- .. automodule:: pymodbus.device :members: :undoc-members: :show-inheritance: pymodbus\.diag\_message module ------------------------------ .. automodule:: pymodbus.diag_message :members: :undoc-members: :show-inheritance: pymodbus\.events module ----------------------- .. automodule:: pymodbus.events :members: :undoc-members: :show-inheritance: pymodbus\.exceptions module --------------------------- .. automodule:: pymodbus.exceptions :members: :undoc-members: :show-inheritance: pymodbus\.factory module ------------------------ .. automodule:: pymodbus.factory :members: :undoc-members: :show-inheritance: pymodbus\.file\_message module ------------------------------ .. automodule:: pymodbus.file_message :members: :undoc-members: :show-inheritance: pymodbus\.interfaces module --------------------------- .. automodule:: pymodbus.interfaces :members: :undoc-members: :show-inheritance: pymodbus\.mei\_message module ----------------------------- .. automodule:: pymodbus.mei_message :members: :undoc-members: :show-inheritance: pymodbus\.other\_message module ------------------------------- .. automodule:: pymodbus.other_message :members: :undoc-members: :show-inheritance: pymodbus\.payload module ------------------------ .. automodule:: pymodbus.payload :members: :undoc-members: :show-inheritance: pymodbus\.pdu module -------------------- .. automodule:: pymodbus.pdu :members: :undoc-members: :show-inheritance: pymodbus\.register\_read\_message module ---------------------------------------- .. automodule:: pymodbus.register_read_message :members: :undoc-members: :show-inheritance: pymodbus\.register\_write\_message module ----------------------------------------- .. automodule:: pymodbus.register_write_message :members: :undoc-members: :show-inheritance: pymodbus\.transaction module ---------------------------- .. automodule:: pymodbus.transaction :members: :undoc-members: :show-inheritance: pymodbus\.utilities module -------------------------- .. automodule:: pymodbus.utilities :members: :undoc-members: :show-inheritance: pymodbus\.version module ------------------------ .. automodule:: pymodbus.version :members: :undoc-members: :show-inheritance: pymodbus-2.1.0/doc/source/library/pymodbus.server.rst000066400000000000000000000007401335513467700230050ustar00rootroot00000000000000pymodbus\.server package ======================== .. automodule:: pymodbus.server :members: :undoc-members: :show-inheritance: Submodules ---------- pymodbus\.server\.async module ------------------------------ .. automodule:: pymodbus.server.async :members: :undoc-members: :show-inheritance: pymodbus\.server\.sync module ----------------------------- .. automodule:: pymodbus.server.sync :members: :undoc-members: :show-inheritance: pymodbus-2.1.0/examples/000077500000000000000000000000001335513467700152105ustar00rootroot00000000000000pymodbus-2.1.0/examples/common/000077500000000000000000000000001335513467700165005ustar00rootroot00000000000000pymodbus-2.1.0/examples/common/README.rst000066400000000000000000000073741335513467700202020ustar00rootroot00000000000000============================================================ Modbus Implementations ============================================================ There are a few reference implementations that you can use to test modbus serial ------------------------------------------------------------ pymodbus ------------------------------------------------------------ You can use pymodbus as a testing server by simply modifying one of the run scripts supplied here. There is an asynchronous version and a synchronous version (that really differ in how mnay dependencies you are willing to have). Regardless of which one you choose, they can be started quite easily:: ./asynchronous-server.py ./synchronous-server.py Currently, each version has implementations of the following: - modbus tcp - modbus udp - modbus udp binary - modbus ascii serial - modbus ascii rtu ------------------------------------------------------------ Modbus Driver ------------------------------------------------------------ Included are reference implementations of a modbus client and server using the modbus driver library (as well as the relevant source code). Both programs have a wealth of options and can be used to test either side of your application:: tools/reference/diagslave -h # (server) tools/reference/modpoll -h # (client) ------------------------------------------------------------ jamod ------------------------------------------------------------ Jamod is a complete modbus implementation for the java jvm. Included are a few simple reference servers using the library, however, a great deal more can be produced using it. I have not tested it, however, it may even be possible to use this library in conjunction with jython to interop between your python code and this library: * http://jamod.sourceforge.net/ ------------------------------------------------------------ nmodbus ------------------------------------------------------------ Although there is not any code included in this package, nmodbus is a complete implementation of the modbus protocol for the .net clr. The site has a number of examples that can be tuned for your testing needs: * http://code.google.com/p/nmodbus/ ============================================================ Serial Loopback Testing ============================================================ In order to test the serial implementations, one needs to create a loopback connection (virtual serial port). This can be done in a number of ways. ------------------------------------------------------------ Linux ------------------------------------------------------------ For linux, there are three ways that are included with this distribution. One is to use the socat utility. The following will get one going quickly:: sudo apt-get install socat sudo socat PTY,link=/dev/pts/13, PTY,link=/dev/pts/14 # connect the master to /dev/pts/13 # connect the client to /dev/pts/14 Next, you can include the loopback kernel driver included in the tools/nullmodem/linux directory:: sudo ./run ------------------------------------------------------------ Windows ------------------------------------------------------------ For Windows, simply use the com2com application that is in the directory tools/nullmodem/windows. Instructions are included in the Readme.txt. ------------------------------------------------------------ Generic ------------------------------------------------------------ For most unix based systems, there is a simple virtual serial forwarding application in the tools/nullmodem/ directory:: make run # connect the master to the master output # connect the client to the client output Or for a tried and true method, simply connect a null modem cable between two of your serial ports and then simply reference those. pymodbus-2.1.0/examples/common/async_asyncio_client.py000066400000000000000000000201201335513467700232450ustar00rootroot00000000000000#!/usr/bin/env python """ Pymodbus Asynchronous Client Examples -------------------------------------------------------------------------- The following is an example of how to use the asynchronous modbus client implementation from pymodbus with ayncio. The example is only valid on Python3.4 and above """ from pymodbus.compat import IS_PYTHON3, PYTHON_VERSION if IS_PYTHON3 and PYTHON_VERSION >= (3, 4): import asyncio import logging # ----------------------------------------------------------------------- # # Import the required async client # ----------------------------------------------------------------------- # from pymodbus.client.async.tcp import AsyncModbusTCPClient as ModbusClient # from pymodbus.client.async.udp import ( # AsyncModbusUDPClient as ModbusClient) from pymodbus.client.async import schedulers else: import sys sys.stderr("This example needs to be run only on python 3.4 and above") sys.exit(1) from threading import Thread import time # --------------------------------------------------------------------------- # # configure the client logging # --------------------------------------------------------------------------- # logging.basicConfig() log = logging.getLogger() log.setLevel(logging.DEBUG) # --------------------------------------------------------------------------- # # specify slave to query # --------------------------------------------------------------------------- # # The slave to query is specified in an optional parameter for each # individual request. This can be done by specifying the `unit` parameter # which defaults to `0x00` # --------------------------------------------------------------------------- # UNIT = 0x01 async def start_async_test(client): # ----------------------------------------------------------------------- # # specify slave to query # ----------------------------------------------------------------------- # # The slave to query is specified in an optional parameter for each # individual request. This can be done by specifying the `unit` parameter # which defaults to `0x00` # ----------------------------------------------------------------------- # log.debug("Reading Coils") rr = await client.read_coils(1, 1, unit=0x01) # ----------------------------------------------------------------------- # # example requests # ----------------------------------------------------------------------- # # simply call the methods that you would like to use. An example session # is displayed below along with some assert checks. Note that some modbus # implementations differentiate holding/input discrete/coils and as such # you will not be able to write to these, therefore the starting values # are not known to these tests. Furthermore, some use the same memory # blocks for the two sets, so a change to one is a change to the other. # Keep both of these cases in mind when testing as the following will # _only_ pass with the supplied async modbus server (script supplied). # ----------------------------------------------------------------------- # log.debug("Write to a Coil and read back") rq = await client.write_coil(0, True, unit=UNIT) rr = await client.read_coils(0, 1, unit=UNIT) assert(rq.function_code < 0x80) # test that we are not an error assert(rr.bits[0] == True) # test the expected value log.debug("Write to multiple coils and read back- test 1") rq = await client.write_coils(1, [True]*8, unit=UNIT) assert(rq.function_code < 0x80) # test that we are not an error rr = await client.read_coils(1, 21, unit=UNIT) assert(rr.function_code < 0x80) # test that we are not an error resp = [True]*21 # 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). resp.extend([False]*3) assert(rr.bits == resp) # test the expected value log.debug("Write to multiple coils and read back - test 2") rq = await client.write_coils(1, [False]*8, unit=UNIT) rr = await client.read_coils(1, 8, unit=UNIT) assert(rq.function_code < 0x80) # test that we are not an error assert(rr.bits == [False]*8) # test the expected value log.debug("Read discrete inputs") rr = await client.read_discrete_inputs(0, 8, unit=UNIT) assert(rq.function_code < 0x80) # test that we are not an error log.debug("Write to a holding register and read back") rq = await client.write_register(1, 10, unit=UNIT) rr = await client.read_holding_registers(1, 1, unit=UNIT) assert(rq.function_code < 0x80) # test that we are not an error assert(rr.registers[0] == 10) # test the expected value log.debug("Write to multiple holding registers and read back") rq = await client.write_registers(1, [10]*8, unit=UNIT) rr = await client.read_holding_registers(1, 8, unit=UNIT) assert(rq.function_code < 0x80) # test that we are not an error assert(rr.registers == [10]*8) # test the expected value log.debug("Read input registers") rr = await client.read_input_registers(1, 8, unit=UNIT) assert(rq.function_code < 0x80) # test that we are not an error arguments = { 'read_address': 1, 'read_count': 8, 'write_address': 1, 'write_registers': [20]*8, } log.debug("Read write registeres simulataneously") rq = await client.readwrite_registers(unit=UNIT, **arguments) rr = await client.read_holding_registers(1, 8, unit=UNIT) assert(rq.function_code < 0x80) # test that we are not an error assert(rq.registers == [20]*8) # test the expected value assert(rr.registers == [20]*8) # test the expected value await asyncio.sleep(1) def run_with_not_running_loop(): """ A loop is created and is passed to ModbusClient factory to be used. :return: """ log.debug("Running Async client with asyncio loop not yet started") log.debug("------------------------------------------------------") loop = asyncio.new_event_loop() assert not loop.is_running() new_loop, client = ModbusClient(schedulers.ASYNC_IO, port=5020, loop=loop) loop.run_until_complete(start_async_test(client.protocol)) loop.close() log.debug("--------------RUN_WITH_NOT_RUNNING_LOOP---------------") log.debug("") def run_with_already_running_loop(): """ An already running loop is passed to ModbusClient Factory :return: """ log.debug("Running Async client with asyncio loop already started") log.debug("------------------------------------------------------") def done(future): log.info("Done !!!") def start_loop(loop): """ Start Loop :param loop: :return: """ asyncio.set_event_loop(loop) loop.run_forever() loop = asyncio.new_event_loop() t = Thread(target=start_loop, args=[loop]) t.daemon = True # Start the loop t.start() assert loop.is_running() asyncio.set_event_loop(loop) loop, client = ModbusClient(schedulers.ASYNC_IO, port=5020, loop=loop) future = asyncio.run_coroutine_threadsafe( start_async_test(client.protocol), loop=loop) future.add_done_callback(done) while not future.done(): time.sleep(0.1) loop.stop() log.debug("--------DONE RUN_WITH_ALREADY_RUNNING_LOOP-------------") log.debug("") def run_with_no_loop(): """ ModbusClient Factory creates a loop. :return: """ loop, client = ModbusClient(schedulers.ASYNC_IO, port=5020) loop.run_until_complete(start_async_test(client.protocol)) loop.close() if __name__ == '__main__': # Run with No loop log.debug("Running Async client") log.debug("------------------------------------------------------") run_with_no_loop() # Run with loop not yet started run_with_not_running_loop() # Run with already running loop run_with_already_running_loop() log.debug("---------------------RUN_WITH_NO_LOOP-----------------") log.debug("") pymodbus-2.1.0/examples/common/async_asyncio_serial_client.py000077500000000000000000000146721335513467700246260ustar00rootroot00000000000000#!/usr/bin/env python """ Pymodbus Asynchronous Client Examples -------------------------------------------------------------------------- The following is an example of how to use the asynchronous serial modbus client implementation from pymodbus with ayncio. The example is only valid on Python3.4 and above """ from pymodbus.compat import IS_PYTHON3, PYTHON_VERSION if IS_PYTHON3 and PYTHON_VERSION >= (3, 4): import logging import asyncio from pymodbus.client.async.serial import ( AsyncModbusSerialClient as ModbusClient) from pymodbus.client.async import schedulers else: import sys sys.stderr("This example needs to be run only on python 3.4 and above") sys.exit(1) # --------------------------------------------------------------------------- # # configure the client logging # --------------------------------------------------------------------------- # logging.basicConfig() log = logging.getLogger() log.setLevel(logging.DEBUG) # --------------------------------------------------------------------------- # # specify slave to query # --------------------------------------------------------------------------- # # The slave to query is specified in an optional parameter for each # individual request. This can be done by specifying the `unit` parameter # which defaults to `0x00` # --------------------------------------------------------------------------- # UNIT = 0x01 async def start_async_test(client): # ----------------------------------------------------------------------- # # specify slave to query # ----------------------------------------------------------------------- # # The slave to query is specified in an optional parameter for each # individual request. This can be done by specifying the `unit` parameter # which defaults to `0x00` # ----------------------------------------------------------------------- # try: log.debug("Reading Coils") rr = client.read_coils(1, 1, unit=UNIT) # ----------------------------------------------------------------------- # # example requests # ----------------------------------------------------------------------- # # simply call the methods that you would like to use. # An example session is displayed below along with some assert checks. # Note that some modbus implementations differentiate holding/ # input discrete/coils and as such you will not be able to write to # these, therefore the starting values are not known to these tests. # Furthermore, some use the same memory blocks for the two sets, # so a change to one is a change to the other. # Keep both of these cases in mind when testing as the following will # _only_ pass with the supplied async modbus server (script supplied). # ----------------------------------------------------------------------- # log.debug("Write to a Coil and read back") rq = await client.write_coil(0, True, unit=UNIT) rr = await client.read_coils(0, 1, unit=UNIT) assert(rq.function_code < 0x80) # test that we are not an error assert(rr.bits[0] == True) # test the expected value log.debug("Write to multiple coils and read back- test 1") rq = await client.write_coils(1, [True]*8, unit=UNIT) assert(rq.function_code < 0x80) # test that we are not an error rr = await client.read_coils(1, 21, unit=UNIT) assert(rr.function_code < 0x80) # test that we are not an error resp = [True]*21 # 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). resp.extend([False]*3) assert(rr.bits == resp) # test the expected value log.debug("Write to multiple coils and read back - test 2") rq = await client.write_coils(1, [False]*8, unit=UNIT) rr = await client.read_coils(1, 8, unit=UNIT) assert(rq.function_code < 0x80) # test that we are not an error assert(rr.bits == [False]*8) # test the expected value log.debug("Read discrete inputs") rr = await client.read_discrete_inputs(0, 8, unit=UNIT) assert(rq.function_code < 0x80) # test that we are not an error log.debug("Write to a holding register and read back") rq = await client.write_register(1, 10, unit=UNIT) rr = await client.read_holding_registers(1, 1, unit=UNIT) assert(rq.function_code < 0x80) # test that we are not an error assert(rr.registers[0] == 10) # test the expected value log.debug("Write to multiple holding registers and read back") rq = await client.write_registers(1, [10]*8, unit=UNIT) rr = await client.read_holding_registers(1, 8, unit=UNIT) assert(rq.function_code < 0x80) # test that we are not an error assert(rr.registers == [10]*8) # test the expected value log.debug("Read input registers") rr = await client.read_input_registers(1, 8, unit=UNIT) assert(rq.function_code < 0x80) # test that we are not an error arguments = { 'read_address': 1, 'read_count': 8, 'write_address': 1, 'write_registers': [20]*8, } log.debug("Read write registers simulataneously") rq = await client.readwrite_registers(unit=UNIT, **arguments) rr = await client.read_holding_registers(1, 8, unit=UNIT) assert(rq.function_code < 0x80) # test that we are not an error assert(rq.registers == [20]*8) # test the expected value assert(rr.registers == [20]*8) # test the expected value except Exception as e: log.exception(e) client.transport.close() await asyncio.sleep(1) if __name__ == '__main__': # ----------------------------------------------------------------------- # # For testing on linux based systems you can use socat to create serial # ports # ----------------------------------------------------------------------- # # socat -d -d PTY,link=/tmp/ptyp0,raw,echo=0,ispeed=9600 PTY, # link=/tmp/ttyp0,raw,echo=0,ospeed=9600 loop, client = ModbusClient(schedulers.ASYNC_IO, port='/tmp/ptyp0', baudrate=9600, timeout=2, method="rtu") loop.run_until_complete(start_async_test(client.protocol)) loop.close() pymodbus-2.1.0/examples/common/async_tornado_client.py000077500000000000000000000123171335513467700232620ustar00rootroot00000000000000#!/usr/bin/env python """ Pymodbus Asynchronous Client Examples -------------------------------------------------------------------------- The following is an example of how to use the asynchronous modbus client implementation from pymodbus using Tornado. """ import functools from tornado.ioloop import IOLoop from pymodbus.client.async import schedulers # ---------------------------------------------------------------------------# # choose the requested modbus protocol # ---------------------------------------------------------------------------# # from pymodbus.client.async.udp import AsyncModbusUDPClient as ModbusClient from pymodbus.client.async.tcp import AsyncModbusTCPClient as ModbusClient # ---------------------------------------------------------------------------# # configure the client logging # ---------------------------------------------------------------------------# import logging logging.basicConfig() log = logging.getLogger() log.setLevel(logging.DEBUG) # ---------------------------------------------------------------------------# # helper method to test deferred callbacks # ---------------------------------------------------------------------------# def dassert(future, callback): def _assertor(value): # by pass assertion, an error here stops the write callbacks assert value def on_done(f): exc = f.exception() if exc: log.debug(exc) return _assertor(False) return _assertor(callback(f.result())) future.add_done_callback(on_done) def _print(value): if hasattr(value, "bits"): t = value.bits elif hasattr(value, "registers"): t = value.registers else: log.error(value) return log.info("Printing : -- {}".format(t)) return t UNIT = 0x01 # ---------------------------------------------------------------------------# # example requests # ---------------------------------------------------------------------------# # simply call the methods that you would like to use. An example session # is displayed below along with some assert checks. Note that unlike the # synchronous version of the client, the asynchronous version returns # deferreds which can be thought of as a handle to the callback to send # the result of the operation. We are handling the result using the # deferred assert helper(dassert). # ---------------------------------------------------------------------------# def beginAsynchronousTest(client, protocol): rq = client.write_coil(1, True, unit=UNIT) rr = client.read_coils(1, 1, unit=UNIT) dassert(rq, lambda r: r.function_code < 0x80) # test for no error dassert(rr, _print) # test the expected value rq = client.write_coils(1, [False]*8, unit=UNIT) rr = client.read_coils(1, 8, unit=UNIT) dassert(rq, lambda r: r.function_code < 0x80) # test for no error dassert(rr, _print) # test the expected value rq = client.write_coils(1, [False]*8, unit=UNIT) rr = client.read_discrete_inputs(1, 8, unit=UNIT) dassert(rq, lambda r: r.function_code < 0x80) # test for no error dassert(rr, _print) # test the expected value rq = client.write_register(1, 10, unit=UNIT) rr = client.read_holding_registers(1, 1, unit=UNIT) dassert(rq, lambda r: r.function_code < 0x80) # test for no error dassert(rr, _print) # test the expected value rq = client.write_registers(1, [10]*8, unit=UNIT) rr = client.read_input_registers(1, 8, unit=UNIT) dassert(rq, lambda r: r.function_code < 0x80) # test for no error dassert(rr, _print) # test the expected value arguments = { 'read_address': 1, 'read_count': 8, 'write_address': 1, 'write_registers': [20]*8, } rq = client.readwrite_registers(**arguments, unit=UNIT) rr = client.read_input_registers(1,8, unit=UNIT) dassert(rq, lambda r: r.registers == [20]*8) # test the expected value dassert(rr, _print) # test the expected value # -----------------------------------------------------------------------# # close the client at some time later # -----------------------------------------------------------------------# IOLoop.current().add_timeout(IOLoop.current().time() + 1, client.close) IOLoop.current().add_timeout(IOLoop.current().time() + 2, protocol.stop) # ---------------------------------------------------------------------------# # choose the client you want # ---------------------------------------------------------------------------# # make sure to start an implementation to hit against. For this # you can use an existing device, the reference implementation in the tools # directory, or start a pymodbus server. # ---------------------------------------------------------------------------# def err(*args, **kwargs): log.error("Err", args, kwargs) def callback(protocol, future): log.debug("Client connected") exp = future.exception() if exp: return err(exp) client = future.result() return beginAsynchronousTest(client, protocol) if __name__ == "__main__": protocol, future = ModbusClient(schedulers.IO_LOOP, port=5020) future.add_done_callback(functools.partial(callback, protocol)) pymodbus-2.1.0/examples/common/async_tornado_client_serial.py000077500000000000000000000155211335513467700246210ustar00rootroot00000000000000#!/usr/bin/env python """ Pymodbus Asynchronous Client Examples -------------------------------------------------------------------------- The following is an example of how to use the asynchronous serial modbus client implementation from pymodbus using tornado. """ # ---------------------------------------------------------------------------# # import needed libraries # ---------------------------------------------------------------------------# import functools from tornado.ioloop import IOLoop from pymodbus.client.async import schedulers # ---------------------------------------------------------------------------# # choose the requested modbus protocol # ---------------------------------------------------------------------------# from pymodbus.client.async.serial import AsyncModbusSerialClient # ---------------------------------------------------------------------------# # configure the client logging # ---------------------------------------------------------------------------# import logging logging.basicConfig() log = logging.getLogger() log.setLevel(logging.DEBUG) # ---------------------------------------------------------------------------# # helper method to test deferred callbacks # ---------------------------------------------------------------------------# def dassert(future, callback): def _assertor(value): # by pass assertion, an error here stops the write callbacks assert value def on_done(f): exc = f.exception() if exc: log.debug(exc) return _assertor(False) return _assertor(callback(f.result())) future.add_done_callback(on_done) def _print(value): if hasattr(value, "bits"): t = value.bits elif hasattr(value, "registers"): t = value.registers else: log.error(value) return log.info("Printing : -- {}".format(t)) return t # ---------------------------------------------------------------------------# # example requests # ---------------------------------------------------------------------------# # simply call the methods that you would like to use. An example session # is displayed below along with some assert checks. Note that unlike the # synchronous version of the client, the asynchronous version returns # deferreds which can be thought of as a handle to the callback to send # the result of the operation. We are handling the result using the # deferred assert helper(dassert). # ---------------------------------------------------------------------------# UNIT = 0x01 def beginAsynchronousTest(client, protocol): rq = client.write_coil(1, True, unit=UNIT) rr = client.read_coils(1, 1, unit=UNIT) dassert(rq, lambda r: r.function_code < 0x80) # test for no error dassert(rr, _print) # test the expected value rq = client.write_coils(1, [False]*8, unit=UNIT) rr = client.read_coils(1, 8, unit=UNIT) dassert(rq, lambda r: r.function_code < 0x80) # test for no error dassert(rr, _print) # test the expected value rq = client.write_coils(1, [False]*8, unit=UNIT) rr = client.read_discrete_inputs(1, 8, unit=UNIT) dassert(rq, lambda r: r.function_code < 0x80) # test for no error dassert(rr, _print) # test the expected value rq = client.write_register(1, 10, unit=UNIT) rr = client.read_holding_registers(1, 1, unit=UNIT) dassert(rq, lambda r: r.function_code < 0x80) # test for no error dassert(rr, _print) # test the expected value rq = client.write_registers(1, [10]*8, unit=UNIT) rr = client.read_input_registers(1, 8, unit=UNIT) dassert(rq, lambda r: r.function_code < 0x80) # test for no error dassert(rr, _print) # test the expected value arguments = { 'read_address': 1, 'read_count': 8, 'write_address': 1, 'write_registers': [20]*8, } rq = client.readwrite_registers(**arguments, unit=UNIT) rr = client.read_input_registers(1,8, unit=UNIT) dassert(rq, lambda r: r.registers == [20]*8) # test the expected value dassert(rr, _print) # test the expected value # -----------------------------------------------------------------------# # close the client at some time later # -----------------------------------------------------------------------# IOLoop.current().add_timeout(IOLoop.current().time() + 1, client.close) IOLoop.current().add_timeout(IOLoop.current().time() + 2, protocol.stop) # ---------------------------------------------------------------------------# # choose the client you want # ---------------------------------------------------------------------------# # make sure to start an implementation to hit against. For this # you can use an existing device, the reference implementation in the tools # directory, or start a pymodbus server. # ---------------------------------------------------------------------------# def err(*args, **kwargs): log.error("Err", args, kwargs) def callback(protocol, future): log.debug("Client connected") exp = future.exception() if exp: return err(exp) client = future.result() return beginAsynchronousTest(client, protocol) if __name__ == "__main__": # ----------------------------------------------------------------------- # # Create temporary serial ports using SOCAT # socat -d -d PTY,link=/tmp/ptyp0,raw,echo=0,ispeed=9600 PTY, # link=/tmp/ttyp0,raw,echo=0,ospeed=9600 # Default framer is ModbusRtuFramer # ----------------------------------------------------------------------- # # Rtu protocol, future = AsyncModbusSerialClient(schedulers.IO_LOOP, method="rtu", port="/dev/ptyp0", baudrate=9600, timeout=2) # Asci # from pymodbus.transaction import ModbusAsciiFramer # protocol, future = AsyncModbusSerialClient(schedulers.IO_LOOP, # method="ascii", # port="/dev/ptyp0", # framer=ModbusAsciiFramer, # baudrate=9600, # timeout=2) # Binary # from pymodbus.transaction import ModbusBinaryFramer # protocol, future = AsyncModbusSerialClient(schedulers.IO_LOOP, # method="binary", # port="/dev/ptyp0", # framer=ModbusBinaryFramer, # baudrate=9600, # timeout=2) future.add_done_callback(functools.partial(callback, protocol)) pymodbus-2.1.0/examples/common/async_twisted_client.py000077500000000000000000000160221335513467700232740ustar00rootroot00000000000000#!/usr/bin/env python """ Pymodbus Asynchronous Client Examples -------------------------------------------------------------------------- The following is an example of how to use the asynchronous modbus client implementation from pymodbus. """ # --------------------------------------------------------------------------- # # import needed libraries # --------------------------------------------------------------------------- # from twisted.internet import reactor from pymodbus.client.async.tcp import AsyncModbusTCPClient # from pymodbus.client.async.udp import AsyncModbusUDPClient from pymodbus.client.async import schedulers # --------------------------------------------------------------------------- # # choose the requested modbus protocol # --------------------------------------------------------------------------- # from twisted.internet import reactor, protocol # --------------------------------------------------------------------------- # # configure the client logging # --------------------------------------------------------------------------- # import logging FORMAT = ('%(asctime)-15s %(threadName)-15s' ' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s') logging.basicConfig(format=FORMAT) log = logging.getLogger() log.setLevel(logging.DEBUG) # --------------------------------------------------------------------------- # # helper method to test deferred callbacks # --------------------------------------------------------------------------- # def err(*args, **kwargs): logging.error("Err-{}-{}".format(args, kwargs)) def dassert(deferred, callback): def _assertor(value): assert value deferred.addCallback(lambda r: _assertor(callback(r))) deferred.addErrback(err) # --------------------------------------------------------------------------- # # specify slave to query # --------------------------------------------------------------------------- # # The slave to query is specified in an optional parameter for each # individual request. This can be done by specifying the `unit` parameter # which defaults to `0x00` # --------------------------------------------------------------------------- # UNIT = 0x01 def processResponse(result): log.debug(result) def exampleRequests(client): rr = client.read_coils(1, 1, unit=0x02) rr.addCallback(processResponse) rr = client.read_holding_registers(1, 1, unit=0x02) rr.addCallback(processResponse) rr = client.read_discrete_inputs(1, 1, unit=0x02) rr.addCallback(processResponse) rr = client.read_input_registers(1, 1, unit=0x02) rr.addCallback(processResponse) stopAsynchronousTest(client) # --------------------------------------------------------------------------- # # example requests # --------------------------------------------------------------------------- # # simply call the methods that you would like to use. An example session # is displayed below along with some assert checks. Note that unlike the # synchronous version of the client, the asynchronous version returns # deferreds which can be thought of as a handle to the callback to send # the result of the operation. We are handling the result using the # deferred assert helper(dassert). # --------------------------------------------------------------------------- # def stopAsynchronousTest(client): # ----------------------------------------------------------------------- # # close the client at some time later # ----------------------------------------------------------------------- # reactor.callLater(1, client.transport.loseConnection) reactor.callLater(2, reactor.stop) def beginAsynchronousTest(client): rq = client.write_coil(1, True, unit=UNIT) rr = client.read_coils(1, 1, unit=UNIT) dassert(rq, lambda r: not r.isError()) # test for no error dassert(rr, lambda r: r.bits[0] == True) # test the expected value rq = client.write_coils(1, [True]*8, unit=UNIT) rr = client.read_coils(1, 8, unit=UNIT) dassert(rq, lambda r: not r.isError()) # test for no error dassert(rr, lambda r: r.bits == [True]*8) # test the expected value rq = client.write_coils(1, [False]*8, unit=UNIT) rr = client.read_discrete_inputs(1, 8, unit=UNIT) dassert(rq, lambda r: not r.isError()) # test for no error dassert(rr, lambda r: r.bits == [True]*8) # test the expected value rq = client.write_register(1, 10, unit=UNIT) rr = client.read_holding_registers(1, 1, unit=UNIT) dassert(rq, lambda r: not r.isError()) # test for no error dassert(rr, lambda r: r.registers[0] == 10) # test the expected value rq = client.write_registers(1, [10]*8, unit=UNIT) rr = client.read_input_registers(1, 8, unit=UNIT) dassert(rq, lambda r: not r.isError()) # test for no error dassert(rr, lambda r: r.registers == [17]*8) # test the expected value arguments = { 'read_address': 1, 'read_count': 8, 'write_address': 1, 'write_registers': [20]*8, } rq = client.readwrite_registers(arguments, unit=UNIT) rr = client.read_input_registers(1, 8, unit=UNIT) dassert(rq, lambda r: r.registers == [20]*8) # test the expected value dassert(rr, lambda r: r.registers == [17]*8) # test the expected value stopAsynchronousTest(client) # ----------------------------------------------------------------------- # # close the client at some time later # ----------------------------------------------------------------------- # # reactor.callLater(1, client.transport.loseConnection) reactor.callLater(2, reactor.stop) # --------------------------------------------------------------------------- # # extra requests # --------------------------------------------------------------------------- # # If you are performing a request that is not available in the client # mixin, you have to perform the request like this instead:: # # from pymodbus.diag_message import ClearCountersRequest # from pymodbus.diag_message import ClearCountersResponse # # request = ClearCountersRequest() # response = client.execute(request) # if isinstance(response, ClearCountersResponse): # ... do something with the response # # --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- # # choose the client you want # --------------------------------------------------------------------------- # # make sure to start an implementation to hit against. For this # you can use an existing device, the reference implementation in the tools # directory, or start a pymodbus server. # --------------------------------------------------------------------------- # if __name__ == "__main__": protocol, deferred = AsyncModbusTCPClient(schedulers.REACTOR, port=5020) # protocol, deferred = AsyncModbusUDPClient(schedulers.REACTOR, port=5020) # callback=beginAsynchronousTest, # errback=err) deferred.addCallback(beginAsynchronousTest) deferred.addErrback(err) pymodbus-2.1.0/examples/common/async_twisted_client_serial.py000077500000000000000000000054231335513467700246360ustar00rootroot00000000000000#!/usr/bin/env python """ Pymodbus Asynchronous Client Examples -------------------------------------------------------------------------- The following is an example of how to use the asynchronous serial modbus client implementation from pymodbus with twisted. """ from twisted.internet import reactor from pymodbus.client.async import schedulers from pymodbus.client.async.serial import AsyncModbusSerialClient from pymodbus.client.async.twisted import ModbusClientProtocol import logging logging.basicConfig() log = logging.getLogger("pymodbus") log.setLevel(logging.DEBUG) # ---------------------------------------------------------------------------# # state a few constants # ---------------------------------------------------------------------------# SERIAL_PORT = "/dev/ptyp0" STATUS_REGS = (1, 2) STATUS_COILS = (1, 3) CLIENT_DELAY = 1 UNIT = 0x01 class ExampleProtocol(ModbusClientProtocol): def __init__(self, framer): """ Initializes our custom protocol :param framer: The decoder to use to process messages :param endpoint: The endpoint to send results to """ ModbusClientProtocol.__init__(self, framer) log.debug("Beginning the processing loop") reactor.callLater(CLIENT_DELAY, self.fetch_holding_registers) def fetch_holding_registers(self): """ Defer fetching holding registers """ log.debug("Starting the next cycle") d = self.read_holding_registers(*STATUS_REGS, unit=UNIT) d.addCallbacks(self.send_holding_registers, self.error_handler) def send_holding_registers(self, response): """ Write values of holding registers, defer fetching coils :param response: The response to process """ log.info(response.getRegister(0)) log.info(response.getRegister(1)) d = self.read_coils(*STATUS_COILS, unit=UNIT) d.addCallbacks(self.start_next_cycle, self.error_handler) def start_next_cycle(self, response): """ Write values of coils, trigger next cycle :param response: The response to process """ log.info(response.getBit(0)) log.info(response.getBit(1)) log.info(response.getBit(2)) reactor.callLater(CLIENT_DELAY, self.fetch_holding_registers) def error_handler(self, failure): """ Handle any twisted errors :param failure: The error to handle """ log.error(failure) if __name__ == "__main__": proto, client = AsyncModbusSerialClient(schedulers.REACTOR, method="rtu", port=SERIAL_PORT, timeout=2, proto_cls=ExampleProtocol) proto.start() # proto.stop() pymodbus-2.1.0/examples/common/asynchronous_processor.py000077500000000000000000000164171335513467700237200ustar00rootroot00000000000000#!/usr/bin/env python """ Pymodbus Asynchronous Processor Example -------------------------------------------------------------------------- The following is a full example of a continuous client processor. Feel free to use it as a skeleton guide in implementing your own. """ # --------------------------------------------------------------------------- # # import the neccessary modules # --------------------------------------------------------------------------- # from twisted.internet import serialport, reactor from twisted.internet.protocol import ClientFactory from pymodbus.factory import ClientDecoder from pymodbus.client.async.twisted import ModbusClientProtocol # --------------------------------------------------------------------------- # # Choose the framer you want to use # --------------------------------------------------------------------------- # # from pymodbus.transaction import ModbusBinaryFramer as ModbusFramer # from pymodbus.transaction import ModbusAsciiFramer as ModbusFramer from pymodbus.transaction import ModbusRtuFramer as ModbusFramer # from pymodbus.transaction import ModbusSocketFramer as ModbusFramer # --------------------------------------------------------------------------- # # configure the client logging # --------------------------------------------------------------------------- # import logging FORMAT = ('%(asctime)-15s %(threadName)-15s' ' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s') logging.basicConfig(format=FORMAT) log = logging.getLogger() log.setLevel(logging.DEBUG) # --------------------------------------------------------------------------- # # state a few constants # --------------------------------------------------------------------------- # SERIAL_PORT = "/dev/ptyp0" STATUS_REGS = (1, 2) STATUS_COILS = (1, 3) CLIENT_DELAY = 1 UNIT = 0x01 # --------------------------------------------------------------------------- # # an example custom protocol # --------------------------------------------------------------------------- # # Here you can perform your main procesing loop utilizing defereds and timed # callbacks. # --------------------------------------------------------------------------- # class ExampleProtocol(ModbusClientProtocol): def __init__(self, framer, endpoint): """ Initializes our custom protocol :param framer: The decoder to use to process messages :param endpoint: The endpoint to send results to """ ModbusClientProtocol.__init__(self, framer) self.endpoint = endpoint log.debug("Beginning the processing loop") reactor.callLater(CLIENT_DELAY, self.fetch_holding_registers) def fetch_holding_registers(self): """ Defer fetching holding registers """ log.debug("Starting the next cycle") d = self.read_holding_registers(*STATUS_REGS, unit=UNIT) d.addCallbacks(self.send_holding_registers, self.error_handler) def send_holding_registers(self, response): """ Write values of holding registers, defer fetching coils :param response: The response to process """ self.endpoint.write(response.getRegister(0)) self.endpoint.write(response.getRegister(1)) d = self.read_coils(*STATUS_COILS, unit=UNIT) d.addCallbacks(self.start_next_cycle, self.error_handler) def start_next_cycle(self, response): """ Write values of coils, trigger next cycle :param response: The response to process """ self.endpoint.write(response.getBit(0)) self.endpoint.write(response.getBit(1)) self.endpoint.write(response.getBit(2)) reactor.callLater(CLIENT_DELAY, self.fetch_holding_registers) def error_handler(self, failure): """ Handle any twisted errors :param failure: The error to handle """ log.error(failure) # --------------------------------------------------------------------------- # # a factory for the example protocol # --------------------------------------------------------------------------- # # This is used to build client protocol's if you tie into twisted's method # of processing. It basically produces client instances of the underlying # protocol:: # # Factory(Protocol) -> ProtocolInstance # # It also persists data between client instances (think protocol singelton). # --------------------------------------------------------------------------- # class ExampleFactory(ClientFactory): protocol = ExampleProtocol def __init__(self, framer, endpoint): """ Remember things necessary for building a protocols """ self.framer = framer self.endpoint = endpoint def buildProtocol(self, _): """ Create a protocol and start the reading cycle """ proto = self.protocol(self.framer, self.endpoint) proto.factory = self return proto # --------------------------------------------------------------------------- # # a custom client for our device # --------------------------------------------------------------------------- # # Twisted provides a number of helper methods for creating and starting # clients: # - protocol.ClientCreator # - reactor.connectTCP # # How you start your client is really up to you. # --------------------------------------------------------------------------- # class SerialModbusClient(serialport.SerialPort): def __init__(self, factory, *args, **kwargs): """ Setup the client and start listening on the serial port :param factory: The factory to build clients with """ protocol = factory.buildProtocol(None) self.decoder = ClientDecoder() serialport.SerialPort.__init__(self, protocol, *args, **kwargs) # --------------------------------------------------------------------------- # # a custom endpoint for our results # --------------------------------------------------------------------------- # # An example line reader, this can replace with: # - the TCP protocol # - a context recorder # - a database or file recorder # --------------------------------------------------------------------------- # class LoggingLineReader(object): def write(self, response): """ Handle the next modbus response :param response: The response to process """ log.info("Read Data: %d" % response) # --------------------------------------------------------------------------- # # start running the processor # --------------------------------------------------------------------------- # # This initializes the client, the framer, the factory, and starts the # twisted event loop (the reactor). It should be noted that a number of # things could be chanegd as one sees fit: # - The ModbusRtuFramer could be replaced with a ModbusAsciiFramer # - The SerialModbusClient could be replaced with reactor.connectTCP # - The LineReader endpoint could be replaced with a database store # --------------------------------------------------------------------------- # def main(): log.debug("Initializing the client") framer = ModbusFramer(ClientDecoder(), client=None) reader = LoggingLineReader() factory = ExampleFactory(framer, reader) SerialModbusClient(factory, SERIAL_PORT, reactor) # factory = reactor.connectTCP("localhost", 502, factory) log.debug("Starting the client") reactor.run() if __name__ == "__main__": main() pymodbus-2.1.0/examples/common/asynchronous_server.py000077500000000000000000000144461335513467700232070ustar00rootroot00000000000000#!/usr/bin/env python """ Pymodbus Asynchronous Server Example -------------------------------------------------------------------------- The asynchronous server is a high performance implementation using the twisted library as its backend. This allows it to scale to many thousands of nodes which can be helpful for testing monitoring software. """ # --------------------------------------------------------------------------- # # import the various server implementations # --------------------------------------------------------------------------- # from pymodbus.server.async import StartTcpServer from pymodbus.server.async import StartUdpServer from pymodbus.server.async import StartSerialServer from pymodbus.device import ModbusDeviceIdentification from pymodbus.datastore import ModbusSequentialDataBlock from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext from pymodbus.transaction import (ModbusRtuFramer, ModbusAsciiFramer, ModbusBinaryFramer) # --------------------------------------------------------------------------- # # configure the service logging # --------------------------------------------------------------------------- # import logging FORMAT = ('%(asctime)-15s %(threadName)-15s' ' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s') logging.basicConfig(format=FORMAT) log = logging.getLogger() log.setLevel(logging.DEBUG) def run_async_server(): # ----------------------------------------------------------------------- # # initialize your data store # ----------------------------------------------------------------------- # # The datastores only respond to the addresses that they are initialized to # Therefore, if you initialize a DataBlock to addresses from 0x00 to 0xFF, # a request to 0x100 will respond with an invalid address exception. # This is because many devices exhibit this kind of behavior (but not all) # # block = ModbusSequentialDataBlock(0x00, [0]*0xff) # # Continuing, you can choose to use a sequential or a sparse DataBlock in # your data context. The difference is that the sequential has no gaps in # the data while the sparse can. Once again, there are devices that exhibit # both forms of behavior:: # # block = ModbusSparseDataBlock({0x00: 0, 0x05: 1}) # block = ModbusSequentialDataBlock(0x00, [0]*5) # # Alternately, you can use the factory methods to initialize the DataBlocks # or simply do not pass them to have them initialized to 0x00 on the full # address range:: # # store = ModbusSlaveContext(di = ModbusSequentialDataBlock.create()) # store = ModbusSlaveContext() # # Finally, you are allowed to use the same DataBlock reference for every # table or you you may use a seperate DataBlock for each table. # This depends if you would like functions to be able to access and modify # the same data or not:: # # block = ModbusSequentialDataBlock(0x00, [0]*0xff) # store = ModbusSlaveContext(di=block, co=block, hr=block, ir=block) # # The server then makes use of a server context that allows the server to # respond with different slave contexts for different unit ids. By default # it will return the same context for every unit id supplied (broadcast # mode). # However, this can be overloaded by setting the single flag to False # and then supplying a dictionary of unit id to context mapping:: # # slaves = { # 0x01: ModbusSlaveContext(...), # 0x02: ModbusSlaveContext(...), # 0x03: ModbusSlaveContext(...), # } # context = ModbusServerContext(slaves=slaves, single=False) # # The slave context can also be initialized in zero_mode which means that a # request to address(0-7) will map to the address (0-7). The default is # False which is based on section 4.4 of the specification, so address(0-7) # will map to (1-8):: # # store = ModbusSlaveContext(..., zero_mode=True) # ----------------------------------------------------------------------- # store = ModbusSlaveContext( di=ModbusSequentialDataBlock(0, [17]*100), co=ModbusSequentialDataBlock(0, [17]*100), hr=ModbusSequentialDataBlock(0, [17]*100), ir=ModbusSequentialDataBlock(0, [17]*100)) context = ModbusServerContext(slaves=store, single=True) # ----------------------------------------------------------------------- # # initialize the server information # ----------------------------------------------------------------------- # # If you don't set this or any fields, they are defaulted to empty strings. # ----------------------------------------------------------------------- # identity = ModbusDeviceIdentification() identity.VendorName = 'Pymodbus' identity.ProductCode = 'PM' identity.VendorUrl = 'http://github.com/bashwork/pymodbus/' identity.ProductName = 'Pymodbus Server' identity.ModelName = 'Pymodbus Server' identity.MajorMinorRevision = '1.5' # ----------------------------------------------------------------------- # # run the server you want # ----------------------------------------------------------------------- # # TCP Server StartTcpServer(context, identity=identity, address=("localhost", 5020)) # TCP Server with deferred reactor run # from twisted.internet import reactor # StartTcpServer(context, identity=identity, address=("localhost", 5020), # defer_reactor_run=True) # reactor.run() # Server with RTU framer # StartTcpServer(context, identity=identity, address=("localhost", 5020), # framer=ModbusRtuFramer) # UDP Server # StartUdpServer(context, identity=identity, address=("127.0.0.1", 5020)) # RTU Server # StartSerialServer(context, identity=identity, # port='/dev/ttyp0', framer=ModbusRtuFramer) # ASCII Server # StartSerialServer(context, identity=identity, # port='/dev/ttyp0', framer=ModbusAsciiFramer) # Binary Server # StartSerialServer(context, identity=identity, # port='/dev/ttyp0', framer=ModbusBinaryFramer) if __name__ == "__main__": run_async_server() pymodbus-2.1.0/examples/common/callback_server.py000077500000000000000000000121741335513467700222040ustar00rootroot00000000000000#!/usr/bin/env python """ Pymodbus Server With Callbacks -------------------------------------------------------------------------- This is an example of adding callbacks to a running modbus server when a value is written to it. In order for this to work, it needs a device-mapping file. """ # --------------------------------------------------------------------------- # # import the modbus libraries we need # --------------------------------------------------------------------------- # from pymodbus.server.async import StartTcpServer from pymodbus.device import ModbusDeviceIdentification from pymodbus.datastore import ModbusSparseDataBlock from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer # --------------------------------------------------------------------------- # # import the python libraries we need # --------------------------------------------------------------------------- # from multiprocessing import Queue, Process # --------------------------------------------------------------------------- # # configure the service logging # --------------------------------------------------------------------------- # import logging logging.basicConfig() log = logging.getLogger() log.setLevel(logging.DEBUG) # --------------------------------------------------------------------------- # # create your custom data block with callbacks # --------------------------------------------------------------------------- # class CallbackDataBlock(ModbusSparseDataBlock): """ A datablock that stores the new value in memory and passes the operation to a message queue for further processing. """ def __init__(self, devices, queue): """ """ self.devices = devices self.queue = queue values = {k: 0 for k in devices.keys()} values[0xbeef] = len(values) # the number of devices super(CallbackDataBlock, self).__init__(values) def setValues(self, address, value): """ Sets the requested values of the datastore :param address: The starting address :param values: The new values to be set """ super(CallbackDataBlock, self).setValues(address, value) self.queue.put((self.devices.get(address, None), value)) # --------------------------------------------------------------------------- # # define your callback process # --------------------------------------------------------------------------- # def rescale_value(value): """ Rescale the input value from the range of 0..100 to -3200..3200. :param value: The input value to scale :returns: The rescaled value """ s = 1 if value >= 50 else -1 c = value if value < 50 else (value - 50) return s * (c * 64) def device_writer(queue): """ A worker process that processes new messages from a queue to write to device outputs :param queue: The queue to get new messages from """ while True: device, value = queue.get() scaled = rescale_value(value[0]) log.debug("Write(%s) = %s" % (device, value)) if not device: continue # do any logic here to update your devices # --------------------------------------------------------------------------- # # initialize your device map # --------------------------------------------------------------------------- # def read_device_map(path): """ A helper method to read the device path to address mapping from file:: 0x0001,/dev/device1 0x0002,/dev/device2 :param path: The path to the input file :returns: The input mapping file """ devices = {} with open(path, 'r') as stream: for line in stream: piece = line.strip().split(',') devices[int(piece[0], 16)] = piece[1] return devices def run_callback_server(): # ----------------------------------------------------------------------- # # initialize your data store # ----------------------------------------------------------------------- # queue = Queue() devices = read_device_map("device-mapping") block = CallbackDataBlock(devices, queue) store = ModbusSlaveContext(di=block, co=block, hr=block, ir=block) context = ModbusServerContext(slaves=store, single=True) # ----------------------------------------------------------------------- # # initialize the server information # ----------------------------------------------------------------------- # identity = ModbusDeviceIdentification() identity.VendorName = 'pymodbus' identity.ProductCode = 'PM' identity.VendorUrl = 'http://github.com/bashwork/pymodbus/' identity.ProductName = 'pymodbus Server' identity.ModelName = 'pymodbus Server' identity.MajorMinorRevision = '1.0' # ----------------------------------------------------------------------- # # run the server you want # ----------------------------------------------------------------------- # p = Process(target=device_writer, args=(queue,)) p.start() StartTcpServer(context, identity=identity, address=("localhost", 5020)) if __name__ == "__main__": run_callback_server() pymodbus-2.1.0/examples/common/changing_framers.py000077500000000000000000000052341335513467700223560ustar00rootroot00000000000000#!/usr/bin/env python """ Pymodbus Client Framer Overload -------------------------------------------------------------------------- All of the modbus clients are designed to have pluggable framers so that the transport and protocol are decoupled. This allows a user to define or plug in their custom protocols into existing transports (like a binary framer over a serial connection). It should be noted that although you are not limited to trying whatever you would like, the library makes no gurantees that all framers with all transports will produce predictable or correct results (for example tcp transport with an RTU framer). However, please let us know of any success cases that are not documented! """ # --------------------------------------------------------------------------- # # import the modbus client and the framers # --------------------------------------------------------------------------- # from pymodbus.client.sync import ModbusTcpClient as ModbusClient # --------------------------------------------------------------------------- # # Import the modbus framer that you want # --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- # from pymodbus.transaction import ModbusSocketFramer as ModbusFramer # from pymodbus.transaction import ModbusRtuFramer as ModbusFramer #from pymodbus.transaction import ModbusBinaryFramer as ModbusFramer #from pymodbus.transaction import ModbusAsciiFramer as ModbusFramer # --------------------------------------------------------------------------- # # configure the client logging # --------------------------------------------------------------------------- # import logging logging.basicConfig() log = logging.getLogger() log.setLevel(logging.DEBUG) if __name__ == "__main__": # ----------------------------------------------------------------------- # # Initialize the client # ----------------------------------------------------------------------- # client = ModbusClient('localhost', port=5020, framer=ModbusFramer) client.connect() # ----------------------------------------------------------------------- # # perform your requests # ----------------------------------------------------------------------- # rq = client.write_coil(1, True) rr = client.read_coils(1,1) assert(not rq.isError()) # test that we are not an error assert(rr.bits[0] == True) # test the expected value # ----------------------------------------------------------------------- # # close the client # ---------------------------------------------------------------------- # client.close() pymodbus-2.1.0/examples/common/custom_datablock.py000077500000000000000000000063521335513467700224010ustar00rootroot00000000000000#!/usr/bin/env python """ Pymodbus Server With Custom Datablock Side Effect -------------------------------------------------------------------------- This is an example of performing custom logic after a value has been written to the datastore. """ # --------------------------------------------------------------------------- # # import the modbus libraries we need # --------------------------------------------------------------------------- # from __future__ import print_function from pymodbus.server.async import StartTcpServer from pymodbus.device import ModbusDeviceIdentification from pymodbus.datastore import ModbusSparseDataBlock from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer # --------------------------------------------------------------------------- # # configure the service logging # --------------------------------------------------------------------------- # import logging logging.basicConfig() log = logging.getLogger() log.setLevel(logging.DEBUG) # --------------------------------------------------------------------------- # # create your custom data block here # --------------------------------------------------------------------------- # class CustomDataBlock(ModbusSparseDataBlock): """ A datablock that stores the new value in memory and performs a custom action after it has been stored. """ def setValues(self, address, value): """ Sets the requested values of the datastore :param address: The starting address :param values: The new values to be set """ super(ModbusSparseDataBlock, self).setValues(address, value) # whatever you want to do with the written value is done here, # however make sure not to do too much work here or it will # block the server, espectially if the server is being written # to very quickly print("wrote {} to {}".format(value, address)) def run_custom_db_server(): # ----------------------------------------------------------------------- # # initialize your data store # ----------------------------------------------------------------------- # block = CustomDataBlock([0]*100) store = ModbusSlaveContext(di=block, co=block, hr=block, ir=block) context = ModbusServerContext(slaves=store, single=True) # ----------------------------------------------------------------------- # # initialize the server information # ----------------------------------------------------------------------- # identity = ModbusDeviceIdentification() identity.VendorName = 'pymodbus' identity.ProductCode = 'PM' identity.VendorUrl = 'http://github.com/bashwork/pymodbus/' identity.ProductName = 'pymodbus Server' identity.ModelName = 'pymodbus Server' identity.MajorMinorRevision = '1.0' # ----------------------------------------------------------------------- # # run the server you want # ----------------------------------------------------------------------- # # p = Process(target=device_writer, args=(queue,)) # p.start() StartTcpServer(context, identity=identity, address=("localhost", 5020)) if __name__ == "__main__": run_custom_db_server() pymodbus-2.1.0/examples/common/custom_message.py000077500000000000000000000071221335513467700220750ustar00rootroot00000000000000#!/usr/bin/env python """ Pymodbus Synchronous Client Examples -------------------------------------------------------------------------- The following is an example of how to use the synchronous modbus client implementation from pymodbus. It should be noted that the client can also be used with the guard construct that is available in python 2.5 and up:: with ModbusClient('127.0.0.1') as client: result = client.read_coils(1,10) print result """ import struct # --------------------------------------------------------------------------- # # import the various server implementations # --------------------------------------------------------------------------- # from pymodbus.pdu import ModbusRequest, ModbusResponse, ModbusExceptions from pymodbus.client.sync import ModbusTcpClient as ModbusClient from pymodbus.bit_read_message import ReadCoilsRequest # --------------------------------------------------------------------------- # # configure the client logging # --------------------------------------------------------------------------- # import logging logging.basicConfig() log = logging.getLogger() log.setLevel(logging.DEBUG) # --------------------------------------------------------------------------- # # create your custom message # --------------------------------------------------------------------------- # # The following is simply a read coil request that always reads 16 coils. # Since the function code is already registered with the decoder factory, # this will be decoded as a read coil response. If you implement a new # method that is not currently implemented, you must register the request # and response with a ClientDecoder factory. # --------------------------------------------------------------------------- # class CustomModbusResponse(ModbusResponse): pass class CustomModbusRequest(ModbusRequest): function_code = 1 def __init__(self, address): ModbusRequest.__init__(self) self.address = address self.count = 16 def encode(self): return struct.pack('>HH', self.address, self.count) def decode(self, data): self.address, self.count = struct.unpack('>HH', data) def execute(self, context): if not (1 <= self.count <= 0x7d0): return self.doException(ModbusExceptions.IllegalValue) if not context.validate(self.function_code, self.address, self.count): return self.doException(ModbusExceptions.IllegalAddress) values = context.getValues(self.function_code, self.address, self.count) return CustomModbusResponse(values) # --------------------------------------------------------------------------- # # This could also have been defined as # --------------------------------------------------------------------------- # class Read16CoilsRequest(ReadCoilsRequest): def __init__(self, address): """ Initializes a new instance :param address: The address to start reading from """ ReadCoilsRequest.__init__(self, address, 16) # --------------------------------------------------------------------------- # # execute the request with your client # --------------------------------------------------------------------------- # # using the with context, the client will automatically be connected # and closed when it leaves the current scope. # --------------------------------------------------------------------------- # if __name__ == "__main__": with ModbusClient('127.0.0.1') as client: request = CustomModbusRequest(0) result = client.execute(request) print(result) pymodbus-2.1.0/examples/common/dbstore_update_server.py000066400000000000000000000100441335513467700234430ustar00rootroot00000000000000""" Pymodbus Server With Updating Thread -------------------------------------------------------------------------- This is an example of having a background thread updating the context in an SQLite4 database while the server is operating. This scrit generates a random address range (within 0 - 65000) and a random value and stores it in a database. It then reads the same address to verify that the process works as expected This can also be done with a python thread:: from threading import Thread thread = Thread(target=updating_writer, args=(context,)) thread.start() """ # --------------------------------------------------------------------------- # # import the modbus libraries we need # --------------------------------------------------------------------------- # from pymodbus.server.async import StartTcpServer from pymodbus.device import ModbusDeviceIdentification from pymodbus.datastore import ModbusSequentialDataBlock from pymodbus.datastore import ModbusServerContext from pymodbus.datastore.database import SqlSlaveContext from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer import random # --------------------------------------------------------------------------- # # import the twisted libraries we need # --------------------------------------------------------------------------- # from twisted.internet.task import LoopingCall # --------------------------------------------------------------------------- # # configure the service logging # --------------------------------------------------------------------------- # import logging logging.basicConfig() log = logging.getLogger() log.setLevel(logging.DEBUG) # --------------------------------------------------------------------------- # # define your callback process # --------------------------------------------------------------------------- # def updating_writer(a): """ A worker process that runs every so often and updates live values of the context which resides in an SQLite3 database. It should be noted that there is a race condition for the update. :param arguments: The input arguments to the call """ log.debug("Updating the database context") context = a[0] readfunction = 0x03 # read holding registers writefunction = 0x10 slave_id = 0x01 # slave address count = 50 # import pdb; pdb.set_trace() rand_value = random.randint(0, 9999) rand_addr = random.randint(0, 65000) log.debug("Writing to datastore: {}, {}".format(rand_addr, rand_value)) # import pdb; pdb.set_trace() context[slave_id].setValues(writefunction, rand_addr, [rand_value]) values = context[slave_id].getValues(readfunction, rand_addr, count) log.debug("Values from datastore: " + str(values)) def run_dbstore_update_server(): # ----------------------------------------------------------------------- # # initialize your data store # ----------------------------------------------------------------------- # block = ModbusSequentialDataBlock(0x00, [0] * 0xff) store = SqlSlaveContext(block) context = ModbusServerContext(slaves={1: store}, single=False) # ----------------------------------------------------------------------- # # initialize the server information # ----------------------------------------------------------------------- # identity = ModbusDeviceIdentification() identity.VendorName = 'pymodbus' identity.ProductCode = 'PM' identity.VendorUrl = 'http://github.com/bashwork/pymodbus/' identity.ProductName = 'pymodbus Server' identity.ModelName = 'pymodbus Server' identity.MajorMinorRevision = '1.0' # ----------------------------------------------------------------------- # # run the server you want # ----------------------------------------------------------------------- # time = 5 # 5 seconds delay loop = LoopingCall(f=updating_writer, a=(context,)) loop.start(time, now=False) # initially delay by time StartTcpServer(context, identity=identity, address=("", 5020)) if __name__ == "__main__": run_dbstore_update_server() pymodbus-2.1.0/examples/common/device_mapping000066400000000000000000000000431335513467700213720ustar00rootroot000000000000000x0001,/dev/ptyp0 0x0002,/dev/ptyp1pymodbus-2.1.0/examples/common/modbus_logging.py000077500000000000000000000037771335513467700220720ustar00rootroot00000000000000#!/usr/bin/env python """ Pymodbus Logging Examples -------------------------------------------------------------------------- """ import logging import logging.handlers as Handlers if __name__ == "__main__": # ----------------------------------------------------------------------- # # This will simply send everything logged to console # ----------------------------------------------------------------------- # logging.basicConfig() log = logging.getLogger() log.setLevel(logging.DEBUG) # ----------------------------------------------------------------------- # # This will send the error messages in the specified namespace to a file. # The available namespaces in pymodbus are as follows: # ----------------------------------------------------------------------- # # * pymodbus.* - The root namespace # * pymodbus.server.* - all logging messages involving the modbus server # * pymodbus.client.* - all logging messages involving the client # * pymodbus.protocol.* - all logging messages inside the protocol layer # ----------------------------------------------------------------------- # logging.basicConfig() log = logging.getLogger('pymodbus.server') log.setLevel(logging.ERROR) # ----------------------------------------------------------------------- # # This will send the error messages to the specified handlers: # * docs.python.org/library/logging.html # ----------------------------------------------------------------------- # log = logging.getLogger('pymodbus') log.setLevel(logging.ERROR) handlers = [ Handlers.RotatingFileHandler("logfile", maxBytes=1024*1024), Handlers.SMTPHandler("mx.host.com", "pymodbus@host.com", ["support@host.com"], "Pymodbus"), Handlers.SysLogHandler(facility="daemon"), Handlers.DatagramHandler('localhost', 12345), ] [log.addHandler(h) for h in handlers] pymodbus-2.1.0/examples/common/modbus_payload.py000077500000000000000000000141031335513467700220560ustar00rootroot00000000000000#!/usr/bin/env python """ Pymodbus Payload Building/Decoding Example -------------------------------------------------------------------------- # Run modbus-payload-server.py or synchronous-server.py to check the behavior """ from pymodbus.constants import Endian from pymodbus.payload import BinaryPayloadDecoder from pymodbus.payload import BinaryPayloadBuilder from pymodbus.client.sync import ModbusTcpClient as ModbusClient from pymodbus.compat import iteritems from collections import OrderedDict # --------------------------------------------------------------------------- # # configure the client logging # --------------------------------------------------------------------------- # import logging FORMAT = ('%(asctime)-15s %(threadName)-15s' ' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s') logging.basicConfig(format=FORMAT) log = logging.getLogger() log.setLevel(logging.INFO) def run_binary_payload_ex(): # ----------------------------------------------------------------------- # # We are going to use a simple client to send our requests # ----------------------------------------------------------------------- # client = ModbusClient('127.0.0.1', port=5020) client.connect() # ----------------------------------------------------------------------- # # If you need to build a complex message to send, you can use the payload # builder to simplify the packing logic. # # Here we demonstrate packing a random payload layout, unpacked it looks # like the following: # # - a 8 byte string 'abcdefgh' # - a 32 bit float 22.34 # - a 16 bit unsigned int 0x1234 # - another 16 bit unsigned int 0x5678 # - an 8 bit int 0x12 # - an 8 bit bitstring [0,1,0,1,1,0,1,0] # - an 32 bit uint 0x12345678 # - an 32 bit signed int -0x1234 # - an 64 bit signed int 0x12345678 # The packing can also be applied to the word (wordorder) and bytes in each # word (byteorder) # The wordorder is applicable only for 32 and 64 bit values # Lets say we need to write a value 0x12345678 to a 32 bit register # The following combinations could be used to write the register # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # # Word Order - Big Byte Order - Big # word1 =0x1234 word2 = 0x5678 # Word Order - Big Byte Order - Little # word1 =0x3412 word2 = 0x7856 # Word Order - Little Byte Order - Big # word1 = 0x5678 word2 = 0x1234 # Word Order - Little Byte Order - Little # word1 =0x7856 word2 = 0x3412 # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # # ----------------------------------------------------------------------- # builder = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little) builder.add_string('abcdefgh') builder.add_bits([0, 1, 0, 1, 1, 0, 1, 0]) builder.add_8bit_int(-0x12) builder.add_8bit_uint(0x12) builder.add_16bit_int(-0x5678) builder.add_16bit_uint(0x1234) builder.add_32bit_int(-0x1234) builder.add_32bit_uint(0x12345678) builder.add_32bit_float(22.34) builder.add_32bit_float(-22.34) builder.add_64bit_int(-0xDEADBEEF) builder.add_64bit_uint(0x12345678DEADBEEF) builder.add_64bit_uint(0x12345678DEADBEEF) builder.add_64bit_float(123.45) builder.add_64bit_float(-123.45) payload = builder.to_registers() print("-" * 60) print("Writing Registers") print("-" * 60) print(payload) print("\n") payload = builder.build() address = 0 # Can write registers # registers = builder.to_registers() # client.write_registers(address, registers, unit=1) # Or can write encoded binary string client.write_registers(address, payload, skip_encode=True, unit=1) # ----------------------------------------------------------------------- # # If you need to decode a collection of registers in a weird layout, the # payload decoder can help you as well. # # Here we demonstrate decoding a random register layout, unpacked it looks # like the following: # # - a 8 byte string 'abcdefgh' # - a 32 bit float 22.34 # - a 16 bit unsigned int 0x1234 # - another 16 bit unsigned int which we will ignore # - an 8 bit int 0x12 # - an 8 bit bitstring [0,1,0,1,1,0,1,0] # ----------------------------------------------------------------------- # address = 0x0 count = len(payload) result = client.read_holding_registers(address, count, unit=1) print("-" * 60) print("Registers") print("-" * 60) print(result.registers) print("\n") decoder = BinaryPayloadDecoder.fromRegisters(result.registers, byteorder=Endian.Little, wordorder=Endian.Little) decoded = OrderedDict([ ('string', decoder.decode_string(8)), ('bits', decoder.decode_bits()), ('8int', decoder.decode_8bit_int()), ('8uint', decoder.decode_8bit_uint()), ('16int', decoder.decode_16bit_int()), ('16uint', decoder.decode_16bit_uint()), ('32int', decoder.decode_32bit_int()), ('32uint', decoder.decode_32bit_uint()), ('32float', decoder.decode_32bit_float()), ('32float2', decoder.decode_32bit_float()), ('64int', decoder.decode_64bit_int()), ('64uint', decoder.decode_64bit_uint()), ('ignore', decoder.skip_bytes(8)), ('64float', decoder.decode_64bit_float()), ('64float2', decoder.decode_64bit_float()), ]) print("-" * 60) print("Decoded Data") print("-" * 60) for name, value in iteritems(decoded): print("%s\t" % name, hex(value) if isinstance(value, int) else value) # ----------------------------------------------------------------------- # # close the client # ----------------------------------------------------------------------- # client.close() if __name__ == "__main__": run_binary_payload_ex() pymodbus-2.1.0/examples/common/modbus_payload_server.py000077500000000000000000000074651335513467700234610ustar00rootroot00000000000000#!/usr/bin/env python """ Pymodbus Server Payload Example -------------------------------------------------------------------------- If you want to initialize a server context with a complicated memory layout, you can actually use the payload builder. """ # --------------------------------------------------------------------------- # # import the various server implementations # --------------------------------------------------------------------------- # from pymodbus.server.sync import StartTcpServer from pymodbus.device import ModbusDeviceIdentification from pymodbus.datastore import ModbusSequentialDataBlock from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext # --------------------------------------------------------------------------- # # import the payload builder # --------------------------------------------------------------------------- # from pymodbus.constants import Endian from pymodbus.payload import BinaryPayloadDecoder from pymodbus.payload import BinaryPayloadBuilder # --------------------------------------------------------------------------- # # configure the service logging # --------------------------------------------------------------------------- # import logging FORMAT = ('%(asctime)-15s %(threadName)-15s' ' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s') logging.basicConfig(format=FORMAT) log = logging.getLogger() log.setLevel(logging.DEBUG) def run_payload_server(): # ----------------------------------------------------------------------- # # build your payload # ----------------------------------------------------------------------- # builder = BinaryPayloadBuilder(byteorder=Endian.Little, wordorder=Endian.Little) builder.add_string('abcdefgh') builder.add_bits([0, 1, 0, 1, 1, 0, 1, 0]) builder.add_8bit_int(-0x12) builder.add_8bit_uint(0x12) builder.add_16bit_int(-0x5678) builder.add_16bit_uint(0x1234) builder.add_32bit_int(-0x1234) builder.add_32bit_uint(0x12345678) builder.add_32bit_float(22.34) builder.add_32bit_float(-22.34) builder.add_64bit_int(-0xDEADBEEF) builder.add_64bit_uint(0x12345678DEADBEEF) builder.add_64bit_uint(0xDEADBEEFDEADBEED) builder.add_64bit_float(123.45) builder.add_64bit_float(-123.45) # ----------------------------------------------------------------------- # # use that payload in the data store # ----------------------------------------------------------------------- # # Here we use the same reference block for each underlying store. # ----------------------------------------------------------------------- # block = ModbusSequentialDataBlock(1, builder.to_registers()) store = ModbusSlaveContext(di=block, co=block, hr=block, ir=block) context = ModbusServerContext(slaves=store, single=True) # ----------------------------------------------------------------------- # # initialize the server information # ----------------------------------------------------------------------- # # If you don't set this or any fields, they are defaulted to empty strings. # ----------------------------------------------------------------------- # identity = ModbusDeviceIdentification() identity.VendorName = 'Pymodbus' identity.ProductCode = 'PM' identity.VendorUrl = 'http://github.com/bashwork/pymodbus/' identity.ProductName = 'Pymodbus Server' identity.ModelName = 'Pymodbus Server' identity.MajorMinorRevision = '1.5' # ----------------------------------------------------------------------- # # run the server you want # ----------------------------------------------------------------------- # StartTcpServer(context, identity=identity, address=("localhost", 5020)) if __name__ == "__main__": run_payload_server() pymodbus-2.1.0/examples/common/performance.py000077500000000000000000000077211335513467700213650ustar00rootroot00000000000000#!/usr/bin/env python """ Pymodbus Performance Example -------------------------------------------------------------------------- The following is an quick performance check of the synchronous modbus client. """ # --------------------------------------------------------------------------- # # import the necessary modules # --------------------------------------------------------------------------- # from __future__ import print_function import logging, os from time import time # from pymodbus.client.sync import ModbusTcpClient from pymodbus.client.sync import ModbusSerialClient try: from multiprocessing import log_to_stderr except ImportError: import logging logging.basicConfig() log_to_stderr = logging.getLogger # --------------------------------------------------------------------------- # # choose between threads or processes # --------------------------------------------------------------------------- # #from multiprocessing import Process as Worker from threading import Thread as Worker from threading import Lock _thread_lock = Lock() # --------------------------------------------------------------------------- # # initialize the test # --------------------------------------------------------------------------- # # Modify the parameters below to control how we are testing the client: # # * workers - the number of workers to use at once # * cycles - the total number of requests to send # * host - the host to send the requests to # --------------------------------------------------------------------------- # workers = 10 cycles = 1000 host = '127.0.0.1' # --------------------------------------------------------------------------- # # perform the test # --------------------------------------------------------------------------- # # This test is written such that it can be used by many threads of processes # although it should be noted that there are performance penalties # associated with each strategy. # --------------------------------------------------------------------------- # def single_client_test(host, cycles): """ Performs a single threaded test of a synchronous client against the specified host :param host: The host to connect to :param cycles: The number of iterations to perform """ logger = log_to_stderr() logger.setLevel(logging.DEBUG) logger.debug("starting worker: %d" % os.getpid()) try: count = 0 # client = ModbusTcpClient(host, port=5020) client = ModbusSerialClient(method="rtu", port="/dev/ttyp0", baudrate=9600) while count < cycles: with _thread_lock: client.read_holding_registers(10, 1, unit=1).registers[0] count += 1 except: logger.exception("failed to run test successfully") logger.debug("finished worker: %d" % os.getpid()) # --------------------------------------------------------------------------- # # run our test and check results # --------------------------------------------------------------------------- # # We shard the total number of requests to perform between the number of # threads that was specified. We then start all the threads and block on # them to finish. This may need to switch to another mechanism to signal # finished as the process/thread start up/shut down may skew the test a bit. # RTU 32 requests/second @9600 # TCP 31430 requests/second # --------------------------------------------------------------------------- # if __name__ == "__main__": args = (host, int(cycles * 1.0 / workers)) procs = [Worker(target=single_client_test, args=args) for _ in range(workers)] start = time() any(p.start() for p in procs) # start the workers any(p.join() for p in procs) # wait for the workers to finish stop = time() print("%d requests/second" % ((1.0 * cycles) / (stop - start))) print("time taken to complete %s cycle by " "%s workers is %s seconds" % (cycles, workers, stop-start)) pymodbus-2.1.0/examples/common/synchronous_client.py000077500000000000000000000164031335513467700230110ustar00rootroot00000000000000#!/usr/bin/env python """ Pymodbus Synchronous Client Examples -------------------------------------------------------------------------- The following is an example of how to use the synchronous modbus client implementation from pymodbus. It should be noted that the client can also be used with the guard construct that is available in python 2.5 and up:: with ModbusClient('127.0.0.1') as client: result = client.read_coils(1,10) print result """ # --------------------------------------------------------------------------- # # import the various server implementations # --------------------------------------------------------------------------- # # from pymodbus.client.sync import ModbusTcpClient as ModbusClient # from pymodbus.client.sync import ModbusUdpClient as ModbusClient from pymodbus.client.sync import ModbusSerialClient as ModbusClient # --------------------------------------------------------------------------- # # configure the client logging # --------------------------------------------------------------------------- # import logging FORMAT = ('%(asctime)-15s %(threadName)-15s ' '%(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s') logging.basicConfig(format=FORMAT) log = logging.getLogger() log.setLevel(logging.DEBUG) UNIT = 0x1 def run_sync_client(): # ------------------------------------------------------------------------# # choose the client you want # ------------------------------------------------------------------------# # make sure to start an implementation to hit against. For this # you can use an existing device, the reference implementation in the tools # directory, or start a pymodbus server. # # If you use the UDP or TCP clients, you can override the framer being used # to use a custom implementation (say RTU over TCP). By default they use # the socket framer:: # # client = ModbusClient('localhost', port=5020, framer=ModbusRtuFramer) # # It should be noted that you can supply an ipv4 or an ipv6 host address # for both the UDP and TCP clients. # # There are also other options that can be set on the client that controls # how transactions are performed. The current ones are: # # * retries - Specify how many retries to allow per transaction (default=3) # * retry_on_empty - Is an empty response a retry (default = False) # * source_address - Specifies the TCP source address to bind to # # Here is an example of using these options:: # # client = ModbusClient('localhost', retries=3, retry_on_empty=True) # ------------------------------------------------------------------------# client = ModbusClient('localhost', port=5020) # from pymodbus.transaction import ModbusRtuFramer # client = ModbusClient('localhost', port=5020, framer=ModbusRtuFramer) # client = ModbusClient(method='binary', port='/dev/ptyp0', timeout=1) # client = ModbusClient(method='ascii', port='/dev/ptyp0', timeout=1) # client = ModbusClient(method='rtu', port='/dev/ptyp0', timeout=1, # baudrate=9600) client.connect() # ------------------------------------------------------------------------# # specify slave to query # ------------------------------------------------------------------------# # The slave to query is specified in an optional parameter for each # individual request. This can be done by specifying the `unit` parameter # which defaults to `0x00` # ----------------------------------------------------------------------- # log.debug("Reading Coils") rr = client.read_coils(1, 1, unit=UNIT) log.debug(rr) # ----------------------------------------------------------------------- # # example requests # ----------------------------------------------------------------------- # # simply call the methods that you would like to use. An example session # is displayed below along with some assert checks. Note that some modbus # implementations differentiate holding/input discrete/coils and as such # you will not be able to write to these, therefore the starting values # are not known to these tests. Furthermore, some use the same memory # blocks for the two sets, so a change to one is a change to the other. # Keep both of these cases in mind when testing as the following will # _only_ pass with the supplied async modbus server (script supplied). # ----------------------------------------------------------------------- # log.debug("Write to a Coil and read back") rq = client.write_coil(0, True, unit=UNIT) rr = client.read_coils(0, 1, unit=UNIT) assert(not rq.isError()) # test that we are not an error assert(rr.bits[0] == True) # test the expected value log.debug("Write to multiple coils and read back- test 1") rq = client.write_coils(1, [True]*8, unit=UNIT) assert(not rq.isError()) # test that we are not an error rr = client.read_coils(1, 21, unit=UNIT) assert(not rr.isError()) # test that we are not an error resp = [True]*21 # 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). resp.extend([False]*3) assert(rr.bits == resp) # test the expected value log.debug("Write to multiple coils and read back - test 2") rq = client.write_coils(1, [False]*8, unit=UNIT) rr = client.read_coils(1, 8, unit=UNIT) assert(not rq.isError()) # test that we are not an error assert(rr.bits == [False]*8) # test the expected value log.debug("Read discrete inputs") rr = client.read_discrete_inputs(0, 8, unit=UNIT) assert(not rq.isError()) # test that we are not an error log.debug("Write to a holding register and read back") rq = client.write_register(1, 10, unit=UNIT) rr = client.read_holding_registers(1, 1, unit=UNIT) assert(not rq.isError()) # test that we are not an error assert(rr.registers[0] == 10) # test the expected value log.debug("Write to multiple holding registers and read back") rq = client.write_registers(1, [10]*8, unit=UNIT) rr = client.read_holding_registers(1, 8, unit=UNIT) assert(not rq.isError()) # test that we are not an error assert(rr.registers == [10]*8) # test the expected value log.debug("Read input registers") rr = client.read_input_registers(1, 8, unit=UNIT) assert(not rq.isError()) # test that we are not an error arguments = { 'read_address': 1, 'read_count': 8, 'write_address': 1, 'write_registers': [20]*8, } log.debug("Read write registeres simulataneously") rq = client.readwrite_registers(unit=UNIT, **arguments) rr = client.read_holding_registers(1, 8, unit=UNIT) assert(not rq.isError()) # test that we are not an error assert(rq.registers == [20]*8) # test the expected value assert(rr.registers == [20]*8) # test the expected value # ----------------------------------------------------------------------- # # close the client # ----------------------------------------------------------------------- # client.close() if __name__ == "__main__": run_sync_client() pymodbus-2.1.0/examples/common/synchronous_client_ext.py000077500000000000000000000234421335513467700236720ustar00rootroot00000000000000#!/usr/bin/env python """ Pymodbus Synchronous Client Extended Examples -------------------------------------------------------------------------- The following is an example of how to use the synchronous modbus client implementation from pymodbus to perform the extended portions of the modbus protocol. """ # --------------------------------------------------------------------------- # # import the various server implementations # --------------------------------------------------------------------------- # # from pymodbus.client.sync import ModbusTcpClient as ModbusClient # from pymodbus.client.sync import ModbusUdpClient as ModbusClient from pymodbus.client.sync import ModbusSerialClient as ModbusClient # --------------------------------------------------------------------------- # # import the extended messages to perform # --------------------------------------------------------------------------- # from pymodbus.diag_message import * from pymodbus.file_message import * from pymodbus.other_message import * from pymodbus.mei_message import * # --------------------------------------------------------------------------- # # configure the client logging # --------------------------------------------------------------------------- # import logging FORMAT = ('%(asctime)-15s %(threadName)-15s ' '%(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s') logging.basicConfig(format=FORMAT) log = logging.getLogger() log.setLevel(logging.DEBUG) UNIT = 0x01 def execute_extended_requests(): # ------------------------------------------------------------------------# # choose the client you want # ------------------------------------------------------------------------# # make sure to start an implementation to hit against. For this # you can use an existing device, the reference implementation in the tools # directory, or start a pymodbus server. # # It should be noted that you can supply an ipv4 or an ipv6 host address # for both the UDP and TCP clients. # ------------------------------------------------------------------------# client = ModbusClient(method='rtu', port="/dev/ptyp0") # client = ModbusClient(method='ascii', port="/dev/ptyp0") # client = ModbusClient(method='binary', port="/dev/ptyp0") # client = ModbusClient('127.0.0.1', port=5020) # from pymodbus.transaction import ModbusRtuFramer # client = ModbusClient('127.0.0.1', port=5020, framer=ModbusRtuFramer) client.connect() # ----------------------------------------------------------------------- # # extra requests # ----------------------------------------------------------------------- # # If you are performing a request that is not available in the client # mixin, you have to perform the request like this instead:: # # from pymodbus.diag_message import ClearCountersRequest # from pymodbus.diag_message import ClearCountersResponse # # request = ClearCountersRequest() # response = client.execute(request) # if isinstance(response, ClearCountersResponse): # ... do something with the response # # # What follows is a listing of all the supported methods. Feel free to # comment, uncomment, or modify each result set to match with your ref. # ----------------------------------------------------------------------- # # ----------------------------------------------------------------------- # # information requests # ----------------------------------------------------------------------- # log.debug("Running ReadDeviceInformationRequest") rq = ReadDeviceInformationRequest(unit=UNIT) rr = client.execute(rq) log.debug(rr) # assert(rr == None) # not supported by reference # assert (not rr.isError()) # test that we are not an error # assert (rr.information[0] == b'Pymodbus') # test the vendor name # assert (rr.information[1] == b'PM') # test the product code # assert (rr.information[2] == b'1.0') # test the code revision log.debug("Running ReportSlaveIdRequest") rq = ReportSlaveIdRequest(unit=UNIT) rr = client.execute(rq) log.debug(rr) # assert(rr == None) # not supported by reference # assert(not rr.isError()) # test that we are not an error # assert(rr.identifier == 0x00) # test the slave identifier # assert(rr.status == 0x00) # test that the status is ok log.debug("Running ReadExceptionStatusRequest") rq = ReadExceptionStatusRequest(unit=UNIT) rr = client.execute(rq) log.debug(rr) # assert(rr == None) # not supported by reference # assert(not rr.isError()) # test that we are not an error # assert(rr.status == 0x55) # test the status code log.debug("Running GetCommEventCounterRequest") rq = GetCommEventCounterRequest(unit=UNIT) rr = client.execute(rq) log.debug(rr) # assert(rr == None) # not supported by reference # assert(not rr.isError()) # test that we are not an error # assert(rr.status == True) # test the status code # assert(rr.count == 0x00) # test the status code log.debug("Running GetCommEventLogRequest") rq = GetCommEventLogRequest(unit=UNIT) rr = client.execute(rq) log.debug(rr) # assert(rr == None) # not supported by reference # assert(not rr.isError()) # test that we are not an error # assert(rr.status == True) # test the status code # assert(rr.event_count == 0x00) # test the number of events # assert(rr.message_count == 0x00) # test the number of messages # assert(len(rr.events) == 0x00) # test the number of events # ------------------------------------------------------------------------# # diagnostic requests # ------------------------------------------------------------------------# log.debug("Running ReturnQueryDataRequest") rq = ReturnQueryDataRequest(unit=UNIT) rr = client.execute(rq) log.debug(rr) # assert(rr == None) # not supported by reference # assert(rr.message[0] == 0x0000) # test the resulting message log.debug("Running RestartCommunicationsOptionRequest") rq = RestartCommunicationsOptionRequest(unit=UNIT) rr = client.execute(rq) log.debug(rr) # assert(rr == None) # not supported by reference # assert(rr.message == 0x0000) # test the resulting message log.debug("Running ReturnDiagnosticRegisterRequest") rq = ReturnDiagnosticRegisterRequest(unit=UNIT) rr = client.execute(rq) log.debug(rr) # assert(rr == None) # not supported by reference log.debug("Running ChangeAsciiInputDelimiterRequest") rq = ChangeAsciiInputDelimiterRequest(unit=UNIT) rr = client.execute(rq) log.debug(rr) # assert(rr == None) # not supported by reference log.debug("Running ForceListenOnlyModeRequest") rq = ForceListenOnlyModeRequest(unit=UNIT) rr = client.execute(rq) # does not send a response log.debug(rr) log.debug("Running ClearCountersRequest") rq = ClearCountersRequest() rr = client.execute(rq) log.debug(rr) # assert(rr == None) # not supported by reference log.debug("Running ReturnBusCommunicationErrorCountRequest") rq = ReturnBusCommunicationErrorCountRequest(unit=UNIT) rr = client.execute(rq) log.debug(rr) # assert(rr == None) # not supported by reference log.debug("Running ReturnBusExceptionErrorCountRequest") rq = ReturnBusExceptionErrorCountRequest(unit=UNIT) rr = client.execute(rq) log.debug(rr) # assert(rr == None) # not supported by reference log.debug("Running ReturnSlaveMessageCountRequest") rq = ReturnSlaveMessageCountRequest(unit=UNIT) rr = client.execute(rq) log.debug(rr) # assert(rr == None) # not supported by reference log.debug("Running ReturnSlaveNoResponseCountRequest") rq = ReturnSlaveNoResponseCountRequest(unit=UNIT) rr = client.execute(rq) log.debug(rr) # assert(rr == None) # not supported by reference log.debug("Running ReturnSlaveNAKCountRequest") rq = ReturnSlaveNAKCountRequest(unit=UNIT) rr = client.execute(rq) log.debug(rr) # assert(rr == None) # not supported by reference log.debug("Running ReturnSlaveBusyCountRequest") rq = ReturnSlaveBusyCountRequest(unit=UNIT) rr = client.execute(rq) log.debug(rr) # assert(rr == None) # not supported by reference log.debug("Running ReturnSlaveBusCharacterOverrunCountRequest") rq = ReturnSlaveBusCharacterOverrunCountRequest(unit=UNIT) rr = client.execute(rq) log.debug(rr) # assert(rr == None) # not supported by reference log.debug("Running ReturnIopOverrunCountRequest") rq = ReturnIopOverrunCountRequest(unit=UNIT) rr = client.execute(rq) log.debug(rr) # assert(rr == None) # not supported by reference log.debug("Running ClearOverrunCountRequest") rq = ClearOverrunCountRequest(unit=UNIT) rr = client.execute(rq) log.debug(rr) # assert(rr == None) # not supported by reference log.debug("Running GetClearModbusPlusRequest") rq = GetClearModbusPlusRequest(unit=UNIT) rr = client.execute(rq) log.debug(rr) # assert(rr == None) # not supported by reference # ------------------------------------------------------------------------# # close the client # ------------------------------------------------------------------------# client.close() if __name__ == "__main__": execute_extended_requests() pymodbus-2.1.0/examples/common/synchronous_server.py000077500000000000000000000141471335513467700230440ustar00rootroot00000000000000#!/usr/bin/env python """ Pymodbus Synchronous Server Example -------------------------------------------------------------------------- The synchronous server is implemented in pure python without any third party libraries (unless you need to use the serial protocols which require pyserial). This is helpful in constrained or old environments where using twisted is just not feasible. What follows is an example of its use: """ # --------------------------------------------------------------------------- # # import the various server implementations # --------------------------------------------------------------------------- # from pymodbus.server.sync import StartTcpServer from pymodbus.server.sync import StartUdpServer from pymodbus.server.sync import StartSerialServer from pymodbus.device import ModbusDeviceIdentification from pymodbus.datastore import ModbusSequentialDataBlock, ModbusSparseDataBlock from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext from pymodbus.transaction import ModbusRtuFramer, ModbusBinaryFramer # --------------------------------------------------------------------------- # # configure the service logging # --------------------------------------------------------------------------- # import logging FORMAT = ('%(asctime)-15s %(threadName)-15s' ' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s') logging.basicConfig(format=FORMAT) log = logging.getLogger() log.setLevel(logging.DEBUG) def run_server(): # ----------------------------------------------------------------------- # # initialize your data store # ----------------------------------------------------------------------- # # The datastores only respond to the addresses that they are initialized to # Therefore, if you initialize a DataBlock to addresses of 0x00 to 0xFF, a # request to 0x100 will respond with an invalid address exception. This is # because many devices exhibit this kind of behavior (but not all):: # # block = ModbusSequentialDataBlock(0x00, [0]*0xff) # # Continuing, you can choose to use a sequential or a sparse DataBlock in # your data context. The difference is that the sequential has no gaps in # the data while the sparse can. Once again, there are devices that exhibit # both forms of behavior:: # # block = ModbusSparseDataBlock({0x00: 0, 0x05: 1}) # block = ModbusSequentialDataBlock(0x00, [0]*5) # # Alternately, you can use the factory methods to initialize the DataBlocks # or simply do not pass them to have them initialized to 0x00 on the full # address range:: # # store = ModbusSlaveContext(di = ModbusSequentialDataBlock.create()) # store = ModbusSlaveContext() # # Finally, you are allowed to use the same DataBlock reference for every # table or you may use a separate DataBlock for each table. # This depends if you would like functions to be able to access and modify # the same data or not:: # # block = ModbusSequentialDataBlock(0x00, [0]*0xff) # store = ModbusSlaveContext(di=block, co=block, hr=block, ir=block) # # The server then makes use of a server context that allows the server to # respond with different slave contexts for different unit ids. By default # it will return the same context for every unit id supplied (broadcast # mode). # However, this can be overloaded by setting the single flag to False and # then supplying a dictionary of unit id to context mapping:: # # slaves = { # 0x01: ModbusSlaveContext(...), # 0x02: ModbusSlaveContext(...), # 0x03: ModbusSlaveContext(...), # } # context = ModbusServerContext(slaves=slaves, single=False) # # The slave context can also be initialized in zero_mode which means that a # request to address(0-7) will map to the address (0-7). The default is # False which is based on section 4.4 of the specification, so address(0-7) # will map to (1-8):: # # store = ModbusSlaveContext(..., zero_mode=True) # ----------------------------------------------------------------------- # store = ModbusSlaveContext( di=ModbusSequentialDataBlock(0, [17]*100), co=ModbusSequentialDataBlock(0, [17]*100), hr=ModbusSequentialDataBlock(0, [17]*100), ir=ModbusSequentialDataBlock(0, [17]*100)) context = ModbusServerContext(slaves=store, single=True) # ----------------------------------------------------------------------- # # initialize the server information # ----------------------------------------------------------------------- # # If you don't set this or any fields, they are defaulted to empty strings. # ----------------------------------------------------------------------- # identity = ModbusDeviceIdentification() identity.VendorName = 'Pymodbus' identity.ProductCode = 'PM' identity.VendorUrl = 'http://github.com/riptideio/pymodbus/' identity.ProductName = 'Pymodbus Server' identity.ModelName = 'Pymodbus Server' identity.MajorMinorRevision = '1.5' # ----------------------------------------------------------------------- # # run the server you want # ----------------------------------------------------------------------- # # Tcp: StartTcpServer(context, identity=identity, address=("localhost", 5020)) # TCP with different framer # StartTcpServer(context, identity=identity, # framer=ModbusRtuFramer, address=("0.0.0.0", 5020)) # Udp: # StartUdpServer(context, identity=identity, address=("0.0.0.0", 5020)) # Ascii: # StartSerialServer(context, identity=identity, # port='/dev/ttyp0', timeout=1) # RTU: # StartSerialServer(context, framer=ModbusRtuFramer, identity=identity, # port='/dev/ttyp0', timeout=.005, baudrate=9600) # Binary # StartSerialServer(context, # identity=identity, # framer=ModbusBinaryFramer, # port='/dev/ttyp0', # timeout=1) if __name__ == "__main__": run_server() pymodbus-2.1.0/examples/common/updating_server.py000077500000000000000000000071771335513467700222720ustar00rootroot00000000000000#!/usr/bin/env python """ Pymodbus Server With Updating Thread -------------------------------------------------------------------------- This is an example of having a background thread updating the context while the server is operating. This can also be done with a python thread:: from threading import Thread thread = Thread(target=updating_writer, args=(context,)) thread.start() """ # --------------------------------------------------------------------------- # # import the modbus libraries we need # --------------------------------------------------------------------------- # from pymodbus.server.async import StartTcpServer from pymodbus.device import ModbusDeviceIdentification from pymodbus.datastore import ModbusSequentialDataBlock from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer # --------------------------------------------------------------------------- # # import the twisted libraries we need # --------------------------------------------------------------------------- # from twisted.internet.task import LoopingCall # --------------------------------------------------------------------------- # # configure the service logging # --------------------------------------------------------------------------- # import logging logging.basicConfig() log = logging.getLogger() log.setLevel(logging.DEBUG) # --------------------------------------------------------------------------- # # define your callback process # --------------------------------------------------------------------------- # def updating_writer(a): """ A worker process that runs every so often and updates live values of the context. It should be noted that there is a race condition for the update. :param arguments: The input arguments to the call """ log.debug("updating the context") context = a[0] register = 3 slave_id = 0x00 address = 0x10 values = context[slave_id].getValues(register, address, count=5) values = [v + 1 for v in values] log.debug("new values: " + str(values)) context[slave_id].setValues(register, address, values) def run_updating_server(): # ----------------------------------------------------------------------- # # initialize your data store # ----------------------------------------------------------------------- # store = ModbusSlaveContext( di=ModbusSequentialDataBlock(0, [17]*100), co=ModbusSequentialDataBlock(0, [17]*100), hr=ModbusSequentialDataBlock(0, [17]*100), ir=ModbusSequentialDataBlock(0, [17]*100)) context = ModbusServerContext(slaves=store, single=True) # ----------------------------------------------------------------------- # # initialize the server information # ----------------------------------------------------------------------- # identity = ModbusDeviceIdentification() identity.VendorName = 'pymodbus' identity.ProductCode = 'PM' identity.VendorUrl = 'http://github.com/bashwork/pymodbus/' identity.ProductName = 'pymodbus Server' identity.ModelName = 'pymodbus Server' identity.MajorMinorRevision = '1.0' # ----------------------------------------------------------------------- # # run the server you want # ----------------------------------------------------------------------- # time = 5 # 5 seconds delay loop = LoopingCall(f=updating_writer, a=(context,)) loop.start(time, now=False) # initially delay by time StartTcpServer(context, identity=identity, address=("localhost", 5020)) if __name__ == "__main__": run_updating_server() pymodbus-2.1.0/examples/contrib/000077500000000000000000000000001335513467700166505ustar00rootroot00000000000000pymodbus-2.1.0/examples/contrib/README.rst000066400000000000000000000032521335513467700203410ustar00rootroot00000000000000============================================================ Contributed Implementations ============================================================ There are a few example implementations of custom utilities interacting with the pymodbus library just to show what is possible. ------------------------------------------------------------ SqlAlchemy Database Datastore Backend ------------------------------------------------------------ This module allows one to use any database available through the sqlalchemy package as a datastore for the modbus server. This could be useful to have many servers who have data they agree upon and is transactional. ------------------------------------------------------------ Redis Datastore Backend ------------------------------------------------------------ This module allows one to use redis as a modbus server datastore backend. This achieves the same thing as the sqlalchemy backend, however, it is much more lightweight and easier to set up. ------------------------------------------------------------ Binary Coded Decimal Payload ------------------------------------------------------------ This module allows one to write binary coded decimal data to the modbus server using the payload encoder/decoder interfaces. ------------------------------------------------------------ Message Generator and Parser ------------------------------------------------------------ These are two utilities that can be used to create a number of modbus messages for any of the available protocols as well as to decode the messages and print descriptive text about them. Also included are a number of request and response messages in tx-messages and rx-messages. pymodbus-2.1.0/examples/contrib/asynchronous_asyncio_serial_client.py000077500000000000000000000125431335513467700264070ustar00rootroot00000000000000from pymodbus.compat import IS_PYTHON3, PYTHON_VERSION if IS_PYTHON3 and PYTHON_VERSION >= (3, 4): import asyncio from serial_asyncio import create_serial_connection from pymodbus.client.async.asyncio import ModbusClientProtocol from pymodbus.transaction import ModbusAsciiFramer, ModbusRtuFramer from pymodbus.factory import ClientDecoder else: import sys sys.stderr("This example needs to be run only on python 3.4 and above") sys.exit(1) # ----------------------------------------------------------------------- # # configure the client logging # ----------------------------------------------------------------------- # import logging logging.basicConfig() log = logging.getLogger() log.setLevel(logging.DEBUG) UNIT = 0x01 async def start_async_test(client): # ----------------------------------------------------------------------- # # specify slave to query # ----------------------------------------------------------------------- # # The slave to query is specified in an optional parameter for each # individual request. This can be done by specifying the `unit` parameter # which defaults to `0x00` # ----------------------------------------------------------------------- # log.debug("Reading Coils") rr = client.read_coils(1, 1, unit=UNIT) # ----------------------------------------------------------------------- # # example requests # ----------------------------------------------------------------------- # # simply call the methods that you would like to use. An example session # is displayed below along with some assert checks. Note that some modbus # implementations differentiate holding/input discrete/coils and as such # you will not be able to write to these, therefore the starting values # are not known to these tests. Furthermore, some use the same memory # blocks for the two sets, so a change to one is a change to the other. # Keep both of these cases in mind when testing as the following will # _only_ pass with the supplied async modbus server (script supplied). # ----------------------------------------------------------------------- # log.debug("Write to a Coil and read back") rq = await client.write_coil(0, True, unit=UNIT) rr = await client.read_coils(0, 1, unit=UNIT) assert(rq.function_code < 0x80) # test that we are not an error assert(rr.bits[0] == True) # test the expected value log.debug("Write to multiple coils and read back- test 1") rq = await client.write_coils(1, [True]*8, unit=UNIT) assert(rq.function_code < 0x80) # test that we are not an error rr = await client.read_coils(1, 21, unit=UNIT) assert(rr.function_code < 0x80) # test that we are not an error resp = [True]*21 # 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). resp.extend([False]*3) assert(rr.bits == resp) # test the expected value log.debug("Write to multiple coils and read back - test 2") rq = await client.write_coils(1, [False]*8, unit=UNIT) rr = await client.read_coils(1, 8, unit=UNIT) assert(rq.function_code < 0x80) # test that we are not an error assert(rr.bits == [False]*8) # test the expected value log.debug("Read discrete inputs") rr = await client.read_discrete_inputs(0, 8, unit=UNIT) assert(rq.function_code < 0x80) # test that we are not an error log.debug("Write to a holding register and read back") rq = await client.write_register(1, 10, unit=UNIT) rr = await client.read_holding_registers(1, 1, unit=UNIT) assert(rq.function_code < 0x80) # test that we are not an error assert(rr.registers[0] == 10) # test the expected value log.debug("Write to multiple holding registers and read back") rq = await client.write_registers(1, [10]*8, unit=UNIT) rr = await client.read_holding_registers(1, 8, unit=UNIT) assert(rq.function_code < 0x80) # test that we are not an error assert(rr.registers == [10]*8) # test the expected value log.debug("Read input registers") rr = await client.read_input_registers(1, 8, unit=UNIT) assert(rq.function_code < 0x80) # test that we are not an error arguments = { 'read_address': 1, 'read_count': 8, 'write_address': 1, 'write_registers': [20]*8, } log.debug("Read write registeres simulataneously") rq = await client.readwrite_registers(unit=UNIT, **arguments) rr = await client.read_holding_registers(1, 8, unit=UNIT) assert(rq.function_code < 0x80) # test that we are not an error assert(rq.registers == [20]*8) # test the expected value assert(rr.registers == [20]*8) # test the expected value # create_serial_connection doesn't allow to pass arguments # to protocol so that this is kind of workaround def make_protocol(): return ModbusClientProtocol(framer=ModbusRtuFramer(ClientDecoder())) if __name__ == '__main__': loop = asyncio.get_event_loop() coro = create_serial_connection(loop, make_protocol, '/dev/ptyp0', baudrate=9600) transport, protocol = loop.run_until_complete(asyncio.gather(coro))[0] loop.run_until_complete(start_async_test(protocol)) loop.close() pymodbus-2.1.0/examples/contrib/bcd_payload.py000066400000000000000000000153441335513467700214720ustar00rootroot00000000000000""" Modbus BCD Payload Builder ----------------------------------------------------------- This is an example of building a custom payload builder that can be used in the pymodbus library. Below is a simple binary coded decimal builder and decoder. """ from struct import pack, unpack from pymodbus.constants import Endian from pymodbus.interfaces import IPayloadBuilder from pymodbus.utilities import pack_bitstring from pymodbus.utilities import unpack_bitstring from pymodbus.exceptions import ParameterException from pymodbus.payload import BinaryPayloadDecoder def convert_to_bcd(decimal): """ Converts a decimal value to a bcd value :param value: The decimal value to to pack into bcd :returns: The number in bcd form """ place, bcd = 0, 0 while decimal > 0: nibble = decimal % 10 bcd += nibble << place decimal /= 10 place += 4 return bcd def convert_from_bcd(bcd): """ Converts a bcd value to a decimal value :param value: The value to unpack from bcd :returns: The number in decimal form """ place, decimal = 1, 0 while bcd > 0: nibble = bcd & 0xf decimal += nibble * place bcd >>= 4 place *= 10 return decimal def count_bcd_digits(bcd): """ Count the number of digits in a bcd value :param bcd: The bcd number to count the digits of :returns: The number of digits in the bcd string """ count = 0 while bcd > 0: count += 1 bcd >>= 4 return count class BcdPayloadBuilder(IPayloadBuilder): """ A utility that helps build binary coded decimal payload messages to be written with the various modbus messages. example:: builder = BcdPayloadBuilder() builder.add_number(1) builder.add_number(int(2.234 * 1000)) payload = builder.build() """ 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 def __str__(self): """ Return the payload buffer as a string :returns: The payload buffer as a string """ return ''.join(self._payload) def reset(self): """ Reset the payload buffer """ self._payload = [] 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 = str(self) length = len(string) string = string + ('\x00' * (length % 2)) return [string[i:i+2] for i in range(0, length, 2)] 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) def add_number(self, value, size=None): """ Adds any 8bit numeric type to the buffer :param value: The value to add to the buffer """ encoded = [] value = convert_to_bcd(value) size = size or count_bcd_digits(value) while size > 0: nibble = value & 0xf encoded.append(pack('B', nibble)) value >>= 4 size -= 1 self._payload.extend(encoded) def add_string(self, value): """ Adds a string to the buffer :param value: The value to add to the buffer """ self._payload.append(value) class BcdPayloadDecoder(object): """ A utility that helps decode binary coded decimal payload messages from a modbus reponse message. What follows is a simple example:: decoder = BcdPayloadDecoder(payload) first = decoder.decode_int(2) second = decoder.decode_int(5) / 100 """ def __init__(self, payload): """ Initialize a new payload decoder :param payload: The payload to decode with """ self._payload = payload self._pointer = 0x00 @staticmethod def fromRegisters(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 = ''.join(pack('>H', x) for x in registers) return BinaryPayloadDecoder(payload, endian) raise ParameterException('Invalid collection of registers supplied') @staticmethod def fromCoils(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 BinaryPayloadDecoder(payload, endian) raise ParameterException('Invalid collection of coils supplied') def reset(self): """ Reset the decoder pointer back to the start """ self._pointer = 0x00 def decode_int(self, size=1): """ Decodes a int or long from the buffer """ self._pointer += size handle = self._payload[self._pointer - size:self._pointer] return convert_from_bcd(handle) def decode_bits(self): """ Decodes a byte worth of bits from the buffer """ self._pointer += 1 handle = self._payload[self._pointer - 1:self._pointer] return unpack_bitstring(handle) 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__ = ["BcdPayloadBuilder", "BcdPayloadDecoder"] pymodbus-2.1.0/examples/contrib/concurrent_client.py000077500000000000000000000240251335513467700227500ustar00rootroot00000000000000#!/usr/bin/env python """ Concurrent Modbus Client --------------------------------------------------------------------------- This is an example of writing a high performance modbus client that allows a high level of concurrency by using worker threads/processes to handle writing/reading from one or more client handles at once. """ # -------------------------------------------------------------------------- # # import system libraries # -------------------------------------------------------------------------- # import multiprocessing import threading import itertools from collections import namedtuple from pymodbus.compat import IS_PYTHON3 # we are using the future from the concurrent.futures released with # python3. Alternatively we will try the backported library:: # pip install futures try: from concurrent.futures import Future except ImportError: from futures import Future # -------------------------------------------------------------------------- # # import neccessary modbus libraries # -------------------------------------------------------------------------- # from pymodbus.client.common import ModbusClientMixin # -------------------------------------------------------------------------- # # configure the client logging # -------------------------------------------------------------------------- # import logging log = logging.getLogger("pymodbus") log.setLevel(logging.DEBUG) logging.basicConfig() # -------------------------------------------------------------------------- # # Initialize out concurrency primitives # -------------------------------------------------------------------------- # class _Primitives(object): """ This is a helper class used to group the threading primitives depending on the type of worker situation we want to run (threads or processes). """ def __init__(self, **kwargs): self.queue = kwargs.get('queue') self.event = kwargs.get('event') self.worker = kwargs.get('worker') @classmethod def create(cls, in_process=False): """ Initialize a new instance of the concurrency primitives. :param in_process: True for threaded, False for processes :returns: An initialized instance of concurrency primitives """ if in_process: if IS_PYTHON3: from queue import Queue else: from Queue import Queue from threading import Thread from threading import Event return cls(queue=Queue, event=Event, worker=Thread) else: from multiprocessing import Queue from multiprocessing import Event from multiprocessing import Process return cls(queue=Queue, event=Event, worker=Process) # -------------------------------------------------------------------------- # # Define our data transfer objects # -------------------------------------------------------------------------- # # These will be used to serialize state between the various workers. # We use named tuples here as they are very lightweight while giving us # all the benefits of classes. # -------------------------------------------------------------------------- # WorkRequest = namedtuple('WorkRequest', 'request, work_id') WorkResponse = namedtuple('WorkResponse', 'is_exception, work_id, response') # -------------------------------------------------------------------------- # # Define our worker processes # -------------------------------------------------------------------------- # def _client_worker_process(factory, input_queue, output_queue, is_shutdown): """ This worker process takes input requests, issues them on its client handle, and then sends the client response (success or failure) to the manager to deliver back to the application. It should be noted that there are N of these workers and they can be run in process or out of process as all the state serializes. :param factory: A client factory used to create a new client :param input_queue: The queue to pull new requests to issue :param output_queue: The queue to place client responses :param is_shutdown: Condition variable marking process shutdown """ log.info("starting up worker : %s", threading.current_thread()) client = factory() while not is_shutdown.is_set(): try: workitem = input_queue.get(timeout=1) log.debug("dequeue worker request: %s", workitem) if not workitem: continue try: log.debug("executing request on thread: %s", workitem) result = client.execute(workitem.request) output_queue.put(WorkResponse(False, workitem.work_id, result)) except Exception as exception: log.exception("error in worker " "thread: %s", threading.current_thread()) output_queue.put(WorkResponse(True, workitem.work_id, exception)) except Exception as ex: pass log.info("request worker shutting down: %s", threading.current_thread()) def _manager_worker_process(output_queue, futures, is_shutdown): """ This worker process manages taking output responses and tying them back to the future keyed on the initial transaction id. Basically this can be thought of as the delivery worker. It should be noted that there are one of these threads and it must be an in process thread as the futures will not serialize across processes.. :param output_queue: The queue holding output results to return :param futures: The mapping of tid -> future :param is_shutdown: Condition variable marking process shutdown """ log.info("starting up manager worker: %s", threading.current_thread()) while not is_shutdown.is_set(): try: workitem = output_queue.get() future = futures.get(workitem.work_id, None) log.debug("dequeue manager response: %s", workitem) if not future: continue if workitem.is_exception: future.set_exception(workitem.response) else: future.set_result(workitem.response) log.debug("updated future result: %s", future) del futures[workitem.work_id] except Exception as ex: log.exception("error in manager") log.info("manager worker shutting down: %s", threading.current_thread()) # -------------------------------------------------------------------------- # # Define our concurrent client # -------------------------------------------------------------------------- # class ConcurrentClient(ModbusClientMixin): """ This is a high performance client that can be used to read/write a large number of reqeusts at once asyncronously. This operates with a backing worker pool of processes or threads to achieve its performance. """ def __init__(self, **kwargs): """ Initialize a new instance of the client """ worker_count = kwargs.get('count', multiprocessing.cpu_count()) self.factory = kwargs.get('factory') primitives = _Primitives.create(kwargs.get('in_process', False)) self.is_shutdown = primitives.event() # process shutdown condition self.input_queue = primitives.queue() # input requests to process self.output_queue = primitives.queue() # output results to return self.futures = {} # mapping of tid -> future self.workers = [] # handle to our worker threads self.counter = itertools.count() # creating the response manager self.manager = threading.Thread( target=_manager_worker_process, args=(self.output_queue, self.futures, self.is_shutdown) ) self.manager.start() self.workers.append(self.manager) # creating the request workers for i in range(worker_count): worker = primitives.worker( target=_client_worker_process, args=(self.factory, self.input_queue, self.output_queue, self.is_shutdown) ) worker.start() self.workers.append(worker) def shutdown(self): """ Shutdown all the workers being used to concurrently process the requests. """ log.info("stating to shut down workers") self.is_shutdown.set() # to wake up the manager self.output_queue.put(WorkResponse(None, None, None)) for worker in self.workers: worker.join() log.info("finished shutting down workers") def execute(self, request): """ Given a request, enqueue it to be processed and then return a future linked to the response of the call. :param request: The request to execute :returns: A future linked to the call's response """ if IS_PYTHON3: fut, work_id = Future(), next(self.counter) else: fut, work_id = Future(), self.counter.next() self.input_queue.put(WorkRequest(request, work_id)) self.futures[work_id] = fut return fut def execute_silently(self, request): """ Given a write request, enqueue it to be processed without worrying about calling the application back (fire and forget) :param request: The request to execute """ self.input_queue.put(WorkRequest(request, None)) if __name__ == "__main__": from pymodbus.client.sync import ModbusTcpClient def client_factory(): log.debug("creating client for: %s", threading.current_thread()) client = ModbusTcpClient('127.0.0.1', port=5020) client.connect() return client client = ConcurrentClient(factory = client_factory) try: log.info("issuing concurrent requests") futures = [client.read_coils(i * 8, 8) for i in range(10)] log.info("waiting on futures to complete") for future in futures: log.info("future result: %s", future.result(timeout=1)) finally: client.shutdown() pymodbus-2.1.0/examples/contrib/deviceinfo_showcase_client.py000077500000000000000000000117641335513467700246030ustar00rootroot00000000000000#!/usr/bin/env python """ Pymodbus Synchronous Client Example to showcase Device Information -------------------------------------------------------------------------- This client demonstrates the use of Device Information to get information about servers connected to the client. This is part of the MODBUS specification, and uses the MEI 0x2B 0x0E request / response. """ # --------------------------------------------------------------------------- # # import the various server implementations # --------------------------------------------------------------------------- # from pymodbus.client.sync import ModbusTcpClient as ModbusClient # from pymodbus.client.sync import ModbusUdpClient as ModbusClient # from pymodbus.client.sync import ModbusSerialClient as ModbusClient # --------------------------------------------------------------------------- # # import the request # --------------------------------------------------------------------------- # from pymodbus.mei_message import ReadDeviceInformationRequest from pymodbus.device import ModbusDeviceIdentification # --------------------------------------------------------------------------- # # configure the client logging # --------------------------------------------------------------------------- # import logging FORMAT = ('%(asctime)-15s %(threadName)-15s ' '%(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s') logging.basicConfig(format=FORMAT) log = logging.getLogger() log.setLevel(logging.DEBUG) UNIT = 0x1 def run_sync_client(): # ------------------------------------------------------------------------# # choose the client you want # ------------------------------------------------------------------------# # make sure to start an implementation to hit against. For this # you can use an existing device, the reference implementation in the tools # directory, or start a pymodbus server. # # If you use the UDP or TCP clients, you can override the framer being used # to use a custom implementation (say RTU over TCP). By default they use # the socket framer:: # # client = ModbusClient('localhost', port=5020, framer=ModbusRtuFramer) # # It should be noted that you can supply an ipv4 or an ipv6 host address # for both the UDP and TCP clients. # # There are also other options that can be set on the client that controls # how transactions are performed. The current ones are: # # * retries - Specify how many retries to allow per transaction (default=3) # * retry_on_empty - Is an empty response a retry (default = False) # * source_address - Specifies the TCP source address to bind to # # Here is an example of using these options:: # # client = ModbusClient('localhost', retries=3, retry_on_empty=True) # ------------------------------------------------------------------------# client = ModbusClient('localhost', port=5020) # from pymodbus.transaction import ModbusRtuFramer # client = ModbusClient('localhost', port=5020, framer=ModbusRtuFramer) # client = ModbusClient(method='binary', port='/dev/ptyp0', timeout=1) # client = ModbusClient(method='ascii', port='/dev/ptyp0', timeout=1) # client = ModbusClient(method='rtu', port='/dev/ptyp0', timeout=1, # baudrate=9600) client.connect() # ------------------------------------------------------------------------# # specify slave to query # ------------------------------------------------------------------------# # The slave to query is specified in an optional parameter for each # individual request. This can be done by specifying the `unit` parameter # which defaults to `0x00` # ----------------------------------------------------------------------- # log.debug("Reading Device Information") information = {} rr = None while not rr or rr.more_follows: next_object_id = rr.next_object_id if rr else 0 rq = ReadDeviceInformationRequest(read_code=0x03, unit=UNIT, object_id=next_object_id) rr = client.execute(rq) information.update(rr.information) log.debug(rr) print("Device Information : ") for key in information.keys(): print(key, information[key]) # ----------------------------------------------------------------------- # # You can also have the information parsed through the # ModbusDeviceIdentificiation class, which gets you a more usable way # to access the Basic and Regular device information objects which are # specifically listed in the Modbus specification # ----------------------------------------------------------------------- # di = ModbusDeviceIdentification(info=information) print('Product Name : ', di.ProductName) # ----------------------------------------------------------------------- # # close the client # ----------------------------------------------------------------------- # client.close() if __name__ == "__main__": run_sync_client() pymodbus-2.1.0/examples/contrib/deviceinfo_showcase_server.py000077500000000000000000000130151335513467700246220ustar00rootroot00000000000000#!/usr/bin/env python """ Pymodbus Synchronous Server Example to showcase Device Information -------------------------------------------------------------------------- This server demonstrates the use of Device Information to provide information to clients about the device. This is part of the MODBUS specification, and uses the MEI 0x2B 0x0E request / response. This example creates an otherwise empty server. """ # --------------------------------------------------------------------------- # # import the various server implementations # --------------------------------------------------------------------------- # from pymodbus.server.sync import StartTcpServer from pymodbus.server.sync import StartUdpServer from pymodbus.server.sync import StartSerialServer from pymodbus.device import ModbusDeviceIdentification from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext from pymodbus.transaction import ModbusRtuFramer, ModbusBinaryFramer # --------------------------------------------------------------------------- # # import versions of libraries which we will use later on for the example # --------------------------------------------------------------------------- # from pymodbus import __version__ as pymodbus_version from serial import __version__ as pyserial_version # --------------------------------------------------------------------------- # # configure the service logging # --------------------------------------------------------------------------- # import logging FORMAT = ('%(asctime)-15s %(threadName)-15s' ' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s') logging.basicConfig(format=FORMAT) log = logging.getLogger() log.setLevel(logging.DEBUG) def run_server(): # ----------------------------------------------------------------------- # # initialize your data store # ----------------------------------------------------------------------- # store = ModbusSlaveContext() context = ModbusServerContext(slaves=store, single=True) # ----------------------------------------------------------------------- # # initialize the server information # ----------------------------------------------------------------------- # # If you don't set this or any fields, they are defaulted to empty strings. # ----------------------------------------------------------------------- # identity = ModbusDeviceIdentification() identity.VendorName = 'Pymodbus' identity.ProductCode = 'PM' identity.VendorUrl = 'http://github.com/riptideio/pymodbus/' identity.ProductName = 'Pymodbus Server' identity.ModelName = 'Pymodbus Server' identity.MajorMinorRevision = '1.5' # ----------------------------------------------------------------------- # # Add an example which is long enough to force the ReadDeviceInformation # request / response to require multiple responses to send back all of the # information. # ----------------------------------------------------------------------- # identity[0x80] = "Lorem ipsum dolor sit amet, consectetur adipiscing " \ "elit. Vivamus rhoncus massa turpis, sit amet " \ "ultrices orci semper ut. Aliquam tristique sapien in " \ "lacus pharetra, in convallis nunc consectetur. Nunc " \ "velit elit, vehicula tempus tempus sed. " # ----------------------------------------------------------------------- # # Add an example with repeated object IDs. The MODBUS specification is # entirely silent on whether or not this is allowed. In practice, this # should be assumed to be contrary to the MODBUS specification and other # clients (other than pymodbus) might behave differently when presented # with an object ID occurring twice in the returned information. # # Use this at your discretion, and at the very least ensure that all # objects which share a single object ID can fit together within a single # ADU unit. In the case of Modbus RTU, this is about 240 bytes or so. In # other words, when the spec says "An object is indivisible, therefore # any object must have a size consistent with the size of transaction # response", if you use repeated OIDs, apply that rule to the entire # grouping of objects with the repeated OID. # ----------------------------------------------------------------------- # identity[0x81] = ['pymodbus {0}'.format(pymodbus_version), 'pyserial {0}'.format(pyserial_version)] # ----------------------------------------------------------------------- # # run the server you want # ----------------------------------------------------------------------- # # Tcp: StartTcpServer(context, identity=identity, address=("localhost", 5020)) # TCP with different framer # StartTcpServer(context, identity=identity, # framer=ModbusRtuFramer, address=("0.0.0.0", 5020)) # Udp: # StartUdpServer(context, identity=identity, address=("0.0.0.0", 5020)) # Ascii: # StartSerialServer(context, identity=identity, # port='/dev/ttyp0', timeout=1) # RTU: # StartSerialServer(context, framer=ModbusRtuFramer, identity=identity, # port='/dev/ttyp0', timeout=.005, baudrate=9600) # Binary # StartSerialServer(context, # identity=identity, # framer=ModbusBinaryFramer, # port='/dev/ttyp0', # timeout=1) if __name__ == "__main__": run_server() pymodbus-2.1.0/examples/contrib/libmodbus_client.py000077500000000000000000000432601335513467700225500ustar00rootroot00000000000000#!/usr/bin/env python """ Libmodbus Protocol Wrapper ------------------------------------------------------------ What follows is an example wrapper of the libmodbus library (http://libmodbus.org/documentation/) for use with pymodbus. There are two utilities involved here: * LibmodbusLevel1Client This is simply a python wrapper around the c library. It is mostly a clone of the pylibmodbus implementation, but I plan on extending it to implement all the available protocol using the raw execute methods. * LibmodbusClient This is just another modbus client that can be used just like any other client in pymodbus. For these to work, you must have `cffi` and `libmodbus-dev` installed: sudo apt-get install libmodbus-dev pip install cffi """ # -------------------------------------------------------------------------- # # import system libraries # -------------------------------------------------------------------------- # from cffi import FFI # -------------------------------------------------------------------------- # # import pymodbus libraries # -------------------------------------------------------------------------- # from pymodbus.constants import Defaults from pymodbus.exceptions import ModbusException from pymodbus.client.common import ModbusClientMixin from pymodbus.bit_read_message import ReadCoilsResponse, ReadDiscreteInputsResponse from pymodbus.register_read_message import ReadHoldingRegistersResponse, ReadInputRegistersResponse from pymodbus.register_read_message import ReadWriteMultipleRegistersResponse from pymodbus.bit_write_message import WriteSingleCoilResponse, WriteMultipleCoilsResponse from pymodbus.register_write_message import WriteSingleRegisterResponse, WriteMultipleRegistersResponse # --------------------------------------------------------------------------- # # create the C interface # --------------------------------------------------------------------------- # # * TODO add the protocol needed for the servers # --------------------------------------------------------------------------- # compiler = FFI() compiler.cdef(""" typedef struct _modbus modbus_t; int modbus_connect(modbus_t *ctx); int modbus_flush(modbus_t *ctx); void modbus_close(modbus_t *ctx); const char *modbus_strerror(int errnum); int modbus_set_slave(modbus_t *ctx, int slave); void modbus_get_response_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec); void modbus_set_response_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec); int modbus_read_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest); int modbus_read_input_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest); int modbus_read_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest); int modbus_read_input_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest); int modbus_write_bit(modbus_t *ctx, int coil_addr, int status); int modbus_write_bits(modbus_t *ctx, int addr, int nb, const uint8_t *data); int modbus_write_register(modbus_t *ctx, int reg_addr, int value); int modbus_write_registers(modbus_t *ctx, int addr, int nb, const uint16_t *data); int modbus_write_and_read_registers(modbus_t *ctx, int write_addr, int write_nb, const uint16_t *src, int read_addr, int read_nb, uint16_t *dest); int modbus_mask_write_register(modbus_t *ctx, int addr, uint16_t and_mask, uint16_t or_mask); int modbus_send_raw_request(modbus_t *ctx, uint8_t *raw_req, int raw_req_length); float modbus_get_float(const uint16_t *src); void modbus_set_float(float f, uint16_t *dest); modbus_t* modbus_new_tcp(const char *ip_address, int port); modbus_t* modbus_new_rtu(const char *device, int baud, char parity, int data_bit, int stop_bit); void modbus_free(modbus_t *ctx); int modbus_receive(modbus_t *ctx, uint8_t *req); int modbus_receive_from(modbus_t *ctx, int sockfd, uint8_t *req); int modbus_receive_confirmation(modbus_t *ctx, uint8_t *rsp); """) LIB = compiler.dlopen('modbus') # create our bindings # -------------------------------------------------------------------------- # # helper utilites # -------------------------------------------------------------------------- # def get_float(data): return LIB.modbus_get_float(data) def set_float(value, data): LIB.modbus_set_float(value, data) def cast_to_int16(data): return int(compiler.cast('int16_t', data)) def cast_to_int32(data): return int(compiler.cast('int32_t', data)) class NotImplementedException(Exception): pass # -------------------------------------------------------------------------- # # level1 client # -------------------------------------------------------------------------- # class LibmodbusLevel1Client(object): """ A raw wrapper around the libmodbus c library. Feel free to use it if you want increased performance and don't mind the entire protocol not being implemented. """ @classmethod def create_tcp_client(klass, host='127.0.0.1', port=Defaults.Port): """ Create a TCP modbus client for the supplied parameters. :param host: The host to connect to :param port: The port to connect to on that host :returns: A new level1 client """ client = LIB.modbus_new_tcp(host.encode(), port) return klass(client) @classmethod def create_rtu_client(klass, **kwargs): """ Create a TCP modbus client for the supplied parameters. :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 :returns: A new level1 client """ port = kwargs.get('port', '/dev/ttyS0') baudrate = kwargs.get('baud', Defaults.Baudrate) parity = kwargs.get('parity', Defaults.Parity) bytesize = kwargs.get('bytesize', Defaults.Bytesize) stopbits = kwargs.get('stopbits', Defaults.Stopbits) client = LIB.modbus_new_rtu(port, baudrate, parity, bytesize, stopbits) return klass(client) def __init__(self, client): """ Initalize a new instance of the LibmodbusLevel1Client. This method should not be used, instead new instances should be created using the two supplied factory methods: * LibmodbusLevel1Client.create_rtu_client(...) * LibmodbusLevel1Client.create_tcp_client(...) :param client: The underlying client instance to operate with. """ self.client = client self.slave = Defaults.UnitId def set_slave(self, slave): """ Set the current slave to operate against. :param slave: The new slave to operate against :returns: The resulting slave to operate against """ self.slave = self._execute(LIB.modbus_set_slave, slave) return self.slave def connect(self): """ Attempt to connect to the client target. :returns: True if successful, throws otherwise """ return (self.__execute(LIB.modbus_connect) == 0) def flush(self): """ Discards the existing bytes on the wire. :returns: The number of flushed bytes, or throws """ return self.__execute(LIB.modbus_flush) def close(self): """ Closes and frees the underlying connection and context structure. :returns: Always True """ LIB.modbus_close(self.client) LIB.modbus_free(self.client) return True def __execute(self, command, *args): """ Run the supplied command against the currently instantiated client with the supplied arguments. This will make sure to correctly handle resulting errors. :param command: The command to execute against the context :param *args: The arguments for the given command :returns: The result of the operation unless -1 which throws """ result = command(self.client, *args) if result == -1: message = LIB.modbus_strerror(compiler.errno) raise ModbusException(compiler.string(message)) return result def read_bits(self, address, count=1): """ :param address: The starting address to read from :param count: The number of coils to read :returns: The resulting bits """ result = compiler.new("uint8_t[]", count) self.__execute(LIB.modbus_read_bits, address, count, result) return result def read_input_bits(self, address, count=1): """ :param address: The starting address to read from :param count: The number of discretes to read :returns: The resulting bits """ result = compiler.new("uint8_t[]", count) self.__execute(LIB.modbus_read_input_bits, address, count, result) return result def write_bit(self, address, value): """ :param address: The starting address to write to :param value: The value to write to the specified address :returns: The number of written bits """ return self.__execute(LIB.modbus_write_bit, address, value) def write_bits(self, address, values): """ :param address: The starting address to write to :param values: The values to write to the specified address :returns: The number of written bits """ count = len(values) return self.__execute(LIB.modbus_write_bits, address, count, values) def write_register(self, address, value): """ :param address: The starting address to write to :param value: The value to write to the specified address :returns: The number of written registers """ return self.__execute(LIB.modbus_write_register, address, value) def write_registers(self, address, values): """ :param address: The starting address to write to :param values: The values to write to the specified address :returns: The number of written registers """ count = len(values) return self.__execute(LIB.modbus_write_registers, address, count, values) def read_registers(self, address, count=1): """ :param address: The starting address to read from :param count: The number of registers to read :returns: The resulting read registers """ result = compiler.new("uint16_t[]", count) self.__execute(LIB.modbus_read_registers, address, count, result) return result def read_input_registers(self, address, count=1): """ :param address: The starting address to read from :param count: The number of registers to read :returns: The resulting read registers """ result = compiler.new("uint16_t[]", count) self.__execute(LIB.modbus_read_input_registers, address, count, result) return result def read_and_write_registers(self, read_address, read_count, write_address, write_registers): """ :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 :returns: The resulting read registers """ write_count = len(write_registers) read_result = compiler.new("uint16_t[]", read_count) self.__execute(LIB.modbus_write_and_read_registers, write_address, write_count, write_registers, read_address, read_count, read_result) return read_result # -------------------------------------------------------------------------- # # level2 client # -------------------------------------------------------------------------- # class LibmodbusClient(ModbusClientMixin): """ A facade around the raw level 1 libmodbus client that implements the pymodbus protocol on top of the lower level client. """ # ----------------------------------------------------------------------- # # these are used to convert from the pymodbus request types to the # libmodbus operations (overloaded operator). # ----------------------------------------------------------------------- # __methods = { 'ReadCoilsRequest': lambda c, r: c.read_bits(r.address, r.count), 'ReadDiscreteInputsRequest': lambda c, r: c.read_input_bits(r.address, r.count), 'WriteSingleCoilRequest': lambda c, r: c.write_bit(r.address, r.value), 'WriteMultipleCoilsRequest': lambda c, r: c.write_bits(r.address, r.values), 'WriteSingleRegisterRequest': lambda c, r: c.write_register(r.address, r.value), 'WriteMultipleRegistersRequest': lambda c, r: c.write_registers(r.address, r.values), 'ReadHoldingRegistersRequest': lambda c, r: c.read_registers(r.address, r.count), 'ReadInputRegistersRequest': lambda c, r: c.read_input_registers(r.address, r.count), 'ReadWriteMultipleRegistersRequest': lambda c, r: c.read_and_write_registers(r.read_address, r.read_count, r.write_address, r.write_registers), } # ----------------------------------------------------------------------- # # these are used to convert from the libmodbus result to the # pymodbus response type # ----------------------------------------------------------------------- # __adapters = { 'ReadCoilsRequest': lambda tx, rx: ReadCoilsResponse(list(rx)), 'ReadDiscreteInputsRequest': lambda tx, rx: ReadDiscreteInputsResponse(list(rx)), 'WriteSingleCoilRequest': lambda tx, rx: WriteSingleCoilResponse(tx.address, rx), 'WriteMultipleCoilsRequest': lambda tx, rx: WriteMultipleCoilsResponse(tx.address, rx), 'WriteSingleRegisterRequest': lambda tx, rx: WriteSingleRegisterResponse(tx.address, rx), 'WriteMultipleRegistersRequest': lambda tx, rx: WriteMultipleRegistersResponse(tx.address, rx), 'ReadHoldingRegistersRequest': lambda tx, rx: ReadHoldingRegistersResponse(list(rx)), 'ReadInputRegistersRequest': lambda tx, rx: ReadInputRegistersResponse(list(rx)), 'ReadWriteMultipleRegistersRequest': lambda tx, rx: ReadWriteMultipleRegistersResponse(list(rx)), } def __init__(self, client): """ Initalize a new instance of the LibmodbusClient. This should be initialized with one of the LibmodbusLevel1Client instances: * LibmodbusLevel1Client.create_rtu_client(...) * LibmodbusLevel1Client.create_tcp_client(...) :param client: The underlying client instance to operate with. """ self.client = client # ----------------------------------------------------------------------- # # We use the client mixin to implement the api methods which are all # forwarded to this method. It is implemented using the previously # defined lookup tables. Any method not defined simply throws. # ----------------------------------------------------------------------- # def execute(self, request): """ Execute the supplied request against the server. :param request: The request to process :returns: The result of the request execution """ if self.client.slave != request.unit_id: self.client.set_slave(request.unit_id) method = request.__class__.__name__ operation = self.__methods.get(method, None) adapter = self.__adapters.get(method, None) if not operation or not adapter: raise NotImplementedException("Method not " "implemented: " + operation) response = operation(self.client, request) return adapter(request, response) # ----------------------------------------------------------------------- # # Other methods can simply be forwarded using the decorator pattern # ----------------------------------------------------------------------- # def connect(self): return self.client.connect() def close(self): return self.client.close() # ----------------------------------------------------------------------- # # magic methods # ----------------------------------------------------------------------- # def __enter__(self): """ Implement the client with enter block :returns: The current instance of the client """ self.client.connect() return self def __exit__(self, klass, value, traceback): """ Implement the client with exit block """ self.client.close() # -------------------------------------------------------------------------- # # main example runner # -------------------------------------------------------------------------- # if __name__ == '__main__': # create our low level client host = '127.0.0.1' port = 502 protocol = LibmodbusLevel1Client.create_tcp_client(host, port) # operate with our high level client with LibmodbusClient(protocol) as client: registers = client.write_registers(0, [13, 12, 11]) print(registers) registers = client.read_holding_registers(0, 10) print(registers.registers) pymodbus-2.1.0/examples/contrib/message_generator.py000077500000000000000000000176111335513467700227250ustar00rootroot00000000000000#!/usr/bin/env python """ Modbus Message Generator -------------------------------------------------------------------------- The following is an example of how to generate example encoded messages for the supplied modbus format: * tcp - `./generate-messages.py -f tcp -m rx -b` * ascii - `./generate-messages.py -f ascii -m tx -a` * rtu - `./generate-messages.py -f rtu -m rx -b` * binary - `./generate-messages.py -f binary -m tx -b` """ from optparse import OptionParser import codecs as c # -------------------------------------------------------------------------- # # import all the available framers # -------------------------------------------------------------------------- # from pymodbus.transaction import ModbusSocketFramer from pymodbus.transaction import ModbusBinaryFramer from pymodbus.transaction import ModbusAsciiFramer from pymodbus.transaction import ModbusRtuFramer # -------------------------------------------------------------------------- # # import all available messages # -------------------------------------------------------------------------- # 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 IS_PYTHON3 # -------------------------------------------------------------------------- # # initialize logging # -------------------------------------------------------------------------- # import logging modbus_log = logging.getLogger("pymodbus") # -------------------------------------------------------------------------- # # enumerate all request messages # -------------------------------------------------------------------------- # _request_messages = [ ReadHoldingRegistersRequest, ReadDiscreteInputsRequest, ReadInputRegistersRequest, ReadCoilsRequest, WriteMultipleCoilsRequest, WriteMultipleRegistersRequest, WriteSingleRegisterRequest, WriteSingleCoilRequest, ReadWriteMultipleRegistersRequest, ReadExceptionStatusRequest, GetCommEventCounterRequest, GetCommEventLogRequest, ReportSlaveIdRequest, ReadFileRecordRequest, WriteFileRecordRequest, MaskWriteRegisterRequest, ReadFifoQueueRequest, ReadDeviceInformationRequest, ReturnQueryDataRequest, RestartCommunicationsOptionRequest, ReturnDiagnosticRegisterRequest, ChangeAsciiInputDelimiterRequest, ForceListenOnlyModeRequest, ClearCountersRequest, ReturnBusMessageCountRequest, ReturnBusCommunicationErrorCountRequest, ReturnBusExceptionErrorCountRequest, ReturnSlaveMessageCountRequest, ReturnSlaveNoResponseCountRequest, ReturnSlaveNAKCountRequest, ReturnSlaveBusyCountRequest, ReturnSlaveBusCharacterOverrunCountRequest, ReturnIopOverrunCountRequest, ClearOverrunCountRequest, GetClearModbusPlusRequest ] # -------------------------------------------------------------------------- # # enumerate all response messages # -------------------------------------------------------------------------- # _response_messages = [ ReadHoldingRegistersResponse, ReadDiscreteInputsResponse, ReadInputRegistersResponse, ReadCoilsResponse, WriteMultipleCoilsResponse, WriteMultipleRegistersResponse, WriteSingleRegisterResponse, WriteSingleCoilResponse, ReadWriteMultipleRegistersResponse, ReadExceptionStatusResponse, GetCommEventCounterResponse, GetCommEventLogResponse, ReportSlaveIdResponse, ReadFileRecordResponse, WriteFileRecordResponse, MaskWriteRegisterResponse, ReadFifoQueueResponse, ReadDeviceInformationResponse, ReturnQueryDataResponse, RestartCommunicationsOptionResponse, ReturnDiagnosticRegisterResponse, ChangeAsciiInputDelimiterResponse, ForceListenOnlyModeResponse, ClearCountersResponse, ReturnBusMessageCountResponse, ReturnBusCommunicationErrorCountResponse, ReturnBusExceptionErrorCountResponse, ReturnSlaveMessageCountResponse, ReturnSlaveNoReponseCountResponse, ReturnSlaveNAKCountResponse, ReturnSlaveBusyCountResponse, ReturnSlaveBusCharacterOverrunCountResponse, ReturnIopOverrunCountResponse, ClearOverrunCountResponse, GetClearModbusPlusResponse ] # -------------------------------------------------------------------------- # # build an arguments singleton # -------------------------------------------------------------------------- # # Feel free to override any values here to generate a specific message # in question. It should be noted that many argument names are reused # between different messages, and a number of messages are simply using # their default values. # -------------------------------------------------------------------------- # _arguments = { 'address': 0x12, 'count': 0x08, 'value': 0x01, 'values': [0x01] * 8, 'read_address': 0x12, 'read_count': 0x08, 'write_address': 0x12, 'write_registers': [0x01] * 8, 'transaction': 0x01, 'protocol': 0x00, 'unit': 0xff, } # -------------------------------------------------------------------------- # # generate all the requested messages # -------------------------------------------------------------------------- # def generate_messages(framer, options): """ A helper method to parse the command line options :param framer: The framer to encode the messages with :param options: The message options to use """ if options.messages == "tx": messages = _request_messages else: messages = _response_messages for message in messages: message = message(**_arguments) print("%-44s = " % message.__class__.__name__) packet = framer.buildPacket(message) if not options.ascii: if not IS_PYTHON3: packet = packet.encode('hex') else: packet = c.encode(packet, 'hex_codec').decode('utf-8') print ("{}\n".format(packet)) # because ascii ends with a \r\n # -------------------------------------------------------------------------- # # initialize our program settings # -------------------------------------------------------------------------- # def get_options(): """ A helper method to parse the command line options :returns: The options manager """ parser = OptionParser() parser.add_option("-f", "--framer", help="The type of framer to use " "(tcp, rtu, binary, ascii)", dest="framer", default="tcp") parser.add_option("-D", "--debug", help="Enable debug tracing", action="store_true", dest="debug", default=False) parser.add_option("-a", "--ascii", help="The indicates that the message is ascii", action="store_true", dest="ascii", default=True) parser.add_option("-b", "--binary", help="The indicates that the message is binary", action="store_false", dest="ascii") parser.add_option("-m", "--messages", help="The messages to encode (rx, tx)", dest="messages", default='rx') (opt, arg) = parser.parse_args() return opt def main(): """ The main runner function """ option = get_options() if option.debug: try: modbus_log.setLevel(logging.DEBUG) logging.basicConfig() except Exception as e: print("Logging is not supported on this system") framer = lookup = { 'tcp': ModbusSocketFramer, 'rtu': ModbusRtuFramer, 'binary': ModbusBinaryFramer, 'ascii': ModbusAsciiFramer, }.get(option.framer, ModbusSocketFramer)(None) generate_messages(framer, option) if __name__ == "__main__": main() pymodbus-2.1.0/examples/contrib/message_parser.py000077500000000000000000000163261335513467700222350ustar00rootroot00000000000000#!/usr/bin/env python """ Modbus Message Parser -------------------------------------------------------------------------- The following is an example of how to parse modbus messages using the supplied framers for a number of protocols: * tcp * ascii * rtu * binary """ # -------------------------------------------------------------------------- # # import needed libraries # -------------------------------------------------------------------------- # from __future__ import print_function import collections import textwrap from optparse import OptionParser import codecs as c from pymodbus.factory import ClientDecoder, ServerDecoder from pymodbus.transaction import ModbusSocketFramer from pymodbus.transaction import ModbusBinaryFramer from pymodbus.transaction import ModbusAsciiFramer from pymodbus.transaction import ModbusRtuFramer from pymodbus.compat import IS_PYTHON3 # -------------------------------------------------------------------------- # # -------------------------------------------------------------------------- # import logging FORMAT = ('%(asctime)-15s %(threadName)-15s' ' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s') logging.basicConfig(format=FORMAT) log = logging.getLogger() # -------------------------------------------------------------------------- # # build a quick wrapper around the framers # -------------------------------------------------------------------------- # class Decoder(object): def __init__(self, framer, encode=False): """ Initialize a new instance of the decoder :param framer: The framer to use :param encode: If the message needs to be encoded """ self.framer = framer self.encode = encode def decode(self, message): """ Attempt to decode the supplied message :param message: The messge to decode """ if IS_PYTHON3: value = message if self.encode else c.encode(message, 'hex_codec') else: value = message if self.encode else message.encode('hex') print("="*80) print("Decoding Message %s" % value) print("="*80) decoders = [ self.framer(ServerDecoder(), client=None), self.framer(ClientDecoder(), client=None) ] for decoder in decoders: print("%s" % decoder.decoder.__class__.__name__) print("-"*80) try: decoder.addToFrame(message) if decoder.checkFrame(): unit = decoder._header.get("uid", 0x00) decoder.advanceFrame() decoder.processIncomingPacket(message, self.report, unit) else: self.check_errors(decoder, message) except Exception as ex: self.check_errors(decoder, message) def check_errors(self, decoder, message): """ Attempt to find message errors :param message: The message to find errors in """ log.error("Unable to parse message - {} with {}".format(message, decoder)) def report(self, message): """ The callback to print the message information :param message: The message to print """ print("%-15s = %s" % ('name', message.__class__.__name__)) for (k, v) in message.__dict__.items(): if isinstance(v, dict): print("%-15s =" % k) for kk,vv in v.items(): print(" %-12s => %s" % (kk, vv)) elif isinstance(v, collections.Iterable): print("%-15s =" % k) value = str([int(x) for x in v]) for line in textwrap.wrap(value, 60): print("%-15s . %s" % ("", line)) else: print("%-15s = %s" % (k, hex(v))) print("%-15s = %s" % ('documentation', message.__doc__)) # -------------------------------------------------------------------------- # # and decode our message # -------------------------------------------------------------------------- # def get_options(): """ A helper method to parse the command line options :returns: The options manager """ parser = OptionParser() parser.add_option("-p", "--parser", help="The type of parser to use " "(tcp, rtu, binary, ascii)", dest="parser", default="tcp") parser.add_option("-D", "--debug", help="Enable debug tracing", action="store_true", dest="debug", default=False) parser.add_option("-m", "--message", help="The message to parse", dest="message", default=None) parser.add_option("-a", "--ascii", help="The indicates that the message is ascii", action="store_true", dest="ascii", default=True) parser.add_option("-b", "--binary", help="The indicates that the message is binary", action="store_false", dest="ascii") parser.add_option("-f", "--file", help="The file containing messages to parse", dest="file", default=None) parser.add_option("-t", "--transaction", help="If the incoming message is in hexadecimal format", action="store_true", dest="transaction", default=False) (opt, arg) = parser.parse_args() if not opt.message and len(arg) > 0: opt.message = arg[0] return opt def get_messages(option): """ A helper method to generate the messages to parse :param options: The option manager :returns: The message iterator to parse """ if option.message: if option.transaction: msg = "" for segment in option.message.split(): segment = segment.replace("0x", "") segment = "0" + segment if len(segment) == 1 else segment msg = msg + segment option.message = msg if not option.ascii: if not IS_PYTHON3: option.message = option.message.decode('hex') else: option.message = c.decode(option.message.encode(), 'hex_codec') yield option.message elif option.file: with open(option.file, "r") as handle: for line in handle: if line.startswith('#'): continue if not option.ascii: line = line.strip() line = line.decode('hex') yield line def main(): """ The main runner function """ option = get_options() if option.debug: try: modbus_log.setLevel(logging.DEBUG) logging.basicConfig() except Exception as e: print("Logging is not supported on this system- {}".format(e)) framer = lookup = { 'tcp': ModbusSocketFramer, 'rtu': ModbusRtuFramer, 'binary': ModbusBinaryFramer, 'ascii': ModbusAsciiFramer, }.get(option.parser, ModbusSocketFramer) decoder = Decoder(framer, option.ascii) for message in get_messages(option): decoder.decode(message) if __name__ == "__main__": main() pymodbus-2.1.0/examples/contrib/modbus_mapper.py000066400000000000000000000255601335513467700220670ustar00rootroot00000000000000""" Given a modbus mapping file, this is used to generate decoder blocks so that non-programmers can define the register values and then decode a modbus device all without having to write a line of code for decoding. Currently supported formats are: * csv * json * xml Here is an example of generating and using a mapping decoder (note that this is still in the works and will be greatly simplified in the final api; it is just an example of the requested functionality):: from modbus_mapper import csv_mapping_parser from modbus_mapper import mapping_decoder from pymodbus.client.sync import ModbusTcpClient from pymodbus.payload import BinaryModbusDecoder template = ['address', 'size', 'function', 'name', 'description'] raw_mapping = csv_mapping_parser('input.csv', template) mapping = mapping_decoder(raw_mapping) index, size = 1, 100 client = ModbusTcpClient('localhost') response = client.read_holding_registers(index, size) decoder = BinaryModbusDecoder.fromRegisters(response.registers) while index < size: print "[{}]\t{}".format(i, mapping[i]['type'](decoder)) index += mapping[i]['size'] Also, using the same input mapping parsers, we can generate populated slave contexts that can be run behing a modbus server:: from modbus_mapper import csv_mapping_parser from modbus_mapper import modbus_context_decoder from pymodbus.client.ssync import StartTcpServer from pymodbus.datastore.context import ModbusServerContext template = ['address', 'value', 'function', 'name', 'description'] raw_mapping = csv_mapping_parser('input.csv', template) slave_context = modbus_context_decoder(raw_mapping) context = ModbusServerContext(slaves=slave_context, single=True) StartTcpServer(context) """ import csv import json from collections import defaultdict from tokenize import generate_tokens from pymodbus.payload import BinaryPayloadDecoder from pymodbus.datastore.store import ModbusSparseDataBlock from pymodbus.compat import IS_PYTHON3 from pymodbus.datastore.context import ModbusSlaveContext if IS_PYTHON3: from io import StringIO else: from StringIO import StringIO # --------------------------------------------------------------------------- # # raw mapping input parsers # --------------------------------------------------------------------------- # # These generate the raw mapping_blocks from some form of input # which can then be passed to the decoder in question to supply # the requested output result. # --------------------------------------------------------------------------- # def csv_mapping_parser(path, template): """ Given a csv file of the the mapping data for a modbus device, return a mapping layout that can be used to decode an new block. .. note:: For the template, a few values are required to be defined: address, size, function, and type. All the remaining values will be stored, but not formatted by the application. So for example:: template = ['address', 'type', 'size', 'name', 'function'] mappings = json_mapping_parser('mapping.json', template) :param path: The path to the csv input file :param template: The row value template :returns: The decoded csv dictionary """ mapping_blocks = defaultdict(dict) with open(path, 'r') as handle: reader = csv.reader(handle) reader.next() # skip the csv header for row in reader: mapping = dict(zip(template, row)) fid = mapping.pop('function') aid = int(mapping['address']) mapping_blocks[aid] = mapping return mapping_blocks def json_mapping_parser(path, template): """ Given a json file of the the mapping data for a modbus device, return a mapping layout that can be used to decode an new block. .. note:: For the template, a few values are required to be mapped: address, size, and type. All the remaining values will be stored, but not formatted by the application. So for example:: template = { 'Start': 'address', 'DataType': 'type', 'Length': 'size' # the remaining keys will just pass through } mappings = json_mapping_parser('mapping.json', template) :param path: The path to the csv input file :param template: The row value template :returns: The decoded csv dictionary """ mapping_blocks = {} with open(path, 'r') as handle: for tid, rows in json.load(handle).iteritems(): mappings = {} for key, values in rows.iteritems(): mapping = {template.get(k, k) : v for k, v in values.iteritems()} mappings[int(key)] = mapping mapping_blocks[tid] = mappings return mapping_blocks def xml_mapping_parser(path): """ Given an xml file of the the mapping data for a modbus device, return a mapping layout that can be used to decode an new block. .. note:: The input of the xml file is defined as follows:: :param path: The path to the xml input file :returns: The decoded csv dictionary """ pass # --------------------------------------------------------------------------- # # modbus context decoders # --------------------------------------------------------------------------- # # These are used to decode a raw mapping_block into a slave context with # populated function data blocks. # --------------------------------------------------------------------------- # def modbus_context_decoder(mapping_blocks): """ Given a mapping block input, generate a backing slave context with initialized data blocks. .. note:: This expects the following for each block: address, value, and function where function is one of di (discretes), co (coils), hr (holding registers), or ir (input registers). :param mapping_blocks: The mapping blocks :returns: The initialized modbus slave context """ blocks = defaultdict(dict) for block in mapping_blocks.itervalues(): for mapping in block.itervalues(): value = int(mapping['value']) address = int(mapping['address']) function = mapping['function'] blocks[function][address] = value return ModbusSlaveContext(**blocks) # --------------------------------------------------------------------------- # # modbus mapping decoder # --------------------------------------------------------------------------- # # These are used to decode a raw mapping_block into a request decoder. # So this allows one to simply grab a number of registers, and then # pass them to this decoder which will do the rest. # --------------------------------------------------------------------------- # class ModbusTypeDecoder(object): """ This is a utility to determine the correct decoder to use given a type name. By default this supports all the types available in the default modbus decoder, however this can easily be extended this class and adding new types to the mapper:: class CustomTypeDecoder(ModbusTypeDecoder): def __init__(self): ModbusTypeDecode.__init__(self) self.mapper['type-token'] = self.callback def parse_my_bitfield(self, tokens): return lambda d: d.decode_my_type() """ def __init__(self): """ Initializes a new instance of the decoder """ self.default = lambda m: self.parse_16bit_uint self.parsers = { 'uint': self.parse_16bit_uint, 'uint8': self.parse_8bit_uint, 'uint16': self.parse_16bit_uint, 'uint32': self.parse_32bit_uint, 'uint64': self.parse_64bit_uint, 'int': self.parse_16bit_int, 'int8': self.parse_8bit_int, 'int16': self.parse_16bit_int, 'int32': self.parse_32bit_int, 'int64': self.parse_64bit_int, 'float': self.parse_32bit_float, 'float32': self.parse_32bit_float, 'float64': self.parse_64bit_float, 'string': self.parse_32bit_int, 'bits': self.parse_bits, } # ------------------------------------------------------------ # # Type parsers # ------------------------------------------------------------ # @staticmethod def parse_string(tokens): _ = tokens.next() size = int(tokens.next()) return lambda d: d.decode_string(size=size) @staticmethod def parse_bits(tokens): return lambda d: d.decode_bits() @staticmethod def parse_8bit_uint(tokens): return lambda d: d.decode_8bit_uint() @staticmethod def parse_16bit_uint(tokens): return lambda d: d.decode_16bit_uint() @staticmethod def parse_32bit_uint(tokens): return lambda d: d.decode_32bit_uint() @staticmethod def parse_64bit_uint(tokens): return lambda d: d.decode_64bit_uint() @staticmethod def parse_8bit_int(tokens): return lambda d: d.decode_8bit_int() @staticmethod def parse_16bit_int(tokens): return lambda d: d.decode_16bit_int() @staticmethod def parse_32bit_int(tokens): return lambda d: d.decode_32bit_int() @staticmethod def parse_64bit_int(tokens): return lambda d: d.decode_64bit_int() @staticmethod def parse_32bit_float(tokens): return lambda d: d.decode_32bit_float() @staticmethod def parse_64bit_float(tokens): return lambda d: d.decode_64bit_float() #------------------------------------------------------------ # Public Interface #------------------------------------------------------------ def tokenize(self, value): """ Given a value, return the tokens :param value: The value to tokenize :returns: A token generator """ tokens = generate_tokens(StringIO(value).readline) for toknum, tokval, _, _, _ in tokens: yield tokval def parse(self, value): """ Given a type value, return a function that supplied with a decoder, will decode the correct value. :param value: The type of value to parse :returns: The decoder method to use """ tokens = self.tokenize(value) token = tokens.next().lower() parser = self.parsers.get(token, self.default) return parser(tokens) def mapping_decoder(mapping_blocks, decoder=None): """ Given the raw mapping blocks, convert them into modbus value decoder map. :param mapping_blocks: The mapping blocks :param decoder: The type decoder to use """ decoder = decoder or ModbusTypeDecoder() for block in mapping_blocks.itervalues(): for mapping in block.itervalues(): mapping['address'] = int(mapping['address']) mapping['size'] = int(mapping['size']) mapping['type'] = decoder.parse(mapping['type']) pymodbus-2.1.0/examples/contrib/modbus_saver.py000066400000000000000000000126221335513467700217160ustar00rootroot00000000000000""" These are a collection of helper methods that can be used to save a modbus server context to file for backup, checkpointing, or any other purpose. There use is very simple:: context = server.context saver = JsonDatastoreSaver(context) saver.save() These can then be re-opened by the parsers in the modbus_mapping module. At the moment, the supported output formats are: * csv * json * xml To implement your own, simply subclass ModbusDatastoreSaver and supply the needed callbacks for your given format: * handle_store_start(self, store) * handle_store_end(self, store) * handle_slave_start(self, slave) * handle_slave_end(self, slave) * handle_save_start(self) * handle_save_end(self) """ import json import xml.etree.ElementTree as xml class ModbusDatastoreSaver(object): """ An abstract base class that can be used to implement a persistance format for the modbus server context. In order to use it, just complete the neccessary callbacks (SAX style) that your persistance format needs. """ def __init__(self, context, path=None): """ Initialize a new instance of the saver. :param context: The modbus server context :param path: The output path to save to """ self.context = context self.path = path or 'modbus-context-dump' def save(self): """ The main runner method to save the context to file which calls the various callbacks which the sub classes will implement. """ with open(self.path, 'w') as self.file_handle: self.handle_save_start() for slave_name, slave in self.context: self.handle_slave_start(slave_name) for store_name, store in slave.store.iteritems(): self.handle_store_start(store_name) self.handle_store_values(iter(store)) self.handle_store_end(store_name) self.handle_slave_end(slave_name) self.handle_save_end() #------------------------------------------------------------ # predefined state machine callbacks #------------------------------------------------------------ def handle_save_start(self): pass def handle_store_start(self, store): pass def handle_store_end(self, store): pass def handle_slave_start(self, slave): pass def handle_slave_end(self, slave): pass def handle_save_end(self): pass # ---------------------------------------------------------------- # # Implementations of the data store savers # ---------------------------------------------------------------- # class JsonDatastoreSaver(ModbusDatastoreSaver): """ An implementation of the modbus datastore saver that persists the context as a json document. """ _context = None _store = None _slave = None STORE_NAMES = { 'i': 'input-registers', 'd': 'discretes', 'h': 'holding-registers', 'c': 'coils', } def handle_save_start(self): self._context = dict() def handle_slave_start(self, slave): self._context[hex(slave)] = self._slave = dict() def handle_store_start(self, store): self._store = self.STORE_NAMES[store] def handle_store_values(self, values): self._slave[self._store] = dict(values) def handle_save_end(self): json.dump(self._context, self.file_handle) class CsvDatastoreSaver(ModbusDatastoreSaver): """ An implementation of the modbus datastore saver that persists the context as a csv document. """ _context = None _store = None _line = None NEWLINE = '\r\n' HEADER = "slave,store,address,value" + NEWLINE STORE_NAMES = { 'i': 'i', 'd': 'd', 'h': 'h', 'c': 'c', } def handle_save_start(self): self.file_handle.write(self.HEADER) def handle_slave_start(self, slave): self._line = [str(slave)] def handle_store_start(self, store): self._line.append(self.STORE_NAMES[store]) def handle_store_values(self, values): self.file_handle.writelines(self.handle_store_value(values)) def handle_store_end(self, store): self._line.pop() def handle_store_value(self, values): for a, v in values: yield ','.join(self._line + [str(a), str(v)]) + self.NEWLINE class XmlDatastoreSaver(ModbusDatastoreSaver): """ An implementation of the modbus datastore saver that persists the context as a XML document. """ _context = None _store = None STORE_NAMES = { 'i' : 'input-registers', 'd' : 'discretes', 'h' : 'holding-registers', 'c' : 'coils', } def handle_save_start(self): self._context = xml.Element("context") self._root = xml.ElementTree(self._context) def handle_slave_start(self, slave): self._slave = xml.SubElement(self._context, "slave") self._slave.set("id", str(slave)) def handle_store_start(self, store): self._store = xml.SubElement(self._slave, "store") self._store.set("function", self.STORE_NAMES[store]) def handle_store_values(self, values): for address, value in values: entry = xml.SubElement(self._store, "entry") entry.text = str(value) entry.set("address", str(address)) def handle_save_end(self): self._root.write(self.file_handle) pymodbus-2.1.0/examples/contrib/modbus_scraper.py000077500000000000000000000244241335513467700222430ustar00rootroot00000000000000#!/usr/bin/env python """ This is a simple scraper that can be pointed at a modbus device to pull down all its values and store them as a collection of sequential data blocks. """ import pickle from optparse import OptionParser from twisted.internet import serialport, reactor from twisted.internet.protocol import ClientFactory from pymodbus.datastore import ModbusSequentialDataBlock from pymodbus.datastore import ModbusSlaveContext from pymodbus.factory import ClientDecoder from pymodbus.client.async.twisted import ModbusClientProtocol # -------------------------------------------------------------------------- # # Configure the client logging # -------------------------------------------------------------------------- # import logging log = logging.getLogger("pymodbus") # --------------------------------------------------------------------------- # # Choose the framer you want to use # --------------------------------------------------------------------------- # from pymodbus.transaction import ModbusBinaryFramer from pymodbus.transaction import ModbusAsciiFramer from pymodbus.transaction import ModbusRtuFramer from pymodbus.transaction import ModbusSocketFramer # --------------------------------------------------------------------------- # # Define some constants # --------------------------------------------------------------------------- # COUNT = 8 # The number of bits/registers to read at once DELAY = 0 # The delay between subsequent reads SLAVE = 0x01 # The slave unit id to read from # --------------------------------------------------------------------------- # # A simple scraper protocol # --------------------------------------------------------------------------- # # I tried to spread the load across the device, but feel free to modify the # logic to suit your own purpose. # --------------------------------------------------------------------------- # class ScraperProtocol(ModbusClientProtocol): address = None def __init__(self, framer, endpoint): """ Initializes our custom protocol :param framer: The decoder to use to process messages :param endpoint: The endpoint to send results to """ ModbusClientProtocol.__init__(self, framer) self.endpoint = endpoint def connectionMade(self): """ Callback for when the client has connected to the remote server. """ super(ScraperProtocol, self).connectionMade() log.debug("Beginning the processing loop") self.address = self.factory.starting reactor.callLater(DELAY, self.scrape_holding_registers) def connectionLost(self, reason): """ Callback for when the client disconnects from the server. :param reason: The reason for the disconnection """ reactor.callLater(DELAY, reactor.stop) def scrape_holding_registers(self): """ Defer fetching holding registers """ log.debug("reading holding registers: %d" % self.address) d = self.read_holding_registers(self.address, count=COUNT, unit=SLAVE) d.addCallbacks(self.scrape_discrete_inputs, self.error_handler) def scrape_discrete_inputs(self, response): """ Defer fetching holding registers """ log.debug("reading discrete inputs: %d" % self.address) self.endpoint.write((3, self.address, response.registers)) d = self.read_discrete_inputs(self.address, count=COUNT, unit=SLAVE) d.addCallbacks(self.scrape_input_registers, self.error_handler) def scrape_input_registers(self, response): """ Defer fetching holding registers """ log.debug("reading discrete inputs: %d" % self.address) self.endpoint.write((2, self.address, response.bits)) d = self.read_input_registers(self.address, count=COUNT, unit=SLAVE) d.addCallbacks(self.scrape_coils, self.error_handler) def scrape_coils(self, response): """ Write values of holding registers, defer fetching coils :param response: The response to process """ log.debug("reading coils: %d" % self.address) self.endpoint.write((4, self.address, response.registers)) d = self.read_coils(self.address, count=COUNT, unit=SLAVE) d.addCallbacks(self.start_next_cycle, self.error_handler) def start_next_cycle(self, response): """ Write values of coils, trigger next cycle :param response: The response to process """ log.debug("starting next round: %d" % self.address) self.endpoint.write((1, self.address, response.bits)) self.address += COUNT if self.address >= self.factory.ending: self.endpoint.finalize() self.transport.loseConnection() else: reactor.callLater(DELAY, self.scrape_holding_registers) def error_handler(self, failure): """ Handle any twisted errors :param failure: The error to handle """ log.error(failure) # --------------------------------------------------------------------------- # # a factory for the example protocol # --------------------------------------------------------------------------- # # This is used to build client protocol's if you tie into twisted's method # of processing. It basically produces client instances of the underlying # protocol:: # # Factory(Protocol) -> ProtocolInstance # # It also persists data between client instances (think protocol singelton). # --------------------------------------------------------------------------- # class ScraperFactory(ClientFactory): protocol = ScraperProtocol def __init__(self, framer, endpoint, query): """ Remember things necessary for building a protocols """ self.framer = framer self.endpoint = endpoint self.starting, self.ending = query def buildProtocol(self, _): """ Create a protocol and start the reading cycle """ protocol = self.protocol(self.framer, self.endpoint) protocol.factory = self return protocol # --------------------------------------------------------------------------- # # a custom client for our device # --------------------------------------------------------------------------- # # Twisted provides a number of helper methods for creating and starting # clients: # - protocol.ClientCreator # - reactor.connectTCP # # How you start your client is really up to you. # --------------------------------------------------------------------------- # class SerialModbusClient(serialport.SerialPort): def __init__(self, factory, *args, **kwargs): """ Setup the client and start listening on the serial port :param factory: The factory to build clients with """ protocol = factory.buildProtocol(None) self.decoder = ClientDecoder() serialport.SerialPort.__init__(self, protocol, *args, **kwargs) # --------------------------------------------------------------------------- # # a custom endpoint for our results # --------------------------------------------------------------------------- # # An example line reader, this can replace with: # - the TCP protocol # - a context recorder # - a database or file recorder # --------------------------------------------------------------------------- # class LoggingContextReader(object): def __init__(self, output): """ Initialize a new instance of the logger :param output: The output file to save to """ self.output = output self.context = ModbusSlaveContext( di = ModbusSequentialDataBlock.create(), co = ModbusSequentialDataBlock.create(), hr = ModbusSequentialDataBlock.create(), ir = ModbusSequentialDataBlock.create()) def write(self, response): """ Handle the next modbus response :param response: The response to process """ log.info("Read Data: %s" % str(response)) fx, address, values = response self.context.setValues(fx, address, values) def finalize(self): with open(self.output, "w") as handle: pickle.dump(self.context, handle) # -------------------------------------------------------------------------- # # Main start point # -------------------------------------------------------------------------- # def get_options(): """ A helper method to parse the command line options :returns: The options manager """ parser = OptionParser() parser.add_option("-o", "--output", help="The resulting output file for the scrape", dest="output", default="datastore.pickle") parser.add_option("-p", "--port", help="The port to connect to", type='int', dest="port", default=502) parser.add_option("-s", "--server", help="The server to scrape", dest="host", default="127.0.0.1") parser.add_option("-r", "--range", help="The address range to scan", dest="query", default="0:1000") parser.add_option("-d", "--debug", help="Enable debug tracing", action="store_true", dest="debug", default=False) (opt, arg) = parser.parse_args() return opt def main(): """ The main runner function """ options = get_options() if options.debug: try: log.setLevel(logging.DEBUG) logging.basicConfig() except Exception as ex: print("Logging is not supported on this system") # split the query into a starting and ending range query = [int(p) for p in options.query.split(':')] try: log.debug("Initializing the client") framer = ModbusSocketFramer(ClientDecoder()) reader = LoggingContextReader(options.output) factory = ScraperFactory(framer, reader, query) # how to connect based on TCP vs Serial clients if isinstance(framer, ModbusSocketFramer): reactor.connectTCP(options.host, options.port, factory) else: SerialModbusClient(factory, options.port, reactor) log.debug("Starting the client") reactor.run() log.debug("Finished scraping the client") except Exception as ex: print(ex) # --------------------------------------------------------------------------- # # Main jumper # --------------------------------------------------------------------------- # if __name__ == "__main__": main() pymodbus-2.1.0/examples/contrib/modbus_simulator.py000066400000000000000000000104741335513467700226200ustar00rootroot00000000000000#!/usr/bin/env python """ An example of creating a fully implemented modbus server with read/write data as well as user configurable base data """ import pickle from optparse import OptionParser from twisted.internet import reactor from pymodbus.server.async import StartTcpServer from pymodbus.datastore import ModbusServerContext,ModbusSlaveContext # -------------------------------------------------------------------------- # # Logging # -------------------------------------------------------------------------- # import logging logging.basicConfig() server_log = logging.getLogger("pymodbus.server") protocol_log = logging.getLogger("pymodbus.protocol") # -------------------------------------------------------------------------- # # Extra Global Functions # -------------------------------------------------------------------------- # # These are extra helper functions that don't belong in a class # -------------------------------------------------------------------------- # import getpass def root_test(): """ Simple test to see if we are running as root """ return True # removed for the time being as it isn't portable #return getpass.getuser() == "root" # -------------------------------------------------------------------------- # # Helper Classes # -------------------------------------------------------------------------- # class ConfigurationException(Exception): """ Exception for configuration error """ def __init__(self, string): """ Initializes the ConfigurationException instance :param string: The message to append to the exception """ Exception.__init__(self, string) self.string = string def __str__(self): """ Builds a representation of the object :returns: A string representation of the object """ return 'Configuration Error: %s' % self.string class Configuration: """ Class used to parse configuration file and create and modbus datastore. The format of the configuration file is actually just a python pickle, which is a compressed memory dump from the scraper. """ def __init__(self, config): """ Trys to load a configuration file, lets the file not found exception fall through :param config: The pickled datastore """ try: self.file = open(config, "rb") except Exception as e: _logger.critical(str(e)) raise ConfigurationException("File not found %s" % config) def parse(self): """ Parses the config file and creates a server context """ handle = pickle.load(self.file) try: # test for existance, or bomb dsd = handle['di'] csd = handle['ci'] hsd = handle['hr'] isd = handle['ir'] except Exception: raise ConfigurationException("Invalid Configuration") slave = ModbusSlaveContext(d=dsd, c=csd, h=hsd, i=isd) return ModbusServerContext(slaves=slave) # -------------------------------------------------------------------------- # # Main start point # -------------------------------------------------------------------------- # def main(): """ Server launcher """ parser = OptionParser() parser.add_option("-c", "--conf", help="The configuration file to load", dest="file") parser.add_option("-D", "--debug", help="Turn on to enable tracing", action="store_true", dest="debug", default=False) (opt, arg) = parser.parse_args() # enable debugging information if opt.debug: try: server_log.setLevel(logging.DEBUG) protocol_log.setLevel(logging.DEBUG) except Exception as e: print("Logging is not supported on this system") # parse configuration file and run try: conf = Configuration(opt.file) StartTcpServer(context=conf.parse()) except ConfigurationException as err: print(err) parser.print_help() # -------------------------------------------------------------------------- # # Main jumper # -------------------------------------------------------------------------- # if __name__ == "__main__": if root_test(): main() else: print("This script must be run as root!") pymodbus-2.1.0/examples/contrib/modicon_payload.py000066400000000000000000000220041335513467700223610ustar00rootroot00000000000000""" Modbus Modicon Payload Builder ----------------------------------------------------------- This is an example of building a custom payload builder that can be used in the pymodbus library. Below is a simple modicon encoded builder and decoder. """ from struct import pack, unpack from pymodbus.constants import Endian from pymodbus.interfaces import IPayloadBuilder from pymodbus.utilities import pack_bitstring from pymodbus.utilities import unpack_bitstring from pymodbus.exceptions import ParameterException class ModiconPayloadBuilder(IPayloadBuilder): """ A utility that helps build modicon encoded payload messages to be written with the various modbus messages. example:: builder = ModiconPayloadBuilder() builder.add_8bit_uint(1) builder.add_16bit_uint(2) payload = builder.build() """ 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 def __str__(self): """ Return the payload buffer as a string :returns: The payload buffer as a string """ return ''.join(self._payload) def reset(self): """ Reset the payload buffer """ self._payload = [] 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 = str(self) length = len(string) string = string + ('\x00' * (length % 2)) return [string[i:i+2] for i in range(0, length, 2)] 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 values: The value to add to the buffer """ value = pack_bitstring(values) self._payload.append(value) 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)) 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)) 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' handle = pack(fstring, value) handle = handle[2:] + handle[:2] self._payload.append(handle) 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)) 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)) 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' handle = pack(fstring, value) handle = handle[2:] + handle[:2] self._payload.append(handle) 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' handle = pack(fstring, value) handle = handle[2:] + handle[:2] self._payload.append(handle) def add_string(self, value): """ Adds a string to the buffer :param value: The value to add to the buffer """ fstring = self._endian + 's' for c in value: self._payload.append(pack(fstring, c)) class ModiconPayloadDecoder(object): """ A utility that helps decode modicon encoded payload messages from a modbus reponse message. What follows is a simple example:: decoder = ModiconPayloadDecoder(payload) first = decoder.decode_8bit_uint() second = decoder.decode_16bit_uint() """ def __init__(self, payload, endian): """ Initialize a new payload decoder :param payload: The payload to decode with """ self._payload = payload self._pointer = 0x00 self._endian = endian @staticmethod def from_registers(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 = ''.join(pack('>H', x) for x in registers) return ModiconPayloadDecoder(payload, endian) raise ParameterException('Invalid collection of registers supplied') @staticmethod def from_coils(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 ModiconPayloadDecoder(payload, endian) raise ParameterException('Invalid collection of coils supplied') def reset(self): """ Reset the decoder pointer back to the start """ self._pointer = 0x00 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] 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] 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] handle = handle[2:] + handle[:2] return unpack(fstring, handle)[0] 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] 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] 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] handle = handle[2:] + handle[:2] return unpack(fstring, handle)[0] def decode_32bit_float(self, size=1): """ Decodes a float from the buffer """ self._pointer += 4 fstring = self._endian + 'f' handle = self._payload[self._pointer - 4:self._pointer] handle = handle[2:] + handle[:2] return unpack(fstring, handle)[0] def decode_bits(self): """ Decodes a byte worth of bits from the buffer """ self._pointer += 1 handle = self._payload[self._pointer - 1:self._pointer] return unpack_bitstring(handle) 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__ = ["ModiconPayloadBuilder", "ModiconPayloadDecoder"] pymodbus-2.1.0/examples/contrib/remote_server_context.py000066400000000000000000000173061335513467700236560ustar00rootroot00000000000000""" Although there is a remote server context already in the main library, it works under the assumption that users would have a server context of the following form:: server_context = { 0x00: client('host1.something.com'), 0x01: client('host2.something.com'), 0x02: client('host3.something.com') } This example is how to create a server context where the client is pointing to the same host, but the requested slave id is used as the slave for the client:: server_context = { 0x00: client('host1.something.com', 0x00), 0x01: client('host1.something.com', 0x01), 0x02: client('host1.something.com', 0x02) } """ from pymodbus.exceptions import NotImplementedException from pymodbus.interfaces import IModbusSlaveContext # -------------------------------------------------------------------------- # # Logging # -------------------------------------------------------------------------- # import logging _logger = logging.getLogger(__name__) # -------------------------------------------------------------------------- # # Slave Context # -------------------------------------------------------------------------- # # Basically we create a new slave context for the given slave identifier so # that this slave context will only make requests to that slave with the # client that the server is maintaining. # -------------------------------------------------------------------------- # class RemoteSingleSlaveContext(IModbusSlaveContext): """ This is a remote server context that allows one to create a server context backed by a single client that may be attached to many slave units. This can be used to effectively create a modbus forwarding server. """ def __init__(self, context, unit_id): """ Initializes the datastores :param context: The underlying context to operate with :param unit_id: The slave that this context will contact """ self.context = context self.unit_id = unit_id def reset(self): """ Resets all the datastores to their default values """ raise NotImplementedException() 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.context.get_callbacks[self.decode(fx)](address, count, self.unit_id) return not result.isError() def getValues(self, fx, address, count=1): """ Get `count` values from datastore :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 """ _logger.debug("get values[%d] %d:%d" % (fx, address, count)) result = self.context.get_callbacks[self.decode(fx)](address, count, self.unit_id) return self.__extract_result(self.decode(fx), result) 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 """ _logger.debug("set values[%d] %d:%d" % (fx, address, len(values))) self.context.set_callbacks[self.decode(fx)](address, values, self.unit_id) def __str__(self): """ Returns a string representation of the context :returns: A string representation of the context """ return "Remote Single Slave Context(%s)" % self.unit_id def __extract_result(self, fx, result): """ A helper method to extract the values out of a response. The future api should make the result consistent so we can just call `result.getValues()`. :param fx: The function to call :param result: The resulting data """ if not result.isError(): if fx in ['d', 'c']: return result.bits if fx in ['h', 'i']: return result.registers else: return result # -------------------------------------------------------------------------- # # Server Context # -------------------------------------------------------------------------- # # Think of this as simply a dictionary of { unit_id: client(req, unit_id) } # -------------------------------------------------------------------------- # class RemoteServerContext(object): """ This is a remote server context that allows one to create a server context backed by a single client that may be attached to many slave units. This can be used to effectively create a modbus forwarding server. """ def __init__(self, client): """ Initializes the datastores :param client: The client to retrieve values with """ self.get_callbacks = { 'd': lambda a, c, s: client.read_discrete_inputs(a, c, s), 'c': lambda a, c, s: client.read_coils(a, c, s), 'h': lambda a, c, s: client.read_holding_registers(a, c, s), 'i': lambda a, c, s: client.read_input_registers(a, c, s), } self.set_callbacks = { 'd': lambda a, v, s: client.write_coils(a, v, s), 'c': lambda a, v, s: client.write_coils(a, v, s), 'h': lambda a, v, s: client.write_registers(a, v, s), 'i': lambda a, v, s: client.write_registers(a, v, s), } self._client = client self.slaves = {} # simply a cache def __str__(self): """ Returns a string representation of the context :returns: A string representation of the context """ return "Remote Server Context(%s)" % self._client def __iter__(self): """ Iterater over the current collection of slave contexts. :returns: An iterator over the slave contexts """ # note, this may not include all slaves return iter(self.slaves.items()) 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 """ # we don't want to check the cache here as the # slave may not exist yet or may not exist any # more. The best thing to do is try and fail. return True 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 """ raise NotImplementedException() # doesn't make sense here def __delitem__(self, slave): """ Wrapper used to access the slave context :param slave: The slave context to remove """ raise NotImplementedException() # doesn't make sense here 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 slave not in self.slaves: self.slaves[slave] = RemoteSingleSlaveContext(self, slave) return self.slaves[slave] pymodbus-2.1.0/examples/contrib/requirements.txt000066400000000000000000000003501335513467700221320ustar00rootroot00000000000000# ------------------------------------------------------------------- # if you want to use the custom data stores, uncomment these # ------------------------------------------------------------------- SQLAlchemy==0.7.9 redis==2.6.2 pymodbus-2.1.0/examples/contrib/rx_messages000066400000000000000000000142151335513467700211160ustar00rootroot00000000000000# ------------------------------------------------------------ # What follows is a collection of encoded messages that can # be used to test the message-parser. Simply uncomment the # messages you want decoded and run the message parser with # the given arguments. What follows is the listing of messages # that are encoded in each format: # # - ReadHoldingRegistersResponse # - ReadDiscreteInputsResponse # - ReadInputRegistersResponse # - ReadCoilsResponse # - WriteMultipleCoilsResponse # - WriteMultipleRegistersResponse # - WriteSingleRegisterResponse # - WriteSingleCoilResponse # - ReadWriteMultipleRegistersResponse # - ReadExceptionStatusResponse # - GetCommEventCounterResponse # - GetCommEventLogResponse # - ReportSlaveIdResponse # - ReadFileRecordResponse # - WriteFileRecordResponse # - MaskWriteRegisterResponse # - ReadFifoQueueResponse # - ReadDeviceInformationResponse # - ReturnQueryDataResponse # - RestartCommunicationsOptionResponse # - ReturnDiagnosticRegisterResponse # - ChangeAsciiInputDelimiterResponse # - ForceListenOnlyModeResponse # - ClearCountersResponse # - ReturnBusMessageCountResponse # - ReturnBusCommunicationErrorCountResponse # - ReturnBusExceptionErrorCountResponse # - ReturnSlaveMessageCountResponse # - ReturnSlaveNoReponseCountResponse # - ReturnSlaveNAKCountResponse # - ReturnSlaveBusyCountResponse # - ReturnSlaveBusCharacterOverrunCountResponse # - ReturnIopOverrunCountResponse # - ClearOverrunCountResponse # - GetClearModbusPlusResponse # ------------------------------------------------------------ # Modbus TCP Messages # ------------------------------------------------------------ # [ MBAP Header ] [ Function Code] [ Data ] # [ tid ][ pid ][ length ][ uid ] # 2b 2b 2b 1b 1b Nb # # ./message-parser -b -p tcp -f messages # ------------------------------------------------------------ #00010000001301031000010001000100010001000100010001 #000100000004010201ff #00010000001301041000010001000100010001000100010001 #000100000004010101ff #000100000006010f00120008 #000100000006011000120008 #000100000006010600120001 #00010000000601050012ff00 #00010000001301171000010001000100010001000100010001 #000100000003010700 #000100000006010b00000008 #000100000009010c06000000000000 #00010000000501110300ff #000100000003011400 #000100000003011500 #00010000000801160012ffff0000 #00010000001601180012001000010001000100010001000100010001 #000100000008012b0e0183000000 #000100000006010800000000 #000100000006010800010000 #000100000006010800020000 #000100000006010800030000 #00010000000401080004 #0001000000060108000a0000 #0001000000060108000b0000 #0001000000060108000c0000 #0001000000060108000d0000 #0001000000060108000e0000 #0001000000060108000f0000 #000100000006010800100000 #000100000006010800110000 #000100000006010800120000 #000100000006010800130000 #000100000006010800140000 #000100000006010800150000 # ------------------------------------------------------------ # Modbus RTU Messages # ------------------------------------------------------------ # [Address ][ Function Code] [ Data ][ CRC ] # 1b 1b Nb 2b # # ./message-parser -b -p rtu -f messages # ------------------------------------------------------------ #0103100001000100010001000100010001000193b4 #010201ffe1c8 #0104100001000100010001000100010001000122c1 #010101ff11c8 #010f00120008f408 #01100012000861ca #010600120001e80f #01050012ff002c3f #01171000010001000100010001000100010001d640 #0107002230 #010b00000008a5cd #010c060000000000006135 #01110300ffacbc #0114002f00 #0115002e90 #01160012ffff00004e21 #01180012001000010001000100010001000100010001d74d #012b0e01830000000faf #010800000000e00b #010800010000b1cb #01080002000041cb #010800030000100b #0108000481d9 #0108000a0000c009 #0108000b000091c9 #0108000c00002008 #0108000d000071c8 #0108000e000081c8 #0108000f0000d008 #010800100000e1ce #010800110000b00e #010800120000400e #01080013000011ce #010800140000a00f #010800150000f1cf # ------------------------------------------------------------ # Modbus ASCII Messages # ------------------------------------------------------------ # [ Start ][Address ][ Function ][ Data ][ LRC ][ End ] # 1c 2c 2c Nc 2c 2c # # ./message-parser -a -p ascii -f messages # ------------------------------------------------------------ #:01031000010001000100010001000100010001E4 #:010201FFFD #:01041000010001000100010001000100010001E3 #:010101FFFE #:010F00120008D6 #:011000120008D5 #:010600120001E6 #:01050012FF00E9 #:01171000010001000100010001000100010001D0 #:010700F8 #:010B00000008EC #:010C06000000000000ED #:01110300FFEC #:011400EB #:011500EA #:01160012FFFF0000D9 #:01180012001000010001000100010001000100010001BD #:012B0E018300000042 #:010800000000F7 #:010800010000F6 #:010800020000F5 #:010800030000F4 #:01080004F3 #:0108000A0000ED #:0108000B0000EC #:0108000C0000EB #:0108000D0000EA #:0108000E0000E9 #:0108000F0000E8 #:010800100000E7 #:010800110000E6 #:010800120000E5 #:010800130000E4 #:010800140000E3 #:010800150000E2 # ------------------------------------------------------------ # Modbus Binary Messages # ------------------------------------------------------------ # [ Start ][Address ][ Function ][ Data ][ CRC ][ End ] # 1b 1b 1b Nb 2b 1b # # ./message-parser -b -p binary -f messages # ------------------------------------------------------------ #7b0103100001000100010001000100010001000193b47d #7b010201ffe1c87d #7b0104100001000100010001000100010001000122c17d #7b010101ff11c87d #7b010f00120008f4087d #7b01100012000861ca7d #7b010600120001e80f7d #7b01050012ff002c3f7d #7b01171000010001000100010001000100010001d6407d #7b01070022307d #7b010b00000008a5cd7d #7b010c0600000000000061357d #7b01110300ffacbc7d #7b0114002f007d #7b0115002e907d #7b01160012ffff00004e217d #7b01180012001000010001000100010001000100010001d74d7d #7b012b0e01830000000faf7d #7b010800000000e00b7d #7b010800010000b1cb7d #7b01080002000041cb7d #7b010800030000100b7d #7b0108000481d97d #7b0108000a0000c0097d #7b0108000b000091c97d #7b0108000c000020087d #7b0108000d000071c87d #7b0108000e000081c87d #7b0108000f0000d0087d #7b010800100000e1ce7d #7b010800110000b00e7d #7b010800120000400e7d #7b01080013000011ce7d #7b010800140000a00f7d #7b010800150000f1cf7d pymodbus-2.1.0/examples/contrib/serial_forwarder.py000077500000000000000000000034061335513467700225620ustar00rootroot00000000000000#!/usr/bin/env python """ Pymodbus Synchronous Serial Forwarder -------------------------------------------------------------------------- We basically set the context for the tcp serial server to be that of a serial client! This is just an example of how clever you can be with the data context (basically anything can become a modbus device). """ # --------------------------------------------------------------------------- # # import the various server implementations # --------------------------------------------------------------------------- # from pymodbus.server.sync import StartTcpServer as StartServer from pymodbus.client.sync import ModbusSerialClient as ModbusClient from pymodbus.datastore.remote import RemoteSlaveContext from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext # --------------------------------------------------------------------------- # # configure the service logging # --------------------------------------------------------------------------- # import logging logging.basicConfig() log = logging.getLogger() log.setLevel(logging.DEBUG) def run_serial_forwarder(): # ----------------------------------------------------------------------- # # initialize the datastore(serial client) # ----------------------------------------------------------------------- # client = ModbusClient(method='rtu', port='/dev/ptyp0') store = RemoteSlaveContext(client) context = ModbusServerContext(slaves=store, single=True) # ----------------------------------------------------------------------- # # run the server you want # ----------------------------------------------------------------------- # StartServer(context, address=("localhost", 5020)) if __name__ == "__main__": run_serial_forwarder() pymodbus-2.1.0/examples/contrib/sunspec_client.py000066400000000000000000000254311335513467700222450ustar00rootroot00000000000000from pymodbus.constants import Endian from pymodbus.client.sync import ModbusTcpClient from pymodbus.payload import BinaryPayloadDecoder from twisted.internet.defer import Deferred # --------------------------------------------------------------------------- # # Logging # --------------------------------------------------------------------------- # import logging _logger = logging.getLogger(__name__) _logger.setLevel(logging.DEBUG) logging.basicConfig() # --------------------------------------------------------------------------- # # Sunspec Common Constants # --------------------------------------------------------------------------- # class SunspecDefaultValue(object): """ A collection of constants to indicate if a value is not implemented. """ Signed16 = 0x8000 Unsigned16 = 0xffff Accumulator16 = 0x0000 Scale = 0x8000 Signed32 = 0x80000000 Float32 = 0x7fc00000 Unsigned32 = 0xffffffff Accumulator32 = 0x00000000 Signed64 = 0x8000000000000000 Unsigned64 = 0xffffffffffffffff Accumulator64 = 0x0000000000000000 String = '\x00' class SunspecStatus(object): """ Indicators of the current status of a sunspec device """ Normal = 0x00000000 Error = 0xfffffffe Unknown = 0xffffffff class SunspecIdentifier(object): """ Assigned identifiers that are pre-assigned by the sunspec protocol. """ Sunspec = 0x53756e53 class SunspecModel(object): """ Assigned device indentifiers that are pre-assigned by the sunspec protocol. """ #--------------------------------------------- # 0xx Common Models #--------------------------------------------- CommonBlock = 1 AggregatorBlock = 2 #--------------------------------------------- # 1xx Inverter Models #--------------------------------------------- SinglePhaseIntegerInverter = 101 SplitPhaseIntegerInverter = 102 ThreePhaseIntegerInverter = 103 SinglePhaseFloatsInverter = 103 SplitPhaseFloatsInverter = 102 ThreePhaseFloatsInverter = 103 #--------------------------------------------- # 2xx Meter Models #--------------------------------------------- SinglePhaseMeter = 201 SplitPhaseMeter = 201 WyeConnectMeter = 201 DeltaConnectMeter = 201 #--------------------------------------------- # 3xx Environmental Models #--------------------------------------------- BaseMeteorological = 301 Irradiance = 302 BackOfModuleTemperature = 303 Inclinometer = 304 Location = 305 ReferencePoint = 306 BaseMeteorological = 307 MiniMeteorological = 308 #--------------------------------------------- # 4xx String Combiner Models #--------------------------------------------- BasicStringCombiner = 401 AdvancedStringCombiner = 402 #--------------------------------------------- # 5xx Panel Models #--------------------------------------------- PanelFloat = 501 PanelInteger = 502 #--------------------------------------------- # 641xx Outback Blocks #--------------------------------------------- OutbackDeviceIdentifier = 64110 OutbackChargeController = 64111 OutbackFMSeriesChargeController = 64112 OutbackFXInverterRealTime = 64113 OutbackFXInverterConfiguration = 64114 OutbackSplitPhaseRadianInverter = 64115 OutbackRadianInverterConfiguration = 64116 OutbackSinglePhaseRadianInverterRealTime = 64117 OutbackFLEXNetDCRealTime = 64118 OutbackFLEXNetDCConfiguration = 64119 OutbackSystemControl = 64120 #--------------------------------------------- # 64xxx Vender Extension Block #--------------------------------------------- EndOfSunSpecMap = 65535 @classmethod def lookup(klass, code): """ Given a device identifier, return the device model name for that identifier :param code: The device code to lookup :returns: The device model name, or None if none available """ values = dict((v, k) for k, v in klass.__dict__.iteritems() if not callable(v)) return values.get(code, None) class SunspecOffsets(object): """ Well known offsets that are used throughout the sunspec protocol """ CommonBlock = 40000 CommonBlockLength = 69 AlternateCommonBlock = 50000 # --------------------------------------------------------------------------- # # Common Functions # --------------------------------------------------------------------------- # def defer_or_apply(func): """ Decorator to apply an adapter method to a result regardless if it is a deferred or a concrete response. :param func: The function to decorate """ def closure(future, adapt): if isinstance(future, Deferred): d = Deferred() future.addCallback(lambda r: d.callback(adapt(r))) return d return adapt(future) return closure def create_sunspec_sync_client(host): """ A quick helper method to create a sunspec client. :param host: The host to connect to :returns: an initialized SunspecClient """ modbus = ModbusTcpClient(host) modbus.connect() client = SunspecClient(modbus) client.initialize() return client # --------------------------------------------------------------------------- # # Sunspec Client # --------------------------------------------------------------------------- # class SunspecDecoder(BinaryPayloadDecoder): """ A decoder that deals correctly with the sunspec binary format. """ def __init__(self, payload, byteorder): """ Initialize a new instance of the SunspecDecoder .. note:: This is always set to big endian byte order as specified in the protocol. """ byteorder = Endian.Big BinaryPayloadDecoder.__init__(self, payload, byteorder) def decode_string(self, size=1): """ Decodes a string from the buffer :param size: The size of the string to decode """ self._pointer += size string = self._payload[self._pointer - size:self._pointer] return string.split(SunspecDefaultValue.String)[0] class SunspecClient(object): def __init__(self, client): """ Initialize a new instance of the client :param client: The modbus client to use """ self.client = client self.offset = SunspecOffsets.CommonBlock def initialize(self): """ Initialize the underlying client values :returns: True if successful, false otherwise """ decoder = self.get_device_block(self.offset, 2) if decoder.decode_32bit_uint() == SunspecIdentifier.Sunspec: return True self.offset = SunspecOffsets.AlternateCommonBlock decoder = self.get_device_block(self.offset, 2) return decoder.decode_32bit_uint() == SunspecIdentifier.Sunspec def get_common_block(self): """ Read and return the sunspec common information block. :returns: A dictionary of the common block information """ length = SunspecOffsets.CommonBlockLength decoder = self.get_device_block(self.offset, length) return { 'SunSpec_ID': decoder.decode_32bit_uint(), 'SunSpec_DID': decoder.decode_16bit_uint(), 'SunSpec_Length': decoder.decode_16bit_uint(), 'Manufacturer': decoder.decode_string(size=32), 'Model': decoder.decode_string(size=32), 'Options': decoder.decode_string(size=16), 'Version': decoder.decode_string(size=16), 'SerialNumber': decoder.decode_string(size=32), 'DeviceAddress': decoder.decode_16bit_uint(), 'Next_DID': decoder.decode_16bit_uint(), 'Next_DID_Length': decoder.decode_16bit_uint(), } def get_device_block(self, offset, size): """ A helper method to retrieve the next device block .. note:: We will read 2 more registers so that we have the information for the next block. :param offset: The offset to start reading at :param size: The size of the offset to read :returns: An initialized decoder for that result """ _logger.debug("reading device block[{}..{}]".format(offset, offset + size)) response = self.client.read_holding_registers(offset, size + 2) return SunspecDecoder.fromRegisters(response.registers) def get_all_device_blocks(self): """ Retrieve all the available blocks in the supplied sunspec device. .. note:: Since we do not know how to decode the available blocks, this returns a list of dictionaries of the form: decoder: the-binary-decoder, model: the-model-identifier (name) :returns: A list of the available blocks """ blocks = [] offset = self.offset + 2 model = SunspecModel.CommonBlock while model != SunspecModel.EndOfSunSpecMap: decoder = self.get_device_block(offset, 2) model = decoder.decode_16bit_uint() length = decoder.decode_16bit_uint() blocks.append({ 'model' : model, 'name' : SunspecModel.lookup(model), 'length': length, 'offset': offset + length + 2 }) offset += length + 2 return blocks #------------------------------------------------------------ # A quick test runner #------------------------------------------------------------ if __name__ == "__main__": client = create_sunspec_sync_client("YOUR.HOST.GOES.HERE") # print out all the device common block common = client.get_common_block() for key, value in common.iteritems(): if key == "SunSpec_DID": value = SunspecModel.lookup(value) print("{:<20}: {}".format(key, value)) # print out all the available device blocks blocks = client.get_all_device_blocks() for block in blocks: print(block) client.client.close() pymodbus-2.1.0/examples/contrib/thread_safe_datastore.py000066400000000000000000000177461335513467700235540ustar00rootroot00000000000000import threading from contextlib import contextmanager from pymodbus.datastore.store import BaseModbusDataBlock class ContextWrapper(object): """ This is a simple wrapper around enter and exit functions that conforms to the pyhton context manager protocol: with ContextWrapper(enter, leave): do_something() """ def __init__(self, enter=None, leave=None, factory=None): self._enter = enter self._leave = leave self._factory = factory def __enter__(self): if self.enter: self._enter() return self if not self._factory else self._factory() def __exit__(self, args): if self._leave: self._leave() class ReadWriteLock(object): """ This reader writer lock gurantees write order, but not read order and is generally biased towards allowing writes if they are available to prevent starvation. TODO: * allow user to choose between read/write/random biasing - currently write biased - read biased allow N readers in queue - random is 50/50 choice of next """ def __init__(self): """ Initializes a new instance of the ReadWriteLock """ self.queue = [] # the current writer queue self.lock = threading.Lock() # the underlying condition lock self.read_condition = threading.Condition(self.lock) # the single reader condition self.readers = 0 # the number of current readers self.writer = False # is there a current writer def __is_pending_writer(self): return (self.writer # if there is a current writer or (self.queue # or if there is a waiting writer and (self.queue[0] != self.read_condition))) # or if the queue head is not a reader def acquire_reader(self): """ Notifies the lock that a new reader is requesting the underlying resource. """ with self.lock: if self.__is_pending_writer(): # if there are existing writers waiting if self.read_condition not in self.queue: # do not pollute the queue with readers self.queue.append(self.read_condition) # add the readers in line for the queue while self.__is_pending_writer(): # until the current writer is finished self.read_condition.wait(1) # wait on our condition if self.queue and self.read_condition == self.queue[0]: # if the read condition is at the queue head self.queue.pop(0) # then go ahead and remove it self.readers += 1 # update the current number of readers def acquire_writer(self): """ Notifies the lock that a new writer is requesting the underlying resource. """ with self.lock: if self.writer or self.readers: # if we need to wait on a writer or readers condition = threading.Condition(self.lock) # create a condition just for this writer self.queue.append(condition) # and put it on the waiting queue while self.writer or self.readers: # until the write lock is free condition.wait(1) # wait on our condition self.queue.pop(0) # remove our condition after our condition is met self.writer = True # stop other writers from operating def release_reader(self): """ Notifies the lock that an existing reader is finished with the underlying resource. """ with self.lock: self.readers = max(0, self.readers - 1) # readers should never go below 0 if not self.readers and self.queue: # if there are no active readers self.queue[0].notify_all() # then notify any waiting writers def release_writer(self): """ Notifies the lock that an existing writer is finished with the underlying resource. """ with self.lock: self.writer = False # give up current writing handle if self.queue: # if someone is waiting in the queue self.queue[0].notify_all() # wake them up first else: self.read_condition.notify_all() # otherwise wake up all possible readers @contextmanager def get_reader_lock(self): """ Wrap some code with a reader lock using the python context manager protocol:: with rwlock.get_reader_lock(): do_read_operation() """ try: self.acquire_reader() yield self finally: self.release_reader() @contextmanager def get_writer_lock(self): """ Wrap some code with a writer lock using the python context manager protocol:: with rwlock.get_writer_lock(): do_read_operation() """ try: self.acquire_writer() yield self finally: self.release_writer() class ThreadSafeDataBlock(BaseModbusDataBlock): """ This is a simple decorator for a data block. This allows a user to inject an existing data block which can then be safely operated on from multiple cocurrent threads. It should be noted that the choice was made to lock around the datablock instead of the manager as there is less source of contention (writes can occur to slave 0x01 while reads can occur to slave 0x02). """ def __init__(self, block): """ Initialize a new thread safe decorator :param block: The block to decorate """ self.rwlock = ReadWriteLock() self.block = block 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 """ with self.rwlock.get_reader_lock(): return self.block.validate(address, count) 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 """ with self.rwlock.get_reader_lock(): return self.block.getValues(address, count) 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 """ with self.rwlock.get_writer_lock(): return self.block.setValues(address, values) if __name__ == "__main__": class AtomicCounter(object): def __init__(self, **kwargs): self.counter = kwargs.get('start', 0) self.finish = kwargs.get('finish', 1000) self.lock = threading.Lock() def increment(self, count=1): with self.lock: self.counter += count def is_running(self): return self.counter <= self.finish locker = ReadWriteLock() readers, writers = AtomicCounter(), AtomicCounter() def read(): while writers.is_running() and readers.is_running(): with locker.get_reader_lock(): readers.increment() def write(): while writers.is_running() and readers.is_running(): with locker.get_writer_lock(): writers.increment() rthreads = [threading.Thread(target=read) for i in range(50)] wthreads = [threading.Thread(target=write) for i in range(2)] for t in rthreads + wthreads: t.start() for t in rthreads + wthreads: t.join() print("readers[%d] writers[%d]" % (readers.counter, writers.counter)) pymodbus-2.1.0/examples/contrib/tx_messages000066400000000000000000000136021335513467700211170ustar00rootroot00000000000000# ------------------------------------------------------------ # What follows is a collection of encoded messages that can # be used to test the message-parser. Simply uncomment the # messages you want decoded and run the message parser with # the given arguments. What follows is the listing of messages # that are encoded in each format: # # - ReadHoldingRegistersRequest # - ReadDiscreteInputsRequest # - ReadInputRegistersRequest # - ReadCoilsRequest # - WriteMultipleCoilsRequest # - WriteMultipleRegistersRequest # - WriteSingleRegisterRequest # - WriteSingleCoilRequest # - ReadWriteMultipleRegistersRequest # - ReadExceptionStatusRequest # - GetCommEventCounterRequest # - GetCommEventLogRequest # - ReportSlaveIdRequest # - ReadFileRecordRequest # - WriteFileRecordRequest # - MaskWriteRegisterRequest # - ReadFifoQueueRequest # - ReadDeviceInformationRequest # - ReturnQueryDataRequest # - RestartCommunicationsOptionRequest # - ReturnDiagnosticRegisterRequest # - ChangeAsciiInputDelimiterRequest # - ForceListenOnlyModeRequest # - ClearCountersRequest # - ReturnBusMessageCountRequest # - ReturnBusCommunicationErrorCountRequest # - ReturnBusExceptionErrorCountRequest # - ReturnSlaveMessageCountRequest # - ReturnSlaveNoReponseCountRequest # - ReturnSlaveNAKCountRequest # - ReturnSlaveBusyCountRequest # - ReturnSlaveBusCharacterOverrunCountRequest # - ReturnIopOverrunCountRequest # - ClearOverrunCountRequest # - GetClearModbusPlusRequest # ------------------------------------------------------------ # Modbus TCP Messages # ------------------------------------------------------------ # [ MBAP Header ] [ Function Code] [ Data ] # [ tid ][ pid ][ length ][ uid ] # 2b 2b 2b 1b 1b Nb # # ./message-parser -b -p tcp -f messages # ------------------------------------------------------------ #000100000006010300120008 #000100000006010200120008 #000100000006010400120008 #000100000006010100120008 #000100000008010f0012000801ff #0001000000170110001200081000010001000100010001000100010001 #000100000006010600120001 #00010000000601050012ff00 #00010000001b011700120008000000081000010001000100010001000100010001 #0001000000020107 #000100000002010b #000100000002010c #0001000000020111 #000100000003011400 #000100000003011500 #00010000000801160012ffff0000 #00010000000401180012 #000100000005012b0e0100 #000100000006010800000000 #000100000006010800010000 #000100000006010800020000 #000100000006010800030000 #000100000006010800040000 #0001000000060108000a0000 #0001000000060108000b0000 #0001000000060108000c0000 #0001000000060108000d0000 #0001000000060108000e0000 #0001000000060108000f0000 #000100000006010800100000 #000100000006010800110000 #000100000006010800120000 #000100000006010800130000 #000100000006010800140000 #000100000006010800150000 # ------------------------------------------------------------ # Modbus RTU Messages # ------------------------------------------------------------ # [Address ][ Function Code] [ Data ][ CRC ] # 1b 1b Nb 2b # # ./message-parser -b -p rtu -f messages # ------------------------------------------------------------ #010300120008e409 #010200120008d9c9 #01040012000851c9 #0101001200089dc9 #010f0012000801ff06d6 #0110001200081000010001000100010001000100010001d551 #010600120001e80f #01050012ff002c3f #011700120008000000081000010001000100010001000100010001e6f8 #010741e2 #010b41e7 #010c0025 #0111c02c #0114002f00 #0115002e90 #01160012ffff00004e21 #0118001201d2 #012b0e01007077 #010800000000e00b #010800010000b1cb #01080002000041cb #010800030000100b #010800040000a1ca #0108000a0000c009 #0108000b000091c9 #0108000c00002008 #0108000d000071c8 #0108000e000081c8 #0108000f0000d008 #010800100000e1ce #010800110000b00e #010800120000400e #01080013000011ce #010800140000a00f #010800150000f1cf # ------------------------------------------------------------ # Modbus ASCII Messages # ------------------------------------------------------------ # [ Start ][Address ][ Function ][ Data ][ LRC ][ End ] # 1c 2c 2c Nc 2c 2c # # ./message-parser -a -p ascii -f messages # ------------------------------------------------------------ #:010300120008E2 #:010200120008E3 #:010400120008E1 #:010100120008E4 #:010F0012000801FFD6 #:0110001200081000010001000100010001000100010001BD #:010600120001E6 #:01050012FF00E9 #:011700120008000000081000010001000100010001000100010001AE #:0107F8 #:010BF4 #:010CF3 #:0111EE #:011400EB #:011500EA #:01160012FFFF0000D9 #:01180012D5 #:012B0E0100C5 #:010800000000F7 #:010800010000F6 #:010800020000F5 #:010800030000F4 #:010800040000F3 #:0108000A0000ED #:0108000B0000EC #:0108000C0000EB #:0108000D0000EA #:0108000E0000E9 #:0108000F0000E8 #:010800100000E7 #:010800110000E6 #:010800120000E5 #:010800130000E4 #:010800140000E3 #:010800150000E2 # ------------------------------------------------------------ # Modbus Binary Messages # ------------------------------------------------------------ # [ Start ][Address ][ Function ][ Data ][ CRC ][ End ] # 1b 1b 1b Nb 2b 1b # # ./message-parser -b -p binary -f messages # ------------------------------------------------------------ #7b010300120008e4097d #7b010200120008d9c97d #7b01040012000851c97d #7b0101001200089dc97d #7b010f0012000801ff06d67d #7b0110001200081000010001000100010001000100010001d5517d #7b010600120001e80f7d #7b01050012ff002c3f7d #7b011700120008000000081000010001000100010001000100010001e6f87d #7b010741e27d #7b010b41e77d #7b010c00257d #7b0111c02c7d #7b0114002f007d #7b0115002e907d #7b01160012ffff00004e217d #7b0118001201d27d #7b012b0e010070777d #7b010800000000e00b7d #7b010800010000b1cb7d #7b01080002000041cb7d #7b010800030000100b7d #7b010800040000a1ca7d #7b0108000a0000c0097d #7b0108000b000091c97d #7b0108000c000020087d #7b0108000d000071c87d #7b0108000e000081c87d #7b0108000f0000d0087d #7b010800100000e1ce7d #7b010800110000b00e7d #7b010800120000400e7d #7b01080013000011ce7d #7b010800140000a00f7d #7b010800150000f1cf7d pymodbus-2.1.0/examples/functional/000077500000000000000000000000001335513467700173525ustar00rootroot00000000000000pymodbus-2.1.0/examples/functional/README.rst000066400000000000000000000017761335513467700210540ustar00rootroot00000000000000============================================================================ Pymodbus Functional Tests ============================================================================ Modbus Clients --------------------------------------------------------------------------- The following can be run to validate the pymodbus clients against a running modbus instance. For these tests, the following are used as references:: * jamod * modpoll Modbus Servers --------------------------------------------------------------------------- The following can be used to create a null modem loopback for testing the serial implementations:: * tty0tty (linux) * com0com (windows) Specialized Datastores --------------------------------------------------------------------------- The following can be run to validate the pymodbus specializes datastores. For these tests, the following are used as references: * sqlite (for the database datastore) * redis (for the redis datastore) * modpoll (for the remote slave datastore) pymodbus-2.1.0/examples/functional/asynchronous_ascii_client.py000066400000000000000000000014301335513467700251630ustar00rootroot00000000000000#!/usr/bin/env python import unittest from pymodbus.client.async import ModbusSerialClient as ModbusClient from base_runner import Runner class AsynchronousAsciiClient(Runner, unittest.TestCase): """ These are the integration tests for the asynchronous serial ascii client. """ def setUp(self): """ Initializes the test environment """ super(Runner, self).setUp() self.client = ModbusClient(method='ascii') def tearDown(self): """ Cleans up the test environment """ self.client.close() self.shutdown() # --------------------------------------------------------------------------- # # Main # --------------------------------------------------------------------------- # if __name__ == "__main__": unittest.main() pymodbus-2.1.0/examples/functional/asynchronous_rtu_client.py000066400000000000000000000014411335513467700247070ustar00rootroot00000000000000#!/usr/bin/env python import unittest from pymodbus.client.async import ModbusSerialClient as ModbusClient from base_runner import Runner class AsynchronousRtuClient(Runner, unittest.TestCase): """ These are the integration tests for the asynchronous serial rtu client. """ def setUp(self): """ Initializes the test environment """ super(Runner, self).setUp() self.client = ModbusClient(method='rtu') def tearDown(self): """ Cleans up the test environment """ self.client.close() super(Runner, self).tearDown() # --------------------------------------------------------------------------- # # Main # --------------------------------------------------------------------------- # if __name__ == "__main__": unittest.main() pymodbus-2.1.0/examples/functional/asynchronous_tcp_client.py000066400000000000000000000022221335513467700246610ustar00rootroot00000000000000#!/usr/bin/env python import unittest from twisted.internet import reactor, protocol from pymodbus.constants import Defaults from pymodbus.client.async import ModbusClientProtocol from base_runner import Runner class AsynchronousTcpClient(Runner, unittest.TestCase): """ These are the integration tests for the asynchronous tcp client. """ def setUp(self): """ Initializes the test environment """ def _callback(client): self.client = client self.initialize(["../tools/reference/diagslave", "-m", "tcp", "-p", "12345"]) defer = protocol.ClientCreator(reactor, ModbusClientProtocol ).connectTCP("localhost", Defaults.Port) defer.addCallback(_callback) reactor.run() def tearDown(self): """ Cleans up the test environment """ reactor.callLater(1, client.transport.loseConnection) reactor.callLater(2, reactor.stop) reactor.shutdown() # --------------------------------------------------------------------------- # # Main # --------------------------------------------------------------------------- # if __name__ == "__main__": unittest.main() pymodbus-2.1.0/examples/functional/asynchronous_udp_client.py000066400000000000000000000014121335513467700246630ustar00rootroot00000000000000#!/usr/bin/env python import unittest from pymodbus.client.sync import ModbusUdpClient as ModbusClient from base_runner import Runner class AsynchronousUdpClient(Runner, unittest.TestCase): """ These are the integration tests for the asynchronous udp client. """ def setUp(self): """ Initializes the test environment """ super(Runner, self).setUp() self.client = ModbusClient() def tearDown(self): """ Cleans up the test environment """ self.client.close() super(Runner, self).tearDown() # --------------------------------------------------------------------------- # # Main # --------------------------------------------------------------------------- # if __name__ == "__main__": unittest.main() pymodbus-2.1.0/examples/functional/base_context.py000066400000000000000000000041441335513467700224050ustar00rootroot00000000000000import os import time from subprocess import Popen as execute from twisted.internet.defer import Deferred # --------------------------------------------------------------------------- # # configure the client logging # --------------------------------------------------------------------------- # import logging log = logging.getLogger(__name__) class ContextRunner(object): """ This is the base runner class for all the integration tests """ __bit_functions = [2,1] # redundant are removed for now __reg_functions = [4,3] # redundant are removed for now def initialize(self, service=None): """ Initializes the test environment """ if service: self.fnull = open(os.devnull, 'w') self.service = execute(service, stdout=self.fnull, stderr=self.fnull) log.debug("%s service started: %s", service, self.service.pid) time.sleep(0.2) else: self.service = None log.debug("%s context started", self.context) def shutdown(self): """ Cleans up the test environment """ try: if self.service: self.service.kill() self.fnull.close() self.context.reset() except: pass log.debug("%s context stopped" % self.context) def testDataContextRegisters(self): """ Test that the context gets and sets registers """ address = 10 values = [0x1234] * 32 for fx in self.__reg_functions: self.context.setValues(fx, address, values) result = self.context.getValues(fx, address, len(values)) self.assertEquals(len(result), len(values)) self.assertEquals(result, values) def testDataContextDiscretes(self): """ Test that the context gets and sets discretes """ address = 10 values = [True] * 32 for fx in self.__bit_functions: self.context.setValues(fx, address, values) result = self.context.getValues(fx, address, len(values)) self.assertEquals(len(result), len(values)) self.assertEquals(result, values) pymodbus-2.1.0/examples/functional/base_runner.py000066400000000000000000000057401335513467700222350ustar00rootroot00000000000000import os import time from subprocess import Popen as execute from twisted.internet.defer import Deferred # --------------------------------------------------------------------------- # # configure the client logging # --------------------------------------------------------------------------- # import logging log = logging.getLogger(__name__) class Runner(object): """ This is the base runner class for all the integration tests """ def initialize(self, service): """ Initializes the test environment """ self.fnull = open(os.devnull, 'w') self.server = execute(service, stdout=self.fnull, stderr=self.fnull) log.debug("%s service started: %s", service, self.server.pid) time.sleep(0.2) def shutdown(self): """ Cleans up the test environment """ self.server.kill() self.fnull.close() log.debug("service stopped") def testReadWriteCoil(self): rq = self.client.write_coil(1, True) rr = self.client.read_coils(1,1) self._validate(rq, lambda r: not r.isError()) self._validate(rr, lambda r: r.bits[0] == True) def testReadWriteCoils(self): rq = self.client.write_coils(1, [True]*8) rr = self.client.read_coils(1,8) self._validate(rq, lambda r: not r.isError()) self._validate(rr, lambda r: r.bits == [True]*8) def testReadWriteDiscreteRegisters(self): rq = self.client.write_coils(1, [False]*8) rr = self.client.read_discrete_inputs(1,8) self._validate(rq, lambda r: not r.isError()) self._validate(rr, lambda r: r.bits == [False]*8) def testReadWriteHoldingRegisters(self): rq = self.client.write_register(1, 10) rr = self.client.read_holding_registers(1,1) self._validate(rq, lambda r: not r.isError()) self._validate(rr, lambda r: r.registers[0] == 10) def testReadWriteInputRegisters(self): rq = self.client.write_registers(1, [10]*8) rr = self.client.read_input_registers(1,8) self._validate(rq, lambda r: not r.isError()) self._validate(rr, lambda r: r.registers == [10]*8) def testReadWriteRegistersTogether(self): arguments = { 'read_address': 1, 'read_count': 8, 'write_address': 1, 'write_registers': [20]*8, } rq = self.client.readwrite_registers(**arguments) rr = self.client.read_input_registers(1,8) self._validate(rq, lambda r: not r.isError()) self._validate(rr, lambda r: r.registers == [20]*8) def _validate(self, result, test): """ Validate the result whether it is a result or a deferred. :param result: The result to _validate :param callback: The test to _validate """ if isinstance(result, Deferred): deferred.callback(lambda : self.assertTrue(test(result))) deferred.errback(lambda _: self.assertTrue(False)) else: self.assertTrue(test(result)) pymodbus-2.1.0/examples/functional/database_slave_context.py000066400000000000000000000017101335513467700244250ustar00rootroot00000000000000#!/usr/bin/env python import unittest, os from pymodbus.datastore.database import DatabaseSlaveContext from base_context import ContextRunner class DatabaseSlaveContextTest(ContextRunner, unittest.TestCase): """ These are the integration tests for using the redis slave context. """ __database = 'sqlite:///pymodbus-test.db' def setUp(self): """ Initializes the test environment """ path = './' + self.__database.split('///')[1] if os.path.exists(path): os.remove(path) self.context = DatabaseSlaveContext(database=self.__database) self.initialize() def tearDown(self): """ Cleans up the test environment """ self.context._connection.close() self.shutdown() # --------------------------------------------------------------------------- # # Main # --------------------------------------------------------------------------- # if __name__ == "__main__": unittest.main() pymodbus-2.1.0/examples/functional/memory_slave_context.py000077500000000000000000000020251335513467700241740ustar00rootroot00000000000000#!/usr/bin/env python import unittest from pymodbus.datastore.context import ModbusSlaveContext from pymodbus.datastore.store import ModbusSequentialDataBlock from base_context import ContextRunner class MemorySlaveContextTest(ContextRunner, unittest.TestCase): """ These are the integration tests for using the in memory slave context. """ def setUp(self): """ Initializes the test environment """ self.context = ModbusSlaveContext(**{ 'di' : ModbusSequentialDataBlock(0, [0]*100), 'co' : ModbusSequentialDataBlock(0, [0]*100), 'ir' : ModbusSequentialDataBlock(0, [0]*100), 'hr' : ModbusSequentialDataBlock(0, [0]*100)}) self.initialize() def tearDown(self): """ Cleans up the test environment """ self.shutdown() # --------------------------------------------------------------------------- # # Main # --------------------------------------------------------------------------- # if __name__ == "__main__": unittest.main() pymodbus-2.1.0/examples/functional/redis_slave_context.py000066400000000000000000000016141335513467700237720ustar00rootroot00000000000000#!/usr/bin/env python import unittest import os from subprocess import Popen as execute from pymodbus.datastore.modredis import RedisSlaveContext from base_context import ContextRunner class RedisSlaveContextTest(ContextRunner, unittest.TestCase): """ These are the integration tests for using the redis slave context. """ def setUp(self): """ Initializes the test environment """ self.context = RedisSlaveContext() # the redis client will block, so no wait needed self.initialize("redis-server") def tearDown(self): """ Cleans up the test environment """ self.server.kill() self.fnull.close() self.shutdown() # --------------------------------------------------------------------------- # # Main # --------------------------------------------------------------------------- # if __name__ == "__main__": unittest.main() pymodbus-2.1.0/examples/functional/remote_slave_context.py000066400000000000000000000020011335513467700241460ustar00rootroot00000000000000#!/usr/bin/env python import unittest from pymodbus.client.sync import ModbusTcpClient from pymodbus.datastore.remote import RemoteSlaveContext from base_context import ContextRunner class RemoteSlaveContextTest(ContextRunner, unittest.TestCase): """ These are the integration tests for using the redis slave context. """ def setUp(self): """ Initializes the test environment """ self.context = RemoteSlaveContext(client=None) # for the log statment self.initialize(["../tools/reference/diagslave", "-m", "tcp", "-p", "12345"]) self.client = ModbusTcpClient(port=12345) self.context = RemoteSlaveContext(client=self.client) def tearDown(self): """ Cleans up the test environment """ self.client.close() self.shutdown() # --------------------------------------------------------------------------- # # Main # --------------------------------------------------------------------------- # if __name__ == "__main__": unittest.main() pymodbus-2.1.0/examples/functional/synchronous_ascii_client.py000066400000000000000000000017501335513467700250270ustar00rootroot00000000000000#!/usr/bin/env python import unittest from pymodbus.client.sync import ModbusSerialClient as ModbusClient from base_runner import Runner class SynchronousAsciiClient(Runner, unittest.TestCase): """ These are the integration tests for the synchronous serial ascii client. """ def setUp(self): """ Initializes the test environment """ super(Runner, self).setUp() # "../tools/nullmodem/linux/run", self.initialize(["../tools/reference/diagslave", "-m", "ascii", "/dev/pts/14"]) self.client = ModbusClient(method='ascii', timeout=0.2, port='/dev/pts/13') self.client.connect() def tearDown(self): """ Cleans up the test environment """ self.client.close() super(Runner, self).tearDown() # --------------------------------------------------------------------------- # # Main # --------------------------------------------------------------------------- # if __name__ == "__main__": unittest.main() pymodbus-2.1.0/examples/functional/synchronous_rtu_client.py000066400000000000000000000016631335513467700245540ustar00rootroot00000000000000#!/usr/bin/env python import unittest from pymodbus.client.sync import ModbusSerialClient as ModbusClient from base_runner import Runner class SynchronousRtuClient(Runner, unittest.TestCase): """ These are the integration tests for the synchronous serial rtu client. """ def setUp(self): """ Initializes the test environment """ super(Runner, self).setUp() self.initialize(["../tools/reference/diagslave", "-m", "rtu", "/dev/pts/14"]) self.client = ModbusClient(method='rtu', timeout=0.2, port='/dev/pts/13') self.client.connect() def tearDown(self): """ Cleans up the test environment """ self.client.close() super(Runner, self).tearDown() # --------------------------------------------------------------------------- # # Main # --------------------------------------------------------------------------- # if __name__ == "__main__": unittest.main() pymodbus-2.1.0/examples/functional/synchronous_tcp_client.py000066400000000000000000000014651335513467700245300ustar00rootroot00000000000000#!/usr/bin/env python import unittest from pymodbus.client.sync import ModbusTcpClient as ModbusClient from base_runner import Runner class SynchronousTcpClient(Runner, unittest.TestCase): """ These are the integration tests for the synchronous tcp client. """ def setUp(self): """ Initializes the test environment """ self.initialize(["../tools/reference/diagslave", "-m", "tcp", "-p", "12345"]) self.client = ModbusClient(port=12345) def tearDown(self): """ Cleans up the test environment """ self.client.close() self.shutdown() # --------------------------------------------------------------------------- # # Main # --------------------------------------------------------------------------- # if __name__ == "__main__": unittest.main() pymodbus-2.1.0/examples/functional/synchronous_udp_client.py000066400000000000000000000014101335513467700245200ustar00rootroot00000000000000#!/usr/bin/env python import unittest from pymodbus.client.sync import ModbusUdpClient as ModbusClient from base_runner import Runner class SynchronousUdpClient(Runner, unittest.TestCase): """ These are the integration tests for the synchronous udp client. """ def setUp(self): """ Initializes the test environment """ super(Runner, self).setUp() self.client = ModbusClient() def tearDown(self): """ Cleans up the test environment """ self.client.close() super(Runner, self).tearDown() # --------------------------------------------------------------------------- # # Main # --------------------------------------------------------------------------- # if __name__ == "__main__": unittest.main() pymodbus-2.1.0/examples/gui/000077500000000000000000000000001335513467700157745ustar00rootroot00000000000000pymodbus-2.1.0/examples/gui/bottle/000077500000000000000000000000001335513467700172655ustar00rootroot00000000000000pymodbus-2.1.0/examples/gui/bottle/frontend.py000066400000000000000000000255131335513467700214640ustar00rootroot00000000000000""" Pymodbus Web Frontend ======================================= This is a simple web frontend using bottle as the web framework. This can be hosted using any wsgi adapter. """ from __future__ import print_function import json, inspect from bottle import route, request, Bottle from bottle import static_file from bottle import jinja2_template as template # --------------------------------------------------------------------------- # # configure the client logging # --------------------------------------------------------------------------- # import logging logging.basicConfig() log = logging.getLogger() log.setLevel(logging.DEBUG) # --------------------------------------------------------------------------- # # REST API # --------------------------------------------------------------------------- # class Response(object): """ A collection of common responses for the frontend api """ success = { 'status' : 200 } failure = { 'status' : 500 } class ModbusApiWebApp(object): """ This is the web REST api interace into the pymodbus service. It can be consumed by any utility that can make web requests (javascript). """ _namespace = '/api/v1' def __init__(self, server): """ Initialize a new instance of the ModbusApi :param server: The current server instance """ self._server = server #---------------------------------------------------------------------# # Device API #---------------------------------------------------------------------# def get_device(self): return { 'mode' : self._server.control.Mode, 'delimiter' : self._server.control.Delimiter, 'readonly' : self._server.control.ListenOnly, 'identity' : self._server.control.Identity.summary(), 'counters' : dict(self._server.control.Counter), 'diagnostic' : self._server.control.getDiagnosticRegister(), } def get_device_identity(self): return { 'identity' : dict(self._server.control.Identity) } def get_device_counters(self): return { 'counters' : dict(self._server.control.Counter) } def get_device_events(self): return { 'events' : self._server.control.Events } def get_device_plus(self): return { 'plus' : dict(self._server.control.Plus) } def delete_device_events(self): self._server.control.clearEvents() return Response.success def get_device_host(self): return { 'hosts' : list(self._server.access) } def post_device_host(self): value = request.forms.get('host') if value: self._server.access.add(value) return Response.success def delete_device_host(self): value = request.forms.get('host') if value: self._server.access.remove(value) return Response.success def post_device_delimiter(self): value = request.forms.get('delimiter') if value: self._server.control.Delimiter = value return Response.success def post_device_mode(self): value = request.forms.get('mode') if value: self._server.control.Mode = value return Response.success def post_device_reset(self): self._server.control.reset() return Response.success #---------------------------------------------------------------------# # Datastore Get API #---------------------------------------------------------------------# def __get_data(self, store, address, count, slave='00'): try: address, count = int(address), int(count) context = self._server.store[int(store)] values = context.getValues(store, address, count) values = dict(zip(range(address, address + count), values)) result = { 'data' : values } result.update(Response.success) return result except Exception as ex: log.error(ex) return Response.failure def get_coils(self, address='0', count='1'): return self.__get_data(1, address, count) def get_discretes(self, address='0', count='1'): return self.__get_data(2, address, count) def get_holdings(self, address='0', count='1'): return self.__get_data(3, address, count) def get_inputs(self, address='0', count='1'): return self.__get_data(4, address, count) #---------------------------------------------------------------------# # Datastore Update API #---------------------------------------------------------------------# def __set_data(self, store, address, values, slave='00'): try: address = int(address) values = json.loads(values) print(values) context = self._server.store[int(store)] context.setValues(store, address, values) return Response.success except Exception as ex: log.error(ex) return Response.failure def post_coils(self, address='0'): values = request.forms.get('data') return self.__set_data(1, address, values) def post_discretes(self, address='0'): values = request.forms.get('data') return self.__set_data(2, address, values) def post_holding(self, address='0'): values = request.forms.get('data') return self.__set_data(3, address, values) def post_inputs(self, address='0'): values = request.forms.get('data') return self.__set_data(4, address, values) #---------------------------------------------------------------------# # webpage routes #---------------------------------------------------------------------# def register_web_routes(application, register): """ A helper method to register the default web routes of a single page application. :param application: The application instance to register :param register: The bottle instance to register the application with """ def get_index_file(): return template('index.html') def get_static_file(filename): return static_file(filename, root='./media') register.route('/', method='GET', name='get_index_file')(get_index_file) register.route('/media/', method='GET', name='get_static_file')(get_static_file) # --------------------------------------------------------------------------- # # Configurations # --------------------------------------------------------------------------- # def register_api_routes(application, register): """ A helper method to register the routes of an application based on convention. This is easier to manage than having to decorate each method with a static route name. :param application: The application instance to register :param register: The bottle instance to register the application with """ log.info("installing application routes:") methods = inspect.getmembers(application) methods = filter(lambda n: not n[0].startswith('_'), methods) for method, func in dict(methods).iteritems(): pieces = method.split('_') verb, path = pieces[0], pieces[1:] args = inspect.getargspec(func).args[1:] args = ['<%s>' % arg for arg in args] args = '/'.join(args) args = '' if len(args) == 0 else '/' + args path.insert(0, application._namespace) path = '/'.join(path) + args log.info("%6s: %s" % (verb, path)) register.route(path, method=verb, name=method)(func) def build_application(server): """ Helper method to create and initiailze a bottle application :param server: The modbus server to pull instance data from :returns: An initialied bottle application """ log.info("building web application") api = ModbusApiWebApp(server) register = Bottle() register_api_routes(api, register) register_web_routes(api, register) return register # --------------------------------------------------------------------------- # # Start Methods # --------------------------------------------------------------------------- # def RunModbusFrontend(server, port=8080): """ Helper method to host bottle in twisted :param server: The modbus server to pull instance data from :param port: The port to host the service on """ from bottle import TwistedServer, run application = build_application(server) run(app=application, server=TwistedServer, port=port) def RunDebugModbusFrontend(server, port=8080): """ Helper method to start the bottle server :param server: The modbus server to pull instance data from :param port: The port to host the service on """ from bottle import run application = build_application(server) run(app=application, port=port) if __name__ == '__main__': # ------------------------------------------------------------ # an example server configuration # ------------------------------------------------------------ from pymodbus.server.async import ModbusServerFactory from pymodbus.constants import Defaults from pymodbus.device import ModbusDeviceIdentification from pymodbus.datastore import ModbusSequentialDataBlock from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext from twisted.internet import reactor # ------------------------------------------------------------ # initialize the identity # ------------------------------------------------------------ identity = ModbusDeviceIdentification() identity.VendorName = 'Pymodbus' identity.ProductCode = 'PM' identity.VendorUrl = 'http://github.com/bashwork/pymodbus/' identity.ProductName = 'Pymodbus Server' identity.ModelName = 'Pymodbus Server' identity.MajorMinorRevision = '1.0' # ------------------------------------------------------------ # initialize the datastore # ------------------------------------------------------------ store = ModbusSlaveContext( di = ModbusSequentialDataBlock(0, [17]*100), co = ModbusSequentialDataBlock(0, [17]*100), hr = ModbusSequentialDataBlock(0, [17]*100), ir = ModbusSequentialDataBlock(0, [17]*100)) context = ModbusServerContext(slaves=store, single=True) # ------------------------------------------------------------ # initialize the factory # ------------------------------------------------------------ address = ("", Defaults.Port) factory = ModbusServerFactory(context, None, identity) # ------------------------------------------------------------ # start the servers # ------------------------------------------------------------ log.info("Starting Modbus TCP Server on %s:%s" % address) reactor.listenTCP(address[1], factory, interface=address[0]) RunDebugModbusFrontend(factory) pymodbus-2.1.0/examples/gui/bottle/requirements.txt000066400000000000000000000003161335513467700225510ustar00rootroot00000000000000# ------------------------------------------------------------------- # if you want to use this frontend uncomment these # ------------------------------------------------------------------- bottle==0.11.2 pymodbus-2.1.0/examples/gui/bottle/views/000077500000000000000000000000001335513467700204225ustar00rootroot00000000000000pymodbus-2.1.0/examples/gui/bottle/views/index.html000066400000000000000000000104361335513467700224230ustar00rootroot00000000000000 {% block title %}pymodbus | server{% endblock %}

Device Identity

Device Counters

Device Settings

pymodbus-2.1.0/examples/gui/gtk/000077500000000000000000000000001335513467700165615ustar00rootroot00000000000000pymodbus-2.1.0/examples/gui/gtk/sims/000077500000000000000000000000001335513467700175345ustar00rootroot00000000000000pymodbus-2.1.0/examples/gui/gtk/sims/example.sim000066400000000000000000000075521335513467700217120ustar00rootroot00000000000000(dp0 S'hr' p1 ccopy_reg _reconstructor p2 (cpymodbus.datastore ModbusSparseDataBlock p3 c__builtin__ object p4 Ntp5 Rp6 (dp7 S'default_value' p8 I1 sS'values' p9 (dp10 I0 I1 sI1 I2 sI2 I3 sI3 I4 sI4 I5 sI5 I6 sI6 I7 sI7 I8 sI8 I9 sI9 I10 sI10 I11 sI11 I12 sI12 I13 sI13 I14 sI14 I15 sI15 I16 sI16 I17 sI17 I18 sI18 I19 sI19 I20 sI20 I21 sI21 I22 sI22 I23 sI23 I24 sI24 I25 sI25 I26 sI26 I27 sI27 I28 sI28 I29 sI29 I30 sI30 I31 sI31 I32 sI32 I33 sI33 I34 sI34 I35 sI35 I36 sI36 I37 sI37 I38 sI38 I39 sI39 I40 sI40 I41 sI41 I42 sI42 I43 sI43 I44 sI44 I45 sI45 I46 sI46 I47 sI47 I48 sI48 I49 sI49 I50 sI50 I51 sI51 I52 sI52 I53 sI53 I54 sI54 I55 sI55 I56 sI56 I57 sI57 I58 sI58 I59 sI59 I60 sI60 I61 sI61 I62 sI62 I63 sI63 I64 sI64 I65 sI65 I66 sI66 I67 sI67 I68 sI68 I69 sI69 I70 sI70 I71 sI71 I72 sI72 I73 sI73 I74 sI74 I75 sI75 I76 sI76 I77 sI77 I78 sI78 I79 sI79 I80 sI80 I81 sI81 I82 sI82 I83 sI83 I84 sI84 I85 sI85 I86 sI86 I87 sI87 I88 sI88 I89 sI89 I90 sI90 I91 sI91 I92 sI92 I93 sI93 I94 sI94 I95 sI95 I96 sI96 I97 sI97 I98 sI98 I99 ssS'address' p11 I0 sbsS'ci' p12 g2 (g3 g4 Ntp13 Rp14 (dp15 g8 I00 sg9 (dp16 I0 I00 sI1 I00 sI2 I00 sI3 I00 sI4 I00 sI5 I00 sI6 I00 sI7 I00 sI8 I00 sI9 I00 sI10 I00 sI11 I00 sI12 I00 sI13 I00 sI14 I00 sI15 I00 sI16 I00 sI17 I00 sI18 I00 sI19 I00 sI20 I00 sI21 I00 sI22 I00 sI23 I00 sI24 I00 sI25 I00 sI26 I00 sI27 I00 sI28 I00 sI29 I00 sI30 I00 sI31 I00 sI32 I00 sI33 I00 sI34 I00 sI35 I00 sI36 I00 sI37 I00 sI38 I00 sI39 I00 sI40 I00 sI41 I00 sI42 I00 sI43 I00 sI44 I00 sI45 I00 sI46 I00 sI47 I00 sI48 I00 sI49 I00 sI50 I00 sI51 I00 sI52 I00 sI53 I00 sI54 I00 sI55 I00 sI56 I00 sI57 I00 sI58 I00 sI59 I00 sI60 I00 sI61 I00 sI62 I00 sI63 I00 sI64 I00 sI65 I00 sI66 I00 sI67 I00 sI68 I00 sI69 I00 sI70 I00 sI71 I00 sI72 I00 sI73 I00 sI74 I00 sI75 I00 sI76 I00 sI77 I00 sI78 I00 sI79 I00 sI80 I00 sI81 I00 sI82 I00 sI83 I00 sI84 I00 sI85 I00 sI86 I00 sI87 I00 sI88 I00 sI89 I00 sI90 I00 sI91 I00 sI92 I00 sI93 I00 sI94 I00 sI95 I00 sI96 I00 sI97 I00 sI98 I00 ssg11 I0 sbsS'ir' p17 g2 (g3 g4 Ntp18 Rp19 (dp20 g8 I2 sg9 (dp21 I0 I2 sI1 I4 sI2 I6 sI3 I8 sI4 I10 sI5 I12 sI6 I14 sI7 I16 sI8 I18 sI9 I20 sI10 I22 sI11 I24 sI12 I26 sI13 I28 sI14 I30 sI15 I32 sI16 I34 sI17 I36 sI18 I38 sI19 I40 sI20 I42 sI21 I44 sI22 I46 sI23 I48 sI24 I50 sI25 I52 sI26 I54 sI27 I56 sI28 I58 sI29 I60 sI30 I62 sI31 I64 sI32 I66 sI33 I68 sI34 I70 sI35 I72 sI36 I74 sI37 I76 sI38 I78 sI39 I80 sI40 I82 sI41 I84 sI42 I86 sI43 I88 sI44 I90 sI45 I92 sI46 I94 sI47 I96 sI48 I98 sI49 I100 sI50 I102 sI51 I104 sI52 I106 sI53 I108 sI54 I110 sI55 I112 sI56 I114 sI57 I116 sI58 I118 sI59 I120 sI60 I122 sI61 I124 sI62 I126 sI63 I128 sI64 I130 sI65 I132 sI66 I134 sI67 I136 sI68 I138 sI69 I140 sI70 I142 sI71 I144 sI72 I146 sI73 I148 sI74 I150 sI75 I152 sI76 I154 sI77 I156 sI78 I158 sI79 I160 sI80 I162 sI81 I164 sI82 I166 sI83 I168 sI84 I170 sI85 I172 sI86 I174 sI87 I176 sI88 I178 sI89 I180 sI90 I182 sI91 I184 sI92 I186 sI93 I188 sI94 I190 sI95 I192 sI96 I194 sI97 I196 sI98 I198 ssg11 I0 sbsS'di' p22 g2 (g3 g4 Ntp23 Rp24 (dp25 g8 I01 sg9 (dp26 I0 I01 sI1 I01 sI2 I01 sI3 I01 sI4 I01 sI5 I01 sI6 I01 sI7 I01 sI8 I01 sI9 I01 sI10 I01 sI11 I01 sI12 I01 sI13 I01 sI14 I01 sI15 I01 sI16 I01 sI17 I01 sI18 I01 sI19 I01 sI20 I01 sI21 I01 sI22 I01 sI23 I01 sI24 I01 sI25 I01 sI26 I01 sI27 I01 sI28 I01 sI29 I01 sI30 I01 sI31 I01 sI32 I01 sI33 I01 sI34 I01 sI35 I01 sI36 I01 sI37 I01 sI38 I01 sI39 I01 sI40 I01 sI41 I01 sI42 I01 sI43 I01 sI44 I01 sI45 I01 sI46 I01 sI47 I01 sI48 I01 sI49 I01 sI50 I01 sI51 I01 sI52 I01 sI53 I01 sI54 I01 sI55 I01 sI56 I01 sI57 I01 sI58 I01 sI59 I01 sI60 I01 sI61 I01 sI62 I01 sI63 I01 sI64 I01 sI65 I01 sI66 I01 sI67 I01 sI68 I01 sI69 I01 sI70 I01 sI71 I01 sI72 I01 sI73 I01 sI74 I01 sI75 I01 sI76 I01 sI77 I01 sI78 I01 sI79 I01 sI80 I01 sI81 I01 sI82 I01 sI83 I01 sI84 I01 sI85 I01 sI86 I01 sI87 I01 sI88 I01 sI89 I01 sI90 I01 sI91 I01 sI92 I01 sI93 I01 sI94 I01 sI95 I01 sI96 I01 sI97 I01 sI98 I01 ssg11 I0 sbs.pymodbus-2.1.0/examples/gui/gtk/simulator.glade000066400000000000000000000211661335513467700216040ustar00rootroot00000000000000 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Modbus Simulator False GTK_WIN_POS_CENTER 400 200 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Device to Simulate True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 220 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK False False 20 1 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Starting Address 230 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK False 20 1 1 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Number of Devices 230 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 0 2000 1 10 0 False 20 1 2 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_BUTTONBOX_SPREAD True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-help True 0 True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-apply True 0 1 True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-stop True 0 2 3 pymodbus-2.1.0/examples/gui/gtk/simulator.py000077500000000000000000000305611335513467700211620ustar00rootroot00000000000000#!/usr/bin/env python # --------------------------------------------------------------------------- # # System # --------------------------------------------------------------------------- # import os import getpass import pickle from threading import Thread # --------------------------------------------------------------------------- # # For Gui # --------------------------------------------------------------------------- # from twisted.internet import gtk2reactor gtk2reactor.install() import gtk from gtk import glade # --------------------------------------------------------------------------- # # SNMP Simulator # --------------------------------------------------------------------------- # from twisted.internet import reactor from twisted.internet import error as twisted_error from pymodbus.server.async import ModbusServerFactory from pymodbus.datastore import ModbusServerContext,ModbusSlaveContext #--------------------------------------------------------------------------# # Logging #--------------------------------------------------------------------------# import logging log = logging.getLogger(__name__) # --------------------------------------------------------------------------- # # Application Error # --------------------------------------------------------------------------- # class ConfigurationException(Exception): """ Exception for configuration error """ def __init__(self, string): Exception.__init__(self, string) self.string = string def __str__(self): return 'Configuration Error: %s' % self.string # --------------------------------------------------------------------------- # # Extra Global Functions # --------------------------------------------------------------------------- # # These are extra helper functions that don't belong in a class # --------------------------------------------------------------------------- # def root_test(): """ Simple test to see if we are running as root """ return getpass.getuser() == "root" # --------------------------------------------------------------------------- # # Simulator Class # --------------------------------------------------------------------------- # class Simulator(object): """ Class used to parse configuration file and create and modbus datastore. The format of the configuration file is actually just a python pickle, which is a compressed memory dump from the scraper. """ def __init__(self, config): """ Trys to load a configuration file, lets the file not found exception fall through @param config The pickled datastore """ try: self.file = open(config, "r") except Exception: raise ConfigurationException("File not found %s" % config) def _parse(self): """ Parses the config file and creates a server context """ try: handle = pickle.load(self.file) dsd = handle['di'] csd = handle['ci'] hsd = handle['hr'] isd = handle['ir'] except KeyError: raise ConfigurationException("Invalid Configuration") slave = ModbusSlaveContext(d=dsd, c=csd, h=hsd, i=isd) return ModbusServerContext(slaves=slave) def _simulator(self): """ Starts the snmp simulator """ ports = [502]+range(20000,25000) for port in ports: try: reactor.listenTCP(port, ModbusServerFactory(self._parse())) print 'listening on port', port return port except twisted_error.CannotListenError: pass def run(self): """ Used to run the simulator """ reactor.callWhenRunning(self._simulator) # --------------------------------------------------------------------------- # # Network reset thread # --------------------------------------------------------------------------- # # This is linux only, maybe I should make a base class that can be filled # in for linux(debian/redhat)/windows/nix # --------------------------------------------------------------------------- # class NetworkReset(Thread): """ This class is simply a daemon that is spun off at the end of the program to call the network restart function (an easy way to remove all the virtual interfaces) """ def __init__(self): Thread.__init__(self) self.setDaemon(True) def run(self): """ Run the network reset """ os.system("/etc/init.d/networking restart") # --------------------------------------------------------------------------- # # Main Gui Class # --------------------------------------------------------------------------- # # Note, if you are using gtk2 before 2.12, the file_set signal is not # introduced. To fix this, you need to apply the following patch # --------------------------------------------------------------------------- # #Index: simulator.py #=================================================================== #--- simulator.py (revision 60) #+++ simulator.py (working copy) #@@ -158,7 +161,7 @@ # "on_helpBtn_clicked" : self.help_clicked, # "on_quitBtn_clicked" : self.close_clicked, # "on_startBtn_clicked" : self.start_clicked, #- "on_file_changed" : self.file_changed, #+ #"on_file_changed" : self.file_changed, # "on_window_destroy" : self.close_clicked # } # self.tree.signal_autoconnect(actions) #@@ -235,6 +238,7 @@ # return False # # # check input file #+ self.file_changed(self.tdevice) # if os.path.exists(self.file): # self.grey_out() # handle = Simulator(config=self.file) # --------------------------------------------------------------------------- # class SimulatorApp(object): """ This class implements the GUI for the flasher application """ file = "none" subnet = 205 number = 1 restart = 0 def __init__(self, xml): """ Sets up the gui, callback, and widget handles """ # --------------------------------------------------------------------------- # # Action Handles # --------------------------------------------------------------------------- # self.tree = glade.XML(xml) self.bstart = self.tree.get_widget("startBtn") self.bhelp = self.tree.get_widget("helpBtn") self.bclose = self.tree.get_widget("quitBtn") self.window = self.tree.get_widget("window") self.tdevice = self.tree.get_widget("fileTxt") self.tsubnet = self.tree.get_widget("addressTxt") self.tnumber = self.tree.get_widget("deviceTxt") # --------------------------------------------------------------------------- # # Actions # --------------------------------------------------------------------------- # actions = { "on_helpBtn_clicked" : self.help_clicked, "on_quitBtn_clicked" : self.close_clicked, "on_startBtn_clicked" : self.start_clicked, "on_file_changed" : self.file_changed, "on_window_destroy" : self.close_clicked } self.tree.signal_autoconnect(actions) if not root_test(): self.error_dialog("This program must be run with root permissions!", True) # --------------------------------------------------------------------------- # # Gui helpers # --------------------------------------------------------------------------- # # Not callbacks, but used by them # --------------------------------------------------------------------------- # def show_buttons(self, state=False, all=0): """ Greys out the buttons """ if all: self.window.set_sensitive(state) self.bstart.set_sensitive(state) self.tdevice.set_sensitive(state) self.tsubnet.set_sensitive(state) self.tnumber.set_sensitive(state) def destroy_interfaces(self): """ This is used to reset the virtual interfaces """ if self.restart: n = NetworkReset() n.start() def error_dialog(self, message, quit=False): """ Quick pop-up for error messages """ dialog = gtk.MessageDialog( parent = self.window, flags = gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, type = gtk.MESSAGE_ERROR, buttons = gtk.BUTTONS_CLOSE, message_format = message) dialog.set_title('Error') if quit: dialog.connect("response", lambda w, r: gtk.main_quit()) else: dialog.connect("response", lambda w, r: w.destroy()) dialog.show() # --------------------------------------------------------------------------- # # Button Actions # --------------------------------------------------------------------------- # # These are all callbacks for the various buttons # --------------------------------------------------------------------------- # def start_clicked(self, widget): """ Starts the simulator """ start = 1 base = "172.16" # check starting network net = self.tsubnet.get_text() octets = net.split('.') if len(octets) == 4: base = "%s.%s" % (octets[0], octets[1]) net = int(octets[2]) % 255 start = int(octets[3]) % 255 else: self.error_dialog("Invalid starting address!"); return False # check interface size size = int(self.tnumber.get_text()) if (size >= 1): for i in range(start, (size + start)): j = i % 255 cmd = "/sbin/ifconfig eth0:%d %s.%d.%d" % (i, base, net, j) os.system(cmd) if j == 254: net = net + 1 self.restart = 1 else: self.error_dialog("Invalid number of devices!"); return False # check input file if os.path.exists(self.file): self.show_buttons(state=False) try: handle = Simulator(config=self.file) handle.run() except ConfigurationException, ex: self.error_dialog("Error %s" % ex) self.show_buttons(state=True) else: self.error_dialog("Device to emulate does not exist!"); return False def help_clicked(self, widget): """ Quick pop-up for about page """ data = gtk.AboutDialog() data.set_version("0.1") data.set_name(('Modbus Simulator')) data.set_authors(["Galen Collins"]) data.set_comments(('First Select a device to simulate,\n' + 'then select the starting subnet of the new devices\n' + 'then select the number of device to simulate and click start')) data.set_website("http://code.google.com/p/pymodbus/") data.connect("response", lambda w,r: w.hide()) data.run() def close_clicked(self, widget): """ Callback for close button """ self.destroy_interfaces() reactor.stop() # quit twisted def file_changed(self, widget): """ Callback for the filename change """ self.file = widget.get_filename() # --------------------------------------------------------------------------- # # Main handle function # --------------------------------------------------------------------------- # # This is called when the application is run from a console # We simply start the gui and start the twisted event loop # --------------------------------------------------------------------------- # def main(): """ Main control function This either launches the gui or runs the command line application """ debug = True if debug: try: log.setLevel(logging.DEBUG) logging.basicConfig() except Exception, e: print "Logging is not supported on this system" simulator = SimulatorApp('./simulator.glade') reactor.run() # --------------------------------------------------------------------------- # # Library/Console Test # --------------------------------------------------------------------------- # # If this is called from console, we start main # --------------------------------------------------------------------------- # if __name__ == "__main__": main() pymodbus-2.1.0/examples/gui/gui_common.py000077500000000000000000000102071335513467700205050ustar00rootroot00000000000000#!/usr/bin/env python # -------------------------------------------------------------------------- # # System # -------------------------------------------------------------------------- # import os import getpass import pickle from threading import Thread # -------------------------------------------------------------------------- # # SNMP Simulator # -------------------------------------------------------------------------- # from twisted.internet import reactor from twisted.internet import error as twisted_error from pymodbus.server.async import ModbusServerFactory from pymodbus.datastore import ModbusServerContext,ModbusSlaveContext # -------------------------------------------------------------------------- # # Logging # -------------------------------------------------------------------------- # import logging log = logging.getLogger("pymodbus") # -------------------------------------------------------------------------- # # Application Error # -------------------------------------------------------------------------- # class ConfigurationException(Exception): """ Exception for configuration error """ pass # -------------------------------------------------------------------------- # # Extra Global Functions # -------------------------------------------------------------------------- # # These are extra helper functions that don't belong in a class # -------------------------------------------------------------------------- # def root_test(): """ Simple test to see if we are running as root """ return getpass.getuser() == "root" # -------------------------------------------------------------------------- # # Simulator Class # -------------------------------------------------------------------------- # class Simulator(object): """ Class used to parse configuration file and create and modbus datastore. The format of the configuration file is actually just a python pickle, which is a compressed memory dump from the scraper. """ def __init__(self, config): """ Trys to load a configuration file, lets the file not found exception fall through :param config: The pickled datastore """ try: self.file = open(config, "r") except Exception: raise ConfigurationException("File not found %s" % config) def _parse(self): """ Parses the config file and creates a server context """ try: handle = pickle.load(self.file) dsd = handle['di'] csd = handle['ci'] hsd = handle['hr'] isd = handle['ir'] except KeyError: raise ConfigurationException("Invalid Configuration") slave = ModbusSlaveContext(d=dsd, c=csd, h=hsd, i=isd) return ModbusServerContext(slaves=slave) def _simulator(self): """ Starts the snmp simulator """ ports = [502]+range(20000,25000) for port in ports: try: reactor.listenTCP(port, ModbusServerFactory(self._parse())) log.debug('listening on port %d' % port) return port except twisted_error.CannotListenError: pass def run(self): """ Used to run the simulator """ log.debug('simulator started') reactor.callWhenRunning(self._simulator) # -------------------------------------------------------------------------- # # Network reset thread # -------------------------------------------------------------------------- # # This is linux only, maybe I should make a base class that can be filled # in for linux(debian/redhat)/windows/nix # -------------------------------------------------------------------------- # class NetworkReset(Thread): """ This class is simply a daemon that is spun off at the end of the program to call the network restart function (an easy way to remove all the virtual interfaces) """ def __init__(self): """ Initialize a new network reset thread """ Thread.__init__(self) self.setDaemon(True) def run(self): """ Run the network reset """ os.system("/etc/init.d/networking restart") pymodbus-2.1.0/examples/gui/tk/000077500000000000000000000000001335513467700164125ustar00rootroot00000000000000pymodbus-2.1.0/examples/gui/tk/fileopen.gif000066400000000000000000000021601335513467700207010ustar00rootroot00000000000000GIF89aç¢>?>LMJLNJMOLNOLOQMQRNRTPSURTVRUWSWYV[]X[]Y^`Zegegh[gi`lnhprdqsdtvguwhxyrxzkxzsy{lz|l{|n|~o~€p€‚rƒs†‡w‹ŒzŒ{ŒŽ|Ž}„Ž‚ƒ‘€’„’“’“‚’“…’“†“”ƒ“•ƒ”•‡“•‹”–„•–„”–ˆ•—‰—˜Ž—™†˜™‡˜š‹š›‰š›Ššœ‡›œœœž‹œžŽžŸ‹ŸŸŽžŸŸ ž •Ÿ¡ ¡Ž  ™ ¢Ž¡£¡£’¢¤£¥“¤¦‘¤¦”¥¦”¥§”¦§“¥¨”§¨–¨ª”©ª—©«•©«–ª«™ª¬š«¬˜ª¬ž«®›¬® ­®¡­¯š­¯œ­¯¡®¯¤®°¯±œ¯±°±ž¯±¡°±¡°²Ÿ±²Ÿ±³±³ž±³¢²´ ²´¡³´¡´µ¡´µ¤´¶£¶·¢¶·¥·¹¤¸º§¸º¨¹»¦¹»§¹»©»¼¨»½©»¾«¼¾©½¿«¾¾¼½À­¾Á¬À¯Á®À²ÁîÁóÂÄ®ÂıÂÄ´Ãŵį´ÅDzÅǶÈÉ·ÈÉ»Ê̺ËÍ»ÍκÎϽÎнÏѾÑÓÀÓÔÂÔÔÂÓÔÆîîì÷÷öÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!þCreated with GIMP!ù ÿ,þÿ H° Á *Tpð ‚2"F,ð!AzhÔ ÂÂÿa$A²d™“(Qš‰ˆ ?F ˆ’K¢rêÜ)JA–>|À ª¨Q£=¿$@øÈCœ…¢J•št©ÀŠ8l zôhÏ0VE2Ò€êÔ©_Y>²Pk×¢i™R˜  T̘"$Ø‹àÁ’O˜"9BdHŸ= 1): for i in range(start, (size + start)): j = i % 255 cmd = "/sbin/ifconfig eth0:%d %s.%d.%d" % (i, base, net, j) os.system(cmd) if j == 254: net = net + 1 self.restart = 1 else: self.error_dialog("Invalid number of devices!"); return False # check input file filename = self.tdevice_value.get() if os.path.exists(filename): self.show_buttons(state=False) try: handle = Simulator(config=filename) handle.run() except ConfigurationException as ex: self.error_dialog("Error %s" % ex) self.show_buttons(state=True) else: self.error_dialog("Device to emulate does not exist!"); return False def help_clicked(self): """ Quick pop-up for about page """ data = gtk.AboutDialog() data.set_version("0.1") data.set_name(('Modbus Simulator')) data.set_authors(["Galen Collins"]) data.set_comments(('First Select a device to simulate,\n' + 'then select the starting subnet of the new devices\n' + 'then select the number of device to simulate and click start')) data.set_website("http://code.google.com/p/pymodbus/") data.connect("response", lambda w,r: w.hide()) data.run() def close_clicked(self): """ Callback for close button """ #self.destroy_interfaces() reactor.stop() def file_clicked(self): """ Callback for the filename change """ file = OpenFilename() self.tdevice_value.set(file) class SimulatorApp(object): """ The main wx application handle for our simulator """ def __init__(self, master): """ Called by wxWindows to initialize our application :param master: The master window to connect to """ font = ('Helvetica', 12, 'normal') frame = SimulatorFrame(master, font) frame.pack() # --------------------------------------------------------------------------- # # Main handle function # --------------------------------------------------------------------------- # # This is called when the application is run from a console # We simply start the gui and start the twisted event loop # --------------------------------------------------------------------------- # def main(): """ Main control function This either launches the gui or runs the command line application """ debug = True if debug: try: log.setLevel(logging.DEBUG) logging.basicConfig() except Exception as e: print("Logging is not supported on this system") simulator = SimulatorApp(root) root.title("Modbus Simulator") reactor.run() # --------------------------------------------------------------------------- # # Library/Console Test # --------------------------------------------------------------------------- # # If this is called from console, we start main # --------------------------------------------------------------------------- # if __name__ == "__main__": main() pymodbus-2.1.0/examples/gui/wx/000077500000000000000000000000001335513467700164325ustar00rootroot00000000000000pymodbus-2.1.0/examples/gui/wx/simulator.py000077500000000000000000000265351335513467700210410ustar00rootroot00000000000000#!/usr/bin/env python """ Note that this is not finished """ # --------------------------------------------------------------------------- # # System # --------------------------------------------------------------------------- # import os import getpass import pickle from threading import Thread # --------------------------------------------------------------------------- # # For Gui # --------------------------------------------------------------------------- # import wx from twisted.internet import wxreactor wxreactor.install() # --------------------------------------------------------------------------- # # SNMP Simulator # --------------------------------------------------------------------------- # from twisted.internet import reactor from twisted.internet import error as twisted_error from pymodbus.server.async import ModbusServerFactory from pymodbus.datastore import ModbusServerContext,ModbusSlaveContext #--------------------------------------------------------------------------# # Logging #--------------------------------------------------------------------------# import logging log = logging.getLogger(__name__) # --------------------------------------------------------------------------- # # Application Error # --------------------------------------------------------------------------- # class ConfigurationException(Exception): """ Exception for configuration error """ pass # --------------------------------------------------------------------------- # # Extra Global Functions # --------------------------------------------------------------------------- # # These are extra helper functions that don't belong in a class # --------------------------------------------------------------------------- # def root_test(): """ Simple test to see if we are running as root """ return getpass.getuser() == "root" # --------------------------------------------------------------------------- # # Simulator Class # --------------------------------------------------------------------------- # class Simulator(object): """ Class used to parse configuration file and create and modbus datastore. The format of the configuration file is actually just a python pickle, which is a compressed memory dump from the scraper. """ def __init__(self, config): """ Trys to load a configuration file, lets the file not found exception fall through @param config The pickled datastore """ try: self.file = open(config, "r") except Exception: raise ConfigurationException("File not found %s" % config) def _parse(self): """ Parses the config file and creates a server context """ try: handle = pickle.load(self.file) dsd = handle['di'] csd = handle['ci'] hsd = handle['hr'] isd = handle['ir'] except KeyError: raise ConfigurationException("Invalid Configuration") slave = ModbusSlaveContext(d=dsd, c=csd, h=hsd, i=isd) return ModbusServerContext(slaves=slave) def _simulator(self): """ Starts the snmp simulator """ ports = [502]+range(20000,25000) for port in ports: try: reactor.listenTCP(port, ModbusServerFactory(self._parse())) print 'listening on port', port return port except twisted_error.CannotListenError: pass def run(self): """ Used to run the simulator """ reactor.callWhenRunning(self._simulator) # --------------------------------------------------------------------------- # # Network reset thread # --------------------------------------------------------------------------- # # This is linux only, maybe I should make a base class that can be filled # in for linux(debian/redhat)/windows/nix # --------------------------------------------------------------------------- # class NetworkReset(Thread): """ This class is simply a daemon that is spun off at the end of the program to call the network restart function (an easy way to remove all the virtual interfaces) """ def __init__(self): """ Initializes a new instance of the network reset thread """ Thread.__init__(self) self.setDaemon(True) def run(self): """ Run the network reset """ os.system("/etc/init.d/networking restart") # --------------------------------------------------------------------------- # # Main Gui Class # --------------------------------------------------------------------------- # class SimulatorFrame(wx.Frame): """ This class implements the GUI for the flasher application """ subnet = 205 number = 1 restart = 0 def __init__(self, parent, id, title): """ Sets up the gui, callback, and widget handles """ wx.Frame.__init__(self, parent, id, title) wx.EVT_CLOSE(self, self.close_clicked) # --------------------------------------------------------------------------- # # Add button row # --------------------------------------------------------------------------- # panel = wx.Panel(self, -1) box = wx.BoxSizer(wx.HORIZONTAL) box.Add(wx.Button(panel, 1, 'Apply'), 1) box.Add(wx.Button(panel, 2, 'Help'), 1) box.Add(wx.Button(panel, 3, 'Close'), 1) panel.SetSizer(box) # --------------------------------------------------------------------------- # # Add input boxes # --------------------------------------------------------------------------- # #self.tdevice = self.tree.get_widget("fileTxt") #self.tsubnet = self.tree.get_widget("addressTxt") #self.tnumber = self.tree.get_widget("deviceTxt") # --------------------------------------------------------------------------- # # Tie callbacks # --------------------------------------------------------------------------- # self.Bind(wx.EVT_BUTTON, self.start_clicked, id=1) self.Bind(wx.EVT_BUTTON, self.help_clicked, id=2) self.Bind(wx.EVT_BUTTON, self.close_clicked, id=3) #if not root_test(): # self.error_dialog("This program must be run with root permissions!", True) # --------------------------------------------------------------------------- # # Gui helpers # --------------------------------------------------------------------------- # # Not callbacks, but used by them # --------------------------------------------------------------------------- # def show_buttons(self, state=False, all=0): """ Greys out the buttons """ if all: self.window.set_sensitive(state) self.bstart.set_sensitive(state) self.tdevice.set_sensitive(state) self.tsubnet.set_sensitive(state) self.tnumber.set_sensitive(state) def destroy_interfaces(self): """ This is used to reset the virtual interfaces """ if self.restart: n = NetworkReset() n.start() def error_dialog(self, message, quit=False): """ Quick pop-up for error messages """ log.debug("error event called") dialog = wx.MessageDialog(self, message, 'Error', wx.OK | wx.ICON_ERROR) dialog.ShowModel() if quit: self.Destroy() dialog.Destroy() # --------------------------------------------------------------------------- # # Button Actions # --------------------------------------------------------------------------- # # These are all callbacks for the various buttons # --------------------------------------------------------------------------- # def start_clicked(self, widget): """ Starts the simulator """ start = 1 base = "172.16" # check starting network net = self.tsubnet.get_text() octets = net.split('.') if len(octets) == 4: base = "%s.%s" % (octets[0], octets[1]) net = int(octets[2]) % 255 start = int(octets[3]) % 255 else: self.error_dialog("Invalid starting address!"); return False # check interface size size = int(self.tnumber.get_text()) if (size >= 1): for i in range(start, (size + start)): j = i % 255 cmd = "/sbin/ifconfig eth0:%d %s.%d.%d" % (i, base, net, j) os.system(cmd) if j == 254: net = net + 1 self.restart = 1 else: self.error_dialog("Invalid number of devices!"); return False # check input file if os.path.exists(self.file): self.show_buttons(state=False) try: handle = Simulator(config=self.file) handle.run() except ConfigurationException, ex: self.error_dialog("Error %s" % ex) self.show_buttons(state=True) else: self.error_dialog("Device to emulate does not exist!"); return False def help_clicked(self, widget): """ Quick pop-up for about page """ data = gtk.AboutDialog() data.set_version("0.1") data.set_name(('Modbus Simulator')) data.set_authors(["Galen Collins"]) data.set_comments(('First Select a device to simulate,\n' + 'then select the starting subnet of the new devices\n' + 'then select the number of device to simulate and click start')) data.set_website("http://code.google.com/p/pymodbus/") data.connect("response", lambda w,r: w.hide()) data.run() def close_clicked(self, event): """ Callback for close button """ log.debug("close event called") reactor.stop() def file_changed(self, event): """ Callback for the filename change """ self.file = widget.get_filename() class SimulatorApp(wx.App): """ The main wx application handle for our simulator """ def OnInit(self): """ Called by wxWindows to initialize our application :returns: Always True """ log.debug("application initialize event called") reactor.registerWxApp(self) frame = SimulatorFrame(None, -1, "Pymodbus Simulator") frame.CenterOnScreen() frame.Show(True) self.SetTopWindow(frame) return True # --------------------------------------------------------------------------- # # Main handle function # --------------------------------------------------------------------------- # # This is called when the application is run from a console # We simply start the gui and start the twisted event loop # --------------------------------------------------------------------------- # def main(): """ Main control function This either launches the gui or runs the command line application """ debug = True if debug: try: log.setLevel(logging.DEBUG) logging.basicConfig() except Exception, e: print "Logging is not supported on this system" simulator = SimulatorApp(0) reactor.run() # --------------------------------------------------------------------------- # # Library/Console Test # --------------------------------------------------------------------------- # # If this is called from console, we start main # --------------------------------------------------------------------------- # if __name__ == "__main__": main() pymodbus-2.1.0/examples/twisted/000077500000000000000000000000001335513467700166735ustar00rootroot00000000000000pymodbus-2.1.0/examples/twisted/modbus_tcp.tac000066400000000000000000000017571335513467700215350ustar00rootroot00000000000000""" This service can be run with the following:: twistd -ny modbus_tcp.tac """ from twisted.application import service, internet from twisted.python.log import ILogObserver, FileLogObserver from twisted.python.logfile import DailyLogFile from pymodbus.constants import Defaults from pymodbus.server.async import ModbusServerFactory from pymodbus.transaction import ModbusSocketFramer from pymodbus.internal.ptwisted import InstallManagementConsole def BuildService(): """ A helper method to build the service """ context = None framer = ModbusSocketFramer factory = ModbusServerFactory(context, framer) InstallManagementConsole({ 'server' : factory }) application = internet.TCPServer(Defaults.Port, factory) return application application = service.Application("Modbus TCP Server") logfile = DailyLogFile("pymodbus.log", "/tmp") application.setComponent(ILogObserver, FileLogObserver(logfile).emit) service = BuildService() service.setServiceParent(application) pymodbus-2.1.0/examples/twisted/modbus_udp.tac000066400000000000000000000017501335513467700215300ustar00rootroot00000000000000""" This service can be run with the following:: twistd -ny modbus_udp.tac """ from twisted.application import service, internet from twisted.python.log import ILogObserver, FileLogObserver from twisted.python.logfile import DailyLogFile from pymodbus.constants import Defaults from pymodbus.server.async import ModbusUdpProtocol from pymodbus.transaction import ModbusSocketFramer from pymodbus.internal.ptwisted import InstallManagementConsole def BuildService(): """ A helper method to build the service """ context = None framer = ModbusSocketFramer server = ModbusUdpProtocol(context, framer) InstallManagementConsole({ 'server' : server }) application = internet.UDPServer(Defaults.Port, server) return application application = service.Application("Modbus UDP Server") logfile = DailyLogFile("pymodbus.log", "/tmp") application.setComponent(ILogObserver, FileLogObserver(logfile).emit) service = BuildService() service.setServiceParent(application) pymodbus-2.1.0/examples/twisted/plugins/000077500000000000000000000000001335513467700203545ustar00rootroot00000000000000pymodbus-2.1.0/examples/twisted/plugins/pymodbus_plugin.py000066400000000000000000000036411335513467700241520ustar00rootroot00000000000000""" """ from zope.interface import implements from twisted.python import usage from twisted.plugin import IPlugin from twisted.application.service import IServiceMaker from twisted.application import internet from pymodbus.constants import Defaults from pymodbus.server.async import ModbusServerFactory from pymodbus.transaction import ModbusSocketFramer from pymodbus.internal.ptwisted import InstallManagementConsole class Options(usage.Options): """ The following are the options available to the pymodbus server. """ optParameters = [ ["port", "p", Defaults.Port, "The port number to listen on."], ["type", "t", "tcp", "The type of server to host (tcp, udp, ascii, rtu)"], ["store", "s", "./datastore", "The pickled datastore to use"], ["console", "c", False, "Should the management console be started"], ] class ModbusServiceMaker(object): """ A helper class used to build a twisted plugin """ implements(IServiceMaker, IPlugin) tapname = "pymodbus" description = "A modbus server" options = Options def makeService(self, options): """ Construct a service from the given options """ if options["type"] == "tcp": server = internet.TCPServer else: server = internet.UDPServer framer = ModbusSocketFramer context = self._build_context(options['store']) factory = ModbusServerFactory(None, framer) if options['console']: InstallManagementConsole({ 'server' : factory }) return server(int(options["port"]), factory) def _build_context(self, path): """ A helper method to unpickle a datastore, note, this should be a ModbusServerContext. """ import pickle try: context = pickle.load(path) except Exception: context = None return context serviceMaker = ModbusServiceMaker() pymodbus-2.1.0/ez_setup.py000077500000000000000000000242301335513467700156060ustar00rootroot00000000000000#!python from __future__ import print_function """Bootstrap setuptools installation If you want to use setuptools in your package's setup.py, just include this file in the same directory with it, and add this to the top of your setup.py:: from ez_setup import use_setuptools use_setuptools() If you want to require a specific version of setuptools, set a download mirror, or use an alternate download directory, you can do so by supplying the appropriate options to ``use_setuptools()``. This file can also be run as a script to install or upgrade setuptools. """ import sys DEFAULT_VERSION = "0.6c11" DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3] IS_PYTHON3 = sys.version_info[0] == 3 md5_data = { 'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca', 'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb', 'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b', 'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a', 'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618', 'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac', 'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5', 'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4', 'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c', 'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b', 'setuptools-0.6c10-py2.3.egg': 'ce1e2ab5d3a0256456d9fc13800a7090', 'setuptools-0.6c10-py2.4.egg': '57d6d9d6e9b80772c59a53a8433a5dd4', 'setuptools-0.6c10-py2.5.egg': 'de46ac8b1c97c895572e5e8596aeb8c7', 'setuptools-0.6c10-py2.6.egg': '58ea40aef06da02ce641495523a0b7f5', 'setuptools-0.6c11-py2.3.egg': '2baeac6e13d414a9d28e7ba5b5a596de', 'setuptools-0.6c11-py2.4.egg': 'bd639f9b0eac4c42497034dec2ec0c2b', 'setuptools-0.6c11-py2.5.egg': '64c94f3bf7a72a13ec83e0b24f2749b2', 'setuptools-0.6c11-py2.6.egg': 'bfa92100bd772d5a213eedd356d64086', 'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27', 'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277', 'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa', 'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e', 'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e', 'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f', 'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2', 'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc', 'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167', 'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64', 'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d', 'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20', 'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab', 'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53', 'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2', 'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e', 'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372', 'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902', 'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de', 'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b', 'setuptools-0.6c9-py2.3.egg': 'a83c4020414807b496e4cfbe08507c03', 'setuptools-0.6c9-py2.4.egg': '260a2be2e5388d66bdaee06abec6342a', 'setuptools-0.6c9-py2.5.egg': 'fe67c3e5a17b12c0e7c541b7ea43a8e6', 'setuptools-0.6c9-py2.6.egg': 'ca37b1ff16fa2ede6e19383e7b59245a', } import sys, os try: from hashlib import md5 except ImportError: from md5 import md5 def print_error(msg, **kwargs): print(msg, file=sys.stderr, **kwargs) def _validate_md5(egg_name, data): if egg_name in md5_data: digest = md5(data).hexdigest() if digest != md5_data[egg_name]: print_error("md5 validation of %s failed! (Possible download problem?)" % egg_name) sys.exit(2) return data def use_setuptools( version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, download_delay=15 ): """Automatically find/download setuptools and make it available on sys.path `version` should be a valid setuptools version number that is available as an egg for download under the `download_base` URL (which should end with a '/'). `to_dir` is the directory where setuptools will be downloaded, if it is not already available. If `download_delay` is specified, it should be the number of seconds that will be paused before initiating a download, should one be required. If an older version of setuptools is installed, this routine will print a message to ``sys.stderr`` and raise SystemExit in an attempt to abort the calling script. """ was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules def do_download(): egg = download_setuptools(version, download_base, to_dir, download_delay) sys.path.insert(0, egg) import setuptools; setuptools.bootstrap_install_from = egg try: import pkg_resources except ImportError: return do_download() try: pkg_resources.require("setuptools>="+version); return except pkg_resources.VersionConflict as e: if was_imported: print_error(( "The required version of setuptools (>=%s) is not available, and\n" "can't be installed while this script is running. Please install\n" " a more recent version first, using 'easy_install -U setuptools'." "\n\n(Currently using %r)" ) % (version, e.args[0])) sys.exit(2) except pkg_resources.DistributionNotFound: pass del pkg_resources, sys.modules['pkg_resources'] # reload ok return do_download() def download_setuptools( version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, delay = 15 ): """Download setuptools from a specified location and return its filename `version` should be a valid setuptools version number that is available as an egg for download under the `download_base` URL (which should end with a '/'). `to_dir` is the directory where the egg will be downloaded. `delay` is the number of seconds to pause before an actual download attempt. """ import urllib.request, urllib.error, urllib.parse, shutil egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3]) url = download_base + egg_name saveto = os.path.join(to_dir, egg_name) src = dst = None if not os.path.exists(saveto): # Avoid repeated downloads try: from distutils import log if delay: log.warn(""" --------------------------------------------------------------------------- This script requires setuptools version %s to run (even to display help). I will attempt to download it for you (from %s), but you may need to enable firewall access for this script first. I will start the download in %d seconds. (Note: if this machine does not have network access, please obtain the file %s and place it in this directory before rerunning this script.) ---------------------------------------------------------------------------""", version, download_base, delay, url ); from time import sleep; sleep(delay) log.warn("Downloading %s", url) src = urllib.request.urlopen(url) # Read/write all in one block, so we don't create a corrupt file # if the download is interrupted. data = _validate_md5(egg_name, src.read()) dst = open(saveto,"wb"); dst.write(data) finally: if src: src.close() if dst: dst.close() return os.path.realpath(saveto) def main(argv, version=DEFAULT_VERSION): """Install or upgrade setuptools and EasyInstall""" try: import setuptools except ImportError: egg = None try: egg = download_setuptools(version, delay=0) sys.path.insert(0,egg) from setuptools.command.easy_install import main return main(list(argv)+[egg]) # we're done here finally: if egg and os.path.exists(egg): os.unlink(egg) else: if setuptools.__version__ == '0.0.1': print(( "You have an obsolete version of setuptools installed. Please\n" "remove it from your system entirely before rerunning this script." ), file=sys.stderr) sys.exit(2) req = "setuptools>="+version import pkg_resources try: pkg_resources.require(req) except pkg_resources.VersionConflict: try: from setuptools.command.easy_install import main except ImportError: from easy_install import main main(list(argv)+[download_setuptools(delay=0)]) sys.exit(0) # try to force an exit else: if argv: from setuptools.command.easy_install import main main(argv) else: print("Setuptools version",version,"or greater has been installed.") print('(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)') def update_md5(filenames): """Update our built-in md5 registry""" import re for name in filenames: base = os.path.basename(name) f = open(name,'rb') md5_data[base] = md5(f.read()).hexdigest() f.close() data = [" %r: %r,\n" % it for it in list(md5_data.items())] data.sort() repl = "".join(data) import inspect srcfile = inspect.getsourcefile(sys.modules[__name__]) f = open(srcfile, 'rb'); src = f.read(); f.close() match = re.search("\nmd5_data = {\n([^}]+)}", src) if not match: print("Internal error!", file=sys.stderr) sys.exit(2) src = src[:match.start(1)] + repl + src[match.end(1):] f = open(srcfile,'w') f.write(src) f.close() if __name__=='__main__': if len(sys.argv)>2 and sys.argv[1]=='--md5update': update_md5(sys.argv[2:]) else: main(sys.argv[1:]) pymodbus-2.1.0/pymodbus/000077500000000000000000000000001335513467700152345ustar00rootroot00000000000000pymodbus-2.1.0/pymodbus/__init__.py000066400000000000000000000016031335513467700173450ustar00rootroot00000000000000''' Pymodbus: Modbus Protocol Implementation ----------------------------------------- TwistedModbus is built on top of the code developed 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 Released under the the BSD license ''' import pymodbus.version as __version __version__ = __version.version.short() __author__ = 'Galen Collins' __maintainer__ = 'dhoomakethu' #---------------------------------------------------------------------------# # Block unhandled logging #---------------------------------------------------------------------------# import logging as __logging try: from logging import NullHandler as __null except ImportError: class __null(__logging.Handler): def emit(self, record): pass __logging.getLogger(__name__).addHandler(__null()) pymodbus-2.1.0/pymodbus/bit_read_message.py000066400000000000000000000206061335513467700210670ustar00rootroot00000000000000""" 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 class ReadBitsRequestBase(ModbusRequest): ''' Base class for Messages Requesting bit values ''' _rtu_frame_size = 8 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 def encode(self): ''' Encodes a request pdu :returns: The encoded pdu ''' return struct.pack('>HH', self.address, self.count) def decode(self, data): ''' Decodes a request pdu :param data: The packet data to decode ''' self.address, self.count = struct.unpack('>HH', data) def get_response_pdu_size(self): """ Func_code (1 byte) + Byte Count(1 byte) + Quantity of Coils (n Bytes)/8, if the remainder is different of 0 then N = N+1 :return: """ count = self.count//8 if self.count % 8: count += 1 return 1 + 1 + count 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) class ReadBitsResponseBase(ModbusResponse): ''' Base class for Messages responding to bit-reading values ''' _rtu_byte_count_pos = 2 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 [] 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 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:]) 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) def resetBit(self, address): ''' Helper function to set the specified bit to 0 :param address: The bit to reset ''' self.setBit(address, 0) 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] def __str__(self): ''' Returns a string representation of the instance :returns: A string representation of the instance ''' return "ReadBitResponse(%d)" % len(self.bits) 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 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) 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) 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 def __init__(self, values=None, **kwargs): ''' Intializes a new instance :param values: The request values to respond with ''' ReadBitsResponseBase.__init__(self, values, **kwargs) 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 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) 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) 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 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", ] pymodbus-2.1.0/pymodbus/bit_write_message.py000066400000000000000000000217651335513467700213150ustar00rootroot00000000000000""" 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) 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 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) 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 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) 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]) def get_response_pdu_size(self): """ Func_code (1 byte) + Output Address (2 byte) + Output Value (2 Bytes) :return: """ return 1 + 2 + 2 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) 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 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 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 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) 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) 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 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 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 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] 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) 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 def get_response_pdu_size(self): """ Func_code (1 byte) + Output Address (2 byte) + Quantity of Outputs (2 Bytes) :return: """ return 1 + 2 + 2 class WriteMultipleCoilsResponse(ModbusResponse): ''' The normal response returns the function code, starting address, and quantity of coils forced. ''' function_code = 15 _rtu_frame_size = 8 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 def encode(self): ''' Encodes write coils response :returns: The byte encoded message ''' return struct.pack('>HH', self.address, self.count) def decode(self, data): ''' Decodes a write coils response :param data: The packet data to decode ''' self.address, self.count = struct.unpack('>HH', data) 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", ] pymodbus-2.1.0/pymodbus/client/000077500000000000000000000000001335513467700165125ustar00rootroot00000000000000pymodbus-2.1.0/pymodbus/client/__init__.py000066400000000000000000000000001335513467700206110ustar00rootroot00000000000000pymodbus-2.1.0/pymodbus/client/async/000077500000000000000000000000001335513467700176275ustar00rootroot00000000000000pymodbus-2.1.0/pymodbus/client/async/__init__.py000066400000000000000000000030371335513467700217430ustar00rootroot00000000000000""" Async Modbus Client implementation based on Twisted, tornado and asyncio ------------------------------------------------------------------------ Example run:: from pymodbus.client.async import schedulers # Import The clients from pymodbus.client.async.tcp import AsyncModbusTCPClient as Client from pymodbus.client.async.serial import AsyncModbusSerialClient as Client from pymodbus.client.async.udp import AsyncModbusUDPClient as Client # For tornado based async client use event_loop, future = Client(schedulers.IO_LOOP, port=5020) # For twisted based async client use event_loop, future = Client(schedulers.REACTOR, port=5020) # For asyncio based async client use event_loop, client = Client(schedulers.ASYNC_IO, port=5020) # Here event_loop is a thread which would control the backend and future is # a Future/deffered object which would be used to # add call backs to run asynchronously. # The Actual client could be accessed with future.result() with Tornado # and future.result when using twisted # For asyncio the actual client is returned and event loop is asyncio loop """ from pymodbus.compat import is_installed installed = is_installed('twisted') if installed: # Import deprecated async client only if twisted is installed #338 from pymodbus.client.async.deprecated.async import * else: import logging logger = logging.getLogger(__name__) logger.warning("Not Importing deprecated clients. " "Dependency Twisted is not Installed") pymodbus-2.1.0/pymodbus/client/async/asyncio/000077500000000000000000000000001335513467700212745ustar00rootroot00000000000000pymodbus-2.1.0/pymodbus/client/async/asyncio/__init__.py000066400000000000000000000604021335513467700234070ustar00rootroot00000000000000""" Asynchronous framework adapter for asyncio. """ import socket import asyncio import functools from pymodbus.exceptions import ConnectionException from pymodbus.client.async.mixins import AsyncModbusClientMixin from pymodbus.compat import byte2int import logging _logger = logging.getLogger(__name__) DGRAM_TYPE = socket.SocketKind.SOCK_DGRAM class BaseModbusAsyncClientProtocol(AsyncModbusClientMixin): """ Asyncio specific implementation of asynchronous modbus client protocol. """ #: Factory that created this instance. factory = None transport = None def connection_made(self, transport): """ Called when a connection is made. The transport argument is the transport representing the connection. :param transport: :return: """ self.transport = transport self._connectionMade() if self.factory: self.factory.protocol_made_connection(self) def connection_lost(self, reason): """ Called when the connection is lost or closed. The argument is either an exception object or None :param reason: :return: """ self.transport = None self._connectionLost(reason) if self.factory: self.factory.protocol_lost_connection(self) def data_received(self, data): """ Called when some data is received. data is a non-empty bytes object containing the incoming data. :param data: :return: """ self._dataReceived(data) def create_future(self): """ Helper function to create asyncio Future object :return: """ return asyncio.Future() def resolve_future(self, f, result): """ Resolves the completed future and sets the result :param f: :param result: :return: """ if not f.done(): f.set_result(result) def raise_future(self, f, exc): """ Sets exception of a future if not done :param f: :param exc: :return: """ if not f.done(): f.set_exception(exc) def _connectionMade(self): """ Called upon a successful client connection. """ _logger.debug("Client connected to modbus server") self._connected = True 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.raise_future(self.transaction.getTransaction(tid), ConnectionException( 'Connection lost during request')) @property def connected(self): """ Return connection status. """ return self._connected def execute(self, request, **kwargs): """ Starts the producer to send the next request to consumer.write(Frame(request)) """ request.transaction_id = self.transaction.getNextTID() packet = self.framer.buildPacket(request) _logger.debug("send: " + " ".join([hex(byte2int(x)) for x in packet])) self.transport.write(packet) return self._buildResponse(request.transaction_id) def _dataReceived(self, data): ''' Get response, check for valid message, decode result :param data: The data returned from the server ''' _logger.debug("recv: " + " ".join([hex(byte2int(x)) for x in data])) unit = self.framer.decode_data(data).get("uid", 0) self.framer.processIncomingPacket(data, self._handleResponse, unit=unit) def _handleResponse(self, reply, **kwargs): """ 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: self.resolve_future(handler, 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 """ f = self.create_future() if not self._connected: self.raise_future(f, ConnectionException( 'Client is not connected')) else: self.transaction.addTransaction(f, tid) return f def close(self): self.transport.close() self._connected = False class ModbusClientProtocol(BaseModbusAsyncClientProtocol, asyncio.Protocol): """ Asyncio specific implementation of asynchronous modbus client protocol. """ #: Factory that created this instance. factory = None transport = None def data_received(self, data): """ Called when some data is received. data is a non-empty bytes object containing the incoming data. :param data: :return: """ self._dataReceived(data) class ModbusUdpClientProtocol(BaseModbusAsyncClientProtocol, asyncio.DatagramProtocol): """ Asyncio specific implementation of asynchronous modbus udp client protocol. """ #: Factory that created this instance. factory = None def __init__(self, host=None, port=0, **kwargs): self.host = host self.port = port super(self.__class__, self).__init__(**kwargs) def datagram_received(self, data, addr): self._dataReceived(data) class ReconnectingAsyncioModbusTcpClient(object): """ Client to connect to modbus device repeatedly over TCP/IP." """ #: Minimum delay in milli seconds before reconnect is attempted. DELAY_MIN_MS = 100 #: Maximum delay in milli seconds before reconnect is attempted. DELAY_MAX_MS = 1000 * 60 * 5 def __init__(self, protocol_class=None, loop=None): """ Initialize ReconnectingAsyncioModbusTcpClient :param protocol_class: Protocol used to talk to modbus device. :param loop: Event loop to use """ #: Protocol used to talk to modbus device. self.protocol_class = protocol_class or ModbusClientProtocol #: Current protocol instance. self.protocol = None #: Event loop to use. self.loop = loop or asyncio.get_event_loop() self.host = None self.port = 0 self.connected = False #: Reconnect delay in milli seconds. self.delay_ms = self.DELAY_MIN_MS def reset_delay(self): """ Resets wait before next reconnect to minimal period. """ self.delay_ms = self.DELAY_MIN_MS @asyncio.coroutine def start(self, host, port=502): """ Initiates connection to start client :param host: :param port: :return: """ # force reconnect if required: self.stop() _logger.debug('Connecting to %s:%s.' % (host, port)) self.host = host self.port = port yield from self._connect() def stop(self): """ Stops client :return: """ # prevent reconnect: self.host = None if self.connected: if self.protocol: if self.protocol.transport: self.protocol.transport.close() def _create_protocol(self): """ Factory function to create initialized protocol instance. """ protocol = self.protocol_class() protocol.factory = self return protocol @asyncio.coroutine def _connect(self): _logger.debug('Connecting.') try: yield from self.loop.create_connection(self._create_protocol, self.host, self.port) except Exception as ex: _logger.warning('Failed to connect: %s' % ex) asyncio.async(self._reconnect(), loop=self.loop) else: _logger.info('Connected to %s:%s.' % (self.host, self.port)) self.reset_delay() def protocol_made_connection(self, protocol): """ Protocol notification of successful connection. """ _logger.info('Protocol made connection.') if not self.connected: self.connected = True self.protocol = protocol else: _logger.error('Factory protocol connect ' 'callback called while connected.') def protocol_lost_connection(self, protocol): """ Protocol notification of lost connection. """ if self.connected: _logger.info('Protocol lost connection.') if protocol is not self.protocol: _logger.error('Factory protocol callback called ' 'from unexpected protocol instance.') self.connected = False self.protocol = None if self.host: asyncio.async(self._reconnect(), loop=self.loop) else: _logger.error('Factory protocol disconnect callback called while not connected.') @asyncio.coroutine def _reconnect(self): _logger.debug('Waiting %d ms before next ' 'connection attempt.' % self.delay_ms) yield from asyncio.sleep(self.delay_ms / 1000) self.delay_ms = min(2 * self.delay_ms, self.DELAY_MAX_MS) yield from self._connect() class AsyncioModbusTcpClient(object): """Client to connect to modbus device over TCP/IP.""" def __init__(self, host=None, port=502, protocol_class=None, loop=None): """ Initializes Asyncio Modbus Tcp Client :param host: Host IP address :param port: Port to connect :param protocol_class: Protocol used to talk to modbus device. :param loop: Asyncio Event loop """ #: Protocol used to talk to modbus device. self.protocol_class = protocol_class or ModbusClientProtocol #: Current protocol instance. self.protocol = None #: Event loop to use. self.loop = loop or asyncio.get_event_loop() self.host = host self.port = port self.connected = False def stop(self): """ Stops the client :return: """ if self.connected: if self.protocol: if self.protocol.transport: self.protocol.transport.close() def _create_protocol(self): """ Factory function to create initialized protocol instance. """ protocol = self.protocol_class() protocol.factory = self return protocol @asyncio.coroutine def connect(self): """ Connect and start Async client :return: """ _logger.debug('Connecting.') try: yield from self.loop.create_connection(self._create_protocol, self.host, self.port) _logger.info('Connected to %s:%s.' % (self.host, self.port)) except Exception as ex: _logger.warning('Failed to connect: %s' % ex) # asyncio.async(self._reconnect(), loop=self.loop) def protocol_made_connection(self, protocol): """ Protocol notification of successful connection. """ _logger.info('Protocol made connection.') if not self.connected: self.connected = True self.protocol = protocol else: _logger.error('Factory protocol connect ' 'callback called while connected.') def protocol_lost_connection(self, protocol): """ Protocol notification of lost connection. """ if self.connected: _logger.info('Protocol lost connection.') if protocol is not self.protocol: _logger.error('Factory protocol callback called' ' from unexpected protocol instance.') self.connected = False self.protocol = None # if self.host: # asyncio.async(self._reconnect(), loop=self.loop) else: _logger.error('Factory protocol disconnect' ' callback called while not connected.') class ReconnectingAsyncioModbusUdpClient(object): """ Client to connect to modbus device repeatedly over UDP. """ #: Reconnect delay in milli seconds. delay_ms = 0 #: Maximum delay in milli seconds before reconnect is attempted. DELAY_MAX_MS = 1000 * 60 * 5 def __init__(self, protocol_class=None, loop=None): """ Initializes ReconnectingAsyncioModbusUdpClient :param protocol_class: Protocol used to talk to modbus device. :param loop: Asyncio Event loop """ #: Protocol used to talk to modbus device. self.protocol_class = protocol_class or ModbusUdpClientProtocol #: Current protocol instance. self.protocol = None #: Event loop to use. self.loop = loop or asyncio.get_event_loop() self.host = None self.port = 0 self.connected = False self.reset_delay() def reset_delay(self): """ Resets wait before next reconnect to minimal period. """ self.delay_ms = 100 @asyncio.coroutine def start(self, host, port=502): """ Start reconnecting async udp client :param host: Host IP to connect :param port: Host port to connect :return: """ # force reconnect if required: self.stop() _logger.debug('Connecting to %s:%s.' % (host, port)) # getaddrinfo returns a list of tuples # - [(family, type, proto, canonname, sockaddr),] # We want sockaddr which is a (ip, port) tuple # udp needs ip addresses, not hostnames addrinfo = yield from self.loop.getaddrinfo(host, port, type=DGRAM_TYPE) self.host, self.port = addrinfo[0][-1] yield from self._connect() def stop(self): """ Stops connection and prevents reconnect :return: """ # prevent reconnect: self.host = None if self.connected: if self.protocol: if self.protocol.transport: self.protocol.transport.close() def _create_protocol(self, host=None, port=0): """ Factory function to create initialized protocol instance. """ protocol = self.protocol_class() protocol.host = host protocol.port = port protocol.factory = self return protocol @asyncio.coroutine def _connect(self): _logger.debug('Connecting.') try: yield from self.loop.create_datagram_endpoint( functools.partial(self._create_protocol, host=self.host, port=self.port), remote_addr=(self.host, self.port) ) _logger.info('Connected to %s:%s.' % (self.host, self.port)) except Exception as ex: _logger.warning('Failed to connect: %s' % ex) asyncio.async(self._reconnect(), loop=self.loop) def protocol_made_connection(self, protocol): """ Protocol notification of successful connection. """ _logger.info('Protocol made connection.') if not self.connected: self.connected = True self.protocol = protocol else: _logger.error('Factory protocol connect callback ' 'called while connected.') def protocol_lost_connection(self, protocol): """ Protocol notification of lost connection. """ if self.connected: _logger.info('Protocol lost connection.') if protocol is not self.protocol: _logger.error('Factory protocol callback called ' 'from unexpected protocol instance.') self.connected = False self.protocol = None if self.host: asyncio.async(self._reconnect(), loop=self.loop) else: _logger.error('Factory protocol disconnect ' 'callback called while not connected.') @asyncio.coroutine def _reconnect(self): _logger.debug('Waiting %d ms before next ' 'connection attempt.' % self.delay_ms) yield from asyncio.sleep(self.delay_ms / 1000) self.delay_ms = min(2 * self.delay_ms, self.DELAY_MAX_MS) yield from self._connect() class AsyncioModbusUdpClient(object): """ Client to connect to modbus device over UDP. """ def __init__(self, host=None, port=502, protocol_class=None, loop=None): """ Initializes Asyncio Modbus UDP Client :param host: Host IP address :param port: Port to connect :param protocol_class: Protocol used to talk to modbus device. :param loop: Asyncio Event loop """ #: Protocol used to talk to modbus device. self.protocol_class = protocol_class or ModbusUdpClientProtocol #: Current protocol instance. self.protocol = None #: Event loop to use. self.loop = loop or asyncio.get_event_loop() self.host = host self.port = port self.connected = False def stop(self): """ Stops connection :return: """ # prevent reconnect: # self.host = None if self.connected: if self.protocol: if self.protocol.transport: self.protocol.transport.close() def _create_protocol(self, host=None, port=0): """ Factory function to create initialized protocol instance. """ protocol = self.protocol_class() protocol.host = host protocol.port = port protocol.factory = self return protocol @asyncio.coroutine def connect(self): _logger.debug('Connecting.') try: addrinfo = yield from self.loop.getaddrinfo( self.host, self.port, type=DGRAM_TYPE) _host, _port = addrinfo[0][-1] yield from self.loop.create_datagram_endpoint( functools.partial(self._create_protocol, host=_host, port=_port), remote_addr=(self.host, self.port) ) _logger.info('Connected to %s:%s.' % (self.host, self.port)) except Exception as ex: _logger.warning('Failed to connect: %s' % ex) # asyncio.async(self._reconnect(), loop=self.loop) def protocol_made_connection(self, protocol): """ Protocol notification of successful connection. """ _logger.info('Protocol made connection.') if not self.connected: self.connected = True self.protocol = protocol else: _logger.error('Factory protocol connect ' 'callback called while connected.') def protocol_lost_connection(self, protocol): """ Protocol notification of lost connection. """ if self.connected: _logger.info('Protocol lost connection.') if protocol is not self.protocol: _logger.error('Factory protocol callback ' 'called from unexpected protocol instance.') self.connected = False self.protocol = None # if self.host: # asyncio.async(self._reconnect(), loop=self.loop) else: _logger.error('Factory protocol disconnect ' 'callback called while not connected.') class AsyncioModbusSerialClient(object): """ Client to connect to modbus device over serial. """ transport = None framer = None def __init__(self, port, protocol_class=None, framer=None, loop=None, baudrate=9600, bytesize=8, parity='N', stopbits=1): """ Initializes Asyncio Modbus Serial Client :param port: Port to connect :param protocol_class: Protocol used to talk to modbus device. :param framer: Framer to use :param loop: Asyncio Event loop """ #: Protocol used to talk to modbus device. self.protocol_class = protocol_class or ModbusClientProtocol #: Current protocol instance. self.protocol = None #: Event loop to use. self.loop = loop or asyncio.get_event_loop() self.port = port self.baudrate = baudrate self.bytesize = bytesize self.parity = parity self.stopbits = stopbits self.framer = framer self._connected_event = asyncio.Event() def stop(self): """ Stops connection :return: """ if self._connected: if self.protocol: if self.protocol.transport: self.protocol.transport.close() def _create_protocol(self): protocol = self.protocol_class(framer=self.framer) protocol.factory = self return protocol @property def _connected(self): return self._connected_event.is_set() @asyncio.coroutine def connect(self): """ Connect Async client :return: """ _logger.debug('Connecting.') try: from serial_asyncio import create_serial_connection yield from create_serial_connection( self.loop, self._create_protocol, self.port, baudrate=self.baudrate, bytesize=self.bytesize, stopbits=self.stopbits ) yield from self._connected_event.wait() _logger.info('Connected to %s', self.port) except Exception as ex: _logger.warning('Failed to connect: %s', ex) def protocol_made_connection(self, protocol): """ Protocol notification of successful connection. """ _logger.info('Protocol made connection.') if not self._connected: self._connected_event.set() self.protocol = protocol else: _logger.error('Factory protocol connect ' 'callback called while connected.') def protocol_lost_connection(self, protocol): """ Protocol notification of lost connection. """ if self._connected: _logger.info('Protocol lost connection.') if protocol is not self.protocol: _logger.error('Factory protocol callback called' ' from unexpected protocol instance.') self._connected_event.clear() self.protocol = None # if self.host: # asyncio.async(self._reconnect(), loop=self.loop) else: _logger.error('Factory protocol disconnect callback ' 'called while not connected.') @asyncio.coroutine def init_tcp_client(proto_cls, loop, host, port, **kwargs): """ Helper function to initialize tcp client :param proto_cls: :param loop: :param host: :param port: :param kwargs: :return: """ client = ReconnectingAsyncioModbusTcpClient(protocol_class=proto_cls, loop=loop) yield from client.start(host, port) return client @asyncio.coroutine def init_udp_client(proto_cls, loop, host, port, **kwargs): """ Helper function to initialize UDP client :param proto_cls: :param loop: :param host: :param port: :param kwargs: :return: """ client = ReconnectingAsyncioModbusUdpClient(protocol_class=proto_cls, loop=loop) yield from client.start(host, port) return client pymodbus-2.1.0/pymodbus/client/async/deprecated/000077500000000000000000000000001335513467700217275ustar00rootroot00000000000000pymodbus-2.1.0/pymodbus/client/async/deprecated/__init__.py000066400000000000000000000032341335513467700240420ustar00rootroot00000000000000import warnings warnings.simplefilter('always', DeprecationWarning) WARNING = """ Usage of '{}' is deprecated from 2.0.0 and will be removed in future releases. Use the new Async Modbus Client implementation based on Twisted, tornado and asyncio ------------------------------------------------------------------------ Example run:: from pymodbus.client.async import schedulers # Import The clients from pymodbus.client.async.tcp import AsyncModbusTCPClient as Client from pymodbus.client.async.serial import AsyncModbusSerialClient as Client from pymodbus.client.async.udp import AsyncModbusUDPClient as Client # For tornado based async client use event_loop, future = Client(schedulers.IO_LOOP, port=5020) # For twisted based async client use event_loop, deferred = Client(schedulers.REACTOR, port=5020) # For asyncio based async client use event_loop, client = Client(schedulers.ASYNC_IO, port=5020) # Here event_loop is a thread which would control the backend and future is # a Future/deffered object which would be used to # add call backs to run asynchronously. # The Actual client could be accessed with future.result() with Tornado # and future.result when using twisted # For asyncio the actual client is returned and event loop is asyncio loop Refer: https://pymodbus.readthedocs.io/en/dev/source/example/async_twisted_client.html https://pymodbus.readthedocs.io/en/dev/source/example/async_tornado_client.html https://pymodbus.readthedocs.io/en/dev/source/example/async_asyncio_client.html """ def deprecated(name): # pragma: no cover warnings.warn(WARNING.format(name), DeprecationWarning) pymodbus-2.1.0/pymodbus/client/async/deprecated/async.py000066400000000000000000000204311335513467700234160ustar00rootroot00000000000000""" 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() """ import logging 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 pymodbus.client.async.deprecated import deprecated from twisted.internet import defer, protocol from twisted.python.failure import Failure # --------------------------------------------------------------------------- # # Logging # --------------------------------------------------------------------------- # _logger = logging.getLogger(__name__) # --------------------------------------------------------------------------- # # Connected Client Protocols # --------------------------------------------------------------------------- # class ModbusClientProtocol(protocol.Protocol, ModbusClientMixin): # pragma: no cover """ 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 """ deprecated(self.__class__.__name__) self._connected = False self.framer = framer or ModbusSocketFramer(ClientDecoder()) if isinstance(self.framer, type): # Framer class not instance self.framer = self.framer(ClientDecoder(), client=None) if isinstance(self.framer, ModbusSocketFramer): self.transaction = DictTransactionManager(self, **kwargs) else: self.transaction = FifoTransactionManager(self, **kwargs) def connectionMade(self): """ Called upon a successful client connection. """ _logger.debug("Client connected to modbus server") self._connected = True 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'))) def dataReceived(self, data): """ Get response, check for valid message, decode result :param data: The data returned from the server """ unit = self.framer.decode_data(data).get("uid", 0) self.framer.processIncomingPacket(data, self._handleResponse, unit=unit) 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 """ 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): # pragma: no cover """ 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 """ deprecated(self.__class__.__name__) 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) unit = self.framer.decode_data(data).get("uid", 0) self.framer.processIncomingPacket(data, self._handleResponse, unit=unit) 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 # --------------------------------------------------------------------------- # class ModbusClientFactory(protocol.ReconnectingClientFactory): # pragma: no cover """ Simple client protocol factory """ protocol = ModbusClientProtocol def __init__(self): deprecated(self.__class__.__name__) protocol.ReconnectingClientFactory.__init__(self) # --------------------------------------------------------------------------- # # Exported symbols # --------------------------------------------------------------------------- # __all__ = [ "ModbusClientProtocol", "ModbusUdpClientProtocol", "ModbusClientFactory" ] pymodbus-2.1.0/pymodbus/client/async/factory/000077500000000000000000000000001335513467700212765ustar00rootroot00000000000000pymodbus-2.1.0/pymodbus/client/async/factory/__init__.py000066400000000000000000000000701335513467700234040ustar00rootroot00000000000000from __future__ import absolute_import, unicode_literalspymodbus-2.1.0/pymodbus/client/async/factory/serial.py000066400000000000000000000101641335513467700231310ustar00rootroot00000000000000""" Factory to create async serial clients based on twisted/tornado/asyncio """ from __future__ import unicode_literals from __future__ import absolute_import import logging from pymodbus.client.async import schedulers from pymodbus.client.async.thread import EventLoopThread LOGGER = logging.getLogger(__name__) def reactor_factory(port, framer, **kwargs): """ Factory to create twisted serial async client :param port: Serial port :param framer: Modbus Framer :param kwargs: :return: event_loop_thread and twisted serial client """ from twisted.internet import reactor from twisted.internet.serialport import SerialPort from twisted.internet.protocol import ClientFactory from pymodbus.factory import ClientDecoder class SerialClientFactory(ClientFactory): def __init__(self, framer, proto_cls): ''' Remember things necessary for building a protocols ''' self.proto_cls = proto_cls self.framer = framer def buildProtocol(self): ''' Create a protocol and start the reading cycle ''' proto = self.proto_cls(self.framer) proto.factory = self return proto class SerialModbusClient(SerialPort): def __init__(self, framer, *args, **kwargs): ''' Setup the client and start listening on the serial port :param factory: The factory to build clients with ''' self.decoder = ClientDecoder() proto_cls = kwargs.pop("proto_cls", None) proto = SerialClientFactory(framer, proto_cls).buildProtocol() SerialPort.__init__(self, proto, *args, **kwargs) proto = EventLoopThread("reactor", reactor.run, reactor.stop, installSignalHandlers=0) ser_client = SerialModbusClient(framer, port, reactor, **kwargs) return proto, ser_client def io_loop_factory(port=None, framer=None, **kwargs): """ Factory to create Tornado based async serial clients :param port: Serial port :param framer: Modbus Framer :param kwargs: :return: event_loop_thread and tornado future """ from tornado.ioloop import IOLoop from pymodbus.client.async.tornado import (AsyncModbusSerialClient as Client) ioloop = IOLoop() protocol = EventLoopThread("ioloop", ioloop.start, ioloop.stop) protocol.start() client = Client(port=port, framer=framer, ioloop=ioloop, **kwargs) future = client.connect() return protocol, future def async_io_factory(port=None, framer=None, **kwargs): """ Factory to create asyncio based async serial clients :param port: Serial port :param framer: Modbus Framer :param kwargs: Serial port options :return: asyncio event loop and serial client """ import asyncio from pymodbus.client.async.asyncio import (ModbusClientProtocol, AsyncioModbusSerialClient) loop = kwargs.pop("loop", None) or asyncio.get_event_loop() proto_cls = kwargs.pop("proto_cls", None) or ModbusClientProtocol try: from serial_asyncio import create_serial_connection except ImportError: LOGGER.critical("pyserial-asyncio is not installed, " "install with 'pip install pyserial-asyncio") import sys sys.exit(1) client = AsyncioModbusSerialClient(port, proto_cls, framer, loop, **kwargs) coro = client.connect() loop.run_until_complete(coro) return loop, client def get_factory(scheduler): """ Gets protocol factory based on the backend scheduler being used :param scheduler: REACTOR/IO_LOOP/ASYNC_IO :return: """ if scheduler == schedulers.REACTOR: return reactor_factory elif scheduler == schedulers.IO_LOOP: return io_loop_factory elif scheduler == schedulers.ASYNC_IO: return async_io_factory else: LOGGER.warning("Allowed Schedulers: {}, {}, {}".format( schedulers.REACTOR, schedulers.IO_LOOP, schedulers.ASYNC_IO )) raise Exception("Invalid Scheduler '{}'".format(scheduler)) pymodbus-2.1.0/pymodbus/client/async/factory/tcp.py000066400000000000000000000076101335513467700224420ustar00rootroot00000000000000""" Factory to create async tcp clients based on twisted/tornado/asyncio """ from __future__ import unicode_literals from __future__ import absolute_import import logging from pymodbus.client.async import schedulers from pymodbus.client.async.thread import EventLoopThread from pymodbus.constants import Defaults LOGGER = logging.getLogger(__name__) def reactor_factory(host="127.0.0.1", port=Defaults.Port, framer=None, source_address=None, timeout=None, **kwargs): """ Factory to create twisted tcp async client :param host: Host IP address :param port: Port :param framer: Modbus Framer :param source_address: Bind address :param timeout: Timeout in seconds :param kwargs: :return: event_loop_thread and twisted_deferred """ from twisted.internet import reactor, protocol from pymodbus.client.async.twisted import ModbusTcpClientProtocol deferred = protocol.ClientCreator( reactor, ModbusTcpClientProtocol ).connectTCP(host, port, timeout=timeout, bindAddress=source_address) callback = kwargs.get("callback") errback = kwargs.get("errback") if callback: deferred.addCallback(callback) if errback: deferred.addErrback(errback) protocol = EventLoopThread("reactor", reactor.run, reactor.stop, installSignalHandlers=0) protocol.start() return protocol, deferred def io_loop_factory(host="127.0.0.1", port=Defaults.Port, framer=None, source_address=None, timeout=None, **kwargs): """ Factory to create Tornado based async tcp clients :param host: Host IP address :param port: Port :param framer: Modbus Framer :param source_address: Bind address :param timeout: Timeout in seconds :param kwargs: :return: event_loop_thread and tornado future """ from tornado.ioloop import IOLoop from pymodbus.client.async.tornado import AsyncModbusTCPClient as \ Client ioloop = IOLoop() protocol = EventLoopThread("ioloop", ioloop.start, ioloop.stop) protocol.start() client = Client(host=host, port=port, framer=framer, source_address=source_address, timeout=timeout, ioloop=ioloop, **kwargs) future = client.connect() return protocol, future def async_io_factory(host="127.0.0.1", port=Defaults.Port, framer=None, source_address=None, timeout=None, **kwargs): """ Factory to create asyncio based async tcp clients :param host: Host IP address :param port: Port :param framer: Modbus Framer :param source_address: Bind address :param timeout: Timeout in seconds :param kwargs: :return: asyncio event loop and tcp client """ import asyncio from pymodbus.client.async.asyncio import init_tcp_client loop = kwargs.get("loop") or asyncio.new_event_loop() proto_cls = kwargs.get("proto_cls", None) if not loop.is_running(): asyncio.set_event_loop(loop) cor = init_tcp_client(proto_cls, loop, host, port) client = loop.run_until_complete(asyncio.gather(cor))[0] else: cor = init_tcp_client(proto_cls, loop, host, port) future = asyncio.run_coroutine_threadsafe(cor, loop=loop) client = future.result() return loop, client def get_factory(scheduler): """ Gets protocol factory based on the backend scheduler being used :param scheduler: REACTOR/IO_LOOP/ASYNC_IO :return """ if scheduler == schedulers.REACTOR: return reactor_factory elif scheduler == schedulers.IO_LOOP: return io_loop_factory elif scheduler == schedulers.ASYNC_IO: return async_io_factory else: LOGGER.warning("Allowed Schedulers: {}, {}, {}".format( schedulers.REACTOR, schedulers.IO_LOOP, schedulers.ASYNC_IO )) raise Exception("Invalid Scheduler '{}'".format(scheduler)) pymodbus-2.1.0/pymodbus/client/async/factory/udp.py000066400000000000000000000057631335513467700224530ustar00rootroot00000000000000from __future__ import unicode_literals from __future__ import absolute_import import logging from pymodbus.client.async import schedulers from pymodbus.client.async.thread import EventLoopThread from pymodbus.constants import Defaults LOGGER = logging.getLogger(__name__) def reactor_factory(host="127.0.0.1", port=Defaults.Port, framer=None, source_address=None, timeout=None, **kwargs): """ Factory to create twisted udp async client :param host: Host IP address :param port: Port :param framer: Modbus Framer :param source_address: Bind address :param timeout: Timeout in seconds :param kwargs: :return: event_loop_thread and twisted_deferred """ raise NotImplementedError() def io_loop_factory(host="127.0.0.1", port=Defaults.Port, framer=None, source_address=None, timeout=None, **kwargs): """ Factory to create Tornado based async udp clients :param host: Host IP address :param port: Port :param framer: Modbus Framer :param source_address: Bind address :param timeout: Timeout in seconds :param kwargs: :return: event_loop_thread and tornado future """ from tornado.ioloop import IOLoop from pymodbus.client.async.tornado import AsyncModbusUDPClient as \ Client client = Client(host=host, port=port, framer=framer, source_address=source_address, timeout=timeout, **kwargs) protocol = EventLoopThread("ioloop", IOLoop.current().start, IOLoop.current().stop) protocol.start() future = client.connect() return protocol, future def async_io_factory(host="127.0.0.1", port=Defaults.Port, framer=None, source_address=None, timeout=None, **kwargs): """ Factory to create asyncio based async udp clients :param host: Host IP address :param port: Port :param framer: Modbus Framer :param source_address: Bind address :param timeout: Timeout in seconds :param kwargs: :return: asyncio event loop and udp client """ import asyncio from pymodbus.client.async.asyncio import init_udp_client loop = kwargs.get("loop") or asyncio.get_event_loop() proto_cls = kwargs.get("proto_cls", None) cor = init_udp_client(proto_cls, loop, host, port) client = loop.run_until_complete(asyncio.gather(cor))[0] return loop, client def get_factory(scheduler): """ Gets protocol factory based on the backend scheduler being used :param scheduler: REACTOR/IO_LOOP/ASYNC_IO :return """ if scheduler == schedulers.REACTOR: return reactor_factory elif scheduler == schedulers.IO_LOOP: return io_loop_factory elif scheduler == schedulers.ASYNC_IO: return async_io_factory else: LOGGER.warning("Allowed Schedulers: {}, {}, {}".format( schedulers.REACTOR, schedulers.IO_LOOP, schedulers.ASYNC_IO )) raise Exception("Invalid Scheduler '{}'".format(scheduler)) pymodbus-2.1.0/pymodbus/client/async/mixins.py000066400000000000000000000041321335513467700215100ustar00rootroot00000000000000import logging from pymodbus.client.sync import BaseModbusClient from pymodbus.constants import Defaults from pymodbus.factory import ClientDecoder from pymodbus.transaction import ModbusSocketFramer LOGGER = logging.getLogger(__name__) class BaseAsyncModbusClient(BaseModbusClient): """ This represents the base ModbusAsyncClient. """ def __init__(self, framer=None, **kwargs): """ Initializes the framer module :param framer: The framer to use for the protocol. Default: ModbusSocketFramer :type framer: pymodbus.transaction.ModbusSocketFramer """ self._connected = False super(BaseAsyncModbusClient, self).__init__( framer or ModbusSocketFramer(ClientDecoder()), **kwargs ) class AsyncModbusClientMixin(BaseAsyncModbusClient): """ Async Modbus client mixing for UDP and TCP clients """ def __init__(self, host="127.0.0.1", port=Defaults.Port, framer=None, source_address=None, timeout=None, **kwargs): """ Initializes a Modbus TCP/UDP async client :param host: Host IP address :param port: Port :param framer: Framer to use :param source_address: Specific to underlying client being used :param timeout: Timeout in seconds :param kwargs: Extra arguments """ super(AsyncModbusClientMixin, self).__init__(framer=framer, **kwargs) self.host = host self.port = port self.source_address = source_address or ("", 0) self.timeout = timeout if timeout is not None else Defaults.Timeout class AsyncModbusSerialClientMixin(BaseAsyncModbusClient): """ Async Modbus Serial Client Mixing """ def __init__(self, framer=None, port=None, **kwargs): """ Initializes a Async Modbus Serial Client :param framer: Modbus Framer :param port: Serial port to use :param kwargs: Extra arguments if any """ super(AsyncModbusSerialClientMixin, self).__init__(framer=framer) self.port = port self.serial_settings = kwargs pymodbus-2.1.0/pymodbus/client/async/schedulers/000077500000000000000000000000001335513467700217705ustar00rootroot00000000000000pymodbus-2.1.0/pymodbus/client/async/schedulers/__init__.py000066400000000000000000000002451335513467700241020ustar00rootroot00000000000000""" Backend schedulers to use with generic Async clients """ from __future__ import unicode_literals REACTOR = "reactor" IO_LOOP = "io_loop" ASYNC_IO = "async_io" pymodbus-2.1.0/pymodbus/client/async/serial.py000066400000000000000000000050541335513467700214640ustar00rootroot00000000000000from __future__ import unicode_literals from __future__ import absolute_import import logging from pymodbus.client.async.factory.serial import get_factory from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer, ModbusBinaryFramer, ModbusSocketFramer from pymodbus.factory import ClientDecoder from pymodbus.exceptions import ParameterException from pymodbus.compat import IS_PYTHON3, PYTHON_VERSION from pymodbus.client.async.schedulers import ASYNC_IO logger = logging.getLogger(__name__) class AsyncModbusSerialClient(object): """ Actual Async Serial Client to be used. To use do:: from pymodbus.client.async.serial import AsyncModbusSerialClient """ @classmethod def _framer(cls, 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") def __new__(cls, scheduler, method, port, **kwargs): """ Scheduler to use: - reactor (Twisted) - io_loop (Tornado) - async_io (asyncio) The methods to connect are:: - ascii - rtu - binary : param scheduler: Backend to use :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) :param scheduler: :param method: :param port: :param kwargs: :return: """ if (not (IS_PYTHON3 and PYTHON_VERSION >= (3, 4)) and scheduler == ASYNC_IO): logger.critical("ASYNCIO is supported only on python3") import sys sys.exit(1) factory_class = get_factory(scheduler) framer = cls._framer(method) yieldable = factory_class(framer=framer, port=port, **kwargs) return yieldable pymodbus-2.1.0/pymodbus/client/async/tcp.py000066400000000000000000000031721335513467700207720ustar00rootroot00000000000000from __future__ import unicode_literals from __future__ import absolute_import import logging from pymodbus.client.async.factory.tcp import get_factory from pymodbus.constants import Defaults from pymodbus.compat import IS_PYTHON3, PYTHON_VERSION from pymodbus.client.async.schedulers import ASYNC_IO logger = logging.getLogger(__name__) class AsyncModbusTCPClient(object): """ Actual Async Serial Client to be used. To use do:: from pymodbus.client.async.tcp import AsyncModbusTCPClient """ def __new__(cls, scheduler, host="127.0.0.1", port=Defaults.Port, framer=None, source_address=None, timeout=None, **kwargs): """ Scheduler to use: - reactor (Twisted) - io_loop (Tornado) - async_io (asyncio) :param scheduler: Backend to use :param host: Host IP address :param port: Port :param framer: Modbus Framer to use :param source_address: source address specific to underlying backend :param timeout: Time out in seconds :param kwargs: Other extra args specific to Backend being used :return: """ if (not (IS_PYTHON3 and PYTHON_VERSION >= (3, 4)) and scheduler == ASYNC_IO): logger.critical("ASYNCIO is supported only on python3") import sys sys.exit(1) factory_class = get_factory(scheduler) yieldable = factory_class(host=host, port=port, framer=framer, source_address=source_address, timeout=timeout, **kwargs) return yieldable pymodbus-2.1.0/pymodbus/client/async/thread.py000066400000000000000000000026701335513467700214550ustar00rootroot00000000000000from __future__ import unicode_literals from __future__ import absolute_import from threading import Thread import logging LOGGER = logging.getLogger(__name__) class EventLoopThread(object): """ Event loop controlling the backend event loops (io_loop for tornado, reactor for twisted and event_loop for Asyncio) """ def __init__(self, name, start, stop, *args, **kwargs): """ Initialize Event loop thread :param name: Name of the event loop :param start: Start method to start the backend event loop :param stop: Stop method to stop the backend event loop :param args: :param kwargs: """ self._name = name self._start_loop = start self._stop_loop = stop self._args = args self._kwargs = kwargs self._event_loop = Thread(name=self._name, target=self._start) def _start(self): """ Starts the backend event loop :return: """ self._start_loop(*self._args, **self._kwargs) def start(self): """ Starts the backend event loop :return: """ LOGGER.info("Starting Event Loop: 'PyModbus_{}'".format(self._name)) self._event_loop.start() def stop(self): """ Stops the backend event loop :return: """ LOGGER.info("Stopping Event Loop: 'PyModbus_{}'".format(self._name)) self._stop_loop() pymodbus-2.1.0/pymodbus/client/async/tornado/000077500000000000000000000000001335513467700212755ustar00rootroot00000000000000pymodbus-2.1.0/pymodbus/client/async/tornado/__init__.py000066400000000000000000000217651335513467700234210ustar00rootroot00000000000000""" Asynchronous framework adapter for tornado. """ from __future__ import unicode_literals import abc import logging import socket from serial import Serial from tornado import gen from tornado.concurrent import Future from tornado.ioloop import IOLoop from tornado.iostream import IOStream from tornado.iostream import BaseIOStream from pymodbus.client.async.mixins import (AsyncModbusClientMixin, AsyncModbusSerialClientMixin) from pymodbus.exceptions import ConnectionException from pymodbus.compat import byte2int LOGGER = logging.getLogger(__name__) class BaseTornadoClient(AsyncModbusClientMixin): """ Base Tornado client """ stream = None io_loop = None def __init__(self, *args, **kwargs): """ Initializes BaseTornadoClient. ioloop to be passed as part of kwargs ('ioloop') :param args: :param kwargs: """ self.io_loop = kwargs.pop("ioloop", None) super(BaseTornadoClient, self).__init__(*args, **kwargs) @abc.abstractmethod def get_socket(self): """ return instance of the socket to connect to """ @gen.coroutine def connect(self): """ Connect to the socket identified by host and port :returns: Future :rtype: tornado.concurrent.Future """ conn = self.get_socket() self.stream = IOStream(conn, io_loop=self.io_loop or IOLoop.current()) self.stream.connect((self.host, self.port)) self.stream.read_until_close(None, streaming_callback=self.on_receive) self._connected = True LOGGER.debug("Client connected") raise gen.Return(self) def on_receive(self, *args): """ On data recieve call back :param args: data received :return: """ data = args[0] if len(args) > 0 else None if not data: return LOGGER.debug("recv: " + " ".join([hex(byte2int(x)) for x in data])) unit = self.framer.decode_data(data).get("uid", 0) self.framer.processIncomingPacket(data, self._handle_response, unit=unit) def execute(self, request=None): """ Executes a transaction :param request: :return: """ request.transaction_id = self.transaction.getNextTID() packet = self.framer.buildPacket(request) LOGGER.debug("send: " + " ".join([hex(byte2int(x)) for x in packet])) self.stream.write(packet) return self._build_response(request.transaction_id) def _handle_response(self, reply, **kwargs): """ Handle response received :param reply: :param kwargs: :return: """ if reply is not None: tid = reply.transaction_id future = self.transaction.getTransaction(tid) if future: future.set_result(reply) else: LOGGER.debug("Unrequested message: {}".format(reply)) def _build_response(self, tid): """ Builds a future response :param tid: :return: """ f = Future() if not self._connected: f.set_exception(ConnectionException("Client is not connected")) return f self.transaction.addTransaction(f, tid) return f def close(self): """ Closes the underlying IOStream """ LOGGER.debug("Client disconnected") if self.stream: self.stream.close_fd() self.stream = None self._connected = False class BaseTornadoSerialClient(AsyncModbusSerialClientMixin): """ Base Tonado serial client """ stream = None io_loop = None def __init__(self, *args, **kwargs): """ Initializes BaseTornadoSerialClient. ioloop to be passed as part of kwargs ('ioloop') :param args: :param kwargs: """ self.io_loop = kwargs.pop("ioloop", None) super(BaseTornadoSerialClient, self).__init__(*args, **kwargs) @abc.abstractmethod def get_socket(self): """ return instance of the socket to connect to """ def on_receive(self, *args): # Will be handled ine execute method pass def execute(self, request=None): """ Executes a transaction :param request: Request to be written on to the bus :return: """ request.transaction_id = self.transaction.getNextTID() def callback(*args): LOGGER.debug("in callback - {}".format(request.transaction_id)) while True: waiting = self.stream.connection.in_waiting if waiting: data = self.stream.connection.read(waiting) LOGGER.debug( "recv: " + " ".join([hex(byte2int(x)) for x in data])) self.framer.processIncomingPacket( data, self._handle_response, tid=request.transaction_id ) break packet = self.framer.buildPacket(request) LOGGER.debug("send: " + " ".join([hex(byte2int(x)) for x in packet])) self.stream.write(packet, callback=callback) f = self._build_response(request.transaction_id) return f def _handle_response(self, reply, **kwargs): """ Handles a received response and updates a future :param reply: Reply received :param kwargs: :return: """ if reply is not None: tid = reply.transaction_id future = self.transaction.getTransaction(tid) if future: future.set_result(reply) else: LOGGER.debug("Unrequested message: {}".format(reply)) def _build_response(self, tid): """ Prepare for a response, returns a future :param tid: :return: Future """ f = Future() if not self._connected: f.set_exception(ConnectionException("Client is not connected")) return f self.transaction.addTransaction(f, tid) return f def close(self): """ Closes the underlying IOStream """ LOGGER.debug("Client disconnected") if self.stream: self.stream.close_fd() self.stream = None self._connected = False class SerialIOStream(BaseIOStream): """ Serial IO Stream class to control and handle serial connections over tornado """ def __init__(self, connection, *args, **kwargs): """ Initializes Serial IO Stream :param connection: serial object :param args: :param kwargs: """ self.connection = connection super(SerialIOStream, self).__init__(*args, **kwargs) def fileno(self): """ Returns serial fd :return: """ return self.connection.fileno() def close_fd(self): """ Closes a serial Fd :return: """ if self.connection: self.connection.close() self.connection = None def read_from_fd(self): """ Reads from a fd :return: """ try: chunk = self.connection.readline() except Exception: return None return chunk def write_to_fd(self, data): """ Writes to a fd :param data: :return: """ try: return self.connection.write(data) except Exception as e: LOGGER.error(e) class AsyncModbusSerialClient(BaseTornadoSerialClient): """ Tornado based async serial client """ def get_socket(self): """ Creates Pyserial object :return: serial object """ return Serial(port=self.port, **self.serial_settings) @gen.coroutine def connect(self): """Connect to the socket identified by host and port :returns: Future :rtype: tornado.concurrent.Future """ conn = self.get_socket() if self.io_loop is None: self.io_loop = IOLoop.current() try: self.stream = SerialIOStream(conn, io_loop=self.io_loop) except Exception as e: LOGGER.exception(e) self._connected = True LOGGER.debug("Client connected") raise gen.Return(self) class AsyncModbusTCPClient(BaseTornadoClient): """ Tornado based Async tcp client """ def get_socket(self): """ Creates socket object :return: socket """ return socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) class AsyncModbusUDPClient(BaseTornadoClient): """ Tornado based Async UDP client """ def get_socket(self): """ Create socket object :return: socket """ return socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)pymodbus-2.1.0/pymodbus/client/async/twisted/000077500000000000000000000000001335513467700213125ustar00rootroot00000000000000pymodbus-2.1.0/pymodbus/client/async/twisted/__init__.py000066400000000000000000000205061335513467700234260ustar00rootroot00000000000000""" 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 __future__ import unicode_literals from twisted.internet import defer, protocol from pymodbus.exceptions import ConnectionException from pymodbus.factory import ClientDecoder from pymodbus.client.async.mixins import AsyncModbusClientMixin from pymodbus.transaction import FifoTransactionManager, DictTransactionManager from pymodbus.transaction import ModbusSocketFramer, ModbusRtuFramer from pymodbus.compat import byte2int from twisted.python.failure import Failure # --------------------------------------------------------------------------- # # Logging # --------------------------------------------------------------------------- # import logging _logger = logging.getLogger(__name__) # --------------------------------------------------------------------------- # # Connected Client Protocols # --------------------------------------------------------------------------- # class ModbusClientProtocol(protocol.Protocol, AsyncModbusClientMixin): """ This represents the base modbus client protocol. All the application layer code is deferred to a higher level wrapper. """ framer = None def __init__(self, framer=None, **kwargs): self._connected = False self.framer = framer or ModbusSocketFramer(ClientDecoder()) if isinstance(self.framer, type): # Framer class not instance self.framer = self.framer(ClientDecoder(), client=None) if isinstance(self.framer, ModbusSocketFramer): self.transaction = DictTransactionManager(self, **kwargs) else: self.transaction = FifoTransactionManager(self, **kwargs) def connectionMade(self): """ Called upon a successful client connection. """ _logger.debug("Client connected to modbus server") self._connected = True def connectionLost(self, reason=None): """ 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'))) def dataReceived(self, data): """ Get response, check for valid message, decode result :param data: The data returned from the server """ unit = self.framer.decode_data(data).get("uid", 0) self.framer.processIncomingPacket(data, self._handleResponse, unit=unit) 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) _logger.debug("send: " + " ".join([hex(byte2int(x)) for x in packet])) self.transport.write(packet) return self._buildResponse(request.transaction_id) def _handleResponse(self, reply, **kwargs): """ 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 """ if not self._connected: return defer.fail(Failure( ConnectionException('Client is not connected'))) d = defer.Deferred() self.transaction.addTransaction(d, tid) return d def close(self): """ Closes underlying transport layer ,essentially closing the client :return: """ if self.transport and hasattr(self.transport, "close"): self.transport.close() self._connected = False class ModbusTcpClientProtocol(ModbusClientProtocol): """ Async TCP Client protocol based on twisted. Default framer: ModbusSocketFramer """ framer = ModbusSocketFramer(ClientDecoder()) class ModbusSerClientProtocol(ModbusClientProtocol): """ Async Serial Client protocol based on twisted Default framer: ModbusRtuFramer """ def __init__(self, framer=None, **kwargs): framer = framer or ModbusRtuFramer(ClientDecoder()) super(ModbusSerClientProtocol, self).__init__(framer, **kwargs) # --------------------------------------------------------------------------- # # Not Connected Client Protocol # --------------------------------------------------------------------------- # class ModbusUdpClientProtocol(protocol.DatagramProtocol, AsyncModbusClientMixin): """ This represents the base modbus client protocol. All the application layer code is deferred to a higher level wrapper. """ 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) unit = self.framer.decode_data(data).get("uid", 0) self.framer.processIncomingPacket(data, self._handleResponse, unit=unit) 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, (self.host, self.port)) return self._buildResponse(request.transaction_id) def _handleResponse(self, reply, **kwargs): """ 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 # --------------------------------------------------------------------------- # class ModbusClientFactory(protocol.ReconnectingClientFactory): """ Simple client protocol factory """ protocol = ModbusClientProtocol # --------------------------------------------------------------------------- # # Exported symbols # --------------------------------------------------------------------------- # __all__ = [ "ModbusClientProtocol", "ModbusUdpClientProtocol", "ModbusClientFactory" ] pymodbus-2.1.0/pymodbus/client/async/udp.py000066400000000000000000000031671335513467700210000ustar00rootroot00000000000000from __future__ import unicode_literals from __future__ import absolute_import import logging from pymodbus.constants import Defaults from pymodbus.compat import IS_PYTHON3, PYTHON_VERSION from pymodbus.client.async.schedulers import ASYNC_IO from pymodbus.client.async.factory.udp import get_factory logger = logging.getLogger(__name__) class AsyncModbusUDPClient(object): """ Actual Async UDP Client to be used. To use do:: from pymodbus.client.async.tcp import AsyncModbusUDPClient """ def __new__(cls, scheduler, host="127.0.0.1", port=Defaults.Port, framer=None, source_address=None, timeout=None, **kwargs): """ Scheduler to use: - reactor (Twisted) - io_loop (Tornado) - async_io (asyncio) :param scheduler: Backend to use :param host: Host IP address :param port: Port :param framer: Modbus Framer to use :param source_address: source address specific to underlying backend :param timeout: Time out in seconds :param kwargs: Other extra args specific to Backend being used :return: """ if (not (IS_PYTHON3 and PYTHON_VERSION >= (3, 4)) and scheduler == ASYNC_IO): logger.critical("ASYNCIO is supported only on python3") import sys sys.exit(1) factory_class = get_factory(scheduler) yieldable = factory_class(host=host, port=port, framer=framer, source_address=source_address, timeout=timeout, **kwargs) return yieldable pymodbus-2.1.0/pymodbus/client/common.py000066400000000000000000000130441335513467700203560ustar00rootroot00000000000000''' 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 * from pymodbus.utilities import ModbusTransactionState 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) ''' state = ModbusTransactionState.IDLE last_frame_end = 0 silent_interval = 0 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) 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) 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) 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) 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) 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) 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) 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) 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) 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' ] pymodbus-2.1.0/pymodbus/client/sync.py000066400000000000000000000500361335513467700200440ustar00rootroot00000000000000import socket import select import serial import time import sys from functools import partial from pymodbus.constants import Defaults from pymodbus.utilities import hexlify_packets, ModbusTransactionState from pymodbus.factory import ClientDecoder 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 # --------------------------------------------------------------------------- # 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. """ 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) self._debug = False self._debugfd = None # ----------------------------------------------------------------------- # # Client interface # ----------------------------------------------------------------------- # def connect(self): """ Connect to the modbus remote host :returns: True if connection succeeded, False otherwise """ raise NotImplementedException("Method not implemented by derived class") def close(self): """ Closes the underlying socket connection """ pass def is_socket_open(self): """ Check whether the underlying socket/serial is open or not. :returns: True if socket/serial is open, False otherwise """ raise NotImplementedException( "is_socket_open() not implemented by {}".format(self.__str__()) ) def send(self, request): _logger.debug("New Transaction state 'SENDING'") self.state = ModbusTransactionState.SENDING return self._send(request) 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") def recv(self, size): return self._recv(size) 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 # ----------------------------------------------------------------------- # 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 # ----------------------------------------------------------------------- # 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 def __exit__(self, klass, value, traceback): """ Implement the client with exit block """ self.close() def idle_time(self): """ Bus Idle Time to initiate next transaction :return: time stamp """ if self.last_frame_end is None or self.silent_interval is None: return 0 return self.last_frame_end + self.silent_interval def debug_enabled(self): """ Returns a boolean indicating if debug is enabled. """ return self._debug def set_debug(self, debug): """ Sets the current debug flag. """ self._debug = debug def trace(self, writeable): if writeable: self.set_debug(True) self._debugfd = writeable def _dump(self, data, direction): fd = self._debugfd if self._debugfd else sys.stdout try: fd.write(hexlify_packets(data)) except Exception as e: self._logger.debug(hexlify_packets(data)) self._logger.exception(e) def __str__(self): """ Builds a string representation of the connection :returns: The string representation """ return "Null Transport" # --------------------------------------------------------------------------- # # Modbus TCP Client Transport Implementation # --------------------------------------------------------------------------- # class ModbusTcpClient(BaseModbusClient): """ Implementation of a modbus tcp client """ 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(), self), **kwargs) def connect(self): """ Connect to the modbus tcp server :returns: True if connection succeeded, False otherwise """ if self.socket: return True try: 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 is not None def close(self): """ Closes the underlying socket connection """ if self.socket: self.socket.close() self.socket = None 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 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__()) # socket.recv(size) waits until it gets some data from the host but # not necessarily the entire response that can be fragmented in # many packets. # To avoid the splitted responses to be recognized as invalid # messages and to be discarded, loops socket.recv until full data # is received or timeout is expired. # If timeout expires returns the read data, also if its length is # less than the expected size. self.socket.setblocking(0) timeout = self.timeout # If size isn't specified read 1 byte at a time. if size is None: recv_size = 1 else: recv_size = size data = b'' begin = time.time() while recv_size > 0: ready = select.select([self.socket], [], [], timeout) if ready[0]: data += self.socket.recv(recv_size) # If size isn't specified continue to read until timeout expires. if size: recv_size = size - len(data) # Timeout is reduced also if some data has been received in order # to avoid infinite loops when there isn't an expected response size # and the slave sends noisy data continuosly. timeout -= time.time() - begin if timeout <= 0: break return data def is_socket_open(self): return True if self.socket is not None else False def __str__(self): """ Builds a string representation of the connection :returns: The string representation """ return "ModbusTcpClient(%s:%s)" % (self.host, self.port) def __repr__(self): return ( "<{} at {} socket={self.socket}, ipaddr={self.host}, " "port={self.port}, timeout={self.timeout}>" ).format(self.__class__.__name__, hex(id(self)), self=self) # --------------------------------------------------------------------------- # # Modbus UDP Client Transport Implementation # --------------------------------------------------------------------------- # class ModbusUdpClient(BaseModbusClient): """ Implementation of a modbus udp client """ 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(), self), **kwargs) @classmethod 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 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 is not None def close(self): """ Closes the underlying socket connection """ self.socket = None 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 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] def is_socket_open(self): return True if self.socket is not None else False def __str__(self): """ Builds a string representation of the connection :returns: The string representation """ return "ModbusUdpClient(%s:%s)" % (self.host, self.port) def __repr__(self): return ( "<{} at {} socket={self.socket}, ipaddr={self.host}, " "port={self.port}, timeout={self.timeout}>" ).format(self.__class__.__name__, hex(id(self)), self=self) # --------------------------------------------------------------------------- # # Modbus Serial Client Transport Implementation # --------------------------------------------------------------------------- # class ModbusSerialClient(BaseModbusClient): """ Implementation of a modbus serial client """ state = ModbusTransactionState.IDLE inter_char_timeout = 0 silent_interval = 0 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, self), **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) self.last_frame_end = None if self.method == "rtu": if self.baudrate > 19200: self.silent_interval = 1.75 / 1000 # ms else: self._t0 = float((1 + 8 + 2)) / self.baudrate self.inter_char_timeout = 1.5 * self._t0 self.silent_interval = 3.5 * self._t0 self.silent_interval = round(self.silent_interval, 6) @staticmethod def __implementation(method, client): """ Returns the requested framer :method: The serial framer to instantiate :returns: The requested serial framer """ method = method.lower() if method == 'ascii': return ModbusAsciiFramer(ClientDecoder(), client) elif method == 'rtu': return ModbusRtuFramer(ClientDecoder(), client) elif method == 'binary': return ModbusBinaryFramer(ClientDecoder(), client) elif method == 'socket': return ModbusSocketFramer(ClientDecoder(), client) raise ParameterException("Invalid framer method requested") 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.socket.interCharTimeout = self.inter_char_timeout self.last_frame_end = None return self.socket is not None def close(self): """ Closes the underlying socket connection """ if self.socket: self.socket.close() self.socket = None def _in_waiting(self): 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)() return waitingbytes 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: try: waitingbytes = self._in_waiting() if waitingbytes: result = self.socket.read(waitingbytes) if _logger.isEnabledFor(logging.WARNING): _logger.warning("Cleanup recv buffer before " "send: " + hexlify_packets(result)) except NotImplementedError: pass size = self.socket.write(request) return size return 0 def _wait_for_data(self): size = 0 more_data = False if self.timeout is not None and self.timeout != 0: condition = partial(lambda start, timeout: (time.time() - start) <= timeout, timeout=self.timeout) else: condition = partial(lambda dummy1, dummy2: True, dummy2=None) start = time.time() while condition(start): avaialble = self._in_waiting() if (more_data and not avaialble) or (more_data and avaialble == size): break if avaialble and avaialble != size: more_data = True size = avaialble time.sleep(0.01) return size 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__()) if size is None: size = self._wait_for_data() result = self.socket.read(size) return result def is_socket_open(self): if self.socket: if hasattr(self.socket, "is_open"): return self.socket.is_open else: return self.socket.isOpen() return False def __str__(self): """ Builds a string representation of the connection :returns: The string representation """ return "ModbusSerialClient(%s baud[%s])" % (self.method, self.baudrate) def __repr__(self): return ( "<{} at {} socket={self.socket}, method={self.method}, " "timeout={self.timeout}>" ).format(self.__class__.__name__, hex(id(self)), self=self) # --------------------------------------------------------------------------- # # Exported symbols # --------------------------------------------------------------------------- # __all__ = [ "ModbusTcpClient", "ModbusUdpClient", "ModbusSerialClient" ] pymodbus-2.1.0/pymodbus/compat.py000066400000000000000000000063251335513467700170770ustar00rootroot00000000000000""" Python 2.x/3.x Compatibility Layer ------------------------------------------------- This is mostly based on the jinja2 compat code: Some py2/py3 compatibility support based on a stripped down version of six so we don't have to depend on a specific version of it. :copyright: Copyright 2013 by the Jinja team, see AUTHORS. :license: BSD, see LICENSE for details. """ import sys import six # --------------------------------------------------------------------------- # # python version checks # --------------------------------------------------------------------------- # PYTHON_VERSION = sys.version_info IS_PYTHON2 = six.PY2 IS_PYTHON3 = six.PY3 IS_PYPY = hasattr(sys, 'pypy_translation_info') IS_JYTHON = sys.platform.startswith('java') # --------------------------------------------------------------------------- # # python > 3.3 compatibility layer # --------------------------------------------------------------------------- # # ----------------------------------------------------------------------- # # portable builtins # ----------------------------------------------------------------------- # int2byte = six.int2byte unichr = six.unichr range_type = six.moves.range text_type = six.string_types string_types = six.string_types iterkeys = six.iterkeys itervalues = six.itervalues iteritems = six.iteritems get_next = six.next unicode_string = six.u NativeStringIO = six.StringIO ifilter = six.moves.filter imap = six.moves.map izip = six.moves.zip intern = six.moves.intern if not IS_PYTHON2: # ----------------------------------------------------------------------- # # module renames # ----------------------------------------------------------------------- # import socketserver # ----------------------------------------------------------------------- # # decorators # ----------------------------------------------------------------------- # implements_to_string = lambda x: x byte2int = lambda b: b if PYTHON_VERSION >= (3, 4): def is_installed(module): import importlib.util found = importlib.util.find_spec(module) return found else: def is_installed(module): import importlib found = importlib.find_loader(module) return found # --------------------------------------------------------------------------- # # python > 2.5 compatability layer # --------------------------------------------------------------------------- # else: byte2int = six.byte2int # ----------------------------------------------------------------------- # # module renames # ----------------------------------------------------------------------- # import SocketServer as socketserver # ----------------------------------------------------------------------- # # decorators # ----------------------------------------------------------------------- # def implements_to_string(klass): klass.__unicode__ = klass.__str__ klass.__str__ = lambda x: x.__unicode__().encode('utf-8') return klass def is_installed(module): import imp try: imp.find_module(module) return True except ImportError: return Falsepymodbus-2.1.0/pymodbus/constants.py000066400000000000000000000147471335513467700176370ustar00rootroot00000000000000''' Constants For Modbus Server/Client ---------------------------------- This is the single location for storing default values for the servers and clients. ''' from pymodbus.interfaces import Singleton 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 ReadSize = 1024 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 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 = '<' 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 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 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", ] pymodbus-2.1.0/pymodbus/datastore/000077500000000000000000000000001335513467700172225ustar00rootroot00000000000000pymodbus-2.1.0/pymodbus/datastore/__init__.py000066400000000000000000000010301335513467700213250ustar00rootroot00000000000000from pymodbus.datastore.store import ModbusSequentialDataBlock from pymodbus.datastore.store import ModbusSparseDataBlock from pymodbus.datastore.context import ModbusSlaveContext from pymodbus.datastore.context import ModbusServerContext #---------------------------------------------------------------------------# # Exported symbols #---------------------------------------------------------------------------# __all__ = [ "ModbusSequentialDataBlock", "ModbusSparseDataBlock", "ModbusSlaveContext", "ModbusServerContext", ] pymodbus-2.1.0/pymodbus/datastore/context.py000066400000000000000000000142001335513467700212550ustar00rootroot00000000000000from 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 #---------------------------------------------------------------------------# class ModbusSlaveContext(IModbusSlaveContext): ''' This creates a modbus data model with each data access stored in its own personal block ''' 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 = dict() 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) def __str__(self): ''' Returns a string representation of the context :returns: A string representation of the context ''' return "Modbus Slave Context" def reset(self): ''' Resets all the datastores to their default values ''' for datastore in itervalues(self.store): datastore.reset() 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) def getValues(self, fx, address, count=1): ''' Get `count` values from datastore :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) 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) 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. ''' 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} def __iter__(self): ''' Iterater over the current collection of slave contexts. :returns: An iterator over the slave contexts ''' return iteritems(self._slaves) 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 ''' if self.single and self._slaves: return True else: return slave in self._slaves 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)) 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)) 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)) def slaves(self): # Python3 now returns keys() as iterable return list(self._slaves.keys()) pymodbus-2.1.0/pymodbus/datastore/database/000077500000000000000000000000001335513467700207665ustar00rootroot00000000000000pymodbus-2.1.0/pymodbus/datastore/database/__init__.py000066400000000000000000000005631335513467700231030ustar00rootroot00000000000000from pymodbus.datastore.database.sql_datastore import SqlSlaveContext from pymodbus.datastore.database.redis_datastore import RedisSlaveContext #---------------------------------------------------------------------------# # Exported symbols #---------------------------------------------------------------------------# __all__ = ["SqlSlaveContext", "RedisSlaveContext"] pymodbus-2.1.0/pymodbus/datastore/database/redis_datastore.py000066400000000000000000000217251335513467700245230ustar00rootroot00000000000000import redis from pymodbus.interfaces import IModbusSlaveContext from pymodbus.utilities import pack_bitstring, unpack_bitstring #---------------------------------------------------------------------------# # Logging #---------------------------------------------------------------------------# import logging _logger = logging.getLogger(__name__) #---------------------------------------------------------------------------# # Context #---------------------------------------------------------------------------# class RedisSlaveContext(IModbusSlaveContext): ''' This is a modbus slave context using redis as a backing store. ''' def __init__(self, **kwargs): ''' Initializes the datastores :param host: The host to connect to :param port: The port to connect to :param prefix: A prefix for the keys ''' host = kwargs.get('host', 'localhost') port = kwargs.get('port', 6379) self.prefix = kwargs.get('prefix', 'pymodbus') self.client = kwargs.get('client', redis.Redis(host=host, port=port)) self._build_mapping() def __str__(self): ''' Returns a string representation of the context :returns: A string representation of the context ''' return "Redis Slave Context %s" % self.client def reset(self): ''' Resets all the datastores to their default values ''' self.client.flushall() 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 ''' address = address + 1 # section 4.4 of specification _logger.debug("validate[%d] %d:%d" % (fx, address, count)) return self._val_callbacks[self.decode(fx)](address, count) def getValues(self, fx, address, count=1): ''' Get `count` values from datastore :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 ''' address = address + 1 # section 4.4 of specification _logger.debug("getValues[%d] %d:%d" % (fx, address, count)) return self._get_callbacks[self.decode(fx)](address, count) 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 ''' address = address + 1 # section 4.4 of specification _logger.debug("setValues[%d] %d:%d" % (fx, address, len(values))) self._set_callbacks[self.decode(fx)](address, values) #--------------------------------------------------------------------------# # Redis Helper Methods #--------------------------------------------------------------------------# def _get_prefix(self, key): ''' This is a helper to abstract getting bit values :param key: The key prefix to use :returns: The key prefix to redis ''' return "%s:%s" % (self.prefix, key) def _build_mapping(self): ''' A quick helper method to build the function code mapper. ''' self._val_callbacks = { 'd': lambda o, c: self._val_bit('d', o, c), 'c': lambda o, c: self._val_bit('c', o, c), 'h': lambda o, c: self._val_reg('h', o, c), 'i': lambda o, c: self._val_reg('i', o, c), } self._get_callbacks = { 'd': lambda o, c: self._get_bit('d', o, c), 'c': lambda o, c: self._get_bit('c', o, c), 'h': lambda o, c: self._get_reg('h', o, c), 'i': lambda o, c: self._get_reg('i', o, c), } self._set_callbacks = { 'd': lambda o, v: self._set_bit('d', o, v), 'c': lambda o, v: self._set_bit('c', o, v), 'h': lambda o, v: self._set_reg('h', o, v), 'i': lambda o, v: self._set_reg('i', o, v), } #--------------------------------------------------------------------------# # Redis discrete implementation #--------------------------------------------------------------------------# _bit_size = 16 _bit_default = '\x00' * (_bit_size % 8) def _get_bit_values(self, key, offset, count): ''' This is a helper to abstract getting bit values :param key: The key prefix to use :param offset: The address offset to start at :param count: The number of bits to read ''' key = self._get_prefix(key) s = divmod(offset, self._bit_size)[0] e = divmod(offset + count, self._bit_size)[0] request = ('%s:%s' % (key, v) for v in range(s, e + 1)) response = self.client.mget(request) return response def _val_bit(self, key, offset, count): ''' Validates that the given range is currently set in redis. If any of the keys return None, then it is invalid. :param key: The key prefix to use :param offset: The address offset to start at :param count: The number of bits to read ''' response = self._get_bit_values(key, offset, count) return True if None not in response else False def _get_bit(self, key, offset, count): ''' :param key: The key prefix to use :param offset: The address offset to start at :param count: The number of bits to read ''' response = self._get_bit_values(key, offset, count) response = (r or self._bit_default for r in response) result = ''.join(response) result = unpack_bitstring(result) return result[offset:offset + count] def _set_bit(self, key, offset, values): ''' :param key: The key prefix to use :param offset: The address offset to start at :param values: The values to set ''' count = len(values) s = divmod(offset, self._bit_size)[0] e = divmod(offset + count, self._bit_size)[0] value = pack_bitstring(values) current = self._get_bit_values(key, offset, count) current = (r or self._bit_default for r in current) current = ''.join(current) current = current[0:offset] + value.decode('utf-8') + current[offset + count:] final = (current[s:s + self._bit_size] for s in range(0, count, self._bit_size)) key = self._get_prefix(key) request = ('%s:%s' % (key, v) for v in range(s, e + 1)) request = dict(zip(request, final)) self.client.mset(request) #--------------------------------------------------------------------------# # Redis register implementation #--------------------------------------------------------------------------# _reg_size = 16 _reg_default = '\x00' * (_reg_size % 8) def _get_reg_values(self, key, offset, count): ''' This is a helper to abstract getting register values :param key: The key prefix to use :param offset: The address offset to start at :param count: The number of bits to read ''' key = self._get_prefix(key) #s = divmod(offset, self.__reg_size)[0] #e = divmod(offset+count, self.__reg_size)[0] #request = ('%s:%s' % (key, v) for v in range(s, e + 1)) request = ('%s:%s' % (key, v) for v in range(offset, count + 1)) response = self.client.mget(request) return response def _val_reg(self, key, offset, count): ''' Validates that the given range is currently set in redis. If any of the keys return None, then it is invalid. :param key: The key prefix to use :param offset: The address offset to start at :param count: The number of bits to read ''' response = self._get_reg_values(key, offset, count) return None not in response def _get_reg(self, key, offset, count): ''' :param key: The key prefix to use :param offset: The address offset to start at :param count: The number of bits to read ''' response = self._get_reg_values(key, offset, count) response = [r or self._reg_default for r in response] return response[offset:offset + count] def _set_reg(self, key, offset, values): ''' :param key: The key prefix to use :param offset: The address offset to start at :param values: The values to set ''' count = len(values) #s = divmod(offset, self.__reg_size) #e = divmod(offset+count, self.__reg_size) #current = self.__get_reg_values(key, offset, count) key = self._get_prefix(key) request = ('%s:%s' % (key, v) for v in range(offset, count + 1)) request = dict(zip(request, values)) self.client.mset(request) pymodbus-2.1.0/pymodbus/datastore/database/sql_datastore.py000066400000000000000000000155431335513467700242150ustar00rootroot00000000000000import sqlalchemy import sqlalchemy.types as sqltypes from sqlalchemy.sql import and_ from sqlalchemy.schema import UniqueConstraint from sqlalchemy.sql.expression import bindparam from pymodbus.exceptions import NotImplementedException from pymodbus.interfaces import IModbusSlaveContext #---------------------------------------------------------------------------# # Logging #---------------------------------------------------------------------------# import logging _logger = logging.getLogger(__name__) #---------------------------------------------------------------------------# # Context #---------------------------------------------------------------------------# class SqlSlaveContext(IModbusSlaveContext): ''' This creates a modbus data model with each data access stored in its own personal block ''' def __init__(self, *args, **kwargs): ''' Initializes the datastores :param kwargs: Each element is a ModbusDataBlock ''' self.table = kwargs.get('table', 'pymodbus') self.database = kwargs.get('database', 'sqlite:///pymodbus.db') self._db_create(self.table, self.database) def __str__(self): ''' Returns a string representation of the context :returns: A string representation of the context ''' return "Modbus Slave Context" def reset(self): ''' Resets all the datastores to their default values ''' self._metadata.drop_all() self._db_create(self.table, self.database) 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 ''' address = address + 1 # section 4.4 of specification _logger.debug("validate[%d] %d:%d" % (fx, address, count)) return self._validate(self.decode(fx), address, count) def getValues(self, fx, address, count=1): ''' Get `count` values from datastore :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 ''' address = address + 1 # section 4.4 of specification _logger.debug("get-values[%d] %d:%d" % (fx, address, count)) return self._get(self.decode(fx), address, count) 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 ''' address = address + 1 # section 4.4 of specification _logger.debug("set-values[%d] %d:%d" % (fx, address, len(values))) self._set(self.decode(fx), address, values) #--------------------------------------------------------------------------# # Sqlite Helper Methods #--------------------------------------------------------------------------# def _db_create(self, table, database): ''' A helper method to initialize the database and handles :param table: The table name to create :param database: The database uri to use ''' self._engine = sqlalchemy.create_engine(database, echo=False) self._metadata = sqlalchemy.MetaData(self._engine) self._table = sqlalchemy.Table(table, self._metadata, sqlalchemy.Column('type', sqltypes.String(1)), sqlalchemy.Column('index', sqltypes.Integer), sqlalchemy.Column('value', sqltypes.Integer), UniqueConstraint('type', 'index', name='key')) self._table.create(checkfirst=True) self._connection = self._engine.connect() def _get(self, type, offset, count): ''' :param type: The key prefix to use :param offset: The address offset to start at :param count: The number of bits to read :returns: The resulting values ''' query = self._table.select(and_( self._table.c.type == type, self._table.c.index >= offset, self._table.c.index <= offset + count) ) query = query.order_by(self._table.c.index.asc()) result = self._connection.execute(query).fetchall() return [row.value for row in result] def _build_set(self, type, offset, values, prefix=''): ''' A helper method to generate the sql update context :param type: The key prefix to use :param offset: The address offset to start at :param values: The values to set :param prefix: Prefix fields index and type, defaults to empty string ''' result = [] for index, value in enumerate(values): result.append({ prefix + 'type': type, prefix + 'index': offset + index, 'value': value }) return result def _check(self, type, offset, values): result = self._get(type, offset, count=1) return False if len(result) > 0 else True def _set(self, type, offset, values): ''' :param key: The type prefix to use :param offset: The address offset to start at :param values: The values to set ''' if self._check(type, offset, values): context = self._build_set(type, offset, values) query = self._table.insert() result = self._connection.execute(query, context) return result.rowcount == len(values) else: return False def _update(self, type, offset, values): ''' :param type: The type prefix to use :param offset: The address offset to start at :param values: The values to set ''' context = self._build_set(type, offset, values, prefix='x_') query = self._table.update().values(name='value') query = query.where(and_( self._table.c.type == bindparam('x_type'), self._table.c.index == bindparam('x_index'))) result = self._connection.execute(query, context) return result.rowcount == len(values) def _validate(self, type, offset, count): ''' :param key: The key prefix to use :param offset: The address offset to start at :param count: The number of bits to read :returns: The result of the validation ''' query = self._table.select(and_( self._table.c.type == type, self._table.c.index >= offset, self._table.c.index <= offset + count)) result = self._connection.execute(query) return result.rowcount == count pymodbus-2.1.0/pymodbus/datastore/remote.py000066400000000000000000000077641335513467700211050ustar00rootroot00000000000000from pymodbus.exceptions import NotImplementedException from pymodbus.interfaces import IModbusSlaveContext #---------------------------------------------------------------------------# # Logging #---------------------------------------------------------------------------# import logging _logger = logging.getLogger(__name__) #---------------------------------------------------------------------------# # Context #---------------------------------------------------------------------------# class RemoteSlaveContext(IModbusSlaveContext): ''' TODO This creates a modbus data model that connects to a remote device (depending on the client used) ''' def __init__(self, client, unit=None): ''' Initializes the datastores :param client: The client to retrieve values with :param unit: Unit ID of the remote slave ''' self._client = client self.unit = unit self.__build_mapping() def reset(self): ''' Resets all the datastores to their default values ''' raise NotImplementedException() 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 not result.isError() def getValues(self, fx, address, count=1): ''' Get `count` values from datastore :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) 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) 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. ''' kwargs = {} if self.unit: kwargs["unit"] = self.unit self.__get_callbacks = { 'd': lambda a, c: self._client.read_discrete_inputs(a, c, **kwargs), 'c': lambda a, c: self._client.read_coils(a, c, **kwargs), 'h': lambda a, c: self._client.read_holding_registers(a, c, **kwargs), 'i': lambda a, c: self._client.read_input_registers(a, c, **kwargs), } self.__set_callbacks = { 'd': lambda a, v: self._client.write_coils(a, v, **kwargs), 'c': lambda a, v: self._client.write_coils(a, v, **kwargs), 'h': lambda a, v: self._client.write_registers(a, v, **kwargs), 'i': lambda a, v: self._client.write_registers(a, v, **kwargs), } def __extract_result(self, fx, result): ''' A helper method to extract the values out of a response. TODO make this consistent (values?) ''' if not result.isError(): if fx in ['d', 'c']: return result.bits if fx in ['h', 'i']: return result.registers else: return result pymodbus-2.1.0/pymodbus/datastore/store.py000066400000000000000000000211341335513467700207310ustar00rootroot00000000000000""" 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 #---------------------------------------------------------------------------# 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) ''' 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 def reset(self): ''' Resets the datastore to the initialized default value ''' self.values = [self.default_value] * len(self.values) 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") 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") 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") 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) 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) class ModbusSequentialDataBlock(BaseModbusDataBlock): ''' Creates a sequential modbus datastore ''' 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 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) 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 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] 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 class ModbusSparseDataBlock(BaseModbusDataBlock): ''' Creates a sparse modbus datastore ''' 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 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) 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))) 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)] 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 pymodbus-2.1.0/pymodbus/device.py000066400000000000000000000553131335513467700170540ustar00rootroot00000000000000""" 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 #---------------------------------------------------------------------------# 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", ] def __iter__(self): ''' Iterater over the network access table :returns: An iterator of the network access table ''' return self.__nmstable.__iter__() def __contains__(self, host): ''' Check if a host is allowed to access resources :param host: The host to check ''' return host in self.__nmstable 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) 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) 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 #---------------------------------------------------------------------------# 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 }) def __init__(self): ''' Initialize the modbus plus statistics with the default information. ''' self.reset() def __iter__(self): ''' Iterater over the statistics :returns: An iterator of the modbus plus statistics ''' return iteritems(self.__data) def reset(self): ''' This clears all of the modbus plus statistics ''' for key in self.__data: self.__data[key] = [0x00] * len(self.__data[key]) def summary(self): ''' Returns a summary of the modbus plus statistics :returns: 54 16-bit words representing the status ''' return itervalues(self.__data) 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 #---------------------------------------------------------------------------# 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', ] 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 (0xFF >= key >= 0x80): self.__data[key] = info[key] def __iter__(self): ''' Iterater over the device information :returns: An iterator of the device information ''' return iteritems(self.__data) 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))) 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) 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 def __getitem__(self, key): ''' Wrapper used to access the device information :param key: The register to read ''' return self.__data.setdefault(key, '') 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) 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(i, 0x03))), DeviceInformation.Regular: lambda c, r, i: c.__gets(r, list(range(i, 0x07)) if c.__get(r, i)[i] else list(range(0, 0x07))), DeviceInformation.Extended: lambda c, r, i: c.__gets(r, [x for x in range(i, 0x100) if x not in range(0x07, 0x80)] if c.__get(r, i)[i] else [x for x in range(0, 0x100) if x not in range(0x07, 0x80)]), DeviceInformation.Specific: lambda c, r, i: c.__get(r, i), } @classmethod 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 if identity[oid]) #---------------------------------------------------------------------------# # 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 control block #---------------------------------------------------------------------------# 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 #-------------------------------------------------------------------------# def __str__(self): ''' Build a representation of the control block :returns: A string representation of the control block ''' return "ModbusControl" def __iter__(self): ''' Iterater over the device counters :returns: An iterator of the device counters ''' return self.__counters.__iter__() #-------------------------------------------------------------------------# # Events #-------------------------------------------------------------------------# 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 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) 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) 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 #-------------------------------------------------------------------------# 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 #-------------------------------------------------------------------------# 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 #-------------------------------------------------------------------------# 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 #-------------------------------------------------------------------------# 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) 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 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" ] pymodbus-2.1.0/pymodbus/diag_message.py000066400000000000000000000710551335513467700202260ustar00rootroot00000000000000''' 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 #---------------------------------------------------------------------------# class DiagnosticStatusRequest(ModbusRequest): ''' This is a base class for all of the diagnostic request functions ''' function_code = 0x08 _rtu_frame_size = 8 def __init__(self, **kwargs): ''' Base initializer for a diagnostic request ''' ModbusRequest.__init__(self, **kwargs) self.message = None 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 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) 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) 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 def __init__(self, **kwargs): ''' Base initializer for a diagnostic response ''' ModbusResponse.__init__(self, **kwargs) self.message = None 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 def decode(self, data): ''' Base decoder for a diagnostic response :param data: The data to decode into the function code ''' word_len = len(data)//2 if len(data) % 2: word_len += 1 data = struct.unpack('>' + 'H'*word_len, data) self.sub_function_code, self.message = data[0], data[1:] 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 ''' 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 def execute(self, *args): ''' Base function to raise if not implemented ''' raise NotImplementedException("Diagnostic Message Has No Execute Method") 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. ''' 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 #---------------------------------------------------------------------------# 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 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] def execute(self, *args): ''' Executes the loopback request (builds the response) :returns: The populated loopback response message ''' return ReturnQueryDataResponse(self.message) 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 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 #---------------------------------------------------------------------------# 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 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] def execute(self, *args): ''' Clear event log and restart :returns: The initialized response message ''' #if _MCB.ListenOnly: return RestartCommunicationsOptionResponse(self.message) 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 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 #---------------------------------------------------------------------------# class ReturnDiagnosticRegisterRequest(DiagnosticStatusSimpleRequest): ''' The contents of the remote device's 16-bit diagnostic register are returned in the response ''' sub_function_code = 0x0002 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) 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 #---------------------------------------------------------------------------# 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 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) 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 #---------------------------------------------------------------------------# 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 def execute(self, *args): ''' Execute the diagnostic request on the given device :returns: The initialized response message ''' _MCB.ListenOnly = True return ForceListenOnlyModeResponse() 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 def __init__(self, **kwargs): ''' Initializer to block a return response ''' DiagnosticStatusResponse.__init__(self, **kwargs) self.message = [] #---------------------------------------------------------------------------# # Diagnostic Sub Code 10 #---------------------------------------------------------------------------# 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 def execute(self, *args): ''' Execute the diagnostic request on the given device :returns: The initialized response message ''' _MCB.reset() return ClearCountersResponse(self.message) 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 #---------------------------------------------------------------------------# 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 def execute(self, *args): ''' Execute the diagnostic request on the given device :returns: The initialized response message ''' count = _MCB.Counter.BusMessage return ReturnBusMessageCountResponse(count) 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 #---------------------------------------------------------------------------# 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 def execute(self, *args): ''' Execute the diagnostic request on the given device :returns: The initialized response message ''' count = _MCB.Counter.BusCommunicationError return ReturnBusCommunicationErrorCountResponse(count) 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 #---------------------------------------------------------------------------# 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 def execute(self, *args): ''' Execute the diagnostic request on the given device :returns: The initialized response message ''' count = _MCB.Counter.BusExceptionError return ReturnBusExceptionErrorCountResponse(count) 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 #---------------------------------------------------------------------------# 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 def execute(self, *args): ''' Execute the diagnostic request on the given device :returns: The initialized response message ''' count = _MCB.Counter.SlaveMessage return ReturnSlaveMessageCountResponse(count) 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 #---------------------------------------------------------------------------# 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 def execute(self, *args): ''' Execute the diagnostic request on the given device :returns: The initialized response message ''' count = _MCB.Counter.SlaveNoResponse return ReturnSlaveNoReponseCountResponse(count) 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 #---------------------------------------------------------------------------# 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 def execute(self, *args): ''' Execute the diagnostic request on the given device :returns: The initialized response message ''' count = _MCB.Counter.SlaveNAK return ReturnSlaveNAKCountResponse(count) 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 #---------------------------------------------------------------------------# 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 def execute(self, *args): ''' Execute the diagnostic request on the given device :returns: The initialized response message ''' count = _MCB.Counter.SlaveBusy return ReturnSlaveBusyCountResponse(count) 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 #---------------------------------------------------------------------------# 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 def execute(self, *args): ''' Execute the diagnostic request on the given device :returns: The initialized response message ''' count = _MCB.Counter.BusCharacterOverrun return ReturnSlaveBusCharacterOverrunCountResponse(count) 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 #---------------------------------------------------------------------------# 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 def execute(self, *args): ''' Execute the diagnostic request on the given device :returns: The initialized response message ''' count = _MCB.Counter.BusCharacterOverrun return ReturnIopOverrunCountResponse(count) 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 #---------------------------------------------------------------------------# 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 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) class ClearOverrunCountResponse(DiagnosticStatusSimpleResponse): ''' Clears the overrun error counter and reset the error flag ''' sub_function_code = 0x0014 #---------------------------------------------------------------------------# # Diagnostic Sub Code 21 #---------------------------------------------------------------------------# 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 def __init__(self, **kwargs): super(GetClearModbusPlusRequest, self).__init__(**kwargs) def get_response_pdu_size(self): """ 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. Func_code (1 byte) + Sub function code (2 byte) + Operation (2 byte) + Data (108 bytes) :return: """ if self.message == ModbusPlusOperation.GetStatistics: data = 2 + 108 # byte count(2) + data (54*2) else: data = 0 return 1 + 2 + 2 + 2+ data 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() message = self.message else: message = [self.message] message += _MCB.Plus.encode() return GetClearModbusPlusResponse(message) 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) packet += struct.pack('>H', self.message) return packet 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", ] pymodbus-2.1.0/pymodbus/events.py000066400000000000000000000143561335513467700171230ustar00rootroot00000000000000''' 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 class ModbusEvent(object): def encode(self): ''' Encodes the status bits to an event message :returns: The encoded event message ''' raise NotImplementedException() def decode(self, event): ''' Decodes the event message to its status bits :param event: The event to decode ''' raise NotImplementedException() 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 ''' 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) 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 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] 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 ''' 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) 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 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] 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' def encode(self): ''' Encodes the status bits to an event message :returns: The encoded event message ''' return self.__encoded 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') 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' def encode(self): ''' Encodes the status bits to an event message :returns: The encoded event message ''' return self.__encoded 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') pymodbus-2.1.0/pymodbus/exceptions.py000066400000000000000000000057041335513467700177750ustar00rootroot00000000000000''' Pymodbus Exceptions -------------------- Custom exceptions to be used in the Modbus code. ''' class ModbusException(Exception): ''' Base modbus exception ''' 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 def isError(self): """Error""" return True class ModbusIOException(ModbusException): ''' Error resulting from data i/o ''' def __init__(self, string="", function_code=None): ''' Initialize the exception :param string: The message to append to the error ''' self.fcode = function_code self.message = "[Input/Output] %s" % string ModbusException.__init__(self, self.message) class ParameterException(ModbusException): ''' Error resulting from invalid parameter ''' 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) class NotImplementedException(ModbusException): ''' Error resulting from not implemented function ''' 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) class InvalidMessageReceivedException(ModbusException): """ Error resulting from invalid response received or decoded """ def __init__(self, string=""): ''' Initialize the exception :param string: The message to append to the error ''' message = "[Invalid Message] %s" % string ModbusException.__init__(self, message) #---------------------------------------------------------------------------# # Exported symbols #---------------------------------------------------------------------------# __all__ = [ "ModbusException", "ModbusIOException", "ParameterException", "NotImplementedException", "ConnectionException", "NoSuchSlaveException", "InvalidMessageReceivedException" ] pymodbus-2.1.0/pymodbus/factory.py000066400000000000000000000233131335513467700172570ustar00rootroot00000000000000""" 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 #---------------------------------------------------------------------------# 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, ] 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 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 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) 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 #---------------------------------------------------------------------------# 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, ] 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 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) 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) except Exception as ex: _logger.error(ex) return None 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 ''' fc_string = function_code = byte2int(data[0]) if function_code in self.__lookup: fc_string = "%s: %s" % ( str(self.__lookup[function_code]).split('.')[-1].rstrip("'>"), function_code ) _logger.debug("Factory Response[%s]" % fc_string) 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'] pymodbus-2.1.0/pymodbus/file_message.py000066400000000000000000000336351335513467700202430ustar00rootroot00000000000000''' 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 #---------------------------------------------------------------------------# class FileRecord(object): ''' Represents a file record and its relevant data. ''' 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) 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 def __ne__(self, relf): ''' Compares the left object to the right ''' return not self.__eq__(relf) 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 #---------------------------------------------------------------------------# 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 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 [] 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 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) 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) 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 def __init__(self, records=None, **kwargs): ''' Initializes a new instance :param records: The requested file records ''' ModbusResponse.__init__(self, **kwargs) self.records = records or [] 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 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) 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 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 [] 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 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) 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) class WriteFileRecordResponse(ModbusResponse): ''' The normal response is an echo of the request. ''' function_code = 0x15 _rtu_byte_count_pos = 2 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 [] 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 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 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 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 def encode(self): ''' Encodes the request packet :returns: The byte encoded packet ''' return struct.pack('>H', self.address) def decode(self, data): ''' Decodes the incoming request :param data: The data to decode into the address ''' self.address = struct.unpack('>H', data)[0] 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) 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 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 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 [] 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 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", "ReadFifoQueueRequest", "ReadFifoQueueResponse", ] pymodbus-2.1.0/pymodbus/framer/000077500000000000000000000000001335513467700165105ustar00rootroot00000000000000pymodbus-2.1.0/pymodbus/framer/__init__.py000066400000000000000000000025031335513467700206210ustar00rootroot00000000000000from pymodbus.interfaces import IModbusFramer import struct # Unit ID, Function Code BYTE_ORDER = '>' FRAME_HEADER = 'BB' # Transaction Id, Protocol ID, Length, Unit ID, Function Code SOCKET_FRAME_HEADER = BYTE_ORDER + 'HHH' + FRAME_HEADER class ModbusFramer(IModbusFramer): """ Base Framer class """ def _validate_unit_id(self, units, single): """ Validates if the received data is valid for the client :param units: list of unit id for which the transaction is valid :param single: Set to true to treat this as a single context :return: """ if single: return True else: if 0 in units or 0xFF in units: # Handle Modbus TCP unit identifier (0x00 0r 0xFF) # in async requests return True return self._header['uid'] in units def sendPacket(self, message): """ Sends packets on the bus with 3.5char delay between frames :param message: Message to be sent over the bus :return: """ return self.client.send(message) def recvPacket(self, size): """ Receives packet from the bus with specified len :param size: Number of bytes to read :return: """ return self.client.recv(size) pymodbus-2.1.0/pymodbus/framer/ascii_framer.py000066400000000000000000000170371335513467700215160ustar00rootroot00000000000000import struct import socket from binascii import b2a_hex, a2b_hex from pymodbus.exceptions import ModbusIOException from pymodbus.utilities import checkLRC, computeLRC from pymodbus.framer import ModbusFramer, FRAME_HEADER, BYTE_ORDER # Python 2 compatibility. try: TimeoutError except NameError: TimeoutError = socket.timeout ASCII_FRAME_HEADER = BYTE_ORDER + FRAME_HEADER # --------------------------------------------------------------------------- # # Logging # --------------------------------------------------------------------------- # import logging _logger = logging.getLogger(__name__) # --------------------------------------------------------------------------- # # Modbus ASCII Message # --------------------------------------------------------------------------- # class ModbusAsciiFramer(ModbusFramer): """ 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. """ def __init__(self, decoder, client=None): """ 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 self.client = client # ----------------------------------------------------------------------- # # Private Helper Functions # ----------------------------------------------------------------------- # def decode_data(self, data): if len(data) > 1: uid = int(data[1:3], 16) fcode = int(data[3:5], 16) return dict(unit=uid, fcode=fcode) return dict() 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 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} 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 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 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'' 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 = {'lrc': '0000', 'len': 0, 'uid': 0x00} 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 # ----------------------------------------------------------------------- # def processIncomingPacket(self, data, callback, unit, **kwargs): """ 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 :param unit: Process if unit id matches, ignore otherwise (could be a \ list of unit ids (server) or single unit id(client/server)) :param single: True or False (If True, ignore unit address validation) """ if not isinstance(unit, (list, tuple)): unit = [unit] single = kwargs.get('single', False) self.addToFrame(data) while self.isFrameReady(): if self.checkFrame(): if self._validate_unit_id(unit, single): frame = self.getFrame() result = self.decoder.decode(frame) if result is None: raise ModbusIOException("Unable to decode response") self.populateResult(result) self.advanceFrame() callback(result) # defer this else: _logger.error("Not a valid unit id - {}, " "ignoring!!".format(self._header['uid'])) self.resetFrame() else: break 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(ASCII_FRAME_HEADER, 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() # __END__ pymodbus-2.1.0/pymodbus/framer/binary_framer.py000066400000000000000000000201301335513467700216760ustar00rootroot00000000000000import struct from pymodbus.exceptions import ModbusIOException from pymodbus.utilities import checkCRC, computeCRC from pymodbus.framer import ModbusFramer, FRAME_HEADER, BYTE_ORDER # --------------------------------------------------------------------------- # # Logging # --------------------------------------------------------------------------- # import logging _logger = logging.getLogger(__name__) BINARY_FRAME_HEADER = BYTE_ORDER + FRAME_HEADER # --------------------------------------------------------------------------- # # Modbus Binary Message # --------------------------------------------------------------------------- # class ModbusBinaryFramer(ModbusFramer): """ 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. """ def __init__(self, decoder, client=None): """ 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 = 0x01 self._start = b'\x7b' # { self._end = b'\x7d' # } self._repeat = [b'}'[0], b'{'[0]] # python3 hack self.decoder = decoder self.client = client # ----------------------------------------------------------------------- # # Private Helper Functions # ----------------------------------------------------------------------- # def decode_data(self, data): if len(data) > self._hsize: uid = struct.unpack('>B', data[1:2])[0] fcode = struct.unpack('>B', data[2:3])[0] return dict(unit=uid, fcode=fcode) return dict() 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])[0] 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 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} 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 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 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'' 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 # ----------------------------------------------------------------------- # def processIncomingPacket(self, data, callback, unit, **kwargs): """ 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 :param unit: Process if unit id matches, ignore otherwise (could be a \ list of unit ids (server) or single unit id(client/server) :param single: True or False (If True, ignore unit address validation) """ self.addToFrame(data) if not isinstance(unit, (list, tuple)): unit = [unit] single = kwargs.get('single', False) while self.isFrameReady(): if self.checkFrame(): if self._validate_unit_id(unit, single): 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: _logger.debug("Not a valid unit id - {}, " "ignoring!!".format(self._header['uid'])) self.resetFrame() break else: _logger.debug("Frame check failed, ignoring!!") self.resetFrame() break 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(BINARY_FRAME_HEADER, message.unit_id, message.function_code) + data packet += struct.pack(">H", computeCRC(packet)) packet = self._start + packet + self._end return packet 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) 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 = {'crc': 0x0000, 'len': 0, 'uid': 0x00} # __END__ pymodbus-2.1.0/pymodbus/framer/rtu_framer.py000066400000000000000000000271471335513467700212430ustar00rootroot00000000000000import struct import time from pymodbus.exceptions import ModbusIOException from pymodbus.exceptions import InvalidMessageReceivedException from pymodbus.utilities import checkCRC, computeCRC from pymodbus.utilities import hexlify_packets, ModbusTransactionState from pymodbus.compat import byte2int from pymodbus.framer import ModbusFramer, FRAME_HEADER, BYTE_ORDER # --------------------------------------------------------------------------- # # Logging # --------------------------------------------------------------------------- # import logging _logger = logging.getLogger(__name__) RTU_FRAME_HEADER = BYTE_ORDER + FRAME_HEADER # --------------------------------------------------------------------------- # # Modbus RTU Message # --------------------------------------------------------------------------- # class ModbusRtuFramer(ModbusFramer): """ 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 transmit at least x many characters. In this case it is 3.5 characters. Also, if we receive 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 """ def __init__(self, decoder, client=None): """ Initializes a new instance of the framer :param decoder: The decoder factory implementation to use """ self._buffer = b'' self._header = {'uid': 0x00, 'len': 0, 'crc': '0000'} self._hsize = 0x01 self._end = b'\x0d\x0a' self._min_frame_size = 4 self.decoder = decoder self.client = client # ----------------------------------------------------------------------- # # Private Helper Functions # ----------------------------------------------------------------------- # def decode_data(self, data): if len(data) > self._hsize: uid = byte2int(data[0]) fcode = byte2int(data[1]) return dict(unit=uid, fcode=fcode) return dict() def checkFrame(self): """ Check if the next frame is available. Return True if we were successful. 1. Populate header 2. Discard frame if UID does not match """ 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, struct.error): return False 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() _logger.debug("Frame advanced, resetting header!!") self._header = {} def resetFrame(self): """ Reset the entire message frame. This allows us to skip over 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). """ _logger.debug("Resetting frame - Current Frame in " "buffer - {}".format(hexlify_packets(self._buffer))) self._buffer = b'' self._header = {} 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 def populateHeader(self, data=None): """ 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. """ data = data if data else self._buffer self._header['uid'] = byte2int(data[0]) func_code = byte2int(data[1]) pdu_class = self.decoder.lookupPduClass(func_code) size = pdu_class.calculateRtuFrameSize(data) self._header['len'] = size self._header['crc'] = data[size - 2:size] 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 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: _logger.debug("Getting Frame - {}".format(hexlify_packets(buffer))) return buffer return b'' 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 # ----------------------------------------------------------------------- # def processIncomingPacket(self, data, callback, unit, **kwargs): """ 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 :param unit: Process if unit id matches, ignore otherwise (could be a \ list of unit ids (server) or single unit id(client/server) :param single: True or False (If True, ignore unit address validation) """ if not isinstance(unit, (list, tuple)): unit = [unit] self.addToFrame(data) single = kwargs.get("single", False) if self.isFrameReady(): if self.checkFrame(): if self._validate_unit_id(unit, single): self._process(callback) else: _logger.debug("Not a valid unit id - {}, " "ignoring!!".format(self._header['uid'])) self.resetFrame() else: _logger.debug("Frame - [{}] not ready".format(data)) 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(RTU_FRAME_HEADER, message.unit_id, message.function_code) + data packet += struct.pack(">H", computeCRC(packet)) return packet def sendPacket(self, message): """ Sends packets on the bus with 3.5char delay between frames :param message: Message to be sent over the bus :return: """ start = time.time() timeout = start + self.client.timeout while self.client.state != ModbusTransactionState.IDLE: if self.client.state == ModbusTransactionState.TRANSACTION_COMPLETE: ts = round(time.time(), 6) _logger.debug("Changing state to IDLE - Last Frame End - {}, " "Current Time stamp - {}".format( self.client.last_frame_end, ts) ) if self.client.last_frame_end: idle_time = self.client.idle_time() if round(ts - idle_time, 6) <= self.client.silent_interval: _logger.debug("Waiting for 3.5 char before next " "send - {} ms".format( self.client.silent_interval * 1000) ) time.sleep(self.client.silent_interval) else: # Recovering from last error ?? time.sleep(self.client.silent_interval) self.client.state = ModbusTransactionState.IDLE else: if time.time() > timeout: _logger.debug("Spent more time than the read time out, " "resetting the transaction to IDLE") self.client.state = ModbusTransactionState.IDLE else: _logger.debug("Sleeping") time.sleep(self.client.silent_interval) size = self.client.send(message) self.client.last_frame_end = round(time.time(), 6) return size def recvPacket(self, size): """ Receives packet from the bus with specified len :param size: Number of bytes to read :return: """ result = self.client.recv(size) self.client.last_frame_end = round(time.time(), 6) return result 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") elif error and result.function_code < 0x80: raise InvalidMessageReceivedException(result) else: self.populateResult(result) self.advanceFrame() callback(result) # defer or push to a thread? def getRawFrame(self): """ Returns the complete buffer """ _logger.debug("Getting Raw Frame - " "{}".format(hexlify_packets(self._buffer))) return self._buffer # __END__ pymodbus-2.1.0/pymodbus/framer/socket_framer.py000066400000000000000000000202311335513467700217040ustar00rootroot00000000000000import struct from pymodbus.exceptions import ModbusIOException from pymodbus.exceptions import InvalidMessageReceivedException from pymodbus.utilities import hexlify_packets from pymodbus.framer import ModbusFramer, SOCKET_FRAME_HEADER # --------------------------------------------------------------------------- # # Logging # --------------------------------------------------------------------------- # import logging _logger = logging.getLogger(__name__) # --------------------------------------------------------------------------- # # Modbus TCP Message # --------------------------------------------------------------------------- # class ModbusSocketFramer(ModbusFramer): """ 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 """ def __init__(self, decoder, client=None): """ 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 self.client = client # ----------------------------------------------------------------------- # # Private Helper Functions # ----------------------------------------------------------------------- # def checkFrame(self): """ Check and decode the next frame Return true if we were successful """ if self.isFrameReady(): (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 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} 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 def addToFrame(self, message): """ Adds new packet data to the current frame buffer :param message: The most recent packet """ self._buffer += message 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] 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 # ----------------------------------------------------------------------- # def decode_data(self, data): if len(data) > self._hsize: tid, pid, length, uid, fcode = struct.unpack(SOCKET_FRAME_HEADER, data[0:self._hsize+1]) return dict(tid=tid, pid=pid, lenght=length, unit=uid, fcode=fcode) return dict() def processIncomingPacket(self, data, callback, unit, **kwargs): """ 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 :param unit: Process if unit id matches, ignore otherwise (could be a \ list of unit ids (server) or single unit id(client/server) :param single: True or False (If True, ignore unit address validation) :return: """ if not isinstance(unit, (list, tuple)): unit = [unit] single = kwargs.get("single", False) _logger.debug("Processing: " + hexlify_packets(data)) self.addToFrame(data) while True: if self.isFrameReady(): if self.checkFrame(): if self._validate_unit_id(unit, single): self._process(callback) else: _logger.debug("Not a valid unit id - {}, " "ignoring!!".format(self._header['uid'])) self.resetFrame() else: _logger.debug("Frame check failed, ignoring!!") self.resetFrame() else: if len(self._buffer): # Possible error ??? if self._header['len'] < 2: self._process(callback, error=True) break 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") elif error and result.function_code < 0x80: raise InvalidMessageReceivedException(result) else: self.populateResult(result) self.advanceFrame() callback(result) # defer or push to a thread? 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 = {'tid': 0, 'pid': 0, 'len': 0, 'uid': 0} def getRawFrame(self): """ Returns the complete buffer """ return self._buffer 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(SOCKET_FRAME_HEADER, message.transaction_id, message.protocol_id, len(data) + 2, message.unit_id, message.function_code) packet += data return packet # __END__ pymodbus-2.1.0/pymodbus/interfaces.py000066400000000000000000000174651335513467700177460ustar00rootroot00000000000000''' Pymodbus Interfaces --------------------- A collection of base classes that are used throughout the pymodbus library. ''' from pymodbus.exceptions import NotImplementedException #---------------------------------------------------------------------------# # Generic #---------------------------------------------------------------------------# class Singleton(object): ''' Singleton base class http://mail.python.org/pipermail/python-list/2007-July/450681.html ''' 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 #---------------------------------------------------------------------------# 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. ''' 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") 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") 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). ''' def checkFrame(self): ''' Check and decode the next frame :returns: True if we successful, False otherwise ''' raise NotImplementedException( "Method not implemented by derived class") 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") 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") 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") def getFrame(self): ''' Get the next frame from the buffer :returns: The frame data or '' ''' raise NotImplementedException( "Method not implemented by derived class") 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") 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") 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") 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]]) 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] def reset(self): ''' Resets all the datastores to their default values ''' raise NotImplementedException("Context Reset") 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") def getValues(self, fx, address, count=1): ''' Get `count` values from datastore :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") 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") 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). ''' 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', ] pymodbus-2.1.0/pymodbus/internal/000077500000000000000000000000001335513467700170505ustar00rootroot00000000000000pymodbus-2.1.0/pymodbus/internal/__init__.py000066400000000000000000000000001335513467700211470ustar00rootroot00000000000000pymodbus-2.1.0/pymodbus/internal/ptwisted.py000066400000000000000000000027201335513467700212660ustar00rootroot00000000000000''' A collection of twisted utility code ''' from pymodbus.compat import IS_PYTHON2, IS_PYTHON3 if IS_PYTHON2: from twisted.cred import portal, checkers from twisted.conch import manhole, manhole_ssh from twisted.conch.insults import insults #---------------------------------------------------------------------------# # Logging #---------------------------------------------------------------------------# import logging _logger = logging.getLogger(__name__) #---------------------------------------------------------------------------# # Twisted Helper Methods #---------------------------------------------------------------------------# def InstallManagementConsole(namespace, users={'admin': 'admin'}, port=503): ''' Helper method to start an ssh management console for the modbus server. :param namespace: The data to constrain the server to :param users: The users to login with :param port: The port to host the server on ''' if IS_PYTHON3: raise NotImplemented("This code currently doesn't work on python3") from twisted.internet import reactor def build_protocol(): p = insults.ServerProtocol(manhole.ColoredManhole, namespace) return p r = manhole_ssh.TerminalRealm() r.chainedProtocolFactory = build_protocol c = checkers.InMemoryUsernamePasswordDatabaseDontUse(**users) p = portal.Portal(r, [c]) factory = manhole_ssh.ConchFactory(p) reactor.listenTCP(port, factory) pymodbus-2.1.0/pymodbus/mei_message.py000066400000000000000000000174031335513467700200710ustar00rootroot00000000000000''' 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, IS_PYTHON3 _MCB = ModbusControlBlock() class _OutOfSpaceException(Exception): # This exception exists here as a simple, local way to manage response # length control for the only MODBUS command which requires it under # standard, non-error conditions. It and the structures associated with # it should ideally be refactored and applied to all responses, however, # since a Client can make requests which result in disallowed conditions, # such as, for instance, requesting a register read of more registers # than will fit in a single PDU. As per the specification, the PDU is # restricted to 253 bytes, irrespective of the transport used. # # See Page 5/50 of MODBUS Application Protocol Specification V1.1b3. def __init__(self, oid): self.oid = oid #---------------------------------------------------------------------------# # Read Device Information #---------------------------------------------------------------------------# 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 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 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 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 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) 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 class ReadDeviceInformationResponse(ModbusResponse): ''' ''' function_code = 0x2b sub_function_code = 0x0e @classmethod 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 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 = 0 self.conformity = 0x83 # I support everything right now self.next_object_id = 0x00 self.more_follows = MoreData.Nothing self.space_left = None def _encode_object(self, object_id, data): self.space_left -= (2 + len(data)) if self.space_left <= 0: raise _OutOfSpaceException(object_id) encoded_obj = struct.pack('>BB', object_id, len(data)) if IS_PYTHON3: if isinstance(data, bytes): encoded_obj += data else: encoded_obj += data.encode() else: encoded_obj += data.encode() self.number_of_objects += 1 return encoded_obj def encode(self): ''' Encodes the response :returns: The byte encoded message ''' packet = struct.pack('>BBB', self.sub_function_code, self.read_code, self.conformity) self.space_left = 253 - 6 objects = b'' try: for (object_id, data) in iteritems(self.information): if isinstance(data, list): for item in data: objects += self._encode_object(object_id, item) else: objects += self._encode_object(object_id, data) except _OutOfSpaceException as e: self.next_object_id = e.oid self.more_follows = MoreData.KeepReading packet += struct.pack('>BBB', self.more_follows, self.next_object_id, self.number_of_objects) packet += objects return packet 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 if object_id not in self.information.keys(): self.information[object_id] = data[count-object_length:count] else: if isinstance(self.information[object_id], list): self.information[object_id].append(data[count-object_length:count]) else: self.information[object_id] = [self.information[object_id], data[count - object_length:count]] 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", ] pymodbus-2.1.0/pymodbus/other_message.py000066400000000000000000000344031335513467700204370ustar00rootroot00000000000000''' 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 #---------------------------------------------------------------------------# 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 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, context=None): ''' Run a read exeception status request against the store :returns: The populated response ''' status = _MCB.Counter.summary() return ReadExceptionStatusResponse(status) def __str__(self): ''' Builds a representation of the request :returns: The string representation of the request ''' return "ReadExceptionStatusRequest(%d)" % (self.function_code) 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 def __init__(self, status=0x00, **kwargs): ''' Initializes a new instance :param status: The status response to report ''' ModbusResponse.__init__(self, **kwargs) self.status = status def encode(self): ''' Encodes the response :returns: The byte encoded message ''' return struct.pack('>B', self.status) def decode(self, data): ''' Decodes a the response :param data: The packet data to decode ''' self.status = byte2int(data[0]) 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 #---------------------------------------------------------------------------# 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 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, context=None): ''' Run a read exeception status request against the store :returns: The populated response ''' status = _MCB.Counter.Event return GetCommEventCounterResponse(status) def __str__(self): ''' Builds a representation of the request :returns: The string representation of the request ''' return "GetCommEventCounterRequest(%d)" % (self.function_code) 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 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 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) 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) 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, context=None): ''' 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 = 2 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 #---------------------------------------------------------------------------# 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 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, context=None): ''' Run a read exeception status request against the store :returns: The populated response ''' identifier = b'Pymodbus' return ReportSlaveIdResponse(identifier) def __str__(self): ''' Builds a representation of the request :returns: The string representation of the request ''' return "ResportSlaveIdRequest(%d)" % self.function_code 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 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 self.byte_count = None 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) + 1 packet = int2byte(length) packet += self.identifier # we assume it is already encoded packet += int2byte(status) return packet 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 ''' self.byte_count = byte2int(data[0]) self.identifier = data[1:self.byte_count + 1] status = byte2int(data[-1]) self.status = status == ModbusStatus.SlaveOn 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(%s, %s, %s)" % arguments #---------------------------------------------------------------------------# # TODO Make these only work on serial #---------------------------------------------------------------------------# # report device identification 43, 14 #---------------------------------------------------------------------------# # Exported symbols #---------------------------------------------------------------------------# __all__ = [ "ReadExceptionStatusRequest", "ReadExceptionStatusResponse", "GetCommEventCounterRequest", "GetCommEventCounterResponse", "GetCommEventLogRequest", "GetCommEventLogResponse", "ReportSlaveIdRequest", "ReportSlaveIdResponse", ] pymodbus-2.1.0/pymodbus/payload.py000066400000000000000000000364101335513467700172430ustar00rootroot00000000000000""" 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.utilities import make_byte_string from pymodbus.exceptions import ParameterException from pymodbus.compat import unicode_string # --------------------------------------------------------------------------- # # Logging # --------------------------------------------------------------------------- # import logging _logger = logging.getLogger(__name__) WC = { "b": 1, "h": 2, "i": 4, "l": 4, "q": 8, "f": 4, "d": 8 } 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(byteorder=Endian.Little) builder.add_8bit_uint(1) builder.add_16bit_uint(2) payload = builder.build() """ def __init__(self, payload=None, byteorder=Endian.Little, wordorder=Endian.Big, repack=False): """ Initialize a new instance of the payload builder :param payload: Raw binary payload data to initialize with :param byteorder: The endianess of the bytes in the words :param wordorder: The endianess of the word (when wordcount is >= 2) :param repack: Repack the provided payload based on BO """ self._payload = payload or [] self._byteorder = byteorder self._wordorder = wordorder self._repack = repack def _pack_words(self, fstring, value): """ Packs Words based on the word order and byte order # ---------------------------------------------- # # pack in to network ordered value # # unpack in to network ordered unsigned integer # # Change Word order if little endian word order # # Pack values back based on correct byte order # # ---------------------------------------------- # :param value: Value to be packed :return: """ value = pack("!{}".format(fstring), value) wc = WC.get(fstring.lower())//2 up = "!{}H".format(wc) payload = unpack(up, value) if self._wordorder == Endian.Little: payload = list(reversed(payload)) fstring = self._byteorder + "H" payload = [pack(fstring, word) for word in payload] payload = b''.join(payload) return payload def to_string(self): """ Return the payload buffer as a string :returns: The payload buffer as a string """ return b''.join(self._payload) def __str__(self): """ Return the payload buffer as a string :returns: The payload buffer as a string """ return self.to_string().decode('utf-8') def reset(self): """ Reset the payload buffer """ self._payload = [] 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._byteorder+'H' fstring = '!H' payload = self.build() if self._repack: payload = [unpack(self._byteorder+"H", value)[0] for value in payload] else: payload = [unpack(fstring, value)[0] for value in payload] _logger.debug(payload) return payload def to_coils(self): """Convert the payload buffer into a coil layout that can be used as a context block. :returns: The coil layout to use as a block """ payload = self.to_registers() coils = [bool(int(bit)) for reg in payload[1:] for bit in format(reg, '016b')] return coils 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)] 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) 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._byteorder + 'B' self._payload.append(pack(fstring, value)) 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._byteorder + 'H' self._payload.append(pack(fstring, value)) 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 = 'I' # fstring = self._byteorder + 'I' p_string = self._pack_words(fstring, value) self._payload.append(p_string) 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 = 'Q' p_string = self._pack_words(fstring, value) self._payload.append(p_string) 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._byteorder + 'b' self._payload.append(pack(fstring, value)) 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._byteorder + 'h' self._payload.append(pack(fstring, value)) 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 = 'i' p_string = self._pack_words(fstring, value) self._payload.append(p_string) 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 = 'q' p_string = self._pack_words(fstring, value) self._payload.append(p_string) def add_32bit_float(self, value): """ Adds a 32 bit float to the buffer :param value: The value to add to the buffer """ fstring = 'f' p_string = self._pack_words(fstring, value) self._payload.append(p_string) 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 = 'd' p_string = self._pack_words(fstring, value) self._payload.append(p_string) def add_string(self, value): """ Adds a string to the buffer :param value: The value to add to the buffer """ value = make_byte_string(value) fstring = self._byteorder + str(len(value)) + 's' self._payload.append(pack(fstring, value)) 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() """ def __init__(self, payload, byteorder=Endian.Little, wordorder=Endian.Big): """ Initialize a new payload decoder :param payload: The payload to decode with :param byteorder: The endianess of the payload :param wordorder: The endianess of the word (when wordcount is >= 2) """ self._payload = payload self._pointer = 0x00 self._byteorder = byteorder self._wordorder = wordorder @classmethod def fromRegisters(klass, registers, byteorder=Endian.Little, wordorder=Endian.Big): """ 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 byteorder: The Byte order of each word :param wordorder: The endianess of the word (when wordcount is >= 2) :returns: An initialized PayloadDecoder """ _logger.debug(registers) if isinstance(registers, list): # repack into flat binary payload = b''.join(pack('!H', x) for x in registers) return klass(payload, byteorder, wordorder) raise ParameterException('Invalid collection of registers supplied') @classmethod def fromCoils(klass, coils, byteorder=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 byteorder: The endianess of the payload :returns: An initialized PayloadDecoder """ if isinstance(coils, list): payload = pack_bitstring(coils) return klass(payload, byteorder) raise ParameterException('Invalid collection of coils supplied') def _unpack_words(self, fstring, handle): """ Un Packs Words based on the word order and byte order # ---------------------------------------------- # # Unpack in to network ordered unsigned integer # # Change Word order if little endian word order # # Pack values back based on correct byte order # # ---------------------------------------------- # :param handle: Value to be unpacked :return: """ handle = make_byte_string(handle) wc = WC.get(fstring.lower()) // 2 up = "!{}H".format(wc) handle = unpack(up, handle) if self._wordorder == Endian.Little: handle = list(reversed(handle)) # Repack as unsigned Integer pk = self._byteorder + 'H' handle = [pack(pk, p) for p in handle] _logger.debug(handle) handle = b''.join(handle) return handle def reset(self): """ Reset the decoder pointer back to the start """ self._pointer = 0x00 def decode_8bit_uint(self): """ Decodes a 8 bit unsigned int from the buffer """ self._pointer += 1 fstring = self._byteorder + 'B' handle = self._payload[self._pointer - 1:self._pointer] handle = make_byte_string(handle) return unpack(fstring, handle)[0] 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] handle = make_byte_string(handle) return unpack_bitstring(handle) def decode_16bit_uint(self): """ Decodes a 16 bit unsigned int from the buffer """ self._pointer += 2 fstring = self._byteorder + 'H' handle = self._payload[self._pointer - 2:self._pointer] handle = make_byte_string(handle) return unpack(fstring, handle)[0] def decode_32bit_uint(self): """ Decodes a 32 bit unsigned int from the buffer """ self._pointer += 4 fstring = 'I' # fstring = 'I' handle = self._payload[self._pointer - 4:self._pointer] handle = self._unpack_words(fstring, handle) return unpack("!"+fstring, handle)[0] def decode_64bit_uint(self): """ Decodes a 64 bit unsigned int from the buffer """ self._pointer += 8 fstring = 'Q' handle = self._payload[self._pointer - 8:self._pointer] handle = self._unpack_words(fstring, handle) return unpack("!"+fstring, handle)[0] def decode_8bit_int(self): """ Decodes a 8 bit signed int from the buffer """ self._pointer += 1 fstring = self._byteorder + 'b' handle = self._payload[self._pointer - 1:self._pointer] handle = make_byte_string(handle) return unpack(fstring, handle)[0] def decode_16bit_int(self): """ Decodes a 16 bit signed int from the buffer """ self._pointer += 2 fstring = self._byteorder + 'h' handle = self._payload[self._pointer - 2:self._pointer] handle = make_byte_string(handle) return unpack(fstring, handle)[0] def decode_32bit_int(self): """ Decodes a 32 bit signed int from the buffer """ self._pointer += 4 fstring = 'i' handle = self._payload[self._pointer - 4:self._pointer] handle = self._unpack_words(fstring, handle) return unpack("!"+fstring, handle)[0] def decode_64bit_int(self): """ Decodes a 64 bit signed int from the buffer """ self._pointer += 8 fstring = 'q' handle = self._payload[self._pointer - 8:self._pointer] handle = self._unpack_words(fstring, handle) return unpack("!"+fstring, handle)[0] def decode_32bit_float(self): """ Decodes a 32 bit float from the buffer """ self._pointer += 4 fstring = 'f' handle = self._payload[self._pointer - 4:self._pointer] handle = self._unpack_words(fstring, handle) return unpack("!"+fstring, handle)[0] def decode_64bit_float(self): """ Decodes a 64 bit float(double) from the buffer """ self._pointer += 8 fstring = 'd' handle = self._payload[self._pointer - 8:self._pointer] handle = self._unpack_words(fstring, handle) return unpack("!"+fstring, handle)[0] def decode_string(self, size=1): """ Decodes a string from the buffer :param size: The size of the string to decode """ self._pointer += size s = self._payload[self._pointer - size:self._pointer] return s def skip_bytes(self, nbytes): """ Skip n bytes in the buffer :param nbytes: The number of bytes to skip """ self._pointer += nbytes return None #---------------------------------------------------------------------------# # Exported Identifiers #---------------------------------------------------------------------------# __all__ = ["BinaryPayloadBuilder", "BinaryPayloadDecoder"] pymodbus-2.1.0/pymodbus/pdu.py000066400000000000000000000174351335513467700164100ustar00rootroot00000000000000""" 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 # --------------------------------------------------------------------------- # 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. """ 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 def encode(self): """ Encodes the message :raises: A not implemented exception """ raise NotImplementedException() def decode(self, data): """ Decodes data part of the message. :param data: is a string object :raises: A not implemented exception """ raise NotImplementedException() @classmethod 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__) class ModbusRequest(ModbusPDU): """ Base class for a modbus request PDU """ def __init__(self, **kwargs): """ Proxy to the lower level initializer """ ModbusPDU.__init__(self, **kwargs) def doException(self, exception): """ Builds an error response based on the function :param exception: The exception to return :raises: An exception response """ exc = ExceptionResponse(self.function_code, exception) _logger.error(exc) return exc 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 def __init__(self, **kwargs): """ Proxy to the lower level initializer """ ModbusPDU.__init__(self, **kwargs) def isError(self): """Checks if the error is a success or failure""" return self.function_code > 0x80 # --------------------------------------------------------------------------- # # Exception PDU's # --------------------------------------------------------------------------- # 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 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) class ExceptionResponse(ModbusResponse): """ Base class for a modbus exception PDU """ ExceptionOffset = 0x80 _rtu_frame_size = 5 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 def encode(self): """ Encodes a modbus exception response :returns: The encoded exception packet """ return int2byte(self.exception_code) def decode(self, data): """ Decodes a modbus exception response :param data: The packet data to decode """ self.exception_code = byte2int(data[0]) 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 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 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 def decode(self, data): """ This is here so this failure will run correctly :param data: Not used """ pass 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', ] pymodbus-2.1.0/pymodbus/register_read_message.py000066400000000000000000000312251335513467700221340ustar00rootroot00000000000000''' 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 class ReadRegistersRequestBase(ModbusRequest): ''' Base class for reading a modbus register ''' _rtu_frame_size = 8 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 def encode(self): ''' Encodes the request packet :return: The encoded packet ''' return struct.pack('>HH', self.address, self.count) def decode(self, data): ''' Decode a register request packet :param data: The request to decode ''' self.address, self.count = struct.unpack('>HH', data) 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 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) class ReadRegistersResponseBase(ModbusResponse): ''' Base class for responsing to a modbus register read ''' _rtu_byte_count_pos = 2 def __init__(self, values, **kwargs): ''' Initializes a new instance :param values: The values to write to ''' ModbusResponse.__init__(self, **kwargs) self.registers = values or [] 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 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]) def getRegister(self, index): ''' Get the requested register :param index: The indexed register to retrieve :returns: The request register ''' return self.registers[index] def __str__(self): ''' Returns a string representation of the instance :returns: A string representation of the instance ''' return "ReadRegisterResponse (%d)" % len(self.registers) 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 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) 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) 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 def __init__(self, values=None, **kwargs): ''' Initializes a new response instance :param values: The resulting register values ''' ReadRegistersResponseBase.__init__(self, values, **kwargs) 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 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) 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) 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 def __init__(self, values=None, **kwargs): ''' Initializes a new response instance :param values: The resulting register values ''' ReadRegistersResponseBase.__init__(self, values, **kwargs) 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 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 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 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) 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) 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 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 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 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 [] 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 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]) 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", ] pymodbus-2.1.0/pymodbus/register_write_message.py000066400000000000000000000274321335513467700223600ustar00rootroot00000000000000''' Register Writing Request/Response Messages ------------------------------------------- ''' import struct from pymodbus.pdu import ModbusRequest from pymodbus.pdu import ModbusResponse from pymodbus.pdu import ModbusExceptions as merror 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 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 def encode(self): ''' Encode a write single register packet packet request :returns: The encoded packet ''' packet = struct.pack('>H', self.address) if self.skip_encode: packet += self.value else: packet += struct.pack('>H', self.value) return packet 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) 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]) def get_response_pdu_size(self): """ Func_code (1 byte) + Register Address(2 byte) + Register Value (2 bytes) :return: """ return 1 + 2 + 2 def __str__(self): ''' Returns a string representation of the instance :returns: A string representation of the instance ''' return "WriteRegisterRequest %d" % self.address 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 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 def encode(self): ''' Encode a write single register packet packet request :returns: The encoded packet ''' return struct.pack('>HH', self.address, self.value) 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) 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 #---------------------------------------------------------------------------# 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 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 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 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]) 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) def get_response_pdu_size(self): """ Func_code (1 byte) + Starting Address (2 byte) + Quantity of Reggisters (2 Bytes) :return: """ return 1 + 2 + 2 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 class WriteMultipleRegistersResponse(ModbusResponse): ''' "The normal response returns the function code, starting address, and quantity of registers written. ''' function_code = 16 _rtu_frame_size = 8 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 def encode(self): ''' Encode a write single register packet packet request :returns: The encoded packet ''' return struct.pack('>HH', self.address, self.count) 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) 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 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) #---------------------------------------------------------------------------# # Exported symbols #---------------------------------------------------------------------------# __all__ = [ "WriteSingleRegisterRequest", "WriteSingleRegisterResponse", "WriteMultipleRegistersRequest", "WriteMultipleRegistersResponse", "MaskWriteRegisterRequest", "MaskWriteRegisterResponse" ] pymodbus-2.1.0/pymodbus/repl/000077500000000000000000000000001335513467700161765ustar00rootroot00000000000000pymodbus-2.1.0/pymodbus/repl/README.md000066400000000000000000000347161335513467700174700ustar00rootroot00000000000000# Pymodbus REPL ## Dependencies Depends on [prompt_toolkit](https://python-prompt-toolkit.readthedocs.io/en/stable/index.html) and [click](http://click.pocoo.org/6/quickstart/) Install dependencies ``` $ pip install click prompt_toolkit --upgarde ``` Or Install pymodbus with repl support ``` $ pip install pymodbus[repl] --upgrade ``` ## Usage Instructions RTU and TCP are supported as of now ``` bash-3.2$ pymodbus.console Usage: pymodbus.console [OPTIONS] COMMAND [ARGS]... Options: --version Show the version and exit. --verbose Verbose logs --support-diag Support Diagnostic messages --help Show this message and exit. Commands: serial tcp ``` TCP Options ``` bash-3.2$ pymodbus.console tcp --help Usage: pymodbus.console tcp [OPTIONS] Options: --host TEXT Modbus TCP IP --port INTEGER Modbus TCP port --help Show this message and exit. ``` SERIAL Options ``` bash-3.2$ pymodbus.console serial --help Usage: pymodbus.console serial [OPTIONS] Options: --method TEXT Modbus Serial Mode (rtu/ascii) --port TEXT Modbus RTU port --baudrate INTEGER Modbus RTU serial baudrate to use. Defaults to 9600 --bytesize [5|6|7|8] Modbus RTU serial Number of data bits. Possible values: FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS. Defaults to 8 --parity [N|E|O|M|S] Modbus RTU serial parity. Enable parity checking. Possible values: PARITY_NONE, PARITY_EVEN, PARITY_ODD PARITY_MARK, PARITY_SPACE. Default to 'N' --stopbits [1|1.5|2] Modbus RTU serial stop bits. Number of stop bits. Possible values: STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO. Default to '1' --xonxoff INTEGER Modbus RTU serial xonxoff. Enable software flow control.Defaults to 0 --rtscts INTEGER Modbus RTU serial rtscts. Enable hardware (RTS/CTS) flow control. Defaults to 0 --dsrdtr INTEGER Modbus RTU serial dsrdtr. Enable hardware (DSR/DTR) flow control. Defaults to 0 --timeout FLOAT Modbus RTU serial read timeout. Defaults to 0.025 sec --write-timeout FLOAT Modbus RTU serial write timeout. Defaults to 2 sec --help Show this message and exit. ``` To view all available commands type `help` TCP ``` $ pymodbus.console tcp --host 192.168.128.126 --port 5020 > help Available commands: client.change_ascii_input_delimiter Diagnostic sub command, Change message delimiter for future requests. client.clear_counters Diagnostic sub command, Clear all counters and diag registers. client.clear_overrun_count Diagnostic sub command, Clear over run counter. client.close Closes the underlying socket connection client.connect Connect to the modbus tcp server client.debug_enabled Returns a boolean indicating if debug is enabled. client.force_listen_only_mode Diagnostic sub command, Forces the addressed remote device to its Listen Only Mode. client.get_clear_modbus_plus Diagnostic sub command, Get or clear stats of remote modbus plus device. client.get_com_event_counter Read status word and an event count from the remote device's communication event counter. client.get_com_event_log Read status word, event count, message count, and a field of event bytes from the remote device. client.host Read Only! client.idle_time Bus Idle Time to initiate next transaction client.is_socket_open Check whether the underlying socket/serial is open or not. client.last_frame_end Read Only! client.mask_write_register Mask content of holding register at `address` with `and_mask` and `or_mask`. client.port Read Only! client.read_coils Reads `count` coils from a given slave starting at `address`. client.read_device_information Read the identification and additional information of remote slave. client.read_discrete_inputs Reads `count` number of discrete inputs starting at offset `address`. client.read_exception_status Read the contents of eight Exception Status outputs in a remote device. client.read_holding_registers Read `count` number of holding registers starting at `address`. client.read_input_registers Read `count` number of input registers starting at `address`. client.readwrite_registers Read `read_count` number of holding registers starting at `read_address` and write `write_registers` starting at `write_address`. client.report_slave_id Report information about remote slave ID. client.restart_comm_option Diagnostic sub command, initialize and restart remote devices serial interface and clear all of its communications event counters . client.return_bus_com_error_count Diagnostic sub command, Return count of CRC errors received by remote slave. client.return_bus_exception_error_count Diagnostic sub command, Return count of Modbus exceptions returned by remote slave. client.return_bus_message_count Diagnostic sub command, Return count of message detected on bus by remote slave. client.return_diagnostic_register Diagnostic sub command, Read 16-bit diagnostic register. client.return_iop_overrun_count Diagnostic sub command, Return count of iop overrun errors by remote slave. client.return_query_data Diagnostic sub command , Loop back data sent in response. client.return_slave_bus_char_overrun_count Diagnostic sub command, Return count of messages not handled by remote slave due to character overrun condition. client.return_slave_busy_count Diagnostic sub command, Return count of server busy exceptions sent by remote slave. client.return_slave_message_count Diagnostic sub command, Return count of messages addressed to remote slave. client.return_slave_no_ack_count Diagnostic sub command, Return count of NO ACK exceptions sent by remote slave. client.return_slave_no_response_count Diagnostic sub command, Return count of No responses by remote slave. client.silent_interval Read Only! client.state Read Only! client.timeout Read Only! client.write_coil Write `value` to coil at `address`. client.write_coils Write `value` to coil at `address`. client.write_register Write `value` to register at `address`. client.write_registers Write list of `values` to registers starting at `address`. ``` SERIAL ``` $ pymodbus.console serial --port /dev/ttyUSB0 --baudrate 19200 --timeout 2 > help Available commands: client.baudrate Read Only! client.bytesize Read Only! client.change_ascii_input_delimiter Diagnostic sub command, Change message delimiter for future requests. client.clear_counters Diagnostic sub command, Clear all counters and diag registers. client.clear_overrun_count Diagnostic sub command, Clear over run counter. client.close Closes the underlying socket connection client.connect Connect to the modbus serial server client.debug_enabled Returns a boolean indicating if debug is enabled. client.force_listen_only_mode Diagnostic sub command, Forces the addressed remote device to its Listen Only Mode. client.get_baudrate Serial Port baudrate. client.get_bytesize Number of data bits. client.get_clear_modbus_plus Diagnostic sub command, Get or clear stats of remote modbus plus device. client.get_com_event_counter Read status word and an event count from the remote device's communication event counter. client.get_com_event_log Read status word, event count, message count, and a field of event bytes from the remote device. client.get_parity Enable Parity Checking. client.get_port Serial Port. client.get_serial_settings Gets Current Serial port settings. client.get_stopbits Number of stop bits. client.get_timeout Serial Port Read timeout. client.idle_time Bus Idle Time to initiate next transaction client.inter_char_timeout Read Only! client.is_socket_open c l i e n t . i s s o c k e t o p e n client.mask_write_register Mask content of holding register at `address` with `and_mask` and `or_mask`. client.method Read Only! client.parity Read Only! client.port Read Only! client.read_coils Reads `count` coils from a given slave starting at `address`. client.read_device_information Read the identification and additional information of remote slave. client.read_discrete_inputs Reads `count` number of discrete inputs starting at offset `address`. client.read_exception_status Read the contents of eight Exception Status outputs in a remote device. client.read_holding_registers Read `count` number of holding registers starting at `address`. client.read_input_registers Read `count` number of input registers starting at `address`. client.readwrite_registers Read `read_count` number of holding registers starting at `read_address` and write `write_registers` starting at `write_address`. client.report_slave_id Report information about remote slave ID. client.restart_comm_option Diagnostic sub command, initialize and restart remote devices serial interface and clear all of its communications event counters . client.return_bus_com_error_count Diagnostic sub command, Return count of CRC errors received by remote slave. client.return_bus_exception_error_count Diagnostic sub command, Return count of Modbus exceptions returned by remote slave. client.return_bus_message_count Diagnostic sub command, Return count of message detected on bus by remote slave. client.return_diagnostic_register Diagnostic sub command, Read 16-bit diagnostic register. client.return_iop_overrun_count Diagnostic sub command, Return count of iop overrun errors by remote slave. client.return_query_data Diagnostic sub command , Loop back data sent in response. client.return_slave_bus_char_overrun_count Diagnostic sub command, Return count of messages not handled by remote slave due to character overrun condition. client.return_slave_busy_count Diagnostic sub command, Return count of server busy exceptions sent by remote slave. client.return_slave_message_count Diagnostic sub command, Return count of messages addressed to remote slave. client.return_slave_no_ack_count Diagnostic sub command, Return count of NO ACK exceptions sent by remote slave. client.return_slave_no_response_count Diagnostic sub command, Return count of No responses by remote slave. client.set_baudrate Baudrate setter. client.set_bytesize Byte size setter. client.set_parity Parity Setter. client.set_port Serial Port setter. client.set_stopbits Stop bit setter. client.set_timeout Read timeout setter. client.silent_interval Read Only! client.state Read Only! client.stopbits Read Only! client.timeout Read Only! client.write_coil Write `value` to coil at `address`. client.write_coils Write `value` to coil at `address`. client.write_register Write `value` to register at `address`. client.write_registers Write list of `values` to registers starting at `address`. result.decode Decode the register response to known formatters. result.raw Return raw result dict. ``` Every command has auto suggetion on the arguments supported , supply arg and value are to be supplied in `arg=val` format. ``` > client.read_holding_registers count=4 address=9 unit=1 { "registers": [ 60497, 47134, 34091, 15424 ] } ``` The last result could be accessed with `result.raw` command ``` > result.raw { "registers": [ 15626, 55203, 28733, 18368 ] } ``` For Holding and Input register reads, the decoded value could be viewed with `result.decode` ``` > result.decode word_order=little byte_order=little formatters=float64 28.17 > ``` Client settings could be retrieved and altered as well. ``` > # For serial settings > # Check the serial mode > client.method "rtu" > client.get_serial_settings { "t1.5": 0.00171875, "baudrate": 9600, "read timeout": 0.5, "port": "/dev/ptyp0", "t3.5": 0.00401, "bytesize": 8, "parity": "N", "stopbits": 1.0 } > client.set_timeout value=1 null > client.get_timeout 1.0 > client.get_serial_settings { "t1.5": 0.00171875, "baudrate": 9600, "read timeout": 1.0, "port": "/dev/ptyp0", "t3.5": 0.00401, "bytesize": 8, "parity": "N", "stopbits": 1.0 } ``` ## DEMO [![asciicast](https://asciinema.org/a/y1xOk7lm59U1bRBE2N1pDIj2o.png)](https://asciinema.org/a/y1xOk7lm59U1bRBE2N1pDIj2o) [![asciicast](https://asciinema.org/a/edUqZN77fdjxL2toisiilJNwI.png)](https://asciinema.org/a/edUqZN77fdjxL2toisiilJNwI) pymodbus-2.1.0/pymodbus/repl/__init__.py000066400000000000000000000002211335513467700203020ustar00rootroot00000000000000""" Pymodbus REPL Module. Copyright (c) 2018 Riptide IO, Inc. All Rights Reserved. """ from __future__ import absolute_import, unicode_literalspymodbus-2.1.0/pymodbus/repl/client.py000066400000000000000000000603021335513467700200270ustar00rootroot00000000000000""" Modbus Clients to be used with REPL. Copyright (c) 2018 Riptide IO, Inc. All Rights Reserved. """ from __future__ import absolute_import, unicode_literals from pymodbus.pdu import ModbusExceptions, ExceptionResponse from pymodbus.exceptions import ModbusIOException from pymodbus.client.sync import ModbusSerialClient as _ModbusSerialClient from pymodbus.client.sync import ModbusTcpClient as _ModbusTcpClient from pymodbus.mei_message import ReadDeviceInformationRequest from pymodbus.other_message import (ReadExceptionStatusRequest, ReportSlaveIdRequest, GetCommEventCounterRequest, GetCommEventLogRequest) from pymodbus.diag_message import ( ReturnQueryDataRequest, RestartCommunicationsOptionRequest, ReturnDiagnosticRegisterRequest, ChangeAsciiInputDelimiterRequest, ForceListenOnlyModeRequest, ClearCountersRequest, ReturnBusMessageCountRequest, ReturnBusCommunicationErrorCountRequest, ReturnBusExceptionErrorCountRequest, ReturnSlaveMessageCountRequest, ReturnSlaveNoResponseCountRequest, ReturnSlaveNAKCountRequest, ReturnSlaveBusyCountRequest, ReturnSlaveBusCharacterOverrunCountRequest, ReturnIopOverrunCountRequest, ClearOverrunCountRequest, GetClearModbusPlusRequest) class ExtendedRequestSupport(object): @staticmethod def _process_exception(resp): if isinstance(resp, ExceptionResponse): err = { 'original_function_code': "{} ({})".format( resp.original_code, hex(resp.original_code)), 'error_function_code': "{} ({})".format( resp.function_code, hex(resp.function_code)), 'exception code': resp.exception_code, 'message': ModbusExceptions.decode(resp.exception_code) } elif isinstance(resp, ModbusIOException): err = { 'original_function_code': "{} ({})".format( resp.fcode, hex(resp.fcode)), 'error': resp.message } else: err = { 'error': str(resp) } return err def read_coils(self, address, count=1, **kwargs): """ Reads `count` coils from a given slave starting at `address`. :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: List of register values """ resp = super(ExtendedRequestSupport, self).read_coils(address, count, **kwargs) if not resp.isError(): return { 'function_code': resp.function_code, 'bits': resp.bits } else: return ExtendedRequestSupport._process_exception(resp) def read_discrete_inputs(self, address, count=1, **kwargs): """ Reads `count` number of discrete inputs starting at offset `address`. :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 :return: List of bits """ resp = super(ExtendedRequestSupport, self).read_discrete_inputs(address, count, **kwargs) if not resp.isError(): return { 'function_code': resp.function_code, 'bits': resp.bits } else: return ExtendedRequestSupport._process_exception(resp) def write_coil(self, address, value, **kwargs): """ Write `value` to coil at `address`. :param address: coil offset to write to :param value: bit value to write :param unit: The slave unit this request is targeting :return: """ resp = super(ExtendedRequestSupport, self).write_coil( address, value, **kwargs) if not resp.isError(): return { 'function_code': resp.function_code, 'address': resp.address, 'value': resp.value } else: return ExtendedRequestSupport._process_exception(resp) def write_coils(self, address, values, **kwargs): """ Write `value` to coil at `address`. :param address: coil offset to write to :param value: list of bit values to write (comma seperated) :param unit: The slave unit this request is targeting :return: """ resp = super(ExtendedRequestSupport, self).write_coils( address, values, **kwargs) if not resp.isError(): return { 'function_code': resp.function_code, 'address': resp.address, 'count': resp.count } else: return ExtendedRequestSupport._process_exception(resp) def write_register(self, address, value, **kwargs): """ Write `value` to register at `address`. :param address: register offset to write to :param value: register value to write :param unit: The slave unit this request is targeting :return: """ resp = super(ExtendedRequestSupport, self).write_register( address, value, **kwargs) if not resp.isError(): return { 'function_code': resp.function_code, 'address': resp.address, 'value': resp.value } else: return ExtendedRequestSupport._process_exception(resp) def write_registers(self, address, values, **kwargs): """ Write list of `values` to registers starting at `address`. :param address: register offset to write to :param value: list of register value to write (comma seperated) :param unit: The slave unit this request is targeting :return: """ resp = super(ExtendedRequestSupport, self).write_registers( address, values, **kwargs) if not resp.isError(): return { 'function_code': resp.function_code, 'address': resp.address, 'count': resp.count } else: return ExtendedRequestSupport._process_exception(resp) def read_holding_registers(self, address, count=1, **kwargs): """ Read `count` number of holding registers starting at `address`. :param address: starting register offset to read from :param count: Number of registers to read :param unit: The slave unit this request is targeting :return: """ resp = super(ExtendedRequestSupport, self).read_holding_registers( address, count, **kwargs) if not resp.isError(): return { 'function_code': resp.function_code, 'registers': resp.registers } else: return ExtendedRequestSupport._process_exception(resp) def read_input_registers(self, address, count=1, **kwargs): """ Read `count` number of input registers starting at `address`. :param address: starting register offset to read from to :param count: Number of registers to read :param unit: The slave unit this request is targeting :return: """ resp = super(ExtendedRequestSupport, self).read_input_registers( address, count, **kwargs) if not resp.isError(): return { 'function_code': resp.function_code, 'registers': resp.registers } else: return ExtendedRequestSupport._process_exception(resp) def readwrite_registers(self, read_address, read_count, write_address, write_registers, **kwargs): """ Read `read_count` number of holding registers starting at \ `read_address` and write `write_registers` \ starting at `write_address`. :param read_address: register offset to read from :param read_count: Number of registers to read :param write_address: register offset to write to :param write_registers: List of register values to write (comma seperated) :param unit: The slave unit this request is targeting :return: """ resp = super(ExtendedRequestSupport, self).readwrite_registers( read_address=read_address, read_count=read_count, write_address=write_address, write_registers=write_registers, **kwargs ) if not resp.isError(): return { 'function_code': resp.function_code, 'registers': resp.registers } else: return ExtendedRequestSupport._process_exception(resp) def mask_write_register(self, address=0x0000, and_mask=0xffff, or_mask=0x0000, **kwargs): """ Mask content of holding register at `address` \ with `and_mask` and `or_mask`. :param address: Reference address of register :param and_mask: And Mask :param or_mask: OR Mask :param unit: The slave unit this request is targeting :return: """ resp = super(ExtendedRequestSupport, self).read_input_registers( address=address, and_mask=and_mask, or_mask=or_mask, **kwargs) if not resp.isError(): return { 'function_code': resp.function_code, 'address': resp.address, 'and mask': resp.and_mask, 'or mask': resp.or_mask } else: return ExtendedRequestSupport._process_exception(resp) def read_device_information(self, read_code=None, object_id=0x00, **kwargs): """ Read the identification and additional information of remote slave. :param read_code: Read Device ID code (0x01/0x02/0x03/0x04) :param object_id: Identification of the first object to obtain. :param unit: The slave unit this request is targeting :return: """ request = ReadDeviceInformationRequest(read_code, object_id, **kwargs) resp = self.execute(request) if not resp.isError(): return { 'function_code': resp.function_code, 'information': resp.information, 'object count': resp.number_of_objects, 'conformity': resp.conformity, 'next object id': resp.next_object_id, 'more follows': resp.more_follows, 'space left': resp.space_left } else: return ExtendedRequestSupport._process_exception(resp) def report_slave_id(self, **kwargs): """ Report information about remote slave ID. :param unit: The slave unit this request is targeting :return: """ request = ReportSlaveIdRequest(**kwargs) resp = self.execute(request) if not resp.isError(): return { 'function_code': resp.function_code, 'identifier': resp.identifier.decode('cp1252'), 'status': resp.status, 'byte count': resp.byte_count } else: return ExtendedRequestSupport._process_exception(resp) def read_exception_status(self, **kwargs): """ Read the contents of eight Exception Status outputs in a remote \ device. :param unit: The slave unit this request is targeting :return: """ request = ReadExceptionStatusRequest(**kwargs) resp = self.execute(request) if not resp.isError(): return { 'function_code': resp.function_code, 'status': resp.status } else: return ExtendedRequestSupport._process_exception(resp) def get_com_event_counter(self, **kwargs): """ Read status word and an event count from the remote device's \ communication event counter. :param unit: The slave unit this request is targeting :return: """ request = GetCommEventCounterRequest(**kwargs) resp = self.execute(request) if not resp.isError(): return { 'function_code': resp.function_code, 'status': resp.status, 'count': resp.count } else: return ExtendedRequestSupport._process_exception(resp) def get_com_event_log(self, **kwargs): """ Read status word, event count, message count, and a field of event bytes from the remote device. :param unit: The slave unit this request is targeting :return: """ request = GetCommEventLogRequest(**kwargs) resp = self.execute(request) if not resp.isError(): return { 'function_code': resp.function_code, 'status': resp.status, 'message count': resp.message_count, 'event count': resp.event_count, 'events': resp.events, } else: return ExtendedRequestSupport._process_exception(resp) def _execute_diagnostic_request(self, request): resp = self.execute(request) if not resp.isError(): return { 'function code': resp.function_code, 'sub function code': resp.sub_function_code, 'message': resp.message } else: return ExtendedRequestSupport._process_exception(resp) def return_query_data(self, message=0, **kwargs): """ Diagnostic sub command , Loop back data sent in response. :param message: Message to be looped back :param unit: The slave unit this request is targeting :return: """ request = ReturnQueryDataRequest(message, **kwargs) return self._execute_diagnostic_request(request) def restart_comm_option(self, toggle=False, **kwargs): """ Diagnostic sub command, initialize and restart remote devices serial \ interface and clear all of its communications event counters . :param toggle: Toggle Status [ON(0xff00)/OFF(0x0000] :param unit: The slave unit this request is targeting :return: """ request = RestartCommunicationsOptionRequest(toggle, **kwargs) return self._execute_diagnostic_request(request) def return_diagnostic_register(self, data=0, **kwargs): """ Diagnostic sub command, Read 16-bit diagnostic register. :param data: Data field (0x0000) :param unit: The slave unit this request is targeting :return: """ request = ReturnDiagnosticRegisterRequest(data, **kwargs) return self._execute_diagnostic_request(request) def change_ascii_input_delimiter(self, data=0, **kwargs): """ Diagnostic sub command, Change message delimiter for future requests. :param data: New delimiter character :param unit: The slave unit this request is targeting :return: """ request = ChangeAsciiInputDelimiterRequest(data, **kwargs) return self._execute_diagnostic_request(request) def force_listen_only_mode(self, data=0, **kwargs): """ Diagnostic sub command, Forces the addressed remote device to \ its Listen Only Mode. :param data: Data field (0x0000) :param unit: The slave unit this request is targeting :return: """ request = ForceListenOnlyModeRequest(data, **kwargs) return self._execute_diagnostic_request(request) def clear_counters(self, data=0, **kwargs): """ Diagnostic sub command, Clear all counters and diag registers. :param data: Data field (0x0000) :param unit: The slave unit this request is targeting :return: """ request = ClearCountersRequest(data, **kwargs) return self._execute_diagnostic_request(request) def return_bus_message_count(self, data=0, **kwargs): """ Diagnostic sub command, Return count of message detected on bus \ by remote slave. :param data: Data field (0x0000) :param unit: The slave unit this request is targeting :return: """ request = ReturnBusMessageCountRequest(data, **kwargs) return self._execute_diagnostic_request(request) def return_bus_com_error_count(self, data=0, **kwargs): """ Diagnostic sub command, Return count of CRC errors \ received by remote slave. :param data: Data field (0x0000) :param unit: The slave unit this request is targeting :return: """ request = ReturnBusCommunicationErrorCountRequest(data, **kwargs) return self._execute_diagnostic_request(request) def return_bus_exception_error_count(self, data=0, **kwargs): """ Diagnostic sub command, Return count of Modbus exceptions \ returned by remote slave. :param data: Data field (0x0000) :param unit: The slave unit this request is targeting :return: """ request = ReturnBusExceptionErrorCountRequest(data, **kwargs) return self._execute_diagnostic_request(request) def return_slave_message_count(self, data=0, **kwargs): """ Diagnostic sub command, Return count of messages addressed to \ remote slave. :param data: Data field (0x0000) :param unit: The slave unit this request is targeting :return: """ request = ReturnSlaveMessageCountRequest(data, **kwargs) return self._execute_diagnostic_request(request) def return_slave_no_response_count(self, data=0, **kwargs): """ Diagnostic sub command, Return count of No responses by remote slave. :param data: Data field (0x0000) :param unit: The slave unit this request is targeting :return: """ request = ReturnSlaveNoResponseCountRequest(data, **kwargs) return self._execute_diagnostic_request(request) def return_slave_no_ack_count(self, data=0, **kwargs): """ Diagnostic sub command, Return count of NO ACK exceptions sent \ by remote slave. :param data: Data field (0x0000) :param unit: The slave unit this request is targeting :return: """ request = ReturnSlaveNAKCountRequest(data, **kwargs) return self._execute_diagnostic_request(request) def return_slave_busy_count(self, data=0, **kwargs): """ Diagnostic sub command, Return count of server busy exceptions sent \ by remote slave. :param data: Data field (0x0000) :param unit: The slave unit this request is targeting :return: """ request = ReturnSlaveBusyCountRequest(data, **kwargs) return self._execute_diagnostic_request(request) def return_slave_bus_char_overrun_count(self, data=0, **kwargs): """ Diagnostic sub command, Return count of messages not handled \ by remote slave due to character overrun condition. :param data: Data field (0x0000) :param unit: The slave unit this request is targeting :return: """ request = ReturnSlaveBusCharacterOverrunCountRequest(data, **kwargs) return self._execute_diagnostic_request(request) def return_iop_overrun_count(self, data=0, **kwargs): """ Diagnostic sub command, Return count of iop overrun errors \ by remote slave. :param data: Data field (0x0000) :param unit: The slave unit this request is targeting :return: """ request = ReturnIopOverrunCountRequest(data, **kwargs) return self._execute_diagnostic_request(request) def clear_overrun_count(self, data=0, **kwargs): """ Diagnostic sub command, Clear over run counter. :param data: Data field (0x0000) :param unit: The slave unit this request is targeting :return: """ request = ClearOverrunCountRequest(data, **kwargs) return self._execute_diagnostic_request(request) def get_clear_modbus_plus(self, data=0, **kwargs): """ Diagnostic sub command, Get or clear stats of remote \ modbus plus device. :param data: Data field (0x0000) :param unit: The slave unit this request is targeting :return: """ request = GetClearModbusPlusRequest(data, **kwargs) return self._execute_diagnostic_request(request) class ModbusSerialClient(ExtendedRequestSupport, _ModbusSerialClient): def __init__(self, method, **kwargs): super(ModbusSerialClient, self).__init__(method, **kwargs) def get_port(self): """ Serial Port. :return: Current Serial port """ return self.port def set_port(self, value): """ Serial Port setter. :param value: New port """ self.port = value if self.is_socket_open(): self.close() def get_stopbits(self): """ Number of stop bits. :return: Current Stop bits """ return self.stopbits def set_stopbits(self, value): """ Stop bit setter. :param value: Possible values (1, 1.5, 2) """ self.stopbits = float(value) if self.is_socket_open(): self.close() def get_bytesize(self): """ Number of data bits. :return: Current bytesize """ return self.bytesize def set_bytesize(self, value): """ Byte size setter. :param value: Possible values (5, 6, 7, 8) """ self.bytesize = int(value) if self.is_socket_open(): self.close() def get_parity(self): """ Enable Parity Checking. :return: Current parity setting """ return self.parity def set_parity(self, value): """ Parity Setter. :param value: Possible values ('N', 'E', 'O', 'M', 'S') """ self.parity = value if self.is_socket_open(): self.close() def get_baudrate(self): """ Serial Port baudrate. :return: Current baudrate """ return self.baudrate def set_baudrate(self, value): """ Baudrate setter. :param value: """ self.baudrate = int(value) if self.is_socket_open(): self.close() def get_timeout(self): """ Serial Port Read timeout. :return: Current read imeout. """ return self.timeout def set_timeout(self, value): """ Read timeout setter. :param value: Read Timeout in seconds """ self.timeout = float(value) if self.is_socket_open(): self.close() def get_serial_settings(self): """ Gets Current Serial port settings. :return: Current Serial settings as dict. """ return { 'baudrate': self.baudrate, 'port': self.port, 'parity': self.parity, 'stopbits': self.stopbits, 'bytesize': self.bytesize, 'read timeout': self.timeout, 't1.5': self.inter_char_timeout, 't3.5': self.silent_interval } class ModbusTcpClient(ExtendedRequestSupport, _ModbusTcpClient): def __init__(self, **kwargs): super(ModbusTcpClient, self).__init__(**kwargs) pymodbus-2.1.0/pymodbus/repl/completer.py000066400000000000000000000124601335513467700205450ustar00rootroot00000000000000""" Command Completion for pymodbus REPL. Copyright (c) 2018 Riptide IO, Inc. All Rights Reserved. """ from __future__ import absolute_import, unicode_literals from prompt_toolkit.completion import Completer, Completion from prompt_toolkit.styles import Style from prompt_toolkit.filters import Condition from prompt_toolkit.application.current import get_app from pymodbus.repl.helper import get_commands from pymodbus.compat import string_types @Condition def has_selected_completion(): complete_state = get_app().current_buffer.complete_state return (complete_state is not None and complete_state.current_completion is not None) style = Style.from_dict({ 'completion-menu.completion': 'bg:#008888 #ffffff', 'completion-menu.completion.current': 'bg:#00aaaa #000000', 'scrollbar.background': 'bg:#88aaaa', 'scrollbar.button': 'bg:#222222', }) class CmdCompleter(Completer): """ Completer for Pymodbus REPL. """ def __init__(self, client, commands=None, ignore_case=True): """ :param client: Modbus Client :param commands: Commands to be added for Completion (list) :param ignore_case: Ignore Case while looking up for commands """ self._commands = commands or get_commands(client) self._commands['help'] = "" self._command_names = self._commands.keys() self.ignore_case = ignore_case @property def commands(self): return self._commands @property def command_names(self): return self._commands.keys() def completing_command(self, words, word_before_cursor): """ Determine if we are dealing with supported command. :param words: Input text broken in to word tokens. :param word_before_cursor: The current word before the cursor, \ which might be one or more blank spaces. :return: """ if len(words) == 1 and word_before_cursor != '': return True else: return False def completing_arg(self, words, word_before_cursor): """ Determine if we are currently completing an argument. :param words: The input text broken into word tokens. :param word_before_cursor: The current word before the cursor, \ which might be one or more blank spaces. :return: Specifies whether we are currently completing an arg. """ if len(words) > 1 and word_before_cursor != '': return True else: return False def arg_completions(self, words, word_before_cursor): """ Generates arguments completions based on the input. :param words: The input text broken into word tokens. :param word_before_cursor: The current word before the cursor, \ which might be one or more blank spaces. :return: A list of completions. """ cmd = words[0].strip() cmd = self._commands.get(cmd, None) if cmd: return cmd def _get_completions(self, word, word_before_cursor): if self.ignore_case: word_before_cursor = word_before_cursor.lower() return self.word_matches(word, word_before_cursor) def word_matches(self, word, word_before_cursor): """ Match the word and word before cursor :param words: The input text broken into word tokens. :param word_before_cursor: The current word before the cursor, \ which might be one or more blank spaces. :return: True if matched. """ if self.ignore_case: word = word.lower() return word.startswith(word_before_cursor) def get_completions(self, document, complete_event): """ Get completions for the current scope. :param document: An instance of `prompt_toolkit.Document`. :param complete_event: (Unused). :return: Yields an instance of `prompt_toolkit.completion.Completion`. """ word_before_cursor = document.get_word_before_cursor(WORD=True) text = document.text_before_cursor.lstrip() words = document.text.strip().split() meta = None commands = [] if len(words) == 0: # yield commands pass if self.completing_command(words, word_before_cursor): commands = self._command_names c_meta = { k: v.help_text if not isinstance(v, string_types) else v for k, v in self._commands.items() } meta = lambda x: (x, c_meta.get(x, '')) else: if not list(filter(lambda cmd: any(x == cmd for x in words), self._command_names)): # yield commands pass if ' ' in text: command = self.arg_completions(words, word_before_cursor) commands = list(command.get_completion()) commands = list(filter(lambda cmd: not(any(cmd in x for x in words)), commands)) meta = command.get_meta for a in commands: if self._get_completions(a, word_before_cursor): cmd, display_meta = meta(a) if meta else ('', '') yield Completion(a, -len(word_before_cursor), display_meta=display_meta) pymodbus-2.1.0/pymodbus/repl/helper.py000066400000000000000000000244561335513467700200420ustar00rootroot00000000000000""" Helper Module for REPL actions. Copyright (c) 2018 Riptide IO, Inc. All Rights Reserved. """ from __future__ import absolute_import, unicode_literals import json import pygments import inspect from collections import OrderedDict from pygments.lexers.data import JsonLexer from prompt_toolkit.formatted_text import PygmentsTokens, HTML from prompt_toolkit import print_formatted_text from pymodbus.payload import BinaryPayloadDecoder, Endian from pymodbus.compat import PYTHON_VERSION, IS_PYTHON2, string_types, izip predicate = inspect.ismethod if IS_PYTHON2 or PYTHON_VERSION < (3, 3): argspec = inspect.getargspec else: predicate = inspect.ismethod argspec = inspect.signature FORMATTERS = { 'int8': 'decode_8bit_int', 'int16': 'decode_16bit_int', 'int32': 'decode_32bit_int', 'int64': 'decode_64bit_int', 'uint8': 'decode_8bit_uint', 'uint16': 'decode_16bit_uint', 'uint32': 'decode_32bit_uint', 'uint64': 'decode_64bit_int', 'float32': 'decode_32bit_float', 'float64': 'decode_64bit_float', } DEFAULT_KWARGS = { 'unit': 'Slave address' } OTHER_COMMANDS = { "result.raw": "Show RAW Result", "result.decode": "Decode register response to known formats", } EXCLUDE = ['execute', 'recv', 'send', 'trace', 'set_debug'] CLIENT_METHODS = [ 'connect', 'close', 'idle_time', 'is_socket_open', 'get_port', 'set_port', 'get_stopbits', 'set_stopbits', 'get_bytesize', 'set_bytesize', 'get_parity', 'set_parity', 'get_baudrate', 'set_baudrate', 'get_timeout', 'set_timeout', 'get_serial_settings' ] CLIENT_ATTRIBUTES = [] class Command(object): """ Class representing Commands to be consumed by Completer. """ def __init__(self, name, signature, doc, unit=False): """ :param name: Name of the command :param signature: inspect object :param doc: Doc string for the command :param unit: Use unit as additional argument in the command . """ self.name = name self.doc = doc.split("\n") if doc else " ".join(name.split("_")) self.help_text = self._create_help() self.param_help = self._create_arg_help() if signature: if IS_PYTHON2: self._params = signature else: self._params = signature.parameters self.args = self.create_completion() else: self._params = '' if self.name.startswith("client.") and unit: self.args.update(**DEFAULT_KWARGS) def _create_help(self): doc = filter(lambda d: d, self.doc) cmd_help = list(filter( lambda x: not x.startswith(":param") and not x.startswith( ":return"), doc)) return " ".join(cmd_help).strip() def _create_arg_help(self): param_dict = {} params = list(filter(lambda d: d.strip().startswith(":param"), self.doc)) for param in params: param, help = param.split(":param")[1].strip().split(":") param_dict[param] = help return param_dict def create_completion(self): """ Create command completion meta data. :return: """ words = {} def _create(entry, default): if entry not in ['self', 'kwargs']: if isinstance(default, (int, string_types)): entry += "={}".format(default) return entry if IS_PYTHON2: if not self._params.defaults: defaults = [None]*len(self._params.args) else: defaults = list(self._params.defaults) missing = len(self._params.args) - len(defaults) if missing > 1: defaults.extend([None]*missing) defaults.insert(0, None) for arg, default in izip(self._params.args, defaults): entry = _create(arg, default) if entry: entry, meta = self.get_meta(entry) words[entry] = help else: for arg in self._params.values(): entry = _create(arg.name, arg.default) if entry: entry, meta = self.get_meta(entry) words[entry] = meta return words def get_completion(self): """ Gets a list of completions. :return: """ return self.args.keys() def get_meta(self, cmd): """ Get Meta info of a given command. :param cmd: Name of command. :return: Dict containing meta info. """ cmd = cmd.strip() cmd = cmd.split("=")[0].strip() return cmd, self.param_help.get(cmd, '') def __str__(self): if self.doc: return "Command {0:>50}{:<20}".format(self.name, self.doc) return "Command {}".format(self.name) def _get_requests(members): commands = list(filter(lambda x: (x[0] not in EXCLUDE and x[0] not in CLIENT_METHODS and callable(x[1])), members)) commands = { "client.{}".format(c[0]): Command("client.{}".format(c[0]), argspec(c[1]), inspect.getdoc(c[1]), unit=True) for c in commands if not c[0].startswith("_") } return commands def _get_client_methods(members): commands = list(filter(lambda x: (x[0] not in EXCLUDE and x[0] in CLIENT_METHODS), members)) commands = { "client.{}".format(c[0]): Command("client.{}".format(c[0]), argspec(c[1]), inspect.getdoc(c[1]), unit=False) for c in commands if not c[0].startswith("_") } return commands def _get_client_properties(members): global CLIENT_ATTRIBUTES commands = list(filter(lambda x: not callable(x[1]), members)) commands = { "client.{}".format(c[0]): Command("client.{}".format(c[0]), None, "Read Only!", unit=False) for c in commands if (not c[0].startswith("_") and isinstance(c[1], (string_types, int, float))) } CLIENT_ATTRIBUTES.extend(list(commands.keys())) return commands def get_commands(client): """ Helper method to retrieve all required methods and attributes of a client \ object and convert it to commands. :param client: Modbus Client object. :return: """ commands = dict() members = inspect.getmembers(client) requests = _get_requests(members) client_methods = _get_client_methods(members) client_attr = _get_client_properties(members) result_commands = inspect.getmembers(Result, predicate=predicate) result_commands = { "result.{}".format(c[0]): Command("result.{}".format(c[0]), argspec(c[1]), inspect.getdoc(c[1])) for c in result_commands if (not c[0].startswith("_") and c[0] != "print_result") } commands.update(requests) commands.update(client_methods) commands.update(client_attr) commands.update(result_commands) return commands class Result(object): """ Represent result command. """ function_code = None data = None def __init__(self, result): """ :param result: Response of a modbus command. """ if isinstance(result, dict): # Modbus response self.function_code = result.pop('function_code', None) self.data = dict(result) else: self.data = result def decode(self, formatters, byte_order='big', word_order='big'): """ Decode the register response to known formatters. :param formatters: int8/16/32/64, uint8/16/32/64, float32/64 :param byte_order: little/big :param word_order: little/big :return: Decoded Value """ # Read Holding Registers (3) # Read Input Registers (4) # Read Write Registers (23) if not isinstance(formatters, (list, tuple)): formatters = [formatters] if self.function_code not in [3, 4, 23]: print_formatted_text( HTML("Decoder works only for registers!!")) return byte_order = (Endian.Little if byte_order.strip().lower() == "little" else Endian.Big) word_order = (Endian.Little if word_order.strip().lower() == "little" else Endian.Big) decoder = BinaryPayloadDecoder.fromRegisters(self.data.get('registers'), byteorder=byte_order, wordorder=word_order) for formatter in formatters: formatter = FORMATTERS.get(formatter) if not formatter: print_formatted_text( HTML("Invalid Formatter - {}" "!!".format(formatter))) return decoded = getattr(decoder, formatter)() self.print_result(decoded) def raw(self): """ Return raw result dict. :return: """ self.print_result() def _process_dict(self, d): new_dict = OrderedDict() for k, v in d.items(): if isinstance(v, bytes): v = v.decode('utf-8') elif isinstance(v, dict): v = self._process_dict(v) elif isinstance(v, (list, tuple)): v = [v1.decode('utf-8') if isinstance(v1, bytes) else v1 for v1 in v ] new_dict[k] = v return new_dict def print_result(self, data=None): """ Prettu print result object. :param data: Data to be printed. :return: """ data = data or self.data if isinstance(data, dict): data = self._process_dict(data) elif isinstance(data, (list, tuple)): data = [v.decode('utf-8') if isinstance(v, bytes) else v for v in data] elif isinstance(data, bytes): data = data.decode('utf-8') tokens = list(pygments.lex(json.dumps(data, indent=4), lexer=JsonLexer())) print_formatted_text(PygmentsTokens(tokens)) pymodbus-2.1.0/pymodbus/repl/main.py000066400000000000000000000265241335513467700175050ustar00rootroot00000000000000""" Pymodbus REPL Entry point. Copyright (c) 2018 Riptide IO, Inc. All Rights Reserved. """ from __future__ import absolute_import, unicode_literals try: import click except ImportError: print("click not installed!! Install with 'pip install click'") exit(1) try: from prompt_toolkit import PromptSession, print_formatted_text except ImportError: print("prompt toolkit is not installed!! " "Install with 'pip install prompt_toolkit --upgrade'") exit(1) from prompt_toolkit.lexers import PygmentsLexer from prompt_toolkit.styles import Style from prompt_toolkit.key_binding import KeyBindings from pygments.lexers.python import PythonLexer from prompt_toolkit.formatted_text import HTML from prompt_toolkit.history import FileHistory from prompt_toolkit.auto_suggest import AutoSuggestFromHistory from pymodbus.version import version from pymodbus.repl.completer import CmdCompleter, has_selected_completion from pymodbus.repl.helper import Result, CLIENT_ATTRIBUTES click.disable_unicode_literals_warning = True TITLE = """ ---------------------------------------------------------------------------- __________ _____ .___ __________ .__ \______ \___.__. / \ ____ __| _/ \______ \ ____ ______ | | | ___< | |/ \ / \ / _ \ / __ | | _// __ \\\____ \| | | | \___ / Y ( <_> ) /_/ | | | \ ___/| |_> > |__ |____| / ____\____|__ /\____/\____ | /\ |____|_ /\___ > __/|____/ \/ \/ \/ \/ \/ \/|__| v{} - {} ---------------------------------------------------------------------------- """.format("1.0.0", version) log = None style = Style.from_dict({ 'completion-menu.completion': 'bg:#008888 #ffffff', 'completion-menu.completion.current': 'bg:#00aaaa #000000', 'scrollbar.background': 'bg:#88aaaa', 'scrollbar.button': 'bg:#222222', }) def bottom_toolbar(): """ Console toolbar. :return: """ return HTML('Press ' ' to exit! Type "help" for list of available commands') class CaseInsenstiveChoice(click.Choice): """ Case Insensitive choice for click commands and options """ def convert(self, value, param, ctx): """ Convert args to uppercase for evaluation. """ if value is None: return None return super(CaseInsenstiveChoice, self).convert( value.strip().upper(), param, ctx) class NumericChoice(click.Choice): """ Numeric choice for click arguments and options. """ def __init__(self, choices, typ): self.typ = typ super(NumericChoice, self).__init__(choices) def convert(self, value, param, ctx): # Exact match if value in self.choices: return self.typ(value) if ctx is not None and ctx.token_normalize_func is not None: value = ctx.token_normalize_func(value) for choice in self.casted_choices: if ctx.token_normalize_func(choice) == value: return choice self.fail('invalid choice: %s. (choose from %s)' % (value, ', '.join(self.choices)), param, ctx) def cli(client): kb = KeyBindings() @kb.add('c-space') def _(event): """ Initialize autocompletion, or select the next completion. """ buff = event.app.current_buffer if buff.complete_state: buff.complete_next() else: buff.start_completion(select_first=False) @kb.add('enter', filter=has_selected_completion) def _(event): """ Makes the enter key work as the tab key only when showing the menu. """ event.current_buffer.complete_state = None b = event.cli.current_buffer b.complete_state = None def _process_args(args, string=True): kwargs = {} execute = True skip_index = None for i, arg in enumerate(args): if i == skip_index: continue arg = arg.strip() if "=" in arg: a, val = arg.split("=") if not string: if "," in val: val = val.split(",") val = [int(v) for v in val] else: val = int(val) kwargs[a] = val else: a, val = arg, args[i + 1] try: if not string: if "," in val: val = val.split(",") val = [int(v) for v in val] else: val = int(val) kwargs[a] = val skip_index = i + 1 except TypeError: click.secho("Error parsing arguments!", fg='yellow') execute = False break except ValueError: click.secho("Error parsing argument", fg='yellow') execute = False break return kwargs, execute session = PromptSession(lexer=PygmentsLexer(PythonLexer), completer=CmdCompleter(client), style=style, complete_while_typing=True, bottom_toolbar=bottom_toolbar, key_bindings=kb, history=FileHistory('.pymodhis'), auto_suggest=AutoSuggestFromHistory()) click.secho("{}".format(TITLE), fg='green') result = None while True: try: text = session.prompt('> ', complete_while_typing=True) if text.strip().lower() == 'help': print_formatted_text(HTML("Available commands:")) for cmd, obj in sorted(session.completer.commands.items()): if cmd != 'help': print_formatted_text( HTML("{:45s}" "{:100s}" "".format(cmd, obj.help_text))) continue elif text.strip().lower() == 'exit': raise EOFError() elif text.strip().lower().startswith("client."): try: text = text.strip().split() cmd = text[0].split(".")[1] args = text[1:] kwargs, execute = _process_args(args, string=False) if execute: if text[0] in CLIENT_ATTRIBUTES: result = Result(getattr(client, cmd)) else: result = Result(getattr(client, cmd)(**kwargs)) result.print_result() except Exception as e: click.secho(repr(e), fg='red') elif text.strip().lower().startswith("result."): if result: words = text.lower().split() if words[0] == 'result.raw': result.raw() if words[0] == 'result.decode': args = words[1:] kwargs, execute = _process_args(args) if execute: result.decode(**kwargs) except KeyboardInterrupt: continue # Control-C pressed. Try again. except EOFError: break # Control-D pressed. except Exception as e: # Handle all other exceptions click.secho(str(e), fg='red') click.secho('GoodBye!', fg='blue') @click.group('pymodbus-repl') @click.version_option(version, message=TITLE) @click.option("--verbose", is_flag=True, default=False, help="Verbose logs") @click.pass_context def main(ctx, verbose): if verbose: global log import logging format = ('%(asctime)-15s %(threadName)-15s ' '%(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s') log = logging.getLogger('pymodbus') logging.basicConfig(format=format) log.setLevel(logging.DEBUG) @main.command("tcp") @click.pass_context @click.option( "--host", help="Modbus TCP IP " ) @click.option( "--port", default=502, type=int, help="Modbus TCP port", ) def tcp(ctx, host, port): from pymodbus.repl.client import ModbusTcpClient client = ModbusTcpClient(host=host, port=port) cli(client) @main.command("serial") @click.pass_context @click.option( "--method", default='rtu', type=str, help="Modbus Serial Mode (rtu/ascii)", ) @click.option( "--port", default=None, type=str, help="Modbus RTU port", ) @click.option( "--baudrate", help="Modbus RTU serial baudrate to use. Defaults to 9600", default=9600, type=int ) @click.option( "--bytesize", help="Modbus RTU serial Number of data bits. " "Possible values: FIVEBITS, SIXBITS, SEVENBITS, " "EIGHTBITS. Defaults to 8", type=NumericChoice(["5", "6", "7", "8"], int), default="8" ) @click.option( "--parity", help="Modbus RTU serial parity. " " Enable parity checking. Possible values: " "PARITY_NONE, PARITY_EVEN, PARITY_ODD PARITY_MARK, " "PARITY_SPACE. Default to 'N'", default='N', type=CaseInsenstiveChoice(['N', 'E', 'O', 'M', 'S']) ) @click.option( "--stopbits", help="Modbus RTU serial stop bits. " "Number of stop bits. Possible values: STOPBITS_ONE, " "STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO. Default to '1'", default="1", type=NumericChoice(["1", "1.5", "2"], float), ) @click.option( "--xonxoff", help="Modbus RTU serial xonxoff. Enable software flow control." "Defaults to 0", default=0, type=int ) @click.option( "--rtscts", help="Modbus RTU serial rtscts. Enable hardware (RTS/CTS) flow " "control. Defaults to 0", default=0, type=int ) @click.option( "--dsrdtr", help="Modbus RTU serial dsrdtr. Enable hardware (DSR/DTR) flow " "control. Defaults to 0", default=0, type=int ) @click.option( "--timeout", help="Modbus RTU serial read timeout. Defaults to 0.025 sec", default=0.25, type=float ) @click.option( "--write-timeout", help="Modbus RTU serial write timeout. Defaults to 2 sec", default=2, type=float ) def serial(ctx, method, port, baudrate, bytesize, parity, stopbits, xonxoff, rtscts, dsrdtr, timeout, write_timeout): from pymodbus.repl.client import ModbusSerialClient client = ModbusSerialClient(method=method, port=port, baudrate=baudrate, bytesize=bytesize, parity=parity, stopbits=stopbits, xonxoff=xonxoff, rtscts=rtscts, dsrdtr=dsrdtr, timeout=timeout, write_timeout=write_timeout) cli(client) if __name__ == "__main__": main() pymodbus-2.1.0/pymodbus/server/000077500000000000000000000000001335513467700165425ustar00rootroot00000000000000pymodbus-2.1.0/pymodbus/server/__init__.py000066400000000000000000000000101335513467700206420ustar00rootroot00000000000000''' ''' pymodbus-2.1.0/pymodbus/server/async.py000066400000000000000000000323231335513467700202340ustar00rootroot00000000000000""" Implementation of a Twisted Modbus Server ------------------------------------------ """ from binascii import b2a_hex from twisted.internet import protocol from twisted.internet.protocol import ServerFactory from twisted.internet import reactor from pymodbus.constants import Defaults from pymodbus.utilities import hexlify_packets 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, ModbusRtuFramer, ModbusAsciiFramer, ModbusBinaryFramer) from pymodbus.pdu import ModbusExceptions as merror from pymodbus.internal.ptwisted import InstallManagementConsole from pymodbus.compat import IS_PYTHON3 # --------------------------------------------------------------------------- # # Logging # --------------------------------------------------------------------------- # import logging _logger = logging.getLogger(__name__) # --------------------------------------------------------------------------- # # Modbus TCP Server # --------------------------------------------------------------------------- # class ModbusTcpProtocol(protocol.Protocol): """ Implements a modbus server in twisted """ 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, client=None) def connectionLost(self, reason): """ Callback for when a client disconnects :param reason: The client's reason for disconnecting """ _logger.debug("Client Disconnected: %s" % reason) 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('Data Received: ' + hexlify_packets(data)) if not self.factory.control.ListenOnly: units = self.factory.store.slaves() single = self.factory.store.single self.framer.processIncomingPacket(data, self._execute, single=single, unit=units) 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) response.transaction_id = request.transaction_id response.unit_id = request.unit_id self._send(response) 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) class ModbusServerFactory(ServerFactory): """ Builder class for a modbus server This also holds the server datastore so that it is persisted between connections """ protocol = ModbusTcpProtocol 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 # --------------------------------------------------------------------------- # class ModbusUdpProtocol(protocol.DatagramProtocol): """ Implements a modbus udp server in twisted """ 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) def datagramReceived(self, data, addr): """ Callback when we receive any data :param data: The data sent by the client """ _logger.debug("Client Connected [%s]" % addr) if _logger.isEnabledFor(logging.DEBUG): _logger.debug("Datagram Received: "+ hexlify_packets(data)) if not self.control.ListenOnly: continuation = lambda request: self._execute(request, addr) self.framer.processIncomingPacket(data, continuation) 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) 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 # --------------------------------------------------------------------------- # def _is_main_thread(): import threading if IS_PYTHON3: if threading.current_thread() != threading.main_thread(): _logger.debug("Running in spawned thread") return False else: if not isinstance(threading.current_thread(), threading._MainThread): _logger.debug("Running in spawned thread") return False _logger.debug("Running in Main thread") return True def StartTcpServer(context, identity=None, address=None, console=False, defer_reactor_run=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 :param defer_reactor_run: True/False defer running reactor.run() as part \ of starting server, to be explictly started by the user """ from twisted.internet import reactor address = address or ("", Defaults.Port) framer = kwargs.pop("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]) if not defer_reactor_run: reactor.run(installSignalHandlers=_is_main_thread()) def StartUdpServer(context, identity=None, address=None, defer_reactor_run=False, **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 :param defer_reactor_run: True/False defer running reactor.run() as part \ of starting server, to be explictly started by the user """ from twisted.internet import reactor address = address or ("", Defaults.Port) framer = kwargs.pop("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]) if not defer_reactor_run: reactor.run(installSignalHandlers=_is_main_thread()) def StartSerialServer(context, identity=None, framer=ModbusAsciiFramer, defer_reactor_run=False, **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 :param defer_reactor_run: True/False defer running reactor.run() as part \ of starting server, to be explictly started by the user """ 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) if not defer_reactor_run: reactor.run(installSignalHandlers=_is_main_thread()) def StopServer(): """ Helper method to stop Async Server """ from twisted.internet import reactor if _is_main_thread(): reactor.stop() _logger.debug("Stopping server from main thread") else: reactor.callFromThread(reactor.stop) _logger.debug("Stopping Server from another thread") # --------------------------------------------------------------------------- # # Exported symbols # --------------------------------------------------------------------------- # __all__ = [ "StartTcpServer", "StartUdpServer", "StartSerialServer", "StopServer" ] pymodbus-2.1.0/pymodbus/server/sync.py000066400000000000000000000534761335513467700201070ustar00rootroot00000000000000""" Implementation of a Threaded Modbus Server ------------------------------------------ """ from binascii import b2a_hex import serial import socket import traceback from pymodbus.constants import Defaults from pymodbus.utilities import hexlify_packets 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 # --------------------------------------------------------------------------- # class ModbusBaseRequestHandler(socketserver.BaseRequestHandler): """ Implements the modbus server protocol This uses the socketserver.BaseRequestHandler to implement the client handler. """ running = False framer = None 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, client=None) self.server.threads.append(self) def finish(self): """ Callback for when a client disconnects """ _logger.debug("Client Disconnected [%s:%s]" % self.client_address) self.server.threads.remove(self) 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 # ----------------------------------------------------------------------- # def handle(self): """ Callback when we receive any data """ raise NotImplementedException("Method not implemented" " by derived class") 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: units = self.server.context.slaves() single = self.server.context.single self.framer.processIncomingPacket(data, self.execute, units, single=single) except Exception as msg: # Since we only have a single socket, we cannot exit # Clear frame buffer self.framer.resetFrame() _logger.debug("Error: Socket error occurred %s" % msg) 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) class CustomSingleRequestHandler(ModbusSingleRequestHandler): def __init__(self, request, client_address, server): self.request = request self.client_address = client_address self.server = server self.running = True self.setup() class ModbusConnectedRequestHandler(ModbusBaseRequestHandler): """ Implements the modbus server protocol This uses the socketserver.BaseRequestHandler to implement the client handler for a connected protocol (TCP). """ def handle(self): """Callback when we receive any data, until self.running becomes False. Blocks indefinitely awaiting data. If shutdown is required, then the global socket.settimeout() 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. """ reset_frame = False while self.running: try: data = self.request.recv(1024) if not data: self.running = False if _logger.isEnabledFor(logging.DEBUG): _logger.debug('Handling data: ' + hexlify_packets(data)) # if not self.server.control.ListenOnly: units = self.server.context.slaves() single = self.server.context.single self.framer.processIncomingPacket(data, self.execute, units, single=single) except socket.timeout as msg: if _logger.isEnabledFor(logging.DEBUG): _logger.debug("Socket timeout occurred %s", msg) reset_frame = True 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 reset_frame = True finally: if reset_frame: self.framer.resetFrame() reset_frame = False 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) 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. """ socket = None def handle(self): """ Callback when we receive any data """ reset_frame = False while self.running: try: data, self.socket = self.request if not data: self.running = False if _logger.isEnabledFor(logging.DEBUG): _logger.debug('Handling data: ' + hexlify_packets(data)) # if not self.server.control.ListenOnly: units = self.server.context.slaves() single = self.server.context.single self.framer.processIncomingPacket(data, self.execute, units, single=single) except socket.timeout: pass except socket.error as msg: _logger.error("Socket error occurred %s" % msg) self.running = False reset_frame = True except Exception as msg: _logger.error(msg) self.running = False reset_frame = True finally: # Reset data after processing self.request = (None, self.socket) if reset_frame: self.framer.resetFrame() reset_frame = False 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.socket.sendto(pdu, self.client_address) # --------------------------------------------------------------------------- # # Server Implementations # --------------------------------------------------------------------------- # 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. """ def __init__(self, context, framer=None, identity=None, address=None, handler=None, allow_reuse_address=False, **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 allow_reuse_address: Whether the server will allow the reuse of an address. :param ignore_missing_slaves: True to not send errors on a request to a missing slave """ self.threads = [] self.allow_reuse_address = allow_reuse_address self.decoder = ServerDecoder() self.framer = framer or ModbusSocketFramer self.context = context or ModbusServerContext() self.control = ModbusControlBlock() self.address = address or ("", Defaults.Port) self.handler = handler or ModbusConnectedRequestHandler 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, self.handler) 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) 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) 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 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. """ 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.handler = handler or ModbusDisconnectedRequestHandler 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, self.handler) # self._BaseServer__shutdown_request = True 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) 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 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. """ handler = None 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 if self._connect(): self.is_running = True self._build_handler() 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 is not None 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 self.handler = CustomSingleRequestHandler(request, (self.device, self.device), self) def serve_forever(self): """ Callback for connecting a new client thread """ if self._connect(): _logger.debug("Started thread to serve client") if not self.handler: self._build_handler() while self.is_running: self.handler.handle() else: _logger.error("Error opening serial port , " "Unable to start server!!") def server_close(self): """ Callback for stopping the running server """ _logger.debug("Modbus server stopped") self.is_running = False self.handler.finish() self.handler.running = False self.handler = None self.socket.close() # --------------------------------------------------------------------------- # # Creation Factories # --------------------------------------------------------------------------- # 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 = kwargs.pop("framer", ModbusSocketFramer) server = ModbusTcpServer(context, framer, identity, address, **kwargs) server.serve_forever() 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() 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" ] pymodbus-2.1.0/pymodbus/transaction.py000066400000000000000000000435141335513467700201420ustar00rootroot00000000000000""" Collection of transaction based abstractions """ import struct import socket from threading import RLock from functools import partial from pymodbus.exceptions import ModbusIOException, NotImplementedException from pymodbus.exceptions import InvalidMessageReceivedException from pymodbus.constants import Defaults from pymodbus.framer.ascii_framer import ModbusAsciiFramer from pymodbus.framer.rtu_framer import ModbusRtuFramer from pymodbus.framer.socket_framer import ModbusSocketFramer from pymodbus.framer.binary_framer import ModbusBinaryFramer from pymodbus.utilities import hexlify_packets, ModbusTransactionState from pymodbus.compat import iterkeys, byte2int # Python 2 compatibility. try: TimeoutError except NameError: TimeoutError = socket.timeout # --------------------------------------------------------------------------- # # 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) or 1 self._transaction_lock = RLock() self._no_response_devices = [] 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 = 7 # start(1)+ Address(2), LRC(2) + end(2) elif isinstance(self.client.framer, ModbusBinaryFramer): self.base_adu_size = 5 # start(1) + Address(1), CRC(2) + end(1) else: self.base_adu_size = -1 def _calculate_response_length(self, expected_pdu_size): if self.base_adu_size == -1: return None else: return self.base_adu_size + expected_pdu_size def _calculate_exception_length(self): """ Returns the length of the Modbus Exception Response according to the type of Framer. """ if isinstance(self.client.framer, ModbusSocketFramer): return self.base_adu_size + 2 # Fcode(1), ExcecptionCode(1) elif isinstance(self.client.framer, ModbusAsciiFramer): return self.base_adu_size + 4 # Fcode(2), ExcecptionCode(2) elif isinstance(self.client.framer, (ModbusRtuFramer, ModbusBinaryFramer)): return self.base_adu_size + 2 # Fcode(1), ExcecptionCode(1) return None def execute(self, request): """ Starts the producer to send the next request to consumer.write(Frame(request)) """ with self._transaction_lock: try: _logger.debug("Current transaction state - {}".format( ModbusTransactionState.to_string(self.client.state)) ) retries = self.retries request.transaction_id = self.getNextTID() _logger.debug("Running transaction %d" % request.transaction_id) _buffer = hexlify_packets(self.client.framer._buffer) if _buffer: _logger.debug("Clearing current Frame : - {}".format(_buffer)) self.client.framer.resetFrame() expected_response_length = None if not isinstance(self.client.framer, ModbusSocketFramer): if hasattr(request, "get_response_pdu_size"): response_pdu_size = request.get_response_pdu_size() if isinstance(self.client.framer, ModbusAsciiFramer): response_pdu_size = response_pdu_size * 2 if response_pdu_size: expected_response_length = self._calculate_response_length(response_pdu_size) if request.unit_id in self._no_response_devices: full = True else: full = False c_str = str(self.client) if "modbusudpclient" in c_str.lower().strip(): full = True if not expected_response_length: expected_response_length = Defaults.ReadSize response, last_exception = self._transact(request, expected_response_length, full=full ) if not response and ( request.unit_id not in self._no_response_devices): self._no_response_devices.append(request.unit_id) elif request.unit_id in self._no_response_devices and response: self._no_response_devices.remove(request.unit_id) if not response and self.retry_on_empty and retries: while retries > 0: if hasattr(self.client, "state"): _logger.debug("RESETTING Transaction state to " "'IDLE' for retry") self.client.state = ModbusTransactionState.IDLE _logger.debug("Retry on empty - {}".format(retries)) response, last_exception = self._transact( request, expected_response_length ) if not response: retries -= 1 continue # Remove entry self._no_response_devices.remove(request.unit_id) break addTransaction = partial(self.addTransaction, tid=request.transaction_id) self.client.framer.processIncomingPacket(response, addTransaction, request.unit_id) response = self.getTransaction(request.transaction_id) if not response: if len(self.transactions): response = self.getTransaction(tid=0) else: last_exception = last_exception or ( "No Response received from the remote unit" "/Unable to decode response") response = ModbusIOException(last_exception, request.function_code) if hasattr(self.client, "state"): _logger.debug("Changing transaction state from " "'PROCESSING REPLY' to " "'TRANSACTION_COMPLETE'") self.client.state = ( ModbusTransactionState.TRANSACTION_COMPLETE) return response except ModbusIOException as ex: # Handle decode errors in processIncomingPacket method _logger.exception(ex) self.client.state = ModbusTransactionState.TRANSACTION_COMPLETE return ex def _transact(self, packet, response_length, full=False): """ Does a Write and Read transaction :param packet: packet to be sent :param response_length: Expected response length :param full: the target device was notorious for its no response. Dont waste time this time by partial querying :return: response """ last_exception = None try: self.client.connect() packet = self.client.framer.buildPacket(packet) if _logger.isEnabledFor(logging.DEBUG): _logger.debug("SEND: " + hexlify_packets(packet)) size = self._send(packet) if size: _logger.debug("Changing transaction state from 'SENDING' " "to 'WAITING FOR REPLY'") self.client.state = ModbusTransactionState.WAITING_FOR_REPLY result = self._recv(response_length, full) if _logger.isEnabledFor(logging.DEBUG): _logger.debug("RECV: " + hexlify_packets(result)) except (socket.error, ModbusIOException, InvalidMessageReceivedException) as msg: self.client.close() _logger.debug("Transaction failed. (%s) " % msg) last_exception = msg result = b'' return result, last_exception def _send(self, packet): return self.client.framer.sendPacket(packet) def _recv(self, expected_response_length, full): total = None if not full: exception_length = self._calculate_exception_length() if isinstance(self.client.framer, ModbusSocketFramer): min_size = 8 elif isinstance(self.client.framer, ModbusRtuFramer): min_size = 2 elif isinstance(self.client.framer, ModbusAsciiFramer): min_size = 5 elif isinstance(self.client.framer, ModbusBinaryFramer): min_size = 3 else: min_size = expected_response_length read_min = self.client.framer.recvPacket(min_size) if len(read_min) != min_size: raise InvalidMessageReceivedException( "Incomplete message received, expected at least %d bytes " "(%d received)" % (min_size, len(read_min)) ) if read_min: if isinstance(self.client.framer, ModbusSocketFramer): func_code = byte2int(read_min[-1]) elif isinstance(self.client.framer, ModbusRtuFramer): func_code = byte2int(read_min[-1]) elif isinstance(self.client.framer, ModbusAsciiFramer): func_code = int(read_min[3:5], 16) elif isinstance(self.client.framer, ModbusBinaryFramer): func_code = byte2int(read_min[-1]) else: func_code = -1 if func_code < 0x80: # Not an error if isinstance(self.client.framer, ModbusSocketFramer): # Ommit UID, which is included in header size h_size = self.client.framer._hsize length = struct.unpack(">H", read_min[4:6])[0] - 1 expected_response_length = h_size + length if expected_response_length is not None: expected_response_length -= min_size total = expected_response_length + min_size else: expected_response_length = exception_length - min_size total = expected_response_length + min_size else: total = expected_response_length else: read_min = b'' total = expected_response_length result = self.client.framer.recvPacket(expected_response_length) result = read_min + result actual = len(result) if total is not None and actual != total: _logger.debug("Incomplete message received, " "Expected {} bytes Recieved " "{} bytes !!!!".format(total, actual)) if self.client.state != ModbusTransactionState.PROCESSING_REPLY: _logger.debug("Changing transaction state from " "'WAITING FOR REPLY' to 'PROCESSING REPLY'") self.client.state = ModbusTransactionState.PROCESSING_REPLY return result 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)() class DictTransactionManager(ModbusTransactionManager): """ Impelements a transaction for a manager where the results are keyed based on the supplied transaction id. """ 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) def __iter__(self): """ Iterater over the current managed transactions :returns: An iterator of the managed transactions """ return iterkeys(self.transactions) 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 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) 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) class FifoTransactionManager(ModbusTransactionManager): """ Impelements a transaction for a manager where the results are returned in a FIFO manner. """ 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 = [] def __iter__(self): """ Iterater over the current managed transactions :returns: An iterator of the managed transactions """ return iter(self.transactions) 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 is not None else request.transaction_id _logger.debug("Adding transaction %d" % tid) self.transactions.append(request) 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 """ return self.transactions.pop(0) if self.transactions else None 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) # --------------------------------------------------------------------------- # # Exported symbols # --------------------------------------------------------------------------- # __all__ = [ "FifoTransactionManager", "DictTransactionManager", "ModbusSocketFramer", "ModbusRtuFramer", "ModbusAsciiFramer", "ModbusBinaryFramer", ] pymodbus-2.1.0/pymodbus/utilities.py000066400000000000000000000163131335513467700176250ustar00rootroot00000000000000""" Modbus Utilities ----------------- A collection of utilities for packing data, unpacking data computing checksums, and decode checksums. """ from pymodbus.compat import int2byte, byte2int, IS_PYTHON3 from six import string_types class ModbusTransactionState(object): """ Modbus Client States """ IDLE = 0 SENDING = 1 WAITING_FOR_REPLY = 2 WAITING_TURNAROUND_DELAY = 3 PROCESSING_REPLY = 4 PROCESSING_ERROR = 5 TRANSACTION_COMPLETE = 6 @classmethod def to_string(cls, state): states = { ModbusTransactionState.IDLE: "IDLE", ModbusTransactionState.SENDING: "SENDING", ModbusTransactionState.WAITING_FOR_REPLY: "WAITING_FOR_REPLY", ModbusTransactionState.WAITING_TURNAROUND_DELAY: "WAITING_TURNAROUND_DELAY", ModbusTransactionState.PROCESSING_REPLY: "PROCESSING_REPLY", ModbusTransactionState.PROCESSING_ERROR: "PROCESSING_ERROR", ModbusTransactionState.TRANSACTION_COMPLETE: "TRANSACTION_COMPLETE" } return states.get(state, None) # --------------------------------------------------------------------------- # # Helpers # --------------------------------------------------------------------------- # 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)() 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 # --------------------------------------------------------------------------- # 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 0 < i < 8: packed >>= (7 - i) ret += int2byte(packed) return ret 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): if IS_PYTHON3: value = byte2int(int(string[byte])) else: value = byte2int(string[byte]) for _ in range(8): bits.append((value & 1) == 1) value >>= 1 return bits def make_byte_string(s): """ Returns byte string from a given string, python3 specific fix :param s: :return: """ if IS_PYTHON3 and isinstance(s, string_types): s = s.encode() return s # --------------------------------------------------------------------------- # # Error Detection Functions # --------------------------------------------------------------------------- # 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() 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 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 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 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 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 def hexlify_packets(packet): """ Returns hex representation of bytestring recieved :param packet: :return: """ if not packet: return '' return " ".join([hex(byte2int(x)) for x in packet]) # --------------------------------------------------------------------------- # # Exported symbols # --------------------------------------------------------------------------- # __all__ = [ 'pack_bitstring', 'unpack_bitstring', 'default', 'computeCRC', 'checkCRC', 'computeLRC', 'checkLRC', 'rtuFrameSize' ] pymodbus-2.1.0/pymodbus/version.py000066400000000000000000000030171335513467700172740ustar00rootroot00000000000000""" Handle the version information here; you should only have to change the version tuple. Since we are using twisted's version class, we can also query the svn version as well using the local .entries file. """ class Version(object): def __init__(self, package, major, minor, micro, pre=None): """ :param package: Name of the package that this is a version of. :param major: The major version number. :param minor: The minor version number. :param micro: The micro version number. :param pre: The pre release tag """ self.package = package self.major = major self.minor = minor self.micro = micro self.pre = pre def short(self): """ Return a string in canonical short version format ...
        """
        if self.pre:
            return '%d.%d.%d.%s' % (self.major, self.minor, self.micro, self.pre)
        else:
            return '%d.%d.%d' % (self.major, self.minor, self.micro)

    def __str__(self):
        """ Returns a string representation of the object

        :returns: A string representation of this object
        """
        return '[%s, version %s]' % (self.package, self.short())


version = Version('pymodbus', 2, 1, 0)


version.__name__ = 'pymodbus'  # fix epydoc error

# --------------------------------------------------------------------------- #
# Exported symbols
# --------------------------------------------------------------------------- #

__all__ = ["version"]
pymodbus-2.1.0/requirements-checks.txt000066400000000000000000000001551335513467700201150ustar00rootroot00000000000000# Python packages required to run `make check'.
flake8 >= 2.6.0
flake8-docstrings >= 0.2.8
pyflakes >= 1.2.3
pymodbus-2.1.0/requirements-docs.txt000066400000000000000000000010211335513467700175760ustar00rootroot00000000000000# Python packages required to run `make docs'.
cryptography>= 2.3 # Required to parse some files
humanfriendly==4.4.1
pyasn1==0.4.2       # Required to parse some files
pyserial-asyncio==0.4.0;python_version>="3.4"
pyserial==3.4       # Required to parse some files
redis==2.10.6       # Required to parse some files
recommonmark==0.4.0
Sphinx==1.6.5
sphinx-rtd-theme==0.2.4
SQLAlchemy==1.1.15  # Required to parse some files
tornado==4.5.2      # Required to parse some files
twisted>= 12.2.0    # Required to parse some files

pymodbus-2.1.0/requirements-tests.txt000066400000000000000000000005011335513467700200120ustar00rootroot00000000000000capturer >= 2.2
coverage >= 4.2
cryptography>=1.8.1
mock >= 1.0.1
pyserial-asyncio==0.4.0;python_version>="3.4"
pep8>=1.7.0
pyasn1>=0.2.3
pycrypto>=2.6.1
pyserial>=3.4
pytest-cov>=2.5.1
pytest>=3.5.0
redis>=2.10.5
sqlalchemy>=1.1.15
#wsgiref>=0.1.2
verboselogs >= 1.5
tornado==4.5.3
Twisted>=17.1.0
zope.interface>=4.4.0
pymodbus-2.1.0/requirements.txt000066400000000000000000000033771335513467700166700ustar00rootroot00000000000000six==1.11.0
# -------------------------------------------------------------------
# if want to use the pymodbus serial stack, uncomment these
# -------------------------------------------------------------------
# pyserial>=3.4
# -------------------------------------------------------------------
# if you want to run the tests and code coverage, uncomment these
# -------------------------------------------------------------------
#coverage==4.4
#mock==2.0.0
#nose==1.3.7
#pep8==1.7.0
# -------------------------------------------------------------------
# if you want to use the Twisted asynchronous version, uncomment these
# -------------------------------------------------------------------
#Twisted==17.1.0
#zope.interface==4.4.0
#pyasn1==0.2.3
#pycrypto==2.6.1
#wsgiref==0.1.2
#cryptography==1.8.1

# -------------------------------------------------------------------
# if you want to use the Tornado asynchronous version, uncomment these
# -------------------------------------------------------------------
# tornado==4.5.2

# -------------------------------------------------------------------
# if you want to use the Asyncio asynchronous version, uncomment these
# -------------------------------------------------------------------
# pyserial-asyncio==0.4.0;python_version>="3.4"

# -------------------------------------------------------------------
# if you want to build the documentation, uncomment these
# -------------------------------------------------------------------
#Jinja2==2.9.6
#Pygments==2.2.0
#Sphinx==1.5.5
#docutils==0.13.1
#pydoctor==16.3.0

# -------------------------------------------------------------------
# if you want to use pymodbus REPL
# -------------------------------------------------------------------
# click>=6.7
# prompt-toolkit==2.0.4pymodbus-2.1.0/scripts/000077500000000000000000000000001335513467700150615ustar00rootroot00000000000000pymodbus-2.1.0/scripts/travis.sh000077500000000000000000000003461335513467700167330ustar00rootroot00000000000000#!/bin/bash -e

if [ "$TRAVIS_OS_NAME" = osx ]; then
  VIRTUAL_ENV="$HOME/.virtualenvs/python2.7"
  if [ ! -x "$VIRTUAL_ENV/bin/python" ]; then
    virtualenv "$VIRTUAL_ENV"
  fi
  source "$VIRTUAL_ENV/bin/activate"
fi

eval "$@"
pymodbus-2.1.0/setup.cfg000066400000000000000000000005701335513467700152150ustar00rootroot00000000000000[aliases]
upload_docs = build_sphinx upload_docs
package = build_apidocs build_sphinx sdist
test=pytest

[egg_info]
#tag_build = dev
tag_svn_revision = false


[build-sphinx]
source-dir = doc/sphinx/
build-dir  = doc/sphinx/build
all_files  = 1

[upload_docs]
upload-dir = build/sphinx/html

[bdist_wheel]
universal=1

[tool:pytest]
testpaths = test
addopts = -p no:warnings

pymodbus-2.1.0/setup.py000066400000000000000000000065241335513467700151130ustar00rootroot00000000000000#!/usr/bin/env python
"""
Installs pymodbus using distutils

Run:
    python setup.py install
to install the package from the source archive.

For information about setuptools
http://peak.telecommunity.com/DevCenter/setuptools#new-and-changed-setup-keywords
"""

# --------------------------------------------------------------------------- #
# initialization
# --------------------------------------------------------------------------- #
try:  # if not installed, install and proceed
    from setuptools import setup, find_packages
except ImportError:
    from ez_setup import use_setuptools
    use_setuptools()
    from setuptools import setup, find_packages

try:
    from setup_commands import command_classes
except ImportError:
    command_classes={}
from pymodbus import __version__, __author__, __maintainer__

with open('requirements.txt') as reqs:
    install_requires = [
        line for line in reqs.read().split('\n')
        if (line and not line.startswith('--'))
    ]
    install_requires.append("pyserial >= 3.4")
# --------------------------------------------------------------------------- #
# configuration
# --------------------------------------------------------------------------- #
setup(
    name="pymodbus",
    version=__version__,
    description="A fully featured modbus protocol stack in python",
    long_description="""
        Pymodbus aims to be a fully implemented modbus protocol stack 
        implemented using twisted/asyncio/tornado.  
        Its orignal goal was to allow simulation of thousands of modbus devices
        on a single machine for monitoring software testing.
    """,
    classifiers=[
        'Development Status :: 4 - Beta',
        'Environment :: Console',
        'Environment :: X11 Applications :: GTK',
        'Framework :: Twisted',
        'Intended Audience :: Developers',
        'License :: OSI Approved :: BSD License',
        'Operating System :: POSIX :: Linux',
        'Operating System :: Unix',
        'Programming Language :: Python',
        'Topic :: System :: Networking',
        'Topic :: Utilities'
    ],
    keywords='modbus, twisted, scada',
    author=__author__,
    author_email='bashwork@gmail.com',
    maintainer=__maintainer__,
    maintainer_email='otlasanju@gmail.com',
    url='https://github.com/riptideio/pymodbus/',
    license='BSD',
    packages=find_packages(exclude=['examples', 'test']),
    exclude_package_data={'': ['examples', 'test', 'tools', 'doc']},
    py_modules=['ez_setup'],
    platforms=['Linux', 'Mac OS X', 'Win'],
    include_package_data=True,
    zip_safe=True,
    install_requires=install_requires,
    extras_require={
        'quality': [
            'coverage >= 3.5.3',
            'nose >= 1.2.1',
            'mock >= 1.0.0',
            'pep8 >= 1.3.3'
        ],
        'documents': ['sphinx >= 1.1.3',
                      'sphinx_rtd_theme',
                      'humanfriendly'],
        'twisted': [
            'twisted >= 12.2.0',
            'pyasn1 >= 0.1.4',
            'pycrypto >= 2.6'
        ],
        'tornado': [
            'tornado >= 4.5.3'
        ],
        'repl': [
            'click>=6.7',
            'prompt-toolkit==2.0.4',
            'pygments==2.2.0'
        ]
    },
    entry_points={
        'console_scripts': ['pymodbus.console=pymodbus.repl.main:main'],
    },
    test_suite='nose.collector',
    cmdclass=command_classes,
)

pymodbus-2.1.0/setup_commands.py000077500000000000000000000127051335513467700167750ustar00rootroot00000000000000from distutils.core import Command
import sys, os, shutil

# --------------------------------------------------------------------------- #
# Extra Commands
# --------------------------------------------------------------------------- #


class BuildApiDocsCommand(Command):
    """ Helper command to build the available api documents
    This scans all the subdirectories under api and runs the
    build.py script underneath trying to build the api
    documentation for the given format.
    """
    description  = "build all the projects api documents"
    user_options = []

    def initialize_options(self):
        """ options setup """
        if not os.path.exists('./build'):
            os.mkdir('./build')

    def finalize_options(self):
        """ options teardown """
        pass

    def run(self):
        """ command runner """
        old_cwd = os.getcwd()
        directories = (d for d in os.listdir('./doc/api') if not d.startswith('.'))
        for entry in directories:
            os.chdir('./doc/api/%s' % entry)
            os.system('python build.py')
            os.chdir(old_cwd)


class DeepCleanCommand(Command):
    """
    Helper command to return the directory to a completely
    clean state.
    """
    description  = "clean everything that we don't want"
    user_options = []
    trash = ['build', 'dist', 'pymodbus.egg-info',
             os.path.join(os.path.join('doc', 'sphinx'), 'build'),
             ]

    def initialize_options(self):
        """ options setup """
        pass

    def finalize_options(self):
        pass

    def run(self):
        """ command runner """
        self._delete_pyc_files()
        self._delete_trash_dirs()

    def _delete_trash_dirs(self):
        """ remove all directories created in building """
        self._delete_pyc_files()
        for directory in self.trash:
            if os.path.exists(directory):
                shutil.rmtree(directory)

    @staticmethod
    def _delete_pyc_files():
        """ remove all python cache files """
        for root, dirs, files in os.walk('.'):
            for file in files:
                if file.endswith('.pyc'):
                    os.remove(os.path.join(root,file))


class LintCommand(Command):
    """
    Helper command to perform a lint scan of the
    sourcecode and return the results.
    """
    description = "perform a lint scan of the code"
    user_options = []

    def initialize_options(self):
        """ options setup """
        if not os.path.exists('./build'):
            os.mkdir('./build')

    def finalize_options(self):
        pass

    def run(self):
        """ command runner """
        scanners = [s for s in dir(self) if s.find('__try') >= 0]
        for scanner in scanners:
            if getattr(self, scanner)():
                break

    def _try_pyflakes(self):
        try:
            from pyflakes.scripts.pyflakes import main
            sys.argv = """pyflakes pymodbus""".split()
            main()
            return True
        except Exception:
            return False

    def _try_pychecker(self):
        try:
            import pychecker
            sys.argv = """pychecker pymodbus/*.py""".split()
            main()
            return True
        except: return False

    def _try_pylint(self):
        try:
            import pylint
            sys.argv = """pylint pymodbus/*.py""".split()
            main()
            return True
        except: return False


class Python3Command(Command):
    """ Helper command to scan for potential python 3
    errors.

    ./setup.py scan_2to3 > build/diffs_2to3 build/report_2to3
    """
    description = "perform 2to3 scan of the code"
    user_options = []
    directories = ['pymodbus', 'test', 'examples']

    def initialize_options(self):
        """ options setup """
        if not os.path.exists('./build'):
            os.mkdir('./build')

    def finalize_options(self):
        pass

    def run(self):
        """ command runner """
        self._run_python3()

    def _run_python3(self):
        try:
            from lib2to3.main import main
            sys.argv = ['2to3'] + self.directories
            main("lib2to3.fixes")
            return True
        except: return False


class Pep8Command(Command):
    """
    Helper command to scan for potential pep8 violations
    """
    description = "perform pep8 scan of the code"
    user_options = []
    directories = ['pymodbus']

    def initialize_options(self):
        """ options setup """
        if not os.path.exists('./build'):
            os.mkdir('./build')

    def finalize_options(self):
        pass

    def run(self):
        """ command runner """
        self._run_pep8()

    def _run_pep8(self):
        try:
            from pep8 import _main as main
            sys.argv = """pep8 --repeat --count --statistics
            """.split() + self.directories
            main()
            return True
        except Exception:
            return False

# --------------------------------------------------------------------------- #
# Command Configuration
# --------------------------------------------------------------------------- #


command_classes = {
    'deep_clean': DeepCleanCommand,
    'build_apidocs': BuildApiDocsCommand,
    'lint': LintCommand,
    'scan_2to3': Python3Command,
    'pep8': Pep8Command,
}

# --------------------------------------------------------------------------- #
# Export command list
# --------------------------------------------------------------------------- #
__all__ = ['command_classes']
pymodbus-2.1.0/test/000077500000000000000000000000001335513467700143515ustar00rootroot00000000000000pymodbus-2.1.0/test/__init__.py000066400000000000000000000000001335513467700164500ustar00rootroot00000000000000pymodbus-2.1.0/test/asyncio_test_helper.py000066400000000000000000000033431335513467700207710ustar00rootroot00000000000000from pymodbus.compat import IS_PYTHON3
if IS_PYTHON3:
    import functools


    def _yielded_return(return_value, *args):
        """Generator factory function with return value."""

        def _():
            """Actual generator producing value."""
            yield
            return return_value

        # return new generator each time this function is called:
        return _()


    def return_as_coroutine(return_value=None):
        """Creates a function that behaves like an asyncio coroutine and returns the given value.

        Typically used as a side effect of a mocked coroutine like this:

            # in module mymod:
            @asyncio.coroutine
            def my_coro_under_test():
                yield from asyncio.sleep(1)
                yield from asyncio.sleep(2)
                return 42

            # in test module:
            @mock.patch('mymod.asyncio.sleep')
            def test_it(mock_sleep):
                mock_sleep.side_effect = return_as_coroutine()
                result = run_coroutine(my_coro_under_test)
                assert mock_sleep.call_count == 2
                assert mock_sleep.call_args_list == [mock.call(1), mock.call(2)]
                assert result == 42
        """
        return functools.partial(_yielded_return, return_value)


    def run_coroutine(coro):
        """Runs a coroutine as top-level task by iterating through all yielded steps."""

        result = None
        try:
            # step through all parts of coro without scheduling anything else:
            while True:
                result = coro.send(result)
        except StopIteration as ex:
            # coro reached end pass on its return value:
            return ex.value
        except:
            raise
pymodbus-2.1.0/test/modbus_mocks.py000066400000000000000000000014671335513467700174200ustar00rootroot00000000000000from pymodbus.interfaces import IModbusSlaveContext

#---------------------------------------------------------------------------#
# Mocks
#---------------------------------------------------------------------------#
class mock(object): pass

class MockContext(IModbusSlaveContext):

    def __init__(self, valid=False, default=True):
        self.valid = valid
        self.default = default

    def validate(self, fx, address, count):
        return self.valid

    def getValues(self, fx, address, count):
        return [self.default] * count

    def setValues(self, fx, address, count):
        pass

class FakeList(object):
    ''' todo, replace with magic mock '''

    def __init__(self, size):
        self.size = size

    def __len__(self):
        return self.size

    def __iter__(self):
        return []

pymodbus-2.1.0/test/test_all_messages.py000066400000000000000000000066321335513467700204300ustar00rootroot00000000000000#!/usr/bin/env python
import unittest
from pymodbus.constants import Defaults
from pymodbus.bit_read_message import *
from pymodbus.bit_write_message import *
from pymodbus.register_read_message import *
from pymodbus.register_write_message import *

#---------------------------------------------------------------------------#
# Fixture
#---------------------------------------------------------------------------#
class ModbusAllMessagesTests(unittest.TestCase):

    #-----------------------------------------------------------------------#
    # Setup/TearDown
    #-----------------------------------------------------------------------#

    def setUp(self):
        '''
        Initializes the test environment and builds request/result
        encoding pairs
        '''
        arguments = {
            'read_address': 1, 'read_count': 1,
            'write_address': 1, 'write_registers': 1
        }
        self.requests = [
            lambda unit: ReadCoilsRequest(1, 5, unit=unit),
            lambda unit: ReadDiscreteInputsRequest(1, 5, unit=unit),
            lambda unit: WriteSingleCoilRequest(1, 1, unit=unit),
            lambda unit: WriteMultipleCoilsRequest(1, [1], unit=unit),
            lambda unit: ReadHoldingRegistersRequest(1, 5, unit=unit),
            lambda unit: ReadInputRegistersRequest(1, 5, unit=unit),
            lambda unit: ReadWriteMultipleRegistersRequest(unit=unit, **arguments),
            lambda unit: WriteSingleRegisterRequest(1, 1, unit=unit),
            lambda unit: WriteMultipleRegistersRequest(1, [1], unit=unit),
        ]
        self.responses = [
            lambda unit: ReadCoilsResponse([1], unit=unit),
            lambda unit: ReadDiscreteInputsResponse([1], unit=unit),
            lambda unit: WriteSingleCoilResponse(1, 1, unit=unit),
            lambda unit: WriteMultipleCoilsResponse(1, [1], unit=unit),
            lambda unit: ReadHoldingRegistersResponse([1], unit=unit),
            lambda unit: ReadInputRegistersResponse([1], unit=unit),
            lambda unit: ReadWriteMultipleRegistersResponse([1], unit=unit),
            lambda unit: WriteSingleRegisterResponse(1, 1, unit=unit),
            lambda unit: WriteMultipleRegistersResponse(1, 1, unit=unit),
        ]

    def tearDown(self):
        ''' Cleans up the test environment '''
        pass

    def testInitializingSlaveAddressRequest(self):
        ''' Test that every request can initialize the unit id '''
        unit_id = 0x12
        for factory in self.requests:
            request = factory(unit_id)
            self.assertEqual(request.unit_id, unit_id)

    def testInitializingSlaveAddressResponse(self):
        ''' Test that every response can initialize the unit id '''
        unit_id = 0x12
        for factory in self.responses:
            response = factory(unit_id)
            self.assertEqual(response.unit_id, unit_id)

    def testForwardingKwargsToPdu(self):
        ''' Test that the kwargs are forwarded to the pdu correctly '''
        request = ReadCoilsRequest(1,5, unit=0x12, transaction=0x12, protocol=0x12)
        self.assertEqual(request.unit_id, 0x12)
        self.assertEqual(request.transaction_id, 0x12)
        self.assertEqual(request.protocol_id, 0x12)

        request = ReadCoilsRequest(1,5)
        self.assertEqual(request.unit_id, Defaults.UnitId)
        self.assertEqual(request.transaction_id, Defaults.TransactionId)
        self.assertEqual(request.protocol_id, Defaults.ProtocolId)
pymodbus-2.1.0/test/test_bit_read_messages.py000066400000000000000000000115631335513467700214300ustar00rootroot00000000000000#!/usr/bin/env python
"""
Bit Message Test Fixture
--------------------------------
This fixture tests the functionality of all the 
bit based request/response messages:

* Read/Write Discretes
* Read Coils
"""
import unittest, struct
from pymodbus.bit_read_message import *
from pymodbus.bit_read_message import ReadBitsRequestBase
from pymodbus.bit_read_message import ReadBitsResponseBase
from pymodbus.exceptions import *
from pymodbus.pdu import ModbusExceptions
from pymodbus.compat import iteritems

from .modbus_mocks import MockContext
res = [True] * 21
res.extend([False] * 3)
#---------------------------------------------------------------------------#
# Fixture
#---------------------------------------------------------------------------#
class ModbusBitMessageTests(unittest.TestCase):

    #-----------------------------------------------------------------------#
    # Setup/TearDown
    #-----------------------------------------------------------------------#

    def setUp(self):
        """
        Initializes the test environment and builds request/result
        encoding pairs
        """
        pass

    def tearDown(self):
        """ Cleans up the test environment """
        pass

    def testReadBitBaseClassMethods(self):
        """ Test basic bit message encoding/decoding """
        handle = ReadBitsRequestBase(1, 1)
        msg    = "ReadBitRequest(1,1)"
        self.assertEqual(msg, str(handle))
        handle = ReadBitsResponseBase([1,1])
        msg    = "ReadBitResponse(2)"
        self.assertEqual(msg, str(handle))

    def testBitReadBaseRequestEncoding(self):
        """ Test basic bit message encoding/decoding """
        for i in range(20):
            handle = ReadBitsRequestBase(i, i)
            result = struct.pack('>HH',i, i)
            self.assertEqual(handle.encode(), result)
            handle.decode(result)
            self.assertEqual((handle.address, handle.count), (i,i))

    def testBitReadBaseResponseEncoding(self):
        """ Test basic bit message encoding/decoding """
        for i in range(20):
            input  = [True] * i
            handle = ReadBitsResponseBase(input)
            result = handle.encode()
            handle.decode(result)
            self.assertEqual(handle.bits[:i], input)

    def testBitReadBaseResponseHelperMethods(self):
        """ Test the extra methods on a ReadBitsResponseBase """
        input  = [False] * 8
        handle = ReadBitsResponseBase(input)
        for i in [1,3,5]: handle.setBit(i, True)
        for i in [1,3,5]: handle.resetBit(i)
        for i in range(8):
            self.assertEqual(handle.getBit(i), False)

    def testBitReadBaseRequests(self):
        """ Test bit read request encoding """
        messages = {
            ReadBitsRequestBase(12, 14)        : b'\x00\x0c\x00\x0e',
            ReadBitsResponseBase([1,0,1,1,0])  : b'\x01\x0d'
        }
        for request, expected in iteritems(messages):
            self.assertEqual(request.encode(), expected)

    def testBitReadMessageExecuteValueErrors(self):
        """ Test bit read request encoding """
        context = MockContext()
        requests = [
            ReadCoilsRequest(1,0x800),
            ReadDiscreteInputsRequest(1,0x800),
        ]
        for request in requests:
            result = request.execute(context)
            self.assertEqual(ModbusExceptions.IllegalValue,
                result.exception_code)

    def testBitReadMessageExecuteAddressErrors(self):
        """ Test bit read request encoding """
        context = MockContext()
        requests = [
            ReadCoilsRequest(1,5),
            ReadDiscreteInputsRequest(1,5),
        ]
        for request in requests:
            result = request.execute(context)
            self.assertEqual(ModbusExceptions.IllegalAddress, result.exception_code)

    def testBitReadMessageExecuteSuccess(self):
        """ Test bit read request encoding """
        context = MockContext()
        context.validate = lambda a,b,c: True
        requests = [
            ReadCoilsRequest(1,5),
            ReadDiscreteInputsRequest(1,5),
        ]
        for request in requests:
            result = request.execute(context)
            self.assertEqual(result.bits, [True] * 5)

    def testBitReadMessageGetResponsePDU(self):
        requests = {
            ReadCoilsRequest(1,5): 3,
            ReadCoilsRequest(1, 8): 3,
            ReadCoilsRequest(0, 16): 4,
            ReadDiscreteInputsRequest(1, 21): 5,
            ReadDiscreteInputsRequest(1, 24): 5,
            ReadDiscreteInputsRequest(1, 1900): 240,
        }
        for request, expected in iteritems(requests):
            pdu_len = request.get_response_pdu_size()
            self.assertEqual(pdu_len, expected)


#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
    unittest.main()
pymodbus-2.1.0/test/test_bit_write_messages.py000066400000000000000000000112671335513467700216500ustar00rootroot00000000000000#!/usr/bin/env python
'''
Bit Message Test Fixture
--------------------------------
This fixture tests the functionality of all the 
bit based request/response messages:

* Read/Write Discretes
* Read Coils
'''
import unittest
from pymodbus.bit_write_message import *
from pymodbus.exceptions import *
from pymodbus.pdu import ModbusExceptions
from pymodbus.compat import iteritems

from .modbus_mocks import MockContext, FakeList

#---------------------------------------------------------------------------#
# Fixture
#---------------------------------------------------------------------------#
class ModbusBitMessageTests(unittest.TestCase):

    #-----------------------------------------------------------------------#
    # Setup/TearDown
    #-----------------------------------------------------------------------#

    def setUp(self):
        '''
        Initializes the test environment and builds request/result
        encoding pairs
        '''
        pass

    def tearDown(self):
        ''' Cleans up the test environment '''
        pass

    def testBitWriteBaseRequests(self):
        messages = {
            WriteSingleCoilRequest(1, 0xabcd)      : b'\x00\x01\xff\x00',
            WriteSingleCoilResponse(1, 0xabcd)     : b'\x00\x01\xff\x00',
            WriteMultipleCoilsRequest(1, [True]*5) : b'\x00\x01\x00\x05\x01\x1f',
            WriteMultipleCoilsResponse(1, 5)       : b'\x00\x01\x00\x05',
        }
        for request, expected in iteritems(messages):
            self.assertEqual(request.encode(), expected)

    def testBitWriteMessageGetResponsePDU(self):
        requests = {
            WriteSingleCoilRequest(1, 0xabcd): 5
        }
        for request, expected in iteritems(requests):
            pdu_len = request.get_response_pdu_size()
            self.assertEqual(pdu_len, expected)

    def testWriteMultipleCoilsRequest(self):
        request = WriteMultipleCoilsRequest(1, [True]*5)
        request.decode(b'\x00\x01\x00\x05\x01\x1f')
        self.assertEqual(request.byte_count, 1)
        self.assertEqual(request.address, 1)
        self.assertEqual(request.values, [True]*5)

    def testInvalidWriteMultipleCoilsRequest(self):
        request = WriteMultipleCoilsRequest(1, None)
        self.assertEqual(request.values, [])

    def testWriteSingleCoilRequestEncode(self):
        request = WriteSingleCoilRequest(1, False)
        self.assertEqual(request.encode(), b'\x00\x01\x00\x00')

    def testWriteSingleCoilExecute(self):
        context = MockContext(False, default=True)
        request = WriteSingleCoilRequest(2, True)
        result  = request.execute(context)
        self.assertEqual(result.exception_code, ModbusExceptions.IllegalAddress)

        context.valid = True
        result = request.execute(context)
        self.assertEqual(result.encode(), b'\x00\x02\xff\x00')

        context = MockContext(True, default=False)
        request = WriteSingleCoilRequest(2, False)
        result = request.execute(context)
        self.assertEqual(result.encode(), b'\x00\x02\x00\x00')

    def testWriteMultipleCoilsExecute(self):
        context = MockContext(False)
        # too many values
        request = WriteMultipleCoilsRequest(2, FakeList(0x123456))
        result  = request.execute(context)
        self.assertEqual(result.exception_code, ModbusExceptions.IllegalValue)

        # bad byte count
        request = WriteMultipleCoilsRequest(2, [0x00]*4)
        request.byte_count = 0x00
        result  = request.execute(context)
        self.assertEqual(result.exception_code, ModbusExceptions.IllegalValue)

        # does not validate
        context.valid = False
        request = WriteMultipleCoilsRequest(2, [0x00]*4)
        result  = request.execute(context)
        self.assertEqual(result.exception_code, ModbusExceptions.IllegalAddress)

        # validated request
        context.valid = True
        result  = request.execute(context)
        self.assertEqual(result.encode(), b'\x00\x02\x00\x04')

    def testWriteMultipleCoilsResponse(self):
        response = WriteMultipleCoilsResponse()
        response.decode(b'\x00\x80\x00\x08')
        self.assertEqual(response.address, 0x80)
        self.assertEqual(response.count, 0x08)

    def testSerializingToString(self):
        requests = [
            WriteSingleCoilRequest(1, 0xabcd),
            WriteSingleCoilResponse(1, 0xabcd),
            WriteMultipleCoilsRequest(1, [True]*5),
            WriteMultipleCoilsResponse(1, 5),
        ]
        for request in requests:
            self.assertTrue(str(request) != None)

#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
    unittest.main()
pymodbus-2.1.0/test/test_client_async.py000066400000000000000000000251621335513467700204430ustar00rootroot00000000000000#!/usr/bin/env python
import unittest
import pytest
from pymodbus.compat import IS_PYTHON3, PYTHON_VERSION
if IS_PYTHON3 and PYTHON_VERSION >= (3, 4):
    from unittest.mock import patch, Mock, MagicMock
    import asyncio
    from pymodbus.client.async.asyncio import AsyncioModbusSerialClient
    from serial_asyncio import SerialTransport
else:
    from mock import patch, Mock, MagicMock
import platform
from distutils.version import LooseVersion

from pymodbus.client.async.serial import AsyncModbusSerialClient
from pymodbus.client.async.tcp import AsyncModbusTCPClient
from pymodbus.client.async.udp import AsyncModbusUDPClient

from pymodbus.client.async.tornado import AsyncModbusSerialClient as AsyncTornadoModbusSerialClient
from pymodbus.client.async.tornado import AsyncModbusTCPClient as AsyncTornadoModbusTcpClient
from pymodbus.client.async.tornado import AsyncModbusUDPClient as AsyncTornadoModbusUdoClient
from pymodbus.client.async import schedulers
from pymodbus.factory import ClientDecoder
from pymodbus.exceptions import ConnectionException
from pymodbus.transaction import ModbusSocketFramer, ModbusRtuFramer, ModbusAsciiFramer, ModbusBinaryFramer
from pymodbus.client.async.twisted import ModbusSerClientProtocol

IS_DARWIN = platform.system().lower() == "darwin"
OSX_SIERRA = LooseVersion("10.12")
if IS_DARWIN:
    IS_HIGH_SIERRA_OR_ABOVE = LooseVersion(platform.mac_ver()[0])
    SERIAL_PORT = '/dev/ttyp0' if not IS_HIGH_SIERRA_OR_ABOVE else '/dev/ptyp0'
else:
    IS_HIGH_SIERRA_OR_ABOVE = False
    SERIAL_PORT = "/dev/ptmx"

# ---------------------------------------------------------------------------#
# Fixture
# ---------------------------------------------------------------------------#


def mock_asyncio_gather(coro):
    return coro


class TestAsynchronousClient(object):
    """
    This is the unittest for the pymodbus.client.async module
    """

    # -----------------------------------------------------------------------#
    # Test TCP Client client
    # -----------------------------------------------------------------------#
    def testTcpTwistedClient(self):
        """
        Test the TCP Twisted client
        :return:
        """
        from twisted.internet import reactor
        with patch("twisted.internet.reactor") as mock_reactor:
            def test_callback(client):
                pass

            def test_errback(client):
                pass
            AsyncModbusTCPClient(schedulers.REACTOR,
                                 framer=ModbusSocketFramer(ClientDecoder()),
                                 callback=test_callback,
                                 errback=test_errback)

    @patch("pymodbus.client.async.tornado.IOLoop")
    @patch("pymodbus.client.async.tornado.IOStream")
    def testTcpTornadoClient(self, mock_iostream, mock_ioloop):
        """ Test the TCP tornado client client initialize """
        protocol, future = AsyncModbusTCPClient(schedulers.IO_LOOP, framer=ModbusSocketFramer(ClientDecoder()))
        client = future.result()
        assert(isinstance(client, AsyncTornadoModbusTcpClient))
        assert(0 == len(list(client.transaction)))
        assert(isinstance(client.framer, ModbusSocketFramer))
        assert(client.port == 502)
        assert client._connected
        assert(client.stream.connect.call_count == 1)
        assert(client.stream.read_until_close.call_count == 1)

        def handle_failure(failure):
            assert(isinstance(failure.exception(), ConnectionException))

        d = client._build_response(0x00)
        d.add_done_callback(handle_failure)

        assert(client._connected)
        client.close()
        protocol.stop()
        assert(not client._connected)

    @pytest.mark.skipif(not IS_PYTHON3 or PYTHON_VERSION < (3, 4),
                        reason="requires python3.4 or above")
    @patch("asyncio.get_event_loop")
    @patch("asyncio.gather")
    def testTcpAsyncioClient(self, mock_gather, mock_loop):
        """
        Test the TCP Twisted client
        :return:
        """
        pytest.skip("TBD")

    # -----------------------------------------------------------------------#
    # Test UDP client
    # -----------------------------------------------------------------------#

    @patch("pymodbus.client.async.tornado.IOLoop")
    @patch("pymodbus.client.async.tornado.IOStream")
    def testUdpTornadoClient(self, mock_iostream, mock_ioloop):
        """ Test the udp tornado client client initialize """
        protocol, future = AsyncModbusUDPClient(schedulers.IO_LOOP, framer=ModbusSocketFramer(ClientDecoder()))
        client = future.result()
        assert(isinstance(client, AsyncTornadoModbusUdoClient))
        assert(0 == len(list(client.transaction)))
        assert(isinstance(client.framer, ModbusSocketFramer))
        assert(client.port == 502)
        assert(client._connected)

        def handle_failure(failure):
            assert(isinstance(failure.exception(), ConnectionException))

        d = client._build_response(0x00)
        d.add_done_callback(handle_failure)

        assert(client._connected)
        client.close()
        protocol.stop()
        assert(not client._connected)

    def testUdpTwistedClient(self):
        """ Test the udp twisted client client initialize """
        with pytest.raises(NotImplementedError):
            AsyncModbusUDPClient(schedulers.REACTOR,
                                 framer=ModbusSocketFramer(ClientDecoder()))

    @pytest.mark.skipif(not IS_PYTHON3 or PYTHON_VERSION < (3, 4),
                        reason="requires python3.4 or above")
    @patch("asyncio.get_event_loop")
    @patch("asyncio.gather", side_effect=mock_asyncio_gather)
    def testUdpAsycioClient(self, mock_gather, mock_event_loop):
        """Test the udp asyncio client"""
        pytest.skip("TBD")
        pass

    # -----------------------------------------------------------------------#
    # Test Serial client
    # -----------------------------------------------------------------------#

    @pytest.mark.parametrize("method, framer", [("rtu", ModbusRtuFramer),
                                                ("socket", ModbusSocketFramer),
                                                ("binary", ModbusBinaryFramer),
                                                ("ascii", ModbusAsciiFramer)])
    def testSerialTwistedClient(self, method, framer):
        """ Test the serial tornado client client initialize """
        from serial import Serial
        with patch("serial.Serial") as mock_sp:
            from twisted.internet import reactor
            from twisted.internet.serialport import SerialPort

            with patch('twisted.internet.reactor') as mock_reactor:

                protocol, client = AsyncModbusSerialClient(schedulers.REACTOR,
                                                           method=method,
                                                           port=SERIAL_PORT,
                                                           proto_cls=ModbusSerClientProtocol)

                assert (isinstance(client, SerialPort))
                assert (isinstance(client.protocol, ModbusSerClientProtocol))
                assert (0 == len(list(client.protocol.transaction)))
                assert (isinstance(client.protocol.framer, framer))
                assert (client.protocol._connected)

                def handle_failure(failure):
                    assert (isinstance(failure.exception(), ConnectionException))

                d = client.protocol._buildResponse(0x00)
                d.addCallback(handle_failure)

                assert (client.protocol._connected)
                client.protocol.close()
                protocol.stop()
                assert (not client.protocol._connected)

    @pytest.mark.parametrize("method, framer", [("rtu", ModbusRtuFramer),
                                        ("socket", ModbusSocketFramer),
                                        ("binary",  ModbusBinaryFramer),
                                        ("ascii", ModbusAsciiFramer)])
    def testSerialTornadoClient(self, method, framer):
        """ Test the serial tornado client client initialize """
        protocol, future = AsyncModbusSerialClient(schedulers.IO_LOOP, method=method, port=SERIAL_PORT)
        client = future.result()
        assert(isinstance(client, AsyncTornadoModbusSerialClient))
        assert(0 == len(list(client.transaction)))
        assert(isinstance(client.framer, framer))
        assert(client.port == SERIAL_PORT)
        assert(client._connected)

        def handle_failure(failure):
            assert(isinstance(failure.exception(), ConnectionException))

        d = client._build_response(0x00)
        d.add_done_callback(handle_failure)

        assert(client._connected)
        client.close()
        protocol.stop()
        assert(not client._connected)

    @pytest.mark.skipif(IS_PYTHON3 , reason="requires python2.7")
    def testSerialAsyncioClientPython2(self):
        """
        Test Serial async asyncio client exits on python2
        :return:
        """
        with pytest.raises(SystemExit) as pytest_wrapped_e:
            AsyncModbusSerialClient(schedulers.ASYNC_IO, method="rtu", port=SERIAL_PORT)
        assert pytest_wrapped_e.type == SystemExit
        assert pytest_wrapped_e.value.code == 1

    @pytest.mark.skipif(not IS_PYTHON3 or PYTHON_VERSION < (3, 4), reason="requires python3.4 or above")
    @patch("asyncio.get_event_loop")
    @patch("asyncio.gather", side_effect=mock_asyncio_gather)
    @pytest.mark.parametrize("method, framer", [("rtu", ModbusRtuFramer),
                                        ("socket", ModbusSocketFramer),
                                        ("binary",  ModbusBinaryFramer),
                                        ("ascii", ModbusAsciiFramer)])
    def testSerialAsyncioClient(self,  mock_gather, mock_event_loop, method, framer):
        """
        Test that AsyncModbusSerialClient instantiates AsyncioModbusSerialClient for asyncio scheduler.
        :return:
        """
        loop = asyncio.get_event_loop()
        loop, client = AsyncModbusSerialClient(schedulers.ASYNC_IO, method=method, port=SERIAL_PORT, loop=loop,
                                               baudrate=19200, parity='E', stopbits=2, bytesize=7)
        assert(isinstance(client, AsyncioModbusSerialClient))
        assert(isinstance(client.framer, framer))
        assert(client.port == SERIAL_PORT)
        assert(client.baudrate == 19200)
        assert(client.parity == 'E')
        assert(client.stopbits == 2)
        assert(client.bytesize == 7)


# ---------------------------------------------------------------------------#
# Main
# ---------------------------------------------------------------------------#


if __name__ == "__main__":
    unittest.main()
pymodbus-2.1.0/test/test_client_async_asyncio.py000066400000000000000000000265431335513467700221740ustar00rootroot00000000000000from pymodbus.compat import IS_PYTHON3, PYTHON_VERSION
import pytest
if IS_PYTHON3 and PYTHON_VERSION >= (3, 4):
    from unittest import mock
    from pymodbus.client.async.asyncio import (
        ReconnectingAsyncioModbusTcpClient,
        ModbusClientProtocol, ModbusUdpClientProtocol)
    from test.asyncio_test_helper import return_as_coroutine, run_coroutine
    from pymodbus.factory import ClientDecoder
    from pymodbus.exceptions import ConnectionException
    from pymodbus.transaction import ModbusSocketFramer
    from pymodbus.bit_read_message import ReadCoilsRequest, ReadCoilsResponse
    protocols = [ModbusUdpClientProtocol, ModbusClientProtocol]
else:
    import mock
    protocols = [None, None]


@pytest.mark.skipif(not IS_PYTHON3, reason="requires python3.4 or above")
class TestAsyncioClient(object):
    def test_protocol_connection_state_propagation_to_factory(self):
        protocol = ModbusClientProtocol()
        assert protocol.factory is None
        assert protocol.transport is None
        assert not protocol._connected

        protocol.factory = mock.MagicMock()

        protocol.connection_made(mock.sentinel.TRANSPORT)
        assert protocol.transport is mock.sentinel.TRANSPORT
        protocol.factory.protocol_made_connection.assert_called_once_with(protocol)
        assert protocol.factory.protocol_lost_connection.call_count == 0

        protocol.factory.reset_mock()

        protocol.connection_lost(mock.sentinel.REASON)
        assert protocol.transport is None
        assert protocol.factory.protocol_made_connection.call_count == 0
        protocol.factory.protocol_lost_connection.assert_called_once_with(protocol)

    def test_factory_initialization_state(self):
        mock_protocol_class = mock.MagicMock()
        mock_loop = mock.MagicMock()
        client = ReconnectingAsyncioModbusTcpClient(protocol_class=mock_protocol_class, loop=mock_loop)
        assert not client.connected
        assert client.delay_ms < client.DELAY_MAX_MS

        assert client.loop is mock_loop
        assert client.protocol_class is mock_protocol_class

    def test_factory_reset_wait_before_reconnect(self):
        mock_protocol_class = mock.MagicMock()
        mock_loop = mock.MagicMock()
        client = ReconnectingAsyncioModbusTcpClient(protocol_class=mock_protocol_class, loop=mock_loop)
        initial_delay = client.delay_ms
        assert initial_delay > 0
        client.delay_ms *= 2

        assert client.delay_ms > initial_delay
        client.reset_delay()
        assert client.delay_ms == initial_delay


    def test_factory_stop(self):
        mock_protocol_class = mock.MagicMock()
        mock_loop = mock.MagicMock()
        client = ReconnectingAsyncioModbusTcpClient(protocol_class=mock_protocol_class, loop=mock_loop)

        assert not client.connected
        client.stop()
        assert not client.connected

        # fake connected client:
        client.protocol = mock.MagicMock()
        client.connected = True

        client.stop()
        client.protocol.transport.close.assert_called_once_with()

    def test_factory_protocol_made_connection(self):
        mock_protocol_class = mock.MagicMock()
        mock_loop = mock.MagicMock()
        client = ReconnectingAsyncioModbusTcpClient(protocol_class=mock_protocol_class, loop=mock_loop)

        assert not client.connected
        assert client.protocol is None
        client.protocol_made_connection(mock.sentinel.PROTOCOL)
        assert client.connected
        assert client.protocol is mock.sentinel.PROTOCOL

        client.protocol_made_connection(mock.sentinel.PROTOCOL_UNEXPECTED)
        assert client.connected
        assert client.protocol is mock.sentinel.PROTOCOL

    @mock.patch('pymodbus.client.async.asyncio.asyncio.async')
    def test_factory_protocol_lost_connection(self, mock_async):
        mock_protocol_class = mock.MagicMock()
        mock_loop = mock.MagicMock()
        client = ReconnectingAsyncioModbusTcpClient(protocol_class=mock_protocol_class, loop=mock_loop)

        assert not client.connected
        assert client.protocol is None
        client.protocol_lost_connection(mock.sentinel.PROTOCOL_UNEXPECTED)
        assert not client.connected

        # fake client ist connected and *then* looses connection:
        client.connected = True
        client.host = mock.sentinel.HOST
        client.port = mock.sentinel.PORT
        client.protocol = mock.sentinel.PROTOCOL

        with mock.patch('pymodbus.client.async.asyncio.ReconnectingAsyncioModbusTcpClient._reconnect') as mock_reconnect:
            mock_reconnect.return_value = mock.sentinel.RECONNECT_GENERATOR
            client.protocol_lost_connection(mock.sentinel.PROTOCOL)
            mock_async.assert_called_once_with(mock.sentinel.RECONNECT_GENERATOR, loop=mock_loop)
        assert not client.connected
        assert client.protocol is None

    @mock.patch('pymodbus.client.async.asyncio.asyncio.async')
    def test_factory_start_success(self, mock_async):
        mock_protocol_class = mock.MagicMock()
        mock_loop = mock.MagicMock()
        client = ReconnectingAsyncioModbusTcpClient(protocol_class=mock_protocol_class, loop=mock_loop)

        run_coroutine(client.start(mock.sentinel.HOST, mock.sentinel.PORT))
        mock_loop.create_connection.assert_called_once_with(mock.ANY, mock.sentinel.HOST, mock.sentinel.PORT)
        assert mock_async.call_count == 0

    @mock.patch('pymodbus.client.async.asyncio.asyncio.async')
    def test_factory_start_failing_and_retried(self, mock_async):
        mock_protocol_class = mock.MagicMock()
        mock_loop = mock.MagicMock()
        mock_loop.create_connection = mock.MagicMock(side_effect=Exception('Did not work.'))
        client = ReconnectingAsyncioModbusTcpClient(protocol_class=mock_protocol_class, loop=mock_loop)

        # check whether reconnect is called upon failed connection attempt:
        with mock.patch('pymodbus.client.async.asyncio.ReconnectingAsyncioModbusTcpClient._reconnect') as mock_reconnect:
            mock_reconnect.return_value = mock.sentinel.RECONNECT_GENERATOR
            run_coroutine(client.start(mock.sentinel.HOST, mock.sentinel.PORT))
            mock_reconnect.assert_called_once_with()
            mock_async.assert_called_once_with(mock.sentinel.RECONNECT_GENERATOR, loop=mock_loop)

    @mock.patch('pymodbus.client.async.asyncio.asyncio.sleep')
    def test_factory_reconnect(self, mock_sleep):
        mock_protocol_class = mock.MagicMock()
        mock_loop = mock.MagicMock()
        client = ReconnectingAsyncioModbusTcpClient(protocol_class=mock_protocol_class, loop=mock_loop)

        client.delay_ms = 5000

        mock_sleep.side_effect = return_as_coroutine()
        run_coroutine(client._reconnect())
        mock_sleep.assert_called_once_with(5)
        assert mock_loop.create_connection.call_count == 1

    @pytest.mark.parametrize("protocol", protocols)
    def testClientProtocolConnectionMade(self, protocol):
        """
        Test the client protocol close
        :return:
        """
        protocol = protocol(ModbusSocketFramer(ClientDecoder()))
        transport = mock.MagicMock()
        factory = mock.MagicMock()
        if isinstance(protocol, ModbusUdpClientProtocol):
            protocol.factory = factory
        protocol.connection_made(transport)
        assert protocol.transport == transport
        assert protocol.connected
        if isinstance(protocol, ModbusUdpClientProtocol):
            assert protocol.factory.protocol_made_connection.call_count == 1

    @pytest.mark.parametrize("protocol", protocols)
    def testClientProtocolClose(self, protocol):
        """
        Test the client protocol close
        :return:
        """
        protocol = protocol(ModbusSocketFramer(ClientDecoder()))
        transport = mock.MagicMock()
        factory = mock.MagicMock()
        if isinstance(protocol, ModbusUdpClientProtocol):
            protocol.factory = factory
        protocol.connection_made(transport)
        assert protocol.transport == transport
        assert protocol.connected
        protocol.close()
        transport.close.assert_called_once_with()
        assert not protocol.connected

    @pytest.mark.parametrize("protocol", protocols)
    def testClientProtocolConnectionLost(self, protocol):
        ''' Test the client protocol connection lost'''
        framer = ModbusSocketFramer(None)
        protocol = protocol(framer=framer)
        transport = mock.MagicMock()
        factory = mock.MagicMock()
        if isinstance(protocol, ModbusUdpClientProtocol):
            protocol.factory = factory
        protocol.connection_made(transport)
        protocol.transport.write = mock.Mock()

        request = ReadCoilsRequest(1, 1)
        d = protocol.execute(request)
        protocol.connection_lost("REASON")
        excp = d.exception()
        assert (isinstance(excp, ConnectionException))
        if isinstance(protocol, ModbusUdpClientProtocol):
            assert protocol.factory.protocol_lost_connection.call_count == 1

    @pytest.mark.parametrize("protocol", protocols)
    def testClientProtocolDataReceived(self, protocol):
        ''' Test the client protocol data received '''
        protocol = protocol(ModbusSocketFramer(ClientDecoder()))
        transport = mock.MagicMock()
        protocol.connection_made(transport)
        assert protocol.transport == transport
        assert protocol.connected
        data = b'\x00\x00\x12\x34\x00\x06\xff\x01\x01\x02\x00\x04'

        # setup existing request
        d = protocol._buildResponse(0x00)
        if isinstance(protocol, ModbusClientProtocol):
            protocol.data_received(data)
        else:
            protocol.datagram_received(data, None)
        result = d.result()
        assert isinstance(result, ReadCoilsResponse)

    @pytest.mark.parametrize("protocol", protocols)
    def testClientProtocolExecute(self, protocol):
        ''' Test the client protocol execute method '''
        framer = ModbusSocketFramer(None)
        protocol = protocol(framer=framer)
        transport = mock.MagicMock()
        protocol.connection_made(transport)
        protocol.transport.write = mock.Mock()

        request = ReadCoilsRequest(1, 1)
        d = protocol.execute(request)
        tid = request.transaction_id
        assert d == protocol.transaction.getTransaction(tid)

    @pytest.mark.parametrize("protocol", protocols)
    def testClientProtocolHandleResponse(self, protocol):
        ''' Test the client protocol handles responses '''
        protocol = protocol()
        transport = mock.MagicMock()
        protocol.connection_made(transport=transport)
        reply = ReadCoilsRequest(1, 1)
        reply.transaction_id = 0x00

        # handle skipped cases
        protocol._handleResponse(None)
        protocol._handleResponse(reply)

        # handle existing cases
        d = protocol._buildResponse(0x00)
        protocol._handleResponse(reply)
        result = d.result()
        assert result == reply

    @pytest.mark.parametrize("protocol", protocols)
    def testClientProtocolBuildResponse(self, protocol):
        ''' Test the udp client protocol builds responses '''
        protocol = protocol()
        assert not len(list(protocol.transaction))

        d = protocol._buildResponse(0x00)
        excp = d.exception()
        assert (isinstance(excp, ConnectionException))
        assert not len(list(protocol.transaction))

        protocol._connected = True
        protocol._buildResponse(0x00)
        assert len(list(protocol.transaction)) == 1
pymodbus-2.1.0/test/test_client_async_tornado.py000066400000000000000000000316051335513467700221700ustar00rootroot00000000000000#!/usr/bin/env python
import unittest
from pymodbus.compat import IS_PYTHON3
if IS_PYTHON3:
    from unittest.mock import patch, Mock
else: # Python 2
    from mock import patch, Mock
from pymodbus.client.async.tornado import (BaseTornadoClient,
    AsyncModbusSerialClient, AsyncModbusUDPClient, AsyncModbusTCPClient
)
from pymodbus.client.async import schedulers
from pymodbus.factory import ClientDecoder
from pymodbus.client.async.twisted import ModbusClientFactory
from pymodbus.exceptions import ConnectionException
from pymodbus.transaction import ModbusSocketFramer, ModbusRtuFramer
from pymodbus.bit_read_message import ReadCoilsRequest, ReadCoilsResponse

# ---------------------------------------------------------------------------#
# Fixture
# ---------------------------------------------------------------------------#
import platform
from distutils.version import LooseVersion

IS_DARWIN = platform.system().lower() == "darwin"
OSX_SIERRA = LooseVersion("10.12")
if IS_DARWIN:
    IS_HIGH_SIERRA_OR_ABOVE = LooseVersion(platform.mac_ver()[0])
    SERIAL_PORT = '/dev/ttyp0' if not IS_HIGH_SIERRA_OR_ABOVE else '/dev/ptyp0'
else:
    IS_HIGH_SIERRA_OR_ABOVE = False
    SERIAL_PORT = "/dev/ptmx"


class AsynchronousClientTest(unittest.TestCase):
    """
    This is the unittest for the pymodbus.client.async module
    """

    # -----------------------------------------------------------------------#
    # Test Client client
    # -----------------------------------------------------------------------#

    def testBaseClientInit(self):
        """ Test the client client initialize """
        client = BaseTornadoClient()
        self.assertTrue(client.port ==502)
        self.assertTrue(client.host =="127.0.0.1")
        self.assertEqual(0, len(list(client.transaction)))
        self.assertFalse(client._connected)
        self.assertTrue(client.io_loop is None)
        self.assertTrue(isinstance(client.framer, ModbusSocketFramer))

        framer = object()
        client = BaseTornadoClient(framer=framer, ioloop=schedulers.IO_LOOP)
        self.assertEqual(0, len(list(client.transaction)))
        self.assertFalse(client._connected)
        self.assertTrue(client.io_loop == schedulers.IO_LOOP)
        self.assertTrue(framer is client.framer)

    @patch("pymodbus.client.async.tornado.IOLoop")
    @patch("pymodbus.client.async.tornado.IOStream")
    def testBaseClientOn_receive(self, mock_iostream, mock_ioloop):
        """ Test the BaseTornado client data received """
        client = AsyncModbusTCPClient(port=5020)
        client.connect()
        out = []
        data = b'\x00\x00\x12\x34\x00\x06\xff\x01\x01\x02\x00\x04'

        # setup existing request
        d = client._build_response(0x00)
        d.add_done_callback(lambda v: out.append(v))

        client.on_receive(data)
        self.assertTrue(isinstance(out[0].result(), ReadCoilsResponse))
        data = b''
        out = []
        d = client._build_response(0x01)
        client.on_receive(data)
        d.add_done_callback(lambda v: out.append(v))
        self.assertFalse(out)

    @patch("pymodbus.client.async.tornado.IOLoop")
    @patch("pymodbus.client.async.tornado.IOStream")
    def testBaseClientExecute(self, mock_iostream, mock_ioloop):
        """ Test the BaseTornado client execute method """
        client = AsyncModbusTCPClient(port=5020)
        client.connect()
        client.stream = Mock()
        client.stream.write = Mock()

        request = ReadCoilsRequest(1, 1)
        d = client.execute(request)
        tid = request.transaction_id
        self.assertEqual(d, client.transaction.getTransaction(tid))

    @patch("pymodbus.client.async.tornado.IOLoop")
    @patch("pymodbus.client.async.tornado.IOStream")
    def testBaseClientHandleResponse(self,  mock_iostream, mock_ioloop):
        """ Test the BaseTornado client handles responses """
        client = AsyncModbusTCPClient(port=5020)
        client.connect()
        out = []
        reply = ReadCoilsRequest(1, 1)
        reply.transaction_id = 0x00

        # handle skipped cases
        client._handle_response(None)
        client._handle_response(reply)

        # handle existing cases
        d = client._build_response(0x00)
        d.add_done_callback(lambda v: out.append(v))
        client._handle_response(reply)
        self.assertEqual(out[0].result(), reply)

    @patch("pymodbus.client.async.tornado.IOLoop")
    @patch("pymodbus.client.async.tornado.IOStream")
    def testBaseClientBuildResponse(self, mock_iostream, mock_ioloop):
        """ Test the BaseTornado client client builds responses """
        client = BaseTornadoClient()
        self.assertEqual(0, len(list(client.transaction)))

        def handle_failure(failure):
            exc = failure.exception()
            self.assertTrue(isinstance(exc, ConnectionException))
        d = client._build_response(0x00)
        d.add_done_callback(handle_failure)
        self.assertEqual(0, len(list(client.transaction)))

        client._connected = True
        d = client._build_response(0x00)
        self.assertEqual(1, len(list(client.transaction)))

    # -----------------------------------------------------------------------#
    # Test TCP Client client
    # -----------------------------------------------------------------------#
    def testTcpClientInit(self):
        """ Test the tornado tcp client client initialize """
        client = AsyncModbusTCPClient()
        self.assertEqual(0, len(list(client.transaction)))
        self.assertTrue(isinstance(client.framer, ModbusSocketFramer))

        framer = object()
        client = AsyncModbusTCPClient(framer=framer)
        self.assertTrue(framer is client.framer)

    @patch("pymodbus.client.async.tornado.IOLoop")
    @patch("pymodbus.client.async.tornado.IOStream")
    def testTcpClientConnect(self, mock_iostream, mock_ioloop):
        """ Test the tornado tcp client client connect """
        client = AsyncModbusTCPClient(port=5020)
        self.assertTrue(client.port, 5020)
        self.assertFalse(client._connected)
        client.connect()
        self.assertTrue(client._connected)

    @patch("pymodbus.client.async.tornado.IOLoop")
    @patch("pymodbus.client.async.tornado.IOStream")
    def testTcpClientDisconnect(self, mock_iostream, mock_ioloop):
        """ Test the tornado tcp client client disconnect """
        client = AsyncModbusTCPClient(port=5020)
        client.connect()

        def handle_failure(failure):
            self.assertTrue(isinstance(failure.exception(), ConnectionException))

        d = client._build_response(0x00)
        d.add_done_callback(handle_failure)

        self.assertTrue(client._connected)
        client.close()
        self.assertFalse(client._connected)


    # -----------------------------------------------------------------------#
    # Test Serial Client client
    # -----------------------------------------------------------------------#
    def testSerialClientInit(self):
        """ Test the tornado serial client client initialize """
        client = AsyncModbusSerialClient(ioloop=schedulers.IO_LOOP, framer=ModbusRtuFramer(ClientDecoder()), port=SERIAL_PORT)
        self.assertEqual(0, len(list(client.transaction)))
        self.assertTrue(isinstance(client.framer, ModbusRtuFramer))

        framer = object()
        client = AsyncModbusSerialClient(framer=framer)
        self.assertTrue(framer is client.framer)

    @patch("pymodbus.client.async.tornado.IOLoop")
    @patch("pymodbus.client.async.tornado.SerialIOStream")
    @patch("pymodbus.client.async.tornado.Serial")
    def testSerialClientConnect(self, mock_serial, mock_seriostream, mock_ioloop):
        """ Test the tornado serial client client connect """
        client = AsyncModbusSerialClient(ioloop=schedulers.IO_LOOP,
                                         framer=ModbusRtuFramer(
                                             ClientDecoder()),
                                         port=SERIAL_PORT)
        self.assertTrue(client.port, SERIAL_PORT)
        self.assertFalse(client._connected)
        client.connect()
        self.assertTrue(client._connected)
        client.close()

    @patch("pymodbus.client.async.tornado.IOLoop")
    @patch("pymodbus.client.async.tornado.SerialIOStream")
    @patch("pymodbus.client.async.tornado.Serial")
    def testSerialClientDisconnect(self, mock_serial, mock_seriostream, mock_ioloop):
        """ Test the tornado serial client client disconnect """
        client = AsyncModbusSerialClient(ioloop=schedulers.IO_LOOP,
                                         framer=ModbusRtuFramer(
                                             ClientDecoder()),
                                         port=SERIAL_PORT)
        client.connect()
        self.assertTrue(client._connected)

        def handle_failure(failure):
            self.assertTrue(isinstance(failure.exception(), ConnectionException))

        d = client._build_response(0x00)
        d.add_done_callback(handle_failure)
        client.close()
        self.assertFalse(client._connected)

    @patch("pymodbus.client.async.tornado.IOLoop")
    @patch("pymodbus.client.async.tornado.SerialIOStream")
    @patch("pymodbus.client.async.tornado.Serial")
    def testSerialClientExecute(self, mock_serial, mock_seriostream, mock_ioloop):
        """ Test the tornado serial client client execute method """
        client = AsyncModbusSerialClient(ioloop=schedulers.IO_LOOP,
                                         framer=ModbusRtuFramer(
                                             ClientDecoder()),
                                         port=SERIAL_PORT)
        client.connect()
        client.stream = Mock()
        client.stream.write = Mock()

        request = ReadCoilsRequest(1, 1)
        d = client.execute(request)
        tid = request.transaction_id
        self.assertEqual(d, client.transaction.getTransaction(tid))

    @patch("pymodbus.client.async.tornado.IOLoop")
    @patch("pymodbus.client.async.tornado.SerialIOStream")
    @patch("pymodbus.client.async.tornado.Serial")
    def testSerialClientHandleResponse(self, mock_serial, mock_seriostream, mock_ioloop):
        """ Test the tornado serial client client handles responses """
        client = AsyncModbusSerialClient(ioloop=schedulers.IO_LOOP,
                                         framer=ModbusRtuFramer(
                                             ClientDecoder()),
                                         port=SERIAL_PORT)
        client.connect()
        out = []
        reply = ReadCoilsRequest(1, 1)
        reply.transaction_id = 0x00

        # handle skipped cases
        client._handle_response(None)
        client._handle_response(reply)

        # handle existing cases
        d = client._build_response(0x00)
        d.add_done_callback(lambda v: out.append(v))
        client._handle_response(reply)
        self.assertEqual(out[0].result(), reply)

    @patch("pymodbus.client.async.tornado.IOLoop")
    @patch("pymodbus.client.async.tornado.SerialIOStream")
    @patch("pymodbus.client.async.tornado.Serial")
    def testSerialClientBuildResponse(self, mock_serial, mock_seriostream, mock_ioloop):
        """ Test the tornado serial client client builds responses """
        client = AsyncModbusSerialClient(ioloop=schedulers.IO_LOOP,
                                         framer=ModbusRtuFramer(
                                             ClientDecoder()),
                                         port=SERIAL_PORT)
        self.assertEqual(0, len(list(client.transaction)))

        def handle_failure(failure):
            exc = failure.exception()
            self.assertTrue(isinstance(exc, ConnectionException))
        d = client._build_response(0x00)
        d.add_done_callback(handle_failure)
        self.assertEqual(0, len(list(client.transaction)))

        client._connected = True
        d = client._build_response(0x00)
        self.assertEqual(1, len(list(client.transaction)))

    # -----------------------------------------------------------------------#
    # Test Udp Client client
    # -----------------------------------------------------------------------#

    def testUdpClientInit(self):
        """ Test the udp client client initialize """
        client = AsyncModbusUDPClient()
        self.assertEqual(0, len(list(client.transaction)))
        self.assertTrue(isinstance(client.framer, ModbusSocketFramer))

        framer = object()
        client = AsyncModbusUDPClient(framer=framer)
        self.assertTrue(framer is client.framer)

    # -----------------------------------------------------------------------#
    # Test Client Factories
    # -----------------------------------------------------------------------#

    def testModbusClientFactory(self):
        """ Test the base class for all the clients """
        factory = ModbusClientFactory()
        self.assertTrue(factory is not None)

# ---------------------------------------------------------------------------#
# Main
# ---------------------------------------------------------------------------#
if __name__ == "__main__":
    unittest.main()
pymodbus-2.1.0/test/test_client_async_twisted.py000066400000000000000000000213041335513467700222000ustar00rootroot00000000000000#!/usr/bin/env python
import unittest
from pymodbus.compat import IS_PYTHON3
if IS_PYTHON3:
    from unittest.mock import patch, Mock
else: # Python 2
    from mock import patch, Mock
from pymodbus.client.async.twisted import (
    ModbusClientProtocol, ModbusUdpClientProtocol, ModbusSerClientProtocol, ModbusTcpClientProtocol
)
from pymodbus.factory import ClientDecoder
from pymodbus.client.async.twisted import ModbusClientFactory
from pymodbus.exceptions import ConnectionException
from pymodbus.transaction import ModbusSocketFramer, ModbusRtuFramer
from pymodbus.bit_read_message import ReadCoilsRequest, ReadCoilsResponse

#---------------------------------------------------------------------------#
# Fixture
#---------------------------------------------------------------------------#

class AsynchronousClientTest(unittest.TestCase):
    '''
    This is the unittest for the pymodbus.client.async module
    '''

    #-----------------------------------------------------------------------#
    # Test Client Protocol
    #-----------------------------------------------------------------------#

    def testClientProtocolInit(self):
        ''' Test the client protocol initialize '''
        protocol = ModbusClientProtocol()
        self.assertEqual(0, len(list(protocol.transaction)))
        self.assertFalse(protocol._connected)
        self.assertTrue(isinstance(protocol.framer, ModbusSocketFramer))

        framer = object()
        protocol = ModbusClientProtocol(framer=framer)
        self.assertEqual(0, len(list(protocol.transaction)))
        self.assertFalse(protocol._connected)
        self.assertTrue(framer is protocol.framer)

    def testClientProtocolConnect(self):
        ''' Test the client protocol connect '''
        decoder = object()
        framer = ModbusSocketFramer(decoder)
        protocol = ModbusClientProtocol(framer=framer)
        self.assertFalse(protocol._connected)
        protocol.connectionMade()
        self.assertTrue(protocol._connected)

    def testClientProtocolDisconnect(self):
        ''' Test the client protocol disconnect '''
        protocol = ModbusClientProtocol()
        protocol.connectionMade()
        def handle_failure(failure):
            self.assertTrue(isinstance(failure.value, ConnectionException))
        d = protocol._buildResponse(0x00)
        d.addErrback(handle_failure)

        self.assertTrue(protocol._connected)
        protocol.connectionLost('because')
        self.assertFalse(protocol._connected)

    def testClientProtocolDataReceived(self):
        ''' Test the client protocol data received '''
        protocol = ModbusClientProtocol(ModbusSocketFramer(ClientDecoder()))
        protocol.connectionMade()
        out = []
        data = b'\x00\x00\x12\x34\x00\x06\xff\x01\x01\x02\x00\x04'

        # setup existing request
        d = protocol._buildResponse(0x00)
        d.addCallback(lambda v: out.append(v))

        protocol.dataReceived(data)
        self.assertTrue(isinstance(out[0], ReadCoilsResponse))

    def testClientProtocolExecute(self):
        ''' Test the client protocol execute method '''
        framer = ModbusSocketFramer(None)
        protocol = ModbusClientProtocol(framer=framer)
        protocol.connectionMade()
        protocol.transport = Mock()
        protocol.transport.write = Mock()

        request = ReadCoilsRequest(1, 1)
        d = protocol.execute(request)
        tid = request.transaction_id
        self.assertEqual(d, protocol.transaction.getTransaction(tid))

    def testClientProtocolHandleResponse(self):
        ''' Test the client protocol handles responses '''
        protocol = ModbusClientProtocol()
        protocol.connectionMade()
        out = []
        reply = ReadCoilsRequest(1, 1)
        reply.transaction_id = 0x00

        # handle skipped cases
        protocol._handleResponse(None)
        protocol._handleResponse(reply)

        # handle existing cases
        d = protocol._buildResponse(0x00)
        d.addCallback(lambda v: out.append(v))
        protocol._handleResponse(reply)
        self.assertEqual(out[0], reply)

    def testClientProtocolBuildResponse(self):
        ''' Test the udp client protocol builds responses '''
        protocol = ModbusClientProtocol()
        self.assertEqual(0, len(list(protocol.transaction)))

        def handle_failure(failure):
            self.assertTrue(isinstance(failure.value, ConnectionException))
        d = protocol._buildResponse(0x00)
        d.addErrback(handle_failure)
        self.assertEqual(0, len(list(protocol.transaction)))

        protocol._connected = True
        d = protocol._buildResponse(0x00)
        self.assertEqual(1, len(list(protocol.transaction)))

    #-----------------------------------------------------------------------#
    # Test TCP Client Protocol
    #-----------------------------------------------------------------------#
    def testTcpClientProtocolInit(self):
        ''' Test the udp client protocol initialize '''
        protocol = ModbusTcpClientProtocol()
        self.assertEqual(0, len(list(protocol.transaction)))
        self.assertTrue(isinstance(protocol.framer, ModbusSocketFramer))

        framer = object()
        protocol = ModbusClientProtocol(framer=framer)
        self.assertTrue(framer is protocol.framer)

    #-----------------------------------------------------------------------#
    # Test Serial Client Protocol
    #-----------------------------------------------------------------------#
    def testSerialClientProtocolInit(self):
        ''' Test the udp client protocol initialize '''
        protocol = ModbusSerClientProtocol()
        self.assertEqual(0, len(list(protocol.transaction)))
        self.assertTrue(isinstance(protocol.framer, ModbusRtuFramer))

        framer = object()
        protocol = ModbusClientProtocol(framer=framer)
        self.assertTrue(framer is protocol.framer)

    #-----------------------------------------------------------------------#
    # Test Udp Client Protocol
    #-----------------------------------------------------------------------#

    def testUdpClientProtocolInit(self):
        ''' Test the udp client protocol initialize '''
        protocol = ModbusUdpClientProtocol()
        self.assertEqual(0, len(list(protocol.transaction)))
        self.assertTrue(isinstance(protocol.framer, ModbusSocketFramer))

        framer = object()
        protocol = ModbusClientProtocol(framer=framer)
        self.assertTrue(framer is protocol.framer)

    def testUdpClientProtocolDataReceived(self):
        ''' Test the udp client protocol data received '''
        protocol = ModbusUdpClientProtocol()
        out = []
        data = b'\x00\x00\x12\x34\x00\x06\xff\x01\x01\x02\x00\x04'
        server = ('127.0.0.1', 12345)

        # setup existing request
        d = protocol._buildResponse(0x00)
        d.addCallback(lambda v: out.append(v))

        protocol.datagramReceived(data, server)
        self.assertTrue(isinstance(out[0], ReadCoilsResponse))

    def testUdpClientProtocolExecute(self):
        ''' Test the udp client protocol execute method '''
        protocol = ModbusUdpClientProtocol()
        protocol.transport = Mock()
        protocol.transport.write = Mock()

        request = ReadCoilsRequest(1, 1)
        d = protocol.execute(request)
        tid = request.transaction_id
        self.assertEqual(d, protocol.transaction.getTransaction(tid))

    def testUdpClientProtocolHandleResponse(self):
        ''' Test the udp client protocol handles responses '''
        protocol = ModbusUdpClientProtocol()
        out = []
        reply = ReadCoilsRequest(1, 1)
        reply.transaction_id = 0x00

        # handle skipped cases
        protocol._handleResponse(None)
        protocol._handleResponse(reply)

        # handle existing cases
        d = protocol._buildResponse(0x00)
        d.addCallback(lambda v: out.append(v))
        protocol._handleResponse(reply)
        self.assertEqual(out[0], reply)

    def testUdpClientProtocolBuildResponse(self):
        ''' Test the udp client protocol builds responses '''
        protocol = ModbusUdpClientProtocol()
        self.assertEqual(0, len(list(protocol.transaction)))

        d = protocol._buildResponse(0x00)
        self.assertEqual(1, len(list(protocol.transaction)))

    #-----------------------------------------------------------------------#
    # Test Client Factories
    #-----------------------------------------------------------------------#

    def testModbusClientFactory(self):
        ''' Test the base class for all the clients '''
        factory = ModbusClientFactory()
        self.assertTrue(factory is not None)

#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
    unittest.main()
pymodbus-2.1.0/test/test_client_common.py000066400000000000000000000051321335513467700206110ustar00rootroot00000000000000#!/usr/bin/env python
import unittest
from pymodbus.client.common import ModbusClientMixin
from pymodbus.bit_read_message import *
from pymodbus.bit_write_message import *
from pymodbus.file_message import *
from pymodbus.register_read_message import *
from pymodbus.register_write_message import *

#---------------------------------------------------------------------------#
# Mocks
#---------------------------------------------------------------------------#
class MockClient(ModbusClientMixin):

    def execute(self, request):
        return request

#---------------------------------------------------------------------------#
# Fixture
#---------------------------------------------------------------------------#
class ModbusCommonClientTests(unittest.TestCase):

    #-----------------------------------------------------------------------#
    # Setup/TearDown
    #-----------------------------------------------------------------------#
    def setUp(self):
        '''
        Initializes the test environment and builds request/result
        encoding pairs
        '''
        self.client = MockClient()

    def tearDown(self):
        ''' Cleans up the test environment '''
        del self.client

    #-----------------------------------------------------------------------#
    # Tests
    #-----------------------------------------------------------------------#
    def testModbusClientMixinMethods(self):
        ''' This tests that the mixing returns the correct request object '''
        arguments = {
            'read_address': 1, 'read_count': 1,
            'write_address': 1, 'write_registers': 1
        }
        self.assertTrue(isinstance(self.client.read_coils(1,1), ReadCoilsRequest))
        self.assertTrue(isinstance(self.client.read_discrete_inputs(1,1), ReadDiscreteInputsRequest))
        self.assertTrue(isinstance(self.client.write_coil(1,True), WriteSingleCoilRequest))
        self.assertTrue(isinstance(self.client.write_coils(1,[True]), WriteMultipleCoilsRequest))
        self.assertTrue(isinstance(self.client.write_register(1,0x00), WriteSingleRegisterRequest))
        self.assertTrue(isinstance(self.client.write_registers(1,[0x00]), WriteMultipleRegistersRequest))
        self.assertTrue(isinstance(self.client.read_holding_registers(1,1), ReadHoldingRegistersRequest))
        self.assertTrue(isinstance(self.client.read_input_registers(1,1), ReadInputRegistersRequest))
        self.assertTrue(isinstance(self.client.readwrite_registers(**arguments), ReadWriteMultipleRegistersRequest))
        self.assertTrue(isinstance(self.client.mask_write_register(1,0,0), MaskWriteRegisterRequest))
pymodbus-2.1.0/test/test_client_sync.py000066400000000000000000000325351335513467700203040ustar00rootroot00000000000000#!/usr/bin/env python
import unittest
from pymodbus.compat import IS_PYTHON3

if IS_PYTHON3:  # Python 3
    from unittest.mock import patch, Mock, MagicMock
else:  # Python 2
    from mock import patch, Mock, MagicMock
import socket
import serial

from pymodbus.client.sync import ModbusTcpClient, ModbusUdpClient
from pymodbus.client.sync import ModbusSerialClient, BaseModbusClient
from pymodbus.exceptions import ConnectionException, NotImplementedException
from pymodbus.exceptions import ParameterException
from pymodbus.transaction import ModbusAsciiFramer, ModbusRtuFramer
from pymodbus.transaction import ModbusBinaryFramer


# ---------------------------------------------------------------------------#
# Mock Classes
# ---------------------------------------------------------------------------#
class mockSocket(object):
    timeout = 2
    def close(self): return True

    def recv(self, size): return b'\x00' * size

    def read(self, size): return b'\x00' * size

    def send(self, msg): return len(msg)

    def write(self, msg): return len(msg)

    def recvfrom(self, size): return [b'\x00' * size]

    def sendto(self, msg, *args): return len(msg)

    def setblocking(self, flag): return None

    def in_waiting(self): return None



# ---------------------------------------------------------------------------#
# Fixture
# ---------------------------------------------------------------------------#
class SynchronousClientTest(unittest.TestCase):
    '''
    This is the unittest for the pymodbus.client.sync module
    '''

    # -----------------------------------------------------------------------#
    # Test Base Client
    # -----------------------------------------------------------------------#

    def testBaseModbusClient(self):
        ''' Test the base class for all the clients '''

        client = BaseModbusClient(None)
        client.transaction = None
        self.assertRaises(NotImplementedException, lambda: client.connect())
        self.assertRaises(NotImplementedException, lambda: client._send(None))
        self.assertRaises(NotImplementedException, lambda: client._recv(None))
        self.assertRaises(NotImplementedException, lambda: client.__enter__())
        self.assertRaises(NotImplementedException, lambda: client.execute())
        self.assertRaises(NotImplementedException, lambda: client.is_socket_open())
        self.assertEqual("Null Transport", str(client))
        client.close()
        client.__exit__(0, 0, 0)

        # a successful execute
        client.connect = lambda: True
        client.transaction = Mock(**{'execute.return_value': True})
        self.assertEqual(client, client.__enter__())
        self.assertTrue(client.execute())

        # a unsuccessful connect
        client.connect = lambda: False
        self.assertRaises(ConnectionException, lambda: client.__enter__())
        self.assertRaises(ConnectionException, lambda: client.execute())

    # -----------------------------------------------------------------------#
    # Test UDP Client
    # -----------------------------------------------------------------------#

    def testSyncUdpClientInstantiation(self):
        client = ModbusUdpClient()
        self.assertNotEqual(client, None)

    def testBasicSyncUdpClient(self):
        ''' Test the basic methods for the udp sync client'''

        # receive/send
        client = ModbusUdpClient()
        client.socket = mockSocket()
        self.assertEqual(0, client._send(None))
        self.assertEqual(1, client._send(b'\x00'))
        self.assertEqual(b'\x00', client._recv(1))

        # connect/disconnect
        self.assertTrue(client.connect())
        client.close()

        # already closed socket
        client.socket = False
        client.close()

        self.assertEqual("ModbusUdpClient(127.0.0.1:502)", str(client))

    def testUdpClientAddressFamily(self):
        ''' Test the Udp client get address family method'''
        client = ModbusUdpClient()
        self.assertEqual(socket.AF_INET,
                         client._get_address_family('127.0.0.1'))
        self.assertEqual(socket.AF_INET6, client._get_address_family('::1'))

    def testUdpClientConnect(self):
        ''' Test the Udp client connection method'''
        with patch.object(socket, 'socket') as mock_method:
            class DummySocket(object):
                def settimeout(self, *a, **kwa):
                    pass

            mock_method.return_value = DummySocket()
            client = ModbusUdpClient()
            self.assertTrue(client.connect())

        with patch.object(socket, 'socket') as mock_method:
            mock_method.side_effect = socket.error()
            client = ModbusUdpClient()
            self.assertFalse(client.connect())

    def testUdpClientSend(self):
        ''' Test the udp client send method'''
        client = ModbusUdpClient()
        self.assertRaises(ConnectionException, lambda: client._send(None))

        client.socket = mockSocket()
        self.assertEqual(0, client._send(None))
        self.assertEqual(4, client._send('1234'))

    def testUdpClientRecv(self):
        ''' Test the udp client receive method'''
        client = ModbusUdpClient()
        self.assertRaises(ConnectionException, lambda: client._recv(1024))

        client.socket = mockSocket()
        self.assertEqual(b'', client._recv(0))
        self.assertEqual(b'\x00' * 4, client._recv(4))

    def testUdpClientRepr(self):
        client = ModbusUdpClient()
        rep = "<{} at {} socket={}, ipaddr={}, port={}, timeout={}>".format(
            client.__class__.__name__, hex(id(client)), client.socket,
            client.host, client.port, client.timeout
        )
        self.assertEqual(repr(client), rep)


    # -----------------------------------------------------------------------#
    # Test TCP Client
    # -----------------------------------------------------------------------#

    def testSyncTcpClientInstantiation(self):
        client = ModbusTcpClient()
        self.assertNotEqual(client, None)

    @patch('pymodbus.client.sync.select')
    def testBasicSyncTcpClient(self, mock_select):
        ''' Test the basic methods for the tcp sync client'''

        # receive/send
        mock_select.select.return_value = [True]
        client = ModbusTcpClient()
        client.socket = mockSocket()
        self.assertEqual(0, client._send(None))
        self.assertEqual(1, client._send(b'\x00'))
        self.assertEqual(b'\x00', client._recv(1))

        # connect/disconnect
        self.assertTrue(client.connect())
        client.close()

        # already closed socket
        client.socket = False
        client.close()

        self.assertEqual("ModbusTcpClient(127.0.0.1:502)", str(client))

    def testTcpClientConnect(self):
        ''' Test the tcp client connection method'''
        with patch.object(socket, 'create_connection') as mock_method:
            mock_method.return_value = object()
            client = ModbusTcpClient()
            self.assertTrue(client.connect())

        with patch.object(socket, 'create_connection') as mock_method:
            mock_method.side_effect = socket.error()
            client = ModbusTcpClient()
            self.assertFalse(client.connect())

    def testTcpClientSend(self):
        ''' Test the tcp client send method'''
        client = ModbusTcpClient()
        self.assertRaises(ConnectionException, lambda: client._send(None))

        client.socket = mockSocket()
        self.assertEqual(0, client._send(None))
        self.assertEqual(4, client._send('1234'))

    @patch('pymodbus.client.sync.select')
    def testTcpClientRecv(self, mock_select):
        ''' Test the tcp client receive method'''

        mock_select.select.return_value = [True]
        client = ModbusTcpClient()
        self.assertRaises(ConnectionException, lambda: client._recv(1024))

        client.socket = mockSocket()
        self.assertEqual(b'', client._recv(0))
        self.assertEqual(b'\x00' * 4, client._recv(4))

        mock_socket = MagicMock()
        mock_socket.recv.side_effect = iter([b'\x00', b'\x01', b'\x02'])
        client.socket = mock_socket
        client.timeout = 1
        self.assertEqual(b'\x00\x01\x02', client._recv(3))
        mock_socket.recv.side_effect = iter([b'\x00', b'\x01', b'\x02'])
        self.assertEqual(b'\x00\x01', client._recv(2))
        mock_select.select.return_value = [False]
        self.assertEqual(b'', client._recv(2))
        client.socket = mockSocket()
        mock_select.select.return_value = [True]
        self.assertIn(b'\x00', client._recv(None))

    def testSerialClientRpr(self):
        client = ModbusTcpClient()
        rep = "<{} at {} socket={}, ipaddr={}, port={}, timeout={}>".format(
            client.__class__.__name__, hex(id(client)), client.socket,
            client.host, client.port, client.timeout
        )
        self.assertEqual(repr(client), rep)
    # -----------------------------------------------------------------------#
    # Test Serial Client
    # -----------------------------------------------------------------------#

    def testSyncSerialClientInstantiation(self):
        client = ModbusSerialClient()
        self.assertNotEqual(client, None)
        self.assertTrue(isinstance(ModbusSerialClient(method='ascii').framer,
                                   ModbusAsciiFramer))
        self.assertTrue(isinstance(ModbusSerialClient(method='rtu').framer,
                                   ModbusRtuFramer))
        self.assertTrue(isinstance(ModbusSerialClient(method='binary').framer,
                                   ModbusBinaryFramer))
        self.assertRaises(ParameterException,
                          lambda: ModbusSerialClient(method='something'))

    def testSyncSerialRTUClientTimeouts(self):
        client = ModbusSerialClient(method="rtu", baudrate=9600)
        assert client.silent_interval == round((3.5 * 11 / 9600), 6)
        client = ModbusSerialClient(method="rtu", baudrate=38400)
        assert client.silent_interval == round((1.75 / 1000), 6)

    @patch("serial.Serial")
    def testBasicSyncSerialClient(self, mock_serial):
        ''' Test the basic methods for the serial sync client'''

        # receive/send
        mock_serial.in_waiting = 0
        mock_serial.write = lambda x: len(x)

        mock_serial.read = lambda size: b'\x00' * size
        client = ModbusSerialClient()
        client.socket = mock_serial
        client.state = 0
        self.assertEqual(0, client._send(None))
        client.state = 0
        self.assertEqual(1, client._send(b'\x00'))
        self.assertEqual(b'\x00', client._recv(1))

        # connect/disconnect
        self.assertTrue(client.connect())
        client.close()

        # already closed socket
        client.socket = False
        client.close()

        self.assertEqual('ModbusSerialClient(ascii baud[19200])', str(client))

    def testSerialClientConnect(self):
        ''' Test the serial client connection method'''
        with patch.object(serial, 'Serial') as mock_method:
            mock_method.return_value = object()
            client = ModbusSerialClient()
            self.assertTrue(client.connect())

        with patch.object(serial, 'Serial') as mock_method:
            mock_method.side_effect = serial.SerialException()
            client = ModbusSerialClient()
            self.assertFalse(client.connect())

    @patch("serial.Serial")
    def testSerialClientSend(self, mock_serial):
        ''' Test the serial client send method'''
        mock_serial.in_waiting = None
        mock_serial.write = lambda x: len(x)
        client = ModbusSerialClient()
        self.assertRaises(ConnectionException, lambda: client._send(None))
        # client.connect()
        client.socket = mock_serial
        client.state = 0
        self.assertEqual(0, client._send(None))
        client.state = 0
        self.assertEqual(4, client._send('1234'))

    @patch("serial.Serial")
    def testSerialClientCleanupBufferBeforeSend(self, mock_serial):
        ''' Test the serial client send method'''
        mock_serial.in_waiting = 4
        mock_serial.read = lambda x: b'1' * x
        mock_serial.write = lambda x: len(x)
        client = ModbusSerialClient()
        self.assertRaises(ConnectionException, lambda: client._send(None))
        # client.connect()
        client.socket = mock_serial
        client.state = 0
        self.assertEqual(0, client._send(None))
        client.state = 0
        self.assertEqual(4, client._send('1234'))

    def testSerialClientRecv(self):
        ''' Test the serial client receive method'''
        client = ModbusSerialClient()
        self.assertRaises(ConnectionException, lambda: client._recv(1024))

        client.socket = mockSocket()
        self.assertEqual(b'', client._recv(0))
        self.assertEqual(b'\x00' * 4, client._recv(4))
        client.socket = MagicMock()
        client.socket.read.return_value = b''
        self.assertEqual(b'', client._recv(None))
        client.socket.timeout = 0
        self.assertEqual(b'', client._recv(0))

    def testSerialClientRepr(self):
        client = ModbusSerialClient()
        rep = "<{} at {} socket={}, method={}, timeout={}>".format(
            client.__class__.__name__, hex(id(client)), client.socket,
            client.method, client.timeout
        )
        self.assertEqual(repr(client), rep)
# ---------------------------------------------------------------------------#
# Main
# ---------------------------------------------------------------------------#
if __name__ == "__main__":
    unittest.main()
pymodbus-2.1.0/test/test_datastore.py000066400000000000000000000314501335513467700177530ustar00rootroot00000000000000#!/usr/bin/env python
import unittest
import mock
from mock import MagicMock
import redis
import random
from pymodbus.datastore import *
from pymodbus.datastore.store import BaseModbusDataBlock
from pymodbus.datastore.database import SqlSlaveContext
from pymodbus.datastore.database import RedisSlaveContext
from pymodbus.exceptions import NotImplementedException
from pymodbus.exceptions import NoSuchSlaveException
from pymodbus.exceptions import ParameterException
from pymodbus.datastore.remote import RemoteSlaveContext

class ModbusDataStoreTest(unittest.TestCase):
    '''
    This is the unittest for the pymodbus.datastore module
    '''

    def setUp(self):
        pass

    def tearDown(self):
        ''' Cleans up the test environment '''
        pass

    def testModbusDataBlock(self):
        ''' Test a base data block store '''
        block = BaseModbusDataBlock()
        block.default(10, True)

        self.assertNotEqual(str(block), None)
        self.assertEqual(block.default_value, True)
        self.assertEqual(block.values, [True]*10)

        block.default_value = False
        block.reset()
        self.assertEqual(block.values, [False]*10)

    def testModbusDataBlockIterate(self):
        ''' Test a base data block store '''
        block = BaseModbusDataBlock()
        block.default(10, False)
        for idx,value in block:
            self.assertEqual(value, False)

        block.values = {0 : False, 2 : False, 3 : False }
        for idx,value in block:
            self.assertEqual(value, False)

    def testModbusDataBlockOther(self):
        ''' Test a base data block store '''
        block = BaseModbusDataBlock()
        self.assertRaises(NotImplementedException, lambda: block.validate(1,1))
        self.assertRaises(NotImplementedException, lambda: block.getValues(1,1))
        self.assertRaises(NotImplementedException, lambda: block.setValues(1,1))

    def testModbusSequentialDataBlock(self):
        ''' Test a sequential data block store '''
        block = ModbusSequentialDataBlock(0x00, [False]*10)
        self.assertFalse(block.validate(-1, 0))
        self.assertFalse(block.validate(0, 20))
        self.assertFalse(block.validate(10, 1))
        self.assertTrue(block.validate(0x00, 10))

        block.setValues(0x00, True)
        self.assertEqual(block.getValues(0x00, 1), [True])

        block.setValues(0x00, [True]*10)
        self.assertEqual(block.getValues(0x00, 10), [True]*10)

    def testModbusSequentialDataBlockFactory(self):
        ''' Test the sequential data block store factory '''
        block = ModbusSequentialDataBlock.create()
        self.assertEqual(block.getValues(0x00, 65536), [False]*65536)
        block = ModbusSequentialDataBlock(0x00, 0x01)
        self.assertEqual(block.values, [0x01])

    def testModbusSparseDataBlock(self):
        ''' Test a sparse data block store '''
        values = dict(enumerate([True]*10))
        block = ModbusSparseDataBlock(values)
        self.assertFalse(block.validate(-1, 0))
        self.assertFalse(block.validate(0, 20))
        self.assertFalse(block.validate(10, 1))
        self.assertTrue(block.validate(0x00, 10))
        self.assertTrue(block.validate(0x00, 10))
        self.assertFalse(block.validate(0, 0))
        self.assertFalse(block.validate(5, 0))

        block.setValues(0x00, True)
        self.assertEqual(block.getValues(0x00, 1), [True])

        block.setValues(0x00, [True]*10)
        self.assertEqual(block.getValues(0x00, 10), [True]*10)

        block.setValues(0x00, dict(enumerate([False]*10)))
        self.assertEqual(block.getValues(0x00, 10), [False]*10)

    def testModbusSparseDataBlockFactory(self):
        ''' Test the sparse data block store factory '''
        block = ModbusSparseDataBlock.create()
        self.assertEqual(block.getValues(0x00, 65536), [False]*65536)

    def testModbusSparseDataBlockOther(self):
        block = ModbusSparseDataBlock([True]*10)
        self.assertEqual(block.getValues(0x00, 10), [True]*10)
        self.assertRaises(ParameterException,
            lambda: ModbusSparseDataBlock(True))

    def testModbusSlaveContext(self):
        ''' Test a modbus slave context '''
        store = {
            'di' : ModbusSequentialDataBlock(0, [False]*10),
            'co' : ModbusSequentialDataBlock(0, [False]*10),
            'ir' : ModbusSequentialDataBlock(0, [False]*10),
            'hr' : ModbusSequentialDataBlock(0, [False]*10),
        }
        context = ModbusSlaveContext(**store)
        self.assertNotEqual(str(context), None)

        for fx in [1,2,3,4]:
            context.setValues(fx, 0, [True]*10)
            self.assertTrue(context.validate(fx, 0,10))
            self.assertEqual(context.getValues(fx, 0,10), [True]*10)
        context.reset()

        for fx in [1,2,3,4]:
            self.assertTrue(context.validate(fx, 0,10))
            self.assertEqual(context.getValues(fx, 0,10), [False]*10)

    def testModbusServerContext(self):
        ''' Test a modbus server context '''
        def _set(ctx):
            ctx[0xffff] = None
        context = ModbusServerContext(single=False)
        self.assertRaises(NoSuchSlaveException, lambda: _set(context))
        self.assertRaises(NoSuchSlaveException, lambda: context[0xffff])


class RedisDataStoreTest(unittest.TestCase):
    '''
    This is the unittest for the pymodbus.datastore.database.redis module
    '''

    def setUp(self):
        self.slave = RedisSlaveContext()

    def tearDown(self):
        ''' Cleans up the test environment '''
        pass

    def testStr(self):
        # slave = RedisSlaveContext()
        self.assertEqual(str(self.slave), "Redis Slave Context %s" % self.slave.client)

    def testReset(self):
        assert isinstance(self.slave.client, redis.Redis)
        self.slave.client = MagicMock()
        self.slave.reset()
        self.slave.client.flushall.assert_called_once_with()

    def testValCallbacksSuccess(self):
        self.slave._build_mapping()
        mock_count = 3
        mock_offset = 0
        self.slave.client.mset = MagicMock()
        self.slave.client.mget = MagicMock(return_value=['11'])

        for key in ('d', 'c', 'h', 'i'):
            self.assertTrue(
                self.slave._val_callbacks[key](mock_offset, mock_count)
            )

    def testValCallbacksFailure(self):
        self.slave._build_mapping()
        mock_count = 3
        mock_offset = 0
        self.slave.client.mset = MagicMock()
        self.slave.client.mget = MagicMock(return_value=['11', None])

        for key in ('d', 'c', 'h', 'i'):
            self.assertFalse(
                self.slave._val_callbacks[key](mock_offset, mock_count)
            )

    def testGetCallbacks(self):
        self.slave._build_mapping()
        mock_count = 3
        mock_offset = 0
        self.slave.client.mget = MagicMock(return_value='11')

        for key in ('d', 'c'):
            resp = self.slave._get_callbacks[key](mock_offset, mock_count)
            self.assertEqual(resp, [True, False, False])

        for key in ('h', 'i'):
            resp = self.slave._get_callbacks[key](mock_offset, mock_count)
            self.assertEqual(resp, ['1', '1'])

    def testSetCallbacks(self):
        self.slave._build_mapping()
        mock_values = [3]
        mock_offset = 0
        self.slave.client.mset = MagicMock()
        self.slave.client.mget = MagicMock()

        for key in ['c', 'd']:
            self.slave._set_callbacks[key](mock_offset, [3])
            k = "pymodbus:{}:{}".format(key, mock_offset)
            self.slave.client.mset.assert_called_with(
                {k: '\x01'}
            )

        for key in ('h', 'i'):
            self.slave._set_callbacks[key](mock_offset, [3])
            k = "pymodbus:{}:{}".format(key, mock_offset)
            self.slave.client.mset.assert_called_with(
                {k: mock_values[0]}
            )

    def testValidate(self):
        self.slave.client.mget = MagicMock(return_value=[123])
        self.assertTrue(self.slave.validate(0x01, 3000))

    def testSetValue(self):
        self.slave.client.mset = MagicMock()
        self.slave.client.mget = MagicMock()
        self.assertEqual(self.slave.setValues(0x01, 1000, [12]), None)

    def testGetValue(self):
        self.slave.client.mget = MagicMock(return_value=["123"])
        self.assertEqual(self.slave.getValues(0x01, 23), [])


class MockSqlResult(object):
        def __init__(self, rowcount=0, value=0):
            self.rowcount = rowcount
            self.value = value


class SqlDataStoreTest(unittest.TestCase):
    '''
    This is the unittest for the pymodbus.datastore.database.SqlSlaveContesxt
    module
    '''

    def setUp(self):
        self.slave = SqlSlaveContext()
        self.slave._metadata.drop_all = MagicMock()
        self.slave._db_create = MagicMock()
        self.slave._table.select = MagicMock()
        self.slave._connection = MagicMock()

        self.mock_addr = random.randint(0, 65000)
        self.mock_values = random.sample(range(1, 100), 5)
        self.mock_function = 0x01
        self.mock_type = 'h'
        self.mock_offset = 0
        self.mock_count = 1

        self.function_map = {2: 'd', 4: 'i'}
        self.function_map.update([(i, 'h') for i in [3, 6, 16, 22, 23]])
        self.function_map.update([(i, 'c') for i in [1, 5, 15]])

    def tearDown(self):
        ''' Cleans up the test environment '''
        pass

    def testStr(self):
        self.assertEqual(str(self.slave), "Modbus Slave Context")

    def testReset(self):
        self.slave.reset()

        self.slave._metadata.drop_all.assert_called_once_with()
        self.slave._db_create.assert_called_once_with(
            self.slave.table, self.slave.database
        )
    def testValidateSuccess(self):
        mock_result = MockSqlResult(
            rowcount=len(self.mock_values)
        )
        self.slave._connection.execute = MagicMock(return_value=mock_result)
        self.assertTrue(self.slave.validate(
            self.mock_function, self.mock_addr, len(self.mock_values))
        )

    def testValidateFailure(self):
        wrong_count = 9
        mock_result = MockSqlResult(rowcount=len(self.mock_values))
        self.slave._connection.execute = MagicMock(return_value=mock_result)
        self.assertFalse(self.slave.validate(
            self.mock_function, self.mock_addr, wrong_count)
        )

    def testBuildSet(self):
        mock_set = [
            {
                'index': 0,
                'type': 'h',
                'value': 11
            },
            {
                'index': 1,
                'type': 'h',
                'value': 12
            }
        ]
        self.assertListEqual(self.slave._build_set('h', 0, [11, 12]), mock_set)

    def testCheckSuccess(self):
        mock_success_results = [1, 2, 3]
        self.slave._get = MagicMock(return_value=mock_success_results)
        self.assertFalse(self.slave._check('h', 0, 1))

    def testCheckFailure(self):
        mock_success_results = []
        self.slave._get = MagicMock(return_value=mock_success_results)
        self.assertTrue(self.slave._check('h', 0, 1))

    def testGetValues(self):
        self.slave._get = MagicMock()

        for key, value in self.function_map.items():
            self.slave.getValues(key, self.mock_addr, self.mock_count)
            self.slave._get.assert_called_with(
                value, self.mock_addr + 1, self.mock_count
            )

    def testSetValues(self):
        self.slave._set = MagicMock()

        for key, value in self.function_map.items():
            self.slave.setValues(key, self.mock_addr, self.mock_values)
            self.slave._set.assert_called_with(
                value, self.mock_addr + 1, self.mock_values
            )

    def testSet(self):
        self.slave._check = MagicMock(return_value=True)
        self.slave._connection.execute = MagicMock(
            return_value=MockSqlResult(rowcount=len(self.mock_values))
        )
        self.assertTrue(self.slave._set(
            self.mock_type, self.mock_offset, self.mock_values)
        )

        self.slave._check = MagicMock(return_value=False)
        self.assertFalse(
            self.slave._set(self.mock_type, self.mock_offset, self.mock_values)
        )

    def testUpdateSuccess(self):
        self.slave._connection.execute = MagicMock(
            return_value=MockSqlResult(rowcount=len(self.mock_values))
        )
        self.assertTrue(
            self.slave._update(self.mock_type, self.mock_offset, self.mock_values)
        )

    def testUpdateFailure(self):
        self.slave._connection.execute = MagicMock(
            return_value=MockSqlResult(rowcount=100)
        )
        self.assertFalse(
            self.slave._update(self.mock_type, self.mock_offset, self.mock_values)
        )

#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
    unittest.main()
pymodbus-2.1.0/test/test_device.py000066400000000000000000000337651335513467700172370ustar00rootroot00000000000000#!/usr/bin/env python
import unittest
from pymodbus.device import *
from pymodbus.events import ModbusEvent, RemoteReceiveEvent
from pymodbus.constants import DeviceInformation

#---------------------------------------------------------------------------#
# Fixture
#---------------------------------------------------------------------------#
class SimpleDataStoreTest(unittest.TestCase):
    '''
    This is the unittest for the pymodbus.device module
    '''

    #-----------------------------------------------------------------------#
    # Setup/TearDown
    #-----------------------------------------------------------------------#

    def setUp(self):
        self.info = {
            0x00: 'Bashwork',               # VendorName
            0x01: 'PTM',                    # ProductCode
            0x02: '1.0',                    # MajorMinorRevision
            0x03: 'http://internets.com',   # VendorUrl
            0x04: 'pymodbus',               # ProductName
            0x05: 'bashwork',               # ModelName
            0x06: 'unittest',               # UserApplicationName
            0x07: 'x',                      # reserved
            0x08: 'x',                      # reserved
            0x10: 'reserved',               # reserved
            0x80: 'custom1',                # device specific start
            0x82: 'custom2',                # device specific
            0xFF: 'customlast',             # device specific last
        }
        self.ident   = ModbusDeviceIdentification(self.info)
        self.control = ModbusControlBlock()
        self.access  = ModbusAccessControl()
        self.control.reset()

    def tearDown(self):
        ''' Cleans up the test environment '''
        del self.ident
        del self.control
        del self.access

    def testUpdateIdentity(self):
        ''' Test device identification reading '''
        self.control.Identity.update(self.ident)
        self.assertEqual(self.control.Identity.VendorName, 'Bashwork')
        self.assertEqual(self.control.Identity.ProductCode, 'PTM')
        self.assertEqual(self.control.Identity.MajorMinorRevision, '1.0')
        self.assertEqual(self.control.Identity.VendorUrl, 'http://internets.com')
        self.assertEqual(self.control.Identity.ProductName, 'pymodbus')
        self.assertEqual(self.control.Identity.ModelName, 'bashwork')
        self.assertEqual(self.control.Identity.UserApplicationName, 'unittest')

    def testDeviceInformationFactory(self):
        ''' Test device identification reading '''
        self.control.Identity.update(self.ident)
        result = DeviceInformationFactory.get(self.control, DeviceInformation.Specific, 0x00)
        self.assertEqual(result[0x00], 'Bashwork')

        result = DeviceInformationFactory.get(self.control, DeviceInformation.Basic, 0x00)
        self.assertEqual(result[0x00], 'Bashwork')
        self.assertEqual(result[0x01], 'PTM')
        self.assertEqual(result[0x02], '1.0')

        result = DeviceInformationFactory.get(self.control, DeviceInformation.Regular, 0x00)
        self.assertEqual(result[0x00], 'Bashwork')
        self.assertEqual(result[0x01], 'PTM')
        self.assertEqual(result[0x02], '1.0')
        self.assertEqual(result[0x03], 'http://internets.com')
        self.assertEqual(result[0x04], 'pymodbus')
        self.assertEqual(result[0x05], 'bashwork')
        self.assertEqual(result[0x06], 'unittest')

    def testDeviceInformationFactoryLookup(self):
        result = DeviceInformationFactory.get(self.control, DeviceInformation.Basic, 0x00)
        self.assertEqual(sorted(result.keys()), [0x00, 0x01, 0x02])
        result = DeviceInformationFactory.get(self.control, DeviceInformation.Basic, 0x02)
        self.assertEqual(sorted(result.keys()), [0x02])
        result = DeviceInformationFactory.get(self.control, DeviceInformation.Regular, 0x00)
        self.assertEqual(sorted(result.keys()), [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06])
        result = DeviceInformationFactory.get(self.control, DeviceInformation.Regular, 0x01)
        self.assertEqual(sorted(result.keys()), [0x01, 0x02, 0x03, 0x04, 0x05, 0x06])
        result = DeviceInformationFactory.get(self.control, DeviceInformation.Regular, 0x05)
        self.assertEqual(sorted(result.keys()), [0x05, 0x06])
        result = DeviceInformationFactory.get(self.control, DeviceInformation.Extended, 0x00)
        self.assertEqual(sorted(result.keys()), [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x80, 0x82, 0xFF])
        result = DeviceInformationFactory.get(self.control, DeviceInformation.Extended, 0x02)
        self.assertEqual(sorted(result.keys()), [0x02, 0x03, 0x04, 0x05, 0x06, 0x80, 0x82, 0xFF])
        result = DeviceInformationFactory.get(self.control, DeviceInformation.Extended, 0x06)
        self.assertEqual(sorted(result.keys()), [0x06, 0x80, 0x82, 0xFF])
        result = DeviceInformationFactory.get(self.control, DeviceInformation.Extended, 0x80)
        self.assertEqual(sorted(result.keys()), [0x80, 0x82, 0xFF])
        result = DeviceInformationFactory.get(self.control, DeviceInformation.Extended, 0x82)
        self.assertEqual(sorted(result.keys()), [0x82, 0xFF])
        result = DeviceInformationFactory.get(self.control, DeviceInformation.Extended, 0x81)
        self.assertEqual(sorted(result.keys()), [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x80, 0x82, 0xFF])

    def testBasicCommands(self):
        ''' Test device identification reading '''
        self.assertEqual(str(self.ident),   "DeviceIdentity")
        self.assertEqual(str(self.control), "ModbusControl")

    def testModbusDeviceIdentificationGet(self):
        ''' Test device identification reading '''
        self.assertEqual(self.ident[0x00], 'Bashwork')
        self.assertEqual(self.ident[0x01], 'PTM')
        self.assertEqual(self.ident[0x02], '1.0')
        self.assertEqual(self.ident[0x03], 'http://internets.com')
        self.assertEqual(self.ident[0x04], 'pymodbus')
        self.assertEqual(self.ident[0x05], 'bashwork')
        self.assertEqual(self.ident[0x06], 'unittest')
        self.assertNotEqual(self.ident[0x07], 'x')
        self.assertNotEqual(self.ident[0x08], 'x')
        self.assertNotEqual(self.ident[0x10], 'reserved')
        self.assertEqual(self.ident[0x54], '')

    def testModbusDeviceIdentificationSummary(self):
        ''' Test device identification summary creation '''
        summary  = sorted(self.ident.summary().values())
        expected = sorted(list(self.info.values())[:0x07]) # remove private
        self.assertEqual(summary, expected)

    def testModbusDeviceIdentificationSet(self):
        ''' Test a device identification writing '''
        self.ident[0x07] = 'y'
        self.ident[0x08] = 'y'
        self.ident[0x10] = 'public'
        self.ident[0x54] = 'testing'

        self.assertNotEqual('y', self.ident[0x07])
        self.assertNotEqual('y', self.ident[0x08])
        self.assertEqual('public', self.ident[0x10])
        self.assertEqual('testing', self.ident[0x54])

    def testModbusControlBlockAsciiModes(self):
        ''' Test a server control block ascii mode '''
        self.assertEqual(id(self.control), id(ModbusControlBlock()))
        self.control.Mode = 'RTU'
        self.assertEqual('RTU', self.control.Mode)
        self.control.Mode = 'FAKE'
        self.assertNotEqual('FAKE', self.control.Mode)

    def testModbusControlBlockCounters(self):
        ''' Tests the MCB counters methods '''
        self.assertEqual(0x0, self.control.Counter.BusMessage)
        for _ in range(10):
            self.control.Counter.BusMessage += 1
            self.control.Counter.SlaveMessage += 1
        self.assertEqual(10, self.control.Counter.BusMessage)
        self.control.Counter.BusMessage = 0x00
        self.assertEqual(0,  self.control.Counter.BusMessage)
        self.assertEqual(10, self.control.Counter.SlaveMessage)
        self.control.Counter.reset()
        self.assertEqual(0, self.control.Counter.SlaveMessage)

    def testModbusControlBlockUpdate(self):
        ''' Tests the MCB counters upate methods '''
        values = {'SlaveMessage':5, 'BusMessage':5}
        self.control.Counter.BusMessage += 1
        self.control.Counter.SlaveMessage += 1
        self.control.Counter.update(values)
        self.assertEqual(6, self.control.Counter.SlaveMessage)
        self.assertEqual(6, self.control.Counter.BusMessage)

    def testModbusControlBlockIterator(self):
        ''' Tests the MCB counters iterator '''
        self.control.Counter.reset()
        for _,count in self.control:
            self.assertEqual(0, count)

    def testModbusCountersHandlerIterator(self):
        ''' Tests the MCB counters iterator '''
        self.control.Counter.reset()
        for _,count in self.control.Counter:
            self.assertEqual(0, count)

    def testModbusControlBlockCounterSummary(self):
        ''' Tests retrieving the current counter summary '''
        self.assertEqual(0x00, self.control.Counter.summary())
        for _ in range(10):
            self.control.Counter.BusMessage += 1
            self.control.Counter.SlaveMessage += 1
            self.control.Counter.SlaveNAK += 1
            self.control.Counter.BusCharacterOverrun += 1
        self.assertEqual(0xa9, self.control.Counter.summary())
        self.control.Counter.reset()
        self.assertEqual(0x00, self.control.Counter.summary())

    def testModbusControlBlockListen(self):
        ''' Tests the MCB listen flag methods '''
        
        self.control.ListenOnly = False        
        self.assertEqual(self.control.ListenOnly, False)
        self.control.ListenOnly = not self.control.ListenOnly
        self.assertEqual(self.control.ListenOnly, True)

    def testModbusControlBlockDelimiter(self):
        ''' Tests the MCB delimiter setting methods '''
        self.control.Delimiter = b'\r'
        self.assertEqual(self.control.Delimiter, b'\r')
        self.control.Delimiter = '='
        self.assertEqual(self.control.Delimiter, b'=')
        self.control.Delimiter = 61
        self.assertEqual(self.control.Delimiter, b'=')

    def testModbusControlBlockDiagnostic(self):
        ''' Tests the MCB delimiter setting methods '''
        self.assertEqual([False] * 16, self.control.getDiagnosticRegister())
        for i in [1,3,4,6]:
            self.control.setDiagnostic({i:True});
        self.assertEqual(True, self.control.getDiagnostic(1))
        self.assertEqual(False, self.control.getDiagnostic(2))
        actual = [False, True, False, True, True, False, True] + [False] * 9
        self.assertEqual(actual, self.control.getDiagnosticRegister())
        for i in range(16):
            self.control.setDiagnostic({i:False});

    def testModbusControlBlockInvalidDiagnostic(self):
        ''' Tests querying invalid MCB counters methods '''
        self.assertEqual(None, self.control.getDiagnostic(-1))
        self.assertEqual(None, self.control.getDiagnostic(17))
        self.assertEqual(None, self.control.getDiagnostic(None))
        self.assertEqual(None, self.control.getDiagnostic([1,2,3]))

    def testAddRemoveSingleClients(self):
        ''' Test adding and removing a host '''
        self.assertFalse(self.access.check("192.168.1.1"))
        self.access.add("192.168.1.1")
        self.assertTrue(self.access.check("192.168.1.1"))
        self.access.add("192.168.1.1")
        self.access.remove("192.168.1.1")
        self.assertFalse(self.access.check("192.168.1.1"))

    def testAddRemoveMultipleClients(self):
        ''' Test adding and removing a host '''
        clients = ["192.168.1.1", "192.168.1.2", "192.168.1.3"]
        self.access.add(clients)
        for host in clients:
            self.assertTrue(self.access.check(host))
        self.access.remove(clients)

    def testNetworkAccessListIterator(self):
        ''' Test adding and removing a host '''
        clients = ["127.0.0.1", "192.168.1.1", "192.168.1.2", "192.168.1.3"]
        self.access.add(clients)
        for host in self.access:
            self.assertTrue(host in clients)
        for host in clients:
            self.assertTrue(host in self.access)

    def testClearingControlEvents(self):
        ''' Test adding and clearing modbus events '''
        self.assertEqual(self.control.Events, [])
        event = ModbusEvent()
        self.control.addEvent(event)
        self.assertEqual(self.control.Events, [event])
        self.assertEqual(self.control.Counter.Event, 1)
        self.control.clearEvents()
        self.assertEqual(self.control.Events, [])
        self.assertEqual(self.control.Counter.Event, 1)

    def testRetrievingControlEvents(self):
        ''' Test adding and removing a host '''
        self.assertEqual(self.control.Events, [])
        event = RemoteReceiveEvent()
        self.control.addEvent(event)
        self.assertEqual(self.control.Events, [event])
        packet = self.control.getEvents()
        self.assertEqual(packet, b'\x40')

    def testModbusPlusStatistics(self):
        ''' Test device identification reading '''
        default = [0x0000] * 55
        statistics = ModbusPlusStatistics()
        self.assertEqual(default, statistics.encode())
        statistics.reset()
        self.assertEqual(default, statistics.encode())
        self.assertEqual(default, self.control.Plus.encode())



    def testModbusPlusStatisticsHelpers(self):
        ''' Test modbus plus statistics helper methods '''
        statistics = ModbusPlusStatistics()
        summary = [
             [0],[0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0],[0],
             [0,0,0,0,0,0,0,0],[0],[0],[0],[0],[0,0],[0],[0],[0],[0],
             [0],[0],[0],[0,0],[0],[0],[0],[0],[0,0,0,0,0,0,0,0],[0],
             [0,0,0,0,0,0,0,0],[0,0],[0],[0,0,0,0,0,0,0,0],
             [0,0,0,0,0,0,0,0],[0],[0],[0,0],[0],[0],[0],[0],[0,0],
             [0],[0],[0],[0],[0],[0,0],[0],[0,0,0,0,0,0,0,0]]
        stats_summary = [x for x in statistics.summary()]
        self.assertEqual(sorted(summary), sorted(stats_summary))
        self.assertEqual(0x00, sum(sum(value[1]) for value in statistics))

#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
    unittest.main()
pymodbus-2.1.0/test/test_diag_messages.py000066400000000000000000000174721335513467700205700ustar00rootroot00000000000000#!/usr/bin/env python
import unittest
from pymodbus.exceptions import *
from pymodbus.constants import ModbusPlusOperation
from pymodbus.diag_message import *
from pymodbus.diag_message import DiagnosticStatusRequest
from pymodbus.diag_message import DiagnosticStatusResponse
from pymodbus.diag_message import DiagnosticStatusSimpleRequest
from pymodbus.diag_message import DiagnosticStatusSimpleResponse

class SimpleDataStoreTest(unittest.TestCase):
    '''
    This is the unittest for the pymodbus.diag_message module
    '''

    def setUp(self):
        self.requests = [
            #(DiagnosticStatusRequest,                      b'\x00\x00\x00\x00'),
            #(DiagnosticStatusSimpleRequest,                b'\x00\x00\x00\x00'),
            (RestartCommunicationsOptionRequest,            b'\x00\x01\x00\x00', b'\x00\x01\xff\x00'),
            (ReturnDiagnosticRegisterRequest,               b'\x00\x02\x00\x00', b'\x00\x02\x00\x00'),
            (ChangeAsciiInputDelimiterRequest,              b'\x00\x03\x00\x00', b'\x00\x03\x00\x00'),
            (ForceListenOnlyModeRequest,                    b'\x00\x04\x00\x00', b'\x00\x04'),
            (ReturnQueryDataRequest,                        b'\x00\x00\x00\x00', b'\x00\x00\x00\x00'),
            (ClearCountersRequest,                          b'\x00\x0a\x00\x00', b'\x00\x0a\x00\x00'),
            (ReturnBusMessageCountRequest,                  b'\x00\x0b\x00\x00', b'\x00\x0b\x00\x00'),
            (ReturnBusCommunicationErrorCountRequest,       b'\x00\x0c\x00\x00', b'\x00\x0c\x00\x00'),
            (ReturnBusExceptionErrorCountRequest,           b'\x00\x0d\x00\x00', b'\x00\x0d\x00\x00'),
            (ReturnSlaveMessageCountRequest,                b'\x00\x0e\x00\x00', b'\x00\x0e\x00\x00'),
            (ReturnSlaveNoResponseCountRequest,             b'\x00\x0f\x00\x00', b'\x00\x0f\x00\x00'),
            (ReturnSlaveNAKCountRequest,                    b'\x00\x10\x00\x00', b'\x00\x10\x00\x00'),
            (ReturnSlaveBusyCountRequest,                   b'\x00\x11\x00\x00', b'\x00\x11\x00\x00'),
            (ReturnSlaveBusCharacterOverrunCountRequest,    b'\x00\x12\x00\x00', b'\x00\x12\x00\x00'),
            (ReturnIopOverrunCountRequest,                  b'\x00\x13\x00\x00', b'\x00\x13\x00\x00'),
            (ClearOverrunCountRequest,                      b'\x00\x14\x00\x00', b'\x00\x14\x00\x00'),
            (GetClearModbusPlusRequest,                     b'\x00\x15\x00\x00', b'\x00\x15\x00\x00' + b'\x00\x00' * 55),
        ]

        self.responses = [
            #(DiagnosticStatusResponse,                     b'\x00\x00\x00\x00'),
            #(DiagnosticStatusSimpleResponse,               b'\x00\x00\x00\x00'),
            (ReturnQueryDataResponse,                      b'\x00\x00\x00\x00'),
            (RestartCommunicationsOptionResponse,          b'\x00\x01\x00\x00'),
            (ReturnDiagnosticRegisterResponse,             b'\x00\x02\x00\x00'),
            (ChangeAsciiInputDelimiterResponse,            b'\x00\x03\x00\x00'),
            (ForceListenOnlyModeResponse,                  b'\x00\x04'),
            (ReturnQueryDataResponse,                      b'\x00\x00\x00\x00'),
            (ClearCountersResponse,                        b'\x00\x0a\x00\x00'),
            (ReturnBusMessageCountResponse,                b'\x00\x0b\x00\x00'),
            (ReturnBusCommunicationErrorCountResponse,     b'\x00\x0c\x00\x00'),
            (ReturnBusExceptionErrorCountResponse,         b'\x00\x0d\x00\x00'),
            (ReturnSlaveMessageCountResponse,              b'\x00\x0e\x00\x00'),
            (ReturnSlaveNoReponseCountResponse,            b'\x00\x0f\x00\x00'),
            (ReturnSlaveNAKCountResponse,                  b'\x00\x10\x00\x00'),
            (ReturnSlaveBusyCountResponse,                 b'\x00\x11\x00\x00'),
            (ReturnSlaveBusCharacterOverrunCountResponse,  b'\x00\x12\x00\x00'),
            (ReturnIopOverrunCountResponse,                b'\x00\x13\x00\x00'),
            (ClearOverrunCountResponse,                    b'\x00\x14\x00\x00'),
            (GetClearModbusPlusResponse,                   b'\x00\x15\x00\x04' + b'\x00\x00' * 55),
        ]

    def tearDown(self):
        ''' Cleans up the test environment '''
        del self.requests
        del self.responses

    def testDiagnosticRequestsDecode(self):
        ''' Testing diagnostic request messages encoding '''
        for msg,enc,exe in self.requests:
            handle = DiagnosticStatusRequest()
            handle.decode(enc)
            self.assertEqual(handle.sub_function_code, msg.sub_function_code)

    def testDiagnosticSimpleRequests(self):
        ''' Testing diagnostic request messages encoding '''
        request = DiagnosticStatusSimpleRequest(b'\x12\x34')
        request.sub_function_code = 0x1234
        self.assertRaises(NotImplementedException, lambda: request.execute())
        self.assertEqual(request.encode(), b'\x12\x34\x12\x34')

        response = DiagnosticStatusSimpleResponse(None)

    def testDiagnosticResponseDecode(self):
        ''' Testing diagnostic request messages encoding '''
        for msg,enc,exe in self.requests:
            handle = DiagnosticStatusResponse()
            handle.decode(enc)
            self.assertEqual(handle.sub_function_code, msg.sub_function_code)

    def testDiagnosticRequestsEncode(self):
        ''' Testing diagnostic request messages encoding '''
        for msg,enc,exe in self.requests:
            self.assertEqual(msg().encode(), enc)

    #def testDiagnosticResponse(self):
    #    ''' Testing diagnostic request messages '''
    #    for msg,enc in self.responses:
    #        self.assertEqual(msg().encode(), enc)

    def testDiagnosticExecute(self):
        ''' Testing diagnostic message execution '''
        for message, encoded, executed in self.requests:
            encoded = message().execute().encode()
            self.assertEqual(encoded, executed)

    def testReturnQueryDataRequest(self):
        ''' Testing diagnostic message execution '''
        message = ReturnQueryDataRequest([0x0000]*2)
        self.assertEqual(message.encode(), b'\x00\x00\x00\x00\x00\x00');
        message = ReturnQueryDataRequest(0x0000)
        self.assertEqual(message.encode(), b'\x00\x00\x00\x00');

    def testReturnQueryDataResponse(self):
        ''' Testing diagnostic message execution '''
        message = ReturnQueryDataResponse([0x0000]*2)
        self.assertEqual(message.encode(), b'\x00\x00\x00\x00\x00\x00');
        message = ReturnQueryDataResponse(0x0000)
        self.assertEqual(message.encode(), b'\x00\x00\x00\x00');

    def testRestartCommunicationsOption(self):
        ''' Testing diagnostic message execution '''
        request = RestartCommunicationsOptionRequest(True);
        self.assertEqual(request.encode(), b'\x00\x01\xff\x00')
        request = RestartCommunicationsOptionRequest(False);
        self.assertEqual(request.encode(), b'\x00\x01\x00\x00')

        response = RestartCommunicationsOptionResponse(True);
        self.assertEqual(response.encode(), b'\x00\x01\xff\x00')
        response = RestartCommunicationsOptionResponse(False);
        self.assertEqual(response.encode(), b'\x00\x01\x00\x00')

    def testGetClearModbusPlusRequestExecute(self):
        ''' Testing diagnostic message execution '''
        request = GetClearModbusPlusRequest(data=ModbusPlusOperation.ClearStatistics);
        response = request.execute()
        self.assertEqual(response.message, ModbusPlusOperation.ClearStatistics)

        request = GetClearModbusPlusRequest(data=ModbusPlusOperation.GetStatistics);
        response = request.execute()
        resp = [ModbusPlusOperation.GetStatistics]
        self.assertEqual(response.message, resp+[0x00] * 55)

#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
    unittest.main()
pymodbus-2.1.0/test/test_events.py000066400000000000000000000047761335513467700173040ustar00rootroot00000000000000#!/usr/bin/env python
import unittest
from pymodbus.events import *
from pymodbus.exceptions import NotImplementedException
from pymodbus.exceptions import ParameterException

class ModbusEventsTest(unittest.TestCase):
    '''
    This is the unittest for the pymodbus.device module
    '''

    def setUp(self):
        ''' Sets up the test environment '''
        pass

    def tearDown(self):
        ''' Cleans up the test environment '''
        pass

    def testModbusEventBaseClass(self):
        event = ModbusEvent()
        self.assertRaises(NotImplementedException, event.encode)
        self.assertRaises(NotImplementedException, lambda: event.decode(None))

    def testRemoteReceiveEvent(self):
        event = RemoteReceiveEvent()
        event.decode(b'\x70')
        self.assertTrue(event.overrun)
        self.assertTrue(event.listen)
        self.assertTrue(event.broadcast)

    def testRemoteSentEvent(self):
        event = RemoteSendEvent()
        result = event.encode()
        self.assertEqual(result, b'\x40')
        event.decode(b'\x7f')
        self.assertTrue(event.read)
        self.assertTrue(event.slave_abort)
        self.assertTrue(event.slave_busy)
        self.assertTrue(event.slave_nak)
        self.assertTrue(event.write_timeout)
        self.assertTrue(event.listen)

    def testRemoteSentEventEncode(self):
        arguments = {
            'read'          : True,
            'slave_abort'   : True,
            'slave_busy'    : True,
            'slave_nak'     : True,
            'write_timeout' : True,
            'listen'        : True,
        }
        event = RemoteSendEvent(**arguments)
        result = event.encode()
        self.assertEqual(result, b'\x7f')

    def testEnteredListenModeEvent(self):
        event = EnteredListenModeEvent()
        result = event.encode()
        self.assertEqual(result, b'\x04')
        event.decode(b'\x04')
        self.assertEqual(event.value, 0x04)
        self.assertRaises(ParameterException, lambda: event.decode(b'\x00'))

    def testCommunicationRestartEvent(self):
        event = CommunicationRestartEvent()
        result = event.encode()
        self.assertEqual(result, b'\x00')
        event.decode(b'\x00')
        self.assertEqual(event.value, 0x00)
        self.assertRaises(ParameterException, lambda: event.decode(b'\x04'))

#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
    unittest.main()
pymodbus-2.1.0/test/test_exceptions.py000066400000000000000000000022531335513467700201450ustar00rootroot00000000000000#!/usr/bin/env python
import unittest
from pymodbus.exceptions import *

class SimpleExceptionsTest(unittest.TestCase):
    '''
    This is the unittest for the pymodbus.exceptions module
    '''

    def setUp(self):
        ''' Initializes the test environment '''
        self.exceptions = [
                ModbusException("bad base"),
                ModbusIOException("bad register"),
                ParameterException("bad paramater"),
                NotImplementedException("bad function"),
                ConnectionException("bad connection"),
        ]

    def tearDown(self):
        ''' Cleans up the test environment '''
        pass

    def testExceptions(self):
        ''' Test all module exceptions '''
        for ex in self.exceptions:
            try:
                raise ex
            except ModbusException as ex:
                self.assertTrue("Modbus Error:" in str(ex))
                pass
            else: self.fail("Excepted a ModbusExceptions")

#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
    unittest.main()
pymodbus-2.1.0/test/test_factory.py000066400000000000000000000212151335513467700174320ustar00rootroot00000000000000#!/usr/bin/env python
import unittest
from pymodbus.factory import ServerDecoder, ClientDecoder
from pymodbus.exceptions import ModbusException

def _raise_exception(_):
    raise ModbusException('something')

class SimpleFactoryTest(unittest.TestCase):
    '''
    This is the unittest for the pymod.exceptions module
    '''

    def setUp(self):
        ''' Initializes the test environment '''
        self.client  = ClientDecoder()
        self.server  = ServerDecoder()
        self.request = (
                (0x01, b'\x01\x00\x01\x00\x01'),                       # read coils
                (0x02, b'\x02\x00\x01\x00\x01'),                       # read discrete inputs
                (0x03, b'\x03\x00\x01\x00\x01'),                       # read holding registers
                (0x04, b'\x04\x00\x01\x00\x01'),                       # read input registers
                (0x05, b'\x05\x00\x01\x00\x01'),                       # write single coil
                (0x06, b'\x06\x00\x01\x00\x01'),                       # write single register
                (0x07, b'\x07'),                                       # read exception status
                (0x08, b'\x08\x00\x00\x00\x00'),                       # read diagnostic
                (0x0b, b'\x0b'),                                       # get comm event counters
                (0x0c, b'\x0c'),                                       # get comm event log
                (0x0f, b'\x0f\x00\x01\x00\x08\x01\x00\xff'),           # write multiple coils
                (0x10, b'\x10\x00\x01\x00\x02\x04\0xff\xff'),          # write multiple registers
                (0x11, b'\x11'),                                       # report slave id
                (0x14, b'\x14\x0e\x06\x00\x04\x00\x01\x00\x02' \
                       b'\x06\x00\x03\x00\x09\x00\x02'),               # read file record
                (0x15, b'\x15\x0d\x06\x00\x04\x00\x07\x00\x03' \
                       b'\x06\xaf\x04\xbe\x10\x0d'),                   # write file record
                (0x16, b'\x16\x00\x01\x00\xff\xff\x00'),               # mask write register
                (0x17, b'\x17\x00\x01\x00\x01\x00\x01\x00\x01\x02\x12\x34'),# read/write multiple registers
                (0x18, b'\x18\x00\x01'),                               # read fifo queue
                (0x2b, b'\x2b\x0e\x01\x00'),                           # read device identification
        )

        self.response = (
                (0x01, b'\x01\x01\x01'),                               # read coils
                (0x02, b'\x02\x01\x01'),                               # read discrete inputs
                (0x03, b'\x03\x02\x01\x01'),                           # read holding registers
                (0x04, b'\x04\x02\x01\x01'),                           # read input registers
                (0x05, b'\x05\x00\x01\x00\x01'),                       # write single coil
                (0x06, b'\x06\x00\x01\x00\x01'),                       # write single register
                (0x07, b'\x07\x00'),                                   # read exception status
                (0x08, b'\x08\x00\x00\x00\x00'),                       # read diagnostic
                (0x0b, b'\x0b\x00\x00\x00\x00'),                       # get comm event counters
                (0x0c, b'\x0c\x08\x00\x00\x01\x08\x01\x21\x20\x00'),   # get comm event log
                (0x0f, b'\x0f\x00\x01\x00\x08'),                       # write multiple coils
                (0x10, b'\x10\x00\x01\x00\x02'),                       # write multiple registers
                (0x11, b'\x11\x03\x05\x01\x54'),                       # report slave id (device specific)
                (0x14, b'\x14\x0c\x05\x06\x0d\xfe\x00\x20\x05' \
                       b'\x06\x33\xcd\x00\x40'),                       # read file record
                (0x15, b'\x15\x0d\x06\x00\x04\x00\x07\x00\x03' \
                       b'\x06\xaf\x04\xbe\x10\x0d'),                   # write file record
                (0x16, b'\x16\x00\x01\x00\xff\xff\x00'),               # mask write register
                (0x17, b'\x17\x02\x12\x34'),                           # read/write multiple registers
                (0x18, b'\x18\x00\x01\x00\x01\x00\x00'),               # read fifo queue
                (0x2b, b'\x2b\x0e\x01\x01\x00\x00\x01\x00\x01\x77'),   # read device identification
        )

        self.exception = (
                (0x81, b'\x81\x01\xd0\x50'),                           # illegal function exception
                (0x82, b'\x82\x02\x90\xa1'),                           # illegal data address exception
                (0x83, b'\x83\x03\x50\xf1'),                           # illegal data value exception
                (0x84, b'\x84\x04\x13\x03'),                           # skave device failure exception
                (0x85, b'\x85\x05\xd3\x53'),                           # acknowledge exception
                (0x86, b'\x86\x06\x93\xa2'),                           # slave device busy exception
                (0x87, b'\x87\x08\x53\xf2'),                           # memory parity exception
                (0x88, b'\x88\x0a\x16\x06'),                           # gateway path unavailable exception
                (0x89, b'\x89\x0b\xd6\x56'),                           # gateway target failed exception
        )

        self.bad = (
                (0x80, b'\x80\x00\x00\x00'),                           # Unknown Function
                (0x81, b'\x81\x00\x00\x00'),                           # error message
        )

    def tearDown(self):
        ''' Cleans up the test environment '''
        del self.bad
        del self.request
        del self.response

    def testExceptionLookup(self):
        ''' Test that we can look up exception messages '''
        for func, _ in self.exception:
            response = self.client.lookupPduClass(func)
            self.assertNotEqual(response, None)

        for func, _ in self.exception:
            response = self.server.lookupPduClass(func)
            self.assertNotEqual(response, None)

    def testResponseLookup(self):
        ''' Test a working response factory lookup '''
        for func, _ in self.response:
            response = self.client.lookupPduClass(func)
            self.assertNotEqual(response, None)

    def testRequestLookup(self):
        ''' Test a working request factory lookup '''
        for func, _ in self.request:
            request = self.client.lookupPduClass(func)
            self.assertNotEqual(request, None)

    def testResponseWorking(self):
        ''' Test a working response factory decoders '''
        for func, msg in self.response:
            try:
                self.client.decode(msg)
            except ModbusException:
                self.fail("Failed to Decode Response Message", func)

    def testResponseErrors(self):
        ''' Test a response factory decoder exceptions '''
        self.assertRaises(ModbusException, self.client._helper, self.bad[0][1])
        self.assertEqual(self.client.decode(self.bad[1][1]).function_code, self.bad[1][0],
                "Failed to decode error PDU")

    def testRequestsWorking(self):
        ''' Test a working request factory decoders '''
        for func, msg in self.request:
            try:
                self.server.decode(msg)
            except ModbusException:
                self.fail("Failed to Decode Request Message", func)

    def testClientFactoryFails(self):
        ''' Tests that a client factory will fail to decode a bad message '''
        self.client._helper = _raise_exception
        actual = self.client.decode(None)
        self.assertEqual(actual, None)

    def testServerFactoryFails(self):
        ''' Tests that a server factory will fail to decode a bad message '''
        self.server._helper = _raise_exception
        actual = self.server.decode(None)
        self.assertEqual(actual, None)

#---------------------------------------------------------------------------#
# I don't actually know what is supposed to be returned here, I assume that
# since the high bit is set, it will simply echo the resulting message
#---------------------------------------------------------------------------#
    def testRequestErrors(self):
        ''' Test a request factory decoder exceptions '''
        for func, msg in self.bad:
            result = self.server.decode(msg)
            self.assertEqual(result.ErrorCode, 1,
                    "Failed to decode invalid requests")
            self.assertEqual(result.execute(None).function_code, func,
                    "Failed to create correct response message")

#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
    unittest.main()
pymodbus-2.1.0/test/test_file_message.py000066400000000000000000000252631335513467700204150ustar00rootroot00000000000000#!/usr/bin/env python
'''
Bit Message Test Fixture
--------------------------------

This fixture tests the functionality of all the 
bit based request/response messages:

* Read/Write Discretes
* Read Coils
'''
import unittest
from pymodbus.file_message import *
from pymodbus.exceptions import *
from pymodbus.pdu import ModbusExceptions

from .modbus_mocks import MockContext

#---------------------------------------------------------------------------#
# Fixture
#---------------------------------------------------------------------------#
class ModbusBitMessageTests(unittest.TestCase):

    #-----------------------------------------------------------------------#
    # Setup/TearDown
    #-----------------------------------------------------------------------#

    def setUp(self):
        '''
        Initializes the test environment and builds request/result
        encoding pairs
        '''
        pass

    def tearDown(self):
        ''' Cleans up the test environment '''
        pass

    #-----------------------------------------------------------------------#
    # Read Fifo Queue
    #-----------------------------------------------------------------------#

    def testReadFifoQueueRequestEncode(self):
        ''' Test basic bit message encoding/decoding '''
        handle  = ReadFifoQueueRequest(0x1234)
        result  = handle.encode()
        self.assertEqual(result, b'\x12\x34')

    def testReadFifoQueueRequestDecode(self):
        ''' Test basic bit message encoding/decoding '''
        handle  = ReadFifoQueueRequest(0x0000)
        handle.decode(b'\x12\x34')
        self.assertEqual(handle.address, 0x1234)

    def testReadFifoQueueRequest(self):
        ''' Test basic bit message encoding/decoding '''
        context = MockContext()
        handle  = ReadFifoQueueRequest(0x1234)
        result  = handle.execute(context)
        self.assertTrue(isinstance(result, ReadFifoQueueResponse))

        handle.address = -1
        result  = handle.execute(context)
        self.assertEqual(ModbusExceptions.IllegalValue,
                result.exception_code)

        handle.values = [0x00]*33
        result  = handle.execute(context)
        self.assertEqual(ModbusExceptions.IllegalValue,
                result.exception_code)

    def testReadFifoQueueRequestError(self):
        ''' Test basic bit message encoding/decoding '''
        context = MockContext()
        handle  = ReadFifoQueueRequest(0x1234)
        handle.values = [0x00]*32
        result = handle.execute(context)
        self.assertEqual(result.function_code, 0x98)

    def testReadFifoQueueResponseEncode(self):
        ''' Test that the read fifo queue response can encode '''
        message = b'\x00\n\x00\x08\x00\x01\x00\x02\x00\x03\x00\x04'
        handle  = ReadFifoQueueResponse([1,2,3,4])
        result  = handle.encode()
        self.assertEqual(result, message)

    def testReadFifoQueueResponseDecode(self):
        ''' Test that the read fifo queue response can decode '''
        message = b'\x00\n\x00\x08\x00\x01\x00\x02\x00\x03\x00\x04'
        handle  = ReadFifoQueueResponse([1,2,3,4])
        handle.decode(message)
        self.assertEqual(handle.values, [1,2,3,4])

    def testRtuFrameSize(self):
        ''' Test that the read fifo queue response can decode '''
        message = b'\x00\n\x00\x08\x00\x01\x00\x02\x00\x03\x00\x04'
        result  = ReadFifoQueueResponse.calculateRtuFrameSize(message)
        self.assertEqual(result, 14)

    #-----------------------------------------------------------------------#
    # File Record
    #-----------------------------------------------------------------------#

    def testFileRecordLength(self):
        ''' Test file record length generation '''
        record = FileRecord(file_number=0x01, record_number=0x02,
            record_data=b'\x00\x01\x02\x04')
        self.assertEqual(record.record_length, 0x02)
        self.assertEqual(record.response_length, 0x05)

    def testFileRecordComapre(self):
        ''' Test file record comparison operations '''
        record1 = FileRecord(file_number=0x01, record_number=0x02, record_data=b'\x00\x01\x02\x04')
        record2 = FileRecord(file_number=0x01, record_number=0x02, record_data=b'\x00\x0a\x0e\x04')
        record3 = FileRecord(file_number=0x02, record_number=0x03, record_data=b'\x00\x01\x02\x04')
        record4 = FileRecord(file_number=0x01, record_number=0x02, record_data=b'\x00\x01\x02\x04')
        self.assertTrue(record1 == record4)
        self.assertTrue(record1 != record2)
        self.assertNotEqual(record1, record2)
        self.assertNotEqual(record1, record3)
        self.assertNotEqual(record2, record3)
        self.assertEqual(record1, record4)
        self.assertEqual(str(record1), "FileRecord(file=1, record=2, length=2)")
        self.assertEqual(str(record2), "FileRecord(file=1, record=2, length=2)")
        self.assertEqual(str(record3), "FileRecord(file=2, record=3, length=2)")

    #-----------------------------------------------------------------------#
    # Read File Record Request
    #-----------------------------------------------------------------------#

    def testReadFileRecordRequestEncode(self):
        ''' Test basic bit message encoding/decoding '''
        records = [FileRecord(file_number=0x01, record_number=0x02)]
        handle  = ReadFileRecordRequest(records)
        result  = handle.encode()
        self.assertEqual(result, b'\x07\x06\x00\x01\x00\x02\x00\x00')

    def testReadFileRecordRequestDecode(self):
        ''' Test basic bit message encoding/decoding '''
        record  = FileRecord(file_number=0x04, record_number=0x01, record_length=0x02)
        request = b'\x0e\x06\x00\x04\x00\x01\x00\x02\x06\x00\x03\x00\x09\x00\x02'
        handle  = ReadFileRecordRequest()
        handle.decode(request)
        self.assertEqual(handle.records[0], record)

    def testReadFileRecordRequestRtuFrameSize(self):
        ''' Test basic bit message encoding/decoding '''
        request = b'\x00\x00\x0e\x06\x00\x04\x00\x01\x00\x02\x06\x00\x03\x00\x09\x00\x02'
        handle  = ReadFileRecordRequest()
        size    = handle.calculateRtuFrameSize(request)
        self.assertEqual(size, 0x0e + 5)

    def testReadFileRecordRequestExecute(self):
        ''' Test basic bit message encoding/decoding '''
        handle  = ReadFileRecordRequest()
        result  = handle.execute(None)
        self.assertTrue(isinstance(result, ReadFileRecordResponse))

    #-----------------------------------------------------------------------#
    # Read File Record Response
    #-----------------------------------------------------------------------#

    def testReadFileRecordResponseEncode(self):
        ''' Test basic bit message encoding/decoding '''
        records = [FileRecord(record_data=b'\x00\x01\x02\x03')]
        handle  = ReadFileRecordResponse(records)
        result  = handle.encode()
        self.assertEqual(result, b'\x06\x06\x02\x00\x01\x02\x03')

    def testReadFileRecordResponseDecode(self):
        ''' Test basic bit message encoding/decoding '''
        record  = FileRecord(file_number=0x00, record_number=0x00,
            record_data=b'\x0d\xfe\x00\x20')
        request = b'\x0c\x05\x06\x0d\xfe\x00\x20\x05\x05\x06\x33\xcd\x00\x40'
        handle  = ReadFileRecordResponse()
        handle.decode(request)
        self.assertEqual(handle.records[0], record)

    def testReadFileRecordResponseRtuFrameSize(self):
        ''' Test basic bit message encoding/decoding '''
        request = b'\x00\x00\x0c\x05\x06\x0d\xfe\x00\x20\x05\x05\x06\x33\xcd\x00\x40'
        handle  = ReadFileRecordResponse()
        size    = handle.calculateRtuFrameSize(request)
        self.assertEqual(size, 0x0c + 5)

    #-----------------------------------------------------------------------#
    # Write File Record Request
    #-----------------------------------------------------------------------#

    def testWriteFileRecordRequestEncode(self):
        ''' Test basic bit message encoding/decoding '''
        records = [FileRecord(file_number=0x01, record_number=0x02, record_data=b'\x00\x01\x02\x03')]
        handle  = WriteFileRecordRequest(records)
        result  = handle.encode()
        self.assertEqual(result, b'\x0b\x06\x00\x01\x00\x02\x00\x02\x00\x01\x02\x03')

    def testWriteFileRecordRequestDecode(self):
        ''' Test basic bit message encoding/decoding '''
        record  = FileRecord(file_number=0x04, record_number=0x07,
            record_data=b'\x06\xaf\x04\xbe\x10\x0d')
        request = b'\x0d\x06\x00\x04\x00\x07\x00\x03\x06\xaf\x04\xbe\x10\x0d'
        handle  = WriteFileRecordRequest()
        handle.decode(request)
        self.assertEqual(handle.records[0], record)

    def testWriteFileRecordRequestRtuFrameSize(self):
        ''' Test write file record request rtu frame size calculation '''
        request = b'\x00\x00\x0d\x06\x00\x04\x00\x07\x00\x03\x06\xaf\x04\xbe\x10\x0d'
        handle  = WriteFileRecordRequest()
        size    = handle.calculateRtuFrameSize(request)
        self.assertEqual(size, 0x0d + 5)

    def testWriteFileRecordRequestExecute(self):
        ''' Test basic bit message encoding/decoding '''
        handle  = WriteFileRecordRequest()
        result  = handle.execute(None)
        self.assertTrue(isinstance(result, WriteFileRecordResponse))

    #-----------------------------------------------------------------------#
    # Write File Record Response
    #-----------------------------------------------------------------------#

    def testWriteFileRecordResponseEncode(self):
        ''' Test basic bit message encoding/decoding '''
        records = [FileRecord(file_number=0x01, record_number=0x02, record_data=b'\x00\x01\x02\x03')]
        handle  = WriteFileRecordResponse(records)
        result  = handle.encode()
        self.assertEqual(result, b'\x0b\x06\x00\x01\x00\x02\x00\x02\x00\x01\x02\x03')

    def testWriteFileRecordResponseDecode(self):
        ''' Test basic bit message encoding/decoding '''
        record  = FileRecord(file_number=0x04, record_number=0x07,
            record_data=b'\x06\xaf\x04\xbe\x10\x0d')
        request = b'\x0d\x06\x00\x04\x00\x07\x00\x03\x06\xaf\x04\xbe\x10\x0d'
        handle  = WriteFileRecordResponse()
        handle.decode(request)
        self.assertEqual(handle.records[0], record)

    def testWriteFileRecordResponseRtuFrameSize(self):
        ''' Test write file record response rtu frame size calculation '''
        request = b'\x00\x00\x0d\x06\x00\x04\x00\x07\x00\x03\x06\xaf\x04\xbe\x10\x0d'
        handle  = WriteFileRecordResponse()
        size    = handle.calculateRtuFrameSize(request)
        self.assertEqual(size, 0x0d + 5)


#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
    unittest.main()
pymodbus-2.1.0/test/test_fixes.py000066400000000000000000000015201335513467700170760ustar00rootroot00000000000000#!/usr/bin/env python
import unittest

class ModbusFixesTest(unittest.TestCase):
    '''
    This is the unittest for the pymodbus._version code
    '''

    def testTrueFalseDefined(self):
        ''' Test that True and False are defined on all versions'''
        try:
            True,False
        except NameError:
            import pymodbus
            self.assertEqual(True, 1)
            self.assertEqual(False, 1)

    def testNullLoggerAttached(self):
        ''' Test that the null logger is attached'''
        import logging
        logger = logging.getLogger('pymodbus')
        self.assertEqual(len(logger.handlers), 1)

#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
    unittest.main()
pymodbus-2.1.0/test/test_framers.py000066400000000000000000000120331335513467700174200ustar00rootroot00000000000000import pytest
from pymodbus.factory import ClientDecoder
from pymodbus.framer.rtu_framer import ModbusRtuFramer
from pymodbus.framer.ascii_framer import ModbusAsciiFramer
from pymodbus.framer.binary_framer import ModbusBinaryFramer
from pymodbus.utilities import ModbusTransactionState
from pymodbus.bit_read_message import ReadCoilsRequest
from pymodbus.exceptions import ModbusIOException
from pymodbus.compat import IS_PYTHON3
if IS_PYTHON3:
    from unittest.mock import Mock
else:  # Python 2
    from mock import Mock

@pytest.fixture
def rtu_framer():
    return ModbusRtuFramer(ClientDecoder())

@pytest.fixture
def ascii_framer():
    return ModbusAsciiFramer(ClientDecoder())

@pytest.fixture
def binary_framer():
    return ModbusBinaryFramer(ClientDecoder())

@pytest.mark.parametrize("framer",  [ModbusRtuFramer,
                                     ModbusAsciiFramer,
                                     ModbusBinaryFramer,
                                     ])
def test_framer_initialization(framer):
    decoder = ClientDecoder()
    framer = framer(decoder)
    assert framer.client == None
    assert framer._buffer == b''
    assert framer.decoder == decoder
    if isinstance(framer, ModbusAsciiFramer):
        assert framer._header == {'lrc': '0000', 'len': 0, 'uid': 0x00}
        assert framer._hsize == 0x02
        assert framer._start == b':'
        assert framer._end == b"\r\n"
    elif isinstance(framer, ModbusRtuFramer):
        assert framer._header == {'uid': 0x00, 'len': 0, 'crc': '0000'}
        assert framer._hsize == 0x01
        assert framer._end == b'\x0d\x0a'
        assert framer._min_frame_size == 4
    else:
        assert framer._header == {'crc': 0x0000, 'len': 0, 'uid': 0x00}
        assert framer._hsize == 0x01
        assert framer._start == b'\x7b'
        assert framer._end == b'\x7d'
        assert framer._repeat == [b'}'[0], b'{'[0]]


@pytest.mark.parametrize("data", [(b'', {}),
                                  (b'abcd', {'fcode': 98, 'unit': 97})])
def test_decode_data(rtu_framer, data):
    data, expected = data
    decoded = rtu_framer.decode_data(data)
    assert decoded == expected


@pytest.mark.parametrize("data", [(b'', False),
                                  (b'\x02\x01\x01\x00Q\xcc', True)])
def test_check_frame(rtu_framer, data):
    data, expected = data
    rtu_framer._buffer = data
    assert expected == rtu_framer.checkFrame()


@pytest.mark.parametrize("data", [b'', b'abcd'])
def test_advance_framer(rtu_framer, data):
    rtu_framer._buffer = data
    rtu_framer.advanceFrame()
    assert rtu_framer._header == {}
    assert rtu_framer._buffer == data


@pytest.mark.parametrize("data", [b'', b'abcd'])
def test_reset_framer(rtu_framer, data):
    rtu_framer._buffer = data
    rtu_framer.resetFrame()
    assert rtu_framer._header == {}
    assert rtu_framer._buffer == b''


@pytest.mark.parametrize("data", [(b'', False), (b'abcd', True)])
def test_is_frame_ready(rtu_framer, data):
    data, expected = data
    rtu_framer._buffer = data
    rtu_framer.advanceFrame()
    assert rtu_framer.isFrameReady() == expected


def test_populate_header(rtu_framer):
    rtu_framer.populateHeader(b'abcd')
    assert rtu_framer._header == {'crc': b'd', 'uid': 97, 'len': 5}


def test_add_to_frame(rtu_framer):
    assert rtu_framer._buffer == b''
    rtu_framer.addToFrame(b'abcd')
    assert rtu_framer._buffer == b'abcd'


def test_get_frame(rtu_framer):
    rtu_framer.addToFrame(b'\x02\x01\x01\x00Q\xcc')
    rtu_framer.populateHeader(b'\x02\x01\x01\x00Q\xcc')
    assert rtu_framer.getFrame() == b'\x01\x01\x00'


def test_populate_result(rtu_framer):
    rtu_framer._header['uid'] = 255
    result = Mock()
    rtu_framer.populateResult(result)
    assert result.unit_id == 255


def test_process_incoming_packet(rtu_framer):
    def cb(res):
        return res


def test_build_packet(rtu_framer):
    message = ReadCoilsRequest(1, 10)
    assert rtu_framer.buildPacket(message) == b'\x00\x01\x00\x01\x00\n\xec\x1c'


def test_send_packet(rtu_framer):
    message = b'\x00\x01\x00\x01\x00\n\xec\x1c'
    client = Mock()
    client.state = ModbusTransactionState.TRANSACTION_COMPLETE
    client.silent_interval = 1
    client.last_frame_end = 1
    client.timeout = 0.25
    client.idle_time.return_value = 1
    client.send.return_value = len(message)
    rtu_framer.client = client
    assert rtu_framer.sendPacket(message) == len(message)
    client.state = ModbusTransactionState.PROCESSING_REPLY
    assert rtu_framer.sendPacket(message) == len(message)


def test_recv_packet(rtu_framer):
    message = b'\x00\x01\x00\x01\x00\n\xec\x1c'
    client = Mock()
    client.recv.return_value = message
    rtu_framer.client = client
    assert rtu_framer.recvPacket(len(message)) == message


def test_process(rtu_framer):
    def cb(res):
        return res

    rtu_framer._buffer = b'\x00\x01\x00\x01\x00\n\xec\x1c'
    with pytest.raises(ModbusIOException):
        rtu_framer._process(cb)


def test_get_raw_frame(rtu_framer):
    rtu_framer._buffer = b'\x00\x01\x00\x01\x00\n\xec\x1c'
    assert rtu_framer.getRawFrame() == rtu_framer._bufferpymodbus-2.1.0/test/test_interfaces.py000066400000000000000000000052501335513467700201070ustar00rootroot00000000000000#!/usr/bin/env python
import unittest
from pymodbus.interfaces import *
from pymodbus.exceptions import NotImplementedException

class _SingleInstance(Singleton):
    pass

class ModbusInterfaceTestsTest(unittest.TestCase):
    '''
    This is the unittest for the pymodbus.interfaces module
    '''

    def setUp(self):
        ''' Initializes the test environment '''
        pass

    def tearDown(self):
        ''' Cleans up the test environment '''
        pass

    def testSingletonInterface(self):
        ''' Test that the singleton interface works '''
        first  = _SingleInstance()
        second = _SingleInstance()
        self.assertEqual(first, second)

    def testModbusDecoderInterface(self):
        ''' Test that the base class isn't implemented '''
        x = None
        instance = IModbusDecoder()
        self.assertRaises(NotImplementedException, lambda: instance.decode(x))
        self.assertRaises(NotImplementedException, lambda: instance.lookupPduClass(x))

    def testModbusFramerInterface(self):
        ''' Test that the base class isn't implemented '''
        x = None
        instance = IModbusFramer()
        self.assertRaises(NotImplementedException, instance.checkFrame)
        self.assertRaises(NotImplementedException, instance.advanceFrame)
        self.assertRaises(NotImplementedException, instance.isFrameReady)
        self.assertRaises(NotImplementedException, instance.getFrame)
        self.assertRaises(NotImplementedException, lambda: instance.addToFrame(x))
        self.assertRaises(NotImplementedException, lambda: instance.populateResult(x))
        self.assertRaises(NotImplementedException, lambda: instance.processIncomingPacket(x,x))
        self.assertRaises(NotImplementedException, lambda: instance.buildPacket(x))

    def testModbusSlaveContextInterface(self):
        ''' Test that the base class isn't implemented '''
        x = None
        instance = IModbusSlaveContext()
        self.assertRaises(NotImplementedException, instance.reset)
        self.assertRaises(NotImplementedException, lambda: instance.validate(x,x,x))
        self.assertRaises(NotImplementedException, lambda: instance.getValues(x,x,x))
        self.assertRaises(NotImplementedException, lambda: instance.setValues(x,x,x))

    def testModbusPayloadBuilderInterface(self):
        ''' Test that the base class isn't implemented '''
        x = None
        instance = IPayloadBuilder()
        self.assertRaises(NotImplementedException, lambda: instance.build())

#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
    unittest.main()
pymodbus-2.1.0/test/test_mei_messages.py000066400000000000000000000153061335513467700204300ustar00rootroot00000000000000'''
MEI Message Test Fixture
--------------------------------

This fixture tests the functionality of all the
mei based request/response messages:
'''
import unittest
from pymodbus.mei_message import *
from pymodbus.constants import DeviceInformation, MoreData
from pymodbus.pdu import ModbusExceptions
from pymodbus.device import ModbusControlBlock

#---------------------------------------------------------------------------#
# Fixture
#---------------------------------------------------------------------------#
class ModbusMeiMessageTest(unittest.TestCase):
    '''
    This is the unittest for the pymodbus.mei_message module
    '''

    #-----------------------------------------------------------------------#
    # Read Device Information
    #-----------------------------------------------------------------------#

    def testReadDeviceInformationRequestEncode(self):
        ''' Test basic bit message encoding/decoding '''
        params  = {'read_code':DeviceInformation.Basic, 'object_id':0x00 }
        handle  = ReadDeviceInformationRequest(**params)
        result  = handle.encode()
        self.assertEqual(result, b'\x0e\x01\x00')
        self.assertEqual("ReadDeviceInformationRequest(1,0)", str(handle))

    def testReadDeviceInformationRequestDecode(self):
        ''' Test basic bit message encoding/decoding '''
        handle  = ReadDeviceInformationRequest()
        handle.decode(b'\x0e\x01\x00')
        self.assertEqual(handle.read_code, DeviceInformation.Basic)
        self.assertEqual(handle.object_id, 0x00)

    def testReadDeviceInformationRequest(self):
        ''' Test basic bit message encoding/decoding '''
        context = None
        control = ModbusControlBlock()
        control.Identity.VendorName  = "Company"
        control.Identity.ProductCode = "Product"
        control.Identity.MajorMinorRevision = "v2.1.12"
        control.Identity.update({0x81: ['Test', 'Repeated']})

        handle  = ReadDeviceInformationRequest()
        result  = handle.execute(context)
        self.assertTrue(isinstance(result, ReadDeviceInformationResponse))
        self.assertEqual(result.information[0x00], "Company")
        self.assertEqual(result.information[0x01], "Product")
        self.assertEqual(result.information[0x02], "v2.1.12")
        with self.assertRaises(KeyError):
            _ = result.information[0x81]

        handle = ReadDeviceInformationRequest(read_code=DeviceInformation.Extended,
                                              object_id=0x80)
        result = handle.execute(context)
        self.assertEqual(result.information[0x81], ['Test', 'Repeated'])

    def testReadDeviceInformationRequestError(self):
        ''' Test basic bit message encoding/decoding '''
        handle  = ReadDeviceInformationRequest()
        handle.read_code = -1
        self.assertEqual(handle.execute(None).function_code, 0xab)
        handle.read_code = 0x05
        self.assertEqual(handle.execute(None).function_code, 0xab)
        handle.object_id = -1
        self.assertEqual(handle.execute(None).function_code, 0xab)
        handle.object_id = 0x100
        self.assertEqual(handle.execute(None).function_code, 0xab)

    def testReadDeviceInformationResponseEncode(self):
        ''' Test that the read fifo queue response can encode '''
        message  = b'\x0e\x01\x83\x00\x00\x03'
        message += b'\x00\x07Company\x01\x07Product\x02\x07v2.1.12'
        dataset  = {
            0x00: 'Company',
            0x01: 'Product',
            0x02: 'v2.1.12',
        }
        handle  = ReadDeviceInformationResponse(
            read_code=DeviceInformation.Basic, information=dataset)
        result  = handle.encode()
        self.assertEqual(result, message)
        self.assertEqual("ReadDeviceInformationResponse(1)", str(handle))

        dataset = {
            0x00: 'Company',
            0x01: 'Product',
            0x02: 'v2.1.12',
            0x81: ['Test', 'Repeated']
        }
        message = b'\x0e\x03\x83\x00\x00\x05'
        message += b'\x00\x07Company\x01\x07Product\x02\x07v2.1.12'
        message += b'\x81\x04Test\x81\x08Repeated'
        handle = ReadDeviceInformationResponse(
            read_code=DeviceInformation.Extended, information=dataset)
        result = handle.encode()
        self.assertEqual(result, message)

    def testReadDeviceInformationResponseEncodeLong(self):
        ''' Test that the read fifo queue response can encode '''
        longstring = "Lorem ipsum dolor sit amet, consectetur adipiscing " \
                     "elit. Vivamus rhoncus massa turpis, sit amet ultrices" \
                     " orci semper ut. Aliquam tristique sapien in lacus " \
                     "pharetra, in convallis nunc consectetur. Nunc velit " \
                     "elit, vehicula tempus tempus sed. "

        message  = b'\x0e\x01\x83\xFF\x80\x03'
        message += b'\x00\x07Company\x01\x07Product\x02\x07v2.1.12'
        dataset  = {
            0x00: 'Company',
            0x01: 'Product',
            0x02: 'v2.1.12',
            0x80: longstring
        }
        handle  = ReadDeviceInformationResponse(
            read_code=DeviceInformation.Basic, information=dataset)
        result  = handle.encode()
        self.assertEqual(result, message)
        self.assertEqual("ReadDeviceInformationResponse(1)", str(handle))

    def testReadDeviceInformationResponseDecode(self):
        ''' Test that the read device information response can decode '''
        message  = b'\x0e\x01\x01\x00\x00\x05'
        message += b'\x00\x07Company\x01\x07Product\x02\x07v2.1.12'
        message += b'\x81\x04Test\x81\x08Repeated\x81\x07Another'
        handle  = ReadDeviceInformationResponse(read_code=0x00, information=[])
        handle.decode(message)
        self.assertEqual(handle.read_code, DeviceInformation.Basic)
        self.assertEqual(handle.conformity, 0x01)
        self.assertEqual(handle.information[0x00], b'Company')
        self.assertEqual(handle.information[0x01], b'Product')
        self.assertEqual(handle.information[0x02], b'v2.1.12')
        self.assertEqual(handle.information[0x81], [b'Test', b'Repeated', b'Another'])

    def testRtuFrameSize(self):
        ''' Test that the read device information response can decode '''
        message = b'\x04\x2B\x0E\x01\x81\x00\x01\x01\x00\x06\x66\x6F\x6F\x62\x61\x72\xD7\x3B'
        result  = ReadDeviceInformationResponse.calculateRtuFrameSize(message)
        self.assertEqual(result, 18)
        message = b'\x00\x2B\x0E\x02\x00\x4D\x47'
        result = ReadDeviceInformationRequest.calculateRtuFrameSize(message)
        self.assertEqual(result, 7)


#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
    unittest.main()
pymodbus-2.1.0/test/test_other_messages.py000066400000000000000000000076731335513467700210070ustar00rootroot00000000000000#!/usr/bin/env python
import unittest
from pymodbus.other_message import *

class ModbusOtherMessageTest(unittest.TestCase):
    '''
    This is the unittest for the pymodbus.other_message module
    '''

    def setUp(self):
        self.requests = [
            ReadExceptionStatusRequest,
            GetCommEventCounterRequest,
            GetCommEventLogRequest,
            ReportSlaveIdRequest,
        ]

        self.responses = [
            lambda: ReadExceptionStatusResponse(0x12),
            lambda: GetCommEventCounterResponse(0x12),
            GetCommEventLogResponse,
            lambda: ReportSlaveIdResponse(0x12),
        ]

    def tearDown(self):
        ''' Cleans up the test environment '''
        del self.requests
        del self.responses

    def testOtherMessagesToString(self):
        for message in self.requests:
            self.assertNotEqual(str(message()), None)
        for message in self.responses:
            self.assertNotEqual(str(message()), None)

    def testReadExceptionStatus(self):
        request = ReadExceptionStatusRequest()
        request.decode(b'\x12')
        self.assertEqual(request.encode(), b'')
        self.assertEqual(request.execute().function_code, 0x07)

        response = ReadExceptionStatusResponse(0x12)
        self.assertEqual(response.encode(), b'\x12')
        response.decode(b'\x12')
        self.assertEqual(response.status, 0x12)

    def testGetCommEventCounter(self):
        request = GetCommEventCounterRequest()
        request.decode(b'\x12')
        self.assertEqual(request.encode(), b'')
        self.assertEqual(request.execute().function_code, 0x0b)

        response = GetCommEventCounterResponse(0x12)
        self.assertEqual(response.encode(), b'\x00\x00\x00\x12')
        response.decode(b'\x00\x00\x00\x12')
        self.assertEqual(response.status, True)
        self.assertEqual(response.count, 0x12)

        response.status = False
        self.assertEqual(response.encode(), b'\xFF\xFF\x00\x12')

    def testGetCommEventLog(self):
        request = GetCommEventLogRequest()
        request.decode(b'\x12')
        self.assertEqual(request.encode(), b'')
        self.assertEqual(request.execute().function_code, 0x0c)

        response = GetCommEventLogResponse()
        self.assertEqual(response.encode(), b'\x06\x00\x00\x00\x00\x00\x00')
        response.decode(b'\x06\x00\x00\x00\x12\x00\x12')
        self.assertEqual(response.status, True)
        self.assertEqual(response.message_count, 0x12)
        self.assertEqual(response.event_count, 0x12)
        self.assertEqual(response.events, [])

        response.status = False
        self.assertEqual(response.encode(), b'\x06\xff\xff\x00\x12\x00\x12')

    def testGetCommEventLogWithEvents(self):
        response = GetCommEventLogResponse(events=[0x12,0x34,0x56])
        self.assertEqual(response.encode(), b'\x09\x00\x00\x00\x00\x00\x00\x12\x34\x56')
        response.decode(b'\x09\x00\x00\x00\x12\x00\x12\x12\x34\x56')
        self.assertEqual(response.status, True)
        self.assertEqual(response.message_count, 0x12)
        self.assertEqual(response.event_count, 0x12)
        self.assertEqual(response.events, [0x12,0x34,0x56])

    def testReportSlaveId(self):
        request = ReportSlaveIdRequest()
        request.decode(b'\x12')
        self.assertEqual(request.encode(), b'')
        self.assertEqual(request.execute().function_code, 0x11)

        response = ReportSlaveIdResponse(request.execute().identifier, True)

        self.assertEqual(response.encode(), b'\tPymodbus\xff')
        response.decode(b'\x03\x12\x00')
        self.assertEqual(response.status, False)
        self.assertEqual(response.identifier, b'\x12\x00')

        response.status = False
        self.assertEqual(response.encode(), b'\x03\x12\x00\x00')

#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
    unittest.main()
pymodbus-2.1.0/test/test_payload.py000066400000000000000000000221111335513467700174100ustar00rootroot00000000000000#!/usr/bin/env python
"""
Payload Utilities Test Fixture
--------------------------------
This fixture tests the functionality of the payload
utilities.

* PayloadBuilder
* PayloadDecoder
"""
import unittest
from pymodbus.exceptions import ParameterException
from pymodbus.constants import Endian
from pymodbus.payload import BinaryPayloadBuilder, BinaryPayloadDecoder

#---------------------------------------------------------------------------#
# Fixture
#---------------------------------------------------------------------------#
class ModbusPayloadUtilityTests(unittest.TestCase):

    # ----------------------------------------------------------------------- #
    # Setup/TearDown
    # ----------------------------------------------------------------------- #

    def setUp(self):
        """
        Initializes the test environment and builds request/result
        encoding pairs
        """
        self.little_endian_payload = \
                       b'\x01\x02\x00\x03\x00\x00\x00\x04\x00\x00\x00\x00' \
                       b'\x00\x00\x00\xff\xfe\xff\xfd\xff\xff\xff\xfc\xff' \
                       b'\xff\xff\xff\xff\xff\xff\x00\x00\xa0\x3f\x00\x00' \
                       b'\x00\x00\x00\x00\x19\x40\x01\x00\x74\x65\x73\x74' \
                       b'\x11'

        self.big_endian_payload = \
                       b'\x01\x00\x02\x00\x00\x00\x03\x00\x00\x00\x00\x00' \
                       b'\x00\x00\x04\xff\xff\xfe\xff\xff\xff\xfd\xff\xff' \
                       b'\xff\xff\xff\xff\xff\xfc\x3f\xa0\x00\x00\x40\x19' \
                       b'\x00\x00\x00\x00\x00\x00\x00\x01\x74\x65\x73\x74' \
                       b'\x11'

        self.bitstring = [True, False, False, False, True, False, False, False]

    def tearDown(self):
        """ Cleans up the test environment """
        pass

    # ----------------------------------------------------------------------- #
    # Payload Builder Tests
    # ----------------------------------------------------------------------- #

    def testLittleEndianPayloadBuilder(self):
        """ Test basic bit message encoding/decoding """
        builder = BinaryPayloadBuilder(byteorder=Endian.Little,
                                       wordorder=Endian.Little)
        builder.add_8bit_uint(1)
        builder.add_16bit_uint(2)
        builder.add_32bit_uint(3)
        builder.add_64bit_uint(4)
        builder.add_8bit_int(-1)
        builder.add_16bit_int(-2)
        builder.add_32bit_int(-3)
        builder.add_64bit_int(-4)
        builder.add_32bit_float(1.25)
        builder.add_64bit_float(6.25)
        builder.add_16bit_uint(1)      # placeholder
        builder.add_string(b'test')
        builder.add_bits(self.bitstring)
        self.assertEqual(self.little_endian_payload, builder.to_string())

    def testBigEndianPayloadBuilder(self):
        """ Test basic bit message encoding/decoding """
        builder = BinaryPayloadBuilder(byteorder=Endian.Big)
        builder.add_8bit_uint(1)
        builder.add_16bit_uint(2)
        builder.add_32bit_uint(3)
        builder.add_64bit_uint(4)
        builder.add_8bit_int(-1)
        builder.add_16bit_int(-2)
        builder.add_32bit_int(-3)
        builder.add_64bit_int(-4)
        builder.add_32bit_float(1.25)
        builder.add_64bit_float(6.25)
        builder.add_16bit_uint(1)      # placeholder
        builder.add_string('test')
        builder.add_bits(self.bitstring)
        self.assertEqual(self.big_endian_payload, builder.to_string())

    def testPayloadBuilderReset(self):
        """ Test basic bit message encoding/decoding """
        builder = BinaryPayloadBuilder()
        builder.add_8bit_uint(0x12)
        builder.add_8bit_uint(0x34)
        builder.add_8bit_uint(0x56)
        builder.add_8bit_uint(0x78)
        self.assertEqual(b'\x12\x34\x56\x78', builder.to_string())
        self.assertEqual([b'\x12\x34', b'\x56\x78'], builder.build())
        builder.reset()
        self.assertEqual(b'', builder.to_string())
        self.assertEqual([], builder.build())

    def testPayloadBuilderWithRawPayload(self):
        """ Test basic bit message encoding/decoding """
        _coils1 = [False, True, True, True, True, False, False, False, False,
                   True, False, True, False, True, True, False]
        _coils2 = [False, True, False, True, False, True, True, False,
                   False, True, True, True, True, False, False, False]

        builder = BinaryPayloadBuilder([b'\x12', b'\x34', b'\x56', b'\x78'],
                                       repack=True)
        self.assertEqual(b'\x12\x34\x56\x78', builder.to_string())
        self.assertEqual([13330, 30806], builder.to_registers())

        self.assertEqual(_coils1, builder.to_coils())

        builder = BinaryPayloadBuilder([b'\x12', b'\x34', b'\x56', b'\x78'],
                                       byteorder=Endian.Big)
        self.assertEqual(b'\x12\x34\x56\x78', builder.to_string())
        self.assertEqual([4660, 22136], builder.to_registers())
        self.assertEqual('\x12\x34\x56\x78', str(builder))
        self.assertEqual(_coils2, builder.to_coils())

    # ----------------------------------------------------------------------- #
    # Payload Decoder Tests
    # ----------------------------------------------------------------------- #

    def testLittleEndianPayloadDecoder(self):
        """ Test basic bit message encoding/decoding """
        decoder = BinaryPayloadDecoder(self.little_endian_payload, 
                                       byteorder=Endian.Little,
                                       wordorder=Endian.Little)
        self.assertEqual(1,      decoder.decode_8bit_uint())
        self.assertEqual(2,      decoder.decode_16bit_uint())
        self.assertEqual(3,      decoder.decode_32bit_uint())
        self.assertEqual(4,      decoder.decode_64bit_uint())
        self.assertEqual(-1,     decoder.decode_8bit_int())
        self.assertEqual(-2,     decoder.decode_16bit_int())
        self.assertEqual(-3,     decoder.decode_32bit_int())
        self.assertEqual(-4,     decoder.decode_64bit_int())
        self.assertEqual(1.25,   decoder.decode_32bit_float())
        self.assertEqual(6.25,   decoder.decode_64bit_float())
        self.assertEqual(None,   decoder.skip_bytes(2))
        self.assertEqual('test', decoder.decode_string(4).decode())
        self.assertEqual(self.bitstring, decoder.decode_bits())

    def testBigEndianPayloadDecoder(self):
        """ Test basic bit message encoding/decoding """
        decoder = BinaryPayloadDecoder(self.big_endian_payload, 
                                       byteorder=Endian.Big)
        self.assertEqual(1,      decoder.decode_8bit_uint())
        self.assertEqual(2,      decoder.decode_16bit_uint())
        self.assertEqual(3,      decoder.decode_32bit_uint())
        self.assertEqual(4,      decoder.decode_64bit_uint())
        self.assertEqual(-1,     decoder.decode_8bit_int())
        self.assertEqual(-2,     decoder.decode_16bit_int())
        self.assertEqual(-3,     decoder.decode_32bit_int())
        self.assertEqual(-4,     decoder.decode_64bit_int())
        self.assertEqual(1.25,   decoder.decode_32bit_float())
        self.assertEqual(6.25,   decoder.decode_64bit_float())
        self.assertEqual(None,   decoder.skip_bytes(2))
        self.assertEqual(b'test', decoder.decode_string(4))
        self.assertEqual(self.bitstring, decoder.decode_bits())

    def testPayloadDecoderReset(self):
        """ Test the payload decoder reset functionality """
        decoder = BinaryPayloadDecoder(b'\x12\x34')
        self.assertEqual(0x12, decoder.decode_8bit_uint())
        self.assertEqual(0x34, decoder.decode_8bit_uint())
        decoder.reset()   
        self.assertEqual(0x3412, decoder.decode_16bit_uint())

    def testPayloadDecoderRegisterFactory(self):
        """ Test the payload decoder reset functionality """
        payload = [1, 2, 3, 4]
        decoder = BinaryPayloadDecoder.fromRegisters(payload, byteorder=Endian.Little)
        encoded = b'\x00\x01\x00\x02\x00\x03\x00\x04'
        self.assertEqual(encoded, decoder.decode_string(8))

        decoder = BinaryPayloadDecoder.fromRegisters(payload, byteorder=Endian.Big)
        encoded = b'\x00\x01\x00\x02\x00\x03\x00\x04'
        self.assertEqual(encoded, decoder.decode_string(8))

        self.assertRaises(ParameterException,
            lambda: BinaryPayloadDecoder.fromRegisters('abcd'))

    def testPayloadDecoderCoilFactory(self):
        """ Test the payload decoder reset functionality """
        payload = [1,0,0,0, 1,0,0,0, 0,0,0,1, 0,0,0,1]
        decoder = BinaryPayloadDecoder.fromCoils(payload, byteorder=Endian.Little)
        encoded = b'\x11\x88'
        self.assertEqual(encoded, decoder.decode_string(2))

        decoder = BinaryPayloadDecoder.fromCoils(payload, byteorder=Endian.Big)
        encoded = b'\x11\x88'
        self.assertEqual(encoded, decoder.decode_string(2))

        self.assertRaises(ParameterException,
            lambda: BinaryPayloadDecoder.fromCoils('abcd'))


#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
    unittest.main()
pymodbus-2.1.0/test/test_pdu.py000066400000000000000000000060061335513467700165540ustar00rootroot00000000000000#!/usr/bin/env python
import unittest
from pymodbus.pdu import *
from pymodbus.exceptions import *
from pymodbus.compat import iteritems

class SimplePduTest(unittest.TestCase):
    '''
    This is the unittest for the pymod.pdu module
    '''

    def setUp(self):
        ''' Initializes the test environment '''
        self.badRequests = (
        #       ModbusPDU(),
                ModbusRequest(),
                ModbusResponse(),
        )
        self.illegal = IllegalFunctionRequest(1)
        self.exception = ExceptionResponse(1,1)

    def tearDown(self):
        ''' Cleans up the test environment '''
        del self.badRequests
        del self.illegal
        del self.exception

    def testNotImpelmented(self):
        ''' Test a base classes for not implemented funtions '''
        for r in self.badRequests:
            self.assertRaises(NotImplementedException, r.encode)

        for r in self.badRequests:
            self.assertRaises(NotImplementedException, r.decode, None)

    def testErrorMethods(self):
        ''' Test all error methods '''
        self.illegal.decode("12345")
        self.illegal.execute(None)

        result = self.exception.encode()
        self.exception.decode(result)
        self.assertEqual(result, b'\x01')
        self.assertEqual(self.exception.exception_code, 1)

    def testRequestExceptionFactory(self):
        ''' Test all error methods '''
        request = ModbusRequest()
        request.function_code = 1
        errors = dict((ModbusExceptions.decode(c), c) for c in range(1,20))
        for error, code in iteritems(errors):
            result = request.doException(code)
            self.assertEqual(str(result), "Exception Response(129, 1, %s)" % error)

    def testCalculateRtuFrameSize(self):
        ''' Test the calculation of Modbus/RTU frame sizes '''
        self.assertRaises(NotImplementedException,
                          ModbusRequest.calculateRtuFrameSize, b'')
        ModbusRequest._rtu_frame_size = 5
        self.assertEqual(ModbusRequest.calculateRtuFrameSize(b''), 5)
        del ModbusRequest._rtu_frame_size

        ModbusRequest._rtu_byte_count_pos = 2
        self.assertEqual(ModbusRequest.calculateRtuFrameSize(
            b'\x11\x01\x05\xcd\x6b\xb2\x0e\x1b\x45\xe6'), 0x05 + 5)
        del ModbusRequest._rtu_byte_count_pos
        
        self.assertRaises(NotImplementedException,
                          ModbusResponse.calculateRtuFrameSize, b'')
        ModbusResponse._rtu_frame_size = 12
        self.assertEqual(ModbusResponse.calculateRtuFrameSize(b''), 12)
        del ModbusResponse._rtu_frame_size
        ModbusResponse._rtu_byte_count_pos = 2
        self.assertEqual(ModbusResponse.calculateRtuFrameSize(
            b'\x11\x01\x05\xcd\x6b\xb2\x0e\x1b\x45\xe6'), 0x05 + 5)
        del ModbusResponse._rtu_byte_count_pos
        
        
#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
    unittest.main()
pymodbus-2.1.0/test/test_ptwisted.py000066400000000000000000000014761335513467700176350ustar00rootroot00000000000000#!/usr/bin/env python
import unittest

#---------------------------------------------------------------------------#
# Fixture
#---------------------------------------------------------------------------#
class TwistedInternalCodeTest(unittest.TestCase):
    '''
    This is the unittest for the pymodbus.internal.ptwisted code
    '''

    #-----------------------------------------------------------------------#
    # Setup/TearDown
    #-----------------------------------------------------------------------#

    def testInstallConch(self):
        ''' Test that we can install the conch backend '''
        pass

#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
    unittest.main()
pymodbus-2.1.0/test/test_register_read_messages.py000066400000000000000000000161531335513467700224760ustar00rootroot00000000000000#!/usr/bin/env python
import unittest
from pymodbus.register_read_message import *
from pymodbus.register_read_message import ReadRegistersRequestBase
from pymodbus.register_read_message import ReadRegistersResponseBase
from pymodbus.exceptions import *
from pymodbus.pdu import ModbusExceptions
from pymodbus.compat import iteritems, iterkeys, get_next

from .modbus_mocks import MockContext, FakeList

#---------------------------------------------------------------------------#
# Fixture
#---------------------------------------------------------------------------#
class ReadRegisterMessagesTest(unittest.TestCase):
    '''
    Register Message Test Fixture
    --------------------------------
    This fixture tests the functionality of all the 
    register based request/response messages:
    
    * Read/Write Input Registers
    * Read Holding Registers
    '''

    def setUp(self):
        '''
        Initializes the test environment and builds request/result
        encoding pairs
        '''
        arguments = {
            'read_address':  1, 'read_count': 5,
            'write_address': 1, 'write_registers': [0x00]*5,
        }
        self.value  = 0xabcd
        self.values = [0xa, 0xb, 0xc]
        self.request_read  = {
            ReadRegistersRequestBase(1, 5)                  :b'\x00\x01\x00\x05',
            ReadHoldingRegistersRequest(1, 5)               :b'\x00\x01\x00\x05',
            ReadInputRegistersRequest(1,5)                  :b'\x00\x01\x00\x05',
            ReadWriteMultipleRegistersRequest(**arguments)  :b'\x00\x01\x00\x05\x00\x01\x00'
                                                             b'\x05\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
        }
        self.response_read  = {
            ReadRegistersResponseBase(self.values)          :b'\x06\x00\x0a\x00\x0b\x00\x0c',
            ReadHoldingRegistersResponse(self.values)       :b'\x06\x00\x0a\x00\x0b\x00\x0c',
            ReadInputRegistersResponse(self.values)         :b'\x06\x00\x0a\x00\x0b\x00\x0c',
            ReadWriteMultipleRegistersResponse(self.values) :b'\x06\x00\x0a\x00\x0b\x00\x0c',
        }

    def tearDown(self):
        ''' Cleans up the test environment '''
        del self.request_read
        del self.response_read

    def testReadRegisterResponseBase(self):
        response = ReadRegistersResponseBase(list(range(10)))
        for index in range(10):
            self.assertEqual(response.getRegister(index), index)

    def testRegisterReadRequests(self):
        for request, response in iteritems(self.request_read):
            self.assertEqual(request.encode(), response)

    def testRegisterReadResponses(self):
        for request, response in iteritems(self.response_read):
            self.assertEqual(request.encode(), response)

    def testRegisterReadResponseDecode(self):
        registers = [
            [0x0a,0x0b,0x0c],
            [0x0a,0x0b,0x0c],
            [0x0a,0x0b,0x0c],
            [0x0a,0x0b,0x0c, 0x0a,0x0b,0x0c],
        ]
        values = sorted(self.response_read.items(), key=lambda x: str(x))
        for packet, register in zip(values, registers):
            request, response = packet
            request.decode(response)
            self.assertEqual(request.registers, register)

    def testRegisterReadRequestsCountErrors(self):
        '''
        This tests that the register request messages
        will break on counts that are out of range
        '''
        mock = FakeList(0x800)
        requests = [
            ReadHoldingRegistersRequest(1, 0x800),
            ReadInputRegistersRequest(1,0x800),
            ReadWriteMultipleRegistersRequest(read_address=1,
                read_count=0x800, write_address=1, write_registers=5),
            ReadWriteMultipleRegistersRequest(read_address=1,
                read_count=5, write_address=1, write_registers=mock),
        ]
        for request in requests:
            result = request.execute(None)
            self.assertEqual(ModbusExceptions.IllegalValue,
                result.exception_code)

    def testRegisterReadRequestsValidateErrors(self):
        '''
        This tests that the register request messages
        will break on counts that are out of range
        '''
        context = MockContext()
        requests = [
            ReadHoldingRegistersRequest(-1, 5),
            ReadInputRegistersRequest(-1,5),
            #ReadWriteMultipleRegistersRequest(-1,5,1,5),
            #ReadWriteMultipleRegistersRequest(1,5,-1,5),
        ]
        for request in requests:
            result = request.execute(context)
            self.assertEqual(ModbusExceptions.IllegalAddress,
                result.exception_code)

    def testRegisterReadRequestsExecute(self):
        '''
        This tests that the register request messages
        will break on counts that are out of range
        '''
        context = MockContext(True)
        requests = [
            ReadHoldingRegistersRequest(-1, 5),
            ReadInputRegistersRequest(-1,5),
        ]
        for request in requests:
            response = request.execute(context)
            self.assertEqual(request.function_code, response.function_code)

    def testReadWriteMultipleRegistersRequest(self):
        context = MockContext(True)
        request = ReadWriteMultipleRegistersRequest(read_address=1,
            read_count=10, write_address=1, write_registers=[0x00])
        response = request.execute(context)
        self.assertEqual(request.function_code, response.function_code)

    def testReadWriteMultipleRegistersValidate(self):
        context = MockContext()
        context.validate = lambda f,a,c: a == 1
        request = ReadWriteMultipleRegistersRequest(read_address=1,
            read_count=10, write_address=2, write_registers=[0x00])
        response = request.execute(context)
        self.assertEqual(response.exception_code, ModbusExceptions.IllegalAddress)

        context.validate = lambda f,a,c: a == 2
        response = request.execute(context)
        self.assertEqual(response.exception_code, ModbusExceptions.IllegalAddress)

        request.write_byte_count = 0x100
        response = request.execute(context)
        self.assertEqual(response.exception_code, ModbusExceptions.IllegalValue)

    def testReadWriteMultipleRegistersRequestDecode(self):
        request, response = get_next((k,v) for k,v in self.request_read.items()
            if getattr(k, 'function_code', 0) == 23)
        request.decode(response)
        self.assertEqual(request.read_address, 0x01)
        self.assertEqual(request.write_address, 0x01)
        self.assertEqual(request.read_count, 0x05)
        self.assertEqual(request.write_count, 0x05)
        self.assertEqual(request.write_byte_count, 0x0a)
        self.assertEqual(request.write_registers, [0x00]*5)

    def testSerializingToString(self):
        for request in iterkeys(self.request_read):
            self.assertTrue(str(request) != None)
        for request in iterkeys(self.response_read):
            self.assertTrue(str(request) != None)

#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
    unittest.main()
pymodbus-2.1.0/test/test_register_write_messages.py000066400000000000000000000157061335513467700227200ustar00rootroot00000000000000#!/usr/bin/env python
import unittest
from pymodbus.register_write_message import *
from pymodbus.exceptions import ParameterException
from pymodbus.pdu import ModbusExceptions
from pymodbus.compat import iteritems, iterkeys
from pymodbus.payload import BinaryPayloadBuilder
from pymodbus.payload import Endian

from .modbus_mocks import MockContext

#---------------------------------------------------------------------------#
# Fixture
#---------------------------------------------------------------------------#
class WriteRegisterMessagesTest(unittest.TestCase):
    '''
    Register Message Test Fixture
    --------------------------------
    This fixture tests the functionality of all the 
    register based request/response messages:
    
    * Read/Write Input Registers
    * Read Holding Registers
    '''

    def setUp(self):
        '''
        Initializes the test environment and builds request/result
        encoding pairs
        '''
        self.value  = 0xabcd
        self.values = [0xa, 0xb, 0xc]
        builder = BinaryPayloadBuilder(byteorder=Endian.Big)
        builder.add_16bit_uint(0x1234)
        self.payload = builder.build()
        self.write  = {
            WriteSingleRegisterRequest(1, self.value)       : b'\x00\x01\xab\xcd',
            WriteSingleRegisterResponse(1, self.value)      : b'\x00\x01\xab\xcd',
            WriteMultipleRegistersRequest(1, self.values)   : b'\x00\x01\x00\x03\x06\x00\n\x00\x0b\x00\x0c',
            WriteMultipleRegistersResponse(1, 5)            : b'\x00\x01\x00\x05',

            WriteSingleRegisterRequest(1, self.payload[0], skip_encode=True): b'\x00\x01\x12\x34',
            WriteMultipleRegistersRequest(1, self.payload, skip_encode=True): b'\x00\x01\x00\x01\x02\x12\x34',
        }

    def tearDown(self):
        ''' Cleans up the test environment '''
        del self.write

    def testRegisterWriteRequestsEncode(self):
        for request, response in iteritems(self.write):
            self.assertEqual(request.encode(), response)

    def testRegisterWriteRequestsDecode(self):
        addresses = [1,1,1,1]
        values = sorted(self.write.items(), key=lambda x: str(x))
        for packet, address in zip(values, addresses):
            request, response = packet
            request.decode(response)
            self.assertEqual(request.address, address)

    def testInvalidWriteMultipleRegistersRequest(self):
        request = WriteMultipleRegistersRequest(0, None)
        self.assertEqual(request.values, [])

    def testSerializingToString(self):
        for request in iterkeys(self.write):
            self.assertTrue(str(request) != None)

    def testWriteSingleRegisterRequest(self):
        context = MockContext()
        request = WriteSingleRegisterRequest(0x00, 0xf0000)
        result = request.execute(context)
        self.assertEqual(result.exception_code, ModbusExceptions.IllegalValue)

        request.value = 0x00ff
        result = request.execute(context)
        self.assertEqual(result.exception_code, ModbusExceptions.IllegalAddress)

        context.valid = True
        result = request.execute(context)
        self.assertEqual(result.function_code, request.function_code)

    def testWriteMultipleRegisterRequest(self):
        context = MockContext()
        request = WriteMultipleRegistersRequest(0x00, [0x00]*10)
        result = request.execute(context)
        self.assertEqual(result.exception_code, ModbusExceptions.IllegalAddress)

        request.count = 0x05 # bytecode != code * 2
        result = request.execute(context)
        self.assertEqual(result.exception_code, ModbusExceptions.IllegalValue)

        request.count = 0x800 # outside of range
        result = request.execute(context)
        self.assertEqual(result.exception_code, ModbusExceptions.IllegalValue)

        context.valid = True
        request = WriteMultipleRegistersRequest(0x00, [0x00]*10)
        result = request.execute(context)
        self.assertEqual(result.function_code, request.function_code)

        # -----------------------------------------------------------------------#
        # Mask Write Register Request
        # -----------------------------------------------------------------------#

    def testMaskWriteRegisterRequestEncode(self):
        ''' Test basic bit message encoding/decoding '''
        handle = MaskWriteRegisterRequest(0x0000, 0x0101, 0x1010)
        result = handle.encode()
        self.assertEqual(result, b'\x00\x00\x01\x01\x10\x10')

    def testMaskWriteRegisterRequestDecode(self):
        ''' Test basic bit message encoding/decoding '''
        request = b'\x00\x04\x00\xf2\x00\x25'
        handle = MaskWriteRegisterRequest()
        handle.decode(request)
        self.assertEqual(handle.address, 0x0004)
        self.assertEqual(handle.and_mask, 0x00f2)
        self.assertEqual(handle.or_mask, 0x0025)

    def testMaskWriteRegisterRequestExecute(self):
        ''' Test write register request valid execution '''
        context = MockContext(valid=True, default=0x0000)
        handle = MaskWriteRegisterRequest(0x0000, 0x0101, 0x1010)
        result = handle.execute(context)
        self.assertTrue(isinstance(result, MaskWriteRegisterResponse))

    def testMaskWriteRegisterRequestInvalidExecute(self):
        ''' Test write register request execute with invalid data '''
        context = MockContext(valid=False, default=0x0000)
        handle = MaskWriteRegisterRequest(0x0000, -1, 0x1010)
        result = handle.execute(context)
        self.assertEqual(ModbusExceptions.IllegalValue,
                         result.exception_code)

        handle = MaskWriteRegisterRequest(0x0000, 0x0101, -1)
        result = handle.execute(context)
        self.assertEqual(ModbusExceptions.IllegalValue,
                         result.exception_code)

        handle = MaskWriteRegisterRequest(0x0000, 0x0101, 0x1010)
        result = handle.execute(context)
        self.assertEqual(ModbusExceptions.IllegalAddress,
                         result.exception_code)

        # -----------------------------------------------------------------------#
        # Mask Write Register Response
        # -----------------------------------------------------------------------#

    def testMaskWriteRegisterResponseEncode(self):
        ''' Test basic bit message encoding/decoding '''
        handle = MaskWriteRegisterResponse(0x0000, 0x0101, 0x1010)
        result = handle.encode()
        self.assertEqual(result, b'\x00\x00\x01\x01\x10\x10')

    def testMaskWriteRegisterResponseDecode(self):
        ''' Test basic bit message encoding/decoding '''
        request = b'\x00\x04\x00\xf2\x00\x25'
        handle = MaskWriteRegisterResponse()
        handle.decode(request)
        self.assertEqual(handle.address, 0x0004)
        self.assertEqual(handle.and_mask, 0x00f2)
        self.assertEqual(handle.or_mask, 0x0025)



#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
    unittest.main()
pymodbus-2.1.0/test/test_remote_datastore.py000066400000000000000000000050421335513467700213240ustar00rootroot00000000000000#!/usr/bin/env python
import unittest
from pymodbus.exceptions import NotImplementedException
from pymodbus.datastore.remote import RemoteSlaveContext
from pymodbus.bit_read_message import *
from pymodbus.bit_write_message import *
from pymodbus.register_read_message import *
from pymodbus.pdu import ExceptionResponse
from .modbus_mocks import mock

class RemoteModbusDataStoreTest(unittest.TestCase):
    '''
    This is the unittest for the pymodbus.datastore.remote module
    '''

    def testRemoteSlaveContext(self):
        ''' Test a modbus remote slave context '''
        context = RemoteSlaveContext(None)
        self.assertNotEqual(str(context), None)
        self.assertRaises(NotImplementedException, lambda: context.reset())

    def testRemoteSlaveSetValues(self):
        ''' Test setting values against a remote slave context '''
        client  = mock()
        client.write_coils = lambda a,b: WriteMultipleCoilsResponse()

        context = RemoteSlaveContext(client)
        result  = context.setValues(1, 0, [1])
        self.assertTrue(True)

    def testRemoteSlaveGetValues(self):
        ''' Test getting values from a remote slave context '''
        client  = mock()
        client.read_coils = lambda a,b: ReadCoilsResponse([1]*10)
        client.read_input_registers = lambda a,b: ReadInputRegistersResponse([10]*10)
        client.read_holding_registers = lambda a,b: ExceptionResponse(0x15)

        context = RemoteSlaveContext(client)
        result  = context.getValues(1, 0, 10)
        self.assertEqual(result, [1]*10)

        result  = context.getValues(4, 0, 10)
        self.assertEqual(result, [10]*10)

        result  = context.getValues(3, 0, 10)
        self.assertNotEqual(result, [10]*10)

    def testRemoteSlaveValidateValues(self):
        ''' Test validating against a remote slave context '''
        client  = mock()
        client.read_coils = lambda a,b: ReadCoilsResponse([1]*10)
        client.read_input_registers = lambda a,b: ReadInputRegistersResponse([10]*10)
        client.read_holding_registers = lambda a,b: ExceptionResponse(0x15)

        context = RemoteSlaveContext(client)
        result  = context.validate(1, 0, 10)
        self.assertTrue(result)

        result  = context.validate(4, 0, 10)
        self.assertTrue(result)

        result  = context.validate(3, 0, 10)
        self.assertFalse(result)

#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
    unittest.main()
pymodbus-2.1.0/test/test_server_async.py000066400000000000000000000266261335513467700205010ustar00rootroot00000000000000#!/usr/bin/env python
from pymodbus.compat import IS_PYTHON3
import unittest
if IS_PYTHON3: # Python 3
    from unittest.mock import patch, Mock, MagicMock
else: # Python 2
    from mock import patch, Mock, MagicMock
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.server.async import ModbusTcpProtocol, ModbusUdpProtocol
from pymodbus.server.async import ModbusServerFactory
from pymodbus.server.async import (
    StartTcpServer, StartUdpServer, StartSerialServer, StopServer,
    _is_main_thread
)
from pymodbus.compat import byte2int
from pymodbus.transaction import ModbusSocketFramer
from pymodbus.exceptions import NoSuchSlaveException, ModbusIOException

import sys
#---------------------------------------------------------------------------#
# Fixture
#---------------------------------------------------------------------------#
import platform
from distutils.version import LooseVersion

IS_DARWIN = platform.system().lower() == "darwin"
OSX_SIERRA = LooseVersion("10.12")
if IS_DARWIN:
    IS_HIGH_SIERRA_OR_ABOVE = LooseVersion(platform.mac_ver()[0])
    SERIAL_PORT = '/dev/ptyp0' if not IS_HIGH_SIERRA_OR_ABOVE else '/dev/ttyp0'
else:
    IS_HIGH_SIERRA_OR_ABOVE = False
    SERIAL_PORT = "/dev/ptmx"


class AsynchronousServerTest(unittest.TestCase):
    '''
    This is the unittest for the pymodbus.server.async module
    '''

    #-----------------------------------------------------------------------#
    # Setup/TearDown
    #-----------------------------------------------------------------------#
    def setUp(self):
        '''
        Initializes the test environment
        '''
        values = dict((i, '') for i in range(10))
        identity = ModbusDeviceIdentification(info=values)

    def tearDown(self):
        ''' Cleans up the test environment '''
        pass

    #-----------------------------------------------------------------------#
    # Test ModbusTcpProtocol
    #-----------------------------------------------------------------------#
    def testTcpServerStartup(self):
        ''' Test that the modbus tcp async server starts correctly '''
        with patch('twisted.internet.reactor') as mock_reactor:
            if IS_PYTHON3:
                console = False
                call_count = 1
            else:
                console = True
                call_count = 2
            StartTcpServer(context=None, console=console)
            self.assertEqual(mock_reactor.listenTCP.call_count, call_count)
            self.assertEqual(mock_reactor.run.call_count, 1)

    def testConnectionMade(self):
        protocol = ModbusTcpProtocol()
        protocol.transport = MagicMock()
        protocol.factory = MagicMock()
        protocol.factory.framer = ModbusSocketFramer
        protocol.connectionMade()
        self.assertIsInstance(protocol.framer, ModbusSocketFramer)

    def testConnectionLost(self):
        protocol = ModbusTcpProtocol()
        protocol.connectionLost("What ever reason")

    def testDataReceived(self):
        protocol = ModbusTcpProtocol()
        # mock_data = "Hellow world!"
        mock_data = b"\x00\x01\x12\x34\x00\x04\xff\x02\x12\x34"
        protocol.factory = MagicMock()
        protocol.factory.control.ListenOnly = False
        protocol.factory.store.slaves = MagicMock()
        protocol.factory.store.single = True
        protocol.factory.store.slaves.return_value = [byte2int(mock_data[6])]
        protocol.framer = protocol._execute = MagicMock()

        protocol.dataReceived(mock_data)
        self.assertTrue(protocol.framer.processIncomingPacket.called)

        # test datareceived returns None
        protocol.factory.control.ListenOnly = False
        self.assertEqual(protocol.dataReceived(mock_data), None)

    def testTcpExecuteSuccess(self):
        protocol = ModbusTcpProtocol()
        protocol.store = MagicMock()
        request = MagicMock()
        protocol._send = MagicMock()

        # tst  if _send being called
        protocol._execute(request)
        self.assertTrue(protocol._send.called)

    def testTcpExecuteFailure(self):
        protocol = ModbusTcpProtocol()
        protocol.factory = MagicMock()
        protocol.factory.store = MagicMock()
        protocol.store = MagicMock()
        protocol.factory.ignore_missing_slaves = False
        request = MagicMock()
        protocol._send = MagicMock()

        # CASE-1: test NoSuchSlaveException exceptions
        request.execute.side_effect = NoSuchSlaveException()
        self.assertRaises(
            NoSuchSlaveException, protocol._execute(request)
        )
        self.assertTrue(request.doException.called)

        # CASE-2: NoSuchSlaveException with ignore_missing_slaves = true
        protocol.ignore_missing_slaves = True
        request.execute.side_effect = NoSuchSlaveException()
        self.assertEqual(protocol._execute(request), None)

        # test other exceptions
        request.execute.side_effect = ModbusIOException()
        self.assertRaises(
            ModbusIOException, protocol._execute(request)
        )
        self.assertTrue(protocol._send.called)

    def testSendTcp(self):

        class MockMsg(object):
            def __init__(self,  msg, resp=False):
                self.should_respond = resp
                self.msg = msg

        mock_msg = b"\x00\x01\x12\x34\x00\x04\xff\x02\x12\x34"
        protocol = ModbusTcpProtocol()
        mock_data = MockMsg(resp=True, msg=mock_msg)

        protocol.control = MagicMock()
        protocol.framer = MagicMock()
        protocol.factory = MagicMock()
        protocol.framer.buildPacket = MagicMock(return_value=mock_msg)
        protocol.transport= MagicMock()

        protocol._send(mock_data)

        self.assertTrue(protocol.framer.buildPacket.called)
        self.assertTrue(protocol.transport.write.called)

        mock_data =MockMsg(resp=False, msg="helloworld")
        self.assertEqual(protocol._send(mock_data), None)

    #-----------------------------------------------------------------------#
    # Test ModbusServerFactory
    #-----------------------------------------------------------------------#
    def testModbusServerFactory(self):
        ''' Test the base class for all the clients '''
        factory = ModbusServerFactory(store=None)
        self.assertEqual(factory.control.Identity.VendorName, '')

        identity = ModbusDeviceIdentification(info={0x00: 'VendorName'})
        factory = ModbusServerFactory(store=None, identity=identity)
        self.assertEqual(factory.control.Identity.VendorName, 'VendorName')

    #-----------------------------------------------------------------------#
    # Test ModbusUdpProtocol
    #-----------------------------------------------------------------------#
    def testUdpServerInitialize(self):
        protocol = ModbusUdpProtocol(store=None)
        self.assertEqual(protocol.control.Identity.VendorName, '')

        identity = ModbusDeviceIdentification(info={0x00: 'VendorName'})
        protocol = ModbusUdpProtocol(store=None, identity=identity)
        self.assertEqual(protocol.control.Identity.VendorName, 'VendorName')


    def testUdpServerStartup(self):
        ''' Test that the modbus udp async server starts correctly '''
        with patch('twisted.internet.reactor') as mock_reactor:
            StartUdpServer(context=None)
            self.assertEqual(mock_reactor.listenUDP.call_count, 1)
            self.assertEqual(mock_reactor.run.call_count, 1)

    @patch("twisted.internet.serialport.SerialPort")
    def testSerialServerStartup(self, mock_sp):
        ''' Test that the modbus serial async server starts correctly '''
        with patch('twisted.internet.reactor') as mock_reactor:
            StartSerialServer(context=None, port=SERIAL_PORT)
            self.assertEqual(mock_reactor.run.call_count, 1)

    @patch("twisted.internet.serialport.SerialPort")
    def testStopServerFromMainThread(self, mock_sp):
        """
        Stop async server
        :return:
        """
        with patch('twisted.internet.reactor') as mock_reactor:
            StartSerialServer(context=None, port=SERIAL_PORT)
            self.assertEqual(mock_reactor.run.call_count, 1)
            StopServer()
            self.assertEqual(mock_reactor.stop.call_count, 1)

    @patch("twisted.internet.serialport.SerialPort")
    def testStopServerFromThread(self, mock_sp):
        """
        Stop async server from child thread
        :return:
        """
        from threading import Thread
        import time
        with patch('twisted.internet.reactor') as mock_reactor:
            StartSerialServer(context=None, port=SERIAL_PORT)
            self.assertEqual(mock_reactor.run.call_count, 1)
            t = Thread(target=StopServer)
            t.start()
            time.sleep(2)
            self.assertEqual(mock_reactor.callFromThread.call_count, 1)
    def testDatagramReceived(self):
        mock_data = b"\x00\x01\x12\x34\x00\x04\xff\x02\x12\x34"
        mock_addr = 0x01
        protocol = ModbusUdpProtocol(store=None)
        protocol.framer.processIncomingPacket = MagicMock()
        protocol.control.ListenOnly = False
        protocol._execute = MagicMock()

        protocol.datagramReceived(mock_data, mock_addr)
        self.assertTrue(protocol.framer.processIncomingPacket.called)

    def testSendUdp(self):
        protocol = ModbusUdpProtocol(store=None)
        mock_data = b"\x00\x01\x12\x34\x00\x04\xff\x02\x12\x34"
        mock_addr = 0x01

        protocol.control = MagicMock()
        protocol.framer = MagicMock()
        protocol.framer.buildPacket = MagicMock(return_value=mock_data)
        protocol.transport= MagicMock()

        protocol._send(mock_data, mock_addr)

        self.assertTrue(protocol.framer.buildPacket.called)
        self.assertTrue(protocol.transport.write.called)


    def testUdpExecuteSuccess(self):
        protocol = ModbusUdpProtocol(store=None)
        mock_addr = 0x01
        protocol.store = MagicMock()
        request = MagicMock()
        protocol._send = MagicMock()

        # tst  if _send being called
        protocol._execute(request, mock_addr)
        self.assertTrue(protocol._send.called)

    def testUdpExecuteFailure(self):
        protocol = ModbusUdpProtocol(store=None)
        mock_addr = 0x01
        protocol.store = MagicMock()
        request = MagicMock()
        protocol._send = MagicMock()

        # CASE-1: test NoSuchSlaveException exceptions
        request.execute.side_effect = NoSuchSlaveException()
        self.assertRaises(
            NoSuchSlaveException, protocol._execute(request, mock_addr)
        )
        self.assertTrue(request.doException.called)

        # CASE-2: NoSuchSlaveException with ignore_missing_slaves = true
        protocol.ignore_missing_slaves = True
        request.execute.side_effect = NoSuchSlaveException()
        self.assertEqual(protocol._execute(request, mock_addr), None)

        # test other exceptions
        request.execute.side_effect = ModbusIOException()
        self.assertRaises(
            ModbusIOException, protocol._execute(request, mock_addr)
        )
        self.assertTrue(protocol._send.called)

    def testStopServer(self):
        from twisted.internet import reactor
        reactor.stop = MagicMock()
        StopServer()

        self.assertTrue(reactor.stop.called)

    def testIsMainThread(self):
        import threading
        self.assertTrue(_is_main_thread())


# --------------------------------------------------------------------------- #
# Main
# --------------------------------------------------------------------------- #
if __name__ == "__main__":
    unittest.main()
pymodbus-2.1.0/test/test_server_context.py000066400000000000000000000070261335513467700210410ustar00rootroot00000000000000#!/usr/bin/env python
import unittest
from pymodbus.datastore import *
from pymodbus.exceptions import NoSuchSlaveException
from pymodbus.compat import iteritems

class ModbusServerSingleContextTest(unittest.TestCase):
    ''' This is the unittest for the pymodbus.datastore.ModbusServerContext
    using a single slave context.
    '''

    def setUp(self):
        ''' Sets up the test environment '''
        self.slave = ModbusSlaveContext()
        self.context = ModbusServerContext(slaves=self.slave, single=True)

    def tearDown(self):
        ''' Cleans up the test environment '''
        del self.context

    def testSingleContextGets(self):
        ''' Test getting on a single context '''
        for id in range(0, 0xff):
            self.assertEqual(self.slave, self.context[id])

    def testSingleContextDeletes(self):
        ''' Test removing on multiple context '''
        def _test():
            del self.context[0x00]
        self.assertRaises(NoSuchSlaveException, _test)

    def testSingleContextIter(self):
        ''' Test iterating over a single context '''
        expected = (0, self.slave)
        for slave in self.context:
            self.assertEqual(slave, expected)

    def testSingleContextDefault(self):
        ''' Test that the single context default values work '''
        self.context = ModbusServerContext()
        slave = self.context[0x00]
        self.assertEqual(slave, {})

    def testSingleContextSet(self):
        ''' Test a setting a single slave context '''
        slave = ModbusSlaveContext()
        self.context[0x00] = slave
        actual = self.context[0x00]
        self.assertEqual(slave, actual)

class ModbusServerMultipleContextTest(unittest.TestCase):
    ''' This is the unittest for the pymodbus.datastore.ModbusServerContext
    using multiple slave contexts.
    '''

    def setUp(self):
        ''' Sets up the test environment '''
        self.slaves  = dict((id, ModbusSlaveContext()) for id in range(10))
        self.context = ModbusServerContext(slaves=self.slaves, single=False)

    def tearDown(self):
        ''' Cleans up the test environment '''
        del self.context

    def testMultipleContextGets(self):
        ''' Test getting on multiple context '''
        for id in range(0, 10):
            self.assertEqual(self.slaves[id], self.context[id])

    def testMultipleContextDeletes(self):
        ''' Test removing on multiple context '''
        del self.context[0x00]
        self.assertRaises(NoSuchSlaveException, lambda: self.context[0x00])

    def testMultipleContextIter(self):
        ''' Test iterating over multiple context '''
        for id, slave in self.context:
            self.assertEqual(slave, self.slaves[id])
            self.assertTrue(id in self.context)

    def testMultipleContextDefault(self):
        ''' Test that the multiple context default values work '''
        self.context = ModbusServerContext(single=False)
        self.assertRaises(NoSuchSlaveException, lambda: self.context[0x00])

    def testMultipleContextSet(self):
        ''' Test a setting multiple slave contexts '''
        slaves = dict((id, ModbusSlaveContext()) for id in range(10))
        for id, slave in iteritems(slaves):
            self.context[id] = slave
        for id, slave in iteritems(slaves):
            actual = self.context[id]
            self.assertEqual(slave, actual)

#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
    unittest.main()
pymodbus-2.1.0/test/test_server_sync.py000066400000000000000000000402011335513467700203210ustar00rootroot00000000000000#!/usr/bin/env python
from pymodbus.compat import IS_PYTHON3
import unittest
if IS_PYTHON3: # Python 3
    from unittest.mock import patch, Mock
else: # Python 2
    from mock import patch, Mock
import serial
import socket

from pymodbus.device import ModbusDeviceIdentification
from pymodbus.server.sync import ModbusBaseRequestHandler
from pymodbus.server.sync import ModbusSingleRequestHandler
from pymodbus.server.sync import ModbusConnectedRequestHandler
from pymodbus.server.sync import ModbusDisconnectedRequestHandler
from pymodbus.server.sync import ModbusTcpServer, ModbusUdpServer, ModbusSerialServer
from pymodbus.server.sync import StartTcpServer, StartUdpServer, StartSerialServer
from pymodbus.exceptions import NotImplementedException
from pymodbus.bit_read_message import ReadCoilsRequest, ReadCoilsResponse
from pymodbus.datastore import ModbusServerContext

from pymodbus.compat import socketserver

import platform
from distutils.version import LooseVersion

IS_DARWIN = platform.system().lower() == "darwin"
OSX_SIERRA = LooseVersion("10.12")
if IS_DARWIN:
    IS_HIGH_SIERRA_OR_ABOVE = LooseVersion(platform.mac_ver()[0])
    SERIAL_PORT = '/dev/ptyp0' if not IS_HIGH_SIERRA_OR_ABOVE else '/dev/ttyp0'
else:
    IS_HIGH_SIERRA_OR_ABOVE = False
    SERIAL_PORT = "/dev/ptmx"
#---------------------------------------------------------------------------#
# Mock Classes
#---------------------------------------------------------------------------#
class MockServer(object):
    def __init__(self):
        self.framer  = lambda _, client=None: "framer"
        self.decoder = "decoder"
        self.threads = []
        self.context = {}


#---------------------------------------------------------------------------#
# Fixture
#---------------------------------------------------------------------------#
class SynchronousServerTest(unittest.TestCase):
    '''
    This is the unittest for the pymodbus.server.sync module
    '''

    #-----------------------------------------------------------------------#
    # Test Base Request Handler
    #-----------------------------------------------------------------------#

    def testBaseHandlerUndefinedMethods(self):
        ''' Test the base handler undefined methods'''
        handler = socketserver.BaseRequestHandler(None, None, None)
        handler.__class__ = ModbusBaseRequestHandler
        self.assertRaises(NotImplementedException, lambda: handler.send(None))
        self.assertRaises(NotImplementedException, lambda: handler.handle())

    def testBaseHandlerMethods(self):
        ''' Test the base class for all the clients '''
        request = ReadCoilsRequest(1, 1)
        address = ('server', 12345)
        server  = MockServer()
        with patch.object(ModbusBaseRequestHandler, 'handle') as mock_handle:
            with patch.object(ModbusBaseRequestHandler, 'send') as mock_send:
                mock_handle.return_value = True
                mock_send.return_value = True
                handler = ModbusBaseRequestHandler(request, address, server)
                self.assertEqual(handler.running, True)
                self.assertEqual(handler.framer, 'framer')

                handler.execute(request)
                self.assertEqual(mock_send.call_count, 1)

                server.context[0x00] = object()
                handler.execute(request)
                self.assertEqual(mock_send.call_count, 2)

    #-----------------------------------------------------------------------#
    # Test Single Request Handler
    #-----------------------------------------------------------------------#
    def testModbusSingleRequestHandlerSend(self):
        handler = socketserver.BaseRequestHandler(None, None, None)
        handler.__class__ = ModbusSingleRequestHandler
        handler.framer  = Mock()
        handler.framer.buildPacket.return_value = b"message"
        handler.request = Mock()
        request = ReadCoilsResponse([1])
        handler.send(request)
        self.assertEqual(handler.request.send.call_count, 1)

        request.should_respond = False
        handler.send(request)
        self.assertEqual(handler.request.send.call_count, 1)

    def testModbusSingleRequestHandlerHandle(self):
        handler = socketserver.BaseRequestHandler(None, None, None)
        handler.__class__ = ModbusSingleRequestHandler
        handler.framer  = Mock()
        handler.framer.buildPacket.return_value = b"message"
        handler.request = Mock()
        handler.socket = Mock()
        handler.server = Mock()
        handler.request.recv.return_value = b"\x12\x34"

        # exit if we are not running
        handler.running = False
        handler.handle()
        self.assertEqual(handler.framer.processIncomingPacket.call_count, 0)

        # run forever if we are running
        def _callback1(a, b, *args, **kwargs):
            handler.running = False # stop infinite loop
        handler.framer.processIncomingPacket.side_effect = _callback1
        handler.running = True
        # Ugly hack
        handler.server.context = ModbusServerContext(slaves={-1: None},
                                                     single=False)
        handler.handle()
        self.assertEqual(handler.framer.processIncomingPacket.call_count, 1)

        # exceptions are simply ignored
        def _callback2(a, b, *args, **kwargs):
            if handler.framer.processIncomingPacket.call_count == 2:
                raise Exception("example exception")
            else: handler.running = False # stop infinite loop
        handler.framer.processIncomingPacket.side_effect = _callback2
        handler.running = True
        handler.handle()
        self.assertEqual(handler.framer.processIncomingPacket.call_count, 3)

    #-----------------------------------------------------------------------#
    # Test Connected Request Handler
    #-----------------------------------------------------------------------#
    def testModbusConnectedRequestHandlerSend(self):
        handler = socketserver.BaseRequestHandler(None, None, None)
        handler.__class__ = ModbusConnectedRequestHandler
        handler.framer = Mock()
        handler.framer.buildPacket.return_value = b"message"
        handler.request = Mock()
        request = ReadCoilsResponse([1])
        handler.send(request)
        self.assertEqual(handler.request.send.call_count, 1)

        request.should_respond = False
        handler.send(request)
        self.assertEqual(handler.request.send.call_count, 1)

    def testModbusConnectedRequestHandlerHandle(self):
        handler = socketserver.BaseRequestHandler(None, None, None)
        handler.__class__ = ModbusConnectedRequestHandler
        handler.server = Mock()
        # handler.server.context.slaves = Mock()
        # protocol.factory.store.single = True
        handler.framer  = Mock()
        handler.framer.buildPacket.return_value = b"message"
        handler.request = Mock()
        handler.request.recv.return_value = b"\x12\x34"

        # exit if we are not running
        handler.running = False
        handler.handle()
        self.assertEqual(handler.framer.processIncomingPacket.call_count, 0)

        # run forever if we are running
        def _callback(a, b, *args, **kwargs):
            handler.running = False # stop infinite loop
        handler.framer.processIncomingPacket.side_effect = _callback
        handler.running = True
        handler.handle()
        self.assertEqual(handler.framer.processIncomingPacket.call_count, 1)

        # socket errors cause the client to disconnect
        handler.framer.processIncomingPacket.side_effect = socket.error()
        handler.running = True
        handler.handle()
        self.assertEqual(handler.framer.processIncomingPacket.call_count, 2)

        # every other exception causes the client to disconnect
        handler.framer.processIncomingPacket.side_effect = Exception()
        handler.running = True
        handler.handle()
        self.assertEqual(handler.framer.processIncomingPacket.call_count, 3)

        # receiving no data causes the client to disconnect
        handler.request.recv.return_value = None
        handler.running = True
        handler.handle()
        self.assertEqual(handler.framer.processIncomingPacket.call_count, 4)

    #-----------------------------------------------------------------------#
    # Test Disconnected Request Handler
    #-----------------------------------------------------------------------#
    def testModbusDisconnectedRequestHandlerSend(self):
        handler = socketserver.BaseRequestHandler(None, None, None)
        handler.__class__ = ModbusDisconnectedRequestHandler
        handler.framer  = Mock()
        handler.server = Mock()
        handler.framer.buildPacket.return_value = b"message"
        handler.request = Mock()
        handler.socket = Mock()
        request = ReadCoilsResponse([1])
        handler.send(request)
        self.assertEqual(handler.socket.sendto.call_count, 1)

        request.should_respond = False
        handler.send(request)
        self.assertEqual(handler.socket.sendto.call_count, 1)

    def testModbusDisconnectedRequestHandlerHandle(self):
        handler = socketserver.BaseRequestHandler(None, None, None)
        handler.__class__ = ModbusDisconnectedRequestHandler
        handler.framer  = Mock()
        handler.server = Mock()
        handler.framer.buildPacket.return_value = b"message"
        handler.request = (b"\x12\x34", handler.request)

        # exit if we are not running
        handler.running = False
        handler.handle()
        self.assertEqual(handler.framer.processIncomingPacket.call_count, 0)

        # run forever if we are running
        def _callback(a, b):
            handler.running = False # stop infinite loop
        handler.framer.processIncomingPacket.side_effect = _callback
        handler.running = True
        handler.handle()
        self.assertEqual(handler.framer.processIncomingPacket.call_count, 1)

        # socket errors cause the client to disconnect
        handler.request = (b"\x12\x34", handler.request)
        handler.framer.processIncomingPacket.side_effect = socket.error()
        handler.running = True
        handler.handle()
        self.assertEqual(handler.framer.processIncomingPacket.call_count, 2)

        # every other exception causes the client to disconnect
        handler.request = (b"\x12\x34", handler.request)
        handler.framer.processIncomingPacket.side_effect = Exception()
        handler.running = True
        handler.handle()
        self.assertEqual(handler.framer.processIncomingPacket.call_count, 3)

        # receiving no data causes the client to disconnect
        handler.request = (None, handler.request)
        handler.running = True
        handler.handle()
        self.assertEqual(handler.framer.processIncomingPacket.call_count, 4)

    #-----------------------------------------------------------------------#
    # Test TCP Server
    #-----------------------------------------------------------------------#
    def testTcpServerClose(self):
        ''' test that the synchronous TCP server closes correctly '''
        with patch.object(socket.socket, 'bind') as mock_socket:
            identity = ModbusDeviceIdentification(info={0x00: 'VendorName'})
            server = ModbusTcpServer(context=None, identity=identity)
            server.threads.append(Mock(**{'running': True}))
            server.server_close()
            self.assertEqual(server.control.Identity.VendorName, 'VendorName')
            self.assertFalse(server.threads[0].running)

    def testTcpServerProcess(self):
        ''' test that the synchronous TCP server processes requests '''
        with patch('pymodbus.compat.socketserver.ThreadingTCPServer') as mock_server:
            server = ModbusTcpServer(None)
            server.process_request('request', 'client')
            self.assertTrue(mock_server.process_request.called)

    #-----------------------------------------------------------------------#
    # Test UDP Server
    #-----------------------------------------------------------------------#
    def testUdpServerClose(self):
        ''' test that the synchronous UDP server closes correctly '''
        with patch.object(socket.socket, 'bind') as mock_socket:
            identity = ModbusDeviceIdentification(info={0x00: 'VendorName'})
            server = ModbusUdpServer(context=None, identity=identity)
            server.threads.append(Mock(**{'running': True}))
            server.server_close()
            self.assertEqual(server.control.Identity.VendorName, 'VendorName')
            self.assertFalse(server.threads[0].running)

    def testUdpServerProcess(self):
        ''' test that the synchronous UDP server processes requests '''
        with patch('pymodbus.compat.socketserver.ThreadingUDPServer') as mock_server:
            server = ModbusUdpServer(None)
            request = ('data', 'socket')
            server.process_request(request, 'client')
            self.assertTrue(mock_server.process_request.called)

    #-----------------------------------------------------------------------#
    # Test Serial Server
    #-----------------------------------------------------------------------#
    def testSerialServerConnect(self):
        with patch.object(serial, 'Serial') as mock_serial:
                # mock_serial.return_value = "socket"
                mock_serial.write = lambda x: len(x)
                mock_serial.read = lambda size: '\x00' * size
                identity = ModbusDeviceIdentification(info={0x00: 'VendorName'})
                server = ModbusSerialServer(context=None, identity=identity, port="dummy")
                # # mock_serial.return_value = "socket"
                # self.assertEqual(server.socket.port, "dummy")
                self.assertEquals(server.handler.__class__.__name__, "CustomSingleRequestHandler")
                self.assertEqual(server.control.Identity.VendorName, 'VendorName')

                server._connect()
                # self.assertEqual(server.socket, "socket")

        with patch.object(serial, 'Serial') as mock_serial:
            mock_serial.write = lambda x: len(x)
            mock_serial.read = lambda size: '\x00' * size
            mock_serial.side_effect = serial.SerialException()
            server = ModbusSerialServer(None, port="dummy")
            self.assertEqual(server.socket, None)

    def testSerialServerServeForever(self):
        ''' test that the synchronous serial server closes correctly '''
        with patch.object(serial, 'Serial') as mock_serial:
            with patch('pymodbus.server.sync.CustomSingleRequestHandler') as mock_handler:
                server = ModbusSerialServer(None)
                instance = mock_handler.return_value
                instance.handle.side_effect = server.server_close
                server.serve_forever()
                instance.handle.assert_any_call()

    def testSerialServerClose(self):
        ''' test that the synchronous serial server closes correctly '''
        with patch.object(serial, 'Serial') as mock_serial:
            instance = mock_serial.return_value
            server = ModbusSerialServer(None)
            server.server_close()
            instance.close.assert_any_call()

    #-----------------------------------------------------------------------#
    # Test Synchronous Factories
    #-----------------------------------------------------------------------#
    def testStartTcpServer(self):
        ''' Test the tcp server starting factory '''
        with patch.object(ModbusTcpServer, 'serve_forever') as mock_server:
            with patch.object(socketserver.TCPServer, 'server_bind') as mock_binder:
                StartTcpServer()

    def testStartUdpServer(self):
        ''' Test the udp server starting factory '''
        with patch.object(ModbusUdpServer, 'serve_forever') as mock_server:
            with patch.object(socketserver.UDPServer, 'server_bind') as mock_binder:
                StartUdpServer()

    def testStartSerialServer(self):
        ''' Test the serial server starting factory '''
        with patch.object(ModbusSerialServer, 'serve_forever') as mock_server:
            StartSerialServer(port=SERIAL_PORT)

#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
    unittest.main()
pymodbus-2.1.0/test/test_transaction.py000066400000000000000000000544351335513467700203220ustar00rootroot00000000000000#!/usr/bin/env python
import pytest
import unittest
from binascii import a2b_hex
from pymodbus.pdu import *
from pymodbus.transaction import *
from pymodbus.transaction import (
    ModbusTransactionManager, ModbusSocketFramer, ModbusAsciiFramer,
    ModbusRtuFramer, ModbusBinaryFramer
)
from pymodbus.factory import ServerDecoder
from pymodbus.compat import byte2int
from mock import MagicMock
from pymodbus.exceptions import (
    NotImplementedException, ModbusIOException, InvalidMessageReceivedException
)


class ModbusTransactionTest(unittest.TestCase):
    """
    This is the unittest for the pymodbus.transaction module
    """

    # ----------------------------------------------------------------------- #
    # Test Construction
    # ----------------------------------------------------------------------- #
    def setUp(self):
        """ Sets up the test environment """
        self.client   = None
        self.decoder  = ServerDecoder()
        self._tcp     = ModbusSocketFramer(decoder=self.decoder, client=None)
        self._rtu     = ModbusRtuFramer(decoder=self.decoder, client=None)
        self._ascii   = ModbusAsciiFramer(decoder=self.decoder, client=None)
        self._binary  = ModbusBinaryFramer(decoder=self.decoder, client=None)
        self._manager = DictTransactionManager(self.client)
        self._queue_manager = FifoTransactionManager(self.client)
        self._tm = ModbusTransactionManager(self.client)

    def tearDown(self):
        """ Cleans up the test environment """
        del self._manager
        del self._tcp
        del self._rtu
        del self._ascii

    # ----------------------------------------------------------------------- #
    # Base transaction manager
    # ----------------------------------------------------------------------- #

    def testCalculateExpectedResponseLength(self):
        self._tm.client = MagicMock()
        self._tm.client.framer = MagicMock()
        self._tm._set_adu_size()
        self.assertEqual(self._tm._calculate_response_length(0), None)
        self._tm.base_adu_size = 10
        self.assertEqual(self._tm._calculate_response_length(5), 15)

    def testCalculateExceptionLength(self):
        for framer, exception_length in [('ascii', 11),
                                         ('binary', 7),
                                         ('rtu', 5),
                                         ('tcp', 9),
                                         ('dummy', None)]:
            self._tm.client = MagicMock()
            if framer == "ascii":
                self._tm.client.framer = self._ascii
            elif framer == "binary":
                self._tm.client.framer = self._binary
            elif framer == "rtu":
                self._tm.client.framer = self._rtu
            elif framer == "tcp":
                self._tm.client.framer = self._tcp
            else:
                self._tm.client.framer = MagicMock()

            self._tm._set_adu_size()
            self.assertEqual(self._tm._calculate_exception_length(),
                             exception_length)

    def testExecute(self):
        client = MagicMock()
        client.framer = self._ascii
        client.framer._buffer = b'deadbeef'
        client.framer.processIncomingPacket = MagicMock()
        client.framer.processIncomingPacket.return_value = None
        client.framer.buildPacket = MagicMock()
        client.framer.buildPacket.return_value = b'deadbeef'
        client.framer.sendPacket = MagicMock()
        client.framer.sendPacket.return_value = len(b'deadbeef')

        request = MagicMock()
        request.get_response_pdu_size.return_value = 10
        request.unit_id = 1
        tm = ModbusTransactionManager(client)
        tm._recv = MagicMock(return_value=b'abcdef')
        self.assertEqual(tm.retries, 3)
        self.assertEqual(tm.retry_on_empty, False)
        # tm._transact = MagicMock()
        # some response
        # tm._transact.return_value = (b'abcdef', None)
        tm.getTransaction = MagicMock()
        tm.getTransaction.return_value = 'response'
        response = tm.execute(request)
        self.assertEqual(response, 'response')
        # No response
        tm._recv = MagicMock(return_value=b'abcdef')
        # tm._transact.return_value = (b'', None)
        tm.transactions = []
        tm.getTransaction = MagicMock()
        tm.getTransaction.return_value = None
        response = tm.execute(request)
        self.assertIsInstance(response, ModbusIOException)

        # No response with retries
        tm.retry_on_empty = True
        tm._recv = MagicMock(side_effect=iter([b'', b'abcdef']))
        # tm._transact.side_effect = [(b'', None), (b'abcdef', None)]
        response = tm.execute(request)
        self.assertIsInstance(response, ModbusIOException)

        # Unable to decode response
        tm._recv = MagicMock(side_effect=ModbusIOException())
        # tm._transact.side_effect = [(b'abcdef', None)]
        client.framer.processIncomingPacket.side_effect = MagicMock(side_effect=ModbusIOException())
        self.assertIsInstance(tm.execute(request), ModbusIOException)

    # ----------------------------------------------------------------------- #
    # Dictionary based transaction manager
    # ----------------------------------------------------------------------- #

    def testDictTransactionManagerTID(self):
        """ Test the dict transaction manager TID """
        for tid in range(1, self._manager.getNextTID() + 10):
            self.assertEqual(tid+1, self._manager.getNextTID())
        self._manager.reset()
        self.assertEqual(1, self._manager.getNextTID())

    def testGetDictTransactionManagerTransaction(self):
        """ Test the dict transaction manager """
        class Request: pass
        self._manager.reset()
        handle = Request()
        handle.transaction_id = self._manager.getNextTID()
        handle.message = b"testing"
        self._manager.addTransaction(handle)
        result = self._manager.getTransaction(handle.transaction_id)
        self.assertEqual(handle.message, result.message)

    def testDeleteDictTransactionManagerTransaction(self):
        """ Test the dict transaction manager """
        class Request: pass
        self._manager.reset()
        handle = Request()
        handle.transaction_id = self._manager.getNextTID()
        handle.message = b"testing"

        self._manager.addTransaction(handle)
        self._manager.delTransaction(handle.transaction_id)
        self.assertEqual(None, self._manager.getTransaction(handle.transaction_id))

    # ----------------------------------------------------------------------- #
    # Queue based transaction manager
    # ----------------------------------------------------------------------- #
    def testFifoTransactionManagerTID(self):
        """ Test the fifo transaction manager TID """
        for tid in range(1, self._queue_manager.getNextTID() + 10):
            self.assertEqual(tid+1, self._queue_manager.getNextTID())
        self._queue_manager.reset()
        self.assertEqual(1, self._queue_manager.getNextTID())

    def testGetFifoTransactionManagerTransaction(self):
        """ Test the fifo transaction manager """
        class Request: pass
        self._queue_manager.reset()
        handle = Request()
        handle.transaction_id = self._queue_manager.getNextTID()
        handle.message = b"testing"
        self._queue_manager.addTransaction(handle)
        result = self._queue_manager.getTransaction(handle.transaction_id)
        self.assertEqual(handle.message, result.message)

    def testDeleteFifoTransactionManagerTransaction(self):
        """ Test the fifo transaction manager """
        class Request: pass
        self._queue_manager.reset()
        handle = Request()
        handle.transaction_id = self._queue_manager.getNextTID()
        handle.message = b"testing"

        self._queue_manager.addTransaction(handle)
        self._queue_manager.delTransaction(handle.transaction_id)
        self.assertEqual(None, self._queue_manager.getTransaction(handle.transaction_id))

    # ----------------------------------------------------------------------- #
    # TCP tests
    # ----------------------------------------------------------------------- #
    def testTCPFramerTransactionReady(self):
        """ Test a tcp frame transaction """
        msg = b"\x00\x01\x12\x34\x00\x04\xff\x02\x12\x34"
        self.assertFalse(self._tcp.isFrameReady())
        self.assertFalse(self._tcp.checkFrame())
        self._tcp.addToFrame(msg)
        self.assertTrue(self._tcp.isFrameReady())
        self.assertTrue(self._tcp.checkFrame())
        self._tcp.advanceFrame()
        self.assertFalse(self._tcp.isFrameReady())
        self.assertFalse(self._tcp.checkFrame())
        self.assertEqual(b'', self._ascii.getFrame())

    def testTCPFramerTransactionFull(self):
        """ Test a full tcp frame transaction """
        msg = b"\x00\x01\x12\x34\x00\x04\xff\x02\x12\x34"
        self._tcp.addToFrame(msg)
        self.assertTrue(self._tcp.checkFrame())
        result = self._tcp.getFrame()
        self.assertEqual(msg[7:], result)
        self._tcp.advanceFrame()

    def testTCPFramerTransactionHalf(self):
        """ Test a half completed tcp frame transaction """
        msg1 = b"\x00\x01\x12\x34\x00"
        msg2 = b"\x04\xff\x02\x12\x34"
        self._tcp.addToFrame(msg1)
        self.assertFalse(self._tcp.checkFrame())
        result = self._tcp.getFrame()
        self.assertEqual(b'', result)
        self._tcp.addToFrame(msg2)
        self.assertTrue(self._tcp.checkFrame())
        result = self._tcp.getFrame()
        self.assertEqual(msg2[2:], result)
        self._tcp.advanceFrame()

    def testTCPFramerTransactionHalf2(self):
        """ Test a half completed tcp frame transaction """
        msg1 = b"\x00\x01\x12\x34\x00\x04\xff"
        msg2 = b"\x02\x12\x34"
        self._tcp.addToFrame(msg1)
        self.assertFalse(self._tcp.checkFrame())
        result = self._tcp.getFrame()
        self.assertEqual(b'', result)
        self._tcp.addToFrame(msg2)
        self.assertTrue(self._tcp.checkFrame())
        result = self._tcp.getFrame()
        self.assertEqual(msg2, result)
        self._tcp.advanceFrame()

    def testTCPFramerTransactionHalf3(self):
        """ Test a half completed tcp frame transaction """
        msg1 = b"\x00\x01\x12\x34\x00\x04\xff\x02\x12"
        msg2 = b"\x34"
        self._tcp.addToFrame(msg1)
        self.assertFalse(self._tcp.checkFrame())
        result = self._tcp.getFrame()
        self.assertEqual(msg1[7:], result)
        self._tcp.addToFrame(msg2)
        self.assertTrue(self._tcp.checkFrame())
        result = self._tcp.getFrame()
        self.assertEqual(msg1[7:] + msg2, result)
        self._tcp.advanceFrame()

    def testTCPFramerTransactionShort(self):
        """ Test that we can get back on track after an invalid message """
        msg1 = b"\x99\x99\x99\x99\x00\x01\x00\x01"
        msg2 = b"\x00\x01\x12\x34\x00\x04\xff\x02\x12\x34"
        self._tcp.addToFrame(msg1)
        self.assertFalse(self._tcp.checkFrame())
        result = self._tcp.getFrame()
        self.assertEqual(b'', result)
        self._tcp.advanceFrame()
        self._tcp.addToFrame(msg2)
        self.assertEqual(10, len(self._tcp._buffer))
        self.assertTrue(self._tcp.checkFrame())
        result = self._tcp.getFrame()
        self.assertEqual(msg2[7:], result)
        self._tcp.advanceFrame()

    def testTCPFramerPopulate(self):
        """ Test a tcp frame packet build """
        expected = ModbusRequest()
        expected.transaction_id = 0x0001
        expected.protocol_id    = 0x1234
        expected.unit_id        = 0xff
        msg = b"\x00\x01\x12\x34\x00\x04\xff\x02\x12\x34"
        self._tcp.addToFrame(msg)
        self.assertTrue(self._tcp.checkFrame())
        actual = ModbusRequest()
        self._tcp.populateResult(actual)
        for name in ['transaction_id', 'protocol_id', 'unit_id']:
            self.assertEqual(getattr(expected, name), getattr(actual, name))
        self._tcp.advanceFrame()

    def testTCPFramerPacket(self):
        """ Test a tcp frame packet build """
        old_encode = ModbusRequest.encode
        ModbusRequest.encode = lambda self: b''
        message = ModbusRequest()
        message.transaction_id = 0x0001
        message.protocol_id    = 0x1234
        message.unit_id        = 0xff
        message.function_code  = 0x01
        expected = b"\x00\x01\x12\x34\x00\x02\xff\x01"
        actual = self._tcp.buildPacket(message)
        self.assertEqual(expected, actual)
        ModbusRequest.encode = old_encode

    # ----------------------------------------------------------------------- #
    # RTU tests
    # ----------------------------------------------------------------------- #
    def testRTUFramerTransactionReady(self):
        """ Test if the checks for a complete frame work """
        self.assertFalse(self._rtu.isFrameReady())

        msg_parts = [b"\x00\x01\x00", b"\x00\x00\x01\xfc\x1b"]
        self._rtu.addToFrame(msg_parts[0])
        self.assertTrue(self._rtu.isFrameReady())
        self.assertFalse(self._rtu.checkFrame())

        self._rtu.addToFrame(msg_parts[1])
        self.assertTrue(self._rtu.isFrameReady())
        self.assertTrue(self._rtu.checkFrame())

    def testRTUFramerTransactionFull(self):
        """ Test a full rtu frame transaction """
        msg = b"\x00\x01\x00\x00\x00\x01\xfc\x1b"
        stripped_msg = msg[1:-2]
        self._rtu.addToFrame(msg)
        self.assertTrue(self._rtu.checkFrame())
        result = self._rtu.getFrame()
        self.assertEqual(stripped_msg, result)
        self._rtu.advanceFrame()

    def testRTUFramerTransactionHalf(self):
        """ Test a half completed rtu frame transaction """
        msg_parts = [b"\x00\x01\x00", b"\x00\x00\x01\xfc\x1b"]
        stripped_msg = b"".join(msg_parts)[1:-2]
        self._rtu.addToFrame(msg_parts[0])
        self.assertFalse(self._rtu.checkFrame())
        self._rtu.addToFrame(msg_parts[1])
        self.assertTrue(self._rtu.isFrameReady())
        self.assertTrue(self._rtu.checkFrame())
        result = self._rtu.getFrame()
        self.assertEqual(stripped_msg, result)
        self._rtu.advanceFrame()

    def testRTUFramerPopulate(self):
        """ Test a rtu frame packet build """
        request = ModbusRequest()
        msg = b"\x00\x01\x00\x00\x00\x01\xfc\x1b"
        self._rtu.addToFrame(msg)
        self._rtu.populateHeader()
        self._rtu.populateResult(request)

        header_dict = self._rtu._header
        self.assertEqual(len(msg), header_dict['len'])
        self.assertEqual(byte2int(msg[0]), header_dict['uid'])
        self.assertEqual(msg[-2:], header_dict['crc'])

        self.assertEqual(0x00, request.unit_id)

    def testRTUFramerPacket(self):
        """ Test a rtu frame packet build """
        old_encode = ModbusRequest.encode
        ModbusRequest.encode = lambda self: b''
        message = ModbusRequest()
        message.unit_id        = 0xff
        message.function_code  = 0x01
        expected = b"\xff\x01\x81\x80" # only header + CRC - no data
        actual = self._rtu.buildPacket(message)
        self.assertEqual(expected, actual)
        ModbusRequest.encode = old_encode

    def testRTUDecodeException(self):
        """ Test that the RTU framer can decode errors """
        message = b"\x00\x90\x02\x9c\x01"
        actual = self._rtu.addToFrame(message)
        result = self._rtu.checkFrame()
        self.assertTrue(result)

    def testProcess(self):
        class MockResult(object):
            def __init__(self, code):
                self.function_code = code

        def mock_callback(self):
            pass

        mock_result = MockResult(code=0)
        self._rtu.getRawFrame = self._rtu.getFrame = MagicMock()
        self._rtu.decoder = MagicMock()
        self._rtu.decoder.decode = MagicMock(return_value=mock_result)
        self._rtu.populateResult = MagicMock()
        self._rtu.advanceFrame = MagicMock()

        self._rtu._process(mock_callback)
        self._rtu.populateResult.assert_called_with(mock_result)
        self._rtu.advanceFrame.assert_called_with()
        self.assertTrue(self._rtu.advanceFrame.called)

        #Check errors
        self._rtu.decoder.decode = MagicMock(return_value=None)
        self.assertRaises(ModbusIOException, lambda: self._rtu._process(mock_callback))

    def testRTUProcessIncomingPAkcets(self):
        mock_data = b"\x00\x01\x00\x00\x00\x01\xfc\x1b"
        unit = 0x00
        def mock_callback(self):
            pass

        self._rtu.addToFrame = MagicMock()
        self._rtu._process = MagicMock()
        self._rtu.isFrameReady = MagicMock(return_value=False)
        self._rtu._buffer = mock_data

        self._rtu.processIncomingPacket(mock_data, mock_callback, unit)

    # ----------------------------------------------------------------------- #
    # ASCII tests
    # ----------------------------------------------------------------------- #
    def testASCIIFramerTransactionReady(self):
        """ Test a ascii frame transaction """
        msg = b':F7031389000A60\r\n'
        self.assertFalse(self._ascii.isFrameReady())
        self.assertFalse(self._ascii.checkFrame())
        self._ascii.addToFrame(msg)
        self.assertTrue(self._ascii.isFrameReady())
        self.assertTrue(self._ascii.checkFrame())
        self._ascii.advanceFrame()
        self.assertFalse(self._ascii.isFrameReady())
        self.assertFalse(self._ascii.checkFrame())
        self.assertEqual(b'', self._ascii.getFrame())

    def testASCIIFramerTransactionFull(self):
        """ Test a full ascii frame transaction """
        msg = b'sss:F7031389000A60\r\n'
        pack = a2b_hex(msg[6:-4])
        self._ascii.addToFrame(msg)
        self.assertTrue(self._ascii.checkFrame())
        result = self._ascii.getFrame()
        self.assertEqual(pack, result)
        self._ascii.advanceFrame()

    def testASCIIFramerTransactionHalf(self):
        """ Test a half completed ascii frame transaction """
        msg1 = b'sss:F7031389'
        msg2 = b'000A60\r\n'
        pack = a2b_hex(msg1[6:] + msg2[:-4])
        self._ascii.addToFrame(msg1)
        self.assertFalse(self._ascii.checkFrame())
        result = self._ascii.getFrame()
        self.assertEqual(b'', result)
        self._ascii.addToFrame(msg2)
        self.assertTrue(self._ascii.checkFrame())
        result = self._ascii.getFrame()
        self.assertEqual(pack, result)
        self._ascii.advanceFrame()

    def testASCIIFramerPopulate(self):
        """ Test a ascii frame packet build """
        request = ModbusRequest()
        self._ascii.populateResult(request)
        self.assertEqual(0x00, request.unit_id)

    def testASCIIFramerPacket(self):
        """ Test a ascii frame packet build """
        old_encode = ModbusRequest.encode
        ModbusRequest.encode = lambda self: b''
        message = ModbusRequest()
        message.unit_id        = 0xff
        message.function_code  = 0x01
        expected = b":FF0100\r\n"
        actual = self._ascii.buildPacket(message)
        self.assertEqual(expected, actual)
        ModbusRequest.encode = old_encode

    def testAsciiProcessIncomingPakcets(self):
        mock_data = msg = b':F7031389000A60\r\n'
        unit = 0x00
        def mock_callback(mock_data, *args, **kwargs):
            pass

        self._ascii.processIncomingPacket(mock_data, mock_callback, unit)

        # Test failure:
        self._ascii.checkFrame = MagicMock(return_value=False)
        self._ascii.processIncomingPacket(mock_data, mock_callback, unit)

    # ----------------------------------------------------------------------- #
    # Binary tests
    # ----------------------------------------------------------------------- #
    def testBinaryFramerTransactionReady(self):
        """ Test a binary frame transaction """
        msg  = b'\x7b\x01\x03\x00\x00\x00\x05\x85\xC9\x7d'
        self.assertFalse(self._binary.isFrameReady())
        self.assertFalse(self._binary.checkFrame())
        self._binary.addToFrame(msg)
        self.assertTrue(self._binary.isFrameReady())
        self.assertTrue(self._binary.checkFrame())
        self._binary.advanceFrame()
        self.assertFalse(self._binary.isFrameReady())
        self.assertFalse(self._binary.checkFrame())
        self.assertEqual(b'', self._binary.getFrame())

    def testBinaryFramerTransactionFull(self):
        """ Test a full binary frame transaction """
        msg  = b'\x7b\x01\x03\x00\x00\x00\x05\x85\xC9\x7d'
        pack = msg[2:-3]
        self._binary.addToFrame(msg)
        self.assertTrue(self._binary.checkFrame())
        result = self._binary.getFrame()
        self.assertEqual(pack, result)
        self._binary.advanceFrame()

    def testBinaryFramerTransactionHalf(self):
        """ Test a half completed binary frame transaction """
        msg1 = b'\x7b\x01\x03\x00'
        msg2 = b'\x00\x00\x05\x85\xC9\x7d'
        pack = msg1[2:] + msg2[:-3]
        self._binary.addToFrame(msg1)
        self.assertFalse(self._binary.checkFrame())
        result = self._binary.getFrame()
        self.assertEqual(b'', result)
        self._binary.addToFrame(msg2)
        self.assertTrue(self._binary.checkFrame())
        result = self._binary.getFrame()
        self.assertEqual(pack, result)
        self._binary.advanceFrame()

    def testBinaryFramerPopulate(self):
        """ Test a binary frame packet build """
        request = ModbusRequest()
        self._binary.populateResult(request)
        self.assertEqual(0x00, request.unit_id)

    def testBinaryFramerPacket(self):
        """ Test a binary frame packet build """
        old_encode = ModbusRequest.encode
        ModbusRequest.encode = lambda self: b''
        message = ModbusRequest()
        message.unit_id        = 0xff
        message.function_code  = 0x01
        expected = b'\x7b\xff\x01\x81\x80\x7d'
        actual = self._binary.buildPacket(message)
        self.assertEqual(expected, actual)
        ModbusRequest.encode = old_encode

    def testBinaryProcessIncomingPacket(self):
        mock_data = b'\x7b\x01\x03\x00\x00\x00\x05\x85\xC9\x7d'
        unit = 0x00
        def mock_callback(mock_data):
            pass

        self._binary.processIncomingPacket(mock_data, mock_callback, unit)

        # Test failure:
        self._binary.checkFrame = MagicMock(return_value=False)
        self._binary.processIncomingPacket(mock_data, mock_callback, unit)

# ----------------------------------------------------------------------- #
# Main
# ----------------------------------------------------------------------- #


if __name__ == "__main__":
    pytest.main()
pymodbus-2.1.0/test/test_utilities.py000066400000000000000000000057341335513467700200060ustar00rootroot00000000000000#!/usr/bin/env python
import unittest
import struct
from pymodbus.utilities import pack_bitstring, unpack_bitstring
from pymodbus.utilities import checkCRC, checkLRC
from pymodbus.utilities import dict_property, default

_test_master = {4 : 'd'}
class DictPropertyTester(object):
    def __init__(self):
        self.test   = {1 : 'a'}
        self._test  = {2 : 'b'}
        self.__test = {3 : 'c'}

    l1 = dict_property(lambda s: s.test, 1)
    l2 = dict_property(lambda s: s._test, 2)
    l3 = dict_property(lambda s: s.__test, 3)
    s1 = dict_property('test', 1)
    s2 = dict_property('_test', 2)
    g1 = dict_property(_test_master, 4)


class SimpleUtilityTest(unittest.TestCase):
    '''
    This is the unittest for the pymod.utilities module
    '''

    def setUp(self):
        ''' Initializes the test environment '''
        self.data = struct.pack('>HHHH', 0x1234, 0x2345, 0x3456, 0x4567)
        self.string = b"test the computation"
        self.bits = [True, False, True, False, True, False, True, False]

    def tearDown(self):
        ''' Cleans up the test environment '''
        del self.bits
        del self.string

    def testDictProperty(self):
        ''' Test all string <=> bit packing functions '''
        d = DictPropertyTester()
        self.assertEqual(d.l1, 'a')
        self.assertEqual(d.l2, 'b')
        self.assertEqual(d.l3, 'c')
        self.assertEqual(d.s1, 'a')
        self.assertEqual(d.s2, 'b')
        self.assertEqual(d.g1, 'd')

        for store in 'l1 l2 l3 s1 s2 g1'.split(' '):
            setattr(d, store, 'x')

        self.assertEqual(d.l1, 'x')
        self.assertEqual(d.l2, 'x')
        self.assertEqual(d.l3, 'x')
        self.assertEqual(d.s1, 'x')
        self.assertEqual(d.s2, 'x')
        self.assertEqual(d.g1, 'x')

    def testDefaultValue(self):
        ''' Test all string <=> bit packing functions '''
        self.assertEqual(default(1), 0)
        self.assertEqual(default(1.1), 0.0)
        self.assertEqual(default(1+1j), 0j)
        self.assertEqual(default('string'), '')
        self.assertEqual(default([1,2,3]), [])
        self.assertEqual(default({1:1}), {})
        self.assertEqual(default(True), False)

    def testBitPacking(self):
        ''' Test all string <=> bit packing functions '''
        self.assertEqual(unpack_bitstring(b'\x55'), self.bits)
        self.assertEqual(pack_bitstring(self.bits), b'\x55')

    def testLongitudinalRedundancyCheck(self):
        ''' Test the longitudinal redundancy check code '''
        self.assertTrue(checkLRC(self.data, 0x1c))
        self.assertTrue(checkLRC(self.string, 0x0c))

    def testCyclicRedundancyCheck(self):
        ''' Test the cyclic redundancy check code '''
        self.assertTrue(checkCRC(self.data, 0xe2db))
        self.assertTrue(checkCRC(self.string, 0x889e))

#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
    unittest.main()
pymodbus-2.1.0/test/test_version.py000066400000000000000000000014641335513467700174540ustar00rootroot00000000000000#!/usr/bin/env python
import unittest
from pymodbus.version import Version

class ModbusVersionTest(unittest.TestCase):
    '''
    This is the unittest for the pymodbus._version code
    '''

    def setUp(self):
        ''' Initializes the test environment '''
        pass

    def tearDown(self):
        ''' Cleans up the test environment '''
        pass

    def testVersionClass(self):
        version = Version('test', 1,2,3, "sometag")
        short = version.short()
        self.assertEqual(version.short(), '1.2.3.sometag')
        self.assertEqual(str(version), '[test, version 1.2.3.sometag]')

#---------------------------------------------------------------------------#
# Main
#---------------------------------------------------------------------------#
if __name__ == "__main__":
    unittest.main()
pymodbus-2.1.0/tox.ini000066400000000000000000000006561335513467700147140ustar00rootroot00000000000000# Tox (http://tox.testrun.org/) is a tool for running tests in multiple
# virtualenvs. This configuration file will run the test suite on all supported
# python versions. To use it, "pip install tox" and then run "tox" from this
# directory.

[tox]
envlist = py27, py34, py35, py36, pypy

[testenv]
deps = -requirements-tests.txt
commands = py.test {posargs}

[flake8]
exclude = .tox
ignore = D211,D400,E731
max-line-length = 120