/* SETTINGS MODULE Copyright (C) 2016-2019 by Xose PĂ©rez */ #include #include "libs/EmbedisWrap.h" // ----------------------------------------------------------------------------- // Reverse engineering EEPROM storage format // ----------------------------------------------------------------------------- unsigned long settingsSize() { unsigned pos = SPI_FLASH_SEC_SIZE - 1; while (size_t len = EEPROMr.read(pos)) { if (0xFF == len) break; pos = pos - len - 2; } return SPI_FLASH_SEC_SIZE - pos + EEPROM_DATA_END; } // ----------------------------------------------------------------------------- unsigned int settingsKeyCount() { unsigned count = 0; unsigned pos = SPI_FLASH_SEC_SIZE - 1; while (size_t len = EEPROMr.read(pos)) { if (0xFF == len) break; pos = pos - len - 2; len = EEPROMr.read(pos); pos = pos - len - 2; count ++; } return count; } String settingsKeyName(unsigned int index) { String s; unsigned count = 0; unsigned pos = SPI_FLASH_SEC_SIZE - 1; while (size_t len = EEPROMr.read(pos)) { if (0xFF == len) break; pos = pos - len - 2; if (count == index) { s.reserve(len); for (unsigned char i = 0 ; i < len; i++) { s += (char) EEPROMr.read(pos + i + 1); } break; } count++; len = EEPROMr.read(pos); pos = pos - len - 2; } return s; } std::vector _settingsKeys() { // Get sorted list of keys std::vector keys; //unsigned int size = settingsKeyCount(); unsigned int size = settingsKeyCount(); for (unsigned int i=0; i 0) { keys.insert(keys.begin() + j, key); inserted = true; break; } } // If we could not insert it, just push it at the end if (!inserted) keys.push_back(key); } return keys; } // ----------------------------------------------------------------------------- // Key-value API // ----------------------------------------------------------------------------- void moveSetting(const char * from, const char * to) { String value = getSetting(from); if (value.length() > 0) setSetting(to, value); delSetting(from); } void moveSetting(const char * from, const char * to, unsigned int index) { String value = getSetting(from, index, ""); if (value.length() > 0) setSetting(to, index, value); delSetting(from, index); } void moveSettings(const char * from, const char * to) { unsigned int index = 0; while (index < 100) { String value = getSetting(from, index, ""); if (value.length() == 0) break; setSetting(to, index, value); delSetting(from, index); index++; } } template String getSetting(const String& key, T defaultValue) { String value; if (!Embedis::get(key, value)) value = String(defaultValue); return value; } template String getSetting(const String& key, unsigned int index, T defaultValue) { return getSetting(key + String(index), defaultValue); } String getSetting(const String& key) { return getSetting(key, ""); } template bool setSetting(const String& key, T value) { return Embedis::set(key, String(value)); } template bool setSetting(const String& key, unsigned int index, T value) { return setSetting(key + String(index), value); } bool delSetting(const String& key) { return Embedis::del(key); } bool delSetting(const String& key, unsigned int index) { return delSetting(key + String(index)); } bool hasSetting(const String& key) { return getSetting(key).length() != 0; } bool hasSetting(const String& key, unsigned int index) { return getSetting(key, index, "").length() != 0; } void saveSettings() { #if not SETTINGS_AUTOSAVE eepromCommit(); #endif } void resetSettings() { for (unsigned int i = 0; i < SPI_FLASH_SEC_SIZE; i++) { EEPROMr.write(i, 0xFF); } EEPROMr.commit(); } // ----------------------------------------------------------------------------- // API // ----------------------------------------------------------------------------- size_t settingsMaxSize() { size_t size = EEPROM_SIZE; if (size > SPI_FLASH_SEC_SIZE) size = SPI_FLASH_SEC_SIZE; size = (size + 3) & (~3); return size; } bool settingsRestoreJson(JsonObject& data) { // Check this is an ESPurna configuration file (must have "app":"ESPURNA") const char* app = data["app"]; if (!app || strcmp(app, APP_NAME) != 0) { DEBUG_MSG_P(PSTR("[SETTING] Wrong or missing 'app' key\n")); return false; } // Clear settings bool is_backup = data["backup"]; if (is_backup) { for (unsigned int i = EEPROM_DATA_END; i < SPI_FLASH_SEC_SIZE; i++) { EEPROMr.write(i, 0xFF); } } // Dump settings to memory buffer for (auto element : data) { if (strcmp(element.key, "app") == 0) continue; if (strcmp(element.key, "version") == 0) continue; if (strcmp(element.key, "backup") == 0) continue; setSetting(element.key, element.value.as()); } // Persist to EEPROM saveSettings(); DEBUG_MSG_P(PSTR("[SETTINGS] Settings restored successfully\n")); return true; } bool settingsRestoreJson(char* json_string, size_t json_buffer_size = 1024) { // XXX: as of right now, arduinojson cannot trigger callbacks for each key individually // Manually separating kv pairs can allow to parse only a small chunk, since we know that there is only string type used (even with bools / ints). Can be problematic when parsing data that was not generated by us. // Current parsing method is limited only by keys (~sizeof(uintptr_t) bytes per key, data is not copied when string is non-const) DynamicJsonBuffer jsonBuffer(json_buffer_size); JsonObject& root = jsonBuffer.parseObject((char *) json_string); if (!root.success()) { DEBUG_MSG_P(PSTR("[SETTINGS] JSON parsing error\n")); return false; } return settingsRestoreJson(root); } void settingsGetJson(JsonObject& root) { // Get sorted list of keys std::vector keys = _settingsKeys(); // Add the key-values to the json object for (unsigned int i=0; i char { return EEPROMr.read(pos); }, [](size_t pos, char value) { EEPROMr.write(pos, value); }, #if SETTINGS_AUTOSAVE []() { eepromCommit(); } #else []() {} #endif ); }