Client examples

Easiest client example

# examples/pyuaf/client/easiest_client_example.py

# Start the demo server ($SDK/bin/uaservercpp) of Unified Automation before running this script!

import pyuaf
from pyuaf.util             import Address, NodeId
from pyuaf.client           import Client
from pyuaf.client.settings  import ClientSettings

# create a client named "myClient", and provide the discovery URL of the server (uaservercpp):
myClient = Client(ClientSettings("myClient", ["opc.tcp://localhost:48010"]))

# specify the address of the node of which we would like to read its Value attribute:
# (here we're using an absolute address, i.e. a NodeId(<identifier>, <namespace URI>) and a <server URI>)
someAddress = Address(NodeId("Demo.SimulationSpeed",                         # NodeId identifier
                             "http://www.unifiedautomation.com/DemoServer"), # NodeId namespace URI
                      "urn:UnifiedAutomation:UaServerCpp")                   # server URI

# read the Value attribute of the node that we just addressed:
result = myClient.read( [someAddress] )
print("The value is %d" %result.targets[0].data.value)

How to define addresses?

# examples/pyuaf/util/how_to_define_addresses.py
"""
Example: how to define addresses
====================================================================================================

An OPC UA address space consists of Nodes, connected to each other by References. 

In the UAF, these Nodes can be addressed in two ways:
  1) by their ExpandedNodeId (or NodeId and a server URI)
  2) by a RelativePath relative to another address

1) An ExpandedNodeId is like an "absolute address" of a Node. It contains 
 - a server URI : information about the server that hosts the Node 
 - a NodeId     : information to locate the Node within the address space of that server.

2) A RelativePath on the other hand is like a "relative address" of a Node. Since
an address space of a server is a sort of "web" of Nodes and References, you 
can follow a path of references and nodes to locate a particular Node. This path contains:
 - a starting node: the node from which the relative path starts
 - a relative path: the path that leads to the node of our interest.
                    This path consists of one or more elements, 
                    and each element consists of a BrowseName and some other properties.

Either way, the node is defined within a certain namespace (a sort of "context") because they 
consist of elements that have an associated namespace URI. This URI (a Uniform Resource Identifier)
is in fact just a string that fully defines the namespace. For instance, the standard OPC UA
namespace has the namespace URI "http://opcfoundation.org/UA/". This means that all NodeIds 
and BrowseNames defined by the OPC UA standard are all qualified by this OPC UA namespace URI. 
If you define your own nodes, you should define them within your own namespace, which could have
the following URI for example: "http://www.myorganization.org/mymachines/".

While namespaces are defined by a URI, they can also be referred to by an integer: the namespace 
index. OPC UA servers expose a NamespaceArray that map these namespace indexes to URIs.
So all identifiers and qualified names exposed by a server can simply refer to a namespace index, 
and it is the responsibility of the client to read the NamespaceArray and use it to convert the 
namespace indexes into URIs. Fortunately, a UAF Client will do this conversion for you, behind the
scenes. Namespace indexes therefore do NOT suffice to qualify an identifier or a name, since they
depend on a mapping (the NamespaceArray), and this mapping depends on the server and may change
over time. One notable exception is namespace index 0 (zero). This index is reserved for the 
standard OPC UA namespace, so it ALWAYS (!) refers to namespace URI "http://opcfoundation.org/UA/".
""" 

import pyuaf
from pyuaf.client                   import Client
from pyuaf.client.settings          import ClientSettings
from pyuaf.util                     import Address, ExpandedNodeId, RelativePathElement, QualifiedName, LocalizedText, NodeId
from pyuaf.util.opcuaidentifiers    import OpcUaId_RootFolder, OpcUaId_ObjectsFolder, OpcUaId_ObjectsFolder, OpcUaId_Server
from pyuaf.util.constants           import OPCUA_NAMESPACE_URI, OPCUA_NAMESPACE_ID

#========================================================#
#                     standard nodes                     #
#========================================================#

# First we define some standard OPC UA nodes. 
# Standard nodes have NodeIds that are fully defined by the OPC UA standard.
# For instance, the Root node that is present on all servers, has a
#   - namespace URI "http://opcfoundation.org/UA/" --> defined as pyuaf.util.constants.OPCUA_NAMESPACE_URI
#   - namespace index 0                            --> defined as pyuaf.util.constants.OPCUA_NAMESPACE_ID
#                                                      (note that index 0 is ALWAYS reserved for the OPC UA namespace!) 
#   - the numeric NodeId identifier 84             --> defined as pyuaf.util.opcuaidentifiers.OpcUaId_RootFolder
# 
# So here are all valid ways to define the Root node: 
Root_NodeId = NodeId(84, 0)
Root_NodeId = NodeId(OpcUaId_RootFolder, OPCUA_NAMESPACE_ID)                      # same as the previous, but with symbols
Root_NodeId = NodeId(OpcUaId_RootFolder, OPCUA_NAMESPACE_URI)                     # namespace URI instead of namespace index
Root_NodeId = NodeId(OpcUaId_RootFolder, OPCUA_NAMESPACE_URI, OPCUA_NAMESPACE_ID) # redundant namespace info, but valid

# NodeIds don't contain info about the server they are hosted by.
# If you specifically want to refer to a NodeId within a particular server, you need to
# use an ExpandedNodeId, which contains
#   - a NodeId
#   - and a serverUri
# Assume from now on that the particular nodes we want to refer to, are hosted by a server defined
# by the server URI "urn:UnifiedAutomation:UaDemoserver":
serverUri = "urn:UnifiedAutomation:UaDemoserver" 
# Then we can refer to the Root node like this:
Root_Node = Address( Root_NodeId, serverUri )
# An absolute address can also be created directly from an ExpandedNodeId:
Root_Node = Address( ExpandedNodeId(OpcUaId_RootFolder, OPCUA_NAMESPACE_ID, serverUri) )

# within the Root folder, there is another standard node called the Objects folder:
Objects_Node = Address( NodeId(OpcUaId_ObjectsFolder, OPCUA_NAMESPACE_ID), serverUri )

# we could also refer to the Objects node via a relative path!
# We can do this, since the standard Object node has a standard BrowseName. 
# The following are all valid ways to define the BrowseName of the Objects node 
Objects_BrowseName = QualifiedName("Objects", OPCUA_NAMESPACE_ID)
Objects_BrowseName = QualifiedName("Objects", OPCUA_NAMESPACE_URI)                      # namespace URI instead of index
Objects_BrowseName = QualifiedName("Objects", OPCUA_NAMESPACE_URI, OPCUA_NAMESPACE_ID)  # redundant namespace info, but valid
# we can now create a path from the Root node to the Objects node, using a path of one element:
Objects_Node = Address( Root_Node, [ RelativePathElement(Objects_BrowseName) ] )
# The Objects_Node that we just created, refers to the exact same node as the Objects_Node that we previously created.

# within the Objects node, there is a standard node called Server, which represents a sort 
# of object that contains all kinds of information about the server. 
# The following are all equally valid ways to define this node:
Server_Node = Address( ExpandedNodeId( NodeId(OpcUaId_Server, OPCUA_NAMESPACE_ID), serverUri) )
Server_Node = Address( ExpandedNodeId( OpcUaId_Server, OPCUA_NAMESPACE_ID, serverUri) )
Server_Node = Address( Root_Node   , [ RelativePathElement(QualifiedName("Objects", OPCUA_NAMESPACE_ID)), 
                                       RelativePathElement(QualifiedName("Server" , OPCUA_NAMESPACE_ID)) ] )
Server_Node = Address( Objects_Node, [ RelativePathElement(QualifiedName("Server" , OPCUA_NAMESPACE_ID)) ] )
# note that all Server_Node instances are equally valid, even the last one 
# (which is a "relative path to a relative path to an ExpandedNodeId") 



How to read some data?

# examples/pyuaf/client/how_to_read_some_data.py
"""
EXAMPLE: how to read some data
====================================================================================================

A UAF Client can read data in two ways:
  - either using the "convenience" method: read()
  - or using the "generic" method: processRequest() 

The "convenience" methods (such as read(), beginRead(), write(), ...) are conveniently to use 
 (since they accept the most frequently used parameters directly), 
but they are less powerful than the "generic" processRequest() method 
 (since this method accepts ReadRequests, AsyncReadRequests, etc. that can be fully configured).

In this example, we will use several ways to read data:
 - first option  : "convenience" synchronous  function read()
 - second option : "generic"     synchronous  function processRequest()
 - third option  : "convenience" asynchronous function beginRead()       , with external callback
 - fourth option : "generic"     asynchronous function processRequest()  , with external callback
 - fifth option  : "convenience" asynchronous function beginRead()       , with overridden callback
 - sixth option  : "generic"     asynchronous function processRequest()  , with overridden callback

To run the example, start the UaDemoServer of UnifiedAutomation first on the same machine. 
"""

import time, os

import pyuaf
from pyuaf.client           import Client
from pyuaf.client.settings  import ClientSettings, ReadSettings, SessionSettings
from pyuaf.client.requests  import ReadRequest, AsyncReadRequest, ReadRequestTarget 
from pyuaf.util             import Address, NodeId, RelativePathElement, QualifiedName, LocalizedText
from pyuaf.util             import primitives
from pyuaf.util.errors      import UafError


# define the namespace URI and server URI of the UaDemoServer
demoNsUri     = "http://www.unifiedautomation.com/DemoServer"
demoServerUri = "urn:UnifiedAutomation:UaServerCpp"

# define some addresses of nodes of which we will read the Value attribute
# (you could also define addresses as Relative Paths to other addresses, 
#  see the example that shows you how to define addresses)
someDoubleNode        = Address( NodeId("Demo.Static.Scalar.Double"              , demoNsUri), demoServerUri )
someUInt32Node        = Address( NodeId("Demo.Static.Scalar.UInt32"              , demoNsUri), demoServerUri )
someStringNode        = Address( NodeId("Demo.Static.Scalar.String"              , demoNsUri), demoServerUri )
someLocalizedTextNode = Address( NodeId("Demo.Static.Scalar.LocalizedText"       , demoNsUri), demoServerUri )
someSByteArrayNode    = Address( NodeId("Demo.Static.Arrays.SByte"               , demoNsUri), demoServerUri )

