Add MQTT cover as entity platform on MQTT subentries (#144381)

* Add MQTT cover as entity platform on MQTT subentries

* Revert change vol.Coerce wrappers on cover schema

* Fix template validator and cleanup redundant validators

* Cleanup more redundant validators
This commit is contained in:
Jan Bouwhuis 2025-05-23 14:25:00 +02:00 committed by GitHub
parent 17297ab929
commit e8ea5c9d62
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 543 additions and 29 deletions

View File

@ -27,6 +27,7 @@ import voluptuous as vol
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
from homeassistant.components.button import ButtonDeviceClass
from homeassistant.components.cover import CoverDeviceClass
from homeassistant.components.file_upload import process_uploaded_file
from homeassistant.components.hassio import AddonError, AddonManager, AddonState
from homeassistant.components.light import (
@ -78,6 +79,10 @@ from homeassistant.const import (
CONF_UNIT_OF_MEASUREMENT,
CONF_USERNAME,
CONF_VALUE_TEMPLATE,
STATE_CLOSED,
STATE_CLOSING,
STATE_OPEN,
STATE_OPENING,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import AbortFlow, SectionConfig, section
@ -150,6 +155,8 @@ from .const import (
CONF_FLASH,
CONF_FLASH_TIME_LONG,
CONF_FLASH_TIME_SHORT,
CONF_GET_POSITION_TEMPLATE,
CONF_GET_POSITION_TOPIC,
CONF_GREEN_TEMPLATE,
CONF_HS_COMMAND_TEMPLATE,
CONF_HS_COMMAND_TOPIC,
@ -163,8 +170,14 @@ from .const import (
CONF_ON_COMMAND_TYPE,
CONF_OPTIONS,
CONF_PAYLOAD_AVAILABLE,
CONF_PAYLOAD_CLOSE,
CONF_PAYLOAD_NOT_AVAILABLE,
CONF_PAYLOAD_OPEN,
CONF_PAYLOAD_PRESS,
CONF_PAYLOAD_STOP,
CONF_PAYLOAD_STOP_TILT,
CONF_POSITION_CLOSED,
CONF_POSITION_OPEN,
CONF_QOS,
CONF_RED_TEMPLATE,
CONF_RETAIN,
@ -181,10 +194,26 @@ from .const import (
CONF_RGBWW_STATE_TOPIC,
CONF_RGBWW_VALUE_TEMPLATE,
CONF_SCHEMA,
CONF_SET_POSITION_TEMPLATE,
CONF_SET_POSITION_TOPIC,
CONF_STATE_CLOSED,
CONF_STATE_CLOSING,
CONF_STATE_OPEN,
CONF_STATE_OPENING,
CONF_STATE_STOPPED,
CONF_STATE_TOPIC,
CONF_STATE_VALUE_TEMPLATE,
CONF_SUGGESTED_DISPLAY_PRECISION,
CONF_SUPPORTED_COLOR_MODES,
CONF_TILT_CLOSED_POSITION,
CONF_TILT_COMMAND_TEMPLATE,
CONF_TILT_COMMAND_TOPIC,
CONF_TILT_MAX,
CONF_TILT_MIN,
CONF_TILT_OPEN_POSITION,
CONF_TILT_STATE_OPTIMISTIC,
CONF_TILT_STATUS_TEMPLATE,
CONF_TILT_STATUS_TOPIC,
CONF_TLS_INSECURE,
CONF_TRANSITION,
CONF_TRANSPORT,
@ -205,14 +234,24 @@ from .const import (
DEFAULT_KEEPALIVE,
DEFAULT_ON_COMMAND_TYPE,
DEFAULT_PAYLOAD_AVAILABLE,
DEFAULT_PAYLOAD_CLOSE,
DEFAULT_PAYLOAD_NOT_AVAILABLE,
DEFAULT_PAYLOAD_OFF,
DEFAULT_PAYLOAD_ON,
DEFAULT_PAYLOAD_OPEN,
DEFAULT_PAYLOAD_PRESS,
DEFAULT_PAYLOAD_STOP,
DEFAULT_PORT,
DEFAULT_POSITION_CLOSED,
DEFAULT_POSITION_OPEN,
DEFAULT_PREFIX,
DEFAULT_PROTOCOL,
DEFAULT_QOS,
DEFAULT_STATE_STOPPED,
DEFAULT_TILT_CLOSED_POSITION,
DEFAULT_TILT_MAX,
DEFAULT_TILT_MIN,
DEFAULT_TILT_OPEN_POSITION,
DEFAULT_TRANSPORT,
DEFAULT_WILL,
DEFAULT_WS_PATH,
@ -313,6 +352,7 @@ KEY_UPLOAD_SELECTOR = FileSelector(
SUBENTRY_PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.BUTTON,
Platform.COVER,
Platform.LIGHT,
Platform.NOTIFY,
Platform.SENSOR,
@ -365,6 +405,14 @@ BUTTON_DEVICE_CLASS_SELECTOR = SelectSelector(
sort=True,
)
)
COVER_DEVICE_CLASS_SELECTOR = SelectSelector(
SelectSelectorConfig(
options=[device_class.value for device_class in CoverDeviceClass],
mode=SelectSelectorMode.DROPDOWN,
translation_key="device_class_cover",
sort=True,
)
)
SENSOR_STATE_CLASS_SELECTOR = SelectSelector(
SelectSelectorConfig(
options=[device_class.value for device_class in SensorStateClass],
@ -386,6 +434,9 @@ TIMEOUT_SELECTOR = NumberSelector(
NumberSelectorConfig(mode=NumberSelectorMode.BOX, min=0)
)
# Cover specific selectors
POSITION_SELECTOR = NumberSelector(NumberSelectorConfig(mode=NumberSelectorMode.BOX))
# Switch specific selectors
SWITCH_DEVICE_CLASS_SELECTOR = SelectSelector(
SelectSelectorConfig(
@ -444,6 +495,48 @@ SUPPORTED_COLOR_MODES_SELECTOR = SelectSelector(
)
@callback
def validate_cover_platform_config(
config: dict[str, Any],
) -> dict[str, str]:
"""Validate the cover platform options."""
errors: dict[str, str] = {}
# If set position topic is set then get position topic is set as well.
if CONF_SET_POSITION_TOPIC in config and CONF_GET_POSITION_TOPIC not in config:
errors["cover_position_settings"] = (
"cover_get_and_set_position_must_be_set_together"
)
# if templates are set make sure the topic for the template is also set
if CONF_VALUE_TEMPLATE in config and CONF_STATE_TOPIC not in config:
errors[CONF_VALUE_TEMPLATE] = (
"cover_value_template_must_be_used_with_state_topic"
)
if CONF_GET_POSITION_TEMPLATE in config and CONF_GET_POSITION_TOPIC not in config:
errors["cover_position_settings"] = (
"cover_get_position_template_must_be_used_with_get_position_topic"
)
if CONF_SET_POSITION_TEMPLATE in config and CONF_SET_POSITION_TOPIC not in config:
errors["cover_position_settings"] = (
"cover_set_position_template_must_be_used_with_set_position_topic"
)
if CONF_TILT_COMMAND_TEMPLATE in config and CONF_TILT_COMMAND_TOPIC not in config:
errors["cover_tilt_settings"] = (
"cover_tilt_command_template_must_be_used_with_tilt_command_topic"
)
if CONF_TILT_STATUS_TEMPLATE in config and CONF_TILT_STATUS_TOPIC not in config:
errors["cover_tilt_settings"] = (
"cover_tilt_status_template_must_be_used_with_tilt_status_topic"
)
return errors
@callback
def validate_sensor_platform_config(
config: dict[str, Any],
@ -571,6 +664,12 @@ PLATFORM_ENTITY_FIELDS = {
required=False,
),
},
Platform.COVER.value: {
CONF_DEVICE_CLASS: PlatformField(
selector=COVER_DEVICE_CLASS_SELECTOR,
required=False,
),
},
Platform.NOTIFY.value: {},
Platform.SENSOR.value: {
CONF_DEVICE_CLASS: PlatformField(
@ -673,6 +772,185 @@ PLATFORM_MQTT_FIELDS = {
),
CONF_RETAIN: PlatformField(selector=BOOLEAN_SELECTOR, required=False),
},
Platform.COVER.value: {
CONF_COMMAND_TOPIC: PlatformField(
selector=TEXT_SELECTOR,
required=False,
validator=valid_publish_topic,
error="invalid_publish_topic",
),
CONF_STATE_TOPIC: PlatformField(
selector=TEXT_SELECTOR,
required=False,
validator=valid_subscribe_topic,
error="invalid_subscribe_topic",
),
CONF_VALUE_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=validate(cv.template),
error="invalid_template",
),
CONF_RETAIN: PlatformField(selector=BOOLEAN_SELECTOR, required=False),
CONF_OPTIMISTIC: PlatformField(selector=BOOLEAN_SELECTOR, required=False),
CONF_PAYLOAD_CLOSE: PlatformField(
selector=TEXT_SELECTOR,
required=False,
default=DEFAULT_PAYLOAD_CLOSE,
section="cover_payload_settings",
),
CONF_PAYLOAD_OPEN: PlatformField(
selector=TEXT_SELECTOR,
required=False,
default=DEFAULT_PAYLOAD_OPEN,
section="cover_payload_settings",
),
CONF_PAYLOAD_STOP: PlatformField(
selector=TEXT_SELECTOR,
required=False,
default=None,
section="cover_payload_settings",
),
CONF_PAYLOAD_STOP_TILT: PlatformField(
selector=TEXT_SELECTOR,
required=False,
default=DEFAULT_PAYLOAD_STOP,
section="cover_payload_settings",
),
CONF_STATE_CLOSED: PlatformField(
selector=TEXT_SELECTOR,
required=False,
default=STATE_CLOSED,
section="cover_payload_settings",
),
CONF_STATE_CLOSING: PlatformField(
selector=TEXT_SELECTOR,
required=False,
default=STATE_CLOSING,
section="cover_payload_settings",
),
CONF_STATE_OPEN: PlatformField(
selector=TEXT_SELECTOR,
required=False,
default=STATE_OPEN,
section="cover_payload_settings",
),
CONF_STATE_OPENING: PlatformField(
selector=TEXT_SELECTOR,
required=False,
default=STATE_OPENING,
section="cover_payload_settings",
),
CONF_STATE_STOPPED: PlatformField(
selector=TEXT_SELECTOR,
required=False,
default=DEFAULT_STATE_STOPPED,
section="cover_payload_settings",
),
CONF_SET_POSITION_TOPIC: PlatformField(
selector=TEXT_SELECTOR,
required=False,
validator=valid_publish_topic,
error="invalid_publish_topic",
section="cover_position_settings",
),
CONF_SET_POSITION_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=validate(cv.template),
error="invalid_template",
section="cover_position_settings",
),
CONF_GET_POSITION_TOPIC: PlatformField(
selector=TEXT_SELECTOR,
required=False,
validator=valid_subscribe_topic,
error="invalid_subscribe_topic",
section="cover_position_settings",
),
CONF_GET_POSITION_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=validate(cv.template),
error="invalid_template",
section="cover_position_settings",
),
CONF_POSITION_CLOSED: PlatformField(
selector=POSITION_SELECTOR,
required=False,
validator=int,
default=DEFAULT_POSITION_CLOSED,
section="cover_position_settings",
),
CONF_POSITION_OPEN: PlatformField(
selector=POSITION_SELECTOR,
required=False,
validator=int,
default=DEFAULT_POSITION_OPEN,
section="cover_position_settings",
),
CONF_TILT_COMMAND_TOPIC: PlatformField(
selector=TEXT_SELECTOR,
required=False,
validator=valid_publish_topic,
error="invalid_publish_topic",
section="cover_tilt_settings",
),
CONF_TILT_COMMAND_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=validate(cv.template),
error="invalid_template",
section="cover_tilt_settings",
),
CONF_TILT_CLOSED_POSITION: PlatformField(
selector=POSITION_SELECTOR,
required=False,
validator=int,
default=DEFAULT_TILT_CLOSED_POSITION,
section="cover_tilt_settings",
),
CONF_TILT_OPEN_POSITION: PlatformField(
selector=POSITION_SELECTOR,
required=False,
validator=int,
default=DEFAULT_TILT_OPEN_POSITION,
section="cover_tilt_settings",
),
CONF_TILT_STATUS_TOPIC: PlatformField(
selector=TEXT_SELECTOR,
required=False,
validator=valid_subscribe_topic,
error="invalid_subscribe_topic",
section="cover_tilt_settings",
),
CONF_TILT_STATUS_TEMPLATE: PlatformField(
selector=TEMPLATE_SELECTOR,
required=False,
validator=validate(cv.template),
error="invalid_template",
section="cover_tilt_settings",
),
CONF_TILT_MIN: PlatformField(
selector=POSITION_SELECTOR,
required=False,
validator=int,
default=DEFAULT_TILT_MIN,
section="cover_tilt_settings",
),
CONF_TILT_MAX: PlatformField(
selector=POSITION_SELECTOR,
required=False,
validator=int,
default=DEFAULT_TILT_MAX,
section="cover_tilt_settings",
),
CONF_TILT_STATE_OPTIMISTIC: PlatformField(
selector=BOOLEAN_SELECTOR,
required=False,
section="cover_tilt_settings",
),
},
Platform.NOTIFY.value: {
CONF_COMMAND_TOPIC: PlatformField(
selector=TEXT_SELECTOR,
@ -1231,6 +1509,7 @@ ENTITY_CONFIG_VALIDATOR: dict[
] = {
Platform.BINARY_SENSOR.value: None,
Platform.BUTTON.value: None,
Platform.COVER.value: validate_cover_platform_config,
Platform.LIGHT.value: validate_light_platform_config,
Platform.NOTIFY.value: None,
Platform.SENSOR.value: validate_sensor_platform_config,

View File

@ -90,6 +90,8 @@ CONF_EXPIRE_AFTER = "expire_after"
CONF_FLASH = "flash"
CONF_FLASH_TIME_LONG = "flash_time_long"
CONF_FLASH_TIME_SHORT = "flash_time_short"
CONF_GET_POSITION_TEMPLATE = "position_template"
CONF_GET_POSITION_TOPIC = "position_topic"
CONF_GREEN_TEMPLATE = "green_template"
CONF_HS_COMMAND_TEMPLATE = "hs_command_template"
CONF_HS_COMMAND_TOPIC = "hs_command_topic"
@ -111,6 +113,7 @@ CONF_PAYLOAD_CLOSE = "payload_close"
CONF_PAYLOAD_OPEN = "payload_open"
CONF_PAYLOAD_PRESS = "payload_press"
CONF_PAYLOAD_STOP = "payload_stop"
CONF_PAYLOAD_STOP_TILT = "payload_stop_tilt"
CONF_POSITION_CLOSED = "position_closed"
CONF_POSITION_OPEN = "position_open"
CONF_POWER_COMMAND_TOPIC = "power_command_topic"
@ -129,10 +132,13 @@ CONF_RGBWW_COMMAND_TEMPLATE = "rgbww_command_template"
CONF_RGBWW_COMMAND_TOPIC = "rgbww_command_topic"
CONF_RGBWW_STATE_TOPIC = "rgbww_state_topic"
CONF_RGBWW_VALUE_TEMPLATE = "rgbww_value_template"
CONF_SET_POSITION_TEMPLATE = "set_position_template"
CONF_SET_POSITION_TOPIC = "set_position_topic"
CONF_STATE_CLOSED = "state_closed"
CONF_STATE_CLOSING = "state_closing"
CONF_STATE_OPEN = "state_open"
CONF_STATE_OPENING = "state_opening"
CONF_STATE_STOPPED = "state_stopped"
CONF_SUGGESTED_DISPLAY_PRECISION = "suggested_display_precision"
CONF_SUPPORTED_COLOR_MODES = "supported_color_modes"
CONF_TEMP_COMMAND_TEMPLATE = "temperature_command_template"
@ -142,6 +148,15 @@ CONF_TEMP_STATE_TOPIC = "temperature_state_topic"
CONF_TEMP_INITIAL = "initial"
CONF_TEMP_MAX = "max_temp"
CONF_TEMP_MIN = "min_temp"
CONF_TILT_COMMAND_TEMPLATE = "tilt_command_template"
CONF_TILT_COMMAND_TOPIC = "tilt_command_topic"
CONF_TILT_STATUS_TOPIC = "tilt_status_topic"
CONF_TILT_STATUS_TEMPLATE = "tilt_status_template"
CONF_TILT_CLOSED_POSITION = "tilt_closed_value"
CONF_TILT_MAX = "tilt_max"
CONF_TILT_MIN = "tilt_min"
CONF_TILT_OPEN_POSITION = "tilt_opened_value"
CONF_TILT_STATE_OPTIMISTIC = "tilt_optimistic"
CONF_TRANSITION = "transition"
CONF_XY_COMMAND_TEMPLATE = "xy_command_template"
CONF_XY_COMMAND_TOPIC = "xy_command_topic"
@ -190,15 +205,25 @@ DEFAULT_PAYLOAD_OFF = "OFF"
DEFAULT_PAYLOAD_ON = "ON"
DEFAULT_PAYLOAD_OPEN = "OPEN"
DEFAULT_PAYLOAD_PRESS = "PRESS"
DEFAULT_PAYLOAD_STOP = "STOP"
DEFAULT_PORT = 1883
DEFAULT_RETAIN = False
DEFAULT_TILT_CLOSED_POSITION = 0
DEFAULT_TILT_MAX = 100
DEFAULT_TILT_MIN = 0
DEFAULT_TILT_OPEN_POSITION = 100
DEFAULT_TILT_OPTIMISTIC = False
DEFAULT_WS_HEADERS: dict[str, str] = {}
DEFAULT_WS_PATH = "/"
DEFAULT_POSITION_CLOSED = 0
DEFAULT_POSITION_OPEN = 100
DEFAULT_RETAIN = False
DEFAULT_STATE_STOPPED = "stopped"
DEFAULT_WHITE_SCALE = 255
COVER_PAYLOAD = "cover"
TILT_PAYLOAD = "tilt"
VALUES_ON_COMMAND_TYPE = ["first", "last", "brightness"]
PROTOCOL_31 = "3.1"

View File

@ -43,23 +43,45 @@ from . import subscription
from .config import MQTT_BASE_SCHEMA
from .const import (
CONF_COMMAND_TOPIC,
CONF_GET_POSITION_TEMPLATE,
CONF_GET_POSITION_TOPIC,
CONF_PAYLOAD_CLOSE,
CONF_PAYLOAD_OPEN,
CONF_PAYLOAD_STOP,
CONF_PAYLOAD_STOP_TILT,
CONF_POSITION_CLOSED,
CONF_POSITION_OPEN,
CONF_RETAIN,
CONF_SET_POSITION_TEMPLATE,
CONF_SET_POSITION_TOPIC,
CONF_STATE_CLOSED,
CONF_STATE_CLOSING,
CONF_STATE_OPEN,
CONF_STATE_OPENING,
CONF_STATE_STOPPED,
CONF_STATE_TOPIC,
CONF_TILT_CLOSED_POSITION,
CONF_TILT_COMMAND_TEMPLATE,
CONF_TILT_COMMAND_TOPIC,
CONF_TILT_MAX,
CONF_TILT_MIN,
CONF_TILT_OPEN_POSITION,
CONF_TILT_STATE_OPTIMISTIC,
CONF_TILT_STATUS_TEMPLATE,
CONF_TILT_STATUS_TOPIC,
DEFAULT_OPTIMISTIC,
DEFAULT_PAYLOAD_CLOSE,
DEFAULT_PAYLOAD_OPEN,
DEFAULT_PAYLOAD_STOP,
DEFAULT_POSITION_CLOSED,
DEFAULT_POSITION_OPEN,
DEFAULT_RETAIN,
DEFAULT_STATE_STOPPED,
DEFAULT_TILT_CLOSED_POSITION,
DEFAULT_TILT_MAX,
DEFAULT_TILT_MIN,
DEFAULT_TILT_OPEN_POSITION,
DEFAULT_TILT_OPTIMISTIC,
PAYLOAD_NONE,
)
from .entity import MqttEntity, async_setup_entity_entry_helper
@ -71,37 +93,8 @@ _LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 0
CONF_GET_POSITION_TOPIC = "position_topic"
CONF_GET_POSITION_TEMPLATE = "position_template"
CONF_SET_POSITION_TOPIC = "set_position_topic"
CONF_SET_POSITION_TEMPLATE = "set_position_template"
CONF_TILT_COMMAND_TOPIC = "tilt_command_topic"
CONF_TILT_COMMAND_TEMPLATE = "tilt_command_template"
CONF_TILT_STATUS_TOPIC = "tilt_status_topic"
CONF_TILT_STATUS_TEMPLATE = "tilt_status_template"
CONF_STATE_STOPPED = "state_stopped"
CONF_PAYLOAD_STOP_TILT = "payload_stop_tilt"
CONF_TILT_CLOSED_POSITION = "tilt_closed_value"
CONF_TILT_MAX = "tilt_max"
CONF_TILT_MIN = "tilt_min"
CONF_TILT_OPEN_POSITION = "tilt_opened_value"
CONF_TILT_STATE_OPTIMISTIC = "tilt_optimistic"
TILT_PAYLOAD = "tilt"
COVER_PAYLOAD = "cover"
DEFAULT_NAME = "MQTT Cover"
DEFAULT_STATE_STOPPED = "stopped"
DEFAULT_PAYLOAD_STOP = "STOP"
DEFAULT_TILT_CLOSED_POSITION = 0
DEFAULT_TILT_MAX = 100
DEFAULT_TILT_MIN = 0
DEFAULT_TILT_OPEN_POSITION = 100
DEFAULT_TILT_OPTIMISTIC = False
TILT_FEATURES = (
CoverEntityFeature.OPEN_TILT
| CoverEntityFeature.CLOSE_TILT

View File

@ -316,6 +316,75 @@
"transition": "Enable the transition feature for this light"
}
},
"cover_payload_settings": {
"name": "Payload settings",
"data": {
"payload_close": "Payload \"close\"",
"payload_open": "Payload \"open\"",
"payload_stop": "Payload \"stop\"",
"payload_stop_tilt": "Payload \"stop tilt\"",
"state_closed": "State \"closed\"",
"state_closing": "State \"closing\"",
"state_open": "State \"open\"",
"state_opening": "State \"opening\"",
"state_stopped": "State \"stopped\""
},
"data_description": {
"payload_close": "The payload sent when a \"close\" command is issued.",
"payload_open": "The payload sent when an \"open\" command is issued.",
"payload_stop": "The payload sent when a \"stop\" command is issued. Leave empty to disable the \"stop\" feature.",
"payload_stop_tilt": "The payload sent when a \"stop tilt\" command is issued.",
"state_closed": "The payload received at the state topic that represents the \"closed\" state.",
"state_closing": "The payload received at the state topic that represents the \"closing\" state.",
"state_open": "The payload received at the state topic that represents the \"open\" state.",
"state_opening": "The payload received at the state topic that represents the \"opening\" state.",
"state_stopped": "The payload received at the state topic that represents the \"stopped\" state (for covers that do not report \"open\"/\"closed\" state)."
}
},
"cover_position_settings": {
"name": "Position settings",
"data": {
"position_closed": "Position \"closed\" value",
"position_open": "Position \"open\" value",
"position_template": "Position value template",
"position_topic": "Position state topic",
"set_position_template": "Set position template",
"set_position_topic": "Set position topic"
},
"data_description": {
"position_closed": "Number which represents \"closed\" position.",
"position_open": "Number which represents \"open\" position.",
"position_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the payload for the position topic. Within the template the following variables are also available: `entity_id`, `position_open`, `position_closed`, `tilt_min` and `tilt_max`. [Learn more.]({url}#position_template)",
"position_topic": "The MQTT topic subscribed to receive cover position state messages. [Learn more.]({url}#position_topic)",
"set_position_template": "[Template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to define the position to be sent to the set position topic. Within the template the following variables are available: `value` (the scaled target position), `entity_id`, `position` (the target position percentage), `position_open`, `position_closed`, `tilt_min` and `tilt_max`. [Learn more.]({url}#set_position_template)",
"set_position_topic": "The MQTT topic to publish position commands to. You need to use the set position topic as well if you want to use the position topic. Use template if position topic wants different values than within range \"position closed\" - \"position_open\". If template is not defined and position \"closed\" != 100 and position \"open\" != 0 then proper position value is calculated from percentage position. [Learn more.]({url}#set_position_topic)"
}
},
"cover_tilt_settings": {
"name": "Tilt settings",
"data": {
"tilt_closed_value": "Tilt \"closed\" value",
"tilt_command_template": "Set tilt template",
"tilt_command_topic": "Set tilt topic",
"tilt_max": "Tilt max",
"tilt_min": "Tilt min",
"tilt_opened_value": "Tilt \"opened\" value",
"tilt_status_template": "Tilt value template",
"tilt_status_topic": "Tilt status topic",
"tilt_optimistic": "Tilt optimistic"
},
"data_description": {
"tilt_closed_value": "The value that will be sent to the \"set tilt topic\" when the cover tilt is closed.",
"tilt_command_template": "[Template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to define the position to be sent to the set tilt topic. Within the template the following variables are available: `entity_id`, `tilt_position` (the target tilt position percentage), `position_open`, `position_closed`, `tilt_min` and `tilt_max`. [Learn more.]({url}#tilt_command_template)",
"tilt_command_topic": "The MQTT topic to publish commands to control the cover tilt. [Learn more.]({url}#tilt_command_topic)",
"tilt_max": "The maximum tilt value.",
"tilt_min": "The minimum tilt value.",
"tilt_opened_value": "The value that will be sent to the \"set tilt topic\" when the cover tilt is opened.",
"tilt_status_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the payload for the tilt status topic. Within the template the following variables are available: `entity_id`, `position_open`, `position_closed`, `tilt_min` and `tilt_max`. [Learn more.]({url}#tilt_status_template)",
"tilt_status_topic": "The MQTT topic subscribed to receive tilt status update values. [Learn more.]({url}#tilt_status_topic)",
"tilt_optimistic": "Flag that defines if tilt works in optimistic mode. If tilt status topic is not defined, tilt works in optimisic mode by default. [Learn more.]({url}#tilt_optimistic)"
}
},
"light_brightness_settings": {
"name": "Brightness settings",
"data": {
@ -476,6 +545,12 @@
"default": "MQTT device with {platform} entity \"{entity}\" was set up successfully.\n\nNote that you can reconfigure the MQTT device at any time, e.g. to add more entities."
},
"error": {
"cover_get_and_set_position_must_be_set_together": "The get position and set position topic options must be set together",
"cover_get_position_template_must_be_used_with_get_position_topic": "The position value template must be used together with the position state topic setting",
"cover_set_position_template_must_be_used_with_set_position_topic": "The set position template must be used with the set position topic",
"cover_tilt_command_template_must_be_used_with_tilt_command_topic": "The tilt command template must be used with the tilt command topic",
"cover_tilt_status_template_must_be_used_with_tilt_status_topic": "The tilt value template must be used with the tilt status topic",
"cover_value_template_must_be_used_with_state_topic": "The value template must be used with the state topic option",
"invalid_input": "Invalid value",
"invalid_subscribe_topic": "Invalid subscribe topic",
"invalid_template": "Invalid template",
@ -643,6 +718,20 @@
"update": "[%key:component::button::entity_component::update::name%]"
}
},
"device_class_cover": {
"options": {
"awning": "[%key:component::cover::entity_component::awning::name%]",
"blind": "[%key:component::cover::entity_component::blind::name%]",
"curtain": "[%key:component::cover::entity_component::curtain::name%]",
"damper": "[%key:component::cover::entity_component::damper::name%]",
"door": "[%key:component::cover::entity_component::door::name%]",
"garage": "[%key:component::cover::entity_component::garage::name%]",
"gate": "[%key:component::cover::entity_component::gate::name%]",
"shade": "[%key:component::cover::entity_component::shade::name%]",
"shutter": "[%key:component::cover::entity_component::shutter::name%]",
"window": "[%key:component::cover::entity_component::window::name%]"
}
},
"device_class_sensor": {
"options": {
"apparent_power": "[%key:component::sensor::entity_component::apparent_power::name%]",
@ -727,6 +816,7 @@
"options": {
"binary_sensor": "[%key:component::binary_sensor::title%]",
"button": "[%key:component::button::title%]",
"cover": "[%key:component::cover::title%]",
"light": "[%key:component::light::title%]",
"notify": "[%key:component::notify::title%]",
"sensor": "[%key:component::sensor::title%]",

View File

@ -92,6 +92,41 @@ MOCK_SUBENTRY_BUTTON_COMPONENT = {
"entity_picture": "https://example.com/365d05e6607c4dfb8ae915cff71a954b",
},
}
MOCK_SUBENTRY_COVER_COMPONENT = {
"b37acf667fa04c688ad7dfb27de2178b": {
"platform": "cover",
"name": "Blind",
"device_class": "blind",
"command_topic": "test-topic",
"payload_stop": None,
"payload_stop_tilt": "STOP",
"payload_open": "OPEN",
"payload_close": "CLOSE",
"position_closed": 0,
"position_open": 100,
"position_template": "{{ value_json.position }}",
"position_topic": "test-topic/position",
"set_position_template": "{{ value }}",
"set_position_topic": "test-topic/position-set",
"state_closed": "closed",
"state_closing": "closing",
"state_open": "open",
"state_opening": "opening",
"state_stopped": "stopped",
"state_topic": "test-topic",
"tilt_closed_value": 0,
"tilt_max": 100,
"tilt_min": 0,
"tilt_opened_value": 100,
"tilt_optimistic": False,
"tilt_command_topic": "test-topic/tilt-set",
"tilt_command_template": "{{ value }}",
"tilt_status_topic": "test-topic/tilt",
"tilt_status_template": "{{ value_json.position }}",
"retain": False,
"entity_picture": "https://example.com/b37acf667fa04c688ad7dfb27de2178b",
},
}
MOCK_SUBENTRY_NOTIFY_COMPONENT1 = {
"363a7ecad6be4a19b939a016ea93e994": {
"platform": "notify",
@ -225,6 +260,10 @@ MOCK_BUTTON_SUBENTRY_DATA_SINGLE = {
"device": MOCK_SUBENTRY_DEVICE_DATA | {"mqtt_settings": {"qos": 2}},
"components": MOCK_SUBENTRY_BUTTON_COMPONENT,
}
MOCK_COVER_SUBENTRY_DATA_SINGLE = {
"device": MOCK_SUBENTRY_DEVICE_DATA | {"mqtt_settings": {"qos": 0}},
"components": MOCK_SUBENTRY_COVER_COMPONENT,
}
MOCK_NOTIFY_SUBENTRY_DATA_SINGLE = {
"device": MOCK_SUBENTRY_DEVICE_DATA | {"mqtt_settings": {"qos": 1}},
"components": MOCK_SUBENTRY_NOTIFY_COMPONENT1,

View File

@ -35,6 +35,7 @@ from homeassistant.helpers.service_info.hassio import HassioServiceInfo
from .common import (
MOCK_BINARY_SENSOR_SUBENTRY_DATA_SINGLE,
MOCK_BUTTON_SUBENTRY_DATA_SINGLE,
MOCK_COVER_SUBENTRY_DATA_SINGLE,
MOCK_LIGHT_BASIC_KELVIN_SUBENTRY_DATA_SINGLE,
MOCK_NOTIFY_SUBENTRY_DATA_MULTI,
MOCK_NOTIFY_SUBENTRY_DATA_NO_NAME,
@ -2698,6 +2699,92 @@ async def test_migrate_of_incompatible_config_entry(
),
"Milk notifier Restart",
),
(
MOCK_COVER_SUBENTRY_DATA_SINGLE,
{"name": "Milk notifier", "mqtt_settings": {"qos": 0}},
{"name": "Blind"},
{"device_class": "blind"},
(),
{
"command_topic": "test-topic",
"cover_position_settings": {
"position_template": "{{ value_json.position }}",
"position_topic": "test-topic/position",
"set_position_template": "{{ value }}",
"set_position_topic": "test-topic/position-set",
},
"state_topic": "test-topic",
"retain": False,
"cover_tilt_settings": {
"tilt_command_topic": "test-topic/tilt-set",
"tilt_command_template": "{{ value }}",
"tilt_status_topic": "test-topic/tilt",
"tilt_status_template": "{{ value_json.position }}",
"tilt_closed_value": 0,
"tilt_opened_value": 100,
"tilt_max": 100,
"tilt_min": 0,
"tilt_optimistic": False,
},
},
(
(
{"value_template": "{{ json_value.state }}"},
{
"value_template": "cover_value_template_must_be_used_with_state_topic"
},
),
(
{"cover_position_settings": {"set_position_topic": "test-topic"}},
{
"cover_position_settings": "cover_get_and_set_position_must_be_set_together"
},
),
(
{
"cover_position_settings": {
"set_position_template": "{{ value }}"
}
},
{
"cover_position_settings": "cover_set_position_template_must_be_used_with_set_position_topic"
},
),
(
{
"cover_position_settings": {
"position_template": "{{ json_value.position }}"
}
},
{
"cover_position_settings": "cover_get_position_template_must_be_used_with_get_position_topic"
},
),
(
{"cover_position_settings": {"set_position_topic": "{{ value }}"}},
{
"cover_position_settings": "cover_get_and_set_position_must_be_set_together"
},
),
(
{"cover_tilt_settings": {"tilt_command_template": "{{ value }}"}},
{
"cover_tilt_settings": "cover_tilt_command_template_must_be_used_with_tilt_command_topic"
},
),
(
{
"cover_tilt_settings": {
"tilt_status_template": "{{ json_value.position }}"
}
},
{
"cover_tilt_settings": "cover_tilt_status_template_must_be_used_with_tilt_status_topic"
},
),
),
"Milk notifier Blind",
),
(
MOCK_NOTIFY_SUBENTRY_DATA_SINGLE,
{"name": "Milk notifier", "mqtt_settings": {"qos": 1}},
@ -2883,6 +2970,7 @@ async def test_migrate_of_incompatible_config_entry(
ids=[
"binary_sensor",
"button",
"cover",
"notify_with_entity_name",
"notify_no_entity_name",
"sensor_options",