Implement Airzone Cloud Aidoo climate support (#101011)

* Implement Airzone Cloud Aidoo climate support

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>

* airzone_cloud: climate: add entity naming

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>

---------

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
This commit is contained in:
Álvaro Fernández Rojas 2023-09-27 20:30:32 +02:00 committed by GitHub
parent 473d20712c
commit 6a52283ce0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 156 additions and 3 deletions

View File

@ -12,6 +12,7 @@ from aioairzone_cloud.const import (
API_UNITS, API_UNITS,
API_VALUE, API_VALUE,
AZD_ACTION, AZD_ACTION,
AZD_AIDOOS,
AZD_HUMIDITY, AZD_HUMIDITY,
AZD_MASTER, AZD_MASTER,
AZD_MODE, AZD_MODE,
@ -39,7 +40,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN from .const import DOMAIN
from .coordinator import AirzoneUpdateCoordinator from .coordinator import AirzoneUpdateCoordinator
from .entity import AirzoneEntity, AirzoneZoneEntity from .entity import AirzoneAidooEntity, AirzoneEntity, AirzoneZoneEntity
HVAC_ACTION_LIB_TO_HASS: Final[dict[OperationAction, HVACAction]] = { HVAC_ACTION_LIB_TO_HASS: Final[dict[OperationAction, HVACAction]] = {
OperationAction.COOLING: HVACAction.COOLING, OperationAction.COOLING: HVACAction.COOLING,
@ -82,6 +83,16 @@ async def async_setup_entry(
entities: list[AirzoneClimate] = [] entities: list[AirzoneClimate] = []
# Aidoos
for aidoo_id, aidoo_data in coordinator.data.get(AZD_AIDOOS, {}).items():
entities.append(
AirzoneAidooClimate(
coordinator,
aidoo_id,
aidoo_data,
)
)
# Zones # Zones
for zone_id, zone_data in coordinator.data.get(AZD_ZONES, {}).items(): for zone_id, zone_data in coordinator.data.get(AZD_ZONES, {}).items():
entities.append( entities.append(
@ -98,6 +109,7 @@ async def async_setup_entry(
class AirzoneClimate(AirzoneEntity, ClimateEntity): class AirzoneClimate(AirzoneEntity, ClimateEntity):
"""Define an Airzone Cloud climate.""" """Define an Airzone Cloud climate."""
_attr_has_entity_name = True
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
_attr_temperature_unit = UnitOfTemperature.CELSIUS _attr_temperature_unit = UnitOfTemperature.CELSIUS
@ -156,11 +168,49 @@ class AirzoneClimate(AirzoneEntity, ClimateEntity):
self._attr_target_temperature = self.get_airzone_value(AZD_TEMP_SET) self._attr_target_temperature = self.get_airzone_value(AZD_TEMP_SET)
class AirzoneAidooClimate(AirzoneAidooEntity, AirzoneClimate):
"""Define an Airzone Cloud Aidoo climate."""
def __init__(
self,
coordinator: AirzoneUpdateCoordinator,
aidoo_id: str,
aidoo_data: dict,
) -> None:
"""Initialize Airzone Cloud Aidoo climate."""
super().__init__(coordinator, aidoo_id, aidoo_data)
self._attr_unique_id = aidoo_id
self._attr_target_temperature_step = self.get_airzone_value(AZD_TEMP_STEP)
self._attr_hvac_modes = [
HVAC_MODE_LIB_TO_HASS[mode] for mode in self.get_airzone_value(AZD_MODES)
]
if HVACMode.OFF not in self._attr_hvac_modes:
self._attr_hvac_modes += [HVACMode.OFF]
self._async_update_attrs()
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set hvac mode."""
params: dict[str, Any] = {}
if hvac_mode == HVACMode.OFF:
params[API_POWER] = {
API_VALUE: False,
}
else:
mode = HVAC_MODE_HASS_TO_LIB[hvac_mode]
params[API_MODE] = {
API_VALUE: mode.value,
}
params[API_POWER] = {
API_VALUE: True,
}
await self._async_update_params(params)
class AirzoneZoneClimate(AirzoneZoneEntity, AirzoneClimate): class AirzoneZoneClimate(AirzoneZoneEntity, AirzoneClimate):
"""Define an Airzone Cloud Zone climate.""" """Define an Airzone Cloud Zone climate."""
_attr_has_entity_name = True
def __init__( def __init__(
self, self,
coordinator: AirzoneUpdateCoordinator, coordinator: AirzoneUpdateCoordinator,

View File

@ -74,6 +74,20 @@ class AirzoneAidooEntity(AirzoneEntity):
value = aidoo.get(key) value = aidoo.get(key)
return value return value
async def _async_update_params(self, params: dict[str, Any]) -> None:
"""Send Aidoo parameters to Cloud API."""
_LOGGER.debug("aidoo=%s: update_params=%s", self.name, params)
try:
await self.coordinator.airzone.api_set_aidoo_id_params(
self.aidoo_id, params
)
except AirzoneCloudError as error:
raise HomeAssistantError(
f"Failed to set {self.name} params: {error}"
) from error
self.coordinator.async_set_updated_data(self.coordinator.airzone.data())
class AirzoneSystemEntity(AirzoneEntity): class AirzoneSystemEntity(AirzoneEntity):
"""Define an Airzone Cloud System entity.""" """Define an Airzone Cloud System entity."""

View File

@ -37,6 +37,25 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None:
await async_init_integration(hass) await async_init_integration(hass)
# Aidoos
state = hass.states.get("climate.bron")
assert state.state == HVACMode.OFF
assert state.attributes.get(ATTR_CURRENT_HUMIDITY) is None
assert state.attributes.get(ATTR_CURRENT_TEMPERATURE) == 21.0
assert state.attributes.get(ATTR_HVAC_ACTION) == HVACAction.OFF
assert state.attributes.get(ATTR_HVAC_MODES) == [
HVACMode.HEAT_COOL,
HVACMode.COOL,
HVACMode.HEAT,
HVACMode.FAN_ONLY,
HVACMode.DRY,
HVACMode.OFF,
]
assert state.attributes.get(ATTR_MAX_TEMP) == 30
assert state.attributes.get(ATTR_MIN_TEMP) == 15
assert state.attributes.get(ATTR_TARGET_TEMP_STEP) == API_TEMPERATURE_STEP
assert state.attributes.get(ATTR_TEMPERATURE) == 22.0
# Zones # Zones
state = hass.states.get("climate.dormitorio") state = hass.states.get("climate.dormitorio")
assert state.state == HVACMode.OFF assert state.state == HVACMode.OFF
@ -78,6 +97,23 @@ async def test_airzone_climate_turn_on_off(hass: HomeAssistant) -> None:
await async_init_integration(hass) await async_init_integration(hass)
# Aidoos
with patch(
"homeassistant.components.airzone_cloud.AirzoneCloudApi.api_patch_device",
return_value=None,
):
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_TURN_ON,
{
ATTR_ENTITY_ID: "climate.bron",
},
blocking=True,
)
state = hass.states.get("climate.bron")
assert state.state == HVACMode.HEAT
# Zones # Zones
with patch( with patch(
"homeassistant.components.airzone_cloud.AirzoneCloudApi.api_patch_device", "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_patch_device",
@ -117,6 +153,41 @@ async def test_airzone_climate_set_hvac_mode(hass: HomeAssistant) -> None:
await async_init_integration(hass) await async_init_integration(hass)
# Aidoos
with patch(
"homeassistant.components.airzone_cloud.AirzoneCloudApi.api_patch_device",
return_value=None,
):
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_HVAC_MODE,
{
ATTR_ENTITY_ID: "climate.bron",
ATTR_HVAC_MODE: HVACMode.HEAT_COOL,
},
blocking=True,
)
state = hass.states.get("climate.bron")
assert state.state == HVACMode.HEAT_COOL
with patch(
"homeassistant.components.airzone_cloud.AirzoneCloudApi.api_patch_device",
return_value=None,
):
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_HVAC_MODE,
{
ATTR_ENTITY_ID: "climate.bron",
ATTR_HVAC_MODE: HVACMode.OFF,
},
blocking=True,
)
state = hass.states.get("climate.bron")
assert state.state == HVACMode.OFF
# Zones # Zones
with patch( with patch(
"homeassistant.components.airzone_cloud.AirzoneCloudApi.api_patch_device", "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_patch_device",
@ -205,6 +276,24 @@ async def test_airzone_climate_set_temp_error(hass: HomeAssistant) -> None:
await async_init_integration(hass) await async_init_integration(hass)
# Aidoos
with patch(
"homeassistant.components.airzone_cloud.AirzoneCloudApi.api_patch_device",
side_effect=AirzoneCloudError,
), pytest.raises(HomeAssistantError):
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{
ATTR_ENTITY_ID: "climate.bron",
ATTR_TEMPERATURE: 20.5,
},
blocking=True,
)
state = hass.states.get("climate.bron")
assert state.attributes.get(ATTR_TEMPERATURE) == 22.0
# Zones # Zones
with patch( with patch(
"homeassistant.components.airzone_cloud.AirzoneCloudApi.api_patch_device", "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_patch_device",