mirror of
https://github.com/esphome/esphome.git
synced 2026-10-22 05:46:12 +00:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a92428bde | ||
|
|
372f13ccce | ||
|
|
e2bae54644 |
+3
-3
@@ -829,9 +829,9 @@ def write_cpp_file() -> int:
|
||||
|
||||
def compile_program(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
if CORE.skip_bootloader and not (CORE.is_esp32 and CORE.using_toolchain_esp_idf):
|
||||
# Debug only: an orchestrator cannot see YAML toolchain overrides,
|
||||
# so this is its expected no-op, and a full build is safe.
|
||||
_LOGGER.debug(
|
||||
# Info, not a warning: an orchestrator cannot see YAML toolchain
|
||||
# overrides, this is its expected no-op, and a full build is safe.
|
||||
_LOGGER.info(
|
||||
"--skip-bootloader ignored: only supported on ESP32 with the "
|
||||
"esp-idf toolchain"
|
||||
)
|
||||
|
||||
@@ -142,7 +142,6 @@ CONF_STATE_SUBSCRIPTION_ONLY = "state_subscription_only"
|
||||
DEFAULT_PORT = 6053
|
||||
DEFAULT_REBOOT_TIMEOUT = "15min"
|
||||
DEFAULT_BATCH_DELAY = "100ms"
|
||||
DEFAULT_LISTEN_BACKLOG = 4
|
||||
|
||||
|
||||
def _register_provisioning_source(config: ConfigType) -> ConfigType:
|
||||
@@ -478,10 +477,8 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add(var.set_reboot_timeout(reboot_timeout))
|
||||
if (batch_delay := config[CONF_BATCH_DELAY]) != cv.time_period(DEFAULT_BATCH_DELAY):
|
||||
cg.add(var.set_batch_delay(batch_delay))
|
||||
if (
|
||||
listen_backlog := config.get(CONF_LISTEN_BACKLOG)
|
||||
) is not None and listen_backlog != DEFAULT_LISTEN_BACKLOG:
|
||||
cg.add(var.set_listen_backlog(listen_backlog))
|
||||
if CONF_LISTEN_BACKLOG in config:
|
||||
cg.add(var.set_listen_backlog(config[CONF_LISTEN_BACKLOG]))
|
||||
cg.add_define("MAX_API_CONNECTIONS", config[CONF_MAX_CONNECTIONS])
|
||||
cg.add_define("API_MAX_SEND_QUEUE", config[CONF_MAX_SEND_QUEUE])
|
||||
|
||||
|
||||
@@ -357,7 +357,9 @@ class APIServer final : public Component
|
||||
// Group smaller types together
|
||||
uint16_t port_{6053}; // Keep in sync with DEFAULT_PORT in __init__.py
|
||||
uint16_t batch_delay_{100}; // Keep in sync with DEFAULT_BATCH_DELAY in __init__.py
|
||||
uint8_t listen_backlog_{4}; // Keep in sync with DEFAULT_LISTEN_BACKLOG in __init__.py
|
||||
// Connection limits - these defaults will be overridden by config values
|
||||
// from cv.SplitDefault in __init__.py which sets platform-specific defaults.
|
||||
uint8_t listen_backlog_{4};
|
||||
bool shutting_down_ = false;
|
||||
uint8_t api_connection_count_{0};
|
||||
#if defined(USE_PROVISIONING) && defined(USE_API_NOISE)
|
||||
|
||||
@@ -17,7 +17,8 @@ extern "C" __attribute__((weak)) void initArduino() {}
|
||||
namespace esphome {
|
||||
|
||||
// HAL functions live in hal.cpp. This file keeps only the loop task setup.
|
||||
static StaticTask_t loop_task_tcb; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
TaskHandle_t loop_task_handle = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
static StaticTask_t loop_task_tcb; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
static StackType_t
|
||||
loop_task_stack[ESPHOME_LOOP_TASK_STACK_SIZE]; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
@@ -39,10 +40,11 @@ extern "C" void app_main() {
|
||||
initArduino();
|
||||
esp32::setup_preferences();
|
||||
#if CONFIG_FREERTOS_UNICORE
|
||||
xTaskCreateStatic(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, loop_task_stack, &loop_task_tcb);
|
||||
loop_task_handle = xTaskCreateStatic(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, loop_task_stack,
|
||||
&loop_task_tcb);
|
||||
#else
|
||||
xTaskCreateStaticPinnedToCore(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, loop_task_stack,
|
||||
&loop_task_tcb, 1);
|
||||
loop_task_handle = xTaskCreateStaticPinnedToCore(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1,
|
||||
loop_task_stack, &loop_task_tcb, 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -235,14 +235,14 @@ class ESP32BLE final : public Component {
|
||||
#ifdef USE_ESP32_BLE_ADVERTISING
|
||||
BLEAdvertising *advertising_{}; // 4 bytes (pointer)
|
||||
#endif
|
||||
const char *name_{nullptr}; // 4 bytes (pointer to string literal in flash)
|
||||
uint32_t advertising_cycle_time_{}; // 4 bytes
|
||||
const char *name_{nullptr}; // 4 bytes (pointer to string literal in flash)
|
||||
esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; // 4 bytes (enum)
|
||||
uint32_t advertising_cycle_time_{}; // 4 bytes
|
||||
|
||||
// 2-byte aligned members
|
||||
uint16_t appearance_{0}; // 2 bytes
|
||||
|
||||
// 1-byte aligned members (grouped together to minimize padding)
|
||||
esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; // 1 byte (uint8_t typedef)
|
||||
BLEComponentState state_{BLE_COMPONENT_STATE_OFF}; // 1 byte (uint8_t enum)
|
||||
bool enable_on_boot_{}; // 1 byte
|
||||
#ifdef USE_ESP32_BLE_ADVERTISING
|
||||
|
||||
@@ -72,6 +72,9 @@ class ImprovBLEComponent final : public Component, public improv_base::ImprovBas
|
||||
improv::Error get_improv_error_state() const { return this->error_state_; }
|
||||
|
||||
protected:
|
||||
bool should_start_{false};
|
||||
bool setup_complete_{false};
|
||||
|
||||
uint32_t identify_start_{0};
|
||||
uint32_t identify_duration_;
|
||||
uint32_t authorized_start_{0};
|
||||
@@ -81,14 +84,6 @@ class ImprovBLEComponent final : public Component, public improv_base::ImprovBas
|
||||
|
||||
std::vector<uint8_t> incoming_data_;
|
||||
wifi::WiFiAP connecting_sta_;
|
||||
// Single byte fields here fill the alignment gap between connecting_sta_ and the next pointer
|
||||
improv::State state_{improv::STATE_STOPPED};
|
||||
improv::Error error_state_{improv::ERROR_NONE};
|
||||
bool should_start_{false};
|
||||
bool setup_complete_{false};
|
||||
bool status_indicator_state_{false};
|
||||
bool advertising_device_name_{false};
|
||||
bool advertising_requested_{false};
|
||||
|
||||
BLEService *service_{nullptr};
|
||||
BLECharacteristic *status_{nullptr};
|
||||
@@ -104,11 +99,16 @@ class ImprovBLEComponent final : public Component, public improv_base::ImprovBas
|
||||
output::BinaryOutput *status_indicator_{nullptr};
|
||||
#endif
|
||||
|
||||
improv::State state_{improv::STATE_STOPPED};
|
||||
improv::Error error_state_{improv::ERROR_NONE};
|
||||
#ifdef USE_IMPROV_BLE_STATE_CALLBACK
|
||||
CallbackManager<void(improv::State, improv::Error)> state_callback_{};
|
||||
#endif
|
||||
|
||||
bool status_indicator_state_{false};
|
||||
uint32_t last_name_adv_time_{0};
|
||||
bool advertising_device_name_{false};
|
||||
bool advertising_requested_{false};
|
||||
void set_status_indicator_state_(bool state);
|
||||
void update_advertising_type_();
|
||||
void request_advertising_();
|
||||
|
||||
@@ -32,6 +32,7 @@ from esphome.storage_json import StorageJSON
|
||||
from . import gpio # noqa: F401
|
||||
from .const import (
|
||||
COMPONENT_BK72XX,
|
||||
COMPONENT_LN882X,
|
||||
CONF_GPIO_RECOVER,
|
||||
CONF_LOGLEVEL,
|
||||
CONF_SDK_SILENT,
|
||||
@@ -490,6 +491,10 @@ async def component_to_code(config):
|
||||
# Not enabled on RTL87xx/LN882x — costs more heap than it saves there.
|
||||
cg.add_build_flag("-DconfigSUPPORT_STATIC_ALLOCATION=1")
|
||||
|
||||
# LN882x: a zero-size allocation must not trip the SDK's assert (see ln882x_zero_malloc.c).
|
||||
if config[CONF_COMPONENT_ID] == COMPONENT_LN882X:
|
||||
cg.add_build_flag("-Wl,--wrap=pvPortMalloc")
|
||||
|
||||
# RTL8710B needs FreeRTOS 8.2.3+ for xTaskNotifyGive/ulTaskNotifyTake
|
||||
# required by AsyncTCP 3.4.3+ (https://github.com/esphome/esphome/issues/10220)
|
||||
# RTL8720C (ambz2) requires FreeRTOS 10.x so this only applies to RTL8710B
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// LN882x: a zero-size pvPortMalloc trips the SDK's malloc-failed LN_ASSERT; ask for 1 byte instead.
|
||||
|
||||
#ifdef USE_LN882X
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
void *__real_pvPortMalloc(size_t size); // NOLINT(readability-identifier-naming)
|
||||
|
||||
void *__wrap_pvPortMalloc(size_t size) { // NOLINT(readability-identifier-naming)
|
||||
return __real_pvPortMalloc(size == 0 ? 1 : size);
|
||||
}
|
||||
|
||||
#endif // USE_LN882X
|
||||
@@ -27,10 +27,6 @@ CONF_HAS_PULLDOWNS = "has_pulldowns"
|
||||
|
||||
|
||||
def check_keys(obj: ConfigType) -> ConfigType:
|
||||
for ch in obj.get(CONF_KEYS, ""):
|
||||
if not ch.isascii():
|
||||
# Each key is reported as one byte, so only ASCII characters can be key codes
|
||||
raise cv.Invalid(f"Key code {ch!r} is not an ASCII character")
|
||||
if CONF_KEYS in obj and len(obj[CONF_KEYS]) != len(obj[CONF_ROWS]) * len(
|
||||
obj[CONF_COLUMNS]
|
||||
):
|
||||
@@ -78,11 +74,8 @@ async def to_code(config: ConfigType) -> None:
|
||||
pin = await cg.gpio_pin_expression(conf[CONF_PIN])
|
||||
col_pins.append(pin)
|
||||
cg.add(var.set_columns(col_pins))
|
||||
if (keys := config.get(CONF_KEYS)) is not None:
|
||||
table = cg.shared_progmem_array(
|
||||
"matrix_keypad_keys", cg.uint8, list(keys.encode())
|
||||
)
|
||||
cg.add(var.set_keys(table))
|
||||
if CONF_KEYS in config:
|
||||
cg.add(var.set_keys(config[CONF_KEYS]))
|
||||
cg.add(var.set_debounce_time(config[CONF_DEBOUNCE_TIME]))
|
||||
if CONF_HAS_DIODES in config:
|
||||
cg.add(var.set_has_diodes(config[CONF_HAS_DIODES]))
|
||||
|
||||
@@ -58,8 +58,8 @@ void MatrixKeypad::loop() {
|
||||
ESP_LOGD(TAG, "key @ row %d, col %d released", row, col);
|
||||
for (auto &listener : this->listeners_)
|
||||
listener->button_released(row, col);
|
||||
if (this->keys_ != nullptr) {
|
||||
uint8_t keycode = progmem_read_byte(&this->keys_[this->pressed_key_]);
|
||||
if (this->pressed_key_ < (int) this->keys_.size()) {
|
||||
uint8_t keycode = this->keys_[this->pressed_key_];
|
||||
ESP_LOGD(TAG, "key '%c' released", keycode);
|
||||
for (auto &listener : this->listeners_)
|
||||
listener->key_released(keycode);
|
||||
@@ -81,8 +81,8 @@ void MatrixKeypad::loop() {
|
||||
ESP_LOGD(TAG, "key @ row %d, col %d pressed", row, col);
|
||||
for (auto &listener : this->listeners_)
|
||||
listener->button_pressed(row, col);
|
||||
if (this->keys_ != nullptr) {
|
||||
uint8_t keycode = progmem_read_byte(&this->keys_[key]);
|
||||
if (key < (int) this->keys_.size()) {
|
||||
uint8_t keycode = this->keys_[key];
|
||||
ESP_LOGD(TAG, "key '%c' pressed", keycode);
|
||||
for (auto &trigger : this->key_triggers_)
|
||||
trigger->trigger(keycode);
|
||||
|
||||
@@ -27,8 +27,7 @@ class MatrixKeypad final : public key_provider::KeyProvider, public Component {
|
||||
void dump_config() override;
|
||||
void set_columns(std::vector<GPIOPin *> pins) { columns_ = std::move(pins); };
|
||||
void set_rows(std::vector<GPIOPin *> pins) { rows_ = std::move(pins); };
|
||||
/// `keys` is a codegen PROGMEM table with one key code per button.
|
||||
void set_keys(const uint8_t *keys) { this->keys_ = keys; }
|
||||
void set_keys(std::string keys) { keys_ = std::move(keys); };
|
||||
void set_debounce_time(uint32_t debounce_time) { debounce_time_ = debounce_time; };
|
||||
void set_has_diodes(bool has_diodes) { has_diodes_ = has_diodes; };
|
||||
void set_has_pulldowns(bool has_pulldowns) { has_pulldowns_ = has_pulldowns; };
|
||||
@@ -39,7 +38,7 @@ class MatrixKeypad final : public key_provider::KeyProvider, public Component {
|
||||
protected:
|
||||
std::vector<GPIOPin *> rows_;
|
||||
std::vector<GPIOPin *> columns_;
|
||||
const uint8_t *keys_{nullptr};
|
||||
std::string keys_;
|
||||
uint32_t debounce_time_ = 0;
|
||||
bool has_diodes_{false};
|
||||
bool has_pulldowns_{false};
|
||||
|
||||
@@ -88,8 +88,8 @@ class RemoteReceiverComponent final : public remote_base::RemoteReceiverBase,
|
||||
rmt_channel_handle_t channel_{NULL};
|
||||
uint32_t filter_symbols_{0};
|
||||
uint32_t receive_symbols_{0};
|
||||
uint32_t carrier_frequency_{0};
|
||||
bool with_dma_{false};
|
||||
uint32_t carrier_frequency_{0};
|
||||
uint8_t carrier_duty_percent_{100};
|
||||
#endif
|
||||
|
||||
|
||||
@@ -254,8 +254,7 @@ template<class... As, typename... Ts> class ScriptExecuteAction<Script<As...>, T
|
||||
}
|
||||
|
||||
Script<As...> *script_;
|
||||
// Empty tuple for scripts without parameters; takes no space then
|
||||
[[no_unique_address]] Args args_;
|
||||
Args args_;
|
||||
};
|
||||
|
||||
/** Wait for a script to finish before continuing.
|
||||
|
||||
@@ -187,8 +187,14 @@ automation.register_apply_action(
|
||||
async def select_is_to_code(config, condition_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
if options := config.get(CONF_OPTIONS):
|
||||
# Shared flash table of option pointers, length passed as a template argument
|
||||
arg = cg.shared_progmem_array("select_is_options", cg.const_char_ptr, options)
|
||||
# List of constant options
|
||||
# Create a constexpr and pass that with a template length
|
||||
arr_id = ID(
|
||||
f"{condition_id}_data",
|
||||
is_declaration=True,
|
||||
type=global_ns.namespace("constexpr char * const"),
|
||||
)
|
||||
arg = cg.static_const_array(arr_id, cg.ArrayInitializer(*options))
|
||||
template_arg = TemplateArguments(len(options), *template_arg)
|
||||
else:
|
||||
# Lambda
|
||||
|
||||
@@ -9,8 +9,7 @@
|
||||
|
||||
namespace esphome::template_ {
|
||||
|
||||
// Distinct per member: two empty members of the same type cannot share an address
|
||||
template<int N> struct Empty {};
|
||||
struct Empty {};
|
||||
class BaseTemplateSelect : public select::Select, public PollingComponent {};
|
||||
|
||||
void dump_config_helper(BaseTemplateSelect *sel_comp, bool optimistic, bool has_lambda, size_t initial_option_index,
|
||||
@@ -60,8 +59,8 @@ class TemplateSelect : public BaseTemplateSelect {
|
||||
if constexpr (RESTORE_VALUE)
|
||||
this->pref_.save(&index);
|
||||
}
|
||||
[[no_unique_address]] std::conditional_t<HAS_LAMBDA, TemplateLambda<std::string>, Empty<0>> f_{};
|
||||
[[no_unique_address]] std::conditional_t<RESTORE_VALUE, ESPPreferenceObject, Empty<1>> pref_{};
|
||||
[[no_unique_address]] std::conditional_t<HAS_LAMBDA, TemplateLambda<std::string>, Empty> f_{};
|
||||
[[no_unique_address]] std::conditional_t<RESTORE_VALUE, ESPPreferenceObject, Empty> pref_{};
|
||||
};
|
||||
|
||||
/// Template select with set_action trigger - only instantiated when set_action is configured
|
||||
|
||||
@@ -402,9 +402,7 @@ async def to_code(config):
|
||||
cg.add(var.set_clock_source(UART_CLOCK_SOURCES[clock_source]))
|
||||
cg.add(var.set_stop_bits(config[CONF_STOP_BITS]))
|
||||
cg.add(var.set_data_bits(config[CONF_DATA_BITS]))
|
||||
# Skip the setter when the config matches the C++ initializer (UART_CONFIG_PARITY_NONE).
|
||||
if (parity := config[CONF_PARITY]) != "NONE":
|
||||
cg.add(var.set_parity(parity))
|
||||
cg.add(var.set_parity(config[CONF_PARITY]))
|
||||
|
||||
if CONF_DEBUG in config:
|
||||
await debug_to_code(config[CONF_DEBUG], var)
|
||||
|
||||
@@ -217,7 +217,7 @@ class UARTComponent {
|
||||
uint32_t baud_rate_{0};
|
||||
uint8_t stop_bits_{0};
|
||||
uint8_t data_bits_{0};
|
||||
UARTParityOptions parity_{UART_CONFIG_PARITY_NONE}; // Must match the parity check in __init__.py
|
||||
UARTParityOptions parity_{UART_CONFIG_PARITY_NONE};
|
||||
#ifdef USE_UART_DEBUGGER
|
||||
CallbackManager<void(UARTDirection, uint8_t)> debug_callback_{};
|
||||
#endif
|
||||
|
||||
@@ -35,8 +35,5 @@ CONFIG_SCHEMA = (
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = await text_sensor.new_text_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
# Both flags default to false in C++, so only true needs a setter.
|
||||
if config[CONF_HIDE_HASH]:
|
||||
cg.add(var.set_hide_hash(True))
|
||||
if config[CONF_HIDE_TIMESTAMP]:
|
||||
cg.add(var.set_hide_timestamp(True))
|
||||
cg.add(var.set_hide_hash(config[CONF_HIDE_HASH]))
|
||||
cg.add(var.set_hide_timestamp(config[CONF_HIDE_TIMESTAMP]))
|
||||
|
||||
@@ -15,8 +15,8 @@ class VersionTextSensor final : public text_sensor::TextSensor, public Component
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
bool hide_hash_{false}; // Must match the setter check in text_sensor.py
|
||||
bool hide_timestamp_{false}; // Must match the setter check in text_sensor.py
|
||||
bool hide_hash_{false};
|
||||
bool hide_timestamp_{false};
|
||||
};
|
||||
|
||||
} // namespace esphome::version
|
||||
|
||||
@@ -413,16 +413,7 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add_define("USE_WEBSERVER_PRIVATE_NETWORK_ACCESS")
|
||||
if (allowed_origins := config.get(CONF_ALLOWED_ORIGINS)) is not None:
|
||||
cg.add_define("USE_WEBSERVER_ALLOWED_ORIGINS")
|
||||
# Shared flash table ended by nullptr, so the server stores only a pointer.
|
||||
cg.add(
|
||||
var.set_allowed_origins(
|
||||
cg.shared_progmem_array(
|
||||
"web_server_allowed_origins",
|
||||
cg.const_char_ptr,
|
||||
[*allowed_origins, cg.nullptr],
|
||||
)
|
||||
)
|
||||
)
|
||||
cg.add(var.set_allowed_origins(allowed_origins))
|
||||
if (auth := config.get(CONF_AUTH)) is not None:
|
||||
cg.add_define("USE_WEBSERVER_AUTH")
|
||||
# The scheme is fixed at build time so the unused Basic/Digest code path is compiled
|
||||
|
||||
@@ -495,8 +495,7 @@ bool WebServer::is_request_origin_allowed_(AsyncWebServerRequest *request, const
|
||||
|
||||
#ifdef USE_WEBSERVER_ALLOWED_ORIGINS
|
||||
// Otherwise the origin must be explicitly allowed via configuration.
|
||||
for (const char *const *it = this->allowed_origins_; *it != nullptr; it++) {
|
||||
const char *allowed_origin = *it;
|
||||
for (const char *allowed_origin : this->allowed_origins_) {
|
||||
// A single "*" entry allows any origin.
|
||||
if (allowed_origin[0] == '*' && allowed_origin[1] == '\0')
|
||||
return true;
|
||||
|
||||
@@ -254,9 +254,9 @@ class WebServer final : public Component, public AsyncWebHandler {
|
||||
*
|
||||
* This list is also used to authorize Private Network Access requests when that feature is enabled.
|
||||
*
|
||||
* @param origins nullptr-terminated table of allowed origins; must outlive the server.
|
||||
* @param origins The list of allowed origins.
|
||||
*/
|
||||
void set_allowed_origins(const char *const *origins) { this->allowed_origins_ = origins; }
|
||||
void set_allowed_origins(std::initializer_list<const char *> origins) { this->allowed_origins_ = origins; }
|
||||
#endif
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
@@ -531,9 +531,6 @@ class WebServer final : public Component, public AsyncWebHandler {
|
||||
bool include_internal_{false};
|
||||
|
||||
protected:
|
||||
// Declared here so it packs into the padding after include_internal_
|
||||
bool expose_log_{true};
|
||||
|
||||
void add_sorting_info_(JsonObject &root, EntityBase *entity);
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
@@ -617,10 +614,11 @@ class WebServer final : public Component, public AsyncWebHandler {
|
||||
#ifdef USE_WEBSERVER_JS_INCLUDE
|
||||
const char *js_include_{nullptr};
|
||||
#endif
|
||||
bool expose_log_{true};
|
||||
#ifdef USE_WEBSERVER_ALLOWED_ORIGINS
|
||||
// Extra origins allowed to make cross-origin browser requests ("*" means any origin).
|
||||
// Only compiled when allowed_origins is configured; same-origin is always allowed regardless.
|
||||
const char *const *allowed_origins_{nullptr};
|
||||
FixedVector<const char *> allowed_origins_;
|
||||
#endif
|
||||
|
||||
/// Check whether the given request Origin is permitted. Same-origin (matching the Host the
|
||||
|
||||
@@ -761,7 +761,7 @@ void AsyncEventSource::adopt_pending_sessions_main_loop_() {
|
||||
this->has_pending_sessions_.store(false, std::memory_order_relaxed);
|
||||
}
|
||||
for (auto *rsp : incoming) {
|
||||
// Already disconnected? Drop it; skip session start on a dead session.
|
||||
// Already disconnected? Drop it; skip on_connect_/session start on a dead session.
|
||||
if (rsp->safe_to_delete_()) {
|
||||
delete rsp; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
continue;
|
||||
@@ -775,7 +775,12 @@ void AsyncEventSource::adopt_pending_sessions_main_loop_() {
|
||||
continue;
|
||||
}
|
||||
this->sessions_.push_back(rsp);
|
||||
// Prime first so on_connect_ observes a session that has already sent its
|
||||
// initial ping/config/sorting_groups, matching the pre-refactor ordering.
|
||||
rsp->start_session_main_loop_();
|
||||
if (this->on_connect_) {
|
||||
this->on_connect_(rsp);
|
||||
}
|
||||
}
|
||||
}
|
||||
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
|
||||
@@ -379,6 +379,7 @@ using AsyncEventSourceClient = AsyncEventSourceResponse;
|
||||
|
||||
class AsyncEventSource : public AsyncWebHandler {
|
||||
friend class AsyncEventSourceResponse;
|
||||
using connect_handler_t = std::function<void(AsyncEventSourceClient *)>;
|
||||
|
||||
public:
|
||||
AsyncEventSource(StringRef url, esphome::web_server::WebServer *ws) : url_(url), web_server_(ws) {}
|
||||
@@ -393,6 +394,11 @@ class AsyncEventSource : public AsyncWebHandler {
|
||||
}
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
void handleRequest(AsyncWebServerRequest *request) override;
|
||||
// Callback runs on the main loop (not the httpd task) after the session's
|
||||
// initial ping/config/sorting_groups have been sent.
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
void onConnect(connect_handler_t &&cb) { this->on_connect_ = std::move(cb); }
|
||||
|
||||
void try_send_nodefer(const char *message, size_t message_len, const char *event = nullptr, uint32_t id = 0,
|
||||
uint32_t reconnect = 0);
|
||||
void deferrable_send_state(void *source, const char *event_type, message_generator_t *message_generator);
|
||||
@@ -412,6 +418,7 @@ class AsyncEventSource : public AsyncWebHandler {
|
||||
// Httpd-task intake; guarded by pending_mutex_, gated by has_pending_sessions_.
|
||||
std::vector<AsyncEventSourceResponse *> pending_sessions_;
|
||||
Mutex pending_mutex_;
|
||||
connect_handler_t on_connect_{};
|
||||
esphome::web_server::WebServer *web_server_;
|
||||
std::atomic<bool> has_pending_sessions_{false};
|
||||
};
|
||||
|
||||
@@ -542,16 +542,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
cv.Optional(CONF_PASSIVE_SCAN, default=False): cv.boolean,
|
||||
cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean,
|
||||
# An LN882H scan drops the association, so roaming costs a disconnect there.
|
||||
cv.SplitDefault(
|
||||
CONF_POST_CONNECT_ROAMING,
|
||||
esp8266=True,
|
||||
esp32=True,
|
||||
rp2=True,
|
||||
bk72xx=True,
|
||||
rtl87xx=True,
|
||||
ln882x=False,
|
||||
): cv.boolean,
|
||||
cv.Optional(CONF_POST_CONNECT_ROAMING, default=True): cv.boolean,
|
||||
cv.Optional(CONF_ON_CONNECT): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_ON_DISCONNECT): automation.validate_automation(
|
||||
single=True
|
||||
|
||||
@@ -303,7 +303,7 @@ class WiFiAP {
|
||||
bssid_t bssid_{}; // 6 bytes, all zeros = any/not set
|
||||
uint8_t channel_{0}; // 1 byte, 0 = auto/not set
|
||||
int8_t priority_{0}; // 1 byte
|
||||
bool hidden_{false}; // 1 byte; WiFiAP is byte aligned unless manual IP or EAP adds a 4 byte aligned member
|
||||
bool hidden_{false}; // 1 byte (+ 3 bytes end padding to 4-byte align)
|
||||
};
|
||||
|
||||
class WiFiScanResult {
|
||||
@@ -874,6 +874,9 @@ class WiFiComponent final : public Component {
|
||||
#ifdef WIFI_SCAN_RESULTS_LOCK_ENABLED
|
||||
Mutex scan_result_lock_;
|
||||
#endif
|
||||
#ifdef USE_WIFI_AP
|
||||
WiFiAP ap_;
|
||||
#endif
|
||||
#ifdef USE_WIFI_IP_STATE_LISTENERS
|
||||
StaticVector<WiFiIPStateListener *, ESPHOME_WIFI_IP_STATE_LISTENERS> ip_state_listeners_;
|
||||
#endif
|
||||
@@ -921,9 +924,6 @@ class WiFiComponent final : public Component {
|
||||
uint32_t roaming_scan_end_{0}; // Timestamp when last roaming scan completed
|
||||
#ifdef USE_WIFI_AP
|
||||
uint32_t ap_timeout_{90000}; // Keep in sync with DEFAULT_AP_TIMEOUT in __init__.py
|
||||
// WiFiAP is byte aligned unless manual IP or EAP is enabled; placed before the
|
||||
// 1-byte members so they pack into its trailing bytes instead of padding after it
|
||||
WiFiAP ap_;
|
||||
#endif
|
||||
|
||||
// 1-byte enums and integers
|
||||
@@ -983,8 +983,7 @@ class WiFiComponent final : public Component {
|
||||
|
||||
// Bools and bitfields
|
||||
// Pending listener callbacks deferred from platform callbacks to main loop.
|
||||
// Empty when no listener needs deferring (e.g. ESP32 without connect state listeners)
|
||||
[[no_unique_address]] struct {
|
||||
struct {
|
||||
#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
|
||||
// Deferred until state machine reaches STA_CONNECTED so wifi.connected
|
||||
// condition returns true in listener automations.
|
||||
|
||||
@@ -27,7 +27,9 @@ namespace esphome {
|
||||
|
||||
template<class T, uint8_t SIZE> class FreeRTOSQueue {
|
||||
public:
|
||||
FreeRTOSQueue() : dropped_count_(0) { xQueueCreateStatic(SIZE, sizeof(T *), this->storage_, &this->queue_buf_); }
|
||||
FreeRTOSQueue() : dropped_count_(0) {
|
||||
this->handle_ = xQueueCreateStatic(SIZE, sizeof(T *), this->storage_, &this->queue_buf_);
|
||||
}
|
||||
|
||||
// No destructor — ESPHome components are never destroyed. Intentionally
|
||||
// omitted to avoid pulling in vQueueDelete code on resource-constrained targets.
|
||||
@@ -42,7 +44,7 @@ template<class T, uint8_t SIZE> class FreeRTOSQueue {
|
||||
if (element == nullptr)
|
||||
return false;
|
||||
|
||||
if (xQueueSend(this->handle_(), &element, 0) != pdPASS) {
|
||||
if (xQueueSend(this->handle_, &element, 0) != pdPASS) {
|
||||
this->increment_dropped_count();
|
||||
return false;
|
||||
}
|
||||
@@ -51,7 +53,7 @@ template<class T, uint8_t SIZE> class FreeRTOSQueue {
|
||||
|
||||
T *pop() {
|
||||
T *element;
|
||||
if (xQueueReceive(this->handle_(), &element, 0) != pdTRUE) {
|
||||
if (xQueueReceive(this->handle_, &element, 0) != pdTRUE) {
|
||||
return nullptr;
|
||||
}
|
||||
return element;
|
||||
@@ -78,24 +80,18 @@ template<class T, uint8_t SIZE> class FreeRTOSQueue {
|
||||
portEXIT_CRITICAL();
|
||||
}
|
||||
|
||||
bool empty() const { return uxQueueMessagesWaiting(this->handle_()) == 0; }
|
||||
bool empty() const { return uxQueueMessagesWaiting(this->handle_) == 0; }
|
||||
|
||||
bool full() const { return uxQueueSpacesAvailable(this->handle_()) == 0; }
|
||||
bool full() const { return uxQueueSpacesAvailable(this->handle_) == 0; }
|
||||
|
||||
size_t size() const { return uxQueueMessagesWaiting(this->handle_()); }
|
||||
size_t size() const { return uxQueueMessagesWaiting(this->handle_); }
|
||||
|
||||
protected:
|
||||
// Static storage for the queue — lives in BSS, no heap allocation
|
||||
uint8_t storage_[SIZE * sizeof(T *)];
|
||||
StaticQueue_t queue_buf_;
|
||||
QueueHandle_t handle_;
|
||||
uint16_t dropped_count_;
|
||||
|
||||
// xQueueCreateStatic() returns the static queue structure itself as the handle;
|
||||
// its only other checks are asserts, and a zero length is the one we could hit
|
||||
static_assert(SIZE > 0, "FreeRTOSQueue needs at least one slot");
|
||||
QueueHandle_t handle_() const {
|
||||
return reinterpret_cast<QueueHandle_t>(const_cast<StaticQueue_t *>(&this->queue_buf_));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
@@ -518,8 +518,7 @@ class Scheduler {
|
||||
uint32_t hash_or_id,
|
||||
SchedulerItem::Type type, bool find_first);
|
||||
|
||||
// Empty on single-threaded platforms (ESP8266, RP2), so it takes no space there
|
||||
[[no_unique_address]] Mutex lock_;
|
||||
Mutex lock_;
|
||||
std::vector<SchedulerItem *> items_;
|
||||
std::vector<SchedulerItem *> to_add_;
|
||||
|
||||
|
||||
@@ -13,4 +13,3 @@ api:
|
||||
port: 6054
|
||||
reboot_timeout: 0s
|
||||
batch_delay: 0ms
|
||||
listen_backlog: 2
|
||||
|
||||
@@ -13,4 +13,3 @@ api:
|
||||
port: 6053
|
||||
reboot_timeout: 15min
|
||||
batch_delay: 100ms
|
||||
listen_backlog: 4
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp8266:
|
||||
board: d1_mini
|
||||
|
||||
wifi:
|
||||
ssid: test
|
||||
password: testtest
|
||||
|
||||
api:
|
||||
@@ -12,7 +12,7 @@ def test_default_values_are_not_emitted(
|
||||
component_config_path: Callable[[str], Path],
|
||||
config_file: str,
|
||||
) -> None:
|
||||
"""Port 6053, a 15 min reboot timeout, 100 ms batch delay and backlog 4 are C++ initializers.
|
||||
"""Port 6053, a 15 min reboot timeout and 100 ms batch delay are C++ initializers.
|
||||
|
||||
Both the schema defaults and the same values written explicitly take the skip path.
|
||||
"""
|
||||
@@ -21,7 +21,6 @@ def test_default_values_are_not_emitted(
|
||||
assert "api_apiserver_id->set_port(" not in main_cpp
|
||||
assert "api_apiserver_id->set_reboot_timeout(" not in main_cpp
|
||||
assert "api_apiserver_id->set_batch_delay(" not in main_cpp
|
||||
assert "api_apiserver_id->set_listen_backlog(" not in main_cpp
|
||||
|
||||
|
||||
def test_custom_values_are_emitted(
|
||||
@@ -34,14 +33,3 @@ def test_custom_values_are_emitted(
|
||||
assert "api_apiserver_id->set_port(6054);" in main_cpp
|
||||
assert "api_apiserver_id->set_reboot_timeout(0);" in main_cpp
|
||||
assert "api_apiserver_id->set_batch_delay(0);" in main_cpp
|
||||
assert "api_apiserver_id->set_listen_backlog(2);" in main_cpp
|
||||
|
||||
|
||||
def test_esp8266_listen_backlog_is_emitted(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""The ESP8266 schema default of 1 differs from the C++ initializer, so it is set."""
|
||||
main_cpp = generate_main(component_config_path("esp8266.yaml"))
|
||||
|
||||
assert "api_apiserver_id->set_listen_backlog(1);" in main_cpp
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp8266:
|
||||
board: esp01_1m
|
||||
|
||||
matrix_keypad:
|
||||
- id: keypad_a
|
||||
rows:
|
||||
- pin: 12
|
||||
- pin: 13
|
||||
columns:
|
||||
- pin: 14
|
||||
- pin: 15
|
||||
keys: "1234"
|
||||
- id: keypad_b
|
||||
rows:
|
||||
- pin: 4
|
||||
- pin: 5
|
||||
columns:
|
||||
- pin: 0
|
||||
- pin: 2
|
||||
keys: "1234"
|
||||
@@ -1,27 +0,0 @@
|
||||
"""Matrix keypad key codes live in a shared PROGMEM table instead of a std::string."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.components.matrix_keypad import check_keys
|
||||
import esphome.config_validation as cv
|
||||
|
||||
|
||||
def test_keys_use_shared_progmem_table(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
main_cpp = generate_main(component_config_path("keys.yaml"))
|
||||
|
||||
assert (
|
||||
"static constexpr uint8_t matrix_keypad_keys[] PROGMEM = {49, 50, 51, 52};"
|
||||
in main_cpp
|
||||
)
|
||||
assert main_cpp.count("->set_keys(matrix_keypad_keys);") == 2
|
||||
|
||||
|
||||
def test_non_ascii_keys_are_rejected() -> None:
|
||||
with pytest.raises(cv.Invalid, match="'é' is not an ASCII"):
|
||||
check_keys({"keys": "1é34"})
|
||||
@@ -1,52 +0,0 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp8266:
|
||||
board: esp01_1m
|
||||
|
||||
select:
|
||||
- platform: template
|
||||
id: mode
|
||||
optimistic: true
|
||||
options: [one, two, three]
|
||||
|
||||
button:
|
||||
- platform: template
|
||||
name: A
|
||||
on_press:
|
||||
- if:
|
||||
condition:
|
||||
select.is:
|
||||
id: mode
|
||||
options: [one, two]
|
||||
then:
|
||||
- logger.log: a
|
||||
- platform: template
|
||||
name: B
|
||||
on_press:
|
||||
- if:
|
||||
condition:
|
||||
select.is:
|
||||
id: mode
|
||||
options: [one, two]
|
||||
then:
|
||||
- logger.log: b
|
||||
- platform: template
|
||||
name: C
|
||||
on_press:
|
||||
- if:
|
||||
condition:
|
||||
select.is:
|
||||
id: mode
|
||||
options: three
|
||||
then:
|
||||
- logger.log: c
|
||||
- if:
|
||||
condition:
|
||||
select.is:
|
||||
id: mode
|
||||
lambda: return current == "two";
|
||||
then:
|
||||
- logger.log: d
|
||||
|
||||
logger:
|
||||
@@ -1,22 +0,0 @@
|
||||
"""select.is option lists live in shared PROGMEM tables."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
|
||||
def test_select_is_options_use_shared_progmem_tables(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
main_cpp = generate_main(component_config_path("select_is.yaml"))
|
||||
|
||||
tables = re.findall(
|
||||
r"static constexpr const char \* (select_is_options\w*)\[\] PROGMEM = \{([^;]*)\};",
|
||||
main_cpp,
|
||||
)
|
||||
# Two conditions with [one, two] share one table; [three] gets its own
|
||||
assert sorted(body for _, body in tables) == ['"one", "two"', '"three"']
|
||||
one_two = next(name for name, body in tables if body == '"one", "two"')
|
||||
assert main_cpp.count(f", {one_two});") == 2
|
||||
assert "SelectIsCondition<0" in main_cpp
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
uart:
|
||||
- id: default_uart
|
||||
tx_pin: GPIO17
|
||||
rx_pin: GPIO16
|
||||
baud_rate: 9600
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
uart:
|
||||
- id: even_uart
|
||||
tx_pin: GPIO17
|
||||
rx_pin: GPIO16
|
||||
baud_rate: 9600
|
||||
parity: EVEN
|
||||
@@ -1,24 +0,0 @@
|
||||
"""Tests for UART parity code generation."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def test_default_parity_is_not_emitted(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""Parity NONE is the C++ initializer, so the setter is skipped."""
|
||||
main_cpp = generate_main(component_config_path("parity_default.yaml"))
|
||||
|
||||
assert "default_uart->set_parity(" not in main_cpp
|
||||
|
||||
|
||||
def test_custom_parity_is_emitted(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""A non default parity still reaches the setter."""
|
||||
main_cpp = generate_main(component_config_path("parity_even.yaml"))
|
||||
|
||||
assert "even_uart->set_parity(uart::UART_CONFIG_PARITY_EVEN);" in main_cpp
|
||||
@@ -1,16 +0,0 @@
|
||||
---
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp8266:
|
||||
board: d1_mini
|
||||
|
||||
text_sensor:
|
||||
- platform: version
|
||||
id: default_version
|
||||
name: Default Version
|
||||
- platform: version
|
||||
id: hidden_version
|
||||
name: Hidden Version
|
||||
hide_hash: true
|
||||
hide_timestamp: true
|
||||
@@ -1,17 +0,0 @@
|
||||
"""Tests for the version text sensor codegen."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def test_default_hide_flags_are_not_emitted(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""Both flags are false in C++, so only true reaches the setters."""
|
||||
main_cpp = generate_main(component_config_path("version.yaml"))
|
||||
|
||||
assert "default_version->set_hide_hash(" not in main_cpp
|
||||
assert "default_version->set_hide_timestamp(" not in main_cpp
|
||||
assert "hidden_version->set_hide_hash(true);" in main_cpp
|
||||
assert "hidden_version->set_hide_timestamp(true);" in main_cpp
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
wifi:
|
||||
ssid: test
|
||||
password: testtest
|
||||
|
||||
web_server:
|
||||
allowed_origins:
|
||||
- https://app.esphome.io
|
||||
- "*"
|
||||
@@ -1,18 +0,0 @@
|
||||
"""Tests for the web_server allowed_origins codegen."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def test_allowed_origins_are_a_progmem_table(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""Allowed origins live in a nullptr-terminated flash table instead of a heap vector."""
|
||||
main_cpp = generate_main(component_config_path("allowed_origins.yaml"))
|
||||
|
||||
assert (
|
||||
"static constexpr const char * web_server_allowed_origins[] PROGMEM = "
|
||||
'{"https://app.esphome.io", "*", nullptr};' in main_cpp
|
||||
)
|
||||
assert "->set_allowed_origins(web_server_allowed_origins);" in main_cpp
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
ln882x:
|
||||
board: generic-ln882h
|
||||
|
||||
wifi:
|
||||
ssid: test
|
||||
password: testtest
|
||||
ap:
|
||||
ssid: fallback
|
||||
@@ -23,7 +23,6 @@ def test_default_values_are_not_emitted(
|
||||
assert "set_reboot_timeout(" not in main_cpp
|
||||
assert "set_power_save_mode(" not in main_cpp
|
||||
assert "set_min_auth_mode(" not in main_cpp
|
||||
assert "set_post_connect_roaming(" not in main_cpp
|
||||
|
||||
|
||||
def test_custom_values_are_emitted(
|
||||
@@ -38,13 +37,3 @@ def test_custom_values_are_emitted(
|
||||
assert "set_reboot_timeout(0);" in main_cpp
|
||||
assert "set_power_save_mode(wifi::WIFI_POWER_SAVE_LIGHT);" in main_cpp
|
||||
assert "set_min_auth_mode(wifi::WIFI_MIN_AUTH_MODE_WPA);" in main_cpp
|
||||
|
||||
|
||||
def test_ln882x_skips_post_connect_roaming(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""LN882x defaults post_connect_roaming off."""
|
||||
main_cpp = generate_main(component_config_path("bare_ln882x.yaml"))
|
||||
|
||||
assert "set_post_connect_roaming(false);" in main_cpp
|
||||
|
||||
@@ -33,20 +33,6 @@ button:
|
||||
cycle: false
|
||||
- select.first: select_test_select
|
||||
- select.last: select_test_select
|
||||
- if:
|
||||
condition:
|
||||
select.is:
|
||||
id: select_test_select
|
||||
options: [one, two]
|
||||
then:
|
||||
- select.next: select_test_select
|
||||
- if:
|
||||
condition:
|
||||
select.is:
|
||||
id: select_test_select
|
||||
lambda: return current == "three";
|
||||
then:
|
||||
- select.first: select_test_select
|
||||
- select.operation:
|
||||
id: select_test_select
|
||||
operation: next
|
||||
@@ -55,47 +41,3 @@ button:
|
||||
id: select_test_select
|
||||
operation: !lambda return SELECT_OP_PREVIOUS;
|
||||
cycle: !lambda return true;
|
||||
- platform: template
|
||||
name: "Test Select Options Lambdas"
|
||||
on_press:
|
||||
- lambda: |-
|
||||
auto &options = id(select_test_select).traits.get_options();
|
||||
for (size_t i = 0; i < options.size(); i++)
|
||||
ESP_LOGD("test", "%s", options[i]);
|
||||
- lambda: |-
|
||||
std::string wanted = "two";
|
||||
const auto &opts = id(select_test_select).traits.get_options();
|
||||
if (opts.empty())
|
||||
return;
|
||||
for (int i = 0; i < (int) opts.size(); i++) {
|
||||
if (opts[i] == wanted)
|
||||
ESP_LOGD("test", "found at %d", i);
|
||||
}
|
||||
if (id(select_test_select).traits.get_options()[0] == wanted)
|
||||
ESP_LOGD("test", "first");
|
||||
auto it = std::find(opts.begin(), opts.end(), std::string("three"));
|
||||
ESP_LOGD("test", "index %d", (int) (it - opts.begin()));
|
||||
- lambda: |-
|
||||
static std::vector<std::string> names{"red", "green"};
|
||||
FixedVector<const char *> fresh;
|
||||
fresh.init(names.size());
|
||||
for (auto &name : names)
|
||||
fresh.push_back(name.c_str());
|
||||
if (id(select_test_select).traits.get_options().size() != fresh.size())
|
||||
id(select_test_select).traits.set_options(fresh);
|
||||
id(select_test_select).traits.set_options({"one", "two", "three"});
|
||||
- lambda: |-
|
||||
const char *value = "two";
|
||||
const auto &options = id(select_test_select).traits.get_options();
|
||||
const auto *it = std::find_if(options.begin(), options.end(),
|
||||
[value](const char *option) { return strcmp(option, value) == 0; });
|
||||
if (it == options.end())
|
||||
return;
|
||||
uint32_t index = (uint32_t) (it - options.begin());
|
||||
ESP_LOGD("test", "index %u", (unsigned) index);
|
||||
const auto *it2 = std::find(options.begin(), options.end(), options[0]);
|
||||
ESP_LOGD("test", "first at %d", (int) (it2 - options.begin()));
|
||||
- lambda: |-
|
||||
auto &options = id(select_test_select).traits.get_options();
|
||||
for (size_t i = 0; i < options.size(); i++)
|
||||
ESP_LOGD("test", "%u: %s", (unsigned) i, options.at(i));
|
||||
|
||||
@@ -5358,7 +5358,7 @@ def _setup_build_info_test(
|
||||
return build_info_path, firmware_path
|
||||
|
||||
|
||||
def test_compile_program_quietly_ignores_skip_bootloader_elsewhere(
|
||||
def test_compile_program_warns_and_ignores_skip_bootloader_elsewhere(
|
||||
tmp_path: Path, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""A full build is always safe; orchestrators cannot see YAML overrides."""
|
||||
@@ -5372,13 +5372,11 @@ def test_compile_program_quietly_ignores_skip_bootloader_elsewhere(
|
||||
side_effect=EsphomeError("stop here"),
|
||||
),
|
||||
pytest.raises(EsphomeError, match="stop here"),
|
||||
caplog.at_level("DEBUG"),
|
||||
caplog.at_level("INFO"),
|
||||
):
|
||||
compile_program(MockArgs(), config)
|
||||
|
||||
assert [
|
||||
r.levelno for r in caplog.records if "--skip-bootloader ignored" in r.message
|
||||
] == [logging.DEBUG]
|
||||
assert "--skip-bootloader ignored" in caplog.text
|
||||
assert CORE.skip_bootloader is False
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user