22import inspect
33import logging
44import re
5- import threading
65import time as ttime
76from logging import Logger
87from types import MethodType
3231 TupleOf ,
3332)
3433from ophyd_async .core import (
34+ DEFAULT_TIMEOUT ,
3535 AsyncStatus ,
36+ LazyMock ,
3637 SignalR ,
3738 SignalRW ,
3839 SignalX ,
@@ -839,8 +840,17 @@ class SECoPNodeDevice(StandardReadable):
839840 to the Sec-node properties
840841 """
841842
843+ name : str = ""
844+
842845 def __init__ (
843- self , secclient : AsyncFrappyClient , logger : Logger , logdir : str | None = None
846+ self ,
847+ sec_node_uri : str ,
848+ # `prefix` not used, it's just that the device connecter requires it for
849+ # some reason.
850+ prefix : str = "" ,
851+ name : str = "" ,
852+ loglevel : str = "INFO" ,
853+ logdir : str | None = None ,
844854 ):
845855 """Initializes the node device and generates all node signals and subdevices
846856 corresponding to the SECoP-modules of the secnode
@@ -849,170 +859,98 @@ def __init__(
849859 :type secclient: AsyncFrappyClient
850860 """
851861
852- self .equipment_id : SignalR
853- self .description : SignalR
854- self .version : SignalR
855-
856- self ._secclient : AsyncFrappyClient = secclient
857-
858- self ._module_name : str = ""
859- self ._node_cls_name : str = ""
860- self .mod_devices : Dict [str , SECoPReadableDevice ] = {}
861- self .node_prop_devices : Dict [str , SignalR ] = {}
862-
863- self .genCode : GenNodeCode
864-
865- # Name is set to sec-node equipment_id
866- name = self ._secclient .properties [EQUIPMENT_ID ].replace ("." , "-" )
867-
868- config = []
869-
870- self .logger : Logger = logger
871-
872- self .logger .info (
873- "Initializing SECoPNodeDevice "
874- + f"({ self ._secclient .host } :{ self ._secclient .port } )"
875- )
876-
877- with self .add_children_as_readables (
878- format = StandardReadableFormat .CONFIG_SIGNAL
879- ):
880- for property in self ._secclient .properties :
881- propb = PropertyBackend (property , self ._secclient .properties , secclient )
882- setattr (self , property , SignalR (backend = propb ))
883- config .append (getattr (self , property ))
884- self .node_prop_devices [property ] = getattr (self , property )
885-
886- with self .add_children_as_readables (format = StandardReadableFormat .CHILD ):
887- for module , module_desc in self ._secclient .modules .items ():
862+ self .host , self .port = sec_node_uri .rsplit (":" , maxsplit = 1 )
888863
889- secop_dev_class = self .class_from_interface (module_desc ["properties" ])
890-
891- if secop_dev_class is not None :
892- setattr (
893- self ,
894- module ,
895- secop_dev_class (
896- self ._secclient ,
897- module ,
898- loglevel = self .logger .level ,
899- logdir = logdir ,
900- ),
901- )
902- self .mod_devices [module ] = getattr (self , module )
903-
904- # register secclient callbacks (these are useful if sec node description
905- # changes after a reconnect)
906- secclient .client .register_callback (
907- None , self .descriptiveDataChange , self .nodeStateChange
864+ self .logger : Logger = setup_logging (
865+ name = f"frappy:{ self .host } :{ self .port } " ,
866+ level = LOG_LEVELS [loglevel ],
867+ log_dir = logdir ,
908868 )
869+ self .logdir = logdir
909870
910- super ().__init__ (name = name )
911-
912- @classmethod
913- def create (
914- cls ,
915- host : str ,
916- port : str ,
917- loop ,
918- loglevel : str = "INFO" ,
919- logdir : str | None = None ,
920- ) -> "SECoPNodeDevice" :
921-
922- secclient : AsyncFrappyClient
923-
924- logger : Logger = setup_logging (
925- name = f"frappy:{ host } :{ port } " , level = LOG_LEVELS [loglevel ], log_dir = logdir
926- )
871+ self .name = name
927872
928- if not loop .is_running ():
929- raise Exception ("The provided Eventloop is not running" )
873+ async def connect (
874+ self ,
875+ mock : bool | LazyMock = False ,
876+ timeout : float = DEFAULT_TIMEOUT ,
877+ force_reconnect : bool = False ,
878+ ):
879+ if not hasattr (self , "_secclient" ):
880+ secclient : AsyncFrappyClient
930881
931- if loop ._thread_id != threading .current_thread ().ident :
932- client_future = asyncio .run_coroutine_threadsafe (
933- AsyncFrappyClient .create (host = host , port = port , loop = loop , log = logger ),
934- loop ,
882+ secclient = await AsyncFrappyClient .create (
883+ host = self .host ,
884+ port = self .port ,
885+ loop = asyncio .get_running_loop (),
886+ log = self .logger ,
935887 )
936- secclient = client_future .result ()
937888
938- secclient .external = True
889+ self .equipment_id : SignalR
890+ self .description : SignalR
891+ self .version : SignalR
939892
940- else :
941- raise Exception (
942- "should be calles with an eventloop that is"
943- "running in a seperate thread"
944- )
893+ self ._secclient : AsyncFrappyClient = secclient
945894
946- return SECoPNodeDevice (secclient = secclient , logger = logger , logdir = logdir )
895+ self ._module_name : str = ""
896+ self ._node_cls_name : str = ""
897+ self .mod_devices : Dict [str , SECoPReadableDevice ] = {}
898+ self .node_prop_devices : Dict [str , SignalR ] = {}
947899
948- @classmethod
949- async def create_async (
950- cls ,
951- host : str ,
952- port : str ,
953- loop ,
954- loglevel : str = "INFO" ,
955- logdir : str | None = None ,
956- ) -> "SECoPNodeDevice" :
900+ self .genCode : GenNodeCode
957901
958- logger : Logger = setup_logging (
959- name = f"frappy:{ host } :{ port } " , level = LOG_LEVELS [loglevel ], log_dir = logdir
960- )
902+ if self .name == "" :
903+ self .name = self ._secclient .properties [EQUIPMENT_ID ].replace ("." , "-" )
961904
962- secclient : AsyncFrappyClient
963-
964- if not loop .is_running ():
965- raise Exception ("The provided Eventloop is not running" )
905+ config = []
966906
967- if loop . _thread_id == threading . current_thread (). ident :
968- secclient = await AsyncFrappyClient . create (
969- host = host , port = port , loop = loop , log = logger
907+ self . logger . info (
908+ "Initializing SECoPNodeDevice "
909+ + f"( { self . _secclient . host } : { self . _secclient . port } )"
970910 )
971911
972- secclient .external = False
912+ with self .add_children_as_readables (
913+ format = StandardReadableFormat .CONFIG_SIGNAL
914+ ):
915+ for property in self ._secclient .properties :
916+ propb = PropertyBackend (
917+ property , self ._secclient .properties , secclient
918+ )
919+ setattr (self , property , SignalR (backend = propb ))
920+ config .append (getattr (self , property ))
921+ self .node_prop_devices [property ] = getattr (self , property )
973922
974- else :
975- # Event loop is running in a different thread
976- client_future = asyncio .run_coroutine_threadsafe (
977- AsyncFrappyClient .create (host = host , port = port , loop = loop , log = logger ),
978- loop ,
979- )
980- secclient = await asyncio .wrap_future (future = client_future )
981- secclient .external = True
923+ with self .add_children_as_readables (format = StandardReadableFormat .CHILD ):
924+ for module , module_desc in self ._secclient .modules .items ():
982925
983- return SECoPNodeDevice (secclient = secclient , logger = logger , logdir = logdir )
926+ secop_dev_class = self .class_from_interface (
927+ module_desc ["properties" ]
928+ )
984929
985- def disconnect (self ):
986- """shuts down secclient, eventloop must be running in external thread"""
987- if (
988- self ._secclient .loop ._thread_id == threading .current_thread ().ident
989- and self ._secclient .loop .is_running ()
990- ):
991- raise Exception (
992- "Eventloop must be running in external thread,"
993- " try await node.disconnect_async()"
994- )
995- else :
996- future = asyncio .run_coroutine_threadsafe (
997- self ._secclient .disconnect (True ), self ._secclient .loop
930+ if secop_dev_class is not None :
931+ setattr (
932+ self ,
933+ module ,
934+ secop_dev_class (
935+ self ._secclient ,
936+ module ,
937+ loglevel = self .logger .level ,
938+ logdir = self .logdir ,
939+ ),
940+ )
941+ self .mod_devices [module ] = getattr (self , module )
942+
943+ # register secclient callbacks (these are useful if sec node description
944+ # changes after a reconnect)
945+ secclient .client .register_callback (
946+ None , self .descriptiveDataChange , self .nodeStateChange
998947 )
999948
1000- future . result ( 2 )
949+ super (). __init__ ( name = self . name )
1001950
1002- async def disconnect_async (self ):
1003- """shuts down secclient using asyncio, eventloop can be running in same or
1004- external thread
1005- """
1006- if (
1007- self ._secclient .loop ._thread_id == threading .current_thread ().ident
1008- and self ._secclient .loop .is_running ()
1009- ):
951+ elif force_reconnect or self ._secclient .client .online is False :
1010952 await self ._secclient .disconnect (True )
1011- else :
1012- disconn_future = asyncio .run_coroutine_threadsafe (
1013- self ._secclient .disconnect (True ), self ._secclient .loop
1014- )
1015- await asyncio .wrap_future (future = disconn_future )
953+ await self ._secclient .connect (try_period = DEFAULT_TIMEOUT )
1016954
1017955 def class_from_instance (self , path_to_module : str | None = None ):
1018956 """Dynamically generate python class file for the SECoP_Node_Device, this
0 commit comments