enable_loop_soon_any_context() sets volatile flags but does not wake
the main loop from ulTaskNotifyTake() sleep. This means components
using ISR-driven state changes (e.g. GPIO binary sensors) wait up
to ~16ms for the select timeout before their loop runs.
Add Application::wake_loop_isrsafe(nullptr) to immediately wake the
main loop when called from ISR context on platforms with fast select.
Also relax the wake_loop_isrsafe() guard from requiring both
USE_WAKE_LOOP_THREADSAFE and USE_LWIP_FAST_SELECT to just
USE_LWIP_FAST_SELECT, since the ISR wake path uses
vTaskNotifyGiveFromISR directly and does not depend on the
UDP socket mechanism.
Use the ESP-IDF UART driver's uart_set_select_notif_callback() to wake
the main loop directly from the UART ISR via vTaskNotifyGiveFromISR(),
eliminating the FreeRTOS trampoline task and event queue that were
previously needed when wake_loop_threadsafe() used a UDP loopback socket.
With fast lwip select, the main loop sleeps on ulTaskNotifyTake(), so
the ISR-safe vTaskNotifyGiveFromISR() can wake it directly.
Saves ~2.6KB RAM per UART instance with wake-on-RX enabled:
- 2,240 bytes FreeRTOS task stack
- ~340 bytes event queue (20 entries + control block)
- 8 bytes QueueHandle_t + TaskHandle_t fields
Also adds Application::wake_loop_isrsafe() as the ISR-safe counterpart
to wake_loop_threadsafe(), backed by esphome_lwip_wake_main_loop_from_isr().
The two_byte_to_int() function used `char` parameters instead of
`uint8_t`, causing sign extension for byte values >= 0x80. This
produced incorrect sensor values for distances where value % 256 >= 128
(e.g. 128cm reported as -128, 200cm reported as -56).
Changed return type to uint16_t to match protocol spec (unsigned
distances), renamed to two_byte_to_uint16, and moved to shared
ld24xx header to eliminate duplication.
The two_byte_to_int() function used `char` parameters instead of
`uint8_t`, causing sign extension for byte values >= 0x80. This
produced incorrect sensor values for distances where value % 256 >= 128
(e.g. 128cm reported as -128, 200cm reported as -56).
Also removed the unnecessary `(int16_t)` cast and switched from `+` to
`|` for proper bitwise assembly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract the keepalive ping/disconnect cold path into a separate
noinline check_keepalive_() method. This code only fires once per
minute but was contributing ~110 bytes to the hot loop() body that
runs every ~16ms.
Results on ESP32 (xtensa):
- loop(): 374 → 263 bytes (-111 bytes, -30%)
- check_keepalive_(): 136 bytes (new, cold path)
- Net: +25 bytes total (call overhead), but the hot path icache
footprint is significantly reduced.
The template wrapper caused the compiler to make different
optimization decisions on setup(), negating the savings.
Calling register_component_ directly was -4 bytes.
Co-Authored-By: J. Nick Koston <nick@koston.org>
Using the original register_component name (just moved to protected)
produces identical compiled output to the baseline, avoiding the
symbol name length overhead from renaming.
Co-Authored-By: J. Nick Koston <nick@koston.org>
Rename the non-template to register_component_impl_ and add
a protected template register_component_ that wraps it. This
preserves the compiler optimization behavior (isra clones)
while keeping the method inaccessible to external components.
Co-Authored-By: J. Nick Koston <nick@koston.org>
Without the template wrapper, the compiler inlines the 24-byte
function at each of ~92 call sites in setup(), growing setup()
by ~284 bytes. Force a function call instead.
Co-Authored-By: J. Nick Koston <nick@koston.org>
Arduino.h on ESP8266 already declares void setup(void), so our
forward declaration triggers readability-redundant-declaration.
Co-Authored-By: J. Nick Koston <nick@koston.org>
The cpp test framework renames setup() to original_setup() and
replaces setup() with the gtest runner, so we need to friend both.
Co-Authored-By: J. Nick Koston <nick@koston.org>
Instead of runtime null, duplicate, and capacity checks in
register_component_, make the method unreachable from outside
codegen by removing the public template wrapper and granting
friend access to the codegen-generated ::setup() function.
Since ESPHOME_COMPONENT_COUNT is set to exactly
len(CORE.component_ids) at codegen time, the StaticVector is
always correctly sized and the runtime capacity check cannot
trigger from codegen. External components that bypassed codegen
to call App.register_component() directly will now get a
compile error, forcing them to properly declare their components
in their config schema.
Co-Authored-By: J. Nick Koston <nick@koston.org>
Move set_component_state_ from component.cpp to the header as
an inline method so it can be reused wherever Component state
needs to be updated. Replace the manual bit manipulation in
Application::enable_pending_loops_ with a call to the helper.
Co-Authored-By: J. Nick Koston <nick@koston.org>
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.