# define a function to print the readResult (which is of type pyuaf.client.results.ReadResult)
def printResult(readResult):
    # overall result
    print("The overall status is: %s" %str(readResult.overallStatus))
    
    # target 0:
    status = readResult.targets[0].status                         # 'status' has type pyuaf.util.Status
    data   = readResult.targets[0].data                           # 'data' has type pyuaf.util.primitives.Double
    if status.isGood() and isinstance(data, primitives.Double):
        print("The double is: %.3f" %data.value)
    
    # target 1:
    status = readResult.targets[1].status                         # 'status' has type pyuaf.util.Status
    data   = readResult.targets[1].data                           # 'data' has type pyuaf.util.primitives.UInt32
    if status.isGood() and isinstance(data, primitives.UInt32):
        print("The uint32 is: %d" %data.value)
    
    # target 2:
    status = readResult.targets[2].status                         # 'status' has type pyuaf.util.Status
    data   = readResult.targets[2].data                           # 'data' has type pyuaf.util.primitives.String
    if status.isGood() and isinstance(data, primitives.String):
        print("The string is: '%s'" %data.value)
    
    # target 3:
    status = readResult.targets[3].status                         # 'status' has type pyuaf.util.Status
    data   = readResult.targets[3].data                           # 'data' has type pyuaf.util.LocalizedText
    if status.isGood() and isinstance(data, LocalizedText):
        print("The locale is: '%s', the text is: '%s'" %(data.locale(), data.text()))
    
    # target 4:
    status = readResult.targets[4].status                         # 'status' has type pyuaf.util.Status
    data   = readResult.targets[4].data                           # 'data' is a list of pyuaf.util.primitives.SByte
    if status.isGood() and isinstance(data, list):
        print("The array is:")
        for i in xrange(len(data)):
            print(" - array[%d] = %d" %(i, data[i].value))


# define the ClientSettings:
settings = ClientSettings()
settings.applicationName = "MyClient"
settings.discoveryUrls.append("opc.tcp://localhost:48010")

# create the client
myClient = Client(settings)



# read the node attributes all at once
print("")
print("First option: use the convenience function read() to read data synchronously")
print("============================================================================")
    
# OPTIONAL: You could also provide a ReadSettings to configure a call timeout, 
#           or maximum age of the values, or ... 
serviceSettings = ReadSettings()
serviceSettings.callTimeoutSec = 0.5
serviceSettings.maxAgeSec = 1.0

# OPTIONAL: And you could also provide a SessionConfig to configure the sessions 
#           (e.g. set the timeout to 600.0 seconds)
#           For more info about SessionConfigs, see the sessionconfig_example
sessionSettings = SessionSettings()
sessionSettings.sessionTimeoutSec = 600.0

try:
    # now read the node attributes
    readResult = myClient.read( addresses     = [someDoubleNode, someUInt32Node, someStringNode, 
                                                 someLocalizedTextNode, someSByteArrayNode],
                                serviceSettings = serviceSettings, # optional argument
                                sessionSettings = sessionSettings) # optional argument
    
    # print the result using the function we defined before
    printResult(readResult)
except UafError, e:
    print("Some error occurred: %s" %e)



print("")
print("Second option: use the generic function processRequest() to read data synchronously")
print("===================================================================================")

# create a request with 5 targets
readRequest = ReadRequest(5)
readRequest.targets[0].address = someDoubleNode
readRequest.targets[1].address = someUInt32Node
readRequest.targets[2].address = someStringNode
readRequest.targets[3].address = someLocalizedTextNode
readRequest.targets[4].address = someSByteArrayNode

# we could also add a 6th node, of which we want to read the DisplayName (= of type LocalizedText)
readRequest.targets.append(ReadRequestTarget()) # or readRequest.targets.resize(6)
readRequest.targets[5].address     = someDoubleNode
readRequest.targets[5].attributeId = pyuaf.util.attributeids.DisplayName
readRequest.serviceSettings        = serviceSettings # optional
readRequest.sessionSettings        = sessionSettings # optional

try:    
    # process the request
    readResult = myClient.processRequest(readRequest)
    
    # print the result
    printResult(readResult)
    
    # also print the display name of the 6th node:
    status = readResult.targets[5].status
    data   = readResult.targets[5].data
    if status.isGood() and isinstance(data, LocalizedText):
        print("The DisplayName-locale is: '%s', the DisplayName-text is: '%s'" %(data.locale(), data.text()))
except UafError, e:
    print("Some error occurred: %s" %e)
    
    

print("")
print("Third option: use the convenience function beginRead() to read data asynchronously")
print("==================================================================================")

# notice we enable asynchronous communication now by providing the printResult() method as 
# a callback function!

try:
    asyncReadResult = myClient.beginRead(addresses       = [someDoubleNode, someUInt32Node, 
                                                            someStringNode, someLocalizedTextNode, 
                                                            someSByteArrayNode],
                                         serviceSettings = serviceSettings, # optional argument
                                         sessionSettings = sessionSettings, # optional argument
                                         callback        = printResult)     # callback function!
    
    # check if there was an "immediate" error on the client side when executing the asynchronous 
    # service call:
    if asyncReadResult.overallStatus.isGood():
        print("OK, the client could invoke the asynchronous request without problems.")
        print("Let's wait a second for the result ...")
        
        time.sleep(1)
        
        print("The read result should have been received by now!")

except UafError, e:
    print("Some error occurred: %s" %e)


print("")
print("Fourth option: use the generic function processRequest() to read data asynchronously")
print("====================================================================================")

# notice we enable asynchronous communication now by providing the printResult() method as 
# a callback function!

# create an asynchronous (!) request with 5 targets
asyncReadRequest = AsyncReadRequest(5)

# we can simply copy the targets of the synchronous request to the asynchronous request:
for i in xrange(5):
    asyncReadRequest.targets[i] = readRequest.targets[i]
    
# OPTIONAL: also copy the service config and session config:
asyncReadRequest.serviceSettings = serviceSettings
asyncReadRequest.sessionSettings = sessionSettings

try:
    # process the request
    asyncReadResult = myClient.processRequest(asyncReadRequest, resultCallback=printResult)
    
    # check if there was an "immediate" error on the client side when executing the asynchronous 
    # service call:
    if asyncReadResult.overallStatus.isGood():
        print("OK, the client could invoke the asynchronous request without problems.")
        print("Let's wait a second for the result ...")
        
        time.sleep(1)
        
        print("The read result should have been received by now!")

    
except UafError, e:
    print("Some error occurred: %s" %e)



print("")
print("Fifth option: read data asynchronously using beginRead() without specifying external callbacks")
print("==============================================================================================")

# instead of using "external" callback functions (such as printResult() above), we can also use
# the "internal" callback function of the Client class. If the argument 'callback' of beginRead() is 
# not used (or 'resultCallback' is not used in case of processRequest()), the asynchronous result
# will be dispatched to the readComplete() method of the Client class. 
# In other words, when you subclass Client, you can simply override the readComplete() method
# to receive the asynchronous read results.

class MySubClasssedClient(Client):
    
    def __init__(self, settings):
        Client.__init__(self, settings)

    def readComplete(self, result): # overridden method!
        print("This is the overridden method readComplete() speaking:")
        printResult(result)

# create the client
mySubClassedClient = MySubClasssedClient(settings)
    
try:
    asyncReadResult = mySubClassedClient.beginRead(
                                         addresses       = [someDoubleNode, someUInt32Node, 
                                                            someStringNode, someLocalizedTextNode, 
                                                            someSByteArrayNode],
                                         serviceSettings = serviceSettings, # optional argument
                                         sessionSettings = sessionSettings) # optional argument!
    # note: the 'callback' argument is not used in the above call!
    
    # check if there was an "immediate" error on the client side when executing the asynchronous 
    # service call:
    if asyncReadResult.overallStatus.isGood():
        print("OK, the client could invoke the asynchronous request without problems.")
        print("Let's wait a second for the result ...")
        
        time.sleep(1)
        
        print("The read result should have been received by now!")
    
except UafError, e:
    print("Some error occurred: %s" %e)
    


print("")
print("Sixth option: read data asynchronously using processRequest() without specifying external callbacks")
print("===================================================================================================")

try:
    # process the request
    asyncReadResult = mySubClassedClient.processRequest(asyncReadRequest) 
    # note: the 'resultCallback' argument is not used in the above call!
    
    # check if there was an "immediate" error on the client side when executing the asynchronous 
    # service call:
    if asyncReadResult.overallStatus.isGood():
        print("OK, the client could invoke the asynchronous request without problems.")
        print("Let's wait a second for the result ...")
        
        time.sleep(1)
        
        print("The read result should have been received by now!")

except UafError, e:
    print("Some error occurred: %s" %e)
    

How to browse some nodes?

# examples/pyuaf/client/how_to_browse_some_nodes.py
"""
EXAMPLE: how to browse some nodes
====================================================================================================

A UAF Client can browse nodes in two ways:
  - either using the "convenience" method: browse()
  - or using the "generic" method: processRequest() 

The "convenience" methods (such as read(), write(), call(), ...) are conveniently to use 
 (since they accept the most frequently used parameters directly), 
but they are less powerful than the "generic" processRequest() method 
 (since this method accepts ReadRequests, WriteRequests, etc. that can be fully configured).

In this example, we will use both ways to browse nodes.

As will be shown in this example, the UAF can automatically take care of BrowseNext calls,
so as a user you don't have to worry about incomplete Browse results!

To run the example, start the UaServerCPP of UnifiedAutomation first on the same machine. 
($SDK/bin/uaservercpp, this executable is part of the SDK).
"""

import time, os, sys

import pyuaf
from pyuaf.client           import Client
from pyuaf.client.settings  import ClientSettings, BrowseSettings, SessionSettings
from pyuaf.client.requests  import BrowseRequest, BrowseRequestTarget
from pyuaf.util             import Address, NodeId
from pyuaf.util             import primitives
from pyuaf.util             import opcuaidentifiers
from pyuaf.util.errors      import UafError


