Skip to content

Commit ab32f9f

Browse files
committed
Add switch as entity platform on MQTT subentries
1 parent 965cfa2 commit ab32f9f

File tree

4 files changed

+99
-4
lines changed

4 files changed

+99
-4
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:
@@ -344,6 +355,9 @@ def unit_of_measurement_selector(user_data: dict[str, Any | None]) -> Selector:
344355
),
345356
CONF_OPTIONS: PlatformField(OPTIONS_SELECTOR, False, cv.ensure_list),
346357
},
358+
Platform.SWITCH.value: {
359+
CONF_DEVICE_CLASS: PlatformField(SWITCH_DEVICE_CLASS_SELECTOR, False, str),
360+
},
347361
}
348362
PLATFORM_MQTT_FIELDS = {
349363
Platform.NOTIFY.value: {
@@ -371,12 +385,29 @@ def unit_of_measurement_selector(user_data: dict[str, Any | None]) -> Selector:
371385
),
372386
CONF_EXPIRE_AFTER: PlatformField(EXIRE_AFTER_SELECTOR, False, cv.positive_int),
373387
},
388+
Platform.SWITCH.value: {
389+
CONF_COMMAND_TOPIC: PlatformField(
390+
TEXT_SELECTOR, True, valid_publish_topic, "invalid_publish_topic"
391+
),
392+
CONF_COMMAND_TEMPLATE: PlatformField(
393+
TEMPLATE_SELECTOR, False, cv.template, "invalid_template"
394+
),
395+
CONF_STATE_TOPIC: PlatformField(
396+
TEXT_SELECTOR, False, valid_subscribe_topic, "invalid_subscribe_topic"
397+
),
398+
CONF_VALUE_TEMPLATE: PlatformField(
399+
TEMPLATE_SELECTOR, False, cv.template, "invalid_template"
400+
),
401+
CONF_RETAIN: PlatformField(BOOLEAN_SELECTOR, False, bool),
402+
CONF_OPTIMISTIC: PlatformField(BOOLEAN_SELECTOR, False, bool),
403+
},
374404
}
375405
ENTITY_CONFIG_VALIDATOR: dict[
376406
str, Callable[[dict[str, Any], dict[str, str]], dict[str, Any]] | None
377407
] = {
378408
Platform.NOTIFY.value: None,
379409
Platform.SENSOR.value: validate_sensor_state_and_device_class_config,
410+
Platform.SWITCH.value: None,
380411
}
381412

382413
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 template 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
}
@@ -433,10 +435,17 @@
433435
"wind_speed": "[%key:component::sensor::entity_component::wind_speed::name%]"
434436
}
435437
},
438+
"device_class_switch": {
439+
"options": {
440+
"outlet": "[%key:component::switch::entity_component::outlet::name%]",
441+
"switch": "[%key:component::switch::title%]"
442+
}
443+
},
436444
"platform": {
437445
"options": {
438446
"notify": "Notify",
439-
"sensor": "Sensor"
447+
"sensor": "Sensor",
448+
"switch": "Switch"
440449
}
441450
},
442451
"set_ca_cert": {

tests/components/mqtt/common.py

+27-2
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,20 @@
130130
"qos": 0,
131131
},
132132
}
133+
MOCK_SUBENTRY_SWITCH_COMPONENT = {
134+
"3faf1318016c46c5aea26707eeb6f12e": {
135+
"platform": "switch",
136+
"name": "Outlet",
137+
"device_class": "outlet",
138+
"qos": 0,
139+
"command_topic": "test-topic",
140+
"state_topic": "test-topic",
141+
"command_template": "{{ value }}",
142+
"value_template": "{{ value_json.value }}",
143+
"entity_picture": "https://example.com/3faf1318016c46c5aea26707eeb6f12e",
144+
"optimistic": True,
145+
},
146+
}
133147

134148
# Bogus light component just for code coverage
135149
# Note that light cannot be setup through the UI yet
@@ -229,7 +243,17 @@
229243
},
230244
"components": MOCK_SUBENTRY_SENSOR_COMPONENT_LAST_RESET,
231245
}
232-
246+
MOCK_SWITCH_SUBENTRY_DATA_SINGLE_STATE_CLASS = {
247+
"device": {
248+
"name": "Test switch",
249+
"sw_version": "1.0",
250+
"hw_version": "2.1 rev a",
251+
"model": "Model XL",
252+
"model_id": "mn002",
253+
"configuration_url": "https://example.com",
254+
},
255+
"components": MOCK_SUBENTRY_SWITCH_COMPONENT,
256+
}
233257
MOCK_SUBENTRY_DATA_BAD_COMPONENT_SCHEMA = {
234258
"device": {
235259
"name": "Milk notifier",
@@ -252,7 +276,8 @@
252276
},
253277
"components": MOCK_SUBENTRY_NOTIFY_COMPONENT1
254278
| MOCK_SUBENTRY_NOTIFY_COMPONENT2
255-
| MOCK_SUBENTRY_LIGHT_COMPONENT,
279+
| MOCK_SUBENTRY_LIGHT_COMPONENT
280+
| MOCK_SUBENTRY_SWITCH_COMPONENT,
256281
} | MOCK_SUBENTRY_AVAILABILITY_DATA
257282
_SENTINEL = object()
258283

tests/components/mqtt/test_config_flow.py

+30
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
MOCK_SENSOR_SUBENTRY_DATA_SINGLE,
4040
MOCK_SENSOR_SUBENTRY_DATA_SINGLE_LAST_RESET_TEMPLATE,
4141
MOCK_SENSOR_SUBENTRY_DATA_SINGLE_STATE_CLASS,
42+
MOCK_SWITCH_SUBENTRY_DATA_SINGLE_STATE_CLASS,
4243
)
4344

4445
from tests.common import MockConfigEntry, MockMqttReasonCode
@@ -2716,12 +2717,41 @@ async def test_migrate_of_incompatible_config_entry(
27162717
(),
27172718
"Test sensor Energy",
27182719
),
2720+
(
2721+
MOCK_SWITCH_SUBENTRY_DATA_SINGLE_STATE_CLASS,
2722+
{"name": "Outlet"},
2723+
{"device_class": "outlet"},
2724+
(),
2725+
{
2726+
"command_topic": "test-topic",
2727+
"command_template": "{{ value }}",
2728+
"state_topic": "test-topic",
2729+
"value_template": "{{ value_json.value }}",
2730+
"qos": 0,
2731+
"optimistic": True,
2732+
},
2733+
(
2734+
(
2735+
{"command_topic": "test-topic#invalid"},
2736+
{"command_topic": "invalid_publish_topic"},
2737+
),
2738+
(
2739+
{
2740+
"command_topic": "test-topic",
2741+
"state_topic": "test-topic#invalid",
2742+
},
2743+
{"state_topic": "invalid_subscribe_topic"},
2744+
),
2745+
),
2746+
"Test switch Outlet",
2747+
),
27192748
],
27202749
ids=[
27212750
"notify_with_entity_name",
27222751
"notify_no_entity_name",
27232752
"sensor_options",
27242753
"sensor_total",
2754+
"switch",
27252755
],
27262756
)
27272757
async def test_subentry_configflow(

0 commit comments

Comments
 (0)