Add turn on/off support for mqtt water_heater (#97197)

pull/97210/head
Jan Bouwhuis 2023-07-25 13:33:02 +02:00 committed by GitHub
parent 6c43ce69d3
commit fb00cd8963
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 138 additions and 3 deletions

View File

@ -64,6 +64,8 @@ from .const import (
CONF_MODE_LIST,
CONF_MODE_STATE_TEMPLATE,
CONF_MODE_STATE_TOPIC,
CONF_POWER_COMMAND_TEMPLATE,
CONF_POWER_COMMAND_TOPIC,
CONF_PRECISION,
CONF_QOS,
CONF_RETAIN,
@ -113,9 +115,6 @@ CONF_HUMIDITY_MIN = "min_humidity"
# was removed in HA Core 2023.8
CONF_POWER_STATE_TEMPLATE = "power_state_template"
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_COMMAND_TOPIC = "preset_mode_command_topic"
CONF_PRESET_MODE_VALUE_TEMPLATE = "preset_mode_value_template"

View File

@ -40,6 +40,8 @@ CONF_MODE_COMMAND_TOPIC = "mode_command_topic"
CONF_MODE_LIST = "modes"
CONF_MODE_STATE_TEMPLATE = "mode_state_template"
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_TEMP_COMMAND_TEMPLATE = "temperature_command_template"
CONF_TEMP_COMMAND_TOPIC = "temperature_command_topic"

View File

@ -51,6 +51,8 @@ from .const import (
CONF_MODE_LIST,
CONF_MODE_STATE_TEMPLATE,
CONF_MODE_STATE_TOPIC,
CONF_POWER_COMMAND_TEMPLATE,
CONF_POWER_COMMAND_TOPIC,
CONF_PRECISION,
CONF_RETAIN,
CONF_TEMP_COMMAND_TEMPLATE,
@ -91,6 +93,7 @@ VALUE_TEMPLATE_KEYS = (
COMMAND_TEMPLATE_KEYS = {
CONF_MODE_COMMAND_TEMPLATE,
CONF_TEMP_COMMAND_TEMPLATE,
CONF_POWER_COMMAND_TEMPLATE,
}
@ -98,6 +101,7 @@ TOPIC_KEYS = (
CONF_CURRENT_TEMP_TOPIC,
CONF_MODE_COMMAND_TOPIC,
CONF_MODE_STATE_TOPIC,
CONF_POWER_COMMAND_TOPIC,
CONF_TEMP_COMMAND_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_PAYLOAD_ON, default="ON"): 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(
[PRECISION_TENTHS, PRECISION_HALVES, PRECISION_WHOLE]
),
@ -266,6 +272,9 @@ class MqttWaterHeater(MqttTemperatureControlEntity, WaterHeaterEntity):
):
support |= WaterHeaterEntityFeature.OPERATION_MODE
if self._topic[CONF_POWER_COMMAND_TOPIC] is not None:
support |= WaterHeaterEntityFeature.ON_OFF
self._attr_supported_features = support
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:
self._attr_current_operation = operation_mode
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)

View File

@ -257,6 +257,91 @@ async def test_set_operation_optimistic(
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])
async def test_set_target_temperature(
hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator
@ -509,9 +594,11 @@ async def test_get_with_templates(
"name": "test",
"mode_command_topic": "mode-topic",
"temperature_command_topic": "temperature-topic",
"power_command_topic": "power-topic",
# Create simple templates
"mode_command_template": "mode: {{ 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)
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(
"hass_config",
@ -1047,6 +1142,20 @@ async def test_precision_whole(
20.1,
"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(