Commit Graph
27713 Commits
Author SHA1 Message Date
J. Nick Koston 9fa8dcc116 Merge remote-tracking branch 'upstream/api-clients-static-array' into integration 2026-04-21 12:13:45 +02:00
J. Nick Koston 69896446c5 [api] Address review: make ActiveClientsView read-only WRT ownership
Change the view to hold `const unique_ptr<APIConnection>&` so external
callers can't reset() or move slots — which would break the
api_connection_count_ invariant. Callers still get a non-const
APIConnection& on dereference (via `(*ptr).method()`), so all existing
call sites work unchanged.

active_clients() is now const-qualified since it only returns a view
over const unique_ptrs.
2026-04-21 12:13:12 +02:00
J. Nick Koston df53dff97a [api] Restore original per-platform RAM rationale comments 2026-04-21 12:11:40 +02:00
J. Nick Koston e89836d0d4 [api] Trim overlong/stale comments from PR 2026-04-21 12:10:52 +02:00
J. Nick Koston 3302903c39 [api] Lower default max_connections to 5 on esp32/libretiny
With N=5 the static-RAM trade becomes net-negative at just 1 client
(the typical ESP32 deployment — just Home Assistant):

  +8 B static (5 slots × 4 B − 12 B removed vector object)
  −12 B heap (8 B header + 4 B slot) at 1 client
  = −4 B net

N=6 had breakeven at 2 clients; N=5 puts breakeven at 1 client so the
common case is pure win.

5 slots covers HA + dashboard + 1 reconnecting socket, which is all
most deployments ever use. Users needing more can bump explicitly in
YAML (schema max is 20).

host stays at 8 (no BSS-slot concern). esp8266 stays at 4, rp2040
stays at 4.
2026-04-21 12:07:18 +02:00
J. Nick Koston 101eaa3222 [api] Keep host default at 8, only lower esp32/libretiny to 6
Revert host=6 to host=8 — the host platform has no BSS-slot concern
(no flash/RAM pressure). The lowering to 6 still applies to
esp32/bk72xx/rtl87xx/ln882x where every array slot costs static RAM.

Users who need more than 6 concurrent clients can bump max_connections
up to the schema's max of 20.
2026-04-21 11:59:53 +02:00
J. Nick Koston 82e9613631 [api] Drop ConstActiveClientsView — single caller reads cleaner as indexed loop
is_connected_with_state_subscription() was the only const caller of
active_clients(). Iterating by index directly in that method lets us
drop the entire ConstActiveClientsView class from the header, leaving
a single ActiveClientsView for the mutable range-for sites.
2026-04-21 11:58:58 +02:00
J. Nick Koston 250d28f69c [api] Replace clients_ std::vector with compile-time std::array + uint8_t count
Convert APIServer's client container from
`std::vector<std::unique_ptr<APIConnection>>` to a compile-time sized
`std::array<..., MAX_API_CONNECTIONS>` with an inline `uint8_t
api_connection_count_` tracking the active slice.

Why:
- Eliminates a persistent heap-held buffer (one of APIServer's
  fragmentation sources). The vector grows via doubling reallocations
  (0 -> 1 -> 2 -> 4 -> 8) and keeps its capacity for the life of the
  process.
- Kills ~100-150 bytes of `_M_realloc_insert` template instantiation
  per build (measured: -92 B on ESP32-S3, -152 B on ESP8266).
- is_connected() becomes `api_connection_count_ != 0` — a single-byte
  load, down from two pointer loads + compare.
- The count lives in what was already 1 byte of padding after
  shutting_down_, replacing the removed max_connections_ runtime
  setter. Zero size overhead in that slot.
- Max connections is now a compile-time constant via
  cg.add_define("MAX_API_CONNECTIONS", ...), so the accept cap check
  and the array size derive from the same value.

Also lower default max_connections on mid/high-RAM platforms from 8 to
6. With a compile-time array, every slot costs 4 bytes of static RAM
whether a connection ever uses it or not — 6 covers HA + dashboard +
spares without paying the full 8-slot static cost. esp8266 stays at 4,
rp2040 stays at 4.

Mechanical churn:
- 13 `for (auto &c : this->clients_)` -> `for (auto &c : this->active_clients())`,
  where active_clients() returns a pointer-range view over the active
  slice.
- `.empty()` / `.size()` -> comparisons against api_connection_count_.
- `.emplace_back()` / `.pop_back()` / `.back()` rewritten in the
  swap-and-pop path to index into the array and reset() the freed slot
  (maintains the invariant that slots [count, N) are always nullptr).

