Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimistic MQTT light #14401

Merged
merged 10 commits into from
May 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 41 additions & 22 deletions homeassistant/components/light/mqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.mqtt/
"""
import asyncio
import logging

import voluptuous as vol
Expand All @@ -17,12 +16,13 @@
SUPPORT_EFFECT, SUPPORT_COLOR, SUPPORT_WHITE_VALUE)
from homeassistant.const import (
CONF_BRIGHTNESS, CONF_COLOR_TEMP, CONF_EFFECT, CONF_NAME,
CONF_OPTIMISTIC, CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON,
CONF_OPTIMISTIC, CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, STATE_ON,
CONF_RGB, CONF_STATE, CONF_VALUE_TEMPLATE, CONF_WHITE_VALUE, CONF_XY)
from homeassistant.components.mqtt import (
CONF_AVAILABILITY_TOPIC, CONF_COMMAND_TOPIC, CONF_PAYLOAD_AVAILABLE,
CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC,
MqttAvailability)
from homeassistant.helpers.restore_state import async_get_last_state
import homeassistant.helpers.config_validation as cv
import homeassistant.util.color as color_util

Expand Down Expand Up @@ -100,8 +100,8 @@
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)


@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
async def async_setup_platform(hass, config, async_add_devices,
discovery_info=None):
"""Set up a MQTT Light."""
if discovery_info is not None:
config = PLATFORM_SCHEMA(discovery_info)
Expand Down Expand Up @@ -213,10 +213,9 @@ def __init__(self, name, effect_list, topic, templates, qos,
self._supported_features |= (
topic[CONF_XY_COMMAND_TOPIC] is not None and SUPPORT_COLOR)

@asyncio.coroutine
def async_added_to_hass(self):
async def async_added_to_hass(self):
"""Subscribe to MQTT events."""
yield from super().async_added_to_hass()
await super().async_added_to_hass()

templates = {}
for key, tpl in list(self._templates.items()):
Expand All @@ -226,6 +225,8 @@ def async_added_to_hass(self):
tpl.hass = self.hass
templates[key] = tpl.async_render_with_possible_json_value

last_state = await async_get_last_state(self.hass, self.entity_id)

@callback
def state_received(topic, payload, qos):
"""Handle new MQTT messages."""
Expand All @@ -237,9 +238,11 @@ def state_received(topic, payload, qos):
self.async_schedule_update_ha_state()

if self._topic[CONF_STATE_TOPIC] is not None:
yield from mqtt.async_subscribe(
await mqtt.async_subscribe(
self.hass, self._topic[CONF_STATE_TOPIC], state_received,
self._qos)
elif self._optimistic and last_state:
self._state = last_state.state == STATE_ON

@callback
def brightness_received(topic, payload, qos):
Expand All @@ -250,10 +253,13 @@ def brightness_received(topic, payload, qos):
self.async_schedule_update_ha_state()

if self._topic[CONF_BRIGHTNESS_STATE_TOPIC] is not None:
yield from mqtt.async_subscribe(
await mqtt.async_subscribe(
self.hass, self._topic[CONF_BRIGHTNESS_STATE_TOPIC],
brightness_received, self._qos)
self._brightness = 255
elif self._optimistic_brightness and last_state\
and last_state.attributes.get(ATTR_BRIGHTNESS):
self._brightness = last_state.attributes.get(ATTR_BRIGHTNESS)
elif self._topic[CONF_BRIGHTNESS_COMMAND_TOPIC] is not None:
self._brightness = 255
else:
Expand All @@ -268,11 +274,14 @@ def rgb_received(topic, payload, qos):
self.async_schedule_update_ha_state()

if self._topic[CONF_RGB_STATE_TOPIC] is not None:
yield from mqtt.async_subscribe(
await mqtt.async_subscribe(
self.hass, self._topic[CONF_RGB_STATE_TOPIC], rgb_received,
self._qos)
self._hs = (0, 0)
if self._topic[CONF_RGB_COMMAND_TOPIC] is not None:
if self._optimistic_rgb and last_state\
and last_state.attributes.get(ATTR_HS_COLOR):
self._hs = last_state.attributes.get(ATTR_HS_COLOR)
elif self._topic[CONF_RGB_COMMAND_TOPIC] is not None:
self._hs = (0, 0)

@callback
Expand All @@ -282,11 +291,14 @@ def color_temp_received(topic, payload, qos):
self.async_schedule_update_ha_state()

if self._topic[CONF_COLOR_TEMP_STATE_TOPIC] is not None:
yield from mqtt.async_subscribe(
await mqtt.async_subscribe(
self.hass, self._topic[CONF_COLOR_TEMP_STATE_TOPIC],
color_temp_received, self._qos)
self._color_temp = 150
if self._topic[CONF_COLOR_TEMP_COMMAND_TOPIC] is not None:
if self._optimistic_color_temp and last_state\
and last_state.attributes.get(ATTR_COLOR_TEMP):
self._color_temp = last_state.attributes.get(ATTR_COLOR_TEMP)
elif self._topic[CONF_COLOR_TEMP_COMMAND_TOPIC] is not None:
self._color_temp = 150
else:
self._color_temp = None
Expand All @@ -298,11 +310,14 @@ def effect_received(topic, payload, qos):
self.async_schedule_update_ha_state()

if self._topic[CONF_EFFECT_STATE_TOPIC] is not None:
yield from mqtt.async_subscribe(
await mqtt.async_subscribe(
self.hass, self._topic[CONF_EFFECT_STATE_TOPIC],
effect_received, self._qos)
self._effect = 'none'
if self._topic[CONF_EFFECT_COMMAND_TOPIC] is not None:
if self._optimistic_effect and last_state\
and last_state.attributes.get(ATTR_EFFECT):
self._effect = last_state.attributes.get(ATTR_EFFECT)
elif self._topic[CONF_EFFECT_COMMAND_TOPIC] is not None:
self._effect = 'none'
else:
self._effect = None
Expand All @@ -316,10 +331,13 @@ def white_value_received(topic, payload, qos):
self.async_schedule_update_ha_state()

if self._topic[CONF_WHITE_VALUE_STATE_TOPIC] is not None:
yield from mqtt.async_subscribe(
await mqtt.async_subscribe(
self.hass, self._topic[CONF_WHITE_VALUE_STATE_TOPIC],
white_value_received, self._qos)
self._white_value = 255
elif self._optimistic_white_value and last_state\
and last_state.attributes.get(ATTR_WHITE_VALUE):
self._white_value = last_state.attributes.get(ATTR_WHITE_VALUE)
elif self._topic[CONF_WHITE_VALUE_COMMAND_TOPIC] is not None:
self._white_value = 255
else:
Expand All @@ -334,11 +352,14 @@ def xy_received(topic, payload, qos):
self.async_schedule_update_ha_state()

if self._topic[CONF_XY_STATE_TOPIC] is not None:
yield from mqtt.async_subscribe(
await mqtt.async_subscribe(
self.hass, self._topic[CONF_XY_STATE_TOPIC], xy_received,
self._qos)
self._hs = (0, 0)
if self._topic[CONF_XY_COMMAND_TOPIC] is not None:
if self._optimistic_xy and last_state\
and last_state.attributes.get(ATTR_HS_COLOR):
self._hs = last_state.attributes.get(ATTR_HS_COLOR)
elif self._topic[CONF_XY_COMMAND_TOPIC] is not None:
self._hs = (0, 0)

@property
Expand Down Expand Up @@ -396,8 +417,7 @@ def supported_features(self):
"""Flag supported features."""
return self._supported_features

@asyncio.coroutine
def async_turn_on(self, **kwargs):
async def async_turn_on(self, **kwargs):
"""Turn the device on.

