mirror of
https://github.com/home-assistant/core.git
synced 2025-07-16 17:57:11 +00:00
Add coordinator to Daikin (#124394)
* Add coordinator to Daikin * Add coordinator to Daikin * Fix * Add seconds
This commit is contained in:
parent
97c55ae6f1
commit
130b6559a6
@ -3,9 +3,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from datetime import timedelta
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from aiohttp import ClientConnectionError
|
from aiohttp import ClientConnectionError
|
||||||
from pydaikin.daikin_base import Appliance
|
from pydaikin.daikin_base import Appliance
|
||||||
@ -23,15 +21,13 @@ from homeassistant.core import HomeAssistant, callback
|
|||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
|
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
|
||||||
from homeassistant.util import Throttle
|
|
||||||
|
|
||||||
from .const import DOMAIN, KEY_MAC, TIMEOUT
|
from .const import DOMAIN, KEY_MAC, TIMEOUT
|
||||||
|
from .coordinator import DaikinCoordinator
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
PARALLEL_UPDATES = 0
|
|
||||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
|
|
||||||
|
|
||||||
PLATFORMS = [Platform.CLIMATE, Platform.SENSOR, Platform.SWITCH]
|
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:
|
if entry.unique_id is None or ".local" in entry.unique_id:
|
||||||
hass.config_entries.async_update_entry(entry, unique_id=conf[KEY_MAC])
|
hass.config_entries.async_update_entry(entry, unique_id=conf[KEY_MAC])
|
||||||
|
|
||||||
daikin_api = await daikin_api_setup(
|
session = async_get_clientsession(hass)
|
||||||
hass,
|
host = conf[CONF_HOST]
|
||||||
conf[CONF_HOST],
|
try:
|
||||||
conf.get(CONF_API_KEY),
|
async with asyncio.timeout(TIMEOUT):
|
||||||
conf.get(CONF_UUID),
|
device: Appliance = await DaikinFactory(
|
||||||
conf.get(CONF_PASSWORD),
|
host,
|
||||||
)
|
session,
|
||||||
if not daikin_api:
|
key=entry.data.get(CONF_API_KEY),
|
||||||
return False
|
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)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -70,83 +79,16 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
return unload_ok
|
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(
|
async def async_migrate_unique_id(
|
||||||
hass: HomeAssistant, config_entry: ConfigEntry, api: DaikinApi
|
hass: HomeAssistant, config_entry: ConfigEntry, device: Appliance
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Migrate old entry."""
|
"""Migrate old entry."""
|
||||||
dev_reg = dr.async_get(hass)
|
dev_reg = dr.async_get(hass)
|
||||||
ent_reg = er.async_get(hass)
|
ent_reg = er.async_get(hass)
|
||||||
old_unique_id = config_entry.unique_id
|
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_mac = dr.format_mac(new_unique_id)
|
||||||
new_name = api.name
|
new_name = device.values.get("name", "Daikin AC")
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _update_unique_id(entity_entry: er.RegistryEntry) -> dict[str, str] | None:
|
def _update_unique_id(entity_entry: er.RegistryEntry) -> dict[str, str] | None:
|
||||||
|
@ -34,7 +34,7 @@ import homeassistant.helpers.config_validation as cv
|
|||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||||
|
|
||||||
from . import DOMAIN as DAIKIN_DOMAIN, DaikinApi
|
from . import DOMAIN as DAIKIN_DOMAIN
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_INSIDE_TEMPERATURE,
|
ATTR_INSIDE_TEMPERATURE,
|
||||||
ATTR_OUTSIDE_TEMPERATURE,
|
ATTR_OUTSIDE_TEMPERATURE,
|
||||||
@ -42,6 +42,8 @@ from .const import (
|
|||||||
ATTR_STATE_ON,
|
ATTR_STATE_ON,
|
||||||
ATTR_TARGET_TEMPERATURE,
|
ATTR_TARGET_TEMPERATURE,
|
||||||
)
|
)
|
||||||
|
from .coordinator import DaikinCoordinator
|
||||||
|
from .entity import DaikinEntity
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -111,7 +113,7 @@ async def async_setup_entry(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Daikin climate based on config_entry."""
|
"""Set up Daikin climate based on config_entry."""
|
||||||
daikin_api = hass.data[DAIKIN_DOMAIN].get(entry.entry_id)
|
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:
|
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(".")
|
return str(round(float(target_temperature) * 2, 0) / 2).rstrip("0").rstrip(".")
|
||||||
|
|
||||||
|
|
||||||
class DaikinClimate(ClimateEntity):
|
class DaikinClimate(DaikinEntity, ClimateEntity):
|
||||||
"""Representation of a Daikin HVAC."""
|
"""Representation of a Daikin HVAC."""
|
||||||
|
|
||||||
_attr_name = None
|
_attr_name = None
|
||||||
_attr_has_entity_name = True
|
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
_attr_hvac_modes = list(HA_STATE_TO_DAIKIN)
|
_attr_hvac_modes = list(HA_STATE_TO_DAIKIN)
|
||||||
_attr_target_temperature_step = 1
|
_attr_target_temperature_step = 1
|
||||||
@ -131,13 +132,11 @@ class DaikinClimate(ClimateEntity):
|
|||||||
_attr_swing_modes: list[str]
|
_attr_swing_modes: list[str]
|
||||||
_enable_turn_on_off_backwards_compatibility = False
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(self, api: DaikinApi) -> None:
|
def __init__(self, coordinator: DaikinCoordinator) -> None:
|
||||||
"""Initialize the climate device."""
|
"""Initialize the climate device."""
|
||||||
|
super().__init__(coordinator)
|
||||||
self._api = api
|
self._attr_fan_modes = self.device.fan_rate
|
||||||
self._attr_fan_modes = api.device.fan_rate
|
self._attr_swing_modes = self.device.swing_modes
|
||||||
self._attr_swing_modes = api.device.swing_modes
|
|
||||||
self._attr_device_info = api.device_info
|
|
||||||
self._list: dict[str, list[Any]] = {
|
self._list: dict[str, list[Any]] = {
|
||||||
ATTR_HVAC_MODE: self._attr_hvac_modes,
|
ATTR_HVAC_MODE: self._attr_hvac_modes,
|
||||||
ATTR_FAN_MODE: self._attr_fan_modes,
|
ATTR_FAN_MODE: self._attr_fan_modes,
|
||||||
@ -150,13 +149,13 @@ class DaikinClimate(ClimateEntity):
|
|||||||
| ClimateEntityFeature.TARGET_TEMPERATURE
|
| 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
|
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
|
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
|
self._attr_supported_features |= ClimateEntityFeature.SWING_MODE
|
||||||
|
|
||||||
async def _set(self, settings: dict[str, Any]) -> None:
|
async def _set(self, settings: dict[str, Any]) -> None:
|
||||||
@ -185,22 +184,22 @@ class DaikinClimate(ClimateEntity):
|
|||||||
_LOGGER.error("Invalid temperature %s", value)
|
_LOGGER.error("Invalid temperature %s", value)
|
||||||
|
|
||||||
if values:
|
if values:
|
||||||
await self._api.device.set(values)
|
await self.device.set(values)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self) -> str:
|
def unique_id(self) -> str:
|
||||||
"""Return a unique ID."""
|
"""Return a unique ID."""
|
||||||
return self._api.device.mac
|
return self.device.mac
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_temperature(self) -> float | None:
|
def current_temperature(self) -> float | None:
|
||||||
"""Return the current temperature."""
|
"""Return the current temperature."""
|
||||||
return self._api.device.inside_temperature
|
return self.device.inside_temperature
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature(self) -> float | None:
|
def target_temperature(self) -> float | None:
|
||||||
"""Return the temperature we try to reach."""
|
"""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:
|
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
@ -212,8 +211,8 @@ class DaikinClimate(ClimateEntity):
|
|||||||
ret = HA_STATE_TO_CURRENT_HVAC.get(self.hvac_mode)
|
ret = HA_STATE_TO_CURRENT_HVAC.get(self.hvac_mode)
|
||||||
if (
|
if (
|
||||||
ret in (HVACAction.COOLING, HVACAction.HEATING)
|
ret in (HVACAction.COOLING, HVACAction.HEATING)
|
||||||
and self._api.device.support_compressor_frequency
|
and self.device.support_compressor_frequency
|
||||||
and self._api.device.compressor_frequency == 0
|
and self.device.compressor_frequency == 0
|
||||||
):
|
):
|
||||||
return HVACAction.IDLE
|
return HVACAction.IDLE
|
||||||
return ret
|
return ret
|
||||||
@ -221,7 +220,7 @@ class DaikinClimate(ClimateEntity):
|
|||||||
@property
|
@property
|
||||||
def hvac_mode(self) -> HVACMode:
|
def hvac_mode(self) -> HVACMode:
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
"""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)
|
return DAIKIN_TO_HA_STATE.get(daikin_mode, HVACMode.HEAT_COOL)
|
||||||
|
|
||||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||||
@ -231,7 +230,7 @@ class DaikinClimate(ClimateEntity):
|
|||||||
@property
|
@property
|
||||||
def fan_mode(self) -> str:
|
def fan_mode(self) -> str:
|
||||||
"""Return the fan setting."""
|
"""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:
|
async def async_set_fan_mode(self, fan_mode: str) -> None:
|
||||||
"""Set fan mode."""
|
"""Set fan mode."""
|
||||||
@ -240,7 +239,7 @@ class DaikinClimate(ClimateEntity):
|
|||||||
@property
|
@property
|
||||||
def swing_mode(self) -> str:
|
def swing_mode(self) -> str:
|
||||||
"""Return the fan setting."""
|
"""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:
|
async def async_set_swing_mode(self, swing_mode: str) -> None:
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
@ -250,18 +249,18 @@ class DaikinClimate(ClimateEntity):
|
|||||||
def preset_mode(self) -> str:
|
def preset_mode(self) -> str:
|
||||||
"""Return the preset_mode."""
|
"""Return the preset_mode."""
|
||||||
if (
|
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]
|
== HA_PRESET_TO_DAIKIN[PRESET_AWAY]
|
||||||
):
|
):
|
||||||
return PRESET_AWAY
|
return PRESET_AWAY
|
||||||
if (
|
if (
|
||||||
HA_PRESET_TO_DAIKIN[PRESET_BOOST]
|
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
|
return PRESET_BOOST
|
||||||
if (
|
if (
|
||||||
HA_PRESET_TO_DAIKIN[PRESET_ECO]
|
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_ECO
|
||||||
return PRESET_NONE
|
return PRESET_NONE
|
||||||
@ -269,23 +268,23 @@ class DaikinClimate(ClimateEntity):
|
|||||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||||
"""Set preset mode."""
|
"""Set preset mode."""
|
||||||
if preset_mode == PRESET_AWAY:
|
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:
|
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
|
HA_PRESET_TO_DAIKIN[PRESET_BOOST], ATTR_STATE_ON
|
||||||
)
|
)
|
||||||
elif preset_mode == PRESET_ECO:
|
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
|
HA_PRESET_TO_DAIKIN[PRESET_ECO], ATTR_STATE_ON
|
||||||
)
|
)
|
||||||
elif self.preset_mode == PRESET_AWAY:
|
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:
|
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
|
HA_PRESET_TO_DAIKIN[PRESET_BOOST], ATTR_STATE_OFF
|
||||||
)
|
)
|
||||||
elif self.preset_mode == PRESET_ECO:
|
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
|
HA_PRESET_TO_DAIKIN[PRESET_ECO], ATTR_STATE_OFF
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -293,22 +292,18 @@ class DaikinClimate(ClimateEntity):
|
|||||||
def preset_modes(self) -> list[str]:
|
def preset_modes(self) -> list[str]:
|
||||||
"""List of available preset modes."""
|
"""List of available preset modes."""
|
||||||
ret = [PRESET_NONE]
|
ret = [PRESET_NONE]
|
||||||
if self._api.device.support_away_mode:
|
if self.device.support_away_mode:
|
||||||
ret.append(PRESET_AWAY)
|
ret.append(PRESET_AWAY)
|
||||||
if self._api.device.support_advanced_modes:
|
if self.device.support_advanced_modes:
|
||||||
ret += [PRESET_ECO, PRESET_BOOST]
|
ret += [PRESET_ECO, PRESET_BOOST]
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
async def async_update(self) -> None:
|
|
||||||
"""Retrieve latest state."""
|
|
||||||
await self._api.async_update()
|
|
||||||
|
|
||||||
async def async_turn_on(self) -> None:
|
async def async_turn_on(self) -> None:
|
||||||
"""Turn device on."""
|
"""Turn device on."""
|
||||||
await self._api.device.set({})
|
await self.device.set({})
|
||||||
|
|
||||||
async def async_turn_off(self) -> None:
|
async def async_turn_off(self) -> None:
|
||||||
"""Turn device off."""
|
"""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]}
|
{HA_ATTR_TO_DAIKIN[ATTR_HVAC_MODE]: HA_STATE_TO_DAIKIN[HVACMode.OFF]}
|
||||||
)
|
)
|
||||||
|
30
homeassistant/components/daikin/coordinator.py
Normal file
30
homeassistant/components/daikin/coordinator.py
Normal 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()
|
25
homeassistant/components/daikin/entity.py
Normal file
25
homeassistant/components/daikin/entity.py
Normal 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("_", "."),
|
||||||
|
)
|
@ -25,7 +25,7 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||||
|
|
||||||
from . import DOMAIN as DAIKIN_DOMAIN, DaikinApi
|
from . import DOMAIN as DAIKIN_DOMAIN
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_COMPRESSOR_FREQUENCY,
|
ATTR_COMPRESSOR_FREQUENCY,
|
||||||
ATTR_COOL_ENERGY,
|
ATTR_COOL_ENERGY,
|
||||||
@ -38,6 +38,8 @@ from .const import (
|
|||||||
ATTR_TOTAL_ENERGY_TODAY,
|
ATTR_TOTAL_ENERGY_TODAY,
|
||||||
ATTR_TOTAL_POWER,
|
ATTR_TOTAL_POWER,
|
||||||
)
|
)
|
||||||
|
from .coordinator import DaikinCoordinator
|
||||||
|
from .entity import DaikinEntity
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
@ -173,26 +175,20 @@ async def async_setup_entry(
|
|||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
class DaikinSensor(SensorEntity):
|
class DaikinSensor(DaikinEntity, SensorEntity):
|
||||||
"""Representation of a Sensor."""
|
"""Representation of a Sensor."""
|
||||||
|
|
||||||
_attr_has_entity_name = True
|
|
||||||
entity_description: DaikinSensorEntityDescription
|
entity_description: DaikinSensorEntityDescription
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, api: DaikinApi, description: DaikinSensorEntityDescription
|
self, coordinator: DaikinCoordinator, description: DaikinSensorEntityDescription
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
|
super().__init__(coordinator)
|
||||||
self.entity_description = description
|
self.entity_description = description
|
||||||
self._attr_device_info = api.device_info
|
self._attr_unique_id = f"{self.device.mac}-{description.key}"
|
||||||
self._attr_unique_id = f"{api.device.mac}-{description.key}"
|
|
||||||
self._api = api
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self) -> float | None:
|
def native_value(self) -> float | None:
|
||||||
"""Return the state of the sensor."""
|
"""Return the state of the sensor."""
|
||||||
return self.entity_description.value_func(self._api.device)
|
return self.entity_description.value_func(self.device)
|
||||||
|
|
||||||
async def async_update(self) -> None:
|
|
||||||
"""Retrieve latest state."""
|
|
||||||
await self._api.async_update()
|
|
||||||
|
@ -10,7 +10,9 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
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_ADVANCED = "adv"
|
||||||
DAIKIN_ATTR_STREAMER = "streamer"
|
DAIKIN_ATTR_STREAMER = "streamer"
|
||||||
@ -34,15 +36,13 @@ async def async_setup_entry(
|
|||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Daikin climate based on config_entry."""
|
"""Set up Daikin climate based on config_entry."""
|
||||||
daikin_api: DaikinApi = hass.data[DAIKIN_DOMAIN][entry.entry_id]
|
daikin_api: DaikinCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||||
switches: list[DaikinZoneSwitch | DaikinStreamerSwitch | DaikinToggleSwitch] = []
|
switches: list[SwitchEntity] = []
|
||||||
if zones := daikin_api.device.zones:
|
if zones := daikin_api.device.zones:
|
||||||
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] != "-"
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
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
|
||||||
@ -53,100 +53,80 @@ async def async_setup_entry(
|
|||||||
async_add_entities(switches)
|
async_add_entities(switches)
|
||||||
|
|
||||||
|
|
||||||
class DaikinZoneSwitch(SwitchEntity):
|
class DaikinZoneSwitch(DaikinEntity, SwitchEntity):
|
||||||
"""Representation of a zone."""
|
"""Representation of a zone."""
|
||||||
|
|
||||||
_attr_has_entity_name = True
|
|
||||||
_attr_translation_key = "zone"
|
_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."""
|
"""Initialize the zone."""
|
||||||
self._api = api
|
super().__init__(coordinator)
|
||||||
self._zone_id = zone_id
|
self._zone_id = zone_id
|
||||||
self._attr_device_info = api.device_info
|
self._attr_unique_id = f"{self.device.mac}-zone{zone_id}"
|
||||||
self._attr_unique_id = f"{api.device.mac}-zone{zone_id}"
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
"""Return the name of the sensor."""
|
"""Return the name of the sensor."""
|
||||||
return self._api.device.zones[self._zone_id][0]
|
return self.device.zones[self._zone_id][0]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
"""Return the state of the sensor."""
|
"""Return the state of the sensor."""
|
||||||
return self._api.device.zones[self._zone_id][1] == "1"
|
return self.device.zones[self._zone_id][1] == "1"
|
||||||
|
|
||||||
async def async_update(self) -> None:
|
|
||||||
"""Retrieve latest state."""
|
|
||||||
await self._api.async_update()
|
|
||||||
|
|
||||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Turn the zone on."""
|
"""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:
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn the zone off."""
|
"""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."""
|
"""Streamer state."""
|
||||||
|
|
||||||
_attr_name = "Streamer"
|
_attr_name = "Streamer"
|
||||||
_attr_has_entity_name = True
|
|
||||||
_attr_translation_key = "streamer"
|
_attr_translation_key = "streamer"
|
||||||
|
|
||||||
def __init__(self, api: DaikinApi) -> None:
|
def __init__(self, coordinator: DaikinCoordinator) -> None:
|
||||||
"""Initialize streamer switch."""
|
"""Initialize switch."""
|
||||||
self._api = api
|
super().__init__(coordinator)
|
||||||
self._attr_device_info = api.device_info
|
self._attr_unique_id = f"{self.device.mac}-streamer"
|
||||||
self._attr_unique_id = f"{api.device.mac}-streamer"
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
"""Return the state of the sensor."""
|
"""Return the state of the sensor."""
|
||||||
return (
|
return DAIKIN_ATTR_STREAMER in self.device.represent(DAIKIN_ATTR_ADVANCED)[1]
|
||||||
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()
|
|
||||||
|
|
||||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Turn the zone on."""
|
"""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:
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn the zone off."""
|
"""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."""
|
"""Switch state."""
|
||||||
|
|
||||||
_attr_has_entity_name = True
|
|
||||||
_attr_translation_key = "toggle"
|
_attr_translation_key = "toggle"
|
||||||
|
|
||||||
def __init__(self, api: DaikinApi) -> None:
|
def __init__(self, coordinator: DaikinCoordinator) -> None:
|
||||||
"""Initialize switch."""
|
"""Initialize switch."""
|
||||||
self._api = api
|
super().__init__(coordinator)
|
||||||
self._attr_device_info = api.device_info
|
self._attr_unique_id = f"{self.device.mac}-toggle"
|
||||||
self._attr_unique_id = f"{self._api.device.mac}-toggle"
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
"""Return the state of the sensor."""
|
"""Return the state of the sensor."""
|
||||||
return "off" not in self._api.device.represent(DAIKIN_ATTR_MODE)
|
return "off" not in self.device.represent(DAIKIN_ATTR_MODE)
|
||||||
|
|
||||||
async def async_update(self) -> None:
|
|
||||||
"""Retrieve latest state."""
|
|
||||||
await self._api.async_update()
|
|
||||||
|
|
||||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Turn the zone on."""
|
"""Turn the zone on."""
|
||||||
await self._api.device.set({})
|
await self.device.set({})
|
||||||
|
|
||||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn the zone off."""
|
"""Turn the zone off."""
|
||||||
await self._api.device.set({DAIKIN_ATTR_MODE: "off"})
|
await self.device.set({DAIKIN_ATTR_MODE: "off"})
|
||||||
|
@ -7,10 +7,10 @@ from aiohttp import ClientConnectionError
|
|||||||
from freezegun.api import FrozenDateTimeFactory
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
import pytest
|
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.components.daikin.const import DOMAIN, KEY_MAC
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
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.core import HomeAssistant
|
||||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
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)
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
|
||||||
api: DaikinApi = hass.data[DOMAIN][config_entry.entry_id]
|
assert hass.states.get("climate.daikinap00000").state != STATE_UNAVAILABLE
|
||||||
|
|
||||||
assert api.available is True
|
|
||||||
|
|
||||||
type(mock_daikin).update_status.side_effect = ClientConnectionError
|
type(mock_daikin).update_status.side_effect = ClientConnectionError
|
||||||
|
|
||||||
freezer.tick(timedelta(seconds=90))
|
freezer.tick(timedelta(seconds=60))
|
||||||
async_fire_time_changed(hass)
|
async_fire_time_changed(hass)
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
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
|
assert mock_daikin.update_status.call_count == 2
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user