mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 14:43:51 +00:00 
			
		
		
		
	Merge branch 'dev' into integration
This commit is contained in:
		| @@ -1,4 +1,5 @@ | ||||
| #ifdef USE_ESP32 | ||||
| #include "soc/soc_caps.h" | ||||
| #include "driver/gpio.h" | ||||
| #include "deep_sleep_component.h" | ||||
| #include "esphome/core/log.h" | ||||
| @@ -83,7 +84,11 @@ void DeepSleepComponent::deep_sleep_() { | ||||
|     } | ||||
|     gpio_sleep_set_direction(gpio_pin, GPIO_MODE_INPUT); | ||||
|     gpio_hold_en(gpio_pin); | ||||
| #if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP | ||||
|     // Some ESP32 variants support holding a single GPIO during deep sleep without this function | ||||
|     // For those variants, gpio_hold_en() is sufficient to hold the pin state during deep sleep | ||||
|     gpio_deep_sleep_hold_en(); | ||||
| #endif | ||||
|     bool level = !this->wakeup_pin_->is_inverted(); | ||||
|     if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) { | ||||
|       level = !level; | ||||
| @@ -120,7 +125,11 @@ void DeepSleepComponent::deep_sleep_() { | ||||
|     } | ||||
|     gpio_sleep_set_direction(gpio_pin, GPIO_MODE_INPUT); | ||||
|     gpio_hold_en(gpio_pin); | ||||
| #if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP | ||||
|     // Some ESP32 variants support holding a single GPIO during deep sleep without this function | ||||
|     // For those variants, gpio_hold_en() is sufficient to hold the pin state during deep sleep | ||||
|     gpio_deep_sleep_hold_en(); | ||||
| #endif | ||||
|     bool level = !this->wakeup_pin_->is_inverted(); | ||||
|     if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) { | ||||
|       level = !level; | ||||
|   | ||||
| @@ -5,9 +5,14 @@ from esphome import automation | ||||
| import esphome.codegen as cg | ||||
| from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import CONF_ENABLE_ON_BOOT, CONF_ESPHOME, CONF_ID, CONF_NAME | ||||
| from esphome.const import ( | ||||
|     CONF_ENABLE_ON_BOOT, | ||||
|     CONF_ESPHOME, | ||||
|     CONF_ID, | ||||
|     CONF_NAME, | ||||
|     CONF_NAME_ADD_MAC_SUFFIX, | ||||
| ) | ||||
| from esphome.core import CORE, TimePeriod | ||||
| from esphome.core.config import CONF_NAME_ADD_MAC_SUFFIX | ||||
| import esphome.final_validate as fv | ||||
|  | ||||
| DEPENDENCIES = ["esp32"] | ||||
|   | ||||
| @@ -298,7 +298,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ | ||||
|         // This should not happen but lets log it in case it does | ||||
|         // because it means we have a bad assumption about how the | ||||
|         // ESP BT stack works. | ||||
|         ESP_LOGE(TAG, "[%d] [%s] Got ESP_GATTC_OPEN_EVT in %s state (status=%d)", this->connection_index_, | ||||
|         ESP_LOGE(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT in %s state (status=%d)", this->connection_index_, | ||||
|                  this->address_str_.c_str(), espbt::client_state_to_string(this->state_), param->open.status); | ||||
|       } | ||||
|       if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) { | ||||
|   | ||||
| @@ -1,7 +1,10 @@ | ||||
| #include "http_request_host.h" | ||||
|  | ||||
| #ifdef USE_HOST | ||||
|  | ||||
| #define USE_HTTP_REQUEST_HOST_H | ||||
| #define CPPHTTPLIB_NO_EXCEPTIONS | ||||
| #include "httplib.h" | ||||
| #include "http_request_host.h" | ||||
|  | ||||
| #include <regex> | ||||
| #include "esphome/components/network/util.h" | ||||
| #include "esphome/components/watchdog/watchdog.h" | ||||
|   | ||||
| @@ -1,11 +1,7 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "http_request.h" | ||||
|  | ||||
| #ifdef USE_HOST | ||||
|  | ||||
| #define CPPHTTPLIB_NO_EXCEPTIONS | ||||
| #include "httplib.h" | ||||
| #include "http_request.h" | ||||
| namespace esphome { | ||||
| namespace http_request { | ||||
|  | ||||
|   | ||||
| @@ -3,12 +3,10 @@ | ||||
| /** | ||||
|  * NOTE: This is a copy of httplib.h from https://github.com/yhirose/cpp-httplib | ||||
|  * | ||||
|  * It has been modified only to add ifdefs for USE_HOST. While it contains many functions unused in ESPHome, | ||||
|  * It has been modified to add ifdefs for USE_HOST. While it contains many functions unused in ESPHome, | ||||
|  * it was considered preferable to use it with as few changes as possible, to facilitate future updates. | ||||
|  */ | ||||
|  | ||||
| #include "esphome/core/defines.h" | ||||
|  | ||||
| // | ||||
| //  httplib.h | ||||
| // | ||||
| @@ -17,6 +15,11 @@ | ||||
| // | ||||
|  | ||||
| #ifdef USE_HOST | ||||
| // Prevent this code being included in main.cpp | ||||
| #ifdef USE_HTTP_REQUEST_HOST_H | ||||
|  | ||||
| #include "esphome/core/defines.h" | ||||
|  | ||||
| #ifndef CPPHTTPLIB_HTTPLIB_H | ||||
| #define CPPHTTPLIB_HTTPLIB_H | ||||
|  | ||||
| @@ -9687,5 +9690,6 @@ inline SSL_CTX *Client::ssl_context() const { | ||||
| #endif | ||||
|  | ||||
| #endif  // CPPHTTPLIB_HTTPLIB_H | ||||
| #endif  // USE_HTTP_REQUEST_HOST_H | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -24,7 +24,7 @@ from ..defines import ( | ||||
|     literal, | ||||
| ) | ||||
| from ..lv_validation import ( | ||||
|     lv_angle, | ||||
|     lv_angle_degrees, | ||||
|     lv_bool, | ||||
|     lv_color, | ||||
|     lv_image, | ||||
| @@ -395,15 +395,15 @@ ARC_PROPS = { | ||||
|     DRAW_OPA_SCHEMA.extend( | ||||
|         { | ||||
|             cv.Required(CONF_RADIUS): pixels, | ||||
|             cv.Required(CONF_START_ANGLE): lv_angle, | ||||
|             cv.Required(CONF_END_ANGLE): lv_angle, | ||||
|             cv.Required(CONF_START_ANGLE): lv_angle_degrees, | ||||
|             cv.Required(CONF_END_ANGLE): lv_angle_degrees, | ||||
|         } | ||||
|     ).extend({cv.Optional(prop): validator for prop, validator in ARC_PROPS.items()}), | ||||
| ) | ||||
| async def canvas_draw_arc(config, action_id, template_arg, args): | ||||
|     radius = await size.process(config[CONF_RADIUS]) | ||||
|     start_angle = await lv_angle.process(config[CONF_START_ANGLE]) | ||||
|     end_angle = await lv_angle.process(config[CONF_END_ANGLE]) | ||||
|     start_angle = await lv_angle_degrees.process(config[CONF_START_ANGLE]) | ||||
|     end_angle = await lv_angle_degrees.process(config[CONF_END_ANGLE]) | ||||
|  | ||||
|     async def do_draw_arc(w: Widget, x, y, dsc_addr): | ||||
|         lv.canvas_draw_arc(w.obj, x, y, radius, start_angle, end_angle, dsc_addr) | ||||
|   | ||||
| @@ -14,7 +14,6 @@ from esphome.const import ( | ||||
|     CONF_VALUE, | ||||
|     CONF_WIDTH, | ||||
| ) | ||||
| from esphome.cpp_generator import IntLiteral | ||||
|  | ||||
| from ..automation import action_to_code | ||||
| from ..defines import ( | ||||
| @@ -32,7 +31,7 @@ from ..helpers import add_lv_use, lvgl_components_required | ||||
| from ..lv_validation import ( | ||||
|     get_end_value, | ||||
|     get_start_value, | ||||
|     lv_angle, | ||||
|     lv_angle_degrees, | ||||
|     lv_bool, | ||||
|     lv_color, | ||||
|     lv_float, | ||||
| @@ -163,7 +162,7 @@ SCALE_SCHEMA = cv.Schema( | ||||
|         cv.Optional(CONF_RANGE_FROM, default=0.0): cv.float_, | ||||
|         cv.Optional(CONF_RANGE_TO, default=100.0): cv.float_, | ||||
|         cv.Optional(CONF_ANGLE_RANGE, default=270): cv.int_range(0, 360), | ||||
|         cv.Optional(CONF_ROTATION): lv_angle, | ||||
|         cv.Optional(CONF_ROTATION): lv_angle_degrees, | ||||
|         cv.Optional(CONF_INDICATORS): cv.ensure_list(INDICATOR_SCHEMA), | ||||
|     } | ||||
| ) | ||||
| @@ -188,9 +187,7 @@ class MeterType(WidgetType): | ||||
|         for scale_conf in config.get(CONF_SCALES, ()): | ||||
|             rotation = 90 + (360 - scale_conf[CONF_ANGLE_RANGE]) / 2 | ||||
|             if CONF_ROTATION in scale_conf: | ||||
|                 rotation = await lv_angle.process(scale_conf[CONF_ROTATION]) | ||||
|                 if isinstance(rotation, IntLiteral): | ||||
|                     rotation = int(str(rotation)) // 10 | ||||
|                 rotation = await lv_angle_degrees.process(scale_conf[CONF_ROTATION]) | ||||
|             with LocalVariable( | ||||
|                 "meter_var", "lv_meter_scale_t", lv_expr.meter_add_scale(var) | ||||
|             ) as meter_var: | ||||
|   | ||||
| @@ -255,4 +255,233 @@ DriverChip( | ||||
|     ), | ||||
| ) | ||||
|  | ||||
| DriverChip( | ||||
|     "JC3636W518V2", | ||||
|     height=360, | ||||
|     width=360, | ||||
|     offset_height=1, | ||||
|     draw_rounding=1, | ||||
|     cs_pin=10, | ||||
|     reset_pin=47, | ||||
|     invert_colors=True, | ||||
|     color_order=MODE_RGB, | ||||
|     bus_mode=TYPE_QUAD, | ||||
|     data_rate="40MHz", | ||||
|     initsequence=( | ||||
|         (0xF0, 0x28), | ||||
|         (0xF2, 0x28), | ||||
|         (0x73, 0xF0), | ||||
|         (0x7C, 0xD1), | ||||
|         (0x83, 0xE0), | ||||
|         (0x84, 0x61), | ||||
|         (0xF2, 0x82), | ||||
|         (0xF0, 0x00), | ||||
|         (0xF0, 0x01), | ||||
|         (0xF1, 0x01), | ||||
|         (0xB0, 0x56), | ||||
|         (0xB1, 0x4D), | ||||
|         (0xB2, 0x24), | ||||
|         (0xB4, 0x87), | ||||
|         (0xB5, 0x44), | ||||
|         (0xB6, 0x8B), | ||||
|         (0xB7, 0x40), | ||||
|         (0xB8, 0x86), | ||||
|         (0xBA, 0x00), | ||||
|         (0xBB, 0x08), | ||||
|         (0xBC, 0x08), | ||||
|         (0xBD, 0x00), | ||||
|         (0xC0, 0x80), | ||||
|         (0xC1, 0x10), | ||||
|         (0xC2, 0x37), | ||||
|         (0xC3, 0x80), | ||||
|         (0xC4, 0x10), | ||||
|         (0xC5, 0x37), | ||||
|         (0xC6, 0xA9), | ||||
|         (0xC7, 0x41), | ||||
|         (0xC8, 0x01), | ||||
|         (0xC9, 0xA9), | ||||
|         (0xCA, 0x41), | ||||
|         (0xCB, 0x01), | ||||
|         (0xD0, 0x91), | ||||
|         (0xD1, 0x68), | ||||
|         (0xD2, 0x68), | ||||
|         (0xF5, 0x00, 0xA5), | ||||
|         (0xDD, 0x4F), | ||||
|         (0xDE, 0x4F), | ||||
|         (0xF1, 0x10), | ||||
|         (0xF0, 0x00), | ||||
|         (0xF0, 0x02), | ||||
|         ( | ||||
|             0xE0, | ||||
|             0xF0, | ||||
|             0x0A, | ||||
|             0x10, | ||||
|             0x09, | ||||
|             0x09, | ||||
|             0x36, | ||||
|             0x35, | ||||
|             0x33, | ||||
|             0x4A, | ||||
|             0x29, | ||||
|             0x15, | ||||
|             0x15, | ||||
|             0x2E, | ||||
|             0x34, | ||||
|         ), | ||||
|         ( | ||||
|             0xE1, | ||||
|             0xF0, | ||||
|             0x0A, | ||||
|             0x0F, | ||||
|             0x08, | ||||
|             0x08, | ||||
|             0x05, | ||||
|             0x34, | ||||
|             0x33, | ||||
|             0x4A, | ||||
|             0x39, | ||||
|             0x15, | ||||
|             0x15, | ||||
|             0x2D, | ||||
|             0x33, | ||||
|         ), | ||||
|         (0xF0, 0x10), | ||||
|         (0xF3, 0x10), | ||||
|         (0xE0, 0x07), | ||||
|         (0xE1, 0x00), | ||||
|         (0xE2, 0x00), | ||||
|         (0xE3, 0x00), | ||||
|         (0xE4, 0xE0), | ||||
|         (0xE5, 0x06), | ||||
|         (0xE6, 0x21), | ||||
|         (0xE7, 0x01), | ||||
|         (0xE8, 0x05), | ||||
|         (0xE9, 0x02), | ||||
|         (0xEA, 0xDA), | ||||
|         (0xEB, 0x00), | ||||
|         (0xEC, 0x00), | ||||
|         (0xED, 0x0F), | ||||
|         (0xEE, 0x00), | ||||
|         (0xEF, 0x00), | ||||
|         (0xF8, 0x00), | ||||
|         (0xF9, 0x00), | ||||
|         (0xFA, 0x00), | ||||
|         (0xFB, 0x00), | ||||
|         (0xFC, 0x00), | ||||
|         (0xFD, 0x00), | ||||
|         (0xFE, 0x00), | ||||
|         (0xFF, 0x00), | ||||
|         (0x60, 0x40), | ||||
|         (0x61, 0x04), | ||||
|         (0x62, 0x00), | ||||
|         (0x63, 0x42), | ||||
|         (0x64, 0xD9), | ||||
|         (0x65, 0x00), | ||||
|         (0x66, 0x00), | ||||
|         (0x67, 0x00), | ||||
|         (0x68, 0x00), | ||||
|         (0x69, 0x00), | ||||
|         (0x6A, 0x00), | ||||
|         (0x6B, 0x00), | ||||
|         (0x70, 0x40), | ||||
|         (0x71, 0x03), | ||||
|         (0x72, 0x00), | ||||
|         (0x73, 0x42), | ||||
|         (0x74, 0xD8), | ||||
|         (0x75, 0x00), | ||||
|         (0x76, 0x00), | ||||
|         (0x77, 0x00), | ||||
|         (0x78, 0x00), | ||||
|         (0x79, 0x00), | ||||
|         (0x7A, 0x00), | ||||
|         (0x7B, 0x00), | ||||
|         (0x80, 0x48), | ||||
|         (0x81, 0x00), | ||||
|         (0x82, 0x06), | ||||
|         (0x83, 0x02), | ||||
|         (0x84, 0xD6), | ||||
|         (0x85, 0x04), | ||||
|         (0x86, 0x00), | ||||
|         (0x87, 0x00), | ||||
|         (0x88, 0x48), | ||||
|         (0x89, 0x00), | ||||
|         (0x8A, 0x08), | ||||
|         (0x8B, 0x02), | ||||
|         (0x8C, 0xD8), | ||||
|         (0x8D, 0x04), | ||||
|         (0x8E, 0x00), | ||||
|         (0x8F, 0x00), | ||||
|         (0x90, 0x48), | ||||
|         (0x91, 0x00), | ||||
|         (0x92, 0x0A), | ||||
|         (0x93, 0x02), | ||||
|         (0x94, 0xDA), | ||||
|         (0x95, 0x04), | ||||
|         (0x96, 0x00), | ||||
|         (0x97, 0x00), | ||||
|         (0x98, 0x48), | ||||
|         (0x99, 0x00), | ||||
|         (0x9A, 0x0C), | ||||
|         (0x9B, 0x02), | ||||
|         (0x9C, 0xDC), | ||||
|         (0x9D, 0x04), | ||||
|         (0x9E, 0x00), | ||||
|         (0x9F, 0x00), | ||||
|         (0xA0, 0x48), | ||||
|         (0xA1, 0x00), | ||||
|         (0xA2, 0x05), | ||||
|         (0xA3, 0x02), | ||||
|         (0xA4, 0xD5), | ||||
|         (0xA5, 0x04), | ||||
|         (0xA6, 0x00), | ||||
|         (0xA7, 0x00), | ||||
|         (0xA8, 0x48), | ||||
|         (0xA9, 0x00), | ||||
|         (0xAA, 0x07), | ||||
|         (0xAB, 0x02), | ||||
|         (0xAC, 0xD7), | ||||
|         (0xAD, 0x04), | ||||
|         (0xAE, 0x00), | ||||
|         (0xAF, 0x00), | ||||
|         (0xB0, 0x48), | ||||
|         (0xB1, 0x00), | ||||
|         (0xB2, 0x09), | ||||
|         (0xB3, 0x02), | ||||
|         (0xB4, 0xD9), | ||||
|         (0xB5, 0x04), | ||||
|         (0xB6, 0x00), | ||||
|         (0xB7, 0x00), | ||||
|         (0xB8, 0x48), | ||||
|         (0xB9, 0x00), | ||||
|         (0xBA, 0x0B), | ||||
|         (0xBB, 0x02), | ||||
|         (0xBC, 0xDB), | ||||
|         (0xBD, 0x04), | ||||
|         (0xBE, 0x00), | ||||
|         (0xBF, 0x00), | ||||
|         (0xC0, 0x10), | ||||
|         (0xC1, 0x47), | ||||
|         (0xC2, 0x56), | ||||
|         (0xC3, 0x65), | ||||
|         (0xC4, 0x74), | ||||
|         (0xC5, 0x88), | ||||
|         (0xC6, 0x99), | ||||
|         (0xC7, 0x01), | ||||
|         (0xC8, 0xBB), | ||||
|         (0xC9, 0xAA), | ||||
|         (0xD0, 0x10), | ||||
|         (0xD1, 0x47), | ||||
|         (0xD2, 0x56), | ||||
|         (0xD3, 0x65), | ||||
|         (0xD4, 0x74), | ||||
|         (0xD5, 0x88), | ||||
|         (0xD6, 0x99), | ||||
|         (0xD7, 0x01), | ||||
|         (0xD8, 0xBB), | ||||
|         (0xD9, 0xAA), | ||||
|         (0xF3, 0x01), | ||||
|         (0xF0, 0x00), | ||||
|     ), | ||||
| ) | ||||
|  | ||||
| models = {} | ||||
|   | ||||
| @@ -119,8 +119,8 @@ async def to_code(config: ConfigType) -> None: | ||||
|     cg.add_platformio_option( | ||||
|         "platform_packages", | ||||
|         [ | ||||
|             "platformio/framework-zephyr@https://github.com/tomaszduda23/framework-sdk-nrf/archive/refs/tags/v2.6.1-4.zip", | ||||
|             "platformio/toolchain-gccarmnoneeabi@https://github.com/tomaszduda23/toolchain-sdk-ng/archive/refs/tags/v0.16.1-1.zip", | ||||
|             "platformio/framework-zephyr@https://github.com/tomaszduda23/framework-sdk-nrf/archive/refs/tags/v2.6.1-7.zip", | ||||
|             "platformio/toolchain-gccarmnoneeabi@https://github.com/tomaszduda23/toolchain-sdk-ng/archive/refs/tags/v0.17.4-0.zip", | ||||
|         ], | ||||
|     ) | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import modbus, sensor | ||||
| from esphome.components.atm90e32.sensor import CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_ACTIVE_POWER, | ||||
| @@ -12,7 +11,10 @@ from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     CONF_IMPORT_ACTIVE_ENERGY, | ||||
|     CONF_IMPORT_REACTIVE_ENERGY, | ||||
|     CONF_PHASE_A, | ||||
|     CONF_PHASE_ANGLE, | ||||
|     CONF_PHASE_B, | ||||
|     CONF_PHASE_C, | ||||
|     CONF_POWER_FACTOR, | ||||
|     CONF_REACTIVE_POWER, | ||||
|     CONF_TOTAL_POWER, | ||||
|   | ||||
| @@ -52,9 +52,9 @@ def default_url(config: ConfigType) -> ConfigType: | ||||
|     config = config.copy() | ||||
|     if config[CONF_VERSION] == 1: | ||||
|         if CONF_CSS_URL not in config: | ||||
|             config[CONF_CSS_URL] = "https://esphome.io/_static/webserver-v1.min.css" | ||||
|             config[CONF_CSS_URL] = "https://oi.esphome.io/v1/webserver-v1.min.css" | ||||
|         if CONF_JS_URL not in config: | ||||
|             config[CONF_JS_URL] = "https://esphome.io/_static/webserver-v1.min.js" | ||||
|             config[CONF_JS_URL] = "https://oi.esphome.io/v1/webserver-v1.min.js" | ||||
|     if config[CONF_VERSION] == 2: | ||||
|         if CONF_CSS_URL not in config: | ||||
|             config[CONF_CSS_URL] = "" | ||||
|   | ||||
| @@ -173,14 +173,14 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { | ||||
|  | ||||
| #if USE_WEBSERVER_VERSION == 1 | ||||
|   /** Set the URL to the CSS <link> that's sent to each client. Defaults to | ||||
|    * https://esphome.io/_static/webserver-v1.min.css | ||||
|    * https://oi.esphome.io/v1/webserver-v1.min.css | ||||
|    * | ||||
|    * @param css_url The url to the web server stylesheet. | ||||
|    */ | ||||
|   void set_css_url(const char *css_url); | ||||
|  | ||||
|   /** Set the URL to the script that's embedded in the index page. Defaults to | ||||
|    * https://esphome.io/_static/webserver-v1.min.js | ||||
|    * https://oi.esphome.io/v1/webserver-v1.min.js | ||||
|    * | ||||
|    * @param js_url The url to the web server script. | ||||
|    */ | ||||
|   | ||||
| @@ -253,7 +253,7 @@ bool AsyncWebServerRequest::authenticate(const char *username, const char *passw | ||||
|   esp_crypto_base64_encode(reinterpret_cast<uint8_t *>(digest.get()), n, &out, | ||||
|                            reinterpret_cast<const uint8_t *>(user_info.c_str()), user_info.size()); | ||||
|  | ||||
|   return strncmp(digest.get(), auth_str + auth_prefix_len, auth.value().size() - auth_prefix_len) == 0; | ||||
|   return strcmp(digest.get(), auth_str + auth_prefix_len) == 0; | ||||
| } | ||||
|  | ||||
| void AsyncWebServerRequest::requestAuthentication(const char *realm) const { | ||||
|   | ||||
| @@ -1112,8 +1112,8 @@ voltage = float_with_unit("voltage", "(v|V|volt|Volts)?") | ||||
| distance = float_with_unit("distance", "(m)") | ||||
| framerate = float_with_unit("framerate", "(FPS|fps|Fps|FpS|Hz)") | ||||
| angle = float_with_unit("angle", "(°|deg)", optional_unit=True) | ||||
| _temperature_c = float_with_unit("temperature", "(°C|° C|°|C)?") | ||||
| _temperature_k = float_with_unit("temperature", "(° K|° K|K)?") | ||||
| _temperature_c = float_with_unit("temperature", "(°C|° C|C|°)?") | ||||
| _temperature_k = float_with_unit("temperature", "(°K|° K|K)?") | ||||
| _temperature_f = float_with_unit("temperature", "(°F|° F|F)?") | ||||
| decibel = float_with_unit("decibel", "(dB|dBm|db|dbm)", optional_unit=True) | ||||
| pressure = float_with_unit("pressure", "(bar|Bar)", optional_unit=True) | ||||
|   | ||||
| @@ -5,6 +5,8 @@ | ||||
| #include "esphome/core/hal.h" | ||||
| #include "esphome/core/defines.h" | ||||
| #include "esphome/core/preferences.h" | ||||
| #include "esphome/core/scheduler.h" | ||||
| #include "esphome/core/application.h" | ||||
|  | ||||
| #include <vector> | ||||
|  | ||||
| @@ -158,7 +160,16 @@ template<typename... Ts> class DelayAction : public Action<Ts...>, public Compon | ||||
|   void play_complex(Ts... x) override { | ||||
|     auto f = std::bind(&DelayAction<Ts...>::play_next_, this, x...); | ||||
|     this->num_running_++; | ||||
|     this->set_timeout("delay", this->delay_.value(x...), f); | ||||
|  | ||||
|     // If num_running_ > 1, we have multiple instances running in parallel | ||||
|     // In single/restart/queued modes, only one instance runs at a time | ||||
|     // Parallel mode uses skip_cancel=true to allow multiple delays to coexist | ||||
|     // WARNING: This can accumulate delays if scripts are triggered faster than they complete! | ||||
|     // Users should set max_runs on parallel scripts to limit concurrent executions. | ||||
|     // Issue #10264: This is a workaround for parallel script delays interfering with each other. | ||||
|     App.scheduler.set_timer_common_(this, Scheduler::SchedulerItem::TIMEOUT, | ||||
|                                     /* is_static_string= */ true, "delay", this->delay_.value(x...), std::move(f), | ||||
|                                     /* is_retry= */ false, /* skip_cancel= */ this->num_running_ > 1); | ||||
|   } | ||||
|   float get_setup_priority() const override { return setup_priority::HARDWARE; } | ||||
|  | ||||
|   | ||||
| @@ -85,7 +85,23 @@ class EntityBase { | ||||
|   // Set has_state - for components that need to manually set this | ||||
|   void set_has_state(bool state) { this->flags_.has_state = state; } | ||||
|  | ||||
|   // Get a unique hash for preferences that includes device_id | ||||
|   /** | ||||
|    * @brief Get a unique hash for storing preferences/settings for this entity. | ||||
|    * | ||||
|    * This method returns a hash that uniquely identifies the entity for the purpose of | ||||
|    * storing preferences (such as calibration, state, etc.). Unlike get_object_id_hash(), | ||||
|    * this hash also incorporates the device_id (if devices are enabled), ensuring uniqueness | ||||
|    * across multiple devices that may have entities with the same object_id. | ||||
|    * | ||||
|    * Use this method when storing or retrieving preferences/settings that should be unique | ||||
|    * per device-entity pair. Use get_object_id_hash() when you need a hash that identifies | ||||
|    * the entity regardless of the device it belongs to. | ||||
|    * | ||||
|    * For backward compatibility, if device_id is 0 (the main device), the hash is unchanged | ||||
|    * from previous versions, so existing single-device configurations will continue to work. | ||||
|    * | ||||
|    * @return uint32_t The unique hash for preferences, including device_id if available. | ||||
|    */ | ||||
|   uint32_t get_preference_hash() { | ||||
| #ifdef USE_DEVICES | ||||
|     // Combine object_id_hash with device_id to ensure uniqueness across devices | ||||
|   | ||||
| @@ -65,14 +65,17 @@ static void validate_static_string(const char *name) { | ||||
|  | ||||
| // Common implementation for both timeout and interval | ||||
| void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type type, bool is_static_string, | ||||
|                                       const void *name_ptr, uint32_t delay, std::function<void()> func, bool is_retry) { | ||||
|                                       const void *name_ptr, uint32_t delay, std::function<void()> func, bool is_retry, | ||||
|                                       bool skip_cancel) { | ||||
|   // Get the name as const char* | ||||
|   const char *name_cstr = this->get_name_cstr_(is_static_string, name_ptr); | ||||
|  | ||||
|   if (delay == SCHEDULER_DONT_RUN) { | ||||
|     // Still need to cancel existing timer if name is not empty | ||||
|     LockGuard guard{this->lock_}; | ||||
|     this->cancel_item_locked_(component, name_cstr, type); | ||||
|     if (!skip_cancel) { | ||||
|       LockGuard guard{this->lock_}; | ||||
|       this->cancel_item_locked_(component, name_cstr, type); | ||||
|     } | ||||
|     return; | ||||
|   } | ||||
|  | ||||
| @@ -97,7 +100,9 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type | ||||
|   if (delay == 0 && type == SchedulerItem::TIMEOUT) { | ||||
|     // Put in defer queue for guaranteed FIFO execution | ||||
|     LockGuard guard{this->lock_}; | ||||
|     this->cancel_item_locked_(component, name_cstr, type); | ||||
|     if (!skip_cancel) { | ||||
|       this->cancel_item_locked_(component, name_cstr, type); | ||||
|     } | ||||
|     this->defer_queue_.push_back(std::move(item)); | ||||
|     return; | ||||
|   } | ||||
| @@ -150,9 +155,11 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // If name is provided, do atomic cancel-and-add | ||||
|   // If name is provided, do atomic cancel-and-add (unless skip_cancel is true) | ||||
|   // Cancel existing items | ||||
|   this->cancel_item_locked_(component, name_cstr, type); | ||||
|   if (!skip_cancel) { | ||||
|     this->cancel_item_locked_(component, name_cstr, type); | ||||
|   } | ||||
|   // Add new item directly to to_add_ | ||||
|   // since we have the lock held | ||||
|   this->to_add_.push_back(std::move(item)); | ||||
|   | ||||
| @@ -21,8 +21,13 @@ struct RetryArgs; | ||||
| void retry_handler(const std::shared_ptr<RetryArgs> &args); | ||||
|  | ||||
| class Scheduler { | ||||
|   // Allow retry_handler to access protected members | ||||
|   // Allow retry_handler to access protected members for internal retry mechanism | ||||
|   friend void ::esphome::retry_handler(const std::shared_ptr<RetryArgs> &args); | ||||
|   // Allow DelayAction to call set_timer_common_ with skip_cancel=true for parallel script delays. | ||||
|   // This is needed to fix issue #10264 where parallel scripts with delays interfere with each other. | ||||
|   // We use friend instead of a public API because skip_cancel is dangerous - it can cause delays | ||||
|   // to accumulate and overload the scheduler if misused. | ||||
|   template<typename... Ts> friend class DelayAction; | ||||
|  | ||||
|  public: | ||||
|   // Public API - accepts std::string for backward compatibility | ||||
| @@ -184,7 +189,7 @@ class Scheduler { | ||||
|  | ||||
|   // Common implementation for both timeout and interval | ||||
|   void set_timer_common_(Component *component, SchedulerItem::Type type, bool is_static_string, const void *name_ptr, | ||||
|                          uint32_t delay, std::function<void()> func, bool is_retry = false); | ||||
|                          uint32_t delay, std::function<void()> func, bool is_retry = false, bool skip_cancel = false); | ||||
|  | ||||
|   // Common implementation for retry | ||||
|   void set_retry_common_(Component *component, bool is_static_string, const void *name_ptr, uint32_t initial_wait_time, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user