From 40f108116b0b4dd28f0bc701acb7138eb38145a8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 8 Jan 2026 11:42:18 -1000 Subject: [PATCH] [mqtt] Reduce heap allocations in topic string building (#13072) --- esphome/components/mqtt/__init__.py | 17 +++- .../mqtt/mqtt_alarm_control_panel.cpp | 2 +- .../mqtt/mqtt_alarm_control_panel.h | 2 +- .../components/mqtt/mqtt_binary_sensor.cpp | 2 +- esphome/components/mqtt/mqtt_binary_sensor.h | 2 +- esphome/components/mqtt/mqtt_button.cpp | 2 +- esphome/components/mqtt/mqtt_button.h | 2 +- esphome/components/mqtt/mqtt_climate.cpp | 2 +- esphome/components/mqtt/mqtt_climate.h | 2 +- esphome/components/mqtt/mqtt_component.cpp | 77 ++++++++++++++++--- esphome/components/mqtt/mqtt_component.h | 21 ++++- esphome/components/mqtt/mqtt_cover.cpp | 2 +- esphome/components/mqtt/mqtt_cover.h | 2 +- esphome/components/mqtt/mqtt_date.cpp | 2 +- esphome/components/mqtt/mqtt_date.h | 2 +- esphome/components/mqtt/mqtt_datetime.cpp | 2 +- esphome/components/mqtt/mqtt_datetime.h | 2 +- esphome/components/mqtt/mqtt_event.cpp | 2 +- esphome/components/mqtt/mqtt_event.h | 2 +- esphome/components/mqtt/mqtt_fan.cpp | 2 +- esphome/components/mqtt/mqtt_fan.h | 2 +- esphome/components/mqtt/mqtt_light.cpp | 2 +- esphome/components/mqtt/mqtt_light.h | 2 +- esphome/components/mqtt/mqtt_lock.cpp | 2 +- esphome/components/mqtt/mqtt_lock.h | 2 +- esphome/components/mqtt/mqtt_number.cpp | 2 +- esphome/components/mqtt/mqtt_number.h | 2 +- esphome/components/mqtt/mqtt_select.cpp | 2 +- esphome/components/mqtt/mqtt_select.h | 2 +- esphome/components/mqtt/mqtt_sensor.cpp | 2 +- esphome/components/mqtt/mqtt_sensor.h | 2 +- esphome/components/mqtt/mqtt_switch.cpp | 2 +- esphome/components/mqtt/mqtt_switch.h | 2 +- esphome/components/mqtt/mqtt_text.cpp | 2 +- esphome/components/mqtt/mqtt_text.h | 2 +- esphome/components/mqtt/mqtt_text_sensor.cpp | 2 +- esphome/components/mqtt/mqtt_text_sensor.h | 2 +- esphome/components/mqtt/mqtt_time.cpp | 2 +- esphome/components/mqtt/mqtt_time.h | 2 +- esphome/components/mqtt/mqtt_update.cpp | 2 +- esphome/components/mqtt/mqtt_update.h | 2 +- esphome/components/mqtt/mqtt_valve.cpp | 2 +- esphome/components/mqtt/mqtt_valve.h | 2 +- esphome/core/config.py | 2 + esphome/core/entity_base.h | 5 +- 45 files changed, 145 insertions(+), 57 deletions(-) diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index e73de49fef..f01c928b30 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -77,6 +77,13 @@ CONF_DISCOVER_IP = "discover_ip" CONF_IDF_SEND_ASYNC = "idf_send_async" CONF_WAIT_FOR_CONNECTION = "wait_for_connection" +# Max lengths for stack-based topic building. +# These values are used in cv.Length() validators below to ensure the C++ code +# in mqtt_component.cpp can safely use fixed-size stack buffers without overflow. +# If you change these, update the corresponding constants in mqtt_component.cpp. +TOPIC_PREFIX_MAX_LEN = 64 # Default is device name, typically short +DISCOVERY_PREFIX_MAX_LEN = 64 # Default is "homeassistant" (13 chars) + def validate_message_just_topic(value): value = cv.publish_topic(value) @@ -253,9 +260,9 @@ CONFIG_SCHEMA = cv.All( ), cv.Optional(CONF_DISCOVERY_RETAIN, default=True): cv.boolean, cv.Optional(CONF_DISCOVER_IP, default=True): cv.boolean, - cv.Optional( - CONF_DISCOVERY_PREFIX, default="homeassistant" - ): cv.publish_topic, + cv.Optional(CONF_DISCOVERY_PREFIX, default="homeassistant"): cv.All( + cv.publish_topic, cv.Length(max=DISCOVERY_PREFIX_MAX_LEN) + ), cv.Optional(CONF_DISCOVERY_UNIQUE_ID_GENERATOR, default="legacy"): cv.enum( MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS ), @@ -266,7 +273,9 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_BIRTH_MESSAGE): MQTT_MESSAGE_SCHEMA, cv.Optional(CONF_WILL_MESSAGE): MQTT_MESSAGE_SCHEMA, cv.Optional(CONF_SHUTDOWN_MESSAGE): MQTT_MESSAGE_SCHEMA, - cv.Optional(CONF_TOPIC_PREFIX, default=lambda: CORE.name): cv.publish_topic, + cv.Optional(CONF_TOPIC_PREFIX, default=lambda: CORE.name): cv.All( + cv.publish_topic, cv.Length(max=TOPIC_PREFIX_MAX_LEN) + ), cv.Optional(CONF_LOG_TOPIC): cv.Any( None, MQTT_MESSAGE_BASE.extend( diff --git a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp index eb46c3b10c..6245d10882 100644 --- a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp +++ b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp @@ -79,7 +79,7 @@ void MQTTAlarmControlPanelComponent::send_discovery(JsonObject root, mqtt::SendD root[MQTT_CODE_ARM_REQUIRED] = this->alarm_control_panel_->get_requires_code_to_arm(); } -std::string MQTTAlarmControlPanelComponent::component_type() const { return "alarm_control_panel"; } +MQTT_COMPONENT_TYPE(MQTTAlarmControlPanelComponent, "alarm_control_panel") const EntityBase *MQTTAlarmControlPanelComponent::get_entity() const { return this->alarm_control_panel_; } bool MQTTAlarmControlPanelComponent::send_initial_state() { return this->publish_state(); } diff --git a/esphome/components/mqtt/mqtt_alarm_control_panel.h b/esphome/components/mqtt/mqtt_alarm_control_panel.h index cf4fac1511..89a0ff1be8 100644 --- a/esphome/components/mqtt/mqtt_alarm_control_panel.h +++ b/esphome/components/mqtt/mqtt_alarm_control_panel.h @@ -25,7 +25,7 @@ class MQTTAlarmControlPanelComponent : public mqtt::MQTTComponent { void dump_config() override; protected: - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; alarm_control_panel::AlarmControlPanel *alarm_control_panel_; diff --git a/esphome/components/mqtt/mqtt_binary_sensor.cpp b/esphome/components/mqtt/mqtt_binary_sensor.cpp index 146ca46f68..a37043406b 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.cpp +++ b/esphome/components/mqtt/mqtt_binary_sensor.cpp @@ -10,7 +10,7 @@ namespace esphome::mqtt { static const char *const TAG = "mqtt.binary_sensor"; -std::string MQTTBinarySensorComponent::component_type() const { return "binary_sensor"; } +MQTT_COMPONENT_TYPE(MQTTBinarySensorComponent, "binary_sensor") const EntityBase *MQTTBinarySensorComponent::get_entity() const { return this->binary_sensor_; } void MQTTBinarySensorComponent::setup() { diff --git a/esphome/components/mqtt/mqtt_binary_sensor.h b/esphome/components/mqtt/mqtt_binary_sensor.h index 82176ec97b..5917a9966c 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.h +++ b/esphome/components/mqtt/mqtt_binary_sensor.h @@ -29,7 +29,7 @@ class MQTTBinarySensorComponent : public mqtt::MQTTComponent { bool publish_state(bool state); protected: - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; binary_sensor::BinarySensor *binary_sensor_; diff --git a/esphome/components/mqtt/mqtt_button.cpp b/esphome/components/mqtt/mqtt_button.cpp index 2b700a4962..718fe93016 100644 --- a/esphome/components/mqtt/mqtt_button.cpp +++ b/esphome/components/mqtt/mqtt_button.cpp @@ -39,7 +39,7 @@ void MQTTButtonComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCon // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) } -std::string MQTTButtonComponent::component_type() const { return "button"; } +MQTT_COMPONENT_TYPE(MQTTButtonComponent, "button") const EntityBase *MQTTButtonComponent::get_entity() const { return this->button_; } } // namespace esphome::mqtt diff --git a/esphome/components/mqtt/mqtt_button.h b/esphome/components/mqtt/mqtt_button.h index ec802664df..a2db64d39d 100644 --- a/esphome/components/mqtt/mqtt_button.h +++ b/esphome/components/mqtt/mqtt_button.h @@ -26,7 +26,7 @@ class MQTTButtonComponent : public mqtt::MQTTComponent { protected: /// "button" component type. - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; button::Button *button_; diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index d402fff6e6..77aabb2461 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -254,7 +254,7 @@ void MQTTClimateComponent::setup() { } MQTTClimateComponent::MQTTClimateComponent(Climate *device) : device_(device) {} bool MQTTClimateComponent::send_initial_state() { return this->publish_state_(); } -std::string MQTTClimateComponent::component_type() const { return "climate"; } +MQTT_COMPONENT_TYPE(MQTTClimateComponent, "climate") const EntityBase *MQTTClimateComponent::get_entity() const { return this->device_; } bool MQTTClimateComponent::publish_state_() { diff --git a/esphome/components/mqtt/mqtt_climate.h b/esphome/components/mqtt/mqtt_climate.h index f561627ac9..f0715929d4 100644 --- a/esphome/components/mqtt/mqtt_climate.h +++ b/esphome/components/mqtt/mqtt_climate.h @@ -15,7 +15,7 @@ class MQTTClimateComponent : public mqtt::MQTTComponent { MQTTClimateComponent(climate::Climate *device); void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override; bool send_initial_state() override; - std::string component_type() const override; + const char *component_type() const override; void setup() override; MQTT_COMPONENT_CUSTOM_TOPIC(current_temperature, state) diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index ccbdb2ea91..d838d1789f 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -13,6 +13,34 @@ namespace esphome::mqtt { static const char *const TAG = "mqtt.component"; +// Helper functions for building topic strings on stack +inline char *append_str(char *p, const char *s, size_t len) { + memcpy(p, s, len); + return p + len; +} + +inline char *append_char(char *p, char c) { + *p = c; + return p + 1; +} + +// Max lengths for stack-based topic building. +// These limits are enforced at Python config validation time in mqtt/__init__.py +// using cv.Length() validators for topic_prefix and discovery_prefix. +// MQTT_COMPONENT_TYPE_MAX_LEN and MQTT_SUFFIX_MAX_LEN are defined in mqtt_component.h. +// ESPHOME_DEVICE_NAME_MAX_LEN and OBJECT_ID_MAX_LEN are defined in entity_base.h. +// This ensures the stack buffers below are always large enough. +static constexpr size_t TOPIC_PREFIX_MAX_LEN = 64; // Validated in Python: cv.Length(max=64) +static constexpr size_t DISCOVERY_PREFIX_MAX_LEN = 64; // Validated in Python: cv.Length(max=64) + +// Stack buffer sizes - safe because all inputs are length-validated at config time +// Format: prefix + "/" + type + "/" + object_id + "/" + suffix + null +static constexpr size_t DEFAULT_TOPIC_MAX_LEN = + TOPIC_PREFIX_MAX_LEN + 1 + MQTT_COMPONENT_TYPE_MAX_LEN + 1 + OBJECT_ID_MAX_LEN + 1 + MQTT_SUFFIX_MAX_LEN + 1; +// Format: prefix + "/" + type + "/" + name + "/" + object_id + "/config" + null +static constexpr size_t DISCOVERY_TOPIC_MAX_LEN = DISCOVERY_PREFIX_MAX_LEN + 1 + MQTT_COMPONENT_TYPE_MAX_LEN + 1 + + ESPHOME_DEVICE_NAME_MAX_LEN + 1 + OBJECT_ID_MAX_LEN + 7 + 1; + void MQTTComponent::set_qos(uint8_t qos) { this->qos_ = qos; } void MQTTComponent::set_subscribe_qos(uint8_t qos) { this->subscribe_qos_ = qos; } @@ -21,8 +49,23 @@ void MQTTComponent::set_retain(bool retain) { this->retain_ = retain; } std::string MQTTComponent::get_discovery_topic_(const MQTTDiscoveryInfo &discovery_info) const { std::string sanitized_name = str_sanitize(App.get_name()); - return discovery_info.prefix + "/" + this->component_type() + "/" + sanitized_name + "/" + - this->get_default_object_id_() + "/config"; + const char *comp_type = this->component_type(); + char object_id_buf[OBJECT_ID_MAX_LEN]; + StringRef object_id = this->get_default_object_id_to_(object_id_buf); + + char buf[DISCOVERY_TOPIC_MAX_LEN]; + char *p = buf; + + p = append_str(p, discovery_info.prefix.data(), discovery_info.prefix.size()); + p = append_char(p, '/'); + p = append_str(p, comp_type, strlen(comp_type)); + p = append_char(p, '/'); + p = append_str(p, sanitized_name.data(), sanitized_name.size()); + p = append_char(p, '/'); + p = append_str(p, object_id.c_str(), object_id.size()); + p = append_str(p, "/config", 7); + + return std::string(buf, p - buf); } std::string MQTTComponent::get_default_topic_for_(const std::string &suffix) const { @@ -32,7 +75,22 @@ std::string MQTTComponent::get_default_topic_for_(const std::string &suffix) con return ""; } - return topic_prefix + "/" + this->component_type() + "/" + this->get_default_object_id_() + "/" + suffix; + const char *comp_type = this->component_type(); + char object_id_buf[OBJECT_ID_MAX_LEN]; + StringRef object_id = this->get_default_object_id_to_(object_id_buf); + + char buf[DEFAULT_TOPIC_MAX_LEN]; + char *p = buf; + + p = append_str(p, topic_prefix.data(), topic_prefix.size()); + p = append_char(p, '/'); + p = append_str(p, comp_type, strlen(comp_type)); + p = append_char(p, '/'); + p = append_str(p, object_id.c_str(), object_id.size()); + p = append_char(p, '/'); + p = append_str(p, suffix.data(), suffix.size()); + + return std::string(buf, p - buf); } std::string MQTTComponent::get_state_topic_() const { @@ -123,6 +181,8 @@ bool MQTTComponent::send_discovery_() { } const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info(); + char object_id_buf[OBJECT_ID_MAX_LEN]; + StringRef object_id = this->get_default_object_id_to_(object_id_buf); if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) { char friendly_name_hash[9]; sprintf(friendly_name_hash, "%08" PRIx32, fnv1_hash(this->friendly_name_())); @@ -131,12 +191,12 @@ bool MQTTComponent::send_discovery_() { } else { // default to almost-unique ID. It's a hack but the only way to get that // gorgeous device registry view. - root[MQTT_UNIQUE_ID] = "ESP" + this->component_type() + this->get_default_object_id_(); + root[MQTT_UNIQUE_ID] = "ESP" + std::string(this->component_type()) + object_id.c_str(); } const std::string &node_name = App.get_name(); if (discovery_info.object_id_generator == MQTT_DEVICE_NAME_OBJECT_ID_GENERATOR) - root[MQTT_OBJECT_ID] = node_name + "_" + this->get_default_object_id_(); + root[MQTT_OBJECT_ID] = node_name + "_" + object_id.c_str(); const std::string &friendly_name_ref = App.get_friendly_name(); const std::string &node_friendly_name = friendly_name_ref.empty() ? node_name : friendly_name_ref; @@ -194,10 +254,6 @@ bool MQTTComponent::is_discovery_enabled() const { return this->discovery_enabled_ && global_mqtt_client->is_discovery_enabled(); } -std::string MQTTComponent::get_default_object_id_() const { - return str_sanitize(str_snake_case(this->friendly_name_())); -} - void MQTTComponent::subscribe(const std::string &topic, mqtt_callback_t callback, uint8_t qos) { global_mqtt_client->subscribe(topic, std::move(callback), qos); } @@ -280,6 +336,9 @@ bool MQTTComponent::is_connected_() const { return global_mqtt_client->is_connec // Pull these properties from EntityBase if not overridden std::string MQTTComponent::friendly_name_() const { return this->get_entity()->get_name(); } +StringRef MQTTComponent::get_default_object_id_to_(std::span buf) const { + return this->get_entity()->get_object_id_to(buf); +} StringRef MQTTComponent::get_icon_ref_() const { return this->get_entity()->get_icon_ref(); } bool MQTTComponent::is_disabled_by_default_() const { return this->get_entity()->is_disabled_by_default(); } bool MQTTComponent::is_internal() { diff --git a/esphome/components/mqtt/mqtt_component.h b/esphome/components/mqtt/mqtt_component.h index e5f9664f77..e0b751f05f 100644 --- a/esphome/components/mqtt/mqtt_component.h +++ b/esphome/components/mqtt/mqtt_component.h @@ -19,6 +19,10 @@ struct SendDiscoveryConfig { bool command_topic{true}; ///< If the command topic should be included. Default to true. }; +// Max lengths for stack-based topic building (must match mqtt_component.cpp) +static constexpr size_t MQTT_COMPONENT_TYPE_MAX_LEN = 20; +static constexpr size_t MQTT_SUFFIX_MAX_LEN = 32; + #define LOG_MQTT_COMPONENT(state_topic, command_topic) \ if (state_topic) { \ ESP_LOGCONFIG(TAG, " State Topic: '%s'", this->get_state_topic_().c_str()); \ @@ -27,7 +31,18 @@ struct SendDiscoveryConfig { ESP_LOGCONFIG(TAG, " Command Topic: '%s'", this->get_command_topic_().c_str()); \ } +// Macro to define component_type() with compile-time length verification +// Usage: MQTT_COMPONENT_TYPE(MQTTSensorComponent, "sensor") +#define MQTT_COMPONENT_TYPE(class_name, type_str) \ + const char *class_name::component_type() const { return type_str; } \ + static_assert(sizeof(type_str) - 1 <= MQTT_COMPONENT_TYPE_MAX_LEN, \ + #class_name "::component_type() exceeds MQTT_COMPONENT_TYPE_MAX_LEN"); + +// Macro to define custom topic getter/setter with compile-time suffix length verification #define MQTT_COMPONENT_CUSTOM_TOPIC_(name, type) \ + static_assert(sizeof(#name "/" #type) - 1 <= MQTT_SUFFIX_MAX_LEN, \ + "topic suffix " #name "/" #type " exceeds MQTT_SUFFIX_MAX_LEN"); \ +\ protected: \ std::string custom_##name##_##type##_topic_{}; \ \ @@ -92,7 +107,7 @@ class MQTTComponent : public Component { void set_subscribe_qos(uint8_t qos); /// Override this method to return the component type (e.g. "light", "sensor", ...) - virtual std::string component_type() const = 0; + virtual const char *component_type() const = 0; /// Set a custom state topic. Set to "" for default behavior. void set_custom_state_topic(const char *custom_state_topic); @@ -185,8 +200,8 @@ class MQTTComponent : public Component { // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) - /// Generate the Home Assistant MQTT discovery object id by automatically transforming the friendly name. - std::string get_default_object_id_() const; + /// Get the object ID for this MQTT component, writing to the provided buffer. + StringRef get_default_object_id_to_(std::span buf) const; StringRef custom_state_topic_{}; StringRef custom_command_topic_{}; diff --git a/esphome/components/mqtt/mqtt_cover.cpp b/esphome/components/mqtt/mqtt_cover.cpp index e628ac37a9..4505027485 100644 --- a/esphome/components/mqtt/mqtt_cover.cpp +++ b/esphome/components/mqtt/mqtt_cover.cpp @@ -90,7 +90,7 @@ void MQTTCoverComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConf } } -std::string MQTTCoverComponent::component_type() const { return "cover"; } +MQTT_COMPONENT_TYPE(MQTTCoverComponent, "cover") const EntityBase *MQTTCoverComponent::get_entity() const { return this->cover_; } bool MQTTCoverComponent::send_initial_state() { return this->publish_state(); } diff --git a/esphome/components/mqtt/mqtt_cover.h b/esphome/components/mqtt/mqtt_cover.h index 6b874af16a..13582d14d1 100644 --- a/esphome/components/mqtt/mqtt_cover.h +++ b/esphome/components/mqtt/mqtt_cover.h @@ -29,7 +29,7 @@ class MQTTCoverComponent : public mqtt::MQTTComponent { void dump_config() override; protected: - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; cover::Cover *cover_; diff --git a/esphome/components/mqtt/mqtt_date.cpp b/esphome/components/mqtt/mqtt_date.cpp index 1715384c5f..dba7c1a671 100644 --- a/esphome/components/mqtt/mqtt_date.cpp +++ b/esphome/components/mqtt/mqtt_date.cpp @@ -39,7 +39,7 @@ void MQTTDateComponent::dump_config() { LOG_MQTT_COMPONENT(true, true) } -std::string MQTTDateComponent::component_type() const { return "date"; } +MQTT_COMPONENT_TYPE(MQTTDateComponent, "date") const EntityBase *MQTTDateComponent::get_entity() const { return this->date_; } void MQTTDateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { diff --git a/esphome/components/mqtt/mqtt_date.h b/esphome/components/mqtt/mqtt_date.h index 380bb69e0e..4a626becb2 100644 --- a/esphome/components/mqtt/mqtt_date.h +++ b/esphome/components/mqtt/mqtt_date.h @@ -31,7 +31,7 @@ class MQTTDateComponent : public mqtt::MQTTComponent { bool publish_state(uint16_t year, uint8_t month, uint8_t day); protected: - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; datetime::DateEntity *date_; diff --git a/esphome/components/mqtt/mqtt_datetime.cpp b/esphome/components/mqtt/mqtt_datetime.cpp index 79a2c82180..5f1cf19b97 100644 --- a/esphome/components/mqtt/mqtt_datetime.cpp +++ b/esphome/components/mqtt/mqtt_datetime.cpp @@ -50,7 +50,7 @@ void MQTTDateTimeComponent::dump_config() { LOG_MQTT_COMPONENT(true, true) } -std::string MQTTDateTimeComponent::component_type() const { return "datetime"; } +MQTT_COMPONENT_TYPE(MQTTDateTimeComponent, "datetime") const EntityBase *MQTTDateTimeComponent::get_entity() const { return this->datetime_; } void MQTTDateTimeComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { diff --git a/esphome/components/mqtt/mqtt_datetime.h b/esphome/components/mqtt/mqtt_datetime.h index 8706bfcf75..d02d6f579c 100644 --- a/esphome/components/mqtt/mqtt_datetime.h +++ b/esphome/components/mqtt/mqtt_datetime.h @@ -31,7 +31,7 @@ class MQTTDateTimeComponent : public mqtt::MQTTComponent { bool publish_state(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second); protected: - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; datetime::DateTimeEntity *datetime_; diff --git a/esphome/components/mqtt/mqtt_event.cpp b/esphome/components/mqtt/mqtt_event.cpp index 67a7aab5bd..42fbc1eabd 100644 --- a/esphome/components/mqtt/mqtt_event.cpp +++ b/esphome/components/mqtt/mqtt_event.cpp @@ -50,7 +50,7 @@ bool MQTTEventComponent::publish_event_(const std::string &event_type) { }); } -std::string MQTTEventComponent::component_type() const { return "event"; } +MQTT_COMPONENT_TYPE(MQTTEventComponent, "event") const EntityBase *MQTTEventComponent::get_entity() const { return this->event_; } } // namespace esphome::mqtt diff --git a/esphome/components/mqtt/mqtt_event.h b/esphome/components/mqtt/mqtt_event.h index fc6e778d44..e6d5b6f278 100644 --- a/esphome/components/mqtt/mqtt_event.h +++ b/esphome/components/mqtt/mqtt_event.h @@ -25,7 +25,7 @@ class MQTTEventComponent : public mqtt::MQTTComponent { protected: bool publish_event_(const std::string &event_type); - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; event::Event *event_; diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index ffecd9c663..bd6c98b679 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -15,7 +15,7 @@ using namespace esphome::fan; MQTTFanComponent::MQTTFanComponent(Fan *state) : state_(state) {} Fan *MQTTFanComponent::get_state() const { return this->state_; } -std::string MQTTFanComponent::component_type() const { return "fan"; } +MQTT_COMPONENT_TYPE(MQTTFanComponent, "fan") const EntityBase *MQTTFanComponent::get_entity() const { return this->state_; } void MQTTFanComponent::setup() { diff --git a/esphome/components/mqtt/mqtt_fan.h b/esphome/components/mqtt/mqtt_fan.h index 16ce246853..43ef67e733 100644 --- a/esphome/components/mqtt/mqtt_fan.h +++ b/esphome/components/mqtt/mqtt_fan.h @@ -36,7 +36,7 @@ class MQTTFanComponent : public mqtt::MQTTComponent { bool send_initial_state() override; bool publish_state(); /// 'fan' component type for discovery. - std::string component_type() const override; + const char *component_type() const override; fan::Fan *get_state() const; diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp index 0dafe487ff..2d588ed10b 100644 --- a/esphome/components/mqtt/mqtt_light.cpp +++ b/esphome/components/mqtt/mqtt_light.cpp @@ -14,7 +14,7 @@ static const char *const TAG = "mqtt.light"; using namespace esphome::light; -std::string MQTTJSONLightComponent::component_type() const { return "light"; } +MQTT_COMPONENT_TYPE(MQTTJSONLightComponent, "light") const EntityBase *MQTTJSONLightComponent::get_entity() const { return this->state_; } void MQTTJSONLightComponent::setup() { diff --git a/esphome/components/mqtt/mqtt_light.h b/esphome/components/mqtt/mqtt_light.h index 2cc631c901..41981655ef 100644 --- a/esphome/components/mqtt/mqtt_light.h +++ b/esphome/components/mqtt/mqtt_light.h @@ -28,7 +28,7 @@ class MQTTJSONLightComponent : public mqtt::MQTTComponent, public light::LightRe void on_light_remote_values_update() override; protected: - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; bool publish_state_(); diff --git a/esphome/components/mqtt/mqtt_lock.cpp b/esphome/components/mqtt/mqtt_lock.cpp index 58fa675eb7..43ef60bdf4 100644 --- a/esphome/components/mqtt/mqtt_lock.cpp +++ b/esphome/components/mqtt/mqtt_lock.cpp @@ -34,7 +34,7 @@ void MQTTLockComponent::dump_config() { LOG_MQTT_COMPONENT(true, true); } -std::string MQTTLockComponent::component_type() const { return "lock"; } +MQTT_COMPONENT_TYPE(MQTTLockComponent, "lock") const EntityBase *MQTTLockComponent::get_entity() const { return this->lock_; } void MQTTLockComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson diff --git a/esphome/components/mqtt/mqtt_lock.h b/esphome/components/mqtt/mqtt_lock.h index 6fb4998b25..666882c73d 100644 --- a/esphome/components/mqtt/mqtt_lock.h +++ b/esphome/components/mqtt/mqtt_lock.h @@ -27,7 +27,7 @@ class MQTTLockComponent : public mqtt::MQTTComponent { protected: /// "lock" component type. - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; lock::Lock *lock_; diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index 381574ae56..8342210ee4 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -33,7 +33,7 @@ void MQTTNumberComponent::dump_config() { LOG_MQTT_COMPONENT(true, false) } -std::string MQTTNumberComponent::component_type() const { return "number"; } +MQTT_COMPONENT_TYPE(MQTTNumberComponent, "number") const EntityBase *MQTTNumberComponent::get_entity() const { return this->number_; } void MQTTNumberComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { diff --git a/esphome/components/mqtt/mqtt_number.h b/esphome/components/mqtt/mqtt_number.h index b89e78a454..021a539988 100644 --- a/esphome/components/mqtt/mqtt_number.h +++ b/esphome/components/mqtt/mqtt_number.h @@ -32,7 +32,7 @@ class MQTTNumberComponent : public mqtt::MQTTComponent { protected: /// Override for MQTTComponent, returns "number". - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; number::Number *number_; diff --git a/esphome/components/mqtt/mqtt_select.cpp b/esphome/components/mqtt/mqtt_select.cpp index 5edc5c50dc..09d90ed46e 100644 --- a/esphome/components/mqtt/mqtt_select.cpp +++ b/esphome/components/mqtt/mqtt_select.cpp @@ -28,7 +28,7 @@ void MQTTSelectComponent::dump_config() { LOG_MQTT_COMPONENT(true, false) } -std::string MQTTSelectComponent::component_type() const { return "select"; } +MQTT_COMPONENT_TYPE(MQTTSelectComponent, "select") const EntityBase *MQTTSelectComponent::get_entity() const { return this->select_; } void MQTTSelectComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { diff --git a/esphome/components/mqtt/mqtt_select.h b/esphome/components/mqtt/mqtt_select.h index 19aad662e5..aaf174ff72 100644 --- a/esphome/components/mqtt/mqtt_select.h +++ b/esphome/components/mqtt/mqtt_select.h @@ -32,7 +32,7 @@ class MQTTSelectComponent : public mqtt::MQTTComponent { protected: /// Override for MQTTComponent, returns "select". - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; select::Select *select_; diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index bd79ae40fe..14eb160e72 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -31,7 +31,7 @@ void MQTTSensorComponent::dump_config() { LOG_MQTT_COMPONENT(true, false) } -std::string MQTTSensorComponent::component_type() const { return "sensor"; } +MQTT_COMPONENT_TYPE(MQTTSensorComponent, "sensor") const EntityBase *MQTTSensorComponent::get_entity() const { return this->sensor_; } uint32_t MQTTSensorComponent::get_expire_after() const { diff --git a/esphome/components/mqtt/mqtt_sensor.h b/esphome/components/mqtt/mqtt_sensor.h index 8c60199e1b..e8202aa8e2 100644 --- a/esphome/components/mqtt/mqtt_sensor.h +++ b/esphome/components/mqtt/mqtt_sensor.h @@ -43,7 +43,7 @@ class MQTTSensorComponent : public mqtt::MQTTComponent { protected: /// Override for MQTTComponent, returns "sensor". - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; sensor::Sensor *sensor_; diff --git a/esphome/components/mqtt/mqtt_switch.cpp b/esphome/components/mqtt/mqtt_switch.cpp index a35ae8f9b6..a985ec66be 100644 --- a/esphome/components/mqtt/mqtt_switch.cpp +++ b/esphome/components/mqtt/mqtt_switch.cpp @@ -41,7 +41,7 @@ void MQTTSwitchComponent::dump_config() { LOG_MQTT_COMPONENT(true, true); } -std::string MQTTSwitchComponent::component_type() const { return "switch"; } +MQTT_COMPONENT_TYPE(MQTTSwitchComponent, "switch") const EntityBase *MQTTSwitchComponent::get_entity() const { return this->switch_; } void MQTTSwitchComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson diff --git a/esphome/components/mqtt/mqtt_switch.h b/esphome/components/mqtt/mqtt_switch.h index fb6a13f172..5f6cb841fd 100644 --- a/esphome/components/mqtt/mqtt_switch.h +++ b/esphome/components/mqtt/mqtt_switch.h @@ -27,7 +27,7 @@ class MQTTSwitchComponent : public mqtt::MQTTComponent { protected: /// "switch" component type. - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; switch_::Switch *switch_; diff --git a/esphome/components/mqtt/mqtt_text.cpp b/esphome/components/mqtt/mqtt_text.cpp index 3cb851fd38..cee94965c6 100644 --- a/esphome/components/mqtt/mqtt_text.cpp +++ b/esphome/components/mqtt/mqtt_text.cpp @@ -29,7 +29,7 @@ void MQTTTextComponent::dump_config() { LOG_MQTT_COMPONENT(true, true) } -std::string MQTTTextComponent::component_type() const { return "text"; } +MQTT_COMPONENT_TYPE(MQTTTextComponent, "text") const EntityBase *MQTTTextComponent::get_entity() const { return this->text_; } void MQTTTextComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { diff --git a/esphome/components/mqtt/mqtt_text.h b/esphome/components/mqtt/mqtt_text.h index 0480b89395..8ae0b9e29a 100644 --- a/esphome/components/mqtt/mqtt_text.h +++ b/esphome/components/mqtt/mqtt_text.h @@ -32,7 +32,7 @@ class MQTTTextComponent : public mqtt::MQTTComponent { protected: /// Override for MQTTComponent, returns "text". - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; text::Text *text_; diff --git a/esphome/components/mqtt/mqtt_text_sensor.cpp b/esphome/components/mqtt/mqtt_text_sensor.cpp index c87f22fb8e..5346923b41 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.cpp +++ b/esphome/components/mqtt/mqtt_text_sensor.cpp @@ -39,7 +39,7 @@ bool MQTTTextSensor::send_initial_state() { return true; } } -std::string MQTTTextSensor::component_type() const { return "sensor"; } +MQTT_COMPONENT_TYPE(MQTTTextSensor, "sensor") const EntityBase *MQTTTextSensor::get_entity() const { return this->sensor_; } } // namespace esphome::mqtt diff --git a/esphome/components/mqtt/mqtt_text_sensor.h b/esphome/components/mqtt/mqtt_text_sensor.h index d4d38d7eb2..d8f9315c1e 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.h +++ b/esphome/components/mqtt/mqtt_text_sensor.h @@ -25,7 +25,7 @@ class MQTTTextSensor : public mqtt::MQTTComponent { bool send_initial_state() override; protected: - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; text_sensor::TextSensor *sensor_; diff --git a/esphome/components/mqtt/mqtt_time.cpp b/esphome/components/mqtt/mqtt_time.cpp index 01b8dd3483..b75325022a 100644 --- a/esphome/components/mqtt/mqtt_time.cpp +++ b/esphome/components/mqtt/mqtt_time.cpp @@ -39,7 +39,7 @@ void MQTTTimeComponent::dump_config() { LOG_MQTT_COMPONENT(true, true) } -std::string MQTTTimeComponent::component_type() const { return "time"; } +MQTT_COMPONENT_TYPE(MQTTTimeComponent, "time") const EntityBase *MQTTTimeComponent::get_entity() const { return this->time_; } void MQTTTimeComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { diff --git a/esphome/components/mqtt/mqtt_time.h b/esphome/components/mqtt/mqtt_time.h index 60345c37ae..cf5780da2d 100644 --- a/esphome/components/mqtt/mqtt_time.h +++ b/esphome/components/mqtt/mqtt_time.h @@ -31,7 +31,7 @@ class MQTTTimeComponent : public mqtt::MQTTComponent { bool publish_state(uint8_t hour, uint8_t minute, uint8_t second); protected: - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; datetime::TimeEntity *time_; diff --git a/esphome/components/mqtt/mqtt_update.cpp b/esphome/components/mqtt/mqtt_update.cpp index aedf2414c1..99e0c85509 100644 --- a/esphome/components/mqtt/mqtt_update.cpp +++ b/esphome/components/mqtt/mqtt_update.cpp @@ -52,7 +52,7 @@ void MQTTUpdateComponent::dump_config() { LOG_MQTT_COMPONENT(true, true); } -std::string MQTTUpdateComponent::component_type() const { return "update"; } +MQTT_COMPONENT_TYPE(MQTTUpdateComponent, "update") const EntityBase *MQTTUpdateComponent::get_entity() const { return this->update_; } } // namespace esphome::mqtt diff --git a/esphome/components/mqtt/mqtt_update.h b/esphome/components/mqtt/mqtt_update.h index d04d22d25f..ec1adb1fcd 100644 --- a/esphome/components/mqtt/mqtt_update.h +++ b/esphome/components/mqtt/mqtt_update.h @@ -27,7 +27,7 @@ class MQTTUpdateComponent : public mqtt::MQTTComponent { protected: /// "update" component type. - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; update::UpdateEntity *update_; diff --git a/esphome/components/mqtt/mqtt_valve.cpp b/esphome/components/mqtt/mqtt_valve.cpp index 8ee693121b..a4c893f84b 100644 --- a/esphome/components/mqtt/mqtt_valve.cpp +++ b/esphome/components/mqtt/mqtt_valve.cpp @@ -65,7 +65,7 @@ void MQTTValveComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConf } } -std::string MQTTValveComponent::component_type() const { return "valve"; } +MQTT_COMPONENT_TYPE(MQTTValveComponent, "valve") const EntityBase *MQTTValveComponent::get_entity() const { return this->valve_; } bool MQTTValveComponent::send_initial_state() { return this->publish_state(); } diff --git a/esphome/components/mqtt/mqtt_valve.h b/esphome/components/mqtt/mqtt_valve.h index 9e5221e495..d3b724a8ba 100644 --- a/esphome/components/mqtt/mqtt_valve.h +++ b/esphome/components/mqtt/mqtt_valve.h @@ -27,7 +27,7 @@ class MQTTValveComponent : public mqtt::MQTTComponent { void dump_config() override; protected: - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; valve::Valve *valve_; diff --git a/esphome/core/config.py b/esphome/core/config.py index f9c3011507..b7e6ab9bee 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -76,6 +76,7 @@ VALID_INCLUDE_EXTS = {".h", ".hpp", ".tcc", ".ino", ".cpp", ".c"} def validate_hostname(config): + # Keep in sync with ESPHOME_DEVICE_NAME_MAX_LEN in esphome/core/entity_base.h max_length = 31 if config[CONF_NAME_ADD_MAC_SUFFIX]: max_length -= 7 # "-AABBCC" is appended when add mac suffix option is used @@ -207,6 +208,7 @@ CONFIG_SCHEMA = cv.All( cv.Schema( { cv.Required(CONF_NAME): cv.valid_name, + # Keep max=120 in sync with OBJECT_ID_MAX_LEN in esphome/core/entity_base.h cv.Optional(CONF_FRIENDLY_NAME, ""): cv.All( cv.string_no_slash, cv.Length(max=120) ), diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index a45c7795bf..1649077dd0 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -13,7 +13,10 @@ namespace esphome { -// Maximum size for object_id buffer (friendly_name max ~120 + margin) +// Maximum device name length - keep in sync with validate_hostname() in esphome/core/config.py +static constexpr size_t ESPHOME_DEVICE_NAME_MAX_LEN = 31; + +// Maximum size for object_id buffer - keep in sync with friendly_name cv.Length(max=120) in esphome/core/config.py static constexpr size_t OBJECT_ID_MAX_LEN = 128; enum EntityCategory : uint8_t {