Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions homeassistant/components/alexa_devices/quality_scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,14 @@ rules:
docs-known-limitations: todo
docs-supported-devices: done
docs-supported-functions: done
docs-troubleshooting: todo
docs-troubleshooting: done
docs-use-cases: done
dynamic-devices: todo
entity-category: done
entity-device-class: done
entity-disabled-by-default: done
entity-translations: done
exception-translations: todo
exception-translations: done
icon-translations: done
reconfiguration-flow: todo
repair-issues:
Expand Down
51 changes: 49 additions & 2 deletions homeassistant/components/ecovacs/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
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,
)
Expand All @@ -16,7 +18,11 @@
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


Expand Down Expand Up @@ -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]],
Expand All @@ -71,3 +88,33 @@ async def on_event(event: EventT) -> None:
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)
50 changes: 48 additions & 2 deletions homeassistant/components/ecovacs/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
)
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
Expand Down Expand Up @@ -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:
Expand All @@ -242,14 +243,21 @@ async def _add_legacy_entities() -> None:
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(
Expand Down Expand Up @@ -344,6 +352,44 @@ async def on_event(event: ErrorEvent) -> None:
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."""

Expand Down
24 changes: 1 addition & 23 deletions homeassistant/components/ecovacs/vacuum.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
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
Expand Down Expand Up @@ -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
Expand All @@ -89,11 +87,6 @@ async def async_added_to_hass(self) -> None:
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()
Expand Down Expand Up @@ -137,21 +130,6 @@ def activity(self) -> VacuumActivity | None:

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."""
Expand Down
29 changes: 28 additions & 1 deletion homeassistant/components/husqvarna_automower/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from typing import TYPE_CHECKING, Any

from aioautomower.model import (
ExternalReasons,
InactiveReasons,
MowerAttributes,
MowerModes,
Expand Down Expand Up @@ -190,11 +191,37 @@
RestrictedReasons.PARK_OVERRIDE,
RestrictedReasons.SENSOR,
RestrictedReasons.WEEK_SCHEDULE,
ExternalReasons.AMAZON_ALEXA,
ExternalReasons.DEVELOPER_PORTAL,
ExternalReasons.GARDENA_SMART_SYSTEM,
ExternalReasons.GOOGLE_ASSISTANT,
ExternalReasons.HOME_ASSISTANT,
ExternalReasons.IFTTT,
ExternalReasons.IFTTT_APPLETS,
ExternalReasons.IFTTT_CALENDAR_CONNECTION,
ExternalReasons.SMART_ROUTINE,
ExternalReasons.SMART_ROUTINE_FROST_GUARD,
ExternalReasons.SMART_ROUTINE_RAIN_GUARD,
ExternalReasons.SMART_ROUTINE_WILDLIFE_PROTECTION,
]

STATE_NO_WORK_AREA_ACTIVE = "no_work_area_active"


@callback
def _get_restricted_reason(data: MowerAttributes) -> str:
"""Return the restricted reason.

If there is an external reason, return that instead, if it's available.
"""
if (
data.planner.restricted_reason == RestrictedReasons.EXTERNAL
and data.planner.external_reason is not None
):
return data.planner.external_reason
return data.planner.restricted_reason


@callback
def _get_work_area_names(data: MowerAttributes) -> list[str]:
"""Return a list with all work area names."""
Expand Down Expand Up @@ -400,7 +427,7 @@ class AutomowerSensorEntityDescription(SensorEntityDescription):
translation_key="restricted_reason",
device_class=SensorDeviceClass.ENUM,
option_fn=lambda data: RESTRICTED_REASONS,
value_fn=attrgetter("planner.restricted_reason"),
value_fn=_get_restricted_reason,
),
AutomowerSensorEntityDescription(
key="inactive_reason",
Expand Down
26 changes: 19 additions & 7 deletions homeassistant/components/husqvarna_automower/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -242,16 +242,28 @@
"restricted_reason": {
"name": "Restricted reason",
"state": {
"none": "No restrictions",
"week_schedule": "Week schedule",
"park_override": "Park override",
"sensor": "Weather timer",
"all_work_areas_completed": "All work areas completed",
"amazon_alexa": "Amazon Alexa",
"daily_limit": "Daily limit",
"developer_portal": "Developer Portal",
"external": "External",
"fota": "Firmware Over-the-Air update running",
"frost": "Frost",
"all_work_areas_completed": "All work areas completed",
"external": "External",
"not_applicable": "Not applicable"
"gardena_smart_system": "Gardena Smart System",
"google_assistant": "Google Assistant",
"home_assistant": "Home Assistant",
"ifttt_applets": "IFTTT applets",
"ifttt_calendar_connection": "IFTTT calendar connection",
"ifttt": "IFTTT",
"none": "No restrictions",
"not_applicable": "Not applicable",
"park_override": "Park override",
"sensor": "Weather timer",
"smart_routine_frost_guard": "Frost guard",
"smart_routine_rain_guard": "Rain guard",
"smart_routine_wildlife_protection": "Wildlife protection",
"smart_routine": "Generic smart routine",
"week_schedule": "Week schedule"
}
},
"total_charging_time": {
Expand Down
Loading
Loading