1 """Config flow to configure Philips Hue."""
3 from __future__
import annotations
10 from aiohue
import LinkButtonNotPressed, create_app_key
11 from aiohue.discovery
import DiscoveredHueBridge, discover_bridge, discover_nupnp
12 from aiohue.util
import normalize_bridge_id
13 import slugify
as unicode_slug
14 import voluptuous
as vol
27 config_validation
as cv,
28 device_registry
as dr,
32 CONF_ALLOW_HUE_GROUPS,
33 CONF_ALLOW_UNREACHABLE,
34 CONF_IGNORE_AVAILABILITY,
35 DEFAULT_ALLOW_HUE_GROUPS,
36 DEFAULT_ALLOW_UNREACHABLE,
39 from .errors
import CannotConnect
41 LOGGER = logging.getLogger(__name__)
43 HUE_MANUFACTURERURL = (
"http://www.philips.com",
"http://www.philips-hue.com")
44 HUE_IGNORED_BRIDGE_NAMES = [
"Home Assistant Bridge",
"Espalexa"]
45 HUE_MANUAL_BRIDGE_ID =
"manual"
49 """Handle a Hue config flow."""
56 config_entry: ConfigEntry,
57 ) -> HueV1OptionsFlowHandler | HueV2OptionsFlowHandler:
58 """Get the options flow for this handler."""
59 if config_entry.data.get(CONF_API_VERSION, 1) == 1:
64 """Initialize the Hue flow."""
65 self.
bridgebridge: DiscoveredHueBridge |
None =
None
66 self.
discovered_bridgesdiscovered_bridges: dict[str, DiscoveredHueBridge] |
None =
None
69 self, user_input: dict[str, Any] |
None =
None
70 ) -> ConfigFlowResult:
71 """Handle a flow initialized by the user."""
76 self, host: str, bridge_id: str |
None =
None
77 ) -> DiscoveredHueBridge:
78 """Return a DiscoveredHueBridge object."""
80 bridge = await discover_bridge(
81 host, websession=aiohttp_client.async_get_clientsession(self.hass)
83 except aiohttp.ClientError
as err:
85 "Error while attempting to retrieve discovery information, "
86 "is there a bridge alive on IP %s ?",
91 if bridge_id
is not None:
92 bridge_id = normalize_bridge_id(bridge_id)
93 assert bridge_id == bridge.id
97 self, user_input: dict[str, Any] |
None =
None
98 ) -> ConfigFlowResult:
99 """Handle a flow start."""
101 if user_input
is not None and user_input[
"id"] == HUE_MANUAL_BRIDGE_ID:
105 user_input
is not None
115 async
with asyncio.timeout(5):
116 bridges = await discover_nupnp(
117 websession=aiohttp_client.async_get_clientsession(self.hass)
126 bridge
for bridge
in bridges
if bridge.id
not in already_configured
135 data_schema=vol.Schema(
137 vol.Required(
"id"): vol.In(
139 **{bridge.id: bridge.host
for bridge
in bridges},
140 HUE_MANUAL_BRIDGE_ID:
"Manually add a Hue Bridge",
148 self, user_input: dict[str, Any] |
None =
None
149 ) -> ConfigFlowResult:
150 """Handle manual bridge setup."""
151 if user_input
is None:
154 data_schema=vol.Schema({vol.Required(CONF_HOST): str}),
158 if (bridge := await self.
_get_bridge_get_bridge(user_input[CONF_HOST]))
is None:
160 self.
bridgebridge = bridge
164 self, user_input: dict[str, Any] |
None =
None
165 ) -> ConfigFlowResult:
166 """Attempt to link with the Hue bridge.
168 Given a configured host, will ask the user to press the link button
169 to connect to the bridge.
171 if user_input
is None:
174 bridge = self.
bridgebridge
175 assert bridge
is not None
177 device_name = unicode_slug.slugify(
178 self.hass.config.location_name, max_length=19
182 app_key = await create_app_key(
184 f
"home-assistant#{device_name}",
185 websession=aiohttp_client.async_get_clientsession(self.hass),
187 except LinkButtonNotPressed:
188 errors[
"base"] =
"register_failed"
189 except CannotConnect:
190 LOGGER.error(
"Error connecting to the Hue bridge at %s", bridge.host)
194 "Unknown error connecting with Hue bridge at %s", bridge.host
196 errors[
"base"] =
"linking"
204 normalize_bridge_id(bridge.id), raise_on_progress=
False
208 title=f
"Hue Bridge {bridge.id}",
210 CONF_HOST: bridge.host,
211 CONF_API_KEY: app_key,
212 CONF_API_VERSION: 2
if bridge.supports_v2
else 1,
217 self, discovery_info: zeroconf.ZeroconfServiceInfo
218 ) -> ConfigFlowResult:
219 """Handle a discovered Hue bridge.
221 This flow is triggered by the Zeroconf component. It will check if the
222 host is already configured and delegate to the import step if not.
225 if discovery_info.ip_address.version == 6:
230 bridge_id = normalize_bridge_id(discovery_info.properties[
"bridgeid"])
233 updates={CONF_HOST: discovery_info.host}, reload_on_update=
True
238 discovery_info.host, discovery_info.properties[
"bridgeid"]
242 self.
bridgebridge = bridge
246 self, discovery_info: zeroconf.ZeroconfServiceInfo
247 ) -> ConfigFlowResult:
248 """Handle a discovered Hue bridge on HomeKit.
250 The bridge ID communicated over HomeKit differs, so we cannot use that
251 as the unique identifier. Therefore, this method uses discovery without
254 bridge = await self.
_get_bridge_get_bridge(discovery_info.host)
257 self.
bridgebridge = bridge
262 """Import a new bridge as a config entry.
264 This flow is triggered by `async_setup` for both configured and
265 discovered bridges. Triggered for any bridge that does not have a
266 config entry yet (based on host).
268 This flow is also triggered by `async_step_discovery`.
273 bridge = await self.
_get_bridge_get_bridge(import_data[
"host"])
276 self.
bridgebridge = bridge
281 """Handle Hue options for V1 implementation."""
284 self, user_input: dict[str, Any] |
None =
None
285 ) -> ConfigFlowResult:
286 """Manage Hue options."""
287 if user_input
is not None:
292 data_schema=vol.Schema(
295 CONF_ALLOW_HUE_GROUPS,
297 CONF_ALLOW_HUE_GROUPS, DEFAULT_ALLOW_HUE_GROUPS
301 CONF_ALLOW_UNREACHABLE,
303 CONF_ALLOW_UNREACHABLE, DEFAULT_ALLOW_UNREACHABLE
312 """Handle Hue options for V2 implementation."""
315 self, user_input: dict[str, Any] |
None =
None
316 ) -> ConfigFlowResult:
317 """Manage Hue options."""
318 if user_input
is not None:
323 dev_reg = dr.async_get(self.hass)
326 identifier[1]: entry.name
328 for identifier
in entry.identifiers
329 if identifier[0] == DOMAIN
340 data_schema=vol.Schema(
343 CONF_IGNORE_AVAILABILITY,
345 ): cv.multi_select(dev_ids),
ConfigFlowResult async_step_init(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_link(self, dict[str, Any]|None user_input=None)
DiscoveredHueBridge _get_bridge(self, str host, str|None bridge_id=None)
ConfigFlowResult async_step_zeroconf(self, zeroconf.ZeroconfServiceInfo discovery_info)
ConfigFlowResult async_step_import(self, dict[str, Any] import_data)
ConfigFlowResult async_step_manual(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_homekit(self, zeroconf.ZeroconfServiceInfo discovery_info)
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
HueV1OptionsFlowHandler|HueV2OptionsFlowHandler async_get_options_flow(ConfigEntry config_entry)
ConfigFlowResult async_step_init(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_init(self, dict[str, Any]|None user_input=None)
None _abort_if_unique_id_configured(self, dict[str, Any]|None updates=None, bool reload_on_update=True, *str error="already_configured")
None _async_handle_discovery_without_unique_id(self)
set[str|None] _async_current_ids(self, bool include_ignore=True)
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)
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)