Newer
Older
from threading import Thread
import socket
import time
import numpy
import opcua
from opcua import Client
from tango import DevState
class CommClient(Thread):
"""
The ProtocolHandler class is the generic interface class between the tango attribute_wrapper and the outside world
"""
def __init__(self, fault_func, streams, try_interval=2):
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
"""
"""
self.fault_func = fault_func
self.try_interval = try_interval
self.streams = streams
self.stopping = False
self.connected = False
super().__init__(daemon=True)
def connect(self):
"""
Function used to connect to the client.
"""
self.connected = True
return True
def disconnect(self):
"""
Function used to connect to the client.
"""
self.connected = False
def run(self):
# Explicitly connect
if not self.connect():
# hardware or infra is down -- needs fixing first
self.fault_func()
return
self.stopping = False
while not self.stopping:
# keep trying to connect
if not self.connected:
if self.connect():
# self.standby_func()
pass
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
else:
# we retry only once, to catch exotic network issues. if the infra or hardware is down,
# our device cannot help, and must be reinitialised after the infra or hardware is fixed.
self.fault_func()
return
# keep checking if the connection is still alive
try:
while not self.stopping:
self.ping()
time.sleep(self.try_interval)
except Exception as e:
self.streams.error_stream("Fault condition in communication detected.", e)
# technically, we may not have dropped the connection, but encounter a different error. so explicitly disconnect.
self.disconnect()
# signal that we're disconnected
self.fault_func()
def ping(self):
pass
def stop(self):
"""
Stop connecting & disconnect. Can take a few seconds for the timeouts to hit.
"""
if not self.ident:
# have not yet been started, so nothing to do
return
self.stopping = True
self.join()
self.disconnect()
def setup_attribute(self, annotation, attribute):
"""
This function is responsible for providing the attribute_wrapper with a read/write function
How this is done is implementation specific.
The setup-attribute has access to the comms_annotation provided to the attribute wrapper to pass along to the comms client
as well as a reference to the attribute itself.
It should do this by first calling: _setup_annotation and setup_value_conversion to get all data necceacry to configure the read/write functions.
It should then return the read and write functions to the attribute.
MANDATORY:
annotation_outputs = _setup_annotation(annotation)
attribute_outputs = _setup_annotation(attribute)
(note: outputs are up to the user)
REQUIRED: provide read and write functions to return, there are no restrictions on how these should be provided,
except that the read function takes a single input value and the write function returns a single value
MANDATORY:
return read_function, write_function
Examples:
- File system: get_mapping returns functions that read/write a fixed
number of bytes at a fixed location in a file. (SEEK)
- OPC-UA: traverse the OPC-UA tree until the node is found.
Then return the read/write functions for that node which automatically
convert values between Python and OPC-UA.
"""
raise NotImplementedError("the setup_attribute must be implemented and provide return a valid read/write function for the attribute")
def _setup_annotation(self, annotation):
"""
This function is responsible for handling the annotation data provided by the attribute to configure the read/write function the client must provide.
This function should be called by setup_attribute
"""
raise NotImplementedError("the _setup_annotation must be implemented, content and outputs are up to the user")
def setup_value_conversion(self, attribute):
"""
this function is responsible for setting up the value conversion between the client and the attribute.
This function should be called by setup_attribute
"""
raise NotImplementedError("the setup_value_conversion must be implemented, content and outputs are up to the user")