Skip to content

Commit 5f800f2

Browse files
committed
Add switch as entity platform on MQTT subentries
1 parent f6e962b commit 5f800f2

File tree

4 files changed

+99
-3
lines changed

4 files changed

+99
-3
lines changed

homeassistant/components/mqtt/config_flow.py

+32-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
SensorDeviceClass,
3434
SensorStateClass,
3535
)
36+
from homeassistant.components.switch import SwitchDeviceClass
3637
from homeassistant.config_entries import (
3738
SOURCE_RECONFIGURE,
3839
ConfigEntry,
@@ -55,6 +56,7 @@
5556
CONF_DISCOVERY,
5657
CONF_HOST,
5758
CONF_NAME,
59+
CONF_OPTIMISTIC,
5860
CONF_PASSWORD,
5961
CONF_PAYLOAD,
6062
CONF_PLATFORM,
@@ -234,7 +236,7 @@
234236

235237
# Subentry selectors
236238
RESET_IF_EMPTY = {CONF_OPTIONS}
237-
SUBENTRY_PLATFORMS = [Platform.NOTIFY, Platform.SENSOR]
239+
SUBENTRY_PLATFORMS = [Platform.NOTIFY, Platform.SENSOR, Platform.SWITCH]
238240
SUBENTRY_PLATFORM_SELECTOR = SelectSelector(
239241
SelectSelectorConfig(
240242
options=[platform.value for platform in SUBENTRY_PLATFORMS],
@@ -288,6 +290,15 @@
288290
NumberSelectorConfig(mode=NumberSelectorMode.BOX, min=0)
289291
)
290292

293+
# Switch specific selectors
294+
SWITCH_DEVICE_CLASS_SELECTOR = SelectSelector(
295+
SelectSelectorConfig(
296+
options=[device_class.value for device_class in SwitchDeviceClass],
297+
mode=SelectSelectorMode.DROPDOWN,
298+
translation_key="device_class_switch",
299+
)
300+
)
301+
291302

292303
@dataclass(frozen=True)
293304
class PlatformField:
@@ -343,6 +354,9 @@ def unit_of_measurement_selector(user_data: dict[str, Any | None]) -> Selector:
343354
),
344355
CONF_OPTIONS: PlatformField(OPTIONS_SELECTOR, False, cv.ensure_list),
345356
},
357+
Platform.SWITCH.value: {
358+
CONF_DEVICE_CLASS: PlatformField(SWITCH_DEVICE_CLASS_SELECTOR, False, str),
359+
},
346360
}
347361
PLATFORM_MQTT_FIELDS = {
348362
Platform.NOTIFY.value: {
@@ -366,12 +380,29 @@ def unit_of_measurement_selector(user_data: dict[str, Any | None]) -> Selector:
366380
),
367381
CONF_EXPIRE_AFTER: PlatformField(EXIRE_AFTER_SELECTOR, False, cv.positive_int),
368382
},
383+
Platform.SWITCH.value: {
384+
CONF_COMMAND_TOPIC: PlatformField(
385+
TEXT_SELECTOR, True, valid_publish_topic, "invalid_publish_topic"
386+
),
387+
CONF_COMMAND_TEMPLATE: PlatformField(
388+
TEMPLATE_SELECTOR, False, cv.template, "invalid_template"
389+
),
390+
CONF_STATE_TOPIC: PlatformField(
391+
TEXT_SELECTOR, False, valid_subscribe_topic, "invalid_subscribe_topic"
392+
),
393+
CONF_VALUE_TEMPLATE: PlatformField(
394+
TEMPLATE_SELECTOR, False, cv.template, "invalid_template"
395+
),
396+
CONF_RETAIN: PlatformField(BOOLEAN_SELECTOR, False, bool),
397+
CONF_OPTIMISTIC: PlatformField(BOOLEAN_SELECTOR, False, bool),
398+
},
369399
}
370400
ENTITY_CONFIG_VALIDATOR: dict[
371401
str, Callable[[dict[str, Any], dict[str, str]], dict[str, Any]] | None
372402
] = {
373403
Platform.NOTIFY.value: None,
374404
Platform.SENSOR.value: validate_sensor_state_and_device_class_config,
405+
Platform.SWITCH.value: None,
375406
}
376407

