Passed
Push — master ( e316e7...1981da )
by Olivier
03:11 queued 45s
created

asyncua.tools._uaserver()   D

Complexity

Conditions 12

Size

Total Lines 85
Code Lines 80

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 80
nop 0
dl 0
loc 85
rs 4.0145
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like asyncua.tools._uaserver() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
import asyncio
2
import logging
3
import sys
4
import argparse
5
from datetime import datetime, timedelta
6
import math
7
import time
8
import concurrent.futures._base
9
10
try:
11
    from IPython import embed
12
except ImportError:
13
    import code
14
15
    def embed():
16
        code.interact(local=dict(globals(), **locals()))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable code does not seem to be defined.
Loading history...
17
18
from asyncua import ua
19
from asyncua import Client, Server
20
from asyncua import Node, uamethod
21
from asyncua import sync
22
from asyncua.ua.uaerrors import UaStatusCodeError
23
24
25
if sys.version_info.major < 3:
26
    raise ValueError("This is a python 3 application")
27
if sys.version_info.minor >= 7:
28
    def run(coro):
29
        return asyncio.run(coro)
30
else:
31
    def run(coro):
32
        loop = asyncio.get_event_loop()
33
        return loop.run_until_complete(coro)
34
35
36
def add_minimum_args(parser):
37
    parser.add_argument("-u",
38
                        "--url",
39
                        help="URL of OPC UA server (for example: opc.tcp://example.org:4840)",
40
                        default='opc.tcp://localhost:4840',
41
                        metavar="URL")
42
    parser.add_argument("-v",
43
                        "--verbose",
44
                        dest="loglevel",
45
                        choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'],
46
                        default='WARNING',
47
                        help="Set log level")
48
    parser.add_argument("--timeout",
49
                        dest="timeout",
50
                        type=int,
51
                        default=1,
52
                        help="Set socket timeout (NOT the diverse UA timeouts)")
53
54
55
def add_common_args(parser, default_node='i=84', require_node=False):
56
    add_minimum_args(parser)
57
    parser.add_argument("-n",
58
                        "--nodeid",
59
                        help="Fully-qualified node ID (for example: i=85). Default: root node",
60
                        default=default_node,
61
                        required=require_node,
62
                        metavar="NODE")
63
    parser.add_argument("-p",
64
                        "--path",
65
                        help="Comma separated browse path to the node starting at NODE (for example: 3:Mybject,3:MyVariable)",
66
                        default='',
67
                        metavar="BROWSEPATH")
68
    parser.add_argument("-i",
69
                        "--namespace",
70
                        help="Default namespace",
71
                        type=int,
72
                        default=0,
73
                        metavar="NAMESPACE")
74
    parser.add_argument("--security",
75
                        help="Security settings, for example: Basic256Sha256,SignAndEncrypt,cert.der,pk.pem[,server_cert.der]. Default: None",
76
                        default='')
77
    parser.add_argument("--user",
78
                        help="User name for authentication. Overrides the user name given in the URL.")
79
    parser.add_argument("--password",
80
                        help="Password name for authentication. Overrides the password given in the URL.")
81
82
83
def _require_nodeid(parser, args):
84
    # check that a nodeid has been given explicitly, a bit hackish...
85
    if args.nodeid == "i=84" and args.path == "":
86
        parser.print_usage()
87
        print(f"{parser.prog}: error: A NodeId or BrowsePath is required")
88
        sys.exit(1)
89
90
91
def parse_args(parser, requirenodeid=False):
92
    args = parser.parse_args()
93
    # logging.basicConfig(format="%(levelname)s: %(message)s", level=getattr(logging, args.loglevel))
94
    logging.basicConfig(level=getattr(logging, args.loglevel))
95
    if args.url and '://' not in args.url:
96
        logging.info(f"Adding default scheme {ua.OPC_TCP_SCHEME} to URL {args.url}")
97
        args.url = ua.OPC_TCP_SCHEME + '://' + args.url
98
    if requirenodeid:
99
        _require_nodeid(parser, args)
