Source code for tango_simlib.sim_xmi_parser

#!/usr/bin/env python
###############################################################################
# SKA South Africa (http://ska.ac.za/)                                        #
# Author: cam@ska.ac.za                                                       #
# Copyright @ 2013 SKA SA. All rights reserved.                               #
#                                                                             #
# THIS SOFTWARE MAY NOT BE COPIED OR DISTRIBUTED IN ANY FORM WITHOUT THE      #
# WRITTEN PERMISSION OF SKA SA.                                               #
###############################################################################
"""
Simlib library generic simulator generator utility to be used to generate an actual
TANGO device that exhibits the behaviour defined in the data description file.
@author MeerKAT CAM team <cam@ska.ac.za>
"""

import logging

import xml.etree.ElementTree as ET
import PyTango

from PyTango import (DevBoolean, DevString, DevEnum, AttrDataFormat,
                     CmdArgType)

MODULE_LOGGER = logging.getLogger(__name__)
CONSTANT_DATA_TYPES = frozenset([DevBoolean, DevEnum, DevString])
POGO_PYTANGO_ATTR_FORMAT_TYPES_MAP = {
    'Image': AttrDataFormat.IMAGE,
    'Scalar': AttrDataFormat.SCALAR,
    'Spectrum': AttrDataFormat.SPECTRUM}

# TODO(KM 31-10-2016): Need to add xmi attributes' properties that are currently
# not being handled by the parser e.g. [displayLevel, enumLabels] etc.
POGO_USER_DEFAULT_ATTR_PROP_MAP = {
    'dynamicAttributes': {
        'name': 'name',
        'dataType': 'data_type',
        'rwType': 'writable',
        'polledPeriod': 'period',
        'attType': 'data_format',
        'enum_labels': 'enum_labels',
        'maxX': 'max_dim_x',
        'maxY': 'max_dim_y'},
    'eventArchiveCriteria': {
        'absChange': 'archive_abs_change',
        'period': 'archive_period',
        'relChange': 'archive_rel_change'},
    'eventCriteria': {
        'absChange': 'abs_change',
        'period': 'event_period',
        'relChange': 'rel_change'},
    'properties': {
        'maxAlarm': 'max_alarm',
        'maxValue': 'max_value',
        'maxWarning': 'max_warning',
        'minAlarm': 'min_alarm',
        'deltaTime': 'delta_t',
        'minValue': 'min_value',
        'deltaValue': 'delta_val',
        'minWarning': 'min_warning',
        'description': 'description',
        'displayUnit': 'display_unit',
        'standardUnit': 'standard_unit',
        'format': 'format',
        'label': 'label',
        'unit': 'unit'}
    }

POGO_USER_DEFAULT_CMD_PROP_MAP = {
    'name': 'name',
    'arginDescription': 'doc_in',
    'arginType': 'dtype_in',
    'argoutDescription': 'doc_out',
    'argoutType': 'dtype_out'}

