ESPHome  2023.11.6
mqtt_climate.cpp
Go to the documentation of this file.
1 #include "mqtt_climate.h"
2 #include "esphome/core/log.h"
3 
4 #include "mqtt_const.h"
5 
6 #ifdef USE_MQTT
7 #ifdef USE_CLIMATE
8 
9 namespace esphome {
10 namespace mqtt {
11 
12 static const char *const TAG = "mqtt.climate";
13 
14 using namespace esphome::climate;
15 
17  auto traits = this->device_->get_traits();
18  // current_temperature_topic
19  if (traits.get_supports_current_temperature()) {
20  // current_temperature_topic
21  root[MQTT_CURRENT_TEMPERATURE_TOPIC] = this->get_current_temperature_state_topic();
22  }
23  // mode_command_topic
24  root[MQTT_MODE_COMMAND_TOPIC] = this->get_mode_command_topic();
25  // mode_state_topic
26  root[MQTT_MODE_STATE_TOPIC] = this->get_mode_state_topic();
27  // modes
28  JsonArray modes = root.createNestedArray(MQTT_MODES);
29  // sort array for nice UI in HA
30  if (traits.supports_mode(CLIMATE_MODE_AUTO))
31  modes.add("auto");
32  modes.add("off");
33  if (traits.supports_mode(CLIMATE_MODE_COOL))
34  modes.add("cool");
35  if (traits.supports_mode(CLIMATE_MODE_HEAT))
36  modes.add("heat");
37  if (traits.supports_mode(CLIMATE_MODE_FAN_ONLY))
38  modes.add("fan_only");
39  if (traits.supports_mode(CLIMATE_MODE_DRY))
40  modes.add("dry");
41  if (traits.supports_mode(CLIMATE_MODE_HEAT_COOL))
42  modes.add("heat_cool");
43 
44  if (traits.get_supports_two_point_target_temperature()) {
45  // temperature_low_command_topic
46  root[MQTT_TEMPERATURE_LOW_COMMAND_TOPIC] = this->get_target_temperature_low_command_topic();
47  // temperature_low_state_topic
48  root[MQTT_TEMPERATURE_LOW_STATE_TOPIC] = this->get_target_temperature_low_state_topic();
49  // temperature_high_command_topic
50  root[MQTT_TEMPERATURE_HIGH_COMMAND_TOPIC] = this->get_target_temperature_high_command_topic();
51  // temperature_high_state_topic
52  root[MQTT_TEMPERATURE_HIGH_STATE_TOPIC] = this->get_target_temperature_high_state_topic();
53  } else {
54  // temperature_command_topic
55  root[MQTT_TEMPERATURE_COMMAND_TOPIC] = this->get_target_temperature_command_topic();
56  // temperature_state_topic
57  root[MQTT_TEMPERATURE_STATE_TOPIC] = this->get_target_temperature_state_topic();
58  }
59 
60  // min_temp
61  root[MQTT_MIN_TEMP] = traits.get_visual_min_temperature();
62  // max_temp
63  root[MQTT_MAX_TEMP] = traits.get_visual_max_temperature();
64  // temp_step
65  root["temp_step"] = traits.get_visual_target_temperature_step();
66  // temperature units are always coerced to Celsius internally
67  root[MQTT_TEMPERATURE_UNIT] = "C";
68 
69  if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) {
70  // preset_mode_command_topic
71  root[MQTT_PRESET_MODE_COMMAND_TOPIC] = this->get_preset_command_topic();
72  // preset_mode_state_topic
73  root[MQTT_PRESET_MODE_STATE_TOPIC] = this->get_preset_state_topic();
74  // presets
75  JsonArray presets = root.createNestedArray("preset_modes");
76  if (traits.supports_preset(CLIMATE_PRESET_HOME))
77  presets.add("home");
78  if (traits.supports_preset(CLIMATE_PRESET_AWAY))
79  presets.add("away");
80  if (traits.supports_preset(CLIMATE_PRESET_BOOST))
81  presets.add("boost");
82  if (traits.supports_preset(CLIMATE_PRESET_COMFORT))
83  presets.add("comfort");
84  if (traits.supports_preset(CLIMATE_PRESET_ECO))
85  presets.add("eco");
86  if (traits.supports_preset(CLIMATE_PRESET_SLEEP))
87  presets.add("sleep");
88  if (traits.supports_preset(CLIMATE_PRESET_ACTIVITY))
89  presets.add("activity");
90  for (const auto &preset : traits.get_supported_custom_presets())
91  presets.add(preset);
92  }
93 
94  if (traits.get_supports_action()) {
95  // action_topic
96  root[MQTT_ACTION_TOPIC] = this->get_action_state_topic();
97  }
98 
99  if (traits.get_supports_fan_modes()) {
100  // fan_mode_command_topic
101  root[MQTT_FAN_MODE_COMMAND_TOPIC] = this->get_fan_mode_command_topic();
102  // fan_mode_state_topic
103  root[MQTT_FAN_MODE_STATE_TOPIC] = this->get_fan_mode_state_topic();
104  // fan_modes
105  JsonArray fan_modes = root.createNestedArray("fan_modes");
106  if (traits.supports_fan_mode(CLIMATE_FAN_ON))
107  fan_modes.add("on");
108  if (traits.supports_fan_mode(CLIMATE_FAN_OFF))
109  fan_modes.add("off");
110  if (traits.supports_fan_mode(CLIMATE_FAN_AUTO))
111  fan_modes.add("auto");
112  if (traits.supports_fan_mode(CLIMATE_FAN_LOW))
113  fan_modes.add("low");
114  if (traits.supports_fan_mode(CLIMATE_FAN_MEDIUM))
115  fan_modes.add("medium");
116  if (traits.supports_fan_mode(CLIMATE_FAN_HIGH))
117  fan_modes.add("high");
118  if (traits.supports_fan_mode(CLIMATE_FAN_MIDDLE))
119  fan_modes.add("middle");
120  if (traits.supports_fan_mode(CLIMATE_FAN_FOCUS))
121  fan_modes.add("focus");
122  if (traits.supports_fan_mode(CLIMATE_FAN_DIFFUSE))
123  fan_modes.add("diffuse");
124  if (traits.supports_fan_mode(CLIMATE_FAN_QUIET))
125  fan_modes.add("quiet");
126  for (const auto &fan_mode : traits.get_supported_custom_fan_modes())
127  fan_modes.add(fan_mode);
128  }
129 
130  if (traits.get_supports_swing_modes()) {
131  // swing_mode_command_topic
132  root[MQTT_SWING_MODE_COMMAND_TOPIC] = this->get_swing_mode_command_topic();
133  // swing_mode_state_topic
134  root[MQTT_SWING_MODE_STATE_TOPIC] = this->get_swing_mode_state_topic();
135  // swing_modes
136  JsonArray swing_modes = root.createNestedArray("swing_modes");
137  if (traits.supports_swing_mode(CLIMATE_SWING_OFF))
138  swing_modes.add("off");
139  if (traits.supports_swing_mode(CLIMATE_SWING_BOTH))
140  swing_modes.add("both");
141  if (traits.supports_swing_mode(CLIMATE_SWING_VERTICAL))
142  swing_modes.add("vertical");
143  if (traits.supports_swing_mode(CLIMATE_SWING_HORIZONTAL))
144  swing_modes.add("horizontal");
145  }
146 
147  config.state_topic = false;
148  config.command_topic = false;
149 }
151  auto traits = this->device_->get_traits();
152  this->subscribe(this->get_mode_command_topic(), [this](const std::string &topic, const std::string &payload) {
153  auto call = this->device_->make_call();
154  call.set_mode(payload);
155  call.perform();
156  });
157 
158  if (traits.get_supports_two_point_target_temperature()) {
159  this->subscribe(this->get_target_temperature_low_command_topic(),
160  [this](const std::string &topic, const std::string &payload) {
161  auto val = parse_number<float>(payload);
162  if (!val.has_value()) {
163  ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str());
164  return;
165  }
166  auto call = this->device_->make_call();
167  call.set_target_temperature_low(*val);
168  call.perform();
169  });
170  this->subscribe(this->get_target_temperature_high_command_topic(),
171  [this](const std::string &topic, const std::string &payload) {
172  auto val = parse_number<float>(payload);
173  if (!val.has_value()) {
174  ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str());
175  return;
176  }
177  auto call = this->device_->make_call();
178  call.set_target_temperature_high(*val);
179  call.perform();
180  });
181  } else {
182  this->subscribe(this->get_target_temperature_command_topic(),
183  [this](const std::string &topic, const std::string &payload) {
184  auto val = parse_number<float>(payload);
185  if (!val.has_value()) {
186  ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str());
187  return;
188  }
189  auto call = this->device_->make_call();
190  call.set_target_temperature(*val);
191  call.perform();
192  });
193  }
194 
195  if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) {
196  this->subscribe(this->get_preset_command_topic(), [this](const std::string &topic, const std::string &payload) {
197  auto call = this->device_->make_call();
198  call.set_preset(payload);
199  call.perform();
200  });
201  }
202 
203  if (traits.get_supports_fan_modes()) {
204  this->subscribe(this->get_fan_mode_command_topic(), [this](const std::string &topic, const std::string &payload) {
205  auto call = this->device_->make_call();
206  call.set_fan_mode(payload);
207  call.perform();
208  });
209  }
210 
211  if (traits.get_supports_swing_modes()) {
212  this->subscribe(this->get_swing_mode_command_topic(), [this](const std::string &topic, const std::string &payload) {
213  auto call = this->device_->make_call();
214  call.set_swing_mode(payload);
215  call.perform();
216  });
217  }
218 
219  this->device_->add_on_state_callback([this](Climate & /*unused*/) { this->publish_state_(); });
220 }
223 std::string MQTTClimateComponent::component_type() const { return "climate"; }
224 const EntityBase *MQTTClimateComponent::get_entity() const { return this->device_; }
225 
227  auto traits = this->device_->get_traits();
228  // mode
229  const char *mode_s = "";
230  switch (this->device_->mode) {
231  case CLIMATE_MODE_OFF:
232  mode_s = "off";
233  break;
234  case CLIMATE_MODE_AUTO:
235  mode_s = "auto";
236  break;
237  case CLIMATE_MODE_COOL:
238  mode_s = "cool";
239  break;
240  case CLIMATE_MODE_HEAT:
241  mode_s = "heat";
242  break;
244  mode_s = "fan_only";
245  break;
246  case CLIMATE_MODE_DRY:
247  mode_s = "dry";
248  break;
250  mode_s = "heat_cool";
251  break;
252  }
253  bool success = true;
254  if (!this->publish(this->get_mode_state_topic(), mode_s))
255  success = false;
256  int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals();
257  int8_t current_accuracy = traits.get_current_temperature_accuracy_decimals();
258  if (traits.get_supports_current_temperature() && !std::isnan(this->device_->current_temperature)) {
259  std::string payload = value_accuracy_to_string(this->device_->current_temperature, current_accuracy);
260  if (!this->publish(this->get_current_temperature_state_topic(), payload))
261  success = false;
262  }
263  if (traits.get_supports_two_point_target_temperature()) {
264  std::string payload = value_accuracy_to_string(this->device_->target_temperature_low, target_accuracy);
265  if (!this->publish(this->get_target_temperature_low_state_topic(), payload))
266  success = false;
267  payload = value_accuracy_to_string(this->device_->target_temperature_high, target_accuracy);
268  if (!this->publish(this->get_target_temperature_high_state_topic(), payload))
269  success = false;
270  } else {
271  std::string payload = value_accuracy_to_string(this->device_->target_temperature, target_accuracy);
272  if (!this->publish(this->get_target_temperature_state_topic(), payload))
273  success = false;
274  }
275 
276  if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) {
277  std::string payload;
278  if (this->device_->preset.has_value()) {
279  switch (this->device_->preset.value()) {
280  case CLIMATE_PRESET_NONE:
281  payload = "none";
282  break;
283  case CLIMATE_PRESET_HOME:
284  payload = "home";
285  break;
286  case CLIMATE_PRESET_AWAY:
287  payload = "away";
288  break;
290  payload = "boost";
291  break;
293  payload = "comfort";
294  break;
295  case CLIMATE_PRESET_ECO:
296  payload = "eco";
297  break;
299  payload = "sleep";
300  break;
302  payload = "activity";
303  break;
304  }
305  }
306  if (this->device_->custom_preset.has_value())
307  payload = this->device_->custom_preset.value();
308  if (!this->publish(this->get_preset_state_topic(), payload))
309  success = false;
310  }
311 
312  if (traits.get_supports_action()) {
313  const char *payload = "unknown";
314  switch (this->device_->action) {
315  case CLIMATE_ACTION_OFF:
316  payload = "off";
317  break;
319  payload = "cooling";
320  break;
322  payload = "heating";
323  break;
324  case CLIMATE_ACTION_IDLE:
325  payload = "idle";
326  break;
328  payload = "drying";
329  break;
330  case CLIMATE_ACTION_FAN:
331  payload = "fan";
332  break;
333  }
334  if (!this->publish(this->get_action_state_topic(), payload))
335  success = false;
336  }
337 
338  if (traits.get_supports_fan_modes()) {
339  std::string payload;
340  if (this->device_->fan_mode.has_value()) {
341  switch (this->device_->fan_mode.value()) {
342  case CLIMATE_FAN_ON:
343  payload = "on";
344  break;
345  case CLIMATE_FAN_OFF:
346  payload = "off";
347  break;
348  case CLIMATE_FAN_AUTO:
349  payload = "auto";
350  break;
351  case CLIMATE_FAN_LOW:
352  payload = "low";
353  break;
354  case CLIMATE_FAN_MEDIUM:
355  payload = "medium";
356  break;
357  case CLIMATE_FAN_HIGH:
358  payload = "high";
359  break;
360  case CLIMATE_FAN_MIDDLE:
361  payload = "middle";
362  break;
363  case CLIMATE_FAN_FOCUS:
364  payload = "focus";
365  break;
366  case CLIMATE_FAN_DIFFUSE:
367  payload = "diffuse";
368  break;
369  case CLIMATE_FAN_QUIET:
370  payload = "quiet";
371  break;
372  }
373  }
374  if (this->device_->custom_fan_mode.has_value())
375  payload = this->device_->custom_fan_mode.value();
376  if (!this->publish(this->get_fan_mode_state_topic(), payload))
377  success = false;
378  }
379 
380  if (traits.get_supports_swing_modes()) {
381  const char *payload = "";
382  switch (this->device_->swing_mode) {
383  case CLIMATE_SWING_OFF:
384  payload = "off";
385  break;
386  case CLIMATE_SWING_BOTH:
387  payload = "both";
388  break;
390  payload = "vertical";
391  break;
393  payload = "horizontal";
394  break;
395  }
396  if (!this->publish(this->get_swing_mode_state_topic(), payload))
397  success = false;
398  }
399 
400  return success;
401 }
402 
403 } // namespace mqtt
404 } // namespace esphome
405 
406 #endif
407 #endif // USE_MQTT
constexpr const char *const MQTT_CURRENT_TEMPERATURE_TOPIC
Definition: mqtt_const.h:52
value_type const & value() const
Definition: optional.h:89
constexpr const char *const MQTT_MIN_TEMP
Definition: mqtt_const.h:103
ClimateSwingMode swing_mode
The active swing mode of the climate device.
Definition: climate.h:185
std::string value_accuracy_to_string(float value, int8_t accuracy_decimals)
Create a string from a value and an accuracy in decimals.
Definition: helpers.cpp:401
constexpr const char *const MQTT_FAN_MODE_COMMAND_TOPIC
Definition: mqtt_const.h:73
constexpr const char *const MQTT_SWING_MODE_COMMAND_TOPIC
Definition: mqtt_const.h:216
constexpr const char *const MQTT_FAN_MODE_STATE_TOPIC
Definition: mqtt_const.h:75
float target_temperature
The target temperature of the climate device.
Definition: climate.h:172
constexpr const char *const MQTT_ACTION_TOPIC
Definition: mqtt_const.h:12
bool state_topic
If the state topic should be included. Defaults to true.
constexpr const char *const MQTT_TEMPERATURE_COMMAND_TOPIC
Definition: mqtt_const.h:220
constexpr const char *const MQTT_TEMPERATURE_STATE_TOPIC
Definition: mqtt_const.h:230
const EntityBase * get_entity() const override
ClimateMode mode
The active mode of the climate device.
Definition: climate.h:164
float target_temperature_high
The maximum target temperature of the climate device, for climate devices with split target temperatu...
Definition: climate.h:177
float current_temperature
The current temperature of the climate device, as reported from the integration.
Definition: climate.h:168
mopeka_std_values val[4]
void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override
constexpr const char *const MQTT_MAX_TEMP
Definition: mqtt_const.h:102
constexpr const char *const MQTT_MODE_STATE_TOPIC
Definition: mqtt_const.h:106
bool has_value() const
Definition: optional.h:87
constexpr const char *const MQTT_PRESET_MODE_COMMAND_TOPIC
Definition: mqtt_const.h:165
MQTTClimateComponent(climate::Climate *device)
bool command_topic
If the command topic should be included. Default to true.
bool publish(const std::string &topic, const std::string &payload)
Send a MQTT message.
optional< std::string > custom_fan_mode
The active custom fan mode of the climate device.
Definition: climate.h:188
constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TOPIC
Definition: mqtt_const.h:226
constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TOPIC
Definition: mqtt_const.h:222
constexpr const char *const MQTT_TEMPERATURE_UNIT
Definition: mqtt_const.h:231
optional< ClimatePreset > preset
The active preset of the climate device.
Definition: climate.h:191
Simple Helper struct used for Home Assistant MQTT send_discovery().
ClimateTraits get_traits()
Get the traits of this climate device with all overrides applied.
Definition: climate.cpp:418
ClimateFanMode fan_mode
Definition: climate.h:533
optional< std::string > custom_preset
The active custom preset mode of the climate device.
Definition: climate.h:194
optional< ClimateFanMode > fan_mode
The active fan mode of the climate device.
Definition: climate.h:182
constexpr const char *const MQTT_MODES
Definition: mqtt_const.h:108
constexpr const char *const MQTT_SWING_MODE_STATE_TOPIC
Definition: mqtt_const.h:218
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
constexpr const char *const MQTT_PRESET_MODE_STATE_TOPIC
Definition: mqtt_const.h:167
constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TOPIC
Definition: mqtt_const.h:224
std::string component_type() const override
constexpr const char *const MQTT_MODE_COMMAND_TOPIC
Definition: mqtt_const.h:105
float target_temperature_low
The minimum target temperature of the climate device, for climate devices with split target temperatu...
Definition: climate.h:175
constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TOPIC
Definition: mqtt_const.h:228
ClimatePreset preset
Definition: climate.h:538
ClimateAction action
The active state of the climate device.
Definition: climate.h:166
ClimateDevice - This is the base class for all climate integrations.
Definition: climate.h:161