Files
core/homeassistant/components/xbox/binary_sensor.py

162 lines
5.0 KiB
Python

"""Xbox friends binary sensors."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from enum import StrEnum
from typing import Any
from xbox.webapi.api.provider.people.models import Person
from xbox.webapi.api.provider.titlehub.models import Title
from yarl import URL
from homeassistant.components.binary_sensor import (
DOMAIN as BINARY_SENSOR_DOMAIN,
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import XboxConfigEntry
from .entity import XboxBaseEntity, XboxBaseEntityDescription, check_deprecated_entity
class XboxBinarySensor(StrEnum):
"""Xbox binary sensor."""
ONLINE = "online"
IN_PARTY = "in_party"
IN_GAME = "in_game"
IN_MULTIPLAYER = "in_multiplayer"
HAS_GAME_PASS = "has_game_pass"
@dataclass(kw_only=True, frozen=True)
class XboxBinarySensorEntityDescription(
XboxBaseEntityDescription, BinarySensorEntityDescription
):
"""Xbox binary sensor description."""
is_on_fn: Callable[[Person], bool | None]
deprecated: bool | None = None
def profile_pic(person: Person, _: Title | None) -> str | None:
"""Return the gamer pic."""
# Xbox sometimes returns a domain that uses a wrong certificate which
# creates issues with loading the image.
# The correct domain is images-eds-ssl which can just be replaced
# to point to the correct image, with the correct domain and certificate.
# We need to also remove the 'mode=Padding' query because with it,
# it results in an error 400.
url = URL(person.display_pic_raw)
if url.host == "images-eds.xboxlive.com":
url = url.with_host("images-eds-ssl.xboxlive.com").with_scheme("https")
query = dict(url.query)
query.pop("mode", None)
return str(url.with_query(query))
def profile_attributes(person: Person, _: Title | None) -> dict[str, Any]:
"""Attributes for the profile."""
attributes: dict[str, Any] = {}
attributes["display_name"] = person.display_name
attributes["real_name"] = person.real_name or None
attributes["bio"] = person.detail.bio if person.detail else None
return attributes
def in_game(person: Person) -> bool:
"""True if person is in a game."""
active_app = (
next(
(presence for presence in person.presence_details if presence.is_primary),
None,
)
if person.presence_details
else None
)
return (
active_app is not None and active_app.is_game and active_app.state == "Active"
)
SENSOR_DESCRIPTIONS: tuple[XboxBinarySensorEntityDescription, ...] = (
XboxBinarySensorEntityDescription(
key=XboxBinarySensor.ONLINE,
translation_key=XboxBinarySensor.ONLINE,
is_on_fn=lambda x: x.presence_state == "Online",
name=None,
entity_picture_fn=profile_pic,
attributes_fn=profile_attributes,
),
XboxBinarySensorEntityDescription(
key=XboxBinarySensor.IN_PARTY,
is_on_fn=lambda _: None,
deprecated=True,
),
XboxBinarySensorEntityDescription(
key=XboxBinarySensor.IN_GAME,
translation_key=XboxBinarySensor.IN_GAME,
is_on_fn=in_game,
),
XboxBinarySensorEntityDescription(
key=XboxBinarySensor.IN_MULTIPLAYER,
is_on_fn=lambda _: None,
deprecated=True,
),
XboxBinarySensorEntityDescription(
key=XboxBinarySensor.HAS_GAME_PASS,
translation_key=XboxBinarySensor.HAS_GAME_PASS,
is_on_fn=lambda x: x.detail.has_game_pass if x.detail else None,
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: XboxConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Xbox Live friends."""
xuids_added: set[str] = set()
coordinator = entry.runtime_data
@callback
def add_entities() -> None:
nonlocal xuids_added
current_xuids = set(coordinator.data.presence)
if new_xuids := current_xuids - xuids_added:
for xuid in new_xuids:
async_add_entities(
[
XboxBinarySensorEntity(coordinator, xuid, description)
for description in SENSOR_DESCRIPTIONS
if check_deprecated_entity(
hass, xuid, description, BINARY_SENSOR_DOMAIN
)
]
)
xuids_added |= new_xuids
xuids_added &= current_xuids
coordinator.async_add_listener(add_entities)
add_entities()
class XboxBinarySensorEntity(XboxBaseEntity, BinarySensorEntity):
"""Representation of a Xbox presence state."""
entity_description: XboxBinarySensorEntityDescription
@property
def is_on(self) -> bool | None:
"""Return the status of the requested attribute."""
return self.entity_description.is_on_fn(self.data)