100
    return args
101
102
103
async def get_node(client, args):
104
    node = client.get_node(args.nodeid)
105
    if args.path:
106
        path = args.path.split(",")
107
        if node.nodeid == ua.NodeId(84, 0) and path[0] == "0:Root":
108
            # let user specify root if not node given
109
            path = path[1:]
110
        node = await node.get_child(path)
111
    return node
112
113
114
def uaread():
115
    run(_uaread())
116
117
118
async def _uaread():
119
    parser = argparse.ArgumentParser(description="Read attribute of a node, per default reads value of a node")
120
    add_common_args(parser)
121
    parser.add_argument("-a",
122
                        "--attribute",
123
                        dest="attribute",
124
                        type=int,
125
                        default=ua.AttributeIds.Value,
126
                        help="Set attribute to read")
127
    parser.add_argument("-t",
128
                        "--datatype",
129
                        dest="datatype",
130
                        default="python",
131
                        choices=['python', 'variant', 'datavalue'],
132
                        help="Data type to return")
133
134
    args = parse_args(parser, requirenodeid=True)
135
136
    client = Client(args.url, timeout=args.timeout)
137
    await client.set_security_string(args.security)
138
    await client.connect()
139
140
    try:
141
        node = await get_node(client, args)
142
        attr = await node.read_attribute(args.attribute)
143
        if args.datatype == "python":
144
            print(attr.Value.Value)
145
        elif args.datatype == "variant":
146
            print(attr.Value)
147
        else:
148
            print(attr)
149
    finally:
150
        await client.disconnect()
151
152
153
def _args_to_array(val, array):
154
    if array == "guess":
155
        if "," in val:
156
            array = "true"
157
    if array == "true":
158
        val = val.split(",")
159
    return val
160
161
162
def _arg_to_bool(val):
163
    return val in ("true", "True")
164
165
166
def _arg_to_variant(val, array, ptype, varianttype=None):
167
    val = _args_to_array(val, array)
168
    if isinstance(val, list):
169
        val = [ptype(i) for i in val]
170
    else:
171
        val = ptype(val)
172
    if varianttype:
173
        return ua.Variant(val, varianttype)
174
    else:
175
        return ua.Variant(val)
176
177
178
def _val_to_variant(val, args):
179
    array = args.array
180
    if args.datatype == "guess":
181
        if val in ("true", "True", "false", "False"):
182
            return _arg_to_variant(val, array, _arg_to_bool)
183
        try:
184
            return _arg_to_variant(val, array, int)
185
        except ValueError:
186
            try:
187
                return _arg_to_variant(val, array, float)
188
            except ValueError:
189
                return _arg_to_variant(val, array, str)
190
    elif args.datatype == "bool":
191
        if val in ("1", "True", "true"):
192
            return ua.Variant(True, ua.VariantType.Boolean)
193
        else:
194
            return ua.Variant(False, ua.VariantType.Boolean)
195
    elif args.datatype == "sbyte":
196
        return _arg_to_variant(val, array, int, ua.VariantType.SByte)
197
    elif args.datatype == "byte":
198
        return _arg_to_variant(val, array, int, ua.VariantType.Byte)
199
    # elif args.datatype == "uint8":
200
        # return _arg_to_variant(val, array, int, ua.VariantType.Byte)
201
    elif args.datatype == "uint16":
202
        return _arg_to_variant(val, array, int, ua.VariantType.UInt16)
203
    elif args.datatype == "uint32":
204
        return _arg_to_variant(val, array, int, ua.VariantType.UInt32)
205
    elif args.datatype == "uint64":
206
        return _arg_to_variant(val, array, int, ua.VariantType.UInt64)
207
    # elif args.datatype == "int8":
208
        # return ua.Variant(int(val), ua.VariantType.Int8)
209
    elif args.datatype == "int16":
210
        return _arg_to_variant(val, array, int, ua.VariantType.Int16)
211
    elif args.datatype == "int32":
212
        return _arg_to_variant(val, array, int, ua.VariantType.Int32)
