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
# -*- 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:numpy.float_
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(
mandatory=True
)
OPC_Server_Port = device_property(
dtype=numpy.uint64,
mandatory=True
)
OPC_Time_Out = device_property(
dtype=numpy.float_,
mandatory=True
)
# ----------
# Attributes
# ----------
dtype = (numpy.bool_,),
access=AttrWriteType.READ_WRITE,
)
dtype = (numpy.int32,),
dtype = (numpy.int32,),
)
fpga_status_R = attribute(
dtype = (numpy.bool_,),
dtype = (numpy.float_,),
fpga_version_R = attribute(
dtype = ((numpy.int16,),),
max_dim_x = 12 * 488 * 2, max_dim_y = 16,
)
fpga_weights_RW = attribute(
dtype = ((numpy.int16,),),
max_dim_x = 12 * 488 * 2, max_dim_y = 16,
access=AttrWriteType.READ_WRITE,
)
tr_busy_R = attribute(
dtype = (numpy.bool_),
tr_reload_RW = attribute(
dtype = (numpy.bool_),
access=AttrWriteType.READ_WRITE,
tr_tod_R = attribute(
dtype = (numpy.uint64),
tr_uptime_R = attribute(
dtype = (numpy.uint64,),
# ---------------
# General methods
# ---------------
def get_node(self, node):
return self.lofar_device_node.get_child(["{}:{}".format(self.name_space_index, node)])
self.error_stream("Could not find LOFAR device %s node %s", self.device, 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.name_space_index = 1
self.warn_stream("Cannot determine the OPC-UA name space index. Will try and use the default = %d." % (self.name_space_index))
# TODO
# The server does not implement the correct namespace yet.
# Instead it is directly using the Objects node.
#self.lofar_device_node = self.obj_node.get_child(["{}:SDP".format(self.name_space_index)])
self.lofar_device_node = self.obj_node
self.info_stream("Mapping OPC-UA MP/CP to attributes...")
self.attribute_mapping["fpga_mask_RW"] = self.get_node("fpga_mask_RW")
self.attribute_mapping["fpga_scrap_R"] = self.get_node("fpga_scrap_R")
self.attribute_mapping["fpga_scrap_RW"] = self.get_node("fpga_scrap_RW")
self.attribute_mapping["fpga_status_R"] = self.get_node("fpga_status_R")
self.attribute_mapping["fpga_temp_R"] = self.get_node("fpga_temp_R")
self.attribute_mapping["fpga_version_R"] = self.get_node("fpga_version_R")
self.attribute_mapping["fpga_weights_R"] = self.get_node("fpga_weights_R")
self.attribute_mapping["fpga_weights_RW"] = self.get_node("fpga_weights_RW")
self.attribute_mapping["tr_busy_R"] = self.get_node("tr_busy_R")
self.attribute_mapping["tr_reload_RW"] = self.get_node("tr_reload_W")
self.attribute_mapping["tr_tod_R"] = self.get_node("tr_tod_R")
self.attribute_mapping["tr_uptime_R"] = self.get_node("tr_uptime_R")
self.info_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._fpga_mask_RW = numpy.full(16, False)
self.attribute_mapping["fpga_mask_RW"] = {}
self._fpga_scrap_R = numpy.full(2048, False)
self.attribute_mapping["fpga_scrap_R"] = {}
self._fpga_scrap_RW = numpy.full(2048, False)
self.attribute_mapping["fpga_scrap_RW"] = {}
self._fpga_status_R = numpy.full(16, False)
self.attribute_mapping["fpga_status_R"] = {}
self._fpga_temp_R = numpy.full(16, 0.0)
self._fpga_version_R = numpy.full(16, "NO_VERSION_INFO_YET")
self.attribute_mapping["fpga_version_R"] = {}
self._fpga_weights_R = numpy.full((16, 2 * 488 * 12), 0)
self.attribute_mapping["fpga_weights_R"] = {}
self._fpga_weights_RW = numpy.full((16, 2 * 488 * 12), 0)
self.attribute_mapping["fpga_weights_RW"] = {}
self._tr_busy_R = False
self.attribute_mapping["tr_busy_R"] = {}
self._tr_reload_RW = False
self.attribute_mapping["tr_reload_RW"] = {}
self._tr_tod_R = 0
self.attribute_mapping["tr_tod_R"] = {}
self._tr_uptime_R = 0
self.attribute_mapping["tr_uptime_R"] = {}
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
# 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_fpga_mask_RW(self._fpga_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_fpga_mask_RW(self):
"""Return the fpga_mask_RW attribute."""
return self._fpga_mask_RW
@only_when_on
@fault_on_error
def write_fpga_mask_RW(self, value):
"""Return the fpga_mask_RW attribute."""
self.attribute_mapping["fpga_mask_RW"].set_value(value.tolist())
return
@only_when_on
@fault_on_error
def read_fpga_scrap_R(self):
"""Return the fpga_scrap_R attribute."""
self._fpga_scrap_R = numpy.array(self.attribute_mapping["fpga_scrap_R"].get_value(), dtype = numpy.int32)
return self._fpga_scrap_R
@only_when_on
@fault_on_error
def read_fpga_scrap_RW(self):
"""Return the fpga_scrap_RW attribute."""
return self._fpga_scrap_RW
@only_when_on
@fault_on_error
def write_fpga_scrap_RW(self, value):
"""Return the fpga_scrap_RW attribute."""
self.attribute_mapping["fpga_scrap_RW"].set_data_value(opcua.ua.uatypes.Variant(value = value.tolist(), varianttype=opcua.ua.VariantType.Int32))
def read_fpga_status_R(self):
"""Return the fpga_status_R attribute."""
self._fpga_status_R = numpy.array(self.attribute_mapping["fpga_status_R"].get_value())
return self._fpga_status_R
@only_when_on
@fault_on_error
def read_fpga_temp_R(self):
"""Return the fpga_temp_R attribute."""
self._fpga_temp_R = numpy.array(self.attribute_mapping["fpga_temp_R"].get_value())
@only_when_on
@fault_on_error
def read_fpga_version_R(self):
"""Return the fpga_version_R attribute."""
self._fpga_version_R = numpy.array(self.attribute_mapping["fpga_version_R"].get_value())
return self._fpga_version_R
@only_when_on
@fault_on_error
def read_fpga_weights_R(self):
"""Return the fpga_weights_R attribute."""
value = numpy.array(numpy.split(numpy.array(self.attribute_mapping["fpga_weights_R"].get_value(), dtype = numpy.int16), indices_or_sections = 16))
self._fpga_weights_R = value
return self._fpga_weights_R
@only_when_on
@fault_on_error
def read_fpga_weights_RW(self):
"""Return the fpga_weights_RW attribute."""
return self._fpga_weights_RW
@only_when_on
@fault_on_error
def write_fpga_weights_RW(self, value):
"""Return the fpga_weights_RW attribute."""
self.attribute_mapping["fpga_weights_RW"].set_data_value(opcua.ua.uatypes.Variant(value = value.flatten().tolist(), varianttype=opcua.ua.VariantType.Int16))
def read_tr_busy_R(self):
"""Return the tr_busy_R attribute."""
self._tr_busy_R = self.attribute_mapping["tr_busy_R"].get_value()
return self._tr_busy_R
@only_when_on
@fault_on_error
def read_tr_reload_RW(self):
"""Return the tr_reload_RW attribute."""
self._tr_reload_RW = self.attribute_mapping["tr_reload_RW"].get_value()
return self._tr_reload_RW
def write_tr_reload_RW(self, value):
"""Return the tr_reload_RW attribute."""
self.attribute_mapping["tr_reload_RW"].set_value(value)
self._tr_reload_RW = value
def read_tr_tod_R(self):
"""Return the _tr_tod_R attribute."""
self._tr_tod_R = self.attribute_mapping["tr_tod_R"].get_value()
return self._tr_tod_R
def read_tr_uptime_R(self):
"""Return the _tr_uptime_R attribute."""
self._tr_uptime_R = self.attribute_mapping["tr_uptime_R"].get_value()
return self._tr_uptime_R
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
# --------
# 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()