377408
MQTT_DEVICE_SCHEMA = vol.Schema(

homeassistant/components/mqtt/strings.json

+10-1
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@
227227
"last_reset_value_template": "Last reset value template",
228228
"expire_after": "Expire after",
229229
"force_update": "Force update",
230+
"optimistic": "Optimistic",
230231
"retain": "Retain",
231232
"qos": "QoS"
232233
},
@@ -238,6 +239,7 @@
238239
"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 temaplate is set, the State class option must be Total. [Learn more..]({url}#last_reset_value_template)",
239240
"expire_after": "If set, it defines the number of seconds after the sensor’s state expires, if it’s not updated. After expiry, the sensor’s state becomes unavailable. Default the sensors state never expires. [Learn more..]({url}#expire_after)",
240241
"force_update": "Sends update events even if the value hasn’t changed. Useful if you want to have meaningful value graphs in history. [Learn more..]({url}#force_update)",
242+
"optimistic": "Flag that defines if the {platform} entity works in optimistic mode. [Learn more..]({url}#optimistic)",
241243
"retain": "Select if values published by the {platform} entity should be retained at the MQTT broker.",
242244
"qos": "The QoS value {platform} entity should use."
243245
}
@@ -434,10 +436,17 @@
434436
"wind_speed": "[%key:component::sensor::entity_component::wind_speed::name%]"
435437
}
436438
},
439+
"device_class_switch": {
440+
"options": {
441+
"outlet": "[%key:component::switch::entity_component::outlet::name%]",
442+
"switch": "[%key:component::switch::title%]"
443+
}
444+
},
437445
"platform": {
438446
"options": {
439447
"notify": "Notify",
440-
"sensor": "Sensor"
448+
"sensor": "Sensor",
449+
"switch": "Switch"
441450
}
442451
},
443452
"set_ca_cert": {

tests/components/mqtt/common.py

+27-1
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,20 @@
119119
"qos": 0,
120120
},
121121
}
122+
MOCK_SUBENTRY_SWITCH_COMPONENT = {
123+
"3faf1318016c46c5aea26707eeb6f12e": {
124+
"platform": "switch",
125+
"name": "Outlet",
126+
"device_class": "outlet",
127+
"qos": 0,
128+
"command_topic": "test-topic",
129+
"state_topic": "test-topic",
130+
"command_template": "{{ value }}",
131+
"value_template": "{{ value_json.value }}",
132+
"entity_picture": "https://example.com/3faf1318016c46c5aea26707eeb6f12e",
133+
"optimistic": True,
134+
},
135+
}
122136

123137
# Bogus light component just for code coverage
124138
# Note that light cannot be setup through the UI yet
@@ -207,6 +221,17 @@
207221
},
208222
"components": MOCK_SUBENTRY_SENSOR_COMPONENT_STATE_CLASS,
209223
}
224+
MOCK_SWITCH_SUBENTRY_DATA_SINGLE_STATE_CLASS = {
225+
"device": {
226+
"name": "Test switch",
227+
"sw_version": "1.0",
228+
"hw_version": "2.1 rev a",
229+
"model": "Model XL",
230+
"model_id": "mn002",
231+
"configuration_url": "https://example.com",
232+
},
233+
"components": MOCK_SUBENTRY_SWITCH_COMPONENT,
234+
}
210235

211236
MOCK_SUBENTRY_DATA_BAD_COMPONENT_SCHEMA = {
212237
"device": {
@@ -230,7 +255,8 @@
230255
},
231256
"components": MOCK_SUBENTRY_NOTIFY_COMPONENT1
232257
| MOCK_SUBENTRY_NOTIFY_COMPONENT2
233-
| MOCK_SUBENTRY_LIGHT_COMPONENT,
258+
| MOCK_SUBENTRY_LIGHT_COMPONENT
259+
| MOCK_SUBENTRY_SWITCH_COMPONENT,
234260
} | MOCK_SUBENTRY_AVAILABILITY_DATA
235261
_SENTINEL = object()
236262

tests/components/mqtt/test_config_flow.py

+30
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
MOCK_NOTIFY_SUBENTRY_DATA_SINGLE,
3939
MOCK_SENSOR_SUBENTRY_DATA_SINGLE,
4040
MOCK_SENSOR_SUBENTRY_DATA_SINGLE_STATE_CLASS,
41+
MOCK_SWITCH_SUBENTRY_DATA_SINGLE_STATE_CLASS,
4142
)
4243

4344
from tests.common import MockConfigEntry, MockMqttReasonCode
@@ -2725,12 +2726,41 @@ async def test_migrate_of_incompatible_config_entry(
27252726
),
27262727
"Test sensor Energy",
27272728
),
2729+
(
2730+
MOCK_SWITCH_SUBENTRY_DATA_SINGLE_STATE_CLASS,
2731+
{"name": "Outlet"},
2732+
{"device_class": "outlet"},
2733+
(),
2734+
{
2735+
"command_topic": "test-topic",
2736+
"command_template": "{{ value }}",
2737+
"state_topic": "test-topic",
2738+
"value_template": "{{ value_json.value }}",
2739+
"qos": 0,
2740+
"optimistic": True,
2741+
},
2742+
(
2743+
(
2744+
{"command_topic": "test-topic#invalid"},
2745+
{"command_topic": "invalid_publish_topic"},
2746+
),
2747+
(
2748+
{
2749+
"command_topic": "test-topic",
2750+
"state_topic": "test-topic#invalid",
2751+
},
2752+
{"state_topic": "invalid_subscribe_topic"},
2753+
),
2754+
),
2755+
"Test switch Outlet",
2756+
),
27282757
],
27292758
ids=[
27302759
"notify_with_entity_name",
27312760
"notify_no_entity_name",
27322761
"sensor_options",
27332762
"sensor_total",
2763+
"switch",
27342764
],
27352765
)
27362766
async def test_subentry_configflow(

0 commit comments

Comments
 (0)