Measured on ESP32-S3 (zwave-proxy-seeedw5500, N=6):
  RAM: 32464 -> 32480 B  (+16 B static, but no heap buffer)
  Flash: 469011 -> 468919 B  (-92 B)

Measured on ESP8266 (basic8266, N=4):
  RAM: 29268 -> 29268 B  (unchanged at this granularity)
  Flash: 323847 -> 323695 B  (-152 B)
2026-04-21 11:54:13 +02:00
J. Nick Koston 110911eddd Merge remote-tracking branch 'upstream/zwave-api-is-connected-inline' into integration 2026-04-21 11:27:18 +02:00
J. Nick Koston e99492a6e3 Merge branch 'zwave-proxy-inline-loop-fast-paths' into integration 2026-04-21 11:27:14 +02:00
J. Nick Koston 3a72deddf9 [core] Address review: drop redundant defines.h include from util.cpp
util.h now includes defines.h unconditionally, so the explicit include in
util.cpp is duplicative.
2026-04-21 11:26:45 +02:00
J. Nick Koston 413b9ca9ad [zwave_proxy] Address review: pin enum ordering and document slow-path precondition
Add static_asserts that the SEND_ACK/CAN/NAK states stay contiguous, so the
inline range check in response_handler_() fails at compile time if the enum
is ever reordered.

Document that process_uart_slow_() requires available() > 0 at the
declaration (the .cpp definition already carries the rationale).
2026-04-21 11:25:46 +02:00
J. Nick Koston ac08d644c7 Merge branch 'zwave-proxy-inline-loop-fast-paths' into integration 2026-04-21 11:15:27 +02:00
J. Nick Koston 2578ee6061 Merge branch 'zwave-api-is-connected-inline' into integration 2026-04-21 11:15:23 +02:00
J. Nick Koston b8de93aabe [core] Inline api_is_connected() for hot-path callers
Move the api_is_connected() definition from util.cpp to util.h and mark it
ESPHOME_ALWAYS_INLINE. The body is trivial — a nullptr check on
global_api_server plus APIServer::is_connected() (which is
!clients_.empty()) — so the out-of-line call8 was pure overhead for hot
paths that check connectivity every loop tick (e.g. zwave_proxy::loop,
serial_proxy::loop).

With USE_API disabled the function collapses to "return false" at compile
time and folds away entirely.

util.h now pulls in api_server.h under USE_API. Only 25 .cpp files include
util.h and all of them are on USE_API builds in practice (wifi, safe_mode,
nextion, web_server, etc.), so the additional transitive include is
essentially free on the platforms that matter.

Measured on ESP32-S3: the call8 in ZWaveProxy::loop() is replaced by
5 inline Xtensa instructions (load global_api_server, deref, null check,
clients_ start/finish compare) — no call overhead, no stack frame.
2026-04-21 11:14:12 +02:00
J. Nick Koston f74c3c0489 [zwave_proxy] Inline loop() hot-path fast-paths for response_handler_ and process_uart_
Split response_handler_ and process_uart_ into tiny inline wrappers in the
header that short-circuit the common case, plus _slow_ bodies in the .cpp.

- response_handler_: most loop() ticks have parsing_state_ outside the three
  SEND_* states; inline the range check and skip the call8 entirely.
- process_uart_: most ticks have no UART bytes pending; inline an available()
  check and skip the out-of-line call + its stack frame. Inside the slow
  path, switch the while() to do/while() since the caller has already
  confirmed available() > 0.

ESPHOME_ALWAYS_INLINE is required — with -Os gcc otherwise clones the
wrapper into a shared \$isra\$ outline and keeps the call8.

Measured on ESP32-S3 zwave-proxy-seeedw5500 build: idle-tick out-of-line
calls in ZWaveProxy::loop() drop from 4 (response_handler_, api_is_connected,
virtual parent_->is_connected, process_uart_ which nested available()) to 3
(api_is_connected, virtual is_connected, available).
2026-04-21 11:09:57 +02:00
J. Nick Koston ecefef4e08 Merge remote-tracking branch 'upstream/debug-buf-append-str' into integration 2026-04-21 07:37:58 +02:00
J. Nick Koston 27dac71670 no strnlen on zephyr 2026-04-21 07:37:25 +02:00
J. Nick Koston dd1c8a83be Merge remote-tracking branch 'upstream/debug-buf-append-str' into integration 2026-04-21 07:05:31 +02:00
J. Nick Koston 05fd2f9a2f [debug] Migrate trivial buf_append_printf sites to buf_append_str
Convert buf_append_printf calls that use only literal format strings
or %s specifiers over to buf_append_str, which avoids pulling in
printf machinery on all platforms and keeps literals in flash on
ESP8266 via the PSTR()-wrapping macro introduced in #15738.

