Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
32 changes: 24 additions & 8 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,28 +1,44 @@

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v5.0.0
hooks:
- id: fix-byte-order-marker
- id: check-ast
- id: check-json
- id: check-yaml
- id: debug-statements
- id: end-of-file-fixer
- id: trailing-whitespace
- id: fix-encoding-pragma
- id: mixed-line-ending
- id: requirements-txt-fixer
- id: trailing-whitespace
- id: mixed-line-ending
args: ['--fix=lf']
description: Forces to replace line ending by the UNIX 'lf' character
- id: detect-aws-credentials
args: ['--allow-missing-credentials']
- id: detect-private-key
- repo: https://github.com/myint/autoflake
rev: v2.3.1
hooks:
- id: autoflake
args:
- --in-place
- --remove-unused-variables
- --remove-all-unused-imports
- repo: https://github.com/hadolint/hadolint
rev: v2.12.0
hooks:
- id: hadolint-docker
- repo: 'https://github.com/charliermarsh/ruff-pre-commit'
rev: v0.2.0
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.7.0
hooks:
- id: ruff
args:
- '--line-length=120'
- '--fix'
- '--exit-non-zero-on-fix'
- repo: 'https://github.com/pycqa/isort'
- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: isort
Expand All @@ -31,8 +47,8 @@ repos:
- '--profile'
- black
- '--filter-files'
- repo: 'https://github.com/psf/black'
rev: 24.1.1
- repo: https://github.com/psf/black
rev: 24.10.0
hooks:
- id: black
args:
Expand Down
3 changes: 2 additions & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pre-commit 3.7.1
python 3.11.4
pre-commit 4.0.1
7 changes: 4 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
FROM alpine:3.20.1
FROM alpine:3.20.3

LABEL maintainer="Michael Oberdorf IT-Consulting <info@oberdorf-itc.de>"
LABEL site.local.program.version="1.3.2"
LABEL site.local.program.version="1.4.0"

