1 """The Samsung TV integration."""
3 from __future__
import annotations
5 from collections.abc
import Coroutine, Mapping
6 from functools
import partial
8 from urllib.parse
import urlparse
21 EVENT_HOMEASSISTANT_STOP,
31 async_get_device_info,
33 model_requires_encryption,
37 CONF_SSDP_MAIN_TV_AGENT_LOCATION,
38 CONF_SSDP_RENDERING_CONTROL_LOCATION,
39 ENTRY_RELOAD_COOLDOWN,
42 METHOD_ENCRYPTED_WEBSOCKET,
44 UPNP_SVC_MAIN_TV_AGENT,
45 UPNP_SVC_RENDERING_CONTROL,
47 from .coordinator
import SamsungTVDataUpdateCoordinator
49 PLATFORMS = [Platform.MEDIA_PLAYER, Platform.REMOTE]
52 SamsungTVConfigEntry = ConfigEntry[SamsungTVDataUpdateCoordinator]
57 hass: HomeAssistant, data: dict[str, Any]
59 """Get device bridge."""
60 return SamsungTVBridge.get_bridge(
70 """Reload only after the timer expires."""
72 def __init__(self, hass: HomeAssistant, entry: ConfigEntry) ->
None:
73 """Init the debounced entry reloader."""
77 self._debounced_reload: Debouncer[Coroutine[Any, Any,
None]] =
Debouncer(
80 cooldown=ENTRY_RELOAD_COOLDOWN,
85 async
def async_call(self, hass: HomeAssistant, entry: ConfigEntry) ->
None:
86 """Start the countdown for a reload."""
87 if (new_token := entry.data.get(CONF_TOKEN)) != self.
tokentoken:
88 LOGGER.debug(
"Skipping reload as its a token update")
89 self.
tokentoken = new_token
91 LOGGER.debug(
"Calling debouncer to get a reload after cooldown")
96 """Cancel any pending reload."""
101 LOGGER.debug(
"Reloading entry %s", self.
entryentry.title)
102 await self.
hasshass.config_entries.async_reload(self.
entryentry.entry_id)
106 """Update ssdp locations from discovery cache."""
108 for ssdp_st, key
in (
109 (UPNP_SVC_RENDERING_CONTROL, CONF_SSDP_RENDERING_CONTROL_LOCATION),
110 (UPNP_SVC_MAIN_TV_AGENT, CONF_SSDP_MAIN_TV_AGENT_LOCATION),
112 for discovery_info
in await ssdp.async_get_discovery_info_by_st(hass, ssdp_st):
113 location = discovery_info.ssdp_location
114 host = urlparse(location).hostname
115 if host == entry.data[CONF_HOST]:
116 updates[key] = location
120 hass.config_entries.async_update_entry(entry, data={**entry.data, **updates})
124 """Set up the Samsung TV platform."""
126 if entry.data.get(CONF_METHOD) == METHOD_ENCRYPTED_WEBSOCKET:
127 if not entry.data.get(CONF_TOKEN)
or not entry.data.get(CONF_SESSION_ID):
129 "Token and session id are required in encrypted mode"
134 def _access_denied() -> None:
135 """Access denied callback."""
136 LOGGER.debug(
"Access denied in getting remote object")
137 entry.async_start_reauth(hass)
139 bridge.register_reauth_callback(_access_denied)
143 def _update_config_entry(updates: Mapping[str, Any]) ->
None:
144 """Update config entry with the new token."""
145 hass.config_entries.async_update_entry(entry, data={**entry.data, **updates})
147 bridge.register_update_config_entry_callback(_update_config_entry)
149 async
def stop_bridge(event: Event |
None =
None) ->
None:
150 """Stop SamsungTV bridge connection."""
151 LOGGER.debug(
"Stopping SamsungTVBridge %s", bridge.host)
152 await bridge.async_close_remote()
154 entry.async_on_unload(
155 hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_bridge)
157 entry.async_on_unload(stop_bridge)
165 entry.async_on_unload(debounced_reloader.async_shutdown)
166 entry.async_on_unload(entry.add_update_listener(debounced_reloader.async_call))
169 await coordinator.async_config_entry_first_refresh()
170 entry.runtime_data = coordinator
171 await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
177 hass: HomeAssistant, entry: ConfigEntry
178 ) -> SamsungTVBridge:
179 """Create a bridge object and update any missing data in the config entry."""
180 updated_data: dict[str, str | int] = {}
181 host: str = entry.data[CONF_HOST]
182 port: int |
None = entry.data.get(CONF_PORT)
183 method: str |
None = entry.data.get(CONF_METHOD)
184 load_info_attempted =
False
185 info: dict[str, Any] |
None =
None
187 if not port
or not method:
188 LOGGER.debug(
"Attempting to get port or method for %s", host)
189 if method == METHOD_LEGACY:
195 load_info_attempted =
True
196 if not port
or not method:
198 "Failed to determine connection method, make sure the device is on."
201 LOGGER.debug(
"Updated port to %s and method to %s for %s", port, method, host)
202 updated_data[CONF_PORT] = port
203 updated_data[CONF_METHOD] = method
207 mac: str |
None = entry.data.get(CONF_MAC)
208 model: str |
None = entry.data.get(CONF_MODEL)
209 mac_is_incorrectly_formatted = mac
and dr.format_mac(mac) != mac
211 not mac
or not model
or mac_is_incorrectly_formatted
212 )
and not load_info_attempted:
213 info = await bridge.async_device_info()
215 if not mac
or mac_is_incorrectly_formatted:
216 LOGGER.debug(
"Attempting to get mac for %s", host)
221 mac = await hass.async_add_executor_job(
222 partial(getmac.get_mac_address, ip=host)
225 if mac
and mac !=
"none":
228 LOGGER.debug(
"Updated mac to %s for %s", mac, host)
229 updated_data[CONF_MAC] = dr.format_mac(mac)
231 LOGGER.warning(
"Failed to get mac for %s", host)
234 LOGGER.debug(
"Attempting to get model for %s", host)
236 model = info.get(
"device", {}).
get(
"modelName")
238 LOGGER.debug(
"Updated model to %s for %s", model, host)
239 updated_data[CONF_MODEL] = model
244 "Detected model %s for %s. Some televisions from H and J series use "
245 "an encrypted protocol but you are using %s which may not be supported"
253 data = {**entry.data, **updated_data}
254 hass.config_entries.async_update_entry(entry, data=data)
260 """Unload a config entry."""
261 return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
265 """Migrate old entry."""
266 version = config_entry.version
267 minor_version = config_entry.minor_version
269 LOGGER.debug(
"Migrating from version %s.%s", version, minor_version)
273 dev_reg = dr.async_get(hass)
274 dev_reg.async_clear_config_entry(config_entry.entry_id)
276 en_reg = er.async_get(hass)
277 en_reg.async_clear_config_entry(config_entry.entry_id)
280 hass.config_entries.async_update_entry(config_entry, version=2)
283 if minor_version < 2:
288 hass.config_entries.async_update_entry(config_entry, minor_version=2)
290 LOGGER.debug(
"Migration to version %s.%s successful", version, minor_version)
None _async_reload_entry(self)
None async_shutdown(self)
None __init__(self, HomeAssistant hass, ConfigEntry entry)
None async_call(self, HomeAssistant hass, ConfigEntry entry)
web.Response get(self, web.Request request, str config_key)
tuple[str, int|None, str|None, dict[str, Any]|None] async_get_device_info(HomeAssistant hass, str host)
str|None mac_from_device_info(dict[str, Any] info)
bool model_requires_encryption(str|None model)
SamsungTVBridge _async_create_bridge_with_updated_data(HomeAssistant hass, ConfigEntry entry)
bool async_setup_entry(HomeAssistant hass, SamsungTVConfigEntry entry)
None _async_update_ssdp_locations(HomeAssistant hass, ConfigEntry entry)
bool async_migrate_entry(HomeAssistant hass, ConfigEntry config_entry)
SamsungTVBridge _async_get_device_bridge(HomeAssistant hass, dict[str, Any] data)
bool async_unload_entry(HomeAssistant hass, SamsungTVConfigEntry entry)