# define the namespace URI and server URI of the UaServerCPP demo server
demoServerUri = "urn:UnifiedAutomation:UaServerCpp"

# define the address of the Root node which we would like to start browsing
rootNode = Address( NodeId(opcuaidentifiers.OpcUaId_RootFolder, 0), demoServerUri )

# define the ClientSettings:
settings = ClientSettings()
settings.applicationName = "MyClient"
settings.discoveryUrls.append("opc.tcp://localhost:48010")

# create the client
myClient = Client(settings)



try:
    print("")
    print("First option: use the convenience function \"browse()\"")
    print("===================================================")
    
    # now browse the root node
    # (notice that there is also an argument called 'maxAutoBrowseNext', which we don't mention
    #  here because we can leave it at the default value (100), to make sure that BrowseNext is 
    # automatically being called for us as much as needed!)
    firstLevelBrowseResult = myClient.browse([rootNode])
    
    # Notice too that you can optionally provide a BrowseSettings and/or a SessionSettings argument
    # for more detailed configuration, like this:
    # OPTIONAL: let's specify a small call timeout, since the UaDemoServer is running 
    #           on the local machine anyway:
    browseSettings = BrowseSettings()
    browseSettings.callTimeoutSec = 2.0
    # OPTIONAL: and finally let's also specify that sessions should have a timeout of 600 seconds:
    sessionSettings = SessionSettings()
    sessionSettings.sessionTimeoutSec = 600.0
    # now call the browse() function:
    myClient.browse([rootNode], browseSettings = browseSettings, sessionSettings = sessionSettings)
    
    # print the result
    print(" - Browse result of the first level:")
    print("   ---------------------------------")
    print(firstLevelBrowseResult)
    
    # we don't expect that "manual" BrowseNext calls are still needed, as the UAF will
    # have done the BrowseNext calls up to 100 times automatically for us! If there would still be
    # some continuation points left, then we surely have some unexpected problem!
    assert len(firstLevelBrowseResult.targets[0].continuationPoint) == 0
    
    # we can now continue browsing the other nodes that we discovered, all simultaneously!!!
    noOfFoundReferences = len(firstLevelBrowseResult.targets[0].references)
    newNodesToBeBrowsed = []
    for i in xrange(noOfFoundReferences):
        newNodesToBeBrowsed.append( Address(firstLevelBrowseResult.targets[0].references[i].nodeId) )
    
    secondLevelBrowseResult = myClient.browse(newNodesToBeBrowsed)
    
    # print the result
    print(" - Browse result of the second level:")
    print("   ----------------------------------")
    print(secondLevelBrowseResult)
    
    
    print("")
    print("Second option: use the generic function \"processRequest()\"")
    print("==========================================================")
    
    # create a request with 1 target, and re-use the browseSettings and sessionSettings 
    # which we created earlier:
    firstLevelBrowseRequest = BrowseRequest(1)
    firstLevelBrowseRequest.targets[0].address = rootNode
    firstLevelBrowseRequest.serviceSettings = browseSettings
    firstLevelBrowseRequest.sessionSettings = sessionSettings
    
    # process the request
    firstLevelBrowseResult = myClient.processRequest(firstLevelBrowseRequest)
    
    # print the result
    print(" - Browse result of the first level:")
    print("   ---------------------------------")
    print(firstLevelBrowseResult)
    
    # we don't expect that "manual" BrowseNext calls are still needed, as the UAF will
    # have done the BrowseNext calls up to 100 times automatically for us! If there would still be
    # some continuation points left, then we surely have some unexpected problem!
    assert len(firstLevelBrowseResult.targets[0].continuationPoint) == 0
    
    # we can now continue browsing the other nodes that we discovered, all simultaneously!!!
    noOfFoundReferences = len(firstLevelBrowseResult.targets[0].references)
    secondLevelBrowseRequest = BrowseRequest(noOfFoundReferences)
    for i in xrange(noOfFoundReferences):
        secondLevelBrowseRequest.targets[i].address = Address(firstLevelBrowseResult.targets[0].references[i].nodeId)
    
    secondLevelBrowseResult =  myClient.processRequest(secondLevelBrowseRequest)
    
    # print the result
    print(" - Browse result of the second level:")
    print("   ----------------------------------")
    print(secondLevelBrowseResult)
    
    
except UafError, e:
    print("Some error occurred: %s" %e)
    raise



How to read some historical data?

# examples/pyuaf/client/how_to_read_some_historical_data.py
"""
EXAMPLE: how to read some historical data
====================================================================================================

A UAF Client can read historical data in two ways:
  - either using the "convenience" methods: historyReadRaw() and historyReadModified()
  - or using the "generic" method: processRequest() 

The "convenience" methods (such as read(), write(), call(), ...) are conveniently to use 
 (since they accept the most frequently used parameters directly), 
but they are less powerful than the "generic" processRequest() method 
 (since this method accepts ReadRequests, WriteRequests, etc. that can be fully configured).

In this example, we will use both ways to read historical data.

As will be shown in this example, the UAF can automatically take care of subsequent 
history reading calls, when the first call returned a continuation point (because either the server
or the client restricted the number of results that were passed in one OPC UA message).
With this "automatic" UAF feature, you don't have to worry about incomplete results anymore!

To run the example, start the UaDemoServer of UnifiedAutomation first on the same machine. 
"""

import time, os, sys

import pyuaf
from pyuaf.client           import Client
from pyuaf.client.settings  import ClientSettings, HistoryReadRawModifiedSettings, SessionSettings
from pyuaf.client.requests  import HistoryReadRawModifiedRequest
from pyuaf.util             import Address, NodeId
from pyuaf.util             import DateTime
from pyuaf.util             import DataValue
from pyuaf.util             import Status
from pyuaf.util             import RelativePathElement
from pyuaf.util             import QualifiedName
from pyuaf.util             import primitives
from pyuaf.util             import opcuaidentifiers
from pyuaf.util             import opcuastatuscodes
from pyuaf.util.errors      import UafError


# define the namespace URI and server URI of the UaDemoServer
demoNsUri     = "http://www.unifiedautomation.com/DemoServer"
demoServerUri = "urn:UnifiedAutomation:UaServerCpp"

# define the addresses of some useful nodes
# (demoAddress is an absolute address, all the others are relative ones) 
demoAddress             = Address( NodeId("Demo", demoNsUri), demoServerUri )
simulationActiveAddress = Address( demoAddress, [RelativePathElement(QualifiedName("SimulationActive", demoNsUri))] )
startSimulationAddress  = Address( demoAddress, [RelativePathElement(QualifiedName("StartSimulation" , demoNsUri))] )
historyAddress          = Address( demoAddress, [RelativePathElement(QualifiedName("History", demoNsUri))] )
loggingActiveAddress    = Address( historyAddress, [RelativePathElement(QualifiedName("DataLoggerActive", demoNsUri))] )
startLoggingAddress     = Address( historyAddress, [RelativePathElement(QualifiedName("StartLogging", demoNsUri))] )
doubleAddress           = Address( historyAddress, [RelativePathElement(QualifiedName("DoubleWithHistory", demoNsUri))] )

# define the ClientSettings:
settings = ClientSettings()
settings.applicationName = "MyClient"
settings.discoveryUrls.append("opc.tcp://localhost:48010")

# create the client
myClient = Client(settings)

# first, we need to start the simulation (if it's not already started),
# and we need to start the logging (if it's not already started)
res = myClient.read( [simulationActiveAddress,loggingActiveAddress]  )

if not res.overallStatus.isGood():
    raise Exception("Couldn't read some nodes of the server: is uaservercpp started and properly configured?")

isSimulationActive = res.targets[0].data.value
isLoggingActive    = res.targets[1].data.value

print("isSimulationActive : %s" %isSimulationActive)
print("isLoggingActive    : %s" %isLoggingActive)

if not isSimulationActive:
    res = myClient.call(demoAddress, startSimulationAddress)
    
    if res.overallStatus.isGood():
        print("The simulation was started")
    else:
        raise Exception("Couldn't start the simulation: %s" %res.overallStatus)

if not isLoggingActive:
    res = myClient.call(historyAddress, startLoggingAddress)
    
    if res.overallStatus.isGood():
        print("The logging was started")
    else:
        raise Exception("Couldn't start the logging: %s" %res.overallStatus)
    

# now wait a bit more than a second, so that we have at least one second of historical data
time.sleep(1.5)


