1 """Support for PlayStation 4 consoles."""
3 from contextlib
import suppress
5 from typing
import Any, cast
7 from pyps4_2ndscreen.errors
import NotReady, PSDataIncomplete
8 from pyps4_2ndscreen.media_art
import TYPE_APP
as PS_TYPE_APP
9 import pyps4_2ndscreen.ps4
as pyps4
12 ATTR_MEDIA_CONTENT_TYPE,
15 MediaPlayerEntityFeature,
33 from .
import format_unique_id, load_games, save_games
39 REGIONS
as deprecated_regions,
42 _LOGGER = logging.getLogger(__name__)
50 config_entry: ConfigEntry,
51 async_add_entities: AddEntitiesCallback,
53 """Set up PS4 from a config entry."""
55 creds: str = config.data[CONF_TOKEN]
57 for device
in config.data[
"devices"]:
58 host: str = device[CONF_HOST]
59 region: str = device[CONF_REGION]
60 name: str = device[CONF_NAME]
61 ps4 = pyps4.Ps4Async(host, creds, device_name=DEFAULT_ALIAS)
62 device_list.append(
PS4Device(config, name, host, region, ps4, creds))
67 """Representation of a PS4."""
69 _attr_supported_features = (
70 MediaPlayerEntityFeature.TURN_OFF
71 | MediaPlayerEntityFeature.TURN_ON
72 | MediaPlayerEntityFeature.PAUSE
73 | MediaPlayerEntityFeature.STOP
74 | MediaPlayerEntityFeature.SELECT_SOURCE
76 _attr_translation_key =
"media_player"
87 """Initialize the ps4 device."""
95 self.
_games_games: JsonObjectType = {}
100 """Handle status callback. Parse status."""
106 """Notify protocol to callback with update changes."""
111 """Notify protocol to remove callback."""
112 self.
hasshass.data[PS4_DATA].protocol.remove_callback(
117 """Display logger msg if region is deprecated."""
119 if self.
_region_region
in deprecated_regions:
121 """Region: %s has been deprecated.
122 Please remove PS4 integration
123 and Re-configure again to utilize
129 """Subscribe PS4 events."""
130 self.
hasshass.data[PS4_DATA].devices.append(self)
134 """Retrieve the latest data."""
135 if self.
_ps4_ps4.ddp_protocol
is not None:
142 not self.
_ps4_ps4.connected
143 and not self.
_ps4_ps4.is_standby
144 and self.
_ps4_ps4.is_available
146 with suppress(NotReady):
147 await self.
_ps4_ps4.async_connect()
150 if self.
_ps4_ps4.ddp_protocol
is None:
152 await self.
hasshass.async_add_executor_job(self.
_ps4_ps4.get_status)
156 self.
_ps4_ps4.ddp_protocol = self.
hasshass.data[PS4_DATA].protocol
163 status: dict[str, Any] |
None = self.
_ps4_ps4.status
164 if status
is not None:
171 if status.get(
"status") ==
"Ok":
172 title_id = status.get(
"running-app-titleid")
173 name = status.get(
"running-app-name")
175 if title_id
and name
is not None:
181 _LOGGER.debug(
"Using saved data for media: %s", title_id)
188 self.
hasshass.async_create_background_task(
190 "ps4.media_player-get_title_data",
197 elif self.
_retry_retry > DEFAULT_RETRIES:
203 """Return True, Set media attrs if data is locked."""
208 if store.get(ATTR_LOCKED):
209 self.
_attr_media_title_attr_media_title = cast(str |
None, store.get(ATTR_MEDIA_TITLE))
211 self.
_media_image_media_image = cast(str |
None, store.get(ATTR_MEDIA_IMAGE_URL))
213 str |
None, store.get(ATTR_MEDIA_CONTENT_TYPE)
219 """Set states for state idle."""
221 self.
_attr_state_attr_state = MediaPlayerState.IDLE
224 """Set states for state standby."""
226 self.
_attr_state_attr_state = MediaPlayerState.STANDBY
229 """Set states for state unknown."""
233 _LOGGER.warning(
"PS4 could not be reached")
238 """Update if there is no title."""
245 """Get PS Store Data."""
251 title = await self.
_ps4_ps4.async_get_ps_store_data(
252 name, title_id, self.
_region_region
255 except PSDataIncomplete:
259 _LOGGER.error(
"PS Store Search Timed out")
262 if title
is not None:
263 app_name = title.name
264 art = title.cover_art
266 if title.game_type != PS_TYPE_APP:
267 media_type = MediaType.GAME
269 media_type = MediaType.APP
272 "Could not find data in region: %s for PS ID: %s",
283 await self.
hasshass.async_add_executor_job(self.
update_listupdate_list)
287 """Update Game List, Correct data if different."""
292 store.get(ATTR_MEDIA_TITLE) != self.
media_titlemedia_title
293 or store.get(ATTR_MEDIA_IMAGE_URL) != self.
_media_image_media_image
309 """Parse data entry and update source list."""
311 for data
in self.
_games_games.values():
312 data = cast(JsonObjectType, data)
313 games.append(cast(str, data[ATTR_MEDIA_TITLE]))
318 title_id: str |
None,
319 app_name: str |
None,
322 is_locked: bool =
False,
324 """Add games to list."""
326 if title_id
is not None and title_id
not in games:
327 game: JsonObjectType = {
329 ATTR_MEDIA_TITLE: app_name,
330 ATTR_MEDIA_IMAGE_URL: image,
331 ATTR_MEDIA_CONTENT_TYPE: g_type,
332 ATTR_LOCKED: is_locked,
339 """Set device info for registry."""
342 _LOGGER.debug(
"Assuming status from registry")
343 e_registry = er.async_get(self.
hasshass)
344 d_registry = dr.async_get(self.
hasshass)
346 for entry
in e_registry.entities.get_entries_for_config_entry_id(
352 for device
in d_registry.devices.get_devices_for_config_entry_id(
356 identifiers=device.identifiers,
357 manufacturer=device.manufacturer,
360 sw_version=device.sw_version,
365 _sw_version = status[
"system-version"]
366 _sw_version = _sw_version[1:4]
367 sw_version = f
"{_sw_version[0]}.{_sw_version[1:]}"
369 identifiers={(PS4_DOMAIN, status[
"host-id"])},
370 manufacturer=
"Sony Interactive Entertainment Inc.",
371 model=
"PlayStation 4",
372 name=status[
"host-name"],
373 sw_version=sw_version,
379 """Remove Entity from Home Assistant."""
381 if self.
_ps4_ps4.connected:
382 await self.
_ps4_ps4.close()
384 self.
hasshass.data[PS4_DATA].devices.remove(self)
388 """Return picture."""
395 f
"/api/media_player_proxy/{self.entity_id}?"
396 f
"token={self.access_token}&cache={image_hash}"
402 """Image url of current playing media."""
408 """Turn off media player."""
409 await self.
_ps4_ps4.standby()
412 """Turn on the media player."""
413 self.
_ps4_ps4.wakeup()
416 """Toggle media player."""
417 await self.
_ps4_ps4.toggle()
420 """Send keypress ps to return to menu."""
424 """Send keypress ps to return to menu."""
428 """Select input source."""
429 for title_id, data
in self.
_games_games.items():
430 data = cast(JsonObjectType, data)
431 game = cast(str, data[ATTR_MEDIA_TITLE])
433 source.lower().encode(encoding=
"utf-8")
434 == game.lower().encode(encoding=
"utf-8")
435 or source == title_id
438 "Starting PS4 game %s (%s) using source %s", game, title_id, source
444 _LOGGER.warning(
"Could not start title. '%s' is not in source list", source)
448 """Send Button Command."""
452 """Send RC command."""
453 await self.
_ps4_ps4.remote_control(command)
None async_write_ha_state(self)
None schedule_update_ha_state(self, bool force_refresh=False)
def get_status(hass, host, port)
JsonObjectType load_games(HomeAssistant hass, str unique_id)
def format_unique_id(creds, mac_address)
def save_games(HomeAssistant hass, dict games, str unique_id)