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