Skip to content

Commit

Permalink
Parse ETS Functions (#254)
Browse files Browse the repository at this point in the history
* add room functions

* rename to singular form and fix some issues.

* Missing docstring in public method

* fix ci

* missing function docstring

* add functions to old tests

* fix typo

* test stub

* correct indent

* correct isort

* add identifier,name,role,ref_id,project_uid to group_addresse

* add usage_text to functions

* add address to function group addresses

* remove ref_id from group_address

* restore tox.ini

* move functions to root level

* use next to find ga address

---------

Co-authored-by: Bacher, Dominik <d.bacher@schubert-system-elektronik.de>
  • Loading branch information
bacherd and Bacher, Dominik authored Jul 25, 2023
1 parent 0d5d5e8 commit 4011b76
Show file tree
Hide file tree
Showing 13 changed files with 361 additions and 24 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Currently, xknxproject supports extracting (password protected) ETS 4, 5 and 6 p
* Group Addresses and their DPT type if set
* The application programs communication objects, their respective flags and the DPT Type
* Location information of devices (in which rooms they are)
* Functions assigned to rooms

Caution: Loading a middle-sized project with this tool takes about 1.5 seconds. For bigger projects this might as well be >3s.

Expand Down
14 changes: 9 additions & 5 deletions test/resources/stubs/module-definition-test.json
Original file line number Diff line number Diff line change
Expand Up @@ -377,11 +377,15 @@
"description": "office description",
"project_uid": 22,
"devices": [],
"spaces": {}
"spaces": {},
"functions": []
}
}
},
"functions": []
}
}
},
"functions": []
}
}
}
},
"functions": {}
}
20 changes: 13 additions & 7 deletions test/resources/stubs/test_project-ets4.json
Original file line number Diff line number Diff line change
Expand Up @@ -273,9 +273,11 @@
"devices": [
"0.0.2"
],
"spaces": {}
"spaces": {},
"functions": []
}
}
},
"functions": []
},
"Second floor": {
"type": "Floor",
Expand All @@ -300,11 +302,15 @@
"devices": [
"0.0.1"
],
"spaces": {}
"spaces": {},
"functions": []
}
}
},
"functions": []
}
}
},
"functions": []
}
}
}
},
"functions": {}
}
131 changes: 131 additions & 0 deletions test/resources/stubs/testprojekt-ets6-functions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
{
"info": {
"project_id": "P-05C0",
"name": "Minimal-Example",
"last_modified": "2023-07-07T12:41:11.4132414Z",
"group_address_style": "ThreeLevel",
"guid": "efabc0f9-4e81-440d-a236-b80913e85730",
"created_by": "ETS6",
"schema_version": "22",
"tool_version": "6.1.5686.0",
"xknxproject_version": "3.2.0",
"language_code": "de-DE"
},
"communication_objects": {},
"topology": {
"0": {
"name": "",
"description": null,
"lines": {
"0": {
"name": "",
"description": null,
"devices": [],
"medium_type": "KNXnet/IP (IP)"
}
}
},
"1": {
"name": "",
"description": null,
"lines": {
"0": {
"name": "",
"description": null,
"devices": [],
"medium_type": "KNXnet/IP (IP)"
},
"1": {
"name": "",
"description": null,
"devices": [],
"medium_type": "Twisted Pair (TP)"
}
}
}
},
"devices": {},
"group_addresses": {
"0/0/1": {
"name": "Schalten",
"identifier": "GA-1",
"raw_address": 1,
"address": "0/0/1",
"project_uid": 14,
"dpt": {
"main": 1,
"sub": 1
},
"communication_object_ids": [],
"description": "Livingroom LivingroomLight"
},
"0/0/2": {
"name": "Status",
"identifier": "GA-2",
"raw_address": 2,
"address": "0/0/2",
"project_uid": 16,
"dpt": {
"main": 1,
"sub": 1
},
"communication_object_ids": [],
"description": "Livingroom LivingroomLight"
}
},
"locations": {
"Minimal-Example": {
"type": "Building",
"identifier": "P-05C0-0_BP-1",
"name": "Minimal-Example",
"usage_id": null,
"usage_text": "",
"number": "",
"description": "",
"project_uid": 9,
"devices": [],
"spaces": {
"Livingroom": {
"type": "Room",
"identifier": "P-05C0-0_BP-2",
"name": "Livingroom",
"usage_id": "SU-4",
"usage_text": "Wohnzimmer",
"number": "",
"description": "",
"project_uid": 10,
"devices": [],
"spaces": {},
"functions": [
"F-1"
]
}
},
"functions": []
}
},
"functions": {
"F-1": {
"function_type": "FT-1",
"group_addresses": {
"0/0/1": {
"address": "0/0/1",
"name": "",
"project_uid": 15,
"role": "SwitchOnOff"
},
"0/0/2": {
"address": "0/0/2",
"name": "",
"project_uid": 17,
"role": "InfoOnOff"
}
},
"identifier": "F-1",
"name": "LivingroomLight",
"project_uid": 11,
"space_id": "P-05C0-0_BP-2",
"usage_text": "switchable light"
}
}
}
8 changes: 5 additions & 3 deletions test/resources/stubs/xknx_test_project.json
Original file line number Diff line number Diff line change
Expand Up @@ -669,7 +669,9 @@
"description": "",
"project_uid": 6,
"devices": [],
"spaces": {}
"spaces": {},
"functions": []
}
}
}
},
"functions": {}
}
Binary file not shown.
10 changes: 10 additions & 0 deletions test/test_knxproj.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,13 @@ def test_parse_project_modules():
)
project = knxproj.parse()
assert_stub(project, "module-definition-test.json")


