Add lawn mower support to HomeKit (#140438)

Add lawn mower support to homekit
This commit is contained in:
Paul Bottein 2025-03-13 21:27:00 +01:00 committed by GitHub
parent 474d427b87
commit cdead8661d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 114 additions and 0 deletions

View File

@ -15,6 +15,7 @@ from pyhap.service import Service
from pyhap.util import callback as pyhap_callback from pyhap.util import callback as pyhap_callback
from homeassistant.components.cover import CoverDeviceClass, CoverEntityFeature from homeassistant.components.cover import CoverDeviceClass, CoverEntityFeature
from homeassistant.components.lawn_mower import LawnMowerEntityFeature
from homeassistant.components.media_player import MediaPlayerDeviceClass from homeassistant.components.media_player import MediaPlayerDeviceClass
from homeassistant.components.remote import RemoteEntityFeature from homeassistant.components.remote import RemoteEntityFeature
from homeassistant.components.sensor import SensorDeviceClass from homeassistant.components.sensor import SensorDeviceClass
@ -250,6 +251,13 @@ def get_accessory( # noqa: C901
elif state.domain == "vacuum": elif state.domain == "vacuum":
a_type = "Vacuum" a_type = "Vacuum"
elif (
state.domain == "lawn_mower"
and features & LawnMowerEntityFeature.DOCK
and features & LawnMowerEntityFeature.START_MOWING
):
a_type = "LawnMower"
elif state.domain == "remote" and features & RemoteEntityFeature.ACTIVITY: elif state.domain == "remote" and features & RemoteEntityFeature.ACTIVITY:
a_type = "ActivityRemote" a_type = "ActivityRemote"

View File

@ -106,6 +106,7 @@ SUPPORTED_DOMAINS = [
"sensor", "sensor",
"switch", "switch",
"vacuum", "vacuum",
"lawn_mower",
"water_heater", "water_heater",
VALVE_DOMAIN, VALVE_DOMAIN,
] ]
@ -123,6 +124,7 @@ DEFAULT_DOMAINS = [
REMOTE_DOMAIN, REMOTE_DOMAIN,
"switch", "switch",
"vacuum", "vacuum",
"lawn_mower",
"water_heater", "water_heater",
] ]

View File

@ -16,6 +16,12 @@ from pyhap.const import (
from homeassistant.components import button, input_button from homeassistant.components import button, input_button
from homeassistant.components.input_select import ATTR_OPTIONS, SERVICE_SELECT_OPTION from homeassistant.components.input_select import ATTR_OPTIONS, SERVICE_SELECT_OPTION
from homeassistant.components.lawn_mower import (
DOMAIN as LAWN_MOWER_DOMAIN,
SERVICE_DOCK,
SERVICE_START_MOWING,
LawnMowerActivity,
)
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.components.vacuum import ( from homeassistant.components.vacuum import (
DOMAIN as VACUUM_DOMAIN, DOMAIN as VACUUM_DOMAIN,
@ -218,6 +224,29 @@ class Vacuum(Switch):
self.char_on.set_value(current_state) self.char_on.set_value(current_state)
@TYPES.register("LawnMower")
class LawnMower(Switch):
"""Generate a Switch accessory."""
def set_state(self, value: bool) -> None:
"""Move switch state to value if call came from HomeKit."""
_LOGGER.debug("%s: Set switch state to %s", self.entity_id, value)
state = self.hass.states.get(self.entity_id)
assert state
service = SERVICE_START_MOWING if value else SERVICE_DOCK
self.async_call_service(
LAWN_MOWER_DOMAIN, service, {ATTR_ENTITY_ID: self.entity_id}
)
@callback
def async_update_state(self, new_state: State) -> None:
"""Update switch state after state changed."""
current_state = new_state.state in (LawnMowerActivity.MOWING, STATE_ON)
_LOGGER.debug("%s: Set current state to %s", self.entity_id, current_state)
self.char_on.set_value(current_state)
class ValveBase(HomeAccessory): class ValveBase(HomeAccessory):
"""Valve base class.""" """Valve base class."""

View File

@ -12,6 +12,7 @@ from homeassistant.components.homekit.const import (
TYPE_VALVE, TYPE_VALVE,
) )
from homeassistant.components.homekit.type_switches import ( from homeassistant.components.homekit.type_switches import (
LawnMower,
Outlet, Outlet,
SelectSwitch, SelectSwitch,
Switch, Switch,
@ -19,6 +20,13 @@ from homeassistant.components.homekit.type_switches import (
Valve, Valve,
ValveSwitch, ValveSwitch,
) )
from homeassistant.components.lawn_mower import (
DOMAIN as LAWN_MOWER_DOMAIN,
SERVICE_DOCK,
SERVICE_START_MOWING,
LawnMowerActivity,
LawnMowerEntityFeature,
)
from homeassistant.components.select import ATTR_OPTIONS from homeassistant.components.select import ATTR_OPTIONS
from homeassistant.components.vacuum import ( from homeassistant.components.vacuum import (
DOMAIN as VACUUM_DOMAIN, DOMAIN as VACUUM_DOMAIN,
@ -383,6 +391,73 @@ async def test_vacuum_set_state_without_returnhome_and_start_support(
assert events[-1].data[ATTR_VALUE] is None assert events[-1].data[ATTR_VALUE] is None
async def test_lawn_mower_set_state(
hass: HomeAssistant, hk_driver, events: list[Event]
) -> None:
"""Test if Lawn mower accessory and HA are updated accordingly."""
entity_id = "lawn_mower.mower"
hass.states.async_set(
entity_id,
None,
{
ATTR_SUPPORTED_FEATURES: LawnMowerEntityFeature.DOCK
| LawnMowerEntityFeature.START_MOWING
},
)
await hass.async_block_till_done()
acc = LawnMower(hass, hk_driver, "LawnMower", entity_id, 2, None)
acc.run()
await hass.async_block_till_done()
assert acc.aid == 2
assert acc.category == 8 # Switch
assert acc.char_on.value == 0
hass.states.async_set(
entity_id,
LawnMowerActivity.MOWING,
{
ATTR_SUPPORTED_FEATURES: LawnMowerEntityFeature.DOCK
| LawnMowerEntityFeature.START_MOWING
},
)
await hass.async_block_till_done()
assert acc.char_on.value == 1
hass.states.async_set(
entity_id,
LawnMowerActivity.DOCKED,
{
ATTR_SUPPORTED_FEATURES: LawnMowerEntityFeature.DOCK
| LawnMowerEntityFeature.START_MOWING
},
)
await hass.async_block_till_done()
assert acc.char_on.value == 0
# Set from HomeKit
call_turn_on = async_mock_service(hass, LAWN_MOWER_DOMAIN, SERVICE_START_MOWING)
call_turn_off = async_mock_service(hass, LAWN_MOWER_DOMAIN, SERVICE_DOCK)
acc.char_on.client_update_value(1)
await hass.async_block_till_done()
assert acc.char_on.value == 1
assert call_turn_on
assert call_turn_on[0].data[ATTR_ENTITY_ID] == entity_id
assert len(events) == 1
assert events[-1].data[ATTR_VALUE] is None
acc.char_on.client_update_value(0)
await hass.async_block_till_done()
assert acc.char_on.value == 0
assert call_turn_off
assert call_turn_off[0].data[ATTR_ENTITY_ID] == entity_id
assert len(events) == 2
assert events[-1].data[ATTR_VALUE] is None
async def test_reset_switch( async def test_reset_switch(
hass: HomeAssistant, hk_driver, events: list[Event] hass: HomeAssistant, hk_driver, events: list[Event]
) -> None: ) -> None: