1 """Support for Xiaomi Yeelight WiFi color bulb."""
3 from __future__
import annotations
7 import voluptuous
as vol
8 from yeelight
import BulbException
9 from yeelight.aio
import AsyncBulb
18 EVENT_HOMEASSISTANT_STOP,
36 CONF_NIGHTLIGHT_SWITCH,
37 CONF_NIGHTLIGHT_SWITCH_TYPE,
45 DEFAULT_NIGHTLIGHT_SWITCH,
46 DEFAULT_SAVE_ON_CHANGE,
49 NIGHTLIGHT_SWITCH_TYPE_LIGHT,
51 YEELIGHT_HSV_TRANSACTION,
52 YEELIGHT_RGB_TRANSITION,
53 YEELIGHT_SLEEP_TRANSACTION,
54 YEELIGHT_TEMPERATURE_TRANSACTION,
56 from .device
import YeelightDevice, async_format_id
57 from .scanner
import YeelightScanner
59 _LOGGER = logging.getLogger(__name__)
62 YEELIGHT_FLOW_TRANSITION_SCHEMA: VolDictType = {
63 vol.Optional(ATTR_COUNT, default=0): cv.positive_int,
64 vol.Optional(ATTR_ACTION, default=ACTION_RECOVER): vol.Any(
65 ACTION_RECOVER, ACTION_OFF, ACTION_STAY
67 vol.Required(ATTR_TRANSITIONS): [
69 vol.Exclusive(YEELIGHT_RGB_TRANSITION, CONF_TRANSITION): vol.All(
70 cv.ensure_list, [cv.positive_int]
72 vol.Exclusive(YEELIGHT_HSV_TRANSACTION, CONF_TRANSITION): vol.All(
73 cv.ensure_list, [cv.positive_int]
75 vol.Exclusive(YEELIGHT_TEMPERATURE_TRANSACTION, CONF_TRANSITION): vol.All(
76 cv.ensure_list, [cv.positive_int]
78 vol.Exclusive(YEELIGHT_SLEEP_TRANSACTION, CONF_TRANSITION): vol.All(
79 cv.ensure_list, [cv.positive_int]
85 DEVICE_SCHEMA = vol.Schema(
87 vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
88 vol.Optional(CONF_TRANSITION, default=DEFAULT_TRANSITION): cv.positive_int,
89 vol.Optional(CONF_MODE_MUSIC, default=
False): cv.boolean,
90 vol.Optional(CONF_SAVE_ON_CHANGE, default=
False): cv.boolean,
91 vol.Optional(CONF_NIGHTLIGHT_SWITCH_TYPE): vol.Any(
92 NIGHTLIGHT_SWITCH_TYPE_LIGHT
94 vol.Optional(CONF_MODEL): cv.string,
98 CONFIG_SCHEMA = vol.Schema(
102 vol.Optional(CONF_DEVICES, default={}): {cv.string: DEVICE_SCHEMA},
103 vol.Optional(CONF_CUSTOM_EFFECTS): [
105 vol.Required(CONF_NAME): cv.string,
106 vol.Required(CONF_FLOW_PARAMS): YEELIGHT_FLOW_TRANSITION_SCHEMA,
112 extra=vol.ALLOW_EXTRA,
116 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
117 """Set up the Yeelight bulbs."""
118 conf = config.get(DOMAIN, {})
119 hass.data[DOMAIN] = {
120 DATA_CUSTOM_EFFECTS: conf.get(CONF_CUSTOM_EFFECTS, {}),
121 DATA_CONFIG_ENTRIES: {},
126 scanner = YeelightScanner.async_get(hass)
127 await scanner.async_setup()
130 for host, device_config
in config.get(DOMAIN, {}).
get(CONF_DEVICES, {}).items():
131 _LOGGER.debug(
"Importing configured %s", host)
132 entry_config = {CONF_HOST: host, **device_config}
133 hass.async_create_task(
134 hass.config_entries.flow.async_init(
135 DOMAIN, context={
"source": SOURCE_IMPORT}, data=entry_config
145 device: YeelightDevice,
147 """Initialize a Yeelight device."""
148 entry_data = hass.data[DOMAIN][DATA_CONFIG_ENTRIES][entry.entry_id] = {}
149 await device.async_setup()
150 entry_data[DATA_DEVICE] = device
154 and entry.data.get(CONF_DETECTED_MODEL) != device.capabilities[
"model"]
156 hass.config_entries.async_update_entry(
158 data={**entry.data, CONF_DETECTED_MODEL: device.capabilities[
"model"]},
164 """Move options from data for imported entries.
166 Initialize options with default values for other entries.
168 Copy the unique id to CONF_ID if it is missing
170 if not entry.options:
171 hass.config_entries.async_update_entry(
174 CONF_HOST: entry.data.get(CONF_HOST),
175 CONF_ID: entry.data.get(CONF_ID)
or entry.unique_id,
176 CONF_DETECTED_MODEL: entry.data.get(CONF_DETECTED_MODEL),
179 CONF_NAME: entry.data.get(CONF_NAME,
""),
180 CONF_MODEL: entry.data.get(
181 CONF_MODEL, entry.data.get(CONF_DETECTED_MODEL,
"")
183 CONF_TRANSITION: entry.data.get(CONF_TRANSITION, DEFAULT_TRANSITION),
184 CONF_MODE_MUSIC: entry.data.get(CONF_MODE_MUSIC, DEFAULT_MODE_MUSIC),
185 CONF_SAVE_ON_CHANGE: entry.data.get(
186 CONF_SAVE_ON_CHANGE, DEFAULT_SAVE_ON_CHANGE
188 CONF_NIGHTLIGHT_SWITCH: entry.data.get(
189 CONF_NIGHTLIGHT_SWITCH, DEFAULT_NIGHTLIGHT_SWITCH
192 unique_id=entry.unique_id
or entry.data.get(CONF_ID),
194 elif entry.unique_id
and not entry.data.get(CONF_ID):
195 hass.config_entries.async_update_entry(
197 data={CONF_HOST: entry.data.get(CONF_HOST), CONF_ID: entry.unique_id},
199 elif entry.data.get(CONF_ID)
and not entry.unique_id:
200 hass.config_entries.async_update_entry(
202 unique_id=entry.data[CONF_ID],
207 """Set up Yeelight from a config entry."""
210 if not entry.data.get(CONF_HOST):
217 except (TimeoutError, OSError, BulbException)
as ex:
218 raise ConfigEntryNotReady
from ex
220 found_unique_id = device.unique_id
221 expected_unique_id = entry.unique_id
222 if expected_unique_id
and found_unique_id
and found_unique_id != expected_unique_id:
229 f
"Unexpected device found at {device.host}; "
230 f
"expected {expected_unique_id}, found {found_unique_id}"
233 await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
236 entry.async_on_unload(entry.add_update_listener(_async_update_listener))
242 """Unload a config entry."""
243 data_config_entries = hass.data[DOMAIN][DATA_CONFIG_ENTRIES]
244 data_config_entries.pop(entry.entry_id)
245 return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
249 """Handle options update."""
250 await hass.config_entries.async_reload(entry.entry_id)
254 hass: HomeAssistant, host: str, entry: ConfigEntry
257 model = entry.options.get(CONF_MODEL)
or entry.data.get(CONF_DETECTED_MODEL)
260 bulb = AsyncBulb(host, model=model
or None)
262 device =
YeelightDevice(hass, host, {**entry.options, **entry.data}, bulb)
264 await device.bulb.async_listen(device.async_update_callback)
267 async
def async_stop_listen_task(event):
268 """Stop listen task."""
269 _LOGGER.debug(
"Shutting down Yeelight Listener (stop event)")
270 await device.bulb.async_stop_listening()
273 def _async_stop_listen_on_unload():
274 """Stop listen task."""
275 _LOGGER.debug(
"Shutting down Yeelight Listener (unload)")
276 hass.async_create_task(device.bulb.async_stop_listening())
278 entry.async_on_unload(
279 hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_stop_listen_task)
281 entry.async_on_unload(_async_stop_listen_on_unload)
284 await device.async_update()
288 not device.bulb.last_properties
291 "main_power" not in device.bulb.last_properties
292 and "power" not in device.bulb.last_properties
296 "Could not fetch initial state; try power cycling the device"
web.Response get(self, web.Request request, str config_key)
str async_format_id(str|None id_)
bool async_setup(HomeAssistant hass, ConfigType config)
None _async_update_listener(HomeAssistant hass, ConfigEntry entry)
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
None _async_normalize_config_entry(HomeAssistant hass, ConfigEntry entry)
None _async_initialize(HomeAssistant hass, ConfigEntry entry, YeelightDevice device)
YeelightDevice _async_get_device(HomeAssistant hass, str host, ConfigEntry entry)