mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 21:57:51 +00:00
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/sensor.py
|
||||||
homeassistant/components/switchbot/switch.py
|
homeassistant/components/switchbot/switch.py
|
||||||
homeassistant/components/switchbot/lock.py
|
homeassistant/components/switchbot/lock.py
|
||||||
|
homeassistant/components/switchbot_cloud/climate.py
|
||||||
homeassistant/components/switchbot_cloud/coordinator.py
|
homeassistant/components/switchbot_cloud/coordinator.py
|
||||||
homeassistant/components/switchbot_cloud/entity.py
|
homeassistant/components/switchbot_cloud/entity.py
|
||||||
homeassistant/components/switchbot_cloud/switch.py
|
homeassistant/components/switchbot_cloud/switch.py
|
||||||
|
@ -1,27 +1,28 @@
|
|||||||
"""The SwitchBot via API integration."""
|
"""The SwitchBot via API integration."""
|
||||||
from asyncio import gather
|
from asyncio import gather
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, field
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
from switchbot_api import CannotConnect, Device, InvalidAuth, Remote, SwitchBotAPI
|
from switchbot_api import CannotConnect, Device, InvalidAuth, Remote, SwitchBotAPI
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_API_KEY, CONF_API_TOKEN, Platform
|
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 homeassistant.exceptions import ConfigEntryNotReady
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .coordinator import SwitchBotCoordinator
|
from .coordinator import SwitchBotCoordinator
|
||||||
|
|
||||||
_LOGGER = getLogger(__name__)
|
_LOGGER = getLogger(__name__)
|
||||||
PLATFORMS: list[Platform] = [Platform.SWITCH]
|
PLATFORMS: list[Platform] = [Platform.CLIMATE, Platform.SWITCH]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class SwitchbotDevices:
|
class SwitchbotDevices:
|
||||||
"""Switchbot devices data."""
|
"""Switchbot devices data."""
|
||||||
|
|
||||||
switches: list[Device | Remote]
|
climates: list[Remote] = field(default_factory=list)
|
||||||
|
switches: list[Device | Remote] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -32,18 +33,47 @@ class SwitchbotCloudData:
|
|||||||
devices: SwitchbotDevices
|
devices: SwitchbotDevices
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
def prepare_device(
|
def prepare_device(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
api: SwitchBotAPI,
|
api: SwitchBotAPI,
|
||||||
device: Device | Remote,
|
device: Device | Remote,
|
||||||
coordinators: list[SwitchBotCoordinator],
|
coordinators_by_id: dict[str, SwitchBotCoordinator],
|
||||||
) -> tuple[Device | Remote, SwitchBotCoordinator]:
|
) -> tuple[Device | Remote, SwitchBotCoordinator]:
|
||||||
"""Instantiate coordinator and adds to list for gathering."""
|
"""Instantiate coordinator and adds to list for gathering."""
|
||||||
coordinator = SwitchBotCoordinator(hass, api, device)
|
coordinator = coordinators_by_id.setdefault(
|
||||||
coordinators.append(coordinator)
|
device.device_id, SwitchBotCoordinator(hass, api, device)
|
||||||
|
)
|
||||||
return (device, coordinator)
|
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:
|
async def async_setup_entry(hass: HomeAssistant, config: ConfigEntry) -> bool:
|
||||||
"""Set up SwitchBot via API from a config entry."""
|
"""Set up SwitchBot via API from a config entry."""
|
||||||
token = config.data[CONF_API_TOKEN]
|
token = config.data[CONF_API_TOKEN]
|
||||||
@ -60,25 +90,15 @@ async def async_setup_entry(hass: HomeAssistant, config: ConfigEntry) -> bool:
|
|||||||
except CannotConnect as ex:
|
except CannotConnect as ex:
|
||||||
raise ConfigEntryNotReady from ex
|
raise ConfigEntryNotReady from ex
|
||||||
_LOGGER.debug("Devices: %s", devices)
|
_LOGGER.debug("Devices: %s", devices)
|
||||||
coordinators: list[SwitchBotCoordinator] = []
|
coordinators_by_id: dict[str, SwitchBotCoordinator] = {}
|
||||||
hass.data.setdefault(DOMAIN, {})
|
hass.data.setdefault(DOMAIN, {})
|
||||||
data = SwitchbotCloudData(
|
hass.data[DOMAIN][config.entry_id] = SwitchbotCloudData(
|
||||||
api=api,
|
api=api, devices=make_device_data(hass, api, devices, coordinators_by_id)
|
||||||
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] = 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 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
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
118
homeassistant/components/switchbot_cloud/climate.py
Normal file
118
homeassistant/components/switchbot_cloud/climate.py
Normal file
@ -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
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
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.components.switchbot_cloud import SwitchBotAPI
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
@ -32,12 +32,24 @@ async def test_setup_entry_success(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Test successful setup of entry."""
|
"""Test successful setup of entry."""
|
||||||
mock_list_devices.return_value = [
|
mock_list_devices.return_value = [
|
||||||
|
Remote(
|
||||||
|
deviceId="air-conditonner-id-1",
|
||||||
|
deviceName="air-conditonner-name-1",
|
||||||
|
remoteType="Air Conditioner",
|
||||||
|
hubDeviceId="test-hub-id",
|
||||||
|
),
|
||||||
Device(
|
Device(
|
||||||
deviceId="test-id",
|
deviceId="plug-id-1",
|
||||||
deviceName="test-name",
|
deviceName="plug-name-1",
|
||||||
deviceType="Plug",
|
deviceType="Plug",
|
||||||
hubDeviceId="test-hub-id",
|
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}
|
mock_get_status.return_value = {"power": PowerState.ON.value}
|
||||||
entry = configure_integration(hass)
|
entry = configure_integration(hass)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user