From 4c29bd1fc7898cf28985cb0b018355caccbb71c0 Mon Sep 17 00:00:00 2001 From: lunDreame <87955512+lunDreame@users.noreply.github.com> Date: Thu, 10 Oct 2024 22:42:54 +0900 Subject: [PATCH] refactoring --- custom_components/apti/__init__.py | 48 +----- custom_components/apti/apti.py | 163 ++++++++++++-------- custom_components/apti/config_flow.py | 18 ++- custom_components/apti/const.py | 6 +- custom_components/apti/coordinator.py | 37 ++--- custom_components/apti/entity.py | 22 ++- custom_components/apti/helper.py | 5 + custom_components/apti/manifest.json | 2 +- custom_components/apti/sensor.py | 60 +++---- custom_components/apti/translations/en.json | 38 +---- custom_components/apti/translations/ko.json | 38 +---- 11 files changed, 184 insertions(+), 253 deletions(-) diff --git a/custom_components/apti/__init__.py b/custom_components/apti/__init__.py index c94f739..2b1ba84 100644 --- a/custom_components/apti/__init__.py +++ b/custom_components/apti/__init__.py @@ -2,36 +2,12 @@ from __future__ import annotations -from datetime import datetime -import voluptuous as vol - -from homeassistant.core import ( - HomeAssistant, - ServiceCall, - ServiceResponse, - SupportsResponse -) +from homeassistant.core import HomeAssistant from homeassistant.config_entries import ConfigEntry -from homeassistant.components import persistent_notification from homeassistant.helpers.event import async_track_time_interval -import homeassistant.helpers.config_validation as cv from .coordinator import APTiDataUpdateCoordinator -from .const import ( - DOMAIN, - PLATFORMS, - UPDATE_SESSION_INTERVAL, - UPDATE_MAINT_INTERVAL, - UPDATE_ENERGY_INTERVAL -) - -format_date = datetime.now().strftime("%Y%m%d") - -VISIT_RESERVATION_SCHEMA = vol.Schema({ - vol.Required("car_no"): cv.string, - vol.Required("phone_no"): cv.string, - vol.Required("visit_date", default=format_date): cv.datetime, -}) +from .const import PLATFORMS, UPDATE_ME_INTERVAL async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: @@ -42,22 +18,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await coordinator.async_config_entry_first_refresh() async_track_time_interval( - hass, coordinator.api.login, UPDATE_SESSION_INTERVAL, - cancel_on_shutdown=True - ) - async_track_time_interval( - hass, coordinator._update_maint, UPDATE_MAINT_INTERVAL, - cancel_on_shutdown=True - ) - async_track_time_interval( - hass, coordinator._update_energy, UPDATE_ENERGY_INTERVAL, + hass, coordinator._update_maint_energy, UPDATE_ME_INTERVAL, cancel_on_shutdown=True ) entry.runtime_data = coordinator - #await _async_setup_service(hass, entry) - await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True @@ -68,11 +34,5 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: entry, PLATFORMS ): coordinator: APTiDataUpdateCoordinator = entry.runtime_data + return unload_ok - - -async def _async_setup_service(hass: HomeAssistant, entry: ConfigEntry) -> None: - """Set up the service.""" - - async def _async_visit_reservation(call: ServiceCall) -> ServiceResponse: - """Perform vehicle visit reservation service.""" diff --git a/custom_components/apti/apti.py b/custom_components/apti/apti.py index 95c952a..0fc6111 100644 --- a/custom_components/apti/apti.py +++ b/custom_components/apti/apti.py @@ -3,14 +3,13 @@ from bs4 import BeautifulSoup from collections.abc import Callable, Set -from datetime import datetime from dataclasses import dataclass, field +from typing import Optional -from homeassistant.const import CONF_USERNAME, CONF_PASSWORD from homeassistant.core import HomeAssistant from homeassistant.config_entries import ConfigEntry -from .helper import get_text_or_log +from .helper import get_text_or_log, is_phone_number from .until import format_date_two_months_ago, get_target_month from .const import LOGGER @@ -19,19 +18,17 @@ class APTiMaint: """APT.i maintenance class.""" - cost: list = field(default_factory=list) # 관리비 항목 - payment: dict = field(default_factory=dict) # 관리비 납부 액 - update_time: datetime = field(default_factory=datetime.now) + item: list = field(default_factory=list) # 관리비 항목 + payment_amount: dict = field(default_factory=dict) # 관리비 납부 액 @dataclass class APTiEnergy: """APT.i energy class.""" - usage: dict = field(default_factory=dict) # 에너지 항목(사용량) - detail: list = field(default_factory=list) # 에너지 항목(상세 사용량) - type: list = field(default_factory=list) # 에너지 종류(사용량) - update_time: datetime = field(default_factory=datetime.now) + item_usage: dict = field(default_factory=dict) # 에너지 항목(사용량) + detail_usage: list = field(default_factory=list) # 에너지 항목(상세 사용량) + type_usage: list = field(default_factory=list) # 에너지 종류(사용량) @dataclass @@ -60,12 +57,18 @@ def update_callback(self): class APTiAPI: """APT.i API class.""" - def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: + def __init__( + self, + hass: HomeAssistant, + entry: Optional[ConfigEntry], + id: str, + password: str, + ) -> None: """Initialize the session and basic information.""" self.hass = hass self.entry = entry - self.username: str | None = None - self.password: str | None = None + self.id = id + self.password = password self.session = aiohttp.ClientSession() self.logged_in = False self.se_token: str | None = None @@ -73,52 +76,82 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: self.dong_ho: str | None = None self.data = APTiData() - async def login(self, username: str = None, password: str = None): + async def login(self): """Login to APT.i.""" - if username and password: - self.username = username - self.password = password - else: - self.username = self.entry.data[CONF_USERNAME] - self.password = self.entry.data[CONF_PASSWORD] - url = "https://www.apti.co.kr/member/login_ok.asp" headers = {"content-type": "application/x-www-form-urlencoded"} data = { "pageGubu": "I", "pageMode": "I", "pwd": self.password, - "id": self.username, + "id": self.id, "gubu": "H", - "hp_id": self.username, + "hp_id": self.id, "hp_pwd": self.password, } + if not is_phone_number(self.id): + data["gubu"] = "I" + data["login_id"] = data.pop("hp_id") + data["login_pwd"] = data.pop("hp_pwd") try: async with self.session.post(url, headers=headers, data=data, timeout=5) as response: if response.status != 200: - LOGGER.error("APTi login failed.") return - cookies = ' '.join(response.headers.getall('Set-Cookie', [])) - self.se_token = re.search(r'se%5Ftoken=([^;]+)', cookies) - self.apti_codesave = re.search(r'apti=codesave=([^;]+)', cookies) + cookies = ' '.join(response.headers.getall("Set-Cookie", [])) + se_token = re.search(r'se%5Ftoken=([^;]+)', cookies) + apti_codesave = re.search(r'apti=codesave=([^;]+)', cookies) - if self.se_token: - self.se_token = self.se_token.group(1) + if se_token: + self.se_token = se_token.group(1) LOGGER.debug(f"Se token: {self.se_token}") - if self.apti_codesave: - self.apti_codesave = self.apti_codesave.group(1) + if apti_codesave: + self.apti_codesave = apti_codesave.group(1) LOGGER.debug(f"APTi codesave: {self.apti_codesave}") - - self.logged_in = True + + if se_token and apti_codesave: + await self.get_subpage_info() except Exception as ex: LOGGER.error(f"Exception during APTi login: {ex}") + async def get_subpage_info(self): + """Get the sub page info.""" + url = "https://www.apti.co.kr/apti/subpage/?menucd=ACAI" + headers = {"cookie": f"se%5Ftoken={self.se_token}"} + + try: + async with self.session.post(url, headers=headers, timeout=5) as response: + if response.status != 200: + return + + raw_data = await response.content.read() + resp = raw_data.decode("EUC-KR") + + soup = BeautifulSoup(resp, "html.parser") + address_div = soup.find("div", class_="Nbox1_txt10", style="font-size:13px; font-weight:600;") + + if address_div: + address_text = address_div.text.strip() + pattern = r"(\d+)동\s*(\d+)호" + match = re.search(pattern, address_text) + if match: + dong = match.group(1) + ho = match.group(2) + self.dong_ho = str('0' + dong + ho.zfill(4)) + LOGGER.debug(f"APT DongHo: {self.dong_ho}") + + self.logged_in = True + else: + self.logged_in = False + LOGGER.error("Failed to get session credentials.") + except Exception as ex: + LOGGER.error(f"An exception occurred while processing sub page info: {ex}") + async def get_maint_fee_item(self): """Get the maintenance fee items.""" url = "https://www.apti.co.kr/apti/manage/manage_dataJquery.asp?ajaxGubu=L&orderType=&chkType=ADD" - headers = {"cookie": f"se%5Ftoken={self.se_token};"} + headers = {"cookie": f"se%5Ftoken={self.se_token}"} params = { "listNum": "20", "manageDataTot": "23", @@ -134,7 +167,6 @@ async def get_maint_fee_item(self): raw_data = await response.content.read() resp = raw_data.decode("EUC-KR") - #LOGGER.debug(f"get_maint_fee_item: {resp}") soup = BeautifulSoup(resp, "html.parser") links = soup.find_all("a", class_="black") @@ -143,27 +175,24 @@ async def get_maint_fee_item(self): row = link.find_parent("td").parent category = link.text current_month, previous_month, change = [ - td.text.strip() for td in row.find_all("td")[1:4] + td.text.strip() + "원" for td in row.find_all("td")[1:4] ] if all([category, current_month, previous_month, change]): - self.data.maint.cost.append({ + self.data.maint.item.append({ "항목": category, "당월": current_month, "전월": previous_month, "증감": change }) else: - LOGGER.warning( - f"Skipping row due to missing values: %s, %s, %s, %s", - category, current_month, previous_month, change - ) + LOGGER.warning(f"Skipping row due to missing values: {category}") except Exception as ex: LOGGER.error(f"An exception occurred while processing maintenance fee item: {ex}") async def get_maint_fee_payment(self): """Get the payment of maintenance fees.""" url = "https://www.apti.co.kr/apti/manage/manage_cost.asp?menucd=ACAI" - headers = {"cookie": f"se%5Ftoken={self.se_token};"} + headers = {"cookie": f"se%5Ftoken={self.se_token}"} try: async with self.session.get(url, headers=headers, timeout=5) as response: @@ -174,8 +203,6 @@ async def get_maint_fee_payment(self): resp = raw_data.decode("EUC-KR") soup = BeautifulSoup(resp, "html.parser") - self.dong_ho = get_text_or_log(soup, 'input[name="dongho"]', "동호 정보를 찾을 수 없습니다.", attr="value") - LOGGER.debug(f"APT DongHo: {self.dong_ho}") target_month = get_target_month() cost_info = { @@ -202,14 +229,14 @@ async def get_maint_fee_payment(self): else: LOGGER.warning("납부할 금액의 span 요소를 찾을 수 없습니다.") - self.data.maint.payment.update(cost_info) + self.data.maint.payment_amount.update(cost_info) except Exception as ex: LOGGER.error(f"An exception occurred while processing maintenance fee payment: {ex}") async def get_energy_category(self): """Get the usage by energy category.""" url = "https://www.apti.co.kr/apti/manage/manage_energy.asp?menucd=ACAD" - headers = {"cookie": f"se%5Ftoken={self.se_token};"} + headers = {"cookie": f"se%5Ftoken={self.se_token}"} try: async with self.session.get(url, headers=headers, timeout=5) as response: @@ -221,33 +248,38 @@ async def get_energy_category(self): soup = BeautifulSoup(resp, "html.parser") energy_top = soup.find("div", class_="energyTop") - total_usage = energy_top.find("strong", class_="data1").text.strip() + total_usage = energy_top.find("strong", class_="data1").text.strip() + "원" month = energy_top.find("span", class_="month").text.strip() energy_data = soup.find("div", class_="energy_data") - average_comparison = energy_data.find("strong").text.strip() + average_comparison = energy_data.find("p", class_="txt").text energy_analysis = soup.find("div", class_="energy_data2") analysis_items = energy_analysis.find_all("li") energy_breakdown = { - item.contents[0].strip(): item.find("strong").text.strip() + item.contents[0].strip(): item.find("strong").text.strip() + "%" for item in analysis_items } - self.data.energy.usage.update({ - "월": month, - "전체 사용량": total_usage, - "평균 대비": average_comparison, - **energy_breakdown + + self.data.energy.item_usage.update({ + month: total_usage, + "비교": average_comparison, + **energy_breakdown + #{ + # "전기": "67%", + # "수도": "18%", + # "온수": "15%" + #} }) energy_boxes = soup.find_all("div", class_="engBox") for box in energy_boxes: energy_type = box.find("h3").text.strip() usage = box.find("li").find("strong").text.strip() - cost = box.find("li", class_="line").find_next_sibling("li").find("strong").text.strip() + cost = box.find("li", class_="line").find_next_sibling("li").find("strong").text.strip() + "원" comparison = box.find("div", class_="txtBox").find("strong").text.strip() - self.data.energy.detail.append({ + self.data.energy.detail_usage.append({ "유형": energy_type, "사용량": usage, "비용": cost, @@ -259,7 +291,7 @@ async def get_energy_category(self): async def get_energy_type(self): """Get the usage by energy type.""" url = "https://www.apti.co.kr/apti/manage/manage_energyGogi.asp?menucd=ACAE" - headers = {"cookie": f"se%5Ftoken={self.se_token};"} + headers = {"cookie": f"se%5Ftoken={self.se_token}"} try: async with self.session.get(url, headers=headers, timeout=5) as response: @@ -273,11 +305,11 @@ async def get_energy_type(self): electricity = soup.find("div", class_="billBox clearfix") if electricity: electricity_info = { - "에너지 유형": "전기", + "유형": "전기", "총액": electricity.find("div", class_="enePay").text.strip(), "사용량": electricity.find("p", class_="eneDownTxt").text.split("(")[1].split(")")[0].strip(), "평균 사용량": electricity.find("p", class_="eneUpTxt").text.split("(")[1].split(")")[0].strip(), - "비교": electricity.find("div", class_="energy_data date1").find("strong").text.strip(), + "비교": electricity.find("div", class_="energy_data date1").find("p", class_="txt").text, } details = electricity.find("div", class_="tbl_bill").find_all("tr") @@ -287,14 +319,14 @@ async def get_energy_type(self): if len(cols) > 1: electricity_info[row.find_all("th")[1].text.strip()] = cols[1].text.strip() - self.data.energy.type.append(electricity_info) + self.data.energy.type_usage.append(electricity_info) heat = soup.find_all("div", class_="billBox clearfix")[1] if heat: heat_info = { - "에너지 유형": "열", + "유형": "열", "총액": heat.find("div", class_="enePay").text.strip(), - "비교": heat.find("div", class_="energy_data date1").find("strong").text.strip(), + "비교": heat.find("div", class_="energy_data date1").find("p", class_="txt").text, } details = heat.find("div", class_="tbl_bill").find_all("tr") @@ -303,10 +335,7 @@ async def get_energy_type(self): heat_info[row.th.text.strip()] = cols[0].text.strip() if len(cols) > 1: heat_info[row.find_all("th")[1].text.strip()] = cols[1].text.strip() - - self.data.energy.type.append(heat_info) + + self.data.energy.type_usage.append(heat_info) except Exception as ex: LOGGER.error(f"An exception occurred while processing energy usage type: {ex}") - - async def _visit_reservation(self): - """Vehicle visit reservation.""" diff --git a/custom_components/apti/config_flow.py b/custom_components/apti/config_flow.py index 9f9acf9..361121e 100644 --- a/custom_components/apti/config_flow.py +++ b/custom_components/apti/config_flow.py @@ -5,12 +5,12 @@ from typing import Any import voluptuous as vol -from homeassistant.const import CONF_USERNAME, CONF_PASSWORD +from homeassistant.const import CONF_ID, CONF_PASSWORD from homeassistant.config_entries import ConfigFlow, ConfigFlowResult import homeassistant.helpers.config_validation as cv from .apti import APTiAPI -from .const import DOMAIN, LOGGER +from .const import DOMAIN class APTiConfigFlow(ConfigFlow, domain=DOMAIN): @@ -26,23 +26,25 @@ async def async_step_user( errors = {} if user_input is not None: - username = user_input[CONF_USERNAME] + id = user_input[CONF_ID] password = user_input[CONF_PASSWORD] - api = APTiAPI(self.hass, None) - await api.login(username, password) + api = APTiAPI( + hass=self.hass, entry=None, id=id, password=password + ) + await api.login() if not api.logged_in: errors["base"] = "login_failed" else: - await self.async_set_unique_id(user_input[CONF_USERNAME]) + await self.async_set_unique_id(user_input[CONF_ID]) self._abort_if_unique_id_configured() - return self.async_create_entry(title=user_input[CONF_USERNAME], data=user_input) + return self.async_create_entry(title=user_input[CONF_ID], data=user_input) return self.async_show_form( step_id="user", data_schema=vol.Schema({ - vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_ID): cv.string, vol.Required(CONF_PASSWORD): cv.string, }), errors=errors diff --git a/custom_components/apti/const.py b/custom_components/apti/const.py index 0d3a866..911e365 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.0" +VERSION = "1.0.1" PLATFORMS: list[Platform] = [ Platform.SENSOR @@ -16,6 +16,4 @@ LOGGER = logging.getLogger(__package__) -UPDATE_SESSION_INTERVAL = timedelta(minutes=20) -UPDATE_MAINT_INTERVAL = timedelta(hours=24) -UPDATE_ENERGY_INTERVAL = timedelta(hours=24) +UPDATE_ME_INTERVAL = timedelta(days=7) diff --git a/custom_components/apti/coordinator.py b/custom_components/apti/coordinator.py index fb482bb..6a8acdd 100644 --- a/custom_components/apti/coordinator.py +++ b/custom_components/apti/coordinator.py @@ -6,6 +6,7 @@ from datetime import datetime from typing import Any +from homeassistant.const import CONF_ID, CONF_PASSWORD from homeassistant.core import HomeAssistant from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed @@ -22,35 +23,32 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: super().__init__(hass, LOGGER, name=DOMAIN) self.hass = hass self.entry = entry - self.api = APTiAPI(hass, entry) + self.id = entry.data.get(CONF_ID) + self.password = entry.data.get(CONF_PASSWORD) + self.api = APTiAPI(hass, entry, self.id, self.password) self.entities: dict[str, dict | list] = dict() - async def _update_maint(self, _=None): - """Fetch maintenance data from the API.""" - await self.api.get_maint_fee_payment() - await self.api.get_maint_fee_item() - self.api.data.update_callback() - self.api.data.maint.update_time = datetime.now() - - async def _update_energy(self, _=None): - """Fetch energy data from the API.""" + async def _update_maint_energy(self, _=None): + """Fetch maintenance/energy data from the API.""" + if isinstance(_, datetime): + LOGGER.info("Update maintenance/energy data.") + await self.api.login() await asyncio.gather( + self.api.get_maint_fee_item(), + self.api.get_maint_fee_payment(), self.api.get_energy_category(), self.api.get_energy_type() ) self.api.data.update_callback() - self.api.data.energy.update_time = datetime.now() def data_to_entities(self) -> dict[str, dict | list]: """Convert APT.i data to entities.""" data_items = { - "maint_payment": self.api.data.maint.payment, - "maint_cost": self.api.data.maint.cost, - "maint_update_time": self.api.data.maint.update_time, - "energy_usage": self.api.data.energy.usage, - "energy_detail": self.api.data.energy.detail, - "energy_type": self.api.data.energy.type, - "energy_update_time": self.api.data.energy.update_time + "maint_item": self.api.data.maint.item, + "maint_payment": self.api.data.maint.payment_amount, + "energy_usage": self.api.data.energy.item_usage, + "energy_detail": self.api.data.energy.detail_usage, + "energy_type": self.api.data.energy.type_usage, } for entity_key, value in data_items.items(): self.entities[entity_key] = value @@ -59,8 +57,7 @@ def data_to_entities(self) -> dict[str, dict | list]: async def _async_update_data(self) -> dict[str, dict | list]: """Update APT.i devices data.""" try: - await self._update_maint() - await self._update_energy() + await self._update_maint_energy() return self.data_to_entities() except Exception as ex: raise UpdateFailed(f"Failed to update APT.i data: {ex}") from ex diff --git a/custom_components/apti/entity.py b/custom_components/apti/entity.py index 4f325f2..63c718a 100644 --- a/custom_components/apti/entity.py +++ b/custom_components/apti/entity.py @@ -15,12 +15,10 @@ class APTiBase: """Base class for APT.i.""" def __init__( - self, - coordinator: APTiDataUpdateCoordinator, - entity + self, coordinator: APTiDataUpdateCoordinator, description ): - self._coordinator = coordinator - self._entity = entity + self.coordinator = coordinator + self.description = description @property def device_info(self) -> DeviceInfo: @@ -28,19 +26,19 @@ def device_info(self) -> DeviceInfo: return DeviceInfo( configuration_url="https://www.apti.co.kr/apti/", identifiers={( - DOMAIN, f"{self._coordinator.api.username}_{self._entity.chepter_name}" + DOMAIN, f"{self.coordinator.id}_{self.description.chepter_name}" )}, manufacturer="APT.i Co.,Ltd.", model="APT.i", - name=self._entity.chepter_name, + name=self.description.chepter_name, ) class APTiDevice(APTiBase, Entity): """APT.i device class.""" - def __init__(self, coordinator, entity): - super().__init__(coordinator, entity) + def __init__(self, coordinator, description): + super().__init__(coordinator, description) self._attr_has_entity_name = True @property @@ -50,12 +48,12 @@ def entity_registry_enabled_default(self): async def async_added_to_hass(self): """Called when added to Hass.""" - self._coordinator.api.data.add_callback(self.async_update_callback) + self.coordinator.api.data.add_callback(self.async_update_callback) self.schedule_update_ha_state() async def async_will_remove_from_hass(self) -> None: """Called when removed from Hass.""" - self._coordinator.api.data.remove_callback(self.async_update_callback) + self.coordinator.api.data.remove_callback(self.async_update_callback) @callback def async_restore_last_state(self, last_state) -> None: @@ -70,7 +68,7 @@ def async_update_callback(self): @property def available(self) -> bool: """Return whether the device is available.""" - return self._coordinator.api.logged_in + return self.coordinator.api.logged_in @property def should_poll(self) -> bool: diff --git a/custom_components/apti/helper.py b/custom_components/apti/helper.py index fe92bee..a4f4b89 100644 --- a/custom_components/apti/helper.py +++ b/custom_components/apti/helper.py @@ -1,6 +1,7 @@ """Defines helper function.""" from typing import Any +import re from .const import LOGGER @@ -27,3 +28,7 @@ def find_value_by_condition(data_dict: dict, condition) -> Any | None: if condition(key): return value return 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)) diff --git a/custom_components/apti/manifest.json b/custom_components/apti/manifest.json index 3cee6b6..07d583d 100644 --- a/custom_components/apti/manifest.json +++ b/custom_components/apti/manifest.json @@ -11,5 +11,5 @@ "requirements": [ "beautifulsoup4" ], - "version": "1.0.0" + "version": "1.0.1" } \ No newline at end of file diff --git a/custom_components/apti/sensor.py b/custom_components/apti/sensor.py index 753a57e..78a75a8 100644 --- a/custom_components/apti/sensor.py +++ b/custom_components/apti/sensor.py @@ -29,36 +29,28 @@ class APTiSensorEntityDescription(SensorEntityDescription): SENSORS: tuple[APTiSensorEntityDescription, ...] = ( APTiSensorEntityDescription( - key="maint_payment", - translation_key="maint_payment", - format_id="maint_payment", - chepter_name="관리비", - value_fn=lambda value: find_value_by_condition(value, lambda k: k.startswith("납부할 금액")), - extra_attributes=lambda value: value, - ), - APTiSensorEntityDescription( - key="maint_cost", - translation_key="maint_cost", + key="maint_item", + translation_key="maint_item", translation_placeholders=lambda key: {"category": key["항목"]}, - format_id=lambda key: f"{key['항목']}_maint_cost", + format_id=lambda key: f"{key['항목']}_maint_item", chepter_name="관리비", value_fn=lambda value: find_value_by_condition(value, lambda k: k.startswith("당월")), extra_attributes=lambda value: value, ), APTiSensorEntityDescription( - key="maint_update_time", - translation_key="maint_update_time", - format_id="maint_update_time", + key="maint_payment", + translation_key="maint_payment", + format_id="maint_payment", chepter_name="관리비", - value_fn=lambda value: value, - extra_attributes=lambda value: None, + value_fn=lambda value: find_value_by_condition(value, lambda k: k.startswith("납부할 금액")), + extra_attributes=lambda value: value, ), APTiSensorEntityDescription( key="energy_usage", translation_key="energy_usage", format_id="energy_usage", chepter_name="에너지", - value_fn=lambda value: find_value_by_condition(value, lambda k: k.startswith("전체 사용량")), + value_fn=lambda value: find_value_by_condition(value, lambda k: k.endswith("사용")), extra_attributes=lambda value: value, ), APTiSensorEntityDescription( @@ -73,20 +65,12 @@ class APTiSensorEntityDescription(SensorEntityDescription): APTiSensorEntityDescription( key="energy_type", translation_key="energy_type", - translation_placeholders=lambda key: {"category": key["에너지 유형"]}, - format_id=lambda key: f"{key['에너지 유형']}_energy_type", + translation_placeholders=lambda key: {"category": key["유형"]}, + format_id=lambda key: f"{key['유형']}_energy_type", chepter_name="에너지", value_fn=lambda value: find_value_by_condition(value, lambda k: k.startswith("총액")), extra_attributes=lambda value: value, ), - APTiSensorEntityDescription( - key="energy_update_time", - translation_key="energy_update_time", - format_id="energy_update_time", - chepter_name="에너지", - value_fn=lambda value: value, - extra_attributes=lambda value: None, - ), ) @@ -125,16 +109,17 @@ def __init__( """Initialize the sensor.""" super().__init__(coordinator, entity_description) self.entity_description = entity_description - self._attr_unique_id = entity_description.format_id - self._attr_extra_state_attributes = entity_description.extra_attributes( - coordinator.data[entity_description.key] + + self._attr_unique_id = self.description.format_id + self._attr_extra_state_attributes = self.description.extra_attributes( + coordinator.data[self.description.key] ) @property def native_value(self) -> str: """Return the state of the sensor.""" - value = self._coordinator.data[self._entity.key] - return self.entity_description.value_fn(value) + value = self.coordinator.data[self.description.key] + return self.description.value_fn(value) class APTiCategorySensor(APTiDevice, SensorEntity): @@ -148,23 +133,24 @@ def __init__( ) -> None: """Initialize the sensor.""" super().__init__(coordinator, entity_description) - self.entity_description = entity_description self.category = category - self._attr_unique_id = entity_description.format_id(category) - self._attr_extra_state_attributes = entity_description.extra_attributes( + self.entity_description = entity_description + + self._attr_unique_id = self.description.format_id(category) + self._attr_extra_state_attributes = self.description.extra_attributes( category ) @property def native_value(self) -> datetime | str: """Return the state of the sensor.""" - return self.entity_description.value_fn(self.category) + 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.entity_description.translation_placeholders( + return self.description.translation_placeholders( self.category ) return None diff --git a/custom_components/apti/translations/en.json b/custom_components/apti/translations/en.json index 7b46f32..9c6b563 100644 --- a/custom_components/apti/translations/en.json +++ b/custom_components/apti/translations/en.json @@ -3,10 +3,13 @@ "config": { "step": { "user": { - "description": "", + "description": "Please enter your ID or mobile phone number.", "data": { - "username": "Username", + "id": "ID / HP", "password": "Password" + }, + "data_description": { + "id": "For mobile phone number, please enter only numbers." } } }, @@ -19,14 +22,11 @@ }, "entity": { "sensor": { - "maint_payment": { - "name": "Maintenance Payment Amount" - }, - "maint_cost": { + "maint_item": { "name": "{category}" }, - "maint_update_time": { - "name": "Update Time" + "maint_payment": { + "name": "Maintenance Payment Amount" }, "energy_usage": { "name": "Total Energy Usage" @@ -36,28 +36,6 @@ }, "energy_type": { "name": "Total {category} Energy" - }, - "energy_update_time": { - "name": "Update Time" - } - } - }, - "services": { - "visit_reservation": { - "name": "Visitor Vehicle Reservation", - "fields": { - "car_no": { - "name": "Car Number", - "description": "Please enter in the format '12GA3456'." - }, - "phone_no": { - "name": "Phone Number", - "description": "Enter numbers only without hyphens." - }, - "visit_date": { - "name": "Visit Date", - "description": "Please enter in the format '20241010'." - } } } } diff --git a/custom_components/apti/translations/ko.json b/custom_components/apti/translations/ko.json index 345faa2..e037ad9 100644 --- a/custom_components/apti/translations/ko.json +++ b/custom_components/apti/translations/ko.json @@ -3,10 +3,13 @@ "config": { "step": { "user": { - "description": "", + "description": "아이디 또는 휴대폰 번호를 입력해 주세요.", "data": { - "username": "사용자 이름", + "id": "아이디 / 휴대폰 번호", "password": "패스워드" + }, + "data_description": { + "id": "휴대폰 번호의 경우 숫자만 입력해 주세요." } } }, @@ -19,14 +22,11 @@ }, "entity": { "sensor": { - "maint_payment": { - "name": "관리비 납부 금액" - }, - "maint_cost": { + "maint_item": { "name": "{category}" }, - "maint_update_time": { - "name": "업데이트 시간" + "maint_payment": { + "name": "관리비 납부 금액" }, "energy_usage": { "name": "에너지 전체 사용량" @@ -36,28 +36,6 @@ }, "energy_type": { "name": "{category} 에너지 총액" - }, - "energy_update_time": { - "name": "업데이트 시간" - } - } - }, - "services": { - "visit_reservation": { - "name": "방문차량 예약", - "fields": { - "car_no": { - "name": "차량 번호", - "description": "'12가3456' 형식으로 입력하세요." - }, - "phone_no": { - "name": "휴대폰 번호", - "description": "하이픈 없이 숫자만 입력하세요." - }, - "visit_date": { - "name": "방문일", - "description": "'20241010' 형식으로 입력하세요." - } } } }