mirror of
https://github.com/home-assistant/core.git
synced 2026-04-02 08:26:41 +01:00
Optimizations to Adax local device control (#162109)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Joostlek <joostlek@outlook.com>
This commit is contained in:
40
homeassistant/components/adax/climate.py
Normal file → Executable file
40
homeassistant/components/adax/climate.py
Normal file → Executable file
@@ -168,29 +168,57 @@ class LocalAdaxDevice(CoordinatorEntity[AdaxLocalCoordinator], ClimateEntity):
|
||||
if hvac_mode == HVACMode.HEAT:
|
||||
temperature = self._attr_target_temperature or self._attr_min_temp
|
||||
await self._adax_data_handler.set_target_temperature(temperature)
|
||||
self._attr_target_temperature = temperature
|
||||
self._attr_icon = "mdi:radiator"
|
||||
elif hvac_mode == HVACMode.OFF:
|
||||
await self._adax_data_handler.set_target_temperature(0)
|
||||
self._attr_icon = "mdi:radiator-off"
|
||||
else:
|
||||
# Ignore unsupported HVAC modes to avoid desynchronizing entity state
|
||||
# from the physical device.
|
||||
return
|
||||
|
||||
self._attr_hvac_mode = hvac_mode
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Set new target temperature."""
|
||||
if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None:
|
||||
return
|
||||
await self._adax_data_handler.set_target_temperature(temperature)
|
||||
if self._attr_hvac_mode == HVACMode.HEAT:
|
||||
await self._adax_data_handler.set_target_temperature(temperature)
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle updated data from the coordinator."""
|
||||
self._attr_target_temperature = temperature
|
||||
self.async_write_ha_state()
|
||||
|
||||
def _update_hvac_attributes(self) -> None:
|
||||
"""Update hvac mode and temperatures from coordinator data.
|
||||
|
||||
The coordinator reports a target temperature of 0 when the heater is
|
||||
turned off. In that case, only the hvac mode and icon are updated and
|
||||
the previous non-zero target temperature is preserved. When the
|
||||
reported target temperature is non-zero, the stored target temperature
|
||||
is updated to match the coordinator value.
|
||||
"""
|
||||
if data := self.coordinator.data:
|
||||
self._attr_current_temperature = data["current_temperature"]
|
||||
self._attr_available = self._attr_current_temperature is not None
|
||||
if (target_temp := data["target_temperature"]) == 0:
|
||||
self._attr_hvac_mode = HVACMode.OFF
|
||||
self._attr_icon = "mdi:radiator-off"
|
||||
if target_temp == 0:
|
||||
if self._attr_target_temperature is None:
|
||||
self._attr_target_temperature = self._attr_min_temp
|
||||
else:
|
||||
self._attr_hvac_mode = HVACMode.HEAT
|
||||
self._attr_icon = "mdi:radiator"
|
||||
self._attr_target_temperature = target_temp
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle updated data from the coordinator."""
|
||||
self._update_hvac_attributes()
|
||||
super()._handle_coordinator_update()
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""When entity is added to hass."""
|
||||
await super().async_added_to_hass()
|
||||
self._update_hvac_attributes()
|
||||
|
||||
@@ -47,11 +47,6 @@ CLOUD_DEVICE_DATA: dict[str, Any] = [
|
||||
}
|
||||
]
|
||||
|
||||
LOCAL_DEVICE_DATA: dict[str, Any] = {
|
||||
"current_temperature": 15,
|
||||
"target_temperature": 20,
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_cloud_config_entry(request: pytest.FixtureRequest) -> MockConfigEntry:
|
||||
@@ -94,5 +89,9 @@ def mock_adax_local():
|
||||
mock_adax_class = mock_adax.return_value
|
||||
|
||||
mock_adax_class.get_status = AsyncMock()
|
||||
mock_adax_class.get_status.return_value = LOCAL_DEVICE_DATA
|
||||
mock_adax_class.get_status.return_value = {
|
||||
"current_temperature": 15,
|
||||
"target_temperature": 20,
|
||||
}
|
||||
mock_adax_class.set_target_temperature = AsyncMock()
|
||||
yield mock_adax_class
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
"""Test Adax climate entity."""
|
||||
|
||||
from homeassistant.components.adax.const import SCAN_INTERVAL
|
||||
from homeassistant.components.climate import ATTR_CURRENT_TEMPERATURE, HVACMode
|
||||
from homeassistant.const import ATTR_TEMPERATURE, STATE_UNAVAILABLE, Platform
|
||||
from homeassistant.components.climate import (
|
||||
ATTR_CURRENT_TEMPERATURE,
|
||||
ATTR_HVAC_MODE,
|
||||
DOMAIN as CLIMATE_DOMAIN,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
HVACMode,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_TEMPERATURE,
|
||||
STATE_UNAVAILABLE,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import setup_integration
|
||||
from .conftest import CLOUD_DEVICE_DATA, LOCAL_DEVICE_DATA
|
||||
from .conftest import CLOUD_DEVICE_DATA
|
||||
|
||||
from tests.common import AsyncMock, MockConfigEntry, async_fire_time_changed
|
||||
from tests.test_setup import FrozenDateTimeFactory
|
||||
@@ -67,13 +79,8 @@ async def test_climate_local(
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == HVACMode.HEAT
|
||||
assert (
|
||||
state.attributes[ATTR_TEMPERATURE] == (LOCAL_DEVICE_DATA["target_temperature"])
|
||||
)
|
||||
assert (
|
||||
state.attributes[ATTR_CURRENT_TEMPERATURE]
|
||||
== (LOCAL_DEVICE_DATA["current_temperature"])
|
||||
)
|
||||
assert state.attributes[ATTR_TEMPERATURE] == 20
|
||||
assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 15
|
||||
|
||||
mock_adax_local.get_status.side_effect = Exception()
|
||||
freezer.tick(SCAN_INTERVAL)
|
||||
@@ -83,3 +90,152 @@ async def test_climate_local(
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_climate_local_initial_state_from_first_refresh(
|
||||
hass: HomeAssistant,
|
||||
mock_local_config_entry: MockConfigEntry,
|
||||
mock_adax_local: AsyncMock,
|
||||
) -> None:
|
||||
"""Test that local climate state is initialized from first refresh data."""
|
||||
await setup_integration(hass, mock_local_config_entry)
|
||||
|
||||
assert len(hass.states.async_entity_ids(Platform.CLIMATE)) == 1
|
||||
entity_id = hass.states.async_entity_ids(Platform.CLIMATE)[0]
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == HVACMode.HEAT
|
||||
assert state.attributes[ATTR_TEMPERATURE] == 20
|
||||
assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 15
|
||||
|
||||
|
||||
async def test_climate_local_initial_state_off_from_first_refresh(
|
||||
hass: HomeAssistant,
|
||||
mock_local_config_entry: MockConfigEntry,
|
||||
mock_adax_local: AsyncMock,
|
||||
) -> None:
|
||||
"""Test that local climate initializes correctly when first refresh reports off."""
|
||||
mock_adax_local.get_status.return_value["target_temperature"] = 0
|
||||
|
||||
await setup_integration(hass, mock_local_config_entry)
|
||||
|
||||
assert len(hass.states.async_entity_ids(Platform.CLIMATE)) == 1
|
||||
entity_id = hass.states.async_entity_ids(Platform.CLIMATE)[0]
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == HVACMode.OFF
|
||||
assert state.attributes[ATTR_TEMPERATURE] == 5
|
||||
assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 15
|
||||
|
||||
|
||||
async def test_climate_local_set_hvac_mode_updates_state_immediately(
|
||||
hass: HomeAssistant,
|
||||
mock_local_config_entry: MockConfigEntry,
|
||||
mock_adax_local: AsyncMock,
|
||||
) -> None:
|
||||
"""Test local hvac mode service updates both device and state immediately."""
|
||||
await setup_integration(hass, mock_local_config_entry)
|
||||
|
||||
entity_id = hass.states.async_entity_ids(Platform.CLIMATE)[0]
|
||||
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
{
|
||||
ATTR_ENTITY_ID: entity_id,
|
||||
ATTR_HVAC_MODE: HVACMode.OFF,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_adax_local.set_target_temperature.assert_called_once_with(0)
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == HVACMode.OFF
|
||||
|
||||
mock_adax_local.set_target_temperature.reset_mock()
|
||||
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
{
|
||||
ATTR_ENTITY_ID: entity_id,
|
||||
ATTR_HVAC_MODE: HVACMode.HEAT,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_adax_local.set_target_temperature.assert_called_once_with(20)
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == HVACMode.HEAT
|
||||
|
||||
|
||||
async def test_climate_local_set_temperature_when_off_does_not_change_hvac_mode(
|
||||
hass: HomeAssistant,
|
||||
mock_local_config_entry: MockConfigEntry,
|
||||
mock_adax_local: AsyncMock,
|
||||
) -> None:
|
||||
"""Test setting target temperature while off does not send command or turn on."""
|
||||
await setup_integration(hass, mock_local_config_entry)
|
||||
|
||||
entity_id = hass.states.async_entity_ids(Platform.CLIMATE)[0]
|
||||
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
{
|
||||
ATTR_ENTITY_ID: entity_id,
|
||||
ATTR_HVAC_MODE: HVACMode.OFF,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
mock_adax_local.set_target_temperature.reset_mock()
|
||||
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
{
|
||||
ATTR_ENTITY_ID: entity_id,
|
||||
ATTR_TEMPERATURE: 23,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_adax_local.set_target_temperature.assert_not_called()
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == HVACMode.OFF
|
||||
assert state.attributes[ATTR_TEMPERATURE] == 23
|
||||
|
||||
|
||||
async def test_climate_local_set_temperature_when_heat_calls_device(
|
||||
hass: HomeAssistant,
|
||||
mock_local_config_entry: MockConfigEntry,
|
||||
mock_adax_local: AsyncMock,
|
||||
) -> None:
|
||||
"""Test setting target temperature while heating calls local API."""
|
||||
await setup_integration(hass, mock_local_config_entry)
|
||||
|
||||
entity_id = hass.states.async_entity_ids(Platform.CLIMATE)[0]
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == HVACMode.HEAT
|
||||
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
{
|
||||
ATTR_ENTITY_ID: entity_id,
|
||||
ATTR_TEMPERATURE: 24,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_adax_local.set_target_temperature.assert_called_once_with(24)
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == HVACMode.HEAT
|
||||
assert state.attributes[ATTR_TEMPERATURE] == 24
|
||||
|
||||
Reference in New Issue
Block a user