Skip to content

Commit e18668b

Browse files
authored
Add MQTT water heater subentry support (home-assistant#157182)
1 parent 15647f2 commit e18668b

File tree

5 files changed

+350
-10
lines changed

5 files changed

+350
-10
lines changed

homeassistant/components/mqtt/config_flow.py

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,7 @@
470470
Platform.SWITCH,
471471
Platform.TEXT,
472472
Platform.VALVE,
473+
Platform.WATER_HEATER,
473474
]
474475

475476
_CODE_VALIDATION_MODE = {
@@ -844,6 +845,21 @@
844845
VALVE_POSITION_SELECTOR = NumberSelector(
845846
NumberSelectorConfig(mode=NumberSelectorMode.BOX, step=1)
846847
)
848+
WATER_HEATER_MODE_SELECTOR = SelectSelector(
849+
SelectSelectorConfig(
850+
options=[
851+
"off",
852+
"eco",
853+
"electric",
854+
"gas",
855+
"heat_pump",
856+
"high_demand",
857+
"performance",
858+
],
859+
multiple=True,
860+
translation_key="water_heater_modes",
861+
)
862+
)
847863

848864

849865
@callback
@@ -1192,6 +1208,16 @@ def validate_text_platform_config(
11921208
return errors
11931209

11941210

1211+
@callback
1212+
def validate_water_heater_platform_config(config: dict[str, Any]) -> dict[str, str]:
1213+
"""Validate the water heater platform options."""
1214+
errors: dict[str, str] = {}
1215+
if CONF_TEMP_MIN in config and config[CONF_TEMP_MIN] >= config[CONF_TEMP_MAX]:
1216+
errors["target_temperature_settings"] = "max_below_min_temperature"
1217+
1218+
return errors
1219+
1220+
11951221
ENTITY_CONFIG_VALIDATOR: dict[
11961222
str,
11971223
Callable[[dict[str, Any]], dict[str, str]] | None,
@@ -1213,6 +1239,7 @@ def validate_text_platform_config(
12131239
Platform.SWITCH: None,
12141240
Platform.TEXT: validate_text_platform_config,
12151241
Platform.VALVE: None,
1242+
Platform.WATER_HEATER: validate_water_heater_platform_config,
12161243
}
12171244

12181245

@@ -1484,6 +1511,30 @@ class PlatformField:
14841511
default=False,
14851512
),
14861513
},
1514+
Platform.WATER_HEATER: {
1515+
CONF_TEMPERATURE_UNIT: PlatformField(
1516+
selector=TEMPERATURE_UNIT_SELECTOR,
1517+
validator=validate(cv.temperature_unit),
1518+
required=True,
1519+
exclude_from_reconfig=True,
1520+
default=lambda _: "C"
1521+
if async_get_hass().config.units.temperature_unit
1522+
is UnitOfTemperature.CELSIUS
1523+
else "F",
1524+
),
1525+
"water_heater_feature_current_temperature": PlatformField(
1526+
selector=BOOLEAN_SELECTOR,
1527+
required=False,
1528+
exclude_from_config=True,
1529+
default=lambda config: bool(config.get(CONF_CURRENT_TEMP_TOPIC)),
1530+
),
1531+
"water_heater_feature_power": PlatformField(
1532+
selector=BOOLEAN_SELECTOR,
1533+
required=False,
1534+
exclude_from_config=True,
1535+
default=lambda config: bool(config.get(CONF_POWER_COMMAND_TOPIC)),
1536+
),
1537+
},
14871538
}
14881539
PLATFORM_MQTT_FIELDS: dict[Platform, dict[str, PlatformField]] = {
14891540
Platform.ALARM_CONTROL_PANEL: {
@@ -3489,6 +3540,150 @@ class PlatformField:
34893540
CONF_RETAIN: PlatformField(selector=BOOLEAN_SELECTOR, required=False),
34903541
CONF_OPTIMISTIC: PlatformField(selector=BOOLEAN_SELECTOR, required=False),
34913542
},
3543+
Platform.WATER_HEATER: {
3544+
# operation mode settings
3545+
CONF_MODE_COMMAND_TOPIC: PlatformField(
3546+
selector=TEXT_SELECTOR,
3547+
required=False,
3548+
validator=valid_publish_topic,
3549+
error="invalid_publish_topic",
3550+
),
3551+
CONF_MODE_COMMAND_TEMPLATE: PlatformField(
3552+
selector=TEMPLATE_SELECTOR,
3553+
required=False,
3554+
validator=validate(cv.template),
3555+
error="invalid_template",
3556+
),
3557+
CONF_MODE_STATE_TOPIC: PlatformField(
3558+
selector=TEXT_SELECTOR,
3559+
required=False,
3560+
validator=valid_subscribe_topic,
3561+
error="invalid_subscribe_topic",
3562+
),
3563+
CONF_MODE_STATE_TEMPLATE: PlatformField(
3564+
selector=TEMPLATE_SELECTOR,
3565+
required=False,
3566+
validator=validate(cv.template),
3567+
error="invalid_template",
3568+
),
3569+
CONF_MODE_LIST: PlatformField(
3570+
selector=WATER_HEATER_MODE_SELECTOR,
3571+
required=True,
3572+
default=[],
3573+
validator=validate(no_empty_list),
3574+
error="empty_list_not_allowed",
3575+
),
3576+
CONF_RETAIN: PlatformField(
3577+
selector=BOOLEAN_SELECTOR, required=False, validator=validate(bool)
3578+
),
3579+
CONF_OPTIMISTIC: PlatformField(
3580+
selector=BOOLEAN_SELECTOR, required=False, validator=validate(bool)
3581+
),
3582+
# target temperature settings
3583+
CONF_TEMP_COMMAND_TOPIC: PlatformField(
3584+
selector=TEXT_SELECTOR,
3585+
required=True,
3586+
validator=valid_publish_topic,
3587+
error="invalid_publish_topic",
3588+
section="target_temperature_settings",
3589+
),
3590+
CONF_TEMP_COMMAND_TEMPLATE: PlatformField(
3591+
selector=TEMPLATE_SELECTOR,
3592+
required=False,
3593+
validator=validate(cv.template),
3594+
error="invalid_template",
3595+
section="target_temperature_settings",
3596+
),
3597+
CONF_TEMP_STATE_TOPIC: PlatformField(
3598+
selector=TEXT_SELECTOR,
3599+
required=False,
3600+
validator=valid_subscribe_topic,
3601+
error="invalid_subscribe_topic",
3602+
section="target_temperature_settings",
3603+
),
3604+
CONF_TEMP_STATE_TEMPLATE: PlatformField(
3605+
selector=TEMPLATE_SELECTOR,
3606+
required=False,
3607+
validator=validate(cv.template),
3608+
error="invalid_template",
3609+
section="target_temperature_settings",
3610+
),
3611+
CONF_TEMP_MIN: PlatformField(
3612+
selector=temperature_selector,
3613+
custom_filtering=True,
3614+
required=True,
3615+
default=temperature_default_from_celsius_to_system_default(43.3),
3616+
section="target_temperature_settings",
3617+
),
3618+
CONF_TEMP_MAX: PlatformField(
3619+
selector=temperature_selector,
3620+
custom_filtering=True,
3621+
required=True,
3622+
default=temperature_default_from_celsius_to_system_default(60),
3623+
section="target_temperature_settings",
3624+
),
3625+
CONF_PRECISION: PlatformField(
3626+
selector=PRECISION_SELECTOR,
3627+
required=False,
3628+
default=default_precision,
3629+
section="target_temperature_settings",
3630+
),
3631+
CONF_TEMP_INITIAL: PlatformField(
3632+
selector=temperature_selector,
3633+
custom_filtering=True,
3634+
required=False,
3635+
default=temperature_default_from_celsius_to_system_default(43.3),
3636+
section="target_temperature_settings",
3637+
),
3638+
# current temperature settings
3639+
CONF_CURRENT_TEMP_TOPIC: PlatformField(
3640+
selector=TEXT_SELECTOR,
3641+
required=False,
3642+
validator=valid_subscribe_topic,
3643+
error="invalid_subscribe_topic",
3644+
section="current_temperature_settings",
3645+
conditions=({"water_heater_feature_current_temperature": True},),
3646+
),
3647+
CONF_CURRENT_TEMP_TEMPLATE: PlatformField(
3648+
selector=TEMPLATE_SELECTOR,
3649+
required=False,
3650+
validator=validate(cv.template),
3651+
error="invalid_template",
3652+
section="current_temperature_settings",
3653+
conditions=({"water_heater_feature_current_temperature": True},),
3654+
),
3655+
# power on/off support
3656+
CONF_POWER_COMMAND_TOPIC: PlatformField(
3657+
selector=TEXT_SELECTOR,
3658+
required=False,
3659+
validator=valid_publish_topic,
3660+
error="invalid_publish_topic",
3661+
section="water_heater_power_settings",
3662+
conditions=({"water_heater_feature_power": True},),
3663+
),
3664+
CONF_POWER_COMMAND_TEMPLATE: PlatformField(
3665+
selector=TEMPLATE_SELECTOR,
3666+
required=False,
3667+
validator=validate(cv.template),
3668+
error="invalid_template",
3669+
section="water_heater_power_settings",
3670+
conditions=({"water_heater_feature_power": True},),
3671+
),
3672+
CONF_PAYLOAD_OFF: PlatformField(
3673+
selector=TEXT_SELECTOR,
3674+
required=False,
3675+
default=DEFAULT_PAYLOAD_OFF,
3676+
section="water_heater_power_settings",
3677+
conditions=({"water_heater_feature_power": True},),
3678+
),
3679+
CONF_PAYLOAD_ON: PlatformField(
3680+
selector=TEXT_SELECTOR,
3681+
required=False,
3682+
default=DEFAULT_PAYLOAD_ON,
3683+
section="water_heater_power_settings",
3684+
conditions=({"water_heater_feature_power": True},),
3685+
),
3686+
},
34923687
}
34933688
MQTT_DEVICE_PLATFORM_FIELDS = {
34943689
ATTR_NAME: PlatformField(selector=TEXT_SELECTOR, required=True),

homeassistant/components/mqtt/strings.json

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,9 @@
248248
"suggested_display_precision": "Suggested display precision",
249249
"supported_features": "Supported features",
250250
"temperature_unit": "Temperature unit",
251-
"unit_of_measurement": "Unit of measurement"
251+
"unit_of_measurement": "Unit of measurement",
252+
"water_heater_feature_current_temperature": "[%key:component::mqtt::config_subentries::device::step::entity_platform_config::data::climate_feature_current_temperature%]",
253+
"water_heater_feature_power": "[%key:component::mqtt::config_subentries::device::step::entity_platform_config::data::climate_feature_power%]"
252254
},
253255
"data_description": {
254256
"alarm_control_panel_code_mode": "Configures how the alarm control panel validates the code. A local code is configured with the entity and is validated by Home Assistant. A remote code is sent to the device and validated remotely. [Learn more.]({url}#code)",
@@ -275,8 +277,10 @@
275277
"state_class": "The [State class]({available_state_classes_url}) of the sensor. [Learn more.]({url}#state_class)",
276278
"suggested_display_precision": "The number of decimals which should be used in the {platform} entity state after rounding. [Learn more.]({url}#suggested_display_precision)",
277279
"supported_features": "The features that the entity supports.",
278-
"temperature_unit": "This determines the native unit of measurement the MQTT climate device works with.",
279-
"unit_of_measurement": "Defines the unit of measurement, if any."
280+
"temperature_unit": "This determines the native unit of measurement the MQTT device works with.",
281+
"unit_of_measurement": "Defines the unit of measurement, if any.",
282+
"water_heater_feature_current_temperature": "The water heater supports reporting the current temperature.",
283+
"water_heater_feature_power": "The water heater supports the power \"on\" and \"off\" commands."
280284
},
281285
"description": "Please configure specific details for {platform} entity \"{entity}\":",
282286
"sections": {
@@ -400,7 +404,7 @@
400404
"min": "Minimum value. [Learn more.]({url}#min)",
401405
"mode": "Control how the number should be displayed in the UI. [Learn more.]({url}#mode)",
402406
"mode_command_template": "[Template]({command_templating_url}) to define the operation mode to be sent to the operation mode command topic. [Learn more.]({url}#mode_command_template)",
403-
"mode_command_topic": "The MQTT topic to publish commands to change the climate operation mode. [Learn more.]({url}#mode_command_topic)",
407+
"mode_command_topic": "The MQTT topic to publish commands to change the operation mode. [Learn more.]({url}#mode_command_topic)",
404408
"mode_state_template": "Defines a [template]({value_templating_url}) to extract the operation mode state. [Learn more.]({url}#mode_state_template)",
405409
"mode_state_topic": "The MQTT topic subscribed to receive operation mode state messages. [Learn more.]({url}#mode_state_topic)",
406410
"modes": "A list of supported operation modes. [Learn more.]({url}#modes)",
@@ -957,13 +961,13 @@
957961
"temperature_state_topic": "Temperature state topic"
958962
},
959963
"data_description": {
960-
"initial": "The climate initializes with this target temperature.",
964+
"initial": "The device initializes with this target temperature.",
961965
"max_temp": "The maximum target temperature that can be set.",
962966
"min_temp": "The minimum target temperature that can be set.",
963967
"precision": "The precision in degrees the thermostat is working at.",
964968
"temp_step": "The target temperature step in degrees Celsius or Fahrenheit.",
965969
"temperature_command_template": "A [template]({command_templating_url}) to compose the payload to be published at the temperature command topic.",
966-
"temperature_command_topic": "The MQTT topic to publish commands to change the climate target temperature. [Learn more.]({url}#temperature_command_topic)",
970+
"temperature_command_topic": "The MQTT topic to publish commands to change the target temperature. [Learn more.]({url}#temperature_command_topic)",
967971
"temperature_high_command_template": "A [template]({command_templating_url}) to compose the payload to be published at the upper temperature command topic.",
968972
"temperature_high_command_topic": "The MQTT topic to publish commands to change the climate upper target temperature. [Learn more.]({url}#temperature_high_command_topic)",
969973
"temperature_high_state_template": "A [template]({value_templating_url}) to render the value received on the upper temperature state topic with.",
@@ -1012,6 +1016,21 @@
10121016
"state_opening": "The payload received at the state topic that represents the \"opening\" state."
10131017
},
10141018
"name": "Valve payload settings"
1019+
},
1020+
"water_heater_power_settings": {
1021+
"data": {
1022+
"payload_off": "[%key:component::mqtt::config_subentries::device::step::mqtt_platform_config::data::payload_off%]",
1023+
"payload_on": "[%key:component::mqtt::config_subentries::device::step::mqtt_platform_config::data::payload_on%]",
1024+
"power_command_template": "[%key:component::mqtt::config_subentries::device::step::mqtt_platform_config::sections::climate_power_settings::data::power_command_template%]",
1025+
"power_command_topic": "[%key:component::mqtt::config_subentries::device::step::mqtt_platform_config::sections::climate_power_settings::data::power_command_topic%]"
1026+
},
1027+
"data_description": {
1028+
"payload_off": "[%key:component::mqtt::config_subentries::device::step::mqtt_platform_config::data_description::payload_off%]",
1029+
"payload_on": "[%key:component::mqtt::config_subentries::device::step::mqtt_platform_config::data_description::payload_on%]",
1030+
"power_command_template": "[%key:component::mqtt::config_subentries::device::step::mqtt_platform_config::sections::climate_power_settings::data_description::power_command_template%]",
1031+
"power_command_topic": "The MQTT topic to publish commands to change the water heater power state. Sends the payload configured with payload \"on\" or payload \"off\". [Learn more.]({url}#power_command_topic)"
1032+
},
1033+
"name": "Power settings"
10151034
}
10161035
},
10171036
"title": "Configure MQTT device \"{mqtt_device}\""
@@ -1437,7 +1456,8 @@
14371456
"siren": "[%key:component::siren::title%]",
14381457
"switch": "[%key:component::switch::title%]",
14391458
"text": "[%key:component::text::title%]",
1440-
"valve": "[%key:component::valve::title%]"
1459+
"valve": "[%key:component::valve::title%]",
1460+
"water_heater": "[%key:component::water_heater::title%]"
14411461
}
14421462
},
14431463
"set_ca_cert": {
@@ -1480,6 +1500,18 @@
14801500
"password": "[%key:common::config_flow::data::password%]",
14811501
"text": "[%key:component::text::entity_component::_::state_attributes::mode::state::text%]"
14821502
}
1503+
},
1504+
"water_heater_modes": {
1505+
"options": {
1506+
"auto": "[%key:common::state::auto%]",
1507+
"eco": "[%key:component::water_heater::entity_component::_::state::eco%]",
1508+
"electric": "[%key:component::water_heater::entity_component::_::state::electric%]",
1509+
"gas": "[%key:component::water_heater::entity_component::_::state::gas%]",
1510+
"heat_pump": "[%key:component::water_heater::entity_component::_::state::heat_pump%]",
1511+
"high_demand": "[%key:component::water_heater::entity_component::_::state::high_demand%]",
1512+
"off": "[%key:common::state::off%]",
1513+
"performance": "[%key:component::water_heater::entity_component::_::state::performance%]"
1514+
}
14831515
}
14841516
},
14851517
"services": {

homeassistant/components/mqtt/water_heater.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,12 @@
136136
vol.Optional(CONF_PAYLOAD_OFF, default="OFF"): cv.string,
137137
vol.Optional(CONF_POWER_COMMAND_TOPIC): valid_publish_topic,
138138
vol.Optional(CONF_POWER_COMMAND_TEMPLATE): cv.template,
139-
vol.Optional(CONF_PRECISION): vol.In(
140-
[PRECISION_TENTHS, PRECISION_HALVES, PRECISION_WHOLE]
139+
vol.Optional(CONF_PRECISION): vol.All(
140+
vol.Coerce(float),
141+
vol.In([PRECISION_TENTHS, PRECISION_HALVES, PRECISION_WHOLE]),
141142
),
142143
vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
143-
vol.Optional(CONF_TEMP_INITIAL): cv.positive_int,
144+
vol.Optional(CONF_TEMP_INITIAL): vol.Coerce(float),
144145
vol.Optional(CONF_TEMP_MIN): vol.Coerce(float),
145146
vol.Optional(CONF_TEMP_MAX): vol.Coerce(float),
146147
vol.Optional(CONF_TEMP_COMMAND_TEMPLATE): cv.template,

0 commit comments

Comments
 (0)