Fix discovery update of MQTT light (#39325)

This commit is contained in:
Erik Montnemery 2020-09-02 10:52:33 +02:00 committed by GitHub
parent e55a014e94
commit 4c6960ed36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 105 additions and 69 deletions

View File

@ -39,7 +39,7 @@ async def async_setup_platform(
hass: HomeAssistantType, config: ConfigType, async_add_entities, discovery_info=None
):
"""Set up MQTT light through configuration.yaml."""
await _async_setup_entity(config, async_add_entities)
await _async_setup_entity(hass, config, async_add_entities)
async def async_setup_entry(hass, config_entry, async_add_entities):
@ -51,7 +51,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
try:
config = PLATFORM_SCHEMA(discovery_payload)
await _async_setup_entity(
config, async_add_entities, config_entry, discovery_data
hass, config, async_add_entities, config_entry, discovery_data
)
except Exception:
clear_discovery_hash(hass, discovery_data[ATTR_DISCOVERY_HASH])
@ -63,7 +63,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
async def _async_setup_entity(
config, async_add_entities, config_entry=None, discovery_data=None
hass, config, async_add_entities, config_entry=None, discovery_data=None
):
"""Set up a MQTT Light."""
setup_entity = {
@ -72,5 +72,5 @@ async def _async_setup_entity(
"template": async_setup_entity_template,
}
await setup_entity[config[CONF_SCHEMA]](
config, async_add_entities, config_entry, discovery_data
hass, config, async_add_entities, config_entry, discovery_data
)

View File

@ -29,21 +29,12 @@ from homeassistant.components.mqtt import (
subscription,
)
from homeassistant.const import (
CONF_BRIGHTNESS,
CONF_COLOR_TEMP,
CONF_DEVICE,
CONF_EFFECT,
CONF_HS,
CONF_NAME,
CONF_OPTIMISTIC,
CONF_PAYLOAD_OFF,
CONF_PAYLOAD_ON,
CONF_RGB,
CONF_STATE,
CONF_UNIQUE_ID,
CONF_VALUE_TEMPLATE,
CONF_WHITE_VALUE,
CONF_XY,
STATE_ON,
)
from homeassistant.core import callback
@ -97,6 +88,18 @@ DEFAULT_ON_COMMAND_TYPE = "last"
VALUES_ON_COMMAND_TYPE = ["first", "last", "brightness"]
COMMAND_TEMPLATE_KEYS = [CONF_COLOR_TEMP_COMMAND_TEMPLATE, CONF_RGB_COMMAND_TEMPLATE]
VALUE_TEMPLATE_KEYS = [
CONF_BRIGHTNESS_VALUE_TEMPLATE,
CONF_COLOR_TEMP_VALUE_TEMPLATE,
CONF_EFFECT_VALUE_TEMPLATE,
CONF_HS_VALUE_TEMPLATE,
CONF_RGB_VALUE_TEMPLATE,
CONF_STATE_VALUE_TEMPLATE,
CONF_WHITE_VALUE_TEMPLATE,
CONF_XY_VALUE_TEMPLATE,
]
PLATFORM_SCHEMA_BASIC = (
mqtt.MQTT_RW_PLATFORM_SCHEMA.extend(
{
@ -151,12 +154,10 @@ PLATFORM_SCHEMA_BASIC = (
async def async_setup_entity_basic(
config, async_add_entities, config_entry, discovery_data=None
hass, config, async_add_entities, config_entry, discovery_data=None
):
"""Set up a MQTT Light."""
config.setdefault(CONF_STATE_VALUE_TEMPLATE, config.get(CONF_VALUE_TEMPLATE))
async_add_entities([MqttLight(config, config_entry, discovery_data)])
async_add_entities([MqttLight(hass, config, config_entry, discovery_data)])
class MqttLight(
@ -169,8 +170,9 @@ class MqttLight(
):
"""Representation of a MQTT light."""
def __init__(self, config, config_entry, discovery_data):
def __init__(self, hass, config, config_entry, discovery_data):
"""Initialize MQTT light."""
self.hass = hass
self._state = False
self._sub_state = None
self._brightness = None
@ -181,7 +183,8 @@ class MqttLight(
self._topic = None
self._payload = None
self._templates = None
self._command_templates = None
self._value_templates = None
self._optimistic = False
self._optimistic_rgb = False
self._optimistic_brightness = False
@ -244,20 +247,24 @@ class MqttLight(
}
self._topic = topic
self._payload = {"on": config[CONF_PAYLOAD_ON], "off": config[CONF_PAYLOAD_OFF]}
self._templates = {
CONF_BRIGHTNESS: config.get(CONF_BRIGHTNESS_VALUE_TEMPLATE),
CONF_COLOR_TEMP: config.get(CONF_COLOR_TEMP_VALUE_TEMPLATE),
CONF_COLOR_TEMP_COMMAND_TEMPLATE: config.get(
CONF_COLOR_TEMP_COMMAND_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),
CONF_WHITE_VALUE: config.get(CONF_WHITE_VALUE_TEMPLATE),
CONF_XY: config.get(CONF_XY_VALUE_TEMPLATE),
}
value_templates = {}
for key in VALUE_TEMPLATE_KEYS:
value_templates[key] = lambda value: value
for key in VALUE_TEMPLATE_KEYS & config.keys():
tpl = config[key]
value_templates[key] = tpl.async_render_with_possible_json_value
tpl.hass = self.hass
self._value_templates = value_templates
command_templates = {}
for key in COMMAND_TEMPLATE_KEYS:
command_templates[key] = None
for key in COMMAND_TEMPLATE_KEYS & config.keys():
tpl = config[key]
command_templates[key] = tpl.async_render
tpl.hass = self.hass
self._command_templates = command_templates
optimistic = config[CONF_OPTIMISTIC]
self._optimistic = optimistic or topic[CONF_STATE_TOPIC] is None
@ -286,13 +293,6 @@ class MqttLight(
async def _subscribe_topics(self):
"""(Re)Subscribe to topics."""
topics = {}
templates = {}
for key, tpl in list(self._templates.items()):
if tpl is None:
templates[key] = lambda value: value
else:
tpl.hass = self.hass
templates[key] = tpl.async_render_with_possible_json_value
last_state = await self.async_get_last_state()
@ -300,7 +300,7 @@ class MqttLight(
@log_messages(self.hass, self.entity_id)
def state_received(msg):
"""Handle new MQTT messages."""
payload = templates[CONF_STATE](msg.payload)
payload = self._value_templates[CONF_STATE_VALUE_TEMPLATE](msg.payload)
if not payload:
_LOGGER.debug("Ignoring empty state message from '%s'", msg.topic)
return
@ -324,7 +324,7 @@ class MqttLight(
@log_messages(self.hass, self.entity_id)
def brightness_received(msg):
"""Handle new MQTT messages for the brightness."""
payload = templates[CONF_BRIGHTNESS](msg.payload)
payload = self._value_templates[CONF_BRIGHTNESS_VALUE_TEMPLATE](msg.payload)
if not payload:
_LOGGER.debug("Ignoring empty brightness message from '%s'", msg.topic)
return
@ -356,7 +356,7 @@ class MqttLight(
@log_messages(self.hass, self.entity_id)
def rgb_received(msg):
"""Handle new MQTT messages for RGB."""
payload = templates[CONF_RGB](msg.payload)
payload = self._value_templates[CONF_RGB_VALUE_TEMPLATE](msg.payload)
if not payload:
_LOGGER.debug("Ignoring empty rgb message from '%s'", msg.topic)
return
@ -388,7 +388,7 @@ class MqttLight(
@log_messages(self.hass, self.entity_id)
def color_temp_received(msg):
"""Handle new MQTT messages for color temperature."""
payload = templates[CONF_COLOR_TEMP](msg.payload)
payload = self._value_templates[CONF_COLOR_TEMP_VALUE_TEMPLATE](msg.payload)
if not payload:
_LOGGER.debug("Ignoring empty color temp message from '%s'", msg.topic)
return
@ -418,7 +418,7 @@ class MqttLight(
@log_messages(self.hass, self.entity_id)
def effect_received(msg):
"""Handle new MQTT messages for effect."""
payload = templates[CONF_EFFECT](msg.payload)
payload = self._value_templates[CONF_EFFECT_VALUE_TEMPLATE](msg.payload)
if not payload:
_LOGGER.debug("Ignoring empty effect message from '%s'", msg.topic)
return
@ -448,7 +448,7 @@ class MqttLight(
@log_messages(self.hass, self.entity_id)
def hs_received(msg):
"""Handle new MQTT messages for hs color."""
payload = templates[CONF_HS](msg.payload)
payload = self._value_templates[CONF_HS_VALUE_TEMPLATE](msg.payload)
if not payload:
_LOGGER.debug("Ignoring empty hs message from '%s'", msg.topic)
return
@ -480,7 +480,7 @@ class MqttLight(
@log_messages(self.hass, self.entity_id)
def white_value_received(msg):
"""Handle new MQTT messages for white value."""
payload = templates[CONF_WHITE_VALUE](msg.payload)
payload = self._value_templates[CONF_WHITE_VALUE_TEMPLATE](msg.payload)
if not payload:
_LOGGER.debug("Ignoring empty white value message from '%s'", msg.topic)
return
@ -512,7 +512,7 @@ class MqttLight(
@log_messages(self.hass, self.entity_id)
def xy_received(msg):
"""Handle new MQTT messages for xy color."""
payload = templates[CONF_XY](msg.payload)
payload = self._value_templates[CONF_XY_VALUE_TEMPLATE](msg.payload)
if not payload:
_LOGGER.debug("Ignoring empty xy-color message from '%s'", msg.topic)
return
@ -692,11 +692,9 @@ class MqttLight(
rgb = color_util.color_hsv_to_RGB(
hs_color[0], hs_color[1], brightness / 255 * 100
)
tpl = self._templates[CONF_RGB_COMMAND_TEMPLATE]
tpl = self._command_templates[CONF_RGB_COMMAND_TEMPLATE]
if tpl:
rgb_color_str = tpl.async_render(
{"red": rgb[0], "green": rgb[1], "blue": rgb[2]}
)
rgb_color_str = tpl({"red": rgb[0], "green": rgb[1], "blue": rgb[2]})
else:
rgb_color_str = f"{rgb[0]},{rgb[1]},{rgb[2]}"
@ -772,11 +770,9 @@ class MqttLight(
rgb = color_util.color_hsv_to_RGB(
self._hs[0], self._hs[1], kwargs[ATTR_BRIGHTNESS] / 255 * 100
)
tpl = self._templates[CONF_RGB_COMMAND_TEMPLATE]
tpl = self._command_templates[CONF_RGB_COMMAND_TEMPLATE]
if tpl:
rgb_color_str = tpl.async_render(
{"red": rgb[0], "green": rgb[1], "blue": rgb[2]}
)
rgb_color_str = tpl({"red": rgb[0], "green": rgb[1], "blue": rgb[2]})
else:
rgb_color_str = f"{rgb[0]},{rgb[1]},{rgb[2]}"
@ -797,10 +793,10 @@ class MqttLight(
and self._topic[CONF_COLOR_TEMP_COMMAND_TOPIC] is not None
):
color_temp = int(kwargs[ATTR_COLOR_TEMP])
tpl = self._templates[CONF_COLOR_TEMP_COMMAND_TEMPLATE]
tpl = self._command_templates[CONF_COLOR_TEMP_COMMAND_TEMPLATE]
if tpl:
color_temp = tpl.async_render({"value": color_temp})
color_temp = tpl({"value": color_temp})
mqtt.async_publish(
self.hass,

View File

@ -125,7 +125,7 @@ PLATFORM_SCHEMA_JSON = (
async def async_setup_entity_json(
config: ConfigType, async_add_entities, config_entry, discovery_data
hass, config: ConfigType, async_add_entities, config_entry, discovery_data
):
"""Set up a MQTT JSON Light."""
async_add_entities([MqttLightJson(config, config_entry, discovery_data)])

View File

@ -98,7 +98,7 @@ PLATFORM_SCHEMA_TEMPLATE = (
async def async_setup_entity_template(
config, async_add_entities, config_entry, discovery_data
hass, config, async_add_entities, config_entry, discovery_data
):
"""Set up a MQTT Template light."""
async_add_entities([MqttLightTemplate(config, config_entry, discovery_data)])

View File

@ -503,7 +503,20 @@ async def help_test_discovery_removal(hass, mqtt_mock, caplog, domain, data):
assert state is None
async def help_test_discovery_update(hass, mqtt_mock, caplog, domain, data1, data2):
async def help_test_discovery_update(
hass,
mqtt_mock,
caplog,
domain,
discovery_data1,
discovery_data2,
state_data1=None,
state_data2=None,
state1=None,
state2=None,
attributes1=None,
attributes2=None,
):
"""Test update of discovered component.
This is a test helper for the MqttDiscoveryUpdate mixin.
@ -511,19 +524,35 @@ async def help_test_discovery_update(hass, mqtt_mock, caplog, domain, data1, dat
entry = hass.config_entries.async_entries(mqtt.DOMAIN)[0]
await async_start(hass, "homeassistant", entry)
async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data1)
async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", discovery_data1)
await hass.async_block_till_done()
if state_data1:
for (topic, data) in state_data1:
async_fire_mqtt_message(hass, topic, data)
state = hass.states.get(f"{domain}.beer")
assert state is not None
assert state.name == "Beer"
if state1:
assert state.state == state1
if attributes1:
for (attr, value) in attributes1:
assert state.attributes.get(attr) == value
async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data2)
async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", discovery_data2)
await hass.async_block_till_done()
if state_data2:
for (topic, data) in state_data2:
async_fire_mqtt_message(hass, topic, data)
state = hass.states.get(f"{domain}.beer")
assert state is not None
assert state.name == "Milk"
if state2:
assert state.state == state2
if attributes2:
for (attr, value) in attributes2:
assert state.attributes.get(attr) == value
state = hass.states.get(f"{domain}.milk")
assert state is None

View File

@ -716,9 +716,8 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock):
hass, "light.test", brightness=50, xy_color=[0.123, 0.123]
)
await common.async_turn_on(hass, "light.test", brightness=50, hs_color=[359, 78])
await common.async_turn_on(
hass, "light.test", rgb_color=[255, 128, 0], white_value=80
)
await common.async_turn_on(hass, "light.test", rgb_color=[255, 128, 0])
await common.async_turn_on(hass, "light.test", white_value=80, color_temp=125)
mqtt_mock.async_publish.assert_has_calls(
[
@ -728,6 +727,7 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock):
call("test_light_rgb/hs/set", "359.0,78.0", 2, False),
call("test_light_rgb/white_value/set", 80, 2, False),
call("test_light_rgb/xy/set", "0.14,0.131", 2, False),
call("test_light_rgb/color_temp/set", 125, 2, False),
],
any_order=True,
)
@ -1439,15 +1439,26 @@ async def test_discovery_update_light(hass, mqtt_mock, caplog):
data1 = (
'{ "name": "Beer",'
' "state_topic": "test_topic",'
' "command_topic": "test_topic" }'
' "command_topic": "test_topic",'
' "state_value_template": "{{value_json.power1}}" }'
)
data2 = (
'{ "name": "Milk",'
' "state_topic": "test_topic",'
' "command_topic": "test_topic" }'
' "command_topic": "test_topic",'
' "state_value_template": "{{value_json.power2}}" }'
)
await help_test_discovery_update(
hass, mqtt_mock, caplog, light.DOMAIN, data1, data2
hass,
mqtt_mock,
caplog,
light.DOMAIN,
data1,
data2,
state_data1=[("test_topic", '{"power1":"ON"}')],
state1="on",
state_data2=[("test_topic", '{"power2":"OFF"}')],
state2="off",
)