Test Failed
Push — master ( 212d98...17207f )
by Olivier
05:20
created

Server.set_security_policy()   A

Complexity

Conditions 1

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

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