Move LG webOS TV actions to entitiy services (#135285)

This commit is contained in:
Shay Levy 2025-01-10 14:02:03 +02:00 committed by GitHub
parent bce7e9ba5e
commit 1f0eda8e47
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 29 additions and 86 deletions

View File

@ -4,72 +4,33 @@ from __future__ import annotations
from contextlib import suppress
import logging
from typing import NamedTuple
from aiowebostv import WebOsClient, WebOsTvPairError
import voluptuous as vol
from homeassistant.components import notify as hass_notify
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_COMMAND,
ATTR_ENTITY_ID,
CONF_CLIENT_SECRET,
CONF_HOST,
CONF_NAME,
EVENT_HOMEASSISTANT_STOP,
)
from homeassistant.core import Event, HomeAssistant, ServiceCall
from homeassistant.core import Event, HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers import config_validation as cv, discovery
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.typing import ConfigType
from .const import (
ATTR_BUTTON,
ATTR_CONFIG_ENTRY_ID,
ATTR_PAYLOAD,
ATTR_SOUND_OUTPUT,
DATA_CONFIG_ENTRY,
DATA_HASS_CONFIG,
DOMAIN,
PLATFORMS,
SERVICE_BUTTON,
SERVICE_COMMAND,
SERVICE_SELECT_SOUND_OUTPUT,
WEBOSTV_EXCEPTIONS,
)
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
CALL_SCHEMA = vol.Schema({vol.Required(ATTR_ENTITY_ID): cv.comp_entity_ids})
class ServiceMethodDetails(NamedTuple):
"""Details for SERVICE_TO_METHOD mapping."""
method: str
schema: vol.Schema
BUTTON_SCHEMA = CALL_SCHEMA.extend({vol.Required(ATTR_BUTTON): cv.string})
COMMAND_SCHEMA = CALL_SCHEMA.extend(
{vol.Required(ATTR_COMMAND): cv.string, vol.Optional(ATTR_PAYLOAD): dict}
)
SOUND_OUTPUT_SCHEMA = CALL_SCHEMA.extend({vol.Required(ATTR_SOUND_OUTPUT): cv.string})
SERVICE_TO_METHOD = {
SERVICE_BUTTON: ServiceMethodDetails(method="async_button", schema=BUTTON_SCHEMA),
SERVICE_COMMAND: ServiceMethodDetails(
method="async_command", schema=COMMAND_SCHEMA
),
SERVICE_SELECT_SOUND_OUTPUT: ServiceMethodDetails(
method="async_select_sound_output",
schema=SOUND_OUTPUT_SCHEMA,
),
}
_LOGGER = logging.getLogger(__name__)
@ -100,17 +61,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# Update the stored key without triggering reauth
update_client_key(hass, entry, client)
async def async_service_handler(service: ServiceCall) -> None:
method = SERVICE_TO_METHOD[service.service]
data = service.data.copy()
data["method"] = method.method
async_dispatcher_send(hass, DOMAIN, data)
for service, method in SERVICE_TO_METHOD.items():
hass.services.async_register(
DOMAIN, service, async_service_handler, schema=method.schema
)
hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id] = client
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
@ -174,17 +124,10 @@ def update_client_key(
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
client = hass.data[DOMAIN][DATA_CONFIG_ENTRY].pop(entry.entry_id)
await hass_notify.async_reload(hass, DOMAIN)
client.clear_state_update_callbacks()
await client.disconnect()
# unregister service calls, check if this is the last entry to unload
if unload_ok and not hass.data[DOMAIN][DATA_CONFIG_ENTRY]:
for service in SERVICE_TO_METHOD:
hass.services.async_remove(DOMAIN, service)
return unload_ok

View File

@ -12,6 +12,7 @@ import logging
from typing import Any, Concatenate, cast
from aiowebostv import WebOsClient, WebOsTvPairError
import voluptuous as vol
from homeassistant import util
from homeassistant.components.media_player import (
@ -22,29 +23,29 @@ from homeassistant.components.media_player import (
MediaType,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_SUPPORTED_FEATURES,
ENTITY_MATCH_ALL,
ENTITY_MATCH_NONE,
)
from homeassistant.const import ATTR_COMMAND, ATTR_SUPPORTED_FEATURES
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.trigger import PluggableAction
from homeassistant.helpers.typing import VolDictType
from . import update_client_key
from .const import (
ATTR_BUTTON,
ATTR_PAYLOAD,
ATTR_SOUND_OUTPUT,
CONF_SOURCES,
DATA_CONFIG_ENTRY,
DOMAIN,
LIVE_TV_APP_ID,
SERVICE_BUTTON,
SERVICE_COMMAND,
SERVICE_SELECT_SOUND_OUTPUT,
WEBOSTV_EXCEPTIONS,
)
from .triggers.turn_on import async_get_turn_on_trigger
@ -71,11 +72,29 @@ MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1)
PARALLEL_UPDATES = 0
SCAN_INTERVAL = timedelta(seconds=10)
BUTTON_SCHEMA: VolDictType = {vol.Required(ATTR_BUTTON): cv.string}
COMMAND_SCHEMA: VolDictType = {
vol.Required(ATTR_COMMAND): cv.string,
vol.Optional(ATTR_PAYLOAD): dict,
}
SOUND_OUTPUT_SCHEMA: VolDictType = {vol.Required(ATTR_SOUND_OUTPUT): cv.string}
SERVICES = (
(SERVICE_BUTTON, BUTTON_SCHEMA, "async_button"),
(SERVICE_COMMAND, COMMAND_SCHEMA, "async_command"),
(SERVICE_SELECT_SOUND_OUTPUT, SOUND_OUTPUT_SCHEMA, "async_select_sound_output"),
)
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the LG webOS Smart TV platform."""
platform = entity_platform.async_get_current_platform()
for service_name, schema, method in SERVICES:
platform.async_register_entity_service(service_name, schema, method)
client = hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id]
async_add_entities([LgWebOSMediaPlayerEntity(entry, client)])
@ -143,10 +162,6 @@ class LgWebOSMediaPlayerEntity(RestoreEntity, MediaPlayerEntity):
)
)
self.async_on_remove(
async_dispatcher_connect(self.hass, DOMAIN, self.async_signal_handler)
)
await self._client.register_state_update_callback(
self.async_handle_state_update
)
@ -166,19 +181,6 @@ class LgWebOSMediaPlayerEntity(RestoreEntity, MediaPlayerEntity):
"""Call disconnect on removal."""
self._client.unregister_state_update_callback(self.async_handle_state_update)
async def async_signal_handler(self, data: dict[str, Any]) -> None:
"""Handle domain-specific signal by calling appropriate method."""
if (entity_ids := data[ATTR_ENTITY_ID]) == ENTITY_MATCH_NONE:
return
if entity_ids == ENTITY_MATCH_ALL or self.entity_id in entity_ids:
params = {
key: value
for key, value in data.items()
if key not in ["entity_id", "method"]
}
await getattr(self, data["method"])(**params)
async def async_handle_state_update(self, _client: WebOsClient) -> None:
"""Update state from WebOsClient."""
self._update_states()

View File

@ -1,8 +1,6 @@
rules:
# Bronze
action-setup:
status: todo
comment: move actions to entity services
action-setup: done
appropriate-polling: done
brands: done
common-modules: