Move battery properties from legacy Ecovacs vacuum entity to separate entities (#149084)

This commit is contained in:
Michael 2025-07-28 18:49:12 +02:00 committed by GitHub
parent 5af4290b77
commit b1dd742a57
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 151 additions and 28 deletions

View File

@ -4,10 +4,12 @@ from collections.abc import Callable
from dataclasses import dataclass
from deebot_client.capabilities import CapabilityEvent
from deebot_client.events.base import Event
from deebot_client.events import Event
from deebot_client.events.water_info import MopAttachedEvent
from sucks import VacBot
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
BinarySensorEntityDescription,
)
@ -16,7 +18,11 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import EcovacsConfigEntry
from .entity import EcovacsCapabilityEntityDescription, EcovacsDescriptionEntity
from .entity import (
EcovacsCapabilityEntityDescription,
EcovacsDescriptionEntity,
EcovacsLegacyEntity,
)
from .util import get_supported_entities
@ -47,12 +53,23 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Add entities for passed config_entry in HA."""
controller = config_entry.runtime_data
async_add_entities(
get_supported_entities(
config_entry.runtime_data, EcovacsBinarySensor, ENTITY_DESCRIPTIONS
)
)
legacy_entities = []
for device in controller.legacy_devices:
if not controller.legacy_entity_is_added(device, "battery_charging"):
controller.add_legacy_entity(device, "battery_charging")
legacy_entities.append(EcovacsLegacyBatteryChargingSensor(device))
if legacy_entities:
async_add_entities(legacy_entities)
class EcovacsBinarySensor[EventT: Event](
EcovacsDescriptionEntity[CapabilityEvent[EventT]],
@ -71,3 +88,33 @@ class EcovacsBinarySensor[EventT: Event](
self.async_write_ha_state()
self._subscribe(self._capability.event, on_event)
class EcovacsLegacyBatteryChargingSensor(EcovacsLegacyEntity, BinarySensorEntity):
"""Legacy battery charging sensor."""
_attr_device_class = BinarySensorDeviceClass.BATTERY_CHARGING
_attr_entity_category = EntityCategory.DIAGNOSTIC
def __init__(
self,
device: VacBot,
) -> None:
"""Initialize the entity."""
super().__init__(device)
self._attr_unique_id = f"{device.vacuum['did']}_battery_charging"
async def async_added_to_hass(self) -> None:
"""Set up the event listeners now that hass is ready."""
self._event_listeners.append(
self.device.statusEvents.subscribe(
lambda _: self.schedule_update_ha_state()
)
)
@property
def is_on(self) -> bool | None:
"""Return true if the binary sensor is on."""
if self.device.charge_status is None:
return None
return bool(self.device.is_charging)

View File

@ -37,6 +37,7 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.icon import icon_for_battery_level
from homeassistant.helpers.typing import StateType
from . import EcovacsConfigEntry
@ -225,7 +226,7 @@ async def async_setup_entry(
async_add_entities(entities)
async def _add_legacy_entities() -> None:
async def _add_legacy_lifespan_entities() -> None:
entities = []
for device in controller.legacy_devices:
for description in LEGACY_LIFESPAN_SENSORS:
@ -242,14 +243,21 @@ async def async_setup_entry(
async_add_entities(entities)
def _fire_ecovacs_legacy_lifespan_event(_: Any) -> None:
hass.create_task(_add_legacy_entities())
hass.create_task(_add_legacy_lifespan_entities())
legacy_entities = []
for device in controller.legacy_devices:
config_entry.async_on_unload(
device.lifespanEvents.subscribe(
_fire_ecovacs_legacy_lifespan_event
).unsubscribe
)
if not controller.legacy_entity_is_added(device, "battery_status"):
controller.add_legacy_entity(device, "battery_status")
legacy_entities.append(EcovacsLegacyBatterySensor(device))
if legacy_entities:
async_add_entities(legacy_entities)
class EcovacsSensor(
@ -344,6 +352,44 @@ class EcovacsErrorSensor(
self._subscribe(self._capability.event, on_event)
class EcovacsLegacyBatterySensor(EcovacsLegacyEntity, SensorEntity):
"""Legacy battery sensor."""
_attr_native_unit_of_measurement = PERCENTAGE
_attr_device_class = SensorDeviceClass.BATTERY
_attr_entity_category = EntityCategory.DIAGNOSTIC
def __init__(
self,
device: VacBot,
) -> None:
"""Initialize the entity."""
super().__init__(device)
self._attr_unique_id = f"{device.vacuum['did']}_battery_status"
async def async_added_to_hass(self) -> None:
"""Set up the event listeners now that hass is ready."""
self._event_listeners.append(
self.device.batteryEvents.subscribe(
lambda _: self.schedule_update_ha_state()
)
)
@property
def native_value(self) -> StateType:
"""Return the value reported by the sensor."""
if (status := self.device.battery_status) is not None:
return status * 100 # type: ignore[no-any-return]
return None
@property
def icon(self) -> str | None:
"""Return the icon to use in the frontend, if any."""
return icon_for_battery_level(
battery_level=self.native_value, charging=self.device.is_charging
)
class EcovacsLegacyLifespanSensor(EcovacsLegacyEntity, SensorEntity):
"""Legacy Lifespan sensor."""

View File

@ -22,7 +22,6 @@ from homeassistant.core import HomeAssistant, SupportsResponse
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import entity_platform
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.icon import icon_for_battery_level
from homeassistant.util import slugify
from . import EcovacsConfigEntry
@ -71,8 +70,7 @@ class EcovacsLegacyVacuum(EcovacsLegacyEntity, StateVacuumEntity):
_attr_fan_speed_list = [sucks.FAN_SPEED_NORMAL, sucks.FAN_SPEED_HIGH]
_attr_supported_features = (
VacuumEntityFeature.BATTERY
| VacuumEntityFeature.RETURN_HOME
VacuumEntityFeature.RETURN_HOME
| VacuumEntityFeature.CLEAN_SPOT
| VacuumEntityFeature.STOP
| VacuumEntityFeature.START
@ -89,11 +87,6 @@ class EcovacsLegacyVacuum(EcovacsLegacyEntity, StateVacuumEntity):
lambda _: self.schedule_update_ha_state()
)
)
self._event_listeners.append(
self.device.batteryEvents.subscribe(
lambda _: self.schedule_update_ha_state()
)
)
self._event_listeners.append(
self.device.lifespanEvents.subscribe(
lambda _: self.schedule_update_ha_state()
@ -137,21 +130,6 @@ class EcovacsLegacyVacuum(EcovacsLegacyEntity, StateVacuumEntity):
return None
@property
def battery_level(self) -> int | None:
"""Return the battery level of the vacuum cleaner."""
if self.device.battery_status is not None:
return self.device.battery_status * 100 # type: ignore[no-any-return]
return None
@property
def battery_icon(self) -> str:
"""Return the battery icon for the vacuum cleaner."""
return icon_for_battery_level(
battery_level=self.battery_level, charging=self.device.is_charging
)
@property
def fan_speed(self) -> str | None:
"""Return the fan speed of the vacuum cleaner."""

View File

@ -1,4 +1,55 @@
# serializer version: 1
# name: test_legacy_sensors[123][sensor.e1234567890000000003_battery:entity-registry]
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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.e1234567890000000003_battery',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown',
'original_name': 'Battery',
'platform': 'ecovacs',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'E1234567890000000003_battery_status',
'unit_of_measurement': '%',
})
# ---
# name: test_legacy_sensors[123][sensor.e1234567890000000003_battery:state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'battery',
'friendly_name': 'E1234567890000000003 Battery',
'icon': 'mdi:battery-unknown',
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'sensor.e1234567890000000003_battery',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unavailable',
})
# ---
# name: test_legacy_sensors[123][sensor.e1234567890000000003_filter_lifespan:entity-registry]
EntityRegistryEntrySnapshot({
'aliases': set({
@ -148,6 +199,7 @@
# ---
# name: test_legacy_sensors[123][states]
list([
'sensor.e1234567890000000003_battery',
'sensor.e1234567890000000003_main_brush_lifespan',
'sensor.e1234567890000000003_side_brush_lifespan',
'sensor.e1234567890000000003_filter_lifespan',

View File

@ -107,7 +107,7 @@ async def test_devices_in_dr(
[
("yna5x1", 26),
("5xu9h3", 25),
("123", 1),
("123", 2),
],
)
async def test_all_entities_loaded(