aboutsummaryrefslogtreecommitdiffhomepage
path: root/components/esp8266/preferences.cpp
blob: 4d87b0be705e14f88b7dc1e9b795bef508d6693a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
#ifdef USE_ESP8266

#include <c_types.h>
extern "C" {
#include "spi_flash.h"
}

#include "preferences.h"
#include <cstring>
#include "esphome/core/preferences.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/defines.h"

#include "esphome/core/version.h"

#if ESPHOME_VERSION_CODE < VERSION_CODE(2022, 2, 0)
  #error "Please Update ESPHome to the latest version."
#endif

#if ESPHOME_VERSION_CODE >= VERSION_CODE(2022, 3, 0)
  #error "KAUF external components have not been updated for this version of ESPHome yet, or you are not using the latest KAUF external components version"
#endif



namespace esphome {
namespace esp8266 {

static const char *const TAG = "esp8266.preferences";

static bool s_prevent_write = false;         // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static uint32_t *s_flash_storage = nullptr;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static bool s_flash_dirty = false;           // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

static const uint32_t ESP_RTC_USER_MEM_START = 0x60001200;
#define ESP_RTC_USER_MEM ((uint32_t *) ESP_RTC_USER_MEM_START)
static const uint32_t ESP_RTC_USER_MEM_SIZE_WORDS = 128;
static const uint32_t ESP_RTC_USER_MEM_SIZE_BYTES = ESP_RTC_USER_MEM_SIZE_WORDS * 4;

#ifdef USE_ESP8266_PREFERENCES_FLASH
static const uint32_t ESP8266_FLASH_STORAGE_SIZE = 128;
#else
static const uint32_t ESP8266_FLASH_STORAGE_SIZE = 64;
#endif

static inline bool esp_rtc_user_mem_read(uint32_t index, uint32_t *dest) {
  if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) {
    return false;
  }
  *dest = ESP_RTC_USER_MEM[index];  // NOLINT(performance-no-int-to-ptr)
  return true;
}

static inline bool esp_rtc_user_mem_write(uint32_t index, uint32_t value) {
  if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) {
    return false;
  }
  if (index < 32 && s_prevent_write) {
    return false;
  }

  auto *ptr = &ESP_RTC_USER_MEM[index];  // NOLINT(performance-no-int-to-ptr)
  *ptr = value;
  return true;
}

extern "C" uint32_t _SPIFFS_end;  // NOLINT

static uint32_t get_esp8266_flash_sector() {
  union {
    uint32_t *ptr;
    uint32_t uint;
  } data{};
  data.ptr = &_SPIFFS_end;
  return (data.uint - 0x40200000) / SPI_FLASH_SEC_SIZE;
}
static uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; }

template<class It> uint32_t calculate_crc(It first, It last, uint32_t type) {
  uint32_t crc = type;
  while (first != last) {
    crc ^= (*first++ * 2654435769UL) >> 1;
  }
  return crc;
}

static bool save_to_flash(size_t offset, const uint32_t *data, size_t len) {
  for (uint32_t i = 0; i < len; i++) {
    uint32_t j = offset + i;
    if (j >= ESP8266_FLASH_STORAGE_SIZE)
      return false;
    uint32_t v = data[i];
    uint32_t *ptr = &s_flash_storage[j];
    if (*ptr != v)
      s_flash_dirty = true;
    *ptr = v;
  }
  return true;
}

static bool load_from_flash(size_t offset, uint32_t *data, size_t len) {
  for (size_t i = 0; i < len; i++) {
    uint32_t j = offset + i;
    if (j >= ESP8266_FLASH_STORAGE_SIZE)
      return false;
    data[i] = s_flash_storage[j];
  }
  return true;
}

static bool save_to_rtc(size_t offset, const uint32_t *data, size_t len) {
  for (uint32_t i = 0; i < len; i++) {
    if (!esp_rtc_user_mem_write(offset + i, data[i]))
      return false;
  }
  return true;
}

static bool load_from_rtc(size_t offset, uint32_t *data, size_t len) {
  for (uint32_t i = 0; i < len; i++) {
    if (!esp_rtc_user_mem_read(offset + i, &data[i]))
      return false;
  }
  return true;
}

class ESP8266PreferenceBackend : public ESPPreferenceBackend {
 public:
  size_t offset = 0;
  uint32_t type = 0;
  bool in_flash = false;
  size_t length_words = 0;

  bool save(const uint8_t *data, size_t len) override {
    if ((len + 3) / 4 != length_words) {
      return false;
    }
    std::vector<uint32_t> buffer;
    buffer.resize(length_words + 1);
    memcpy(buffer.data(), data, len);
    buffer[buffer.size() - 1] = calculate_crc(buffer.begin(), buffer.end() - 1, type);

    if (in_flash) {
      return save_to_flash(offset, buffer.data(), buffer.size());
    } else {
      return save_to_rtc(offset, buffer.data(), buffer.size());
    }
  }
  bool load(uint8_t *data, size_t len) override {
    if ((len + 3) / 4 != length_words) {
      return false;
    }
    std::vector<uint32_t> buffer;
    buffer.resize(length_words + 1);
    bool ret;
    if (in_flash) {
      ret = load_from_flash(offset, buffer.data(), buffer.size());
    } else {
      ret = load_from_rtc(offset, buffer.data(), buffer.size());
    }
    if (!ret)
      return false;

    uint32_t crc = calculate_crc(buffer.begin(), buffer.end() - 1, type);
    if (buffer[buffer.size() - 1] != crc) {
      return false;
    }

    memcpy(data, buffer.data(), len);
    return true;
  }
};

