Files
esphome/esphome/preferences.py
T

107 lines
3.9 KiB
Python

"""Helpers for letting a component choose where a preference is persisted.
Preferences can be stored either in flash (durable across power loss) or in RTC
memory (fast, survives deep sleep and soft resets but not power loss). The
flash-vs-RTC choice is only meaningful on platforms whose preferences backend
honors the ``in_flash`` flag — currently ESP32 and ESP8266. On other platforms
the value is accepted only as ``flash`` (the sole supported backend).
Components include :func:`storage_schema` in their config and convert the chosen
value with :func:`is_in_flash` when calling ``make_preference``.
"""
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_STORAGE
from esphome.core import CORE
STORAGE_FLASH = "flash"
STORAGE_RTC = "rtc"
def _rtc_supported() -> bool:
"""Whether the active platform has an RTC-backed preferences backend.
Mirrors the C++ ``SOC_RTC_MEM_SUPPORTED`` guard in the ESP32 backend: the ESP32-C2
and -C61 have no RTC memory at all, so RTC storage is unavailable there.
"""
if CORE.is_esp8266:
return True
if CORE.is_esp32:
from esphome.components.esp32 import get_esp32_variant
from esphome.components.esp32.const import VARIANT_ESP32C2, VARIANT_ESP32C61
return get_esp32_variant() not in (VARIANT_ESP32C2, VARIANT_ESP32C61)
return False
def _default_storage() -> str:
"""Default that preserves each platform's historic behavior.
ESP8266 has always stored these preferences in RTC memory; every other
platform effectively used flash. Evaluated at validation time.
"""
return STORAGE_RTC if CORE.is_esp8266 else STORAGE_FLASH
def _validate_storage(value):
value = cv.one_of(STORAGE_FLASH, STORAGE_RTC, lower=True)(value)
if value == STORAGE_RTC and not _rtc_supported():
raise cv.Invalid(
f"'{STORAGE_RTC}' storage is not supported on this platform; only "
f"'{STORAGE_FLASH}' is available"
)
return value
def storage_schema():
"""Return an Optional(CONF_STORAGE) entry for merging into a component schema."""
return {cv.Optional(CONF_STORAGE, default=_default_storage): _validate_storage}
def request_rtc_storage() -> None:
"""Compile the RTC-backed storage into the ESP32 preferences backend.
The RTC storage region is left out of ESP32 builds unless something asks for
it, so unused builds don't reserve RTC memory. Call this from ``to_code``
when a config option selects RTC storage. No-op on other platforms (ESP8266
always has its RTC backend).
"""
if CORE.is_esp32:
cg.add_define("USE_ESP32_RTC_PREFERENCES")
def validate_rtc_storage(value):
"""Validate a boolean option that requests RTC-backed preference storage.
``false`` means "no request", not "disable": it never turns RTC storage off
(another option selecting ``storage: rtc`` still compiles it in). On ESP8266
the backend is integral and always enabled, so ``false`` is rejected rather
than silently ignored; ``true`` is a tolerated no-op there so shared config
packages work across mixed fleets.
"""
value = cv.boolean(value)
if not value:
if CORE.is_esp8266:
raise cv.Invalid(
"RTC preference storage is always enabled on ESP8266 and cannot "
"be disabled"
)
return value
if not _rtc_supported():
raise cv.Invalid("RTC preference storage is not supported on this platform")
return value
def is_in_flash(value: str) -> bool:
"""Map a CONF_STORAGE value to the ``in_flash`` argument of make_preference.
Call this from ``to_code``: when RTC storage is selected on ESP32 it also emits
the define that compiles the RTC storage buffer into the ESP32 backend (see
:func:`request_rtc_storage`).
"""
in_flash = value == STORAGE_FLASH
if not in_flash:
request_rtc_storage()
return in_flash