mirror of
https://github.com/esphome/esphome.git
synced 2025-10-29 22:24:26 +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