Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
c5ee49e
clean pull request
JackJPowell Dec 23, 2024
3676679
Create one device per console
JackJPowell Jan 12, 2025
ba0e8f8
Requested changes
JackJPowell Jan 18, 2025
788aeac
Pr/tr4nt0r/1 (#2)
JackJPowell Jan 18, 2025
e7aab39
nitpicks
tr4nt0r Jan 18, 2025
d6387b6
Update config_flow test
JackJPowell Jan 18, 2025
264bdef
Update quality_scale.yaml
JackJPowell Jan 19, 2025
d8674d4
repair integrations.json
JackJPowell Jan 19, 2025
e46ed99
minor updates
JackJPowell Mar 14, 2025
15728ee
Add translation string for invalid account
JackJPowell Mar 21, 2025
043dc40
Merge remote-tracking branch 'upstream/dev' into PlayStationNetwork
JackJPowell Mar 28, 2025
0106018
misc changes post review
JackJPowell Mar 28, 2025
bd6e7b7
Merge remote-tracking branch 'upstream/dev' into PlayStationNetwork
JackJPowell Mar 28, 2025
cad55d5
Minor strings updates
JackJPowell Mar 28, 2025
0adb849
Merge remote-tracking branch 'upstream/dev' into PlayStationNetwork
JackJPowell Apr 20, 2025
d1df8b3
strengthen config_flow test
JackJPowell Apr 20, 2025
a358725
Requested changes
JackJPowell Apr 29, 2025
d6d1e44
Applied patch to commit a358725
JackJPowell Apr 30, 2025
becb5a5
migrate PlayStationNetwork helper classes to HA
JackJPowell May 1, 2025
cb96503
Merge remote-tracking branch 'upstream/dev' into PlayStationNetwork
JackJPowell May 2, 2025
c2fa09f
Revert to standard psn library
JackJPowell May 2, 2025
81b0ba4
Updates to media_player logic
JackJPowell May 2, 2025
7b6dbb7
add default_factory, change registered_platforms to set
tr4nt0r May 2, 2025
f4ba3a0
Merge pull request #4 from tr4nt0r:PlayStationNetwork
JackJPowell May 2, 2025
d401ab0
Merge remote-tracking branch 'upstream/dev' into PlayStationNetwork
JackJPowell May 2, 2025
dcc008a
Improve test coverage
JackJPowell May 2, 2025
fedbe89
Add snapshot test for media_player platform
tr4nt0r May 2, 2025
40fac57
fix token parse error
tr4nt0r May 2, 2025
6879da6
Merge pull request #5 from tr4nt0r:psn_test_media_player
JackJPowell May 2, 2025
585aef2
Parametrize media player test
tr4nt0r May 2, 2025
bf6f7ef
Add PS3 support
JackJPowell May 3, 2025
e872ef6
Add PS3 support
JackJPowell May 3, 2025
33e0246
Add concurrent console support
JackJPowell May 12, 2025
1cffe74
Adjust psnawp rate limit
JackJPowell May 15, 2025
116bddd
Convert to package PlatformType
JackJPowell May 22, 2025
53bdca8
Merge remote-tracking branch 'upstream/dev' into PlayStationNetwork
JackJPowell May 24, 2025
001f083
Update dependency to PSNAWP==3.0.0
JackJPowell May 24, 2025
1683026
small improvements
JackJPowell May 24, 2025
e82792b
Add PlayStation PC Support
JackJPowell May 26, 2025
1b776cc
Refactor active sessions list
JackJPowell May 26, 2025
fdbc308
shift async logic to helper
JackJPowell May 26, 2025
7442b09
Implemented suggested changes
JackJPowell May 28, 2025
4c482ca
Merge remote-tracking branch 'upstream/dev' into PlayStationNetwork
JackJPowell May 28, 2025
d29c6a1
Merge pull request #6 from tr4nt0r/psn_parametrize_media_player_test
JackJPowell May 30, 2025
fa72287
Suggested changes
JackJPowell May 31, 2025
b578d73
Merge remote-tracking branch 'upstream/dev' into PlayStationNetwork
JackJPowell May 31, 2025
e2d2d9b
Updated tests
JackJPowell May 31, 2025
aff17f9
Suggested changes
JackJPowell Jun 2, 2025
4a2ccd2
Merge remote-tracking branch 'upstream/dev' into PlayStationNetwork
JackJPowell Jun 3, 2025
e7c165f
Fix test
JackJPowell Jun 3, 2025
49ca5d5
Merge remote-tracking branch 'upstream/dev' into PlayStationNetwork
JackJPowell Jun 7, 2025
c5dc305
Merge remote-tracking branch 'upstream/dev' into PlayStationNetwork
JackJPowell Jun 8, 2025
0d692b1
Suggested changes
JackJPowell Jun 8, 2025
a0a478c
Suggested changes
JackJPowell Jun 8, 2025
5874f01
Merge remote-tracking branch 'upstream/dev' into PlayStationNetwork
JackJPowell Jun 13, 2025
5c90091
Update config_flow tests
JackJPowell Jun 13, 2025
42214cf
Group remaining api call in single executor
JackJPowell Jun 13, 2025
35e4e71
Merge branch 'dev' into PlayStationNetwork
joostlek Jun 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CODEOWNERS

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion homeassistant/brands/sony.json
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"
]
}
34 changes: 34 additions & 0 deletions homeassistant/components/playstation_network/__init__.py
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 homeassistant/components/playstation_network/config_flow.py
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:
_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",
},
)
15 changes: 15 additions & 0 deletions homeassistant/components/playstation_network/const.py
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 homeassistant/components/playstation_network/coordinator.py
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:
"""Set up the coordinator."""