213
    elif args.datatype == "int64":
214
        return _arg_to_variant(val, array, int, ua.VariantType.Int64)
215
    elif args.datatype == "float":
216
        return _arg_to_variant(val, array, float, ua.VariantType.Float)
217
    elif args.datatype == "double":
218
        return _arg_to_variant(val, array, float, ua.VariantType.Double)
219
    elif args.datatype == "string":
220
        return _arg_to_variant(val, array, str, ua.VariantType.String)
221
    elif args.datatype == "datetime":
222
        raise NotImplementedError
223
    elif args.datatype == "Guid":
224
        return _arg_to_variant(val, array, bytes, ua.VariantType.Guid)
225
    elif args.datatype == "ByteString":
226
        return _arg_to_variant(val, array, bytes, ua.VariantType.ByteString)
227
    elif args.datatype == "xml":
228
        return _arg_to_variant(val, array, str, ua.VariantType.XmlElement)
229
    elif args.datatype == "nodeid":
230
        return _arg_to_variant(val, array, ua.NodeId.from_string, ua.VariantType.NodeId)
231
    elif args.datatype == "expandednodeid":
232
        return _arg_to_variant(val, array, ua.ExpandedNodeId.from_string, ua.VariantType.ExpandedNodeId)
233
    elif args.datatype == "statuscode":
234
        return _arg_to_variant(val, array, int, ua.VariantType.StatusCode)
235
    elif args.datatype in ("qualifiedname", "browsename"):
236
        return _arg_to_variant(val, array, ua.QualifiedName.from_string, ua.VariantType.QualifiedName)
237
    elif args.datatype == "LocalizedText":
238
        return _arg_to_variant(val, array, ua.LocalizedText, ua.VariantType.LocalizedText)
239
240
241
async def _configure_client_with_args(client, args):
242
    if args.user:
243
        client.set_user(args.user)
244
    if args.password:
245
        client.set_password(args.password)
246
    await client.set_security_string(args.security)
247
248
249
def uawrite():
250
    run(_uawrite())
251
252
253
async def _uawrite():
254
    parser = argparse.ArgumentParser(description="Write attribute of a node, per default write value of node")
255
    add_common_args(parser)
256
    parser.add_argument("-a",
257
                        "--attribute",
258
                        dest="attribute",
259
                        type=int,
260
                        default=ua.AttributeIds.Value,
261
                        help="Set attribute to read")
262
    parser.add_argument("-l",
263
                        "--list",
264
                        "--array",
265
                        dest="array",
266
                        default="guess",
267
                        choices=["guess", "true", "false"],
268
                        help="Value is an array")
269
    parser.add_argument("-t",
270
                        "--datatype",
271
                        dest="datatype",
272
                        default="guess",
273
                        choices=["guess", 'byte', 'sbyte', 'nodeid', 'expandednodeid', 'qualifiedname', 'browsename', 'string', 'float', 'double', 'int16', 'int32', "int64", 'uint16', 'uint32', 'uint64', "bool", "string", 'datetime', 'bytestring', 'xmlelement', 'statuscode', 'localizedtext'],
274
                        help="Data type to return")
275
    parser.add_argument("value",
276
                        help="Value to be written",
277
                        metavar="VALUE")
278
    args = parse_args(parser, requirenodeid=True)
279
280
    client = Client(args.url, timeout=args.timeout)
281
    await _configure_client_with_args(client, args)
282
    try:
283
        await client.connect()
284
        node = await get_node(client, args)
285
        val = _val_to_variant(args.value, args)
286
        await node.write_attribute(args.attribute, ua.DataValue(val))
287
    finally:
288
        await client.disconnect()
289
290
291
def uals():
292
    run(_uals())
293
294
295
async def _uals():
296
    parser = argparse.ArgumentParser(description="Browse OPC-UA node and print result")
297
    add_common_args(parser)
298
    parser.add_argument("-l",
299
                        dest="long_format",
300
                        const=3,
301
                        nargs="?",
302
                        type=int,
303
                        help="use a long listing format")
