Ecovacs get_positions service (#118572)

Co-authored-by: Robert Resch <robert@resch.dev>
This commit is contained in:
Lorenzo Monaco 2024-06-07 10:11:49 +02:00 committed by GitHub
parent 9a6902d827
commit 78c7af40ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 143 additions and 1 deletions

View File

@ -140,5 +140,8 @@
"default": "mdi:laser-pointer"
}
}
},
"services": {
"raw_get_positions": "mdi:map-marker-radius-outline"
}
}

View File

@ -0,0 +1,4 @@
raw_get_positions:
target:
entity:
domain: vacuum

View File

@ -226,6 +226,9 @@
},
"vacuum_send_command_params_required": {
"message": "Params are required for the command: {command}"
},
"vacuum_raw_get_positions_not_supported": {
"message": "Getting the positions of the charges and the device itself is not supported"
}
},
"issues": {
@ -261,5 +264,11 @@
"self_hosted": "Self-hosted"
}
}
},
"services": {
"raw_get_positions": {
"name": "Get raw positions",
"description": "Get the raw response for the positions of the chargers and the device itself."
}
}
}

View File

@ -23,8 +23,9 @@ from homeassistant.components.vacuum import (
StateVacuumEntityDescription,
VacuumEntityFeature,
)
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, SupportsResponse
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import entity_platform
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.icon import icon_for_battery_level
from homeassistant.util import slugify
@ -39,6 +40,8 @@ _LOGGER = logging.getLogger(__name__)
ATTR_ERROR = "error"
ATTR_COMPONENT_PREFIX = "component_"
SERVICE_RAW_GET_POSITIONS = "raw_get_positions"
async def async_setup_entry(
hass: HomeAssistant,
@ -46,6 +49,7 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Ecovacs vacuums."""
controller = config_entry.runtime_data
vacuums: list[EcovacsVacuum | EcovacsLegacyVacuum] = [
EcovacsVacuum(device) for device in controller.devices(VacuumCapabilities)
@ -56,6 +60,14 @@ async def async_setup_entry(
_LOGGER.debug("Adding Ecovacs Vacuums to Home Assistant: %s", vacuums)
async_add_entities(vacuums)
platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service(
SERVICE_RAW_GET_POSITIONS,
{},
"async_raw_get_positions",
supports_response=SupportsResponse.ONLY,
)
class EcovacsLegacyVacuum(StateVacuumEntity):
"""Legacy Ecovacs vacuums."""
@ -197,6 +209,15 @@ class EcovacsLegacyVacuum(StateVacuumEntity):
"""Send a command to a vacuum cleaner."""
self.device.run(sucks.VacBotCommand(command, params))
async def async_raw_get_positions(
self,
) -> None:
"""Get bot and chargers positions."""
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="vacuum_raw_get_positions_not_supported",
)
_STATE_TO_VACUUM_STATE = {
State.IDLE: STATE_IDLE,
@ -377,3 +398,19 @@ class EcovacsVacuum(
await self._device.execute_command(
self._capability.custom.set(command, params)
)
async def async_raw_get_positions(
self,
) -> dict[str, Any]:
"""Get bot and chargers positions."""
_LOGGER.debug("async_raw_get_positions")
if not (map_cap := self._capability.map) or not (
position_commands := map_cap.position.get
):
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="vacuum_raw_get_positions_not_supported",
)
return await self._device.execute_command(position_commands[0])

View File

@ -0,0 +1,89 @@
"""Tests for Ecovacs services."""
from collections.abc import Generator
from typing import Any
from unittest.mock import patch
from deebot_client.device import Device
import pytest
from homeassistant.components.ecovacs.const import DOMAIN
from homeassistant.components.ecovacs.vacuum import SERVICE_RAW_GET_POSITIONS
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
pytestmark = [pytest.mark.usefixtures("init_integration")]
@pytest.fixture
def mock_device_execute_response(
data: dict[str, Any],
) -> Generator[dict[str, Any], None, None]:
"""Mock the device execute function response."""
response = {
"ret": "ok",
"resp": {
"header": {
"pri": 1,
"tzm": 480,
"ts": "1717113600000",
"ver": "0.0.1",
"fwVer": "1.2.0",
"hwVer": "0.1.0",
},
"body": {
"code": 0,
"msg": "ok",
"data": data,
},
},
"id": "xRV3",
"payloadType": "j",
}
with patch.object(
Device,
"execute_command",
return_value=response,
):
yield response
@pytest.mark.usefixtures("mock_device_execute_response")
@pytest.mark.parametrize(
"data",
[
{
"deebotPos": {"x": 1, "y": 5, "a": 85},
"chargePos": {"x": 5, "y": 9, "a": 85},
},
{
"deebotPos": {"x": 375, "y": 313, "a": 90},
"chargePos": [{"x": 112, "y": 768, "a": 32}, {"x": 489, "y": 322, "a": 0}],
},
],
)
@pytest.mark.parametrize(
("device_fixture", "entity_id"),
[
("yna5x1", "vacuum.ozmo_950"),
],
ids=["yna5x1"],
)
async def test_get_positions_service(
hass: HomeAssistant,
mock_device_execute_response: dict[str],
entity_id: str,
) -> None:
"""Test that get_positions service response snapshots match."""
vacuum = hass.states.get(entity_id)
assert vacuum
assert await hass.services.async_call(
DOMAIN,
SERVICE_RAW_GET_POSITIONS,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
return_response=True,
) == {entity_id: mock_device_execute_response}