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