Completed
Push — master ( 92e9e1...c0b22b )
by Olivier
02:22
created

opcua._require_nodeid()   A

Complexity

Conditions 3

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 6
rs 9.4286
cc 3
1
import logging
2
import sys
3
import argparse
4
from datetime import datetime
5
from enum import Enum
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())) 
16
17
from opcua import ua
18
from opcua import Client
19
from opcua import Server
20
from opcua import Node
21
from opcua import uamethod
22
from opcua import security_policies
23
24
25
def add_minimum_args(parser):
26
    parser.add_argument("-u",
27
                        "--url",
28
                        help="URL of OPC UA server (for example: opc.tcp://example.org:4840)",
29
                        default='opc.tcp://localhost:4841',
30
                        metavar="URL")
31
    parser.add_argument("-v",
32
                        "--verbose",
33
                        dest="loglevel",
34
                        choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'],
35
                        default='WARNING',
36
                        help="Set log level")
37
    parser.add_argument("--timeout",
38
                        dest="timeout",
39
                        type=int,
40
                        default=1,
41
                        help="Set socket timeout (NOT the diverse UA timeouts)")
42
43
44
def add_common_args(parser, default_node='i=84'):
45
    add_minimum_args(parser)
46
    parser.add_argument("-n",
47
                        "--nodeid",
48
                        help="Fully-qualified node ID (for example: i=85). Default: root node",
49
                        default=default_node,
50
                        metavar="NODE")
51
    parser.add_argument("-p",
52
                        "--path",
53
                        help="Comma separated browse path to the node starting at NODE (for example: 3:Mybject,3:MyVariable)",
54
                        default='',
55
                        metavar="BROWSEPATH")
56
    parser.add_argument("-i",
57
                        "--namespace",
58
                        help="Default namespace",
59
                        type=int,
60
                        default=0,
61
                        metavar="NAMESPACE")
62
    parser.add_argument("--security",
63
                        help="Security settings, for example: Basic256,SignAndEncrypt,cert.der,pk.pem[,server_cert.der]. Default: None",
64
                        default='')
65
66
67
def client_security(security, url, timeout):
68
    parts = security.split(',')
69
    if len(parts) < 4:
70
        raise Exception('Wrong format: `{}`, expected at least 4 comma-separated values'.format(security))
71
    policy_class = getattr(security_policies, 'SecurityPolicy' + parts[0])
72
    mode = getattr(ua.MessageSecurityMode, parts[1])
73
    cert = open(parts[2], 'rb').read()
74
    pk = open(parts[3], 'rb').read()
75
    server_cert = None
76
    if len(parts) == 5:
77
        server_cert = open(parts[4], 'rb').read()
78
    else:
79
        # we need server's certificate too. Let's get it from the list of endpoints
80
        client = Client(url, timeout=timeout)
81
        for ep in client.connect_and_get_server_endpoints():
82
            if ep.EndpointUrl.startswith(ua.OPC_TCP_SCHEME) and ep.SecurityMode == mode and ep.SecurityPolicyUri == policy_class.URI:
83
                server_cert = ep.ServerCertificate
84
    return policy_class(server_cert, cert, pk, mode)
85
86
87
def _require_nodeid(parser, args):
88
    # check that a nodeid has been given explicitly, a bit hackish...
89
    if args.nodeid == "i=84" and args.path == "":
90
        parser.print_usage()
91
        print("{}: error: A NodeId or BrowsePath is required".format(parser.prog))
92
        sys.exit(1)
93
94
95
def parse_args(parser, requirenodeid=False):
96
    args = parser.parse_args()
97
    logging.basicConfig(format="%(levelname)s: %(message)s", level=getattr(logging, args.loglevel))
98
    if args.url and '://' not in args.url:
99
        logging.info("Adding default scheme %s to URL %s", ua.OPC_TCP_SCHEME, args.url)
100
        args.url = ua.OPC_TCP_SCHEME + '://' + args.url
101
    if hasattr(args, 'security'):
102
        if args.security:
103
            args.security = client_security(args.security, args.url, args.timeout)
104
        else:
105
            args.security = ua.SecurityPolicy()
106
    if requirenodeid:
107
        _require_nodeid(parser, args)
108
    return args
