ESPHome  2024.9.0
mqtt_component.cpp
Go to the documentation of this file.
1 #include "mqtt_component.h"
2 
3 #ifdef USE_MQTT
4 
6 #include "esphome/core/helpers.h"
7 #include "esphome/core/log.h"
8 #include "esphome/core/version.h"
9 
10 #include "mqtt_const.h"
11 
12 namespace esphome {
13 namespace mqtt {
14 
15 static const char *const TAG = "mqtt.component";
16 
17 void MQTTComponent::set_qos(uint8_t qos) { this->qos_ = qos; }
18 
19 void MQTTComponent::set_retain(bool retain) { this->retain_ = retain; }
20 
21 std::string MQTTComponent::get_discovery_topic_(const MQTTDiscoveryInfo &discovery_info) const {
22  std::string sanitized_name = str_sanitize(App.get_name());
23  return discovery_info.prefix + "/" + this->component_type() + "/" + sanitized_name + "/" +
24  this->get_default_object_id_() + "/config";
25 }
26 
27 std::string MQTTComponent::get_default_topic_for_(const std::string &suffix) const {
28  const std::string &topic_prefix = global_mqtt_client->get_topic_prefix();
29  if (topic_prefix.empty()) {
30  // If the topic_prefix is null, the default topic should be null
31  return "";
32  }
33 
34  return topic_prefix + "/" + this->component_type() + "/" + this->get_default_object_id_() + "/" + suffix;
35 }
36 
37 std::string MQTTComponent::get_state_topic_() const {
38  if (this->has_custom_state_topic_)
39  return this->custom_state_topic_.str();
40  return this->get_default_topic_for_("state");
41 }
42 
43 std::string MQTTComponent::get_command_topic_() const {
44  if (this->has_custom_command_topic_)
45  return this->custom_command_topic_.str();
46  return this->get_default_topic_for_("command");
47 }
48 
49 bool MQTTComponent::publish(const std::string &topic, const std::string &payload) {
50  if (topic.empty())
51  return false;
52  return global_mqtt_client->publish(topic, payload, this->qos_, this->retain_);
53 }
54 
55 bool MQTTComponent::publish_json(const std::string &topic, const json::json_build_t &f) {
56  if (topic.empty())
57  return false;
58  return global_mqtt_client->publish_json(topic, f, this->qos_, this->retain_);
59 }
60 
62  const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info();
63 
64  if (discovery_info.clean) {
65  ESP_LOGV(TAG, "'%s': Cleaning discovery...", this->friendly_name().c_str());
66  return global_mqtt_client->publish(this->get_discovery_topic_(discovery_info), "", 0, this->qos_, true);
67  }
68 
69  ESP_LOGV(TAG, "'%s': Sending discovery...", this->friendly_name().c_str());
70 
72  this->get_discovery_topic_(discovery_info),
73  [this](JsonObject root) {
74  SendDiscoveryConfig config;
75  config.state_topic = true;
76  config.command_topic = true;
77 
78  this->send_discovery(root, config);
79 
80  // Fields from EntityBase
81  if (this->get_entity()->has_own_name()) {
82  root[MQTT_NAME] = this->friendly_name();
83  } else {
84  root[MQTT_NAME] = "";
85  }
86  if (this->is_disabled_by_default())
87  root[MQTT_ENABLED_BY_DEFAULT] = false;
88  if (!this->get_icon().empty())
89  root[MQTT_ICON] = this->get_icon();
90 
91  switch (this->get_entity()->get_entity_category()) {
93  break;
95  root[MQTT_ENTITY_CATEGORY] = "config";
96  break;
98  root[MQTT_ENTITY_CATEGORY] = "diagnostic";
99  break;
100  }
101 
102  if (config.state_topic)
103  root[MQTT_STATE_TOPIC] = this->get_state_topic_();
104  if (config.command_topic)
105  root[MQTT_COMMAND_TOPIC] = this->get_command_topic_();
106  if (this->command_retain_)
107  root[MQTT_COMMAND_RETAIN] = true;
108 
109  if (this->availability_ == nullptr) {
110  if (!global_mqtt_client->get_availability().topic.empty()) {
116  }
117  } else if (!this->availability_->topic.empty()) {
118  root[MQTT_AVAILABILITY_TOPIC] = this->availability_->topic;
119  if (this->availability_->payload_available != "online")
120  root[MQTT_PAYLOAD_AVAILABLE] = this->availability_->payload_available;
121  if (this->availability_->payload_not_available != "offline")
122  root[MQTT_PAYLOAD_NOT_AVAILABLE] = this->availability_->payload_not_available;
123  }
124 
125  std::string unique_id = this->unique_id();
126  const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info();
127  if (!unique_id.empty()) {
128  root[MQTT_UNIQUE_ID] = unique_id;
129  } else {
131  char friendly_name_hash[9];
132  sprintf(friendly_name_hash, "%08" PRIx32, fnv1_hash(this->friendly_name()));
133  friendly_name_hash[8] = 0; // ensure the hash-string ends with null
134  root[MQTT_UNIQUE_ID] = get_mac_address() + "-" + this->component_type() + "-" + friendly_name_hash;
135  } else {
136  // default to almost-unique ID. It's a hack but the only way to get that
137  // gorgeous device registry view.
138  root[MQTT_UNIQUE_ID] = "ESP" + this->component_type() + this->get_default_object_id_();
139  }
140  }
141 
142  const std::string &node_name = App.get_name();
144  root[MQTT_OBJECT_ID] = node_name + "_" + this->get_default_object_id_();
145 
146  std::string node_friendly_name = App.get_friendly_name();
147  if (node_friendly_name.empty()) {
148  node_friendly_name = node_name;
149  }
150  const std::string &node_area = App.get_area();
151 
152  JsonObject device_info = root.createNestedObject(MQTT_DEVICE);
153  const auto mac = get_mac_address();
154  device_info[MQTT_DEVICE_IDENTIFIERS] = mac;
155  device_info[MQTT_DEVICE_NAME] = node_friendly_name;
156 #ifdef ESPHOME_PROJECT_NAME
157  device_info[MQTT_DEVICE_SW_VERSION] = ESPHOME_PROJECT_VERSION " (ESPHome " ESPHOME_VERSION ")";
158  const char *model = std::strchr(ESPHOME_PROJECT_NAME, '.');
159  if (model == nullptr) { // must never happen but check anyway
160  device_info[MQTT_DEVICE_MODEL] = ESPHOME_BOARD;
161  device_info[MQTT_DEVICE_MANUFACTURER] = ESPHOME_PROJECT_NAME;
162  } else {
163  device_info[MQTT_DEVICE_MODEL] = model + 1;
164  device_info[MQTT_DEVICE_MANUFACTURER] = std::string(ESPHOME_PROJECT_NAME, model - ESPHOME_PROJECT_NAME);
165  }
166 #else
167  device_info[MQTT_DEVICE_SW_VERSION] = ESPHOME_VERSION " (" + App.get_compilation_time() + ")";
168  device_info[MQTT_DEVICE_MODEL] = ESPHOME_BOARD;
169 #if defined(USE_ESP8266) || defined(USE_ESP32)
170  device_info[MQTT_DEVICE_MANUFACTURER] = "Espressif";
171 #elif defined(USE_RP2040)
172  device_info[MQTT_DEVICE_MANUFACTURER] = "Raspberry Pi";
173 #elif defined(USE_BK72XX)
174  device_info[MQTT_DEVICE_MANUFACTURER] = "Beken";
175 #elif defined(USE_RTL87XX)
176  device_info[MQTT_DEVICE_MANUFACTURER] = "Realtek";
177 #elif defined(USE_HOST)
178  device_info[MQTT_DEVICE_MANUFACTURER] = "Host";
179 #endif
180 #endif
181  if (!node_area.empty()) {
182  device_info[MQTT_DEVICE_SUGGESTED_AREA] = node_area;
183  }
184 
185  device_info[MQTT_DEVICE_CONNECTIONS][0][0] = "mac";
186  device_info[MQTT_DEVICE_CONNECTIONS][0][1] = mac;
187  },
188  this->qos_, discovery_info.retain);
189 }
190 
191 uint8_t MQTTComponent::get_qos() const { return this->qos_; }
192 
193 bool MQTTComponent::get_retain() const { return this->retain_; }
194 
197 }
198 
200  return str_sanitize(str_snake_case(this->friendly_name()));
201 }
202 
203 void MQTTComponent::subscribe(const std::string &topic, mqtt_callback_t callback, uint8_t qos) {
204  global_mqtt_client->subscribe(topic, std::move(callback), qos);
205 }
206 
207 void MQTTComponent::subscribe_json(const std::string &topic, const mqtt_json_callback_t &callback, uint8_t qos) {
208  global_mqtt_client->subscribe_json(topic, callback, qos);
209 }
210 
211 MQTTComponent::MQTTComponent() = default;
212 
215 void MQTTComponent::set_custom_state_topic(const char *custom_state_topic) {
216  this->custom_state_topic_ = StringRef(custom_state_topic);
217  this->has_custom_state_topic_ = true;
218 }
219 void MQTTComponent::set_custom_command_topic(const char *custom_command_topic) {
220  this->custom_command_topic_ = StringRef(custom_command_topic);
221  this->has_custom_command_topic_ = true;
222 }
223 void MQTTComponent::set_command_retain(bool command_retain) { this->command_retain_ = command_retain; }
224 
225 void MQTTComponent::set_availability(std::string topic, std::string payload_available,
226  std::string payload_not_available) {
227  this->availability_ = make_unique<Availability>();
228  this->availability_->topic = std::move(topic);
229  this->availability_->payload_available = std::move(payload_available);
230  this->availability_->payload_not_available = std::move(payload_not_available);
231 }
234  if (this->is_internal())
235  return;
236 
237  this->setup();
238 
240 
241  if (!this->is_connected_())
242  return;
243 
244  if (this->is_discovery_enabled()) {
245  if (!this->send_discovery_()) {
246  this->schedule_resend_state();
247  }
248  }
249  if (!this->send_initial_state()) {
250  this->schedule_resend_state();
251  }
252 }
253 
255  if (this->is_internal())
256  return;
257 
258  this->loop();
259 
260  if (!this->resend_state_ || !this->is_connected_()) {
261  return;
262  }
263 
264  this->resend_state_ = false;
265  if (this->is_discovery_enabled()) {
266  if (!this->send_discovery_()) {
267  this->schedule_resend_state();
268  }
269  }
270  if (!this->send_initial_state()) {
271  this->schedule_resend_state();
272  }
273 }
275  if (this->is_internal())
276  return;
277 
278  this->dump_config();
279 }
281 std::string MQTTComponent::unique_id() { return ""; }
283 
284 // Pull these properties from EntityBase if not overridden
285 std::string MQTTComponent::friendly_name() const { return this->get_entity()->get_name(); }
286 std::string MQTTComponent::get_icon() const { return this->get_entity()->get_icon(); }
289  if (this->has_custom_state_topic_) {
290  // If the custom state_topic is null, return true as it is internal and should not publish
291  // else, return false, as it is explicitly set to a topic, so it is not internal and should publish
292  return this->get_state_topic_().empty();
293  }
294 
295  if (this->has_custom_command_topic_) {
296  // If the custom command_topic is null, return true as it is internal and should not publish
297  // else, return false, as it is explicitly set to a topic, so it is not internal and should publish
298  return this->get_command_topic_().empty();
299  }
300 
301  // No custom topics have been set
302  if (this->get_default_topic_for_("").empty()) {
303  // If the default topic prefix is null, then the component, by default, is internal and should not publish
304  return true;
305  }
306 
307  // Use ESPHome's component internal state if topic_prefix is not null with no custom state_topic or command_topic
308  return this->get_entity()->is_internal();
309 }
310 
311 } // namespace mqtt
312 } // namespace esphome
313 
314 #endif // USE_MQTT
float get_setup_priority() const override
MQTT_COMPONENT setup priority.
const Availability & get_availability()
std::string str_snake_case(const std::string &str)
Convert the string to snake case (lowercase with underscores).
Definition: helpers.cpp:281
std::string str() const
Definition: string_ref.h:73
virtual void loop()
This method will be called repeatedly.
Definition: component.cpp:50
std::string get_default_topic_for_(const std::string &suffix) const
Get this components state/command/...
constexpr const char *const MQTT_DEVICE_SW_VERSION
Definition: mqtt_const.h:64
const float AFTER_CONNECTION
For components that should be initialized after a data connection (API/MQTT) is connected.
Definition: component.cpp:27
constexpr const char *const MQTT_DEVICE_MODEL
Definition: mqtt_const.h:61
constexpr const char *const MQTT_NAME
Definition: mqtt_const.h:119
std::string topic
Empty means disabled.
Definition: mqtt_client.h:58
MQTTDiscoveryUniqueIdGenerator unique_id_generator
Definition: mqtt_client.h:84
Internal struct for MQTT Home Assistant discovery.
Definition: mqtt_client.h:79
std::function< void(const std::string &, const std::string &)> mqtt_callback_t
Callback for MQTT subscriptions.
Definition: mqtt_client.h:35
bool state_topic
If the state topic should be included. Defaults to true.
constexpr const char *const MQTT_PAYLOAD_AVAILABLE
Definition: mqtt_const.h:135
StringRef is a reference to a string owned by something else.
Definition: string_ref.h:21
constexpr const char *const MQTT_ENTITY_CATEGORY
Definition: mqtt_const.h:74
const std::string & get_area() const
Get the area of this Application set by pre_setup().
Definition: application.h:208
const std::string & get_friendly_name() const
Get the friendly name of this Application set by pre_setup().
Definition: application.h:205
void set_custom_state_topic(const char *custom_state_topic)
Set a custom state topic. Set to "" for default behavior.
constexpr const char *const MQTT_PAYLOAD_NOT_AVAILABLE
Definition: mqtt_const.h:146
void disable_discovery()
Disable discovery. Sets friendly name to "".
constexpr const char *const MQTT_DEVICE_IDENTIFIERS
Definition: mqtt_const.h:59
std::string prefix
The Home Assistant discovery prefix. Empty means disabled.
Definition: mqtt_client.h:80
bool publish_json(const std::string &topic, const json::json_build_t &f)
Construct and send a JSON MQTT message.
const std::string & get_topic_prefix() const
Get the topic prefix of this device, using default if necessary.
virtual void dump_config()
Definition: component.cpp:186
void subscribe_json(const std::string &topic, const mqtt_json_callback_t &callback, uint8_t qos=0)
Subscribe to a MQTT topic and automatically parse JSON payload.
constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA
Definition: mqtt_const.h:63
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.
virtual void send_discovery(JsonObject root, SendDiscoveryConfig &config)=0
Send discovery info the Home Assistant, override this.
MQTTClientComponent * global_mqtt_client
std::string get_icon() const
Definition: entity_base.cpp:30
void set_custom_command_topic(const char *custom_command_topic)
Set a custom command topic. Set to "" for default behavior.
constexpr const char *const MQTT_STATE_TOPIC
Definition: mqtt_const.h:219
void subscribe(const std::string &topic, mqtt_callback_t callback, uint8_t qos=0)
Subscribe to a MQTT topic.
void set_retain(bool retain)
Set whether state message should be retained.
constexpr const char *const MQTT_DEVICE_NAME
Definition: mqtt_const.h:62
void set_availability(std::string topic, std::string payload_available, std::string payload_not_available)
Set the Home Assistant availability data.
virtual void setup()
Where the component&#39;s initialization should happen.
Definition: component.cpp:48
virtual const EntityBase * get_entity() const =0
Gets the Entity served by this MQTT component.
virtual std::string component_type() const =0
Override this method to return the component type (e.g. "light", "sensor", ...)
void register_mqtt_component(MQTTComponent *component)
bool publish(const MQTTMessage &message)
Publish a MQTTMessage.
std::string get_mac_address()
Get the device MAC address as a string, in lowercase hex notation.
Definition: helpers.cpp:688
constexpr const char *const MQTT_COMMAND_TOPIC
Definition: mqtt_const.h:50
bool is_internal() const
Definition: entity_base.cpp:22
Simple Helper struct used for Home Assistant MQTT send_discovery().
virtual bool send_initial_state()=0
std::function< void(JsonObject)> json_build_t
Callback function typedef for building JsonObjects.
Definition: json_util.h:20
Application App
Global storage of Application pointer - only one Application can exist.
const std::string & get_name() const
Get the name of this Application set by pre_setup().
Definition: application.h:202
constexpr const char *const MQTT_DEVICE_MANUFACTURER
Definition: mqtt_const.h:60
bool publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos=0, bool retain=false)
Construct and send a JSON MQTT message.
constexpr const char *const MQTT_COMMAND_RETAIN
Definition: mqtt_const.h:48
const MQTTDiscoveryInfo & get_discovery_info() const
Get Home Assistant discovery info.
void call_setup() override
Override setup_ so that we can call send_discovery() when needed.
std::string str_sanitize(const std::string &str)
Sanitizes the input string by removing all characters but alphanumerics, dashes and underscores...
Definition: helpers.cpp:288
void set_qos(uint8_t qos)
Set QOS for state messages.
MQTTComponent()
Constructs a MQTTComponent.
uint32_t fnv1_hash(const std::string &str)
Calculate a FNV-1 hash of str.
Definition: helpers.cpp:184
virtual std::string get_icon() const
Get the icon field of this component.
virtual bool is_disabled_by_default() const
Get whether the underlying Entity is disabled by default.
constexpr const char *const MQTT_ICON
Definition: mqtt_const.h:98
std::unique_ptr< Availability > availability_
virtual std::string friendly_name() const
Get the friendly name of this MQTT component.
constexpr const char *const MQTT_OBJECT_ID
Definition: mqtt_const.h:120
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
std::string payload_not_available
Definition: mqtt_client.h:60
MQTTDiscoveryObjectIdGenerator object_id_generator
Definition: mqtt_client.h:85
void subscribe_json(const std::string &topic, const mqtt_json_callback_t &callback, uint8_t qos=0)
Subscribe to a MQTT topic and automatically parse JSON payload.
constexpr const char *const MQTT_ENABLED_BY_DEFAULT
Definition: mqtt_const.h:73
constexpr const char *const MQTT_UNIQUE_ID
Definition: mqtt_const.h:258
constexpr const char *const MQTT_AVAILABILITY_TOPIC
Definition: mqtt_const.h:20
std::string get_state_topic_() const
Get the MQTT topic that new states will be shared to.
virtual std::string unique_id()
A unique ID for this MQTT component, empty for no unique id.
void set_command_retain(bool command_retain)
Set whether command message should be retained.
std::string get_compilation_time() const
Definition: application.h:215
std::string get_default_object_id_() const
Generate the Home Assistant MQTT discovery object id by automatically transforming the friendly name...
std::string get_command_topic_() const
Get the MQTT topic for listening to commands.
bool is_disabled_by_default() const
Definition: entity_base.cpp:26
void subscribe(const std::string &topic, mqtt_callback_t callback, uint8_t qos=0)
Subscribe to an MQTT topic and call callback when a message is received.
constexpr const char *const MQTT_DEVICE
Definition: mqtt_const.h:56
const StringRef & get_name() const
Definition: entity_base.cpp:10
std::string get_discovery_topic_(const MQTTDiscoveryInfo &discovery_info) const
Helper method to get the discovery topic for this component.
constexpr const char *const MQTT_DEVICE_CONNECTIONS
Definition: mqtt_const.h:58
bool retain
Whether to retain discovery messages.
Definition: mqtt_client.h:81
bool send_discovery_()
Internal method to start sending discovery info, this will call send_discovery(). ...
std::function< void(const std::string &, JsonObject)> mqtt_json_callback_t
Definition: mqtt_client.h:36
void schedule_resend_state()
Internal method for the MQTT client base to schedule a resend of the state on reconnect.