try:
self.user = await self.psn.get_user()
except PSNAWPAuthenticationError as error:
raise ConfigEntryNotReady(

Check warning on line 56 in homeassistant/components/playstation_network/coordinator.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/playstation_network/coordinator.py#L55-L56

Added lines #L55 - L56 were not covered by tests
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(

Check warning on line 66 in homeassistant/components/playstation_network/coordinator.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/playstation_network/coordinator.py#L65-L66

Added lines #L65 - L66 were not covered by tests
translation_domain=DOMAIN,
translation_key="update_failed",
) from error
151 changes: 151 additions & 0 deletions homeassistant/components/playstation_network/helpers.py
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)
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()

Check warning on line 82 in homeassistant/components/playstation_network/helpers.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/playstation_network/helpers.py#L82

Added line #L82 was not covered by tests
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()

Check warning on line 124 in homeassistant/components/playstation_network/helpers.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/playstation_network/helpers.py#L122-L124

Added lines #L122 - L124 were not covered by tests

# If primary console isn't online, check legacy platforms for status
if not data.available:
data.available = game_title_info["onlineStatus"] == "online"

Check warning on line 128 in homeassistant/components/playstation_network/helpers.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/playstation_network/helpers.py#L127-L128

Added lines #L127 - L128 were not covered by tests

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(

Check warning on line 140 in homeassistant/components/playstation_network/helpers.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/playstation_network/helpers.py#L130-L140

Added lines #L130 - L140 were not covered by tests
session.title_id, platform=PlatformType.PS3, account_id="me"
)
except PSNAWPNotFoundError:
session.media_image_url = None

Check warning on line 144 in homeassistant/components/playstation_network/helpers.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/playstation_network/helpers.py#L143-L144

Added lines #L143 - L144 were not covered by tests

if title:
session.media_image_url = title.get_title_icon_url()

Check warning on line 147 in homeassistant/components/playstation_network/helpers.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/playstation_network/helpers.py#L146-L147

Added lines #L146 - L147 were not covered by tests

if game_title_info["onlineStatus"] == "online":
data.active_sessions[session.platform] = session

Check warning on line 150 in homeassistant/components/playstation_network/helpers.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/playstation_network/helpers.py#L149-L150

Added lines #L149 - L150 were not covered by tests
return data
9 changes: 9 additions & 0 deletions homeassistant/components/playstation_network/icons.json
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 homeassistant/components/playstation_network/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"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"]
}
Loading