Source code for ingenialink.canopen.dictionary

from functools import cached_property
from typing import Any, Optional, cast
from xml.etree import ElementTree

import ingenialogger

from ingenialink.canopen.register import CanopenRegister
from ingenialink.constants import (
    CANOPEN_ADDRESS_OFFSET,
    MAP_ADDRESS_OFFSET,
)
from ingenialink.dictionary import (
    CanOpenObject,
    CanOpenObjectType,
    Dictionary,
    DictionarySafetyModule,
    DictionaryV2,
    DictionaryV3,
    Interface,
)
from ingenialink.enums.register import RegAccess, RegCyclicType, RegDtype
from ingenialink.ethercat.register import EthercatRegister
from ingenialink.register import MonDistV3

logger = ingenialogger.get_logger(__name__)


[docs] class CanopenDictionary(Dictionary): """Base class for CANopen dictionaries.""" interface = Interface.CAN def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) self.__idx_subindex_map: dict[int, dict[int, CanopenRegister]] = { # Idx -> {subindex -> register} } self.__idx_map: dict[int, CanOpenObject] = { # Idx -> object } for objs in self.items.values(): for obj in objs.values(): index = obj.idx self.__idx_map[index] = obj for register in self.all_registers(): register = cast("CanopenRegister", register) index = register.idx subindex = register.subidx if index not in self.__idx_subindex_map: self.__idx_subindex_map[index] = {subindex: register} else: self.__idx_subindex_map[index][subindex] = register
[docs] def get_register_by_index_subindex(self, index: int, subindex: int) -> CanopenRegister: """Get a register by its index and subindex. Args: index: The index of the register. subindex: The subindex of the register. Returns: CanopenRegister: The register with the given index and subindex. """ return self.__idx_subindex_map[index][subindex]
[docs] def get_object_by_index(self, index: int) -> CanOpenObject: """Get an object by its index. Args: index: The index of the object. Returns: CanOpenObject: The object with the given index. """ return self.__idx_map[index]
[docs] class CanopenDictionaryV2(CanopenDictionary, DictionaryV2): """Contains all registers and information of a CANopen dictionary. Args: dictionary_path: Path to the Ingenia dictionary. """ @cached_property def _monitoring_disturbance_objects(self) -> list["CanOpenObject"]: monitoring_obj = CanOpenObject( uid="MON_DATA_VALUE", idx=0x58B2, object_type=CanOpenObjectType.RECORD, registers=[ CanopenRegister( identifier="MON_DATA_VALUE", units="none", idx=0x58B2, subidx=0x00, pdo_access=RegCyclicType.CONFIG, dtype=RegDtype.BYTE_ARRAY_512, access=RegAccess.RO, subnode=0, labels={"en_US": "Monitoring data"}, cat_id="MONITORING", ), ], ) disturbance_obj = CanOpenObject( uid="DIST_DATA_VALUE", idx=0x58B4, object_type=CanOpenObjectType.RECORD, registers=[ CanopenRegister( identifier="DIST_DATA_VALUE", units="none", idx=0x58B4, subidx=0x00, pdo_access=RegCyclicType.CONFIG, dtype=RegDtype.BYTE_ARRAY_512, access=RegAccess.WO, subnode=0, labels={"en_US": "Disturbance data"}, cat_id="MONITORING", ), ], ) return [monitoring_obj, disturbance_obj] @cached_property def _monitoring_disturbance_registers(self) -> list[CanopenRegister]: return [ register for obj in self._monitoring_disturbance_objects for register in obj.registers ] @cached_property def _safety_registers(self) -> list[EthercatRegister]: raise NotImplementedError("Safety registers are not implemented for this device.") @cached_property def _safety_modules(self) -> list[DictionarySafetyModule]: raise NotImplementedError("Safety modules are not implemented for this device.") def _read_xdf_register(self, register: ElementTree.Element) -> Optional[CanopenRegister]: current_read_register = super()._read_xdf_register(register) if current_read_register is None: return None try: aux_var = int(register.attrib["address"], 16) idx = aux_var >> 8 subidx = aux_var & 0xFF monitoring: Optional[MonDistV3] = None if current_read_register.pdo_access != RegCyclicType.CONFIG: address = idx - ( CANOPEN_ADDRESS_OFFSET + (MAP_ADDRESS_OFFSET * (current_read_register.subnode - 1)) ) monitoring = MonDistV3( address=address, subnode=current_read_register.subnode, cyclic=current_read_register.pdo_access, ) canopen_register = CanopenRegister( idx, subidx, current_read_register.dtype, current_read_register.access, identifier=current_read_register.identifier, units=current_read_register.units, pdo_access=current_read_register.pdo_access, phy=current_read_register.phy, subnode=current_read_register.subnode, storage=current_read_register.storage, reg_range=current_read_register.range, labels=current_read_register.labels, enums=current_read_register.enums, cat_id=current_read_register.cat_id, scat_id=current_read_register.scat_id, internal_use=current_read_register.internal_use, address_type=current_read_register.address_type, bitfields=current_read_register.bitfields, monitoring=monitoring, description=current_read_register.description, ) return canopen_register except KeyError as ke: logger.error( f"Register with ID {current_read_register.identifier} has not attribute {ke}" ) return None def _add_canopen_object(self, canopen_object: "CanOpenObject") -> None: """Adds Canopen object into the items list. Args: canopen_object: Canopen object to add. """ axis = canopen_object.registers[0].subnode if axis not in self.items: self.items[axis] = {} self.items[axis][canopen_object.uid] = canopen_object for reg in canopen_object.registers: reg.obj = canopen_object def _append_missing_registers(self) -> None: super()._append_missing_registers() if self._DictionaryV2__MON_DIST_STATUS_REGISTER in self._registers[0]: # type: ignore[attr-defined] for obj in self._monitoring_disturbance_objects: self._add_canopen_object(obj)
[docs] class CanopenDictionaryV3(CanopenDictionary, DictionaryV3): """Contains all registers and information of a CANopen dictionary. Args: dictionary_path: Path to the Ingenia dictionary. """