Skip to main content

Modbus Gateway

A flexible and extensible ModBus gateway written in Python, supporting pluggable frontends and backends, advanced routing, and security filtering.

The gateway allows to bridge, transform, and secure ModBus communication across heterogeneous systems - from serial devices to TCP and Unix domain sockets - while maintaining full control over addressing and access boundaries. It exposes a variety of frontends (emulating serial ports, ModBus TCP over UDS or IP with and without (m)TLS support) as well as backends (serial ports, ModBus IP targets, etc.). It allows multiple applications to access the same backends, providing arbitration and synchronization.

Note: Arbitration does not work when multiple applications access the same virtual serial port frontend. In this case the operating system mimics the behaviour of a real serial port, leading to interleaving of reads and writes of the various applications. When using virtual serial ports each application instance should use their own virtual serial port. This does not apply to ModBus/TCP frontends.

This project has been developed to allow multiple services to access various services attached to the same hardware ModBus network on a machine exploiting multiple RS485 interfaces (identified via unique device names).

Features

  • Pluggable architecture: Modular frontends and backends, allowing further extensions in future versions.
  • Multiple frontends: Virtual serial ports (pty), ModBus TCP over TCP and Unix domain sockets
  • Secure TCP support: TLS and mTLS (with client certificate authentication) for ModBus TCP over TCP/IP
  • Multiple backends: Hardware serial ports and ModBus TCP IP backends
  • Flexible Routing Engine: Map device IDs and registers between frontends and backends; split or aggregate devices across multiple backends and passthrough mode for transparent forwarding
  • Security Filtering: Per-frontend filtering rules enforcing access boundaries on register/device level. Ideal for isolating subsystems or exposing limited views.
  • Future extensions: Planned MQTT interface for IoT integration

Work in Progress

The following features are still work in progress:

  • MQTT support
  • REST API support

Installation

The gateway can be installed via PyPi via

pip install modbus-gateway

or from the repository root via

pip install -e .

The associated client library is available via

pip install modbusgw-client

Again it can also be installed from the repositories modbusgw_client subdirectory via

pip install -e ./modbusgw_client/

Configuration

The default configuration file is located at ~/.config/modbusgateway.cfg. It is composed of a single large JSON dictionary consisting of the following keys:

  • service provides configuration of the main daemon
  • bus configures the internal message bus
  • frontends contains a list of frontend configurations over which clients are capable of accessing the daemon
  • backends is the counterparts and defines the interfaces that are accessed on behalf of the clients via the gateway.
  • routes provides a match-list based configuration on how to route messages between frontends and backends.

The service section configures PID file to prevent multiple running instances, the state directory that will be used for log- and tracefiles as well as the loglevel:

"service" : {
   "log_level" : "INFO",
   "pid_file" : "/var/run/modbusgw.pid",
   "state_dir" : "/var/modbusgw/",
   "reload_grace_seconds" : 5
}

The bus configuration configures the internal buffer for incoming requests that are routed to various backends:

"bus" : {
   "request_queue_size" : 64,
   "response_timeout_ms" : 1500
}

Note that this timeout should be shorter than the applications and frontends timeouts.

Frontend Configurations

Virtual Serial Ports (pty)

Virtual serial ports are directly accessible via pyserial and similar interfaces. This allows existing legacy software to access the gateway via unmodified code by pointing it at the virtual serial port file handles:

{
   "id" : "virtual_serial_rtu",
   "type" : "serial_rtu_socket",
   "socket_path" : "/var/modbusgw/ttyBus0",
   "pty_mode" : "rw",
   "idle_close_seconds" : 600,
   "frame_timeout_ms" : 5.0
}

The shown configuration instantiates a virtual serial port at the specified socket_path, allowing read-write transactions. The frame timeout handles incomplete messages on the application side. The name virtual_serial_rtu is an arbitrary chosen name that is used in the routing configuration.

ModBus IP TCP Socket