try:
    print("")
    print("First option: use the convenience functions historyReadRaw() and historyReadModified()")
    print("======================================================================================")
    
    print("")
    print("First example:")
    print("-------------")
    
    # Read the raw historical data 
    #   - that is provided by the double node,
    #   - that was recorded during the past second
    #   - with a maximum of a 100 returned values 
    #     (we expect around 10 values, so 100 is a very safe margin) 
    #   - and let the UAF invoke at most 10 "continuation calls"
    #     (we expect that all data can be returned by a single call, so a maximum of 10 
    #      additional calls is again a very safe margin)
    result = myClient.historyReadRaw(addresses          = [doubleAddress],
                                     startTime          = DateTime(time.time() - 1.5),
                                     endTime            = DateTime(time.time()),
                                     numValuesPerNode   = 100, 
                                     maxAutoReadMore    = 10)
    
    # print the result:
    print(str(result))
    
    # do some processing on the result
    if result.targets[0].status.isNotGood():
        print("Could not read historical data from the double: %s" %str(result.targets[0].status))
    else:
        if len(result.targets[0].dataValues) == 0:
            # Strange, we didn't receive any historical data.
            # Check if this is expected behavior:
            if result.targets[0].opcUaStatusCode == opcuastatuscodes.OpcUa_GoodNoData:
                print("OK, no data could be received because he server reports that there is "
                      "no data that matches your request")
        else:
            allStatusCodes  = []
            allDoubleValues = []
            allSourceTimes  = []
            
            for dataValue in result.targets[0].dataValues:
                # check the data type
                if type(dataValue.data) == primitives.Double:
                    
                    # NOTE that dataValue is just a POINTER!
                    # So if you want to copy any objects 
                    #  (like we're copying Status or DateTime objects to a Python list in this case),
                    # then you must use the copy constructors 
                    #  (like Status(...) and DateTime(...) in this case),
                    # to actually copy the data!
                    # If you don't do this, then you will just create pointers to the underlying data.
                    # And when Python garbage collects this data, your pointers will point to
                    # invalid memory, and your software will crash when you try to use them!
                    
                    allStatusCodes.append( dataValue.opcUaStatusCode )           # notice the copy constructor!
                    allDoubleValues.append( dataValue.data.value )               # an int, no copy constructor needed
                    allSourceTimes.append( DateTime(dataValue.sourceTimestamp) ) # notice the copy constructor!
            
            # now print the lists 
            print("")
            print("The results are:")
            for i in xrange(len(allDoubleValues)):
                
                timeFloat  = allSourceTimes[i].ctime()
                timeTuple  = time.localtime( long(timeFloat) )
                timeString = time.strftime("%H:%M:%S", timeTuple)   \
                             + "."                                  \
                             + ("%.3d" %allSourceTimes[i].msec())
                
                print("Code=%d - Value=%s - Time=%s" %(allStatusCodes[i], allDoubleValues[i], timeString))
    
    print("")
    print("Second example:")
    print("--------------")
    # Now do the same, but this time get the modifications of the data of the last second.
    # Since we didn't do any modifications, none will be reported!
    result = myClient.historyReadModified(addresses          = [doubleAddress],
                                          startTime          = DateTime(time.time() - 1),
                                          endTime            = DateTime(time.time()),
                                          numValuesPerNode   = 100, 
                                          maxAutoReadMore    = 10)
    
    # print the result:
    print(str(result))
    
    
    print("")
    print("Third example:")
    print("--------------")
    
    # Now suppose we want to use the continuation points manually, 
    # instead of letting the UAF handle them.
    # We therefore put 
    #   - numValuesPerNode to a very low value, so that not all results will be retrieved at once
    #   - maxAutoReadMore to 0, so that the UAF may not invoke "continuation request" automatically
    
    startTime = DateTime(time.time() - 1)
    endTime   = DateTime(time.time())
    
    result = myClient.historyReadRaw(addresses          = [doubleAddress],
                                     startTime          = startTime,
                                     endTime            = endTime,
                                     numValuesPerNode   = 3, 
                                     maxAutoReadMore    = 0)
    
    # append all DataValues to the following list:
    allDataValues = []
    
    for dataValue in result.targets[0].dataValues:
        allDataValues.append( DataValue(dataValue) ) # notice the copy constructor!!!
    
    # as long as we get continuation points, we must continue to read the data
    while len(result.targets[0].continuationPoint) > 0:
        
        result = myClient.historyReadRaw(addresses          = [doubleAddress],
                                         startTime          = startTime,
                                         endTime            = endTime,
                                         numValuesPerNode   = 3, 
                                         maxAutoReadMore    = 0,
                                         continuationPoints = [result.targets[0].continuationPoint])
        
        for dataValue in result.targets[0].dataValues:
            allDataValues.append( DataValue(dataValue) ) # notice the copy constructor!!!
    
    print("The following values were received:")
    for dataValue in allDataValues:
        print(" - %s" %dataValue.toCompactString())
    
    
    
    print("")
    print("Second option: use the generic method processRequest()")
    print("======================================================")
    
    print("")
    print("Fourth example:")
    print("---------------")
    
    # create a request with 1 target
    request = HistoryReadRawModifiedRequest(1)
    request.targets[0].address = doubleAddress
    
    # configure the request further:
    serviceSettings = HistoryReadRawModifiedSettings()
    serviceSettings.startTime         = DateTime(time.time() - 1)
    serviceSettings.endTime           = DateTime(time.time())
    serviceSettings.callTimeoutSec    = 2.0                        # optional of course
    serviceSettings.numValuesPerNode  = 100
    serviceSettings.maxAutoReadMore   = 10
    serviceSettings.isReadModified    = False                      # we want raw historical data
    request.serviceSettings = serviceSettings
    request.serviceSettingsGiven = True
    
    sessionSettings = SessionSettings()
    sessionSettings.sessionTimeoutSec = 600.0                      # optional of course
    request.sessionSettings = sessionSettings
    request.sessionSettingsGiven = True
    
    # process the request
    result = myClient.processRequest(request)
    
    # print the result
    print(str(result))
    
    # process the result in the same way as before
    
except UafError, e:
    print("Some error occurred: %s" %e)
    raise

# don't worry about closing sessions etc., the UAF client will cleanup everything
# when it's garbage collected


How to read some data?

# examples/pyuaf/client/how_to_read_some_data.py
"""
EXAMPLE: how to read some data
====================================================================================================

A UAF Client can read data in two ways:
  - either using the "convenience" method: read()
  - or using the "generic" method: processRequest() 

The "convenience" methods (such as read(), beginRead(), write(), ...) are conveniently to use 
 (since they accept the most frequently used parameters directly), 
but they are less powerful than the "generic" processRequest() method 
 (since this method accepts ReadRequests, AsyncReadRequests, etc. that can be fully configured).

In this example, we will use several ways to read data:
 - first option  : "convenience" synchronous  function read()
 - second option : "generic"     synchronous  function processRequest()
 - third option  : "convenience" asynchronous function beginRead()       , with external callback
 - fourth option : "generic"     asynchronous function processRequest()  , with external callback
 - fifth option  : "convenience" asynchronous function beginRead()       , with overridden callback
 - sixth option  : "generic"     asynchronous function processRequest()  , with overridden callback

To run the example, start the UaDemoServer of UnifiedAutomation first on the same machine. 
"""

import time, os

import pyuaf
from pyuaf.client           import Client
from pyuaf.client.settings  import ClientSettings, ReadSettings, SessionSettings
from pyuaf.client.requests  import ReadRequest, AsyncReadRequest, ReadRequestTarget 
from pyuaf.util             import Address, NodeId, RelativePathElement, QualifiedName, LocalizedText
from pyuaf.util             import primitives
from pyuaf.util.errors      import UafError


# define the namespace URI and server URI of the UaDemoServer
demoNsUri     = "http://www.unifiedautomation.com/DemoServer"
demoServerUri = "urn:UnifiedAutomation:UaServerCpp"

# define some addresses of nodes of which we will read the Value attribute
# (you could also define addresses as Relative Paths to other addresses, 
#  see the example that shows you how to define addresses)
someDoubleNode        = Address( NodeId("Demo.Static.Scalar.Double"              , demoNsUri), demoServerUri )
someUInt32Node        = Address( NodeId("Demo.Static.Scalar.UInt32"              , demoNsUri), demoServerUri )
someStringNode        = Address( NodeId("Demo.Static.Scalar.String"              , demoNsUri), demoServerUri )
someLocalizedTextNode = Address( NodeId("Demo.Static.Scalar.LocalizedText"       , demoNsUri), demoServerUri )
someSByteArrayNode    = Address( NodeId("Demo.Static.Arrays.SByte"               , demoNsUri), demoServerUri )

# define a function to print the readResult (which is of type pyuaf.client.results.ReadResult)
def printResult(readResult):
    # overall result
    print("The overall status is: %s" %str(readResult.overallStatus))
    
    # target 0:
    status = readResult.targets[0].status                         # 'status' has type pyuaf.util.Status
    data   = readResult.targets[0].data                           # 'data' has type pyuaf.util.primitives.Double
    if status.isGood() and isinstance(data, primitives.Double):
        print("The double is: %.3f" %data.value)
    
    # target 1:
    status = readResult.targets[1].status                         # 'status' has type pyuaf.util.Status
    data   = readResult.targets[1].data                           # 'data' has type pyuaf.util.primitives.UInt32
    if status.isGood() and isinstance(data, primitives.UInt32):
        print("The uint32 is: %d" %data.value)
    
    # target 2:
    status = readResult.targets[2].status                         # 'status' has type pyuaf.util.Status
    data   = readResult.targets[2].data                           # 'data' has type pyuaf.util.primitives.String
    if status.isGood() and isinstance(data, primitives.String):
        print("The string is: '%s'" %data.value)
    
    # target 3:
    status = readResult.targets[3].status                         # 'status' has type pyuaf.util.Status
    data   = readResult.targets[3].data                           # 'data' has type pyuaf.util.LocalizedText
    if status.isGood() and isinstance(data, LocalizedText):
        print("The locale is: '%s', the text is: '%s'" %(data.locale(), data.text()))
    
    # target 4:
    status = readResult.targets[4].status                         # 'status' has type pyuaf.util.Status
    data   = readResult.targets[4].data                           # 'data' is a list of pyuaf.util.primitives.SByte
    if status.isGood() and isinstance(data, list):
        print("The array is:")
        for i in xrange(len(data)):
            print(" - array[%d] = %d" %(i, data[i].value))


# define the ClientSettings:
settings = ClientSettings()
settings.applicationName = "MyClient"
settings.discoveryUrls.append("opc.tcp://localhost:48010")

# create the client
myClient = Client(settings)



# read the node attributes all at once
print("")
print("First option: use the convenience function read() to read data synchronously")
print("============================================================================")
    
# OPTIONAL: You could also provide a ReadSettings to configure a call timeout, 
#           or maximum age of the values, or ... 
serviceSettings = ReadSettings()
serviceSettings.callTimeoutSec = 0.5
serviceSettings.maxAgeSec = 1.0

# OPTIONAL: And you could also provide a SessionConfig to configure the sessions 
#           (e.g. set the timeout to 600.0 seconds)
#           For more info about SessionConfigs, see the sessionconfig_example
sessionSettings = SessionSettings()
sessionSettings.sessionTimeoutSec = 600.0

try:
    # now read the node attributes
    readResult = myClient.read( addresses     = [someDoubleNode, someUInt32Node, someStringNode, 
                                                 someLocalizedTextNode, someSByteArrayNode],
                                serviceSettings = serviceSettings, # optional argument
                                sessionSettings = sessionSettings) # optional argument
    
    # print the result using the function we defined before
    printResult(readResult)
