Fix yeelight connection issue (#40251)

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
Xiaonan Shen 2020-10-27 21:47:11 +08:00 committed by GitHub
parent 08342a1e05
commit f23fcfcd9b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 230 additions and 126 deletions

View file

@ -7,7 +7,7 @@ from typing import Optional
import voluptuous as vol
from yeelight import Bulb, BulbException, discover_bulbs
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry, ConfigEntryNotReady
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import (
CONF_DEVICES,
CONF_HOST,
@ -180,8 +180,8 @@ async def async_setup(hass: HomeAssistant, config: dict) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Yeelight from a config entry."""
async def _initialize(host: str) -> None:
device = await _async_setup_device(hass, host, entry.options)
async def _initialize(host: str, capabilities: Optional[dict] = None) -> None:
device = await _async_setup_device(hass, host, entry, capabilities)
hass.data[DOMAIN][DATA_CONFIG_ENTRIES][entry.entry_id][DATA_DEVICE] = device
for component in PLATFORMS:
hass.async_create_task(
@ -252,20 +252,33 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
async def _async_setup_device(
hass: HomeAssistant,
host: str,
config: dict,
entry: ConfigEntry,
capabilities: Optional[dict],
) -> None:
# Get model from config and capabilities
model = entry.options.get(CONF_MODEL)
if not model and capabilities is not None:
model = capabilities.get("model")
# Set up device
bulb = Bulb(host, model=config.get(CONF_MODEL) or None)
capabilities = await hass.async_add_executor_job(bulb.get_capabilities)
if capabilities is None: # timeout
_LOGGER.error("Failed to get capabilities from %s", host)
raise ConfigEntryNotReady
device = YeelightDevice(hass, host, config, bulb)
bulb = Bulb(host, model=model or None)
if capabilities is None:
capabilities = await hass.async_add_executor_job(bulb.get_capabilities)
device = YeelightDevice(hass, host, entry.options, bulb, capabilities)
await hass.async_add_executor_job(device.update)
await device.async_setup()
return device
@callback
def _async_unique_name(capabilities: dict) -> str:
"""Generate name from capabilities."""
model = capabilities["model"]
unique_id = capabilities["id"]
return f"yeelight_{model}_{unique_id}"
async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry):
"""Handle options update."""
await hass.config_entries.async_reload(entry.entry_id)
@ -332,7 +345,7 @@ class YeelightScanner:
"""Register callback function."""
host = self._seen.get(unique_id)
if host is not None:
self._hass.async_add_job(callback_func(host))
self._hass.async_create_task(callback_func(host))
else:
self._callbacks[unique_id] = callback_func
if len(self._callbacks) == 1:
@ -351,18 +364,25 @@ class YeelightScanner:
class YeelightDevice:
"""Represents single Yeelight device."""
def __init__(self, hass, host, config, bulb):
def __init__(self, hass, host, config, bulb, capabilities):
"""Initialize device."""
self._hass = hass
self._config = config
self._host = host
unique_id = bulb.capabilities.get("id")
self._name = config.get(CONF_NAME) or f"yeelight_{bulb.model}_{unique_id}"
self._bulb_device = bulb
self._capabilities = capabilities or {}
self._device_type = None
self._available = False
self._remove_time_tracker = None
self._name = host # Default name is host
if capabilities:
# Generate name from model and id when capabilities is available
self._name = _async_unique_name(capabilities)
if config.get(CONF_NAME):
# Override default name when name is set in config
self._name = config[CONF_NAME]
@property
def bulb(self):
"""Return bulb device."""
@ -396,7 +416,7 @@ class YeelightDevice:
@property
def fw_version(self):
"""Return the firmware version."""
return self._bulb_device.capabilities.get("fw_ver")
return self._capabilities.get("fw_ver")
@property
def is_nightlight_supported(self) -> bool:
@ -449,11 +469,6 @@ class YeelightDevice:
return self._device_type
@property
def unique_id(self) -> Optional[str]:
"""Return a unique ID."""
return self.bulb.capabilities.get("id")
def turn_on(self, duration=DEFAULT_TRANSITION, light_type=None, power_mode=None):
"""Turn on device."""
try:
@ -532,15 +547,24 @@ class YeelightDevice:
class YeelightEntity(Entity):
"""Represents single Yeelight entity."""
def __init__(self, device: YeelightDevice):
def __init__(self, device: YeelightDevice, entry: ConfigEntry):
"""Initialize the entity."""
self._device = device
self._unique_id = entry.entry_id
if entry.unique_id is not None:
# Use entry unique id (device id) whenever possible
self._unique_id = entry.unique_id
@property
def unique_id(self) -> str:
"""Return the unique ID."""
return self._unique_id
@property
def device_info(self) -> dict:
"""Return the device info."""
return {
"identifiers": {(DOMAIN, self._device.unique_id)},
"identifiers": {(DOMAIN, self._unique_id)},
"name": self._device.name,
"manufacturer": "Yeelight",
"model": self._device.model,

View file

@ -1,6 +1,5 @@
"""Sensor platform support for yeelight."""
import logging
from typing import Optional
from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.config_entries import ConfigEntry
@ -19,7 +18,7 @@ async def async_setup_entry(
device = hass.data[DOMAIN][DATA_CONFIG_ENTRIES][config_entry.entry_id][DATA_DEVICE]
if device.is_nightlight_supported:
_LOGGER.debug("Adding nightlight mode sensor for %s", device.name)
async_add_entities([YeelightNightlightModeSensor(device)])
async_add_entities([YeelightNightlightModeSensor(device, config_entry)])
class YeelightNightlightModeSensor(YeelightEntity, BinarySensorEntity):
@ -35,16 +34,6 @@ class YeelightNightlightModeSensor(YeelightEntity, BinarySensorEntity):
)
)
@property
def unique_id(self) -> Optional[str]:
"""Return a unique ID."""
unique = self._device.unique_id
if unique:
return unique + "-nightlight_sensor"
return None
@property
def name(self):
"""Return the name of the sensor."""

View file

@ -18,6 +18,7 @@ from . import (
CONF_SAVE_ON_CHANGE,
CONF_TRANSITION,
NIGHTLIGHT_SWITCH_TYPE_LIGHT,
_async_unique_name,
)
from . import DOMAIN # pylint:disable=unused-import
@ -38,7 +39,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
def __init__(self):
"""Initialize the config flow."""
self._capabilities = None
self._discovered_devices = {}
async def async_step_user(self, user_input=None):
@ -49,7 +49,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
try:
await self._async_try_connect(user_input[CONF_HOST])
return self.async_create_entry(
title=self._async_default_name(),
title=user_input[CONF_HOST],
data=user_input,
)
except CannotConnect:
@ -59,9 +59,12 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
else:
return await self.async_step_pick_device()
user_input = user_input or {}
return self.async_show_form(
step_id="user",
data_schema=vol.Schema({vol.Optional(CONF_HOST): str}),
data_schema=vol.Schema(
{vol.Optional(CONF_HOST, default=user_input.get(CONF_HOST, "")): str}
),
errors=errors,
)
@ -69,9 +72,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle the step to pick discovered device."""
if user_input is not None:
unique_id = user_input[CONF_DEVICE]
self._capabilities = self._discovered_devices[unique_id]
capabilities = self._discovered_devices[unique_id]
await self.async_set_unique_id(unique_id)
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=self._async_default_name(),
title=_async_unique_name(capabilities),
data={CONF_ID: unique_id},
)
@ -122,25 +127,32 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def _async_try_connect(self, host):
"""Set up with options."""
for entry in self._async_current_entries():
if entry.data.get(CONF_HOST) == host:
raise AlreadyConfigured
bulb = yeelight.Bulb(host)
try:
capabilities = await self.hass.async_add_executor_job(bulb.get_capabilities)
if capabilities is None: # timeout
_LOGGER.error("Failed to get capabilities from %s: timeout", host)
raise CannotConnect
_LOGGER.debug("Failed to get capabilities from %s: timeout", host)
else:
_LOGGER.debug("Get capabilities: %s", capabilities)
await self.async_set_unique_id(capabilities["id"])
self._abort_if_unique_id_configured()
return
except OSError as err:
_LOGGER.error("Failed to get capabilities from %s: %s", host, err)
raise CannotConnect from err
_LOGGER.debug("Get capabilities: %s", capabilities)
self._capabilities = capabilities
await self.async_set_unique_id(capabilities["id"])
self._abort_if_unique_id_configured()
_LOGGER.debug("Failed to get capabilities from %s: %s", host, err)
# Ignore the error since get_capabilities uses UDP discovery packet
# which does not work in all network environments
@callback
def _async_default_name(self):
model = self._capabilities["model"]
unique_id = self._capabilities["id"]
return f"yeelight_{model}_{unique_id}"
# Fallback to get properties
try:
await self.hass.async_add_executor_job(bulb.get_properties)
except yeelight.BulbException as err:
_LOGGER.error("Failed to get properties from %s: %s", host, err)
raise CannotConnect from err
_LOGGER.debug("Get properties: %s", bulb.last_properties)
class OptionsFlowHandler(config_entries.OptionsFlow):
@ -153,11 +165,8 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
async def async_step_init(self, user_input=None):
"""Handle the initial step."""
if user_input is not None:
# keep the name from imported entries
options = {
CONF_NAME: self._config_entry.options.get(CONF_NAME),
**user_input,
}
options = {**self._config_entry.options}
options.update(user_input)
return self.async_create_entry(title="", data=options)
options = self._config_entry.options

View file

@ -1,7 +1,6 @@
"""Light platform support for yeelight."""
from functools import partial
import logging
from typing import Optional
import voluptuous as vol
import yeelight
@ -241,7 +240,7 @@ async def async_setup_entry(
device_type = device.type
def _lights_setup_helper(klass):
lights.append(klass(device, custom_effects=custom_effects))
lights.append(klass(device, config_entry, custom_effects=custom_effects))
if device_type == BulbType.White:
_lights_setup_helper(YeelightGenericLight)
@ -382,9 +381,9 @@ def _async_setup_services(hass: HomeAssistant):
class YeelightGenericLight(YeelightEntity, LightEntity):
"""Representation of a Yeelight generic light."""
def __init__(self, device, custom_effects=None):
def __init__(self, device, entry, custom_effects=None):
"""Initialize the Yeelight light."""
super().__init__(device)
super().__init__(device, entry)
self.config = device.config
@ -418,12 +417,6 @@ class YeelightGenericLight(YeelightEntity, LightEntity):
)
)
@property
def unique_id(self) -> Optional[str]:
"""Return a unique ID."""
return self.device.unique_id
@property
def supported_features(self) -> int:
"""Flag supported features."""
@ -852,14 +845,10 @@ class YeelightNightLightMode(YeelightGenericLight):
"""Representation of a Yeelight when in nightlight mode."""
@property
def unique_id(self) -> Optional[str]:
def unique_id(self) -> str:
"""Return a unique ID."""
unique = super().unique_id
if unique:
return unique + "-nightlight"
return None
return f"{unique}-nightlight"
@property
def name(self) -> str:
@ -945,12 +934,10 @@ class YeelightAmbientLight(YeelightColorLightWithoutNightlightSwitch):
self._light_type = LightType.Ambient
@property
def unique_id(self) -> Optional[str]:
def unique_id(self) -> str:
"""Return a unique ID."""
unique = super().unique_id
if unique:
return unique + "-ambilight"
return f"{unique}-ambilight"
@property
def name(self) -> str:

