1
mirror of https://github.com/home-assistant/core synced 2024-08-02 23:40:32 +02:00
ha-core/homeassistant/components/flux_led/__init__.py

163 lines
5.7 KiB
Python

"""The Flux LED/MagicLight integration."""
from __future__ import annotations
from datetime import timedelta
import logging
from typing import Any, Final, cast
from flux_led import DeviceType
from flux_led.aio import AIOWifiLedBulb
from flux_led.const import ATTR_ID, WhiteChannelType
from flux_led.scanner import FluxLEDDiscovery
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STARTED, Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.event import (
async_track_time_change,
async_track_time_interval,
)
from homeassistant.helpers.typing import ConfigType
from .const import (
CONF_WHITE_CHANNEL_TYPE,
DISCOVER_SCAN_TIMEOUT,
DOMAIN,
FLUX_LED_DISCOVERY,
FLUX_LED_EXCEPTIONS,
SIGNAL_STATE_UPDATED,
STARTUP_SCAN_TIMEOUT,
)
from .coordinator import FluxLedUpdateCoordinator
from .discovery import (
async_build_cached_discovery,
async_clear_discovery_cache,
async_discover_device,
async_discover_devices,
async_get_discovery,
async_trigger_discovery,
async_update_entry_from_discovery,
)
_LOGGER = logging.getLogger(__name__)
PLATFORMS_BY_TYPE: Final = {
DeviceType.Bulb: [
Platform.BUTTON,
Platform.LIGHT,
Platform.NUMBER,
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
],
DeviceType.Switch: [Platform.BUTTON, Platform.SELECT, Platform.SWITCH],
}
DISCOVERY_INTERVAL: Final = timedelta(minutes=15)
REQUEST_REFRESH_DELAY: Final = 1.5
NAME_TO_WHITE_CHANNEL_TYPE: Final = {
option.name.lower(): option for option in WhiteChannelType
}
@callback
def async_wifi_bulb_for_host(
host: str, discovery: FluxLEDDiscovery | None
) -> AIOWifiLedBulb:
"""Create a AIOWifiLedBulb from a host."""
return AIOWifiLedBulb(host, discovery=discovery)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the flux_led component."""
domain_data = hass.data.setdefault(DOMAIN, {})
domain_data[FLUX_LED_DISCOVERY] = await async_discover_devices(
hass, STARTUP_SCAN_TIMEOUT
)
async def _async_discovery(*_: Any) -> None:
async_trigger_discovery(
hass, await async_discover_devices(hass, DISCOVER_SCAN_TIMEOUT)
)
async_trigger_discovery(hass, domain_data[FLUX_LED_DISCOVERY])
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, _async_discovery)
async_track_time_interval(hass, _async_discovery, DISCOVERY_INTERVAL)
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Flux LED/MagicLight from a config entry."""
host = entry.data[CONF_HOST]
discovery_cached = True
if discovery := async_get_discovery(hass, host):
discovery_cached = False
else:
discovery = async_build_cached_discovery(entry)
device: AIOWifiLedBulb = async_wifi_bulb_for_host(host, discovery=discovery)
signal = SIGNAL_STATE_UPDATED.format(device.ipaddr)
device.discovery = discovery
if white_channel_type := entry.data.get(CONF_WHITE_CHANNEL_TYPE):
device.white_channel_channel_type = NAME_TO_WHITE_CHANNEL_TYPE[
white_channel_type
]
@callback
def _async_state_changed(*_: Any) -> None:
_LOGGER.debug("%s: Device state updated: %s", device.ipaddr, device.raw_state)
async_dispatcher_send(hass, signal)
try:
await device.async_setup(_async_state_changed)
except FLUX_LED_EXCEPTIONS as ex:
raise ConfigEntryNotReady(
str(ex) or f"Timed out trying to connect to {device.ipaddr}"
) from ex
# UDP probe after successful connect only
if discovery_cached:
if directed_discovery := await async_discover_device(hass, host):
device.discovery = discovery = directed_discovery
discovery_cached = False
if entry.unique_id and discovery.get(ATTR_ID):
mac = dr.format_mac(cast(str, discovery[ATTR_ID]))
if mac != entry.unique_id:
# The device is offline and another flux_led device is now using the ip address
raise ConfigEntryNotReady(
f"Unexpected device found at {host}; Expected {entry.unique_id}, found {mac}"
)
if not discovery_cached:
# Only update the entry once we have verified the unique id
# is either missing or we have verified it matches
async_update_entry_from_discovery(hass, entry, discovery, device.model_num)
coordinator = FluxLedUpdateCoordinator(hass, device, entry)
hass.data[DOMAIN][entry.entry_id] = coordinator
platforms = PLATFORMS_BY_TYPE[device.device_type]
hass.config_entries.async_setup_platforms(entry, platforms)
async def _async_sync_time(*args: Any) -> None:
"""Set the time every morning at 02:40:30."""
await device.async_set_time()
await _async_sync_time() # set at startup
entry.async_on_unload(async_track_time_change(hass, _async_sync_time, 2, 40, 30))
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
device: AIOWifiLedBulb = hass.data[DOMAIN][entry.entry_id].device
platforms = PLATFORMS_BY_TYPE[device.device_type]
if unload_ok := await hass.config_entries.async_unload_platforms(entry, platforms):
# Make sure we probe the device again in case something has changed externally
async_clear_discovery_cache(hass, entry.data[CONF_HOST])
del hass.data[DOMAIN][entry.entry_id]
await device.async_stop()
return unload_ok