Skip to content

Commit

Permalink
Add lawnmower entity (home-assistant#93623)
Browse files Browse the repository at this point in the history
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
  • Loading branch information
3 people authored Aug 21, 2023
1 parent 538de6d commit 82b3ced
Show file tree
Hide file tree
Showing 17 changed files with 686 additions and 2 deletions.
1 change: 1 addition & 0 deletions .core_files.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ base_platforms: &base_platforms
- homeassistant/components/humidifier/**
- homeassistant/components/image/**
- homeassistant/components/image_processing/**
- homeassistant/components/lawn_mower/**
- homeassistant/components/light/**
- homeassistant/components/lock/**
- homeassistant/components/media_player/**
Expand Down
1 change: 1 addition & 0 deletions .strict-typing
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ homeassistant.components.lacrosse.*
homeassistant.components.lacrosse_view.*
homeassistant.components.lametric.*
homeassistant.components.laundrify.*
homeassistant.components.lawn_mower.*
homeassistant.components.lcn.*
homeassistant.components.ld2410_ble.*
homeassistant.components.lidarr.*
Expand Down
2 changes: 2 additions & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,8 @@ build.json @home-assistant/supervisor
/tests/components/launch_library/ @ludeeus @DurgNomis-drol
/homeassistant/components/laundrify/ @xLarry
/tests/components/laundrify/ @xLarry
/homeassistant/components/lawn_mower/ @home-assistant/core
/tests/components/lawn_mower/ @home-assistant/core
/homeassistant/components/lcn/ @alengwenus
/tests/components/lcn/ @alengwenus
/homeassistant/components/ld2410_ble/ @930913
Expand Down
5 changes: 3 additions & 2 deletions homeassistant/components/kitchen_sink/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@


COMPONENTS_WITH_DEMO_PLATFORM = [
Platform.SENSOR,
Platform.LOCK,
Platform.IMAGE,
Platform.LAWN_MOWER,
Platform.LOCK,
Platform.SENSOR,
Platform.WEATHER,
]

Expand Down
100 changes: 100 additions & 0 deletions homeassistant/components/kitchen_sink/lawn_mower.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"""Demo platform that has a couple fake lawn mowers."""
from __future__ import annotations

from homeassistant.components.lawn_mower import (
LawnMowerActivity,
LawnMowerEntity,
LawnMowerEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType


async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Demo lawn mowers."""
async_add_entities(
[
DemoLawnMower(
"kitchen_sink_mower_001",
"Mower can mow",
LawnMowerActivity.DOCKED,
LawnMowerEntityFeature.START_MOWING,
),
DemoLawnMower(
"kitchen_sink_mower_002",
"Mower can dock",
LawnMowerActivity.MOWING,
LawnMowerEntityFeature.DOCK | LawnMowerEntityFeature.START_MOWING,
),
DemoLawnMower(
"kitchen_sink_mower_003",
"Mower can pause",
LawnMowerActivity.DOCKED,
LawnMowerEntityFeature.PAUSE | LawnMowerEntityFeature.START_MOWING,
),
DemoLawnMower(
"kitchen_sink_mower_004",
"Mower can do all",
LawnMowerActivity.DOCKED,
LawnMowerEntityFeature.DOCK
| LawnMowerEntityFeature.PAUSE
| LawnMowerEntityFeature.START_MOWING,
),
DemoLawnMower(
"kitchen_sink_mower_005",
"Mower is paused",
LawnMowerActivity.PAUSED,
LawnMowerEntityFeature.DOCK
| LawnMowerEntityFeature.PAUSE
| LawnMowerEntityFeature.START_MOWING,
),
]
)


async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Everything but the Kitchen Sink config entry."""
await async_setup_platform(hass, {}, async_add_entities)


class DemoLawnMower(LawnMowerEntity):
"""Representation of a Demo lawn mower."""

def __init__(
self,
unique_id: str,
name: str,
activity: LawnMowerActivity,
features: LawnMowerEntityFeature = LawnMowerEntityFeature(0),
) -> None:
"""Initialize the lawn mower."""
self._attr_name = name
self._attr_unique_id = unique_id
self._attr_supported_features = features
self._attr_activity = activity

async def async_start_mowing(self) -> None:
"""Start mowing."""
self._attr_activity = LawnMowerActivity.MOWING
self.async_write_ha_state()

async def async_dock(self) -> None:
"""Start docking."""
self._attr_activity = LawnMowerActivity.DOCKED
self.async_write_ha_state()

async def async_pause(self) -> None:
"""Pause mower."""
self._attr_activity = LawnMowerActivity.PAUSED
self.async_write_ha_state()
120 changes: 120 additions & 0 deletions homeassistant/components/lawn_mower/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
"""The lawn mower integration."""
from __future__ import annotations

from dataclasses import dataclass
from datetime import timedelta
import logging
from typing import final

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.config_validation import ( # noqa: F401
PLATFORM_SCHEMA,
PLATFORM_SCHEMA_BASE,
)
from homeassistant.helpers.entity import Entity, EntityDescription
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.typing import ConfigType

from .const import (
DOMAIN,
SERVICE_DOCK,
SERVICE_PAUSE,
SERVICE_START_MOWING,
LawnMowerActivity,
LawnMowerEntityFeature,
)

SCAN_INTERVAL = timedelta(seconds=60)

_LOGGER = logging.getLogger(__name__)


async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the lawn_mower component."""
component = hass.data[DOMAIN] = EntityComponent[LawnMowerEntity](
_LOGGER, DOMAIN, hass, SCAN_INTERVAL
)
await component.async_setup(config)

component.async_register_entity_service(
SERVICE_START_MOWING,
{},
"async_start_mowing",
[LawnMowerEntityFeature.START_MOWING],
)
component.async_register_entity_service(
SERVICE_PAUSE, {}, "async_pause", [LawnMowerEntityFeature.PAUSE]
)
component.async_register_entity_service(
SERVICE_DOCK, {}, "async_dock", [LawnMowerEntityFeature.DOCK]
)

return True


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up lawn mower devices."""
component: EntityComponent[LawnMowerEntity] = hass.data[DOMAIN]
return await component.async_setup_entry(entry)


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
component: EntityComponent[LawnMowerEntity] = hass.data[DOMAIN]
return await component.async_unload_entry(entry)


@dataclass
class LawnMowerEntityEntityDescription(EntityDescription):
"""A class that describes lawn mower entities."""


class LawnMowerEntity(Entity):
"""Base class for lawn mower entities."""

entity_description: LawnMowerEntityEntityDescription
_attr_activity: LawnMowerActivity | None = None
_attr_supported_features: LawnMowerEntityFeature = LawnMowerEntityFeature(0)

@final
@property
def state(self) -> str | None:
"""Return the current state."""
if (activity := self.activity) is None:
return None
return str(activity)

@property
def activity(self) -> LawnMowerActivity | None:
"""Return the current lawn mower activity."""
return self._attr_activity

@property
def supported_features(self) -> LawnMowerEntityFeature:
"""Flag lawn mower features that are supported."""
return self._attr_supported_features

def start_mowing(self) -> None:
"""Start or resume mowing."""
raise NotImplementedError()

async def async_start_mowing(self) -> None:
"""Start or resume mowing."""
await self.hass.async_add_executor_job(self.start_mowing)

def dock(self) -> None:
"""Dock the mower."""
raise NotImplementedError()

async def async_dock(self) -> None:
"""Dock the mower."""
await self.hass.async_add_executor_job(self.dock)

def pause(self) -> None:
"""Pause the lawn mower."""
raise NotImplementedError()

async def async_pause(self) -> None:
"""Pause the lawn mower."""
await self.hass.async_add_executor_job(self.pause)
33 changes: 33 additions & 0 deletions homeassistant/components/lawn_mower/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""Constants for the lawn mower integration."""
from enum import IntFlag, StrEnum


class LawnMowerActivity(StrEnum):
"""Activity state of lawn mower devices."""

ERROR = "error"
"""Device is in error state, needs assistance."""

PAUSED = "paused"
"""Paused during activity."""

MOWING = "mowing"
"""Device is mowing."""

DOCKED = "docked"
"""Device is docked."""


class LawnMowerEntityFeature(IntFlag):
"""Supported features of the lawn mower entity."""

START_MOWING = 1
PAUSE = 2
DOCK = 4


DOMAIN = "lawn_mower"

SERVICE_START_MOWING = "start_mowing"
SERVICE_PAUSE = "pause"
SERVICE_DOCK = "dock"
8 changes: 8 additions & 0 deletions homeassistant/components/lawn_mower/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"domain": "lawn_mower",
"name": "Lawn Mower",
"codeowners": ["@home-assistant/core"],
"documentation": "https://www.home-assistant.io/integrations/lawn_mower",
"integration_type": "entity",
"quality_scale": "internal"
}
22 changes: 22 additions & 0 deletions homeassistant/components/lawn_mower/services.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Describes the format for available lawn_mower services

start_mowing:
target:
entity:
domain: lawn_mower
supported_features:
- lawn_mower.LawnMowerEntityFeature.START_MOWING

dock:
target:
entity:
domain: lawn_mower
supported_features:
- lawn_mower.LawnMowerEntityFeature.DOCK

pause:
target:
entity:
domain: lawn_mower
supported_features:
- lawn_mower.LawnMowerEntityFeature.PAUSE
28 changes: 28 additions & 0 deletions homeassistant/components/lawn_mower/strings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"title": "Lawn mower",
"entity_component": {
"_": {
"name": "[%key:component::lawn_mower::title%]",
"state": {
"error": "Error",
"paused": "Paused",
"mowing": "Mowing",
"docked": "Docked"
}
}
},
"services": {
"start_mowing": {
"name": "Start mowing",
"description": "Starts the mowing task."
},
"dock": {
"name": "Return to dock",
"description": "Stops the mowing task and returns to the dock."
},
"pause": {
"name": "Pause",
"description": "Pauses the mowing task."
}
}
}
1 change: 1 addition & 0 deletions homeassistant/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class Platform(StrEnum):
HUMIDIFIER = "humidifier"
IMAGE = "image"
IMAGE_PROCESSING = "image_processing"
LAWN_MOWER = "lawn_mower"
LIGHT = "light"
LOCK = "lock"
MAILBOX = "mailbox"
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/helpers/selector.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ def _entity_features() -> dict[str, type[IntFlag]]:
from homeassistant.components.cover import CoverEntityFeature
from homeassistant.components.fan import FanEntityFeature
from homeassistant.components.humidifier import HumidifierEntityFeature
from homeassistant.components.lawn_mower import LawnMowerEntityFeature
from homeassistant.components.light import LightEntityFeature
from homeassistant.components.lock import LockEntityFeature
from homeassistant.components.media_player import MediaPlayerEntityFeature
Expand All @@ -110,6 +111,7 @@ def _entity_features() -> dict[str, type[IntFlag]]:
"CoverEntityFeature": CoverEntityFeature,
"FanEntityFeature": FanEntityFeature,
"HumidifierEntityFeature": HumidifierEntityFeature,
"LawnMowerEntityFeature": LawnMowerEntityFeature,
"LightEntityFeature": LightEntityFeature,
"LockEntityFeature": LockEntityFeature,
"MediaPlayerEntityFeature": MediaPlayerEntityFeature,
Expand Down
10 changes: 10 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1702,6 +1702,16 @@ disallow_untyped_defs = true
warn_return_any = true
warn_unreachable = true

[mypy-homeassistant.components.lawn_mower.*]
check_untyped_defs = true
disallow_incomplete_defs = true
disallow_subclassing_any = true
disallow_untyped_calls = true
disallow_untyped_decorators = true
disallow_untyped_defs = true
warn_return_any = true
warn_unreachable = true

[mypy-homeassistant.components.lcn.*]
check_untyped_defs = true
disallow_incomplete_defs = true
Expand Down
Loading

0 comments on commit 82b3ced

Please sign in to comment.