Home Assistant Unofficial Reference 2024.12.1
project.py
Go to the documentation of this file.
1 """Handle KNX project data."""
2 
3 from __future__ import annotations
4 
5 from dataclasses import dataclass
6 import logging
7 from typing import Final
8 
9 from xknx import XKNX
10 from xknx.dpt import DPTBase
11 from xknx.telegram.address import DeviceAddressableType, GroupAddress, GroupAddressType
12 from xknxproject import XKNXProj
13 from xknxproject.models import (
14  Device,
15  DPTType,
16  GroupAddress as GroupAddressModel,
17  GroupAddressStyle as XknxProjectGroupAddressStyle,
18  KNXProject as KNXProjectModel,
19  ProjectInfo,
20 )
21 
22 from homeassistant.components.file_upload import process_uploaded_file
23 from homeassistant.config_entries import ConfigEntry
24 from homeassistant.core import HomeAssistant
25 from homeassistant.helpers.storage import Store
26 
27 from .const import DOMAIN
28 
29 _LOGGER = logging.getLogger(__name__)
30 
31 STORAGE_VERSION: Final = 1
32 STORAGE_KEY: Final = f"{DOMAIN}/knx_project.json"
33 
34 
35 @dataclass
37  """Group address info for runtime usage."""
38 
39  address: str
40  name: str
41  description: str
42  dpt_main: int | None
43  dpt_sub: int | None
44  transcoder: type[DPTBase] | None
45 
46 
47 def _create_group_address_info(ga_model: GroupAddressModel) -> GroupAddressInfo:
48  """Convert GroupAddress dict value into GroupAddressInfo instance."""
49  dpt = ga_model["dpt"]
50  transcoder = DPTBase.transcoder_by_dpt(dpt["main"], dpt.get("sub")) if dpt else None
51  return GroupAddressInfo(
52  address=ga_model["address"],
53  name=ga_model["name"],
54  description=ga_model["description"],
55  transcoder=transcoder,
56  dpt_main=dpt["main"] if dpt else None,
57  dpt_sub=dpt["sub"] if dpt else None,
58  )
59 
60 
61 class KNXProject:
62  """Manage KNX project data."""
63 
64  loaded: bool
65  devices: dict[str, Device]
66  group_addresses: dict[str, GroupAddressInfo]
67  info: ProjectInfo | None
68 
69  def __init__(
70  self,
71  hass: HomeAssistant,
72  entry: ConfigEntry,
73  ) -> None:
74  """Initialize project data."""
75  self.hasshass = hass
76  self._store_store = Store[KNXProjectModel](hass, STORAGE_VERSION, STORAGE_KEY)
77 
78  self.initial_stateinitial_state()
79 
80  def initial_state(self) -> None:
81  """Set initial state for project data."""
82  self.loadedloaded = False
83  self.devicesdevices = {}
84  self.group_addressesgroup_addresses = {}
85  self.infoinfo = None
86 
87  async def load_project(
88  self, xknx: XKNX, data: KNXProjectModel | None = None
89  ) -> None:
90  """Load project data from storage."""
91  if project := data or await self._store_store.async_load():
92  self.devicesdevices = project["devices"]
93  self.infoinfo = project["info"]
94  GroupAddress.address_format = self.get_address_formatget_address_format()
95  xknx.group_address_dpt.clear()
96  xknx_ga_dict: dict[DeviceAddressableType, DPTType] = {}
97 
98  for ga_model in project["group_addresses"].values():
99  ga_info = _create_group_address_info(ga_model)
100  self.group_addressesgroup_addresses[ga_info.address] = ga_info
101  if (dpt_model := ga_model.get("dpt")) is not None:
102  xknx_ga_dict[ga_model["address"]] = dpt_model
103 
104  xknx.group_address_dpt.set(xknx_ga_dict)
105 
106  _LOGGER.debug(
107  "Loaded KNX project data with %s group addresses from storage",
108  len(self.group_addressesgroup_addresses),
109  )
110  self.loadedloaded = True
111 
113  self, xknx: XKNX, file_id: str, password: str
114  ) -> None:
115  """Process an uploaded project file."""
116 
117  def _parse_project() -> KNXProjectModel:
118  with process_uploaded_file(self.hasshass, file_id) as file_path:
119  xknxproj = XKNXProj(
120  file_path,
121  password=password,
122  language=self.hasshass.config.language,
123  )
124  return xknxproj.parse()
125 
126  project = await self.hasshass.async_add_executor_job(_parse_project)
127  await self._store_store.async_save(project)
128  await self.load_projectload_project(xknx, data=project)
129 
130  async def remove_project_file(self) -> None:
131  """Remove project file from storage."""
132  await self._store_store.async_remove()
133  self.initial_stateinitial_state()
134 
135  async def get_knxproject(self) -> KNXProjectModel | None:
136  """Load the project file from local storage."""
137  return await self._store_store.async_load()
138 
139  def get_address_format(self) -> GroupAddressType:
140  """Return the address format for group addresses used in the project."""
141  if self.infoinfo:
142  match self.infoinfo["group_address_style"]:
143  case XknxProjectGroupAddressStyle.TWOLEVEL.value:
144  return GroupAddressType.SHORT
145  case XknxProjectGroupAddressStyle.FREE.value:
146  return GroupAddressType.FREE
147  return GroupAddressType.LONG
None __init__(self, HomeAssistant hass, ConfigEntry entry)
Definition: project.py:73
None load_project(self, XKNX xknx, KNXProjectModel|None data=None)
Definition: project.py:89
None process_project_file(self, XKNX xknx, str file_id, str password)
Definition: project.py:114
KNXProjectModel|None get_knxproject(self)
Definition: project.py:135
Iterator[Path] process_uploaded_file(HomeAssistant hass, str file_id)
Definition: __init__.py:36
GroupAddressInfo _create_group_address_info(GroupAddressModel ga_model)
Definition: project.py:47
None async_load(HomeAssistant hass)
None async_remove(HomeAssistant hass, str intent_type)
Definition: intent.py:90
None async_save(self, _T data)
Definition: storage.py:424