Completed
Push — master ( 144955...e7a349 )
by Olivier
04:23
created

Server.set_security_policy()   A

Complexity

Conditions 1

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1.125

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
c 1
b 0
f 0
dl 0
loc 22
ccs 1
cts 2
cp 0.5
crap 1.125
rs 9.2
1
"""
2
High level interface to pure python OPC-UA server
3
"""
4
5 1
import logging
6 1
from datetime import timedelta, datetime
7 1
try:
8 1
    from urllib.parse import urlparse
9
except ImportError:
10
    from urlparse import urlparse
11
12
13 1
from opcua import ua
14
# from opcua.binary_server import BinaryServer
15 1
from opcua.server.binary_server_asyncio import BinaryServer
16 1
from opcua.server.internal_server import InternalServer
17 1
from opcua.server.event_generator import EventGenerator
18 1
from opcua.common.node import Node
19 1
from opcua.common.subscription import Subscription
20 1
from opcua.common.manage_nodes import delete_nodes
21 1
from opcua.client.client import Client
22 1
from opcua.crypto import security_policies
23 1
from opcua.common.event_objects import BaseEvent
24 1
from opcua.common.shortcuts import Shortcuts
25 1
from opcua.common.structures import load_type_definitions
26 1
from opcua.common.xmlexporter import XmlExporter
27 1
from opcua.common.xmlimporter import XmlImporter
28 1
from opcua.common.ua_utils import get_nodes_of_namespace
29 1
use_crypto = True
30 1
try:
31 1
    from opcua.crypto import uacrypto
32
except ImportError:
33
    logging.getLogger(__name__).warning("cryptography is not installed, use of crypto disabled")
34
    use_crypto = False
35
36
37 1
class Server(object):
38
39
    """
40
    High level Server class
41
42
    This class creates an opcua server with default values
43
44
    Create your own namespace and then populate your server address space
45
    using use the get_root() or get_objects() to get Node objects.
46
    and get_event_object() to fire events.
47
    Then start server. See example_server.py
48
    All methods are threadsafe
49
50
    If you need more flexibility you call directly the Ua Service methods
51
    on the iserver  or iserver.isession object members.
52
53
    During startup the standard address space will be constructed, which may be
54
    time-consuming when running a server on a less powerful device (e.g. a
55
    Raspberry Pi). In order to improve startup performance, a optional path to a
56
    cache file can be passed to the server constructor.
57
    If the parameter is defined, the address space will be loaded from the
58
    cache file or the file will be created if it does not exist yet.
59
    As a result the first startup will be even slower due to the cache file
60
    generation but all further start ups will be significantly faster.
61
62
    :ivar product_uri:
63
    :vartype product_uri: uri
64
    :ivar name:
65
    :vartype name: string
66
    :ivar default_timeout: timeout in milliseconds for sessions and secure channel
67
    :vartype default_timeout: int
68
    :ivar iserver: internal server object
69
    :vartype default_timeout: InternalServer
70
    :ivar bserver: binary protocol server
71
    :vartype bserver: BinaryServer
72
    :ivar nodes: shortcuts to common nodes
73
    :vartype nodes: Shortcuts
74
75
    """
76
77 1
    def __init__(self, shelffile=None, iserver=None):
78 1
        self.logger = logging.getLogger(__name__)
79 1
        self.endpoint = urlparse("opc.tcp://0.0.0.0:4840/freeopcua/server/")
80 1
        self._application_uri = "urn:freeopcua:python:server"
81 1
        self.product_uri = "urn:freeopcua.github.io:python:server"
82 1
        self.name = "FreeOpcUa Python Server"
83 1
        self.manufacturer_name = "FreeOpcUa"
84 1
        self.application_type = ua.ApplicationType.ClientAndServer
85 1
        self.default_timeout = 3600000
86 1
        if iserver is not None:
87
            self.iserver = iserver
88
        else:
89 1
            self.iserver = InternalServer(shelffile)
90 1
        self.bserver = None
91 1
        self._discovery_clients = {}
92 1
        self._discovery_period = 60
93 1
        self.certificate = None
94 1
        self.private_key = None
95 1
        self._policies = []
96 1
        self.nodes = Shortcuts(self.iserver.isession)
97
98
        # setup some expected values
99 1
        self.set_application_uri(self._application_uri)