This method is a coroutine.
Expand Down Expand Up @@ -517,8 +537,7 @@ def async_turn_on(self, **kwargs):
if should_update:
self.async_schedule_update_ha_state()

@asyncio.coroutine
def async_turn_off(self, **kwargs):
async def async_turn_off(self, **kwargs):
"""Turn the device off.

This method is a coroutine.
Expand Down
18 changes: 17 additions & 1 deletion homeassistant/components/light/mqtt_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@
SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE)
from homeassistant.components.light.mqtt import CONF_BRIGHTNESS_SCALE
from homeassistant.const import (
CONF_BRIGHTNESS, CONF_COLOR_TEMP, CONF_EFFECT,
CONF_BRIGHTNESS, CONF_COLOR_TEMP, CONF_EFFECT, STATE_ON,
CONF_NAME, CONF_OPTIMISTIC, CONF_RGB, CONF_WHITE_VALUE, CONF_XY)
from homeassistant.components.mqtt import (
CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC, CONF_COMMAND_TOPIC,
CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, CONF_RETAIN,
MqttAvailability)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import HomeAssistantType, ConfigType
from homeassistant.helpers.restore_state import async_get_last_state
import homeassistant.util.color as color_util

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -177,6 +178,8 @@ async def async_added_to_hass(self):
"""Subscribe to MQTT events."""
await super().async_added_to_hass()

last_state = await async_get_last_state(self.hass, self.entity_id)

@callback
def state_received(topic, payload, qos):
"""Handle new MQTT messages."""
Expand Down Expand Up @@ -260,6 +263,19 @@ def state_received(topic, payload, qos):
self.hass, self._topic[CONF_STATE_TOPIC], state_received,
self._qos)

if self._optimistic and last_state:
self._state = last_state.state == STATE_ON
if last_state.attributes.get(ATTR_BRIGHTNESS):
self._brightness = last_state.attributes.get(ATTR_BRIGHTNESS)
if last_state.attributes.get(ATTR_HS_COLOR):
self._hs = last_state.attributes.get(ATTR_HS_COLOR)
if last_state.attributes.get(ATTR_COLOR_TEMP):
self._color_temp = last_state.attributes.get(ATTR_COLOR_TEMP)
if last_state.attributes.get(ATTR_EFFECT):
self._effect = last_state.attributes.get(ATTR_EFFECT)
if last_state.attributes.get(ATTR_WHITE_VALUE):
self._white_value = last_state.attributes.get(ATTR_WHITE_VALUE)

@property
def brightness(self):
"""Return the brightness of this light between 0..255."""
Expand Down
34 changes: 23 additions & 11 deletions homeassistant/components/light/mqtt_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.mqtt_template/
"""
import asyncio
import logging
import voluptuous as vol

