Add effect mode support for switchbot light (#147326)

* add support for strip light3 and floor lamp

* clear the color mode

* add led unit test

* use property for effect

* fix color mode issue

* remove new products

* fix adv data

* adjust log level

* add translation and icon
This commit is contained in:
Retha Runolfsson 2025-06-25 20:45:07 +08:00 committed by GitHub
parent 8393f17bb3
commit c5f8acfe93
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 420 additions and 144 deletions

View File

@ -60,6 +60,35 @@
} }
} }
} }
},
"light": {
"light": {
"state_attributes": {
"effect": {
"state": {
"christmas": "mdi:string-lights",
"halloween": "mdi:halloween",
"sunset": "mdi:weather-sunset",
"vitality": "mdi:parachute",
"flashing": "mdi:flash",
"strobe": "mdi:led-strip-variant",
"fade": "mdi:water-opacity",
"smooth": "mdi:led-strip-variant",
"forest": "mdi:forest",
"ocean": "mdi:waves",
"autumn": "mdi:leaf-maple",
"cool": "mdi:emoticon-cool-outline",
"flow": "mdi:pulse",
"relax": "mdi:coffee",
"modern": "mdi:school-outline",
"rose": "mdi:flower",
"colorful": "mdi:looks",
"flickering": "mdi:led-strip-variant",
"breathing": "mdi:heart-pulse"
}
}
}
}
} }
} }
} }

View File