RUN apk upgrade --available --no-cache --update \
&& apk add --no-cache --update \
python3=3.12.3-r1 \
python3=3.12.7-r0 \
py3-pip=24.0-r2 \
# Cleanup APK
&& rm -rf /var/cache/apk/* /tmp/* /var/tmp/*
Expand All @@ -15,6 +15,7 @@ COPY --chown=root:root /src /
RUN pip3 install --no-cache-dir -r /requirements.txt --break-system-packages

EXPOSE 5020/tcp
EXPOSE 5020/udp

USER 1434:1434

Expand Down
7 changes: 4 additions & 3 deletions Dockerfile.test
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
FROM alpine:3.20.1
FROM alpine:3.20.3

LABEL maintainer="Michael Oberdorf IT-Consulting <info@oberdorf-itc.de>"
LABEL site.local.program.version="1.3.2"
LABEL site.local.program.version="1.4.0"

RUN apk upgrade --available --no-cache --update \
&& apk add --no-cache --update \
python3=3.12.3-r1 \
python3=3.12.7-r0 \
py3-pip=24.0-r2 \
# Cleanup APK
&& rm -rf /var/cache/apk/* /tmp/* /var/tmp/*
Expand All @@ -16,6 +16,7 @@ COPY --chown=root:root /examples/test.json /test.json
RUN pip3 install --no-cache-dir -r /requirements.txt --break-system-packages

EXPOSE 5020/tcp
EXPOSE 5020/udp

USER 1434:1434

Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ Container image: [DockerHub](https://hub.docker.com/r/oitc/modbus-server)

# Supported tags and respective `Dockerfile` links

* [`latest`, `1.3.2`](https://github.com/cybcon/modbus-server/blob/v1.3.2/Dockerfile)
* [`latest`, `1.4.0`](https://github.com/cybcon/modbus-server/blob/v1.4.0/Dockerfile)
* [`1.3.2`](https://github.com/cybcon/modbus-server/blob/v1.3.2/Dockerfile)
* [`1.3.1`](https://github.com/cybcon/modbus-server/blob/v1.3.1/Dockerfile)
* [`1.3.0`](https://github.com/cybcon/modbus-server/blob/v1.3.0/Dockerfile)
* [`1.2.0`](https://github.com/cybcon/modbus-server/blob/v1.2.0/Dockerfile)
Expand Down Expand Up @@ -92,6 +93,7 @@ The `/app/modbus_server.json` file comes with following content:
"server": {
"listenerAddress": "0.0.0.0",
"listenerPort": 5020,
"protocol": "TCP",
"tlsParams": {
"description": "path to certificate and private key to enable tls",
"privateKey": null,
Expand Down Expand Up @@ -120,7 +122,8 @@ The `/app/modbus_server.json` file comes with following content:
|------------------------------------------|---------|-----------------------------------------------------------------------------------------------------------------------|
| `server` | Object | Modbus slave specific runtime parameters. |
| `server.listenerAddress` | String | The IPv4 Address to bound to when starting the server. `"0.0.0.0"` let the server listens on all interface addresses. |
| `server.listenerPort` | Integer | The TCP port number of the modbus slave to listen to. |
| `server.listenerPort` | Integer | The port number of the modbus slave to listen to. |
| `server.protocol` | String | Defines if the server should use `TCP` or `UDP` (default: `TCP`) |
| `server.tlsParams` | Object | Configuration parameters to use TLS encrypted modbus tcp slave. (untested) |
| `server.tlsParams.description` | String | No configuration option, just a description of the parameters. |
| `server.tlsParams.privateKey` | String | Filesystem path of the private key to use for a TLS encrypted communication. |
Expand Down Expand Up @@ -176,6 +179,7 @@ Example configuration of pre-defined registers from type "Holding Registers" or
- [src/app/modbus_server.json](https://github.com/cybcon/modbus-server/blob/main/src/app/modbus_server.json)
- [examples/abb_coretec_example.json](https://github.com/cybcon/modbus-server/blob/main/examples/abb_coretec_example.json)
- [examples/test.json](https://github.com/cybcon/modbus-server/blob/main/examples/test.json)
- [examples/udp.json](https://github.com/cybcon/modbus-server/blob/main/examples/udp.json)



Expand Down
1 change: 1 addition & 0 deletions examples/abb_coretec_example.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"server": {
"listenerAddress": "0.0.0.0",
"listenerPort": 1503,
"protocol": "TCP",
"tlsParams": {
"description": "path to certificate and private key to enable tls",
"privateKey": null,
Expand Down
1 change: 1 addition & 0 deletions examples/test.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"server": {
"listenerAddress": "0.0.0.0",
"listenerPort": 5020,
"protocol": "TCP",
"tlsParams": {
"description": "path to certificate and private key to enable tls",
"privateKey": null,
Expand Down
45 changes: 45 additions & 0 deletions examples/udp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"server": {
"listenerAddress": "0.0.0.0",
"listenerPort": 5020,
"protocol": "UDP",
"tlsParams": {
"description": "path to certificate and private key to enable tls",
"privateKey": null,
"certificate": null
},
"logging": {
"format": "%(asctime)-15s %(threadName)-15s %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s",
"logLevel": "DEBUG"
}
},
"registers": {
"description": "initial values for the register types",
"zeroMode": false,
"initializeUndefinedRegisters": true,
"discreteInput": {
"1": true,
"2": false,
"3": true,
"4": false
},
"coils": {
"1": false,
"2": true,
"3": true,
"4": false
},
"holdingRegister": {
"1": 52225,
"2": "0xCC02",
"3": 52227,
"4": "0xCC04"
},
"inputRegister": {
"1": "0xDD01",
"2": 56578,
"3": "0xDD03",
"4": 56580
}
}
}
1 change: 1 addition & 0 deletions src/app/modbus_server.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"server": {
"listenerAddress": "0.0.0.0",
"listenerPort": 5020,
"protocol": "TCP",
"tlsParams": {
"description": "path to certificate and private key to enable tls",
"privateKey": null,
Expand Down
27 changes: 19 additions & 8 deletions src/app/modbus_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
Author: Michael Oberdorf IT-Consulting
Datum: 2020-03-30
Last modified by: Michael Oberdorf
Last modified at: 2024-07-19
Last modified at: 2024-10-22
*************************************************************************** """
import argparse
import json
Expand All @@ -21,11 +21,11 @@
ModbusSparseDataBlock,
)
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.server.sync import StartTcpServer, StartTlsServer
from pymodbus.server.sync import StartTcpServer, StartTlsServer, StartUdpServer

# default configuration file path
default_config_file = "/app/modbus_server.json"
VERSION = "1.3.2"
VERSION = "1.4.0"

log = logging.getLogger()

Expand Down Expand Up @@ -55,6 +55,7 @@ def get_ip_address() -> str:
def run_server(
listener_address: str = "0.0.0.0",
listener_port: int = 5020,
protocol: str = "TCP",
tls_cert: str = None,
tls_key: str = None,
zero_mode: bool = False,
Expand All @@ -67,6 +68,7 @@ def run_server(
Run the modbus server(s)
@param listener_address: string, IP address to bind the listener (default: '0.0.0.0')
@param listener_port: integer, TCP port to bin the listener (default: 5020)
@param protocol: string, defines if the server listenes to TCP or UDP (default: 'TCP')
@param tls_cert: boolean, path to certificate to start tcp server with TLS (default: None)
@param tls_key: boolean, path to private key to start tcp server with TLS (default: None)
@param zero_mode: boolean, request to address(0-7) will map to the address (0-7) instead of (1-8) (default: False)
Expand Down Expand Up @@ -157,10 +159,14 @@ def run_server(
address=(listener_address, listener_port),
)
else:
log.info(f"Starting Modbus TCP server on {listener_address}:{listener_port}")
StartTcpServer(context, identity=identity, address=(listener_address, listener_port))
# TCP with different framer
# StartTcpServer(context, identity=identity, framer=ModbusRtuFramer, address=(listener_address, listener_port))
if protocol == "UDP":
log.info(f"Starting Modbus UDP server on {listener_address}:{listener_port}")
StartUdpServer(context, identity=identity, address=(listener_address, listener_port))
else:
log.info(f"Starting Modbus TCP server on {listener_address}:{listener_port}")
StartTcpServer(context, identity=identity, address=(listener_address, listener_port))
# TCP with different framer
# StartTcpServer(context, identity=identity, framer=ModbusRtuFramer, address=(listener_address, listener_port))


def prepare_register(
Expand Down Expand Up @@ -281,7 +287,7 @@ def prepare_register(
logging.basicConfig(format=CONFIG["server"]["logging"]["format"])

# start the server
log.info(f"Starting Modbus TCP Server, v{VERSION}")
log.info(f"Starting Modbus Server, v{VERSION}")
log.debug(f"Loaded successfully the configuration file: {config_file}")

# be sure the data types within the dictionaries are correct (json will only allow strings as keys)
Expand All @@ -306,13 +312,18 @@ def prepare_register(
initialize_undefined_registers=CONFIG["registers"]["initializeUndefinedRegisters"],
)

# add TCP protocol to configuration if not defined
if "protocol" not in CONFIG["server"]:
CONFIG["server"]["protocol"] = "TCP"

# try to get the interface IP address
local_ip_addr = get_ip_address()
if local_ip_addr != "":
log.info(f"Outbound device IP address is: {local_ip_addr}")
run_server(
listener_address=CONFIG["server"]["listenerAddress"],
listener_port=CONFIG["server"]["listenerPort"],
protocol=CONFIG["server"]["protocol"],
tls_cert=CONFIG["server"]["tlsParams"]["privateKey"],
tls_key=CONFIG["server"]["tlsParams"]["certificate"],
zero_mode=CONFIG["registers"]["zeroMode"],
Expand Down
Loading