Compare commits

...

3 Commits

Author SHA1 Message Date
J. Nick Koston
fcebfe6f48 cleanup 2026-01-19 14:26:19 -10:00
J. Nick Koston
2c10ebe16a tweaks 2026-01-19 14:24:57 -10:00
J. Nick Koston
2970d3d54f [mqtt] Reduce heap allocations in publish path 2026-01-19 14:05:32 -10:00
2 changed files with 40 additions and 17 deletions

View File

@@ -5,6 +5,7 @@
#include <utility>
#include "esphome/components/network/util.h"
#include "esphome/core/application.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/version.h"
@@ -66,10 +67,13 @@ void MQTTClientComponent::setup() {
"esphome/discover", [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); },
2);
std::string topic = "esphome/ping/";
topic.append(App.get_name());
// Format topic on stack - subscribe() copies it
// "esphome/ping/" (13) + name (ESPHOME_DEVICE_NAME_MAX_LEN) + null (1)
constexpr size_t ping_topic_buffer_size = 13 + ESPHOME_DEVICE_NAME_MAX_LEN + 1;
char ping_topic[ping_topic_buffer_size];
buf_append_printf(ping_topic, sizeof(ping_topic), 0, "esphome/ping/%s", App.get_name().c_str());
this->subscribe(
topic, [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); }, 2);
ping_topic, [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); }, 2);
}
if (this->enable_on_boot_) {
@@ -81,8 +85,11 @@ void MQTTClientComponent::send_device_info_() {
if (!this->is_connected() or !this->is_discovery_ip_enabled()) {
return;
}
std::string topic = "esphome/discover/";
topic.append(App.get_name());
// Format topic on stack to avoid heap allocation
// "esphome/discover/" (17) + name (ESPHOME_DEVICE_NAME_MAX_LEN) + null (1)
constexpr size_t topic_buffer_size = 17 + ESPHOME_DEVICE_NAME_MAX_LEN + 1;
char topic[topic_buffer_size];
buf_append_printf(topic, sizeof(topic), 0, "esphome/discover/%s", App.get_name().c_str());
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
this->publish_json(
@@ -500,39 +507,49 @@ bool MQTTClientComponent::publish(const std::string &topic, const std::string &p
bool MQTTClientComponent::publish(const std::string &topic, const char *payload, size_t payload_length, uint8_t qos,
bool retain) {
return publish({.topic = topic, .payload = std::string(payload, payload_length), .qos = qos, .retain = retain});
return this->publish(topic.c_str(), payload, payload_length, qos, retain);
}
bool MQTTClientComponent::publish(const MQTTMessage &message) {
return this->publish(message.topic.c_str(), message.payload.c_str(), message.payload.length(), message.qos,
message.retain);
}
bool MQTTClientComponent::publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos,
bool retain) {
return this->publish_json(topic.c_str(), f, qos, retain);
}
bool MQTTClientComponent::publish(const char *topic, const char *payload, size_t payload_length, uint8_t qos,
bool retain) {
if (!this->is_connected()) {
// critical components will re-transmit their messages
return false;
}
bool logging_topic = this->log_message_.topic == message.topic;
bool ret = this->mqtt_backend_.publish(message);
size_t topic_len = strlen(topic);
bool logging_topic = (topic_len == this->log_message_.topic.size()) &&
(memcmp(this->log_message_.topic.c_str(), topic, topic_len) == 0);
bool ret = this->mqtt_backend_.publish(topic, payload, payload_length, qos, retain);
delay(0);
if (!ret && !logging_topic && this->is_connected()) {
delay(0);
ret = this->mqtt_backend_.publish(message);
ret = this->mqtt_backend_.publish(topic, payload, payload_length, qos, retain);
delay(0);
}
if (!logging_topic) {
if (ret) {
ESP_LOGV(TAG, "Publish(topic='%s' payload='%s' retain=%d qos=%d)", message.topic.c_str(), message.payload.c_str(),
message.retain, message.qos);
ESP_LOGV(TAG, "Publish(topic='%s' retain=%d qos=%d)", topic, retain, qos);
ESP_LOGVV(TAG, "Publish payload (len=%u): '%.*s'", payload_length, static_cast<int>(payload_length), payload);
} else {
ESP_LOGV(TAG, "Publish failed for topic='%s' (len=%u). Will retry", message.topic.c_str(),
message.payload.length());
ESP_LOGV(TAG, "Publish failed for topic='%s' (len=%u). Will retry", topic, payload_length);
this->status_momentary_warning("publish", 1000);
}
}
return ret != 0;
}
bool MQTTClientComponent::publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos,
bool retain) {
bool MQTTClientComponent::publish_json(const char *topic, const json::json_build_t &f, uint8_t qos, bool retain) {
std::string message = json::build_json(f);
return this->publish(topic, message, qos, retain);
return this->publish(topic, message.c_str(), message.length(), qos, retain);
}
void MQTTClientComponent::enable() {

View File

@@ -229,6 +229,9 @@ class MQTTClientComponent : public Component
bool publish(const std::string &topic, const char *payload, size_t payload_length, uint8_t qos = 0,
bool retain = false);
/// Publish directly without creating MQTTMessage (avoids heap allocation for topic)
bool publish(const char *topic, const char *payload, size_t payload_length, uint8_t qos = 0, bool retain = false);
/** Construct and send a JSON MQTT message.
*
* @param topic The topic.
@@ -237,6 +240,9 @@ class MQTTClientComponent : public Component
*/
bool publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos = 0, bool retain = false);
/// Publish JSON directly without heap allocation for topic
bool publish_json(const char *topic, const json::json_build_t &f, uint8_t qos = 0, bool retain = false);
/// Setup the MQTT client, registering a bunch of callbacks and attempting to connect.
void setup() override;
void dump_config() override;