Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: toggling fan_hot_tolerance malfunction #265

Merged
merged 1 commit into from
Aug 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion custom_components/dual_smart_thermostat/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ async def _async_startup(*_) -> None:
self.environment.update_floor_temp_from_state(floor_sensor_state)
self.async_write_ha_state()

await self.hvac_device.async_on_startup()
await self.hvac_device.async_on_startup(self.async_write_ha_state)

if self.hass.state == CoreState.running:
await _async_startup()
Expand Down Expand Up @@ -1162,6 +1162,7 @@ async def _async_control_climate(self, time=None, force=False) -> None:

async with self._temp_lock:
await self.hvac_device.async_control_hvac(time, force)

_LOGGER.info(
"updating HVACActionReason: %s", self.hvac_device.HVACActionReason
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
from typing import Callable

from homeassistant.components.climate import HVACMode
from homeassistant.const import STATE_ON, STATE_UNAVAILABLE, STATE_UNKNOWN
Expand Down Expand Up @@ -52,9 +53,16 @@ def __init__(
if self.fan_device is None or self.cooler_device is None:
_LOGGER.error("Fan or cooler device is not found")

self._set_fan_hot_tolerance_on_state()

def _set_fan_hot_tolerance_on_state(self):
if self._features.fan_hot_tolerance_on_entity is not None:
_LOGGER.debug(
"Setting fan_hot_tolerance_on state: %s",
self.hass.states.get(self._features.fan_hot_tolerance_on_entity).state,
)
self._fan_hot_tolerance_on = (
self.hass.states.get(self._features.fan_hot_tolerance_on_entity)
self.hass.states.get(self._features.fan_hot_tolerance_on_entity).state
== STATE_ON
)
else:
Expand All @@ -71,8 +79,8 @@ def hvac_mode(self, hvac_mode: HVACMode): # noqa: F811
self._hvac_mode = hvac_mode
self.set_sub_devices_hvac_mode(hvac_mode)

async def async_on_startup(self):
await super().async_on_startup()
async def async_on_startup(self, async_write_ha_state_cb: Callable = None) -> None:
await super().async_on_startup(async_write_ha_state_cb)

if self._features.fan_hot_tolerance_on_entity is not None:
self.async_on_remove(
Expand All @@ -90,7 +98,7 @@ async def _async_fan_hot_tolerance_on_changed(

new_state = data["new_state"]

_LOGGER.debug("Fan hot tolerance state changed: %s", new_state)
_LOGGER.debug("Fan hot tolerance on changed: %s", new_state)

if new_state is None or new_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN):
self._fan_hot_tolerance_on = True
Expand All @@ -100,7 +108,8 @@ async def _async_fan_hot_tolerance_on_changed(

_LOGGER.debug("fan_hot_tolerance_on is %s", self._fan_hot_tolerance_on)

await self.async_control_hvac(force=True)
await self.async_control_hvac()
self._async_write_ha_state_cb()

async def _async_check_device_initial_state(self) -> None:
"""Prevent the device from keep running if HVACMode.OFF."""
Expand All @@ -109,6 +118,11 @@ async def _async_check_device_initial_state(self) -> None:
async def async_control_hvac(self, time=None, force=False):
_LOGGER.info({self.__class__.__name__})
_LOGGER.debug("hvac_mode: %s", self._hvac_mode)
self._set_fan_hot_tolerance_on_state()
_LOGGER.debug(
"async_control_hvac fan_hot_tolerance_on: %s", self._fan_hot_tolerance_on
)

match self._hvac_mode:
case HVACMode.COOL:
if self._fan_on_with_cooler:
Expand Down Expand Up @@ -141,6 +155,8 @@ async def async_control_hvac(self, time=None, force=False):
_LOGGER.debug(
"fan_hot_tolerance_on: %s", self._fan_hot_tolerance_on
)
_LOGGER.debug("force_override: %s", force_override)

self.fan_device.hvac_mode = HVACMode.FAN_ONLY
await self.fan_device.async_control_hvac(time, force_override)
await self.cooler_device.async_turn_off()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from datetime import timedelta
import logging
from typing import Callable

from homeassistant.components.climate import HVACMode
from homeassistant.components.valve import DOMAIN as VALVE_DOMAIN, ValveEntityFeature
Expand Down Expand Up @@ -237,8 +238,11 @@ async def async_control_hvac(self, time=None, force=False):
self._hvac_action_reason = self.hvac_controller.hvac_action_reason
self.hvac_power.update_hvac_power(self.strategy, self.target_env_attr)

async def async_on_startup(self):
async def async_on_startup(self, async_write_ha_state_cb: Callable = None):

self._async_write_ha_state_cb = async_write_ha_state_cb
entity_state = self.hass.states.get(self.entity_id)

if entity_state and entity_state.state not in (
STATE_UNAVAILABLE,
STATE_UNKNOWN,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
from typing import Callable

from homeassistant.components.climate import HVACAction, HVACMode
from homeassistant.core import Context, HomeAssistant, callback
Expand Down Expand Up @@ -151,9 +152,10 @@ async def async_control_hvac(self, time=None, force: bool = False):

self._hvac_action_reason = device.HVACActionReason

async def async_on_startup(self):
async def async_on_startup(self, async_write_ha_state_cb: Callable = None):
self._async_write_ha_state_cb = async_write_ha_state_cb
for device in self.hvac_devices:
await device.async_on_startup()
await device.async_on_startup(async_write_ha_state_cb)

async def async_turn_on(self):
await self.async_control_hvac(force=True)
Expand Down
6 changes: 4 additions & 2 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1065,9 +1065,11 @@ def setup_boolean(hass: HomeAssistant, entity, state) -> None:
hass.states.async_set(entity, state)


def setup_switch(hass: HomeAssistant, is_on: bool) -> None:
def setup_switch(
hass: HomeAssistant, is_on: bool, entity_id: str = common.ENT_SWITCH
) -> None:
"""Set up the test switch."""
hass.states.async_set(common.ENT_SWITCH, STATE_ON if is_on else STATE_OFF)
hass.states.async_set(entity_id, STATE_ON if is_on else STATE_OFF)
calls = []

@callback
Expand Down
125 changes: 101 additions & 24 deletions tests/test_fan_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -2619,37 +2619,114 @@ async def test_set_target_temp_ac_on_after_fan_tolerance_toggle_off(
hass.states.get(common.ENTITY).attributes["hvac_action"] == HVACAction.COOLING
)

# setup_fan_heat_tolerance_toggle(hass, True)
# await hass.async_block_till_done()
calls = setup_switch(hass, True, cooler_switch)
setup_fan_heat_tolerance_toggle(hass, True)

# assert hass.states.get(cooler_switch).state == STATE_OFF
# assert hass.states.get(fan_switch).state == STATE_ON
# assert hass.states.get(common.ENTITY).attributes["hvac_action"] == HVACAction.FAN
await hass.async_block_till_done()

_LOGGER.debug("after fan_hot_tolerance_toggle on")
_LOGGER.debug("call 1: %s ", calls[0])
_LOGGER.debug("call 2: %s ", calls[1])

# # within hot_tolerance and fan_hot_tolerance
# setup_sensor(hass, 20.5)
# await hass.async_block_till_done()
assert len(calls) == 2
call1 = calls[0]
assert call1.domain == HASS_DOMAIN
assert call1.service == SERVICE_TURN_ON
assert call1.data["entity_id"] == fan_switch

# assert hass.states.get(cooler_switch).state == STATE_OFF
# assert hass.states.get(fan_switch).state == STATE_ON
# assert hass.states.get(common.ENTITY).attributes["hvac_action"] == HVACAction.FAN
call2 = calls[1]
assert call2.domain == HASS_DOMAIN
assert call2.service == SERVICE_TURN_OFF
assert call2.data["entity_id"] == cooler_switch

# # within hot_tolerance and fan_hot_tolerance
# setup_sensor(hass, 20.7)
# await hass.async_block_till_done()
# if toggling in idle state not turningon anything
setup_sensor(hass, 20)
calls = setup_switch(hass, False, cooler_switch)
setup_fan_heat_tolerance_toggle(hass, False)
await hass.async_block_till_done()

# assert hass.states.get(cooler_switch).state == STATE_OFF
# assert hass.states.get(fan_switch).state == STATE_ON
assert len(calls) == 0

setup_fan_heat_tolerance_toggle(hass, True)
await hass.async_block_till_done()

assert len(calls) == 0

# # outside fan_hot_tolerance, within hot_tolerance
# setup_sensor(hass, 20.8)
# await hass.async_block_till_done()

# assert hass.states.get(cooler_switch).state == STATE_ON
# assert hass.states.get(fan_switch).state == STATE_OFF
# assert (
# hass.states.get(common.ENTITY).attributes["hvac_action"] == HVACAction.COOLING
# )
async def test_set_target_temp_ac_on_after_fan_tolerance_toggle_when_idle(
hass: HomeAssistant, setup_comp_1 # noqa: F811
) -> None:
cooler_switch = "input_boolean.test"
fan_switch = "input_boolean.fan"
fan_hot_tolerance_toggle = common.ENT_FAN_HOT_TOLERNACE_TOGGLE

assert await async_setup_component(
hass,
input_boolean.DOMAIN,
{
"input_boolean": {
"test": None,
"fan": None,
"test_fan_hot_tolerance_toggle": None,
}
},
)

assert await async_setup_component(
hass,
input_number.DOMAIN,
{
"input_number": {
"temp": {"name": "test", "initial": 10, "min": 0, "max": 40, "step": 1}
}
},
)

assert await async_setup_component(
hass,
CLIMATE,
{
"climate": {
"platform": DOMAIN,
"name": "test",
"cold_tolerance": 0.2,
"hot_tolerance": 0.2,
"fan_hot_tolerance_toggle": fan_hot_tolerance_toggle,
"ac_mode": True,
"heater": cooler_switch,
"target_sensor": common.ENT_SENSOR,
"fan": fan_switch,
"fan_hot_tolerance": 0.5,
"initial_hvac_mode": HVACMode.OFF,
}
},
)
await hass.async_block_till_done()

await common.async_set_hvac_mode(hass, HVACMode.COOL)
await common.async_set_temperature(hass, 20)
setup_fan_heat_tolerance_toggle(hass, False)
calls = setup_switch(hass, False, cooler_switch)

# below hot_tolerance
setup_sensor(hass, 20)
await hass.async_block_till_done()

assert len(calls) == 0
assert hass.states.get(cooler_switch).state == STATE_OFF
assert hass.states.get(fan_switch).state == STATE_OFF
assert hass.states.get(common.ENTITY).attributes["hvac_action"] == HVACAction.IDLE

# within hot_tolerance and fan_hot_tolerance
# calls = setup_switch(hass, False, cooler_switch)
setup_fan_heat_tolerance_toggle(hass, True)
await hass.async_block_till_done()

assert len(calls) == 0
assert hass.states.get(cooler_switch).state == STATE_OFF
assert hass.states.get(fan_switch).state == STATE_OFF

assert hass.states.get(common.ENTITY).attributes["hvac_action"] == HVACAction.IDLE


async def test_set_target_temp_ac_on_ignore_fan_tolerance(
Expand Down