Skip to content

Commit

Permalink
Fix numbers for module defined group objects (#319)
Browse files Browse the repository at this point in the history
* Fix numbers for module defined group objects

* upadte test project

* fix tests

* stop iteration when module instance found
  • Loading branch information
farmio authored Nov 2, 2023
1 parent a270814 commit bada418
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 20 deletions.
4 changes: 2 additions & 2 deletions test/resources/stubs/module-definition-test.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
},
"1.1.1/MD-2_M-2_MI-1_O-2-1_R-1": {
"name": "IstWert",
"number": 1,
"number": 41,
"text": "Kanal B: Küche",
"function_text": "Temperaturwert empfangen",
"description": "Beschreibung KO 41",
Expand All @@ -123,7 +123,7 @@
},
"1.1.1/MD-2_M-2_MI-1_O-2-2_R-3": {
"name": "SollWert",
"number": 2,
"number": 42,
"text": "Kanal B: Küche",
"function_text": "Sollwert vorgeben",
"description": "Beschreibung KO 42 (U-Flag)",
Expand Down
120 changes: 106 additions & 14 deletions test/resources/stubs/xknx_test_project.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"info": {
"project_id": "P-0242",
"name": "xknx test project",
"last_modified": "2023-09-29T21:48:57.7711452Z",
"last_modified": "2023-11-02T06:19:30.3859365Z",
"group_address_style": "ThreeLevel",
"guid": "7aceb083-274e-4568-806f-b8f9df992873",
"created_by": "ETS5",
Expand Down Expand Up @@ -401,8 +401,8 @@
},
"1.1.7/MD-2_M-20_MI-1_O-2-2_R-3": {
"name": "RTR1 - 2 - Controller",
"number": 2,
"text": "RTR 1 (...) - Ausgang",
"number": 935,
"text": "RTR 1 (name in application) - Ausgang",
"function_text": "Command value - Heating",
"description": "",
"device_address": "1.1.7",
Expand All @@ -428,8 +428,8 @@
},
"1.1.7/MD-2_M-20_MI-1_O-5-0_R-17": {
"name": "RTR1 - 0 - RSM",
"number": 0,
"text": "RTR 1 (...) - Eingang",
"number": 333,
"text": "RTR 1 (name in application) - Eingang",
"function_text": "Setpoint temperature - Active operating mode",
"description": "",
"device_address": "1.1.7",
Expand All @@ -455,8 +455,8 @@
},
"1.1.7/MD-2_M-20_MI-1_O-5-10_R-18": {
"name": "RTR1 - 10 - RSM",
"number": 10,
"text": "RTR 1 (...) - Ausgang",
"number": 343,
"text": "RTR 1 (name in application) - Ausgang",
"function_text": "Setpoint temperature - Active operating mode - Status",
"description": "",
"device_address": "1.1.7",
Expand All @@ -482,8 +482,8 @@
},
"1.1.7/MD-2_M-20_MI-1_O-7-1_R-45": {
"name": "RTR1 - 1 - Sensor",
"number": 1,
"text": "RTR 1 (...) - Eingang",
"number": 1438,
"text": "RTR 1 (name in application) - Eingang",
"function_text": "Room temperature - Measured value 1",
"description": "",
"device_address": "1.1.7",
Expand All @@ -506,6 +506,60 @@
"group_address_links": [
"7/1/2"
]
},
"1.1.7/MD-2_M-4_MI-1_O-7-1_R-45": {
"name": "RTR1 - 1 - Sensor",
"number": 1448,
"text": "RTR 2 (...) - Eingang",
"function_text": "Room temperature - Measured value 1",
"description": "",
"device_address": "1.1.7",
"channel": "MD-2_M-4_MI-1_CH-81",
"dpts": [
{
"main": 9,
"sub": 1
}
],
"object_size": "2 Bytes",
"flags": {
"read": false,
"write": true,
"communication": true,
"update": true,
"read_on_init": false,
"transmit": true
},
"group_address_links": [
"2/0/1"
]
},
"1.1.7/O-3_R-3": {
"name": "Objekt",
"number": 3,
"text": "Manual operation - Input/Output",
"function_text": "Temporary status indication",
"description": "no-channel GO",
"device_address": "1.1.7",
"channel": null,
"dpts": [
{
"main": 1,
"sub": 2
}
],
"object_size": "1 Bit",
"flags": {
"read": true,
"write": true,
"communication": true,
"update": true,
"read_on_init": false,
"transmit": true
},
"group_address_links": [
"2/0/0"
]
}
},
"topology": {
Expand Down Expand Up @@ -600,7 +654,9 @@
"1.1.7/MD-2_M-20_MI-1_O-2-2_R-3",
"1.1.7/MD-2_M-20_MI-1_O-5-0_R-17",
"1.1.7/MD-2_M-20_MI-1_O-5-10_R-18",
"1.1.7/MD-2_M-20_MI-1_O-7-1_R-45"
"1.1.7/MD-2_M-20_MI-1_O-7-1_R-45",
"1.1.7/MD-2_M-4_MI-1_O-7-1_R-45",
"1.1.7/O-3_R-3"
],
"channels": {
"MD-1_M-13_MI-1_CH-80": {
Expand Down Expand Up @@ -629,15 +685,15 @@
},
"MD-2_M-20_MI-1_CH-81": {
"identifier": "MD-2_M-20_MI-1_CH-81",
"name": "Raumtemperaturregler 1 (...)"
"name": "Raumtemperaturregler 1 (name in application)"
},
"MD-2_M-4_MI-1_CH-81": {
"identifier": "MD-2_M-4_MI-1_CH-81",
"name": "Raumtemperaturregler 2 (...)"
"name": "channel name in properties"
},
"MD-2_M-9_MI-1_CH-81": {
"identifier": "MD-2_M-9_MI-1_CH-81",
"name": "Raumtemperaturregler 3 (...)"
"name": "in application and properties"
},
"MD-2_M-10_MI-1_CH-81": {
"identifier": "MD-2_M-10_MI-1_CH-81",
Expand Down Expand Up @@ -757,6 +813,40 @@
"description": "",
"comment": ""
},
"2/0/0": {
"name": "channel-test",
"identifier": "GA-19",
"raw_address": 4096,
"address": "2/0/0",
"project_uid": 66,
"dpt": {
"main": 1,
"sub": 2
},
"data_secure": true,
"communication_object_ids": [
"1.1.7/O-3_R-3"
],
"description": "",
"comment": ""
},
"2/0/1": {
"name": "Temperature",
"identifier": "GA-18",
"raw_address": 4097,
"address": "2/0/1",
"project_uid": 65,
"dpt": {
"main": 9,
"sub": 1
},
"data_secure": true,
"communication_object_ids": [
"1.1.7/MD-2_M-4_MI-1_O-7-1_R-45"
],
"description": "",
"comment": ""
},
"2/0/6": {
"name": "Windalarm",
"identifier": "GA-7",
Expand Down Expand Up @@ -978,7 +1068,9 @@
"address_start": 4096,
"address_end": 4351,
"group_addresses": [
"2/0/6"
"2/0/6",
"2/0/1",
"2/0/0"
],
"comment": "",
"group_ranges": {}
Expand Down
Binary file modified test/resources/xknx_test_project.knxproj
Binary file not shown.
4 changes: 3 additions & 1 deletion test/xml/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def test_parse_project_ets5():
parser = XMLParser(knx_project_contents)
parser.parse()

assert len(parser.group_addresses) == 17
assert len(parser.group_addresses) == 19
parsed_gas = {ga.address for ga in parser.group_addresses}
assert len(parsed_gas) == len(parser.group_addresses)
assert parsed_gas == {
Expand All @@ -47,6 +47,8 @@ def test_parse_project_ets5():
"1/0/3",
"1/0/4",
"1/0/5",
"2/0/0",
"2/0/1",
"2/0/6",
"2/1/1",
"2/1/2",
Expand Down
1 change: 1 addition & 0 deletions xknxproject/loader/application_program_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ def parse_com_object(
update_flag=parse_xml_flag(elem.get("UpdateFlag"), False),
read_on_init_flag=parse_xml_flag(elem.get("ReadOnInitFlag"), False),
datapoint_types=parse_dpt_types(elem.get("DatapointType")),
base_number_argument_ref=elem.get("BaseNumber"),
)

@staticmethod
Expand Down
37 changes: 35 additions & 2 deletions xknxproject/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ def module_instance_arguments(self) -> Iterator[ModuleInstanceArgument]:
for _module_instance in self.module_instances:
yield from _module_instance.arguments

def complete_channel_placeholders(self) -> None:
def _complete_channel_placeholders(self) -> None:
"""Replace placeholders in channel names with module instance arguments."""
for channel in self.channels:
if not (
Expand All @@ -197,6 +197,31 @@ def complete_channel_placeholders(self) -> None:
f"{{{{{argument.name}}}}}", argument.value
)

def _add_com_object_instance_numbers(self) -> None:
"""Add module base object number to merged ComObjectInstanceRef."""
for coir in self.com_object_instance_refs:
if (
coir.base_number_argument_ref is None
or not coir.ref_id.startswith("MD-")
or coir.number is None # only for type safety
):
continue
_module_instance = next(
mi
for mi in self.module_instances
if coir.ref_id.startswith(f"{mi.identifier}_")
)
coir.number += next(
int(arg.value)
for arg in _module_instance.arguments
if arg.ref_id == coir.base_number_argument_ref
)

def apply_module_instance_arguments(self) -> None:
"""Apply module instance arguments."""
self._complete_channel_placeholders()
self._add_com_object_instance_numbers()


@dataclass
class ChannelNode:
Expand Down Expand Up @@ -254,8 +279,10 @@ class ComObjectInstanceRef:

# only available form ComObject and ComObjectRef
name: str | None = None
number: int | None = None
object_size: str | None = None
# only available form ComObject
base_number_argument_ref: str | None = None # optional in ComObject
number: int | None = None # required in ComObject

def resolve_com_object_ref_id(
self, application_program_ref: str, knx_proj_contents: KNXProjContents
Expand Down Expand Up @@ -294,6 +321,7 @@ def merge_from_application(self, com_object: ComObject | ComObjectRef) -> None:
self.datapoint_types = com_object.datapoint_types
if isinstance(com_object, ComObject):
self.number = com_object.number
self.base_number_argument_ref = com_object.base_number_argument_ref


@dataclass
Expand All @@ -314,6 +342,7 @@ class ComObject:
"update_flag",
"read_on_init_flag",
"datapoint_types",
"base_number_argument_ref",
)

# all items required in the XML
Expand All @@ -330,6 +359,10 @@ class ComObject:
update_flag: bool # "UpdateFlag" - knx:Enable_t
read_on_init_flag: bool # "ReadOnInitFlag" - knx:Enable_t
datapoint_types: list[DPTType] # "DataPointType" - knx:IDREFS - optional
# "BaseNumber" - knx:IDREF - optional - schema version >= 20
# ModuleArgument identifier that holds value to add for
# communication object number of ComObjectInstanceRef
base_number_argument_ref: str | None


@dataclass
Expand Down
2 changes: 1 addition & 1 deletion xknxproject/xml/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ def _load(self, language: str | None) -> None:
language_code=self.language_code,
)
for device in self.devices:
device.complete_channel_placeholders()
device.apply_module_instance_arguments()

def _sort(self) -> None:
"""Sort loaded structures as XML content is sorted by creation time."""
Expand Down

0 comments on commit bada418

Please sign in to comment.