mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Add turn on/off support for mqtt water_heater (#97197)
This commit is contained in:
parent
6c43ce69d3
commit
fb00cd8963
@ -64,6 +64,8 @@ from .const import (
|
|||||||
CONF_MODE_LIST,
|
CONF_MODE_LIST,
|
||||||
CONF_MODE_STATE_TEMPLATE,
|
CONF_MODE_STATE_TEMPLATE,
|
||||||
CONF_MODE_STATE_TOPIC,
|
CONF_MODE_STATE_TOPIC,
|
||||||
|
CONF_POWER_COMMAND_TEMPLATE,
|
||||||
|
CONF_POWER_COMMAND_TOPIC,
|
||||||
CONF_PRECISION,
|
CONF_PRECISION,
|
||||||
CONF_QOS,
|
CONF_QOS,
|
||||||
CONF_RETAIN,
|
CONF_RETAIN,
|
||||||
@ -113,9 +115,6 @@ CONF_HUMIDITY_MIN = "min_humidity"
|
|||||||
# was removed in HA Core 2023.8
|
# was removed in HA Core 2023.8
|
||||||
CONF_POWER_STATE_TEMPLATE = "power_state_template"
|
CONF_POWER_STATE_TEMPLATE = "power_state_template"
|
||||||
CONF_POWER_STATE_TOPIC = "power_state_topic"
|
CONF_POWER_STATE_TOPIC = "power_state_topic"
|
||||||
|
|
||||||
CONF_POWER_COMMAND_TOPIC = "power_command_topic"
|
|
||||||
CONF_POWER_COMMAND_TEMPLATE = "power_command_template"
|
|
||||||
CONF_PRESET_MODE_STATE_TOPIC = "preset_mode_state_topic"
|
CONF_PRESET_MODE_STATE_TOPIC = "preset_mode_state_topic"
|
||||||
CONF_PRESET_MODE_COMMAND_TOPIC = "preset_mode_command_topic"
|
CONF_PRESET_MODE_COMMAND_TOPIC = "preset_mode_command_topic"
|
||||||
CONF_PRESET_MODE_VALUE_TEMPLATE = "preset_mode_value_template"
|
CONF_PRESET_MODE_VALUE_TEMPLATE = "preset_mode_value_template"
|
||||||
|
@ -40,6 +40,8 @@ CONF_MODE_COMMAND_TOPIC = "mode_command_topic"
|
|||||||
CONF_MODE_LIST = "modes"
|
CONF_MODE_LIST = "modes"
|
||||||
CONF_MODE_STATE_TEMPLATE = "mode_state_template"
|
CONF_MODE_STATE_TEMPLATE = "mode_state_template"
|
||||||
CONF_MODE_STATE_TOPIC = "mode_state_topic"
|
CONF_MODE_STATE_TOPIC = "mode_state_topic"
|
||||||
|
CONF_POWER_COMMAND_TOPIC = "power_command_topic"
|
||||||
|
CONF_POWER_COMMAND_TEMPLATE = "power_command_template"
|
||||||
CONF_PRECISION = "precision"
|
CONF_PRECISION = "precision"
|
||||||
CONF_TEMP_COMMAND_TEMPLATE = "temperature_command_template"
|
CONF_TEMP_COMMAND_TEMPLATE = "temperature_command_template"
|
||||||
CONF_TEMP_COMMAND_TOPIC = "temperature_command_topic"
|
CONF_TEMP_COMMAND_TOPIC = "temperature_command_topic"
|
||||||
|
@ -51,6 +51,8 @@ from .const import (
|
|||||||
CONF_MODE_LIST,
|
CONF_MODE_LIST,
|
||||||
CONF_MODE_STATE_TEMPLATE,
|
CONF_MODE_STATE_TEMPLATE,
|
||||||
CONF_MODE_STATE_TOPIC,
|
CONF_MODE_STATE_TOPIC,
|
||||||
|
CONF_POWER_COMMAND_TEMPLATE,
|
||||||
|
CONF_POWER_COMMAND_TOPIC,
|
||||||
CONF_PRECISION,
|
CONF_PRECISION,
|
||||||
CONF_RETAIN,
|
CONF_RETAIN,
|
||||||
CONF_TEMP_COMMAND_TEMPLATE,
|
CONF_TEMP_COMMAND_TEMPLATE,
|
||||||
@ -91,6 +93,7 @@ VALUE_TEMPLATE_KEYS = (
|
|||||||
COMMAND_TEMPLATE_KEYS = {
|
COMMAND_TEMPLATE_KEYS = {
|
||||||
CONF_MODE_COMMAND_TEMPLATE,
|
CONF_MODE_COMMAND_TEMPLATE,
|
||||||
CONF_TEMP_COMMAND_TEMPLATE,
|
CONF_TEMP_COMMAND_TEMPLATE,
|
||||||
|
CONF_POWER_COMMAND_TEMPLATE,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -98,6 +101,7 @@ TOPIC_KEYS = (
|
|||||||
CONF_CURRENT_TEMP_TOPIC,
|
CONF_CURRENT_TEMP_TOPIC,
|
||||||
CONF_MODE_COMMAND_TOPIC,
|
CONF_MODE_COMMAND_TOPIC,
|
||||||
CONF_MODE_STATE_TOPIC,
|
CONF_MODE_STATE_TOPIC,
|
||||||
|
CONF_POWER_COMMAND_TOPIC,
|
||||||
CONF_TEMP_COMMAND_TOPIC,
|
CONF_TEMP_COMMAND_TOPIC,
|
||||||
CONF_TEMP_STATE_TOPIC,
|
CONF_TEMP_STATE_TOPIC,
|
||||||
)
|
)
|
||||||
@ -127,6 +131,8 @@ _PLATFORM_SCHEMA_BASE = MQTT_BASE_SCHEMA.extend(
|
|||||||
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
|
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
|
||||||
vol.Optional(CONF_PAYLOAD_ON, default="ON"): cv.string,
|
vol.Optional(CONF_PAYLOAD_ON, default="ON"): cv.string,
|
||||||
vol.Optional(CONF_PAYLOAD_OFF, default="OFF"): cv.string,
|
vol.Optional(CONF_PAYLOAD_OFF, default="OFF"): cv.string,
|
||||||
|
vol.Optional(CONF_POWER_COMMAND_TOPIC): valid_publish_topic,
|
||||||
|
vol.Optional(CONF_POWER_COMMAND_TEMPLATE): cv.template,
|
||||||
vol.Optional(CONF_PRECISION): vol.In(
|
vol.Optional(CONF_PRECISION): vol.In(
|
||||||
[PRECISION_TENTHS, PRECISION_HALVES, PRECISION_WHOLE]
|
[PRECISION_TENTHS, PRECISION_HALVES, PRECISION_WHOLE]
|
||||||
),
|
),
|
||||||
@ -266,6 +272,9 @@ class MqttWaterHeater(MqttTemperatureControlEntity, WaterHeaterEntity):
|
|||||||
):
|
):
|
||||||
support |= WaterHeaterEntityFeature.OPERATION_MODE
|
support |= WaterHeaterEntityFeature.OPERATION_MODE
|
||||||
|
|
||||||
|
if self._topic[CONF_POWER_COMMAND_TOPIC] is not None:
|
||||||
|
support |= WaterHeaterEntityFeature.ON_OFF
|
||||||
|
|
||||||
self._attr_supported_features = support
|
self._attr_supported_features = support
|
||||||
|
|
||||||
def _prepare_subscribe_topics(self) -> None:
|
def _prepare_subscribe_topics(self) -> None:
|
||||||
@ -317,3 +326,19 @@ class MqttWaterHeater(MqttTemperatureControlEntity, WaterHeaterEntity):
|
|||||||
if self._optimistic or self._topic[CONF_MODE_STATE_TOPIC] is None:
|
if self._optimistic or self._topic[CONF_MODE_STATE_TOPIC] is None:
|
||||||
self._attr_current_operation = operation_mode
|
self._attr_current_operation = operation_mode
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn the entity on."""
|
||||||
|
if CONF_POWER_COMMAND_TOPIC in self._config:
|
||||||
|
mqtt_payload = self._command_templates[CONF_POWER_COMMAND_TEMPLATE](
|
||||||
|
self._config[CONF_PAYLOAD_ON]
|
||||||
|
)
|
||||||
|
await self._publish(CONF_POWER_COMMAND_TOPIC, mqtt_payload)
|
||||||
|
|
||||||
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn the entity off."""
|
||||||
|
if CONF_POWER_COMMAND_TOPIC in self._config:
|
||||||
|
mqtt_payload = self._command_templates[CONF_POWER_COMMAND_TEMPLATE](
|
||||||
|
self._config[CONF_PAYLOAD_OFF]
|
||||||
|
)
|
||||||
|
await self._publish(CONF_POWER_COMMAND_TOPIC, mqtt_payload)
|
||||||
|
@ -257,6 +257,91 @@ async def test_set_operation_optimistic(
|
|||||||
assert state.state == "performance"
|
assert state.state == "performance"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hass_config",
|
||||||
|
[
|
||||||
|
help_custom_config(
|
||||||
|
water_heater.DOMAIN,
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
({"power_command_topic": "power-command"},),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_set_operation_with_power_command(
|
||||||
|
hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator
|
||||||
|
) -> None:
|
||||||
|
"""Test setting of new operation mode with power command enabled."""
|
||||||
|
mqtt_mock = await mqtt_mock_entry()
|
||||||
|
|
||||||
|
state = hass.states.get(ENTITY_WATER_HEATER)
|
||||||
|
assert state.state == "off"
|
||||||
|
await common.async_set_operation_mode(hass, "electric", ENTITY_WATER_HEATER)
|
||||||
|
state = hass.states.get(ENTITY_WATER_HEATER)
|
||||||
|
assert state.state == "electric"
|
||||||
|
mqtt_mock.async_publish.assert_has_calls([call("mode-topic", "electric", 0, False)])
|
||||||
|
mqtt_mock.async_publish.reset_mock()
|
||||||
|
|
||||||
|
await common.async_set_operation_mode(hass, "off", ENTITY_WATER_HEATER)
|
||||||
|
state = hass.states.get(ENTITY_WATER_HEATER)
|
||||||
|
assert state.state == "off"
|
||||||
|
mqtt_mock.async_publish.assert_has_calls([call("mode-topic", "off", 0, False)])
|
||||||
|
mqtt_mock.async_publish.reset_mock()
|
||||||
|
|
||||||
|
await common.async_turn_on(hass, ENTITY_WATER_HEATER)
|
||||||
|
# the water heater is not updated optimistically as this is not supported
|
||||||
|
mqtt_mock.async_publish.assert_has_calls([call("power-command", "ON", 0, False)])
|
||||||
|
mqtt_mock.async_publish.reset_mock()
|
||||||
|
|
||||||
|
await common.async_turn_off(hass, ENTITY_WATER_HEATER)
|
||||||
|
mqtt_mock.async_publish.assert_has_calls([call("power-command", "OFF", 0, False)])
|
||||||
|
mqtt_mock.async_publish.reset_mock()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hass_config",
|
||||||
|
[
|
||||||
|
help_custom_config(
|
||||||
|
water_heater.DOMAIN,
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
({"power_command_topic": "power-command", "optimistic": True},),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_turn_on_and_off_optimistic_with_power_command(
|
||||||
|
hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator
|
||||||
|
) -> None:
|
||||||
|
"""Test setting of turn on/off with power command enabled."""
|
||||||
|
mqtt_mock = await mqtt_mock_entry()
|
||||||
|
|
||||||
|
state = hass.states.get(ENTITY_WATER_HEATER)
|
||||||
|
assert state.state == "off"
|
||||||
|
await common.async_set_operation_mode(hass, "electric", ENTITY_WATER_HEATER)
|
||||||
|
state = hass.states.get(ENTITY_WATER_HEATER)
|
||||||
|
assert state.state == "electric"
|
||||||
|
mqtt_mock.async_publish.assert_has_calls([call("mode-topic", "electric", 0, False)])
|
||||||
|
mqtt_mock.async_publish.reset_mock()
|
||||||
|
await common.async_set_operation_mode(hass, "off", ENTITY_WATER_HEATER)
|
||||||
|
state = hass.states.get(ENTITY_WATER_HEATER)
|
||||||
|
assert state.state == "off"
|
||||||
|
|
||||||
|
await common.async_turn_on(hass, ENTITY_WATER_HEATER)
|
||||||
|
# the water heater is not updated optimistically as this is not supported
|
||||||
|
state = hass.states.get(ENTITY_WATER_HEATER)
|
||||||
|
assert state.state == "off"
|
||||||
|
mqtt_mock.async_publish.assert_has_calls([call("power-command", "ON", 0, False)])
|
||||||
|
mqtt_mock.async_publish.reset_mock()
|
||||||
|
|
||||||
|
await common.async_set_operation_mode(hass, "gas", ENTITY_WATER_HEATER)
|
||||||
|
state = hass.states.get(ENTITY_WATER_HEATER)
|
||||||
|
assert state.state == "gas"
|
||||||
|
await common.async_turn_off(hass, ENTITY_WATER_HEATER)
|
||||||
|
# the water heater is not updated optimistically as this is not supported
|
||||||
|
state = hass.states.get(ENTITY_WATER_HEATER)
|
||||||
|
assert state.state == "gas"
|
||||||
|
mqtt_mock.async_publish.assert_has_calls([call("power-command", "OFF", 0, False)])
|
||||||
|
mqtt_mock.async_publish.reset_mock()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("hass_config", [DEFAULT_CONFIG])
|
@pytest.mark.parametrize("hass_config", [DEFAULT_CONFIG])
|
||||||
async def test_set_target_temperature(
|
async def test_set_target_temperature(
|
||||||
hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator
|
hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator
|
||||||
@ -509,9 +594,11 @@ async def test_get_with_templates(
|
|||||||
"name": "test",
|
"name": "test",
|
||||||
"mode_command_topic": "mode-topic",
|
"mode_command_topic": "mode-topic",
|
||||||
"temperature_command_topic": "temperature-topic",
|
"temperature_command_topic": "temperature-topic",
|
||||||
|
"power_command_topic": "power-topic",
|
||||||
# Create simple templates
|
# Create simple templates
|
||||||
"mode_command_template": "mode: {{ value }}",
|
"mode_command_template": "mode: {{ value }}",
|
||||||
"temperature_command_template": "temp: {{ value }}",
|
"temperature_command_template": "temp: {{ value }}",
|
||||||
|
"power_command_template": "pwr: {{ value }}",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -544,6 +631,14 @@ async def test_set_and_templates(
|
|||||||
state = hass.states.get(ENTITY_WATER_HEATER)
|
state = hass.states.get(ENTITY_WATER_HEATER)
|
||||||
assert state.attributes.get("temperature") == 107
|
assert state.attributes.get("temperature") == 107
|
||||||
|
|
||||||
|
# Power
|
||||||
|
await common.async_turn_on(hass, entity_id=ENTITY_WATER_HEATER)
|
||||||
|
mqtt_mock.async_publish.assert_called_once_with("power-topic", "pwr: ON", 0, False)
|
||||||
|
mqtt_mock.async_publish.reset_mock()
|
||||||
|
await common.async_turn_off(hass, entity_id=ENTITY_WATER_HEATER)
|
||||||
|
mqtt_mock.async_publish.assert_called_once_with("power-topic", "pwr: OFF", 0, False)
|
||||||
|
mqtt_mock.async_publish.reset_mock()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"hass_config",
|
"hass_config",
|
||||||
@ -1047,6 +1142,20 @@ async def test_precision_whole(
|
|||||||
20.1,
|
20.1,
|
||||||
"temperature_command_template",
|
"temperature_command_template",
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
water_heater.SERVICE_TURN_ON,
|
||||||
|
"power_command_topic",
|
||||||
|
{},
|
||||||
|
"ON",
|
||||||
|
"power_command_template",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
water_heater.SERVICE_TURN_OFF,
|
||||||
|
"power_command_topic",
|
||||||
|
{},
|
||||||
|
"OFF",
|
||||||
|
"power_command_template",
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_publishing_with_custom_encoding(
|
async def test_publishing_with_custom_encoding(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user