304
    parser.add_argument("-d",
305
                        "--depth",
306
                        default=1,
307
                        type=int,
308
                        help="Browse depth")
309
310
    args = parse_args(parser)
311
    if args.long_format is None:
312
        args.long_format = 1
313
314
    client = Client(args.url, timeout=args.timeout)
315
    await _configure_client_with_args(client, args)
316
    try:
317
        async with client:
318
            node = await get_node(client, args)
319
            print(f"Browsing node {node} at {args.url}\n")
320
            if args.long_format == 0:
321
                await _lsprint_0(node, args.depth - 1)
322
            elif args.long_format == 1:
323
                await _lsprint_1(node, args.depth - 1)
324
            else:
325
                _lsprint_long(node, args.depth - 1)
326
    except (OSError, concurrent.futures._base.TimeoutError) as e:
327
        print(e)
328
        sys.exit(1)
329
330
331
async def _lsprint_0(node, depth, indent=""):
332
    if not indent:
333
        print("{0:30} {1:25}".format("DisplayName", "NodeId"))
334
        print("")
335
    for desc in await node.get_children_descriptions():
336
        print("{0}{1:30} {2:25}".format(indent, desc.DisplayName.to_string(), desc.NodeId.to_string()))
337
        if depth:
338
            await _lsprint_0(Node(node.server, desc.NodeId), depth - 1, indent + "  ")
339
340
341
async def _lsprint_1(node, depth, indent=""):
342
    if not indent:
343
        print("{0:30} {1:25} {2:25} {3:25}".format("DisplayName", "NodeId", "BrowseName", "Value"))
344
        print("")
345
346
    for desc in await node.get_children_descriptions():
347
        if desc.NodeClass == ua.NodeClass.Variable:
348
            try:
349
                val = await Node(node.server, desc.NodeId).read_value()
350
            except UaStatusCodeError as err:
351
                val = "Bad (0x{0:x})".format(err.code)
352
            print("{0}{1:30} {2!s:25} {3!s:25}, {4!s:3}".format(indent, desc.DisplayName.to_string(), desc.NodeId.to_string(), desc.BrowseName.to_string(), val))
353
        else:
354
            print("{0}{1:30} {2!s:25} {3!s:25}".format(indent, desc.DisplayName.to_string(), desc.NodeId.to_string(), desc.BrowseName.to_string()))
355
        if depth:
356
            await _lsprint_1(Node(node.server, desc.NodeId), depth - 1, indent + "  ")
357
358
359
def _lsprint_long(pnode, depth, indent=""):
360
    if not indent:
361
        print("{0:30} {1:25} {2:25} {3:10} {4:30} {5:25}".format("DisplayName", "NodeId", "BrowseName", "DataType", "Timestamp", "Value"))
362
        print("")
363
    for node in pnode.get_children():
364
        attrs = node.read_attributes([ua.AttributeIds.DisplayName,
365
                                     ua.AttributeIds.BrowseName,
366
                                     ua.AttributeIds.NodeClass,
367
                                     ua.AttributeIds.WriteMask,
368
                                     ua.AttributeIds.UserWriteMask,
369
                                     ua.AttributeIds.DataType,
370
                                     ua.AttributeIds.Value])
371
        name, bname, nclass, mask, umask, dtype, val = [attr.Value.Value for attr in attrs]
372
        update = attrs[-1].ServerTimestamp
373
        if nclass == ua.NodeClass.Variable:
374
            print("{0}{1:30} {2:25} {3:25} {4:10} {5!s:30} {6!s:25}".format(indent, name.to_string(), node.nodeid.to_string(), bname.to_string(), dtype.to_string(), update, val))
375
        else:
376
            print("{0}{1:30} {2:25} {3:25}".format(indent, name.to_string(), bname.to_string(), node.nodeid.to_string()))
377
        if depth:
378
            _lsprint_long(node, depth - 1, indent + "  ")
379
380
381
class SubHandler(object):
382
383
    def datachange_notification(self, node, val, data):
