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
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
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
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
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
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
00382
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)