mirror of
https://github.com/esphome/esphome.git
synced 2026-07-10 08:55:36 +00:00
Merge branch 'wifi-fast-connect-substitution-string' into integration
This commit is contained in:
+1
-1
@@ -22,7 +22,7 @@ RUN \
|
||||
-r /requirements.txt
|
||||
|
||||
# Install the ESPHome Device Builder dashboard.
|
||||
RUN uv pip install --no-cache-dir esphome-device-builder==1.0.29
|
||||
RUN uv pip install --no-cache-dir esphome-device-builder==1.1.0
|
||||
|
||||
RUN \
|
||||
platformio settings set enable_telemetry No \
|
||||
|
||||
@@ -6,7 +6,11 @@ from pathlib import Path
|
||||
from esphome.components.esp32 import get_esp32_variant, idf_version
|
||||
import esphome.config_validation as cv
|
||||
from esphome.core import CORE
|
||||
from esphome.framework_helpers import get_project_compile_flags, get_project_link_flags
|
||||
from esphome.framework_helpers import (
|
||||
get_project_compile_flags,
|
||||
get_project_cxx_compile_flags,
|
||||
get_project_link_flags,
|
||||
)
|
||||
from esphome.helpers import mkdir_p, write_file_if_changed
|
||||
|
||||
# Replaces the IDF default C++ standard (-std=gnu++2b appended to
|
||||
@@ -91,6 +95,14 @@ def get_project_cmakelists(minimal: bool = False) -> str:
|
||||
for flag in project_compile_opts
|
||||
)
|
||||
|
||||
# Flags registered via cg.add_cxx_build_flag() go on CXX_COMPILE_OPTIONS
|
||||
# (not COMPILE_OPTIONS) because GCC warns when a C++-only flag such as
|
||||
# -Wno-volatile is passed on a C compile.
|
||||
cxx_compile_options = "\n".join(
|
||||
f'idf_build_set_property(CXX_COMPILE_OPTIONS "{flag}" APPEND)'
|
||||
for flag in get_project_cxx_compile_flags()
|
||||
)
|
||||
|
||||
cpp_standard_options = (
|
||||
CPP_STANDARD_TEMPLATE.format(standard=CORE.cpp_standard)
|
||||
if CORE.cpp_standard
|
||||
@@ -155,6 +167,8 @@ include($ENV{{IDF_PATH}}/tools/cmake/project.cmake)
|
||||
|
||||
{cpp_standard_options}
|
||||
|
||||
{cxx_compile_options}
|
||||
|
||||
{extra_compile_options}
|
||||
|
||||
{managed_components_property}
|
||||
|
||||
@@ -108,7 +108,6 @@ Import("env")
|
||||
def write_cxx_flags_script() -> None:
|
||||
path = CORE.relative_build_path(CXX_FLAGS_FILE_NAME)
|
||||
contents = CXX_FLAGS_FILE_CONTENTS
|
||||
if not CORE.is_host:
|
||||
contents += 'env.Append(CXXFLAGS=["-Wno-volatile"])'
|
||||
contents += "\n"
|
||||
for flag in sorted(CORE.cxx_build_flags):
|
||||
contents += f'env.Append(CXXFLAGS=["{flag}"])\n'
|
||||
write_file_if_changed(path, contents)
|
||||
|
||||
@@ -26,6 +26,7 @@ from esphome.cpp_generator import ( # noqa: F401
|
||||
add,
|
||||
add_build_flag,
|
||||
add_build_unflag,
|
||||
add_cxx_build_flag,
|
||||
add_define,
|
||||
add_global,
|
||||
add_library,
|
||||
|
||||
@@ -11,8 +11,11 @@ class ESP32PreferenceBackend final {
|
||||
bool save(const uint8_t *data, size_t len);
|
||||
bool load(uint8_t *data, size_t len);
|
||||
|
||||
uint32_t key;
|
||||
uint32_t nvs_handle;
|
||||
uint32_t key{0};
|
||||
uint32_t nvs_handle{0}; // NVS (flash) path
|
||||
uint16_t rtc_offset{0}; // RTC path: word offset into the RTC storage region
|
||||
uint8_t length_words{0}; // RTC path: data length in 32-bit words
|
||||
bool in_flash{true}; // true: store in NVS (flash); false: store in RTC memory
|
||||
};
|
||||
|
||||
class ESP32Preferences;
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
#include "preferences.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/preferences_rtc.h"
|
||||
#include <esp_attr.h>
|
||||
#include <nvs_flash.h>
|
||||
#include <soc/soc_caps.h>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
@@ -18,6 +21,48 @@ struct NVSData {
|
||||
|
||||
static std::vector<NVSData> s_pending_save; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
// RTC memory backend for preferences requested with in_flash=false. Survives deep sleep and
|
||||
// software/CPU resets, but not power loss; integrity is guarded by a per-record checksum so
|
||||
// power-on garbage is detected on load. Keep this small: RTC memory is scarce and shared.
|
||||
//
|
||||
// Only compiled in when USE_ESP32_RTC_PREFERENCES_STORAGE is set (see preferences.h): the storage
|
||||
// buffer reserves RTC memory, so it exists only when some config option actually selected RTC
|
||||
// storage AND the variant has RTC memory (the ESP32-C2 and -C61 have none, so RTC_NOINIT_ATTR would
|
||||
// have no section to land in and fail to link). Otherwise in_flash=false transparently falls back
|
||||
// to NVS (see make_preference below).
|
||||
//
|
||||
// On variants with only RTC fast memory (C3/C6/H2/P4/C5/...) RTC_NOINIT_ATTR lands in RTC fast memory.
|
||||
// This is still safe: the linker reserves .rtc_noinit ahead of any RTC-fast-as-heap pool
|
||||
// (CONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP), and IDF keeps the RTC fast power domain on in deep
|
||||
// sleep (forced on whether or not it is used as heap), so the data is retained across both resets and
|
||||
// deep sleep -- only power loss clears it.
|
||||
#ifdef USE_ESP32_RTC_PREFERENCES_STORAGE
|
||||
static constexpr size_t RTC_PREF_SIZE_WORDS = 64; // 256 bytes
|
||||
static constexpr size_t RTC_PREF_MAX_WORDS = 255; // length_words field is a uint8_t
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
static RTC_NOINIT_ATTR uint32_t s_rtc_storage[RTC_PREF_SIZE_WORDS];
|
||||
|
||||
static bool save_to_rtc(uint16_t offset, uint32_t key, uint8_t length_words, const uint8_t *data, size_t len) {
|
||||
if (rtc_pref_bytes_to_words(len) != length_words)
|
||||
return false;
|
||||
const size_t buffer_size = static_cast<size_t>(length_words) + 1;
|
||||
if (static_cast<size_t>(offset) + buffer_size > RTC_PREF_SIZE_WORDS)
|
||||
return false;
|
||||
rtc_pref_encode(&s_rtc_storage[offset], key, length_words, data, len);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool load_from_rtc(uint16_t offset, uint32_t key, uint8_t length_words, uint8_t *data, size_t len) {
|
||||
if (rtc_pref_bytes_to_words(len) != length_words)
|
||||
return false;
|
||||
const size_t buffer_size = static_cast<size_t>(length_words) + 1;
|
||||
if (static_cast<size_t>(offset) + buffer_size > RTC_PREF_SIZE_WORDS)
|
||||
return false;
|
||||
return rtc_pref_decode(&s_rtc_storage[offset], key, length_words, data, len);
|
||||
}
|
||||
#endif // USE_ESP32_RTC_PREFERENCES_STORAGE
|
||||
|
||||
// open() runs from app_main() before the logger is initialized, so any failure
|
||||
// must be deferred until after global_logger is set. This is emitted from the
|
||||
// first make_preference() call, which runs from the generated setup() after
|
||||
@@ -25,6 +70,10 @@ static std::vector<NVSData> s_pending_save; // NOLINT(cppcoreguidelines-avoid-n
|
||||
static esp_err_t s_open_err = ESP_OK; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
bool ESP32PreferenceBackend::save(const uint8_t *data, size_t len) {
|
||||
#ifdef USE_ESP32_RTC_PREFERENCES_STORAGE
|
||||
if (!this->in_flash)
|
||||
return save_to_rtc(this->rtc_offset, this->key, this->length_words, data, len);
|
||||
#endif
|
||||
// try find in pending saves and update that
|
||||
for (auto &obj : s_pending_save) {
|
||||
if (obj.key == this->key) {
|
||||
@@ -41,6 +90,10 @@ bool ESP32PreferenceBackend::save(const uint8_t *data, size_t len) {
|
||||
}
|
||||
|
||||
bool ESP32PreferenceBackend::load(uint8_t *data, size_t len) {
|
||||
#ifdef USE_ESP32_RTC_PREFERENCES_STORAGE
|
||||
if (!this->in_flash)
|
||||
return load_from_rtc(this->rtc_offset, this->key, this->length_words, data, len);
|
||||
#endif
|
||||
// try find in pending saves and load from that
|
||||
for (auto &obj : s_pending_save) {
|
||||
if (obj.key == this->key) {
|
||||
@@ -94,6 +147,26 @@ void ESP32Preferences::open() {
|
||||
}
|
||||
}
|
||||
|
||||
ESPPreferenceObject ESP32Preferences::make_preference(size_t length, uint32_t type, bool in_flash) {
|
||||
#ifdef USE_ESP32_RTC_PREFERENCES_STORAGE
|
||||
if (!in_flash)
|
||||
return this->make_rtc_preference_(length, type);
|
||||
#else
|
||||
if (!in_flash) {
|
||||
// RTC storage is not compiled in (no config option selected it), so this request
|
||||
// falls back to NVS -- the historic ESP32 behavior. Warn once so callers explicitly
|
||||
// asking for RTC storage can discover the fallback.
|
||||
static bool warned = false;
|
||||
if (!warned) {
|
||||
ESP_LOGW(TAG, "RTC preference storage not compiled in; using NVS (enable with 'preferences: rtc_storage: true')");
|
||||
warned = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// in_flash, or RTC storage not compiled in: fall back to NVS.
|
||||
return this->make_preference(length, type);
|
||||
}
|
||||
|
||||
ESPPreferenceObject ESP32Preferences::make_preference(size_t length, uint32_t type) {
|
||||
if (s_open_err != ESP_OK) {
|
||||
if (this->nvs_handle == 0) {
|
||||
@@ -106,10 +179,34 @@ ESPPreferenceObject ESP32Preferences::make_preference(size_t length, uint32_t ty
|
||||
auto *pref = new ESP32PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
pref->nvs_handle = this->nvs_handle;
|
||||
pref->key = type;
|
||||
pref->in_flash = true;
|
||||
|
||||
return ESPPreferenceObject(pref);
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_RTC_PREFERENCES_STORAGE
|
||||
ESPPreferenceObject ESP32Preferences::make_rtc_preference_(size_t length, uint32_t type) {
|
||||
const uint32_t length_words = rtc_pref_bytes_to_words(length);
|
||||
if (length_words > RTC_PREF_MAX_WORDS) {
|
||||
ESP_LOGE(TAG, "RTC preference too large: %" PRIu32 " words", length_words);
|
||||
return {};
|
||||
}
|
||||
const uint32_t total_words = length_words + 1; // +1 for checksum
|
||||
if (static_cast<size_t>(this->current_rtc_offset_) + total_words > RTC_PREF_SIZE_WORDS) {
|
||||
ESP_LOGE(TAG, "RTC preference storage full, cannot allocate %" PRIu32 " words", total_words);
|
||||
return {};
|
||||
}
|
||||
auto *pref = new ESP32PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
pref->key = type;
|
||||
pref->in_flash = false;
|
||||
pref->rtc_offset = this->current_rtc_offset_;
|
||||
pref->length_words = static_cast<uint8_t>(length_words);
|
||||
this->current_rtc_offset_ += static_cast<uint16_t>(total_words);
|
||||
|
||||
return ESPPreferenceObject(pref);
|
||||
}
|
||||
#endif // USE_ESP32_RTC_PREFERENCES_STORAGE
|
||||
|
||||
bool ESP32Preferences::sync() {
|
||||
if (s_pending_save.empty())
|
||||
return true;
|
||||
@@ -186,6 +283,12 @@ bool ESP32Preferences::is_changed_(uint32_t nvs_handle, const NVSData &to_save,
|
||||
bool ESP32Preferences::reset() {
|
||||
ESP_LOGD(TAG, "Erasing storage");
|
||||
s_pending_save.clear();
|
||||
#ifdef USE_ESP32_RTC_PREFERENCES_STORAGE
|
||||
// Invalidate RTC-backed preferences too (checksum will no longer match). current_rtc_offset_ is
|
||||
// deliberately left alone: existing backends keep pointing at their allocated slots, and reset()
|
||||
// is always followed by a restart (same reason nvs_handle is zeroed below).
|
||||
memset(s_rtc_storage, 0, sizeof(s_rtc_storage));
|
||||
#endif
|
||||
|
||||
nvs_flash_deinit();
|
||||
nvs_flash_erase();
|
||||
|
||||
@@ -2,6 +2,15 @@
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/core/preference_backend.h"
|
||||
#include <soc/soc_caps.h>
|
||||
|
||||
// RTC-backed preference storage is compiled in only when a config option actually selects it
|
||||
// (USE_ESP32_RTC_PREFERENCES, emitted during code generation) and the variant has RTC memory
|
||||
// (SOC_RTC_MEM_SUPPORTED; the ESP32-C2 and -C61 have none). Otherwise in_flash=false falls
|
||||
// back to NVS and no RTC memory is reserved.
|
||||
#if defined(USE_ESP32_RTC_PREFERENCES) && SOC_RTC_MEM_SUPPORTED
|
||||
#define USE_ESP32_RTC_PREFERENCES_STORAGE
|
||||
#endif
|
||||
|
||||
namespace esphome::esp32 {
|
||||
|
||||
@@ -11,9 +20,8 @@ class ESP32Preferences final : public PreferencesMixin<ESP32Preferences> {
|
||||
public:
|
||||
using PreferencesMixin<ESP32Preferences>::make_preference;
|
||||
void open();
|
||||
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) {
|
||||
return this->make_preference(length, type);
|
||||
}
|
||||
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash);
|
||||
// Two-argument form defaults to NVS (flash) storage, preserving historic ESP32 behavior.
|
||||
ESPPreferenceObject make_preference(size_t length, uint32_t type);
|
||||
bool sync();
|
||||
bool reset();
|
||||
@@ -22,6 +30,13 @@ class ESP32Preferences final : public PreferencesMixin<ESP32Preferences> {
|
||||
|
||||
protected:
|
||||
bool is_changed_(uint32_t nvs_handle, const NVSData &to_save, const char *key_str);
|
||||
|
||||
#ifdef USE_ESP32_RTC_PREFERENCES_STORAGE
|
||||
// RTC-backed storage (in_flash=false).
|
||||
ESPPreferenceObject make_rtc_preference_(size_t length, uint32_t type);
|
||||
// Next free word offset in the RTC storage region (bump allocated in make_preference order).
|
||||
uint16_t current_rtc_offset_{0};
|
||||
#endif
|
||||
};
|
||||
|
||||
void setup_preferences();
|
||||
|
||||
@@ -8,6 +8,7 @@ extern "C" {
|
||||
#include "preferences.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/preferences_rtc.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
@@ -80,16 +81,6 @@ static uint32_t get_esp8266_flash_sector() {
|
||||
}
|
||||
static uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; }
|
||||
|
||||
static inline size_t bytes_to_words(size_t bytes) { return (bytes + 3) / 4; }
|
||||
|
||||
template<class It> uint32_t calculate_crc(It first, It last, uint32_t type) {
|
||||
uint32_t crc = type;
|
||||
while (first != last) {
|
||||
crc ^= (*first++ * 2654435769UL) >> 1;
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
static bool save_to_flash(size_t offset, const uint32_t *data, size_t len) {
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
uint32_t j = offset + i;
|
||||
@@ -137,21 +128,19 @@ static constexpr size_t PREF_MAX_BUFFER_WORDS =
|
||||
ESP8266_FLASH_STORAGE_SIZE > RTC_NORMAL_REGION_WORDS ? ESP8266_FLASH_STORAGE_SIZE : RTC_NORMAL_REGION_WORDS;
|
||||
|
||||
bool ESP8266PreferenceBackend::save(const uint8_t *data, size_t len) {
|
||||
if (bytes_to_words(len) != this->length_words)
|
||||
if (rtc_pref_bytes_to_words(len) != this->length_words)
|
||||
return false;
|
||||
const size_t buffer_size = static_cast<size_t>(this->length_words) + 1;
|
||||
if (buffer_size > PREF_MAX_BUFFER_WORDS)
|
||||
return false;
|
||||
uint32_t buffer[PREF_MAX_BUFFER_WORDS];
|
||||
memset(buffer, 0, buffer_size * sizeof(uint32_t));
|
||||
memcpy(buffer, data, len);
|
||||
buffer[this->length_words] = calculate_crc(buffer, buffer + this->length_words, this->type);
|
||||
rtc_pref_encode(buffer, this->type, this->length_words, data, len);
|
||||
return this->in_flash ? save_to_flash(this->offset, buffer, buffer_size)
|
||||
: save_to_rtc(this->offset, buffer, buffer_size);
|
||||
}
|
||||
|
||||
bool ESP8266PreferenceBackend::load(uint8_t *data, size_t len) {
|
||||
if (bytes_to_words(len) != this->length_words)
|
||||
if (rtc_pref_bytes_to_words(len) != this->length_words)
|
||||
return false;
|
||||
const size_t buffer_size = static_cast<size_t>(this->length_words) + 1;
|
||||
if (buffer_size > PREF_MAX_BUFFER_WORDS)
|
||||
@@ -161,10 +150,7 @@ bool ESP8266PreferenceBackend::load(uint8_t *data, size_t len) {
|
||||
: load_from_rtc(this->offset, buffer, buffer_size);
|
||||
if (!ret)
|
||||
return false;
|
||||
if (buffer[this->length_words] != calculate_crc(buffer, buffer + this->length_words, this->type))
|
||||
return false;
|
||||
memcpy(data, buffer, len);
|
||||
return true;
|
||||
return rtc_pref_decode(buffer, this->type, this->length_words, data, len);
|
||||
}
|
||||
|
||||
void ESP8266Preferences::setup() {
|
||||
@@ -177,13 +163,13 @@ void ESP8266Preferences::setup() {
|
||||
}
|
||||
|
||||
ESPPreferenceObject ESP8266Preferences::make_preference(size_t length, uint32_t type, bool in_flash) {
|
||||
const uint32_t length_words = bytes_to_words(length);
|
||||
const uint32_t length_words = rtc_pref_bytes_to_words(length);
|
||||
if (length_words > MAX_PREFERENCE_WORDS) {
|
||||
ESP_LOGE(TAG, "Preference too large: %u words", static_cast<unsigned int>(length_words));
|
||||
return {};
|
||||
}
|
||||
|
||||
const uint32_t total_words = length_words + 1; // +1 for CRC
|
||||
const uint32_t total_words = length_words + 1; // +1 for checksum
|
||||
uint16_t offset;
|
||||
|
||||
if (in_flash) {
|
||||
|
||||
@@ -16,6 +16,7 @@ from esphome.components.mipi import (
|
||||
delay,
|
||||
)
|
||||
from esphome.components.spi import TYPE_QUAD
|
||||
from esphome.config_validation import UNDEFINED
|
||||
|
||||
DriverChip(
|
||||
"T-DISPLAY-S3-AMOLED",
|
||||
@@ -97,6 +98,9 @@ CO5300 = DriverChip(
|
||||
color_order=MODE_RGB,
|
||||
bus_mode=TYPE_QUAD,
|
||||
no_slpout=True,
|
||||
swap_xy=UNDEFINED,
|
||||
width=480,
|
||||
height=480,
|
||||
initsequence=(
|
||||
(SLPOUT,), # Requires early SLPOUT
|
||||
(PAGESEL, 0x00),
|
||||
|
||||
@@ -282,3 +282,13 @@ ST7789V.extend(
|
||||
invert_colors=True,
|
||||
data_rate="40MHz",
|
||||
)
|
||||
|
||||
CO5300.extend(
|
||||
"WAVESHARE-ESP32-S3-TOUCH-AMOLED-1.64",
|
||||
width=280,
|
||||
height=456,
|
||||
offset_width=20,
|
||||
cs_pin=9,
|
||||
reset_pin=21,
|
||||
enable_pin=1,
|
||||
)
|
||||
|
||||
@@ -200,6 +200,7 @@ DeviceFirmwareUpdate = nrf52_ns.class_("DeviceFirmwareUpdate", cg.Component)
|
||||
|
||||
CONF_DFU = "dfu"
|
||||
CONF_DCDC = "dcdc"
|
||||
CONF_LIBC_NANO = "libc_nano"
|
||||
CONF_REG0 = "reg0"
|
||||
CONF_UICR_ERASE = "uicr_erase"
|
||||
|
||||
@@ -248,6 +249,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_VERSION): cv.string_strict,
|
||||
cv.Optional(CONF_LIBC_NANO, default=True): cv.boolean,
|
||||
cv.Optional(CONF_ADVANCED, default={}): cv.Schema(
|
||||
{
|
||||
cv.Optional(
|
||||
@@ -273,6 +275,7 @@ def _validate_mcumgr(config):
|
||||
|
||||
|
||||
def _final_validate(config):
|
||||
|
||||
if CONF_DFU in config:
|
||||
_validate_mcumgr(config)
|
||||
if config[KEY_BOOTLOADER] == BOOTLOADER_ADAFRUIT:
|
||||
@@ -283,6 +286,13 @@ def _final_validate(config):
|
||||
conf = config[CONF_FRAMEWORK]
|
||||
advanced = conf[CONF_ADVANCED]
|
||||
|
||||
if conf[CONF_LIBC_NANO] and "logger" in CORE.loaded_integrations:
|
||||
_LOGGER.warning(
|
||||
"Logger is enabled with newlib-nano (libc_nano: true). Some format specifiers "
|
||||
"such as %%zu are not supported and will print incorrectly. "
|
||||
"Set 'libc_nano: false' under 'framework:' to use the full newlib."
|
||||
)
|
||||
|
||||
if advanced[CONF_ENABLE_OTA_ROLLBACK]:
|
||||
# "disabled: false" means safe mode *is* enabled.
|
||||
safe_mode_config = full_config.get(CONF_SAFE_MODE, {CONF_DISABLED: True})
|
||||
@@ -379,6 +389,9 @@ async def to_code(config: ConfigType) -> None:
|
||||
# Enable OTA rollback support
|
||||
if advanced[CONF_ENABLE_OTA_ROLLBACK]:
|
||||
cg.add_define("USE_OTA_ROLLBACK")
|
||||
zephyr_add_prj_conf("NEWLIB_LIBC", True)
|
||||
zephyr_add_prj_conf("NEWLIB_LIBC_FLOAT_PRINTF", True)
|
||||
zephyr_add_prj_conf("NEWLIB_LIBC_NANO", conf[CONF_LIBC_NANO])
|
||||
# c++ support
|
||||
if framework_ver < cv.Version(2, 9, 2):
|
||||
zephyr_add_prj_conf("CPLUSPLUS", True)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from esphome import preferences
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
@@ -10,10 +11,17 @@ preferences_ns = cg.esphome_ns.namespace("preferences")
|
||||
IntervalSyncer = preferences_ns.class_("IntervalSyncer", cg.Component)
|
||||
|
||||
CONF_FLASH_WRITE_INTERVAL = "flash_write_interval"
|
||||
CONF_RTC_STORAGE = "rtc_storage"
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(IntervalSyncer),
|
||||
cv.Optional(CONF_FLASH_WRITE_INTERVAL, default="60s"): cv.update_interval,
|
||||
# Compile the RTC-backed storage into the ESP32 preferences backend even
|
||||
# when no other option selects it, so components (including external
|
||||
# ones) requesting in_flash=false are honoured instead of falling back
|
||||
# to NVS. No default: absence means "no request" (see
|
||||
# preferences.validate_rtc_storage for the per-platform rules).
|
||||
cv.Optional(CONF_RTC_STORAGE): preferences.validate_rtc_storage,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
@@ -26,4 +34,6 @@ async def to_code(config):
|
||||
cg.add_define("USE_PREFERENCES_SYNC_EVERY_LOOP")
|
||||
else:
|
||||
cg.add(var.set_write_interval(write_interval))
|
||||
if config.get(CONF_RTC_STORAGE):
|
||||
preferences.request_rtc_storage()
|
||||
await cg.register_component(var, config)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from esphome import automation
|
||||
from esphome import automation, preferences
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -7,6 +7,7 @@ from esphome.const import (
|
||||
CONF_NUM_ATTEMPTS,
|
||||
CONF_REBOOT_TIMEOUT,
|
||||
CONF_SAFE_MODE,
|
||||
CONF_STORAGE,
|
||||
KEY_PAST_SAFE_MODE,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
@@ -42,6 +43,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
CONF_REBOOT_TIMEOUT, default="5min"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_ON_SAFE_MODE): automation.validate_automation({}),
|
||||
**preferences.storage_schema(),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
_remove_id_if_disabled,
|
||||
@@ -87,6 +89,7 @@ async def to_code(config):
|
||||
config[CONF_NUM_ATTEMPTS],
|
||||
config[CONF_REBOOT_TIMEOUT],
|
||||
config[CONF_BOOT_IS_GOOD_AFTER],
|
||||
preferences.is_in_flash(config[CONF_STORAGE]),
|
||||
)
|
||||
cg.add(RawExpression(f"if ({condition}) return"))
|
||||
|
||||
|
||||
@@ -162,13 +162,13 @@ bool SafeModeComponent::get_safe_mode_pending() {
|
||||
return this->read_rtc_() == SafeModeComponent::ENTER_SAFE_MODE_MAGIC;
|
||||
}
|
||||
|
||||
bool SafeModeComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time,
|
||||
uint32_t boot_is_good_after) {
|
||||
bool SafeModeComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time, uint32_t boot_is_good_after,
|
||||
bool in_flash) {
|
||||
this->safe_mode_start_time_ = millis();
|
||||
this->safe_mode_enable_time_ = enable_time;
|
||||
this->safe_mode_boot_is_good_after_ = boot_is_good_after;
|
||||
this->safe_mode_num_attempts_ = num_attempts;
|
||||
this->rtc_ = global_preferences->make_preference<uint32_t>(RTC_KEY, false);
|
||||
this->rtc_ = global_preferences->make_preference<uint32_t>(RTC_KEY, in_flash);
|
||||
|
||||
#if defined(USE_ESP32) && defined(USE_OTA_ROLLBACK)
|
||||
// Check partition state to detect if bootloader supports rollback
|
||||
|
||||
@@ -17,7 +17,7 @@ constexpr uint32_t RTC_KEY = 233825507UL;
|
||||
/// SafeModeComponent provides a safe way to recover from repeated boot failures
|
||||
class SafeModeComponent final : public Component {
|
||||
public:
|
||||
bool should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time, uint32_t boot_is_good_after);
|
||||
bool should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time, uint32_t boot_is_good_after, bool in_flash);
|
||||
|
||||
/// Set to true if the next startup will enter safe mode
|
||||
void set_safe_mode_pending(const bool &pending);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import logging
|
||||
import math
|
||||
from typing import Any
|
||||
|
||||
from esphome import automation
|
||||
from esphome import automation, preferences
|
||||
from esphome.automation import Condition
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.const import CONF_USE_PSRAM
|
||||
from esphome.components.const import CONF_ENABLED, CONF_USE_PSRAM
|
||||
from esphome.components.esp32 import (
|
||||
add_idf_sdkconfig_option,
|
||||
const,
|
||||
@@ -50,6 +51,7 @@ from esphome.const import (
|
||||
CONF_REBOOT_TIMEOUT,
|
||||
CONF_SSID,
|
||||
CONF_STATIC_IP,
|
||||
CONF_STORAGE,
|
||||
CONF_SUBNET,
|
||||
CONF_TIMEOUT,
|
||||
CONF_TTLS_PHASE_2,
|
||||
@@ -434,6 +436,23 @@ def _validate(config):
|
||||
|
||||
|
||||
CONF_PASSIVE_SCAN = "passive_scan"
|
||||
|
||||
FAST_CONNECT_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_ENABLED, default=True): cv.boolean,
|
||||
**preferences.storage_schema(),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _fast_connect_schema(value: Any) -> ConfigType:
|
||||
"""Accept the historic plain boolean (including boolean-like strings from
|
||||
substitutions) or a dict with enabled/storage keys."""
|
||||
if not isinstance(value, dict):
|
||||
value = {CONF_ENABLED: value}
|
||||
return FAST_CONNECT_SCHEMA(value)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
@@ -459,7 +478,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
rtl87xx="none",
|
||||
ln882x="light",
|
||||
): cv.enum(WIFI_POWER_SAVE_MODES, upper=True),
|
||||
cv.Optional(CONF_FAST_CONNECT, default=False): cv.boolean,
|
||||
cv.Optional(CONF_FAST_CONNECT, default=False): _fast_connect_schema,
|
||||
cv.Optional(CONF_USE_ADDRESS): cv.string_strict,
|
||||
cv.Optional(CONF_MIN_AUTH_MODE): cv.All(
|
||||
VALIDATE_WIFI_MIN_AUTH_MODE,
|
||||
@@ -619,8 +638,14 @@ async def to_code(config):
|
||||
cg.add(var.set_power_save_mode(config[CONF_POWER_SAVE_MODE]))
|
||||
if CONF_MIN_AUTH_MODE in config:
|
||||
cg.add(var.set_min_auth_mode(config[CONF_MIN_AUTH_MODE]))
|
||||
if config[CONF_FAST_CONNECT]:
|
||||
fast_connect = config[CONF_FAST_CONNECT]
|
||||
if fast_connect[CONF_ENABLED]:
|
||||
cg.add_define("USE_WIFI_FAST_CONNECT")
|
||||
# The storage default preserves this preference's historic location:
|
||||
# ESP8266 has always used RTC memory; every other platform effectively
|
||||
# used flash (the in_flash flag was previously ignored outside ESP8266).
|
||||
if preferences.is_in_flash(fast_connect[CONF_STORAGE]):
|
||||
cg.add_define("USE_WIFI_FAST_CONNECT_IN_FLASH")
|
||||
# passive_scan defaults to false in C++ - only set if true
|
||||
if config[CONF_PASSIVE_SCAN]:
|
||||
cg.add(var.set_passive_scan(True))
|
||||
|
||||
@@ -649,7 +649,13 @@ void WiFiComponent::start() {
|
||||
|
||||
this->pref_ = global_preferences->make_preference<wifi::SavedWifiSettings>(hash, true);
|
||||
#ifdef USE_WIFI_FAST_CONNECT
|
||||
this->fast_connect_pref_ = global_preferences->make_preference<wifi::SavedWifiFastConnectSettings>(hash + 1, false);
|
||||
#ifdef USE_WIFI_FAST_CONNECT_IN_FLASH
|
||||
const bool fast_connect_in_flash = true;
|
||||
#else
|
||||
const bool fast_connect_in_flash = false;
|
||||
#endif
|
||||
this->fast_connect_pref_ =
|
||||
global_preferences->make_preference<wifi::SavedWifiFastConnectSettings>(hash + 1, fast_connect_in_flash);
|
||||
#endif
|
||||
|
||||
SavedWifiSettings save{};
|
||||
|
||||
@@ -134,9 +134,7 @@ def zephyr_to_code(config: ConfigType) -> None:
|
||||
cg.add_define("USE_NATIVE_64BIT_TIME")
|
||||
cg.set_cpp_standard("gnu++20")
|
||||
# c++ support
|
||||
zephyr_add_prj_conf("NEWLIB_LIBC", True)
|
||||
zephyr_add_prj_conf("FPU", True)
|
||||
zephyr_add_prj_conf("NEWLIB_LIBC_FLOAT_PRINTF", True)
|
||||
zephyr_add_prj_conf("STD_CPP20", True)
|
||||
# random_bytes() uses sys_rand_get() which requires the entropy subsystem
|
||||
zephyr_add_prj_conf("ENTROPY_GENERATOR", True)
|
||||
|
||||
@@ -976,6 +976,7 @@ CONF_STEP_PIN = "step_pin"
|
||||
CONF_STILL_THRESHOLD = "still_threshold"
|
||||
CONF_STOP = "stop"
|
||||
CONF_STOP_ACTION = "stop_action"
|
||||
CONF_STORAGE = "storage"
|
||||
CONF_STORE_BASELINE = "store_baseline"
|
||||
CONF_SUBNET = "subnet"
|
||||
CONF_SUBSCRIBE_QOS = "subscribe_qos"
|
||||
|
||||
@@ -627,6 +627,9 @@ class EsphomeCore:
|
||||
self.platformio_libraries: dict[str, Library] = {}
|
||||
# A set of build flags to set in the platformio project
|
||||
self.build_flags: set[str] = set()
|
||||
# A set of build flags that apply to C++ compiles only (CXXFLAGS /
|
||||
# CXX_COMPILE_OPTIONS), for flags GCC rejects or warns about on C
|
||||
self.cxx_build_flags: set[str] = set()
|
||||
# A set of build unflags to set in the platformio project
|
||||
self.build_unflags: set[str] = set()
|
||||
# The C++ language standard for the build (e.g. "gnu++20"), set via cg.set_cpp_standard()
|
||||
@@ -686,6 +689,7 @@ class EsphomeCore:
|
||||
self.global_statements = []
|
||||
self.platformio_libraries = {}
|
||||
self.build_flags = set()
|
||||
self.cxx_build_flags = set()
|
||||
self.build_unflags = set()
|
||||
self.cpp_standard = None
|
||||
self.defines = set()
|
||||
@@ -993,6 +997,11 @@ class EsphomeCore:
|
||||
_LOGGER.debug("Adding build flag: %s", build_flag)
|
||||
return build_flag
|
||||
|
||||
def add_cxx_build_flag(self, build_flag: str) -> str:
|
||||
self.cxx_build_flags.add(build_flag)
|
||||
_LOGGER.debug("Adding C++ build flag: %s", build_flag)
|
||||
return build_flag
|
||||
|
||||
def add_build_unflag(self, build_unflag: str) -> None:
|
||||
if self.using_toolchain_esp_idf:
|
||||
# The native ESP-IDF build generator does not consume build_unflags
|
||||
|
||||
@@ -723,6 +723,15 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add_build_flag("-Wno-unused-variable")
|
||||
cg.add_build_flag("-Wno-unused-but-set-variable")
|
||||
cg.add_build_flag("-Wno-sign-compare")
|
||||
# C++20 deprecated ++/--, compound assignment, and chained assignment on
|
||||
# volatile lvalues; GCC warns via -Wvolatile, on by default at gnu++20.
|
||||
# C++23 (P2327R1) removed the deprecation for compound assignment, so the
|
||||
# warning flags patterns that are valid again under newer standards.
|
||||
# C++-only flag: GCC warns when it is passed on a C compile, hence
|
||||
# add_cxx_build_flag. Skipped for host builds, where the compiler may be
|
||||
# clang, which does not know this GCC option.
|
||||
if not CORE.is_host:
|
||||
cg.add_cxx_build_flag("-Wno-volatile")
|
||||
if config[CONF_DEBUG_SCHEDULER]:
|
||||
cg.add_define("ESPHOME_DEBUG_SCHEDULER")
|
||||
|
||||
|
||||
@@ -240,6 +240,7 @@
|
||||
#define USE_OTA_ROLLBACK
|
||||
#define USE_OTA_SIGNED_VERIFICATION
|
||||
#define USE_ESP32_MIN_CHIP_REVISION_SET
|
||||
#define USE_ESP32_RTC_PREFERENCES
|
||||
#define USE_ESP32_SRAM1_AS_IRAM
|
||||
|
||||
#define USE_BLUETOOTH_PROXY
|
||||
@@ -300,6 +301,7 @@
|
||||
#define USE_CAPTIVE_PORTAL_GZIP
|
||||
#define USE_WIFI_11KV_SUPPORT
|
||||
#define USE_WIFI_FAST_CONNECT
|
||||
#define USE_WIFI_FAST_CONNECT_IN_FLASH
|
||||
#define USE_WIFI_PHY_MODE
|
||||
#define USE_WIFI_IP_STATE_LISTENERS
|
||||
#define USE_WIFI_SCAN_RESULTS_LISTENERS
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
namespace esphome {
|
||||
|
||||
// Shared storage format for word-addressable preference backends.
|
||||
//
|
||||
// Several platforms persist preferences as a buffer of 32-bit words followed by a
|
||||
// single checksum word, seeded with the preference's `type` (its hashed key). This
|
||||
// format is used for RTC user memory (ESP8266, ESP32) and for the ESP8266
|
||||
// flash-emulation buffer. The helpers here are platform independent; each backend
|
||||
// supplies its own word read/write primitives and offset allocation.
|
||||
|
||||
/// Round a byte count up to whole 32-bit words.
|
||||
inline size_t rtc_pref_bytes_to_words(size_t bytes) { return (bytes + 3) / 4; }
|
||||
|
||||
/// Compute the integrity checksum over [first, last), seeded with `type`.
|
||||
/// Iterates over 32-bit words; the result is stored as the trailing word of a record.
|
||||
/// (Not a true CRC -- it XORs each word after a Fibonacci-hash multiply -- but the
|
||||
/// algorithm is kept as-is for compatibility with records written by old firmware.)
|
||||
template<class It> uint32_t rtc_pref_calculate_checksum(It first, It last, uint32_t type) {
|
||||
uint32_t checksum = type;
|
||||
while (first != last) {
|
||||
// UINT32_C keeps the multiply wrapping at 32 bits regardless of the width of
|
||||
// unsigned long, so 64-bit host builds compute the same value as the devices.
|
||||
checksum ^= (*first++ * UINT32_C(2654435769)) >> 1;
|
||||
}
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// Encode `len` data bytes into `buffer` (length_words data words + 1 trailing checksum word).
|
||||
/// `buffer` must have capacity for at least `length_words + 1` words. Trailing padding in
|
||||
/// the final data word is zeroed so the checksum is deterministic.
|
||||
inline void rtc_pref_encode(uint32_t *buffer, uint32_t type, uint8_t length_words, const uint8_t *data, size_t len) {
|
||||
memset(buffer, 0, (static_cast<size_t>(length_words) + 1) * sizeof(uint32_t));
|
||||
memcpy(buffer, data, len);
|
||||
buffer[length_words] = rtc_pref_calculate_checksum(buffer, buffer + length_words, type);
|
||||
}
|
||||
|
||||
/// Verify the checksum of a record held in `buffer` (length_words data words + 1 checksum
|
||||
/// word) and, on success, copy `len` bytes out to `data`. Returns false on checksum mismatch
|
||||
/// (e.g. the record was never written or RTC memory holds power-on garbage).
|
||||
inline bool rtc_pref_decode(const uint32_t *buffer, uint32_t type, uint8_t length_words, uint8_t *data, size_t len) {
|
||||
if (buffer[length_words] != rtc_pref_calculate_checksum(buffer, buffer + length_words, type)) {
|
||||
return false;
|
||||
}
|
||||
memcpy(data, buffer, len);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
@@ -724,6 +724,15 @@ def add_build_flag(build_flag: str):
|
||||
CORE.add_build_flag(build_flag)
|
||||
|
||||
|
||||
def add_cxx_build_flag(build_flag: str) -> None:
|
||||
"""Add a global build flag that applies to C++ compiles only.
|
||||
|
||||
Use for flags GCC rejects or warns about when passed on C compiles
|
||||
(e.g. ``-Wno-volatile``).
|
||||
"""
|
||||
CORE.add_cxx_build_flag(build_flag)
|
||||
|
||||
|
||||
def add_build_unflag(build_unflag: str) -> None:
|
||||
"""Add a global build unflag to the compiler flags."""
|
||||
CORE.add_build_unflag(build_unflag)
|
||||
|
||||
@@ -37,6 +37,13 @@ def get_project_compile_flags() -> list[str]:
|
||||
]
|
||||
|
||||
|
||||
def get_project_cxx_compile_flags() -> list[str]:
|
||||
"""Return the sorted flags that apply to C++ compiles only."""
|
||||
from esphome.core import CORE # local import to avoid circular dependency
|
||||
|
||||
return sorted(CORE.cxx_build_flags)
|
||||
|
||||
|
||||
def str_to_lst_of_str(a: str | list[str]) -> list[str]:
|
||||
"""
|
||||
Convert a string to a list of string
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
"""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
|
||||
+1
-1
@@ -20,7 +20,7 @@ resvg-py==0.3.3
|
||||
freetype-py==2.5.1
|
||||
jinja2==3.1.6
|
||||
bleak==2.1.1
|
||||
smpclient==6.0.0
|
||||
smpclient==7.2.0
|
||||
requests==2.34.2
|
||||
py7zr==1.1.3
|
||||
platformdirs==4.10.0 # native esp-idf toolchain global cache dir
|
||||
|
||||
+1
-1
@@ -555,7 +555,7 @@ def lint_constants_usage():
|
||||
# Maximum allowed CONF_ constants in esphome/const.py.
|
||||
# This file is frozen — new constants go in esphome/components/const/__init__.py.
|
||||
# Decrease this number when constants are moved out of const.py.
|
||||
CONST_PY_MAX_CONF = 1014
|
||||
CONST_PY_MAX_CONF = 1015
|
||||
|
||||
|
||||
@lint_content_check(include=["esphome/const.py"])
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"target_module": "esphome.__main__",
|
||||
"margin_pct": 20,
|
||||
"cumulative_us": 91000
|
||||
"cumulative_us": 95000
|
||||
}
|
||||
|
||||
@@ -4,5 +4,6 @@ substitutions:
|
||||
reset_pin: "21"
|
||||
|
||||
packages:
|
||||
- !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||
- !include common.yaml
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -2,3 +2,5 @@ nrf52:
|
||||
dfu: true
|
||||
reg0:
|
||||
voltage: 1.8V
|
||||
framework:
|
||||
libc_nano: false
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# Exercises the opt-in that compiles the RTC-backed preference storage into
|
||||
# the ESP32 backend without any other option selecting it.
|
||||
preferences:
|
||||
rtc_storage: true
|
||||
@@ -0,0 +1,4 @@
|
||||
# Exercises the ESP32 RTC-backed preferences path (storage: rtc) for safe_mode.
|
||||
safe_mode:
|
||||
num_attempts: 3
|
||||
storage: rtc
|
||||
@@ -0,0 +1,7 @@
|
||||
# Exercises the dict form of fast_connect with RTC-backed preference storage.
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
fast_connect:
|
||||
enabled: true
|
||||
storage: rtc
|
||||
@@ -0,0 +1,7 @@
|
||||
# Exercises the dict form of fast_connect overriding the ESP8266 default (rtc)
|
||||
# back to flash storage.
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
fast_connect:
|
||||
storage: flash
|
||||
@@ -0,0 +1,9 @@
|
||||
# fast_connect passed through a substitution arrives as a string ("false"),
|
||||
# which must be accepted like the historic plain boolean form.
|
||||
substitutions:
|
||||
fast_connect_value: "false"
|
||||
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
fast_connect: ${fast_connect_value}
|
||||
@@ -243,6 +243,7 @@ def test_get_project_cmakelists_no_cpp_standard(tmp_path: Path) -> None:
|
||||
patch("esphome.build_gen.espidf.get_esp32_variant", return_value="ESP32"),
|
||||
patch.object(CORE, "name", "test"),
|
||||
patch.object(CORE, "cpp_standard", None),
|
||||
patch.object(CORE, "cxx_build_flags", set()),
|
||||
):
|
||||
from esphome.build_gen.espidf import get_project_cmakelists
|
||||
|
||||
@@ -251,6 +252,28 @@ def test_get_project_cmakelists_no_cpp_standard(tmp_path: Path) -> None:
|
||||
assert "CXX_COMPILE_OPTIONS" not in content
|
||||
|
||||
|
||||
def test_get_project_cmakelists_cxx_build_flags(tmp_path: Path) -> None:
|
||||
"""Flags registered via cg.add_cxx_build_flag() are appended to
|
||||
CXX_COMPILE_OPTIONS (C++-only, GCC warns if they reach C compiles)
|
||||
between include(project.cmake) and project()."""
|
||||
with (
|
||||
patch("esphome.build_gen.espidf.get_esp32_variant", return_value="ESP32"),
|
||||
patch.object(CORE, "name", "test"),
|
||||
patch.object(CORE, "cpp_standard", None),
|
||||
patch.object(CORE, "cxx_build_flags", {"-Wno-volatile"}),
|
||||
):
|
||||
from esphome.build_gen.espidf import get_project_cmakelists
|
||||
|
||||
content = get_project_cmakelists(minimal=True)
|
||||
|
||||
flag_line = 'idf_build_set_property(CXX_COMPILE_OPTIONS "-Wno-volatile" APPEND)'
|
||||
assert flag_line in content
|
||||
include_pos = content.index("tools/cmake/project.cmake")
|
||||
flag_pos = content.index(flag_line)
|
||||
project_pos = content.index("project(test)")
|
||||
assert include_pos < flag_pos < project_pos
|
||||
|
||||
|
||||
def test_get_component_cmakelists_no_compile_features() -> None:
|
||||
"""The C++ standard is pinned project-wide via CXX_COMPILE_OPTIONS in the
|
||||
top-level CMakeLists; the src component must not set its own."""
|
||||
|
||||
@@ -200,3 +200,32 @@ def test_get_ini_content_no_cpp_standard(
|
||||
content = platformio.get_ini_content()
|
||||
|
||||
assert "-std=" not in content
|
||||
|
||||
|
||||
def test_write_cxx_flags_script_emits_registered_flags(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""Flags registered via cg.add_cxx_build_flag() are emitted as CXXFLAGS,
|
||||
sorted, so they apply to C++ compiles only."""
|
||||
CORE.build_path = str(tmp_path)
|
||||
monkeypatch.setattr(CORE, "cxx_build_flags", {"-Wno-volatile", "-Wno-deprecated"})
|
||||
|
||||
platformio.write_cxx_flags_script()
|
||||
|
||||
content = (tmp_path / platformio.CXX_FLAGS_FILE_NAME).read_text()
|
||||
assert (
|
||||
'env.Append(CXXFLAGS=["-Wno-deprecated"])\n'
|
||||
'env.Append(CXXFLAGS=["-Wno-volatile"])\n'
|
||||
) in content
|
||||
|
||||
|
||||
def test_write_cxx_flags_script_no_flags(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
CORE.build_path = str(tmp_path)
|
||||
monkeypatch.setattr(CORE, "cxx_build_flags", set())
|
||||
|
||||
platformio.write_cxx_flags_script()
|
||||
|
||||
content = (tmp_path / platformio.CXX_FLAGS_FILE_NAME).read_text()
|
||||
assert "CXXFLAGS" not in content
|
||||
|
||||
@@ -26,6 +26,7 @@ from esphome.framework_helpers import (
|
||||
create_venv,
|
||||
download_from_mirrors,
|
||||
get_project_compile_flags,
|
||||
get_project_cxx_compile_flags,
|
||||
get_project_link_flags,
|
||||
get_python_env_executable_path,
|
||||
get_system_python_path,
|
||||
@@ -1048,3 +1049,25 @@ class TestGetProjectLinkFlags:
|
||||
):
|
||||
result = get_project_link_flags()
|
||||
assert result == sorted(result)
|
||||
|
||||
|
||||
def _make_core_cxx(flags: set[str]) -> MagicMock:
|
||||
core = MagicMock()
|
||||
core.cxx_build_flags = flags
|
||||
return core
|
||||
|
||||
|
||||
class TestGetProjectCxxCompileFlags:
|
||||
def test_returns_sorted_flags(self) -> None:
|
||||
with patch(
|
||||
"esphome.core.CORE",
|
||||
_make_core_cxx({"-Wno-volatile", "-Wno-deprecated"}),
|
||||
):
|
||||
assert get_project_cxx_compile_flags() == [
|
||||
"-Wno-deprecated",
|
||||
"-Wno-volatile",
|
||||
]
|
||||
|
||||
def test_empty_flags(self) -> None:
|
||||
with patch("esphome.core.CORE", _make_core_cxx(set())):
|
||||
assert get_project_cxx_compile_flags() == []
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
"""Tests for esphome.preferences storage backend selection."""
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome import preferences
|
||||
from esphome.components.esp32 import KEY_ESP32
|
||||
from esphome.components.esp32.const import (
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32C2,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32C61,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_STORAGE,
|
||||
KEY_CORE,
|
||||
KEY_TARGET_PLATFORM,
|
||||
KEY_VARIANT,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_RP2040,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
|
||||
|
||||
def _set_platform(platform: str) -> None:
|
||||
CORE.data[KEY_CORE] = {KEY_TARGET_PLATFORM: platform}
|
||||
|
||||
|
||||
def _set_esp32(variant: str) -> None:
|
||||
_set_platform(PLATFORM_ESP32)
|
||||
CORE.data[KEY_ESP32] = {KEY_VARIANT: variant}
|
||||
|
||||
|
||||
def _validate(value: dict):
|
||||
return cv.Schema(preferences.storage_schema())(value)
|
||||
|
||||
|
||||
def _define_names() -> set[str]:
|
||||
return {define.name for define in CORE.defines}
|
||||
|
||||
|
||||
def test_is_in_flash() -> None:
|
||||
_set_platform(PLATFORM_ESP8266)
|
||||
assert preferences.is_in_flash(preferences.STORAGE_FLASH) is True
|
||||
assert preferences.is_in_flash(preferences.STORAGE_RTC) is False
|
||||
# The RTC storage define is ESP32-specific.
|
||||
assert "USE_ESP32_RTC_PREFERENCES" not in _define_names()
|
||||
|
||||
|
||||
def test_is_in_flash_esp32_rtc_emits_define() -> None:
|
||||
_set_esp32(VARIANT_ESP32)
|
||||
assert preferences.is_in_flash(preferences.STORAGE_FLASH) is True
|
||||
assert "USE_ESP32_RTC_PREFERENCES" not in _define_names()
|
||||
assert preferences.is_in_flash(preferences.STORAGE_RTC) is False
|
||||
assert "USE_ESP32_RTC_PREFERENCES" in _define_names()
|
||||
|
||||
|
||||
def test_request_rtc_storage_esp32_only() -> None:
|
||||
_set_platform(PLATFORM_ESP8266)
|
||||
preferences.request_rtc_storage()
|
||||
# ESP8266 always has its RTC backend; no define is needed or emitted.
|
||||
assert "USE_ESP32_RTC_PREFERENCES" not in _define_names()
|
||||
|
||||
|
||||
def test_request_rtc_storage_esp32_emits_define() -> None:
|
||||
_set_esp32(VARIANT_ESP32)
|
||||
preferences.request_rtc_storage()
|
||||
assert "USE_ESP32_RTC_PREFERENCES" in _define_names()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("variant", [VARIANT_ESP32, VARIANT_ESP32C3])
|
||||
def test_validate_rtc_storage_accepted(variant: str) -> None:
|
||||
_set_esp32(variant)
|
||||
assert preferences.validate_rtc_storage(True) is True
|
||||
assert preferences.validate_rtc_storage(False) is False
|
||||
|
||||
|
||||
def test_validate_rtc_storage_esp8266() -> None:
|
||||
_set_platform(PLATFORM_ESP8266)
|
||||
# Tolerated no-op: the ESP8266 backend always has RTC storage.
|
||||
assert preferences.validate_rtc_storage(True) is True
|
||||
# But it cannot be disabled, so an explicit false is an error.
|
||||
with pytest.raises(cv.Invalid, match="always enabled on ESP8266"):
|
||||
preferences.validate_rtc_storage(False)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("variant", [VARIANT_ESP32C2, VARIANT_ESP32C61])
|
||||
def test_validate_rtc_storage_rejected_without_rtc_memory(variant: str) -> None:
|
||||
_set_esp32(variant)
|
||||
with pytest.raises(cv.Invalid, match="not supported on this platform"):
|
||||
preferences.validate_rtc_storage(True)
|
||||
# Disabling it is always fine.
|
||||
assert preferences.validate_rtc_storage(False) is False
|
||||
|
||||
|
||||
def test_validate_rtc_storage_rejected_on_unsupported_platform() -> None:
|
||||
_set_platform(PLATFORM_RP2040)
|
||||
with pytest.raises(cv.Invalid, match="not supported on this platform"):
|
||||
preferences.validate_rtc_storage(True)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("platform", "expected"),
|
||||
[
|
||||
# Defaults preserve each platform's historic behavior.
|
||||
(PLATFORM_ESP8266, preferences.STORAGE_RTC),
|
||||
(PLATFORM_RP2040, preferences.STORAGE_FLASH),
|
||||
],
|
||||
)
|
||||
def test_default_storage_per_platform(platform: str, expected: str) -> None:
|
||||
_set_platform(platform)
|
||||
assert _validate({})[CONF_STORAGE] == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("variant", [VARIANT_ESP32, VARIANT_ESP32C2])
|
||||
def test_default_storage_esp32_is_flash(variant: str) -> None:
|
||||
# ESP32 defaults to flash on every variant, including those without RTC memory.
|
||||
_set_esp32(variant)
|
||||
assert _validate({})[CONF_STORAGE] == preferences.STORAGE_FLASH
|
||||
|
||||
|
||||
def test_rtc_allowed_on_esp8266() -> None:
|
||||
_set_platform(PLATFORM_ESP8266)
|
||||
assert _validate({CONF_STORAGE: "rtc"})[CONF_STORAGE] == preferences.STORAGE_RTC
|
||||
|
||||
|
||||
@pytest.mark.parametrize("variant", [VARIANT_ESP32, VARIANT_ESP32C3])
|
||||
def test_rtc_allowed_on_esp32_with_rtc_memory(variant: str) -> None:
|
||||
_set_esp32(variant)
|
||||
assert _validate({CONF_STORAGE: "rtc"})[CONF_STORAGE] == preferences.STORAGE_RTC
|
||||
|
||||
|
||||
@pytest.mark.parametrize("variant", [VARIANT_ESP32C2, VARIANT_ESP32C61])
|
||||
def test_rtc_rejected_on_esp32_without_rtc_memory(variant: str) -> None:
|
||||
_set_esp32(variant)
|
||||
with pytest.raises(cv.Invalid, match="not supported on this platform"):
|
||||
_validate({CONF_STORAGE: "rtc"})
|
||||
|
||||
|
||||
def test_rtc_rejected_on_unsupported_platform() -> None:
|
||||
_set_platform(PLATFORM_RP2040)
|
||||
with pytest.raises(cv.Invalid, match="not supported on this platform"):
|
||||
_validate({CONF_STORAGE: "rtc"})
|
||||
|
||||
|
||||
def test_flash_allowed_on_unsupported_platform() -> None:
|
||||
_set_platform(PLATFORM_RP2040)
|
||||
assert _validate({CONF_STORAGE: "flash"})[CONF_STORAGE] == preferences.STORAGE_FLASH
|
||||
Reference in New Issue
Block a user