384
        print("New data change event", node, val, data)
385
386
    def event_notification(self, event):
387
        print("New event", event)
388
389
390
async def uasubscribe():
391
    run(_uasubscribe())
392
393
394
async def _uasubscribe():
395
    parser = argparse.ArgumentParser(description="Subscribe to a node and print results")
396
    add_common_args(parser)
397
    parser.add_argument("-t",
398
                        "--eventtype",
399
                        dest="eventtype",
400
                        default="datachange",
401
                        choices=['datachange', 'event'],
402
                        help="Event type to subscribe to")
403
404
    args = parse_args(parser, requirenodeid=False)
405
    if args.eventtype == "datachange":
406
        _require_nodeid(parser, args)
407
    else:
408
        # FIXME: this is broken, someone may have written i=84 on purpose
409
        if args.nodeid == "i=84" and args.path == "":
410
            args.nodeid = "i=2253"
411
412
    client = Client(args.url, timeout=args.timeout)
413
    await _configure_client_with_args(client, args)
414
    await client.connect()
415
    try:
416
        node = await get_node(client, args)
417
        handler = SubHandler()
418
        sub = await client.create_subscription(500, handler)
419
        if args.eventtype == "datachange":
420
            await sub.subscribe_data_change(node)
421
        else:
422
            await sub.subscribe_events(node)
423
        print("Type Ctr-C to exit")
424
        while True:
425
            time.sleep(1)
426
    finally:
427
        await client.disconnect()
428
429
430
def application_to_strings(app):
431
    result = [('Application URI', app.ApplicationUri)]
432
    optionals = [
433
        ('Product URI', app.ProductUri),
434
        ('Application Name', app.ApplicationName.to_string()),
435
        ('Application Type', str(app.ApplicationType)),
436
        ('Gateway Server URI', app.GatewayServerUri),
437
        ('Discovery Profile URI', app.DiscoveryProfileUri),
438
    ]
439
    for (n, v) in optionals:
440
        if v:
441
            result.append((n, v))
442
    for url in app.DiscoveryUrls:
443
        result.append(('Discovery URL', url))
444
    return result  # ['{}: {}'.format(n, v) for (n, v) in result]
445
446
447
def cert_to_string(der):
448
    if not der:
449
        return '[no certificate]'
450
    try:
451
        from .crypto import uacrypto
452
    except ImportError:
453
        return f"{len(der)} bytes"
454
    cert = uacrypto.x509_from_der(der)
455
    return uacrypto.x509_to_string(cert)
456
457
458
def endpoint_to_strings(ep):
459
    result = [('Endpoint URL', ep.EndpointUrl)]
460
    result += application_to_strings(ep.Server)
461
    result += [
462
        ('Server Certificate', cert_to_string(ep.ServerCertificate)),
463
        ('Security Mode', str(ep.SecurityMode)),
464
        ('Security Policy URI', ep.SecurityPolicyUri)]
465
    for tok in ep.UserIdentityTokens:
466
        result += [
467
            ('User policy', tok.PolicyId),
468
            ('  Token type', str(tok.TokenType))]
469
        if tok.IssuedTokenType or tok.IssuerEndpointUrl:
470
            result += [
471
                ('  Issued Token type', tok.IssuedTokenType),
472
                ('  Issuer Endpoint URL', tok.IssuerEndpointUrl)]
473
        if tok.SecurityPolicyUri:
474
            result.append(('  Security Policy URI', tok.SecurityPolicyUri))
475
    result += [
476
        ('Transport Profile URI', ep.TransportProfileUri),
477
        ('Security Level', ep.SecurityLevel)]
478
    return result
479
480
481
def uaclient():
482
    run(_uaclient())
483
    
484
async def _uaclient():
485
    parser = argparse.ArgumentParser(description="Connect to server and start python shell. root and objects nodes are available. Node specificed in command line is available as mynode variable")
486
    add_common_args(parser)
487
    parser.add_argument("-c",
488
                        "--certificate",
489
                        help="set client certificate")
