Basic MQTT vacuum support (#9386)
* Basic MQTT vacuum support * PR feedback * Support for fan_speed and send_command services * Fix configurable topics * Use configurable bools for cleaning/docked/stopped state * Fix language in docstring * PR feedback * Remove duplicate vacuum/state topic defaults * Fix incorrect template for docked value * Move direction like default mqtt platfom/components * fix None on templates * fix tests * fix int * fix tests * ready to merge
This commit is contained in:
parent
0100af0fa6
commit
175b4ae5e0
4 changed files with 701 additions and 3 deletions
|
@ -582,6 +582,7 @@ omit =
|
|||
homeassistant/components/weather/zamg.py
|
||||
homeassistant/components/zeroconf.py
|
||||
homeassistant/components/zwave/util.py
|
||||
homeassistant/components/vacuum/mqtt.py
|
||||
|
||||
|
||||
[report]
|
||||
|
|
|
@ -142,7 +142,7 @@ class DemoVacuum(VacuumDevice):
|
|||
self.schedule_update_ha_state()
|
||||
|
||||
def stop(self, **kwargs):
|
||||
"""Turn the vacuum off."""
|
||||
"""Stop the vacuum."""
|
||||
if self.supported_features & SUPPORT_STOP == 0:
|
||||
return
|
||||
|
||||
|
@ -162,7 +162,7 @@ class DemoVacuum(VacuumDevice):
|
|||
self.schedule_update_ha_state()
|
||||
|
||||
def locate(self, **kwargs):
|
||||
"""Turn the vacuum off."""
|
||||
"""Locate the vacuum (usually by playing a song)."""
|
||||
if self.supported_features & SUPPORT_LOCATE == 0:
|
||||
return
|
||||
|
||||
|
@ -184,7 +184,7 @@ class DemoVacuum(VacuumDevice):
|
|||
self.schedule_update_ha_state()
|
||||
|
||||
def set_fan_speed(self, fan_speed, **kwargs):
|
||||
"""Tell the vacuum to return to its dock."""
|
||||
"""Set the vacuum's fan speed."""
|
||||
if self.supported_features & SUPPORT_FAN_SPEED == 0:
|
||||
return
|
||||
|
||||
|
|
498
homeassistant/components/vacuum/mqtt.py
Normal file
498
homeassistant/components/vacuum/mqtt.py
Normal file
|
@ -0,0 +1,498 @@
|
|||
"""
|
||||
Support for a generic MQTT vacuum.
|
||||
|
||||
For more details about this platform, please refer to the documentation
|
||||
https://home-assistant.io/components/vacuum.mqtt/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.components.mqtt as mqtt
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.vacuum import (
|
||||
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)
|
||||
from homeassistant.const import ATTR_SUPPORTED_FEATURES, CONF_NAME
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.util.icon import icon_for_battery_level
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['mqtt']
|
||||
|
||||
SERVICE_TO_STRING = {
|
||||
SUPPORT_TURN_ON: 'turn_on',
|
||||
SUPPORT_TURN_OFF: 'turn_off',
|
||||
SUPPORT_PAUSE: 'pause',
|
||||
SUPPORT_STOP: 'stop',
|
||||
SUPPORT_RETURN_HOME: 'return_home',
|
||||
SUPPORT_FAN_SPEED: 'fan_speed',
|
||||
SUPPORT_BATTERY: 'battery',
|
||||
SUPPORT_STATUS: 'status',
|
||||
SUPPORT_SEND_COMMAND: 'send_command',
|
||||
SUPPORT_LOCATE: 'locate',
|
||||
SUPPORT_CLEAN_SPOT: 'clean_spot',
|
||||
}
|
||||
|
||||
STRING_TO_SERVICE = {v: k for k, v in SERVICE_TO_STRING.items()}
|
||||
|
||||
|
||||
def services_to_strings(services):
|
||||
"""Convert SUPPORT_* service bitmask to list of service strings."""
|
||||
strings = []
|
||||
for service in SERVICE_TO_STRING:
|
||||
if service & services:
|
||||
strings.append(SERVICE_TO_STRING[service])
|
||||
return strings
|
||||
|
||||
|
||||
def strings_to_services(strings):
|
||||
"""Convert service strings to SUPPORT_* service bitmask."""
|
||||
services = 0
|
||||
for string in strings:
|
||||
services |= STRING_TO_SERVICE[string]
|
||||
return services
|
||||
|
||||
|
||||
DEFAULT_SERVICES = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_STOP |\
|
||||
SUPPORT_RETURN_HOME | SUPPORT_STATUS | SUPPORT_BATTERY |\
|
||||
SUPPORT_CLEAN_SPOT
|
||||
ALL_SERVICES = DEFAULT_SERVICES | SUPPORT_PAUSE | SUPPORT_LOCATE |\
|
||||
SUPPORT_FAN_SPEED | SUPPORT_SEND_COMMAND
|
||||
|
||||
BOOL_TRUE_STRINGS = {'true', '1', 'yes', 'on'}
|
||||
|
||||
CONF_SUPPORTED_FEATURES = ATTR_SUPPORTED_FEATURES
|
||||
CONF_PAYLOAD_TURN_ON = 'payload_turn_on'
|
||||
CONF_PAYLOAD_TURN_OFF = 'payload_turn_off'
|
||||
CONF_PAYLOAD_RETURN_TO_BASE = 'payload_return_to_base'
|
||||
CONF_PAYLOAD_STOP = 'payload_stop'
|
||||
CONF_PAYLOAD_CLEAN_SPOT = 'payload_clean_spot'
|
||||
CONF_PAYLOAD_LOCATE = 'payload_locate'
|
||||
CONF_PAYLOAD_START_PAUSE = 'payload_start_pause'
|
||||
CONF_BATTERY_LEVEL_TOPIC = 'battery_level_topic'
|
||||
CONF_BATTERY_LEVEL_TEMPLATE = 'battery_level_template'
|
||||
CONF_CHARGING_TOPIC = 'charging_topic'
|
||||
CONF_CHARGING_TEMPLATE = 'charging_template'
|
||||
CONF_CLEANING_TOPIC = 'cleaning_topic'
|
||||
CONF_CLEANING_TEMPLATE = 'cleaning_template'
|
||||
CONF_DOCKED_TOPIC = 'docked_topic'
|
||||
CONF_DOCKED_TEMPLATE = 'docked_template'
|
||||
CONF_STATE_TOPIC = 'state_topic'
|
||||
CONF_STATE_TEMPLATE = 'state_template'
|
||||
CONF_FAN_SPEED_TOPIC = 'fan_speed_topic'
|
||||
CONF_FAN_SPEED_TEMPLATE = 'fan_speed_template'
|
||||
CONF_SET_FAN_SPEED_TOPIC = 'set_fan_speed_topic'
|
||||
CONF_FAN_SPEED_LIST = 'fan_speed_list'
|
||||
CONF_SEND_COMMAND_TOPIC = 'send_command_topic'
|
||||
|
||||
DEFAULT_NAME = 'MQTT Vacuum'
|
||||
DEFAULT_RETAIN = False
|
||||
DEFAULT_SERVICE_STRINGS = services_to_strings(DEFAULT_SERVICES)
|
||||
DEFAULT_PAYLOAD_TURN_ON = 'turn_on'
|
||||
DEFAULT_PAYLOAD_TURN_OFF = 'turn_off'
|
||||
DEFAULT_PAYLOAD_RETURN_TO_BASE = 'return_to_base'
|
||||
DEFAULT_PAYLOAD_STOP = 'stop'
|
||||
DEFAULT_PAYLOAD_CLEAN_SPOT = 'clean_spot'
|
||||
DEFAULT_PAYLOAD_LOCATE = 'locate'
|
||||
DEFAULT_PAYLOAD_START_PAUSE = 'start_pause'
|
||||
|
||||
PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_SUPPORTED_FEATURES, default=DEFAULT_SERVICE_STRINGS):
|
||||
vol.All(cv.ensure_list, [vol.In(STRING_TO_SERVICE.keys())]),
|
||||
vol.Optional(mqtt.CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
|
||||
vol.Optional(mqtt.CONF_COMMAND_TOPIC): mqtt.valid_publish_topic,
|
||||
vol.Optional(CONF_PAYLOAD_TURN_ON,
|
||||
default=DEFAULT_PAYLOAD_TURN_ON): cv.string,
|
||||
vol.Optional(CONF_PAYLOAD_TURN_OFF,
|
||||
default=DEFAULT_PAYLOAD_TURN_OFF): cv.string,
|
||||
vol.Optional(CONF_PAYLOAD_RETURN_TO_BASE,
|
||||
default=DEFAULT_PAYLOAD_RETURN_TO_BASE): cv.string,
|
||||
vol.Optional(CONF_PAYLOAD_STOP,
|
||||
default=DEFAULT_PAYLOAD_STOP): cv.string,
|
||||
vol.Optional(CONF_PAYLOAD_CLEAN_SPOT,
|
||||
default=DEFAULT_PAYLOAD_CLEAN_SPOT): cv.string,
|
||||
vol.Optional(CONF_PAYLOAD_LOCATE,
|
||||
default=DEFAULT_PAYLOAD_LOCATE): cv.string,
|
||||
vol.Optional(CONF_PAYLOAD_START_PAUSE,
|
||||
default=DEFAULT_PAYLOAD_START_PAUSE): cv.string,
|
||||
vol.Optional(CONF_BATTERY_LEVEL_TOPIC): mqtt.valid_publish_topic,
|
||||
vol.Optional(CONF_BATTERY_LEVEL_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_CHARGING_TOPIC): mqtt.valid_publish_topic,
|
||||
vol.Optional(CONF_CHARGING_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_CLEANING_TOPIC): mqtt.valid_publish_topic,
|
||||
vol.Optional(CONF_CLEANING_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_DOCKED_TOPIC): mqtt.valid_publish_topic,
|
||||
vol.Optional(CONF_DOCKED_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_STATE_TOPIC): mqtt.valid_publish_topic,
|
||||
vol.Optional(CONF_STATE_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_FAN_SPEED_TOPIC): mqtt.valid_publish_topic,
|
||||
vol.Optional(CONF_FAN_SPEED_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_SET_FAN_SPEED_TOPIC): mqtt.valid_publish_topic,
|
||||
vol.Optional(CONF_FAN_SPEED_LIST, default=[]):
|
||||
vol.All(cv.ensure_list, [cv.string]),
|
||||
vol.Optional(CONF_SEND_COMMAND_TOPIC): mqtt.valid_publish_topic,
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
"""Set up the vacuum."""
|
||||
name = config.get(CONF_NAME)
|
||||
supported_feature_strings = config.get(CONF_SUPPORTED_FEATURES)
|
||||
supported_features = strings_to_services(supported_feature_strings)
|
||||
|
||||
qos = config.get(mqtt.CONF_QOS)
|
||||
retain = config.get(mqtt.CONF_RETAIN)
|
||||
|
||||
command_topic = config.get(mqtt.CONF_COMMAND_TOPIC)
|
||||
payload_turn_on = config.get(CONF_PAYLOAD_TURN_ON)
|
||||
payload_turn_off = config.get(CONF_PAYLOAD_TURN_OFF)
|
||||
payload_return_to_base = config.get(CONF_PAYLOAD_RETURN_TO_BASE)
|
||||
payload_stop = config.get(CONF_PAYLOAD_STOP)
|
||||
payload_clean_spot = config.get(CONF_PAYLOAD_CLEAN_SPOT)
|
||||
payload_locate = config.get(CONF_PAYLOAD_LOCATE)
|
||||
payload_start_pause = config.get(CONF_PAYLOAD_START_PAUSE)
|
||||
|
||||
battery_level_topic = config.get(CONF_BATTERY_LEVEL_TOPIC)
|
||||
battery_level_template = config.get(CONF_BATTERY_LEVEL_TEMPLATE)
|
||||
if battery_level_template:
|
||||
battery_level_template.hass = hass
|
||||
|
||||
charging_topic = config.get(CONF_CHARGING_TOPIC)
|
||||
charging_template = config.get(CONF_CHARGING_TEMPLATE)
|
||||
if charging_template:
|
||||
charging_template.hass = hass
|
||||
|
||||
cleaning_topic = config.get(CONF_CLEANING_TOPIC)
|
||||
cleaning_template = config.get(CONF_CLEANING_TEMPLATE)
|
||||
if cleaning_template:
|
||||
cleaning_template.hass = hass
|
||||
|
||||
docked_topic = config.get(CONF_DOCKED_TOPIC)
|
||||
docked_template = config.get(CONF_DOCKED_TEMPLATE)
|
||||
if docked_template:
|
||||
docked_template.hass = hass
|
||||
|
||||
fan_speed_topic = config.get(CONF_FAN_SPEED_TOPIC)
|
||||
fan_speed_template = config.get(CONF_FAN_SPEED_TEMPLATE)
|
||||
if fan_speed_template:
|
||||
fan_speed_template.hass = hass
|
||||
|
||||
set_fan_speed_topic = config.get(CONF_SET_FAN_SPEED_TOPIC)
|
||||
fan_speed_list = config.get(CONF_FAN_SPEED_LIST)
|
||||
|
||||
send_command_topic = config.get(CONF_SEND_COMMAND_TOPIC)
|
||||
|
||||
async_add_devices([
|
||||
MqttVacuum(
|
||||
name, supported_features, qos, retain, command_topic,
|
||||
payload_turn_on, payload_turn_off, payload_return_to_base,
|
||||
payload_stop, payload_clean_spot, payload_locate,
|
||||
payload_start_pause, battery_level_topic, battery_level_template,
|
||||
charging_topic, charging_template, cleaning_topic,
|
||||
cleaning_template, docked_topic, docked_template, fan_speed_topic,
|
||||
fan_speed_template, set_fan_speed_topic, fan_speed_list,
|
||||
send_command_topic
|
||||
),
|
||||
])
|
||||
|
||||
|
||||
class MqttVacuum(VacuumDevice):
|
||||
"""Representation of a MQTT-controlled vacuum."""
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
def __init__(
|
||||
self, name, supported_features, qos, retain, command_topic,
|
||||
payload_turn_on, payload_turn_off, payload_return_to_base,
|
||||
payload_stop, payload_clean_spot, payload_locate,
|
||||
payload_start_pause, battery_level_topic, battery_level_template,
|
||||
charging_topic, charging_template, cleaning_topic,
|
||||
cleaning_template, docked_topic, docked_template, fan_speed_topic,
|
||||
fan_speed_template, set_fan_speed_topic, fan_speed_list,
|
||||
send_command_topic):
|
||||
"""Initialize the vacuum."""
|
||||
self._name = name
|
||||
self._supported_features = supported_features
|
||||
self._qos = qos
|
||||
self._retain = retain
|
||||
|
||||
self._command_topic = command_topic
|
||||
self._payload_turn_on = payload_turn_on
|
||||
self._payload_turn_off = payload_turn_off
|
||||
self._payload_return_to_base = payload_return_to_base
|
||||
self._payload_stop = payload_stop
|
||||
self._payload_clean_spot = payload_clean_spot
|
||||
self._payload_locate = payload_locate
|
||||
self._payload_start_pause = payload_start_pause
|
||||
|
||||
self._battery_level_topic = battery_level_topic
|
||||
self._battery_level_template = battery_level_template
|
||||
|
||||
self._charging_topic = charging_topic
|
||||
self._charging_template = charging_template
|
||||
|
||||
self._cleaning_topic = cleaning_topic
|
||||
self._cleaning_template = cleaning_template
|
||||
|
||||
self._docked_topic = docked_topic
|
||||
self._docked_template = docked_template
|
||||
|
||||
self._fan_speed_topic = fan_speed_topic
|
||||
self._fan_speed_template = fan_speed_template
|
||||
|
||||
self._set_fan_speed_topic = set_fan_speed_topic
|
||||
self._fan_speed_list = fan_speed_list
|
||||
self._send_command_topic = send_command_topic
|
||||
|
||||
self._cleaning = False
|
||||
self._charging = False
|
||||
self._docked = False
|
||||
self._status = 'Unknown'
|
||||
self._battery_level = 0
|
||||
self._fan_speed = 'unknown'
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
"""Subscribe MQTT events.
|
||||
|
||||
This method is a coroutine.
|
||||
"""
|
||||
@callback
|
||||
def message_received(topic, payload, qos):
|
||||
"""Handle new MQTT message."""
|
||||
if topic == self._battery_level_topic and \
|
||||
self._battery_level_template:
|
||||
battery_level = self._battery_level_template\
|
||||
.async_render_with_possible_json_value(
|
||||
payload,
|
||||
error_value=None)
|
||||
if battery_level is not None:
|
||||
self._battery_level = int(battery_level)
|
||||
|
||||
if topic == self._charging_topic and self._charging_template:
|
||||
charging = self._charging_template\
|
||||
.async_render_with_possible_json_value(
|
||||
payload,
|
||||
error_value=None)
|
||||
if charging is not None:
|
||||
self._charging = str(charging).lower() in BOOL_TRUE_STRINGS
|
||||
|
||||
if topic == self._cleaning_topic and self._cleaning_template:
|
||||
cleaning = self._cleaning_template \
|
||||
.async_render_with_possible_json_value(
|
||||
payload,
|
||||
error_value=None)
|
||||
if cleaning is not None:
|
||||
self._cleaning = str(cleaning).lower() in BOOL_TRUE_STRINGS
|
||||
|
||||
if topic == self._docked_topic and self._docked_template:
|
||||
docked = self._docked_template \
|
||||
.async_render_with_possible_json_value(
|
||||
payload,
|
||||
error_value=None)
|
||||
if docked is not None:
|
||||
self._docked = str(docked).lower() in BOOL_TRUE_STRINGS
|
||||
|
||||
if self._docked:
|
||||
if self._charging:
|
||||
self._status = "Docked & Charging"
|
||||
else:
|
||||
self._status = "Docked"
|
||||
elif self._cleaning:
|
||||
self._status = "Cleaning"
|
||||
else:
|
||||
self._status = "Stopped"
|
||||
|
||||
if topic == self._fan_speed_topic and self._fan_speed_template:
|
||||
fan_speed = self._fan_speed_template\
|
||||
.async_render_with_possible_json_value(
|
||||
payload,
|
||||
error_value=None)
|
||||
if fan_speed is not None:
|
||||
self._fan_speed = fan_speed
|
||||
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
topics_set = [topic for topic in (self._battery_level_topic,
|
||||
self._charging_topic,
|
||||
self._cleaning_topic,
|
||||
self._docked_topic,
|
||||
self._fan_speed_topic) if topic]
|
||||
for topic in topics_set:
|
||||
yield from self.hass.components.mqtt.async_subscribe(
|
||||
topic, message_received, self._qos)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the vacuum."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon for the vacuum."""
|
||||
return DEFAULT_ICON
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed for an MQTT vacuum."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if vacuum is on."""
|
||||
return self._cleaning
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
"""Return a status string for the vacuum."""
|
||||
if self.supported_features & SUPPORT_STATUS == 0:
|
||||
return
|
||||
|
||||
return self._status
|
||||
|
||||
@property
|
||||
def fan_speed(self):
|
||||
"""Return the status of the vacuum."""
|
||||
if self.supported_features & SUPPORT_FAN_SPEED == 0:
|
||||
return
|
||||
|
||||
return self._fan_speed
|
||||
|
||||
@property
|
||||
def fan_speed_list(self):
|
||||
"""Return the status of the vacuum."""
|
||||
if self.supported_features & SUPPORT_FAN_SPEED == 0:
|
||||
return []
|
||||
return self._fan_speed_list
|
||||
|
||||
@property
|
||||
def battery_level(self):
|
||||
"""Return the status of the vacuum."""
|
||||
if self.supported_features & SUPPORT_BATTERY == 0:
|
||||
return
|
||||
|
||||
return max(0, min(100, self._battery_level))
|
||||
|
||||
@property
|
||||
def battery_icon(self):
|
||||
"""Return the battery icon for the vacuum cleaner."""
|
||||
if self.supported_features & SUPPORT_BATTERY == 0:
|
||||
return
|
||||
|
||||
return icon_for_battery_level(
|
||||
battery_level=self.battery_level, charging=self._charging)
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
return self._supported_features
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_turn_on(self, **kwargs):
|
||||
"""Turn the vacuum on."""
|
||||
if self.supported_features & SUPPORT_TURN_ON == 0:
|
||||
return
|
||||
|
||||
mqtt.async_publish(self.hass, self._command_topic,
|
||||
self._payload_turn_on, self._qos, self._retain)
|
||||
self._status = 'Cleaning'
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_turn_off(self, **kwargs):
|
||||
"""Turn the vacuum off."""
|
||||
if self.supported_features & SUPPORT_TURN_OFF == 0:
|
||||
return
|
||||
|
||||
mqtt.async_publish(self.hass, self._command_topic,
|
||||
self._payload_turn_off, self._qos, self._retain)
|
||||
self._status = 'Turning Off'
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_stop(self, **kwargs):
|
||||
"""Stop the vacuum."""
|
||||
if self.supported_features & SUPPORT_STOP == 0:
|
||||
return
|
||||
|
||||
mqtt.async_publish(self.hass, self._command_topic, self._payload_stop,
|
||||
self._qos, self._retain)
|
||||
self._status = 'Stopping the current task'
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_clean_spot(self, **kwargs):
|
||||
"""Perform a spot clean-up."""
|
||||
if self.supported_features & SUPPORT_CLEAN_SPOT == 0:
|
||||
return
|
||||
|
||||
mqtt.async_publish(self.hass, self._command_topic,
|
||||
self._payload_clean_spot, self._qos, self._retain)
|
||||
self._status = "Cleaning spot"
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_locate(self, **kwargs):
|
||||
"""Locate the vacuum (usually by playing a song)."""
|
||||
if self.supported_features & SUPPORT_LOCATE == 0:
|
||||
return
|
||||
|
||||
mqtt.async_publish(self.hass, self._command_topic,
|
||||
self._payload_locate, self._qos, self._retain)
|
||||
self._status = "Hi, I'm over here!"
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_start_pause(self, **kwargs):
|
||||
"""Start, pause or resume the cleaning task."""
|
||||
if self.supported_features & SUPPORT_PAUSE == 0:
|
||||
return
|
||||
|
||||
mqtt.async_publish(self.hass, self._command_topic,
|
||||
self._payload_start_pause, self._qos, self._retain)
|
||||
self._status = 'Pausing/Resuming cleaning...'
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_return_to_base(self, **kwargs):
|
||||
"""Tell the vacuum to return to its dock."""
|
||||
if self.supported_features & SUPPORT_RETURN_HOME == 0:
|
||||
return
|
||||
|
||||
mqtt.async_publish(self.hass, self._command_topic,
|
||||
self._payload_return_to_base, self._qos,
|
||||
self._retain)
|
||||
self._status = 'Returning home...'
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_set_fan_speed(self, fan_speed, **kwargs):
|
||||
"""Set fan speed."""
|
||||
if self.supported_features & SUPPORT_FAN_SPEED == 0:
|
||||
return
|
||||
if not self._fan_speed_list or fan_speed not in self._fan_speed_list:
|
||||
return
|
||||
|
||||
mqtt.async_publish(
|
||||
self.hass, self._set_fan_speed_topic, fan_speed, self._qos,
|
||||
self._retain)
|
||||
self._status = "Setting fan to {}...".format(fan_speed)
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_send_command(self, command, params=None, **kwargs):
|
||||
"""Send a command to a vacuum cleaner."""
|
||||
if self.supported_features & SUPPORT_SEND_COMMAND == 0:
|
||||
return
|
||||
|
||||
mqtt.async_publish(
|
||||
self.hass, self._send_command_topic, command, self._qos,
|
||||
self._retain)
|
||||
self._status = "Sending command {}...".format(command)
|
||||
self.async_schedule_update_ha_state()
|
199
tests/components/vacuum/test_mqtt.py
Normal file
199
tests/components/vacuum/test_mqtt.py
Normal file
|
@ -0,0 +1,199 @@
|
|||
"""The tests for the Demo vacuum platform."""
|
||||
import unittest
|
||||
|
||||
from homeassistant.components import vacuum
|
||||
from homeassistant.components.vacuum import (
|
||||
ATTR_BATTERY_LEVEL, ATTR_BATTERY_ICON, ATTR_STATUS,
|
||||
ATTR_FAN_SPEED, mqtt)
|
||||
from homeassistant.components.mqtt import CONF_COMMAND_TOPIC
|
||||
from homeassistant.const import CONF_PLATFORM, STATE_OFF, STATE_ON, CONF_NAME
|
||||
from homeassistant.setup import setup_component
|
||||
from tests.common import (
|
||||
fire_mqtt_message, get_test_home_assistant, mock_mqtt_component)
|
||||
|
||||
|
||||
class TestVacuumMQTT(unittest.TestCase):
|
||||
"""MQTT vacuum component test class."""
|
||||
|
||||
def setUp(self): # pylint: disable=invalid-name
|
||||
"""Set up things to be run when tests are started."""
|
||||
self.hass = get_test_home_assistant()
|
||||
self.mock_publish = mock_mqtt_component(self.hass)
|
||||
|
||||
self.default_config = {
|
||||
CONF_PLATFORM: 'mqtt',
|
||||
CONF_NAME: 'mqtttest',
|
||||
CONF_COMMAND_TOPIC: 'vacuum/command',
|
||||
mqtt.CONF_SEND_COMMAND_TOPIC: 'vacuum/send_command',
|
||||
mqtt.CONF_BATTERY_LEVEL_TOPIC: 'vacuum/state',
|
||||
mqtt.CONF_BATTERY_LEVEL_TEMPLATE:
|
||||
'{{ value_json.battery_level }}',
|
||||
mqtt.CONF_CHARGING_TOPIC: 'vacuum/state',
|
||||
mqtt.CONF_CHARGING_TEMPLATE: '{{ value_json.charging }}',
|
||||
mqtt.CONF_CLEANING_TOPIC: 'vacuum/state',
|
||||
mqtt.CONF_CLEANING_TEMPLATE: '{{ value_json.cleaning }}',
|
||||
mqtt.CONF_DOCKED_TOPIC: 'vacuum/state',
|
||||
mqtt.CONF_DOCKED_TEMPLATE: '{{ value_json.docked }}',
|
||||
mqtt.CONF_STATE_TOPIC: 'vacuum/state',
|
||||
mqtt.CONF_STATE_TEMPLATE: '{{ value_json.state }}',
|
||||
mqtt.CONF_FAN_SPEED_TOPIC: 'vacuum/state',
|
||||
mqtt.CONF_FAN_SPEED_TEMPLATE: '{{ value_json.fan_speed }}',
|
||||
mqtt.CONF_SET_FAN_SPEED_TOPIC: 'vacuum/set_fan_speed',
|
||||
mqtt.CONF_FAN_SPEED_LIST: ['min', 'medium', 'high', 'max'],
|
||||
}
|
||||
|
||||
def tearDown(self): # pylint: disable=invalid-name
|
||||
"""Stop down everything that was started."""
|
||||
self.hass.stop()
|
||||
|
||||
def test_default_supported_features(self):
|
||||
"""Test that the correct supported features."""
|
||||
self.assertTrue(setup_component(self.hass, vacuum.DOMAIN, {
|
||||
vacuum.DOMAIN: self.default_config,
|
||||
}))
|
||||
entity = self.hass.states.get('vacuum.mqtttest')
|
||||
entity_features = \
|
||||
entity.attributes.get(mqtt.CONF_SUPPORTED_FEATURES, 0)
|
||||
self.assertListEqual(sorted(mqtt.services_to_strings(entity_features)),
|
||||
sorted(['turn_on', 'turn_off', 'stop',
|
||||
'return_home', 'battery', 'status',
|
||||
'clean_spot']))
|
||||
|
||||
def test_all_commands(self):
|
||||
"""Test simple commands to the vacuum."""
|
||||
self.default_config[mqtt.CONF_SUPPORTED_FEATURES] = \
|
||||
mqtt.services_to_strings(mqtt.ALL_SERVICES)
|
||||
|
||||
self.assertTrue(setup_component(self.hass, vacuum.DOMAIN, {
|
||||
vacuum.DOMAIN: self.default_config,
|
||||
}))
|
||||
|
||||
vacuum.turn_on(self.hass, 'vacuum.mqtttest')
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(('vacuum/command', 'turn_on', 0, False),
|
||||
self.mock_publish.mock_calls[-2][1])
|
||||
|
||||
vacuum.turn_off(self.hass, 'vacuum.mqtttest')
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(('vacuum/command', 'turn_off', 0, False),
|
||||
self.mock_publish.mock_calls[-2][1])
|
||||
|
||||
vacuum.stop(self.hass, 'vacuum.mqtttest')
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(('vacuum/command', 'stop', 0, False),
|
||||
self.mock_publish.mock_calls[-2][1])
|
||||
|
||||
vacuum.clean_spot(self.hass, 'vacuum.mqtttest')
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(('vacuum/command', 'clean_spot', 0, False),
|
||||
self.mock_publish.mock_calls[-2][1])
|
||||
|
||||
vacuum.locate(self.hass, 'vacuum.mqtttest')
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(('vacuum/command', 'locate', 0, False),
|
||||
self.mock_publish.mock_calls[-2][1])
|
||||
|
||||
vacuum.start_pause(self.hass, 'vacuum.mqtttest')
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(('vacuum/command', 'start_pause', 0, False),
|
||||
self.mock_publish.mock_calls[-2][1])
|
||||
|
||||
vacuum.return_to_base(self.hass, 'vacuum.mqtttest')
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(('vacuum/command', 'return_to_base', 0, False),
|
||||
self.mock_publish.mock_calls[-2][1])
|
||||
|
||||
vacuum.set_fan_speed(self.hass, 'high', 'vacuum.mqtttest')
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(
|
||||
('vacuum/set_fan_speed', 'high', 0, False),
|
||||
self.mock_publish.mock_calls[-2][1]
|
||||
)
|
||||
|
||||
vacuum.send_command(self.hass, '44 FE 93', entity_id='vacuum.mqtttest')
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(
|
||||
('vacuum/send_command', '44 FE 93', 0, False),
|
||||
self.mock_publish.mock_calls[-2][1]
|
||||
)
|
||||
|
||||
def test_status(self):
|
||||
"""Test status updates from the vacuum."""
|
||||
self.default_config[mqtt.CONF_SUPPORTED_FEATURES] = \
|
||||
mqtt.services_to_strings(mqtt.ALL_SERVICES)
|
||||
|
||||
self.assertTrue(setup_component(self.hass, vacuum.DOMAIN, {
|
||||
vacuum.DOMAIN: self.default_config,
|
||||
}))
|
||||
|
||||
message = """{
|
||||
"battery_level": 54,
|
||||
"cleaning": true,
|
||||
"docked": false,
|
||||
"charging": false,
|
||||
"fan_speed": "max"
|
||||
}"""
|
||||
fire_mqtt_message(self.hass, 'vacuum/state', message)
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get('vacuum.mqtttest')
|
||||
self.assertEqual(STATE_ON, state.state)
|
||||
self.assertEqual(
|
||||
'mdi:battery-50',
|
||||
state.attributes.get(ATTR_BATTERY_ICON)
|
||||
)
|
||||
self.assertEqual(54, state.attributes.get(ATTR_BATTERY_LEVEL))
|
||||
self.assertEqual('max', state.attributes.get(ATTR_FAN_SPEED))
|
||||
|
||||
message = """{
|
||||
"battery_level": 61,
|
||||
"docked": true,
|
||||
"cleaning": false,
|
||||
"charging": true,
|
||||
"fan_speed": "min"
|
||||
}"""
|
||||
|
||||
fire_mqtt_message(self.hass, 'vacuum/state', message)
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get('vacuum.mqtttest')
|
||||
self.assertEqual(STATE_OFF, state.state)
|
||||
self.assertEqual(
|
||||
'mdi:battery-charging-60',
|
||||
state.attributes.get(ATTR_BATTERY_ICON)
|
||||
)
|
||||
self.assertEqual(61, state.attributes.get(ATTR_BATTERY_LEVEL))
|
||||
self.assertEqual('min', state.attributes.get(ATTR_FAN_SPEED))
|
||||
|
||||
def test_battery_template(self):
|
||||
"""Test that you can use non-default templates for battery_level."""
|
||||
self.default_config.update({
|
||||
mqtt.CONF_SUPPORTED_FEATURES:
|
||||
mqtt.services_to_strings(mqtt.ALL_SERVICES),
|
||||
mqtt.CONF_BATTERY_LEVEL_TOPIC: "retroroomba/battery_level",
|
||||
mqtt.CONF_BATTERY_LEVEL_TEMPLATE: "{{ value }}"
|
||||
})
|
||||
|
||||
self.assertTrue(setup_component(self.hass, vacuum.DOMAIN, {
|
||||
vacuum.DOMAIN: self.default_config,
|
||||
}))
|
||||
|
||||
fire_mqtt_message(self.hass, 'retroroomba/battery_level', '54')
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get('vacuum.mqtttest')
|
||||
self.assertEqual(54, state.attributes.get(ATTR_BATTERY_LEVEL))
|
||||
self.assertEqual(state.attributes.get(ATTR_BATTERY_ICON),
|
||||
'mdi:battery-50')
|
||||
|
||||
def test_status_invalid_json(self):
|
||||
"""Test to make sure nothing breaks if the vacuum sends bad JSON."""
|
||||
self.default_config[mqtt.CONF_SUPPORTED_FEATURES] = \
|
||||
mqtt.services_to_strings(mqtt.ALL_SERVICES)
|
||||
|
||||
self.assertTrue(setup_component(self.hass, vacuum.DOMAIN, {
|
||||
vacuum.DOMAIN: self.default_config,
|
||||
}))
|
||||
|
||||
fire_mqtt_message(self.hass, 'vacuum/state', '{"asdfasas false}')
|
||||
self.hass.block_till_done()
|
||||
state = self.hass.states.get('vacuum.mqtttest')
|
||||
self.assertEqual(STATE_OFF, state.state)
|
||||
self.assertEqual("Stopped", state.attributes.get(ATTR_STATUS))
|
Loading…
Add table
Reference in a new issue