Add light as entity platform on MQTT subentries (#141345)

* Add light as entity platform on MQTT subentries

* Improve translation strings

* Rename to separate brightness

* Remove option to use mireds for color temperature

* Fix tests

* Add translation reference

* Correct reference

* Add flash and transition feature switches

---------

Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
Jan Bouwhuis
2025-04-30 15:57:51 +02:00
committed by GitHub
parent 84634ce288
commit 5b0ea21607
6 changed files with 1032 additions and 111 deletions

View File

@ -27,6 +27,12 @@ import voluptuous as vol
from homeassistant.components.file_upload import process_uploaded_file
from homeassistant.components.hassio import AddonError, AddonManager, AddonState
from homeassistant.components.light import (
DEFAULT_MAX_KELVIN,
DEFAULT_MIN_KELVIN,
VALID_COLOR_MODES,
valid_supported_color_modes,
)
from homeassistant.components.sensor import (
CONF_STATE_CLASS,
DEVICE_CLASS_UNITS,
@ -50,18 +56,23 @@ from homeassistant.const import (
ATTR_MODEL_ID,
ATTR_NAME,
ATTR_SW_VERSION,
CONF_BRIGHTNESS,
CONF_CLIENT_ID,
CONF_DEVICE,
CONF_DEVICE_CLASS,
CONF_DISCOVERY,
CONF_EFFECT,
CONF_HOST,
CONF_NAME,
CONF_OPTIMISTIC,
CONF_PASSWORD,
CONF_PAYLOAD,
CONF_PAYLOAD_OFF,
CONF_PAYLOAD_ON,
CONF_PLATFORM,
CONF_PORT,
CONF_PROTOCOL,
CONF_STATE_TEMPLATE,
CONF_UNIT_OF_MEASUREMENT,
CONF_USERNAME,
CONF_VALUE_TEMPLATE,
@ -102,37 +113,97 @@ from .const import (
CONF_AVAILABILITY_TEMPLATE,
CONF_AVAILABILITY_TOPIC,
CONF_BIRTH_MESSAGE,
CONF_BLUE_TEMPLATE,
CONF_BRIGHTNESS_COMMAND_TEMPLATE,
CONF_BRIGHTNESS_COMMAND_TOPIC,
CONF_BRIGHTNESS_SCALE,
CONF_BRIGHTNESS_STATE_TOPIC,
CONF_BRIGHTNESS_TEMPLATE,
CONF_BRIGHTNESS_VALUE_TEMPLATE,
CONF_BROKER,
CONF_CERTIFICATE,
CONF_CLIENT_CERT,
CONF_CLIENT_KEY,
CONF_COLOR_MODE_STATE_TOPIC,
CONF_COLOR_MODE_VALUE_TEMPLATE,
CONF_COLOR_TEMP_COMMAND_TEMPLATE,
CONF_COLOR_TEMP_COMMAND_TOPIC,
CONF_COLOR_TEMP_KELVIN,
CONF_COLOR_TEMP_STATE_TOPIC,
CONF_COLOR_TEMP_TEMPLATE,
CONF_COLOR_TEMP_VALUE_TEMPLATE,
CONF_COMMAND_OFF_TEMPLATE,
CONF_COMMAND_ON_TEMPLATE,
CONF_COMMAND_TEMPLATE,
CONF_COMMAND_TOPIC,
CONF_DISCOVERY_PREFIX,
CONF_EFFECT_COMMAND_TEMPLATE,
CONF_EFFECT_COMMAND_TOPIC,
CONF_EFFECT_LIST,
CONF_EFFECT_STATE_TOPIC,
CONF_EFFECT_TEMPLATE,
CONF_EFFECT_VALUE_TEMPLATE,
CONF_ENTITY_PICTURE,
CONF_EXPIRE_AFTER,
CONF_FLASH,
CONF_FLASH_TIME_LONG,
CONF_FLASH_TIME_SHORT,
CONF_GREEN_TEMPLATE,
CONF_HS_COMMAND_TEMPLATE,
CONF_HS_COMMAND_TOPIC,
CONF_HS_STATE_TOPIC,
CONF_HS_VALUE_TEMPLATE,
CONF_KEEPALIVE,
CONF_LAST_RESET_VALUE_TEMPLATE,
CONF_MAX_KELVIN,
CONF_MIN_KELVIN,
CONF_ON_COMMAND_TYPE,
CONF_OPTIONS,
CONF_PAYLOAD_AVAILABLE,
CONF_PAYLOAD_NOT_AVAILABLE,
CONF_QOS,
CONF_RED_TEMPLATE,
CONF_RETAIN,
CONF_RGB_COMMAND_TEMPLATE,
CONF_RGB_COMMAND_TOPIC,
CONF_RGB_STATE_TOPIC,
CONF_RGB_VALUE_TEMPLATE,
CONF_RGBW_COMMAND_TEMPLATE,
CONF_RGBW_COMMAND_TOPIC,
CONF_RGBW_STATE_TOPIC,
CONF_RGBW_VALUE_TEMPLATE,
CONF_RGBWW_COMMAND_TEMPLATE,
CONF_RGBWW_COMMAND_TOPIC,
CONF_RGBWW_STATE_TOPIC,
CONF_RGBWW_VALUE_TEMPLATE,
CONF_SCHEMA,
CONF_STATE_TOPIC,
CONF_STATE_VALUE_TEMPLATE,
CONF_SUGGESTED_DISPLAY_PRECISION,
CONF_SUPPORTED_COLOR_MODES,
CONF_TLS_INSECURE,
CONF_TRANSITION,
CONF_TRANSPORT,
CONF_WHITE_COMMAND_TOPIC,
CONF_WHITE_SCALE,
CONF_WILL_MESSAGE,
CONF_WS_HEADERS,
CONF_WS_PATH,
CONF_XY_COMMAND_TEMPLATE,
CONF_XY_COMMAND_TOPIC,
CONF_XY_STATE_TOPIC,
CONF_XY_VALUE_TEMPLATE,
CONFIG_ENTRY_MINOR_VERSION,
CONFIG_ENTRY_VERSION,
DEFAULT_BIRTH,
DEFAULT_DISCOVERY,
DEFAULT_ENCODING,
DEFAULT_KEEPALIVE,
DEFAULT_ON_COMMAND_TYPE,
DEFAULT_PAYLOAD_AVAILABLE,
DEFAULT_PAYLOAD_NOT_AVAILABLE,
DEFAULT_PAYLOAD_OFF,
DEFAULT_PAYLOAD_ON,
DEFAULT_PORT,
DEFAULT_PREFIX,
DEFAULT_PROTOCOL,
@ -144,6 +215,7 @@ from .const import (
SUPPORTED_PROTOCOLS,
TRANSPORT_TCP,
TRANSPORT_WEBSOCKETS,
VALUES_ON_COMMAND_TYPE,
Platform,
)
from .models import MqttAvailabilityData, MqttDeviceData, MqttSubentryData
@ -233,7 +305,7 @@ KEY_UPLOAD_SELECTOR = FileSelector(
)
# Subentry selectors
SUBENTRY_PLATFORMS = [Platform.NOTIFY, Platform.SENSOR, Platform.SWITCH]
SUBENTRY_PLATFORMS = [Platform.LIGHT, Platform.NOTIFY, Platform.SENSOR, Platform.SWITCH]
SUBENTRY_PLATFORM_SELECTOR = SelectSelector(
SelectSelectorConfig(
options=[platform.value for platform in SUBENTRY_PLATFORMS],
@ -295,6 +367,54 @@ SWITCH_DEVICE_CLASS_SELECTOR = SelectSelector(
)
)
# Light specific selectors
LIGHT_SCHEMA_SELECTOR = SelectSelector(
SelectSelectorConfig(
options=["basic", "json", "template"],
translation_key="light_schema",
)
)
KELVIN_SELECTOR = NumberSelector(
NumberSelectorConfig(
mode=NumberSelectorMode.BOX,
min=1000,
max=10000,
step="any",
unit_of_measurement="K",
)
)
SCALE_SELECTOR = NumberSelector(
NumberSelectorConfig(
mode=NumberSelectorMode.BOX,
min=1,
max=255,
step=1,
)
)
FLASH_TIME_SELECTOR = NumberSelector(
NumberSelectorConfig(
mode=NumberSelectorMode.BOX,
min=1,
)
)
ON_COMMAND_TYPE_SELECTOR = SelectSelector(
SelectSelectorConfig(
options=VALUES_ON_COMMAND_TYPE,
mode=SelectSelectorMode.DROPDOWN,
translation_key=CONF_ON_COMMAND_TYPE,
sort=True,
)
)
SUPPORTED_COLOR_MODES_SELECTOR = SelectSelector(
SelectSelectorConfig(
options=[platform.value for platform in VALID_COLOR_MODES],
mode=SelectSelectorMode.DROPDOWN,
translation_key=CONF_SUPPORTED_COLOR_MODES,
multiple=True,
sort=True,
)
)
@callback
def validate_sensor_platform_config(
@ -345,7 +465,8 @@ class PlatformField:
required: bool
validator: Callable[..., Any]
error: str | None = None
default: str | int | vol.Undefined = vol.UNDEFINED
default: str | int | bool | vol.Undefined = vol.UNDEFINED
is_schema_default: bool = False
exclude_from_reconfig: bool = False
conditions: tuple[dict[str, Any], ...] | None = None
custom_filtering: bool = False
@ -370,6 +491,18 @@ def unit_of_measurement_selector(user_data: dict[str, Any | None]) -> Selector:
)
@callback
def validate_light_platform_config(user_data: dict[str, Any]) -> dict[str, str]:
"""Validate MQTT light configuration."""
errors: dict[str, Any] = {}
if user_data.get(CONF_MIN_KELVIN, DEFAULT_MIN_KELVIN) >= user_data.get(
CONF_MAX_KELVIN, DEFAULT_MAX_KELVIN
):
errors[CONF_MAX_KELVIN] = "max_below_min_kelvin"
errors[CONF_MIN_KELVIN] = "max_below_min_kelvin"
return errors
COMMON_ENTITY_FIELDS = {
CONF_PLATFORM: PlatformField(
selector=SUBENTRY_PLATFORM_SELECTOR,
@ -421,6 +554,22 @@ PLATFORM_ENTITY_FIELDS = {
selector=SWITCH_DEVICE_CLASS_SELECTOR, required=False, validator=str
),
},
Platform.LIGHT.value: {
CONF_SCHEMA: PlatformField(
selector=LIGHT_SCHEMA_SELECTOR,
required=True,
validator=str,
default="basic",
exclude_from_reconfig=True,
),
CONF_COLOR_TEMP_KELVIN: PlatformField(
selector=BOOLEAN_SELECTOR,
required=True,
validator=bool,
default=True,
is_schema_default=True,
),
},
}
PLATFORM_MQTT_FIELDS = {
Platform.NOTIFY.value: {
@ -499,11 +648,502 @@ PLATFORM_MQTT_FIELDS = {
selector=BOOLEAN_SELECTOR, required=False, validator=bool
),
},
Platform.LIGHT.value: {
CONF_COMMAND_TOPIC: PlatformField(
selector=TEXT_SELECTOR,
required=True,
validator=valid_publish_topic,
error="invalid_publish_topic",
),
CONF_COMMAND_ON_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=True,
validator=cv.template,
error="invalid_template",
conditions=({CONF_SCHEMA: "template"},),
),
CONF_COMMAND_OFF_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=True,
validator=cv.template,
error="invalid_template",
conditions=({CONF_SCHEMA: "template"},),
),
CONF_ON_COMMAND_TYPE: PlatformField(
selector=ON_COMMAND_TYPE_SELECTOR,
required=False,
validator=str,
default=DEFAULT_ON_COMMAND_TYPE,
conditions=({CONF_SCHEMA: "basic"},),
),
CONF_STATE_TOPIC: PlatformField(
selector=TEXT_SELECTOR,
required=False,
validator=valid_subscribe_topic,
error="invalid_subscribe_topic",
),
CONF_STATE_VALUE_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=cv.template,
error="invalid_template",
conditions=({CONF_SCHEMA: "basic"},),
),
CONF_STATE_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=cv.template,
error="invalid_template",
conditions=({CONF_SCHEMA: "template"},),
),
CONF_SUPPORTED_COLOR_MODES: PlatformField(
selector=SUPPORTED_COLOR_MODES_SELECTOR,
required=False,
validator=valid_supported_color_modes,
error="invalid_supported_color_modes",
conditions=({CONF_SCHEMA: "json"},),
),
CONF_OPTIMISTIC: PlatformField(
selector=BOOLEAN_SELECTOR, required=False, validator=bool
),
CONF_RETAIN: PlatformField(
selector=BOOLEAN_SELECTOR,
required=False,
validator=bool,
conditions=({CONF_SCHEMA: "basic"},),
),
CONF_BRIGHTNESS: PlatformField(
selector=BOOLEAN_SELECTOR,
required=False,
validator=bool,
conditions=({CONF_SCHEMA: "json"},),
section="light_brightness_settings",
),
CONF_BRIGHTNESS_COMMAND_TOPIC: PlatformField(
selector=TEXT_SELECTOR,
required=False,
validator=valid_publish_topic,
error="invalid_publish_topic",
conditions=({CONF_SCHEMA: "basic"},),
section="light_brightness_settings",
),
CONF_BRIGHTNESS_COMMAND_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=cv.template,
error="invalid_template",
conditions=({CONF_SCHEMA: "basic"},),
section="light_brightness_settings",
),
CONF_BRIGHTNESS_STATE_TOPIC: PlatformField(
selector=TEXT_SELECTOR,
required=False,
validator=valid_subscribe_topic,
error="invalid_subscribe_topic",
conditions=({CONF_SCHEMA: "basic"},),
section="light_brightness_settings",
),
CONF_PAYLOAD_OFF: PlatformField(
selector=TEXT_SELECTOR,
required=False,
validator=str,
default=DEFAULT_PAYLOAD_OFF,
conditions=({CONF_SCHEMA: "basic"},),
),
CONF_PAYLOAD_ON: PlatformField(
selector=TEXT_SELECTOR,
required=False,
validator=str,
default=DEFAULT_PAYLOAD_ON,
conditions=({CONF_SCHEMA: "basic"},),
),
CONF_BRIGHTNESS_VALUE_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=cv.template,
error="invalid_template",
conditions=({CONF_SCHEMA: "basic"},),
section="light_brightness_settings",
),
CONF_BRIGHTNESS_SCALE: PlatformField(
selector=SCALE_SELECTOR,
required=False,
validator=cv.positive_int,
default=255,
conditions=(
{CONF_SCHEMA: "basic"},
{CONF_SCHEMA: "json"},
),
section="light_brightness_settings",
),
CONF_COLOR_MODE_STATE_TOPIC: PlatformField(
selector=TEXT_SELECTOR,
required=False,
validator=valid_subscribe_topic,
error="invalid_subscribe_topic",
conditions=({CONF_SCHEMA: "basic"},),
section="light_color_mode_settings",
),
CONF_COLOR_MODE_VALUE_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=cv.template,
error="invalid_template",
conditions=({CONF_SCHEMA: "basic"},),
section="light_color_mode_settings",
),
CONF_COLOR_TEMP_COMMAND_TOPIC: PlatformField(
selector=TEXT_SELECTOR,
required=False,
validator=valid_publish_topic,
error="invalid_publish_topic",
conditions=({CONF_SCHEMA: "basic"},),
section="light_color_temp_settings",
),
CONF_COLOR_TEMP_COMMAND_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=cv.template,
error="invalid_template",
conditions=({CONF_SCHEMA: "basic"},),
section="light_color_temp_settings",
),
CONF_COLOR_TEMP_STATE_TOPIC: PlatformField(
selector=TEXT_SELECTOR,
required=False,
validator=valid_subscribe_topic,
error="invalid_subscribe_topic",
conditions=({CONF_SCHEMA: "basic"},),
section="light_color_temp_settings",
),
CONF_COLOR_TEMP_VALUE_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=cv.template,
error="invalid_template",
conditions=({CONF_SCHEMA: "basic"},),
section="light_color_temp_settings",
),
CONF_BRIGHTNESS_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=cv.template,
error="invalid_template",
conditions=({CONF_SCHEMA: "template"},),
),
CONF_RED_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=cv.template,
error="invalid_template",
conditions=({CONF_SCHEMA: "template"},),
),
CONF_GREEN_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=cv.template,
error="invalid_template",
conditions=({CONF_SCHEMA: "template"},),
),
CONF_BLUE_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=cv.template,
error="invalid_template",
conditions=({CONF_SCHEMA: "template"},),
),
CONF_COLOR_TEMP_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=cv.template,
error="invalid_template",
conditions=({CONF_SCHEMA: "template"},),
),
CONF_HS_COMMAND_TOPIC: PlatformField(
selector=TEXT_SELECTOR,
required=False,
validator=valid_publish_topic,
error="invalid_publish_topic",
conditions=({CONF_SCHEMA: "basic"},),
section="light_hs_settings",
),
CONF_HS_COMMAND_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=cv.template,
error="invalid_template",
conditions=({CONF_SCHEMA: "basic"},),
section="light_hs_settings",
),
CONF_HS_STATE_TOPIC: PlatformField(
selector=TEXT_SELECTOR,
required=False,
validator=valid_subscribe_topic,
error="invalid_subscribe_topic",
conditions=({CONF_SCHEMA: "basic"},),
section="light_hs_settings",
),
CONF_HS_VALUE_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=cv.template,
error="invalid_template",
conditions=({CONF_SCHEMA: "basic"},),
section="light_hs_settings",
),
CONF_RGB_COMMAND_TOPIC: PlatformField(
selector=TEXT_SELECTOR,
required=False,
validator=valid_publish_topic,
error="invalid_publish_topic",
conditions=({CONF_SCHEMA: "basic"},),
section="light_rgb_settings",
),
CONF_RGB_COMMAND_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=cv.template,
error="invalid_template",
conditions=({CONF_SCHEMA: "basic"},),
section="light_rgb_settings",
),
CONF_RGB_STATE_TOPIC: PlatformField(
selector=TEXT_SELECTOR,
required=False,
validator=valid_subscribe_topic,
error="invalid_subscribe_topic",
conditions=({CONF_SCHEMA: "basic"},),
section="light_rgb_settings",
),
CONF_RGB_VALUE_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=cv.template,
error="invalid_template",
conditions=({CONF_SCHEMA: "basic"},),
section="light_rgb_settings",
),
CONF_RGBW_COMMAND_TOPIC: PlatformField(
selector=TEXT_SELECTOR,
required=False,
validator=valid_publish_topic,
error="invalid_publish_topic",
conditions=({CONF_SCHEMA: "basic"},),
section="light_rgbw_settings",
),
CONF_RGBW_COMMAND_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=cv.template,
error="invalid_template",
conditions=({CONF_SCHEMA: "basic"},),
section="light_rgbw_settings",
),
CONF_RGBW_STATE_TOPIC: PlatformField(
selector=TEXT_SELECTOR,
required=False,
validator=valid_subscribe_topic,
error="invalid_subscribe_topic",
conditions=({CONF_SCHEMA: "basic"},),
section="light_rgbw_settings",
),
CONF_RGBW_VALUE_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=cv.template,
error="invalid_template",
conditions=({CONF_SCHEMA: "basic"},),
section="light_rgbw_settings",
),
CONF_RGBWW_COMMAND_TOPIC: PlatformField(
selector=TEXT_SELECTOR,
required=False,
validator=valid_publish_topic,
error="invalid_publish_topic",
conditions=({CONF_SCHEMA: "basic"},),
section="light_rgbww_settings",
),
CONF_RGBWW_COMMAND_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=cv.template,
error="invalid_template",
conditions=({CONF_SCHEMA: "basic"},),
section="light_rgbww_settings",
),
CONF_RGBWW_STATE_TOPIC: PlatformField(
selector=TEXT_SELECTOR,
required=False,
validator=valid_subscribe_topic,
error="invalid_subscribe_topic",
conditions=({CONF_SCHEMA: "basic"},),
section="light_rgbww_settings",
),
CONF_RGBWW_VALUE_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=cv.template,
error="invalid_template",
conditions=({CONF_SCHEMA: "basic"},),
section="light_rgbww_settings",
),
CONF_XY_COMMAND_TOPIC: PlatformField(
selector=TEXT_SELECTOR,
required=False,
validator=valid_publish_topic,
error="invalid_publish_topic",
conditions=({CONF_SCHEMA: "basic"},),
section="light_xy_settings",
),
CONF_XY_COMMAND_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=cv.template,
error="invalid_template",
conditions=({CONF_SCHEMA: "basic"},),
section="light_xy_settings",
),
CONF_XY_STATE_TOPIC: PlatformField(
selector=TEXT_SELECTOR,
required=False,
validator=valid_subscribe_topic,
error="invalid_subscribe_topic",
conditions=({CONF_SCHEMA: "basic"},),
section="light_xy_settings",
),
CONF_XY_VALUE_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=cv.template,
error="invalid_template",
conditions=({CONF_SCHEMA: "basic"},),
section="light_xy_settings",
),
CONF_WHITE_COMMAND_TOPIC: PlatformField(
selector=TEXT_SELECTOR,
required=False,
validator=valid_publish_topic,
error="invalid_publish_topic",
conditions=({CONF_SCHEMA: "basic"},),
section="light_white_settings",
),
CONF_WHITE_SCALE: PlatformField(
selector=SCALE_SELECTOR,
required=False,
validator=cv.positive_int,
default=255,
conditions=(
{CONF_SCHEMA: "basic"},
{CONF_SCHEMA: "json"},
),
section="light_white_settings",
),
CONF_EFFECT: PlatformField(
selector=BOOLEAN_SELECTOR,
required=False,
validator=bool,
conditions=({CONF_SCHEMA: "json"},),
section="light_effect_settings",
),
CONF_EFFECT_COMMAND_TOPIC: PlatformField(
selector=TEXT_SELECTOR,
required=False,
validator=valid_publish_topic,
error="invalid_publish_topic",
conditions=({CONF_SCHEMA: "basic"},),
section="light_effect_settings",
),
CONF_EFFECT_COMMAND_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=cv.template,
error="invalid_template",
conditions=({CONF_SCHEMA: "basic"},),
section="light_effect_settings",
),
CONF_EFFECT_STATE_TOPIC: PlatformField(
selector=TEXT_SELECTOR,
required=False,
validator=valid_subscribe_topic,
error="invalid_subscribe_topic",
conditions=({CONF_SCHEMA: "basic"},),
section="light_effect_settings",
),
CONF_EFFECT_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=cv.template,
error="invalid_template",
conditions=({CONF_SCHEMA: "template"},),
section="light_effect_settings",
),
CONF_EFFECT_VALUE_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=cv.template,
error="invalid_template",
conditions=({CONF_SCHEMA: "basic"},),
section="light_effect_settings",
),
CONF_EFFECT_LIST: PlatformField(
selector=OPTIONS_SELECTOR,
required=False,
validator=cv.ensure_list,
section="light_effect_settings",
),
CONF_FLASH: PlatformField(
selector=BOOLEAN_SELECTOR,
required=False,
default=False,
validator=cv.boolean,
conditions=({CONF_SCHEMA: "json"},),
section="advanced_settings",
),
CONF_FLASH_TIME_SHORT: PlatformField(
selector=FLASH_TIME_SELECTOR,
required=False,
validator=cv.positive_int,
default=2,
conditions=({CONF_SCHEMA: "json"},),
section="advanced_settings",
),
CONF_FLASH_TIME_LONG: PlatformField(
selector=FLASH_TIME_SELECTOR,
required=False,
validator=cv.positive_int,
default=10,
conditions=({CONF_SCHEMA: "json"},),
section="advanced_settings",
),
CONF_TRANSITION: PlatformField(
selector=BOOLEAN_SELECTOR,
required=False,
default=False,
validator=cv.boolean,
conditions=({CONF_SCHEMA: "json"},),
section="advanced_settings",
),
CONF_MAX_KELVIN: PlatformField(
selector=KELVIN_SELECTOR,
required=False,
validator=cv.positive_int,
default=DEFAULT_MAX_KELVIN,
section="advanced_settings",
),
CONF_MIN_KELVIN: PlatformField(
selector=KELVIN_SELECTOR,
required=False,
validator=cv.positive_int,
default=DEFAULT_MIN_KELVIN,
section="advanced_settings",
),
},
}
ENTITY_CONFIG_VALIDATOR: dict[
str,
Callable[[dict[str, Any]], dict[str, str]] | None,
] = {
Platform.LIGHT.value: validate_light_platform_config,
Platform.NOTIFY.value: None,
Platform.SENSOR.value: validate_sensor_platform_config,
Platform.SWITCH.value: None,
@ -576,7 +1216,7 @@ def validate_field(
return
try:
validator(user_input[field])
except (ValueError, vol.Invalid):
except (ValueError, vol.Error, vol.Invalid):
errors[field] = error
@ -634,7 +1274,7 @@ def validate_user_input(
validator = data_schema_fields[field].validator
try:
validator(value)
except (ValueError, vol.Invalid):
except (ValueError, vol.Error, vol.Invalid):
errors[field] = data_schema_fields[field].error or "invalid_input"
if config_validator is not None:
@ -672,7 +1312,9 @@ def data_schema_from_fields(
component_data_with_user_input |= user_input
sections: dict[str | None, None] = {
field_details.section: None for field_details in data_schema_fields.values()
field_details.section: None
for field_details in data_schema_fields.values()
if not field_details.is_schema_default
}
data_schema: dict[Any, Any] = {}
all_data_element_options: set[Any] = set()
@ -687,7 +1329,8 @@ def data_schema_from_fields(
if field_details.custom_filtering
else field_details.selector
for field_name, field_details in data_schema_fields.items()
if field_details.section == schema_section
if not field_details.is_schema_default
and field_details.section == schema_section
and (not field_details.exclude_from_reconfig or not reconfig)
and _check_conditions(field_details, component_data_with_user_input)
}
@ -699,6 +1342,8 @@ def data_schema_from_fields(
if field_details.section == schema_section
and field_details.exclude_from_reconfig
}
if not data_element_options:
continue
if schema_section is None:
data_schema.update(data_schema_element)
continue
@ -727,6 +1372,18 @@ def data_schema_from_fields(
return vol.Schema(data_schema)
@callback
def subentry_schema_default_data_from_fields(
data_schema_fields: dict[str, PlatformField],
) -> dict[str, Any]:
"""Generate custom data schema from platform fields or device data."""
return {
key: field.default
for key, field in data_schema_fields.items()
if field.is_schema_default
}
class FlowHandler(ConfigFlow, domain=DOMAIN):
"""Handle a config flow."""
@ -1543,6 +2200,16 @@ class MQTTSubentryFlowHandler(ConfigSubentryFlow):
last_step=False,
)
@callback
def _async_update_component_data_defaults(self) -> None:
"""Update component data defaults."""
for component_data in self._subentry_data["components"].values():
platform = component_data[CONF_PLATFORM]
subentry_default_data = subentry_schema_default_data_from_fields(
PLATFORM_ENTITY_FIELDS[platform]
)
component_data.update(subentry_default_data)
@callback
def _async_create_subentry(
self, user_input: dict[str, Any] | None = None
@ -1559,6 +2226,7 @@ class MQTTSubentryFlowHandler(ConfigSubentryFlow):
else:
full_entity_name = device_name
self._async_update_component_data_defaults()
return self.async_create_entry(
data=self._subentry_data,
title=self._subentry_data[CONF_DEVICE][CONF_NAME],
@ -1623,6 +2291,7 @@ class MQTTSubentryFlowHandler(ConfigSubentryFlow):
if len(self._subentry_data["components"]) > 1:
menu_options.append("delete_entity")
menu_options.extend(["device", "availability"])
self._async_update_component_data_defaults()
if self._subentry_data != self._get_reconfigure_subentry().data:
menu_options.append("save_changes")
return self.async_show_menu(

View File

@ -196,6 +196,8 @@ DEFAULT_POSITION_OPEN = 100
DEFAULT_RETAIN = False
DEFAULT_WHITE_SCALE = 255
VALUES_ON_COMMAND_TYPE = ["first", "last", "brightness"]
PROTOCOL_31 = "3.1"
PROTOCOL_311 = "3.1.1"
PROTOCOL_5 = "5"

View File

@ -104,6 +104,7 @@ from ..const import (
DEFAULT_PAYLOAD_ON,
DEFAULT_WHITE_SCALE,
PAYLOAD_NONE,
VALUES_ON_COMMAND_TYPE,
)
from ..entity import MqttEntity
from ..models import (
@ -143,8 +144,6 @@ MQTT_LIGHT_ATTRIBUTES_BLOCKED = frozenset(
}
)
VALUES_ON_COMMAND_TYPE = ["first", "last", "brightness"]
COMMAND_TEMPLATE_KEYS = [
CONF_BRIGHTNESS_COMMAND_TEMPLATE,
CONF_COLOR_TEMP_COMMAND_TEMPLATE,

View File

@ -214,15 +214,19 @@
"description": "Please configure specific details for {platform} entity \"{entity}\":",
"data": {
"device_class": "Device class",
"options": "Add option",
"schema": "Schema",
"state_class": "State class",
"unit_of_measurement": "Unit of measurement",
"options": "Add option"
"suggested_display_precision": "Suggested display precision",
"unit_of_measurement": "Unit of measurement"
},
"data_description": {
"device_class": "The Device class of the {platform} entity. [Learn more.]({url}#device_class)",
"options": "Options for allowed sensor state values. The sensors Device class must be set to Enumeration. The 'Options' setting cannot be used together with State class or Unit of measurement.",
"schema": "The schema to use. [Learn more.]({url}#comparison-of-light-mqtt-schemas)",
"state_class": "The [State class](https://developers.home-assistant.io/docs/core/entity/sensor/#available-state-classes) of the sensor. [Learn more.]({url}#state_class)",
"unit_of_measurement": "Defines the unit of measurement of the sensor, if any.",
"options": "Options for allowed sensor state values. The sensors Device class must be set to Enumeration. The 'Options' setting cannot be used together with State class or Unit of measurement."
"suggested_display_precision": "The number of decimals which should be used in the {platform} entity state after rounding. [Learn more.]({url}#suggested_display_precision)",
"unit_of_measurement": "Defines the unit of measurement of the sensor, if any."
},
"sections": {
"advanced_settings": {
@ -240,33 +244,222 @@
"title": "Configure MQTT device \"{mqtt_device}\"",
"description": "Please configure MQTT specific details for {platform} entity \"{entity}\":",
"data": {
"command_topic": "Command topic",
"on_command_type": "ON command type",
"blue_template": "Blue template",
"brightness_template": "Brightness template",
"command_template": "Command template",
"state_topic": "State topic",
"value_template": "Value template",
"last_reset_value_template": "Last reset value template",
"command_topic": "Command topic",
"command_off_template": "Command \"off\" template",
"command_on_template": "Command \"on\" template",
"color_temp_template": "Color temperature template",
"force_update": "Force update",
"green_template": "Green template",
"last_reset_value_template": "Last reset value template",
"optimistic": "Optimistic",
"retain": "Retain"
"payload_off": "Payload off",
"payload_on": "Payload on",
"qos": "QoS",
"red_template": "Red template",
"retain": "Retain",
"state_template": "State template",
"state_topic": "State topic",
"state_value_template": "State value template",
"supported_color_modes": "Supported color modes",
"value_template": "Value template"
},
"data_description": {
"command_topic": "The publishing topic that will be used to control the {platform} entity. [Learn more.]({url}#command_topic)",
"blue_template": "[Template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract blue color from the state payload value. Expected result of the template is an integer from 0-255 range.",
"brightness_template": "[Template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract brightness from the state payload value. Expected result of the template is an integer from 0-255 range.",
"command_off_template": "The [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) for \"off\" state changes. Available variables are: `state` and `transition`.",
"command_on_template": "The [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) for \"on\" state changes. Available variables: `state`, `brightness`, `color_temp`, `red`, `green`, `blue`, `hue`, `sat`, `flash`, `transition` and `effect`. Values `red`, `green`, `blue` and `brightness` are provided as integers from range 0-255. Value of `hue` is provided as float from range 0-360. Value of `sat` is provided as float from range 0-100. Value of `color_temp` is provided as integer representing Kelvin units.",
"command_template": "A [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to render the payload to be published at the command topic.",
"state_topic": "The MQTT topic subscribed to receive {platform} state values. [Learn more.]({url}#state_topic)",
"value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the {platform} entity value.",
"command_topic": "The publishing topic that will be used to control the {platform} entity. [Learn more.]({url}#command_topic)",
"color_temp_template": "[Template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract color temperature in Kelvin from the state payload value. Expected result of the template is an integer.",
"green_template": "[Template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract green color from the state payload value. Expected result of the template is an integer from 0-255 range.",
"last_reset_value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the last reset. When Last reset template is set, the State class option must be Total. [Learn more.]({url}#last_reset_value_template)",
"force_update": "Sends update events even if the value hasnt changed. Useful if you want to have meaningful value graphs in history. [Learn more.]({url}#force_update)",
"on_command_type": "Defines when the `payload on` is sent. Using `last` (the default) will send any style (brightness, color, etc) topics first and then a `payload on` to the command_topic. Using `first` will send the `payload on` and then any style topics. Using `brightness` will only send brightness commands instead of the `Payload on` to turn the light on.",
"optimistic": "Flag that defines if the {platform} entity works in optimistic mode. [Learn more.]({url}#optimistic)",
"retain": "Select if values published by the {platform} entity should be retained at the MQTT broker."
"payload_off": "The payload that represents the off state.",
"payload_on": "The payload that represents the on state.",
"qos": "The QoS value a {platform} entity should use.",
"red_template": "[Template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract red color from the state payload value. Expected result of the template is an integer from 0-255 range.",
"retain": "Select if values published by the {platform} entity should be retained at the MQTT broker.",
"state_template": "[Template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract state from the state payload value.",
"state_topic": "The MQTT topic subscribed to receive {platform} state values. [Learn more.]({url}#state_topic)",
"supported_color_modes": "A list of color modes supported by the list. Possible color modes are On/Off, Brightness, Color temperature, HS, XY, RGB, RGBW, RGBWW, WHITE. Note that if onoff or brightness are used, that must be the only value in the list. [Learn more.]({url}#supported_color_modes)",
"value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the {platform} entity value. [Learn more.]({url}#value_template)"
},
"sections": {
"advanced_settings": {
"name": "Advanced settings",
"data": {
"expire_after": "Expire after"
"expire_after": "Expire after",
"flash": "Flash support",
"flash_time_long": "Flash time long",
"flash_time_short": "Flash time short",
"max_kelvin": "Max Kelvin",
"min_kelvin": "Min Kelvin",
"transition": "Transition support"
},
"data_description": {
"expire_after": "If set, it defines the number of seconds after the sensors state expires, if its not updated. After expiry, the sensors state becomes unavailable. If not set, the sensor's state never expires. [Learn more.]({url}#expire_after)"
"expire_after": "If set, it defines the number of seconds after the sensors state expires, if its not updated. After expiry, the sensors state becomes unavailable. If not set, the sensor's state never expires. [Learn more.]({url}#expire_after)",
"flash": "Enable the flash feature for this light",
"flash_time_long": "The duration, in seconds, of a \"long\" flash.",
"flash_time_short": "The duration, in seconds, of a \"short\" flash.",
"max_kelvin": "The maximum color temperature in Kelvin.",
"min_kelvin": "The minimum color temperature in Kelvin.",
"transition": "Enable the transition feature for this light"
}
},
"light_brightness_settings": {
"name": "Brightness settings",
"data": {
"brightness": "Separate brightness",
"brightness_command_template": "Brightness command template",
"brightness_command_topic": "Brightness command topic",
"brightness_scale": "Brightness scale",
"brightness_state_topic": "Brightness state topic",
"brightness_value_template": "Brightness value template"
},
"data_description": {
"brightness": "Flag that defines if light supports brightness when the RGB, RGBW, or RGBWW color mode is supported.",
"brightness_command_template": "A [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to compose the payload to be published at the brightness command topic.",
"brightness_command_topic": "The publishing topic that will be used to control the brigthness. [Learn more.]({url}#brightness_command_topic)",
"brightness_scale": "Defines the maximum brightness value (i.e., 100%) of the maximum brightness.",
"brightness_state_topic": "The MQTT topic subscribed to receive brightness state values. [Learn more.]({url}#brightness_state_topic)",
"brightness_value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the brightness value."
}
},
"light_color_mode_settings": {
"name": "Color mode settings",
"data": {
"color_mode_state_topic": "Color mode state topic",
"color_mode_value_template": "Color mode value template"
},
"data_description": {
"color_mode_state_topic": "The MQTT topic subscribed to receive color mode updates. If this is not configured, the color mode will be automatically set according to the last received valid color or color temperature.",
"color_mode_value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the color mode value."
}
},
"light_color_temp_settings": {
"name": "Color temperature settings",
"data": {
"color_temp_command_template": "Color temperature command template",
"color_temp_command_topic": "Color temperature command topic",
"color_temp_state_topic": "Color temperature state topic",
"color_temp_value_template": "Color temperature value template"
},
"data_description": {
"color_temp_command_template": "A [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to compose the payload to be published at the color temperature command topic.",
"color_temp_command_topic": "The publishing topic that will be used to control the color temperature. [Learn more.]({url}#color_temp_command_topic)",
"color_temp_state_topic": "The MQTT topic subscribed to receive color temperature state updates. [Learn more.]({url}#color_temp_state_topic)",
"color_temp_value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the color temperature value."
}
},
"light_effect_settings": {
"name": "Effect settings",
"data": {
"effect": "Effect",
"effect_command_template": "Effect command template",
"effect_command_topic": "Effect command topic",
"effect_list": "Effect list",
"effect_state_topic": "Effect state topic",
"effect_template": "Effect template",
"effect_value_template": "Effect value template"
},
"data_description": {
"effect": "Flag that defines if the light supports effects.",
"effect_command_template": "A [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to compose the payload to be published at the effect command topic.",
"effect_command_topic": "The publishing topic that will be used to control the light's effect state. [Learn more.]({url}#effect_command_topic)",
"effect_list": "The list of effects the light supports.",
"effect_state_topic": "The MQTT topic subscribed to receive effect state updates. [Learn more.]({url}#effect_state_topic)"
}
},
"light_hs_settings": {
"name": "HS color mode settings",
"data": {
"hs_command_template": "HS command template",
"hs_command_topic": "HS command topic",
"hs_state_topic": "HS state topic",
"hs_value_template": "HS value template"
},
"data_description": {
"hs_command_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to compose message which will be sent to hs_command_topic. Available variables: `hue` and `sat`.",
"hs_command_topic": "The MQTT topic to publish commands to change the lights color state in HS format (Hue Saturation). Range for Hue: 0° .. 360°, Range of Saturation: 0..100. Note: Brightness is sent separately in the brightness command topic. [Learn more.]({url}#hs_command_topic)",
"hs_state_topic": "The MQTT topic subscribed to receive color state updates in HS format. The expected payload is the hue and saturation values separated by commas, for example, `359.5,100.0`. Note: Brightness is received separately in the brightness state topic. [Learn more.]({url}#hs_state_topic)",
"hs_value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the HS value."
}
},
"light_rgb_settings": {
"name": "RGB color mode settings",
"data": {
"rgb_command_template": "RGB command template",
"rgb_command_topic": "RGB command topic",
"rgb_state_topic": "RGB state topic",
"rgb_value_template": "RGB value template"
},
"data_description": {
"rgb_command_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to compose message which will be sent to RGB command topic. Available variables: `red`, `green` and `blue`.",
"rgb_command_topic": "The MQTT topic to publish commands to change the lights RGB state. [Learn more.]({url}#rgb_command_topic)",
"rgb_state_topic": "The MQTT topic subscribed to receive RGB state updates. The expected payload is the RGB values separated by commas, for example, `255,0,127`. [Learn more.]({url}rgb_state_topic)",
"rgb_value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the RGB value."
}
},
"light_rgbw_settings": {
"name": "RGBW color mode settings",
"data": {
"rgbw_command_template": "RGBW command template",
"rgbw_command_topic": "RGBW command topic",
"rgbw_state_topic": "RGBW state topic",
"rgbw_value_template": "RGBW value template"
},
"data_description": {
"rgbw_command_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to compose message which will be sent to RGBW command topic. Available variables: `red`, `green`, `blue` and `white`.",
"rgbw_command_topic": "The MQTT topic to publish commands to change the lights RGBW state. [Learn more.]({url}#rgbw_command_topic)",
"rgbw_state_topic": "The MQTT topic subscribed to receive RGBW state updates. The expected payload is the RGBW values separated by commas, for example, `255,0,127,64`. [Learn more.]({url}#rgbw_state_topic)",
"rgbw_value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the RGBW value."
}
},
"light_rgbww_settings": {
"name": "RGBWW color mode settings",
"data": {
"rgbww_command_template": "RGBWW command template",
"rgbww_command_topic": "RGBWW command topic",
"rgbww_state_topic": "RGBWW state topic",
"rgbww_value_template": "RGBWW value template"
},
"data_description": {
"rgbww_command_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to compose message which will be sent to RGBWW command topic. Available variables: `red`, `green`, `blue`, `cold_white` and `warm_white`.",
"rgbww_command_topic": "The MQTT topic to publish commands to change the lights RGBWW state. [Learn more.]({url}#rgbww_command_topic)",
"rgbww_state_topic": "The MQTT topic subscribed to receive RGBWW state updates. The expected payload is the RGBWW values separated by commas, for example, `255,0,127,64,32`. [Learn more.]({url}#rgbww_state_topic)",
"rgbww_value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the RGBWW value."
}
},
"light_white_settings": {
"name": "White color mode settings",
"data": {
"white_command_topic": "White command topic",
"white_scale": "White scale"
},
"data_description": {
"white_command_topic": "The MQTT topic to publish commands to change the light to white mode with a given brightness. [Learn more.]({url}#white_command_topic)",
"white_scale": "Defines the maximum white level (i.e., 100%) of the maximum."
}
},
"light_xy_settings": {
"name": "XY color mode settings",
"data": {
"xy_command_template": "XY command template",
"xy_command_topic": "XY command topic",
"xy_state_topic": "XY state topic",
"xy_value_template": "XY value template"
},
"data_description": {
"xy_command_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to compose message which will be sent to XY command topic. Available variables: `x` and `y`.",
"xy_command_topic": "The MQTT topic to publish commands to change the lights XY state. [Learn more.]({url}#xy_command_topic)",
"xy_state_topic": "The MQTT topic subscribed to receive XY state updates. The expected payload is the X and Y color values separated by commas, for example, `0.675,0.322`. [Learn more.]({url}#xy_state_topic)",
"xy_value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the XY value."
}
}
}
@ -282,8 +475,11 @@
"invalid_input": "Invalid value",
"invalid_subscribe_topic": "Invalid subscribe topic",
"invalid_template": "Invalid template",
"invalid_supported_color_modes": "Invalid supported color modes selection",
"invalid_uom": "The unit of measurement \"{unit_of_measurement}\" is not supported by the selected device class, please either remove the device class, select a device class which supports \"{unit_of_measurement}\", or pick a supported unit of measurement from the list",
"invalid_url": "Invalid URL",
"last_reset_not_with_state_class_total": "The last reset value template option should be used with state class 'Total' only",
"max_below_min_kelvin": "Max Kelvin value should be greater than min Kelvin value",
"options_not_allowed_with_state_class_or_uom": "The 'Options' setting is not allowed when state class or unit of measurement are used",
"options_device_class_enum": "The 'Options' setting must be used with the Enumeration device class. If you continue, the existing options will be reset",
"options_with_enum_device_class": "Configure options for the enumeration sensor",
@ -470,8 +666,23 @@
"switch": "[%key:component::switch::title%]"
}
},
"light_schema": {
"options": {
"basic": "Default schema",
"json": "JSON",
"template": "Template"
}
},
"on_command_type": {
"options": {
"brightness": "Brightness",
"first": "First",
"last": "Last"
}
},
"platform": {
"options": {
"light": "[%key:component::light::title%]",
"notify": "[%key:component::notify::title%]",
"sensor": "[%key:component::sensor::title%]",
"switch": "[%key:component::switch::title%]"
@ -490,6 +701,19 @@
"total": "[%key:component::sensor::entity_component::_::state_attributes::state_class::state::total%]",
"total_increasing": "[%key:component::sensor::entity_component::_::state_attributes::state_class::state::total_increasing%]"
}
},
"supported_color_modes": {
"options": {
"onoff": "[%key:component::light::entity_component::_::state_attributes::color_mode::state::onoff%]",
"brightness": "[%key:component::light::entity_component::_::state_attributes::color_mode::state::brightness%]",
"color_temp": "[%key:component::light::entity_component::_::state_attributes::color_mode::state::color_temp%]",
"hs": "[%key:component::light::entity_component::_::state_attributes::color_mode::state::hs%]",
"xy": "[%key:component::light::entity_component::_::state_attributes::color_mode::state::xy%]",
"rgb": "[%key:component::light::entity_component::_::state_attributes::color_mode::state::rgb%]",
"rgbw": "[%key:component::light::entity_component::_::state_attributes::color_mode::state::rgbw%]",
"rgbww": "[%key:component::light::entity_component::_::state_attributes::color_mode::state::rgbww%]",
"white": "[%key:component::light::entity_component::_::state_attributes::color_mode::state::white%]"
}
}
},
"services": {

View File

@ -139,15 +139,19 @@ MOCK_SUBENTRY_SWITCH_COMPONENT = {
},
}
# Bogus light component just for code coverage
# Note that light cannot be setup through the UI yet
# The test is for code coverage
MOCK_SUBENTRY_LIGHT_COMPONENT = {
MOCK_SUBENTRY_LIGHT_BASIC_KELVIN_COMPONENT = {
"8131babc5e8d4f44b82e0761d39091a2": {
"platform": "light",
"name": "Test light",
"command_topic": "test-topic4",
"name": "Basic light",
"on_command_type": "last",
"optimistic": True,
"payload_off": "OFF",
"payload_on": "ON",
"command_topic": "test-topic",
"schema": "basic",
"state_topic": "test-topic",
"color_temp_kelvin": True,
"state_value_template": "{{ value_json.value }}",
"entity_picture": "https://example.com/8131babc5e8d4f44b82e0761d39091a2",
},
}
@ -168,108 +172,57 @@ MOCK_SUBENTRY_AVAILABILITY_DATA = {
}
}
MOCK_SUBENTRY_DEVICE_DATA = {
"name": "Milk notifier",
"sw_version": "1.0",
"hw_version": "2.1 rev a",
"model": "Model XL",
"model_id": "mn002",
"configuration_url": "https://example.com",
}
MOCK_NOTIFY_SUBENTRY_DATA_MULTI = {
"device": {
"name": "Milk notifier",
"sw_version": "1.0",
"hw_version": "2.1 rev a",
"model": "Model XL",
"model_id": "mn002",
"configuration_url": "https://example.com",
},
"device": MOCK_SUBENTRY_DEVICE_DATA | {"mqtt_settings": {"qos": 0}},
"components": MOCK_SUBENTRY_NOTIFY_COMPONENT1 | MOCK_SUBENTRY_NOTIFY_COMPONENT2,
} | MOCK_SUBENTRY_AVAILABILITY_DATA
MOCK_NOTIFY_SUBENTRY_DATA_SINGLE = {
"device": {
"name": "Milk notifier",
"sw_version": "1.0",
"hw_version": "2.1 rev a",
"model": "Model XL",
"model_id": "mn002",
"configuration_url": "https://example.com",
"mqtt_settings": {"qos": 1},
},
"device": MOCK_SUBENTRY_DEVICE_DATA | {"mqtt_settings": {"qos": 1}},
"components": MOCK_SUBENTRY_NOTIFY_COMPONENT1,
}
MOCK_NOTIFY_SUBENTRY_DATA_NO_NAME = {
"device": {
"name": "Milk notifier",
"sw_version": "1.0",
"hw_version": "2.1 rev a",
"model": "Model XL",
"model_id": "mn002",
"configuration_url": "https://example.com",
},
"device": MOCK_SUBENTRY_DEVICE_DATA | {"mqtt_settings": {"qos": 0}},
"components": MOCK_SUBENTRY_NOTIFY_COMPONENT_NO_NAME,
}
MOCK_LIGHT_BASIC_KELVIN_SUBENTRY_DATA_SINGLE = {
"device": MOCK_SUBENTRY_DEVICE_DATA | {"mqtt_settings": {"qos": 0}},
"components": MOCK_SUBENTRY_LIGHT_BASIC_KELVIN_COMPONENT,
}
MOCK_SENSOR_SUBENTRY_DATA_SINGLE = {
"device": {
"name": "Test sensor",
"sw_version": "1.0",
"hw_version": "2.1 rev a",
"model": "Model XL",
"model_id": "mn002",
"configuration_url": "https://example.com",
},
"device": MOCK_SUBENTRY_DEVICE_DATA | {"mqtt_settings": {"qos": 0}},
"components": MOCK_SUBENTRY_SENSOR_COMPONENT,
}
MOCK_SENSOR_SUBENTRY_DATA_SINGLE_STATE_CLASS = {
"device": {
"name": "Test sensor",
"sw_version": "1.0",
"hw_version": "2.1 rev a",
"model": "Model XL",
"model_id": "mn002",
"configuration_url": "https://example.com",
},
"device": MOCK_SUBENTRY_DEVICE_DATA | {"mqtt_settings": {"qos": 0}},
"components": MOCK_SUBENTRY_SENSOR_COMPONENT_STATE_CLASS,
}
MOCK_SENSOR_SUBENTRY_DATA_SINGLE_LAST_RESET_TEMPLATE = {
"device": {
"name": "Test sensor",
"sw_version": "1.0",
"hw_version": "2.1 rev a",
"model": "Model XL",
"model_id": "mn002",
"configuration_url": "https://example.com",
},
"device": MOCK_SUBENTRY_DEVICE_DATA | {"mqtt_settings": {"qos": 0}},
"components": MOCK_SUBENTRY_SENSOR_COMPONENT_LAST_RESET,
}
MOCK_SWITCH_SUBENTRY_DATA_SINGLE_STATE_CLASS = {
"device": {
"name": "Test switch",
"sw_version": "1.0",
"hw_version": "2.1 rev a",
"model": "Model XL",
"model_id": "mn002",
"configuration_url": "https://example.com",
},
"device": MOCK_SUBENTRY_DEVICE_DATA | {"mqtt_settings": {"qos": 0}},
"components": MOCK_SUBENTRY_SWITCH_COMPONENT,
}
MOCK_SUBENTRY_DATA_BAD_COMPONENT_SCHEMA = {
"device": {
"name": "Milk notifier",
"sw_version": "1.0",
"hw_version": "2.1 rev a",
"model": "Model XL",
"model_id": "mn002",
"configuration_url": "https://example.com",
},
"device": MOCK_SUBENTRY_DEVICE_DATA | {"mqtt_settings": {"qos": 0}},
"components": MOCK_SUBENTRY_NOTIFY_BAD_SCHEMA,
}
MOCK_SUBENTRY_DATA_SET_MIX = {
"device": {
"name": "Milk notifier",
"sw_version": "1.0",
"hw_version": "2.1 rev a",
"model": "Model XL",
"model_id": "mn002",
"configuration_url": "https://example.com",
},
"device": MOCK_SUBENTRY_DEVICE_DATA | {"mqtt_settings": {"qos": 0}},
"components": MOCK_SUBENTRY_NOTIFY_COMPONENT1
| MOCK_SUBENTRY_NOTIFY_COMPONENT2
| MOCK_SUBENTRY_LIGHT_COMPONENT
| MOCK_SUBENTRY_LIGHT_BASIC_KELVIN_COMPONENT
| MOCK_SUBENTRY_SWITCH_COMPONENT,
} | MOCK_SUBENTRY_AVAILABILITY_DATA
_SENTINEL = object()

View File

@ -33,6 +33,7 @@ from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.service_info.hassio import HassioServiceInfo
from .common import (
MOCK_LIGHT_BASIC_KELVIN_SUBENTRY_DATA_SINGLE,
MOCK_NOTIFY_SUBENTRY_DATA_MULTI,
MOCK_NOTIFY_SUBENTRY_DATA_NO_NAME,
MOCK_NOTIFY_SUBENTRY_DATA_SINGLE,
@ -2696,7 +2697,7 @@ async def test_migrate_of_incompatible_config_entry(
),
(
MOCK_SENSOR_SUBENTRY_DATA_SINGLE,
{"name": "Test sensor", "mqtt_settings": {"qos": 0}},
{"name": "Milk notifier", "mqtt_settings": {"qos": 0}},
{"name": "Energy"},
{"device_class": "enum", "options": ["low", "medium", "high"]},
(
@ -2748,11 +2749,11 @@ async def test_migrate_of_incompatible_config_entry(
{"state_topic": "invalid_subscribe_topic"},
),
),
"Test sensor Energy",
"Milk notifier Energy",
),
(
MOCK_SENSOR_SUBENTRY_DATA_SINGLE_STATE_CLASS,
{"name": "Test sensor", "mqtt_settings": {"qos": 0}},
{"name": "Milk notifier", "mqtt_settings": {"qos": 0}},
{"name": "Energy"},
{
"state_class": "measurement",
@ -2762,11 +2763,11 @@ async def test_migrate_of_incompatible_config_entry(
"state_topic": "test-topic",
},
(),
"Test sensor Energy",
"Milk notifier Energy",
),
(
MOCK_SWITCH_SUBENTRY_DATA_SINGLE_STATE_CLASS,
{"name": "Test switch", "mqtt_settings": {"qos": 0}},
{"name": "Milk notifier", "mqtt_settings": {"qos": 0}},
{"name": "Outlet"},
{"device_class": "outlet"},
(),
@ -2790,7 +2791,44 @@ async def test_migrate_of_incompatible_config_entry(
{"state_topic": "invalid_subscribe_topic"},
),
),
"Test switch Outlet",
"Milk notifier Outlet",
),
(
MOCK_LIGHT_BASIC_KELVIN_SUBENTRY_DATA_SINGLE,
{"name": "Milk notifier", "mqtt_settings": {"qos": 1}},
{"name": "Basic light"},
{},
{},
{
"command_topic": "test-topic",
"state_topic": "test-topic",
"state_value_template": "{{ value_json.value }}",
"optimistic": True,
},
(
(
{"command_topic": "test-topic#invalid"},
{"command_topic": "invalid_publish_topic"},
),
(
{
"command_topic": "test-topic",
"state_topic": "test-topic#invalid",
},
{"state_topic": "invalid_subscribe_topic"},
),
(
{
"command_topic": "test-topic",
"advanced_settings": {"max_kelvin": 2000, "min_kelvin": 2000},
},
{
"max_kelvin": "max_below_min_kelvin",
"min_kelvin": "max_below_min_kelvin",
},
),
),
"Milk notifier Basic light",
),
],
ids=[
@ -2799,6 +2837,7 @@ async def test_migrate_of_incompatible_config_entry(
"sensor_options",
"sensor_total",
"switch",
"light_basic_kelvin",
],
)
async def test_subentry_configflow(
@ -3199,6 +3238,7 @@ async def test_subentry_reconfigure_edit_entity_multi_entitites(
"user_input_platform_config_validation",
"user_input_platform_config",
"user_input_mqtt",
"component_data",
"removed_options",
),
[
@ -3217,6 +3257,11 @@ async def test_subentry_reconfigure_edit_entity_multi_entitites(
"command_template": "{{ value }}",
"retain": True,
},
{
"command_topic": "test-topic1-updated",
"command_template": "{{ value }}",
"retain": True,
},
{"entity_picture"},
),
(
@ -3253,10 +3298,38 @@ async def test_subentry_reconfigure_edit_entity_multi_entitites(
"state_topic": "test-topic1-updated",
"value_template": "{{ value_json.value }}",
},
{
"state_topic": "test-topic1-updated",
"value_template": "{{ value_json.value }}",
},
{"options", "expire_after", "entity_picture"},
),
(
(
ConfigSubentryData(
data=MOCK_LIGHT_BASIC_KELVIN_SUBENTRY_DATA_SINGLE,
subentry_type="device",
title="Mock subentry",
),
),
None,
None,
{
"command_topic": "test-topic1-updated",
"state_topic": "test-topic1-updated",
"light_brightness_settings": {
"brightness_command_template": "{{ value_json.value }}"
},
},
{
"command_topic": "test-topic1-updated",
"state_topic": "test-topic1-updated",
"brightness_command_template": "{{ value_json.value }}",
},
{"optimistic", "state_value_template", "entity_picture"},
),
],
ids=["notify", "sensor"],
ids=["notify", "sensor", "light_basic"],
)
async def test_subentry_reconfigure_edit_entity_single_entity(
hass: HomeAssistant,
@ -3269,6 +3342,7 @@ async def test_subentry_reconfigure_edit_entity_single_entity(
| None,
user_input_platform_config: dict[str, Any] | None,
user_input_mqtt: dict[str, Any],
component_data: dict[str, Any],
removed_options: tuple[str, ...],
) -> None:
"""Test the subentry ConfigFlow reconfigure with single entity."""
@ -3373,7 +3447,7 @@ async def test_subentry_reconfigure_edit_entity_single_entity(
assert "entity_picture" not in new_components[component_id]
# Check the second component was updated
for key, value in user_input_mqtt.items():
for key, value in component_data.items():
assert new_components[component_id][key] == value
assert set(component) - set(new_components[component_id]) == removed_options