109
110
111
def get_node(client, args):
112
    node = client.get_node(args.nodeid)
113
    if args.path:
114
        node = node.get_child(args.path.split(","))
115
    return node
116
117
118
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, security_policy=args.security)
137
    client.connect()
138
    try:
139
        node = get_node(client, args)
140
        attr = node.get_attribute(args.attribute)
141
        if args.datatype == "python":
142
            print(attr.Value.Value)
143
        elif args.datatype == "variant":
144
            print(attr.Value)
145
        else:
146
            print(attr)
147
    finally:
148
        client.disconnect()
149
    sys.exit(0)
150
    print(args)
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
    if val in ("true", "True"):
164
        return True
165
    else:
166
        return False
167
168
169
def _arg_to_variant(val, array, ptype, varianttype=None):
170
    val = _args_to_array(val, array)
171
    if isinstance(val, list):
172
        val = [ptype(i) for i in val]
173
    else:
174
        val = ptype(val)
175
    if varianttype:
176
        return ua.Variant(val, varianttype)
177
    else:
178
        return ua.Variant(val)
179
180
181
def _val_to_variant(val, args):
182
    array = args.array
183
    if args.datatype == "guess":
184
        if val in ("true", "True", "false", "False"):
185
            return _arg_to_variant(val, array, _arg_to_bool)
186
        # FIXME: guess bool value
187
        try:
188
            return _arg_to_variant(val, array, int)
189
        except ValueError:
190
            try:
191
                return _arg_to_variant(val, array, float)
192
            except ValueError:
193
                return _arg_to_variant(val, array, str)
194
    elif args.datatype == "bool":
195
        if val in ("1", "True", "true"):
196
            return ua.Variant(True, ua.VariantType.Boolean)
197
        else:
198
            return ua.Variant(False, ua.VariantType.Boolean)
199
    elif args.datatype == "sbyte":
200
        return _arg_to_variant(val, array, int, ua.VariantType.SByte)
201
    elif args.datatype == "byte":
202
        return _arg_to_variant(val, array, int, ua.VariantType.Byte)
203
    #elif args.datatype == "uint8":
204
        #return _arg_to_variant(val, array, int, ua.VariantType.Byte)
205
    elif args.datatype == "uint16":
206
        return _arg_to_variant(val, array, int, ua.VariantType.UInt16)
207
    elif args.datatype == "uint32":
208
        return _arg_to_variant(val, array, int, ua.VariantType.UInt32)
209
    elif args.datatype == "uint64":
210
        return _arg_to_variant(val, array, int, ua.VariantType.UInt64)
211
    #elif args.datatype == "int8":
212
        #return ua.Variant(int(val), ua.VariantType.Int8)
213
    elif args.datatype == "int16":
214
        return _arg_to_variant(val, array, int, ua.VariantType.Int16)
215
    elif args.datatype == "int32":
216
        return _arg_to_variant(val, array, int, ua.VariantType.Int32)
217
    elif args.datatype == "int64":
218
        return _arg_to_variant(val, array, int, ua.VariantType.Int64)
219
    elif args.datatype == "float":
220
        return _arg_to_variant(val, array, float, ua.VariantType.Float)
221
    elif args.datatype == "double":
222
        return _arg_to_variant(val, array, float, ua.VariantType.Double)
223
    elif args.datatype == "string":
224
        return _arg_to_variant(val, array, str, ua.VariantType.String)
225
    elif args.datatype == "datetime":
226
        raise NotImplementedError
227
    elif args.datatype == "Guid":
228
        return _arg_to_variant(val, array, bytes, ua.VariantType.Guid)
229
    elif args.datatype == "ByteString":
230
        return _arg_to_variant(val, array, bytes, ua.VariantType.ByteString)
231
    elif args.datatype == "xml":
232
        return _arg_to_variant(val, array, str, ua.VariantType.XmlElement)
233
    elif args.datatype == "nodeid":
234
        return _arg_to_variant(val, array, ua.NodeId.from_string, ua.VariantType.NodeId)
235
    elif args.datatype == "expandednodeid":
236
        return _arg_to_variant(val, array, ua.ExpandedNodeId.from_string, ua.VariantType.ExpandedNodeId)
237
    elif args.datatype == "statuscode":
