1
0
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:
Tor André Roland
2026-03-09 19:29:43 +01:00
committed by GitHub
parent c5e0c78cbc
commit 01200ef0a8
3 changed files with 205 additions and 22 deletions

40
homeassistant/components/adax/climate.py Normal file → Executable file
View 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()

View File

@@ -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

View File

@@ -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