diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index d95cbd239b5..6088c20b520 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -53,6 +53,10 @@ from .const import ( KEY_SERIAL1_REQUIRED, KEY_SERIAL_REQUIRED, KEY_WAVEFORM_REQUIRED, + PREFERENCES_FLASH_WORDS, + PREFERENCES_FLASH_WORDS_WITHOUT_RESTORE, + PREFERENCES_RTC_EBOOT_WORDS, + PREFERENCES_RTC_NORMAL_WORDS, THROW_STUBS_HEADER, enable_serial, enable_serial1, @@ -467,8 +471,16 @@ async def to_code(config: ConfigType) -> None: # Other projects like Tasmota & ESPEasy also use this cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH") + # Saved-settings layout, written down once in const.py and read by preferences.cpp + cg.add_define("ESP8266_PREFERENCES_RTC_NORMAL_WORDS", PREFERENCES_RTC_NORMAL_WORDS) + cg.add_define("ESP8266_PREFERENCES_RTC_EBOOT_WORDS", PREFERENCES_RTC_EBOOT_WORDS) if config[CONF_RESTORE_FROM_FLASH]: cg.add_define("USE_ESP8266_PREFERENCES_FLASH") + cg.add_define("ESP8266_PREFERENCES_FLASH_WORDS", PREFERENCES_FLASH_WORDS) + else: + cg.add_define( + "ESP8266_PREFERENCES_FLASH_WORDS", PREFERENCES_FLASH_WORDS_WITHOUT_RESTORE + ) if config[CONF_EARLY_PIN_INIT]: cg.add_define("USE_ESP8266_EARLY_PIN_INIT") diff --git a/esphome/components/esp8266/const.py b/esphome/components/esp8266/const.py index a702b397049..7926bf84b28 100644 --- a/esphome/components/esp8266/const.py +++ b/esphome/components/esp8266/const.py @@ -24,11 +24,35 @@ KEY_LDSCRIPT = "ldscript" # Crash-decode tools, resolved once per run by __init__ KEY_DECODE_TOOLS = "decode_tools" -# Space for saved settings, in 4-byte words, from preferences.cpp: RTC memory has two regions -# (RTC_NORMAL_REGION_WORDS, RTC_EBOOT_REGION_WORDS) that one saved value cannot span; with -# restore_from_flash everything goes to flash (ESP8266_FLASH_STORAGE_SIZE) -PREFERENCES_RTC_REGION_WORDS = (78, 32) +# Space for saved settings, in 4-byte words. Codegen hands these to preferences.cpp as defines, so this is the +# only place they are written down. RTC memory has two regions that one saved value cannot span, the normal one +# and the one eboot also uses; crash_handler.cpp keeps the last 18 of the 128 RTC words. The flash store holds +# every saved value with restore_from_flash and only the explicit ones without, so it is smaller then. +PREFERENCES_RTC_NORMAL_WORDS = 78 +PREFERENCES_RTC_EBOOT_WORDS = 32 PREFERENCES_FLASH_WORDS = 128 +PREFERENCES_FLASH_WORDS_WITHOUT_RESTORE = 64 + + +def preference_words(value_bytes: int) -> int: + """Words one saved value of this size takes, including its checksum word.""" + return (value_bytes + 3) // 4 + 1 + + +def preference_slots(value_bytes: int, restore_from_flash: bool) -> int: + """How many saved values of this size fit when every slot holds one of them. + + With restore_from_flash they all go to the flash store; otherwise they go to RTC memory, where a value + cannot straddle the two regions. + """ + words = preference_words(value_bytes) + regions = ( + (PREFERENCES_FLASH_WORDS,) + if restore_from_flash + else (PREFERENCES_RTC_NORMAL_WORDS, PREFERENCES_RTC_EBOOT_WORDS) + ) + return sum(region // words for region in regions) + # esp8266 namespace is already defined by arduino, manually prefix esphome esp8266_ns = cg.global_ns.namespace("esphome").namespace("esp8266") diff --git a/esphome/components/esp8266/preferences.cpp b/esphome/components/esp8266/preferences.cpp index dbfb233a1d4..39ef000e350 100644 --- a/esphome/components/esp8266/preferences.cpp +++ b/esphome/components/esp8266/preferences.cpp @@ -20,29 +20,23 @@ static constexpr uint32_t ESP_RTC_USER_MEM_START = 0x60001200; static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_WORDS = 128; static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_BYTES = ESP_RTC_USER_MEM_SIZE_WORDS * 4; -// RTC memory layout: +// RTC memory layout, with the sizes defined once in const.py and handed over by codegen: // - Eboot region: RTC words 0-31 (reserved, mapped from preference offset 78-109) // - Normal region: RTC words 32-109 (mapped from preference offset 0-77) // - Crash handler: RTC words 110-127 (reserved for crash_handler.cpp backtrace data) -// const.py mirrors the region and flash sizes for config-time space checks -static constexpr uint32_t RTC_EBOOT_REGION_WORDS = 32; // Words 0-31 reserved for eboot -static constexpr uint32_t RTC_NORMAL_REGION_WORDS = 78; // Words 32-109 for normal prefs +static constexpr uint32_t RTC_EBOOT_REGION_WORDS = ESP8266_PREFERENCES_RTC_EBOOT_WORDS; +static constexpr uint32_t RTC_NORMAL_REGION_WORDS = ESP8266_PREFERENCES_RTC_NORMAL_WORDS; static constexpr uint32_t PREF_TOTAL_WORDS = RTC_EBOOT_REGION_WORDS + RTC_NORMAL_REGION_WORDS; // 110 +static_assert(PREF_TOTAL_WORDS <= ESP_RTC_USER_MEM_SIZE_WORDS, "preference regions overflow RTC memory"); // Maximum preference size in words (limited by uint8_t length_words field) static constexpr uint32_t MAX_PREFERENCE_WORDS = 255; #define ESP_RTC_USER_MEM ((uint32_t *) ESP_RTC_USER_MEM_START) -// Flash storage size depends on esp8266 -> restore_from_flash YAML option (default: false). -// When enabled (USE_ESP8266_PREFERENCES_FLASH), all preferences default to flash and need -// 128 words (512 bytes). When disabled, only explicit flash prefs use this storage so -// 64 words (256 bytes) suffices since most preferences go to RTC memory instead. -#ifdef USE_ESP8266_PREFERENCES_FLASH -static constexpr uint32_t ESP8266_FLASH_STORAGE_SIZE = 128; -#else -static constexpr uint32_t ESP8266_FLASH_STORAGE_SIZE = 64; -#endif +// With restore_from_flash every preference goes here; without it only the explicit flash ones do, +// so codegen hands over a smaller size (const.py). +static constexpr uint32_t ESP8266_FLASH_STORAGE_SIZE = ESP8266_PREFERENCES_FLASH_WORDS; static uint32_t s_flash_storage[ESP8266_FLASH_STORAGE_SIZE]; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) @@ -124,7 +118,7 @@ static bool load_from_rtc(size_t offset, uint32_t *data, size_t len) { // Maximum buffer for any single preference - bounded by storage sizes. // Flash prefs: bounded by ESP8266_FLASH_STORAGE_SIZE (128 or 64 words). -// RTC prefs: bounded by RTC_NORMAL_REGION_WORDS (96) - a single pref can't span both RTC regions. +// RTC prefs: bounded by RTC_NORMAL_REGION_WORDS (78) - a single pref can't span both RTC regions. static constexpr size_t PREF_MAX_BUFFER_WORDS = ESP8266_FLASH_STORAGE_SIZE > RTC_NORMAL_REGION_WORDS ? ESP8266_FLASH_STORAGE_SIZE : RTC_NORMAL_REGION_WORDS; @@ -181,8 +175,8 @@ ESPPreferenceObject ESP8266Preferences::make_preference(size_t length, uint32_t } else { uint32_t start = this->current_offset; bool in_normal = start < RTC_NORMAL_REGION_WORDS; - // Normal: offset 0-95 maps to RTC offset 32-127 - // Eboot: offset 96-127 maps to RTC offset 0-31 + // Normal: offset 0-77 maps to RTC offset 32-109 + // Eboot: offset 78-109 maps to RTC offset 0-31 if (in_normal && start + total_words > RTC_NORMAL_REGION_WORDS) { // start is in normal but end is not -> switch to Eboot this->current_offset = start = RTC_NORMAL_REGION_WORDS; diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 8d243043bde..7a10abe3a3c 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -597,6 +597,9 @@ #define USE_ESP8266_LOGGER_SERIAL #define USE_ESP8266_LOGGER_SERIAL1 #define USE_ESP8266_PREFERENCES_FLASH +#define ESP8266_PREFERENCES_FLASH_WORDS 128 +#define ESP8266_PREFERENCES_RTC_EBOOT_WORDS 32 +#define ESP8266_PREFERENCES_RTC_NORMAL_WORDS 78 #define USE_ESP8266_UART_SERIAL #define USE_ESP8266_UART_SERIAL1 #define USE_HTTP_REQUEST_ESP8266_HTTPS