mirror of
https://github.com/home-assistant/core.git
synced 2026-04-02 00:20:30 +01:00
Add sensors for more game modes to Chess.com (#166331)
This commit is contained in:
committed by
GitHub
parent
7a4f953fa6
commit
686ab66a52
@@ -1,20 +1,68 @@
|
||||
{
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"chess960_daily_draw": {
|
||||
"default": "mdi:chess-pawn"
|
||||
},
|
||||
"chess960_daily_lost": {
|
||||
"default": "mdi:chess-pawn"
|
||||
},
|
||||
"chess960_daily_rating": {
|
||||
"default": "mdi:chart-line"
|
||||
},
|
||||
"chess960_daily_won": {
|
||||
"default": "mdi:chess-pawn"
|
||||
},
|
||||
"chess_blitz_draw": {
|
||||
"default": "mdi:chess-pawn"
|
||||
},
|
||||
"chess_blitz_lost": {
|
||||
"default": "mdi:chess-pawn"
|
||||
},
|
||||
"chess_blitz_rating": {
|
||||
"default": "mdi:chart-line"
|
||||
},
|
||||
"chess_blitz_won": {
|
||||
"default": "mdi:chess-pawn"
|
||||
},
|
||||
"chess_bullet_draw": {
|
||||
"default": "mdi:chess-pawn"
|
||||
},
|
||||
"chess_bullet_lost": {
|
||||
"default": "mdi:chess-pawn"
|
||||
},
|
||||
"chess_bullet_rating": {
|
||||
"default": "mdi:chart-line"
|
||||
},
|
||||
"chess_bullet_won": {
|
||||
"default": "mdi:chess-pawn"
|
||||
},
|
||||
"chess_daily_draw": {
|
||||
"default": "mdi:chess-pawn"
|
||||
},
|
||||
"chess_daily_lost": {
|
||||
"default": "mdi:chess-pawn"
|
||||
},
|
||||
"chess_daily_rating": {
|
||||
"default": "mdi:chart-line"
|
||||
},
|
||||
"chess_daily_won": {
|
||||
"default": "mdi:chess-pawn"
|
||||
},
|
||||
"chess_rapid_draw": {
|
||||
"default": "mdi:chess-pawn"
|
||||
},
|
||||
"chess_rapid_lost": {
|
||||
"default": "mdi:chess-pawn"
|
||||
},
|
||||
"chess_rapid_rating": {
|
||||
"default": "mdi:chart-line"
|
||||
},
|
||||
"chess_rapid_won": {
|
||||
"default": "mdi:chess-pawn"
|
||||
},
|
||||
"followers": {
|
||||
"default": "mdi:account-multiple"
|
||||
},
|
||||
"total_daily_draw": {
|
||||
"default": "mdi:chess-pawn"
|
||||
},
|
||||
"total_daily_lost": {
|
||||
"default": "mdi:chess-pawn"
|
||||
},
|
||||
"total_daily_won": {
|
||||
"default": "mdi:chess-pawn"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from chess_com_api import PlayerStats
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorEntity,
|
||||
@@ -24,7 +27,14 @@ class ChessEntityDescription(SensorEntityDescription):
|
||||
value_fn: Callable[[ChessData], float]
|
||||
|
||||
|
||||
SENSORS: tuple[ChessEntityDescription, ...] = (
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class ChessModeEntityDescription(SensorEntityDescription):
|
||||
"""Sensor description for a Chess.com game mode."""
|
||||
|
||||
value_fn: Callable[[dict[str, Any]], float]
|
||||
|
||||
|
||||
PLAYER_SENSORS: tuple[ChessEntityDescription, ...] = (
|
||||
ChessEntityDescription(
|
||||
key="followers",
|
||||
translation_key="followers",
|
||||
@@ -33,35 +43,46 @@ SENSORS: tuple[ChessEntityDescription, ...] = (
|
||||
value_fn=lambda state: state.player.followers,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
ChessEntityDescription(
|
||||
key="chess_daily_rating",
|
||||
translation_key="chess_daily_rating",
|
||||
)
|
||||
|
||||
GAME_MODE_SENSORS: tuple[ChessModeEntityDescription, ...] = (
|
||||
ChessModeEntityDescription(
|
||||
key="rating",
|
||||
translation_key="rating",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda state: state.stats.chess_daily["last"]["rating"],
|
||||
value_fn=lambda mode: mode["last"]["rating"],
|
||||
),
|
||||
ChessEntityDescription(
|
||||
key="total_daily_won",
|
||||
translation_key="total_daily_won",
|
||||
ChessModeEntityDescription(
|
||||
key="won",
|
||||
translation_key="won",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
value_fn=lambda state: state.stats.chess_daily["record"]["win"],
|
||||
value_fn=lambda mode: mode["record"]["win"],
|
||||
),
|
||||
ChessEntityDescription(
|
||||
key="total_daily_lost",
|
||||
translation_key="total_daily_lost",
|
||||
ChessModeEntityDescription(
|
||||
key="lost",
|
||||
translation_key="lost",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
value_fn=lambda state: state.stats.chess_daily["record"]["loss"],
|
||||
value_fn=lambda mode: mode["record"]["loss"],
|
||||
),
|
||||
ChessEntityDescription(
|
||||
key="total_daily_draw",
|
||||
translation_key="total_daily_draw",
|
||||
ChessModeEntityDescription(
|
||||
key="draw",
|
||||
translation_key="draw",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
value_fn=lambda state: state.stats.chess_daily["record"]["draw"],
|
||||
value_fn=lambda mode: mode["record"]["draw"],
|
||||
),
|
||||
)
|
||||
|
||||
GAME_MODES: dict[str, Callable[[PlayerStats], dict[str, Any] | None]] = {
|
||||
"chess_daily": lambda stats: stats.chess_daily,
|
||||
"chess_rapid": lambda stats: stats.chess_rapid,
|
||||
"chess_bullet": lambda stats: stats.chess_bullet,
|
||||
"chess_blitz": lambda stats: stats.chess_blitz,
|
||||
"chess960_daily": lambda stats: stats.chess960_daily,
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
@@ -71,13 +92,22 @@ async def async_setup_entry(
|
||||
"""Initialize the entries."""
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
async_add_entities(
|
||||
ChessPlayerSensor(coordinator, description) for description in SENSORS
|
||||
)
|
||||
entities: list[SensorEntity] = [
|
||||
ChessPlayerSensor(coordinator, description) for description in PLAYER_SENSORS
|
||||
]
|
||||
|
||||
for game_mode, stats_fn in GAME_MODES.items():
|
||||
if stats_fn(coordinator.data.stats) is not None:
|
||||
entities.extend(
|
||||
ChessGameModeSensor(coordinator, description, game_mode, stats_fn)
|
||||
for description in GAME_MODE_SENSORS
|
||||
)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class ChessPlayerSensor(ChessEntity, SensorEntity):
|
||||
"""Chess.com sensor."""
|
||||
"""Chess.com player sensor."""
|
||||
|
||||
entity_description: ChessEntityDescription
|
||||
|
||||
@@ -95,3 +125,33 @@ class ChessPlayerSensor(ChessEntity, SensorEntity):
|
||||
def native_value(self) -> float:
|
||||
"""Return the state of the sensor."""
|
||||
return self.entity_description.value_fn(self.coordinator.data)
|
||||
|
||||
|
||||
class ChessGameModeSensor(ChessEntity, SensorEntity):
|
||||
"""Chess.com game mode sensor."""
|
||||
|
||||
entity_description: ChessModeEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: ChessCoordinator,
|
||||
description: ChessModeEntityDescription,
|
||||
game_mode: str,
|
||||
stats_fn: Callable[[PlayerStats], dict[str, Any] | None],
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(coordinator)
|
||||
self.entity_description = description
|
||||
self._stats_fn = stats_fn
|
||||
self._attr_unique_id = (
|
||||
f"{coordinator.config_entry.unique_id}.{game_mode}.{description.key}"
|
||||
)
|
||||
self._attr_translation_key = f"{game_mode}_{description.translation_key}"
|
||||
|
||||
@property
|
||||
def native_value(self) -> float:
|
||||
"""Return the state of the sensor."""
|
||||
mode_data = self._stats_fn(self.coordinator.data.stats)
|
||||
if TYPE_CHECKING:
|
||||
assert mode_data is not None
|
||||
return self.entity_description.value_fn(mode_data)
|
||||
|
||||
@@ -23,24 +23,84 @@
|
||||
},
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"chess960_daily_draw": {
|
||||
"name": "Total daily Chess960 games drawn",
|
||||
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::chess_daily_won::unit_of_measurement%]"
|
||||
},
|
||||
"chess960_daily_lost": {
|
||||
"name": "Total daily Chess960 games lost",
|
||||
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::chess_daily_won::unit_of_measurement%]"
|
||||
},
|
||||
"chess960_daily_rating": {
|
||||
"name": "Daily Chess960 rating"
|
||||
},
|
||||
"chess960_daily_won": {
|
||||
"name": "Total daily Chess960 games won",
|
||||
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::chess_daily_won::unit_of_measurement%]"
|
||||
},
|
||||
"chess_blitz_draw": {
|
||||
"name": "Total blitz chess games drawn",
|
||||
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::chess_daily_won::unit_of_measurement%]"
|
||||
},
|
||||
"chess_blitz_lost": {
|
||||
"name": "Total blitz chess games lost",
|
||||
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::chess_daily_won::unit_of_measurement%]"
|
||||
},
|
||||
"chess_blitz_rating": {
|
||||
"name": "Blitz chess rating"
|
||||
},
|
||||
"chess_blitz_won": {
|
||||
"name": "Total blitz chess games won",
|
||||
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::chess_daily_won::unit_of_measurement%]"
|
||||
},
|
||||
"chess_bullet_draw": {
|
||||
"name": "Total bullet chess games drawn",
|
||||
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::chess_daily_won::unit_of_measurement%]"
|
||||
},
|
||||
"chess_bullet_lost": {
|
||||
"name": "Total bullet chess games lost",
|
||||
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::chess_daily_won::unit_of_measurement%]"
|
||||
},
|
||||
"chess_bullet_rating": {
|
||||
"name": "Bullet chess rating"
|
||||
},
|
||||
"chess_bullet_won": {
|
||||
"name": "Total bullet chess games won",
|
||||
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::chess_daily_won::unit_of_measurement%]"
|
||||
},
|
||||
"chess_daily_draw": {
|
||||
"name": "Total daily chess games drawn",
|
||||
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::chess_daily_won::unit_of_measurement%]"
|
||||
},
|
||||
"chess_daily_lost": {
|
||||
"name": "Total daily chess games lost",
|
||||
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::chess_daily_won::unit_of_measurement%]"
|
||||
},
|
||||
"chess_daily_rating": {
|
||||
"name": "Daily chess rating"
|
||||
},
|
||||
"chess_daily_won": {
|
||||
"name": "Total daily chess games won",
|
||||
"unit_of_measurement": "games"
|
||||
},
|
||||
"chess_rapid_draw": {
|
||||
"name": "Total rapid chess games drawn",
|
||||
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::chess_daily_won::unit_of_measurement%]"
|
||||
},
|
||||
"chess_rapid_lost": {
|
||||
"name": "Total rapid chess games lost",
|
||||
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::chess_daily_won::unit_of_measurement%]"
|
||||
},
|
||||
"chess_rapid_rating": {
|
||||
"name": "Rapid chess rating"
|
||||
},
|
||||
"chess_rapid_won": {
|
||||
"name": "Total rapid chess games won",
|
||||
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::chess_daily_won::unit_of_measurement%]"
|
||||
},
|
||||
"followers": {
|
||||
"name": "Followers",
|
||||
"unit_of_measurement": "followers"
|
||||
},
|
||||
"total_daily_draw": {
|
||||
"name": "Total chess games drawn",
|
||||
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::total_daily_won::unit_of_measurement%]"
|
||||
},
|
||||
"total_daily_lost": {
|
||||
"name": "Total chess games lost",
|
||||
"unit_of_measurement": "[%key:component::chess_com::entity::sensor::total_daily_won::unit_of_measurement%]"
|
||||
},
|
||||
"total_daily_won": {
|
||||
"name": "Total chess games won",
|
||||
"unit_of_measurement": "games"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,62 @@
|
||||
"timeout_percent": 0
|
||||
}
|
||||
},
|
||||
"chess_rapid": {
|
||||
"last": {
|
||||
"rating": 812,
|
||||
"date": 1772800350,
|
||||
"rd": 74
|
||||
},
|
||||
"record": {
|
||||
"win": 5,
|
||||
"loss": 3,
|
||||
"draw": 1,
|
||||
"time_per_move": 42,
|
||||
"timeout_percent": 0
|
||||
}
|
||||
},
|
||||
"chess_bullet": {
|
||||
"last": {
|
||||
"rating": 650,
|
||||
"date": 1772800350,
|
||||
"rd": 120
|
||||
},
|
||||
"record": {
|
||||
"win": 2,
|
||||
"loss": 6,
|
||||
"draw": 0,
|
||||
"time_per_move": 8,
|
||||
"timeout_percent": 0
|
||||
}
|
||||
},
|
||||
"chess_blitz": {
|
||||
"last": {
|
||||
"rating": 734,
|
||||
"date": 1772800350,
|
||||
"rd": 95
|
||||
},
|
||||
"record": {
|
||||
"win": 10,
|
||||
"loss": 8,
|
||||
"draw": 2,
|
||||
"time_per_move": 18,
|
||||
"timeout_percent": 0
|
||||
}
|
||||
},
|
||||
"chess960_daily": {
|
||||
"last": {
|
||||
"rating": 521,
|
||||
"date": 1772800350,
|
||||
"rd": 200
|
||||
},
|
||||
"record": {
|
||||
"win": 1,
|
||||
"loss": 2,
|
||||
"draw": 0,
|
||||
"time_per_move": 7200,
|
||||
"timeout_percent": 0
|
||||
}
|
||||
},
|
||||
"fide": 0,
|
||||
"tactics": {
|
||||
"highest": {
|
||||
|
||||
33
tests/components/chess_com/fixtures/stats_daily_only.json
Normal file
33
tests/components/chess_com/fixtures/stats_daily_only.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"chess_daily": {
|
||||
"last": {
|
||||
"rating": 495,
|
||||
"date": 1772800350,
|
||||
"rd": 196
|
||||
},
|
||||
"record": {
|
||||
"win": 0,
|
||||
"loss": 4,
|
||||
"draw": 0,
|
||||
"time_per_move": 6974,
|
||||
"timeout_percent": 0
|
||||
}
|
||||
},
|
||||
"fide": 0,
|
||||
"tactics": {
|
||||
"highest": {
|
||||
"rating": 764,
|
||||
"date": 1772782351
|
||||
},
|
||||
"lowest": {
|
||||
"rating": 400,
|
||||
"date": 1771584762
|
||||
}
|
||||
},
|
||||
"puzzle_rush": {
|
||||
"best": {
|
||||
"total_attempts": 11,
|
||||
"score": 8
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,9 +19,48 @@
|
||||
'username': 'joostlek',
|
||||
}),
|
||||
'stats': dict({
|
||||
'chess960_daily': None,
|
||||
'chess_blitz': None,
|
||||
'chess_bullet': None,
|
||||
'chess960_daily': dict({
|
||||
'last': dict({
|
||||
'date': 1772800350,
|
||||
'rating': 521,
|
||||
'rd': 200,
|
||||
}),
|
||||
'record': dict({
|
||||
'draw': 0,
|
||||
'loss': 2,
|
||||
'time_per_move': 7200,
|
||||
'timeout_percent': 0,
|
||||
'win': 1,
|
||||
}),
|
||||
}),
|
||||
'chess_blitz': dict({
|
||||
'last': dict({
|
||||
'date': 1772800350,
|
||||
'rating': 734,
|
||||
'rd': 95,
|
||||
}),
|
||||
'record': dict({
|
||||
'draw': 2,
|
||||
'loss': 8,
|
||||
'time_per_move': 18,
|
||||
'timeout_percent': 0,
|
||||
'win': 10,
|
||||
}),
|
||||
}),
|
||||
'chess_bullet': dict({
|
||||
'last': dict({
|
||||
'date': 1772800350,
|
||||
'rating': 650,
|
||||
'rd': 120,
|
||||
}),
|
||||
'record': dict({
|
||||
'draw': 0,
|
||||
'loss': 6,
|
||||
'time_per_move': 8,
|
||||
'timeout_percent': 0,
|
||||
'win': 2,
|
||||
}),
|
||||
}),
|
||||
'chess_daily': dict({
|
||||
'last': dict({
|
||||
'date': 1772800350,
|
||||
@@ -36,7 +75,20 @@
|
||||
'win': 0,
|
||||
}),
|
||||
}),
|
||||
'chess_rapid': None,
|
||||
'chess_rapid': dict({
|
||||
'last': dict({
|
||||
'date': 1772800350,
|
||||
'rating': 812,
|
||||
'rd': 74,
|
||||
}),
|
||||
'record': dict({
|
||||
'draw': 1,
|
||||
'loss': 3,
|
||||
'time_per_move': 42,
|
||||
'timeout_percent': 0,
|
||||
'win': 5,
|
||||
}),
|
||||
}),
|
||||
'lessons': None,
|
||||
'puzzle_rush': dict({
|
||||
'best': dict({
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,16 +2,22 @@
|
||||
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from chess_com_api import PlayerStats
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.chess_com.const import DOMAIN
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry, snapshot_platform
|
||||
from tests.common import (
|
||||
MockConfigEntry,
|
||||
async_load_json_object_fixture,
|
||||
snapshot_platform,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
@@ -27,3 +33,21 @@ async def test_all_entities(
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||
|
||||
|
||||
async def test_daily_only(
|
||||
hass: HomeAssistant,
|
||||
mock_chess_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test that entities for unplayed game modes are not created."""
|
||||
mock_chess_client.get_player_stats.return_value = PlayerStats.from_dict(
|
||||
await async_load_json_object_fixture(hass, "stats_daily_only.json", DOMAIN)
|
||||
)
|
||||
with patch("homeassistant.components.chess_com._PLATFORMS", [Platform.SENSOR]):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
assert hass.states.get("sensor.joost_rapid_chess_rating") is None
|
||||
assert hass.states.get("sensor.joost_bullet_chess_rating") is None
|
||||
assert hass.states.get("sensor.joost_blitz_chess_rating") is None
|
||||
assert hass.states.get("sensor.joost_daily_chess960_rating") is None
|
||||
|
||||
Reference in New Issue
Block a user