Add missing translations for LG webOS TV and fix names (#136438)

This commit is contained in:
Shay Levy 2025-01-24 15:13:10 +02:00 committed by GitHub
parent 5d353a9833
commit 47efb68780
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 92 additions and 75 deletions

View File

@ -1,14 +1,12 @@
"""Support for LG webOS Smart TV.""" """The LG webOS TV integration."""
from __future__ import annotations from __future__ import annotations
from contextlib import suppress from contextlib import suppress
import logging
from aiowebostv import WebOsClient, WebOsTvPairError from aiowebostv import WebOsClient, WebOsTvPairError
from homeassistant.components import notify as hass_notify from homeassistant.components import notify as hass_notify
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
CONF_CLIENT_SECRET, CONF_CLIENT_SECRET,
CONF_HOST, CONF_HOST,
@ -29,17 +27,13 @@ from .const import (
PLATFORMS, PLATFORMS,
WEBOSTV_EXCEPTIONS, WEBOSTV_EXCEPTIONS,
) )
from .helpers import WebOsTvConfigEntry, update_client_key
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN) CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
_LOGGER = logging.getLogger(__name__)
type WebOsTvConfigEntry = ConfigEntry[WebOsClient]
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the LG WebOS TV platform.""" """Set up the LG webOS TV platform."""
hass.data.setdefault(DOMAIN, {DATA_HASS_CONFIG: config}) hass.data.setdefault(DOMAIN, {DATA_HASS_CONFIG: config})
return True return True
@ -62,7 +56,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: WebOsTvConfigEntry) -> b
# If pairing request accepted there will be no error # If pairing request accepted there will be no error
# 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)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
@ -99,19 +93,6 @@ async def async_update_options(hass: HomeAssistant, entry: WebOsTvConfigEntry) -
await hass.config_entries.async_reload(entry.entry_id) await hass.config_entries.async_reload(entry.entry_id)
def update_client_key(
hass: HomeAssistant, entry: ConfigEntry, client: WebOsClient
) -> None:
"""Check and update stored client key if key has changed."""
host = entry.data[CONF_HOST]
key = entry.data[CONF_CLIENT_SECRET]
if client.client_key != key:
_LOGGER.debug("Updating client key for host %s", host)
data = {CONF_HOST: host, CONF_CLIENT_SECRET: client.client_key}
hass.config_entries.async_update_entry(entry, data=data)
async def async_unload_entry(hass: HomeAssistant, entry: WebOsTvConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: WebOsTvConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):

View File

@ -1,4 +1,4 @@
"""Config flow to configure webostv component.""" """Config flow for LG webOS TV integration."""
from __future__ import annotations from __future__ import annotations
@ -35,7 +35,7 @@ DATA_SCHEMA = vol.Schema(
async def async_control_connect( async def async_control_connect(
hass: HomeAssistant, host: str, key: str | None hass: HomeAssistant, host: str, key: str | None
) -> WebOsClient: ) -> WebOsClient:
"""Create LG WebOS client and connect to the TV.""" """Create LG webOS client and connect to the TV."""
client = WebOsClient( client = WebOsClient(
host, host,
key, key,
@ -48,7 +48,7 @@ async def async_control_connect(
class FlowHandler(ConfigFlow, domain=DOMAIN): class FlowHandler(ConfigFlow, domain=DOMAIN):
"""WebosTV configuration flow.""" """LG webOS TV configuration flow."""
VERSION = 1 VERSION = 1

View File

@ -1,4 +1,4 @@
"""Constants used for LG webOS Smart TV.""" """Constants for the LG webOS TV integration."""
import asyncio import asyncio

View File

@ -1,4 +1,4 @@
"""Provides device automations for control of LG webOS Smart TV.""" """Provides device automations for control of LG webOS TV."""
from __future__ import annotations from __future__ import annotations

View File

@ -1,4 +1,4 @@
"""Diagnostics support for LG webOS Smart TV.""" """Diagnostics support for LG webOS TV."""
from __future__ import annotations from __future__ import annotations

View File

@ -1,17 +1,24 @@
"""Helper functions for webOS Smart TV.""" """Helper functions for LG webOS TV."""
from __future__ import annotations from __future__ import annotations
import logging
from aiowebostv import WebOsClient from aiowebostv import WebOsClient
from homeassistant.config_entries import ConfigEntryState from homeassistant.config_entries import ConfigEntry, ConfigEntryState
from homeassistant.const import CONF_CLIENT_SECRET, CONF_HOST
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.device_registry import DeviceEntry from homeassistant.helpers.device_registry import DeviceEntry
from . import WebOsTvConfigEntry
from .const import DOMAIN, LIVE_TV_APP_ID from .const import DOMAIN, LIVE_TV_APP_ID
_LOGGER = logging.getLogger(__name__)
type WebOsTvConfigEntry = ConfigEntry[WebOsClient]
@callback @callback
def async_get_device_entry_by_device_id( def async_get_device_entry_by_device_id(
@ -32,7 +39,7 @@ def async_get_device_entry_by_device_id(
def async_get_device_id_from_entity_id(hass: HomeAssistant, entity_id: str) -> str: def async_get_device_id_from_entity_id(hass: HomeAssistant, entity_id: str) -> str:
"""Get device ID from an entity ID. """Get device ID from an entity ID.
Raises ValueError if entity or device ID is invalid. Raises HomeAssistantError if entity or device ID is invalid.
""" """
ent_reg = er.async_get(hass) ent_reg = er.async_get(hass)
entity_entry = ent_reg.async_get(entity_id) entity_entry = ent_reg.async_get(entity_id)
@ -42,7 +49,11 @@ def async_get_device_id_from_entity_id(hass: HomeAssistant, entity_id: str) -> s
or entity_entry.device_id is None or entity_entry.device_id is None
or entity_entry.platform != DOMAIN or entity_entry.platform != DOMAIN
): ):
raise ValueError(f"Entity {entity_id} is not a valid {DOMAIN} entity.") raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="invalid_entity_id",
translation_placeholders={"entity_id": entity_id},
)
return entity_entry.device_id return entity_entry.device_id
@ -91,3 +102,15 @@ def get_sources(client: WebOsClient) -> list[str]:
# Preserve order when filtering duplicates # Preserve order when filtering duplicates
return list(dict.fromkeys(sources)) return list(dict.fromkeys(sources))
def update_client_key(hass: HomeAssistant, entry: WebOsTvConfigEntry) -> None:
"""Check and update stored client key if key has changed."""
client: WebOsClient = entry.runtime_data
host = entry.data[CONF_HOST]
key = entry.data[CONF_CLIENT_SECRET]
if client.client_key != key:
_LOGGER.debug("Updating client key for host %s", host)
data = {CONF_HOST: host, CONF_CLIENT_SECRET: client.client_key}
hass.config_entries.async_update_entry(entry, data=data)

View File

@ -1,4 +1,4 @@
"""Support for interface with an LG webOS Smart TV.""" """Support for interface with an LG webOS TV."""
from __future__ import annotations from __future__ import annotations
@ -33,7 +33,6 @@ 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 homeassistant.helpers.typing import VolDictType
from . import WebOsTvConfigEntry, update_client_key
from .const import ( from .const import (
ATTR_BUTTON, ATTR_BUTTON,
ATTR_PAYLOAD, ATTR_PAYLOAD,
@ -46,6 +45,7 @@ from .const import (
SERVICE_SELECT_SOUND_OUTPUT, SERVICE_SELECT_SOUND_OUTPUT,
WEBOSTV_EXCEPTIONS, WEBOSTV_EXCEPTIONS,
) )
from .helpers import WebOsTvConfigEntry, update_client_key
from .triggers.turn_on import async_get_turn_on_trigger from .triggers.turn_on import async_get_turn_on_trigger
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -89,7 +89,7 @@ async def async_setup_entry(
entry: WebOsTvConfigEntry, entry: WebOsTvConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up the LG webOS Smart TV platform.""" """Set up the LG webOS TV platform."""
platform = entity_platform.async_get_current_platform() platform = entity_platform.async_get_current_platform()
for service_name, schema, method in SERVICES: for service_name, schema, method in SERVICES:
@ -132,7 +132,7 @@ def cmd[_T: LgWebOSMediaPlayerEntity, **_P](
class LgWebOSMediaPlayerEntity(RestoreEntity, MediaPlayerEntity): class LgWebOSMediaPlayerEntity(RestoreEntity, MediaPlayerEntity):
"""Representation of a LG webOS Smart TV.""" """Representation of a LG webOS TV."""
_attr_device_class = MediaPlayerDeviceClass.TV _attr_device_class = MediaPlayerDeviceClass.TV
_attr_has_entity_name = True _attr_has_entity_name = True
@ -335,7 +335,7 @@ class LgWebOSMediaPlayerEntity(RestoreEntity, MediaPlayerEntity):
except WebOsTvPairError: except WebOsTvPairError:
self._entry.async_start_reauth(self.hass) self._entry.async_start_reauth(self.hass)
else: else:
update_client_key(self.hass, self._entry, self._client) update_client_key(self.hass, self._entry)
@property @property
def supported_features(self) -> MediaPlayerEntityFeature: def supported_features(self) -> MediaPlayerEntityFeature:
@ -392,10 +392,14 @@ class LgWebOSMediaPlayerEntity(RestoreEntity, MediaPlayerEntity):
async def async_select_source(self, source: str) -> None: async def async_select_source(self, source: str) -> None:
"""Select input source.""" """Select input source."""
if (source_dict := self._source_list.get(source)) is None: if (source_dict := self._source_list.get(source)) is None:
_LOGGER.warning( raise HomeAssistantError(
"Source %s not found for %s", source, self._friendly_name_internal() translation_domain=DOMAIN,
translation_key="source_not_found",
translation_placeholders={
"source": source,
"name": str(self._friendly_name_internal()),
},
) )
return
if source_dict.get("title"): if source_dict.get("title"):
await self._client.launch_app(source_dict["id"]) await self._client.launch_app(source_dict["id"])
elif source_dict.get("label"): elif source_dict.get("label"):

View File

@ -1,4 +1,4 @@
"""Support for LG WebOS TV notification service.""" """Support for LG webOS TV notification service."""
from __future__ import annotations from __future__ import annotations
@ -37,7 +37,7 @@ async def async_get_service(
class LgWebOSNotificationService(BaseNotificationService): class LgWebOSNotificationService(BaseNotificationService):
"""Implement the notification service for LG WebOS TV.""" """Implement the notification service for LG webOS TV."""
def __init__(self, entry: WebOsTvConfigEntry) -> None: def __init__(self, entry: WebOsTvConfigEntry) -> None:
"""Initialize the service.""" """Initialize the service."""

View File

@ -12,7 +12,7 @@
} }
}, },
"pairing": { "pairing": {
"title": "webOS TV Pairing", "title": "LG webOS TV Pairing",
"description": "Select **Submit** and accept the pairing request on your TV.\n\n![Image](/static/images/config_webos.png)" "description": "Select **Submit** and accept the pairing request on your TV.\n\n![Image](/static/images/config_webos.png)"
}, },
"reauth_confirm": { "reauth_confirm": {
@ -43,7 +43,7 @@
"options": { "options": {
"step": { "step": {
"init": { "init": {
"title": "Options for webOS Smart TV", "title": "Options for LG webOS TV",
"description": "Select enabled sources", "description": "Select enabled sources",
"data": { "data": {
"sources": "Sources list" "sources": "Sources list"
@ -129,6 +129,15 @@
}, },
"unhandled_trigger_type": { "unhandled_trigger_type": {
"message": "Unhandled trigger type: {trigger_type}" "message": "Unhandled trigger type: {trigger_type}"
},
"unknown_trigger_platform": {
"message": "Unknown trigger platform: {platform}"
},
"invalid_entity_id": {
"message": "Entity {entity_id} is not a valid webostv entity."
},
"source_not_found": {
"message": "Source {source} not found in the sources list for {name}."
} }
} }
} }

View File

@ -1,4 +1,4 @@
"""webOS Smart TV trigger dispatcher.""" """LG webOS TV trigger dispatcher."""
from __future__ import annotations from __future__ import annotations
@ -6,6 +6,7 @@ from typing import cast
from homeassistant.const import CONF_PLATFORM from homeassistant.const import CONF_PLATFORM
from homeassistant.core import CALLBACK_TYPE, HomeAssistant from homeassistant.core import CALLBACK_TYPE, HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.trigger import ( from homeassistant.helpers.trigger import (
TriggerActionType, TriggerActionType,
TriggerInfo, TriggerInfo,
@ -13,6 +14,7 @@ from homeassistant.helpers.trigger import (
) )
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from .const import DOMAIN
from .triggers import turn_on from .triggers import turn_on
TRIGGERS = { TRIGGERS = {
@ -24,8 +26,10 @@ def _get_trigger_platform(config: ConfigType) -> TriggerProtocol:
"""Return trigger platform.""" """Return trigger platform."""
platform_split = config[CONF_PLATFORM].split(".", maxsplit=1) platform_split = config[CONF_PLATFORM].split(".", maxsplit=1)
if len(platform_split) < 2 or platform_split[1] not in TRIGGERS: if len(platform_split) < 2 or platform_split[1] not in TRIGGERS:
raise ValueError( raise HomeAssistantError(
f"Unknown webOS Smart TV trigger platform {config[CONF_PLATFORM]}" translation_domain=DOMAIN,
translation_key="unknown_trigger_platform",
translation_placeholders={"platform": config[CONF_PLATFORM]},
) )
return cast(TriggerProtocol, TRIGGERS[platform_split[1]]) return cast(TriggerProtocol, TRIGGERS[platform_split[1]])

View File

@ -1 +1 @@
"""webOS Smart TV triggers.""" """LG webOS TV triggers."""

View File

@ -1,4 +1,4 @@
"""webOS Smart TV device turn on trigger.""" """LG webOS TV device turn on trigger."""
from __future__ import annotations from __future__ import annotations

View File

@ -1,4 +1,4 @@
"""Tests for the WebOS TV integration.""" """Tests for the LG webOS TV integration."""
from homeassistant.components.webostv.const import DOMAIN from homeassistant.components.webostv.const import DOMAIN
from homeassistant.const import CONF_CLIENT_SECRET, CONF_HOST from homeassistant.const import CONF_CLIENT_SECRET, CONF_HOST

View File

@ -1,4 +1,4 @@
"""Common fixtures and objects for the LG webOS integration tests.""" """Common fixtures and objects for the LG webOS TV integration tests."""
from collections.abc import Generator from collections.abc import Generator
from unittest.mock import AsyncMock, Mock, patch from unittest.mock import AsyncMock, Mock, patch

View File

@ -1,4 +1,4 @@
"""Constants for LG webOS Smart TV tests.""" """Constants for LG webOS TV tests."""
from homeassistant.components.media_player import DOMAIN as MP_DOMAIN from homeassistant.components.media_player import DOMAIN as MP_DOMAIN
from homeassistant.components.webostv.const import LIVE_TV_APP_ID from homeassistant.components.webostv.const import LIVE_TV_APP_ID

View File

@ -1,4 +1,4 @@
"""Test the WebOS Tv config flow.""" """Test the LG webOS TV config flow."""
from aiowebostv import WebOsTvPairError from aiowebostv import WebOsTvPairError
import pytest import pytest

View File

@ -1,4 +1,4 @@
"""The tests for WebOS TV device triggers.""" """The tests for LG webOS TV device triggers."""
import pytest import pytest
@ -140,7 +140,6 @@ async def test_invalid_entry_raises(
hass: HomeAssistant, hass: HomeAssistant,
device_registry: dr.DeviceRegistry, device_registry: dr.DeviceRegistry,
client, client,
caplog: pytest.LogCaptureFixture,
domain: str, domain: str,
entry_state: ConfigEntryState, entry_state: ConfigEntryState,
) -> None: ) -> None:

View File

@ -1,4 +1,4 @@
"""Tests for the diagnostics data provided by LG webOS Smart TV.""" """Tests for the diagnostics data provided by LG webOS TV."""
from syrupy.assertion import SnapshotAssertion from syrupy.assertion import SnapshotAssertion
from syrupy.filters import props from syrupy.filters import props

View File

@ -1,4 +1,4 @@
"""The tests for the LG webOS media player platform.""" """The tests for the LG webOS TV media player platform."""
from datetime import timedelta from datetime import timedelta
from http import HTTPStatus from http import HTTPStatus
@ -165,7 +165,7 @@ async def test_media_next_previous_track(
async def test_select_source_with_empty_source_list( async def test_select_source_with_empty_source_list(
hass: HomeAssistant, client, caplog: pytest.LogCaptureFixture hass: HomeAssistant, client
) -> None: ) -> None:
"""Ensure we don't call client methods when we don't have sources.""" """Ensure we don't call client methods when we don't have sources."""
await setup_webostv(hass) await setup_webostv(hass)
@ -175,11 +175,14 @@ async def test_select_source_with_empty_source_list(
ATTR_ENTITY_ID: ENTITY_ID, ATTR_ENTITY_ID: ENTITY_ID,
ATTR_INPUT_SOURCE: "nonexistent", ATTR_INPUT_SOURCE: "nonexistent",
} }
with pytest.raises(
HomeAssistantError,
match=f"Source nonexistent not found in the sources list for {TV_NAME}",
):
await hass.services.async_call(MP_DOMAIN, SERVICE_SELECT_SOURCE, data, True) await hass.services.async_call(MP_DOMAIN, SERVICE_SELECT_SOURCE, data, True)
client.launch_app.assert_not_called() client.launch_app.assert_not_called()
client.set_input.assert_not_called() client.set_input.assert_not_called()
assert f"Source nonexistent not found for {TV_NAME}" in caplog.text
async def test_select_app_source(hass: HomeAssistant, client) -> None: async def test_select_app_source(hass: HomeAssistant, client) -> None:

View File

@ -1,4 +1,4 @@
"""The tests for the WebOS TV notify platform.""" """The tests for the LG webOS TV notify platform."""
from unittest.mock import call from unittest.mock import call

View File

@ -1,4 +1,4 @@
"""The tests for WebOS TV automation triggers.""" """The tests for LG webOS TV automation triggers."""
from unittest.mock import patch from unittest.mock import patch
@ -118,10 +118,10 @@ async def test_webostv_turn_on_trigger_entity_id(
assert service_calls[1].data["id"] == 0 assert service_calls[1].data["id"] == 0
async def test_wrong_trigger_platform_type( async def test_unknown_trigger_platform_type(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, client hass: HomeAssistant, caplog: pytest.LogCaptureFixture, client
) -> None: ) -> None:
"""Test wrong trigger platform type.""" """Test unknown trigger platform type."""
await setup_webostv(hass) await setup_webostv(hass)
await async_setup_component( await async_setup_component(
@ -131,7 +131,7 @@ async def test_wrong_trigger_platform_type(
automation.DOMAIN: [ automation.DOMAIN: [
{ {
"trigger": { "trigger": {
"platform": "webostv.wrong_type", "platform": "webostv.unknown",
"entity_id": ENTITY_ID, "entity_id": ENTITY_ID,
}, },
"action": { "action": {
@ -146,10 +146,7 @@ async def test_wrong_trigger_platform_type(
}, },
) )
assert ( assert "Unknown trigger platform: webostv.unknown" in caplog.text
"ValueError: Unknown webOS Smart TV trigger platform webostv.wrong_type"
in caplog.text
)
async def test_trigger_invalid_entity_id( async def test_trigger_invalid_entity_id(
@ -185,7 +182,4 @@ async def test_trigger_invalid_entity_id(
}, },
) )
assert ( assert f"Entity {invalid_entity} is not a valid {DOMAIN} entity" in caplog.text
f"ValueError: Entity {invalid_entity} is not a valid webostv entity"
in caplog.text
)