The sen6x test YAML used identical sensor IDs (pm_1_0, pm_2_5,
pm_4_0, pm_10_0) as sen5x, causing ID redefinition errors when
both components are tested together in grouped builds. Prefix
the sen6x IDs with sen6x_ to make them unique.
Make set_mode(const char*, size_t) the real implementation using
ESPHOME_strncasecmp_P with length checks, instead of ignoring len
and delegating to the null-terminated overload.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Avoid implicit StringRef->std::string conversion when the protobuf
message fields are already StringRef. Use the (const char*, size_t)
overloads directly.
ESP-IDF SDK components reference fprintf(), printf(), and vprintf()
which pull in newlib's _vfprintf_r (~11 KB). This is a separate
implementation from _svfprintf_r (used by snprintf/vsnprintf) that
handles FILE* stream I/O with buffering and locking.
ESPHome replaces the ESP-IDF log handler via esp_log_set_vprintf_(),
so the SDK's vprintf() path is dead code at runtime. The fprintf()
and printf() calls in SDK components are only in debug/assert paths
that are either GC'd or never called.
These linker --wrap stubs redirect through vsnprintf() + fwrite(),
allowing the linker to dead-code eliminate _vfprintf_r.
An escape hatch is provided via enable_full_printf: true in the
esp32 advanced config section for external components that need
full FILE*-based fprintf.
These are only used in debug rollover logging. Guarding them avoids
unused variable warnings and unnecessary includes in normal builds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pure code reorganization — no functional change. Combines the separate
#ifndef ESPHOME_THREAD_SINGLE and #ifdef ESPHOME_THREAD_MULTI_ATOMICS
guards into one if/elif/else chain so each threading model's static
variables are grouped together.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
A std::atomic_flag spinlock is unsafe on preemptive single-core RTOS
platforms (like LN882H) due to priority inversion: a high-priority task
spinning would prevent the lock holder from running to release it.
FreeRTOS Mutex has priority inheritance to handle this correctly.
Added a comment explaining why spinlock can't be used here.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
defer() items go straight into defer_queue_ and never use a timestamp.
Now that millis_64() has its own lock separate from the scheduler,
the call no longer needs to happen before taking the scheduler lock.
Move it inside the non-defer branch so defer() avoids the cost entirely.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The atomics path only takes the lock during rollover (every ~49.7 days)
for a ~5 instruction critical section. A std::atomic_flag spinlock
(1 byte) replaces the full FreeRTOS Mutex (~80-100 bytes RAM) since
contention is near-zero. The full Mutex is kept only for the
MULTI_NO_ATOMICS path (BK72xx) which needs it for broader locking.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On ESPHOME_THREAD_SINGLE (ESP8266), the lock is never used.
Guarding it avoids the static local guard variable overhead
(8 bytes RAM + init check on every call).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
millis_64_impl_() handles 32-bit millis() rollover tracking for
platforms without native 64-bit time (ESP8266, LibreTiny). It was a
Scheduler method using Scheduler fields (last_millis_, millis_major_,
lock_), but has nothing to do with scheduling.
Move it to esphome/core/time_64.cpp as Millis64Impl::compute() with
static local state. Access is restricted via friend to millis_64()
(HAL) and Scheduler only.
Also introduces USE_NATIVE_64BIT_TIME define emitted by ESP32, Host,
Zephyr, and RP2040 platforms, replacing the verbose multi-platform
#if guards throughout scheduler.h/cpp.
time_64.cpp is only compiled on platforms that need it (ESP8266,
LibreTiny) via the build filter in config.py.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>