Add config flow for image platform in Template (#121648)

This commit is contained in:
dougiteixeira 2024-07-10 18:49:24 -03:00 committed by GitHub
parent 088717926d
commit e0c7073da1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 213 additions and 4 deletions

View File

@ -24,6 +24,8 @@ from homeassistant.const import (
CONF_NAME, CONF_NAME,
CONF_STATE, CONF_STATE,
CONF_UNIT_OF_MEASUREMENT, CONF_UNIT_OF_MEASUREMENT,
CONF_URL,
CONF_VERIFY_SSL,
Platform, Platform,
) )
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
@ -83,6 +85,13 @@ def generate_schema(domain: str, flow_type: str) -> vol.Schema:
) )
} }
if domain == Platform.IMAGE:
schema |= {
vol.Required(CONF_URL): selector.TemplateSelector(),
vol.Optional(CONF_VERIFY_SSL, default=True): selector.BooleanSelector(),
vol.Optional(CONF_DEVICE_ID): selector.DeviceSelector(),
}
if domain == Platform.SENSOR: if domain == Platform.SENSOR:
schema |= _SCHEMA_STATE | { schema |= _SCHEMA_STATE | {
vol.Optional(CONF_UNIT_OF_MEASUREMENT): selector.SelectSelector( vol.Optional(CONF_UNIT_OF_MEASUREMENT): selector.SelectSelector(
@ -213,6 +222,7 @@ def validate_user_input(
TEMPLATE_TYPES = [ TEMPLATE_TYPES = [
"binary_sensor", "binary_sensor",
"button", "button",
"image",
"sensor", "sensor",
] ]
@ -227,6 +237,10 @@ CONFIG_FLOW = {
config_schema(Platform.BUTTON), config_schema(Platform.BUTTON),
validate_user_input=validate_user_input(Platform.BUTTON), validate_user_input=validate_user_input(Platform.BUTTON),
), ),
Platform.IMAGE: SchemaFlowFormStep(
config_schema(Platform.IMAGE),
validate_user_input=validate_user_input(Platform.IMAGE),
),
Platform.SENSOR: SchemaFlowFormStep( Platform.SENSOR: SchemaFlowFormStep(
config_schema(Platform.SENSOR), config_schema(Platform.SENSOR),
preview="template", preview="template",
@ -246,6 +260,10 @@ OPTIONS_FLOW = {
options_schema(Platform.BUTTON), options_schema(Platform.BUTTON),
validate_user_input=validate_user_input(Platform.BUTTON), validate_user_input=validate_user_input(Platform.BUTTON),
), ),
Platform.IMAGE: SchemaFlowFormStep(
options_schema(Platform.IMAGE),
validate_user_input=validate_user_input(Platform.IMAGE),
),
Platform.SENSOR: SchemaFlowFormStep( Platform.SENSOR: SchemaFlowFormStep(
options_schema(Platform.SENSOR), options_schema(Platform.SENSOR),
preview="template", preview="template",

View File

@ -8,10 +8,18 @@ from typing import Any
import voluptuous as vol import voluptuous as vol
from homeassistant.components.image import DOMAIN as IMAGE_DOMAIN, ImageEntity from homeassistant.components.image import DOMAIN as IMAGE_DOMAIN, ImageEntity
from homeassistant.const import CONF_UNIQUE_ID, CONF_URL, CONF_VERIFY_SSL from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_DEVICE_ID,
CONF_NAME,
CONF_UNIQUE_ID,
CONF_URL,
CONF_VERIFY_SSL,
)
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import TemplateError from homeassistant.exceptions import TemplateError
import homeassistant.helpers.config_validation as cv from homeassistant.helpers import config_validation as cv, selector
from homeassistant.helpers.device import async_device_info_to_link_from_device_id
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
@ -35,6 +43,16 @@ IMAGE_SCHEMA = vol.Schema(
).extend(make_template_entity_common_schema(DEFAULT_NAME).schema) ).extend(make_template_entity_common_schema(DEFAULT_NAME).schema)
IMAGE_CONFIG_SCHEMA = vol.Schema(
{
vol.Optional(CONF_NAME): cv.template,
vol.Required(CONF_URL): cv.template,
vol.Optional(CONF_VERIFY_SSL, default=True): bool,
vol.Optional(CONF_DEVICE_ID): selector.DeviceSelector(),
}
)
async def _async_create_entities( async def _async_create_entities(
hass: HomeAssistant, definitions: list[dict[str, Any]], unique_id_prefix: str | None hass: HomeAssistant, definitions: list[dict[str, Any]], unique_id_prefix: str | None
) -> list[StateImageEntity]: ) -> list[StateImageEntity]:
@ -75,6 +93,20 @@ async def async_setup_platform(
) )
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Initialize config entry."""
_options = dict(config_entry.options)
_options.pop("template_type")
validated_config = IMAGE_CONFIG_SCHEMA(_options)
async_add_entities(
[StateImageEntity(hass, validated_config, config_entry.entry_id)]
)
class StateImageEntity(TemplateEntity, ImageEntity): class StateImageEntity(TemplateEntity, ImageEntity):
"""Representation of a template image.""" """Representation of a template image."""
@ -91,6 +123,10 @@ class StateImageEntity(TemplateEntity, ImageEntity):
TemplateEntity.__init__(self, hass, config=config, unique_id=unique_id) TemplateEntity.__init__(self, hass, config=config, unique_id=unique_id)
ImageEntity.__init__(self, hass, config[CONF_VERIFY_SSL]) ImageEntity.__init__(self, hass, config[CONF_VERIFY_SSL])
self._url_template = config[CONF_URL] self._url_template = config[CONF_URL]
self._attr_device_info = async_device_info_to_link_from_device_id(
hass,
config.get(CONF_DEVICE_ID),
)
@property @property
def entity_picture(self) -> str | None: def entity_picture(self) -> str | None:

View File

@ -25,6 +25,18 @@
}, },
"title": "Template button" "title": "Template button"
}, },
"image": {
"data": {
"device_id": "[%key:common::config_flow::data::device%]",
"name": "[%key:common::config_flow::data::name%]",
"url": "[%key:common::config_flow::data::url%]",
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
},
"data_description": {
"device_id": "[%key:component::template::config::step::sensor::data_description::device_id%]"
},
"title": "Template image"
},
"sensor": { "sensor": {
"data": { "data": {
"device_id": "[%key:common::config_flow::data::device%]", "device_id": "[%key:common::config_flow::data::device%]",
@ -44,6 +56,7 @@
"menu_options": { "menu_options": {
"binary_sensor": "Template a binary sensor", "binary_sensor": "Template a binary sensor",
"button": "Template a button", "button": "Template a button",
"image": "Template a image",
"sensor": "Template a sensor" "sensor": "Template a sensor"
}, },
"title": "Template helper" "title": "Template helper"
@ -72,6 +85,17 @@
}, },
"title": "[%key:component::template::config::step::button::title%]" "title": "[%key:component::template::config::step::button::title%]"
}, },
"image": {
"data": {
"device_id": "[%key:common::config_flow::data::device%]",
"url": "[%key:common::config_flow::data::url%]",
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
},
"data_description": {
"device_id": "[%key:component::template::config::step::sensor::data_description::device_id%]"
},
"title": "[%key:component::template::config::step::image::title%]"
},
"sensor": { "sensor": {
"data": { "data": {
"device_id": "[%key:common::config_flow::data::device%]", "device_id": "[%key:common::config_flow::data::device%]",

View File

@ -81,8 +81,19 @@ from tests.typing import WebSocketGenerator
}, },
{}, {},
), ),
(
"image",
{"url": "{{ states('sensor.one') }}"},
"2024-07-09T00:00:00+00:00",
{"one": "http://www.test.com", "two": ""},
{},
{"verify_ssl": True},
{"verify_ssl": True},
{},
),
], ],
) )
@pytest.mark.freeze_time("2024-07-09 00:00:00+00:00")
async def test_config_flow( async def test_config_flow(
hass: HomeAssistant, hass: HomeAssistant,
template_type, template_type,
@ -181,6 +192,14 @@ async def test_config_flow(
{}, {},
{}, {},
), ),
(
"image",
{
"url": "{{ states('sensor.one') }}",
},
{"verify_ssl": True},
{"verify_ssl": True},
),
], ],
) )
async def test_config_flow_device( async def test_config_flow_device(
@ -330,8 +349,25 @@ def get_suggested(schema, key):
], ],
}, },
), ),
(
"image",
{
"url": "{{ states('sensor.one') }}",
},
{
"url": "{{ states('sensor.two') }}",
},
["2024-07-09T00:00:00+00:00", "2024-07-09T00:00:00+00:00"],
{"one": "http://www.test.com", "two": "http://www.test2.com"},
{"verify_ssl": True},
{
"url": "{{ states('sensor.two') }}",
"verify_ssl": True,
},
),
], ],
) )
@pytest.mark.freeze_time("2024-07-09 00:00:00+00:00")
async def test_options( async def test_options(
hass: HomeAssistant, hass: HomeAssistant,
template_type, template_type,
@ -1050,6 +1086,15 @@ async def test_option_flow_sensor_preview_config_entry_removed(
{}, {},
{}, {},
), ),
(
"image",
{
"url": "{{ states('sensor.one') }}",
"verify_ssl": True,
},
{},
{},
),
], ],
) )
async def test_options_flow_change_device( async def test_options_flow_change_device(

View File

@ -8,6 +8,7 @@ import httpx
from PIL import Image from PIL import Image
import pytest import pytest
import respx import respx
from syrupy.assertion import SnapshotAssertion
from homeassistant import setup from homeassistant import setup
from homeassistant.components.input_text import ( from homeassistant.components.input_text import (
@ -15,12 +16,13 @@ from homeassistant.components.input_text import (
DOMAIN as INPUT_TEXT_DOMAIN, DOMAIN as INPUT_TEXT_DOMAIN,
SERVICE_SET_VALUE as INPUT_TEXT_SERVICE_SET_VALUE, SERVICE_SET_VALUE as INPUT_TEXT_SERVICE_SET_VALUE,
) )
from homeassistant.components.template import DOMAIN
from homeassistant.const import ATTR_ENTITY_PICTURE, CONF_ENTITY_ID, STATE_UNKNOWN from homeassistant.const import ATTR_ENTITY_PICTURE, CONF_ENTITY_ID, STATE_UNKNOWN
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from tests.common import assert_setup_component from tests.common import MockConfigEntry, assert_setup_component
from tests.typing import ClientSessionGenerator from tests.typing import ClientSessionGenerator
_DEFAULT = object() _DEFAULT = object()
@ -74,6 +76,37 @@ async def _assert_state(
assert body == expected_image assert body == expected_image
@pytest.mark.freeze_time("2024-07-09 00:00:00+00:00")
async def test_setup_config_entry(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
) -> None:
"""Test the config flow."""
respx.get("http://example.com").respond(
stream=imgbytes_jpg, content_type="image/jpeg"
)
template_config_entry = MockConfigEntry(
data={},
domain=DOMAIN,
options={
"name": "My template",
"template_type": "image",
"url": "http://example.com",
},
title="My template",
)
template_config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(template_config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("image.my_template")
assert state is not None
assert state.state == "2024-07-09T00:00:00+00:00"
@respx.mock @respx.mock
@pytest.mark.freeze_time("2023-04-01 00:00:00+00:00") @pytest.mark.freeze_time("2023-04-01 00:00:00+00:00")
async def test_platform_config( async def test_platform_config(
@ -503,3 +536,46 @@ async def test_trigger_image_custom_entity_picture(
imgbytes_jpg, imgbytes_jpg,
expected_entity_picture="http://example2.com", expected_entity_picture="http://example2.com",
) )
async def test_device_id(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test for device for image template."""
device_config_entry = MockConfigEntry()
device_config_entry.add_to_hass(hass)
device_entry = device_registry.async_get_or_create(
config_entry_id=device_config_entry.entry_id,
identifiers={("test", "identifier_test")},
connections={("mac", "30:31:32:33:34:35")},
)
await hass.async_block_till_done()
assert device_entry is not None
assert device_entry.id is not None
respx.get("http://example.com").respond(
stream=imgbytes_jpg, content_type="image/jpeg"
)
template_config_entry = MockConfigEntry(
data={},
domain=DOMAIN,
options={
"name": "My template",
"template_type": "image",
"url": "http://example.com",
"device_id": device_entry.id,
},
title="My template",
)
template_config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(template_config_entry.entry_id)
await hass.async_block_till_done()
template_entity = entity_registry.async_get("image.my_template")
assert template_entity is not None
assert template_entity.device_id == device_entry.id

View File

@ -297,6 +297,16 @@ async def async_yaml_patch_helper(hass, filename):
"state": "{{1 == 2}}", "state": "{{1 == 2}}",
}, },
), ),
(
{
"template_type": "image",
"name": "My template",
"url": "http://example.com",
},
{
"url": "http://example.com",
},
),
( (
{ {
"template_type": "button", "template_type": "button",