class ESP8266Preferences : public ESPPreferences {
 public:
  uint32_t current_offset = 0;
  uint32_t current_flash_offset = 0;  // in words

  void setup() {
    s_flash_storage = new uint32_t[ESP8266_FLASH_STORAGE_SIZE];  // NOLINT
    ESP_LOGVV(TAG, "Loading preferences from flash...");

    {
      InterruptLock lock;
      spi_flash_read(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4);
    }
  }

  ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override {
    uint32_t length_words = (length + 3) / 4;
    if (in_flash) {

      uint32_t start;

      // ESPHome just assigns addresses as they pop up, but we want to preserve all addresses so they always remain
      // the same after an update and then saved values are always loaded properly after update.  Address is based on
      // type/hash we receive, which will always be the same for each entity based on its name.  Except WiFi, which
      // is based on compile time but we have hard coded to the original compile time of the first public release.
      if      ( type == 817087403  ) { start = 0;  }  // v1.5 - Blue LED
      else if ( type == 41191675   ) { start = 2;  }  // v1.5 - Relay
      else if ( type == 3932521563 ) { start = 4;  }  // v1.5 - Use Threshold
      else if ( type == 1903527169 ) { start = 6;  }  // v1.5 - Total Daily Energy
      else if ( type == 1432266978 ) { start = 8;  }  // v1.5 - WiFi Credentials
      else if ( type == 0          ) { start = 8;  }  // v1.7 - Clear Wifi Credentials
      else if ( type == 3616613942 ) { start = 34; }  // v1.6 - Select 1 (Button)
      else if ( type == 3104663617 ) { start = 36; }  // v1.6 - Select 2 (LED)
      else if ( type == 629479035  ) { start = 38; }  // v1.6 - Force AP Global Variable
      else if ( type == 3755051405 ) { start = 40; }  // v1.7 - First Boot - for factory testing
      
      // should never get to this because ESPHome only saves a pretty small number of well defined things.
      // end users might see it if they add their own entities and also have uart logging hooked up to see messages this early.
      else {
        ESP_LOGD("KAUF Preferences", "              !!!! NOTE: STORING PAST DEFAULT FLASH TABLE !!!!");

        // set start to end of memory map above if it's not there already.
        if ( this->current_flash_offset < 42 ) {this->current_flash_offset = 42;}
        start = this->current_flash_offset;
      }


      uint32_t end = start + length_words + 1;
      if (end > ESP8266_FLASH_STORAGE_SIZE)
        return {};
      auto *pref = new ESP8266PreferenceBackend();  // NOLINT(cppcoreguidelines-owning-memory)
      pref->offset = start;
      pref->type = type;
      pref->length_words = length_words;
      pref->in_flash = true;


      // don't adopt end as current_flash_offset unless past end of memory map.
      // otherwise it will reset to 104 every time an expected type comes through.
      if ( end > 42 ) {current_flash_offset = end;}

      ESP_LOGD(TAG, "Making Preference in Flash - start: %u: length: %u, length_words:%u type: %u", start, length, length_words, type);
      return {pref};
    }

    uint32_t start = current_offset;
    uint32_t end = start + length_words + 1;
    bool in_normal = start < 96;
    // Normal: offset 0-95 maps to RTC offset 32 - 127,
    // Eboot: offset 96-127 maps to RTC offset 0 - 31 words
    if (in_normal && end > 96) {
      // start is in normal but end is not -> switch to Eboot
      current_offset = start = 96;
      end = start + length_words + 1;
      in_normal = false;
    }

    if (end > 128) {
      // Doesn't fit in data, return uninitialized preference obj.
      return {};
    }

    uint32_t rtc_offset = in_normal ? start + 32 : start - 96;

    auto *pref = new ESP8266PreferenceBackend();  // NOLINT(cppcoreguidelines-owning-memory)
    pref->offset = rtc_offset;
    pref->type = type;
    pref->length_words = length_words;
    pref->in_flash = false;
    current_offset += length_words + 1;
    return pref;
  }

  ESPPreferenceObject make_preference(size_t length, uint32_t type) override {
#ifdef USE_ESP8266_PREFERENCES_FLASH
    return make_preference(length, type, true);
#else
    return make_preference(length, type, false);
#endif
  }

  bool sync() override {
    if (!s_flash_dirty)
      return true;
    if (s_prevent_write)
      return false;

    ESP_LOGD(TAG, "Saving preferences to flash...");
    SpiFlashOpResult erase_res, write_res = SPI_FLASH_RESULT_OK;
    {
      InterruptLock lock;
      erase_res = spi_flash_erase_sector(get_esp8266_flash_sector());
      if (erase_res == SPI_FLASH_RESULT_OK) {
        write_res = spi_flash_write(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4);
      }
    }
    if (erase_res != SPI_FLASH_RESULT_OK) {
      ESP_LOGV(TAG, "Erase ESP8266 flash failed!");
      return false;
    }
    if (write_res != SPI_FLASH_RESULT_OK) {
      ESP_LOGV(TAG, "Write ESP8266 flash failed!");
      return false;
    }

    s_flash_dirty = false;
    return true;
  }
};

void setup_preferences() {
  auto *pref = new ESP8266Preferences();  // NOLINT(cppcoreguidelines-owning-memory)
  pref->setup();
  global_preferences = pref;
}
void preferences_prevent_write(bool prevent) { s_prevent_write = prevent; }

}  // namespace esp8266

ESPPreferences *global_preferences;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

}  // namespace esphome

#endif  // USE_ESP8266