Skip to content

Commit

Permalink
feat: picks target temperature from preset range if target temp is no…
Browse files Browse the repository at this point in the history
…t set

Fixes #185
  • Loading branch information
= authored and swingerman committed May 30, 2024
1 parent 489e888 commit ed98880
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 48 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,9 +251,19 @@ Currrnetly supported presets are:

To set presets you need to add entries for them in the configuration file like this:

You have 4 options here:

1. Set the `temperature` for heat, cool or fan-only mode
2. Set the `target_temp_low` and `target_temp_high` for heat_cool mode. If `temperature` is not set but `target_temp_low` and `target_temp_high` are set, the `temperature` will be picked based on hvac mode. For heat mode it will be `target_temp_low` and for cool, fan_only mode it will be `target_temp_high`
3. Set the `humidity` for dry mode
4. Set all above

### Presets Configuration

```yaml
preset_name:
temperature: 13
humidity: 50 # <-- only if dry mode configured
target_temp_low: 12
target_temp_high: 14
```
Expand Down
8 changes: 8 additions & 0 deletions config/configuration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,14 @@ climate:
target_temp: 20
cold_tolerance: 0.3
hot_tolerance: 0.3
away:
target_temp_high: 30
target_temp_low: 23
humidity: 55
sleep:
target_temp_high: 26
target_temp_low: 18
humidity: 60

