Commit Graph
28397 Commits
Author SHA1 Message Date
J. Nick Koston 5fab869286 [web_server_idf] Move has_pending_sessions_ to end to tighten layout
Keep the single-byte std::atomic<bool> as the last member of
AsyncEventSource so it consumes what would otherwise be trailing
padding instead of introducing 3 bytes of interior padding between
pending_mutex_ and on_connect_.
2026-04-24 02:57:21 -05:00
J. Nick Koston f4a055f342 [web_server_idf] Defer SSE session adoption/priming to the main loop
AsyncEventSource::handleRequest() runs on the ESP-IDF httpd task. Prior to
this change it mutated sessions_, event_buffer_, deferred_queue_, and
entities_iterator_ directly: appending the new response to sessions_,
sending the initial ping/config/sorting_groups through try_send_nodefer()
(which writes event_buffer_), and calling entities_iterator_.begin().
The main loop reads/writes the same fields from WebServer::loop() on the
main task, so every new SSE connect raced with any in-flight loop tick.

The httpd path now only performs the HTTP-level setup that requires the
live httpd_req_t (headers, initial chunk, sess_ctx/free_ctx, fd_, send
override) and parks the response on pending_sessions_ under a mutex.
The main loop checks a std::atomic<bool> fast-path gate per tick; when
set it swaps the pending list out under the lock and then — outside the
lock — pushes into sessions_, invokes on_connect_, and calls prime_()
which performs the initial sends and starts entities_iterator_.

