diff --git a/custom_components/apti/const.py b/custom_components/apti/const.py index 967e67c..823db1d 100644 --- a/custom_components/apti/const.py +++ b/custom_components/apti/const.py @@ -8,7 +8,7 @@ from homeassistant.const import Platform DOMAIN = "apti" -VERSION = "1.0.2" +VERSION = "1.0.3" PLATFORMS: list[Platform] = [ Platform.SENSOR diff --git a/custom_components/apti/helper.py b/custom_components/apti/helper.py index a4f4b89..99bc0c6 100644 --- a/custom_components/apti/helper.py +++ b/custom_components/apti/helper.py @@ -1,6 +1,8 @@ """Defines helper function.""" from typing import Any +import aiofiles +import json import re from .const import LOGGER @@ -32,3 +34,25 @@ def find_value_by_condition(data_dict: dict, condition) -> Any | None: def is_phone_number(id_value: str) -> bool: phone_number_pattern = r'^010\d{8}$' return bool(re.match(phone_number_pattern, id_value)) + +async def get_icon( + category: str, + key: str, + json_file_path: str = "custom_components/apti/icons/icon.json" +) -> str: + try: + async with aiofiles.open(json_file_path, "r", encoding="utf-8") as file: + content = await file.read() + data = json.loads(content) + + if category in data: + icon = data[category].get(key, None) + if icon is None: + LOGGER.warning(f"Icon for key '{key}' in category '{category}' not found.") + return icon + else: + LOGGER.warning(f"Category '{category}' not found in icon data.") + return None + except (FileNotFoundError, json.JSONDecodeError) as e: + LOGGER.error(f"Error reading or decoding '{json_file_path}': {e}") + return None diff --git a/custom_components/apti/icons/icon.json b/custom_components/apti/icons/icon.json new file mode 100644 index 0000000..52f744f --- /dev/null +++ b/custom_components/apti/icons/icon.json @@ -0,0 +1,34 @@ +{ + "maint_item": { + "건물보험료": "mdi:home-lock", + "경비비": "mdi:security", + "공동수도료": "mdi:water-pump", + "공동전기료": "mdi:lightbulb-group", + "관리비 납부 금액": "mdi:currency-krw", + "기본난방비": "mdi:radiator", + "세대수도료": "mdi:water-outline", + "세대전기료": "mdi:flash-outline", + "소독비": "mdi:biohazard", + "수선유지비": "mdi:tools", + "승강기유지비": "mdi:elevator", + "승강기전기": "mdi:elevator-passenger-outline", + "위탁관리수수료": "mdi:account-cash", + "일반관리비": "mdi:clipboard-outline", + "입주자대표회의운": "mdi:account-group-outline", + "장기수선충당금": "mdi:hammer-wrench", + "청소비": "mdi:broom", + "커뮤니티사용료": "mdi:account-multiple-outline", + "커뮤니티시설유지": "mdi:account-group", + "홈네트워크": "mdi:network", + "TV수신료": "mdi:television-classic" + }, + "energy_detail": { + "수도": "mdi:water", + "온수": "mdi:water-boiler", + "전기": "mdi:power-plug" + }, + "energy_type": { + "열": "mdi:fire", + "전기": "mdi:currency-krw" + } +} \ No newline at end of file diff --git a/custom_components/apti/manifest.json b/custom_components/apti/manifest.json index 0ad52da..dfdb069 100644 --- a/custom_components/apti/manifest.json +++ b/custom_components/apti/manifest.json @@ -9,7 +9,8 @@ "iot_class": "local_polling", "issue_tracker": "https://github.com/lunDreame/homeassistant-apti/issues", "requirements": [ - "beautifulsoup4" + "beautifulsoup4", + "aiofiles" ], - "version": "1.0.2" + "version": "1.0.3" } \ No newline at end of file diff --git a/custom_components/apti/sensor.py b/custom_components/apti/sensor.py index 3830631..9ed7035 100644 --- a/custom_components/apti/sensor.py +++ b/custom_components/apti/sensor.py @@ -13,68 +13,69 @@ from .coordinator import APTiDataUpdateCoordinator from .entity import APTiDevice -from .helper import find_value_by_condition +from .helper import find_value_by_condition, get_icon @dataclass(kw_only=True) class APTiSensorEntityDescription(SensorEntityDescription): """Describes APT.i sensor entity.""" - format_id: str - chepter_name: str + format_id: str # unique_id + chepter_name: str # device_info exists_fn: Callable[..., bool] = lambda _: True + icon_fn: Callable[..., str] = lambda _: None + trans_ph: Callable[..., dict] = lambda _: {} value_fn: Callable[..., datetime | str] - extra_attributes: Callable[..., dict] = lambda _: {} SENSORS: tuple[APTiSensorEntityDescription, ...] = ( APTiSensorEntityDescription( key="maint_item", translation_key="maint_item", - translation_placeholders=lambda key: {"category": key["항목"]}, - format_id=lambda key: f"{key['항목']}_maint_item", + native_unit_of_measurement="원", + format_id=lambda k: f"{k['항목']}_maint_item", chepter_name="관리비", - unit_of_measurement = "원", - value_fn=lambda value: find_value_by_condition(value, lambda k: k.startswith("당월")), - extra_attributes=lambda value: value, + icon_fn=lambda c, k: get_icon(c, k["항목"]), + trans_ph=lambda k: {"category": k["항목"]}, + value_fn=lambda v: find_value_by_condition(v, lambda k: k.startswith("당월")), ), APTiSensorEntityDescription( key="maint_payment", + icon="mdi:currency-krw", translation_key="maint_payment", + native_unit_of_measurement="원", format_id="maint_payment", chepter_name="관리비", - unit_of_measurement = "원", - value_fn=lambda value: find_value_by_condition(value, lambda k: k.startswith("납부할 금액")), - extra_attributes=lambda value: value, + value_fn=lambda v: find_value_by_condition(v, lambda k: k.startswith("납부할 금액")), ), APTiSensorEntityDescription( key="energy_usage", + icon="mdi:flash", translation_key="energy_usage", + native_unit_of_measurement="원", format_id="energy_usage", chepter_name="에너지", - unit_of_measurement = "원", - value_fn=lambda value: find_value_by_condition(value, lambda k: k.endswith("사용")), - extra_attributes=lambda value: value, + value_fn=lambda v: find_value_by_condition(v, lambda k: k.endswith("사용")), ), APTiSensorEntityDescription( key="energy_detail", translation_key="energy_detail", - translation_placeholders=lambda key: {"category": key["유형"]}, - format_id=lambda key: f"{key['유형']}_energy_detail", + native_unit_of_measurement="", + format_id=lambda k: f"{k['유형']}_energy_detail", chepter_name="에너지", - unit_of_measurement = "", - value_fn=lambda value: find_value_by_condition(value, lambda k: k.startswith("사용량")), - extra_attributes=lambda value: value, + icon_fn=lambda c, k: get_icon(c, k["유형"]), + trans_ph=lambda k: {"category": k["유형"]}, + value_fn=lambda v: find_value_by_condition(v, lambda k: k.startswith("사용량")), ), APTiSensorEntityDescription( key="energy_type", translation_key="energy_type", - translation_placeholders=lambda key: {"category": key["유형"]}, - format_id=lambda key: f"{key['유형']}_energy_type", + native_unit_of_measurement="원", + format_id=lambda k: f"{k['유형']}_energy_type", chepter_name="에너지", - unit_of_measurement = "원", - value_fn=lambda value: find_value_by_condition(value, lambda k: k.startswith("총액")), - extra_attributes=lambda value: value, + icon_fn=lambda c, k: get_icon(c, k["유형"]), + trans_ph=lambda k: {"category": k["유형"]}, + value_fn=lambda v: find_value_by_condition(v, lambda k: k.startswith("총액")), ), ) @@ -114,19 +115,15 @@ def __init__( """Initialize the sensor.""" super().__init__(coordinator, entity_description) self.entity_description = entity_description + self._value = coordinator.data[self.description.key] + self._attr_extra_state_attributes = self._value self._attr_unique_id = self.description.format_id - self._attr_extra_state_attributes = self.description.extra_attributes( - coordinator.data[self.description.key] - ) - - self._attr_native_unit_of_measurement = self.description.unit_of_measurement @property def native_value(self) -> str: """Return the state of the sensor.""" - value = self.coordinator.data[self.description.key] - return self.description.value_fn(value) + return self.description.value_fn(self._value) class APTiCategorySensor(APTiDevice, SensorEntity): @@ -143,23 +140,16 @@ def __init__( self.category = category self.entity_description = entity_description + self._attr_extra_state_attributes = category + self._attr_translation_placeholders = self.description.trans_ph(category) self._attr_unique_id = self.description.format_id(category) - self._attr_extra_state_attributes = self.description.extra_attributes( - category - ) - - self._attr_native_unit_of_measurement = self.description.unit_of_measurement + async def async_added_to_hass(self) -> None: + """Called when added to Hass.""" + self._attr_icon = await self.description.icon_fn(self.description.key, self.category) + await super().async_added_to_hass() + @property def native_value(self) -> datetime | str: """Return the state of the sensor.""" return self.description.value_fn(self.category) - - @property - def translation_placeholders(self) -> dict[str, str] | None: - """Return the translation placeholders.""" - if self.category: - return self.description.translation_placeholders( - self.category - ) - return None