diff --git a/custom_components/adaptive_cover/calculation.py b/custom_components/adaptive_cover/calculation.py index f9c320a..3bfb19c 100644 --- a/custom_components/adaptive_cover/calculation.py +++ b/custom_components/adaptive_cover/calculation.py @@ -138,6 +138,7 @@ class ClimateCoverData: temp_high: float presence: str presence_entity: str + weather_condition: str blind_type: str @property @@ -150,7 +151,7 @@ def is_presence(self): return self.presence == "home" if domain == "zone": return int(self.presence) > 0 - if domain in ["binary_sensor","input_boolean"]: + if domain in ["binary_sensor", "input_boolean"]: return self.presence == "on" return True @@ -166,8 +167,15 @@ def is_winter(self) -> bool: @property def is_summer(self) -> bool: - """Check if temperature is over thresholds.""" - self.current_temperature > self.temp_high + """Check if temperature is over threshold.""" + return self.current_temperature > self.temp_high + + @property + def is_sunny(self) -> bool: + """Check if condition can contain radiation in winter.""" + if self.weather_condition in ["sunny", "partlycloudy", "windy"]: + return True + return False @dataclass @@ -194,17 +202,19 @@ def normal_type_cover(self) -> int: # prefer glare reduction over climate control # adjust according basic algorithm + if not self.climate_data.is_sunny and self.climate_data.is_winter: + return self.cover.default return super().get_state() def control_method_tilt_single(self): """Single direction control schema.""" if self.climate_data.is_presence: - if self.climate_data.is_winter: + if self.climate_data.is_winter and self.climate_data.is_sunny: return super().get_state() if self.climate_data.is_summer: return 45 / 90 * 100 # 80 degrees is optimal by no need to shield or use solar contribution - if self.cover.valid: + if self.cover.valid and self.climate_data.is_sunny: return super().get_state() return 80 / 90 * 100 else: @@ -219,12 +229,12 @@ def control_method_tilt_bi(self): """bi-directional control schema.""" beta = np.rad2deg(self.cover.beta) if self.climate_data.is_presence: - if self.climate_data.is_winter: + if self.climate_data.is_winter and self.climate_data.is_sunny: return super().get_state() if self.climate_data.is_summer: return 45 / 180 * 100 # 80 degrees is optimal by no need to shield or use solar contribution - if self.cover.valid: + if self.cover.valid and self.climate_data.is_sunny: return super().get_state() return 80 / 180 * 100 else: diff --git a/custom_components/adaptive_cover/config_flow.py b/custom_components/adaptive_cover/config_flow.py index 3a37e9c..1d40836 100644 --- a/custom_components/adaptive_cover/config_flow.py +++ b/custom_components/adaptive_cover/config_flow.py @@ -37,6 +37,7 @@ CONF_TILT_MODE, CONF_TEMP_ENTITY, CONF_PRESENCE_ENTITY, + CONF_WEATHER_ENTITY, CONF_TEMP_LOW, CONF_TEMP_HIGH, CONF_MODE, @@ -145,9 +146,12 @@ ), vol.Optional(CONF_PRESENCE_ENTITY, default=[]): selector.EntitySelector( selector.EntityFilterSelectorConfig( - domain=["device_tracker", "zone", "binary_sensor","input_boolean"] + domain=["device_tracker", "zone", "binary_sensor", "input_boolean"] ) ), + vol.Optional(CONF_WEATHER_ENTITY, default=[]): selector.EntitySelector( + selector.EntityFilterSelectorConfig(domain="weather") + ), } ) @@ -208,6 +212,7 @@ async def async_step_vertical(self, user_input): CONF_SUNSET_OFFSET: user_input[CONF_SUNSET_OFFSET], CONF_TEMP_ENTITY: user_input.get(CONF_TEMP_ENTITY, None), CONF_PRESENCE_ENTITY: user_input.get(CONF_PRESENCE_ENTITY, None), + CONF_WEATHER_ENTITY: user_input.get(CONF_WEATHER_ENTITY, None), CONF_TEMP_LOW: user_input.get(CONF_TEMP_LOW, None), CONF_TEMP_HIGH: user_input.get(CONF_TEMP_HIGH, None), }, @@ -247,6 +252,7 @@ async def async_step_horizontal(self, user_input): CONF_SUNSET_OFFSET: user_input[CONF_SUNSET_OFFSET], CONF_TEMP_ENTITY: user_input.get(CONF_TEMP_ENTITY, None), CONF_PRESENCE_ENTITY: user_input.get(CONF_PRESENCE_ENTITY, None), + CONF_WEATHER_ENTITY: user_input.get(CONF_WEATHER_ENTITY, None), CONF_TEMP_LOW: user_input.get(CONF_TEMP_LOW, None), CONF_TEMP_HIGH: user_input.get(CONF_TEMP_HIGH, None), }, @@ -285,6 +291,7 @@ async def async_step_tilt(self, user_input): CONF_SUNSET_OFFSET: user_input[CONF_SUNSET_OFFSET], CONF_TEMP_ENTITY: user_input.get(CONF_TEMP_ENTITY, None), CONF_PRESENCE_ENTITY: user_input.get(CONF_PRESENCE_ENTITY, None), + CONF_WEATHER_ENTITY: user_input.get(CONF_WEATHER_ENTITY, None), CONF_TEMP_LOW: user_input.get(CONF_TEMP_LOW, None), CONF_TEMP_HIGH: user_input.get(CONF_TEMP_HIGH, None), }, diff --git a/custom_components/adaptive_cover/const.py b/custom_components/adaptive_cover/const.py index 011547c..04188dd 100644 --- a/custom_components/adaptive_cover/const.py +++ b/custom_components/adaptive_cover/const.py @@ -24,6 +24,7 @@ CONF_SUNSET_OFFSET = "sunset_offset" CONF_TEMP_ENTITY = "temp_entity" CONF_PRESENCE_ENTITY = "presence_entity" +CONF_WEATHER_ENTITY = "weather_entity" CONF_TEMP_LOW = "temp_low" CONF_TEMP_HIGH = "temp_high" CONF_MODE = "mode" diff --git a/custom_components/adaptive_cover/sensor.py b/custom_components/adaptive_cover/sensor.py index 57709a9..5809fe2 100644 --- a/custom_components/adaptive_cover/sensor.py +++ b/custom_components/adaptive_cover/sensor.py @@ -45,7 +45,6 @@ ) - async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, @@ -99,11 +98,11 @@ def __init__( self._mode = self.config_entry.data.get(CONF_MODE, "basic") self._temp_entity = self.config_entry.options.get("temp_entity", None) self._presence_entity = self.config_entry.options.get("presence_entity", None) + self._weather_entity = self.config_entry.options.get("weather_entity", None) self._entities = ["sun.sun"] - if self._temp_entity is not None: - self._entities.append(self._temp_entity) - if self._presence_entity is not None: - self._entities.append(self._presence_entity) + for entity in [self._temp_entity, self._presence_entity, self._weather_entity]: + if entity is not None: + self._entities.append(entity) @callback def async_on_state_change(self, event: EventType[EventStateChangedData]) -> None: @@ -146,8 +145,8 @@ def extra_state_attributes(self) -> Mapping[str, Any] | None: # noqa: D102 "azimuth_window": self.config_entry.options[CONF_AZIMUTH], "default_height": self.config_entry.options[CONF_DEFAULT_HEIGHT], "field_of_view": self._cover_data.fov, - 'start_time': self._cover_data.start, - 'end_time': self._cover_data.end, + "start_time": self._cover_data.start, + "end_time": self._cover_data.end, "entity_id": self.config_entry.options[CONF_ENTITIES], "cover_type": self._cover_type, # "test": self.config_entry, @@ -207,6 +206,8 @@ def __init__( # noqa: D107 self.temp_high = None self.presence = None self.presence_entity = None + self.weather_entity = None + self.weather_condition = None self.climate_state = None self.climate_data = None @@ -313,6 +314,7 @@ def update_climate(self): self.temp_high = self.config_entry.options[CONF_TEMP_HIGH] self.temp_entity = self.config_entry.options["temp_entity"] self.presence_entity = self.config_entry.options["presence_entity"] + self.weather_entity = self.config_entry.options["weather_entity"] self.presence = None if get_domain(self.temp_entity) == "climate": self.current_temp = self.hass.states.get(self.temp_entity).attributes[ @@ -323,6 +325,8 @@ def update_climate(self): if self.presence_entity is not None: self.presence = get_safe_state(self.hass, self.presence_entity) + if self.weather_entity is not None: + self.weather_condition = get_safe_state(self.hass, self.weather_entity) self.climate_data = ClimateCoverData( self.current_temp, @@ -330,6 +334,7 @@ def update_climate(self): self.temp_high, self.presence, self.presence_entity, + self.weather_condition, self.sensor_type, ) @@ -342,5 +347,3 @@ def state(self): if self.inverse_state: return 100 - self._state return self._state - - diff --git a/custom_components/adaptive_cover/strings.json b/custom_components/adaptive_cover/strings.json index 352982a..e25d589 100644 --- a/custom_components/adaptive_cover/strings.json +++ b/custom_components/adaptive_cover/strings.json @@ -28,7 +28,12 @@ "group": "Cover Entities", "inverse_state": "Inverse the state (needed for some covers that don't follow HA guidelines)", "sunset_position": "Sunset Position", - "sunset_offset": "Sunset Offset" + "sunset_offset": "Sunset Offset", + "temp_entity": "Inside temperature entity", + "presence_entity": "Presence entity", + "weather_entity": "Weather entity (optional)", + "temp_low": "Low temperature threshold", + "temp_high": "High temperature threshold" }, "data_description": { "set_azimuth": "Set the Azimuth", @@ -57,7 +62,12 @@ "group": "Cover Entities", "inverse_state": "Inverse the state (needed for some covers that don't follow HA guidelines)", "sunset_position": "Sunset Position", - "sunset_offset": "Sunset Offset" + "sunset_offset": "Sunset Offset", + "temp_entity": "Inside temperature entity", + "presence_entity": "Presence entity", + "weather_entity": "Weather entity (optional)", + "temp_low": "Low temperature threshold", + "temp_high": "High temperature threshold" }, "data_description": { "set_azimuth": "Set the Azimuth", @@ -86,7 +96,12 @@ "group": "Cover Entities", "inverse_state": "Inverse the state (needed for some covers that don't follow HA guidelines)", "sunset_position": "Sunset Position", - "sunset_offset": "Sunset Offset" + "sunset_offset": "Sunset Offset", + "temp_entity": "Inside temperature entity", + "presence_entity": "Presence entity", + "weather_entity": "Weather entity (optional)", + "temp_low": "Low temperature threshold", + "temp_high": "High temperature threshold" }, "data_description": { "set_azimuth": "Set the Azimuth", @@ -120,7 +135,12 @@ "sunset_position": "Sunset Position", "sunset_offset": "Sunset Offset", "slat_depth": "Slat Depth", - "slat_distance": "Slat Spacing" + "slat_distance": "Slat Spacing", + "temp_entity": "Inside temperature entity", + "presence_entity": "Presence entity", + "weather_entity": "Weather entity (optional)", + "temp_low": "Low temperature threshold", + "temp_high": "High temperature threshold" }, "data_description": { "set_azimuth": "Set the Azimuth", diff --git a/custom_components/adaptive_cover/translations/en.json b/custom_components/adaptive_cover/translations/en.json index 352982a..e25d589 100644 --- a/custom_components/adaptive_cover/translations/en.json +++ b/custom_components/adaptive_cover/translations/en.json @@ -28,7 +28,12 @@ "group": "Cover Entities", "inverse_state": "Inverse the state (needed for some covers that don't follow HA guidelines)", "sunset_position": "Sunset Position", - "sunset_offset": "Sunset Offset" + "sunset_offset": "Sunset Offset", + "temp_entity": "Inside temperature entity", + "presence_entity": "Presence entity", + "weather_entity": "Weather entity (optional)", + "temp_low": "Low temperature threshold", + "temp_high": "High temperature threshold" }, "data_description": { "set_azimuth": "Set the Azimuth", @@ -57,7 +62,12 @@ "group": "Cover Entities", "inverse_state": "Inverse the state (needed for some covers that don't follow HA guidelines)", "sunset_position": "Sunset Position", - "sunset_offset": "Sunset Offset" + "sunset_offset": "Sunset Offset", + "temp_entity": "Inside temperature entity", + "presence_entity": "Presence entity", + "weather_entity": "Weather entity (optional)", + "temp_low": "Low temperature threshold", + "temp_high": "High temperature threshold" }, "data_description": { "set_azimuth": "Set the Azimuth", @@ -86,7 +96,12 @@ "group": "Cover Entities", "inverse_state": "Inverse the state (needed for some covers that don't follow HA guidelines)", "sunset_position": "Sunset Position", - "sunset_offset": "Sunset Offset" + "sunset_offset": "Sunset Offset", + "temp_entity": "Inside temperature entity", + "presence_entity": "Presence entity", + "weather_entity": "Weather entity (optional)", + "temp_low": "Low temperature threshold", + "temp_high": "High temperature threshold" }, "data_description": { "set_azimuth": "Set the Azimuth", @@ -120,7 +135,12 @@ "sunset_position": "Sunset Position", "sunset_offset": "Sunset Offset", "slat_depth": "Slat Depth", - "slat_distance": "Slat Spacing" + "slat_distance": "Slat Spacing", + "temp_entity": "Inside temperature entity", + "presence_entity": "Presence entity", + "weather_entity": "Weather entity (optional)", + "temp_low": "Low temperature threshold", + "temp_high": "High temperature threshold" }, "data_description": { "set_azimuth": "Set the Azimuth", diff --git a/custom_components/adaptive_cover/translations/nl.json b/custom_components/adaptive_cover/translations/nl.json index 29a58ed..ebd8fe3 100644 --- a/custom_components/adaptive_cover/translations/nl.json +++ b/custom_components/adaptive_cover/translations/nl.json @@ -23,7 +23,12 @@ "group": "Cover Entities", "inverse_state": "Draai de status om (nodig voor sommige covers die omgekeerde percentages hanteren)", "sunset_position": "Zonsondergang Posistie", - "sunset_offset": "Afwijking Zonsondergang" + "sunset_offset": "Afwijking Zonsondergang", + "temp_entity": "Binnen temperatuur entiteit", + "presence_entity": "Aanwezigheid entiteit", + "weather_entity": "Weer entiteit (optioneel)", + "temp_low": "Minimale comfort temperatuur", + "temp_high": "Maximale comfort temperatuur" }, "data_description": { "set_azimuth": "Zet de Azimuth", @@ -53,7 +58,12 @@ "group": "Cover Entities", "inverse_state": "Draai de status om (nodig voor sommige covers die omgekeerde percentages hanteren)", "sunset_position": "Zonsondergang Posistie", - "sunset_offset": "Afwijking Zonsondergang" + "sunset_offset": "Afwijking Zonsondergang", + "temp_entity": "Binnen temperatuur entiteit", + "presence_entity": "Aanwezigheid entiteit", + "weather_entity": "Weer entiteit (optioneel)", + "temp_low": "Minimale comfort temperatuur", + "temp_high": "Maximale comfort temperatuur" }, "data_description": { "set_azimuth": "Zet de Azimuth", @@ -67,6 +77,43 @@ "fov_right": "Aantal graden aan de rechter kant vanuit het midden gezien van het raam", "group": "Selecteer de entities die worden bestuurd met de Blueprint", "sunset_position": "Standaard posistie bij zonsondergang in percentages", + "sunset_offset": "Tijd voor of na zonsondergang in minuten", + "temp_entity": "Binnen temperatuur entiteit", + "presence_entity": "Aanwezigheid entiteit", + "weather_entity": "Weer entiteit (optioneel)", + "temp_low": "Minimale comfort temperatuur", + "temp_high": "Maximale comfort temperatuur" + } + }, + "tilt": { + "data": { + "name": "Naam", + "blueprint": "Voeg blueprint toe aan HomeAssistant", + "set_azimuth": "Raam Azimuth", + "slat_depth": "Lat diepte", + "slat_distance": "Ruimte tussen latten", + "default_percentage": "Standaard Positie", + "fov_left": "Gezichtsveld links", + "fov_right": "Gezichtsveld rechts", + "group": "Cover Entities", + "inverse_state": "Draai de status om (nodig voor sommige covers die omgekeerde percentages hanteren)", + "sunset_position": "Zonsondergang Posistie", + "sunset_offset": "Afwijking Zonsondergang", + "temp_entity": "Binnen temperatuur entiteit", + "presence_entity": "Aanwezigheid entiteit", + "weather_entity": "Weer entiteit (optioneel)", + "temp_low": "Minimale comfort temperatuur", + "temp_high": "Maximale comfort temperatuur" + }, + "data_description": { + "set_azimuth": "Zet de Azimuth", + "slat_depth": "Diepte van een individuele lat in cm", + "slat_distance": "Verticale afstand tussen twee latten in cm", + "default_percentage": "Standaard posistie van de cover in percentages", + "fov_left": "Aantal graden aan de linker kant vanuit het midden gezien van het raam", + "fov_right": "Aantal graden aan de rechter kant vanuit het midden gezien van het raam", + "group": "Selecteer de entities die worden bestuurd met de Blueprint", + "sunset_position": "Standaard posistie bij zonsondergang in percentages", "sunset_offset": "Tijd voor of na zonsondergang in minuten" } } @@ -90,7 +137,12 @@ "group": "Cover Entities", "inverse_state": "Draai de status om (nodig voor sommige covers die omgekeerde percentages hanteren)", "sunset_position": "Zonsondergang Posistie", - "sunset_offset": "Afwijking Zonsondergang" + "sunset_offset": "Afwijking Zonsondergang", + "temp_entity": "Binnen temperatuur entiteit", + "presence_entity": "Aanwezigheid entiteit", + "weather_entity": "Weer entiteit (optioneel)", + "temp_low": "Minimale comfort temperatuur", + "temp_high": "Maximale comfort temperatuur" }, "data_description": { "set_azimuth": "Zet de Azimuth",