Fix optimistic mode + other bugs, tests (#22976)

pull/23225/head
Erik Montnemery 2019-04-19 05:59:41 +02:00 committed by Paulus Schoutsen
parent b0ce3dc683
commit 7a84cfb0be
2 changed files with 383 additions and 25 deletions

View File

@ -9,7 +9,7 @@ from homeassistant.components.fan import (
SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, FanEntity)
from homeassistant.const import (
CONF_DEVICE, CONF_NAME, CONF_OPTIMISTIC, CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON,
CONF_STATE, STATE_OFF, STATE_ON)
CONF_STATE)
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_connect
@ -32,6 +32,7 @@ CONF_OSCILLATION_COMMAND_TOPIC = 'oscillation_command_topic'
CONF_OSCILLATION_VALUE_TEMPLATE = 'oscillation_value_template'
CONF_PAYLOAD_OSCILLATION_ON = 'payload_oscillation_on'
CONF_PAYLOAD_OSCILLATION_OFF = 'payload_oscillation_off'
CONF_PAYLOAD_OFF_SPEED = 'payload_off_speed'
CONF_PAYLOAD_LOW_SPEED = 'payload_low_speed'
CONF_PAYLOAD_MEDIUM_SPEED = 'payload_medium_speed'
CONF_PAYLOAD_HIGH_SPEED = 'payload_high_speed'
@ -57,12 +58,13 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PAYLOAD_HIGH_SPEED, default=SPEED_HIGH): cv.string,
vol.Optional(CONF_PAYLOAD_LOW_SPEED, default=SPEED_LOW): cv.string,
vol.Optional(CONF_PAYLOAD_MEDIUM_SPEED, default=SPEED_MEDIUM): cv.string,
vol.Optional(CONF_PAYLOAD_OFF_SPEED, default=SPEED_OFF): cv.string,
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_PAYLOAD_OSCILLATION_OFF,
default=DEFAULT_PAYLOAD_OFF): cv.string,
default=OSCILLATE_OFF_PAYLOAD): cv.string,
vol.Optional(CONF_PAYLOAD_OSCILLATION_ON,
default=DEFAULT_PAYLOAD_ON): cv.string,
default=OSCILLATE_ON_PAYLOAD): cv.string,
vol.Optional(CONF_SPEED_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_SPEED_LIST,
default=[SPEED_OFF, SPEED_LOW,
@ -172,13 +174,14 @@ class MqttFan(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
OSCILLATION: config.get(CONF_OSCILLATION_VALUE_TEMPLATE)
}
self._payload = {
STATE_ON: config[CONF_PAYLOAD_ON],
STATE_OFF: config[CONF_PAYLOAD_OFF],
OSCILLATE_ON_PAYLOAD: config[CONF_PAYLOAD_OSCILLATION_ON],
OSCILLATE_OFF_PAYLOAD: config[CONF_PAYLOAD_OSCILLATION_OFF],
SPEED_LOW: config[CONF_PAYLOAD_LOW_SPEED],
SPEED_MEDIUM: config[CONF_PAYLOAD_MEDIUM_SPEED],
SPEED_HIGH: config[CONF_PAYLOAD_HIGH_SPEED],
'STATE_ON': config[CONF_PAYLOAD_ON],
'STATE_OFF': config[CONF_PAYLOAD_OFF],
'OSCILLATE_ON_PAYLOAD': config[CONF_PAYLOAD_OSCILLATION_ON],
'OSCILLATE_OFF_PAYLOAD': config[CONF_PAYLOAD_OSCILLATION_OFF],
'SPEED_LOW': config[CONF_PAYLOAD_LOW_SPEED],
'SPEED_MEDIUM': config[CONF_PAYLOAD_MEDIUM_SPEED],
'SPEED_HIGH': config[CONF_PAYLOAD_HIGH_SPEED],
'SPEED_OFF': config[CONF_PAYLOAD_OFF_SPEED],
}
optimistic = config[CONF_OPTIMISTIC]
self._optimistic = optimistic or self._topic[CONF_STATE_TOPIC] is None
@ -208,9 +211,9 @@ class MqttFan(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
def state_received(msg):
"""Handle new received MQTT message."""
payload = templates[CONF_STATE](msg.payload)
if payload == self._payload[STATE_ON]:
if payload == self._payload['STATE_ON']:
self._state = True
elif payload == self._payload[STATE_OFF]:
elif payload == self._payload['STATE_OFF']:
self._state = False
self.async_write_ha_state()
@ -224,12 +227,14 @@ class MqttFan(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
def speed_received(msg):
"""Handle new received MQTT message for the speed."""
payload = templates[ATTR_SPEED](msg.payload)
if payload == self._payload[SPEED_LOW]:
if payload == self._payload['SPEED_LOW']:
self._speed = SPEED_LOW
elif payload == self._payload[SPEED_MEDIUM]:
elif payload == self._payload['SPEED_MEDIUM']:
self._speed = SPEED_MEDIUM
elif payload == self._payload[SPEED_HIGH]:
elif payload == self._payload['SPEED_HIGH']:
self._speed = SPEED_HIGH
elif payload == self._payload['SPEED_OFF']:
self._speed = SPEED_OFF
self.async_write_ha_state()
if self._topic[CONF_SPEED_STATE_TOPIC] is not None:
@ -243,9 +248,9 @@ class MqttFan(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
def oscillation_received(msg):
"""Handle new received MQTT message for the oscillation."""
payload = templates[OSCILLATION](msg.payload)
if payload == self._payload[OSCILLATE_ON_PAYLOAD]:
if payload == self._payload['OSCILLATE_ON_PAYLOAD']:
self._oscillation = True
elif payload == self._payload[OSCILLATE_OFF_PAYLOAD]:
elif payload == self._payload['OSCILLATE_OFF_PAYLOAD']:
self._oscillation = False
self.async_write_ha_state()
@ -314,10 +319,13 @@ class MqttFan(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
"""
mqtt.async_publish(
self.hass, self._topic[CONF_COMMAND_TOPIC],
self._payload[STATE_ON], self._config[CONF_QOS],
self._payload['STATE_ON'], self._config[CONF_QOS],
self._config[CONF_RETAIN])
if speed:
await self.async_set_speed(speed)
if self._optimistic:
self._state = True
self.async_write_ha_state()
async def async_turn_off(self, **kwargs) -> None:
"""Turn off the entity.
@ -326,8 +334,11 @@ class MqttFan(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
"""
mqtt.async_publish(
self.hass, self._topic[CONF_COMMAND_TOPIC],
self._payload[STATE_OFF], self._config[CONF_QOS],
self._payload['STATE_OFF'], self._config[CONF_QOS],
self._config[CONF_RETAIN])
if self._optimistic:
self._state = False
self.async_write_ha_state()
async def async_set_speed(self, speed: str) -> None:
"""Set the speed of the fan.
@ -338,11 +349,13 @@ class MqttFan(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
return
if speed == SPEED_LOW:
mqtt_payload = self._payload[SPEED_LOW]
mqtt_payload = self._payload['SPEED_LOW']
elif speed == SPEED_MEDIUM:
mqtt_payload = self._payload[SPEED_MEDIUM]
mqtt_payload = self._payload['SPEED_MEDIUM']
elif speed == SPEED_HIGH:
mqtt_payload = self._payload[SPEED_HIGH]
mqtt_payload = self._payload['SPEED_HIGH']
elif speed == SPEED_OFF:
mqtt_payload = self._payload['SPEED_OFF']
else:
mqtt_payload = speed
@ -364,9 +377,9 @@ class MqttFan(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
return
if oscillating is False:
payload = self._payload[OSCILLATE_OFF_PAYLOAD]
payload = self._payload['OSCILLATE_OFF_PAYLOAD']
else:
payload = self._payload[OSCILLATE_ON_PAYLOAD]
payload = self._payload['OSCILLATE_ON_PAYLOAD']
mqtt.async_publish(
self.hass, self._topic[CONF_OSCILLATION_COMMAND_TOPIC],

View File

@ -4,12 +4,14 @@ from unittest.mock import ANY
from homeassistant.components import fan, mqtt
from homeassistant.components.mqtt.discovery import async_start
from homeassistant.const import ATTR_ASSUMED_STATE, STATE_UNAVAILABLE
from homeassistant.const import (
ATTR_ASSUMED_STATE, STATE_OFF, STATE_ON, STATE_UNAVAILABLE)
from homeassistant.setup import async_setup_component
from tests.common import (
MockConfigEntry, async_fire_mqtt_message, async_mock_mqtt_component,
mock_registry)
from tests.components.fan import common
async def test_fail_setup_if_no_command_topic(hass, mqtt_mock):
@ -23,6 +25,349 @@ async def test_fail_setup_if_no_command_topic(hass, mqtt_mock):
assert hass.states.get('fan.test') is None
async def test_controlling_state_via_topic(hass, mqtt_mock):
"""Test the controlling state via topic."""
assert await async_setup_component(hass, fan.DOMAIN, {
fan.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'state-topic',
'command_topic': 'command-topic',
'payload_off': 'StAtE_OfF',
'payload_on': 'StAtE_On',
'oscillation_state_topic': 'oscillation-state-topic',
'oscillation_command_topic': 'oscillation-command-topic',
'payload_oscillation_off': 'OsC_OfF',
'payload_oscillation_on': 'OsC_On',
'speed_state_topic': 'speed-state-topic',
'speed_command_topic': 'speed-command-topic',
'payload_off_speed': 'speed_OfF',
'payload_low_speed': 'speed_lOw',
'payload_medium_speed': 'speed_mEdium',
'payload_high_speed': 'speed_High',
}
})
state = hass.states.get('fan.test')
assert state.state is STATE_OFF
assert not state.attributes.get(ATTR_ASSUMED_STATE)
async_fire_mqtt_message(hass, 'state-topic', 'StAtE_On')
await hass.async_block_till_done()
state = hass.states.get('fan.test')
assert state.state is STATE_ON
async_fire_mqtt_message(hass, 'state-topic', 'StAtE_OfF')
await hass.async_block_till_done()
await hass.async_block_till_done()
state = hass.states.get('fan.test')
assert state.state is STATE_OFF
assert state.attributes.get('oscillating') is False
async_fire_mqtt_message(hass, 'oscillation-state-topic', 'OsC_On')
await hass.async_block_till_done()
await hass.async_block_till_done()
state = hass.states.get('fan.test')
assert state.attributes.get('oscillating') is True
async_fire_mqtt_message(hass, 'oscillation-state-topic', 'OsC_OfF')
await hass.async_block_till_done()
await hass.async_block_till_done()
state = hass.states.get('fan.test')
assert state.attributes.get('oscillating') is False
assert fan.SPEED_OFF == state.attributes.get('speed')
async_fire_mqtt_message(hass, 'speed-state-topic', 'speed_lOw')
await hass.async_block_till_done()
await hass.async_block_till_done()
state = hass.states.get('fan.test')
assert fan.SPEED_LOW == state.attributes.get('speed')
async_fire_mqtt_message(hass, 'speed-state-topic', 'speed_mEdium')
await hass.async_block_till_done()
await hass.async_block_till_done()
state = hass.states.get('fan.test')
assert fan.SPEED_MEDIUM == state.attributes.get('speed')
async_fire_mqtt_message(hass, 'speed-state-topic', 'speed_High')
await hass.async_block_till_done()
await hass.async_block_till_done()
state = hass.states.get('fan.test')
assert fan.SPEED_HIGH == state.attributes.get('speed')
async_fire_mqtt_message(hass, 'speed-state-topic', 'speed_OfF')
await hass.async_block_till_done()
await hass.async_block_till_done()
state = hass.states.get('fan.test')
assert fan.SPEED_OFF == state.attributes.get('speed')
async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock):
"""Test the controlling state via topic and JSON message."""
assert await async_setup_component(hass, fan.DOMAIN, {
fan.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'state-topic',
'command_topic': 'command-topic',
'oscillation_state_topic': 'oscillation-state-topic',
'oscillation_command_topic': 'oscillation-command-topic',
'speed_state_topic': 'speed-state-topic',
'speed_command_topic': 'speed-command-topic',
'state_value_template': '{{ value_json.val }}',
'oscillation_value_template': '{{ value_json.val }}',
'speed_value_template': '{{ value_json.val }}',
}
})
state = hass.states.get('fan.test')
assert state.state is STATE_OFF
assert not state.attributes.get(ATTR_ASSUMED_STATE)
async_fire_mqtt_message(hass, 'state-topic', '{"val":"ON"}')
await hass.async_block_till_done()
state = hass.states.get('fan.test')
assert state.state is STATE_ON
async_fire_mqtt_message(hass, 'state-topic', '{"val":"OFF"}')
await hass.async_block_till_done()
await hass.async_block_till_done()
state = hass.states.get('fan.test')
assert state.state is STATE_OFF
assert state.attributes.get('oscillating') is False
async_fire_mqtt_message(
hass, 'oscillation-state-topic', '{"val":"oscillate_on"}')
await hass.async_block_till_done()
await hass.async_block_till_done()
state = hass.states.get('fan.test')
assert state.attributes.get('oscillating') is True
async_fire_mqtt_message(
hass, 'oscillation-state-topic', '{"val":"oscillate_off"}')
await hass.async_block_till_done()
await hass.async_block_till_done()
state = hass.states.get('fan.test')
assert state.attributes.get('oscillating') is False
assert fan.SPEED_OFF == state.attributes.get('speed')
async_fire_mqtt_message(hass, 'speed-state-topic', '{"val":"low"}')
await hass.async_block_till_done()
await hass.async_block_till_done()
state = hass.states.get('fan.test')
assert fan.SPEED_LOW == state.attributes.get('speed')
async_fire_mqtt_message(hass, 'speed-state-topic', '{"val":"medium"}')
await hass.async_block_till_done()
await hass.async_block_till_done()
state = hass.states.get('fan.test')
assert fan.SPEED_MEDIUM == state.attributes.get('speed')
async_fire_mqtt_message(hass, 'speed-state-topic', '{"val":"high"}')
await hass.async_block_till_done()
await hass.async_block_till_done()
state = hass.states.get('fan.test')
assert fan.SPEED_HIGH == state.attributes.get('speed')
async_fire_mqtt_message(hass, 'speed-state-topic', '{"val":"off"}')
await hass.async_block_till_done()
await hass.async_block_till_done()
state = hass.states.get('fan.test')
assert fan.SPEED_OFF == state.attributes.get('speed')
async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock):
"""Test optimistic mode without state topic."""
assert await async_setup_component(hass, fan.DOMAIN, {
fan.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'command_topic': 'command-topic',
'payload_off': 'StAtE_OfF',
'payload_on': 'StAtE_On',
'oscillation_command_topic': 'oscillation-command-topic',
'payload_oscillation_off': 'OsC_OfF',
'payload_oscillation_on': 'OsC_On',
'speed_command_topic': 'speed-command-topic',
'payload_off_speed': 'speed_OfF',
'payload_low_speed': 'speed_lOw',
'payload_medium_speed': 'speed_mEdium',
'payload_high_speed': 'speed_High',
}
})
state = hass.states.get('fan.test')
assert state.state is STATE_OFF
assert state.attributes.get(ATTR_ASSUMED_STATE)
common.async_turn_on(hass, 'fan.test')
await hass.async_block_till_done()
mqtt_mock.async_publish.assert_called_once_with(
'command-topic', 'StAtE_On', 0, False)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get('fan.test')
assert state.state is STATE_ON
assert state.attributes.get(ATTR_ASSUMED_STATE)
common.async_turn_off(hass, 'fan.test')
await hass.async_block_till_done()
mqtt_mock.async_publish.assert_called_once_with(
'command-topic', 'StAtE_OfF', 0, False)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get('fan.test')
assert state.state is STATE_OFF
assert state.attributes.get(ATTR_ASSUMED_STATE)
common.async_oscillate(hass, 'fan.test', True)
await hass.async_block_till_done()
mqtt_mock.async_publish.assert_called_once_with(
'oscillation-command-topic', 'OsC_On', 0, False)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get('fan.test')
assert state.state is STATE_OFF
assert state.attributes.get(ATTR_ASSUMED_STATE)
common.async_oscillate(hass, 'fan.test', False)
await hass.async_block_till_done()
mqtt_mock.async_publish.assert_called_once_with(
'oscillation-command-topic', 'OsC_OfF', 0, False)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get('fan.test')
assert state.state is STATE_OFF
assert state.attributes.get(ATTR_ASSUMED_STATE)
common.async_set_speed(hass, 'fan.test', fan.SPEED_LOW)
await hass.async_block_till_done()
mqtt_mock.async_publish.assert_called_once_with(
'speed-command-topic', 'speed_lOw', 0, False)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get('fan.test')
assert state.state is STATE_OFF
assert state.attributes.get(ATTR_ASSUMED_STATE)
common.async_set_speed(hass, 'fan.test', fan.SPEED_MEDIUM)
await hass.async_block_till_done()
mqtt_mock.async_publish.assert_called_once_with(
'speed-command-topic', 'speed_mEdium', 0, False)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get('fan.test')
assert state.state is STATE_OFF
assert state.attributes.get(ATTR_ASSUMED_STATE)
common.async_set_speed(hass, 'fan.test', fan.SPEED_HIGH)
await hass.async_block_till_done()
mqtt_mock.async_publish.assert_called_once_with(
'speed-command-topic', 'speed_High', 0, False)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get('fan.test')
assert state.state is STATE_OFF
assert state.attributes.get(ATTR_ASSUMED_STATE)
common.async_set_speed(hass, 'fan.test', fan.SPEED_OFF)
await hass.async_block_till_done()
mqtt_mock.async_publish.assert_called_once_with(
'speed-command-topic', 'speed_OfF', 0, False)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get('fan.test')
assert state.state is STATE_OFF
assert state.attributes.get(ATTR_ASSUMED_STATE)
async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock):
"""Test optimistic mode with state topic."""
assert await async_setup_component(hass, fan.DOMAIN, {
fan.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'state-topic',
'command_topic': 'command-topic',
'oscillation_state_topic': 'oscillation-state-topic',
'oscillation_command_topic': 'oscillation-command-topic',
'speed_state_topic': 'speed-state-topic',
'speed_command_topic': 'speed-command-topic',
'optimistic': True
}
})
state = hass.states.get('fan.test')
assert state.state is STATE_OFF
assert state.attributes.get(ATTR_ASSUMED_STATE)
common.async_turn_on(hass, 'fan.test')
await hass.async_block_till_done()
mqtt_mock.async_publish.assert_called_once_with(
'command-topic', 'ON', 0, False)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get('fan.test')
assert state.state is STATE_ON
assert state.attributes.get(ATTR_ASSUMED_STATE)
common.async_turn_off(hass, 'fan.test')
await hass.async_block_till_done()
mqtt_mock.async_publish.assert_called_once_with(
'command-topic', 'OFF', 0, False)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get('fan.test')
assert state.state is STATE_OFF
assert state.attributes.get(ATTR_ASSUMED_STATE)
common.async_oscillate(hass, 'fan.test', True)
await hass.async_block_till_done()
mqtt_mock.async_publish.assert_called_once_with(
'oscillation-command-topic', 'oscillate_on', 0, False)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get('fan.test')
assert state.state is STATE_OFF
assert state.attributes.get(ATTR_ASSUMED_STATE)
common.async_oscillate(hass, 'fan.test', False)
await hass.async_block_till_done()
mqtt_mock.async_publish.assert_called_once_with(
'oscillation-command-topic', 'oscillate_off', 0, False)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get('fan.test')
assert state.state is STATE_OFF
assert state.attributes.get(ATTR_ASSUMED_STATE)
common.async_set_speed(hass, 'fan.test', fan.SPEED_LOW)
await hass.async_block_till_done()
mqtt_mock.async_publish.assert_called_once_with(
'speed-command-topic', 'low', 0, False)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get('fan.test')
assert state.state is STATE_OFF
assert state.attributes.get(ATTR_ASSUMED_STATE)
common.async_set_speed(hass, 'fan.test', fan.SPEED_MEDIUM)
await hass.async_block_till_done()
mqtt_mock.async_publish.assert_called_once_with(
'speed-command-topic', 'medium', 0, False)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get('fan.test')
assert state.state is STATE_OFF
assert state.attributes.get(ATTR_ASSUMED_STATE)
common.async_set_speed(hass, 'fan.test', fan.SPEED_HIGH)
await hass.async_block_till_done()
mqtt_mock.async_publish.assert_called_once_with(
'speed-command-topic', 'high', 0, False)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get('fan.test')
assert state.state is STATE_OFF
assert state.attributes.get(ATTR_ASSUMED_STATE)
common.async_set_speed(hass, 'fan.test', fan.SPEED_OFF)
await hass.async_block_till_done()
mqtt_mock.async_publish.assert_called_once_with(
'speed-command-topic', 'off', 0, False)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get('fan.test')
assert state.state is STATE_OFF
assert state.attributes.get(ATTR_ASSUMED_STATE)
async def test_default_availability_payload(hass, mqtt_mock):
"""Test the availability payload."""
assert await async_setup_component(hass, fan.DOMAIN, {