mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Add switchbot air purifier support (#144552)
* add support for air purifier * add unit tests for air purifier * fix aqi translation * fix aqi translation * add air purifier table * fix air purifier * remove init and add options for aqi level
This commit is contained in:
parent
49cf66269c
commit
2d5867cab6
@ -90,6 +90,8 @@ PLATFORMS_BY_TYPE = {
|
|||||||
Platform.LOCK,
|
Platform.LOCK,
|
||||||
Platform.SENSOR,
|
Platform.SENSOR,
|
||||||
],
|
],
|
||||||
|
SupportedModels.AIR_PURIFIER.value: [Platform.FAN, Platform.SENSOR],
|
||||||
|
SupportedModels.AIR_PURIFIER_TABLE.value: [Platform.FAN, Platform.SENSOR],
|
||||||
}
|
}
|
||||||
CLASS_BY_DEVICE = {
|
CLASS_BY_DEVICE = {
|
||||||
SupportedModels.CEILING_LIGHT.value: switchbot.SwitchbotCeilingLight,
|
SupportedModels.CEILING_LIGHT.value: switchbot.SwitchbotCeilingLight,
|
||||||
@ -113,6 +115,8 @@ CLASS_BY_DEVICE = {
|
|||||||
SupportedModels.K10_PRO_COMBO_VACUUM.value: switchbot.SwitchbotVacuum,
|
SupportedModels.K10_PRO_COMBO_VACUUM.value: switchbot.SwitchbotVacuum,
|
||||||
SupportedModels.LOCK_LITE.value: switchbot.SwitchbotLock,
|
SupportedModels.LOCK_LITE.value: switchbot.SwitchbotLock,
|
||||||
SupportedModels.LOCK_ULTRA.value: switchbot.SwitchbotLock,
|
SupportedModels.LOCK_ULTRA.value: switchbot.SwitchbotLock,
|
||||||
|
SupportedModels.AIR_PURIFIER.value: switchbot.SwitchbotAirPurifier,
|
||||||
|
SupportedModels.AIR_PURIFIER_TABLE.value: switchbot.SwitchbotAirPurifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,6 +46,8 @@ class SupportedModels(StrEnum):
|
|||||||
HUB3 = "hub3"
|
HUB3 = "hub3"
|
||||||
LOCK_LITE = "lock_lite"
|
LOCK_LITE = "lock_lite"
|
||||||
LOCK_ULTRA = "lock_ultra"
|
LOCK_ULTRA = "lock_ultra"
|
||||||
|
AIR_PURIFIER = "air_purifier"
|
||||||
|
AIR_PURIFIER_TABLE = "air_purifier_table"
|
||||||
|
|
||||||
|
|
||||||
CONNECTABLE_SUPPORTED_MODEL_TYPES = {
|
CONNECTABLE_SUPPORTED_MODEL_TYPES = {
|
||||||
@ -71,6 +73,8 @@ CONNECTABLE_SUPPORTED_MODEL_TYPES = {
|
|||||||
SwitchbotModel.K10_PRO_COMBO_VACUUM: SupportedModels.K10_PRO_COMBO_VACUUM,
|
SwitchbotModel.K10_PRO_COMBO_VACUUM: SupportedModels.K10_PRO_COMBO_VACUUM,
|
||||||
SwitchbotModel.LOCK_LITE: SupportedModels.LOCK_LITE,
|
SwitchbotModel.LOCK_LITE: SupportedModels.LOCK_LITE,
|
||||||
SwitchbotModel.LOCK_ULTRA: SupportedModels.LOCK_ULTRA,
|
SwitchbotModel.LOCK_ULTRA: SupportedModels.LOCK_ULTRA,
|
||||||
|
SwitchbotModel.AIR_PURIFIER: SupportedModels.AIR_PURIFIER,
|
||||||
|
SwitchbotModel.AIR_PURIFIER_TABLE: SupportedModels.AIR_PURIFIER_TABLE,
|
||||||
}
|
}
|
||||||
|
|
||||||
NON_CONNECTABLE_SUPPORTED_MODEL_TYPES = {
|
NON_CONNECTABLE_SUPPORTED_MODEL_TYPES = {
|
||||||
@ -97,6 +101,8 @@ ENCRYPTED_MODELS = {
|
|||||||
SwitchbotModel.LOCK_PRO,
|
SwitchbotModel.LOCK_PRO,
|
||||||
SwitchbotModel.LOCK_LITE,
|
SwitchbotModel.LOCK_LITE,
|
||||||
SwitchbotModel.LOCK_ULTRA,
|
SwitchbotModel.LOCK_ULTRA,
|
||||||
|
SwitchbotModel.AIR_PURIFIER,
|
||||||
|
SwitchbotModel.AIR_PURIFIER_TABLE,
|
||||||
}
|
}
|
||||||
|
|
||||||
ENCRYPTED_SWITCHBOT_MODEL_TO_CLASS: dict[
|
ENCRYPTED_SWITCHBOT_MODEL_TO_CLASS: dict[
|
||||||
@ -108,6 +114,8 @@ ENCRYPTED_SWITCHBOT_MODEL_TO_CLASS: dict[
|
|||||||
SwitchbotModel.RELAY_SWITCH_1: switchbot.SwitchbotRelaySwitch,
|
SwitchbotModel.RELAY_SWITCH_1: switchbot.SwitchbotRelaySwitch,
|
||||||
SwitchbotModel.LOCK_LITE: switchbot.SwitchbotLock,
|
SwitchbotModel.LOCK_LITE: switchbot.SwitchbotLock,
|
||||||
SwitchbotModel.LOCK_ULTRA: switchbot.SwitchbotLock,
|
SwitchbotModel.LOCK_ULTRA: switchbot.SwitchbotLock,
|
||||||
|
SwitchbotModel.AIR_PURIFIER: switchbot.SwitchbotAirPurifier,
|
||||||
|
SwitchbotModel.AIR_PURIFIER_TABLE: switchbot.SwitchbotAirPurifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
HASS_SENSOR_TYPE_TO_SWITCHBOT_MODEL = {
|
HASS_SENSOR_TYPE_TO_SWITCHBOT_MODEL = {
|
||||||
|
@ -6,7 +6,7 @@ import logging
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import switchbot
|
import switchbot
|
||||||
from switchbot import FanMode
|
from switchbot import AirPurifierMode, FanMode
|
||||||
|
|
||||||
from homeassistant.components.fan import FanEntity, FanEntityFeature
|
from homeassistant.components.fan import FanEntity, FanEntityFeature
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@ -14,7 +14,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
|||||||
from homeassistant.helpers.restore_state import RestoreEntity
|
from homeassistant.helpers.restore_state import RestoreEntity
|
||||||
|
|
||||||
from .coordinator import SwitchbotConfigEntry, SwitchbotDataUpdateCoordinator
|
from .coordinator import SwitchbotConfigEntry, SwitchbotDataUpdateCoordinator
|
||||||
from .entity import SwitchbotEntity
|
from .entity import SwitchbotEntity, exception_handler
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
PARALLEL_UPDATES = 0
|
PARALLEL_UPDATES = 0
|
||||||
@ -27,7 +27,10 @@ async def async_setup_entry(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Switchbot fan based on a config entry."""
|
"""Set up Switchbot fan based on a config entry."""
|
||||||
coordinator = entry.runtime_data
|
coordinator = entry.runtime_data
|
||||||
async_add_entities([SwitchBotFanEntity(coordinator)])
|
if isinstance(coordinator.device, switchbot.SwitchbotAirPurifier):
|
||||||
|
async_add_entities([SwitchBotAirPurifierEntity(coordinator)])
|
||||||
|
else:
|
||||||
|
async_add_entities([SwitchBotFanEntity(coordinator)])
|
||||||
|
|
||||||
|
|
||||||
class SwitchBotFanEntity(SwitchbotEntity, FanEntity, RestoreEntity):
|
class SwitchBotFanEntity(SwitchbotEntity, FanEntity, RestoreEntity):
|
||||||
@ -120,3 +123,65 @@ class SwitchBotFanEntity(SwitchbotEntity, FanEntity, RestoreEntity):
|
|||||||
_LOGGER.debug("Switchbot fan to set turn off %s", self._address)
|
_LOGGER.debug("Switchbot fan to set turn off %s", self._address)
|
||||||
self._last_run_success = bool(await self._device.turn_off())
|
self._last_run_success = bool(await self._device.turn_off())
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
|
||||||
|
class SwitchBotAirPurifierEntity(SwitchbotEntity, FanEntity):
|
||||||
|
"""Representation of a Switchbot air purifier."""
|
||||||
|
|
||||||
|
_device: switchbot.SwitchbotAirPurifier
|
||||||
|
_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
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self) -> bool | None:
|
||||||
|
"""Return true if device is on."""
|
||||||
|
return self._device.is_on()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def preset_mode(self) -> str | None:
|
||||||
|
"""Return the current preset mode."""
|
||||||
|
return self._device.get_current_mode()
|
||||||
|
|
||||||
|
@exception_handler
|
||||||
|
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._address,
|
||||||
|
)
|
||||||
|
self._last_run_success = bool(await self._device.set_preset_mode(preset_mode))
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
@exception_handler
|
||||||
|
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._address,
|
||||||
|
)
|
||||||
|
self._last_run_success = bool(await self._device.turn_on())
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
@exception_handler
|
||||||
|
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._address)
|
||||||
|
self._last_run_success = bool(await self._device.turn_off())
|
||||||
|
self.async_write_ha_state()
|
||||||
|
@ -12,6 +12,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"air_purifier": {
|
||||||
|
"default": "mdi:air-purifier",
|
||||||
|
"state": {
|
||||||
|
"off": "mdi:air-purifier-off"
|
||||||
|
},
|
||||||
|
"state_attributes": {
|
||||||
|
"preset_mode": {
|
||||||
|
"state": {
|
||||||
|
"level_1": "mdi:fan-speed-1",
|
||||||
|
"level_2": "mdi:fan-speed-2",
|
||||||
|
"level_3": "mdi:fan-speed-3",
|
||||||
|
"auto": "mdi:auto-mode",
|
||||||
|
"pet": "mdi:paw",
|
||||||
|
"sleep": "mdi:power-sleep"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from switchbot.const.air_purifier import AirQualityLevel
|
||||||
|
|
||||||
from homeassistant.components.bluetooth import async_last_service_info
|
from homeassistant.components.bluetooth import async_last_service_info
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
SensorDeviceClass,
|
SensorDeviceClass,
|
||||||
@ -102,6 +104,12 @@ SENSOR_TYPES: dict[str, SensorEntityDescription] = {
|
|||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
device_class=SensorDeviceClass.VOLTAGE,
|
device_class=SensorDeviceClass.VOLTAGE,
|
||||||
),
|
),
|
||||||
|
"aqi_level": SensorEntityDescription(
|
||||||
|
key="aqi_level",
|
||||||
|
translation_key="aqi_quality_level",
|
||||||
|
device_class=SensorDeviceClass.ENUM,
|
||||||
|
options=[member.name.lower() for member in AirQualityLevel],
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,6 +105,15 @@
|
|||||||
},
|
},
|
||||||
"light_level": {
|
"light_level": {
|
||||||
"name": "Light level"
|
"name": "Light level"
|
||||||
|
},
|
||||||
|
"aqi_quality_level": {
|
||||||
|
"name": "Air quality level",
|
||||||
|
"state": {
|
||||||
|
"excellent": "Excellent",
|
||||||
|
"good": "Good",
|
||||||
|
"moderate": "Moderate",
|
||||||
|
"unhealthy": "Unhealthy"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cover": {
|
"cover": {
|
||||||
@ -179,6 +188,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"air_purifier": {
|
||||||
|
"state_attributes": {
|
||||||
|
"last_run_success": {
|
||||||
|
"state": {
|
||||||
|
"true": "[%key:component::binary_sensor::entity_component::problem::state::off%]",
|
||||||
|
"false": "[%key:component::binary_sensor::entity_component::problem::state::on%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"preset_mode": {
|
||||||
|
"state": {
|
||||||
|
"level_1": "Level 1",
|
||||||
|
"level_2": "Level 2",
|
||||||
|
"level_3": "Level 3",
|
||||||
|
"auto": "[%key:common::state::auto%]",
|
||||||
|
"pet": "Pet",
|
||||||
|
"sleep": "Sleep"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vacuum": {
|
"vacuum": {
|
||||||
|
@ -759,3 +759,103 @@ LOCK_ULTRA_SERVICE_INFO = BluetoothServiceInfoBleak(
|
|||||||
connectable=True,
|
connectable=True,
|
||||||
tx_power=-127,
|
tx_power=-127,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
AIR_PURIFIER_TBALE_PM25_SERVICE_INFO = BluetoothServiceInfoBleak(
|
||||||
|
name="Air Purifier Table PM25",
|
||||||
|
manufacturer_data={
|
||||||
|
2409: b"\xf0\x9e\x9e\x96j\xd6\xa1\x81\x88\xe4\x00\x01\x95\x00\x00",
|
||||||
|
},
|
||||||
|
service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"7\x00\x00\x95-\x00"},
|
||||||
|
service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"],
|
||||||
|
address="AA:BB:CC:DD:EE:FF",
|
||||||
|
rssi=-60,
|
||||||
|
source="local",
|
||||||
|
advertisement=generate_advertisement_data(
|
||||||
|
local_name="Air Purifier Table PM25",
|
||||||
|
manufacturer_data={
|
||||||
|
2409: b"\xf0\x9e\x9e\x96j\xd6\xa1\x81\x88\xe4\x00\x01\x95\x00\x00",
|
||||||
|
},
|
||||||
|
service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"7\x00\x00\x95-\x00"},
|
||||||
|
service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"],
|
||||||
|
),
|
||||||
|
device=generate_ble_device("AA:BB:CC:DD:EE:FF", "Air Purifier Table PM25"),
|
||||||
|
time=0,
|
||||||
|
connectable=True,
|
||||||
|
tx_power=-127,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
AIR_PURIFIER_PM25_SERVICE_INFO = BluetoothServiceInfoBleak(
|
||||||
|
name="Air Purifier PM25",
|
||||||
|
manufacturer_data={
|
||||||
|
2409: b'\xcc\x8d\xa2\xa7\x92>\t"\x80\x000\x00\x0f\x00\x00',
|
||||||
|
},
|
||||||
|
service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"*\x00\x00\x15\x04\x00"},
|
||||||
|
service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"],
|
||||||
|
address="AA:BB:CC:DD:EE:FF",
|
||||||
|
rssi=-60,
|
||||||
|
source="local",
|
||||||
|
advertisement=generate_advertisement_data(
|
||||||
|
local_name="Air Purifier PM25",
|
||||||
|
manufacturer_data={
|
||||||
|
2409: b'\xcc\x8d\xa2\xa7\x92>\t"\x80\x000\x00\x0f\x00\x00',
|
||||||
|
},
|
||||||
|
service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"*\x00\x00\x15\x04\x00"},
|
||||||
|
service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"],
|
||||||
|
),
|
||||||
|
device=generate_ble_device("AA:BB:CC:DD:EE:FF", "Air Purifier PM25"),
|
||||||
|
time=0,
|
||||||
|
connectable=True,
|
||||||
|
tx_power=-127,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
AIR_PURIFIER_VOC_SERVICE_INFO = BluetoothServiceInfoBleak(
|
||||||
|
name="Air Purifier VOC",
|
||||||
|
manufacturer_data={
|
||||||
|
2409: b"\xcc\x8d\xa2\xa7\xe4\xa6\x0b\x83\x88d\x00\xea`\x00\x00",
|
||||||
|
},
|
||||||
|
service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"+\x00\x00\x15\x04\x00"},
|
||||||
|
service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"],
|
||||||
|
address="AA:BB:CC:DD:EE:FF",
|
||||||
|
rssi=-60,
|
||||||
|
source="local",
|
||||||
|
advertisement=generate_advertisement_data(
|
||||||
|
local_name="Air Purifier VOC",
|
||||||
|
manufacturer_data={
|
||||||
|
2409: b"\xcc\x8d\xa2\xa7\xe4\xa6\x0b\x83\x88d\x00\xea`\x00\x00",
|
||||||
|
},
|
||||||
|
service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"+\x00\x00\x15\x04\x00"},
|
||||||
|
service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"],
|
||||||
|
),
|
||||||
|
device=generate_ble_device("AA:BB:CC:DD:EE:FF", "Air Purifier VOC"),
|
||||||
|
time=0,
|
||||||
|
connectable=True,
|
||||||
|
tx_power=-127,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
AIR_PURIFIER_TABLE_VOC_SERVICE_INFO = BluetoothServiceInfoBleak(
|
||||||
|
name="Air Purifier Table VOC",
|
||||||
|
manufacturer_data={
|
||||||
|
2409: b"\xcc\x8d\xa2\xa7\xc1\xae\x9b\x81\x8c\xb2\x00\x01\x94\x00\x00",
|
||||||
|
},
|
||||||
|
service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"8\x00\x00\x95-\x00"},
|
||||||
|
service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"],
|
||||||
|
address="AA:BB:CC:DD:EE:FF",
|
||||||
|
rssi=-60,
|
||||||
|
source="local",
|
||||||
|
advertisement=generate_advertisement_data(
|
||||||
|
local_name="Air Purifier Table VOC",
|
||||||
|
manufacturer_data={
|
||||||
|
2409: b"\xcc\x8d\xa2\xa7\xc1\xae\x9b\x81\x8c\xb2\x00\x01\x94\x00\x00",
|
||||||
|
},
|
||||||
|
service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"8\x00\x00\x95-\x00"},
|
||||||
|
service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"],
|
||||||
|
),
|
||||||
|
device=generate_ble_device("AA:BB:CC:DD:EE:FF", "Air Purifier Table VOC"),
|
||||||
|
time=0,
|
||||||
|
connectable=True,
|
||||||
|
tx_power=-127,
|
||||||
|
)
|
||||||
|
@ -4,7 +4,9 @@ from collections.abc import Callable
|
|||||||
from unittest.mock import AsyncMock, patch
|
from unittest.mock import AsyncMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from switchbot.devices.device import SwitchbotOperationError
|
||||||
|
|
||||||
|
from homeassistant.components.bluetooth import BluetoothServiceInfoBleak
|
||||||
from homeassistant.components.fan import (
|
from homeassistant.components.fan import (
|
||||||
ATTR_OSCILLATING,
|
ATTR_OSCILLATING,
|
||||||
ATTR_PERCENTAGE,
|
ATTR_PERCENTAGE,
|
||||||
@ -16,8 +18,15 @@ from homeassistant.components.fan import (
|
|||||||
)
|
)
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON
|
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
|
||||||
from . import CIRCULATOR_FAN_SERVICE_INFO
|
from . import (
|
||||||
|
AIR_PURIFIER_PM25_SERVICE_INFO,
|
||||||
|
AIR_PURIFIER_TABLE_VOC_SERVICE_INFO,
|
||||||
|
AIR_PURIFIER_TBALE_PM25_SERVICE_INFO,
|
||||||
|
AIR_PURIFIER_VOC_SERVICE_INFO,
|
||||||
|
CIRCULATOR_FAN_SERVICE_INFO,
|
||||||
|
)
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
from tests.components.bluetooth import inject_bluetooth_service_info
|
from tests.components.bluetooth import inject_bluetooth_service_info
|
||||||
@ -89,3 +98,132 @@ async def test_circulator_fan_controlling(
|
|||||||
)
|
)
|
||||||
|
|
||||||
mocked_instance.assert_awaited_once()
|
mocked_instance.assert_awaited_once()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("service_info", "sensor_type"),
|
||||||
|
[
|
||||||
|
(AIR_PURIFIER_VOC_SERVICE_INFO, "air_purifier"),
|
||||||
|
(AIR_PURIFIER_TABLE_VOC_SERVICE_INFO, "air_purifier_table"),
|
||||||
|
(AIR_PURIFIER_PM25_SERVICE_INFO, "air_purifier"),
|
||||||
|
(AIR_PURIFIER_TBALE_PM25_SERVICE_INFO, "air_purifier_table"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("service", "service_data", "mock_method"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
SERVICE_SET_PRESET_MODE,
|
||||||
|
{ATTR_PRESET_MODE: "sleep"},
|
||||||
|
"set_preset_mode",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
SERVICE_TURN_OFF,
|
||||||
|
{},
|
||||||
|
"turn_off",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
SERVICE_TURN_ON,
|
||||||
|
{},
|
||||||
|
"turn_on",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_air_purifier_controlling(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_entry_encrypted_factory: Callable[[str], MockConfigEntry],
|
||||||
|
service_info: BluetoothServiceInfoBleak,
|
||||||
|
sensor_type: str,
|
||||||
|
service: str,
|
||||||
|
service_data: dict,
|
||||||
|
mock_method: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test controlling the air purifier with different services."""
|
||||||
|
inject_bluetooth_service_info(hass, service_info)
|
||||||
|
|
||||||
|
entry = mock_entry_encrypted_factory(sensor_type)
|
||||||
|
entity_id = "fan.test_name"
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
mocked_instance = AsyncMock(return_value=True)
|
||||||
|
mcoked_none_instance = AsyncMock(return_value=None)
|
||||||
|
with patch.multiple(
|
||||||
|
"homeassistant.components.switchbot.fan.switchbot.SwitchbotAirPurifier",
|
||||||
|
get_basic_info=mcoked_none_instance,
|
||||||
|
update=mcoked_none_instance,
|
||||||
|
**{mock_method: mocked_instance},
|
||||||
|
):
|
||||||
|
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
FAN_DOMAIN,
|
||||||
|
service,
|
||||||
|
{**service_data, ATTR_ENTITY_ID: entity_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
mocked_instance.assert_awaited_once()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("service_info", "sensor_type"),
|
||||||
|
[
|
||||||
|
(AIR_PURIFIER_VOC_SERVICE_INFO, "air_purifier"),
|
||||||
|
(AIR_PURIFIER_TABLE_VOC_SERVICE_INFO, "air_purifier_table"),
|
||||||
|
(AIR_PURIFIER_PM25_SERVICE_INFO, "air_purifier"),
|
||||||
|
(AIR_PURIFIER_TBALE_PM25_SERVICE_INFO, "air_purifier_table"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("service", "service_data", "mock_method"),
|
||||||
|
[
|
||||||
|
(SERVICE_SET_PRESET_MODE, {ATTR_PRESET_MODE: "sleep"}, "set_preset_mode"),
|
||||||
|
(SERVICE_TURN_OFF, {}, "turn_off"),
|
||||||
|
(SERVICE_TURN_ON, {}, "turn_on"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("exception", "error_message"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
SwitchbotOperationError("Operation failed"),
|
||||||
|
"An error occurred while performing the action: Operation failed",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_exception_handling_air_purifier_service(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_entry_encrypted_factory: Callable[[str], MockConfigEntry],
|
||||||
|
service_info: BluetoothServiceInfoBleak,
|
||||||
|
sensor_type: str,
|
||||||
|
service: str,
|
||||||
|
service_data: dict,
|
||||||
|
mock_method: str,
|
||||||
|
exception: Exception,
|
||||||
|
error_message: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test exception handling for air purifier service with exception."""
|
||||||
|
inject_bluetooth_service_info(hass, service_info)
|
||||||
|
|
||||||
|
entry = mock_entry_encrypted_factory(sensor_type)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
entity_id = "fan.test_name"
|
||||||
|
|
||||||
|
mcoked_none_instance = AsyncMock(return_value=None)
|
||||||
|
with patch.multiple(
|
||||||
|
"homeassistant.components.switchbot.fan.switchbot.SwitchbotAirPurifier",
|
||||||
|
get_basic_info=mcoked_none_instance,
|
||||||
|
update=mcoked_none_instance,
|
||||||
|
**{mock_method: AsyncMock(side_effect=exception)},
|
||||||
|
):
|
||||||
|
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
with pytest.raises(HomeAssistantError, match=error_message):
|
||||||
|
await hass.services.async_call(
|
||||||
|
FAN_DOMAIN,
|
||||||
|
service,
|
||||||
|
{**service_data, ATTR_ENTITY_ID: entity_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user