8000 Add support for HS color to mqtt light by emontnemery · Pull Request #16958 · home-assistant/core · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Add support for HS color to mqtt light #16958

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

Merged
merged 2 commits into from
Oct 8, 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
57 changes: 55 additions & 2 deletions homeassistant/components/light/mqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
ATTR_WHITE_VALUE, Light, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP,
SUPPORT_EFFECT, SUPPORT_COLOR, SUPPORT_WHITE_VALUE)
from homeassistant.const import (
CONF_BRIGHTNESS, CONF_COLOR_TEMP, CONF_EFFECT, CONF_NAME,
CONF_BRIGHTNESS, CONF_COLOR_TEMP, CONF_EFFECT, CONF_HS, CONF_NAME,
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 (
Expand Down Expand Up @@ -44,6 +44,9 @@
CONF_EFFECT_LIST = 'effect_list'
CONF_EFFECT_STATE_TOPIC = 'effect_state_topic'
CONF_EFFECT_VALUE_TEMPLATE = 'effect_value_template'
CONF_HS_COMMAND_TOPIC = 'hs_command_topic'
CONF_HS_STATE_TOPIC = 'hs_state_topic'
CONF_HS_VALUE_TEMPLATE = 'hs_value_template'
CONF_RGB_COMMAND_TEMPLATE = 'rgb_command_template'
CONF_RGB_COMMAND_TOPIC = 'rgb_command_topic'
CONF_RGB_STATE_TOPIC = 'rgb_state_topic'
Expand Down Expand Up @@ -82,6 +85,9 @@
vol.Optional(CONF_EFFECT_LIST): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_EFFECT_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_EFFECT_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_HS_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_HS_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_HS_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_UNIQUE_ID): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
Expand Down Expand Up @@ -143,6 +149,8 @@ async def _async_setup_entity(hass, config, async_add_entities,
CONF_COMMAND_TOPIC,
CONF_EFFECT_COMMAND_TOPIC,
CONF_EFFECT_STATE_TOPIC,
CONF_HS_COMMAND_TOPIC,
CONF_HS_STATE_TOPIC,
CONF_RGB_COMMAND_TOPIC,
CONF_RGB_STATE_TOPIC,
CONF_STATE_TOPIC,
Expand All @@ -156,6 +164,7 @@ async def _async_setup_entity(hass, config, async_add_entities,
CONF_BRIGHTNESS: config.get(CONF_BRIGHTNESS_VALUE_TEMPLATE),
CONF_COLOR_TEMP: config.get(CONF_COLOR_TEMP_VALUE_TEMPLATE),
CONF_EFFECT: config.get(CONF_EFFECT_VALUE_TEMPLATE),
CONF_HS: config.get(CONF_HS_VALUE_TEMPLATE),
CONF_RGB: config.get(CONF_RGB_VALUE_TEMPLATE),
CONF_RGB_COMMAND_TEMPLATE: config.get(CONF_RGB_COMMAND_TEMPLATE),
CONF_STATE: config.get(CONF_STATE_VALUE_TEMPLATE),
Expand Down Expand Up @@ -207,6 +216,8 @@ def __init__(self, name, unique_id, effect_list, topic, templates,
optimistic or topic[CONF_COLOR_TEMP_STATE_TOPIC] is None)
self._optimistic_effect = (
optimistic or topic[CONF_EFFECT_STATE_TOPIC] is None)
self._optimistic_hs = \
optimistic or topic[CONF_HS_STATE_TOPIC] is None
self._optimistic_white_value = (
optimistic or topic[CONF_WHITE_VALUE_STATE_TOPIC] is None)
self._optimistic_xy = \
Expand All @@ -232,6 +243,8 @@ def __init__(self, name, unique_id, effect_list, topic, templates,
self._supported_features |= (
topic[CONF_EFFECT_COMMAND_TOPIC] is not None and
SUPPORT_EFFECT)
self._supported_features |= (
topic[CONF_HS_COMMAND_TOPIC] is not None and SUPPORT_COLOR)
self._supported_features |= (
topic[CONF_WHITE_VALUE_COMMAND_TOPIC] is not None and
SUPPORT_WHITE_VALUE)
Expand Down Expand Up @@ -374,6 +387,33 @@ def effect_received(topic, payload, qos):
else:
self._effect = None

@callback
def hs_received(topic, payload, qos):
"""Handle new MQTT messages for hs color."""
payload = templates[CONF_HS](payload)
if not payload:
_LOGGER.debug("Ignoring empty hs message from '%s'", topic)
return

try:
hs_color = [float(val) for val in payload.split(',', 2)]
self._hs = hs_color
self.async_schedule_update_ha_state()
except ValueError:
_LOGGER.debug("Failed to parse hs state update: '%s'",
payload)

if self._topic[CONF_HS_STATE_TOPIC] is not None:
await mqtt.async_subscribe(
self.hass, self._topic[CONF_HS_STATE_TOPIC], hs_received,
self._qos)
self._hs = (0, 0)
if self._optimistic_hs and last_state\
and last_state.attributes.get(ATTR_HS_COLOR):
self._hs = last_state.attributes.get(ATTR_HS_COLOR)
elif self._topic[CONF_HS_COMMAND_TOPIC] is not None:
self._hs = (0, 0)

@callback
def white_value_received(topic, payload, qos):
"""Handle new MQTT messages for white value."""
Expand Down Expand Up @@ -403,7 +443,7 @@ def white_value_received(topic, payload, qos):

@callback
def xy_received(topic, payload, qos):
"""Handle new MQTT messages for color."""
"""Handle new MQTT messages for xy color."""
payload = templates[CONF_XY](payload)
if not payload:
_LOGGER.debug("Ignoring empty xy-color message from '%s'",
Expand Down Expand Up @@ -539,6 +579,19 @@ async def async_turn_on(self, **kwargs):
self._hs = kwargs[ATTR_HS_COLOR]
should_update = True

if ATTR_HS_COLOR in kwargs and \
self._topic[CONF_HS_COMMAND_TOPIC] is not None:

hs_color = kwargs[ATTR_HS_COLOR]
mqtt.async_publish(
self.hass, self._topic[CONF_HS_COMMAND_TOPIC],
'{},{}'.format(*hs_color), self._qos,
self._retain)

if self._optimistic_hs:
self._hs = kwargs[ATTR_HS_COLOR]
should_update = True

if ATTR_HS_COLOR in kwargs and \
self._topic[CONF_XY_COMMAND_TOPIC] is not None:

Expand Down
1 change: 1 addition & 0 deletions homeassistant/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
CONF_HEADERS = 'headers'
CONF_HOST = 'host'
CONF_HOSTS = 'hosts'
CONF_HS = 'hs'
CONF_ICON = 'icon'
CONF_ICON_TEMPLATE = 'icon_template'
CONF_INCLUDE = 'include'
Expand Down
82 changes: 77 additions & 5 deletions tests/components/light/test_mqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,21 @@
payload_on: "on"
payload_off: "off"

Configuration for HS Version with brightness:

light:
platform: mqtt
name: "Office Light HS"
state_topic: "office/hs1/light/status"
command_topic: "office/hs1/light/switch"
brightness_state_topic: "office/hs1/brightness/status"
brightness_command_topic: "office/hs1/brightness/set"
hs_state_topic: "office/hs1/hs/status"
hs_command_topic: "office/hs1/hs/set"
qos: 0
payload_on: "on"
payload_off: "off"

"""
import unittest
from unittest import mock
Expand Down Expand Up @@ -180,7 +195,7 @@ def test_fail_setup_if_no_command_topic(self):
})
self.assertIsNone(self.hass.states.get('light.test'))

def test_no_color_brightness_color_temp_white_xy_if_no_topics(self):
def test_no_color_brightness_color_temp_hs_white_xy_if_no_topics(self):
"""Test if there is no color and brightness if no topic."""
with assert_setup_component(1, light.DOMAIN):
assert setup_component(self.hass, light.DOMAIN, {
Expand All @@ -197,6 +212,7 @@ def test_no_color_brightness_color_temp_white_xy_if_no_topics(self):
self.assertIsNone(state.attributes.get('rgb_color'))
self.assertIsNone(state.attributes.get('brightness'))
self.assertIsNone(state.attributes.get('color_temp'))
self.assertIsNone(state.attributes.get('hs_color'))
self.assertIsNone(state.attributes.get('white_value'))
self.assertIsNone(state.attributes.get('xy_color'))

Expand All @@ -208,6 +224,7 @@ def test_no_color_brightness_color_temp_white_xy_if_no_topics(self):
self.assertIsNone(state.attributes.get('rgb_color'))
self.assertIsNone(state.attributes.get('brightness'))
self.assertIsNone(state.attributes.get('color_temp'))
self.assertIsNone(state.attributes.get('hs_color'))
self.assertIsNone(state.attributes.get('white_value'))
self.assertIsNone(state.attributes.get('xy_color'))

Expand All @@ -226,6 +243,8 @@ def test_controlling_state_via_topic(self):
'color_temp_command_topic': 'test_light_rgb/color_temp/set',
'effect_state_topic': 'test_light_rgb/effect/status',
'effect_command_topic': 'test_light_rgb/effect/set',
'hs_state_topic': 'test_light_rgb/hs/status',
'hs_command_topic': 'test_light_rgb/hs/set',
'white_value_state_topic': 'test_light_rgb/white_value/status',
'white_value_command_topic': 'test_light_rgb/white_value/set',
'xy_state_topic': 'test_light_rgb/xy/status',
Expand All @@ -244,6 +263,7 @@ def test_controlling_state_via_topic(self):
self.assertIsNone(state.attributes.get('brightness'))
self.assertIsNone(state.attributes.get('color_temp'))
self.assertIsNone(state.attributes.get('effect'))
self.assertIsNone(state.attributes.get('hs_color'))
self.assertIsNone(state.attributes.get('white_value'))
self.assertIsNone(state.attributes.get('xy_color'))
self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE))
Expand All @@ -257,6 +277,7 @@ def test_controlling_state_via_topic(self):
self.assertEqual(255, state.attributes.get('brightness'))
self.assertEqual(150, state.attributes.get('color_temp'))
self.assertEqual('none', state.attributes.get('effect'))
self.assertEqual((0, 0), state.attributes.get('hs_color'))
self.assertEqual(255, state.attributes.get('white_value'))
self.assertEqual((0.323, 0.329), state.attributes.get('xy_color'))

Expand Down Expand Up @@ -309,6 +330,14 @@ def test_controlling_state_via_topic(self):
self.assertEqual((255, 255, 255),
light_state.attributes.get('rgb_color'))

fire_mqtt_message(self.hass, 'test_light_rgb/hs/status',
'200,50')
self.hass.block_till_done()

light_state = self.hass.states.get('light.test')
self.assertEqual((200, 50),
light_state.attributes.get('hs_color'))

fire_mqtt_message(self.hass, 'test_light_rgb/xy/status',
'0.675,0.322')
self.hass.block_till_done()
Expand Down Expand Up @@ -412,7 +441,7 @@ def test_white_value_controlling_scale(self):
light_state.attributes['white_value'])

def test_controlling_state_via_topic_with_templates(self):
"""Test the setting og the state with a template."""
"""Test the setting of the state with a template."""
config = {light.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
Expand All @@ -422,18 +451,21 @@ def test_controlling_state_via_topic_with_templates(self):
'rgb_command_topic': 'test_light_rgb/rgb/set',
'color_temp_command_topic': 'test_light_rgb/color_temp/set',
'effect_command_topic': 'test_light_rgb/effect/set',
'hs_command_topic': 'test_light_rgb/hs/set',
'white_value_command_topic': 'test_light_rgb/white_value/set',
'xy_command_topic': 'test_light_rgb/xy/set',
'brightness_state_topic': 'test_light_rgb/brightness/status',
'color_temp_state_topic': 'test_light_rgb/color_temp/status',
'effect_state_topic': 'test_light_rgb/effect/status',
'hs_state_topic': 'test_light_rgb/hs/status',
'rgb_state_topic': 'test_light_rgb/rgb/status',
'white_value_state_topic': 'test_light_rgb/white_value/status',
'xy_state_topic': 'test_light_rgb/xy/status',
'state_value_template': '{{ value_json.hello }}',
'brightness_value_template': '{{ value_json.hello }}',
'color_temp_value_template': '{{ value_json.hello }}',
'effect_value_template': '{{ value_json.hello }}',
'hs_value_template': '{{ value_json.hello | join(",") }}',
'rgb_value_template': '{{ value_json.hello | join(",") }}',
'white_value_template': '{{ value_json.hello }}',
'xy_value_template': '{{ value_json.hello | join(",") }}',
Expand All @@ -459,17 +491,28 @@ def test_controlling_state_via_topic_with_templates(self):
'{"hello": "rainbow"}')
fire_mqtt_message(self.hass, 'test_light_rgb/white_value/status',
'{"hello": "75"}')
fire_mqtt_message(self.hass, 'test_light_rgb/xy/status',
'{"hello": [0.123,0.123]}')
self.hass.block_till_done()

state = self.hass.states.get('light.test')
self.assertEqual(STATE_ON, state.state)
self.assertEqual(50, state.attributes.get('brightness'))
self.assertEqual((0, 123, 255), state.attributes.get('rgb_color'))
self.assertEqual((84, 169, 255), state.attributes.get('rgb_color'))
self.assertEqual(300, state.attributes.get('color_temp'))
self.assertEqual('rainbow', state.attributes.get('effect'))
self.assertEqual(75, state.attributes.get('white_value'))

fire_mqtt_message(self.hass, 'test_light_rgb/hs/status',
'{"hello": [100,50]}')
self.hass.block_till_done()

state = self.hass.states.get('light.test')
self.assertEqual((100, 50), state.attributes.get('hs_color'))

fire_mqtt_message(self.hass, 'test_light_rgb/xy/status',
'{"hello": [0.123,0.123]}')
self.hass.block_till_done()

state = self.hass.states.get('light.test')
self.assertEqual((0.14, 0.131), state.attributes.get('xy_color'))

def test_sending_mqtt_commands_and_optimistic(self):
Expand All @@ -482,6 +525,7 @@ def test_sending_mqtt_commands_and_optimistic(self):
'rgb_command_topic': 'test_light_rgb/rgb/set',
'color_temp_command_topic': 'test_light_rgb/color_temp/set',
'effect_command_topic': 'test_light_rgb/effect/set',
'hs_command_topic': 'test_light_rgb/hs/set',
'white_value_command_topic': 'test_light_rgb/white_value/set',
'xy_command_topic': 'test_light_rgb/xy/set',
'effect_list': ['colorloop', 'random'],
Expand Down Expand Up @@ -529,6 +573,8 @@ def test_sending_mqtt_commands_and_optimistic(self):
self.mock_publish.reset_mock()
common.turn_on(self.hass, 'light.test',
brightness=50, xy_color=[0.123, 0.123])
common.turn_on(self.hass, 'light.test',
brightness=50, hs_color=[359, 78])
common.turn_on(self.hass, 'light.test', rgb_color=[255, 128, 0],
white_value=80)
self.hass.block_till_done()
Expand All @@ -537,6 +583,7 @@ def test_sending_mqtt_commands_and_optimistic(self):
mock.call('test_light_rgb/set', 'on', 2, False),
mock.call('test_light_rgb/rgb/set', '255,128,0', 2, False),
mock.call('test_light_rgb/brightness/set', 50, 2, False),
mock.call('test_light_rgb/hs/set', '359.0,78.0', 2, False),
mock.call('test_light_rgb/white_value/set', 80, 2, False),
mock.call('test_light_rgb/xy/set', '0.14,0.131', 2, False),
], any_order=True)
Expand All @@ -545,6 +592,7 @@ def test_sending_mqtt_commands_and_optimistic(self):
self.assertEqual(STATE_ON, state.state)
self.assertEqual((255, 128, 0), state.attributes['rgb_color'])
self.assertEqual(50, state.attributes['brightness'])
self.assertEqual((30.118, 100), state.attributes['hs_color'])
self.assertEqual(80, state.attributes['white_value'])
self.assertEqual((0.611, 0.375), state.attributes['xy_color'])

Expand Down Expand Up @@ -652,6 +700,30 @@ def test_show_effect_only_if_command_topic(self):
self.assertEqual(STATE_ON, state.state)
self.assertEqual('none', state.attributes.get('effect'))

def test_show_hs_if_only_command_topic(self):
"""Test the hs if only a command topic is present."""
config = {light.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'hs_command_topic': 'test_light_rgb/hs/set',
'command_topic': 'test_light_rgb/set',
'state_topic': 'test_light_rgb/status',
}}

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.assertIsNone(state.attributes.get('hs_color'))

fire_mqtt_message(self.hass, 'test_light_rgb/status', 'ON')
self.hass.block_till_done()

state = self.hass.states.get('light.test')
self.assertEqual(STATE_ON, state.state)
self.assertEqual((0, 0), state.attributes.get('hs_color'))

def test_show_white_value_if_only_command_topic(self):
"""Test the white_value if only a command topic is present."""
config = {light.DOMAIN: {
Expand Down
0