mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 18:57:06 +00:00
Add Fan platform to Switchbot cloud (#148304)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
parent
0acfb81d50
commit
cd94685b7d
@ -29,6 +29,7 @@ PLATFORMS: list[Platform] = [
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.BUTTON,
|
||||
Platform.CLIMATE,
|
||||
Platform.FAN,
|
||||
Platform.LOCK,
|
||||
Platform.SENSOR,
|
||||
Platform.SWITCH,
|
||||
@ -51,6 +52,7 @@ class SwitchbotDevices:
|
||||
sensors: list[tuple[Device, SwitchBotCoordinator]] = field(default_factory=list)
|
||||
vacuums: list[tuple[Device, SwitchBotCoordinator]] = field(default_factory=list)
|
||||
locks: list[tuple[Device, SwitchBotCoordinator]] = field(default_factory=list)
|
||||
fans: list[tuple[Device, SwitchBotCoordinator]] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -96,7 +98,6 @@ async def make_switchbot_devices(
|
||||
for device in devices
|
||||
]
|
||||
)
|
||||
|
||||
return devices_data
|
||||
|
||||
|
||||
@ -177,6 +178,16 @@ async def make_device_data(
|
||||
else:
|
||||
devices_data.switches.append((device, coordinator))
|
||||
|
||||
if isinstance(device, Device) and device.device_type in [
|
||||
"Battery Circulator Fan",
|
||||
"Circulator Fan",
|
||||
]:
|
||||
coordinator = await coordinator_for_device(
|
||||
hass, entry, api, device, coordinators_by_id
|
||||
)
|
||||
devices_data.fans.append((device, coordinator))
|
||||
devices_data.sensors.append((device, coordinator))
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up SwitchBot via API from a config entry."""
|
||||
|
120
homeassistant/components/switchbot_cloud/fan.py
Normal file
120
homeassistant/components/switchbot_cloud/fan.py
Normal file
@ -0,0 +1,120 @@
|
||||
"""Support for the Switchbot Battery Circulator fan."""
|
||||
|
||||
import asyncio
|
||||
from typing import Any
|
||||
|
||||
from switchbot_api import (
|
||||
BatteryCirculatorFanCommands,
|
||||
BatteryCirculatorFanMode,
|
||||
CommonCommands,
|
||||
)
|
||||
|
||||
from homeassistant.components.fan import FanEntity, FanEntityFeature
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import SwitchbotCloudData
|
||||
from .const import DOMAIN
|
||||
from .entity import SwitchBotCloudEntity
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up SwitchBot Cloud entry."""
|
||||
data: SwitchbotCloudData = hass.data[DOMAIN][config.entry_id]
|
||||
async_add_entities(
|
||||
SwitchBotCloudFan(data.api, device, coordinator)
|
||||
for device, coordinator in data.devices.fans
|
||||
)
|
||||
|
||||
|
||||
class SwitchBotCloudFan(SwitchBotCloudEntity, FanEntity):
|
||||
"""Representation of a SwitchBot Battery Circulator Fan."""
|
||||
|
||||
_attr_name = None
|
||||
|
||||
_attr_supported_features = (
|
||||
FanEntityFeature.SET_SPEED
|
||||
| FanEntityFeature.PRESET_MODE
|
||||
| FanEntityFeature.TURN_OFF
|
||||
| FanEntityFeature.TURN_ON
|
||||
)
|
||||
_attr_preset_modes = list(BatteryCirculatorFanMode)
|
||||
|
||||
_attr_is_on: bool | None = None
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool | None:
|
||||
"""Return true if the entity is on."""
|
||||
return self._attr_is_on
|
||||
|
||||
def _set_attributes(self) -> None:
|
||||
"""Set attributes from coordinator data."""
|
||||
if self.coordinator.data is None:
|
||||
return
|
||||
|
||||
power: str = self.coordinator.data["power"]
|
||||
mode: str = self.coordinator.data["mode"]
|
||||
fan_speed: str = self.coordinator.data["fanSpeed"]
|
||||
self._attr_is_on = power == "on"
|
||||
self._attr_preset_mode = mode
|
||||
self._attr_percentage = int(fan_speed)
|
||||
self._attr_supported_features = (
|
||||
FanEntityFeature.PRESET_MODE
|
||||
| FanEntityFeature.TURN_OFF
|
||||
| FanEntityFeature.TURN_ON
|
||||
)
|
||||
if self.is_on and self.preset_mode == BatteryCirculatorFanMode.DIRECT.value:
|
||||
self._attr_supported_features |= FanEntityFeature.SET_SPEED
|
||||
|
||||
async def async_turn_on(
|
||||
self,
|
||||
percentage: int | None = None,
|
||||
preset_mode: str | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Turn on the fan."""
|
||||
await self.send_api_command(CommonCommands.ON)
|
||||
await self.send_api_command(
|
||||
command=BatteryCirculatorFanCommands.SET_WIND_MODE,
|
||||
parameters=str(self.preset_mode),
|
||||
)
|
||||
if self.preset_mode == BatteryCirculatorFanMode.DIRECT.value:
|
||||
await self.send_api_command(
|
||||
command=BatteryCirculatorFanCommands.SET_WIND_SPEED,
|
||||
parameters=str(self.percentage),
|
||||
)
|
||||
await asyncio.sleep(5)
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn off the fan."""
|
||||
await self.send_api_command(CommonCommands.OFF)
|
||||
await asyncio.sleep(5)
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
||||
async def async_set_percentage(self, percentage: int) -> None:
|
||||
"""Set the speed of the fan, as a percentage."""
|
||||
await self.send_api_command(
|
||||
command=BatteryCirculatorFanCommands.SET_WIND_MODE,
|
||||
parameters=str(BatteryCirculatorFanMode.DIRECT.value),
|
||||
)
|
||||
await self.send_api_command(
|
||||
command=BatteryCirculatorFanCommands.SET_WIND_SPEED,
|
||||
parameters=str(percentage),
|
||||
)
|
||||
await asyncio.sleep(5)
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set new preset mode."""
|
||||
await self.send_api_command(
|
||||
command=BatteryCirculatorFanCommands.SET_WIND_MODE,
|
||||
parameters=preset_mode,
|
||||
)
|
||||
await asyncio.sleep(5)
|
||||
await self.coordinator.async_request_refresh()
|
@ -91,6 +91,7 @@ CO2_DESCRIPTION = SensorEntityDescription(
|
||||
|
||||
SENSOR_DESCRIPTIONS_BY_DEVICE_TYPES = {
|
||||
"Bot": (BATTERY_DESCRIPTION,),
|
||||
"Battery Circulator Fan": (BATTERY_DESCRIPTION,),
|
||||
"Meter": (
|
||||
TEMPERATURE_DESCRIPTION,
|
||||
HUMIDITY_DESCRIPTION,
|
||||
|
187
tests/components/switchbot_cloud/test_fan.py
Normal file
187
tests/components/switchbot_cloud/test_fan.py
Normal file
@ -0,0 +1,187 @@
|
||||
"""Test for the Switchbot Battery Circulator Fan."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from switchbot_api import Device, SwitchBotAPI
|
||||
|
||||
from homeassistant.components.fan import (
|
||||
ATTR_PERCENTAGE,
|
||||
ATTR_PRESET_MODE,
|
||||
DOMAIN as FAN_DOMAIN,
|
||||
SERVICE_SET_PERCENTAGE,
|
||||
SERVICE_SET_PRESET_MODE,
|
||||
SERVICE_TURN_ON,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
SERVICE_TURN_OFF,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import configure_integration
|
||||
|
||||
|
||||
async def test_coordinator_data_is_none(
|
||||
hass: HomeAssistant, mock_list_devices, mock_get_status
|
||||
) -> None:
|
||||
"""Test coordinator data is none."""
|
||||
mock_list_devices.return_value = [
|
||||
Device(
|
||||
version="V1.0",
|
||||
deviceId="battery-fan-id-1",
|
||||
deviceName="battery-fan-1",
|
||||
deviceType="Battery Circulator Fan",
|
||||
hubDeviceId="test-hub-id",
|
||||
),
|
||||
]
|
||||
mock_get_status.side_effect = [
|
||||
None,
|
||||
]
|
||||
entry = await configure_integration(hass)
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
entity_id = "fan.battery_fan_1"
|
||||
state = hass.states.get(entity_id)
|
||||
|
||||
assert state.state == STATE_UNKNOWN
|
||||
|
||||
|
||||
async def test_turn_on(hass: HomeAssistant, mock_list_devices, mock_get_status) -> None:
|
||||
"""Test turning on the fan."""
|
||||
mock_list_devices.return_value = [
|
||||
Device(
|
||||
version="V1.0",
|
||||
deviceId="battery-fan-id-1",
|
||||
deviceName="battery-fan-1",
|
||||
deviceType="Battery Circulator Fan",
|
||||
hubDeviceId="test-hub-id",
|
||||
),
|
||||
]
|
||||
mock_get_status.side_effect = [
|
||||
{"power": "off", "mode": "direct", "fanSpeed": "0"},
|
||||
{"power": "on", "mode": "direct", "fanSpeed": "0"},
|
||||
{"power": "on", "mode": "direct", "fanSpeed": "0"},
|
||||
]
|
||||
entry = await configure_integration(hass)
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
entity_id = "fan.battery_fan_1"
|
||||
state = hass.states.get(entity_id)
|
||||
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
with patch.object(SwitchBotAPI, "send_command") as mock_send_command:
|
||||
await hass.services.async_call(
|
||||
FAN_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id}, blocking=True
|
||||
)
|
||||
mock_send_command.assert_called()
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == STATE_ON
|
||||
|
||||
|
||||
async def test_turn_off(
|
||||
hass: HomeAssistant, mock_list_devices, mock_get_status
|
||||
) -> None:
|
||||
"""Test turning off the fan."""
|
||||
mock_list_devices.return_value = [
|
||||
Device(
|
||||
version="V1.0",
|
||||
deviceId="battery-fan-id-1",
|
||||
deviceName="battery-fan-1",
|
||||
deviceType="Battery Circulator Fan",
|
||||
hubDeviceId="test-hub-id",
|
||||
),
|
||||
]
|
||||
mock_get_status.side_effect = [
|
||||
{"power": "on", "mode": "direct", "fanSpeed": "0"},
|
||||
{"power": "off", "mode": "direct", "fanSpeed": "0"},
|
||||
{"power": "off", "mode": "direct", "fanSpeed": "0"},
|
||||
]
|
||||
entry = await configure_integration(hass)
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
entity_id = "fan.battery_fan_1"
|
||||
state = hass.states.get(entity_id)
|
||||
|
||||
assert state.state == STATE_ON
|
||||
|
||||
with patch.object(SwitchBotAPI, "send_command") as mock_send_command:
|
||||
await hass.services.async_call(
|
||||
FAN_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id}, blocking=True
|
||||
)
|
||||
mock_send_command.assert_called()
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
|
||||
async def test_set_percentage(
|
||||
hass: HomeAssistant, mock_list_devices, mock_get_status
|
||||
) -> None:
|
||||
"""Test set percentage."""
|
||||
mock_list_devices.return_value = [
|
||||
Device(
|
||||
version="V1.0",
|
||||
deviceId="battery-fan-id-1",
|
||||
deviceName="battery-fan-1",
|
||||
deviceType="Battery Circulator Fan",
|
||||
hubDeviceId="test-hub-id",
|
||||
),
|
||||
]
|
||||
mock_get_status.side_effect = [
|
||||
{"power": "on", "mode": "direct", "fanSpeed": "0"},
|
||||
{"power": "on", "mode": "direct", "fanSpeed": "0"},
|
||||
{"power": "off", "mode": "direct", "fanSpeed": "5"},
|
||||
]
|
||||
entry = await configure_integration(hass)
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
entity_id = "fan.battery_fan_1"
|
||||
state = hass.states.get(entity_id)
|
||||
|
||||
assert state.state == STATE_ON
|
||||
|
||||
with patch.object(SwitchBotAPI, "send_command") as mock_send_command:
|
||||
await hass.services.async_call(
|
||||
FAN_DOMAIN,
|
||||
SERVICE_SET_PERCENTAGE,
|
||||
{ATTR_ENTITY_ID: entity_id, ATTR_PERCENTAGE: 5},
|
||||
blocking=True,
|
||||
)
|
||||
mock_send_command.assert_called()
|
||||
|
||||
|
||||
async def test_set_preset_mode(
|
||||
hass: HomeAssistant, mock_list_devices, mock_get_status
|
||||
) -> None:
|
||||
"""Test set preset mode."""
|
||||
mock_list_devices.return_value = [
|
||||
Device(
|
||||
version="V1.0",
|
||||
deviceId="battery-fan-id-1",
|
||||
deviceName="battery-fan-1",
|
||||
deviceType="Battery Circulator Fan",
|
||||
hubDeviceId="test-hub-id",
|
||||
),
|
||||
]
|
||||
mock_get_status.side_effect = [
|
||||
{"power": "on", "mode": "direct", "fanSpeed": "0"},
|
||||
{"power": "on", "mode": "direct", "fanSpeed": "0"},
|
||||
{"power": "on", "mode": "baby", "fanSpeed": "0"},
|
||||
]
|
||||
entry = await configure_integration(hass)
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
entity_id = "fan.battery_fan_1"
|
||||
state = hass.states.get(entity_id)
|
||||
|
||||
assert state.state == STATE_ON
|
||||
|
||||
with patch.object(SwitchBotAPI, "send_command") as mock_send_command:
|
||||
await hass.services.async_call(
|
||||
FAN_DOMAIN,
|
||||
SERVICE_SET_PRESET_MODE,
|
||||
{ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: "baby"},
|
||||
blocking=True,
|
||||
)
|
||||
mock_send_command.assert_called_once()
|
Loading…
x
Reference in New Issue
Block a user