mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Merge branch 'disable_touch_pad_read_filtered' into integration
This commit is contained in:
		| @@ -1,6 +1,6 @@ | ||||
| from esphome import pins | ||||
| import esphome.codegen as cg | ||||
| from esphome.components.esp32 import get_esp32_variant | ||||
| from esphome.components.esp32 import VARIANT_ESP32P4, get_esp32_variant | ||||
| from esphome.components.esp32.const import ( | ||||
|     VARIANT_ESP32, | ||||
|     VARIANT_ESP32C2, | ||||
| @@ -140,6 +140,16 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = { | ||||
|         9: adc_channel_t.ADC_CHANNEL_8, | ||||
|         10: adc_channel_t.ADC_CHANNEL_9, | ||||
|     }, | ||||
|     VARIANT_ESP32P4: { | ||||
|         16: adc_channel_t.ADC_CHANNEL_0, | ||||
|         17: adc_channel_t.ADC_CHANNEL_1, | ||||
|         18: adc_channel_t.ADC_CHANNEL_2, | ||||
|         19: adc_channel_t.ADC_CHANNEL_3, | ||||
|         20: adc_channel_t.ADC_CHANNEL_4, | ||||
|         21: adc_channel_t.ADC_CHANNEL_5, | ||||
|         22: adc_channel_t.ADC_CHANNEL_6, | ||||
|         23: adc_channel_t.ADC_CHANNEL_7, | ||||
|     }, | ||||
| } | ||||
|  | ||||
| # pin to adc2 channel mapping | ||||
| @@ -198,6 +208,14 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = { | ||||
|         19: adc_channel_t.ADC_CHANNEL_8, | ||||
|         20: adc_channel_t.ADC_CHANNEL_9, | ||||
|     }, | ||||
|     VARIANT_ESP32P4: { | ||||
|         49: adc_channel_t.ADC_CHANNEL_0, | ||||
|         50: adc_channel_t.ADC_CHANNEL_1, | ||||
|         51: adc_channel_t.ADC_CHANNEL_2, | ||||
|         52: adc_channel_t.ADC_CHANNEL_3, | ||||
|         53: adc_channel_t.ADC_CHANNEL_4, | ||||
|         54: adc_channel_t.ADC_CHANNEL_5, | ||||
|     }, | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -136,8 +136,8 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage | ||||
|   adc_oneshot_unit_handle_t adc_handle_{nullptr}; | ||||
|   adc_cali_handle_t calibration_handle_{nullptr}; | ||||
|   adc_atten_t attenuation_{ADC_ATTEN_DB_0}; | ||||
|   adc_channel_t channel_; | ||||
|   adc_unit_t adc_unit_; | ||||
|   adc_channel_t channel_{}; | ||||
|   adc_unit_t adc_unit_{}; | ||||
|   struct SetupFlags { | ||||
|     uint8_t init_complete : 1; | ||||
|     uint8_t config_complete : 1; | ||||
|   | ||||
| @@ -72,10 +72,9 @@ void ADCSensor::setup() { | ||||
|   // Initialize ADC calibration | ||||
|   if (this->calibration_handle_ == nullptr) { | ||||
|     adc_cali_handle_t handle = nullptr; | ||||
|     esp_err_t err; | ||||
|  | ||||
| #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ | ||||
|     USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 | ||||
|     USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 | ||||
|     // RISC-V variants and S3 use curve fitting calibration | ||||
|     adc_cali_curve_fitting_config_t cali_config = {};  // Zero initialize first | ||||
| #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) | ||||
| @@ -187,7 +186,7 @@ float ADCSensor::sample_fixed_attenuation_() { | ||||
|       ESP_LOGW(TAG, "ADC calibration conversion failed with error %d, disabling calibration", err); | ||||
|       if (this->calibration_handle_ != nullptr) { | ||||
| #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ | ||||
|     USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 | ||||
|     USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 | ||||
|         adc_cali_delete_scheme_curve_fitting(this->calibration_handle_); | ||||
| #else   // Other ESP32 variants use line fitting calibration | ||||
|         adc_cali_delete_scheme_line_fitting(this->calibration_handle_); | ||||
| @@ -220,7 +219,7 @@ float ADCSensor::sample_autorange_() { | ||||
|     if (this->calibration_handle_ != nullptr) { | ||||
|       // Delete old calibration handle | ||||
| #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ | ||||
|     USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 | ||||
|     USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 | ||||
|       adc_cali_delete_scheme_curve_fitting(this->calibration_handle_); | ||||
| #else | ||||
|       adc_cali_delete_scheme_line_fitting(this->calibration_handle_); | ||||
| @@ -232,7 +231,7 @@ float ADCSensor::sample_autorange_() { | ||||
|     adc_cali_handle_t handle = nullptr; | ||||
|  | ||||
| #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ | ||||
|     USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 | ||||
|     USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 | ||||
|     adc_cali_curve_fitting_config_t cali_config = {}; | ||||
| #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) | ||||
|     cali_config.chan = this->channel_; | ||||
| @@ -261,7 +260,7 @@ float ADCSensor::sample_autorange_() { | ||||
|       ESP_LOGW(TAG, "ADC read failed in autorange with error %d", err); | ||||
|       if (handle != nullptr) { | ||||
| #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ | ||||
|     USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 | ||||
|     USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 | ||||
|         adc_cali_delete_scheme_curve_fitting(handle); | ||||
| #else | ||||
|         adc_cali_delete_scheme_line_fitting(handle); | ||||
| @@ -281,7 +280,7 @@ float ADCSensor::sample_autorange_() { | ||||
|       } | ||||
|       // Clean up calibration handle | ||||
| #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ | ||||
|     USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 | ||||
|     USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 | ||||
|       adc_cali_delete_scheme_curve_fitting(handle); | ||||
| #else | ||||
|       adc_cali_delete_scheme_line_fitting(handle); | ||||
|   | ||||
| @@ -300,7 +300,7 @@ class APIConnection : public APIServerConnection { | ||||
|  | ||||
| #ifdef USE_API_HOMEASSISTANT_STATES | ||||
|   void process_state_subscriptions_(); | ||||
| #endif  // USE_API_HOMEASSISTANT_STATES | ||||
| #endif | ||||
|  | ||||
|   // Non-template helper to encode any ProtoMessage | ||||
|   static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint8_t message_type, APIConnection *conn, | ||||
|   | ||||
| @@ -516,6 +516,7 @@ def binary_sensor_schema( | ||||
|     icon: str = cv.UNDEFINED, | ||||
|     entity_category: str = cv.UNDEFINED, | ||||
|     device_class: str = cv.UNDEFINED, | ||||
|     filters: list = cv.UNDEFINED, | ||||
| ) -> cv.Schema: | ||||
|     schema = {} | ||||
|  | ||||
| @@ -527,6 +528,7 @@ def binary_sensor_schema( | ||||
|         (CONF_ICON, icon, cv.icon), | ||||
|         (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category), | ||||
|         (CONF_DEVICE_CLASS, device_class, validate_device_class), | ||||
|         (CONF_FILTERS, filters, validate_filters), | ||||
|     ]: | ||||
|         if default is not cv.UNDEFINED: | ||||
|             schema[cv.Optional(key, default=default)] = validator | ||||
|   | ||||
| @@ -313,7 +313,7 @@ def _format_framework_espidf_version( | ||||
| RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 2, 1) | ||||
| # The platform-espressif32 version to use for arduino frameworks | ||||
| #  - https://github.com/pioarduino/platform-espressif32/releases | ||||
| ARDUINO_PLATFORM_VERSION = cv.Version(54, 3, 21) | ||||
| ARDUINO_PLATFORM_VERSION = cv.Version(54, 3, 21, "1") | ||||
|  | ||||
| # The default/recommended esp-idf framework version | ||||
| #  - https://github.com/espressif/esp-idf/releases | ||||
| @@ -322,7 +322,7 @@ RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(5, 4, 2) | ||||
| # The platformio/espressif32 version to use for esp-idf frameworks | ||||
| #  - https://github.com/platformio/platform-espressif32/releases | ||||
| #  - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 | ||||
| ESP_IDF_PLATFORM_VERSION = cv.Version(54, 3, 21) | ||||
| ESP_IDF_PLATFORM_VERSION = cv.Version(54, 3, 21, "1") | ||||
|  | ||||
| # List based on https://registry.platformio.org/tools/platformio/framework-espidf/versions | ||||
| SUPPORTED_PLATFORMIO_ESP_IDF_5X = [ | ||||
| @@ -468,10 +468,10 @@ def _parse_platform_version(value): | ||||
|     try: | ||||
|         ver = cv.Version.parse(cv.version_number(value)) | ||||
|         if ver.major >= 50:  # a pioarduino version | ||||
|             if "-" in value: | ||||
|                 # maybe a release candidate?...definitely not our default, just use it as-is... | ||||
|                 return f"https://github.com/pioarduino/platform-espressif32/releases/download/{value}/platform-espressif32.zip" | ||||
|             return f"https://github.com/pioarduino/platform-espressif32/releases/download/{ver.major}.{ver.minor:02d}.{ver.patch:02d}/platform-espressif32.zip" | ||||
|             release = f"{ver.major}.{ver.minor:02d}.{ver.patch:02d}" | ||||
|             if ver.extra: | ||||
|                 release += f"-{ver.extra}" | ||||
|             return f"https://github.com/pioarduino/platform-espressif32/releases/download/{release}/platform-espressif32.zip" | ||||
|         # if platform version is a valid version constraint, prefix the default package | ||||
|         cv.platformio_version_constraint(value) | ||||
|         return f"platformio/espressif32@{value}" | ||||
|   | ||||
| @@ -1,10 +1,11 @@ | ||||
| Import("env") | ||||
| Import("env")  # noqa: F821 | ||||
|  | ||||
| import itertools  # noqa: E402 | ||||
| import json  # noqa: E402 | ||||
| import os  # noqa: E402 | ||||
| import pathlib  # noqa: E402 | ||||
| import shutil  # noqa: E402 | ||||
|  | ||||
| import os | ||||
| import json | ||||
| import shutil | ||||
| import pathlib | ||||
| import itertools | ||||
|  | ||||
| def merge_factory_bin(source, target, env): | ||||
|     """ | ||||
| @@ -25,7 +26,9 @@ def merge_factory_bin(source, target, env): | ||||
|         try: | ||||
|             with flasher_args_path.open() as f: | ||||
|                 flash_data = json.load(f) | ||||
|             for addr, fname in sorted(flash_data["flash_files"].items(), key=lambda kv: int(kv[0], 16)): | ||||
|             for addr, fname in sorted( | ||||
|                 flash_data["flash_files"].items(), key=lambda kv: int(kv[0], 16) | ||||
|             ): | ||||
|                 file_path = pathlib.Path(fname) | ||||
|                 if file_path.exists(): | ||||
|                     sections.append((addr, str(file_path))) | ||||
| @@ -40,20 +43,27 @@ def merge_factory_bin(source, target, env): | ||||
|         if flash_images: | ||||
|             print("Using FLASH_EXTRA_IMAGES from PlatformIO environment") | ||||
|             # flatten any nested lists | ||||
|             flat = list(itertools.chain.from_iterable( | ||||
|                 x if isinstance(x, (list, tuple)) else [x] for x in flash_images | ||||
|             )) | ||||
|             flat = list( | ||||
|                 itertools.chain.from_iterable( | ||||
|                     x if isinstance(x, (list, tuple)) else [x] for x in flash_images | ||||
|                 ) | ||||
|             ) | ||||
|             entries = [env.subst(x) for x in flat] | ||||
|             for i in range(0, len(entries) - 1, 2): | ||||
|                 addr, fname = entries[i], entries[i + 1] | ||||
|                 if isinstance(fname, (list, tuple)): | ||||
|                     print(f"Warning: Skipping malformed FLASH_EXTRA_IMAGES entry: {fname}") | ||||
|                     print( | ||||
|                         f"Warning: Skipping malformed FLASH_EXTRA_IMAGES entry: {fname}" | ||||
|                     ) | ||||
|                     continue | ||||
|                 file_path = pathlib.Path(str(fname)) | ||||
|                 if file_path.exists(): | ||||
|                     sections.append((addr, str(file_path))) | ||||
|                     sections.append((addr, file_path)) | ||||
|                 else: | ||||
|                     print(f"Info: {file_path.name} not found — skipping") | ||||
|         if sections: | ||||
|             # Append main firmware to sections | ||||
|             sections.append(("0x10000", firmware_path)) | ||||
|  | ||||
|     # 3. Final fallback: guess standard image locations | ||||
|     if not sections: | ||||
| @@ -62,11 +72,11 @@ def merge_factory_bin(source, target, env): | ||||
|             ("0x0", build_dir / "bootloader" / "bootloader.bin"), | ||||
|             ("0x8000", build_dir / "partition_table" / "partition-table.bin"), | ||||
|             ("0xe000", build_dir / "ota_data_initial.bin"), | ||||
|             ("0x10000", firmware_path) | ||||
|             ("0x10000", firmware_path), | ||||
|         ] | ||||
|         for addr, file_path in guesses: | ||||
|             if file_path.exists(): | ||||
|                 sections.append((addr, str(file_path))) | ||||
|                 sections.append((addr, file_path)) | ||||
|             else: | ||||
|                 print(f"Info: {file_path.name} not found — skipping") | ||||
|  | ||||
| @@ -76,21 +86,25 @@ def merge_factory_bin(source, target, env): | ||||
|         return | ||||
|  | ||||
|     output_path = firmware_path.with_suffix(".factory.bin") | ||||
|     python_exe = f'"{env.subst("$PYTHONEXE")}"' | ||||
|     cmd = [ | ||||
|         "--chip", chip, | ||||
|         python_exe, | ||||
|         "-m", | ||||
|         "esptool", | ||||
|         "--chip", | ||||
|         chip, | ||||
|         "merge_bin", | ||||
|         "--flash_size", flash_size, | ||||
|         "--output", str(output_path) | ||||
|         "--flash_size", | ||||
|         flash_size, | ||||
|         "--output", | ||||
|         str(output_path), | ||||
|     ] | ||||
|     for addr, file_path in sections: | ||||
|         cmd += [addr, file_path] | ||||
|         cmd += [addr, str(file_path)] | ||||
|  | ||||
|     print(f"Merging binaries into {output_path}") | ||||
|     result = env.Execute( | ||||
|         env.VerboseAction( | ||||
|             f"{env.subst('$PYTHONEXE')} -m esptool " + " ".join(cmd), | ||||
|             "Merging binaries with esptool" | ||||
|         ) | ||||
|         env.VerboseAction(" ".join(cmd), "Merging binaries with esptool") | ||||
|     ) | ||||
|  | ||||
|     if result == 0: | ||||
| @@ -98,6 +112,7 @@ def merge_factory_bin(source, target, env): | ||||
|     else: | ||||
|         print(f"Error: esptool merge_bin failed with code {result}") | ||||
|  | ||||
|  | ||||
| def esp32_copy_ota_bin(source, target, env): | ||||
|     """ | ||||
|     Copy the main firmware to a .ota.bin file for compatibility with ESPHome OTA tools. | ||||
| @@ -107,6 +122,7 @@ def esp32_copy_ota_bin(source, target, env): | ||||
|     shutil.copyfile(firmware_name, new_file_name) | ||||
|     print(f"Copied firmware to {new_file_name}") | ||||
|  | ||||
|  | ||||
| # Run merge first, then ota copy second | ||||
| env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", merge_factory_bin) | ||||
| env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_copy_ota_bin) | ||||
| env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", merge_factory_bin)  # noqa: F821 | ||||
| env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_copy_ota_bin)  # noqa: F821 | ||||
|   | ||||
| @@ -201,15 +201,13 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { | ||||
|     touch_pad_t pad = child->get_touch_pad(); | ||||
|  | ||||
|     // Read current value using ISR-safe API | ||||
|     uint32_t value; | ||||
|     if (component->iir_filter_enabled_()) { | ||||
|       uint16_t temp_value = 0; | ||||
|       touch_pad_read_filtered(pad, &temp_value); | ||||
|       value = temp_value; | ||||
|     } else { | ||||
|       // Use low-level HAL function when filter is not enabled | ||||
|       value = touch_ll_read_raw_data(pad); | ||||
|     } | ||||
|     // IMPORTANT: ESP-IDF v5.4 regression - touch_pad_read_filtered() is no longer ISR-safe | ||||
|     // In v5.3 and earlier it was ISR-safe, but v5.4 added mutex protection that causes: | ||||
|     // "assert failed: xQueueSemaphoreTake queue.c:1718" | ||||
|     // We must use raw values even when filter is enabled as a workaround. | ||||
|     // Users should adjust thresholds to compensate for the lack of IIR filtering. | ||||
|     // See: https://github.com/espressif/esp-idf/issues/17045 | ||||
|     uint32_t value = touch_ll_read_raw_data(pad); | ||||
|  | ||||
|     // Skip pads that aren’t in the trigger mask | ||||
|     if (((mask >> pad) & 1) == 0) { | ||||
|   | ||||
| @@ -52,7 +52,7 @@ void GPS::update() { | ||||
| void GPS::loop() { | ||||
|   while (this->available() > 0 && !this->has_time_) { | ||||
|     if (!this->tiny_gps_.encode(this->read())) { | ||||
|       return; | ||||
|       continue; | ||||
|     } | ||||
|     if (this->tiny_gps_.location.isUpdated()) { | ||||
|       this->latitude_ = this->tiny_gps_.location.lat(); | ||||
|   | ||||
| @@ -126,6 +126,6 @@ async def to_code(config): | ||||
|     cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE])) | ||||
|     cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE])) | ||||
|  | ||||
|     cg.add_library("tonia/HeatpumpIR", "1.0.35") | ||||
|     if CORE.is_libretiny: | ||||
|     cg.add_library("tonia/HeatpumpIR", "1.0.37") | ||||
|     if CORE.is_libretiny or CORE.is_esp32: | ||||
|         CORE.add_platformio_option("lib_ignore", "IRremoteESP8266") | ||||
|   | ||||
| @@ -15,7 +15,7 @@ from ..defines import ( | ||||
|     TILE_DIRECTIONS, | ||||
|     literal, | ||||
| ) | ||||
| from ..lv_validation import animated, lv_int | ||||
| from ..lv_validation import animated, lv_int, lv_pct | ||||
| from ..lvcode import lv, lv_assign, lv_expr, lv_obj, lv_Pvariable | ||||
| from ..schemas import container_schema | ||||
| from ..types import LV_EVENT, LvType, ObjUpdateAction, lv_obj_t, lv_obj_t_ptr | ||||
| @@ -41,8 +41,8 @@ TILEVIEW_SCHEMA = cv.Schema( | ||||
|             container_schema( | ||||
|                 obj_spec, | ||||
|                 { | ||||
|                     cv.Required(CONF_ROW): lv_int, | ||||
|                     cv.Required(CONF_COLUMN): lv_int, | ||||
|                     cv.Required(CONF_ROW): cv.positive_int, | ||||
|                     cv.Required(CONF_COLUMN): cv.positive_int, | ||||
|                     cv.GenerateID(): cv.declare_id(lv_tile_t), | ||||
|                     cv.Optional(CONF_DIR, default="ALL"): TILE_DIRECTIONS.several_of, | ||||
|                 }, | ||||
| @@ -63,21 +63,29 @@ class TileviewType(WidgetType): | ||||
|         ) | ||||
|  | ||||
|     async def to_code(self, w: Widget, config: dict): | ||||
|         for tile_conf in config.get(CONF_TILES, ()): | ||||
|         tiles = config[CONF_TILES] | ||||
|         for tile_conf in tiles: | ||||
|             w_id = tile_conf[CONF_ID] | ||||
|             tile_obj = lv_Pvariable(lv_obj_t, w_id) | ||||
|             tile = Widget.create(w_id, tile_obj, tile_spec, tile_conf) | ||||
|             dirs = tile_conf[CONF_DIR] | ||||
|             if isinstance(dirs, list): | ||||
|                 dirs = "|".join(dirs) | ||||
|             row_pos = tile_conf[CONF_ROW] | ||||
|             col_pos = tile_conf[CONF_COLUMN] | ||||
|             lv_assign( | ||||
|                 tile_obj, | ||||
|                 lv_expr.tileview_add_tile( | ||||
|                     w.obj, tile_conf[CONF_COLUMN], tile_conf[CONF_ROW], literal(dirs) | ||||
|                 ), | ||||
|                 lv_expr.tileview_add_tile(w.obj, col_pos, row_pos, literal(dirs)), | ||||
|             ) | ||||
|             # Bugfix for LVGL 8.x | ||||
|             lv_obj.set_pos(tile_obj, lv_pct(col_pos * 100), lv_pct(row_pos * 100)) | ||||
|             await set_obj_properties(tile, tile_conf) | ||||
|             await add_widgets(tile, tile_conf) | ||||
|         if tiles: | ||||
|             # Set the first tile as active | ||||
|             lv_obj.set_tile_id( | ||||
|                 w.obj, tiles[0][CONF_COLUMN], tiles[0][CONF_ROW], literal("LV_ANIM_OFF") | ||||
|             ) | ||||
|  | ||||
|  | ||||
| tileview_spec = TileviewType() | ||||
|   | ||||
| @@ -1,6 +1,9 @@ | ||||
| #include "esphome/core/defines.h" | ||||
| #ifdef USE_OPENTHREAD | ||||
| #include "openthread.h" | ||||
| #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) | ||||
| #include "esp_openthread.h" | ||||
| #endif | ||||
|  | ||||
| #include <freertos/portmacro.h> | ||||
|  | ||||
| @@ -28,18 +31,6 @@ OpenThreadComponent *global_openthread_component =  // NOLINT(cppcoreguidelines- | ||||
|  | ||||
| OpenThreadComponent::OpenThreadComponent() { global_openthread_component = this; } | ||||
|  | ||||
| OpenThreadComponent::~OpenThreadComponent() { | ||||
|   auto lock = InstanceLock::try_acquire(100); | ||||
|   if (!lock) { | ||||
|     ESP_LOGW(TAG, "Failed to acquire OpenThread lock in destructor, leaking memory"); | ||||
|     return; | ||||
|   } | ||||
|   otInstance *instance = lock->get_instance(); | ||||
|   otSrpClientClearHostAndServices(instance); | ||||
|   otSrpClientBuffersFreeAllServices(instance); | ||||
|   global_openthread_component = nullptr; | ||||
| } | ||||
|  | ||||
| bool OpenThreadComponent::is_connected() { | ||||
|   auto lock = InstanceLock::try_acquire(100); | ||||
|   if (!lock) { | ||||
| @@ -199,6 +190,33 @@ void *OpenThreadSrpComponent::pool_alloc_(size_t size) { | ||||
|  | ||||
| void OpenThreadSrpComponent::set_mdns(esphome::mdns::MDNSComponent *mdns) { this->mdns_ = mdns; } | ||||
|  | ||||
| bool OpenThreadComponent::teardown() { | ||||
|   if (!this->teardown_started_) { | ||||
|     this->teardown_started_ = true; | ||||
|     ESP_LOGD(TAG, "Clear Srp"); | ||||
|     auto lock = InstanceLock::try_acquire(100); | ||||
|     if (!lock) { | ||||
|       ESP_LOGW(TAG, "Failed to acquire OpenThread lock during teardown, leaking memory"); | ||||
|       return true; | ||||
|     } | ||||
|     otInstance *instance = lock->get_instance(); | ||||
|     otSrpClientClearHostAndServices(instance); | ||||
|     otSrpClientBuffersFreeAllServices(instance); | ||||
|     global_openthread_component = nullptr; | ||||
| #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) | ||||
|     ESP_LOGD(TAG, "Exit main loop "); | ||||
|     int error = esp_openthread_mainloop_exit(); | ||||
|     if (error != ESP_OK) { | ||||
|       ESP_LOGW(TAG, "Failed attempt to stop main loop %d", error); | ||||
|       this->teardown_complete_ = true; | ||||
|     } | ||||
| #else | ||||
|     this->teardown_complete_ = true; | ||||
| #endif | ||||
|   } | ||||
|   return this->teardown_complete_; | ||||
| } | ||||
|  | ||||
| }  // namespace openthread | ||||
| }  // namespace esphome | ||||
|  | ||||
|   | ||||
| @@ -21,6 +21,7 @@ class OpenThreadComponent : public Component { | ||||
|   OpenThreadComponent(); | ||||
|   ~OpenThreadComponent(); | ||||
|   void setup() override; | ||||
|   bool teardown() override; | ||||
|   float get_setup_priority() const override { return setup_priority::WIFI; } | ||||
|  | ||||
|   bool is_connected(); | ||||
| @@ -30,6 +31,8 @@ class OpenThreadComponent : public Component { | ||||
|  | ||||
|  protected: | ||||
|   std::optional<otIp6Address> get_omr_address_(InstanceLock &lock); | ||||
|   bool teardown_started_{false}; | ||||
|   bool teardown_complete_{false}; | ||||
| }; | ||||
|  | ||||
| extern OpenThreadComponent *global_openthread_component;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) | ||||
|   | ||||
| @@ -143,10 +143,13 @@ void OpenThreadComponent::ot_main() { | ||||
|   esp_openthread_launch_mainloop(); | ||||
|  | ||||
|   // Clean up | ||||
|   esp_openthread_deinit(); | ||||
|   esp_openthread_netif_glue_deinit(); | ||||
|   esp_netif_destroy(openthread_netif); | ||||
|  | ||||
|   esp_vfs_eventfd_unregister(); | ||||
|   this->teardown_complete_ = true; | ||||
|   vTaskDelete(NULL); | ||||
| } | ||||
|  | ||||
| network::IPAddresses OpenThreadComponent::get_ip_addresses() { | ||||
|   | ||||
| @@ -43,6 +43,8 @@ FloatOutputPtr = FloatOutput.operator("ptr") | ||||
| TurnOffAction = output_ns.class_("TurnOffAction", automation.Action) | ||||
| TurnOnAction = output_ns.class_("TurnOnAction", automation.Action) | ||||
| SetLevelAction = output_ns.class_("SetLevelAction", automation.Action) | ||||
| SetMinPowerAction = output_ns.class_("SetMinPowerAction", automation.Action) | ||||
| SetMaxPowerAction = output_ns.class_("SetMaxPowerAction", automation.Action) | ||||
|  | ||||
|  | ||||
| async def setup_output_platform_(obj, config): | ||||
| @@ -104,6 +106,42 @@ async def output_set_level_to_code(config, action_id, template_arg, args): | ||||
|     return var | ||||
|  | ||||
|  | ||||
| @automation.register_action( | ||||
|     "output.set_min_power", | ||||
|     SetMinPowerAction, | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.Required(CONF_ID): cv.use_id(FloatOutput), | ||||
|             cv.Required(CONF_MIN_POWER): cv.templatable(cv.percentage), | ||||
|         } | ||||
|     ), | ||||
| ) | ||||
| async def output_set_min_power_to_code(config, action_id, template_arg, args): | ||||
|     paren = await cg.get_variable(config[CONF_ID]) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, paren) | ||||
|     template_ = await cg.templatable(config[CONF_MIN_POWER], args, float) | ||||
|     cg.add(var.set_min_power(template_)) | ||||
|     return var | ||||
|  | ||||
|  | ||||
| @automation.register_action( | ||||
|     "output.set_max_power", | ||||
|     SetMaxPowerAction, | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.Required(CONF_ID): cv.use_id(FloatOutput), | ||||
|             cv.Required(CONF_MAX_POWER): cv.templatable(cv.percentage), | ||||
|         } | ||||
|     ), | ||||
| ) | ||||
| async def output_set_max_power_to_code(config, action_id, template_arg, args): | ||||
|     paren = await cg.get_variable(config[CONF_ID]) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, paren) | ||||
|     template_ = await cg.templatable(config[CONF_MAX_POWER], args, float) | ||||
|     cg.add(var.set_max_power(template_)) | ||||
|     return var | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     cg.add_define("USE_OUTPUT") | ||||
|     cg.add_global(output_ns.using) | ||||
|   | ||||
| @@ -40,5 +40,29 @@ template<typename... Ts> class SetLevelAction : public Action<Ts...> { | ||||
|   FloatOutput *output_; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class SetMinPowerAction : public Action<Ts...> { | ||||
|  public: | ||||
|   SetMinPowerAction(FloatOutput *output) : output_(output) {} | ||||
|  | ||||
|   TEMPLATABLE_VALUE(float, min_power) | ||||
|  | ||||
|   void play(Ts... x) override { this->output_->set_min_power(this->min_power_.value(x...)); } | ||||
|  | ||||
|  protected: | ||||
|   FloatOutput *output_; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class SetMaxPowerAction : public Action<Ts...> { | ||||
|  public: | ||||
|   SetMaxPowerAction(FloatOutput *output) : output_(output) {} | ||||
|  | ||||
|   TEMPLATABLE_VALUE(float, max_power) | ||||
|  | ||||
|   void play(Ts... x) override { this->output_->set_max_power(this->max_power_.value(x...)); } | ||||
|  | ||||
|  protected: | ||||
|   FloatOutput *output_; | ||||
| }; | ||||
|  | ||||
| }  // namespace output | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -332,6 +332,7 @@ def sensor_schema( | ||||
|     device_class: str = cv.UNDEFINED, | ||||
|     state_class: str = cv.UNDEFINED, | ||||
|     entity_category: str = cv.UNDEFINED, | ||||
|     filters: list = cv.UNDEFINED, | ||||
| ) -> cv.Schema: | ||||
|     schema = {} | ||||
|  | ||||
| @@ -346,6 +347,7 @@ def sensor_schema( | ||||
|         (CONF_DEVICE_CLASS, device_class, validate_device_class), | ||||
|         (CONF_STATE_CLASS, state_class, validate_state_class), | ||||
|         (CONF_ENTITY_CATEGORY, entity_category, sensor_entity_category), | ||||
|         (CONF_FILTERS, filters, validate_filters), | ||||
|     ]: | ||||
|         if default is not cv.UNDEFINED: | ||||
|             schema[cv.Optional(key, default=default)] = validator | ||||
|   | ||||
| @@ -162,6 +162,7 @@ def text_sensor_schema( | ||||
|     device_class: str = cv.UNDEFINED, | ||||
|     entity_category: str = cv.UNDEFINED, | ||||
|     icon: str = cv.UNDEFINED, | ||||
|     filters: list = cv.UNDEFINED, | ||||
| ) -> cv.Schema: | ||||
|     schema = {} | ||||
|  | ||||
| @@ -172,6 +173,7 @@ def text_sensor_schema( | ||||
|         (CONF_ICON, icon, cv.icon), | ||||
|         (CONF_DEVICE_CLASS, device_class, validate_device_class), | ||||
|         (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category), | ||||
|         (CONF_FILTERS, filters, validate_filters), | ||||
|     ]: | ||||
|         if default is not cv.UNDEFINED: | ||||
|             schema[cv.Optional(key, default=default)] = validator | ||||
|   | ||||
| @@ -291,6 +291,8 @@ class Version: | ||||
|     extra: str = "" | ||||
|  | ||||
|     def __str__(self): | ||||
|         if self.extra: | ||||
|             return f"{self.major}.{self.minor}.{self.patch}-{self.extra}" | ||||
|         return f"{self.major}.{self.minor}.{self.patch}" | ||||
|  | ||||
|     @classmethod | ||||
|   | ||||
| @@ -225,9 +225,10 @@ class _Schema(vol.Schema): | ||||
|             return ret | ||||
|  | ||||
|         schema = schemas[0] | ||||
|         extra_schemas = self._extra_schemas.copy() | ||||
|         if isinstance(schema, _Schema): | ||||
|             extra_schemas.extend(schema._extra_schemas) | ||||
|         if isinstance(schema, vol.Schema): | ||||
|             schema = schema.schema | ||||
|         ret = super().extend(schema, extra=extra) | ||||
|         return _Schema( | ||||
|             ret.schema, extra=ret.extra, extra_schemas=self._extra_schemas.copy() | ||||
|         ) | ||||
|         return _Schema(ret.schema, extra=ret.extra, extra_schemas=extra_schemas) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user