Add zone temperature support to Daikin integration (#152642)

This commit is contained in:
James
2026-02-17 04:44:38 +11:00
committed by GitHub
parent 5bb7699df0
commit 66dc566d3a
6 changed files with 701 additions and 3 deletions

View File

@@ -2,9 +2,12 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import Sequence
import logging import logging
from typing import Any from typing import Any
from pydaikin.daikin_base import Appliance
from homeassistant.components.climate import ( from homeassistant.components.climate import (
ATTR_FAN_MODE, ATTR_FAN_MODE,
ATTR_HVAC_MODE, ATTR_HVAC_MODE,
@@ -21,6 +24,7 @@ from homeassistant.components.climate import (
) )
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import ( from .const import (
@@ -29,12 +33,19 @@ from .const import (
ATTR_STATE_OFF, ATTR_STATE_OFF,
ATTR_STATE_ON, ATTR_STATE_ON,
ATTR_TARGET_TEMPERATURE, ATTR_TARGET_TEMPERATURE,
DOMAIN,
ZONE_NAME_UNCONFIGURED,
) )
from .coordinator import DaikinConfigEntry, DaikinCoordinator from .coordinator import DaikinConfigEntry, DaikinCoordinator
from .entity import DaikinEntity from .entity import DaikinEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
type DaikinZone = Sequence[str | int]
DAIKIN_ZONE_TEMP_HEAT = "lztemp_h"
DAIKIN_ZONE_TEMP_COOL = "lztemp_c"
HA_STATE_TO_DAIKIN = { HA_STATE_TO_DAIKIN = {
HVACMode.FAN_ONLY: "fan", HVACMode.FAN_ONLY: "fan",
@@ -78,6 +89,70 @@ HA_ATTR_TO_DAIKIN = {
} }
DAIKIN_ATTR_ADVANCED = "adv" DAIKIN_ATTR_ADVANCED = "adv"
ZONE_TEMPERATURE_WINDOW = 2
def _zone_error(
translation_key: str, placeholders: dict[str, str] | None = None
) -> HomeAssistantError:
"""Return a Home Assistant error with Daikin translation info."""
return HomeAssistantError(
translation_domain=DOMAIN,
translation_key=translation_key,
translation_placeholders=placeholders,
)
def _zone_is_configured(zone: DaikinZone) -> bool:
"""Return True if the Daikin zone represents a configured zone."""
if not zone:
return False
return zone[0] != ZONE_NAME_UNCONFIGURED
def _zone_temperature_lists(device: Appliance) -> tuple[list[str], list[str]]:
"""Return the decoded zone temperature lists."""
try:
heating = device.represent(DAIKIN_ZONE_TEMP_HEAT)[1]
cooling = device.represent(DAIKIN_ZONE_TEMP_COOL)[1]
except AttributeError:
return ([], [])
return (list(heating or []), list(cooling or []))
def _supports_zone_temperature_control(device: Appliance) -> bool:
"""Return True if the device exposes zone temperature settings."""
zones = device.zones
if not zones:
return False
heating, cooling = _zone_temperature_lists(device)
return bool(
heating
and cooling
and len(heating) >= len(zones)
and len(cooling) >= len(zones)
)
def _system_target_temperature(device: Appliance) -> float | None:
"""Return the system target temperature when available."""
target = device.target_temperature
if target is None:
return None
try:
return float(target)
except TypeError, ValueError:
return None
def _zone_temperature_from_list(values: list[str], zone_id: int) -> float | None:
"""Return the parsed temperature for a zone from a Daikin list."""
if zone_id >= len(values):
return None
try:
return float(values[zone_id])
except TypeError, ValueError:
return None
async def async_setup_entry( async def async_setup_entry(
@@ -86,8 +161,16 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback, async_add_entities: AddConfigEntryEntitiesCallback,
) -> None: ) -> None:
"""Set up Daikin climate based on config_entry.""" """Set up Daikin climate based on config_entry."""
daikin_api = entry.runtime_data coordinator = entry.runtime_data
async_add_entities([DaikinClimate(daikin_api)]) entities: list[ClimateEntity] = [DaikinClimate(coordinator)]
if _supports_zone_temperature_control(coordinator.device):
zones = coordinator.device.zones or []
entities.extend(
DaikinZoneClimate(coordinator, zone_id)
for zone_id, zone in enumerate(zones)
if _zone_is_configured(zone)
)
async_add_entities(entities)
def format_target_temperature(target_temperature: float) -> str: def format_target_temperature(target_temperature: float) -> str:
@@ -284,3 +367,130 @@ class DaikinClimate(DaikinEntity, ClimateEntity):
{HA_ATTR_TO_DAIKIN[ATTR_HVAC_MODE]: HA_STATE_TO_DAIKIN[HVACMode.OFF]} {HA_ATTR_TO_DAIKIN[ATTR_HVAC_MODE]: HA_STATE_TO_DAIKIN[HVACMode.OFF]}
) )
await self.coordinator.async_refresh() await self.coordinator.async_refresh()
class DaikinZoneClimate(DaikinEntity, ClimateEntity):
"""Representation of a Daikin zone temperature controller."""
_attr_temperature_unit = UnitOfTemperature.CELSIUS
_attr_has_entity_name = True
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
_attr_target_temperature_step = 1
def __init__(self, coordinator: DaikinCoordinator, zone_id: int) -> None:
"""Initialize the zone climate entity."""
super().__init__(coordinator)
self._zone_id = zone_id
self._attr_unique_id = f"{self.device.mac}-zone{zone_id}-temperature"
zone_name = self.device.zones[self._zone_id][0]
self._attr_name = f"{zone_name} temperature"
@property
def hvac_modes(self) -> list[HVACMode]:
"""Return the hvac modes (mirrors the main unit)."""
return [self.hvac_mode]
@property
def hvac_mode(self) -> HVACMode:
"""Return the current HVAC mode."""
daikin_mode = self.device.represent(HA_ATTR_TO_DAIKIN[ATTR_HVAC_MODE])[1]
return DAIKIN_TO_HA_STATE.get(daikin_mode, HVACMode.HEAT_COOL)
@property
def hvac_action(self) -> HVACAction | None:
"""Return the current HVAC action."""
return HA_STATE_TO_CURRENT_HVAC.get(self.hvac_mode)
@property
def target_temperature(self) -> float | None:
"""Return the zone target temperature for the active mode."""
heating, cooling = _zone_temperature_lists(self.device)
mode = self.hvac_mode
if mode == HVACMode.HEAT:
return _zone_temperature_from_list(heating, self._zone_id)
if mode == HVACMode.COOL:
return _zone_temperature_from_list(cooling, self._zone_id)
return None
@property
def min_temp(self) -> float:
"""Return the minimum selectable temperature."""
target = _system_target_temperature(self.device)
if target is None:
return super().min_temp
return target - ZONE_TEMPERATURE_WINDOW
@property
def max_temp(self) -> float:
"""Return the maximum selectable temperature."""
target = _system_target_temperature(self.device)
if target is None:
return super().max_temp
return target + ZONE_TEMPERATURE_WINDOW
@property
def available(self) -> bool:
"""Return if the entity is available."""
return (
super().available
and _supports_zone_temperature_control(self.device)
and _system_target_temperature(self.device) is not None
)
@property
def extra_state_attributes(self) -> dict[str, Any]:
"""Return additional metadata."""
return {"zone_id": self._zone_id}
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set the zone temperature."""
if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="zone_temperature_missing",
)
zones = self.device.zones
if not zones or not _supports_zone_temperature_control(self.device):
raise _zone_error("zone_parameters_unavailable")
try:
zone = zones[self._zone_id]
except (IndexError, TypeError) as err:
raise _zone_error(
"zone_missing",
{
"zone_id": str(self._zone_id),
"max_zone": str(len(zones) - 1),
},
) from err
if not _zone_is_configured(zone):
raise _zone_error("zone_inactive", {"zone_id": str(self._zone_id)})
temperature_value = float(temperature)
target = _system_target_temperature(self.device)
if target is None:
raise _zone_error("zone_parameters_unavailable")
mode = self.hvac_mode
if mode == HVACMode.HEAT:
zone_key = DAIKIN_ZONE_TEMP_HEAT
elif mode == HVACMode.COOL:
zone_key = DAIKIN_ZONE_TEMP_COOL
else:
raise _zone_error("zone_hvac_mode_unsupported")
zone_value = str(round(temperature_value))
try:
await self.device.set_zone(self._zone_id, zone_key, zone_value)
except (AttributeError, KeyError, NotImplementedError, TypeError) as err:
raise _zone_error("zone_set_failed") from err
await self.coordinator.async_request_refresh()
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Disallow changing HVAC mode via zone climate."""
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="zone_hvac_read_only",
)

View File

@@ -24,4 +24,6 @@ ATTR_STATE_OFF = "off"
KEY_MAC = "mac" KEY_MAC = "mac"
KEY_IP = "ip" KEY_IP = "ip"
ZONE_NAME_UNCONFIGURED = "-"
TIMEOUT_SEC = 120 TIMEOUT_SEC = 120

View File

@@ -57,5 +57,28 @@
"name": "Power" "name": "Power"
} }
} }
},
"exceptions": {
"zone_hvac_mode_unsupported": {
"message": "Zone temperature can only be changed when the main climate mode is heat or cool."
},
"zone_hvac_read_only": {
"message": "Zone HVAC mode is controlled by the main climate entity."
},
"zone_inactive": {
"message": "Zone {zone_id} is not active. Enable the zone on your Daikin device first."
},
"zone_missing": {
"message": "Zone {zone_id} does not exist. Available zones are 0-{max_zone}."
},
"zone_parameters_unavailable": {
"message": "This device does not expose the required zone temperature parameters."
},
"zone_set_failed": {
"message": "Failed to set zone temperature. The device may not support this operation."
},
"zone_temperature_missing": {
"message": "Provide a temperature value when adjusting a zone."
}
} }
} }

