Skip to content

Commit 1addb9b

Browse files
committed
fix(ecovacs): split raw_get_positions into vacuum + mower services
`async_register_platform_entity_service` only supports one `entity_domain` per call — registering twice with the same service name silently overrode the vacuum handler, breaking the existing service. Split into two distinct service names: - `ecovacs.raw_get_positions` (vacuums, unchanged, backward-compatible) - `ecovacs.mower_raw_get_positions` (lawn mowers, new) The mower handler still raises `ServiceValidationError` until the optional position capability for mowers lands in deebot-client (see DeebotUniverse/client.py#1588). The test now reflects that user-facing behaviour and will be updated once the lib bump happens. Addresses CI 'Run tests Python 3.14.4 (ecovacs)' fail.
1 parent 02d6bea commit 1addb9b

4 files changed

Lines changed: 58 additions & 18 deletions

File tree

homeassistant/components/ecovacs/services.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
from .const import DOMAIN
99

1010
SERVICE_RAW_GET_POSITIONS = "raw_get_positions"
11+
SERVICE_MOWER_RAW_GET_POSITIONS = "mower_raw_get_positions"
1112

1213

1314
@callback
1415
def async_setup_services(hass: HomeAssistant) -> None:
1516
"""Set up services."""
1617

17-
# Vacuum Services
18+
# Vacuum: existing service, unchanged
1819
service.async_register_platform_entity_service(
1920
hass,
2021
DOMAIN,
@@ -25,14 +26,13 @@ def async_setup_services(hass: HomeAssistant) -> None:
2526
supports_response=SupportsResponse.ONLY,
2627
)
2728

28-
# Lawn Mower Services (Ecovacs GOAT family — same raw position payload as
29-
# vacuums; exposed here so users can capture the response and inspect
30-
# additional fields the cloud may return, e.g. RTK status / satellite count
31-
# for GOAT mowers).
29+
# Lawn Mower: distinct service name (helper only supports one entity_domain
30+
# per service registration — registering twice with the same name would
31+
# silently override the vacuum handler).
3232
service.async_register_platform_entity_service(
3333
hass,
3434
DOMAIN,
35-
SERVICE_RAW_GET_POSITIONS,
35+
SERVICE_MOWER_RAW_GET_POSITIONS,
3636
entity_domain=LAWN_MOWER_DOMAIN,
3737
schema=None,
3838
func="async_raw_get_positions",

homeassistant/components/ecovacs/services.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,9 @@ raw_get_positions:
33
entity:
44
- domain: vacuum
55
integration: ecovacs
6+
7+
mower_raw_get_positions:
8+
target:
9+
entity:
610
- domain: lawn_mower
711
integration: ecovacs

homeassistant/components/ecovacs/strings.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,10 @@
313313
"raw_get_positions": {
314314
"description": "Retrieves a raw response containing the positions of the chargers and the device itself.",
315315
"name": "Get raw positions"
316+
},
317+
"mower_raw_get_positions": {
318+
"description": "Retrieves a raw response containing the positions of the chargers and the mower itself (Ecovacs GOAT family). The payload includes RTK fix quality, satellite counts and signal strength when supported by the mower firmware.",
319+
"name": "Get mower raw positions"
316320
}
317321
}
318322
}

tests/components/ecovacs/test_services.py

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@
88
import pytest
99

1010
from homeassistant.components.ecovacs.const import DOMAIN
11-
from homeassistant.components.ecovacs.services import SERVICE_RAW_GET_POSITIONS
11+
from homeassistant.components.ecovacs.services import (
12+
SERVICE_MOWER_RAW_GET_POSITIONS,
13+
SERVICE_RAW_GET_POSITIONS,
14+
)
1215
from homeassistant.const import ATTR_ENTITY_ID
1316
from homeassistant.core import HomeAssistant
17+
from homeassistant.exceptions import ServiceValidationError
1418

1519
pytestmark = [pytest.mark.usefixtures("init_integration")]
1620

@@ -63,21 +67,16 @@ def mock_device_execute_response(data: dict[str, Any]) -> Generator[dict[str, An
6367
],
6468
)
6569
@pytest.mark.parametrize(
66-
("device_fixture", "entity_id"),
67-
[
68-
("yna5x1", "vacuum.ozmo_950"),
69-
("5xu9h3", "lawn_mower.goat_g1"),
70-
],
71-
ids=["yna5x1", "5xu9h3"],
70+
"device_fixture",
71+
["yna5x1"],
7272
)
73-
async def test_get_positions_service(
73+
async def test_get_positions_service_vacuum(
7474
hass: HomeAssistant,
7575
mock_device_execute_response: dict[str, Any],
76-
entity_id: str,
7776
) -> None:
78-
"""Test that get_positions service response snapshots match."""
79-
entity = hass.states.get(entity_id)
80-
assert entity
77+
"""Vacuum service returns the raw payload."""
78+
entity_id = "vacuum.ozmo_950"
79+
assert hass.states.get(entity_id)
8180

8281
assert await hass.services.async_call(
8382
DOMAIN,
@@ -86,3 +85,36 @@ async def test_get_positions_service(
8685
blocking=True,
8786
return_response=True,
8887
) == {entity_id: mock_device_execute_response}
88+
89+
90+
@pytest.mark.parametrize(
91+
"data",
92+
[{"deebotPos": {"x": 1, "y": 5, "a": 85}, "chargePos": {"x": 5, "y": 9, "a": 85}}],
93+
)
94+
@pytest.mark.parametrize(
95+
"device_fixture",
96+
["5xu9h3"],
97+
)
98+
@pytest.mark.usefixtures("mock_device_execute_response")
99+
async def test_get_positions_service_mower_not_supported(
100+
hass: HomeAssistant,
101+
) -> None:
102+
"""Mower service raises ServiceValidationError when the position capability is absent.
103+
104+
The optional position capability for mowers is being added in
105+
DeebotUniverse/client.py#1588. Until that ships in a deebot-client release and
106+
the integration bumps its pin, ``map.position.get`` is None on the 5xu9h3
107+
fixture and the handler reports ``raw_get_positions_not_supported`` — which
108+
is the user-facing behaviour we ship today.
109+
"""
110+
entity_id = "lawn_mower.goat_g1"
111+
assert hass.states.get(entity_id)
112+
113+
with pytest.raises(ServiceValidationError):
114+
await hass.services.async_call(
115+
DOMAIN,
116+
SERVICE_MOWER_RAW_GET_POSITIONS,
117+
{ATTR_ENTITY_ID: entity_id},
118+
blocking=True,
119+
return_response=True,
120+
)

0 commit comments

Comments
 (0)