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.SENSOR,
|
||||
],
|
||||
SupportedModels.AIR_PURIFIER.value: [Platform.FAN, Platform.SENSOR],
|
||||
SupportedModels.AIR_PURIFIER_TABLE.value: [Platform.FAN, Platform.SENSOR],
|
||||
}
|
||||
CLASS_BY_DEVICE = {
|
||||
SupportedModels.CEILING_LIGHT.value: switchbot.SwitchbotCeilingLight,
|
||||
@ -113,6 +115,8 @@ CLASS_BY_DEVICE = {
|
||||
SupportedModels.K10_PRO_COMBO_VACUUM.value: switchbot.SwitchbotVacuum,
|
||||
SupportedModels.LOCK_LITE.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"
|
||||
LOCK_LITE = "lock_lite"
|
||||
LOCK_ULTRA = "lock_ultra"
|
||||
AIR_PURIFIER = "air_purifier"
|
||||
AIR_PURIFIER_TABLE = "air_purifier_table"
|
||||
|
||||
|
||||
CONNECTABLE_SUPPORTED_MODEL_TYPES = {
|
||||
@ -71,6 +73,8 @@ CONNECTABLE_SUPPORTED_MODEL_TYPES = {
|
||||
SwitchbotModel.K10_PRO_COMBO_VACUUM: SupportedModels.K10_PRO_COMBO_VACUUM,
|
||||
SwitchbotModel.LOCK_LITE: SupportedModels.LOCK_LITE,
|
||||
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 = {
|
||||
@ -97,6 +101,8 @@ ENCRYPTED_MODELS = {
|
||||
SwitchbotModel.LOCK_PRO,
|
||||
SwitchbotModel.LOCK_LITE,
|
||||
SwitchbotModel.LOCK_ULTRA,
|
||||
SwitchbotModel.AIR_PURIFIER,
|
||||
SwitchbotModel.AIR_PURIFIER_TABLE,
|
||||
}
|
||||
|
||||
ENCRYPTED_SWITCHBOT_MODEL_TO_CLASS: dict[
|
||||
@ -108,6 +114,8 @@ ENCRYPTED_SWITCHBOT_MODEL_TO_CLASS: dict[
|
||||
SwitchbotModel.RELAY_SWITCH_1: switchbot.SwitchbotRelaySwitch,
|
||||
SwitchbotModel.LOCK_LITE: switchbot.SwitchbotLock,
|
||||
SwitchbotModel.LOCK_ULTRA: switchbot.SwitchbotLock,
|
||||
SwitchbotModel.AIR_PURIFIER: switchbot.SwitchbotAirPurifier,
|
||||
SwitchbotModel.AIR_PURIFIER_TABLE: switchbot.SwitchbotAirPurifier,
|
||||
}
|
||||
|
||||
HASS_SENSOR_TYPE_TO_SWITCHBOT_MODEL = {
|
||||
|
@ -6,7 +6,7 @@ import logging
|
||||
from typing import Any
|
||||
|
||||
import switchbot
|
||||
from switchbot import FanMode
|
||||
from switchbot import AirPurifierMode, FanMode
|
||||
|
||||
from homeassistant.components.fan import FanEntity, FanEntityFeature
|
||||
from homeassistant.core import HomeAssistant
|
||||
@ -14,7 +14,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
|
||||
from .coordinator import SwitchbotConfigEntry, SwitchbotDataUpdateCoordinator
|
||||
from .entity import SwitchbotEntity
|
||||
from .entity import SwitchbotEntity, exception_handler
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
PARALLEL_UPDATES = 0
|
||||
@ -27,7 +27,10 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Set up Switchbot fan based on a config entry."""
|
||||
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):
|
||||
@ -120,3 +123,65 @@ class SwitchBotFanEntity(SwitchbotEntity, FanEntity, RestoreEntity):
|
||||
_LOGGER.debug("Switchbot fan to set turn off %s", self._address)
|
||||
self._last_run_success = bool(await self._device.turn_off())
|
||||
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 switchbot.const.air_purifier import AirQualityLevel
|
||||
|
||||
from homeassistant.components.bluetooth import async_last_service_info
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
@ -102,6 +104,12 @@ SENSOR_TYPES: dict[str, SensorEntityDescription] = {
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
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": {
|
||||
"name": "Light level"
|
||||
},
|
||||
"aqi_quality_level": {
|
||||
"name": "Air quality level",
|
||||
"state": {
|
||||
"excellent": "Excellent",
|
||||
"good": "Good",
|
||||
"moderate": "Moderate",
|
||||
"unhealthy": "Unhealthy"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
|
@ -759,3 +759,103 @@ LOCK_ULTRA_SERVICE_INFO = BluetoothServiceInfoBleak(
|
||||
connectable=True,
|
||||
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
|
||||
|
||||
import pytest
|
||||
from switchbot.devices.device import SwitchbotOperationError
|
||||
|
||||
from homeassistant.components.bluetooth import BluetoothServiceInfoBleak
|
||||
from homeassistant.components.fan import (
|
||||
ATTR_OSCILLATING,
|
||||
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.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.components.bluetooth import inject_bluetooth_service_info
|
||||
@ -89,3 +98,132 @@ async def test_circulator_fan_controlling(
|
||||
)
|
||||
|
||||
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