Refactor LIFX discovery to make it faster and more reliable (#70458)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Avi Miller 2022-04-27 04:58:01 +10:00 committed by GitHub
parent c15c22655b
commit f593b387a4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 33 additions and 22 deletions

View file

@ -4,6 +4,7 @@ from __future__ import annotations
import asyncio
from datetime import timedelta
from functools import partial
from ipaddress import IPv4Address
import logging
import math
@ -13,6 +14,7 @@ from awesomeversion import AwesomeVersion
import voluptuous as vol
from homeassistant import util
from homeassistant.components import network
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_BRIGHTNESS_PCT,
@ -69,8 +71,8 @@ _LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=10)
DISCOVERY_INTERVAL = 60
MESSAGE_TIMEOUT = 1.0
MESSAGE_RETRIES = 8
MESSAGE_TIMEOUT = 1
MESSAGE_RETRIES = 3
UNAVAILABLE_GRACE = 90
FIX_MAC_FW = AwesomeVersion("3.70")
@ -193,12 +195,13 @@ async def async_setup_entry(
"""Set up LIFX from a config entry."""
# Priority 1: manual config
if not (interfaces := hass.data[LIFX_DOMAIN].get(DOMAIN)):
# Priority 2: scanned interfaces
lifx_ip_addresses = await aiolifx().LifxScan(hass.loop).scan()
interfaces = [{CONF_SERVER: ip} for ip in lifx_ip_addresses]
if not interfaces:
# Priority 3: default interface
interfaces = [{}]
# Priority 2: Home Assistant enabled interfaces
ip_addresses = (
source_ip
for source_ip in await network.async_get_enabled_source_ips(hass)
if isinstance(source_ip, IPv4Address) and not source_ip.is_loopback
)
interfaces = [{CONF_SERVER: str(ip)} for ip in ip_addresses]
platform = entity_platform.async_get_current_platform()
lifx_manager = LIFXManager(hass, platform, config_entry, async_add_entities)
@ -259,6 +262,7 @@ class LIFXManager:
def __init__(self, hass, platform, config_entry, async_add_entities):
"""Initialize the light."""
self.entities = {}
self.discoveries_inflight = {}
self.hass = hass
self.platform = platform
self.config_entry = config_entry
@ -378,18 +382,23 @@ class LIFXManager:
@callback
def register(self, bulb):
"""Handle aiolifx detected bulb."""
self.hass.async_create_task(self.register_new_bulb(bulb))
"""Allow a single in-flight discovery per bulb."""
if bulb.mac_addr not in self.discoveries_inflight:
self.discoveries_inflight[bulb.mac_addr] = bulb.ip_addr
_LOGGER.debug("Discovered %s (%s)", bulb.ip_addr, bulb.mac_addr)
self.hass.async_create_task(self.register_bulb(bulb))
else:
_LOGGER.warning("Duplicate LIFX discovery response ignored")
async def register_new_bulb(self, bulb):
"""Handle newly detected bulb."""
async def register_bulb(self, bulb):
"""Handle LIFX bulb registration lifecycle."""
if bulb.mac_addr in self.entities:
entity = self.entities[bulb.mac_addr]
entity.registered = True
_LOGGER.debug("%s register AGAIN", entity.who)
_LOGGER.debug("Reconnected to %s", entity.who)
await entity.update_hass()
else:
_LOGGER.debug("%s register NEW", bulb.ip_addr)
_LOGGER.debug("Connecting to %s (%s)", bulb.ip_addr, bulb.mac_addr)
# Read initial state
ack = AwaitAioLIFX().wait
@ -398,8 +407,8 @@ class LIFXManager:
# can be ignored.
version_resp = await ack(bulb.get_version)
if version_resp and bulb.product in SWITCH_PRODUCT_IDS:
_LOGGER.warning(
"(Switch) action=skip_discovery, reason=unsupported, serial=%s, ip_addr=%s, type='LIFX Switch'",
_LOGGER.debug(
"Not connecting to LIFX Switch %s (%s)",
str(bulb.mac_addr).replace(":", ""),
bulb.ip_addr,
)
@ -408,7 +417,7 @@ class LIFXManager:
color_resp = await ack(bulb.get_color)
if color_resp is None or version_resp is None:
_LOGGER.error("Failed to initialize %s", bulb.ip_addr)
_LOGGER.error("Failed to connect to %s", bulb.ip_addr)
bulb.registered = False
else:
bulb.timeout = MESSAGE_TIMEOUT
@ -422,16 +431,17 @@ class LIFXManager:
else:
entity = LIFXWhite(bulb, self.effects_conductor)
_LOGGER.debug("%s register READY", entity.who)
_LOGGER.debug("Connected to %s", entity.who)
self.entities[bulb.mac_addr] = entity
self.discoveries_inflight.pop(bulb.mac_addr, None)
self.async_add_entities([entity], True)
@callback
def unregister(self, bulb):
"""Handle aiolifx disappearing bulbs."""
"""Disconnect and unregister non-responsive bulbs."""
if bulb.mac_addr in self.entities:
entity = self.entities[bulb.mac_addr]
_LOGGER.debug("%s unregister", entity.who)
_LOGGER.debug("Disconnected from %s", entity.who)
entity.registered = False
entity.async_write_ha_state()
@ -551,8 +561,8 @@ class LIFXLight(LightEntity):
@property
def who(self):
"""Return a string identifying the bulb."""
return f"{self.bulb.ip_addr} ({self.name})"
"""Return a string identifying the bulb by name and mac."""
return f"{self.name} ({self.bulb.mac_addr})"
@property
def min_mireds(self):

View file

@ -4,6 +4,7 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/lifx",
"requirements": ["aiolifx==0.7.1", "aiolifx_effects==0.2.2"],
"dependencies": ["network"],
"homekit": {
"models": [
"LIFX A19",