238
        return _arg_to_variant(val, array, int, ua.VariantType.StatusCode)
239
    elif args.datatype in ("qualifiedname", "browsename"):
240
        return _arg_to_variant(val, array, ua.QualifiedName.from_string, ua.VariantType.QualifiedName)
241
    elif args.datatype == "LocalizedText":
242
        return _arg_to_variant(val, array, ua.LocalizedText, ua.VariantType.LocalizedText)
243
244
245
def uawrite():
246
    parser = argparse.ArgumentParser(description="Write attribute of a node, per default write value of node")
247
    add_common_args(parser)
248
    parser.add_argument("-a",
249
                        "--attribute",
250
                        dest="attribute",
251
                        type=int,
252
                        default=ua.AttributeIds.Value,
253
                        help="Set attribute to read")
254
    parser.add_argument("-l",
255
                        "--list",
256
                        "--array",
257
                        dest="array",
258
                        default="guess",
259
                        choices=["guess", "true", "false"],
260
                        help="Value is an array")
261
    parser.add_argument("-t",
262
                        "--datatype",
263
                        dest="datatype",
264
                        default="guess",
265
                        choices=["guess", 'byte', 'sbyte', 'nodeid', 'expandednodeid', 'qualifiedname', 'browsename', 'string', 'float', 'double', 'int16', 'int32', "int64", 'uint16', 'uint32', 'uint64', "bool", "string", 'datetime', 'bytestring', 'xmlelement', 'statuscode', 'localizedtext'],  
266
                        help="Data type to return")
267
    parser.add_argument("value",
268
                        help="Value to be written",
269
                        metavar="VALUE")
270
    args = parse_args(parser, requirenodeid=True)
271
272
    client = Client(args.url, timeout=args.timeout, security_policy=args.security)
273
    client.connect()
274
    try:
275
        node = get_node(client, args)
276
        val = _val_to_variant(args.value, args)
277
        node.set_attribute(args.attribute, ua.DataValue(val))
278
    finally:
279
        client.disconnect()
280
    sys.exit(0)
281
    print(args)
282
283
284
def uals():
285
    parser = argparse.ArgumentParser(description="Browse OPC-UA node and print result")
286
    add_common_args(parser)
287
    parser.add_argument("-l",
288
                        dest="long_format",
289
                        const=3,
290
                        nargs="?",
291
                        type=int,
292
                        help="use a long listing format")
293
    parser.add_argument("-d",
294
                        "--depth",
295
                        default=1,
296
                        type=int,
297
                        help="Browse depth")
298
299
    args = parse_args(parser)
300
    if args.long_format is None:
301
        args.long_format = 1
302
303
    client = Client(args.url, timeout=args.timeout, security_policy=args.security)
304
    client.connect()
305
    try:
306
        node = get_node(client, args)
307
        print("Browsing node {} at {}\n".format(node, args.url))
308
        if args.long_format == 0:
309
            _lsprint_0(node, args.depth - 1)
310
        elif args.long_format == 1:
311
            _lsprint_1(node, args.depth - 1)
312
        else:
313
            _lsprint_long(node, args.depth - 1)
314
    finally:
315
        client.disconnect()
316
    sys.exit(0)
317
    print(args)
318
319
320
def _lsprint_0(node, depth, indent=""):
321
    if not indent:
322
        print("{:30} {:25}".format("DisplayName", "NodeId"))
323
        print("")
324
    for desc in node.get_children_descriptions():
325
        print("{}{:30} {:25}".format(indent, desc.DisplayName.to_string(), desc.NodeId.to_string()))
326
        if depth:
327
            _lsprint_0(Node(node.server, desc.NodeId), depth - 1, indent + "  ")
328
329
330
def _lsprint_1(node, depth, indent=""):
331
    if not indent:
332
        print("{:30} {:25} {:25} {:25}".format("DisplayName", "NodeId", "BrowseName", "Value"))
333
        print("")
334
335
    for desc in node.get_children_descriptions():
336
        if desc.NodeClass == ua.NodeClass.Variable:
337
            val = Node(node.server, desc.NodeId).get_value()
338
            print("{}{:30} {!s:25} {!s:25}, {!s:3}".format(indent, desc.DisplayName.to_string(), desc.NodeId.to_string(), desc.BrowseName.to_string(), val))
