Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
55
56
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# -*- coding: utf-8 -*-
#
# This file is part of the SDP project
#
#
#
# Distributed under the terms of the APACHE license.
# See LICENSE.txt for more info.
""" SDP Device Server for LOFAR2.0
"""
# PyTango imports
from tango import DebugIt
from tango.server import run
from tango.server import Device
from tango.server import attribute, command
from tango.server import device_property
from tango import AttrQuality, DispLevel, DevState
from tango import AttrWriteType, PipeWriteType
# Additional import
import sys
import opcua
import numpy
from wrappers import only_in_states, only_when_on, fault_on_error
from opcua_connection import OPCUAConnection
__all__ = ["SDP", "main"]
class SDP(Device):
"""
**Properties:**
- Device Property
OPC_Server_Name
- Type:'DevString'
OPC_Server_Port
- Type:'DevULong'
OPC_Time_Out
- Type:'DevDouble'
States are as follows:
INIT = Device is initialising.
STANDBY = Device is initialised, but pends external configuration and an explicit turning on,
ON = Device is fully configured, functional, controls the hardware, and is possibly actively running,
FAULT = Device detected an unrecoverable error, and is thus malfunctional,
OFF = Device is turned off, drops connection to the hardware,
The following state transitions are implemented:
boot -> OFF: Triggered by tango. Device will be instantiated,
OFF -> INIT: Triggered by device. Device will initialise (connect to hardware, other devices),
INIT -> STANDBY: Triggered by device. Device is initialised, and is ready for additional configuration by the user,
STANDBY -> ON: Triggered by user. Device reports to be functional,
* -> FAULT: Triggered by device. Device has degraded to malfunctional, for example because the connection to the hardware is lost,
* -> FAULT: Triggered by user. Emulate a forced malfunction for integration testing purposes,
* -> OFF: Triggered by user. Device is turned off. Triggered by the Off() command,
FAULT -> INIT: Triggered by user. Device is reinitialised to recover from an error,
The user triggers their transitions by the commands reflecting the target state (Initialise(), On(), Fault()).
"""
client = 0
name_space_index = 0
obj = 0
# -----------------
# Device Properties
# -----------------
OPC_Server_Name = device_property(
dtype='DevString',
mandatory=True
)
OPC_Server_Port = device_property(
dtype='DevULong',
mandatory=True
)
OPC_Time_Out = device_property(
dtype='DevDouble',
mandatory=True
)
# ----------
# Attributes
# ----------
SDP_mask_RW = attribute(
dtype = ('DevBoolean',),
max_dim_x = 32,
access=AttrWriteType.READ_WRITE,
)
fpga_temp_R = attribute(
dtype = ('DevDouble',),
max_dim_x = 4,
)
# ---------------
# General methods
# ---------------
def get_sdp_node(self, node):
try:
return self.sdp_node.get_child(["{}:{}".format(self.name_space_index, node)])
except opcua.ua.uaerrors._auto.BadNoMatch:
self.error_stream("Could not find SDP node %s", node)
# Contract with hardware is broken --- cannot recover
raise
def _map_attributes(self):
try:
self.name_space_index = self.client.get_namespace_index("http://lofar.eu")
except Exception as e:
self.warn_stream("Cannot determine the OPC-UA name space index. Will try and use the default = 2.")
self.name_space_index = 2
self.obj_node = self.client.get_objects_node()
self.sdp_node = self.obj_node.get_child(["{}:SDP".format(self.name_space_index)])
self.debug_stream("Mapping OPC-UA MP/CP to attributes...")
self.attribute_mapping["SDP_mask_RW"] = self.get_pcc_node("SDP_mask_RW")
self.attribute_mapping["fpga_temp_R"] = self.get_pcc_node("fpga_temp_R")
self.debug_stream("Mapping OPC-UA MP/CP to attributes done.")
def init_device(self):
""" Instantiates the device in the OFF state. """
# NOTE: Will delete_device first, if necessary
Device.init_device(self)
self.set_state(DevState.OFF)
def initialise(self):
"""Initialises the attributes and properties of the SDP."""
self.set_state(DevState.INIT)
# Init the dict that contains attribute to OPC-UA MP/CP mappings.
self.attribute_mapping = {}
# Set default values in the RW/R attributes and add them to
# the mapping.
self._SDP_mask_RW = numpy.full(32, False)
self.attribute_mapping["SDP_mask_RW"] = {}
self._fpga_temp_R = numpy.full(4, False)
self.attribute_mapping["fpga_temp_R"] = {}
# Init the dict that contains function to OPC-UA function mappings.
self.function_mapping = {}
self.client = opcua.Client("opc.tcp://{}:{}/".format(self.OPC_Server_Name, self.OPC_Server_Port), self.OPC_Time_Out) # timeout in seconds
# Connect to OPC-UA -- will set ON state on success in case of a reconnect
self.opcua_connection = OPCUAConnection(self.client, self.Standby, self.Fault, self)
# Explicitly connect
if not self.opcua_connection.connect():
# hardware or infra is down -- needs fixing first
self.Fault()
return
# Retrieve and map server attributes
try:
self._map_attributes()
except Exception as e:
self.error_stream("Could not map server interface: %s", e)
self.Fault()
return
# Start keep-alive
self.opcua_connection.start()
# Set the masks.
#
# Attention!
# Set the masks only after the OPCUA connection has been
# established! The setting of the masks needs to go through
# to the server.
#
# TODO
# Read default masks from config DB
#self.write_SDP_mask_RW(self._SDP_mask_R)
# Everything went ok -- go standby.
self.set_state(DevState.STANDBY)
def always_executed_hook(self):
"""Method always executed before any TANGO command is executed."""
pass
@DebugIt()
def delete_device(self):
"""Hook to delete resources allocated in init_device.
This method allows for any memory or other resources allocated in the
init_device method to be released. This method is called by the device
destructor and by the device Init command (a Tango built-in).
"""
self.Off()
# ------------------
# Attributes methods
# ------------------
@only_when_on
@fault_on_error
def read_SDP_mask_RW(self):
"""Return the SDP_mask_RW attribute."""
return self._SDP_mask_RW
@only_when_on
@fault_on_error
def write_SDP_mask_RW(self, value):
"""Set the SDP_mask_RW attribute."""
self.attribute_mapping["SDP_mask_RW"].set_value(value.tolist())
self._SDP_mask_RW = value
@only_when_on
@fault_on_error
def read_fpga_temp_R(self):
"""Return the fpga_temp_R attribute."""
value = numpy.array(self.attribute_mapping["fpga_temp_R"].get_value())
self._fpga_temp_R = numpy.array(numpy.split(value, indices_or_sections = 4))
return self._fpga_temp_R
# --------
# Commands
# --------
@command()
@only_in_states([DevState.FAULT, DevState.OFF])
@DebugIt()
def Initialise(self):
"""
Command to ask for initialisation of this device. Can only be called in FAULT or OFF state.
:return:None
"""
self.initialise()
@only_in_states([DevState.INIT])
def Standby(self):
"""
Command to ask for initialisation of this device. Can only be called in FAULT or OFF state.
:return:None
"""
self.set_state(DevState.STANDBY)
@command()
@only_in_states([DevState.STANDBY])
@DebugIt()
def On(self):
"""
Command to ask for initialisation of this device. Can only be called in FAULT or OFF state.
:return:None
"""
self.set_state(DevState.ON)
@command()
@DebugIt()
def Off(self):
"""
Command to ask for shutdown of this device.
:return:None
"""
if self.get_state() == DevState.OFF:
# Already off. Don't complain.
return
# Turn off
self.set_state(DevState.OFF)
# Stop keep-alive
self.opcua_connection.stop()
# Turn off again, in case of race conditions through reconnecting
self.set_state(DevState.OFF)
@command()
@only_in_states([DevState.ON, DevState.INIT, DevState.STANDBY])
@DebugIt()
def Fault(self):
"""
FAULT state is used to indicate our connection with the OPC-UA server is down.
This device will try to reconnect once, and transition to the ON state on success.
If reconnecting fails, the user needs to call Initialise() to retry to restart this device.
:return:None
"""
self.set_state(DevState.FAULT)
# ----------
# Run server
# ----------
def main(args=None, **kwargs):
"""Main function of the SDP module."""
return run((SDP,), args=args, **kwargs)
if __name__ == '__main__':
main()