Format strings with numeric specifiers (PRIu32, %u, PRIX32, etc.)
are kept on buf_append_printf. Runtime %s calls in debug_esp8266.cpp
stay as-is because the ESP8266 buf_append_str macro requires a
string literal for PSTR().
2026-04-21 07:02:50 +02:00
J. Nick Koston c87c1e27f5 Merge remote-tracking branch 'upstream/decouple_scheduler_loop_cadence' into integration 2026-04-21 06:23:25 +02:00
J. Nick Koston ecdbe0f9bb [runtime_stats] report tail_us=0 on Phase A-only ticks
Previously loop_tail_start_us defaulted to loop_before_end_us, so on
ticks where Phase B was gated out (loop_interval_ not yet elapsed, no
wake, no high-frequency request) tail_us measured "time from end of
Phase A to stats recording" — i.e. gate-check + stats-prefix overhead —
and was accumulated into the per-tick "tail" bucket even though no
component tail had actually run.

That mis-attributed gate-check + stats-prefix overhead to the tail
metric, which is supposed to represent trailing overhead of the
component phase specifically (after_component_phase_ + the little bit
before record_loop_active). On a device whose loop_interval_ is raised
for power savings, Phase A-only ticks dominate and the mis-attributed
tail would skew the stats report.

Fix: gate the tail_us computation on do_component_phase. Initialize
loop_tail_start_us to 0 (unused on Phase A-only ticks) and only
subtract from loop_now_us when Phase B ran. The overhead that used to
land in "tail" now falls into "residual" (active − before − components
− tail), which is the correct bucket for per-iteration bookkeeping that
is not phase-specific.
2026-04-21 06:22:55 +02:00
J. Nick Koston d3e9ccc83a Merge remote-tracking branch 'origin/decouple_scheduler_loop_cadence' into integration 2026-04-21 06:10:22 +02:00
J. Nick Koston 66e0c866d7 [core] rename after_loop_tasks_ → after_component_phase_
Matches the before_component_phase_ / after_component_phase_ symmetry
now that Phase A and Phase B are separated. Also move the call to the
very end of the `if (do_component_phase) { ... }` block so the helper
semantically closes out the phase, rather than sitting before the
last_loop_ / now bookkeeping.

