From b23cc55145d69be93c70ab89e743fb28f3a63ce1 Mon Sep 17 00:00:00 2001 From: marq24 Date: Tue, 3 Sep 2024 18:43:03 +0200 Subject: [PATCH] remove country from hacs.json def state -> def native_value --- .../waterkotte_heatpump/const.py | 13 ++-- .../waterkotte_heatpump/manifest.json | 2 +- .../pywaterkotte_ha/__init__.py | 65 ++++++++++--------- .../pywaterkotte_ha/tags.py | 49 ++++++++++++-- .../waterkotte_heatpump/sensor.py | 47 +++++++++----- hacs.json | 3 - requirements.txt | 1 + requirements_dev.txt | 1 + 8 files changed, 120 insertions(+), 61 deletions(-) create mode 100644 requirements.txt create mode 100644 requirements_dev.txt diff --git a/custom_components/waterkotte_heatpump/const.py b/custom_components/waterkotte_heatpump/const.py index 25f8a89..3c90c29 100644 --- a/custom_components/waterkotte_heatpump/const.py +++ b/custom_components/waterkotte_heatpump/const.py @@ -1,6 +1,8 @@ from dataclasses import dataclass from typing import Final +from custom_components.waterkotte_heatpump.pywaterkotte_ha.const import SIX_STEPS_MODES +from custom_components.waterkotte_heatpump.pywaterkotte_ha.tags import WKHPTag from homeassistant.components.binary_sensor import BinarySensorDeviceClass, BinarySensorEntityDescription from homeassistant.components.number import NumberEntityDescription, NumberDeviceClass, NumberMode, DEFAULT_STEP from homeassistant.components.select import SelectEntityDescription @@ -19,9 +21,6 @@ REVOLUTIONS_PER_MINUTE ) -from custom_components.waterkotte_heatpump.pywaterkotte_ha.const import SIX_STEPS_MODES -from custom_components.waterkotte_heatpump.pywaterkotte_ha.tags import WKHPTag - # Base component constants NAME: Final = "Waterkotte Heatpump [+2020]" DOMAIN: Final = "waterkotte_heatpump" @@ -1714,7 +1713,7 @@ class ExtSwitchEntityDescription(SwitchEntityDescription): ExtSensorEntityDescription( key="WATERKOTTE_BIOS_TIME", tag=WKHPTag.WATERKOTTE_BIOS_TIME, - state_class=SensorStateClass.TOTAL_INCREASING, + state_class=None, device_class=SensorDeviceClass.DATE, native_unit_of_measurement=None, icon="mdi:clock-digital", @@ -1724,7 +1723,7 @@ class ExtSwitchEntityDescription(SwitchEntityDescription): ExtSensorEntityDescription( key="HOLIDAY_START_TIME", tag=WKHPTag.HOLIDAY_START_TIME, - state_class=SensorStateClass.TOTAL_INCREASING, + state_class=None, device_class=SensorDeviceClass.DATE, native_unit_of_measurement=None, icon="mdi:calendar-arrow-right", @@ -1733,7 +1732,7 @@ class ExtSwitchEntityDescription(SwitchEntityDescription): ExtSensorEntityDescription( key="HOLIDAY_END_TIME", tag=WKHPTag.HOLIDAY_END_TIME, - state_class=SensorStateClass.TOTAL_INCREASING, + state_class=None, device_class=SensorDeviceClass.DATE, native_unit_of_measurement=None, icon="mdi:calendar-arrow-left", @@ -1742,7 +1741,7 @@ class ExtSwitchEntityDescription(SwitchEntityDescription): ExtSensorEntityDescription( key="SCHEDULE_WATER_DISINFECTION_START_TIME", tag=WKHPTag.SCHEDULE_WATER_DISINFECTION_START_TIME, - state_class=SensorStateClass.TOTAL_INCREASING, + state_class=None, device_class=SensorDeviceClass.DATE, native_unit_of_measurement=None, icon="mdi:clock-digital", diff --git a/custom_components/waterkotte_heatpump/manifest.json b/custom_components/waterkotte_heatpump/manifest.json index 7eb1f40..1f5c194 100644 --- a/custom_components/waterkotte_heatpump/manifest.json +++ b/custom_components/waterkotte_heatpump/manifest.json @@ -11,5 +11,5 @@ "iot_class": "local_polling", "issue_tracker": "https://github.com/marq24/ha-waterkotte/issues", "requirements": [], - "version": "2024.8.0" + "version": "2024.9.0" } diff --git a/custom_components/waterkotte_heatpump/pywaterkotte_ha/__init__.py b/custom_components/waterkotte_heatpump/pywaterkotte_ha/__init__.py index 508449e..bd21f85 100644 --- a/custom_components/waterkotte_heatpump/pywaterkotte_ha/__init__.py +++ b/custom_components/waterkotte_heatpump/pywaterkotte_ha/__init__.py @@ -2,9 +2,7 @@ import logging import re import xml.etree.ElementTree as ElemTree - from datetime import datetime - from typing import ( Any, Sequence, @@ -21,7 +19,6 @@ SIX_STEPS_MODES, TRANSLATIONS ) - from custom_components.waterkotte_heatpump.pywaterkotte_ha.error import ( InvalidResponseException, InvalidValueException, @@ -29,7 +26,6 @@ TooManyUsersException, Http404Exception, InvalidPasswordException ) - from custom_components.waterkotte_heatpump.pywaterkotte_ha.tags import WKHPTag _LOGGER: logging.Logger = logging.getLogger(__package__) @@ -206,37 +202,48 @@ async def read_values(self, tags: Sequence[WKHPTag]): try: t_values = [e_values[a_tag] for a_tag in a_wphp_tag.tags] t_states = [e_status[a_tag] for a_tag in a_wphp_tag.tags] - if a_wphp_tag.decode_f == WKHPTag._decode_alarms: - result[a_wphp_tag] = { - "value": a_wphp_tag.decode_f(a_wphp_tag, t_values, self.lang_map), - "status": t_states[0] - } - else: - result[a_wphp_tag] = { - "value": a_wphp_tag.decode_f(a_wphp_tag, t_values), - "status": t_states[0] - } - if a_wphp_tag.translate and a_wphp_tag.tags[0] in self.lang_map: - value_map = self.lang_map[a_wphp_tag.tags[0]] - final_value = "" - temp_values = result[a_wphp_tag]["value"] - for idx in range(len(temp_values)): - if temp_values[idx]: - final_value = final_value + ", " + str(value_map[idx]) - - # we need to trim the firsts initial added ', ' - if len(final_value) > 0: - final_value = final_value[2:] - - result[a_wphp_tag]["value"] = final_value + if t_values is None or (len(t_values) > 0 and t_values[0] is None): + if t_states is not None and len(t_states)>0: + result[a_wphp_tag] = { + "value": None, + "status": t_states[0] + } + else: + result[a_wphp_tag] = None + else: + if a_wphp_tag.decode_f == WKHPTag._decode_alarms: + result[a_wphp_tag] = { + "value": a_wphp_tag.decode_f(a_wphp_tag, t_values, self.lang_map), + "status": t_states[0] + } + else: + result[a_wphp_tag] = { + "value": a_wphp_tag.decode_f(a_wphp_tag, t_values), + "status": t_states[0] + } + + if a_wphp_tag.translate and a_wphp_tag.tags[0] in self.lang_map: + value_map = self.lang_map[a_wphp_tag.tags[0]] + final_value = "" + temp_values = result[a_wphp_tag]["value"] + if temp_values is not None: + for idx in range(len(temp_values)): + if temp_values[idx]: + final_value = final_value + ", " + str(value_map[idx]) + + # we need to trim the firsts initial added ', ' + if len(final_value) > 0: + final_value = final_value[2:] + + result[a_wphp_tag]["value"] = final_value except KeyError: _LOGGER.warning( - f"Key Error while read_values. EcoTag: {a_wphp_tag} vals: {t_values} states: {t_states}") + f"Key Error while read_values. EcoTag: {a_wphp_tag} t_values: {t_values} t_states: {t_states}") except Exception as other_exc: _LOGGER.error( - f"Exception {other_exc} while read_values. EcoTag: {a_wphp_tag} vals: {t_values} states: {t_states} -> {other_exc}" + f"Exception of type '{other_exc}' while read_values. EcoTag: {a_wphp_tag} t_values: {t_values} t_states: {t_states} -> {other_exc}" ) return result diff --git a/custom_components/waterkotte_heatpump/pywaterkotte_ha/tags.py b/custom_components/waterkotte_heatpump/pywaterkotte_ha/tags.py index 63e7367..99043c9 100644 --- a/custom_components/waterkotte_heatpump/pywaterkotte_ha/tags.py +++ b/custom_components/waterkotte_heatpump/pywaterkotte_ha/tags.py @@ -35,6 +35,9 @@ def _decode_value_analog(self, str_vals: List[str]): return self.__decode_value_default(str_vals, factor=-1.0) def __decode_value_default(self, str_vals: List[str], factor: float): + if str_vals is None: + return None + first_val = str_vals[0] if first_val is None: # do not check any further if for what ever reason the first value of the str_vals is None @@ -125,6 +128,9 @@ def __encode_value_default(self, value, encoded_values, factor: int): encoded_values[ecotouch_tag] = str(int(value)) def _decode_alarms(self, str_vals: List[str], lang_map: dict): + if str_vals is None: + return None + error_tag_index = 0 final_value = "" for a_val in str_vals: @@ -153,6 +159,9 @@ def _decode_alarms(self, str_vals: List[str], lang_map: dict): return final_value def _decode_datetime(self, str_vals: List[str]): + if str_vals is None: + return None + int_vals = list(map(int, str_vals)) if int_vals[0] < 2000: int_vals[0] = int_vals[0] + 2000 @@ -184,6 +193,9 @@ def _encode_datetime(self, value, encoded_values): encoded_values[tags] = vals[i] def _decode_time_hhmm(self, str_vals: List[str]): + if str_vals is None: + return None + int_vals = list(map(int, str_vals)) if int_vals[0] > 23: int_vals[0] = 0 @@ -203,6 +215,9 @@ def _encode_time_hhmm(self, value, encoded_values): encoded_values[tags] = vals[i] def _decode_state(self, str_vals: List[str]): + if str_vals is None: + return None + assert len(self.tags) == 1 if str_vals[0] == "0": return "off" @@ -225,12 +240,15 @@ def _encode_state(self, value, encoded_values): encoded_values[ecotouch_tag] = "2" def _decode_six_steps_mode(self, str_vals: List[str]): + if str_vals is None: + return None + assert len(self.tags) == 1 - int_val = int(str_vals[0]) - if 0 <= int_val <= len(SIX_STEPS_MODES): - return SIX_STEPS_MODES[int_val] - else: - return "Error" + if str_vals[0] is not None: + int_val = int(str_vals[0]) + if 0 <= int_val <= len(SIX_STEPS_MODES): + return SIX_STEPS_MODES[int_val] + return "Error" def _encode_six_steps_mode(self, value, encoded_values): assert len(self.tags) == 1 @@ -249,6 +267,9 @@ def _get_key_from_value(a_dict: dict, value_to_find): return None def _decode_status(self, str_vals: List[str]): + if str_vals is None: + return None + assert len(self.tags) == 1 if str_vals[0] == "0": return "off" @@ -271,6 +292,9 @@ def _encode_status(self, value, encoded_values): encoded_values[ecotouch_tag] = "2" def _decode_ro_series(self, str_vals: List[str]): + if str_vals is None: + return None + if str_vals[0]: if isinstance(str_vals[0], int): idx = int(str_vals[0]) @@ -282,6 +306,9 @@ def _decode_ro_series(self, str_vals: List[str]): return "UNKNOWN_SERIES" def _decode_ro_id(self, str_vals: List[str]): + if str_vals is None: + return None + assert len(self.tags) == 1 if str_vals[0]: if isinstance(str_vals[0], int): @@ -295,6 +322,9 @@ def _decode_ro_id(self, str_vals: List[str]): def _decode_ro_bios(self, str_vals: List[str]): + if str_vals is None: + return None + assert len(self.tags) == 1 str_val = str_vals[0] if len(str_val) > 2: @@ -303,6 +333,9 @@ def _decode_ro_bios(self, str_vals: List[str]): return str_val def _decode_ro_fw(self, str_vals: List[str]): + if str_vals is None: + return None + assert len(self.tags) == 2 str_val1 = str_vals[0] str_val2 = str_vals[1] @@ -314,6 +347,9 @@ def _decode_ro_fw(self, str_vals: List[str]): return f"FW_{str_val1}-{str_val2}" def _decode_ro_sn(self, str_vals: List[str]): + if str_vals is None: + return None + assert len(self.tags) == 2 sn1 = int(str_vals[0]) sn2 = int(str_vals[1]) @@ -327,6 +363,9 @@ def _decode_ro_sn(self, str_vals: List[str]): return f"Serial_{sn1}-{sn2}" def _decode_year(self, str_vals: List[str]): + if str_vals is None: + return None + assert len(self.tags) == 1 return int(str_vals[0]) + 2000 diff --git a/custom_components/waterkotte_heatpump/sensor.py b/custom_components/waterkotte_heatpump/sensor.py index d01a826..d8e3aa1 100644 --- a/custom_components/waterkotte_heatpump/sensor.py +++ b/custom_components/waterkotte_heatpump/sensor.py @@ -1,13 +1,12 @@ import logging from datetime import datetime, time +from homeassistant.components.sensor import SensorEntity, SensorDeviceClass from homeassistant.config_entries import ConfigEntry +from homeassistant.const import EntityCategory +from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.restore_state import RestoreEntity -from homeassistant.core import HomeAssistant -from homeassistant.const import EntityCategory -from homeassistant.components.sensor import SensorEntity - from . import WKHPDataUpdateCoordinator, WKHPBaseEntity from .const import DOMAIN, SENSOR_SENSORS, ExtSensorEntityDescription from .const_gen import SENSOR_SENSORS_GENERATED @@ -33,6 +32,11 @@ class WKHPSensor(WKHPBaseEntity, SensorEntity, RestoreEntity): def __init__(self, coordinator: WKHPDataUpdateCoordinator, description: ExtSensorEntityDescription): super().__init__(coordinator=coordinator, description=description) + # if description.device_class is not None and description.device_class.SensorDeviceClass.DATE: + # if description.tag == WKHPTag.SCHEDULE_WATER_DISINFECTION_START_TIME: + # self._attr_native_value = time + # else: + # self._attr_native_value = datetime # self._previous_float_value: float | None = None # self._is_total_increasing: bool = description is not None and isinstance(description, @@ -45,29 +49,40 @@ def _is_bit_field(self) -> bool: @property def state(self): + # for SensorDeviceClass.DATE we will use out OWN 'state' render impl!!! + if self.entity_description.device_class == SensorDeviceClass.DATE: + value = self.native_value + if value is None: + value = "unknown" + return value + else: + return SensorEntity.state.fget(self) + + @property + def native_value(self): """Return the state of the sensor.""" try: value = self.coordinator.data[self.wkhp_tag]["value"] - if value is None or value == "": + if value is None or len(str(value)) == 0: if self._is_bit_field: value = "none" else: - value = "unknown" + value = None else: if isinstance(value, datetime): return value.isoformat(sep=' ', timespec="minutes") elif isinstance(value, time): return value.isoformat(timespec="minutes") - elif self.entity_description.suggested_display_precision is not None: - value = round(float(value), self.entity_description.suggested_display_precision) - except KeyError: - value = "unknown" - except TypeError: - return "unknown" - if value is True: - value = "on" - elif value is False: - value = "off" + elif isinstance(value, bool): + if value is True: + value = "on" + elif value is False: + value = "off" + + except (KeyError, TypeError): + value = None + + # final return statement... return value @property diff --git a/hacs.json b/hacs.json index abafa08..ef8a614 100644 --- a/hacs.json +++ b/hacs.json @@ -1,8 +1,5 @@ { "name": "Waterkotte Heatpump [+2020]", - "country": [ - "ALL" - ], "homeassistant": "2023.7.0", "hacs": "1.18.0", "render_readme": true diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5be84db --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +homeassistant>=2024.8.2 \ No newline at end of file diff --git a/requirements_dev.txt b/requirements_dev.txt new file mode 100644 index 0000000..2e0ce26 --- /dev/null +++ b/requirements_dev.txt @@ -0,0 +1 @@ +pytest-homeassistant-custom-component>=0.13.154