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