diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 5b096788f5..0352d7347b 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -433,8 +433,8 @@ void APIServer::handle_action_response(uint32_t call_id, bool success, StringRef #ifdef USE_API_HOMEASSISTANT_STATES // Helper to add subscription (reduces duplication) -void APIServer::add_state_subscription_(const char *entity_id, const char *attribute, std::function f, - bool once) { +void APIServer::add_state_subscription_(const char *entity_id, const char *attribute, + std::function &&f, bool once) { this->state_subs_.push_back(HomeAssistantStateSubscription{ .entity_id = entity_id, .attribute = attribute, .callback = std::move(f), .once = once, // entity_id_dynamic_storage and attribute_dynamic_storage remain nullptr (no heap allocation) @@ -443,7 +443,7 @@ void APIServer::add_state_subscription_(const char *entity_id, const char *attri // Helper to add subscription with heap-allocated strings (reduces duplication) void APIServer::add_state_subscription_(std::string entity_id, optional attribute, - std::function f, bool once) { + std::function &&f, bool once) { HomeAssistantStateSubscription sub; // Allocate heap storage for the strings sub.entity_id_dynamic_storage = std::make_unique(std::move(entity_id)); @@ -463,29 +463,29 @@ void APIServer::add_state_subscription_(std::string entity_id, optional f) { + std::function &&f) { this->add_state_subscription_(entity_id, attribute, std::move(f), false); } void APIServer::get_home_assistant_state(const char *entity_id, const char *attribute, - std::function f) { + std::function &&f) { this->add_state_subscription_(entity_id, attribute, std::move(f), true); } // std::string overload with StringRef callback (zero-allocation callback) void APIServer::subscribe_home_assistant_state(std::string entity_id, optional attribute, - std::function f) { + std::function &&f) { this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), false); } void APIServer::get_home_assistant_state(std::string entity_id, optional attribute, - std::function f) { + std::function &&f) { this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), true); } // Legacy helper: wraps std::string callback and delegates to StringRef version void APIServer::add_state_subscription_(std::string entity_id, optional attribute, - std::function f, bool once) { + std::function &&f, bool once) { // Wrap callback to convert StringRef -> std::string, then delegate this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::function([f = std::move(f)](StringRef state) { f(state.str()); }), @@ -494,12 +494,12 @@ void APIServer::add_state_subscription_(std::string entity_id, optional attribute, - std::function f) { + std::function &&f) { this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), false); } void APIServer::get_home_assistant_state(std::string entity_id, optional attribute, - std::function f) { + std::function &&f) { this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), true); } diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index fed29016b3..3abf68358c 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -201,20 +201,20 @@ class APIServer : public Component, }; // New const char* overload (for internal components - zero allocation) - void subscribe_home_assistant_state(const char *entity_id, const char *attribute, std::function f); - void get_home_assistant_state(const char *entity_id, const char *attribute, std::function f); + void subscribe_home_assistant_state(const char *entity_id, const char *attribute, std::function &&f); + void get_home_assistant_state(const char *entity_id, const char *attribute, std::function &&f); // std::string overload with StringRef callback (for custom_api_device.h with zero-allocation callback) void subscribe_home_assistant_state(std::string entity_id, optional attribute, - std::function f); + std::function &&f); void get_home_assistant_state(std::string entity_id, optional attribute, - std::function f); + std::function &&f); // Legacy std::string overload (for custom_api_device.h - converts StringRef to std::string for callback) void subscribe_home_assistant_state(std::string entity_id, optional attribute, - std::function f); + std::function &&f); void get_home_assistant_state(std::string entity_id, optional attribute, - std::function f); + std::function &&f); const std::vector &get_state_subs() const; #endif @@ -241,13 +241,13 @@ class APIServer : public Component, #endif // USE_API_NOISE #ifdef USE_API_HOMEASSISTANT_STATES // Helper methods to reduce code duplication - void add_state_subscription_(const char *entity_id, const char *attribute, std::function f, - bool once); - void add_state_subscription_(std::string entity_id, optional attribute, std::function f, + void add_state_subscription_(const char *entity_id, const char *attribute, std::function &&f, bool once); + void add_state_subscription_(std::string entity_id, optional attribute, + std::function &&f, bool once); // Legacy helper: wraps std::string callback and delegates to StringRef version void add_state_subscription_(std::string entity_id, optional attribute, - std::function f, bool once); + std::function &&f, bool once); #endif // USE_API_HOMEASSISTANT_STATES // No explicit close() needed — listen sockets have no active connections on // failure/shutdown. Destructor handles fd cleanup (close or abort per platform). diff --git a/esphome/components/api/user_services.h b/esphome/components/api/user_services.h index 0fc529108c..d1b8a6ef0d 100644 --- a/esphome/components/api/user_services.h +++ b/esphome/components/api/user_services.h @@ -230,7 +230,7 @@ template class APIRespondAction : public Action { void set_is_optional_mode(bool is_optional) { this->is_optional_mode_ = is_optional; } #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON - void set_data(std::function func) { + void set_data(std::function &&func) { this->json_builder_ = std::move(func); this->has_data_ = true; } diff --git a/esphome/components/cse7766/cse7766.cpp b/esphome/components/cse7766/cse7766.cpp index 7ffdf757a0..806b79e19e 100644 --- a/esphome/components/cse7766/cse7766.cpp +++ b/esphome/components/cse7766/cse7766.cpp @@ -3,8 +3,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -namespace esphome { -namespace cse7766 { +namespace esphome::cse7766 { static const char *const TAG = "cse7766"; @@ -258,5 +257,4 @@ void CSE7766Component::dump_config() { this->check_uart_settings(4800, 1, uart::UART_CONFIG_PARITY_EVEN); } -} // namespace cse7766 -} // namespace esphome +} // namespace esphome::cse7766 diff --git a/esphome/components/cse7766/cse7766.h b/esphome/components/cse7766/cse7766.h index 66a4e04633..77b80dd824 100644 --- a/esphome/components/cse7766/cse7766.h +++ b/esphome/components/cse7766/cse7766.h @@ -5,8 +5,7 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/uart/uart.h" -namespace esphome { -namespace cse7766 { +namespace esphome::cse7766 { static constexpr size_t CSE7766_RAW_DATA_SIZE = 24; @@ -49,5 +48,4 @@ class CSE7766Component : public Component, public uart::UARTDevice { uint16_t cf_pulses_last_{0}; }; -} // namespace cse7766 -} // namespace esphome +} // namespace esphome::cse7766 diff --git a/esphome/components/dlms_meter/dlms_meter.cpp b/esphome/components/dlms_meter/dlms_meter.cpp index 11d05b3a08..bd2150e8dd 100644 --- a/esphome/components/dlms_meter/dlms_meter.cpp +++ b/esphome/components/dlms_meter/dlms_meter.cpp @@ -1,7 +1,5 @@ #include "dlms_meter.h" -#include - #if defined(USE_ESP8266_FRAMEWORK_ARDUINO) #include #elif defined(USE_ESP32) @@ -410,7 +408,7 @@ void DlmsMeterComponent::decode_obis_(uint8_t *plaintext, uint16_t message_lengt if (current_position + 1 < message_length) { int8_t scaler = static_cast(plaintext[current_position + 1]); if (scaler != 0) { - value *= powf(10.0f, scaler); + value *= pow10_int(scaler); } } diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 4c211b2f2a..a34747a183 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -14,6 +14,7 @@ from esphome.const import ( CONF_BOARD, CONF_COMPONENTS, CONF_DISABLED, + CONF_ENABLE_OTA_ROLLBACK, CONF_ESPHOME, CONF_FRAMEWORK, CONF_IGNORE_EFUSE_CUSTOM_MAC, @@ -90,7 +91,6 @@ CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES = "enable_idf_experimental_features" CONF_ENGINEERING_SAMPLE = "engineering_sample" CONF_INCLUDE_BUILTIN_IDF_COMPONENTS = "include_builtin_idf_components" CONF_ENABLE_LWIP_ASSERT = "enable_lwip_assert" -CONF_ENABLE_OTA_ROLLBACK = "enable_ota_rollback" CONF_EXECUTE_FROM_PSRAM = "execute_from_psram" CONF_MINIMUM_CHIP_REVISION = "minimum_chip_revision" CONF_RELEASE = "release" @@ -790,8 +790,15 @@ def _detect_variant(value): engineering_sample = value.get(CONF_ENGINEERING_SAMPLE) if engineering_sample is None: _LOGGER.warning( - "No board specified for ESP32-P4. Defaulting to production silicon (rev3). " - "If you have an early engineering sample (pre-rev3), set 'engineering_sample: true'." + "No board specified for ESP32-P4. Defaulting to production silicon (rev3).\n" + "If you have an early engineering sample (pre-rev3), add this to your config:\n" + "\n" + " esp32:\n" + " engineering_sample: true\n" + "\n" + "To check your chip revision, look for 'chip revision: vX.Y' in the boot log.\n" + "Engineering samples will show a revision below v3.0.\n" + "The 'debug:' component also reports the revision (e.g. Revision: 100 = v1.0, 300 = v3.0)." ) elif engineering_sample: value[CONF_BOARD] = "esp32-p4-evboard" diff --git a/esphome/components/esp32_ble_server/__init__.py b/esphome/components/esp32_ble_server/__init__.py index cb494ed1bc..b08e791e7e 100644 --- a/esphome/components/esp32_ble_server/__init__.py +++ b/esphome/components/esp32_ble_server/__init__.py @@ -24,6 +24,7 @@ from esphome.const import ( __version__ as ESPHOME_VERSION, ) from esphome.core import CORE +import esphome.final_validate as fv from esphome.schema_extractors import SCHEMA_EXTRACT AUTO_LOAD = ["esp32_ble", "bytebuffer"] @@ -42,6 +43,7 @@ CONF_FIRMWARE_VERSION = "firmware_version" CONF_INDICATE = "indicate" CONF_MANUFACTURER = "manufacturer" CONF_MANUFACTURER_DATA = "manufacturer_data" +CONF_MAX_CLIENTS = "max_clients" CONF_ON_WRITE = "on_write" CONF_READ = "read" CONF_STRING = "string" @@ -287,6 +289,22 @@ def create_device_information_service(config): def final_validate_config(config): + # Validate max_clients does not exceed esp32_ble max_connections + max_clients = config[CONF_MAX_CLIENTS] + if max_clients > 1: + full_config = fv.full_config.get() + ble_config = full_config.get("esp32_ble", {}) + max_connections = ble_config.get( + "max_connections", esp32_ble.DEFAULT_MAX_CONNECTIONS + ) + if max_clients > max_connections: + raise cv.Invalid( + f"'max_clients' ({max_clients}) cannot exceed esp32_ble " + f"'max_connections' ({max_connections}). " + f"Please set 'max_connections: {max_clients}' in the " + f"'esp32_ble' component." + ) + # Check if all characteristics that require notifications have the notify property set for char_id in CORE.data.get(DOMAIN, {}).get(KEY_NOTIFY_REQUIRED, set()): # Look for the characteristic in the configuration @@ -428,6 +446,7 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional(CONF_MODEL): value_schema("string", templatable=False), cv.Optional(CONF_FIRMWARE_VERSION): value_schema("string", templatable=False), cv.Optional(CONF_MANUFACTURER_DATA): cv.Schema([cv.uint8_t]), + cv.Optional(CONF_MAX_CLIENTS, default=1): cv.int_range(min=1, max=9), cv.Optional(CONF_SERVICES, default=[]): cv.ensure_list(SERVICE_SCHEMA), cv.Optional(CONF_ON_CONNECT): automation.validate_automation(single=True), cv.Optional(CONF_ON_DISCONNECT): automation.validate_automation(single=True), @@ -552,6 +571,7 @@ async def to_code(config): esp32_ble.register_ble_status_event_handler(parent, var) cg.add(var.set_parent(parent)) cg.add(parent.advertising_set_appearance(config[CONF_APPEARANCE])) + cg.add(var.set_max_clients(config[CONF_MAX_CLIENTS])) if CONF_MANUFACTURER_DATA in config: cg.add(var.set_manufacturer_data(config[CONF_MANUFACTURER_DATA])) for service_config in config[CONF_SERVICES]: diff --git a/esphome/components/esp32_ble_server/ble_server.cpp b/esphome/components/esp32_ble_server/ble_server.cpp index 2c13a8ac36..f292cf8722 100644 --- a/esphome/components/esp32_ble_server/ble_server.cpp +++ b/esphome/components/esp32_ble_server/ble_server.cpp @@ -175,6 +175,10 @@ void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t ga case ESP_GATTS_CONNECT_EVT: { ESP_LOGD(TAG, "BLE Client connected"); this->add_client_(param->connect.conn_id); + // Resume advertising so additional clients can discover and connect + if (this->client_count_ < this->max_clients_) { + this->parent_->advertising_start(); + } this->dispatch_callbacks_(CallbackType::ON_CONNECT, param->connect.conn_id); break; } @@ -241,7 +245,12 @@ void BLEServer::ble_before_disabled_event_handler() { float BLEServer::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH + 10; } -void BLEServer::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 BLE Server:"); } +void BLEServer::dump_config() { + ESP_LOGCONFIG(TAG, + "ESP32 BLE Server:\n" + " Max clients: %u", + this->max_clients_); +} BLEServer *global_ble_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/esp32_ble_server/ble_server.h b/esphome/components/esp32_ble_server/ble_server.h index 6fa86dd67f..ff7e0044e4 100644 --- a/esphome/components/esp32_ble_server/ble_server.h +++ b/esphome/components/esp32_ble_server/ble_server.h @@ -39,6 +39,9 @@ class BLEServer : public Component, public GATTsEventHandler, public BLEStatusEv this->restart_advertising_(); } + void set_max_clients(uint8_t max_clients) { this->max_clients_ = max_clients; } + uint8_t get_max_clients() const { return this->max_clients_; } + BLEService *create_service(ESPBTUUID uuid, bool advertise = false, uint16_t num_handles = 15); void remove_service(ESPBTUUID uuid, uint8_t inst_id = 0); BLEService *get_service(ESPBTUUID uuid, uint8_t inst_id = 0); @@ -95,6 +98,7 @@ class BLEServer : public Component, public GATTsEventHandler, public BLEStatusEv uint16_t clients_[USE_ESP32_BLE_MAX_CONNECTIONS]{}; uint8_t client_count_{0}; + uint8_t max_clients_{1}; std::vector services_{}; std::vector services_to_start_{}; BLEService *device_information_service_{}; diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index fcd32223e4..f855bc89cc 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -19,8 +19,7 @@ #include #endif -namespace esphome { -namespace ethernet { +namespace esphome::ethernet { #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 2) // work around IDF compile issue on P4 https://github.com/espressif/esp-idf/pull/15637 @@ -881,7 +880,6 @@ void EthernetComponent::write_phy_register_(esp_eth_mac_t *mac, PHYRegister regi #endif -} // namespace ethernet -} // namespace esphome +} // namespace esphome::ethernet #endif // USE_ESP32 diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index b4859c308d..1cd44d2b2c 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -15,8 +15,7 @@ #include "esp_mac.h" #include "esp_idf_version.h" -namespace esphome { -namespace ethernet { +namespace esphome::ethernet { #ifdef USE_ETHERNET_IP_STATE_LISTENERS /** Listener interface for Ethernet IP state changes. @@ -218,7 +217,6 @@ extern EthernetComponent *global_eth_component; extern "C" esp_eth_phy_t *esp_eth_phy_new_jl1101(const eth_phy_config_t *config); #endif -} // namespace ethernet -} // namespace esphome +} // namespace esphome::ethernet #endif // USE_ESP32 diff --git a/esphome/components/http_request/ota/ota_http_request.cpp b/esphome/components/http_request/ota/ota_http_request.cpp index d77a768211..0db3a50b47 100644 --- a/esphome/components/http_request/ota/ota_http_request.cpp +++ b/esphome/components/http_request/ota/ota_http_request.cpp @@ -1,5 +1,7 @@ #include "ota_http_request.h" +#include + #include "esphome/core/application.h" #include "esphome/core/defines.h" #include "esphome/core/log.h" @@ -209,6 +211,26 @@ uint8_t OtaHttpRequestComponent::do_ota_() { return ota::OTA_RESPONSE_OK; } +// URL-encode characters that are not unreserved per RFC 3986 section 2.3. +// This is needed for embedding userinfo (username/password) in URLs safely. +static std::string url_encode(const std::string &str) { + std::string result; + result.reserve(str.size()); + for (char c : str) { + if (std::isalnum(static_cast(c)) || c == '-' || c == '_' || c == '.' || c == '~') { + result += c; + } else { + result += '%'; + result += format_hex_pretty_char((static_cast(c) >> 4) & 0x0F); + result += format_hex_pretty_char(static_cast(c) & 0x0F); + } + } + return result; +} + +void OtaHttpRequestComponent::set_password(const std::string &password) { this->password_ = url_encode(password); } +void OtaHttpRequestComponent::set_username(const std::string &username) { this->username_ = url_encode(username); } + std::string OtaHttpRequestComponent::get_url_with_auth_(const std::string &url) { if (this->username_.empty() || this->password_.empty()) { return url; diff --git a/esphome/components/http_request/ota/ota_http_request.h b/esphome/components/http_request/ota/ota_http_request.h index 8735189e99..e3f1a4aa90 100644 --- a/esphome/components/http_request/ota/ota_http_request.h +++ b/esphome/components/http_request/ota/ota_http_request.h @@ -29,9 +29,9 @@ class OtaHttpRequestComponent : public ota::OTAComponent, public Parentedmd5_expected_ = md5; } - void set_password(const std::string &password) { this->password_ = password; } + void set_password(const std::string &password); void set_url(const std::string &url); - void set_username(const std::string &username) { this->username_ = username; } + void set_username(const std::string &username); std::string md5_computed() { return this->md5_computed_; } std::string md5_expected() { return this->md5_expected_; } diff --git a/esphome/components/http_request/update/http_request_update.cpp b/esphome/components/http_request/update/http_request_update.cpp index 85609bd31f..1900f69a69 100644 --- a/esphome/components/http_request/update/http_request_update.cpp +++ b/esphome/components/http_request/update/http_request_update.cpp @@ -24,8 +24,29 @@ namespace http_request { static const char *const TAG = "http_request.update"; static const size_t MAX_READ_SIZE = 256; +static constexpr uint32_t INITIAL_CHECK_INTERVAL_ID = 0; +static constexpr uint32_t INITIAL_CHECK_INTERVAL_MS = 10000; +static constexpr uint8_t INITIAL_CHECK_MAX_ATTEMPTS = 6; -void HttpRequestUpdate::setup() { this->ota_parent_->add_state_listener(this); } +void HttpRequestUpdate::setup() { + this->ota_parent_->add_state_listener(this); + + // Check periodically until network is ready + // Only if update interval is > total retry window to avoid redundant checks + if (this->get_update_interval() != SCHEDULER_DONT_RUN && + this->get_update_interval() > INITIAL_CHECK_INTERVAL_MS * INITIAL_CHECK_MAX_ATTEMPTS) { + this->initial_check_remaining_ = INITIAL_CHECK_MAX_ATTEMPTS; + this->set_interval(INITIAL_CHECK_INTERVAL_ID, INITIAL_CHECK_INTERVAL_MS, [this]() { + bool connected = network::is_connected(); + if (--this->initial_check_remaining_ == 0 || connected) { + this->cancel_interval(INITIAL_CHECK_INTERVAL_ID); + if (connected) { + this->update(); + } + } + }); + } +} void HttpRequestUpdate::on_ota_state(ota::OTAState state, float progress, uint8_t error) { if (state == ota::OTAState::OTA_IN_PROGRESS) { @@ -45,6 +66,7 @@ void HttpRequestUpdate::update() { ESP_LOGD(TAG, "Network not connected, skipping update check"); return; } + this->cancel_interval(INITIAL_CHECK_INTERVAL_ID); #ifdef USE_ESP32 xTaskCreate(HttpRequestUpdate::update_task, "update_task", 8192, (void *) this, 1, &this->update_task_handle_); #else diff --git a/esphome/components/http_request/update/http_request_update.h b/esphome/components/http_request/update/http_request_update.h index cf34ace18e..b8350346f9 100644 --- a/esphome/components/http_request/update/http_request_update.h +++ b/esphome/components/http_request/update/http_request_update.h @@ -40,6 +40,7 @@ class HttpRequestUpdate final : public update::UpdateEntity, public PollingCompo #ifdef USE_ESP32 TaskHandle_t update_task_handle_{nullptr}; #endif + uint8_t initial_check_remaining_{0}; }; } // namespace http_request diff --git a/esphome/components/i2c/i2c.cpp b/esphome/components/i2c/i2c.cpp index b9b5d79428..76b3ec3b4d 100644 --- a/esphome/components/i2c/i2c.cpp +++ b/esphome/components/i2c/i2c.cpp @@ -5,8 +5,7 @@ #include "esphome/core/log.h" #include -namespace esphome { -namespace i2c { +namespace esphome::i2c { static const char *const TAG = "i2c"; @@ -109,5 +108,4 @@ uint8_t I2CRegister16::get() const { return value; } -} // namespace i2c -} // namespace esphome +} // namespace esphome::i2c diff --git a/esphome/components/i2c/i2c.h b/esphome/components/i2c/i2c.h index aab98d5f46..00929db620 100644 --- a/esphome/components/i2c/i2c.h +++ b/esphome/components/i2c/i2c.h @@ -6,8 +6,7 @@ #include "esphome/core/optional.h" #include "i2c_bus.h" -namespace esphome { -namespace i2c { +namespace esphome::i2c { #define LOG_I2C_DEVICE(this) ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_); @@ -272,5 +271,4 @@ class I2CDevice { I2CBus *bus_{nullptr}; ///< pointer to I2CBus instance }; -} // namespace i2c -} // namespace esphome +} // namespace esphome::i2c diff --git a/esphome/components/i2c/i2c_bus.h b/esphome/components/i2c/i2c_bus.h index 2bc0dc1ef9..0c5e80bfe5 100644 --- a/esphome/components/i2c/i2c_bus.h +++ b/esphome/components/i2c/i2c_bus.h @@ -6,8 +6,7 @@ #include "esphome/core/helpers.h" -namespace esphome { -namespace i2c { +namespace esphome::i2c { /// @brief Error codes returned by I2CBus and I2CDevice methods enum ErrorCode { @@ -69,5 +68,4 @@ class InternalI2CBus : public I2CBus { virtual int get_port() const = 0; }; -} // namespace i2c -} // namespace esphome +} // namespace esphome::i2c diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index edd6b81588..5120eb4c00 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -7,8 +7,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -namespace esphome { -namespace i2c { +namespace esphome::i2c { static const char *const TAG = "i2c.arduino"; @@ -262,7 +261,6 @@ void ArduinoI2CBus::recover_() { recovery_result_ = RECOVERY_COMPLETED; } -} // namespace i2c -} // namespace esphome +} // namespace esphome::i2c #endif // defined(USE_ARDUINO) && !defined(USE_ESP32) diff --git a/esphome/components/i2c/i2c_bus_arduino.h b/esphome/components/i2c/i2c_bus_arduino.h index 2d69e7684c..edc14af7bc 100644 --- a/esphome/components/i2c/i2c_bus_arduino.h +++ b/esphome/components/i2c/i2c_bus_arduino.h @@ -6,8 +6,7 @@ #include "esphome/core/component.h" #include "i2c_bus.h" -namespace esphome { -namespace i2c { +namespace esphome::i2c { enum RecoveryCode { RECOVERY_FAILED_SCL_LOW, @@ -45,7 +44,6 @@ class ArduinoI2CBus : public InternalI2CBus, public Component { bool initialized_ = false; }; -} // namespace i2c -} // namespace esphome +} // namespace esphome::i2c #endif // defined(USE_ARDUINO) && !defined(USE_ESP32) diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index 7a965ce5ad..eaefabf75b 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -10,8 +10,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -namespace esphome { -namespace i2c { +namespace esphome::i2c { static const char *const TAG = "i2c.idf"; @@ -312,6 +311,5 @@ void IDFI2CBus::recover_() { recovery_result_ = RECOVERY_COMPLETED; } -} // namespace i2c -} // namespace esphome +} // namespace esphome::i2c #endif // USE_ESP32 diff --git a/esphome/components/i2c/i2c_bus_esp_idf.h b/esphome/components/i2c/i2c_bus_esp_idf.h index 84f4616967..c23f9f0c54 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.h +++ b/esphome/components/i2c/i2c_bus_esp_idf.h @@ -6,8 +6,7 @@ #include "i2c_bus.h" #include -namespace esphome { -namespace i2c { +namespace esphome::i2c { enum RecoveryCode { RECOVERY_FAILED_SCL_LOW, @@ -56,7 +55,6 @@ class IDFI2CBus : public InternalI2CBus, public Component { #endif }; -} // namespace i2c -} // namespace esphome +} // namespace esphome::i2c #endif // USE_ESP32 diff --git a/esphome/components/kamstrup_kmp/kamstrup_kmp.cpp b/esphome/components/kamstrup_kmp/kamstrup_kmp.cpp index 534939f9af..00c65a1937 100644 --- a/esphome/components/kamstrup_kmp/kamstrup_kmp.cpp +++ b/esphome/components/kamstrup_kmp/kamstrup_kmp.cpp @@ -222,11 +222,11 @@ void KamstrupKMPComponent::parse_command_message_(uint16_t command, const uint8_ } // Calculate exponent - float exponent = msg[6] & 0x3F; + int8_t exp_val = msg[6] & 0x3F; if (msg[6] & 0x40) { - exponent = -exponent; + exp_val = -exp_val; } - exponent = powf(10, exponent); + float exponent = pow10_int(exp_val); if (msg[6] & 0x80) { exponent = -exponent; } diff --git a/esphome/components/logger/logger_zephyr.cpp b/esphome/components/logger/logger_zephyr.cpp index c2d24d6efc..6b46b93c61 100644 --- a/esphome/components/logger/logger_zephyr.cpp +++ b/esphome/components/logger/logger_zephyr.cpp @@ -20,8 +20,6 @@ __attribute__((weak)) void print_coredump() {} namespace esphome::logger { -static const uint32_t CRASH_MAGIC = 0xDEADBEEF; - __attribute__((section(".noinit"))) struct { uint32_t magic; uint32_t reason; @@ -152,7 +150,7 @@ static const char *reason_to_str(unsigned int reason, char *buf) { void Logger::dump_crash_() { ESP_LOGD(TAG, "Crash buffer address %p", &crash_buf); - if (crash_buf.magic == CRASH_MAGIC) { + if (crash_buf.magic == App.get_config_hash()) { char reason_buf[REASON_BUF_SIZE]; ESP_LOGE(TAG, "Last crash:"); ESP_LOGE(TAG, "Reason=%s PC=0x%08x LR=0x%08x", reason_to_str(crash_buf.reason, reason_buf), crash_buf.pc, @@ -164,7 +162,7 @@ void Logger::dump_crash_() { } void k_sys_fatal_error_handler(unsigned int reason, const z_arch_esf_t *esf) { - crash_buf.magic = CRASH_MAGIC; + crash_buf.magic = App.get_config_hash(); crash_buf.reason = reason; if (esf) { crash_buf.pc = esf->basic.pc; diff --git a/esphome/components/mipi_dsi/display.py b/esphome/components/mipi_dsi/display.py index de3791b3a4..85bfad7f1a 100644 --- a/esphome/components/mipi_dsi/display.py +++ b/esphome/components/mipi_dsi/display.py @@ -39,6 +39,7 @@ import esphome.config_validation as cv from esphome.const import ( CONF_COLOR_ORDER, CONF_DIMENSIONS, + CONF_DISABLED, CONF_ENABLE_PIN, CONF_ID, CONF_INIT_SEQUENCE, @@ -88,14 +89,17 @@ COLOR_DEPTHS = { def model_schema(config): model = MODELS[config[CONF_MODEL].upper()] model.defaults[CONF_SWAP_XY] = cv.UNDEFINED - transform = cv.Schema( - { - cv.Required(CONF_MIRROR_X): cv.boolean, - cv.Required(CONF_MIRROR_Y): cv.boolean, - cv.Optional(CONF_SWAP_XY): cv.invalid( - "Axis swapping not supported by DSI displays" - ), - } + transform = cv.Any( + cv.Schema( + { + cv.Required(CONF_MIRROR_X): cv.boolean, + cv.Required(CONF_MIRROR_Y): cv.boolean, + cv.Optional(CONF_SWAP_XY): cv.invalid( + "Axis swapping not supported by DSI displays" + ), + } + ), + cv.one_of(CONF_DISABLED, lower=True), ) # CUSTOM model will need to provide a custom init sequence iseqconf = ( @@ -199,9 +203,9 @@ async def to_code(config): cg.add(var.set_vsync_pulse_width(config[CONF_VSYNC_PULSE_WIDTH])) cg.add(var.set_vsync_back_porch(config[CONF_VSYNC_BACK_PORCH])) cg.add(var.set_vsync_front_porch(config[CONF_VSYNC_FRONT_PORCH])) - cg.add(var.set_pclk_frequency(int(config[CONF_PCLK_FREQUENCY] / 1e6))) + cg.add(var.set_pclk_frequency(config[CONF_PCLK_FREQUENCY] / 1.0e6)) cg.add(var.set_lanes(int(config[CONF_LANES]))) - cg.add(var.set_lane_bit_rate(int(config[CONF_LANE_BIT_RATE] / 1e6))) + cg.add(var.set_lane_bit_rate(config[CONF_LANE_BIT_RATE] / 1.0e6)) if reset_pin := config.get(CONF_RESET_PIN): reset = await cg.gpio_pin_expression(reset_pin) cg.add(var.set_reset_pin(reset)) diff --git a/esphome/components/mipi_dsi/mipi_dsi.cpp b/esphome/components/mipi_dsi/mipi_dsi.cpp index 18cafab684..4d45cfb799 100644 --- a/esphome/components/mipi_dsi/mipi_dsi.cpp +++ b/esphome/components/mipi_dsi/mipi_dsi.cpp @@ -374,7 +374,7 @@ void MIPI_DSI::dump_config() { "\n Swap X/Y: %s" "\n Rotation: %d degrees" "\n DSI Lanes: %u" - "\n Lane Bit Rate: %uMbps" + "\n Lane Bit Rate: %.0fMbps" "\n HSync Pulse Width: %u" "\n HSync Back Porch: %u" "\n HSync Front Porch: %u" @@ -385,7 +385,7 @@ void MIPI_DSI::dump_config() { "\n Display Pixel Mode: %d bit" "\n Color Order: %s" "\n Invert Colors: %s" - "\n Pixel Clock: %dMHz", + "\n Pixel Clock: %.1fMHz", this->model_, this->width_, this->height_, YESNO(this->madctl_ & (MADCTL_XFLIP | MADCTL_MX)), YESNO(this->madctl_ & (MADCTL_YFLIP | MADCTL_MY)), YESNO(this->madctl_ & MADCTL_MV), this->rotation_, this->lanes_, this->lane_bit_rate_, this->hsync_pulse_width_, this->hsync_back_porch_, diff --git a/esphome/components/mipi_dsi/mipi_dsi.h b/esphome/components/mipi_dsi/mipi_dsi.h index 1cffe3b178..6e27912aa5 100644 --- a/esphome/components/mipi_dsi/mipi_dsi.h +++ b/esphome/components/mipi_dsi/mipi_dsi.h @@ -47,7 +47,7 @@ class MIPI_DSI : public display::Display { void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; } void set_enable_pins(std::vector enable_pins) { this->enable_pins_ = std::move(enable_pins); } - void set_pclk_frequency(uint32_t pclk_frequency) { this->pclk_frequency_ = pclk_frequency; } + void set_pclk_frequency(float pclk_frequency) { this->pclk_frequency_ = pclk_frequency; } int get_width_internal() override { return this->width_; } int get_height_internal() override { return this->height_; } void set_hsync_back_porch(uint16_t hsync_back_porch) { this->hsync_back_porch_ = hsync_back_porch; } @@ -58,7 +58,7 @@ class MIPI_DSI : public display::Display { void set_vsync_front_porch(uint16_t vsync_front_porch) { this->vsync_front_porch_ = vsync_front_porch; } void set_init_sequence(const std::vector &init_sequence) { this->init_sequence_ = init_sequence; } void set_model(const char *model) { this->model_ = model; } - void set_lane_bit_rate(uint16_t lane_bit_rate) { this->lane_bit_rate_ = lane_bit_rate; } + void set_lane_bit_rate(float lane_bit_rate) { this->lane_bit_rate_ = lane_bit_rate; } void set_lanes(uint8_t lanes) { this->lanes_ = lanes; } void set_madctl(uint8_t madctl) { this->madctl_ = madctl; } @@ -95,9 +95,9 @@ class MIPI_DSI : public display::Display { uint16_t vsync_front_porch_ = 10; const char *model_{"Unknown"}; std::vector init_sequence_{}; - uint16_t pclk_frequency_ = 16; // in MHz - uint16_t lane_bit_rate_{1500}; // in Mbps - uint8_t lanes_{2}; // 1, 2, 3 or 4 lanes + float pclk_frequency_ = 16; // in MHz + float lane_bit_rate_{1500}; // in Mbps + uint8_t lanes_{2}; // 1, 2, 3 or 4 lanes bool invert_colors_{}; display::ColorOrder color_mode_{display::COLOR_ORDER_BGR}; diff --git a/esphome/components/network/ip_address.h b/esphome/components/network/ip_address.h index b2a2c563e2..c0e7b2886c 100644 --- a/esphome/components/network/ip_address.h +++ b/esphome/components/network/ip_address.h @@ -37,8 +37,7 @@ using ip4_addr_t = in_addr; #include #endif -namespace esphome { -namespace network { +namespace esphome::network { /// Buffer size for IP address string (IPv6 max: 39 chars + null) static constexpr size_t IP_ADDRESS_BUFFER_SIZE = 40; @@ -187,6 +186,5 @@ struct IPAddress { using IPAddresses = std::array; -} // namespace network -} // namespace esphome +} // namespace esphome::network #endif diff --git a/esphome/components/network/util.cpp b/esphome/components/network/util.cpp index 5e741fd244..e397d77077 100644 --- a/esphome/components/network/util.cpp +++ b/esphome/components/network/util.cpp @@ -17,8 +17,7 @@ #include "esphome/components/modem/modem_component.h" #endif -namespace esphome { -namespace network { +namespace esphome::network { // The order of the components is important: WiFi should come after any possible main interfaces (it may be used as // an AP that use a previous interface for NAT). @@ -109,6 +108,5 @@ const char *get_use_address() { #endif } -} // namespace network -} // namespace esphome +} // namespace esphome::network #endif diff --git a/esphome/components/network/util.h b/esphome/components/network/util.h index 3dc12232aa..ae949ab0a8 100644 --- a/esphome/components/network/util.h +++ b/esphome/components/network/util.h @@ -4,8 +4,7 @@ #include #include "ip_address.h" -namespace esphome { -namespace network { +namespace esphome::network { /// Return whether the node is connected to the network (through wifi, eth, ...) bool is_connected(); @@ -15,6 +14,5 @@ bool is_disabled(); const char *get_use_address(); IPAddresses get_ip_addresses(); -} // namespace network -} // namespace esphome +} // namespace esphome::network #endif diff --git a/esphome/components/nfc/nfc_helpers.cpp b/esphome/components/nfc/nfc_helpers.cpp index bfaed6e486..fb0954a833 100644 --- a/esphome/components/nfc/nfc_helpers.cpp +++ b/esphome/components/nfc/nfc_helpers.cpp @@ -39,7 +39,7 @@ std::string get_random_ha_tag_ndef() { for (int i = 0; i < 12; i++) { uri += ALPHANUM[random_uint32() % (sizeof(ALPHANUM) - 1)]; } - ESP_LOGD("pn7160", "Payload to be written: %s", uri.c_str()); + ESP_LOGD(TAG, "Payload to be written: %s", uri.c_str()); return uri; } diff --git a/esphome/components/remote_receiver/__init__.py b/esphome/components/remote_receiver/__init__.py index 362f6e99db..53a0f8fb77 100644 --- a/esphome/components/remote_receiver/__init__.py +++ b/esphome/components/remote_receiver/__init__.py @@ -237,7 +237,7 @@ async def to_code(config): FILTER_SOURCE_FILES = filter_source_files_from_platform( { - "remote_receiver_esp32.cpp": { + "remote_receiver_rmt.cpp": { PlatformFramework.ESP32_ARDUINO, PlatformFramework.ESP32_IDF, }, diff --git a/esphome/components/remote_receiver/remote_receiver_esp32.cpp b/esphome/components/remote_receiver/remote_receiver_rmt.cpp similarity index 100% rename from esphome/components/remote_receiver/remote_receiver_esp32.cpp rename to esphome/components/remote_receiver/remote_receiver_rmt.cpp diff --git a/esphome/components/remote_transmitter/__init__.py b/esphome/components/remote_transmitter/__init__.py index fc772f88b2..371dbb685f 100644 --- a/esphome/components/remote_transmitter/__init__.py +++ b/esphome/components/remote_transmitter/__init__.py @@ -171,7 +171,7 @@ async def to_code(config): FILTER_SOURCE_FILES = filter_source_files_from_platform( { - "remote_transmitter_esp32.cpp": { + "remote_transmitter_rmt.cpp": { PlatformFramework.ESP32_ARDUINO, PlatformFramework.ESP32_IDF, }, diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_rmt.cpp similarity index 100% rename from esphome/components/remote_transmitter/remote_transmitter_esp32.cpp rename to esphome/components/remote_transmitter/remote_transmitter_rmt.cpp diff --git a/esphome/components/sensor/filter.cpp b/esphome/components/sensor/filter.cpp index cd4db98457..0fe1effe17 100644 --- a/esphome/components/sensor/filter.cpp +++ b/esphome/components/sensor/filter.cpp @@ -5,6 +5,7 @@ #include #include "esphome/core/application.h" #include "esphome/core/hal.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "sensor.h" @@ -240,7 +241,7 @@ ValueListFilter::ValueListFilter(std::initializer_list> bool ValueListFilter::value_matches_any_(float sensor_value) { int8_t accuracy = this->parent_->get_accuracy_decimals(); - float accuracy_mult = powf(10.0f, accuracy); + float accuracy_mult = pow10_int(accuracy); float rounded_sensor = roundf(accuracy_mult * sensor_value); for (auto &filter_value : this->values_) { @@ -472,7 +473,7 @@ optional ClampFilter::new_value(float value) { RoundFilter::RoundFilter(uint8_t precision) : precision_(precision) {} optional RoundFilter::new_value(float value) { if (std::isfinite(value)) { - float accuracy_mult = powf(10.0f, this->precision_); + float accuracy_mult = pow10_int(this->precision_); return roundf(accuracy_mult * value) / accuracy_mult; } return value; diff --git a/esphome/components/switch/automation.cpp b/esphome/components/switch/automation.cpp index 5989ae9ce3..9a0221fe56 100644 --- a/esphome/components/switch/automation.cpp +++ b/esphome/components/switch/automation.cpp @@ -1,10 +1,8 @@ #include "automation.h" #include "esphome/core/log.h" -namespace esphome { -namespace switch_ { +namespace esphome::switch_ { static const char *const TAG = "switch.automation"; -} // namespace switch_ -} // namespace esphome +} // namespace esphome::switch_ diff --git a/esphome/components/switch/automation.h b/esphome/components/switch/automation.h index 27d3474c97..ed1f056c8b 100644 --- a/esphome/components/switch/automation.h +++ b/esphome/components/switch/automation.h @@ -4,8 +4,7 @@ #include "esphome/core/automation.h" #include "esphome/components/switch/switch.h" -namespace esphome { -namespace switch_ { +namespace esphome::switch_ { template class TurnOnAction : public Action { public: @@ -104,5 +103,4 @@ template class SwitchPublishAction : public Action { Switch *switch_; }; -} // namespace switch_ -} // namespace esphome +} // namespace esphome::switch_ diff --git a/esphome/components/switch/binary_sensor/switch_binary_sensor.cpp b/esphome/components/switch/binary_sensor/switch_binary_sensor.cpp index ba57154446..19995fb1ae 100644 --- a/esphome/components/switch/binary_sensor/switch_binary_sensor.cpp +++ b/esphome/components/switch/binary_sensor/switch_binary_sensor.cpp @@ -1,8 +1,7 @@ #include "switch_binary_sensor.h" #include "esphome/core/log.h" -namespace esphome { -namespace switch_ { +namespace esphome::switch_ { static const char *const TAG = "switch.binary_sensor"; @@ -13,5 +12,4 @@ void SwitchBinarySensor::setup() { void SwitchBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "Switch Binary Sensor", this); } -} // namespace switch_ -} // namespace esphome +} // namespace esphome::switch_ diff --git a/esphome/components/switch/binary_sensor/switch_binary_sensor.h b/esphome/components/switch/binary_sensor/switch_binary_sensor.h index 53b07da903..0b77cdd920 100644 --- a/esphome/components/switch/binary_sensor/switch_binary_sensor.h +++ b/esphome/components/switch/binary_sensor/switch_binary_sensor.h @@ -4,8 +4,7 @@ #include "esphome/core/component.h" #include "esphome/components/binary_sensor/binary_sensor.h" -namespace esphome { -namespace switch_ { +namespace esphome::switch_ { class SwitchBinarySensor : public binary_sensor::BinarySensor, public Component { public: @@ -17,5 +16,4 @@ class SwitchBinarySensor : public binary_sensor::BinarySensor, public Component Switch *source_; }; -} // namespace switch_ -} // namespace esphome +} // namespace esphome::switch_ diff --git a/esphome/components/switch/switch.cpp b/esphome/components/switch/switch.cpp index 61a273d25c..9e9af21368 100644 --- a/esphome/components/switch/switch.cpp +++ b/esphome/components/switch/switch.cpp @@ -3,8 +3,7 @@ #include "esphome/core/controller_registry.h" #include "esphome/core/log.h" -namespace esphome { -namespace switch_ { +namespace esphome::switch_ { static const char *const TAG = "switch"; @@ -107,5 +106,4 @@ void log_switch(const char *tag, const char *prefix, const char *type, Switch *o } } -} // namespace switch_ -} // namespace esphome +} // namespace esphome::switch_ diff --git a/esphome/components/switch/switch.h b/esphome/components/switch/switch.h index 9319adf9ed..982c640cf9 100644 --- a/esphome/components/switch/switch.h +++ b/esphome/components/switch/switch.h @@ -5,8 +5,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" -namespace esphome { -namespace switch_ { +namespace esphome::switch_ { #define SUB_SWITCH(name) \ protected: \ @@ -16,10 +15,10 @@ namespace switch_ { void set_##name##_switch(switch_::Switch *s) { this->name##_switch_ = s; } // bit0: on/off. bit1: persistent. bit2: inverted. bit3: disabled -const int RESTORE_MODE_ON_MASK = 0x01; -const int RESTORE_MODE_PERSISTENT_MASK = 0x02; -const int RESTORE_MODE_INVERTED_MASK = 0x04; -const int RESTORE_MODE_DISABLED_MASK = 0x08; +constexpr int RESTORE_MODE_ON_MASK = 0x01; +constexpr int RESTORE_MODE_PERSISTENT_MASK = 0x02; +constexpr int RESTORE_MODE_INVERTED_MASK = 0x04; +constexpr int RESTORE_MODE_DISABLED_MASK = 0x08; enum SwitchRestoreMode : uint8_t { SWITCH_ALWAYS_OFF = !RESTORE_MODE_ON_MASK, @@ -146,5 +145,4 @@ class Switch : public EntityBase, public EntityBase_DeviceClass { #define LOG_SWITCH(prefix, type, obj) log_switch((TAG), (prefix), LOG_STR_LITERAL(type), (obj)) void log_switch(const char *tag, const char *prefix, const char *type, Switch *obj); -} // namespace switch_ -} // namespace esphome +} // namespace esphome::switch_ diff --git a/esphome/components/text/automation.h b/esphome/components/text/automation.h index e7667fe491..ac8166d0be 100644 --- a/esphome/components/text/automation.h +++ b/esphome/components/text/automation.h @@ -4,8 +4,7 @@ #include "esphome/core/component.h" #include "text.h" -namespace esphome { -namespace text { +namespace esphome::text { class TextStateTrigger : public Trigger { public: @@ -29,5 +28,4 @@ template class TextSetAction : public Action { Text *text_; }; -} // namespace text -} // namespace esphome +} // namespace esphome::text diff --git a/esphome/components/text/text.cpp b/esphome/components/text/text.cpp index e3f74b685b..d8ab6b1b92 100644 --- a/esphome/components/text/text.cpp +++ b/esphome/components/text/text.cpp @@ -4,8 +4,7 @@ #include "esphome/core/log.h" #include -namespace esphome { -namespace text { +namespace esphome::text { static const char *const TAG = "text"; @@ -34,5 +33,4 @@ void Text::add_on_state_callback(std::function &&call this->state_callback_.add(std::move(callback)); } -} // namespace text -} // namespace esphome +} // namespace esphome::text diff --git a/esphome/components/text/text.h b/esphome/components/text/text.h index 3a1bea56cb..7d255e5688 100644 --- a/esphome/components/text/text.h +++ b/esphome/components/text/text.h @@ -6,8 +6,7 @@ #include "text_call.h" #include "text_traits.h" -namespace esphome { -namespace text { +namespace esphome::text { #define LOG_TEXT(prefix, type, obj) \ if ((obj) != nullptr) { \ @@ -47,5 +46,4 @@ class Text : public EntityBase { LazyCallbackManager state_callback_; }; -} // namespace text -} // namespace esphome +} // namespace esphome::text diff --git a/esphome/components/text/text_call.cpp b/esphome/components/text/text_call.cpp index 0d0a1d228d..8a1630c5ca 100644 --- a/esphome/components/text/text_call.cpp +++ b/esphome/components/text/text_call.cpp @@ -2,8 +2,7 @@ #include "esphome/core/log.h" #include "text.h" -namespace esphome { -namespace text { +namespace esphome::text { static const char *const TAG = "text"; @@ -52,5 +51,4 @@ void TextCall::perform() { this->parent_->control(target_value); } -} // namespace text -} // namespace esphome +} // namespace esphome::text diff --git a/esphome/components/text/text_call.h b/esphome/components/text/text_call.h index 9f75a25c6b..532fae34b2 100644 --- a/esphome/components/text/text_call.h +++ b/esphome/components/text/text_call.h @@ -3,8 +3,7 @@ #include "esphome/core/helpers.h" #include "text_traits.h" -namespace esphome { -namespace text { +namespace esphome::text { class Text; @@ -21,5 +20,4 @@ class TextCall { void validate_(); }; -} // namespace text -} // namespace esphome +} // namespace esphome::text diff --git a/esphome/components/text/text_traits.h b/esphome/components/text/text_traits.h index 473daafb8e..72e65b83ce 100644 --- a/esphome/components/text/text_traits.h +++ b/esphome/components/text/text_traits.h @@ -4,8 +4,7 @@ #include "esphome/core/string_ref.h" -namespace esphome { -namespace text { +namespace esphome::text { enum TextMode : uint8_t { TEXT_MODE_TEXT = 0, @@ -37,5 +36,4 @@ class TextTraits { TextMode mode_{TEXT_MODE_TEXT}; }; -} // namespace text -} // namespace esphome +} // namespace esphome::text diff --git a/esphome/components/text_sensor/automation.h b/esphome/components/text_sensor/automation.h index 709c54c140..ab30362774 100644 --- a/esphome/components/text_sensor/automation.h +++ b/esphome/components/text_sensor/automation.h @@ -6,8 +6,7 @@ #include "esphome/core/automation.h" #include "esphome/components/text_sensor/text_sensor.h" -namespace esphome { -namespace text_sensor { +namespace esphome::text_sensor { class TextSensorStateTrigger : public Trigger { public: @@ -46,5 +45,4 @@ template class TextSensorPublishAction : public Action { TextSensor *sensor_; }; -} // namespace text_sensor -} // namespace esphome +} // namespace esphome::text_sensor diff --git a/esphome/components/text_sensor/filter.cpp b/esphome/components/text_sensor/filter.cpp index f6552c7c66..f7c6a695fb 100644 --- a/esphome/components/text_sensor/filter.cpp +++ b/esphome/components/text_sensor/filter.cpp @@ -6,8 +6,7 @@ #include "esphome/core/log.h" #include "esphome/core/hal.h" -namespace esphome { -namespace text_sensor { +namespace esphome::text_sensor { static const char *const TAG = "text_sensor.filter"; @@ -107,7 +106,6 @@ bool MapFilter::new_value(std::string &value) { return true; // Pass through if no match } -} // namespace text_sensor -} // namespace esphome +} // namespace esphome::text_sensor #endif // USE_TEXT_SENSOR_FILTER diff --git a/esphome/components/text_sensor/filter.h b/esphome/components/text_sensor/filter.h index f88e8645cc..8a8bc55c8e 100644 --- a/esphome/components/text_sensor/filter.h +++ b/esphome/components/text_sensor/filter.h @@ -6,8 +6,7 @@ #include "esphome/core/component.h" #include "esphome/core/helpers.h" -namespace esphome { -namespace text_sensor { +namespace esphome::text_sensor { class TextSensor; @@ -165,7 +164,6 @@ class MapFilter : public Filter { FixedVector mappings_; }; -} // namespace text_sensor -} // namespace esphome +} // namespace esphome::text_sensor #endif // USE_TEXT_SENSOR_FILTER diff --git a/esphome/components/text_sensor/text_sensor.cpp b/esphome/components/text_sensor/text_sensor.cpp index c66d08ec40..91561c5f42 100644 --- a/esphome/components/text_sensor/text_sensor.cpp +++ b/esphome/components/text_sensor/text_sensor.cpp @@ -4,8 +4,7 @@ #include "esphome/core/log.h" #include -namespace esphome { -namespace text_sensor { +namespace esphome::text_sensor { static const char *const TAG = "text_sensor"; @@ -125,5 +124,4 @@ void TextSensor::notify_frontend_() { #endif } -} // namespace text_sensor -} // namespace esphome +} // namespace esphome::text_sensor diff --git a/esphome/components/text_sensor/text_sensor.h b/esphome/components/text_sensor/text_sensor.h index 97373dc716..9916aa63b2 100644 --- a/esphome/components/text_sensor/text_sensor.h +++ b/esphome/components/text_sensor/text_sensor.h @@ -10,8 +10,7 @@ #include #include -namespace esphome { -namespace text_sensor { +namespace esphome::text_sensor { class TextSensor; @@ -84,5 +83,4 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass { #endif }; -} // namespace text_sensor -} // namespace esphome +} // namespace esphome::text_sensor diff --git a/esphome/components/usb_host/usb_host.h b/esphome/components/usb_host/usb_host.h index a6a97d0bd7..2eec0c9699 100644 --- a/esphome/components/usb_host/usb_host.h +++ b/esphome/components/usb_host/usb_host.h @@ -12,8 +12,7 @@ #include "esphome/core/event_pool.h" #include -namespace esphome { -namespace usb_host { +namespace esphome::usb_host { // THREADING MODEL: // This component uses a dedicated USB task for event processing to prevent data loss. @@ -44,16 +43,16 @@ struct TransferRequest; class USBClient; // constants for setup packet type -static const uint8_t USB_RECIP_DEVICE = 0; -static const uint8_t USB_RECIP_INTERFACE = 1; -static const uint8_t USB_RECIP_ENDPOINT = 2; -static const uint8_t USB_TYPE_STANDARD = 0 << 5; -static const uint8_t USB_TYPE_CLASS = 1 << 5; -static const uint8_t USB_TYPE_VENDOR = 2 << 5; -static const uint8_t USB_DIR_MASK = 1 << 7; -static const uint8_t USB_DIR_IN = 1 << 7; -static const uint8_t USB_DIR_OUT = 0; -static const size_t SETUP_PACKET_SIZE = 8; +static constexpr uint8_t USB_RECIP_DEVICE = 0; +static constexpr uint8_t USB_RECIP_INTERFACE = 1; +static constexpr uint8_t USB_RECIP_ENDPOINT = 2; +static constexpr uint8_t USB_TYPE_STANDARD = 0 << 5; +static constexpr uint8_t USB_TYPE_CLASS = 1 << 5; +static constexpr uint8_t USB_TYPE_VENDOR = 2 << 5; +static constexpr uint8_t USB_DIR_MASK = 1 << 7; +static constexpr uint8_t USB_DIR_IN = 1 << 7; +static constexpr uint8_t USB_DIR_OUT = 0; +static constexpr size_t SETUP_PACKET_SIZE = 8; static constexpr size_t MAX_REQUESTS = USB_HOST_MAX_REQUESTS; // maximum number of outstanding requests possible. static_assert(MAX_REQUESTS >= 1 && MAX_REQUESTS <= 32, "MAX_REQUESTS must be between 1 and 32"); @@ -189,7 +188,6 @@ class USBHost : public Component { std::vector clients_{}; }; -} // namespace usb_host -} // namespace esphome +} // namespace esphome::usb_host #endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 diff --git a/esphome/components/usb_host/usb_host_client.cpp b/esphome/components/usb_host/usb_host_client.cpp index 422d74095c..5b0aed4c59 100644 --- a/esphome/components/usb_host/usb_host_client.cpp +++ b/esphome/components/usb_host/usb_host_client.cpp @@ -10,8 +10,7 @@ #include #include #include -namespace esphome { -namespace usb_host { +namespace esphome::usb_host { #pragma GCC diagnostic ignored "-Wparentheses" @@ -568,6 +567,5 @@ void USBClient::release_trq(TransferRequest *trq) { this->trq_in_use_.fetch_and(mask, std::memory_order_release); } -} // namespace usb_host -} // namespace esphome +} // namespace esphome::usb_host #endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 diff --git a/esphome/components/usb_host/usb_host_component.cpp b/esphome/components/usb_host/usb_host_component.cpp index 790fe6713b..8ce0a70dc9 100644 --- a/esphome/components/usb_host/usb_host_component.cpp +++ b/esphome/components/usb_host/usb_host_component.cpp @@ -4,8 +4,7 @@ #include #include "esphome/core/log.h" -namespace esphome { -namespace usb_host { +namespace esphome::usb_host { void USBHost::setup() { usb_host_config_t config{}; @@ -28,7 +27,6 @@ void USBHost::loop() { } } -} // namespace usb_host -} // namespace esphome +} // namespace esphome::usb_host #endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 diff --git a/esphome/components/version/text_sensor.py b/esphome/components/version/text_sensor.py index ba8c493d4b..a55e18a5a7 100644 --- a/esphome/components/version/text_sensor.py +++ b/esphome/components/version/text_sensor.py @@ -1,7 +1,12 @@ import esphome.codegen as cg from esphome.components import text_sensor import esphome.config_validation as cv -from esphome.const import CONF_HIDE_TIMESTAMP, ENTITY_CATEGORY_DIAGNOSTIC, ICON_NEW_BOX +from esphome.const import ( + CONF_HIDE_HASH, + CONF_HIDE_TIMESTAMP, + ENTITY_CATEGORY_DIAGNOSTIC, + ICON_NEW_BOX, +) version_ns = cg.esphome_ns.namespace("version") VersionTextSensor = version_ns.class_( @@ -16,6 +21,9 @@ CONFIG_SCHEMA = ( .extend( { cv.GenerateID(): cv.declare_id(VersionTextSensor), + # Hide the config hash suffix and restore the pre-2026.1 + # version text format when set to true. + cv.Optional(CONF_HIDE_HASH, default=False): cv.boolean, cv.Optional(CONF_HIDE_TIMESTAMP, default=False): cv.boolean, } ) @@ -26,4 +34,5 @@ CONFIG_SCHEMA = ( async def to_code(config): var = await text_sensor.new_text_sensor(config) await cg.register_component(var, config) + cg.add(var.set_hide_hash(config[CONF_HIDE_HASH])) cg.add(var.set_hide_timestamp(config[CONF_HIDE_TIMESTAMP])) diff --git a/esphome/components/version/version_text_sensor.cpp b/esphome/components/version/version_text_sensor.cpp index 2e5686008b..8aec98d2da 100644 --- a/esphome/components/version/version_text_sensor.cpp +++ b/esphome/components/version/version_text_sensor.cpp @@ -1,39 +1,54 @@ #include "version_text_sensor.h" #include "esphome/core/application.h" -#include "esphome/core/log.h" -#include "esphome/core/version.h" +#include "esphome/core/build_info_data.h" #include "esphome/core/helpers.h" +#include "esphome/core/log.h" #include "esphome/core/progmem.h" +#include "esphome/core/version.h" -namespace esphome { -namespace version { +namespace esphome::version { static const char *const TAG = "version.text_sensor"; void VersionTextSensor::setup() { - static const char PREFIX[] PROGMEM = ESPHOME_VERSION " (config hash 0x"; + static const char HASH_PREFIX[] PROGMEM = ESPHOME_VERSION " (config hash 0x"; + static const char VERSION_PREFIX[] PROGMEM = ESPHOME_VERSION; static const char BUILT_STR[] PROGMEM = ", built "; - // Buffer size: PREFIX + 8 hex chars + BUILT_STR + BUILD_TIME_STR_SIZE + ")" + null - constexpr size_t buf_size = sizeof(PREFIX) + 8 + sizeof(BUILT_STR) + esphome::Application::BUILD_TIME_STR_SIZE + 2; + + // Buffer size: HASH_PREFIX + 8 hex chars + BUILT_STR + BUILD_TIME_STR_SIZE + ")" + null + constexpr size_t buf_size = + sizeof(HASH_PREFIX) + 8 + sizeof(BUILT_STR) + esphome::Application::BUILD_TIME_STR_SIZE + 2; char version_str[buf_size]; - ESPHOME_strncpy_P(version_str, PREFIX, sizeof(version_str)); + // hide_hash restores the pre-2026.1 base format by omitting + // the " (config hash 0x...)" suffix entirely. + if (this->hide_hash_) { + ESPHOME_strncpy_P(version_str, VERSION_PREFIX, sizeof(version_str)); + } else { + ESPHOME_strncpy_P(version_str, HASH_PREFIX, sizeof(version_str)); - size_t len = strlen(version_str); - snprintf(version_str + len, sizeof(version_str) - len, "%08" PRIx32, App.get_config_hash()); + size_t len = strlen(version_str); + snprintf(version_str + len, sizeof(version_str) - len, "%08" PRIx32, App.get_config_hash()); + } + // Keep hide_timestamp behavior independent from hide_hash so all + // combinations remain available to users. if (!this->hide_timestamp_) { size_t len = strlen(version_str); ESPHOME_strncat_P(version_str, BUILT_STR, sizeof(version_str) - len - 1); ESPHOME_strncat_P(version_str, ESPHOME_BUILD_TIME_STR, sizeof(version_str) - strlen(version_str) - 1); } - strncat(version_str, ")", sizeof(version_str) - strlen(version_str) - 1); + // The closing parenthesis is part of the config-hash suffix and must + // only be appended when that suffix is present. + if (!this->hide_hash_) { + strncat(version_str, ")", sizeof(version_str) - strlen(version_str) - 1); + } version_str[sizeof(version_str) - 1] = '\0'; this->publish_state(version_str); } +void VersionTextSensor::set_hide_hash(bool hide_hash) { this->hide_hash_ = hide_hash; } void VersionTextSensor::set_hide_timestamp(bool hide_timestamp) { this->hide_timestamp_ = hide_timestamp; } void VersionTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Version Text Sensor", this); } -} // namespace version -} // namespace esphome +} // namespace esphome::version diff --git a/esphome/components/version/version_text_sensor.h b/esphome/components/version/version_text_sensor.h index b7d8001120..fec898ae03 100644 --- a/esphome/components/version/version_text_sensor.h +++ b/esphome/components/version/version_text_sensor.h @@ -3,18 +3,18 @@ #include "esphome/core/component.h" #include "esphome/components/text_sensor/text_sensor.h" -namespace esphome { -namespace version { +namespace esphome::version { class VersionTextSensor : public text_sensor::TextSensor, public Component { public: + void set_hide_hash(bool hide_hash); void set_hide_timestamp(bool hide_timestamp); void setup() override; void dump_config() override; protected: + bool hide_hash_{false}; bool hide_timestamp_{false}; }; -} // namespace version -} // namespace esphome +} // namespace esphome::version diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 4b572417c1..682008c40e 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -375,7 +375,7 @@ json::SerializationBuffer<> WebServer::get_config_json() { JsonObject root = builder.root(); root[ESPHOME_F("title")] = App.get_friendly_name().empty() ? App.get_name().c_str() : App.get_friendly_name().c_str(); - char comment_buffer[ESPHOME_COMMENT_SIZE]; + char comment_buffer[Application::ESPHOME_COMMENT_SIZE_MAX]; App.get_comment_string(comment_buffer); root[ESPHOME_F("comment")] = comment_buffer; #if defined(USE_WEBSERVER_OTA_DISABLED) || !defined(USE_WEBSERVER_OTA) diff --git a/esphome/components/web_server_idf/multipart.h b/esphome/components/web_server_idf/multipart.h index cb1e0ecd1d..8b94b0491e 100644 --- a/esphome/components/web_server_idf/multipart.h +++ b/esphome/components/web_server_idf/multipart.h @@ -33,8 +33,8 @@ class MultipartReader { ~MultipartReader(); // Set callbacks for handling data - void set_data_callback(DataCallback callback) { data_callback_ = std::move(callback); } - void set_part_complete_callback(PartCompleteCallback callback) { part_complete_callback_ = std::move(callback); } + void set_data_callback(DataCallback &&callback) { data_callback_ = std::move(callback); } + void set_part_complete_callback(PartCompleteCallback &&callback) { part_complete_callback_ = std::move(callback); } // Parse incoming data size_t parse(const char *data, size_t len); diff --git a/esphome/components/web_server_idf/web_server_idf.h b/esphome/components/web_server_idf/web_server_idf.h index 76ddfa35fd..9d81d89ec7 100644 --- a/esphome/components/web_server_idf/web_server_idf.h +++ b/esphome/components/web_server_idf/web_server_idf.h @@ -204,7 +204,7 @@ class AsyncWebServer { ~AsyncWebServer() { this->end(); } // NOLINTNEXTLINE(readability-identifier-naming) - void onNotFound(std::function fn) { on_not_found_ = std::move(fn); } + void onNotFound(std::function &&fn) { on_not_found_ = std::move(fn); } void begin(); void end(); @@ -327,7 +327,7 @@ class AsyncEventSource : public AsyncWebHandler { // NOLINTNEXTLINE(readability-identifier-naming) void handleRequest(AsyncWebServerRequest *request) override; // NOLINTNEXTLINE(readability-identifier-naming) - void onConnect(connect_handler_t cb) { this->on_connect_ = std::move(cb); } + void onConnect(connect_handler_t &&cb) { this->on_connect_ = std::move(cb); } void try_send_nodefer(const char *message, 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); diff --git a/esphome/const.py b/esphome/const.py index ccc9d56dbb..1611aeb101 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -354,6 +354,7 @@ CONF_ELSE = "else" CONF_ENABLE_BTM = "enable_btm" CONF_ENABLE_IPV6 = "enable_ipv6" CONF_ENABLE_ON_BOOT = "enable_on_boot" +CONF_ENABLE_OTA_ROLLBACK = "enable_ota_rollback" CONF_ENABLE_PIN = "enable_pin" CONF_ENABLE_PRIVATE_NETWORK_ACCESS = "enable_private_network_access" CONF_ENABLE_RRM = "enable_rrm" @@ -462,6 +463,7 @@ CONF_HEAT_OVERRUN = "heat_overrun" CONF_HEATER = "heater" CONF_HEIGHT = "height" CONF_HIDDEN = "hidden" +CONF_HIDE_HASH = "hide_hash" CONF_HIDE_TIMESTAMP = "hide_timestamp" CONF_HIGH = "high" CONF_HIGH_VOLTAGE_REFERENCE = "high_voltage_reference" diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index c6597897dc..6a7683a987 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -134,7 +134,7 @@ void Application::setup() { this->after_loop_tasks_(); this->app_state_ = new_app_state; yield(); - } while (!component->can_proceed()); + } while (!component->can_proceed() && !component->is_failed()); } ESP_LOGI(TAG, "setup() finished successfully!"); @@ -749,4 +749,15 @@ void Application::get_build_time_string(std::span buf buffer[buffer.size() - 1] = '\0'; } +void Application::get_comment_string(std::span buffer) { + ESPHOME_strncpy_P(buffer.data(), ESPHOME_COMMENT_STR, ESPHOME_COMMENT_SIZE); + buffer[ESPHOME_COMMENT_SIZE - 1] = '\0'; +} + +uint32_t Application::get_config_hash() { return ESPHOME_CONFIG_HASH; } + +uint32_t Application::get_config_version_hash() { return fnv1a_hash_extend(ESPHOME_CONFIG_HASH, ESPHOME_VERSION); } + +time_t Application::get_build_time() { return ESPHOME_BUILD_TIME; } + } // namespace esphome diff --git a/esphome/core/application.h b/esphome/core/application.h index 5b3e3dfed6..cd275bb97f 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -6,7 +6,6 @@ #include #include #include -#include "esphome/core/build_info_data.h" #include "esphome/core/component.h" #include "esphome/core/defines.h" #include "esphome/core/hal.h" @@ -274,16 +273,15 @@ class Application { return ""; } + /// Maximum size of the comment buffer (including null terminator) + static constexpr size_t ESPHOME_COMMENT_SIZE_MAX = 256; + /// Copy the comment string into the provided buffer - /// Buffer must be ESPHOME_COMMENT_SIZE bytes (compile-time enforced) - void get_comment_string(std::span buffer) { - ESPHOME_strncpy_P(buffer.data(), ESPHOME_COMMENT_STR, buffer.size()); - buffer[buffer.size() - 1] = '\0'; - } + void get_comment_string(std::span buffer); /// Get the comment of this Application as a string std::string get_comment() { - char buffer[ESPHOME_COMMENT_SIZE]; + char buffer[ESPHOME_COMMENT_SIZE_MAX]; this->get_comment_string(buffer); return std::string(buffer); } @@ -294,13 +292,13 @@ class Application { static constexpr size_t BUILD_TIME_STR_SIZE = 26; /// Get the config hash as a 32-bit integer - constexpr uint32_t get_config_hash() { return ESPHOME_CONFIG_HASH; } + uint32_t get_config_hash(); /// Get the config hash extended with ESPHome version - constexpr uint32_t get_config_version_hash() { return fnv1a_hash_extend(ESPHOME_CONFIG_HASH, ESPHOME_VERSION); } + uint32_t get_config_version_hash(); /// Get the build time as a Unix timestamp - constexpr time_t get_build_time() { return ESPHOME_BUILD_TIME; } + time_t get_build_time(); /// Copy the build time string into the provided buffer /// Buffer must be BUILD_TIME_STR_SIZE bytes (compile-time enforced) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 9f850b5df8..6d801e7ebc 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -468,8 +468,15 @@ ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) { static inline void normalize_accuracy_decimals(float &value, int8_t &accuracy_decimals) { if (accuracy_decimals < 0) { - auto multiplier = powf(10.0f, accuracy_decimals); - value = roundf(value * multiplier) / multiplier; + float divisor; + if (accuracy_decimals == -1) { + divisor = 10.0f; + } else if (accuracy_decimals == -2) { + divisor = 100.0f; + } else { + divisor = pow10_int(-accuracy_decimals); + } + value = roundf(value / divisor) * divisor; accuracy_decimals = 0; } } diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 298b93fbc4..b606e68df3 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -439,6 +439,21 @@ template class SmallBufferWithHeapFallb /// @name Mathematics ///@{ +/// Compute 10^exp using iterative multiplication/division. +/// Avoids pulling in powf/__ieee754_powf (~2.3KB flash) for small integer exponents. +/// Matches powf(10, exp) for the int8_t exponent range used by sensor accuracy_decimals. +inline float pow10_int(int8_t exp) { + float result = 1.0f; + if (exp >= 0) { + for (int8_t i = 0; i < exp; i++) + result *= 10.0f; + } else { + for (int8_t i = exp; i < 0; i++) + result /= 10.0f; + } + return result; +} + /// Remap \p value from the range (\p min, \p max) to (\p min_out, \p max_out). template T remap(U value, U min, U max, T min_out, T max_out) { return (value - min) * (max_out - min_out) / (max - min) + min_out; diff --git a/esphome/core/lock_free_queue.h b/esphome/core/lock_free_queue.h index e96b739b58..522fbd36e1 100644 --- a/esphome/core/lock_free_queue.h +++ b/esphome/core/lock_free_queue.h @@ -38,13 +38,27 @@ template class LockFreeQueue { } protected: + // Advance ring buffer index by one, wrapping at SIZE. + // Power-of-2 sizes use modulo (compiler emits single mask instruction). + // Non-power-of-2 sizes use comparison to avoid expensive multiply-shift sequences. + static constexpr uint8_t next_index(uint8_t index) { + if constexpr ((SIZE & (SIZE - 1)) == 0) { + return (index + 1) % SIZE; + } else { + uint8_t next = index + 1; + if (next >= SIZE) [[unlikely]] + next = 0; + return next; + } + } + // Internal push that reports queue state - for use by derived classes bool push_internal_(T *element, bool &was_empty, uint8_t &old_tail) { if (element == nullptr) return false; uint8_t current_tail = tail_.load(std::memory_order_relaxed); - uint8_t next_tail = (current_tail + 1) % SIZE; + uint8_t next_tail = next_index(current_tail); // Read head before incrementing tail uint8_t head_before = head_.load(std::memory_order_acquire); @@ -73,14 +87,21 @@ template class LockFreeQueue { } T *element = buffer_[current_head]; - head_.store((current_head + 1) % SIZE, std::memory_order_release); + head_.store(next_index(current_head), std::memory_order_release); return element; } size_t size() const { uint8_t tail = tail_.load(std::memory_order_acquire); uint8_t head = head_.load(std::memory_order_acquire); - return (tail - head + SIZE) % SIZE; + if constexpr ((SIZE & (SIZE - 1)) == 0) { + return (tail - head + SIZE) % SIZE; + } else { + int diff = static_cast(tail) - static_cast(head); + if (diff < 0) + diff += SIZE; + return static_cast(diff); + } } uint16_t get_and_reset_dropped_count() { return dropped_count_.exchange(0, std::memory_order_relaxed); } @@ -90,7 +111,7 @@ template class LockFreeQueue { bool empty() const { return head_.load(std::memory_order_acquire) == tail_.load(std::memory_order_acquire); } bool full() const { - uint8_t next_tail = (tail_.load(std::memory_order_relaxed) + 1) % SIZE; + uint8_t next_tail = next_index(tail_.load(std::memory_order_relaxed)); return next_tail == head_.load(std::memory_order_acquire); } diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index e4e0751e10..d9c66b2000 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -33,6 +33,11 @@ static constexpr uint32_t HALF_MAX_UINT32 = std::numeric_limits::max() // max delay to start an interval sequence static constexpr uint32_t MAX_INTERVAL_DELAY = 5000; +// Prevent inlining of SchedulerItem deletion. On BK7231N (Thumb-1), GCC inlines +// ~unique_ptr (~30 bytes each) at every destruction site. Defining +// the deleter in the .cpp file ensures a single copy of the destructor + operator delete. +void Scheduler::SchedulerItemDeleter::operator()(SchedulerItem *ptr) const noexcept { delete ptr; } + #if defined(ESPHOME_LOG_HAS_VERBOSE) || defined(ESPHOME_DEBUG_SCHEDULER) // Helper struct for formatting scheduler item names consistently in logs // Uses a stack buffer to avoid heap allocation @@ -135,7 +140,7 @@ bool Scheduler::is_retry_cancelled_locked_(Component *component, NameType name_t // name_type determines storage type: STATIC_STRING uses static_name, others use hash_or_id void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type type, NameType name_type, const char *static_name, uint32_t hash_or_id, uint32_t delay, - std::function func, bool is_retry, bool skip_cancel) { + std::function &&func, bool is_retry, bool skip_cancel) { if (delay == SCHEDULER_DONT_RUN) { // Still need to cancel existing timer if we have a name/id if (!skip_cancel) { @@ -216,17 +221,18 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type target->push_back(std::move(item)); } -void HOT Scheduler::set_timeout(Component *component, const char *name, uint32_t timeout, std::function func) { +void HOT Scheduler::set_timeout(Component *component, const char *name, uint32_t timeout, + std::function &&func) { this->set_timer_common_(component, SchedulerItem::TIMEOUT, NameType::STATIC_STRING, name, 0, timeout, std::move(func)); } void HOT Scheduler::set_timeout(Component *component, const std::string &name, uint32_t timeout, - std::function func) { + std::function &&func) { this->set_timer_common_(component, SchedulerItem::TIMEOUT, NameType::HASHED_STRING, nullptr, fnv1a_hash(name), timeout, std::move(func)); } -void HOT Scheduler::set_timeout(Component *component, uint32_t id, uint32_t timeout, std::function func) { +void HOT Scheduler::set_timeout(Component *component, uint32_t id, uint32_t timeout, std::function &&func) { this->set_timer_common_(component, SchedulerItem::TIMEOUT, NameType::NUMERIC_ID, nullptr, id, timeout, std::move(func)); } @@ -240,17 +246,17 @@ bool HOT Scheduler::cancel_timeout(Component *component, uint32_t id) { return this->cancel_item_(component, NameType::NUMERIC_ID, nullptr, id, SchedulerItem::TIMEOUT); } void HOT Scheduler::set_interval(Component *component, const std::string &name, uint32_t interval, - std::function func) { + std::function &&func) { this->set_timer_common_(component, SchedulerItem::INTERVAL, NameType::HASHED_STRING, nullptr, fnv1a_hash(name), interval, std::move(func)); } void HOT Scheduler::set_interval(Component *component, const char *name, uint32_t interval, - std::function func) { + std::function &&func) { this->set_timer_common_(component, SchedulerItem::INTERVAL, NameType::STATIC_STRING, name, 0, interval, std::move(func)); } -void HOT Scheduler::set_interval(Component *component, uint32_t id, uint32_t interval, std::function func) { +void HOT Scheduler::set_interval(Component *component, uint32_t id, uint32_t interval, std::function &&func) { this->set_timer_common_(component, SchedulerItem::INTERVAL, NameType::NUMERIC_ID, nullptr, id, interval, std::move(func)); } @@ -467,7 +473,7 @@ void HOT Scheduler::call(uint32_t now) { if (now_64 - last_print > 2000) { last_print = now_64; - std::vector> old_items; + std::vector old_items; #ifdef ESPHOME_THREAD_MULTI_ATOMICS const auto last_dbg = this->last_millis_.load(std::memory_order_relaxed); const auto major_dbg = this->millis_major_.load(std::memory_order_relaxed); @@ -480,7 +486,7 @@ void HOT Scheduler::call(uint32_t now) { // Cleanup before debug output this->cleanup_(); while (!this->items_.empty()) { - std::unique_ptr item; + SchedulerItemPtr item; { LockGuard guard{this->lock_}; item = this->pop_raw_locked_(); @@ -641,7 +647,7 @@ size_t HOT Scheduler::cleanup_() { } return this->items_.size(); } -std::unique_ptr HOT Scheduler::pop_raw_locked_() { +Scheduler::SchedulerItemPtr HOT Scheduler::pop_raw_locked_() { std::pop_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp); // Move the item out before popping - this is the item that was at the front of the heap @@ -864,8 +870,7 @@ uint64_t Scheduler::millis_64_(uint32_t now) { #endif } -bool HOT Scheduler::SchedulerItem::cmp(const std::unique_ptr &a, - const std::unique_ptr &b) { +bool HOT Scheduler::SchedulerItem::cmp(const SchedulerItemPtr &a, const SchedulerItemPtr &b) { // High bits are almost always equal (change only on 32-bit rollover ~49 days) // Optimize for common case: check low bits first when high bits are equal return (a->next_execution_high_ == b->next_execution_high_) ? (a->next_execution_low_ > b->next_execution_low_) @@ -876,7 +881,7 @@ bool HOT Scheduler::SchedulerItem::cmp(const std::unique_ptr &a, // IMPORTANT: Caller must hold the scheduler lock before calling this function. // This protects scheduler_item_pool_ from concurrent access by other threads // that may be acquiring items from the pool in set_timer_common_(). -void Scheduler::recycle_item_main_loop_(std::unique_ptr item) { +void Scheduler::recycle_item_main_loop_(SchedulerItemPtr item) { if (!item) return; @@ -919,8 +924,8 @@ void Scheduler::debug_log_timer_(const SchedulerItem *item, NameType name_type, // Helper to get or create a scheduler item from the pool // IMPORTANT: Caller must hold the scheduler lock before calling this function. -std::unique_ptr Scheduler::get_item_from_pool_locked_() { - std::unique_ptr item; +Scheduler::SchedulerItemPtr Scheduler::get_item_from_pool_locked_() { + SchedulerItemPtr item; if (!this->scheduler_item_pool_.empty()) { item = std::move(this->scheduler_item_pool_.back()); this->scheduler_item_pool_.pop_back(); @@ -928,7 +933,7 @@ std::unique_ptr Scheduler::get_item_from_pool_locked_( ESP_LOGD(TAG, "Reused item from pool (pool size now: %zu)", this->scheduler_item_pool_.size()); #endif } else { - item = make_unique(); + item = SchedulerItemPtr(new SchedulerItem()); #ifdef ESPHOME_DEBUG_SCHEDULER ESP_LOGD(TAG, "Allocated new item (pool empty)"); #endif diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h index 16b0ded312..840ee7159a 100644 --- a/esphome/core/scheduler.h +++ b/esphome/core/scheduler.h @@ -33,7 +33,7 @@ class Scheduler { // std::string overload - deprecated, use const char* or uint32_t instead // Remove before 2026.7.0 ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") - void set_timeout(Component *component, const std::string &name, uint32_t timeout, std::function func); + void set_timeout(Component *component, const std::string &name, uint32_t timeout, std::function &&func); /** Set a timeout with a const char* name. * @@ -43,11 +43,11 @@ class Scheduler { * - A static const char* variable * - A pointer with lifetime >= the scheduled task */ - void set_timeout(Component *component, const char *name, uint32_t timeout, std::function func); + void set_timeout(Component *component, const char *name, uint32_t timeout, std::function &&func); /// Set a timeout with a numeric ID (zero heap allocation) - void set_timeout(Component *component, uint32_t id, uint32_t timeout, std::function func); + void set_timeout(Component *component, uint32_t id, uint32_t timeout, std::function &&func); /// Set a timeout with an internal scheduler ID (separate namespace from component NUMERIC_ID) - void set_timeout(Component *component, InternalSchedulerID id, uint32_t timeout, std::function func) { + void set_timeout(Component *component, InternalSchedulerID id, uint32_t timeout, std::function &&func) { this->set_timer_common_(component, SchedulerItem::TIMEOUT, NameType::NUMERIC_ID_INTERNAL, nullptr, static_cast(id), timeout, std::move(func)); } @@ -62,7 +62,7 @@ class Scheduler { } ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") - void set_interval(Component *component, const std::string &name, uint32_t interval, std::function func); + void set_interval(Component *component, const std::string &name, uint32_t interval, std::function &&func); /** Set an interval with a const char* name. * @@ -72,11 +72,11 @@ class Scheduler { * - A static const char* variable * - A pointer with lifetime >= the scheduled task */ - void set_interval(Component *component, const char *name, uint32_t interval, std::function func); + void set_interval(Component *component, const char *name, uint32_t interval, std::function &&func); /// Set an interval with a numeric ID (zero heap allocation) - void set_interval(Component *component, uint32_t id, uint32_t interval, std::function func); + void set_interval(Component *component, uint32_t id, uint32_t interval, std::function &&func); /// Set an interval with an internal scheduler ID (separate namespace from component NUMERIC_ID) - void set_interval(Component *component, InternalSchedulerID id, uint32_t interval, std::function func) { + void set_interval(Component *component, InternalSchedulerID id, uint32_t interval, std::function &&func) { this->set_timer_common_(component, SchedulerItem::INTERVAL, NameType::NUMERIC_ID_INTERNAL, nullptr, static_cast(id), interval, std::move(func)); } @@ -142,6 +142,19 @@ class Scheduler { }; protected: + struct SchedulerItem; + + // Custom deleter for SchedulerItem unique_ptr that prevents the compiler from + // inlining the destructor at every destruction site. On BK7231N (Thumb-1), GCC + // inlines ~unique_ptr (~30 bytes: null check + ~std::function + + // operator delete) at every destruction site, while ESP32/ESP8266/RTL8720CF outline + // it into a single helper. This noinline deleter ensures only one copy exists. + // operator() is defined in scheduler.cpp to prevent inlining. + struct SchedulerItemDeleter { + void operator()(SchedulerItem *ptr) const noexcept; + }; + using SchedulerItemPtr = std::unique_ptr; + struct SchedulerItem { // Ordered by size to minimize padding Component *component; @@ -233,7 +246,7 @@ class Scheduler { name_type_ = type; } - static bool cmp(const std::unique_ptr &a, const std::unique_ptr &b); + static bool cmp(const SchedulerItemPtr &a, const SchedulerItemPtr &b); // Note: We use 48 bits total (32 + 16), stored in a 64-bit value for API compatibility. // The upper 16 bits of the 64-bit value are always zero, which is fine since @@ -255,7 +268,7 @@ class Scheduler { // Common implementation for both timeout and interval // name_type determines storage type: STATIC_STRING uses static_name, others use hash_or_id void set_timer_common_(Component *component, SchedulerItem::Type type, NameType name_type, const char *static_name, - uint32_t hash_or_id, uint32_t delay, std::function func, bool is_retry = false, + uint32_t hash_or_id, uint32_t delay, std::function &&func, bool is_retry = false, bool skip_cancel = false); // Common implementation for retry - Remove before 2026.8.0 @@ -276,10 +289,10 @@ class Scheduler { size_t cleanup_(); // Remove and return the front item from the heap // IMPORTANT: Caller must hold the scheduler lock before calling this function. - std::unique_ptr pop_raw_locked_(); + SchedulerItemPtr pop_raw_locked_(); // Get or create a scheduler item from the pool // IMPORTANT: Caller must hold the scheduler lock before calling this function. - std::unique_ptr get_item_from_pool_locked_(); + SchedulerItemPtr get_item_from_pool_locked_(); private: // Helper to cancel items - must be called with lock held @@ -303,9 +316,9 @@ class Scheduler { // Helper function to check if item matches criteria for cancellation // name_type determines matching: STATIC_STRING uses static_name, others use hash_or_id // IMPORTANT: Must be called with scheduler lock held - inline bool HOT matches_item_locked_(const std::unique_ptr &item, Component *component, - NameType name_type, const char *static_name, uint32_t hash_or_id, - SchedulerItem::Type type, bool match_retry, bool skip_removed = true) const { + inline bool HOT matches_item_locked_(const SchedulerItemPtr &item, Component *component, NameType name_type, + const char *static_name, uint32_t hash_or_id, SchedulerItem::Type type, + bool match_retry, bool skip_removed = true) const { // THREAD SAFETY: Check for nullptr first to prevent LoadProhibited crashes. On multi-threaded // platforms, items can be moved out of defer_queue_ during processing, leaving nullptr entries. // PR #11305 added nullptr checks in callers (mark_matching_items_removed_locked_()), but this check @@ -340,7 +353,7 @@ class Scheduler { // IMPORTANT: Only call from main loop context! Recycling clears the callback, // so calling from another thread while the callback is executing causes use-after-free. // IMPORTANT: Caller must hold the scheduler lock before calling this function. - void recycle_item_main_loop_(std::unique_ptr item); + void recycle_item_main_loop_(SchedulerItemPtr item); // Helper to perform full cleanup when too many items are cancelled void full_cleanup_removed_items_(); @@ -396,7 +409,7 @@ class Scheduler { // Merge lock acquisitions: instead of separate locks for move-out and recycle (2N+1 total), // recycle each item after re-acquiring the lock for the next iteration (N+1 total). // The lock is held across: recycle → loop condition → move-out, then released for execution. - std::unique_ptr item; + SchedulerItemPtr item; this->lock_.lock(); while (this->defer_queue_front_ < defer_queue_end) { @@ -496,9 +509,10 @@ class Scheduler { // name_type determines matching: STATIC_STRING uses static_name, others use hash_or_id // Returns the number of items marked for removal // IMPORTANT: Must be called with scheduler lock held - size_t mark_matching_items_removed_locked_(std::vector> &container, - Component *component, NameType name_type, const char *static_name, - uint32_t hash_or_id, SchedulerItem::Type type, bool match_retry) { + __attribute__((noinline)) size_t mark_matching_items_removed_locked_(std::vector &container, + Component *component, NameType name_type, + const char *static_name, uint32_t hash_or_id, + SchedulerItem::Type type, bool match_retry) { size_t count = 0; for (auto &item : container) { // Skip nullptr items (can happen in defer_queue_ when items are being processed) @@ -514,15 +528,15 @@ class Scheduler { } Mutex lock_; - std::vector> items_; - std::vector> to_add_; + std::vector items_; + std::vector to_add_; #ifndef ESPHOME_THREAD_SINGLE // Single-core platforms don't need the defer queue and save ~32 bytes of RAM // Using std::vector instead of std::deque avoids 512-byte chunked allocations // Index tracking avoids O(n) erase() calls when draining the queue each loop - std::vector> defer_queue_; // FIFO queue for defer() calls - size_t defer_queue_front_{0}; // Index of first valid item in defer_queue_ (tracks consumed items) -#endif /* ESPHOME_THREAD_SINGLE */ + std::vector defer_queue_; // FIFO queue for defer() calls + size_t defer_queue_front_{0}; // Index of first valid item in defer_queue_ (tracks consumed items) +#endif /* ESPHOME_THREAD_SINGLE */ uint32_t to_remove_{0}; // Memory pool for recycling SchedulerItem objects to reduce heap churn. @@ -533,7 +547,7 @@ class Scheduler { // - The pool significantly reduces heap fragmentation which is critical because heap allocation/deallocation // can stall the entire system, causing timing issues and dropped events for any components that need // to synchronize between tasks (see https://github.com/esphome/backlog/issues/52) - std::vector> scheduler_item_pool_; + std::vector scheduler_item_pool_; #ifdef ESPHOME_THREAD_MULTI_ATOMICS /* diff --git a/requirements.txt b/requirements.txt index be3445dceb..d22097b3ca 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ platformio==6.1.19 esptool==5.2.0 click==8.1.7 esphome-dashboard==20260210.0 -aioesphomeapi==44.0.0 +aioesphomeapi==44.1.0 zeroconf==0.148.0 puremagic==1.30 ruamel.yaml==0.19.1 # dashboard_import diff --git a/tests/component_tests/mipi_dsi/fixtures/mipi_dsi.yaml b/tests/component_tests/mipi_dsi/fixtures/mipi_dsi.yaml index 6de2bd5a77..6f76dcb1d1 100644 --- a/tests/component_tests/mipi_dsi/fixtures/mipi_dsi.yaml +++ b/tests/component_tests/mipi_dsi/fixtures/mipi_dsi.yaml @@ -22,6 +22,23 @@ display: id: p4_86 model: "WAVESHARE-P4-86-PANEL" rotation: 180 + - platform: mipi_dsi + model: custom + id: custom_id + dimensions: + width: 400 + height: 1280 + hsync_back_porch: 40 + hsync_pulse_width: 30 + hsync_front_porch: 40 + vsync_back_porch: 20 + vsync_pulse_width: 10 + vsync_front_porch: 20 + pclk_frequency: 48Mhz + lane_bit_rate: 1.2Gbps + rotation: 180 + transform: disabled + init_sequence: i2c: sda: GPIO7 scl: GPIO8 diff --git a/tests/component_tests/mipi_dsi/test_mipi_dsi_config.py b/tests/component_tests/mipi_dsi/test_mipi_dsi_config.py index d465a8c81b..92f56b5451 100644 --- a/tests/component_tests/mipi_dsi/test_mipi_dsi_config.py +++ b/tests/component_tests/mipi_dsi/test_mipi_dsi_config.py @@ -123,7 +123,8 @@ def test_code_generation( in main_cpp ) assert "set_init_sequence({224, 1, 0, 225, 1, 147, 226, 1," in main_cpp - assert "p4_nano->set_lane_bit_rate(1500);" in main_cpp + assert "p4_nano->set_lane_bit_rate(1500.0f);" in main_cpp assert "p4_nano->set_rotation(display::DISPLAY_ROTATION_90_DEGREES);" in main_cpp assert "p4_86->set_rotation(display::DISPLAY_ROTATION_0_DEGREES);" in main_cpp + assert "custom_id->set_rotation(display::DISPLAY_ROTATION_180_DEGREES);" in main_cpp # assert "backlight_id = new light::LightState(mipi_dsi_dsibacklight_id);" in main_cpp diff --git a/tests/components/light/common.yaml b/tests/components/light/common.yaml index 55525fc67f..e5fab62a79 100644 --- a/tests/components/light/common.yaml +++ b/tests/components/light/common.yaml @@ -71,6 +71,32 @@ esphome: - light.control: id: test_monochromatic_light state: on + # Test static effect name resolution at codegen time + - light.turn_on: + id: test_monochromatic_light + effect: Strobe + - light.turn_on: + id: test_monochromatic_light + effect: none + # Test resolving a different effect on the same light + - light.control: + id: test_monochromatic_light + effect: My Flicker + # Test effect: None (capitalized) + - light.control: + id: test_monochromatic_light + effect: None + # Test effect lambda with no args (on_boot has empty Ts...) + - light.turn_on: + id: test_monochromatic_light + effect: !lambda 'return "Strobe";' + # Test effect lambda with non-empty args (repeat passes uint32_t iteration) + - repeat: + count: 3 + then: + - light.turn_on: + id: test_monochromatic_light + effect: !lambda 'return iteration > 1 ? "Strobe" : "none";' - light.dim_relative: id: test_monochromatic_light relative_brightness: 5% diff --git a/tests/components/version/common.yaml b/tests/components/version/common.yaml index 7713afc37c..f9ff778e14 100644 --- a/tests/components/version/common.yaml +++ b/tests/components/version/common.yaml @@ -1,3 +1,16 @@ text_sensor: - platform: version - name: "ESPHome Version" + name: "ESPHome Version Full" + + - platform: version + name: "ESPHome Version No Timestamp" + hide_timestamp: true + + - platform: version + name: "ESPHome Version No Hash" + hide_hash: true + + - platform: version + name: "ESPHome Version Shortest" + hide_timestamp: true + hide_hash: true