Improve Yeelight code (#39543)

* Rename ipaddr to ip_addr

* Move custom services to entity services

* Remove platform data

* Change service setup to callback

* Rename ip_addr to host

* Use _host inside class
This commit is contained in:
Xiaonan Shen 2020-09-03 00:42:12 +08:00 committed by GitHub
parent 04c849b0ee
commit 7b3182fa8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 173 additions and 205 deletions

View File

@ -9,10 +9,9 @@ from yeelight import Bulb, BulbException, discover_bulbs
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry, ConfigEntryNotReady from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry, ConfigEntryNotReady
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID,
CONF_DEVICES, CONF_DEVICES,
CONF_HOST,
CONF_ID, CONF_ID,
CONF_IP_ADDRESS,
CONF_NAME, CONF_NAME,
CONF_SCAN_INTERVAL, CONF_SCAN_INTERVAL,
) )
@ -126,8 +125,6 @@ CONFIG_SCHEMA = vol.Schema(
extra=vol.ALLOW_EXTRA, extra=vol.ALLOW_EXTRA,
) )
YEELIGHT_SERVICE_SCHEMA = vol.Schema({vol.Required(ATTR_ENTITY_ID): cv.entity_ids})
UPDATE_REQUEST_PROPERTIES = [ UPDATE_REQUEST_PROPERTIES = [
"power", "power",
"main_power", "main_power",
@ -163,10 +160,10 @@ async def async_setup(hass: HomeAssistant, config: dict) -> bool:
} }
# Import manually configured devices # Import manually configured devices
for ipaddr, device_config in config.get(DOMAIN, {}).get(CONF_DEVICES, {}).items(): for host, device_config in config.get(DOMAIN, {}).get(CONF_DEVICES, {}).items():
_LOGGER.debug("Importing configured %s", ipaddr) _LOGGER.debug("Importing configured %s", host)
entry_config = { entry_config = {
CONF_IP_ADDRESS: ipaddr, CONF_HOST: host,
**device_config, **device_config,
} }
hass.async_create_task( hass.async_create_task(
@ -183,8 +180,8 @@ async def async_setup(hass: HomeAssistant, config: dict) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Yeelight from a config entry.""" """Set up Yeelight from a config entry."""
async def _initialize(ipaddr: str) -> None: async def _initialize(host: str) -> None:
device = await _async_setup_device(hass, ipaddr, entry.options) device = await _async_setup_device(hass, host, entry.options)
hass.data[DOMAIN][DATA_CONFIG_ENTRIES][entry.entry_id][DATA_DEVICE] = device hass.data[DOMAIN][DATA_CONFIG_ENTRIES][entry.entry_id][DATA_DEVICE] = device
for component in PLATFORMS: for component in PLATFORMS:
hass.async_create_task( hass.async_create_task(
@ -197,7 +194,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.config_entries.async_update_entry( hass.config_entries.async_update_entry(
entry, entry,
data={ data={
CONF_IP_ADDRESS: entry.data.get(CONF_IP_ADDRESS), CONF_HOST: entry.data.get(CONF_HOST),
CONF_ID: entry.data.get(CONF_ID), CONF_ID: entry.data.get(CONF_ID),
}, },
options={ options={
@ -218,9 +215,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
DATA_UNSUB_UPDATE_LISTENER: entry.add_update_listener(_async_update_listener) DATA_UNSUB_UPDATE_LISTENER: entry.add_update_listener(_async_update_listener)
} }
if entry.data.get(CONF_IP_ADDRESS): if entry.data.get(CONF_HOST):
# manually added device # manually added device
await _initialize(entry.data[CONF_IP_ADDRESS]) await _initialize(entry.data[CONF_HOST])
else: else:
# discovery # discovery
scanner = YeelightScanner.async_get(hass) scanner = YeelightScanner.async_get(hass)
@ -254,16 +251,16 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
async def _async_setup_device( async def _async_setup_device(
hass: HomeAssistant, hass: HomeAssistant,
ipaddr: str, host: str,
config: dict, config: dict,
) -> None: ) -> None:
# Set up device # Set up device
bulb = Bulb(ipaddr, model=config.get(CONF_MODEL) or None) bulb = Bulb(host, model=config.get(CONF_MODEL) or None)
capabilities = await hass.async_add_executor_job(bulb.get_capabilities) capabilities = await hass.async_add_executor_job(bulb.get_capabilities)
if capabilities is None: # timeout if capabilities is None: # timeout
_LOGGER.error("Failed to get capabilities from %s", ipaddr) _LOGGER.error("Failed to get capabilities from %s", host)
raise ConfigEntryNotReady raise ConfigEntryNotReady
device = YeelightDevice(hass, ipaddr, config, bulb) device = YeelightDevice(hass, host, config, bulb)
await hass.async_add_executor_job(device.update) await hass.async_add_executor_job(device.update)
await device.async_setup() await device.async_setup()
return device return device
@ -303,11 +300,11 @@ class YeelightScanner:
unique_id = device["capabilities"]["id"] unique_id = device["capabilities"]["id"]
if unique_id in self._seen: if unique_id in self._seen:
continue continue
ipaddr = device["ip"] host = device["ip"]
self._seen[unique_id] = ipaddr self._seen[unique_id] = host
_LOGGER.debug("Yeelight discovered at %s", ipaddr) _LOGGER.debug("Yeelight discovered at %s", host)
if unique_id in self._callbacks: if unique_id in self._callbacks:
self._hass.async_create_task(self._callbacks[unique_id](ipaddr)) self._hass.async_create_task(self._callbacks[unique_id](host))
self._callbacks.pop(unique_id) self._callbacks.pop(unique_id)
if len(self._callbacks) == 0: if len(self._callbacks) == 0:
self._async_stop_scan() self._async_stop_scan()
@ -333,9 +330,9 @@ class YeelightScanner:
@callback @callback
def async_register_callback(self, unique_id, callback_func): def async_register_callback(self, unique_id, callback_func):
"""Register callback function.""" """Register callback function."""
ipaddr = self._seen.get(unique_id) host = self._seen.get(unique_id)
if ipaddr is not None: if host is not None:
self._hass.async_add_job(callback_func(ipaddr)) self._hass.async_add_job(callback_func(host))
else: else:
self._callbacks[unique_id] = callback_func self._callbacks[unique_id] = callback_func
if len(self._callbacks) == 1: if len(self._callbacks) == 1:
@ -354,11 +351,11 @@ class YeelightScanner:
class YeelightDevice: class YeelightDevice:
"""Represents single Yeelight device.""" """Represents single Yeelight device."""
def __init__(self, hass, ipaddr, config, bulb): def __init__(self, hass, host, config, bulb):
"""Initialize device.""" """Initialize device."""
self._hass = hass self._hass = hass
self._config = config self._config = config
self._ipaddr = ipaddr self._host = host
unique_id = bulb.capabilities.get("id") unique_id = bulb.capabilities.get("id")
self._name = config.get(CONF_NAME) or f"yeelight_{bulb.model}_{unique_id}" self._name = config.get(CONF_NAME) or f"yeelight_{bulb.model}_{unique_id}"
self._bulb_device = bulb self._bulb_device = bulb
@ -382,9 +379,9 @@ class YeelightDevice:
return self._config return self._config
@property @property
def ipaddr(self): def host(self):
"""Return ip address.""" """Return hostname."""
return self._ipaddr return self._host
@property @property
def available(self): def available(self):
@ -472,7 +469,7 @@ class YeelightDevice:
self.bulb.turn_off(duration=duration, light_type=light_type) self.bulb.turn_off(duration=duration, light_type=light_type)
except BulbException as ex: except BulbException as ex:
_LOGGER.error( _LOGGER.error(
"Unable to turn the bulb off: %s, %s: %s", self.ipaddr, self.name, ex "Unable to turn the bulb off: %s, %s: %s", self._host, self.name, ex
) )
def _update_properties(self): def _update_properties(self):
@ -486,7 +483,7 @@ class YeelightDevice:
except BulbException as ex: except BulbException as ex:
if self._available: # just inform once if self._available: # just inform once
_LOGGER.error( _LOGGER.error(
"Unable to update device %s, %s: %s", self.ipaddr, self.name, ex "Unable to update device %s, %s: %s", self._host, self.name, ex
) )
self._available = False self._available = False
@ -498,14 +495,14 @@ class YeelightDevice:
self.bulb.get_capabilities() self.bulb.get_capabilities()
_LOGGER.debug( _LOGGER.debug(
"Device %s, %s capabilities: %s", "Device %s, %s capabilities: %s",
self.ipaddr, self._host,
self.name, self.name,
self.bulb.capabilities, self.bulb.capabilities,
) )
except BulbException as ex: except BulbException as ex:
_LOGGER.error( _LOGGER.error(
"Unable to get device capabilities %s, %s: %s", "Unable to get device capabilities %s, %s: %s",
self.ipaddr, self._host,
self.name, self.name,
ex, ex,
) )
@ -513,7 +510,7 @@ class YeelightDevice:
def update(self): def update(self):
"""Update device properties and send data updated signal.""" """Update device properties and send data updated signal."""
self._update_properties() self._update_properties()
dispatcher_send(self._hass, DATA_UPDATED.format(self._ipaddr)) dispatcher_send(self._hass, DATA_UPDATED.format(self._host))
async def async_setup(self): async def async_setup(self):
"""Set up the device.""" """Set up the device."""

View File

@ -30,7 +30,7 @@ class YeelightNightlightModeSensor(YeelightEntity, BinarySensorEntity):
self.async_on_remove( self.async_on_remove(
async_dispatcher_connect( async_dispatcher_connect(
self.hass, self.hass,
DATA_UPDATED.format(self._device.ipaddr), DATA_UPDATED.format(self._device.host),
self.async_write_ha_state, self.async_write_ha_state,
) )
) )

View File

@ -5,7 +5,7 @@ import voluptuous as vol
import yeelight import yeelight
from homeassistant import config_entries, exceptions from homeassistant import config_entries, exceptions
from homeassistant.const import CONF_ID, CONF_IP_ADDRESS, CONF_NAME from homeassistant.const import CONF_HOST, CONF_ID, CONF_NAME
from homeassistant.core import callback from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -45,9 +45,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle the initial step.""" """Handle the initial step."""
errors = {} errors = {}
if user_input is not None: if user_input is not None:
if user_input.get(CONF_IP_ADDRESS): if user_input.get(CONF_HOST):
try: try:
await self._async_try_connect(user_input[CONF_IP_ADDRESS]) await self._async_try_connect(user_input[CONF_HOST])
return self.async_create_entry( return self.async_create_entry(
title=self._async_default_name(), title=self._async_default_name(),
data=user_input, data=user_input,
@ -61,7 +61,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
return self.async_show_form( return self.async_show_form(
step_id="user", step_id="user",
data_schema=vol.Schema({vol.Optional(CONF_IP_ADDRESS): str}), data_schema=vol.Schema({vol.Optional(CONF_HOST): str}),
errors=errors, errors=errors,
) )
@ -90,8 +90,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
if unique_id in configured_devices: if unique_id in configured_devices:
continue # ignore configured devices continue # ignore configured devices
model = capabilities["model"] model = capabilities["model"]
ipaddr = device["ip"] host = device["ip"]
name = f"{ipaddr} {model} {unique_id}" name = f"{host} {model} {unique_id}"
self._discovered_devices[unique_id] = capabilities self._discovered_devices[unique_id] = capabilities
devices_name[unique_id] = name devices_name[unique_id] = name
@ -105,11 +105,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_import(self, user_input=None): async def async_step_import(self, user_input=None):
"""Handle import step.""" """Handle import step."""
ipaddr = user_input[CONF_IP_ADDRESS] host = user_input[CONF_HOST]
try: try:
await self._async_try_connect(ipaddr) await self._async_try_connect(host)
except CannotConnect: except CannotConnect:
_LOGGER.error("Failed to import %s: cannot connect", ipaddr) _LOGGER.error("Failed to import %s: cannot connect", host)
return self.async_abort(reason="cannot_connect") return self.async_abort(reason="cannot_connect")
except AlreadyConfigured: except AlreadyConfigured:
return self.async_abort(reason="already_configured") return self.async_abort(reason="already_configured")
@ -120,16 +120,16 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
) )
return self.async_create_entry(title=user_input[CONF_NAME], data=user_input) return self.async_create_entry(title=user_input[CONF_NAME], data=user_input)
async def _async_try_connect(self, ipaddr): async def _async_try_connect(self, host):
"""Set up with options.""" """Set up with options."""
bulb = yeelight.Bulb(ipaddr) bulb = yeelight.Bulb(host)
try: try:
capabilities = await self.hass.async_add_executor_job(bulb.get_capabilities) capabilities = await self.hass.async_add_executor_job(bulb.get_capabilities)
if capabilities is None: # timeout if capabilities is None: # timeout
_LOGGER.error("Failed to get capabilities from %s: timeout", ipaddr) _LOGGER.error("Failed to get capabilities from %s: timeout", host)
raise CannotConnect raise CannotConnect
except OSError as err: except OSError as err:
_LOGGER.error("Failed to get capabilities from %s: %s", ipaddr, err) _LOGGER.error("Failed to get capabilities from %s: %s", host, err)
raise CannotConnect from err raise CannotConnect from err
_LOGGER.debug("Get capabilities: %s", capabilities) _LOGGER.debug("Get capabilities: %s", capabilities)
self._capabilities = capabilities self._capabilities = capabilities

View File

@ -36,9 +36,9 @@ from homeassistant.components.light import (
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_ENTITY_ID, ATTR_MODE, CONF_NAME from homeassistant.const import ATTR_ENTITY_ID, ATTR_MODE, CONF_NAME
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_platform
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.service import extract_entity_ids
import homeassistant.util.color as color_util import homeassistant.util.color as color_util
from homeassistant.util.color import ( from homeassistant.util.color import (
color_temperature_kelvin_to_mired as kelvin_to_mired, color_temperature_kelvin_to_mired as kelvin_to_mired,
@ -59,17 +59,13 @@ from . import (
DATA_CUSTOM_EFFECTS, DATA_CUSTOM_EFFECTS,
DATA_DEVICE, DATA_DEVICE,
DATA_UPDATED, DATA_UPDATED,
DATA_YEELIGHT,
DOMAIN, DOMAIN,
YEELIGHT_FLOW_TRANSITION_SCHEMA, YEELIGHT_FLOW_TRANSITION_SCHEMA,
YEELIGHT_SERVICE_SCHEMA,
YeelightEntity, YeelightEntity,
) )
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
PLATFORM_DATA_KEY = f"{DATA_YEELIGHT}_lights"
SUPPORT_YEELIGHT = ( SUPPORT_YEELIGHT = (
SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION | SUPPORT_FLASH | SUPPORT_EFFECT SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION | SUPPORT_FLASH | SUPPORT_EFFECT
) )
@ -148,25 +144,20 @@ EFFECTS_MAP = {
VALID_BRIGHTNESS = vol.All(vol.Coerce(int), vol.Range(min=1, max=100)) VALID_BRIGHTNESS = vol.All(vol.Coerce(int), vol.Range(min=1, max=100))
SERVICE_SCHEMA_SET_MODE = YEELIGHT_SERVICE_SCHEMA.extend( SERVICE_SCHEMA_SET_MODE = {
{vol.Required(ATTR_MODE): vol.In([mode.name.lower() for mode in PowerMode])} vol.Required(ATTR_MODE): vol.In([mode.name.lower() for mode in PowerMode])
) }
SERVICE_SCHEMA_START_FLOW = YEELIGHT_SERVICE_SCHEMA.extend( SERVICE_SCHEMA_START_FLOW = YEELIGHT_FLOW_TRANSITION_SCHEMA
YEELIGHT_FLOW_TRANSITION_SCHEMA
)
SERVICE_SCHEMA_SET_COLOR_SCENE = YEELIGHT_SERVICE_SCHEMA.extend( SERVICE_SCHEMA_SET_COLOR_SCENE = {
{
vol.Required(ATTR_RGB_COLOR): vol.All( vol.Required(ATTR_RGB_COLOR): vol.All(
vol.ExactSequence((cv.byte, cv.byte, cv.byte)), vol.Coerce(tuple) vol.ExactSequence((cv.byte, cv.byte, cv.byte)), vol.Coerce(tuple)
), ),
vol.Required(ATTR_BRIGHTNESS): VALID_BRIGHTNESS, vol.Required(ATTR_BRIGHTNESS): VALID_BRIGHTNESS,
} }
)
SERVICE_SCHEMA_SET_HSV_SCENE = YEELIGHT_SERVICE_SCHEMA.extend( SERVICE_SCHEMA_SET_HSV_SCENE = {
{
vol.Required(ATTR_HS_COLOR): vol.All( vol.Required(ATTR_HS_COLOR): vol.All(
vol.ExactSequence( vol.ExactSequence(
( (
@ -177,30 +168,22 @@ SERVICE_SCHEMA_SET_HSV_SCENE = YEELIGHT_SERVICE_SCHEMA.extend(
vol.Coerce(tuple), vol.Coerce(tuple),
), ),
vol.Required(ATTR_BRIGHTNESS): VALID_BRIGHTNESS, vol.Required(ATTR_BRIGHTNESS): VALID_BRIGHTNESS,
} }
)
SERVICE_SCHEMA_SET_COLOR_TEMP_SCENE = YEELIGHT_SERVICE_SCHEMA.extend( SERVICE_SCHEMA_SET_COLOR_TEMP_SCENE = {
{ vol.Required(ATTR_KELVIN): vol.All(vol.Coerce(int), vol.Range(min=1700, max=6500)),
vol.Required(ATTR_KELVIN): vol.All(
vol.Coerce(int), vol.Range(min=1700, max=6500)
),
vol.Required(ATTR_BRIGHTNESS): VALID_BRIGHTNESS, vol.Required(ATTR_BRIGHTNESS): VALID_BRIGHTNESS,
} }
)
SERVICE_SCHEMA_SET_COLOR_FLOW_SCENE = YEELIGHT_SERVICE_SCHEMA.extend( SERVICE_SCHEMA_SET_COLOR_FLOW_SCENE = YEELIGHT_FLOW_TRANSITION_SCHEMA
YEELIGHT_FLOW_TRANSITION_SCHEMA
)
SERVICE_SCHEMA_SET_AUTO_DELAY_OFF = YEELIGHT_SERVICE_SCHEMA.extend( SERVICE_SCHEMA_SET_AUTO_DELAY_OFF_SCENE = {
{
vol.Required(ATTR_MINUTES): vol.All(vol.Coerce(int), vol.Range(min=1, max=60)), vol.Required(ATTR_MINUTES): vol.All(vol.Coerce(int), vol.Range(min=1, max=60)),
vol.Required(ATTR_BRIGHTNESS): VALID_BRIGHTNESS, vol.Required(ATTR_BRIGHTNESS): VALID_BRIGHTNESS,
} }
)
@callback
def _transitions_config_parser(transitions): def _transitions_config_parser(transitions):
"""Parse transitions config into initialized objects.""" """Parse transitions config into initialized objects."""
transition_objects = [] transition_objects = []
@ -211,6 +194,7 @@ def _transitions_config_parser(transitions):
return transition_objects return transition_objects
@callback
def _parse_custom_effects(effects_config): def _parse_custom_effects(effects_config):
effects = {} effects = {}
for config in effects_config: for config in effects_config:
@ -245,9 +229,6 @@ async def async_setup_entry(
) -> None: ) -> None:
"""Set up Yeelight from a config entry.""" """Set up Yeelight from a config entry."""
if PLATFORM_DATA_KEY not in hass.data:
hass.data[PLATFORM_DATA_KEY] = []
custom_effects = _parse_custom_effects(hass.data[DOMAIN][DATA_CUSTOM_EFFECTS]) custom_effects = _parse_custom_effects(hass.data[DOMAIN][DATA_CUSTOM_EFFECTS])
device = hass.data[DOMAIN][DATA_CONFIG_ENTRIES][config_entry.entry_id][DATA_DEVICE] device = hass.data[DOMAIN][DATA_CONFIG_ENTRIES][config_entry.entry_id][DATA_DEVICE]
@ -287,124 +268,114 @@ async def async_setup_entry(
_lights_setup_helper(YeelightGenericLight) _lights_setup_helper(YeelightGenericLight)
_LOGGER.warning( _LOGGER.warning(
"Cannot determine device type for %s, %s. Falling back to white only", "Cannot determine device type for %s, %s. Falling back to white only",
device.ipaddr, device.host,
device.name, device.name,
) )
hass.data[PLATFORM_DATA_KEY] += lights
async_add_entities(lights, True) async_add_entities(lights, True)
await hass.async_add_executor_job(partial(setup_services, hass)) _async_setup_services(hass)
def setup_services(hass): @callback
"""Set up the service listeners.""" def _async_setup_services(hass: HomeAssistant):
"""Set up custom services."""
def service_call(func): async def _async_start_flow(entity, service_call):
def service_to_entities(service): params = {**service_call.data}
"""Return the known entities that a service call mentions.""" params.pop(ATTR_ENTITY_ID)
entity_ids = extract_entity_ids(hass, service)
target_devices = [
light
for light in hass.data[PLATFORM_DATA_KEY]
if light.entity_id in entity_ids
]
return target_devices
def service_to_params(service):
"""Return service call params, without entity_id."""
return {
key: value
for key, value in service.data.items()
if key != ATTR_ENTITY_ID
}
def wrapper(service):
params = service_to_params(service)
target_devices = service_to_entities(service)
for device in target_devices:
func(device, params)
return wrapper
@service_call
def service_set_mode(target_device, params):
target_device.set_mode(**params)
@service_call
def service_start_flow(target_devices, params):
params[ATTR_TRANSITIONS] = _transitions_config_parser(params[ATTR_TRANSITIONS]) params[ATTR_TRANSITIONS] = _transitions_config_parser(params[ATTR_TRANSITIONS])
target_devices.start_flow(**params) await hass.async_add_executor_job(partial(entity.start_flow, **params))
@service_call async def _async_set_color_scene(entity, service_call):
def service_set_color_scene(target_device, params): await hass.async_add_executor_job(
target_device.set_scene( partial(
SceneClass.COLOR, *[*params[ATTR_RGB_COLOR], params[ATTR_BRIGHTNESS]] entity.set_scene,
SceneClass.COLOR,
*service_call.data[ATTR_RGB_COLOR],
service_call.data[ATTR_BRIGHTNESS],
)
) )
@service_call async def _async_set_hsv_scene(entity, service_call):
def service_set_hsv_scene(target_device, params): await hass.async_add_executor_job(
target_device.set_scene( partial(
SceneClass.HSV, *[*params[ATTR_HS_COLOR], params[ATTR_BRIGHTNESS]] entity.set_scene,
SceneClass.HSV,
*service_call.data[ATTR_HS_COLOR],
service_call.data[ATTR_BRIGHTNESS],
)
) )
@service_call async def _async_set_color_temp_scene(entity, service_call):
def service_set_color_temp_scene(target_device, params): await hass.async_add_executor_job(
target_device.set_scene( partial(
SceneClass.CT, params[ATTR_KELVIN], params[ATTR_BRIGHTNESS] entity.set_scene,
SceneClass.CT,
service_call.data[ATTR_KELVIN],
service_call.data[ATTR_BRIGHTNESS],
)
) )
@service_call async def _async_set_color_flow_scene(entity, service_call):
def service_set_color_flow_scene(target_device, params):
flow = Flow( flow = Flow(
count=params[ATTR_COUNT], count=service_call.data[ATTR_COUNT],
action=Flow.actions[params[ATTR_ACTION]], action=Flow.actions[service_call.data[ATTR_ACTION]],
transitions=_transitions_config_parser(params[ATTR_TRANSITIONS]), transitions=_transitions_config_parser(service_call.data[ATTR_TRANSITIONS]),
)
await hass.async_add_executor_job(
partial(
entity.set_scene,
SceneClass.CF,
flow,
) )
target_device.set_scene(SceneClass.CF, flow)
@service_call
def service_set_auto_delay_off_scene(target_device, params):
target_device.set_scene(
SceneClass.AUTO_DELAY_OFF, params[ATTR_BRIGHTNESS], params[ATTR_MINUTES]
) )
hass.services.register( async def _async_set_auto_delay_off_scene(entity, service_call):
DOMAIN, SERVICE_SET_MODE, service_set_mode, schema=SERVICE_SCHEMA_SET_MODE await hass.async_add_executor_job(
partial(
entity.set_scene,
SceneClass.AUTO_DELAY_OFF,
service_call.data[ATTR_BRIGHTNESS],
service_call.data[ATTR_MINUTES],
) )
hass.services.register(
DOMAIN, SERVICE_START_FLOW, service_start_flow, schema=SERVICE_SCHEMA_START_FLOW
) )
hass.services.register(
DOMAIN, platform = entity_platform.current_platform.get()
platform.async_register_entity_service(
SERVICE_SET_MODE,
SERVICE_SCHEMA_SET_MODE,
"set_mode",
)
platform.async_register_entity_service(
SERVICE_START_FLOW,
SERVICE_SCHEMA_START_FLOW,
_async_start_flow,
)
platform.async_register_entity_service(
SERVICE_SET_COLOR_SCENE, SERVICE_SET_COLOR_SCENE,
service_set_color_scene, SERVICE_SCHEMA_SET_COLOR_SCENE,
schema=SERVICE_SCHEMA_SET_COLOR_SCENE, _async_set_color_scene,
) )
hass.services.register( platform.async_register_entity_service(
DOMAIN,
SERVICE_SET_HSV_SCENE, SERVICE_SET_HSV_SCENE,
service_set_hsv_scene, SERVICE_SCHEMA_SET_HSV_SCENE,
schema=SERVICE_SCHEMA_SET_HSV_SCENE, _async_set_hsv_scene,
) )
hass.services.register( platform.async_register_entity_service(
DOMAIN,
SERVICE_SET_COLOR_TEMP_SCENE, SERVICE_SET_COLOR_TEMP_SCENE,
service_set_color_temp_scene, SERVICE_SCHEMA_SET_COLOR_TEMP_SCENE,
schema=SERVICE_SCHEMA_SET_COLOR_TEMP_SCENE, _async_set_color_temp_scene,
) )
hass.services.register( platform.async_register_entity_service(
DOMAIN,
SERVICE_SET_COLOR_FLOW_SCENE, SERVICE_SET_COLOR_FLOW_SCENE,
service_set_color_flow_scene, SERVICE_SCHEMA_SET_COLOR_FLOW_SCENE,
schema=SERVICE_SCHEMA_SET_COLOR_FLOW_SCENE, _async_set_color_flow_scene,
) )
hass.services.register( platform.async_register_entity_service(
DOMAIN,
SERVICE_SET_AUTO_DELAY_OFF_SCENE, SERVICE_SET_AUTO_DELAY_OFF_SCENE,
service_set_auto_delay_off_scene, SERVICE_SCHEMA_SET_AUTO_DELAY_OFF_SCENE,
schema=SERVICE_SCHEMA_SET_AUTO_DELAY_OFF, _async_set_auto_delay_off_scene,
) )
@ -442,7 +413,7 @@ class YeelightGenericLight(YeelightEntity, LightEntity):
self.async_on_remove( self.async_on_remove(
async_dispatcher_connect( async_dispatcher_connect(
self.hass, self.hass,
DATA_UPDATED.format(self._device.ipaddr), DATA_UPDATED.format(self._device.host),
self._schedule_immediate_update, self._schedule_immediate_update,
) )
) )

View File

@ -3,9 +3,9 @@
"config": { "config": {
"step": { "step": {
"user": { "user": {
"description": "If you leave IP address empty, discovery will be used to find devices.", "description": "If you leave the host empty, discovery will be used to find devices.",
"data": { "data": {
"ip_address": "[%key:common::config_flow::data::ip%]" "host": "[%key:common::config_flow::data::host%]"
} }
}, },
"pick_device": { "pick_device": {

View File

@ -16,7 +16,7 @@ from homeassistant.components.yeelight import (
DOMAIN, DOMAIN,
NIGHTLIGHT_SWITCH_TYPE_LIGHT, NIGHTLIGHT_SWITCH_TYPE_LIGHT,
) )
from homeassistant.const import CONF_ID, CONF_IP_ADDRESS, CONF_NAME from homeassistant.const import CONF_HOST, CONF_ID, CONF_NAME
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from . import ( from . import (
@ -112,7 +112,7 @@ async def test_import(hass: HomeAssistant):
"""Test import from yaml.""" """Test import from yaml."""
config = { config = {
CONF_NAME: DEFAULT_NAME, CONF_NAME: DEFAULT_NAME,
CONF_IP_ADDRESS: IP_ADDRESS, CONF_HOST: IP_ADDRESS,
CONF_TRANSITION: DEFAULT_TRANSITION, CONF_TRANSITION: DEFAULT_TRANSITION,
CONF_MODE_MUSIC: DEFAULT_MODE_MUSIC, CONF_MODE_MUSIC: DEFAULT_MODE_MUSIC,
CONF_SAVE_ON_CHANGE: DEFAULT_SAVE_ON_CHANGE, CONF_SAVE_ON_CHANGE: DEFAULT_SAVE_ON_CHANGE,
@ -145,7 +145,7 @@ async def test_import(hass: HomeAssistant):
assert result["title"] == DEFAULT_NAME assert result["title"] == DEFAULT_NAME
assert result["data"] == { assert result["data"] == {
CONF_NAME: DEFAULT_NAME, CONF_NAME: DEFAULT_NAME,
CONF_IP_ADDRESS: IP_ADDRESS, CONF_HOST: IP_ADDRESS,
CONF_TRANSITION: DEFAULT_TRANSITION, CONF_TRANSITION: DEFAULT_TRANSITION,
CONF_MODE_MUSIC: DEFAULT_MODE_MUSIC, CONF_MODE_MUSIC: DEFAULT_MODE_MUSIC,
CONF_SAVE_ON_CHANGE: DEFAULT_SAVE_ON_CHANGE, CONF_SAVE_ON_CHANGE: DEFAULT_SAVE_ON_CHANGE,
@ -178,7 +178,7 @@ async def test_manual(hass: HomeAssistant):
mocked_bulb = _mocked_bulb(cannot_connect=True) mocked_bulb = _mocked_bulb(cannot_connect=True)
with patch(f"{MODULE_CONFIG_FLOW}.yeelight.Bulb", return_value=mocked_bulb): with patch(f"{MODULE_CONFIG_FLOW}.yeelight.Bulb", return_value=mocked_bulb):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_IP_ADDRESS: IP_ADDRESS} result["flow_id"], {CONF_HOST: IP_ADDRESS}
) )
assert result2["type"] == "form" assert result2["type"] == "form"
assert result2["step_id"] == "user" assert result2["step_id"] == "user"
@ -188,7 +188,7 @@ async def test_manual(hass: HomeAssistant):
type(mocked_bulb).get_capabilities = MagicMock(side_effect=OSError) type(mocked_bulb).get_capabilities = MagicMock(side_effect=OSError)
with patch(f"{MODULE_CONFIG_FLOW}.yeelight.Bulb", return_value=mocked_bulb): with patch(f"{MODULE_CONFIG_FLOW}.yeelight.Bulb", return_value=mocked_bulb):
result3 = await hass.config_entries.flow.async_configure( result3 = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_IP_ADDRESS: IP_ADDRESS} result["flow_id"], {CONF_HOST: IP_ADDRESS}
) )
assert result3["errors"] == {"base": "cannot_connect"} assert result3["errors"] == {"base": "cannot_connect"}
@ -201,10 +201,10 @@ async def test_manual(hass: HomeAssistant):
return_value=True, return_value=True,
): ):
result4 = await hass.config_entries.flow.async_configure( result4 = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_IP_ADDRESS: IP_ADDRESS} result["flow_id"], {CONF_HOST: IP_ADDRESS}
) )
assert result4["type"] == "create_entry" assert result4["type"] == "create_entry"
assert result4["data"] == {CONF_IP_ADDRESS: IP_ADDRESS} assert result4["data"] == {CONF_HOST: IP_ADDRESS}
# Duplicate # Duplicate
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
@ -213,7 +213,7 @@ async def test_manual(hass: HomeAssistant):
mocked_bulb = _mocked_bulb() mocked_bulb = _mocked_bulb()
with patch(f"{MODULE_CONFIG_FLOW}.yeelight.Bulb", return_value=mocked_bulb): with patch(f"{MODULE_CONFIG_FLOW}.yeelight.Bulb", return_value=mocked_bulb):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_IP_ADDRESS: IP_ADDRESS} result["flow_id"], {CONF_HOST: IP_ADDRESS}
) )
assert result2["type"] == "abort" assert result2["type"] == "abort"
assert result2["reason"] == "already_configured" assert result2["reason"] == "already_configured"
@ -221,7 +221,7 @@ async def test_manual(hass: HomeAssistant):
async def test_options(hass: HomeAssistant): async def test_options(hass: HomeAssistant):
"""Test options flow.""" """Test options flow."""
config_entry = MockConfigEntry(domain=DOMAIN, data={CONF_IP_ADDRESS: IP_ADDRESS}) config_entry = MockConfigEntry(domain=DOMAIN, data={CONF_HOST: IP_ADDRESS})
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
mocked_bulb = _mocked_bulb() mocked_bulb = _mocked_bulb()

View File

@ -71,7 +71,7 @@ from homeassistant.components.yeelight.light import (
YEELIGHT_MONO_EFFECT_LIST, YEELIGHT_MONO_EFFECT_LIST,
YEELIGHT_TEMP_ONLY_EFFECT_LIST, YEELIGHT_TEMP_ONLY_EFFECT_LIST,
) )
from homeassistant.const import ATTR_ENTITY_ID, CONF_ID, CONF_IP_ADDRESS, CONF_NAME from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, CONF_ID, CONF_NAME
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from homeassistant.util.color import ( from homeassistant.util.color import (
@ -104,7 +104,7 @@ async def test_services(hass: HomeAssistant, caplog):
domain=DOMAIN, domain=DOMAIN,
data={ data={
CONF_ID: "", CONF_ID: "",
CONF_IP_ADDRESS: IP_ADDRESS, CONF_HOST: IP_ADDRESS,
CONF_TRANSITION: DEFAULT_TRANSITION, CONF_TRANSITION: DEFAULT_TRANSITION,
CONF_MODE_MUSIC: True, CONF_MODE_MUSIC: True,
CONF_SAVE_ON_CHANGE: True, CONF_SAVE_ON_CHANGE: True,
@ -306,7 +306,7 @@ async def test_device_types(hass: HomeAssistant):
domain=DOMAIN, domain=DOMAIN,
data={ data={
CONF_ID: "", CONF_ID: "",
CONF_IP_ADDRESS: IP_ADDRESS, CONF_HOST: IP_ADDRESS,
CONF_TRANSITION: DEFAULT_TRANSITION, CONF_TRANSITION: DEFAULT_TRANSITION,
CONF_MODE_MUSIC: DEFAULT_MODE_MUSIC, CONF_MODE_MUSIC: DEFAULT_MODE_MUSIC,
CONF_SAVE_ON_CHANGE: DEFAULT_SAVE_ON_CHANGE, CONF_SAVE_ON_CHANGE: DEFAULT_SAVE_ON_CHANGE,
@ -337,7 +337,7 @@ async def test_device_types(hass: HomeAssistant):
domain=DOMAIN, domain=DOMAIN,
data={ data={
CONF_ID: "", CONF_ID: "",
CONF_IP_ADDRESS: IP_ADDRESS, CONF_HOST: IP_ADDRESS,
CONF_TRANSITION: DEFAULT_TRANSITION, CONF_TRANSITION: DEFAULT_TRANSITION,
CONF_MODE_MUSIC: DEFAULT_MODE_MUSIC, CONF_MODE_MUSIC: DEFAULT_MODE_MUSIC,
CONF_SAVE_ON_CHANGE: DEFAULT_SAVE_ON_CHANGE, CONF_SAVE_ON_CHANGE: DEFAULT_SAVE_ON_CHANGE,
@ -520,7 +520,7 @@ async def test_effects(hass: HomeAssistant):
domain=DOMAIN, domain=DOMAIN,
data={ data={
CONF_ID: "", CONF_ID: "",
CONF_IP_ADDRESS: IP_ADDRESS, CONF_HOST: IP_ADDRESS,
CONF_TRANSITION: DEFAULT_TRANSITION, CONF_TRANSITION: DEFAULT_TRANSITION,
CONF_MODE_MUSIC: DEFAULT_MODE_MUSIC, CONF_MODE_MUSIC: DEFAULT_MODE_MUSIC,
CONF_SAVE_ON_CHANGE: DEFAULT_SAVE_ON_CHANGE, CONF_SAVE_ON_CHANGE: DEFAULT_SAVE_ON_CHANGE,