490
    parser.add_argument("-k",
491
                        "--private_key",
492
                        help="set client private key")
493
    args = parse_args(parser)
494
495
    client = Client(args.url, timeout=args.timeout)
496
    await _configure_client_with_args(client, args)
497
    if args.certificate:
498
        client.load_client_certificate(args.certificate)
499
    if args.private_key:
500
        client.load_private_key(args.private_key)
501
502
    try:
503
        async with client:
504
            mynode = await get_node(client, args)
505
            # embed()
506
    except (OSError, concurrent.futures._base.TimeoutError) as e:
507
        print(e)
508
        sys.exit(1)
509
510
    sys.exit(0)
511
512
513
async def _uaserver():
514
    parser = argparse.ArgumentParser(description="Run an example OPC-UA server. By importing xml definition and using uawrite command line, it is even possible to expose real data using this server")
515
    # we setup a server, this is a bit different from other tool so we do not reuse common arguments
516
    parser.add_argument("-u",
517
                        "--url",
518
                        help="URL of OPC UA server, default is opc.tcp://0.0.0.0:4840",
519
                        default='opc.tcp://0.0.0.0:4840',
520
                        metavar="URL")
521
    parser.add_argument("-v",
522
                        "--verbose",
523
                        dest="loglevel",
524
                        choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'],
525
                        default='WARNING',
526
                        help="Set log level")
527
    parser.add_argument("-x",
528
                        "--xml",
529
                        metavar="XML_FILE",
530
                        help="Populate address space with nodes defined in XML")
531
    parser.add_argument("-p",
532
                        "--populate",
533
                        action="store_true",
534
                        help="Populate address space with some sample nodes")
535
    parser.add_argument("-c",
536
                        "--disable-clock",
537
                        action="store_true",
538
                        help="Disable clock, to avoid seeing many write if debugging an application")
539
    parser.add_argument("-s",
540
                        "--shell",
541
                        action="store_true",
542
                        help="Start python shell instead of randomly changing node values")
543
    parser.add_argument("--certificate",
544
                        help="set server certificate")
545
    parser.add_argument("--private_key",
546
                        help="set server private key")
547
    args = parser.parse_args()
548
    logging.basicConfig(format="%(levelname)s: %(message)s", level=getattr(logging, args.loglevel))
549
550
    server = Server()
551
    server.set_endpoint(args.url)
552
    if args.certificate:
553
        server.load_certificate(args.certificate)
554
    if args.private_key:
555
        server.load_private_key(args.private_key)
556
    server.disable_clock(args.disable_clock)
557
    server.set_server_name("FreeOpcUa Example Server")
558
    if args.xml:
559
        server.import_xml(args.xml)
560
    if args.populate:
561
        @uamethod
562
        def multiply(parent, x, y):
563
            print("multiply method call with parameters: ", x, y)
564
            return x * y
565
566
        uri = "http://examples.freeopcua.github.io"
567
        idx = server.register_namespace(uri)
568
        objects = server.nodes.objects
569
        myobj = objects.add_object(idx, "MyObject")
570
        mywritablevar = myobj.add_variable(idx, "MyWritableVariable", 6.7)
571
        mywritablevar.set_writable()    # Set MyVariable to be writable by clients
572
        myvar = myobj.add_variable(idx, "MyVariable", 6.7)
573
        myarrayvar = myobj.add_variable(idx, "MyVarArray", [6.7, 7.9])
574
        myprop = myobj.add_property(idx, "MyProperty", "I am a property")
575
        mymethod = myobj.add_method(idx, "MyMethod", multiply, [ua.VariantType.Double, ua.VariantType.Int64], [ua.VariantType.Double])
576
577
    try:
578
        await server.init()
579
        async with server:
580
            if args.shell:
581
                embed()
582
            elif args.populate:
583
                count = 0
584
                while True:
585
                    time.sleep(1)
586
                    myvar.write_value(math.sin(count / 10))
0 ignored issues
show
introduced by
The variable myvar does not seem to be defined in case args.populate on line 560 is False. Are you sure this can never be the case?
Loading history...
587
                    myarrayvar.write_value([math.sin(count / 10), math.sin(count / 100)])
0 ignored issues
show
introduced by
The variable myarrayvar does not seem to be defined in case args.populate on line 560 is False. Are you sure this can never be the case?
Loading history...
588
                    count += 1
589
            else:
590
                while True:
591
                    time.sleep(1)
592
    except OSError as e:
593
        print(e)
594
        sys.exit(1)
595
    except KeyboardInterrupt:
596
        pass
597
    sys.exit(0)
598
    
599
def uaserver():
600
    run(_uaserver())
601
602
603
def uadiscover():
604
    run(_uadiscover())
605
606
607
async def _uadiscover():
608
    parser = argparse.ArgumentParser(description="Performs OPC UA discovery and prints information on servers and endpoints.")
609
    add_minimum_args(parser)
610
    parser.add_argument("-n",
611
                        "--network",
612
                        action="store_true",
613
                        help="Also send a FindServersOnNetwork request to server")
614
    # parser.add_argument("-s",
615
                        # "--servers",
616
                        # action="store_false",
617
                        # help="send a FindServers request to server")
618
    # parser.add_argument("-e",
619
                        # "--endpoints",
620
                        # action="store_false",
621
                        # help="send a GetEndpoints request to server")
622
    args = parse_args(parser)
623
624
    client = Client(args.url, timeout=args.timeout)
625
626
    try:
627
        if args.network:
628
            print(f"Performing discovery at {args.url}\n")
629
            for i, server in enumerate(await client.connect_and_find_servers_on_network(), start=1):
630
                print(f'Server {i}:')
631
                # for (n, v) in application_to_strings(server):
632
                    # print('  {}: {}'.format(n, v))
633
                print('')
634
635
        print(f"Performing discovery at {args.url}\n")
636
        for i, server in enumerate(await client.connect_and_find_servers(), start=1):
637
            print(f'Server {i}:')
638
            for (n, v) in application_to_strings(server):
639
                print(f'  {n}: {v}')
640
            print('')
641
642
        for i, ep in enumerate(await client.connect_and_get_server_endpoints(), start=1):
643
            print(f'Endpoint {i}:')
644
            for (n, v) in endpoint_to_strings(ep):
645
                print(f'  {n}: {v}')
646
            print('')
647
    except (OSError, concurrent.futures._base.TimeoutError) as e:
648
        print(e)
649
        sys.exit(1)
650
651
    sys.exit(0)
652
653
654
def print_history(o):
655
    print("{0:30} {1:10} {2}".format('Source timestamp', 'Status', 'Value'))
656
    for d in o:
657
        print("{0:30} {1:10} {2}".format(str(d.SourceTimestamp), d.StatusCode.name, d.Value.Value))
658
659
660
def str_to_datetime(s, default=None):
661
    if not s:
662
        if default is not None:
663
            return default
664
        return datetime.utcnow()
665
    # FIXME: try different datetime formats
666
    for fmt in ["%Y-%m-%d", "%Y-%m-%d %H:%M", "%Y-%m-%d %H:%M:%S"]:
667
        try:
668
            return datetime.strptime(s, fmt)
669
        except ValueError:
670
            pass
671
672
673
def uahistoryread():
674
    run(_uahistoryread())
675
676
677
async def _uahistoryread():
678
    parser = argparse.ArgumentParser(description="Read history of a node")
679
    add_common_args(parser)
680
    parser.add_argument("--starttime",
681
                        default=None,
682
                        help="Start time, formatted as YYYY-MM-DD [HH:MM[:SS]]. Default: current time - one day")
683
    parser.add_argument("--endtime",
684
                        default=None,
685
                        help="End time, formatted as YYYY-MM-DD [HH:MM[:SS]]. Default: current time")
686
    parser.add_argument("-e",
687
                        "--events",
688
                        action="store_true",
689
                        help="Read event history instead of data change history")
690
    parser.add_argument("-l",
691
                        "--limit",
692
                        type=int,
693
                        default=10,
694
                        help="Maximum number of notfication to return")