except UafError, e:
    print("Some error occurred: %s" %e)



print("")
print("Second option: use the generic function processRequest() to read data synchronously")
print("===================================================================================")

# create a request with 5 targets
readRequest = ReadRequest(5)
readRequest.targets[0].address = someDoubleNode
readRequest.targets[1].address = someUInt32Node
readRequest.targets[2].address = someStringNode
readRequest.targets[3].address = someLocalizedTextNode
readRequest.targets[4].address = someSByteArrayNode

# we could also add a 6th node, of which we want to read the DisplayName (= of type LocalizedText)
readRequest.targets.append(ReadRequestTarget()) # or readRequest.targets.resize(6)
readRequest.targets[5].address     = someDoubleNode
readRequest.targets[5].attributeId = pyuaf.util.attributeids.DisplayName
readRequest.serviceSettings        = serviceSettings # optional
readRequest.sessionSettings        = sessionSettings # optional

try:    
    # process the request
    readResult = myClient.processRequest(readRequest)
    
    # print the result
    printResult(readResult)
    
    # also print the display name of the 6th node:
    status = readResult.targets[5].status
    data   = readResult.targets[5].data
    if status.isGood() and isinstance(data, LocalizedText):
        print("The DisplayName-locale is: '%s', the DisplayName-text is: '%s'" %(data.locale(), data.text()))
except UafError, e:
    print("Some error occurred: %s" %e)
    
    

print("")
print("Third option: use the convenience function beginRead() to read data asynchronously")
print("==================================================================================")

# notice we enable asynchronous communication now by providing the printResult() method as 
# a callback function!

try:
    asyncReadResult = myClient.beginRead(addresses       = [someDoubleNode, someUInt32Node, 
                                                            someStringNode, someLocalizedTextNode, 
                                                            someSByteArrayNode],
                                         serviceSettings = serviceSettings, # optional argument
                                         sessionSettings = sessionSettings, # optional argument
                                         callback        = printResult)     # callback function!
    
    # check if there was an "immediate" error on the client side when executing the asynchronous 
    # service call:
    if asyncReadResult.overallStatus.isGood():
        print("OK, the client could invoke the asynchronous request without problems.")
        print("Let's wait a second for the result ...")
        
        time.sleep(1)
        
        print("The read result should have been received by now!")

except UafError, e:
    print("Some error occurred: %s" %e)


print("")
print("Fourth option: use the generic function processRequest() to read data asynchronously")
print("====================================================================================")

# notice we enable asynchronous communication now by providing the printResult() method as 
# a callback function!

# create an asynchronous (!) request with 5 targets
asyncReadRequest = AsyncReadRequest(5)

# we can simply copy the targets of the synchronous request to the asynchronous request:
for i in xrange(5):
    asyncReadRequest.targets[i] = readRequest.targets[i]
    
# OPTIONAL: also copy the service config and session config:
asyncReadRequest.serviceSettings = serviceSettings
asyncReadRequest.sessionSettings = sessionSettings

try:
    # process the request
    asyncReadResult = myClient.processRequest(asyncReadRequest, resultCallback=printResult)
    
    # check if there was an "immediate" error on the client side when executing the asynchronous 
    # service call:
    if asyncReadResult.overallStatus.isGood():
        print("OK, the client could invoke the asynchronous request without problems.")
        print("Let's wait a second for the result ...")
        
        time.sleep(1)
        
        print("The read result should have been received by now!")

    
except UafError, e:
    print("Some error occurred: %s" %e)



print("")
print("Fifth option: read data asynchronously using beginRead() without specifying external callbacks")
print("==============================================================================================")

# instead of using "external" callback functions (such as printResult() above), we can also use
# the "internal" callback function of the Client class. If the argument 'callback' of beginRead() is 
# not used (or 'resultCallback' is not used in case of processRequest()), the asynchronous result
# will be dispatched to the readComplete() method of the Client class. 
# In other words, when you subclass Client, you can simply override the readComplete() method
# to receive the asynchronous read results.

class MySubClasssedClient(Client):
    
    def __init__(self, settings):
        Client.__init__(self, settings)

    def readComplete(self, result): # overridden method!
        print("This is the overridden method readComplete() speaking:")
        printResult(result)

# create the client
mySubClassedClient = MySubClasssedClient(settings)
    
try:
    asyncReadResult = mySubClassedClient.beginRead(
                                         addresses       = [someDoubleNode, someUInt32Node, 
                                                            someStringNode, someLocalizedTextNode, 
                                                            someSByteArrayNode],
                                         serviceSettings = serviceSettings, # optional argument
                                         sessionSettings = sessionSettings) # optional argument!
    # note: the 'callback' argument is not used in the above call!
    
    # check if there was an "immediate" error on the client side when executing the asynchronous 
    # service call:
    if asyncReadResult.overallStatus.isGood():
        print("OK, the client could invoke the asynchronous request without problems.")
        print("Let's wait a second for the result ...")
        
        time.sleep(1)
        
        print("The read result should have been received by now!")
    
except UafError, e:
    print("Some error occurred: %s" %e)
    


print("")
print("Sixth option: read data asynchronously using processRequest() without specifying external callbacks")
print("===================================================================================================")

try:
    # process the request
    asyncReadResult = mySubClassedClient.processRequest(asyncReadRequest) 
    # note: the 'resultCallback' argument is not used in the above call!
    
    # check if there was an "immediate" error on the client side when executing the asynchronous 
    # service call:
    if asyncReadResult.overallStatus.isGood():
        print("OK, the client could invoke the asynchronous request without problems.")
        print("Let's wait a second for the result ...")
        
        time.sleep(1)
        
        print("The read result should have been received by now!")

except UafError, e:
    print("Some error occurred: %s" %e)
    

How to read and write structures?

# examples/pyuaf/client/how_to_read_and_write_structures.py
"""
EXAMPLE: how to read and write structures
====================================================================================================

To run the example, start the UaServerCPP of UnifiedAutomation first on the same machine. 
($SDK/bin/uaservercpp, this executable is part of the SDK).
"""

import time, os, sys

import pyuaf
from pyuaf.client           import Client
from pyuaf.client.settings  import ClientSettings, SessionSettings
from pyuaf.util             import Address, NodeId, ExtensionObject, SdkStatus
from pyuaf.util             import GenericStructureValue, GenericStructureVector, GenericUnionValue
from pyuaf.util             import primitives
from pyuaf.util             import opcuaidentifiers, attributeids, opcuatypes, structurefielddatatypes
from pyuaf.util.errors      import UafError


# define the namespace URI and server URI of the UaServerCPP demo server
demoServerUri = "urn:UnifiedAutomation:UaServerCpp"
demoNamespaceUri = "http://www.unifiedautomation.com/DemoServer/"

# define the ClientSettings:
settings = ClientSettings()
settings.applicationName = "MyClient"
settings.discoveryUrls.append("opc.tcp://localhost:48010")

# create the client
myClient = Client(settings)



print("#**************************************************************************************************#")
print("#                                                                                                  #")
print("# 1) To understand a GenericStructureValue instance, let's define the function below to print one: #")
print("#                                                                                                  #")
print("#**************************************************************************************************#")

def printStructure(structure, indentation=""):
    """
    Print a structure.
    
    Parameters:
      structure:   a pyuaf.util.GenericStructureValue instance. May represent a structure
                   or union or optionset.
      indentation: a string to print at the beginning of each line (to add indentation when
                   this function is called recursively).
    """
    # get the definition of the structure:
    definition = structure.definition()
    
    # we'll print the contents of the structure
    print(indentation + "Structure contents:")
    
    # loop through the fields of the structure, by looking at the definition:
    for i in xrange(definition.childrenCount()):
        
        # retrieve information both from the definition and from the structure itself:
        child = definition.child(i)
        childName  = child.name()           # a string
        childType  = child.valueType()      # datatype, e.g. Double
        fieldType, opcUaStatusCode  = structure.valueType(i) # e.g. Variant, GenericStructure, GenericStructureArray...
        
        if not SdkStatus(opcUaStatusCode).isGood():
            raise Exception("Oops, we could not get the valueType due to: %s" %SdkStatus(opcUaStatusCode))
        
        print(indentation + "  * child number %d:"         %i)
        print(indentation + "     - child name = %s"       %childName)
        print(indentation + "     - child type = %d (%s)"  %(childType, opcuatypes.toString(childType)))
        print(indentation + "     - field type = %s (%s)"  %(fieldType, structurefielddatatypes.toString(fieldType)))
        
        if fieldType == structurefielddatatypes.Variant:
            value, opcUaStatusCode = structure.value(i)
            print(indentation + "     - value      = %s" %value)
        elif fieldType == structurefielddatatypes.GenericStructure:
            print(indentation + "     - value:")
            # recursive call
            printStructure(structureValue.genericStructureValue(i), indentation + "   ")
        elif fieldType == structurefielddatatypes.GenericStructureArray:
            array, opcUaStatusCode = structureValue.genericStructureArray(i)
            
            if not SdkStatus(opcUaStatusCode).isGood():
                raise Exception("Oops, we could not get the structure due to: %s" %SdkStatus(opcUaStatusCode))
            
            print(indentation + "     - value:")
            # recursive calls to all array items:
            for j in xrange(len(array)):
                print(indentation + "        array[%d]:" %j)
                printStructure(array[j], indentation + "          ")
        
    print("") # empty line



print("#******************************************************************************************#")
print("#                                                                                          #")
print("# 2) How to read and write a scalar structure (i.e. a single structure, not an array)      #")
print("#                                                                                          #")
print("#******************************************************************************************#")

# define the address of the Structure which we would like to read
address = Address( NodeId("Demo.Static.Scalar.Vector", demoNamespaceUri), demoServerUri )

# try to read a structure
result = myClient.read( [address] )

# we're expecting an ExtensionObject. Let's check this:
if isinstance(result.targets[0].data, ExtensionObject):
    print("We received an ExtensionObject")
    print("")
else:
    raise Exception("Oops, we were expecting an ExtensionObject, but we received a: %s" %result.targets[0].data)

extensionObject = result.targets[0].data

