Passed
Pull Request — master (#120)
by Olivier
02:26
created

server-ua-python-mirror.UaObject.write_value()   A

Complexity

Conditions 4

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 7
nop 2
dl 0
loc 10
rs 10
c 0
b 0
f 0
1
import sys
2
sys.path.insert(0, "..")
3
import time
4
5
6
from asyncua import ua, Server
7
8
# INFO: The concept in this example is that the software model is first built in OPC UA via XML. After that, matching
9
# python objects are created based on the UA address space design. Do not use this example to build a UA address space
10
# from the python design.
11
12
# The advantage of this is that the software can be designed in a tool like UA Modeler. Then with minimal setup, a
13
# python program will import the XML and mirror all the objects in the software design. After this mirroring is achieved
14
# the user can focus on programming in python knowing that all all data from UA clients will reach the python object,
15
# and all data that needs to be output to the server can be published from the python object.
16
#
17
# Be aware that subscription calls are asynchronous.
18
19
20
class SubHandler(object):
21
    """
22
    Subscription Handler. To receive events from server for a subscription.
23
    The handler forwards updates to it's referenced python object
24
    """
25
26
    def __init__(self, obj):
27
        self.obj = obj
28
29
    def datachange_notification(self, node, val, data):
30
        # print("Python: New data change event", node, val, data)
31
32
        _node_name = node.get_browse_name()
33
        setattr(self.obj, _node_name.Name, data.monitored_item.Value.Value.Value)
34
35
36
class UaObject(object):
37
    """
38
    Python object which mirrors an OPC UA object
39
    Child UA variables/properties are auto subscribed to to synchronize python with UA server
40
    Python can write to children via write method, which will trigger an update for UA clients
41
    """
42
    def __init__(self, asyncua_server, ua_node):
43
        self.asyncua_server = asyncua_server
44
        self.nodes = {}
45
        self.b_name = ua_node.get_browse_name().Name
46
47
        # keep track of the children of this object (in case python needs to write, or get more info from UA server)
48
        for _child in ua_node.get_children():
49
            _child_name = _child.get_browse_name()
50
            self.nodes[_child_name.Name] = _child
51
52
        # find all children which can be subscribed to (python object is kept up to date via subscription)
53
        sub_children = ua_node.get_properties()
54
        sub_children.extend(ua_node.get_variables())
55
56
        # subscribe to properties/variables
57
        handler = SubHandler(self)
58
        sub = asyncua_server.create_subscription(500, handler)
59
        handle = sub.subscribe_data_change(sub_children)
60
61
    def write_value(self, attr=None):
62
        # if a specific attr isn't passed to write, write all OPC UA children
63
        if attr is None:
64
            for k, node in self.nodes.items():
65
                node_class = node.get_node_class()
66
                if node_class == ua.NodeClass.Variable:
67
                    node.write_value(getattr(self, k))
68
        # only update a specific attr
69
        else:
70
            self.nodes[attr].write_value(getattr(self, attr))
71
72
73
class MyObj(UaObject):
74
    """
75
    Definition of OPC UA object which represents a object to be mirrored in python
76
    This class mirrors it's UA counterpart and semi-configures itself according to the UA model (generally from XML)
77
    """
78
    def __init__(self, asyncua_server, ua_node):
79
80
        # properties and variables; must mirror UA model (based on browsename!)
81
        self.MyVariable = 0
82
        self.MyProperty = 0
83
        self.MyClientWrite = 0
84
85
        # init the UaObject super class to connect the python object to the UA object
86
        super().__init__(asyncua_server, ua_node)
87
88
        # local values only for use inside python
89
        self.testval = 'python only'
90
91
        # If the object has other objects as children it is best to search by type and instantiate more
92
        # mirrored python classes so that your UA object tree matches your python object tree
93
94
        # ADD CUSTOM OBJECT INITIALIZATION BELOW
95
        # find children by type and instantiate them as sub-objects of this class
96
        # NOT PART OF THIS EXAMPLE
97
98
99
if __name__ == "__main__":
100
101
    # setup our server
102
    server = Server()
103
    server.set_endpoint("opc.tcp://0.0.0.0:4840/freeopcua/server/")
104
105
    # setup our own namespace, not really necessary but should as spec
106
    uri = "http://examples.freeopcua.github.io"
107
    idx = server.register_namespace(uri)
108
109
    # get Objects node, this is where we should put our nodes
110
    objects = server.get_objects_node()
111
112
    # populating our address space; in most real use cases this should be imported from UA spec XML
113
    myobj = objects.add_object(idx, "MyObject")
114
    myvar = myobj.add_variable(idx, "MyVariable", 0.0)
115
    myprop = myobj.add_property(idx, "MyProperty", 0)
116
    mywritevar = myobj.add_variable(idx, "MyClientWrite", 0)
117
    mywritevar.set_writable()    # Set MyVariable to be writable by clients
118
119
    # starting!
120
    server.start()
121
122
    # after the UA server is started initialize the mirrored object
123
    my_python_obj = MyObj(server, myobj)
124
    
125
    try:
126
        while True:
127
            # from an OPC UA client write a value to this node to see it show up in the python object
128
            print('Python mirror of MyClientWrite is: ' + str(my_python_obj.MyClientWrite))
129
130
            # write a single attr to OPC UA
131
            my_python_obj.MyVariable = 12.3
132
            my_python_obj.MyProperty = 55  # this value will not be visible to clients because write is not called
133
            my_python_obj.write_value('MyVariable')
134
135
            time.sleep(3)
136
137
            # write all attr of the object to OPC UA
138
            my_python_obj.MyVariable = 98.1
139
            my_python_obj.MyProperty = 99
140
            my_python_obj.write_value()
141
142
            time.sleep(3)
143
144
            # write directly to the OPC UA node of the object
145
            dv = ua.DataValue(ua.Variant(5.5, ua.VariantType.Double))
146
            my_python_obj.nodes['MyVariable'].write_value(dv)
147
            dv = ua.DataValue(ua.Variant(4, ua.VariantType.UInt64))
148
            my_python_obj.nodes['MyVariable'].write_value(dv)
149
150
            time.sleep(3)
151
152
    finally:
153
        # close connection, remove subscriptions, etc
154
        server.stop()
155