100 1
        sa_node = self.get_node(ua.NodeId(ua.ObjectIds.Server_ServerArray))
101 1
        sa_node.set_value([self._application_uri])
102 1
        status_node = self.get_node(ua.NodeId(ua.ObjectIds.Server_ServerStatus))
103 1
        build_node = self.get_node(ua.NodeId(ua.ObjectIds.Server_ServerStatus_BuildInfo))
104 1
        status = ua.ServerStatusDataType()
105 1
        status.BuildInfo.ProductUri = self.product_uri
106 1
        status.BuildInfo.ManufacturerName = self.manufacturer_name
107 1
        status.BuildInfo.ProductName = self.name
108 1
        status.BuildInfo.SoftwareVersion = "1.0pre"
109 1
        status.BuildInfo.BuildNumber = "0"
110 1
        status.BuildInfo.BuildDate = datetime.now()
111 1
        status.SecondsTillShutdown = 0
112 1
        status_node.set_value(status)
113 1
        build_node.set_value(status.BuildInfo)
114
115
116
        # enable all endpoints by default
117 1
        self._security_policy = ["None", "Basic128Rsa15_Sign", "Basic128Rsa15_SignAndEncrypt", "Basic256_Sign", "Basic256_SignAndEncrypt"]
118 1
        self._policyIDs = ["Anonymous", "Username"]
119
        #self._policyIDs = ["Anonymous", "Basic256", "Basic128", "Username"]
120
121 1
    def __enter__(self):
122 1
        self.start()
123 1
        return self
124
125 1
    def __exit__(self, exc_type, exc_value, traceback):
126 1
        self.stop()
127
128 1
    def load_certificate(self, path):
129
        """
130
        load server certificate from file, either pem or der
131
        """
132 1
        self.certificate = uacrypto.load_certificate(path)
133
134 1
    def load_private_key(self, path):
135 1
        self.private_key = uacrypto.load_private_key(path)
136
137 1
    def disable_clock(self, val=True):
138
        """
139
        for debugging you may want to disable clock that write every second
140
        to address space
141
        """
142
        self.iserver.disabled_clock = val
143
144 1
    def set_application_uri(self, uri):
145
        """
146
        Set application/server URI.
147
        This uri is supposed to be unique. If you intent to register
148
        your server to a discovery server, it really should be unique in
149
        your system!
150
        default is : "urn:freeopcua:python:server"
151
        """
152 1
        self._application_uri = uri
153 1
        ns_node = self.get_node(ua.NodeId(ua.ObjectIds.Server_NamespaceArray))
154 1
        uries = ns_node.get_value()
155 1
        if len(uries) > 1:
156 1
            uries[1] = uri  # application uri is always namespace 1
157
        else:
158 1
            uries.append(uri)
159 1
        ns_node.set_value(uries)
160
161 1
    def find_servers(self, uris=None):
162
        """
163
        find_servers. mainly implemented for symmetry with client
164
        """
165 1
        if uris is None:
166 1
            uris = []
167 1
        params = ua.FindServersParameters()
168 1
        params.EndpointUrl = self.endpoint.geturl()
169 1
        params.ServerUris = uris
170 1
        return self.iserver.find_servers(params)
171
172 1
    def register_to_discovery(self, url="opc.tcp://localhost:4840", period=60):
173
        """
174
        Register to an OPC-UA Discovery server. Registering must be renewed at
175
        least every 10 minutes, so this method will use our asyncio thread to
176
        re-register every period seconds
177
        if period is 0 registration is not automatically renewed
178
        """
179
        # FIXME: have a period per discovery
180 1
        if url in self._discovery_clients:
181 1
            self._discovery_clients[url].disconnect()
182 1
        self._discovery_clients[url] = Client(url)
183 1
        self._discovery_clients[url].connect()
184 1
        self._discovery_clients[url].register_server(self)
185 1
        self._discovery_period = period
186 1
        if period:
187
            self.iserver.loop.call_soon(self._renew_registration)
188
189 1
    def unregister_to_discovery(self, url="opc.tcp://localhost:4840"):
190
        """
191
        stop registration thread
192
        """
193
        # FIXME: is there really no way to deregister?
194
        self._discovery_clients[url].disconnect()
195
196 1
    def _renew_registration(self):
197
        for client in self._discovery_clients.values():
198
            client.register_server(self)
