mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Fix yeelight state when controlled outside of Home Assistant (#56964)
This commit is contained in:
parent
d0827a9129
commit
1aeab65f56
@ -36,6 +36,9 @@ from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
STATE_CHANGE_TIME = 0.25 # seconds
|
||||
|
||||
|
||||
DOMAIN = "yeelight"
|
||||
DATA_YEELIGHT = DOMAIN
|
||||
DATA_UPDATED = "yeelight_{}_data_updated"
|
||||
@ -546,6 +549,17 @@ class YeelightScanner:
|
||||
self._async_stop_scan()
|
||||
|
||||
|
||||
def update_needs_bg_power_workaround(data):
|
||||
"""Check if a push update needs the bg_power workaround.
|
||||
|
||||
Some devices will push the incorrect state for bg_power.
|
||||
|
||||
To work around this any time we are pushed an update
|
||||
with bg_power, we force poll state which will be correct.
|
||||
"""
|
||||
return "bg_power" in data
|
||||
|
||||
|
||||
class YeelightDevice:
|
||||
"""Represents single Yeelight device."""
|
||||
|
||||
@ -692,12 +706,18 @@ class YeelightDevice:
|
||||
await self._async_update_properties()
|
||||
async_dispatcher_send(self._hass, DATA_UPDATED.format(self._host))
|
||||
|
||||
async def _async_forced_update(self, _now):
|
||||
"""Call a forced update."""
|
||||
await self.async_update(True)
|
||||
|
||||
@callback
|
||||
def async_update_callback(self, data):
|
||||
"""Update push from device."""
|
||||
was_available = self._available
|
||||
self._available = data.get(KEY_CONNECTED, True)
|
||||
if self._did_first_update and not was_available and self._available:
|
||||
if update_needs_bg_power_workaround(data) or (
|
||||
self._did_first_update and not was_available and self._available
|
||||
):
|
||||
# On reconnect the properties may be out of sync
|
||||
#
|
||||
# We need to make sure the DEVICE_INITIALIZED dispatcher is setup
|
||||
@ -708,7 +728,7 @@ class YeelightDevice:
|
||||
# to be called when async_setup_entry reaches the end of the
|
||||
# function
|
||||
#
|
||||
asyncio.create_task(self.async_update(True))
|
||||
async_call_later(self._hass, STATE_CHANGE_TIME, self._async_forced_update)
|
||||
async_dispatcher_send(self._hass, DATA_UPDATED.format(self._host))
|
||||
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
"""Light platform support for yeelight."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import math
|
||||
|
||||
@ -210,9 +209,6 @@ SERVICE_SCHEMA_SET_AUTO_DELAY_OFF_SCENE = {
|
||||
}
|
||||
|
||||
|
||||
STATE_CHANGE_TIME = 0.25 # seconds
|
||||
|
||||
|
||||
@callback
|
||||
def _transitions_config_parser(transitions):
|
||||
"""Parse transitions config into initialized objects."""
|
||||
@ -252,13 +248,15 @@ def _async_cmd(func):
|
||||
# A network error happened, the bulb is likely offline now
|
||||
self.device.async_mark_unavailable()
|
||||
self.async_write_ha_state()
|
||||
exc_message = str(ex) or type(ex)
|
||||
raise HomeAssistantError(
|
||||
f"Error when calling {func.__name__} for bulb {self.device.name} at {self.device.host}: {ex}"
|
||||
f"Error when calling {func.__name__} for bulb {self.device.name} at {self.device.host}: {exc_message}"
|
||||
) from ex
|
||||
except BULB_EXCEPTIONS as ex:
|
||||
# The bulb likely responded but had an error
|
||||
exc_message = str(ex) or type(ex)
|
||||
raise HomeAssistantError(
|
||||
f"Error when calling {func.__name__} for bulb {self.device.name} at {self.device.host}: {ex}"
|
||||
f"Error when calling {func.__name__} for bulb {self.device.name} at {self.device.host}: {exc_message}"
|
||||
) from ex
|
||||
|
||||
return _async_wrap
|
||||
@ -762,11 +760,6 @@ class YeelightGenericLight(YeelightEntity, LightEntity):
|
||||
if self.config[CONF_SAVE_ON_CHANGE] and (brightness or colortemp or rgb):
|
||||
await self.async_set_default()
|
||||
|
||||
# Some devices (mainly nightlights) will not send back the on state so we need to force a refresh
|
||||
await asyncio.sleep(STATE_CHANGE_TIME)
|
||||
if not self.is_on:
|
||||
await self.device.async_update(True)
|
||||
|
||||
@_async_cmd
|
||||
async def _async_turn_off(self, duration) -> None:
|
||||
"""Turn off with a given transition duration wrapped with _async_cmd."""
|
||||
@ -782,10 +775,6 @@ class YeelightGenericLight(YeelightEntity, LightEntity):
|
||||
duration = int(kwargs.get(ATTR_TRANSITION) * 1000) # kwarg in s
|
||||
|
||||
await self._async_turn_off(duration)
|
||||
# Some devices will not send back the off state so we need to force a refresh
|
||||
await asyncio.sleep(STATE_CHANGE_TIME)
|
||||
if self.is_on:
|
||||
await self.device.async_update(True)
|
||||
|
||||
@_async_cmd
|
||||
async def async_set_mode(self, mode: str):
|
||||
@ -850,10 +839,8 @@ class YeelightNightLightSupport:
|
||||
return PowerMode.NORMAL
|
||||
|
||||
|
||||
class YeelightColorLightWithoutNightlightSwitch(
|
||||
YeelightColorLightSupport, YeelightGenericLight
|
||||
):
|
||||
"""Representation of a Color Yeelight light."""
|
||||
class YeelightWithoutNightlightSwitchMixIn:
|
||||
"""A mix-in for yeelights without a nightlight switch."""
|
||||
|
||||
@property
|
||||
def _brightness_property(self):
|
||||
@ -861,9 +848,25 @@ class YeelightColorLightWithoutNightlightSwitch(
|
||||
# want to "current_brightness" since it will check
|
||||
# "bg_power" and main light could still be on
|
||||
if self.device.is_nightlight_enabled:
|
||||
return "current_brightness"
|
||||
return "nl_br"
|
||||
return super()._brightness_property
|
||||
|
||||
@property
|
||||
def color_temp(self) -> int:
|
||||
"""Return the color temperature."""
|
||||
if self.device.is_nightlight_enabled:
|
||||
# Enabling the nightlight locks the colortemp to max
|
||||
return self._max_mireds
|
||||
return super().color_temp
|
||||
|
||||
|
||||
class YeelightColorLightWithoutNightlightSwitch(
|
||||
YeelightColorLightSupport,
|
||||
YeelightWithoutNightlightSwitchMixIn,
|
||||
YeelightGenericLight,
|
||||
):
|
||||
"""Representation of a Color Yeelight light."""
|
||||
|
||||
|
||||
class YeelightColorLightWithNightlightSwitch(
|
||||
YeelightNightLightSupport, YeelightColorLightSupport, YeelightGenericLight
|
||||
@ -880,19 +883,12 @@ class YeelightColorLightWithNightlightSwitch(
|
||||
|
||||
|
||||
class YeelightWhiteTempWithoutNightlightSwitch(
|
||||
YeelightWhiteTempLightSupport, YeelightGenericLight
|
||||
YeelightWhiteTempLightSupport,
|
||||
YeelightWithoutNightlightSwitchMixIn,
|
||||
YeelightGenericLight,
|
||||
):
|
||||
"""White temp light, when nightlight switch is not set to light."""
|
||||
|
||||
@property
|
||||
def _brightness_property(self):
|
||||
# If the nightlight is not active, we do not
|
||||
# want to "current_brightness" since it will check
|
||||
# "bg_power" and main light could still be on
|
||||
if self.device.is_nightlight_enabled:
|
||||
return "current_brightness"
|
||||
return super()._brightness_property
|
||||
|
||||
|
||||
class YeelightWithNightLight(
|
||||
YeelightNightLightSupport, YeelightWhiteTempLightSupport, YeelightGenericLight
|
||||
@ -911,6 +907,9 @@ class YeelightWithNightLight(
|
||||
class YeelightNightLightMode(YeelightGenericLight):
|
||||
"""Representation of a Yeelight when in nightlight mode."""
|
||||
|
||||
_attr_color_mode = COLOR_MODE_BRIGHTNESS
|
||||
_attr_supported_color_modes = {COLOR_MODE_BRIGHTNESS}
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return a unique ID."""
|
||||
@ -941,8 +940,9 @@ class YeelightNightLightMode(YeelightGenericLight):
|
||||
return PowerMode.MOONLIGHT
|
||||
|
||||
@property
|
||||
def _predefined_effects(self):
|
||||
return YEELIGHT_TEMP_ONLY_EFFECT_LIST
|
||||
def supported_features(self):
|
||||
"""Flag no supported features."""
|
||||
return 0
|
||||
|
||||
|
||||
class YeelightNightLightModeWithAmbientSupport(YeelightNightLightMode):
|
||||
@ -962,11 +962,6 @@ class YeelightNightLightModeWithoutBrightnessControl(YeelightNightLightMode):
|
||||
_attr_color_mode = COLOR_MODE_ONOFF
|
||||
_attr_supported_color_modes = {COLOR_MODE_ONOFF}
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag no supported features."""
|
||||
return 0
|
||||
|
||||
|
||||
class YeelightWithAmbientWithoutNightlight(YeelightWhiteTempWithoutNightlightSwitch):
|
||||
"""Representation of a Yeelight which has ambilight support.
|
||||
|
@ -13,6 +13,7 @@ from homeassistant.components.yeelight import (
|
||||
DATA_DEVICE,
|
||||
DOMAIN,
|
||||
NIGHTLIGHT_SWITCH_TYPE_LIGHT,
|
||||
STATE_CHANGE_TIME,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import (
|
||||
@ -458,6 +459,8 @@ async def test_connection_dropped_resyncs_properties(hass: HomeAssistant):
|
||||
await hass.async_block_till_done()
|
||||
assert len(mocked_bulb.async_get_properties.mock_calls) == 1
|
||||
mocked_bulb._async_callback({KEY_CONNECTED: True})
|
||||
await hass.async_block_till_done()
|
||||
async_fire_time_changed(
|
||||
hass, dt_util.utcnow() + timedelta(seconds=STATE_CHANGE_TIME)
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mocked_bulb.async_get_properties.mock_calls) == 2
|
||||
|
@ -545,7 +545,8 @@ async def test_update_errors(hass: HomeAssistant, caplog):
|
||||
|
||||
# Timeout usually means the bulb is overloaded with commands
|
||||
# but will still respond eventually.
|
||||
mocked_bulb.async_get_properties = AsyncMock(side_effect=asyncio.TimeoutError)
|
||||
mocked_bulb.async_turn_off = AsyncMock(side_effect=asyncio.TimeoutError)
|
||||
with pytest.raises(HomeAssistantError):
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
SERVICE_TURN_OFF,
|
||||
@ -557,7 +558,8 @@ async def test_update_errors(hass: HomeAssistant, caplog):
|
||||
# socket.error usually means the bulb dropped the connection
|
||||
# or lost wifi, then came back online and forced the existing
|
||||
# connection closed with a TCP RST
|
||||
mocked_bulb.async_get_properties = AsyncMock(side_effect=socket.error)
|
||||
mocked_bulb.async_turn_off = AsyncMock(side_effect=socket.error)
|
||||
with pytest.raises(HomeAssistantError):
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
SERVICE_TURN_OFF,
|
||||
@ -572,6 +574,7 @@ async def test_state_already_set_avoid_ratelimit(hass: HomeAssistant):
|
||||
mocked_bulb = _mocked_bulb()
|
||||
properties = {**PROPERTIES}
|
||||
properties.pop("active_mode")
|
||||
properties.pop("nl_br")
|
||||
properties["color_mode"] = "3" # HSV
|
||||
mocked_bulb.last_properties = properties
|
||||
mocked_bulb.bulb_type = BulbType.Color
|
||||
@ -579,7 +582,9 @@ async def test_state_already_set_avoid_ratelimit(hass: HomeAssistant):
|
||||
domain=DOMAIN, data={**CONFIG_ENTRY_DATA, CONF_NIGHTLIGHT_SWITCH: False}
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
with patch(f"{MODULE}.AsyncBulb", return_value=mocked_bulb):
|
||||
with _patch_discovery(), _patch_discovery_interval(), patch(
|
||||
f"{MODULE}.AsyncBulb", return_value=mocked_bulb
|
||||
):
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
# We use asyncio.create_task now to avoid
|
||||
@ -623,7 +628,7 @@ async def test_state_already_set_avoid_ratelimit(hass: HomeAssistant):
|
||||
SERVICE_TURN_ON,
|
||||
{
|
||||
ATTR_ENTITY_ID: ENTITY_LIGHT,
|
||||
ATTR_BRIGHTNESS_PCT: PROPERTIES["current_brightness"],
|
||||
ATTR_BRIGHTNESS_PCT: PROPERTIES["bright"],
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
@ -696,9 +701,10 @@ async def test_device_types(hass: HomeAssistant, caplog):
|
||||
bulb_type,
|
||||
model,
|
||||
target_properties,
|
||||
nightlight_properties=None,
|
||||
nightlight_entity_properties=None,
|
||||
name=UNIQUE_FRIENDLY_NAME,
|
||||
entity_id=ENTITY_LIGHT,
|
||||
nightlight_mode_properties=None,
|
||||
):
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN, data={**CONFIG_ENTRY_DATA, CONF_NIGHTLIGHT_SWITCH: False}
|
||||
@ -708,6 +714,9 @@ async def test_device_types(hass: HomeAssistant, caplog):
|
||||
mocked_bulb.bulb_type = bulb_type
|
||||
model_specs = _MODEL_SPECS.get(model)
|
||||
type(mocked_bulb).get_model_specs = MagicMock(return_value=model_specs)
|
||||
original_nightlight_brightness = mocked_bulb.last_properties["nl_br"]
|
||||
|
||||
mocked_bulb.last_properties["nl_br"] = "0"
|
||||
await _async_setup(config_entry)
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
@ -715,18 +724,36 @@ async def test_device_types(hass: HomeAssistant, caplog):
|
||||
assert state.state == "on"
|
||||
target_properties["friendly_name"] = name
|
||||
target_properties["flowing"] = False
|
||||
target_properties["night_light"] = True
|
||||
target_properties["night_light"] = False
|
||||
target_properties["music_mode"] = False
|
||||
assert dict(state.attributes) == target_properties
|
||||
|
||||
await hass.config_entries.async_unload(config_entry.entry_id)
|
||||
await config_entry.async_remove(hass)
|
||||
registry = er.async_get(hass)
|
||||
registry.async_clear_config_entry(config_entry.entry_id)
|
||||
mocked_bulb.last_properties["nl_br"] = original_nightlight_brightness
|
||||
|
||||
# nightlight
|
||||
if nightlight_properties is None:
|
||||
return
|
||||
# nightlight as a setting of the main entity
|
||||
if nightlight_mode_properties is not None:
|
||||
mocked_bulb.last_properties["active_mode"] = True
|
||||
config_entry.add_to_hass(hass)
|
||||
await _async_setup(config_entry)
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == "on"
|
||||
nightlight_mode_properties["friendly_name"] = name
|
||||
nightlight_mode_properties["flowing"] = False
|
||||
nightlight_mode_properties["night_light"] = True
|
||||
nightlight_mode_properties["music_mode"] = False
|
||||
assert dict(state.attributes) == nightlight_mode_properties
|
||||
|
||||
await hass.config_entries.async_unload(config_entry.entry_id)
|
||||
await config_entry.async_remove(hass)
|
||||
registry.async_clear_config_entry(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
mocked_bulb.last_properties.pop("active_mode")
|
||||
|
||||
# nightlight as a separate entity
|
||||
if nightlight_entity_properties is not None:
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN, data={**CONFIG_ENTRY_DATA, CONF_NIGHTLIGHT_SWITCH: True}
|
||||
)
|
||||
@ -736,12 +763,12 @@ async def test_device_types(hass: HomeAssistant, caplog):
|
||||
assert hass.states.get(entity_id).state == "off"
|
||||
state = hass.states.get(f"{entity_id}_nightlight")
|
||||
assert state.state == "on"
|
||||
nightlight_properties["friendly_name"] = f"{name} Nightlight"
|
||||
nightlight_properties["icon"] = "mdi:weather-night"
|
||||
nightlight_properties["flowing"] = False
|
||||
nightlight_properties["night_light"] = True
|
||||
nightlight_properties["music_mode"] = False
|
||||
assert dict(state.attributes) == nightlight_properties
|
||||
nightlight_entity_properties["friendly_name"] = f"{name} Nightlight"
|
||||
nightlight_entity_properties["icon"] = "mdi:weather-night"
|
||||
nightlight_entity_properties["flowing"] = False
|
||||
nightlight_entity_properties["night_light"] = True
|
||||
nightlight_entity_properties["music_mode"] = False
|
||||
assert dict(state.attributes) == nightlight_entity_properties
|
||||
|
||||
await hass.config_entries.async_unload(config_entry.entry_id)
|
||||
await config_entry.async_remove(hass)
|
||||
@ -749,7 +776,6 @@ async def test_device_types(hass: HomeAssistant, caplog):
|
||||
await hass.async_block_till_done()
|
||||
|
||||
bright = round(255 * int(PROPERTIES["bright"]) / 100)
|
||||
current_brightness = round(255 * int(PROPERTIES["current_brightness"]) / 100)
|
||||
ct = color_temperature_kelvin_to_mired(int(PROPERTIES["ct"]))
|
||||
hue = int(PROPERTIES["hue"])
|
||||
sat = int(PROPERTIES["sat"])
|
||||
@ -806,7 +832,7 @@ async def test_device_types(hass: HomeAssistant, caplog):
|
||||
"max_mireds": color_temperature_kelvin_to_mired(
|
||||
model_specs["color_temp"]["min"]
|
||||
),
|
||||
"brightness": current_brightness,
|
||||
"brightness": bright,
|
||||
"color_temp": ct,
|
||||
"color_mode": "color_temp",
|
||||
"supported_color_modes": ["color_temp", "hs", "rgb"],
|
||||
@ -814,11 +840,30 @@ async def test_device_types(hass: HomeAssistant, caplog):
|
||||
"rgb_color": (255, 205, 166),
|
||||
"xy_color": (0.421, 0.364),
|
||||
},
|
||||
{
|
||||
nightlight_entity_properties={
|
||||
"supported_features": 0,
|
||||
"color_mode": "onoff",
|
||||
"supported_color_modes": ["onoff"],
|
||||
},
|
||||
nightlight_mode_properties={
|
||||
"effect_list": YEELIGHT_COLOR_EFFECT_LIST,
|
||||
"supported_features": SUPPORT_YEELIGHT,
|
||||
"hs_color": (28.401, 100.0),
|
||||
"rgb_color": (255, 120, 0),
|
||||
"xy_color": (0.621, 0.367),
|
||||
"min_mireds": color_temperature_kelvin_to_mired(
|
||||
model_specs["color_temp"]["max"]
|
||||
),
|
||||
"max_mireds": color_temperature_kelvin_to_mired(
|
||||
model_specs["color_temp"]["min"]
|
||||
),
|
||||
"brightness": nl_br,
|
||||
"color_mode": "color_temp",
|
||||
"supported_color_modes": ["color_temp", "hs", "rgb"],
|
||||
"color_temp": color_temperature_kelvin_to_mired(
|
||||
model_specs["color_temp"]["min"]
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
# Color - color mode HS
|
||||
@ -836,14 +881,14 @@ async def test_device_types(hass: HomeAssistant, caplog):
|
||||
"max_mireds": color_temperature_kelvin_to_mired(
|
||||
model_specs["color_temp"]["min"]
|
||||
),
|
||||
"brightness": current_brightness,
|
||||
"brightness": bright,
|
||||
"hs_color": hs_color,
|
||||
"rgb_color": color_hs_to_RGB(*hs_color),
|
||||
"xy_color": color_hs_to_xy(*hs_color),
|
||||
"color_mode": "hs",
|
||||
"supported_color_modes": ["color_temp", "hs", "rgb"],
|
||||
},
|
||||
{
|
||||
nightlight_entity_properties={
|
||||
"supported_features": 0,
|
||||
"color_mode": "onoff",
|
||||
"supported_color_modes": ["onoff"],
|
||||
@ -865,14 +910,14 @@ async def test_device_types(hass: HomeAssistant, caplog):
|
||||
"max_mireds": color_temperature_kelvin_to_mired(
|
||||
model_specs["color_temp"]["min"]
|
||||
),
|
||||
"brightness": current_brightness,
|
||||
"brightness": bright,
|
||||
"hs_color": color_RGB_to_hs(*rgb_color),
|
||||
"rgb_color": rgb_color,
|
||||
"xy_color": color_RGB_to_xy(*rgb_color),
|
||||
"color_mode": "rgb",
|
||||
"supported_color_modes": ["color_temp", "hs", "rgb"],
|
||||
},
|
||||
{
|
||||
nightlight_entity_properties={
|
||||
"supported_features": 0,
|
||||
"color_mode": "onoff",
|
||||
"supported_color_modes": ["onoff"],
|
||||
@ -895,11 +940,11 @@ async def test_device_types(hass: HomeAssistant, caplog):
|
||||
"max_mireds": color_temperature_kelvin_to_mired(
|
||||
model_specs["color_temp"]["min"]
|
||||
),
|
||||
"brightness": current_brightness,
|
||||
"brightness": bright,
|
||||
"color_mode": "hs",
|
||||
"supported_color_modes": ["color_temp", "hs", "rgb"],
|
||||
},
|
||||
{
|
||||
nightlight_entity_properties={
|
||||
"supported_features": 0,
|
||||
"color_mode": "onoff",
|
||||
"supported_color_modes": ["onoff"],
|
||||
@ -922,11 +967,11 @@ async def test_device_types(hass: HomeAssistant, caplog):
|
||||
"max_mireds": color_temperature_kelvin_to_mired(
|
||||
model_specs["color_temp"]["min"]
|
||||
),
|
||||
"brightness": current_brightness,
|
||||
"brightness": bright,
|
||||
"color_mode": "rgb",
|
||||
"supported_color_modes": ["color_temp", "hs", "rgb"],
|
||||
},
|
||||
{
|
||||
nightlight_entity_properties={
|
||||
"supported_features": 0,
|
||||
"color_mode": "onoff",
|
||||
"supported_color_modes": ["onoff"],
|
||||
@ -973,7 +1018,7 @@ async def test_device_types(hass: HomeAssistant, caplog):
|
||||
"max_mireds": color_temperature_kelvin_to_mired(
|
||||
model_specs["color_temp"]["min"]
|
||||
),
|
||||
"brightness": current_brightness,
|
||||
"brightness": bright,
|
||||
"color_temp": ct,
|
||||
"color_mode": "color_temp",
|
||||
"supported_color_modes": ["color_temp"],
|
||||
@ -981,13 +1026,31 @@ async def test_device_types(hass: HomeAssistant, caplog):
|
||||
"rgb_color": (255, 205, 166),
|
||||
"xy_color": (0.421, 0.364),
|
||||
},
|
||||
{
|
||||
"effect_list": YEELIGHT_TEMP_ONLY_EFFECT_LIST,
|
||||
"supported_features": SUPPORT_YEELIGHT,
|
||||
nightlight_entity_properties={
|
||||
"supported_features": 0,
|
||||
"brightness": nl_br,
|
||||
"color_mode": "brightness",
|
||||
"supported_color_modes": ["brightness"],
|
||||
},
|
||||
nightlight_mode_properties={
|
||||
"effect_list": YEELIGHT_TEMP_ONLY_EFFECT_LIST,
|
||||
"supported_features": SUPPORT_YEELIGHT,
|
||||
"min_mireds": color_temperature_kelvin_to_mired(
|
||||
model_specs["color_temp"]["max"]
|
||||
),
|
||||
"max_mireds": color_temperature_kelvin_to_mired(
|
||||
model_specs["color_temp"]["min"]
|
||||
),
|
||||
"brightness": nl_br,
|
||||
"color_temp": color_temperature_kelvin_to_mired(
|
||||
model_specs["color_temp"]["min"]
|
||||
),
|
||||
"color_mode": "color_temp",
|
||||
"supported_color_modes": ["color_temp"],
|
||||
"hs_color": (28.391, 65.659),
|
||||
"rgb_color": (255, 166, 87),
|
||||
"xy_color": (0.526, 0.387),
|
||||
},
|
||||
)
|
||||
|
||||
# WhiteTempMood
|
||||
@ -1009,7 +1072,7 @@ async def test_device_types(hass: HomeAssistant, caplog):
|
||||
"max_mireds": color_temperature_kelvin_to_mired(
|
||||
model_specs["color_temp"]["min"]
|
||||
),
|
||||
"brightness": current_brightness,
|
||||
"brightness": bright,
|
||||
"color_temp": ct,
|
||||
"color_mode": "color_temp",
|
||||
"supported_color_modes": ["color_temp"],
|
||||
@ -1017,13 +1080,34 @@ async def test_device_types(hass: HomeAssistant, caplog):
|
||||
"rgb_color": (255, 205, 166),
|
||||
"xy_color": (0.421, 0.364),
|
||||
},
|
||||
{
|
||||
"effect_list": YEELIGHT_TEMP_ONLY_EFFECT_LIST,
|
||||
"supported_features": SUPPORT_YEELIGHT,
|
||||
nightlight_entity_properties={
|
||||
"supported_features": 0,
|
||||
"brightness": nl_br,
|
||||
"color_mode": "brightness",
|
||||
"supported_color_modes": ["brightness"],
|
||||
},
|
||||
nightlight_mode_properties={
|
||||
"friendly_name": NAME,
|
||||
"effect_list": YEELIGHT_TEMP_ONLY_EFFECT_LIST,
|
||||
"flowing": False,
|
||||
"night_light": True,
|
||||
"supported_features": SUPPORT_YEELIGHT,
|
||||
"min_mireds": color_temperature_kelvin_to_mired(
|
||||
model_specs["color_temp"]["max"]
|
||||
),
|
||||
"max_mireds": color_temperature_kelvin_to_mired(
|
||||
model_specs["color_temp"]["min"]
|
||||
),
|
||||
"brightness": nl_br,
|
||||
"color_temp": color_temperature_kelvin_to_mired(
|
||||
model_specs["color_temp"]["min"]
|
||||
),
|
||||
"color_mode": "color_temp",
|
||||
"supported_color_modes": ["color_temp"],
|
||||
"hs_color": (28.391, 65.659),
|
||||
"rgb_color": (255, 166, 87),
|
||||
"xy_color": (0.526, 0.387),
|
||||
},
|
||||
)
|
||||
# Background light - color mode CT
|
||||
mocked_bulb.last_properties["bg_lmode"] = "2" # CT
|
||||
@ -1261,62 +1345,6 @@ async def test_effects(hass: HomeAssistant):
|
||||
await _async_test_effect("not_existed", called=False)
|
||||
|
||||
|
||||
async def test_state_fails_to_update_triggers_update(hass: HomeAssistant):
|
||||
"""Ensure we call async_get_properties if the turn on/off fails to update the state."""
|
||||
mocked_bulb = _mocked_bulb()
|
||||
properties = {**PROPERTIES}
|
||||
properties.pop("active_mode")
|
||||
properties["color_mode"] = "3" # HSV
|
||||
mocked_bulb.last_properties = properties
|
||||
mocked_bulb.bulb_type = BulbType.Color
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN, data={**CONFIG_ENTRY_DATA, CONF_NIGHTLIGHT_SWITCH: False}
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
with patch(f"{MODULE}.AsyncBulb", return_value=mocked_bulb):
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
# We use asyncio.create_task now to avoid
|
||||
# blocking starting so we need to block again
|
||||
await hass.async_block_till_done()
|
||||
|
||||
mocked_bulb.last_properties["power"] = "off"
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
SERVICE_TURN_ON,
|
||||
{
|
||||
ATTR_ENTITY_ID: ENTITY_LIGHT,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
assert len(mocked_bulb.async_turn_on.mock_calls) == 1
|
||||
assert len(mocked_bulb.async_get_properties.mock_calls) == 2
|
||||
|
||||
mocked_bulb.last_properties["power"] = "on"
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
SERVICE_TURN_OFF,
|
||||
{
|
||||
ATTR_ENTITY_ID: ENTITY_LIGHT,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
assert len(mocked_bulb.async_turn_off.mock_calls) == 1
|
||||
assert len(mocked_bulb.async_get_properties.mock_calls) == 3
|
||||
|
||||
# But if the state is correct no calls
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
SERVICE_TURN_ON,
|
||||
{
|
||||
ATTR_ENTITY_ID: ENTITY_LIGHT,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
assert len(mocked_bulb.async_turn_on.mock_calls) == 1
|
||||
assert len(mocked_bulb.async_get_properties.mock_calls) == 3
|
||||
|
||||
|
||||
async def test_ambilight_with_nightlight_disabled(hass: HomeAssistant):
|
||||
"""Test that main light on ambilights with the nightlight disabled shows the correct brightness."""
|
||||
mocked_bulb = _mocked_bulb()
|
||||
@ -1325,7 +1353,6 @@ async def test_ambilight_with_nightlight_disabled(hass: HomeAssistant):
|
||||
capabilities["model"] = "ceiling10"
|
||||
properties["color_mode"] = "3" # HSV
|
||||
properties["bg_power"] = "off"
|
||||
properties["current_brightness"] = 0
|
||||
properties["bg_lmode"] = "2" # CT
|
||||
mocked_bulb.last_properties = properties
|
||||
mocked_bulb.bulb_type = BulbType.WhiteTempMood
|
||||
|
Loading…
x
Reference in New Issue
Block a user