Add coordinator to Daikin (#124394)

* Add coordinator to Daikin

* Add coordinator to Daikin

* Fix

* Add seconds
pull/125313/head^2
Joost Lekkerkerker 2024-09-10 15:30:30 +02:00 committed by GitHub
parent 97c55ae6f1
commit 130b6559a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 162 additions and 197 deletions

View File

@ -3,9 +3,7 @@
from __future__ import annotations
import asyncio
from datetime import timedelta
import logging
from typing import Any
from aiohttp import ClientConnectionError
from pydaikin.daikin_base import Appliance
@ -23,15 +21,13 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
from homeassistant.util import Throttle
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from .const import DOMAIN, KEY_MAC, TIMEOUT
from .coordinator import DaikinCoordinator
_LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 0
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
PLATFORMS = [Platform.CLIMATE, Platform.SENSOR, Platform.SWITCH]
@ -43,19 +39,32 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
if entry.unique_id is None or ".local" in entry.unique_id:
hass.config_entries.async_update_entry(entry, unique_id=conf[KEY_MAC])
daikin_api = await daikin_api_setup(
hass,
conf[CONF_HOST],
conf.get(CONF_API_KEY),
conf.get(CONF_UUID),
conf.get(CONF_PASSWORD),
)
if not daikin_api:
return False
session = async_get_clientsession(hass)
host = conf[CONF_HOST]
try:
async with asyncio.timeout(TIMEOUT):
device: Appliance = await DaikinFactory(
host,
session,
key=entry.data.get(CONF_API_KEY),
uuid=entry.data.get(CONF_UUID),
password=entry.data.get(CONF_PASSWORD),
)
_LOGGER.debug("Connection to %s successful", host)
except TimeoutError as err:
_LOGGER.debug("Connection to %s timed out in 60 seconds", host)
raise ConfigEntryNotReady from err
except ClientConnectionError as err:
_LOGGER.debug("ClientConnectionError to %s", host)
raise ConfigEntryNotReady from err
await async_migrate_unique_id(hass, entry, daikin_api)
coordinator = DaikinCoordinator(hass, device)
hass.data.setdefault(DOMAIN, {}).update({entry.entry_id: daikin_api})
await coordinator.async_config_entry_first_refresh()
await async_migrate_unique_id(hass, entry, device)
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
@ -70,83 +79,16 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return unload_ok
async def daikin_api_setup(
hass: HomeAssistant,
host: str,
key: str | None,
uuid: str | None,
password: str | None,
) -> DaikinApi | None:
"""Create a Daikin instance only once."""
session = async_get_clientsession(hass)
try:
async with asyncio.timeout(TIMEOUT):
device: Appliance = await DaikinFactory(
host, session, key=key, uuid=uuid, password=password
)
_LOGGER.debug("Connection to %s successful", host)
except TimeoutError as err:
_LOGGER.debug("Connection to %s timed out", host)
raise ConfigEntryNotReady from err
except ClientConnectionError as err:
_LOGGER.debug("ClientConnectionError to %s", host)
raise ConfigEntryNotReady from err
except Exception: # noqa: BLE001
_LOGGER.error("Unexpected error creating device %s", host)
return None
return DaikinApi(device)
class DaikinApi:
"""Keep the Daikin instance in one place and centralize the update."""
def __init__(self, device: Appliance) -> None:
"""Initialize the Daikin Handle."""
self.device = device
self.name = device.values.get("name", "Daikin AC")
self.ip_address = device.device_ip
self._available = True
@Throttle(MIN_TIME_BETWEEN_UPDATES)
async def async_update(self, **kwargs: Any) -> None:
"""Pull the latest data from Daikin."""
try:
await self.device.update_status()
self._available = True
except ClientConnectionError:
_LOGGER.warning("Connection failed for %s", self.ip_address)
self._available = False
@property
def available(self) -> bool:
"""Return True if entity is available."""
return self._available
@property
def device_info(self) -> DeviceInfo:
"""Return a device description for device registry."""
info = self.device.values
return DeviceInfo(
connections={(CONNECTION_NETWORK_MAC, self.device.mac)},
manufacturer="Daikin",
model=info.get("model"),
name=info.get("name"),
sw_version=info.get("ver", "").replace("_", "."),
)
async def async_migrate_unique_id(
hass: HomeAssistant, config_entry: ConfigEntry, api: DaikinApi
hass: HomeAssistant, config_entry: ConfigEntry, device: Appliance
) -> None:
"""Migrate old entry."""
dev_reg = dr.async_get(hass)
ent_reg = er.async_get(hass)
old_unique_id = config_entry.unique_id
new_unique_id = api.device.mac
new_unique_id = device.mac
new_mac = dr.format_mac(new_unique_id)
new_name = api.name
new_name = device.values.get("name", "Daikin AC")
@callback
def _update_unique_id(entity_entry: er.RegistryEntry) -> dict[str, str] | None:

View File

@ -34,7 +34,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import DOMAIN as DAIKIN_DOMAIN, DaikinApi
from . import DOMAIN as DAIKIN_DOMAIN
from .const import (
ATTR_INSIDE_TEMPERATURE,
ATTR_OUTSIDE_TEMPERATURE,
@ -42,6 +42,8 @@ from .const import (
ATTR_STATE_ON,
ATTR_TARGET_TEMPERATURE,
)
from .coordinator import DaikinCoordinator
from .entity import DaikinEntity
_LOGGER = logging.getLogger(__name__)
@ -111,7 +113,7 @@ async def async_setup_entry(
) -> None:
"""Set up Daikin climate based on config_entry."""
daikin_api = hass.data[DAIKIN_DOMAIN].get(entry.entry_id)
async_add_entities([DaikinClimate(daikin_api)], update_before_add=True)
async_add_entities([DaikinClimate(daikin_api)])
def format_target_temperature(target_temperature: float) -> str:
@ -119,11 +121,10 @@ def format_target_temperature(target_temperature: float) -> str:
return str(round(float(target_temperature) * 2, 0) / 2).rstrip("0").rstrip(".")
class DaikinClimate(ClimateEntity):
class DaikinClimate(DaikinEntity, ClimateEntity):
"""Representation of a Daikin HVAC."""
_attr_name = None
_attr_has_entity_name = True
_attr_temperature_unit = UnitOfTemperature.CELSIUS
_attr_hvac_modes = list(HA_STATE_TO_DAIKIN)
_attr_target_temperature_step = 1
@ -131,13 +132,11 @@ class DaikinClimate(ClimateEntity):
_attr_swing_modes: list[str]
_enable_turn_on_off_backwards_compatibility = False
def __init__(self, api: DaikinApi) -> None:
def __init__(self, coordinator: DaikinCoordinator) -> None:
"""Initialize the climate device."""
self._api = api
self._attr_fan_modes = api.device.fan_rate
self._attr_swing_modes = api.device.swing_modes
self._attr_device_info = api.device_info
super().__init__(coordinator)
self._attr_fan_modes = self.device.fan_rate
self._attr_swing_modes = self.device.swing_modes
self._list: dict[str, list[Any]] = {
ATTR_HVAC_MODE: self._attr_hvac_modes,
ATTR_FAN_MODE: self._attr_fan_modes,
@ -150,13 +149,13 @@ class DaikinClimate(ClimateEntity):
| ClimateEntityFeature.TARGET_TEMPERATURE
)
if api.device.support_away_mode or api.device.support_advanced_modes:
if self.device.support_away_mode or self.device.support_advanced_modes:
self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE
if api.device.support_fan_rate:
if self.device.support_fan_rate:
self._attr_supported_features |= ClimateEntityFeature.FAN_MODE
if api.device.support_swing_mode:
if self.device.support_swing_mode:
self._attr_supported_features |= ClimateEntityFeature.SWING_MODE
async def _set(self, settings: dict[str, Any]) -> None:
@ -185,22 +184,22 @@ class DaikinClimate(ClimateEntity):
_LOGGER.error("Invalid temperature %s", value)
if values:
await self._api.device.set(values)
await self.device.set(values)
@property
def unique_id(self) -> str:
"""Return a unique ID."""
return self._api.device.mac
return self.device.mac
@property
def current_temperature(self) -> float | None:
"""Return the current temperature."""
return self._api.device.inside_temperature
return self.device.inside_temperature
@property
def target_temperature(self) -> float | None:
"""Return the temperature we try to reach."""
return self._api.device.target_temperature
return self.device.target_temperature
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
@ -212,8 +211,8 @@ class DaikinClimate(ClimateEntity):
ret = HA_STATE_TO_CURRENT_HVAC.get(self.hvac_mode)
if (
ret in (HVACAction.COOLING, HVACAction.HEATING)
and self._api.device.support_compressor_frequency
and self._api.device.compressor_frequency == 0
and self.device.support_compressor_frequency
and self.device.compressor_frequency == 0
):
return HVACAction.IDLE
return ret
@ -221,7 +220,7 @@ class DaikinClimate(ClimateEntity):
@property
def hvac_mode(self) -> HVACMode:
"""Return current operation ie. heat, cool, idle."""
daikin_mode = self._api.device.represent(HA_ATTR_TO_DAIKIN[ATTR_HVAC_MODE])[1]
daikin_mode = self.device.represent(HA_ATTR_TO_DAIKIN[ATTR_HVAC_MODE])[1]
return DAIKIN_TO_HA_STATE.get(daikin_mode, HVACMode.HEAT_COOL)
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
@ -231,7 +230,7 @@ class DaikinClimate(ClimateEntity):
@property
def fan_mode(self) -> str:
"""Return the fan setting."""
return self._api.device.represent(HA_ATTR_TO_DAIKIN[ATTR_FAN_MODE])[1].title()
return self.device.represent(HA_ATTR_TO_DAIKIN[ATTR_FAN_MODE])[1].title()
async def async_set_fan_mode(self, fan_mode: str) -> None:
"""Set fan mode."""
@ -240,7 +239,7 @@ class DaikinClimate(ClimateEntity):
@property
def swing_mode(self) -> str:
"""Return the fan setting."""
return self._api.device.represent(HA_ATTR_TO_DAIKIN[ATTR_SWING_MODE])[1].title()
return self.device.represent(HA_ATTR_TO_DAIKIN[ATTR_SWING_MODE])[1].title()
async def async_set_swing_mode(self, swing_mode: str) -> None:
"""Set new target temperature."""
@ -250,18 +249,18 @@ class DaikinClimate(ClimateEntity):
def preset_mode(self) -> str:
"""Return the preset_mode."""
if (
self._api.device.represent(HA_ATTR_TO_DAIKIN[ATTR_PRESET_MODE])[1]
self.device.represent(HA_ATTR_TO_DAIKIN[ATTR_PRESET_MODE])[1]
== HA_PRESET_TO_DAIKIN[PRESET_AWAY]
):
return PRESET_AWAY
if (
HA_PRESET_TO_DAIKIN[PRESET_BOOST]
in self._api.device.represent(DAIKIN_ATTR_ADVANCED)[1]
in self.device.represent(DAIKIN_ATTR_ADVANCED)[1]
):
return PRESET_BOOST
if (
HA_PRESET_TO_DAIKIN[PRESET_ECO]
in self._api.device.represent(DAIKIN_ATTR_ADVANCED)[1]
in self.device.represent(DAIKIN_ATTR_ADVANCED)[1]
):
return PRESET_ECO
return PRESET_NONE
@ -269,23 +268,23 @@ class DaikinClimate(ClimateEntity):
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set preset mode."""
if preset_mode == PRESET_AWAY:
await self._api.device.set_holiday(ATTR_STATE_ON)
await self.device.set_holiday(ATTR_STATE_ON)
elif preset_mode == PRESET_BOOST:
await self._api.device.set_advanced_mode(
await self.device.set_advanced_mode(
HA_PRESET_TO_DAIKIN[PRESET_BOOST], ATTR_STATE_ON
)
elif preset_mode == PRESET_ECO:
await self._api.device.set_advanced_mode(
await self.device.set_advanced_mode(
HA_PRESET_TO_DAIKIN[PRESET_ECO], ATTR_STATE_ON
)
elif self.preset_mode == PRESET_AWAY:
await self._api.device.set_holiday(ATTR_STATE_OFF)
await self.device.set_holiday(ATTR_STATE_OFF)
elif self.preset_mode == PRESET_BOOST:
await self._api.device.set_advanced_mode(
await self.device.set_advanced_mode(
HA_PRESET_TO_DAIKIN[PRESET_BOOST], ATTR_STATE_OFF
)
elif self.preset_mode == PRESET_ECO:
await self._api.device.set_advanced_mode(
await self.device.set_advanced_mode(
HA_PRESET_TO_DAIKIN[PRESET_ECO], ATTR_STATE_OFF
)
@ -293,22 +292,18 @@ class DaikinClimate(ClimateEntity):
def preset_modes(self) -> list[str]:
"""List of available preset modes."""
ret = [PRESET_NONE]
if self._api.device.support_away_mode:
if self.device.support_away_mode:
ret.append(PRESET_AWAY)
if self._api.device.support_advanced_modes:
if self.device.support_advanced_modes:
ret += [PRESET_ECO, PRESET_BOOST]
return ret
async def async_update(self) -> None:
"""Retrieve latest state."""
await self._api.async_update()
async def async_turn_on(self) -> None:
"""Turn device on."""
await self._api.device.set({})
await self.device.set({})
async def async_turn_off(self) -> None:
"""Turn device off."""
await self._api.device.set(
await self.device.set(
{HA_ATTR_TO_DAIKIN[ATTR_HVAC_MODE]: HA_STATE_TO_DAIKIN[HVACMode.OFF]}
)

View File

@ -0,0 +1,30 @@
"""Coordinator for Daikin integration."""
from datetime import timedelta
import logging
from pydaikin.daikin_base import Appliance
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
class DaikinCoordinator(DataUpdateCoordinator[None]):
"""Class to manage fetching Daikin data."""
def __init__(self, hass: HomeAssistant, device: Appliance) -> None:
"""Initialize global Daikin data updater."""
super().__init__(
hass,
_LOGGER,
name=device.values.get("name", DOMAIN),
update_interval=timedelta(seconds=60),
)
self.device = device
async def _async_update_data(self) -> None:
await self.device.update_status()

View File

@ -0,0 +1,25 @@
"""Base entity for Daikin."""
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .coordinator import DaikinCoordinator
class DaikinEntity(CoordinatorEntity[DaikinCoordinator]):
"""Base entity for Daikin."""
_attr_has_entity_name = True
def __init__(self, coordinator: DaikinCoordinator) -> None:
"""Initialize the entity."""
super().__init__(coordinator)
self.device = coordinator.device
info = self.device.values
self._attr_device_info = DeviceInfo(
connections={(CONNECTION_NETWORK_MAC, self.device.mac)},
manufacturer="Daikin",
model=info.get("model"),
name=info.get("name"),
sw_version=info.get("ver", "").replace("_", "."),
)

View File

@ -25,7 +25,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import DOMAIN as DAIKIN_DOMAIN, DaikinApi
from . import DOMAIN as DAIKIN_DOMAIN
from .const import (
ATTR_COMPRESSOR_FREQUENCY,
ATTR_COOL_ENERGY,
@ -38,6 +38,8 @@ from .const import (
ATTR_TOTAL_ENERGY_TODAY,
ATTR_TOTAL_POWER,
)
from .coordinator import DaikinCoordinator
from .entity import DaikinEntity
@dataclass(frozen=True, kw_only=True)
@ -173,26 +175,20 @@ async def async_setup_entry(
async_add_entities(entities)
class DaikinSensor(SensorEntity):
class DaikinSensor(DaikinEntity, SensorEntity):
"""Representation of a Sensor."""
_attr_has_entity_name = True
entity_description: DaikinSensorEntityDescription
def __init__(
self, api: DaikinApi, description: DaikinSensorEntityDescription
self, coordinator: DaikinCoordinator, description: DaikinSensorEntityDescription
) -> None:
"""Initialize the sensor."""
super().__init__(coordinator)
self.entity_description = description
self._attr_device_info = api.device_info
self._attr_unique_id = f"{api.device.mac}-{description.key}"
self._api = api
self._attr_unique_id = f"{self.device.mac}-{description.key}"
@property
def native_value(self) -> float | None:
"""Return the state of the sensor."""
return self.entity_description.value_func(self._api.device)
async def async_update(self) -> None:
"""Retrieve latest state."""
await self._api.async_update()
return self.entity_description.value_func(self.device)

View File

@ -10,7 +10,9 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import DOMAIN as DAIKIN_DOMAIN, DaikinApi
from . import DOMAIN
from .coordinator import DaikinCoordinator
from .entity import DaikinEntity
DAIKIN_ATTR_ADVANCED = "adv"
DAIKIN_ATTR_STREAMER = "streamer"
@ -34,15 +36,13 @@ async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up Daikin climate based on config_entry."""
daikin_api: DaikinApi = hass.data[DAIKIN_DOMAIN][entry.entry_id]
switches: list[DaikinZoneSwitch | DaikinStreamerSwitch | DaikinToggleSwitch] = []
daikin_api: DaikinCoordinator = hass.data[DOMAIN][entry.entry_id]
switches: list[SwitchEntity] = []
if zones := daikin_api.device.zones:
switches.extend(
[
DaikinZoneSwitch(daikin_api, zone_id)
for zone_id, zone in enumerate(zones)
if zone[0] != "-"
]
DaikinZoneSwitch(daikin_api, zone_id)
for zone_id, zone in enumerate(zones)
if zone[0] != "-"
)
if daikin_api.device.support_advanced_modes:
# It isn't possible to find out from the API responses if a specific
@ -53,100 +53,80 @@ async def async_setup_entry(
async_add_entities(switches)
class DaikinZoneSwitch(SwitchEntity):
class DaikinZoneSwitch(DaikinEntity, SwitchEntity):
"""Representation of a zone."""
_attr_has_entity_name = True
_attr_translation_key = "zone"
def __init__(self, api: DaikinApi, zone_id: int) -> None:
def __init__(self, coordinator: DaikinCoordinator, zone_id: int) -> None:
"""Initialize the zone."""
self._api = api
super().__init__(coordinator)
self._zone_id = zone_id
self._attr_device_info = api.device_info
self._attr_unique_id = f"{api.device.mac}-zone{zone_id}"
self._attr_unique_id = f"{self.device.mac}-zone{zone_id}"
@property
def name(self) -> str:
"""Return the name of the sensor."""
return self._api.device.zones[self._zone_id][0]
return self.device.zones[self._zone_id][0]
@property
def is_on(self) -> bool:
"""Return the state of the sensor."""
return self._api.device.zones[self._zone_id][1] == "1"
async def async_update(self) -> None:
"""Retrieve latest state."""
await self._api.async_update()
return self.device.zones[self._zone_id][1] == "1"
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the zone on."""
await self._api.device.set_zone(self._zone_id, "zone_onoff", "1")
await self.device.set_zone(self._zone_id, "zone_onoff", "1")
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the zone off."""
await self._api.device.set_zone(self._zone_id, "zone_onoff", "0")
await self.device.set_zone(self._zone_id, "zone_onoff", "0")
class DaikinStreamerSwitch(SwitchEntity):
class DaikinStreamerSwitch(DaikinEntity, SwitchEntity):
"""Streamer state."""
_attr_name = "Streamer"
_attr_has_entity_name = True
_attr_translation_key = "streamer"
def __init__(self, api: DaikinApi) -> None:
"""Initialize streamer switch."""
self._api = api
self._attr_device_info = api.device_info
self._attr_unique_id = f"{api.device.mac}-streamer"
def __init__(self, coordinator: DaikinCoordinator) -> None:
"""Initialize switch."""
super().__init__(coordinator)
self._attr_unique_id = f"{self.device.mac}-streamer"
@property
def is_on(self) -> bool:
"""Return the state of the sensor."""
return (
DAIKIN_ATTR_STREAMER in self._api.device.represent(DAIKIN_ATTR_ADVANCED)[1]
)
async def async_update(self) -> None:
"""Retrieve latest state."""
await self._api.async_update()
return DAIKIN_ATTR_STREAMER in self.device.represent(DAIKIN_ATTR_ADVANCED)[1]
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the zone on."""
await self._api.device.set_streamer("on")
await self.device.set_streamer("on")
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the zone off."""
await self._api.device.set_streamer("off")
await self.device.set_streamer("off")
class DaikinToggleSwitch(SwitchEntity):
class DaikinToggleSwitch(DaikinEntity, SwitchEntity):
"""Switch state."""
_attr_has_entity_name = True
_attr_translation_key = "toggle"
def __init__(self, api: DaikinApi) -> None:
def __init__(self, coordinator: DaikinCoordinator) -> None:
"""Initialize switch."""
self._api = api
self._attr_device_info = api.device_info
self._attr_unique_id = f"{self._api.device.mac}-toggle"
super().__init__(coordinator)
self._attr_unique_id = f"{self.device.mac}-toggle"
@property
def is_on(self) -> bool:
"""Return the state of the sensor."""
return "off" not in self._api.device.represent(DAIKIN_ATTR_MODE)
async def async_update(self) -> None:
"""Retrieve latest state."""
await self._api.async_update()
return "off" not in self.device.represent(DAIKIN_ATTR_MODE)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the zone on."""
await self._api.device.set({})
await self.device.set({})
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the zone off."""
await self._api.device.set({DAIKIN_ATTR_MODE: "off"})
await self.device.set({DAIKIN_ATTR_MODE: "off"})

View File

@ -7,10 +7,10 @@ from aiohttp import ClientConnectionError
from freezegun.api import FrozenDateTimeFactory
import pytest
from homeassistant.components.daikin import DaikinApi, update_unique_id
from homeassistant.components.daikin import update_unique_id
from homeassistant.components.daikin.const import DOMAIN, KEY_MAC
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_HOST
from homeassistant.const import CONF_HOST, STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
@ -183,18 +183,15 @@ async def test_client_update_connection_error(
await hass.config_entries.async_setup(config_entry.entry_id)
api: DaikinApi = hass.data[DOMAIN][config_entry.entry_id]
assert api.available is True
assert hass.states.get("climate.daikinap00000").state != STATE_UNAVAILABLE
type(mock_daikin).update_status.side_effect = ClientConnectionError
freezer.tick(timedelta(seconds=90))
freezer.tick(timedelta(seconds=60))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert api.available is False
assert hass.states.get("climate.daikinap00000").state == STATE_UNAVAILABLE
assert mock_daikin.update_status.call_count == 2