mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Merge branch 'integration' into memory_api
This commit is contained in:
		| @@ -80,7 +80,7 @@ CONFIG_SCHEMA = cv.All( | ||||
|         cv.Schema( | ||||
|             { | ||||
|                 cv.GenerateID(): cv.declare_id(BluetoothProxy), | ||||
|                 cv.Optional(CONF_ACTIVE, default=False): cv.boolean, | ||||
|                 cv.Optional(CONF_ACTIVE, default=True): cv.boolean, | ||||
|                 cv.SplitDefault(CONF_CACHE_SERVICES, esp32_idf=True): cv.All( | ||||
|                     cv.only_with_esp_idf, cv.boolean | ||||
|                 ), | ||||
|   | ||||
| @@ -40,6 +40,7 @@ from esphome.cpp_generator import RawExpression | ||||
| import esphome.final_validate as fv | ||||
| from esphome.helpers import copy_file_if_changed, mkdir_p, write_file_if_changed | ||||
| from esphome.types import ConfigType | ||||
| from esphome.writer import clean_cmake_cache | ||||
|  | ||||
| from .boards import BOARDS, STANDARD_BOARDS | ||||
| from .const import (  # noqa | ||||
| @@ -840,6 +841,9 @@ async def to_code(config): | ||||
|     if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_CUSTOM_MAC]: | ||||
|         cg.add_define("USE_ESP32_IGNORE_EFUSE_CUSTOM_MAC") | ||||
|  | ||||
|     for clean_var in ("IDF_PATH", "IDF_TOOLS_PATH"): | ||||
|         os.environ.pop(clean_var, None) | ||||
|  | ||||
|     add_extra_script( | ||||
|         "post", | ||||
|         "post_build.py", | ||||
| @@ -1074,7 +1078,11 @@ def _write_idf_component_yml(): | ||||
|         contents = yaml_util.dump({"dependencies": dependencies}) | ||||
|     else: | ||||
|         contents = "" | ||||
|     write_file_if_changed(yml_path, contents) | ||||
|     if write_file_if_changed(yml_path, contents): | ||||
|         dependencies_lock = CORE.relative_build_path("dependencies.lock") | ||||
|         if os.path.isfile(dependencies_lock): | ||||
|             os.remove(dependencies_lock) | ||||
|         clean_cmake_cache() | ||||
|  | ||||
|  | ||||
| # Called by writer.py | ||||
|   | ||||
| @@ -47,9 +47,9 @@ ErrorCode I2CDevice::write_register(uint8_t a_register, const uint8_t *data, siz | ||||
|  | ||||
| ErrorCode I2CDevice::write_register16(uint16_t a_register, const uint8_t *data, size_t len) const { | ||||
|   std::vector<uint8_t> v(len + 2); | ||||
|   v.push_back(a_register >> 8); | ||||
|   v.push_back(a_register); | ||||
|   v.insert(v.end(), data, data + len); | ||||
|   v[0] = a_register >> 8; | ||||
|   v[1] = a_register; | ||||
|   std::copy(data, data + len, v.begin() + 2); | ||||
|   return bus_->write_readv(this->address_, v.data(), v.size(), nullptr, 0); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -51,7 +51,7 @@ from . import wpa2_eap | ||||
|  | ||||
| AUTO_LOAD = ["network"] | ||||
|  | ||||
| NO_WIFI_VARIANTS = [const.VARIANT_ESP32H2] | ||||
| NO_WIFI_VARIANTS = [const.VARIANT_ESP32H2, const.VARIANT_ESP32P4] | ||||
| CONF_SAVE = "save" | ||||
|  | ||||
| wifi_ns = cg.esphome_ns.namespace("wifi") | ||||
| @@ -179,8 +179,8 @@ WIFI_NETWORK_STA = WIFI_NETWORK_BASE.extend( | ||||
| def validate_variant(_): | ||||
|     if CORE.is_esp32: | ||||
|         variant = get_esp32_variant() | ||||
|         if variant in NO_WIFI_VARIANTS: | ||||
|             raise cv.Invalid(f"{variant} does not support WiFi") | ||||
|         if variant in NO_WIFI_VARIANTS and "esp32_hosted" not in fv.full_config.get(): | ||||
|             raise cv.Invalid(f"WiFi requires component esp32_hosted on {variant}") | ||||
|  | ||||
|  | ||||
| def final_validate(config): | ||||
|   | ||||
| @@ -146,12 +146,12 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type | ||||
|     // first execution happens immediately after a random smallish offset | ||||
|     // Calculate random offset (0 to min(interval/2, 5s)) | ||||
|     uint32_t offset = (uint32_t) (std::min(delay / 2, MAX_INTERVAL_DELAY) * random_float()); | ||||
|     item->next_execution_ = now + offset; | ||||
|     item->set_next_execution(now + offset); | ||||
|     ESP_LOGV(TAG, "Scheduler interval for %s is %" PRIu32 "ms, offset %" PRIu32 "ms", name_cstr ? name_cstr : "", delay, | ||||
|              offset); | ||||
|   } else { | ||||
|     item->interval = 0; | ||||
|     item->next_execution_ = now + delay; | ||||
|     item->set_next_execution(now + delay); | ||||
|   } | ||||
|  | ||||
| #ifdef ESPHOME_DEBUG_SCHEDULER | ||||
| @@ -167,7 +167,8 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type | ||||
|              name_cstr ? name_cstr : "(null)", type_str, delay); | ||||
|   } else { | ||||
|     ESP_LOGD(TAG, "set_%s(name='%s/%s', %s=%" PRIu32 ", offset=%" PRIu32 ")", type_str, item->get_source(), | ||||
|              name_cstr ? name_cstr : "(null)", type_str, delay, static_cast<uint32_t>(item->next_execution_ - now)); | ||||
|              name_cstr ? name_cstr : "(null)", type_str, delay, | ||||
|              static_cast<uint32_t>(item->get_next_execution() - now)); | ||||
|   } | ||||
| #endif /* ESPHOME_DEBUG_SCHEDULER */ | ||||
|  | ||||
| @@ -312,9 +313,10 @@ optional<uint32_t> HOT Scheduler::next_schedule_in(uint32_t now) { | ||||
|   auto &item = this->items_[0]; | ||||
|   // Convert the fresh timestamp from caller (usually Application::loop()) to 64-bit | ||||
|   const auto now_64 = this->millis_64_(now);  // 'now' from parameter - fresh from caller | ||||
|   if (item->next_execution_ < now_64) | ||||
|   const uint64_t next_exec = item->get_next_execution(); | ||||
|   if (next_exec < now_64) | ||||
|     return 0; | ||||
|   return item->next_execution_ - now_64; | ||||
|   return next_exec - now_64; | ||||
| } | ||||
| void HOT Scheduler::call(uint32_t now) { | ||||
| #ifndef ESPHOME_THREAD_SINGLE | ||||
| @@ -384,7 +386,7 @@ void HOT Scheduler::call(uint32_t now) { | ||||
|       bool is_cancelled = is_item_removed_(item.get()); | ||||
|       ESP_LOGD(TAG, "  %s '%s/%s' interval=%" PRIu32 " next_execution in %" PRIu64 "ms at %" PRIu64 "%s", | ||||
|                item->get_type_str(), item->get_source(), name ? name : "(null)", item->interval, | ||||
|                item->next_execution_ - now_64, item->next_execution_, is_cancelled ? " [CANCELLED]" : ""); | ||||
|                item->get_next_execution() - now_64, item->get_next_execution(), is_cancelled ? " [CANCELLED]" : ""); | ||||
|  | ||||
|       old_items.push_back(std::move(item)); | ||||
|     } | ||||
| @@ -436,7 +438,7 @@ void HOT Scheduler::call(uint32_t now) { | ||||
|     { | ||||
|       // Don't copy-by value yet | ||||
|       auto &item = this->items_[0]; | ||||
|       if (item->next_execution_ > now_64) { | ||||
|       if (item->get_next_execution() > now_64) { | ||||
|         // Not reached timeout yet, done for this call | ||||
|         break; | ||||
|       } | ||||
| @@ -475,7 +477,7 @@ void HOT Scheduler::call(uint32_t now) { | ||||
|       const char *item_name = item->get_name(); | ||||
|       ESP_LOGV(TAG, "Running %s '%s/%s' with interval=%" PRIu32 " next_execution=%" PRIu64 " (now=%" PRIu64 ")", | ||||
|                item->get_type_str(), item->get_source(), item_name ? item_name : "(null)", item->interval, | ||||
|                item->next_execution_, now_64); | ||||
|                item->get_next_execution(), now_64); | ||||
| #endif /* ESPHOME_DEBUG_SCHEDULER */ | ||||
|  | ||||
|       // Warning: During callback(), a lot of stuff can happen, including: | ||||
| @@ -500,7 +502,7 @@ void HOT Scheduler::call(uint32_t now) { | ||||
|       } | ||||
|  | ||||
|       if (item->type == SchedulerItem::INTERVAL) { | ||||
|         item->next_execution_ = now_64 + item->interval; | ||||
|         item->set_next_execution(now_64 + item->interval); | ||||
|         // Add new item directly to to_add_ | ||||
|         // since we have the lock held | ||||
|         this->to_add_.push_back(std::move(item)); | ||||
| @@ -799,7 +801,10 @@ uint64_t Scheduler::millis_64_(uint32_t now) { | ||||
|  | ||||
| bool HOT Scheduler::SchedulerItem::cmp(const std::unique_ptr<SchedulerItem> &a, | ||||
|                                        const std::unique_ptr<SchedulerItem> &b) { | ||||
|   return a->next_execution_ > b->next_execution_; | ||||
|   // 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_) | ||||
|                                                               : (a->next_execution_high_ > b->next_execution_high_); | ||||
| } | ||||
|  | ||||
| void Scheduler::recycle_item_(std::unique_ptr<SchedulerItem> item) { | ||||
|   | ||||
| @@ -88,19 +88,19 @@ class Scheduler { | ||||
|   struct SchedulerItem { | ||||
|     // Ordered by size to minimize padding | ||||
|     Component *component; | ||||
|     uint32_t interval; | ||||
|     // 64-bit time to handle millis() rollover. The scheduler combines the 32-bit millis() | ||||
|     // with a 16-bit rollover counter to create a 64-bit time that won't roll over for | ||||
|     // billions of years. This ensures correct scheduling even when devices run for months. | ||||
|     uint64_t next_execution_; | ||||
|  | ||||
|     // Optimized name storage using tagged union | ||||
|     union { | ||||
|       const char *static_name;  // For string literals (no allocation) | ||||
|       char *dynamic_name;       // For allocated strings | ||||
|     } name_; | ||||
|  | ||||
|     uint32_t interval; | ||||
|     // Split 64-bit time to handle millis() rollover. The scheduler combines the 32-bit millis() | ||||
|     // with a 16-bit rollover counter to create a 64-bit time that won't roll over for | ||||
|     // billions of years. This ensures correct scheduling even when devices run for months. | ||||
|     // Split into two fields for better memory alignment on 32-bit systems. | ||||
|     uint32_t next_execution_low_;  // Lower 32 bits of next execution time | ||||
|     std::function<void()> callback; | ||||
|     uint16_t next_execution_high_;  // Upper 16 bits of next execution time | ||||
|  | ||||
| #ifdef ESPHOME_THREAD_MULTI_ATOMICS | ||||
|     // Multi-threaded with atomics: use atomic for lock-free access | ||||
| @@ -126,7 +126,8 @@ class Scheduler { | ||||
|     SchedulerItem() | ||||
|         : component(nullptr), | ||||
|           interval(0), | ||||
|           next_execution_(0), | ||||
|           next_execution_low_(0), | ||||
|           next_execution_high_(0), | ||||
| #ifdef ESPHOME_THREAD_MULTI_ATOMICS | ||||
|           // remove is initialized in the member declaration as std::atomic<bool>{false} | ||||
|           type(TIMEOUT), | ||||
| @@ -153,7 +154,7 @@ class Scheduler { | ||||
|     SchedulerItem &operator=(SchedulerItem &&) = delete; | ||||
|  | ||||
|     // Helper to get the name regardless of storage type | ||||
|     const char *get_name() const { return name_is_dynamic ? name_.dynamic_name : name_.static_name; } | ||||
|     constexpr const char *get_name() const { return name_is_dynamic ? name_.dynamic_name : name_.static_name; } | ||||
|  | ||||
|     // Helper to clear dynamic name if allocated | ||||
|     void clear_dynamic_name() { | ||||
| @@ -185,7 +186,17 @@ class Scheduler { | ||||
|     } | ||||
|  | ||||
|     static bool cmp(const std::unique_ptr<SchedulerItem> &a, const std::unique_ptr<SchedulerItem> &b); | ||||
|     const char *get_type_str() const { return (type == TIMEOUT) ? "timeout" : "interval"; } | ||||
|  | ||||
|     // Helper methods to work with split execution time (constexpr for optimization) | ||||
|     constexpr uint64_t get_next_execution() const { | ||||
|       return (static_cast<uint64_t>(next_execution_high_) << 32) | next_execution_low_; | ||||
|     } | ||||
|  | ||||
|     constexpr void set_next_execution(uint64_t value) { | ||||
|       next_execution_low_ = static_cast<uint32_t>(value); | ||||
|       next_execution_high_ = static_cast<uint16_t>(value >> 32); | ||||
|     } | ||||
|     constexpr const char *get_type_str() const { return (type == TIMEOUT) ? "timeout" : "interval"; } | ||||
|     const char *get_source() const { return component ? component->get_component_source() : "unknown"; } | ||||
|   }; | ||||
|  | ||||
|   | ||||
| @@ -310,6 +310,10 @@ def clean_build(): | ||||
|     if os.path.isdir(piolibdeps): | ||||
|         _LOGGER.info("Deleting %s", piolibdeps) | ||||
|         shutil.rmtree(piolibdeps) | ||||
|     dependencies_lock = CORE.relative_build_path("dependencies.lock") | ||||
|     if os.path.isfile(dependencies_lock): | ||||
|         _LOGGER.info("Deleting %s", dependencies_lock) | ||||
|         os.remove(dependencies_lock) | ||||
|  | ||||
|  | ||||
| GITIGNORE_CONTENT = """# Gitignore settings for ESPHome | ||||
|   | ||||
| @@ -1059,7 +1059,9 @@ def _generate_array_dump_content( | ||||
|     # Check if underlying type can use dump_field | ||||
|     if ti.can_use_dump_field(): | ||||
|         # For types that have dump_field overloads, use them with extra indent | ||||
|         o += f'  dump_field(out, "{name}", {ti.dump_field_value("it")}, 4);\n' | ||||
|         # std::vector<bool> iterators return proxy objects, need explicit cast | ||||
|         value_expr = "static_cast<bool>(it)" if is_bool else ti.dump_field_value("it") | ||||
|         o += f'  dump_field(out, "{name}", {value_expr}, 4);\n' | ||||
|     else: | ||||
|         # For complex types (messages, bytes), use the old pattern | ||||
|         o += f'  out.append("  {name}: ");\n' | ||||
|   | ||||
| @@ -4,7 +4,7 @@ esphome: | ||||
| host: | ||||
|  | ||||
| logger: | ||||
|   level: DEBUG | ||||
|   level: VERY_VERBOSE | ||||
|  | ||||
| api: | ||||
|   actions: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user