ESPHome  2022.5.1
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_temperature_step();
66  // temperature units are always coerced to Celsius internally
67  root[MQTT_TEMPERATURE_UNIT] = "C";
68 
69  if (traits.supports_preset(CLIMATE_PRESET_AWAY)) {
70  // away_mode_command_topic
71  root[MQTT_AWAY_MODE_COMMAND_TOPIC] = this->get_away_command_topic();
72  // away_mode_state_topic
73  root[MQTT_AWAY_MODE_STATE_TOPIC] = this->get_away_state_topic();
74  }
75  if (traits.get_supports_action()) {
76  // action_topic
77  root[MQTT_ACTION_TOPIC] = this->get_action_state_topic();
78  }
79 
80  if (traits.get_supports_fan_modes() || !traits.get_supported_custom_fan_modes().empty()) {
81  // fan_mode_command_topic
82  root[MQTT_FAN_MODE_COMMAND_TOPIC] = this->get_fan_mode_command_topic();
83  // fan_mode_state_topic
84  root[MQTT_FAN_MODE_STATE_TOPIC] = this->get_fan_mode_state_topic();
85  // fan_modes
86  JsonArray fan_modes = root.createNestedArray("fan_modes");
87  if (traits.supports_fan_mode(CLIMATE_FAN_ON))
88  fan_modes.add("on");
89  if (traits.supports_fan_mode(CLIMATE_FAN_OFF))
90  fan_modes.add("off");
91  if (traits.supports_fan_mode(CLIMATE_FAN_AUTO))
92  fan_modes.add("auto");
93  if (traits.supports_fan_mode(CLIMATE_FAN_LOW))
94  fan_modes.add("low");
95  if (traits.supports_fan_mode(CLIMATE_FAN_MEDIUM))
96  fan_modes.add("medium");
97  if (traits.supports_fan_mode(CLIMATE_FAN_HIGH))
98  fan_modes.add("high");
99  if (traits.supports_fan_mode(CLIMATE_FAN_MIDDLE))
100  fan_modes.add("middle");
101  if (traits.supports_fan_mode(CLIMATE_FAN_FOCUS))
102  fan_modes.add("focus");
103  if (traits.supports_fan_mode(CLIMATE_FAN_DIFFUSE))
104  fan_modes.add("diffuse");
105  for (const auto &fan_mode : traits.get_supported_custom_fan_modes())
106  fan_modes.add(fan_mode);
107  }
108 
109  if (traits.get_supports_swing_modes()) {
110  // swing_mode_command_topic
111  root[MQTT_SWING_MODE_COMMAND_TOPIC] = this->get_swing_mode_command_topic();
112  // swing_mode_state_topic
113  root[MQTT_SWING_MODE_STATE_TOPIC] = this->get_swing_mode_state_topic();
114  // swing_modes
115  JsonArray swing_modes = root.createNestedArray("swing_modes");
116  if (traits.supports_swing_mode(CLIMATE_SWING_OFF))
117  swing_modes.add("off");
118  if (traits.supports_swing_mode(CLIMATE_SWING_BOTH))
119  swing_modes.add("both");
120  if (traits.supports_swing_mode(CLIMATE_SWING_VERTICAL))
121  swing_modes.add("vertical");
122  if (traits.supports_swing_mode(CLIMATE_SWING_HORIZONTAL))
123  swing_modes.add("horizontal");
124  }
125 
126  config.state_topic = false;
127  config.command_topic = false;
128 }
130  auto traits = this->device_->get_traits();
131  this->subscribe(this->get_mode_command_topic(), [this](const std::string &topic, const std::string &payload) {
132  auto call = this->device_->make_call();
133  call.set_mode(payload);
134  call.perform();
135  });
136 
137  if (traits.get_supports_two_point_target_temperature()) {
138  this->subscribe(this->get_target_temperature_low_command_topic(),
139  [this](const std::string &topic, const std::string &payload) {
140  auto val = parse_number<float>(payload);
141  if (!val.has_value()) {
142  ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str());
143  return;
144  }
145  auto call = this->device_->make_call();
146  call.set_target_temperature_low(*val);
147  call.perform();
148  });
149  this->subscribe(this->get_target_temperature_high_command_topic(),
150  [this](const std::string &topic, const std::string &payload) {
151  auto val = parse_number<float>(payload);
152  if (!val.has_value()) {
153  ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str());
154  return;
155  }
156  auto call = this->device_->make_call();
157  call.set_target_temperature_high(*val);
158  call.perform();
159  });
160  } else {
161  this->subscribe(this->get_target_temperature_command_topic(),
162  [this](const std::string &topic, const std::string &payload) {
163  auto val = parse_number<float>(payload);
164  if (!val.has_value()) {
165  ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str());
166  return;
167  }
168  auto call = this->device_->make_call();
169  call.set_target_temperature(*val);
170  call.perform();
171  });
172  }
173 
174  if (traits.supports_preset(CLIMATE_PRESET_AWAY)) {
175  this->subscribe(this->get_away_command_topic(), [this](const std::string &topic, const std::string &payload) {
176  auto onoff = parse_on_off(payload.c_str());
177  auto call = this->device_->make_call();
178  switch (onoff) {
179  case PARSE_ON:
180  call.set_preset(CLIMATE_PRESET_AWAY);
181  break;
182  case PARSE_OFF:
183  call.set_preset(CLIMATE_PRESET_HOME);
184  break;
185  case PARSE_TOGGLE:
186  call.set_preset(this->device_->preset == CLIMATE_PRESET_AWAY ? CLIMATE_PRESET_HOME : CLIMATE_PRESET_AWAY);
187  break;
188  case PARSE_NONE:
189  default:
190  ESP_LOGW(TAG, "Unknown payload '%s'", payload.c_str());
191  return;
192  }
193  call.perform();
194  });
195  }
196 
197  if (traits.get_supports_fan_modes()) {
198  this->subscribe(this->get_fan_mode_command_topic(), [this](const std::string &topic, const std::string &payload) {
199  auto call = this->device_->make_call();
200  call.set_fan_mode(payload);
201  call.perform();
202  });
203  }
204 
205  if (traits.get_supports_swing_modes()) {
206  this->subscribe(this->get_swing_mode_command_topic(), [this](const std::string &topic, const std::string &payload) {
207  auto call = this->device_->make_call();
208  call.set_swing_mode(payload);
209  call.perform();
210  });
211  }
212 
213  this->device_->add_on_state_callback([this]() { this->publish_state_(); });
214 }
217 std::string MQTTClimateComponent::component_type() const { return "climate"; }
218 const EntityBase *MQTTClimateComponent::get_entity() const { return this->device_; }
219 
221  auto traits = this->device_->get_traits();
222  // mode
223  const char *mode_s = "";
224  switch (this->device_->mode) {
225  case CLIMATE_MODE_OFF:
226  mode_s = "off";
227  break;
228  case CLIMATE_MODE_AUTO:
229  mode_s = "auto";
230  break;
231  case CLIMATE_MODE_COOL:
232  mode_s = "cool";
233  break;
234  case CLIMATE_MODE_HEAT:
235  mode_s = "heat";
236  break;
238  mode_s = "fan_only";
239  break;
240  case CLIMATE_MODE_DRY:
241  mode_s = "dry";
242  break;
244  mode_s = "heat_cool";
245  break;
246  }
247  bool success = true;
248  if (!this->publish(this->get_mode_state_topic(), mode_s))
249  success = false;
250  int8_t accuracy = traits.get_temperature_accuracy_decimals();
251  if (traits.get_supports_current_temperature() && !std::isnan(this->device_->current_temperature)) {
252  std::string payload = value_accuracy_to_string(this->device_->current_temperature, accuracy);
253  if (!this->publish(this->get_current_temperature_state_topic(), payload))
254  success = false;
255  }
256  if (traits.get_supports_two_point_target_temperature()) {
257  std::string payload = value_accuracy_to_string(this->device_->target_temperature_low, accuracy);
258  if (!this->publish(this->get_target_temperature_low_state_topic(), payload))
259  success = false;
260  payload = value_accuracy_to_string(this->device_->target_temperature_high, accuracy);
261  if (!this->publish(this->get_target_temperature_high_state_topic(), payload))
262  success = false;
263  } else {
264  std::string payload = value_accuracy_to_string(this->device_->target_temperature, accuracy);
265  if (!this->publish(this->get_target_temperature_state_topic(), payload))
266  success = false;
267  }
268 
269  if (traits.supports_preset(CLIMATE_PRESET_AWAY)) {
270  std::string payload = ONOFF(this->device_->preset == CLIMATE_PRESET_AWAY);
271  if (!this->publish(this->get_away_state_topic(), payload))
272  success = false;
273  }
274  if (traits.get_supports_action()) {
275  const char *payload = "unknown";
276  switch (this->device_->action) {
277  case CLIMATE_ACTION_OFF:
278  payload = "off";
279  break;
281  payload = "cooling";
282  break;
284  payload = "heating";
285  break;
286  case CLIMATE_ACTION_IDLE:
287  payload = "idle";
288  break;
290  payload = "drying";
291  break;
292  case CLIMATE_ACTION_FAN:
293  payload = "fan";
294  break;
295  }
296  if (!this->publish(this->get_action_state_topic(), payload))
297  success = false;
298  }
299 
300  if (traits.get_supports_fan_modes()) {
301  std::string payload;
302  if (this->device_->fan_mode.has_value()) {
303  switch (this->device_->fan_mode.value()) {
304  case CLIMATE_FAN_ON:
305  payload = "on";
306  break;
307  case CLIMATE_FAN_OFF:
308  payload = "off";
309  break;
310  case CLIMATE_FAN_AUTO:
311  payload = "auto";
312  break;
313  case CLIMATE_FAN_LOW:
314  payload = "low";
315  break;
316  case CLIMATE_FAN_MEDIUM:
317  payload = "medium";
318  break;
319  case CLIMATE_FAN_HIGH:
320  payload = "high";
321  break;
322  case CLIMATE_FAN_MIDDLE:
323  payload = "middle";
324  break;
325  case CLIMATE_FAN_FOCUS:
326  payload = "focus";
327  break;
328  case CLIMATE_FAN_DIFFUSE:
329  payload = "diffuse";
330  break;
331  }
332  }
333  if (this->device_->custom_fan_mode.has_value())
334  payload = this->device_->custom_fan_mode.value();
335  if (!this->publish(this->get_fan_mode_state_topic(), payload))
336  success = false;
337  }
338 
339  if (traits.get_supports_swing_modes()) {
340  const char *payload = "";
341  switch (this->device_->swing_mode) {
342  case CLIMATE_SWING_OFF:
343  payload = "off";
344  break;
345  case CLIMATE_SWING_BOTH:
346  payload = "both";
347  break;
349  payload = "vertical";
350  break;
352  payload = "horizontal";
353  break;
354  }
355  if (!this->publish(this->get_swing_mode_state_topic(), payload))
356  success = false;
357  }
358 
359  return success;
360 }
361 
362 } // namespace mqtt
363 } // namespace esphome
364 
365 #endif
366 #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:204
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:250
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:183
constexpr const char *const MQTT_ACTION_TOPIC
Definition: mqtt_const.h:12
constexpr const char *const MQTT_AWAY_MODE_COMMAND_TOPIC
Definition: mqtt_const.h:21
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:175
float target_temperature_high
The maximum target temperature of the climate device, for climate devices with split target temperatu...
Definition: climate.h:188
float current_temperature
The current temperature of the climate device, as reported from the integration.
Definition: climate.h:179
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
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.
ParseOnOffState parse_on_off(const char *str, const char *on, const char *off)
Parse a string that contains either on, off or toggle.
Definition: helpers.cpp:235
optional< std::string > custom_fan_mode
The active custom fan mode of the climate device.
Definition: climate.h:207
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:210
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:424
constexpr const char *const MQTT_AWAY_MODE_STATE_TOPIC
Definition: mqtt_const.h:23
ClimateFanMode fan_mode
Definition: climate.h:541
optional< ClimateFanMode > fan_mode
The active fan mode of the climate device.
Definition: climate.h:198
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
Definition: a4988.cpp:4
uint32_t val
Definition: datatypes.h:79
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:186
constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TOPIC
Definition: mqtt_const.h:228
ClimateAction action
The active state of the climate device.
Definition: climate.h:177
ClimateDevice - This is the base class for all climate integrations.
Definition: climate.h:167