diff -Nru pymodbus-1.5.2+dfsg/CHANGELOG.rst pymodbus-2.1.0+dfsg/CHANGELOG.rst --- pymodbus-1.5.2+dfsg/CHANGELOG.rst 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/CHANGELOG.rst 2018-10-03 12:44:47.000000000 +0000 @@ -1,3 +1,37 @@ +Version 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 @@ -11,13 +45,14 @@ * 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 diff -Nru pymodbus-1.5.2+dfsg/.coveragerc pymodbus-2.1.0+dfsg/.coveragerc --- pymodbus-1.5.2+dfsg/.coveragerc 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/.coveragerc 2018-10-03 12:44:47.000000000 +0000 @@ -1,2 +1,3 @@ -[report] -show_missing = True \ No newline at end of file +[run] +omit = + pymodbus/repl/* \ No newline at end of file diff -Nru pymodbus-1.5.2+dfsg/debian/changelog pymodbus-2.1.0+dfsg/debian/changelog --- pymodbus-1.5.2+dfsg/debian/changelog 2018-05-23 23:34:05.000000000 +0000 +++ pymodbus-2.1.0+dfsg/debian/changelog 2018-11-26 12:37:21.000000000 +0000 @@ -1,3 +1,16 @@ +pymodbus (2.1.0+dfsg-1) unstable; urgency=medium + + [ Ondřej Nový ] + * Use 'python3 -m sphinx' instead of sphinx-build for building docs + + [ W. Martin Borgert ] + * New upstream version + * Add patch for Python 3.7 compatibility + - disable tests temporarily, because they hang after running + successfully + + -- W. Martin Borgert Mon, 26 Nov 2018 12:37:21 +0000 + pymodbus (1.5.2+dfsg-2) unstable; urgency=medium [ Ondřej Nový ] diff -Nru pymodbus-1.5.2+dfsg/debian/control pymodbus-2.1.0+dfsg/debian/control --- pymodbus-1.5.2+dfsg/debian/control 2018-05-23 23:11:36.000000000 +0000 +++ pymodbus-2.1.0+dfsg/debian/control 2018-11-26 12:35:26.000000000 +0000 @@ -13,21 +13,27 @@ python-redis, python-serial, python-setuptools, + python-six, python-sphinx-rtd-theme, python-sqlalchemy, + python-tornado, python-twisted, python3, python3-humanfriendly, python3-mock, + python3-recommonmark, python3-redis, python3-pytest, python3-pytest-cov, python3-serial, + python3-serial-asyncio, python3-setuptools, + python3-six, python3-sphinx, python3-sphinx-rtd-theme, python3-sqlalchemy, - python3-twisted + python3-tornado, + python3-twisted, Standards-Version: 4.1.3 Rules-Requires-Root: no Vcs-Git: https://salsa.debian.org/python-team/modules/pymodbus.git @@ -64,7 +70,8 @@ Package: python3-pymodbus Architecture: all Depends: ${python3:Depends}, ${misc:Depends} -Recommends: python3-twisted +Recommends: python3-serial-asyncio, + python3-twisted, Provides: ${python3:Provides} Description: full Modbus protocol implementation for Python 3 Pymodbus is a full Modbus protocol implementation. diff -Nru pymodbus-1.5.2+dfsg/debian/patches/disable-failing-unittests.patch pymodbus-2.1.0+dfsg/debian/patches/disable-failing-unittests.patch --- pymodbus-1.5.2+dfsg/debian/patches/disable-failing-unittests.patch 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/debian/patches/disable-failing-unittests.patch 2018-11-25 01:45:07.000000000 +0000 @@ -0,0 +1,32 @@ +Description: disable broken tornado unittest +Author: W. Martin Borgert +Origin: vendor +Last-Update: 2018-11-25 +--- +This patch header follows DEP-3: http://dep.debian.net/deps/dep3/ +--- a/test/test_client_async_tornado.py ++++ b/test/test_client_async_tornado.py +@@ -57,6 +57,7 @@ + self.assertTrue(client.io_loop == schedulers.IO_LOOP) + self.assertTrue(framer is client.framer) + ++ @unittest.skip("temporarily disabled") + @patch("pymodbus.client.asynchronous.tornado.IOLoop") + @patch("pymodbus.client.asynchronous.tornado.IOStream") + def testBaseClientOn_receive(self, mock_iostream, mock_ioloop): +@@ -93,6 +94,7 @@ + tid = request.transaction_id + self.assertEqual(d, client.transaction.getTransaction(tid)) + ++ @unittest.skip("temporarily disabled") + @patch("pymodbus.client.asynchronous.tornado.IOLoop") + @patch("pymodbus.client.asynchronous.tornado.IOStream") + def testBaseClientHandleResponse(self, mock_iostream, mock_ioloop): +@@ -238,6 +240,7 @@ + tid = request.transaction_id + self.assertEqual(d, client.transaction.getTransaction(tid)) + ++ @unittest.skip("temporarily disabled") + @patch("pymodbus.client.asynchronous.tornado.IOLoop") + @patch("pymodbus.client.asynchronous.tornado.SerialIOStream") + @patch("pymodbus.client.asynchronous.tornado.Serial") diff -Nru pymodbus-1.5.2+dfsg/debian/patches/privacy-breach-fixes.patch pymodbus-2.1.0+dfsg/debian/patches/privacy-breach-fixes.patch --- pymodbus-1.5.2+dfsg/debian/patches/privacy-breach-fixes.patch 2018-05-23 20:34:45.000000000 +0000 +++ pymodbus-2.1.0+dfsg/debian/patches/privacy-breach-fixes.patch 2018-11-26 10:26:29.000000000 +0000 @@ -6,19 +6,47 @@ This patch header follows DEP-3: http://dep.debian.net/deps/dep3/ --- a/README.rst +++ b/README.rst -@@ -1,12 +1,8 @@ +@@ -2,16 +2,13 @@ + PyModbus - A Python Modbus Stack + ================================ + -.. image:: https://travis-ci.org/riptideio/pymodbus.svg?branch=master - :target: https://travis-ci.org/riptideio/pymodbus -+`Travis `_ - -.. image:: https://badges.gitter.im/Join%20Chat.svg -- :target: https://gitter.im/pymodbus_dev/Lobby -+`Gitter `_ - --.. image:: https://readthedocs.org/projects/pymodbus-n/badge/?version=latest -- :target: http://pymodbus.readthedocs.io/en/latest/?badge=latest +- :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 ++`Travis `_ ++ ++`Gitter `_ ++ +`Documentation Status `_ ++ ++`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.** +@@ -110,8 +107,7 @@ + + For more info on REPL refer `Pymodbus REPL `_ + +-.. image:: https://asciinema.org/a/y1xOk7lm59U1bRBE2N1pDIj2o.png +- :target: https://asciinema.org/a/y1xOk7lm59U1bRBE2N1pDIj2o ++`asciinema `_ + + ------------------------------------------------------------ + Installing +--- a/doc/source/library/REPL.md ++++ b/doc/source/library/REPL.md +@@ -276,5 +276,5 @@ + + ## DEMO - ============================================================ - Summary +-[![asciicast](https://asciinema.org/a/y1xOk7lm59U1bRBE2N1pDIj2o.png)](https://asciinema.org/a/y1xOk7lm59U1bRBE2N1pDIj2o) +-[![asciicast](https://asciinema.org/a/edUqZN77fdjxL2toisiilJNwI.png)](https://asciinema.org/a/edUqZN77fdjxL2toisiilJNwI) ++`asciicast `_ ++`asciicast `_ diff -Nru pymodbus-1.5.2+dfsg/debian/patches/python-3.7.patch pymodbus-2.1.0+dfsg/debian/patches/python-3.7.patch --- pymodbus-1.5.2+dfsg/debian/patches/python-3.7.patch 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/debian/patches/python-3.7.patch 2018-11-25 01:30:58.000000000 +0000 @@ -0,0 +1,1910 @@ +Description: make code compatiable with Python 3.7 +Origin: upstream +Last-Update: 2018-11-25 +--- +This patch header follows DEP-3: http://dep.debian.net/deps/dep3/ +diff --git a/doc/source/library/pymodbus.client.async.asyncio.rst b/doc/source/library/pymodbus.client.async.asyncio.rst +deleted file mode 100644 +index 0d5ea5f..0000000 +--- a/doc/source/library/pymodbus.client.async.asyncio.rst ++++ /dev/null +@@ -1,8 +0,0 @@ +-pymodbus\.client\.async\.asyncio package +-======================================== +- +-.. automodule:: pymodbus.client.async.asyncio +- :members: +- :undoc-members: +- :show-inheritance: +- +diff --git a/doc/source/library/pymodbus.client.async.factory.rst b/doc/source/library/pymodbus.client.async.factory.rst +deleted file mode 100644 +index 3eeecc2..0000000 +--- a/doc/source/library/pymodbus.client.async.factory.rst ++++ /dev/null +@@ -1,36 +0,0 @@ +-pymodbus\.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: +- +- +diff --git a/doc/source/library/pymodbus.client.async.rst b/doc/source/library/pymodbus.client.async.rst +deleted file mode 100644 +index 04ce812..0000000 +--- a/doc/source/library/pymodbus.client.async.rst ++++ /dev/null +@@ -1,55 +0,0 @@ +-pymodbus\.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: +- +- +diff --git a/doc/source/library/pymodbus.client.async.schedulers.rst b/doc/source/library/pymodbus.client.async.schedulers.rst +deleted file mode 100644 +index 113aeb1..0000000 +--- a/doc/source/library/pymodbus.client.async.schedulers.rst ++++ /dev/null +@@ -1,8 +0,0 @@ +-pymodbus\.client\.async\.schedulers package +-=========================================== +- +-.. automodule:: pymodbus.client.async.schedulers +- :members: +- :undoc-members: +- :show-inheritance: +- +diff --git a/doc/source/library/pymodbus.client.async.tornado.rst b/doc/source/library/pymodbus.client.async.tornado.rst +deleted file mode 100644 +index 2c89539..0000000 +--- a/doc/source/library/pymodbus.client.async.tornado.rst ++++ /dev/null +@@ -1,8 +0,0 @@ +-pymodbus\.client\.async\.tornado package +-======================================== +- +-.. automodule:: pymodbus.client.async.tornado +- :members: +- :undoc-members: +- :show-inheritance: +- +diff --git a/doc/source/library/pymodbus.client.async.twisted.rst b/doc/source/library/pymodbus.client.async.twisted.rst +deleted file mode 100644 +index 8bfe9d7..0000000 +--- a/doc/source/library/pymodbus.client.async.twisted.rst ++++ /dev/null +@@ -1,8 +0,0 @@ +-pymodbus\.client\.async\.twisted package +-======================================== +- +-.. automodule:: pymodbus.client.async.twisted +- :members: +- :undoc-members: +- :show-inheritance: +- +diff --git a/doc/source/library/pymodbus.client.asynchronous.asyncio.rst b/doc/source/library/pymodbus.client.asynchronous.asyncio.rst +new file mode 100644 +index 0000000..d19a1a2 +--- /dev/null ++++ b/doc/source/library/pymodbus.client.asynchronous.asyncio.rst +@@ -0,0 +1,8 @@ ++pymodbus\.client\.asynchronous\.asyncio package ++=============================================== ++ ++.. automodule:: pymodbus.client.asynchronous.asyncio ++ :members: ++ :undoc-members: ++ :show-inheritance: ++ +diff --git a/doc/source/library/pymodbus.client.asynchronous.factory.rst b/doc/source/library/pymodbus.client.asynchronous.factory.rst +new file mode 100644 +index 0000000..74c933e +--- /dev/null ++++ b/doc/source/library/pymodbus.client.asynchronous.factory.rst +@@ -0,0 +1,36 @@ ++pymodbus\.client\.asynchronous\.factory package ++=============================================== ++ ++.. automodule:: pymodbus.client.asynchronous.factory ++ :members: ++ :undoc-members: ++ :show-inheritance: ++ ++Submodules ++---------- ++ ++pymodbus\.client\.asynchronous\.factory\.serial module ++------------------------------------------------------ ++ ++.. automodule:: pymodbus.client.asynchronous.factory.serial ++ :members: ++ :undoc-members: ++ :show-inheritance: ++ ++pymodbus\.client\.asynchronous\.factory\.tcp module ++--------------------------------------------------- ++ ++.. automodule:: pymodbus.client.asynchronous.factory.tcp ++ :members: ++ :undoc-members: ++ :show-inheritance: ++ ++pymodbus\.client\.asynchronous\.factory\.udp module ++--------------------------------------------------- ++ ++.. automodule:: pymodbus.client.asynchronous.factory.udp ++ :members: ++ :undoc-members: ++ :show-inheritance: ++ ++ +diff --git a/doc/source/library/pymodbus.client.asynchronous.rst b/doc/source/library/pymodbus.client.asynchronous.rst +new file mode 100644 +index 0000000..c72291a +--- /dev/null ++++ b/doc/source/library/pymodbus.client.asynchronous.rst +@@ -0,0 +1,55 @@ ++pymodbus\.client\.asynchronous package ++====================================== ++ ++.. automodule:: pymodbus.client.asynchronous ++ :members: ++ :undoc-members: ++ :show-inheritance: ++ ++Subpackages ++----------- ++ ++.. toctree:: ++ ++ pymodbus.client.asynchronous.asyncio ++ pymodbus.client.asynchronous.factory ++ pymodbus.client.asynchronous.schedulers ++ pymodbus.client.asynchronous.tornado ++ pymodbus.client.asynchronous.twisted ++ ++Submodules ++---------- ++ ++pymodbus\.client\.asynchronous\.serial module ++--------------------------------------------- ++ ++.. automodule:: pymodbus.client.asynchronous.serial ++ :members: ++ :undoc-members: ++ :show-inheritance: ++ ++pymodbus\.client\.asynchronous\.tcp module ++------------------------------------------ ++ ++.. automodule:: pymodbus.client.asynchronous.tcp ++ :members: ++ :undoc-members: ++ :show-inheritance: ++ ++pymodbus\.client\.asynchronous\.thread module ++--------------------------------------------- ++ ++.. automodule:: pymodbus.client.asynchronous.thread ++ :members: ++ :undoc-members: ++ :show-inheritance: ++ ++pymodbus\.client\.asynchronous\.udp module ++------------------------------------------ ++ ++.. automodule:: pymodbus.client.asynchronous.udp ++ :members: ++ :undoc-members: ++ :show-inheritance: ++ ++ +diff --git a/doc/source/library/pymodbus.client.asynchronous.schedulers.rst b/doc/source/library/pymodbus.client.asynchronous.schedulers.rst +new file mode 100644 +index 0000000..f6956ad +--- /dev/null ++++ b/doc/source/library/pymodbus.client.asynchronous.schedulers.rst +@@ -0,0 +1,8 @@ ++pymodbus\.client\.asynchronous\.schedulers package ++================================================== ++ ++.. automodule:: pymodbus.client.asynchronous.schedulers ++ :members: ++ :undoc-members: ++ :show-inheritance: ++ +diff --git a/doc/source/library/pymodbus.client.asynchronous.tornado.rst b/doc/source/library/pymodbus.client.asynchronous.tornado.rst +new file mode 100644 +index 0000000..c326219 +--- /dev/null ++++ b/doc/source/library/pymodbus.client.asynchronous.tornado.rst +@@ -0,0 +1,8 @@ ++pymodbus\.client\.asynchronous\.tornado package ++=============================================== ++ ++.. automodule:: pymodbus.client.asynchronous.tornado ++ :members: ++ :undoc-members: ++ :show-inheritance: ++ +diff --git a/doc/source/library/pymodbus.client.asynchronous.twisted.rst b/doc/source/library/pymodbus.client.asynchronous.twisted.rst +new file mode 100644 +index 0000000..537dd68 +--- /dev/null ++++ b/doc/source/library/pymodbus.client.asynchronous.twisted.rst +@@ -0,0 +1,8 @@ ++pymodbus\.client\.asynchronous\.twisted package ++=============================================== ++ ++.. automodule:: pymodbus.client.asynchronous.twisted ++ :members: ++ :undoc-members: ++ :show-inheritance: ++ +diff --git a/doc/source/library/pymodbus.client.rst b/doc/source/library/pymodbus.client.rst +index 774f58a..cf8e1df 100644 +--- a/doc/source/library/pymodbus.client.rst ++++ b/doc/source/library/pymodbus.client.rst +@@ -11,7 +11,7 @@ Subpackages + + .. toctree:: + +- pymodbus.client.async ++ pymodbus.client.asynchronous + + Submodules + ---------- +diff --git a/doc/source/library/pymodbus.server.rst b/doc/source/library/pymodbus.server.rst +index 2975409..67f4925 100644 +--- a/doc/source/library/pymodbus.server.rst ++++ b/doc/source/library/pymodbus.server.rst +@@ -9,10 +9,10 @@ pymodbus\.server package + Submodules + ---------- + +-pymodbus\.server\.async module +------------------------------- ++pymodbus\.server\.asynchronous module ++------------------------------------- + +-.. automodule:: pymodbus.server.async ++.. automodule:: pymodbus.server.asynchronous + :members: + :undoc-members: + :show-inheritance: +diff --git a/examples/common/async_asyncio_client.py b/examples/common/async_asyncio_client.py +index d4dbb97..5aefd6b 100644 +--- a/examples/common/async_asyncio_client.py ++++ b/examples/common/async_asyncio_client.py +@@ -13,12 +13,12 @@ if IS_PYTHON3 and PYTHON_VERSION >= (3, 4): + import asyncio + import logging + # ----------------------------------------------------------------------- # +- # Import the required async client ++ # Import the required asynchronous client + # ----------------------------------------------------------------------- # +- from pymodbus.client.async.tcp import AsyncModbusTCPClient as ModbusClient +- # from pymodbus.client.async.udp import ( ++ from pymodbus.client.asynchronous.tcp import AsyncModbusTCPClient as ModbusClient ++ # from pymodbus.client.asynchronous.udp import ( + # AsyncModbusUDPClient as ModbusClient) +- from pymodbus.client.async import schedulers ++ from pymodbus.client.asynchronous import schedulers + + else: + import sys +@@ -68,7 +68,7 @@ async def start_async_test(client): + # 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). ++ # _only_ pass with the supplied asynchronous modbus server (script supplied). + # ----------------------------------------------------------------------- # + log.debug("Write to a Coil and read back") + rq = await client.write_coil(0, True, unit=UNIT) +diff --git a/examples/common/async_asyncio_serial_client.py b/examples/common/async_asyncio_serial_client.py +index 1ac6c4c..95d7a44 100755 +--- a/examples/common/async_asyncio_serial_client.py ++++ b/examples/common/async_asyncio_serial_client.py +@@ -12,9 +12,9 @@ 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 ( ++ from pymodbus.client.asynchronous.serial import ( + AsyncModbusSerialClient as ModbusClient) +- from pymodbus.client.async import schedulers ++ from pymodbus.client.asynchronous import schedulers + else: + import sys + sys.stderr("This example needs to be run only on python 3.4 and above") +@@ -63,7 +63,7 @@ async def start_async_test(client): + # 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). ++ # _only_ pass with the supplied asynchronous modbus server (script supplied). + # ----------------------------------------------------------------------- # + log.debug("Write to a Coil and read back") + rq = await client.write_coil(0, True, unit=UNIT) +diff --git a/examples/common/async_tornado_client.py b/examples/common/async_tornado_client.py +index 4c3d7ef..c35ef7c 100755 +--- a/examples/common/async_tornado_client.py ++++ b/examples/common/async_tornado_client.py +@@ -9,14 +9,14 @@ client implementation from pymodbus using Tornado. + + import functools + from tornado.ioloop import IOLoop +-from pymodbus.client.async import schedulers ++from pymodbus.client.asynchronous 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 ++# from pymodbus.client.asynchronous.udp import AsyncModbusUDPClient as ModbusClient ++from pymodbus.client.asynchronous.tcp import AsyncModbusTCPClient as ModbusClient + + # ---------------------------------------------------------------------------# + # configure the client logging +diff --git a/examples/common/async_tornado_client_serial.py b/examples/common/async_tornado_client_serial.py +index a3f4cb1..74ee932 100755 +--- a/examples/common/async_tornado_client_serial.py ++++ b/examples/common/async_tornado_client_serial.py +@@ -12,13 +12,13 @@ client implementation from pymodbus using tornado. + import functools + + from tornado.ioloop import IOLoop +-from pymodbus.client.async import schedulers ++from pymodbus.client.asynchronous import schedulers + + # ---------------------------------------------------------------------------# + # choose the requested modbus protocol + # ---------------------------------------------------------------------------# + +-from pymodbus.client.async.serial import AsyncModbusSerialClient ++from pymodbus.client.asynchronous.serial import AsyncModbusSerialClient + + # ---------------------------------------------------------------------------# + # configure the client logging +diff --git a/examples/common/async_twisted_client.py b/examples/common/async_twisted_client.py +index 4916f16..d522622 100755 +--- a/examples/common/async_twisted_client.py ++++ b/examples/common/async_twisted_client.py +@@ -12,9 +12,9 @@ client implementation from pymodbus. + + 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 ++from pymodbus.client.asynchronous.tcp import AsyncModbusTCPClient ++# from pymodbus.client.asynchronous.udp import AsyncModbusUDPClient ++from pymodbus.client.asynchronous import schedulers + + # --------------------------------------------------------------------------- # + # choose the requested modbus protocol +diff --git a/examples/common/async_twisted_client_serial.py b/examples/common/async_twisted_client_serial.py +index 056a0e6..f7230cb 100755 +--- a/examples/common/async_twisted_client_serial.py ++++ b/examples/common/async_twisted_client_serial.py +@@ -8,9 +8,9 @@ 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 ++from pymodbus.client.asynchronous import schedulers ++from pymodbus.client.asynchronous.serial import AsyncModbusSerialClient ++from pymodbus.client.asynchronous.twisted import ModbusClientProtocol + + import logging + logging.basicConfig() +diff --git a/examples/common/asynchronous_processor.py b/examples/common/asynchronous_processor.py +index e193d12..bc74bfe 100755 +--- a/examples/common/asynchronous_processor.py ++++ b/examples/common/asynchronous_processor.py +@@ -12,7 +12,7 @@ free to use it as a skeleton guide in implementing your own. + from twisted.internet import serialport, reactor + from twisted.internet.protocol import ClientFactory + from pymodbus.factory import ClientDecoder +-from pymodbus.client.async.twisted import ModbusClientProtocol ++from pymodbus.client.asynchronous.twisted import ModbusClientProtocol + + # --------------------------------------------------------------------------- # + # Choose the framer you want to use +diff --git a/examples/common/asynchronous_server.py b/examples/common/asynchronous_server.py +index 2186698..f961c62 100755 +--- a/examples/common/asynchronous_server.py ++++ b/examples/common/asynchronous_server.py +@@ -10,9 +10,9 @@ 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.server.asynchronous import StartTcpServer ++from pymodbus.server.asynchronous import StartUdpServer ++from pymodbus.server.asynchronous import StartSerialServer + + from pymodbus.device import ModbusDeviceIdentification + from pymodbus.datastore import ModbusSequentialDataBlock +diff --git a/examples/common/callback_server.py b/examples/common/callback_server.py +index 045ead0..b04110e 100755 +--- a/examples/common/callback_server.py ++++ b/examples/common/callback_server.py +@@ -10,7 +10,7 @@ a device-mapping file. + # --------------------------------------------------------------------------- # + # import the modbus libraries we need + # --------------------------------------------------------------------------- # +-from pymodbus.server.async import StartTcpServer ++from pymodbus.server.asynchronous import StartTcpServer + from pymodbus.device import ModbusDeviceIdentification + from pymodbus.datastore import ModbusSparseDataBlock + from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext +diff --git a/examples/common/custom_datablock.py b/examples/common/custom_datablock.py +index a9873bc..db0d715 100755 +--- a/examples/common/custom_datablock.py ++++ b/examples/common/custom_datablock.py +@@ -10,7 +10,7 @@ written to the datastore. + # import the modbus libraries we need + # --------------------------------------------------------------------------- # + from __future__ import print_function +-from pymodbus.server.async import StartTcpServer ++from pymodbus.server.asynchronous import StartTcpServer + from pymodbus.device import ModbusDeviceIdentification + from pymodbus.datastore import ModbusSparseDataBlock + from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext +diff --git a/examples/common/dbstore_update_server.py b/examples/common/dbstore_update_server.py +index 7119a31..5de74c3 100644 +--- a/examples/common/dbstore_update_server.py ++++ b/examples/common/dbstore_update_server.py +@@ -16,7 +16,7 @@ This can also be done with a python thread:: + # --------------------------------------------------------------------------- # + # import the modbus libraries we need + # --------------------------------------------------------------------------- # +-from pymodbus.server.async import StartTcpServer ++from pymodbus.server.asynchronous import StartTcpServer + from pymodbus.device import ModbusDeviceIdentification + from pymodbus.datastore import ModbusSequentialDataBlock + from pymodbus.datastore import ModbusServerContext +diff --git a/examples/common/synchronous_client.py b/examples/common/synchronous_client.py +index b21f4f7..f6ad09c 100755 +--- a/examples/common/synchronous_client.py ++++ b/examples/common/synchronous_client.py +@@ -92,7 +92,7 @@ def run_sync_client(): + # 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). ++ # _only_ pass with the supplied asynchronous modbus server (script supplied). + # ----------------------------------------------------------------------- # + log.debug("Write to a Coil and read back") + rq = client.write_coil(0, True, unit=UNIT) +diff --git a/examples/common/updating_server.py b/examples/common/updating_server.py +index 7b7b6be..99fc33b 100755 +--- a/examples/common/updating_server.py ++++ b/examples/common/updating_server.py +@@ -15,7 +15,7 @@ a python thread:: + # --------------------------------------------------------------------------- # + # import the modbus libraries we need + # --------------------------------------------------------------------------- # +-from pymodbus.server.async import StartTcpServer ++from pymodbus.server.asynchronous import StartTcpServer + from pymodbus.device import ModbusDeviceIdentification + from pymodbus.datastore import ModbusSequentialDataBlock + from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext +diff --git a/examples/contrib/asynchronous_asyncio_serial_client.py b/examples/contrib/asynchronous_asyncio_serial_client.py +index e4b02f5..ec48cf3 100755 +--- a/examples/contrib/asynchronous_asyncio_serial_client.py ++++ b/examples/contrib/asynchronous_asyncio_serial_client.py +@@ -2,7 +2,7 @@ from 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.client.asynchronous.asyncio import ModbusClientProtocol + from pymodbus.transaction import ModbusAsciiFramer, ModbusRtuFramer + from pymodbus.factory import ClientDecoder + else: +@@ -43,7 +43,7 @@ async def start_async_test(client): + # 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). ++ # _only_ pass with the supplied asynchronous modbus server (script supplied). + # ----------------------------------------------------------------------- # + log.debug("Write to a Coil and read back") + rq = await client.write_coil(0, True, unit=UNIT) +diff --git a/examples/contrib/modbus_scraper.py b/examples/contrib/modbus_scraper.py +index ac65622..ea12368 100755 +--- a/examples/contrib/modbus_scraper.py ++++ b/examples/contrib/modbus_scraper.py +@@ -11,7 +11,7 @@ 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 ++from pymodbus.client.asynchronous.twisted import ModbusClientProtocol + + # -------------------------------------------------------------------------- # + # Configure the client logging +diff --git a/examples/contrib/modbus_simulator.py b/examples/contrib/modbus_simulator.py +index 6f9e425..d0dc503 100644 +--- a/examples/contrib/modbus_simulator.py ++++ b/examples/contrib/modbus_simulator.py +@@ -8,7 +8,7 @@ import pickle + from optparse import OptionParser + from twisted.internet import reactor + +-from pymodbus.server.async import StartTcpServer ++from pymodbus.server.asynchronous import StartTcpServer + from pymodbus.datastore import ModbusServerContext,ModbusSlaveContext + + # -------------------------------------------------------------------------- # +diff --git a/examples/functional/asynchronous_ascii_client.py b/examples/functional/asynchronous_ascii_client.py +index 2fb0574..582690a 100644 +--- a/examples/functional/asynchronous_ascii_client.py ++++ b/examples/functional/asynchronous_ascii_client.py +@@ -1,6 +1,6 @@ + #!/usr/bin/env python + import unittest +-from pymodbus.client.async import ModbusSerialClient as ModbusClient ++from pymodbus.client.asynchronous import ModbusSerialClient as ModbusClient + from base_runner import Runner + + class AsynchronousAsciiClient(Runner, unittest.TestCase): +diff --git a/examples/functional/asynchronous_rtu_client.py b/examples/functional/asynchronous_rtu_client.py +index 5df8254..a92905e 100644 +--- a/examples/functional/asynchronous_rtu_client.py ++++ b/examples/functional/asynchronous_rtu_client.py +@@ -1,6 +1,6 @@ + #!/usr/bin/env python + import unittest +-from pymodbus.client.async import ModbusSerialClient as ModbusClient ++from pymodbus.client.asynchronous import ModbusSerialClient as ModbusClient + from base_runner import Runner + + class AsynchronousRtuClient(Runner, unittest.TestCase): +diff --git a/examples/functional/asynchronous_tcp_client.py b/examples/functional/asynchronous_tcp_client.py +index 3cfffe0..1b9e336 100644 +--- a/examples/functional/asynchronous_tcp_client.py ++++ b/examples/functional/asynchronous_tcp_client.py +@@ -2,7 +2,7 @@ + import unittest + from twisted.internet import reactor, protocol + from pymodbus.constants import Defaults +-from pymodbus.client.async import ModbusClientProtocol ++from pymodbus.client.asynchronous import ModbusClientProtocol + from base_runner import Runner + + class AsynchronousTcpClient(Runner, unittest.TestCase): +diff --git a/examples/gui/bottle/frontend.py b/examples/gui/bottle/frontend.py +index 56ef04e..a09fb0d 100644 +--- a/examples/gui/bottle/frontend.py ++++ b/examples/gui/bottle/frontend.py +@@ -260,7 +260,7 @@ if __name__ == '__main__': + # ------------------------------------------------------------ + # an example server configuration + # ------------------------------------------------------------ +- from pymodbus.server.async import ModbusServerFactory ++ from pymodbus.server.asynchronous import ModbusServerFactory + from pymodbus.constants import Defaults + from pymodbus.device import ModbusDeviceIdentification + from pymodbus.datastore import ModbusSequentialDataBlock +diff --git a/examples/gui/gtk/simulator.py b/examples/gui/gtk/simulator.py +index 56cd948..d73c89e 100755 +--- a/examples/gui/gtk/simulator.py ++++ b/examples/gui/gtk/simulator.py +@@ -20,7 +20,7 @@ from gtk import glade + # --------------------------------------------------------------------------- # + from twisted.internet import reactor + from twisted.internet import error as twisted_error +-from pymodbus.server.async import ModbusServerFactory ++from pymodbus.server.asynchronous import ModbusServerFactory + from pymodbus.datastore import ModbusServerContext,ModbusSlaveContext + + #--------------------------------------------------------------------------# +diff --git a/examples/gui/gui_common.py b/examples/gui/gui_common.py +index 8ac4088..b180115 100755 +--- a/examples/gui/gui_common.py ++++ b/examples/gui/gui_common.py +@@ -12,7 +12,7 @@ from threading import Thread + # -------------------------------------------------------------------------- # + from twisted.internet import reactor + from twisted.internet import error as twisted_error +-from pymodbus.server.async import ModbusServerFactory ++from pymodbus.server.asynchronous import ModbusServerFactory + from pymodbus.datastore import ModbusServerContext,ModbusSlaveContext + + # -------------------------------------------------------------------------- # +diff --git a/examples/gui/tk/simulator.py b/examples/gui/tk/simulator.py +index 316d10a..9598f0d 100755 +--- a/examples/gui/tk/simulator.py ++++ b/examples/gui/tk/simulator.py +@@ -24,7 +24,7 @@ tksupport.install(root) + # --------------------------------------------------------------------------- # + from twisted.internet import reactor + from twisted.internet import error as twisted_error +-from pymodbus.server.async import ModbusServerFactory ++from pymodbus.server.asynchronous import ModbusServerFactory + from pymodbus.datastore import ModbusServerContext,ModbusSlaveContext + + #--------------------------------------------------------------------------# +diff --git a/examples/gui/wx/simulator.py b/examples/gui/wx/simulator.py +index 519daa1..787de55 100755 +--- a/examples/gui/wx/simulator.py ++++ b/examples/gui/wx/simulator.py +@@ -22,7 +22,7 @@ wxreactor.install() + # --------------------------------------------------------------------------- # + from twisted.internet import reactor + from twisted.internet import error as twisted_error +-from pymodbus.server.async import ModbusServerFactory ++from pymodbus.server.asynchronous import ModbusServerFactory + from pymodbus.datastore import ModbusServerContext,ModbusSlaveContext + + #--------------------------------------------------------------------------# +diff --git a/examples/twisted/plugins/pymodbus_plugin.py b/examples/twisted/plugins/pymodbus_plugin.py +index 9625b6b..f6d4fcd 100644 +--- a/examples/twisted/plugins/pymodbus_plugin.py ++++ b/examples/twisted/plugins/pymodbus_plugin.py +@@ -8,7 +8,7 @@ from twisted.application.service import IServiceMaker + from twisted.application import internet + + from pymodbus.constants import Defaults +-from pymodbus.server.async import ModbusServerFactory ++from pymodbus.server.asynchronous import ModbusServerFactory + from pymodbus.transaction import ModbusSocketFramer + from pymodbus.internal.ptwisted import InstallManagementConsole + +diff --git a/pymodbus/client/async/__init__.py b/pymodbus/client/asynchronous/__init__.py +similarity index 68% +rename from pymodbus/client/async/__init__.py +rename to pymodbus/client/asynchronous/__init__.py +index 6839846..c339353 100644 +--- a/pymodbus/client/async/__init__.py ++++ b/pymodbus/client/asynchronous/__init__.py +@@ -4,21 +4,21 @@ Async Modbus Client implementation based on Twisted, tornado and asyncio + + Example run:: + +- from pymodbus.client.async import schedulers ++ from pymodbus.client.asynchronous 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 ++ from pymodbus.client.asynchronous.tcp import AsyncModbusTCPClient as Client ++ from pymodbus.client.asynchronous.serial import AsyncModbusSerialClient as Client ++ from pymodbus.client.asynchronous.udp import AsyncModbusUDPClient as Client + +- # For tornado based async client use ++ # For tornado based asynchronous client use + event_loop, future = Client(schedulers.IO_LOOP, port=5020) + +- # For twisted based async client use ++ # For twisted based asynchronous client use + event_loop, future = Client(schedulers.REACTOR, port=5020) + +- # For asyncio based async client use ++ # For asyncio based asynchronous 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 +@@ -36,7 +36,7 @@ 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 * ++ from pymodbus.client.asynchronous.deprecated.asynchronous import * + else: + import logging + logger = logging.getLogger(__name__) +diff --git a/pymodbus/client/async/asyncio/__init__.py b/pymodbus/client/asynchronous/asyncio/__init__.py +similarity index 96% +rename from pymodbus/client/async/asyncio/__init__.py +rename to pymodbus/client/asynchronous/asyncio/__init__.py +index c7bde65..baa9f71 100644 +--- a/pymodbus/client/async/asyncio/__init__.py ++++ b/pymodbus/client/asynchronous/asyncio/__init__.py +@@ -5,7 +5,7 @@ import socket + import asyncio + import functools + from pymodbus.exceptions import ConnectionException +-from pymodbus.client.async.mixins import AsyncModbusClientMixin ++from pymodbus.client.asynchronous.mixins import AsyncModbusClientMixin + from pymodbus.compat import byte2int + import logging + +@@ -286,7 +286,7 @@ class ReconnectingAsyncioModbusTcpClient(object): + self.port) + except Exception as ex: + _logger.warning('Failed to connect: %s' % ex) +- asyncio.async(self._reconnect(), loop=self.loop) ++ asyncio.ensure_future(self._reconnect(), loop=self.loop) + else: + _logger.info('Connected to %s:%s.' % (self.host, self.port)) + self.reset_delay() +@@ -316,7 +316,7 @@ class ReconnectingAsyncioModbusTcpClient(object): + self.connected = False + self.protocol = None + if self.host: +- asyncio.async(self._reconnect(), loop=self.loop) ++ asyncio.ensure_future(self._reconnect(), loop=self.loop) + else: + _logger.error('Factory protocol disconnect callback called while not connected.') + +@@ -384,7 +384,7 @@ class AsyncioModbusTcpClient(object): + _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) ++ # asyncio.asynchronous(self._reconnect(), loop=self.loop) + + def protocol_made_connection(self, protocol): + """ +@@ -411,7 +411,7 @@ class AsyncioModbusTcpClient(object): + self.connected = False + self.protocol = None + # if self.host: +- # asyncio.async(self._reconnect(), loop=self.loop) ++ # asyncio.asynchronous(self._reconnect(), loop=self.loop) + else: + _logger.error('Factory protocol disconnect' + ' callback called while not connected.') +@@ -456,7 +456,7 @@ class ReconnectingAsyncioModbusUdpClient(object): + @asyncio.coroutine + def start(self, host, port=502): + """ +- Start reconnecting async udp client ++ Start reconnecting asynchronous udp client + :param host: Host IP to connect + :param port: Host port to connect + :return: +@@ -513,7 +513,7 @@ class ReconnectingAsyncioModbusUdpClient(object): + _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) ++ asyncio.ensure_future(self._reconnect(), loop=self.loop) + + def protocol_made_connection(self, protocol): + """ +@@ -540,7 +540,7 @@ class ReconnectingAsyncioModbusUdpClient(object): + self.connected = False + self.protocol = None + if self.host: +- asyncio.async(self._reconnect(), loop=self.loop) ++ asyncio.ensure_future(self._reconnect(), loop=self.loop) + else: + _logger.error('Factory protocol disconnect ' + 'callback called while not connected.') +@@ -619,7 +619,7 @@ class AsyncioModbusUdpClient(object): + _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) ++ # asyncio.asynchronous(self._reconnect(), loop=self.loop) + + def protocol_made_connection(self, protocol): + """ +@@ -646,7 +646,7 @@ class AsyncioModbusUdpClient(object): + self.connected = False + self.protocol = None + # if self.host: +- # asyncio.async(self._reconnect(), loop=self.loop) ++ # asyncio.asynchronous(self._reconnect(), loop=self.loop) + else: + _logger.error('Factory protocol disconnect ' + 'callback called while not connected.') +@@ -745,7 +745,7 @@ class AsyncioModbusSerialClient(object): + self._connected_event.clear() + self.protocol = None + # if self.host: +- # asyncio.async(self._reconnect(), loop=self.loop) ++ # asyncio.asynchronous(self._reconnect(), loop=self.loop) + else: + _logger.error('Factory protocol disconnect callback ' + 'called while not connected.') +diff --git a/pymodbus/client/async/deprecated/__init__.py b/pymodbus/client/asynchronous/deprecated/__init__.py +similarity index 74% +rename from pymodbus/client/async/deprecated/__init__.py +rename to pymodbus/client/asynchronous/deprecated/__init__.py +index 02d241a..d891071 100644 +--- a/pymodbus/client/async/deprecated/__init__.py ++++ b/pymodbus/client/asynchronous/deprecated/__init__.py +@@ -9,21 +9,21 @@ and asyncio + + Example run:: + +- from pymodbus.client.async import schedulers ++ from pymodbus.client.asynchronous 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 ++ from pymodbus.client.asynchronous.tcp import AsyncModbusTCPClient as Client ++ from pymodbus.client.asynchronous.serial import AsyncModbusSerialClient as Client ++ from pymodbus.client.asynchronous.udp import AsyncModbusUDPClient as Client + +- # For tornado based async client use ++ # For tornado based asynchronous client use + event_loop, future = Client(schedulers.IO_LOOP, port=5020) + +- # For twisted based async client use ++ # For twisted based asynchronous client use + event_loop, deferred = Client(schedulers.REACTOR, port=5020) + +- # For asyncio based async client use ++ # For asyncio based asynchronous 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 +diff --git a/pymodbus/client/async/deprecated/async.py b/pymodbus/client/asynchronous/deprecated/asynchronous.py +similarity index 97% +rename from pymodbus/client/async/deprecated/async.py +rename to pymodbus/client/asynchronous/deprecated/asynchronous.py +index 5d163a1..bd948ae 100644 +--- a/pymodbus/client/async/deprecated/async.py ++++ b/pymodbus/client/asynchronous/deprecated/asynchronous.py +@@ -5,7 +5,7 @@ Implementation of a Modbus Client Using Twisted + Example run:: + + from twisted.internet import reactor, protocol +- from pymodbus.client.async import ModbusClientProtocol ++ from pymodbus.client.asynchronous import ModbusClientProtocol + + def printResult(result): + print "Result: %d" % result.bits[0] +@@ -22,7 +22,7 @@ Example run:: + Another example:: + + from twisted.internet import reactor +- from pymodbus.client.async import ModbusClientFactory ++ from pymodbus.client.asynchronous import ModbusClientFactory + + def process(): + factory = reactor.connectTCP("localhost", 502, ModbusClientFactory()) +@@ -39,7 +39,7 @@ 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 pymodbus.client.asynchronous.deprecated import deprecated + from twisted.internet import defer, protocol + from twisted.python.failure import Failure + +diff --git a/pymodbus/client/async/factory/__init__.py b/pymodbus/client/asynchronous/factory/__init__.py +similarity index 100% +rename from pymodbus/client/async/factory/__init__.py +rename to pymodbus/client/asynchronous/factory/__init__.py +diff --git a/pymodbus/client/async/factory/serial.py b/pymodbus/client/asynchronous/factory/serial.py +similarity index 85% +rename from pymodbus/client/async/factory/serial.py +rename to pymodbus/client/asynchronous/factory/serial.py +index 8a0a67b..592de8e 100644 +--- a/pymodbus/client/async/factory/serial.py ++++ b/pymodbus/client/asynchronous/factory/serial.py +@@ -1,20 +1,20 @@ + """ +-Factory to create async serial clients based on twisted/tornado/asyncio ++Factory to create asynchronous 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 ++from pymodbus.client.asynchronous import schedulers ++from pymodbus.client.asynchronous.thread import EventLoopThread + + LOGGER = logging.getLogger(__name__) + + + def reactor_factory(port, framer, **kwargs): + """ +- Factory to create twisted serial async client ++ Factory to create twisted serial asynchronous client + :param port: Serial port + :param framer: Modbus Framer + :param kwargs: +@@ -58,7 +58,7 @@ def reactor_factory(port, framer, **kwargs): + + def io_loop_factory(port=None, framer=None, **kwargs): + """ +- Factory to create Tornado based async serial clients ++ Factory to create Tornado based asynchronous serial clients + :param port: Serial port + :param framer: Modbus Framer + :param kwargs: +@@ -66,7 +66,7 @@ def io_loop_factory(port=None, framer=None, **kwargs): + """ + + from tornado.ioloop import IOLoop +- from pymodbus.client.async.tornado import (AsyncModbusSerialClient as ++ from pymodbus.client.asynchronous.tornado import (AsyncModbusSerialClient as + Client) + + ioloop = IOLoop() +@@ -81,15 +81,15 @@ def io_loop_factory(port=None, framer=None, **kwargs): + + def async_io_factory(port=None, framer=None, **kwargs): + """ +- Factory to create asyncio based async serial clients ++ Factory to create asyncio based asynchronous 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) ++ from pymodbus.client.asynchronous.asyncio import (ModbusClientProtocol, ++ AsyncioModbusSerialClient) + loop = kwargs.pop("loop", None) or asyncio.get_event_loop() + proto_cls = kwargs.pop("proto_cls", None) or ModbusClientProtocol + +diff --git a/pymodbus/client/async/factory/tcp.py b/pymodbus/client/asynchronous/factory/tcp.py +similarity index 85% +rename from pymodbus/client/async/factory/tcp.py +rename to pymodbus/client/asynchronous/factory/tcp.py +index e8f425d..fb61378 100644 +--- a/pymodbus/client/async/factory/tcp.py ++++ b/pymodbus/client/asynchronous/factory/tcp.py +@@ -1,13 +1,13 @@ + """ +-Factory to create async tcp clients based on twisted/tornado/asyncio ++Factory to create asynchronous 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.client.asynchronous import schedulers ++from pymodbus.client.asynchronous.thread import EventLoopThread + from pymodbus.constants import Defaults + + LOGGER = logging.getLogger(__name__) +@@ -16,7 +16,7 @@ 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 ++ Factory to create twisted tcp asynchronous client + :param host: Host IP address + :param port: Port + :param framer: Modbus Framer +@@ -26,7 +26,7 @@ def reactor_factory(host="127.0.0.1", port=Defaults.Port, framer=None, + :return: event_loop_thread and twisted_deferred + """ + from twisted.internet import reactor, protocol +- from pymodbus.client.async.twisted import ModbusTcpClientProtocol ++ from pymodbus.client.asynchronous.twisted import ModbusTcpClientProtocol + + deferred = protocol.ClientCreator( + reactor, ModbusTcpClientProtocol +@@ -51,7 +51,7 @@ def reactor_factory(host="127.0.0.1", port=Defaults.Port, framer=None, + 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 ++ Factory to create Tornado based asynchronous tcp clients + :param host: Host IP address + :param port: Port + :param framer: Modbus Framer +@@ -61,7 +61,7 @@ def io_loop_factory(host="127.0.0.1", port=Defaults.Port, framer=None, + :return: event_loop_thread and tornado future + """ + from tornado.ioloop import IOLoop +- from pymodbus.client.async.tornado import AsyncModbusTCPClient as \ ++ from pymodbus.client.asynchronous.tornado import AsyncModbusTCPClient as \ + Client + + ioloop = IOLoop() +@@ -80,7 +80,7 @@ def io_loop_factory(host="127.0.0.1", port=Defaults.Port, framer=None, + 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 ++ Factory to create asyncio based asynchronous tcp clients + :param host: Host IP address + :param port: Port + :param framer: Modbus Framer +@@ -90,7 +90,7 @@ def async_io_factory(host="127.0.0.1", port=Defaults.Port, framer=None, + :return: asyncio event loop and tcp client + """ + import asyncio +- from pymodbus.client.async.asyncio import init_tcp_client ++ from pymodbus.client.asynchronous.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(): +diff --git a/pymodbus/client/async/factory/udp.py b/pymodbus/client/asynchronous/factory/udp.py +similarity index 85% +rename from pymodbus/client/async/factory/udp.py +rename to pymodbus/client/asynchronous/factory/udp.py +index c6896b6..d6dc75e 100644 +--- a/pymodbus/client/async/factory/udp.py ++++ b/pymodbus/client/asynchronous/factory/udp.py +@@ -3,8 +3,8 @@ from __future__ import absolute_import + + import logging + +-from pymodbus.client.async import schedulers +-from pymodbus.client.async.thread import EventLoopThread ++from pymodbus.client.asynchronous import schedulers ++from pymodbus.client.asynchronous.thread import EventLoopThread + from pymodbus.constants import Defaults + + LOGGER = logging.getLogger(__name__) +@@ -13,7 +13,7 @@ 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 ++ Factory to create twisted udp asynchronous client + :param host: Host IP address + :param port: Port + :param framer: Modbus Framer +@@ -28,7 +28,7 @@ def reactor_factory(host="127.0.0.1", port=Defaults.Port, framer=None, + 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 ++ Factory to create Tornado based asynchronous udp clients + :param host: Host IP address + :param port: Port + :param framer: Modbus Framer +@@ -38,7 +38,7 @@ def io_loop_factory(host="127.0.0.1", port=Defaults.Port, framer=None, + :return: event_loop_thread and tornado future + """ + from tornado.ioloop import IOLoop +- from pymodbus.client.async.tornado import AsyncModbusUDPClient as \ ++ from pymodbus.client.asynchronous.tornado import AsyncModbusUDPClient as \ + Client + + client = Client(host=host, port=port, framer=framer, +@@ -55,7 +55,7 @@ def io_loop_factory(host="127.0.0.1", port=Defaults.Port, framer=None, + 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 ++ Factory to create asyncio based asynchronous udp clients + :param host: Host IP address + :param port: Port + :param framer: Modbus Framer +@@ -65,7 +65,7 @@ def async_io_factory(host="127.0.0.1", port=Defaults.Port, framer=None, + :return: asyncio event loop and udp client + """ + import asyncio +- from pymodbus.client.async.asyncio import init_udp_client ++ from pymodbus.client.asynchronous.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) +diff --git a/pymodbus/client/async/mixins.py b/pymodbus/client/asynchronous/mixins.py +similarity index 97% +rename from pymodbus/client/async/mixins.py +rename to pymodbus/client/asynchronous/mixins.py +index 008cf49..f4cae32 100644 +--- a/pymodbus/client/async/mixins.py ++++ b/pymodbus/client/asynchronous/mixins.py +@@ -37,7 +37,7 @@ class AsyncModbusClientMixin(BaseAsyncModbusClient): + 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 ++ Initializes a Modbus TCP/UDP asynchronous client + :param host: Host IP address + :param port: Port + :param framer: Framer to use +diff --git a/pymodbus/client/async/schedulers/__init__.py b/pymodbus/client/asynchronous/schedulers/__init__.py +similarity index 100% +rename from pymodbus/client/async/schedulers/__init__.py +rename to pymodbus/client/asynchronous/schedulers/__init__.py +diff --git a/pymodbus/client/async/serial.py b/pymodbus/client/asynchronous/serial.py +similarity index 92% +rename from pymodbus/client/async/serial.py +rename to pymodbus/client/asynchronous/serial.py +index 372072d..5d2e2e8 100644 +--- a/pymodbus/client/async/serial.py ++++ b/pymodbus/client/asynchronous/serial.py +@@ -2,12 +2,12 @@ from __future__ import unicode_literals + from __future__ import absolute_import + + import logging +-from pymodbus.client.async.factory.serial import get_factory ++from pymodbus.client.asynchronous.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 ++from pymodbus.client.asynchronous.schedulers import ASYNC_IO + + logger = logging.getLogger(__name__) + +@@ -18,7 +18,7 @@ class AsyncModbusSerialClient(object): + + To use do:: + +- from pymodbus.client.async.serial import AsyncModbusSerialClient ++ from pymodbus.client.asynchronous.serial import AsyncModbusSerialClient + """ + @classmethod + def _framer(cls, method): +diff --git a/pymodbus/client/async/tcp.py b/pymodbus/client/asynchronous/tcp.py +similarity index 88% +rename from pymodbus/client/async/tcp.py +rename to pymodbus/client/asynchronous/tcp.py +index 23470bb..45b7cfd 100644 +--- a/pymodbus/client/async/tcp.py ++++ b/pymodbus/client/asynchronous/tcp.py +@@ -2,10 +2,10 @@ from __future__ import unicode_literals + from __future__ import absolute_import + + import logging +-from pymodbus.client.async.factory.tcp import get_factory ++from pymodbus.client.asynchronous.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 ++from pymodbus.client.asynchronous.schedulers import ASYNC_IO + + logger = logging.getLogger(__name__) + +@@ -16,7 +16,7 @@ class AsyncModbusTCPClient(object): + + To use do:: + +- from pymodbus.client.async.tcp import AsyncModbusTCPClient ++ from pymodbus.client.asynchronous.tcp import AsyncModbusTCPClient + """ + def __new__(cls, scheduler, host="127.0.0.1", port=Defaults.Port, + framer=None, source_address=None, timeout=None, **kwargs): +diff --git a/pymodbus/client/async/thread.py b/pymodbus/client/asynchronous/thread.py +similarity index 100% +rename from pymodbus/client/async/thread.py +rename to pymodbus/client/asynchronous/thread.py +diff --git a/pymodbus/client/async/tornado/__init__.py b/pymodbus/client/asynchronous/tornado/__init__.py +similarity index 97% +rename from pymodbus/client/async/tornado/__init__.py +rename to pymodbus/client/asynchronous/tornado/__init__.py +index 5a9a67c..780bf5f 100644 +--- a/pymodbus/client/async/tornado/__init__.py ++++ b/pymodbus/client/asynchronous/tornado/__init__.py +@@ -15,8 +15,8 @@ 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.client.asynchronous.mixins import (AsyncModbusClientMixin, ++ AsyncModbusSerialClientMixin) + from pymodbus.exceptions import ConnectionException + from pymodbus.compat import byte2int + +@@ -287,7 +287,7 @@ class SerialIOStream(BaseIOStream): + + class AsyncModbusSerialClient(BaseTornadoSerialClient): + """ +- Tornado based async serial client ++ Tornado based asynchronous serial client + """ + def get_socket(self): + """ +diff --git a/pymodbus/client/async/twisted/__init__.py b/pymodbus/client/asynchronous/twisted/__init__.py +similarity index 97% +rename from pymodbus/client/async/twisted/__init__.py +rename to pymodbus/client/asynchronous/twisted/__init__.py +index 268afef..b1841a8 100644 +--- a/pymodbus/client/async/twisted/__init__.py ++++ b/pymodbus/client/asynchronous/twisted/__init__.py +@@ -5,7 +5,7 @@ Implementation of a Modbus Client Using Twisted + Example run:: + + from twisted.internet import reactor, protocol +- from pymodbus.client.async import ModbusClientProtocol ++ from pymodbus.client.asynchronous import ModbusClientProtocol + + def printResult(result): + print "Result: %d" % result.bits[0] +@@ -22,7 +22,7 @@ Example run:: + Another example:: + + from twisted.internet import reactor +- from pymodbus.client.async import ModbusClientFactory ++ from pymodbus.client.asynchronous import ModbusClientFactory + + def process(): + factory = reactor.connectTCP("localhost", 502, ModbusClientFactory()) +@@ -37,7 +37,7 @@ 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.client.asynchronous.mixins import AsyncModbusClientMixin + from pymodbus.transaction import FifoTransactionManager, DictTransactionManager + from pymodbus.transaction import ModbusSocketFramer, ModbusRtuFramer + from pymodbus.compat import byte2int +diff --git a/pymodbus/client/async/udp.py b/pymodbus/client/asynchronous/udp.py +similarity index 88% +rename from pymodbus/client/async/udp.py +rename to pymodbus/client/asynchronous/udp.py +index b8f529a..a111d96 100644 +--- a/pymodbus/client/async/udp.py ++++ b/pymodbus/client/asynchronous/udp.py +@@ -4,8 +4,8 @@ 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 ++from pymodbus.client.asynchronous.schedulers import ASYNC_IO ++from pymodbus.client.asynchronous.factory.udp import get_factory + + logger = logging.getLogger(__name__) + +@@ -16,7 +16,7 @@ class AsyncModbusUDPClient(object): + + To use do:: + +- from pymodbus.client.async.tcp import AsyncModbusUDPClient ++ from pymodbus.client.asynchronous.tcp import AsyncModbusUDPClient + """ + def __new__(cls, scheduler, host="127.0.0.1", port=Defaults.Port, + framer=None, source_address=None, timeout=None, **kwargs): +diff --git a/pymodbus/framer/__init__.py b/pymodbus/framer/__init__.py +index fb0530b..5d84cc4 100644 +--- a/pymodbus/framer/__init__.py ++++ b/pymodbus/framer/__init__.py +@@ -26,7 +26,7 @@ class ModbusFramer(IModbusFramer): + else: + if 0 in units or 0xFF in units: + # Handle Modbus TCP unit identifier (0x00 0r 0xFF) +- # in async requests ++ # in asynchronous requests + return True + return self._header['uid'] in units + +diff --git a/pymodbus/framer/ascii_framer.py b/pymodbus/framer/ascii_framer.py +index 4f7f99c..f98bdca 100644 +--- a/pymodbus/framer/ascii_framer.py ++++ b/pymodbus/framer/ascii_framer.py +@@ -162,9 +162,10 @@ class ModbusAsciiFramer(ModbusFramer): + + :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 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] +diff --git a/pymodbus/framer/binary_framer.py b/pymodbus/framer/binary_framer.py +index 9ccfbf7..ba6655b 100644 +--- a/pymodbus/framer/binary_framer.py ++++ b/pymodbus/framer/binary_framer.py +@@ -152,9 +152,10 @@ class ModbusBinaryFramer(ModbusFramer): + + :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 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)): +diff --git a/pymodbus/framer/rtu_framer.py b/pymodbus/framer/rtu_framer.py +index 4c343db..8579a57 100644 +--- a/pymodbus/framer/rtu_framer.py ++++ b/pymodbus/framer/rtu_framer.py +@@ -205,9 +205,10 @@ class ModbusRtuFramer(ModbusFramer): + + :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 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] +diff --git a/pymodbus/framer/socket_framer.py b/pymodbus/framer/socket_framer.py +index 45c8a73..fff99d9 100644 +--- a/pymodbus/framer/socket_framer.py ++++ b/pymodbus/framer/socket_framer.py +@@ -136,8 +136,8 @@ class ModbusSocketFramer(ModbusFramer): + + :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 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: + """ +diff --git a/pymodbus/repl/client.py b/pymodbus/repl/client.py +index 3360260..146779a 100644 +--- a/pymodbus/repl/client.py ++++ b/pymodbus/repl/client.py +@@ -319,7 +319,9 @@ class ExtendedRequestSupport(object): + device. + + :param unit: The slave unit this request is targeting ++ + :return: ++ + """ + request = ReadExceptionStatusRequest(**kwargs) + resp = self.execute(request) +@@ -337,7 +339,9 @@ class ExtendedRequestSupport(object): + communication event counter. + + :param unit: The slave unit this request is targeting ++ + :return: ++ + """ + request = GetCommEventCounterRequest(**kwargs) + resp = self.execute(request) +diff --git a/pymodbus/server/async.py b/pymodbus/server/asynchronous.py +similarity index 97% +rename from pymodbus/server/async.py +rename to pymodbus/server/asynchronous.py +index d58027a..8f1311f 100644 +--- a/pymodbus/server/async.py ++++ b/pymodbus/server/asynchronous.py +@@ -238,10 +238,11 @@ def StartTcpServer(context, identity=None, address=None, + :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 ++ :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 + +@@ -265,10 +266,10 @@ def StartUdpServer(context, identity=None, address=None, + :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 ++ :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 + +@@ -295,10 +296,11 @@ def StartSerialServer(context, identity=None, + :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 ++ :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 +diff --git a/test/test_client_async.py b/test/test_client_async.py +index d160527..337b425 100644 +--- a/test/test_client_async.py ++++ b/test/test_client_async.py +@@ -5,25 +5,25 @@ 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 pymodbus.client.asynchronous.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.asynchronous.serial import AsyncModbusSerialClient ++from pymodbus.client.asynchronous.tcp import AsyncModbusTCPClient ++from pymodbus.client.asynchronous.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.client.asynchronous.tornado import AsyncModbusSerialClient as AsyncTornadoModbusSerialClient ++from pymodbus.client.asynchronous.tornado import AsyncModbusTCPClient as AsyncTornadoModbusTcpClient ++from pymodbus.client.asynchronous.tornado import AsyncModbusUDPClient as AsyncTornadoModbusUdoClient ++from pymodbus.client.asynchronous 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 ++from pymodbus.client.asynchronous.twisted import ModbusSerClientProtocol + + IS_DARWIN = platform.system().lower() == "darwin" + OSX_SIERRA = LooseVersion("10.12") +@@ -45,7 +45,7 @@ def mock_asyncio_gather(coro): + + class TestAsynchronousClient(object): + """ +- This is the unittest for the pymodbus.client.async module ++ This is the unittest for the pymodbus.client.asynchronous module + """ + + # -----------------------------------------------------------------------# +@@ -68,8 +68,8 @@ class TestAsynchronousClient(object): + callback=test_callback, + errback=test_errback) + +- @patch("pymodbus.client.async.tornado.IOLoop") +- @patch("pymodbus.client.async.tornado.IOStream") ++ @patch("pymodbus.client.asynchronous.tornado.IOLoop") ++ @patch("pymodbus.client.asynchronous.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())) +@@ -108,8 +108,8 @@ class TestAsynchronousClient(object): + # Test UDP client + # -----------------------------------------------------------------------# + +- @patch("pymodbus.client.async.tornado.IOLoop") +- @patch("pymodbus.client.async.tornado.IOStream") ++ @patch("pymodbus.client.asynchronous.tornado.IOLoop") ++ @patch("pymodbus.client.asynchronous.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())) +@@ -213,7 +213,7 @@ class TestAsynchronousClient(object): + @pytest.mark.skipif(IS_PYTHON3 , reason="requires python2.7") + def testSerialAsyncioClientPython2(self): + """ +- Test Serial async asyncio client exits on python2 ++ Test Serial asynchronous asyncio client exits on python2 + :return: + """ + with pytest.raises(SystemExit) as pytest_wrapped_e: +diff --git a/test/test_client_async_asyncio.py b/test/test_client_async_asyncio.py +index 669a102..de2de9d 100644 +--- a/test/test_client_async_asyncio.py ++++ b/test/test_client_async_asyncio.py +@@ -2,7 +2,7 @@ from 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 ( ++ from pymodbus.client.asynchronous.asyncio import ( + ReconnectingAsyncioModbusTcpClient, + ModbusClientProtocol, ModbusUdpClientProtocol) + from test.asyncio_test_helper import return_as_coroutine, run_coroutine +@@ -92,7 +92,7 @@ class TestAsyncioClient(object): + assert client.connected + assert client.protocol is mock.sentinel.PROTOCOL + +- @mock.patch('pymodbus.client.async.asyncio.asyncio.async') ++ @mock.patch('pymodbus.client.asynchronous.asyncio.asyncio.ensure_future') + def test_factory_protocol_lost_connection(self, mock_async): + mock_protocol_class = mock.MagicMock() + mock_loop = mock.MagicMock() +@@ -109,14 +109,14 @@ class TestAsyncioClient(object): + client.port = mock.sentinel.PORT + client.protocol = mock.sentinel.PROTOCOL + +- with mock.patch('pymodbus.client.async.asyncio.ReconnectingAsyncioModbusTcpClient._reconnect') as mock_reconnect: ++ with mock.patch('pymodbus.client.asynchronous.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') ++ @mock.patch('pymodbus.client.asynchronous.asyncio.asyncio.ensure_future') + def test_factory_start_success(self, mock_async): + mock_protocol_class = mock.MagicMock() + mock_loop = mock.MagicMock() +@@ -126,7 +126,7 @@ class TestAsyncioClient(object): + 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') ++ @mock.patch('pymodbus.client.asynchronous.asyncio.asyncio.ensure_future') + def test_factory_start_failing_and_retried(self, mock_async): + mock_protocol_class = mock.MagicMock() + mock_loop = mock.MagicMock() +@@ -134,13 +134,13 @@ class TestAsyncioClient(object): + 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: ++ with mock.patch('pymodbus.client.asynchronous.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') ++ @mock.patch('pymodbus.client.asynchronous.asyncio.asyncio.sleep') + def test_factory_reconnect(self, mock_sleep): + mock_protocol_class = mock.MagicMock() + mock_loop = mock.MagicMock() +diff --git a/test/test_client_async_tornado.py b/test/test_client_async_tornado.py +index 07aef85..25d1f60 100644 +--- a/test/test_client_async_tornado.py ++++ b/test/test_client_async_tornado.py +@@ -5,12 +5,12 @@ 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.client.asynchronous.tornado import (BaseTornadoClient, ++ AsyncModbusSerialClient, AsyncModbusUDPClient, AsyncModbusTCPClient ++ ) ++from pymodbus.client.asynchronous import schedulers + from pymodbus.factory import ClientDecoder +-from pymodbus.client.async.twisted import ModbusClientFactory ++from pymodbus.client.asynchronous.twisted import ModbusClientFactory + from pymodbus.exceptions import ConnectionException + from pymodbus.transaction import ModbusSocketFramer, ModbusRtuFramer + from pymodbus.bit_read_message import ReadCoilsRequest, ReadCoilsResponse +@@ -33,7 +33,7 @@ else: + + class AsynchronousClientTest(unittest.TestCase): + """ +- This is the unittest for the pymodbus.client.async module ++ This is the unittest for the pymodbus.client.asynchronous module + """ + + # -----------------------------------------------------------------------# +@@ -57,8 +57,8 @@ class AsynchronousClientTest(unittest.TestCase): + 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") ++ @patch("pymodbus.client.asynchronous.tornado.IOLoop") ++ @patch("pymodbus.client.asynchronous.tornado.IOStream") + def testBaseClientOn_receive(self, mock_iostream, mock_ioloop): + """ Test the BaseTornado client data received """ + client = AsyncModbusTCPClient(port=5020) +@@ -79,8 +79,8 @@ class AsynchronousClientTest(unittest.TestCase): + d.add_done_callback(lambda v: out.append(v)) + self.assertFalse(out) + +- @patch("pymodbus.client.async.tornado.IOLoop") +- @patch("pymodbus.client.async.tornado.IOStream") ++ @patch("pymodbus.client.asynchronous.tornado.IOLoop") ++ @patch("pymodbus.client.asynchronous.tornado.IOStream") + def testBaseClientExecute(self, mock_iostream, mock_ioloop): + """ Test the BaseTornado client execute method """ + client = AsyncModbusTCPClient(port=5020) +@@ -93,8 +93,8 @@ class AsynchronousClientTest(unittest.TestCase): + tid = request.transaction_id + self.assertEqual(d, client.transaction.getTransaction(tid)) + +- @patch("pymodbus.client.async.tornado.IOLoop") +- @patch("pymodbus.client.async.tornado.IOStream") ++ @patch("pymodbus.client.asynchronous.tornado.IOLoop") ++ @patch("pymodbus.client.asynchronous.tornado.IOStream") + def testBaseClientHandleResponse(self, mock_iostream, mock_ioloop): + """ Test the BaseTornado client handles responses """ + client = AsyncModbusTCPClient(port=5020) +@@ -113,8 +113,8 @@ class AsynchronousClientTest(unittest.TestCase): + client._handle_response(reply) + self.assertEqual(out[0].result(), reply) + +- @patch("pymodbus.client.async.tornado.IOLoop") +- @patch("pymodbus.client.async.tornado.IOStream") ++ @patch("pymodbus.client.asynchronous.tornado.IOLoop") ++ @patch("pymodbus.client.asynchronous.tornado.IOStream") + def testBaseClientBuildResponse(self, mock_iostream, mock_ioloop): + """ Test the BaseTornado client client builds responses """ + client = BaseTornadoClient() +@@ -144,8 +144,8 @@ class AsynchronousClientTest(unittest.TestCase): + client = AsyncModbusTCPClient(framer=framer) + self.assertTrue(framer is client.framer) + +- @patch("pymodbus.client.async.tornado.IOLoop") +- @patch("pymodbus.client.async.tornado.IOStream") ++ @patch("pymodbus.client.asynchronous.tornado.IOLoop") ++ @patch("pymodbus.client.asynchronous.tornado.IOStream") + def testTcpClientConnect(self, mock_iostream, mock_ioloop): + """ Test the tornado tcp client client connect """ + client = AsyncModbusTCPClient(port=5020) +@@ -154,8 +154,8 @@ class AsynchronousClientTest(unittest.TestCase): + client.connect() + self.assertTrue(client._connected) + +- @patch("pymodbus.client.async.tornado.IOLoop") +- @patch("pymodbus.client.async.tornado.IOStream") ++ @patch("pymodbus.client.asynchronous.tornado.IOLoop") ++ @patch("pymodbus.client.asynchronous.tornado.IOStream") + def testTcpClientDisconnect(self, mock_iostream, mock_ioloop): + """ Test the tornado tcp client client disconnect """ + client = AsyncModbusTCPClient(port=5020) +@@ -185,9 +185,9 @@ class AsynchronousClientTest(unittest.TestCase): + 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") ++ @patch("pymodbus.client.asynchronous.tornado.IOLoop") ++ @patch("pymodbus.client.asynchronous.tornado.SerialIOStream") ++ @patch("pymodbus.client.asynchronous.tornado.Serial") + def testSerialClientConnect(self, mock_serial, mock_seriostream, mock_ioloop): + """ Test the tornado serial client client connect """ + client = AsyncModbusSerialClient(ioloop=schedulers.IO_LOOP, +@@ -200,9 +200,9 @@ class AsynchronousClientTest(unittest.TestCase): + self.assertTrue(client._connected) + client.close() + +- @patch("pymodbus.client.async.tornado.IOLoop") +- @patch("pymodbus.client.async.tornado.SerialIOStream") +- @patch("pymodbus.client.async.tornado.Serial") ++ @patch("pymodbus.client.asynchronous.tornado.IOLoop") ++ @patch("pymodbus.client.asynchronous.tornado.SerialIOStream") ++ @patch("pymodbus.client.asynchronous.tornado.Serial") + def testSerialClientDisconnect(self, mock_serial, mock_seriostream, mock_ioloop): + """ Test the tornado serial client client disconnect """ + client = AsyncModbusSerialClient(ioloop=schedulers.IO_LOOP, +@@ -220,9 +220,9 @@ class AsynchronousClientTest(unittest.TestCase): + client.close() + self.assertFalse(client._connected) + +- @patch("pymodbus.client.async.tornado.IOLoop") +- @patch("pymodbus.client.async.tornado.SerialIOStream") +- @patch("pymodbus.client.async.tornado.Serial") ++ @patch("pymodbus.client.asynchronous.tornado.IOLoop") ++ @patch("pymodbus.client.asynchronous.tornado.SerialIOStream") ++ @patch("pymodbus.client.asynchronous.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, +@@ -238,9 +238,9 @@ class AsynchronousClientTest(unittest.TestCase): + 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") ++ @patch("pymodbus.client.asynchronous.tornado.IOLoop") ++ @patch("pymodbus.client.asynchronous.tornado.SerialIOStream") ++ @patch("pymodbus.client.asynchronous.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, +@@ -262,9 +262,9 @@ class AsynchronousClientTest(unittest.TestCase): + 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") ++ @patch("pymodbus.client.asynchronous.tornado.IOLoop") ++ @patch("pymodbus.client.asynchronous.tornado.SerialIOStream") ++ @patch("pymodbus.client.asynchronous.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, +diff --git a/test/test_client_async_twisted.py b/test/test_client_async_twisted.py +index d2241c3..6289013 100644 +--- a/test/test_client_async_twisted.py ++++ b/test/test_client_async_twisted.py +@@ -5,11 +5,11 @@ if IS_PYTHON3: + from unittest.mock import patch, Mock + else: # Python 2 + from mock import patch, Mock +-from pymodbus.client.async.twisted import ( ++from pymodbus.client.asynchronous.twisted import ( + ModbusClientProtocol, ModbusUdpClientProtocol, ModbusSerClientProtocol, ModbusTcpClientProtocol + ) + from pymodbus.factory import ClientDecoder +-from pymodbus.client.async.twisted import ModbusClientFactory ++from pymodbus.client.asynchronous.twisted import ModbusClientFactory + from pymodbus.exceptions import ConnectionException + from pymodbus.transaction import ModbusSocketFramer, ModbusRtuFramer + from pymodbus.bit_read_message import ReadCoilsRequest, ReadCoilsResponse +@@ -20,7 +20,7 @@ from pymodbus.bit_read_message import ReadCoilsRequest, ReadCoilsResponse + + class AsynchronousClientTest(unittest.TestCase): + ''' +- This is the unittest for the pymodbus.client.async module ++ This is the unittest for the pymodbus.client.asynchronous module + ''' + + #-----------------------------------------------------------------------# +diff --git a/test/test_server_async.py b/test/test_server_async.py +index 8c78a95..447029a 100644 +--- a/test/test_server_async.py ++++ b/test/test_server_async.py +@@ -6,9 +6,9 @@ if IS_PYTHON3: # Python 3 + 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 ( ++from pymodbus.server.asynchronous import ModbusTcpProtocol, ModbusUdpProtocol ++from pymodbus.server.asynchronous import ModbusServerFactory ++from pymodbus.server.asynchronous import ( + StartTcpServer, StartUdpServer, StartSerialServer, StopServer, + _is_main_thread + ) +@@ -35,7 +35,7 @@ else: + + class AsynchronousServerTest(unittest.TestCase): + ''' +- This is the unittest for the pymodbus.server.async module ++ This is the unittest for the pymodbus.server.asynchronous module + ''' + + #-----------------------------------------------------------------------# +@@ -56,7 +56,7 @@ class AsynchronousServerTest(unittest.TestCase): + # Test ModbusTcpProtocol + #-----------------------------------------------------------------------# + def testTcpServerStartup(self): +- ''' Test that the modbus tcp async server starts correctly ''' ++ ''' Test that the modbus tcp asynchronous server starts correctly ''' + with patch('twisted.internet.reactor') as mock_reactor: + if IS_PYTHON3: + console = False +@@ -186,7 +186,7 @@ class AsynchronousServerTest(unittest.TestCase): + + + def testUdpServerStartup(self): +- ''' Test that the modbus udp async server starts correctly ''' ++ ''' Test that the modbus udp asynchronous server starts correctly ''' + with patch('twisted.internet.reactor') as mock_reactor: + StartUdpServer(context=None) + self.assertEqual(mock_reactor.listenUDP.call_count, 1) +@@ -194,7 +194,7 @@ class AsynchronousServerTest(unittest.TestCase): + + @patch("twisted.internet.serialport.SerialPort") + def testSerialServerStartup(self, mock_sp): +- ''' Test that the modbus serial async server starts correctly ''' ++ ''' Test that the modbus serial asynchronous 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) +@@ -202,7 +202,7 @@ class AsynchronousServerTest(unittest.TestCase): + @patch("twisted.internet.serialport.SerialPort") + def testStopServerFromMainThread(self, mock_sp): + """ +- Stop async server ++ Stop asynchronous server + :return: + """ + with patch('twisted.internet.reactor') as mock_reactor: +@@ -214,7 +214,7 @@ class AsynchronousServerTest(unittest.TestCase): + @patch("twisted.internet.serialport.SerialPort") + def testStopServerFromThread(self, mock_sp): + """ +- Stop async server from child thread ++ Stop asynchronous server from child thread + :return: + """ + from threading import Thread diff -Nru pymodbus-1.5.2+dfsg/debian/patches/relax-dependencies.patch pymodbus-2.1.0+dfsg/debian/patches/relax-dependencies.patch --- pymodbus-1.5.2+dfsg/debian/patches/relax-dependencies.patch 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/debian/patches/relax-dependencies.patch 2018-11-26 11:19:18.000000000 +0000 @@ -0,0 +1,14 @@ +Description: relax dependency on six +Author: W. Martin Borgert +Origin: vendor +Last-Update: 2018-11-26 +--- +This patch header follows DEP-3: http://dep.debian.net/deps/dep3/ +--- a/requirements.txt ++++ b/requirements.txt +@@ -1,4 +1,4 @@ +-six==1.11.0 ++six>=1.11.0 + # ------------------------------------------------------------------- + # if want to use the pymodbus serial stack, uncomment these + # ------------------------------------------------------------------- diff -Nru pymodbus-1.5.2+dfsg/debian/patches/series pymodbus-2.1.0+dfsg/debian/patches/series --- pymodbus-1.5.2+dfsg/debian/patches/series 2018-05-23 20:27:05.000000000 +0000 +++ pymodbus-2.1.0+dfsg/debian/patches/series 2018-11-26 11:17:09.000000000 +0000 @@ -1 +1,4 @@ +relax-dependencies.patch +python-3.7.patch +disable-failing-unittests.patch privacy-breach-fixes.patch diff -Nru pymodbus-1.5.2+dfsg/debian/rules pymodbus-2.1.0+dfsg/debian/rules --- pymodbus-1.5.2+dfsg/debian/rules 2018-05-23 22:32:50.000000000 +0000 +++ pymodbus-2.1.0+dfsg/debian/rules 2018-11-26 12:34:15.000000000 +0000 @@ -1,7 +1,6 @@ #!/usr/bin/make -f # -*- makefile -*- -# Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 export PYBUILD_NAME=pymodbus export http_proxy = http://127.0.0.1:9 @@ -13,11 +12,19 @@ override_dh_auto_build: dh_auto_build ( cd doc; \ - PYTHONPATH=. http_proxy='127.0.0.1:9' sphinx-build -M html . _build ) + PYTHONPATH=.. http_proxy='127.0.0.1:9' python3 -m sphinx -M html . _build ) + +# pybuild hangs after successful tests since Python 3.7, therefore disabled +# hopefully solved, when upstream supports Python 3.7 officially +override_dh_auto_test: override_dh_install: dh_install rm -f $(CURDIR)/debian/python*-pymodbus/usr/lib/python*/dist-packages/.coverage + # remove the new script, currently broken on Debian anyway + rm -f $(CURDIR)/debian/python*-pymodbus/usr/bin/pymodbus.console + # remove asyncio code for Python 2 + rm -rf $(CURDIR)/debian/python-pymodbus/usr/lib/python2*/dist-packages/pymodbus/client/asynchronous/asyncio/ override_dh_installdocs: dh_installdocs -ppython-pymodbus-doc --doc-main-package=python-pymodbus-doc diff -Nru pymodbus-1.5.2+dfsg/doc/changelog.rst pymodbus-2.1.0+dfsg/doc/changelog.rst --- pymodbus-1.5.2+dfsg/doc/changelog.rst 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/doc/changelog.rst 2018-10-03 12:44:47.000000000 +0000 @@ -1 +1,5 @@ +============ +CHANGELOGS +============ + .. include:: ../CHANGELOG.rst \ No newline at end of file diff -Nru pymodbus-1.5.2+dfsg/doc/conf.py pymodbus-2.1.0+dfsg/doc/conf.py --- pymodbus-1.5.2+dfsg/doc/conf.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/doc/conf.py 2018-10-03 12:44:47.000000000 +0000 @@ -18,6 +18,10 @@ # 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") @@ -31,7 +35,7 @@ # 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. @@ -41,7 +45,7 @@ # 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'] +extensions = ['sphinx.ext.autodoc', 'recommonmark'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -49,8 +53,12 @@ # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_parsers = { + '.md': CommonMarkParser, +} + +source_suffix = ['.rst', '.md'] +# source_suffix = '.rst' # The master toctree document. master_doc = 'index' @@ -65,9 +73,9 @@ # built documents. # # The short X.Y version. -version = u'1.4.0' +version = __version__ # The full version, including alpha/beta/rc tags. -release = u'1.4.0' +release = __version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -176,4 +184,10 @@ ] +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) diff -Nru pymodbus-1.5.2+dfsg/doc/index.rst pymodbus-2.1.0+dfsg/doc/index.rst --- pymodbus-1.5.2+dfsg/doc/index.rst 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/doc/index.rst 2018-10-03 12:44:47.000000000 +0000 @@ -12,6 +12,7 @@ readme.rst changelog.rst + source/library/REPL source/example/modules.rst source/library/modules.rst diff -Nru pymodbus-1.5.2+dfsg/doc/source/example/async_asyncio_client.rst pymodbus-2.1.0+dfsg/doc/source/example/async_asyncio_client.rst --- pymodbus-1.5.2+dfsg/doc/source/example/async_asyncio_client.rst 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/doc/source/example/async_asyncio_client.rst 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,4 @@ +================================================== +Async Asyncio Client Example +================================================== +.. literalinclude:: ../../../examples/common/async_asyncio_client.py \ No newline at end of file diff -Nru pymodbus-1.5.2+dfsg/doc/source/example/async_asyncio_serial_client.rst pymodbus-2.1.0+dfsg/doc/source/example/async_asyncio_serial_client.rst --- pymodbus-1.5.2+dfsg/doc/source/example/async_asyncio_serial_client.rst 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/doc/source/example/async_asyncio_serial_client.rst 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,4 @@ +================================================== +Async Asyncio Serial Client Example +================================================== +.. literalinclude:: ../../../examples/common/async_asyncio_serial_client.py \ No newline at end of file diff -Nru pymodbus-1.5.2+dfsg/doc/source/example/asynchronous_asyncio_serial_client.rst pymodbus-2.1.0+dfsg/doc/source/example/asynchronous_asyncio_serial_client.rst --- pymodbus-1.5.2+dfsg/doc/source/example/asynchronous_asyncio_serial_client.rst 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/doc/source/example/asynchronous_asyncio_serial_client.rst 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,4 @@ +================================================== +Asynchronous Asyncio Serial Client Example +================================================== +.. literalinclude:: ../../../examples/contrib/asynchronous_asyncio_serial_client.py \ No newline at end of file diff -Nru pymodbus-1.5.2+dfsg/doc/source/example/asynchronous_client.rst pymodbus-2.1.0+dfsg/doc/source/example/asynchronous_client.rst --- pymodbus-1.5.2+dfsg/doc/source/example/asynchronous_client.rst 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/doc/source/example/asynchronous_client.rst 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ -================================================== -Asynchronous Client Example -================================================== -.. literalinclude:: ../../../examples/common/asynchronous_client.py \ No newline at end of file diff -Nru pymodbus-1.5.2+dfsg/doc/source/example/asynchronous_server.rst pymodbus-2.1.0+dfsg/doc/source/example/asynchronous_server.rst --- pymodbus-1.5.2+dfsg/doc/source/example/asynchronous_server.rst 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/doc/source/example/asynchronous_server.rst 2018-10-03 12:44:47.000000000 +0000 @@ -1,4 +1,4 @@ -================================================== +=========================== Asynchronous Server Example -================================================== -.. literalinclude:: ../../../examples/common/asynchronous_server.py \ No newline at end of file +=========================== +.. literalinclude:: ../../../examples/common/asynchronous_server.py diff -Nru pymodbus-1.5.2+dfsg/doc/source/example/async_tornado_client.rst pymodbus-2.1.0+dfsg/doc/source/example/async_tornado_client.rst --- pymodbus-1.5.2+dfsg/doc/source/example/async_tornado_client.rst 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/doc/source/example/async_tornado_client.rst 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,4 @@ +================================================== +Async Tornado Client Example +================================================== +.. literalinclude:: ../../../examples/common/async_tornado_client.py \ No newline at end of file diff -Nru pymodbus-1.5.2+dfsg/doc/source/example/async_tornado_client_serial.rst pymodbus-2.1.0+dfsg/doc/source/example/async_tornado_client_serial.rst --- pymodbus-1.5.2+dfsg/doc/source/example/async_tornado_client_serial.rst 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/doc/source/example/async_tornado_client_serial.rst 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,4 @@ +================================================== +Async Tornado Client Serial Example +================================================== +.. literalinclude:: ../../../examples/common/async_tornado_client_serial.py \ No newline at end of file diff -Nru pymodbus-1.5.2+dfsg/doc/source/example/async_twisted_client.rst pymodbus-2.1.0+dfsg/doc/source/example/async_twisted_client.rst --- pymodbus-1.5.2+dfsg/doc/source/example/async_twisted_client.rst 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/doc/source/example/async_twisted_client.rst 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,4 @@ +================================================== +Async Twisted Client Example +================================================== +.. literalinclude:: ../../../examples/common/async_twisted_client.py \ No newline at end of file diff -Nru pymodbus-1.5.2+dfsg/doc/source/example/async_twisted_client_serial.rst pymodbus-2.1.0+dfsg/doc/source/example/async_twisted_client_serial.rst --- pymodbus-1.5.2+dfsg/doc/source/example/async_twisted_client_serial.rst 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/doc/source/example/async_twisted_client_serial.rst 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,4 @@ +================================================== +Async Twisted Client Serial Example +================================================== +.. literalinclude:: ../../../examples/common/async_twisted_client_serial.py \ No newline at end of file diff -Nru pymodbus-1.5.2+dfsg/doc/source/example/callback_server.rst pymodbus-2.1.0+dfsg/doc/source/example/callback_server.rst --- pymodbus-1.5.2+dfsg/doc/source/example/callback_server.rst 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/doc/source/example/callback_server.rst 2018-10-03 12:44:47.000000000 +0000 @@ -1,4 +1,4 @@ -================================================== +=========================== Callback Server Example -================================================== +=========================== .. literalinclude:: ../../../examples/common/callback_server.py \ No newline at end of file diff -Nru pymodbus-1.5.2+dfsg/doc/source/example/modules.rst pymodbus-2.1.0+dfsg/doc/source/example/modules.rst --- pymodbus-1.5.2+dfsg/doc/source/example/modules.rst 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/doc/source/example/modules.rst 2018-10-03 12:44:47.000000000 +0000 @@ -6,7 +6,12 @@ .. toctree:: :maxdepth: 4 - asynchronous_client + 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 @@ -22,6 +27,7 @@ synchronous_client_ext synchronous_server updating_server + asynchronous_asyncio_serial_client bcd_payload concurrent_client libmodbus_client diff -Nru pymodbus-1.5.2+dfsg/doc/source/example/performance.rst pymodbus-2.1.0+dfsg/doc/source/example/performance.rst --- pymodbus-1.5.2+dfsg/doc/source/example/performance.rst 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/doc/source/example/performance.rst 2018-10-03 12:44:47.000000000 +0000 @@ -2,3 +2,4 @@ performance module ================== .. literalinclude:: ../../../examples/common/performance.py + diff -Nru pymodbus-1.5.2+dfsg/doc/source/library/pymodbus.client.async.asyncio.rst pymodbus-2.1.0+dfsg/doc/source/library/pymodbus.client.async.asyncio.rst --- pymodbus-1.5.2+dfsg/doc/source/library/pymodbus.client.async.asyncio.rst 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/doc/source/library/pymodbus.client.async.asyncio.rst 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,8 @@ +pymodbus\.client\.async\.asyncio package +======================================== + +.. automodule:: pymodbus.client.async.asyncio + :members: + :undoc-members: + :show-inheritance: + diff -Nru pymodbus-1.5.2+dfsg/doc/source/library/pymodbus.client.async.factory.rst pymodbus-2.1.0+dfsg/doc/source/library/pymodbus.client.async.factory.rst --- pymodbus-1.5.2+dfsg/doc/source/library/pymodbus.client.async.factory.rst 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/doc/source/library/pymodbus.client.async.factory.rst 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,36 @@ +pymodbus\.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: + + diff -Nru pymodbus-1.5.2+dfsg/doc/source/library/pymodbus.client.async.rst pymodbus-2.1.0+dfsg/doc/source/library/pymodbus.client.async.rst --- pymodbus-1.5.2+dfsg/doc/source/library/pymodbus.client.async.rst 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/doc/source/library/pymodbus.client.async.rst 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,55 @@ +pymodbus\.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: + + diff -Nru pymodbus-1.5.2+dfsg/doc/source/library/pymodbus.client.async.schedulers.rst pymodbus-2.1.0+dfsg/doc/source/library/pymodbus.client.async.schedulers.rst --- pymodbus-1.5.2+dfsg/doc/source/library/pymodbus.client.async.schedulers.rst 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/doc/source/library/pymodbus.client.async.schedulers.rst 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,8 @@ +pymodbus\.client\.async\.schedulers package +=========================================== + +.. automodule:: pymodbus.client.async.schedulers + :members: + :undoc-members: + :show-inheritance: + diff -Nru pymodbus-1.5.2+dfsg/doc/source/library/pymodbus.client.async.tornado.rst pymodbus-2.1.0+dfsg/doc/source/library/pymodbus.client.async.tornado.rst --- pymodbus-1.5.2+dfsg/doc/source/library/pymodbus.client.async.tornado.rst 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/doc/source/library/pymodbus.client.async.tornado.rst 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,8 @@ +pymodbus\.client\.async\.tornado package +======================================== + +.. automodule:: pymodbus.client.async.tornado + :members: + :undoc-members: + :show-inheritance: + diff -Nru pymodbus-1.5.2+dfsg/doc/source/library/pymodbus.client.async.twisted.rst pymodbus-2.1.0+dfsg/doc/source/library/pymodbus.client.async.twisted.rst --- pymodbus-1.5.2+dfsg/doc/source/library/pymodbus.client.async.twisted.rst 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/doc/source/library/pymodbus.client.async.twisted.rst 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,8 @@ +pymodbus\.client\.async\.twisted package +======================================== + +.. automodule:: pymodbus.client.async.twisted + :members: + :undoc-members: + :show-inheritance: + diff -Nru pymodbus-1.5.2+dfsg/doc/source/library/pymodbus.client.rst pymodbus-2.1.0+dfsg/doc/source/library/pymodbus.client.rst --- pymodbus-1.5.2+dfsg/doc/source/library/pymodbus.client.rst 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/doc/source/library/pymodbus.client.rst 2018-10-03 12:44:47.000000000 +0000 @@ -1,17 +1,21 @@ pymodbus\.client package ======================== -Submodules ----------- - -pymodbus\.client\.async module ------------------------------- - -.. automodule:: pymodbus.client.async +.. automodule:: pymodbus.client :members: :undoc-members: :show-inheritance: +Subpackages +----------- + +.. toctree:: + + pymodbus.client.async + +Submodules +---------- + pymodbus\.client\.common module ------------------------------- @@ -29,10 +33,3 @@ :show-inheritance: -Module contents ---------------- - -.. automodule:: pymodbus.client - :members: - :undoc-members: - :show-inheritance: diff -Nru pymodbus-1.5.2+dfsg/doc/source/library/pymodbus.datastore.database.rst pymodbus-2.1.0+dfsg/doc/source/library/pymodbus.datastore.database.rst --- pymodbus-1.5.2+dfsg/doc/source/library/pymodbus.datastore.database.rst 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/doc/source/library/pymodbus.datastore.database.rst 2018-10-03 12:44:47.000000000 +0000 @@ -1,6 +1,11 @@ pymodbus\.datastore\.database package ===================================== +.. automodule:: pymodbus.datastore.database + :members: + :undoc-members: + :show-inheritance: + Submodules ---------- @@ -21,10 +26,3 @@ :show-inheritance: -Module contents ---------------- - -.. automodule:: pymodbus.datastore.database - :members: - :undoc-members: - :show-inheritance: diff -Nru pymodbus-1.5.2+dfsg/doc/source/library/pymodbus.datastore.rst pymodbus-2.1.0+dfsg/doc/source/library/pymodbus.datastore.rst --- pymodbus-1.5.2+dfsg/doc/source/library/pymodbus.datastore.rst 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/doc/source/library/pymodbus.datastore.rst 2018-10-03 12:44:47.000000000 +0000 @@ -1,6 +1,11 @@ pymodbus\.datastore package =========================== +.. automodule:: pymodbus.datastore + :members: + :undoc-members: + :show-inheritance: + Subpackages ----------- @@ -36,10 +41,3 @@ :show-inheritance: -Module contents ---------------- - -.. automodule:: pymodbus.datastore - :members: - :undoc-members: - :show-inheritance: diff -Nru pymodbus-1.5.2+dfsg/doc/source/library/pymodbus.internal.rst pymodbus-2.1.0+dfsg/doc/source/library/pymodbus.internal.rst --- pymodbus-1.5.2+dfsg/doc/source/library/pymodbus.internal.rst 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/doc/source/library/pymodbus.internal.rst 2018-10-03 12:44:47.000000000 +0000 @@ -1,6 +1,11 @@ pymodbus\.internal package ========================== +.. automodule:: pymodbus.internal + :members: + :undoc-members: + :show-inheritance: + Submodules ---------- @@ -13,10 +18,3 @@ :show-inheritance: -Module contents ---------------- - -.. automodule:: pymodbus.internal - :members: - :undoc-members: - :show-inheritance: diff -Nru pymodbus-1.5.2+dfsg/doc/source/library/pymodbus.repl.rst pymodbus-2.1.0+dfsg/doc/source/library/pymodbus.repl.rst --- pymodbus-1.5.2+dfsg/doc/source/library/pymodbus.repl.rst 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/doc/source/library/pymodbus.repl.rst 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,44 @@ +pymodbus\.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: diff -Nru pymodbus-1.5.2+dfsg/doc/source/library/pymodbus.rst pymodbus-2.1.0+dfsg/doc/source/library/pymodbus.rst --- pymodbus-1.5.2+dfsg/doc/source/library/pymodbus.rst 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/doc/source/library/pymodbus.rst 2018-10-03 12:44:47.000000000 +0000 @@ -1,6 +1,11 @@ -Pymodbus package +pymodbus package ================ +.. automodule:: pymodbus + :members: + :undoc-members: + :show-inheritance: + Subpackages ----------- @@ -11,6 +16,7 @@ pymodbus.framer pymodbus.internal pymodbus.server + pymodbus.repl Submodules @@ -177,10 +183,3 @@ :show-inheritance: -Module contents ---------------- - -.. automodule:: pymodbus - :members: - :undoc-members: - :show-inheritance: diff -Nru pymodbus-1.5.2+dfsg/doc/source/library/pymodbus.server.rst pymodbus-2.1.0+dfsg/doc/source/library/pymodbus.server.rst --- pymodbus-1.5.2+dfsg/doc/source/library/pymodbus.server.rst 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/doc/source/library/pymodbus.server.rst 2018-10-03 12:44:47.000000000 +0000 @@ -1,6 +1,11 @@ pymodbus\.server package ======================== +.. automodule:: pymodbus.server + :members: + :undoc-members: + :show-inheritance: + Submodules ---------- @@ -21,10 +26,3 @@ :show-inheritance: -Module contents ---------------- - -.. automodule:: pymodbus.server - :members: - :undoc-members: - :show-inheritance: diff -Nru pymodbus-1.5.2+dfsg/doc/source/library/REPL.md pymodbus-2.1.0+dfsg/doc/source/library/REPL.md --- pymodbus-1.5.2+dfsg/doc/source/library/REPL.md 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/doc/source/library/REPL.md 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,280 @@ +# 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) diff -Nru pymodbus-1.5.2+dfsg/examples/common/async_asyncio_client.py pymodbus-2.1.0+dfsg/examples/common/async_asyncio_client.py --- pymodbus-1.5.2+dfsg/examples/common/async_asyncio_client.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/common/async_asyncio_client.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,211 @@ +#!/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("") diff -Nru pymodbus-1.5.2+dfsg/examples/common/async_asyncio_serial_client.py pymodbus-2.1.0+dfsg/examples/common/async_asyncio_serial_client.py --- pymodbus-1.5.2+dfsg/examples/common/async_asyncio_serial_client.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/common/async_asyncio_serial_client.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,143 @@ +#!/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() + diff -Nru pymodbus-1.5.2+dfsg/examples/common/asynchronous_client.py pymodbus-2.1.0+dfsg/examples/common/asynchronous_client.py --- pymodbus-1.5.2+dfsg/examples/common/asynchronous_client.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/common/asynchronous_client.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,163 +0,0 @@ -#!/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, protocol -from pymodbus.constants import Defaults - -# --------------------------------------------------------------------------- # -# choose the requested modbus protocol -# --------------------------------------------------------------------------- # -from pymodbus.client.async import ModbusClientProtocol -from pymodbus.client.async import ModbusUdpClientProtocol -from pymodbus.framer.rtu_framer import ModbusRtuFramer - -# --------------------------------------------------------------------------- # -# 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 dassert(deferred, callback): - def _assertor(value): - assert value - deferred.addCallback(lambda r: _assertor(callback(r))) - deferred.addErrback(lambda _: _assertor(False)) - -# --------------------------------------------------------------------------- # -# 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` -# --------------------------------------------------------------------------- # - - -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). -# --------------------------------------------------------------------------- # - - -UNIT = 0x00 - - -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) - - -# --------------------------------------------------------------------------- # -# 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__": - defer = protocol.ClientCreator( - reactor, ModbusClientProtocol).connectTCP("localhost", 5020) - - # TCP server with a different framer - - # defer = protocol.ClientCreator( - # reactor, ModbusClientProtocol, framer=ModbusRtuFramer).connectTCP( - # "localhost", 5020) - defer.addCallback(beginAsynchronousTest) - reactor.run() diff -Nru pymodbus-1.5.2+dfsg/examples/common/asynchronous_processor.py pymodbus-2.1.0+dfsg/examples/common/asynchronous_processor.py --- pymodbus-1.5.2+dfsg/examples/common/asynchronous_processor.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/common/asynchronous_processor.py 2018-10-03 12:44:47.000000000 +0000 @@ -12,13 +12,13 @@ from twisted.internet import serialport, reactor from twisted.internet.protocol import ClientFactory from pymodbus.factory import ClientDecoder -from pymodbus.client.async import ModbusClientProtocol +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 ModbusBinaryFramer as ModbusFramer +# from pymodbus.transaction import ModbusAsciiFramer as ModbusFramer from pymodbus.transaction import ModbusRtuFramer as ModbusFramer # from pymodbus.transaction import ModbusSocketFramer as ModbusFramer @@ -35,12 +35,14 @@ # --------------------------------------------------------------------------- # # 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 # --------------------------------------------------------------------------- # diff -Nru pymodbus-1.5.2+dfsg/examples/common/async_tornado_client.py pymodbus-2.1.0+dfsg/examples/common/async_tornado_client.py --- pymodbus-1.5.2+dfsg/examples/common/async_tornado_client.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/common/async_tornado_client.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,149 @@ +#!/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)) + + + diff -Nru pymodbus-1.5.2+dfsg/examples/common/async_tornado_client_serial.py pymodbus-2.1.0+dfsg/examples/common/async_tornado_client_serial.py --- pymodbus-1.5.2+dfsg/examples/common/async_tornado_client_serial.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/common/async_tornado_client_serial.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,179 @@ +#!/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)) diff -Nru pymodbus-1.5.2+dfsg/examples/common/async_twisted_client.py pymodbus-2.1.0+dfsg/examples/common/async_twisted_client.py --- pymodbus-1.5.2+dfsg/examples/common/async_twisted_client.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/common/async_twisted_client.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,172 @@ +#!/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) diff -Nru pymodbus-1.5.2+dfsg/examples/common/async_twisted_client_serial.py pymodbus-2.1.0+dfsg/examples/common/async_twisted_client_serial.py --- pymodbus-1.5.2+dfsg/examples/common/async_twisted_client_serial.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/common/async_twisted_client_serial.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,86 @@ +#!/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() + + diff -Nru pymodbus-1.5.2+dfsg/examples/common/custom_message.py pymodbus-2.1.0+dfsg/examples/common/custom_message.py --- pymodbus-1.5.2+dfsg/examples/common/custom_message.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/common/custom_message.py 2018-10-03 12:44:47.000000000 +0000 @@ -1,6 +1,6 @@ #!/usr/bin/env python """ -Pymodbus Synchrnonous Client Examples +Pymodbus Synchronous Client Examples -------------------------------------------------------------------------- The following is an example of how to use the synchronous modbus client diff -Nru pymodbus-1.5.2+dfsg/examples/common/performance.py pymodbus-2.1.0+dfsg/examples/common/performance.py --- pymodbus-1.5.2+dfsg/examples/common/performance.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/common/performance.py 2018-10-03 12:44:47.000000000 +0000 @@ -8,7 +8,7 @@ """ # --------------------------------------------------------------------------- # # import the necessary modules -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # from __future__ import print_function import logging, os from time import time diff -Nru pymodbus-1.5.2+dfsg/examples/common/synchronous_client.py pymodbus-2.1.0+dfsg/examples/common/synchronous_client.py --- pymodbus-1.5.2+dfsg/examples/common/synchronous_client.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/common/synchronous_client.py 2018-10-03 12:44:47.000000000 +0000 @@ -16,9 +16,9 @@ # --------------------------------------------------------------------------- # # import the various server implementations # --------------------------------------------------------------------------- # -from pymodbus.client.sync import ModbusTcpClient as ModbusClient +# from pymodbus.client.sync import ModbusTcpClient as ModbusClient # from pymodbus.client.sync import ModbusUdpClient as ModbusClient -# from pymodbus.client.sync import ModbusSerialClient as ModbusClient +from pymodbus.client.sync import ModbusSerialClient as ModbusClient # --------------------------------------------------------------------------- # # configure the client logging diff -Nru pymodbus-1.5.2+dfsg/examples/common/synchronous_server.py pymodbus-2.1.0+dfsg/examples/common/synchronous_server.py --- pymodbus-1.5.2+dfsg/examples/common/synchronous_server.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/common/synchronous_server.py 2018-10-03 12:44:47.000000000 +0000 @@ -6,11 +6,11 @@ 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 just is not feasable. What follows is an examle of its use: +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 @@ -20,9 +20,9 @@ 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') @@ -32,9 +32,9 @@ 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 @@ -58,7 +58,7 @@ # 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. + # 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:: # @@ -85,7 +85,7 @@ # will map to (1-8):: # # store = ModbusSlaveContext(..., zero_mode=True) - # ----------------------------------------------------------------------- # + # ----------------------------------------------------------------------- # store = ModbusSlaveContext( di=ModbusSequentialDataBlock(0, [17]*100), co=ModbusSequentialDataBlock(0, [17]*100), @@ -93,12 +93,12 @@ 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' @@ -109,7 +109,7 @@ # ----------------------------------------------------------------------- # # run the server you want - # ----------------------------------------------------------------------- # + # ----------------------------------------------------------------------- # # Tcp: StartTcpServer(context, identity=identity, address=("localhost", 5020)) @@ -119,11 +119,11 @@ # 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) diff -Nru pymodbus-1.5.2+dfsg/examples/contrib/asynchronous_asyncio_serial_client.py pymodbus-2.1.0+dfsg/examples/contrib/asynchronous_asyncio_serial_client.py --- pymodbus-1.5.2+dfsg/examples/contrib/asynchronous_asyncio_serial_client.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/contrib/asynchronous_asyncio_serial_client.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,121 @@ +from 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() diff -Nru pymodbus-1.5.2+dfsg/examples/contrib/modbus_scraper.py pymodbus-2.1.0+dfsg/examples/contrib/modbus_scraper.py --- pymodbus-1.5.2+dfsg/examples/contrib/modbus_scraper.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/contrib/modbus_scraper.py 2018-10-03 12:44:47.000000000 +0000 @@ -11,11 +11,11 @@ from pymodbus.datastore import ModbusSequentialDataBlock from pymodbus.datastore import ModbusSlaveContext from pymodbus.factory import ClientDecoder -from pymodbus.client.async import ModbusClientProtocol +from pymodbus.client.async.twisted import ModbusClientProtocol -#--------------------------------------------------------------------------# +# -------------------------------------------------------------------------- # # Configure the client logging -#--------------------------------------------------------------------------# +# -------------------------------------------------------------------------- # import logging log = logging.getLogger("pymodbus") @@ -32,14 +32,16 @@ # --------------------------------------------------------------------------- # 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 +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 @@ -114,7 +116,8 @@ if self.address >= self.factory.ending: self.endpoint.finalize() self.transport.loseConnection() - else: reactor.callLater(DELAY, self.scrape_holding_registers) + else: + reactor.callLater(DELAY, self.scrape_holding_registers) def error_handler(self, failure): """ Handle any twisted errors diff -Nru pymodbus-1.5.2+dfsg/examples/contrib/remote_server_context.py pymodbus-2.1.0+dfsg/examples/contrib/remote_server_context.py --- pymodbus-1.5.2+dfsg/examples/contrib/remote_server_context.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/contrib/remote_server_context.py 2018-10-03 12:44:47.000000000 +0000 @@ -73,7 +73,7 @@ return not result.isError() def getValues(self, fx, address, count=1): - """ Validates the request to make sure it is in range + """ Get `count` values from datastore :param fx: The function we are working with :param address: The starting address @@ -118,7 +118,8 @@ return result.bits if fx in ['h', 'i']: return result.registers - else: return result + else: + return result # -------------------------------------------------------------------------- # # Server Context @@ -152,7 +153,7 @@ 'i': lambda a, v, s: client.write_registers(a, v, s), } self._client = client - self.slaves = {} # simply a cache + self.slaves = {} # simply a cache def __str__(self): """ Returns a string representation of the context @@ -187,14 +188,14 @@ :param slave: The slave context to set :param context: The new context to set for this slave """ - raise NotImplementedException() # doesn't make sense here + 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 + raise NotImplementedException() # doesn't make sense here def __getitem__(self, slave): """ Used to get access to a slave context diff -Nru pymodbus-1.5.2+dfsg/examples/contrib/sunspec_client.py pymodbus-2.1.0+dfsg/examples/contrib/sunspec_client.py --- pymodbus-1.5.2+dfsg/examples/contrib/sunspec_client.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/contrib/sunspec_client.py 2018-10-03 12:44:47.000000000 +0000 @@ -4,9 +4,9 @@ from twisted.internet.defer import Deferred -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # # Logging -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # import logging _logger = logging.getLogger(__name__) _logger.setLevel(logging.DEBUG) diff -Nru pymodbus-1.5.2+dfsg/examples/functional/asynchronous_ascii_client.py pymodbus-2.1.0+dfsg/examples/functional/asynchronous_ascii_client.py --- pymodbus-1.5.2+dfsg/examples/functional/asynchronous_ascii_client.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/functional/asynchronous_ascii_client.py 2018-10-03 12:44:47.000000000 +0000 @@ -19,8 +19,8 @@ self.client.close() self.shutdown() -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # # Main -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # if __name__ == "__main__": unittest.main() diff -Nru pymodbus-1.5.2+dfsg/examples/functional/asynchronous_rtu_client.py pymodbus-2.1.0+dfsg/examples/functional/asynchronous_rtu_client.py --- pymodbus-1.5.2+dfsg/examples/functional/asynchronous_rtu_client.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/functional/asynchronous_rtu_client.py 2018-10-03 12:44:47.000000000 +0000 @@ -19,8 +19,8 @@ self.client.close() super(Runner, self).tearDown() -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # # Main -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # if __name__ == "__main__": unittest.main() diff -Nru pymodbus-1.5.2+dfsg/examples/functional/asynchronous_tcp_client.py pymodbus-2.1.0+dfsg/examples/functional/asynchronous_tcp_client.py --- pymodbus-1.5.2+dfsg/examples/functional/asynchronous_tcp_client.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/functional/asynchronous_tcp_client.py 2018-10-03 12:44:47.000000000 +0000 @@ -26,8 +26,8 @@ reactor.callLater(2, reactor.stop) reactor.shutdown() -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # # Main -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # if __name__ == "__main__": unittest.main() diff -Nru pymodbus-1.5.2+dfsg/examples/functional/asynchronous_udp_client.py pymodbus-2.1.0+dfsg/examples/functional/asynchronous_udp_client.py --- pymodbus-1.5.2+dfsg/examples/functional/asynchronous_udp_client.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/functional/asynchronous_udp_client.py 2018-10-03 12:44:47.000000000 +0000 @@ -19,8 +19,8 @@ self.client.close() super(Runner, self).tearDown() -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # # Main -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # if __name__ == "__main__": unittest.main() diff -Nru pymodbus-1.5.2+dfsg/examples/functional/database_slave_context.py pymodbus-2.1.0+dfsg/examples/functional/database_slave_context.py --- pymodbus-1.5.2+dfsg/examples/functional/database_slave_context.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/functional/database_slave_context.py 2018-10-03 12:44:47.000000000 +0000 @@ -22,8 +22,8 @@ self.context._connection.close() self.shutdown() -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # # Main -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # if __name__ == "__main__": unittest.main() diff -Nru pymodbus-1.5.2+dfsg/examples/functional/memory_slave_context.py pymodbus-2.1.0+dfsg/examples/functional/memory_slave_context.py --- pymodbus-1.5.2+dfsg/examples/functional/memory_slave_context.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/functional/memory_slave_context.py 2018-10-03 12:44:47.000000000 +0000 @@ -23,8 +23,8 @@ """ Cleans up the test environment """ self.shutdown() -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # # Main -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # if __name__ == "__main__": unittest.main() diff -Nru pymodbus-1.5.2+dfsg/examples/functional/redis_slave_context.py pymodbus-2.1.0+dfsg/examples/functional/redis_slave_context.py --- pymodbus-1.5.2+dfsg/examples/functional/redis_slave_context.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/functional/redis_slave_context.py 2018-10-03 12:44:47.000000000 +0000 @@ -22,8 +22,8 @@ self.fnull.close() self.shutdown() -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # # Main -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # if __name__ == "__main__": unittest.main() diff -Nru pymodbus-1.5.2+dfsg/examples/functional/remote_slave_context.py pymodbus-2.1.0+dfsg/examples/functional/remote_slave_context.py --- pymodbus-1.5.2+dfsg/examples/functional/remote_slave_context.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/functional/remote_slave_context.py 2018-10-03 12:44:47.000000000 +0000 @@ -22,8 +22,8 @@ self.client.close() self.shutdown() -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # # Main -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # if __name__ == "__main__": unittest.main() diff -Nru pymodbus-1.5.2+dfsg/examples/functional/synchronous_ascii_client.py pymodbus-2.1.0+dfsg/examples/functional/synchronous_ascii_client.py --- pymodbus-1.5.2+dfsg/examples/functional/synchronous_ascii_client.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/functional/synchronous_ascii_client.py 2018-10-03 12:44:47.000000000 +0000 @@ -22,8 +22,8 @@ self.client.close() super(Runner, self).tearDown() -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # # Main -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # if __name__ == "__main__": unittest.main() diff -Nru pymodbus-1.5.2+dfsg/examples/functional/synchronous_rtu_client.py pymodbus-2.1.0+dfsg/examples/functional/synchronous_rtu_client.py --- pymodbus-1.5.2+dfsg/examples/functional/synchronous_rtu_client.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/functional/synchronous_rtu_client.py 2018-10-03 12:44:47.000000000 +0000 @@ -21,8 +21,8 @@ self.client.close() super(Runner, self).tearDown() -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # # Main -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # if __name__ == "__main__": unittest.main() diff -Nru pymodbus-1.5.2+dfsg/examples/functional/synchronous_tcp_client.py pymodbus-2.1.0+dfsg/examples/functional/synchronous_tcp_client.py --- pymodbus-1.5.2+dfsg/examples/functional/synchronous_tcp_client.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/functional/synchronous_tcp_client.py 2018-10-03 12:44:47.000000000 +0000 @@ -19,8 +19,8 @@ self.client.close() self.shutdown() -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # # Main -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # if __name__ == "__main__": unittest.main() diff -Nru pymodbus-1.5.2+dfsg/examples/functional/synchronous_udp_client.py pymodbus-2.1.0+dfsg/examples/functional/synchronous_udp_client.py --- pymodbus-1.5.2+dfsg/examples/functional/synchronous_udp_client.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/functional/synchronous_udp_client.py 2018-10-03 12:44:47.000000000 +0000 @@ -19,8 +19,8 @@ self.client.close() super(Runner, self).tearDown() -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # # Main -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # if __name__ == "__main__": unittest.main() diff -Nru pymodbus-1.5.2+dfsg/examples/gui/gtk/simulator.py pymodbus-2.1.0+dfsg/examples/gui/gtk/simulator.py --- pymodbus-1.5.2+dfsg/examples/gui/gtk/simulator.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/gui/gtk/simulator.py 2018-10-03 12:44:47.000000000 +0000 @@ -1,23 +1,23 @@ #!/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 @@ -29,9 +29,9 @@ import logging log = logging.getLogger(__name__) -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # # Application Error -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # class ConfigurationException(Exception): """ Exception for configuration error """ @@ -42,18 +42,18 @@ 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 @@ -104,12 +104,12 @@ """ 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 @@ -124,12 +124,12 @@ """ 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) @@ -151,7 +151,7 @@ # 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 @@ -164,9 +164,9 @@ 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") @@ -176,9 +176,9 @@ 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, @@ -190,11 +190,11 @@ 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: @@ -225,11 +225,11 @@ 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 @@ -294,12 +294,12 @@ """ 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 @@ -315,10 +315,10 @@ simulator = SimulatorApp('./simulator.glade') reactor.run() -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # # Library/Console Test -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # # If this is called from console, we start main -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # if __name__ == "__main__": main() diff -Nru pymodbus-1.5.2+dfsg/examples/gui/tk/simulator.py pymodbus-2.1.0+dfsg/examples/gui/tk/simulator.py --- pymodbus-1.5.2+dfsg/examples/gui/tk/simulator.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/gui/tk/simulator.py 2018-10-03 12:44:47.000000000 +0000 @@ -2,26 +2,26 @@ """ Note that this is not finished """ -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # # System -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # import os import getpass import pickle from threading import Thread -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # # For Gui -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # from Tkinter import * from tkFileDialog import askopenfilename as OpenFilename from twisted.internet import tksupport root = Tk() tksupport.install(root) -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # # SNMP Simulator -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # from twisted.internet import reactor from twisted.internet import error as twisted_error from pymodbus.server.async import ModbusServerFactory @@ -33,25 +33,25 @@ 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 @@ -102,12 +102,12 @@ """ 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 @@ -122,9 +122,9 @@ """ Run the network reset """ os.system("/etc/init.d/networking restart") -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # # Main Gui Class -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # class SimulatorFrame(Frame): """ This class implements the GUI for the flasher application @@ -138,9 +138,9 @@ Frame.__init__(self, master) self._widgets = [] - #---------------------------------------------------------------------------# + # --------------------------------------------------------------------------- # # Initialize Buttons Handles - #---------------------------------------------------------------------------# + # --------------------------------------------------------------------------- # frame = Frame(self) frame.pack(side=BOTTOM, pady=5) @@ -156,9 +156,9 @@ button.pack(side=LEFT, padx=15) #self._widgets.append(button) # we don't want to grey this out - #---------------------------------------------------------------------------# + # --------------------------------------------------------------------------- # # Initialize Input Fields - #---------------------------------------------------------------------------# + # --------------------------------------------------------------------------- # frame = Frame(self) frame.pack(side=TOP, padx=10, pady=5) @@ -192,11 +192,11 @@ #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): """ Greys out the buttons """ state = 'active' if state else 'disabled' @@ -223,11 +223,11 @@ 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): """ Starts the simulator """ start = 1 @@ -308,12 +308,12 @@ 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 @@ -330,10 +330,10 @@ root.title("Modbus Simulator") reactor.run() -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # # Library/Console Test -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # # If this is called from console, we start main -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # if __name__ == "__main__": main() diff -Nru pymodbus-1.5.2+dfsg/examples/gui/wx/simulator.py pymodbus-2.1.0+dfsg/examples/gui/wx/simulator.py --- pymodbus-1.5.2+dfsg/examples/gui/wx/simulator.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/examples/gui/wx/simulator.py 2018-10-03 12:44:47.000000000 +0000 @@ -2,24 +2,24 @@ """ 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 @@ -31,25 +31,25 @@ 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 @@ -100,12 +100,12 @@ """ 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 @@ -121,9 +121,9 @@ """ 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 @@ -139,9 +139,9 @@ 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) @@ -149,16 +149,16 @@ 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) @@ -166,11 +166,11 @@ #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: @@ -195,11 +195,11 @@ 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 @@ -281,12 +281,12 @@ 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 @@ -302,10 +302,10 @@ simulator = SimulatorApp(0) reactor.run() -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # # Library/Console Test -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # # If this is called from console, we start main -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # if __name__ == "__main__": main() diff -Nru pymodbus-1.5.2+dfsg/.gitignore pymodbus-2.1.0+dfsg/.gitignore --- pymodbus-1.5.2+dfsg/.gitignore 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/.gitignore 2018-10-03 12:44:47.000000000 +0000 @@ -22,6 +22,11 @@ test/__pycache__/ **/pymodbus.db /.eggs/ +/test/bin/ +/test/include/ +/test/lib/ +/test/pip-selfcheck.json +/test/.Python /.cache/ /doc/sphinx/doctrees/ /doc_new/ @@ -31,3 +36,4 @@ /doc/html/ /doc/_build/ .pytest_cache/ +**/.pymodhis diff -Nru pymodbus-1.5.2+dfsg/Makefile pymodbus-2.1.0+dfsg/Makefile --- pymodbus-1.5.2+dfsg/Makefile 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/Makefile 2018-10-03 12:44:47.000000000 +0000 @@ -39,7 +39,7 @@ test: install @pip install --quiet --requirement=requirements-tests.txt - @py.test + @pytest --cov=pymodbus/ --cov-report term-missing @coverage report --fail-under=90 tox: install @@ -47,7 +47,7 @@ docs: install @pip install --quiet --requirement=requirements-docs.txt - @cd doc && make html + @cd doc && make clean && make html publish: install git push origin && git push --tags origin diff -Nru pymodbus-1.5.2+dfsg/MANIFEST.in pymodbus-2.1.0+dfsg/MANIFEST.in --- pymodbus-1.5.2+dfsg/MANIFEST.in 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/MANIFEST.in 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,3 @@ +include requirements.txt +include README.rst +include CHANGELOG.rst \ No newline at end of file diff -Nru pymodbus-1.5.2+dfsg/pymodbus/client/async/asyncio/__init__.py pymodbus-2.1.0+dfsg/pymodbus/client/async/asyncio/__init__.py --- pymodbus-1.5.2+dfsg/pymodbus/client/async/asyncio/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/client/async/asyncio/__init__.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,785 @@ +""" +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 diff -Nru pymodbus-1.5.2+dfsg/pymodbus/client/async/deprecated/async.py pymodbus-2.1.0+dfsg/pymodbus/client/async/deprecated/async.py --- pymodbus-1.5.2+dfsg/pymodbus/client/async/deprecated/async.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/client/async/deprecated/async.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,231 @@ +""" +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" +] diff -Nru pymodbus-1.5.2+dfsg/pymodbus/client/async/deprecated/__init__.py pymodbus-2.1.0+dfsg/pymodbus/client/async/deprecated/__init__.py --- pymodbus-1.5.2+dfsg/pymodbus/client/async/deprecated/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/client/async/deprecated/__init__.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,47 @@ +import 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) diff -Nru pymodbus-1.5.2+dfsg/pymodbus/client/async/factory/__init__.py pymodbus-2.1.0+dfsg/pymodbus/client/async/factory/__init__.py --- pymodbus-1.5.2+dfsg/pymodbus/client/async/factory/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/client/async/factory/__init__.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1 @@ +from __future__ import absolute_import, unicode_literals \ No newline at end of file diff -Nru pymodbus-1.5.2+dfsg/pymodbus/client/async/factory/serial.py pymodbus-2.1.0+dfsg/pymodbus/client/async/factory/serial.py --- pymodbus-1.5.2+dfsg/pymodbus/client/async/factory/serial.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/client/async/factory/serial.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,126 @@ +""" +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)) diff -Nru pymodbus-1.5.2+dfsg/pymodbus/client/async/factory/tcp.py pymodbus-2.1.0+dfsg/pymodbus/client/async/factory/tcp.py --- pymodbus-1.5.2+dfsg/pymodbus/client/async/factory/tcp.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/client/async/factory/tcp.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,124 @@ +""" +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)) diff -Nru pymodbus-1.5.2+dfsg/pymodbus/client/async/factory/udp.py pymodbus-2.1.0+dfsg/pymodbus/client/async/factory/udp.py --- pymodbus-1.5.2+dfsg/pymodbus/client/async/factory/udp.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/client/async/factory/udp.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,93 @@ +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 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)) + diff -Nru pymodbus-1.5.2+dfsg/pymodbus/client/async/__init__.py pymodbus-2.1.0+dfsg/pymodbus/client/async/__init__.py --- pymodbus-1.5.2+dfsg/pymodbus/client/async/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/client/async/__init__.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,44 @@ +""" +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") diff -Nru pymodbus-1.5.2+dfsg/pymodbus/client/async/mixins.py pymodbus-2.1.0+dfsg/pymodbus/client/async/mixins.py --- pymodbus-1.5.2+dfsg/pymodbus/client/async/mixins.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/client/async/mixins.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,69 @@ +import 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 + diff -Nru pymodbus-1.5.2+dfsg/pymodbus/client/async/schedulers/__init__.py pymodbus-2.1.0+dfsg/pymodbus/client/async/schedulers/__init__.py --- pymodbus-1.5.2+dfsg/pymodbus/client/async/schedulers/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/client/async/schedulers/__init__.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,9 @@ +""" +Backend schedulers to use with generic Async clients +""" +from __future__ import unicode_literals + + +REACTOR = "reactor" +IO_LOOP = "io_loop" +ASYNC_IO = "async_io" diff -Nru pymodbus-1.5.2+dfsg/pymodbus/client/async/serial.py pymodbus-2.1.0+dfsg/pymodbus/client/async/serial.py --- pymodbus-1.5.2+dfsg/pymodbus/client/async/serial.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/client/async/serial.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,76 @@ +from __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 diff -Nru pymodbus-1.5.2+dfsg/pymodbus/client/async/tcp.py pymodbus-2.1.0+dfsg/pymodbus/client/async/tcp.py --- pymodbus-1.5.2+dfsg/pymodbus/client/async/tcp.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/client/async/tcp.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,47 @@ +from __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 + diff -Nru pymodbus-1.5.2+dfsg/pymodbus/client/async/thread.py pymodbus-2.1.0+dfsg/pymodbus/client/async/thread.py --- pymodbus-1.5.2+dfsg/pymodbus/client/async/thread.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/client/async/thread.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,53 @@ +from __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() diff -Nru pymodbus-1.5.2+dfsg/pymodbus/client/async/tornado/__init__.py pymodbus-2.1.0+dfsg/pymodbus/client/async/tornado/__init__.py --- pymodbus-1.5.2+dfsg/pymodbus/client/async/tornado/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/client/async/tornado/__init__.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,341 @@ +""" +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) \ No newline at end of file diff -Nru pymodbus-1.5.2+dfsg/pymodbus/client/async/twisted/__init__.py pymodbus-2.1.0+dfsg/pymodbus/client/async/twisted/__init__.py --- pymodbus-1.5.2+dfsg/pymodbus/client/async/twisted/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/client/async/twisted/__init__.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,251 @@ +""" +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" +] + diff -Nru pymodbus-1.5.2+dfsg/pymodbus/client/async/udp.py pymodbus-2.1.0+dfsg/pymodbus/client/async/udp.py --- pymodbus-1.5.2+dfsg/pymodbus/client/async/udp.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/client/async/udp.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,47 @@ +from __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 + diff -Nru pymodbus-1.5.2+dfsg/pymodbus/client/async.py pymodbus-2.1.0+dfsg/pymodbus/client/async.py --- pymodbus-1.5.2+dfsg/pymodbus/client/async.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/client/async.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,222 +0,0 @@ -""" -Implementation of a Modbus Client Using Twisted --------------------------------------------------- - -Example run:: - - from twisted.internet import reactor, protocol - from pymodbus.client.async import ModbusClientProtocol - - def printResult(result): - print "Result: %d" % result.bits[0] - - def process(client): - result = client.write_coil(1, True) - result.addCallback(printResult) - reactor.callLater(1, reactor.stop) - - defer = protocol.ClientCreator(reactor, ModbusClientProtocol - ).connectTCP("localhost", 502) - defer.addCallback(process) - -Another example:: - - from twisted.internet import reactor - from pymodbus.client.async import ModbusClientFactory - - def process(): - factory = reactor.connectTCP("localhost", 502, ModbusClientFactory()) - reactor.stop() - - if __name__ == "__main__": - reactor.callLater(1, process) - reactor.run() -""" -from twisted.internet import defer, protocol -from pymodbus.factory import ClientDecoder -from pymodbus.exceptions import ConnectionException -from pymodbus.transaction import ModbusSocketFramer -from pymodbus.transaction import FifoTransactionManager -from pymodbus.transaction import DictTransactionManager -from pymodbus.client.common import ModbusClientMixin -from twisted.python.failure import Failure - -#---------------------------------------------------------------------------# -# Logging -#---------------------------------------------------------------------------# -import logging -_logger = logging.getLogger(__name__) - - -#---------------------------------------------------------------------------# -# Connected Client Protocols -#---------------------------------------------------------------------------# -class ModbusClientProtocol(protocol.Protocol, ModbusClientMixin): - ''' - This represents the base modbus client protocol. All the application - layer code is deferred to a higher level wrapper. - ''' - - def __init__(self, framer=None, **kwargs): - ''' Initializes the framer module - - :param framer: The framer to use for the protocol - ''' - self._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): - ''' - This represents the base modbus client protocol. All the application - layer code is deferred to a higher level wrapper. - ''' - - def __init__(self, framer=None, **kwargs): - ''' Initializes the framer module - - :param framer: The framer to use for the protocol - ''' - self.framer = framer or ModbusSocketFramer(ClientDecoder()) - if isinstance(self.framer, ModbusSocketFramer): - self.transaction = DictTransactionManager(self, **kwargs) - else: self.transaction = FifoTransactionManager(self, **kwargs) - - def datagramReceived(self, data, params): - ''' Get response, check for valid message, decode result - - :param data: The data returned from the server - :param params: The host parameters sending the datagram - ''' - _logger.debug("Datagram from: %s:%d" % params) - 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): - ''' Simple client protocol factory ''' - - protocol = ModbusClientProtocol - -#---------------------------------------------------------------------------# -# Exported symbols -#---------------------------------------------------------------------------# -__all__ = [ - "ModbusClientProtocol", "ModbusUdpClientProtocol", - "ModbusClientFactory", -] diff -Nru pymodbus-1.5.2+dfsg/pymodbus/client/sync.py pymodbus-2.1.0+dfsg/pymodbus/client/sync.py --- pymodbus-1.5.2+dfsg/pymodbus/client/sync.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/client/sync.py 2018-10-03 12:44:47.000000000 +0000 @@ -1,4 +1,5 @@ import socket +import select import serial import time import sys @@ -123,6 +124,10 @@ 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 @@ -230,34 +235,43 @@ """ 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. + + # 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) - begin = time.time() - data = b'' - if size is not None: - while len(data) < size: - try: - data += self.socket.recv(size - len(data)) - except socket.error: - pass - if not self.timeout or (time.time() - begin > self.timeout): - break + timeout = self.timeout + + # If size isn't specified read 1 byte at a time. + if size is None: + recv_size = 1 else: - while True: - try: - data += self.socket.recv(1) - except socket.error: - pass - if not self.timeout or (time.time() - begin > self.timeout): - break + 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): @@ -276,11 +290,11 @@ "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 """ @@ -376,10 +390,14 @@ # --------------------------------------------------------------------------- # # 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 @@ -414,7 +432,9 @@ if self.baudrate > 19200: self.silent_interval = 1.75 / 1000 # ms else: - self.silent_interval = 3.5 * (1 + 8 + 2) / self.baudrate + 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 @@ -453,6 +473,7 @@ _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 @@ -502,15 +523,22 @@ 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) + 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): - size = self._in_waiting() - if size: + 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 diff -Nru pymodbus-1.5.2+dfsg/pymodbus/compat.py pymodbus-2.1.0+dfsg/pymodbus/compat.py --- pymodbus-1.5.2+dfsg/pymodbus/compat.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/compat.py 2018-10-03 12:44:47.000000000 +0000 @@ -1,4 +1,4 @@ -''' +""" Python 2.x/3.x Compatibility Layer ------------------------------------------------- @@ -10,87 +10,87 @@ :copyright: Copyright 2013 by the Jinja team, see AUTHORS. :license: BSD, see LICENSE for details. -''' +""" import sys -import struct +import six -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # # python version checks -#---------------------------------------------------------------------------# -IS_PYTHON2 = sys.version_info[0] == 2 -IS_PYTHON3 = sys.version_info[0] == 3 -IS_PYPY = hasattr(sys, 'pypy_translation_info') -IS_JYTHON = sys.platform.startswith('java') - -#---------------------------------------------------------------------------# -# python > 3.3 compatability layer -#---------------------------------------------------------------------------# -if not IS_PYTHON2: - #-----------------------------------------------------------------------# - # portable builtins - #-----------------------------------------------------------------------# - int2byte = lambda b: struct.pack('B', b) - byte2int = lambda b: b - unichr = chr - range_type = range - text_type = str - string_types = (str,) - iterkeys = lambda d: iter(d.keys()) - itervalues = lambda d: iter(d.values()) - iteritems = lambda d: iter(d.items()) - get_next = lambda x: x.__next__() +# --------------------------------------------------------------------------- # +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 - #-----------------------------------------------------------------------# - from io import BytesIO, StringIO - NativeStringIO = StringIO - - ifilter = filter - imap = map - izip = zip - intern = sys.intern - + # ----------------------------------------------------------------------- # 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: - #-----------------------------------------------------------------------# - # portable builtins - #-----------------------------------------------------------------------# - int2byte = chr - byte2int = ord - unichr = unichr - text_type = unicode - range_type = xrange - string_types = (str, unicode) - iterkeys = lambda d: d.iterkeys() - itervalues = lambda d: d.itervalues() - iteritems = lambda d: d.iteritems() - get_next = lambda x: x.next() - - #-----------------------------------------------------------------------# + byte2int = six.byte2int + # ----------------------------------------------------------------------- # # module renames - #-----------------------------------------------------------------------# - from cStringIO import StringIO as BytesIO, StringIO - NativeStringIO = BytesIO - - from itertools import imap, izip, ifilter - intern = intern + # ----------------------------------------------------------------------- # 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 False \ No newline at end of file diff -Nru pymodbus-1.5.2+dfsg/pymodbus/datastore/context.py pymodbus-2.1.0+dfsg/pymodbus/datastore/context.py --- pymodbus-1.5.2+dfsg/pymodbus/datastore/context.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/datastore/context.py 2018-10-03 12:44:47.000000000 +0000 @@ -36,7 +36,7 @@ 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) + self.zero_mode = kwargs.get('zero_mode', Defaults.ZeroMode) def __str__(self): ''' Returns a string representation of the context @@ -58,19 +58,21 @@ :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 + 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): - ''' Validates the request to make sure it is in range + ''' 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 + 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) @@ -81,7 +83,8 @@ :param address: The starting address :param values: The new values to be set ''' - if not self.zero_mode: address = address + 1 + 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) diff -Nru pymodbus-1.5.2+dfsg/pymodbus/datastore/database/redis_datastore.py pymodbus-2.1.0+dfsg/pymodbus/datastore/database/redis_datastore.py --- pymodbus-1.5.2+dfsg/pymodbus/datastore/database/redis_datastore.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/datastore/database/redis_datastore.py 2018-10-03 12:44:47.000000000 +0000 @@ -5,7 +5,7 @@ #---------------------------------------------------------------------------# # Logging #---------------------------------------------------------------------------# -import logging; +import logging _logger = logging.getLogger(__name__) @@ -55,7 +55,7 @@ return self._val_callbacks[self.decode(fx)](address, count) def getValues(self, fx, address, count=1): - ''' Validates the request to make sure it is in range + ''' Get `count` values from datastore :param fx: The function we are working with :param address: The starting address @@ -94,28 +94,28 @@ 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), + '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), + '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), + '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_size = 16 _bit_default = '\x00' * (_bit_size % 8) def _get_bit_values(self, key, offset, count): @@ -129,7 +129,7 @@ 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)) + request = ('%s:%s' % (key, v) for v in range(s, e + 1)) response = self.client.mget(request) return response @@ -173,7 +173,7 @@ 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)) + 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)) @@ -183,7 +183,7 @@ #--------------------------------------------------------------------------# # Redis register implementation #--------------------------------------------------------------------------# - _reg_size = 16 + _reg_size = 16 _reg_default = '\x00' * (_reg_size % 8) def _get_reg_values(self, key, offset, count): @@ -198,7 +198,7 @@ #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)) + request = ('%s:%s' % (key, v) for v in range(offset, count + 1)) response = self.client.mget(request) return response diff -Nru pymodbus-1.5.2+dfsg/pymodbus/datastore/database/sql_datastore.py pymodbus-2.1.0+dfsg/pymodbus/datastore/database/sql_datastore.py --- pymodbus-1.5.2+dfsg/pymodbus/datastore/database/sql_datastore.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/datastore/database/sql_datastore.py 2018-10-03 12:44:47.000000000 +0000 @@ -10,7 +10,7 @@ #---------------------------------------------------------------------------# # Logging #---------------------------------------------------------------------------# -import logging; +import logging _logger = logging.getLogger(__name__) @@ -57,7 +57,7 @@ return self._validate(self.decode(fx), address, count) def getValues(self, fx, address, count=1): - ''' Validates the request to make sure it is in range + ''' Get `count` values from datastore :param fx: The function we are working with :param address: The starting address @@ -91,10 +91,10 @@ 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')) + 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() @@ -105,7 +105,7 @@ :param count: The number of bits to read :returns: The resulting values ''' - query = self._table.select(and_( + query = self._table.select(and_( self._table.c.type == type, self._table.c.index >= offset, self._table.c.index <= offset + count) @@ -125,9 +125,9 @@ result = [] for index, value in enumerate(values): result.append({ - prefix + 'type' : type, - prefix + 'index' : offset + index, - 'value' : value + prefix + 'type': type, + prefix + 'index': offset + index, + 'value': value }) return result @@ -144,8 +144,8 @@ ''' if self._check(type, offset, values): context = self._build_set(type, offset, values) - query = self._table.insert() - result = self._connection.execute(query, context) + query = self._table.insert() + result = self._connection.execute(query, context) return result.rowcount == len(values) else: return False @@ -158,11 +158,11 @@ :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'), + 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) + result = self._connection.execute(query, context) return result.rowcount == len(values) def _validate(self, type, offset, count): @@ -172,7 +172,7 @@ :param count: The number of bits to read :returns: The result of the validation ''' - query = self._table.select(and_( + query = self._table.select(and_( self._table.c.type == type, self._table.c.index >= offset, self._table.c.index <= offset + count)) diff -Nru pymodbus-1.5.2+dfsg/pymodbus/datastore/remote.py pymodbus-2.1.0+dfsg/pymodbus/datastore/remote.py --- pymodbus-1.5.2+dfsg/pymodbus/datastore/remote.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/datastore/remote.py 2018-10-03 12:44:47.000000000 +0000 @@ -44,7 +44,7 @@ return not result.isError() def getValues(self, fx, address, count=1): - ''' Validates the request to make sure it is in range + ''' Get `count` values from datastore :param fx: The function we are working with :param address: The starting address @@ -100,6 +100,9 @@ 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 + if fx in ['d', 'c']: + return result.bits + if fx in ['h', 'i']: + return result.registers + else: + return result diff -Nru pymodbus-1.5.2+dfsg/pymodbus/exceptions.py pymodbus-2.1.0+dfsg/pymodbus/exceptions.py --- pymodbus-1.5.2+dfsg/pymodbus/exceptions.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/exceptions.py 2018-10-03 12:44:47.000000000 +0000 @@ -26,12 +26,13 @@ class ModbusIOException(ModbusException): ''' Error resulting from data i/o ''' - def __init__(self, string=""): + def __init__(self, string="", function_code=None): ''' Initialize the exception :param string: The message to append to the error ''' - message = "[Input/Output] %s" % string - ModbusException.__init__(self, message) + self.fcode = function_code + self.message = "[Input/Output] %s" % string + ModbusException.__init__(self, self.message) class ParameterException(ModbusException): @@ -103,4 +104,5 @@ "ModbusException", "ModbusIOException", "ParameterException", "NotImplementedException", "ConnectionException", "NoSuchSlaveException", + "InvalidMessageReceivedException" ] diff -Nru pymodbus-1.5.2+dfsg/pymodbus/framer/ascii_framer.py pymodbus-2.1.0+dfsg/pymodbus/framer/ascii_framer.py --- pymodbus-1.5.2+dfsg/pymodbus/framer/ascii_framer.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/framer/ascii_framer.py 2018-10-03 12:44:47.000000000 +0000 @@ -154,7 +154,7 @@ 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 + 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 @@ -162,10 +162,9 @@ :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 + :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] diff -Nru pymodbus-1.5.2+dfsg/pymodbus/framer/binary_framer.py pymodbus-2.1.0+dfsg/pymodbus/framer/binary_framer.py --- pymodbus-1.5.2+dfsg/pymodbus/framer/binary_framer.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/framer/binary_framer.py 2018-10-03 12:44:47.000000000 +0000 @@ -144,7 +144,7 @@ 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 + 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 @@ -152,10 +152,9 @@ :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 + :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)): diff -Nru pymodbus-1.5.2+dfsg/pymodbus/framer/rtu_framer.py pymodbus-2.1.0+dfsg/pymodbus/framer/rtu_framer.py --- pymodbus-1.5.2+dfsg/pymodbus/framer/rtu_framer.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/framer/rtu_framer.py 2018-10-03 12:44:47.000000000 +0000 @@ -54,7 +54,7 @@ (1/Baud)(bits) = delay seconds """ - def __init__(self, decoder, client): + def __init__(self, decoder, client=None): """ Initializes a new instance of the framer :param decoder: The decoder factory implementation to use @@ -92,7 +92,7 @@ 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): + except (IndexError, KeyError, struct.error): return False def advanceFrame(self): @@ -197,7 +197,7 @@ 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 + 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 @@ -205,7 +205,7 @@ :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 + :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) """ @@ -243,9 +243,8 @@ :param message: Message to be sent over the bus :return: """ - # _logger.debug("Current transaction state - {}".format( - # ModbusTransactionState.to_string(self.client.state)) - # ) + 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) @@ -253,7 +252,7 @@ "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: @@ -267,14 +266,14 @@ time.sleep(self.client.silent_interval) self.client.state = ModbusTransactionState.IDLE else: - _logger.debug("Sleeping") - time.sleep(self.client.silent_interval) + 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) - # if size: - # _logger.debug("Changing transaction state from 'SENDING' " - # "to 'WAITING FOR REPLY'") - # self.client.state = ModbusTransactionState.WAITING_FOR_REPLY - self.client.last_frame_end = round(time.time(), 6) return size diff -Nru pymodbus-1.5.2+dfsg/pymodbus/framer/socket_framer.py pymodbus-2.1.0+dfsg/pymodbus/framer/socket_framer.py --- pymodbus-1.5.2+dfsg/pymodbus/framer/socket_framer.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/framer/socket_framer.py 2018-10-03 12:44:47.000000000 +0000 @@ -21,7 +21,7 @@ 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 ] + [ MBAP Header ] [ Function Code] [ Data ] \ [ tid ][ pid ][ length ][ uid ] 2b 2b 2b 1b 1b Nb @@ -128,7 +128,7 @@ 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 + 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 @@ -136,10 +136,10 @@ :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 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] diff -Nru pymodbus-1.5.2+dfsg/pymodbus/interfaces.py pymodbus-2.1.0+dfsg/pymodbus/interfaces.py --- pymodbus-1.5.2+dfsg/pymodbus/interfaces.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/interfaces.py 2018-10-03 12:44:47.000000000 +0000 @@ -190,7 +190,7 @@ raise NotImplementedException("validate context values") def getValues(self, fx, address, count=1): - ''' Validates the request to make sure it is in range + ''' Get `count` values from datastore :param fx: The function we are working with :param address: The starting address @@ -227,6 +227,7 @@ ''' raise NotImplementedException("set context values") + #---------------------------------------------------------------------------# # Exported symbols #---------------------------------------------------------------------------# diff -Nru pymodbus-1.5.2+dfsg/pymodbus/payload.py pymodbus-2.1.0+dfsg/pymodbus/payload.py --- pymodbus-1.5.2+dfsg/pymodbus/payload.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/payload.py 2018-10-03 12:44:47.000000000 +0000 @@ -14,7 +14,7 @@ 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 # --------------------------------------------------------------------------- # @@ -124,6 +124,17 @@ _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 @@ -320,7 +331,7 @@ :return: """ handle = make_byte_string(handle) - wc = WC.get(fstring.lower())//2 + wc = WC.get(fstring.lower()) // 2 up = "!{}H".format(wc) handle = unpack(up, handle) if self._wordorder == Endian.Little: @@ -329,8 +340,8 @@ # Repack as unsigned Integer pk = self._byteorder + 'H' handle = [pack(pk, p) for p in handle] - handle = b''.join(handle) _logger.debug(handle) + handle = b''.join(handle) return handle def reset(self): diff -Nru pymodbus-1.5.2+dfsg/pymodbus/pdu.py pymodbus-2.1.0+dfsg/pymodbus/pdu.py --- pymodbus-1.5.2+dfsg/pymodbus/pdu.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/pdu.py 2018-10-03 12:44:47.000000000 +0000 @@ -103,9 +103,9 @@ :param exception: The exception to return :raises: An exception response """ - _logger.error("Exception Response F(%d) E(%d)" % - (self.function_code, exception)) - return ExceptionResponse(self.function_code, exception) + exc = ExceptionResponse(self.function_code, exception) + _logger.error(exc) + return exc class ModbusResponse(ModbusPDU): diff -Nru pymodbus-1.5.2+dfsg/pymodbus/repl/client.py pymodbus-2.1.0+dfsg/pymodbus/repl/client.py --- pymodbus-1.5.2+dfsg/pymodbus/repl/client.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/repl/client.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,717 @@ +""" +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) diff -Nru pymodbus-1.5.2+dfsg/pymodbus/repl/completer.py pymodbus-2.1.0+dfsg/pymodbus/repl/completer.py --- pymodbus-1.5.2+dfsg/pymodbus/repl/completer.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/repl/completer.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,156 @@ +""" +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) diff -Nru pymodbus-1.5.2+dfsg/pymodbus/repl/helper.py pymodbus-2.1.0+dfsg/pymodbus/repl/helper.py --- pymodbus-1.5.2+dfsg/pymodbus/repl/helper.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/repl/helper.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,326 @@ +""" +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)) diff -Nru pymodbus-1.5.2+dfsg/pymodbus/repl/__init__.py pymodbus-2.1.0+dfsg/pymodbus/repl/__init__.py --- pymodbus-1.5.2+dfsg/pymodbus/repl/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/repl/__init__.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,7 @@ +""" +Pymodbus REPL Module. + +Copyright (c) 2018 Riptide IO, Inc. All Rights Reserved. + +""" +from __future__ import absolute_import, unicode_literals \ No newline at end of file diff -Nru pymodbus-1.5.2+dfsg/pymodbus/repl/main.py pymodbus-2.1.0+dfsg/pymodbus/repl/main.py --- pymodbus-1.5.2+dfsg/pymodbus/repl/main.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/repl/main.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,355 @@ +""" +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() diff -Nru pymodbus-1.5.2+dfsg/pymodbus/repl/README.md pymodbus-2.1.0+dfsg/pymodbus/repl/README.md --- pymodbus-1.5.2+dfsg/pymodbus/repl/README.md 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/repl/README.md 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,281 @@ +# 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) + diff -Nru pymodbus-1.5.2+dfsg/pymodbus/server/async.py pymodbus-2.1.0+dfsg/pymodbus/server/async.py --- pymodbus-1.5.2+dfsg/pymodbus/server/async.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/server/async.py 2018-10-03 12:44:47.000000000 +0000 @@ -219,27 +219,28 @@ if IS_PYTHON3: if threading.current_thread() != threading.main_thread(): - _logger.debug("Starting in spawned thread") + _logger.debug("Running in spawned thread") return False else: if not isinstance(threading.current_thread(), threading._MainThread): - _logger.debug("Starting in spawned thread") + _logger.debug("Running in spawned thread") return False - _logger.debug("Starting in Main thread") + _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 + """ + 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 + :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 + :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 @@ -258,14 +259,15 @@ def StartUdpServer(context, identity=None, address=None, defer_reactor_run=False, **kwargs): - """ Helper method to start the Modbus Async Udp server + """ + 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 + :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 + :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 @@ -282,9 +284,10 @@ def StartSerialServer(context, identity=None, framer=ModbusAsciiFramer, - defer_reactor_run=False, + defer_reactor_run=False, **kwargs): - """ Helper method to start the Modbus Async Serial server + """ + Helper method to start the Modbus Async Serial server :param context: The server data context :param identify: The server identity to use (default empty) @@ -292,9 +295,9 @@ :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 + :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 + :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 @@ -323,10 +326,10 @@ from twisted.internet import reactor if _is_main_thread(): reactor.stop() - _logger.debug("Stopping main thread") + _logger.debug("Stopping server from main thread") else: reactor.callFromThread(reactor.stop) - _logger.debug("Stopping current thread") + _logger.debug("Stopping Server from another thread") # --------------------------------------------------------------------------- # diff -Nru pymodbus-1.5.2+dfsg/pymodbus/server/sync.py pymodbus-2.1.0+dfsg/pymodbus/server/sync.py --- pymodbus-1.5.2+dfsg/pymodbus/server/sync.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/server/sync.py 2018-10-03 12:44:47.000000000 +0000 @@ -62,12 +62,14 @@ 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 ) + _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() ) + _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 @@ -79,15 +81,15 @@ def handle(self): """ Callback when we receive any data """ - raise NotImplementedException("Method not implemented by derived class") - + 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") - + raise NotImplementedException("Method not implemented " + "by derived class") class ModbusSingleRequestHandler(ModbusBaseRequestHandler): """ Implements the modbus server protocol @@ -181,7 +183,8 @@ _logger.error("Socket error occurred %s" % msg) self.running = False except: - _logger.error("Socket exception occurred %s" % traceback.format_exc() ) + _logger.error("Socket exception occurred " + "%s" % traceback.format_exc() ) self.running = False reset_frame = True finally: @@ -270,7 +273,8 @@ """ def __init__(self, context, framer=None, identity=None, - address=None, handler=None, **kwargs): + 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 @@ -282,10 +286,13 @@ :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() @@ -298,9 +305,8 @@ if isinstance(identity, ModbusDeviceIdentification): self.control.Identity.update(identity) - socketserver.ThreadingTCPServer.__init__(self, - self.address, self.handler) - # self._BaseServer__shutdown_request = True + socketserver.ThreadingTCPServer.__init__(self, self.address, + self.handler) def process_request(self, request, client): """ Callback for connecting a new client thread @@ -474,9 +480,6 @@ def serve_forever(self): """ Callback for connecting a new client thread - - :param request: The request to handle - :param client: The address of the client """ if self._connect(): _logger.debug("Started thread to serve client") @@ -508,7 +511,8 @@ :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 + :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) @@ -542,8 +546,8 @@ :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 + :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) diff -Nru pymodbus-1.5.2+dfsg/pymodbus/transaction.py pymodbus-2.1.0+dfsg/pymodbus/transaction.py --- pymodbus-1.5.2+dfsg/pymodbus/transaction.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/transaction.py 2018-10-03 12:44:47.000000000 +0000 @@ -1,6 +1,8 @@ -''' +""" Collection of transaction based abstractions -''' + +""" + import struct import socket from threading import RLock @@ -23,6 +25,7 @@ except NameError: TimeoutError = socket.timeout + # --------------------------------------------------------------------------- # # Logging # --------------------------------------------------------------------------- # @@ -34,7 +37,7 @@ # The Global Transaction Manager # --------------------------------------------------------------------------- # class ModbusTransactionManager(object): - ''' Impelements a transaction for a manager + """ Impelements a transaction for a manager The transaction protocol can be represented by the following pseudo code:: @@ -47,15 +50,15 @@ 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 + """ 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) @@ -85,22 +88,23 @@ return self.base_adu_size + expected_pdu_size def _calculate_exception_length(self): - ''' Returns the length of the Modbus Exception Response according to + """ 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)): + 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 + """ Starts the producer to send the next request to consumer.write(Frame(request)) - ''' + """ with self._transaction_lock: try: _logger.debug("Current transaction state - {}".format( @@ -170,7 +174,8 @@ last_exception = last_exception or ( "No Response received from the remote unit" "/Unable to decode response") - response = ModbusIOException(last_exception) + response = ModbusIOException(last_exception, + request.function_code) if hasattr(self.client, "state"): _logger.debug("Changing transaction state from " "'PROCESSING REPLY' to " @@ -282,156 +287,159 @@ return result def addTransaction(self, request, tid=None): - ''' Adds a transaction to the handler + """ 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 + """ 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 + """ 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 + """ 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 ''' + """ Resets the transaction identifier """ self.tid = Defaults.TransactionId self.transactions = type(self.transactions)() class DictTransactionManager(ModbusTransactionManager): - ''' Impelements a transaction for a manager where the + """ 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 + """ 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 + """ 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 + """ 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 + """ 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 + """ Removes a transaction matching the referenced tid :param tid: The transaction to remove - ''' - _logger.debug("Deleting transaction %d" % tid) + """ + _logger.debug("deleting transaction %d" % tid) + self.transactions.pop(tid, None) class FifoTransactionManager(ModbusTransactionManager): - ''' Impelements a transaction for a manager where the + """ 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 + """ 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 + """ 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 + """ 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 + """ + 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 + """ Returns a transaction matching the referenced tid If the transaction does not exist, None is returned :param tid: The transaction to retrieve - ''' - _logger.debug("Getting transaction %s" % str(tid)) + """ return self.transactions.pop(0) if self.transactions else None def delTransaction(self, tid): - ''' Removes a transaction matching the referenced 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", diff -Nru pymodbus-1.5.2+dfsg/pymodbus/utilities.py pymodbus-2.1.0+dfsg/pymodbus/utilities.py --- pymodbus-1.5.2+dfsg/pymodbus/utilities.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/utilities.py 2018-10-03 12:44:47.000000000 +0000 @@ -30,7 +30,7 @@ ModbusTransactionState.WAITING_TURNAROUND_DELAY: "WAITING_TURNAROUND_DELAY", ModbusTransactionState.PROCESSING_REPLY: "PROCESSING_REPLY", ModbusTransactionState.PROCESSING_ERROR: "PROCESSING_ERROR", - ModbusTransactionState.TRANSACTION_COMPLETE: "TRANSCATION_COMPLETE" + ModbusTransactionState.TRANSACTION_COMPLETE: "TRANSACTION_COMPLETE" } return states.get(state, None) diff -Nru pymodbus-1.5.2+dfsg/pymodbus/version.py pymodbus-2.1.0+dfsg/pymodbus/version.py --- pymodbus-1.5.2+dfsg/pymodbus/version.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/pymodbus/version.py 2018-10-03 12:44:47.000000000 +0000 @@ -41,7 +41,7 @@ return '[%s, version %s]' % (self.package, self.short()) -version = Version('pymodbus', 1, 5, 2) +version = Version('pymodbus', 2, 1, 0) version.__name__ = 'pymodbus' # fix epydoc error diff -Nru pymodbus-1.5.2+dfsg/README.rst pymodbus-2.1.0+dfsg/README.rst --- pymodbus-1.5.2+dfsg/README.rst 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/README.rst 2018-10-03 12:44:47.000000000 +0000 @@ -1,16 +1,24 @@ +================================ +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-n/badge/?version=latest - :target: http://pymodbus.readthedocs.io/en/latest/?badge=latest + :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 @@ -19,23 +27,24 @@ (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) and synchronous versions + * 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 @@ -43,9 +52,9 @@ * 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 @@ -90,6 +99,21 @@ 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 ------------------------------------------------------------ @@ -100,6 +124,18 @@ 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 diff -Nru pymodbus-1.5.2+dfsg/.readthedocs.yml pymodbus-2.1.0+dfsg/.readthedocs.yml --- pymodbus-1.5.2+dfsg/.readthedocs.yml 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/.readthedocs.yml 2018-10-03 12:44:47.000000000 +0000 @@ -6,4 +6,6 @@ python: extra_requirements: - twisted + - tornado - documents + version: 3.5 diff -Nru pymodbus-1.5.2+dfsg/requirements-docs.txt pymodbus-2.1.0+dfsg/requirements-docs.txt --- pymodbus-1.5.2+dfsg/requirements-docs.txt 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/requirements-docs.txt 2018-10-03 12:44:47.000000000 +0000 @@ -1,11 +1,14 @@ # Python packages required to run `make docs'. -cryptography==2.1.4 # Required to parse some files +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 diff -Nru pymodbus-1.5.2+dfsg/requirements-tests.txt pymodbus-2.1.0+dfsg/requirements-tests.txt --- pymodbus-1.5.2+dfsg/requirements-tests.txt 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/requirements-tests.txt 2018-10-03 12:44:47.000000000 +0000 @@ -1,11 +1,9 @@ capturer >= 2.2 coverage >= 4.2 +cryptography>=1.8.1 mock >= 1.0.1 -nose>=1.3.7 +pyserial-asyncio==0.4.0;python_version>="3.4" pep8>=1.7.0 -verboselogs >= 1.5 -Twisted>=17.1.0 -zope.interface>=4.4.0 pyasn1>=0.2.3 pycrypto>=2.6.1 pyserial>=3.4 @@ -14,4 +12,7 @@ redis>=2.10.5 sqlalchemy>=1.1.15 #wsgiref>=0.1.2 -cryptography>=1.8.1 \ No newline at end of file +verboselogs >= 1.5 +tornado==4.5.3 +Twisted>=17.1.0 +zope.interface>=4.4.0 diff -Nru pymodbus-1.5.2+dfsg/requirements.txt pymodbus-2.1.0+dfsg/requirements.txt --- pymodbus-1.5.2+dfsg/requirements.txt 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/requirements.txt 2018-10-03 12:44:47.000000000 +0000 @@ -2,7 +2,7 @@ # ------------------------------------------------------------------- # if want to use the pymodbus serial stack, uncomment these # ------------------------------------------------------------------- -#pyserial==3.4 +# pyserial>=3.4 # ------------------------------------------------------------------- # if you want to run the tests and code coverage, uncomment these # ------------------------------------------------------------------- @@ -11,7 +11,7 @@ #nose==1.3.7 #pep8==1.7.0 # ------------------------------------------------------------------- -# if you want to use the asynchronous version, uncomment these +# if you want to use the Twisted asynchronous version, uncomment these # ------------------------------------------------------------------- #Twisted==17.1.0 #zope.interface==4.4.0 @@ -19,6 +19,17 @@ #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 # ------------------------------------------------------------------- @@ -28,3 +39,8 @@ #docutils==0.13.1 #pydoctor==16.3.0 +# ------------------------------------------------------------------- +# if you want to use pymodbus REPL +# ------------------------------------------------------------------- +# click>=6.7 +# prompt-toolkit==2.0.4 \ No newline at end of file diff -Nru pymodbus-1.5.2+dfsg/setup.cfg pymodbus-2.1.0+dfsg/setup.cfg --- pymodbus-1.5.2+dfsg/setup.cfg 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/setup.cfg 2018-10-03 12:44:47.000000000 +0000 @@ -1,21 +1,12 @@ [aliases] upload_docs = build_sphinx upload_docs package = build_apidocs build_sphinx sdist +test=pytest [egg_info] #tag_build = dev tag_svn_revision = false -[nosetests] -verbosity=0 -detailed-errors=1 -with-coverage=1 -cover-html=1 -cover-html-dir=build/coverage/ -cover-package=pymodbus -#debug=nose.loader -#pdb=1 -#pdb-failures=1 [build-sphinx] source-dir = doc/sphinx/ @@ -29,5 +20,6 @@ universal=1 [tool:pytest] -addopts = --cov=pymodbus/ -testpaths = test \ No newline at end of file +testpaths = test +addopts = -p no:warnings + diff -Nru pymodbus-1.5.2+dfsg/setup.py pymodbus-2.1.0+dfsg/setup.py --- pymodbus-1.5.2+dfsg/setup.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/setup.py 2018-10-03 12:44:47.000000000 +0000 @@ -26,18 +26,24 @@ 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', + name="pymodbus", version=__version__, - description='A fully featured modbus protocol stack in python', + description="A fully featured modbus protocol stack in python", long_description=""" Pymodbus aims to be a fully implemented modbus protocol stack - implemented using twisted. Its orignal goal was to allow simulation - of thousands of modbus devices on a single machine for monitoring - software testing. + 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', @@ -65,9 +71,7 @@ platforms=['Linux', 'Mac OS X', 'Win'], include_package_data=True, zip_safe=True, - install_requires=[ - 'pyserial >= 2.6' - ], + install_requires=install_requires, extras_require={ 'quality': [ 'coverage >= 3.5.3', @@ -83,6 +87,17 @@ '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, diff -Nru pymodbus-1.5.2+dfsg/test/asyncio_test_helper.py pymodbus-2.1.0+dfsg/test/asyncio_test_helper.py --- pymodbus-1.5.2+dfsg/test/asyncio_test_helper.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/test/asyncio_test_helper.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,54 @@ +from 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 diff -Nru pymodbus-1.5.2+dfsg/test/test_bit_read_messages.py pymodbus-2.1.0+dfsg/test/test_bit_read_messages.py --- pymodbus-1.5.2+dfsg/test/test_bit_read_messages.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/test/test_bit_read_messages.py 2018-10-03 12:44:47.000000000 +0000 @@ -1,5 +1,5 @@ #!/usr/bin/env python -''' +""" Bit Message Test Fixture -------------------------------- This fixture tests the functionality of all the @@ -7,7 +7,7 @@ * Read/Write Discretes * Read Coils -''' +""" import unittest, struct from pymodbus.bit_read_message import * from pymodbus.bit_read_message import ReadBitsRequestBase @@ -29,18 +29,18 @@ #-----------------------------------------------------------------------# def setUp(self): - ''' + """ Initializes the test environment and builds request/result encoding pairs - ''' + """ pass def tearDown(self): - ''' Cleans up the test environment ''' + """ Cleans up the test environment """ pass def testReadBitBaseClassMethods(self): - ''' Test basic bit message encoding/decoding ''' + """ Test basic bit message encoding/decoding """ handle = ReadBitsRequestBase(1, 1) msg = "ReadBitRequest(1,1)" self.assertEqual(msg, str(handle)) @@ -49,7 +49,7 @@ self.assertEqual(msg, str(handle)) def testBitReadBaseRequestEncoding(self): - ''' Test basic bit message encoding/decoding ''' + """ Test basic bit message encoding/decoding """ for i in range(20): handle = ReadBitsRequestBase(i, i) result = struct.pack('>HH',i, i) @@ -58,7 +58,7 @@ self.assertEqual((handle.address, handle.count), (i,i)) def testBitReadBaseResponseEncoding(self): - ''' Test basic bit message encoding/decoding ''' + """ Test basic bit message encoding/decoding """ for i in range(20): input = [True] * i handle = ReadBitsResponseBase(input) @@ -67,7 +67,7 @@ self.assertEqual(handle.bits[:i], input) def testBitReadBaseResponseHelperMethods(self): - ''' Test the extra methods on a ReadBitsResponseBase ''' + """ Test the extra methods on a ReadBitsResponseBase """ input = [False] * 8 handle = ReadBitsResponseBase(input) for i in [1,3,5]: handle.setBit(i, True) @@ -76,7 +76,7 @@ self.assertEqual(handle.getBit(i), False) def testBitReadBaseRequests(self): - ''' Test bit read request encoding ''' + """ Test bit read request encoding """ messages = { ReadBitsRequestBase(12, 14) : b'\x00\x0c\x00\x0e', ReadBitsResponseBase([1,0,1,1,0]) : b'\x01\x0d' @@ -85,7 +85,7 @@ self.assertEqual(request.encode(), expected) def testBitReadMessageExecuteValueErrors(self): - ''' Test bit read request encoding ''' + """ Test bit read request encoding """ context = MockContext() requests = [ ReadCoilsRequest(1,0x800), @@ -97,7 +97,7 @@ result.exception_code) def testBitReadMessageExecuteAddressErrors(self): - ''' Test bit read request encoding ''' + """ Test bit read request encoding """ context = MockContext() requests = [ ReadCoilsRequest(1,5), @@ -108,7 +108,7 @@ self.assertEqual(ModbusExceptions.IllegalAddress, result.exception_code) def testBitReadMessageExecuteSuccess(self): - ''' Test bit read request encoding ''' + """ Test bit read request encoding """ context = MockContext() context.validate = lambda a,b,c: True requests = [ diff -Nru pymodbus-1.5.2+dfsg/test/test_client_async_asyncio.py pymodbus-2.1.0+dfsg/test/test_client_async_asyncio.py --- pymodbus-1.5.2+dfsg/test/test_client_async_asyncio.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/test/test_client_async_asyncio.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,276 @@ +from 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 diff -Nru pymodbus-1.5.2+dfsg/test/test_client_async.py pymodbus-2.1.0+dfsg/test/test_client_async.py --- pymodbus-1.5.2+dfsg/test/test_client_async.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/test/test_client_async.py 2018-10-03 12:44:47.000000000 +0000 @@ -1,195 +1,254 @@ #!/usr/bin/env python import unittest -from pymodbus.compat import IS_PYTHON3 -if IS_PYTHON3: # Python 3 - from unittest.mock import patch, Mock -else: # Python 2 - from mock import patch, Mock -from pymodbus.client.async import ModbusClientProtocol, ModbusUdpClientProtocol -from pymodbus.client.async import ModbusClientFactory +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 -from pymodbus.bit_read_message import ReadCoilsRequest, ReadCoilsResponse +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 -#---------------------------------------------------------------------------# -class AsynchronousClientTest(unittest.TestCase): - ''' +# ---------------------------------------------------------------------------# + + +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) - #-----------------------------------------------------------------------# - # 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 ''' - protocol = ModbusClientProtocol() - 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() - 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 ''' - protocol = ModbusClientProtocol() - 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))) + 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): - 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 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) + 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() diff -Nru pymodbus-1.5.2+dfsg/test/test_client_async_tornado.py pymodbus-2.1.0+dfsg/test/test_client_async_tornado.py --- pymodbus-1.5.2+dfsg/test/test_client_async_tornado.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/test/test_client_async_tornado.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,314 @@ +#!/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() diff -Nru pymodbus-1.5.2+dfsg/test/test_client_async_twisted.py pymodbus-2.1.0+dfsg/test/test_client_async_twisted.py --- pymodbus-1.5.2+dfsg/test/test_client_async_twisted.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/test/test_client_async_twisted.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,228 @@ +#!/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() diff -Nru pymodbus-1.5.2+dfsg/test/test_client_sync.py pymodbus-2.1.0+dfsg/test/test_client_sync.py --- pymodbus-1.5.2+dfsg/test/test_client_sync.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/test/test_client_sync.py 2018-10-03 12:44:47.000000000 +0000 @@ -166,10 +166,12 @@ client = ModbusTcpClient() self.assertNotEqual(client, None) - def testBasicSyncTcpClient(self): + @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)) @@ -207,8 +209,11 @@ self.assertEqual(0, client._send(None)) self.assertEqual(4, client._send('1234')) - def testTcpClientRecv(self): + @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)) @@ -223,10 +228,10 @@ 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_socket.recv.side_effect = socket.error('No data') + mock_select.select.return_value = [False] self.assertEqual(b'', client._recv(2)) client.socket = mockSocket() - client.socket.timeout = 0.1 + mock_select.select.return_value = [True] self.assertIn(b'\x00', client._recv(None)) def testSerialClientRpr(self): @@ -351,4 +356,4 @@ # Main # ---------------------------------------------------------------------------# if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff -Nru pymodbus-1.5.2+dfsg/test/test_framers.py pymodbus-2.1.0+dfsg/test/test_framers.py --- pymodbus-1.5.2+dfsg/test/test_framers.py 1970-01-01 00:00:00.000000000 +0000 +++ pymodbus-2.1.0+dfsg/test/test_framers.py 2018-10-03 12:44:47.000000000 +0000 @@ -0,0 +1,163 @@ +import 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._buffer \ No newline at end of file diff -Nru pymodbus-1.5.2+dfsg/test/test_payload.py pymodbus-2.1.0+dfsg/test/test_payload.py --- pymodbus-1.5.2+dfsg/test/test_payload.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/test/test_payload.py 2018-10-03 12:44:47.000000000 +0000 @@ -103,15 +103,24 @@ def testPayloadBuilderWithRawPayload(self): """ Test basic bit message encoding/decoding """ - builder = BinaryPayloadBuilder([b'\x12', b'\x34', b'\x56', b'\x78'], repack=True) + _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 diff -Nru pymodbus-1.5.2+dfsg/test/test_server_async.py pymodbus-2.1.0+dfsg/test/test_server_async.py --- pymodbus-1.5.2+dfsg/test/test_server_async.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/test/test_server_async.py 2018-10-03 12:44:47.000000000 +0000 @@ -20,9 +20,17 @@ #---------------------------------------------------------------------------# # Fixture #---------------------------------------------------------------------------# -SERIAL_PORT = "/dev/ptmx" -if sys.platform == "darwin": - SERIAL_PORT = "/dev/ptyp0" +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): @@ -184,12 +192,40 @@ self.assertEqual(mock_reactor.listenUDP.call_count, 1) self.assertEqual(mock_reactor.run.call_count, 1) - def testSerialServerStartup(self): + @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 @@ -266,9 +302,8 @@ self.assertTrue(_is_main_thread()) - -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # # Main -#---------------------------------------------------------------------------# +# --------------------------------------------------------------------------- # if __name__ == "__main__": unittest.main() diff -Nru pymodbus-1.5.2+dfsg/test/test_server_sync.py pymodbus-2.1.0+dfsg/test/test_server_sync.py --- pymodbus-1.5.2+dfsg/test/test_server_sync.py 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/test/test_server_sync.py 2018-10-03 12:44:47.000000000 +0000 @@ -17,13 +17,21 @@ 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 -import sys +from pymodbus.datastore import ModbusServerContext + from pymodbus.compat import socketserver -SERIAL_PORT = "/dev/ptmx" -if sys.platform == "darwin": - SERIAL_PORT = "/dev/ptyp0" +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 #---------------------------------------------------------------------------# diff -Nru pymodbus-1.5.2+dfsg/.travis.yml pymodbus-2.1.0+dfsg/.travis.yml --- pymodbus-1.5.2+dfsg/.travis.yml 2018-05-14 03:54:52.000000000 +0000 +++ pymodbus-2.1.0+dfsg/.travis.yml 2018-10-03 12:44:47.000000000 +0000 @@ -11,6 +11,7 @@ - os: linux python: "3.6" - os: osx + osx_image: xcode8.3 language: generic before_install: - if [ $TRAVIS_OS_NAME = osx ]; then brew update; fi @@ -18,6 +19,7 @@ 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