# - platform: dual_smart_thermostat
# name: AUX Heat Room
Expand Down
25 changes: 1 addition & 24 deletions custom_components/dual_smart_thermostat/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,27 +264,6 @@ async def async_setup_platform(
sensor_humidity_entity_id = config.get(CONF_HUMIDITY_SENSOR)
sensor_stale_duration: timedelta | None = config.get(CONF_STALE_DURATION)
keep_alive = config.get(CONF_KEEP_ALIVE)
presets_dict = {
key: config[value] for key, value in CONF_PRESETS.items() if value in config
}
_LOGGER.debug("Presets dict: %s", presets_dict)
presets = {
key: values[ATTR_TEMPERATURE]
for key, values in presets_dict.items()
if ATTR_TEMPERATURE in values
}
_LOGGER.debug("Presets: %s", presets)

# Try to load presets in old format and use if new format not available in config
old_presets = {k: config[v] for k, v in CONF_PRESETS_OLD.items() if v in config}
if old_presets:
_LOGGER.warning(
"Found deprecated presets settings in configuration. "
"Please remove and replace with new presets settings format. "
"Read documentation in integration repository for more details"
)
if not presets_dict:
presets = old_presets

precision = config.get(CONF_PRECISION)
unit = hass.config.units.temperature_unit
Expand All @@ -295,7 +274,6 @@ async def async_setup_platform(
environment_manager = EnvironmentManager(
hass,
config,
presets,
)

feature_manager = FeatureManager(hass, config, environment_manager)
Expand Down Expand Up @@ -779,7 +757,6 @@ def _set_support_flags(self) -> None:
self.presets.presets,
self.presets.presets_range,
self.presets.preset_mode,
self.hvac_device.hvac_modes,
self._hvac_mode,
)
self._attr_supported_features = self.features.supported_features
Expand Down Expand Up @@ -1145,7 +1122,7 @@ def _is_device_active(self) -> bool:
return self.hvac_device.is_active

async def async_set_preset_mode(self, preset_mode: str) -> None:
self.presets.set_preset_mode(preset_mode)
self.presets.set_preset_mode(preset_mode, self.hvac_device.hvac_mode)

self._attr_preset_mode = self.presets.preset_mode
await self._async_control_climate(force=True)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
DEFAULT_MAX_TEMP,
DEFAULT_MIN_TEMP,
)
from homeassistant.components.climate.const import HVACMode
from homeassistant.components.climate.const import PRESET_NONE, HVACMode
from homeassistant.const import (
ATTR_TEMPERATURE,
PRECISION_WHOLE,
Expand Down Expand Up @@ -69,12 +69,7 @@ def __init__(self, temperature: float, temp_high: float, temp_low: float) -> Non
class EnvironmentManager(StateManager):
"""Class to manage the temperatures of the thermostat."""

def __init__(
self,
hass: HomeAssistant,
config: ConfigType,
presets: dict[str, Any],
):
def __init__(self, hass: HomeAssistant, config: ConfigType):
self.hass = hass
self._sensor_floor = config.get(CONF_FLOOR_SENSOR)
self._sensor = config.get(CONF_SENSOR)
Expand Down Expand Up @@ -102,7 +97,7 @@ def __init__(
self._hot_tolerance = config.get(CONF_HOT_TOLERANCE)
self._fan_hot_tolerance = config.get(CONF_FAN_HOT_TOLERANCE)

self._saved_target_temp = self.target_temp or next(iter(presets.values()), None)
self._saved_target_temp = self.target_temp or None
self._saved_target_temp_low = None
self._saved_target_temp_high = None
self._temp_precision = config.get(CONF_PRECISION)
Expand Down Expand Up @@ -581,6 +576,27 @@ def _set_default_temps_range_mode(self) -> None:
else:
self._target_temp_high += PRECISION_WHOLE

def set_temepratures_from_hvac_mode_and_presets(
self, hvac_mode: HVACMode, preset_mode: str, presets_range: dict[str, Any]
) -> None:
if preset_mode is None or preset_mode is PRESET_NONE:
return

_LOGGER.debug(
"Setting temperatures from hvac mode and presets: %s, %s, %s",
hvac_mode,
preset_mode,
presets_range,
)

if hvac_mode == HVACMode.HEAT and presets_range[preset_mode][0] is not None:
self._target_temp = presets_range[preset_mode][0]
elif (
hvac_mode in [HVACMode.COOL, HVACMode.FAN_ONLY]
and presets_range[preset_mode][0] is not None
):
self._target_temp = presets_range[preset_mode][1]

def apply_old_state(self, old_state: State) -> None:
_LOGGER.debug("Applying old state: %s", old_state)
if old_state is None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,6 @@ def set_support_flags(
presets: dict[str, Any],
presets_range,
preset_mode: str,
hvac_modes: list[HVACMode],
current_hvac_mode: HVACMode = None,
) -> None:
"""Set the correct support flags based on configuration."""
Expand All @@ -185,14 +184,19 @@ def set_support_flags(
HVACMode.FAN_ONLY,
HVACMode.HEAT,
):
if self.is_range_mode and preset_mode != PRESET_NONE:
self.environment.set_temperature_range_from_saved()
self._supported_features = (
self._default_support_flags | ClimateEntityFeature.TARGET_TEMPERATURE
)
if len(presets):
_LOGGER.debug("Setting support flags to %s", self._supported_features)
_LOGGER.debug(
"Setting support target mode flags to %s", self._supported_features
)
self._supported_features |= ClimateEntityFeature.PRESET_MODE

self.environment.set_temepratures_from_hvac_mode_and_presets(
current_hvac_mode, preset_mode, presets_range
)

elif current_hvac_mode == HVACMode.DRY:
self._supported_features = (
self._default_support_flags | ClimateEntityFeature.TARGET_HUMIDITY
Expand All @@ -213,12 +217,13 @@ def set_support_flags(
"Setting support flags presets in range mode to %s",
self._supported_features,
)
self.environment.set_default_target_temps(
self.is_target_mode, self.is_range_mode, current_hvac_mode
)

if self.is_configured_for_dryer_mode:
if preset_mode == PRESET_NONE:
self.environment.set_default_target_temps(
self.is_target_mode, self.is_range_mode, current_hvac_mode
)

if self.is_configured_for_dryer_mode:
self._supported_features |= ClimateEntityFeature.TARGET_HUMIDITY

def apply_old_state(self, old_state: State, hvac_mode, presets_range) -> None:
Expand Down
23 changes: 19 additions & 4 deletions custom_components/dual_smart_thermostat/managers/preset_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW,
PRESET_NONE,
HVACMode,
)
from homeassistant.components.humidifier import ATTR_HUMIDITY
from homeassistant.const import ATTR_TEMPERATURE
Expand Down Expand Up @@ -93,6 +94,11 @@ def __init__(
set(self._preset_range_modes) - set(self._preset_modes)
)

# sets the target environment to the preset mode
self._environment.saved_target_temp = self._environment.target_temp or next(
iter(presets.values()), None
)

@property
def presets(self):
return self._presets
Expand All @@ -113,7 +119,7 @@ def preset_mode(self):
def has_presets(self):
return len(self.presets) > 0

def set_preset_mode(self, preset_mode: str) -> None:
def set_preset_mode(self, preset_mode: str, hvac_mode: HVACMode) -> None:
"""Set new preset mode."""
_LOGGER.debug("Setting preset mode: %s", preset_mode)
if preset_mode not in (self.preset_modes or []):
Expand All @@ -127,7 +133,7 @@ def set_preset_mode(self, preset_mode: str) -> None:
if preset_mode == PRESET_NONE:
self._set_presets_when_no_preset_mode()
else:
self._set_presets_when_have_preset_mode(preset_mode)
self._set_presets_when_have_preset_mode(preset_mode, hvac_mode)

def _set_presets_when_no_preset_mode(self):
"""Sets target environment when preset is none."""
Expand All @@ -150,7 +156,7 @@ def _set_presets_when_no_preset_mode(self):
if self._environment.saved_target_humidity:
self._environment.target_humidity = self._environment.saved_target_humidity

def _set_presets_when_have_preset_mode(self, preset_mode: str):
def _set_presets_when_have_preset_mode(self, preset_mode: str, hvac_mode: HVACMode):
"""Sets target temperatures when have preset is not none."""
_LOGGER.debug("Setting presets when have preset mode")
if self._features.is_range_mode:
Expand All @@ -166,7 +172,16 @@ def _set_presets_when_have_preset_mode(self, preset_mode: str):
else:
if self._preset_mode == PRESET_NONE:
self._environment.saved_target_temp = self._environment.target_temp
self._environment.target_temp = self._presets[preset_mode][ATTR_TEMPERATURE]
# handles when temperature is set in preset
if self._presets[preset_mode].get(ATTR_TEMPERATURE) is not None:
self._environment.target_temp = self._presets[preset_mode][
ATTR_TEMPERATURE
]
# handles when temperature is not set in preset but temp range is set
else:
self._environment.set_temepratures_from_hvac_mode_and_presets(
hvac_mode, preset_mode, self._presets_range
)

if self._features.is_configured_for_dryer_mode:
if self._preset_mode == PRESET_NONE:
Expand Down
52 changes: 52 additions & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,58 @@ async def setup_comp_heat_cool_presets(hass: HomeAssistant) -> None:
await hass.async_block_till_done()


@pytest.fixture
async def setup_comp_heat_cool_presets_range_only(hass: HomeAssistant) -> None:
"""Initialize components."""
hass.config.units = METRIC_SYSTEM
assert await async_setup_component(
hass,
CLIMATE,
{
"climate": {
"platform": DOMAIN,
"name": "test",
"cold_tolerance": 2,
"hot_tolerance": 4,
"heat_cool_mode": True,
"heater": common.ENT_HEATER,
"cooler": common.ENT_COOLER,
"target_sensor": common.ENT_SENSOR,
"initial_hvac_mode": HVACMode.HEAT_COOL,
PRESET_AWAY: {
"target_temp_low": 16,
"target_temp_high": 30,
},
PRESET_COMFORT: {
"target_temp_low": 20,
"target_temp_high": 27,
},
PRESET_ECO: {
"target_temp_low": 18,
"target_temp_high": 29,
},
PRESET_HOME: {
"target_temp_low": 19,
"target_temp_high": 23,
},
PRESET_SLEEP: {
"target_temp_low": 17,
"target_temp_high": 24,
},
PRESET_ACTIVITY: {
"target_temp_low": 21,
"target_temp_high": 28,
},
"anti_freeze": {
"target_temp_low": 5,
"target_temp_high": 32,
},
}
},
)
await hass.async_block_till_done()


@pytest.fixture
async def setup_comp_heat_cool_safety_delay(hass: HomeAssistant) -> None:
"""Initialize components."""
Expand Down
Loading

0 comments on commit ed98880

Please sign in to comment.