ESPHome 2026.1.5
Loading...
Searching...
No Matches
preferences.cpp
Go to the documentation of this file.
1#ifdef USE_ESP8266
2
3#include <c_types.h>
4extern "C" {
5#include "spi_flash.h"
6}
7
10#include "esphome/core/log.h"
12#include "preferences.h"
13
14#include <cstring>
15#include <memory>
16
17namespace esphome::esp8266 {
18
19static const char *const TAG = "esp8266.preferences";
20
21static uint32_t *s_flash_storage = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
22static bool s_prevent_write = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
23static bool s_flash_dirty = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
24
25static constexpr uint32_t ESP_RTC_USER_MEM_START = 0x60001200;
26static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_WORDS = 128;
27static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_BYTES = ESP_RTC_USER_MEM_SIZE_WORDS * 4;
28
29// RTC memory layout for preferences:
30// - Eboot region: RTC words 0-31 (reserved, mapped from preference offset 96-127)
31// - Normal region: RTC words 32-127 (mapped from preference offset 0-95)
32static constexpr uint32_t RTC_EBOOT_REGION_WORDS = 32; // Words 0-31 reserved for eboot
33static constexpr uint32_t RTC_NORMAL_REGION_WORDS = 96; // Words 32-127 for normal prefs
34static constexpr uint32_t PREF_TOTAL_WORDS = RTC_EBOOT_REGION_WORDS + RTC_NORMAL_REGION_WORDS; // 128
35
36// Maximum preference size in words (limited by uint8_t length_words field)
37static constexpr uint32_t MAX_PREFERENCE_WORDS = 255;
38
39#define ESP_RTC_USER_MEM ((uint32_t *) ESP_RTC_USER_MEM_START)
40
41#ifdef USE_ESP8266_PREFERENCES_FLASH
42static constexpr uint32_t ESP8266_FLASH_STORAGE_SIZE = 128;
43#else
44static constexpr uint32_t ESP8266_FLASH_STORAGE_SIZE = 64;
45#endif
46
47static inline bool esp_rtc_user_mem_read(uint32_t index, uint32_t *dest) {
48 if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) {
49 return false;
50 }
51 *dest = ESP_RTC_USER_MEM[index]; // NOLINT(performance-no-int-to-ptr)
52 return true;
53}
54
55static inline bool esp_rtc_user_mem_write(uint32_t index, uint32_t value) {
56 if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) {
57 return false;
58 }
59 if (index < 32 && s_prevent_write) {
60 return false;
61 }
62
63 auto *ptr = &ESP_RTC_USER_MEM[index]; // NOLINT(performance-no-int-to-ptr)
64 *ptr = value;
65 return true;
66}
67
68extern "C" uint32_t _SPIFFS_end; // NOLINT
69
70static uint32_t get_esp8266_flash_sector() {
71 union {
72 uint32_t *ptr;
73 uint32_t uint;
74 } data{};
75 data.ptr = &_SPIFFS_end;
76 return (data.uint - 0x40200000) / SPI_FLASH_SEC_SIZE;
77}
78static uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; }
79
80static inline size_t bytes_to_words(size_t bytes) { return (bytes + 3) / 4; }
81
82template<class It> uint32_t calculate_crc(It first, It last, uint32_t type) {
83 uint32_t crc = type;
84 while (first != last) {
85 crc ^= (*first++ * 2654435769UL) >> 1;
86 }
87 return crc;
88}
89
90static bool save_to_flash(size_t offset, const uint32_t *data, size_t len) {
91 for (uint32_t i = 0; i < len; i++) {
92 uint32_t j = offset + i;
93 if (j >= ESP8266_FLASH_STORAGE_SIZE)
94 return false;
95 uint32_t v = data[i];
96 uint32_t *ptr = &s_flash_storage[j];
97 if (*ptr != v)
98 s_flash_dirty = true;
99 *ptr = v;
100 }
101 return true;
102}
103
104static bool load_from_flash(size_t offset, uint32_t *data, size_t len) {
105 for (size_t i = 0; i < len; i++) {
106 uint32_t j = offset + i;
107 if (j >= ESP8266_FLASH_STORAGE_SIZE)
108 return false;
109 data[i] = s_flash_storage[j];
110 }
111 return true;
112}
113
114static bool save_to_rtc(size_t offset, const uint32_t *data, size_t len) {
115 for (uint32_t i = 0; i < len; i++) {
116 if (!esp_rtc_user_mem_write(offset + i, data[i]))
117 return false;
118 }
119 return true;
120}
121
122static bool load_from_rtc(size_t offset, uint32_t *data, size_t len) {
123 for (uint32_t i = 0; i < len; i++) {
124 if (!esp_rtc_user_mem_read(offset + i, &data[i]))
125 return false;
126 }
127 return true;
128}
129
130// Stack buffer size - 16 words total: up to 15 words of preference data + 1 word CRC (60 bytes of preference data)
131// This handles virtually all real-world preferences without heap allocation
132static constexpr size_t PREF_BUFFER_WORDS = 16;
133
134class ESP8266PreferenceBackend : public ESPPreferenceBackend {
135 public:
136 uint32_t type = 0;
137 uint16_t offset = 0;
138 uint8_t length_words = 0; // Max 255 words (1020 bytes of data)
139 bool in_flash = false;
140
141 bool save(const uint8_t *data, size_t len) override {
142 if (bytes_to_words(len) != this->length_words)
143 return false;
144
145 const size_t buffer_size = static_cast<size_t>(this->length_words) + 1;
146 uint32_t stack_buffer[PREF_BUFFER_WORDS];
147 std::unique_ptr<uint32_t[]> heap_buffer;
148 uint32_t *buffer;
149
150 if (buffer_size <= PREF_BUFFER_WORDS) {
151 buffer = stack_buffer;
152 } else {
153 heap_buffer = make_unique<uint32_t[]>(buffer_size);
154 buffer = heap_buffer.get();
155 }
156 memset(buffer, 0, buffer_size * sizeof(uint32_t));
157
158 memcpy(buffer, data, len);
159 buffer[this->length_words] = calculate_crc(buffer, buffer + this->length_words, this->type);
160
161 return this->in_flash ? save_to_flash(this->offset, buffer, buffer_size)
162 : save_to_rtc(this->offset, buffer, buffer_size);
163 }
164
165 bool load(uint8_t *data, size_t len) override {
166 if (bytes_to_words(len) != this->length_words)
167 return false;
168
169 const size_t buffer_size = static_cast<size_t>(this->length_words) + 1;
170 uint32_t stack_buffer[PREF_BUFFER_WORDS];
171 std::unique_ptr<uint32_t[]> heap_buffer;
172 uint32_t *buffer;
173
174 if (buffer_size <= PREF_BUFFER_WORDS) {
175 buffer = stack_buffer;
176 } else {
177 heap_buffer = make_unique<uint32_t[]>(buffer_size);
178 buffer = heap_buffer.get();
179 }
180
181 bool ret = this->in_flash ? load_from_flash(this->offset, buffer, buffer_size)
182 : load_from_rtc(this->offset, buffer, buffer_size);
183 if (!ret)
184 return false;
185
186 if (buffer[this->length_words] != calculate_crc(buffer, buffer + this->length_words, this->type))
187 return false;
188
189 memcpy(data, buffer, len);
190 return true;
191 }
192};
193
194class ESP8266Preferences : public ESPPreferences {
195 public:
196 uint32_t current_offset = 0;
197 uint32_t current_flash_offset = 0; // in words
198
199 void setup() {
200 s_flash_storage = new uint32_t[ESP8266_FLASH_STORAGE_SIZE]; // NOLINT
201 ESP_LOGVV(TAG, "Loading preferences from flash");
202
203 {
204 InterruptLock lock;
205 spi_flash_read(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4);
206 }
207 }
208
209 ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override {
210 const uint32_t length_words = bytes_to_words(length);
211 if (length_words > MAX_PREFERENCE_WORDS) {
212 ESP_LOGE(TAG, "Preference too large: %u words", static_cast<unsigned int>(length_words));
213 return {};
214 }
215
216 const uint32_t total_words = length_words + 1; // +1 for CRC
217 uint16_t offset;
218
219 if (in_flash) {
220 if (this->current_flash_offset + total_words > ESP8266_FLASH_STORAGE_SIZE)
221 return {};
222 offset = static_cast<uint16_t>(this->current_flash_offset);
223 this->current_flash_offset += total_words;
224 } else {
225 uint32_t start = this->current_offset;
226 bool in_normal = start < RTC_NORMAL_REGION_WORDS;
227 // Normal: offset 0-95 maps to RTC offset 32-127
228 // Eboot: offset 96-127 maps to RTC offset 0-31
229 if (in_normal && start + total_words > RTC_NORMAL_REGION_WORDS) {
230 // start is in normal but end is not -> switch to Eboot
231 this->current_offset = start = RTC_NORMAL_REGION_WORDS;
232 in_normal = false;
233 }
234 if (start + total_words > PREF_TOTAL_WORDS)
235 return {}; // Doesn't fit in RTC memory
236 // Convert preference offset to RTC memory offset
237 offset = static_cast<uint16_t>(in_normal ? start + RTC_EBOOT_REGION_WORDS : start - RTC_NORMAL_REGION_WORDS);
238 this->current_offset = start + total_words;
239 }
240
241 auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
242 pref->offset = offset;
243 pref->type = type;
244 pref->length_words = static_cast<uint8_t>(length_words);
245 pref->in_flash = in_flash;
246 return pref;
247 }
248
249 ESPPreferenceObject make_preference(size_t length, uint32_t type) override {
250#ifdef USE_ESP8266_PREFERENCES_FLASH
251 return make_preference(length, type, true);
252#else
253 return make_preference(length, type, false);
254#endif
255 }
256
257 bool sync() override {
258 if (!s_flash_dirty)
259 return true;
260 if (s_prevent_write)
261 return false;
262
263 ESP_LOGD(TAG, "Saving");
264 SpiFlashOpResult erase_res, write_res = SPI_FLASH_RESULT_OK;
265 {
266 InterruptLock lock;
267 erase_res = spi_flash_erase_sector(get_esp8266_flash_sector());
268 if (erase_res == SPI_FLASH_RESULT_OK) {
269 write_res = spi_flash_write(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4);
270 }
271 }
272 if (erase_res != SPI_FLASH_RESULT_OK) {
273 ESP_LOGE(TAG, "Erasing failed");
274 return false;
275 }
276 if (write_res != SPI_FLASH_RESULT_OK) {
277 ESP_LOGE(TAG, "Writing failed");
278 return false;
279 }
280
281 s_flash_dirty = false;
282 return true;
283 }
284
285 bool reset() override {
286 ESP_LOGD(TAG, "Erasing storage");
287 SpiFlashOpResult erase_res;
288 {
289 InterruptLock lock;
290 erase_res = spi_flash_erase_sector(get_esp8266_flash_sector());
291 }
292 if (erase_res != SPI_FLASH_RESULT_OK) {
293 ESP_LOGE(TAG, "Erasing failed");
294 return false;
295 }
296
297 // Protect flash from writing till restart
298 s_prevent_write = true;
299 return true;
300 }
301};
302
304 auto *pref = new ESP8266Preferences(); // NOLINT(cppcoreguidelines-owning-memory)
305 pref->setup();
306 global_preferences = pref;
307}
308void preferences_prevent_write(bool prevent) { s_prevent_write = prevent; }
309
310} // namespace esphome::esp8266
311
312namespace esphome {
313ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
314} // namespace esphome
315
316#endif // USE_ESP8266
uint16_t type
void preferences_prevent_write(bool prevent)
uint32_t calculate_crc(It first, It last, uint32_t type)
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::string size_t len
Definition helpers.h:595
ESPPreferences * global_preferences
uint16_t length
Definition tt21100.cpp:0