Compare commits

..

4 Commits

Author SHA1 Message Date
J. Nick Koston
154256e4ee Merge branch 'dev' into http_request_reduce_alloc 2026-02-06 18:41:54 +01:00
J. Nick Koston
038a939f96 Merge branch 'dev' into http_request_reduce_alloc 2026-01-28 10:57:51 -10:00
J. Nick Koston
3f9d6e39a9 add comment here as well 2026-01-28 10:51:29 -10:00
J. Nick Koston
ba6e050c91 [http_request] Reduce heap allocations in update check by parsing JSON directly from buffer 2026-01-28 00:02:32 -10:00
4 changed files with 49 additions and 26 deletions

View File

@@ -90,16 +90,14 @@ void HttpRequestUpdate::update_task(void *params) {
UPDATE_RETURN;
}
size_t read_index = container->get_bytes_read();
size_t content_length = container->content_length;
container->end();
container.reset(); // Release ownership of the container's shared_ptr
bool valid = false;
{ // Ensures the response string falls out of scope and deallocates before the task ends
std::string response((char *) data, read_index);
allocator.deallocate(data, container->content_length);
container->end();
container.reset(); // Release ownership of the container's shared_ptr
valid = json::parse_json(response, [this_update](JsonObject root) -> bool {
{ // Scope to ensure JsonDocument is destroyed before deallocating buffer
valid = json::parse_json(data, read_index, [this_update](JsonObject root) -> bool {
if (!root[ESPHOME_F("name")].is<const char *>() || !root[ESPHOME_F("version")].is<const char *>() ||
!root[ESPHOME_F("builds")].is<JsonArray>()) {
ESP_LOGE(TAG, "Manifest does not contain required fields");
@@ -137,6 +135,7 @@ void HttpRequestUpdate::update_task(void *params) {
return false;
});
}
allocator.deallocate(data, content_length);
if (!valid) {
ESP_LOGE(TAG, "Failed to parse JSON from %s", this_update->source_url_.c_str());
@@ -157,17 +156,12 @@ void HttpRequestUpdate::update_task(void *params) {
}
}
{ // Ensures the current version string falls out of scope and deallocates before the task ends
std::string current_version;
#ifdef ESPHOME_PROJECT_VERSION
current_version = ESPHOME_PROJECT_VERSION;
this_update->update_info_.current_version = ESPHOME_PROJECT_VERSION;
#else
current_version = ESPHOME_VERSION;
this_update->update_info_.current_version = ESPHOME_VERSION;
#endif
this_update->update_info_.current_version = current_version;
}
bool trigger_update_available = false;
if (this_update->update_info_.latest_version.empty() ||

View File

@@ -25,8 +25,13 @@ std::string build_json(const json_build_t &f) {
}
bool parse_json(const std::string &data, const json_parse_t &f) {
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
return parse_json(reinterpret_cast<const uint8_t *>(data.c_str()), data.size(), f);
}
bool parse_json(const uint8_t *data, size_t len, const json_parse_t &f) {
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
JsonDocument doc = parse_json(reinterpret_cast<const uint8_t *>(data.c_str()), data.size());
JsonDocument doc = parse_json(data, len);
if (doc.overflowed() || doc.isNull())
return false;
return f(doc.as<JsonObject>());

View File

@@ -50,6 +50,8 @@ std::string build_json(const json_build_t &f);
/// Parse a JSON string and run the provided json parse function if it's valid.
bool parse_json(const std::string &data, const json_parse_t &f);
/// Parse JSON from raw bytes and run the provided json parse function if it's valid.
bool parse_json(const uint8_t *data, size_t len, const json_parse_t &f);
/// Parse a JSON string and return the root JsonDocument (or an unbound object on error)
JsonDocument parse_json(const uint8_t *data, size_t len);

View File

@@ -4,7 +4,6 @@
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
#include <cinttypes>
#include <utility>
@@ -1545,19 +1544,42 @@ void Sprinkler::log_multiplier_zero_warning_(const LogString *method_name) {
ESP_LOGW(TAG, "%s called but multiplier is set to zero; no action taken", LOG_STR_ARG(method_name));
}
// Request origin strings indexed by SprinklerValveRunRequestOrigin enum (0-2): USER, CYCLE, QUEUE
PROGMEM_STRING_TABLE(SprinklerRequestOriginStrings, "USER", "CYCLE", "QUEUE", "UNKNOWN");
const LogString *Sprinkler::req_as_str_(SprinklerValveRunRequestOrigin origin) {
return SprinklerRequestOriginStrings::get_log_str(static_cast<uint8_t>(origin),
SprinklerRequestOriginStrings::LAST_INDEX);
switch (origin) {
case USER:
return LOG_STR("USER");
case CYCLE:
return LOG_STR("CYCLE");
case QUEUE:
return LOG_STR("QUEUE");
default:
return LOG_STR("UNKNOWN");
}
}
// Sprinkler state strings indexed by SprinklerState enum (0-4): IDLE, STARTING, ACTIVE, STOPPING, BYPASS
PROGMEM_STRING_TABLE(SprinklerStateStrings, "IDLE", "STARTING", "ACTIVE", "STOPPING", "BYPASS", "UNKNOWN");
const LogString *Sprinkler::state_as_str_(SprinklerState state) {
return SprinklerStateStrings::get_log_str(static_cast<uint8_t>(state), SprinklerStateStrings::LAST_INDEX);
switch (state) {
case IDLE:
return LOG_STR("IDLE");
case STARTING:
return LOG_STR("STARTING");
case ACTIVE:
return LOG_STR("ACTIVE");
case STOPPING:
return LOG_STR("STOPPING");
case BYPASS:
return LOG_STR("BYPASS");
default:
return LOG_STR("UNKNOWN");
}
}
void Sprinkler::start_timer_(const SprinklerTimerIndex timer_index) {