Add switch as entity platform on MQTT subentries (#140658)

This commit is contained in:
Jan Bouwhuis 2025-03-26 16:46:44 +01:00 committed by GitHub
parent 57f65c205e
commit febc455bc5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 102 additions and 6 deletions

View File

@ -33,6 +33,7 @@ from homeassistant.components.sensor import (
SensorDeviceClass,
SensorStateClass,
)
from homeassistant.components.switch import SwitchDeviceClass
from homeassistant.config_entries import (
SOURCE_RECONFIGURE,
ConfigEntry,
@ -55,6 +56,7 @@ from homeassistant.const import (
CONF_DISCOVERY,
CONF_HOST,
CONF_NAME,
CONF_OPTIMISTIC,
CONF_PASSWORD,
CONF_PAYLOAD,
CONF_PLATFORM,
@ -233,7 +235,7 @@ KEY_UPLOAD_SELECTOR = FileSelector(
)
# Subentry selectors
SUBENTRY_PLATFORMS = [Platform.NOTIFY, Platform.SENSOR]
SUBENTRY_PLATFORMS = [Platform.NOTIFY, Platform.SENSOR, Platform.SWITCH]
SUBENTRY_PLATFORM_SELECTOR = SelectSelector(
SelectSelectorConfig(
options=[platform.value for platform in SUBENTRY_PLATFORMS],
@ -286,6 +288,15 @@ EXPIRE_AFTER_SELECTOR = NumberSelector(
NumberSelectorConfig(mode=NumberSelectorMode.BOX, min=0)
)
# Switch specific selectors
SWITCH_DEVICE_CLASS_SELECTOR = SelectSelector(
SelectSelectorConfig(
options=[device_class.value for device_class in SwitchDeviceClass],
mode=SelectSelectorMode.DROPDOWN,
translation_key="device_class_switch",
)
)
@callback
def validate_sensor_platform_config(
@ -390,6 +401,9 @@ PLATFORM_ENTITY_FIELDS = {
conditions=({"device_class": "enum"},),
),
},
Platform.SWITCH.value: {
CONF_DEVICE_CLASS: PlatformField(SWITCH_DEVICE_CLASS_SELECTOR, False, str),
},
}
PLATFORM_MQTT_FIELDS = {
Platform.NOTIFY.value: {
@ -419,6 +433,22 @@ PLATFORM_MQTT_FIELDS = {
EXPIRE_AFTER_SELECTOR, False, cv.positive_int, section="advanced_settings"
),
},
Platform.SWITCH.value: {
CONF_COMMAND_TOPIC: PlatformField(
TEXT_SELECTOR, True, valid_publish_topic, "invalid_publish_topic"
),
CONF_COMMAND_TEMPLATE: PlatformField(
TEMPLATE_SELECTOR, False, cv.template, "invalid_template"
),
CONF_STATE_TOPIC: PlatformField(
TEXT_SELECTOR, False, valid_subscribe_topic, "invalid_subscribe_topic"
),
CONF_VALUE_TEMPLATE: PlatformField(
TEMPLATE_SELECTOR, False, cv.template, "invalid_template"
),
CONF_RETAIN: PlatformField(BOOLEAN_SELECTOR, False, bool),
CONF_OPTIMISTIC: PlatformField(BOOLEAN_SELECTOR, False, bool),
},
}
ENTITY_CONFIG_VALIDATOR: dict[
str,
@ -426,6 +456,7 @@ ENTITY_CONFIG_VALIDATOR: dict[
] = {
Platform.NOTIFY.value: None,
Platform.SENSOR.value: validate_sensor_platform_config,
Platform.SWITCH.value: None,
}
MQTT_DEVICE_PLATFORM_FIELDS = {

View File

@ -246,7 +246,9 @@
"value_template": "Value template",
"last_reset_value_template": "Last reset value template",
"force_update": "Force update",
"retain": "Retain"
"optimistic": "Optimistic",
"retain": "Retain",
"qos": "QoS"
},
"data_description": {
"command_topic": "The publishing topic that will be used to control the {platform} entity. [Learn more.]({url}#command_topic)",
@ -255,7 +257,9 @@
"value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the {platform} entity value.",
"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)",
"retain": "Select if values published by the {platform} entity should be retained at the MQTT broker."
"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.",
"qos": "The QoS value {platform} entity should use."
},
"sections": {
"advanced_settings": {
@ -462,10 +466,17 @@
"wind_speed": "[%key:component::sensor::entity_component::wind_speed::name%]"
}
},
"device_class_switch": {
"options": {
"outlet": "[%key:component::switch::entity_component::outlet::name%]",
"switch": "[%key:component::switch::title%]"
}
},
"platform": {
"options": {
"notify": "Notify",
"sensor": "Sensor"
"sensor": "Sensor",
"switch": "Switch"
}
},
"set_ca_cert": {

View File

@ -125,6 +125,19 @@ MOCK_SUBENTRY_SENSOR_COMPONENT_LAST_RESET = {
"entity_picture": "https://example.com/e9261f6feed443e7b7d5f3fbe2a47412",
},
}
MOCK_SUBENTRY_SWITCH_COMPONENT = {
"3faf1318016c46c5aea26707eeb6f12e": {
"platform": "switch",
"name": "Outlet",
"device_class": "outlet",
"command_topic": "test-topic",
"state_topic": "test-topic",
"command_template": "{{ value }}",
"value_template": "{{ value_json.value }}",
"entity_picture": "https://example.com/3faf1318016c46c5aea26707eeb6f12e",
"optimistic": True,
},
}
# Bogus light component just for code coverage
# Note that light cannot be setup through the UI yet
@ -223,7 +236,17 @@ MOCK_SENSOR_SUBENTRY_DATA_SINGLE_LAST_RESET_TEMPLATE = {
},
"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",
},
"components": MOCK_SUBENTRY_SWITCH_COMPONENT,
}
MOCK_SUBENTRY_DATA_BAD_COMPONENT_SCHEMA = {
"device": {
"name": "Milk notifier",
@ -246,7 +269,8 @@ MOCK_SUBENTRY_DATA_SET_MIX = {
},
"components": MOCK_SUBENTRY_NOTIFY_COMPONENT1
| MOCK_SUBENTRY_NOTIFY_COMPONENT2
| MOCK_SUBENTRY_LIGHT_COMPONENT,
| MOCK_SUBENTRY_LIGHT_COMPONENT
| MOCK_SUBENTRY_SWITCH_COMPONENT,
} | MOCK_SUBENTRY_AVAILABILITY_DATA
_SENTINEL = object()

View File

@ -39,6 +39,7 @@ from .common import (
MOCK_SENSOR_SUBENTRY_DATA_SINGLE,
MOCK_SENSOR_SUBENTRY_DATA_SINGLE_LAST_RESET_TEMPLATE,
MOCK_SENSOR_SUBENTRY_DATA_SINGLE_STATE_CLASS,
MOCK_SWITCH_SUBENTRY_DATA_SINGLE_STATE_CLASS,
)
from tests.common import MockConfigEntry, MockMqttReasonCode
@ -2733,12 +2734,41 @@ async def test_migrate_of_incompatible_config_entry(
(),
"Test sensor Energy",
),
(
MOCK_SWITCH_SUBENTRY_DATA_SINGLE_STATE_CLASS,
{"name": "Test switch", "mqtt_settings": {"qos": 0}},
{"name": "Outlet"},
{"device_class": "outlet"},
(),
{
"command_topic": "test-topic",
"command_template": "{{ value }}",
"state_topic": "test-topic",
"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"},
),
),
"Test switch Outlet",
),
],
ids=[
"notify_with_entity_name",
"notify_no_entity_name",
"sensor_options",
"sensor_total",
"switch",
],
)
async def test_subentry_configflow(