Test Failed
Pull Request — master (#212)
by
unknown
02:17
created

asyncua.tools._arg_to_bool()   A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nop 1
dl 0
loc 2
rs 10
c 0
b 0
f 0
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
9
try:
10
    from IPython import embed
11
except ImportError:
12
    import code
13
14
    def embed():
15
        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...
16
17
from asyncua import ua
18
from asyncua import Client
19
from asyncua import Node, uamethod
20
from asyncua import sync
21
from asyncua.ua.uaerrors import UaStatusCodeError
22
23
24
if sys.version_info.major < 3:
25
    raise ValueError("This is a python 3 application")
26
if sys.version_info.minor >= 7:
27
    def run(coro):
28
        return asyncio.run(coro)
29
else:
30
    def run(coro):
31
        loop = asyncio.get_event_loop()
32
        return loop.run_until_complete(coro)
33
34
35
def add_minimum_args(parser):
36
    parser.add_argument("-u",
37
                        "--url",
38
                        help="URL of OPC UA server (for example: opc.tcp://example.org:4840)",
39
                        default='opc.tcp://localhost:4840',
40
                        metavar="URL")
41
    parser.add_argument("-v",
42
                        "--verbose",
43
                        dest="loglevel",
44
                        choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'],
45
                        default='WARNING',
46
                        help="Set log level")
47
    parser.add_argument("--timeout",
48
                        dest="timeout",
49
                        type=int,
50
                        default=1,
51
                        help="Set socket timeout (NOT the diverse UA timeouts)")
52
53
54
def add_common_args(parser, default_node='i=84', require_node=False):
55
    add_minimum_args(parser)
56
    parser.add_argument("-n",
57
                        "--nodeid",
58
                        help="Fully-qualified node ID (for example: i=85). Default: root node",
59
                        default=default_node,
60
                        required=require_node,
61
                        metavar="NODE")
62
    parser.add_argument("-p",
63
                        "--path",
64
                        help="Comma separated browse path to the node starting at NODE (for example: 3:Mybject,3:MyVariable)",
65
                        default='',
66
                        metavar="BROWSEPATH")
67
    parser.add_argument("-i",
68
                        "--namespace",
69
                        help="Default namespace",
70
                        type=int,
71
                        default=0,
72
                        metavar="NAMESPACE")
73
    parser.add_argument("--security",
74
                        help="Security settings, for example: Basic256Sha256,SignAndEncrypt,cert.der,pk.pem[,server_cert.der]. Default: None",
75
                        default='')
76
    parser.add_argument("--user",
77
                        help="User name for authentication. Overrides the user name given in the URL.")
78
    parser.add_argument("--password",
79
                        help="Password name for authentication. Overrides the password given in the URL.")
80
81
82
def _require_nodeid(parser, args):
83
    # check that a nodeid has been given explicitly, a bit hackish...
84
    if args.nodeid == "i=84" and args.path == "":
85
        parser.print_usage()
86
        print("{0}: error: A NodeId or BrowsePath is required".format(parser.prog))
87
        sys.exit(1)
88
89
90
def parse_args(parser, requirenodeid=False):
91
    args = parser.parse_args()
92
    #logging.basicConfig(format="%(levelname)s: %(message)s", level=getattr(logging, args.loglevel))
93
    logging.basicConfig(level=getattr(logging, args.loglevel))
94
    if args.url and '://' not in args.url:
95
        logging.info("Adding default scheme %s to URL %s", ua.OPC_TCP_SCHEME, args.url)
96
        args.url = ua.OPC_TCP_SCHEME + '://' + args.url
97
    if requirenodeid:
98
        _require_nodeid(parser, args)
99
    return args
100
101
102
async def get_node(client, args):
103
    node = client.get_node(args.nodeid)
104
    if args.path:
105
        path = args.path.split(",")
106
        if node.nodeid == ua.NodeId(84, 0) and path[0] == "0:Root":
107
            # let user specify root if not node given
108
            path = path[1:]
109
        node = await node.get_child(path)
110
    return node
111
112
113
def uaread():
114
    run(_uaread())
115
116
117
async def _uaread():
118
    parser = argparse.ArgumentParser(description="Read attribute of a node, per default reads value of a node")
119
    add_common_args(parser)
120
    parser.add_argument("-a",
121
                        "--attribute",
122
                        dest="attribute",
123
                        type=int,
124
                        default=ua.AttributeIds.Value,
125
                        help="Set attribute to read")
126
    parser.add_argument("-t",
127
                        "--datatype",
128
                        dest="datatype",
129
                        default="python",
130
                        choices=['python', 'variant', 'datavalue'],
131
                        help="Data type to return")
132
133
    args = parse_args(parser, requirenodeid=True)
134
135
    client = Client(args.url, timeout=args.timeout)
136
    await client.set_security_string(args.security)
137
    await client.connect()
138
139
    try:
140
        node = await get_node(client, args)
141
        attr = await node.read_attribute(args.attribute)
142
        if args.datatype == "python":
143
            print(attr.Value.Value)
144
        elif args.datatype == "variant":
145
            print(attr.Value)
146
        else:
147
            print(attr)
148
    finally:
149
        await client.disconnect()
150
151
152
def _args_to_array(val, array):
153
    if array == "guess":
154
        if "," in val:
155
            array = "true"
156
    if array == "true":
157
        val = val.split(",")
158
    return val
159
160
161
def _arg_to_bool(val):
162
    return val in ("true", "True")
163
164
165
def _arg_to_variant(val, array, ptype, varianttype=None):
166
    val = _args_to_array(val, array)
167
    if isinstance(val, list):
168
        val = [ptype(i) for i in val]
169
    else:
170
        val = ptype(val)
171
    if varianttype:
172
        return ua.Variant(val, varianttype)
173
    else:
174
        return ua.Variant(val)
175
176
177
def _val_to_variant(val, args):
178
    array = args.array
179
    if args.datatype == "guess":
180
        if val in ("true", "True", "false", "False"):
181
            return _arg_to_variant(val, array, _arg_to_bool)
182
        try:
183
            return _arg_to_variant(val, array, int)
184
        except ValueError:
185
            try:
186
                return _arg_to_variant(val, array, float)
187
            except ValueError:
188
                return _arg_to_variant(val, array, str)
189
    elif args.datatype == "bool":
190
        if val in ("1", "True", "true"):
191
            return ua.Variant(True, ua.VariantType.Boolean)
192
        else:
193
            return ua.Variant(False, ua.VariantType.Boolean)
194
    elif args.datatype == "sbyte":
195
        return _arg_to_variant(val, array, int, ua.VariantType.SByte)
196
    elif args.datatype == "byte":
197
        return _arg_to_variant(val, array, int, ua.VariantType.Byte)
198
    #elif args.datatype == "uint8":
199
        #return _arg_to_variant(val, array, int, ua.VariantType.Byte)
200
    elif args.datatype == "uint16":
201
        return _arg_to_variant(val, array, int, ua.VariantType.UInt16)
202
    elif args.datatype == "uint32":
203
        return _arg_to_variant(val, array, int, ua.VariantType.UInt32)
204
    elif args.datatype == "uint64":
205
        return _arg_to_variant(val, array, int, ua.VariantType.UInt64)
206
    #elif args.datatype == "int8":
207
        #return ua.Variant(int(val), ua.VariantType.Int8)
208
    elif args.datatype == "int16":
209
        return _arg_to_variant(val, array, int, ua.VariantType.Int16)
210
    elif args.datatype == "int32":
211
        return _arg_to_variant(val, array, int, ua.VariantType.Int32)
212
    elif args.datatype == "int64":
213
        return _arg_to_variant(val, array, int, ua.VariantType.Int64)
214
    elif args.datatype == "float":
215
        return _arg_to_variant(val, array, float, ua.VariantType.Float)
216
    elif args.datatype == "double":
217
        return _arg_to_variant(val, array, float, ua.VariantType.Double)
218
    elif args.datatype == "string":
219
        return _arg_to_variant(val, array, str, ua.VariantType.String)
220
    elif args.datatype == "datetime":
221
        raise NotImplementedError
222
    elif args.datatype == "Guid":
223
        return _arg_to_variant(val, array, bytes, ua.VariantType.Guid)
224
    elif args.datatype == "ByteString":
225
        return _arg_to_variant(val, array, bytes, ua.VariantType.ByteString)
226
    elif args.datatype == "xml":
227
        return _arg_to_variant(val, array, str, ua.VariantType.XmlElement)
228
    elif args.datatype == "nodeid":
229
        return _arg_to_variant(val, array, ua.NodeId.from_string, ua.VariantType.NodeId)
230
    elif args.datatype == "expandednodeid":
231
        return _arg_to_variant(val, array, ua.ExpandedNodeId.from_string, ua.VariantType.ExpandedNodeId)
232
    elif args.datatype == "statuscode":
233
        return _arg_to_variant(val, array, int, ua.VariantType.StatusCode)
234
    elif args.datatype in ("qualifiedname", "browsename"):
235
        return _arg_to_variant(val, array, ua.QualifiedName.from_string, ua.VariantType.QualifiedName)
236
    elif args.datatype == "LocalizedText":
237
        return _arg_to_variant(val, array, ua.LocalizedText, ua.VariantType.LocalizedText)
238
239
240
async def _configure_client_with_args(client, args):
241
    if args.user:
242
        client.set_user(args.user)
243
    if args.password:
244
        client.set_password(args.password)
245
    await client.set_security_string(args.security)
246
247
248
def uawrite():
249
    run(_uawrite())
250
251
252
async def _uawrite():
253
    parser = argparse.ArgumentParser(description="Write attribute of a node, per default write value of node")
254
    add_common_args(parser)
255
    parser.add_argument("-a",
256
                        "--attribute",
257
                        dest="attribute",
258
                        type=int,
259
                        default=ua.AttributeIds.Value,
260
                        help="Set attribute to read")
261
    parser.add_argument("-l",
262
                        "--list",
263
                        "--array",
264
                        dest="array",
265
                        default="guess",
266
                        choices=["guess", "true", "false"],
267
                        help="Value is an array")
268
    parser.add_argument("-t",
269
                        "--datatype",
270
                        dest="datatype",
271
                        default="guess",
272
                        choices=["guess", 'byte', 'sbyte', 'nodeid', 'expandednodeid', 'qualifiedname', 'browsename', 'string', 'float', 'double', 'int16', 'int32', "int64", 'uint16', 'uint32', 'uint64', "bool", "string", 'datetime', 'bytestring', 'xmlelement', 'statuscode', 'localizedtext'],
273
                        help="Data type to return")
274
    parser.add_argument("value",
275
                        help="Value to be written",
276
                        metavar="VALUE")
277
    args = parse_args(parser, requirenodeid=True)
278
279
    client = Client(args.url, timeout=args.timeout)
280
    await _configure_client_with_args(client, args)
281
    try:
282
        await client.connect()
283
        node = await get_node(client, args)
284
        val = _val_to_variant(args.value, args)
285
        await node.write_attribute(args.attribute, ua.DataValue(val))
286
    finally:
287
        await client.disconnect()
288
289
290
def uals():
291
    run(_uals())
292
293
294
async def _uals():
295
    parser = argparse.ArgumentParser(description="Browse OPC-UA node and print result")
296
    add_common_args(parser)
297
    parser.add_argument("-l",
298
                        dest="long_format",
299
                        const=3,
300
                        nargs="?",
301
                        type=int,
302
                        help="use a long listing format")
303
    parser.add_argument("-d",
304
                        "--depth",
305
                        default=1,
306
                        type=int,
307
                        help="Browse depth")
308
309
    args = parse_args(parser)
310
    if args.long_format is None:
311
        args.long_format = 1
312
313
    client = Client(args.url, timeout=args.timeout)
314
    await _configure_client_with_args(client, args)
315
    try:
316
        async with client:
317
            node = await get_node(client, args)
318
            print("Browsing node {0} at {1}\n".format(node, args.url))
319
            if args.long_format == 0:
320
                await _lsprint_0(node, args.depth - 1)
321
            elif args.long_format == 1:
322
                await _lsprint_1(node, args.depth - 1)
323
            else:
324
                _lsprint_long(node, args.depth - 1)
325
    except OSError as e:
326
        print(e)
327
        sys.exit(1)
328
329
330
async def _lsprint_0(node, depth, indent=""):
331
    if not indent:
332
        print("{0:30} {1:25}".format("DisplayName", "NodeId"))
333
        print("")
334
    for desc in await node.get_children_descriptions():
335
        print("{0}{1:30} {2:25}".format(indent, desc.DisplayName.to_string(), desc.NodeId.to_string()))
336
        if depth:
337
            await _lsprint_0(Node(node.server, desc.NodeId), depth - 1, indent + "  ")
338
339
340
async def _lsprint_1(node, depth, indent=""):
341
    if not indent:
342
        print("{0:30} {1:25} {2:25} {3:25}".format("DisplayName", "NodeId", "BrowseName", "Value"))
343
        print("")
344
345
    for desc in await node.get_children_descriptions():
346
        if desc.NodeClass == ua.NodeClass.Variable:
347
            try:
348
                val = await Node(node.server, desc.NodeId).read_value()
349
            except UaStatusCodeError as err:
350
                val = "Bad (0x{0:x})".format(err.code)
351
            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))
