diff --git a/homeassistant/components/mqtt/button.py b/homeassistant/components/mqtt/button.py index 5b2bcc8920f..f5821896071 100644 --- a/homeassistant/components/mqtt/button.py +++ b/homeassistant/components/mqtt/button.py @@ -14,7 +14,13 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.typing import ConfigType from .config import DEFAULT_RETAIN, MQTT_BASE_SCHEMA -from .const import CONF_COMMAND_TEMPLATE, CONF_COMMAND_TOPIC, CONF_RETAIN +from .const import ( + CONF_COMMAND_TEMPLATE, + CONF_COMMAND_TOPIC, + CONF_PAYLOAD_PRESS, + CONF_RETAIN, + DEFAULT_PAYLOAD_PRESS, +) from .entity import MqttEntity, async_setup_entity_entry_helper from .models import MqttCommandTemplate from .schemas import MQTT_ENTITY_COMMON_SCHEMA @@ -22,9 +28,7 @@ from .util import valid_publish_topic PARALLEL_UPDATES = 0 -CONF_PAYLOAD_PRESS = "payload_press" DEFAULT_NAME = "MQTT Button" -DEFAULT_PAYLOAD_PRESS = "PRESS" PLATFORM_SCHEMA_MODERN = MQTT_BASE_SCHEMA.extend( { diff --git a/homeassistant/components/mqtt/config_flow.py b/homeassistant/components/mqtt/config_flow.py index 9e1773fab62..0ccf7468cbf 100644 --- a/homeassistant/components/mqtt/config_flow.py +++ b/homeassistant/components/mqtt/config_flow.py @@ -26,6 +26,7 @@ from cryptography.x509 import load_der_x509_certificate, load_pem_x509_certifica import voluptuous as vol from homeassistant.components.binary_sensor import BinarySensorDeviceClass +from homeassistant.components.button import ButtonDeviceClass from homeassistant.components.file_upload import process_uploaded_file from homeassistant.components.hassio import AddonError, AddonManager, AddonState from homeassistant.components.light import ( @@ -163,6 +164,7 @@ from .const import ( CONF_OPTIONS, CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, + CONF_PAYLOAD_PRESS, CONF_QOS, CONF_RED_TEMPLATE, CONF_RETAIN, @@ -206,6 +208,7 @@ from .const import ( DEFAULT_PAYLOAD_NOT_AVAILABLE, DEFAULT_PAYLOAD_OFF, DEFAULT_PAYLOAD_ON, + DEFAULT_PAYLOAD_PRESS, DEFAULT_PORT, DEFAULT_PREFIX, DEFAULT_PROTOCOL, @@ -309,6 +312,7 @@ KEY_UPLOAD_SELECTOR = FileSelector( # Subentry selectors SUBENTRY_PLATFORMS = [ Platform.BINARY_SENSOR, + Platform.BUTTON, Platform.LIGHT, Platform.NOTIFY, Platform.SENSOR, @@ -353,6 +357,14 @@ BINARY_SENSOR_DEVICE_CLASS_SELECTOR = SelectSelector( sort=True, ) ) +BUTTON_DEVICE_CLASS_SELECTOR = SelectSelector( + SelectSelectorConfig( + options=[device_class.value for device_class in ButtonDeviceClass], + mode=SelectSelectorMode.DROPDOWN, + translation_key="device_class_button", + sort=True, + ) +) SENSOR_STATE_CLASS_SELECTOR = SelectSelector( SelectSelectorConfig( options=[device_class.value for device_class in SensorStateClass], @@ -546,6 +558,13 @@ PLATFORM_ENTITY_FIELDS = { validator=str, ), }, + Platform.BUTTON.value: { + CONF_DEVICE_CLASS: PlatformField( + selector=BUTTON_DEVICE_CLASS_SELECTOR, + required=False, + validator=str, + ), + }, Platform.NOTIFY.value: {}, Platform.SENSOR.value: { CONF_DEVICE_CLASS: PlatformField( @@ -634,6 +653,29 @@ PLATFORM_MQTT_FIELDS = { section="advanced_settings", ), }, + Platform.BUTTON.value: { + CONF_COMMAND_TOPIC: PlatformField( + selector=TEXT_SELECTOR, + required=True, + validator=valid_publish_topic, + error="invalid_publish_topic", + ), + CONF_COMMAND_TEMPLATE: PlatformField( + selector=TEMPLATE_SELECTOR, + required=False, + validator=cv.template, + error="invalid_template", + ), + CONF_PAYLOAD_PRESS: PlatformField( + selector=TEXT_SELECTOR, + required=False, + validator=str, + default=DEFAULT_PAYLOAD_PRESS, + ), + CONF_RETAIN: PlatformField( + selector=BOOLEAN_SELECTOR, required=False, validator=bool + ), + }, Platform.NOTIFY.value: { CONF_COMMAND_TOPIC: PlatformField( selector=TEXT_SELECTOR, @@ -1206,6 +1248,7 @@ ENTITY_CONFIG_VALIDATOR: dict[ Callable[[dict[str, Any]], dict[str, str]] | None, ] = { Platform.BINARY_SENSOR.value: None, + Platform.BUTTON.value: None, Platform.LIGHT.value: validate_light_platform_config, Platform.NOTIFY.value: None, Platform.SENSOR.value: validate_sensor_platform_config, diff --git a/homeassistant/components/mqtt/const.py b/homeassistant/components/mqtt/const.py index b6dda0c0f8a..89e721f022b 100644 --- a/homeassistant/components/mqtt/const.py +++ b/homeassistant/components/mqtt/const.py @@ -109,6 +109,7 @@ CONF_OFF_DELAY = "off_delay" CONF_ON_COMMAND_TYPE = "on_command_type" CONF_PAYLOAD_CLOSE = "payload_close" CONF_PAYLOAD_OPEN = "payload_open" +CONF_PAYLOAD_PRESS = "payload_press" CONF_PAYLOAD_STOP = "payload_stop" CONF_POSITION_CLOSED = "position_closed" CONF_POSITION_OPEN = "position_open" @@ -188,6 +189,7 @@ DEFAULT_PAYLOAD_NOT_AVAILABLE = "offline" DEFAULT_PAYLOAD_OFF = "OFF" DEFAULT_PAYLOAD_ON = "ON" DEFAULT_PAYLOAD_OPEN = "OPEN" +DEFAULT_PAYLOAD_PRESS = "PRESS" DEFAULT_PORT = 1883 DEFAULT_RETAIN = False DEFAULT_WS_HEADERS: dict[str, str] = {} diff --git a/homeassistant/components/mqtt/strings.json b/homeassistant/components/mqtt/strings.json index b3eede62332..fdf1ebd8089 100644 --- a/homeassistant/components/mqtt/strings.json +++ b/homeassistant/components/mqtt/strings.json @@ -258,6 +258,7 @@ "optimistic": "Optimistic", "payload_off": "Payload \"off\"", "payload_on": "Payload \"on\"", + "payload_press": "Payload \"press\"", "qos": "QoS", "red_template": "Red template", "retain": "Retain", @@ -282,6 +283,7 @@ "optimistic": "Flag that defines if the {platform} entity works in optimistic mode. [Learn more.]({url}#optimistic)", "payload_off": "The payload that represents the \"off\" state.", "payload_on": "The payload that represents the \"on\" state.", + "payload_press": "The payload to send when the button is triggered.", "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.", @@ -634,6 +636,13 @@ "window": "[%key:component::binary_sensor::entity_component::window::name%]" } }, + "device_class_button": { + "options": { + "identify": "[%key:component::button::entity_component::identify::name%]", + "restart": "[%key:common::action::restart%]", + "update": "[%key:component::button::entity_component::update::name%]" + } + }, "device_class_sensor": { "options": { "apparent_power": "[%key:component::sensor::entity_component::apparent_power::name%]", @@ -717,6 +726,7 @@ "platform": { "options": { "binary_sensor": "[%key:component::binary_sensor::title%]", + "button": "[%key:component::button::title%]", "light": "[%key:component::light::title%]", "notify": "[%key:component::notify::title%]", "sensor": "[%key:component::sensor::title%]", diff --git a/tests/components/mqtt/common.py b/tests/components/mqtt/common.py index 283414cb96a..3c017de89f4 100644 --- a/tests/components/mqtt/common.py +++ b/tests/components/mqtt/common.py @@ -80,6 +80,18 @@ MOCK_SUBENTRY_BINARY_SENSOR_COMPONENT = { "entity_picture": "https://example.com/5b06357ef8654e8d9c54cee5bb0e939b", }, } +MOCK_SUBENTRY_BUTTON_COMPONENT = { + "365d05e6607c4dfb8ae915cff71a954b": { + "platform": "button", + "name": "Restart", + "device_class": "restart", + "command_topic": "test-topic", + "payload_press": "PRESS", + "command_template": "{{ value }}", + "retain": False, + "entity_picture": "https://example.com/365d05e6607c4dfb8ae915cff71a954b", + }, +} MOCK_SUBENTRY_NOTIFY_COMPONENT1 = { "363a7ecad6be4a19b939a016ea93e994": { "platform": "notify", @@ -205,6 +217,10 @@ MOCK_BINARY_SENSOR_SUBENTRY_DATA_SINGLE = { "device": MOCK_SUBENTRY_DEVICE_DATA | {"mqtt_settings": {"qos": 2}}, "components": MOCK_SUBENTRY_BINARY_SENSOR_COMPONENT, } +MOCK_BUTTON_SUBENTRY_DATA_SINGLE = { + "device": MOCK_SUBENTRY_DEVICE_DATA | {"mqtt_settings": {"qos": 2}}, + "components": MOCK_SUBENTRY_BUTTON_COMPONENT, +} MOCK_NOTIFY_SUBENTRY_DATA_SINGLE = { "device": MOCK_SUBENTRY_DEVICE_DATA | {"mqtt_settings": {"qos": 1}}, "components": MOCK_SUBENTRY_NOTIFY_COMPONENT1, diff --git a/tests/components/mqtt/test_config_flow.py b/tests/components/mqtt/test_config_flow.py index 50f718e332d..81d4960be23 100644 --- a/tests/components/mqtt/test_config_flow.py +++ b/tests/components/mqtt/test_config_flow.py @@ -34,6 +34,7 @@ from homeassistant.helpers.service_info.hassio import HassioServiceInfo from .common import ( MOCK_BINARY_SENSOR_SUBENTRY_DATA_SINGLE, + MOCK_BUTTON_SUBENTRY_DATA_SINGLE, MOCK_LIGHT_BASIC_KELVIN_SUBENTRY_DATA_SINGLE, MOCK_NOTIFY_SUBENTRY_DATA_MULTI, MOCK_NOTIFY_SUBENTRY_DATA_NO_NAME, @@ -2677,6 +2678,26 @@ async def test_migrate_of_incompatible_config_entry( ), "Milk notifier Hatch", ), + ( + MOCK_BUTTON_SUBENTRY_DATA_SINGLE, + {"name": "Milk notifier", "mqtt_settings": {"qos": 2}}, + {"name": "Restart"}, + {"device_class": "restart"}, + (), + { + "command_topic": "test-topic", + "command_template": "{{ value }}", + "payload_press": "PRESS", + "retain": False, + }, + ( + ( + {"command_topic": "test-topic#invalid"}, + {"command_topic": "invalid_publish_topic"}, + ), + ), + "Milk notifier Restart", + ), ( MOCK_NOTIFY_SUBENTRY_DATA_SINGLE, {"name": "Milk notifier", "mqtt_settings": {"qos": 1}}, @@ -2853,6 +2874,7 @@ async def test_migrate_of_incompatible_config_entry( ], ids=[ "binary_sensor", + "button", "notify_with_entity_name", "notify_no_entity_name", "sensor_options",