# now let's find out the datatype of the ExtensionObject
result = myClient.read( [address] , attributeId = attributeids.DataType)

dataTypeId = result.targets[0].data # data represents a NodeId

print("The datatype of the ExtensionObject is: NodeId: %s" %dataTypeId)
print("")

# using the datatypeId, get the definition of the structure
definition = myClient.structureDefinition(dataTypeId)

print("The definition of the structure:")
print(str(definition))
print("")

# using the original ExtensionObject the StructureDefinition, we can now create the GenericStructureValue:
structureValue = GenericStructureValue(extensionObject, definition)

# print the structure:
print("So we can now print the full structure:")
printStructure(structureValue)

# now change the value of the first child (i = 0):
structureValue.setField(0, primitives.Double(0.1))

# we can also change a field via its name:
structureValue.setField("Y", primitives.Double(0.2))

# write back the structure
newExtensionObject = ExtensionObject()
structureValue.toExtensionObject(newExtensionObject)

print("Now writing {structure}.X = 0.1 and {structure}.Y = 0.2")
print("")
try:
    result = myClient.write( [address], [newExtensionObject] )
    if result.overallStatus.isGood():
        print("OK, the new structure value has been written successfully")
    else:
        print("Oops, some OPC UA problem occurred. Here's the result:\n%s" %result)
except UafError, e:
    print("Oops, some error occurred on the client side. Here's the error message: %s" %e)

print("")
print("Let's read the same structure again, to verify that the values have changed:")
result          = myClient.read( [address] )
extensionObject = result.targets[0].data
structureValue  = GenericStructureValue(extensionObject, definition)
printStructure(structureValue)



print("#******************************************************************************************#")
print("#                                                                                          #")
print("# 3) How to read and write an array of structures                                          #")
print("#                                                                                          #")
print("#******************************************************************************************#")

# let's now read an array of structures
arrayAddress = Address( NodeId("Demo.Static.Arrays.Vector", demoNamespaceUri), demoServerUri )

# try to read a structure
result = myClient.read( [arrayAddress] )

# we're expecting a list of ExtensionObjects. Let's check this:
if isinstance(result.targets[0].data, list) and isinstance(result.targets[0].data[0], ExtensionObject):
    print("We received a list of ExtensionObjects")
    print("")
else:
    raise Exception("Oops, we were expecting a list of ExtensionObjects, but we received a: %s" %result.targets[0].data)

extensionObjectList = result.targets[0].data

# get the datatype
result = myClient.read( [arrayAddress] , attributeId = attributeids.DataType)

arrayDataTypeId = result.targets[0].data # data represents a NodeId


print("The datatype of the ExtensionObjects of this array is: NodeId: %s" %dataTypeId)
print("")

# using the datatypeId, get the definition of the structure
definition = myClient.structureDefinition(dataTypeId)

print("The definition of the structures is:")
print(str(definition))
print("")

print("Now let's loop through the list:")

for i in xrange(len(extensionObjectList)):
    
    extensionObject = extensionObjectList[i]
    
    # using the original ExtensionObject the StructureDefinition, we can now create the GenericStructureValue:
    structureValue = GenericStructureValue(extensionObject, definition)
    
    # print the structure in one line:
    print("item[%d]: " %i)
    printStructure(structureValue)
    
print("")

# now change the values of the first child of the first array item:
firstItem = GenericStructureValue(extensionObjectList[0], definition)
firstItem.setField(0, primitives.Double(0.1))
firstItem.setField("Y", primitives.Double(0.2))

# write back the structure
firstItem.toExtensionObject(extensionObjectList[0])
 
print("Now writing the modified structure array")
print("")
try:
    result = myClient.write( [arrayAddress], [extensionObjectList] )
    if result.overallStatus.isGood():
        print("OK, the new structure array has been written successfully")
        print("")
    else:
        print("Oops, some OPC UA problem occurred. Here's the result:\n%s" %result)
except UafError, e:
    print("Oops, some error occurred on the client side. Here's the error message: %s" %e)



print("#*************************************************************************************************#")
print("#                                                                                                 #")
print("# 4) How to read and write a structure which contains an array of structures as one of its fields #")
print("#                                                                                                 #")
print("#*************************************************************************************************#")

# define the address of the Structure which has an array of structure as one of its fields 
address = Address( NodeId("Demo.Static.Scalar.WorkOrder", demoNamespaceUri), demoServerUri )

# read the structure
result = myClient.read( [address] )

# make sure it's an ExtensionObject
assert type(result.targets[0].data) == ExtensionObject
extensionObject = result.targets[0].data

# read the datatype
result = myClient.read( [address] , attributeId = attributeids.DataType)
dataTypeId = result.targets[0].data # data represents a NodeId

# get the definition of the structure
definition = myClient.structureDefinition(dataTypeId)

# using the original ExtensionObject the StructureDefinition, we can now create the GenericStructureValue:
structureValue = GenericStructureValue(extensionObject, definition)

print("")
print("Let's read a structure which has an array of WorkOrder structures as child number 3:")
printStructure(structureValue)

print("")
print("Let's change one of the array items")
array, opcUaStatusCode = structureValue.genericStructureArray(3) # child number 3 of the main structure

if not SdkStatus(opcUaStatusCode).isGood():
    raise Exception("Oops, we could not get the structure array due to: %s" %SdkStatus(opcUaStatusCode))

status = array[1].setField(2, pyuaf.util.LocalizedText("en", "THIS FIELD WAS CHANGED!!!"))
assert status.isGood()

status = structureValue.setField(3, array)
assert status.isGood()

structureValue.toExtensionObject(extensionObject)

print("Now writing the modified structure")
print("")
try:
    result = myClient.write( [address], [extensionObject] )
    if result.overallStatus.isGood():
        print("OK, the new structure array has been written successfully")
        print("")
    else:
        print("Oops, some OPC UA problem occurred. Here's the result:\n%s" %result)
except UafError, e:
    print("Oops, some error occurred on the client side. Here's the error message: %s" %e)



print("#*************************************************************************************************#")
print("#                                                                                                 #")
print("# 4) How to read and write a union                                                                #")
print("#                                                                                                 #")
print("#*************************************************************************************************#")

def printUnion(union, indentation=""):
    """
    Print a structure.
    
    Parameters:
      union:       a pyuaf.util.GenericUnionValue instance. 
      indentation: a string to print at the beginning of each line (to add indentation when
                   this function is called recursively).
    """
    # get the definition of the structure:
    definition = union.definition()
    
    # we'll print the contents of the structure
    print(indentation + "Union contents:")
    
    if not definition.isUnion():
        raise Exception("The given union is no union!")
    
    # let's show the possible values by looking at the definition:
    for i in xrange(definition.childrenCount()):
        
        # retrieve information both from the definition and from the structure itself:
        child = definition.child(i)
        childName   = child.name()           # a string
        childType   = child.valueType()      # datatype, e.g. Double
        
        print(indentation + "  * child number %d:"         %i)
        print(indentation + "     - child name = %s"       %childName)
        print(indentation + "     - child type = %d (%s)"  %(childType, opcuatypes.toString(childType)))
        
    # show the active value (the "switchValue")    
    switchValue = union.switchValue()
    field       = union.field()
    
    print(indentation + "Switch value: %d" %switchValue)
    print(indentation + "Field name: %s" %union.field().name())
    print(indentation + "Value: %s" %union.value())
    print("") # empty line


# define the address of the Union
address = Address( NodeId("Demo.Static.Scalar.Structures.AnalogMeasurement", demoNamespaceUri), demoServerUri )

# try to read a structure
result = myClient.read( [address] )

assert type(result.targets[0].data) == ExtensionObject
extensionObject = result.targets[0].data

# now let's find out the datatype of the ExtensionObject
result = myClient.read( [address] , attributeId = attributeids.DataType)
dataTypeId = result.targets[0].data # data represents a NodeId

# using the datatypeId, get the definition of the structure
definition = myClient.structureDefinition(dataTypeId)

print("The definition of the union:")
print(str(definition))
print("")

# we can now create the union:
union = GenericUnionValue(extensionObject, definition)

# print the union:
print("So we can now print the full union:")
printUnion(union)

# now change the active value
union.setValue(union.field().name(), primitives.Float(111.222))

# write back the union
newExtensionObject = ExtensionObject()
union.toExtensionObject(newExtensionObject)

print("Now writing union['%s'] = 111.222333" %union.field().name())
print("")
try:
    result = myClient.write( [address], [newExtensionObject] )
    if result.overallStatus.isGood():
        print("OK, the new structure value has been written successfully")
    else:
        print("Oops, some OPC UA problem occurred. Here's the result:\n%s" %result)
except UafError, e:
    print("Oops, some error occurred on the client side. Here's the error message: %s" %e)

print("")
print("Let's read the same union again, to verify that the active value have changed:")
result          = myClient.read( [address] )
extensionObject = result.targets[0].data
union           = GenericUnionValue(extensionObject, definition)
printUnion(union)

How to configure all settings?

# examples/pyuaf/client/how_to_configure_all_settings.py
"""
EXAMPLE: how to configure the client/session/subscription/service settings
=================================================================================================

With PyUAF you can configure sessions, subscriptions and services (like read, write, ...) in
great detail. This example shows how to change them. 

This is an example to show you some more advanced feature of the UAF, you can skip it if you're 
happy with the default session/subscription/service settings of the UAF!
"""

import pyuaf
from pyuaf.util             import Address, NodeId
from pyuaf.util.primitives  import UInt32, Boolean
from pyuaf.client           import Client
from pyuaf.client.settings  import ClientSettings, SessionSettings, SubscriptionSettings, \
                                   ReadSettings


# Some constants needed during the rest of this example:
NAMESPACE_URI = "http://www.unifiedautomation.com/DemoServer"
SERVER_URI = "urn:UnifiedAutomation:UaServerCpp"

# ==================================================================================================
# 1) Setting the defaults
# ==================================================================================================

# using ClientSettings, you can configure all default session/subscription and service settings:
clientSettings = ClientSettings()

