ESPHome 2026.4.0-dev
Loading...
Searching...
No Matches
water_heater.cpp
Go to the documentation of this file.
1#include "water_heater.h"
2#include "esphome/core/log.h"
3
4#include <cinttypes>
8
9#include <cmath>
10#include <cstring>
11
13
14static const char *const TAG = "water_heater";
15
16void log_water_heater(const char *tag, const char *prefix, const char *type, WaterHeater *obj) {
17 if (obj != nullptr) {
18 ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());
19 }
20}
21
22WaterHeaterCall::WaterHeaterCall(WaterHeater *parent) : parent_(parent) {}
23
28
29WaterHeaterCall &WaterHeaterCall::set_mode(const char *mode) { return this->set_mode(mode, strlen(mode)); }
30
32 if (len == 3 && ESPHOME_strncasecmp_P(mode, ESPHOME_PSTR("OFF"), 3) == 0) {
34 } else if (len == 3 && ESPHOME_strncasecmp_P(mode, ESPHOME_PSTR("ECO"), 3) == 0) {
36 } else if (len == 8 && ESPHOME_strncasecmp_P(mode, ESPHOME_PSTR("ELECTRIC"), 8) == 0) {
38 } else if (len == 11 && ESPHOME_strncasecmp_P(mode, ESPHOME_PSTR("PERFORMANCE"), 11) == 0) {
40 } else if (len == 11 && ESPHOME_strncasecmp_P(mode, ESPHOME_PSTR("HIGH_DEMAND"), 11) == 0) {
42 } else if (len == 9 && ESPHOME_strncasecmp_P(mode, ESPHOME_PSTR("HEAT_PUMP"), 9) == 0) {
44 } else if (len == 3 && ESPHOME_strncasecmp_P(mode, ESPHOME_PSTR("GAS"), 3) == 0) {
46 } else {
47 ESP_LOGW(TAG, "'%s' - Unrecognized mode %.*s", this->parent_->get_name().c_str(), (int) len, mode);
48 }
49 return *this;
50}
51
56
61
66
68 if (away) {
70 } else {
71 this->state_ &= ~WATER_HEATER_STATE_AWAY;
72 }
74 return *this;
75}
76
78 if (on) {
80 } else {
81 this->state_ &= ~WATER_HEATER_STATE_ON;
82 }
84 return *this;
85}
86
88 ESP_LOGV(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
89 this->validate_();
90 if (this->mode_.has_value()) {
91 ESP_LOGV(TAG, " Mode: %s", LOG_STR_ARG(water_heater_mode_to_string(*this->mode_)));
92 }
93 if (!std::isnan(this->target_temperature_)) {
94 ESP_LOGV(TAG, " Target Temperature: %.2f", this->target_temperature_);
95 }
96 if (!std::isnan(this->target_temperature_low_)) {
97 ESP_LOGV(TAG, " Target Temperature Low: %.2f", this->target_temperature_low_);
98 }
99 if (!std::isnan(this->target_temperature_high_)) {
100 ESP_LOGV(TAG, " Target Temperature High: %.2f", this->target_temperature_high_);
101 }
103 ESP_LOGV(TAG, " Away: %s", (this->state_ & WATER_HEATER_STATE_AWAY) ? "YES" : "NO");
104 }
106 ESP_LOGV(TAG, " On: %s", (this->state_ & WATER_HEATER_STATE_ON) ? "YES" : "NO");
107 }
108 this->parent_->control(*this);
109}
110
112 auto traits = this->parent_->get_traits();
113 if (this->mode_.has_value()) {
114 if (!traits.supports_mode(*this->mode_)) {
115 ESP_LOGW(TAG, "'%s' - Mode %" PRIu32 " not supported", this->parent_->get_name().c_str(),
116 static_cast<uint32_t>(*this->mode_));
117 this->mode_.reset();
118 }
119 }
120 if (!std::isnan(this->target_temperature_)) {
121 if (traits.get_supports_two_point_target_temperature()) {
122 ESP_LOGW(TAG, "'%s' - Cannot set target temperature for device with two-point target temperature",
123 this->parent_->get_name().c_str());
124 this->target_temperature_ = NAN;
125 } else if (this->target_temperature_ < traits.get_min_temperature() ||
126 this->target_temperature_ > traits.get_max_temperature()) {
127 ESP_LOGW(TAG, "'%s' - Target temperature %.1f is out of range [%.1f - %.1f]", this->parent_->get_name().c_str(),
128 this->target_temperature_, traits.get_min_temperature(), traits.get_max_temperature());
129 this->target_temperature_ =
130 std::max(traits.get_min_temperature(), std::min(this->target_temperature_, traits.get_max_temperature()));
131 }
132 }
133 if (!std::isnan(this->target_temperature_low_) || !std::isnan(this->target_temperature_high_)) {
134 if (!traits.get_supports_two_point_target_temperature()) {
135 ESP_LOGW(TAG, "'%s' - Cannot set low/high target temperature", this->parent_->get_name().c_str());
136 this->target_temperature_low_ = NAN;
137 this->target_temperature_high_ = NAN;
138 }
139 }
140 if (!std::isnan(this->target_temperature_low_) && !std::isnan(this->target_temperature_high_)) {
142 ESP_LOGW(TAG, "'%s' - Target temperature low %.2f must be less than high %.2f", this->parent_->get_name().c_str(),
143 this->target_temperature_low_, this->target_temperature_high_);
144 this->target_temperature_low_ = NAN;
145 this->target_temperature_high_ = NAN;
146 }
147 }
148 if (!traits.get_supports_away_mode()) {
149 if (this->state_ & WATER_HEATER_STATE_AWAY) {
150 ESP_LOGW(TAG, "'%s' - Away mode not supported", this->parent_->get_name().c_str());
151 }
152 this->state_ &= ~WATER_HEATER_STATE_AWAY;
153 this->state_mask_ &= ~WATER_HEATER_STATE_AWAY;
154 }
155 // If ON/OFF not supported, device is always on - clear the flag silently
156 if (!traits.has_feature_flags(WATER_HEATER_SUPPORTS_ON_OFF)) {
157 this->state_ &= ~WATER_HEATER_STATE_ON;
158 this->state_mask_ &= ~WATER_HEATER_STATE_ON;
159 }
160}
161
163 auto traits = this->get_traits();
164 ESP_LOGV(TAG,
165 "'%s' >>\n"
166 " Mode: %s",
167 this->name_.c_str(), LOG_STR_ARG(water_heater_mode_to_string(this->mode_)));
168 if (!std::isnan(this->current_temperature_)) {
169 ESP_LOGV(TAG, " Current Temperature: %.2f°C", this->current_temperature_);
170 }
172 ESP_LOGV(TAG, " Target Temperature: Low: %.2f°C High: %.2f°C", this->target_temperature_low_,
174 } else if (!std::isnan(this->target_temperature_)) {
175 ESP_LOGV(TAG, " Target Temperature: %.2f°C", this->target_temperature_);
176 }
177 if (this->state_ & WATER_HEATER_STATE_AWAY) {
178 ESP_LOGV(TAG, " Away: YES");
179 }
181 ESP_LOGV(TAG, " On: %s", (this->state_ & WATER_HEATER_STATE_ON) ? "YES" : "NO");
182 }
183
184#if defined(USE_WATER_HEATER) && defined(USE_CONTROLLER_REGISTRY)
186#endif
187
188 SavedWaterHeaterState saved{};
189 saved.mode = this->mode_;
191 saved.target_temperature_low = this->target_temperature_low_;
192 saved.target_temperature_high = this->target_temperature_high_;
193 } else {
194 saved.target_temperature = this->target_temperature_;
195 }
196 saved.state = this->state_;
197 this->pref_.save(&saved);
198}
199
200optional<WaterHeaterCall> WaterHeater::restore_state_() {
202 SavedWaterHeaterState recovered{};
203 if (!this->pref_.load(&recovered))
204 return {};
205
206 auto traits = this->get_traits();
207 auto call = this->make_call();
208 call.set_mode(recovered.mode);
210 call.set_target_temperature_low(recovered.target_temperature_low);
211 call.set_target_temperature_high(recovered.target_temperature_high);
212 } else {
213 call.set_target_temperature(recovered.target_temperature);
214 }
215 call.set_away((recovered.state & WATER_HEATER_STATE_AWAY) != 0);
216 call.set_on((recovered.state & WATER_HEATER_STATE_ON) != 0);
217 return call;
218}
219
221 auto traits = this->traits();
222#ifdef USE_WATER_HEATER_VISUAL_OVERRIDES
223 if (!std::isnan(this->visual_min_temperature_override_)) {
225 }
226 if (!std::isnan(this->visual_max_temperature_override_)) {
228 }
229 if (!std::isnan(this->visual_target_temperature_step_override_)) {
231 }
232#endif
233 return traits;
234}
235
236#ifdef USE_WATER_HEATER_VISUAL_OVERRIDES
237void WaterHeater::set_visual_min_temperature_override(float min_temperature_override) {
238 this->visual_min_temperature_override_ = min_temperature_override;
239}
240void WaterHeater::set_visual_max_temperature_override(float max_temperature_override) {
241 this->visual_max_temperature_override_ = max_temperature_override;
242}
243void WaterHeater::set_visual_target_temperature_step_override(float visual_target_temperature_step_override) {
244 this->visual_target_temperature_step_override_ = visual_target_temperature_step_override;
245}
246#endif
247
248// Water heater mode strings indexed by WaterHeaterMode enum (0-6): OFF, ECO, ELECTRIC, PERFORMANCE, HIGH_DEMAND,
249// HEAT_PUMP, GAS
250PROGMEM_STRING_TABLE(WaterHeaterModeStrings, "OFF", "ECO", "ELECTRIC", "PERFORMANCE", "HIGH_DEMAND", "HEAT_PUMP", "GAS",
251 "UNKNOWN");
252
254 return WaterHeaterModeStrings::get_log_str(static_cast<uint8_t>(mode), WaterHeaterModeStrings::LAST_INDEX);
255}
256
258 auto traits = this->get_traits();
259 ESP_LOGCONFIG(tag,
260 " Min Temperature: %.1f°C\n"
261 " Max Temperature: %.1f°C\n"
262 " Temperature Step: %.1f",
265 ESP_LOGCONFIG(tag, " Supports Two-Point Target Temperature: YES");
266 }
268 ESP_LOGCONFIG(tag, " Supports Away Mode: YES");
269 }
271 ESP_LOGCONFIG(tag, " Supports On/Off: YES");
272 }
274 ESP_LOGCONFIG(tag, " Supported Modes:");
276 ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(water_heater_mode_to_string(m)));
277 }
278 }
279}
280
281} // namespace esphome::water_heater
BedjetMode mode
BedJet operating mode.
uint8_t m
Definition bl0906.h:1
static void notify_water_heater_update(water_heater::WaterHeater *obj)
const StringRef & get_name() const
Definition entity_base.h:71
ESPPreferenceObject make_entity_preference(uint32_t version=0)
Create a preference object for storing this entity's state/settings.
constexpr bool empty() const
Check if the set is empty.
constexpr const char * c_str() const
Definition string_ref.h:73
WaterHeaterCall & set_away(bool away)
WaterHeaterCall & set_target_temperature_high(float temperature)
WaterHeaterCall & set_mode(WaterHeaterMode mode)
WaterHeaterCall & set_target_temperature_low(float temperature)
WaterHeaterCall & set_on(bool on)
optional< WaterHeaterMode > mode_
WaterHeaterCall & set_target_temperature(float temperature)
void set_visual_min_temperature_override(float min_temperature_override)
virtual void control(const WaterHeaterCall &call)=0
void set_visual_target_temperature_step_override(float visual_target_temperature_step_override)
void set_visual_max_temperature_override(float max_temperature_override)
optional< WaterHeaterCall > restore_state_()
Restore the state of the water heater, call this from your setup() method.
virtual WaterHeaterCallInternal make_call()=0
void dump_traits_(const char *tag)
Log the traits of this water heater for dump_config().
virtual WaterHeaterTraits get_traits()
virtual WaterHeaterTraits traits()=0
const WaterHeaterModeMask & get_supported_modes() const
void set_min_temperature(float min_temperature)
bool has_feature_flags(uint32_t flags) const
void set_target_temperature_step(float target_temperature_step)
void set_max_temperature(float max_temperature)
uint16_t type
PROGMEM_STRING_TABLE(WaterHeaterModeStrings, "OFF", "ECO", "ELECTRIC", "PERFORMANCE", "HIGH_DEMAND", "HEAT_PUMP", "GAS", "UNKNOWN")
void log_water_heater(const char *tag, const char *prefix, const char *type, WaterHeater *obj)
@ WATER_HEATER_STATE_ON
Water heater is on (not in standby)
@ WATER_HEATER_STATE_AWAY
Away/vacation mode is currently active.
@ WATER_HEATER_SUPPORTS_ON_OFF
The water heater can be turned on/off.
const LogString * water_heater_mode_to_string(WaterHeaterMode mode)
Convert the given WaterHeaterMode to a human-readable string for logging.
const char * tag
Definition log.h:74
std::string size_t len
Definition helpers.h:1045
static void uint32_t
uint16_t temperature
Definition sun_gtil2.cpp:12