mirror of
https://github.com/esphome/esphome.git
synced 2024-11-10 01:07:45 +01:00
Configurable Flash Write Interval (#2119)
Co-authored-by: Alex <33379584+alexyao2015@users.noreply.github.com> Co-authored-by: Otto winter <otto@otto-winter.com>
This commit is contained in:
parent
71fc61117b
commit
491f8cc611
10 changed files with 160 additions and 41 deletions
|
@ -104,6 +104,7 @@ esphome/components/pn532/* @OttoWinter @jesserockz
|
||||||
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
|
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
|
||||||
esphome/components/pn532_spi/* @OttoWinter @jesserockz
|
esphome/components/pn532_spi/* @OttoWinter @jesserockz
|
||||||
esphome/components/power_supply/* @esphome/core
|
esphome/components/power_supply/* @esphome/core
|
||||||
|
esphome/components/preferences/* @esphome/core
|
||||||
esphome/components/pulse_meter/* @stevebaxter
|
esphome/components/pulse_meter/* @stevebaxter
|
||||||
esphome/components/pvvx_mithermometer/* @pasiz
|
esphome/components/pvvx_mithermometer/* @pasiz
|
||||||
esphome/components/rc522/* @glmnet
|
esphome/components/rc522/* @glmnet
|
||||||
|
|
|
@ -34,6 +34,7 @@ from .gpio import esp32_pin_to_code # noqa
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
|
AUTO_LOAD = ["preferences"]
|
||||||
|
|
||||||
|
|
||||||
def set_core_data(config):
|
def set_core_data(config):
|
||||||
|
|
|
@ -4,30 +4,53 @@
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include <nvs_flash.h>
|
#include <nvs_flash.h>
|
||||||
|
#include <cstring>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace esp32 {
|
namespace esp32 {
|
||||||
|
|
||||||
static const char *const TAG = "esp32.preferences";
|
static const char *const TAG = "esp32.preferences";
|
||||||
|
|
||||||
|
struct NVSData {
|
||||||
|
std::string key;
|
||||||
|
std::vector<uint8_t> data;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::vector<NVSData> s_pending_save; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
|
||||||
class ESP32PreferenceBackend : public ESPPreferenceBackend {
|
class ESP32PreferenceBackend : public ESPPreferenceBackend {
|
||||||
public:
|
public:
|
||||||
std::string key;
|
std::string key;
|
||||||
uint32_t nvs_handle;
|
uint32_t nvs_handle;
|
||||||
bool save(const uint8_t *data, size_t len) override {
|
bool save(const uint8_t *data, size_t len) override {
|
||||||
esp_err_t err = nvs_set_blob(nvs_handle, key.c_str(), data, len);
|
// try find in pending saves and update that
|
||||||
if (err != 0) {
|
for (auto &obj : s_pending_save) {
|
||||||
ESP_LOGV(TAG, "nvs_set_blob('%s', len=%u) failed: %s", key.c_str(), len, esp_err_to_name(err));
|
if (obj.key == key) {
|
||||||
return false;
|
obj.data.assign(data, data + len);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
err = nvs_commit(nvs_handle);
|
|
||||||
if (err != 0) {
|
|
||||||
ESP_LOGV(TAG, "nvs_commit('%s', len=%u) failed: %s", key.c_str(), len, esp_err_to_name(err));
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
NVSData save{};
|
||||||
|
save.key = key;
|
||||||
|
save.data.assign(data, data + len);
|
||||||
|
s_pending_save.emplace_back(save);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
bool load(uint8_t *data, size_t len) override {
|
bool load(uint8_t *data, size_t len) override {
|
||||||
|
// try find in pending saves and load from that
|
||||||
|
for (auto &obj : s_pending_save) {
|
||||||
|
if (obj.key == key) {
|
||||||
|
if (obj.data.size() != len) {
|
||||||
|
// size mismatch
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
memcpy(data, obj.data.data(), len);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
size_t actual_len;
|
size_t actual_len;
|
||||||
esp_err_t err = nvs_get_blob(nvs_handle, key.c_str(), nullptr, &actual_len);
|
esp_err_t err = nvs_get_blob(nvs_handle, key.c_str(), nullptr, &actual_len);
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
|
@ -82,6 +105,37 @@ class ESP32Preferences : public ESPPreferences {
|
||||||
|
|
||||||
return ESPPreferenceObject(pref);
|
return ESPPreferenceObject(pref);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool sync() override {
|
||||||
|
if (s_pending_save.empty())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "Saving preferences to flash...");
|
||||||
|
// goal try write all pending saves even if one fails
|
||||||
|
bool any_failed = false;
|
||||||
|
|
||||||
|
// go through vector from back to front (makes erase easier/more efficient)
|
||||||
|
for (ssize_t i = s_pending_save.size() - 1; i >= 0; i--) {
|
||||||
|
const auto &save = s_pending_save[i];
|
||||||
|
esp_err_t err = nvs_set_blob(nvs_handle, save.key.c_str(), save.data.data(), save.data.size());
|
||||||
|
if (err != 0) {
|
||||||
|
ESP_LOGV(TAG, "nvs_set_blob('%s', len=%u) failed: %s", save.key.c_str(), save.data.size(),
|
||||||
|
esp_err_to_name(err));
|
||||||
|
any_failed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
s_pending_save.erase(s_pending_save.begin() + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// note: commit on esp-idf currently is a no-op, nvs_set_blob always writes
|
||||||
|
esp_err_t err = nvs_commit(nvs_handle);
|
||||||
|
if (err != 0) {
|
||||||
|
ESP_LOGV(TAG, "nvs_commit() failed: %s", esp_err_to_name(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !any_failed;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void setup_preferences() {
|
void setup_preferences() {
|
||||||
|
|
|
@ -23,6 +23,7 @@ from .gpio import esp8266_pin_to_code # noqa
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
AUTO_LOAD = ["preferences"]
|
||||||
|
|
||||||
|
|
||||||
def set_core_data(config):
|
def set_core_data(config):
|
||||||
|
|
|
@ -73,33 +73,7 @@ template<class It> uint32_t calculate_crc(It first, It last, uint32_t type) {
|
||||||
return crc;
|
return crc;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool safe_flash() {
|
static bool save_to_flash(size_t offset, const uint32_t *data, size_t len) {
|
||||||
if (!s_flash_dirty)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
ESP_LOGVV(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool safe_to_flash(size_t offset, const uint32_t *data, size_t len) {
|
|
||||||
for (uint32_t i = 0; i < len; i++) {
|
for (uint32_t i = 0; i < len; i++) {
|
||||||
uint32_t j = offset + i;
|
uint32_t j = offset + i;
|
||||||
if (j >= ESP8266_FLASH_STORAGE_SIZE)
|
if (j >= ESP8266_FLASH_STORAGE_SIZE)
|
||||||
|
@ -110,7 +84,7 @@ static bool safe_to_flash(size_t offset, const uint32_t *data, size_t len) {
|
||||||
s_flash_dirty = true;
|
s_flash_dirty = true;
|
||||||
*ptr = v;
|
*ptr = v;
|
||||||
}
|
}
|
||||||
return safe_flash();
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool load_from_flash(size_t offset, uint32_t *data, size_t len) {
|
static bool load_from_flash(size_t offset, uint32_t *data, size_t len) {
|
||||||
|
@ -123,7 +97,7 @@ static bool load_from_flash(size_t offset, uint32_t *data, size_t len) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool safe_to_rtc(size_t offset, const uint32_t *data, size_t len) {
|
static bool save_to_rtc(size_t offset, const uint32_t *data, size_t len) {
|
||||||
for (uint32_t i = 0; i < len; i++)
|
for (uint32_t i = 0; i < len; i++)
|
||||||
if (!esp_rtc_user_mem_write(offset + i, data[i]))
|
if (!esp_rtc_user_mem_write(offset + i, data[i]))
|
||||||
return false;
|
return false;
|
||||||
|
@ -154,9 +128,9 @@ class ESP8266PreferenceBackend : public ESPPreferenceBackend {
|
||||||
buffer[buffer.size() - 1] = calculate_crc(buffer.begin(), buffer.end() - 1, type);
|
buffer[buffer.size() - 1] = calculate_crc(buffer.begin(), buffer.end() - 1, type);
|
||||||
|
|
||||||
if (in_flash) {
|
if (in_flash) {
|
||||||
return safe_to_flash(offset, buffer.data(), buffer.size());
|
return save_to_flash(offset, buffer.data(), buffer.size());
|
||||||
} else {
|
} else {
|
||||||
return safe_to_rtc(offset, buffer.data(), buffer.size());
|
return save_to_rtc(offset, buffer.data(), buffer.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bool load(uint8_t *data, size_t len) override {
|
bool load(uint8_t *data, size_t len) override {
|
||||||
|
@ -245,6 +219,34 @@ class ESP8266Preferences : public ESPPreferences {
|
||||||
return make_preference(length, type, false);
|
return make_preference(length, type, false);
|
||||||
#endif
|
#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() {
|
void setup_preferences() {
|
||||||
|
|
|
@ -548,7 +548,10 @@ bool OTAComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void OTAComponent::write_rtc_(uint32_t val) { this->rtc_.save(&val); }
|
void OTAComponent::write_rtc_(uint32_t val) {
|
||||||
|
this->rtc_.save(&val);
|
||||||
|
global_preferences->sync();
|
||||||
|
}
|
||||||
uint32_t OTAComponent::read_rtc_() {
|
uint32_t OTAComponent::read_rtc_() {
|
||||||
uint32_t val;
|
uint32_t val;
|
||||||
if (!this->rtc_.load(&val))
|
if (!this->rtc_.load(&val))
|
||||||
|
|
24
esphome/components/preferences/__init__.py
Normal file
24
esphome/components/preferences/__init__.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
from esphome.const import CONF_ID
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
|
||||||
|
CODEOWNERS = ["@esphome/core"]
|
||||||
|
|
||||||
|
preferences_ns = cg.esphome_ns.namespace("preferences")
|
||||||
|
IntervalSyncer = preferences_ns.class_("IntervalSyncer", cg.Component)
|
||||||
|
|
||||||
|
CONF_FLASH_WRITE_INTERVAL = "flash_write_interval"
|
||||||
|
CONFIG_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(IntervalSyncer),
|
||||||
|
cv.Optional(
|
||||||
|
CONF_FLASH_WRITE_INTERVAL, default="60s"
|
||||||
|
): cv.positive_time_period_milliseconds,
|
||||||
|
}
|
||||||
|
).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
cg.add(var.set_write_interval(config[CONF_FLASH_WRITE_INTERVAL]))
|
||||||
|
await cg.register_component(var, config)
|
23
esphome/components/preferences/syncer.h
Normal file
23
esphome/components/preferences/syncer.h
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/preferences.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace preferences {
|
||||||
|
|
||||||
|
class IntervalSyncer : public Component {
|
||||||
|
public:
|
||||||
|
void set_write_interval(uint32_t write_interval) { write_interval_ = write_interval; }
|
||||||
|
void setup() override {
|
||||||
|
set_interval(write_interval_, []() { global_preferences->sync(); });
|
||||||
|
}
|
||||||
|
void on_shutdown() override { global_preferences->sync(); }
|
||||||
|
float get_setup_priority() const override { return setup_priority::BUS; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint32_t write_interval_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace preferences
|
||||||
|
} // namespace esphome
|
|
@ -232,6 +232,8 @@ void WiFiComponent::save_wifi_sta(const std::string &ssid, const std::string &pa
|
||||||
strncpy(save.ssid, ssid.c_str(), sizeof(save.ssid));
|
strncpy(save.ssid, ssid.c_str(), sizeof(save.ssid));
|
||||||
strncpy(save.password, password.c_str(), sizeof(save.password));
|
strncpy(save.password, password.c_str(), sizeof(save.password));
|
||||||
this->pref_.save(&save);
|
this->pref_.save(&save);
|
||||||
|
// ensure it's written immediately
|
||||||
|
global_preferences->sync();
|
||||||
|
|
||||||
WiFiAP sta{};
|
WiFiAP sta{};
|
||||||
sta.set_ssid(ssid);
|
sta.set_ssid(ssid);
|
||||||
|
|
|
@ -37,6 +37,14 @@ class ESPPreferences {
|
||||||
public:
|
public:
|
||||||
virtual ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) = 0;
|
virtual ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) = 0;
|
||||||
virtual ESPPreferenceObject make_preference(size_t length, uint32_t type) = 0;
|
virtual ESPPreferenceObject make_preference(size_t length, uint32_t type) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Commit pending writes to flash.
|
||||||
|
*
|
||||||
|
* @return true if write is successful.
|
||||||
|
*/
|
||||||
|
virtual bool sync() = 0;
|
||||||
|
|
||||||
#ifndef USE_ESP8266
|
#ifndef USE_ESP8266
|
||||||
template<typename T, typename std::enable_if<std::is_trivially_copyable<T>::value, bool>::type = true>
|
template<typename T, typename std::enable_if<std::is_trivially_copyable<T>::value, bool>::type = true>
|
||||||
#else
|
#else
|
||||||
|
|
Loading…
Reference in a new issue