339
        else:
340
            print("{}{:30} {!s:25} {!s:25}".format(indent, desc.DisplayName.to_string(), desc.NodeId.to_string(), desc.BrowseName.to_string()))
341
        if depth:
342
            _lsprint_1(Node(node.server, desc.NodeId), depth - 1, indent + "  ")
343
344
345
def _lsprint_long(pnode, depth, indent=""):
346
    if not indent:
347
        print("{:30} {:25} {:25} {:10} {:30} {:25}".format("DisplayName", "NodeId", "BrowseName", "DataType", "Timestamp", "Value"))
348
        print("")
349
    for node in pnode.get_children():
350
        attrs = node.get_attributes([ua.AttributeIds.DisplayName, 
351
                                     ua.AttributeIds.BrowseName,
352
                                     ua.AttributeIds.NodeClass,
353
                                     ua.AttributeIds.WriteMask,
354
                                     ua.AttributeIds.UserWriteMask,
355
                                     ua.AttributeIds.DataType,
356
                                     ua.AttributeIds.Value])
357
        name, bname, nclass, mask, umask, dtype, val = [attr.Value.Value for attr in attrs]
358
        update = attrs[-1].ServerTimestamp
359
        if nclass == ua.NodeClass.Variable:
360
            print("{}{:30} {:25} {:25} {:10} {!s:30} {!s:25}".format(indent, name.to_string(), node.nodeid.to_string(), bname.to_string(), dtype.to_string(), update, val))
361
        else:
362
            print("{}{:30} {:25} {:25}".format(indent, name.to_string(), bname.to_string(), node.nodeid.to_string()))
363
        if depth:
364
            _lsprint_long(node, depth - 1, indent + "  ")
365
366
367
class SubHandler(object):
368
369
    def datachange_notification(self, node, val, data):
370
        print("New data change event", node, val, data)
371
372
    def event_notification(self, event):
373
        print("New event", event)
374
375
376
def uasubscribe():
377
    parser = argparse.ArgumentParser(description="Subscribe to a node and print results")
378
    add_common_args(parser)
379
    parser.add_argument("-t",
380
                        "--eventtype",
381
                        dest="eventtype",
382
                        default="datachange",
383
                        choices=['datachange', 'event'],
384
                        help="Event type to subscribe to")
385
386
    args = parse_args(parser, requirenodeid=False)
387
    if args.eventtype == "datachange":
388
        _require_nodeid(parser, args)
389
    else:
390
        # FIXME: this is broken, someone may have written i=84 on purpose
391
        if args.nodeid == "i=84" and args.path == "":
392
            args.nodeid = "i=2253"
393
394
    client = Client(args.url, timeout=args.timeout, security_policy=args.security)
395
    client.connect()
396
    try:
397
        node = get_node(client, args)
398
        handler = SubHandler()
399
        sub = client.create_subscription(500, handler)
400
        if args.eventtype == "datachange":
401
            sub.subscribe_data_change(node)
402
        else:
403
            sub.subscribe_events(node)
404
        embed()
405
    finally:
406
        client.disconnect()
407
    sys.exit(0)
408
    print(args)
409
410
411
# converts numeric value to its enum name.
412
def enum_to_string(klass, value):
413
    if isinstance(value, Enum):
414
        return value.name
415
    # if value is not a subtype of Enum, try to find a constant
416
    # with this value in this class
417
    for k, v in vars(klass).items():
418
        if not k.startswith('__') and v == value:
419
            return k
420
    return 'Unknown {} ({})'.format(klass.__name__, value)
421
422
423
def application_to_strings(app):
424
    result = []
425
    result.append(('Application URI', app.ApplicationUri))
426
    optionals = [
427
        ('Product URI', app.ProductUri),
428
        ('Application Name', app.ApplicationName.to_string()),
429
        ('Application Type', enum_to_string(ua.ApplicationType, app.ApplicationType)),
430
        ('Gateway Server URI', app.GatewayServerUri),
431
        ('Discovery Profile URI', app.DiscoveryProfileUri),
432
    ]
433
    for (n, v) in optionals:
434
        if v:
435
            result.append((n, v))
436
    for url in app.DiscoveryUrls:
437
        result.append(('Discovery URL', url))
