_decode_pc shells out to PlatformIO via _run_idedata; without a
populated build dir for the device that subprocess fails for every
PC/BT line in a crash dump. Disable decoding after the first
EsphomeError (per logs session) and emit a single user-facing warning
instead of retrying on every line.
Also rename the helper to _LogLineProcessor since it now owns the
per-session decode-enabled state, not just the decode call.
When 'esphome logs' processes a crash backtrace from the device,
process_stacktrace -> _decode_pc -> _run_idedata can raise
EsphomeError if the local build dir hasn't been populated (e.g. the
device was flashed from a different machine). on_log runs inside an
asyncio protocol callback, so the unhandled exception triggers
'Fatal error: protocol.data_received() call failed.', the loop tears
the connection down, and ReconnectLogic immediately reconnects. The
device replays the same crash trace and we loop forever.
Wrap the per-line decode in a helper that swallows EsphomeError so
the connection stays up. Also covered with unit tests for the new
helper.
Last step in the HAL reorg started in #15977 and continued in the
per-platform follow-ups #16111 (esp32), #16112 (esp8266), #16113
(libretiny), #16114 (rp2040), #16115 (host), #16116 (zephyr) — each of
which moved its platform's out-of-line HAL bodies from
components/<platform>/core.cpp into a new components/<platform>/hal.cpp.
This PR moves each platform's HAL header next to its hal.cpp inside
components/<platform>/, rather than under core/hal/. The dispatcher
core/hal.h is updated to include from the new locations and the
now-empty core/hal/ directory is removed.
Unlike the wake split (#15978) where freertos-class platforms (ESP32 +
LibreTiny) share a single wake_freertos.{h,cpp}, HAL primitives are
genuinely per-platform — there is no shared HAL implementation across
platforms — so the natural home is alongside each platform component
rather than in a separate core/ subdirectory.
Each .h file gains an empty 'namespace esphome::<platform> {}' block to
satisfy ci-custom's lint_namespace check. HAL functions live in
'namespace esphome' (root); they are not part of the per-component API,
matching the convention used by the corresponding hal.cpp files.
Comment-only updates to the components/<platform>/hal.cpp files to
reflect the new include path. No public API change, no behavior change.
The classic std::vector swap-with-copy idiom (vector<T>(other).swap(other))
instantiates the iterator-range copy constructor, which pulls in
std::__throw_bad_array_new_length and the related typeinfo + vtable + dtor +
what() method (~118 B of stdlib RTTI). Build into a temp via reserve +
push_back instead, then move-assign:
- reserve uses ::operator new (throws bad_alloc, already linked).
- push_back without growth is the noexcept tail path.
- move-assign just swaps pointers, no allocation.
Same shrink semantics, saves ~128 B flash on ESP32. Also adds a fast-path
early return for the case where capacity already equals size (common after
a quiet period, since vector capacity only grows when crossing the doubling
threshold).
Project's .clang-tidy applies different naming rules to static methods
(ClassMethodCase, no suffix) vs instance methods (PrivateMethodCase /
ProtectedMethodCase, _ suffix required). The helper had a trailing _ but
was static, so clang-tidy flagged it. Drop static -- the helper is already
noinline'd and called only at trim time, so the hidden this arg is free
in any meaningful sense.
Project's .clang-tidy applies different naming rules to static methods
(ClassMethodCase, no suffix) vs instance methods (PrivateMethodCase /
ProtectedMethodCase, _ suffix required). The helper had a trailing _ but
was static, so clang-tidy flagged it. Drop static -- the helper is already
noinline'd and called only at trim time, so the hidden this arg is free
in any meaningful sense.