From 4d6e694d1488634439420411867a6d717d41587b Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 10 Sep 2020 20:52:23 +0200 Subject: [PATCH] Fix discovery update of MQTT state templates (#39901) --- homeassistant/components/mqtt/__init__.py | 2 +- .../components/mqtt/alarm_control_panel.py | 25 +- .../components/mqtt/binary_sensor.py | 25 +- homeassistant/components/mqtt/cover.py | 18 +- homeassistant/components/mqtt/fan.py | 31 +- .../components/mqtt/light/schema_basic.py | 28 +- homeassistant/components/mqtt/lock.py | 17 +- homeassistant/components/mqtt/sensor.py | 27 +- homeassistant/components/mqtt/switch.py | 17 +- .../mqtt/test_alarm_control_panel.py | 58 ++- tests/components/mqtt/test_binary_sensor.py | 62 ++- tests/components/mqtt/test_common.py | 42 +- tests/components/mqtt/test_cover.py | 8 + tests/components/mqtt/test_light.py | 471 +++++++++++++++++- tests/components/mqtt/test_sensor.py | 68 ++- tests/components/mqtt/test_switch.py | 82 ++- 16 files changed, 853 insertions(+), 128 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index d4263ee6ba3..2b5dca6474f 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -1305,7 +1305,7 @@ class MqttDiscoveryUpdate(Entity): debug_info.add_entity_discovery_data( self.hass, self._discovery_data, self.entity_id ) - # Set in case the entity has been removed and is re-added + # Set in case the entity has been removed and is re-added, for example when changing entity_id set_discovery_hash(self.hass, discovery_hash) self._remove_signal = async_dispatcher_connect( self.hass, diff --git a/homeassistant/components/mqtt/alarm_control_panel.py b/homeassistant/components/mqtt/alarm_control_panel.py index 505c7616a0a..6d33175e6ca 100644 --- a/homeassistant/components/mqtt/alarm_control_panel.py +++ b/homeassistant/components/mqtt/alarm_control_panel.py @@ -104,7 +104,7 @@ async def async_setup_platform( ): """Set up MQTT alarm control panel through configuration.yaml.""" await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - 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): @@ -116,7 +116,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]) @@ -128,10 +128,10 @@ 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 the MQTT Alarm Control Panel platform.""" - async_add_entities([MqttAlarm(config, config_entry, discovery_data)]) + async_add_entities([MqttAlarm(hass, config, config_entry, discovery_data)]) class MqttAlarm( @@ -143,13 +143,16 @@ class MqttAlarm( ): """Representation of a MQTT alarm status.""" - def __init__(self, config, config_entry, discovery_data): + def __init__(self, hass, config, config_entry, discovery_data): """Init the MQTT Alarm Control Panel.""" + self.hass = hass self._state = None - self._config = config self._unique_id = config.get(CONF_UNIQUE_ID) self._sub_state = None + # Load config + self._setup_from_config(config) + device_config = config.get(CONF_DEVICE) MqttAttributes.__init__(self, config) @@ -165,26 +168,30 @@ class MqttAlarm( async def discovery_update(self, discovery_payload): """Handle updated discovery message.""" config = PLATFORM_SCHEMA(discovery_payload) - self._config = config + self._setup_from_config(config) await self.attributes_discovery_update(config) await self.availability_discovery_update(config) await self.device_info_discovery_update(config) await self._subscribe_topics() self.async_write_ha_state() - async def _subscribe_topics(self): - """(Re)Subscribe to topics.""" + def _setup_from_config(self, config): + self._config = config value_template = self._config.get(CONF_VALUE_TEMPLATE) if value_template is not None: value_template.hass = self.hass command_template = self._config[CONF_COMMAND_TEMPLATE] command_template.hass = self.hass + async def _subscribe_topics(self): + """(Re)Subscribe to topics.""" + @callback @log_messages(self.hass, self.entity_id) def message_received(msg): """Run when new MQTT message has been received.""" payload = msg.payload + value_template = self._config.get(CONF_VALUE_TEMPLATE) if value_template is not None: payload = value_template.async_render_with_possible_json_value( msg.payload, self._state diff --git a/homeassistant/components/mqtt/binary_sensor.py b/homeassistant/components/mqtt/binary_sensor.py index c9fd2bba2b1..cf7b0fc3ca6 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -76,7 +76,7 @@ async def async_setup_platform( ): """Set up MQTT binary sensor through configuration.yaml.""" await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - 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): @@ -88,7 +88,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]) @@ -100,10 +100,10 @@ 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 the MQTT binary sensor.""" - async_add_entities([MqttBinarySensor(config, config_entry, discovery_data)]) + async_add_entities([MqttBinarySensor(hass, config, config_entry, discovery_data)]) class MqttBinarySensor( @@ -115,9 +115,9 @@ class MqttBinarySensor( ): """Representation a binary sensor that is updated by MQTT.""" - def __init__(self, config, config_entry, discovery_data): + def __init__(self, hass, config, config_entry, discovery_data): """Initialize the MQTT binary sensor.""" - self._config = config + self.hass = hass self._unique_id = config.get(CONF_UNIQUE_ID) self._state = None self._sub_state = None @@ -128,6 +128,10 @@ class MqttBinarySensor( self._expired = True else: self._expired = None + + # Load config + self._setup_from_config(config) + device_config = config.get(CONF_DEVICE) MqttAttributes.__init__(self, config) @@ -143,19 +147,22 @@ class MqttBinarySensor( async def discovery_update(self, discovery_payload): """Handle updated discovery message.""" config = PLATFORM_SCHEMA(discovery_payload) - self._config = config + self._setup_from_config(config) await self.attributes_discovery_update(config) await self.availability_discovery_update(config) await self.device_info_discovery_update(config) await self._subscribe_topics() self.async_write_ha_state() - async def _subscribe_topics(self): - """(Re)Subscribe to topics.""" + def _setup_from_config(self, config): + self._config = config value_template = self._config.get(CONF_VALUE_TEMPLATE) if value_template is not None: value_template.hass = self.hass + async def _subscribe_topics(self): + """(Re)Subscribe to topics.""" + @callback def off_delay_listener(now): """Switch device off after a delay.""" diff --git a/homeassistant/components/mqtt/cover.py b/homeassistant/components/mqtt/cover.py index b8a5b778a98..20146b0b7d6 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -174,7 +174,7 @@ async def async_setup_platform( ): """Set up MQTT cover through configuration.yaml.""" await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - 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): @@ -186,7 +186,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]) @@ -198,10 +198,10 @@ 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 the MQTT Cover.""" - async_add_entities([MqttCover(config, config_entry, discovery_data)]) + async_add_entities([MqttCover(hass, config, config_entry, discovery_data)]) class MqttCover( @@ -213,8 +213,9 @@ class MqttCover( ): """Representation of a cover that can be controlled using MQTT.""" - def __init__(self, config, config_entry, discovery_data): + def __init__(self, hass, config, config_entry, discovery_data): """Initialize the cover.""" + self.hass = hass self._unique_id = config.get(CONF_UNIQUE_ID) self._position = None self._state = None @@ -257,8 +258,6 @@ class MqttCover( ) self._tilt_optimistic = config[CONF_TILT_STATE_OPTIMISTIC] - async def _subscribe_topics(self): - """(Re)Subscribe to topics.""" template = self._config.get(CONF_VALUE_TEMPLATE) if template is not None: template.hass = self.hass @@ -269,6 +268,8 @@ class MqttCover( if tilt_status_template is not None: tilt_status_template.hass = self.hass + async def _subscribe_topics(self): + """(Re)Subscribe to topics.""" topics = {} @callback @@ -276,6 +277,7 @@ class MqttCover( def tilt_message_received(msg): """Handle tilt updates.""" payload = msg.payload + tilt_status_template = self._config.get(CONF_TILT_STATUS_TEMPLATE) if tilt_status_template is not None: payload = tilt_status_template.async_render_with_possible_json_value( payload @@ -296,6 +298,7 @@ class MqttCover( def state_message_received(msg): """Handle new MQTT state messages.""" payload = msg.payload + template = self._config.get(CONF_VALUE_TEMPLATE) if template is not None: payload = template.async_render_with_possible_json_value(payload) @@ -321,6 +324,7 @@ class MqttCover( def position_message_received(msg): """Handle new MQTT state messages.""" payload = msg.payload + template = self._config.get(CONF_VALUE_TEMPLATE) if template is not None: payload = template.async_render_with_possible_json_value(payload) diff --git a/homeassistant/components/mqtt/fan.py b/homeassistant/components/mqtt/fan.py index b1ec7aabeef..14469e415e0 100644 --- a/homeassistant/components/mqtt/fan.py +++ b/homeassistant/components/mqtt/fan.py @@ -115,7 +115,7 @@ async def async_setup_platform( ): """Set up MQTT fan through configuration.yaml.""" await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - 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): @@ -127,7 +127,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]) @@ -139,10 +139,10 @@ 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 the MQTT fan.""" - async_add_entities([MqttFan(config, config_entry, discovery_data)]) + async_add_entities([MqttFan(hass, config, config_entry, discovery_data)]) class MqttFan( @@ -154,8 +154,9 @@ class MqttFan( ): """A MQTT fan component.""" - def __init__(self, config, config_entry, discovery_data): + def __init__(self, hass, config, config_entry, discovery_data): """Initialize the MQTT fan.""" + self.hass = hass self._unique_id = config.get(CONF_UNIQUE_ID) self._state = False self._speed = None @@ -242,22 +243,22 @@ class MqttFan( self._topic[CONF_SPEED_COMMAND_TOPIC] is not None and SUPPORT_SET_SPEED ) + for key, tpl in list(self._templates.items()): + if tpl is None: + self._templates[key] = lambda value: value + else: + tpl.hass = self.hass + self._templates[key] = tpl.async_render_with_possible_json_value + 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 @callback @log_messages(self.hass, self.entity_id) def state_received(msg): """Handle new received MQTT message.""" - payload = templates[CONF_STATE](msg.payload) + payload = self._templates[CONF_STATE](msg.payload) if payload == self._payload["STATE_ON"]: self._state = True elif payload == self._payload["STATE_OFF"]: @@ -275,7 +276,7 @@ class MqttFan( @log_messages(self.hass, self.entity_id) def speed_received(msg): """Handle new received MQTT message for the speed.""" - payload = templates[ATTR_SPEED](msg.payload) + payload = self._templates[ATTR_SPEED](msg.payload) if payload == self._payload["SPEED_LOW"]: self._speed = SPEED_LOW elif payload == self._payload["SPEED_MEDIUM"]: @@ -298,7 +299,7 @@ class MqttFan( @log_messages(self.hass, self.entity_id) def oscillation_received(msg): """Handle new received MQTT message for the oscillation.""" - payload = templates[OSCILLATION](msg.payload) + payload = self._templates[OSCILLATION](msg.payload) if payload == self._payload["OSCILLATE_ON_PAYLOAD"]: self._oscillation = True elif payload == self._payload["OSCILLATE_OFF_PAYLOAD"]: diff --git a/homeassistant/components/mqtt/light/schema_basic.py b/homeassistant/components/mqtt/light/schema_basic.py index e19fcbf0e40..d7c373bf72b 100644 --- a/homeassistant/components/mqtt/light/schema_basic.py +++ b/homeassistant/components/mqtt/light/schema_basic.py @@ -254,7 +254,7 @@ class MqttLight( value_templates = {} for key in VALUE_TEMPLATE_KEYS: - value_templates[key] = lambda value: value + 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 @@ -304,7 +304,9 @@ class MqttLight( @log_messages(self.hass, self.entity_id) def state_received(msg): """Handle new MQTT messages.""" - payload = self._value_templates[CONF_STATE_VALUE_TEMPLATE](msg.payload) + payload = self._value_templates[CONF_STATE_VALUE_TEMPLATE]( + msg.payload, None + ) if not payload: _LOGGER.debug("Ignoring empty state message from '%s'", msg.topic) return @@ -328,7 +330,9 @@ class MqttLight( @log_messages(self.hass, self.entity_id) def brightness_received(msg): """Handle new MQTT messages for the brightness.""" - payload = self._value_templates[CONF_BRIGHTNESS_VALUE_TEMPLATE](msg.payload) + payload = self._value_templates[CONF_BRIGHTNESS_VALUE_TEMPLATE]( + msg.payload, None + ) if not payload: _LOGGER.debug("Ignoring empty brightness message from '%s'", msg.topic) return @@ -360,7 +364,7 @@ class MqttLight( @log_messages(self.hass, self.entity_id) def rgb_received(msg): """Handle new MQTT messages for RGB.""" - payload = self._value_templates[CONF_RGB_VALUE_TEMPLATE](msg.payload) + payload = self._value_templates[CONF_RGB_VALUE_TEMPLATE](msg.payload, None) if not payload: _LOGGER.debug("Ignoring empty rgb message from '%s'", msg.topic) return @@ -392,7 +396,9 @@ class MqttLight( @log_messages(self.hass, self.entity_id) def color_temp_received(msg): """Handle new MQTT messages for color temperature.""" - payload = self._value_templates[CONF_COLOR_TEMP_VALUE_TEMPLATE](msg.payload) + payload = self._value_templates[CONF_COLOR_TEMP_VALUE_TEMPLATE]( + msg.payload, None + ) if not payload: _LOGGER.debug("Ignoring empty color temp message from '%s'", msg.topic) return @@ -422,7 +428,9 @@ class MqttLight( @log_messages(self.hass, self.entity_id) def effect_received(msg): """Handle new MQTT messages for effect.""" - payload = self._value_templates[CONF_EFFECT_VALUE_TEMPLATE](msg.payload) + payload = self._value_templates[CONF_EFFECT_VALUE_TEMPLATE]( + msg.payload, None + ) if not payload: _LOGGER.debug("Ignoring empty effect message from '%s'", msg.topic) return @@ -452,7 +460,7 @@ class MqttLight( @log_messages(self.hass, self.entity_id) def hs_received(msg): """Handle new MQTT messages for hs color.""" - payload = self._value_templates[CONF_HS_VALUE_TEMPLATE](msg.payload) + payload = self._value_templates[CONF_HS_VALUE_TEMPLATE](msg.payload, None) if not payload: _LOGGER.debug("Ignoring empty hs message from '%s'", msg.topic) return @@ -484,7 +492,9 @@ class MqttLight( @log_messages(self.hass, self.entity_id) def white_value_received(msg): """Handle new MQTT messages for white value.""" - payload = self._value_templates[CONF_WHITE_VALUE_TEMPLATE](msg.payload) + payload = self._value_templates[CONF_WHITE_VALUE_TEMPLATE]( + msg.payload, None + ) if not payload: _LOGGER.debug("Ignoring empty white value message from '%s'", msg.topic) return @@ -516,7 +526,7 @@ class MqttLight( @log_messages(self.hass, self.entity_id) def xy_received(msg): """Handle new MQTT messages for xy color.""" - payload = self._value_templates[CONF_XY_VALUE_TEMPLATE](msg.payload) + payload = self._value_templates[CONF_XY_VALUE_TEMPLATE](msg.payload, None) if not payload: _LOGGER.debug("Ignoring empty xy-color message from '%s'", msg.topic) return diff --git a/homeassistant/components/mqtt/lock.py b/homeassistant/components/mqtt/lock.py index f6d56a30431..aea1e40b0f9 100644 --- a/homeassistant/components/mqtt/lock.py +++ b/homeassistant/components/mqtt/lock.py @@ -77,7 +77,7 @@ async def async_setup_platform( ): """Set up MQTT lock panel through configuration.yaml.""" await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - 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): @@ -89,7 +89,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]) @@ -101,10 +101,10 @@ 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 the MQTT Lock platform.""" - async_add_entities([MqttLock(config, config_entry, discovery_data)]) + async_add_entities([MqttLock(hass, config, config_entry, discovery_data)]) class MqttLock( @@ -116,8 +116,9 @@ class MqttLock( ): """Representation of a lock that can be toggled using MQTT.""" - def __init__(self, config, config_entry, discovery_data): + def __init__(self, hass, config, config_entry, discovery_data): """Initialize the lock.""" + self.hass = hass self._unique_id = config.get(CONF_UNIQUE_ID) self._state = False self._sub_state = None @@ -154,17 +155,19 @@ class MqttLock( self._optimistic = config[CONF_OPTIMISTIC] - async def _subscribe_topics(self): - """(Re)Subscribe to topics.""" value_template = self._config.get(CONF_VALUE_TEMPLATE) if value_template is not None: value_template.hass = self.hass + async def _subscribe_topics(self): + """(Re)Subscribe to topics.""" + @callback @log_messages(self.hass, self.entity_id) def message_received(msg): """Handle new MQTT messages.""" payload = msg.payload + value_template = self._config.get(CONF_VALUE_TEMPLATE) if value_template is not None: payload = value_template.async_render_with_possible_json_value(payload) if payload == self._config[CONF_STATE_LOCKED]: diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index eb241bba7a0..ffd34cef8c9 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -70,7 +70,7 @@ async def async_setup_platform( ): """Set up MQTT sensors through configuration.yaml.""" await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - 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): @@ -82,7 +82,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]) @@ -94,10 +94,10 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async def _async_setup_entity( - config: ConfigType, async_add_entities, config_entry=None, discovery_data=None + hass, config: ConfigType, async_add_entities, config_entry=None, discovery_data=None ): """Set up MQTT sensor.""" - async_add_entities([MqttSensor(config, config_entry, discovery_data)]) + async_add_entities([MqttSensor(hass, config, config_entry, discovery_data)]) class MqttSensor( @@ -105,9 +105,9 @@ class MqttSensor( ): """Representation of a sensor that can be updated using MQTT.""" - def __init__(self, config, config_entry, discovery_data): + def __init__(self, hass, config, config_entry, discovery_data): """Initialize the sensor.""" - self._config = config + self.hass = hass self._unique_id = config.get(CONF_UNIQUE_ID) self._state = None self._sub_state = None @@ -118,6 +118,10 @@ class MqttSensor( self._expired = True else: self._expired = None + + # Load config + self._setup_from_config(config) + device_config = config.get(CONF_DEVICE) MqttAttributes.__init__(self, config) @@ -133,19 +137,23 @@ class MqttSensor( async def discovery_update(self, discovery_payload): """Handle updated discovery message.""" config = PLATFORM_SCHEMA(discovery_payload) - self._config = config + self._setup_from_config(config) await self.attributes_discovery_update(config) await self.availability_discovery_update(config) await self.device_info_discovery_update(config) await self._subscribe_topics() self.async_write_ha_state() - async def _subscribe_topics(self): - """(Re)Subscribe to topics.""" + def _setup_from_config(self, config): + """(Re)Setup the entity.""" + self._config = config template = self._config.get(CONF_VALUE_TEMPLATE) if template is not None: template.hass = self.hass + async def _subscribe_topics(self): + """(Re)Subscribe to topics.""" + @callback @log_messages(self.hass, self.entity_id) def message_received(msg): @@ -169,6 +177,7 @@ class MqttSensor( self.hass, self._value_is_expired, expiration_at ) + template = self._config.get(CONF_VALUE_TEMPLATE) if template is not None: payload = template.async_render_with_possible_json_value( payload, self._state diff --git a/homeassistant/components/mqtt/switch.py b/homeassistant/components/mqtt/switch.py index 364f060ac14..761f19ef054 100644 --- a/homeassistant/components/mqtt/switch.py +++ b/homeassistant/components/mqtt/switch.py @@ -73,7 +73,7 @@ async def async_setup_platform( ): """Set up MQTT switch through configuration.yaml.""" await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - await _async_setup_entity(config, async_add_entities, discovery_info) + await _async_setup_entity(hass, config, async_add_entities, discovery_info) async def async_setup_entry(hass, config_entry, async_add_entities): @@ -85,7 +85,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]) @@ -97,10 +97,10 @@ 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 the MQTT switch.""" - async_add_entities([MqttSwitch(config, config_entry, discovery_data)]) + async_add_entities([MqttSwitch(hass, config, config_entry, discovery_data)]) class MqttSwitch( @@ -113,8 +113,9 @@ class MqttSwitch( ): """Representation of a switch that can be toggled using MQTT.""" - def __init__(self, config, config_entry, discovery_data): + def __init__(self, hass, config, config_entry, discovery_data): """Initialize the MQTT switch.""" + self.hass = hass self._state = False self._sub_state = None @@ -160,17 +161,19 @@ class MqttSwitch( self._optimistic = config[CONF_OPTIMISTIC] - async def _subscribe_topics(self): - """(Re)Subscribe to topics.""" template = self._config.get(CONF_VALUE_TEMPLATE) if template is not None: template.hass = self.hass + async def _subscribe_topics(self): + """(Re)Subscribe to topics.""" + @callback @log_messages(self.hass, self.entity_id) def state_message_received(msg): """Handle new MQTT state messages.""" payload = msg.payload + template = self._config.get(CONF_VALUE_TEMPLATE) if template is not None: payload = template.async_render_with_possible_json_value(payload) if payload == self._state_on: diff --git a/tests/components/mqtt/test_alarm_control_panel.py b/tests/components/mqtt/test_alarm_control_panel.py index 30ec5316399..7eb890903fd 100644 --- a/tests/components/mqtt/test_alarm_control_panel.py +++ b/tests/components/mqtt/test_alarm_control_panel.py @@ -603,17 +603,71 @@ async def test_discovery_removal_alarm(hass, mqtt_mock, caplog): ) -async def test_discovery_update_alarm(hass, mqtt_mock, caplog): +async def test_discovery_update_alarm_topic_and_template(hass, mqtt_mock, caplog): """Test update of discovered alarm_control_panel.""" config1 = copy.deepcopy(DEFAULT_CONFIG[alarm_control_panel.DOMAIN]) config2 = copy.deepcopy(DEFAULT_CONFIG[alarm_control_panel.DOMAIN]) config1["name"] = "Beer" config2["name"] = "Milk" + config1["state_topic"] = "alarm/state1" + config2["state_topic"] = "alarm/state2" + config1["value_template"] = "{{ value_json.state1.state }}" + config2["value_template"] = "{{ value_json.state2.state }}" + + state_data1 = [ + ([("alarm/state1", '{"state1":{"state":"armed_away"}}')], "armed_away", None), + ] + state_data2 = [ + ([("alarm/state1", '{"state1":{"state":"triggered"}}')], "armed_away", None), + ([("alarm/state1", '{"state2":{"state":"triggered"}}')], "armed_away", None), + ([("alarm/state2", '{"state1":{"state":"triggered"}}')], "armed_away", None), + ([("alarm/state2", '{"state2":{"state":"triggered"}}')], "triggered", None), + ] data1 = json.dumps(config1) data2 = json.dumps(config2) await help_test_discovery_update( - hass, mqtt_mock, caplog, alarm_control_panel.DOMAIN, data1, data2 + hass, + mqtt_mock, + caplog, + alarm_control_panel.DOMAIN, + data1, + data2, + state_data1=state_data1, + state_data2=state_data2, + ) + + +async def test_discovery_update_alarm_template(hass, mqtt_mock, caplog): + """Test update of discovered alarm_control_panel.""" + config1 = copy.deepcopy(DEFAULT_CONFIG[alarm_control_panel.DOMAIN]) + config2 = copy.deepcopy(DEFAULT_CONFIG[alarm_control_panel.DOMAIN]) + config1["name"] = "Beer" + config2["name"] = "Milk" + config1["state_topic"] = "alarm/state1" + config2["state_topic"] = "alarm/state1" + config1["value_template"] = "{{ value_json.state1.state }}" + config2["value_template"] = "{{ value_json.state2.state }}" + + state_data1 = [ + ([("alarm/state1", '{"state1":{"state":"armed_away"}}')], "armed_away", None), + ] + state_data2 = [ + ([("alarm/state1", '{"state1":{"state":"triggered"}}')], "armed_away", None), + ([("alarm/state1", '{"state2":{"state":"triggered"}}')], "triggered", None), + ] + + data1 = json.dumps(config1) + data2 = json.dumps(config2) + await help_test_discovery_update( + hass, + mqtt_mock, + caplog, + alarm_control_panel.DOMAIN, + data1, + data2, + state_data1=state_data1, + state_data2=state_data2, ) diff --git a/tests/components/mqtt/test_binary_sensor.py b/tests/components/mqtt/test_binary_sensor.py index c739f4378d1..d2375278c4d 100644 --- a/tests/components/mqtt/test_binary_sensor.py +++ b/tests/components/mqtt/test_binary_sensor.py @@ -580,17 +580,75 @@ async def test_discovery_removal_binary_sensor(hass, mqtt_mock, caplog): ) -async def test_discovery_update_binary_sensor(hass, mqtt_mock, caplog): +async def test_discovery_update_binary_sensor_topic_template(hass, mqtt_mock, caplog): """Test update of discovered binary_sensor.""" config1 = copy.deepcopy(DEFAULT_CONFIG[binary_sensor.DOMAIN]) config2 = copy.deepcopy(DEFAULT_CONFIG[binary_sensor.DOMAIN]) config1["name"] = "Beer" config2["name"] = "Milk" + config1["state_topic"] = "sensor/state1" + config2["state_topic"] = "sensor/state2" + config1["value_template"] = "{{ value_json.state1.state }}" + config2["value_template"] = "{{ value_json.state2.state }}" + + state_data1 = [ + ([("sensor/state1", '{"state1":{"state":"ON"}}')], "on", None), + ] + state_data2 = [ + ([("sensor/state2", '{"state2":{"state":"OFF"}}')], "off", None), + ([("sensor/state2", '{"state2":{"state":"ON"}}')], "on", None), + ([("sensor/state1", '{"state1":{"state":"OFF"}}')], "on", None), + ([("sensor/state1", '{"state2":{"state":"OFF"}}')], "on", None), + ([("sensor/state2", '{"state1":{"state":"OFF"}}')], "on", None), + ([("sensor/state2", '{"state2":{"state":"OFF"}}')], "off", None), + ] data1 = json.dumps(config1) data2 = json.dumps(config2) await help_test_discovery_update( - hass, mqtt_mock, caplog, binary_sensor.DOMAIN, data1, data2 + hass, + mqtt_mock, + caplog, + binary_sensor.DOMAIN, + data1, + data2, + state_data1=state_data1, + state_data2=state_data2, + ) + + +async def test_discovery_update_binary_sensor_template(hass, mqtt_mock, caplog): + """Test update of discovered binary_sensor.""" + config1 = copy.deepcopy(DEFAULT_CONFIG[binary_sensor.DOMAIN]) + config2 = copy.deepcopy(DEFAULT_CONFIG[binary_sensor.DOMAIN]) + config1["name"] = "Beer" + config2["name"] = "Milk" + config1["state_topic"] = "sensor/state1" + config2["state_topic"] = "sensor/state1" + config1["value_template"] = "{{ value_json.state1.state }}" + config2["value_template"] = "{{ value_json.state2.state }}" + + state_data1 = [ + ([("sensor/state1", '{"state1":{"state":"ON"}}')], "on", None), + ] + state_data2 = [ + ([("sensor/state1", '{"state2":{"state":"OFF"}}')], "off", None), + ([("sensor/state1", '{"state2":{"state":"ON"}}')], "on", None), + ([("sensor/state1", '{"state1":{"state":"OFF"}}')], "on", None), + ([("sensor/state1", '{"state2":{"state":"OFF"}}')], "off", None), + ] + + data1 = json.dumps(config1) + data2 = json.dumps(config2) + await help_test_discovery_update( + hass, + mqtt_mock, + caplog, + binary_sensor.DOMAIN, + data1, + data2, + state_data1=state_data1, + state_data2=state_data2, ) diff --git a/tests/components/mqtt/test_common.py b/tests/components/mqtt/test_common.py index 75e1c12a46c..27ee060ebfa 100644 --- a/tests/components/mqtt/test_common.py +++ b/tests/components/mqtt/test_common.py @@ -512,10 +512,6 @@ async def help_test_discovery_update( discovery_data2, state_data1=None, state_data2=None, - state1=None, - state2=None, - attributes1=None, - attributes2=None, ): """Test update of discovered component. @@ -527,32 +523,38 @@ async def help_test_discovery_update( 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 + + if state_data1: + for (mqtt_messages, expected_state, attributes) in state_data1: + for (topic, data) in mqtt_messages: + async_fire_mqtt_message(hass, topic, data) + state = hass.states.get(f"{domain}.beer") + if expected_state: + assert state.state == expected_state + if attributes: + for (attr, value) in attributes: + assert state.attributes.get(attr) == value 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 + + if state_data2: + for (mqtt_messages, expected_state, attributes) in state_data2: + for (topic, data) in mqtt_messages: + async_fire_mqtt_message(hass, topic, data) + state = hass.states.get(f"{domain}.beer") + if expected_state: + assert state.state == expected_state + if attributes: + for (attr, value) in attributes: + assert state.attributes.get(attr) == value state = hass.states.get(f"{domain}.milk") assert state is None diff --git a/tests/components/mqtt/test_cover.py b/tests/components/mqtt/test_cover.py index f9036bcfa0f..d1529e63fc7 100644 --- a/tests/components/mqtt/test_cover.py +++ b/tests/components/mqtt/test_cover.py @@ -1397,6 +1397,7 @@ async def test_tilt_position_altered_range(hass, mqtt_mock): async def test_find_percentage_in_range_defaults(hass, mqtt_mock): """Test find percentage in range with default range.""" mqtt_cover = MqttCover( + hass, { "name": "cover.test", "state_topic": "state-topic", @@ -1440,6 +1441,7 @@ async def test_find_percentage_in_range_defaults(hass, mqtt_mock): async def test_find_percentage_in_range_altered(hass, mqtt_mock): """Test find percentage in range with altered range.""" mqtt_cover = MqttCover( + hass, { "name": "cover.test", "state_topic": "state-topic", @@ -1483,6 +1485,7 @@ async def test_find_percentage_in_range_altered(hass, mqtt_mock): async def test_find_percentage_in_range_defaults_inverted(hass, mqtt_mock): """Test find percentage in range with default range but inverted.""" mqtt_cover = MqttCover( + hass, { "name": "cover.test", "state_topic": "state-topic", @@ -1526,6 +1529,7 @@ async def test_find_percentage_in_range_defaults_inverted(hass, mqtt_mock): async def test_find_percentage_in_range_altered_inverted(hass, mqtt_mock): """Test find percentage in range with altered range and inverted.""" mqtt_cover = MqttCover( + hass, { "name": "cover.test", "state_topic": "state-topic", @@ -1569,6 +1573,7 @@ async def test_find_percentage_in_range_altered_inverted(hass, mqtt_mock): async def test_find_in_range_defaults(hass, mqtt_mock): """Test find in range with default range.""" mqtt_cover = MqttCover( + hass, { "name": "cover.test", "state_topic": "state-topic", @@ -1612,6 +1617,7 @@ async def test_find_in_range_defaults(hass, mqtt_mock): async def test_find_in_range_altered(hass, mqtt_mock): """Test find in range with altered range.""" mqtt_cover = MqttCover( + hass, { "name": "cover.test", "state_topic": "state-topic", @@ -1655,6 +1661,7 @@ async def test_find_in_range_altered(hass, mqtt_mock): async def test_find_in_range_defaults_inverted(hass, mqtt_mock): """Test find in range with default range but inverted.""" mqtt_cover = MqttCover( + hass, { "name": "cover.test", "state_topic": "state-topic", @@ -1698,6 +1705,7 @@ async def test_find_in_range_defaults_inverted(hass, mqtt_mock): async def test_find_in_range_altered_inverted(hass, mqtt_mock): """Test find in range with altered range and inverted.""" mqtt_cover = MqttCover( + hass, { "name": "cover.test", "state_topic": "state-topic", diff --git a/tests/components/mqtt/test_light.py b/tests/components/mqtt/test_light.py index da4d90ad5ec..5481b8b2565 100644 --- a/tests/components/mqtt/test_light.py +++ b/tests/components/mqtt/test_light.py @@ -153,6 +153,7 @@ light: payload_off: "off" """ +import json from os import path import pytest @@ -1466,20 +1467,249 @@ async def test_discovery_deprecated(hass, mqtt_mock, caplog): assert state.name == "Beer" -async def test_discovery_update_light(hass, mqtt_mock, caplog): +async def test_discovery_update_light_topic_and_template(hass, mqtt_mock, caplog): """Test update of discovered light.""" - data1 = ( - '{ "name": "Beer",' - ' "state_topic": "test_topic",' - ' "command_topic": "test_topic",' - ' "state_value_template": "{{value_json.power1}}" }' + data1 = json.dumps( + { + "name": "Beer", + "state_topic": "test_light_rgb/state1", + "command_topic": "test_light_rgb/set", + "brightness_command_topic": "test_light_rgb/state1", + "rgb_command_topic": "test_light_rgb/rgb/set", + "color_temp_command_topic": "test_light_rgb/state1", + "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/state1", + "color_temp_state_topic": "test_light_rgb/state1", + "effect_state_topic": "test_light_rgb/state1", + "hs_state_topic": "test_light_rgb/state1", + "rgb_state_topic": "test_light_rgb/state1", + "white_value_state_topic": "test_light_rgb/state1", + "xy_state_topic": "test_light_rgb/state1", + "state_value_template": "{{ value_json.state1.state }}", + "brightness_value_template": "{{ value_json.state1.brightness }}", + "color_temp_value_template": "{{ value_json.state1.ct }}", + "effect_value_template": "{{ value_json.state1.fx }}", + "hs_value_template": "{{ value_json.state1.hs }}", + "rgb_value_template": "{{ value_json.state1.rgb }}", + "white_value_template": "{{ value_json.state1.white }}", + "xy_value_template": "{{ value_json.state1.xy }}", + } ) - data2 = ( - '{ "name": "Milk",' - ' "state_topic": "test_topic",' - ' "command_topic": "test_topic",' - ' "state_value_template": "{{value_json.power2}}" }' + + data2 = json.dumps( + { + "name": "Milk", + "state_topic": "test_light_rgb/state2", + "command_topic": "test_light_rgb/set", + "brightness_command_topic": "test_light_rgb/state2", + "rgb_command_topic": "test_light_rgb/rgb/set", + "color_temp_command_topic": "test_light_rgb/state2", + "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/state2", + "color_temp_state_topic": "test_light_rgb/state2", + "effect_state_topic": "test_light_rgb/state2", + "hs_state_topic": "test_light_rgb/state2", + "rgb_state_topic": "test_light_rgb/state2", + "white_value_state_topic": "test_light_rgb/state2", + "xy_state_topic": "test_light_rgb/state2", + "state_value_template": "{{ value_json.state2.state }}", + "brightness_value_template": "{{ value_json.state2.brightness }}", + "color_temp_value_template": "{{ value_json.state2.ct }}", + "effect_value_template": "{{ value_json.state2.fx }}", + "hs_value_template": "{{ value_json.state2.hs }}", + "rgb_value_template": "{{ value_json.state2.rgb }}", + "white_value_template": "{{ value_json.state2.white }}", + "xy_value_template": "{{ value_json.state2.xy }}", + } ) + state_data1 = [ + ( + [ + ( + "test_light_rgb/state1", + '{"state1":{"state":"ON", "brightness":100, "ct":123, "fx":"cycle"}}', + ) + ], + "on", + [("brightness", 100), ("color_temp", 123), ("effect", "cycle")], + ), + ( + [("test_light_rgb/state1", '{"state1":{"state":"OFF"}}')], + "off", + None, + ), + ( + [ + ( + "test_light_rgb/state1", + '{"state1":{"state":"ON", "hs":"1,2"}}', + ) + ], + "on", + [("hs_color", (1, 2))], + ), + ( + [ + ( + "test_light_rgb/state1", + '{"state1":{"rgb":"255,127,63"}}', + ) + ], + "on", + [("rgb_color", (255, 127, 63))], + ), + ( + [ + ( + "test_light_rgb/state1", + '{"state1":{"white":50, "xy":"0.3, 0.4"}}', + ) + ], + "on", + [("white_value", 50), ("xy_color", (0.3, 0.401))], + ), + ] + state_data2 = [ + ( + [ + ( + "test_light_rgb/state2", + '{"state2":{"state":"ON", "brightness":50, "ct":200, "fx":"loop"}}', + ) + ], + "on", + [("brightness", 50), ("color_temp", 200), ("effect", "loop")], + ), + ( + [ + ( + "test_light_rgb/state1", + '{"state1":{"state":"ON", "brightness":100, "ct":123, "fx":"cycle"}}', + ), + ( + "test_light_rgb/state1", + '{"state2":{"state":"ON", "brightness":100, "ct":123, "fx":"cycle"}}', + ), + ( + "test_light_rgb/state2", + '{"state1":{"state":"ON", "brightness":100, "ct":123, "fx":"cycle"}}', + ), + ], + "on", + [("brightness", 50), ("color_temp", 200), ("effect", "loop")], + ), + ( + [("test_light_rgb/state1", '{"state1":{"state":"OFF"}}')], + "on", + None, + ), + ( + [("test_light_rgb/state1", '{"state2":{"state":"OFF"}}')], + "on", + None, + ), + ( + [("test_light_rgb/state2", '{"state1":{"state":"OFF"}}')], + "on", + None, + ), + ( + [("test_light_rgb/state2", '{"state2":{"state":"OFF"}}')], + "off", + None, + ), + ( + [ + ( + "test_light_rgb/state2", + '{"state2":{"state":"ON", "hs":"1.2,2.2"}}', + ) + ], + "on", + [("hs_color", (1.2, 2.2))], + ), + ( + [ + ( + "test_light_rgb/state1", + '{"state1":{"state":"ON", "hs":"1,2"}}', + ), + ( + "test_light_rgb/state1", + '{"state2":{"state":"ON", "hs":"1,2"}}', + ), + ( + "test_light_rgb/state2", + '{"state1":{"state":"ON", "hs":"1,2"}}', + ), + ], + "on", + [("hs_color", (1.2, 2.2))], + ), + ( + [ + ( + "test_light_rgb/state2", + '{"state2":{"rgb":"63,127,255"}}', + ) + ], + "on", + [("rgb_color", (63, 127, 255))], + ), + ( + [ + ( + "test_light_rgb/state1", + '{"state1":{"rgb":"255,127,63"}}', + ), + ( + "test_light_rgb/state1", + '{"state2":{"rgb":"255,127,63"}}', + ), + ( + "test_light_rgb/state2", + '{"state1":{"rgb":"255,127,63"}}', + ), + ], + "on", + [("rgb_color", (63, 127, 255))], + ), + ( + [ + ( + "test_light_rgb/state2", + '{"state2":{"white":75, "xy":"0.4, 0.3"}}', + ) + ], + "on", + [("white_value", 75), ("xy_color", (0.4, 0.3))], + ), + ( + [ + ( + "test_light_rgb/state1", + '{"state1":{"white":50, "xy":"0.3, 0.4"}}', + ), + ( + "test_light_rgb/state1", + '{"state2":{"white":50, "xy":"0.3, 0.4"}}', + ), + ( + "test_light_rgb/state2", + '{"state1":{"white":50, "xy":"0.3, 0.4"}}', + ), + ], + "on", + [("white_value", 75), ("xy_color", (0.4, 0.3))], + ), + ] + await help_test_discovery_update( hass, mqtt_mock, @@ -1487,10 +1717,221 @@ async def test_discovery_update_light(hass, mqtt_mock, caplog): light.DOMAIN, data1, data2, - state_data1=[("test_topic", '{"power1":"ON"}')], - state1="on", - state_data2=[("test_topic", '{"power2":"OFF"}')], - state2="off", + state_data1=state_data1, + state_data2=state_data2, + ) + + +async def test_discovery_update_light_template(hass, mqtt_mock, caplog): + """Test update of discovered light.""" + data1 = json.dumps( + { + "name": "Beer", + "state_topic": "test_light_rgb/state1", + "command_topic": "test_light_rgb/set", + "brightness_command_topic": "test_light_rgb/state1", + "rgb_command_topic": "test_light_rgb/rgb/set", + "color_temp_command_topic": "test_light_rgb/state1", + "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/state1", + "color_temp_state_topic": "test_light_rgb/state1", + "effect_state_topic": "test_light_rgb/state1", + "hs_state_topic": "test_light_rgb/state1", + "rgb_state_topic": "test_light_rgb/state1", + "white_value_state_topic": "test_light_rgb/state1", + "xy_state_topic": "test_light_rgb/state1", + "state_value_template": "{{ value_json.state1.state }}", + "brightness_value_template": "{{ value_json.state1.brightness }}", + "color_temp_value_template": "{{ value_json.state1.ct }}", + "effect_value_template": "{{ value_json.state1.fx }}", + "hs_value_template": "{{ value_json.state1.hs }}", + "rgb_value_template": "{{ value_json.state1.rgb }}", + "white_value_template": "{{ value_json.state1.white }}", + "xy_value_template": "{{ value_json.state1.xy }}", + } + ) + + data2 = json.dumps( + { + "name": "Milk", + "state_topic": "test_light_rgb/state1", + "command_topic": "test_light_rgb/set", + "brightness_command_topic": "test_light_rgb/state1", + "rgb_command_topic": "test_light_rgb/rgb/set", + "color_temp_command_topic": "test_light_rgb/state1", + "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/state1", + "color_temp_state_topic": "test_light_rgb/state1", + "effect_state_topic": "test_light_rgb/state1", + "hs_state_topic": "test_light_rgb/state1", + "rgb_state_topic": "test_light_rgb/state1", + "white_value_state_topic": "test_light_rgb/state1", + "xy_state_topic": "test_light_rgb/state1", + "state_value_template": "{{ value_json.state2.state }}", + "brightness_value_template": "{{ value_json.state2.brightness }}", + "color_temp_value_template": "{{ value_json.state2.ct }}", + "effect_value_template": "{{ value_json.state2.fx }}", + "hs_value_template": "{{ value_json.state2.hs }}", + "rgb_value_template": "{{ value_json.state2.rgb }}", + "white_value_template": "{{ value_json.state2.white }}", + "xy_value_template": "{{ value_json.state2.xy }}", + } + ) + state_data1 = [ + ( + [ + ( + "test_light_rgb/state1", + '{"state1":{"state":"ON", "brightness":100, "ct":123, "fx":"cycle"}}', + ) + ], + "on", + [("brightness", 100), ("color_temp", 123), ("effect", "cycle")], + ), + ( + [("test_light_rgb/state1", '{"state1":{"state":"OFF"}}')], + "off", + None, + ), + ( + [ + ( + "test_light_rgb/state1", + '{"state1":{"state":"ON", "hs":"1,2"}}', + ) + ], + "on", + [("hs_color", (1, 2))], + ), + ( + [ + ( + "test_light_rgb/state1", + '{"state1":{"rgb":"255,127,63"}}', + ) + ], + "on", + [("rgb_color", (255, 127, 63))], + ), + ( + [ + ( + "test_light_rgb/state1", + '{"state1":{"white":50, "xy":"0.3, 0.4"}}', + ) + ], + "on", + [("white_value", 50), ("xy_color", (0.3, 0.401))], + ), + ] + state_data2 = [ + ( + [ + ( + "test_light_rgb/state1", + '{"state2":{"state":"ON", "brightness":50, "ct":200, "fx":"loop"}}', + ) + ], + "on", + [("brightness", 50), ("color_temp", 200), ("effect", "loop")], + ), + ( + [ + ( + "test_light_rgb/state1", + '{"state1":{"state":"ON", "brightness":100, "ct":123, "fx":"cycle"}}', + ), + ], + "on", + [("brightness", 50), ("color_temp", 200), ("effect", "loop")], + ), + ( + [("test_light_rgb/state1", '{"state1":{"state":"OFF"}}')], + "on", + None, + ), + ( + [("test_light_rgb/state1", '{"state2":{"state":"OFF"}}')], + "off", + None, + ), + ( + [ + ( + "test_light_rgb/state1", + '{"state2":{"state":"ON", "hs":"1.2,2.2"}}', + ) + ], + "on", + [("hs_color", (1.2, 2.2))], + ), + ( + [ + ( + "test_light_rgb/state1", + '{"state1":{"state":"ON", "hs":"1,2"}}', + ) + ], + "on", + [("hs_color", (1.2, 2.2))], + ), + ( + [ + ( + "test_light_rgb/state1", + '{"state2":{"rgb":"63,127,255"}}', + ) + ], + "on", + [("rgb_color", (63, 127, 255))], + ), + ( + [ + ( + "test_light_rgb/state1", + '{"state1":{"rgb":"255,127,63"}}', + ) + ], + "on", + [("rgb_color", (63, 127, 255))], + ), + ( + [ + ( + "test_light_rgb/state1", + '{"state2":{"white":75, "xy":"0.4, 0.3"}}', + ) + ], + "on", + [("white_value", 75), ("xy_color", (0.4, 0.3))], + ), + ( + [ + ( + "test_light_rgb/state1", + '{"state1":{"white":50, "xy":"0.3, 0.4"}}', + ) + ], + "on", + [("white_value", 75), ("xy_color", (0.4, 0.3))], + ), + ] + + await help_test_discovery_update( + hass, + mqtt_mock, + caplog, + light.DOMAIN, + data1, + data2, + state_data1=state_data1, + state_data2=state_data2, ) diff --git a/tests/components/mqtt/test_sensor.py b/tests/components/mqtt/test_sensor.py index 0d31b9f33f2..77fd8c561b2 100644 --- a/tests/components/mqtt/test_sensor.py +++ b/tests/components/mqtt/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the MQTT sensor platform.""" +import copy from datetime import datetime, timedelta import json @@ -430,12 +431,71 @@ async def test_discovery_removal_sensor(hass, mqtt_mock, caplog): await help_test_discovery_removal(hass, mqtt_mock, caplog, sensor.DOMAIN, data) -async def test_discovery_update_sensor(hass, mqtt_mock, caplog): +async def test_discovery_update_sensor_topic_template(hass, mqtt_mock, caplog): """Test update of discovered sensor.""" - data1 = '{ "name": "Beer", "state_topic": "test_topic" }' - data2 = '{ "name": "Milk", "state_topic": "test_topic" }' + config = {"name": "test", "state_topic": "test_topic"} + config1 = copy.deepcopy(config) + config2 = copy.deepcopy(config) + config1["name"] = "Beer" + config2["name"] = "Milk" + config1["state_topic"] = "sensor/state1" + config2["state_topic"] = "sensor/state2" + config1["value_template"] = "{{ value_json.state | int }}" + config2["value_template"] = "{{ value_json.state | int * 2 }}" + + state_data1 = [ + ([("sensor/state1", '{"state":100}')], "100", None), + ] + state_data2 = [ + ([("sensor/state1", '{"state":1000}')], "100", None), + ([("sensor/state1", '{"state":1000}')], "100", None), + ([("sensor/state2", '{"state":100}')], "200", None), + ] + + data1 = json.dumps(config1) + data2 = json.dumps(config2) await help_test_discovery_update( - hass, mqtt_mock, caplog, sensor.DOMAIN, data1, data2 + hass, + mqtt_mock, + caplog, + sensor.DOMAIN, + data1, + data2, + state_data1=state_data1, + state_data2=state_data2, + ) + + +async def test_discovery_update_sensor_template(hass, mqtt_mock, caplog): + """Test update of discovered sensor.""" + config = {"name": "test", "state_topic": "test_topic"} + config1 = copy.deepcopy(config) + config2 = copy.deepcopy(config) + config1["name"] = "Beer" + config2["name"] = "Milk" + config1["state_topic"] = "sensor/state1" + config2["state_topic"] = "sensor/state1" + config1["value_template"] = "{{ value_json.state | int }}" + config2["value_template"] = "{{ value_json.state | int * 2 }}" + + state_data1 = [ + ([("sensor/state1", '{"state":100}')], "100", None), + ] + state_data2 = [ + ([("sensor/state1", '{"state":100}')], "200", None), + ] + + data1 = json.dumps(config1) + data2 = json.dumps(config2) + await help_test_discovery_update( + hass, + mqtt_mock, + caplog, + sensor.DOMAIN, + data1, + data2, + state_data1=state_data1, + state_data2=state_data2, ) diff --git a/tests/components/mqtt/test_switch.py b/tests/components/mqtt/test_switch.py index a6edb8d6f14..4d9c6dc2c77 100644 --- a/tests/components/mqtt/test_switch.py +++ b/tests/components/mqtt/test_switch.py @@ -1,4 +1,7 @@ """The tests for the MQTT switch platform.""" +import copy +import json + import pytest from homeassistant.components import switch @@ -304,20 +307,75 @@ async def test_discovery_removal_switch(hass, mqtt_mock, caplog): await help_test_discovery_removal(hass, mqtt_mock, caplog, switch.DOMAIN, data) -async def test_discovery_update_switch(hass, mqtt_mock, caplog): +async def test_discovery_update_switch_topic_template(hass, mqtt_mock, caplog): """Test update of discovered switch.""" - data1 = ( - '{ "name": "Beer",' - ' "state_topic": "test_topic",' - ' "command_topic": "test_topic" }' - ) - data2 = ( - '{ "name": "Milk",' - ' "state_topic": "test_topic",' - ' "command_topic": "test_topic" }' - ) + config1 = copy.deepcopy(DEFAULT_CONFIG[switch.DOMAIN]) + config2 = copy.deepcopy(DEFAULT_CONFIG[switch.DOMAIN]) + config1["name"] = "Beer" + config2["name"] = "Milk" + config1["state_topic"] = "switch/state1" + config2["state_topic"] = "switch/state2" + config1["value_template"] = "{{ value_json.state1.state }}" + config2["value_template"] = "{{ value_json.state2.state }}" + + state_data1 = [ + ([("switch/state1", '{"state1":{"state":"ON"}}')], "on", None), + ] + state_data2 = [ + ([("switch/state2", '{"state2":{"state":"OFF"}}')], "off", None), + ([("switch/state2", '{"state2":{"state":"ON"}}')], "on", None), + ([("switch/state1", '{"state1":{"state":"OFF"}}')], "on", None), + ([("switch/state1", '{"state2":{"state":"OFF"}}')], "on", None), + ([("switch/state2", '{"state1":{"state":"OFF"}}')], "on", None), + ([("switch/state2", '{"state2":{"state":"OFF"}}')], "off", None), + ] + + data1 = json.dumps(config1) + data2 = json.dumps(config2) await help_test_discovery_update( - hass, mqtt_mock, caplog, switch.DOMAIN, data1, data2 + hass, + mqtt_mock, + caplog, + switch.DOMAIN, + data1, + data2, + state_data1=state_data1, + state_data2=state_data2, + ) + + +async def test_discovery_update_switch_template(hass, mqtt_mock, caplog): + """Test update of discovered switch.""" + config1 = copy.deepcopy(DEFAULT_CONFIG[switch.DOMAIN]) + config2 = copy.deepcopy(DEFAULT_CONFIG[switch.DOMAIN]) + config1["name"] = "Beer" + config2["name"] = "Milk" + config1["state_topic"] = "switch/state1" + config2["state_topic"] = "switch/state1" + config1["value_template"] = "{{ value_json.state1.state }}" + config2["value_template"] = "{{ value_json.state2.state }}" + + state_data1 = [ + ([("switch/state1", '{"state1":{"state":"ON"}}')], "on", None), + ] + state_data2 = [ + ([("switch/state1", '{"state2":{"state":"OFF"}}')], "off", None), + ([("switch/state1", '{"state2":{"state":"ON"}}')], "on", None), + ([("switch/state1", '{"state1":{"state":"OFF"}}')], "on", None), + ([("switch/state1", '{"state2":{"state":"OFF"}}')], "off", None), + ] + + data1 = json.dumps(config1) + data2 = json.dumps(config2) + await help_test_discovery_update( + hass, + mqtt_mock, + caplog, + switch.DOMAIN, + data1, + data2, + state_data1=state_data1, + state_data2=state_data2, )