199
            self.iserver.loop.call_later(self._discovery_period, self._renew_registration)
200
201 1
    def get_client_to_discovery(self, url="opc.tcp://localhost:4840"):
202
        """
203
        Create a client to discovery server and return it
204
        """
205
        client = Client(url)
206
        client.connect()
207
        return client
208
209 1
    def allow_remote_admin(self, allow):
210
        """
211
        Enable or disable the builtin Admin user from network clients
212
        """
213
        self.iserver.allow_remote_admin = allow
214
215 1
    def set_endpoint(self, url):
216 1
        self.endpoint = urlparse(url)
217
218 1
    def get_endpoints(self):
219 1
        return self.iserver.get_endpoints()
220
221 1
    def set_security_policy(self, security_policy):
222
        """
223
            Method setting up the security policies for connections
224
            to the server. During server object initialization, all
225
            possible endpoints are enabled:
226
227
                security_policy = ["None",
228
                                    "Basic128Rsa15_Sign",
229
                                    "Basic128Rsa15_SignAndEncrypt", 
230
                                    "Basic256_Sign",
231
                                    "Basic256_SignAndEncrypt"]
232
233
            where security_policy is a list of strings. "None" enables an 
234
            endpoint without any security.
235
236
            E.g. to limit the number of endpoints and disable no encryption:
237
238
                set_security_policy(["Basic256_Sign", 
239
                                        "Basic256_SignAndEncrypt"])
240
241
        """
242
        self._security_policy = security_policy
243
244 1
    def set_security_IDs(self, policyIDs):
245
        """
246
            Method setting up the security endpoints for identification
247
            of clients. During server object initialization, all possible 
248
            endpoints are enabled:
249
250
            self._policyIDs = ["Anonymous", "Basic256", "Basic128", "Username"]
251
252
            E.g. to limit the number of IDs and disable anonymous clients:
253
254
                set_security_policy(["Basic256"])
255
256
            (Implementation for ID check is currently not finalized...)
257
258
        """
259
        self._policyIDs = policyIDs
260
261 1
    def _setup_server_nodes(self):
262
        # to be called just before starting server since it needs all parameters to be setup
263 1
        if "None" in self._security_policy:
264 1
            self._set_endpoints()
265 1
            self._policies = [ua.SecurityPolicyFactory()]
266 1
            if (len(self._security_policy)>1) and self.private_key:
267 1
                self.logger.warning("Creating an open endpoint to the server, although encrypted endpoints are enabled.")
268
269 1
        if self.certificate and self.private_key:
270 1
            if "Basic128Rsa15_SignAndEncrypt" in self._security_policy:
271 1
                self._set_endpoints(security_policies.SecurityPolicyBasic128Rsa15,
272
                                    ua.MessageSecurityMode.SignAndEncrypt)
273 1
                self._policies.append(ua.SecurityPolicyFactory(security_policies.SecurityPolicyBasic128Rsa15,
274
                                                           ua.MessageSecurityMode.SignAndEncrypt,
275
                                                           self.certificate,
276
                                                           self.private_key)
277
                                 )
278 1
            if "Basic128Rsa15_Sign" in self._security_policy:
279 1
                self._set_endpoints(security_policies.SecurityPolicyBasic128Rsa15,
280
                                    ua.MessageSecurityMode.Sign)
281 1
                self._policies.append(ua.SecurityPolicyFactory(security_policies.SecurityPolicyBasic128Rsa15,
282
                                                           ua.MessageSecurityMode.Sign,
283
                                                           self.certificate,
284
                                                           self.private_key)
285
                                 )
286 1
            if "Basic256_SignAndEncrypt" in self._security_policy:
287 1
                self._set_endpoints(security_policies.SecurityPolicyBasic256,
288
                                    ua.MessageSecurityMode.SignAndEncrypt)
289 1
                self._policies.append(ua.SecurityPolicyFactory(security_policies.SecurityPolicyBasic256,
290
                                                           ua.MessageSecurityMode.SignAndEncrypt,
291
                                                           self.certificate,
292
                                                           self.private_key)
293
                                 )
294 1
            if "Basic256_Sign" in self._security_policy:
295 1
                self._set_endpoints(security_policies.SecurityPolicyBasic256,
296
                                    ua.MessageSecurityMode.Sign)
