1 """Support for MQTT water heater devices."""
3 from __future__
import annotations
6 from typing
import TYPE_CHECKING, Any
8 import voluptuous
as vol
21 WaterHeaterEntityFeature,
29 CONF_TEMPERATURE_UNIT,
44 from .climate
import MqttTemperatureControlEntity
45 from .config
import DEFAULT_RETAIN, MQTT_BASE_SCHEMA
47 CONF_CURRENT_TEMP_TEMPLATE,
48 CONF_CURRENT_TEMP_TOPIC,
49 CONF_MODE_COMMAND_TEMPLATE,
50 CONF_MODE_COMMAND_TOPIC,
52 CONF_MODE_STATE_TEMPLATE,
53 CONF_MODE_STATE_TOPIC,
54 CONF_POWER_COMMAND_TEMPLATE,
55 CONF_POWER_COMMAND_TOPIC,
58 CONF_TEMP_COMMAND_TEMPLATE,
59 CONF_TEMP_COMMAND_TOPIC,
63 CONF_TEMP_STATE_TEMPLATE,
64 CONF_TEMP_STATE_TOPIC,
68 from .entity
import async_setup_entity_entry_helper
69 from .models
import MqttCommandTemplate, MqttValueTemplate, ReceiveMessage
70 from .schemas
import MQTT_ENTITY_COMMON_SCHEMA
71 from .util
import valid_publish_topic, valid_subscribe_topic
73 _LOGGER = logging.getLogger(__name__)
77 DEFAULT_NAME =
"MQTT Water Heater"
79 MQTT_WATER_HEATER_ATTRIBUTES_BLOCKED = frozenset(
81 water_heater.ATTR_CURRENT_TEMPERATURE,
82 water_heater.ATTR_MAX_TEMP,
83 water_heater.ATTR_MIN_TEMP,
84 water_heater.ATTR_TEMPERATURE,
85 water_heater.ATTR_OPERATION_LIST,
86 water_heater.ATTR_OPERATION_MODE,
90 VALUE_TEMPLATE_KEYS = (
91 CONF_CURRENT_TEMP_TEMPLATE,
92 CONF_MODE_STATE_TEMPLATE,
93 CONF_TEMP_STATE_TEMPLATE,
96 COMMAND_TEMPLATE_KEYS = {
97 CONF_MODE_COMMAND_TEMPLATE,
98 CONF_TEMP_COMMAND_TEMPLATE,
99 CONF_POWER_COMMAND_TEMPLATE,
104 CONF_CURRENT_TEMP_TOPIC,
105 CONF_MODE_COMMAND_TOPIC,
106 CONF_MODE_STATE_TOPIC,
107 CONF_POWER_COMMAND_TOPIC,
108 CONF_TEMP_COMMAND_TOPIC,
109 CONF_TEMP_STATE_TOPIC,
113 _PLATFORM_SCHEMA_BASE = MQTT_BASE_SCHEMA.extend(
115 vol.Optional(CONF_CURRENT_TEMP_TEMPLATE): cv.template,
116 vol.Optional(CONF_CURRENT_TEMP_TOPIC): valid_subscribe_topic,
117 vol.Optional(CONF_MODE_COMMAND_TEMPLATE): cv.template,
118 vol.Optional(CONF_MODE_COMMAND_TOPIC): valid_publish_topic,
131 vol.Optional(CONF_MODE_STATE_TEMPLATE): cv.template,
132 vol.Optional(CONF_MODE_STATE_TOPIC): valid_subscribe_topic,
133 vol.Optional(CONF_NAME): vol.Any(cv.string,
None),
134 vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
135 vol.Optional(CONF_PAYLOAD_ON, default=
"ON"): cv.string,
136 vol.Optional(CONF_PAYLOAD_OFF, default=
"OFF"): cv.string,
137 vol.Optional(CONF_POWER_COMMAND_TOPIC): valid_publish_topic,
138 vol.Optional(CONF_POWER_COMMAND_TEMPLATE): cv.template,
139 vol.Optional(CONF_PRECISION): vol.In(
140 [PRECISION_TENTHS, PRECISION_HALVES, PRECISION_WHOLE]
142 vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
143 vol.Optional(CONF_TEMP_INITIAL): cv.positive_int,
144 vol.Optional(CONF_TEMP_MIN): vol.Coerce(float),
145 vol.Optional(CONF_TEMP_MAX): vol.Coerce(float),
146 vol.Optional(CONF_TEMP_COMMAND_TEMPLATE): cv.template,
147 vol.Optional(CONF_TEMP_COMMAND_TOPIC): valid_publish_topic,
148 vol.Optional(CONF_TEMP_STATE_TEMPLATE): cv.template,
149 vol.Optional(CONF_TEMP_STATE_TOPIC): valid_subscribe_topic,
150 vol.Optional(CONF_TEMPERATURE_UNIT): cv.temperature_unit,
151 vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
153 ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema)
155 PLATFORM_SCHEMA_MODERN = vol.All(
156 _PLATFORM_SCHEMA_BASE,
159 _DISCOVERY_SCHEMA_BASE = _PLATFORM_SCHEMA_BASE.extend({}, extra=vol.REMOVE_EXTRA)
161 DISCOVERY_SCHEMA = vol.All(
162 _DISCOVERY_SCHEMA_BASE,
168 config_entry: ConfigEntry,
169 async_add_entities: AddEntitiesCallback,
171 """Set up MQTT water heater device through YAML and through MQTT discovery."""
179 PLATFORM_SCHEMA_MODERN,
184 """Representation of an MQTT water heater device."""
186 _default_name = DEFAULT_NAME
187 _entity_id_format = water_heater.ENTITY_ID_FORMAT
188 _attributes_extra_blocked = MQTT_WATER_HEATER_ATTRIBUTES_BLOCKED
189 _attr_target_temperature_low: float |
None =
None
190 _attr_target_temperature_high: float |
None =
None
194 """Return the config schema."""
195 return DISCOVERY_SCHEMA
198 """(Re)Setup the entity."""
201 CONF_TEMPERATURE_UNIT, self.
hasshasshass.config.units.temperature_unit
203 if (min_temp := config.get(CONF_TEMP_MIN))
is not None:
205 if (max_temp := config.get(CONF_TEMP_MAX))
is not None:
207 if (precision := config.get(CONF_PRECISION))
is not None:
210 self.
_topic_topic = {key: config.get(key)
for key
in TOPIC_KEYS}
215 init_temp: float = config.get(
217 TemperatureConverter.convert(
219 UnitOfTemperature.FAHRENHEIT,
223 if self.
_topic_topic[CONF_TEMP_STATE_TOPIC]
is None or self.
_optimistic_optimistic:
225 if self.
_topic_topic[CONF_MODE_STATE_TOPIC]
is None or self.
_optimistic_optimistic:
228 value_templates: dict[str, Template |
None] = {
229 key: config.get(CONF_VALUE_TEMPLATE)
for key
in VALUE_TEMPLATE_KEYS
231 value_templates.update(
232 {key: config[key]
for key
in VALUE_TEMPLATE_KEYS & config.keys()}
236 template, entity=self
237 ).async_render_with_possible_json_value
238 for key, template
in value_templates.items()
243 for key
in COMMAND_TEMPLATE_KEYS
247 if (self.
_topic_topic[CONF_TEMP_STATE_TOPIC]
is not None)
or (
248 self.
_topic_topic[CONF_TEMP_COMMAND_TOPIC]
is not None
250 support |= WaterHeaterEntityFeature.TARGET_TEMPERATURE
252 if (self.
_topic_topic[CONF_MODE_STATE_TOPIC]
is not None)
or (
253 self.
_topic_topic[CONF_MODE_COMMAND_TOPIC]
is not None
255 support |= WaterHeaterEntityFeature.OPERATION_MODE
257 if self.
_topic_topic[CONF_POWER_COMMAND_TOPIC]
is not None:
258 support |= WaterHeaterEntityFeature.ON_OFF
264 """Handle receiving operation mode via MQTT."""
266 payload = self.
render_templaterender_template(msg, CONF_MODE_STATE_TEMPLATE)
268 if not payload.strip():
270 "Ignoring empty payload '%s' for current operation "
271 "after rendering for topic %s",
277 if payload == PAYLOAD_NONE:
279 elif payload
not in self.
_config_config[CONF_MODE_LIST]:
280 _LOGGER.warning(
"Invalid %s mode: %s", CONF_MODE_LIST, payload)
283 assert isinstance(payload, str)
288 """(Re)Subscribe to topics."""
291 CONF_MODE_STATE_TOPIC,
293 {
"_attr_current_operation"},
299 """Set new target temperature."""
300 operation_mode: str |
None
301 if (operation_mode := kwargs.get(ATTR_OPERATION_MODE))
is not None:
306 """Set new operation mode."""
307 payload = self.
_command_templates_command_templates[CONF_MODE_COMMAND_TEMPLATE](operation_mode)
308 await self.
_publish_publish(CONF_MODE_COMMAND_TOPIC, payload)
310 if self.
_optimistic_optimistic
or self.
_topic_topic[CONF_MODE_STATE_TOPIC]
is None:
315 """Turn the entity on."""
316 if CONF_POWER_COMMAND_TOPIC
in self.
_config_config:
317 mqtt_payload = self.
_command_templates_command_templates[CONF_POWER_COMMAND_TEMPLATE](
318 self.
_config_config[CONF_PAYLOAD_ON]
320 await self.
_publish_publish(CONF_POWER_COMMAND_TOPIC, mqtt_payload)
323 """Turn the entity off."""
324 if CONF_POWER_COMMAND_TOPIC
in self.
_config_config:
325 mqtt_payload = self.
_command_templates_command_templates[CONF_POWER_COMMAND_TEMPLATE](
326 self.
_config_config[CONF_PAYLOAD_OFF]
328 await self.
_publish_publish(CONF_POWER_COMMAND_TOPIC, mqtt_payload)
None _publish(self, str topic, PublishPayloadType payload)
ReceivePayloadType render_template(self, ReceiveMessage msg, str template_name)
None prepare_subscribe_topics(self)
bool add_subscription(self, str state_topic_config_key, Callable[[ReceiveMessage], None] msg_callback, set[str]|None tracked_attributes, bool disable_encoding=False)
None async_turn_on(self, **Any kwargs)
None _prepare_subscribe_topics(self)
VolSchemaType config_schema()
None async_set_operation_mode(self, str operation_mode)
None async_turn_off(self, **Any kwargs)
None _setup_from_config(self, ConfigType config)
None _handle_current_mode_received(self, ReceiveMessage msg)
None async_set_temperature(self, **Any kwargs)
None async_set_operation_mode(self, str operation_mode)
str temperature_unit(self)
None async_write_ha_state(self)
None async_setup_entity_entry_helper(HomeAssistant hass, ConfigEntry entry, type[MqttEntity]|None entity_class, str domain, AddEntitiesCallback async_add_entities, VolSchemaType discovery_schema, VolSchemaType platform_schema_modern, dict[str, type[MqttEntity]]|None schema_class_mapping=None)
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)