Add sensor platform to PlayStation Network (#147469)

This commit is contained in:
Manu 2025-06-25 14:33:02 +02:00 committed by GitHub
parent 12812049ea
commit c447729ce4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 645 additions and 3 deletions

View File

@ -9,7 +9,7 @@ from .const import CONF_NPSSO
from .coordinator import PlaystationNetworkConfigEntry, PlaystationNetworkCoordinator
from .helpers import PlaystationNetwork
PLATFORMS: list[Platform] = [Platform.MEDIA_PLAYER]
PLATFORMS: list[Platform] = [Platform.MEDIA_PLAYER, Platform.SENSOR]
async def async_setup_entry(

View File

@ -8,7 +8,7 @@ from typing import Any
from psnawp_api import PSNAWP
from psnawp_api.models.client import Client
from psnawp_api.models.trophies import PlatformType
from psnawp_api.models.trophies import PlatformType, TrophySummary
from psnawp_api.models.user import User
from pyrate_limiter import Duration, Rate
@ -41,6 +41,8 @@ class PlaystationNetworkData:
available: bool = False
active_sessions: dict[PlatformType, SessionData] = field(default_factory=dict)
registered_platforms: set[PlatformType] = field(default_factory=set)
trophy_summary: TrophySummary | None = None
profile: dict[str, Any] = field(default_factory=dict)
class PlaystationNetwork:
@ -76,6 +78,9 @@ class PlaystationNetwork:
data.presence = self.user.get_presence()
data.trophy_summary = self.client.trophy_summary()
data.profile = self.user.profile()
# check legacy platforms if owned
if LEGACY_PLATFORMS & data.registered_platforms:
self.legacy_profile = self.client.get_profile_legacy()

View File

@ -4,6 +4,29 @@
"playstation": {
"default": "mdi:sony-playstation"
}
},
"sensor": {
"trophy_level": {
"default": "mdi:trophy-award"
},
"trophy_level_progress": {
"default": "mdi:trending-up"
},
"earned_trophies_platinum": {
"default": "mdi:trophy"
},
"earned_trophies_gold": {
"default": "mdi:trophy-variant"
},
"earned_trophies_silver": {
"default": "mdi:trophy-variant"
},
"earned_trophies_bronze": {
"default": "mdi:trophy-variant"
},
"online_id": {
"default": "mdi:account"
}
}
}
}

View File

