diff --git a/esphome/components/esp8266/const.py b/esphome/components/esp8266/const.py index 7e152ac41c7..bab40174c4f 100644 --- a/esphome/components/esp8266/const.py +++ b/esphome/components/esp8266/const.py @@ -37,15 +37,20 @@ def preference_words(value_bytes: int) -> int: return (value_bytes + 3) // 4 + 1 -def preference_slots(value_bytes: int, restore_from_flash: bool) -> int: - """How many values of this size fit, given where restore_from_flash sends them.""" +def preference_slots(value_bytes: int, in_flash: bool, restore_from_flash: bool) -> int: + """How many values of this size fit where make_preference() puts them.""" words = preference_words(value_bytes) - regions = ( - (PREFERENCES_FLASH_WORDS,) - if restore_from_flash - else (PREFERENCES_RTC_NORMAL_WORDS, PREFERENCES_RTC_EBOOT_WORDS) + if in_flash or restore_from_flash: + store = ( + PREFERENCES_FLASH_WORDS + if restore_from_flash + else PREFERENCES_FLASH_WORDS_WITHOUT_RESTORE + ) + return store // words + return sum( + region // words + for region in (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 diff --git a/esphome/components/esp8266/preferences.cpp b/esphome/components/esp8266/preferences.cpp index e11f1bec022..4af45b7c3d6 100644 --- a/esphome/components/esp8266/preferences.cpp +++ b/esphome/components/esp8266/preferences.cpp @@ -27,7 +27,9 @@ static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_BYTES = ESP_RTC_USER_MEM_SIZE_WO 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"); +static constexpr uint32_t RTC_CRASH_HANDLER_WORDS = 18; // Words 110-127, see crash_handler.cpp +static_assert(PREF_TOTAL_WORDS + RTC_CRASH_HANDLER_WORDS <= ESP_RTC_USER_MEM_SIZE_WORDS, + "preference regions overlap the crash handler's RTC words"); // Maximum preference size in words (limited by uint8_t length_words field) static constexpr uint32_t MAX_PREFERENCE_WORDS = 255; @@ -54,7 +56,7 @@ 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) { + if (index < RTC_EBOOT_REGION_WORDS && s_prevent_write) { return false; } diff --git a/tests/unit_tests/components/esp8266/test_preferences.py b/tests/unit_tests/components/esp8266/test_preferences.py new file mode 100644 index 00000000000..550dbf58b14 --- /dev/null +++ b/tests/unit_tests/components/esp8266/test_preferences.py @@ -0,0 +1,31 @@ +"""The config-time fit rule must match make_preference() in preferences.cpp.""" + +import pytest + +from esphome.components.esp8266.const import preference_slots, preference_words + + +@pytest.mark.parametrize( + ("value_bytes", "words"), [(0, 1), (1, 2), (4, 2), (5, 3), (94, 25)] +) +def test_preference_words_rounds_up_and_adds_the_checksum( + value_bytes: int, words: int +) -> None: + assert preference_words(value_bytes) == words + + +def test_rtc_values_cannot_straddle_the_regions() -> None: + # 25 words each: 3 fit in the 78-word region and 1 in the 32-word one + assert preference_slots(94, in_flash=False, restore_from_flash=False) == 4 + # 32 words each: 2 + 1, where 110 // 32 would also say 3 + assert preference_slots(124, in_flash=False, restore_from_flash=False) == 3 + # 39 words each: 2 + 0, where 110 // 39 would say 2 as well but the eboot region is skipped + assert preference_slots(152, in_flash=False, restore_from_flash=False) == 2 + # Larger than the biggest region + assert preference_slots(400, in_flash=False, restore_from_flash=False) == 0 + + +def test_flash_store_size_follows_restore_from_flash() -> None: + assert preference_slots(94, in_flash=True, restore_from_flash=True) == 5 + assert preference_slots(94, in_flash=False, restore_from_flash=True) == 5 + assert preference_slots(94, in_flash=True, restore_from_flash=False) == 2