View file

@ -36,4 +36,4 @@
}
}
}
}
}

View file

@ -1,5 +1,5 @@
"""Tests for the Yeelight integration."""
from yeelight import BulbType
from yeelight import BulbException, BulbType
from yeelight.main import _MODEL_SPECS
from homeassistant.components.yeelight import (
@ -8,6 +8,7 @@ from homeassistant.components.yeelight import (
CONF_SAVE_ON_CHANGE,
DOMAIN,
NIGHTLIGHT_SWITCH_TYPE_LIGHT,
YeelightScanner,
)
from homeassistant.const import CONF_DEVICES, CONF_ID, CONF_NAME
@ -27,7 +28,8 @@ CAPABILITIES = {
"name": "",
}
NAME = f"yeelight_{MODEL}_{ID}"
NAME = "name"
UNIQUE_NAME = f"yeelight_{MODEL}_{ID}"
MODULE = "homeassistant.components.yeelight"
MODULE_CONFIG_FLOW = f"{MODULE}.config_flow"
@ -53,9 +55,10 @@ PROPERTIES = {
"current_brightness": "30",
}
ENTITY_BINARY_SENSOR = f"binary_sensor.{NAME}_nightlight"
ENTITY_LIGHT = f"light.{NAME}"
ENTITY_NIGHTLIGHT = f"light.{NAME}_nightlight"
ENTITY_BINARY_SENSOR = f"binary_sensor.{UNIQUE_NAME}_nightlight"
ENTITY_LIGHT = f"light.{UNIQUE_NAME}"
ENTITY_NIGHTLIGHT = f"light.{UNIQUE_NAME}_nightlight"
ENTITY_AMBILIGHT = f"light.{UNIQUE_NAME}_ambilight"
YAML_CONFIGURATION = {
DOMAIN: {
@ -80,6 +83,9 @@ def _mocked_bulb(cannot_connect=False):
type(bulb).get_capabilities = MagicMock(
return_value=None if cannot_connect else CAPABILITIES
)
type(bulb).get_properties = MagicMock(
side_effect=BulbException if cannot_connect else None
)
type(bulb).get_model_specs = MagicMock(return_value=_MODEL_SPECS[MODEL])
bulb.capabilities = CAPABILITIES
@ -92,6 +98,8 @@ def _mocked_bulb(cannot_connect=False):
def _patch_discovery(prefix, no_device=False):
YeelightScanner._scanner = None # Clear class scanner to reset hass
def _mocked_discovery(timeout=2, interface=False):
if no_device:
return []

View file

@ -4,10 +4,12 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_component
from homeassistant.setup import async_setup_component
from . import ENTITY_BINARY_SENSOR, MODULE, PROPERTIES, YAML_CONFIGURATION, _mocked_bulb
from . import MODULE, NAME, PROPERTIES, YAML_CONFIGURATION, _mocked_bulb
from tests.async_mock import patch
ENTITY_BINARY_SENSOR = f"binary_sensor.{NAME}_nightlight"
async def test_nightlight(hass: HomeAssistant):
"""Test nightlight sensor."""

View file

@ -25,6 +25,7 @@ from . import (
MODULE,
MODULE_CONFIG_FLOW,
NAME,
UNIQUE_NAME,
_mocked_bulb,
_patch_discovery,
)
@ -33,7 +34,6 @@ from tests.async_mock import MagicMock, patch
from tests.common import MockConfigEntry
DEFAULT_CONFIG = {
CONF_NAME: NAME,
CONF_MODEL: "",
CONF_TRANSITION: DEFAULT_TRANSITION,
CONF_MODE_MUSIC: DEFAULT_MODE_MUSIC,
@ -67,9 +67,8 @@ async def test_discovery(hass: HomeAssistant):
result3 = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_DEVICE: ID}
)
assert result3["type"] == "create_entry"
assert result3["title"] == NAME
assert result3["title"] == UNIQUE_NAME
assert result3["data"] == {CONF_ID: ID}
await hass.async_block_till_done()
mock_setup.assert_called_once()
@ -126,6 +125,7 @@ async def test_import(hass: HomeAssistant):
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=config
)
type(mocked_bulb).get_capabilities.assert_called_once()
type(mocked_bulb).get_properties.assert_called_once()
assert result["type"] == "abort"
assert result["reason"] == "cannot_connect"
@ -203,7 +203,9 @@ async def test_manual(hass: HomeAssistant):
result4 = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_HOST: IP_ADDRESS}
)
await hass.async_block_till_done()
assert result4["type"] == "create_entry"
assert result4["title"] == IP_ADDRESS
assert result4["data"] == {CONF_HOST: IP_ADDRESS}
# Duplicate
@ -221,7 +223,9 @@ async def test_manual(hass: HomeAssistant):
async def test_options(hass: HomeAssistant):
"""Test options flow."""
config_entry = MockConfigEntry(domain=DOMAIN, data={CONF_HOST: IP_ADDRESS})
config_entry = MockConfigEntry(
domain=DOMAIN, data={CONF_HOST: IP_ADDRESS, CONF_NAME: NAME}
)
config_entry.add_to_hass(hass)
mocked_bulb = _mocked_bulb()
@ -230,16 +234,14 @@ async def test_options(hass: HomeAssistant):
await hass.async_block_till_done()
config = {
CONF_NAME: NAME,
CONF_MODEL: "",
CONF_TRANSITION: DEFAULT_TRANSITION,
CONF_MODE_MUSIC: DEFAULT_MODE_MUSIC,
CONF_SAVE_ON_CHANGE: DEFAULT_SAVE_ON_CHANGE,
CONF_NIGHTLIGHT_SWITCH: DEFAULT_NIGHTLIGHT_SWITCH,
}
assert config_entry.options == {
CONF_NAME: "",
**config,
}
assert config_entry.options == config
assert hass.states.get(f"light.{NAME}_nightlight") is None
result = await hass.config_entries.options.async_init(config_entry.entry_id)
@ -247,15 +249,40 @@ async def test_options(hass: HomeAssistant):
assert result["step_id"] == "init"
config[CONF_NIGHTLIGHT_SWITCH] = True
user_input = {**config}
user_input.pop(CONF_NAME)
with patch(f"{MODULE}.Bulb", return_value=mocked_bulb):
result2 = await hass.config_entries.options.async_configure(
result["flow_id"], config
result["flow_id"], user_input
)
await hass.async_block_till_done()
assert result2["type"] == "create_entry"
assert result2["data"] == {
CONF_NAME: "",
**config,
}
assert result2["data"] == config
assert result2["data"] == config_entry.options
assert hass.states.get(f"light.{NAME}_nightlight") is not None
async def test_manual_no_capabilities(hass: HomeAssistant):
"""Test manually setup without successful get_capabilities."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == "form"
assert result["step_id"] == "user"
assert not result["errors"]
mocked_bulb = _mocked_bulb()
type(mocked_bulb).get_capabilities = MagicMock(return_value=None)
with patch(f"{MODULE_CONFIG_FLOW}.yeelight.Bulb", return_value=mocked_bulb), patch(
f"{MODULE}.async_setup", return_value=True
), patch(
f"{MODULE}.async_setup_entry",
return_value=True,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_HOST: IP_ADDRESS}
)
type(mocked_bulb).get_capabilities.assert_called_once()
type(mocked_bulb).get_properties.assert_called_once()
assert result["type"] == "create_entry"
assert result["data"] == {CONF_HOST: IP_ADDRESS}

View file

@ -1,19 +1,27 @@
"""Test Yeelight."""
from yeelight import BulbType
from homeassistant.components.yeelight import (
CONF_NIGHTLIGHT_SWITCH,
CONF_NIGHTLIGHT_SWITCH_TYPE,
DOMAIN,
NIGHTLIGHT_SWITCH_TYPE_LIGHT,
)
from homeassistant.const import CONF_DEVICES, CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry
from homeassistant.setup import async_setup_component
from . import (
CONFIG_ENTRY_DATA,
ENTITY_AMBILIGHT,
ENTITY_BINARY_SENSOR,
ENTITY_LIGHT,
ENTITY_NIGHTLIGHT,
ID,
IP_ADDRESS,
MODULE,
MODULE_CONFIG_FLOW,
NAME,
_mocked_bulb,
_patch_discovery,
)
@ -32,13 +40,13 @@ async def test_setup_discovery(hass: HomeAssistant):
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert hass.states.get(f"binary_sensor.{NAME}_nightlight") is not None
assert hass.states.get(f"light.{NAME}") is not None
assert hass.states.get(ENTITY_BINARY_SENSOR) is not None
assert hass.states.get(ENTITY_LIGHT) is not None
# Unload
assert await hass.config_entries.async_unload(config_entry.entry_id)
assert hass.states.get(f"binary_sensor.{NAME}_nightlight") is None
assert hass.states.get(f"light.{NAME}") is None
assert hass.states.get(ENTITY_BINARY_SENSOR) is None
assert hass.states.get(ENTITY_LIGHT) is None
async def test_setup_import(hass: HomeAssistant):
@ -67,3 +75,57 @@ async def test_setup_import(hass: HomeAssistant):
assert hass.states.get(f"binary_sensor.{name}_nightlight") is not None
assert hass.states.get(f"light.{name}") is not None
assert hass.states.get(f"light.{name}_nightlight") is not None
async def test_unique_ids_device(hass: HomeAssistant):
"""Test Yeelight unique IDs from yeelight device IDs."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data={
**CONFIG_ENTRY_DATA,
CONF_NIGHTLIGHT_SWITCH: True,
},
unique_id=ID,
)
config_entry.add_to_hass(hass)
mocked_bulb = _mocked_bulb()
mocked_bulb.bulb_type = BulbType.WhiteTempMood
with _patch_discovery(MODULE), patch(f"{MODULE}.Bulb", return_value=mocked_bulb):
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
er = await entity_registry.async_get_registry(hass)
assert er.async_get(ENTITY_BINARY_SENSOR).unique_id == ID
assert er.async_get(ENTITY_LIGHT).unique_id == ID
assert er.async_get(ENTITY_NIGHTLIGHT).unique_id == f"{ID}-nightlight"
assert er.async_get(ENTITY_AMBILIGHT).unique_id == f"{ID}-ambilight"
async def test_unique_ids_entry(hass: HomeAssistant):
"""Test Yeelight unique IDs from entry IDs."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data={
**CONFIG_ENTRY_DATA,
CONF_NIGHTLIGHT_SWITCH: True,
},
)
config_entry.add_to_hass(hass)
mocked_bulb = _mocked_bulb()
mocked_bulb.bulb_type = BulbType.WhiteTempMood
with _patch_discovery(MODULE), patch(f"{MODULE}.Bulb", return_value=mocked_bulb):
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
er = await entity_registry.async_get_registry(hass)
assert er.async_get(ENTITY_BINARY_SENSOR).unique_id == config_entry.entry_id
assert er.async_get(ENTITY_LIGHT).unique_id == config_entry.entry_id
assert (
er.async_get(ENTITY_NIGHTLIGHT).unique_id
== f"{config_entry.entry_id}-nightlight"
)
assert (
er.async_get(ENTITY_AMBILIGHT).unique_id == f"{config_entry.entry_id}-ambilight"
)

View file

@ -71,8 +71,9 @@ from homeassistant.components.yeelight.light import (
YEELIGHT_MONO_EFFECT_LIST,
YEELIGHT_TEMP_ONLY_EFFECT_LIST,
)
from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, CONF_ID, CONF_NAME
from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry
from homeassistant.setup import async_setup_component
from homeassistant.util.color import (
color_hs_to_RGB,
@ -90,6 +91,7 @@ from . import (
MODULE,
NAME,
PROPERTIES,
UNIQUE_NAME,
_mocked_bulb,
_patch_discovery,
)
@ -97,15 +99,21 @@ from . import (
from tests.async_mock import MagicMock, patch
from tests.common import MockConfigEntry
CONFIG_ENTRY_DATA = {
CONF_HOST: IP_ADDRESS,
CONF_TRANSITION: DEFAULT_TRANSITION,
CONF_MODE_MUSIC: DEFAULT_MODE_MUSIC,
CONF_SAVE_ON_CHANGE: DEFAULT_SAVE_ON_CHANGE,
CONF_NIGHTLIGHT_SWITCH: DEFAULT_NIGHTLIGHT_SWITCH,
}
async def test_services(hass: HomeAssistant, caplog):
"""Test Yeelight services."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_ID: "",
CONF_HOST: IP_ADDRESS,
CONF_TRANSITION: DEFAULT_TRANSITION,
**CONFIG_ENTRY_DATA,
CONF_MODE_MUSIC: True,
CONF_SAVE_ON_CHANGE: True,
CONF_NIGHTLIGHT_SWITCH: True,
@ -299,17 +307,13 @@ async def test_device_types(hass: HomeAssistant):
model,
target_properties,
nightlight_properties=None,
name=NAME,
name=UNIQUE_NAME,
entity_id=ENTITY_LIGHT,
):
config_entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_ID: "",
CONF_HOST: IP_ADDRESS,
CONF_TRANSITION: DEFAULT_TRANSITION,
CONF_MODE_MUSIC: DEFAULT_MODE_MUSIC,
CONF_SAVE_ON_CHANGE: DEFAULT_SAVE_ON_CHANGE,
**CONFIG_ENTRY_DATA,
CONF_NIGHTLIGHT_SWITCH: False,
},
)
@ -329,6 +333,8 @@ async def test_device_types(hass: HomeAssistant):
await hass.config_entries.async_unload(config_entry.entry_id)
await config_entry.async_remove(hass)
registry = await entity_registry.async_get_registry(hass)
registry.async_clear_config_entry(config_entry.entry_id)
# nightlight
if nightlight_properties is None:
@ -336,11 +342,7 @@ async def test_device_types(hass: HomeAssistant):
config_entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_ID: "",
CONF_HOST: IP_ADDRESS,
CONF_TRANSITION: DEFAULT_TRANSITION,
CONF_MODE_MUSIC: DEFAULT_MODE_MUSIC,
CONF_SAVE_ON_CHANGE: DEFAULT_SAVE_ON_CHANGE,
**CONFIG_ENTRY_DATA,
CONF_NIGHTLIGHT_SWITCH: True,
},
)
@ -358,6 +360,7 @@ async def test_device_types(hass: HomeAssistant):
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)
bright = round(255 * int(PROPERTIES["bright"]) / 100)
current_brightness = round(255 * int(PROPERTIES["current_brightness"]) / 100)
@ -486,7 +489,7 @@ async def test_device_types(hass: HomeAssistant):
"rgb_color": bg_rgb_color,
"xy_color": bg_xy_color,
},
name=f"{NAME} ambilight",
name=f"{UNIQUE_NAME} ambilight",
entity_id=f"{ENTITY_LIGHT}_ambilight",
)
@ -518,14 +521,7 @@ async def test_effects(hass: HomeAssistant):
config_entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_ID: "",
CONF_HOST: IP_ADDRESS,
CONF_TRANSITION: DEFAULT_TRANSITION,
CONF_MODE_MUSIC: DEFAULT_MODE_MUSIC,
CONF_SAVE_ON_CHANGE: DEFAULT_SAVE_ON_CHANGE,
CONF_NIGHTLIGHT_SWITCH: DEFAULT_NIGHTLIGHT_SWITCH,
},
data=CONFIG_ENTRY_DATA,
)
config_entry.add_to_hass(hass)