mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 12:47:08 +00:00
Fix discovery update of MQTT light (#39325)
This commit is contained in:
parent
e55a014e94
commit
4c6960ed36
@ -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
|
||||
)
|
||||
|
@ -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,
|
||||
|
@ -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)])
|
||||
|
@ -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)])
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
)
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user