Add stop action to MQTT lawn mower (#181192)

This commit is contained in:
Paul Bottein
2026-09-11 13:11:21 +02:00
committed by GitHub
parent d907b6aea7
commit 9e443bcd8b
3 changed files with 89 additions and 0 deletions
@@ -215,6 +215,8 @@ ABBREVIATIONS = {
"stat_tpl": "state_template",
"stat_val_tpl": "state_value_template",
"step": "step",
"stop_cmd_t": "stop_command_topic",
"stop_cmd_tpl": "stop_command_template",
"strt_mw_cmd_t": "start_mowing_command_topic",
"strt_mw_cmd_tpl": "start_mowing_command_template",
"stype": "subtype",
@@ -48,6 +48,8 @@ CONF_PAUSE_COMMAND_TOPIC = "pause_command_topic"
CONF_PAUSE_COMMAND_TEMPLATE = "pause_command_template"
CONF_START_MOWING_COMMAND_TOPIC = "start_mowing_command_topic"
CONF_START_MOWING_COMMAND_TEMPLATE = "start_mowing_command_template"
CONF_STOP_COMMAND_TOPIC = "stop_command_topic"
CONF_STOP_COMMAND_TEMPLATE = "stop_command_template"
DEFAULT_NAME = "MQTT Lawn Mower"
@@ -56,6 +58,7 @@ MQTT_LAWN_MOWER_ATTRIBUTES_BLOCKED: frozenset[str] = frozenset()
FEATURE_DOCK = "dock"
FEATURE_PAUSE = "pause"
FEATURE_START_MOWING = "start_mowing"
FEATURE_STOP = "stop"
PLATFORM_SCHEMA_MODERN = MQTT_BASE_SCHEMA.extend(
{
@@ -70,6 +73,8 @@ PLATFORM_SCHEMA_MODERN = MQTT_BASE_SCHEMA.extend(
vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
vol.Optional(CONF_START_MOWING_COMMAND_TEMPLATE): cv.template,
vol.Optional(CONF_START_MOWING_COMMAND_TOPIC): valid_publish_topic,
vol.Optional(CONF_STOP_COMMAND_TEMPLATE): cv.template,
vol.Optional(CONF_STOP_COMMAND_TOPIC): valid_publish_topic,
},
).extend(MQTT_ENTITY_COMMON_SCHEMA.schema)
@@ -130,6 +135,9 @@ class MqttLawnMower(MqttEntity, LawnMowerEntity, RestoreEntity):
CONF_START_MOWING_COMMAND_TOPIC
]
supported_features |= LawnMowerEntityFeature.START_MOWING
if CONF_STOP_COMMAND_TOPIC in config:
self._command_topics[FEATURE_STOP] = config[CONF_STOP_COMMAND_TOPIC]
supported_features |= LawnMowerEntityFeature.STOP
self._attr_supported_features = supported_features
self._command_templates = {}
self._command_templates[FEATURE_DOCK] = MqttCommandTemplate(
@@ -141,6 +149,9 @@ class MqttLawnMower(MqttEntity, LawnMowerEntity, RestoreEntity):
self._command_templates[FEATURE_START_MOWING] = MqttCommandTemplate(
config.get(CONF_START_MOWING_COMMAND_TEMPLATE), entity=self
).async_render
self._command_templates[FEATURE_STOP] = MqttCommandTemplate(
config.get(CONF_STOP_COMMAND_TEMPLATE), entity=self
).async_render
@callback
def _message_received(self, msg: ReceiveMessage) -> None:
@@ -212,3 +223,8 @@ class MqttLawnMower(MqttEntity, LawnMowerEntity, RestoreEntity):
async def async_pause(self) -> None:
"""Pause the lawn mower."""
await self._async_operate("pause", LawnMowerActivity.PAUSED)
@override
async def async_stop(self) -> None:
"""Stop the lawn mower."""
await self._async_operate("stop", LawnMowerActivity.IDLE)
+71
View File
@@ -13,6 +13,7 @@ from homeassistant.components.lawn_mower import (
SERVICE_DOCK,
SERVICE_PAUSE,
SERVICE_START_MOWING,
SERVICE_STOP,
LawnMowerEntityFeature,
)
from homeassistant.components.mqtt.const import DOMAIN
@@ -61,6 +62,7 @@ DEFAULT_FEATURES = (
LawnMowerEntityFeature.START_MOWING
| LawnMowerEntityFeature.PAUSE
| LawnMowerEntityFeature.DOCK
| LawnMowerEntityFeature.STOP
)
DEFAULT_CONFIG = {
@@ -70,6 +72,7 @@ DEFAULT_CONFIG = {
"dock_command_topic": "dock-test-topic",
"pause_command_topic": "pause-test-topic",
"start_mowing_command_topic": "start_mowing-test-topic",
"stop_command_topic": "stop-test-topic",
}
}
}
@@ -111,6 +114,13 @@ async def test_run_lawn_mower_setup_and_state_updates(
state = hass.states.get("lawn_mower.test_lawn_mower")
assert state.state == "returning"
async_fire_mqtt_message(hass, "test/lawn_mower_stat", "idle")
await hass.async_block_till_done()
state = hass.states.get("lawn_mower.test_lawn_mower")
assert state.state == "idle"
async_fire_mqtt_message(hass, "test/lawn_mower_stat", "docked")
await hass.async_block_till_done()
@@ -157,6 +167,17 @@ async def test_run_lawn_mower_setup_and_state_updates(
},
LawnMowerEntityFeature.START_MOWING | LawnMowerEntityFeature.DOCK,
),
(
{
DOMAIN: {
lawn_mower.DOMAIN: {
"stop_command_topic": "stop-test-topic",
"name": "test",
}
}
},
LawnMowerEntityFeature.STOP,
),
],
)
async def test_supported_features(
@@ -285,6 +306,20 @@ async def test_run_lawn_mower_service_optimistic(
state = hass.states.get("lawn_mower.test")
assert state.state == "docked"
await hass.services.async_call(
lawn_mower.DOMAIN,
SERVICE_STOP,
{ATTR_ENTITY_ID: "lawn_mower.test"},
blocking=True,
)
mqtt_mock.async_publish.assert_called_once_with(
"stop-test-topic", "stop", 0, False, message_expiry_interval=None
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("lawn_mower.test")
assert state.state == "idle"
@pytest.mark.parametrize(
"hass_config",
@@ -325,6 +360,8 @@ async def test_restore_lawn_mower_from_invalid_state(
"pause_command_template": '{"action": "{{ value }}"}',
"start_mowing_command_topic": "test/lawn_mower_start_mowing_cmd",
"start_mowing_command_template": '{"action": "{{ value }}"}',
"stop_command_topic": "test/lawn_mower_stop_cmd",
"stop_command_template": '{"action": "{{ value }}"}',
}
}
}
@@ -397,6 +434,24 @@ async def test_run_lawn_mower_service_optimistic_with_command_templates(
state = hass.states.get("lawn_mower.test_lawn_mower")
assert state.state == "docked"
await hass.services.async_call(
lawn_mower.DOMAIN,
SERVICE_STOP,
{ATTR_ENTITY_ID: "lawn_mower.test_lawn_mower"},
blocking=True,
)
mqtt_mock.async_publish.assert_called_once_with(
"test/lawn_mower_stop_cmd",
'{"action": "stop"}',
0,
False,
message_expiry_interval=None,
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("lawn_mower.test_lawn_mower")
assert state.state == "idle"
@pytest.mark.parametrize("hass_config", [DEFAULT_CONFIG])
async def test_availability_when_connection_lost(
@@ -675,6 +730,13 @@ async def test_entity_id_update_discovery_update(
"test/lawn_mower_stat",
"dock-test-topic",
),
(
SERVICE_STOP,
"stop",
"idle",
"test/lawn_mower_stat",
"stop-test-topic",
),
],
)
async def test_entity_debug_info_message(
@@ -694,6 +756,7 @@ async def test_entity_debug_info_message(
"dock_command_topic": "dock-test-topic",
"pause_command_topic": "pause-test-topic",
"start_mowing_command_topic": "start_mowing-test-topic",
"stop_command_topic": "stop-test-topic",
"name": "test",
}
}
@@ -770,6 +833,13 @@ async def test_mqtt_payload_not_a_valid_activity_warning(
"dock",
"dock_command_template",
),
(
SERVICE_STOP,
"stop_command_topic",
{},
"stop",
"stop_command_template",
),
],
)
async def test_publishing_with_custom_encoding(
@@ -816,6 +886,7 @@ async def test_reloadable(
("activity_state_topic", "docked", None, "docked"),
("activity_state_topic", "returning", None, "returning"),
("activity_state_topic", "mowing", None, "mowing"),
("activity_state_topic", "idle", None, "idle"),
],
)
async def test_encoding_subscribable_topics(