"""The LED Screen Controller integration.""" from __future__ import annotations import logging from datetime import timedelta import voluptuous as vol from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ( DOMAIN, CONF_SERVER_NAME, CONF_SERVER_URL, CONF_API_KEY, DEFAULT_SCAN_INTERVAL, TARGET_TYPE_HA_LIGHT, DATA_COORDINATOR, DATA_EVENT_LISTENER, ) from .coordinator import LedGrabCoordinator from .event_listener import EventStreamListener _LOGGER = logging.getLogger(__name__) PLATFORMS: list[Platform] = [ Platform.BUTTON, Platform.LIGHT, Platform.SWITCH, Platform.SENSOR, Platform.NUMBER, Platform.SELECT, ] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up LED Screen Controller from a config entry.""" server_name = entry.data.get(CONF_SERVER_NAME, "LED Screen Controller") server_url = entry.data[CONF_SERVER_URL] api_key = entry.data[CONF_API_KEY] session = async_get_clientsession(hass) coordinator = LedGrabCoordinator( hass, session, server_url, api_key, update_interval=timedelta(seconds=DEFAULT_SCAN_INTERVAL), ) await coordinator.async_config_entry_first_refresh() event_listener = EventStreamListener(hass, server_url, api_key, coordinator) await event_listener.start() # Create device entries for each target and remove stale ones device_registry = dr.async_get(hass) current_identifiers: set[tuple[str, str]] = set() if coordinator.data and "targets" in coordinator.data: for target_id, target_data in coordinator.data["targets"].items(): info = target_data["info"] target_type = info.get("target_type", "led") if target_type == TARGET_TYPE_HA_LIGHT: model = "HA Light Target" else: model = "LED Target" device_registry.async_get_or_create( config_entry_id=entry.entry_id, identifiers={(DOMAIN, target_id)}, name=info.get("name", target_id), manufacturer=server_name, model=model, configuration_url=server_url, ) current_identifiers.add((DOMAIN, target_id)) # Create a single "Scenes" device for scene preset buttons scenes_identifier = (DOMAIN, f"{entry.entry_id}_scenes") scene_presets = coordinator.data.get("scene_presets", []) if coordinator.data else [] if scene_presets: device_registry.async_get_or_create( config_entry_id=entry.entry_id, identifiers={scenes_identifier}, name="Scenes", manufacturer=server_name, model="Scene Presets", configuration_url=server_url, ) current_identifiers.add(scenes_identifier) # Remove devices for targets that no longer exist for device_entry in dr.async_entries_for_config_entry(device_registry, entry.entry_id): if not device_entry.identifiers & current_identifiers: _LOGGER.info("Removing stale device: %s", device_entry.name) device_registry.async_remove_device(device_entry.id) # Store data hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = { DATA_COORDINATOR: coordinator, DATA_EVENT_LISTENER: event_listener, } # Track target and scene IDs to detect changes known_target_ids = set(coordinator.data.get("targets", {}).keys() if coordinator.data else []) known_scene_ids = set( p["id"] for p in (coordinator.data.get("scene_presets", []) if coordinator.data else []) ) def _on_coordinator_update() -> None: """Detect target/scene list changes and trigger reload.""" nonlocal known_target_ids, known_scene_ids if not coordinator.data: return targets = coordinator.data.get("targets", {}) # Reload if target or scene list changed current_ids = set(targets.keys()) current_scene_ids = set(p["id"] for p in coordinator.data.get("scene_presets", [])) if current_ids != known_target_ids or current_scene_ids != known_scene_ids: known_target_ids = current_ids known_scene_ids = current_scene_ids _LOGGER.info("Target or scene list changed, reloading integration") hass.async_create_task(hass.config_entries.async_reload(entry.entry_id)) coordinator.async_add_listener(_on_coordinator_update) # Register set_leds service (once across all entries) async def handle_set_leds(call) -> None: """Handle the set_leds service call.""" source_id = call.data["source_id"] segments = call.data["segments"] # Route to the coordinator that owns this source for entry_data in hass.data[DOMAIN].values(): coord = entry_data.get(DATA_COORDINATOR) if not coord or not coord.data: continue source_ids = {s["id"] for s in coord.data.get("css_sources", [])} if source_id in source_ids: await coord.push_segments(source_id, segments) return _LOGGER.error("No server found with source_id %s", source_id) if not hass.services.has_service(DOMAIN, "set_leds"): hass.services.async_register( DOMAIN, "set_leds", handle_set_leds, schema=vol.Schema( { vol.Required("source_id"): str, vol.Required("segments"): list, } ), ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" entry_data = hass.data[DOMAIN][entry.entry_id] await entry_data[DATA_EVENT_LISTENER].shutdown() unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) if unload_ok: hass.data[DOMAIN].pop(entry.entry_id) # Unregister service if no entries remain if not hass.data[DOMAIN]: hass.services.async_remove(DOMAIN, "set_leds") return unload_ok