Skip to content

Commit bfaafba

Browse files
authored
Add ModbusUnixServer / StartAsyncUnixServer. (#1273)
1 parent 94fa6b1 commit bfaafba

File tree

2 files changed

+135
-0
lines changed

2 files changed

+135
-0
lines changed

pymodbus/server/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77
ModbusTcpServer,
88
ModbusTlsServer,
99
ModbusUdpServer,
10+
ModbusUnixServer,
1011
ServerAsyncStop,
1112
ServerStop,
1213
StartAsyncSerialServer,
1314
StartAsyncTcpServer,
1415
StartAsyncTlsServer,
1516
StartAsyncUdpServer,
17+
StartAsyncUnixServer,
1618
StartSerialServer,
1719
StartTcpServer,
1820
StartTlsServer,
@@ -30,12 +32,14 @@
3032
"ModbusTcpServer",
3133
"ModbusTlsServer",
3234
"ModbusUdpServer",
35+
"ModbusUnixServer",
3336
"ServerAsyncStop",
3437
"ServerStop",
3538
"StartAsyncSerialServer",
3639
"StartAsyncTcpServer",
3740
"StartAsyncTlsServer",
3841
"StartAsyncUdpServer",
42+
"StartAsyncUnixServer",
3943
"StartSerialServer",
4044
"StartTcpServer",
4145
"StartTlsServer",

pymodbus/server/async_io.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,107 @@ def _send_(self, data):
483483
# --------------------------------------------------------------------------- #
484484

485485

486+
class ModbusUnixServer:
487+
"""A modbus threaded Unix socket server.
488+
489+
We inherit and overload the socket server so that we
490+
can control the client threads as well as have a single
491+
server context instance.
492+
"""
493+
494+
def __init__(
495+
self,
496+
context,
497+
path,
498+
framer=None,
499+
identity=None,
500+
handler=None,
501+
**kwargs,
502+
):
503+
"""Initialize the socket server.
504+
505+
If the identify structure is not passed in, the ModbusControlBlock
506+
uses its own default structure.
507+
508+
:param context: The ModbusServerContext datastore
509+
:param path: unix socket path
510+
:param framer: The framer strategy to use
511+
:param identity: An optional identify structure
512+
:param handler: A handler for each client session; default is
513+
ModbusConnectedRequestHandler. The handler class
514+
receives connection create/teardown events
515+
:param allow_reuse_address: Whether the server will allow the
516+
reuse of an address.
517+
:param ignore_missing_slaves: True to not send errors on a request
518+
to a missing slave
519+
:param broadcast_enable: True to treat unit_id 0 as broadcast address,
520+
False to treat 0 as any other unit_id
521+
:param response_manipulator: Callback method for manipulating the
522+
response
523+
"""
524+
self.active_connections = {}
525+
self.loop = kwargs.get("loop") or asyncio.get_event_loop()
526+
self.decoder = ServerDecoder()
527+
self.framer = framer or ModbusSocketFramer
528+
self.context = context or ModbusServerContext()
529+
self.control = ModbusControlBlock()
530+
self.path = path
531+
self.handler = handler or ModbusConnectedRequestHandler
532+
self.handler.server = self
533+
self.ignore_missing_slaves = kwargs.get(
534+
"ignore_missing_slaves", Defaults.IgnoreMissingSlaves
535+
)
536+
self.broadcast_enable = kwargs.get("broadcast_enable", Defaults.BroadcastEnable)
537+
self.response_manipulator = kwargs.get("response_manipulator", None)
538+
if isinstance(identity, ModbusDeviceIdentification):
539+
self.control.Identity.update(identity)
540+
541+
# asyncio future that will be done once server has started
542+
self.serving = self.loop.create_future()
543+
# constructors cannot be declared async, so we have to
544+
# defer the initialization of the server
545+
self.server = None
546+
self.factory_parms = {}
547+
548+
async def serve_forever(self):
549+
"""Start endless loop."""
550+
if self.server is None:
551+
self.server = await self.loop.create_unix_server(
552+
lambda: self.handler(self),
553+
*self.path,
554+
**self.factory_parms,
555+
)
556+
self.serving.set_result(True)
557+
try:
558+
await self.server.serve_forever()
559+
except asyncio.exceptions.CancelledError:
560+
raise
561+
except Exception as exc: # pylint: disable=broad-except
562+
txt = f"Server unexpected exception {exc}"
563+
_logger.error(txt)
564+
else:
565+
raise RuntimeError(
566+
"Can't call serve_forever on an already running server object"
567+
)
568+
_logger.info("Server graceful shutdown.")
569+
570+
async def shutdown(self):
571+
"""Shutdown server."""
572+
await self.server_close()
573+
574+
async def server_close(self):
575+
"""Close server."""
576+
for k_item, v_item in self.active_connections.items():
577+
txt = f"aborting active session {k_item}"
578+
_logger.warning(txt)
579+
v_item.handler_task.cancel()
580+
self.active_connections = {}
581+
if self.server is not None:
582+
self.server.close()
583+
await self.server.wait_closed()
584+
self.server = None
585+
586+
486587
class ModbusTcpServer:
487588
"""A modbus threaded tcp socket server.
488589
@@ -998,6 +1099,36 @@ async def async_await_stop(self):
9981099
# self._remove()
9991100

10001101

1102+
async def StartAsyncUnixServer( # pylint: disable=invalid-name,dangerous-default-value
1103+
context=None,
1104+
identity=None,
1105+
path=None,
1106+
custom_functions=[],
1107+
defer_start=False,
1108+
**kwargs,
1109+
):
1110+
"""Start and run a tcp modbus server.
1111+
1112+
:param context: The ModbusServerContext datastore
1113+
:param identity: An optional identify structure
1114+
:param path: An optional path to bind to.
1115+
:param custom_functions: An optional list of custom function classes
1116+
supported by server instance.
1117+
:param defer_start: if set, the server object will be returned ready to start.
1118+
Otherwise, the server will be immediately spun
1119+
up without the ability to shut it off
1120+
:param kwargs: The rest
1121+
:return: an initialized but inactive server object coroutine
1122+
"""
1123+
server = ModbusUnixServer(
1124+
context, path, kwargs.pop("framer", ModbusSocketFramer), identity, **kwargs
1125+
)
1126+
if not defer_start:
1127+
job = _serverList(server, custom_functions, not defer_start)
1128+
await job.run()
1129+
return server
1130+
1131+
10011132
async def StartAsyncTcpServer( # pylint: disable=invalid-name,dangerous-default-value
10021133
context=None,
10031134
identity=None,

0 commit comments

Comments
 (0)