View File

@@ -8,6 +8,7 @@ from homeassistant.components.switch import SwitchEntity
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import ZONE_NAME_UNCONFIGURED
from .coordinator import DaikinConfigEntry, DaikinCoordinator from .coordinator import DaikinConfigEntry, DaikinCoordinator
from .entity import DaikinEntity from .entity import DaikinEntity
@@ -28,7 +29,7 @@ async def async_setup_entry(
switches.extend( switches.extend(
DaikinZoneSwitch(daikin_api, zone_id) DaikinZoneSwitch(daikin_api, zone_id)
for zone_id, zone in enumerate(zones) for zone_id, zone in enumerate(zones)
if zone[0] != "-" if zone[0] != ZONE_NAME_UNCONFIGURED
) )
if daikin_api.device.support_advanced_modes: if daikin_api.device.support_advanced_modes:
# It isn't possible to find out from the API responses if a specific # It isn't possible to find out from the API responses if a specific

View File

@@ -0,0 +1,109 @@
"""Fixtures for Daikin tests."""
from __future__ import annotations
from collections.abc import Callable, Generator
import re
from typing import Any
from unittest.mock import AsyncMock, MagicMock, patch
import urllib.parse
import pytest
type ZoneDefinition = list[str | int]
type ZoneDevice = MagicMock
def _decode_zone_values(value: str) -> list[str]:
"""Decode a semicolon separated list into zone values."""
return re.findall(r"[^;]+", urllib.parse.unquote(value))
def configure_zone_device(
zone_device: ZoneDevice,
*,
zones: list[ZoneDefinition],
target_temperature: float | None = 22,
mode: str = "hot",
heating_values: str | None = None,
cooling_values: str | None = None,
) -> None:
"""Configure a mocked zone-capable Daikin device for a test."""
zone_device.target_temperature = target_temperature
zone_device.zones = zones
zone_device._mode = mode
encoded_zone_temperatures = ";".join(str(zone[2]) for zone in zones)
zone_device.values = {
"name": "Daikin Test",
"model": "TESTMODEL",
"ver": "1_0_0",
"zone_name": ";".join(str(zone[0]) for zone in zones),
"zone_onoff": ";".join(str(zone[1]) for zone in zones),
"lztemp_h": (
encoded_zone_temperatures if heating_values is None else heating_values
),
"lztemp_c": (
encoded_zone_temperatures if cooling_values is None else cooling_values
),
}
@pytest.fixture
def zone_device() -> Generator[ZoneDevice]:
"""Return a mocked zone-capable Daikin device and patch its factory."""
device = MagicMock(name="DaikinZoneDevice")
device.mac = "001122334455"
device.fan_rate = []
device.swing_modes = []
device.support_away_mode = False
device.support_advanced_modes = False
device.support_fan_rate = False
device.support_swing_mode = False
device.support_outside_temperature = False
device.support_energy_consumption = False
device.support_humidity = False
device.support_compressor_frequency = False
device.compressor_frequency = 0
device.inside_temperature = 21.0
device.outside_temperature = 13.0
device.humidity = 40
device.current_total_power_consumption = 0.0
device.last_hour_cool_energy_consumption = 0.0
device.last_hour_heat_energy_consumption = 0.0
device.today_energy_consumption = 0.0
device.today_total_energy_consumption = 0.0
configure_zone_device(device, zones=[["Living", "1", 22]])
def _represent(key: str) -> tuple[None, list[str] | str]:
dynamic_values: dict[str, Callable[[], list[str] | str]] = {
"lztemp_h": lambda: _decode_zone_values(device.values["lztemp_h"]),
"lztemp_c": lambda: _decode_zone_values(device.values["lztemp_c"]),
"mode": lambda: device._mode,
"f_rate": lambda: "auto",
"f_dir": lambda: "3d",
"en_hol": lambda: "off",
"adv": lambda: "",
"htemp": lambda: str(device.inside_temperature),
"otemp": lambda: str(device.outside_temperature),
}
return (None, dynamic_values.get(key, lambda: "")())
async def _set(values: dict[str, Any]) -> None:
if mode := values.get("mode"):
device._mode = mode
device.represent = MagicMock(side_effect=_represent)
device.update_status = AsyncMock()
device.set = AsyncMock(side_effect=_set)
device.set_zone = AsyncMock()
device.set_holiday = AsyncMock()
device.set_advanced_mode = AsyncMock()
device.set_streamer = AsyncMock()
with patch(
"homeassistant.components.daikin.DaikinFactory",
new=AsyncMock(return_value=device),
):
yield device

View File

@@ -0,0 +1,353 @@
"""Tests for Daikin zone climate entities."""
from __future__ import annotations
from unittest.mock import AsyncMock
import pytest
from homeassistant.components.climate import (
ATTR_HVAC_ACTION,
ATTR_HVAC_MODE,
ATTR_HVAC_MODES,
ATTR_MAX_TEMP,
ATTR_MIN_TEMP,
DOMAIN as CLIMATE_DOMAIN,
SERVICE_SET_HVAC_MODE,
SERVICE_SET_TEMPERATURE,
HVACAction,
HVACMode,
)
from homeassistant.components.daikin.const import DOMAIN, KEY_MAC
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_TEMPERATURE,
CONF_HOST,
STATE_UNAVAILABLE,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_component import async_update_entity
from .conftest import ZoneDevice, configure_zone_device
from tests.common import MockConfigEntry
HOST = "127.0.0.1"
async def _async_setup_daikin(
hass: HomeAssistant, zone_device: ZoneDevice
) -> MockConfigEntry:
"""Set up a Daikin config entry with a mocked library device."""
config_entry = MockConfigEntry(
domain=DOMAIN,
unique_id=zone_device.mac,
data={CONF_HOST: HOST, KEY_MAC: zone_device.mac},
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
return config_entry
def _zone_entity_id(
entity_registry: er.EntityRegistry, zone_device: ZoneDevice, zone_id: int
) -> str | None:
"""Return the entity id for a zone climate unique id."""
return entity_registry.async_get_entity_id(
CLIMATE_DOMAIN,
DOMAIN,
f"{zone_device.mac}-zone{zone_id}-temperature",
)
async def _async_set_zone_temperature(
hass: HomeAssistant, entity_id: str, temperature: float
) -> None:
"""Call `climate.set_temperature` for a zone climate."""
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{
ATTR_ENTITY_ID: entity_id,
ATTR_TEMPERATURE: temperature,
},
blocking=True,
)
async def test_setup_entry_adds_zone_climates(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
zone_device: ZoneDevice,
) -> None:
"""Configured zones create zone climate entities."""
configure_zone_device(
zone_device, zones=[["-", "0", 0], ["Living", "1", 22], ["Office", "1", 21]]
)
await _async_setup_daikin(hass, zone_device)
assert _zone_entity_id(entity_registry, zone_device, 0) is None
assert _zone_entity_id(entity_registry, zone_device, 1) is not None
assert _zone_entity_id(entity_registry, zone_device, 2) is not None
async def test_setup_entry_skips_zone_climates_without_support(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
zone_device: ZoneDevice,
) -> None:
"""Missing zone temperature lists skip zone climate entities."""
configure_zone_device(zone_device, zones=[["Living", "1", 22]])
zone_device.values["lztemp_h"] = ""
zone_device.values["lztemp_c"] = ""
await _async_setup_daikin(hass, zone_device)
assert _zone_entity_id(entity_registry, zone_device, 0) is None
@pytest.mark.parametrize(
("mode", "expected_zone_key"),
[("hot", "lztemp_h"), ("cool", "lztemp_c")],
)
async def test_zone_climate_sets_temperature_for_active_mode(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
zone_device: ZoneDevice,
mode: str,
expected_zone_key: str,
) -> None:
"""Setting temperature updates the active mode zone value."""
configure_zone_device(
zone_device,
zones=[["Living", "1", 22], ["Office", "1", 21]],
mode=mode,
)
await _async_setup_daikin(hass, zone_device)
entity_id = _zone_entity_id(entity_registry, zone_device, 0)
assert entity_id is not None
await _async_set_zone_temperature(hass, entity_id, 23)
zone_device.set_zone.assert_awaited_once_with(0, expected_zone_key, "23")
async def test_zone_climate_rejects_out_of_range_temperature(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
zone_device: ZoneDevice,
) -> None:
"""Service validation rejects values outside the allowed range."""
configure_zone_device(
zone_device,
zones=[["Living", "1", 22]],
target_temperature=22,
)
await _async_setup_daikin(hass, zone_device)
entity_id = _zone_entity_id(entity_registry, zone_device, 0)
assert entity_id is not None
with pytest.raises(ServiceValidationError) as err:
await _async_set_zone_temperature(hass, entity_id, 30)
assert err.value.translation_key == "temp_out_of_range"
async def test_zone_climate_unavailable_without_target_temperature(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
zone_device: ZoneDevice,
) -> None:
"""Zones are unavailable if system target temperature is missing."""
configure_zone_device(
zone_device,
zones=[["Living", "1", 22]],
target_temperature=None,
)
await _async_setup_daikin(hass, zone_device)
entity_id = _zone_entity_id(entity_registry, zone_device, 0)
assert entity_id is not None
state = hass.states.get(entity_id)
assert state is not None
assert state.state == STATE_UNAVAILABLE
async def test_zone_climate_zone_inactive_after_setup(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
zone_device: ZoneDevice,
) -> None:
"""Inactive zones raise a translated error during service calls."""
configure_zone_device(zone_device, zones=[["Living", "1", 22]])
await _async_setup_daikin(hass, zone_device)
entity_id = _zone_entity_id(entity_registry, zone_device, 0)
assert entity_id is not None
zone_device.zones[0][0] = "-"
with pytest.raises(HomeAssistantError) as err:
await _async_set_zone_temperature(hass, entity_id, 21)
assert err.value.translation_key == "zone_inactive"
async def test_zone_climate_zone_missing_after_setup(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
zone_device: ZoneDevice,
) -> None:
"""Missing zones raise a translated error during service calls."""
configure_zone_device(
zone_device,
zones=[["Living", "1", 22], ["Office", "1", 22]],
)
await _async_setup_daikin(hass, zone_device)
entity_id = _zone_entity_id(entity_registry, zone_device, 1)
assert entity_id is not None
zone_device.zones = [["Living", "1", 22]]
with pytest.raises(HomeAssistantError) as err:
await _async_set_zone_temperature(hass, entity_id, 21)
assert err.value.translation_key == "zone_missing"
async def test_zone_climate_parameters_unavailable(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
zone_device: ZoneDevice,
) -> None:
"""Missing zone parameter lists make the zone entity unavailable."""
configure_zone_device(zone_device, zones=[["Living", "1", 22]])
await _async_setup_daikin(hass, zone_device)
entity_id = _zone_entity_id(entity_registry, zone_device, 0)
assert entity_id is not None
zone_device.values["lztemp_h"] = ""
zone_device.values["lztemp_c"] = ""
await async_update_entity(hass, entity_id)
state = hass.states.get(entity_id)
assert state is not None
assert state.state == STATE_UNAVAILABLE
async def test_zone_climate_hvac_modes_read_only(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
zone_device: ZoneDevice,
) -> None:
"""Changing HVAC mode through a zone climate is blocked."""
configure_zone_device(zone_device, zones=[["Living", "1", 22]])
await _async_setup_daikin(hass, zone_device)
entity_id = _zone_entity_id(entity_registry, zone_device, 0)
assert entity_id is not None
with pytest.raises(HomeAssistantError) as err:
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_HVAC_MODE,
{
ATTR_ENTITY_ID: entity_id,
ATTR_HVAC_MODE: HVACMode.HEAT,
},
blocking=True,
)
assert err.value.translation_key == "zone_hvac_read_only"
async def test_zone_climate_set_temperature_requires_heat_or_cool(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
zone_device: ZoneDevice,
) -> None:
"""Setting temperature in unsupported modes raises a translated error."""
configure_zone_device(
zone_device,
zones=[["Living", "1", 22]],
mode="auto",
)
await _async_setup_daikin(hass, zone_device)
entity_id = _zone_entity_id(entity_registry, zone_device, 0)
assert entity_id is not None
with pytest.raises(HomeAssistantError) as err:
await _async_set_zone_temperature(hass, entity_id, 21)
assert err.value.translation_key == "zone_hvac_mode_unsupported"
async def test_zone_climate_properties(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
zone_device: ZoneDevice,
) -> None:
"""Zone climate exposes expected state attributes."""
configure_zone_device(
zone_device,
zones=[["Living", "1", 22]],
target_temperature=24,
mode="cool",
heating_values="20",
cooling_values="18",
)
await _async_setup_daikin(hass, zone_device)
entity_id = _zone_entity_id(entity_registry, zone_device, 0)
assert entity_id is not None
state = hass.states.get(entity_id)
assert state is not None
assert state.state == HVACMode.COOL
assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.COOLING
assert state.attributes[ATTR_TEMPERATURE] == 18.0
assert state.attributes[ATTR_MIN_TEMP] == 22.0
assert state.attributes[ATTR_MAX_TEMP] == 26.0
assert state.attributes[ATTR_HVAC_MODES] == [HVACMode.COOL]
assert state.attributes["zone_id"] == 0
async def test_zone_climate_target_temperature_inactive_mode(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
zone_device: ZoneDevice,
) -> None:
"""In non-heating/cooling modes, zone target temperature is None."""
configure_zone_device(
zone_device,
zones=[["Living", "1", 22]],
mode="auto",
heating_values="bad",
cooling_values="19",
)
await _async_setup_daikin(hass, zone_device)
entity_id = _zone_entity_id(entity_registry, zone_device, 0)
assert entity_id is not None
state = hass.states.get(entity_id)
assert state is not None
assert state.state == HVACMode.HEAT_COOL
assert state.attributes[ATTR_TEMPERATURE] is None
async def test_zone_climate_set_zone_failed(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
zone_device: ZoneDevice,
) -> None:
"""Service call surfaces backend zone update errors."""
configure_zone_device(zone_device, zones=[["Living", "1", 22]])
await _async_setup_daikin(hass, zone_device)
entity_id = _zone_entity_id(entity_registry, zone_device, 0)
assert entity_id is not None
zone_device.set_zone = AsyncMock(side_effect=NotImplementedError)
with pytest.raises(HomeAssistantError) as err:
await _async_set_zone_temperature(hass, entity_id, 21)
assert err.value.translation_key == "zone_set_failed"