297 1
                self._policies.append(ua.SecurityPolicyFactory(security_policies.SecurityPolicyBasic256,
298
                                                           ua.MessageSecurityMode.Sign,
299
                                                           self.certificate,
300
                                                           self.private_key)
301
                                 )
302
303 1
    def _set_endpoints(self, policy=ua.SecurityPolicy, mode=ua.MessageSecurityMode.None_):
304 1
        idtokens = []
305 1
        if "Anonymous" in self._policyIDs:
306 1
            idtoken = ua.UserTokenPolicy()
307 1
            idtoken.PolicyId = 'anonymous'
308 1
            idtoken.TokenType = ua.UserTokenType.Anonymous
309 1
            idtokens.append(idtoken)
310
311 1
        if "Basic256" in self._policyIDs:
312
            idtoken = ua.UserTokenPolicy()
313
            idtoken.PolicyId = 'certificate_basic256'
314
            idtoken.TokenType = ua.UserTokenType.Certificate
315
            idtokens.append(idtoken)
316
317 1
        if "Basic128" in self._policyIDs:
318
            idtoken = ua.UserTokenPolicy()
319
            idtoken.PolicyId = 'certificate_basic128'
320
            idtoken.TokenType = ua.UserTokenType.Certificate
321
            idtokens.append(idtoken)
322
323 1
        if "Username" in self._policyIDs:
324 1
            idtoken = ua.UserTokenPolicy()
325 1
            idtoken.PolicyId = 'username'
326 1
            idtoken.TokenType = ua.UserTokenType.UserName
327 1
            idtokens.append(idtoken)
328
329 1
        appdesc = ua.ApplicationDescription()
330 1
        appdesc.ApplicationName = ua.LocalizedText(self.name)
331 1
        appdesc.ApplicationUri = self._application_uri
332 1
        appdesc.ApplicationType = self.application_type
333 1
        appdesc.ProductUri = self.product_uri
334 1
        appdesc.DiscoveryUrls.append(self.endpoint.geturl())
335
336 1
        edp = ua.EndpointDescription()
337 1
        edp.EndpointUrl = self.endpoint.geturl()
338 1
        edp.Server = appdesc
339 1
        if self.certificate:
340 1
            edp.ServerCertificate = uacrypto.der_from_x509(self.certificate)
341 1
        edp.SecurityMode = mode
342 1
        edp.SecurityPolicyUri = policy.URI
343 1
        edp.UserIdentityTokens = idtokens
344 1
        edp.TransportProfileUri = 'http://opcfoundation.org/UA-Profile/Transport/uatcp-uasc-uabinary'
345 1
        edp.SecurityLevel = 0
346 1
        self.iserver.add_endpoint(edp)
347
348 1
    def set_server_name(self, name):
349
        self.name = name
350
351 1
    def start(self):
352
        """
353
        Start to listen on network
354
        """
355 1
        self._setup_server_nodes()
356 1
        self.iserver.start()
357 1
        try:
358 1
            self.bserver = BinaryServer(self.iserver, self.endpoint.hostname, self.endpoint.port)
359 1
            self.bserver.set_policies(self._policies)
360 1
            self.bserver.start()
361 1
        except Exception as exp:
362 1
            self.iserver.stop()
363 1
            raise exp
364
365 1
    def stop(self):
366
        """
367
        Stop server
368
        """
369 1
        for client in self._discovery_clients.values():
370 1
            client.disconnect()
371 1
        self.bserver.stop()
372 1
        self.iserver.stop()
373
374 1
    def get_root_node(self):
375
        """
376
        Get Root node of server. Returns a Node object.
377
        """
378 1
        return self.get_node(ua.TwoByteNodeId(ua.ObjectIds.RootFolder))
379
380 1
    def get_objects_node(self):
381
        """
382
        Get Objects node of server. Returns a Node object.
383
        """
384 1
        return self.get_node(ua.TwoByteNodeId(ua.ObjectIds.ObjectsFolder))
385
386 1
    def get_server_node(self):
387
        """
388
        Get Server node of server. Returns a Node object.
389
        """
390 1
        return self.get_node(ua.TwoByteNodeId(ua.ObjectIds.Server))
391
392 1
    def get_node(self, nodeid):
393
        """
394
        Get a specific node using NodeId object or a string representing a NodeId
395
        """
396 1
        return Node(self.iserver.isession, nodeid)
397
398 1
    def create_subscription(self, period, handler):
399
        """
400
        Create a subscription.
401
        returns a Subscription object which allow
402
        to subscribe to events or data on server
403
        period is in milliseconds
404
        handler is a python object with following methods:
405
            def datachange_notification(self, node, val, data):
406
            def event_notification(self, event):
407
            def status_change_notification(self, status):
408
        """
409 1
        params = ua.CreateSubscriptionParameters()
410 1
        params.RequestedPublishingInterval = period
411 1
        params.RequestedLifetimeCount = 3000
412 1
        params.RequestedMaxKeepAliveCount = 10000
413 1
        params.MaxNotificationsPerPublish = 0
414 1
        params.PublishingEnabled = True
415 1
        params.Priority = 0
416 1
        return Subscription(self.iserver.isession, params, handler)
417
418 1
    def get_namespace_array(self):
419
        """
420
        get all namespace defined in server
421
        """
422 1
        ns_node = self.get_node(ua.NodeId(ua.ObjectIds.Server_NamespaceArray))
423 1
        return ns_node.get_value()
424
425 1
    def register_namespace(self, uri):
426
        """
427
        Register a new namespace. Nodes should in custom namespace, not 0.
428
        """
429 1
        ns_node = self.get_node(ua.NodeId(ua.ObjectIds.Server_NamespaceArray))
430 1
        uries = ns_node.get_value()
431 1
        if uri in uries:
432 1
            return uries.index(uri)
433 1
        uries.append(uri)
434 1
        ns_node.set_value(uries)
435 1
        return len(uries) - 1
436
437 1
    def get_namespace_index(self, uri):
438
        """
439
        get index of a namespace using its uri
440
        """
441 1
        uries = self.get_namespace_array()
442 1
        return uries.index(uri)
443
444 1
    def get_event_generator(self, etype=None, source=ua.ObjectIds.Server):
445
        """
446
        Returns an event object using an event type from address space.
447
        Use this object to fire events
448
        """
449 1
        if not etype:
450 1
            etype = BaseEvent()
451 1
        return EventGenerator(self.iserver.isession, etype, source)
452
453 1
    def create_custom_data_type(self, idx, name, basetype=ua.ObjectIds.BaseDataType, properties=None):
454 1
        if properties is None:
455
            properties = []
456 1
        return self._create_custom_type(idx, name, basetype, properties, [], [])
457
458 1
    def create_custom_event_type(self, idx, name, basetype=ua.ObjectIds.BaseEventType, properties=None):
459 1
        if properties is None:
460
            properties = []
461 1
        return self._create_custom_type(idx, name, basetype, properties, [], [])
462
463 1
    def create_custom_object_type(self, idx, name, basetype=ua.ObjectIds.BaseObjectType, properties=None, variables=None, methods=None):
464 1
        if properties is None:
465
            properties = []
466 1
        if variables is None:
467
            variables = []
468 1
        if methods is None:
469
            methods = []
470 1
        return self._create_custom_type(idx, name, basetype, properties, variables, methods)
471
472
    # def create_custom_reference_type(self, idx, name, basetype=ua.ObjectIds.BaseReferenceType, properties=[]):
473
        # return self._create_custom_type(idx, name, basetype, properties)
474
475 1
    def create_custom_variable_type(self, idx, name, basetype=ua.ObjectIds.BaseVariableType, properties=None, variables=None, methods=None):
476 1
        if properties is None:
477
            properties = []
478 1
        if variables is None:
479 1
            variables = []
480 1
        if methods is None:
481 1
            methods = []
482 1
        return self._create_custom_type(idx, name, basetype, properties, variables, methods)
483
484 1
    def _create_custom_type(self, idx, name, basetype, properties, variables, methods):
485 1
        if isinstance(basetype, Node):
486 1
            base_t = basetype
487 1
        elif isinstance(basetype, ua.NodeId):
488 1
            base_t = Node(self.iserver.isession, basetype)
489
        else:
490 1
            base_t = Node(self.iserver.isession, ua.NodeId(basetype))
491
492 1
        custom_t = base_t.add_object_type(idx, name)
493 1
        for prop in properties:
494 1
            datatype = None
495 1
            if len(prop) > 2:
496
                datatype = prop[2]
497 1
            custom_t.add_property(idx, prop[0], ua.get_default_value(prop[1]), varianttype=prop[1], datatype=datatype)
