Fix bugs in MQTT vacuum (#23048)

This commit is contained in:
Erik Montnemery 2019-04-14 05:29:01 +02:00 committed by Paulus Schoutsen
parent d99637e51b
commit b390de1598
2 changed files with 241 additions and 50 deletions

View file

@ -63,58 +63,57 @@ ALL_SERVICES = DEFAULT_SERVICES | SUPPORT_PAUSE | SUPPORT_LOCATE |\
SUPPORT_FAN_SPEED | SUPPORT_SEND_COMMAND
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_BATTERY_LEVEL_TEMPLATE = 'battery_level_template'
CONF_BATTERY_LEVEL_TOPIC = 'battery_level_topic'
CONF_CHARGING_TEMPLATE = 'charging_template'
CONF_CHARGING_TOPIC = 'charging_topic'
CONF_CLEANING_TEMPLATE = 'cleaning_template'
CONF_CLEANING_TOPIC = 'cleaning_topic'
CONF_DOCKED_TEMPLATE = 'docked_template'
CONF_DOCKED_TOPIC = 'docked_topic'
CONF_ERROR_TEMPLATE = 'error_template'
CONF_ERROR_TOPIC = 'error_topic'
CONF_FAN_SPEED_LIST = 'fan_speed_list'
CONF_FAN_SPEED_TEMPLATE = 'fan_speed_template'
CONF_FAN_SPEED_TOPIC = 'fan_speed_topic'
CONF_PAYLOAD_CLEAN_SPOT = 'payload_clean_spot'
CONF_PAYLOAD_LOCATE = 'payload_locate'
CONF_PAYLOAD_RETURN_TO_BASE = 'payload_return_to_base'
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_ERROR_TOPIC = 'error_topic'
CONF_ERROR_TEMPLATE = 'error_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_PAYLOAD_STOP = 'payload_stop'
CONF_PAYLOAD_TURN_OFF = 'payload_turn_off'
CONF_PAYLOAD_TURN_ON = 'payload_turn_on'
CONF_SEND_COMMAND_TOPIC = 'send_command_topic'
CONF_SET_FAN_SPEED_TOPIC = 'set_fan_speed_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_RETURN_TO_BASE = 'return_to_base'
DEFAULT_PAYLOAD_START_PAUSE = 'start_pause'
DEFAULT_PAYLOAD_STOP = 'stop'
DEFAULT_PAYLOAD_TURN_OFF = 'turn_off'
DEFAULT_PAYLOAD_TURN_ON = 'turn_on'
DEFAULT_RETAIN = False
DEFAULT_SERVICE_STRINGS = services_to_strings(DEFAULT_SERVICES)
PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_BATTERY_LEVEL_TEMPLATE): cv.template,
vol.Optional(CONF_BATTERY_LEVEL_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_CHARGING_TEMPLATE): cv.template,
vol.Optional(CONF_CHARGING_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_CLEANING_TEMPLATE): cv.template,
vol.Optional(CONF_CLEANING_TOPIC): mqtt.valid_publish_topic,
vol.Inclusive(CONF_BATTERY_LEVEL_TEMPLATE, 'battery'): cv.template,
vol.Inclusive(CONF_BATTERY_LEVEL_TOPIC,
'battery'): mqtt.valid_publish_topic,
vol.Inclusive(CONF_CHARGING_TEMPLATE, 'charging'): cv.template,
vol.Inclusive(CONF_CHARGING_TOPIC, 'charging'): mqtt.valid_publish_topic,
vol.Inclusive(CONF_CLEANING_TEMPLATE, 'cleaning'): cv.template,
vol.Inclusive(CONF_CLEANING_TOPIC, 'cleaning'): mqtt.valid_publish_topic,
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
vol.Optional(CONF_DOCKED_TEMPLATE): cv.template,
vol.Optional(CONF_DOCKED_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_ERROR_TEMPLATE): cv.template,
vol.Optional(CONF_ERROR_TOPIC): mqtt.valid_publish_topic,
vol.Inclusive(CONF_DOCKED_TEMPLATE, 'docked'): cv.template,
vol.Inclusive(CONF_DOCKED_TOPIC, 'docked'): mqtt.valid_publish_topic,
vol.Inclusive(CONF_ERROR_TEMPLATE, 'error'): cv.template,
vol.Inclusive(CONF_ERROR_TOPIC, 'error'): mqtt.valid_publish_topic,
vol.Optional(CONF_FAN_SPEED_LIST, default=[]):
vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_FAN_SPEED_TEMPLATE): cv.template,
vol.Optional(CONF_FAN_SPEED_TOPIC): mqtt.valid_publish_topic,
vol.Inclusive(CONF_FAN_SPEED_TEMPLATE, 'fan_speed'): cv.template,
vol.Inclusive(CONF_FAN_SPEED_TOPIC, 'fan_speed'): mqtt.valid_publish_topic,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PAYLOAD_CLEAN_SPOT,
default=DEFAULT_PAYLOAD_CLEAN_SPOT): cv.string,
@ -131,8 +130,6 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
default=DEFAULT_PAYLOAD_TURN_ON): cv.string,
vol.Optional(CONF_SEND_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_SET_FAN_SPEED_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_STATE_TEMPLATE): cv.template,
vol.Optional(CONF_STATE_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_SUPPORTED_FEATURES, default=DEFAULT_SERVICE_STRINGS):
vol.All(cv.ensure_list, [vol.In(STRING_TO_SERVICE.keys())]),
vol.Optional(CONF_UNIQUE_ID): cv.string,
@ -283,7 +280,7 @@ class MqttVacuum(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
battery_level = self._templates[CONF_BATTERY_LEVEL_TEMPLATE]\
.async_render_with_possible_json_value(
msg.payload, error_value=None)
if battery_level is not None:
if battery_level:
self._battery_level = int(battery_level)
if msg.topic == self._state_topics[CONF_CHARGING_TOPIC] and \
@ -291,7 +288,7 @@ class MqttVacuum(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
charging = self._templates[CONF_CHARGING_TEMPLATE]\
.async_render_with_possible_json_value(
msg.payload, error_value=None)
if charging is not None:
if charging:
self._charging = cv.boolean(charging)
if msg.topic == self._state_topics[CONF_CLEANING_TOPIC] and \
@ -299,7 +296,7 @@ class MqttVacuum(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
cleaning = self._templates[CONF_CLEANING_TEMPLATE]\
.async_render_with_possible_json_value(
msg.payload, error_value=None)
if cleaning is not None:
if cleaning:
self._cleaning = cv.boolean(cleaning)
if msg.topic == self._state_topics[CONF_DOCKED_TOPIC] and \
@ -307,7 +304,7 @@ class MqttVacuum(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
docked = self._templates[CONF_DOCKED_TEMPLATE]\
.async_render_with_possible_json_value(
msg.payload, error_value=None)
if docked is not None:
if docked:
self._docked = cv.boolean(docked)
if msg.topic == self._state_topics[CONF_ERROR_TOPIC] and \
@ -315,7 +312,7 @@ class MqttVacuum(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
error = self._templates[CONF_ERROR_TEMPLATE]\
.async_render_with_possible_json_value(
msg.payload, error_value=None)
if error is not None:
if error:
self._error = cv.string(error)
if self._docked:
@ -325,7 +322,7 @@ class MqttVacuum(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
self._status = "Docked"
elif self._cleaning:
self._status = "Cleaning"
elif self._error is not None and not self._error:
elif self._error:
self._status = "Error: {}".format(self._error)
else:
self._status = "Stopped"
@ -335,7 +332,7 @@ class MqttVacuum(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
fan_speed = self._templates[CONF_FAN_SPEED_TEMPLATE]\
.async_render_with_possible_json_value(
msg.payload, error_value=None)
if fan_speed is not None:
if fan_speed:
self._fan_speed = fan_speed
self.async_write_ha_state()

View file

@ -1,6 +1,6 @@
"""The tests for the Mqtt vacuum platform."""
import copy
import json
import pytest
from homeassistant.components import mqtt, vacuum
@ -31,8 +31,8 @@ default_config = {
mqttvacuum.CONF_CLEANING_TEMPLATE: '{{ value_json.cleaning }}',
mqttvacuum.CONF_DOCKED_TOPIC: 'vacuum/state',
mqttvacuum.CONF_DOCKED_TEMPLATE: '{{ value_json.docked }}',
mqttvacuum.CONF_STATE_TOPIC: 'vacuum/state',
mqttvacuum.CONF_STATE_TEMPLATE: '{{ value_json.state }}',
mqttvacuum.CONF_ERROR_TOPIC: 'vacuum/state',
mqttvacuum.CONF_ERROR_TEMPLATE: '{{ value_json.error }}',
mqttvacuum.CONF_FAN_SPEED_TOPIC: 'vacuum/state',
mqttvacuum.CONF_FAN_SPEED_TEMPLATE: '{{ value_json.fan_speed }}',
mqttvacuum.CONF_SET_FAN_SPEED_TOPIC: 'vacuum/set_fan_speed',
@ -177,6 +177,122 @@ async def test_status(hass, mock_publish):
assert 'min' == state.attributes.get(ATTR_FAN_SPEED)
async def test_status_battery(hass, mock_publish):
"""Test status updates from the vacuum."""
default_config[mqttvacuum.CONF_SUPPORTED_FEATURES] = \
mqttvacuum.services_to_strings(mqttvacuum.ALL_SERVICES)
assert await async_setup_component(hass, vacuum.DOMAIN, {
vacuum.DOMAIN: default_config,
})
message = """{
"battery_level": 54
}"""
async_fire_mqtt_message(hass, 'vacuum/state', message)
await hass.async_block_till_done()
await hass.async_block_till_done()
state = hass.states.get('vacuum.mqtttest')
assert 'mdi:battery-50' == \
state.attributes.get(ATTR_BATTERY_ICON)
async def test_status_cleaning(hass, mock_publish):
"""Test status updates from the vacuum."""
default_config[mqttvacuum.CONF_SUPPORTED_FEATURES] = \
mqttvacuum.services_to_strings(mqttvacuum.ALL_SERVICES)
assert await async_setup_component(hass, vacuum.DOMAIN, {
vacuum.DOMAIN: default_config,
})
message = """{
"cleaning": true
}"""
async_fire_mqtt_message(hass, 'vacuum/state', message)
await hass.async_block_till_done()
await hass.async_block_till_done()
state = hass.states.get('vacuum.mqtttest')
assert STATE_ON == state.state
async def test_status_docked(hass, mock_publish):
"""Test status updates from the vacuum."""
default_config[mqttvacuum.CONF_SUPPORTED_FEATURES] = \
mqttvacuum.services_to_strings(mqttvacuum.ALL_SERVICES)
assert await async_setup_component(hass, vacuum.DOMAIN, {
vacuum.DOMAIN: default_config,
})
message = """{
"docked": true
}"""
async_fire_mqtt_message(hass, 'vacuum/state', message)
await hass.async_block_till_done()
await hass.async_block_till_done()
state = hass.states.get('vacuum.mqtttest')
assert STATE_OFF == state.state
async def test_status_charging(hass, mock_publish):
"""Test status updates from the vacuum."""
default_config[mqttvacuum.CONF_SUPPORTED_FEATURES] = \
mqttvacuum.services_to_strings(mqttvacuum.ALL_SERVICES)
assert await async_setup_component(hass, vacuum.DOMAIN, {
vacuum.DOMAIN: default_config,
})
message = """{
"charging": true
}"""
async_fire_mqtt_message(hass, 'vacuum/state', message)
await hass.async_block_till_done()
await hass.async_block_till_done()
state = hass.states.get('vacuum.mqtttest')
assert 'mdi:battery-outline' == \
state.attributes.get(ATTR_BATTERY_ICON)
async def test_status_fan_speed(hass, mock_publish):
"""Test status updates from the vacuum."""
default_config[mqttvacuum.CONF_SUPPORTED_FEATURES] = \
mqttvacuum.services_to_strings(mqttvacuum.ALL_SERVICES)
assert await async_setup_component(hass, vacuum.DOMAIN, {
vacuum.DOMAIN: default_config,
})
message = """{
"fan_speed": "max"
}"""
async_fire_mqtt_message(hass, 'vacuum/state', message)
await hass.async_block_till_done()
await hass.async_block_till_done()
state = hass.states.get('vacuum.mqtttest')
assert 'max' == state.attributes.get(ATTR_FAN_SPEED)
async def test_status_error(hass, mock_publish):
"""Test status updates from the vacuum."""
default_config[mqttvacuum.CONF_SUPPORTED_FEATURES] = \
mqttvacuum.services_to_strings(mqttvacuum.ALL_SERVICES)
assert await async_setup_component(hass, vacuum.DOMAIN, {
vacuum.DOMAIN: default_config,
})
message = """{
"error": "Error1"
}"""
async_fire_mqtt_message(hass, 'vacuum/state', message)
await hass.async_block_till_done()
await hass.async_block_till_done()
state = hass.states.get('vacuum.mqtttest')
assert 'Error: Error1' == state.attributes.get(ATTR_STATUS)
async def test_battery_template(hass, mock_publish):
"""Test that you can use non-default templates for battery_level."""
default_config.update({
@ -214,6 +330,84 @@ async def test_status_invalid_json(hass, mock_publish):
assert "Stopped" == state.attributes.get(ATTR_STATUS)
async def test_missing_battery_template(hass, mock_publish):
"""Test to make sure missing template is not allowed."""
config = copy.deepcopy(default_config)
config.pop(mqttvacuum.CONF_BATTERY_LEVEL_TEMPLATE)
assert await async_setup_component(hass, vacuum.DOMAIN, {
vacuum.DOMAIN: config,
})
state = hass.states.get('vacuum.mqtttest')
assert state is None
async def test_missing_charging_template(hass, mock_publish):
"""Test to make sure missing template is not allowed."""
config = copy.deepcopy(default_config)
config.pop(mqttvacuum.CONF_CHARGING_TEMPLATE)
assert await async_setup_component(hass, vacuum.DOMAIN, {
vacuum.DOMAIN: config,
})
state = hass.states.get('vacuum.mqtttest')
assert state is None
async def test_missing_cleaning_template(hass, mock_publish):
"""Test to make sure missing template is not allowed."""
config = copy.deepcopy(default_config)
config.pop(mqttvacuum.CONF_CLEANING_TEMPLATE)
assert await async_setup_component(hass, vacuum.DOMAIN, {
vacuum.DOMAIN: config,
})
state = hass.states.get('vacuum.mqtttest')
assert state is None
async def test_missing_docked_template(hass, mock_publish):
"""Test to make sure missing template is not allowed."""
config = copy.deepcopy(default_config)
config.pop(mqttvacuum.CONF_DOCKED_TEMPLATE)
assert await async_setup_component(hass, vacuum.DOMAIN, {
vacuum.DOMAIN: config,
})
state = hass.states.get('vacuum.mqtttest')
assert state is None
async def test_missing_error_template(hass, mock_publish):
"""Test to make sure missing template is not allowed."""
config = copy.deepcopy(default_config)
config.pop(mqttvacuum.CONF_ERROR_TEMPLATE)
assert await async_setup_component(hass, vacuum.DOMAIN, {
vacuum.DOMAIN: config,
})
state = hass.states.get('vacuum.mqtttest')
assert state is None
async def test_missing_fan_speed_template(hass, mock_publish):
"""Test to make sure missing template is not allowed."""
config = copy.deepcopy(default_config)
config.pop(mqttvacuum.CONF_FAN_SPEED_TEMPLATE)
assert await async_setup_component(hass, vacuum.DOMAIN, {
vacuum.DOMAIN: config,
})
state = hass.states.get('vacuum.mqtttest')
assert state is None
async def test_default_availability_payload(hass, mock_publish):
"""Test availability by default payload with defined topic."""
default_config.update({