Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
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
2 changes: 1 addition & 1 deletion doc/source/example/modbus_forwarder.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
==================================================
Serial Forwarder Example
Forwarder Example
==================================================
.. literalinclude:: ../../../examples/modbus_forwarder.py
4 changes: 4 additions & 0 deletions doc/source/example/modbus_serial_forwarder.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
==================================================
Serial Forwarder Example
==================================================
.. literalinclude:: ../../../examples/serial_forwarder.py
1 change: 1 addition & 0 deletions doc/source/example/modules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Examples
dbstore_update_server
modbus_logging
modbus_forwarder
modbus_serial_forwarder
payload_client
payload_server
performance
Expand Down
88 changes: 88 additions & 0 deletions examples/serial_forwarder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""Pymodbus SerialRTU2TCP Forwarder

usage :
python3 serial_forwarder.py --log DEBUG --port "/dev/ttyUSB0" --baudrate 9600 --server_ip "192.168.1.27" --server_port 5020 --slaves 1 2 3

sudo python3 serial_forwarder.py --port "/dev/ttyUSB0" --baudrate 9600 --server_ip "192.168.1.27" --server_port=502 --slaves 1 2 3
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a duplicate, please remove. If the code needs a sudo something needs to be corrected.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To what i know, the default Modbus port number is 502

if you want to run on port 502, you need to use sudo

Ports below 1024 are restricted - only apps with root privileges can listen on those. I think this is the general rule on Linux/Unix

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are quite right depending on which system you are on. But independent you have already documented how to call it, so there are really no need to repeat it.

"""
# pylint: disable=unused-argument
import argparse
import logging
import signal

from pymodbus.server.async_io import ModbusTcpServer
from pymodbus.client import ModbusSerialClient
from pymodbus.datastore import ModbusServerContext
from pymodbus.datastore.remote import RemoteSlaveContext

FORMAT = "%(asctime)-15s %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s"
logging.basicConfig(format=FORMAT)
_logger = logging.getLogger()


def raise_graceful_exit(*args):
"""Enters shutdown mode"""
_logger.info("receiving shutdown signal now")
raise SystemExit


class SerialForwarderTCPServer():
"""SerialRTU2TCP Forwarder Server"""

def __init__(self):
"""Initialize the server"""
self.server = None

async def run(self):
"""Run the server"""
port, baudrate, server_port, server_ip, slaves = get_commandline()
client = ModbusSerialClient(method='rtu', port=port, baudrate=baudrate)
message = f'RTU bus on {port} - baudrate {baudrate}'
_logger.info(message)
store = {}
for i in slaves:
store[i] = RemoteSlaveContext(client, unit=i)
context = ModbusServerContext(slaves=store, single=False)
self.server = ModbusTcpServer(context, address=(server_ip, server_port), allow_reuse_address=True)
message = f'serving on {server_ip} port {server_port}'
_logger.info(message)
message = f'listening to slaves {context.slaves()}'
_logger.info(message)
await self.server.serve_forever()

async def stop(self):
"""Stop the server"""
if self.server:
await self.server.shutdown()
_logger.info("TCP server is down")


def get_commandline():
"""Read and validate command line arguments"""
logchoices = ["critical", "error", "warning", "info", "debug"]

parser = argparse.ArgumentParser(description="Command line options for examples")
parser.add_argument("--log", help=",".join(logchoices), default="info", type=str)
parser.add_argument("--port", help="RTU serial port", default='/dev/ttyUSB0', type=str)
parser.add_argument("--baudrate", help="RTU baudrate", default=9600, type=int)
parser.add_argument("--server_port", help="server port", default=5020, type=int)
parser.add_argument("--server_ip", help="server IP", default="127.0.0.1", type=str)
parser.add_argument("--slaves", help="list of slaves to forward", type=int, nargs="+")

args = parser.parse_args()

# set defaults
_logger.setLevel(args.log.upper() if args.log.lower() in logchoices else logging.INFO)
if not args.slaves:
args.slaves = {1, 2, 3}
return args.port, args.baudrate, args.server_port, args.server_ip, args.slaves


if __name__ == "__main__":
server = SerialForwarderTCPServer()
import asyncio
try:
signal.signal(signal.SIGINT, raise_graceful_exit)
asyncio.run(server.run())
finally:
asyncio.run(server.stop())