mirror of https://github.com/home-assistant/core
Add Climate to switchbot cloud integration (#101660)
This commit is contained in:
parent
0cb0e3ceeb
commit
7038bd67f7
|
@ -1264,6 +1264,7 @@ omit =
|
|||
homeassistant/components/switchbot/sensor.py
|
||||
homeassistant/components/switchbot/switch.py
|
||||
homeassistant/components/switchbot/lock.py
|
||||
homeassistant/components/switchbot_cloud/climate.py
|
||||
homeassistant/components/switchbot_cloud/coordinator.py
|
||||
homeassistant/components/switchbot_cloud/entity.py
|
||||
homeassistant/components/switchbot_cloud/switch.py
|
||||
|
|
|
@ -1,27 +1,28 @@
|
|||
"""The SwitchBot via API integration."""
|
||||
from asyncio import gather
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, field
|
||||
from logging import getLogger
|
||||
|
||||
from switchbot_api import CannotConnect, Device, InvalidAuth, Remote, SwitchBotAPI
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_API_KEY, CONF_API_TOKEN, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import SwitchBotCoordinator
|
||||
|
||||
_LOGGER = getLogger(__name__)
|
||||
PLATFORMS: list[Platform] = [Platform.SWITCH]
|
||||
PLATFORMS: list[Platform] = [Platform.CLIMATE, Platform.SWITCH]
|
||||
|
||||
|
||||
@dataclass
|
||||
class SwitchbotDevices:
|
||||
"""Switchbot devices data."""
|
||||
|
||||
switches: list[Device | Remote]
|
||||
climates: list[Remote] = field(default_factory=list)
|
||||
switches: list[Device | Remote] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -32,18 +33,47 @@ class SwitchbotCloudData:
|
|||
devices: SwitchbotDevices
|
||||
|
||||
|
||||
@callback
|
||||
def prepare_device(
|
||||
hass: HomeAssistant,
|
||||
api: SwitchBotAPI,
|
||||
device: Device | Remote,
|
||||
coordinators: list[SwitchBotCoordinator],
|
||||
coordinators_by_id: dict[str, SwitchBotCoordinator],
|
||||
) -> tuple[Device | Remote, SwitchBotCoordinator]:
|
||||
"""Instantiate coordinator and adds to list for gathering."""
|
||||
coordinator = SwitchBotCoordinator(hass, api, device)
|
||||
coordinators.append(coordinator)
|
||||
coordinator = coordinators_by_id.setdefault(
|
||||
device.device_id, SwitchBotCoordinator(hass, api, device)
|
||||
)
|
||||
return (device, coordinator)
|
||||
|
||||
|
||||
@callback
|
||||
def make_device_data(
|
||||
hass: HomeAssistant,
|
||||
api: SwitchBotAPI,
|
||||
devices: list[Device | Remote],
|
||||
coordinators_by_id: dict[str, SwitchBotCoordinator],
|
||||
) -> SwitchbotDevices:
|
||||
"""Make device data."""
|
||||
devices_data = SwitchbotDevices()
|
||||
for device in devices:
|
||||
if isinstance(device, Remote) and device.device_type.endswith(
|
||||
"Air Conditioner"
|
||||
):
|
||||
devices_data.climates.append(
|
||||
prepare_device(hass, api, device, coordinators_by_id)
|
||||
)
|
||||
if (
|
||||
isinstance(device, Device)
|
||||
and device.device_type.startswith("Plug")
|
||||
or isinstance(device, Remote)
|
||||
):
|
||||
devices_data.switches.append(
|
||||
prepare_device(hass, api, device, coordinators_by_id)
|
||||
)
|
||||
return devices_data
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, config: ConfigEntry) -> bool:
|
||||
"""Set up SwitchBot via API from a config entry."""
|
||||
token = config.data[CONF_API_TOKEN]
|
||||
|
@ -60,25 +90,15 @@ async def async_setup_entry(hass: HomeAssistant, config: ConfigEntry) -> bool:
|
|||
except CannotConnect as ex:
|
||||
raise ConfigEntryNotReady from ex
|
||||
_LOGGER.debug("Devices: %s", devices)
|
||||
coordinators: list[SwitchBotCoordinator] = []
|
||||
coordinators_by_id: dict[str, SwitchBotCoordinator] = {}
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
data = SwitchbotCloudData(
|
||||
api=api,
|
||||
devices=SwitchbotDevices(
|
||||
switches=[
|
||||
prepare_device(hass, api, device, coordinators)
|
||||
for device in devices
|
||||
if isinstance(device, Device)
|
||||
and device.device_type.startswith("Plug")
|
||||
or isinstance(device, Remote)
|
||||
],
|
||||
),
|
||||
hass.data[DOMAIN][config.entry_id] = SwitchbotCloudData(
|
||||
api=api, devices=make_device_data(hass, api, devices, coordinators_by_id)
|
||||
)
|
||||
hass.data[DOMAIN][config.entry_id] = data
|
||||
for device_type, devices in vars(data.devices).items():
|
||||
_LOGGER.debug("%s: %s", device_type, devices)
|
||||
await hass.config_entries.async_forward_entry_setups(config, PLATFORMS)
|
||||
await gather(*[coordinator.async_refresh() for coordinator in coordinators])
|
||||
await gather(
|
||||
*[coordinator.async_refresh() for coordinator in coordinators_by_id.values()]
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
"""Support for SwitchBot Air Conditioner remotes."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from switchbot_api import AirConditionerCommands
|
||||
|
||||
import homeassistant.components.climate as FanState
|
||||
from homeassistant.components.climate import (
|
||||
ClimateEntity,
|
||||
ClimateEntityFeature,
|
||||
HVACMode,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import DiscoveryInfoType
|
||||
|
||||
from . import SwitchbotCloudData
|
||||
from .const import DOMAIN
|
||||
from .entity import SwitchBotCloudEntity
|
||||
|
||||
_SWITCHBOT_HVAC_MODES: dict[HVACMode, int] = {
|
||||
HVACMode.HEAT_COOL: 1,
|
||||
HVACMode.COOL: 2,
|
||||
HVACMode.DRY: 3,
|
||||
HVACMode.FAN_ONLY: 4,
|
||||
HVACMode.HEAT: 5,
|
||||
}
|
||||
|
||||
_DEFAULT_SWITCHBOT_HVAC_MODE = _SWITCHBOT_HVAC_MODES[HVACMode.FAN_ONLY]
|
||||
|
||||
_SWITCHBOT_FAN_MODES: dict[str, int] = {
|
||||
FanState.FAN_AUTO: 1,
|
||||
FanState.FAN_LOW: 2,
|
||||
FanState.FAN_MEDIUM: 3,
|
||||
FanState.FAN_HIGH: 4,
|
||||
}
|
||||
|
||||
_DEFAULT_SWITCHBOT_FAN_MODE = _SWITCHBOT_FAN_MODES[FanState.FAN_AUTO]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up SwitchBot Cloud entry."""
|
||||
data: SwitchbotCloudData = hass.data[DOMAIN][config.entry_id]
|
||||
async_add_entities(
|
||||
SwitchBotCloudAirConditionner(data.api, device, coordinator)
|
||||
for device, coordinator in data.devices.climates
|
||||
)
|
||||
|
||||
|
||||
class SwitchBotCloudAirConditionner(SwitchBotCloudEntity, ClimateEntity):
|
||||
"""Representation of a SwitchBot air conditionner, as it is an IR device, we don't know the actual state."""
|
||||
|
||||
_attr_assumed_state = True
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.FAN_MODE | ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
)
|
||||
_attr_fan_modes = [
|
||||
FanState.FAN_AUTO,
|
||||
FanState.FAN_LOW,
|
||||
FanState.FAN_MEDIUM,
|
||||
FanState.FAN_HIGH,
|
||||
]
|
||||
_attr_fan_mode = FanState.FAN_AUTO
|
||||
_attr_hvac_modes = [
|
||||
HVACMode.HEAT_COOL,
|
||||
HVACMode.COOL,
|
||||
HVACMode.DRY,
|
||||
HVACMode.FAN_ONLY,
|
||||
HVACMode.HEAT,
|
||||
]
|
||||
_attr_hvac_mode = HVACMode.FAN_ONLY
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_attr_target_temperature = 21
|
||||
_attr_name = None
|
||||
|
||||
async def _do_send_command(
|
||||
self,
|
||||
hvac_mode: HVACMode | None = None,
|
||||
fan_mode: str | None = None,
|
||||
temperature: float | None = None,
|
||||
) -> None:
|
||||
new_temperature = temperature or self._attr_target_temperature
|
||||
new_mode = _SWITCHBOT_HVAC_MODES.get(
|
||||
hvac_mode or self._attr_hvac_mode, _DEFAULT_SWITCHBOT_HVAC_MODE
|
||||
)
|
||||
new_fan_speed = _SWITCHBOT_FAN_MODES.get(
|
||||
fan_mode or self._attr_fan_mode, _DEFAULT_SWITCHBOT_FAN_MODE
|
||||
)
|
||||
await self.send_command(
|
||||
AirConditionerCommands.SET_ALL,
|
||||
parameters=f"{new_temperature},{new_mode},{new_fan_speed},on",
|
||||
)
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||
"""Set target hvac mode."""
|
||||
await self._do_send_command(hvac_mode=hvac_mode)
|
||||
self._attr_hvac_mode = hvac_mode
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_set_fan_mode(self, fan_mode: str) -> None:
|
||||
"""Set target fan mode."""
|
||||
await self._do_send_command(fan_mode=fan_mode)
|
||||
self._attr_fan_mode = fan_mode
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Set target temperature."""
|
||||
if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None:
|
||||
return
|
||||
await self._do_send_command(temperature=temperature)
|
||||
self._attr_target_temperature = temperature
|
|
@ -3,7 +3,7 @@
|
|||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from switchbot_api import CannotConnect, Device, InvalidAuth, PowerState
|
||||
from switchbot_api import CannotConnect, Device, InvalidAuth, PowerState, Remote
|
||||
|
||||
from homeassistant.components.switchbot_cloud import SwitchBotAPI
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
|
@ -32,12 +32,24 @@ async def test_setup_entry_success(
|
|||
) -> None:
|
||||
"""Test successful setup of entry."""
|
||||
mock_list_devices.return_value = [
|
||||
Remote(
|
||||
deviceId="air-conditonner-id-1",
|
||||
deviceName="air-conditonner-name-1",
|
||||
remoteType="Air Conditioner",
|
||||
hubDeviceId="test-hub-id",
|
||||
),
|
||||
Device(
|
||||
deviceId="test-id",
|
||||
deviceName="test-name",
|
||||
deviceId="plug-id-1",
|
||||
deviceName="plug-name-1",
|
||||
deviceType="Plug",
|
||||
hubDeviceId="test-hub-id",
|
||||
)
|
||||
),
|
||||
Remote(
|
||||
deviceId="plug-id-2",
|
||||
deviceName="plug-name-2",
|
||||
remoteType="DIY Plug",
|
||||
hubDeviceId="test-hub-id",
|
||||
),
|
||||
]
|
||||
mock_get_status.return_value = {"power": PowerState.ON.value}
|
||||
entry = configure_integration(hass)
|
||||
|
|
Loading…
Reference in New Issue