mirror of
https://github.com/home-assistant/core
synced 2024-08-02 23:40:32 +02:00
163 lines
5.7 KiB
Python
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
|