438
    return result  # ['{}: {}'.format(n, v) for (n, v) in result]
439
440
441
def endpoint_to_strings(ep):
442
    result = [('Endpoint URL', ep.EndpointUrl)]
443
    result += application_to_strings(ep.Server)
444
    result += [
445
        ('Server Certificate', len(ep.ServerCertificate)),
446
        ('Security Mode', enum_to_string(ua.MessageSecurityMode, ep.SecurityMode)),
447
        ('Security Policy URI', ep.SecurityPolicyUri)]
448
    for tok in ep.UserIdentityTokens:
449
        result += [
450
            ('User policy', tok.PolicyId),
451
            ('  Token type', enum_to_string(ua.UserTokenType, tok.TokenType))]
452
        if tok.IssuedTokenType or tok.IssuerEndpointUrl:
453
            result += [
454
                ('  Issued Token type', tok.IssuedTokenType),
455
                ('  Issuer Endpoint URL', tok.IssuerEndpointUrl)]
456
        if tok.SecurityPolicyUri:
457
            result.append(('  Security Policy URI', tok.SecurityPolicyUri))
458
    result += [
459
        ('Transport Profile URI', ep.TransportProfileUri),
460
        ('Security Level', ep.SecurityLevel)]
461
    return result
462
463
464
def uaclient():
465
    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")
466
    add_common_args(parser)
467
    parser.add_argument("-c",
468
                        "--certificate",
469
                        help="set client certificate")
470
    parser.add_argument("-k",
471
                        "--private_key",
472
                        help="set client private key")
473
    args = parse_args(parser)
474
475
    client = Client(args.url, timeout=args.timeout, security_policy=args.security)
476
    client.connect()
477
    if args.certificate:
478
        client.load_client_certificate(args.certificate)
479
    if args.private_key:
480
        client.load_private_key(args.private_key)
481
    try:
482
        root = client.get_root_node()
483
        objects = client.get_objects_node()
484
        mynode = get_node(client, args)
485
        embed()
486
    finally:
487
        client.disconnect()
488
    sys.exit(0)
489
490
491
def uaserver():
492
    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")
493
    # we setup a server, this is a bit different from other tool so we do not reuse common arguments
494
    parser.add_argument("-u",
495
                        "--url",
496
                        help="URL of OPC UA server, default is opc.tcp://0.0.0.0:4841",
497
                        default='opc.tcp://0.0.0.0:4841',
498
                        metavar="URL")
499
    parser.add_argument("-v",
500
                        "--verbose",
501
                        dest="loglevel",
502
                        choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'],
503
                        default='WARNING',
504
                        help="Set log level")
505
    parser.add_argument("-x",
506
                        "--xml",
507
                        metavar="XML_FILE",
508
                        help="Populate address space with nodes defined in XML")
509
    parser.add_argument("-p",
510
                        "--populate",
511
                        action="store_false",
512
                        help="Populate address space with some sample nodes")
513
    parser.add_argument("-c",
514
                        "--disable-clock",
515
                        action="store_true",
516
                        help="Disable clock, to avoid seeing many write if debugging an application")
517
    parser.add_argument("-s",
518
                        "--shell",
519
                        action="store_true",
520
                        help="Start python shell instead of randomly changing node values")
521
    args = parser.parse_args()
522
    logging.basicConfig(format="%(levelname)s: %(message)s", level=getattr(logging, args.loglevel))
523
524
    server = Server()
525
    server.set_endpoint(args.url)
526
    server.disable_clock(args.disable_clock)
527
    server.set_server_name("FreeOpcUa Example Server")
528
    if args.xml:
529
        server.import_xml(args.xml)
530
    if args.populate:
531
        @uamethod
532
        def multiply(parent, x, y):
533
            print("multiply method call with parameters: ", x, y)
534
            return x * y
535
536
        uri = "http://examples.freeopcua.github.io"
537
        idx = server.register_namespace(uri)
538
        objects = server.get_objects_node()
539
        myobj = objects.add_object(idx, "MyObject")
540
        mywritablevar = myobj.add_variable(idx, "MyWritableVariable", 6.7)
541
        mywritablevar.set_writable()    # Set MyVariable to be writable by clients
542
        myvar = myobj.add_variable(idx, "MyVariable", 6.7)
