import xml.etree.ElementTree as ET
from ..dictionary import Dictionary
from ..constants import SINGLE_AXIS_MINIMUM_SUBNODES
from .register import CanopenRegister, REG_ACCESS, REG_DTYPE
from ingenialink.utils._utils import *
from .._ingenialink import lib
[docs]class CanopenCategories:
"""Contains all categories from a CANopen Dictionary.
Args:
dict_ (str): Path to the Ingenia dictionary.
"""
def __init__(self, dict_):
self._dict = dict_
self._cat_ids = []
self._categories = {} # { cat_id : label }
self.load_cat_ids()
[docs] def load_cat_ids(self):
"""Load category IDs from dictionary."""
with open(self._dict, 'r', encoding='utf-8') as xml_file:
tree = ET.parse(xml_file)
root = tree.getroot()
for element in root.findall('./Body/Device/Categories/Category'):
self._cat_ids.append(element.attrib['id'])
self._categories[element.attrib['id']] = {
'en_US': element.find('./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.
Returns:
dict: Labels dictionary.
"""
return self._categories[cat_id]
[docs]class CanopenErrors:
"""Errors for the CANopen dictionary.
Args:
dict_ (str): Path to the Ingenia dictionary.
"""
def __init__(self, dict_):
self._dict = dict_
self._errors = {} # { cat_id : label }
self.load_errors()
[docs] def load_errors(self):
"""Load errors from dictionary."""
with open(self._dict, 'r', encoding='utf-8') as xml_file:
tree = ET.parse(xml_file)
root = tree.getroot()
for element in root.findall('./Body/Errors/Error'):
label = element.find('./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):
"""dict: Errors dictionary."""
return self._errors
[docs]class CanopenDictionary(Dictionary):
"""Contains all registers and information of a CANopen dictionary.
Args:
dictionary_path (str): Path to the Ingenia dictionary.
"""
def __init__(self, dictionary_path):
super(CanopenDictionary, self).__init__(dictionary_path)
self.version = '1'
self.categories = None
self.subnodes = SINGLE_AXIS_MINIMUM_SUBNODES
self.__registers = []
self.errors = None
self.read_dictionary()
[docs] def read_dictionary(self):
"""Reads the dictionary file and initializes all its components."""
with open(self.path, 'r', encoding='utf-8') as xml_file:
tree = ET.parse(xml_file)
root = tree.getroot()
device = root.find('./Body/Device')
# Subnodes
if root.findall('./Body/Device/Axes/'):
self.subnodes = len(root.findall('./Body/Device/Axes/Axis'))
for _ in range(self.subnodes):
self.__registers.append({})
# Categories
self.categories = CanopenCategories(self.path)
# Errors
self.errors = CanopenErrors(self.path)
# Version
version_node = root.find('.Header/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('./Body/Device/Axes/'):
# For each axis
for axis in root.findall('./Body/Device/Axes/Axis'):
for register in axis.findall('./Registers/Register'):
self.read_register(register)
else:
for register in root.findall('./Body/Device/Registers/Register'):
self.read_register(register)
# Closing xml file
xml_file.close()
[docs] def read_register(self, register):
"""Reads a register from the dictionary and creates a Register instance.
Args:
register (Element): Register instance from the dictionary.
"""
try:
# Identifier
identifier = register.attrib['id']
# Units
units = register.attrib['units']
# Cyclic
cyclic = register.attrib[
'cyclic'] if 'cyclic' in register.attrib else "CONFIG"
idx = int(register.attrib['address'][:6], 16)
subidx = int("0x" + register.attrib['address'][-2:], 16)
# Data type
dtype = register.attrib['dtype']
if dtype == "float":
dtype = REG_DTYPE.FLOAT
elif dtype == "s8":
dtype = REG_DTYPE.S8
elif dtype == "u8":
dtype = REG_DTYPE.U8
elif dtype == "u16":
dtype = REG_DTYPE.U16
elif dtype == "s16":
dtype = REG_DTYPE.S16
elif dtype == "s32":
dtype = REG_DTYPE.S32
elif dtype == "u32":
dtype = REG_DTYPE.U32
elif dtype == "str":
dtype = REG_DTYPE.STR
else:
raise_err(lib.IL_EINVAL, 'Invalid data type')
# Access
access = register.attrib['access']
if access == "r":
access = REG_ACCESS.RO
elif access == "w":
access = REG_ACCESS.WO
elif access == "rw":
access = REG_ACCESS.RW
else:
raise_err(lib.IL_EACCESS, 'Invalid access type')
# Subnode
subnode = int(
register.attrib['subnode']) if 'subnode' in register.attrib else 1
# Storage
storage = register.attrib[
'storage'] if 'storage' in register.attrib else None
cat_id = register.attrib['cat_id'] if 'cat_id' in register.attrib else None
if 'internal_use' in register.attrib:
internal_use = register.attrib['internal_use']
else:
internal_use = 0
# Labels
labels_elem = register.findall('./Labels/Label')
labels = {label.attrib['lang']: label.text for label in labels_elem}
# Range
range_elem = register.find('./Range')
reg_range = (None, None)
if range_elem is not None:
range_min = range_elem.attrib['min']
range_max = range_elem.attrib['max']
reg_range = (range_min, range_max)
# Enumerations
enums_elem = register.findall('./Enumerations/Enum')
enums = [{enum.attrib['value']: enum.text} for enum in enums_elem]
reg = CanopenRegister(identifier, units, cyclic, idx, subidx, dtype,
access, subnode=subnode,
storage=storage, reg_range=reg_range,
labels=labels, enums=enums,
enums_count=len(enums), cat_id=cat_id,
internal_use=internal_use)
self.__registers[int(subnode)][identifier] = reg
except Exception as e:
pass
[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]