352
        else:
353
            print("{0}{1:30} {2!s:25} {3!s:25}".format(indent, desc.DisplayName.to_string(), desc.NodeId.to_string(), desc.BrowseName.to_string()))
354
        if depth:
355
            await _lsprint_1(Node(node.server, desc.NodeId), depth - 1, indent + "  ")
356
357
358
def _lsprint_long(pnode, depth, indent=""):
359
    if not indent:
360
        print("{0:30} {1:25} {2:25} {3:10} {4:30} {5:25}".format("DisplayName", "NodeId", "BrowseName", "DataType", "Timestamp", "Value"))
361
        print("")
362
    for node in pnode.get_children():
363
        attrs = node.read_attributes([ua.AttributeIds.DisplayName,
364
                                     ua.AttributeIds.BrowseName,
365
                                     ua.AttributeIds.NodeClass,
366
                                     ua.AttributeIds.WriteMask,
367
                                     ua.AttributeIds.UserWriteMask,
368
                                     ua.AttributeIds.DataType,
369
                                     ua.AttributeIds.Value])
370
        name, bname, nclass, mask, umask, dtype, val = [attr.Value.Value for attr in attrs]
371
        update = attrs[-1].ServerTimestamp
372
        if nclass == ua.NodeClass.Variable:
373
            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))