695
696
    args = parse_args(parser, requirenodeid=True)
697
698
    client = Client(args.url, timeout=args.timeout)
699
    await _configure_client_with_args(client, args)
700
    await client.connect()
701
    try:
702
        node = await get_node(client, args)
703
        starttime = str_to_datetime(args.starttime, datetime.utcnow() - timedelta(days=1))
704
        endtime = str_to_datetime(args.endtime, datetime.utcnow())
705
        print(f"Reading raw history of node {node} at {args.url}; start at {starttime}, end at {endtime}\n")
706
        if args.events:
707
            evs = await node.read_event_history(starttime, endtime, numvalues=args.limit)
708
            for ev in evs:
709
                print(ev)
710
        else:
711
            print_history(await node.read_raw_history(starttime, endtime, numvalues=args.limit))
712
    finally:
713
        await client.disconnect()
714
    sys.exit(0)
715
716
717
def uacall():
718
    run(_uacall())
719
720
721
async def _uacall():
722
    parser = argparse.ArgumentParser(description="Call method of a node")
723
    add_common_args(parser)
724
    parser.add_argument("-m",
725
                        "--method",
726
                        dest="method",
727
                        type=str,
728
                        default=None,
729
                        help="browse name of method to call")
730
    parser.add_argument("-t",
731
                        "--datatype",
732
                        dest="datatype",
733
                        default="guess",
734
                        choices=["guess", 'byte', 'sbyte', 'nodeid', 'expandednodeid', 'qualifiedname', 'browsename', 'string', 'float', 'double', 'int16', 'int32', "int64", 'uint16', 'uint32', 'uint64', "bool", "string", 'datetime', 'bytestring', 'xmlelement', 'statuscode', 'localizedtext'],
735
                        help="Data type to return")
736
    parser.add_argument("-l",
737
                        "--list",
738
                        "--array",
739
                        dest="array",
740
                        default="guess",
741
                        choices=["guess", "true", "false"],
742
                        help="Value is an array")
743
    parser.add_argument("value",
744
                        help="Comma separated value(s) to use for call to method, if any",
745
                        nargs="?",
746
                        metavar="VALUE")
747
748
    args = parse_args(parser, requirenodeid=True)
749
750
    client = Client(args.url, timeout=args.timeout)
751
    await _configure_client_with_args(client, args)
752
    await client.connect()
753
    try:
754
        node = await get_node(client, args)
755
        if args.value is None:
756
            val = ()  # empty tuple
757
        else:
758
            val = args.value.split(",")
759
            val = [_val_to_variant(v, args) for v in val]
760
761
        method_id = None
762
763
        if args.method is not None:
764
            method_id = args.method
765
        else:
766
            methods = await node.get_methods()
767
            if len(methods) == 0:
768
                raise ValueError("No methods in selected node and no method given")
769
            else:
770
                method_id = methods[0]
771
        result = await node.call_method(method_id, *val)
772
        print(f"resulting result_variants={result}")
773
    finally:
774
        await client.disconnect()
775
776
777
def uageneratestructs():
778
    run(_uageneratestructs())
779
780
781
async def _uageneratestructs():
782
    parser = argparse.ArgumentParser(description="Generate a Python module from the xml structure definition (.bsd), the node argument is typically a children of i=93")
783
    add_common_args(parser, require_node=True)
784
    parser.add_argument("-o",
785
                        "--output",
786
                        dest="output_path",
787
                        required=True,
788
                        type=str,
789
                        default=None,
790
                        help="The python file to be generated.",
791
                        )
792
    args = parse_args(parser, requirenodeid=True)
793
794
    client = Client(args.url, timeout=args.timeout)
795
    await _configure_client_with_args(client, args)
796
    await client.connect()
797
    try:
798
        node = await get_node(client, args)
799
        generators, _ = await client.load_type_definitions([node])
800
        generators[0].save_to_file(args.output_path, True)
801
    finally:
802
        await client.disconnect()
803