579553a850
HACS-compatible custom component split out from the main LedGrab repo. Creates light, switch, sensor, number, and select entities for each configured LedGrab device.
183 lines
6.4 KiB
Python
183 lines
6.4 KiB
Python
"""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
|