ESPHome 2026.8.0-dev
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
8#include "preferences.h"
10#include "esphome/core/log.h"
12
13#include <cstring>
14
15namespace esphome::esp8266 {
16
17static const char *const TAG = "preferences";
18
19static constexpr uint32_t ESP_RTC_USER_MEM_START = 0x60001200;
20static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_WORDS = 128;
21static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_BYTES = ESP_RTC_USER_MEM_SIZE_WORDS * 4;
22
23// RTC memory layout:
24// - Eboot region: RTC words 0-31 (reserved, mapped from preference offset 78-109)
25// - Normal region: RTC words 32-109 (mapped from preference offset 0-77)
26// - Crash handler: RTC words 110-127 (reserved for crash_handler.cpp backtrace data)
27static constexpr uint32_t RTC_EBOOT_REGION_WORDS = 32; // Words 0-31 reserved for eboot
28static constexpr uint32_t RTC_NORMAL_REGION_WORDS = 78; // Words 32-109 for normal prefs
29static constexpr uint32_t PREF_TOTAL_WORDS = RTC_EBOOT_REGION_WORDS + RTC_NORMAL_REGION_WORDS; // 110
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,clang-analyzer-core.FixedAddressDereference)
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; // NOLINT(clang-analyzer-core.FixedAddressDereference)
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 bool save_to_flash(size_t offset, const uint32_t *data, size_t len) {
85 for (uint32_t i = 0; i < len; i++) {
86 uint32_t j = offset + i;
87 if (j >= ESP8266_FLASH_STORAGE_SIZE)
88 return false;
89 uint32_t v = data[i];
90 uint32_t *ptr = &s_flash_storage[j];
91 if (*ptr != v)
92 s_flash_dirty = true;
93 *ptr = v;
94 }
95 return true;
96}
97
98static bool load_from_flash(size_t offset, uint32_t *data, size_t len) {
99 for (size_t i = 0; i < len; i++) {
100 uint32_t j = offset + i;
101 if (j >= ESP8266_FLASH_STORAGE_SIZE)
102 return false;
103 data[i] = s_flash_storage[j];
104 }
105 return true;
106}
107
108static bool save_to_rtc(size_t offset, const uint32_t *data, size_t len) {
109 for (uint32_t i = 0; i < len; i++) {
110 if (!esp_rtc_user_mem_write(offset + i, data[i]))
111 return false;
112 }
113 return true;
114}
115
116static bool load_from_rtc(size_t offset, uint32_t *data, size_t len) {
117 for (uint32_t i = 0; i < len; i++) {
118 if (!esp_rtc_user_mem_read(offset + i, &data[i]))
119 return false;
120 }
121 return true;
122}
123
124// Maximum buffer for any single preference - bounded by storage sizes.
125// Flash prefs: bounded by ESP8266_FLASH_STORAGE_SIZE (128 or 64 words).
126// RTC prefs: bounded by RTC_NORMAL_REGION_WORDS (96) - a single pref can't span both RTC regions.
127static constexpr size_t PREF_MAX_BUFFER_WORDS =
128 ESP8266_FLASH_STORAGE_SIZE > RTC_NORMAL_REGION_WORDS ? ESP8266_FLASH_STORAGE_SIZE : RTC_NORMAL_REGION_WORDS;
129
130bool ESP8266PreferenceBackend::save(const uint8_t *data, size_t len) {
132 return false;
133 const size_t buffer_size = static_cast<size_t>(this->length_words) + 1;
134 if (buffer_size > PREF_MAX_BUFFER_WORDS)
135 return false;
136 uint32_t buffer[PREF_MAX_BUFFER_WORDS];
137 rtc_pref_encode(buffer, this->type, this->length_words, data, len);
138 return this->in_flash ? save_to_flash(this->offset, buffer, buffer_size)
139 : save_to_rtc(this->offset, buffer, buffer_size);
140}
141
142bool ESP8266PreferenceBackend::load(uint8_t *data, size_t len) {
144 return false;
145 const size_t buffer_size = static_cast<size_t>(this->length_words) + 1;
146 if (buffer_size > PREF_MAX_BUFFER_WORDS)
147 return false;
148 uint32_t buffer[PREF_MAX_BUFFER_WORDS];
149 bool ret = this->in_flash ? load_from_flash(this->offset, buffer, buffer_size)
150 : load_from_rtc(this->offset, buffer, buffer_size);
151 if (!ret)
152 return false;
153 return rtc_pref_decode(buffer, this->type, this->length_words, data, len);
154}
155
157 ESP_LOGVV(TAG, "Loading preferences from flash");
158
159 {
161 spi_flash_read(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4);
162 }
163}
164
166 const uint32_t length_words = rtc_pref_bytes_to_words(length);
167 if (length_words > MAX_PREFERENCE_WORDS) {
168 ESP_LOGE(TAG, "Preference too large: %u words", static_cast<unsigned int>(length_words));
169 return {};
170 }
171
172 const uint32_t total_words = length_words + 1; // +1 for checksum
173 uint16_t offset;
174
175 if (in_flash) {
176 if (this->current_flash_offset + total_words > ESP8266_FLASH_STORAGE_SIZE)
177 return {};
178 offset = static_cast<uint16_t>(this->current_flash_offset);
179 this->current_flash_offset += total_words;
180 } else {
181 uint32_t start = this->current_offset;
182 bool in_normal = start < RTC_NORMAL_REGION_WORDS;
183 // Normal: offset 0-95 maps to RTC offset 32-127
184 // Eboot: offset 96-127 maps to RTC offset 0-31
185 if (in_normal && start + total_words > RTC_NORMAL_REGION_WORDS) {
186 // start is in normal but end is not -> switch to Eboot
187 this->current_offset = start = RTC_NORMAL_REGION_WORDS;
188 in_normal = false;
189 }
190 if (start + total_words > PREF_TOTAL_WORDS)
191 return {}; // Doesn't fit in RTC memory
192 // Convert preference offset to RTC memory offset
193 offset = static_cast<uint16_t>(in_normal ? start + RTC_EBOOT_REGION_WORDS : start - RTC_NORMAL_REGION_WORDS);
194 this->current_offset = start + total_words;
195 }
196
197 auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
198 pref->offset = offset;
199 pref->type = type;
200 pref->length_words = static_cast<uint8_t>(length_words);
201 pref->in_flash = in_flash;
202 return ESPPreferenceObject(pref);
203}
204
206 if (!s_flash_dirty)
207 return true;
208 if (s_prevent_write)
209 return false;
210
211 ESP_LOGD(TAG, "Saving");
212 SpiFlashOpResult erase_res, write_res = SPI_FLASH_RESULT_OK;
213 {
215 erase_res = spi_flash_erase_sector(get_esp8266_flash_sector());
216 if (erase_res == SPI_FLASH_RESULT_OK) {
217 write_res = spi_flash_write(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4);
218 }
219 }
220 if (erase_res != SPI_FLASH_RESULT_OK) {
221 ESP_LOGE(TAG, "Erasing failed");
222 return false;
223 }
224 if (write_res != SPI_FLASH_RESULT_OK) {
225 ESP_LOGE(TAG, "Writing failed");
226 return false;
227 }
228
229 s_flash_dirty = false;
230 return true;
231}
232
234 ESP_LOGD(TAG, "Erasing storage");
235 SpiFlashOpResult erase_res;
236 {
238 erase_res = spi_flash_erase_sector(get_esp8266_flash_sector());
239 }
240 if (erase_res != SPI_FLASH_RESULT_OK) {
241 ESP_LOGE(TAG, "Erasing failed");
242 return false;
243 }
244
245 // Protect flash from writing till restart
246 s_prevent_write = true;
247 return true;
248}
249
250static ESP8266Preferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
251
252ESP8266Preferences *get_preferences() { return &s_preferences; }
253
255 s_preferences.setup();
256 global_preferences = &s_preferences;
257}
258void preferences_prevent_write(bool prevent) { s_prevent_write = prevent; }
259
260} // namespace esphome::esp8266
261
262namespace esphome {
263ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
264} // namespace esphome
265
266#endif // USE_ESP8266
Helper class to disable interrupts.
Definition helpers.h:1972
bool save(const uint8_t *data, size_t len)
bool load(uint8_t *data, size_t len)
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash)
uint16_t type
void preferences_prevent_write(bool prevent)
ESP8266Preferences * get_preferences()
std::span< const uint8_t > data
Preferences ESPPreferences
Definition preferences.h:42
size_t rtc_pref_bytes_to_words(size_t bytes)
Round a byte count up to whole 32-bit words.
const void size_t len
Definition hal.h:64
bool rtc_pref_decode(const uint32_t *buffer, uint32_t type, uint8_t length_words, uint8_t *data, size_t len)
Verify the checksum of a record held in buffer (length_words data words + 1 checksum word) and,...
ESPPreferences * global_preferences
void rtc_pref_encode(uint32_t *buffer, uint32_t type, uint8_t length_words, const uint8_t *data, size_t len)
Encode len data bytes into buffer (length_words data words + 1 trailing checksum word).
static void uint32_t
uint16_t length
Definition tt21100.cpp:0
SemaphoreHandle_t lock