Add visits today sensor for pets (#147459)

This commit is contained in:
Nathan Spencer 2025-07-10 04:24:54 -06:00 committed by GitHub
parent 8881919efd
commit 2829cc1248
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 59 additions and 2 deletions

View File

@ -48,6 +48,9 @@ class LitterRobotDataUpdateCoordinator(DataUpdateCoordinator[None]):
"""Update all device states from the Litter-Robot API."""
await self.account.refresh_robots()
await self.account.load_pets()
for pet in self.account.pets:
# Need to fetch weight history for `get_visits_since`
await pet.fetch_weight_history()
async def _async_setup(self) -> None:
"""Set up the coordinator."""

View File

@ -49,6 +49,9 @@
},
"total_cycles": {
"default": "mdi:counter"
},
"visits_today": {
"default": "mdi:counter"
}
},
"switch": {

View File

@ -18,6 +18,7 @@ from homeassistant.components.sensor import (
from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfMass
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util import dt as dt_util
from .coordinator import LitterRobotConfigEntry
from .entity import LitterRobotEntity, _WhiskerEntityT
@ -39,6 +40,7 @@ class RobotSensorEntityDescription(SensorEntityDescription, Generic[_WhiskerEnti
"""A class that describes robot sensor entities."""
icon_fn: Callable[[Any], str | None] = lambda _: None
last_reset_fn: Callable[[], datetime | None] = lambda: None
value_fn: Callable[[_WhiskerEntityT], float | datetime | str | None]
@ -179,7 +181,14 @@ PET_SENSORS: list[RobotSensorEntityDescription] = [
native_unit_of_measurement=UnitOfMass.POUNDS,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda pet: pet.weight,
)
),
RobotSensorEntityDescription[Pet](
key="visits_today",
translation_key="visits_today",
state_class=SensorStateClass.TOTAL,
last_reset_fn=dt_util.start_of_local_day,
value_fn=lambda pet: pet.get_visits_since(dt_util.start_of_local_day()),
),
]
@ -225,3 +234,8 @@ class LitterRobotSensorEntity(LitterRobotEntity[_WhiskerEntityT], SensorEntity):
if (icon := self.entity_description.icon_fn(self.state)) is not None:
return icon
return super().icon
@property
def last_reset(self) -> datetime | None:
"""Return the time when the sensor was last reset, if any."""
return self.entity_description.last_reset_fn() or super().last_reset

View File

@ -122,6 +122,10 @@
"name": "Total cycles",
"unit_of_measurement": "cycles"
},
"visits_today": {
"name": "Visits today",
"unit_of_measurement": "visits"
},
"waste_drawer": {
"name": "Waste drawer"
}

View File

@ -159,6 +159,15 @@ PET_DATA = {
"gender": "FEMALE",
"lastWeightReading": 9.1,
"breeds": ["sphynx"],
"weightHistory": [
{"weight": 6.48, "timestamp": "2025-06-13T16:12:36"},
{"weight": 6.6, "timestamp": "2025-06-14T03:52:00"},
{"weight": 6.59, "timestamp": "2025-06-14T17:20:32"},
{"weight": 6.5, "timestamp": "2025-06-14T19:22:48"},
{"weight": 6.35, "timestamp": "2025-06-15T03:12:15"},
{"weight": 6.45, "timestamp": "2025-06-15T15:27:21"},
{"weight": 6.25, "timestamp": "2025-06-15T15:29:26"},
],
}
VACUUM_ENTITY_ID = "vacuum.test_litter_box"

View File

@ -52,6 +52,20 @@ def create_mock_robot(
return robot
def create_mock_pet(
pet_data: dict | None,
account: Account,
side_effect: Any | None = None,
) -> Pet:
"""Create a mock Pet."""
if not pet_data:
pet_data = {}
pet = Pet(data={**PET_DATA, **pet_data}, session=account.session)
pet.fetch_weight_history = AsyncMock(side_effect=side_effect)
return pet
def create_mock_account(
robot_data: dict | None = None,
side_effect: Any | None = None,
@ -69,7 +83,7 @@ def create_mock_account(
if skip_robots
else [create_mock_robot(robot_data, account, v4, feeder, side_effect)]
)
account.pets = [Pet(PET_DATA, account.session)] if pet else []
account.pets = [create_mock_pet(PET_DATA, account, side_effect)] if pet else []
return account

View File

@ -124,6 +124,16 @@ async def test_pet_weight_sensor(
assert sensor.attributes["unit_of_measurement"] == UnitOfMass.POUNDS
@pytest.mark.freeze_time("2025-06-15 12:00:00+00:00")
async def test_pet_visits_today_sensor(
hass: HomeAssistant, mock_account_with_pet: MagicMock
) -> None:
"""Tests pet visits today sensors."""
await setup_integration(hass, mock_account_with_pet, PLATFORM_DOMAIN)
sensor = hass.states.get("sensor.kitty_visits_today")
assert sensor.state == "2"
async def test_litterhopper_sensor(
hass: HomeAssistant, mock_account_with_litterhopper: MagicMock
) -> None: