Skip to content

Commit f4285ec

Browse files
authored
Feature/modbus udp (#43)
* Upgrade Dependencies, expose also UDP port and increase version * Upgrade pre-commit framework and linters * Adding protocol * Add new Version and describe new protocol parameter * Adding protocol * Add UDP mode * fix start message * Adding udp example
1 parent 3ee1c98 commit f4285ec

File tree

10 files changed

+107
-25
lines changed

10 files changed

+107
-25
lines changed

.pre-commit-config.yaml

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,44 @@
1+
12
repos:
23
- repo: https://github.com/pre-commit/pre-commit-hooks
3-
rev: v4.4.0
4+
rev: v5.0.0
45
hooks:
6+
- id: fix-byte-order-marker
57
- id: check-ast
68
- id: check-json
9+
- id: check-yaml
710
- id: debug-statements
811
- id: end-of-file-fixer
12+
- id: trailing-whitespace
913
- id: fix-encoding-pragma
10-
- id: mixed-line-ending
1114
- id: requirements-txt-fixer
12-
- id: trailing-whitespace
15+
- id: mixed-line-ending
16+
args: ['--fix=lf']
17+
description: Forces to replace line ending by the UNIX 'lf' character
18+
- id: detect-aws-credentials
19+
args: ['--allow-missing-credentials']
20+
- id: detect-private-key
21+
- repo: https://github.com/myint/autoflake
22+
rev: v2.3.1
23+
hooks:
24+
- id: autoflake
25+
args:
26+
- --in-place
27+
- --remove-unused-variables
28+
- --remove-all-unused-imports
1329
- repo: https://github.com/hadolint/hadolint
1430
rev: v2.12.0
1531
hooks:
1632
- id: hadolint-docker
17-
- repo: 'https://github.com/charliermarsh/ruff-pre-commit'
18-
rev: v0.2.0
33+
- repo: https://github.com/charliermarsh/ruff-pre-commit
34+
rev: v0.7.0
1935
hooks:
2036
- id: ruff
2137
args:
2238
- '--line-length=120'
2339
- '--fix'
2440
- '--exit-non-zero-on-fix'
25-
- repo: 'https://github.com/pycqa/isort'
41+
- repo: https://github.com/pycqa/isort
2642
rev: 5.13.2
2743
hooks:
2844
- id: isort
@@ -31,8 +47,8 @@ repos:
3147
- '--profile'
3248
- black
3349
- '--filter-files'
34-
- repo: 'https://github.com/psf/black'
35-
rev: 24.1.1
50+
- repo: https://github.com/psf/black
51+
rev: 24.10.0
3652
hooks:
3753
- id: black
3854
args:

.tool-versions

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
pre-commit 3.7.1
1+
python 3.11.4
2+
pre-commit 4.0.1

Dockerfile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
FROM alpine:3.20.1
1+
FROM alpine:3.20.3
22

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

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

1717
EXPOSE 5020/tcp
18+
EXPOSE 5020/udp
1819

1920
USER 1434:1434
2021

Dockerfile.test

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
FROM alpine:3.20.1
1+
FROM alpine:3.20.3
22

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

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

1818
EXPOSE 5020/tcp
19+
EXPOSE 5020/udp
1920

2021
USER 1434:1434
2122

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ Container image: [DockerHub](https://hub.docker.com/r/oitc/modbus-server)
2323

2424
# Supported tags and respective `Dockerfile` links
2525

26-
* [`latest`, `1.3.2`](https://github.com/cybcon/modbus-server/blob/v1.3.2/Dockerfile)
26+
* [`latest`, `1.4.0`](https://github.com/cybcon/modbus-server/blob/v1.4.0/Dockerfile)
27+
* [`1.3.2`](https://github.com/cybcon/modbus-server/blob/v1.3.2/Dockerfile)
2728
* [`1.3.1`](https://github.com/cybcon/modbus-server/blob/v1.3.1/Dockerfile)
2829
* [`1.3.0`](https://github.com/cybcon/modbus-server/blob/v1.3.0/Dockerfile)
2930
* [`1.2.0`](https://github.com/cybcon/modbus-server/blob/v1.2.0/Dockerfile)
@@ -92,6 +93,7 @@ The `/app/modbus_server.json` file comes with following content:
9293
"server": {
9394
"listenerAddress": "0.0.0.0",
9495
"listenerPort": 5020,
96+
"protocol": "TCP",
9597
"tlsParams": {
9698
"description": "path to certificate and private key to enable tls",
9799
"privateKey": null,
@@ -120,7 +122,8 @@ The `/app/modbus_server.json` file comes with following content:
120122
|------------------------------------------|---------|-----------------------------------------------------------------------------------------------------------------------|
121123
| `server` | Object | Modbus slave specific runtime parameters. |
122124
| `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. |
123-
| `server.listenerPort` | Integer | The TCP port number of the modbus slave to listen to. |
125+
| `server.listenerPort` | Integer | The port number of the modbus slave to listen to. |
126+
| `server.protocol` | String | Defines if the server should use `TCP` or `UDP` (default: `TCP`) |
124127
| `server.tlsParams` | Object | Configuration parameters to use TLS encrypted modbus tcp slave. (untested) |
125128
| `server.tlsParams.description` | String | No configuration option, just a description of the parameters. |
126129
| `server.tlsParams.privateKey` | String | Filesystem path of the private key to use for a TLS encrypted communication. |
@@ -176,6 +179,7 @@ Example configuration of pre-defined registers from type "Holding Registers" or
176179
- [src/app/modbus_server.json](https://github.com/cybcon/modbus-server/blob/main/src/app/modbus_server.json)
177180
- [examples/abb_coretec_example.json](https://github.com/cybcon/modbus-server/blob/main/examples/abb_coretec_example.json)
178181
- [examples/test.json](https://github.com/cybcon/modbus-server/blob/main/examples/test.json)
182+
- [examples/udp.json](https://github.com/cybcon/modbus-server/blob/main/examples/udp.json)
179183

180184

181185

examples/abb_coretec_example.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"server": {
33
"listenerAddress": "0.0.0.0",
44
"listenerPort": 1503,
5+
"protocol": "TCP",
56
"tlsParams": {
67
"description": "path to certificate and private key to enable tls",
78
"privateKey": null,

examples/test.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"server": {
33
"listenerAddress": "0.0.0.0",
44
"listenerPort": 5020,
5+
"protocol": "TCP",
56
"tlsParams": {
67
"description": "path to certificate and private key to enable tls",
78
"privateKey": null,

examples/udp.json

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"server": {
3+
"listenerAddress": "0.0.0.0",
4+
"listenerPort": 5020,
5+
"protocol": "UDP",
6+
"tlsParams": {
7+
"description": "path to certificate and private key to enable tls",
8+
"privateKey": null,
9+
"certificate": null
10+
},
11+
"logging": {
12+
"format": "%(asctime)-15s %(threadName)-15s %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s",
13+
"logLevel": "DEBUG"
14+
}
15+
},
16+
"registers": {
17+
"description": "initial values for the register types",
18+
"zeroMode": false,
19+
"initializeUndefinedRegisters": true,
20+
"discreteInput": {
21+
"1": true,
22+
"2": false,
23+
"3": true,
24+
"4": false
25+
},
26+
"coils": {
27+
"1": false,
28+
"2": true,
29+
"3": true,
30+
"4": false
31+
},
32+
"holdingRegister": {
33+
"1": 52225,
34+
"2": "0xCC02",
35+
"3": 52227,
36+
"4": "0xCC04"
37+
},
38+
"inputRegister": {
39+
"1": "0xDD01",
40+
"2": 56578,
41+
"3": "0xDD03",
42+
"4": 56580
43+
}
44+
}
45+
}

src/app/modbus_server.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"server": {
33
"listenerAddress": "0.0.0.0",
44
"listenerPort": 5020,
5+
"protocol": "TCP",
56
"tlsParams": {
67
"description": "path to certificate and private key to enable tls",
78
"privateKey": null,

src/app/modbus_server.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
Author: Michael Oberdorf IT-Consulting
55
Datum: 2020-03-30
66
Last modified by: Michael Oberdorf
7-
Last modified at: 2024-07-19
7+
Last modified at: 2024-10-22
88
*************************************************************************** """
99
import argparse
1010
import json
@@ -21,11 +21,11 @@
2121
ModbusSparseDataBlock,
2222
)
2323
from pymodbus.device import ModbusDeviceIdentification
24-
from pymodbus.server.sync import StartTcpServer, StartTlsServer
24+
from pymodbus.server.sync import StartTcpServer, StartTlsServer, StartUdpServer
2525

2626
# default configuration file path
2727
default_config_file = "/app/modbus_server.json"
28-
VERSION = "1.3.2"
28+
VERSION = "1.4.0"
2929

3030
log = logging.getLogger()
3131

@@ -55,6 +55,7 @@ def get_ip_address() -> str:
5555
def run_server(
5656
listener_address: str = "0.0.0.0",
5757
listener_port: int = 5020,
58+
protocol: str = "TCP",
5859
tls_cert: str = None,
5960
tls_key: str = None,
6061
zero_mode: bool = False,
@@ -67,6 +68,7 @@ def run_server(
6768
Run the modbus server(s)
6869
@param listener_address: string, IP address to bind the listener (default: '0.0.0.0')
6970
@param listener_port: integer, TCP port to bin the listener (default: 5020)
71+
@param protocol: string, defines if the server listenes to TCP or UDP (default: 'TCP')
7072
@param tls_cert: boolean, path to certificate to start tcp server with TLS (default: None)
7173
@param tls_key: boolean, path to private key to start tcp server with TLS (default: None)
7274
@param zero_mode: boolean, request to address(0-7) will map to the address (0-7) instead of (1-8) (default: False)
@@ -157,10 +159,14 @@ def run_server(
157159
address=(listener_address, listener_port),
158160
)
159161
else:
160-
log.info(f"Starting Modbus TCP server on {listener_address}:{listener_port}")
161-
StartTcpServer(context, identity=identity, address=(listener_address, listener_port))
162-
# TCP with different framer
163-
# StartTcpServer(context, identity=identity, framer=ModbusRtuFramer, address=(listener_address, listener_port))
162+
if protocol == "UDP":
163+
log.info(f"Starting Modbus UDP server on {listener_address}:{listener_port}")
164+
StartUdpServer(context, identity=identity, address=(listener_address, listener_port))
165+
else:
166+
log.info(f"Starting Modbus TCP server on {listener_address}:{listener_port}")
167+
StartTcpServer(context, identity=identity, address=(listener_address, listener_port))
168+
# TCP with different framer
169+
# StartTcpServer(context, identity=identity, framer=ModbusRtuFramer, address=(listener_address, listener_port))
164170

165171

166172
def prepare_register(
@@ -281,7 +287,7 @@ def prepare_register(
281287
logging.basicConfig(format=CONFIG["server"]["logging"]["format"])
282288

283289
# start the server
284-
log.info(f"Starting Modbus TCP Server, v{VERSION}")
290+
log.info(f"Starting Modbus Server, v{VERSION}")
285291
log.debug(f"Loaded successfully the configuration file: {config_file}")
286292

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

315+
# add TCP protocol to configuration if not defined
316+
if "protocol" not in CONFIG["server"]:
317+
CONFIG["server"]["protocol"] = "TCP"
318+
309319
# try to get the interface IP address
310320
local_ip_addr = get_ip_address()
311321
if local_ip_addr != "":
312322
log.info(f"Outbound device IP address is: {local_ip_addr}")
313323
run_server(
314324
listener_address=CONFIG["server"]["listenerAddress"],
315325
listener_port=CONFIG["server"]["listenerPort"],
326+
protocol=CONFIG["server"]["protocol"],
316327
tls_cert=CONFIG["server"]["tlsParams"]["privateKey"],
317328
tls_key=CONFIG["server"]["tlsParams"]["certificate"],
318329
zero_mode=CONFIG["registers"]["zeroMode"],

0 commit comments

Comments
 (0)