@ -1,6 +1,7 @@
"""Media player entity for the PlayStation Network Integration."""
import logging
from typing import TYPE_CHECKING
from psnawp_api.models.trophies import PlatformType
@ -89,7 +90,8 @@ class PsnMediaPlayerEntity(
) -> None:
"""Initialize PSN MediaPlayer."""
super().__init__(coordinator)
if TYPE_CHECKING:
assert coordinator.config_entry.unique_id
self._attr_unique_id = f"{coordinator.config_entry.unique_id}_{platform.value}"
self.key = platform
self._attr_device_info = DeviceInfo(
@ -97,6 +99,7 @@ class PsnMediaPlayerEntity(
name=PLATFORM_MAP[platform],
manufacturer="Sony Interactive Entertainment",
model=PLATFORM_MAP[platform],
via_device=(DOMAIN, coordinator.config_entry.unique_id),
)
@property

View File

@ -0,0 +1,168 @@
"""Sensor platform for PlayStation Network integration."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from enum import StrEnum
from typing import TYPE_CHECKING
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
from homeassistant.const import PERCENTAGE
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .coordinator import (
PlaystationNetworkConfigEntry,
PlaystationNetworkCoordinator,
PlaystationNetworkData,
)
PARALLEL_UPDATES = 0
@dataclass(kw_only=True, frozen=True)
class PlaystationNetworkSensorEntityDescription(SensorEntityDescription):
"""PlayStation Network sensor description."""
value_fn: Callable[[PlaystationNetworkData], StateType]
entity_picture: str | None = None
class PlaystationNetworkSensor(StrEnum):
"""PlayStation Network sensors."""
TROPHY_LEVEL = "trophy_level"
TROPHY_LEVEL_PROGRESS = "trophy_level_progress"
EARNED_TROPHIES_PLATINUM = "earned_trophies_platinum"
EARNED_TROPHIES_GOLD = "earned_trophies_gold"
EARNED_TROPHIES_SILVER = "earned_trophies_silver"
EARNED_TROPHIES_BRONZE = "earned_trophies_bronze"
ONLINE_ID = "online_id"
SENSOR_DESCRIPTIONS: tuple[PlaystationNetworkSensorEntityDescription, ...] = (
PlaystationNetworkSensorEntityDescription(
key=PlaystationNetworkSensor.TROPHY_LEVEL,
translation_key=PlaystationNetworkSensor.TROPHY_LEVEL,
value_fn=(
lambda psn: psn.trophy_summary.trophy_level if psn.trophy_summary else None
),
),
PlaystationNetworkSensorEntityDescription(
key=PlaystationNetworkSensor.TROPHY_LEVEL_PROGRESS,
translation_key=PlaystationNetworkSensor.TROPHY_LEVEL_PROGRESS,
value_fn=(
lambda psn: psn.trophy_summary.progress if psn.trophy_summary else None
),
native_unit_of_measurement=PERCENTAGE,
),
PlaystationNetworkSensorEntityDescription(
key=PlaystationNetworkSensor.EARNED_TROPHIES_PLATINUM,
translation_key=PlaystationNetworkSensor.EARNED_TROPHIES_PLATINUM,
value_fn=(
lambda psn: psn.trophy_summary.earned_trophies.platinum
if psn.trophy_summary
else None
),
),
PlaystationNetworkSensorEntityDescription(
key=PlaystationNetworkSensor.EARNED_TROPHIES_GOLD,
translation_key=PlaystationNetworkSensor.EARNED_TROPHIES_GOLD,
value_fn=(
lambda psn: psn.trophy_summary.earned_trophies.gold
if psn.trophy_summary
else None
),
),
PlaystationNetworkSensorEntityDescription(
key=PlaystationNetworkSensor.EARNED_TROPHIES_SILVER,
translation_key=PlaystationNetworkSensor.EARNED_TROPHIES_SILVER,
value_fn=(
lambda psn: psn.trophy_summary.earned_trophies.silver
if psn.trophy_summary
else None
),
),
PlaystationNetworkSensorEntityDescription(
key=PlaystationNetworkSensor.EARNED_TROPHIES_BRONZE,
translation_key=PlaystationNetworkSensor.EARNED_TROPHIES_BRONZE,
value_fn=(
lambda psn: psn.trophy_summary.earned_trophies.bronze
if psn.trophy_summary
else None
),
),
PlaystationNetworkSensorEntityDescription(
key=PlaystationNetworkSensor.ONLINE_ID,
translation_key=PlaystationNetworkSensor.ONLINE_ID,
value_fn=lambda psn: psn.username,
),
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: PlaystationNetworkConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the sensor platform."""
coordinator = config_entry.runtime_data
async_add_entities(
PlaystationNetworkSensorEntity(coordinator, description)
for description in SENSOR_DESCRIPTIONS
)
class PlaystationNetworkSensorEntity(
CoordinatorEntity[PlaystationNetworkCoordinator], SensorEntity
):
"""Representation of a PlayStation Network sensor entity."""
entity_description: PlaystationNetworkSensorEntityDescription
coordinator: PlaystationNetworkCoordinator
_attr_has_entity_name = True
def __init__(
self,
coordinator: PlaystationNetworkCoordinator,
description: PlaystationNetworkSensorEntityDescription,
) -> None:
"""Initialize a sensor entity."""
super().__init__(coordinator)
self.entity_description = description
if TYPE_CHECKING:
assert coordinator.config_entry.unique_id
self._attr_unique_id = f"{coordinator.config_entry.unique_id}_{description.key}"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, coordinator.config_entry.unique_id)},
name=coordinator.data.username,
entry_type=DeviceEntryType.SERVICE,
manufacturer="Sony Interactive Entertainment",
)
@property
def native_value(self) -> StateType:
"""Return the state of the sensor."""
return self.entity_description.value_fn(self.coordinator.data)
@property
def entity_picture(self) -> str | None:
"""Return the entity picture to use in the frontend, if any."""
if self.entity_description.key is PlaystationNetworkSensor.ONLINE_ID and (
profile_pictures := self.coordinator.data.profile["personalDetail"].get(
"profilePictures"
)
):
return next(
(pic.get("url") for pic in profile_pictures if pic.get("size") == "xl"),
None,
)
return super().entity_picture

