1
0
mirror of https://github.com/esphome/esphome.git synced 2026-02-08 08:41:59 +00:00

[mqtt] Add zero-allocation topic getters to MQTT_COMPONENT_CUSTOM_TOPIC macro

Add _to() variants that write into a stack buffer and return StringRef,
avoiding heap allocation on every publish. Update publish_state() in
climate (11 allocations eliminated), fan (3), cover (2), and valve (1)
to use the new stack-based getters.

The allocating getters are retained for setup-time paths (subscribe,
send_discovery, dump_config) where the allocation is one-time.
This commit is contained in:
J. Nick Koston
2026-02-06 20:41:51 +01:00
parent 86f91eed2f
commit a9a943b858
5 changed files with 34 additions and 24 deletions

View File

@@ -300,9 +300,11 @@ const EntityBase *MQTTClimateComponent::get_entity() const { return this->device
bool MQTTClimateComponent::publish_state_() {
auto traits = this->device_->get_traits();
// Reusable stack buffer for topic construction (avoids heap allocation per publish)
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
// mode
bool success = true;
if (!this->publish(this->get_mode_state_topic(), climate_mode_to_mqtt_str(this->device_->mode)))
if (!this->publish(this->get_mode_state_topic_to(topic_buf), climate_mode_to_mqtt_str(this->device_->mode)))
success = false;
int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals();
int8_t current_accuracy = traits.get_current_temperature_accuracy_decimals();
@@ -311,68 +313,70 @@ bool MQTTClimateComponent::publish_state_() {
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE) &&
!std::isnan(this->device_->current_temperature)) {
len = value_accuracy_to_buf(payload, this->device_->current_temperature, current_accuracy);
if (!this->publish(this->get_current_temperature_state_topic(), payload, len))
if (!this->publish(this->get_current_temperature_state_topic_to(topic_buf), payload, len))
success = false;
}
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
climate::CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) {
len = value_accuracy_to_buf(payload, this->device_->target_temperature_low, target_accuracy);
if (!this->publish(this->get_target_temperature_low_state_topic(), payload, len))
if (!this->publish(this->get_target_temperature_low_state_topic_to(topic_buf), payload, len))
success = false;
len = value_accuracy_to_buf(payload, this->device_->target_temperature_high, target_accuracy);
if (!this->publish(this->get_target_temperature_high_state_topic(), payload, len))
if (!this->publish(this->get_target_temperature_high_state_topic_to(topic_buf), payload, len))
success = false;
} else {
len = value_accuracy_to_buf(payload, this->device_->target_temperature, target_accuracy);
if (!this->publish(this->get_target_temperature_state_topic(), payload, len))
if (!this->publish(this->get_target_temperature_state_topic_to(topic_buf), payload, len))
success = false;
}
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY) &&
!std::isnan(this->device_->current_humidity)) {
len = value_accuracy_to_buf(payload, this->device_->current_humidity, 0);
if (!this->publish(this->get_current_humidity_state_topic(), payload, len))
if (!this->publish(this->get_current_humidity_state_topic_to(topic_buf), payload, len))
success = false;
}
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY) &&
!std::isnan(this->device_->target_humidity)) {
len = value_accuracy_to_buf(payload, this->device_->target_humidity, 0);
if (!this->publish(this->get_target_humidity_state_topic(), payload, len))
if (!this->publish(this->get_target_humidity_state_topic_to(topic_buf), payload, len))
success = false;
}
if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) {
if (this->device_->has_custom_preset()) {
if (!this->publish(this->get_preset_state_topic(), this->device_->get_custom_preset()))
if (!this->publish(this->get_preset_state_topic_to(topic_buf), this->device_->get_custom_preset()))
success = false;
} else if (this->device_->preset.has_value()) {
if (!this->publish(this->get_preset_state_topic(), climate_preset_to_mqtt_str(this->device_->preset.value())))
if (!this->publish(this->get_preset_state_topic_to(topic_buf),
climate_preset_to_mqtt_str(this->device_->preset.value())))
success = false;
} else if (!this->publish(this->get_preset_state_topic(), "")) {
} else if (!this->publish(this->get_preset_state_topic_to(topic_buf), "")) {
success = false;
}
}
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION)) {
if (!this->publish(this->get_action_state_topic(), climate_action_to_mqtt_str(this->device_->action)))
if (!this->publish(this->get_action_state_topic_to(topic_buf), climate_action_to_mqtt_str(this->device_->action)))
success = false;
}
if (traits.get_supports_fan_modes()) {
if (this->device_->has_custom_fan_mode()) {
if (!this->publish(this->get_fan_mode_state_topic(), this->device_->get_custom_fan_mode()))
if (!this->publish(this->get_fan_mode_state_topic_to(topic_buf), this->device_->get_custom_fan_mode()))
success = false;
} else if (this->device_->fan_mode.has_value()) {
if (!this->publish(this->get_fan_mode_state_topic(),
if (!this->publish(this->get_fan_mode_state_topic_to(topic_buf),
climate_fan_mode_to_mqtt_str(this->device_->fan_mode.value())))
success = false;
} else if (!this->publish(this->get_fan_mode_state_topic(), "")) {
} else if (!this->publish(this->get_fan_mode_state_topic_to(topic_buf), "")) {
success = false;
}
}
if (traits.get_supports_swing_modes()) {
if (!this->publish(this->get_swing_mode_state_topic(), climate_swing_mode_to_mqtt_str(this->device_->swing_mode)))
if (!this->publish(this->get_swing_mode_state_topic_to(topic_buf),
climate_swing_mode_to_mqtt_str(this->device_->swing_mode)))
success = false;
}

View File

@@ -55,6 +55,11 @@ void log_mqtt_component(const char *tag, MQTTComponent *obj, bool state_topic, b
\
public: \
void set_custom_##name##_##type##_topic(const std::string &topic) { this->custom_##name##_##type##_topic_ = topic; } \
StringRef get_##name##_##type##_topic_to(std::span<char, MQTT_DEFAULT_TOPIC_MAX_LEN> buf) const { \
if (!this->custom_##name##_##type##_topic_.empty()) \
return StringRef(this->custom_##name##_##type##_topic_.data(), this->custom_##name##_##type##_topic_.size()); \
return this->get_default_topic_for_to_(buf, #name "/" #type, sizeof(#name "/" #type) - 1); \
} \
std::string get_##name##_##type##_topic() const { \
if (this->custom_##name##_##type##_topic_.empty()) \
return this->get_default_topic_for_(#name "/" #type); \

View File

@@ -112,19 +112,19 @@ bool MQTTCoverComponent::send_initial_state() { return this->publish_state(); }
bool MQTTCoverComponent::publish_state() {
auto traits = this->cover_->get_traits();
bool success = true;
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
if (traits.get_supports_position()) {
char pos[VALUE_ACCURACY_MAX_LEN];
size_t len = value_accuracy_to_buf(pos, roundf(this->cover_->position * 100), 0);
if (!this->publish(this->get_position_state_topic(), pos, len))
if (!this->publish(this->get_position_state_topic_to(topic_buf), pos, len))
success = false;
}
if (traits.get_supports_tilt()) {
char pos[VALUE_ACCURACY_MAX_LEN];
size_t len = value_accuracy_to_buf(pos, roundf(this->cover_->tilt * 100), 0);
if (!this->publish(this->get_tilt_state_topic(), pos, len))
if (!this->publish(this->get_tilt_state_topic_to(topic_buf), pos, len))
success = false;
}
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
if (!this->publish(this->get_state_topic_to_(topic_buf),
cover_state_to_mqtt_str(this->cover_->current_operation, this->cover_->position,
traits.get_supports_position())))

View File

@@ -173,19 +173,20 @@ bool MQTTFanComponent::publish_state() {
this->publish(this->get_state_topic_to_(topic_buf), state_s);
bool failed = false;
if (this->state_->get_traits().supports_direction()) {
bool success = this->publish(this->get_direction_state_topic(), fan_direction_to_mqtt_str(this->state_->direction));
bool success = this->publish(this->get_direction_state_topic_to(topic_buf),
fan_direction_to_mqtt_str(this->state_->direction));
failed = failed || !success;
}
if (this->state_->get_traits().supports_oscillation()) {
bool success =
this->publish(this->get_oscillation_state_topic(), fan_oscillation_to_mqtt_str(this->state_->oscillating));
bool success = this->publish(this->get_oscillation_state_topic_to(topic_buf),
fan_oscillation_to_mqtt_str(this->state_->oscillating));
failed = failed || !success;
}
auto traits = this->state_->get_traits();
if (traits.supports_speed()) {
char buf[12];
size_t len = buf_append_printf(buf, sizeof(buf), 0, "%d", this->state_->speed);
bool success = this->publish(this->get_speed_level_state_topic(), buf, len);
bool success = this->publish(this->get_speed_level_state_topic_to(topic_buf), buf, len);
failed = failed || !success;
}
return !failed;

View File

@@ -87,13 +87,13 @@ bool MQTTValveComponent::send_initial_state() { return this->publish_state(); }
bool MQTTValveComponent::publish_state() {
auto traits = this->valve_->get_traits();
bool success = true;
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
if (traits.get_supports_position()) {
char pos[VALUE_ACCURACY_MAX_LEN];
size_t len = value_accuracy_to_buf(pos, roundf(this->valve_->position * 100), 0);
if (!this->publish(this->get_position_state_topic(), pos, len))
if (!this->publish(this->get_position_state_topic_to(topic_buf), pos, len))
success = false;
}
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
if (!this->publish(this->get_state_topic_to_(topic_buf),
valve_state_to_mqtt_str(this->valve_->current_operation, this->valve_->position,
traits.get_supports_position())))