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 from contextlib import suppress
import logging import logging
from typing import NamedTuple
from aiowebostv import WebOsClient, WebOsTvPairError from aiowebostv import WebOsClient, WebOsTvPairError
import voluptuous as vol
from homeassistant.components import notify as hass_notify from homeassistant.components import notify as hass_notify
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
ATTR_COMMAND,
ATTR_ENTITY_ID,
CONF_CLIENT_SECRET, CONF_CLIENT_SECRET,
CONF_HOST, CONF_HOST,
CONF_NAME, CONF_NAME,
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_STOP,
) )
from homeassistant.core import Event, HomeAssistant, ServiceCall from homeassistant.core import Event, HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers import config_validation as cv, discovery from homeassistant.helpers import config_validation as cv, discovery
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from .const import ( from .const import (
ATTR_BUTTON,
ATTR_CONFIG_ENTRY_ID, ATTR_CONFIG_ENTRY_ID,
ATTR_PAYLOAD,
ATTR_SOUND_OUTPUT,
DATA_CONFIG_ENTRY, DATA_CONFIG_ENTRY,
DATA_HASS_CONFIG, DATA_HASS_CONFIG,
DOMAIN, DOMAIN,
PLATFORMS, PLATFORMS,
SERVICE_BUTTON,
SERVICE_COMMAND,
SERVICE_SELECT_SOUND_OUTPUT,
WEBOSTV_EXCEPTIONS, WEBOSTV_EXCEPTIONS,
) )
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN) 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__) _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 the stored key without triggering reauth
update_client_key(hass, entry, client) 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 hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id] = client
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) 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: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
if unload_ok:
client = hass.data[DOMAIN][DATA_CONFIG_ENTRY].pop(entry.entry_id) client = hass.data[DOMAIN][DATA_CONFIG_ENTRY].pop(entry.entry_id)
await hass_notify.async_reload(hass, DOMAIN) await hass_notify.async_reload(hass, DOMAIN)
client.clear_state_update_callbacks() client.clear_state_update_callbacks()
await client.disconnect() 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 return unload_ok

View File

@ -12,6 +12,7 @@ import logging
from typing import Any, Concatenate, cast from typing import Any, Concatenate, cast
from aiowebostv import WebOsClient, WebOsTvPairError from aiowebostv import WebOsClient, WebOsTvPairError
import voluptuous as vol
from homeassistant import util from homeassistant import util
from homeassistant.components.media_player import ( from homeassistant.components.media_player import (
@ -22,29 +23,29 @@ from homeassistant.components.media_player import (
MediaType, MediaType,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import ATTR_COMMAND, ATTR_SUPPORTED_FEATURES
ATTR_ENTITY_ID,
ATTR_SUPPORTED_FEATURES,
ENTITY_MATCH_ALL,
ENTITY_MATCH_NONE,
)
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError 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.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import DeviceInfo 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.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.trigger import PluggableAction from homeassistant.helpers.trigger import PluggableAction
from homeassistant.helpers.typing import VolDictType
from . import update_client_key from . import update_client_key
from .const import ( from .const import (
ATTR_BUTTON,
ATTR_PAYLOAD, ATTR_PAYLOAD,
ATTR_SOUND_OUTPUT, ATTR_SOUND_OUTPUT,
CONF_SOURCES, CONF_SOURCES,
DATA_CONFIG_ENTRY, DATA_CONFIG_ENTRY,
DOMAIN, DOMAIN,
LIVE_TV_APP_ID, LIVE_TV_APP_ID,
SERVICE_BUTTON,
SERVICE_COMMAND,
SERVICE_SELECT_SOUND_OUTPUT,
WEBOSTV_EXCEPTIONS, WEBOSTV_EXCEPTIONS,
) )
from .triggers.turn_on import async_get_turn_on_trigger 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 PARALLEL_UPDATES = 0
SCAN_INTERVAL = timedelta(seconds=10) 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( async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None: ) -> None:
"""Set up the LG webOS Smart TV platform.""" """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] client = hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id]
async_add_entities([LgWebOSMediaPlayerEntity(entry, client)]) 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( await self._client.register_state_update_callback(
self.async_handle_state_update self.async_handle_state_update
) )
@ -166,19 +181,6 @@ class LgWebOSMediaPlayerEntity(RestoreEntity, MediaPlayerEntity):
"""Call disconnect on removal.""" """Call disconnect on removal."""
self._client.unregister_state_update_callback(self.async_handle_state_update) 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: async def async_handle_state_update(self, _client: WebOsClient) -> None:
"""Update state from WebOsClient.""" """Update state from WebOsClient."""
self._update_states() self._update_states()

View File

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