diff --git a/CHANGELOG.md b/CHANGELOG.md index 46ea9ae..3244f91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,48 @@ +# [2.0.0-beta.3](https://github.com/mguyard/appdaemon-coversmanager/compare/v2.0.0-beta.2...v2.0.0-beta.3) (2024-11-16) + + +### Bug Fixes + +* Handle indoor setpoint when seasons configuration is absent ([eebe9f9](https://github.com/mguyard/appdaemon-coversmanager/commit/eebe9f99e30abf2e4fcef8c0f075c17b21141457)) +* Set default value for temperature configuration and handle None case ([22bf86d](https://github.com/mguyard/appdaemon-coversmanager/commit/22bf86dbeefdf0882c3d9487cc658de0ac34761a)) +* Update lux sensor comparison to handle float values correctly ([f5835e1](https://github.com/mguyard/appdaemon-coversmanager/commit/f5835e118ae2ce83ab2ea0e304dc83008d3a1347)) + +> [!CAUTION] +> You need to restart your AppDaemon addon. + +# [2.0.0-beta.2](https://github.com/mguyard/appdaemon-coversmanager/compare/v2.0.0-beta.1...v2.0.0-beta.2) (2024-11-12) + + +### Bug Fixes + +* Add config parameter to lambda when sun leave window ([9b74c79](https://github.com/mguyard/appdaemon-coversmanager/commit/9b74c79f9a6f65e2422da9f8f0e24656eb9e59ee)) + +# [2.0.0-beta.1](https://github.com/mguyard/appdaemon-coversmanager/compare/v1.5.0-beta.2...v2.0.0-beta.1) (2024-11-10) + + +### Features + +* Refactor adaptive configuration to allow a dedicated locker for adaptive ([440fba2](https://github.com/mguyard/appdaemon-coversmanager/commit/440fba2ad1982d7d922e1e791eb2bd4fa469837b)) + + +### BREAKING CHANGES + +* adaptive configuration is no more in common.closing.adaptive but directly in common.adaptive with 2 parameters (enable and locker). You need to adjust your configuration to follow this change. Follow documentation please + +# [1.5.0-beta.2](https://github.com/mguyard/appdaemon-coversmanager/compare/v1.5.0-beta.1...v1.5.0-beta.2) (2024-10-12) + + +### Bug Fixes + +* Update _get_indoor_setpoint method to allow None for seasons parameter ([3dbc033](https://github.com/mguyard/appdaemon-coversmanager/commit/3dbc03337aa91bfe8616e63e6a2b43d555ca1d11)) + +# [1.5.0-beta.1](https://github.com/mguyard/appdaemon-coversmanager/compare/v1.4.0...v1.5.0-beta.1) (2024-10-12) + + +### Features + +* Add seasonal configuration support for indoor temperature setpoints ([8c2c853](https://github.com/mguyard/appdaemon-coversmanager/commit/8c2c853f0d9f62ce934dc39f98c1f52a8a872bf4)) + # [1.4.0](https://github.com/mguyard/appdaemon-coversmanager/compare/v1.3.1...v1.4.0) (2024-08-11) diff --git a/README.md b/README.md index 843bce2..7dcdd8f 100644 --- a/README.md +++ b/README.md @@ -220,7 +220,8 @@ CoversManager: type: "lux" closing: type: "lux" - adaptive: True + adaptive: + enable: true temperature: indoor: sensor: "sensor.indoor_temperature" @@ -247,32 +248,35 @@ You have more configuration available. All is detailled in next chapter [🧩 Pa Please find below all configuration parameters who don't apply to covers directly -| Parent | Parameters | Description | Configuration Path | Default | Type | Status | -|---------------------|------------------|-------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------|---------|----------------------|----------| -| - | dryrun | Enable a dryrun mode that don't execute open or close functions | config.dryrun | False | Boolean | Optional | -| - | locker | A binary sensor who block opening for open and close when state is On (including all moves by adaptive mode) | config.locker | None | Binary Sensor Entity | Optional | -| position | opened | Define the max position allowed (%) when cover is open | config.common.position.opened | 100 | Integer | Optional | -| position | closed | Define the min position allowed (%) when cover is closed | config.common.position.opened | 0 | Integer | Optional | -| position | min_ratio_change | Define minimum percent of move to allow action | config.common.position.min_ratio_change | 5 | Integer | Optional | -| position | min_time_change | Define minimum time in minutes allowed between move | config.common.position.min_time_change | 10 | Integer | Optional | -| opening | type | Define method to open covers the morning (Allowed value : off|time|sunrise|lux|prefer-lux) | config.common.opening.type | off | String | Optional | -| opening | time | Time to open covers - Only work with time or prefer-lux type | config.common.opening.time | None | Time | Optional | -| opening | locker | A binary sensor who block opening when state is On (including opening by adaptive mode) | config.common.opening.locker | None | Binary Sensor Entity | Optional | -| closing | type | Define method to open covers the morning (Allowed value : off|time|sunrise|lux|prefer-lux) | config.common.closing.type | off | String | Optional | -| closing | time | Time to open covers - Only work with time or prefer-lux type | config.common.closing.time | None | Time | Optional | -| closing | locker | A binary sensor who block closing when state is On (including all moves by adaptive mode) | config.common.closing.locker | None | Binary Sensor Entity | Optional | -| closing | secure_dusk | Close at dusk in 2 layer if first closing method failed - Only work with time or prefer-lux type | config.common.closing.secure_dusk | False | Boolean | Optional | -| closing | adaptive | Enable adaptive mode who close/open covers based on Sun position and indoor/outdoor temperature | config.common.closing.adaptive | False | Boolean | Optional | -| manual | allow | Enable or Disable detection of manual position change of covers | config.common.manual.allow | False | Boolean | Optional | -| manual | timer | Time to block movements when manual position change is detected. Required if config.common.manual.allow is True | config.common.manual.timer | None | TimeDelta | Optional | -| temperature.indoor | sensor | Sensor who provide indoor temperature (Positive Integer - No Float) | config.common.temperature.indoor.sensor | None | Sensor Entity | Optional | -| temperature.indoor | setpoint | Indoor temperature setpoint. Below => We need to heat with sun / Above => We need to block sun | config.common.temperature.indoor.setpoint | None | PositiveInt | Optional | -| temperature.outdoor | sensor | Sensor who provide outdoor temperature (Positive Integer - No Float) | config.common.temperature.outdoor.sensor | None | Sensor Entity | Optional | -| temperature.outdoor | low_temperature | Outdoor temperature to trigger to enable adaptive mode in addition to indoor_temperature | config.common.temperature.outdoor.low_temperature | None | PositiveInt | Optional | -| temperature.outdoor | high_temperature | Outdoor temperature to trigger when we need to totally close cover to protect from heat. Required when Outdoor sensor is configured | config.common.temperature.outdoor.high_temperature | None | PositiveInt | Optional | -| lux | sensor | Sensor who provide outside Lux | config.common.lux.sensor | None | Sensor Entity | Optional | -| lux | open_lux | Trigger in lux to open covers. Required if type of opening is lux or prefer-lux | config.common.lux.open_lux | None | PositiveInt | Optional | -| lux | close_lux | Trigger in lux to close covers. Required if type of closing is lux or prefer-lux | config.common.lux.close_lux | None | PositiveInt | Optional | +| Parent | Parameters | Description | Configuration Path | Default | Type | Status | +|-------------------------------------------|------------------|-------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------|---------|----------------------|----------| +| - | dryrun | Enable a dryrun mode that don't execute open or close functions | config.dryrun | False | Boolean | Optional | +| common | locker | A binary sensor who block opening for open, close and adaptive when state is On | config.common.locker | None | Binary Sensor Entity | Optional | +| common | seasons | The sensor of Seasons integration (https://www.home-assistant.io/integrations/season) who return the actual season | config.common.seasons | None | Sensor Entity | Optional | +| position | opened | Define the max position allowed (%) when cover is open | config.common.position.opened | 100 | Integer | Optional | +| position | closed | Define the min position allowed (%) when cover is closed | config.common.position.opened | 0 | Integer | Optional | +| position | min_ratio_change | Define minimum percent of move to allow action | config.common.position.min_ratio_change | 5 | Integer | Optional | +| position | min_time_change | Define minimum time in minutes allowed between move | config.common.position.min_time_change | 10 | Integer | Optional | +| opening | type | Define method to open covers the morning (Allowed value : off|time|sunrise|lux|prefer-lux) | config.common.opening.type | off | String | Optional | +| opening | time | Time to open covers - Only work with time or prefer-lux type | config.common.opening.time | None | Time | Optional | +| opening | locker | A binary sensor who block opening when state is On | config.common.opening.locker | None | Binary Sensor Entity | Optional | +| closing | type | Define method to open covers the morning (Allowed value : off|time|sunrise|lux|prefer-lux) | config.common.closing.type | off | String | Optional | +| closing | time | Time to open covers - Only work with time or prefer-lux type | config.common.closing.time | None | Time | Optional | +| closing | locker | A binary sensor who block closing when state is On | config.common.closing.locker | None | Binary Sensor Entity | Optional | +| closing | secure_dusk | Close at dusk in 2 layer if first closing method failed - Only work with time or prefer-lux type | config.common.closing.secure_dusk | False | Boolean | Optional | +| adaptive | enable | Enable adaptive mode who close/open covers based on Sun position and indoor/outdoor temperature | config.common.adaptive.enable | False | Boolean | Optional | +| adaptive | locker | A binary sensor who block adaptive mode when state is On | config.common.adaptive.enable | False | Boolean | Optional | +| manual | allow | Enable or Disable detection of manual position change of covers | config.common.manual.allow | False | Boolean | Optional | +| manual | timer | Time to block movements when manual position change is detected. Required if config.common.manual.allow is True | config.common.manual.timer | None | TimeDelta | Optional | +| temperature.indoor | sensor | Sensor who provide indoor temperature (Positive Integer - No Float) | config.common.temperature.indoor.sensor | None | Sensor Entity | Optional | +| temperature.indoor | setpoint | Indoor temperature setpoint. Below => We need to heat with sun / Above => We need to block sun | config.common.temperature.indoor.setpoint | None | PositiveInt | Optional | +| temperature.indoor.seasons. | setpoint | Define a setpoint for indoor temperature depending of a specific season | config.common.temperature.indoor.seasons..setpoint | None | PositiveInt | Optional | +| temperature.outdoor | sensor | Sensor who provide outdoor temperature (Positive Integer - No Float) | config.common.temperature.outdoor.sensor | None | Sensor Entity | Optional | +| temperature.outdoor | low_temperature | Outdoor temperature to trigger to enable adaptive mode in addition to indoor_temperature | config.common.temperature.outdoor.low_temperature | None | PositiveInt | Optional | +| temperature.outdoor | high_temperature | Outdoor temperature to trigger when we need to totally close cover to protect from heat. Required when Outdoor sensor is configured | config.common.temperature.outdoor.high_temperature | None | PositiveInt | Optional | +| lux | sensor | Sensor who provide outside Lux | config.common.lux.sensor | None | Sensor Entity | Optional | +| lux | open_lux | Trigger in lux to open covers. Required if type of opening is lux or prefer-lux | config.common.lux.open_lux | None | PositiveInt | Optional | +| lux | close_lux | Trigger in lux to close covers. Required if type of closing is lux or prefer-lux | config.common.lux.close_lux | None | PositiveInt | Optional | Parameters for covers are : @@ -302,6 +306,7 @@ CoversManager: config: common: locker: "binary_sensor.alarm_status" + seasons: "sensor.season" position: opened: 100 closed: 0 @@ -314,8 +319,10 @@ CoversManager: closing: type: "prefer-lux" secure_dusk: True - adaptive: True locker: "binary_sensor.locker_closing" # If at least one of closing and global locker are True, lock is True + adaptive: + enable: true + locker: "binary_sensor.locker_adaptive" # If at least one of adaptive and global locker are True, lock is True manual: allow: true timer: 01:00:00 @@ -323,6 +330,15 @@ CoversManager: indoor: sensor: "sensor.indoor_temperature" setpoint: 23 + seasons: + spring: + setpoint: 23 + summer: + setpoint: 23 + autumn: + setpoint: 23 + winter: + setpoint: 23 outdoor: sensor: "sensor.outdoor_sensor_temperature" low_temperature: 25 @@ -354,7 +370,8 @@ CoversManager: ## 🪲 Debug -To help to debug and understand an issue, you can enable the debug mode in app. +To help to debug and understand an issue, you can enable the debug mode in your CoverManager app declared in `apps.yaml`. + For this, edit the app configuration and set `log_level` to DEBUG ```yaml @@ -404,4 +421,4 @@ Once your PR is reviewed and approved, it will be merged into the dev branch. > [!WARNING] > -> Only PR to `dev` branch will be accepted. \ No newline at end of file +> Only PR to `dev` branch will be accepted. \ No newline at end of file diff --git a/apps/CoversManager/CoversManagerLibs/config_validator.py b/apps/CoversManager/CoversManagerLibs/config_validator.py index af1cd8b..6428923 100644 --- a/apps/CoversManager/CoversManagerLibs/config_validator.py +++ b/apps/CoversManager/CoversManagerLibs/config_validator.py @@ -37,10 +37,49 @@ def checks(self) -> Self: raise ValueError("High temperature must be defined when outdoor sensor is defined") return self +class SeasonConfig(BaseModel): + setpoint: PositiveInt | None = None + +class SeasonsConfig(BaseModel): + spring: SeasonConfig = SeasonConfig() + summer: SeasonConfig = SeasonConfig() + autumn: SeasonConfig = SeasonConfig() + winter: SeasonConfig = SeasonConfig() + + @field_validator("spring", mode="before") + def spring_none_default_values(cls, value): + if value is None: + return SeasonConfig() + return value + + @field_validator("summer", mode="before") + def summer_none_default_values(cls, value): + if value is None: + return SeasonConfig() + return value + + @field_validator("autumn", mode="before") + def autumn_none_default_values(cls, value): + if value is None: + return SeasonConfig() + return value + + @field_validator("winter", mode="before") + def winter_none_default_values(cls, value): + if value is None: + return SeasonConfig() + return value class TemperatureIndoorConfig(BaseModel): sensor: sensor_entity_format | None = None setpoint: PositiveInt | None = None + seasons: SeasonsConfig = SeasonsConfig() + + @field_validator("seasons", mode="before") + def seasons_none_default_values(cls, value): + if value is None: + return SeasonsConfig() + return value class TemperatureConfig(BaseModel): @@ -98,7 +137,6 @@ class ClosingConfig(BaseModel): type: Literal["off", "time", "sunset", "lux", "prefer-lux"] = "off" time: time_ | None = None secure_dusk: bool = False - adaptive: bool = False locker: binary_sensor_entity_format | None = None @model_validator(mode="after") @@ -117,16 +155,21 @@ def checks(self) -> Self: ) return self +class AdaptiveConfig(BaseModel): + enable: bool = False + locker: binary_sensor_entity_format | None = None + class CommonConfig(BaseModel): position: PositionConfig = PositionConfig() opening: OpeningConfig = OpeningConfig() closing: ClosingConfig = ClosingConfig() - adaptive: bool = False + adaptive: AdaptiveConfig = AdaptiveConfig() manual: ManualConfig = ManualConfig() - temperature: TemperatureConfig | None = None + temperature: TemperatureConfig = TemperatureConfig() lux: LuxConfig | None = None locker: binary_sensor_entity_format | None = None + seasons: sensor_entity_format | None = None @field_validator("position", mode="before") def position_none_default_values(cls, value): @@ -146,12 +189,42 @@ def closing_none_default_values(cls, value): return ClosingConfig() return value + @field_validator("adaptive", mode="before") + def adaptive_none_default_values(cls, value): + if value is None: + return AdaptiveConfig() + return value + @field_validator("manual", mode="before") def manual_none_default_values(cls, value): if value is None: return ManualConfig() return value + @field_validator("temperature", mode="before") + def temperature_none_default_values(cls, value): + if value is None: + return TemperatureConfig() + return value + + @model_validator(mode="after") + def checks(self) -> Self: + # Seasons configuration check + if ( + self.seasons is None + and ( + self.temperature.indoor.seasons.spring.setpoint is not None + or self.temperature.indoor.seasons.summer.setpoint is not None + or self.temperature.indoor.seasons.autumn.setpoint is not None + or self.temperature.indoor.seasons.winter.setpoint is not None + ) + ): + raise ValueError( + "Seasons configuration (config.common.seasons) is missing to use " + "seasons setpoints (config.common.temperature.indoor.seasons)" + ) + return self + class PositionalConfig(BaseModel): action: bool = True @@ -222,16 +295,16 @@ def checks(self) -> Self: "sensor", "setpoint", ] - if self.common.adaptive and (self.common.temperature is None or self.common.temperature.indoor is None): + if self.common.adaptive.enable and (self.common.temperature is None or self.common.temperature.indoor is None): raise ValueError( "Temperature configuration (config.common.temperature.indoor) is missing as " - "adaptive mode (config.common.adaptive) is enabled (True)" + "adaptive mode (config.common.adaptive.enable) is enabled (True)" ) for param in temperatureIndoorParameters: - if self.common.adaptive and getattr(self.common.temperature.indoor, param) is None: + if self.common.adaptive.enable and getattr(self.common.temperature.indoor, param) is None: raise ValueError( f"Configuration {param} (config.common.temperature.indoor.{param}) must be defined as " - "adaptive mode (config.common.adaptive) is enabled (True)" + "adaptive mode (config.common.adaptive.enable) is enabled (True)" ) # Covers configuration check if self.covers is None: diff --git a/apps/CoversManager/covers_manager.py b/apps/CoversManager/covers_manager.py index c03e901..9f76988 100644 --- a/apps/CoversManager/covers_manager.py +++ b/apps/CoversManager/covers_manager.py @@ -51,8 +51,10 @@ def initialize(self): self.listen_state( callback=self._callback_listenstate_covers, entity_id=config.common.lux.sensor, - new=lambda x: int(x) >= int(config.common.lux.open_lux) if x.isdigit() else False, - old=lambda x: int(x) < int(config.common.lux.open_lux) if x.isdigit() else False, + new=lambda x: float(x) >= config.common.lux.open_lux + if x.replace('.', '', 1).isdigit() else False, + old=lambda x: float(x) < config.common.lux.open_lux + if x.replace('.', '', 1).isdigit() else False, config=config, action="open", ) @@ -66,8 +68,10 @@ def initialize(self): self.listen_state( callback=self._callback_listenstate_covers, entity_id=config.common.lux.sensor, - new=lambda x: int(x) >= int(config.common.lux.open_lux) if x.isdigit() else False, - old=lambda x: int(x) < int(config.common.lux.open_lux) if x.isdigit() else False, + new=lambda x: float(x) >= config.common.lux.open_lux + if x.replace('.', '', 1).isdigit() else False, + old=lambda x: float(x) < config.common.lux.open_lux + if x.replace('.', '', 1).isdigit() else False, config=config, action="open", ) @@ -78,7 +82,7 @@ def initialize(self): ) # Manage Adaptive - if config.common.closing.adaptive: + if config.common.adaptive.enable: self.log("Adaptive mode is enabled", level="DEBUG") for cover in config.covers.dict().keys(): self.log( @@ -157,6 +161,7 @@ def initialize(self): ) and (int(sunazimuth) <= azimuth_right), new=lambda sunazimuth, azimuth_right=azimuth_right: int(sunazimuth) > azimuth_right, + config=config, cover=cover, ) # Listen state for cover manual move detection @@ -196,8 +201,10 @@ def initialize(self): self.listen_state( callback=self._callback_listenstate_covers, entity_id=config.common.lux.sensor, - new=lambda x: int(x) <= config.common.lux.close_lux if x.isdigit() else False, - old=lambda x: int(x) > config.common.lux.close_lux if x.isdigit() else False, + new=lambda x: float(x) <= config.common.lux.close_lux + if x.replace('.', '', 1).isdigit() else False, + old=lambda x: float(x) > config.common.lux.close_lux + if x.replace('.', '', 1).isdigit() else False, config=config, action="close", ) @@ -224,8 +231,10 @@ def initialize(self): self.listen_state( callback=self._callback_listenstate_covers, entity_id=config.common.lux.sensor, - new=lambda x: int(x) <= config.common.lux.close_lux if x.isdigit() else False, - old=lambda x: int(x) > config.common.lux.close_lux if x.isdigit() else False, + new=lambda x: float(x) <= config.common.lux.close_lux + if x.replace('.', '', 1).isdigit() else False, + old=lambda x: float(x) > config.common.lux.close_lux if + x.replace('.', '', 1).isdigit() else False, config=config, action="close", ) @@ -250,6 +259,7 @@ def _verify_entities(self, config: ConfigValidator.Config) -> None: """ # Define all entities to check entities_dict = { + "config.common.seasons": (config.common.seasons if config.common.seasons is not None else None), "config.common.temperature.indoor.sensor": ( config.common.temperature.indoor.sensor if config.common.temperature is not None else None ), @@ -264,6 +274,9 @@ def _verify_entities(self, config: ConfigValidator.Config) -> None: "config.common.closing.locker": ( config.common.closing.locker if config.common.closing.locker is not None else None ), + "config.common.adaptive.locker": ( + config.common.adaptive.locker if config.common.adaptive.locker is not None else None + ), } # Add covers entities in list of entities to check for index, cover in enumerate(config.covers.dict().keys()): @@ -417,10 +430,18 @@ def _callback_listenstate_suninwindow( outdoor_temperature = float(self.get_state(entity_id=kwargs["config"].common.temperature.outdoor.sensor)) # Check if the indoor temperature is lower or equal than the indoor setpoint - if indoor_temperature <= int(kwargs["config"].common.temperature.indoor.setpoint): + if kwargs["config"].common.seasons is not None: + setpoint = self._get_indoor_setpoint( + seasons_entity=kwargs["config"].common.seasons, + setpoint=kwargs["config"].common.temperature.indoor.setpoint, + seasons=kwargs["config"].common.temperature.indoor.seasons, + ) + else: + setpoint = kwargs["config"].common.temperature.indoor.setpoint + if indoor_temperature <= setpoint: self.log( f"Indoor temperature ({indoor_temperature}) <= " - f"{kwargs['config'].common.temperature.indoor.setpoint} " + f"{setpoint} " f"- Cover '{self.friendly_name(entity_id=kwargs['cover']).strip()}' ({kwargs['cover']}) " "need to be open to heat the room", level="DEBUG", @@ -431,10 +452,9 @@ def _callback_listenstate_suninwindow( # or if the outdoor temperature is higher or equal than the outdoor low temperature (if defined) # but lower than the high temperature if ( - indoor_temperature > int(kwargs["config"].common.temperature.indoor.setpoint) - and kwargs["config"].common.temperature.outdoor.low_temperature is None + indoor_temperature > setpoint and kwargs["config"].common.temperature.outdoor.low_temperature is None ) or ( - indoor_temperature > int(kwargs["config"].common.temperature.indoor.setpoint) + indoor_temperature > setpoint and kwargs["config"].common.temperature.outdoor.sensor is not None and ( outdoor_temperature >= int(kwargs["config"].common.temperature.outdoor.low_temperature) @@ -442,19 +462,19 @@ def _callback_listenstate_suninwindow( ) ): if ( - indoor_temperature > int(kwargs["config"].common.temperature.indoor.setpoint) + indoor_temperature > setpoint and kwargs["config"].common.temperature.outdoor.low_temperature is None ): self.log( f"Indoor temperature ({indoor_temperature}) is greater than " - f"{kwargs['config'].common.temperature.indoor.setpoint} - Adaptive mode will be used for cover " + f"{setpoint} - Adaptive mode will be used for cover " f"'{self.friendly_name(entity_id=kwargs['cover']).strip()}' ({kwargs['cover']})", level="INFO", ) else: self.log( f"Indoor temperature ({indoor_temperature}) is greater than " - f"{kwargs['config'].common.temperature.indoor.setpoint} and outdoor temperature " + f"{setpoint} and outdoor temperature " f"({outdoor_temperature}) is between " f"{kwargs['config'].common.temperature.outdoor.low_temperature} and " f"{kwargs['config'].common.temperature.outdoor.high_temperature} - " @@ -1090,9 +1110,15 @@ def _get_islocked(self, config: ConfigValidator.Config, action: str) -> bool: else: closing_locker = False + if config.common.adaptive.locker is not None: + adaptive_locker = True if self.get_state(entity_id=config.common.adaptive.locker) == "on" else False + else: + adaptive_locker = False + self.log( f"Action : {action} - Global Locker: {global_locker} " - f"- Opening Locker: {opening_locker} - Closing/Adaptive Locker: {closing_locker}", + f"- Opening Locker: {opening_locker} - Closing Locker: {closing_locker} " + f"- Adaptive Locker: {adaptive_locker}", level="DEBUG", ) @@ -1100,12 +1126,41 @@ def _get_islocked(self, config: ConfigValidator.Config, action: str) -> bool: case "open": decision = global_locker or opening_locker return decision - case "close" | "adaptive": + case "close": decision = global_locker or closing_locker return decision + case "adaptive": + decision = global_locker or adaptive_locker + return decision case _: self.log( f"Action {action} is not valid for locker verification. Returning unlocked...", level="ERROR", ) return False + + def _get_indoor_setpoint( + self, seasons_entity: str, setpoint: int, seasons: ConfigValidator.SeasonsConfig | None + ) -> int: + """ + Determines the indoor setpoint based on the current season. + Args: + seasons_entity (str): The entity ID representing the current season. + setpoint (int): The default setpoint to use if no specific setpoint is found for the current season. + seasons (ConfigValidator.SeasonsConfig): Configuration object containing setpoints for different seasons. + Returns: + int: The setpoint for the current season if available, otherwise the default setpoint. + """ + + current_season = str(self.get_state(entity_id=seasons_entity)) + self.log(f"Current season : {current_season}", level="DEBUG") + self.log(f"Seasons : {seasons}", level="DEBUG") + + if current_season in ["spring", "summer", "autumn", "winter"]: + season_setpoint = getattr(seasons, current_season).setpoint + if season_setpoint is not None: + self.log(f"Setpoint for {current_season} : {season_setpoint}", level="DEBUG") + return int(season_setpoint) + + self.log(f"Setpoint for {current_season} not found. Using default setpoint : {setpoint}", level="DEBUG") + return int(setpoint)