View File

@ -40,5 +40,34 @@
"update_failed": {
"message": "Data retrieval failed when trying to access the PlayStation Network."
}
},
"entity": {
"sensor": {
"trophy_level": {
"name": "Trophy level"
},
"trophy_level_progress": {
"name": "Next level"
},
"earned_trophies_platinum": {
"name": "Platinum trophies",
"unit_of_measurement": "trophies"
},
"earned_trophies_gold": {
"name": "Gold trophies",
"unit_of_measurement": "[%key:component::playstation_network::entity::sensor::earned_trophies_platinum::unit_of_measurement%]"
},
"earned_trophies_silver": {
"name": "Silver trophies",
"unit_of_measurement": "[%key:component::playstation_network::entity::sensor::earned_trophies_platinum::unit_of_measurement%]"
},
"earned_trophies_bronze": {
"name": "Bronze trophies",
"unit_of_measurement": "[%key:component::playstation_network::entity::sensor::earned_trophies_platinum::unit_of_measurement%]"
},
"online_id": {
"name": "Online-ID"
}
}
}
}

View File

@ -3,6 +3,7 @@
from collections.abc import Generator
from unittest.mock import AsyncMock, MagicMock, patch
from psnawp_api.models.trophies import TrophySet, TrophySummary
import pytest
from homeassistant.components.playstation_network.const import CONF_NPSSO, DOMAIN
@ -89,6 +90,34 @@ def mock_psnawpapi(mock_user: MagicMock) -> Generator[MagicMock]:
"accountDeviceVector": "abcdefghijklmnopqrstuv",
}
]
client.me.return_value.trophy_summary.return_value = TrophySummary(
PSN_ID, 1079, 19, 10, TrophySet(14450, 8722, 11754, 1398)
)
client.user.return_value.profile.return_value = {
"onlineId": "testuser",
"personalDetail": {
"firstName": "Rick",
"lastName": "Astley",
"profilePictures": [
{
"size": "xl",
"url": "http://static-resource.np.community.playstation.net/avatar_xl/WWS_A/UP90001312L24_DD96EB6A4FF5FE883C09_XL.png",
}
],
},
"aboutMe": "Never Gonna Give You Up",
"avatars": [
{
"size": "xl",
"url": "http://static-resource.np.community.playstation.net/avatar_xl/WWS_A/UP90001312L24_DD96EB6A4FF5FE883C09_XL.png",
}
],
"languages": ["de-DE"],
"isPlus": True,
"isOfficiallyVerified": False,
"isMe": True,
}
yield client

View File