498 1
        for variable in variables:
499 1
            datatype = None
500 1
            if len(variable) > 2:
501 1
                datatype = variable[2]
502 1
            custom_t.add_variable(idx, variable[0], ua.get_default_value(variable[1]), varianttype=variable[1], datatype=datatype)
503 1
        for method in methods:
504 1
            custom_t.add_method(idx, method[0], method[1], method[2], method[3])
505
506 1
        return custom_t
507
508 1
    def import_xml(self, path=None, xmlstring=None):
509
        """
510
        Import nodes defined in xml
511
        """
512 1
        importer = XmlImporter(self)
513 1
        return importer.import_xml(path, xmlstring)
514
515 1
    def export_xml(self, nodes, path):
516
        """
517
        Export defined nodes to xml
518
        """
519 1
        exp = XmlExporter(self)
520 1
        exp.build_etree(nodes)
521 1
        return exp.write_xml(path)
522
523 1
    def export_xml_by_ns(self, path, namespaces=None):
524
        """
525
        Export nodes of one or more namespaces to an XML file.
526
        Namespaces used by nodes are always exported for consistency.
527
        Args:
528
            server: opc ua server to use
529
            path: name of the xml file to write
530
            namespaces: list of string uris or int indexes of the namespace to export, if not provide all ns are used except 0
531
532
        Returns:
533
        """
534
        if namespaces is None:
535
            namespaces = []
536
        nodes = get_nodes_of_namespace(self, namespaces)
537
        self.export_xml(nodes, path)
538
539 1
    def delete_nodes(self, nodes, recursive=False):
540 1
        return delete_nodes(self.iserver.isession, nodes, recursive)
541
542 1
    def historize_node_data_change(self, node, period=timedelta(days=7), count=0):
543
        """
544
        Start historizing supplied nodes; see history module
545
        Args:
546
            node: node or list of nodes that can be historized (variables/properties)
547
            period: time delta to store the history; older data will be deleted from the storage
548
            count: number of changes to store in the history
549
550
        Returns:
551
        """
552 1
        nodes = node if isinstance(node, (list, tuple)) else [node]
553 1
        for node in nodes:
554 1
            self.iserver.enable_history_data_change(node, period, count)
555
556 1
    def dehistorize_node_data_change(self, node):
557
        """
558
        Stop historizing supplied nodes; see history module
559
        Args:
560
            node: node or list of nodes that can be historized (UA variables/properties)
561
562
        Returns:
563
        """
564
        nodes = node if isinstance(node, (list, tuple)) else [node]
565
        for node in nodes:
566
            self.iserver.disable_history_data_change(node)
567
568 1
    def historize_node_event(self, node, period=timedelta(days=7), count=0):
569
        """
570
        Start historizing events from node (typically a UA object); see history module
571
        Args:
572
            node: node or list of nodes that can be historized (UA objects)
573
            period: time delta to store the history; older data will be deleted from the storage
574
            count: number of events to store in the history
575
576
        Returns:
577
        """
578 1
        nodes = node if isinstance(node, (list, tuple)) else [node]
579 1
        for node in nodes:
580 1
            self.iserver.enable_history_event(node, period, count)
581
582 1
    def dehistorize_node_event(self, node):
583
        """
584
        Stop historizing events from node (typically a UA object); see history module
585
        Args:
586
           node: node or list of nodes that can be historized (UA objects)
587
588
        Returns:
589
        """
590
        nodes = node if isinstance(node, (list, tuple)) else [node]
591
        for node in nodes:
592
            self.iserver.disable_history_event(node)
593
594 1
    def subscribe_server_callback(self, event, handle):
595
        self.iserver.subscribe_server_callback(event, handle)
596
597 1
    def unsubscribe_server_callback(self, event, handle):
598
        self.iserver.unsubscribe_server_callback(event, handle)
599
600 1
    def link_method(self, node, callback):
601
        """
602
        Link a python function to a UA method in the address space; required when a UA method has been imported
603
        to the address space via XML; the python executable must be linked manually
604
        Args:
605
            node: UA method node
606
            callback: python function that the UA method will call
607
608
        Returns:
609
        """
610
        self.iserver.isession.add_method_callback(node.nodeid, callback)
611
612 1
    def load_type_definitions(self, nodes=None):
613
        return load_type_definitions(self, nodes)
614