Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Fix broadcast error with REPL client #515
  • Loading branch information
dhoomakethu committed Sep 8, 2020
commit 126983bb35048808c14676a1ff62866aaf58a95b
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ Version 2.4.0
* Support async moduls tls server/client
* Add local echo option
* Add exponential backoffs on retries.
* REPL - Support broadcasts.

Version 2.3.0
-----------------------------------------------------------
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
99 changes: 50 additions & 49 deletions pymodbus/repl/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,29 +35,54 @@
GetClearModbusPlusRequest)


def handle_brodcast(func):
def _wrapper(*args, **kwargs):
self = args[0]
resp = func(*args, **kwargs)
if kwargs.get("unit") == 0 and self.broadcast_enable:
return {
'broadcasted': True
}
if not resp.isError():
return {
'function_code': resp.function_code,
'address': resp.address,
'count': resp.count
}
else:
return ExtendedRequestSupport._process_exception(resp, **kwargs)
return _wrapper


class ExtendedRequestSupport(object):

@staticmethod
def _process_exception(resp):
if isinstance(resp, ExceptionResponse):
def _process_exception(resp, **kwargs):
unit = kwargs.get("unit")
if unit == 0:
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
"message": "Broadcast message, ignoring errors!!!"
}
else:
err = {
'error': str(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):
Expand Down Expand Up @@ -98,6 +123,7 @@ def read_discrete_inputs(self, address, count=1, **kwargs):
else:
return ExtendedRequestSupport._process_exception(resp)

@handle_brodcast
def write_coil(self, address, value, **kwargs):
"""
Write `value` to coil at `address`.
Expand All @@ -109,15 +135,9 @@ def write_coil(self, address, value, **kwargs):
"""
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)
return resp

@handle_brodcast
def write_coils(self, address, values, **kwargs):
"""
Write `value` to coil at `address`.
Expand All @@ -129,15 +149,9 @@ def write_coils(self, address, values, **kwargs):
"""
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)
return resp

@handle_brodcast
def write_register(self, address, value, **kwargs):
"""
Write `value` to register at `address`.
Expand All @@ -149,15 +163,9 @@ def write_register(self, address, value, **kwargs):
"""
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)
return resp

@handle_brodcast
def write_registers(self, address, values, **kwargs):
"""
Write list of `values` to registers starting at `address`.
Expand All @@ -169,14 +177,7 @@ def write_registers(self, address, values, **kwargs):
"""
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)
return resp

def read_holding_registers(self, address, count=1, **kwargs):
"""
Expand Down
9 changes: 6 additions & 3 deletions pymodbus/repl/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
\/ \/ \/ \/ \/ \/|__|
v{} - {}
----------------------------------------------------------------------------
""".format("1.1.0", version)
""".format("1.2.0", version)
log = None


Expand Down Expand Up @@ -226,8 +226,9 @@ def _process_args(args, string=True):
@click.group('pymodbus-repl')
@click.version_option(version, message=TITLE)
@click.option("--verbose", is_flag=True, default=False, help="Verbose logs")
@click.option("--broadcast-support", is_flag=True, default=False, help="Support broadcast messages")
@click.pass_context
def main(ctx, verbose):
def main(ctx, verbose, broadcast_support):
if verbose:
global log
import logging
Expand All @@ -236,6 +237,7 @@ def main(ctx, verbose):
log = logging.getLogger('pymodbus')
logging.basicConfig(format=format)
log.setLevel(logging.DEBUG)
ctx.obj = {"broadcast": broadcast_support}


@main.command("tcp")
Expand All @@ -258,7 +260,8 @@ def main(ctx, verbose):
)
def tcp(ctx, host, port, framer):
from pymodbus.repl.client import ModbusTcpClient
kwargs = dict(host=host, port=port)
broadcast = ctx.obj.get("broadcast")
kwargs = dict(host=host, port=port, broadcast_enable=broadcast)
if framer == 'rtu':
from pymodbus.framer.rtu_framer import ModbusRtuFramer
kwargs['framer'] = ModbusRtuFramer
Expand Down