mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 15:47:12 +00:00
Add visits today sensor for pets (#147459)
This commit is contained in:
parent
8881919efd
commit
2829cc1248
@ -48,6 +48,9 @@ class LitterRobotDataUpdateCoordinator(DataUpdateCoordinator[None]):
|
|||||||
"""Update all device states from the Litter-Robot API."""
|
"""Update all device states from the Litter-Robot API."""
|
||||||
await self.account.refresh_robots()
|
await self.account.refresh_robots()
|
||||||
await self.account.load_pets()
|
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:
|
async def _async_setup(self) -> None:
|
||||||
"""Set up the coordinator."""
|
"""Set up the coordinator."""
|
||||||
|
@ -49,6 +49,9 @@
|
|||||||
},
|
},
|
||||||
"total_cycles": {
|
"total_cycles": {
|
||||||
"default": "mdi:counter"
|
"default": "mdi:counter"
|
||||||
|
},
|
||||||
|
"visits_today": {
|
||||||
|
"default": "mdi:counter"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"switch": {
|
"switch": {
|
||||||
|
@ -18,6 +18,7 @@ from homeassistant.components.sensor import (
|
|||||||
from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfMass
|
from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfMass
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from .coordinator import LitterRobotConfigEntry
|
from .coordinator import LitterRobotConfigEntry
|
||||||
from .entity import LitterRobotEntity, _WhiskerEntityT
|
from .entity import LitterRobotEntity, _WhiskerEntityT
|
||||||
@ -39,6 +40,7 @@ class RobotSensorEntityDescription(SensorEntityDescription, Generic[_WhiskerEnti
|
|||||||
"""A class that describes robot sensor entities."""
|
"""A class that describes robot sensor entities."""
|
||||||
|
|
||||||
icon_fn: Callable[[Any], str | None] = lambda _: None
|
icon_fn: Callable[[Any], str | None] = lambda _: None
|
||||||
|
last_reset_fn: Callable[[], datetime | None] = lambda: None
|
||||||
value_fn: Callable[[_WhiskerEntityT], float | datetime | str | None]
|
value_fn: Callable[[_WhiskerEntityT], float | datetime | str | None]
|
||||||
|
|
||||||
|
|
||||||
@ -179,7 +181,14 @@ PET_SENSORS: list[RobotSensorEntityDescription] = [
|
|||||||
native_unit_of_measurement=UnitOfMass.POUNDS,
|
native_unit_of_measurement=UnitOfMass.POUNDS,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value_fn=lambda pet: pet.weight,
|
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:
|
if (icon := self.entity_description.icon_fn(self.state)) is not None:
|
||||||
return icon
|
return icon
|
||||||
return super().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
|
||||||
|
@ -122,6 +122,10 @@
|
|||||||
"name": "Total cycles",
|
"name": "Total cycles",
|
||||||
"unit_of_measurement": "cycles"
|
"unit_of_measurement": "cycles"
|
||||||
},
|
},
|
||||||
|
"visits_today": {
|
||||||
|
"name": "Visits today",
|
||||||
|
"unit_of_measurement": "visits"
|
||||||
|
},
|
||||||
"waste_drawer": {
|
"waste_drawer": {
|
||||||
"name": "Waste drawer"
|
"name": "Waste drawer"
|
||||||
}
|
}
|
||||||
|
@ -159,6 +159,15 @@ PET_DATA = {
|
|||||||
"gender": "FEMALE",
|
"gender": "FEMALE",
|
||||||
"lastWeightReading": 9.1,
|
"lastWeightReading": 9.1,
|
||||||
"breeds": ["sphynx"],
|
"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"
|
VACUUM_ENTITY_ID = "vacuum.test_litter_box"
|
||||||
|
@ -52,6 +52,20 @@ def create_mock_robot(
|
|||||||
return 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(
|
def create_mock_account(
|
||||||
robot_data: dict | None = None,
|
robot_data: dict | None = None,
|
||||||
side_effect: Any | None = None,
|
side_effect: Any | None = None,
|
||||||
@ -69,7 +83,7 @@ def create_mock_account(
|
|||||||
if skip_robots
|
if skip_robots
|
||||||
else [create_mock_robot(robot_data, account, v4, feeder, side_effect)]
|
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
|
return account
|
||||||
|
|
||||||
|
|
||||||
|
@ -124,6 +124,16 @@ async def test_pet_weight_sensor(
|
|||||||
assert sensor.attributes["unit_of_measurement"] == UnitOfMass.POUNDS
|
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(
|
async def test_litterhopper_sensor(
|
||||||
hass: HomeAssistant, mock_account_with_litterhopper: MagicMock
|
hass: HomeAssistant, mock_account_with_litterhopper: MagicMock
|
||||||
) -> None:
|
) -> None:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user