Source code for tango_simlib.utilities.simdd_json_parser
#########################################################################################
# Copyright 2017 SKA South Africa (http://ska.ac.za/) #
# #
# BSD license - see LICENSE.txt for details #
#########################################################################################
"""This module performs the parsing of the Simulator Description Datafile,
containing the information needed to instantiate a useful device simulator.
"""
from __future__ import absolute_import, division, print_function
from future import standard_library
standard_library.install_aliases() # noqa: E402
import json
import logging
import pkg_resources
from jsonschema import validate
from tango import AttrDataFormat, CmdArgType
from tango_simlib.utilities import helper_module
from tango_simlib.utilities.base_parser import Parser
MODULE_LOGGER = logging.getLogger(__name__)
EXPECTED_SIMULATION_PARAMETERS = {
"GaussianSlewLimited": [
"min_bound",
"max_bound",
"max_slew_rate",
"mean",
"std_dev",
"quantity_simulation_type",
"update_period",
],
"ConstantQuantity": ["quantity_simulation_type", "initial_value"],
}
[docs]class SimddParser(Parser):
"""Parses the SimDD JSON file.
Attributes
----------
data_description_file_name: str
device_class_name: str
"""
def __init__(self):
super(SimddParser, self).__init__()
self._device_override_class = {}
[docs] def parse(self, simdd_json_file):
"""Read simulator description data from json file.
Stores all the simulator description data from the json file into appropriate
attribute, command and device property data structures.
Parameters
----------
simdd_json_file: str
Name of simulator descrition data file
Notes
=====
- Data structures, are type dict with dictionary elements keyed with element name
and values must be the corresponding data value.
"""
simdd_schema_file = pkg_resources.resource_filename(
"tango_simlib.utilities", "SimDD.schema"
)
with open(simdd_schema_file) as simdd_schema:
schema_data = json.load(simdd_schema)
self.data_description_file_name = simdd_json_file
with open(simdd_json_file) as simdd_file:
device_data = json.load(simdd_file)
validate(device_data, schema_data)
for data_component, elements in device_data.items():
if data_component == "class_name":
self.device_class_name = str(elements)
elif data_component == "dynamicAttributes":
attribute_info = self.get_device_data_components_dict(
elements, data_component
)
self._device_attributes.update(attribute_info)
elif data_component == "commands":
command_info = self.get_device_data_components_dict(
elements, data_component
)
self._device_commands.update(command_info)
elif data_component == "deviceProperties":
device_prop_info = self.get_device_data_components_dict(
elements, data_component
)
self._device_properties.update(device_prop_info)
elif data_component == "class_overrides":
device_prop_info = self.get_device_data_components_dict(
elements, data_component
)
self._device_override_class.update(device_prop_info)
[docs] def get_device_data_components_dict(self, elements, element_type):
"""Extract description data from the simdd json element.
Parameters
----------
elements: list
List of device data elements with items in unicode format
e.g.
[
{
'basicAttributeData': {
'name': '<attribute-name>',
'unit': '',
'label': '',
'description': '',
'data_type': '<tango._tango.CmdArgType>',
'data_format': '<tango._tango.AttrDataFormat>',
'delta_t': '',
'delta_val': '',
'data_shape': {
'max_dim_x': '<int>',
'max_dim_y': '<int>'
},
'attributeErrorChecking': {
'min_value': '',
'max_value': '',
'min_alarm': '',
'max_alarm': ''
},
'attributeInterlocks': {
'writable': ''
},
'dataSimulationParameters': {
'quantity_simulation_type': 'GaussianSlewLimited',
'min_bound': '-10',
'max_bound': '50',
'mean': '25',
'max_slew_rate': '1',
'update_period': '1',
'std_dev': '5'
},
'attributeControlSystem': {
'display_level': 'OPERATOR',
'period': '1000',
'EventSettings': {
'eventArchiveCriteria': {
'archive_abs_change': '0.5',
'archive_period': '1000',
'archive_rel_change': '10'
},
'eventCrateria': {
'abs_change': '0.5',
'event_period': '1000',
'rel_change': '10'
}
}
}
}
}]
Returns
-------
device_dict: dict
device data dictionary in the format of
`self._device_attributes` or `self._device_commands`
"""
device_dict = {}
params_template = helper_module.DEFAULT_TANGO_ATTRIBUTE_PARAMETER_TEMPLATE.copy()
for element_data in elements:
for element_info in element_data.values():
name = element_info["name"]
element_params = self._get_reformatted_data(element_info, element_type)
if "Attributes" in element_type:
device_dict[str(name)] = {}
device_dict[str(name)].update(params_template.items())
device_dict[str(name)].update(element_params.items())
else:
device_dict[str(name)] = element_params
return device_dict
def _get_reformatted_data(self, sim_device_element_info, element_type):
"""Helper function for flattening the data dicts to be more readable.
Parameters
----------
sim_device_info: dict
Data element Dict
e.g.
{
'basicAttributeData': {
'name': '<attribute-name>',
'unit': '',
'label': '',
'description': '',
'data_type': '<tango._tango.CmdArgType>',
'data_format': '',
'delta_t': '',
'delta_val': '',
'data_shape': {
'max_dim_x': '<int>',
'max_dim_y': '<int>'
},
'attributeErrorChecking': {
'min_value': '',
'max_value': '',
'min_alarm': '',
'max_alarm': ''
},
'attributeInterlocks': {
'writable': ''
},
'dataSimulationParameters': {
'quantity_simulation_type': '<Quantity-subclass>',
'min_bound': '',
'max_bound': '',
'mean': '',
'max_slew_rate': '',
'update_period': ''
},
'attributeControlSystem': {
'display_level': '',
'period': '',
'EventSettings': {
'eventArchiveCriteria': {
'archive_abs_change': '',
'archive_period': '',
'archive_rel_change': ''
},
'eventCrateria': {
'abs_change': '',
'event_period': '',
'rel_change': ''
}
}
}
}
}
Returns
-------
items : dict
A more formatted and easy to read dictionary.
e.g.
{
'abs_change': '',
'archive_abs_change': '',
'archive_period': '',
'archive_rel_change': '',
'data_format': '',
'data_type': <tango._tango.CmdArgType>,
'delta_t': '',
'delta_val': '',
'description': '',
'display_level': '',
'event_period': '',
'label': '',
'max_alarm': '',
'max_bound': '',
'max_dim_x': '<int>','
'quantity_simulation_type': '<Quantity-subclass>',
'max_dim_y': '<int>',
'max_slew_rate': '',
'max_value': '',
'mean': '',
'min_alarm': '',
'min_bound': '',
'min_value': '',
'name': '<attribute-name>',
'period': '',
'rel_change': '',
'unit': '',
'update_period': '',
'writable': ''
}
"""
def expand(value):
"""Method to expand values of a value if it is an instance of dict."""
# Recursively call get_reformatted_data if value is still a dict
return [
(param_name, param_val)
for param_name, param_val in self._get_reformatted_data(
value, element_type
).items()
]
formated_info = {}
for param_name, param_val in sim_device_element_info.items():
if isinstance(param_val, dict):
if "dataSimulationParameters" in param_name:
try:
sim_type = param_val["quantity_simulation_type"]
except ValueError:
raise ValueError(
"{} with name {} has no quantity "
"simulation type specified".format(
str(element_type), str(sim_device_element_info["name"])
)
)
for sim_param in param_val:
try:
assert str(sim_param) in (
EXPECTED_SIMULATION_PARAMETERS[sim_type]
)
except AssertionError:
raise ValueError(
"{} with name {} has "
"unexpected simulation parameter {}".format(
str(element_type),
str(sim_device_element_info["name"]),
str(sim_param),
)
)
for item in expand(param_val):
property_key = str(item[0])
# Since the data type specified in the SimDD is a string format
# e.g. String, it is require in Tango device as a CmdArgType
# i.e. tango._tango.CmdArgType.DevString
if property_key in ["dtype_in", "dtype_out"]:
# Here we extract the CmdArgType object since for later when
# creating a Tango command, data type is required in this format.
val = getattr(CmdArgType, "Dev%s" % str(item[1]))
formated_info[property_key] = val
elif property_key in ["dformat_in", "dformat_out"]:
val = getattr(AttrDataFormat, str(item[1]).upper())
formated_info[property_key] = val
else:
formated_info[property_key] = str(item[1])
elif param_name in ["actions"]:
actions = []
for item in param_val:
string_items = {}
for key, value in item.items():
string_items[str(key)] = str(value)
actions.append(string_items)
formated_info["actions"] = actions
else:
# Since the data type specified in the SimDD is a string format
# e.g. Double, it is required in Tango device as a CmdArgType
# i.e. tango._tango.CmdArgType.DevDouble
if str(param_name) in ["data_type"]:
# Here we extract the CmdArgType object since for later when creating
# a Tango attibute, data type is required in this format.
val = getattr(CmdArgType, "Dev%s" % str(param_val))
elif str(param_name) in ["DefaultPropValue"]:
# Default property value can be an string, number and array
# NB: It is also an optional parameter
val = param_val
else:
val = str(param_val)
formated_info[str(param_name)] = val
return formated_info
[docs] def get_device_attribute_metadata(self):
"""Returns a more formatted attribute data structure in a format of dict.
e.g.
{
'<attribute-name>': {
'abs_change': '',
'archive_abs_change': '',
'archive_period': '',
'archive_rel_change': '',
'data_format': '',
'data_type': <tango._tango.CmdArgType>,
'delta_t': '',
'delta_val': '',
'description': '',
'display_level': '',
'event_period': '',
'label': '',
'max_alarm': '',
'max_bound': '',
'max_dim_x': '<int>',
'max_dim_y': '<int>',
'max_slew_rate': '',
'max_value': '',
'mean': '',
'min_alarm': '',
'min_bound': '',
'min_value': '',
'name': '<attribute-name>',
'quantity_simulation_type': '<Quantity-subclass',
'period': '',
'rel_change': '',
'unit': '',
'update_period': '',
'writable': ''},
}
"""
return self._device_attributes
[docs] def get_device_command_metadata(self):
"""Returns a more formatted command data structure in a format of dict.
e.g.
{
'<command-name>': {
'actions': [
{
'behaviour': '',
'source_variable': ''
}
],
'description': '',
'dformat_in': '',
'dformat_out': '',
'doc_in': '',
'doc_out': '',
'dtype_in': <tango._tango.CmdArgType>,
'dtype_out': <tango._tango.CmdArgType>,
'name': '<command-name>',
'override_handler': '<boolean>'},
}
"""
return self._device_commands
[docs] def get_device_properties_metadata(self, property_group):
"""Returns a more formatted device prop data structure in a format of dict.
e.g.
{
'<property-name>': {
'DefaultPropValue': '', # The key was changed from 'default_value'
# so as to have the same data structures
# across all the three parsers.
'name': '<property-name>',
'type': '<data-type'},
}
"""
return self._device_properties
[docs] def get_device_cmd_override_metadata(self):
"""Returns more formatted device override info data structure in dict format.
e.g.
{
'Sim_<class-name>_Override': {
'class_name': '<override-class-name>',
'module_directory': '<absolute-path>',
'module_name': '<module_name>',
'name': 'Sim_<class-name>_Override'
}
}
"""
return self._device_override_class