Skip to content
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

Add PlayStation Network Integration #133901

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from

Conversation

JackJPowell
Copy link

@JackJPowell JackJPowell commented Dec 23, 2024

Proposed change

This change adds support for integrating with the PlayStation Network. In this first release (my first HA PR), I have attempted to remove all unneeded code (no entity base class since I only have a single platform) but anticipate adding it, and refactoring elsewhere to follow home assistant conventions in subsequent pull requests. The integration contains a single platform (Media Player), is setup fully via the UI, supplies unique IDs, and attempts to follow all current Home Assistant best practices.

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New integration (thank you!)
  • New feature (which adds functionality to an existing integration)
  • Deprecation (breaking change to happen in the future)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

When reviewing this integration, please keep in mind that it represents a user on the playstation network and not the PlayStation console itself. It is structured around allowing the user to associate multiple PSN accounts and the device names are setup to match this. This integration started its life as a custom component and has about ~400 users. For a look at what this may evolve into, here is a link to the custom repository.

This initial release of the PlayStation Network Integration exposes a media player based on the user's registered systems with the playstation network. One will be created for any PS4 systems and another for any PS5 systems. However, due to api limitations, it is not possible to pull data for more than a single console at a time nor do I have dates for when a console was last active. Given these limitations I'd like to discuss what the Home Assistant team believes is the best course of action to take in this regard:

  • Only a single system is reported as active. If a user is active on more than one, the one with the oldest action will be misrepresented in home assistant (reported as Off).
  • The api does not provide date of last access information so the integration may be creating a media player for a PS4 that the user no longer owns.
  • Since this integration is a representation of the user's account on the PlayStation Network, and not a PlayStation console device, does it make sense to expose a single PlayStation Console Media Player that contains the state of whatever currently active console the API is reporting?

A quality_scale.yaml file is included and I did my best to conform to all bronze level items. Some are marked as done, like common-modules, that still have pending tasks (the creation of entity.py for instance) but was skipped for this initial release. If this is the wrong approach, please let me know.

The integration communicates with the PlayStation Network via a supplied NPSSO token that the config flow walks the user through obtaining (along with the documentation referenced in PR#36520.

This integration relies on the PSNAWP project which was reversed engineered from the PlayStation Mobile app and is not endorsed or supported by Sony.

This PR wouldn't have been possible without the amazing help and guidance from @tr4nt0r. He put in so much time and effort to ensure this first PR go smoothly.

The associated brand images were included for the custom integration and are still up to date. Brands PR

Checklist

  • The code change is tested and works locally.
  • Local tests pass. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.
  • I have followed the development checklist
  • I have followed the perfect PR recommendations
  • The code has been formatted using Ruff (ruff format homeassistant tests)
  • Tests have been added to verify that the new code works.

If user exposed functionality or configuration variables are added/changed:

If the code communicates with devices, web services, or third-party tools:

  • The manifest file has all fields filled out correctly.
    Updated and included derived files by running: python3 -m script.hassfest.
  • New or updated dependencies have been added to requirements_all.txt.
    Updated by running python3 -m script.gen_requirements_all.
  • For the updated dependencies - a link to the changelog, or at minimum a diff between library versions is added to the PR description.

To help with the load of incoming pull requests:

@joostlek
Copy link
Member

Do you get a separate unique id for every playstation?

@JackJPowell
Copy link
Author

Do you get a separate unique id for every playstation?

Yes and no. I can pull a list of registered devices and they do have a unique deviceId. However, the call to return the user state (What game are they playing, are they online), only returns the device type (PS4, PS5) which is not associated to any particular deviceId.

I also question the validity of the registered device data. I've had two PS4s over the years but I only have one entry in the response. I do have entries for my three PS3 systems though.

I meant to mention this in the original pull request, and I'll add it there now too, but I'm working with an undocumented and "unofficial" api. The psnawp project reverse engineered it from the android PlayStation Mobile app. The end result is that I'm left to infer some things rather than read it in the docs.

Comment on lines +74 to +95
self.entity_description = MediaPlayerEntityDescription(
key=platform,
translation_key="playstation",
device_class=MediaPlayerDeviceClass.RECEIVER,
name=self.coordinator.user.online_id,
has_entity_name=True,
)
self._attr_unique_id = (
f"{coordinator.config_entry.unique_id}_{self.entity_description.key}"
)
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self.coordinator.user.account_id)},
name=self.coordinator.user.online_id,
manufacturer="Sony Interactive Entertainment",
model="PlayStation Network",
)

@property
def name(self) -> str:
"""Name getter."""
return PLATFORM_MAP[self.entity_description.key]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self.entity_description = MediaPlayerEntityDescription(
key=platform,
translation_key="playstation",
device_class=MediaPlayerDeviceClass.RECEIVER,
name=self.coordinator.user.online_id,
has_entity_name=True,
)
self._attr_unique_id = (
f"{coordinator.config_entry.unique_id}_{self.entity_description.key}"
)
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self.coordinator.user.account_id)},
name=self.coordinator.user.online_id,
manufacturer="Sony Interactive Entertainment",
model="PlayStation Network",
)
@property
def name(self) -> str:
"""Name getter."""
return PLATFORM_MAP[self.entity_description.key]
self.entity_description = MediaPlayerEntityDescription(
key=platform,
translation_key="playstation",
device_class=MediaPlayerDeviceClass.RECEIVER,
name=None,
has_entity_name=True,
)
self._attr_unique_id = (
f"{coordinator.config_entry.unique_id}_{self.entity_description.key}"
)
self._attr_device_info = DeviceInfo(
via_device={(DOMAIN, coordinator.config_entry.unique_id)},
identifiers={(DOMAIN, self._attr_unique_id)},
name=PLATFORM_MAP[self.entity_description.key],
manufacturer="Sony Interactive Entertainment",
model=PLATFORM_MAP[self.entity_description.key],
)

IMHO each console type should be represented as individual device, and media player as the main entity, doesn't need a name, it can be set to None. via_device should added maybe later when more entities are added to the integration, this would link the devices to the psn account.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could be missing something, but doesn't this limit the user to adding a single account? When setup this way, each entity is named after only the device (PlayStation X) but what happens when I add my second PSN account? Now I have two devices that would be named the same. This is of course solvable, but it lead me to think it was the wrong approach. The device in this scenario is the user's PSN account, not the physical hardware. I think this makes sense since we are not able to directly talk to the console. We only get details that pertain to it via the playstation network. All the other sensors that the custom integration provides also aren't console dependent but are associated with the user account.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Home Assistant would name it media_player.playstation_5_2 , there won't be any conflicts.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, but isn't that "wrong"? In the UI, there is no affordance for the user to differentiate them. They are both named "PlayStation 5" and the entity ID only differs with an _2. Which one is associated with my primary account and which one with my alt account?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's quite usual behaviour, many integrations name entities by their device model, sometimes also manufacturer name prepended. Users can rename entities afterwards however they want.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants