mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
Add ecovacs lawn mover (#111673)
This commit is contained in:
parent
515ca2b6f1
commit
69bb827a20
@ -331,8 +331,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/ecoforest/ @pjanuario
|
||||
/homeassistant/components/econet/ @w1ll1am23
|
||||
/tests/components/econet/ @w1ll1am23
|
||||
/homeassistant/components/ecovacs/ @OverloadUT @mib1185 @edenhaus
|
||||
/tests/components/ecovacs/ @OverloadUT @mib1185 @edenhaus
|
||||
/homeassistant/components/ecovacs/ @OverloadUT @mib1185 @edenhaus @augar
|
||||
/tests/components/ecovacs/ @OverloadUT @mib1185 @edenhaus @augar
|
||||
/homeassistant/components/ecowitt/ @pvizeli
|
||||
/tests/components/ecowitt/ @pvizeli
|
||||
/homeassistant/components/efergy/ @tkdrob
|
||||
|
@ -28,6 +28,7 @@ PLATFORMS = [
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.BUTTON,
|
||||
Platform.IMAGE,
|
||||
Platform.LAWN_MOWER,
|
||||
Platform.NUMBER,
|
||||
Platform.SELECT,
|
||||
Platform.SENSOR,
|
||||
|
99
homeassistant/components/ecovacs/lawn_mower.py
Normal file
99
homeassistant/components/ecovacs/lawn_mower.py
Normal file
@ -0,0 +1,99 @@
|
||||
"""Ecovacs mower entity."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from deebot_client.capabilities import MowerCapabilities
|
||||
from deebot_client.device import Device
|
||||
from deebot_client.events import StateEvent
|
||||
from deebot_client.models import CleanAction, State
|
||||
|
||||
from homeassistant.components.lawn_mower import (
|
||||
LawnMowerActivity,
|
||||
LawnMowerEntity,
|
||||
LawnMowerEntityEntityDescription,
|
||||
LawnMowerEntityFeature,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .controller import EcovacsController
|
||||
from .entity import EcovacsEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
_STATE_TO_MOWER_STATE = {
|
||||
State.IDLE: LawnMowerActivity.PAUSED,
|
||||
State.CLEANING: LawnMowerActivity.MOWING,
|
||||
State.RETURNING: LawnMowerActivity.MOWING,
|
||||
State.DOCKED: LawnMowerActivity.DOCKED,
|
||||
State.ERROR: LawnMowerActivity.ERROR,
|
||||
State.PAUSED: LawnMowerActivity.PAUSED,
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Ecovacs mowers."""
|
||||
mowers: list[EcovacsMower] = []
|
||||
controller: EcovacsController = hass.data[DOMAIN][config_entry.entry_id]
|
||||
for device in controller.devices(MowerCapabilities):
|
||||
mowers.append(EcovacsMower(device))
|
||||
_LOGGER.debug("Adding Ecovacs Mowers to Home Assistant: %s", mowers)
|
||||
async_add_entities(mowers)
|
||||
|
||||
|
||||
class EcovacsMower(
|
||||
EcovacsEntity[MowerCapabilities, MowerCapabilities],
|
||||
LawnMowerEntity,
|
||||
):
|
||||
"""Ecovacs Mower."""
|
||||
|
||||
_attr_supported_features = (
|
||||
LawnMowerEntityFeature.DOCK
|
||||
| LawnMowerEntityFeature.PAUSE
|
||||
| LawnMowerEntityFeature.START_MOWING
|
||||
)
|
||||
|
||||
entity_description = LawnMowerEntityEntityDescription(
|
||||
key="mower", translation_key="mower", name=None
|
||||
)
|
||||
|
||||
def __init__(self, device: Device[MowerCapabilities]) -> None:
|
||||
"""Initialize the mower."""
|
||||
capabilities = device.capabilities
|
||||
super().__init__(device, capabilities)
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Set up the event listeners now that hass is ready."""
|
||||
await super().async_added_to_hass()
|
||||
|
||||
async def on_status(event: StateEvent) -> None:
|
||||
self._attr_activity = _STATE_TO_MOWER_STATE[event.state]
|
||||
self.async_write_ha_state()
|
||||
|
||||
self._subscribe(self._capability.state.event, on_status)
|
||||
|
||||
async def _clean_command(self, action: CleanAction) -> None:
|
||||
await self._device.execute_command(
|
||||
self._capability.clean.action.command(action)
|
||||
)
|
||||
|
||||
async def async_start_mowing(self) -> None:
|
||||
"""Resume schedule."""
|
||||
await self._clean_command(CleanAction.START)
|
||||
|
||||
async def async_pause(self) -> None:
|
||||
"""Pauses the mower."""
|
||||
await self._clean_command(CleanAction.PAUSE)
|
||||
|
||||
async def async_dock(self) -> None:
|
||||
"""Parks the mower until next schedule."""
|
||||
await self._device.execute_command(self._capability.charge.execute())
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"domain": "ecovacs",
|
||||
"name": "Ecovacs",
|
||||
"codeowners": ["@OverloadUT", "@mib1185", "@edenhaus"],
|
||||
"codeowners": ["@OverloadUT", "@mib1185", "@edenhaus", "@augar"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/ecovacs",
|
||||
"iot_class": "cloud_push",
|
||||
|
22
tests/components/ecovacs/fixtures/devices/5xu9h3/device.json
Normal file
22
tests/components/ecovacs/fixtures/devices/5xu9h3/device.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"did": "8516fbb1-17f1-4194-0000000",
|
||||
"name": "E1234567890000000002",
|
||||
"class": "5xu9h3",
|
||||
"resource": "cbCt",
|
||||
"company": "eco-ng",
|
||||
"service": {
|
||||
"jmq": "jmq-ngiot-eu.dc.ww.ecouser.net",
|
||||
"mqs": "api-ngiot.dc-as.ww.ecouser.net"
|
||||
},
|
||||
"deviceName": "GOAT",
|
||||
"icon": "https://portal-ww.ecouser.net/api/pim/file/get/63913d74df25d049785c3bd7",
|
||||
"UILogicId": "goat_ww_h_goat",
|
||||
"materialNo": "116-2201-0001",
|
||||
"pid": "0000000",
|
||||
"product_category": "GOATBOT",
|
||||
"model": "GOAT",
|
||||
"nick": "Goat G1",
|
||||
"homeSort": 9999,
|
||||
"status": 1,
|
||||
"ota": true
|
||||
}
|
67
tests/components/ecovacs/snapshots/test_lawn_mower.ambr
Normal file
67
tests/components/ecovacs/snapshots/test_lawn_mower.ambr
Normal file
@ -0,0 +1,67 @@
|
||||
# serializer version: 1
|
||||
# name: test_lawn_mower[5xu9h3][lawn_mower.goat_g1-entity_entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'lawn_mower',
|
||||
'entity_category': None,
|
||||
'entity_id': 'lawn_mower.goat_g1',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'ecovacs',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': <LawnMowerEntityFeature: 7>,
|
||||
'translation_key': 'mower',
|
||||
'unique_id': '8516fbb1-17f1-4194-0000000_mower',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_lawn_mower[5xu9h3][lawn_mower.goat_g1-state]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'lawn_mower',
|
||||
'entity_category': None,
|
||||
'entity_id': 'lawn_mower.goat_g1',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'ecovacs',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': <LawnMowerEntityFeature: 7>,
|
||||
'translation_key': 'mower',
|
||||
'unique_id': '8516fbb1-17f1-4194-0000000_mower',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
@ -121,6 +121,7 @@ async def test_devices_in_dr(
|
||||
("device_fixture", "entities"),
|
||||
[
|
||||
("yna5x1", 25),
|
||||
("5xu9h3", 14),
|
||||
],
|
||||
)
|
||||
async def test_all_entities_loaded(
|
||||
|
119
tests/components/ecovacs/test_lawn_mower.py
Normal file
119
tests/components/ecovacs/test_lawn_mower.py
Normal file
@ -0,0 +1,119 @@
|
||||
"""Tests for Ecovacs lawn mower entities."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from deebot_client.capabilities import MowerCapabilities
|
||||
from deebot_client.command import Command
|
||||
from deebot_client.commands.json import Charge, CleanV2
|
||||
from deebot_client.events import StateEvent
|
||||
from deebot_client.models import CleanAction, State
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.ecovacs.const import DOMAIN
|
||||
from homeassistant.components.ecovacs.controller import EcovacsController
|
||||
from homeassistant.components.lawn_mower import (
|
||||
DOMAIN as PLATFORM_DOMAIN,
|
||||
LawnMowerActivity,
|
||||
)
|
||||
from homeassistant.components.lawn_mower.const import (
|
||||
SERVICE_DOCK,
|
||||
SERVICE_PAUSE,
|
||||
SERVICE_START_MOWING,
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
|
||||
from .util import notify_and_wait
|
||||
|
||||
pytestmark = [pytest.mark.usefixtures("init_integration")]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def platforms() -> Platform | list[Platform]:
|
||||
"""Platforms, which should be loaded during the test."""
|
||||
return Platform.LAWN_MOWER
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("device_fixture"),
|
||||
[
|
||||
"5xu9h3",
|
||||
],
|
||||
)
|
||||
async def test_lawn_mower(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
controller: EcovacsController,
|
||||
) -> None:
|
||||
"""Test lawn mower states."""
|
||||
entity_id = "lawn_mower.goat_g1"
|
||||
assert (state := hass.states.get(entity_id))
|
||||
assert state.state == STATE_UNKNOWN
|
||||
|
||||
assert (entity_entry := entity_registry.async_get(state.entity_id))
|
||||
assert entity_entry == snapshot(name=f"{entity_id}-entity_entry")
|
||||
assert entity_entry.device_id
|
||||
|
||||
device = next(controller.devices(MowerCapabilities))
|
||||
|
||||
assert (device_entry := device_registry.async_get(entity_entry.device_id))
|
||||
assert device_entry.identifiers == {(DOMAIN, device.device_info["did"])}
|
||||
|
||||
event_bus = device.events
|
||||
await notify_and_wait(hass, event_bus, StateEvent(State.CLEANING))
|
||||
|
||||
assert (state := hass.states.get(state.entity_id))
|
||||
assert entity_entry == snapshot(name=f"{entity_id}-state")
|
||||
assert state.state == LawnMowerActivity.MOWING
|
||||
|
||||
await notify_and_wait(hass, event_bus, StateEvent(State.DOCKED))
|
||||
|
||||
assert (state := hass.states.get(state.entity_id))
|
||||
assert state.state == LawnMowerActivity.DOCKED
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MowerTestCase:
|
||||
"""Mower test."""
|
||||
|
||||
command: Command
|
||||
service_name: str
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("device_fixture", "entity_id", "tests"),
|
||||
[
|
||||
(
|
||||
"5xu9h3",
|
||||
"lawn_mower.goat_g1",
|
||||
[
|
||||
MowerTestCase(Charge(), SERVICE_DOCK),
|
||||
MowerTestCase(CleanV2(CleanAction.PAUSE), SERVICE_PAUSE),
|
||||
MowerTestCase(CleanV2(CleanAction.START), SERVICE_START_MOWING),
|
||||
],
|
||||
),
|
||||
],
|
||||
ids=["5xu9h3"],
|
||||
)
|
||||
async def test_mover_services(
|
||||
hass: HomeAssistant,
|
||||
controller: EcovacsController,
|
||||
entity_id: list[str],
|
||||
tests: list[MowerTestCase],
|
||||
) -> None:
|
||||
"""Test mover services."""
|
||||
device = next(controller.devices(MowerCapabilities))
|
||||
|
||||
for test in tests:
|
||||
device._execute_command.reset_mock()
|
||||
await hass.services.async_call(
|
||||
PLATFORM_DOMAIN,
|
||||
test.service_name,
|
||||
{ATTR_ENTITY_ID: entity_id},
|
||||
blocking=True,
|
||||
)
|
||||
device._execute_command.assert_called_with(test.command)
|
Loading…
x
Reference in New Issue
Block a user