-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathserver_async.py
232 lines (210 loc) · 9.5 KB
/
server_async.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
#!/usr/bin/env python3
"""Pymodbus asynchronous Server Example.
An example of a multi threaded asynchronous server.
usage::
server_async.py [-h] [--comm {tcp,udp,serial,tls}]
[--framer {ascii,binary,rtu,socket,tls}]
[--log {critical,error,warning,info,debug}]
[--port PORT] [--store {sequential,sparse,factory,none}]
[--slaves SLAVES]
-h, --help
show this help message and exit
-c, --comm {tcp,udp,serial,tls}
set communication, default is tcp
-f, --framer {ascii,binary,rtu,socket,tls}
set framer, default depends on --comm
-l, --log {critical,error,warning,info,debug}
set log level, default is info
-p, --port PORT
set port
set serial device baud rate
--store {sequential,sparse,factory,none}
set datastore type
--slaves SLAVES
set number of slaves to respond to
The corresponding client can be started as:
python3 client_sync.py
"""
import asyncio
import logging
import helper
from pymodbus import __version__ as pymodbus_version
from pymodbus.datastore import (
ModbusSequentialDataBlock,
ModbusServerContext,
ModbusSlaveContext,
ModbusSparseDataBlock,
)
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.server import (
StartAsyncSerialServer,
StartAsyncTcpServer,
StartAsyncTlsServer,
StartAsyncUdpServer,
)
_logger = logging.getLogger(__file__)
_logger.setLevel(logging.INFO)
def setup_server(description=None, context=None, cmdline=None):
"""Run server setup."""
args = helper.get_commandline(server=True, description=description, cmdline=cmdline)
if context:
args.context = context
if not args.context:
_logger.info("### Create datastore")
# The datastores only respond to the addresses that are initialized
# If you initialize a DataBlock to addresses of 0x00 to 0xFF, a request to
# 0x100 will respond with an invalid address exception.
# This is because many devices exhibit this kind of behavior (but not all)
if args.store == "sequential":
# Continuing, use a sequential block without gaps.
datablock = ModbusSequentialDataBlock(0x00, [17] * 100)
elif args.store == "sparse":
# Continuing, or use a sparse DataBlock which can have gaps
datablock = ModbusSparseDataBlock({0x00: 0, 0x05: 1})
elif args.store == "factory":
# Alternately, use the factory methods to initialize the DataBlocks
# or simply do not pass them to have them initialized to 0x00 on the
# full address range::
datablock = ModbusSequentialDataBlock.create()
if args.slaves:
# The server then makes use of a server context that allows the server
# to respond with different slave contexts for different slave ids.
# By default it will return the same context for every slave id supplied
# (broadcast mode).
# However, this can be overloaded by setting the single flag to False and
# then supplying a dictionary of slave id to context mapping::
#
# The slave context can also be initialized in zero_mode which means
# that a request to address(0-7) will map to the address (0-7).
# The default is False which is based on section 4.4 of the
# specification, so address(0-7) will map to (1-8)::
context = {
0x01: ModbusSlaveContext(
di=datablock,
co=datablock,
hr=datablock,
ir=datablock,
),
0x02: ModbusSlaveContext(
di=datablock,
co=datablock,
hr=datablock,
ir=datablock,
),
0x03: ModbusSlaveContext(
di=datablock,
co=datablock,
hr=datablock,
ir=datablock,
zero_mode=True,
),
}
single = False
else:
context = ModbusSlaveContext(
di=datablock, co=datablock, hr=datablock, ir=datablock
)
single = True
# Build data storage
args.context = ModbusServerContext(slaves=context, single=single)
# ----------------------------------------------------------------------- #
# initialize the server information
# ----------------------------------------------------------------------- #
# If you don't set this or any fields, they are defaulted to empty strings.
# ----------------------------------------------------------------------- #
args.identity = ModbusDeviceIdentification(
info_name={
"VendorName": "Pymodbus",
"ProductCode": "PM",
"VendorUrl": "https://github.com/pymodbus-dev/pymodbus/",
"ProductName": "Pymodbus Server",
"ModelName": "Pymodbus Server",
"MajorMinorRevision": pymodbus_version,
}
)
return args
async def run_async_server(args):
"""Run server."""
txt = f"### start ASYNC server, listening on {args.port} - {args.comm}"
_logger.info(txt)
if args.comm == "tcp":
address = (args.host if args.host else "", args.port if args.port else None)
server = await StartAsyncTcpServer(
context=args.context, # Data storage
identity=args.identity, # server identify
# TBD host=
# TBD port=
address=address, # listen address
# custom_functions=[], # allow custom handling
framer=args.framer, # The framer strategy to use
# ignore_missing_slaves=True, # ignore request to a missing slave
# broadcast_enable=False, # treat slave_id 0 as broadcast address,
# timeout=1, # waiting time for request to complete
# TBD strict=True, # use strict timing, t1.5 for Modbus RTU
)
elif args.comm == "udp":
address = (
args.host if args.host else "127.0.0.1",
args.port if args.port else None,
)
server = await StartAsyncUdpServer(
context=args.context, # Data storage
identity=args.identity, # server identify
address=address, # listen address
# custom_functions=[], # allow custom handling
framer=args.framer, # The framer strategy to use
# ignore_missing_slaves=True, # ignore request to a missing slave
# broadcast_enable=False, # treat slave_id 0 as broadcast address,
# timeout=1, # waiting time for request to complete
# TBD strict=True, # use strict timing, t1.5 for Modbus RTU
)
elif args.comm == "serial":
# socat -d -d PTY,link=/tmp/ptyp0,raw,echo=0,ispeed=9600
# PTY,link=/tmp/ttyp0,raw,echo=0,ospeed=9600
server = await StartAsyncSerialServer(
context=args.context, # Data storage
identity=args.identity, # server identify
# timeout=1, # waiting time for request to complete
port=args.port, # serial port
# custom_functions=[], # allow custom handling
framer=args.framer, # The framer strategy to use
# stopbits=1, # The number of stop bits to use
# bytesize=8, # The bytesize of the serial messages
# parity="N", # Which kind of parity to use
baudrate=args.baudrate, # The baud rate to use for the serial device
# handle_local_echo=False, # Handle local echo of the USB-to-RS485 adaptor
# ignore_missing_slaves=True, # ignore request to a missing slave
# broadcast_enable=False, # treat slave_id 0 as broadcast address,
# strict=True, # use strict timing, t1.5 for Modbus RTU
)
elif args.comm == "tls":
address = (args.host if args.host else "", args.port if args.port else None)
server = await StartAsyncTlsServer(
context=args.context, # Data storage
host="localhost", # define tcp address where to connect to.
# port=port, # on which port
identity=args.identity, # server identify
# custom_functions=[], # allow custom handling
address=address, # listen address
framer=args.framer, # The framer strategy to use
certfile=helper.get_certificate(
"crt"
), # The cert file path for TLS (used if sslctx is None)
# sslctx=sslctx, # The SSLContext to use for TLS (default None and auto create)
keyfile=helper.get_certificate(
"key"
), # The key file path for TLS (used if sslctx is None)
# password="none", # The password for for decrypting the private key file
# ignore_missing_slaves=True, # ignore request to a missing slave
# broadcast_enable=False, # treat slave_id 0 as broadcast address,
# timeout=1, # waiting time for request to complete
# TBD strict=True, # use strict timing, t1.5 for Modbus RTU
)
return server
async def async_helper():
"""Combine setup and run."""
_logger.info("Starting...")
run_args = setup_server(description="Run asynchronous server.")
await run_async_server(run_args)
if __name__ == "__main__":
asyncio.run(async_helper(), debug=True) # pragma: no cover