server.py
Go to the documentation of this file.
00001 """
00002 High level interface to pure python OPC-UA server
00003 """
00004 
00005 import logging
00006 from datetime import timedelta
00007 try:
00008     from urllib.parse import urlparse
00009 except ImportError:
00010     from urlparse import urlparse
00011 
00012 
00013 from opcua import ua
00014 # from opcua.binary_server import BinaryServer
00015 from opcua.server.binary_server_asyncio import BinaryServer
00016 from opcua.server.internal_server import InternalServer
00017 from opcua.server.event_generator import EventGenerator
00018 from opcua.common.node import Node
00019 from opcua.common.subscription import Subscription
00020 from opcua.common import xmlimporter
00021 from opcua.common.manage_nodes import delete_nodes
00022 from opcua.client.client import Client
00023 from opcua.crypto import security_policies
00024 from opcua.common.event_objects import BaseEvent
00025 from opcua.common.shortcuts import Shortcuts
00026 from opcua.common.xmlexporter import XmlExporter
00027 from opcua.common.ua_utils import get_nodes_of_namespace
00028 use_crypto = True
00029 try:
00030     from opcua.crypto import uacrypto
00031 except ImportError:
00032     print("cryptography is not installed, use of crypto disabled")
00033     use_crypto = False
00034 
00035 
00036 class Server(object):
00037 
00038     """
00039     High level Server class
00040 
00041     This class creates an opcua server with default values
00042 
00043     Create your own namespace and then populate your server address space
00044     using use the get_root() or get_objects() to get Node objects.
00045     and get_event_object() to fire events.
00046     Then start server. See example_server.py
00047     All methods are threadsafe
00048 
00049     If you need more flexibility you call directly the Ua Service methods
00050     on the iserver  or iserver.isesssion object members.
00051 
00052     During startup the standard address space will be constructed, which may be
00053     time-consuming when running a server on a less powerful device (e.g. a
00054     Raspberry Pi). In order to improve startup performance, a optional path to a
00055     cache file can be passed to the server constructor.
00056     If the parameter is defined, the address space will be loaded from the
00057     cache file or the file will be created if it does not exist yet.
00058     As a result the first startup will be even slower due to the cache file
00059     generation but all further start ups will be significantly faster.
00060 
00061     :ivar application_uri:
00062     :vartype application_uri: uri
00063     :ivar product_uri:
00064     :vartype product_uri: uri
00065     :ivar name:
00066     :vartype name: string
00067     :ivar default_timeout: timeout in milliseconds for sessions and secure channel
00068     :vartype default_timeout: int
00069     :ivar iserver: internal server object
00070     :vartype default_timeout: InternalServer
00071     :ivar bserver: binary protocol server
00072     :vartype bserver: BinaryServer
00073     :ivar nodes: shortcuts to common nodes
00074     :vartype nodes: Shortcuts
00075 
00076     """
00077 
00078     def __init__(self, shelffile=None, iserver=None):
00079         self.logger = logging.getLogger(__name__)
00080         self.endpoint = urlparse("opc.tcp://0.0.0.0:4840/freeopcua/server/")
00081         self.application_uri = "urn:freeopcua:python:server"
00082         self.product_uri = "urn:freeopcua.github.no:python:server"
00083         self.name = "FreeOpcUa Python Server"
00084         self.application_type = ua.ApplicationType.ClientAndServer
00085         self.default_timeout = 3600000
00086         if iserver is not None:
00087             self.iserver = iserver
00088         else:
00089             self.iserver = InternalServer(shelffile)
00090         self.bserver = None
00091         self._discovery_clients = {}
00092         self._discovery_period = 60
00093         self.certificate = None
00094         self.private_key = None
00095         self._policies = []
00096         self.nodes = Shortcuts(self.iserver.isession)
00097 
00098         # setup some expected values
00099         self.register_namespace(self.application_uri)
00100         sa_node = self.get_node(ua.NodeId(ua.ObjectIds.Server_ServerArray))
00101         sa_node.set_value([self.application_uri])
00102 
00103     def __enter__(self):
00104         self.start()
00105         return self
00106 
00107     def __exit__(self, exc_type, exc_value, traceback):
00108         self.stop()
00109 
00110     def load_certificate(self, path):
00111         """
00112         load server certificate from file, either pem or der
00113         """
00114         self.certificate = uacrypto.load_certificate(path)
00115 
00116     def load_private_key(self, path):
00117         self.private_key = uacrypto.load_private_key(path)
00118 
00119     def disable_clock(self, val=True):
00120         """
00121         for debugging you may want to disable clock that write every second
00122         to address space
00123         """
00124         self.iserver.disabled_clock = val
00125 
00126     def set_application_uri(self, uri):
00127         """
00128         Set application/server URI.
00129         This uri is supposed to be unique. If you intent to register
00130         your server to a discovery server, it really should be unique in
00131         your system!
00132         default is : "urn:freeopcua:python:server"
00133         """
00134         self.application_uri = uri
00135 
00136     def find_servers(self, uris=None):
00137         """
00138         find_servers. mainly implemented for symmetry with client
00139         """
00140         if uris is None:
00141             uris = []
00142         params = ua.FindServersParameters()
00143         params.EndpointUrl = self.endpoint.geturl()
00144         params.ServerUris = uris
00145         return self.iserver.find_servers(params)
00146 
00147     def register_to_discovery(self, url="opc.tcp://localhost:4840", period=60):
00148         """
00149         Register to an OPC-UA Discovery server. Registering must be renewed at
00150         least every 10 minutes, so this method will use our asyncio thread to
00151         re-register every period seconds
00152         if period is 0 registration is not automatically renewed
00153         """
00154         # FIXME: have a period per discovery
00155         if url in self._discovery_clients:
00156             self._discovery_clients[url].disconnect()
00157         self._discovery_clients[url] = Client(url)
00158         self._discovery_clients[url].connect()
00159         self._discovery_clients[url].register_server(self)
00160         self._discovery_period = period
00161         if period:
00162             self.iserver.loop.call_soon(self._renew_registration)
00163 
00164     def unregister_to_discovery(self, url="opc.tcp://localhost:4840"):
00165         """
00166         stop registration thread
00167         """
00168         # FIXME: is there really no way to deregister?
00169         self._discovery_clients[url].disconnect()
00170 
00171     def _renew_registration(self):
00172         for client in self._discovery_clients.values():
00173             client.register_server(self)
00174             self.iserver.loop.call_later(self._discovery_period, self._renew_registration)
00175 
00176     def get_client_to_discovery(self, url="opc.tcp://localhost:4840"):
00177         """
00178         Create a client to discovery server and return it
00179         """
00180         client = Client(url)
00181         client.connect()
00182         return client
00183 
00184     def allow_remote_admin(self, allow):
00185         """
00186         Enable or disable the builtin Admin user from network clients
00187         """
00188         self.iserver.allow_remote_admin = allow
00189 
00190     def set_endpoint(self, url):
00191         self.endpoint = urlparse(url)
00192 
00193     def get_endpoints(self):
00194         return self.iserver.get_endpoints()
00195 
00196     def _setup_server_nodes(self):
00197         # to be called just before starting server since it needs all parameters to be setup
00198         self._set_endpoints()
00199         self._policies = [ua.SecurityPolicyFactory()]
00200         if self.certificate and self.private_key:
00201             self._set_endpoints(security_policies.SecurityPolicyBasic128Rsa15,
00202                                 ua.MessageSecurityMode.SignAndEncrypt)
00203             self._policies.append(ua.SecurityPolicyFactory(security_policies.SecurityPolicyBasic128Rsa15,
00204                                                            ua.MessageSecurityMode.SignAndEncrypt,
00205                                                            self.certificate,
00206                                                            self.private_key)
00207                                  )
00208             self._set_endpoints(security_policies.SecurityPolicyBasic128Rsa15,
00209                                 ua.MessageSecurityMode.Sign)
00210             self._policies.append(ua.SecurityPolicyFactory(security_policies.SecurityPolicyBasic128Rsa15,
00211                                                            ua.MessageSecurityMode.Sign,
00212                                                            self.certificate,
00213                                                            self.private_key)
00214                                  )
00215             self._set_endpoints(security_policies.SecurityPolicyBasic256,
00216                                 ua.MessageSecurityMode.SignAndEncrypt)
00217             self._policies.append(ua.SecurityPolicyFactory(security_policies.SecurityPolicyBasic256,
00218                                                            ua.MessageSecurityMode.SignAndEncrypt,
00219                                                            self.certificate,
00220                                                            self.private_key)
00221                                  )
00222             self._set_endpoints(security_policies.SecurityPolicyBasic256,
00223                                 ua.MessageSecurityMode.Sign)
00224             self._policies.append(ua.SecurityPolicyFactory(security_policies.SecurityPolicyBasic256,
00225                                                            ua.MessageSecurityMode.Sign,
00226                                                            self.certificate,
00227                                                            self.private_key)
00228                                  )
00229 
00230     def _set_endpoints(self, policy=ua.SecurityPolicy, mode=ua.MessageSecurityMode.None_):
00231         idtoken = ua.UserTokenPolicy()
00232         idtoken.PolicyId = 'anonymous'
00233         idtoken.TokenType = ua.UserTokenType.Anonymous
00234 
00235         idtoken2 = ua.UserTokenPolicy()
00236         idtoken2.PolicyId = 'certificate_basic256'
00237         idtoken2.TokenType = ua.UserTokenType.Certificate
00238 
00239         idtoken3 = ua.UserTokenPolicy()
00240         idtoken3.PolicyId = 'certificate_basic128'
00241         idtoken3.TokenType = ua.UserTokenType.Certificate
00242 
00243         idtoken4 = ua.UserTokenPolicy()
00244         idtoken4.PolicyId = 'username'
00245         idtoken4.TokenType = ua.UserTokenType.UserName
00246 
00247         appdesc = ua.ApplicationDescription()
00248         appdesc.ApplicationName = ua.LocalizedText(self.name)
00249         appdesc.ApplicationUri = self.application_uri
00250         appdesc.ApplicationType = self.application_type
00251         appdesc.ProductUri = self.product_uri
00252         appdesc.DiscoveryUrls.append(self.endpoint.geturl())
00253 
00254         edp = ua.EndpointDescription()
00255         edp.EndpointUrl = self.endpoint.geturl()
00256         edp.Server = appdesc
00257         if self.certificate:
00258             edp.ServerCertificate = uacrypto.der_from_x509(self.certificate)
00259         edp.SecurityMode = mode
00260         edp.SecurityPolicyUri = policy.URI
00261         edp.UserIdentityTokens = [idtoken, idtoken2, idtoken3, idtoken4]
00262         edp.TransportProfileUri = 'http://opcfoundation.org/UA-Profile/Transport/uatcp-uasc-uabinary'
00263         edp.SecurityLevel = 0
00264         self.iserver.add_endpoint(edp)
00265 
00266     def set_server_name(self, name):
00267         self.name = name
00268 
00269     def start(self):
00270         """
00271         Start to listen on network
00272         """
00273         self._setup_server_nodes()
00274         self.iserver.start()
00275         self.bserver = BinaryServer(self.iserver, self.endpoint.hostname, self.endpoint.port)
00276         self.bserver.set_policies(self._policies)
00277         self.bserver.start()
00278 
00279     def stop(self):
00280         """
00281         Stop server
00282         """
00283         for client in self._discovery_clients.values():
00284             client.disconnect()
00285         self.bserver.stop()
00286         self.iserver.stop()
00287 
00288     def get_root_node(self):
00289         """
00290         Get Root node of server. Returns a Node object.
00291         """
00292         return self.get_node(ua.TwoByteNodeId(ua.ObjectIds.RootFolder))
00293 
00294     def get_objects_node(self):
00295         """
00296         Get Objects node of server. Returns a Node object.
00297         """
00298         return self.get_node(ua.TwoByteNodeId(ua.ObjectIds.ObjectsFolder))
00299 
00300     def get_server_node(self):
00301         """
00302         Get Server node of server. Returns a Node object.
00303         """
00304         return self.get_node(ua.TwoByteNodeId(ua.ObjectIds.Server))
00305 
00306     def get_node(self, nodeid):
00307         """
00308         Get a specific node using NodeId object or a string representing a NodeId
00309         """
00310         return Node(self.iserver.isession, nodeid)
00311 
00312     def create_subscription(self, period, handler):
00313         """
00314         Create a subscription.
00315         returns a Subscription object which allow
00316         to subscribe to events or data on server
00317         """
00318         params = ua.CreateSubscriptionParameters()
00319         params.RequestedPublishingInterval = period
00320         params.RequestedLifetimeCount = 3000
00321         params.RequestedMaxKeepAliveCount = 10000
00322         params.MaxNotificationsPerPublish = 0
00323         params.PublishingEnabled = True
00324         params.Priority = 0
00325         return Subscription(self.iserver.isession, params, handler)
00326 
00327     def get_namespace_array(self):
00328         """
00329         get all namespace defined in server
00330         """
00331         ns_node = self.get_node(ua.NodeId(ua.ObjectIds.Server_NamespaceArray))
00332         return ns_node.get_value()
00333 
00334     def register_namespace(self, uri):
00335         """
00336         Register a new namespace. Nodes should in custom namespace, not 0.
00337         """
00338         ns_node = self.get_node(ua.NodeId(ua.ObjectIds.Server_NamespaceArray))
00339         uries = ns_node.get_value()
00340         if uri in uries:
00341             return uries.index(uri)
00342         uries.append(uri)
00343         ns_node.set_value(uries)
00344         return len(uries) - 1
00345 
00346     def get_namespace_index(self, uri):
00347         """
00348         get index of a namespace using its uri
00349         """
00350         uries = self.get_namespace_array()
00351         return uries.index(uri)
00352 
00353     def get_event_generator(self, etype=None, source=ua.ObjectIds.Server):
00354         """
00355         Returns an event object using an event type from address space.
00356         Use this object to fire events
00357         """
00358         if not etype:
00359             etype = BaseEvent()
00360         return EventGenerator(self.iserver.isession, etype, source)
00361 
00362     def create_custom_data_type(self, idx, name, basetype=ua.ObjectIds.BaseDataType, properties=None):
00363         if properties is None:
00364             properties = []
00365         return self._create_custom_type(idx, name, basetype, properties, [], [])
00366 
00367     def create_custom_event_type(self, idx, name, basetype=ua.ObjectIds.BaseEventType, properties=None):
00368         if properties is None:
00369             properties = []
00370         return self._create_custom_type(idx, name, basetype, properties, [], [])
00371 
00372     def create_custom_object_type(self, idx, name, basetype=ua.ObjectIds.BaseObjectType, properties=None, variables=None, methods=None):
00373         if properties is None:
00374             properties = []
00375         if variables is None:
00376             variables = []
00377         if methods is None:
00378             methods = []
00379         return self._create_custom_type(idx, name, basetype, properties, variables, methods)
00380 
00381     # def create_custom_reference_type(self, idx, name, basetype=ua.ObjectIds.BaseReferenceType, properties=[]):
00382         # return self._create_custom_type(idx, name, basetype, properties)
00383 
00384     def create_custom_variable_type(self, idx, name, basetype=ua.ObjectIds.BaseVariableType, properties=None, variables=None, methods=None):
00385         if properties is None:
00386             properties = []
00387         if variables is None:
00388             variables = []
00389         if methods is None:
00390             methods = []
00391         return self._create_custom_type(idx, name, basetype, properties, variables, methods)
00392 
00393     def _create_custom_type(self, idx, name, basetype, properties, variables, methods):
00394         if isinstance(basetype, Node):
00395             base_t = basetype
00396         elif isinstance(basetype, ua.NodeId):
00397             base_t = Node(self.iserver.isession, basetype)
00398         else:
00399             base_t = Node(self.iserver.isession, ua.NodeId(basetype))
00400 
00401         custom_t = base_t.add_object_type(idx, name)
00402         for prop in properties:
00403             datatype = None
00404             if len(prop) > 2:
00405                 datatype = prop[2]
00406             custom_t.add_property(idx, prop[0], ua.get_default_value(prop[1]), varianttype=prop[1], datatype=datatype)
00407         for variable in variables:
00408             datatype = None
00409             if len(variable) > 2:
00410                 datatype = variable[2]
00411             custom_t.add_variable(idx, variable[0], ua.get_default_value(variable[1]), varianttype=variable[1], datatype=datatype)
00412         for method in methods:
00413             custom_t.add_method(idx, method[0], method[1], method[2], method[3])
00414 
00415         return custom_t
00416 
00417     def import_xml(self, path):
00418         """
00419         Import nodes defined in xml
00420         """
00421         importer = xmlimporter.XmlImporter(self)
00422         return importer.import_xml(path)
00423 
00424     def export_xml(self, nodes, path):
00425         """
00426         Export defined nodes to xml
00427         """
00428         exp = XmlExporter(self)
00429         exp.build_etree(nodes)
00430         return exp.write_xml(path)
00431 
00432     def export_xml_by_ns(self, path, namespaces=None):
00433         """
00434         Export nodes of one or more namespaces to an XML file.  
00435         Namespaces used by nodes are always exported for consistency.
00436         Args:
00437             server: opc ua server to use
00438             path: name of the xml file to write
00439             namespaces: list of string uris or int indexes of the namespace to export, if not provide all ns are used except 0
00440     
00441         Returns:
00442         """
00443         if namespaces is None:
00444             namespaces = []
00445         nodes = get_nodes_of_namespace(self, namespaces)
00446         self.export_xml(nodes, path)
00447 
00448     def delete_nodes(self, nodes, recursive=False):
00449         return delete_nodes(self.iserver.isession, nodes, recursive)
00450 
00451     def historize_node_data_change(self, node, period=timedelta(days=7), count=0):
00452         """
00453         Start historizing supplied nodes; see history module
00454         Args:
00455             node: node or list of nodes that can be historized (variables/properties)
00456             period: time delta to store the history; older data will be deleted from the storage
00457             count: number of changes to store in the history
00458 
00459         Returns:
00460         """
00461         nodes = [node]
00462         for node in nodes:
00463             self.iserver.enable_history_data_change(node, period, count)
00464 
00465     def dehistorize_node_data_change(self, node):
00466         """
00467         Stop historizing supplied nodes; see history module
00468         Args:
00469             node: node or list of nodes that can be historized (UA variables/properties)
00470 
00471         Returns:
00472         """
00473         nodes = [node]
00474         for node in nodes:
00475             self.iserver.disable_history_data_change(node)
00476 
00477     def historize_node_event(self, node, period=timedelta(days=7), count=0):
00478         """
00479         Start historizing events from node (typically a UA object); see history module
00480         Args:
00481             node: node or list of nodes that can be historized (UA objects)
00482             period: time delta to store the history; older data will be deleted from the storage
00483             count: number of events to store in the history
00484 
00485         Returns:
00486         """
00487         nodes = [node]
00488         for node in nodes:
00489             self.iserver.enable_history_event(node, period, count)
00490 
00491     def dehistorize_node_event(self, node):
00492         """
00493         Stop historizing events from node (typically a UA object); see history module
00494         Args:
00495            node: node or list of nodes that can be historized (UA objects)
00496 
00497         Returns:
00498         """
00499         nodes = [node]
00500         for node in nodes:
00501             self.iserver.disable_history_event(node)
00502 
00503     def subscribe_server_callback(self, event, handle):
00504         self.iserver.subscribe_server_callback(event, handle)
00505 
00506     def unsubscribe_server_callback(self, event, handle):
00507         self.iserver.unsubscribe_server_callback(event, handle)
00508 
00509     def link_method(self, node, callback):
00510         """
00511         Link a python function to a UA method in the address space; required when a UA method has been imported
00512         to the address space via XML; the python executable must be linked manually
00513         Args:
00514             node: UA method node
00515             callback: python function that the UA method will call
00516 
00517         Returns:
00518         """
00519         self.iserver.isession.add_method_callback(node.nodeid, callback)


ros_opcua_impl_python_opcua
Author(s): Denis Štogl , Daniel Draper
autogenerated on Sat Jun 8 2019 18:26:23