1 """The Flux LED/MagicLight integration discovery."""
3 from __future__
import annotations
6 from collections.abc
import Mapping
8 from typing
import Any, Final
10 from flux_led.aioscanner
import AIOBulbScanner
11 from flux_led.const
import (
15 ATTR_MODEL_DESCRIPTION,
18 ATTR_REMOTE_ACCESS_ENABLED,
19 ATTR_REMOTE_ACCESS_HOST,
20 ATTR_REMOTE_ACCESS_PORT,
23 from flux_led.models_db
import get_model_description
24 from flux_led.scanner
import FluxLEDDiscovery
26 from homeassistant
import config_entries
37 CONF_MODEL_DESCRIPTION,
40 CONF_REMOTE_ACCESS_ENABLED,
41 CONF_REMOTE_ACCESS_HOST,
42 CONF_REMOTE_ACCESS_PORT,
43 DIRECTED_DISCOVERY_TIMEOUT,
47 from .util
import format_as_flux_mac, mac_matches_by_one
49 _LOGGER = logging.getLogger(__name__)
52 CONF_TO_DISCOVERY: Final = {
53 CONF_HOST: ATTR_IPADDR,
54 CONF_REMOTE_ACCESS_ENABLED: ATTR_REMOTE_ACCESS_ENABLED,
55 CONF_REMOTE_ACCESS_HOST: ATTR_REMOTE_ACCESS_HOST,
56 CONF_REMOTE_ACCESS_PORT: ATTR_REMOTE_ACCESS_PORT,
57 CONF_MINOR_VERSION: ATTR_VERSION_NUM,
58 CONF_MODEL: ATTR_MODEL,
59 CONF_MODEL_NUM: ATTR_MODEL_NUM,
60 CONF_MODEL_INFO: ATTR_MODEL_INFO,
61 CONF_MODEL_DESCRIPTION: ATTR_MODEL_DESCRIPTION,
67 """When discovery is unavailable, load it from the config entry."""
69 return FluxLEDDiscovery(
70 ipaddr=data[CONF_HOST],
71 model=data.get(CONF_MODEL),
73 model_num=data.get(CONF_MODEL_NUM),
74 version_num=data.get(CONF_MINOR_VERSION),
76 model_info=data.get(CONF_MODEL_INFO),
77 model_description=data.get(CONF_MODEL_DESCRIPTION),
78 remote_access_enabled=data.get(CONF_REMOTE_ACCESS_ENABLED),
79 remote_access_host=data.get(CONF_REMOTE_ACCESS_HOST),
80 remote_access_port=data.get(CONF_REMOTE_ACCESS_PORT),
86 device: FluxLEDDiscovery, model_num: int |
None =
None
88 """Convert a flux_led discovery to a human readable name."""
89 if (mac_address := device[ATTR_ID])
is None:
90 return device[ATTR_IPADDR]
91 short_mac = mac_address[-6:]
92 if device[ATTR_MODEL_DESCRIPTION]:
93 return f
"{device[ATTR_MODEL_DESCRIPTION]} {short_mac}"
94 if model_num
is not None:
95 return f
"{get_model_description(model_num, None)} {short_mac}"
96 return f
"{device[ATTR_MODEL]} {short_mac}"
101 current_data: Mapping[str, Any],
102 data_updates: dict[str, Any],
103 device: FluxLEDDiscovery,
105 """Copy discovery data into config entry data."""
106 for conf_key, discovery_key
in CONF_TO_DISCOVERY.items():
108 device.get(discovery_key)
is not None
111 and current_data.get(conf_key) != device[discovery_key]
113 data_updates[conf_key] = device[discovery_key]
120 device: FluxLEDDiscovery,
121 model_num: int |
None,
122 allow_update_mac: bool,
124 """Update a config entry from a flux_led discovery."""
125 data_updates: dict[str, Any] = {}
126 mac_address = device[ATTR_ID]
127 assert mac_address
is not None
128 updates: dict[str, Any] = {}
129 formatted_mac = dr.format_mac(mac_address)
130 if not entry.unique_id
or (
132 and entry.unique_id != formatted_mac
135 updates[
"unique_id"] = formatted_mac
136 if model_num
and entry.data.get(CONF_MODEL_NUM) != model_num:
137 data_updates[CONF_MODEL_NUM] = model_num
141 title_matches_name = entry.title == entry.data.get(CONF_NAME)
142 if data_updates
or title_matches_name:
143 updates[
"data"] = {**entry.data, **data_updates}
144 if title_matches_name:
145 del updates[
"data"][CONF_NAME]
148 if updates
and not (
"title" in updates
and entry.state
is ConfigEntryState.LOADED):
149 return hass.config_entries.async_update_entry(entry, **updates)
155 """Check if a device was already discovered via a broadcast discovery."""
156 discoveries: list[FluxLEDDiscovery] = hass.data[DOMAIN][FLUX_LED_DISCOVERY]
157 for discovery
in discoveries:
158 if discovery[ATTR_IPADDR] == host:
165 """Clear the host from the discovery cache."""
166 domain_data = hass.data[DOMAIN]
167 discoveries: list[FluxLEDDiscovery] = domain_data[FLUX_LED_DISCOVERY]
168 domain_data[FLUX_LED_DISCOVERY] = [
169 discovery
for discovery
in discoveries
if discovery[ATTR_IPADDR] != host
174 hass: HomeAssistant, timeout: int, address: str |
None =
None
175 ) -> list[FluxLEDDiscovery]:
176 """Discover flux led devices."""
181 str(broadcast_address)
182 for broadcast_address
in await network.async_get_ipv4_broadcast_addresses(
187 scanner = AIOBulbScanner()
188 for idx, discovered
in enumerate(
189 await asyncio.gather(
192 scanner.async_scan(timeout=timeout, address=target_address)
194 for target_address
in targets
196 return_exceptions=
True,
199 if isinstance(discovered, Exception):
200 _LOGGER.debug(
"Scanning %s failed with error: %s", targets[idx], discovered)
204 return scanner.getBulbInfo()
207 device
for device
in scanner.getBulbInfo()
if device[ATTR_IPADDR] == address
212 hass: HomeAssistant, host: str
213 ) -> FluxLEDDiscovery |
None:
214 """Direct discovery at a single ip instead of broadcast."""
218 if device[ATTR_IPADDR] == host:
226 discovered_devices: list[FluxLEDDiscovery],
228 """Trigger config flows for discovered devices."""
229 for device
in discovered_devices:
230 discovery_flow.async_create_flow(
233 context={
"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
None async_clear_discovery_cache(HomeAssistant hass, str host)
FluxLEDDiscovery|None async_discover_device(HomeAssistant hass, str host)
FluxLEDDiscovery async_build_cached_discovery(ConfigEntry entry)
FluxLEDDiscovery|None async_get_discovery(HomeAssistant hass, str host)
str async_name_from_discovery(FluxLEDDiscovery device, int|None model_num=None)
bool async_update_entry_from_discovery(HomeAssistant hass, config_entries.ConfigEntry entry, FluxLEDDiscovery device, int|None model_num, bool allow_update_mac)
list[FluxLEDDiscovery] async_discover_devices(HomeAssistant hass, int timeout, str|None address=None)
None async_populate_data_from_discovery(Mapping[str, Any] current_data, dict[str, Any] data_updates, FluxLEDDiscovery device)
None async_trigger_discovery(HomeAssistant hass, list[FluxLEDDiscovery] discovered_devices)
bool mac_matches_by_one(str formatted_mac_1, str formatted_mac_2)
str|None format_as_flux_mac(str|None mac)
bool is_ip_address(str address)