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:
parent
d8ca04a4bc
commit
c6aaacbb08
@ -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()
|
||||
|
@ -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:
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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."""
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user