374
        else:
375
            print("{0}{1:30} {2:25} {3:25}".format(indent, name.to_string(), bname.to_string(), node.nodeid.to_string()))
376
        if depth:
377
            _lsprint_long(node, depth - 1, indent + "  ")
378
379
380
class SubHandler(object):
381
382
    def datachange_notification(self, node, val, data):
383
        print("New data change event", node, val, data)
384
385
    def event_notification(self, event):
386
        print("New event", event)
387
388
389
async def uasubscribe():
390
    run(_uasubscribe())
391
392
393
async def _uasubscribe():
394
    parser = argparse.ArgumentParser(description="Subscribe to a node and print results")
395
    add_common_args(parser)
396
    parser.add_argument("-t",
397
                        "--eventtype",
398
                        dest="eventtype",
399
                        default="datachange",
400
                        choices=['datachange', 'event'],
401
                        help="Event type to subscribe to")
402
403
    args = parse_args(parser, requirenodeid=False)
404
    if args.eventtype == "datachange":
405
        _require_nodeid(parser, args)
406
    else:
407
        # FIXME: this is broken, someone may have written i=84 on purpose
408
        if args.nodeid == "i=84" and args.path == "":
409
            args.nodeid = "i=2253"
410
411
    client = Client(args.url, timeout=args.timeout)