@ -2,6 +2,7 @@
from __future__ import annotations from __future__ import annotations
import logging
from typing import Any, cast from typing import Any, cast
import switchbot import switchbot
@ -10,14 +11,16 @@ from switchbot import ColorMode as SwitchBotColorMode
from homeassistant.components.light import ( from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP_KELVIN, ATTR_COLOR_TEMP_KELVIN,
ATTR_EFFECT,
ATTR_RGB_COLOR, ATTR_RGB_COLOR,
ColorMode, ColorMode,
LightEntity, LightEntity,
LightEntityFeature,
) )
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import SwitchbotConfigEntry, SwitchbotDataUpdateCoordinator from .coordinator import SwitchbotConfigEntry
from .entity import SwitchbotEntity, exception_handler from .entity import SwitchbotEntity, exception_handler
SWITCHBOT_COLOR_MODE_TO_HASS = { SWITCHBOT_COLOR_MODE_TO_HASS = {
@ -25,6 +28,7 @@ SWITCHBOT_COLOR_MODE_TO_HASS = {
SwitchBotColorMode.COLOR_TEMP: ColorMode.COLOR_TEMP, SwitchBotColorMode.COLOR_TEMP: ColorMode.COLOR_TEMP,
} }
_LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 0 PARALLEL_UPDATES = 0
@ -42,34 +46,69 @@ class SwitchbotLightEntity(SwitchbotEntity, LightEntity):
_device: switchbot.SwitchbotBaseLight _device: switchbot.SwitchbotBaseLight
_attr_name = None _attr_name = None
_attr_translation_key = "light"
def __init__(self, coordinator: SwitchbotDataUpdateCoordinator) -> None: @property
"""Initialize the Switchbot light.""" def max_color_temp_kelvin(self) -> int:
super().__init__(coordinator) """Return the max color temperature."""
device = self._device return self._device.max_temp
self._attr_max_color_temp_kelvin = device.max_temp
self._attr_min_color_temp_kelvin = device.min_temp
self._attr_supported_color_modes = {
SWITCHBOT_COLOR_MODE_TO_HASS[mode] for mode in device.color_modes
}
self._async_update_attrs()
@callback @property
def _async_update_attrs(self) -> None: def min_color_temp_kelvin(self) -> int:
"""Handle updating _attr values.""" """Return the min color temperature."""
device = self._device return self._device.min_temp
self._attr_is_on = self._device.on
self._attr_brightness = max(0, min(255, round(device.brightness * 2.55))) @property
if device.color_mode == SwitchBotColorMode.COLOR_TEMP: def supported_color_modes(self) -> set[ColorMode]:
self._attr_color_temp_kelvin = device.color_temp """Return the supported color modes."""
self._attr_color_mode = ColorMode.COLOR_TEMP return {SWITCHBOT_COLOR_MODE_TO_HASS[mode] for mode in self._device.color_modes}
return
self._attr_rgb_color = device.rgb @property
self._attr_color_mode = ColorMode.RGB def supported_features(self) -> LightEntityFeature:
"""Return the supported features."""
return LightEntityFeature.EFFECT if self.effect_list else LightEntityFeature(0)
@property
def brightness(self) -> int | None:
"""Return the brightness of the light."""
return max(0, min(255, round(self._device.brightness * 2.55)))
@property
def color_mode(self) -> ColorMode | None:
"""Return the color mode of the light."""
return SWITCHBOT_COLOR_MODE_TO_HASS.get(
self._device.color_mode, ColorMode.UNKNOWN
)
@property
def effect_list(self) -> list[str] | None:
"""Return the list of effects supported by the light."""
return self._device.get_effect_list
@property
def effect(self) -> str | None:
"""Return the current effect of the light."""
return self._device.get_effect()
@property
def rgb_color(self) -> tuple[int, int, int] | None:
"""Return the RGB color of the light."""
return self._device.rgb
@property
def color_temp_kelvin(self) -> int | None:
"""Return the color temperature of the light."""
return self._device.color_temp
@property
def is_on(self) -> bool:
"""Return true if the light is on."""
return self._device.on
@exception_handler @exception_handler
async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None:
"""Instruct the light to turn on.""" """Instruct the light to turn on."""
_LOGGER.debug("Turning on light %s, address %s", kwargs, self._address)
brightness = round( brightness = round(
cast(int, kwargs.get(ATTR_BRIGHTNESS, self.brightness)) / 255 * 100 cast(int, kwargs.get(ATTR_BRIGHTNESS, self.brightness)) / 255 * 100
) )
@ -82,6 +121,10 @@ class SwitchbotLightEntity(SwitchbotEntity, LightEntity):
kelvin = max(2700, min(6500, kwargs[ATTR_COLOR_TEMP_KELVIN])) kelvin = max(2700, min(6500, kwargs[ATTR_COLOR_TEMP_KELVIN]))
await self._device.set_color_temp(brightness, kelvin) await self._device.set_color_temp(brightness, kelvin)
return return
if ATTR_EFFECT in kwargs:
effect = kwargs[ATTR_EFFECT]
await self._device.set_effect(effect)
return
if ATTR_RGB_COLOR in kwargs: if ATTR_RGB_COLOR in kwargs:
rgb = kwargs[ATTR_RGB_COLOR] rgb = kwargs[ATTR_RGB_COLOR]
await self._device.set_rgb(brightness, rgb[0], rgb[1], rgb[2]) await self._device.set_rgb(brightness, rgb[0], rgb[1], rgb[2])
@ -94,4 +137,5 @@ class SwitchbotLightEntity(SwitchbotEntity, LightEntity):
@exception_handler @exception_handler
async def async_turn_off(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None:
"""Instruct the light to turn off.""" """Instruct the light to turn off."""
_LOGGER.debug("Turning off light %s, address %s", kwargs, self._address)
await self._device.turn_off() await self._device.turn_off()

View File

@ -246,6 +246,35 @@
} }
} }
} }
},
"light": {
"light": {
"state_attributes": {
"effect": {
"state": {
"christmas": "Christmas",
"halloween": "Halloween",
"sunset": "Sunset",
"vitality": "Vitality",
"flashing": "Flashing",
"strobe": "Strobe",
"fade": "Fade",
"smooth": "Smooth",
"forest": "Forest",
"ocean": "Ocean",
"autumn": "Autumn",
"cool": "Cool",
"flow": "Flow",
"relax": "Relax",
"modern": "Modern",
"rose": "Rose",
"colorful": "Colorful",
"flickering": "Flickering",
"breathing": "Breathing"
}
}
}
}
} }
}, },
"exceptions": { "exceptions": {

View File

@ -883,3 +883,61 @@ EVAPORATIVE_HUMIDIFIER_SERVICE_INFO = BluetoothServiceInfoBleak(
connectable=True, connectable=True,
tx_power=-127, tx_power=-127,
) )
BULB_SERVICE_INFO = BluetoothServiceInfoBleak(
name="Bulb",
manufacturer_data={
2409: b"@L\xca\xa7_\x12\x02\x81\x12\x00\x00",
},
service_data={
"0000fd3d-0000-1000-8000-00805f9b34fb": b"u\x00d",
},
service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"],
address="AA:BB:CC:DD:EE:FF",
rssi=-60,
source="local",
advertisement=generate_advertisement_data(
local_name="Bulb",
manufacturer_data={
2409: b"@L\xca\xa7_\x12\x02\x81\x12\x00\x00",
},
service_data={
"0000fd3d-0000-1000-8000-00805f9b34fb": b"u\x00d",
},
service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"],
),
device=generate_ble_device("AA:BB:CC:DD:EE:FF", "Bulb"),
time=0,
connectable=True,
tx_power=-127,
)
CEILING_LIGHT_SERVICE_INFO = BluetoothServiceInfoBleak(
name="Ceiling Light",
manufacturer_data={
2409: b"\xef\xfe\xfb\x9d\x10\xfe\n\x01\x18\xf3\xa4",
},
service_data={
"0000fd3d-0000-1000-8000-00805f9b34fb": b"q\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="Ceiling Light",
manufacturer_data={
2409: b"\xef\xfe\xfb\x9d\x10\xfe\n\x01\x18\xf3$",
},
service_data={
"0000fd3d-0000-1000-8000-00805f9b34fb": b"q\x00",
},
service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"],
),
device=generate_ble_device("AA:BB:CC:DD:EE:FF", "Ceiling Light"),
time=0,
connectable=True,
tx_power=-127,
)

View File

@ -5,12 +5,12 @@ from typing import Any
from unittest.mock import AsyncMock, patch from unittest.mock import AsyncMock, patch
import pytest import pytest
from switchbot import ColorMode as switchbotColorMode
from switchbot.devices.device import SwitchbotOperationError from switchbot.devices.device import SwitchbotOperationError
from homeassistant.components.light import ( from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP_KELVIN, ATTR_COLOR_TEMP_KELVIN,
ATTR_EFFECT,
ATTR_RGB_COLOR, ATTR_RGB_COLOR,
DOMAIN as LIGHT_DOMAIN, DOMAIN as LIGHT_DOMAIN,
SERVICE_TURN_OFF, SERVICE_TURN_OFF,
@ -20,89 +20,111 @@ from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from . import WOSTRIP_SERVICE_INFO from . import BULB_SERVICE_INFO, CEILING_LIGHT_SERVICE_INFO, WOSTRIP_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
COMMON_PARAMETERS = (
@pytest.mark.parametrize( "service",
( "service_data",
"service", "mock_method",
"service_data", "expected_args",
"mock_method", )
"expected_args", TURN_ON_PARAMETERS = (
"color_modes", SERVICE_TURN_ON,
"color_mode", {},
), "turn_on",
{},
)
TURN_OFF_PARAMETERS = (
SERVICE_TURN_OFF,
{},
"turn_off",
{},
)
SET_BRIGHTNESS_PARAMETERS = (
SERVICE_TURN_ON,
{ATTR_BRIGHTNESS: 128},
"set_brightness",
(round(128 / 255 * 100),),
)
SET_RGB_PARAMETERS = (
SERVICE_TURN_ON,
{ATTR_BRIGHTNESS: 128, ATTR_RGB_COLOR: (255, 0, 0)},
"set_rgb",
(round(128 / 255 * 100), 255, 0, 0),
)
SET_COLOR_TEMP_PARAMETERS = (
SERVICE_TURN_ON,
{ATTR_BRIGHTNESS: 128, ATTR_COLOR_TEMP_KELVIN: 4000},
"set_color_temp",
(round(128 / 255 * 100), 4000),
)
BULB_PARAMETERS = (
COMMON_PARAMETERS,
[ [
( TURN_ON_PARAMETERS,
SERVICE_TURN_OFF, TURN_OFF_PARAMETERS,
{}, SET_BRIGHTNESS_PARAMETERS,
"turn_off", SET_RGB_PARAMETERS,
(), SET_COLOR_TEMP_PARAMETERS,
{switchbotColorMode.RGB},
switchbotColorMode.RGB,
),
( (
SERVICE_TURN_ON, SERVICE_TURN_ON,
{}, {ATTR_EFFECT: "Breathing"},
"turn_on", "set_effect",
(), ("Breathing",),
{switchbotColorMode.RGB},
switchbotColorMode.RGB,
),
(
SERVICE_TURN_ON,
{ATTR_BRIGHTNESS: 128},
"set_brightness",
(round(128 / 255 * 100),),
{switchbotColorMode.RGB},
switchbotColorMode.RGB,
),
(
SERVICE_TURN_ON,
{ATTR_RGB_COLOR: (255, 0, 0)},
"set_rgb",
(round(255 / 255 * 100), 255, 0, 0),
{switchbotColorMode.RGB},
switchbotColorMode.RGB,
),
(
SERVICE_TURN_ON,
{ATTR_COLOR_TEMP_KELVIN: 4000},
"set_color_temp",
(100, 4000),
{switchbotColorMode.COLOR_TEMP},
switchbotColorMode.COLOR_TEMP,
), ),
], ],
) )
async def test_light_strip_services( CEILING_LIGHT_PARAMETERS = (
COMMON_PARAMETERS,
[
TURN_ON_PARAMETERS,
TURN_OFF_PARAMETERS,
SET_BRIGHTNESS_PARAMETERS,
SET_COLOR_TEMP_PARAMETERS,
],
)
STRIP_LIGHT_PARAMETERS = (
COMMON_PARAMETERS,
[
TURN_ON_PARAMETERS,
TURN_OFF_PARAMETERS,
SET_BRIGHTNESS_PARAMETERS,
SET_RGB_PARAMETERS,
(
SERVICE_TURN_ON,
{ATTR_EFFECT: "Halloween"},
"set_effect",
("Halloween",),
),
],
)
@pytest.mark.parametrize(*BULB_PARAMETERS)
async def test_bulb_services(
hass: HomeAssistant, hass: HomeAssistant,
mock_entry_factory: Callable[[str], MockConfigEntry], mock_entry_factory: Callable[[str], MockConfigEntry],
service: str, service: str,
service_data: dict, service_data: dict,
mock_method: str, mock_method: str,
expected_args: Any, expected_args: Any,
color_modes: set | None,
color_mode: switchbotColorMode | None,
) -> None: ) -> None:
"""Test all SwitchBot light strip services with proper parameters.""" """Test all SwitchBot bulb services."""
inject_bluetooth_service_info(hass, WOSTRIP_SERVICE_INFO) inject_bluetooth_service_info(hass, BULB_SERVICE_INFO)
entry = mock_entry_factory(sensor_type="light_strip") entry = mock_entry_factory(sensor_type="bulb")
entry.add_to_hass(hass) entry.add_to_hass(hass)
entity_id = "light.test_name" entity_id = "light.test_name"
mocked_instance = AsyncMock(return_value=True) mocked_instance = AsyncMock(return_value=True)
with patch.multiple( with patch.multiple(
"homeassistant.components.switchbot.light.switchbot.SwitchbotLightStrip", "homeassistant.components.switchbot.light.switchbot.SwitchbotBulb",
color_modes=color_modes,
color_mode=color_mode,
update=AsyncMock(return_value=None),
**{mock_method: mocked_instance}, **{mock_method: mocked_instance},
update=AsyncMock(return_value=None),
): ):
assert await hass.config_entries.async_setup(entry.entry_id) assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -117,79 +139,173 @@ async def test_light_strip_services(
mocked_instance.assert_awaited_once_with(*expected_args) mocked_instance.assert_awaited_once_with(*expected_args)
@pytest.mark.parametrize( @pytest.mark.parametrize(*BULB_PARAMETERS)
("exception", "error_message"), async def test_bulb_services_exception(
[
(
SwitchbotOperationError("Operation failed"),
"An error occurred while performing the action: Operation failed",
),
],
)
@pytest.mark.parametrize(
("service", "service_data", "mock_method", "color_modes", "color_mode"),
[
(
SERVICE_TURN_ON,
{},
"turn_on",
{switchbotColorMode.RGB},
switchbotColorMode.RGB,
),
(
SERVICE_TURN_OFF,
{},
"turn_off",
{switchbotColorMode.RGB},
switchbotColorMode.RGB,
),
(
SERVICE_TURN_ON,
{ATTR_BRIGHTNESS: 128},
"set_brightness",
{switchbotColorMode.RGB},
switchbotColorMode.RGB,
),
(
SERVICE_TURN_ON,
{ATTR_RGB_COLOR: (255, 0, 0)},
"set_rgb",
{switchbotColorMode.RGB},
switchbotColorMode.RGB,
),
(
SERVICE_TURN_ON,
{ATTR_COLOR_TEMP_KELVIN: 4000},
"set_color_temp",
{switchbotColorMode.COLOR_TEMP},
switchbotColorMode.COLOR_TEMP,
),
],
)
async def test_exception_handling_light_service(
hass: HomeAssistant, hass: HomeAssistant,
mock_entry_factory: Callable[[str], MockConfigEntry], mock_entry_factory: Callable[[str], MockConfigEntry],
service: str, service: str,
service_data: dict, service_data: dict,
mock_method: str, mock_method: str,
color_modes: set | None, expected_args: Any,
color_mode: switchbotColorMode | None,
exception: Exception,
error_message: str,
) -> None: ) -> None:
"""Test exception handling for light service with exception.""" """Test all SwitchBot bulb services with exception."""
inject_bluetooth_service_info(hass, WOSTRIP_SERVICE_INFO) inject_bluetooth_service_info(hass, BULB_SERVICE_INFO)
entry = mock_entry_factory(sensor_type="light_strip") entry = mock_entry_factory(sensor_type="bulb")
entry.add_to_hass(hass) entry.add_to_hass(hass)
entity_id = "light.test_name" entity_id = "light.test_name"
exception = SwitchbotOperationError("Operation failed")
error_message = "An error occurred while performing the action: Operation failed"
with patch.multiple( with patch.multiple(
"homeassistant.components.switchbot.light.switchbot.SwitchbotLightStrip", "homeassistant.components.switchbot.light.switchbot.SwitchbotBulb",
color_modes=color_modes,
color_mode=color_mode,
update=AsyncMock(return_value=None),
**{mock_method: AsyncMock(side_effect=exception)}, **{mock_method: AsyncMock(side_effect=exception)},
update=AsyncMock(return_value=None),
):
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(
LIGHT_DOMAIN,
service,
{**service_data, ATTR_ENTITY_ID: entity_id},
blocking=True,
)
@pytest.mark.parametrize(*CEILING_LIGHT_PARAMETERS)
async def test_ceiling_light_services(
hass: HomeAssistant,
mock_entry_factory: Callable[[str], MockConfigEntry],
service: str,
service_data: dict,
mock_method: str,
expected_args: Any,
) -> None:
"""Test all SwitchBot ceiling light services."""
inject_bluetooth_service_info(hass, CEILING_LIGHT_SERVICE_INFO)
entry = mock_entry_factory(sensor_type="ceiling_light")
entry.add_to_hass(hass)
entity_id = "light.test_name"
mocked_instance = AsyncMock(return_value=True)
with patch.multiple(
"homeassistant.components.switchbot.light.switchbot.SwitchbotCeilingLight",
**{mock_method: mocked_instance},
update=AsyncMock(return_value=None),
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
await hass.services.async_call(
LIGHT_DOMAIN,
service,
{**service_data, ATTR_ENTITY_ID: entity_id},
blocking=True,
)
mocked_instance.assert_awaited_once_with(*expected_args)
@pytest.mark.parametrize(*CEILING_LIGHT_PARAMETERS)
async def test_ceiling_light_services_exception(
hass: HomeAssistant,
mock_entry_factory: Callable[[str], MockConfigEntry],
service: str,
service_data: dict,
mock_method: str,
expected_args: Any,
) -> None:
"""Test all SwitchBot ceiling light services with exception."""
inject_bluetooth_service_info(hass, CEILING_LIGHT_SERVICE_INFO)
entry = mock_entry_factory(sensor_type="ceiling_light")
entry.add_to_hass(hass)
entity_id = "light.test_name"
exception = SwitchbotOperationError("Operation failed")
error_message = "An error occurred while performing the action: Operation failed"
with patch.multiple(
"homeassistant.components.switchbot.light.switchbot.SwitchbotCeilingLight",
**{mock_method: AsyncMock(side_effect=exception)},
update=AsyncMock(return_value=None),
):
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(
LIGHT_DOMAIN,
service,
{**service_data, ATTR_ENTITY_ID: entity_id},
blocking=True,
)
@pytest.mark.parametrize(*STRIP_LIGHT_PARAMETERS)
async def test_strip_light_services(
hass: HomeAssistant,
mock_entry_factory: Callable[[str], MockConfigEntry],
service: str,
service_data: dict,
mock_method: str,
expected_args: Any,
) -> None:
"""Test all SwitchBot strip light services."""
inject_bluetooth_service_info(hass, WOSTRIP_SERVICE_INFO)
entry = mock_entry_factory(sensor_type="light_strip")
entry.add_to_hass(hass)
entity_id = "light.test_name"
mocked_instance = AsyncMock(return_value=True)
with patch.multiple(
"homeassistant.components.switchbot.light.switchbot.SwitchbotLightStrip",
**{mock_method: mocked_instance},
update=AsyncMock(return_value=None),
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
await hass.services.async_call(
LIGHT_DOMAIN,
service,
{**service_data, ATTR_ENTITY_ID: entity_id},
blocking=True,
)
mocked_instance.assert_awaited_once_with(*expected_args)
@pytest.mark.parametrize(*STRIP_LIGHT_PARAMETERS)
async def test_strip_light_services_exception(
hass: HomeAssistant,
mock_entry_factory: Callable[[str], MockConfigEntry],
service: str,
service_data: dict,
mock_method: str,
expected_args: Any,
) -> None:
"""Test all SwitchBot strip light services with exception."""
inject_bluetooth_service_info(hass, WOSTRIP_SERVICE_INFO)
entry = mock_entry_factory(sensor_type="light_strip")
entry.add_to_hass(hass)
entity_id = "light.test_name"
exception = SwitchbotOperationError("Operation failed")
error_message = "An error occurred while performing the action: Operation failed"
with patch.multiple(
"homeassistant.components.switchbot.light.switchbot.SwitchbotLightStrip",
**{mock_method: AsyncMock(side_effect=exception)},
update=AsyncMock(return_value=None),
): ):
assert await hass.config_entries.async_setup(entry.entry_id) assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()