Skip to content

Commit

Permalink
fix: unable to set hvac mode after selecting preset
Browse files Browse the repository at this point in the history
Fixes #178
  • Loading branch information
= authored and swingerman committed May 3, 2024
1 parent ce8b169 commit 2111365
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 4 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ The `dual_smart_thermostat` is an enhanced version of generic thermostat impleme
| **Floor Temperature Control** | <img src="docs/images/heating-coil.svg" height="30" /> <img src="docs/images/snowflake-thermometer.svg" height="30" /> <img src="docs/images/thermometer-alert.svg" height="30" /> | [<img src="docs/images/file-document-outline.svg" height="30" />](#floor-heating-temperature-control) |
| **Window/Door sensor integration** | <img src="docs/images/window-open.svg" height="30" /> <img src="docs/images/door-open.svg" height="30" /> <img src="docs/images/chevron-right.svg" height="30" /> <img src="docs/images/timer-cog-outline.svg" height="30" /> <img src="docs/images/chevron-right.svg" height="30" /> <img src="docs/images/hvac-off.svg" height="30" /> | [<img src="docs/images/file-document-outline.svg" height="30" />](#openings) |
| **Presets** | <img src="docs/images/sleep.svg" height="30" /> <img src="docs/images/snowflake-thermometer.svg" height="30" /> <img src="docs/images/shield-lock-outline.svg" height="30" /> | [<img src="docs/images/file-document-outline.svg" height="30" />](#presets) |
| **HVAC Action Reason** | | [<img src="docs/images/file-document-outline.svg" height="30" />](#presets) |
| **HVAC Action Reason** | | [<img src="docs/images/file-document-outline.svg" height="30" />](#hvac-action-reason) |

## Heat/Cool Mode

Expand Down Expand Up @@ -239,6 +239,7 @@ The internal values can be set by the component only and the external values can
|-------|-------------|
| `none` | No action reason |
| `target_temp_not_reached` | The target temperature has not been reached |
| `target_temp_not_reached_with_fan` | The target temperature has not been reached trying it with a fan |
| `target_temp_reached` | The target temperature has been reached |
| `misconfiguration` | The thermostat is misconfigured |
| `opening` | The thermostat is idle because an opening is open |
Expand Down
30 changes: 30 additions & 0 deletions config/configuration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,36 @@ climate:
target_temp_high: 20.5
target_temp_low: 19.5

- platform: dual_smart_thermostat
name: Edge Case 178
unique_id: edge_case_178
heater: switch.heater
cooler: switch.cooler
target_sensor: sensor.room_temp
heat_cool_mode: true
target_temp_step: 0.5
min_temp: 9
max_temp: 32
target_temp: 19.5
target_temp_high: 20.5
target_temp_low: 19.5
away:
temperature: 12
target_temp_low: 12
target_temp_high: 22.5
home:
temperature: 20
target_temp_low: 19
target_temp_high: 20.5
sleep:
temperature: 17
target_temp_low: 17
target_temp_high: 21
eco:
temperature: 19
target_temp_low: 19
target_temp_high: 21.5

- platform: dual_smart_thermostat
name: AUX Heat Room
unique_id: aux_heat_room
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ class HVACActionReasonInternal(StrEnum):

TARGET_TEMP_REACHED = "target_temp_reached"

TARGET_TEMP_NOT_REACHED_WITH_FAN = "target_temp_not_reached_with_fan"

MISCONFIGURATION = "misconfiguration"

OPENING = "opening"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,9 @@ async def async_control_hvac(self, time=None, force=False):
_LOGGER.info("within fan tolerance")
await self.fan_device.async_control_hvac(time, force)
await self.cooler_device.async_turn_off()
self.HVACActionReason = self.fan_device.HVACActionReason
self.HVACActionReason = (
HVACActionReason.TARGET_TEMP_NOT_REACHED_WITH_FAN
)

else:
_LOGGER.info("outside fan tolerance")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,12 @@ def set_support_flags(
HVACMode.HEAT,
):
if self.is_range_mode and preset_mode != PRESET_NONE:
self.temperatures.target_temp_low = self._saved_target_temp_low
self.temperatures.target_temp_high = self._saved_target_temp_high
self.temperatures.target_temp_low = (
self.temperatures.saved_target_temp_low
)
self.temperatures.target_temp_high = (
self.temperatures.saved_target_temp_high
)
self._supported_features = (
self._default_support_flags | ClimateEntityFeature.TARGET_TEMPERATURE
)
Expand Down
60 changes: 60 additions & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,66 @@ async def setup_comp_heat_cool_presets(hass: HomeAssistant) -> None:
await hass.async_block_till_done()


@pytest.fixture
async def setup_comp_heat_cool_fan_presets(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,
"fan": common.ENT_FAN,
"target_sensor": common.ENT_SENSOR,
"initial_hvac_mode": HVACMode.HEAT_COOL,
PRESET_AWAY: {
"temperature": 16,
"target_temp_low": 16,
"target_temp_high": 30,
},
PRESET_COMFORT: {
"temperature": 20,
"target_temp_low": 20,
"target_temp_high": 27,
},
PRESET_ECO: {
"temperature": 18,
"target_temp_low": 18,
"target_temp_high": 29,
},
PRESET_HOME: {
"temperature": 19,
"target_temp_low": 19,
"target_temp_high": 23,
},
PRESET_SLEEP: {
"temperature": 17,
"target_temp_low": 17,
"target_temp_high": 24,
},
PRESET_ACTIVITY: {
"temperature": 21,
"target_temp_low": 21,
"target_temp_high": 28,
},
"anti_freeze": {
"temperature": 5,
"target_temp_low": 5,
"target_temp_high": 32,
},
}
},
)
await hass.async_block_till_done()


async def setup_component(hass: HomeAssistant, mock_config: dict) -> MockConfigEntry:
"""Initialize knmi for tests."""
config_entry = MockConfigEntry(domain=DOMAIN, data=mock_config, entry_id="test")
Expand Down
112 changes: 112 additions & 0 deletions tests/test_dual_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
setup_comp_heat_cool_2,
setup_comp_heat_cool_fan_config,
setup_comp_heat_cool_fan_config_2,
setup_comp_heat_cool_fan_presets,
setup_comp_heat_cool_presets,
setup_floor_sensor,
setup_sensor,
Expand Down Expand Up @@ -749,6 +750,117 @@ async def test_heat_cool_set_preset_mode_set_temp_keeps_preset_mode(
assert state.attributes.get("target_temp_high") == 22


@pytest.mark.parametrize(
("preset", "temp_low", "temp_high"),
[
(PRESET_NONE, 18, 22),
(PRESET_AWAY, 16, 30),
(PRESET_COMFORT, 20, 27),
(PRESET_ECO, 18, 29),
(PRESET_HOME, 19, 23),
(PRESET_SLEEP, 17, 24),
(PRESET_ACTIVITY, 21, 28),
(PRESET_ANTI_FREEZE, 5, 32),
],
)
async def test_heat_cool_fan_set_preset_mode_set_temp_keeps_preset_mode(
hass: HomeAssistant,
setup_comp_heat_cool_fan_presets, # noqa: F811
preset,
temp_low,
temp_high,
) -> None:
"""Test the setting preset mode then set temperature.
Verify preset mode preserved while temperature updated.
"""
test_target_temp_low = 3
test_target_temp_high = 33
await common.async_set_temperature(hass, 18, common.ENTITY, 22, 18)
await common.async_set_preset_mode(hass, preset)
state = hass.states.get(common.ENTITY)
assert state.attributes.get("target_temp_low") == temp_low
assert state.attributes.get("target_temp_high") == temp_high
await common.async_set_temperature(
hass,
test_target_temp_low,
common.ENTITY,
test_target_temp_high,
test_target_temp_low,
)
state = hass.states.get(common.ENTITY)
assert state.attributes.get("target_temp_low") == test_target_temp_low
assert state.attributes.get("target_temp_high") == test_target_temp_high
assert state.attributes.get("preset_mode") == preset
assert state.attributes.get("supported_features") == 402
await common.async_set_preset_mode(hass, PRESET_NONE)
state = hass.states.get(common.ENTITY)
if preset == PRESET_NONE:
assert state.attributes.get("target_temp_low") == test_target_temp_low
assert state.attributes.get("target_temp_high") == test_target_temp_high
else:
assert state.attributes.get("target_temp_low") == 18
assert state.attributes.get("target_temp_high") == 22


@pytest.mark.parametrize(
("preset", "temp_low", "temp_high"),
[
(PRESET_NONE, 18, 22),
(PRESET_AWAY, 16, 30),
(PRESET_COMFORT, 20, 27),
(PRESET_ECO, 18, 29),
(PRESET_HOME, 19, 23),
(PRESET_SLEEP, 17, 24),
(PRESET_ACTIVITY, 21, 28),
(PRESET_ANTI_FREEZE, 5, 32),
],
)
async def test_heat_cool_fan_set_preset_mode_change_hvac_mode(
hass: HomeAssistant,
setup_comp_heat_cool_fan_presets, # noqa: F811
preset,
temp_low,
temp_high,
) -> None:
"""Test the setting preset mode then set temperature.
Verify preset mode preserved while temperature updated.
"""
await common.async_set_temperature(hass, 18, common.ENTITY, 22, 18)
await common.async_set_preset_mode(hass, preset)
state = hass.states.get(common.ENTITY)
assert state.attributes.get("target_temp_low") == temp_low
assert state.attributes.get("target_temp_high") == temp_high

await common.async_set_hvac_mode(hass, HVACMode.HEAT)
await hass.async_block_till_done()

state = hass.states.get(common.ENTITY)
assert state.attributes.get("preset_mode") == preset
assert state.attributes.get("temperature") == 18
assert state.attributes.get("target_temp_low") is None
assert state.attributes.get("target_temp_high") is None

await common.async_set_hvac_mode(hass, HVACMode.COOL)
await hass.async_block_till_done()

state = hass.states.get(common.ENTITY)
assert state.attributes.get("preset_mode") == preset
assert state.attributes.get("temperature") == 18
assert state.attributes.get("target_temp_low") is None
assert state.attributes.get("target_temp_high") is None

await common.async_set_hvac_mode(hass, HVACMode.FAN_ONLY)
await hass.async_block_till_done()

state = hass.states.get(common.ENTITY)
assert state.attributes.get("preset_mode") == preset
assert state.attributes.get("temperature") == 18
assert state.attributes.get("target_temp_low") is None
assert state.attributes.get("target_temp_high") is None


###################
# HVAC OPERATIONS #
###################
Expand Down

0 comments on commit 2111365

Please sign in to comment.