[docs]class XmiParser(object): def __init__(self): """Parser class handling a simulator description datafile in xmi format. Creating an instance of this class requires calling :meth:`parse` afterwards to extract all the provided tango attributes, commands, device property and device override class information from the specified file. The formated data is a dict structure and can be obtained using :meth:`get_reformatted_device_attr_metadata`, :meth:`get_reformatted_cmd_metadata`, :meth:`get_reformatted_properties_metadata` and :meth:`get_reformatted_override_metadata` """ self.data_description_file_name = '' self.device_class_name = '' self.device_attributes = [] """The Data structure format is a list containing attribute info in a dict e.g. [{ "attribute": { "displayLevel": "OPERATOR", "maxX": "", "maxY": "", "attType": "Scalar", "polledPeriod": "1000", "dataType": DevDouble, "isDynamic": "true", "rwType": "READ", "allocReadMember": "true", "name": "temperature" }, "eventCriteria": { "relChange": "10", "absChange": "0.5", "period": "1000" }, "evArchiveCriteria": { "relChange": "10", "absChange": "0.5", "period": "1000" }, "properties": { "description": "Current temperature outside near the telescope.", "deltaValue": "", "maxAlarm": "50", "maxValue": "51", "minValue": "-10", "standardUnit": "", "minAlarm": "-9", "maxWarning": "45", "unit": "Degrees Centrigrade", "displayUnit": "", "format": "", "deltaTime": "", "label": "Outside Temperature", "minWarning": "-5" } }] """ self.device_commands = [] """The Data structure format is a list containing command info in a dict e.g. [{ "name": "On", "arginDescription": "", "arginType": tango._tango.CmdArgType.DevVoid, "argoutDescription": "ok | Device ON", "argoutType": tango._tango.CmdArgType.DevString, "description": "Turn On Device" }] """ self.device_properties = [] """Data structure format is a list containing device property info in a dict e.g. [{ "deviceProperties": { "type": DevString, "mandatory": "true", "description": "Path to the pogo generate xmi file", "name": "sim_xmi_description_file", "DefaultPropValue": "<any object>" } }] """ self.class_properties = [] """Data structure format is a list containing class property info in a dict e.g. [{ "classProperties": { "type": DevString, "mandatory": "true", "description": "Path to the pogo generate xmi file", "name": "sim_xmi_description_file", "DefaultPropValue": "<any object>" } }] """
[docs] def parse(self, sim_xmi_file): """Read simulator description data from xmi file into `self.device_properties` Stores all the simulator description data from the xmi tree into appropriate attribute, command and device property data structures. Loops through the xmi tree class elements and appends description information of dynamic/attributes into `self.device_attributes`, commands into `self.device_commands`, and device_properties into `self.device_properties`. Parameters ---------- sim_xmi_file: str Name of simulator descrition data file Notes ===== - Data structures, are type list with dictionary elements keyed with description data and values must be the corresponding data value. """ self.data_description_file_name = sim_xmi_file tree = ET.parse(sim_xmi_file) root = tree.getroot() device_class = root.find('classes') self.device_class_name = device_class.attrib['name'] for class_description_data in device_class: if class_description_data.tag in ['commands']: command_info = ( self.extract_command_description_data(class_description_data)) self.device_commands.append(command_info) elif class_description_data.tag in ['dynamicAttributes', 'attributes']: attribute_info = self.extract_attributes_description_data( class_description_data) self.device_attributes.append(attribute_info) elif class_description_data.tag in ['deviceProperties']: device_property_info = self.extract_property_description_data( class_description_data, class_description_data.tag) self.device_properties.append(device_property_info) elif class_description_data.tag in ['classProperties']: class_property_info = self.extract_property_description_data( class_description_data, class_description_data.tag) self.class_properties.append(class_property_info)
[docs] def extract_command_description_data(self, description_data): """Extract command description data from the xmi tree element. Parameters ---------- description_data: xml.etree.ElementTree.Element XMI tree element with command data, where expected element tag(s) are (i.e. description_data.tag) ['argin', 'argout'] and description_data.attrib contains { "description": "Turn On Device", "displayLevel": "OPERATOR", "isDynamic": "false", "execMethod": "on", "polledPeriod": "0", "name": "On" } Returns ------- command_data: dict Dictionary of all the command data required to create a tango command """ command_data = description_data.attrib input_parameter = description_data.find('argin') command_data['arginDescription'] = input_parameter.attrib['description'] command_data['arginType'] = self._get_arg_type(input_parameter) output_parameter = description_data.find('argout') command_data['argoutDescription'] = output_parameter.attrib['description'] command_data['argoutType'] = self._get_arg_type(output_parameter) return command_data
[docs] def extract_attributes_description_data(self, description_data): """Extract attribute description data from the xmi tree element. Parameters ---------- description_data: xml.etree.ElementTree.Element XMI tree element with attribute data Expected element tag(s) are (i.e. description_data.tag) 'dynamicAttributes' description_data.find('properties').attrib contains { "description": "", "deltaValue": "", "maxAlarm": "", "maxValue": "", "minValue": "", "standardUnit": "", "minAlarm": "", "maxWarning": "", "unit": "", "displayUnit": "", "format": "", "deltaTime": "", "label": "", "minWarning": "" } and description_data.attrib contains { "maxX": "", "maxY": "", "attType": "Scalar", "polledPeriod": "0", "displayLevel": "OPERATOR", "isDynamic": "false", "rwType": "WRITE", "allocReadMember": "false", "name": "Constant" } description_data.find('eventCriteria').attrib contains { "relChange": "10", "absChange": "0.5", "period": "1000" } description_data.find('evArchiveCriteria').attrib contains { "relChange": "10", "absChange": "0.5", "period": "1000" } Returns ------- attribute_data: dict Dictionary of all attribute data required to create a tango attribute """ attribute_data = dict() attribute_data['dynamicAttributes'] = description_data.attrib attType = attribute_data['dynamicAttributes']['attType'] if attType in POGO_PYTANGO_ATTR_FORMAT_TYPES_MAP.keys(): attribute_data['dynamicAttributes']['attType'] = ( POGO_PYTANGO_ATTR_FORMAT_TYPES_MAP[attType]) attribute_data['dynamicAttributes']['maxX'] = (1 if attribute_data['dynamicAttributes']['maxX'] == '' else int(attribute_data['dynamicAttributes']['maxX'])) attribute_data['dynamicAttributes']['maxY'] = (0 if attribute_data['dynamicAttributes']['maxY'] == '' else int(attribute_data['dynamicAttributes']['maxY'])) attribute_data['dynamicAttributes']['dataType'] = ( self._get_arg_type(description_data)) if str(attribute_data['dynamicAttributes']['dataType']) == 'DevEnum': enum_labels = [] for child in description_data.getchildren(): if child.tag == 'enumLabels': enum_labels.append(child.text) attribute_data['dynamicAttributes']['enum_labels'] = sorted(enum_labels) attribute_data['properties'] = description_data.find('properties').attrib try: attribute_data['eventCriteria'] = description_data.find( 'eventCriteria').attrib except AttributeError: MODULE_LOGGER.info( "No periodic/change event(s) information was captured in the XMI file") try: attribute_data['eventArchiveCriteria'] = description_data.find( 'evArchiveCriteria').attrib except AttributeError: MODULE_LOGGER.info( "No archive event(s) information was captured in the XMI file.") return attribute_data
[docs] def extract_property_description_data(self, description_data, property_group): """Extract device/class property description data from the xmi tree element. Parameters ---------- description_data: xml.etree.ElementTree.Element XMI tree element with device property data Expected element tag(s) are (i.e. description_data.tag) ['DefaultPropValue'] description_data.attrib contains { 'description': '', 'name': 'katcp_address', 'type': '' } property_group: str A string representing a group to which the property belongs to, either device properties or class properties. Returns ------- device_property_data: dict Dictionary of all device property data required to create a tango device property """ property_data = dict() property_data[property_group] = description_data.attrib property_data[property_group]['type'] = ( self._get_arg_type(description_data)) try: property_data[property_group]['DefaultPropValue'] = ( description_data.find('DefaultPropValue').text) except KeyError: MODULE_LOGGER.info("%s has no default value(s) specified", property_group) except AttributeError: MODULE_LOGGER.info("The 'DefaultPropValue' element is not specified in the" " description file for the %s tag", property_group) return property_data
def _get_arg_type(self, description_data): """Extract argument data type from the xmi tree element. Parameters ---------- description_data: xml.etree.ElementTree.Element XMI tree element with device_property or attribute or command data Expected element tag(s) are (i.e. description_data.tag) ['dataType'] for attributes and dynamicAttributes ['type'] for commands and deviceProperties Returns ------- arg_type: tango._tango.CmdArgType Tango argument type """ if description_data.tag in ['attributes', 'dynamicAttributes']: pogo_type = description_data.find('dataType').attrib.values()[0] else: pogo_type = description_data.find('type').attrib.values()[0] # pogo_type has format -> pogoDsl:DoubleType # Pytango type must be of the form DevDouble arg_type = pogo_type.split(':')[1].replace('Type', '') # pogo_type for status turns out to be 'pogoDsl:ConstStringType # For now it will be treated as normal DevString type if arg_type.find('Const') != -1: arg_type = arg_type.replace('Const', '') # The out_type of the device State command is PyTango._PyTango.CmdArgType.DevState # instead of the default PyTango.utils.DevState if arg_type == 'State': return CmdArgType.DevState try: # The DevVarTypeArray data type specified in pogo writes # TypeArray in xmi file instead if arg_type in ['FloatArray', 'DoubleArray', 'StringArray']: arg_type = getattr(PyTango, 'DevVar' + arg_type) else: arg_type = getattr(PyTango, 'Dev' + arg_type) except AttributeError: MODULE_LOGGER.debug("PyTango has no attribute 'Dev{}".format(arg_type)) raise AttributeError("PyTango has no attribute 'Dev{}.\n Try replacing" " '{}' with 'Var{}' in the configuration file" .format(*(3*(arg_type,)))) return arg_type
[docs] def get_reformatted_device_attr_metadata(self): """Converts the device_attributes data structure into a dictionary to make searching easier. Returns ------- attributes: dict A dictionary of all the device attributes together with their metadata specified in the POGO generated XMI file. The key represents the name of the attribute and the value is a dictionary of all the attribute's metadata. e.g. {'input_comms_ok': { 'abs_change': '', 'archive_abs_change': '', 'archive_period': '1000', 'archive_rel_change': '', 'data_type': PyTango._PyTango.CmdArgType.DevBoolean, 'data_format: PyTango._PyTango.AttrDataFormat.SCALAR, 'delta_t': '', 'delta_val': '', 'description': 'Communications with all weather sensors are nominal.', 'display_unit': '', 'event_period': '1000', 'format': '', 'label': 'Input communication OK', 'max_alarm': '', 'max_value': '', 'max_warning': '', 'min_alarm': '', 'min_value': '', 'min_warning': '', 'name': 'input_comms_ok', 'period': '1000', 'rel_change': '', 'standard_unit': '', 'unit': '', 'writable': 'READ', 'enum_labels': []}, # If attribute data type is DevEnum } """ attributes = {} for pogo_attribute_data in self.device_attributes: attribute_meta = {} for (prop_group, default_attr_props) in ( POGO_USER_DEFAULT_ATTR_PROP_MAP.items()): for pogo_prop, user_default_prop in default_attr_props.items(): try: attribute_meta[user_default_prop] = ( pogo_attribute_data[prop_group][pogo_prop]) except KeyError: MODULE_LOGGER.debug("{} information is not captured in the XMI" " file".format(pogo_prop)) attributes[attribute_meta['name']] = attribute_meta return attributes
[docs] def get_reformatted_cmd_metadata(self): """Converts the device_commands data structure into a dictionary that makes searching easier. Returns ------- commands : dict A dictionary of all the device commands together with their metadata specified in the POGO generated XMI file. The key represents the name of the command and the value is a dictionary of all the attribute's metadata. e.g. { 'cmd_name': {cmd_properties} } """ temp_commands = {} for cmd_info in self.device_commands: temp_commands[cmd_info['name']] = cmd_info commands = {} # Need to convert the POGO parameter names to the TANGO names for cmd_name, cmd_metadata in temp_commands.items(): commands_metadata = {} for cmd_prop_name, cmd_prop_value in cmd_metadata.items(): try: commands_metadata.update( {POGO_USER_DEFAULT_CMD_PROP_MAP[cmd_prop_name]: cmd_prop_value}) except KeyError: MODULE_LOGGER.info( "The property '%s' cannot be translated to a " "corresponding parameter in the TANGO library", cmd_prop_name) commands[cmd_name] = commands_metadata return commands
[docs] def get_reformatted_properties_metadata(self, property_group): """Creates a dictionary of the device properties and their metadata. Parameter --------- property_group: str A string representing a group to which the property belongs to, either device properties or class properties (deviceProperties or classProperties). Returns ------- device_properties: dict A dictionary of all the device properties together with their metadata specified in the POGO generated XMI file. The keys represent the name of the device property and the value is a dictionary of all the property's metadata. e.g. { 'device_property_name' : {device_property_metadata}} property_group: str A string representing a group to which the property belongs to, either device properties or class properties. """ properties = {} if property_group == 'deviceProperties': props = self.device_properties elif property_group == 'classProperties': props = self.class_properties else: raise Exception("Wrong argument provided") for properties_info in props: properties[properties_info[property_group]['name']] = ( properties_info[property_group]) return properties
[docs] def get_reformatted_override_metadata(self): # TODO(KM 15-12-2016) The PopulateModelQuantities and PopulateModelActions # classes assume that the parsers we have developed have the same interface # so this method does nothing but return an empty dictionary. Might provide # an implementation when the XMI file has such parameter information (provided # in the SIMDD file). return {}