Skip to content

Commit

Permalink
#515 fix repl broadcast (#531)
Browse files Browse the repository at this point in the history
* 1. update requirements
2. Fix examples
3. Fix #494 - handle_local_echo
4. Fix #500 -- asyncio serial client with already running loop
5. Fix #486 - Pass serial args for asyncio serial client
6. Fix #490 - Typo in decode_data for socker_framer
7. Fix #385 - Support timeouts to break out of responspe await when server goes offline
8. Misc updates

* #516 custom data block fix

* Fix broadcast error  with REPL client #515

* Fix #509 Wrong unit ID referenced in framers

* Update documentation for serial forwarder example. Fixes #525

* Fix unit tests, support python 3.8 for tests, renamed:    pymodbus/server/asyncio.py -> pymodbus/server/async_io.py and pymodbus/client/asynchronous/asyncio -> pymodbus/client/asynchronous/async_io

* Ignore python3 code syntax while reporting coverage

* Fix tests failing on python 3.6 and osx

* Fix typo in makefile

* Fix test execution errors specific to python3.6

* Osx travis issue - Fix trial 1

* Travis reverting xcode to 8.x for mac osx
  • Loading branch information
dhoomakethu authored Sep 11, 2020
1 parent 675b461 commit 29f694c
Show file tree
Hide file tree
Showing 27 changed files with 220 additions and 170 deletions.
7 changes: 4 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,23 @@ matrix:
include:
- os: linux
python: "2.7"
- os: linux
python: "3.5"
- os: linux
python: "3.6"
- os: linux
python: "3.7"
- os: linux
python: "3.8"
- os: osx
osx_image: xcode8.3
language: generic
before_install:
- if [ $TRAVIS_OS_NAME = osx ]; then brew update; fi
- if [ $TRAVIS_OS_NAME = osx ]; then brew install openssl; fi
# - if [$TRAVIS_OS_NAME = osx ]; then python -c "import fcntl; fcntl.fcntl(1, fcntl.F_SETFL, 0)"; fi

install:
# - scripts/travis.sh pip install pip-accel
- scripts/travis.sh pip install -U setuptools
- if [ $TRAVIS_OS_NAME = osx ]; then scripts/travis.sh pip install -U "\"setuptools<45"\"; else pip install -U setuptools --upgrade ; fi
- scripts/travis.sh pip install coveralls
- scripts/travis.sh pip install --requirement=requirements-checks.txt
- scripts/travis.sh pip install --requirement=requirements-tests.txt
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ Version 2.4.0
* Support async moduls tls server/client
* Add local echo option
* Add exponential backoffs on retries.
* REPL - Support broadcasts.
* Fix framers using wrong unit address.
* Update documentation for serial_forwarder example
* Fix error with rtu client for `local_echo`
* Fix asyncio client not working with already running loop
* Fix passing serial arguments to async clients
Expand Down
10 changes: 9 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ VIRTUAL_ENV ?= $(WORKON_HOME)/pymodbus
PATH := $(VIRTUAL_ENV)/bin:$(PATH)
MAKE := $(MAKE) --no-print-directory
SHELL = bash
PYVER=$(shell python -c "import sys;t='{v[0]}.{v[1]}'.format(v=list(sys.version_info[:2]));print(t)")

default:
@echo 'Makefile for pymodbus'
Expand Down Expand Up @@ -37,10 +38,16 @@ check: install
@pip install --upgrade --quiet --requirement=requirements-checks.txt
@flake8


test: install
@pip install --upgrade --quiet --requirement=requirements-tests.txt
ifeq ($(PYVER),3.6)
@pytest --cov=pymodbus/ --cov-report term-missing test/test_server_asyncio.py test
@coverage report --fail-under=90 -i
else
@pytest --cov=pymodbus/ --cov-report term-missing
@coverage report --fail-under=90
@coverage report --fail-under=90 -i
endif

