mirror of
https://github.com/home-assistant/core.git
synced 2025-07-20 19:57:07 +00:00
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:
parent
8393f17bb3
commit
c5f8acfe93
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any, cast
|
||||
|
||||
import switchbot
|
||||
@ -10,14 +11,16 @@ from switchbot import ColorMode as SwitchBotColorMode
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_COLOR_TEMP_KELVIN,
|
||||
ATTR_EFFECT,
|
||||
ATTR_RGB_COLOR,
|
||||
ColorMode,
|
||||
LightEntity,
|
||||
LightEntityFeature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .coordinator import SwitchbotConfigEntry, SwitchbotDataUpdateCoordinator
|
||||
from .coordinator import SwitchbotConfigEntry
|
||||
from .entity import SwitchbotEntity, exception_handler
|
||||
|
||||
SWITCHBOT_COLOR_MODE_TO_HASS = {
|
||||
@ -25,6 +28,7 @@ SWITCHBOT_COLOR_MODE_TO_HASS = {
|
||||
SwitchBotColorMode.COLOR_TEMP: ColorMode.COLOR_TEMP,
|
||||
}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
@ -42,34 +46,69 @@ class SwitchbotLightEntity(SwitchbotEntity, LightEntity):
|
||||
|
||||
_device: switchbot.SwitchbotBaseLight
|
||||
_attr_name = None
|
||||
_attr_translation_key = "light"
|
||||
|
||||
def __init__(self, coordinator: SwitchbotDataUpdateCoordinator) -> None:
|
||||
"""Initialize the Switchbot light."""
|
||||
super().__init__(coordinator)
|
||||
device = self._device
|
||||
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()
|
||||
@property
|
||||
def max_color_temp_kelvin(self) -> int:
|
||||
"""Return the max color temperature."""
|
||||
return self._device.max_temp
|
||||
|
||||
@callback
|
||||
def _async_update_attrs(self) -> None:
|
||||
"""Handle updating _attr values."""
|
||||
device = self._device
|
||||
self._attr_is_on = self._device.on
|
||||
self._attr_brightness = max(0, min(255, round(device.brightness * 2.55)))
|
||||
if device.color_mode == SwitchBotColorMode.COLOR_TEMP:
|
||||
self._attr_color_temp_kelvin = device.color_temp
|
||||
self._attr_color_mode = ColorMode.COLOR_TEMP
|
||||
return
|
||||
self._attr_rgb_color = device.rgb
|
||||
self._attr_color_mode = ColorMode.RGB
|
||||
@property
|
||||
def min_color_temp_kelvin(self) -> int:
|
||||
"""Return the min color temperature."""
|
||||
return self._device.min_temp
|
||||
|
||||
@property
|
||||
def supported_color_modes(self) -> set[ColorMode]:
|
||||
"""Return the supported color modes."""
|
||||
return {SWITCHBOT_COLOR_MODE_TO_HASS[mode] for mode in self._device.color_modes}
|
||||
|
||||
@property
|
||||
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
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Instruct the light to turn on."""
|
||||
_LOGGER.debug("Turning on light %s, address %s", kwargs, self._address)
|
||||
brightness = round(
|
||||
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]))
|
||||
await self._device.set_color_temp(brightness, kelvin)
|
||||
return
|
||||
if ATTR_EFFECT in kwargs:
|
||||
effect = kwargs[ATTR_EFFECT]
|
||||
await self._device.set_effect(effect)
|
||||
return
|
||||
if ATTR_RGB_COLOR in kwargs:
|
||||
rgb = kwargs[ATTR_RGB_COLOR]
|
||||
await self._device.set_rgb(brightness, rgb[0], rgb[1], rgb[2])
|
||||
@ -94,4 +137,5 @@ class SwitchbotLightEntity(SwitchbotEntity, LightEntity):
|
||||
@exception_handler
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Instruct the light to turn off."""
|
||||
_LOGGER.debug("Turning off light %s, address %s", kwargs, self._address)
|
||||
await self._device.turn_off()
|
||||
|
@ -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": {
|
||||
|
@ -883,3 +883,61 @@ EVAPORATIVE_HUMIDIFIER_SERVICE_INFO = BluetoothServiceInfoBleak(
|
||||
connectable=True,
|
||||
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,
|
||||
)
|
||||
|
@ -5,12 +5,12 @@ from typing import Any
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
from switchbot import ColorMode as switchbotColorMode
|
||||
from switchbot.devices.device import SwitchbotOperationError
|
||||
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_COLOR_TEMP_KELVIN,
|
||||
ATTR_EFFECT,
|
||||
ATTR_RGB_COLOR,
|
||||
DOMAIN as LIGHT_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
@ -20,89 +20,111 @@ from homeassistant.const import ATTR_ENTITY_ID
|
||||
from homeassistant.core import HomeAssistant
|
||||
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.components.bluetooth import inject_bluetooth_service_info
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"service",
|
||||
"service_data",
|
||||
"mock_method",
|
||||
"expected_args",
|
||||
"color_modes",
|
||||
"color_mode",
|
||||
),
|
||||
COMMON_PARAMETERS = (
|
||||
"service",
|
||||
"service_data",
|
||||
"mock_method",
|
||||
"expected_args",
|
||||
)
|
||||
TURN_ON_PARAMETERS = (
|
||||
SERVICE_TURN_ON,
|
||||
{},
|
||||
"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,
|
||||
[
|
||||
(
|
||||
SERVICE_TURN_OFF,
|
||||
{},
|
||||
"turn_off",
|
||||
(),
|
||||
{switchbotColorMode.RGB},
|
||||
switchbotColorMode.RGB,
|
||||
),
|
||||
TURN_ON_PARAMETERS,
|
||||
TURN_OFF_PARAMETERS,
|
||||
SET_BRIGHTNESS_PARAMETERS,
|
||||
SET_RGB_PARAMETERS,
|
||||
SET_COLOR_TEMP_PARAMETERS,
|
||||
(
|
||||
SERVICE_TURN_ON,
|
||||
{},
|
||||
"turn_on",
|
||||
(),
|
||||
{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,
|
||||
{ATTR_EFFECT: "Breathing"},
|
||||
"set_effect",
|
||||
("Breathing",),
|
||||
),
|
||||
],
|
||||
)
|
||||
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,
|
||||
mock_entry_factory: Callable[[str], MockConfigEntry],
|
||||
service: str,
|
||||
service_data: dict,
|
||||
mock_method: str,
|
||||
expected_args: Any,
|
||||
color_modes: set | None,
|
||||
color_mode: switchbotColorMode | None,
|
||||
) -> None:
|
||||
"""Test all SwitchBot light strip services with proper parameters."""
|
||||
inject_bluetooth_service_info(hass, WOSTRIP_SERVICE_INFO)
|
||||
"""Test all SwitchBot bulb services."""
|
||||
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)
|
||||
entity_id = "light.test_name"
|
||||
|
||||
mocked_instance = AsyncMock(return_value=True)
|
||||
|
||||
with patch.multiple(
|
||||
"homeassistant.components.switchbot.light.switchbot.SwitchbotLightStrip",
|
||||
color_modes=color_modes,
|
||||
color_mode=color_mode,
|
||||
update=AsyncMock(return_value=None),
|
||||
"homeassistant.components.switchbot.light.switchbot.SwitchbotBulb",
|
||||
**{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()
|
||||
@ -117,79 +139,173 @@ async def test_light_strip_services(
|
||||
mocked_instance.assert_awaited_once_with(*expected_args)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("exception", "error_message"),
|
||||
[
|
||||
(
|
||||
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(
|
||||
@pytest.mark.parametrize(*BULB_PARAMETERS)
|
||||
async def test_bulb_services_exception(
|
||||
hass: HomeAssistant,
|
||||
mock_entry_factory: Callable[[str], MockConfigEntry],
|
||||
service: str,
|
||||
service_data: dict,
|
||||
mock_method: str,
|
||||
color_modes: set | None,
|
||||
color_mode: switchbotColorMode | None,
|
||||
exception: Exception,
|
||||
error_message: str,
|
||||
expected_args: Any,
|
||||
) -> None:
|
||||
"""Test exception handling for light service with exception."""
|
||||
inject_bluetooth_service_info(hass, WOSTRIP_SERVICE_INFO)
|
||||
"""Test all SwitchBot bulb services with exception."""
|
||||
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)
|
||||
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",
|
||||
color_modes=color_modes,
|
||||
color_mode=color_mode,
|
||||
update=AsyncMock(return_value=None),
|
||||
"homeassistant.components.switchbot.light.switchbot.SwitchbotBulb",
|
||||
**{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)
|
||||
await hass.async_block_till_done()
|
||||
|
Loading…
x
Reference in New Issue
Block a user