# first configure some general client settings:
clientSettings.applicationName = "MyClient";
clientSettings.discoveryUrls.append("opc.tcp://localhost:48010")
# etc. ...

# now configure the default session settings:
clientSettings.defaultSessionSettings.sessionTimeoutSec = 100.0
clientSettings.defaultSessionSettings.connectTimeoutSec = 2.0
# etc. ...

# now configure the default subscription settings:
clientSettings.defaultSubscriptionSettings.publishingIntervalSec = 0.5
clientSettings.defaultSubscriptionSettings.lifeTimeCount         = 600
# etc. ...

# now configure the default settings for the Read service:
clientSettings.defaultReadSettings.callTimeoutSec = 2.0
clientSettings.defaultReadSettings.maxAgeSec      = 10.0

# same for the writeSettings, methodCallSettings, ...
clientSettings.defaultMethodCallSettings.callTimeoutSec = 2.0
clientSettings.defaultWriteSettings.callTimeoutSec = 2.0
clientSettings.defaultBrowseSettings.callTimeoutSec = 2.0
# and so on, and so on ...

# NOTE: for clientSettings, you may also configure SessionSettings for specific servers:
clientSettings.specificSessionSettings["My/Unreliable/Device/Server/URI"] = SessionSettings()
clientSettings.specificSessionSettings["My/Unreliable/Device/Server/URI"].sessionTimeoutSec = 200.0
clientSettings.specificSessionSettings["My/Unreliable/Device/Server/URI"].connectTimeoutSec = 5.0
# If the client connects to the server with ServerUri = "My/Unreliable/Device/Server/URI",
# then it will use these settings instead of the defaultSessionSettings!

# Now you can simply create a client that uses these settings:
myClient = Client(clientSettings)
# or alternatively:
# myClient = Client()
# myClient.setClientSettings(clientSettings)

# let's read 2 nodes:
address0 = Address(NodeId("Demo.SimulationSpeed" , NAMESPACE_URI), SERVER_URI)
address1 = Address(NodeId("Demo.SimulationActive", NAMESPACE_URI), SERVER_URI)

# the next line will result in 
#  - a session being created, with the defaultSessionSettings which we configured before
#  - the Read service being invoked, with the defaultReadSettings we configured before
myClient.read( [address0, address1] ) 

# when the next line is executed, then:
#  - the session we created before will be reused (as we're requesting a session with the
#    defaultSessionSettings, but such a session exists already, so it can be reused!)
#  - the Write service will be invoked, with the defaultWriteSettings we configured before
myClient.write( [address0, address1] , [ UInt32(60), Boolean(True) ] )


# ==================================================================================================
# 2) Overriding the defaults
# ==================================================================================================

# If needed, you can also configure the settings for each service call, using the **kwargs arguments
# of read/write/call/...

# e.g. set some special session settings for a specific read() call:
mySessionSettings = SessionSettings()
mySessionSettings.connectTimeoutSec = 0.5
mySessionSettings.sessionTimeoutSec = 100.0
# simply mention 'sessionSettings = ...' to use these settings as a **kwarg:
myClient.read( [address0, address1], sessionSettings = mySessionSettings ) 

# e.g. set some special service settings for a specific read() call:
myReadSettings = ReadSettings()
myReadSettings.callTimeoutSec = 0.5
myReadSettings.maxAgeSec      = 1.0
# simply mention 'serviceSettings = ...' to use these settings as a **kwarg:
myClient.read( [address0, address1], serviceSettings = myReadSettings ) 
# or combine them:
myClient.read( [address0, address1], serviceSettings = myReadSettings, sessionSettings = mySessionSettings ) 

# e.g. set some special subscription settings for a specific createMonitoredData() call:
mySubscriptionSettings = SubscriptionSettings()
mySubscriptionSettings.publishingIntervalSec = 2.0
mySubscriptionSettings.priority = 20
myClient.createMonitoredData( [address0, address1], subscriptionSettings = mySubscriptionSettings )

# Both the SessionSettings and SubscriptionSettings have a special 'unique' attribute, which 
# can be True or False.
# By default it is False, which tells the PyUAF Client that it may re-use a session (or subscription)
# if there exists one with the same SessionSettings (or SubscriptionSettings).
# However, if you set the unique attribute to True, then the PyUAF Client will ignore any existing
# sessions (or subscriptions) and create a new unique one.
# This is useful, e.g. if your HMI (Human Machine Interface) has several tabs, then you can create
# a subscription for each tab (to hold all monitored items for that specific tab). Using the
# setPublishingMode() service, you can then temporarily enable the subscription for a visible
# tab, and temporarily disable the subscription for a hidden one. Like this: 
# tab 1:  
tab1SubscriptionSettings = SubscriptionSettings()
tab1SubscriptionSettings.unique = True
tab1SubscriptionSettings.publishingIntervalSec = 2.0
# tab 2:
tab2SubscriptionSettings = SubscriptionSettings()
tab2SubscriptionSettings.unique = True
tab2SubscriptionSettings.publishingIntervalSec = 2.0
# create the subscriptions and monitored items:
tab1Result = myClient.createMonitoredData( [address0] , subscriptionSettings = tab1SubscriptionSettings )
tab2Result = myClient.createMonitoredData( [address0] , subscriptionSettings = tab2SubscriptionSettings )
# disable the subscription for tab 2:
myClient.setPublishingMode(clientSubscriptionHandle = tab2Result.targets[0].clientSubscriptionHandle, 
                           publishingEnabled        = False)


# ==================================================================================================
# Keeping full control over the sessions and subscriptions
# ==================================================================================================

# By manually creating and configuring sessions and subscriptions, you can keep full control over 
# them. You'll have to take care of some things yourself (e.g. re-create monitored items in case
# a session or subscription dies due to a restart of the server), but you can still benefit from
# may of PyUAF's features such as automatic browsepath translation, discovery etc.

# create a Session and remember its clientConnectionId:
myClientConnectionId = myClient.manuallyConnect(serverUri       = SERVER_URI, 
                                                sessionSettings = mySessionSettings)

# use this clientConnectionId to read/write/call/...:
myClient.read( addresses          = [address0, address1], 
               clientConnectionId = myClientConnectionId )

# of course if you don't want to use the defaultReadSettings of the ClientSettings (see above)
# then you can provide the service settings as well:
myClient.read( addresses          = [address0, address1], 
               clientConnectionId = myClientConnectionId, 
               serviceSettings    = myReadSettings )

# you can also create a subscription "within" the session created before:
myClientSubscriptionHandle = myClient.manuallySubscribe(clientConnectionId   = myClientConnectionId, 
                                                        subscriptionSettings = mySubscriptionSettings)

# now you can add monitored items to this specific subscription of this specific session:
def myCallback(notification):
    print("new notification received:")
    print(notification)
    
myClient.createMonitoredData( addresses                = [address0], 
                              notificationCallbacks    = [myCallback],
                              clientConnectionId       = myClientConnectionId,
                              clientSubscriptionHandle = myClientSubscriptionHandle )

# allow some time to receive a notification:
import time
time.sleep(3)

# don't worry about disconnecting sessions etc., these things will be done automatically
# when the client is destroyed.

How to manually connect to an endpoint without discovery?

# examples/pyuaf/client/how_to_manually_connect_to_an_endpoint_without_discovery.py
"""
EXAMPLE: how to manually connect to an endpoint without discovery
====================================================================================================

A UAF client normally uses the discovery process to identify a server and connect to it.
The user therefore doesn't have to worry about connecting, disconnecting, session management, etc.

However, in certain cases you may want to connect manually to a specific endpoint, without using
the discovery process (i.e. without relying on the discovery endpoint of the server). 

This example will show you how to manually connect to the UaServerCPP demo server of Untified 
Automation, without any discovery involved. 

To run the example, start the UaServerCPP of UnifiedAutomation first on the same machine. 
($SDK/bin/uaservercpp, this executable is part of the SDK).
"""

import time, os, sys

import pyuaf
from pyuaf.client           import Client
from pyuaf.client.settings  import ClientSettings
from pyuaf.util             import Address, NodeId
from pyuaf.util.errors      import UafError, ConnectionError


# define the ClientSettings (note that we do NOT provide a discoveryUrl!!!):
settings = ClientSettings()
settings.applicationName = "MyClient"
# settings.logToStdOutLevel = pyuaf.util.loglevels.Debug # uncomment to see the full UAF logging

# create the client
myClient = Client(settings)

# Now manually connect to an endpoint of the demo server using 'manuallyConnectToEndpoint()'.
#  - The first argument (endpointUrl) is the URL of the endpoint you want to connect to 
#  - The optional (!) second argument (sessionSettings) a SessionSettings instance.
#    If you don't provide one, then a default SessionSettings instance will be used.
#    A default SessionSettings instance has:
#     - no security policy (pyuaf.util.securitypolicies.UA_None)
#     - no security mode (pyuaf.util.messagesecuritymodes.Mode_None)
#     - no authentication (pyuaf.util.usertokentypes.Anonymous)
try:
    print("")
    print("Now connecting")
    clientConnectionId = myClient.manuallyConnectToEndpoint("opc.tcp://localhost:48010")
except ConnectionError, e:
    # if an error was raised, then no Session has been created! 
    # In other words, the client is back in it's original state, there is no 
    # session that tries to reconnect in the background.
    print(e)
    print("ConnectionError -> sys.exit()")
    sys.exit()
except UafError, e:
    print(e)
    print("UafError -> sys.exit()")
    sys.exit()


# The clientConnectionId is a number that identifies the session.
# You can use it to request some information about the Session that you just created:
try:
    print("")
    print("Now getting the session information")
    
    info = myClient.sessionInformation(clientConnectionId)
    
    print("")
    print("SessionInformation:")
    print(info)
    
except UafError, e:
    print("Couldn't read the session information: %s" %e)
    print("Exiting...")
    sys.exit()


