Since init_log_buffer_ is now called from the constructor (before
pre_setup sets global_logger), calling disable_loop() would trigger
ESP_LOGVV which dereferences the null global_logger pointer.
The loop self-disables on its first iteration when no messages are
found, so the explicit disable in init_log_buffer_ was unnecessary.
Move TaskLogBuffer allocation from init_log_buffer() (called at
DIAGNOSTICS priority) into the Logger constructor (called at
EARLY_INIT priority). This ensures the buffer exists before
global_logger is set, eliminating a window where another FreeRTOS
task could dereference a null log_buffer_ pointer.
Fixes crash: Guru Meditation Error: Core 0 panic'ed (Load access fault)
in TaskLogBuffer::send_message_thread_safe when a task logs before
init_log_buffer() is called.
Move trivial null-check getter from component.cpp to component.h
so the compiler can inline it at call sites, eliminating function
call overhead in hot logging paths.
Extend the precomputed-tag approach from fixed32 key fields to all forced
fields with single-byte tags (field IDs 1-15). The code generator now
emits write_raw_byte(tag) followed by the raw encode primitive instead
of calling the full encode_* method.
For varint types (uint32, uint64, sint32, sint64, int64, bool, enum),
this eliminates the zero-check branch and encode_field_raw indirection.
For length-delimited types (bytes, string), it additionally skips the
encode_string wrapper.
Benchmarked on real hardware with BluetoothLERawAdvertisementsResponse
(12 advertisements per message, 10000 iterations):
ESP32 (Xtensa dual-core 240MHz):
encode: 38498 -> 30460 ns/op (-20.9%)
calc+encode: 48479 -> 40458 ns/op (-16.6%)
ESP32-C3 (RISC-V single-core 160MHz):
encode: 54199 -> 40342 ns/op (-25.6%)
calc+encode: 57800 -> 51365 ns/op (-11.1%)
The single-message case (via write_protobuf_packet) is the most common
path. Peeling the first loop iteration and outlining the multi-message
batch path avoids the ~300-byte StaticVector<iovec> stack allocation
on the hot path.
Plaintext write_protobuf_messages:
- Stack frame: 352 → 64 bytes
- Code size: 246 → 127 bytes
Noise write_protobuf_messages:
- Extracted encrypt_noise_message_ helper for reuse
- Same peeling pattern with outlined batch path
Use real TCP sockets instead of AF_UNIX socketpair so TCP_NODELAY
succeeds during init() and the benchmark exercises the full write
path. Replace hardcoded message type 38 with SensorStateResponse::MESSAGE_TYPE.
Avoid benchmarking heap allocation by pre-reserving the buffer
to typical TCP MSS size and reusing it across iterations, matching
real-world usage where the buffer persists across writes.
Extract the gamma table generation into a public generate_gamma_table()
function and add unit tests covering table properties and the
zero_means_zero regression from #15055.
When MQTT reconnects, all components have their discovery and state
republished. With many components, processing all of them in a single
loop iteration blocks the main loop long enough to trigger the task
watchdog timer. Limit to 4 resends per loop iteration to spread the
work across multiple cycles.
Closes https://github.com/esphome/esphome/issues/15057
The gamma LUT refactor (#14123) introduced a regression where small
brightness values (e.g. 1%) get quantized to exactly 0.0 because
the uint16 LUT entries round down to 0 for indices 1-3 with the
default gamma of 2.8.
This breaks zero_means_zero: true in FloatOutput because the
min_power scaling is skipped when state == 0.0, causing LEDs to
turn off completely instead of respecting the configured min_power.
Fix by clamping non-zero LUT entries to a minimum of 1, preserving
the invariant that non-zero input always produces non-zero output.
Fixes#15055
The Realtek SDK defines `#define SUCCESS 0` in basic_types.h which
collides with the FlushResult::SUCCESS enum value, breaking compilation
on RTL87xx devices when api/wifi components pull in the SDK headers.
Add `#undef SUCCESS` before the enum definition, following the same
pattern used elsewhere in the codebase for vendor SDK macro collisions.
Also add RTL87xx UART compilation test.
- Combine tag byte + fixed32 value into single write_tag_and_fixed32()
method: pos[0] = tag, memcpy(pos+1, &value, 4), pos += 5
- Extract calculate_tag() from duplicated computation in
calculate_field_id_size() and encode_content
- Combine tag byte + fixed32 value into single write_tag_and_fixed32()
method: pos[0] = tag, memcpy(pos+1, &value, 4), pos += 5
- Extract calculate_tag() from duplicated computation in
calculate_field_id_size() and encode_content
Instead of ALWAYS_INLINE on encode_field_raw (which bloated all
callers), have the code generator precompute the tag byte and emit
write_raw_byte(tag) + write_fixed32_raw(value) directly.
This gives the same tight codegen (single byte store + memcpy) for
key fields without inflating encode_bool/encode_uint32/etc.
+108 bytes flash vs baseline, -48 bytes vs the ALWAYS_INLINE approach.
- Add ESPHOME_ALWAYS_INLINE to encode_fixed32 so the compiler inlines it
on hot paths despite -Os heuristics (removing noinline alone was not
enough — gcc still chose not to inline at 51 call sites)
- Mark all fixed32 key fields in api.proto with [(force) = true] since
entity keys are FNV hashes and never zero, eliminating the zero-check
branch and making calculate_size() use constants
+96 bytes flash (555167 → 555263) on ESP32 — negligible for removing
branch + call overhead on every sensor state encode.
Profiling showed the noinline call overhead dominated hot paths like
SensorStateResponse encoding, where encode_fixed32 is called twice
per message (once for key, once via encode_float for state).
The function body is trivial (tag byte + 4-byte memcpy), so the
function call prologue/epilogue cost exceeded the actual work.
Despite 51 call sites, removing noinline shows no measurable flash
size increase (555167 bytes before and after on ESP32).
On some ESP32 boards (especially cheap clones), the eFuse custom MAC
area contains random garbage that passes the existing all-zeros/all-ones
validation. Additionally, esp_efuse_mac_get_default() can fail with CRC
errors, but the return value was being ignored, causing garbage MAC
addresses to be advertised via mDNS.
This caused Home Assistant to report false "MAC address changed" device
conflicts on every boot.
Two fixes:
- Check return values from eFuse MAC read functions and add a fallback
chain: custom MAC -> default MAC -> raw eFuse bytes -> zeroed MAC.
- Reject multicast MACs (bit 0 of first byte set) in mac_address_is_valid()
since device MACs must always be unicast.
Closes https://github.com/esphome/esphome/issues/14501
Regression tests for #15040 using a single compiled binary with two
cwww lights to verify:
- constant_brightness: true maintains constant total CW+WW power
output across all color temperatures with gamma correction
- constant_brightness: false correctly varies total power (higher
at mid-range where both channels contribute)
The gamma LUT refactor (#14123) moved gamma correction to after
the constant_brightness balancing formula (max/sum ratio). This
broke constant_brightness because gamma is nonlinear and does not
commute with the ratio calculation, causing a severe brightness
dip at mid-range color temperatures.
Fix by applying gamma to individual CW/WW/brightness values
before the constant_brightness formula, restoring the original
behavior where total power output remains constant across all
color temperatures.
Closes#15040
- Update comments to reflect that ProtoService methods moved to
APIConnection, not APIServerConnectionBase
- Fix comment referring to read_message as "override"
- Wrap #include "api_connection.h" and read_message_ implementation
in #ifdef USE_API guards