ESPHome 2025.12.0-dev
Loading...
Searching...
No Matches
split_buffer.cpp
Go to the documentation of this file.
1#include "split_buffer.h"
2
4#include "esphome/core/log.h"
5
7static constexpr const char *const TAG = "split_buffer";
8
10
11bool SplitBuffer::init(size_t total_length) {
12 this->free(); // Clean up any existing allocation
13
14 if (total_length == 0) {
15 return false;
16 }
17
18 this->total_length_ = total_length;
19 size_t current_buffer_size = total_length;
20
21 RAMAllocator<uint8_t *> ptr_allocator;
22 RAMAllocator<uint8_t> allocator;
23
24 // Try to allocate the entire buffer first
25 while (current_buffer_size > 0) {
26 // Calculate how many buffers we need of this size
27 size_t needed_buffers = (total_length + current_buffer_size - 1) / current_buffer_size;
28
29 // Try to allocate array of buffer pointers
30 uint8_t **temp_buffers = ptr_allocator.allocate(needed_buffers);
31 if (temp_buffers == nullptr) {
32 // If we can't even allocate the pointer array, don't need to continue
33 ESP_LOGE(TAG, "Failed to allocate pointers");
34 return false;
35 }
36
37 // Initialize all pointers to null
38 for (size_t i = 0; i < needed_buffers; i++) {
39 temp_buffers[i] = nullptr;
40 }
41
42 // Try to allocate all the buffers
43 bool allocation_success = true;
44 for (size_t i = 0; i < needed_buffers; i++) {
45 size_t this_buffer_size = current_buffer_size;
46 // Last buffer might be smaller if total_length is not divisible by current_buffer_size
47 if (i == needed_buffers - 1 && total_length % current_buffer_size != 0) {
48 this_buffer_size = total_length % current_buffer_size;
49 }
50
51 temp_buffers[i] = allocator.allocate(this_buffer_size);
52 if (temp_buffers[i] == nullptr) {
53 allocation_success = false;
54 break;
55 }
56
57 // Initialize buffer to zero
58 memset(temp_buffers[i], 0, this_buffer_size);
59 }
60
61 if (allocation_success) {
62 // Success! Store the result
63 this->buffers_ = temp_buffers;
64 this->buffer_count_ = needed_buffers;
65 this->buffer_size_ = current_buffer_size;
66 ESP_LOGD(TAG, "Allocated %zu * %zu bytes - %zu bytes", this->buffer_count_, this->buffer_size_,
67 this->total_length_);
68 return true;
69 }
70
71 // Allocation failed, clean up and try smaller buffers
72 for (size_t i = 0; i < needed_buffers; i++) {
73 if (temp_buffers[i] != nullptr) {
74 allocator.deallocate(temp_buffers[i], 0);
75 }
76 }
77 ptr_allocator.deallocate(temp_buffers, 0);
78
79 // Halve the buffer size and try again
80 current_buffer_size = current_buffer_size / 2;
81 }
82
83 ESP_LOGE(TAG, "Failed to allocate %zu bytes", total_length);
84 return false;
85}
86
88 if (this->buffers_ != nullptr) {
89 RAMAllocator<uint8_t> allocator;
90 for (size_t i = 0; i < this->buffer_count_; i++) {
91 if (this->buffers_[i] != nullptr) {
92 allocator.deallocate(this->buffers_[i], 0);
93 }
94 }
95 RAMAllocator<uint8_t *> ptr_allocator;
96 ptr_allocator.deallocate(this->buffers_, 0);
97 this->buffers_ = nullptr;
98 }
99 this->buffer_count_ = 0;
100 this->buffer_size_ = 0;
101 this->total_length_ = 0;
102}
103
104const uint8_t &SplitBuffer::operator[](size_t index) const {
105 if (index >= this->total_length_) {
106 ESP_LOGE(TAG, "Out of bounds - %zu >= %zu", index, this->total_length_);
107 // Return reference to a static dummy byte since we can't throw exceptions.
108 // the byte is non-const since it will also be used by the non-const [] overload.
109 static uint8_t dummy = 0;
110 return dummy;
111 }
112
113 const auto buffer_index = index / this->buffer_size_;
114 const auto offset_in_buffer = index % this->buffer_size_;
115
116 return this->buffers_[buffer_index][offset_in_buffer];
117}
118
119// non-const version of operator[] for write access
120uint8_t &SplitBuffer::operator[](size_t index) {
121 // avoid code duplication. These casts are safe since we know the object is not const.
122 return const_cast<uint8_t &>(static_cast<const SplitBuffer *>(this)->operator[](index));
123}
124
129void SplitBuffer::fill(uint8_t value) const {
130 if (this->buffer_count_ == 0)
131 return;
132 // clear all the full sized buffers
133 size_t i = 0;
134 for (; i != this->buffer_count_ - 1; i++) {
135 memset(this->buffers_[i], value, this->buffer_size_);
136 }
137 // clear the last, potentially short, buffer.
138 // `i` is guaranteed to equal the last index since the loop terminates at that value.
139 // where all buffers are the same size, the modulus must return the size, not 0.
140 auto size_last = ((this->total_length_ - 1) % this->buffer_size_) + 1;
141 memset(this->buffers_[i], value, size_last);
142}
143
144} // namespace esphome::split_buffer
An STL allocator that uses SPI or internal RAM.
Definition helpers.h:1084
void deallocate(T *p, size_t n)
Definition helpers.h:1142
T * allocate(size_t n)
Definition helpers.h:1104
A SplitBuffer allocates a large memory buffer potentially as multiple smaller buffers to facilitate a...
bool init(size_t total_length)
void fill(uint8_t value) const
Fill the entire buffer with a single byte value.
uint8_t & operator[](size_t index)