From b48d4ab785f1deb84d59865b2a0abb9adc69a44f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 19 Jan 2026 17:37:54 -1000 Subject: [PATCH] [mqtt] Reduce heap allocations in publish path (#13372) --- esphome/components/mqtt/mqtt_client.cpp | 51 ++++++++++++++++--------- esphome/components/mqtt/mqtt_client.h | 6 +++ 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index f8517c4f89..96eba37a57 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -5,6 +5,7 @@ #include #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(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() { diff --git a/esphome/components/mqtt/mqtt_client.h b/esphome/components/mqtt/mqtt_client.h index 9e9db03b19..38bc0b4da3 100644 --- a/esphome/components/mqtt/mqtt_client.h +++ b/esphome/components/mqtt/mqtt_client.h @@ -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;