No functional change: in_loop_ is read only inside the component
iteration (disable_looping_component's swap fixup), so setting it false
at any point after the for-loop ends is equivalent. Runtime-stats tail
timing still captures the same region (the micros() sample happens
before any of the moved lines).
2026-04-21 06:08:34 +02:00
J. Nick Koston 6e37503667 Merge branch 'dev' into decouple_scheduler_loop_cadence 2026-04-21 04:55:41 +02:00
J. Nick Koston 00da5d0a26 Merge remote-tracking branch 'upstream/fast-millis-esp32' into integration 2026-04-21 04:55:03 +02:00
J. Nick Koston 02f77cce79 Merge remote-tracking branch 'upstream/dev' into fast-millis-esp32
# Conflicts:
#	esphome/core/application.cpp
2026-04-21 04:53:01 +02:00
dependabot[bot] a8bd035b62 Bump CodSpeedHQ/action from 4.13.1 to 4.14.0 (#15880)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-21 04:44:25 +02:00
J. Nick Koston f05fa45747 [sensor] Specialize throttle_with_priority NaN-only case (#15823) 2026-04-21 04:41:13 +02:00
J. Nick KostonandCopilot 78875abee4 [core] Make buf_append_str PROGMEM-aware on ESP8266 (#15738)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-21 04:40:40 +02:00
J. Nick Koston 37608c2656 [ltr390] Reduce data polling delay and timeout (#15507) 2026-04-21 04:40:24 +02:00
J. Nick Koston a5b1f3eece [core] Remove pre-sleep socket scan from fast select path (#15639) 2026-04-21 04:40:03 +02:00
J. Nick Koston 0d3a3552da [core] Move heap-allocating helpers to alloc_helpers.h/cpp (#15623) 2026-04-21 04:39:49 +02:00
J. Nick Koston 0a0176d600 [core] raise WDT_FEED_INTERVAL_MS from 3 ms to 300 ms (#15846) 2026-04-21 04:38:12 +02:00
J. Nick Koston 4cb7ea2584 [light] Force-inline LightCall::set_flag_/clear_flag_ (#15729) 2026-04-21 04:37:56 +02:00
Jonathan Swoboda a43ee15b56 [core] Fix Pvariable placement new losing subclass identity (#15881) 2026-04-20 22:33:48 -04:00
Jonathan Swoboda 213ab312d2 [image] Fix rodata bloat for multi-frame RGB565+alpha animations (#15873) 2026-04-20 16:27:34 -04:00
Kevin Ahrendt 94f30d5950 [micro_wake_word] Use ESPMicroSpeechFeatures from Espressif registry (v1.2.3) (#15879) 2026-04-20 16:26:47 -04:00
Elvin Luff 6af341bb5b [epaper_spi] Support SSD1683 and GDEY042T81 4.2 inch display (#13910) 2026-04-20 09:34:31 -04:00
Clyde Stubbs 82656cb0cf [mipi_dsi] Add Seeed reTerminal d1001 display (#15867) 2026-04-20 09:28:52 -04:00
Rui Marinho b72f5447c3 [modbus] Simplify payload size validation in modbus_helpers (#15838) 2026-04-20 09:24:07 -04:00
Clyde Stubbs 73b8e8ac09 [lvgl] Fix update of textarea attached to keyboard (#15866) 2026-04-20 09:15:51 -04:00
Clyde Stubbs 9459f0426d [lvgl] Fix overloads for setting images on styles (#15864) 2026-04-20 09:14:15 -04:00
Clyde Stubbs 0dae41aa22 [lvgl] Fix format of hello world page (#15868) 2026-04-20 09:13:42 -04:00
7321e6e52f [rtttl] allow any control parameters order and default value fallback (#14438)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-04-20 09:10:05 -04:00
guillempages f0c21520aa [mipi_rgb] Add definitions for sunton displays (#15858) 2026-04-20 21:56:56 +10:00
J. Nick Koston 574bdc9a00 Merge remote-tracking branch 'origin/decouple_scheduler_loop_cadence' into integration 2026-04-20 12:06:28 +02:00
J. Nick Koston 78aabf257f [core] wake_loop_threadsafe() forces a component-phase iteration
After the Phase A / Phase B split in this PR, an external producer that
called wake_loop_threadsafe() (MQTT RX, USB RX, BLE event, espnow,
camera, mWW, speakers, USB host/CDC, lwip socket, enable_loop_soon_any_context)
only got Phase A — the component phase stayed gated by loop_interval_,
so the producer's component loop() could be delayed by up to
loop_interval_ ms before draining its queued work. That breaks the
long-standing semantic of wake_loop_threadsafe().

Add a wake_request flag set by every wake_loop_* entry point and
exchange-cleared at the gate in Application::loop(). When the flag is
set, force Phase B regardless of loop_interval_.

Storage is conditional on the threading model:
  - ESPHOME_THREAD_MULTI_ATOMICS: std::atomic<uint8_t> (uint8_t, not
    bool, because GCC on Xtensa generates an indirect call for
    atomic<bool> ops — same workaround as scheduler.h)
  - ESPHOME_THREAD_SINGLE / ESPHOME_THREAD_MULTI_NO_ATOMICS: volatile
    uint8_t (8-bit aligned loads/stores are atomic on every supported
    MCU; the platform signal that follows wake_request_set provides the
    cross-thread/cross-core memory barrier)

Helpers (wake_request_set / wake_request_take) are always_inline so
IRAM_ATTR call sites stay in IRAM. Set BEFORE the platform signal so the
consumer is guaranteed to see the flag on its next gate check.

Adds an integration test that raises loop_interval_ to 2s, snapshots a
counting component's loop count, spawns a std::thread that calls
App.wake_loop_threadsafe() after 50ms, and asserts the count increments
inside a 500ms observation window. Without the fix the count would not
move for ~2s.
2026-04-20 11:52:56 +02:00
J. Nick Koston 09aca12521 Merge branch 'dev' into decouple_scheduler_loop_cadence 2026-04-20 04:31:11 -05:00
Jesse Hills b0c133201f Merge branch 'release' into dev 2026-04-20 13:53:30 +12:00