Skip to main content

ModBus TCP proxy

Project description

ModBus TCP proxy

ModBus proxy Python Versions Pypi status License CI

Many modbus devices support only one or very few clients. This proxy acts as a bridge between the client and the modbus device. It can be seen as a layer 7 reverse proxy. This allows multiple clients to communicate with the same modbus device.

When multiple clients are connected, cross messages are avoided by serializing communication on a first come first served REQ/REP basis.

Installation

From within your favorite python 3 environment type:

$ pip install modbus-proxy

Note: On some systems pip points to a python 2 installation. You might need to use pip3 command instead.

Additionally, if you want logging configuration:

  • YAML: pip install modbus-proxy[yaml] (see below)
  • TOML: pip install modbus-proxy[toml] (see below)

Running the server

First, you will need write a configuration file where you specify for each modbus device you which to control:

  • modbus connection (the modbus device url)
  • listen interface (to which url your clients should connect)

Configuration files can be written in YAML (.yml or .yaml) or TOML (.toml).

Suppose you have a PLC modbus device listening on plc1.acme.org:502 and you want your clients to connect to your machine on port 9000. A YAML configuration would look like this:

devices:
- modbus:
    url: plc1.acme.org:502     # device url (mandatory)
    timeout: 10                # communication timeout (s) (optional, default: 10)
    connection_time: 0.1       # delay after connection (s) (optional, default: 0)
  listen:
    bind: 0:9000               # listening address (mandatory)
  unit_id_remapping:           # remap/forward unit IDs (optional, empty by default)
    1: 0

Assuming you saved this file as modbus-config.yml, start the server with:

$ modbus-proxy -c ./modbus-config.yml

Now, instead of connecting your client(s) to plc1.acme.org:502 you just need to tell them to connect to *machine*:9000 (where machine is the host where modbus-proxy is running).

Note that the server is capable of handling multiple modbus devices. Here is a configuration example for 2 devices:

devices:
- modbus:
    url: plc1.acme.org:502
  listen:
    bind: 0:9000
- modbus:
    url: plc2.acme.org:502
  listen:
    bind: 0:9001

If you have a single modbus device, you can avoid writting a configuration file by providing all arguments in the command line:

modbus-proxy -b tcp://0:9000 --modbus tcp://plc1.acme.org:502

(hint: run modbus-proxy --help to see all available options)

Forwarding Unit Identifiers

You can also forward one unit ID to another whilst proxying. This is handy if the target modbus server has a unit on an index that is not supported by one of your clients.

devices:
- modbus: ... # see above.
  listen: ... # see above.
  unit_id_remapping:
    1: 0

The above forwards requests to unit ID 1 to your modbus-proxy server to unit ID 0 on the actual modbus server.

Note that the reverse also applies: if you forward unit ID 1 to unit ID 0, all responses coming from unit 0 will look as if they are coming from 1, so this may pose problems if you want to use unit ID 0 for some clients and unit ID 1 for others (use unit ID 1 for all in that case).

Running the examples

To run the examples you will need to have umodbus installed (do it with pip install umodbus).

Start the simple_tcp_server.py (this will simulate an actual modbus hardware):

$ python examples/simple_tcp_server.py -b :5020

You can run the example client just to be sure direct communication works:

$ python examples/simple_tcp_client.py -a 0:5020
holding registers: [1, 2, 3, 4]

Now for the real test:

Start a modbus-proxy bridge server with:

$ modbus-proxy -b tcp://:9000 --modbus tcp://:5020

Finally run a the example client but now address the proxy instead of the server (notice we are now using port 9000 and not 5020):

$ python examples/simple_tcp_client.py -a 0:9000
holding registers: [1, 2, 3, 4]

Running as a Service

  1. move the config file to a location you can remember, for example: to /usr/lib/mproxy-conf.yaml
  2. go to /etc/systemd/system/
  3. use nano or any other text editor of your choice to create a service file mproxy.service
  4. the file should contain the following information:
[Unit]
Description=Modbus-Proxy
After=network.target

[Service]
Type=simple
Restart=always
ExecStart = modbus-proxy -c ./usr/lib/mproxy-conf.yaml

[Install]
WantedBy=multi-user.target
  1. run systemctl daemon-reload
  2. systemctl enable mproxy.service
  3. systemctl start mproxy.service

The file names given here are examples, you can choose other names, if you wish.

Docker

This project ships with a basic Dockerfile which you can use as a base to launch modbus-proxy inside a docker container.

First, build the docker image with:

$ docker build -t modbus-proxy .

To bridge a single modbus device without needing a configuration file is straight forward:

$ docker run -d -p 5020:502 modbus-proxy -b tcp://0:502 --modbus tcp://plc1.acme.org:502

