@@ -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+
486587class 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+
10011132async def StartAsyncTcpServer ( # pylint: disable=invalid-name,dangerous-default-value
10021133 context = None ,
10031134 identity = None ,
0 commit comments