def test_parse_project_ets6_with_functions():
"""Test parsing of ETS6 project with room functions."""
knxproj = XKNXProj(
RESOURCES_PATH / "testprojekt-ets6-functions.knxproj",
language="De", # resolves to "de-DE" in parser for knx_master.xml
)
project = knxproj.parse()
assert_stub(project, "testprojekt-ets6-functions.json")
18 changes: 16 additions & 2 deletions xknxproject/loader/knx_master_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ def load(
knx_proj_contents: KNXProjContents,
knx_master_file: Path,
language: str | None,
) -> tuple[dict[str, str], dict[str, str], str | None]:
) -> tuple[dict[str, str], dict[str, str], str | None, dict[str, str]]:
"""Load KNX master data."""
manufacturer_mapping: dict[str, str] = {}
space_usage_mapping: dict[str, str] = {}
product_languages: list[str] = [] # eg. "en-US", "de-DE", "fr-FR"
language_code: str | None = None
function_type_mapping: dict[str, str] = {}

with knx_master_file.open(mode="rb") as master_xml:
tree = ElementTree.parse(master_xml)
Expand All @@ -44,6 +45,14 @@ def load(
for language_node in tree.findall(".//{*}ProductLanguages/{*}Language"):
product_languages.append(language_node.get("Identifier", ""))

for function_type_node in tree.findall(
".//{*}FunctionTypes/{*}FunctionType"
):
identifier = function_type_node.get("Id", "")
function_type_mapping[identifier] = function_type_node.get(
"Text", ""
)

if language is not None:
language_code = KNXMasterLoader.get_language_code(
language, product_languages
Expand All @@ -65,7 +74,12 @@ def load(
) is not None:
space_usage_mapping[_ref_id] = translation_node.get("Text", "")

return manufacturer_mapping, space_usage_mapping, language_code
return (
manufacturer_mapping,
space_usage_mapping,
language_code,
function_type_mapping,
)

@staticmethod
def get_language_code(language: str, product_languages: list[str]) -> str | None:
Expand Down
Loading

0 comments on commit 4011b76

Please sign in to comment.