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
12 changes: 9 additions & 3 deletions homeassistant/components/ecovacs/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from dataclasses import dataclass
from typing import Generic

from deebot_client.capabilities import CapabilityEvent
from deebot_client.capabilities import CapabilityEvent, VacuumCapabilities
from deebot_client.events.water_info import WaterInfoEvent

from homeassistant.components.binary_sensor import (
Expand All @@ -17,7 +17,12 @@

from .const import DOMAIN
from .controller import EcovacsController
from .entity import EcovacsCapabilityEntityDescription, EcovacsDescriptionEntity, EventT
from .entity import (
CapabilityDevice,
EcovacsCapabilityEntityDescription,
EcovacsDescriptionEntity,
EventT,
)
from .util import get_supported_entitites


Expand All @@ -34,6 +39,7 @@ class EcovacsBinarySensorEntityDescription(

ENTITY_DESCRIPTIONS: tuple[EcovacsBinarySensorEntityDescription, ...] = (
EcovacsBinarySensorEntityDescription[WaterInfoEvent](
device_capabilities=VacuumCapabilities,
capability_fn=lambda caps: caps.water,
value_fn=lambda e: e.mop_attached,
key="water_mop_attached",
Expand All @@ -56,7 +62,7 @@ async def async_setup_entry(


class EcovacsBinarySensor(
EcovacsDescriptionEntity[CapabilityEvent[EventT]],
EcovacsDescriptionEntity[CapabilityDevice, CapabilityEvent[EventT]],
BinarySensorEntity,
):
"""Ecovacs binary sensor."""
Expand Down
15 changes: 11 additions & 4 deletions homeassistant/components/ecovacs/button.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
"""Ecovacs button module."""
from dataclasses import dataclass

from deebot_client.capabilities import CapabilityExecute, CapabilityLifeSpan
from deebot_client.capabilities import (
Capabilities,
CapabilityExecute,
CapabilityLifeSpan,
VacuumCapabilities,
)
from deebot_client.events import LifeSpan

from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
Expand All @@ -13,6 +18,7 @@
from .const import DOMAIN, SUPPORTED_LIFESPANS
from .controller import EcovacsController
from .entity import (
CapabilityDevice,
EcovacsCapabilityEntityDescription,
EcovacsDescriptionEntity,
EcovacsEntity,
Expand All @@ -37,6 +43,7 @@ class EcovacsLifespanButtonEntityDescription(ButtonEntityDescription):

ENTITY_DESCRIPTIONS: tuple[EcovacsButtonEntityDescription, ...] = (
EcovacsButtonEntityDescription(
device_capabilities=VacuumCapabilities,
capability_fn=lambda caps: caps.map.relocation if caps.map else None,
key="relocate",
translation_key="relocate",
Expand Down Expand Up @@ -66,7 +73,7 @@ async def async_setup_entry(
entities: list[EcovacsEntity] = get_supported_entitites(
controller, EcovacsButtonEntity, ENTITY_DESCRIPTIONS
)
for device in controller.devices:
for device in controller.devices(Capabilities):
lifespan_capability = device.capabilities.life_span
for description in LIFESPAN_ENTITY_DESCRIPTIONS:
if description.component in lifespan_capability.types:
Expand All @@ -81,7 +88,7 @@ async def async_setup_entry(


class EcovacsButtonEntity(
EcovacsDescriptionEntity[CapabilityExecute],
EcovacsDescriptionEntity[CapabilityDevice, CapabilityExecute],
ButtonEntity,
):
"""Ecovacs button entity."""
Expand All @@ -94,7 +101,7 @@ async def async_press(self) -> None:


class EcovacsResetLifespanButtonEntity(
EcovacsDescriptionEntity[CapabilityLifeSpan],
EcovacsDescriptionEntity[Capabilities, CapabilityLifeSpan],
ButtonEntity,
):
"""Ecovacs reset lifespan button entity."""
Expand Down
18 changes: 13 additions & 5 deletions homeassistant/components/ecovacs/controller.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
"""Controller module."""
from __future__ import annotations

from collections.abc import Mapping
from collections.abc import Generator, Mapping
import logging
import ssl
from typing import Any

from deebot_client.api_client import ApiClient
from deebot_client.authentication import Authenticator, create_rest_config
from deebot_client.capabilities import Capabilities
from deebot_client.const import UNDEFINED, UndefinedType
from deebot_client.device import Device
from deebot_client.exceptions import DeebotError, InvalidAuthenticationError
Expand All @@ -18,7 +19,7 @@
from sucks import EcoVacsAPI, VacBot

from homeassistant.const import CONF_COUNTRY, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryError, ConfigEntryNotReady
from homeassistant.helpers import aiohttp_client
from homeassistant.util.ssl import get_default_no_verify_context
Expand All @@ -39,7 +40,7 @@ class EcovacsController:
def __init__(self, hass: HomeAssistant, config: Mapping[str, Any]) -> None:
"""Initialize controller."""
self._hass = hass
self.devices: list[Device] = []
self._devices: list[Device] = []
self.legacy_devices: list[VacBot] = []
self._device_id = get_client_device_id()
country = config[CONF_COUNTRY]
Expand Down Expand Up @@ -86,7 +87,7 @@ async def initialize(self) -> None:
mqtt_config_verfied = True
device = Device(device_config, self._authenticator)
await device.initialize(self._mqtt)
self.devices.append(device)
self._devices.append(device)
else:
# Legacy device
bot = VacBot(
Expand All @@ -108,9 +109,16 @@ async def initialize(self) -> None:

async def teardown(self) -> None:
"""Disconnect controller."""
for device in self.devices:
for device in self._devices:
await device.teardown()
for legacy_device in self.legacy_devices:
await self._hass.async_add_executor_job(legacy_device.disconnect)
await self._mqtt.disconnect()
await self._authenticator.teardown()

@callback
def devices(self, capability: type[Capabilities]) -> Generator[Device, None, None]:
"""Return generator for devices with a specific capability."""
for device in self._devices:
if isinstance(device.capabilities, capability):
yield device
6 changes: 4 additions & 2 deletions homeassistant/components/ecovacs/diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

from typing import Any

from deebot_client.capabilities import Capabilities

from homeassistant.components.diagnostics import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME, CONF_PASSWORD, CONF_USERNAME
Expand Down Expand Up @@ -31,8 +33,8 @@ async def async_get_config_entry_diagnostics(
}

diag["devices"] = [
async_redact_data(device.device_info.api_device_info, REDACT_DEVICE)
for device in controller.devices
async_redact_data(device.device_info, REDACT_DEVICE)
for device in controller.devices(Capabilities)
]
diag["legacy_devices"] = [
async_redact_data(device.vacuum, REDACT_DEVICE)
Expand Down
32 changes: 18 additions & 14 deletions homeassistant/components/ecovacs/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@

from .const import DOMAIN

CapabilityT = TypeVar("CapabilityT")
CapabilityEntity = TypeVar("CapabilityEntity")
CapabilityDevice = TypeVar("CapabilityDevice", bound=Capabilities)
EventT = TypeVar("EventT", bound=Event)


class EcovacsEntity(Entity, Generic[CapabilityT]):
class EcovacsEntity(Entity, Generic[CapabilityDevice, CapabilityEntity]):
"""Ecovacs entity."""

_attr_should_poll = False
Expand All @@ -29,13 +30,15 @@ class EcovacsEntity(Entity, Generic[CapabilityT]):

def __init__(
self,
device: Device,
capability: CapabilityT,
device: Device[CapabilityDevice],
capability: CapabilityEntity,
**kwargs: Any,
) -> None:
"""Initialize entity."""
super().__init__(**kwargs)
self._attr_unique_id = f"{device.device_info.did}_{self.entity_description.key}"
self._attr_unique_id = (
f"{device.device_info['did']}_{self.entity_description.key}"
)

self._device = device
self._capability = capability
Expand All @@ -46,16 +49,16 @@ def device_info(self) -> DeviceInfo | None:
"""Return device specific attributes."""
device_info = self._device.device_info
info = DeviceInfo(
identifiers={(DOMAIN, device_info.did)},
identifiers={(DOMAIN, device_info["did"])},
manufacturer="Ecovacs",
sw_version=self._device.fw_version,
serial_number=device_info.name,
serial_number=device_info["name"],
)

if nick := device_info.api_device_info.get("nick"):
if nick := device_info.get("nick"):
info["name"] = nick

if model := device_info.api_device_info.get("deviceName"):
if model := device_info.get("deviceName"):
info["model"] = model

if mac := self._device.mac:
Expand Down Expand Up @@ -93,13 +96,13 @@ async def async_update(self) -> None:
self._device.events.request_refresh(event_type)


class EcovacsDescriptionEntity(EcovacsEntity[CapabilityT]):
class EcovacsDescriptionEntity(EcovacsEntity[CapabilityDevice, CapabilityEntity]):
"""Ecovacs entity."""

def __init__(
self,
device: Device,
capability: CapabilityT,
device: Device[CapabilityDevice],
capability: CapabilityEntity,
entity_description: EntityDescription,
**kwargs: Any,
) -> None:
Expand All @@ -111,8 +114,9 @@ def __init__(
@dataclass(kw_only=True, frozen=True)
class EcovacsCapabilityEntityDescription(
EntityDescription,
Generic[CapabilityT],
Generic[CapabilityDevice, CapabilityEntity],
):
"""Ecovacs entity description."""

capability_fn: Callable[[Capabilities], CapabilityT | None]
device_capabilities: type[CapabilityDevice]
capability_fn: Callable[[CapabilityDevice], CapabilityEntity | None]
11 changes: 6 additions & 5 deletions homeassistant/components/ecovacs/image.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Ecovacs image entities."""

from deebot_client.capabilities import CapabilityMap
from deebot_client.capabilities import CapabilityMap, VacuumCapabilities
from deebot_client.device import Device
from deebot_client.events.map import CachedMapInfoEvent, MapChangedEvent

Expand All @@ -23,16 +23,17 @@ async def async_setup_entry(
"""Add entities for passed config_entry in HA."""
controller: EcovacsController = hass.data[DOMAIN][config_entry.entry_id]
entities = []
for device in controller.devices:
if caps := device.capabilities.map:
for device in controller.devices(VacuumCapabilities):
capabilities: VacuumCapabilities = device.capabilities
if caps := capabilities.map:
entities.append(EcovacsMap(device, caps, hass))

if entities:
async_add_entities(entities)


class EcovacsMap(
EcovacsEntity[CapabilityMap],
EcovacsEntity[VacuumCapabilities, CapabilityMap],
ImageEntity,
):
"""Ecovacs map."""
Expand Down Expand Up @@ -72,7 +73,7 @@ async def on_changed(event: MapChangedEvent) -> None:
self._attr_image_last_updated = event.when
self.async_write_ha_state()

self._subscribe(self._capability.chached_info.event, on_info)
self._subscribe(self._capability.cached_info.event, on_info)
self._subscribe(self._capability.changed.event, on_changed)

async def async_update(self) -> None:
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/ecovacs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/ecovacs",
"iot_class": "cloud_push",
"loggers": ["sleekxmppfs", "sucks", "deebot_client"],
"requirements": ["py-sucks==0.9.9", "deebot-client==5.2.2"]
"requirements": ["py-sucks==0.9.9", "deebot-client==6.0.2"]
}
7 changes: 5 additions & 2 deletions homeassistant/components/ecovacs/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from dataclasses import dataclass
from typing import Generic

from deebot_client.capabilities import CapabilitySet
from deebot_client.capabilities import Capabilities, CapabilitySet, VacuumCapabilities
from deebot_client.events import CleanCountEvent, VolumeEvent

from homeassistant.components.number import NumberEntity, NumberEntityDescription
Expand All @@ -17,6 +17,7 @@
from .const import DOMAIN
from .controller import EcovacsController
from .entity import (
CapabilityDevice,
EcovacsCapabilityEntityDescription,
EcovacsDescriptionEntity,
EcovacsEntity,
Expand All @@ -39,6 +40,7 @@ class EcovacsNumberEntityDescription(

ENTITY_DESCRIPTIONS: tuple[EcovacsNumberEntityDescription, ...] = (
EcovacsNumberEntityDescription[VolumeEvent](
device_capabilities=Capabilities,
capability_fn=lambda caps: caps.settings.volume,
value_fn=lambda e: e.volume,
native_max_value_fn=lambda e: e.maximum,
Expand All @@ -51,6 +53,7 @@ class EcovacsNumberEntityDescription(
native_step=1.0,
),
EcovacsNumberEntityDescription[CleanCountEvent](
device_capabilities=VacuumCapabilities,
capability_fn=lambda caps: caps.clean.count,
value_fn=lambda e: e.count,
key="clean_count",
Expand Down Expand Up @@ -79,7 +82,7 @@ async def async_setup_entry(


class EcovacsNumberEntity(
EcovacsDescriptionEntity[CapabilitySet[EventT, int]],
EcovacsDescriptionEntity[CapabilityDevice, CapabilitySet[EventT, int]],
NumberEntity,
):
"""Ecovacs number entity."""
Expand Down
13 changes: 10 additions & 3 deletions homeassistant/components/ecovacs/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from dataclasses import dataclass
from typing import Any, Generic

from deebot_client.capabilities import CapabilitySetTypes
from deebot_client.capabilities import CapabilitySetTypes, VacuumCapabilities
from deebot_client.device import Device
from deebot_client.events import WaterInfoEvent, WorkModeEvent

Expand All @@ -15,7 +15,12 @@

from .const import DOMAIN
from .controller import EcovacsController
from .entity import EcovacsCapabilityEntityDescription, EcovacsDescriptionEntity, EventT
from .entity import (
CapabilityDevice,
EcovacsCapabilityEntityDescription,
EcovacsDescriptionEntity,
EventT,
)
from .util import get_supported_entitites


Expand All @@ -33,6 +38,7 @@ class EcovacsSelectEntityDescription(

ENTITY_DESCRIPTIONS: tuple[EcovacsSelectEntityDescription, ...] = (
EcovacsSelectEntityDescription[WaterInfoEvent](
device_capabilities=VacuumCapabilities,
capability_fn=lambda caps: caps.water,
current_option_fn=lambda e: e.amount.display_name,
options_fn=lambda water: [amount.display_name for amount in water.types],
Expand All @@ -41,6 +47,7 @@ class EcovacsSelectEntityDescription(
entity_category=EntityCategory.CONFIG,
),
EcovacsSelectEntityDescription[WorkModeEvent](
device_capabilities=VacuumCapabilities,
capability_fn=lambda caps: caps.clean.work_mode,
current_option_fn=lambda e: e.mode.display_name,
options_fn=lambda cap: [mode.display_name for mode in cap.types],
Expand All @@ -67,7 +74,7 @@ async def async_setup_entry(


class EcovacsSelectEntity(
EcovacsDescriptionEntity[CapabilitySetTypes[EventT, str]],
EcovacsDescriptionEntity[CapabilityDevice, CapabilitySetTypes[EventT, str]],
SelectEntity,
):
"""Ecovacs select entity."""
Expand Down
Loading