@ -0,0 +1,343 @@
# serializer version: 1
# name: test_sensors[sensor.testuser_bronze_trophies-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.testuser_bronze_trophies',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Bronze trophies',
'platform': 'playstation_network',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': <PlaystationNetworkSensor.EARNED_TROPHIES_BRONZE: 'earned_trophies_bronze'>,
'unique_id': 'my-psn-id_earned_trophies_bronze',
'unit_of_measurement': 'trophies',
})
# ---
# name: test_sensors[sensor.testuser_bronze_trophies-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'testuser Bronze trophies',
'unit_of_measurement': 'trophies',
}),
'context': <ANY>,
'entity_id': 'sensor.testuser_bronze_trophies',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '14450',
})
# ---
# name: test_sensors[sensor.testuser_gold_trophies-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.testuser_gold_trophies',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Gold trophies',
'platform': 'playstation_network',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': <PlaystationNetworkSensor.EARNED_TROPHIES_GOLD: 'earned_trophies_gold'>,
'unique_id': 'my-psn-id_earned_trophies_gold',
'unit_of_measurement': 'trophies',
})
# ---
# name: test_sensors[sensor.testuser_gold_trophies-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'testuser Gold trophies',
'unit_of_measurement': 'trophies',
}),
'context': <ANY>,
'entity_id': 'sensor.testuser_gold_trophies',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '11754',
})
# ---
# name: test_sensors[sensor.testuser_next_level-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.testuser_next_level',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Next level',
'platform': 'playstation_network',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': <PlaystationNetworkSensor.TROPHY_LEVEL_PROGRESS: 'trophy_level_progress'>,
'unique_id': 'my-psn-id_trophy_level_progress',
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[sensor.testuser_next_level-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'testuser Next level',
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'sensor.testuser_next_level',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '19',
})
# ---
# name: test_sensors[sensor.testuser_online_id-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.testuser_online_id',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Online-ID',
'platform': 'playstation_network',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': <PlaystationNetworkSensor.ONLINE_ID: 'online_id'>,
'unique_id': 'my-psn-id_online_id',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[sensor.testuser_online_id-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'entity_picture': 'http://static-resource.np.community.playstation.net/avatar_xl/WWS_A/UP90001312L24_DD96EB6A4FF5FE883C09_XL.png',
'friendly_name': 'testuser Online-ID',
}),
'context': <ANY>,
'entity_id': 'sensor.testuser_online_id',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'testuser',
})
# ---
# name: test_sensors[sensor.testuser_platinum_trophies-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.testuser_platinum_trophies',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Platinum trophies',
'platform': 'playstation_network',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': <PlaystationNetworkSensor.EARNED_TROPHIES_PLATINUM: 'earned_trophies_platinum'>,
'unique_id': 'my-psn-id_earned_trophies_platinum',
'unit_of_measurement': 'trophies',
})
# ---
# name: test_sensors[sensor.testuser_platinum_trophies-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'testuser Platinum trophies',
'unit_of_measurement': 'trophies',
}),
'context': <ANY>,
'entity_id': 'sensor.testuser_platinum_trophies',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '1398',
})
# ---
# name: test_sensors[sensor.testuser_silver_trophies-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.testuser_silver_trophies',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Silver trophies',
'platform': 'playstation_network',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': <PlaystationNetworkSensor.EARNED_TROPHIES_SILVER: 'earned_trophies_silver'>,
'unique_id': 'my-psn-id_earned_trophies_silver',
'unit_of_measurement': 'trophies',
})
# ---
# name: test_sensors[sensor.testuser_silver_trophies-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'testuser Silver trophies',
'unit_of_measurement': 'trophies',
}),
'context': <ANY>,
'entity_id': 'sensor.testuser_silver_trophies',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '8722',
})
# ---
# name: test_sensors[sensor.testuser_trophy_level-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.testuser_trophy_level',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Trophy level',
'platform': 'playstation_network',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': <PlaystationNetworkSensor.TROPHY_LEVEL: 'trophy_level'>,
'unique_id': 'my-psn-id_trophy_level',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[sensor.testuser_trophy_level-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'testuser Trophy level',
}),
'context': <ANY>,
'entity_id': 'sensor.testuser_trophy_level',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '1079',
})
# ---

View File

@ -0,0 +1,42 @@
"""Test the Playstation Network sensor platform."""
from collections.abc import Generator
from unittest.mock import patch
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from tests.common import MockConfigEntry, snapshot_platform
@pytest.fixture(autouse=True)
def sensor_only() -> Generator[None]:
"""Enable only the sensor platform."""
with patch(
"homeassistant.components.playstation_network.PLATFORMS",
[Platform.SENSOR],
):
yield
@pytest.mark.usefixtures("mock_psnawpapi")
async def test_sensors(
hass: HomeAssistant,
config_entry: MockConfigEntry,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
) -> None:
"""Test setup of the PlayStation Network sensor platform."""
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED
await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id)