Completed
Pull Request — master (#614)
by
unknown
04:48
created

Server._set_endpoints()   B

Complexity

Conditions 6

Size

Total Lines 44

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 39
CRAP Score 6

Importance

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