Commit 113763f3 authored by Elisabetta Giani's avatar Elisabetta Giani
Browse files

AT5-257: implementation of CspMaster class methods and attributes.

Added classes to create enumerated constants for SCM attributes.
parent 5ad0bea7
Loading
Loading
Loading
Loading
+417 −8
Original line number Diff line number Diff line
@@ -11,25 +11,47 @@

CSP.LMC Common Class for the CSPMaster TANGO Device.
"""
# PROTECTED REGION ID (CspMaster.standardlibray_import) ENABLED START #
# Python standard library
from __future__ import absolute_import
import sys
import os
from future.utils import with_metaclass
from collections import defaultdict
from enum import IntEnum, unique
# PROTECTED REGION END# //CspMaster.standardlibray_import

# tango imports# PROTECTED REGION ID (CspMaster.add_path) ENABLED START #

# tango imports
import tango
from tango import DebugIt
from tango.server import run
from tango.server import Device, DeviceMeta
from tango.server import attribute, command
from tango.server import device_property
from tango import AttrQuality, DispLevel, DevState
from tango import AttrWriteType, PipeWriteType
from skabase.SKAMaster  import SKAMaster
from tango import AttrQuality, EventType, DevState
from tango import AttrWriteType, DeviceProxy
# Additional import
# PROTECTED REGION ID(CspMaster.additionnal_import) ENABLED START #
#
from skabase.SKAMaster  import SKAMaster
from skabase.auxiliary import utils
# PROTECTED REGION END #    //  CspMaster.additionnal_import

# PROTECTED REGION ID (CspMaster.add_path) ENABLED START #
# add the path to import global_enum package.
file_path = os.path.dirname(os.path.abspath(__file__))
utils_path = os.path.abspath(os.path.join(file_path, os.pardir)) + "/utils"
sys.path.insert(0, utils_path)
import cspcommons
from cspcommons import HealthState, AdminMode, ObsState
import release
# PROTECTED REGION END# //CspMaster.add_path

__all__ = ["CspMaster", "main"]


class CspMaster(SKAMaster):
class CspMaster(with_metaclass(DeviceMeta, SKAMaster)):
    """
    CSP.LMC Common Class for the CSPMaster TANGO Device.

