|
9 | 9 | import aiohttp |
10 | 10 | from aiohue import LinkButtonNotPressed, create_app_key |
11 | 11 | from aiohue.discovery import DiscoveredHueBridge, discover_bridge, discover_nupnp |
| 12 | +from aiohue.errors import AiohueException |
12 | 13 | from aiohue.util import normalize_bridge_id |
| 14 | +from aiohue.v2 import HueBridgeV2 |
13 | 15 | import slugify as unicode_slug |
14 | 16 | import voluptuous as vol |
15 | 17 |
|
|
40 | 42 | HUE_IGNORED_BRIDGE_NAMES = ["Home Assistant Bridge", "Espalexa"] |
41 | 43 | HUE_MANUAL_BRIDGE_ID = "manual" |
42 | 44 |
|
| 45 | +BSB002_MODEL_ID = "BSB002" |
| 46 | +BSB003_MODEL_ID = "BSB003" |
| 47 | + |
43 | 48 |
|
44 | 49 | class HueFlowHandler(ConfigFlow, domain=DOMAIN): |
45 | 50 | """Handle a Hue config flow.""" |
@@ -74,7 +79,14 @@ async def _get_bridge( |
74 | 79 | """Return a DiscoveredHueBridge object.""" |
75 | 80 | try: |
76 | 81 | bridge = await discover_bridge( |
77 | | - host, websession=aiohttp_client.async_get_clientsession(self.hass) |
| 82 | + host, |
| 83 | + websession=aiohttp_client.async_get_clientsession( |
| 84 | + # NOTE: we disable SSL verification for now due to the fact that the (BSB003) |
| 85 | + # Hue bridge uses a certificate from a on-bridge root authority. |
| 86 | + # We need to specifically handle this case in a follow-up update. |
| 87 | + self.hass, |
| 88 | + verify_ssl=False, |
| 89 | + ), |
78 | 90 | ) |
79 | 91 | except aiohttp.ClientError as err: |
80 | 92 | LOGGER.warning( |
@@ -110,7 +122,9 @@ async def async_step_init( |
110 | 122 | try: |
111 | 123 | async with asyncio.timeout(5): |
112 | 124 | bridges = await discover_nupnp( |
113 | | - websession=aiohttp_client.async_get_clientsession(self.hass) |
| 125 | + websession=aiohttp_client.async_get_clientsession( |
| 126 | + self.hass, verify_ssl=False |
| 127 | + ) |
114 | 128 | ) |
115 | 129 | except TimeoutError: |
116 | 130 | bridges = [] |
@@ -178,7 +192,9 @@ async def async_step_link( |
178 | 192 | app_key = await create_app_key( |
179 | 193 | bridge.host, |
180 | 194 | f"home-assistant#{device_name}", |
181 | | - websession=aiohttp_client.async_get_clientsession(self.hass), |
| 195 | + websession=aiohttp_client.async_get_clientsession( |
| 196 | + self.hass, verify_ssl=False |
| 197 | + ), |
182 | 198 | ) |
183 | 199 | except LinkButtonNotPressed: |
184 | 200 | errors["base"] = "register_failed" |
@@ -228,14 +244,21 @@ async def async_step_zeroconf( |
228 | 244 | self._abort_if_unique_id_configured( |
229 | 245 | updates={CONF_HOST: discovery_info.host}, reload_on_update=True |
230 | 246 | ) |
231 | | - |
232 | 247 | # we need to query the other capabilities too |
233 | 248 | bridge = await self._get_bridge( |
234 | 249 | discovery_info.host, discovery_info.properties["bridgeid"] |
235 | 250 | ) |
236 | 251 | if bridge is None: |
237 | 252 | return self.async_abort(reason="cannot_connect") |
238 | 253 | self.bridge = bridge |
| 254 | + if ( |
| 255 | + bridge.supports_v2 |
| 256 | + and discovery_info.properties.get("modelid") == BSB003_MODEL_ID |
| 257 | + ): |
| 258 | + # try to handle migration of BSB002 --> BSB003 |
| 259 | + if await self._check_migrated_bridge(bridge): |
| 260 | + return self.async_abort(reason="migrated_bridge") |
| 261 | + |
239 | 262 | return await self.async_step_link() |
240 | 263 |
|
241 | 264 | async def async_step_homekit( |
@@ -272,6 +295,55 @@ async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResu |
272 | 295 | self.bridge = bridge |
273 | 296 | return await self.async_step_link() |
274 | 297 |
|
| 298 | + async def _check_migrated_bridge(self, bridge: DiscoveredHueBridge) -> bool: |
| 299 | + """Check if the discovered bridge is a migrated bridge.""" |
| 300 | + # Try to handle migration of BSB002 --> BSB003. |
| 301 | + # Once we detect a BSB003 bridge on the network which has not yet been |
| 302 | + # configured in HA (otherwise we would have had a unique id match), |
| 303 | + # we check if we have any existing (BSB002) entries and if we can connect to the |
| 304 | + # new bridge with our previously stored api key. |
| 305 | + # If that succeeds, we migrate the entry to the new bridge. |
| 306 | + for conf_entry in self.hass.config_entries.async_entries( |
| 307 | + DOMAIN, include_ignore=False, include_disabled=False |
| 308 | + ): |
| 309 | + if conf_entry.data[CONF_API_VERSION] != 2: |
| 310 | + continue |
| 311 | + if conf_entry.data[CONF_HOST] == bridge.host: |
| 312 | + continue |
| 313 | + # found an existing (BSB002) bridge entry, |
| 314 | + # check if we can connect to the new BSB003 bridge using the old credentials |
| 315 | + api = HueBridgeV2(bridge.host, conf_entry.data[CONF_API_KEY]) |
| 316 | + try: |
| 317 | + await api.fetch_full_state() |
| 318 | + except (AiohueException, aiohttp.ClientError): |
| 319 | + continue |
| 320 | + old_bridge_id = conf_entry.unique_id |
| 321 | + assert old_bridge_id is not None |
| 322 | + # found a matching entry, migrate it |
| 323 | + self.hass.config_entries.async_update_entry( |
| 324 | + conf_entry, |
| 325 | + data={ |
| 326 | + **conf_entry.data, |
| 327 | + CONF_HOST: bridge.host, |
| 328 | + }, |
| 329 | + unique_id=bridge.id, |
| 330 | + ) |
| 331 | + # also update the bridge device |
| 332 | + dev_reg = dr.async_get(self.hass) |
| 333 | + if bridge_device := dev_reg.async_get_device( |
| 334 | + identifiers={(DOMAIN, old_bridge_id)} |
| 335 | + ): |
| 336 | + dev_reg.async_update_device( |
| 337 | + bridge_device.id, |
| 338 | + # overwrite identifiers with new bridge id |
| 339 | + new_identifiers={(DOMAIN, bridge.id)}, |
| 340 | + # overwrite mac addresses with empty set to drop the old (incorrect) addresses |
| 341 | + # this will be auto corrected once the integration is loaded |
| 342 | + new_connections=set(), |
| 343 | + ) |
| 344 | + return True |
| 345 | + return False |
| 346 | + |
275 | 347 |
|
276 | 348 | class HueV1OptionsFlowHandler(OptionsFlow): |
277 | 349 | """Handle Hue options for V1 implementation.""" |
|
0 commit comments