Source code for ingenialink.dictionary

import xml.etree.ElementTree as ET
import ingenialogger

from abc import ABC, abstractmethod
from ingenialink.constants import SINGLE_AXIS_MINIMUM_SUBNODES
from ingenialink.register import REG_DTYPE, REG_ACCESS, REG_ADDRESS_TYPE
from ingenialink import exceptions as exc

logger = ingenialogger.get_logger(__name__)

# Dictionary constants guide:
# Each constant has this structure: DICT_ORIGIN_END
# ORIGIN: The start point of the path
# END: The end point of the path
# ORIGIN: LABELS
DICT_LABELS = "./Labels"
DICT_LABELS_LABEL = f"{DICT_LABELS}/Label"


[docs]class DictionaryCategories: """Contains all categories from a Dictionary. Args: list_xdf_categories (list): List of Elements from xdf file """ def __init__(self, list_xdf_categories): self._list_xdf_categories = list_xdf_categories self._cat_ids = [] self._categories = {} self.load_cat_ids()
[docs] def load_cat_ids(self): """Load category IDs from dictionary.""" for element in self._list_xdf_categories: self._cat_ids.append(element.attrib["id"]) self._categories[element.attrib["id"]] = {"en_US": element.find(DICT_LABELS_LABEL).text}
@property def category_ids(self): """list: Category IDs.""" return self._cat_ids
[docs] def labels(self, cat_id): """Obtain labels for a certain category ID. Args: cat_id (str): Category ID Returns: dict: Labels dictionary. """ return self._categories[cat_id]
[docs]class DictionaryErrors: """Errors for the dictionary. Args: list_xdf_errors (list): List of Elements from xdf file """ def __init__(self, list_xdf_errors): self._list_xdf_errors = list_xdf_errors self._errors = {} self.load_errors()
[docs] def load_errors(self): """Load errors from dictionary.""" for element in self._list_xdf_errors: label = element.find(DICT_LABELS_LABEL) self._errors[int(element.attrib["id"], 16)] = [ element.attrib["id"], element.attrib["affected_module"], element.attrib["error_type"].capitalize(), label.text, ]
@property def errors(self): """Get the errors dictionary. Returns: dict: Errors dictionary. """ return self._errors
[docs]class Dictionary(ABC): """Ingenia dictionary Abstract Base Class. Args: dictionary_path (str): Dictionary file path. Raises: ILCreationError: If the dictionary could not be created. """ # Dictionary constants guide: # Each constant has this structure: DICT_ORIGIN_END # ORIGIN: The start point of the path # END: The end point of the path # ORIGIN: ROOT DICT_ROOT = "." DICT_ROOT_HEADER = f"{DICT_ROOT}/Header" DICT_ROOT_VERSION = f"{DICT_ROOT_HEADER}/Version" DICT_ROOT_BODY = f"{DICT_ROOT}/Body" DICT_ROOT_DEVICE = f"{DICT_ROOT_BODY}/Device" DICT_ROOT_CATEGORIES = f"{DICT_ROOT_DEVICE}/Categories" DICT_ROOT_CATEGORY = f"{DICT_ROOT_CATEGORIES}/Category" DICT_ROOT_ERRORS = f"{DICT_ROOT_BODY}/Errors" DICT_ROOT_ERROR = f"{DICT_ROOT_ERRORS}/Error" DICT_ROOT_AXES = f"{DICT_ROOT_DEVICE}/Axes" DICT_ROOT_AXIS = f"{DICT_ROOT_AXES}/Axis" DICT_ROOT_REGISTERS = f"{DICT_ROOT_DEVICE}/Registers" DICT_ROOT_REGISTER = f"{DICT_ROOT_REGISTERS}/Register" # ORIGIN: REGISTERS DICT_REGISTERS = "./Registers" DICT_REGISTERS_REGISTER = f"{DICT_REGISTERS}/Register" # ORIGIN: RANGE DICT_RANGE = "./Range" # ORIGIN: ENUMERATIONS DICT_ENUMERATIONS = "./Enumerations" DICT_ENUMERATIONS_ENUMERATION = f"{DICT_ENUMERATIONS}/Enum" class AttrRegDict: IDENTIFIER = "identifier" UNITS = "units" CYCLIC = "cyclic" DTYPE = "dtype" ACCESS = "access" SUBNODE = "subnode" STORAGE = "storage" REG_RANGE = "reg_range" LABELS = "labels" ENUMS = "enums" CAT_ID = "cat_id" INT_USE = "internal_use" ADDRESS_TYPE = "address_type" dtype_xdf_options = { "float": REG_DTYPE.FLOAT, "s8": REG_DTYPE.S8, "u8": REG_DTYPE.U8, "s16": REG_DTYPE.S16, "u16": REG_DTYPE.U16, "s32": REG_DTYPE.S32, "u32": REG_DTYPE.U32, "s64": REG_DTYPE.S64, "u64": REG_DTYPE.U64, "str": REG_DTYPE.STR, } access_xdf_options = {"r": REG_ACCESS.RO, "w": REG_ACCESS.WO, "rw": REG_ACCESS.RW} address_type_xdf_options = { "NVM": REG_ADDRESS_TYPE.NVM, "NVM_NONE": REG_ADDRESS_TYPE.NVM_NONE, "NVM_CFG": REG_ADDRESS_TYPE.NVM_CFG, "NVM_LOCK": REG_ADDRESS_TYPE.NVM_LOCK, "NVM_HW": REG_ADDRESS_TYPE.NVM_HW, } def __init__(self, dictionary_path): self.path = dictionary_path """str: Path of the dictionary.""" self.version = "1" """str: Version of the dictionary.""" self.firmware_version = None """str: Firmware version declared in the dictionary.""" self.product_code = None """int: Product code declared in the dictionary.""" self.part_number = None """str: Part number declared in the dictionary.""" self.revision_number = None """int: Revision number declared in the dictionary.""" self.interface = None """str: Interface declared in the dictionary.""" self.subnodes = SINGLE_AXIS_MINIMUM_SUBNODES """int: Number of subnodes in the dictionary.""" self.categories = None """DictionaryCategories: Instance of all the categories in the dictionary.""" self.errors = None """DictionaryErrors: Instance of all the errors in the dictionary.""" self._registers = [] """list(dict): Instance of all the registers in the dictionary""" self.read_dictionary()
[docs] def registers(self, subnode): """Gets the register dictionary to the targeted subnode. Args: subnode (int): Identifier for the subnode. Returns: dict: Dictionary of all the registers for a subnode. """ return self._registers[subnode]
[docs] def read_dictionary(self): """Reads the dictionary file and initializes all its components.""" try: with open(self.path, "r", encoding="utf-8") as xdf_file: tree = ET.parse(xdf_file) except FileNotFoundError: raise FileNotFoundError(f"There is not any xdf file in the path: {self.path}") root = tree.getroot() device = root.find(self.DICT_ROOT_DEVICE) # Subnodes if root.findall(self.DICT_ROOT_AXES): self.subnodes = len(root.findall(self.DICT_ROOT_AXIS)) for _ in range(self.subnodes): self._registers.append({}) # Categories list_xdf_categories = root.findall(self.DICT_ROOT_CATEGORY) self.categories = DictionaryCategories(list_xdf_categories) # Errors list_xdf_errors = root.findall(self.DICT_ROOT_ERROR) self.errors = DictionaryErrors(list_xdf_errors) # Version version_node = root.find(self.DICT_ROOT_VERSION) if version_node is not None: self.version = version_node.text self.firmware_version = device.attrib.get("firmwareVersion") product_code = device.attrib.get("ProductCode") if product_code is not None and product_code.isdecimal(): self.product_code = int(product_code) self.part_number = device.attrib.get("PartNumber") revision_number = device.attrib.get("RevisionNumber") if revision_number is not None and revision_number.isdecimal(): self.revision_number = int(revision_number) self.interface = device.attrib.get("Interface") if root.findall(self.DICT_ROOT_AXES): # For each axis for axis in root.findall(self.DICT_ROOT_AXIS): for register in axis.findall(self.DICT_REGISTERS_REGISTER): current_read_register = self._read_xdf_register(register) if current_read_register: self._add_register_list(current_read_register) else: for register in root.findall(self.DICT_ROOT_REGISTER): current_read_register = self._read_xdf_register(register) if current_read_register: self._add_register_list(current_read_register) # Closing xdf file xdf_file.close()
def _read_xdf_register(self, register): """Reads a register from the dictionary and creates a Register instance. Args: register (Element): Register instance from the dictionary. Returns: dict: The current register which it has been reading None: When at least a mandatory attribute is not in a xdf file Raises: KeyError: If the register doesn't have an identifier. ValueError: If the register data type is invalid. ValueError: If the register access type is invalid. ValueError: If the register address type is invalid. KeyError: If some attribute is missing. """ try: # Dictionary where the current register attributes will be saved current_read_register = dict() # Identifier current_read_register[self.AttrRegDict.IDENTIFIER] = register.attrib["id"] except KeyError as ke: logger.error(f"The register doesn't have an identifier. Error caught: {ke}") return None try: # Units current_read_register[self.AttrRegDict.UNITS] = register.attrib["units"] # Cyclic current_read_register[self.AttrRegDict.CYCLIC] = register.attrib.get("cyclic", "CONFIG") # Data type dtype_aux = register.attrib["dtype"] if dtype_aux in self.dtype_xdf_options: current_read_register[self.AttrRegDict.DTYPE] = self.dtype_xdf_options[dtype_aux] else: raise ValueError( f"The data type {dtype_aux} does not exist for the register: " f'{current_read_register["identifier"]}' ) # Access type access_aux = register.attrib["access"] if access_aux in self.access_xdf_options: current_read_register[self.AttrRegDict.ACCESS] = self.access_xdf_options[access_aux] else: raise ValueError( f"The access type {access_aux} does not exist for the register: " f"{current_read_register[self.AttrRegDict.IDENTIFIER]}" ) # Address type address_type_aux = register.attrib["address_type"] if address_type_aux in self.address_type_xdf_options: current_read_register[ self.AttrRegDict.ADDRESS_TYPE ] = self.address_type_xdf_options[address_type_aux] else: raise ValueError( f"The address type {address_type_aux} does not exist for the register: " f"{current_read_register[self.AttrRegDict.IDENTIFIER]}" ) # Subnode current_read_register[self.AttrRegDict.SUBNODE] = int(register.attrib.get("subnode", 1)) # Storage current_read_register[self.AttrRegDict.STORAGE] = register.attrib.get("storage") # Category Id current_read_register[self.AttrRegDict.CAT_ID] = register.attrib.get("cat_id") # Description current_read_register[self.AttrRegDict.INT_USE] = register.attrib.get("internal_use", 0) # Labels labels_elem = register.findall(DICT_LABELS_LABEL) current_read_register[self.AttrRegDict.LABELS] = { label.attrib["lang"]: label.text for label in labels_elem } # Range range_elem = register.find(self.DICT_RANGE) current_read_register[self.AttrRegDict.REG_RANGE] = (None, None) if range_elem is not None: range_min = range_elem.attrib["min"] range_max = range_elem.attrib["max"] current_read_register[self.AttrRegDict.REG_RANGE] = (range_min, range_max) # Enumerations enums_elem = register.findall(self.DICT_ENUMERATIONS_ENUMERATION) current_read_register[self.AttrRegDict.ENUMS] = { enum.attrib["value"]: enum.text for enum in enums_elem } return current_read_register except KeyError as ke: logger.error( f"Register with ID {current_read_register[self.AttrRegDict.IDENTIFIER]} has not attribute {ke}" ) return None @abstractmethod def _add_register_list(self, register): """Adds the current read register into the _registers list Args: register (dict): the current read register it will be instanced """ pass