# now use the server URI to read some variables
try:
    print("")
    print("Now reading some variables")
    
    # define the namespace URI of the UaDemoServer:
    demoNsUri = "http://www.unifiedautomation.com/DemoServer"
    
    # define some addresses:
    someDoubleNode = Address( NodeId("Demo.Static.Scalar.Double", demoNsUri), info.serverUri )
    someUInt32Node = Address( NodeId("Demo.Static.Scalar.UInt32", demoNsUri), info.serverUri )
    
    # read the nodes of the addresses:
    result = myClient.read([someDoubleNode, someUInt32Node])
    
    print("")
    print("ReadResult:")
    print(result)
    
except UafError, e:
    print("Couldn't read some variables: %s" %e)
    print("Exiting...")
    sys.exit()


How to connect to a secured endpoint?

import pyuaf
from pyuaf.client           import Client
from pyuaf.client.settings  import ClientSettings, SessionSettings, SessionSecuritySettings
from pyuaf.util             import Address, NodeId
from pyuaf.util             import PkiRsaKeyPair, PkiIdentity, PkiCertificateInfo, PkiCertificate
from pyuaf.util.errors      import UafError, ConnectionError
from pyuaf.util             import securitypolicies, messagesecuritymodes
import socket
import shutil
import os
import subprocess

DISCOVERY_URL     = "opc.tcp://localhost:48010"
SERVER_URI        = "urn:UnifiedAutomation:UaServerCpp"
DEMOSERVER_NS_URI = "http://www.unifiedautomation.com/DemoServer"

# define a folder where we will store the Public Key Infrastructure files (i.e. certificates and keys)
PKI_FOLDER = "./securityexample/PKI"

description = """
To run this example, start the UaServerCpp demo server first.
If you use the evaluation SDK, you can find thCliente executable here (e.g. on Windows):
   C:/Program Files/UnifiedAutomation/UaSdkCppBundleEval/bin/uaservercpp.exe
Note that all paths below must ALWAYS be specified using '/' separators, also on Windows!
This means you cannot use e.g. os.path.join or similar, since those functions may
introduce platform-dependent path separators.

Here's how the UAF Client handles certificates:
1) first the server certificate is verified. 
   This server certificate was automatically received and kept in memory by the UAF
   client in the background during the discovery process, so you don't have to take 
   care of it yourself. (Note, that if you use ``manuallyConnectToEndpoint`` to avoid 
   discovery for some reason, you may provide the server certificate manually). 
   If the server certificate is not trusted (e.g. because it's invalid or because 
   it's not in the trust list), then the ``untrustedServerCertificateReceived`` 
   will be called. This is done **regardless** of whether or not you want to sign or
   encrypt your communication! In this step you're **only** checking if you're trying
   to connect to the right server. In principle you should always override the 
   ``untrustedServerCertificateReceived`` method for this purpose (or register
   your own callback via ``registerUntrustedServerCertificateCallback``). 
   By default pyuaf will simply assume that you trust the server to which you're
   trying to connect (i.e. ``untrustedServerCertificateReceived`` returns 
   ``Action_AcceptTemporarily`` unless you override it). 
2) then both the client and server certificates are sent around again:
   - so that the server can decide to accept or reject the session
   - and so that the actual communication data can be encrypted and/or signed, 
     if needed.
   All of this is handled by the UAF and SDK behind the scenes, so you don't have to 
   take care of it yourself. Basically you only have to configure the client
   settings and session settings. 
   
"""
print(description)

print("")
print("===========================================================================================")
print(" STEP 0: Cleanup and files and directories in the securityexample/ folder, if you want")
print("===========================================================================================")
print("")

if os.path.exists(PKI_FOLDER):
    print("Remove the %s folder and all of its contents?" %PKI_FOLDER)
    answer = raw_input("Enter your choice (y/n) [n] : ")
    
    if answer.upper() in ["Y", "YES"]:
        shutil.rmtree(PKI_FOLDER)


print("")
print("===========================================================================================")
print("STEP 1: Create a client Certificate")
print("===========================================================================================")
print("")

# we will create a self-signed certificate, which means that the subject and issuer are the same!

keyPair = PkiRsaKeyPair(1024)
issuerPrivateKey = keyPair.privateKey()
subjectPublicKey = keyPair.publicKey()

identity = PkiIdentity()
identity.commonName         = "Wim Pessemier"
identity.organization       = "KU Leuven"
identity.organizationUnit   = "Institute of Astronomy"
identity.locality           = "Leuven"
identity.country            = "BE"

info = PkiCertificateInfo()
info.uri        = "urn:%s:InstituteOfAstronomy::MyExampleCode" %socket.gethostname() # must be unique!
info.dns        = socket.gethostname()
info.eMail      = "Wxx.Pxxxxxxxx@ster.kuleuven.be"
info.validTime  = 60*60*24*365*5 # 5 years

certificate = PkiCertificate(info, identity, subjectPublicKey,  identity, issuerPrivateKey)

# note: we will store the certificate and private key in STEP 3

print("")
print("===========================================================================================")
print("STEP 2: Create a Client instance")
print("===========================================================================================")
print("")


# define the clientSettings
clientSettings = ClientSettings()
clientSettings.applicationName  = "MyClient"
clientSettings.applicationUri   = info.uri # Certificate info URI and application URI must be the same !!!
#clientSettings.logToStdOutLevel = pyuaf.util.loglevels.Debug # uncomment if needed
clientSettings.discoveryUrls.append(DISCOVERY_URL)

# We configure the PKI folder structure (set the PKI_FOLDER to 'PKI' and you get the defaults).
# Note that paths must ALWAYS be specified using '/', also on Windows! 
# You cannot use os.path.join or similar, since these will introduce platform-dependent separators!
clientSettings.clientCertificate                 = PKI_FOLDER + '/client/certs/client.der'
clientSettings.clientPrivateKey                  = PKI_FOLDER + '/client/private/client.pem'
clientSettings.certificateTrustListLocation      = PKI_FOLDER + '/trusted/certs/'
clientSettings.certificateRevocationListLocation = PKI_FOLDER + '/trusted/crl/'
clientSettings.issuersCertificatesLocation       = PKI_FOLDER + '/issuers/certs/'
clientSettings.issuersRevocationListLocation     = PKI_FOLDER + '/issuers/crl/'

# make sure the above directories are created
clientSettings.createSecurityLocations()

# create the client
myClient = Client(clientSettings)

print("")
print("===========================================================================================")
print("STEP 3: Store the client certificate and private key")
print("===========================================================================================")
print("")

# store the certificate 
result = certificate.toDERFile(clientSettings.clientCertificate)
if result == -1:
    raise Exception("Could not save the client certificate!")

# store the private key:
result = keyPair.toPEMFile(clientSettings.clientPrivateKey)
if result == -1:
    raise Exception("Could not save the client private key!")


print("")
print("===========================================================================================")
print("STEP 4: Copy the client certificate to the trust list of the demo server")
print("===========================================================================================")
print("")

# for convenience, let's try to find out the path of the uaservercpp demo server,
# so that the user doesn't have to type this path manually
if os.name == "posix":
    # in linux we can do the following trick: since the uaservercpp process should be running, 
    # we can extract the uaservercpp path from there
    uaservercpp = subprocess.Popen("ps ax -o cmd | grep uaservercpp", 
                                   shell=True, 
                                   stdout=subprocess.PIPE).stdout.read().split('\n')[0]
    suggestion = os.path.dirname(uaservercpp) + "/pkiserver/trusted/certs/pyuafexample.der"
elif os.name == "nt":
    suggestion = "C:/Documents and Settings/All Users/Application Data/UnifiedAutomation/UaSdkCppBundleEval/pkiserver/trusted/certs/pyuafexample.der"
else:
    suggestion = ""

print("Enter the path to copy the client certificate to,")
print(" or leave blank to accept the following suggestion:")
print(" '%s'" %suggestion)
answer = raw_input("Enter your choice: ")

if answer == "":
    answer = suggestion

# store the certificate 
result = certificate.toDERFile(answer)
if result == -1:
    raise Exception("Could not save the client certificate to %s" %answer)



print("")
print("===========================================================================================")
print("STEP 5: Configure the session to be created")
print("===========================================================================================")
print("")

# suppose we want Basic128Rsa15 encryption + signed communication:
sessionSettings = SessionSettings()
sessionSettings.securitySettings.securityPolicy      = securitypolicies.UA_Basic128Rsa15
sessionSettings.securitySettings.messageSecurityMode = messagesecuritymodes.Mode_Sign


print("")
print("===========================================================================================")
print("STEP 6: Register a callback function to handle the server certificate")
print("===========================================================================================")
print("")


def myCallback(certificate, cause):
    print("The following server certificate was not found in the trust list:")
    print(certificate)
    print("")
    print("Choose one of the following options:")
    print(" [r] : Reject the certificate")
    print(" [t] : Accept the certificate Temporarily (i.e. don't store it inside the trust list)")
    print(" [p] : Accept the certificate Permanently (i.e. store it inside the trust list)")
    answer = raw_input("Enter your choice (r/t/p) [r] : ")
    
    if answer.upper() == "R":
        return PkiCertificate.Action_Reject
    elif answer.upper() == "T":
        return PkiCertificate.Action_AcceptTemporarily
    elif answer.upper() == "P":
        return PkiCertificate.Action_AcceptPermanently
    else:
        print("INVALID CHOICE")
        print("The certificate will be rejected.")
        return PkiCertificate.Action_Reject


myClient.registerUntrustedServerCertificateCallback(myCallback)



print("")
print("===========================================================================================")
print("STEP 7: Read some variable using the configured secure session")
print("===========================================================================================")
print("")

# define the address of a node which we would like to read:
someAddress = Address(NodeId("Demo.SimulationSpeed", DEMOSERVER_NS_URI), SERVER_URI)

result = myClient.read( [someAddress], sessionSettings = sessionSettings )

# Note that instead of explicitly providing the sessionSettings **kwarg argument, we could also
# have done the following:
#  clientSettings.defaultSessionSettings = sessionSettings
#  myClient.setClientSettings(clientSettings)
#  result = myClient.read( [someAddress] )

print("")
print("Read result:")
print(result)
print("")


# check the session that was used to read this address:
if result.overallStatus.isGood():
    print("Information about the session:")
    print(myClient.sessionInformation(result.targets[0].clientConnectionId))