Expand All @@ -22,6 +21,7 @@
MqttAvailability)
import homeassistant.helpers.config_validation as cv
import homeassistant.util.color as color_util
from homeassistant.helpers.restore_state import async_get_last_state

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -66,8 +66,8 @@
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)


@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
async def async_setup_platform(hass, config, async_add_devices,
discovery_info=None):
"""Set up a MQTT Template light."""
if discovery_info is not None:
config = PLATFORM_SCHEMA(discovery_info)
Expand Down Expand Up @@ -152,10 +152,11 @@ def __init__(self, hass, name, effect_list, topics, templates, optimistic,
if tpl is not None:
tpl.hass = hass

@asyncio.coroutine
def async_added_to_hass(self):
async def async_added_to_hass(self):
"""Subscribe to MQTT events."""
yield from super().async_added_to_hass()
await super().async_added_to_hass()

last_state = await async_get_last_state(self.hass, self.entity_id)

@callback
def state_received(topic, payload, qos):
Expand Down Expand Up @@ -223,10 +224,23 @@ def state_received(topic, payload, qos):
self.async_schedule_update_ha_state()

if self._topics[CONF_STATE_TOPIC] is not None:
yield from mqtt.async_subscribe(
await mqtt.async_subscribe(
self.hass, self._topics[CONF_STATE_TOPIC], state_received,
self._qos)

if self._optimistic and last_state:
self._state = last_state.state == STATE_ON
if last_state.attributes.get(ATTR_BRIGHTNESS):
self._brightness = last_state.attributes.get(ATTR_BRIGHTNESS)
if last_state.attributes.get(ATTR_HS_COLOR):
self._hs = last_state.attributes.get(ATTR_HS_COLOR)
if last_state.attributes.get(ATTR_COLOR_TEMP):
self._color_temp = last_state.attributes.get(ATTR_COLOR_TEMP)
if last_state.attributes.get(ATTR_EFFECT):
self._effect = last_state.attributes.get(ATTR_EFFECT)
if last_state.attributes.get(ATTR_WHITE_VALUE):
self._white_value = last_state.attributes.get(ATTR_WHITE_VALUE)

@property
def brightness(self):
"""Return the brightness of this light between 0..255."""
Expand Down Expand Up @@ -280,8 +294,7 @@ def effect(self):
"""Return the current effect."""
return self._effect

@asyncio.coroutine
def async_turn_on(self, **kwargs):
async def async_turn_on(self, **kwargs):
"""Turn the entity on.

This method is a coroutine.
Expand Down Expand Up @@ -339,8 +352,7 @@ def async_turn_on(self, **kwargs):
if self._optimistic:
self.async_schedule_update_ha_state()

@asyncio.coroutine
def async_turn_off(self, **kwargs):
async def async_turn_off(self, **kwargs):
"""Turn the entity off.

This method is a coroutine.
Expand Down
23 changes: 18 additions & 5 deletions tests/components/light/test_mqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,14 +140,16 @@
"""
import unittest
from unittest import mock
from unittest.mock import patch

from homeassistant.setup import setup_component
from homeassistant.const import (
STATE_ON, STATE_OFF, STATE_UNAVAILABLE, ATTR_ASSUMED_STATE)
import homeassistant.components.light as light
import homeassistant.core as ha
from tests.common import (
assert_setup_component, get_test_home_assistant, mock_mqtt_component,
fire_mqtt_message)
fire_mqtt_message, mock_coro)


class TestLightMQTT(unittest.TestCase):
Expand Down Expand Up @@ -481,12 +483,23 @@ def test_sending_mqtt_commands_and_optimistic(self): \
'payload_on': 'on',
'payload_off': 'off'
}}

with assert_setup_component(1, light.DOMAIN):
assert setup_component(self.hass, light.DOMAIN, config)
fake_state = ha.State('light.test', 'on', {'brightness': 95,
'hs_color': [100, 100],
'effect': 'random',
'color_temp': 100,
'white_value': 50})
with patch('homeassistant.components.light.mqtt.async_get_last_state',
return_value=mock_coro(fake_state)):
with assert_setup_component(1, light.DOMAIN):
assert setup_component(self.hass, light.DOMAIN, config)

state = self.hass.states.get('light.test')
self.assertEqual(STATE_OFF, state.state)
self.assertEqual(STATE_ON, state.state)
self.assertEqual(95, state.attributes.get('brightness'))
self.assertEqual((100, 100), state.attributes.get('hs_color'))
self.assertEqual('random', state.attributes.get('effect'))
self.assertEqual(100, state.attributes.get('color_temp'))
self.assertEqual(50, state.attributes.get('white_value'))
self.assertTrue(state.attributes.get(ATTR_ASSUMED_STATE))

light.turn_on(self.hass, 'light.test')
Expand Down
Loading