mirror of
https://github.com/esphome/esphome.git
synced 2024-12-20 12:34:54 +01:00
[helpers] Provide calls to get free heap and largest available block. (#7978)
This commit is contained in:
parent
ac631711ab
commit
b33b4481ea
3 changed files with 61 additions and 46 deletions
|
@ -1,38 +1,20 @@
|
||||||
#include "json_util.h"
|
#include "json_util.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
#ifdef USE_ESP8266
|
|
||||||
#include <Esp.h>
|
|
||||||
#endif
|
|
||||||
#ifdef USE_ESP32
|
|
||||||
#include <esp_heap_caps.h>
|
|
||||||
#endif
|
|
||||||
#ifdef USE_RP2040
|
|
||||||
#include <Arduino.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace json {
|
namespace json {
|
||||||
|
|
||||||
static const char *const TAG = "json";
|
static const char *const TAG = "json";
|
||||||
|
|
||||||
static std::vector<char> global_json_build_buffer; // NOLINT
|
static std::vector<char> global_json_build_buffer; // NOLINT
|
||||||
|
static const auto ALLOCATOR = RAMAllocator<uint8_t>(RAMAllocator<uint8_t>::ALLOC_INTERNAL);
|
||||||
|
|
||||||
std::string build_json(const json_build_t &f) {
|
std::string build_json(const json_build_t &f) {
|
||||||
// Here we are allocating up to 5kb of memory,
|
// Here we are allocating up to 5kb of memory,
|
||||||
// with the heap size minus 2kb to be safe if less than 5kb
|
// with the heap size minus 2kb to be safe if less than 5kb
|
||||||
// as we can not have a true dynamic sized document.
|
// as we can not have a true dynamic sized document.
|
||||||
// The excess memory is freed below with `shrinkToFit()`
|
// The excess memory is freed below with `shrinkToFit()`
|
||||||
#ifdef USE_ESP8266
|
auto free_heap = ALLOCATOR.get_max_free_block_size();
|
||||||
const size_t free_heap = ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance)
|
|
||||||
#elif defined(USE_ESP32)
|
|
||||||
const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT);
|
|
||||||
#elif defined(USE_RP2040)
|
|
||||||
const size_t free_heap = rp2040.getFreeHeap();
|
|
||||||
#elif defined(USE_LIBRETINY)
|
|
||||||
const size_t free_heap = lt_heap_get_free();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
size_t request_size = std::min(free_heap, (size_t) 512);
|
size_t request_size = std::min(free_heap, (size_t) 512);
|
||||||
while (true) {
|
while (true) {
|
||||||
ESP_LOGV(TAG, "Attempting to allocate %u bytes for JSON serialization", request_size);
|
ESP_LOGV(TAG, "Attempting to allocate %u bytes for JSON serialization", request_size);
|
||||||
|
@ -67,20 +49,12 @@ bool parse_json(const std::string &data, const json_parse_t &f) {
|
||||||
// with the heap size minus 2kb to be safe if less than that
|
// with the heap size minus 2kb to be safe if less than that
|
||||||
// as we can not have a true dynamic sized document.
|
// as we can not have a true dynamic sized document.
|
||||||
// The excess memory is freed below with `shrinkToFit()`
|
// The excess memory is freed below with `shrinkToFit()`
|
||||||
#ifdef USE_ESP8266
|
auto free_heap = ALLOCATOR.get_max_free_block_size();
|
||||||
const size_t free_heap = ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance)
|
|
||||||
#elif defined(USE_ESP32)
|
|
||||||
const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT);
|
|
||||||
#elif defined(USE_RP2040)
|
|
||||||
const size_t free_heap = rp2040.getFreeHeap();
|
|
||||||
#elif defined(USE_LIBRETINY)
|
|
||||||
const size_t free_heap = lt_heap_get_free();
|
|
||||||
#endif
|
|
||||||
size_t request_size = std::min(free_heap, (size_t) (data.size() * 1.5));
|
size_t request_size = std::min(free_heap, (size_t) (data.size() * 1.5));
|
||||||
while (true) {
|
while (true) {
|
||||||
DynamicJsonDocument json_document(request_size);
|
DynamicJsonDocument json_document(request_size);
|
||||||
if (json_document.capacity() == 0) {
|
if (json_document.capacity() == 0) {
|
||||||
ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, free heap: %u", request_size,
|
ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %zu bytes, free heap: %zu", request_size,
|
||||||
free_heap);
|
free_heap);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,15 +80,7 @@ bool OnlineImage::resize_(int width_in, int height_in) {
|
||||||
this->width_ = width;
|
this->width_ = width;
|
||||||
ESP_LOGD(TAG, "New size: (%d, %d)", width, height);
|
ESP_LOGD(TAG, "New size: (%d, %d)", width, height);
|
||||||
} else {
|
} else {
|
||||||
#if defined(USE_ESP8266)
|
ESP_LOGE(TAG, "allocation failed. Biggest block in heap: %zu Bytes", this->allocator_.get_max_free_block_size());
|
||||||
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
|
|
||||||
int max_block = ESP.getMaxFreeBlockSize();
|
|
||||||
#elif defined(USE_ESP32)
|
|
||||||
int max_block = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL);
|
|
||||||
#else
|
|
||||||
int max_block = -1;
|
|
||||||
#endif
|
|
||||||
ESP_LOGE(TAG, "allocation failed. Biggest block in heap: %d Bytes", max_block);
|
|
||||||
this->end_connection_();
|
this->end_connection_();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,14 @@
|
||||||
|
|
||||||
#include "esphome/core/optional.h"
|
#include "esphome/core/optional.h"
|
||||||
|
|
||||||
|
#ifdef USE_ESP8266
|
||||||
|
#include <Esp.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
#include <Arduino.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
#include <esp_heap_caps.h>
|
#include <esp_heap_caps.h>
|
||||||
#endif
|
#endif
|
||||||
|
@ -684,20 +692,23 @@ template<class T> class RAMAllocator {
|
||||||
};
|
};
|
||||||
|
|
||||||
RAMAllocator() = default;
|
RAMAllocator() = default;
|
||||||
RAMAllocator(uint8_t flags) : flags_{flags} {}
|
RAMAllocator(uint8_t flags) {
|
||||||
|
// default is both external and internal
|
||||||
|
flags &= ALLOC_INTERNAL | ALLOC_EXTERNAL;
|
||||||
|
if (flags != 0)
|
||||||
|
this->flags_ = flags;
|
||||||
|
}
|
||||||
template<class U> constexpr RAMAllocator(const RAMAllocator<U> &other) : flags_{other.flags_} {}
|
template<class U> constexpr RAMAllocator(const RAMAllocator<U> &other) : flags_{other.flags_} {}
|
||||||
|
|
||||||
T *allocate(size_t n) {
|
T *allocate(size_t n) {
|
||||||
size_t size = n * sizeof(T);
|
size_t size = n * sizeof(T);
|
||||||
T *ptr = nullptr;
|
T *ptr = nullptr;
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
// External allocation by default or if explicitely requested
|
if (this->flags_ & Flags::ALLOC_EXTERNAL) {
|
||||||
if ((this->flags_ & Flags::ALLOC_EXTERNAL) || ((this->flags_ & Flags::ALLOC_INTERNAL) == 0)) {
|
|
||||||
ptr = static_cast<T *>(heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
|
ptr = static_cast<T *>(heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
|
||||||
}
|
}
|
||||||
// Fallback to internal allocation if explicitely requested or no flag is specified
|
if (ptr == nullptr && this->flags_ & Flags::ALLOC_INTERNAL) {
|
||||||
if (ptr == nullptr && ((this->flags_ & Flags::ALLOC_INTERNAL) || (this->flags_ & Flags::ALLOC_EXTERNAL) == 0)) {
|
ptr = static_cast<T *>(heap_caps_malloc(size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
|
||||||
ptr = static_cast<T *>(malloc(size)); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc)
|
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
// Ignore ALLOC_EXTERNAL/ALLOC_INTERNAL flags if external allocation is not supported
|
// Ignore ALLOC_EXTERNAL/ALLOC_INTERNAL flags if external allocation is not supported
|
||||||
|
@ -710,8 +721,46 @@ template<class T> class RAMAllocator {
|
||||||
free(p); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc)
|
free(p); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the total heap space available via this allocator
|
||||||
|
*/
|
||||||
|
size_t get_free_heap_size() const {
|
||||||
|
#ifdef USE_ESP8266
|
||||||
|
return ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance)
|
||||||
|
#elif defined(USE_ESP32)
|
||||||
|
auto max_internal =
|
||||||
|
this->flags_ & ALLOC_INTERNAL ? heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL) : 0;
|
||||||
|
auto max_external =
|
||||||
|
this->flags_ & ALLOC_EXTERNAL ? heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM) : 0;
|
||||||
|
return max_internal + max_external;
|
||||||
|
#elif defined(USE_RP2040)
|
||||||
|
return ::rp2040.getFreeHeap();
|
||||||
|
#elif defined(USE_LIBRETINY)
|
||||||
|
return lt_heap_get_free();
|
||||||
|
#else
|
||||||
|
return 100000;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the maximum size block this allocator could allocate. This may be an approximation on some platforms
|
||||||
|
*/
|
||||||
|
size_t get_max_free_block_size() const {
|
||||||
|
#ifdef USE_ESP8266
|
||||||
|
return ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance)
|
||||||
|
#elif defined(USE_ESP32)
|
||||||
|
auto max_internal =
|
||||||
|
this->flags_ & ALLOC_INTERNAL ? heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL) : 0;
|
||||||
|
auto max_external =
|
||||||
|
this->flags_ & ALLOC_EXTERNAL ? heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM) : 0;
|
||||||
|
return std::max(max_internal, max_external);
|
||||||
|
#else
|
||||||
|
return this->get_free_heap_size();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint8_t flags_{Flags::ALLOW_FAILURE};
|
uint8_t flags_{ALLOC_INTERNAL | ALLOC_EXTERNAL};
|
||||||
};
|
};
|
||||||
|
|
||||||
template<class T> using ExternalRAMAllocator = RAMAllocator<T>;
|
template<class T> using ExternalRAMAllocator = RAMAllocator<T>;
|
||||||
|
|
Loading…
Reference in a new issue