From 6380ebd1ebd973b894dcc2ec4280a1be8fd8d93b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 26 Oct 2020 16:46:26 +0100 Subject: [PATCH] Fix MQTT publish from a script with templates (#42398) --- homeassistant/components/mqtt/__init__.py | 6 +- homeassistant/components/snips/__init__.py | 2 +- .../mqtt/test_alarm_control_panel.py | 8 +-- tests/components/mqtt/test_climate.py | 13 ++-- tests/components/mqtt/test_cover.py | 62 +++++++++++++------ tests/components/mqtt/test_init.py | 45 +++++++++++++- tests/components/mqtt/test_light.py | 34 +++++----- 7 files changed, 126 insertions(+), 44 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index d7d11e6f49a..09655ababda 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -322,7 +322,7 @@ MQTT_RW_PLATFORM_SCHEMA = MQTT_BASE_PLATFORM_SCHEMA.extend( MQTT_PUBLISH_SCHEMA = vol.Schema( { vol.Required(ATTR_TOPIC): valid_publish_topic, - vol.Exclusive(ATTR_PAYLOAD, CONF_PAYLOAD): object, + vol.Exclusive(ATTR_PAYLOAD, CONF_PAYLOAD): cv.string, vol.Exclusive(ATTR_PAYLOAD_TEMPLATE, CONF_PAYLOAD): cv.string, vol.Optional(ATTR_QOS, default=DEFAULT_QOS): _VALID_QOS_SCHEMA, vol.Optional(ATTR_RETAIN, default=DEFAULT_RETAIN): cv.boolean, @@ -567,7 +567,9 @@ async def async_setup_entry(hass, entry): retain: bool = call.data[ATTR_RETAIN] if payload_template is not None: try: - payload = template.Template(payload_template, hass).async_render() + payload = template.Template(payload_template, hass).async_render( + parse_result=False + ) except template.jinja2.TemplateError as exc: _LOGGER.error( "Unable to publish to %s: rendering payload template of " diff --git a/homeassistant/components/snips/__init__.py b/homeassistant/components/snips/__init__.py index 618190e8e70..11f732a3bd5 100644 --- a/homeassistant/components/snips/__init__.py +++ b/homeassistant/components/snips/__init__.py @@ -98,7 +98,7 @@ async def async_setup(hass, config): for site_id in site_ids: payload = json.dumps({"siteId": site_id}) hass.components.mqtt.async_publish( - FEEDBACK_ON_TOPIC, None, qos=0, retain=False + FEEDBACK_ON_TOPIC, "", qos=0, retain=False ) hass.components.mqtt.async_publish( topic, payload, qos=int(state), retain=state diff --git a/tests/components/mqtt/test_alarm_control_panel.py b/tests/components/mqtt/test_alarm_control_panel.py index b6c162eb87e..524448e5839 100644 --- a/tests/components/mqtt/test_alarm_control_panel.py +++ b/tests/components/mqtt/test_alarm_control_panel.py @@ -397,9 +397,9 @@ async def test_disarm_publishes_mqtt_with_template(hass, mqtt_mock): """ config = copy.deepcopy(DEFAULT_CONFIG_CODE) config[alarm_control_panel.DOMAIN]["code"] = "0123" - config[alarm_control_panel.DOMAIN]["command_template"] = ( - '{"action":"{{ action }}",' '"code":"{{ code }}"}' - ) + config[alarm_control_panel.DOMAIN][ + "command_template" + ] = '{"action":"{{ action }}","code":"{{ code }}"}' assert await async_setup_component( hass, alarm_control_panel.DOMAIN, @@ -409,7 +409,7 @@ async def test_disarm_publishes_mqtt_with_template(hass, mqtt_mock): await common.async_alarm_disarm(hass, "0123") mqtt_mock.async_publish.assert_called_once_with( - "alarm/command", {"action": "DISARM", "code": "0123"}, 0, False + "alarm/command", '{"action":"DISARM","code":"0123"}', 0, False ) diff --git a/tests/components/mqtt/test_climate.py b/tests/components/mqtt/test_climate.py index d60af211d71..3c629a82012 100644 --- a/tests/components/mqtt/test_climate.py +++ b/tests/components/mqtt/test_climate.py @@ -322,7 +322,9 @@ async def test_set_target_temperature(hass, mqtt_mock): await common.async_set_temperature(hass, temperature=47, entity_id=ENTITY_CLIMATE) state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get("temperature") == 47 - mqtt_mock.async_publish.assert_called_once_with("temperature-topic", 47, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "temperature-topic", "47.0", 0, False + ) # also test directly supplying the operation mode to set_temperature mqtt_mock.async_publish.reset_mock() @@ -333,7 +335,10 @@ async def test_set_target_temperature(hass, mqtt_mock): assert state.state == "cool" assert state.attributes.get("temperature") == 21 mqtt_mock.async_publish.assert_has_calls( - [call("mode-topic", "cool", 0, False), call("temperature-topic", 21, 0, False)] + [ + call("mode-topic", "cool", 0, False), + call("temperature-topic", "21.0", 0, False), + ] ) mqtt_mock.async_publish.reset_mock() @@ -372,8 +377,8 @@ async def test_set_target_temperature_low_high(hass, mqtt_mock): state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get("target_temp_low") == 20 assert state.attributes.get("target_temp_high") == 23 - mqtt_mock.async_publish.assert_any_call("temperature-low-topic", 20, 0, False) - mqtt_mock.async_publish.assert_any_call("temperature-high-topic", 23, 0, False) + mqtt_mock.async_publish.assert_any_call("temperature-low-topic", "20.0", 0, False) + mqtt_mock.async_publish.assert_any_call("temperature-high-topic", "23.0", 0, False) async def test_set_target_temperature_low_highpessimistic(hass, mqtt_mock): diff --git a/tests/components/mqtt/test_cover.py b/tests/components/mqtt/test_cover.py index dd879338a41..629d9674b22 100644 --- a/tests/components/mqtt/test_cover.py +++ b/tests/components/mqtt/test_cover.py @@ -702,7 +702,9 @@ async def test_set_position_templated(hass, mqtt_mock): blocking=True, ) - mqtt_mock.async_publish.assert_called_once_with("set-position-topic", 38, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "set-position-topic", "38", 0, False + ) async def test_set_position_untemplated(hass, mqtt_mock): @@ -732,7 +734,7 @@ async def test_set_position_untemplated(hass, mqtt_mock): blocking=True, ) - mqtt_mock.async_publish.assert_called_once_with("position-topic", 62, 0, False) + mqtt_mock.async_publish.assert_called_once_with("position-topic", "62", 0, False) async def test_set_position_untemplated_custom_percentage_range(hass, mqtt_mock): @@ -764,7 +766,7 @@ async def test_set_position_untemplated_custom_percentage_range(hass, mqtt_mock) blocking=True, ) - mqtt_mock.async_publish.assert_called_once_with("position-topic", 62, 0, False) + mqtt_mock.async_publish.assert_called_once_with("position-topic", "62", 0, False) async def test_no_command_topic(hass, mqtt_mock): @@ -896,7 +898,9 @@ async def test_tilt_via_invocation_defaults(hass, mqtt_mock): blocking=True, ) - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 100, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "tilt-command-topic", "100", 0, False + ) mqtt_mock.async_publish.reset_mock() await hass.services.async_call( @@ -906,7 +910,7 @@ async def test_tilt_via_invocation_defaults(hass, mqtt_mock): blocking=True, ) - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 0, 0, False) + mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", "0", 0, False) mqtt_mock.async_publish.reset_mock() # Close tilt status would be received from device when non-optimistic @@ -924,7 +928,9 @@ async def test_tilt_via_invocation_defaults(hass, mqtt_mock): blocking=True, ) - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 100, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "tilt-command-topic", "100", 0, False + ) mqtt_mock.async_publish.reset_mock() # Open tilt status would be received from device when non-optimistic @@ -942,7 +948,7 @@ async def test_tilt_via_invocation_defaults(hass, mqtt_mock): blocking=True, ) - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 0, 0, False) + mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", "0", 0, False) async def test_tilt_given_value(hass, mqtt_mock): @@ -976,7 +982,9 @@ async def test_tilt_given_value(hass, mqtt_mock): blocking=True, ) - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 80, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "tilt-command-topic", "80", 0, False + ) mqtt_mock.async_publish.reset_mock() await hass.services.async_call( @@ -986,7 +994,9 @@ async def test_tilt_given_value(hass, mqtt_mock): blocking=True, ) - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 25, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "tilt-command-topic", "25", 0, False + ) mqtt_mock.async_publish.reset_mock() # Close tilt status would be received from device when non-optimistic @@ -1004,7 +1014,9 @@ async def test_tilt_given_value(hass, mqtt_mock): blocking=True, ) - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 80, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "tilt-command-topic", "80", 0, False + ) mqtt_mock.async_publish.reset_mock() # Open tilt status would be received from device when non-optimistic @@ -1022,7 +1034,9 @@ async def test_tilt_given_value(hass, mqtt_mock): blocking=True, ) - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 25, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "tilt-command-topic", "25", 0, False + ) async def test_tilt_given_value_optimistic(hass, mqtt_mock): @@ -1062,7 +1076,9 @@ async def test_tilt_given_value_optimistic(hass, mqtt_mock): ] assert current_cover_tilt_position == 80 - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 80, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "tilt-command-topic", "80", 0, False + ) mqtt_mock.async_publish.reset_mock() await hass.services.async_call( @@ -1077,7 +1093,9 @@ async def test_tilt_given_value_optimistic(hass, mqtt_mock): ] assert current_cover_tilt_position == 25 - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 25, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "tilt-command-topic", "25", 0, False + ) async def test_tilt_given_value_altered_range(hass, mqtt_mock): @@ -1119,7 +1137,9 @@ async def test_tilt_given_value_altered_range(hass, mqtt_mock): ] assert current_cover_tilt_position == 50 - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 25, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "tilt-command-topic", "25", 0, False + ) mqtt_mock.async_publish.reset_mock() await hass.services.async_call( @@ -1134,7 +1154,7 @@ async def test_tilt_given_value_altered_range(hass, mqtt_mock): ] assert current_cover_tilt_position == 0 - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 0, 0, False) + mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", "0", 0, False) mqtt_mock.async_publish.reset_mock() await hass.services.async_call( @@ -1149,7 +1169,9 @@ async def test_tilt_given_value_altered_range(hass, mqtt_mock): ] assert current_cover_tilt_position == 50 - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 25, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "tilt-command-topic", "25", 0, False + ) async def test_tilt_via_topic(hass, mqtt_mock): @@ -1353,7 +1375,9 @@ async def test_tilt_position(hass, mqtt_mock): blocking=True, ) - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 50, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "tilt-command-topic", "50", 0, False + ) async def test_tilt_position_altered_range(hass, mqtt_mock): @@ -1389,7 +1413,9 @@ async def test_tilt_position_altered_range(hass, mqtt_mock): blocking=True, ) - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 25, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "tilt-command-topic", "25", 0, False + ) async def test_find_percentage_in_range_defaults(hass, mqtt_mock): diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 7b634714a5b..7a833a72d51 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -122,7 +122,7 @@ async def test_service_call_with_template_payload_renders_template(hass, mqtt_mo mqtt.async_publish_template(hass, "test/topic", "{{ 1+1 }}") await hass.async_block_till_done() assert mqtt_mock.async_publish.called - assert mqtt_mock.async_publish.call_args[0][1] == 2 + assert mqtt_mock.async_publish.call_args[0][1] == "2" async def test_service_call_with_payload_doesnt_render_template(hass, mqtt_mock): @@ -1497,3 +1497,46 @@ async def test_debug_info_qos_retain(hass, mqtt_mock): "time": start_dt, "topic": "sensor/abc", } in debug_info_data["entities"][0]["subscriptions"][0]["messages"] + + +async def test_publish_json_from_template(hass, mqtt_mock): + """Test the publishing of call to services.""" + test_str = "{'valid': 'python', 'invalid': 'json'}" + + await async_setup_component( + hass, + "script", + { + "script": { + "test_script_payload": { + "sequence": { + "service": "mqtt.publish", + "data": {"topic": "test-topic", "payload": test_str}, + } + }, + "test_script_payload_template": { + "sequence": { + "service": "mqtt.publish", + "data": {"topic": "test-topic", "payload_template": test_str}, + } + }, + } + }, + ) + + await hass.services.async_call("script", "test_script_payload", blocking=True) + await hass.async_block_till_done() + + assert mqtt_mock.async_publish.called + assert mqtt_mock.async_publish.call_args[0][1] == test_str + + mqtt_mock.async_publish.reset_mock() + assert not mqtt_mock.async_publish.called + + await hass.services.async_call( + "script", "test_script_payload_template", blocking=True + ) + await hass.async_block_till_done() + + assert mqtt_mock.async_publish.called + assert mqtt_mock.async_publish.call_args[0][1] == test_str diff --git a/tests/components/mqtt/test_light.py b/tests/components/mqtt/test_light.py index 62291fd18b7..102d1726ccc 100644 --- a/tests/components/mqtt/test_light.py +++ b/tests/components/mqtt/test_light.py @@ -760,11 +760,11 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): [ call("test_light_rgb/set", "on", 2, False), call("test_light_rgb/rgb/set", "255,128,0", 2, False), - call("test_light_rgb/brightness/set", 50, 2, False), + call("test_light_rgb/brightness/set", "50", 2, False), 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/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), + call("test_light_rgb/color_temp/set", "125", 2, False), ], any_order=True, ) @@ -841,7 +841,7 @@ async def test_sending_mqtt_color_temp_command_with_template(hass, mqtt_mock): mqtt_mock.async_publish.assert_has_calls( [ call("test_light_color_temp/set", "on", 0, False), - call("test_light_color_temp/color_temp/set", 10, 0, False), + call("test_light_color_temp/color_temp/set", "10", 0, False), ], any_order=True, ) @@ -877,7 +877,7 @@ async def test_on_command_first(hass, mqtt_mock): mqtt_mock.async_publish.assert_has_calls( [ call("test_light/set", "ON", 0, False), - call("test_light/bright", 50, 0, False), + call("test_light/bright", "50", 0, False), ], ) mqtt_mock.async_publish.reset_mock() @@ -911,7 +911,7 @@ async def test_on_command_last(hass, mqtt_mock): # test_light/set: 'ON' mqtt_mock.async_publish.assert_has_calls( [ - call("test_light/bright", 50, 0, False), + call("test_light/bright", "50", 0, False), call("test_light/set", "ON", 0, False), ], ) @@ -946,7 +946,9 @@ async def test_on_command_brightness(hass, mqtt_mock): # Should get the following MQTT messages. # test_light/bright: 255 - mqtt_mock.async_publish.assert_called_once_with("test_light/bright", 255, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "test_light/bright", "255", 0, False + ) mqtt_mock.async_publish.reset_mock() await common.async_turn_off(hass, "light.test") @@ -957,7 +959,7 @@ async def test_on_command_brightness(hass, mqtt_mock): # Turn on w/ brightness await common.async_turn_on(hass, "light.test", brightness=50) - mqtt_mock.async_publish.assert_called_once_with("test_light/bright", 50, 0, False) + mqtt_mock.async_publish.assert_called_once_with("test_light/bright", "50", 0, False) mqtt_mock.async_publish.reset_mock() await common.async_turn_off(hass, "light.test") @@ -969,7 +971,7 @@ async def test_on_command_brightness(hass, mqtt_mock): mqtt_mock.async_publish.assert_has_calls( [ call("test_light/rgb", "255,128,0", 0, False), - call("test_light/bright", 50, 0, False), + call("test_light/bright", "50", 0, False), ], any_order=True, ) @@ -1000,7 +1002,9 @@ async def test_on_command_brightness_scaled(hass, mqtt_mock): # Should get the following MQTT messages. # test_light/bright: 100 - mqtt_mock.async_publish.assert_called_once_with("test_light/bright", 100, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "test_light/bright", "100", 0, False + ) mqtt_mock.async_publish.reset_mock() await common.async_turn_off(hass, "light.test") @@ -1011,19 +1015,21 @@ async def test_on_command_brightness_scaled(hass, mqtt_mock): # Turn on w/ brightness await common.async_turn_on(hass, "light.test", brightness=50) - mqtt_mock.async_publish.assert_called_once_with("test_light/bright", 20, 0, False) + mqtt_mock.async_publish.assert_called_once_with("test_light/bright", "20", 0, False) mqtt_mock.async_publish.reset_mock() # Turn on w/ max brightness await common.async_turn_on(hass, "light.test", brightness=255) - mqtt_mock.async_publish.assert_called_once_with("test_light/bright", 100, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "test_light/bright", "100", 0, False + ) mqtt_mock.async_publish.reset_mock() # Turn on w/ min brightness await common.async_turn_on(hass, "light.test", brightness=1) - mqtt_mock.async_publish.assert_called_once_with("test_light/bright", 1, 0, False) + mqtt_mock.async_publish.assert_called_once_with("test_light/bright", "1", 0, False) mqtt_mock.async_publish.reset_mock() await common.async_turn_off(hass, "light.test") @@ -1035,7 +1041,7 @@ async def test_on_command_brightness_scaled(hass, mqtt_mock): mqtt_mock.async_publish.assert_has_calls( [ call("test_light/rgb", "255,128,0", 0, False), - call("test_light/bright", 1, 0, False), + call("test_light/bright", "1", 0, False), ], any_order=True, )