From ead6d922a3f2ad342a8112b6f2e32d9c6af0df41 Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Tue, 10 Aug 2021 02:27:29 -0700 Subject: [PATCH] Split entity between entity and executor logic [refactor] (#506) --- .../tahoma/alarm_control_panel.py | 62 +++--- custom_components/tahoma/binary_sensor.py | 4 +- .../atlantic_electrical_heater.py | 14 +- ...er_with_adjustable_temperature_setpoint.py | 34 +-- .../atlantic_electrical_towel_dryer.py | 26 +-- ...antic_pass_apc_heating_and_cooling_zone.py | 34 +-- .../atlantic_pass_apc_zone_control.py | 10 +- .../climate_devices/atlantic_pass_apcdhw.py | 38 ++-- .../dimmer_exterior_heating.py | 18 +- .../climate_devices/evo_home_controller.py | 12 +- .../climate_devices/heating_set_point.py | 10 +- .../hitachi_air_to_air_heat_pump.py | 12 +- .../hitachi_air_to_water_heating_zone.py | 24 ++- .../climate_devices/somfy_thermostat.py | 40 ++-- .../stateless_exterior_heating.py | 10 +- custom_components/tahoma/cover.py | 5 +- .../tahoma/cover_devices/awning.py | 16 +- .../tahoma/cover_devices/tahoma_cover.py | 68 +++--- .../tahoma/cover_devices/vertical_cover.py | 20 +- custom_components/tahoma/entity.py | 138 ++++++++++++ custom_components/tahoma/executor.py | 98 +++++++++ custom_components/tahoma/light.py | 36 ++-- custom_components/tahoma/lock.py | 10 +- custom_components/tahoma/sensor.py | 8 +- custom_components/tahoma/switch.py | 41 ++-- custom_components/tahoma/tahoma_entity.py | 204 ------------------ .../domestic_hot_water_production.py | 29 +-- .../water_heater_devices/hitachi_dhw.py | 26 ++- 28 files changed, 571 insertions(+), 476 deletions(-) create mode 100644 custom_components/tahoma/entity.py create mode 100644 custom_components/tahoma/executor.py delete mode 100644 custom_components/tahoma/tahoma_entity.py diff --git a/custom_components/tahoma/alarm_control_panel.py b/custom_components/tahoma/alarm_control_panel.py index 928650aeb..57e08bd97 100644 --- a/custom_components/tahoma/alarm_control_panel.py +++ b/custom_components/tahoma/alarm_control_panel.py @@ -25,7 +25,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN -from .tahoma_entity import TahomaEntity +from .entity import OverkizEntity COMMAND_ALARM_OFF = "alarmOff" COMMAND_ALARM_ON = "alarmOn" @@ -95,14 +95,16 @@ async def async_setup_entry( async_add_entities(entities) -class TahomaAlarmControlPanel(TahomaEntity, AlarmControlPanelEntity): +class TahomaAlarmControlPanel(OverkizEntity, AlarmControlPanelEntity): """Representation of a TaHoma Alarm Control Panel.""" @property def state(self): """Return the state of the device.""" - if self.has_state(CORE_INTRUSION_STATE, INTERNAL_INTRUSION_DETECTED_STATE): - state = self.select_state( + if self.executor.has_state( + CORE_INTRUSION_STATE, INTERNAL_INTRUSION_DETECTED_STATE + ): + state = self.executor.select_state( CORE_INTRUSION_STATE, INTERNAL_INTRUSION_DETECTED_STATE ) if state == STATE_DETECTED: @@ -111,24 +113,26 @@ def state(self): return STATE_ALARM_PENDING if ( - self.has_state(INTERNAL_CURRENT_ALARM_MODE_STATE) - and self.has_state(INTERNAL_TARGET_ALARM_MODE_STATE) - and self.select_state(INTERNAL_CURRENT_ALARM_MODE_STATE) - != self.select_state(INTERNAL_TARGET_ALARM_MODE_STATE) + self.executor.has_state(INTERNAL_CURRENT_ALARM_MODE_STATE) + and self.executor.has_state(INTERNAL_TARGET_ALARM_MODE_STATE) + and self.executor.select_state(INTERNAL_CURRENT_ALARM_MODE_STATE) + != self.executor.select_state(INTERNAL_TARGET_ALARM_MODE_STATE) ): return STATE_ALARM_PENDING - if self.has_state(MYFOX_ALARM_STATUS_STATE): - return MAP_MYFOX_STATUS_STATE[self.select_state(MYFOX_ALARM_STATUS_STATE)] + if self.executor.has_state(MYFOX_ALARM_STATUS_STATE): + return MAP_MYFOX_STATUS_STATE[ + self.executor.select_state(MYFOX_ALARM_STATUS_STATE) + ] - if self.has_state(INTERNAL_CURRENT_ALARM_MODE_STATE): + if self.executor.has_state(INTERNAL_CURRENT_ALARM_MODE_STATE): return MAP_INTERNAL_STATUS_STATE[ - self.select_state(INTERNAL_CURRENT_ALARM_MODE_STATE) + self.executor.select_state(INTERNAL_CURRENT_ALARM_MODE_STATE) ] - if self.has_state(VERISURE_ALARM_PANEL_MAIN_ARM_TYPE_STATE): + if self.executor.has_state(VERISURE_ALARM_PANEL_MAIN_ARM_TYPE_STATE): return MAP_VERISURE_STATUS_STATE[ - self.select_state(VERISURE_ALARM_PANEL_MAIN_ARM_TYPE_STATE) + self.executor.select_state(VERISURE_ALARM_PANEL_MAIN_ARM_TYPE_STATE) ] return None @@ -138,18 +142,18 @@ def supported_features(self) -> int: """Return the list of supported features.""" supported_features = 0 - if self.has_command(COMMAND_ARM, COMMAND_ALARM_ON): + if self.executor.has_command(COMMAND_ARM, COMMAND_ALARM_ON): supported_features |= SUPPORT_ALARM_ARM_AWAY - if self.has_command(COMMAND_ALARM_PARTIAL_1, COMMAND_ARM_PARTIAL_DAY): + if self.executor.has_command(COMMAND_ALARM_PARTIAL_1, COMMAND_ARM_PARTIAL_DAY): supported_features |= SUPPORT_ALARM_ARM_HOME - if self.has_command( + if self.executor.has_command( COMMAND_PARTIAL, COMMAND_ALARM_PARTIAL_2, COMMAND_ARM_PARTIAL_NIGHT ): supported_features |= SUPPORT_ALARM_ARM_NIGHT - if self.has_command(COMMAND_SET_ALARM_STATUS): + if self.executor.has_command(COMMAND_SET_ALARM_STATUS): supported_features |= SUPPORT_ALARM_TRIGGER supported_features |= SUPPORT_ALARM_ARM_CUSTOM_BYPASS @@ -157,40 +161,40 @@ def supported_features(self) -> int: async def async_alarm_disarm(self, code=None): """Send disarm command.""" - await self.async_execute_command( - self.select_command(COMMAND_DISARM, COMMAND_ALARM_OFF) + await self.executor.async_execute_command( + self.executor.select_command(COMMAND_DISARM, COMMAND_ALARM_OFF) ) async def async_alarm_arm_home(self, code=None): """Send arm home command.""" - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_ALARM_PARTIAL_1, COMMAND_ARM_PARTIAL_DAY ) async def async_alarm_arm_night(self, code=None): """Send arm night command.""" - await self.async_execute_command( - self.select_command( + await self.executor.async_execute_command( + self.executor.select_command( COMMAND_PARTIAL, COMMAND_ALARM_PARTIAL_2, COMMAND_ARM_PARTIAL_NIGHT ) ) async def async_alarm_arm_away(self, code=None): """Send arm away command.""" - await self.async_execute_command( - self.select_command(COMMAND_ARM, COMMAND_ALARM_ON) + await self.executor.async_execute_command( + self.executor.select_command(COMMAND_ARM, COMMAND_ALARM_ON) ) async def async_alarm_trigger(self, code=None) -> None: """Send alarm trigger command.""" - await self.async_execute_command( - self.select_command(COMMAND_SET_ALARM_STATUS, STATE_DETECTED) + await self.executor.async_execute_command( + self.executor.select_command(COMMAND_SET_ALARM_STATUS, STATE_DETECTED) ) async def async_alarm_arm_custom_bypass(self, code=None) -> None: """Send arm custom bypass command.""" - await self.async_execute_command( - self.select_command(COMMAND_SET_ALARM_STATUS, STATE_UNDETECTED) + await self.executor.async_execute_command( + self.executor.select_command(COMMAND_SET_ALARM_STATUS, STATE_UNDETECTED) ) @property diff --git a/custom_components/tahoma/binary_sensor.py b/custom_components/tahoma/binary_sensor.py index 9886554d9..db7e08ab0 100644 --- a/custom_components/tahoma/binary_sensor.py +++ b/custom_components/tahoma/binary_sensor.py @@ -16,7 +16,7 @@ from .const import DOMAIN from .coordinator import TahomaDataUpdateCoordinator -from .tahoma_entity import TahomaEntity +from .entity import OverkizEntity STATE_OPEN = "open" STATE_PERSON_INSIDE = "personInside" @@ -121,7 +121,7 @@ async def async_setup_entry( async_add_entities(entities) -class TahomaBinarySensor(TahomaEntity, BinarySensorEntity): +class TahomaBinarySensor(OverkizEntity, BinarySensorEntity): """Representation of a TaHoma Binary Sensor.""" def __init__( diff --git a/custom_components/tahoma/climate_devices/atlantic_electrical_heater.py b/custom_components/tahoma/climate_devices/atlantic_electrical_heater.py index 4dc32e7a3..6afef3731 100644 --- a/custom_components/tahoma/climate_devices/atlantic_electrical_heater.py +++ b/custom_components/tahoma/climate_devices/atlantic_electrical_heater.py @@ -14,7 +14,7 @@ ) from homeassistant.const import TEMP_CELSIUS -from ..tahoma_entity import TahomaEntity +from ..entity import OverkizEntity COMMAND_SET_HEATING_LEVEL = "setHeatingLevel" @@ -44,7 +44,7 @@ HVAC_MODES_TO_TAHOMA = {v: k for k, v in TAHOMA_TO_HVAC_MODES.items()} -class AtlanticElectricalHeater(TahomaEntity, ClimateEntity): +class AtlanticElectricalHeater(OverkizEntity, ClimateEntity): """Representation of Atlantic Electrical Heater.""" @property @@ -60,7 +60,7 @@ def supported_features(self) -> int: @property def hvac_mode(self) -> str: """Return hvac operation ie. heat, cool mode.""" - return TAHOMA_TO_HVAC_MODES[self.select_state(CORE_ON_OFF_STATE)] + return TAHOMA_TO_HVAC_MODES[self.executor.select_state(CORE_ON_OFF_STATE)] @property def hvac_modes(self) -> List[str]: @@ -69,14 +69,16 @@ def hvac_modes(self) -> List[str]: async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set new target hvac mode.""" - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_HEATING_LEVEL, HVAC_MODES_TO_TAHOMA[hvac_mode] ) @property def preset_mode(self) -> Optional[str]: """Return the current preset mode, e.g., home, away, temp.""" - return TAHOMA_TO_PRESET_MODES[self.select_state(IO_TARGET_HEATING_LEVEL_STATE)] + return TAHOMA_TO_PRESET_MODES[ + self.executor.select_state(IO_TARGET_HEATING_LEVEL_STATE) + ] @property def preset_modes(self) -> Optional[List[str]]: @@ -85,6 +87,6 @@ def preset_modes(self) -> Optional[List[str]]: async def async_set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_HEATING_LEVEL, PRESET_MODES_TO_TAHOMA[preset_mode] ) diff --git a/custom_components/tahoma/climate_devices/atlantic_electrical_heater_with_adjustable_temperature_setpoint.py b/custom_components/tahoma/climate_devices/atlantic_electrical_heater_with_adjustable_temperature_setpoint.py index d2ea2e6ed..02caf2fdc 100644 --- a/custom_components/tahoma/climate_devices/atlantic_electrical_heater_with_adjustable_temperature_setpoint.py +++ b/custom_components/tahoma/climate_devices/atlantic_electrical_heater_with_adjustable_temperature_setpoint.py @@ -26,7 +26,7 @@ from homeassistant.helpers.event import async_track_state_change from ..coordinator import TahomaDataUpdateCoordinator -from ..tahoma_entity import TahomaEntity +from ..entity import OverkizEntity _LOGGER = logging.getLogger(__name__) @@ -80,7 +80,7 @@ class AtlanticElectricalHeaterWithAdjustableTemperatureSetpoint( - TahomaEntity, ClimateEntity + OverkizEntity, ClimateEntity ): """Representation of Atlantic Electrical Heater (With Adjustable Temperature Setpoint).""" @@ -164,10 +164,10 @@ def supported_features(self) -> int: """Return the list of supported features.""" supported_features = 0 - if self.has_command(COMMAND_SET_HEATING_LEVEL): + if self.executor.has_command(COMMAND_SET_HEATING_LEVEL): supported_features |= SUPPORT_PRESET_MODE - if self.has_command(COMMAND_SET_TARGET_TEMPERATURE): + if self.executor.has_command(COMMAND_SET_TARGET_TEMPERATURE): supported_features |= SUPPORT_TARGET_TEMPERATURE return supported_features @@ -181,23 +181,25 @@ def hvac_modes(self) -> List[str]: def hvac_mode(self) -> str: """Return hvac operation ie. heat, cool mode.""" if CORE_OPERATING_MODE_STATE in self.device.states: - return TAHOMA_TO_HVAC_MODE[self.select_state(CORE_OPERATING_MODE_STATE)] + return TAHOMA_TO_HVAC_MODE[ + self.executor.select_state(CORE_OPERATING_MODE_STATE) + ] if CORE_ON_OFF_STATE in self.device.states: - return TAHOMA_TO_HVAC_MODE[self.select_state(CORE_ON_OFF_STATE)] + return TAHOMA_TO_HVAC_MODE[self.executor.select_state(CORE_ON_OFF_STATE)] async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set new target hvac mode.""" if CORE_OPERATING_MODE_STATE in self.device.states: - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_OPERATING_MODE, HVAC_MODE_TO_TAHOMA[hvac_mode] ) else: if hvac_mode == HVAC_MODE_OFF: - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_OFF, ) else: - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_HEATING_LEVEL, PRESET_STATE_COMFORT ) @@ -209,16 +211,18 @@ def preset_modes(self) -> Optional[List[str]]: @property def preset_mode(self) -> Optional[str]: """Return the current preset mode, e.g., home, away, temp.""" - return TAHOMA_TO_PRESET_MODE[self.select_state(IO_TARGET_HEATING_LEVEL_STATE)] + return TAHOMA_TO_PRESET_MODE[ + self.executor.select_state(IO_TARGET_HEATING_LEVEL_STATE) + ] async def async_set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" if preset_mode == PRESET_AUTO or preset_mode == PRESET_PROG: - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_OPERATING_MODE, PRESET_MODE_TO_TAHOMA[preset_mode] ) else: - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_HEATING_LEVEL, PRESET_MODE_TO_TAHOMA[preset_mode] ) @@ -226,7 +230,7 @@ async def async_set_preset_mode(self, preset_mode: str) -> None: def target_temperature(self) -> None: """Return the temperature.""" if CORE_TARGET_TEMPERATURE_STATE in self.device.states: - return self.select_state(CORE_TARGET_TEMPERATURE_STATE) + return self.executor.select_state(CORE_TARGET_TEMPERATURE_STATE) @property def current_temperature(self): @@ -236,4 +240,6 @@ def current_temperature(self): async def async_set_temperature(self, **kwargs) -> None: """Set new temperature.""" temperature = kwargs.get(ATTR_TEMPERATURE) - await self.async_execute_command(COMMAND_SET_TARGET_TEMPERATURE, temperature) + await self.executor.async_execute_command( + COMMAND_SET_TARGET_TEMPERATURE, temperature + ) diff --git a/custom_components/tahoma/climate_devices/atlantic_electrical_towel_dryer.py b/custom_components/tahoma/climate_devices/atlantic_electrical_towel_dryer.py index 529ad2283..77564f09b 100644 --- a/custom_components/tahoma/climate_devices/atlantic_electrical_towel_dryer.py +++ b/custom_components/tahoma/climate_devices/atlantic_electrical_towel_dryer.py @@ -15,7 +15,7 @@ ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS -from ..tahoma_entity import TahomaEntity +from ..entity import OverkizEntity _LOGGER = logging.getLogger(__name__) @@ -63,7 +63,7 @@ HVAC_MODE_TO_TAHOMA = {v: k for k, v in TAHOMA_TO_HVAC_MODE.items()} -class AtlanticElectricalTowelDryer(TahomaEntity, ClimateEntity): +class AtlanticElectricalTowelDryer(OverkizEntity, ClimateEntity): """Representation of Atlantic Electrical Towel Dryer.""" @property @@ -85,14 +85,16 @@ def hvac_modes(self) -> List[str]: def hvac_mode(self) -> str: """Return hvac operation ie. heat, cool mode.""" if CORE_OPERATING_MODE_STATE in self.device.states: - return TAHOMA_TO_HVAC_MODE[self.select_state(CORE_OPERATING_MODE_STATE)] + return TAHOMA_TO_HVAC_MODE[ + self.executor.select_state(CORE_OPERATING_MODE_STATE) + ] if CORE_ON_OFF_STATE in self.device.states: - return TAHOMA_TO_HVAC_MODE[self.select_state(CORE_ON_OFF_STATE)] + return TAHOMA_TO_HVAC_MODE[self.executor.select_state(CORE_ON_OFF_STATE)] async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set new target hvac mode.""" - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_TOWEL_DRYER_OPERATING_MODE, HVAC_MODE_TO_TAHOMA[hvac_mode] ) @@ -105,12 +107,12 @@ def preset_modes(self) -> Optional[List[str]]: def preset_mode(self) -> Optional[str]: """Return the current preset mode, e.g., home, away, temp.""" return TAHOMA_TO_PRESET_MODE[ - self.select_state(IO_TOWEL_DRYER_TEMPORARY_STATE_STATE) + self.executor.select_state(IO_TOWEL_DRYER_TEMPORARY_STATE_STATE) ] async def async_set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_TOWEL_DRYER_TEMPORARY_STATE, PRESET_MODE_TO_TAHOMA[preset_mode] ) @@ -118,24 +120,24 @@ async def async_set_preset_mode(self, preset_mode: str) -> None: def target_temperature(self) -> None: """Return the temperature.""" if self.hvac_mode == HVAC_MODE_AUTO: - return self.select_state(IO_EFFECTIVE_TEMPERATURE_SETPOINT_STATE) + return self.executor.select_state(IO_EFFECTIVE_TEMPERATURE_SETPOINT_STATE) else: - return self.select_state(CORE_TARGET_TEMPERATURE_STATE) + return self.executor.select_state(CORE_TARGET_TEMPERATURE_STATE) @property def current_temperature(self): """Return current temperature.""" - return self.select_state(CORE_COMFORT_ROOM_TEMPERATURE_STATE) + return self.executor.select_state(CORE_COMFORT_ROOM_TEMPERATURE_STATE) async def async_set_temperature(self, **kwargs) -> None: """Set new temperature.""" temperature = kwargs.get(ATTR_TEMPERATURE) if self.hvac_mode == HVAC_MODE_AUTO: - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_DEROGATED_TARGET_TEMPERATURE, temperature ) else: - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_TARGET_TEMPERATURE, temperature ) diff --git a/custom_components/tahoma/climate_devices/atlantic_pass_apc_heating_and_cooling_zone.py b/custom_components/tahoma/climate_devices/atlantic_pass_apc_heating_and_cooling_zone.py index e349a4d67..af7331054 100644 --- a/custom_components/tahoma/climate_devices/atlantic_pass_apc_heating_and_cooling_zone.py +++ b/custom_components/tahoma/climate_devices/atlantic_pass_apc_heating_and_cooling_zone.py @@ -19,7 +19,7 @@ from homeassistant.helpers.event import async_track_state_change from ..coordinator import TahomaDataUpdateCoordinator -from ..tahoma_entity import TahomaEntity +from ..entity import OverkizEntity _LOGGER = logging.getLogger(__name__) @@ -59,7 +59,7 @@ HVAC_MODE_TO_TAHOMA = {v: k for k, v in TAHOMA_TO_HVAC_MODE.items()} -class AtlanticPassAPCHeatingAndCoolingZone(TahomaEntity, ClimateEntity): +class AtlanticPassAPCHeatingAndCoolingZone(OverkizEntity, ClimateEntity): """Representation of Atlantic Pass APC Heating and Cooling Zone.""" def __init__(self, device_url: str, coordinator: TahomaDataUpdateCoordinator): @@ -132,12 +132,12 @@ def update_temp(self, state): @property def min_temp(self) -> float: """Return the minimum temperature.""" - return self.select_state(CORE_MINIMUM_HEATING_TARGET_TEMPERATURE_STATE) + return self.executor.select_state(CORE_MINIMUM_HEATING_TARGET_TEMPERATURE_STATE) @property def max_temp(self) -> float: """Return the maximum temperature.""" - return self.select_state(CORE_MAXIMUM_HEATING_TARGET_TEMPERATURE_STATE) + return self.executor.select_state(CORE_MAXIMUM_HEATING_TARGET_TEMPERATURE_STATE) @property def current_temperature(self) -> Optional[float]: @@ -166,36 +166,44 @@ def hvac_modes(self) -> List[str]: def hvac_mode(self) -> str: """Return hvac operation ie. heat, cool mode.""" - if self.select_state(CORE_HEATING_ON_OFF_STATE) == "off": + if self.executor.select_state(CORE_HEATING_ON_OFF_STATE) == "off": return HVAC_MODE_OFF - return TAHOMA_TO_HVAC_MODE[self.select_state(IO_PASS_APC_HEATING_MODE_STATE)] + return TAHOMA_TO_HVAC_MODE[ + self.executor.select_state(IO_PASS_APC_HEATING_MODE_STATE) + ] async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set new target hvac mode.""" if hvac_mode == HVAC_MODE_OFF: - await self.async_execute_command(COMMAND_SET_HEATING_ON_OFF_STATE, "off") + await self.executor.async_execute_command( + COMMAND_SET_HEATING_ON_OFF_STATE, "off" + ) else: if self.hvac_mode == HVAC_MODE_OFF: - await self.async_execute_command(COMMAND_SET_HEATING_ON_OFF_STATE, "on") + await self.executor.async_execute_command( + COMMAND_SET_HEATING_ON_OFF_STATE, "on" + ) - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_PASS_APC_HEATING_MODE, HVAC_MODE_TO_TAHOMA[hvac_mode] ) - await self.async_execute_command(COMMAND_REFRESH_PASS_APC_HEATING_PROFILE) + await self.executor.async_execute_command( + COMMAND_REFRESH_PASS_APC_HEATING_PROFILE + ) @property def target_temperature(self) -> None: """Return the temperature.""" - return self.select_state(CORE_HEATING_TARGET_TEMPERATURE_STATE) + return self.executor.select_state(CORE_HEATING_TARGET_TEMPERATURE_STATE) async def async_set_temperature(self, **kwargs) -> None: """Set new temperature.""" temperature = kwargs.get(ATTR_TEMPERATURE) - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_HEATING_TARGET_TEMPERATURE, temperature ) - await self.async_execute_command(COMMAND_REFRESH_TARGET_TEMPERATURE) + await self.executor.async_execute_command(COMMAND_REFRESH_TARGET_TEMPERATURE) diff --git a/custom_components/tahoma/climate_devices/atlantic_pass_apc_zone_control.py b/custom_components/tahoma/climate_devices/atlantic_pass_apc_zone_control.py index 4b1b6444a..434382edc 100644 --- a/custom_components/tahoma/climate_devices/atlantic_pass_apc_zone_control.py +++ b/custom_components/tahoma/climate_devices/atlantic_pass_apc_zone_control.py @@ -11,7 +11,7 @@ ) from homeassistant.const import TEMP_CELSIUS -from ..tahoma_entity import TahomaEntity +from ..entity import OverkizEntity _LOGGER = logging.getLogger(__name__) @@ -30,7 +30,7 @@ HVAC_MODE_TO_TAHOMA = {v: k for k, v in TAHOMA_TO_HVAC_MODE.items()} -class AtlanticPassAPCZoneControl(TahomaEntity, ClimateEntity): +class AtlanticPassAPCZoneControl(OverkizEntity, ClimateEntity): """Representation of Atlantic Pass APC Zone Control.""" @property @@ -53,10 +53,12 @@ def hvac_modes(self) -> List[str]: @property def hvac_mode(self) -> str: """Return hvac operation ie. heat, cool mode.""" - return TAHOMA_TO_HVAC_MODE[self.select_state(IO_PASS_APC_OPERATING_MODE_STATE)] + return TAHOMA_TO_HVAC_MODE[ + self.executor.select_state(IO_PASS_APC_OPERATING_MODE_STATE) + ] async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set new target hvac mode.""" - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_PASS_APC_OPERATING_MODE, HVAC_MODE_TO_TAHOMA[hvac_mode] ) diff --git a/custom_components/tahoma/climate_devices/atlantic_pass_apcdhw.py b/custom_components/tahoma/climate_devices/atlantic_pass_apcdhw.py index 580fd749d..617497637 100644 --- a/custom_components/tahoma/climate_devices/atlantic_pass_apcdhw.py +++ b/custom_components/tahoma/climate_devices/atlantic_pass_apcdhw.py @@ -15,7 +15,7 @@ ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS -from ..tahoma_entity import TahomaEntity +from ..entity import OverkizEntity BOOST_ON_STATE = "on" BOOST_OFF_STATE = "off" @@ -70,7 +70,7 @@ MAP_REVERSE_PRESET_MODES = {v: k for k, v in MAP_PRESET_MODES.items()} -class AtlanticPassAPCDHW(TahomaEntity, ClimateEntity): +class AtlanticPassAPCDHW(OverkizEntity, ClimateEntity): """Representation of TaHoma IO Atlantic Electrical Heater.""" @property @@ -96,14 +96,16 @@ def max_temp(self) -> float: @property def preset_mode(self) -> Optional[str]: """Return the current preset mode, e.g., home, away, temp.""" - if self.select_state(IO_PASS_APCDWH_MODE_STATE) in [ + if self.executor.select_state(IO_PASS_APCDWH_MODE_STATE) in [ PASS_APCDHW_MODE_ECO, PASS_APCDWH_MODE_INTERNAL_SCHEDULING, PASS_APCDHW_MODE_STOP, ]: - return MAP_PRESET_MODES[self.select_state(IO_PASS_APCDWH_MODE_STATE)] + return MAP_PRESET_MODES[ + self.executor.select_state(IO_PASS_APCDWH_MODE_STATE) + ] - if self.select_state(CORE_BOOST_ON_OFF_STATE) == BOOST_ON_STATE: + if self.executor.select_state(CORE_BOOST_ON_OFF_STATE) == BOOST_ON_STATE: return PRESET_BOOST return PRESET_COMFORT @@ -127,18 +129,20 @@ async def async_set_preset_mode(self, preset_mode: str) -> None: preset_mode_to_set = MAP_REVERSE_PRESET_MODES[PRESET_COMFORT] boost_mode_to_set = BOOST_ON_STATE - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_BOOST_ON_OFF_STATE, boost_mode_to_set ) - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_PASS_APCDHW_MODE, preset_mode_to_set ) - await self.async_execute_command(COMMAND_REFRESH_TARGET_DWH_TEMPERATURE) + await self.executor.async_execute_command( + COMMAND_REFRESH_TARGET_DWH_TEMPERATURE + ) @property def hvac_mode(self) -> str: """Return hvac operation ie. heat, cool mode.""" - return MAP_HVAC_MODES[self.select_state(CORE_DWH_ON_OFF_STATE)] + return MAP_HVAC_MODES[self.executor.select_state(CORE_DWH_ON_OFF_STATE)] @property def hvac_modes(self) -> List[str]: @@ -147,7 +151,7 @@ def hvac_modes(self) -> List[str]: async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set new target hvac mode.""" - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_DWH_ON_OFF_STATE, MAP_REVERSE_HVAC_MODES[hvac_mode] ) @@ -155,12 +159,12 @@ async def async_set_hvac_mode(self, hvac_mode: str) -> None: def target_temperature(self) -> None: """Return the temperature corresponding to the PRESET.""" if self.preset_mode == PRESET_ECO: - return self.select_state(CORE_ECO_TARGET_DWH_TEMPERATURE_STATE) + return self.executor.select_state(CORE_ECO_TARGET_DWH_TEMPERATURE_STATE) if self.preset_mode in [PRESET_COMFORT, PRESET_BOOST]: - return self.select_state(CORE_COMFORT_TARGET_DWH_TEMPERATURE_STATE) + return self.executor.select_state(CORE_COMFORT_TARGET_DWH_TEMPERATURE_STATE) - return self.select_state(CORE_TARGET_DWH_TEMPERATURE_STATE) + return self.executor.select_state(CORE_TARGET_DWH_TEMPERATURE_STATE) @property def current_temperature(self): @@ -171,13 +175,15 @@ async def async_set_temperature(self, **kwargs) -> None: """Set new temperature for current preset.""" temperature = kwargs.get(ATTR_TEMPERATURE) if self.preset_mode == PRESET_ECO: - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_ECO_TARGET_DWH_TEMPERATURE, temperature ) if self.preset_mode in [PRESET_COMFORT, PRESET_BOOST]: - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_COMFORT_TARGET_DWH_TEMPERATURE, temperature ) - await self.async_execute_command(COMMAND_REFRESH_TARGET_DWH_TEMPERATURE) + await self.executor.async_execute_command( + COMMAND_REFRESH_TARGET_DWH_TEMPERATURE + ) diff --git a/custom_components/tahoma/climate_devices/dimmer_exterior_heating.py b/custom_components/tahoma/climate_devices/dimmer_exterior_heating.py index e07c0e485..453939228 100644 --- a/custom_components/tahoma/climate_devices/dimmer_exterior_heating.py +++ b/custom_components/tahoma/climate_devices/dimmer_exterior_heating.py @@ -11,7 +11,7 @@ from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from ..coordinator import TahomaDataUpdateCoordinator -from ..tahoma_entity import TahomaEntity +from ..entity import OverkizEntity _LOGGER = logging.getLogger(__name__) @@ -21,13 +21,13 @@ CORE_LEVEL_STATE = "core:LevelState" -class DimmerExteriorHeating(TahomaEntity, ClimateEntity): +class DimmerExteriorHeating(OverkizEntity, ClimateEntity): """Representation of TaHoma IO Atlantic Electrical Heater.""" def __init__(self, device_url: str, coordinator: TahomaDataUpdateCoordinator): """Init method.""" super().__init__(device_url, coordinator) - self._saved_level = 100 - self.select_state(CORE_LEVEL_STATE) + self._saved_level = 100 - self.executor.select_state(CORE_LEVEL_STATE) @property def supported_features(self) -> int: @@ -52,20 +52,20 @@ def max_temp(self) -> float: @property def target_temperature(self): """Return the temperature we try to reach.""" - return 100 - self.select_state(CORE_LEVEL_STATE) + return 100 - self.executor.select_state(CORE_LEVEL_STATE) async def async_set_temperature(self, **kwargs) -> None: """Set new target temperature.""" level = kwargs.get(ATTR_TEMPERATURE) if level is None: return - await self.async_execute_command(COMMAND_SET_LEVEL, 100 - int(level)) - await self.async_execute_command(COMMAND_GET_LEVEL) + await self.executor.async_execute_command(COMMAND_SET_LEVEL, 100 - int(level)) + await self.executor.async_execute_command(COMMAND_GET_LEVEL) @property def hvac_mode(self) -> str: """Return hvac operation ie. heat, cool mode.""" - if self.select_state(CORE_LEVEL_STATE) == 100: + if self.executor.select_state(CORE_LEVEL_STATE) == 100: return HVAC_MODE_OFF return HVAC_MODE_HEAT @@ -81,5 +81,5 @@ async def async_set_hvac_mode(self, hvac_mode: str) -> None: level = self._saved_level else: self._saved_level = self.target_temperature - await self.async_execute_command(COMMAND_SET_LEVEL, 100 - int(level)) - await self.async_execute_command(COMMAND_GET_LEVEL) + await self.executor.async_execute_command(COMMAND_SET_LEVEL, 100 - int(level)) + await self.executor.async_execute_command(COMMAND_GET_LEVEL) diff --git a/custom_components/tahoma/climate_devices/evo_home_controller.py b/custom_components/tahoma/climate_devices/evo_home_controller.py index 8464b061e..80f0b39b6 100644 --- a/custom_components/tahoma/climate_devices/evo_home_controller.py +++ b/custom_components/tahoma/climate_devices/evo_home_controller.py @@ -12,7 +12,7 @@ from homeassistant.const import TEMP_CELSIUS import homeassistant.util.dt as dt_util -from ..tahoma_entity import TahomaEntity +from ..entity import OverkizEntity PRESET_DAY_OFF = "day-off" PRESET_HOLIDAYS = "holidays" @@ -31,7 +31,7 @@ PRESET_MODES_TO_TAHOMA = {v: k for k, v in TAHOMA_TO_PRESET_MODES.items()} -class EvoHomeController(TahomaEntity, ClimateEntity): +class EvoHomeController(OverkizEntity, ClimateEntity): """Representation of EvoHomeController device.""" @property @@ -47,7 +47,7 @@ def supported_features(self) -> int: @property def hvac_mode(self) -> str: """Return hvac operation ie. heat, cool mode.""" - operating_mode = self.select_state(RAMSES_RAMSES_OPERATING_MODE_STATE) + operating_mode = self.executor.select_state(RAMSES_RAMSES_OPERATING_MODE_STATE) if operating_mode in TAHOMA_TO_HVAC_MODES: return TAHOMA_TO_HVAC_MODES[operating_mode] @@ -64,14 +64,14 @@ def hvac_modes(self) -> List[str]: async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set new target hvac mode.""" - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_OPERATING_MODE, HVAC_MODES_TO_TAHOMA[hvac_mode] ) @property def preset_mode(self) -> Optional[str]: """Return the current preset mode, e.g., home, away, temp.""" - operating_mode = self.select_state(RAMSES_RAMSES_OPERATING_MODE_STATE) + operating_mode = self.executor.select_state(RAMSES_RAMSES_OPERATING_MODE_STATE) if operating_mode in TAHOMA_TO_PRESET_MODES: return TAHOMA_TO_PRESET_MODES[operating_mode] @@ -97,7 +97,7 @@ async def async_set_preset_mode(self, preset_mode: str) -> None: ) + timedelta(days=7) time_interval = one_week_from_now - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_OPERATING_MODE, PRESET_MODES_TO_TAHOMA[preset_mode], time_interval.strftime("%Y/%m/%d %H:%M"), diff --git a/custom_components/tahoma/climate_devices/heating_set_point.py b/custom_components/tahoma/climate_devices/heating_set_point.py index 067e53cad..5adc86ef2 100644 --- a/custom_components/tahoma/climate_devices/heating_set_point.py +++ b/custom_components/tahoma/climate_devices/heating_set_point.py @@ -10,7 +10,7 @@ TEMP_KELVIN, ) -from ..tahoma_entity import TahomaEntity +from ..entity import OverkizEntity COMMAND_SET_TARGET_TEMPERATURE = "setTargetTemperature" @@ -28,7 +28,7 @@ } -class HeatingSetPoint(TahomaEntity, ClimateEntity): +class HeatingSetPoint(OverkizEntity, ClimateEntity): """Representation of EvoHome HeatingSetPoint device.""" @property @@ -61,7 +61,7 @@ def hvac_modes(self) -> List[str]: @property def current_temperature(self) -> Optional[float]: """Return the current temperature.""" - return self.select_state(CORE_TEMPERATURE_STATE) + return self.executor.select_state(CORE_TEMPERATURE_STATE) @property def target_temperature_step(self) -> Optional[float]: @@ -81,12 +81,12 @@ def max_temp(self) -> float: @property def target_temperature(self): """Return the temperature we try to reach.""" - return self.select_state(CORE_TARGET_TEMPERATURE_STATE) + return self.executor.select_state(CORE_TARGET_TEMPERATURE_STATE) async def async_set_temperature(self, **kwargs) -> None: """Set new target temperature.""" temperature = kwargs.get(ATTR_TEMPERATURE) - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_TARGET_TEMPERATURE, float(temperature) ) diff --git a/custom_components/tahoma/climate_devices/hitachi_air_to_air_heat_pump.py b/custom_components/tahoma/climate_devices/hitachi_air_to_air_heat_pump.py index 6fd66aca6..711925f81 100644 --- a/custom_components/tahoma/climate_devices/hitachi_air_to_air_heat_pump.py +++ b/custom_components/tahoma/climate_devices/hitachi_air_to_air_heat_pump.py @@ -26,7 +26,7 @@ ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS -from ..tahoma_entity import TahomaEntity +from ..entity import OverkizEntity _LOGGER = logging.getLogger(__name__) @@ -88,7 +88,7 @@ FAN_MODES_TO_OVP_TAHOMA = {v: k for k, v in OVP_TAHOMA_TO_FAN_MODES.items()} -class HitachiAirToAirHeatPump(TahomaEntity, ClimateEntity): +class HitachiAirToAirHeatPump(OverkizEntity, ClimateEntity): """Representation of HitachiAirToAirHeatPump.""" @property @@ -104,7 +104,7 @@ def supported_features(self) -> int: SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE | SUPPORT_PRESET_MODE ) - if self.has_state(*SWING_STATE): + if self.executor.has_state(*SWING_STATE): supported_features |= SUPPORT_SWING_MODE return supported_features @@ -231,7 +231,7 @@ async def _global_control( ): """Execute globalControl command with all parameters.""" if self.device.controllable_name == "ovp:HLinkMainController": - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_GLOBAL_CONTROL, main_operation or self._select_state(*MAIN_OPERATION_STATE), # Main Operation @@ -244,7 +244,7 @@ async def _global_control( swing_mode or self._select_state(*SWING_STATE), # Swing Mode ) else: - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_GLOBAL_CONTROL, main_operation or self._select_state(*MAIN_OPERATION_STATE), # Main Operation @@ -260,7 +260,7 @@ async def _global_control( def _select_state(self, *states) -> Optional[str]: """Make all strings lowercase, since Hi Kumo server returns capitalized strings for some devices.""" - state = self.select_state(*states) + state = self.executor.select_state(*states) if state and isinstance(state, str): return state.lower() diff --git a/custom_components/tahoma/climate_devices/hitachi_air_to_water_heating_zone.py b/custom_components/tahoma/climate_devices/hitachi_air_to_water_heating_zone.py index 3da728df5..479159322 100644 --- a/custom_components/tahoma/climate_devices/hitachi_air_to_water_heating_zone.py +++ b/custom_components/tahoma/climate_devices/hitachi_air_to_water_heating_zone.py @@ -13,7 +13,7 @@ ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS -from ..tahoma_entity import TahomaEntity +from ..entity import OverkizEntity _LOGGER = logging.getLogger(__name__) @@ -54,7 +54,7 @@ PRESET_MODE_TO_TAHOMA = {v: k for k, v in TAHOMA_TO_PRESET_MODE.items()} -class HitachiAirToWaterHeatingZone(TahomaEntity, ClimateEntity): +class HitachiAirToWaterHeatingZone(OverkizEntity, ClimateEntity): """Representation of HitachiAirToWaterHeatingZone.""" @property @@ -79,7 +79,7 @@ def supported_features(self) -> int: def hvac_mode(self) -> str: """Return hvac operation ie. heat, cool mode.""" return TAHOMA_TO_HVAC_MODE[ - self.select_state(MODBUS_AUTO_MANU_MODE_ZONE_1_STATE) + self.executor.select_state(MODBUS_AUTO_MANU_MODE_ZONE_1_STATE) ] @property @@ -89,7 +89,7 @@ def hvac_modes(self) -> List[str]: async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set new target hvac mode.""" - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_AUTO_MANU_MODE, HVAC_MODE_TO_TAHOMA[hvac_mode] ) @@ -101,18 +101,22 @@ def preset_modes(self) -> Optional[List[str]]: @property def preset_mode(self) -> Optional[str]: """Return the current preset mode, e.g., home, away, temp.""" - return TAHOMA_TO_PRESET_MODE[self.select_state(MODBUS_YUTAKI_TARGET_MODE_STATE)] + return TAHOMA_TO_PRESET_MODE[ + self.executor.select_state(MODBUS_YUTAKI_TARGET_MODE_STATE) + ] async def async_set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_TARGET_MODE, PRESET_MODE_TO_TAHOMA[preset_mode] ) @property def current_temperature(self) -> Optional[float]: """Return the current temperature.""" - return self.select_state(MODBUS_ROOM_AMBIENT_TEMPERATURE_STATUS_ZONE_1_STATE) + return self.executor.select_state( + MODBUS_ROOM_AMBIENT_TEMPERATURE_STATUS_ZONE_1_STATE + ) @property def min_temp(self) -> float: @@ -132,12 +136,14 @@ def target_temperature_step(self) -> Optional[float]: @property def target_temperature(self): """Return the temperature we try to reach.""" - return self.select_state(MODBUS_THERMOSTAT_SETTING_CONTROL_ZONE_1_STATE) + return self.executor.select_state( + MODBUS_THERMOSTAT_SETTING_CONTROL_ZONE_1_STATE + ) async def async_set_temperature(self, **kwargs) -> None: """Set new target temperature.""" temperature = kwargs.get(ATTR_TEMPERATURE) - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_THERMOSTAT_SETTING_CONTROL_ZONE_1, int(temperature) ) diff --git a/custom_components/tahoma/climate_devices/somfy_thermostat.py b/custom_components/tahoma/climate_devices/somfy_thermostat.py index 86ebb59bd..14c5c8399 100644 --- a/custom_components/tahoma/climate_devices/somfy_thermostat.py +++ b/custom_components/tahoma/climate_devices/somfy_thermostat.py @@ -25,7 +25,7 @@ from homeassistant.helpers.event import async_track_state_change from ..coordinator import TahomaDataUpdateCoordinator -from ..tahoma_entity import TahomaEntity +from ..entity import OverkizEntity _LOGGER = logging.getLogger(__name__) @@ -74,7 +74,7 @@ } -class SomfyThermostat(TahomaEntity, ClimateEntity): +class SomfyThermostat(OverkizEntity, ClimateEntity): """Representation of Somfy Smart Thermostat.""" def __init__(self, device_url: str, coordinator: TahomaDataUpdateCoordinator): @@ -85,11 +85,11 @@ def __init__(self, device_url: str, coordinator: TahomaDataUpdateCoordinator): if self.preset_mode == PRESET_NONE: self._saved_target_temp = None else: - self._saved_target_temp = self.select_state( + self._saved_target_temp = self.executor.select_state( MAP_PRESET_TEMPERATURES[self.preset_mode] ) else: - self._saved_target_temp = self.select_state( + self._saved_target_temp = self.executor.select_state( CORE_DEROGATED_TARGET_TEMPERATURE_STATE ) self._current_temperature = None @@ -163,7 +163,9 @@ def supported_features(self) -> int: @property def hvac_mode(self) -> str: """Return hvac operation ie. heat, cool mode.""" - return MAP_HVAC_MODES[self.select_state(CORE_DEROGATION_ACTIVATION_STATE)] + return MAP_HVAC_MODES[ + self.executor.select_state(CORE_DEROGATION_ACTIVATION_STATE) + ] @property def hvac_modes(self) -> List[str]: @@ -183,8 +185,10 @@ def hvac_action(self) -> str: def preset_mode(self) -> Optional[str]: """Return the current preset mode, e.g., home, away, temp.""" if self.hvac_mode == HVAC_MODE_AUTO: - return MAP_PRESET_MODES[self.select_state(ST_HEATING_MODE_STATE)] - return MAP_PRESET_MODES[self.select_state(ST_DEROGATION_HEATING_MODE_STATE)] + return MAP_PRESET_MODES[self.executor.select_state(ST_HEATING_MODE_STATE)] + return MAP_PRESET_MODES[ + self.executor.select_state(ST_DEROGATION_HEATING_MODE_STATE) + ] @property def preset_modes(self) -> Optional[List[str]]: @@ -218,8 +222,8 @@ def target_temperature(self): if self.hvac_mode == HVAC_MODE_AUTO: if self.preset_mode == PRESET_NONE: return None - return self.select_state(MAP_PRESET_TEMPERATURES[self.preset_mode]) - return self.select_state(CORE_DEROGATED_TARGET_TEMPERATURE_STATE) + return self.executor.select_state(MAP_PRESET_TEMPERATURES[self.preset_mode]) + return self.executor.select_state(CORE_DEROGATED_TARGET_TEMPERATURE_STATE) async def async_set_temperature(self, **kwargs) -> None: """Set new target temperature.""" @@ -232,13 +236,13 @@ async def async_set_temperature(self, **kwargs) -> None: elif temperature > self.max_temp: temperature = self.max_temp - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_DEROGATION, temperature, STATE_DEROGATION_FURTHER_NOTICE ) - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_MODE_TEMPERATURE, STATE_PRESET_MANUAL, temperature ) - await self.async_execute_command(COMMAND_REFRESH_STATE) + await self.executor.async_execute_command(COMMAND_REFRESH_STATE) async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set new target hvac mode.""" @@ -246,8 +250,8 @@ async def async_set_hvac_mode(self, hvac_mode: str) -> None: return if hvac_mode == HVAC_MODE_AUTO: self._saved_target_temp = self.target_temperature - await self.async_execute_command(COMMAND_EXIT_DEROGATION) - await self.async_execute_command(COMMAND_REFRESH_STATE) + await self.executor.async_execute_command(COMMAND_EXIT_DEROGATION) + await self.executor.async_execute_command(COMMAND_REFRESH_STATE) elif hvac_mode == HVAC_MODE_HEAT: await self.async_set_preset_mode(PRESET_NONE) @@ -257,20 +261,20 @@ async def async_set_preset_mode(self, preset_mode: str) -> None: return if preset_mode in [PRESET_FREEZE, PRESET_NIGHT, PRESET_AWAY, PRESET_HOME]: self._saved_target_temp = self.target_temperature - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_DEROGATION, MAP_REVERSE_PRESET_MODES[preset_mode], STATE_DEROGATION_FURTHER_NOTICE, ) elif preset_mode == PRESET_NONE: - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_DEROGATION, self._saved_target_temp, STATE_DEROGATION_FURTHER_NOTICE, ) - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_MODE_TEMPERATURE, STATE_PRESET_MANUAL, self._saved_target_temp, ) - await self.async_execute_command(COMMAND_REFRESH_STATE) + await self.executor.async_execute_command(COMMAND_REFRESH_STATE) diff --git a/custom_components/tahoma/climate_devices/stateless_exterior_heating.py b/custom_components/tahoma/climate_devices/stateless_exterior_heating.py index 5d821a239..a0cc2b7e3 100644 --- a/custom_components/tahoma/climate_devices/stateless_exterior_heating.py +++ b/custom_components/tahoma/climate_devices/stateless_exterior_heating.py @@ -10,7 +10,7 @@ ) from homeassistant.const import TEMP_CELSIUS -from ..tahoma_entity import TahomaEntity +from ..entity import OverkizEntity _LOGGER = logging.getLogger(__name__) @@ -21,7 +21,7 @@ PRESET_MY = "My" -class StatelessExteriorHeating(TahomaEntity, ClimateEntity): +class StatelessExteriorHeating(OverkizEntity, ClimateEntity): """Representation of TaHoma Stateless Exterior Heating device.""" @property @@ -47,7 +47,7 @@ def preset_modes(self) -> Optional[List[str]]: async def async_set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" if preset_mode in PRESET_MY: - await self.async_execute_command(COMMAND_MY) + await self.executor.async_execute_command(COMMAND_MY) else: _LOGGER.error( "Invalid preset mode %s for device %s", preset_mode, self.name @@ -66,6 +66,6 @@ def hvac_modes(self) -> List[str]: async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set new target hvac mode.""" if hvac_mode == HVAC_MODE_HEAT: - await self.async_execute_command(COMMAND_ON) + await self.executor.async_execute_command(COMMAND_ON) else: - await self.async_execute_command(COMMAND_OFF) + await self.executor.async_execute_command(COMMAND_OFF) diff --git a/custom_components/tahoma/cover.py b/custom_components/tahoma/cover.py index 829952cd3..607e5bc44 100644 --- a/custom_components/tahoma/cover.py +++ b/custom_components/tahoma/cover.py @@ -6,10 +6,9 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback import voluptuous as vol -from custom_components.tahoma.cover_devices.awning import Awning -from custom_components.tahoma.cover_devices.vertical_cover import VerticalCover - from .const import DOMAIN +from .cover_devices.awning import Awning +from .cover_devices.vertical_cover import VerticalCover SERVICE_COVER_MY_POSITION = "set_cover_my_position" SERVICE_COVER_POSITION_LOW_SPEED = "set_cover_position_low_speed" diff --git a/custom_components/tahoma/cover_devices/awning.py b/custom_components/tahoma/cover_devices/awning.py index 149aaab6b..ac043ba46 100644 --- a/custom_components/tahoma/cover_devices/awning.py +++ b/custom_components/tahoma/cover_devices/awning.py @@ -25,16 +25,16 @@ def supported_features(self): """Flag supported features.""" supported_features = super().supported_features - if self.has_command(COMMAND_SET_DEPLOYMENT): + if self.executor.has_command(COMMAND_SET_DEPLOYMENT): supported_features |= SUPPORT_SET_POSITION - if self.has_command(COMMAND_DEPLOY): + if self.executor.has_command(COMMAND_DEPLOY): supported_features |= SUPPORT_OPEN - if self.has_command(*COMMANDS_STOP): + if self.executor.has_command(*COMMANDS_STOP): supported_features |= SUPPORT_STOP - if self.has_command(COMMAND_UNDEPLOY): + if self.executor.has_command(COMMAND_UNDEPLOY): supported_features |= SUPPORT_CLOSE return supported_features @@ -51,17 +51,17 @@ def current_cover_position(self): None is unknown, 0 is closed, 100 is fully open. """ - return self.select_state(CORE_DEPLOYMENT_STATE) + return self.executor.select_state(CORE_DEPLOYMENT_STATE) async def async_set_cover_position(self, **kwargs): """Move the cover to a specific position.""" position = kwargs.get(ATTR_POSITION, 0) - await self.async_execute_command(COMMAND_SET_DEPLOYMENT, position) + await self.executor.async_execute_command(COMMAND_SET_DEPLOYMENT, position) async def async_open_cover(self, **_): """Open the cover.""" - await self.async_execute_command(COMMAND_DEPLOY) + await self.executor.async_execute_command(COMMAND_DEPLOY) async def async_close_cover(self, **_): """Close the cover.""" - await self.async_execute_command(COMMAND_UNDEPLOY) + await self.executor.async_execute_command(COMMAND_UNDEPLOY) diff --git a/custom_components/tahoma/cover_devices/tahoma_cover.py b/custom_components/tahoma/cover_devices/tahoma_cover.py index 6cd044e77..a08ed0619 100644 --- a/custom_components/tahoma/cover_devices/tahoma_cover.py +++ b/custom_components/tahoma/cover_devices/tahoma_cover.py @@ -9,7 +9,7 @@ CoverEntity, ) -from ..tahoma_entity import TahomaEntity +from ..entity import OverkizEntity ATTR_OBSTRUCTION_DETECTED = "obstruction-detected" @@ -76,7 +76,7 @@ SUPPORT_COVER_POSITION_LOW_SPEED = 1024 -class TahomaGenericCover(TahomaEntity, CoverEntity): +class TahomaGenericCover(OverkizEntity, CoverEntity): """Representation of a TaHoma Cover.""" @property @@ -85,7 +85,7 @@ def current_cover_tilt_position(self): None is unknown, 0 is closed, 100 is fully open. """ - position = self.select_state( + position = self.executor.select_state( CORE_SLATS_ORIENTATION_STATE, CORE_SLATE_ORIENTATION_STATE ) return 100 - position if position is not None else None @@ -94,14 +94,14 @@ async def async_set_cover_position_low_speed(self, **kwargs): """Move the cover to a specific position with a low speed.""" position = 100 - kwargs.get(ATTR_POSITION, 0) - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_CLOSURE_AND_LINEAR_SPEED, position, "lowspeed" ) async def async_set_cover_tilt_position(self, **kwargs): """Move the cover tilt to a specific position.""" - await self.async_execute_command( - self.select_command(*COMMANDS_SET_TILT_POSITION), + await self.executor.async_execute_command( + self.executor.select_command(*COMMANDS_SET_TILT_POSITION), 100 - kwargs.get(ATTR_TILT_POSITION, 0), ) @@ -109,7 +109,7 @@ async def async_set_cover_tilt_position(self, **kwargs): def is_closed(self): """Return if the cover is closed.""" - state = self.select_state( + state = self.executor.select_state( CORE_OPEN_CLOSED_STATE, CORE_SLATS_OPEN_CLOSED_STATE, CORE_OPEN_CLOSED_PARTIAL_STATE, @@ -128,13 +128,30 @@ def is_closed(self): return None + @property + def icon(self): + """Return the icon to use in the frontend, if any.""" + if ( + self.executor.has_state(CORE_PRIORITY_LOCK_TIMER_STATE) + and self.executor.select_state(CORE_PRIORITY_LOCK_TIMER_STATE) > 0 + ): + if self.executor.select_state(IO_PRIORITY_LOCK_ORIGINATOR_STATE) == "wind": + return ICON_WEATHER_WINDY + return ICON_LOCK_ALERT + + return None + async def async_open_cover_tilt(self, **_): """Open the cover tilt.""" - await self.async_execute_command(self.select_command(*COMMANDS_OPEN_TILT)) + await self.executor.async_execute_command( + self.executor.select_command(*COMMANDS_OPEN_TILT) + ) async def async_close_cover_tilt(self, **_): """Close the cover tilt.""" - await self.async_execute_command(self.select_command(*COMMANDS_CLOSE_TILT)) + await self.executor.async_execute_command( + self.executor.select_command(*COMMANDS_CLOSE_TILT) + ) async def async_stop_cover(self, **_): """Stop the cover.""" @@ -188,11 +205,13 @@ async def async_cancel_or_stop_cover(self, cancel_commands, stop_commands) -> No # Fallback to available stop commands when no executions are found # Stop commands don't work with all devices, due to a bug in Somfy service - await self.async_execute_command(self.select_command(*stop_commands)) + await self.executor.async_execute_command( + self.executor.select_command(*stop_commands) + ) async def async_my(self, **_): """Set cover to preset position.""" - await self.async_execute_command(COMMAND_MY) + await self.executor.async_execute_command(COMMAND_MY) @property def is_opening(self): @@ -250,7 +269,7 @@ def device_state_attributes(self): attr = super().device_state_attributes or {} # Obstruction Detected attribute is used by HomeKit - if self.has_state(IO_PRIORITY_LOCK_LEVEL_STATE): + if self.executor.has_state(IO_PRIORITY_LOCK_LEVEL_STATE): attr[ATTR_OBSTRUCTION_DETECTED] = True return attr @@ -260,35 +279,22 @@ def supported_features(self): """Flag supported features.""" supported_features = 0 - if self.has_command(*COMMANDS_OPEN_TILT): + if self.executor.has_command(*COMMANDS_OPEN_TILT): supported_features |= SUPPORT_OPEN_TILT - if self.has_command(*COMMANDS_STOP_TILT): + if self.executor.has_command(*COMMANDS_STOP_TILT): supported_features |= SUPPORT_STOP_TILT - if self.has_command(*COMMANDS_CLOSE_TILT): + if self.executor.has_command(*COMMANDS_CLOSE_TILT): supported_features |= SUPPORT_CLOSE_TILT - if self.has_command(*COMMANDS_SET_TILT_POSITION): + if self.executor.has_command(*COMMANDS_SET_TILT_POSITION): supported_features |= SUPPORT_SET_TILT_POSITION - if self.has_command(COMMAND_SET_CLOSURE_AND_LINEAR_SPEED): + if self.executor.has_command(COMMAND_SET_CLOSURE_AND_LINEAR_SPEED): supported_features |= SUPPORT_COVER_POSITION_LOW_SPEED - if self.has_command(COMMAND_MY): + if self.executor.has_command(COMMAND_MY): supported_features |= SUPPORT_MY return supported_features - - @property - def icon(self): - """Return the icon to use in the frontend, if any.""" - if ( - self.has_state(CORE_PRIORITY_LOCK_TIMER_STATE) - and self.select_state(CORE_PRIORITY_LOCK_TIMER_STATE) > 0 - ): - if self.select_state(IO_PRIORITY_LOCK_ORIGINATOR_STATE) == "wind": - return ICON_WEATHER_WINDY - return ICON_LOCK_ALERT - - return None diff --git a/custom_components/tahoma/cover_devices/vertical_cover.py b/custom_components/tahoma/cover_devices/vertical_cover.py index f42756e4e..25497c67e 100644 --- a/custom_components/tahoma/cover_devices/vertical_cover.py +++ b/custom_components/tahoma/cover_devices/vertical_cover.py @@ -56,16 +56,16 @@ def supported_features(self): """Flag supported features.""" supported_features = super().supported_features - if self.has_command(COMMAND_SET_CLOSURE): + if self.executor.has_command(COMMAND_SET_CLOSURE): supported_features |= SUPPORT_SET_POSITION - if self.has_command(*COMMANDS_OPEN): + if self.executor.has_command(*COMMANDS_OPEN): supported_features |= SUPPORT_OPEN - if self.has_command(*COMMANDS_STOP): + if self.executor.has_command(*COMMANDS_STOP): supported_features |= SUPPORT_STOP - if self.has_command(*COMMANDS_CLOSE): + if self.executor.has_command(*COMMANDS_CLOSE): supported_features |= SUPPORT_CLOSE return supported_features @@ -86,7 +86,7 @@ def current_cover_position(self): None is unknown, 0 is closed, 100 is fully open. """ - position = self.select_state( + position = self.executor.select_state( CORE_CLOSURE_STATE, CORE_CLOSURE_OR_ROCKER_POSITION_STATE, CORE_PEDESTRIAN_POSITION_STATE, @@ -101,12 +101,16 @@ def current_cover_position(self): async def async_set_cover_position(self, **kwargs): """Move the cover to a specific position.""" position = 100 - kwargs.get(ATTR_POSITION, 0) - await self.async_execute_command(COMMAND_SET_CLOSURE, position) + await self.executor.async_execute_command(COMMAND_SET_CLOSURE, position) async def async_open_cover(self, **_): """Open the cover.""" - await self.async_execute_command(self.select_command(*COMMANDS_OPEN)) + await self.executor.async_execute_command( + self.executor.select_command(*COMMANDS_OPEN) + ) async def async_close_cover(self, **_): """Close the cover.""" - await self.async_execute_command(self.select_command(*COMMANDS_CLOSE)) + await self.executor.async_execute_command( + self.executor.select_command(*COMMANDS_CLOSE) + ) diff --git a/custom_components/tahoma/entity.py b/custom_components/tahoma/entity.py new file mode 100644 index 000000000..0dd571e86 --- /dev/null +++ b/custom_components/tahoma/entity.py @@ -0,0 +1,138 @@ +"""Parent class for every Overkiz device.""" +import logging +from typing import Any, Dict + +from homeassistant.const import ATTR_BATTERY_LEVEL +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.update_coordinator import CoordinatorEntity +from pyhoma.models import Device + +from .const import DOMAIN +from .coordinator import TahomaDataUpdateCoordinator +from .executor import OverkizExecutor + +ATTR_RSSI_LEVEL = "rssi_level" + +CORE_AVAILABILITY_STATE = "core:AvailabilityState" +CORE_BATTERY_STATE = "core:BatteryState" +CORE_MANUFACTURER = "core:Manufacturer" +CORE_MANUFACTURER_NAME_STATE = "core:ManufacturerNameState" +CORE_MODEL_STATE = "core:ModelState" +CORE_PRODUCT_MODEL_NAME_STATE = "core:ProductModelNameState" +CORE_RSSI_LEVEL_STATE = "core:RSSILevelState" +CORE_SENSOR_DEFECT_STATE = "core:SensorDefectState" +CORE_STATUS_STATE = "core:StatusState" + +IO_MODEL_STATE = "io:ModelState" + +STATE_AVAILABLE = "available" +STATE_BATTERY_FULL = "full" +STATE_BATTERY_NORMAL = "normal" +STATE_BATTERY_LOW = "low" +STATE_BATTERY_VERY_LOW = "verylow" +STATE_DEAD = "dead" + +BATTERY_MAP = { + STATE_BATTERY_FULL: 100, + STATE_BATTERY_NORMAL: 75, + STATE_BATTERY_LOW: 25, + STATE_BATTERY_VERY_LOW: 10, +} + +_LOGGER = logging.getLogger(__name__) + + +class OverkizEntity(CoordinatorEntity, Entity): + """Representation of a Overkiz device entity.""" + + def __init__(self, device_url: str, coordinator: TahomaDataUpdateCoordinator): + """Initialize the device.""" + super().__init__(coordinator) + self.device_url = device_url + self.executor = OverkizExecutor(device_url, coordinator) + + @property + def device(self) -> Device: + """Return Overkiz device linked to this entity.""" + return self.coordinator.data[self.device_url] + + @property + def name(self) -> str: + """Return the name of the device.""" + return self.device.label + + @property + def available(self) -> bool: + """Return True if entity is available.""" + return self.device.available + + @property + def unique_id(self) -> str: + """Return a unique ID.""" + return self.device.deviceurl + + @property + def assumed_state(self) -> bool: + """Return True if unable to access real state of the entity.""" + return not self.device.states + + @property + def device_info(self) -> Dict[str, Any]: + """Return device registry information for this entity.""" + # Some devices, such as the Smart Thermostat have several devices in one physical device, + # with same device url, terminated by '#' and a number. + # In this case, we use the base device url as the device identifier. + if "#" in self.device_url and not self.device_url.endswith("#1"): + # Only return the url of the base device, to inherit device name and model from parent device. + return { + "identifiers": {(DOMAIN, self.executor.base_device_url)}, + } + + manufacturer = ( + self.executor.select_attribute(CORE_MANUFACTURER) + or self.executor.select_state(CORE_MANUFACTURER_NAME_STATE) + or "Somfy" + ) + + model = ( + self.executor.select_state( + CORE_MODEL_STATE, CORE_PRODUCT_MODEL_NAME_STATE, IO_MODEL_STATE + ) + or self.device.widget + ) + + return { + "identifiers": {(DOMAIN, self.unique_id)}, + "manufacturer": manufacturer, + "name": self.name, + "model": model, + "sw_version": self.device.controllable_name, + "suggested_area": self.coordinator.areas[self.device.placeoid], + "via_device": self.executor.get_gateway_id(), + } + + @property + def device_state_attributes(self) -> Dict[str, Any]: + """Return the state attributes of the device.""" + attr = {} + + if self.executor.has_state(CORE_RSSI_LEVEL_STATE): + attr[ATTR_RSSI_LEVEL] = self.executor.select_state(CORE_RSSI_LEVEL_STATE) + + if self.executor.has_state(CORE_BATTERY_STATE): + battery_state = self.executor.select_state(CORE_BATTERY_STATE) + attr[ATTR_BATTERY_LEVEL] = BATTERY_MAP.get(battery_state, battery_state) + + if self.executor.select_state(CORE_SENSOR_DEFECT_STATE) == STATE_DEAD: + attr[ATTR_BATTERY_LEVEL] = 0 + + if self.device.attributes: + for attribute in self.device.attributes: + attr[attribute.name] = attribute.value + + if self.device.states: + for state in self.device.states: + if "State" in state.name: + attr[state.name] = state.value + + return attr diff --git a/custom_components/tahoma/executor.py b/custom_components/tahoma/executor.py new file mode 100644 index 000000000..9fccb3ed4 --- /dev/null +++ b/custom_components/tahoma/executor.py @@ -0,0 +1,98 @@ +"""Class for helpers and community with the OverKiz API.""" +import logging +from typing import Any, Optional +from urllib.parse import urlparse + +from pyhoma.models import Command, Device + +from .coordinator import TahomaDataUpdateCoordinator + +_LOGGER = logging.getLogger(__name__) + + +class OverkizExecutor: + """Representation of an Overkiz device with execution handler.""" + + def __init__(self, device_url: str, coordinator: TahomaDataUpdateCoordinator): + """Initialize the executor.""" + self.device_url = device_url + self.coordinator = coordinator + self.device_url = device_url + self.base_device_url, *index = self.device_url.split("#") + self.index = index[0] if index else None + + @property + def device(self) -> Device: + """Return Overkiz device linked to this entity.""" + return self.coordinator.data[self.device_url] + + def select_command(self, *commands: str) -> Optional[str]: + """Select first existing command in a list of commands.""" + existing_commands = self.device.definition.commands + return next((c for c in commands if c in existing_commands), None) + + def has_command(self, *commands: str) -> bool: + """Return True if a command exists in a list of commands.""" + return self.select_command(*commands) is not None + + def select_state(self, *states) -> Optional[str]: + """Select first existing active state in a list of states.""" + if self.device.states: + return next( + ( + state.value + for state in self.device.states + if state.name in list(states) + ), + None, + ) + return None + + def has_state(self, *states: str) -> bool: + """Return True if a state exists in self.""" + return self.select_state(*states) is not None + + def select_attribute(self, *attributes) -> Optional[str]: + """Select first existing active state in a list of states.""" + if self.device.attributes: + return next( + ( + attribute.value + for attribute in self.device.attributes + if attribute.name in list(attributes) + ), + None, + ) + + async def async_execute_command(self, command_name: str, *args: Any): + """Execute device command in async context.""" + try: + exec_id = await self.coordinator.client.execute_command( + self.device.deviceurl, + Command(command_name, list(args)), + "Home Assistant", + ) + except Exception as exception: # pylint: disable=broad-except + _LOGGER.error(exception) + return + + # ExecutionRegisteredEvent doesn't contain the deviceurl, thus we need to register it here + self.coordinator.executions[exec_id] = { + "deviceurl": self.device.deviceurl, + "command_name": command_name, + } + + await self.coordinator.async_refresh() + + async def async_cancel_command(self, exec_id: str): + """Cancel device command in async context.""" + await self.coordinator.client.cancel_command(exec_id) + + def get_gateway_id(self): + """ + Retrieve gateway id from device url. + + device URL (:///[#]) + """ + url = urlparse(self.device_url) + return url.netloc diff --git a/custom_components/tahoma/light.py b/custom_components/tahoma/light.py index 062e51ec8..e5b771d2f 100644 --- a/custom_components/tahoma/light.py +++ b/custom_components/tahoma/light.py @@ -20,7 +20,7 @@ from .const import COMMAND_OFF, COMMAND_ON, CORE_ON_OFF_STATE, DOMAIN from .coordinator import TahomaDataUpdateCoordinator -from .tahoma_entity import TahomaEntity +from .entity import OverkizEntity _LOGGER = logging.getLogger(__name__) @@ -61,7 +61,7 @@ async def async_setup_entry( ) -class TahomaLight(TahomaEntity, LightEntity): +class TahomaLight(OverkizEntity, LightEntity): """Representation of a TaHoma Light.""" def __init__(self, device_url: str, coordinator: TahomaDataUpdateCoordinator): @@ -72,20 +72,20 @@ def __init__(self, device_url: str, coordinator: TahomaDataUpdateCoordinator): @property def brightness(self) -> int: """Return the brightness of this light between 0..255.""" - brightness = self.select_state(CORE_LIGHT_INTENSITY_STATE) + brightness = self.executor.select_state(CORE_LIGHT_INTENSITY_STATE) return round(brightness * 255 / 100) @property def is_on(self) -> bool: """Return true if light is on.""" - return self.select_state(CORE_ON_OFF_STATE) == STATE_ON + return self.executor.select_state(CORE_ON_OFF_STATE) == STATE_ON @property def hs_color(self): """Return the hue and saturation color value [float, float].""" - r = self.select_state(CORE_RED_COLOR_INTENSITY_STATE) - g = self.select_state(CORE_GREEN_COLOR_INTENSITY_STATE) - b = self.select_state(CORE_BLUE_COLOR_INTENSITY_STATE) + r = self.executor.select_state(CORE_RED_COLOR_INTENSITY_STATE) + g = self.executor.select_state(CORE_GREEN_COLOR_INTENSITY_STATE) + b = self.executor.select_state(CORE_BLUE_COLOR_INTENSITY_STATE) return None if None in [r, g, b] else color_util.color_RGB_to_hs(r, g, b) @property @@ -93,16 +93,16 @@ def supported_features(self) -> int: """Flag supported features.""" supported_features = 0 - if self.has_command(COMMAND_SET_INTENSITY): + if self.executor.has_command(COMMAND_SET_INTENSITY): supported_features |= SUPPORT_BRIGHTNESS - if self.has_command(COMMAND_WINK): + if self.executor.has_command(COMMAND_WINK): supported_features |= SUPPORT_EFFECT - if self.has_command(COMMAND_SET_RGB): + if self.executor.has_command(COMMAND_SET_RGB): supported_features |= SUPPORT_COLOR - if self.has_command(COMMAND_MY): + if self.executor.has_command(COMMAND_MY): supported_features |= SUPPORT_MY return supported_features @@ -110,7 +110,7 @@ def supported_features(self) -> int: async def async_turn_on(self, **kwargs) -> None: """Turn the light on.""" if ATTR_HS_COLOR in kwargs: - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_RGB, *[ round(float(c)) @@ -120,27 +120,27 @@ async def async_turn_on(self, **kwargs) -> None: if ATTR_BRIGHTNESS in kwargs: brightness = round(float(kwargs[ATTR_BRIGHTNESS]) / 255 * 100) - await self.async_execute_command(COMMAND_SET_INTENSITY, brightness) + await self.executor.async_execute_command(COMMAND_SET_INTENSITY, brightness) elif ATTR_EFFECT in kwargs: self._effect = kwargs[ATTR_EFFECT] - await self.async_execute_command(self._effect, 100) + await self.executor.async_execute_command(self._effect, 100) else: - await self.async_execute_command(COMMAND_ON) + await self.executor.async_execute_command(COMMAND_ON) async def async_turn_off(self, **_) -> None: """Turn the light off.""" - await self.async_execute_command(COMMAND_OFF) + await self.executor.async_execute_command(COMMAND_OFF) async def async_my(self, **_): """Set light to preset position.""" - await self.async_execute_command(COMMAND_MY) + await self.executor.async_execute_command(COMMAND_MY) @property def effect_list(self) -> list: """Return the list of supported effects.""" - return [COMMAND_WINK] if self.has_command(COMMAND_WINK) else None + return [COMMAND_WINK] if self.executor.has_command(COMMAND_WINK) else None @property def effect(self) -> str: diff --git a/custom_components/tahoma/lock.py b/custom_components/tahoma/lock.py index b9ebc3921..a7ff011d3 100644 --- a/custom_components/tahoma/lock.py +++ b/custom_components/tahoma/lock.py @@ -8,7 +8,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN -from .tahoma_entity import TahomaEntity +from .entity import OverkizEntity _LOGGER = logging.getLogger(__name__) @@ -34,18 +34,18 @@ async def async_setup_entry( async_add_entities(entities) -class TahomaLock(TahomaEntity, LockEntity): +class TahomaLock(OverkizEntity, LockEntity): """Representation of a TaHoma Lock.""" async def async_unlock(self, **_): """Unlock method.""" - await self.async_execute_command(COMMAND_UNLOCK) + await self.executor.async_execute_command(COMMAND_UNLOCK) async def async_lock(self, **_): """Lock method.""" - await self.async_execute_command(COMMAND_LOCK) + await self.executor.async_execute_command(COMMAND_LOCK) @property def is_locked(self): """Return True if the lock is locked.""" - return self.select_state(CORE_LOCKED_UNLOCKED_STATE) == STATE_LOCKED + return self.executor.select_state(CORE_LOCKED_UNLOCKED_STATE) == STATE_LOCKED diff --git a/custom_components/tahoma/sensor.py b/custom_components/tahoma/sensor.py index b2263b8fe..b7ca86d29 100644 --- a/custom_components/tahoma/sensor.py +++ b/custom_components/tahoma/sensor.py @@ -29,7 +29,7 @@ from .const import DOMAIN from .coordinator import TahomaDataUpdateCoordinator -from .tahoma_entity import TahomaEntity +from .entity import OverkizEntity _LOGGER = logging.getLogger(__name__) @@ -340,7 +340,7 @@ async def async_setup_entry( async_add_entities(entities) -class TahomaStateSensor(TahomaEntity, SensorEntity): +class TahomaStateSensor(OverkizEntity, SensorEntity): """Representation of a TaHoma Sensor.""" def __init__( @@ -367,8 +367,8 @@ def state(self): @property def name(self) -> str: """Return the name of the device.""" - if self.index: - return f"{self.entity_description.name} {self.index}" + if self.executor.index: + return f"{self.entity_description.name} {self.executor.index}" return self.entity_description.name @property diff --git a/custom_components/tahoma/switch.py b/custom_components/tahoma/switch.py index 832174646..77f59a78a 100644 --- a/custom_components/tahoma/switch.py +++ b/custom_components/tahoma/switch.py @@ -13,7 +13,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import COMMAND_OFF, COMMAND_ON, CORE_ON_OFF_STATE, DOMAIN -from .tahoma_entity import TahomaEntity +from .entity import OverkizEntity _LOGGER = logging.getLogger(__name__) @@ -48,7 +48,7 @@ async def async_setup_entry( async_add_entities(entities) -class TahomaSwitch(TahomaEntity, SwitchEntity): +class TahomaSwitch(OverkizEntity, SwitchEntity): """Representation a TaHoma Switch.""" @property @@ -71,14 +71,16 @@ def icon(self) -> Optional[str]: async def async_turn_on(self, **_): """Send the on command.""" - if self.has_command(COMMAND_ON): - await self.async_execute_command(COMMAND_ON) + if self.executor.has_command(COMMAND_ON): + await self.executor.async_execute_command(COMMAND_ON) - elif self.has_command(COMMAND_SET_FORCE_HEATING): - await self.async_execute_command(COMMAND_SET_FORCE_HEATING, STATE_ON) + elif self.executor.has_command(COMMAND_SET_FORCE_HEATING): + await self.executor.async_execute_command( + COMMAND_SET_FORCE_HEATING, STATE_ON + ) - elif self.has_command(COMMAND_RING_WITH_SINGLE_SIMPLE_SEQUENCE): - await self.async_execute_command( + elif self.executor.has_command(COMMAND_RING_WITH_SINGLE_SIMPLE_SEQUENCE): + await self.executor.async_execute_command( COMMAND_RING_WITH_SINGLE_SIMPLE_SEQUENCE, # https://www.tahomalink.com/enduser-mobile-web/steer-html5-client/vendor/somfy/io/siren/const.js 2 * 60 * 1000, # 2 minutes 75, # 90 seconds bip, 30 seconds silence @@ -88,8 +90,8 @@ async def async_turn_on(self, **_): async def async_turn_off(self, **_): """Send the off command.""" - if self.has_command(COMMAND_RING_WITH_SINGLE_SIMPLE_SEQUENCE): - await self.async_execute_command( + if self.executor.has_command(COMMAND_RING_WITH_SINGLE_SIMPLE_SEQUENCE): + await self.executor.async_execute_command( COMMAND_RING_WITH_SINGLE_SIMPLE_SEQUENCE, 2000, 100, @@ -97,18 +99,23 @@ async def async_turn_off(self, **_): COMMAND_STANDARD, ) - elif self.has_command(COMMAND_SET_FORCE_HEATING): - await self.async_execute_command(COMMAND_SET_FORCE_HEATING, STATE_OFF) + elif self.executor.has_command(COMMAND_SET_FORCE_HEATING): + await self.executor.async_execute_command( + COMMAND_SET_FORCE_HEATING, STATE_OFF + ) - elif self.has_command(COMMAND_OFF): - await self.async_execute_command(COMMAND_OFF) + elif self.executor.has_command(COMMAND_OFF): + await self.executor.async_execute_command(COMMAND_OFF) async def async_toggle(self, **_): """Click the switch.""" - if self.has_command(COMMAND_CYCLE): - await self.async_execute_command(COMMAND_CYCLE) + if self.executor.has_command(COMMAND_CYCLE): + await self.executor.async_execute_command(COMMAND_CYCLE) @property def is_on(self): """Get whether the switch is in on state.""" - return self.select_state(CORE_ON_OFF_STATE, IO_FORCE_HEATING_STATE) == STATE_ON + return ( + self.executor.select_state(CORE_ON_OFF_STATE, IO_FORCE_HEATING_STATE) + == STATE_ON + ) diff --git a/custom_components/tahoma/tahoma_entity.py b/custom_components/tahoma/tahoma_entity.py deleted file mode 100644 index d4fc39e65..000000000 --- a/custom_components/tahoma/tahoma_entity.py +++ /dev/null @@ -1,204 +0,0 @@ -"""Parent class for every TaHoma device.""" -import logging -import re -from typing import Any, Dict, Optional - -from homeassistant.const import ATTR_BATTERY_LEVEL -from homeassistant.helpers.entity import Entity -from homeassistant.helpers.update_coordinator import CoordinatorEntity -from pyhoma.models import Command, Device - -from .const import DOMAIN -from .coordinator import TahomaDataUpdateCoordinator - -CORE_MANUFACTURER = "core:Manufacturer" -CORE_MANUFACTURER_NAME_STATE = "core:ManufacturerNameState" -CORE_MODEL_STATE = "core:ModelState" -CORE_PRODUCT_MODEL_NAME_STATE = "core:ProductModelNameState" - -IO_MODEL_STATE = "io:ModelState" - -# To be removed when this is implemented in sensor/binary sensor -CORE_BATTERY_STATE = "core:BatteryState" -CORE_SENSOR_DEFECT_STATE = "core:SensorDefectState" - -STATE_AVAILABLE = "available" -STATE_BATTERY_FULL = "full" -STATE_BATTERY_NORMAL = "normal" -STATE_BATTERY_LOW = "low" -STATE_BATTERY_VERY_LOW = "verylow" -STATE_DEAD = "dead" - -BATTERY_MAP = { - STATE_BATTERY_FULL: 100, - STATE_BATTERY_NORMAL: 75, - STATE_BATTERY_LOW: 25, - STATE_BATTERY_VERY_LOW: 10, -} - -_LOGGER = logging.getLogger(__name__) - - -class TahomaEntity(CoordinatorEntity, Entity): - """Representation of a TaHoma device entity.""" - - def __init__(self, device_url: str, coordinator: TahomaDataUpdateCoordinator): - """Initialize the device.""" - super().__init__(coordinator) - self.device_url = device_url - self.base_device_url, *index = self.device_url.split("#") - self.index = index[0] if index else None - - @property - def device(self) -> Device: - """Return TaHoma device linked to this entity.""" - return self.coordinator.data[self.device_url] - - @property - def name(self) -> str: - """Return the name of the device.""" - return self.device.label - - @property - def available(self) -> bool: - """Return True if entity is available.""" - return self.device.available - - @property - def unique_id(self) -> str: - """Return a unique ID.""" - return self.device.deviceurl - - @property - def assumed_state(self) -> bool: - """Return True if unable to access real state of the entity.""" - return not self.device.states - - @property - def device_state_attributes(self) -> Dict[str, Any]: - """Return the state attributes of the device.""" - attr = {} - - if self.has_state(CORE_BATTERY_STATE): - battery_state = self.select_state(CORE_BATTERY_STATE) - attr[ATTR_BATTERY_LEVEL] = BATTERY_MAP.get(battery_state, battery_state) - - if self.select_state(CORE_SENSOR_DEFECT_STATE) == STATE_DEAD: - attr[ATTR_BATTERY_LEVEL] = 0 - - if self.device.attributes: - for attribute in self.device.attributes: - attr[attribute.name] = attribute.value - - if self.device.states: - for state in self.device.states: - if "State" in state.name: - attr[state.name] = state.value - - return attr - - @property - def device_info(self) -> Dict[str, Any]: - """Return device registry information for this entity.""" - # Some devices, such as the Smart Thermostat have several devices in one physical device, - # with same device url, terminated by '#' and a number. - # In this case, we use the base device url as the device identifier. - if "#" in self.device_url and not self.device_url.endswith("#1"): - # Only return the url of the base device, to inherit device name and model from parent device. - return { - "identifiers": {(DOMAIN, self.base_device_url)}, - } - - manufacturer = ( - self.select_attribute(CORE_MANUFACTURER) - or self.select_state(CORE_MANUFACTURER_NAME_STATE) - or "Somfy" - ) - model = ( - self.select_state( - CORE_MODEL_STATE, CORE_PRODUCT_MODEL_NAME_STATE, IO_MODEL_STATE - ) - or self.device.widget - ) - - return { - "identifiers": {(DOMAIN, self.base_device_url)}, - "name": self.device.label, - "manufacturer": manufacturer, - "model": model, - "sw_version": self.device.controllable_name, - "via_device": self.get_gateway_id(), - "suggested_area": self.coordinator.areas[self.device.placeoid], - } - - def select_command(self, *commands: str) -> Optional[str]: - """Select first existing command in a list of commands.""" - existing_commands = self.device.definition.commands - - return next((c for c in commands if c in existing_commands), None) - - def has_command(self, *commands: str) -> bool: - """Return True if a command exists in a list of commands.""" - return self.select_command(*commands) is not None - - def select_state(self, *states) -> Optional[str]: - """Select first existing active state in a list of states.""" - if self.device.states: - return next( - ( - state.value - for state in self.device.states - if state.name in list(states) - ), - None, - ) - return None - - def has_state(self, *states: str) -> bool: - """Return True if a state exists in self.""" - return self.select_state(*states) is not None - - def select_attribute(self, *attributes) -> Optional[str]: - """Select first existing active state in a list of states.""" - if self.device.attributes: - return next( - ( - attribute.value - for attribute in self.device.attributes - if attribute.name in list(attributes) - ), - None, - ) - - async def async_execute_command(self, command_name: str, *args: Any): - """Execute device command in async context.""" - try: - exec_id = await self.coordinator.client.execute_command( - self.device.deviceurl, - Command(command_name, list(args)), - "Home Assistant", - ) - except Exception as exception: # pylint: disable=broad-except - _LOGGER.error(exception) - return - - # ExecutionRegisteredEvent doesn't contain the deviceurl, thus we need to register it here - self.coordinator.executions[exec_id] = { - "deviceurl": self.device.deviceurl, - "command_name": command_name, - } - - await self.coordinator.async_refresh() - - async def async_cancel_command(self, exec_id: str): - """Cancel device command in async context.""" - await self.coordinator.client.cancel_command(exec_id) - - def get_gateway_id(self): - """Retrieve gateway id from device url.""" - result = re.search(r":\/\/(.*)\/", self.device_url) - - if result: - return result.group(1) - else: - return None diff --git a/custom_components/tahoma/water_heater_devices/domestic_hot_water_production.py b/custom_components/tahoma/water_heater_devices/domestic_hot_water_production.py index 324cda130..ae642322f 100644 --- a/custom_components/tahoma/water_heater_devices/domestic_hot_water_production.py +++ b/custom_components/tahoma/water_heater_devices/domestic_hot_water_production.py @@ -8,7 +8,7 @@ ) from homeassistant.const import ATTR_TEMPERATURE, STATE_OFF, STATE_ON, TEMP_CELSIUS -from ..tahoma_entity import TahomaEntity +from ..entity import OverkizEntity CORE_MAXIMAL_TEMPERATURE_MANUAL_MODE_STATE = "core:MaximalTemperatureManualModeState" CORE_MINIMAL_TEMPERATURE_MANUAL_MODE_STATE = "core:MinimalTemperatureManualModeState" @@ -40,7 +40,7 @@ MAP_REVERSE_OPERATION_MODES = {v: k for k, v in MAP_OPERATION_MODES.items()} -class DomesticHotWaterProduction(TahomaEntity, WaterHeaterEntity): +class DomesticHotWaterProduction(OverkizEntity, WaterHeaterEntity): """Representation of a DomesticHotWaterProduction Water Heater.""" @property @@ -51,17 +51,17 @@ def temperature_unit(self) -> str: @property def min_temp(self): """Return the minimum temperature.""" - return self.select_state(CORE_MINIMAL_TEMPERATURE_MANUAL_MODE_STATE) + return self.executor.select_state(CORE_MINIMAL_TEMPERATURE_MANUAL_MODE_STATE) @property def max_temp(self): """Return the maximum temperature.""" - return self.select_state(CORE_MAXIMAL_TEMPERATURE_MANUAL_MODE_STATE) + return self.executor.select_state(CORE_MAXIMAL_TEMPERATURE_MANUAL_MODE_STATE) @property def current_operation(self): """Return current operation ie. eco, electric, performance, ...""" - return MAP_OPERATION_MODES[self.select_state(IO_DHW_MODE_STATE)] + return MAP_OPERATION_MODES[self.executor.select_state(IO_DHW_MODE_STATE)] @property def operation_list(self): @@ -71,33 +71,33 @@ def operation_list(self): @property def current_temperature(self): """Return the current temperature.""" - return self.select_state(IO_MIDDLE_WATER_TEMPERATURE_STATE) + return self.executor.select_state(IO_MIDDLE_WATER_TEMPERATURE_STATE) @property def target_temperature(self): """Return the temperature we try to reach.""" - return self.select_state(CORE_TARGET_TEMPERATURE_STATE) + return self.executor.select_state(CORE_TARGET_TEMPERATURE_STATE) @property def target_temperature_high(self): """Return the highbound target temperature we try to reach.""" - return self.select_state(CORE_MAXIMAL_TEMPERATURE_MANUAL_MODE_STATE) + return self.executor.select_state(CORE_MAXIMAL_TEMPERATURE_MANUAL_MODE_STATE) @property def target_temperature_low(self): """Return the lowbound target temperature we try to reach.""" - return self.select_state(CORE_MINIMAL_TEMPERATURE_MANUAL_MODE_STATE) + return self.executor.select_state(CORE_MINIMAL_TEMPERATURE_MANUAL_MODE_STATE) async def async_set_temperature(self, **kwargs): """Set new target temperature.""" target_temperature = kwargs.get(ATTR_TEMPERATURE) - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_TARGET_TEMPERATURE, target_temperature ) async def async_set_operation_mode(self, operation_mode): """Set new target operation mode.""" - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_DHW_MODE, MAP_REVERSE_OPERATION_MODES[operation_mode] ) @@ -110,12 +110,13 @@ def supported_features(self): def is_away_mode_on(self): """Return true if away mode is on.""" return ( - self.select_state(CORE_OPERATING_MODE_STATE).get(STATE_ABSENCE) == STATE_ON + self.executor.select_state(CORE_OPERATING_MODE_STATE).get(STATE_ABSENCE) + == STATE_ON ) async def async_turn_away_mode_on(self): """Turn away mode on.""" - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_CURRENT_OPERATING_MODE, { STATE_RELAUNCH: STATE_OFF, @@ -125,7 +126,7 @@ async def async_turn_away_mode_on(self): async def async_turn_away_mode_off(self): """Turn away mode off.""" - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_CURRENT_OPERATING_MODE, { STATE_RELAUNCH: STATE_OFF, diff --git a/custom_components/tahoma/water_heater_devices/hitachi_dhw.py b/custom_components/tahoma/water_heater_devices/hitachi_dhw.py index 118139b4c..2cebf4b01 100644 --- a/custom_components/tahoma/water_heater_devices/hitachi_dhw.py +++ b/custom_components/tahoma/water_heater_devices/hitachi_dhw.py @@ -12,7 +12,7 @@ TEMP_CELSIUS, ) -from ..tahoma_entity import TahomaEntity +from ..entity import OverkizEntity CORE_DHW_TEMPERATURE_STATE = "core:DHWTemperatureState" MODBUS_DHW_MODE_STATE = "modbus:DHWModeState" @@ -41,7 +41,7 @@ OPERATION_MODE_TO_TAHOMA = {v: k for k, v in TAHOMA_TO_OPERATION_MODE.items()} -class HitachiDHW(TahomaEntity, WaterHeaterEntity): +class HitachiDHW(OverkizEntity, WaterHeaterEntity): """Representation of a HitachiDHW Water Heater.""" @property @@ -72,27 +72,29 @@ def precision(self): @property def current_temperature(self): """Return the current temperature.""" - return self.select_state(CORE_DHW_TEMPERATURE_STATE) + return self.executor.select_state(CORE_DHW_TEMPERATURE_STATE) @property def target_temperature(self): """Return the temperature we try to reach.""" - return self.select_state(MODBUS_CONTROL_DHW_SETTING_TEMPERATURE_STATE) + return self.executor.select_state(MODBUS_CONTROL_DHW_SETTING_TEMPERATURE_STATE) async def async_set_temperature(self, **kwargs): """Set new target temperature.""" temperature = kwargs.get(ATTR_TEMPERATURE) - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_CONTROL_DHW_SETTING_TEMPERATURE, int(temperature) ) @property def current_operation(self): """Return current operation ie. eco, electric, performance, ...""" - if self.select_state(MODBUS_CONTROL_DHW_STATE) == STATE_STOP: + if self.executor.select_state(MODBUS_CONTROL_DHW_STATE) == STATE_STOP: return STATE_OFF - return TAHOMA_TO_OPERATION_MODE[self.select_state(MODBUS_DHW_MODE_STATE)] + return TAHOMA_TO_OPERATION_MODE[ + self.executor.select_state(MODBUS_DHW_MODE_STATE) + ] @property def operation_list(self): @@ -103,13 +105,17 @@ async def async_set_operation_mode(self, operation_mode): """Set new target operation mode.""" # Turn water heater off if operation_mode == STATE_OFF: - return await self.async_execute_command(COMMAND_SET_CONTROL_DHW, STATE_STOP) + return await self.executor.async_execute_command( + COMMAND_SET_CONTROL_DHW, STATE_STOP + ) # Turn water heater on, when off if self.current_operation == STATE_OFF and operation_mode != STATE_OFF: - await self.async_execute_command(COMMAND_SET_CONTROL_DHW, STATE_RUN) + await self.executor.async_execute_command( + COMMAND_SET_CONTROL_DHW, STATE_RUN + ) # Change operation mode - await self.async_execute_command( + await self.executor.async_execute_command( COMMAND_SET_DHW_MODE, OPERATION_MODE_TO_TAHOMA[operation_mode] )