412
    await _configure_client_with_args(client, args)
413
    await client.connect()
414
    try:
415
        node = await get_node(client, args)
416
        handler = SubHandler()
417
        sub = await client.create_subscription(500, handler)
418
        if args.eventtype == "datachange":
419
            await sub.subscribe_data_change(node)
420
        else:
421
            await sub.subscribe_events(node)
422
        print("Type Ctr-C to exit")
423
        while True:
424
            time.sleep(1)
425
    finally:
426
        await client.disconnect()
427
428
429
def application_to_strings(app):
430
    result = []
431
    result.append(('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 "{0} bytes".format(len(der))
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 as e:
507
        print(e)
508
        sys.exit(1)
509
510
    sys.exit(0)
511
512
513
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 = sync.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
    with server:
578
        try:
579
            if args.shell:
580
                embed()
581
            elif args.populate:
582
                count = 0
583
                while True:
584
                    time.sleep(1)
585
                    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...
586
                    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...
587
                    count += 1
588
            else:
589
                while True:
590
                    time.sleep(1)
591
        except KeyboardInterrupt:
592
            pass
593
    sys.exit(0)
594
595
596
def uadiscover():
597
    run(_uadiscover())
598
599
600
async def _uadiscover():
601
    parser = argparse.ArgumentParser(description="Performs OPC UA discovery and prints information on servers and endpoints.")
602
    add_minimum_args(parser)
603
    parser.add_argument("-n",
604
                        "--network",
605
                        action="store_true",
606
                        help="Also send a FindServersOnNetwork request to server")
607
    #parser.add_argument("-s",
608
                        #"--servers",
609
                        #action="store_false",
610
                        #help="send a FindServers request to server")
611
    #parser.add_argument("-e",
612
                        #"--endpoints",
613
                        #action="store_false",
614
                        #help="send a GetEndpoints request to server")
615
    args = parse_args(parser)
616
617
    client = Client(args.url, timeout=args.timeout)
618
619
    try:
620
        if args.network:
621
            print("Performing discovery at {0}\n".format(args.url))
622
            for i, server in enumerate(await client.connect_and_find_servers_on_network(), start=1):
623
                print('Server {0}:'.format(i))
624
                #for (n, v) in application_to_strings(server):
625
                    #print('  {}: {}'.format(n, v))
626
                print('')
627
628
        print("Performing discovery at {0}\n".format(args.url))
629
        for i, server in enumerate(await client.connect_and_find_servers(), start=1):
630
            print('Server {0}:'.format(i))
631
            for (n, v) in application_to_strings(server):
632
                print('  {0}: {1}'.format(n, v))
633
            print('')
634
635
        for i, ep in enumerate(await client.connect_and_get_server_endpoints(), start=1):
636
            print('Endpoint {0}:'.format(i))
637
            for (n, v) in endpoint_to_strings(ep):
638
                print('  {0}: {1}'.format(n, v))
639
            print('')
640
    except OSError as e:
641
        print(e)
642
        sys.exit(1)
643
644
    sys.exit(0)
645
646
647
def print_history(o):
648
    print("{0:30} {1:10} {2}".format('Source timestamp', 'Status', 'Value'))
649
    for d in o:
650
        print("{0:30} {1:10} {2}".format(str(d.SourceTimestamp), d.StatusCode.name, d.Value.Value))
651
652
653
def str_to_datetime(s, default=None):
654
    if not s:
655
        if default is not None:
656
            return default
657
        return datetime.utcnow()
658
    # FIXME: try different datetime formats
659
    for fmt in ["%Y-%m-%d", "%Y-%m-%d %H:%M", "%Y-%m-%d %H:%M:%S"]:
660
        try:
661
            return datetime.strptime(s, fmt)
662
        except ValueError:
663
            pass
664
665
666
def uahistoryread():
667
    run(_uahistoryread())
668
669
670
async def _uahistoryread():
671
    parser = argparse.ArgumentParser(description="Read history of a node")
672
    add_common_args(parser)
673
    parser.add_argument("--starttime",
674
                        default=None,
675
                        help="Start time, formatted as YYYY-MM-DD [HH:MM[:SS]]. Default: current time - one day")
676
    parser.add_argument("--endtime",
677
                        default=None,
678
                        help="End time, formatted as YYYY-MM-DD [HH:MM[:SS]]. Default: current time")
679
    parser.add_argument("-e",
680
                        "--events",
681
                        action="store_true",
682
                        help="Read event history instead of data change history")
683
    parser.add_argument("-l",
684
                        "--limit",
685
                        type=int,
686
                        default=10,
687
                        help="Maximum number of notfication to return")
688
689
    args = parse_args(parser, requirenodeid=True)
690
691
    client = Client(args.url, timeout=args.timeout)
692
    await _configure_client_with_args(client, args)
693
    await client.connect()
694
    try:
695
        node = await get_node(client, args)
696
        starttime = str_to_datetime(args.starttime, datetime.utcnow() - timedelta(days=1))
697
        endtime = str_to_datetime(args.endtime, datetime.utcnow())
698
        print("Reading raw history of node {0} at {1}; start at {2}, end at {3}\n".format(node, args.url, starttime, endtime))
699
        if args.events:
700
            evs = await node.read_event_history(starttime, endtime, numvalues=args.limit)
701
            for ev in evs:
702
                print(ev)
703
        else:
704
            print_history(await node.read_raw_history(starttime, endtime, numvalues=args.limit))
705
    finally:
706
        await client.disconnect()
707
    sys.exit(0)
708
709
710
def uacall():
711
    run(_uacall())
712
713
714
async def _uacall():
715
    parser = argparse.ArgumentParser(description="Call method of a node")
716
    add_common_args(parser)
717
    parser.add_argument("-m",
718
                        "--method",
719
                        dest="method",
720
                        type=str,
721
                        default=None,
722
                        help="browse name of method to call")
723
    parser.add_argument("-t",
724
                        "--datatype",
725
                        dest="datatype",
726
                        default="guess",
727
                        choices=["guess", 'byte', 'sbyte', 'nodeid', 'expandednodeid', 'qualifiedname', 'browsename', 'string', 'float', 'double', 'int16', 'int32', "int64", 'uint16', 'uint32', 'uint64', "bool", "string", 'datetime', 'bytestring', 'xmlelement', 'statuscode', 'localizedtext'],
728
                        help="Data type to return")
729
    parser.add_argument("-l",
730
                        "--list",
731
                        "--array",
732
                        dest="array",
733
                        default="guess",
734
                        choices=["guess", "true", "false"],
735
                        help="Value is an array")
736
    parser.add_argument("value",
737
                        help="Comma separated value(s) to use for call to method, if any",
738
                        nargs="?",
739
                        metavar="VALUE")
740
741
    args = parse_args(parser, requirenodeid=True)
742
743
    client = Client(args.url, timeout=args.timeout)
744
    await _configure_client_with_args(client, args)
745
    await client.connect()
746
    try:
747
        node = await get_node(client, args)
748
        if args.value is None:
749
            val = ()  # empty tuple
750
        else:
751
            val = args.value.split(",")
752
            val = [_val_to_variant(v, args) for v in val]
753
754
        method_id = None
755
756
        if args.method is not None:
757
            method_id = args.method
758
        else:
759
            methods = await node.get_methods()
760
            if len(methods) == 0:
761
                raise ValueError("No methods in selected node and no method given")
762
            else:
763
                method_id = methods[0]
764
        result = await node.call_method(method_id, *val)
765
        print(f"resulting result_variants={result}")
766
    finally:
767
        await client.disconnect()
768
769
770
def uageneratestructs():
771
    run(_uageneratestructs())
772
773
774
async def _uageneratestructs():
775
    parser = argparse.ArgumentParser(description="Generate a Python module from the xml structure definition (.bsd), the node argument is typically a children of i=93")
776
    add_common_args(parser, require_node=True)
777
    parser.add_argument("-o",
778
                        "--output",
779
                        dest="output_path",
780
                        required=True,
781
                        type=str,
782
                        default=None,
783
                        help="The python file to be generated.",
784
                        )
785
    args = parse_args(parser, requirenodeid=True)
786
787
    client = Client(args.url, timeout=args.timeout)
788
    await _configure_client_with_args(client, args)
789
    await client.connect()
790
    try:
791
        node = await get_node(client, args)
792
        generators, _ = await client.load_type_definitions([node])
793
        generators[0].save_to_file(args.output_path, True)
794
    finally:
795
        await client.disconnect()
796