1 """Config flow for Yeelight integration."""
3 from __future__
import annotations
6 from typing
import Any, Self
7 from urllib.parse
import urlparse
9 import voluptuous
as vol
11 from yeelight.aio
import AsyncBulb
12 from yeelight.main
import get_known_models
31 CONF_NIGHTLIGHT_SWITCH,
32 CONF_NIGHTLIGHT_SWITCH_TYPE,
36 NIGHTLIGHT_SWITCH_TYPE_LIGHT,
42 async_format_model_id,
44 from .scanner
import YeelightScanner
46 MODEL_UNKNOWN =
"unknown"
48 _LOGGER = logging.getLogger(__name__)
52 """Handle a config flow for Yeelight."""
56 _discovered_ip: str =
""
57 _discovered_model: str
62 config_entry: ConfigEntry,
63 ) -> OptionsFlowHandler:
64 """Return the options flow."""
68 """Initialize the config flow."""
69 self._discovered_devices: dict[str, Any] = {}
72 self, discovery_info: zeroconf.ZeroconfServiceInfo
73 ) -> ConfigFlowResult:
74 """Handle discovery from homekit."""
79 self, discovery_info: dhcp.DhcpServiceInfo
80 ) -> ConfigFlowResult:
81 """Handle discovery from dhcp."""
86 self, discovery_info: zeroconf.ZeroconfServiceInfo
87 ) -> ConfigFlowResult:
88 """Handle discovery from zeroconf."""
90 await self.
async_set_unique_idasync_set_unique_id(f
"{int(discovery_info.name[-26:-18]):#018x}")
94 self, discovery_info: ssdp.SsdpServiceInfo
95 ) -> ConfigFlowResult:
96 """Handle discovery from ssdp."""
97 self.
_discovered_ip_discovered_ip = urlparse(discovery_info.ssdp_headers[
"location"]).hostname
102 """Handle any discovery with a unique id."""
104 if entry.unique_id != self.
unique_idunique_id
and self.
unique_idunique_id != entry.data.get(
108 reload = entry.state == ConfigEntryState.SETUP_RETRY
109 if entry.data.get(CONF_HOST) != self.
_discovered_ip_discovered_ip:
110 self.hass.config_entries.async_update_entry(
111 entry, data={**entry.data, CONF_HOST: self.
_discovered_ip_discovered_ip}
113 reload = entry.state
in (
114 ConfigEntryState.SETUP_RETRY,
115 ConfigEntryState.LOADED,
118 self.hass.config_entries.async_schedule_reload(entry.entry_id)
123 """Handle any discovery."""
124 if self.hass.config_entries.flow.async_has_matching_flow(self):
132 except CannotConnect:
139 updates={CONF_HOST: self.
_discovered_ip_discovered_ip}, reload_on_update=
False
144 """Return True if other_flow is matching this flow."""
145 return other_flow._discovered_ip == self.
_discovered_ip_discovered_ip
148 self, user_input: dict[str, Any] |
None =
None
149 ) -> ConfigFlowResult:
150 """Confirm discovery."""
151 if user_input
is not None or not onboarding.async_is_onboarded(self.hass):
167 self.context[
"title_placeholders"] = placeholders
169 step_id=
"discovery_confirm", description_placeholders=placeholders
173 self, user_input: dict[str, Any] |
None =
None
174 ) -> ConfigFlowResult:
175 """Handle the initial step."""
177 if user_input
is not None:
178 if not user_input.get(CONF_HOST):
182 user_input[CONF_HOST], raise_on_progress=
False
184 except CannotConnect:
185 errors[
"base"] =
"cannot_connect"
191 CONF_HOST: user_input[CONF_HOST],
197 user_input = user_input
or {}
200 data_schema=vol.Schema(
201 {vol.Optional(CONF_HOST, default=user_input.get(CONF_HOST,
"")): str}
207 self, user_input: dict[str, str] |
None =
None
208 ) -> ConfigFlowResult:
209 """Handle the step to pick discovered device."""
210 if user_input
is not None:
211 unique_id = user_input[CONF_DEVICE]
212 capabilities = self._discovered_devices[unique_id]
215 host = urlparse(capabilities[
"location"]).hostname
221 CONF_MODEL: capabilities[
"model"],
225 configured_devices = {
228 if entry.data[CONF_ID]
231 scanner = YeelightScanner.async_get(self.hass)
232 devices = await scanner.async_discover()
234 for capabilities
in devices:
235 unique_id = capabilities[
"id"]
236 if unique_id
in configured_devices:
238 model = capabilities[
"model"]
239 host = urlparse(capabilities[
"location"]).hostname
241 name = f
"{model_id} ({host})"
242 self._discovered_devices[unique_id] = capabilities
243 devices_name[unique_id] = name
249 step_id=
"pick_device",
250 data_schema=vol.Schema({vol.Required(CONF_DEVICE): vol.In(devices_name)}),
254 """Handle import step."""
255 host = import_data[CONF_HOST]
258 except CannotConnect:
259 _LOGGER.error(
"Failed to import %s: cannot connect", host)
261 if CONF_NIGHTLIGHT_SWITCH_TYPE
in import_data:
262 import_data[CONF_NIGHTLIGHT_SWITCH] = (
263 import_data.pop(CONF_NIGHTLIGHT_SWITCH_TYPE)
264 == NIGHTLIGHT_SWITCH_TYPE_LIGHT
270 self, host: str, raise_on_progress: bool =
True
272 """Set up with options."""
275 scanner = YeelightScanner.async_get(self.hass)
276 capabilities = await scanner.async_get_capabilities(host)
277 if capabilities
is None:
278 _LOGGER.debug(
"Failed to get capabilities from %s: timeout", host)
280 _LOGGER.debug(
"Get capabilities: %s", capabilities)
282 capabilities[
"id"], raise_on_progress=raise_on_progress
284 return capabilities[
"model"]
286 bulb = AsyncBulb(host)
288 await bulb.async_listen(
lambda _:
True)
289 await bulb.async_get_properties()
290 await bulb.async_stop_listening()
291 except (TimeoutError, yeelight.BulbException)
as err:
292 _LOGGER.debug(
"Failed to get properties from %s: %s", host, err)
293 raise CannotConnect
from err
294 _LOGGER.debug(
"Get properties: %s", bulb.last_properties)
299 """Handle a option flow for Yeelight."""
302 self, user_input: dict[str, Any] |
None =
None
303 ) -> ConfigFlowResult:
304 """Handle the initial step."""
307 detected_model = data.get(CONF_DETECTED_MODEL)
308 model = options[CONF_MODEL]
or detected_model
310 if user_input
is not None:
312 title=
"", data={CONF_MODEL: model, **options, **user_input}
315 schema_dict: VolDictType = {}
316 known_models = get_known_models()
317 if is_unknown_model := model
not in known_models:
318 known_models.insert(0, model)
320 if is_unknown_model
or model != detected_model:
323 vol.Optional(CONF_MODEL, default=model): vol.In(known_models),
329 CONF_TRANSITION, default=options[CONF_TRANSITION]
331 vol.Required(CONF_MODE_MUSIC, default=options[CONF_MODE_MUSIC]): bool,
333 CONF_SAVE_ON_CHANGE, default=options[CONF_SAVE_ON_CHANGE]
336 CONF_NIGHTLIGHT_SWITCH, default=options[CONF_NIGHTLIGHT_SWITCH]
343 data_schema=vol.Schema(schema_dict),
348 """Error to indicate we cannot connect."""
ConfigFlowResult async_step_init(self, dict[str, Any]|None user_input=None)
str _async_try_connect(self, str host, bool raise_on_progress=True)
ConfigFlowResult async_step_dhcp(self, dhcp.DhcpServiceInfo discovery_info)
ConfigFlowResult async_step_import(self, dict[str, Any] import_data)
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_homekit(self, zeroconf.ZeroconfServiceInfo discovery_info)
OptionsFlowHandler async_get_options_flow(ConfigEntry config_entry)
ConfigFlowResult _async_handle_discovery(self)
ConfigFlowResult async_step_zeroconf(self, zeroconf.ZeroconfServiceInfo discovery_info)
ConfigFlowResult async_step_discovery_confirm(self, dict[str, Any]|None user_input=None)
ConfigFlowResult _async_handle_discovery_with_unique_id(self)
bool is_matching(self, Self other_flow)
ConfigFlowResult async_step_pick_device(self, dict[str, str]|None user_input=None)
ConfigFlowResult async_step_ssdp(self, ssdp.SsdpServiceInfo discovery_info)
None _abort_if_unique_id_configured(self, dict[str, Any]|None updates=None, bool reload_on_update=True, *str error="already_configured")
None _set_confirm_only(self)
ConfigEntry|None async_set_unique_id(self, str|None unique_id=None, *bool raise_on_progress=True)
ConfigFlowResult async_create_entry(self, *str title, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None, Mapping[str, Any]|None options=None)
list[ConfigEntry] _async_current_entries(self, bool|None include_ignore=None)
ConfigFlowResult async_abort(self, *str reason, Mapping[str, str]|None description_placeholders=None)
None _async_abort_entries_match(self, dict[str, Any]|None match_dict=None)
ConfigFlowResult async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
ConfigEntry config_entry(self)
None config_entry(self, ConfigEntry value)
_FlowResultT async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
_FlowResultT async_create_entry(self, *str|None title=None, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None)
_FlowResultT async_abort(self, *str reason, Mapping[str, str]|None description_placeholders=None)
str _async_unique_name(dict capabilities)
str async_format_id(str|None id_)
str async_format_model_id(str model, str|None id_)
str async_format_model(str model)