-
-
Notifications
You must be signed in to change notification settings - Fork 36.2k
Add PlayStation Network Integration #133901
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
58 commits
Select commit
Hold shift + click to select a range
c5ee49e
clean pull request
JackJPowell 3676679
Create one device per console
JackJPowell ba0e8f8
Requested changes
JackJPowell 788aeac
Pr/tr4nt0r/1 (#2)
JackJPowell e7aab39
nitpicks
tr4nt0r d6387b6
Update config_flow test
JackJPowell 264bdef
Update quality_scale.yaml
JackJPowell d8674d4
repair integrations.json
JackJPowell e46ed99
minor updates
JackJPowell 15728ee
Add translation string for invalid account
JackJPowell 043dc40
Merge remote-tracking branch 'upstream/dev' into PlayStationNetwork
JackJPowell 0106018
misc changes post review
JackJPowell bd6e7b7
Merge remote-tracking branch 'upstream/dev' into PlayStationNetwork
JackJPowell cad55d5
Minor strings updates
JackJPowell 0adb849
Merge remote-tracking branch 'upstream/dev' into PlayStationNetwork
JackJPowell d1df8b3
strengthen config_flow test
JackJPowell a358725
Requested changes
JackJPowell d6d1e44
Applied patch to commit a358725
JackJPowell becb5a5
migrate PlayStationNetwork helper classes to HA
JackJPowell cb96503
Merge remote-tracking branch 'upstream/dev' into PlayStationNetwork
JackJPowell c2fa09f
Revert to standard psn library
JackJPowell 81b0ba4
Updates to media_player logic
JackJPowell 7b6dbb7
add default_factory, change registered_platforms to set
tr4nt0r f4ba3a0
Merge pull request #4 from tr4nt0r:PlayStationNetwork
JackJPowell d401ab0
Merge remote-tracking branch 'upstream/dev' into PlayStationNetwork
JackJPowell dcc008a
Improve test coverage
JackJPowell fedbe89
Add snapshot test for media_player platform
tr4nt0r 40fac57
fix token parse error
tr4nt0r 6879da6
Merge pull request #5 from tr4nt0r:psn_test_media_player
JackJPowell 585aef2
Parametrize media player test
tr4nt0r bf6f7ef
Add PS3 support
JackJPowell e872ef6
Add PS3 support
JackJPowell 33e0246
Add concurrent console support
JackJPowell 1cffe74
Adjust psnawp rate limit
JackJPowell 116bddd
Convert to package PlatformType
JackJPowell 53bdca8
Merge remote-tracking branch 'upstream/dev' into PlayStationNetwork
JackJPowell 001f083
Update dependency to PSNAWP==3.0.0
JackJPowell 1683026
small improvements
JackJPowell e82792b
Add PlayStation PC Support
JackJPowell 1b776cc
Refactor active sessions list
JackJPowell fdbc308
shift async logic to helper
JackJPowell 7442b09
Implemented suggested changes
JackJPowell 4c482ca
Merge remote-tracking branch 'upstream/dev' into PlayStationNetwork
JackJPowell d29c6a1
Merge pull request #6 from tr4nt0r/psn_parametrize_media_player_test
JackJPowell fa72287
Suggested changes
JackJPowell b578d73
Merge remote-tracking branch 'upstream/dev' into PlayStationNetwork
JackJPowell e2d2d9b
Updated tests
JackJPowell aff17f9
Suggested changes
JackJPowell 4a2ccd2
Merge remote-tracking branch 'upstream/dev' into PlayStationNetwork
JackJPowell e7c165f
Fix test
JackJPowell 49ca5d5
Merge remote-tracking branch 'upstream/dev' into PlayStationNetwork
JackJPowell c5dc305
Merge remote-tracking branch 'upstream/dev' into PlayStationNetwork
JackJPowell 0d692b1
Suggested changes
JackJPowell a0a478c
Suggested changes
JackJPowell 5874f01
Merge remote-tracking branch 'upstream/dev' into PlayStationNetwork
JackJPowell 5c90091
Update config_flow tests
JackJPowell 42214cf
Group remaining api call in single executor
JackJPowell 35e4e71
Merge branch 'dev' into PlayStationNetwork
joostlek File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,11 @@ | ||
| { | ||
| "domain": "sony", | ||
| "name": "Sony", | ||
| "integrations": ["braviatv", "ps4", "sony_projector", "songpal"] | ||
| "integrations": [ | ||
| "braviatv", | ||
| "ps4", | ||
| "sony_projector", | ||
| "songpal", | ||
| "playstation_network" | ||
| ] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| """The PlayStation Network integration.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from homeassistant.const import Platform | ||
| from homeassistant.core import HomeAssistant | ||
|
|
||
| from .const import CONF_NPSSO | ||
| from .coordinator import PlaystationNetworkConfigEntry, PlaystationNetworkCoordinator | ||
| from .helpers import PlaystationNetwork | ||
|
|
||
| PLATFORMS: list[Platform] = [Platform.MEDIA_PLAYER] | ||
|
|
||
|
|
||
| async def async_setup_entry( | ||
| hass: HomeAssistant, entry: PlaystationNetworkConfigEntry | ||
| ) -> bool: | ||
| """Set up Playstation Network from a config entry.""" | ||
|
|
||
| psn = PlaystationNetwork(hass, entry.data[CONF_NPSSO]) | ||
|
|
||
| coordinator = PlaystationNetworkCoordinator(hass, psn, entry) | ||
| await coordinator.async_config_entry_first_refresh() | ||
| entry.runtime_data = coordinator | ||
|
|
||
| await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) | ||
| return True | ||
|
|
||
|
|
||
| async def async_unload_entry( | ||
| hass: HomeAssistant, entry: PlaystationNetworkConfigEntry | ||
| ) -> bool: | ||
| """Unload a config entry.""" | ||
| return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) |
70 changes: 70 additions & 0 deletions
70
homeassistant/components/playstation_network/config_flow.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| """Config flow for the PlayStation Network integration.""" | ||
|
|
||
| import logging | ||
| from typing import Any | ||
|
|
||
| from psnawp_api.core.psnawp_exceptions import ( | ||
| PSNAWPAuthenticationError, | ||
| PSNAWPError, | ||
| PSNAWPInvalidTokenError, | ||
| PSNAWPNotFoundError, | ||
| ) | ||
| from psnawp_api.models.user import User | ||
| from psnawp_api.utils.misc import parse_npsso_token | ||
| import voluptuous as vol | ||
|
|
||
| from homeassistant.config_entries import ConfigFlow, ConfigFlowResult | ||
|
|
||
| from .const import CONF_NPSSO, DOMAIN | ||
| from .helpers import PlaystationNetwork | ||
|
|
||
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
| STEP_USER_DATA_SCHEMA = vol.Schema({vol.Required(CONF_NPSSO): str}) | ||
|
|
||
|
|
||
| class PlaystationNetworkConfigFlow(ConfigFlow, domain=DOMAIN): | ||
| """Handle a config flow for Playstation Network.""" | ||
|
|
||
| async def async_step_user( | ||
| self, user_input: dict[str, Any] | None = None | ||
| ) -> ConfigFlowResult: | ||
| """Handle the initial step.""" | ||
| errors: dict[str, str] = {} | ||
| npsso: str | None = None | ||
| if user_input is not None: | ||
| try: | ||
| npsso = parse_npsso_token(user_input[CONF_NPSSO]) | ||
| except PSNAWPInvalidTokenError: | ||
| errors["base"] = "invalid_account" | ||
|
|
||
| if npsso: | ||
| psn = PlaystationNetwork(self.hass, npsso) | ||
| try: | ||
| user: User = await psn.get_user() | ||
| except PSNAWPAuthenticationError: | ||
| errors["base"] = "invalid_auth" | ||
| except PSNAWPNotFoundError: | ||
| errors["base"] = "invalid_account" | ||
| except PSNAWPError: | ||
| errors["base"] = "cannot_connect" | ||
| except Exception: | ||
JackJPowell marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| _LOGGER.exception("Unexpected exception") | ||
| errors["base"] = "unknown" | ||
| else: | ||
| await self.async_set_unique_id(user.account_id) | ||
| self._abort_if_unique_id_configured() | ||
| return self.async_create_entry( | ||
| title=user.online_id, | ||
| data={CONF_NPSSO: npsso}, | ||
| ) | ||
|
|
||
| return self.async_show_form( | ||
| step_id="user", | ||
| data_schema=STEP_USER_DATA_SCHEMA, | ||
| errors=errors, | ||
| description_placeholders={ | ||
| "npsso_link": "https://ca.account.sony.com/api/v1/ssocookie", | ||
| "psn_link": "https://playstation.com", | ||
| }, | ||
| ) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| """Constants for the Playstation Network integration.""" | ||
|
|
||
| from typing import Final | ||
|
|
||
| from psnawp_api.models.trophies import PlatformType | ||
|
|
||
| DOMAIN = "playstation_network" | ||
| CONF_NPSSO: Final = "npsso" | ||
|
|
||
| SUPPORTED_PLATFORMS = { | ||
| PlatformType.PS5, | ||
| PlatformType.PS4, | ||
| PlatformType.PS3, | ||
| PlatformType.PSPC, | ||
| } |
69 changes: 69 additions & 0 deletions
69
homeassistant/components/playstation_network/coordinator.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| """Coordinator for the PlayStation Network Integration.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from datetime import timedelta | ||
| import logging | ||
|
|
||
| from psnawp_api.core.psnawp_exceptions import ( | ||
| PSNAWPAuthenticationError, | ||
| PSNAWPServerError, | ||
| ) | ||
| from psnawp_api.models.user import User | ||
|
|
||
| from homeassistant.config_entries import ConfigEntry | ||
| from homeassistant.core import HomeAssistant | ||
| from homeassistant.exceptions import ConfigEntryNotReady | ||
| from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed | ||
|
|
||
| from .const import DOMAIN | ||
| from .helpers import PlaystationNetwork, PlaystationNetworkData | ||
|
|
||
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
| type PlaystationNetworkConfigEntry = ConfigEntry[PlaystationNetworkCoordinator] | ||
|
|
||
|
|
||
| class PlaystationNetworkCoordinator(DataUpdateCoordinator[PlaystationNetworkData]): | ||
| """Data update coordinator for PSN.""" | ||
|
|
||
| config_entry: PlaystationNetworkConfigEntry | ||
| user: User | ||
|
|
||
| def __init__( | ||
| self, | ||
| hass: HomeAssistant, | ||
| psn: PlaystationNetwork, | ||
| config_entry: PlaystationNetworkConfigEntry, | ||
| ) -> None: | ||
| """Initialize the Coordinator.""" | ||
| super().__init__( | ||
| hass, | ||
| name=DOMAIN, | ||
| logger=_LOGGER, | ||
| config_entry=config_entry, | ||
| update_interval=timedelta(seconds=30), | ||
| ) | ||
|
|
||
| self.psn = psn | ||
|
|
||
| async def _async_setup(self) -> None: | ||
JackJPowell marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| """Set up the coordinator.""" | ||
|
|
||
| try: | ||
| self.user = await self.psn.get_user() | ||
JackJPowell marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| except PSNAWPAuthenticationError as error: | ||
| raise ConfigEntryNotReady( | ||
| translation_domain=DOMAIN, | ||
| translation_key="not_ready", | ||
| ) from error | ||
|
|
||
| async def _async_update_data(self) -> PlaystationNetworkData: | ||
| """Get the latest data from the PSN.""" | ||
| try: | ||
| return await self.psn.get_data() | ||
| except (PSNAWPAuthenticationError, PSNAWPServerError) as error: | ||
| raise UpdateFailed( | ||
| translation_domain=DOMAIN, | ||
| translation_key="update_failed", | ||
| ) from error | ||
151 changes: 151 additions & 0 deletions
151
homeassistant/components/playstation_network/helpers.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,151 @@ | ||
| """Helper methods for common PlayStation Network integration operations.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from dataclasses import dataclass, field | ||
| from functools import partial | ||
| from typing import Any | ||
|
|
||
| from psnawp_api import PSNAWP | ||
| from psnawp_api.core.psnawp_exceptions import PSNAWPNotFoundError | ||
| from psnawp_api.models.client import Client | ||
| from psnawp_api.models.trophies import PlatformType | ||
| from psnawp_api.models.user import User | ||
| from pyrate_limiter import Duration, Rate | ||
|
|
||
| from homeassistant.core import HomeAssistant | ||
|
|
||
| from .const import SUPPORTED_PLATFORMS | ||
|
|
||
| LEGACY_PLATFORMS = {PlatformType.PS3, PlatformType.PS4} | ||
|
|
||
|
|
||
| @dataclass | ||
| class SessionData: | ||
| """Dataclass representing console session data.""" | ||
|
|
||
| platform: PlatformType = PlatformType.UNKNOWN | ||
| title_id: str | None = None | ||
| title_name: str | None = None | ||
| format: PlatformType | None = None | ||
| media_image_url: str | None = None | ||
| status: str = "" | ||
|
|
||
|
|
||
| @dataclass | ||
| class PlaystationNetworkData: | ||
| """Dataclass representing data retrieved from the Playstation Network api.""" | ||
|
|
||
| presence: dict[str, Any] = field(default_factory=dict) | ||
| username: str = "" | ||
| account_id: str = "" | ||
| available: bool = False | ||
| active_sessions: dict[PlatformType, SessionData] = field(default_factory=dict) | ||
| registered_platforms: set[PlatformType] = field(default_factory=set) | ||
|
|
||
|
|
||
| class PlaystationNetwork: | ||
| """Helper Class to return playstation network data in an easy to use structure.""" | ||
|
|
||
| def __init__(self, hass: HomeAssistant, npsso: str) -> None: | ||
| """Initialize the class with the npsso token.""" | ||
| rate = Rate(300, Duration.MINUTE * 15) | ||
JackJPowell marked this conversation as resolved.
Show resolved
Hide resolved
JackJPowell marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| self.psn = PSNAWP(npsso, rate_limit=rate) | ||
| self.client: Client | None = None | ||
| self.hass = hass | ||
| self.user: User | ||
| self.legacy_profile: dict[str, Any] | None = None | ||
|
|
||
| async def get_user(self) -> User: | ||
| """Get the user object from the PlayStation Network.""" | ||
| self.user = await self.hass.async_add_executor_job( | ||
| partial(self.psn.user, online_id="me") | ||
| ) | ||
| return self.user | ||
|
|
||
| def retrieve_psn_data(self) -> PlaystationNetworkData: | ||
| """Bundle api calls to retrieve data from the PlayStation Network.""" | ||
| data = PlaystationNetworkData() | ||
|
|
||
| if not self.client: | ||
| self.client = self.psn.me() | ||
|
|
||
| data.registered_platforms = { | ||
| PlatformType(device["deviceType"]) | ||
| for device in self.client.get_account_devices() | ||
| } & SUPPORTED_PLATFORMS | ||
|
|
||
| data.presence = self.user.get_presence() | ||
|
|
||
| # check legacy platforms if owned | ||
| if LEGACY_PLATFORMS & data.registered_platforms: | ||
| self.legacy_profile = self.client.get_profile_legacy() | ||
| return data | ||
|
|
||
| async def get_data(self) -> PlaystationNetworkData: | ||
| """Get title data from the PlayStation Network.""" | ||
| data = await self.hass.async_add_executor_job(self.retrieve_psn_data) | ||
| data.username = self.user.online_id | ||
| data.account_id = self.user.account_id | ||
|
|
||
| data.available = ( | ||
| data.presence.get("basicPresence", {}).get("availability") | ||
| == "availableToPlay" | ||
| ) | ||
|
|
||
| session = SessionData() | ||
| session.platform = PlatformType( | ||
| data.presence["basicPresence"]["primaryPlatformInfo"]["platform"] | ||
| ) | ||
|
|
||
| if session.platform in SUPPORTED_PLATFORMS: | ||
| session.status = data.presence.get("basicPresence", {}).get( | ||
| "primaryPlatformInfo" | ||
| )["onlineStatus"] | ||
|
|
||
| game_title_info = data.presence.get("basicPresence", {}).get( | ||
| "gameTitleInfoList" | ||
| ) | ||
|
|
||
| if game_title_info: | ||
| session.title_id = game_title_info[0]["npTitleId"] | ||
| session.title_name = game_title_info[0]["titleName"] | ||
| session.format = PlatformType(game_title_info[0]["format"]) | ||
| if session.format in {PlatformType.PS5, PlatformType.PSPC}: | ||
| session.media_image_url = game_title_info[0]["conceptIconUrl"] | ||
| else: | ||
| session.media_image_url = game_title_info[0]["npTitleIconUrl"] | ||
|
|
||
| data.active_sessions[session.platform] = session | ||
|
|
||
| if self.legacy_profile: | ||
| presence = self.legacy_profile["profile"].get("presences", []) | ||
| game_title_info = presence[0] if presence else {} | ||
| session = SessionData() | ||
|
|
||
| # If primary console isn't online, check legacy platforms for status | ||
| if not data.available: | ||
| data.available = game_title_info["onlineStatus"] == "online" | ||
|
|
||
| if "npTitleId" in game_title_info: | ||
| session.title_id = game_title_info["npTitleId"] | ||
| session.title_name = game_title_info["titleName"] | ||
| session.format = game_title_info["platform"] | ||
| session.platform = game_title_info["platform"] | ||
| session.status = game_title_info["onlineStatus"] | ||
| if PlatformType(session.format) is PlatformType.PS4: | ||
| session.media_image_url = game_title_info["npTitleIconUrl"] | ||
| elif PlatformType(session.format) is PlatformType.PS3: | ||
| try: | ||
| title = self.psn.game_title( | ||
| session.title_id, platform=PlatformType.PS3, account_id="me" | ||
| ) | ||
| except PSNAWPNotFoundError: | ||
| session.media_image_url = None | ||
|
|
||
| if title: | ||
| session.media_image_url = title.get_title_icon_url() | ||
|
|
||
| if game_title_info["onlineStatus"] == "online": | ||
| data.active_sessions[session.platform] = session | ||
| return data | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| { | ||
| "entity": { | ||
| "media_player": { | ||
| "playstation": { | ||
| "default": "mdi:sony-playstation" | ||
| } | ||
| } | ||
| } | ||
| } |
11 changes: 11 additions & 0 deletions
11
homeassistant/components/playstation_network/manifest.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| { | ||
JackJPowell marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| "domain": "playstation_network", | ||
| "name": "PlayStation Network", | ||
| "codeowners": ["@jackjpowell"], | ||
| "config_flow": true, | ||
| "documentation": "https://www.home-assistant.io/integrations/playstation_network", | ||
| "integration_type": "service", | ||
| "iot_class": "cloud_polling", | ||
| "quality_scale": "bronze", | ||
| "requirements": ["PSNAWP==3.0.0", "pyrate-limiter==3.7.0"] | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.