diff --git a/homeassistant/components/generic/config_flow.py b/homeassistant/components/generic/config_flow.py index 651ed87da39..272c7a2d98e 100644 --- a/homeassistant/components/generic/config_flow.py +++ b/homeassistant/components/generic/config_flow.py @@ -198,6 +198,13 @@ async def async_test_stream(hass, info) -> dict[str, str]: """Verify that the stream is valid before we create an entity.""" if not (stream_source := info.get(CONF_STREAM_SOURCE)): return {} + if not isinstance(stream_source, template_helper.Template): + stream_source = template_helper.Template(stream_source, hass) + try: + stream_source = stream_source.async_render(parse_result=False) + except TemplateError as err: + _LOGGER.warning("Problem rendering template %s: %s", stream_source, err) + return {CONF_STREAM_SOURCE: "template_error"} try: # For RTSP streams, prefer TCP. This code is duplicated from # homeassistant.components.stream.__init__.py:create_stream() diff --git a/homeassistant/components/generic/strings.json b/homeassistant/components/generic/strings.json index 0954656f71d..6b73c70cf3d 100644 --- a/homeassistant/components/generic/strings.json +++ b/homeassistant/components/generic/strings.json @@ -8,6 +8,7 @@ "invalid_still_image": "URL did not return a valid still image", "stream_file_not_found": "File not found while trying to connect to stream (is ffmpeg installed?)", "stream_http_not_found": "HTTP 404 Not found while trying to connect to stream", + "template_error": "Error rendering template. Review log for more info.", "timeout": "Timeout while loading URL", "stream_no_route_to_host": "Could not find host while trying to connect to stream", "stream_io_error": "Input/Output error while trying to connect to stream. Wrong RTSP transport protocol?", @@ -79,6 +80,7 @@ "invalid_still_image": "[%key:component::generic::config::error::invalid_still_image%]", "stream_file_not_found": "[%key:component::generic::config::error::stream_file_not_found%]", "stream_http_not_found": "[%key:component::generic::config::error::stream_http_not_found%]", + "template_error": "[%key:component::generic::config::error::template_error%]", "timeout": "[%key:component::generic::config::error::timeout%]", "stream_no_route_to_host": "[%key:component::generic::config::error::stream_no_route_to_host%]", "stream_io_error": "[%key:component::generic::config::error::stream_io_error%]", diff --git a/homeassistant/components/generic/translations/en.json b/homeassistant/components/generic/translations/en.json index b552c780d29..d01e6e59a4b 100644 --- a/homeassistant/components/generic/translations/en.json +++ b/homeassistant/components/generic/translations/en.json @@ -15,6 +15,7 @@ "stream_no_video": "Stream has no video", "stream_not_permitted": "Operation not permitted while trying to connect to stream. Wrong RTSP transport protocol?", "stream_unauthorised": "Authorisation failed while trying to connect to stream", + "template_error": "Error rendering template. Review log for more info.", "timeout": "Timeout while loading URL", "unable_still_load": "Unable to load valid image from still image URL (e.g. invalid host, URL or authentication failure). Review log for more info.", "unknown": "Unexpected error" @@ -57,6 +58,7 @@ "stream_no_video": "Stream has no video", "stream_not_permitted": "Operation not permitted while trying to connect to stream. Wrong RTSP transport protocol?", "stream_unauthorised": "Authorisation failed while trying to connect to stream", + "template_error": "Error rendering template. Review log for more info.", "timeout": "Timeout while loading URL", "unable_still_load": "Unable to load valid image from still image URL (e.g. invalid host, URL or authentication failure). Review log for more info.", "unknown": "Unexpected error" diff --git a/tests/components/generic/test_config_flow.py b/tests/components/generic/test_config_flow.py index b7f3bf73527..a525619d962 100644 --- a/tests/components/generic/test_config_flow.py +++ b/tests/components/generic/test_config_flow.py @@ -534,6 +534,16 @@ async def test_options_template_error(hass, fakeimgbytes_png, mock_av_open): assert result4.get("type") == data_entry_flow.RESULT_TYPE_FORM assert result4["errors"] == {"still_image_url": "template_error"} + # verify that an invalid template reports the correct UI error. + data[CONF_STILL_IMAGE_URL] = "http://127.0.0.1/testurl/1" + data[CONF_STREAM_SOURCE] = "http://127.0.0.2/testurl/{{1/0}}" + result5 = await hass.config_entries.options.async_configure( + result4["flow_id"], + user_input=data, + ) + assert result5.get("type") == data_entry_flow.RESULT_TYPE_FORM + assert result5["errors"] == {"stream_source": "template_error"} + @respx.mock async def test_options_only_stream(hass, fakeimgbytes_png, mock_av_open):