1
mirror of https://github.com/home-assistant/core synced 2024-09-18 19:55:20 +02:00

Add new service clean_spot to vacuums (#8862)

* Add new service `clean_spot` to vacuums

    - Add as base component service, with associated support flag to make it optional
    - Implement on Demo vacuum
    - Implement on Xiaomi vacuum
    - Update tests for platforms Demo and Xiaomi
    - Change default icon for vacuums to `mdi:roomba`, but keep the one for the Xiaomi
    - (In a polymer PR: add new service to command toolbar in the 'more-info' card)

* Add `clean_spot` service description

* fix default properties for vacuum component
This commit is contained in:
Eugenio Panadero 2017-08-06 19:23:22 +02:00 committed by Paulus Schoutsen
parent d8ca04a4bc
commit c6aaacbb08
6 changed files with 89 additions and 25 deletions

View File

@ -41,6 +41,7 @@ ATTR_FAN_SPEED_LIST = 'fan_speed_list'
ATTR_PARAMS = 'params'
ATTR_STATUS = 'status'
SERVICE_CLEAN_SPOT = 'clean_spot'
SERVICE_LOCATE = 'locate'
SERVICE_RETURN_TO_BASE = 'return_to_base'
SERVICE_SEND_COMMAND = 'send_command'
@ -67,6 +68,7 @@ SERVICE_TO_METHOD = {
SERVICE_TOGGLE: {'method': 'async_toggle'},
SERVICE_START_PAUSE: {'method': 'async_start_pause'},
SERVICE_RETURN_TO_BASE: {'method': 'async_return_to_base'},
SERVICE_CLEAN_SPOT: {'method': 'async_clean_spot'},
SERVICE_LOCATE: {'method': 'async_locate'},
SERVICE_STOP: {'method': 'async_stop'},
SERVICE_SET_FAN_SPEED: {'method': 'async_set_fan_speed',
@ -76,7 +78,7 @@ SERVICE_TO_METHOD = {
}
DEFAULT_NAME = 'Vacuum cleaner robot'
DEFAULT_ICON = 'mdi:google-circles-group'
DEFAULT_ICON = 'mdi:roomba'
SUPPORT_TURN_ON = 1
SUPPORT_TURN_OFF = 2
@ -88,7 +90,8 @@ SUPPORT_BATTERY = 64
SUPPORT_STATUS = 128
SUPPORT_SEND_COMMAND = 256
SUPPORT_LOCATE = 512
SUPPORT_MAP = 1024
SUPPORT_CLEAN_SPOT = 1024
SUPPORT_MAP = 2048
@bind_hass
@ -126,6 +129,13 @@ def locate(hass, entity_id=None):
hass.services.call(DOMAIN, SERVICE_LOCATE, data)
@bind_hass
def clean_spot(hass, entity_id=None):
"""Tell all or specified vacuum to perform a spot clean-up."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_CLEAN_SPOT, data)
@bind_hass
def return_to_base(hass, entity_id=None):
"""Tell all or specified vacuum to return to base."""
@ -222,12 +232,12 @@ class VacuumDevice(ToggleEntity):
@property
def status(self):
"""Return the status of the vacuum cleaner."""
raise NotImplementedError()
return None
@property
def battery_level(self):
"""Return the battery level of the vacuum cleaner."""
raise NotImplementedError()
return None
@property
def battery_icon(self):
@ -241,7 +251,7 @@ class VacuumDevice(ToggleEntity):
@property
def fan_speed(self):
"""Return the fan speed of the vacuum cleaner."""
raise NotImplementedError()
return None
@property
def fan_speed_list(self):
@ -310,6 +320,17 @@ class VacuumDevice(ToggleEntity):
"""
return self.hass.async_add_job(partial(self.stop, **kwargs))
def clean_spot(self, **kwargs):
"""Perform a spot clean-up."""
raise NotImplementedError()
def async_clean_spot(self, **kwargs):
"""Perform a spot clean-up.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(partial(self.clean_spot, **kwargs))
def locate(self, **kwargs):
"""Locate the vacuum cleaner."""
raise NotImplementedError()

View File

@ -7,10 +7,10 @@ https://home-assistant.io/components/demo/
import logging
from homeassistant.components.vacuum import (
ATTR_CLEANED_AREA, SUPPORT_BATTERY, SUPPORT_FAN_SPEED,
SUPPORT_LOCATE, SUPPORT_PAUSE, SUPPORT_RETURN_HOME, SUPPORT_SEND_COMMAND,
SUPPORT_STATUS, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
VacuumDevice)
ATTR_CLEANED_AREA, DEFAULT_ICON, SUPPORT_BATTERY, SUPPORT_CLEAN_SPOT,
SUPPORT_FAN_SPEED, SUPPORT_LOCATE, SUPPORT_PAUSE, SUPPORT_RETURN_HOME,
SUPPORT_SEND_COMMAND, SUPPORT_STATUS, SUPPORT_STOP, SUPPORT_TURN_OFF,
SUPPORT_TURN_ON, VacuumDevice)
_LOGGER = logging.getLogger(__name__)
@ -25,7 +25,8 @@ SUPPORT_MOST_SERVICES = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_STOP | \
SUPPORT_ALL_SERVICES = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PAUSE | \
SUPPORT_STOP | SUPPORT_RETURN_HOME | \
SUPPORT_FAN_SPEED | SUPPORT_SEND_COMMAND | \
SUPPORT_LOCATE | SUPPORT_STATUS | SUPPORT_BATTERY
SUPPORT_LOCATE | SUPPORT_STATUS | SUPPORT_BATTERY | \
SUPPORT_CLEAN_SPOT
FAN_SPEEDS = ['min', 'medium', 'high', 'max']
DEMO_VACUUM_COMPLETE = '0_Ground_floor'
@ -68,7 +69,7 @@ class DemoVacuum(VacuumDevice):
@property
def icon(self):
"""Return the icon for the vacuum."""
return 'mdi:roomba'
return DEFAULT_ICON
@property
def should_poll(self):
@ -149,6 +150,17 @@ class DemoVacuum(VacuumDevice):
self._status = 'Stopping the current task'
self.schedule_update_ha_state()
def clean_spot(self, **kwargs):
"""Perform a spot clean-up."""
if self.supported_features & SUPPORT_CLEAN_SPOT == 0:
return
self._state = True
self._cleaned_area += 1.32
self._battery_level -= 1
self._status = "Cleaning spot"
self.schedule_update_ha_state()
def locate(self, **kwargs):
"""Turn the vacuum off."""
if self.supported_features & SUPPORT_LOCATE == 0:

View File

@ -46,6 +46,14 @@ return_to_base:
description: Name of the botvac entity.
example: 'vacuum.xiaomi_vacuum_cleaner'
clean_spot:
description: Tell the vacuum cleaner to do a spot clean-up.
fields:
entity_id:
description: Name of the botvac entity.
example: 'vacuum.xiaomi_vacuum_cleaner'
send_command:
description: Send a raw command to the vacuum cleaner.

View File

@ -12,11 +12,10 @@ import os
import voluptuous as vol
from homeassistant.components.vacuum import (
ATTR_CLEANED_AREA, DEFAULT_ICON, DOMAIN, PLATFORM_SCHEMA,
SUPPORT_BATTERY, SUPPORT_FAN_SPEED, SUPPORT_LOCATE, SUPPORT_PAUSE,
SUPPORT_RETURN_HOME, SUPPORT_SEND_COMMAND,
SUPPORT_STATUS, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
VACUUM_SERVICE_SCHEMA, VacuumDevice)
ATTR_CLEANED_AREA, DOMAIN, PLATFORM_SCHEMA, SUPPORT_BATTERY,
SUPPORT_CLEAN_SPOT, SUPPORT_FAN_SPEED, SUPPORT_LOCATE, SUPPORT_PAUSE,
SUPPORT_RETURN_HOME, SUPPORT_SEND_COMMAND, SUPPORT_STATUS, SUPPORT_STOP,
SUPPORT_TURN_OFF, SUPPORT_TURN_ON, VACUUM_SERVICE_SCHEMA, VacuumDevice)
from homeassistant.config import load_yaml_config_file
from homeassistant.const import (
ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_TOKEN, STATE_OFF, STATE_ON)
@ -27,7 +26,7 @@ REQUIREMENTS = ['python-mirobo==0.1.2']
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Xiaomi Vacuum cleaner'
ICON = DEFAULT_ICON
ICON = 'mdi:google-circles-group'
PLATFORM = 'xiaomi'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@ -76,7 +75,7 @@ SERVICE_TO_METHOD = {
SUPPORT_XIAOMI = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PAUSE | \
SUPPORT_STOP | SUPPORT_RETURN_HOME | SUPPORT_FAN_SPEED | \
SUPPORT_SEND_COMMAND | SUPPORT_LOCATE | \
SUPPORT_STATUS | SUPPORT_BATTERY
SUPPORT_STATUS | SUPPORT_BATTERY | SUPPORT_CLEAN_SPOT
@asyncio.coroutine
@ -283,6 +282,13 @@ class MiroboVacuum(VacuumDevice):
if return_home:
self._is_on = False
@asyncio.coroutine
def async_clean_spot(self, **kwargs):
"""Perform a spot clean-up."""
yield from self._try_command(
"Unable to start the vacuum for a spot clean-up: %s",
self._vacuum.spot)
@asyncio.coroutine
def async_locate(self, **kwargs):
"""Locate the vacuum cleaner."""

View File

@ -39,7 +39,7 @@ class TestVacuumDemo(unittest.TestCase):
def test_supported_features(self):
"""Test vacuum supported features."""
state = self.hass.states.get(ENTITY_VACUUM_COMPLETE)
self.assertEqual(1023, state.attributes.get(ATTR_SUPPORTED_FEATURES))
self.assertEqual(2047, state.attributes.get(ATTR_SUPPORTED_FEATURES))
self.assertEqual("Charging", state.attributes.get(ATTR_STATUS))
self.assertEqual(100, state.attributes.get(ATTR_BATTERY_LEVEL))
self.assertEqual("medium", state.attributes.get(ATTR_FAN_SPEED))
@ -141,6 +141,12 @@ class TestVacuumDemo(unittest.TestCase):
state = self.hass.states.get(ENTITY_VACUUM_COMPLETE)
self.assertEqual(FAN_SPEEDS[-1], state.attributes.get(ATTR_FAN_SPEED))
vacuum.clean_spot(self.hass, entity_id=ENTITY_VACUUM_COMPLETE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_VACUUM_COMPLETE)
self.assertIn("spot", state.attributes.get(ATTR_STATUS))
self.assertEqual(STATE_ON, state.state)
def test_unsupported_methods(self):
"""Test service calls for unsupported vacuums."""
self.hass.states.set(ENTITY_VACUUM_NONE, STATE_ON)
@ -189,6 +195,12 @@ class TestVacuumDemo(unittest.TestCase):
self.assertNotEqual(FAN_SPEEDS[-1],
state.attributes.get(ATTR_FAN_SPEED))
vacuum.clean_spot(self.hass, entity_id=ENTITY_VACUUM_BASIC)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_VACUUM_BASIC)
self.assertNotIn("spot", state.attributes.get(ATTR_STATUS))
self.assertEqual(STATE_OFF, state.state)
def test_services(self):
"""Test vacuum services."""
# Test send_command

View File

@ -8,9 +8,9 @@ import pytest
from homeassistant.components.vacuum import (
ATTR_BATTERY_ICON,
ATTR_FAN_SPEED, ATTR_FAN_SPEED_LIST, DOMAIN,
SERVICE_LOCATE, SERVICE_RETURN_TO_BASE, SERVICE_SEND_COMMAND,
SERVICE_SET_FAN_SPEED, SERVICE_START_PAUSE, SERVICE_STOP,
SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON)
SERVICE_CLEAN_SPOT, SERVICE_LOCATE, SERVICE_RETURN_TO_BASE,
SERVICE_SEND_COMMAND, SERVICE_SET_FAN_SPEED, SERVICE_START_PAUSE,
SERVICE_STOP, SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON)
from homeassistant.components.vacuum.xiaomi import (
ATTR_CLEANED_AREA, ATTR_CLEANING_TIME, ATTR_DO_NOT_DISTURB, ATTR_ERROR,
CONF_HOST, CONF_NAME, CONF_TOKEN, PLATFORM,
@ -112,7 +112,7 @@ def test_xiaomi_vacuum_services(hass, caplog, mock_mirobo_is_off):
state = hass.states.get(entity_id)
assert state.state == STATE_OFF
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 1023
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 2047
assert state.attributes.get(ATTR_DO_NOT_DISTURB) == STATE_ON
assert state.attributes.get(ATTR_ERROR) == 'Error message'
assert (state.attributes.get(ATTR_BATTERY_ICON)
@ -159,6 +159,11 @@ def test_xiaomi_vacuum_services(hass, caplog, mock_mirobo_is_off):
assert str(mock_mirobo_is_off.mock_calls[-2]) == 'call.Vacuum().find()'
assert str(mock_mirobo_is_off.mock_calls[-1]) == 'call.Vacuum().status()'
yield from hass.services.async_call(
DOMAIN, SERVICE_CLEAN_SPOT, {}, blocking=True)
assert str(mock_mirobo_is_off.mock_calls[-2]) == 'call.Vacuum().spot()'
assert str(mock_mirobo_is_off.mock_calls[-1]) == 'call.Vacuum().status()'
# Set speed service:
yield from hass.services.async_call(
DOMAIN, SERVICE_SET_FAN_SPEED, {"fan_speed": 60}, blocking=True)
@ -193,7 +198,7 @@ def test_xiaomi_vacuum_services(hass, caplog, mock_mirobo_is_off):
@asyncio.coroutine
def test_xiaomi_vacuum_specific_services(hass, caplog, mock_mirobo_is_on):
def test_xiaomi_specific_services(hass, caplog, mock_mirobo_is_on):
"""Test vacuum supported features."""
entity_name = 'test_vacuum_cleaner_2'
entity_id = '{}.{}'.format(DOMAIN, entity_name)
@ -210,7 +215,7 @@ def test_xiaomi_vacuum_specific_services(hass, caplog, mock_mirobo_is_on):
# Check state attributes
state = hass.states.get(entity_id)
assert state.state == STATE_ON
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 1023
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 2047
assert state.attributes.get(ATTR_DO_NOT_DISTURB) == STATE_OFF
assert state.attributes.get(ATTR_ERROR) is None
assert (state.attributes.get(ATTR_BATTERY_ICON)