543
        myarrayvar = myobj.add_variable(idx, "MyVarArray", [6.7, 7.9])
544
        myprop = myobj.add_property(idx, "MyProperty", "I am a property")
545
        mymethod = myobj.add_method(idx, "MyMethod", multiply, [ua.VariantType.Double, ua.VariantType.Int64], [ua.VariantType.Double])
546
547
    server.start()
548
    try:
549
        if args.shell:
550
            embed()
551
        else:
552
            count = 0
553
            while True:
554
                time.sleep(1)
555
                myvar.set_value(math.sin(count / 10))
556
                myarrayvar.set_value([math.sin(count / 10), math.sin(count / 100)])
557
                count += 1
558
    finally:
559
        server.stop()
560
    sys.exit(0)
561
562
563
def uadiscover():
564
    parser = argparse.ArgumentParser(description="Performs OPC UA discovery and prints information on servers and endpoints.")
565
    add_minimum_args(parser)
566
    parser.add_argument("-n",
567
                        "--network",
568
                        action="store_true",
569
                        help="Also send a FindServersOnNetwork request to server")
570
    #parser.add_argument("-s",
571
                        #"--servers",
572
                        #action="store_false",
573
                        #help="send a FindServers request to server")
574
    #parser.add_argument("-e",
575
                        #"--endpoints",
576
                        #action="store_false",
577
                        #help="send a GetEndpoints request to server")
578
    args = parse_args(parser)
579
    
580
    client = Client(args.url, timeout=args.timeout)
581
582
    if args.network:
583
        print("Performing discovery at {}\n".format(args.url))
584
        for i, server in enumerate(client.connect_and_find_servers_on_network(), start=1):
585
            print('Server {}:'.format(i))
586
            #for (n, v) in application_to_strings(server):
587
                #print('  {}: {}'.format(n, v))
588
            print('')
589
590
    print("Performing discovery at {}\n".format(args.url))
591
    for i, server in enumerate(client.connect_and_find_servers(), start=1):
592
        print('Server {}:'.format(i))
593
        for (n, v) in application_to_strings(server):
594
            print('  {}: {}'.format(n, v))
595
        print('')
596
597
    for i, ep in enumerate(client.connect_and_get_server_endpoints(), start=1):
598
        print('Endpoint {}:'.format(i))
599
        for (n, v) in endpoint_to_strings(ep):
600
            print('  {}: {}'.format(n, v))
601
        print('')
602
603
    sys.exit(0)
604
605
606
def print_history(o):
607
    if isinstance(o, ua.HistoryData):
608
        print("{:30} {:10} {}".format('Source timestamp', 'Status', 'Value'))
609
        for d in o.DataValues:
610
            print("{:30} {:10} {}".format(str(d.SourceTimestamp), d.StatusCode.name, d.Value))
611
612
613
def str_to_datetime(s):
614
    if not s:
615
        return datetime.utcnow()
616
    # try different datetime formats
617
    for fmt in ["%Y-%m-%d", "%Y-%m-%d %H:%M", "%Y-%m-%d %H:%M:%S"]:
618
        try:
619
            return datetime.strptime(s, fmt)
620
        except ValueError:
621
            pass
622
623
624
def uahistoryread():
625
    parser = argparse.ArgumentParser(description="Read history of a node")
626
    add_common_args(parser)
627
    parser.add_argument("--starttime",
628
                        default="",
629
                        help="Start time, formatted as YYYY-MM-DD [HH:MM[:SS]]. Default: current time")
630
    parser.add_argument("--endtime",
631
                        default="",
632
                        help="End time, formatted as YYYY-MM-DD [HH:MM[:SS]]. Default: current time")
633
634
    args = parse_args(parser, requirenodeid=True)
635
636
    client = Client(args.url, timeout=args.timeout, security_policy=args.security)
637
    client.connect()
638
    try:
639
        node = get_node(client, args)
640
        starttime = str_to_datetime(args.starttime)
641
        endtime = str_to_datetime(args.endtime)
642
        print("Reading raw history of node {} at {}; start at {}, end at {}\n".format(node, args.url, starttime, endtime))
643
        print_history(node.read_raw_history(starttime, endtime))
644
    finally:
645
        client.disconnect()
646
    sys.exit(0)
647