@@ -66,10 +88,237 @@ class CspMaster(SKAMaster):
            - Type:'DevVarStringArray'
    
    """
    __metaclass__ = DeviceMeta
    # PROTECTED REGION ID(CspMaster.class_variable) ENABLED START #
    @unique
    class CmdExecState(IntEnum):
        IDLE        = 0
        RUNNING     = 1
        QUEUED      = 2
    # PROTECTED REGION END #    //  CspMaster.class_variable
    # PROTECTED REGION ID(CspMaster.class_protected_methods) ENABLED START #
    
    #----------------
    # Event Callback functions
    # ---------------
    def __se_smc_change_event_cb(self, evt):
        """
        Class protected callback function.
        Retrieve the values of the sub-element SCM attributes subscribed
        for change event at device initialization.

        :param evt: The event data

        :return: None
        """
        if not evt.err:
            dev_name = evt.device.dev_name()
            try:
                if dev_name in self._se_fqdn:
                    if evt.attr_value.name.lower() == "state":
                        self._se_state[dev_name] = evt.attr_value.value
                    elif evt.attr_value.name.lower() == "healthstate":
                        self._se_health_state[dev_name] = evt.attr_value.value
                    elif evt.attr_value.name.lower() == "adminmode":
                        self._se_admin_mode[dev_name] = evt.attr_value.value
                    else:
                        log_msg = ("Attribute {} not still "
                                   "handled".format(evt.attr_name))
                        self.dev_logging(log_msg, tango.LogLevel.LOG_WARN)
                else:
                    log_msg = ("Unexpected change event for"
                               " attribute: {}".format(str(evt.attr_name)))
                    self.dev_logging(log_msg, tango.LogLevel.LOG_WARN)
                    return

                log_msg = "New value for {} is {}".format(str(evt.attr_name),
                                                          str(evt.attr_value.value))
                self.dev_logging(log_msg, tango.LogLevel.LOG_INFO)
                # update CSP global state
                if evt.attr_value.name.lower() in ["state", "healthstate"]:
                    self._update_csp_state()
            except tango.DevFailed as df:
                self.dev_logging(str(df.args[0].desc), tango.LogLevel.LOG_ERROR)
            except Exception as except_occurred:
                self.dev_logging(str(except_occurred), tango.LogLevel.LOG_ERROR)
        else:
            for item in evt.errors:
                # API_EventTimeout: if sub-element device not reachable it transitions
                # to UNKNOWN state.
                if item.reason == "API_EventTimeout":
                    if evt.attr_name.find(dev_name) > 0:
                        self._se_state[dev_name] = tango.DevState.UNKNOWN
                        self._se_health_state[dev_name] = HealthState.UNKNOWN
                        if self._se_to_switch_off[dev_name]:
                            self._se_state[dev_name] = tango.DevState.OFF
                        # update the State and healthState of the CSP Element
                        self._update_csp_state()
                log_msg = item.reason + ": on attribute " + str(evt.attr_name)
                self.dev_logging(log_msg, tango.LogLevel.LOG_WARN)
    # ---------------
    # Class protected methods
    # ---------------
    def _update_csp_state(self):
        """
        Class protected method.
        Retrieve the State attribute of the CSP sub-elements and aggregate
        them to build up the CSP global state.

        :param: None

        :return: None
        """
        self._update_csp_health_state()
        # CSP state reflects the status of CBF. Only if CBF is present
        # CSP can work. The state of PSS and PST sub-elements only contributes
        # to determine the CSP health state.
        self.set_state(self._se_state[self.CspCbf])

    def _update_csp_health_state(self):
        """
        Class private method.
        Retrieve the healthState attribute of the CSP sub-elements and
        aggregate them to build up the CSP health state

        :param: None

        :return: None
        """

        # The whole CSP HealthState is OK only if:
        # - all sub-elements with adminMode OFF-LINE or MAINTENACE are ON AND
        # - each sub-element HealthState is OK
        
        admin_on = 0
        health_ok = 0
        self._health_state = HealthState.DEGRADED
        for fqdn in self._fqdn:
            if self._se_admin_mode[fqdn] not in [AdminMode.OFFLINE, AdminMode.MAINTENANCE]:
                if fqdn == self.CspCbf:
                    self._health_state = self._se_health_state[self.CspCbf] 
                    return
                continue
            admin_on += 1
            if self._se_health_state[fqdn] == HealthState.OK:
                health_ok += 1
        if admin_on == health_ok:
            self._healthstate = HealthState.OK
        return
    
    def _timeout_handler_cb(self):
        pass
    
    def _cmd_progress_cb(self, evt):
        pass
    
    def _cmd_ended_cb(self):
        pass
    
    def _connect_to_subelements (self):
        """
        Class private method.
        Establish a *stateless* connection with each CSP sub-element and if
        the sub-element adminMod is ON-LINE or MAINTENACE, subscribes the sub-element
        State, healthState and adminMode attributes.
        Exceptions are logged.

        :return: None
        """
        # connect to TANGO DB
        csp_tango_db = tango.Database() 
        
        for fqdn in self._se_fqdn:
            # initialize the list for each dictionary key-name
            self._se_event_id[fqdn] = {}
            try:
                log_msg = "Trying connection to" + str(fqdn) + " device"
                self.dev_logging(log_msg, int(tango.LogLevel.LOG_INFO))
                device_proxy = DeviceProxy(fqdn)
                # Note: ping() methos is not called. The DeviceProy is registerd even if the
                # sub-element is not running. As soon as the sub-element Master device 
                # starts the connection is establish. When the sub-element adminMode is
                # set ONLINE (or MAINTENANCE) the SCM  attributes are subscribed.
                
                # store the sub-element proxies
                self._se_proxies[fqdn] = device_proxy
                
                # read the sub-element adminMode (memorized) attribute from
                # the TANGO DB. If the value is ON-LINE or MAINTENACE, the
                # SCM attributes of the sub-element are subscribed
                # read the sub-element adminMode value from the TANGO DB
                attribute_properties = csp_tango_db.get_device_attribute_property(fqdn, {'adminMode': ['__value']})
                print("fqdn: {} attribute_properties: {}".format(fqdn, attribute_properties))
                admin_mode_memorized = attribute_properties['adminMode']['__value']
                self._se_admin_mode[fqdn] = int(admin_mode_memorized[0])
                if self._se_admin_mode[fqdn] not in [AdminMode.ONLINE, 
                                                     AdminMode.MAINTENANCE]:
                    return
                # Subscription of the sub-element State,healthState and adminMode
                ev_id = device_proxy.subscribe_event("State",
                                                     EventType.CHANGE_EVENT,
                                                     self._se_scm_change_event_cb,
                                                     stateless=True)
                self._se_event_id[fqdn]['state'] = ev_id

                ev_id = device_proxy.subscribe_event("healthState",
                                                     EventType.CHANGE_EVENT,
                                                     self._se_scm_change_event_cb,
                                                     stateless=True)
                self._se_event_id[fqdn]['healthState'] = ev_id

                ev_id = device_proxy.subscribe_event("adminMode",
                                                     EventType.CHANGE_EVENT,
                                                     self._se_scm_change_event_cb,
                                                     stateless=True)
                self._se_event_id[fqdn]['adminMode'] = ev_id
            except tango.DevFailed as df:
                #for item in df.args:
                log_msg = ("Failure in connection to {}"
                           " device: {}".format(str(fqdn), str(df.args[0].desc)))
                self.dev_logging(log_msg, tango.LogLevel.LOG_ERROR)
                # NOTE: if the exception is thrown, the Device server fails
                # and exit. In this case we rely on K8s/Docker restart policy
                # to restart the Device Server.

    def is_se_device_running (self, subelement_name):
        """
        *Class private method.*

        Check if the sub-element is exported in the TANGO DB.
        If the device is not present in the list of the connected
        sub-elements, a connection with the device is performed.

        :param: subelement_name : the FQDN of the sub-element
        :type: `DevString`
        :return: True if the connection with the subarray is established,
                False otherwise
        """
        try:
            proxy = self._se_proxies[subelement_name]
            proxy.ping()
        except KeyError as key_err:
            # Raised when a mapping (dictionary) key is not found in the set
            # of existing keys.
            # no proxy registered for the subelement device
            msg = "Can't retrieve the information of key {}".format(key_err)
            self.dev_logging(msg, tango.LogLevel.LOG_ERROR)
            proxy = tango.DeviceProxy(subelement_name)
            # execute a ping to detect if the device is actually running
            proxy.ping()
            self._se_proxies[subelement_name] = proxy
        except tango.DevFailed as df:
            msg = "Failure reason: {} Desc: {}".format(str(df.args[0].reason), str(df.args[0].desc))
            self.dev_logging(msg, tango.LogLevel.LOG_ERROR)
            return False
        return True
    
    def _on_cmd_queued(self):
        pass
    
    def _off_cmd_queued(self):
        pass
    
                
# PROTECTED REGION END #    //  CspMaster.class_protected_methods
    # -----------------
    # Device Properties
    # -----------------
@@ -114,6 +363,8 @@ class CspMaster(SKAMaster):
        doc= ("The admin mode reported for this device. It may interpret the" 
              "current device condition and condition of all managed devices"
              "to set this. Most possibly an aggregate attribute."),
        enum_labels=["ON-LINE", "OFF-LINE", "MAINTENANCE", "NOT-FITTED", "RESERVED", ],

    )

    onCommandProgress = attribute(
@@ -535,6 +786,164 @@ class CspMaster(SKAMaster):
        """Initialises the attributes and properties of the CspMaster."""
        SKAMaster.init_device(self)       
        # PROTECTED REGION ID(CspMaster.init_device) ENABLED START #
        self._build_state = '{}, {}, {}'.format(release.name, release.version, release.description)
        self._version_id = release.version
        # set storage and element logging level
        self._storage_logging_level = int(tango.LogLevel.LOG_INFO)
        self._element_logging_level = int(tango.LogLevel.LOG_INFO)
        # set init values for the CSP Element and Sub-element SCM states
        self.set_state(tango.DevState.INIT)
        self._health_state = HealthState.OK
        self._admin_mode = AdminMode.ONLINE
        # use defaultdict to initialize the sub-element State,healthState
        # and adminMode. The dictionary uses as keys the sub-element
        # fqdn, for example
        # self._se_state[self.CspCbf]
        # return the State value of the Mid Cbf sub-element.
        self._se_state         = defaultdict(lambda: tango.DevState.DISABLED)
        self._se_health_state  = defaultdict(lambda: HealthState.UNKNOWN)
        self._se_admin_mode    = defaultdict(lambda: AdminMode.NOTFITTED)

        # initialize attribute values

       

        # initialize list with CSP sub-element FQDNs
        self._se_fqdn = []
        #NOTE:
        # The normal behavior when a Device Property is created is:
        # - a self.Property attribute is created in the Dev_Impl object
        # - it takes a value from the Database if it has been defined.
        # - if not, it takes the default value assigned in Pogo.
        # - if no value is specified nowhere, the attribute is created
        #   with [] value.
        self._se_fqdn.append(self.CspCbf)
        self._se_fqdn.append(self.CspPss)
        self._se_fqdn.append(self.CspPst)

              
        # _se_proxies: the sub-element proxies
        # implementes s a dictionary:
        # keys: sub-element FQDN
        # values: devic eproxy
        self._se_proxies = {}
        
        # Nested default dictionary with list of event ids/sub-element. Need to
        # store the event ids for each sub-element and attribute to un-subscribe
        # them at sub-element disconnection.
        # keys: sub-element FQDN
        # values: dictionary (keys: attribute name, values: event id)
        self._se_event_id = defaultdict(lambda: defaultdict(lambda: 0))
        
        # _se_cmd_timer: implement a Timer for each sub-element for each command
        # implemented as a dictionary:
        # keys: sub-element FQDN
        # values: python Timer
        self._se_cmd_timer = {}
        
        # _se_cmd_execution_state: implement the execution state of a long-running
        # command for each sub-element.
        # implemented as a default dictionary:
        # keys: sub-element FQDN
        # values: defaut dictionary (keys: command cname, values: command state)
        self._se_cmd_execution_state = defaultdict(lambda: defaultdict(lambda: CmdState.IDLE))
        
        # _se_cmd_starting_time: for each sub-element report the long-running command 
        # starting time
        # Implemented as dictionary:
        # keys: the sub-element FQDN
        # values: starting time
        self._se_cmd_starting_time = defaultdict(lambda: 0.0)
        
        # _se_on_cmd_thread: for each sub-element implement a thread for the On command
        # Implemented as default dictionary:
        # keys: the sub-element FQDN
        # values: the thread
        self._se_on_cmd_thread = {}
        
        # _se_off_cmd_thread: for each sub-element implement a thread for the Off command
        # Implemented as default dictionary:
        # keys: the sub-element FQDN
        # values: the thread
        self._se_off_cmd_thread = {}
        
        # _se_standby_cmd_thread: for each sub-element implement a thread for the 
        # Standby command
        # Implemented as default dictionary:
        # keys: the sub-element FQDN
        # values: the thread
        self._se_standby_cmd_thread = {}
        
        # _se_cmd_progress: for each sub-element report the execution progress 
        # of a long-running command
        # implemented as a default dictionary:
        # keys: sub-element FQDN
        # values: the percentage
        self._se_cmd_progress = defaultdict(lambda: defaultdict(lambda: 0))
        
        # _se_cmd_duration_expected: for each sub-element, store the duration (in sec.) 
        # configured for a long-running command 
        # Implemented as a nested default dictionary
        # keys: FQDN
        # values: default dictionary (keys: command name, values: the duration (in sec))
        self._se_cmd_duration_expected = defaultdict(lambda: defaultdict(lambda: 30))
        
        # _se_cmd_duration_measured: for each sub-element report the measured duration 
        # (in sec.) configured for a long-running command 
        # Implemented as a nested default dictionary
        # keys: FQDN
        # values: default dictionary (keys: command name, values: the duration (in sec))
        self._se_cmd_duration_measured = defaultdict(lambda: defaultdict(lambda: 0))
    
        # _se_timeout_expired: report the timeout flag 
        # Implemented as a default dictionary
        # keys: FQDN
        # values: default dictionary (keys: command name, values: True/False))
        self._se_timeout_expired = defaultdict(lambda: defaultdict(lambda: False))
        
        # _list_dev_completed_task: for each long-running command report the list of subordinate
        # components that completed the task
        # Implemented as a dictionary
        # keys: the command name ('on', 'off',...)
        # values: the list of components
        self._list_dev_completed_task = defaultdict(lambda: [])
        
        # _num_dev_completed_task: for each long-running command report the number
        #  of subordinate components that completed the task
        # Implemented as a dictionary
        # keys: the command name ('on', 'off',...)
        # values: the number of components
        self._num_dev_completed_task = defaultdict(lambda:0)
        
        # initialize CSP SearchBeam, TimingBeam and VlbiBeam Capabilities State, 
        # healthState, adminMode and obsState
        self._search_beam_state = defaultdict(lambda: tango.DevState.DISABLED)
        self._search_beam_health_state = defaultdict(lambda: tango.DevState.UNKNOWN)
        self._search_beam_admin_mode = defaultdict(lambda: tango.DevState.NOTFITTED)
        self._search_beam_obs_state = defaultdict(lambda: tango.ObsState.IDLE)
        self._timing_beam_state = defaultdict(lambda: tango.DevState.DISABLED)
        self._timing_beam_health_state = defaultdict(lambda: tango.DevState.UNKNOWN)
        self._timing_beam_admin_mode = defaultdict(lambda: tango.DevState.NOTFITTED)
        self._timing_beam_obs_state = defaultdict(lambda: tango.ObsState.IDLE)
        self._vlbi_beam_state = defaultdict(lambda: tango.DevState.DISABLED)
        self._vlbi_beam_health_state = defaultdict(lambda: tango.DevState.UNKNOWN)
        self._vlbi_beam_admin_mode = defaultdict(lambda: tango.DevState.NOTFITTED)
        self._vlbi_beam_obs_state = defaultdict(lambda: tango.ObsState.IDLE)
         #initialize Csp capabilities subarray membership affiliation
        self._timingBeamsMembership = [] 
        self._vlbiBeamsMembership   = []
        # unassigned CSP SearchBeam, TimingBeam, VlbiBeam Capability IDs
        self._unassigned_search_beam_id = []
        #self._unassigned_search_beam_num =  0
        self._reserved_search_beam_id =  []
        #self._reserved_search_beam_num =  0
        self._unassigned_timing_beam_id = []
        self._unassigned_vlbi_beam_id = []
                                                                
        
        # Try connection with sub-elements
        self._connect_to_subelements()
        
        # PROTECTED REGION END #    //  CspMaster.init_device

    def always_executed_hook(self):
@@ -558,13 +967,13 @@ class CspMaster(SKAMaster):
    def read_adminMode(self):
        # PROTECTED REGION ID(CspMaster.adminMode_read) ENABLED START #
        """Return the adminMode attribute."""
        return 0
        return self._admin_mode
        # PROTECTED REGION END #    //  CspMaster.adminMode_read

    def write_adminMode(self, value):
        # PROTECTED REGION ID(CspMaster.adminMode_write) ENABLED START #
        """Set the adminMode attribute."""
        pass
        self._admin_mode = value
        # PROTECTED REGION END #    //  CspMaster.adminMode_write

    def read_onCommandProgress(self):
+42 −0
Original line number Diff line number Diff line
from enum import IntEnum, unique

@unique
class HealthState(IntEnum):
    OK       = 0
    DEGRADED = 1
    FAILED   = 2
    UNKNOWN  = 3

@unique
class AdminMode(IntEnum):
    ONLINE      = 0
    OFFLINE     = 1
    MAINTENANCE = 2
    NOTFITTED   = 3
    RESERVED    = 4

@unique
class ControlMode(IntEnum):
    REMOTE = 0
    LOCAL  = 1

@unique
class ObsMode(IntEnum):
    IDLE             = 0
    IMAGING          = 1
    PULSARSEARCH     = 2
    PULSARTIMING     = 3
    DYNAMICSPECTRUM  = 4
    TRANSIENTSEARCH  = 5
    VLBI             = 6
    CALIBRATION      = 7

@unique
class ObsState(IntEnum):
    IDLE        = 0
    CONFIGURING = 1
    READY       = 2
    SCANNING    = 3
    PAUSED      = 4
    ABORTED     = 5
    FAULT       = 6
+20 −0
Original line number Diff line number Diff line
# -*- coding: utf-8 -*-
#
# This file is part of the CentralNode project
#
#
#
# Distributed under the terms of the BSD-3-Clause license.
# See LICENSE.txt for more info.

"""Release information for Python Package"""

name = """csplmc"""
version = "0.1.0"
version_info = version.split(".")
description = """SKA CSP.LMC Common Software"""
author = "INAF-OAA"
author_email = "elisabetta.giani@inaf.it"
license = """BSD-3-Clause"""
url = """https://gitlab.com/ska-telescope/csp-lmc.git"""
copyright = """INAF, SKA Telescope"""