1 """Update coordinator for Bravia TV integration."""
3 from __future__
import annotations
5 from collections.abc
import Awaitable, Callable, Coroutine, Iterable
6 from datetime
import datetime, timedelta
7 from functools
import wraps
9 from types
import MappingProxyType
10 from typing
import Any, Concatenate, Final
12 from pybravia
import (
15 BraviaConnectionError,
16 BraviaConnectionTimeout,
38 _LOGGER = logging.getLogger(__name__)
43 def catch_braviatv_errors[_BraviaTVCoordinatorT: BraviaTVCoordinator, **_P](
44 func: Callable[Concatenate[_BraviaTVCoordinatorT, _P], Awaitable[
None]],
45 ) -> Callable[Concatenate[_BraviaTVCoordinatorT, _P], Coroutine[Any, Any,
None]]:
46 """Catch Bravia errors."""
50 self: _BraviaTVCoordinatorT,
54 """Catch Bravia errors and log message."""
56 await func(self, *args, **kwargs)
57 except BraviaError
as err:
58 _LOGGER.error(
"Command error: %s", err)
59 await self.async_request_refresh()
65 """Representation of a Bravia TV Coordinator."""
71 config: MappingProxyType[str, Any],
73 """Initialize Bravia TV Client."""
76 self.
pinpin = config[CONF_PIN]
77 self.
use_pskuse_psk = config.get(CONF_USE_PSK,
False)
78 self.
client_idclient_id = config.get(CONF_CLIENT_ID, LEGACY_CLIENT_ID)
79 self.
nicknamenickname = config.get(CONF_NICKNAME, NICKNAME_PREFIX)
80 self.
sourcesource: str |
None =
None
82 self.
source_mapsource_map: dict[str, dict] = {}
87 self.
media_urimedia_uri: str |
None =
None
102 update_interval=SCAN_INTERVAL,
104 hass, _LOGGER, cooldown=1.0, immediate=
False
111 source_type: SourceType,
112 add_to_list: bool =
False,
113 sort_by: str |
None =
None,
115 """Extend source map and source list."""
117 sources = sorted(sources, key=
lambda d: d.get(sort_by,
""))
119 title = item.get(
"title")
120 uri = item.get(
"uri")
121 if not title
or not uri:
123 self.
source_mapsource_map[uri] = {**item,
"type": source_type}
124 if add_to_list
and title
not in self.
source_listsource_list:
128 """Connect and fetch data."""
133 await self.
clientclient.connect(psk=self.
pinpin)
135 await self.
clientclient.connect(
141 except BraviaAuthError
as err:
142 raise ConfigEntryAuthFailed
from err
144 power_status = await self.
clientclient.get_power_status()
145 self.
is_onis_on = power_status ==
"active"
148 if self.
is_onis_on
is False:
155 except BraviaNotFound
as err:
159 _LOGGER.debug(
"Update skipped, Bravia API service is reloading")
161 raise UpdateFailed(
"Error communicating with device")
from err
162 except (BraviaConnectionError, BraviaConnectionTimeout, BraviaTurnedOff):
163 self.
is_onis_on =
False
165 _LOGGER.debug(
"Update skipped, Bravia TV is off")
166 except BraviaError
as err:
167 self.
is_onis_on =
False
169 raise UpdateFailed(
"Error communicating with device")
from err
172 """Update volume information."""
173 volume_info = await self.
clientclient.get_volume_info()
174 if (volume_level := volume_info.get(
"volume"))
is not None:
176 self.
volume_mutedvolume_muted = volume_info.get(
"mute",
False)
180 """Update current playing information."""
181 playing_info = await self.
clientclient.get_playing_info()
189 if start_datetime := playing_info.get(
"startDateTime"):
190 start_datetime = datetime.fromisoformat(start_datetime)
191 current_datetime = datetime.now().replace(tzinfo=start_datetime.tzinfo)
193 (current_datetime - start_datetime).total_seconds()
201 if self.
media_urimedia_uri[:8] ==
"extInput":
202 self.
sourcesource = playing_info.get(
"title")
215 """Update all sources."""
219 inputs = await self.
clientclient.get_external_status()
220 self.
_sources_extend_sources_extend(inputs, SourceType.INPUT, add_to_list=
True)
222 apps = await self.
clientclient.get_app_list()
223 self.
_sources_extend_sources_extend(apps, SourceType.APP, sort_by=
"title")
225 channels = await self.
clientclient.get_content_list_all(
"tv")
229 """Select source by uri."""
230 if source_type == SourceType.APP:
231 await self.
clientclient.set_active_app(uri)
233 await self.
clientclient.set_play_content(uri)
236 self, query: str, source_type: SourceType | str
238 """Find and select source by query."""
239 if query.startswith((
"extInput:",
"tv:",
"com.sony.dtv.")):
242 is_numeric_search = source_type == SourceType.CHANNEL
and query.isnumeric()
243 for uri, item
in self.
source_mapsource_map.items():
244 if item[
"type"] == source_type:
245 if is_numeric_search:
246 num = item.get(
"dispNum")
247 if num
and int(query) ==
int(num):
250 title: str = item[
"title"]
251 if query.lower() == title.lower():
253 if query.lower()
in title.lower():
257 raise ValueError(f
"Not found {source_type}: {query}")
259 @catch_braviatv_errors
261 """Turn the device on."""
262 await self.
clientclient.turn_on()
264 @catch_braviatv_errors
266 """Turn off device."""
267 await self.
clientclient.turn_off()
269 @catch_braviatv_errors
271 """Set volume level, range 0..1."""
274 @catch_braviatv_errors
276 """Send volume up command to device."""
277 await self.
clientclient.volume_up()
279 @catch_braviatv_errors
281 """Send volume down command to device."""
282 await self.
clientclient.volume_down()
284 @catch_braviatv_errors
286 """Send mute command to device."""
287 await self.
clientclient.volume_mute()
289 @catch_braviatv_errors
291 """Send play command to device."""
292 await self.
clientclient.play()
294 @catch_braviatv_errors
296 """Send pause command to device."""
297 await self.
clientclient.pause()
299 @catch_braviatv_errors
301 """Send stop command to device."""
302 await self.
clientclient.stop()
304 @catch_braviatv_errors
306 """Send next track command."""
308 await self.
clientclient.channel_up()
310 await self.
clientclient.next_track()
312 @catch_braviatv_errors
314 """Send previous track command."""
316 await self.
clientclient.channel_down()
318 await self.
clientclient.previous_track()
320 @catch_braviatv_errors
322 self, media_type: MediaType | str, media_id: str, **kwargs: Any
324 """Play a piece of media."""
325 if media_type
not in (MediaType.APP, MediaType.CHANNEL):
326 raise ValueError(f
"Invalid media type: {media_type}")
329 @catch_braviatv_errors
331 """Set the input source."""
334 @catch_braviatv_errors
336 """Send command to device."""
337 for _
in range(repeats):
339 response = await self.
clientclient.send_command(cmd)
341 commands = await self.
clientclient.get_command_list()
342 commands_keys =
", ".join(commands.keys())
346 "Unsupported command: %s, list of available commands: %s",
351 @catch_braviatv_errors
353 """Send command to reboot the device."""
354 await self.
clientclient.reboot()
356 @catch_braviatv_errors
358 """Send command to terminate all applications."""
359 await self.
clientclient.terminate_apps()
None async_play_media(self, MediaType|str media_type, str media_id, **Any kwargs)
media_position_updated_at
None async_media_stop(self)
None async_source_find(self, str query, SourceType|str source_type)
None async_volume_up(self)
None async_turn_off(self)
None async_terminate_apps(self)
None async_media_play(self)
None async_set_volume_level(self, float volume)
None async_update_sources(self)
None __init__(self, HomeAssistant hass, BraviaClient client, MappingProxyType[str, Any] config)
None async_update_volume(self)
None async_update_playing(self)
None async_send_command(self, Iterable[str] command, int repeats)
None async_media_pause(self)
None _async_update_data(self)
None async_source_start(self, str uri, SourceType|str source_type)
None async_media_previous_track(self)
None async_select_source(self, str source)
None _sources_extend(self, list[dict] sources, SourceType source_type, bool add_to_list=False, str|None sort_by=None)
None async_volume_mute(self, bool mute)
None async_reboot_device(self)
None async_volume_down(self)
None async_media_next_track(self)