decode() is never called polymorphically - all call sites in
read_message_() use concrete types. The only indirect call site was
decode_to_message(), which also always knows the concrete type.
Convert decode_to_message() to a template so the concrete type is
preserved, allowing decode() to be non-virtual. The two classes that
override decode() (ExecuteServiceArgument, ExecuteServiceRequest) now
hide the base method, which works since all calls use concrete types.
This removes one vtable slot (4 bytes) from each decodable message
class vtable, saving ~148 bytes of flash.
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.