Now you should be able to access your modbus device through the modbus-proxy by connecting your client(s) to <your-hostname/ip>:5020.

If, instead, you want to use a configuration file, you must mount the file so it is visible by the container.

Assuming you have prepared a conf.yml in the current directory:

devices:
- modbus:
    url: plc1.acme.org:502
  listen:
    bind: 0:502

Here is an example of how to run the container:

docker run -p 5020:502 -v $PWD/conf.yml:/config/modbus-proxy.yml modbus-proxy

By default the Dockerfile will run modbus-proxy -c /config/modbus-proxy.yml so if your mounting that volume you don't need to pass any arguments.

Note that for each modbus device you add in the configuration file you need to publish the corresponding bind port on the host (-p <host port>:<container port> argument).

Logging configuration

Logging configuration can be added to the configuration file by adding a new logging keyword.

The logging configuration will be passed to logging.config.dictConfig() so the file contents must obey the Configuration dictionary schema.

Here is a YAML example:

devices:
- modbus:
    url: plc1.acme.org:502
  listen:
    bind: 0:9000
logging:
  version: 1
  formatters:
    standard:
      format: "%(asctime)s %(levelname)8s %(name)s: %(message)s"
  handlers:
    console:
      class: logging.StreamHandler
      formatter: standard
  root:
    handlers: ['console']
    level: DEBUG

--log-config-file (deprecated)

Logging configuration file.

If a relative path is given, it is relative to the current working directory.

If a .conf or .ini file is given, it is passed directly to logging.config.fileConfig() so the file contents must obey the Configuration file format.

A simple logging configuration (also available at log.conf) which mimics the default configuration looks like this:

[formatters]
keys=standard

[handlers]
keys=console

[loggers]
keys=root

[formatter_standard]
format=%(asctime)s %(levelname)8s %(name)s: %(message)s

[handler_console]
class=StreamHandler
formatter=standard

[logger_root]
level=INFO
handlers=console

A more verbose example logging with a rotating file handler: log-verbose.conf

The same example above (also available at log.yml) can be achieved in YAML with:

version: 1
formatters:
  standard:
    format: "%(asctime)s %(levelname)8s %(name)s: %(message)s"
handlers:
  console:
    class: logging.StreamHandler
    formatter: standard
root:
  handlers: ['console']
  level: DEBUG

Credits

Development Lead

Contributors

None yet. Why not be the first?

History

0.6.1 (2021-09-29)

  • Change default command line --modbus-connection-time from 0.1 to 0
  • Add basic unit tests
  • Github actions
  • Repository cleanup

0.5.0 (2021-09-28)

  • Add support for multiple devices
  • Adapt docker to changes
  • Deprecate --log-config-file command line parameter

0.4.2 (2021-09-23)

  • Add connection time delay (fixes #4)

0.4.1 (2021-01-26)

  • Logging improvements

0.4.0 (2021-01-26)

  • Logging improvements

0.3.0 (2021-01-25)

  • More robust server (fixes #2)

0.2.0 (2021-01-23)

  • Document (README)
  • Add docker intructions (fixes #1)
  • Fix setup dependencies and meta data

0.1.1 (2020-12-02)

  • Fix project package

0.1.0 (2020-11-11)

  • First release on PyPI.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

modbus_proxy-0.8.0.tar.gz (13.8 kB view details)

Uploaded Source

Built Distribution

modbus_proxy-0.8.0-py2.py3-none-any.whl (9.6 kB view details)

Uploaded Python 2 Python 3

File details

Details for the file modbus_proxy-0.8.0.tar.gz.

File metadata

  • Download URL: modbus_proxy-0.8.0.tar.gz
  • Upload date:
  • Size: 13.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.12.4

File hashes

Hashes for modbus_proxy-0.8.0.tar.gz
Algorithm Hash digest
SHA256 97a6b89732a0d6c727045df968cffd4e6e21e63200e7eae2ea1db15dad6689e1
MD5 1c76bcfae0354cb8fa128a2f6bdc510c
BLAKE2b-256 e81bc265f2f53cfedaeea4b13df5517da1c0556c29aaca7b150323e9e5561de6

See more details on using hashes here.

File details

Details for the file modbus_proxy-0.8.0-py2.py3-none-any.whl.

File metadata

File hashes

Hashes for modbus_proxy-0.8.0-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 55f01533d7591021205c61419b9b9ae9b2c926ec6bebd4e436797484f375ed03
MD5 4f98517753af115434468a0fdb4b014b
BLAKE2b-256 16f61a17c0c20043199514fe3a9aad4e0329d6ba51752af1d28a2d3ddbc9a75d

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page