Wake primitives (esp_schedule, __sev, FreeRTOS task notifications)
are platform SDK functions that don't depend on the socket/network
stack. Move them to esphome/core/wake.h/.cpp so they work
unconditionally on all platforms.
This eliminates the require_wake_loop_threadsafe() boilerplate,
the USE_WAKE_LOOP_THREADSAFE define, and the socket AUTO_LOAD
entries that 12 components needed just to use wake.
wake_loop_threadsafe() and wake_loop_any_context() now just work
on every platform without opt-in.
Extend interrupt_pin support to MCP23008, MCP23017, MCP23S08, MCP23S17,
and PI4IOE5V6408 GPIO expander components. Same approach as PCF8574/PCA9554:
when configured, the component disables its loop and only wakes on interrupt,
and the cache stays valid between interrupts so binary sensors return from
cache instead of doing I2C/SPI reads every loop iteration.
For MCP23xxx, the interrupt_pin config and C++ logic lives in the shared
MCP23XXXBase template class, so all four variants (I2C 8/16-pin, SPI
8/16-pin) inherit it automatically.
Inspired by jesserockz's work in #11959 which proposed a more comprehensive
interrupt-driven approach with per-pin interrupt status register reading.
This implementation takes a simpler path by leveraging the existing
CachedGpioExpander cache invalidation mechanism.
UniqueID benchmark was unrealistic — unique IDs still ran
cancel_item_locked_ scanning all containers for matches that never
exist. Replace with just two meaningful defer variants:
- Defer: anonymous (nullptr name, skips cancel entirely)
- Defer_SameID: fixed ID (cancel-and-replace coalescing pattern)
- Scheduler_Defer: use nullptr name matching Component::defer(func)
production pattern (skips cancel_item_locked_ entirely)
- Scheduler_Defer_SameID: fixed ID 0 measuring cancel-and-replace
pattern for coalescing rapid updates
- Scheduler_Defer_UniqueID: unique IDs measuring cancel scan overhead
when no match is found
Change kInnerIterations from 2000 to 2100 (divisible by batch sizes 3
and 10) to prevent pool imbalance at iteration boundaries that caused
spurious malloc. Add static_assert to each benchmark to catch this at
compile time.
Replace duplicated pool warmup blocks with a shared warm_pool() helper
that registers and replaces items twice to populate the recycling pool
before the benchmark loop begins.
Add Scheduler_SetTimeout_ExceedPool with batch size 10 (exceeding
MAX_POOL_SIZE=5) to measure the performance impact when the recycling
pool is exhausted and items must be malloc'd/freed each cycle.
Instead of draining after every single registration or batching 5,
use a batch size of 3 which represents a realistic worst case where
multiple components schedule in the same loop iteration while staying
within the recycling pool (MAX_POOL_SIZE=5).