mirror of
https://github.com/home-assistant/core.git
synced 2026-09-13 12:10:22 +01:00
Add idle state and stop action to lawn mower entity (#181188)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot Autofix powered by AI
parent
86584eab66
commit
f7a35f97c8
@@ -57,7 +57,8 @@ async def async_setup_platform(
|
||||
LawnMowerActivity.DOCKED,
|
||||
LawnMowerEntityFeature.DOCK
|
||||
| LawnMowerEntityFeature.PAUSE
|
||||
| LawnMowerEntityFeature.START_MOWING,
|
||||
| LawnMowerEntityFeature.START_MOWING
|
||||
| LawnMowerEntityFeature.STOP,
|
||||
),
|
||||
DemoLawnMower(
|
||||
"kitchen_sink_mower_006",
|
||||
@@ -67,6 +68,12 @@ async def async_setup_platform(
|
||||
| LawnMowerEntityFeature.PAUSE
|
||||
| LawnMowerEntityFeature.START_MOWING,
|
||||
),
|
||||
DemoLawnMower(
|
||||
"kitchen_sink_mower_007",
|
||||
"Mower can stop",
|
||||
LawnMowerActivity.MOWING,
|
||||
LawnMowerEntityFeature.STOP | LawnMowerEntityFeature.START_MOWING,
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -113,3 +120,9 @@ class DemoLawnMower(LawnMowerEntity):
|
||||
"""Pause mower."""
|
||||
self._attr_activity = LawnMowerActivity.PAUSED
|
||||
self.async_write_ha_state()
|
||||
|
||||
@override
|
||||
async def async_stop(self) -> None:
|
||||
"""Stop mower."""
|
||||
self._attr_activity = LawnMowerActivity.IDLE
|
||||
self.async_write_ha_state()
|
||||
|
||||
@@ -19,6 +19,7 @@ from .const import (
|
||||
SERVICE_DOCK,
|
||||
SERVICE_PAUSE,
|
||||
SERVICE_START_MOWING,
|
||||
SERVICE_STOP,
|
||||
LawnMowerActivity,
|
||||
LawnMowerEntityFeature,
|
||||
)
|
||||
@@ -51,6 +52,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
component.async_register_entity_service(
|
||||
SERVICE_DOCK, None, "async_dock", [LawnMowerEntityFeature.DOCK]
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_STOP, None, "async_stop", [LawnMowerEntityFeature.STOP]
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
@@ -123,3 +127,11 @@ class LawnMowerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
async def async_pause(self) -> None:
|
||||
"""Pause the lawn mower."""
|
||||
await self.hass.async_add_executor_job(self.pause)
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Stop the lawn mower."""
|
||||
raise NotImplementedError
|
||||
|
||||
async def async_stop(self) -> None:
|
||||
"""Stop the lawn mower."""
|
||||
await self.hass.async_add_executor_job(self.stop)
|
||||
|
||||
@@ -10,6 +10,7 @@ CONDITIONS: dict[str, type[Condition]] = {
|
||||
"is_encountering_an_error": make_entity_state_condition(
|
||||
DOMAIN, LawnMowerActivity.ERROR
|
||||
),
|
||||
"is_idle": make_entity_state_condition(DOMAIN, LawnMowerActivity.IDLE),
|
||||
"is_mowing": make_entity_state_condition(DOMAIN, LawnMowerActivity.MOWING),
|
||||
"is_paused": make_entity_state_condition(DOMAIN, LawnMowerActivity.PAUSED),
|
||||
"is_returning": make_entity_state_condition(DOMAIN, LawnMowerActivity.RETURNING),
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
is_docked: *condition_common
|
||||
is_encountering_an_error: *condition_common
|
||||
is_idle: *condition_common
|
||||
is_mowing: *condition_common
|
||||
is_paused: *condition_common
|
||||
is_returning: *condition_common
|
||||
|
||||
@@ -22,6 +22,9 @@ class LawnMowerActivity(StrEnum):
|
||||
RETURNING = "returning"
|
||||
"""Device is returning."""
|
||||
|
||||
IDLE = "idle"
|
||||
"""Device is stopped, but neither docked nor paused."""
|
||||
|
||||
|
||||
class LawnMowerEntityFeature(IntFlag):
|
||||
"""Supported features of the lawn mower entity."""
|
||||
@@ -29,6 +32,7 @@ class LawnMowerEntityFeature(IntFlag):
|
||||
START_MOWING = 1
|
||||
PAUSE = 2
|
||||
DOCK = 4
|
||||
STOP = 8
|
||||
|
||||
|
||||
DOMAIN: Final = "lawn_mower"
|
||||
@@ -36,3 +40,4 @@ DOMAIN: Final = "lawn_mower"
|
||||
SERVICE_START_MOWING = "start_mowing"
|
||||
SERVICE_PAUSE = "pause"
|
||||
SERVICE_DOCK = "dock"
|
||||
SERVICE_STOP = "stop"
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
"is_encountering_an_error": {
|
||||
"condition": "mdi:alert-circle-outline"
|
||||
},
|
||||
"is_idle": {
|
||||
"condition": "mdi:stop"
|
||||
},
|
||||
"is_mowing": {
|
||||
"condition": "mdi:play"
|
||||
},
|
||||
@@ -30,9 +33,15 @@
|
||||
},
|
||||
"start_mowing": {
|
||||
"service": "mdi:play"
|
||||
},
|
||||
"stop": {
|
||||
"service": "mdi:stop"
|
||||
}
|
||||
},
|
||||
"triggers": {
|
||||
"became_idle": {
|
||||
"trigger": "mdi:stop"
|
||||
},
|
||||
"errored": {
|
||||
"trigger": "mdi:alert-circle-outline"
|
||||
},
|
||||
|
||||
@@ -20,3 +20,10 @@ pause:
|
||||
domain: lawn_mower
|
||||
supported_features:
|
||||
- lawn_mower.LawnMowerEntityFeature.PAUSE
|
||||
|
||||
stop:
|
||||
target:
|
||||
entity:
|
||||
domain: lawn_mower
|
||||
supported_features:
|
||||
- lawn_mower.LawnMowerEntityFeature.STOP
|
||||
|
||||
@@ -30,6 +30,18 @@
|
||||
},
|
||||
"name": "Lawn mower is encountering an error"
|
||||
},
|
||||
"is_idle": {
|
||||
"description": "Tests if one or more lawn mowers are idle.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::lawn_mower::common::condition_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::lawn_mower::common::condition_for_name%]"
|
||||
}
|
||||
},
|
||||
"name": "Lawn mower is idle"
|
||||
},
|
||||
"is_mowing": {
|
||||
"description": "Tests if one or more lawn mowers are mowing.",
|
||||
"fields": {
|
||||
@@ -73,6 +85,7 @@
|
||||
"state": {
|
||||
"docked": "Docked",
|
||||
"error": "[%key:common::state::error%]",
|
||||
"idle": "[%key:common::state::idle%]",
|
||||
"mowing": "Mowing",
|
||||
"paused": "[%key:common::state::paused%]",
|
||||
"returning": "Returning"
|
||||
@@ -91,10 +104,26 @@
|
||||
"start_mowing": {
|
||||
"description": "Starts a lawn mower's mowing task.",
|
||||
"name": "Start lawn mower"
|
||||
},
|
||||
"stop": {
|
||||
"description": "Stops a lawn mower's current task.",
|
||||
"name": "Stop lawn mower"
|
||||
}
|
||||
},
|
||||
"title": "Lawn mower",
|
||||
"triggers": {
|
||||
"became_idle": {
|
||||
"description": "Triggers when one or more lawn mowers become idle.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::lawn_mower::common::trigger_behavior_name%]"
|
||||
},
|
||||
"for": {
|
||||
"name": "[%key:component::lawn_mower::common::trigger_for_name%]"
|
||||
}
|
||||
},
|
||||
"name": "Lawn mower became idle"
|
||||
},
|
||||
"errored": {
|
||||
"description": "Triggers when one or more lawn mowers encounter an error.",
|
||||
"fields": {
|
||||
|
||||
@@ -17,6 +17,7 @@ TRIGGERS: dict[str, type[Trigger]] = {
|
||||
"started_returning": make_entity_target_state_trigger(
|
||||
DOMAIN, LawnMowerActivity.RETURNING
|
||||
),
|
||||
"became_idle": make_entity_target_state_trigger(DOMAIN, LawnMowerActivity.IDLE),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -20,3 +20,4 @@ errored: *trigger_common
|
||||
paused_mowing: *trigger_common
|
||||
started_mowing: *trigger_common
|
||||
started_returning: *trigger_common
|
||||
became_idle: *trigger_common
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Mower can do all',
|
||||
<EntityStateAttribute.SUPPORTED_FEATURES: 'supported_features'>: <LawnMowerEntityFeature: 7>,
|
||||
<EntityStateAttribute.SUPPORTED_FEATURES: 'supported_features'>: <LawnMowerEntityFeature: 15>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'lawn_mower.mower_can_do_all',
|
||||
@@ -61,6 +61,18 @@
|
||||
'last_updated': <ANY>,
|
||||
'state': 'returning',
|
||||
}),
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Mower can stop',
|
||||
<EntityStateAttribute.SUPPORTED_FEATURES: 'supported_features'>: <LawnMowerEntityFeature: 9>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'lawn_mower.mower_can_stop',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'mowing',
|
||||
}),
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Mower is paused',
|
||||
|
||||
@@ -11,6 +11,7 @@ from homeassistant.components.lawn_mower import (
|
||||
SERVICE_DOCK,
|
||||
SERVICE_PAUSE,
|
||||
SERVICE_START_MOWING,
|
||||
SERVICE_STOP,
|
||||
LawnMowerActivity,
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, EVENT_STATE_CHANGED, Platform
|
||||
@@ -78,6 +79,12 @@ async def test_states(hass: HomeAssistant, snapshot: SnapshotAssertion) -> None:
|
||||
LawnMowerActivity.RETURNING,
|
||||
LawnMowerActivity.DOCKED,
|
||||
),
|
||||
(
|
||||
"lawn_mower.mower_can_stop",
|
||||
SERVICE_STOP,
|
||||
LawnMowerActivity.MOWING,
|
||||
LawnMowerActivity.IDLE,
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_mower(
|
||||
@@ -109,6 +116,7 @@ async def test_mower(
|
||||
SERVICE_DOCK,
|
||||
SERVICE_START_MOWING,
|
||||
SERVICE_PAUSE,
|
||||
SERVICE_STOP,
|
||||
],
|
||||
)
|
||||
async def test_service_calls_mocked(hass: HomeAssistant, service_call) -> None:
|
||||
|
||||
@@ -32,6 +32,7 @@ async def target_lawn_mowers(hass: HomeAssistant) -> dict[str, list[str]]:
|
||||
_CONDITION_TARGET_SUPPORT: dict[str, TargetSupport] = {
|
||||
"is_docked": TargetSupport.STANDARD,
|
||||
"is_encountering_an_error": TargetSupport.STANDARD,
|
||||
"is_idle": TargetSupport.STANDARD,
|
||||
"is_mowing": TargetSupport.STANDARD,
|
||||
"is_paused": TargetSupport.STANDARD,
|
||||
"is_returning": TargetSupport.STANDARD,
|
||||
@@ -43,6 +44,7 @@ _CONDITION_TARGET_SUPPORT: dict[str, TargetSupport] = {
|
||||
[
|
||||
("lawn_mower.is_docked", {}, True, True),
|
||||
("lawn_mower.is_encountering_an_error", {}, True, True),
|
||||
("lawn_mower.is_idle", {}, True, True),
|
||||
("lawn_mower.is_mowing", {}, True, True),
|
||||
("lawn_mower.is_paused", {}, True, True),
|
||||
("lawn_mower.is_returning", {}, True, True),
|
||||
@@ -87,6 +89,11 @@ def test_condition_target_support() -> None:
|
||||
target_states=[LawnMowerActivity.ERROR],
|
||||
other_states=other_states(LawnMowerActivity.ERROR),
|
||||
),
|
||||
*parametrize_condition_states_any(
|
||||
condition="lawn_mower.is_idle",
|
||||
target_states=[LawnMowerActivity.IDLE],
|
||||
other_states=other_states(LawnMowerActivity.IDLE),
|
||||
),
|
||||
*parametrize_condition_states_any(
|
||||
condition="lawn_mower.is_mowing",
|
||||
target_states=[LawnMowerActivity.MOWING],
|
||||
@@ -144,6 +151,11 @@ async def test_lawn_mower_state_condition_behavior_any(
|
||||
target_states=[LawnMowerActivity.ERROR],
|
||||
other_states=other_states(LawnMowerActivity.ERROR),
|
||||
),
|
||||
*parametrize_condition_states_all(
|
||||
condition="lawn_mower.is_idle",
|
||||
target_states=[LawnMowerActivity.IDLE],
|
||||
other_states=other_states(LawnMowerActivity.IDLE),
|
||||
),
|
||||
*parametrize_condition_states_all(
|
||||
condition="lawn_mower.is_mowing",
|
||||
target_states=[LawnMowerActivity.MOWING],
|
||||
|
||||
@@ -160,6 +160,17 @@ async def test_sync_pause(hass: HomeAssistant) -> None:
|
||||
assert lawn_mower.pause.called
|
||||
|
||||
|
||||
async def test_sync_stop(hass: HomeAssistant) -> None:
|
||||
"""Test if async stop calls sync stop."""
|
||||
lawn_mower = MockLawnMowerEntity()
|
||||
lawn_mower.hass = hass
|
||||
|
||||
lawn_mower.stop = MagicMock()
|
||||
await lawn_mower.async_stop()
|
||||
|
||||
assert lawn_mower.stop.called
|
||||
|
||||
|
||||
async def test_lawn_mower_default(hass: HomeAssistant) -> None:
|
||||
"""Test lawn mower entity with defaults."""
|
||||
lawn_mower = MockLawnMowerEntity()
|
||||
|
||||
@@ -35,6 +35,7 @@ _TRIGGER_TARGET_SUPPORT: dict[str, TargetSupport] = {
|
||||
"paused_mowing": TargetSupport.STANDARD,
|
||||
"started_mowing": TargetSupport.STANDARD,
|
||||
"started_returning": TargetSupport.STANDARD,
|
||||
"became_idle": TargetSupport.STANDARD,
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +47,7 @@ _TRIGGER_TARGET_SUPPORT: dict[str, TargetSupport] = {
|
||||
("lawn_mower.paused_mowing", {}, True, True),
|
||||
("lawn_mower.started_mowing", {}, True, True),
|
||||
("lawn_mower.started_returning", {}, True, True),
|
||||
("lawn_mower.became_idle", {}, True, True),
|
||||
],
|
||||
)
|
||||
async def test_lawn_mower_trigger_options_validation(
|
||||
@@ -102,6 +104,11 @@ def test_trigger_target_support() -> None:
|
||||
target_states=[LawnMowerActivity.RETURNING],
|
||||
other_states=other_states(LawnMowerActivity.RETURNING),
|
||||
),
|
||||
*parametrize_trigger_states(
|
||||
trigger="lawn_mower.became_idle",
|
||||
target_states=[LawnMowerActivity.IDLE],
|
||||
other_states=other_states(LawnMowerActivity.IDLE),
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_lawn_mower_state_trigger_behavior_each(
|
||||
@@ -159,6 +166,11 @@ async def test_lawn_mower_state_trigger_behavior_each(
|
||||
target_states=[LawnMowerActivity.RETURNING],
|
||||
other_states=other_states(LawnMowerActivity.RETURNING),
|
||||
),
|
||||
*parametrize_trigger_states(
|
||||
trigger="lawn_mower.became_idle",
|
||||
target_states=[LawnMowerActivity.IDLE],
|
||||
other_states=other_states(LawnMowerActivity.IDLE),
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_lawn_mower_state_trigger_behavior_first(
|
||||
@@ -216,6 +228,11 @@ async def test_lawn_mower_state_trigger_behavior_first(
|
||||
target_states=[LawnMowerActivity.RETURNING],
|
||||
other_states=other_states(LawnMowerActivity.RETURNING),
|
||||
),
|
||||
*parametrize_trigger_states(
|
||||
trigger="lawn_mower.became_idle",
|
||||
target_states=[LawnMowerActivity.IDLE],
|
||||
other_states=other_states(LawnMowerActivity.IDLE),
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_lawn_mower_state_trigger_behavior_all(
|
||||
|
||||
@@ -741,8 +741,8 @@ async def test_mqtt_payload_not_a_valid_activity_warning(
|
||||
|
||||
assert (
|
||||
"Invalid activity for lawn_mower.test_lawn_mower: 'painting' "
|
||||
"(valid activities: ['error', 'paused', 'mowing', 'docked', 'returning'])"
|
||||
in caplog.text
|
||||
"(valid activities: ['error', 'paused', 'mowing', 'docked', 'returning', "
|
||||
"'idle'])" in caplog.text
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user