ESPHome 2026.3.0-dev
Loading...
Searching...
No Matches
mqtt_cover.cpp
Go to the documentation of this file.
1#include "mqtt_cover.h"
2#include "esphome/core/log.h"
4
5#include "mqtt_const.h"
6
7#ifdef USE_MQTT
8#ifdef USE_COVER
9
10namespace esphome::mqtt {
11
12static const char *const TAG = "mqtt.cover";
13
14using namespace esphome::cover;
15
16static ProgmemStr cover_state_to_mqtt_str(CoverOperation operation, float position, bool supports_position) {
17 if (operation == COVER_OPERATION_OPENING)
18 return ESPHOME_F("opening");
19 if (operation == COVER_OPERATION_CLOSING)
20 return ESPHOME_F("closing");
21 if (position == COVER_CLOSED)
22 return ESPHOME_F("closed");
23 if (position == COVER_OPEN)
24 return ESPHOME_F("open");
25 if (supports_position)
26 return ESPHOME_F("open");
27 return ESPHOME_F("unknown");
28}
29
32 auto traits = this->cover_->get_traits();
33 this->cover_->add_on_state_callback([this]() { this->publish_state(); });
34 this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) {
35 auto call = this->cover_->make_call();
36 call.set_command(payload.c_str());
37 call.perform();
38 });
39 if (traits.get_supports_position()) {
40 this->subscribe(this->get_position_command_topic(), [this](const std::string &topic, const std::string &payload) {
41 auto value = parse_number<float>(payload);
42 if (!value.has_value()) {
43 ESP_LOGW(TAG, "Invalid position value: '%s'", payload.c_str());
44 return;
45 }
46 auto call = this->cover_->make_call();
47 call.set_position(*value / 100.0f);
48 call.perform();
49 });
50 }
51 if (traits.get_supports_tilt()) {
52 this->subscribe(this->get_tilt_command_topic(), [this](const std::string &topic, const std::string &payload) {
53 auto value = parse_number<float>(payload);
54 if (!value.has_value()) {
55 ESP_LOGW(TAG, "Invalid tilt value: '%s'", payload.c_str());
56 return;
57 }
58 auto call = this->cover_->make_call();
59 call.set_tilt(*value / 100.0f);
60 call.perform();
61 });
62 }
63}
64
66 ESP_LOGCONFIG(TAG, "MQTT cover '%s':", this->cover_->get_name().c_str());
67 auto traits = this->cover_->get_traits();
68 bool has_command_topic = traits.get_supports_position() || !traits.get_supports_tilt();
69 LOG_MQTT_COMPONENT(true, has_command_topic);
70 char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
71#ifdef USE_MQTT_COVER_JSON
72 if (this->use_json_format_) {
73 ESP_LOGCONFIG(TAG, " JSON State Payload: YES");
74 } else {
75#endif
76 if (traits.get_supports_position()) {
77 ESP_LOGCONFIG(TAG, " Position State Topic: '%s'", this->get_position_state_topic_to(topic_buf).c_str());
78 }
79 if (traits.get_supports_tilt()) {
80 ESP_LOGCONFIG(TAG, " Tilt State Topic: '%s'", this->get_tilt_state_topic_to(topic_buf).c_str());
81 }
82#ifdef USE_MQTT_COVER_JSON
83 }
84#endif
85 if (traits.get_supports_position()) {
86 ESP_LOGCONFIG(TAG, " Position Command Topic: '%s'", this->get_position_command_topic_to(topic_buf).c_str());
87 }
88 if (traits.get_supports_tilt()) {
89 ESP_LOGCONFIG(TAG, " Tilt Command Topic: '%s'", this->get_tilt_command_topic_to(topic_buf).c_str());
90 }
91}
93 // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
94 const auto device_class = this->cover_->get_device_class_ref();
95 if (!device_class.empty()) {
96 root[MQTT_DEVICE_CLASS] = device_class;
97 }
98 // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
99
100 auto traits = this->cover_->get_traits();
101 if (traits.get_is_assumed_state()) {
102 root[MQTT_OPTIMISTIC] = true;
103 }
104 char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
105#ifdef USE_MQTT_COVER_JSON
106 if (this->use_json_format_) {
107 // JSON mode: all state published to state_topic as JSON, use templates to extract
108 root[MQTT_VALUE_TEMPLATE] = ESPHOME_F("{{ value_json.state }}");
109 if (traits.get_supports_position()) {
110 root[MQTT_POSITION_TOPIC] = this->get_state_topic_to_(topic_buf);
111 root[MQTT_POSITION_TEMPLATE] = ESPHOME_F("{{ value_json.position }}");
112 root[MQTT_SET_POSITION_TOPIC] = this->get_position_command_topic_to(topic_buf);
113 }
114 if (traits.get_supports_tilt()) {
115 root[MQTT_TILT_STATUS_TOPIC] = this->get_state_topic_to_(topic_buf);
116 root[MQTT_TILT_STATUS_TEMPLATE] = ESPHOME_F("{{ value_json.tilt }}");
117 root[MQTT_TILT_COMMAND_TOPIC] = this->get_tilt_command_topic_to(topic_buf);
118 }
119 } else
120#endif
121 {
122 // Standard mode: separate topics for position and tilt
123 if (traits.get_supports_position()) {
124 root[MQTT_POSITION_TOPIC] = this->get_position_state_topic_to(topic_buf);
125 root[MQTT_SET_POSITION_TOPIC] = this->get_position_command_topic_to(topic_buf);
126 }
127 if (traits.get_supports_tilt()) {
128 root[MQTT_TILT_STATUS_TOPIC] = this->get_tilt_state_topic_to(topic_buf);
129 root[MQTT_TILT_COMMAND_TOPIC] = this->get_tilt_command_topic_to(topic_buf);
130 }
131 }
132 if (traits.get_supports_tilt() && !traits.get_supports_position()) {
133 config.command_topic = false;
134 }
135}
136
138const EntityBase *MQTTCoverComponent::get_entity() const { return this->cover_; }
139
142 auto traits = this->cover_->get_traits();
143 char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
144#ifdef USE_MQTT_COVER_JSON
145 if (this->use_json_format_) {
146 return this->publish_json(this->get_state_topic_to_(topic_buf), [this, traits](JsonObject root) {
147 // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
148 root[ESPHOME_F("state")] = cover_state_to_mqtt_str(this->cover_->current_operation, this->cover_->position,
149 traits.get_supports_position());
150 if (traits.get_supports_position()) {
151 root[ESPHOME_F("position")] = static_cast<int>(roundf(this->cover_->position * 100));
152 }
153 if (traits.get_supports_tilt()) {
154 root[ESPHOME_F("tilt")] = static_cast<int>(roundf(this->cover_->tilt * 100));
155 }
156 // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
157 });
158 }
159#endif
160 bool success = true;
161 if (traits.get_supports_position()) {
162 char pos[VALUE_ACCURACY_MAX_LEN];
163 size_t len = value_accuracy_to_buf(pos, roundf(this->cover_->position * 100), 0);
164 if (!this->publish(this->get_position_state_topic_to(topic_buf), pos, len))
165 success = false;
166 }
167 if (traits.get_supports_tilt()) {
168 char pos[VALUE_ACCURACY_MAX_LEN];
169 size_t len = value_accuracy_to_buf(pos, roundf(this->cover_->tilt * 100), 0);
170 if (!this->publish(this->get_tilt_state_topic_to(topic_buf), pos, len))
171 success = false;
172 }
173 if (!this->publish(this->get_state_topic_to_(topic_buf),
174 cover_state_to_mqtt_str(this->cover_->current_operation, this->cover_->position,
175 traits.get_supports_position())))
176 success = false;
177 return success;
178}
179
180} // namespace esphome::mqtt
181
182#endif
183#endif // USE_MQTT
StringRef get_device_class_ref() const
Get the device class as StringRef.
const StringRef & get_name() const
constexpr const char * c_str() const
Definition string_ref.h:73
CoverCall & set_command(const char *command)
Set the command as a string, "STOP", "OPEN", "CLOSE", "TOGGLE".
Definition cover.cpp:32
void perform()
Perform the cover call.
Definition cover.cpp:70
CoverCall & set_position(float position)
Set the call to a certain target position.
Definition cover.cpp:62
CoverCall & set_tilt(float tilt)
Set the call to a certain target tilt.
Definition cover.cpp:66
Base class for all cover devices.
Definition cover.h:110
CoverOperation current_operation
The current operation of the cover (idle, opening, closing).
Definition cover.h:115
void add_on_state_callback(std::function< void()> &&f)
Definition cover.cpp:142
CoverCall make_call()
Construct a new cover call used to control the cover.
Definition cover.cpp:140
float tilt
The current tilt value of the cover from 0.0 to 1.0.
Definition cover.h:123
float position
The position of the cover from 0.0 (fully closed) to 1.0 (fully open).
Definition cover.h:121
virtual CoverTraits get_traits()=0
bool get_supports_position() const
bool publish(const std::string &topic, const std::string &payload)
Send a MQTT message.
bool publish_json(const std::string &topic, const json::json_build_t &f)
Construct and send a JSON MQTT message.
StringRef get_state_topic_to_(std::span< char, MQTT_DEFAULT_TOPIC_MAX_LEN > buf) const
Get the MQTT state topic into a buffer (no heap allocation for non-lambda custom topics).
std::string get_command_topic_() const
Get the MQTT topic for listening to commands (allocates std::string).
void subscribe(const std::string &topic, mqtt_callback_t callback, uint8_t qos=0)
Subscribe to a MQTT topic.
void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override
state state bool send_initial_state() override
MQTTCoverComponent(cover::Cover *cover)
float position
Definition cover.h:0
CoverOperation
Enum encoding the current operation of a cover.
Definition cover.h:79
@ COVER_OPERATION_OPENING
The cover is currently opening.
Definition cover.h:83
@ COVER_OPERATION_CLOSING
The cover is currently closing.
Definition cover.h:85
MQTT_COMPONENT_TYPE(MQTTAlarmControlPanelComponent, "alarm_control_panel") const EntityBase *MQTTAlarmControlPanelComponent
size_t value_accuracy_to_buf(std::span< char, VALUE_ACCURACY_MAX_LEN > buf, float value, int8_t accuracy_decimals)
Format value with accuracy to buffer, returns chars written (excluding null)
Definition helpers.cpp:490
std::string size_t len
Definition helpers.h:817
optional< T > parse_number(const char *str)
Parse an unsigned decimal number from a null-terminated string.
Definition helpers.h:910
size_t size_t pos
Definition helpers.h:854
const __FlashStringHelper * ProgmemStr
Definition progmem.h:26
Simple Helper struct used for Home Assistant MQTT send_discovery().
bool command_topic
If the command topic should be included. Default to true.