sessions_, event_buffer_, deferred_queue_, and entities_iterator_ are
now mutated exclusively from the main loop. fd_ remains the only
cross-thread signal (std::atomic<int> cleared by destroy() on the
httpd/tcpip task).
2026-04-24 02:55:23 -05:00
J. Nick Koston eceb534895 [deep_sleep] Fix sleep_duration codegen type to uint32_t (#15965) 2026-04-24 07:19:59 +00:00
J. Nick Koston 811d4ddfbd [mdns] Tighten ESP8266 guards, expand window to 15s, document derivation
- mdns_esp8266.cpp: narrow setup()'s listener block and on_ip_state from
  USE_MDNS_EVENT_DRIVEN_POLLING to USE_MDNS_WIFI_LISTENER. ESP8266 has no
  ethernet so the practical effect is identical, but the tighter guard is
  symmetric with mdns_rp2040.cpp and more defensible if the listener macros
  ever drift.
- MDNS_POLL_WINDOW_MS 12000 → 15000. The RFC-compliant LEAmDNS phase is ~9s
  worst case, but a blocked main loop (long component setup, WiFi scan, flash
  writes) can stretch the deadlines between our polls. 6s of margin absorbs
  that without materially extending the steady-state quiet period.
- Header comment for MDNS_POLL_WINDOW_MS now breaks down the 9s derivation
  with the exact LEAmDNS constants (MDNS_PROBE_DELAY × MDNS_PROBE_COUNT +
  MDNS_ANNOUNCE_DELAY × MDNS_ANNOUNCE_COUNT + jitter + debounce hop) so
  future library bumps can be justified against the same math.
2026-04-23 21:48:18 -05:00
tomaszduda23andCopilot 404620b99c [deep_sleep][logger][zephyr][zigbee] add deep sleep support with zigbee wakeup (#13950)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-23 22:31:46 -04:00
J. Nick Koston 097377f7b5 Merge remote-tracking branch 'origin/mdns-event-driven-polling' into integration 2026-04-23 21:30:46 -05:00
J. Nick Koston fad3912997 [mdns] Restore USE_MDNS_EVENT_DRIVEN_POLLING guards around listener definitions
clang-tidy CI compiles with the raw esp8266-arduino-tidy env's flags — no
Python codegen, and it doesn't pick up the feature defines from defines.h in
the same way a real IDE does. USE_WIFI_IP_STATE_LISTENERS ends up undefined,
the derivation in mdns_component.h doesn't fire, and the class doesn't declare
start_polling_window_ / on_ip_state / the MDNS_POLL_* constants. Removing the
guards in the platform cpps made those definitions dangle.

Keep the listener-specific definitions under #ifdef USE_MDNS_EVENT_DRIVEN_POLLING.
The Python validator still enforces the runtime invariant (wifi or ethernet
must be present on ESP8266/RP2040), so production builds always fire through
the listener path; this is purely about what the tidy compiler sees.
2026-04-23 21:20:03 -05:00
J. Nick Koston c74931aecb [mdns] Revert defines.h USE_MDNS_EVENT_DRIVEN_POLLING / USE_MDNS_WIFI_LISTENER adds
Copilot flagged that defining these unconditionally in defines.h breaks static
analysis on platforms that don't meet the derivation's platform+listener guard:
USE_MDNS_WIFI_LISTENER enables 'public wifi::WiFiIPStateListener' inheritance in
the class declaration, but wifi_component.h is only #include'd inside the
mdns_component.h derivation block. On analysis envs where the derivation guard
is false (non-ESP8266/RP2040, or missing listener feature flags), the inherit
fires without the header in scope, causing 'use of undeclared identifier' and
related errors.

Letting the derivation in mdns_component.h be the sole source of truth keeps the
define and its corresponding include atomically linked.
2026-04-23 20:55:22 -05:00
3ccaa771a7 [sendspin] Add a group media player controller (PR3) (#15948)
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-04-24 01:46:25 +00:00
J. Nick Koston fad9210cb9 [mdns] Drop redundant USE_MDNS_EVENT_DRIVEN_POLLING guards in platform .cpp files
The Python validator enforces that wifi or ethernet is configured on ESP8266/
RP2040, so USE_WIFI_IP_STATE_LISTENERS (or the ethernet equivalent) is always
requested in production, which always defines USE_MDNS_EVENT_DRIVEN_POLLING via
mdns_component.h's derivation. defines.h also declares it unconditionally for
static analysis. The #ifdef guards around start_polling_window_, setup()'s
listener subscription, and on_ip_state were dead code — a misconfiguration
should surface as a compile error, not silently strip out the method bodies.
2026-04-23 20:43:04 -05:00
J. Nick Koston b9a9067d98 [mdns] Wrap validator error string at 80 cols 2026-04-23 20:41:15 -05:00
J. Nick Koston d32db7904a [mdns] Inline MDNS.update() as a lambda in each platform's start_polling_window_
Drops the mdns_pump_update() trampoline. It existed because start_polling_window_
lived in mdns_component.cpp which can't see the platform's MDNS global. Moving
start_polling_window_ into each platform cpp lets the set_interval lambda call
MDNS.update() directly — no forward decl, no ODR comment, no indirection.
2026-04-23 20:40:38 -05:00
J. Nick Koston 4f8cfff4ac [mdns] Address review feedback: tighten guards, comments, validator
- Drop redundant wifi_component.h / ethernet_component.h includes from the platform
  .cpp files — mdns_component.h already pulls them in transitively under their
  listener defines.
- Guard initialized_ with USE_RP2040 && USE_MDNS_EVENT_DRIVEN_POLLING instead of
  USE_RP2040 alone, making the coupling with the listener-driven path explicit.
- Short comment on mdns_pump_update noting ODR is preserved by
  FILTER_SOURCE_FILES compiling exactly one platform cpp per build.
- Inline comment on ESP8266 setup() noting AFTER_CONNECTION priority is why the
  unconditional start_polling_window_() is safe.
- Collapse the disabled/platform early-return in _require_network_interface; drop
  the redundant CORE.using_arduino check (ESP8266/RP2040 are always Arduino).
- Add USE_MDNS_EVENT_DRIVEN_POLLING and USE_MDNS_WIFI_LISTENER to defines.h for
  static-analysis discoverability (ethernet listener is mutually exclusive with
  wifi on these platforms, so one representative is enough).
2026-04-23 20:34:29 -05:00
Kevin AhrendtandCopilot b4a86e46b2 [sendspin] Add controller role and sendspin.switch action (PR2) (#15929)
Co-authored-by: Copilot <copilot@github.com>
2026-04-23 20:22:47 -05:00
J. Nick Koston ae41427523 [mdns] Enforce network-interface availability at config time on ESP8266/RP2040
Addresses Copilot review feedback: if someone enables mdns on ESP8266 or RP2040
without wifi (or ethernet on RP2040), the listener-based setup() is a no-op and
the user sees a silent failure rather than a helpful error.

FINAL_VALIDATE_SCHEMA rejects mdns on these platforms when no compatible
network component is present, naming the specific options that would satisfy
the requirement. The existing DEPENDENCIES = ["network"] covers most
misconfigurations indirectly, but an explicit network: alone (without wifi or
ethernet) slips past that check — now it fails with a clear message.
2026-04-23 20:11:48 -05:00
J. Nick Koston 6938f2b400 [mdns] Gate listener code under USE_MDNS_EVENT_DRIVEN_POLLING for clang-tidy
clang-tidy compiles mdns_esp8266.cpp / mdns_rp2040.cpp with only the tidy env's
raw build flags, without the Python codegen defines (USE_WIFI_IP_STATE_LISTENERS,
USE_ETHERNET_IP_STATE_LISTENERS, USE_MDNS_EVENT_DRIVEN_POLLING). Wrap all
listener-specific code paths under USE_MDNS_EVENT_DRIVEN_POLLING so tidy sees a
compilable translation unit with an empty setup() instead of unresolved members
on WiFiComponent / MDNSComponent.

Production builds always have the listener defines via the mdns Python
to_code()'s wifi.request_wifi_ip_state_listener() /
ethernet.request_ethernet_ip_state_listener() calls, so this is tidy-only dead
code at runtime.
2026-04-23 20:06:14 -05:00
J. Nick Koston 09fe59c5cc [mdns] Drop fallback paths, collapse everything under USE_MDNS_EVENT_DRIVEN_POLLING
mDNS on ESP8266/RP2040 always runs over a network interface (WiFi on ESP8266;
WiFi or W5500/etc. ethernet on RP2040), and every such interface already
publishes an IP state listener in tree. USE_MDNS_EVENT_DRIVEN_POLLING is
therefore always defined in production, so the fallback set_interval() paths
in both platform files and the was_connected_ bookkeeping are dead code.

Also drop the ethernet-specific test — the existing wifi and ethernet+mdns
combos are already covered by the mdns test fixtures paired with their network
component tests.

Trim redundant comments throughout: the header now documents the ~9s
probe/announce window and why update() can stop afterward in a few lines
instead of a full essay; platform files keep only the non-obvious bits (why
RP2040 needs to drive begin()/notifyAPChange() itself, why re-arming on any
listener notification is correct).
2026-04-23 19:57:48 -05:00
J. Nick Koston ceada86325 [mdns] Drive event-driven polling from Ethernet IP state events on RP2040
Extends the WiFi-only listener pattern from the previous commit to also subscribe
to EthernetIPStateListener when Ethernet is configured. RP2040 can run mDNS over a
W5500 ethernet shield without WiFi, and mDNS and WiFi are mutually exclusive on
RP2040 (the framework doesn't support both simultaneously on the CYW43/PIO paths),
so this adds the ethernet-only path without touching the WiFi path.

ESPHome's wifi and ethernet components already publish compatible IP state listener
APIs (`WiFiIPStateListener::on_ip_state` and `EthernetIPStateListener::on_ip_state`
with identical signatures). MDNSComponent multiply-inherits both when available; a
single on_ip_state() override satisfies both vtable entries.

- New `USE_MDNS_WIFI_LISTENER` / `USE_MDNS_ETHERNET_LISTENER` gates control per-
  interface subscription. `USE_MDNS_EVENT_DRIVEN_POLLING` fires if either is
  available.
- Python side now calls `ethernet.request_ethernet_ip_state_listener()` when
  ethernet is in the config (RP2040 only — ESP8266 has no ethernet driver).
- setup() seeds current state for each registered listener so an already-up
  interface still triggers MDNS.begin() + polling window under AFTER_CONNECTION
  priority.

Tests: adds `test-enabled-ethernet.rp2040-ard.yaml` covering the ethernet-only
path. Existing `test-enabled.rp2040-ard.yaml` (WiFi-only) and ESP8266 tests
continue to pass.
2026-04-23 19:41:23 -05:00
J. Nick Koston 5cb258034b [mdns] Fall back to legacy polling when WiFi IP state listener isn't available
clang-tidy CI compiles the source with the esp8266-arduino-tidy env's raw build
flags (-DUSE_ESP8266 only) without running the Python codegen that adds
USE_WIFI_IP_STATE_LISTENERS. The previous guard assumed USE_WIFI_IP_STATE_LISTENERS
would always be defined on ESP8266, so clang-tidy failed with 'no member named
add_ip_state_listener in wifi::WiFiComponent'.

Gate USE_MDNS_EVENT_DRIVEN_POLLING on USE_WIFI + USE_WIFI_IP_STATE_LISTENERS for
both ESP8266 and RP2040. When either is absent, fall back to the pre-PR behaviour:
set_interval(MDNS_UPDATE_INTERVAL_MS, MDNS.update) running forever. Python side
already only requests the listener slot when WiFi is in the config, so real
production builds on ESP8266 (which always have WiFi) continue to use the
event-driven path — only the clang-tidy static-analysis build takes the fallback.
2026-04-23 19:36:11 -05:00
J. Nick Koston 25601434d9 [mdns] Simplify listener logic: always re-arm on IP notify, drop transition tracking
ESPHome's WiFiIPStateListener only notifies on IP acquisition (GOT_IP events), not
on IP loss — on disconnect, only the WiFiConnectStateListener's disconnect path
fires (see wifi_component_esp8266.cpp:952-962 and wifi_component_pico_w.cpp:340).

The previous commit's `ip_was_up_` transition tracking was broken: after the first
IP-up event, `ip_was_up_` latched to true and never reset, so subsequent
disconnect+reconnect cycles would see has_ip=true && ip_was_up_=true and skip
re-arming the polling window.

Fix: always re-arm on any IP notification. The scheduler's set_interval/set_timeout
with a uint32_t ID already performs atomic cancel-and-add for matching IDs
(Scheduler::set_timer_common_ line 232-234), so start_polling_window_ is idempotent
and needs no explicit cancel. Drop the ip_was_up_ field and cancel_polling_window_
helper entirely.

The !has_ip branch (cancel on disconnect) was dead code: it would never fire because
the listener doesn't receive disconnect events. Removing it; the polling window will
naturally expire on its own (at most 12s of harmless MDNS.update() calls during a
disconnect that isn't followed by reconnect within the window).
2026-04-23 19:26:01 -05:00
J. Nick Koston bf7083c501 [mdns] Drive MDNS.update() polling from WiFi IP state events on ESP8266/RP2040
The Arduino LEAmDNS library only has meaningful timer-driven work during the
~9 s probe+announce phase following MDNS.begin() or _restart(): 3 probes at
250 ms + 8 announcements at 1000 ms, then all internal timeouts are set to
resetToNeverExpires(). Incoming packets are handled via the lwIP UDP RX
callback independently of update(). ESPHome does not issue service queries,
so the query cache path is always a no-op.

The previous implementation ran set_interval(50) forever — ~20 dispatches/sec,
1200+ scheduler calls per minute of pure overhead once probing completed.

This PR arms a bounded MDNS_POLL_WINDOW_MS (12 s) polling window driven by
WiFiIPStateListener events. A fresh window covers each probe/announce cycle
(boot, wifi reconnect, or internal _restart() triggered by netif changes);
outside the window there are zero scheduler dispatches and the scheduler
heap contains no mDNS items.

ESP8266 is WiFi-only in the Arduino build so the path is unconditional.
RP2040 supports W5500 ethernet without WiFi, so the listener is requested
only when WiFi is in the config; ethernet-only RP2040 builds keep the
legacy polling loop.

Scheduler IDs use uint32_t (MDNS_POLL_ID / MDNS_POLL_STOP_ID) to avoid the
name-hash/strcmp cost of string-named timers on the cancel + re-arm paths.
2026-04-23 19:19:29 -05:00
J. Nick Koston 64592c0a4a Revert "revert time"
This reverts commit 7bb3d7e4a0.
2026-04-23 18:56:22 -05:00
J. Nick Koston 7bb3d7e4a0 revert time 2026-04-23 18:51:47 -05:00
J. Nick Koston cc8b8242fa Merge remote-tracking branch 'upstream/fast-millis-esp8266' into integration 2026-04-23 18:41:36 -05:00
J. Nick Koston d632e00e7d Trim overflow comment 2026-04-23 18:41:21 -05:00
J. Nick Koston 83b76f616b Address review: fix misleading static_assert, drop %= on LX106 2026-04-23 18:40:28 -05:00
J. Nick Koston d1af72e623 Merge remote-tracking branch 'upstream/fast-millis-esp8266' into integration 2026-04-23 18:34:27 -05:00
J. Nick Koston a82b00ba12 Merge branch 'dev' into fast-millis-esp8266 2026-04-23 18:34:11 -05:00
Kevin AhrendtandCopilot ddf1426f86 [sendspin] Add initial Sendspin hub component (PR1) (#15924)
Co-authored-by: Copilot <copilot@github.com>
2026-04-23 22:09:36 +00:00
J. Nick Koston c27977389f Merge remote-tracking branch 'upstream/dev' into integration 2026-04-23 16:54:55 -05:00
J. Nick Koston 90d7bfe02e [ci] Auto-close PRs opened from a fork's default branch (#15957) 2026-04-23 16:36:32 -05:00
Kevin AhrendtandCopilot d759f1a567 [audio_http] Add a media source for playing audio from HTTP URLs (#15741)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-23 15:53:52 -05:00
J. Nick Koston 52f47d0b91 [core] Import yaml_util once per build in generate_cpp_contents
Address copilot review: wrap_to_code is called once per component, so
the lazy `from esphome import yaml_util` inside it was doing a
(cached) sys.modules lookup per component. Move the import up to
generate_cpp_contents, pass the module down, rename the helper to
_wrap_to_code to mark it private.
2026-04-23 14:57:31 -05:00
J. Nick Koston 835b9a55a0 Merge remote-tracking branch 'upstream/dev' into integration 2026-04-23 14:53:33 -05:00
J. Nick Koston 87fa5a2d48 [ci] Reset import-time baseline to CI-observed 91ms
CI measured 90.6ms after the lazy-import refactor lands. Set baseline to
91ms with 15% margin (ceiling 104.65ms, ~14ms headroom for GHA variance
over the observed measurement).
2026-04-23 14:47:50 -05:00
J. Nick Koston 5d624c7f76 [core] Lazy-load esphome.core.config from loader and config
The top-level `import esphome.core.config` in `esphome/loader.py` (used
only to register the "esphome" pseudo-component in `_COMPONENT_CACHE`)
was pulling `esphome.automation` + `esphome.config_validation` eagerly
into every `esphome` CLI invocation, even fast paths like
`esphome version`. Move the cache registration into `_lookup_module`,
triggered on first `get_component("esphome")`.

Also defers the two `esphome.core.config` call sites inside
`esphome/config.py` (CoreFinalValidateStep and validate_config), which
were the other eager entry point.

Local `import esphome.__main__` drops from ~49ms to ~46ms; more
importantly, `esphome.core.config` / `esphome.automation` no longer
appear in the top offenders table, and `esphome.config_validation`
drops from 4.7ms self to 2.0ms self on CI.
2026-04-23 14:47:10 -05:00
J. Nick Koston fd63a67ad8 [ci] Lower import-time budget after lazy-import refactor
CI now measures 81.2ms after the zeroconf/writer/yaml_util lazy loads.
Reset baseline to 82ms with the 15% margin (ceiling 94.3ms, ~13ms
headroom for GHA variance).
2026-04-23 14:47:10 -05:00
J. Nick Koston fa055b9e49 [core] Lazy-load zeroconf/writer/yaml_util in __main__
Every \`esphome\` CLI invocation pays the cost of whatever \`esphome/__main__.py\`
imports at module scope before the requested command even runs. This
moves three heavy imports into the functions that actually use them:

- \`esphome.zeroconf\` (discover_mdns_devices): only needed for the
  name_add_mac_suffix OTA discovery path.
- \`esphome.writer\`: only needed by compile/clean paths.
- \`esphome.yaml_util\`: only needed by codegen, config dump, and rename.

Local measurement drops from ~75ms to ~47ms (-37%) for a cold
\`python -c 'import esphome.__main__'\`. The zeroconf chain alone accounts
for most of the gain — the case that motivated the CI budget check.

Also adds a module-level note explaining the intent so future PRs don't
innocently promote these back to the top.

Retargets four test patches from \`esphome.__main__.discover_mdns_devices\`
to \`esphome.zeroconf.discover_mdns_devices\` now that the symbol is only
bound inside the function that calls it.
2026-04-23 14:47:10 -05:00
J. Nick Koston 99c80b7dfa [ci] Address copilot review feedback on import-time check
- determine-jobs: trigger on changes to requirements_test.txt too; that
  file is hashed into the venv cache key and installed during
  restore-python, so a change there can alter the import environment.
- ci.yml: merge the --check and --har steps so we only run
  importtime-waterfall once per job (was measuring twice: ~7-8s of
  wasted CI time per run). Uses the new script/check_import_time.py
  --check --har <path> combination; the HAR reflects the same
  measurement that produced the pass/fail decision.
- script/check_import_time.py: refactor the CLI so --har is a standalone
  option rather than a mutually-exclusive mode. --check and --update
  each accept an optional --har PATH that writes the HAR from the same
  subprocess invocation. Plain --har is still supported for local use.
- tests: add tests/script/test_check_import_time.py covering HAR parsing,
  root lookup, offender ranking/dedup, budget round-trip, and the three
  --check exit paths (pass, regression, missing budget) plus the new
  --check --har combined write. Add requirements_test.txt case to the
  should_run_import_time parametrized test.
2026-04-23 14:47:03 -05:00
J. Nick Koston b8c6bd69c9 [ci] Cache importtime-waterfall in the shared venv
The import-time job previously did a `pip install` on every run (~1s
and some noisy output). Moving the pin to `requirements_test.txt` bakes
it into the venv that `common` already builds and caches, so the job
restores it from the cache and skips the install step.
2026-04-23 14:42:18 -05:00
J. Nick Koston 6b7e834174 [ci] Always print top import-time offenders, not just on regression
Seeing the top contributors every run — regression or not — gives
reviewers and future-PR authors a running reference for which imports
are currently most expensive. That's the data we need to prioritize
lazy-import refactors, so there's no reason to hide it on pass.
2026-04-23 14:23:27 -05:00
J. Nick Koston 64e000de13 [ci] Tighten import-time margin to 15%
CI measured 126.2ms against 123ms baseline + 25% ceiling (153.8ms). That's
27.6ms of headroom — enough slack that a real regression could hide under
it. Drop to 15% (ceiling 141.5ms, ~15ms headroom over observed). Still
well above GHA runner variance, tight enough to catch the class of
regression we care about (zeroconf-style top-level import additions).
2026-04-23 14:22:33 -05:00
J. Nick Koston 900232be21 [ci] Parse importtime-waterfall HAR JSON; raise budget for CI runner
The previous parser walked `-X importtime` stderr by hand (string-splitting
on `|`, indent-width math). Replace it with a load of the HAR JSON that
importtime-waterfall already produces: each entry carries the module name,
self-time, and cumulative — no tree reconstruction needed. Drops the
hand-rolled retry loop too, since importtime-waterfall does best-of-6
internally.

The committed budget (75.2ms) was seeded on a fast local machine; GHA
runners measured 122.8ms, so the first CI run tripped the check. Reset
the baseline to 123ms with a 25% margin (ceiling ~154ms) to absorb GHA
variance. Tighten later once we have several data points.

Adds unit tests for should_run_import_time across the trigger matrix and
wires the new mock into the existing test_main_* suite.
2026-04-23 14:20:31 -05:00
J. Nick Koston 0dd2a57ba4 [ci] Add import-time regression check for esphome.__main__
Adds a CI gate that runs `python -X importtime -c "import esphome.__main__"`
via importtime-waterfall's best-of-N harness, compares the root cumulative
time against a checked-in budget (script/import_time_budget.json, seeded
at 75.2ms with a 15% margin), and fails the build when top-level imports
regress.

The CLI pays this cost on every invocation before the requested command
even runs, so silently adding a top-level dep chain (the recent zeroconf
move in #13135 being the motivating case) hurts every user. The check
gives us a signal without waiting for user reports.

- script/check_import_time.py: --check / --update / --har PATH modes.
  On regression, prints a ranked top-15 offenders table by self-time.
- script/import_time_budget.json: baseline + margin_pct.
- script/determine-jobs.py: should_run_import_time() gates the job on
  esphome/**/*.py, requirements.txt, requirements_dev.txt, pyproject.toml,
  or changes to the check itself.
- .github/workflows/ci.yml: new import-time job, runs when gated and
  uploads a waterfall HAR artifact (14-day retention) for inspection.
2026-04-23 14:13:01 -05:00
f757cd1210 [zigbee][core] Add support for Zigbee binary sensors on ESP32 H2 and C6 (#11553)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-23 12:46:56 -04:00
Paulus Schoutsen 9b45b046a8 [core] Allow finding all devices as target that match mac suffix (#13135) 2026-04-23 08:43:32 -05:00
J. Nick Koston 70ae614abd [api] Fall back to plaintext for logger connections (#15938) 2026-04-23 08:23:38 -05:00
J. Nick Koston 8f9b91eece [wifi] Avoid BDK 3.0.78 wifi_event_sta_disconnected_t collision on BK72xx (#15942) 2026-04-23 08:22:17 -05:00
J. Nick Koston 3ca86fc3fc [core] Raise WDT_FEED_INTERVAL_MS to 2000ms on BK72xx (#15943) 2026-04-23 08:21:46 -05:00
J. Nick Koston b38db617a2 [core] Clean up stale includes and inline yield_with_select_ in application (#15945) 2026-04-23 08:21:05 -05:00