Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Support for LG webOS Smart TV."""
2 
3 from __future__ import annotations
4 
5 from contextlib import suppress
6 import logging
7 from typing import NamedTuple
8 
9 from aiowebostv import WebOsClient, WebOsTvPairError
10 import voluptuous as vol
11 
12 from homeassistant.components import notify as hass_notify
13 from homeassistant.config_entries import ConfigEntry
14 from homeassistant.const import (
15  ATTR_COMMAND,
16  ATTR_ENTITY_ID,
17  CONF_CLIENT_SECRET,
18  CONF_HOST,
19  CONF_NAME,
20  EVENT_HOMEASSISTANT_STOP,
21 )
22 from homeassistant.core import Event, HomeAssistant, ServiceCall
23 from homeassistant.exceptions import ConfigEntryAuthFailed
24 from homeassistant.helpers import config_validation as cv, discovery
25 from homeassistant.helpers.dispatcher import async_dispatcher_send
26 from homeassistant.helpers.typing import ConfigType
27 
28 from .const import (
29  ATTR_BUTTON,
30  ATTR_CONFIG_ENTRY_ID,
31  ATTR_PAYLOAD,
32  ATTR_SOUND_OUTPUT,
33  DATA_CONFIG_ENTRY,
34  DATA_HASS_CONFIG,
35  DOMAIN,
36  PLATFORMS,
37  SERVICE_BUTTON,
38  SERVICE_COMMAND,
39  SERVICE_SELECT_SOUND_OUTPUT,
40  WEBOSTV_EXCEPTIONS,
41 )
42 
43 CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
44 
45 CALL_SCHEMA = vol.Schema({vol.Required(ATTR_ENTITY_ID): cv.comp_entity_ids})
46 
47 
48 class ServiceMethodDetails(NamedTuple):
49  """Details for SERVICE_TO_METHOD mapping."""
50 
51  method: str
52  schema: vol.Schema
53 
54 
55 BUTTON_SCHEMA = CALL_SCHEMA.extend({vol.Required(ATTR_BUTTON): cv.string})
56 
57 COMMAND_SCHEMA = CALL_SCHEMA.extend(
58  {vol.Required(ATTR_COMMAND): cv.string, vol.Optional(ATTR_PAYLOAD): dict}
59 )
60 
61 SOUND_OUTPUT_SCHEMA = CALL_SCHEMA.extend({vol.Required(ATTR_SOUND_OUTPUT): cv.string})
62 
63 SERVICE_TO_METHOD = {
64  SERVICE_BUTTON: ServiceMethodDetails(method="async_button", schema=BUTTON_SCHEMA),
65  SERVICE_COMMAND: ServiceMethodDetails(
66  method="async_command", schema=COMMAND_SCHEMA
67  ),
68  SERVICE_SELECT_SOUND_OUTPUT: ServiceMethodDetails(
69  method="async_select_sound_output",
70  schema=SOUND_OUTPUT_SCHEMA,
71  ),
72 }
73 
74 _LOGGER = logging.getLogger(__name__)
75 
76 
77 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
78  """Set up the LG WebOS TV platform."""
79  hass.data.setdefault(DOMAIN, {})
80  hass.data[DOMAIN].setdefault(DATA_CONFIG_ENTRY, {})
81  hass.data[DOMAIN][DATA_HASS_CONFIG] = config
82 
83  return True
84 
85 
86 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
87  """Set the config entry up."""
88  host = entry.data[CONF_HOST]
89  key = entry.data[CONF_CLIENT_SECRET]
90 
91  # Attempt a connection, but fail gracefully if tv is off for example.
92  client = WebOsClient(host, key)
93  with suppress(*WEBOSTV_EXCEPTIONS):
94  try:
95  await client.connect()
96  except WebOsTvPairError as err:
97  raise ConfigEntryAuthFailed(err) from err
98 
99  # If pairing request accepted there will be no error
100  # Update the stored key without triggering reauth
101  update_client_key(hass, entry, client)
102 
103  async def async_service_handler(service: ServiceCall) -> None:
104  method = SERVICE_TO_METHOD[service.service]
105  data = service.data.copy()
106  data["method"] = method.method
107  async_dispatcher_send(hass, DOMAIN, data)
108 
109  for service, method in SERVICE_TO_METHOD.items():
110  hass.services.async_register(
111  DOMAIN, service, async_service_handler, schema=method.schema
112  )
113 
114  hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id] = client
115  await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
116 
117  # set up notify platform, no entry support for notify component yet,
118  # have to use discovery to load platform.
119  hass.async_create_task(
120  discovery.async_load_platform(
121  hass,
122  "notify",
123  DOMAIN,
124  {
125  CONF_NAME: entry.title,
126  ATTR_CONFIG_ENTRY_ID: entry.entry_id,
127  },
128  hass.data[DOMAIN][DATA_HASS_CONFIG],
129  )
130  )
131 
132  if not entry.update_listeners:
133  entry.async_on_unload(entry.add_update_listener(async_update_options))
134 
135  async def async_on_stop(_event: Event) -> None:
136  """Unregister callbacks and disconnect."""
137  client.clear_state_update_callbacks()
138  await client.disconnect()
139 
140  entry.async_on_unload(
141  hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_on_stop)
142  )
143  return True
144 
145 
146 async def async_update_options(hass: HomeAssistant, entry: ConfigEntry) -> None:
147  """Update options."""
148  await hass.config_entries.async_reload(entry.entry_id)
149 
150 
151 async def async_control_connect(host: str, key: str | None) -> WebOsClient:
152  """LG Connection."""
153  client = WebOsClient(host, key)
154  try:
155  await client.connect()
156  except WebOsTvPairError:
157  _LOGGER.warning("Connected to LG webOS TV %s but not paired", host)
158  raise
159 
160  return client
161 
162 
164  hass: HomeAssistant, entry: ConfigEntry, client: WebOsClient
165 ) -> None:
166  """Check and update stored client key if key has changed."""
167  host = entry.data[CONF_HOST]
168  key = entry.data[CONF_CLIENT_SECRET]
169 
170  if client.client_key != key:
171  _LOGGER.debug("Updating client key for host %s", host)
172  data = {CONF_HOST: host, CONF_CLIENT_SECRET: client.client_key}
173  hass.config_entries.async_update_entry(entry, data=data)
174 
175 
176 async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
177  """Unload a config entry."""
178  unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
179 
180  if unload_ok:
181  client = hass.data[DOMAIN][DATA_CONFIG_ENTRY].pop(entry.entry_id)
182  await hass_notify.async_reload(hass, DOMAIN)
183  client.clear_state_update_callbacks()
184  await client.disconnect()
185 
186  # unregister service calls, check if this is the last entry to unload
187  if unload_ok and not hass.data[DOMAIN][DATA_CONFIG_ENTRY]:
188  for service in SERVICE_TO_METHOD:
189  hass.services.async_remove(DOMAIN, service)
190 
191  return unload_ok
WebOsClient async_control_connect(str host, str|None key)
Definition: __init__.py:151
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:77
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:176
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:86
None async_update_options(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:146
None update_client_key(HomeAssistant hass, ConfigEntry entry, WebOsClient client)
Definition: __init__.py:165
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:193