A ModBus IP socket speaks the ModBus IP protocol over an TCP socket (optionally supporting TLS or mTLS for authenticated sessions). The following configuration exposes unencrypted ModBus IP applying only IP subnet based filters:

{
   "id" : "frontend_tcp",
   "type" : "tcp_modbus_tcp",
   "host" : "192.0.2.1",
   "port" : 1234,
   "cidr_allow" : [
      "127.0.0.0/8",
      "192.0.2.0/24"
   ]
}

If TLS is desired the following configuration can be added to the frontend configuration object:

   "tls" : {
      "cert_file" : "/path/to/server.crt",
      "key_file" : "/path/to/server.key",
      "ca_file" : "/path/to/rootca.crt",
      "require_client_cert" : true,
      "client_dn_allow" : [
         "CN=ModbusGW Test Client"
      ]
   }

The cert_file and key_file establish the server identity. The ca_file is only used when require_client_cert is set to true to allow client authentication. The additional (optional) client_dn_allow filter allows to filter the DNs from valid certificates (after certificate validation) that are allowed to access the frontend.

Backend Configurations

Hardware Serial Ports

The pyserial backend uses the pyserial library to access an USB to RS485 based interface. This is the most simple hardware interface for DIY setups. The specified serial configuration is applied when accessing the backend. Again the arbitrary id is used in the routing configuration.

{
   "id" : "hardware_serial",
   "type" : "pyserial",
   "device" : "/dev/ttyU0",
   "baudrate" : 9600,
   "parity" : "N",
   "stop_bits" : 1,
   "request_timeout_ms" : 1200
}

ModBus IP via TCP

A TCP backend can be configured via the tcp_modbus backend:

{
   "id" : "tcp_backend",
   "type" : "tcp_modbus",
   "host" : "127.0.0.1",
   "port" : 1234,
   "connect_timeout" : 2.0,
   "pool_size" : 2,
   "use_tls" : true,
   "tls" : {
      "ca_file" : "/path/to/root.crt",
      "cert_file" : "/path/to/client.crt",
      "key_file" : "/path/to/client.key"
   }
}

The use_tls and tls blocks are optional and are only used when (m)TLS is desired. The root.crt is used for validation, the client keys for authentication via mTLS.

Routing Configuration

The routing configuration is provided as a list of routing commands that are matched against incoming requests from the frontends. The first match determines to which backend a message is routed. The backend key and the mirror_to_mqtt key is not used for matching, all other fields apply:

{
   "frontend" : "virtual_serial_rtu",
   "backend" : "hardware_serial",
   "match" : {
      "unit_ids" : [ "*" ],
      "function_codes" : [ "*" ]
   },
   "mirror_to_mqtt" : [ ]
}

The routing match block allows to filter given device IDs and function codes as well as operations. For example to allow only function code 1 (read coils) for the virtual device 5, redirecting the operation to the backend device id 1, one would use

{
   "frontend" : "virtual_serial_rtu",
   "backend" : "hardware_serial",
   "match" : {
      "unit_ids" : [ 5 ],
      "function_codes" : [ 1 ],
      "operations" : [ "read" ]
   },
   "unit_override" : 1,
   "mirror_to_mqtt" : [ ]
}

Here the match block specifies conditions that have to be fulfilled (all have to be fulfilled). The optional unit_override replaces the device ID on the virtual frontend bus to the given unit number before handing off the the backend device. All fields can be used in arbitrary combinations.

Example configuration file

The following configuration exposes a single serial to RS485 interface via a local virtual serial port as well as a ModBus IP socket available via unencrypted TCP:

{
   "service" : {
      "log_level" : "INFO",
      "pid_file" : "/var/run/modbusgw.pid",
      "state_dir" : "/var/modbusgw/",
      "reload_grace_seconds" : 5
   },
   "bus" : {
      "request_queue_size" : 64,
      "response_timeout_ms" : 1500
   },
   "frontends" : [
      {
         "id" : "virtual_serial_rtu",
         "type" : "serial_rtu_socket",
         "socket_path" : "/var/modbusgw/ttyBus0",
         "pty_mode" : "rw",
         "idle_close_seconds" : 600,
         "frame_timeout_ms" : 5.0
      },
      {
         "id" : "frontend_tcp",
         "type" : "tcp_modbus_tcp",
         "host" : "192.0.2.1",
         "port" : 1234,
         "cidr_allow" : [
            "127.0.0.0/8",
            "192.0.2.0/24"
         ]
      }
   ],
   "backends" : [
      {
         "id" : "hardware_serial",
         "type" : "pyserial",
         "device" : "/dev/ttyU0",
         "baudrate" : 9600,
         "parity" : "N",
         "stop_bits" : 1,
         "request_timeout_ms" : 1200
      }
   ],
   "routes" : [
      {
         "frontend" : "virtual_serial_rtu",
         "backend" : "hardware_serial",
         "match" : {
            "unit_ids" : [ "*" ],
            "function_codes" : [ "*" ]
         },
         "mirror_to_mqtt" : [ ]
      },
      {
         "frontend" : "frontend_tcp",
         "backend" : "hardware_serial",
         "match" : {
            "unit_ids" : [ "*" ],
            "function_codes" : [ "*" ]
         },
         "mirror_to_mqtt" : [ ]
      }
   ]
}

FreeBSD rc.init script

The repository contains an rc.init script in the rc.d subdirectory. This is suited for FreeBSDs rc.init infrastructure and allows setting startup parameters in rc.conf:

modbusgw_enable="YES"
modbusgw_user="modbusgw"
modbusgw_group="modbusgw"

The default configuration file location when using the rc.init script is /usr/local/etc/modbusgw/modbusgateway.cfg, which can be overriden via the modbusgw_config variable. In addition the script ensures the presence and writeability of the /var/modbusgw directory. The script supports:

  • start
  • stop
  • status
  • restart
  • reload (note that this does not reload the configuration for logging!)

Running

When not using the rc.init script mentioned above one can launch the application in foreground via

$ modbusgw

To execute the daemon simply add the appropriate lifecycle commands:

$ modbusgw start
$ modbusgw stop
$ modbusgw status
$ modbusgw restart
$ modbusgw reload

Client Library

This repository also contains an independent client library for interacting with ModBus systems via serial ports or ModBus TCP. The documentation is found in the modbusgw-client directory.

Common Pitfalls

  • Seeing collisions even when using modbus-gateway and accessing it via virtual serial ports. Arbitration only starts after the data has entered the application. Virtual serial ports do not provide arbitration between different applications by operating system design. When using this frontend each application should use their own virtual serial port. When accessing the same serial ports the reads and writes may get interleaved and interfer, as for a real serial port. This does not apply to the TCP frontends.

Metadata

Release files for modbus-gateway 0.0.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for modbus-gateway 0.0.5
File Size Uploaded Provenance
modbus_gateway-0.0.5.tar.gz 34.4 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for modbus-gateway 0.0.5
File Interpreter ABI Platform
modbus_gateway-0.0.5-py3-none-any.whl Python 3 none any Details

Total release size: 70.7 kB

Release files / modbus_gateway-0.0.5.tar.gz

Download URL modbus_gateway-0.0.5.tar.gz
Size 34.4 kB
Tags Source
SHA-256 checksum
How to use checksums
b00fe72d50ce52fad7d424713c45a0d6e1f9b1ccf0f61da0437750961249403d
BLAKE2b-256 checksum
How to use checksums
bd4193f7092d403221e115536b0c69e7f7827be93f894b5fd81433be71051ca1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.11.11

Release files / modbus_gateway-0.0.5-py3-none-any.whl

Download URL modbus_gateway-0.0.5-py3-none-any.whl
Size 36.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ff92658ec4294f2b16f3d555dcd962b75896bf79287b502f711e6d17004af833
BLAKE2b-256 checksum
How to use checksums
7aca23caad1b7afb01acf082d11c28e7b84c0d5949e4c95f3d6fbbea26f166cf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.11.11

Security for modbus-gateway 0.0.5

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page