From b4b795dcaf0cb1ef3ba440bcc3d6cda223627876 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Sep 2025 23:26:46 -0500 Subject: [PATCH 1/3] [i2c] Optimize memory usage with stack allocation for small buffers (#10565) Co-authored-by: Keith Burzinski --- esphome/components/i2c/i2c.cpp | 22 ++++++++++------- esphome/components/i2c/i2c_bus.h | 42 +++++++++++++++++++++++++++----- 2 files changed, 49 insertions(+), 15 deletions(-) diff --git a/esphome/components/i2c/i2c.cpp b/esphome/components/i2c/i2c.cpp index 48e1cf8aca..31c21f398c 100644 --- a/esphome/components/i2c/i2c.cpp +++ b/esphome/components/i2c/i2c.cpp @@ -39,18 +39,22 @@ ErrorCode I2CDevice::read_register16(uint16_t a_register, uint8_t *data, size_t } ErrorCode I2CDevice::write_register(uint8_t a_register, const uint8_t *data, size_t len) const { - std::vector v{}; - v.push_back(a_register); - v.insert(v.end(), data, data + len); - return bus_->write_readv(this->address_, v.data(), v.size(), nullptr, 0); + SmallBufferWithHeapFallback<17> buffer_alloc; // Most I2C writes are <= 16 bytes + uint8_t *buffer = buffer_alloc.get(len + 1); + + buffer[0] = a_register; + std::copy(data, data + len, buffer + 1); + return this->bus_->write_readv(this->address_, buffer, len + 1, nullptr, 0); } ErrorCode I2CDevice::write_register16(uint16_t a_register, const uint8_t *data, size_t len) const { - std::vector v(len + 2); - v[0] = a_register >> 8; - v[1] = a_register; - std::copy(data, data + len, v.begin() + 2); - return bus_->write_readv(this->address_, v.data(), v.size(), nullptr, 0); + SmallBufferWithHeapFallback<18> buffer_alloc; // Most I2C writes are <= 16 bytes + 2 for register + uint8_t *buffer = buffer_alloc.get(len + 2); + + buffer[0] = a_register >> 8; + buffer[1] = a_register; + std::copy(data, data + len, buffer + 2); + return this->bus_->write_readv(this->address_, buffer, len + 2, nullptr, 0); } bool I2CDevice::read_bytes_16(uint8_t a_register, uint16_t *data, uint8_t len) { diff --git a/esphome/components/i2c/i2c_bus.h b/esphome/components/i2c/i2c_bus.h index df4df628e8..1acbe506a3 100644 --- a/esphome/components/i2c/i2c_bus.h +++ b/esphome/components/i2c/i2c_bus.h @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -10,6 +11,22 @@ namespace esphome { namespace i2c { +/// @brief Helper class for efficient buffer allocation - uses stack for small sizes, heap for large +template class SmallBufferWithHeapFallback { + public: + uint8_t *get(size_t size) { + if (size <= STACK_SIZE) { + return this->stack_buffer_; + } + this->heap_buffer_ = std::unique_ptr(new uint8_t[size]); + return this->heap_buffer_.get(); + } + + private: + uint8_t stack_buffer_[STACK_SIZE]; + std::unique_ptr heap_buffer_; +}; + /// @brief Error codes returned by I2CBus and I2CDevice methods enum ErrorCode { NO_ERROR = 0, ///< No error found during execution of method @@ -74,14 +91,17 @@ class I2CBus { for (size_t i = 0; i != count; i++) { total_len += read_buffers[i].len; } - std::vector buffer(total_len); - auto err = this->write_readv(address, nullptr, 0, buffer.data(), total_len); + + SmallBufferWithHeapFallback<128> buffer_alloc; // Most I2C reads are small + uint8_t *buffer = buffer_alloc.get(total_len); + + auto err = this->write_readv(address, nullptr, 0, buffer, total_len); if (err != ERROR_OK) return err; size_t pos = 0; for (size_t i = 0; i != count; i++) { if (read_buffers[i].len != 0) { - std::memcpy(read_buffers[i].data, buffer.data() + pos, read_buffers[i].len); + std::memcpy(read_buffers[i].data, buffer + pos, read_buffers[i].len); pos += read_buffers[i].len; } } @@ -91,11 +111,21 @@ class I2CBus { ESPDEPRECATED("This method is deprecated and will be removed in ESPHome 2026.3.0. Use write_readv() instead.", "2025.9.0") ErrorCode writev(uint8_t address, const WriteBuffer *write_buffers, size_t count, bool stop = true) { - std::vector buffer{}; + size_t total_len = 0; for (size_t i = 0; i != count; i++) { - buffer.insert(buffer.end(), write_buffers[i].data, write_buffers[i].data + write_buffers[i].len); + total_len += write_buffers[i].len; } - return this->write_readv(address, buffer.data(), buffer.size(), nullptr, 0); + + SmallBufferWithHeapFallback<128> buffer_alloc; // Most I2C writes are small + uint8_t *buffer = buffer_alloc.get(total_len); + + size_t pos = 0; + for (size_t i = 0; i != count; i++) { + std::memcpy(buffer + pos, write_buffers[i].data, write_buffers[i].len); + pos += write_buffers[i].len; + } + + return this->write_readv(address, buffer, total_len, nullptr, 0); } protected: From 86c2af48825d825801098982efa32b67f8a21cf6 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Fri, 5 Sep 2025 01:37:57 -0500 Subject: [PATCH 2/3] [sen5x] Fix initialization (#10603) --- esphome/components/sen5x/sen5x.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/sen5x/sen5x.cpp b/esphome/components/sen5x/sen5x.cpp index 0f27ec1b10..f3222221a2 100644 --- a/esphome/components/sen5x/sen5x.cpp +++ b/esphome/components/sen5x/sen5x.cpp @@ -38,6 +38,7 @@ void SEN5XComponent::setup() { this->mark_failed(); return; } + delay(20); // per datasheet uint16_t raw_read_status; if (!this->read_data(raw_read_status)) { From 4248cbc596027988d88637f0bcb1cb040781aeef Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 5 Sep 2025 11:32:52 -0500 Subject: [PATCH 3/3] [sensor] ESP8266: Use LogString for state_class_to_string() to save RAM --- esphome/components/mqtt/mqtt_sensor.cpp | 7 ++++++- esphome/components/sensor/sensor.cpp | 13 +++++++------ esphome/components/sensor/sensor.h | 2 +- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index 2e1db1908f..032dd3b6c6 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -58,8 +58,13 @@ void MQTTSensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCon if (this->sensor_->get_force_update()) root[MQTT_FORCE_UPDATE] = true; - if (this->sensor_->get_state_class() != STATE_CLASS_NONE) + if (this->sensor_->get_state_class() != STATE_CLASS_NONE) { +#ifdef USE_STORE_LOG_STR_IN_FLASH + root[MQTT_STATE_CLASS] = (const __FlashStringHelper *) state_class_to_string(this->sensor_->get_state_class()); +#else root[MQTT_STATE_CLASS] = state_class_to_string(this->sensor_->get_state_class()); +#endif + } config.command_topic = false; } diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index e2e8302d8b..4292b8c0bc 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -17,7 +17,8 @@ void log_sensor(const char *tag, const char *prefix, const char *type, Sensor *o "%s State Class: '%s'\n" "%s Unit of Measurement: '%s'\n" "%s Accuracy Decimals: %d", - prefix, type, obj->get_name().c_str(), prefix, state_class_to_string(obj->get_state_class()), prefix, + prefix, type, obj->get_name().c_str(), prefix, + LOG_STR_ARG(state_class_to_string(obj->get_state_class())), prefix, obj->get_unit_of_measurement_ref().c_str(), prefix, obj->get_accuracy_decimals()); if (!obj->get_device_class_ref().empty()) { @@ -33,17 +34,17 @@ void log_sensor(const char *tag, const char *prefix, const char *type, Sensor *o } } -const char *state_class_to_string(StateClass state_class) { +const LogString *state_class_to_string(StateClass state_class) { switch (state_class) { case STATE_CLASS_MEASUREMENT: - return "measurement"; + return LOG_STR("measurement"); case STATE_CLASS_TOTAL_INCREASING: - return "total_increasing"; + return LOG_STR("total_increasing"); case STATE_CLASS_TOTAL: - return "total"; + return LOG_STR("total"); case STATE_CLASS_NONE: default: - return ""; + return LOG_STR(""); } } diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index 507cb326b2..f3fa601a5e 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -33,7 +33,7 @@ enum StateClass : uint8_t { STATE_CLASS_TOTAL = 3, }; -const char *state_class_to_string(StateClass state_class); +const LogString *state_class_to_string(StateClass state_class); /** Base-class for all sensors. *