diff --git a/luxtronik/cfi/vector.py b/luxtronik/cfi/vector.py index 8b6c9892..2124d314 100644 --- a/luxtronik/cfi/vector.py +++ b/luxtronik/cfi/vector.py @@ -45,7 +45,7 @@ def empty(cls, safe=True): obj._init_instance(safe) return obj - def add(self, def_field_name_or_idx, alias=None): + def add(self, def_field_name_or_idx): """ Adds an additional field to this data vector. Mainly used for data vectors created via `empty()` @@ -54,7 +54,6 @@ def add(self, def_field_name_or_idx, alias=None): Args: def_field_name_or_idx (LuxtronikDefinition | Base | str | int): Field to add. Either by definition, name or index, or the field itself. - alias (Hashable | None): Alias, which can be used to access the field again. Returns: Base | None: The added field object if this could be added or @@ -79,5 +78,5 @@ def add(self, def_field_name_or_idx, alias=None): # Add a (new) field if field is None: field = definition.create_field() - self._data.add_sorted(definition, field, alias) + self._data.add_sorted(definition, field) return field \ No newline at end of file diff --git a/luxtronik/collections.py b/luxtronik/collections.py index 8ead2a59..8464f4d8 100644 --- a/luxtronik/collections.py +++ b/luxtronik/collections.py @@ -206,11 +206,10 @@ def integrate_data(self, raw_data, num_bits, data_offset=-1): class LuxtronikFieldsDictionary: """ Dictionary that maps definitions, names or indices to added fields. - Aliases are also supported. """ def __init__(self): - # There may be several names or alias that points to one definition and field. + # There may be several names that points to one definition and field. # So in order to spare memory we split the name/index-to-field-lookup # into a name/index-to-definition-lookup and a definition-to-field-lookup self._def_lookup = LuxtronikDefinitionsDictionary() @@ -289,61 +288,34 @@ def field_dict(self): """ return self._field_lookup - def add(self, definition, field, alias=None): + def add(self, definition, field): """ Add a definition-field-pair to the internal dictionaries. Args: definition (LuxtronikDefinition): Definition related to the field. field (Base): Field to add. - alias (Hashable | None): Alias, which can be used to access the field again. Note: Only use this method if the definitions order is already correct. """ if definition.valid: - self._def_lookup.add(definition, alias) + self._def_lookup.add(definition) self._field_lookup[definition] = field self._pairs.append(LuxtronikDefFieldPair(definition, field)) - def add_sorted(self, definition, field, alias=None): + def add_sorted(self, definition, field): """ Behaves like the normal `add` but then sorts the pairs. Args: definition (LuxtronikDefinition): Definition related to the field. field (Base): Field to add. - alias (Hashable | None): Alias, which can be used to access the field again. """ if definition.valid: - self.add(definition, field, alias) + self.add(definition, field) # sort _pairs by definition.index self._pairs.sort(key=lambda pair: pair.definition.index) - def register_alias(self, def_field_name_or_idx, alias): - """ - Add an alternative name (or anything hashable else) - that can be used to access a specific field. - - Args: - def_field_name_or_idx (LuxtronikDefinition | Base | str | int): - Field to which the alias is to be added. - Either by definition, name, register index, or the field itself. - alias (Hashable): Alias, which can be used to access the field again. - - Returns: - Base | None: The field to which the alias was added, - or None if not possible - """ - def_name_or_idx = def_field_name_or_idx - # Resolve a field argument - if isinstance(def_name_or_idx, Base): - def_name_or_idx = def_name_or_idx.name - # register alias - definition = self._def_lookup.register_alias(def_name_or_idx, alias) - if definition is None: - return None - return self._field_lookup.get(definition, None) - def get(self, def_field_name_or_idx, default=None): """ Retrieve an added field by definition, name or register index, or the field itself. diff --git a/luxtronik/data_vector.py b/luxtronik/data_vector.py index efa2c41d..00ce0dc0 100644 --- a/luxtronik/data_vector.py +++ b/luxtronik/data_vector.py @@ -18,10 +18,7 @@ class DataVector: """ Class that holds a vector of data fields. - Provides access to fields by name, index or alias. - To use aliases, they must first be registered here (locally = only valid - for this vector) or directly in the `LuxtronikDefinitionsList` - (globally = valid for all newly created vector). + Provides access to fields by name or index. """ name = "DataVector" @@ -172,16 +169,6 @@ def items(self): return iter(self._data.items()) -# Alias methods ############################################################### - - def register_alias(self, def_field_name_or_idx, alias): - """ - Forward the `LuxtronikFieldsDictionary.register_alias` method. - Please check its documentation. - """ - return self._data.register_alias(def_field_name_or_idx, alias) - - # Get and set methods ######################################################### def _get_definition(self, def_field_name_or_idx, all_not_version_dependent): diff --git a/luxtronik/definitions/__init__.py b/luxtronik/definitions/__init__.py index 9afa9318..386fb54d 100644 --- a/luxtronik/definitions/__init__.py +++ b/luxtronik/definitions/__init__.py @@ -79,7 +79,6 @@ def __init__(self, data_dict, type_name, offset): if not names: names = ["_invalid_"] self._names = names - self._aliases = [] since = str(data_dict["since"]) self._since = parse_version(since) until = str(data_dict["until"]) @@ -183,10 +182,6 @@ def num_bits(self): def names(self): return self._names - @property - def aliases(self): - return self._aliases - @property def name(self): "Returns the preferred name." @@ -228,11 +223,7 @@ def check_raw_not_none(self, raw): class LuxtronikDefinitionsDictionary: """ - Dictionary of definitions that can be searched by index, name, or aliases. - - To use aliases, they must first be registered here (locally = - only valid for this dictionary) or directly in the `LuxtronikDefinitionsList` - (globally = valid for all newly created dictionaries). + Dictionary of definitions that can be searched by index or name. This class is intended to speed up the lookup of definitions. Dictionaries are used instead of searching through a list of definitions @@ -242,7 +233,6 @@ class LuxtronikDefinitionsDictionary: def __init__(self): self._index_dict = {} self._name_dict = {} - self._alias_dict = {} def __getitem__(self, name_or_idx): return self.get(name_or_idx) @@ -252,51 +242,13 @@ def __contains__(self, def_name_or_idx): return any(def_name_or_idx is d for d in self._name_dict.values()) return self._get(def_name_or_idx) is not None - def _add_alias(self, definition, alias): - """ - Register a single alias that references the given definition. - - Args: - definition (LuxtronikDefinition): Definition that the alias should map to. - alias (Hashable): Alias to register (str will be normalized). + def add(self, definition): """ - alias = alias.lower() if isinstance(alias, str) else alias - self._alias_dict[alias] = definition - - def register_alias(self, def_name_or_idx, alias): - """ - Register an alias (locally) that references a definition specified by - name, index, or the definition object. - - Args: - def_name_or_idx (str | int | LuxtronikDefinition): - Name, index, or definition to alias. - alias (Hashable): Alias key to register (str will be normalized). - - Returns: - LuxtronikDefinition | None: The resolved definition - when registration succeeded, otherwise None. - """ - if alias is None: - return None - # look-up definition - if isinstance(def_name_or_idx, LuxtronikDefinition): - definition = self.get(def_name_or_idx.name) - else: - definition = self.get(def_name_or_idx) - if definition is None: - return None - self._add_alias(definition, alias) - return definition - - def add(self, definition, alias=None): - """ - Add a definition to internal lookup tables and register its aliases. + Add a definition to internal lookup tables. Existing entries will be overwritten. Args: definition (LuxtronikDefinition): Definition to add. - alias (Hashable): Optional additional alias to register for this definition. """ # Add to indices-dictionary self._index_dict[definition.index] = definition @@ -306,12 +258,6 @@ def add(self, definition, alias=None): for name in definition.names: self._name_dict[name.lower()] = definition - # Add to alias-dictionary - for a in definition.aliases: - self._add_alias(definition, a) - if alias is not None: - self._add_alias(definition, alias) - def get(self, name_or_idx, default=None): """ Retrieve a definition by name or index. @@ -331,13 +277,6 @@ def get(self, name_or_idx, default=None): LOGGER.debug(f"Definition for '{name_or_idx}' not found", ) return d if d is not None else default - def _is_hashable(self, x): - try: - hash(x) - return True - except TypeError: - return False - def _get(self, name_or_idx): """ Retrieve a definition by name or index. @@ -352,24 +291,14 @@ def _get(self, name_or_idx): If multiple definitions added for the same index/name, the last added takes precedence. """ d = None - if self._is_hashable(name_or_idx): - d = self._get_definition_by_alias(name_or_idx) - if d is None: - if isinstance(name_or_idx, int): - d = self._get_definition_by_idx(name_or_idx) - if d is None: - # search in alias-dict again with the index converted to a string - d = self._get_definition_by_alias(str(name_or_idx)) - if isinstance(name_or_idx, str): - try: - # Numbers are not allowed as names, so it could be an index as string - idx_from_str = int(name_or_idx) - d = self._get_definition_by_idx(idx_from_str) - if d is None: - # search in alias-dict again with the string converted to an index - d = self._get_definition_by_alias(str(name_or_idx)) - except ValueError: - d = self._get_definition_by_name(name_or_idx) + if isinstance(name_or_idx, int): + d = self._get_definition_by_idx(name_or_idx) + if isinstance(name_or_idx, str): + try: + # Numbers are not allowed as names, so it could be an index as string + d = self._get_definition_by_idx(int(name_or_idx)) + except ValueError: + d = self._get_definition_by_name(name_or_idx) return d def _get_definition_by_idx(self, idx): @@ -405,22 +334,6 @@ def _get_definition_by_name(self, name): LOGGER.warning(f"'{name}' is outdated! Use '{definition.name}' instead.") return definition - def _get_definition_by_alias(self, alias): - """ - Retrieve a definition by its alias (case-insensitive when using strings). - - Args: - alias (Hashable): Alias for a definition. - - Returns: - LuxtronikDefinition | None: The matching definition, or None if not found. - - Note: - If multiple definitions added for the same alias, the last added takes precedence. - """ - alias = alias.lower() if isinstance(alias, str) else alias - return self._alias_dict.get(alias, None) - ############################################################################### # LuxtronikDefinitionsList @@ -430,11 +343,7 @@ class LuxtronikDefinitionsList: """ Container for Luxtronik definitions. - Provides lookup by index, name or alias. - - To use aliases, they must first be registered here (globally = valid for - all newly created dictionaries) or within the `LuxtronikDefinitionsDictionary` - (locally = only valid for that dictionary). + Provides lookup by index or name. """ def _init_instance(self, name, offset, default_data_type, version): @@ -522,27 +431,6 @@ def create_unknown_definition(self, index): """ return LuxtronikDefinition.unknown(index, self._name, self._offset, self._default_data_type) - def register_alias(self, def_name_or_idx, alias): - """ - Register an alias (globally) that references a definition specified by - name, index, or the definition object. - - Args: - def_name_or_idx (str | int | LuxtronikDefinition): - Name, index, or definition to alias. - alias (any): (Hashable) Alias key to register (str will be normalized). - - Returns: - LuxtronikDefinition | None: The resolved definition - when registration succeeded, otherwise None. - """ - # "local" registration to be able to find the definition again - definition = self._lookup.register_alias(def_name_or_idx, alias) - # "global" registration that is used in newly created definition-dictionaries - if definition is not None: - definition.aliases.append(alias) - return definition - @property def name(self): return self._name diff --git a/luxtronik/shi/README.md b/luxtronik/shi/README.md index 65f6eb95..a32fb39f 100644 --- a/luxtronik/shi/README.md +++ b/luxtronik/shi/README.md @@ -17,12 +17,11 @@ Not every function can be explained in detail or even listed in the readme file. 3. [Read](#read) 4. [Write](#write) 5. [Collect](#collect) -2. [Using aliases](#using-aliases) -3. [Alternative use cases](#alternative-use-cases) +2. [Alternative use cases](#alternative-use-cases) 1. [Latest or specific version](#latest-or-specific-version) 2. [Trial-and-error mode](#trial-and-error-mode) -4. [Customization](#customization) -5. [Implementation Details](#implementation-details) +3. [Customization](#customization) +4. [Implementation Details](#implementation-details) ## Common Usage @@ -204,43 +203,6 @@ Attention: It is technically possible to collect the same instance of a data field multiple times. However, this practice is discouraged, as it can lead to ambiguous behavior. During a write operation, the value is not extracted from the field until the actual transmission occurs. Similarly, during a read, the results are written into the field’s single memory location multiple times. If the same field is collected multiple times, the last value assigned before transmission will take precedence and overwrite any previous ones. To ensure clarity and avoid unintended side effects, each field should be collected only once per transmission, or using multiple instances of the same data field. This ensures that each collected field maintains its own memory space and avoids conflicts during read or write operations. By using distinct field objects, values can be managed independently, and the outcome of each transmission remains predictable and clearly scoped. -## Using aliases - -Instead of the predefined names, any (hashable) values can also be used. -However, these must be registered beforehand. This also makes it possible -to "overwrite" existing names or register indices. - -There are two ways to register: - -**global:** - -The aliases are registered in the `LuxtronikDefinitionsList`. They are then available in every newly created data vector. - -```python -from luxtronik.shi import create_modbus_tcp - -shi = create_modbus_tcp('your.lux.ip.addr') -shi.inputs.register_alias(input_definition_to_alias, any_hashable_alias) - -data = shi.read() -print(data.inputs[any_hashable_alias].value) -``` - -**local:** - -The aliases can also only be registered in a specific data vector. - -```python -from luxtronik.shi import create_modbus_tcp - -shi = create_modbus_tcp('your.lux.ip.addr') - -data = shi.read() -data.holdings.register_alias(holding_definition_to_alias, any_hashable_alias) - -print(data.holdings[any_hashable_alias].value) -``` - ## Alternative use cases ### Latest or specific version diff --git a/luxtronik/shi/vector.py b/luxtronik/shi/vector.py index a4550ae8..62c5fe99 100644 --- a/luxtronik/shi/vector.py +++ b/luxtronik/shi/vector.py @@ -75,7 +75,7 @@ def empty(cls, version=LUXTRONIK_LATEST_SHI_VERSION, safe=True): def version(self): return self._version - def add(self, def_field_name_or_idx, alias=None): + def add(self, def_field_name_or_idx): """ Adds an additional version-dependent field (= included in class variable `cls.definitions` and is valid for `self.version`) to this data vector. @@ -85,7 +85,6 @@ def add(self, def_field_name_or_idx, alias=None): Args: def_field_name_or_idx (LuxtronikDefinition | Base | str | int): Field to add. Either by definition, name or index, or the field itself. - alias (Hashable | None): Alias, which can be used to access the field again. Returns: Base | None: The added field object if this could be added or @@ -112,7 +111,7 @@ def add(self, def_field_name_or_idx, alias=None): if field is None: field = definition.create_field() self._read_blocks_up_to_date = False - self._data.add_sorted(definition, field, alias) + self._data.add_sorted(definition, field) return field return None @@ -128,4 +127,3 @@ def update_read_blocks(self): for definition, field in self._data.pairs: self._read_blocks.collect(definition, field) self._read_blocks_up_to_date = True - diff --git a/tests/shi/test_shi_vector.py b/tests/shi/test_shi_vector.py index 8537f965..79d3d033 100644 --- a/tests/shi/test_shi_vector.py +++ b/tests/shi/test_shi_vector.py @@ -321,56 +321,6 @@ def test_set(self): assert field_9.value == 6 assert field_9.write_pending - def test_alias(self): - TEST_DEFINITIONS.register_alias('field_9a', 10) - data_vector = DataVectorTest(parse_version("1.1.2")) - - # global alias - def_9a = TEST_DEFINITIONS[10] - field_9a = data_vector[10] - assert def_9a is not None - assert field_9a is not None - assert field_9a.name == 'field_9a' - - # local alias - data_vector.register_alias(5, 6) - def_5 = TEST_DEFINITIONS[6] - field_5 = data_vector[6] - assert def_5 is None - assert field_5 is not None - assert field_5.name == 'field_5' - - # alias of alias - data_vector.register_alias(6, 7) - field_5 = data_vector[7] - assert field_5 is not None - assert field_5.name == 'field_5' - - # alias of field - data_vector.register_alias(field_9a, 11) - field_9a = data_vector[11] - assert field_9a is not None - assert field_9a.name == 'field_9a' - - # alias of field - field = data_vector.register_alias(2, 7) - assert field is None - - # alias 7 still valid - field_5 = data_vector[7] - assert field_5 is not None - assert field_5.name == 'field_5' - - def test_global_alias(self): - data_vector = DataVectorTest(parse_version("1.1.2")) - - # persistent alias - def_9a = TEST_DEFINITIONS[10] - field_9a = data_vector[10] - assert def_9a is not None - assert field_9a is not None - assert field_9a.name == 'field_9a' - def test_read_blocks(self): data_vector = DataVectorTest(parse_version("1.1.2")) assert not data_vector._read_blocks_up_to_date @@ -416,7 +366,7 @@ def test_version_none(self): assert len(data_vector) == 3 data_vector.add("field_invalid") assert len(data_vector) == 3 - data_vector.add(10) # field_9a alias + data_vector.add('field_9a') assert len(data_vector) == 4 assert len(data_vector.data.pairs) == 4 diff --git a/tests/test_collections.py b/tests/test_collections.py index 21392835..a085b0f9 100644 --- a/tests/test_collections.py +++ b/tests/test_collections.py @@ -281,7 +281,7 @@ def create_instance(self): "type": Base, "names": ["base3"], }, "test", 0) - d.add(b, b.create_field(), "base4") + d.add(b, b.create_field()) return d, u, f def test_len(self): @@ -304,8 +304,6 @@ def test_get_contains(self): assert d["base2"].name == "base2" assert "base3" in d assert d.get("base3").name == "base3" - assert "base4" in d - assert d.get("base4").name == "base3" assert u in d assert d[u].name == "unknown_test_2" assert f in d @@ -366,24 +364,3 @@ def test_items(self): class MyTestClass: pass - - def test_alias(self): - d, u, f = self.create_instance() - my = self.MyTestClass() - - d.register_alias(0, "abc") - assert d["abc"] is d[0] - - field = d.register_alias("unknown_test_1", 6) - assert d[6] is field - - field = d.register_alias(u, my) - assert d[my] is d[u] - - d.register_alias(f, my) - assert d[my] is not d[u] - assert d[my] is d[f] - - field = d.register_alias(9, my) - assert field is None - assert d[my] is d[f] \ No newline at end of file diff --git a/tests/test_definitions.py b/tests/test_definitions.py index 43a16f22..7edd7a1e 100644 --- a/tests/test_definitions.py +++ b/tests/test_definitions.py @@ -43,7 +43,6 @@ def test_init(self): assert definition.offset == 20 assert definition.addr == 20 + self.TEST_DATA['index'] assert definition.num_bits == 16 - assert definition.aliases == [] assert definition.since == (1, 1, 0, 0) assert definition.until == (3, 16, 7, 0) @@ -71,7 +70,6 @@ def test_init_unknown(self): assert definition.type_name == 'foo' assert definition.offset == 30 assert definition.addr == 30 + 2 - assert definition.aliases == [] assert definition.since is None assert definition.until is None @@ -196,102 +194,6 @@ def test_add_get(self): d_out = def_dict.get(list()) assert d_out is None - def test_alias(self): - def_dict = LuxtronikDefinitionsDictionary() - - # add via "global" - d1 = LuxtronikDefinition.unknown(1, 'def', 0) - d1._aliases = [0xDEAD, 123456, 'Unknown_Def_2'] - def_dict.add(d1) - assert len(def_dict._alias_dict) == 3 - - # override via "global" - d2 = LuxtronikDefinition.unknown(2, 'def', 0) - d2._aliases = [123456] - def_dict.add(d2, 'foo') - assert len(def_dict._alias_dict) == 4 - - # add nothing - d_alias = def_dict.register_alias(d2, None) - assert d_alias is None - assert len(def_dict._alias_dict) == 4 - - # add via method - d_alias = def_dict.register_alias('UNKNOWN_DEF_1', 'abc') - assert d_alias == d1 - assert len(def_dict._alias_dict) == 5 - - # use a number as alias (which covers the original index) - d_alias = def_dict.register_alias(2, 1) - assert d_alias == d2 - assert len(def_dict._alias_dict) == 6 - - # add nothing - d_alias = def_dict.register_alias(3, 0xAFFE) - assert d_alias is None - assert len(def_dict._alias_dict) == 6 - - # get via alias (which covers the original index) - assert 1 in def_dict - d_out = def_dict.get(1) - assert d_out == d2 - - # get via index - assert 2 in def_dict - d_out = def_dict.get(2) - assert d_out == d2 - - # get non-existing - assert 3 not in def_dict - d_out = def_dict.get(3) - assert d_out is None - - # get via name - assert 'unknown_def_1' in def_dict - d_out = def_dict.get('unknown_def_1') - assert d_out == d1 - - # get via name - assert 'foo' in def_dict - d_out = def_dict.get('foo') - assert d_out == d2 - - # get via alias (which covers the original name) - assert 'unknown_def_2' in def_dict - d_out = def_dict.get('unknown_def_2') - assert d_out == d1 - - # get via string-alias - assert 'abc' in def_dict - d_out = def_dict.get('abc') - assert d_out == d1 - - # get via hex-alias - assert 0xDEAD in def_dict - d_out = def_dict.get(0xDEAD) - assert d_out == d1 - - # get via int-alias - assert 123456 in def_dict - d_out = def_dict.get(123456) - assert d_out == d2 - - # get non-existing - assert 0xBEEF not in def_dict - d_out = def_dict.get(0xBEEF) - assert d_out is None - - # add via definition object - d_alias = def_dict.register_alias(d1, 'foo') - assert d_alias == d1 - assert len(def_dict._alias_dict) == 6 - - # get via overwritten alias - assert 'foo' in def_dict - d_out = def_dict.get('foo') - assert d_out == d1 - - class TestDefinitionsList: def_list = [ @@ -404,25 +306,9 @@ def test_create(self): assert definition.type_name == 'foo' assert definition.offset == 100 assert definition.addr == 100 + 4 - assert definition.aliases == [] assert definition.since is None assert definition.until is None - def test_alias(self): - definitions = LuxtronikDefinitionsList(self.def_list, 'foo', 100, '') - - # add alias - d = definitions.register_alias(5, 'bar') - assert d.name == 'field_5' - - # get via alias - d = definitions[5] - assert d.name == 'field_5' - - # add nothing - d = definitions.register_alias(4, 'baz') - assert d is None - def test_add(self): definitions = LuxtronikDefinitionsList(self.def_list, 'foo', 100, '') assert len(definitions) == 4