mirror of
https://github.com/home-assistant/core.git
synced 2025-09-06 05:11:35 +02:00
Add air purifier for switchbot cloud integration (#147001)
This commit is contained in:
@@ -184,6 +184,11 @@ async def make_device_data(
|
|||||||
devices_data.buttons.append((device, coordinator))
|
devices_data.buttons.append((device, coordinator))
|
||||||
else:
|
else:
|
||||||
devices_data.switches.append((device, coordinator))
|
devices_data.switches.append((device, coordinator))
|
||||||
|
if isinstance(device, Device) and device.device_type.startswith("Air Purifier"):
|
||||||
|
coordinator = await coordinator_for_device(
|
||||||
|
hass, entry, api, device, coordinators_by_id
|
||||||
|
)
|
||||||
|
devices_data.fans.append((device, coordinator))
|
||||||
|
|
||||||
if isinstance(device, Device) and device.device_type in [
|
if isinstance(device, Device) and device.device_type in [
|
||||||
"Battery Circulator Fan",
|
"Battery Circulator Fan",
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
"""Constants for the SwitchBot Cloud integration."""
|
"""Constants for the SwitchBot Cloud integration."""
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from enum import Enum
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
DOMAIN: Final = "switchbot_cloud"
|
DOMAIN: Final = "switchbot_cloud"
|
||||||
@@ -17,5 +18,18 @@ VACUUM_FAN_SPEED_STRONG = "strong"
|
|||||||
VACUUM_FAN_SPEED_MAX = "max"
|
VACUUM_FAN_SPEED_MAX = "max"
|
||||||
|
|
||||||
AFTER_COMMAND_REFRESH = 5
|
AFTER_COMMAND_REFRESH = 5
|
||||||
|
|
||||||
COVER_ENTITY_AFTER_COMMAND_REFRESH = 10
|
COVER_ENTITY_AFTER_COMMAND_REFRESH = 10
|
||||||
|
|
||||||
|
|
||||||
|
class AirPurifierMode(Enum):
|
||||||
|
"""Air Purifier Modes."""
|
||||||
|
|
||||||
|
NORMAL = 1
|
||||||
|
AUTO = 2
|
||||||
|
SLEEP = 3
|
||||||
|
PET = 4
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_modes(cls) -> list[str]:
|
||||||
|
"""Return a list of available air purifier modes as lowercase strings."""
|
||||||
|
return [mode.name.lower() for mode in cls]
|
||||||
|
@@ -1,23 +1,30 @@
|
|||||||
"""Support for the Switchbot Battery Circulator fan."""
|
"""Support for the Switchbot Battery Circulator fan."""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from switchbot_api import (
|
from switchbot_api import (
|
||||||
|
AirPurifierCommands,
|
||||||
BatteryCirculatorFanCommands,
|
BatteryCirculatorFanCommands,
|
||||||
BatteryCirculatorFanMode,
|
BatteryCirculatorFanMode,
|
||||||
CommonCommands,
|
CommonCommands,
|
||||||
|
SwitchBotAPI,
|
||||||
)
|
)
|
||||||
|
|
||||||
from homeassistant.components.fan import FanEntity, FanEntityFeature
|
from homeassistant.components.fan import FanEntity, FanEntityFeature
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import STATE_ON
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from . import SwitchbotCloudData
|
from . import SwitchbotCloudData
|
||||||
from .const import AFTER_COMMAND_REFRESH, DOMAIN
|
from .const import AFTER_COMMAND_REFRESH, DOMAIN, AirPurifierMode
|
||||||
from .entity import SwitchBotCloudEntity
|
from .entity import SwitchBotCloudEntity
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
PARALLEL_UPDATES = 0
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@@ -26,10 +33,13 @@ async def async_setup_entry(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Set up SwitchBot Cloud entry."""
|
"""Set up SwitchBot Cloud entry."""
|
||||||
data: SwitchbotCloudData = hass.data[DOMAIN][config.entry_id]
|
data: SwitchbotCloudData = hass.data[DOMAIN][config.entry_id]
|
||||||
async_add_entities(
|
for device, coordinator in data.devices.fans:
|
||||||
SwitchBotCloudFan(data.api, device, coordinator)
|
if device.device_type.startswith("Air Purifier"):
|
||||||
for device, coordinator in data.devices.fans
|
async_add_entities(
|
||||||
)
|
[SwitchBotAirPurifierEntity(data.api, device, coordinator)]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
async_add_entities([SwitchBotCloudFan(data.api, device, coordinator)])
|
||||||
|
|
||||||
|
|
||||||
class SwitchBotCloudFan(SwitchBotCloudEntity, FanEntity):
|
class SwitchBotCloudFan(SwitchBotCloudEntity, FanEntity):
|
||||||
@@ -37,6 +47,7 @@ class SwitchBotCloudFan(SwitchBotCloudEntity, FanEntity):
|
|||||||
|
|
||||||
_attr_name = None
|
_attr_name = None
|
||||||
|
|
||||||
|
_api: SwitchBotAPI
|
||||||
_attr_supported_features = (
|
_attr_supported_features = (
|
||||||
FanEntityFeature.SET_SPEED
|
FanEntityFeature.SET_SPEED
|
||||||
| FanEntityFeature.PRESET_MODE
|
| FanEntityFeature.PRESET_MODE
|
||||||
@@ -118,3 +129,75 @@ class SwitchBotCloudFan(SwitchBotCloudEntity, FanEntity):
|
|||||||
)
|
)
|
||||||
await asyncio.sleep(AFTER_COMMAND_REFRESH)
|
await asyncio.sleep(AFTER_COMMAND_REFRESH)
|
||||||
await self.coordinator.async_request_refresh()
|
await self.coordinator.async_request_refresh()
|
||||||
|
|
||||||
|
|
||||||
|
class SwitchBotAirPurifierEntity(SwitchBotCloudEntity, FanEntity):
|
||||||
|
"""Representation of a Switchbot air purifier."""
|
||||||
|
|
||||||
|
_api: SwitchBotAPI
|
||||||
|
_attr_supported_features = (
|
||||||
|
FanEntityFeature.PRESET_MODE
|
||||||
|
| FanEntityFeature.TURN_OFF
|
||||||
|
| FanEntityFeature.TURN_ON
|
||||||
|
)
|
||||||
|
_attr_preset_modes = AirPurifierMode.get_modes()
|
||||||
|
_attr_translation_key = "air_purifier"
|
||||||
|
_attr_name = None
|
||||||
|
_attr_is_on: bool | None = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self) -> bool | None:
|
||||||
|
"""Return true if device is on."""
|
||||||
|
return self._attr_is_on
|
||||||
|
|
||||||
|
def _set_attributes(self) -> None:
|
||||||
|
"""Set attributes from coordinator data."""
|
||||||
|
if self.coordinator.data is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._attr_is_on = self.coordinator.data.get("power") == STATE_ON.upper()
|
||||||
|
mode = self.coordinator.data.get("mode")
|
||||||
|
self._attr_preset_mode = (
|
||||||
|
AirPurifierMode(mode).name.lower() if mode is not None else None
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||||
|
"""Set the preset mode of the air purifier."""
|
||||||
|
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Switchbot air purifier to set preset mode %s %s",
|
||||||
|
preset_mode,
|
||||||
|
self._attr_unique_id,
|
||||||
|
)
|
||||||
|
await self.send_api_command(
|
||||||
|
AirPurifierCommands.SET_MODE,
|
||||||
|
parameters={"mode": AirPurifierMode[preset_mode.upper()].value},
|
||||||
|
)
|
||||||
|
await asyncio.sleep(AFTER_COMMAND_REFRESH)
|
||||||
|
await self.coordinator.async_request_refresh()
|
||||||
|
|
||||||
|
async def async_turn_on(
|
||||||
|
self,
|
||||||
|
percentage: int | None = None,
|
||||||
|
preset_mode: str | None = None,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> None:
|
||||||
|
"""Turn on the air purifier."""
|
||||||
|
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Switchbot air purifier to set turn on %s %s %s",
|
||||||
|
percentage,
|
||||||
|
preset_mode,
|
||||||
|
self._attr_unique_id,
|
||||||
|
)
|
||||||
|
await self.send_api_command(CommonCommands.ON)
|
||||||
|
await asyncio.sleep(AFTER_COMMAND_REFRESH)
|
||||||
|
await self.coordinator.async_request_refresh()
|
||||||
|
|
||||||
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn off the air purifier."""
|
||||||
|
|
||||||
|
_LOGGER.debug("Switchbot air purifier to set turn off %s", self._attr_unique_id)
|
||||||
|
await self.send_api_command(CommonCommands.OFF)
|
||||||
|
await asyncio.sleep(AFTER_COMMAND_REFRESH)
|
||||||
|
await self.coordinator.async_request_refresh()
|
||||||
|
22
homeassistant/components/switchbot_cloud/icons.json
Normal file
22
homeassistant/components/switchbot_cloud/icons.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"entity": {
|
||||||
|
"fan": {
|
||||||
|
"air_purifier": {
|
||||||
|
"default": "mdi:air-purifier",
|
||||||
|
"state": {
|
||||||
|
"off": "mdi:air-purifier-off"
|
||||||
|
},
|
||||||
|
"state_attributes": {
|
||||||
|
"preset_mode": {
|
||||||
|
"state": {
|
||||||
|
"normal": "mdi:fan",
|
||||||
|
"auto": "mdi:auto-mode",
|
||||||
|
"pet": "mdi:paw",
|
||||||
|
"sleep": "mdi:power-sleep"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -16,5 +16,21 @@
|
|||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"entity": {
|
||||||
|
"fan": {
|
||||||
|
"air_purifier": {
|
||||||
|
"state_attributes": {
|
||||||
|
"preset_mode": {
|
||||||
|
"state": {
|
||||||
|
"normal": "[%key:common::state::normal%]",
|
||||||
|
"auto": "[%key:common::state::auto%]",
|
||||||
|
"pet": "Pet",
|
||||||
|
"sleep": "Sleep"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
"""Tests for the SwitchBot Cloud integration."""
|
"""Tests for the SwitchBot Cloud integration."""
|
||||||
|
|
||||||
|
from switchbot_api import Device
|
||||||
|
|
||||||
from homeassistant.components.switchbot_cloud.const import DOMAIN
|
from homeassistant.components.switchbot_cloud.const import DOMAIN
|
||||||
from homeassistant.const import CONF_API_KEY, CONF_API_TOKEN
|
from homeassistant.const import CONF_API_KEY, CONF_API_TOKEN
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@@ -21,3 +23,20 @@ async def configure_integration(hass: HomeAssistant) -> MockConfigEntry:
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
return entry
|
return entry
|
||||||
|
|
||||||
|
|
||||||
|
AIR_PURIFIER_INFO = Device(
|
||||||
|
version="V1.0",
|
||||||
|
deviceId="air-purifier-id-1",
|
||||||
|
deviceName="air-purifier-1",
|
||||||
|
deviceType="Air Purifier Table PM2.5",
|
||||||
|
hubDeviceId="test-hub-id",
|
||||||
|
)
|
||||||
|
|
||||||
|
CIRCULATOR_FAN_INFO = Device(
|
||||||
|
version="V1.0",
|
||||||
|
deviceId="battery-fan-id-1",
|
||||||
|
deviceName="battery-fan-1",
|
||||||
|
deviceType="Battery Circulator Fan",
|
||||||
|
hubDeviceId="test-hub-id",
|
||||||
|
)
|
||||||
|
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"version": "V2.3",
|
||||||
|
"power": "ON",
|
||||||
|
"mode": 2,
|
||||||
|
"deviceId": "air-purifier-id-1",
|
||||||
|
"deviceType": "Air Purifier Table PM2.5",
|
||||||
|
"hubDeviceId": "test-hub-id"
|
||||||
|
}
|
64
tests/components/switchbot_cloud/snapshots/test_fan.ambr
Normal file
64
tests/components/switchbot_cloud/snapshots/test_fan.ambr
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# serializer version: 1
|
||||||
|
# name: test_air_purifier[fan.air_purifier_1-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'preset_modes': list([
|
||||||
|
'normal',
|
||||||
|
'auto',
|
||||||
|
'sleep',
|
||||||
|
'pet',
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'fan',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'fan.air_purifier_1',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': None,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': None,
|
||||||
|
'platform': 'switchbot_cloud',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': <FanEntityFeature: 56>,
|
||||||
|
'translation_key': 'air_purifier',
|
||||||
|
'unique_id': 'air-purifier-id-1',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_air_purifier[fan.air_purifier_1-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'friendly_name': 'air-purifier-1',
|
||||||
|
'preset_mode': 'auto',
|
||||||
|
'preset_modes': list([
|
||||||
|
'normal',
|
||||||
|
'auto',
|
||||||
|
'sleep',
|
||||||
|
'pet',
|
||||||
|
]),
|
||||||
|
'supported_features': <FanEntityFeature: 56>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'fan.air_purifier_1',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'on',
|
||||||
|
})
|
||||||
|
# ---
|
@@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import switchbot_api
|
||||||
from switchbot_api import Device, SwitchBotAPI
|
from switchbot_api import Device, SwitchBotAPI
|
||||||
|
from syrupy.assertion import SnapshotAssertion
|
||||||
|
|
||||||
from homeassistant.components.fan import (
|
from homeassistant.components.fan import (
|
||||||
ATTR_PERCENTAGE,
|
ATTR_PERCENTAGE,
|
||||||
@@ -12,6 +15,7 @@ from homeassistant.components.fan import (
|
|||||||
SERVICE_SET_PRESET_MODE,
|
SERVICE_SET_PRESET_MODE,
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
)
|
)
|
||||||
|
from homeassistant.components.switchbot_cloud.const import DOMAIN
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
@@ -19,32 +23,37 @@ from homeassistant.const import (
|
|||||||
STATE_OFF,
|
STATE_OFF,
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
STATE_UNKNOWN,
|
STATE_UNKNOWN,
|
||||||
|
Platform,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
from . import configure_integration
|
from . import AIR_PURIFIER_INFO, CIRCULATOR_FAN_INFO, configure_integration
|
||||||
|
|
||||||
|
from tests.common import async_load_json_object_fixture, snapshot_platform
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("device_info", "entry_id"),
|
||||||
|
[
|
||||||
|
(AIR_PURIFIER_INFO, "fan.air_purifier_1"),
|
||||||
|
(CIRCULATOR_FAN_INFO, "fan.battery_fan_1"),
|
||||||
|
],
|
||||||
|
)
|
||||||
async def test_coordinator_data_is_none(
|
async def test_coordinator_data_is_none(
|
||||||
hass: HomeAssistant, mock_list_devices, mock_get_status
|
hass: HomeAssistant,
|
||||||
|
mock_list_devices,
|
||||||
|
mock_get_status,
|
||||||
|
device_info: Device,
|
||||||
|
entry_id: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test coordinator data is none."""
|
"""Test coordinator data is none."""
|
||||||
mock_list_devices.return_value = [
|
mock_list_devices.return_value = [device_info]
|
||||||
Device(
|
mock_get_status.side_effect = [None]
|
||||||
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)
|
entry = await configure_integration(hass)
|
||||||
assert entry.state is ConfigEntryState.LOADED
|
assert entry.state is ConfigEntryState.LOADED
|
||||||
entity_id = "fan.battery_fan_1"
|
state = hass.states.get(entry_id)
|
||||||
state = hass.states.get(entity_id)
|
|
||||||
|
|
||||||
assert state.state == STATE_UNKNOWN
|
assert state.state == STATE_UNKNOWN
|
||||||
|
|
||||||
@@ -52,13 +61,7 @@ async def test_coordinator_data_is_none(
|
|||||||
async def test_turn_on(hass: HomeAssistant, mock_list_devices, mock_get_status) -> None:
|
async def test_turn_on(hass: HomeAssistant, mock_list_devices, mock_get_status) -> None:
|
||||||
"""Test turning on the fan."""
|
"""Test turning on the fan."""
|
||||||
mock_list_devices.return_value = [
|
mock_list_devices.return_value = [
|
||||||
Device(
|
CIRCULATOR_FAN_INFO,
|
||||||
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 = [
|
mock_get_status.side_effect = [
|
||||||
{"power": "off", "mode": "direct", "fanSpeed": "0"},
|
{"power": "off", "mode": "direct", "fanSpeed": "0"},
|
||||||
@@ -72,7 +75,9 @@ async def test_turn_on(hass: HomeAssistant, mock_list_devices, mock_get_status)
|
|||||||
|
|
||||||
assert state.state == STATE_OFF
|
assert state.state == STATE_OFF
|
||||||
|
|
||||||
with patch.object(SwitchBotAPI, "send_command") as mock_send_command:
|
with (
|
||||||
|
patch.object(SwitchBotAPI, "send_command") as mock_send_command,
|
||||||
|
):
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
FAN_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id}, blocking=True
|
FAN_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id}, blocking=True
|
||||||
)
|
)
|
||||||
@@ -87,13 +92,7 @@ async def test_turn_off(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Test turning off the fan."""
|
"""Test turning off the fan."""
|
||||||
mock_list_devices.return_value = [
|
mock_list_devices.return_value = [
|
||||||
Device(
|
CIRCULATOR_FAN_INFO,
|
||||||
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 = [
|
mock_get_status.side_effect = [
|
||||||
{"power": "on", "mode": "direct", "fanSpeed": "0"},
|
{"power": "on", "mode": "direct", "fanSpeed": "0"},
|
||||||
@@ -107,7 +106,9 @@ async def test_turn_off(
|
|||||||
|
|
||||||
assert state.state == STATE_ON
|
assert state.state == STATE_ON
|
||||||
|
|
||||||
with patch.object(SwitchBotAPI, "send_command") as mock_send_command:
|
with (
|
||||||
|
patch.object(SwitchBotAPI, "send_command") as mock_send_command,
|
||||||
|
):
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
FAN_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id}, blocking=True
|
FAN_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id}, blocking=True
|
||||||
)
|
)
|
||||||
@@ -122,13 +123,7 @@ async def test_set_percentage(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Test set percentage."""
|
"""Test set percentage."""
|
||||||
mock_list_devices.return_value = [
|
mock_list_devices.return_value = [
|
||||||
Device(
|
CIRCULATOR_FAN_INFO,
|
||||||
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 = [
|
mock_get_status.side_effect = [
|
||||||
{"power": "on", "mode": "direct", "fanSpeed": "0"},
|
{"power": "on", "mode": "direct", "fanSpeed": "0"},
|
||||||
@@ -142,7 +137,9 @@ async def test_set_percentage(
|
|||||||
|
|
||||||
assert state.state == STATE_ON
|
assert state.state == STATE_ON
|
||||||
|
|
||||||
with patch.object(SwitchBotAPI, "send_command") as mock_send_command:
|
with (
|
||||||
|
patch.object(SwitchBotAPI, "send_command") as mock_send_command,
|
||||||
|
):
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
FAN_DOMAIN,
|
FAN_DOMAIN,
|
||||||
SERVICE_SET_PERCENTAGE,
|
SERVICE_SET_PERCENTAGE,
|
||||||
@@ -157,13 +154,7 @@ async def test_set_preset_mode(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Test set preset mode."""
|
"""Test set preset mode."""
|
||||||
mock_list_devices.return_value = [
|
mock_list_devices.return_value = [
|
||||||
Device(
|
CIRCULATOR_FAN_INFO,
|
||||||
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 = [
|
mock_get_status.side_effect = [
|
||||||
{"power": "on", "mode": "direct", "fanSpeed": "0"},
|
{"power": "on", "mode": "direct", "fanSpeed": "0"},
|
||||||
@@ -177,7 +168,9 @@ async def test_set_preset_mode(
|
|||||||
|
|
||||||
assert state.state == STATE_ON
|
assert state.state == STATE_ON
|
||||||
|
|
||||||
with patch.object(SwitchBotAPI, "send_command") as mock_send_command:
|
with (
|
||||||
|
patch.object(SwitchBotAPI, "send_command") as mock_send_command,
|
||||||
|
):
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
FAN_DOMAIN,
|
FAN_DOMAIN,
|
||||||
SERVICE_SET_PRESET_MODE,
|
SERVICE_SET_PRESET_MODE,
|
||||||
@@ -185,3 +178,86 @@ async def test_set_preset_mode(
|
|||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
mock_send_command.assert_called_once()
|
mock_send_command.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_air_purifier(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
snapshot: SnapshotAssertion,
|
||||||
|
mock_list_devices,
|
||||||
|
mock_get_status,
|
||||||
|
) -> None:
|
||||||
|
"""Test air purifier."""
|
||||||
|
|
||||||
|
mock_list_devices.return_value = [AIR_PURIFIER_INFO]
|
||||||
|
mock_get_status.return_value = await async_load_json_object_fixture(
|
||||||
|
hass, "air_purifier_status.json", DOMAIN
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch("homeassistant.components.switchbot_cloud.PLATFORMS", [Platform.FAN]):
|
||||||
|
entry = await configure_integration(hass)
|
||||||
|
|
||||||
|
await snapshot_platform(hass, entity_registry, snapshot, entry.entry_id)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("service", "service_data", "expected_call_args"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"turn_on",
|
||||||
|
{},
|
||||||
|
(
|
||||||
|
"air-purifier-id-1",
|
||||||
|
switchbot_api.CommonCommands.ON,
|
||||||
|
"command",
|
||||||
|
"default",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"turn_off",
|
||||||
|
{},
|
||||||
|
(
|
||||||
|
"air-purifier-id-1",
|
||||||
|
switchbot_api.CommonCommands.OFF,
|
||||||
|
"command",
|
||||||
|
"default",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"set_preset_mode",
|
||||||
|
{"preset_mode": "sleep"},
|
||||||
|
(
|
||||||
|
"air-purifier-id-1",
|
||||||
|
switchbot_api.AirPurifierCommands.SET_MODE,
|
||||||
|
"command",
|
||||||
|
{"mode": 3},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_air_purifier_controller(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_list_devices,
|
||||||
|
mock_get_status,
|
||||||
|
service: str,
|
||||||
|
service_data: dict,
|
||||||
|
expected_call_args: tuple,
|
||||||
|
) -> None:
|
||||||
|
"""Test controlling the air purifier with mocked delay."""
|
||||||
|
mock_list_devices.return_value = [AIR_PURIFIER_INFO]
|
||||||
|
mock_get_status.return_value = {"power": "OFF", "mode": 2}
|
||||||
|
|
||||||
|
await configure_integration(hass)
|
||||||
|
fan_id = "fan.air_purifier_1"
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch.object(SwitchBotAPI, "send_command") as mocked_send_command,
|
||||||
|
):
|
||||||
|
await hass.services.async_call(
|
||||||
|
FAN_DOMAIN,
|
||||||
|
service,
|
||||||
|
{**service_data, ATTR_ENTITY_ID: fan_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
mocked_send_command.assert_awaited_once_with(*expected_call_args)
|
||||||
|
Reference in New Issue
Block a user