tox: install
@pip install --upgrade --quiet tox && tox
Expand All @@ -57,6 +64,7 @@ publish: install
twine upload dist/*
$(MAKE) clean


clean:
@rm -Rf *.egg .eggs *.egg-info *.db .cache .coverage .tox build dist docs/build htmlcov doc/_build test/.Python test/pip-selfcheck.json test/lib/ test/include/ test/bin/
@find . -depth -type d -name __pycache__ -exec rm -Rf {} \;
Expand Down
5 changes: 2 additions & 3 deletions examples/common/async_asyncio_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
# Import the required asynchronous client
# ----------------------------------------------------------------------- #
from pymodbus.client.asynchronous.tcp import AsyncModbusTCPClient as ModbusClient
from pymodbus.client.asynchronous.udp import (
AsyncModbusUDPClient as ModbusClient)
# from pymodbus.client.asynchronous.udp import (
# AsyncModbusUDPClient as ModbusClient)
from pymodbus.client.asynchronous import schedulers

else:
Expand Down Expand Up @@ -210,7 +210,6 @@ def run_with_no_loop():
# run_with_not_running_loop()

# Run with already running loop

# run_with_already_running_loop()

log.debug("")
8 changes: 4 additions & 4 deletions examples/common/asyncio_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
# import the various server implementations
# --------------------------------------------------------------------------- #
import asyncio
from pymodbus.server.asyncio import StartTcpServer
from pymodbus.server.asyncio import StartTlsServer
from pymodbus.server.asyncio import StartUdpServer
from pymodbus.server.asyncio import StartSerialServer
from pymodbus.server.async_io import StartTcpServer
from pymodbus.server.async_io import StartTlsServer
from pymodbus.server.async_io import StartUdpServer
from pymodbus.server.async_io import StartSerialServer

from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock, ModbusSparseDataBlock
Expand Down
1 change: 0 additions & 1 deletion examples/common/modbus_payload.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,6 @@ def run_binary_payload_ex():
print("-" * 60)
for name, value in iteritems(decoded):
print("%s\t" % name, hex(value) if isinstance(value, int) else value)


# ----------------------------------------------------------------------- #
# close the client
Expand Down
2 changes: 1 addition & 1 deletion examples/contrib/asynchronous_asyncio_serial_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
if IS_PYTHON3 and PYTHON_VERSION >= (3, 4):
import asyncio
from serial_asyncio import create_serial_connection
from pymodbus.client.asynchronous.asyncio import ModbusClientProtocol
from pymodbus.client.asynchronous.async_io import ModbusClientProtocol
from pymodbus.transaction import ModbusAsciiFramer, ModbusRtuFramer
from pymodbus.factory import ClientDecoder
else:
Expand Down
8 changes: 7 additions & 1 deletion examples/contrib/serial_forwarder.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,14 @@
def run_serial_forwarder():
# ----------------------------------------------------------------------- #
# initialize the datastore(serial client)
# Note this would send the requests on the serial client with address = 0

# ----------------------------------------------------------------------- #
client = ModbusClient(method='rtu', port='/dev/ptyp0')
client = ModbusClient(method='rtu', port='/tmp/ptyp0')
# If required to communicate with a specified client use unit=<unit_id>
# in RemoteSlaveContext
# For e.g to forward the requests to slave with unit address 1 use
# store = RemoteSlaveContext(client, unit=1)
store = RemoteSlaveContext(client)
context = ModbusServerContext(slaves=store, single=True)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ class BaseModbusAsyncClientProtocol(AsyncModbusClientMixin):
factory = None
transport = None

async def execute(self, request=None):
"""
Executes requests asynchronously
:param request:
:return:
"""
req = self._execute(request)
resp = await asyncio.wait_for(req, timeout=self._timeout)
return resp

def connection_made(self, transport):
"""
Called when a connection is made.
Expand Down Expand Up @@ -120,9 +130,7 @@ def connected(self):
def write_transport(self, packet):
return self.transport.write(packet)


def _execute(self, request, **kwargs):

"""
Starts the producer to send the next request to
consumer.write(Frame(request))
Expand All @@ -139,7 +147,7 @@ def _dataReceived(self, data):
: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)
unit = self.framer.decode_data(data).get("unit", 0)
self.framer.processIncomingPacket(data, self._handleResponse, unit=unit)

def _handleResponse(self, reply, **kwargs):
Expand Down
4 changes: 2 additions & 2 deletions pymodbus/client/asynchronous/factory/serial.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ def async_io_factory(port=None, framer=None, **kwargs):
:return: asyncio event loop and serial client
"""
import asyncio
from pymodbus.client.asynchronous.asyncio import (ModbusClientProtocol,
AsyncioModbusSerialClient)
from pymodbus.client.asynchronous.async_io import (ModbusClientProtocol,
AsyncioModbusSerialClient)
loop = kwargs.pop("loop", None) or asyncio.get_event_loop()
proto_cls = kwargs.pop("proto_cls", None) or ModbusClientProtocol

Expand Down
2 changes: 1 addition & 1 deletion pymodbus/client/asynchronous/factory/tcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.asynchronous.asyncio import init_tcp_client
from pymodbus.client.asynchronous.async_io 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():
Expand Down
2 changes: 1 addition & 1 deletion pymodbus/client/asynchronous/factory/tls.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def async_io_factory(host="127.0.0.1", port=Defaults.TLSPort, sslctx=None,
:return: asyncio event loop and tcp client
"""
import asyncio
from pymodbus.client.asynchronous.asyncio import init_tls_client
from pymodbus.client.asynchronous.async_io import init_tls_client
loop = kwargs.get("loop") or asyncio.new_event_loop()
proto_cls = kwargs.get("proto_cls", None)
if not loop.is_running():
Expand Down
2 changes: 1 addition & 1 deletion pymodbus/client/asynchronous/factory/udp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.asynchronous.asyncio import init_udp_client
from pymodbus.client.asynchronous.async_io 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)
Expand Down
25 changes: 7 additions & 18 deletions pymodbus/client/asynchronous/mixins.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import logging
import asyncio
from pymodbus.client.sync import BaseModbusClient
from pymodbus.bit_read_message import *
from pymodbus.bit_write_message import *
from pymodbus.register_read_message import *
from pymodbus.register_write_message import *
from pymodbus.diag_message import *
from pymodbus.file_message import *
from pymodbus.other_message import *
# from pymodbus.bit_read_message import *
# from pymodbus.bit_write_message import *
# from pymodbus.register_read_message import *
# from pymodbus.register_write_message import *
# from pymodbus.diag_message import *
# from pymodbus.file_message import *
# from pymodbus.other_message import *
from pymodbus.constants import Defaults

from pymodbus.factory import ClientDecoder
Expand Down Expand Up @@ -36,16 +35,6 @@ def __init__(self, framer=None, timeout=2, **kwargs):
framer or ModbusSocketFramer(ClientDecoder()), **kwargs
)

async def execute(self, request=None):
"""
Executes requests asynchronously
:param request:
:return:
"""
req = self._execute(request)
resp = await asyncio.wait_for(req, timeout=self._timeout)
return resp


class AsyncModbusClientMixin(BaseAsyncModbusClient):
"""
Expand Down
2 changes: 1 addition & 1 deletion pymodbus/client/asynchronous/tornado/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def on_receive(self, *args):
if not data:
return
LOGGER.debug("recv: " + " ".join([hex(byte2int(x)) for x in data]))
unit = self.framer.decode_data(data).get("uid", 0)
unit = self.framer.decode_data(data).get("unit", 0)
self.framer.processIncomingPacket(data, self._handle_response, unit=unit)

def execute(self, request=None):
Expand Down
4 changes: 2 additions & 2 deletions pymodbus/client/asynchronous/twisted/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def process():
from pymodbus.client.asynchronous.mixins import AsyncModbusClientMixin
from pymodbus.transaction import FifoTransactionManager, DictTransactionManager
from pymodbus.transaction import ModbusSocketFramer, ModbusRtuFramer
from pymodbus.compat import byte2int
from pymodbus.compat import byte2int
from twisted.python.failure import Failure


Expand Down Expand Up @@ -98,7 +98,7 @@ def dataReceived(self, data):
:param data: The data returned from the server
"""
unit = self.framer.decode_data(data).get("uid", 0)
unit = self.framer.decode_data(data).get("unit", 0)
self.framer.processIncomingPacket(data, self._handleResponse,
unit=unit)

Expand Down
53 changes: 43 additions & 10 deletions pymodbus/repl/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,16 @@ $ pip install pymodbus[repl] --upgrade

## Usage Instructions
RTU and TCP are supported as of now

```
bash-3.2$ pymodbus.console
pymodbus.console --help
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.
--version Show the version and exit.
--verbose Verbose logs
--broadcast-support Support broadcast messages
--help Show this message and exit.
Commands:
serial
Expand All @@ -34,8 +35,9 @@ Commands:
```
TCP Options

```
bash-3.2$ pymodbus.console tcp --help
pymodbus.console tcp --help
Usage: pymodbus.console tcp [OPTIONS]
Options:
Expand All @@ -44,14 +46,11 @@ Options:
--framer TEXT Override the default packet framer tcp|rtu
--help Show this message and exit.
```

SERIAL Options
```
bash-3.2$ pymodbus.console serial --help
pymodbus.console serial --help
Usage: pymodbus.console serial [OPTIONS]
Options:
Expand All @@ -61,18 +60,24 @@ Options:
--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.
Expand Down Expand Up @@ -275,6 +280,34 @@ null
```

To Send broadcast requests, use `--broadcast-support` and send requests with unit id as `0`.
`write_coil`, `write_coils`, `write_register`, `write_registers` are supported.

```
✗ pymodbus.console --broadcast-support tcp --host 192.168.1.8 --port 5020
----------------------------------------------------------------------------
__________ _____ .___ __________ .__
\______ \___.__. / \ ____ __| _/ \______ \ ____ ______ | |
| ___< | |/ \ / \ / _ \ / __ | | _// __ \\____ \| |
| | \___ / Y ( <_> ) /_/ | | | \ ___/| |_> > |__
|____| / ____\____|__ /\____/\____ | /\ |____|_ /\___ > __/|____/
\/ \/ \/ \/ \/ \/|__|
v1.2.0 - [pymodbus, version 2.4.0]
----------------------------------------------------------------------------
> client.write_registers address=0 values=10,20,30,40 unit=0
{
"broadcasted": true
}
> client.write_registers address=0 values=10,20,30,40 unit=1
{
"address": 0,
"count": 4
}
```

## DEMO

[![asciicast](https://asciinema.org/a/y1xOk7lm59U1bRBE2N1pDIj2o.png)](https://asciinema.org/a/y1xOk7lm59U1bRBE2N1pDIj2o)
Expand Down
Loading

0 comments on commit 29f694c

Please sign in to comment.