Compare commits

..
Author SHA1 Message Date
J. Nick Koston 9740fd3c36 Merge branch 'dev' into platformio-skip-private-probe 2026-08-30 19:31:25 -05:00
J. Nick Koston fbe306f00b [homeassistant] Add integration test for binary sensor initial state triggers (#18894) 2026-08-30 19:30:40 -05:00
J. Nick Koston fc977b7b5e [ci] Cache the integration test PlatformIO dir (#18896) 2026-08-30 17:34:32 -05:00
J. Nick Koston 2f1d3f8299 [core] Deduplicate the host program path lookup (#18897) 2026-08-30 17:34:16 -05:00
Bonne Eggleston cb54e57d84 [modbus] Yield the whole-millisecond part of the interframe wait (#18898) 2026-08-30 16:43:28 -05:00
Brandon HarveyandJ. Nick Koston fb65096ea3 [api] Add description and example metadata to user-defined actions (#18881)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-08-30 13:52:51 -05:00
esphome[bot] 0607c228f5 Bump aioesphomeapi from 46.2.1 to 46.3.0 (#18892) 2026-08-30 17:25:29 +00:00
Clyde Stubbs b9eda644cd [lvgl] Add radial and conical gradients (#18818) 2026-08-30 21:21:07 +10:00
Bonne Eggleston a811aa840c [modbus] Speed up the bus with microsecond-accurate timing (#12421) 2026-08-30 03:14:12 -05:00
J. Nick Koston cd28a8a03e [internal_temperature] Re-include esp_phy on the original ESP32 so the PHY blob links (#18884) 2026-08-29 23:25:25 -05:00
J. Nick Koston 6957576867 [esp32] Skip full rebuild on sdkconfig change with the esp-idf toolchain (#18876) 2026-08-29 03:09:12 +00:00
J. Nick Koston 3fea080ed8 [core] Hash downloaded file paths at the default data dir location (#18824) 2026-08-28 21:08:15 -05:00
Clyde Stubbs 06bc3d70c2 [mipi_rgb] Add Elecrow Crowpanel Advance 7 (#18810) 2026-08-28 22:02:27 -04:00
J. Nick Koston 2576a0e340 [ci] Drop picolibc from the cached ESP-IDF toolchains (#18871) 2026-08-29 01:50:24 +00:00
J. Nick Koston ce163b8258 [ci] Cache clang-tidy idedata and key ESP-IDF cache on Python version (#18868) 2026-08-28 20:41:57 -05:00
Bonne Eggleston bcec1d6cb8 [modbus] Decode by register address in the modbus sensor components (#18874) 2026-08-28 18:44:37 -05:00
Bonne Eggleston 1c44cec343 [modbus] Add value_at() and decode registers without the byte round-trip (#18873) 2026-08-28 17:44:00 -05:00
Clyde Stubbs c9848d8fa6 [light] Fix gamma table dead zone collapsing to 0 (#18845) 2026-08-29 08:37:20 +10:00
Frédéric Metrichdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Claude Opus 4.5pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>CopilotJ. Nick Koston
0dc0cf83de [mk2pvrouter] Add Mk2PVRouter component with sensor support (#8487)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2026-08-28 21:02:32 +00:00
dependabot[bot] ed3429d372 Bump resvg-py from 0.4.0 to 0.5.0 (#18864)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-28 15:43:52 -05:00
Bonne EgglestonandJ. Nick Koston dfac9e1f11 [pzemac] Use the typed modbus read callback with address-based extraction (#18855)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-28 15:35:10 -05:00
Bonne EgglestonandJ. Nick Koston 7255315ce2 [sdm_meter] Use the typed modbus read callback with address-based extraction (#18849)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-28 15:34:23 -05:00
Bonne EgglestonandJ. Nick Koston 346c250901 [selec_meter] Use the typed modbus read callback with address-based extraction (#18854)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-28 15:34:03 -05:00
Bonne EgglestonandJ. Nick Koston 5fc9bff371 [pzemdc] Use the typed modbus read callback with address-based extraction (#18853)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-28 15:27:45 -05:00
Bonne EgglestonandJ. Nick Koston 97f643574c [kuntze] Use the typed modbus read callback and queue all reads at once (#18852)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-28 15:27:01 -05:00
Bonne EgglestonandJ. Nick Koston e8d76b735b [havells_solar] Use the typed modbus read callback with address-based extraction (#18851)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-28 15:21:52 -05:00
J. Nick Koston 03147bc3b1 [core] Avoid double promotion in update interval and step formatting (#18825) 2026-08-28 15:12:50 -05:00
Bonne Eggleston d4348335dd [growatt_solar] Use the typed modbus read callback and drop the send-pacing state machine (#18850) 2026-08-28 15:11:42 -05:00
Bonne Eggleston 246670e22b [modbus] Add a compile-time register value decoder (#18863) 2026-08-28 14:54:31 -05:00
J. Nick Koston 0dce7f4845 [esp8266] Add the native library backend (#18558) 2026-08-28 13:50:52 -05:00
J. Nick Koston 1623fe0852 [esp32] Exclude the WiFi and Bluetooth stacks from builds that do not use them (#18599) 2026-08-28 18:49:21 +00:00
J. Nick Koston 4333870590 [core] Use uv for the ESP-IDF Python environment when available (#18838) 2026-08-28 13:43:48 -05:00
J. Nick Koston cc41fd0beb [core] Make rmtree tolerate missing paths and concurrent directory changes (#18846) 2026-08-28 13:42:42 -05:00
Bonne EgglestonandJ. Nick Koston 59397b4e28 [modbus] Build single-value register writes on a right-sized stack buffer (#18844)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-28 18:36:24 +00:00
Bonne Eggleston 768ab5b672 [modbus] Hub and helpers cleanup; tighten queue_pdu validation (#18847) 2026-08-28 13:16:41 -05:00
J. Nick Koston 8db07d0de5 [mdns] Bump espressif/mdns to 1.12.0 (#18861) 2026-08-28 12:57:45 -05:00
a1515ec662 [modbus_controller] Replace register_count/force_new_range with reuse_previous_range (#18085)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-28 11:54:06 -05:00
Jesse Hills 2031be0c23 [core] Make script/setup idempotent and prepare new worktrees (#18843) 2026-08-28 14:11:38 +12:00
esphome[bot] 708b7bfb53 Update webserver local assets to 20260824-021242 (#18704) 2026-08-28 11:39:30 +12:00
J. Nick Koston 7782bc11c6 [core] Remove make_name_with_suffix std::string overloads (#18828) 2026-08-27 15:29:43 -05:00
J. Nick Koston a9a66baeba [ci] Compress the compile-test image with zstd (#18812) 2026-08-27 15:29:23 -05:00
J. Nick Koston 16d0fa1165 [core] Add step_to_accuracy_decimals benchmarks (#18826) 2026-08-27 15:28:04 -05:00
25d5cd3e14 [modbus_controller] Poll through PollingDevice; deprecate ModbusCommandItem (#18071)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-27 14:38:13 -05:00
dependabot[bot] 9598449b6b Bump CodSpeedHQ/action from 5.0.3 to 5.2.1 (#18834)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-27 14:31:11 -05:00
dependabot[bot] a8c7279b3b Bump platformdirs from 4.11.3 to 4.11.4 (#18835)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-27 14:30:53 -05:00
rwrozellepre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>Jonathan SwobodaClaude Sonnet 4.6
c3c69c730c [openthread] fix shutdown (#16332)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-27 12:59:17 -05:00
J. Nick Koston 8a4d584906 Trim docstring 2026-08-27 10:55:57 -05:00
J. Nick Koston 7bbb1ff685 Trim comment 2026-08-27 10:55:17 -05:00
J. Nick Koston e829d5cc4b Simplify: drop unneeded patch guard, build the client eagerly, slim tests 2026-08-27 10:53:34 -05:00
J. Nick Koston 9270f3fd34 Address review: patch the prefetch subprocess, scope the in-process override to our client, isolate tests 2026-08-27 10:49:12 -05:00
J. Nick Koston 6a56dffd28 Trim comments 2026-08-27 10:39:50 -05:00
J. Nick Koston 61c0f84ed2 [core] Skip PlatformIO's private-package authorization probe 2026-08-27 10:35:55 -05:00
Bonne EgglestonandJ. Nick Koston a8c8827a5b [modbus_controller] Switch write_lambda return value is the wire value only (#18788)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-27 10:33:16 -05:00
Bonne EgglestonandJ. Nick Koston 18b7002604 [modbus_controller] Apply the write offset byte-accurately on switch and output (#18787)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-27 08:43:51 -05:00
51105a25db [tuya] Add water_heater platform (#17323)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2026-08-27 20:55:51 +12:00
J. Nick Koston fb132b4421 [improv_serial][esp32_improv] Use buffer based RPC response builder (#18793) 2026-08-27 01:01:35 -05:00
guillempages 20b38ac8b0 [runtime_image] Add support for QOI images (#16945) 2026-08-27 16:37:20 +12:00
Keith BurzinskiandJ. Nick Koston cdd892a526 [provisioning] Shut down the Wi-Fi AP and captive portal when the window closes (#17466)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-27 15:16:25 +12:00
J. Nick Koston b99e7f5ae2 [core] Install prefetched PlatformIO packages with parallel extraction (#18775) 2026-08-27 03:02:02 +00:00
J. Nick Koston e55a8aeabe [api] Drop connection instead of crashing when buffer allocation fails (#18803) 2026-08-27 14:49:12 +12:00
J. Nick Koston a8094ed548 [docker] Parallelize the image's PlatformIO library preinstall (#18777) 2026-08-27 14:45:47 +12:00
J. Nick Koston 2104096f02 [core] Prefetch PlatformIO packages in parallel (#18769) 2026-08-27 14:42:30 +12:00
J. Nick Koston e53870085b [improv_serial] Add uart bus support and host integration test (#18794) 2026-08-26 21:13:47 -05:00
J. Nick Koston b62305793d [improv_base] Bump Improv library to 1.2.7 (#18809) 2026-08-27 01:40:12 +00:00
Jonathan Swoboda a1b29b0d0b [external_components] Log when a source overrides built-in components (#18805) 2026-08-26 20:56:57 -04:00
n-IA-hane e458a38f89 [spi] Use PSRAM DMA for external buffers (#18699) 2026-08-27 10:55:39 +10:00
J. Nick Koston 39177402dd [api] Deprecate media player supports_pause field (#18801) 2026-08-26 19:46:28 -05:00
J. Nick Koston 3361d031de [api] Drop connection instead of crashing when overflow buffer allocation fails (#18802) 2026-08-26 19:46:13 -05:00
Bonne EgglestonandJ. Nick Koston 5df1c7f1d3 [modbus_controller] Writer entities as their own hub device; heap-free, byte-accurate write path (#18082)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-26 18:24:09 -05:00
guillempages 9508165797 [runtime_image] Add check for dimensions in BMP (#18800) 2026-08-26 16:36:47 -05:00
esphome[bot] 5878406918 Bump bundled esphome-device-builder to 1.13.1 (#18807) 2026-08-26 16:33:52 -05:00
esphome[bot] fedd46e648 Bump aioesphomeapi from 46.2.0 to 46.2.1 (#18804) 2026-08-26 20:55:29 +00:00
dependabot[bot] d28b17c619 Bump filelock from 3.32.3 to 3.32.4 (#18799)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-26 19:26:45 +00:00
Josef Zweck f17ef133f3 [hoermann_hcp] Add buttons to hoermann_hcp (#18544) 2026-08-26 14:27:16 -04:00
Leonardo Rivera 34e7453677 [climate] Don't restore a saved mode the device no longer supports (#18296) 2026-08-26 14:26:31 -04:00
Frédéric Metrich 8a89f3075f [emontx] Fix sensor storage initialization ordering (#18771) 2026-08-26 14:03:10 -04:00
MakerYuichianddoraemon2200 5328813814 [time] Silence compiler warning by initializing transit variables (#18715) (#18723)
Co-authored-by: doraemon2200 <106516578+doraemon2200@users.noreply.github.com>
2026-08-26 12:43:55 -04:00
Keith Burzinski 1cbe3a49b2 [remote_transmitter] ISR-driven transmission and non_blocking support on BK7231N/BK7238 (#18660) 2026-08-26 11:30:07 -05:00
guillempages 5c79c92c06 [runtime_image] Add FILTER_SOURCE_FILES (#18768) 2026-08-26 11:26:39 -05:00
J. Nick Koston 7b4894da03 [mdns] Skip MDNS.update() while the ESP8266 radio cannot transmit (#18785) 2026-08-26 15:16:44 +00:00
Clyde Stubbs 4eb85a24c2 [mipi_spi] Fix dimensions for jc3636518v2 (#18786) 2026-08-26 10:04:57 -05:00
Bonne Eggleston 1e7c48e2cf [modbus_controller] Add integration tests for register offset, response size and write buffer (#18741) 2026-08-26 08:35:46 -05:00
IreuN 6ef13cc8de [climate_ir_lg] advanced vert. swing, setting temp. in heat/cool mode, jet mode decoding, other fixes + refactor (#10875) 2026-08-25 22:26:50 -07:00
Keith Burzinski e37a540fb7 [esp32] Apply custom eFuse MAC as base MAC for all interfaces (#18452) 2026-08-25 23:59:37 -05:00
J. Nick Koston 9baff76520 [api] Treat homeassistant.event variables as lambdas (#18759) 2026-08-26 16:29:14 +12:00
J. Nick KostonandCopilot Autofix powered by AI 612d58ec37 [http_request] Default watchdog_timeout from timeout on ESP32 (#18732)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-08-26 16:11:26 +12:00
Keith Burzinski cb981c2930 [remote_transmitter] ISR-driven transmission and non_blocking support on RTL8720C (#18648) 2026-08-25 22:19:54 -05:00
Clyde StubbsandClaude Sonnet 5 8b8de0c9c6 [lvgl] Fix on_value/on_update triggers for LVGL select entities (#18778)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 13:33:39 +12:00
J. Nick Koston 9e4c52989c [esp8266] Add the native framework and toolchain installer (#18557) 2026-08-25 20:24:47 -05:00
J. Nick Koston 7ff56c62f9 [core] Add shared registry, ninja, and cache infrastructure for native toolchains (#18570) 2026-08-26 13:23:16 +12:00
J. Nick Koston 370e8fffed [core] Add the arduino toolchain seam and validate --toolchain on every platform (#18556) 2026-08-26 11:16:02 +12:00
c272c4c1a6 [lvgl] Add table widget (#18422)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2026-08-26 08:44:42 +10:00
J. Nick Koston 150f75d8f6 [esp8266] Add linker-script surgery and board build metadata for the native toolchain (#18555) 2026-08-26 10:29:57 +12:00
Jonathan Swoboda cf5ec2d277 [ci] Remove the remaining max-parallel caps (#18762) 2026-08-25 14:42:32 -04:00
J. Nick Koston 145373ed13 [api] Widen message type storage to uint16_t (#18526) 2026-08-25 18:04:41 +00:00
J. Nick Koston 8749c3d18d [esp32_ble_tracker] Warn when the scan window is above 600ms with wifi (#18725) 2026-08-25 12:40:57 -05:00
J. Nick Koston 688e833413 [core] Consolidate HTTP download paths (#18742) 2026-08-25 12:38:20 -05:00
J. Nick Kostonandpre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> 19b434f99c [espidf] Cache the discovered component list to skip the discovery configure (#18752)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-08-25 12:36:57 -05:00
Frédéric MetrichClaudepre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
0b78800e3d [emontx] Add apparent power (AP) and frequency (F) sensor support (#18586)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-08-25 11:07:18 -05:00
Jesse Hills 6a86ef0ee0 [ethernet] Add spi_id option to attach to an existing SPI bus (#18530) 2026-08-25 10:45:38 -05:00
Frédéric Metrich 4eba9ee41a [core] Fix false-positive readability-non-const-parameter in string_ref.h (#18758) 2026-08-25 10:36:00 -05:00
NoQuarrelandpre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> b579751bdf [sfa40] Add SFA40 sensor support (#17815)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-08-25 07:55:19 -04:00
J. Nick Koston bc8d0840eb [time] Skip automation.cpp when no on_time or on_time_sync automation is configured (#18750) 2026-08-25 19:50:43 +12:00
J. Nick KostonandJesse Hills 3f615f87cf [esp32] Exclude esp_http_server and nvs_sec_provider from IDF builds by default (#18748)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2026-08-25 06:16:32 +00:00
J. Nick Koston c6d423159c [esp32] Only build the mbedTLS certificate bundle when a component needs it (#18747) 2026-08-25 17:26:18 +12:00
J. Nick Koston 791590c8a3 [uptime] Skip the timestamp sensor source unless a timestamp sensor is configured (#18746) 2026-08-25 17:24:20 +12:00
J. Nick Koston 036e5cda7e [core] Remove empty automation.cpp files from sensor, switch and output (#18745) 2026-08-25 17:24:14 +12:00
J. Nick Koston 44860ff512 [time] Skip posix_tz.cpp when no timezone is configured (#18680) 2026-08-25 17:22:41 +12:00
Edvard Filistovič 90927ba788 [rp2_ble_tracker] Automation triggers and scan actions (#18717) 2026-08-24 22:57:01 -05:00
Edvard Filistovič 169c055011 [wifi] libretiny: reset the STA state on synchronous connect failure (#18719) 2026-08-25 01:25:44 +00:00
esphome[bot] 8623b6836d Bump bundled esphome-device-builder to 1.13.0 (#18743) 2026-08-25 00:44:28 +00:00
J. Nick Koston b3d9aa2d84 [core] Log which source change triggered a rebuild (#18552) 2026-08-24 18:46:43 -05:00
acd3c4f156 [lvgl] Implement list widget (#18018)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-24 23:20:20 +00:00
luar123andJ. Nick Koston 37b78ad46a [zigbee][time] Add zigbee time component on esp32 (#18656)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-24 17:24:57 -05:00
esphome[bot] a15fae6a1e Bump aioesphomeapi from 46.1.0 to 46.2.0 (#18740) 2026-08-24 22:15:09 +00:00
guillempagesandJ. Nick Koston c866add320 [runtime_image] Fix include (#18738)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-24 16:23:07 -05:00
J. Nick Koston 2a05372a51 [gpio] Fix one_wire reset busy-waiting with interrupts off when delay wraps (#18733) 2026-08-24 16:20:25 -05:00
J. Nick Koston 19866c60ee [core] Parallelize registry and tool downloads (#18662) 2026-08-24 15:59:00 -05:00
J. Nick Koston 3f0ec0b157 [espidf] Always reconfigure after component discovery (#18730) 2026-08-25 08:56:59 +12:00
Clyde Stubbs 7c5105b16e [key_collector][mipi_rgb] Suppress compilation warnings (#18721) 2026-08-25 06:36:06 +10:00
Keith BurzinskiandJ. Nick Koston b97c877992 [api] Acknowledge proxy subscribe and configuration requests (#18312)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-24 20:27:24 +00:00
guillempagesJ. Nick Kostonpre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>J. Nick Koston
ca96365143 [runtime_image] Add MIME types to image formats (#16361)
Co-authored-by: J. Nick Koston <nick@koston.org>
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@home-assistant.io>
2026-08-24 15:07:01 -05:00
esphome[bot] 3b6c6e3dad Bump aioesphomeapi from 46.0.0 to 46.1.0 (#18737) 2026-08-24 14:50:35 -05:00
dependabot[bot] ad508d79af Bump github/codeql-action/init from 4.37.7 to 4.37.8 (#18736)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-24 14:33:52 -05:00
dependabot[bot] 32a0a5c55c Bump github/codeql-action/analyze from 4.37.7 to 4.37.8 (#18735)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-24 14:33:39 -05:00
dependabot[bot] b103df1bb1 Bump ruff from 0.16.3 to 0.16.4 (#18734)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-24 14:33:24 -05:00
Bonne Eggleston c2a153a946 [modbus_controller] Add continuous polling option (#18080) 2026-08-24 13:26:26 -05:00
J. Nick Koston 49fc4be861 [ci] Run C++ unit tests when a component's Python or test override changes (#18706) 2026-08-24 12:18:17 -05:00
GafieltandJ. Nick Koston b8408e14b9 [uart] Delete ESP32 UART driver on shutdown to keep ROM output off the bus (#18684)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-08-24 11:36:23 -05:00
Boris Krivonog b11a34af5c [mitsubishi_cn105] Defer status requests after responses (#18227) 2026-08-24 10:51:09 -05:00
n-IA-haneandn-IA-hane 5ed20805ce [audio_http] Add persistent ring buffer option (#18708)
Co-authored-by: n-IA-hane <n-IA-hane@users.noreply.github.com>
2026-08-24 11:35:59 -04:00
Bonne Eggleston 7674477dd0 [modbus_controller] Brace poll-refused log to fix -Wempty-body warning (#18724) 2026-08-24 09:05:00 -05:00
2b11fbe164 [preferences] modify IntervalSyncer to be a PollingComponent (#14370)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-23 22:50:19 -05:00
J. Nick Koston 5f6a910e2d [core] Harden the shared library converter (#18685) 2026-08-23 22:24:12 -05:00
J. Nick Koston 9974ad97fa [core] Extract the shared idedata and size-summary helpers into build_helpers (#18661) 2026-08-23 21:59:19 -05:00
Jesse Hills c8ebe461d1 [ci] Skip CodSpeed benchmarks on beta and release pull requests (#18703) 2026-08-24 14:11:35 +12:00
Jesse HillsandJ. Nick Koston de1361ac0e [core] Add type annotations to component Python (#18697)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-23 20:50:27 -05:00
J. Nick Koston d877fb021c [time] Remove C++ POSIX TZ string parser (#18383) 2026-08-24 13:24:00 +12:00
Brandon Harveypre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>J. Nick KostonBluetooth Devices Bot
3c56b9e66e [wifi] Share the scan list dedupe helper with improv_serial (#18612)
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: Bluetooth Devices Bot <bluetooth@koston.org>
2026-08-24 01:05:35 +00:00
Bonne Eggleston 1ceaf699c2 [modbus_controller] Rename custom_command to custom_pdu; reject address 0; retire skip_updates (#18652) 2026-08-23 19:33:53 -05:00
J. Nick Koston d75f85b089 [network] Add tcp_send_buffer option (#18610) 2026-08-23 19:23:08 -05:00
J. Nick KostonandJesse Hills 34c1a009b6 [core] Use filter_source_files_from_defines in api, socket and ethernet (#18674)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2026-08-24 00:20:00 +00:00
J. Nick KostonJesse Hillspre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
c9e91be35f [web_server_idf] Skip multipart parser sources when OTA upload is disabled (#18534)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-08-23 23:45:26 +00:00
J. Nick Koston 31afd94a30 [noise] Declare libsodium as a direct dependency (#18692) 2026-08-23 23:35:45 +00:00
J. Nick Koston 6f7b9a1482 [gpio] Fix linker error when binary sensor only uses expander pins (#18698) 2026-08-24 11:31:48 +12:00
J. Nick Koston 6c23ad661d [bluetooth_proxy] Warn once per transaction when the TCP buffer is congested (#18605) 2026-08-24 11:30:11 +12:00
J. Nick Koston 41171e61d1 [core] Keep str_contains_ignore_case needle literals in flash on ESP8266 (#18574) 2026-08-24 11:29:05 +12:00
J. Nick Koston bdf8dec028 [i2s_audio] Skip SPDIF speaker sources when spdif_mode is off (#18676) 2026-08-24 11:16:03 +12:00
J. Nick Koston a872626fba [esp32_ble] Skip ble_advertising.cpp when advertising is not used (#18679) 2026-08-24 11:15:54 +12:00
J. Nick Koston 00bdbf8ed1 [uart] Skip uart_debugger.cpp when no debug block is configured (#18678) 2026-08-24 11:15:21 +12:00
J. Nick Koston 25fb3983cb [mqtt] Skip entity sources for entity types not in the config (#18677) 2026-08-24 11:14:40 +12:00
J. Nick Koston 4510f10491 [core] Treat a malformed build_info.json as stale instead of crashing (#18669) 2026-08-24 11:13:57 +12:00
J. Nick Koston 0ecc7045fa [core] Parse the common on/off spellings in boolean env vars (#18667) 2026-08-24 11:13:34 +12:00
J. Nick Koston 5cc1f001fa [light] Skip light_json_schema.cpp when json is not used (#18675) 2026-08-24 11:12:58 +12:00
J. Nick Koston 76caadc594 [noise] Add shared noise component and move api noise primitives (#18490) 2026-08-24 11:03:44 +12:00
Jesse Hills 7d34efecf2 Merge branch 'release' into dev 2026-08-24 10:37:28 +12:00
Jesse Hills c4096d44d8 Merge pull request #18696 from esphome/bump-2026.8.1
2026.8.1
2026-08-24 10:37:06 +12:00
Jesse Hills 68f3a6b9a5 Bump version to 2026.8.1 2026-08-24 09:19:29 +12:00
J. Nick Koston 662bf7d7f0 [esp32_ble_tracker] Scan at the default window while a GATT connection is active (#18609) 2026-08-24 09:19:28 +12:00
J. Nick Koston 0d71ab8efb [api] Fix loop stall that triggers the task watchdog when entity list sends block (#18577) 2026-08-24 09:19:28 +12:00
J. Nick Koston 4ce4768ebd [core] Retry transient network errors when downloading external files (#18538) 2026-08-24 09:19:28 +12:00
J. Nick Koston fb13327922 [esp32] Report abort and task watchdog panics correctly in crash handler (#18575) 2026-08-24 09:19:28 +12:00
J. Nick Koston 4c62420f1b [core] Dump the main.cpp config comment with sorted keys (#18653) 2026-08-24 09:19:28 +12:00
J. Nick Koston ca3f31643f [esp8266] Don't report stale crash state after hardware WDT resets (#18597) 2026-08-24 09:19:28 +12:00
J. Nick Koston d770004e0e [esp32_ble] Log connection parameter update results (#18607) 2026-08-24 09:19:28 +12:00
J. Nick Koston b28efcd545 [bk72xx_ble] Block BK7238 until the LibreTiny bonding partition fix lands (#18649) 2026-08-24 09:19:28 +12:00
esphome[bot] 6b6d27f905 Bump bundled esphome-device-builder to 1.12.4 (#18651) 2026-08-24 09:19:28 +12:00
esphome[bot] 603c3539a3 Bump bundled esphome-device-builder to 1.12.3 (#18601) 2026-08-24 09:19:28 +12:00
Jonathan Swoboda f248a85b51 [esp32_hosted] Fire on_update_available trigger when update is detected (#18591) 2026-08-24 09:19:28 +12:00
Frédéric Metrichpre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>Claude Sonnet 4.6
5a3d7e3292 [emontx] Fix sensor state_class defaults not being applied correctly (#17610)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-24 09:19:28 +12:00
J. Nick Koston c6d329db64 [espnow] Fix dump_config crash when enable_on_boot is false (#18572) 2026-08-24 09:19:28 +12:00
esphome[bot] bdb203d742 Bump bundled esphome-device-builder to 1.12.2 (#18573) 2026-08-24 09:19:27 +12:00
J. Nick Koston f509516d60 [core] Keep templated !include filenames as strings so Windows path normalization cannot corrupt them (#18549) 2026-08-24 09:19:27 +12:00
J. Nick Koston 4c856949c2 [nrf52] Rebuild the Python env when its interpreter symlink dangles (#18540) 2026-08-24 09:19:27 +12:00
esphome[bot] c90a5f4cff Bump bundled esphome-device-builder to 1.12.1 (#18541) 2026-08-24 09:19:27 +12:00
Markus Häll 964fc1ef3f [wifi] Take the lwIP core lock around sntp_servermode_dhcp() (#18511) 2026-08-24 09:19:27 +12:00
J. Nick Koston 98f138f6b2 [esp32_ble_tracker] Scan at the default window while a GATT connection is active (#18609) 2026-08-24 09:16:01 +12:00
J. Nick Koston 9179fe26ce [api] Fix loop stall that triggers the task watchdog when entity list sends block (#18577) 2026-08-24 08:38:19 +12:00
J. Nick Koston e6c49e2bd9 [core] Retry transient network errors when downloading external files (#18538) 2026-08-24 08:25:39 +12:00
guillempagesJ. Nick Kostonpre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>J. Nick Koston
d84293931b [online_image] Support image format auto-detection (#16337)
Co-authored-by: J. Nick Koston <nick@koston.org>
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@home-assistant.io>
2026-08-23 19:58:01 +00:00
Boris Krivonog 2c32ac2221 [mitsubishi_cn105] Mark configurable classes as final (#18272) 2026-08-23 19:40:08 +00:00
Bonne EgglestonandClaude dde6906f98 [modbus_client] Add continuous option to the read and send actions (#18542)
Co-authored-by: Claude <noreply@anthropic.com>
2026-08-23 13:01:34 -05:00
J. Nick Koston 4efd308345 [tests] Fix flaky pty log probe test on macOS (#18681) 2026-08-23 11:26:55 -05:00
esphome[bot] f741c274d5 Bump aioesphomeapi from 45.13.1 to 46.0.0 (#18683) 2026-08-23 15:55:42 +00:00
J. Nick Koston cf31c08a5c [core] Skip copying entity automation and filter sources when unused (#18602) 2026-08-23 09:05:04 -05:00
J. Nick Koston e7574a574b [ota] Restore lazy flash erase for ESP32 OTA with 64 KiB block erase (#18580) 2026-08-23 09:04:48 -05:00
J. Nick Koston 33484108a9 [core] Replace a damaged existing file in write_file_if_changed (#18665) 2026-08-23 09:04:24 -05:00
J. Nick Koston e697a40fda [core] Register the OTA component in dummy_main like its siblings (#18666) 2026-08-23 09:04:06 -05:00
J. Nick Koston d1f065671e [http_request] Abort OTA backend when update fails before first write (#18581) 2026-08-22 22:21:04 -05:00
J. Nick Koston 02da5c6484 [ethernet] Remove deprecated get_eth_mac_address_pretty() (#18379) 2026-08-22 22:02:41 -05:00
J. Nick Koston b2440cb655 [modbus] Remove deprecated waiting_for_response() (#18381) 2026-08-22 22:02:22 -05:00
J. Nick Koston 160d8b8f0c [web_server_idf] Remove deprecated AsyncWebServerRequest::url() (#18382) 2026-08-22 22:02:05 -05:00
J. Nick Koston 8899713ef9 [core] Remove deprecated gamma_correct and gamma_uncorrect (#18376) 2026-08-22 22:00:53 -05:00
J. Nick Koston f3cdefce21 [wifi] Remove deprecated wifi_ssid() (#18378) 2026-08-22 22:00:38 -05:00
J. Nick Koston b115813fbe [esp32] Report abort and task watchdog panics correctly in crash handler (#18575) 2026-08-22 22:00:17 -05:00
J. Nick Koston ab45ab316a [core] Remove deprecated entity_base getters (#18375) 2026-08-22 22:00:02 -05:00
J. Nick Koston 5b3a6c05bf [core] Remove deprecated esp_log_vprintf_ flash-string overload (#18377) 2026-08-22 21:59:47 -05:00
J. Nick Koston cd53681787 [wifi] Inline the remaining trivial WiFiAP and WiFiComponent accessors (#18617) 2026-08-23 02:51:39 +00:00
J. Nick Koston f0651e5c9b [radio_frequency] Inline the trivial make_call helper (#18645) 2026-08-22 21:07:34 -05:00
J. Nick Koston f24b731f95 [infrared] Inline the trivial make_call helper (#18644) 2026-08-22 21:07:14 -05:00
J. Nick Koston 01ad424d12 [datetime] Inline the trivial make_call helpers (#18643) 2026-08-22 21:07:03 -05:00
J. Nick Koston 435d522683 [text] Inline the trivial Text publish_state forwarding overloads (#18642) 2026-08-22 21:06:47 -05:00
J. Nick Koston a63c3bc0c7 [valve] Inline the trivial Valve and ValveCall accessors (#18641) 2026-08-22 21:06:31 -05:00
J. Nick Koston b83ce91528 [display] Inline the trivial DisplayPage setters and page navigation helpers (#18639) 2026-08-22 21:06:03 -05:00
J. Nick Koston a30238aab6 [wireguard] Inline the trivial Wireguard setters (#18638) 2026-08-22 21:05:51 -05:00
J. Nick Koston 47156c9a5b [mqtt] Inline the trivial MQTT client, component and sensor accessors (#18637) 2026-08-22 21:05:39 -05:00
J. Nick Koston 78240c9a46 [sprinkler] Inline the trivial Sprinkler accessors (#18636) 2026-08-22 21:04:57 -05:00
J. Nick Koston 5a9f06e584 [thermostat] Inline the trivial ThermostatClimate setters and getters (#18635) 2026-08-22 21:04:41 -05:00
J. Nick Koston ecb007da70 [deep_sleep] Inline the trivial DeepSleepComponent setters (#18634) 2026-08-22 21:04:23 -05:00
J. Nick Koston ad1a4fca36 [version] Inline the trivial VersionTextSensor setters (#18633) 2026-08-22 21:04:08 -05:00
J. Nick Koston 832a738588 [switch] Inline the trivial inverted accessors (#18632) 2026-08-22 21:03:51 -05:00
J. Nick Koston fedb3ac5c1 [fan] Inline the trivial Fan call helpers (#18631) 2026-08-22 21:03:38 -05:00
J. Nick Koston cb4e55e444 [cover] Inline the trivial Cover and CoverCall accessors (#18630) 2026-08-22 21:03:15 -05:00
J. Nick Koston 3ef5a8e6a4 [water_heater] Inline the trivial visual override setters (#18629) 2026-08-22 21:03:07 -05:00
J. Nick Koston 0e915e9b8b [core] Inline the ESPTime::strftime std::string overload (#18628) 2026-08-22 21:02:58 -05:00
J. Nick Koston dba3b287dd [api] Inline the trivial APIServer accessors (#18627) 2026-08-22 21:02:14 -05:00
J. Nick Koston ce019f508d [safe_mode] Inline the trivial set_safe_mode setters (#18626) 2026-08-22 21:02:04 -05:00
J. Nick Koston 5c2286cc4a [climate] Inline the trivial visual override setters (#18624) 2026-08-22 21:01:55 -05:00
J. Nick Koston efc0a94112 [text_sensor] Inline the trivial TextSensor forwarding overloads (#18623) 2026-08-22 21:01:42 -05:00
J. Nick Koston c60062c418 [sensor] Inline the trivial ExponentialMovingAverageFilter setters (#18622) 2026-08-22 21:01:28 -05:00
J. Nick Koston 763a1d9371 [select] Inline the trivial Select accessors (#18621) 2026-08-22 21:01:16 -05:00
J. Nick Koston 4db1666024 [light] Inline the trivial LightState accessors (#18620) 2026-08-22 21:00:59 -05:00
J. Nick Koston 0dc69aab1e [logger] Inline the trivial Logger accessors (#18619) 2026-08-22 21:00:49 -05:00
J. Nick Koston a282cb095e [ethernet] Inline the trivial EthernetComponent setters (#18618) 2026-08-22 21:00:39 -05:00
J. Nick Koston 259e7182a3 [esp32] Exclude esp_gdbstub from the build by default (#18604) 2026-08-22 20:59:55 -05:00
J. Nick Koston 74bdf275d2 [core] Dump the main.cpp config comment with sorted keys (#18653) 2026-08-22 20:58:28 -05:00
J. Nick Koston 14499223fd [esp8266] Don't report stale crash state after hardware WDT resets (#18597) 2026-08-22 20:58:13 -05:00
J. Nick Koston 6aab523dd9 [esp32_ble] Log connection parameter update results (#18607) 2026-08-22 20:57:58 -05:00
J. Nick Koston 1f31e51446 [esphome] Inline the trivial OTA port accessors (#18625) 2026-08-22 20:57:37 -05:00
J. Nick Koston c062d0c717 [ota] Log prepare, upload, and total OTA timing in espota2 (#18582) 2026-08-22 20:57:13 -05:00
J. Nick Koston dcabaedff1 [bk72xx_ble] Block BK7238 until the LibreTiny bonding partition fix lands (#18649) 2026-08-23 11:59:24 +12:00
Oliver Kleineckeandp1ngb4ck d119ad6c60 [usb_uart] Extract non-final USBUartChannelBase from USBUartChannel (#17472)
Co-authored-by: p1ngb4ck <p@local>
2026-08-22 10:47:46 -05:00
esphome[bot] 74fc2e367a Bump bundled esphome-device-builder to 1.12.4 (#18651) 2026-08-22 15:42:53 +00:00
Jesse HillsandJonathan Swoboda ea10f94376 [core] Add type annotations to component Python (10/11) (#18347)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-08-22 10:31:47 -05:00
Brandon Harveypre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>J. Nick KostonBluetooth Devices Bot
ef1d77885d [captive_portal] Show each network once in the scan list (#17847)
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: Bluetooth Devices Bot <bluetooth@koston.org>
2026-08-22 05:04:08 +00:00
Keith Burzinski dccf55eadc [remote_transmitter] Use hardware PWM on rtl87xx to fix watchdog crash (#18579) 2026-08-21 23:53:16 -05:00
Boris KrivonogandJ. Nick Koston 65704e881f [mitsubishi_cn105] Add Fahrenheit support (#15488)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-21 23:52:18 -05:00
Brandon Harvey a30e82459f [deep_sleep] Reject wakeup_pin_mode at both levels on BK72xx (#18615) 2026-08-21 23:51:19 -05:00
Brandon Harvey 5a300e92f1 [wifi] Inline the trivial WiFiScanResult accessors (#18613) 2026-08-22 04:14:56 +00:00
Keith BurzinskiandJ. Nick Koston 8e9fb0f93c [remote_transmitter] Fix repeat gap timing on LibreTiny Beken (#18585)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-21 23:10:47 -05:00
esphome[bot] 11ea819bc7 Bump aioesphomeapi from 45.12.0 to 45.13.1 (#18600) 2026-08-21 14:36:47 -05:00
esphome[bot] abc9098bd8 Bump bundled esphome-device-builder to 1.12.3 (#18601) 2026-08-21 14:05:00 -05:00
Kevin Ahrendt 00cffa09a2 [sendspin] Convert tests to package-style includes (#18588) 2026-08-21 13:05:07 -05:00
Jesse Hills aa944456e0 [core] Add type annotations to component Python (6/11) (#18343) 2026-08-21 11:11:52 -05:00
Jonathan Swoboda 409d74a48d [esp32_hosted] Fire on_update_available trigger when update is detected (#18591) 2026-08-21 11:53:44 -04:00
Frédéric Metrichpre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>Claude Sonnet 4.6
7957808f00 [emontx] Fix sensor state_class defaults not being applied correctly (#17610)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-21 09:57:58 -05:00
J. Nick Koston 52bfc0efb1 [espnow] Fix dump_config crash when enable_on_boot is false (#18572) 2026-08-20 18:06:23 -05:00
guillempagesandJ. Nick Koston 46f90d0c54 [core] Add portable strcasestr implementation named str_contains_ignore_case (#18497)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-20 17:28:17 -05:00
guillempagespre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>J. Nick KostonJ. Nick Koston
5177972c04 [runtime_image] keep decoder allocated (#18488)
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@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-20 17:27:44 -05:00
esphome[bot] ece90ee97b Bump bundled esphome-device-builder to 1.12.2 (#18573) 2026-08-20 16:59:30 -05:00
dependabot[bot] 4cfa4893ef Bump docker/setup-buildx-action from 4.2.0 to 4.3.0 in the docker-actions group (#18564)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-20 15:24:28 -05:00
dependabot[bot] edd4a86d14 Bump prek from 0.4.13 to 0.4.14 (#18563)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-20 15:02:15 -05:00
Jesse Hills 44dcd82d78 [mipi_spi] Toggle D/C only while holding the SPI bus (#18529) 2026-08-21 07:58:11 +12:00
Jesse Hills 12da2140cf [core] Add type annotations to component Python (11/11) (#18348) 2026-08-21 07:57:14 +12:00
Jesse Hills c006e9804a [core] Add type annotations to component Python (9/11) (#18346) 2026-08-21 07:56:48 +12:00
Jesse HillsandJonathan Swoboda 6343c11873 [core] Add type annotations to component Python (5/11) (#18342)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-08-21 07:56:02 +12:00
David van 't Wout 2ab09e1a77 [core] Add add_cmake_arg (#18498) 2026-08-20 12:30:33 -05:00
J. Nick Koston a3ea77c2f1 [core] Keep templated !include filenames as strings so Windows path normalization cannot corrupt them (#18549) 2026-08-20 11:32:15 -05:00
J. Nick Koston 7fe4399b94 [esp32] Grow the default IDF component exclusion list (#18536) 2026-08-20 16:11:53 +00:00
Jesse Hills 545f762568 [core] Add type annotations to component Python (8/11) (#18345) 2026-08-20 12:08:26 -04:00
Jesse Hills e83439eaae [core] Add type annotations to component Python (7/11) (#18344) 2026-08-20 12:07:10 -04:00
Jesse Hills b6a9761dae [core] Add type annotations to component Python (4/11) (#18341) 2026-08-20 10:59:51 -04:00
Jesse Hills fbe4b39a16 [core] Add type annotations to component Python (3/11) (#18340) 2026-08-20 10:58:58 -04:00
Jesse HillsandJonathan Swoboda ecca240eef [core] Add type annotations to component Python (2/11) (#18339)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-08-20 14:26:16 +00:00
J. Nick Koston 347a6155f8 [uptime] Skip the timestamp sensor source when no time component is configured (#18535) 2026-08-20 10:07:17 -04:00
J. Nick Koston a8e721abeb [esp32] Apply IDF component exclusions to native toolchain builds (#18531) 2026-08-20 10:03:49 -04:00
J. Nick Koston 2d62ea78d2 [ota] Skip partition-access OTA sources when the feature is disabled (#18532) 2026-08-20 10:00:11 -04:00
J. Nick Koston 8ef0f38f4e [ethernet] Skip the custom W5500 SPI driver for other ethernet types (#18533) 2026-08-20 09:59:30 -04:00
Jesse Hills 3c47ab42d6 [core] Add type annotations to component Python (1/11) (#18338) 2026-08-20 09:55:15 -04:00
Alar Aun aafeca5859 [modbus_controller] Brace single-statement log bodies to fix -Wempty-body (#18543) 2026-08-20 09:54:28 -04:00
J. Nick Koston 132f494195 [nrf52] Rebuild the Python env when its interpreter symlink dangles (#18540) 2026-08-20 07:59:57 -04:00
esphome[bot] 5e9de7c94b Bump bundled esphome-device-builder to 1.12.1 (#18541) 2026-08-20 00:00:44 -05:00
Markus Häll b7d0b676fc [wifi] Take the lwIP core lock around sntp_servermode_dhcp() (#18511) 2026-08-19 23:31:27 -05:00
Jesse Hills 55b45fc6fb Merge branch 'release' into dev 2026-08-20 14:32:46 +12:00
Jesse Hills 6f8dbb6fbc Merge pull request #18527 from esphome/bump-2026.8.0
2026.8.0
2026-08-20 14:32:15 +12:00
J. Nick Koston ca97c86d65 [ci] Install requirements_dev.txt when the venv cache misses (#18502) 2026-08-20 13:12:33 +12:00
Jesse Hills 828eac90f3 Bump version to 2026.8.0 2026-08-20 11:51:59 +12:00
J. Nick Koston d9359a70c1 [tests] Keep PlatformIO libdeps per xdist worker to stop a compile race (#18524) 2026-08-20 11:51:57 +12:00
Jesse Hills 29404a782c Merge branch 'beta' into dev 2026-08-20 10:32:46 +12:00
Jesse Hills e75a7a61fa Merge pull request #18523 from esphome/bump-2026.8.0b6
2026.8.0b6
2026-08-20 10:32:26 +12:00
J. Nick Koston 185f12266a [tests] Keep PlatformIO libdeps per xdist worker to stop a compile race (#18524) 2026-08-19 17:29:10 -05:00
J. Nick Koston c455991962 [ci] Key PlatformIO cache on the Python version so a runner image bump does not serve a broken LibreTiny venv (#18512) 2026-08-20 09:38:44 +12:00
Jesse Hills f735dcadc0 Bump version to 2026.8.0b6 2026-08-20 09:33:05 +12:00
J. Nick Koston 78a65eabdc [ci] Stop jobs hanging on apt by restoring the cached apt action and bounding raw apt calls (#18518) 2026-08-20 09:33:03 +12:00
b3fda9973e [image] Restore defaults:/files: support for platform entries (#18032)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-20 09:33:03 +12:00
esphome[bot] 4a85c98285 Bump bundled esphome-device-builder to 1.12.0 (#18514) 2026-08-20 09:33:03 +12:00
esphome[bot] 2c92a2498e Bump bundled esphome-device-builder to 1.11.5 (#18507) 2026-08-20 09:33:03 +12:00
esphome[bot] 74e22b5ad7 Bump bundled esphome-device-builder to 1.11.4 (#18506) 2026-08-20 09:33:03 +12:00
esphome[bot] e9e77d02a0 Bump bundled esphome-device-builder to 1.11.3 (#18505) 2026-08-20 09:33:03 +12:00
Jonathan Swoboda 7418fcce8d [ci] Stop persisting the integration test ccache (#18504) 2026-08-20 09:33:03 +12:00
Jonathan Swoboda b768e2a1ce [esp32] Fix ESP32-P4 bootloop on rev3 (v3.x) chips when only variant is set (#18500) 2026-08-20 09:33:03 +12:00
J. Nick Koston 6084314cc9 [vscode] Report the origin of an unexpected exception during validation (#18494) 2026-08-20 09:33:03 +12:00
J. Nick Koston 2df953f3d7 [platformio] Give the ccache wrapper a cmd.exe safe path (#18495) 2026-08-20 09:33:03 +12:00
Bonne EgglestonandClaude Opus 4.8 10e592fa3a [modbus] CRC scan all unknown function codes (#18483)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-20 09:33:03 +12:00
J. Nick Koston a99a8f364e [api] Bump noise-c to 0.1.21 (#18484) 2026-08-20 09:33:03 +12:00
J. Nick Koston 200a1644a5 [ci] Fail the benchmark job when the C++ benchmark build fails (#18480) 2026-08-20 09:33:03 +12:00
J. Nick Koston 9daae377fc [api] Bump noise-c to 0.1.20 (#18482) 2026-08-20 09:33:02 +12:00
J. Nick Koston 0b1065feee [ci] Stop jobs hanging on apt by restoring the cached apt action and bounding raw apt calls (#18518) 2026-08-20 09:30:34 +12:00
Clyde Stubbs 47da743d11 [ai] Add instructions for concise comments (#18522) 2026-08-19 16:09:12 -05:00
470226ca03 [image] Restore defaults:/files: support for platform entries (#18032)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-19 12:30:12 -05:00
esphome[bot] 7b7107556f Bump bundled esphome-device-builder to 1.12.0 (#18514) 2026-08-19 12:21:58 -05:00
J. Nick Koston f90b776071 [ci] Key PlatformIO cache on the Python version so a runner image bump does not serve a broken LibreTiny venv (#18512) 2026-08-19 16:39:01 +00:00
esphome[bot] e26237e57d Bump bundled esphome-device-builder to 1.11.5 (#18507) 2026-08-19 00:42:05 -05:00
esphome[bot] d2c3f749ab Bump bundled esphome-device-builder to 1.11.4 (#18506) 2026-08-18 23:24:23 -05:00
esphome[bot] b7cc271219 Bump bundled esphome-device-builder to 1.11.3 (#18505) 2026-08-18 21:57:51 -05:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 17eed7055b Bump resvg-py from 0.3.4 to 0.4.0 (#18460)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-18 22:22:51 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> b7121940c8 Update wheel requirement from <0.48,>=0.43 to >=0.43,<0.49 (#18459)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-18 22:08:23 -04:00
Jonathan Swoboda 07fa16e2e7 [ci] Stop persisting the integration test ccache (#18504) 2026-08-18 21:38:18 -04:00
Jonathan Swoboda 8aa7db15e5 [esp32] Fix ESP32-P4 bootloop on rev3 (v3.x) chips when only variant is set (#18500) 2026-08-18 21:25:09 -04:00
J. Nick Koston 8b637b339b [vscode] Report the origin of an unexpected exception during validation (#18494) 2026-08-18 19:37:10 -05:00
J. Nick Koston 4f866c563b [platformio] Give the ccache wrapper a cmd.exe safe path (#18495) 2026-08-18 19:36:57 -05:00
J. Nick Koston 7a999f9a48 [ci] Install requirements_dev.txt when the venv cache misses (#18502) 2026-08-18 19:06:55 -05:00
esphome[bot] 0a88c81d95 Bump aioesphomeapi from 45.11.0 to 45.12.0 (#18501) 2026-08-18 18:38:23 -05:00
J. Nick Koston 804e8fb856 [socket] Remove constant duplicated by the beta merge (#18496) 2026-08-18 15:44:38 -05:00
Jesse Hills 43939a9199 Merge branch 'beta' into dev 2026-08-19 08:01:33 +12:00
Jesse Hills f414a07bcd Merge pull request #18481 from esphome/bump-2026.8.0b5
2026.8.0b5
2026-08-19 08:01:14 +12:00
esphome[bot] 5c9d050ebe Bump aioesphomeapi from 45.10.3 to 45.11.0 (#18493) 2026-08-18 17:02:54 +00:00
Bonne EgglestonandClaude Opus 4.8 285a508e09 [modbus] CRC scan all unknown function codes (#18483)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-18 11:09:12 -05:00
J. Nick Koston 92f55f721f [api] Bump noise-c to 0.1.21 (#18484) 2026-08-18 14:32:37 +00:00
Jonathan Swoboda 476d540065 [ci] Fall back to files API when PR diff exceeds GitHub line limit (#18486) 2026-08-18 10:22:28 -04:00
J. Nick Koston ae730d6357 [ci] Fail the benchmark job when the C++ benchmark build fails (#18480) 2026-08-18 09:03:33 -05:00
J. Nick Koston 9823205ef3 [api] Bump noise-c to 0.1.20 (#18482) 2026-08-18 09:03:14 -05:00
Jesse Hills d1391c2b10 Bump version to 2026.8.0b5 2026-08-18 16:04:49 +12:00
J. Nick Koston 8b888f31e0 [gpio_expander][pcf8574][pca9554][tca9555][pca6416a][pi4ioe5v6408][mcp23016][mcp23xxx_base] Reject unsupported interrupt_pin options (inverted, allow_other_uses) (#18472) 2026-08-18 16:04:47 +12:00
Jesse Hills 6a247dfe91 [light] Replace rgb_order/is_rgbw/is_wrgb with channel_colors (#18474) 2026-08-18 16:01:54 +12:00
J. Nick Koston 482869fbbe [socket] Fix multi-second TCP stalls on ESP8266 by yielding to the SYS context (#18455) 2026-08-18 14:27:54 +12:00
esphome[bot] 4dea147386 Bump bundled esphome-device-builder to 1.11.2 (#18477) 2026-08-18 14:27:54 +12:00
J. Nick Koston 1fd6337254 [api] Bump noise-c to 0.1.19 (#18473) 2026-08-18 14:27:54 +12:00
esphome[bot] 4ce6d59484 Bump bundled esphome-device-builder to 1.11.1 (#18475) 2026-08-18 14:27:54 +12:00
J. Nick Koston 014cc19902 [api] Bump noise-c to 0.1.18 (#18451) 2026-08-18 14:27:54 +12:00
esphome[bot]andesphome[bot] <115708604+esphome[bot]@users.noreply.github.com> b9041566ea Bump aioesphomeapi from 45.10.2 to 45.10.3 (#18433)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-08-18 14:27:54 +12:00
J. Nick Koston 3a403c40d5 [ld2420] Drop the setup priority override so setup runs after the UART bus (#18428) 2026-08-18 14:27:54 +12:00
esphome[bot] 096e71bd67 Bump aioesphomeapi from 45.10.1 to 45.10.2 (#18357) 2026-08-18 14:27:54 +12:00
J. Nick Koston fffa902a1a [gpio_expander][pcf8574][pca9554][tca9555][pca6416a][pi4ioe5v6408][mcp23016][mcp23xxx_base] Reject unsupported interrupt_pin options (inverted, allow_other_uses) (#18472) 2026-08-18 13:32:03 +12:00
Jesse Hills 463e3833da [light] Replace rgb_order/is_rgbw/is_wrgb with channel_colors (#18474) 2026-08-18 13:19:48 +12:00
J. Nick Koston 4416aacebb [socket] Fix multi-second TCP stalls on ESP8266 by yielding to the SYS context (#18455) 2026-08-18 13:09:29 +12:00
esphome[bot] 15a626bcf3 Bump bundled esphome-device-builder to 1.11.2 (#18477) 2026-08-17 19:16:38 -05:00
J. Nick Koston a347a2e879 [api] Bump noise-c to 0.1.19 (#18473) 2026-08-17 18:58:11 -05:00
dependabot[bot] 7be4566b41 Bump platformdirs from 4.11.2 to 4.11.3 (#18468)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-17 18:33:13 -05:00
esphome[bot] 96e26c6a5f Bump bundled esphome-device-builder to 1.11.1 (#18475) 2026-08-17 17:54:23 -05:00
J. Nick Koston 7362c01c67 [core] Replace base64 lookup tables with arithmetic mapping (#18454) 2026-08-17 16:14:06 -05:00
Jesse Hills e45b4e4938 [core] Make FINAL_VALIDATE_SCHEMA functions return None (#18457) 2026-08-18 08:26:24 +12:00
J. Nick Koston e32329cc11 [core] Sync pre-commit ruff hook with requirements (0.16.3) (#18469) 2026-08-17 15:00:37 -05:00
dependabot[bot] 199e368fe2 Bump astral-sh/setup-uv from 9.0.0 to 10.0.1 (#18463)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-17 14:57:50 -05:00
dependabot[bot] 55726120db Bump esphome/workflows/.github/workflows/lock.yml from 2026.7.0 to 2026.8.1 (#18465)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-17 14:47:28 -05:00
dependabot[bot] 346ba7e831 Bump github/codeql-action/analyze from 4.37.6 to 4.37.7 (#18467)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-17 14:47:18 -05:00
dependabot[bot] 9518006724 Bump ruff from 0.16.2 to 0.16.3 (#18458)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-17 14:46:04 -05:00
dependabot[bot] 3d5f6f692f Bump filelock from 3.32.2 to 3.32.3 (#18461)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-17 14:45:41 -05:00
dependabot[bot] 4712c15c75 Bump github/codeql-action/init from 4.37.6 to 4.37.7 (#18466)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-17 14:42:24 -05:00
dependabot[bot] 9bc72529a6 Bump astral-sh/setup-uv from 9.0.0 to 10.0.1 in /.github/actions/restore-python (#18462)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-17 14:42:08 -05:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 6d20943a9c Bump esphome/workflows/.github/workflows/stale.yml from 61fd37a044cad4e9aa4303027b2a61b6a34da855 to a1c1485ab46ef41a84a6a9d8abd7fa4b7628fd70 (#18464)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-17 15:38:06 -04:00
J. Nick Koston a3af82867b [api] Bump noise-c to 0.1.18 (#18451) 2026-08-17 14:00:39 -05:00
J. Nick Koston 27483a4101 [core] Retry gh CLI calls on transient network errors in CI scripts (#18292) 2026-08-17 13:15:31 -05:00
J. Nick Koston 6d20ebc66b [socket] Shorten lwip TAG string (#18450) 2026-08-17 17:18:43 +00:00
J. Nick Koston 031a038b49 [deep_sleep] Shorten bk72xx TAG string (#18445) 2026-08-17 11:56:37 -05:00
J. Nick Koston f6c7434b2a [i2c] Shorten platform backend TAG strings (#18440) 2026-08-17 11:54:55 -05:00
J. Nick Koston 3d9fecb562 [remote_receiver] Shorten esp32 TAG string (#18447) 2026-08-17 11:54:52 -05:00
J. Nick Koston ebe93e2c68 [bluetooth_connection] Shorten platform TAG strings (#18449) 2026-08-17 11:54:35 -05:00
J. Nick Koston 1f4fcead38 [nextion] Shorten upload TAG strings (#18448) 2026-08-17 11:54:29 -05:00
J. Nick Koston 1f000ba668 [mqtt] Shorten esp32 backend TAG string (#18446) 2026-08-17 11:54:17 -05:00
J. Nick Koston e0d28d7f5c [http_request] Shorten platform backend TAG strings (#18444) 2026-08-17 11:54:03 -05:00
J. Nick Koston 47a58dd799 [internal_temperature] Shorten platform TAG strings (#18443) 2026-08-17 11:53:56 -05:00
J. Nick Koston 37bea1c153 [spi] Shorten platform backend TAG strings (#18442) 2026-08-17 11:53:48 -05:00
J. Nick Koston d1a7b8df8b [adc] Shorten platform TAG strings (#18441) 2026-08-17 11:53:42 -05:00
J. Nick Koston c01f24553c [uart] Shorten platform backend TAG strings (#18439) 2026-08-17 11:53:29 -05:00
J. Nick Koston 07e8b303b9 [ota] Shorten platform backend TAG strings (#18438) 2026-08-17 11:52:56 -05:00
esphome[bot]andesphome[bot] <115708604+esphome[bot]@users.noreply.github.com> ebb0923362 Bump aioesphomeapi from 45.10.2 to 45.10.3 (#18433)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-08-16 22:34:00 -05:00
J. Nick Koston e1c279718f [ld2420] Drop the setup priority override so setup runs after the UART bus (#18428) 2026-08-16 21:57:12 -05:00
J. Nick Koston cf764740cf [api] Create the camera image reader lazily (#18421) 2026-08-16 21:56:58 -05:00
J. Nick Koston 58d549ed4c [api] Move NoiseProtocolId off the connection object (#18420) 2026-08-16 21:56:37 -05:00
Jesse Hills 6b22d8068d Merge branch 'beta' into dev 2026-08-17 11:36:10 +12:00
Jesse Hills 443d8f1f28 Merge pull request #18424 from esphome/bump-2026.8.0b4
2026.8.0b4
2026-08-17 11:33:27 +12:00
Jesse Hills 1ec21a2245 Bump version to 2026.8.0b4 2026-08-17 10:22:41 +12:00
J. Nick Koston bb7d4c3630 [esp32] Split crash handler addr2line hint per core (#18418) 2026-08-17 10:22:39 +12:00
J. Nick Koston f42fe9af29 [core] Skip redundant ESP8266 main loop wake posts from ISR context (#18416) 2026-08-17 10:22:39 +12:00
J. Nick Koston 0bc2d71370 [bk72xx_ble] Fail early with a clear error on non BLE 5.x SoCs (#18406) 2026-08-17 10:22:39 +12:00
luar123 594c12b3d9 [zigbee] bump esp-zigbee-sdk to 2.0.4 (#18415) 2026-08-17 10:22:39 +12:00
Jonathan Swoboda bca72e9b6d [sensor] Pass NaN through the delta filter again (#18400) 2026-08-17 10:22:39 +12:00
J. Nick Koston ce09504c92 [platformio] Skip ccache when the binary on PATH fails to run (#18407) 2026-08-17 10:22:39 +12:00
esphome[bot] dda4566b9e Bump bundled esphome-device-builder to 1.11.0 (#18403) 2026-08-17 10:22:39 +12:00
Samuel Sieb 46a5665a66 [rotary_encoder] account for min and max value when resetting (#18197) 2026-08-17 10:22:39 +12:00
esphome[bot] 9161f74bb1 Bump bundled esphome-device-builder to 1.10.0 (#18389) 2026-08-17 10:22:39 +12:00
J. Nick Koston 3f01f9895f [esp32_hosted] Require ESP-IDF 5.3 or newer (#18417) 2026-08-16 12:44:49 -07:00
J. Nick Koston 801a1817b5 [esp32] Split crash handler addr2line hint per core (#18418) 2026-08-16 12:44:00 -07:00
J. Nick Koston 32c76ae828 [core] Skip redundant ESP8266 main loop wake posts from ISR context (#18416) 2026-08-16 12:43:27 -07:00
J. Nick Koston c664f5fc95 [bk72xx_ble] Fail early with a clear error on non BLE 5.x SoCs (#18406) 2026-08-16 11:06:22 -07:00
luar123 2bc4681fd6 [zigbee] bump esp-zigbee-sdk to 2.0.4 (#18415) 2026-08-16 13:33:25 -04:00
Jonathan Swoboda 646501b0ef [sensor] Pass NaN through the delta filter again (#18400) 2026-08-16 12:36:51 -04:00
J. Nick Koston de3e657d8b [platformio] Skip ccache when the binary on PATH fails to run (#18407) 2026-08-16 08:42:47 -07:00
esphome[bot] 1add726892 Bump bundled esphome-device-builder to 1.11.0 (#18403) 2026-08-15 13:36:52 -07:00
Samuel Sieb 5a000cf5e4 [rotary_encoder] account for min and max value when resetting (#18197) 2026-08-15 11:16:04 -07:00
Joppy Furr 6ed676fe32 [lvgl] Restore long_press_repeat_time functionality (#18393) 2026-08-14 23:14:53 -07:00
esphome[bot] 7cceddb8a3 Bump bundled esphome-device-builder to 1.10.0 (#18389) 2026-08-15 01:48:27 +00:00
J. Nick Koston 039b897e7b [ethernet] Defer clk_mode removal to 2026.11.0 (#18380) 2026-08-14 18:45:36 -07:00
J. Nick Koston b178f74e5d [core] Save the validated config cache on the first upload or logs run (#18367) 2026-08-14 18:45:23 -07:00
Clyde Stubbs e5224e22ae [ci] Compare merge-branch base ref against the default branch (#18385) 2026-08-15 06:18:22 +10:00
Clyde Stubbs be66e8b99c [ci] Disable CodSpeed benchmarks job outside esphome/esphome (#18372) 2026-08-15 04:33:47 +10:00
Jesse Hills 4b91c8aff4 Merge branch 'beta' into dev 2026-08-14 23:15:57 +12:00
Jesse Hills 9d7997f55a Merge pull request #18368 from esphome/bump-2026.8.0b3
2026.8.0b3
2026-08-14 22:49:40 +12:00
Jesse Hills 4db47de556 Bump version to 2026.8.0b3 2026-08-14 17:36:56 +12:00
Karl Beecken c8de632764 [core] fix PYTHONPATH leak (#18360) 2026-08-14 17:36:54 +12:00
J. Nick Koston b794b7b1d1 [core] Restore cv.parse_esphome_version as a deprecated helper (#18366) 2026-08-14 17:36:54 +12:00
J. Nick Koston add18d4e35 [core] Partially revert "Hash entity keys from the raw name to fix collisions" (#18361) 2026-08-14 17:36:54 +12:00
J. Nick Koston 02c1810c3a [core] Load component aliases from a generated registry (#18335) 2026-08-14 17:36:54 +12:00
J. Nick Koston 4f3153375a [ota] Retry uploads that fail from network errors (#18332) 2026-08-14 17:36:54 +12:00
J. Nick Koston 1c3a67b5e8 [wifi] Fix ESP8266 crash in cnx_node_search when lwIP transmits after disconnect (#18333) 2026-08-14 17:36:54 +12:00
J. Nick Koston 236ff33a09 [esp32_ble] Silence spurious warnings for local key GAP events (#18359) 2026-08-14 17:36:54 +12:00
J. Nick Koston 7c07fb48c5 [esp32_ble_tracker] Fix missed BLE advertisements with WiFi on ESP-IDF 5.5.5 (#18356) 2026-08-14 17:36:54 +12:00
J. Nick Koston d72bab79d7 [web_server_base] Stop deleting the web server on captive portal teardown (#18324) 2026-08-14 17:36:54 +12:00
Karl Beecken 617e2ec1e0 [core] fix PYTHONPATH leak (#18360) 2026-08-14 17:23:34 +12:00
J. Nick Koston 1afac0312d [core] Restore cv.parse_esphome_version as a deprecated helper (#18366) 2026-08-14 17:22:22 +12:00
J. Nick Koston b05465145f [core] Add preference key stability integration test (#18364) 2026-08-13 21:14:30 -05:00
dependabot[bot] 990fc402fd Bump bleak from 2.1.1 to 3.0.2 (#16246)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-13 20:24:02 -05:00
J. Nick Koston dd51624fbb [core] Partially revert "Hash entity keys from the raw name to fix collisions" (#18361) 2026-08-13 20:06:48 -05:00
dependabot[bot] 45a056e337 Bump platformdirs from 4.11.1 to 4.11.2 (#18363)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-13 18:54:51 -05:00
dependabot[bot] 37782f7206 Bump prek from 0.4.12 to 0.4.13 (#18362)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-13 18:54:37 -05:00
J. Nick Koston 945c2458b3 [core] Load component aliases from a generated registry (#18335) 2026-08-13 13:31:19 -05:00
J. Nick Koston 7420d23867 [ota] Retry uploads that fail from network errors (#18332) 2026-08-13 13:31:06 -05:00
J. Nick Koston 191686c5b3 [wifi] Fix ESP8266 crash in cnx_node_search when lwIP transmits after disconnect (#18333) 2026-08-13 13:30:53 -05:00
J. Nick Koston 137351fa8d [esp32_ble] Silence spurious warnings for local key GAP events (#18359) 2026-08-13 13:27:01 -05:00
J. Nick Koston db5173697a [esp32_ble_tracker] Fix missed BLE advertisements with WiFi on ESP-IDF 5.5.5 (#18356) 2026-08-13 13:22:48 -05:00
Oliver KleineckeOliver Kleineckepre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
c7940382a9 [const] move CONF_LABEL to components/const (#18354)
Co-authored-by: Oliver Kleinecke <kleinecke.oliver@googlemail.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-08-13 14:18:22 -04:00
esphome[bot] 87045ab9c0 Bump aioesphomeapi from 45.10.1 to 45.10.2 (#18357) 2026-08-13 11:14:14 -05:00
Oliver KleineckeandOliver Kleinecke f1c4086778 [const] Move CONF_SLOT to components/const (#18350)
Co-authored-by: Oliver Kleinecke <p1ngb4ck@users.noreply.github.com>
2026-08-13 23:36:11 +12:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Jonathan Swoboda
f337d0acff Bump pylint from 4.0.6 to 4.0.7 (#18320)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-08-13 02:13:29 +00:00
J. Nick Koston 9cc05b30d4 [web_server_base] Stop deleting the web server on captive portal teardown (#18324) 2026-08-12 21:09:01 -05:00
Jesse Hills e0b68c4d6d Merge branch 'beta' into dev 2026-08-13 14:06:46 +12:00
Jesse Hills 9c3407cfdd Merge pull request #18334 from esphome/bump-2026.8.0b2
2026.8.0b2
2026-08-13 14:06:28 +12:00
Jesse Hills d3e27054f6 Bump version to 2026.8.0b2 2026-08-13 13:30:47 +12:00
J. Nick Koston bd58b5c8b3 [core] Retry framework downloads on transient network errors (#18330) 2026-08-13 13:30:46 +12:00
J. Nick Koston c1a326f32e [core] Don't block logs startup on MQTT IP discovery when addresses are known (#18313) 2026-08-13 13:30:46 +12:00
J. Nick Koston a14ea0e8fa [ld2420] Fix out-of-bounds read when device reports unknown command error (#18322) 2026-08-13 13:30:46 +12:00
esphome[bot] 48d6368ff9 Bump bundled esphome-device-builder to 1.9.6 (#18328) 2026-08-13 13:30:46 +12:00
Kevin Ahrendt 83cff59fdd [sendspin] Bump sendspin-cpp to v0.7.2 (#18316) 2026-08-13 13:30:46 +12:00
esphome[bot] 89489b1f0d Bump aioesphomeapi from 45.10.0 to 45.10.1 (#18318) 2026-08-13 13:30:46 +12:00
J. Nick Koston 7569a7b5ce [usb_uart] Fix uint32_t format specifier warning in pl2303 (#18310) 2026-08-13 13:30:45 +12:00
J. Nick Koston 8e624b4117 [core] Retry framework downloads on transient network errors (#18330) 2026-08-13 12:14:50 +12:00
J. Nick Koston 787a909aa4 [core] Don't block logs startup on MQTT IP discovery when addresses are known (#18313) 2026-08-13 12:11:32 +12:00
J. Nick Koston 905485b673 [ld2420] Fix out-of-bounds read when device reports unknown command error (#18322) 2026-08-13 11:49:34 +12:00
esphome[bot] 99677390e0 Bump bundled esphome-device-builder to 1.9.6 (#18328) 2026-08-12 16:27:14 -05:00
Kevin Ahrendt f2121130f9 [sendspin] Bump sendspin-cpp to v0.7.2 (#18316) 2026-08-12 12:49:54 -04:00
esphome[bot] 1a01c34ec4 Bump aioesphomeapi from 45.10.0 to 45.10.1 (#18318) 2026-08-12 16:33:45 +00:00
J. Nick Koston 3c46cc9c35 [usb_uart] Fix uint32_t format specifier warning in pl2303 (#18310) 2026-08-12 10:59:24 -04:00
Jesse Hills e192ec8fee Merge branch 'beta' into dev 2026-08-13 00:29:08 +12:00
Jesse Hills ec6b4263a2 Merge pull request #18314 from esphome/bump-2026.8.0b1
2026.8.0b1
2026-08-13 00:28:40 +12:00
Jesse Hills 8a1aa5753d Bump version to 2026.9.0-dev 2026-08-12 22:53:42 +12:00
Jesse Hills 3e4661fe1e Bump version to 2026.8.0b1 2026-08-12 22:53:42 +12:00
J. Nick Koston 22153be4cd [core] Add esphome logs over web_server HTTP SSE (#17110) 2026-08-12 22:00:07 +12:00
3f490fe1ed [internal_temperature] Read the RP2 on-die sensor directly instead of via the Arduino API (#18262)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-08-12 21:42:05 +12:00
J. Nick Koston 58a42fe5c2 [core] Batch remote file downloads during config validation (#18069) 2026-08-12 18:44:35 +12:00
Josef ZweckJ. Nick Kostonpre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
25c0c2c97b [hoermann_hcp] Add garage light control (#18190)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-08-12 06:10:23 +00:00
J. Nick Koston 622942482c [rp2] Size the lwIP segment pool and heap for concurrent senders (#18257) 2026-08-12 17:30:17 +12:00
Keith Burzinski cffd775450 [ethernet][network][wifi] Arbitrate the default route from the network priority list (#17797) 2026-08-12 16:21:37 +12:00
J. Nick Koston 8d87ba34d9 [api] Move the generic buffer-full log to very verbose (#18300) 2026-08-12 04:04:07 +00:00
J. Nick Koston 08c6585915 [esp32_ble_tracker] Don't log an error when a scan stop is already in flight (#18306) 2026-08-12 16:03:28 +12:00
J. Nick Koston 3ef74d17af [bluetooth_proxy] Give partial advertisement batches 200ms to fill on Wi-Fi (#18303) 2026-08-12 16:02:51 +12:00
J. Nick Koston 5d04c1dc18 [esp32_ble_tracker] Demote scan-state echoes to verbose (#18302) 2026-08-12 16:01:45 +12:00
J. Nick Koston a3d599ac69 [bluetooth_connection] Demote rp2 backend connection logs to verbose (#18301) 2026-08-12 16:00:37 +12:00
J. Nick Koston a4e05cd1c8 [bluetooth_proxy] Move per-advertisement logging to very verbose (#18299) 2026-08-12 15:59:41 +12:00
J. Nick Koston 1758653330 [voice_assistant] Do not consume the audio chunk when the send is refused (#18295) 2026-08-12 15:59:26 +12:00
J. Nick Koston a2feff8f68 [api] Mark send_message nodiscard so refused frames are never silent (#18293) 2026-08-11 22:23:08 -05:00
J. Nick Koston 37eae9b466 [api] Gate the bluetooth connection messages on their own define (#18281) 2026-08-11 21:31:13 -05:00
Jesse Hills 9605b34c69 [adc] Deprecate pin: TEMPERATURE in favour of internal_temperature (#18304) 2026-08-12 14:20:41 +12:00
Jesse Hills bab62b345b [adc] Fix internal temperature channel on RP2350A under arduino-pico (#18307) 2026-08-12 14:19:59 +12:00
Jesse Hills 3349046c5d [rp2] Record the RP2350 die on generated board entries (#18305) 2026-08-12 01:56:05 +00:00
J. Nick Koston 3f5b8139f3 [bluetooth_proxy] Latch the connection replies and tighten the send paths (#18278) 2026-08-12 13:52:25 +12:00
Jonathan SwobodaandJ. Nick Koston be5e28ea9e [core] Warn when running a different source tree than the one you are in (#18288)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-11 21:06:57 -04:00
J. Nick Koston 9f28638ee7 [bluetooth_proxy] Reset every owed reply in one place (#18276) 2026-08-11 19:15:21 -05:00
J. Nick Koston 201f843e95 [bluetooth_proxy] Latch the unpair reply (#18274) 2026-08-11 18:57:51 -05:00
J. Nick Koston 37a59a07bc [bluetooth_proxy] Retry dropped GATT acks and stop the retry log spam (#18259) 2026-08-11 18:40:47 -05:00
Boris KrivonogandJ. Nick Koston 9556c2bc4c [mitsubishi_cn105] Add vertical vane control action (#16737)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-11 18:39:05 -05:00
Bonne EgglestonandJonathan Swoboda 3540012529 [tests] Build tests from the tree they run in, not the venv's editable install (#18248)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-08-11 19:09:51 -04:00
Kevin AhrendtandJ. Nick Koston e0b112c584 [sendspin] Clear metadata and controller state on disconnect (#18289)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-11 21:03:27 +00:00
Kevin Ahrendt 7f0d6a8696 [sendspin] Add image platform for artwork (#17937) 2026-08-11 15:49:08 -05:00
Kevin Ahrendt 4a7de87bff [audio] Bump microDecoder to v0.4.0 (#18291) 2026-08-11 15:33:37 -05:00
esphome[bot] b9d1d2f06b Bump aioesphomeapi from 45.9.0 to 45.10.0 (#18294) 2026-08-11 20:02:53 +00:00
dependabot[bot] 74c30c62ef Bump setuptools from 83.0.0 to 84.0.0 (#18290)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-11 14:36:59 -05:00
J. Nick Koston 94fbfa05de [core] Show the out-of-flash tip instead of crashing the build (#18280) 2026-08-11 14:35:06 -05:00
Boris KrivonogandJ. Nick Koston 55bd63732d [mitsubishi_cn105] Add vertical vane state trigger (#16727)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-11 13:56:12 -05:00
J. Nick Koston 3ae651af7b [core] Stop dropping complete lines behind an unfinished one (#18279) 2026-08-11 12:38:55 -05:00
esphome[bot] 55e8bc3b14 Bump aioesphomeapi from 45.8.0 to 45.9.0 (#18283) 2026-08-11 17:23:36 +00:00
J. Nick Koston 9938a2487d [core] Show the last line when output stops without a newline (#18265) 2026-08-11 10:55:42 -05:00
J. Nick Koston aee41d64c2 [rp2040_ble][bluetooth_connection] 3 connection slots on rp2 with esp32 parity (#18247) 2026-08-11 08:43:08 -05:00
J. Nick Koston f3a5a9fbd5 [core] Retry transient git network failures with backoff (#18242) 2026-08-11 08:41:55 -05:00
Bonne EgglestonandJ. Nick Koston 8ee3c8d41d [modbus] Properly support client-mode broadcast sends (#17467)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-11 08:31:15 -05:00
J. Nick Koston eefd2a00c7 [espidf] Flush the runner's output so dashboard builds stream (#18264) 2026-08-11 08:11:42 -05:00
J. Nick Koston e8852c5950 [core] Flush stdout in safe_print so logs stream live (#18261) 2026-08-11 08:11:23 -05:00
Josef ZweckandJ. Nick Koston 069f40f653 [hoermann_hcp] Add connectivity binary sensor (#18189)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-11 08:09:33 -05:00
Jesse Hills 04dd6b3a55 [core] Use MAC address size constants instead of literals (#18254) 2026-08-11 08:27:32 -04:00
Jesse Hills 2ff55e3058 [rp2] Use SDK clock query directly in arch_get_cpu_freq_hz (#18269) 2026-08-11 08:23:08 -04:00
Jesse Hills e5cdda9ee5 [adc] Fix RP2350B internal temperature reading wrong ADC channel (#18270) 2026-08-11 08:22:52 -04:00
Jesse Hills ecec3a19c7 [debug] Remove Arduino core dependency from RP2 platform code (#18267) 2026-08-11 08:22:20 -04:00
J. Nick Koston 9823ad6abc [tests] Fix flaky modbus server/controller integration tests (#18258) 2026-08-10 23:56:32 -05:00
J. Nick Koston 51b0240fb8 [rp2] Fix %f formatting in logs (#18256) 2026-08-10 22:05:19 -05:00
Jesse Hills f697cf2011 [api] Add DeviceCapabilities message for optional-feature flags (#17984) 2026-08-11 14:52:19 +12:00
Jesse Hills 8728aaa616 [core] Use MAC address size constants in BLE components (#18252) 2026-08-11 14:21:03 +12:00
J. Nick Koston eab9a47aa2 [bluetooth_proxy] Retry dropped services-done, disconnect and scanner-state notifications (#18225) 2026-08-11 14:20:39 +12:00
Jesse Hills c2ba2fbfc4 [ld24xx] Use MAC address size constants instead of literals (#18253) 2026-08-11 14:18:09 +12:00
J. Nick Koston 3de01d2a2e Bump aioesphomeapi to 45.8.0 (#18251) 2026-08-10 19:56:35 -05:00
dependabot[bot] 791d6659c0 Bump platformdirs from 4.11.0 to 4.11.1 (#18250)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-11 00:43:35 +00:00
Boris KrivonogandJ. Nick Koston 007c677da1 [mitsubishi_cn105] Refactor property encoding/decoding (#16709)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-10 18:58:34 -05:00
Bonne EgglestonandClaude Fable 5 2a0f2d59f0 [modbus_server] Add coil/discrete-input support (#17464)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-10 18:13:12 -05:00
c8d2c3691a [mitsubishi_cn105] Add vertical vane direction select (#16723)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-08-10 17:58:37 -05:00
Josef ZweckandJ. Nick Koston 82a63658f9 [hoermann_hcp] Add Hörmann HCP garage door component (#17355)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-10 17:20:20 -05:00
Bonne Eggleston 596827c51c [modbus_client] Add read/write multiple registers (FC 0x17) (#18215) 2026-08-10 15:18:48 -05:00
J. Nick Koston 8a16ead8ce [web_server] Fix basic auth with long credentials (#18237) 2026-08-10 14:49:14 -05:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 334afdefb5 Update argcomplete requirement from >=3.7.0 to >=3.7.2 (#18243)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 15:44:32 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> e7a2980b9c Bump ruff from 0.16.1 to 0.16.2 (#18245)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 15:44:30 -04:00
dependabot[bot] b5a78c6c46 Bump CodSpeedHQ/action from 5.0.2 to 5.0.3 (#18246)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-10 19:36:22 +00:00
dependabot[bot] 8650d175f4 Bump aiohappyeyeballs from 2.6.2 to 2.7.1 (#18244)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-10 14:26:41 -05:00
2999e7b925 [modbus] Add API for reading/writing coils and discrete inputs in server mode (#17264)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-10 14:23:49 -05:00
J. Nick Kostoncopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>bdraco
207ae2e4cb [bk72xx_ble] Support active scanning by packing the GAPM start command (#18169)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: bdraco <663432+bdraco@users.noreply.github.com>
2026-08-10 14:02:37 -05:00
Boris Krivonog 9e78a768a2 [mitsubishi_cn105] Extract top-level hub (#16987) 2026-08-10 14:02:16 -05:00
J. Nick Koston e4d08a73b0 [ufm01] Prefix PassiveReadResult enumerators to avoid Realtek SDK macro collision (#18240) 2026-08-10 13:03:01 -05:00
J. Nick Koston 4cf7ad9c63 [ble_device_base] Treat a partially bound merger as unbound (#18235) 2026-08-10 13:02:27 -05:00
J. Nick Koston 293d0b90d9 [core] Document enum class value naming to avoid platform SDK macro collisions (#18241) 2026-08-10 13:02:02 -05:00
J. Nick Koston 8f1e439792 [bluetooth_proxy] Finish the connection scan before reserving a slot (#18239) 2026-08-10 12:56:16 -05:00
J. Nick Koston ad733272e5 [bluetooth_proxy] Pair the advertisement flush time with hub_ to close alignment holes (#18234) 2026-08-10 12:40:22 -05:00
J. Nick Koston b7c6245388 [wifi] Save fast connect settings and reset roaming bookkeeping after driver initiated roams (#18167) 2026-08-10 12:39:32 -05:00
2184ec2928 [ethernet] Add CH390 SPI ethernet support (#18226)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-10 12:24:16 -05:00
J. Nick Koston bae7f19323 [core] Extend '/' in names deprecation window to 2027.7.0 (#18236) 2026-08-10 11:32:02 -05:00
56ec21d950 [ufm01] Improve startup with reset retry and passive polling fallback (#17567)
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-08-10 12:31:31 -04:00
J. Nick Koston 2798ef4de2 [ota] Enforce the backend contract with a concept (#18192) 2026-08-10 11:20:38 -05:00
J. Nick Koston e90b4abe9c [core] Enforce the preferences contracts with concepts (#18191) 2026-08-10 11:20:24 -05:00
J. Nick Koston f3d1fc0d64 [bluetooth_proxy] Migrate esp32 onto the neutral GATT backend (#18198) 2026-08-10 10:47:56 -05:00
J. Nick Koston c8c929d487 [ble_device_base] Merge adv and scan response before delivery on rp2 (#18217) 2026-08-10 10:06:06 -05:00
J. Nick Koston d9567b2974 Normalize marker-wrapped callable keys in the schema dump (#18218) 2026-08-10 10:05:37 -05:00
J. Nick Koston 02fa18b74f [core] Skip colorama init for terminal and dashboard runs (#18224) 2026-08-10 09:44:01 -05:00
Kevin Ahrendt 3656375516 [sendspin] Bump sendspin-cpp to v0.7.1 (#18232) 2026-08-10 09:00:29 -05:00
Mustafa KURUandJonathan Swoboda b0a9bfd381 [ld6002b] Add area and zone configuration (5/5) (#17823)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-08-10 09:59:32 -04:00
J. Nick Koston 25cb440005 [core] Use Happy Eyeballs for remote file downloads (#18050) 2026-08-10 17:29:00 +12:00
J. Nick Koston 0f59ef36a9 [core] Store the validated-config cache as JSON to drop YAML off the upload fast path (#18106) 2026-08-10 17:26:41 +12:00
esphome[bot] 3de8c7f95c Bump bundled esphome-device-builder to 1.9.5 (#18223) 2026-08-10 01:49:54 +00:00
Jesse Hills 989dbd7550 [ci] Name release runs after the version or dev tag they build (#18110) 2026-08-10 10:10:38 +12:00
ab12e5490f [modbus] Rename send_pdu() to queue_pdu() (#18196)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-09 17:00:44 -05:00
J. Nick Koston e9f428983e [ble_device_base] Bind BLEHub to the build's tracker at compile time (#18181) 2026-08-09 08:57:31 -05:00
J. Nick Koston 862b13c8dd [esp32_ble_tracker] Retire the scanner-state listener interface (#18179) 2026-08-08 20:58:48 -05:00
Edu_Coder 2bb01853e0 [tuya] GMT time 0x0C command handler (#17158) 2026-08-08 17:53:10 -07:00
J. Nick Koston c7d6b4aaa4 [esp32_ble_tracker] Retire the raw listener path and parser-type enum (#18177) 2026-08-08 19:13:07 -05:00
J. Nick Koston 8c74e3d5ef [bluetooth_proxy] Deliver scanner state through the hub callback (#18175) 2026-08-08 15:59:49 -05:00
J. Nick Koston 04384e0f5b [ble_device_base] Bind the GATT backend at compile time (#18185) 2026-08-08 15:59:32 -05:00
J. Nick Koston 7218aa4803 [bluetooth_connection] Explicit pairing for rp2 (#18166) 2026-08-08 09:51:12 -05:00
J. Nick Koston 9cb46aa584 [bluetooth_proxy] Deliver esp32 advertisements through the hub callback (#18173) 2026-08-08 09:50:32 -05:00
Josef Zweck 747c5c3e40 [modbus] Make sure we log on no accepting device (#18187) 2026-08-08 09:34:59 -05:00
Edvard Filistovič 68acc055bf [ble_device_base] Migrate BLE sensor platforms to the neutral layer (batch 13: xiaomi_rtcgq02lm, xiaomi_wx08zm, xiaomi_xmwsdj04mmc) (#18183) 2026-08-08 02:06:40 -05:00
2730c10c2c [modbus] Route broadcast writes (address 0) to all server devices (#17387)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-08 01:35:49 -05:00
Edvard Filistovič 252bb3333e [ble_device_base] Migrate BLE sensor platforms to the neutral layer (batch 12: xiaomi_miscale, xiaomi_mjyd02yla, xiaomi_mue4094rt) (#18180) 2026-08-08 01:34:08 -05:00
Edvard Filistovič 98f4854ecd [ble_device_base] Migrate BLE sensor platforms to the neutral layer (batch 11: xiaomi_lywsdcgq, xiaomi_mhoc303, xiaomi_mhoc401) (#18178) 2026-08-08 01:01:24 -05:00
Edvard Filistovič 413a4c5885 [ble_device_base] Migrate BLE sensor platforms to the neutral layer (batch 10: xiaomi_lywsd02, xiaomi_lywsd02mmc, xiaomi_lywsd03mmc) (#18174) 2026-08-08 00:27:44 -05:00
Edvard Filistovič 087b80eeb5 [ble_device_base] Migrate BLE sensor platforms to the neutral layer (batch 9: xiaomi_hhccjcy10, xiaomi_hhccpot002, xiaomi_jqjcy01ym) (#18172) 2026-08-07 23:42:37 -05:00
745dee0734 [usb_cdc_acm] Don't discard queued TX data when USB flush times out (#17637)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-07 23:24:56 -05:00
Edvard Filistovič b986530efc [ble_device_base] Migrate BLE sensor platforms to the neutral layer (batch 8: xiaomi_cgpr1, xiaomi_gcls002, xiaomi_hhccjcy01) (#18171) 2026-08-07 22:51:33 -05:00
Edvard Filistovič 3aaea907ba [ble_device_base] Migrate BLE sensor platforms to the neutral layer (batch 7: xiaomi_cgd1, xiaomi_cgdk2, xiaomi_cgg1) (#18170) 2026-08-07 21:59:21 -05:00
Edvard Filistovič 1d184b43eb [ble_device_base] Migrate BLE sensor platforms to the neutral layer (batch 6: thermopro_ble, exposure_notifications, xiaomi_ble) (#18168) 2026-08-07 20:47:52 -05:00
J. Nick Koston 8d494a84c5 [bluetooth_connection] Engine follow-ups from the rp2 GATT series (#18159) 2026-08-07 16:25:55 -05:00
Bonne EgglestonandClaude b0aec6dc2f [modbus_client] Lambda sugar (#18146)
Co-authored-by: Claude <noreply@anthropic.com>
2026-08-07 16:09:15 -05:00
Edvard FilistovičandJ. Nick Koston 4a7d270494 [ble_device_base] Migrate BLE sensor platforms to the neutral layer (batch 5: airthings_ble, inkbird_ibsth1_mini, radon_eye_ble) (#18165)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-08-07 20:46:43 +00:00
dependabot[bot] ae26483be6 Bump github/codeql-action/init from 4.37.5 to 4.37.6 (#18164)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-07 19:27:57 +00:00
dependabot[bot] 5d7bd179b1 Bump github/codeql-action/analyze from 4.37.5 to 4.37.6 (#18163)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-07 14:27:09 -05:00
Edvard Filistovič 23a3454551 [ble_device_base] Migrate BLE sensor platforms to the neutral layer (batch 4: ruuvi_ble, ruuvitag, b_parasite) (#18161) 2026-08-07 14:21:46 -05:00
f5ee72753d [modbus_client] Add typed read/write actions (#18078)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Claude <noreply@anthropic.com>
2026-08-07 14:18:20 -05:00
esphome[bot] ee13996ee6 Bump bundled esphome-device-builder to 1.9.4 (#18162) 2026-08-07 19:17:26 +00:00
Josef ZweckandJ. Nick Koston c24e61439b [modbus] Add server support for read/write multiple registers (0x17) (#17357)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-07 14:17:11 -05:00
J. Nick Koston 2e1c517821 [bluetooth_proxy] Enable active connections on rp2 (#18132) 2026-08-07 13:51:41 -05:00
Mustafa KURUandJonathan Swoboda 950cfc4da3 [ld6002b] Add select and button platforms (4/5) (#17822)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-08-07 14:33:37 -04:00
Edvard FilistovičandJ. Nick Koston 715b14aeba [ble_device_base] Migrate BLE sensor platforms to the neutral layer (batch 3: mopeka_ble, mopeka_pro_check, mopeka_std_check) (#17951)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-07 12:59:27 -05:00
J. Nick Koston d7b5ad77da [bluetooth_connection] Add BTstack GATT client backend for rp2 (#18131) 2026-08-07 12:53:09 -05:00
Edvard Filistovič 77bfda6f1f [ble_device_base] Migrate BLE sensor platforms to the neutral layer (batch 2: atc_mithermometer, pvvx_mithermometer, bthome_mithermometer) (#17950) 2026-08-07 12:48:18 -05:00
Oliver KleineckeOliver KleineckeClaude Sonnet 4.6Copilot Autofix powered by AIstorage splitpre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
b326cefea7 [esp32] refactor esp32-vfs default configs to FINAL co-routine & add some defaults (#17337)
Co-authored-by: Oliver Kleinecke <kleinecke.oliver@googlemail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: storage split <ai@local>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-08-07 12:36:59 -05:00
J. Nick Koston c0e70d9beb [rp2040_ble] Add scan arbitration and GATT client hooks (#18155) 2026-08-07 11:50:18 -05:00
J. Nick Koston c5e165d062 [bluetooth_proxy] Make the GATT dispatch platform neutral (#18130) 2026-08-07 11:29:19 -05:00
Edvard Filistovič 5f483b11b6 [ble_device_base] Migrate BLE sensor platforms to the neutral layer (batch 1: ble_presence, ble_rssi, ble_scanner) (#17716) 2026-08-07 16:19:08 +00:00
Mustafa KURUandJonathan Swoboda b802740997 [ld6002b] Add switch, number and text sensor platforms (3/5) (#17821)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-08-07 11:11:29 -04:00
Edvard Filistovič 1bbe8b415f [bluetooth_proxy] Only advance the scanner-state detector when the frame was sent (#18154) 2026-08-07 15:01:50 +00:00
Edvard FilistovičandJ. Nick Koston 8b0e23d55b [bluetooth_proxy] Fold scanner-state bookkeeping into the sender; extend platform-gate tests (#18150)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-08-07 14:30:00 +00:00
J. Nick Koston 2f2634bf6b [bluetooth_connection] Move BluetoothConnection out of bluetooth_proxy (#18129) 2026-08-07 08:54:28 -05:00
Jonathan Swoboda 9c93d8925f [ota] Use PSA crypto for signature verification on ESP-IDF 6 (#18145) 2026-08-07 07:41:48 -04:00
J. Nick Koston 634515ecc5 [ble_device_base] Add platform-neutral GATT client contract (#18128) 2026-08-06 21:45:57 -05:00
J. Nick Koston e58ba59288 [core] Add StringRef::starts_with (#18142) 2026-08-06 20:58:41 -05:00
Jonathan Swoboda bf95be5550 [opentherm] Initialize output state before the first write (#18126) 2026-08-06 21:46:37 -04:00
Jonathan Swoboda 96f0ea10f4 [midea] Bump MideaUART so its ESP-IDF shims stop shadowing millis() (#18119) 2026-08-06 21:46:28 -04:00
J. Nick Koston 97558ce64a [core] Add EventPool::warm() (#18127) 2026-08-06 20:40:04 -05:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Jonathan Swoboda
4301e5a2a2 Bump resvg-py from 0.3.3 to 0.3.4 (#18137)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-08-06 19:58:52 -04:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Jonathan Swoboda
09ecfa58de Bump prek from 0.4.11 to 0.4.12 (#18138)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-08-06 19:57:03 -04:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Jonathan Swoboda
27f6ced1a9 Bump github/codeql-action/analyze from 4.37.4 to 4.37.5 (#18139)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-08-06 19:49:40 -04:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Jonathan Swoboda
9802c7f32f Bump github/codeql-action/init from 4.37.4 to 4.37.5 (#18140)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-08-06 19:49:32 -04:00
Jonathan Swoboda bf36a62f62 [ci] Raise runner-concurrency caps for the OHF enterprise pool (#18125) 2026-08-06 19:47:12 -04:00
luar123andJonathan Swoboda 4c47051da9 [zigbee] Add on_start automation (3/3) (#18009)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-08-06 19:45:44 -04:00
56682534f8 [modbus_client] Add component for ad-hoc modbus request/response (#17676)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-06 14:52:23 -05:00
Mustafa KURUandJonathan Swoboda 53b1b3a253 [ld6002b] Add target sensors (2/5) (#17820)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-08-06 15:36:32 -04:00
zerafachris 4ccf4210be [epaper_spi] Do not yield on the final row of a T133A01 transfer phase (#17709) 2026-08-07 03:34:33 +10:00
Edvard FilistovičandJ. Nick Koston 3221ed2bad [ble_device_base] Hub provider registry and shared consumer helpers (#18081)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-06 12:13:30 -05:00
J. Nick Koston abc13a7c0a [script] Disable queued script polling while the queue is empty (#18121) 2026-08-06 10:00:46 -05:00
luar123 3353e71f9a [zigbee] Improve network handling on esp32 (2/3) (#18008) 2026-08-06 10:34:32 -04:00
J. Nick Koston 73689f8d8b [rp2] Limit pin validation to GPIO 0-29 on RP2350A boards (#18102) 2026-08-06 08:55:24 -05:00
Mustafa KURU ba86685361 [const] Move CONF_TARGET_COUNT to components/const (#18120) 2026-08-06 09:37:36 -04:00
Keith Burzinskiandpre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> a16c2e2b04 [esp32] Use constants for the signing scheme identifiers (#18118)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-08-06 01:54:21 -05:00
J. Nick Koston eb0fac9cbf [core] Only snapshot the user config when esphome config --no-defaults asks for it (#18113) 2026-08-05 21:20:52 -05:00
Bonne Eggleston 3b6f45894b [core] StaticVector converting ctor (#18117) 2026-08-05 21:20:37 -05:00
Mustafa KURUandJonathan Swoboda f8cabc9d1f [ld6002b] Add LD6002B 60GHz presence radar (1/5) (#17819)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-08-05 22:18:51 -04:00
J. Nick Koston a7740091ec [ci] Run CodSpeed benchmarks when top-level esphome Python modules change (#18114) 2026-08-05 20:50:13 -05:00
esphome[bot] 0fcc2a6ff1 Bump bundled esphome-device-builder to 1.9.3 (#18116) 2026-08-05 20:50:00 -05:00
J. Nick Kostonandcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> 0d192e8334 [rp2] Bump arduino-pico framework to 6.0.0 (#18101)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-08-06 01:21:35 +00:00
J. Nick Koston 2dfcde477f [ble_client] Reject descriptor_uuid combined with notify at validation time (#18109) 2026-08-05 19:09:45 -05:00
J. Nick Koston f971e404c7 [core] Resolve GCC multilib include dirs for clang-tidy (#18111) 2026-08-05 19:09:13 -05:00
J. Nick Koston 524b278811 [core] Defer stdlib imports out of the upload and logs fast path (#18105) 2026-08-05 19:09:00 -05:00
J. Nick Koston 7f80276cf4 [ble_device_base] Document the None output for unset UUIDs in to_str() (#18108) 2026-08-05 23:02:13 +00:00
J. Nick Koston 26256d3d2e [ble_device_base] [ble_client] Restore unset UUID state so notify sensors set up again (#18098) 2026-08-05 22:21:24 +00:00
Edvard Filistovič 1bd94bb805 [bluetooth_proxy] Scanner-state sync in set_mode; platform-gate tests (#18100) 2026-08-05 21:41:07 +00:00
J. Nick Koston 28aedc2b14 [core] Drop the duplicated FileLock import from a merge collision (#18103) 2026-08-05 16:08:06 -05:00
J. Nick Koston b700193a0a [core] Generalize the resolver's async thread dispatch into run_async (#18088) 2026-08-05 20:57:08 +00:00
J. Nick Koston 4fd2ddee55 [core] Trim the stacktrace gate commentary (#18097) 2026-08-05 20:50:48 +00:00
J. Nick Koston a2d1f73bca [core] Defer voluptuous and bundle imports out of the upload and logs fast path (#18093) 2026-08-05 20:39:19 +00:00
Jesse Hills f2472563f4 Merge branch 'release' into dev 2026-08-06 08:09:23 +12:00
Jesse Hills 90d6d8f5ca Merge pull request #18083 from esphome/bump-2026.7.4
2026.7.4
2026-08-06 08:08:37 +12:00
Jesse Hills d4b3d37165 Update URL for ESPHome Builder issue reporting (#16250) 2026-08-06 08:07:30 +12:00
Edvard FilistovičandJ. Nick Koston 563b6ade0f [bluetooth_proxy] Platform-neutral advertisement proxy via ble_device_base (#17880)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-05 15:06:01 -05:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 17f3c9d780 Bump CodSpeedHQ/action from 5.0.1 to 5.0.2 (#18094)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-05 15:50:57 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 98622bd029 Bump esphome/workflows/.github/workflows/stale.yml from 203cea60ebfd18e2b966e57750750e0417a9feec to 61fd37a044cad4e9aa4303027b2a61b6a34da855 (#18095)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-05 15:50:36 -04:00
J. Nick Koston 253ecdd145 [core] Resolve the stacktrace decoder lazily behind an address gate (#18048) 2026-08-05 14:34:16 -05:00
Keith Burzinskiandpre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> 1cc83172ab [ota] Multi-key OTA signature verification for external RSA signing (#17981)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-08-05 14:24:38 -05:00
Marek PilchandJonathan Swoboda b521b5e1ca [modbus_server] Add tests for U_WORD_S and S_WORD_S (#17832)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-08-05 14:47:48 -04:00
J. Nick Koston 202b31f711 [ble_device_base] Deprecate address_str in favor of address_str_to (#18092) 2026-08-05 18:33:32 +00:00
Marek Pilch fc682dc75b [modbus_server] Support byte-swapped word types U_WORD_S and S_WORD_S (#17829) 2026-08-05 14:11:37 -04:00
Oliver KleineckeClaudepre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>Oliver KleineckeCopilot Autofix powered by AI
4627f07a7b [mcp4461] nonvolatile-by-default persistence, TCON boot sync, wiper actions (#17561)
Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Oliver Kleinecke <kleinecke.oliver@googlemail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-08-05 14:11:03 -04:00
Edvard Filistovič 9bfce75bc5 [ln882h_ble_tracker] Use the shared ble_device_base automation layer (#18089) 2026-08-05 16:46:01 +00:00
Bonne EgglestonandClaude Fable 5 b93b21eab1 [modbus_controller] Refactor to simplify message handling (#11781)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 11:02:56 -05:00
youkorrandClaude e31a43af17 [esp32_camera_web_server] Fix MJPEG stream ending on a stale semaphore (#18052)
Co-authored-by: Claude <noreply@anthropic.com>
2026-08-05 11:40:15 -04:00
Edvard Filistovič 0496627d2b [bk72xx_ble_tracker] Automation triggers and actions (#17776) 2026-08-05 09:31:51 -05:00
Edvard Filistovičpre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>J. Nick Koston
d548454dbe [ln882h_ble_tracker] Automation triggers and actions (#17778)
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-08-05 09:30:46 -05:00
Kevin Ahrendt 64f0e38a51 [micro_wake_word] Support adding and removing wake word models at runtime (#17927) 2026-08-05 10:24:46 -04:00
TesseractTimmeerootpre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
3964ef61f8 [zigbee] Resolution attribute for esp32 sensors (#17973)
Co-authored-by: root <root@core-ssh.local.hass.io>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-08-05 10:23:57 -04:00
luar123andCopilot Autofix powered by AI 2d5d34d33f [zigbee] cleanup, docstrings, refactor connected state on esp32 (1/3) (#18007)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-08-05 10:19:57 -04:00
J. Nick Koston 6bcbdd79c3 [core] Lift the log line processor into esphome/stacktrace.py (#18076) 2026-08-05 08:43:14 -05:00
Pete Keen 3d093c0ae8 [esp32_rmt_led_strip] Add RGBW channel ordering (#18028) 2026-08-05 07:57:59 -04:00
Edvard Filistovič 450232964b [tests] Unignore component-test config fixture directories (#18084) 2026-08-04 21:58:31 -05:00
J. Nick Koston 112d8b26ee [core] Skip source range tracking when loading the validated config cache (#18046) 2026-08-04 21:24:38 -05:00
Clyde Stubbs 27dcf64b45 [lvgl] Add pause option to round_trip animation timing (#17574) 2026-08-05 14:16:22 +12:00
Clyde StubbsandClaude Sonnet 5 97d33a5679 [lvgl] Add lvgl.widget.set_z_index action (#17993)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 14:14:32 +12:00
Edvard Filistovič 0a99b007a9 [ble_device_base] Advertise runtime scan-mode switching in HubCapabilities (#18079) 2026-08-05 01:56:32 +00:00
Jesse Hills e2ca80ec41 Bump version to 2026.7.4 2026-08-05 13:19:43 +12:00
Samuel SiebandSamuel Sieb 920ff9c25f [esp32] explicitly disable BLE 5.0 (#18047)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2026-08-05 13:19:42 +12:00
esphome[bot] 56a90d2b25 Bump bundled esphome-device-builder to 1.9.2 (#18051) 2026-08-05 13:19:42 +12:00
esphome[bot] b4166a883e Bump bundled esphome-device-builder to 1.9.1 (#18025) 2026-08-05 13:19:42 +12:00
esphome[bot] b8703a8a1b Bump bundled esphome-device-builder to 1.9.0 (#18017) 2026-08-05 13:19:42 +12:00
esphome[bot] 57bb4e4e77 Bump bundled esphome-device-builder to 1.8.2 (#17995) 2026-08-05 13:19:42 +12:00
J. Nick Koston de93815c6b [api] Fix double free when overflow buffer drain is re-entered (#17969) 2026-08-05 13:19:42 +12:00
Jesse Hills d4372ed008 [esp32] Restrict toolchain validation to supported values (#17972) 2026-08-05 13:19:42 +12:00
Jonathan SwobodaandJ. Nick Koston a199ac41ee [espidf] Use forward slashes in generated component CMakeLists (#17965)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-05 13:19:42 +12:00
J. Nick Koston c0f494450d [git] Fix device adoption failing on first attempt: lock the clone cache against concurrent resolutions (#17923) 2026-08-05 13:19:42 +12:00
Clyde StubbsandClaude Sonnet 5 6b6903e568 [epaper_spi] Default init sequence to empty not None (#17966)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 13:19:42 +12:00
esphome[bot] d0f68802b9 Bump bundled esphome-device-builder to 1.8.1 (#17964) 2026-08-05 13:19:42 +12:00
esphome[bot]andesphome[bot] <115708604+esphome[bot]@users.noreply.github.com> 7862520450 Bump bundled esphome-device-builder to 1.8.0 (#17953)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-08-05 13:19:42 +12:00
dependabot[bot] cbdddc8020 Bump platformdirs from 4.10.0 to 4.11.0 (#17756)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-05 13:19:41 +12:00
dependabot[bot] 7d1317ad53 Bump filelock from 3.29.0 to 3.32.0 (#17759)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-05 13:19:41 +12:00
J. Nick Koston ad18bbc644 [esp32_ble] Migrate the BLE count machinery to the shared slot counter factory (#18059) 2026-08-05 00:54:07 +00:00
Edvard FilistovičandJ. Nick Koston 1bae91abaa [ln882h_ble_tracker] BLE 5.x scanner for LN882H (#16691)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-08-04 22:45:47 +00:00
J. Nick Koston 1f5df20cb2 [rp2040_ble] Codegen sized StaticVector for scan listeners (#18058) 2026-08-04 21:04:22 +00:00
J. Nick Koston d47acf5d0b [core] Read the mqtt IP discovery flag without importing the mqtt component (#18066) 2026-08-04 15:12:57 -05:00
J. Nick Koston 79a305d69a [core] Drop the duplicate ESPHome version parser (#18065) 2026-08-04 14:48:39 -05:00
J. Nick Koston d344fd74d1 [core] Read the esp32 variant for esptool without importing the esp32 package (#18067) 2026-08-04 19:47:07 +00:00
J. Nick Koston 68640f8074 [core] Shared slot count factory for codegen sized listener storage (#18057) 2026-08-04 19:46:44 +00:00
J. Nick Koston 342f3d6994 [core] Share the stacktrace analyzer resolution in platform_hooks (#18075) 2026-08-04 14:38:28 -05:00
J. Nick Koston a904426431 [core] Unbind the esp-idf toolchain from the esp32 component package (#18068) 2026-08-04 14:38:10 -05:00
Edvard Filistovič b00f32db8a [bthome_mithermometer] Gate log-only MAC helper by log level (#18064) 2026-08-04 18:59:11 +00:00
Edvard Filistovič 914cab301f [rp2_ble_tracker] Extract stamp-and-start helper; pin capability/switch independence (#18072) 2026-08-04 18:20:57 +00:00
Marek Pilch 8bd9f213e5 [modbus] Add unit tests for U_WORD_S and S_WORD_S (#17831) 2026-08-04 12:17:27 -04:00
J. Nick Koston 5abd100b53 [core] Resolve platform CLI hooks from a registry instead of importing the platform package (#18044) 2026-08-04 11:08:56 -05:00
J. Nick Koston adb86a052c [core] Ship secrets referenced by remote package files in config bundles (#18053) 2026-08-04 11:05:52 -05:00
J. Nick Koston c2ddc17063 [libretiny] Give each platform its own CONFIG_SCHEMA instance (#18056) 2026-08-04 11:05:04 -05:00
Edvard Filistovič 37b6578259 [bk72xx_ble_tracker] Brace single-statement loop flagged by clang-tidy (#18070) 2026-08-04 15:57:42 +00:00
Edvard Filistovič c6a37847e6 [ble_device_base] Scan-mode request contract on BLEHub (#18061) 2026-08-04 15:18:31 +00:00
Bonne EgglestonandClaude Opus 4.8 61a4722c93 [modbus_controller] Add bus-fairness integration test (xfail until #11781) (#17345)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-04 09:15:31 -05:00
Edvard Filistovič dbf7167622 [bk72xx_ble] Codegen-sized StaticVector for scan listeners (#18055) 2026-08-04 04:31:43 +00:00
Jesse Hills 4ceae9cf85 [ci] Replace pre-commit with prek (#18029) 2026-08-04 16:18:46 +12:00
Samuel SiebandSamuel Sieb dafc86d960 [esp32] explicitly disable BLE 5.0 (#18047)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2026-08-03 22:36:52 -05:00
Edvard Filistovič 56b1c59eb2 [ln882h_ble] Scan primitives and main-task scan report queue (#17835) 2026-08-03 21:54:07 -05:00
J. Nick Koston 1b3891866c [core] Parse stored framework versions without importing the validation stack (#18045) 2026-08-03 20:59:38 -05:00
J. Nick Koston 11c3c06b12 [rp2_ble_tracker] Add active scanning support (#18035) 2026-08-03 20:51:17 -05:00
Jonathan Swoboda 735c64d138 [core] Support local library directories via file:// on the native toolchain (#18005) 2026-08-03 21:48:57 -04:00
J. Nick Koston 4f67932e0d [substitutions][core] Expand templated !include paths to on-disk candidates during bundle discovery (#17647) 2026-08-03 20:48:11 -05:00
esphome[bot] 25d5985775 Bump bundled esphome-device-builder to 1.9.2 (#18051) 2026-08-04 01:29:21 +00:00
J. Nick Koston a1c2983394 [core] Hash entity keys from the raw name to fix collisions (#17949) 2026-08-03 20:05:25 -05:00
J. Nick Koston 4424af8c5b [api] Move runtime log client out of the component package (#18043) 2026-08-03 19:57:52 -05:00
J. Nick Koston e8dadf2852 [esp32] Flag crash records captured by a different firmware build (#17770) 2026-08-03 19:56:20 -05:00
J. Nick Koston 2b6bf1f0fe [esp8266] Move wifi rate tables to DRAM to fix beacon parse crash (#17968) 2026-08-03 19:54:43 -05:00
J. Nick Koston 644f279972 [logger] Stub out ROM ets_putc on ESP8266 when serial logging is disabled (#17970) 2026-08-03 19:53:51 -05:00
J. Nick Koston 43deec3063 [espnow] Stop polling the Wi-Fi driver every loop and disable loop when idle (#18027) 2026-08-03 19:53:37 -05:00
J. Nick Koston a50369fc0e [rp2_ble_tracker] BLE tracker for Raspberry Pi Pico W (#18002) 2026-08-03 19:53:07 -05:00
Bonne EgglestonandClaude Opus 4.8 5d89e432da [modbus_controller] Span response path; keep sensor addresses as configured (#17677)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-03 19:15:00 -05:00
0b34c97742 [midea] Add ESP-IDF framework support (#12646)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-08-03 19:21:27 -04:00
dependabot[bot] 66db4fdd61 Bump github/codeql-action/analyze from 4.37.3 to 4.37.4 (#18041)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-03 15:14:07 -05:00
dependabot[bot] 9931647a8c Bump ruff from 0.16.0 to 0.16.1 (#18038)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-03 15:13:51 -05:00
dependabot[bot] 77546b6111 Bump github/codeql-action/init from 4.37.3 to 4.37.4 (#18040)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-03 15:13:39 -05:00
dependabot[bot] 050b4c3c94 Bump cryptography from 48.0.1 to 50.0.0 (#18037)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-03 15:13:26 -05:00
dependabot[bot] 3be32846e4 Bump filelock from 3.32.0 to 3.32.2 (#18039)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-03 15:13:02 -05:00
J. Nick Koston f8b83cc8a2 [wifi] Notify connect state listeners after driver initiated roams (#18034) 2026-08-03 16:39:14 +00:00
J. Nick Koston 4f058585bb [rp2040_ble] Add controller scan primitives (#18001) 2026-08-03 11:14:34 -05:00
Egor Vorontsovandpre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> 4d9c514a1a [i2s_audio] Implemented I2S PDM Microphone DSR selection (#17751)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-08-03 09:22:03 -04:00
Jonathan Swoboda 6b10e8bd15 [esp32_hosted] Bump esp_hosted to 2.12.12, esp_wifi_remote to 1.6.3 (#18030) 2026-08-02 23:58:47 -04:00
Edvard Filistovič 67654fd1e1 [bk72xx_ble_tracker] Fix scan start retry and stale-millis underflow (#17992) 2026-08-03 02:55:20 +00:00
esphome[bot] 2604169080 Bump bundled esphome-device-builder to 1.9.1 (#18025) 2026-08-02 19:46:03 -05:00
J. Nick KostonandCopilot Autofix powered by AI 74fbee7d8d [ble_device_base] Share scan parameter validation between trackers (#18003)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-08-02 19:28:22 -05:00
d6ad1560fa [nrf52] feat: add support for zephyr pwm (#16483)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: tomaszduda23 <tomaszduda23@gmail.com>
2026-08-02 21:16:21 +00:00
Bonne Eggleston fcc2c26367 [modbus] Restore function code byte for custom codes in deprecated on_modbus_data (#18006) 2026-08-02 11:40:34 -05:00
esphome[bot] e36d7fe82b Bump bundled esphome-device-builder to 1.9.0 (#18017) 2026-08-01 22:08:57 -05:00
d38a458de9 [modbus] Rework the client queue as a per-frame state machine (#17922)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-01 21:34:36 -05:00
Jonathan Swoboda 5821915aad [heatpumpir] Expose configured min/max as the visual temperature range (#17985) 2026-08-01 12:37:44 -04:00
esphome[bot] fae2f7a11d Bump bundled esphome-device-builder to 1.8.2 (#17995) 2026-08-01 08:52:42 -05:00
Keith Burzinski 1df2759db6 [nextion] Reconnect stale HTTP connection during TFT upload (#17824) 2026-07-31 23:05:45 -05:00
Keith Burzinski ce9b221be4 [serial_proxy] Restrict port operations to the subscriber and harden subscription handling (#17796) 2026-07-31 23:05:06 -05:00
Edvard Filistovič e50fae3f46 [ln882h_ble] BLE controller support for LN882H (#17777) 2026-07-31 22:40:57 +00:00
Ardumine 15711f8112 [openthread_info] Add more sensors for Openthread info (#17276) 2026-07-31 12:13:44 -04:00
Jesse Hills 7ad662781d [ci] Order jobs to follow the CI flow and close ci-status gaps (#17979) 2026-07-31 07:53:59 -04:00
TesseractTimmeerootpre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2d1530adf7 [zigbee] Add more units (#17982)
Co-authored-by: root <root@core-ssh.local.hass.io>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-07-31 07:16:07 -04:00
Jesse Hills 40b4cd8e9b [ci] Drop the unused Codecov upload token (#17977) 2026-07-31 06:32:33 -04:00
Jesse Hills e11a295680 [ci] Report an empty Codecov upload when pytest is skipped (#17976) 2026-07-31 16:27:05 +12:00
Jesse Hills f9cd789f7c [ci] Tell stacked pull requests apart from hand-built chains (#17980) 2026-07-31 15:39:36 +12:00
Jesse Hills 80c17fb2cd [ci] Authenticate the stale workflow as esphome[bot] (#17974) 2026-07-31 13:52:01 +12:00
J. Nick Koston fb21320826 [api] Fix double free when overflow buffer drain is re-entered (#17969) 2026-07-30 14:48:17 -10:00
Jesse Hills 3899429cfa [esp32] Restrict toolchain validation to supported values (#17972) 2026-07-30 20:34:44 -04:00
Jonathan SwobodaandJ. Nick Koston 44f08d15db [espidf] Use forward slashes in generated component CMakeLists (#17965)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-07-30 20:34:04 -04:00
J. Nick Koston 963a3379a6 [core] Log when a git source update is skipped and when it will next refresh (#17943) 2026-07-30 13:06:32 -10:00
J. Nick Koston 4951f4fc2e [git] Fix device adoption failing on first attempt: lock the clone cache against concurrent resolutions (#17923) 2026-07-30 11:44:12 -10:00
Clyde StubbsandClaude Sonnet 5 a87ad66746 [epaper_spi] Default init sequence to empty not None (#17966)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 16:25:17 -04:00
Jonathan Swoboda 60662830dc [ci] Switch stale to the shared real-activity workflow (#17960) 2026-07-30 15:41:10 -04:00
esphome[bot] 33ccaf8a85 Bump bundled esphome-device-builder to 1.8.1 (#17964) 2026-07-30 08:54:39 -10:00
Pieter Viljoen d64e1ebba3 [ble_client] Do not walk the GATT cache after releasing services (#17919) 2026-07-30 08:31:32 -10:00
esphome[bot]andesphome[bot] <115708604+esphome[bot]@users.noreply.github.com> 6733e930eb Bump bundled esphome-device-builder to 1.8.0 (#17953)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-07-30 08:01:17 -04:00
Jesse Hills c23a107a87 [config_validation] Add underscore/hyphen normalization to one_of (#17792) 2026-07-30 18:36:28 +12:00
Kevin Ahrendt 4224d90567 [runtime_image] Allow decoding into a caller-owned buffer (#17936) 2026-07-29 23:32:21 -04:00
Jonathan Swoboda fb1b1db87f [esp32][mdns][runtime_image] Bump recommended Arduino to 3.3.11 and platform to 55.03.311 (#17875) 2026-07-29 18:04:36 -04:00
Jonathan Swoboda be78b8011d [esp32] Add platformio toolchain deprecation warning (#17947) 2026-07-29 17:17:39 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> a99d03e172 Bump docker/login-action from 4.5.2 to 4.6.0 in the docker-actions group (#17944)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-29 15:49:19 -04:00
matt123pandKevin Ahrendt c7d16dde15 [voice_assistant] Fix playback when CPU is loaded (#17043)
Co-authored-by: Kevin Ahrendt <kevin.ahrendt@openhomefoundation.org>
2026-07-29 19:48:43 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> be7748ccb6 Bump CodSpeedHQ/action from 4.19.1 to 5.0.1 (#17946)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-29 15:35:07 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 41902d7538 Bump pypa/gh-action-pypi-publish from 1.14.1 to 1.14.2 (#17945)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-29 15:30:49 -04:00
J. Nick Koston ca1f89e500 [ci] Enable the ruff rule requiring explicit encoding on text file I/O (#17897) 2026-07-29 13:44:39 -04:00
Kevin Ahrendt 306d200b02 [api] Stop advertising external wake words the device cannot load (#17926) 2026-07-29 13:27:06 -04:00
98ee7e0f82 [lvgl] Add lvgl.theme.update action (#17678)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 13:08:52 -04:00
Keith Burzinskipre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>Copilot Autofix powered by AI
ad3e2f83b8 [sgp4x] Fix datasheet conformance issues (#17828)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-29 12:57:54 -04:00
Jeroenandjeroen85 33dacb06ff [opentherm] Restore idle output when stopped (#17834)
Co-authored-by: jeroen85 <26403565+jeroen85@users.noreply.github.com>
2026-07-29 12:57:03 -04:00
Fran Fodor 363a91c185 [epaper_spi] Add Inkplate 6COLOR support (#17717) 2026-07-30 00:31:22 +10:00
Clyde Stubbs 4209f242e5 [mipi_spi] Add Waveshare ESP32-S3-Touch-LCD-3.5B (#17513) 2026-07-28 23:18:49 -04:00
Brandon HarveyandClyde Stubbs 9af0302bb9 [core] Expand AGENTS.md AI contributor guidance (#17741)
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
2026-07-28 22:47:43 -04:00
Brandon Harvey 4c571c3ec7 [sen6x] Rename voc/nox sensor keys to voc_index/nox_index (#17725) 2026-07-28 22:35:53 -04:00
Brandon Harvey a9df82fa0a [sen5x] Rename voc/nox sensor keys to voc_index/nox_index (#17724) 2026-07-28 22:35:34 -04:00
Brandon Harvey 6ad804fbc5 [sgp4x] Rename voc/nox sensor keys to voc_index/nox_index (#17723) 2026-07-28 22:35:07 -04:00
Brandon Harvey 227f1d3842 [core] Reject configs that set both keys in cv.rename_key (#17916) 2026-07-28 22:32:59 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 72f904dcfb Bump docker/login-action from 4.5.1 to 4.5.2 in the docker-actions group (#17912)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-28 15:41:50 -04:00
J. Nick Koston 324e222bd2 [improv_serial] Reduce per-loop overhead (#16019) 2026-07-28 15:38:29 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 7b1d70c2c0 Bump actions/stale from 10.4.0 to 11.0.0 (#17913)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-28 15:28:01 -04:00
Kobi Hikri b80fa4ae19 [ci] Bind the branch name to env before sanitising it into a docker tag (#17910) 2026-07-28 12:17:50 -04:00
Edvard Filistovič a4611907a9 [captive_portal] Re-apply json_escape move lost in release merge (#17906) 2026-07-28 08:23:01 -04:00
Jesse Hills 17bd1b2a42 Merge branch 'release' into dev 2026-07-28 22:44:03 +12:00
Jesse Hills 985a08e247 Merge pull request #17905 from esphome/bump-2026.7.3
2026.7.3
2026-07-28 22:43:16 +12:00
Edvard Filistovič 95786459c1 [ble_device_base] Replace raw-advertisement std::function with a lightweight callback slot (#17902) 2026-07-28 09:54:58 +00:00
Jesse Hills 4f3db4c15a Bump version to 2026.7.3 2026-07-28 21:32:52 +12:00
esphome[bot]andJesse Hills 56512abb7e Update webserver local assets to 20260728-053845 (#17899)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2026-07-28 21:32:51 +12:00
Clyde Stubbs 71349a6feb [light] Ensure binary light is off with brightness 0 (#17893) 2026-07-28 21:32:51 +12:00
Jonathan Swoboda 489e3d17ca [ethernet] Set SPI CS hold time for ENC28J60 (#17885) 2026-07-28 21:32:51 +12:00
Jesse Hills d93772fed6 [captive_portal] Escape SSID when building config JSON (#17872) 2026-07-28 21:32:51 +12:00
J. Nick Koston 50f02aa523 [git] Fix submodule update failure when cloning libraries on the esp-idf toolchain (#17862) 2026-07-28 21:32:51 +12:00
J. Nick Koston 5a87ad8fc0 [wifi] Lock scan results shared with the captive portal web task (#17850) 2026-07-28 21:32:51 +12:00
esphome[bot] e40579ad93 Bump bundled esphome-device-builder to 1.7.0 (#17871) 2026-07-28 21:32:51 +12:00
Rui Marinho 37fe59dc37 [espidf] Include .cc, .cxx and .c++ sources in the app source glob (#17754) 2026-07-28 21:32:51 +12:00
Jonathan Swoboda 3829d368ff [espidf] Honor compile_process_limit in native ESP-IDF builds (#17857) 2026-07-28 21:32:42 +12:00
Marcus Voß 013c5d7217 [esp32_ble] Forward ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT to GAP handlers (#17833) 2026-07-28 21:29:28 +12:00
esphome[bot] e38ae51de2 Bump bundled esphome-device-builder to 1.6.10 (#17801) 2026-07-28 21:29:28 +12:00
esphome[bot]andJesse Hills 333ad42e29 Update webserver local assets to 20260728-053845 (#17899)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2026-07-28 21:28:23 +12:00
Edvard Filistovič bb8ffac269 [const] Move CONF_SCAN_PARAMETERS and CONF_WINDOW to components/const (#17900) 2026-07-27 21:19:17 -10:00
Edvard Filistovič 10303b3fa7 [ble_device_base] Add mac_lsb_first_to_uint64 address-packing helper (#17901) 2026-07-27 21:17:40 -10:00
Jesse Hills 93eec5c961 [ci] Attribute device class sync commits to the esphome[bot] account (#17898) 2026-07-28 16:19:00 +12:00
J. Nick Koston 16930c4e2c [ci] Read the GitHub event file as UTF-8 (#17896) 2026-07-27 17:33:08 -10:00
Edvard Filistovič c2476911a3 [bluetooth_proxy] Answer UNPAIR with BluetoothDeviceUnpairingResponse (#17895) 2026-07-27 17:14:37 -10:00
Clyde Stubbs 98d99fb9fd [light] Ensure binary light is off with brightness 0 (#17893) 2026-07-28 14:19:44 +12:00
Jesse Hills ce6c122449 [core] Move JSON string escaping into helpers (#17879) 2026-07-27 22:16:57 -04:00
Brandon Harvey f1f8b102e2 [core] Add optional deprecation warning to cv.rename_key (#17740) 2026-07-27 22:10:34 -04:00
Egor Vorontsov e1ab5a85bb [i2s_audio] Eliminated a double unit conversion in read_() (#17752) 2026-07-27 20:28:40 -04:00
0959141f88 [nrf52] Clone the sdk-nrf manifest repository shallow (#17892)
Co-authored-by: esphbot <esphome@openhomefoundation.org>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-07-27 19:58:50 -04:00
Edvard Filistovič 01616c4f30 [bk72xx_ble_tracker] BLE 5.x scanner for BK72xx (#17135) 2026-07-27 11:58:29 -10:00
J. Nick Kostonandcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> 7c89e81449 [ci] Persist the integration test ccache across runs (#17735)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-27 11:26:49 -10:00
Jonathan Swoboda 6420de53f0 [ethernet] Set SPI CS hold time for ENC28J60 (#17885) 2026-07-27 17:24:53 -04:00
J. Nick Kostonandcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> fa8c7e60da [host] Speed up builds with ccache when available (#17728)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-27 20:47:12 +00:00
Bonne EgglestonandClaude Fable 5 5eeb780538 [modbus] Fold the repeated PDU validation idioms into shared helpers (#17888)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 10:44:15 -10:00
Bonne EgglestonandClaude Fable 5 19511f5787 [modbus] Command lifecycle: PDU-carrying callbacks, on_sent(), notified queue clearing (#17886)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 10:35:51 -10:00
Jesse Hills 56028d0932 [safe_mode] Prevent OTA rollback when entering deep sleep (#17699) 2026-07-27 16:30:52 -04:00
tomaszduda23andJonathan Swoboda ca08803425 [nrf52] add OTA for Adafruit_nRF52_Bootloader (#17381)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-07-27 16:03:22 -04:00
Samuel SiebandSamuel Sieb e209bbb5ae [logger] flush lines for host logger (#17878)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2026-07-27 15:48:29 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 580b3a1a69 Bump CodSpeedHQ/action from 4.18.5 to 4.19.1 (#17890)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-27 15:35:53 -04:00
J. Nick Koston c395ea00cc [libretiny] Speed up builds with ccache when available (#17726) 2026-07-27 18:54:32 +00:00
J. Nick Koston 89654fea66 [rp2] Speed up builds with ccache when available (#17727) 2026-07-27 18:52:29 +00:00
J. Nick Koston 700c0b0460 [esp8266] Speed up builds with ccache when available (#17722) 2026-07-27 08:42:52 -10:00
J. Nick Koston c0aa121c3d [espidf] Install only the toolchains for the variants being built (#17688) 2026-07-27 08:39:03 -10:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Jonathan Swoboda
345bd11a2c Bump ruff from 0.15.22 to 0.16.0 (#17818)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-07-27 12:08:51 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 071ef5016d Bump actions/setup-python from 6.3.0 to 7.0.0 in /.github/actions/restore-python (#17732)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-27 11:47:45 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> fdc2974c0d Bump docker/login-action from 4.4.0 to 4.5.1 in the docker-actions group across 1 directory (#17817)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-27 11:47:31 -04:00
Samuel SiebandSamuel Sieb 899ed02ef2 [remote_base] support haier short IR message (#17826)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2026-07-27 09:52:15 -04:00
Jesse Hills a930baab7c [captive_portal] Escape SSID when building config JSON (#17872) 2026-07-27 19:42:44 +12:00
J. Nick Koston 43526a815e [git] Fix submodule update failure when cloning libraries on the esp-idf toolchain (#17862) 2026-07-27 19:42:21 +12:00
J. Nick Koston 8f321bf346 [wifi] Lock scan results shared with the captive portal web task (#17850) 2026-07-27 17:31:24 +12:00
Daniele PalumboandJesse Hills 91cbcab25e [web_server] Add assumed_state to cover JSON detail (#16800)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2026-07-27 14:01:45 +12:00
Bonne EgglestonandClaude Fable 5 67e0058e72 [modbus] Typed client send helpers and response callbacks (#17435)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 13:14:54 -10:00
esphome[bot] b395a87cad Bump bundled esphome-device-builder to 1.7.0 (#17871) 2026-07-26 13:13:55 -10:00
Jonathan Swoboda 8a7d2d0ca0 [ci] Enforce list form for platform domains in test fixtures (#17869) 2026-07-26 19:01:15 -04:00
Rui Marinho ed066cf0fe [espidf] Include .cc, .cxx and .c++ sources in the app source glob (#17754) 2026-07-27 10:34:29 +12:00
Jonathan Swoboda 741fbbff5a [dallas_temp][ds2484] Fix one_wire test configs for grouped CI batches (#17868) 2026-07-26 17:49:57 -04:00
Edvard Filistovič cf17749307 [ble_device_base] Shared address-type string and discovered-device log (#17861) 2026-07-26 00:49:29 -10:00
Bonne Eggleston 98f2a0b4a4 [modbus] Typed send helpers; migrate in-tree callers off send() (#17859) 2026-07-25 21:57:35 -10:00
J. Nick Kostonandcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> 9dd7dcb06d [espidf] Also skip installing gdb and ULP toolchains ESPHome never runs (#17687)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-25 20:07:14 -10:00
Thomas RupprechtandClaude Opus 4.8 559614077d [ezo_pmp] Fix ml/min and ml unit capitalization (#17803)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 00:22:49 -04:00
Thomas RupprechtandClaude Opus 4.8 b5e18eb101 [opentherm] Fix l/min unit capitalization to L/min (#17804)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 00:16:07 -04:00
J. Nick Koston e0644ab20a [esp32] Capture fault address and raw cause in crash handler (#17769) 2026-07-25 18:12:53 -10:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 98e9dd7954 Bump github/codeql-action/init from 4.37.2 to 4.37.3 (#17785)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-26 00:09:46 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> fbb1a76e2a Bump github/codeql-action/analyze from 4.37.2 to 4.37.3 (#17786)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-26 00:09:37 -04:00
Jonathan Swoboda 7fbb647c65 [espidf] Honor compile_process_limit in native ESP-IDF builds (#17857) 2026-07-25 23:59:33 -04:00
a2c749c277 [modbus] PackedBits and right-sized typed PDU builders (#17848)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-07-25 11:18:46 -10:00
Bonne EgglestonandClaude Fable 5 0833e91fb5 [modbus] Turn the ModbusDevice alias into a working compatibility shim (#17854)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 09:39:07 -10:00
Bonne EgglestonandClaude Fable 5 eaa79b3696 [modbus] Frame accessors, PDU-relative lengths, and span-based send_pdu (#17846)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 03:09:05 +00:00
Ryan Gammon 58385bebff [mipi_spi] Add ST77916 / ESP-VoCat model (#17679) 2026-07-25 02:57:17 +00:00
Michael Pöttgen eb7575f59e [gsl3670] Add a model configuration for the Guition JC8012P4A1 (#17843) 2026-07-25 11:33:17 +10:00
Bonne EgglestonandClaude Fable 5 6648ccc278 [modbus_controller] Qualify modbus::EntityType at use sites (#17845)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 14:01:53 -10:00
Bonne EgglestonandClaude Fable 5 9ac4039c01 [modbus] Rename core enums to EntityType, FunctionCode and ExceptionCode (#17844)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 13:28:22 -10:00
5e2d428e69 [modbus] Heap-free response path (#17377)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-07-24 11:45:59 -10:00
Edvard FilistovičandJ. Nick Koston d12300679e [bk72xx_ble] Scan primitives and main-task scan report queue (#17802)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-07-24 00:26:33 -10:00
Marcus Voß e224a781ff [esp32_ble] Forward ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT to GAP handlers (#17833) 2026-07-23 23:47:27 -10:00
Johan HenkensandClaude Sonnet 4.6 a210c27d14 [api][climate][water_heater][core] Add temperature unit support to climate and water heater c++ entities (#16477)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-23 13:58:39 -10:00
esphome[bot] f0800336b8 Bump bundled esphome-device-builder to 1.6.10 (#17801) 2026-07-23 00:02:31 -10:00
Edvard Filistovič 33fa9f5069 [bk72xx_ble] BLE controller support for BK72xx (BLE 5.x) (#17775) 2026-07-23 08:54:30 +00:00
StasKeith Burzinskipre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
427a504308 [sgp4x] Fix sgp4x VOC baseline restoration (#15667)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-07-23 02:49:38 -05:00
Jesse Hills dd2fbdd43a [github] Add developer-facing feature PR classification (#17795) 2026-07-23 14:48:25 +12:00
Jesse Hills d7d1e5c2f6 Merge branch 'release' into dev 2026-07-23 14:42:36 +12:00
rwalker777pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>kbx81Claude Opus 4.7J. Nick Koston
deba7a1f0c [ethernet][network][wifi] Add network priority for multi-interface support (#14255)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: kbx81 <kbx81x@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-07-22 20:55:04 -05:00
esphome[bot] 4817904637 Bump bundled esphome-device-builder to 1.6.9 (#17793) 2026-07-22 15:49:38 -10:00
J. Nick Koston 98c6fa294a [bundle] Remap absolute file paths when compiling an extracted bundle (#17765) 2026-07-23 11:28:22 +12:00
Flautzandclyde 312b0c53c2 [mipi_spi] fix partial update bug (#17747)
Co-authored-by: clyde <2366188+clydebarrow@users.noreply.github.com>
2026-07-22 20:50:11 +00:00
Kevin Ahrendt 5dfce9b90c [sendspin] Bump sendspin-cpp to v0.7.0 (#17781) 2026-07-22 08:41:33 -10:00
Brandon Harvey 69085a7a42 [sen5x] Add model option to override autodetection (#17764) 2026-07-21 13:18:21 -10:00
dependabot[bot] 2edb089f71 Bump pypa/gh-action-pypi-publish from 1.14.0 to 1.14.1 (#17734)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-21 13:09:45 -10:00
dependabot[bot] 078b1aa1d6 Bump aioesphomeapi from 45.6.2 to 45.7.0 (#17768)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-21 23:02:30 +00:00
dependabot[bot] 1f8fea5379 Bump actions/checkout from 7.0.0 to 7.0.1 (#17733)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-21 12:58:29 -10:00
dependabot[bot] 9c211abc54 Bump github/codeql-action/analyze from 4.37.1 to 4.37.2 (#17761)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-21 12:37:31 -10:00
dependabot[bot] 4afb619672 Bump actions/setup-python from 6.3.0 to 7.0.0 (#17731)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-21 12:32:31 -10:00
Edvard Filistovič 4d3b4659d7 [ble_device_base] Platform-neutral BLE layer; esp32_ble_tracker implements BLEHub (#17150) 2026-07-21 12:32:14 -10:00
J. Nick Koston 37be04f58e [core] Reduce memory footprint of esphome upload (#17684) 2026-07-21 12:21:12 -10:00
dependabot[bot] 1e4bf48ea3 Bump github/codeql-action/init from 4.37.1 to 4.37.2 (#17758)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-21 11:45:39 -10:00
dependabot[bot] 52c6a50b6d Bump astral-sh/setup-uv from 8.3.2 to 9.0.0 (#17760)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-21 11:45:27 -10:00
dependabot[bot] 008c85c7d5 Bump astral-sh/setup-uv from 8.3.2 to 9.0.0 in /.github/actions/restore-python (#17757)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-21 11:34:03 -10:00
dependabot[bot] e84880e4ec Bump platformdirs from 4.10.0 to 4.11.0 (#17756)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-21 11:33:49 -10:00
dependabot[bot] a30f9f7c65 Bump filelock from 3.29.0 to 3.32.0 (#17759)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-21 10:39:43 -10:00
Jesse Hills e931919fb4 Merge branch 'release' into dev 2026-07-21 10:45:17 +12:00
J. Nick Koston cd8d76fa34 [esp8266] Fail fast when Rosetta 2 is missing on Apple Silicon Macs (#17737) 2026-07-21 08:17:01 +12:00
J. Nick Koston 500d4aa9e9 [wireguard] Mark private keys sensitive, stop redacting public keys (#17736) 2026-07-20 20:13:56 +00:00
J. Nick Koston 271c1b409a [espidf] Resume interrupted toolchain downloads instead of restarting (#17706) 2026-07-21 08:12:17 +12:00
J. Nick Koston 9a0e8606cd [platformio] Include cache path in invalid library error (#17692) 2026-07-21 08:08:46 +12:00
tomaszduda23 d5681ac6a0 [nrf52] add platformio deprecation warning (#17705) 2026-07-20 20:29:00 +02:00
057143838d [network] Fix IPAddress IPv6 support on HOST platform (#17067)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-07-20 18:18:37 +00:00
Grégoire SeuxandJ. Nick Koston 4268d91da3 [http_request] Fix usage of http response body (#17713)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-07-20 17:07:28 +00:00
JeroenandJeroen Jansen 6f39030b41 [core] Prevent blocking-warning log time from cascading (#17710)
Co-authored-by: Jeroen Jansen <jeroen@Jeroens-MacBook-Air.local>
2026-07-20 06:28:56 -10:00
J. Nick KostonandJesse Hills 3e1a9e8a3a [platformio] Re-download library when cached copy is missing its manifest (#17691)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2026-07-19 19:34:56 -10:00
J. Nick Kostonandpre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> bb1318dce1 [core] Auto-clean the PlatformIO build environment when the Python version changes (#17671)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-07-19 19:32:38 -10:00
esphome[bot] 26fcc068b6 Bump bundled esphome-device-builder to 1.6.8 (#17708) 2026-07-19 19:32:22 -10:00
2a96fa44ff [light] Fix pulse and other effects (#17645)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-07-20 17:09:02 +12:00
J. Nick Koston 5fa93513f2 [espidf] Make openocd-esp32 optional so its libusb check cannot break installs (#17686) 2026-07-20 17:05:50 +12:00
J. Nick Koston 4a1f54ce4b [git] Detect interrupted clones and re-clone automatically (#17690) 2026-07-20 16:34:29 +12:00
J. Nick Koston 76655cf951 [platformio] Accept git URLs passed as the library name (#17697) 2026-07-20 16:32:20 +12:00
J. Nick Koston dfb988c563 [micro_wake_word] Download models in parallel (#17701) 2026-07-20 16:16:15 +12:00
Jesse Hills 0443b4849b [ssd1306] Fix offset_x being ignored on SH1106/SH1107 displays (#17700) 2026-07-20 15:51:46 +12:00
Brandon Harvey 977376d55c Split multi-token build.flags entries when generating ESP-IDF component CMakeLists (#17649) 2026-07-19 12:20:43 -10:00
esphome[bot] 1f93345af8 Bump bundled esphome-device-builder to 1.6.7 (#17696) 2026-07-19 09:17:53 -10:00
esphome[bot] 96dd2382c1 Bump bundled esphome-device-builder to 1.6.6 (#17681) 2026-07-18 21:25:56 -10:00
esphome[bot] 3064fb0d48 Bump bundled esphome-device-builder to 1.6.5 (#17675) 2026-07-18 11:46:14 -10:00
Jonathan Swoboda 4c9dab9c98 [esp32] Bump recommended ESP-IDF to 5.5.5 and Arduino to 3.3.10 (#17669) 2026-07-18 11:14:42 -10:00
tomaszduda23 0b0e706349 [nrf52] add platform: ultrasonic test (#17665) 2026-07-18 08:05:19 -04:00
tomaszduda23 017040ec8e [zigbee] add radio power off stats (#17521) 2026-07-18 09:09:52 +02:00
Tom 52fe461e9c [ds248x] Add OneWireBus platform for DS248x I2C-to-1Wire bridges (#12717) 2026-07-17 22:01:15 -07:00
esphome[bot] a7dad14449 Bump bundled esphome-device-builder to 1.6.4 (#17662) 2026-07-17 17:44:52 -10:00
Jesse Hills 1493a09521 [nrf52] Install PlatformIO toolchain Python packages into a dedicated venv (#17635) 2026-07-17 23:22:23 -04:00
J. Nick Koston 2a59c0ad9e [logs] Cap the logs reconnect backoff for deep-sleep devices (#17656) 2026-07-17 17:16:14 -10:00
Jonathan Swoboda 70421fb14b [espidf] Prune tool download cache after install to shrink the ESP-IDF cache (#17661) 2026-07-17 22:49:50 -04:00
J. Nick Koston bacc223360 Ship component requirements.txt files in the sdist and wheel (#17660) 2026-07-17 15:59:12 -10:00
J. Nick Koston 94be1da893 Pin cryptography to 48.0.1 on Intel macOS (#17658) 2026-07-17 15:46:16 -10:00
Brandon Harvey cb66dc01ab [core] Fix srcFilter exclusions being silently ignored on Windows (#17648) 2026-07-17 21:45:13 -04:00
Jonathan Swoboda ebaf32c82a [ci] Add LibreTiny clang-tidy environments (#17491) 2026-07-17 21:38:55 -04:00
Jonathan Swoboda f918c299b1 [espidf] Suggest installing missing system libraries when the tools install fails (#17619) 2026-07-17 19:46:45 -04:00
dependabot[bot] 328a2018d2 Bump aioesphomeapi from 45.6.1 to 45.6.2 (#17654)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-17 13:32:47 -10:00
dependabot[bot] a4a9feac16 Bump esphome/workflows/.github/workflows/lock.yml from 2026.4.1 to 2026.7.0 (#17652)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-17 12:26:04 -10:00
esphome[bot] 6f27c7f3fe Bump bundled esphome-device-builder to 1.6.3 (#17651) 2026-07-17 12:25:18 -10:00
dependabot[bot] 300ab1be35 Bump aioesphomeapi from 45.6.0 to 45.6.1 (#17653)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-17 12:00:54 -10:00
esphome[bot]andesphome[bot] <115708604+esphome[bot]@users.noreply.github.com> 7aed5fb94b Bump bundled esphome-device-builder to 1.6.2 (#17640)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-07-17 08:08:28 -04:00
Keith Burzinski 2132cae1c9 [nextion] Fix RP2040 clang-tidy failure from TFT upload guard mismatch (#17638) 2026-07-17 08:07:38 -04:00
Jesse Hills 735f8d607d [nrf52] Set ZEPHYR_SDK_INSTALL_DIR for Zephyr SDK discovery (#17633) 2026-07-18 00:04:35 +12:00
Jonathan Swoboda cc6392785f [runtime_image] Prevent integer overflow in image buffer size calculation (#17624) 2026-07-16 18:36:18 -04:00
Jonathan Swoboda 7a2d13da90 [web_server_idf] Use core format_hex_to helper for digest auth (fixes Arduino build) (#17608) 2026-07-16 15:59:53 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> e2df9fb554 Bump ruff from 0.15.21 to 0.15.22 (#17628)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-16 15:43:43 -04:00
Jonathan Swoboda 4507d05822 [http_request] Fix use-after-return of header collection state in IDF backend (#17627) 2026-07-16 15:34:49 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 3caae3031a Bump github/codeql-action/init from 4.37.0 to 4.37.1 (#17629)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-16 15:33:38 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> a624659856 Bump github/codeql-action/analyze from 4.37.0 to 4.37.1 (#17630)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-16 15:33:30 -04:00
Jonathan Swoboda 6c8d0050ab [esp32_ble_server] Validate descriptor write length before copying (#17626) 2026-07-16 15:32:44 -04:00
Jonathan Swoboda 09da2766ed [bluetooth_proxy] Bound GATT characteristic/descriptor enumeration to allocated size (#17625) 2026-07-16 15:32:09 -04:00
Jonathan SwobodaandCopilot Autofix powered by AI 72bd45538e [opentherm] Rename OpenthermData accessors that collide with vendor SDK type macros (#17611)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-16 15:25:45 -04:00
Jonathan Swoboda 5b37049dff [midea] Restrict to platforms where MideaUART actually builds (#17613) 2026-07-16 15:23:46 -04:00
Jonathan Swoboda c0e78a5574 [core] Improve framework mirror selection, download errors, and version parsing (#17615) 2026-07-16 15:22:19 -04:00
Jonathan Swoboda f53394e46f [mlx90393] Compile out on BK72xx where the bundled library cannot build (#17614) 2026-07-16 15:14:18 -04:00
J. Nick Koston 2218edf20c [web_server] Switch entity id to the new format and drop name_id (#17586) 2026-07-16 08:49:43 -10:00
J. Nick Koston dcdd044234 [web_server] Use alarm_control_panel as the domain in JSON (#17594) 2026-07-16 08:48:47 -10:00
J. Nick Koston bc8510c6d8 [micro_wake_word] Include the local model file in bundles (#17604) 2026-07-16 08:09:30 -04:00
J. Nick Koston 5b4ae22f58 [api] Don't spam tracebacks when decoding a crash without a local build (#17597) 2026-07-16 08:08:28 -04:00
J. Nick Koston 14e71e190c [ci] Restore memory impact detail for ESP-IDF builds (#17587) 2026-07-16 08:00:19 -04:00
Guanzhong Chen 2333e6eef5 [zephyr] implement ISRInternalGPIOPin::digital_write (#17601) 2026-07-16 07:57:30 -04:00
Jonathan Swoboda d748c04b28 [libretiny] Code fixes for upcoming LibreTiny clang-tidy scans (#17596) 2026-07-16 07:25:46 -04:00
Jonathan Swoboda 8d1a0446a2 [haier][teleinfo][hlk_fm22x][rp2040_ble] Rename enum members that collide with vendor SDK macros (#17595) 2026-07-16 07:25:37 -04:00
Keith BurzinskiandClaude Opus 4.8 0887e01828 [improv_base] Bump Improv library to 1.2.6 (#17599)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 05:17:01 +00:00
J. Nick Koston f88621f2e1 [network] Remove deprecated IPAddress::str() (#17589) 2026-07-16 05:12:06 +00:00
J. Nick Koston ec3f7ec16e [api] Remove outdated API version warning (#17593) 2026-07-16 05:07:26 +00:00
J. Nick Koston 880cb1db43 [voice_assistant] Remove deprecated Timer::to_string() (#17591) 2026-07-16 05:06:49 +00:00
J. Nick Koston f336c45177 [water_heater] Remove deprecated WaterHeaterCall::get_state() (#17592) 2026-07-16 05:06:38 +00:00
J. Nick Koston b12d392e02 [esp32_ble] Remove deprecated ESPBTUUID::to_string() (#17590) 2026-07-16 05:06:30 +00:00
Keith BurzinskiandClaude Fable 5 49a1d2bb1b [veml3235] Overhaul auto-gain and fix latent config bugs (#17551)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 23:08:14 -05:00
J. Nick Koston e327a7f52f [scheduler] Remove deprecated set_retry/cancel_retry (#17585) 2026-07-15 17:58:41 -10:00
Jonathan Swoboda 90e838d327 [ci] Extend variant clang-tidy scans to USB logger, tsens and LP peripheral code (#17580) 2026-07-15 23:07:34 -04:00
Jonathan Swoboda f91aa7aa13 [as3935_i2c] Use repeated start when reading registers (#17584) 2026-07-16 14:26:23 +12:00
lsellens be3b27f378 [rc522_i2c] Change default address to match whats in the docs (#17566) 2026-07-15 21:20:07 -04:00
Jesse Hills 3439a08b68 [deep_sleep] Add on_wake automation triggers (#17569) 2026-07-15 21:19:41 -04:00
dedca344f9 [aqi] Add extended_range option for over-range AQI values (#17570)
Co-authored-by: jas <jas@asspa.in>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 21:17:58 -04:00
Hajo Noerenberg c4975e1870 [cc1101] Add FOCCFG and BSCFG config options (#17577) 2026-07-15 21:17:39 -04:00
Jonathan Swoboda 5a3c2f4d11 [ci] Add rp2 clang-tidy environment (#17486) 2026-07-15 21:17:21 -04:00
Jonathan Swoboda e700b01406 [haier] Fix outdoor defrost temperature reporting the coil temperature (#17492) 2026-07-15 21:16:38 -04:00
Jonathan Swoboda d53d4c5b58 [emc2101] Fix negative external temperatures reported as large positives (#17494) 2026-07-15 21:16:24 -04:00
Jesse Hills 3c34fc950e Merge branch 'release' into dev 2026-07-16 12:59:58 +12:00
Jesse Hills b0489e21b2 Merge branch 'beta' into dev 2026-07-16 09:45:16 +12:00
Jesse Hills a3ab7961e0 [core] Fix wait_until crash when re-entered from its own continuation (#17571) 2026-07-16 08:14:55 +12:00
Hajo Noerenberg 54987d7d23 [cc1101] Export CC1101Listener to Python (#17576) 2026-07-15 12:38:08 -04:00
esphome[bot]andesphome[bot] <115708604+esphome[bot]@users.noreply.github.com> 053ce39fc6 Bump bundled esphome-device-builder to 1.6.1 (#17575)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-07-15 08:51:50 -04:00
Jesse Hills b295b8d5a2 [api] Warn when Home Assistant actions are sent with no subscribed client (#17560) 2026-07-15 23:11:47 +12:00
esphome[bot] 4d3d06959b Bump bundled esphome-device-builder to 1.6.0 (#17573) 2026-07-14 23:59:19 -10:00
Jesse Hills a5583dcba6 [tests] Add test_display component to free touchscreen tests from display pins (#17540) 2026-07-15 11:22:47 +12:00
luar123 e2b62bcd00 [zigbee] bump esp-zigbee-sdk to 2.0.3 (#17564) 2026-07-14 17:27:17 -04:00
Jonathan Swoboda 4275343231 [atc_mithermometer] Make duplicate-packet counter per-instance (#17496) 2026-07-15 09:23:37 +12:00
dependabot[bot] 4ebe49b141 Bump clang-tidy from 22.1.7 to 22.1.8 (#17565)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-14 09:54:02 -10:00
Jesse Hills d3adb6fb81 Merge branch 'beta' into dev 2026-07-14 16:46:31 +12:00
esphome[bot] 2753ab1f45 Bump bundled esphome-device-builder to 1.5.0 (#17558) 2026-07-13 16:50:08 -10:00
Daniele PalumboandJesse Hills b6b5b61640 [mcp23017] reset IPOL registers to 0x00 on setup (#17177)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2026-07-14 02:38:07 +00:00
Jesse Hills db9a09d05a [script] Recursively expand nested packages when merging component tests (#17557) 2026-07-14 14:11:03 +12:00
Keith Burzinski ca77cc585c [nextion] Fix unbounded queue growth and OOM crash when display sends no data (#17553) 2026-07-14 11:56:03 +12:00
Jonathan Swoboda f1e4726f4e [veml7700][as7341][ltr501] Fix device class on raw-count sensors (#17549) 2026-07-14 11:53:31 +12:00
J. Nick Koston 49bbceb1da [libretiny] Keep renamed board generic-ln882hki validating against generic-ln882h (#17542) 2026-07-14 11:51:54 +12:00
Jesse Hills 65d6c028ce [web_server] Add HTTP digest authentication with selectable scheme (#17541) 2026-07-14 10:33:44 +12:00
Clyde Stubbs 5e3e2f82c9 [script] Fix duplicate import in build_codeowners.py (#17543) 2026-07-13 10:16:53 -04:00
Jesse Hills d78cb09b17 [web_server] Use dict-style packages in tests so they can be batch-grouped (#17544) 2026-07-13 23:23:55 +12:00
Keith Burzinski bcfb438a81 [esp32] Do not require verification_key with Secure Boot V2 signing schemes (#17497) 2026-07-13 16:23:40 +12:00
Jesse Hills a8dfd00cc6 [web_server] Add CORS origin checking with allowed_origins (#17530) 2026-07-13 15:38:48 +12:00
Jesse Hills 4a82b10783 [ci] Group component test output into collapsible CI log sections (#17536) 2026-07-13 15:29:35 +12:00
Jesse Hills 07460ebee4 [gsl3670] Fix i2c package variant in esp32-s3-idf test (#17535) 2026-07-13 15:28:55 +12:00
Jesse Hills cdc42e5e55 Merge branch 'beta' into dev 2026-07-13 13:25:21 +12:00
Jonathan Swoboda b3e03868b3 [mipi_rgb] Test in isolation to avoid bus/pin merge conflicts (#17534) 2026-07-13 12:00:22 +12:00
Jesse Hills 0ff11674ef [mipi_rgb] Use dict-style packages in test so it can be batch-grouped (#17533) 2026-07-12 18:38:38 -04:00
Keith Burzinski 434cffb745 [zwave_proxy] Fix parser gaps and harden frame and subscription handling (#17461) 2026-07-13 08:58:17 +12:00
J. Nick Koston 2b3027a7fd [api] Provision encryption keys over an encrypted zero-PSK noise connection (#17482) 2026-07-13 08:55:30 +12:00
Jesse Hills e27a14ec70 [core] Classify entity metadata visibility for the visual editor (#17503) 2026-07-12 08:33:29 -04:00
luar123 91c42381f6 [zigbee] prevent task watchdog trigger with large configs. (#17506) 2026-07-12 08:32:01 -04:00
luar123 a39607476a [zigbee] Fix merge endpoint (#17511) 2026-07-12 08:31:39 -04:00
J. Nick Koston b098571a6f [web_server] Fix unused function warning for json_state_str (#17524) 2026-07-11 14:31:14 -10:00
J. Nick Koston 614fd88829 [mdns] Fix missing device info TXT records when native API is not enabled (#17520) 2026-07-11 14:31:01 -10:00
J. Nick Koston c0636e2bf7 [core] Make config-hash independent of machine-local paths (#17523) 2026-07-11 14:30:47 -10:00
esphome[bot] 65353006c8 Bump bundled esphome-device-builder to 1.4.3 (#17522) 2026-07-11 13:32:45 -10:00
tomaszduda23 35a99f478e [deep_sleep] feed watchdog in deep sleep (#17516) 2026-07-11 09:48:11 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> b6a4dd237e Update tzdata requirement from >=2026.2 to >=2026.3 (#17510)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-11 09:45:42 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 0ef85783dc Bump actions/stale from 10.3.0 to 10.4.0 (#17509)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-11 09:45:23 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 5020179210 Bump ruff from 0.15.20 to 0.15.21 (#17508)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-11 09:45:02 -04:00
Clyde StubbsandClaude Opus 4.8 54529412dc [mipi_dsi] New model for M5Stack Tab5 (#17500)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 09:38:01 -04:00
Clyde Stubbsandpre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> e6525b5d93 [mipi][mipi_spi] SWRESET handling improved (#17504)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-07-11 09:36:26 -04:00
Raymond 9f62cf9243 [mipi_dsi] Add JC8012P4A1-V2 (#17457) 2026-07-11 21:24:00 +10:00
esphome[bot] 478bca026c Bump bundled esphome-device-builder to 1.4.2 (#17512) 2026-07-10 16:37:00 -10:00
Clyde StubbsandClaude Opus 4.8 e1719cd85d [mipi][mipi_spi][mipi_dsi][mipi_rgb] Transform cleanup (#17405)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 22:06:40 -04:00
esphome[bot] 83aaed71e1 Bump bundled esphome-device-builder to 1.4.1 (#17507) 2026-07-10 10:30:19 -10:00
Jesse Hills 88ca0d44e0 [docs] Document web server as an open HTTP API by design in threat model (#17465) 2026-07-10 16:38:23 +12:00
esphome[bot] 7a1e0bbbec Bump bundled esphome-device-builder to 1.4.0 (#17495) 2026-07-09 15:36:42 -10:00
Jonathan Swoboda b2226b91ff [web_server] Serialize entity state strings without a copy buffer (#17488) 2026-07-09 16:51:04 -04:00
dependabot[bot] ba84f2ec55 Bump aioesphomeapi from 45.5.2 to 45.6.0 (#17490)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-09 19:53:42 +00:00
dependabot[bot] 4b19de0c1a Bump CodSpeedHQ/action from 4.18.4 to 4.18.5 (#17489)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-09 19:35:16 +00:00
Jonathan Swoboda 4292e7988e [mcp4461] Fix wiper increment/decrement write length (#17487) 2026-07-09 15:27:05 -04:00
Jonathan Swoboda 9f21fd0b55 [usb_uart] Fix output chunk length truncated to 8 bits (#17480) 2026-07-09 15:04:42 -04:00
Jesse Hills 9327d011fc Merge branch 'beta' into dev 2026-07-09 16:54:49 +12:00
Jonathan Swoboda 435dde67d0 [ci] Stop per-PR cache copies from crowding the 10GB Actions cache quota (#17463) 2026-07-08 23:29:23 -04:00
Jonathan Swoboda 9c92ab63fb [gsl3670] Reference the test display explicitly so grouped CI builds validate (#17462) 2026-07-09 13:43:56 +12:00
Elvin Luff acc8381cbb [epaper_spi] Remove noop deep sleep command (#15595) 2026-07-09 11:29:39 +10:00
Jesse Hills 0a8a7e22d2 Bump version to 2026.8.0-dev 2026-07-09 12:30:54 +12:00
2612 changed files with 118743 additions and 28513 deletions
+16
View File
@@ -0,0 +1,16 @@
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "d=\"${CLAUDE_PROJECT_DIR:-.}\"; [ -x \"$d/venv/bin/python\" ] || { mkdir -p \"$d/.temp\"; env -u VIRTUAL_ENV \"$d/script/setup\" >\"$d/.temp/setup.log\" 2>&1 || echo '{\"systemMessage\":\"script/setup failed; see .temp/setup.log\"}'; }",
"statusMessage": "Setting up dev environment (script/setup)...",
"timeout": 900
}
]
}
]
}
}
+1 -1
View File
@@ -8,7 +8,7 @@ contact_links:
url: https://github.com/esphome/esphome-webserver/issues/new/choose
about: Report an issue with the ESPHome web server.
- name: Report an issue with the ESPHome Builder / Dashboard
url: https://github.com/esphome/dashboard/issues/new/choose
url: https://github.com/esphome/device-builder/issues/new/choose
about: Report an issue with the ESPHome Builder / Dashboard.
- name: Report an issue with the ESPHome API client
url: https://github.com/esphome/aioesphomeapi/issues/new/choose
+5
View File
@@ -6,6 +6,7 @@
- [ ] Bugfix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] New developer-facing feature (adds functionality for component developers; no end-user configuration change)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) — [policy](https://developers.esphome.io/contributing/code/#what-constitutes-a-c-breaking-change)
- [ ] Developer breaking change (an API change that could break external components) — [policy](https://developers.esphome.io/contributing/code/#what-is-considered-public-c-api)
- [ ] Undocumented C++ API change (removal or change of undocumented public methods that lambda users may depend on) — [policy](https://developers.esphome.io/contributing/code/#c-user-expectations)
@@ -20,6 +21,10 @@
- esphome/esphome.io#<esphome.io PR number goes here>
**Pull request in [developers.esphome.io](https://github.com/esphome/developers.esphome.io) with developer documentation (if applicable):**
- esphome/developers.esphome.io#<developers.esphome.io PR number goes here>
## Test Environment
- [ ] ESP32
@@ -0,0 +1,50 @@
name: Cache clang-tidy idedata
description: >
Cache the clang-tidy idedata and the headers it references under .temp
(headers only, about 30MB per env). Run after restore-python and cache-esp-idf.
inputs:
environment:
description: 'clang-tidy environment (e.g. esp32-idf-tidy).'
required: true
runs:
using: composite
steps:
- name: Compute cache key
id: key
shell: bash
run: |
. venv/bin/activate
[ -n "${{ inputs.environment }}" ] || { echo "::error::cache-clang-tidy-idedata: 'environment' input is empty"; exit 1; }
hash=$(python -c 'import sys; sys.path.insert(0, "script"); from clang_tidy_hash import idedata_cache_hash; print(idedata_cache_hash("${{ inputs.environment }}"))')
pyver=$(python -c 'import platform; print(platform.python_version())')
# Generating idedata is what installs ESP-IDF; never skip it over a missing
# install. This also skips the save, so a dev run that installs ESP-IDF
# warms the idedata cache on the next run.
if [ -d ~/.esphome-idf/frameworks ]; then
echo "skip=false" >> "$GITHUB_OUTPUT"
else
echo "ESP-IDF install missing, not using the clang-tidy idedata cache"
echo "skip=true" >> "$GITHUB_OUTPUT"
fi
echo "key=${{ runner.os }}-tidy-idedata-${{ inputs.environment }}-$hash-py$pyver" >> "$GITHUB_OUTPUT"
{
echo "path<<EOF"
printf '%s\n' '.temp/idedata-*.json' '.temp/idedata-*.hash'
printf '.temp/**/*.%s\n' h hpp hh hxx inc inl ipp tpp
echo "EOF"
} >> "$GITHUB_OUTPUT"
# Mirror cache-esp-idf: write on dev, restore-only on PRs. The post-step
# save only runs when the job succeeded, so a failed generation is never saved.
# Extend the extension list if a component ships extensionless headers.
- name: Cache clang-tidy idedata (write on dev)
if: (github.ref == 'refs/heads/dev' || contains(github.event.pull_request.labels.*.name, 'ci-cache-write')) && steps.key.outputs.skip != 'true'
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ steps.key.outputs.path }}
key: ${{ steps.key.outputs.key }}
- name: Cache clang-tidy idedata (restore-only off dev)
if: github.ref != 'refs/heads/dev' && !contains(github.event.pull_request.labels.*.name, 'ci-cache-write') && steps.key.outputs.skip != 'true'
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ steps.key.outputs.path }}
key: ${{ steps.key.outputs.key }}
+25 -7
View File
@@ -3,8 +3,10 @@ description: >
Resolve the pinned ESP-IDF version and cache the native ESP-IDF install
(toolchains + source) at ~/.esphome-idf. Every job that installs ESP-IDF
natively (clang-tidy for IDF/Arduino and the component test batches) shares
one cache, since the install is identical (ESPHOME_IDF_DEFAULT_TARGETS
defaults to "all", so all toolchains are present regardless of the chip).
one cache, since the install is identical: ESPHOME_IDF_DEFAULT_TARGETS
defaults to "all", and _get_configured_targets() in espidf/toolchain.py
skips per-variant narrowing whenever CI is set, so all toolchains are
present regardless of the chip a job builds.
Callers must set env ESPHOME_ESP_IDF_PREFIX: ~/.esphome-idf and have the
Python venv already restored.
inputs:
@@ -24,6 +26,9 @@ runs:
# The native-IDF version is pinned in code, not in any file that feeds the
# other cache keys, so resolve it explicitly. Keying on it means the cache
# invalidates on a version bump (actions/cache never overwrites a key).
# Also key on the Python version: the cached IDF venv links to the
# runner's toolcache interpreter and is reinstalled every run after a
# runner image bump.
id: version
shell: bash
run: |
@@ -34,19 +39,32 @@ runs:
version=$(python -c 'from esphome.components.esp32 import ESP_IDF_FRAMEWORK_VERSION_LOOKUP as L; print(L["recommended"])')
fi
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "python-version=$(python -c 'import platform; print(platform.python_version())')" >> "$GITHUB_OUTPUT"
# Mirror the adjacent PlatformIO cache: only dev-branch runs write the
# shared cache (so it lives in the default-branch scope readable by all
# PRs), and PRs are restore-only -- they never push multi-GB artifacts into
# their own scope / the repo quota (e.g. on a version-bump PR).
# their own scope / the repo quota (e.g. on a version-bump PR). The
# ci-cache-write label lets a PR write into its own scope to test the hit path;
# that costs about 1GB of the repo cache quota per run, so remove it when done.
# -slim: bump when prune-esp-idf changes what it removes; a key is never overwritten.
- name: Cache ESP-IDF install (write on dev)
if: github.ref == 'refs/heads/dev' && inputs.restore-only != 'true'
if: (github.ref == 'refs/heads/dev' || contains(github.event.pull_request.labels.*.name, 'ci-cache-write')) && inputs.restore-only != 'true'
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ~/.esphome-idf
key: ${{ runner.os }}-esphome-idf-${{ steps.version.outputs.version }}
key: ${{ runner.os }}-esphome-idf-${{ steps.version.outputs.version }}-py${{ steps.version.outputs.python-version }}-slim
- name: Cache ESP-IDF install (restore-only off dev)
if: github.ref != 'refs/heads/dev' || inputs.restore-only == 'true'
if: github.ref != 'refs/heads/dev' && !contains(github.event.pull_request.labels.*.name, 'ci-cache-write') || inputs.restore-only == 'true'
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ~/.esphome-idf
key: ${{ runner.os }}-esphome-idf-${{ steps.version.outputs.version }}
key: ${{ runner.os }}-esphome-idf-${{ steps.version.outputs.version }}-py${{ steps.version.outputs.python-version }}-slim
# Install explicitly so the prune below sees the toolchains on a cache miss
# too, instead of the install happening inside the first build step.
- name: Install ESP-IDF
shell: bash
run: |
. venv/bin/activate
python -c 'from esphome.espidf.framework import check_esp_idf_install; check_esp_idf_install("${{ steps.version.outputs.version }}")'
- name: Prune ESP-IDF install
uses: ./.github/actions/prune-esp-idf
+34
View File
@@ -0,0 +1,34 @@
name: Prune ESP-IDF install
description: >
Remove the picolibc sysroots (1.1GB of the 3.9GB install) from the native
ESP-IDF toolchains; IDF 5.x links newlib. Skipped when an IDF 6 install is
present, which links picolibc (see esp32/__init__.py).
runs:
using: composite
steps:
- name: Prune picolibc
shell: bash
run: |
shopt -s nullglob
prefix="${ESPHOME_ESP_IDF_PREFIX:-$HOME/.esphome-idf}"
prefix="${prefix/#\~/$HOME}"
for fw in "$prefix"/frameworks/*/; do
case "$(basename "$fw")" in
[6-9].*) echo "IDF $(basename "$fw") installed, keeping picolibc"; exit 0 ;;
esac
done
n=0
for dir in "$prefix"/tools/*-esp-elf/*/*-esp-elf/picolibc; do
echo "Removing $dir ($(du -sh "$dir" | cut -f1))"
rm -rf "$dir"
n=$((n + 1))
done
# The marker rides along in the cache entry so a restored slim tree stays quiet.
if [ "$n" -gt 0 ]; then
touch "$prefix/.picolibc-pruned"
elif [ -d "$prefix/tools" ] && [ ! -f "$prefix/.picolibc-pruned" ]; then
echo "::warning::no picolibc sysroots matched under $prefix/tools"
fi
if [ -d "$prefix" ]; then
du -sh "$prefix"
fi
+4 -4
View File
@@ -17,7 +17,7 @@ runs:
steps:
- name: Set up Python ${{ inputs.python-version }}
id: python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ inputs.python-version }}
- name: Restore Python virtual environment
@@ -32,7 +32,7 @@ runs:
# detects the activated venv via ``VIRTUAL_ENV`` so the venv layout
# downstream jobs rely on is preserved.
if: steps.cache-venv.outputs.cache-hit != 'true'
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: true
# Pull request saves land in per-PR scopes nothing else can
@@ -49,7 +49,7 @@ runs:
python -m venv venv
source venv/bin/activate
python --version
uv pip install -r requirements.txt -r requirements_test.txt
uv pip install -r requirements.txt -r requirements_dev.txt -r requirements_test.txt
uv pip install -e .
- name: Create Python virtual environment
if: steps.cache-venv.outputs.cache-hit != 'true' && runner.os == 'Windows'
@@ -58,5 +58,5 @@ runs:
python -m venv venv
source ./venv/Scripts/activate
python --version
uv pip install -r requirements.txt -r requirements_test.txt
uv pip install -r requirements.txt -r requirements_dev.txt -r requirements_test.txt
uv pip install -e .
@@ -13,6 +13,7 @@ module.exports = {
'merging-to-release',
'merging-to-beta',
'chained-pr',
'stacked-pr',
'core',
'small-pr',
'medium-pr',
@@ -22,11 +23,13 @@ module.exports = {
'has-tests',
'needs-tests',
'needs-docs',
'needs-developer-docs',
'needs-codeowners',
'too-big',
'labeller-recheck',
'bugfix',
'new-feature',
'new-feature-developer',
'breaking-change',
'developer-breaking-change',
'undocumented-api-change',
@@ -40,5 +43,17 @@ module.exports = {
// Keep matching the old esphome-docs name during the transition period
/https:\/\/github\.com\/esphome\/esphome-docs\/pull\/\d+/,
/esphome\/esphome-docs#\d+/
],
DEVELOPER_DOCS_PR_PATTERNS: [
/https:\/\/github\.com\/esphome\/developers\.esphome\.io\/pull\/\d+/,
/esphome\/developers\.esphome\.io#\d+/
],
// Files whose developer-facing changes are documented via Python docstrings
// only - developers.esphome.io has no reference page for them yet, so PRs
// touching nothing but these files (and tests/) skip needs-developer-docs.
DEV_DOCS_EXEMPT_FILES: [
'esphome/config_validation.py'
]
};
+62 -5
View File
@@ -1,4 +1,4 @@
const { DOCS_PR_PATTERNS } = require('./constants');
const { DOCS_PR_PATTERNS, DEVELOPER_DOCS_PR_PATTERNS, DEV_DOCS_EXEMPT_FILES } = require('./constants');
const {
COMPONENT_REGEX,
detectComponents,
@@ -33,16 +33,54 @@ async function fetchPrFileContent(github, context, path) {
}
}
// Check whether a pull request is part of a GitHub stack.
//
// GitHub's stacked pull request feature adds a `stack` object to the pull
// request resource. It is present on every pull request in the stack -
// including the bottom one, whose base is already `dev` - and is absent
// entirely on standalone pull requests.
//
// The `pull_request_target` webhook payload is not guaranteed to carry this
// field, so fall back to asking the API when it is missing. Guessing wrong
// here is costly: a stacked pull request mistaken for a manually chained one
// gets a label that blocks merging.
async function isStackedPr(github, context) {
const pr = context.payload.pull_request;
if (pr.stack != null) {
return true;
}
try {
const { owner, repo } = context.repo;
const { data } = await github.rest.pulls.get({
owner,
repo,
pull_number: pr.number,
});
return data.stack != null;
} catch (error) {
// Treat an API failure as "not stacked" so a chained pull request still
// gets its blocking label rather than silently slipping through.
console.log('Failed to check stack membership:', error.message);
return false;
}
}
// Strategy: Merge branch detection
async function detectMergeBranch(context) {
async function detectMergeBranch(github, context) {
const labels = new Set();
const baseRef = context.payload.pull_request.base.ref;
const defaultBranch = context.payload.repository.default_branch;
if (baseRef === 'release') {
labels.add('merging-to-release');
} else if (baseRef === 'beta') {
labels.add('merging-to-beta');
} else if (baseRef !== 'dev') {
} else if (await isStackedPr(github, context)) {
// GitHub manages the merge order for a stack, so these are not blocked.
labels.add('stacked-pr');
} else if (baseRef !== defaultBranch) {
// A chain built by hand: it must not merge until its base branch does.
labels.add('chained-pr');
}
@@ -245,6 +283,7 @@ async function detectPRTemplateCheckboxes(context) {
const checkboxPatterns = [
{ pattern: /- \[x\] Bugfix \(non-breaking change which fixes an issue\)/i, label: 'bugfix' },
{ pattern: /- \[x\] New feature \(non-breaking change which adds functionality\)/i, label: 'new-feature' },
{ pattern: /- \[x\] New developer-facing feature \(adds functionality for component developers; no end-user configuration change\)/i, label: 'new-feature-developer' },
{ pattern: /- \[x\] Breaking change \(fix or feature that would cause existing functionality to not work as expected\)/i, label: 'breaking-change' },
{ pattern: /- \[x\] Developer breaking change \(an API change that could break external components\)/i, label: 'developer-breaking-change' },
{ pattern: /- \[x\] Undocumented C\+\+ API change \(removal or change of undocumented public methods that lambda users may depend on\)/i, label: 'undocumented-api-change' },
@@ -355,12 +394,14 @@ async function detectRequirements(allLabels, prFiles, context, hasYamlLoadable)
const labels = new Set();
// Check for missing tests
if ((allLabels.has('new-component') || allLabels.has('new-platform') || allLabels.has('new-feature')) && !allLabels.has('has-tests')) {
if ((allLabels.has('new-component') || allLabels.has('new-platform') || allLabels.has('new-feature') || allLabels.has('new-feature-developer')) && !allLabels.has('has-tests')) {
labels.add('needs-tests');
}
// Check for missing docs.
// `new-feature` (PR-body checkbox) always counts. `new-component` / `new-platform`
// `new-feature` (PR-body checkbox) always counts. `new-feature-developer` is
// deliberately excluded here: its docs live on developers.esphome.io and are
// checked separately below. `new-component` / `new-platform`
// only count when at least one newly added file defines a top-level CONFIG_SCHEMA,
// i.e. the new component/platform is actually loadable from YAML.
const docsEligible =
@@ -376,6 +417,22 @@ async function detectRequirements(allLabels, prFiles, context, hasYamlLoadable)
}
}
// Check for missing developer docs. `new-feature-developer` requires a
// developers.esphome.io PR link, unless every changed file outside tests/ is
// in DEV_DOCS_EXEMPT_FILES (core validators documented via docstrings only).
if (allLabels.has('new-feature-developer')) {
const prBody = context.payload.pull_request.body || '';
const nonTestFiles = prFiles
.map(file => file.filename)
.filter(file => !file.startsWith('tests/'));
const onlyExemptFiles = nonTestFiles.every(file => DEV_DOCS_EXEMPT_FILES.includes(file));
const hasDevDocsLink = DEVELOPER_DOCS_PR_PATTERNS.some(pattern => pattern.test(prBody));
if (!onlyExemptFiles && !hasDevDocsLink) {
labels.add('needs-developer-docs');
}
}
// Check for missing CODEOWNERS
if (allLabels.has('new-component')) {
const codeownersModified = prFiles.some(file =>
+2 -2
View File
@@ -88,7 +88,7 @@ module.exports = async ({ github, context }) => {
// Early exit for release and beta branches only
if (baseRef === 'release' || baseRef === 'beta') {
const branchLabels = await detectMergeBranch(context);
const branchLabels = await detectMergeBranch(github, context);
const finalLabels = Array.from(branchLabels);
console.log('Computed labels (merge branch only):', finalLabels.join(', '));
@@ -118,7 +118,7 @@ module.exports = async ({ github, context }) => {
deprecatedResult,
maintainerAccess
] = await Promise.all([
detectMergeBranch(context),
detectMergeBranch(github, context),
detectComponentPlatforms(changedFiles, apiData),
detectNewComponents(github, context, prFiles),
detectNewPlatforms(github, context, prFiles, apiData),
@@ -1,6 +1,14 @@
const { describe, it } = require('node:test');
const assert = require('node:assert/strict');
const { detectNewPlatforms, detectNewComponents, detectPRSize } = require('../detectors');
const {
detectMergeBranch,
detectNewPlatforms,
detectNewComponents,
detectPRSize,
detectPRTemplateCheckboxes,
detectRequirements,
} = require('../detectors');
const { MANAGED_LABELS } = require('../constants');
// Minimal GitHub API mock — only repos.getContent is called by detectNewPlatforms/detectNewComponents
// to check for CONFIG_SCHEMA in newly added files.
@@ -29,6 +37,122 @@ const API_DATA = {
const WITH_SCHEMA = 'CONFIG_SCHEMA = cv.Schema({})';
const WITHOUT_SCHEMA = 'CODEOWNERS = ["@esphome/core"]';
// ---------------------------------------------------------------------------
// detectMergeBranch
// ---------------------------------------------------------------------------
// Builds a fresh context for detectMergeBranch tests instead of mutating the
// shared CONTEXT fixture above (which other describe blocks rely on).
function makeMergeContext(baseRef, { stack, defaultBranch = 'dev' } = {}) {
const pull_request = { number: 1, base: { ref: baseRef } };
if (stack !== undefined) {
pull_request.stack = stack;
}
return {
repo: { owner: 'esphome', repo: 'esphome' },
payload: { pull_request, repository: { default_branch: defaultBranch } }
};
}
// A GitHub API mock exposing only rest.pulls.get, with a call counter so
// tests can assert whether the API fallback was actually invoked.
function makeStackGithub({ stack = null, error = null } = {}) {
const state = { calls: 0 };
const github = {
rest: {
pulls: {
get: async () => {
state.calls++;
if (error) throw error;
return { data: { stack } };
}
}
}
};
return { github, state };
}
const STACK_INFO = { base: { ref: 'dev' }, id: 71540, number: 17978, position: 3, size: 3 };
describe('detectMergeBranch', () => {
it('base ref release adds merging-to-release only and never checks the stack', async () => {
const { github, state } = makeStackGithub({ stack: STACK_INFO });
const context = makeMergeContext('release', { stack: STACK_INFO });
const labels = await detectMergeBranch(github, context);
assert.deepEqual(Array.from(labels).sort(), ['merging-to-release']);
assert.equal(state.calls, 0);
});
it('base ref beta adds merging-to-beta only and never checks the stack', async () => {
const { github, state } = makeStackGithub({ stack: STACK_INFO });
const context = makeMergeContext('beta', { stack: STACK_INFO });
const labels = await detectMergeBranch(github, context);
assert.deepEqual(Array.from(labels).sort(), ['merging-to-beta']);
assert.equal(state.calls, 0);
});
it('stack present on the webhook payload adds stacked-pr without calling the API', async () => {
const { github, state } = makeStackGithub();
const context = makeMergeContext('feature-branch', { stack: STACK_INFO });
const labels = await detectMergeBranch(github, context);
assert.deepEqual(Array.from(labels).sort(), ['stacked-pr']);
assert.equal(state.calls, 0);
});
it('stack absent from payload falls back to the API and adds stacked-pr', async () => {
const { github, state } = makeStackGithub({ stack: STACK_INFO });
const context = makeMergeContext('feature-branch');
const labels = await detectMergeBranch(github, context);
assert.deepEqual(Array.from(labels).sort(), ['stacked-pr']);
assert.equal(state.calls, 1);
});
it('bottom of a stack (base ref dev, stack present) still adds stacked-pr', async () => {
const { github, state } = makeStackGithub();
const context = makeMergeContext('dev', { stack: STACK_INFO });
const labels = await detectMergeBranch(github, context);
assert.deepEqual(Array.from(labels).sort(), ['stacked-pr']);
assert.equal(state.calls, 0);
});
it('not stacked, base ref not dev adds chained-pr', async () => {
const { github } = makeStackGithub({ stack: null });
const context = makeMergeContext('feature-branch');
const labels = await detectMergeBranch(github, context);
assert.deepEqual(Array.from(labels).sort(), ['chained-pr']);
});
it('not stacked, base ref dev adds no labels', async () => {
const { github } = makeStackGithub({ stack: null });
const context = makeMergeContext('dev');
const labels = await detectMergeBranch(github, context);
assert.deepEqual(Array.from(labels).sort(), []);
});
it('a failed stack lookup falls back to not-stacked, so a feature-branch base adds chained-pr', async () => {
const { github, state } = makeStackGithub({ error: new Error('API unavailable') });
const context = makeMergeContext('feature-branch');
const labels = await detectMergeBranch(github, context);
assert.deepEqual(Array.from(labels).sort(), ['chained-pr']);
assert.equal(state.calls, 1);
});
it('base ref matches default branch adds no labels', async () => {
const { github } = makeStackGithub({ stack: null });
const context = makeMergeContext('other', { defaultBranch: 'other' });
const labels = await detectMergeBranch(github, context);
assert.deepEqual(Array.from(labels).sort(), []);
});
it('base ref dev when the default branch is main adds chained-pr', async () => {
const { github } = makeStackGithub({ stack: null });
const context = makeMergeContext('dev', { defaultBranch: 'main' });
const labels = await detectMergeBranch(github, context);
assert.deepEqual(Array.from(labels).sort(), ['chained-pr']);
});
});
// ---------------------------------------------------------------------------
// detectNewPlatforms
// ---------------------------------------------------------------------------
@@ -146,6 +270,125 @@ describe('detectNewComponents', () => {
});
});
// ---------------------------------------------------------------------------
// detectPRTemplateCheckboxes
// ---------------------------------------------------------------------------
const NEW_FEATURE_LINE = '- [x] New feature (non-breaking change which adds functionality)';
const DEV_FEATURE_LINE = '- [x] New developer-facing feature (adds functionality for component developers; no end-user configuration change)';
const DEV_FEATURE_LINE_UNTICKED = '- [ ] New developer-facing feature (adds functionality for component developers; no end-user configuration change)';
function makeBodyContext(body) {
return { payload: { pull_request: { body } } };
}
describe('detectPRTemplateCheckboxes', () => {
it('ticked developer-facing feature checkbox adds new-feature-developer only', async () => {
const labels = await detectPRTemplateCheckboxes(makeBodyContext(DEV_FEATURE_LINE));
assert.ok(labels.has('new-feature-developer'));
assert.ok(!labels.has('new-feature'));
});
it('unticked developer-facing feature checkbox adds no label', async () => {
const labels = await detectPRTemplateCheckboxes(makeBodyContext(DEV_FEATURE_LINE_UNTICKED));
assert.ok(!labels.has('new-feature-developer'));
});
it('ticked new feature checkbox does not add new-feature-developer', async () => {
const labels = await detectPRTemplateCheckboxes(makeBodyContext(NEW_FEATURE_LINE));
assert.ok(labels.has('new-feature'));
assert.ok(!labels.has('new-feature-developer'));
});
});
// ---------------------------------------------------------------------------
// detectRequirements
// ---------------------------------------------------------------------------
describe('detectRequirements', () => {
// PR body without any docs-PR link.
const NO_DOCS_CONTEXT = makeBodyContext('Just a description, no docs link.');
const USER_DOCS_CONTEXT = makeBodyContext('Docs: esphome/esphome.io#1234');
const DEV_DOCS_CONTEXT = makeBodyContext('Docs: esphome/developers.esphome.io#1234');
const DEV_DOCS_URL_CONTEXT = makeBodyContext('Docs: https://github.com/esphome/developers.esphome.io/pull/1234');
// File sets: a normal source change vs. one confined to the exempt core validators.
const SOURCE_FILES = [
{ filename: 'esphome/components/foo/foo.py' },
{ filename: 'tests/components/foo/common.yaml' },
];
const VALIDATOR_FILES = [
{ filename: 'esphome/config_validation.py' },
{ filename: 'tests/unit_tests/test_config_validation.py' },
];
it('new-feature-developer without has-tests adds needs-tests but not needs-docs', async () => {
const labels = await detectRequirements(new Set(['new-feature-developer']), SOURCE_FILES, NO_DOCS_CONTEXT, false);
assert.ok(labels.has('needs-tests'));
assert.ok(!labels.has('needs-docs'));
});
it('new-feature-developer with has-tests does not add needs-tests', async () => {
const labels = await detectRequirements(new Set(['new-feature-developer', 'has-tests']), SOURCE_FILES, NO_DOCS_CONTEXT, false);
assert.ok(!labels.has('needs-tests'));
});
it('new-feature without a docs link still adds needs-docs', async () => {
const labels = await detectRequirements(new Set(['new-feature', 'has-tests']), [], NO_DOCS_CONTEXT, false);
assert.ok(labels.has('needs-docs'));
});
it('new-feature-developer without a developer docs link adds needs-developer-docs', async () => {
const labels = await detectRequirements(new Set(['new-feature-developer', 'has-tests']), SOURCE_FILES, NO_DOCS_CONTEXT, false);
assert.ok(labels.has('needs-developer-docs'));
});
it('a developers.esphome.io shorthand link satisfies needs-developer-docs', async () => {
const labels = await detectRequirements(new Set(['new-feature-developer', 'has-tests']), SOURCE_FILES, DEV_DOCS_CONTEXT, false);
assert.ok(!labels.has('needs-developer-docs'));
});
it('a developers.esphome.io URL link satisfies needs-developer-docs', async () => {
const labels = await detectRequirements(new Set(['new-feature-developer', 'has-tests']), SOURCE_FILES, DEV_DOCS_URL_CONTEXT, false);
assert.ok(!labels.has('needs-developer-docs'));
});
it('a user docs (esphome.io) link does not satisfy needs-developer-docs', async () => {
const labels = await detectRequirements(new Set(['new-feature-developer', 'has-tests']), SOURCE_FILES, USER_DOCS_CONTEXT, false);
assert.ok(labels.has('needs-developer-docs'));
});
it('a developer docs link does not satisfy needs-docs for new-feature', async () => {
const labels = await detectRequirements(new Set(['new-feature', 'has-tests']), [], DEV_DOCS_CONTEXT, false);
assert.ok(labels.has('needs-docs'));
});
it('changes confined to core validator files are exempt from needs-developer-docs', async () => {
const labels = await detectRequirements(new Set(['new-feature-developer', 'has-tests']), VALIDATOR_FILES, NO_DOCS_CONTEXT, false);
assert.ok(!labels.has('needs-developer-docs'));
});
it('validator changes mixed with other source files are not exempt', async () => {
const prFiles = [...VALIDATOR_FILES, { filename: 'esphome/components/foo/foo.py' }];
const labels = await detectRequirements(new Set(['new-feature-developer', 'has-tests']), prFiles, NO_DOCS_CONTEXT, false);
assert.ok(labels.has('needs-developer-docs'));
});
});
// ---------------------------------------------------------------------------
// MANAGED_LABELS
// ---------------------------------------------------------------------------
describe('MANAGED_LABELS', () => {
it('includes new-feature-developer so the workflow syncs it', () => {
assert.ok(MANAGED_LABELS.includes('new-feature-developer'));
});
it('includes needs-developer-docs so the workflow syncs it', () => {
assert.ok(MANAGED_LABELS.includes('needs-developer-docs'));
});
});
// ---------------------------------------------------------------------------
// detectPRSize
// ---------------------------------------------------------------------------
+2 -2
View File
@@ -24,7 +24,7 @@ jobs:
if: github.event.pull_request.state == 'open' && (github.event.action != 'labeled' || github.event.sender.type != 'Bot')
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Generate a token
id: generate-token
@@ -35,7 +35,7 @@ jobs:
# Scope the minted App token to the minimum needed by auto-label-pr/*.js.
permission-contents: read # repos.getContent for CODEOWNERS and file lookups in detectors.js
permission-issues: write # listLabelsOnIssue, addLabels, removeLabel, list/createComment
permission-pull-requests: write # pulls.listFiles, list/create/update/dismissReview
permission-pull-requests: write # pulls.get, pulls.listFiles, list/create/update/dismissReview
- name: Auto Label PR
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+28 -6
View File
@@ -21,15 +21,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
- name: Set up uv
# ``--system`` (below) installs into the setup-python interpreter;
# no venv is created or restored by this workflow.
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: true
# Pull-request-only workflow: a save could never be shared and
@@ -41,10 +41,32 @@ jobs:
version: "0.11.15"
- name: Install apt dependencies
# PR-only workflow, so nothing on dev could seed a shared apt cache
# entry; the cached apt action would save one copy per PR. Plain apt
# with every call bounded: the apt.conf.d timeouts make a dead
# mirror fail over in seconds, and timeout runs under sudo so it can
# kill apt-get itself. Install without update first: image lists are
# fresh, and the index refresh is what a congested mirror makes slow.
timeout-minutes: 15
run: |
sudo apt update
sudo apt-cache show protobuf-compiler
sudo apt install -y protobuf-compiler
sudo tee /etc/apt/apt.conf.d/99ci-acquire-timeouts >/dev/null <<'EOF'
Acquire::Retries "1";
Acquire::http::Timeout "15";
Acquire::https::Timeout "15";
EOF
# Common path: the image's package lists are fresh enough.
if sudo DEBIAN_FRONTEND=noninteractive timeout -k 15 90 \
apt-get install -y protobuf-compiler; then
protoc --version
exit 0
fi
# Rescue path: refresh the lists once with a generous bound; the
# apt config already fails a stalled mirror over quickly.
sudo DEBIAN_FRONTEND=noninteractive timeout -k 10 30 \
dpkg --configure -a || true
sudo timeout -k 15 300 apt-get update
sudo DEBIAN_FRONTEND=noninteractive timeout -k 15 300 \
apt-get install -y protobuf-compiler
protoc --version
- name: Install python dependencies
run: uv pip install --system aioesphomeapi -c requirements.txt -r requirements_dev.txt
+25 -19
View File
@@ -61,21 +61,23 @@ jobs:
tag: ${{ steps.tag.outputs.tag }}
push: ${{ steps.tag.outputs.push }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
- name: Determine tag and whether to push
id: tag
env:
HEAD_REF: ${{ github.head_ref || github.ref_name }}
run: |
# Sanitize the branch name into a valid docker tag: replace invalid
# characters, ensure the first character is valid (tags must start
# with [A-Za-z0-9_]), and cap the length at 128 characters.
branch="${{ github.head_ref || github.ref_name }}"
branch="$HEAD_REF"
tag="${branch//[^a-zA-Z0-9_.-]/-}"
case "$tag" in
[a-zA-Z0-9_]*) ;;
@@ -96,7 +98,7 @@ jobs:
- name: Log in to the GitHub container registry
if: steps.tag.outputs.push == 'true'
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -117,16 +119,22 @@ jobs:
# pushed image) keeps it working for fork PRs, which never push to ghcr.io.
- name: Export image for compile-test
if: matrix.os == 'ubuntu-24.04' && matrix.build_type == 'docker'
run: docker save "ghcr.io/esphome/esphome-amd64:${{ steps.tag.outputs.tag }}" | gzip > compile-test-image.tar.gz
# zstd over gzip: docker save is on the critical path for every
# compile-test job, and zstd -T0 is multithreaded (export 50s -> 9s).
# docker load auto-detects the format; its time is layer extraction,
# not decompression, so it is unchanged. shell: bash adds pipefail so
# a failed docker save cannot upload a truncated artifact.
shell: bash
run: docker save "ghcr.io/esphome/esphome-amd64:${{ steps.tag.outputs.tag }}" | zstd -T0 -3 > compile-test-image.tar.zst
- name: Upload compile-test image artifact
if: matrix.os == 'ubuntu-24.04' && matrix.build_type == 'docker'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
# The tar is already gzipped, so upload it as-is. archive: false skips
# the redundant zip and makes the file name the artifact name (the
# `name` input is ignored in that mode).
path: compile-test-image.tar.gz
# The tar is already compressed, so upload it as-is. archive: false
# skips the redundant zip and makes the file name the artifact name
# (the `name` input is ignored in that mode).
path: compile-test-image.tar.zst
retention-days: 1
archive: false
@@ -145,16 +153,16 @@ jobs:
- "ha-addon"
- "docker"
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
- name: Log in to the GitHub container registry
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -180,8 +188,6 @@ jobs:
contents: read # actions/checkout to load the test configs
strategy:
fail-fast: false
# Cap concurrency so this smoke test doesn't hog all the shared runners.
max-parallel: 2
matrix:
# One entry per distinct toolchain. ESP32 variants (c3/c6/s2/s3/p4)
# share a toolchain bundle, so esp32 is exercised on the base variant
@@ -202,13 +208,13 @@ jobs:
- nrf52
- host
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Download image artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: compile-test-image.tar.gz
name: compile-test-image.tar.zst
- name: Load image
run: docker load --input compile-test-image.tar.gz
run: docker load --input compile-test-image.tar.zst
- name: Compile ${{ matrix.id }}
run: |
docker run --rm \
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code from GitHub
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Run tests
working-directory: .github/scripts/auto-label-pr
@@ -49,7 +49,7 @@ jobs:
- name: Check out code from base repository
if: steps.pr.outputs.skip != 'true'
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Always check out from the base repository (esphome/esphome), never from forks
# Use the PR's target branch to ensure we run trusted code from the main repo
+510 -321
View File
File diff suppressed because it is too large Load Diff
@@ -26,7 +26,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout base branch
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.base.sha }}
sparse-checkout: |
@@ -29,7 +29,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout base branch
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.base.sha }}
+3 -3
View File
@@ -52,11 +52,11 @@ jobs:
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
@@ -84,6 +84,6 @@ jobs:
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
with:
category: "/language:${{matrix.language}}"
+1 -1
View File
@@ -14,4 +14,4 @@ jobs:
permissions:
issues: write # issues.lock on closed issues
pull-requests: write # issues.lock on closed pull requests
uses: esphome/workflows/.github/workflows/lock.yml@025a1e6255610c498ed590403b7e510b69e474df # 2026.4.1
uses: esphome/workflows/.github/workflows/lock.yml@0fdd5e311b7e744069166696072a1a9cbc5fbeb6 # 2026.8.1
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
name: Validate PR title
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
+38
View File
@@ -0,0 +1,38 @@
---
name: Nightly Dev Release
# Works out the dated dev tag and starts the release workflow with it, so that
# the release run is named after the tag it builds. A workflow run name is
# fixed when the run starts and cannot read a file or the current date.
on:
schedule:
- cron: "0 2 * * *"
permissions:
contents: read # actions/checkout to read the version from esphome/const.py
jobs:
trigger:
name: Start release build
if: github.repository == 'esphome/esphome'
runs-on: ubuntu-latest
permissions:
contents: read # actions/checkout to read the version from esphome/const.py
actions: write # gh workflow run starts release.yml
steps:
- name: Check out the repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Start the release workflow
env:
GH_TOKEN: ${{ github.token }}
run: |
VERSION=$(sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p" esphome/const.py)
if [[ -z "$VERSION" ]]; then
echo "::error::Could not read __version__ from esphome/const.py"
exit 1
fi
TAG="${VERSION}$(date --utc '+%Y%m%d')"
echo "Starting release build for ${TAG}"
gh workflow run release.yml --ref "${GITHUB_REF_NAME}" --field tag="${TAG}"
+43 -19
View File
@@ -1,12 +1,23 @@
---
name: Publish Release
# Releases (production and beta) are named after the version they publish.
# Dev builds are named after the dated dev tag, which is passed in by the
# nightly workflow because a run name cannot compute it itself.
run-name: ${{ github.event.inputs.tag || github.event.release.tag_name || format('Manual build ({0})', github.ref_name) }}
on:
workflow_dispatch:
inputs:
tag:
description: >-
Tag to build. Only supported on dev, where the nightly workflow
uses it. Leave empty to build the version from esphome/const.py
with today's date appended.
required: false
default: ""
release:
types: [published]
schedule:
- cron: "0 2 * * *"
permissions:
contents: read # actions/checkout for all jobs; deploy jobs add their own scopes when they need to write
@@ -20,9 +31,11 @@ jobs:
branch_build: ${{ steps.tag.outputs.branch_build }}
deploy_env: ${{ steps.tag.outputs.deploy_env }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Get tag
id: tag
env:
INPUT_TAG: ${{ github.event.inputs.tag }}
# yamllint disable rule:line-length
run: |
if [[ "${{ github.event_name }}" = "release" ]]; then
@@ -34,12 +47,23 @@ jobs:
ENVIRONMENT="production"
fi
else
TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p")
today="$(date --utc '+%Y%m%d')"
TAG="${TAG}${today}"
BRANCH=${GITHUB_REF#refs/heads/}
# The nightly workflow passes the finished tag so that the run name
# matches what is built. Without it, work it out here.
TAG="${INPUT_TAG}"
if [[ -n "$TAG" && "$BRANCH" != "dev" ]]; then
echo "::error::The tag input is only supported on dev. A build from ${BRANCH} has to use the tag worked out here, which carries the branch name, so that it cannot publish over the dev, beta, latest or stable images."
exit 1
fi
if [[ -z "$TAG" ]]; then
TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p")
today="$(date --utc '+%Y%m%d')"
TAG="${TAG}${today}"
if [[ "$BRANCH" != "dev" ]]; then
TAG="${TAG}-${BRANCH}"
fi
fi
if [[ "$BRANCH" != "dev" ]]; then
TAG="${TAG}-${BRANCH}"
BRANCH_BUILD="true"
ENVIRONMENT=""
else
@@ -60,9 +84,9 @@ jobs:
contents: read # actions/checkout to build the sdist/wheel
id-token: write # OIDC token for PyPI Trusted Publishing (pypa/gh-action-pypi-publish)
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.x"
- name: Build
@@ -70,7 +94,7 @@ jobs:
pip3 install build
python3 -m build
- name: Publish
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
with:
skip-existing: true
@@ -92,22 +116,22 @@ jobs:
os: "ubuntu-24.04-arm"
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
- name: Log in to docker hub
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the GitHub container registry
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -168,7 +192,7 @@ jobs:
- ghcr
- dockerhub
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Download digests
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
@@ -178,17 +202,17 @@ jobs:
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
- name: Log in to docker hub
if: matrix.registry == 'dockerhub'
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the GitHub container registry
if: matrix.registry == 'ghcr'
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
+35 -50
View File
@@ -6,61 +6,46 @@ on:
- cron: "30 0 * * *"
workflow_dispatch:
permissions:
issues: write # actions/stale labels, comments on, and closes stale issues
pull-requests: write # actions/stale labels, comments on, and closes stale pull requests
concurrency:
group: lock
# The reusable workflow authenticates as the ESPHome GitHub App, so GITHUB_TOKEN
# needs no permissions at all.
permissions: {}
jobs:
stale:
if: github.repository_owner == 'esphome'
runs-on: ubuntu-latest
steps:
- name: Stale
uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
with:
debug-only: ${{ github.ref != 'refs/heads/dev' }} # Dry-run when not run on dev branch
remove-stale-when-updated: true
operations-per-run: 400
# No GITHUB_TOKEN permissions: the reusable workflow mints an ESPHome
# GitHub App token so the labels, comments and closures come from
# esphome[bot] instead of github-actions[bot].
uses: esphome/workflows/.github/workflows/stale.yml@a1c1485ab46ef41a84a6a9d8abd7fa4b7628fd70 # main
secrets:
ESPHOME_GITHUB_APP_PRIVATE_KEY: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
with:
# Live only on dev: a workflow_dispatch from any other branch is a dry run
dry-run: ${{ github.ref != 'refs/heads/dev' }}
days-before-stale: 90
days-before-close: 7
stale-label: stale
exempt-label: not-stale
ignored-users: esphbot,codecov-commenter
stale-pr-message: >
There hasn't been any activity on this pull request recently. This
pull request has been automatically marked as stale because of that
and will be closed if no further activity occurs within 7 days.
# The 90 day stale policy for PRs
# - PRs
# - No PRs marked as "not-stale"
# - No Issues (see below)
days-before-pr-stale: 90
days-before-pr-close: 7
stale-pr-label: "stale"
exempt-pr-labels: "not-stale"
stale-pr-message: >
There hasn't been any activity on this pull request recently. This
pull request has been automatically marked as stale because of that
and will be closed if no further activity occurs within 7 days.
If you are the author of this PR, please leave a comment if you want
to keep it open. Also, please rebase your PR onto the latest dev
branch to ensure that it's up to date with the latest changes.
If you are the author of this PR, please leave a comment if you want
to keep it open. Also, please rebase your PR onto the latest dev
branch to ensure that it's up to date with the latest changes.
Thank you for your contribution!
stale-issue-message: >
There hasn't been any activity on this issue recently. Due to the
high number of incoming GitHub notifications, we have to clean some
of the old issues, as many of them have already been resolved with
the latest updates.
Thank you for your contribution!
Please make sure to update to the latest ESPHome version and
check if that solves the issue. Let us know if that works for you by
adding a comment 👍
# The 90 day stale policy for Issues
# - Issues
# - No Issues marked as "not-stale"
# - No PRs (see above)
days-before-issue-stale: 90
days-before-issue-close: 7
stale-issue-label: "stale"
exempt-issue-labels: "not-stale"
stale-issue-message: >
There hasn't been any activity on this issue recently. Due to the
high number of incoming GitHub notifications, we have to clean some
of the old issues, as many of them have already been resolved with
the latest updates.
Please make sure to update to the latest ESPHome version and
check if that solves the issue. Let us know if that works for you by
adding a comment 👍
This issue has now been marked as stale and will be closed if no
further activity occurs. Thank you for your contributions.
This issue has now been marked as stale and will be closed if no
further activity occurs. Thank you for your contributions.
+2 -2
View File
@@ -5,7 +5,7 @@ on:
types: [opened, reopened, labeled, unlabeled, synchronize]
permissions:
pull-requests: read # issues.listLabelsOnIssue to detect blocking labels (needs-docs, merge-after-release, chained-pr)
pull-requests: read # issues.listLabelsOnIssue to detect blocking labels (needs-docs, needs-developer-docs, merge-after-release, chained-pr)
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
@@ -20,7 +20,7 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const blockingLabels = ['needs-docs', 'merge-after-release', 'chained-pr'];
const blockingLabels = ['needs-docs', 'needs-developer-docs', 'merge-after-release', 'chained-pr'];
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner,
repo: context.repo.repo,
+17 -17
View File
@@ -28,26 +28,26 @@ jobs:
permission-pull-requests: write # pulls.create / pulls.update to open or refresh the sync PR
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Checkout Home Assistant
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: home-assistant/core
path: lib/home-assistant
- name: Setup Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.14"
- name: Set up uv
# An order of magnitude faster than pip on cold boots, with its
# own wheel cache. ``--system`` (below) installs into the
# setup-python interpreter so subsequent ``pre-commit`` /
# setup-python interpreter so subsequent ``prek`` /
# ``script/run-in-env.py`` steps find the deps without a
# ``uv run`` prefix.
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: true
# Pin uv version so the action does not have to fetch the
@@ -58,19 +58,19 @@ jobs:
- name: Install Home Assistant
run: |
uv pip install --system -e lib/home-assistant
uv pip install --system -r requirements.txt -r requirements_test.txt pre-commit
uv pip install --system -r requirements.txt -r requirements_test.txt
- name: Sync
run: |
python ./script/sync-device_class.py
- name: Apply pre-commit auto-fixes
- name: Apply prek auto-fixes
# First pass: let formatters (ruff, end-of-file-fixer, etc.) modify
# files. pre-commit exits non-zero whenever a hook touches anything,
# files. prek exits non-zero whenever a hook touches anything,
# which would otherwise abort the workflow before the auto-fixes
# can flow into the sync PR.
#
# SKIP:
# PREK_SKIP:
# - no-commit-to-branch is a local guard against committing on
# dev/release/beta; CI runs on dev by definition, and
# peter-evans/create-pull-request creates the branch itself.
@@ -79,25 +79,25 @@ jobs:
# the runtime deps (HA + requirements*.txt); main CI already
# gates pylint on real PRs.
env:
SKIP: pylint,no-commit-to-branch
run: python script/run-in-env.py pre-commit run --all-files || true
PREK_SKIP: pylint,no-commit-to-branch
run: python script/run-in-env.py prek run --all-files || true
- name: Verify pre-commit clean
- name: Verify prek clean
# Second pass: re-run all hooks against the now-fixed tree.
# Auto-fixers exit 0 (nothing to change); any remaining failure
# from a check-only hook (flake8 / yamllint / ci-custom) is a
# real issue and fails the workflow loudly. Same SKIP list as
# real issue and fails the workflow loudly. Same PREK_SKIP list as
# above for the same reasons.
env:
SKIP: pylint,no-commit-to-branch
run: python script/run-in-env.py pre-commit run --all-files
PREK_SKIP: pylint,no-commit-to-branch
run: python script/run-in-env.py prek run --all-files
- name: Commit changes
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
commit-message: "Synchronise Device Classes from Home Assistant"
committer: esphomebot <esphome@openhomefoundation.org>
author: esphomebot <esphome@openhomefoundation.org>
committer: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
author: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
branch: sync/device-classes
delete-branch: true
title: "Synchronise Device Classes from Home Assistant"
+2
View File
@@ -133,6 +133,8 @@ CTestTestfile.cmake
.gcc-flags.json
config/
# Test fixture config/ directories are tracked (the rule above is the dashboard dir)
!tests/component_tests/**/config/
tests/build/
tests/.esphome/
/.temp-clang-tidy.cpp
+1 -1
View File
@@ -11,7 +11,7 @@ ci:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.15.15
rev: v0.16.3
hooks:
# Run the linter.
- id: ruff
+79 -22
View File
@@ -57,6 +57,12 @@ This document provides essential context for AI models interacting with this pro
- Function-local constants: `lower_snake_case`
- Protected/private fields: `lower_snake_case_with_trailing_underscore_`
- Favor descriptive names over abbreviations
- Enumerator names: prefix every value of an `enum class` with the enum name converted to
`UPPER_SNAKE_CASE` (e.g. `UARTFlushResult::UART_FLUSH_RESULT_SUCCESS`). Never use bare
names like `SUCCESS`, `FAILURE`, `OK`, or `FAIL`: platform SDK headers define macros with
these common names (for example the Realtek SDKs used by LibreTiny define
`#define SUCCESS 0` in `basic_types.h`), and the preprocessor replaces the enumerator
before the compiler sees it, breaking the build and clang-tidy on those platforms.
* **Python Idioms:**
* **Assignment expressions (PEP 572):** Prefer the walrus operator (`:=`) wherever it removes a redundant lookup or a throwaway temporary. The most common case in component code is presence-checking a config key and then indexing it separately — fetch once with `.get()` and bind in the condition instead:
@@ -191,11 +197,14 @@ This document provides essential context for AI models interacting with this pro
my_component_ns = cg.esphome_ns.namespace("my_component")
MyComponent = my_component_ns.class_("MyComponent", cg.Component)
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(MyComponent),
cv.Required(CONF_KEY): cv.string,
cv.Optional(CONF_PARAM, default=42): cv.int_,
}).extend(cv.COMPONENT_SCHEMA)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(MyComponent),
cv.Required(CONF_KEY): cv.string,
cv.Optional(CONF_PARAM, default=42): cv.int_,
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
@@ -229,7 +238,12 @@ This document provides essential context for AI models interacting with this pro
- **Sensor:**
```python
from esphome.components import sensor
CONFIG_SCHEMA = sensor.sensor_schema(MySensor).extend(cv.polling_component_schema("60s"))
CONFIG_SCHEMA = sensor.sensor_schema(MySensor).extend(
cv.polling_component_schema("60s")
)
async def to_code(config):
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
@@ -238,7 +252,10 @@ This document provides essential context for AI models interacting with this pro
- **Binary Sensor:**
```python
from esphome.components import binary_sensor
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema().extend({ ... })
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema().extend({...})
async def to_code(config):
var = await binary_sensor.new_binary_sensor(config)
```
@@ -246,7 +263,10 @@ This document provides essential context for AI models interacting with this pro
- **Switch:**
```python
from esphome.components import switch
CONFIG_SCHEMA = switch.switch_schema().extend({ ... })
CONFIG_SCHEMA = switch.switch_schema().extend({...})
async def to_code(config):
var = await switch.new_switch(config)
```
@@ -263,10 +283,13 @@ This document provides essential context for AI models interacting with this pro
```python
from esphome import automation
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(MyComponent),
cv.Optional(CONF_ON_STATE): automation.validate_automation({}),
}).extend(cv.COMPONENT_SCHEMA)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(MyComponent),
cv.Optional(CONF_ON_STATE): automation.validate_automation({}),
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
@@ -316,11 +339,14 @@ This document provides essential context for AI models interacting with this pro
```python
TurnOnTrigger = my_ns.class_("TurnOnTrigger", automation.Trigger.template())
CONFIG_SCHEMA = cv.Schema({
cv.Optional(CONF_ON_TURN_ON): automation.validate_automation(
{cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TurnOnTrigger)}
),
})
CONFIG_SCHEMA = cv.Schema(
{
cv.Optional(CONF_ON_TURN_ON): automation.validate_automation(
{cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TurnOnTrigger)}
),
}
)
async def to_code(config):
for conf in config.get(CONF_ON_TURN_ON, []):
@@ -368,7 +394,10 @@ This document provides essential context for AI models interacting with this pro
```
Register with `@automation.register_condition("my_component.is_active", MyCondition, schema)`.
* **Type Hints:** Type-hint all function signatures, including test functions and config validators (e.g. `def validate_x(config: ConfigType) -> ConfigType:`, `def test_x() -> None:`). Import `ConfigType` from `esphome.types`.
* **Configuration Validation:**
* **Reuse existing validators:** Before writing a custom validator, check for an existing one in `config_validation.py` and compose it in `cv.All(...)` rather than duplicating logic across components. For example, rename a config key with `cv.rename_key(CONF_OLD, CONF_NEW, removed_in="2026.6.0")`, and reject mutually-exclusive keys with `cv.has_at_most_one_key(...)` / `cv.has_exactly_one_key(...)`. See how `api` composes `cv.has_exactly_one_key` + `cv.rename_key`.
* **Common Validators:** `cv.int_`, `cv.float_`, `cv.string`, `cv.boolean`, `cv.int_range(min=0, max=100)`, `cv.positive_int`, `cv.percentage`.
* **Complex Validation:** `cv.All(cv.string, cv.Length(min=1, max=50))`, `cv.Any(cv.int_, cv.string)`.
* **Platform-Specific:** `cv.only_on(["esp32", "esp8266"])`, `esp32.only_on_variant(...)`, `cv.only_on_esp32`, `cv.only_on_esp8266`, `cv.only_on_rp2040`.
@@ -381,6 +410,7 @@ This document provides essential context for AI models interacting with this pro
.extend(i2c.i2c_device_schema(0x48))
.extend(spi.spi_device_schema(cs_pin_required=True))
```
* **Constants:** `esphome/const.py` is frozen — do not add new `CONF_` constants there. Define a component-local constant in the component's own `.py` (as with `CONF_PARAM` above); for a constant shared by multiple components, add it to `esphome/components/const/__init__.py`. CI (`lint_constants_usage`) fails if the same constant is defined in three or more component files. Constants used in core files (i.e. those not under `esphome/components`) may be added to `esphome/const.py` but will require adjustment to the CI validation check.
## 5. Key Files & Entrypoints
@@ -388,7 +418,7 @@ This document provides essential context for AI models interacting with this pro
* **Configuration:**
* `pyproject.toml`: Defines the Python project metadata and dependencies.
* `platformio.ini`: Configures the PlatformIO build environments for different microcontrollers.
* `.pre-commit-config.yaml`: Configures the pre-commit hooks for linting and formatting.
* `.pre-commit-config.yaml`: Configures the lint and format hooks, run by `prek`.
* **CI/CD Pipeline:** Defined in `.github/workflows`.
* **Static Analysis & Development:**
* `esphome/core/defines.h`: A comprehensive header file containing all `#define` directives that can be added by components using `cg.add_define()` in Python. This file is used exclusively for development, static analysis tools, and CI testing - it is not used during runtime compilation. When developing components that add new defines, they must be added to this file to ensure proper IDE support and static analysis coverage. The file includes feature flags, build configurations, and platform-specific defines that help static analyzers understand the complete codebase without needing to compile for specific platforms.
@@ -396,7 +426,7 @@ This document provides essential context for AI models interacting with this pro
## 6. Development & Testing Workflow
* **Local Development Environment:** Use the provided Docker container or create a Python virtual environment and install dependencies from `requirements_dev.txt`.
* **Running Commands:** Use the `script/run-in-env.py` script to execute commands within the project's virtual environment. For example, to run the linter: `python3 script/run-in-env.py pre-commit run`.
* **Running Commands:** Use the `script/run-in-env.py` script to execute commands within the project's virtual environment. For example, to run the linter: `python3 script/run-in-env.py prek run`.
* **Testing:**
* **Python:** Run unit tests with `pytest`.
* **C++:** Use `clang-tidy` for static analysis.
@@ -469,9 +499,9 @@ This document provides essential context for AI models interacting with this pro
1. **Fork & Branch:** Create a new branch based on the `dev` branch (always use `git checkout -b <branch-name> dev` to ensure you're branching from `dev`, not the currently checked out branch).
2. **Make Changes:** Adhere to all coding conventions and patterns.
3. **Test:** Create component tests for all supported platforms and run the full test suite locally.
4. **Lint:** Run `pre-commit` to ensure code is compliant.
4. **Lint:** Run `prek` to ensure code is compliant.
5. **Commit:** Commit your changes. There is no strict format for commit messages.
6. **Pull Request:** Submit a PR against the `dev` branch. The Pull Request title should have a prefix of the component being worked on (e.g., `[display] Fix bug`, `[abc123] Add new component`). Update documentation, examples, and add `CODEOWNERS` entries as needed. Pull requests should always be made using the `.github/PULL_REQUEST_TEMPLATE.md` template - fill out all sections completely without removing any parts of the template.
6. **Pull Request:** Submit a PR against the `dev` branch. The Pull Request title must start with a `[tag]` prefix. For component work, use the component name (e.g., `[display] Fix bug`, `[abc123] Add new component`); for changes to shared/core code that isn't tied to a single component, use `[core]` (e.g., `[core] Add validator`). Update documentation, examples, and add `CODEOWNERS` entries as needed. Pull requests should always be made using the `.github/PULL_REQUEST_TEMPLATE.md` template - fill out all sections completely without removing any parts of the template.
* **Documentation Contributions:**
* Documentation is hosted in the separate `esphome/esphome.io` repository.
@@ -617,6 +647,7 @@ This document provides essential context for AI models interacting with this pro
_component_state = []
_use_feature = None
def enable_feature():
global _use_feature
_use_feature = True
@@ -636,20 +667,24 @@ This document provides essential context for AI models interacting with this pro
DOMAIN = "my_component"
@dataclass
class MyComponentData:
feature_enabled: bool = False
item_count: int = 0
items: list[str] = field(default_factory=list)
def _get_data() -> MyComponentData:
if DOMAIN not in CORE.data:
CORE.data[DOMAIN] = MyComponentData()
return CORE.data[DOMAIN]
def request_feature() -> None:
_get_data().feature_enabled = True
def add_item(item: str) -> None:
_get_data().items.append(item)
```
@@ -704,10 +739,22 @@ This document provides essential context for AI models interacting with this pro
```
* **Deprecation Pattern (Python):**
For a renamed config key, use the shared `cv.rename_key` validator with `removed_in` (and `component` for context) — it warns and auto-migrates:
```python
CONFIG_SCHEMA = cv.All(
cv.rename_key(
CONF_OLD_KEY, CONF_NEW_KEY, removed_in="2026.6.0", component="my_component"
),
cv.Schema({ ... }),
)
```
For other deprecations, warn manually during validation:
```python
# Remove before 2026.6.0
if CONF_OLD_KEY in config:
_LOGGER.warning(f"'{CONF_OLD_KEY}' deprecated, use '{CONF_NEW_KEY}'. Removed in 2026.6.0")
_LOGGER.warning(
f"'{CONF_OLD_KEY}' deprecated, use '{CONF_NEW_KEY}'. Removed in 2026.6.0"
)
config[CONF_NEW_KEY] = config.pop(CONF_OLD_KEY) # Auto-migrate
```
## 9. English Language
@@ -716,3 +763,13 @@ The project uses English for non-code content. When drafting documentation, code
PR descriptions, and similar text, avoid technical jargon. Instead, express concepts in plain English,
using standard technical terms only when required. Ensure the text is readily comprehensible to a wide
audience, including non-native English speakers.
## 10. Code Comments
Code comments on individual lines should be used only where necessary to flag issues that may not be obvious
on a simple reading of the code. Keep them short (e.g. 1 or 2 lines).
Function and method comment blocks may include more detail as required to make
calling contracts clear and document parameter usage, but should still be kept concise.
Avoid redundancy and repetition; comments should never simply restate what the code already says.
+17
View File
@@ -69,12 +69,16 @@ esphome/components/bh1750/* @OttoWinter
esphome/components/bh1900nux/* @B48D81EFCC
esphome/components/binary_sensor/* @esphome/core
esphome/components/bk72xx/* @kuba2k2
esphome/components/bk72xx_ble/* @Bl00d-B0b
esphome/components/bk72xx_ble_tracker/* @Bl00d-B0b
esphome/components/bl0906/* @athom-tech @jesserockz @tarontop
esphome/components/bl0939/* @ziceva
esphome/components/bl0940/* @dan-s-github @tobias-
esphome/components/bl0942/* @dbuezas @dwmw2
esphome/components/ble_client/* @buxtronix @clydebarrow
esphome/components/ble_device_base/* @Bl00d-B0b
esphome/components/ble_nus/* @tomaszduda23
esphome/components/bluetooth_connection/* @bdraco @jesserockz
esphome/components/bluetooth_proxy/* @bdraco @jesserockz
esphome/components/bm8563/* @abmantis
esphome/components/bme280_base/* @esphome/core
@@ -145,6 +149,7 @@ esphome/components/dlms_meter/* @latonita @PolarGoose @SimonFischer04 @Tomer27cz
esphome/components/dps310/* @kbx81
esphome/components/ds1307/* @badbadc0ffee
esphome/components/ds2484/* @mrk-its
esphome/components/ds248x/* @tomwellnitz
esphome/components/dsmr/* @glmnet @PolarGoose
esphome/components/duty_time/* @dudanov
esphome/components/ee895/* @Stock-M
@@ -233,6 +238,7 @@ esphome/components/hlw8032/* @rici4kubicek
esphome/components/hm3301/* @freekode
esphome/components/hmac_md5/* @dwmw2
esphome/components/hmac_sha256/* @dwmw2
esphome/components/hoermann_hcp/* @zweckj
esphome/components/homeassistant/* @esphome/core @OttoWinter
esphome/components/homeassistant/number/* @landonr
esphome/components/homeassistant/switch/* @Links2004
@@ -284,6 +290,7 @@ esphome/components/ld2412/* @Rihan9
esphome/components/ld2420/* @descipher
esphome/components/ld2450/* @hareeshmu
esphome/components/ld24xx/* @kbx81
esphome/components/ld6002b/* @hepter
esphome/components/ledc/* @OttoWinter
esphome/components/libretiny/* @kuba2k2
esphome/components/libretiny_pwm/* @kuba2k2
@@ -291,6 +298,8 @@ esphome/components/light/* @esphome/core
esphome/components/lightwaverf/* @max246
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
esphome/components/lm75b/* @beormund
esphome/components/ln882h_ble/* @Bl00d-B0b
esphome/components/ln882h_ble_tracker/* @Bl00d-B0b
esphome/components/ln882x/* @lamauny
esphome/components/lock/* @esphome/core
esphome/components/logger/* @esphome/core
@@ -341,10 +350,12 @@ esphome/components/mipi_spi/* @clydebarrow
esphome/components/mitsubishi/* @RubyBailey
esphome/components/mitsubishi_cn105/* @crnjan
esphome/components/mixer/speaker/* @kahrendt
esphome/components/mk2pvrouter/* @FredM67
esphome/components/mlx90393/* @functionpointer
esphome/components/mlx90614/* @jesserockz
esphome/components/mmc5603/* @benhoff
esphome/components/mmc5983/* @agoode
esphome/components/modbus_client/* @exciton
esphome/components/modbus_controller/* @martgras
esphome/components/modbus_controller/binary_sensor/* @martgras
esphome/components/modbus_controller/number/* @martgras
@@ -371,6 +382,7 @@ esphome/components/nextion/switch/* @senexcrenshaw
esphome/components/nextion/text_sensor/* @senexcrenshaw
esphome/components/nfc/* @jesserockz @kbx81
esphome/components/noblex/* @AGalfra
esphome/components/noise/* @esphome/core
esphome/components/npi19/* @bakerkj
esphome/components/nrf52/* @tomaszduda23
esphome/components/number/* @esphome/core
@@ -433,6 +445,7 @@ esphome/components/rp2/* @jesserockz
esphome/components/rp2040_ble/* @bdraco
esphome/components/rp2040_pio_led_strip/* @Papa-DMan
esphome/components/rp2040_pwm/* @jesserockz
esphome/components/rp2_ble_tracker/* @bdraco
esphome/components/rpi_dpi_rgb/* @clydebarrow
esphome/components/rtl87xx/* @kuba2k2
esphome/components/rtttl/* @glmnet @ximex
@@ -455,6 +468,7 @@ esphome/components/sen21231/* @shreyaskarnik
esphome/components/sen5x/* @martgras
esphome/components/sen6x/* @martgras @mebner86 @tuct
esphome/components/sendspin/* @kahrendt
esphome/components/sendspin/image/* @kahrendt
esphome/components/sendspin/media_player/* @kahrendt
esphome/components/sendspin/media_source/* @kahrendt
esphome/components/sendspin/sensor/* @kahrendt
@@ -463,6 +477,7 @@ esphome/components/sensirion_common/* @martgras
esphome/components/sensor/* @esphome/core
esphome/components/serial_proxy/* @kbx81
esphome/components/sfa30/* @ghsensdev
esphome/components/sfa40/* @NoQuarrel
esphome/components/sgp40/* @SenexCrenshaw
esphome/components/sgp4x/* @martgras @SenexCrenshaw
esphome/components/sha256/* @esphome/core
@@ -563,6 +578,7 @@ esphome/components/tuya/select/* @bearpawmaxim
esphome/components/tuya/sensor/* @jesserockz
esphome/components/tuya/switch/* @jesserockz
esphome/components/tuya/text_sensor/* @dentra
esphome/components/tuya/water_heater/* @iago-veiga
esphome/components/uart/* @esphome/core
esphome/components/uart/button/* @ssieb
esphome/components/uart/event/* @eoasmxd
@@ -623,6 +639,7 @@ esphome/components/xpt2046/touchscreen/* @nielsnl68 @numo68
esphome/components/xxtea/* @clydebarrow
esphome/components/zephyr/* @tomaszduda23
esphome/components/zephyr_mcumgr/ota/* @tomaszduda23
esphome/components/zephyr_pwm/* @wiomoc
esphome/components/zhlt01/* @cfeenstra1024
esphome/components/zigbee/* @luar123 @tomaszduda23
esphome/components/zio_ultrasonic/* @kahrendt
+1 -1
View File
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = 2026.7.2
PROJECT_NUMBER = 2026.9.0-dev
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
+1 -1
View File
@@ -22,7 +22,7 @@ RUN \
-r /requirements.txt
# Install the ESPHome Device Builder dashboard.
RUN uv pip install --no-cache-dir esphome-device-builder==1.6.9
RUN uv pip install --no-cache-dir esphome-device-builder==1.13.1
RUN \
platformio settings set enable_telemetry No \
+309 -154
View File
@@ -2,39 +2,33 @@
import argparse
from collections.abc import Callable
from contextlib import suppress
from datetime import datetime
import functools
import getpass
import importlib
import logging
import os
from pathlib import Path
import re
import shutil
import subprocess
import sys
import time
from typing import Protocol
import argcomplete
from typing import TYPE_CHECKING, Protocol
# Note: Do not import modules from esphome.components here, as this would
# cause them to be loaded before external components are processed, resulting
# in the built-in version being used instead of the external component one.
from esphome import const
import esphome.codegen as cg
from esphome.config import iter_component_configs, read_config, strip_default_ids
from esphome import const, platform_hooks
from esphome.const import (
ALLOWED_NAME_CHARS,
ARGUMENT_HELP_DEVICE,
BUNDLE_EXTENSION,
CONF_API,
CONF_AUTH,
CONF_BAUD_RATE,
CONF_BROKER,
CONF_DEASSERT_RTS_DTR,
CONF_DISABLED,
CONF_DISCOVER_IP,
CONF_ESPHOME,
CONF_LEVEL,
CONF_LOG,
CONF_LOG_TOPIC,
CONF_LOGGER,
CONF_MDNS,
@@ -48,10 +42,12 @@ from esphome.const import (
CONF_PORT,
CONF_SUBSTITUTIONS,
CONF_TOPIC,
CONF_USERNAME,
CONF_VERSION,
CONF_WEB_SERVER,
CONF_WIFI,
ENV_NOGITIGNORE,
KEY_ESP32,
KEY_VARIANT,
SECRETS_FILES,
Toolchain,
)
@@ -59,6 +55,7 @@ from esphome.core import CORE, EsphomeError, coroutine
from esphome.enum import StrEnum
from esphome.helpers import get_bool_env, indent, is_ip_address
from esphome.log import AnsiFore, color, setup_log
from esphome.stacktrace import LogLineProcessor
from esphome.types import ConfigType
from esphome.upload_targets import PortType, get_port_type
from esphome.util import (
@@ -74,6 +71,9 @@ from esphome.util import (
safe_print,
)
if TYPE_CHECKING:
import threading
# Keep expensive imports (zeroconf, writer, yaml_util, etc.) out of this
# module's top level. Every `esphome` invocation — including fast paths
# like `esphome version` — pays the cost of what's imported here before
@@ -276,8 +276,8 @@ def _unresolved_default_error(purpose: Purpose, defaults: list[str]) -> str:
if purpose == Purpose.LOGGING and not has_api():
return (
"Cannot view logs over the network: no 'api:' component is "
"configured. Network log streaming requires the native API; add "
"an 'api:' component, enable MQTT logging, or view logs over USB."
"configured. Add an 'api:' component, enable MQTT logging, add a "
"'web_server:' component, or view logs over USB."
)
if purpose == Purpose.UPLOADING and not has_ota():
return (
@@ -317,9 +317,12 @@ def choose_upload_log_host(
]
resolved.append(choose_prompt(options, purpose=purpose))
elif device == "OTA":
# Logs can stream over a network transport via the native API
# or the web_server HTTP SSE feed.
network_logging = has_api() or has_web_server_logging()
# ensure IP adresses are used first
if is_ip_address(CORE.address) and (
(purpose == Purpose.LOGGING and has_api())
(purpose == Purpose.LOGGING and network_logging)
or (purpose == Purpose.UPLOADING and has_ota())
):
resolved.extend(_resolve_with_cache(CORE.address, purpose))
@@ -331,7 +334,11 @@ def choose_upload_log_host(
if has_mqtt_logging():
resolved.append("MQTT")
if has_api() and has_non_ip_address() and has_resolvable_address():
if (
network_logging
and has_non_ip_address()
and has_resolvable_address()
):
resolved.extend(_ota_hostnames_for_default(purpose))
elif purpose == Purpose.UPLOADING:
@@ -393,7 +400,7 @@ def choose_upload_log_host(
mqtt_config = CORE.config[CONF_MQTT]
options.append((f"MQTT ({mqtt_config[CONF_BROKER]})", "MQTT"))
if has_api():
if has_api() or has_web_server_logging():
add_ota_options()
elif purpose == Purpose.UPLOADING and has_ota():
@@ -486,10 +493,23 @@ def has_web_server_ota() -> bool:
)
def has_web_server_logging() -> bool:
"""Check if logs can be streamed over the web_server HTTP SSE endpoint.
The ``web_server`` component exposes a ``/events`` Server-Sent Events
stream that carries ``event: log`` frames. This requires version 2+ (the
v1 UI has no ``/events`` endpoint) and the ``log`` option enabled (default).
"""
web_conf = CORE.config.get(CONF_WEB_SERVER)
if web_conf is None:
return False
if web_conf.get(CONF_VERSION, 2) == 1:
return False
return web_conf.get(CONF_LOG, True)
def has_mqtt_ip_lookup() -> bool:
"""Check if MQTT is available and IP lookup is supported."""
from esphome.components.mqtt import CONF_DISCOVER_IP
if CONF_MQTT not in CORE.config:
return False
# Default Enabled
@@ -550,11 +570,48 @@ def has_name_add_mac_suffix() -> bool:
def mqtt_get_ip(
config: ConfigType, username: str, password: str, client_id: str
config: ConfigType,
username: str,
password: str,
client_id: str,
stop_event: "threading.Event | None" = None,
) -> list[str]:
from esphome import mqtt
return mqtt.get_esphome_device_ip(config, username, password, client_id)
return mqtt.get_esphome_device_ip(
config, username, password, client_id, stop_event=stop_event
)
def _add_network_device(device: str, network_devices: list[str]) -> None:
"""Append a device to the list, expanding it through ``CORE.address_cache``.
If the hostname is already in the address cache (e.g. populated by mDNS
discovery), substitute the cached IPs so aioesphomeapi doesn't open its
own Zeroconf to re-resolve it. Duplicates are dropped.
"""
if CORE.address_cache and (cached := CORE.address_cache.get_addresses(device)):
network_devices.extend(addr for addr in cached if addr not in network_devices)
elif device not in network_devices:
network_devices.append(device)
def _split_network_devices(devices: list[str]) -> tuple[list[str], bool]:
"""Split the device list into direct addresses and an MQTT-lookup flag.
Direct addresses are expanded through ``CORE.address_cache`` and deduped
the same way ``_resolve_network_devices`` does; MQTT/MQTTIP magic strings
are not resolved, only reported via the returned bool so the caller can
defer the broker lookup.
"""
network_devices: list[str] = []
has_mqtt_lookup = False
for device in devices:
if get_port_type(device) in _MQTT_PORT_TYPES:
has_mqtt_lookup = True
else:
_add_network_device(device, network_devices)
return network_devices, has_mqtt_lookup
def _resolve_network_devices(
@@ -587,41 +644,47 @@ def _resolve_network_devices(
if port_type in _MQTT_PORT_TYPES:
# Only resolve MQTT once, even if multiple MQTT entries
if not mqtt_resolved:
try:
mqtt_ips = mqtt_get_ip(
config, args.username, args.password, args.client_id
)
# pylint can't infer mqtt_get_ip's return through its
# lazy ``from esphome import mqtt`` import, so it flags
# the genexpr below.
network_devices.extend(
addr
for addr in mqtt_ips # pylint: disable=not-an-iterable
if addr not in network_devices
)
except EsphomeError as err:
_LOGGER.warning(
"MQTT IP discovery failed (%s), will try other devices if available",
err,
)
mqtt_ips = _mqtt_get_ip_or_warn(
config, args.username, args.password, args.client_id
)
network_devices.extend(
addr for addr in mqtt_ips if addr not in network_devices
)
mqtt_resolved = True
continue
# If the hostname is already in the address cache (e.g. populated by
# mDNS discovery), substitute the cached IPs so aioesphomeapi doesn't
# open its own Zeroconf to re-resolve it.
if CORE.address_cache and (cached := CORE.address_cache.get_addresses(device)):
network_devices.extend(
addr for addr in cached if addr not in network_devices
)
elif device not in network_devices:
# Regular network address or IP - add if not already present
network_devices.append(device)
_add_network_device(device, network_devices)
return network_devices
def _mqtt_get_ip_or_warn(
config: ConfigType,
username: str,
password: str,
client_id: str,
stop_event: "threading.Event | None" = None,
) -> list[str]:
"""Look up the device IP via MQTT, returning [] with a warning on failure.
This owns the failure policy for MQTT IP discovery on paths that have
other addresses to fall back on: a broker problem must not abort the
operation. Also used as the deferred resolver handed to ``run_logs``,
where it runs in a worker thread.
"""
try:
return mqtt_get_ip(config, username, password, client_id, stop_event=stop_event)
except EsphomeError as err:
_LOGGER.warning(
"MQTT IP discovery failed (%s), will try other devices if available",
err,
)
return []
def run_miniterm(config: ConfigType, port: str, args) -> int:
from datetime import datetime
from aioesphomeapi import LogParser
import serial
@@ -634,18 +697,9 @@ def run_miniterm(config: ConfigType, port: str, args) -> int:
return 1
_LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate)
process_stacktrace = None
try:
module = importlib.import_module("esphome.components." + CORE.target_platform)
process_stacktrace = module.process_stacktrace
except (AttributeError, ImportError):
_LOGGER.info(
'Stacktrace analysis is unavailable: no compatible analyzer found for target platform "%s".',
CORE.target_platform,
)
backtrace_state = False
# Decoder resolution, crash isolation, and disable-after-failure
# all live in LogLineProcessor, shared with the API log path.
processor = LogLineProcessor(config, CORE.target_platform)
ser = serial.Serial()
ser.baudrate = baud_rate
ser.port = port
@@ -685,11 +739,7 @@ def run_miniterm(config: ConfigType, port: str, args) -> int:
"utf8", "backslashreplace"
)
safe_print(parser.parse_line(line, time_str))
if process_stacktrace is not None:
backtrace_state = process_stacktrace(
config, line, backtrace_state
)
processor.process_line(line)
except serial.SerialException:
_LOGGER.error("Serial port closed!")
return 0
@@ -704,15 +754,19 @@ def run_miniterm(config: ConfigType, port: str, args) -> int:
def _wrap_to_code(name, comp, yaml_util):
import esphome.codegen as cg
coro = coroutine(comp.to_code)
@functools.wraps(comp.to_code)
async def wrapped(conf):
cg.add(cg.LineComment(f"{name}:"))
if comp.config_schema is not None:
conf_str = yaml_util.dump(conf)
# sort_keys: voluptuous fills defaults in set order, so an
# unsorted dump would churn main.cpp and relink every run
conf_str = yaml_util.dump(conf, sort_keys=True)
conf_str = conf_str.replace("//", "")
# remove tailing \ to avoid multi-line comment warning
# remove trailing \ to avoid multi-line comment warning
conf_str = conf_str.replace("\\\n", "\n")
cg.add(cg.LineComment(indent(conf_str)))
await coro(conf)
@@ -739,6 +793,7 @@ def write_cpp(config: ConfigType) -> int:
def generate_cpp_contents(config: ConfigType) -> None:
from esphome import yaml_util
from esphome.config import iter_component_configs
_LOGGER.info("Generating C++ source...")
@@ -802,7 +857,20 @@ def compile_program(args: ArgsProtocol, config: ConfigType) -> int:
toolchain.create_factory_bin()
toolchain.create_ota_bin()
toolchain.create_elf_copy()
toolchain.get_idedata()
from esphome.build_helpers.idedata import IDEDATA_BEST_EFFORT_ERRORS
try:
if toolchain.get_idedata() is None:
_LOGGER.warning("No idedata was generated for this build")
except IDEDATA_BEST_EFFORT_ERRORS as err:
# The firmware already built; an idedata failure must not fail
# a successful build.
_LOGGER.warning(
"Could not generate idedata: %s (IDE, clang-tidy, and "
"memory-analysis data will be unavailable for this build)",
err,
)
_LOGGER.debug("Idedata failure detail", exc_info=True)
else:
from esphome.platformio import toolchain
@@ -931,9 +999,10 @@ def upload_using_esptool(
mcu = "esp8266"
if CORE.is_esp32:
from esphome.components.esp32 import get_esp32_variant
mcu = get_esp32_variant().lower()
# Same lookup as esp32.get_esp32_variant(), read directly so the
# serial upload path does not import the esp32 package; both the
# validator and the warm-cache apply_to_core populate this key.
mcu = CORE.data[KEY_ESP32][KEY_VARIANT].lower()
line_callbacks: list[Callable[[str], str | None]] = []
if (
@@ -987,6 +1056,8 @@ def upload_using_esptool(
def upload_using_platformio(config: ConfigType, port: str) -> int:
import shutil
from esphome.platformio import toolchain
# RP2040 platform-raspberrypi build recipe expects firmware.bin.signed for
@@ -1024,6 +1095,8 @@ def upload_using_picotool(config: ConfigType) -> int:
the mass storage copy approach that causes "disk not ejected properly"
warnings on macOS.
"""
import subprocess
from esphome.platformio import toolchain
idedata = toolchain.get_idedata(config)
@@ -1130,6 +1203,8 @@ def check_permissions(port: str):
"the USB cable can be used for data and is not a power-only cable."
)
if not (os.access(port, os.R_OK | os.W_OK)):
import getpass
raise EsphomeError(
"You do not have read or write permission on the selected serial port. "
"To resolve this issue, you can add your user to the dialout group "
@@ -1142,12 +1217,11 @@ def upload_program(
config: ConfigType, args: ArgsProtocol, devices: list[str]
) -> tuple[int, str | None]:
host = devices[0]
try:
module = importlib.import_module("esphome.components." + CORE.target_platform)
if module.upload_program(config, args, host):
return 0, host
except AttributeError:
pass
platform_upload = platform_hooks.get_platform_hook(
CORE.target_platform, "upload_program"
)
if platform_upload is not None and platform_upload(config, args, host):
return 0, host
port_type = get_port_type(host)
@@ -1298,25 +1372,23 @@ def _upload_via_native_api(
def _upload_via_web_server(
config: ConfigType, network_devices: list[str], binary: Path
) -> tuple[int, str | None]:
web_conf = config.get(CONF_WEB_SERVER)
if not web_conf:
raise EsphomeError(
f"Cannot upload via web_server OTA: the {CONF_WEB_SERVER} component "
f"is not configured."
)
remote_port = int(web_conf[CONF_PORT])
auth = web_conf.get(CONF_AUTH) or {}
username = auth.get(CONF_USERNAME)
password = auth.get(CONF_PASSWORD)
from esphome import web_server_ota
from esphome.web_server_helpers import get_web_server_connection
remote_port, username, password = get_web_server_connection(config)
return web_server_ota.run_ota(
network_devices, remote_port, username, password, binary
)
def _show_logs_via_web_server(config: ConfigType, network_devices: list[str]) -> int:
from esphome import web_server_logs
from esphome.web_server_helpers import get_web_server_connection
port, username, password = get_web_server_connection(config)
return web_server_logs.run_logs(network_devices, port, username, password)
# Layout of esp_partition_info_t on flash. Each entry is 32 bytes, leading with a
# 16-bit little-endian magic. ESP-IDF defines ESP_PARTITION_MAGIC = 0x50AA (stored as
# bytes 0xAA, 0x50) for partition entries and ESP_PARTITION_MAGIC_MD5 = 0xEBEB for the
@@ -1407,12 +1479,11 @@ def _should_subscribe_states(args: ArgsProtocol) -> bool:
def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int | None:
try:
module = importlib.import_module("esphome.components." + CORE.target_platform)
if module.show_logs(config, args, devices):
return 0
except AttributeError:
pass
platform_show_logs = platform_hooks.get_platform_hook(
CORE.target_platform, "show_logs"
)
if platform_show_logs is not None and platform_show_logs(config, args, devices):
return 0
if "logger" not in config:
raise EsphomeError("Logger is not configured!")
@@ -1426,17 +1497,37 @@ def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int
return run_miniterm(config, port, args)
# Check if we should use API for logging
# Resolve MQTT magic strings to actual IP addresses
if has_api() and (
network_devices := _resolve_network_devices(devices, config, args)
):
from esphome.components.api.client import run_logs
if has_api():
network_devices, has_mqtt_lookup = _split_network_devices(devices)
mqtt_resolver = None
if has_mqtt_lookup:
if network_devices:
# Addresses are already known, so don't block startup on the
# MQTT broker lookup; hand it to run_logs as a deferred
# resolver that runs in the background and feeds discovered
# addresses into the running log client, keeping MQTT as a
# fallback for when the known addresses are stale (e.g. DHCP
# reassigned the IP).
mqtt_resolver = functools.partial(
_mqtt_get_ip_or_warn,
config,
args.username,
args.password,
args.client_id,
)
else:
# The MQTT lookup is the only way to find the device; resolve
# it up front since the client needs an address to start with.
network_devices = _resolve_network_devices(devices, config, args)
if network_devices:
from esphome.api_client import run_logs
return run_logs(
config,
network_devices,
subscribe_states=_should_subscribe_states(args),
)
return run_logs(
config,
network_devices,
subscribe_states=_should_subscribe_states(args),
mqtt_resolver=mqtt_resolver,
)
if port_type in (PortType.NETWORK, PortType.MQTT) and has_mqtt_logging():
from esphome import mqtt
@@ -1445,6 +1536,13 @@ def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int
config, args.topic, args.username, args.password, args.client_id
)
# Fall back to the web_server HTTP SSE log stream for devices that have
# web_server: but no api: (the logging counterpart to web_server OTA).
if has_web_server_logging() and (
network_devices := _resolve_network_devices(devices, config, args)
):
return _show_logs_via_web_server(config, network_devices)
raise EsphomeError("No remote or local logging method configured (api/mqtt/logger)")
@@ -1464,6 +1562,7 @@ def command_wizard(args: ArgsProtocol) -> int | None:
def command_config(args: ArgsProtocol, config: ConfigType) -> int | None:
from esphome import yaml_util
from esphome.config import strip_default_ids
if getattr(args, "no_defaults", False):
user_config = getattr(config, "user_config", None)
@@ -1571,20 +1670,26 @@ def command_compile(args: ArgsProtocol, config: ConfigType) -> int | None:
if exit_code != 0:
return exit_code
if CORE.is_host:
if CORE.using_toolchain_esp_idf:
from esphome.espidf import toolchain
program_path = str(toolchain.get_elf_path())
else:
from esphome.platformio.toolchain import get_idedata
program_path = str(get_idedata(config).firmware_elf_path)
_LOGGER.info("Successfully compiled program to path '%s'", program_path)
_LOGGER.info(
"Successfully compiled program to path '%s'", _host_program_path(config)
)
else:
_LOGGER.info("Successfully compiled program.")
return 0
def _host_program_path(config: ConfigType) -> str:
"""Return the compiled host ELF path."""
if CORE.using_toolchain_esp_idf:
from esphome.espidf import toolchain
return str(toolchain.get_elf_path())
from esphome.platformio.toolchain import get_idedata
# Memoized by compile_program's own call; this is a dict lookup
return str(get_idedata(config).firmware_elf_path)
def command_upload(args: ArgsProtocol, config: ConfigType) -> int | None:
# Get devices, resolving special identifiers like OTA
devices = choose_upload_log_host(
@@ -1629,14 +1734,7 @@ def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
return exit_code
_LOGGER.info("Successfully compiled program.")
if CORE.is_host:
if CORE.using_toolchain_esp_idf:
from esphome.espidf import toolchain
program_path = str(toolchain.get_elf_path())
else:
from esphome.platformio.toolchain import get_idedata
program_path = str(get_idedata(config).firmware_elf_path)
program_path = _host_program_path(config)
_LOGGER.info("Running program from path '%s'", program_path)
return run_external_process(program_path)
@@ -1713,7 +1811,7 @@ def command_clean(args: ArgsProtocol, config: ConfigType) -> int | None:
def command_bundle(args: ArgsProtocol, config: ConfigType) -> int | None:
from esphome.bundle import BUNDLE_EXTENSION, ConfigBundleCreator
from esphome.bundle import ConfigBundleCreator
creator = ConfigBundleCreator(config)
@@ -1948,7 +2046,7 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
new_name = args.name
for c in new_name:
if c not in ALLOWED_NAME_CHARS:
print(
safe_print(
color(
AnsiFore.BOLD_RED,
f"'{c}' is an invalid character for names. Valid characters are: "
@@ -1961,7 +2059,7 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
yaml = yaml_util.load_yaml(CORE.config_path)
if CONF_ESPHOME not in yaml or CONF_NAME not in yaml[CONF_ESPHOME]:
print(
safe_print(
color(
AnsiFore.BOLD_RED, "Complex YAML files cannot be automatically renamed."
)
@@ -2008,7 +2106,9 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
)
> 1
):
print(color(AnsiFore.BOLD_RED, "Too many matches in YAML to safely rename"))
safe_print(
color(AnsiFore.BOLD_RED, "Too many matches in YAML to safely rename")
)
return 1
new_raw = re.sub(
@@ -2026,7 +2126,7 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
# ``kitchen``; running ``esphome rename weird-file.yaml kitchen``
# would otherwise just re-flash the same hostname).
if new_name == old_name:
print(
safe_print(
color(
AnsiFore.BOLD_RED,
f"'{new_name}' is already the device's name.",
@@ -2036,7 +2136,7 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
new_path: Path = CORE.config_dir / (new_name + ".yaml")
if new_path.resolve() == CORE.config_path.resolve():
print(
safe_print(
color(
AnsiFore.BOLD_RED,
f"'{new_name}' is already the device's name.",
@@ -2044,7 +2144,7 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
)
return 1
if new_path.exists():
print(
safe_print(
color(
AnsiFore.BOLD_RED,
f"Cannot rename: {new_path} already exists. "
@@ -2052,7 +2152,7 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
)
)
return 1
print(
safe_print(
f"Updating {color(AnsiFore.CYAN, str(CORE.config_path))} to {color(AnsiFore.CYAN, str(new_path))}"
)
print()
@@ -2061,7 +2161,7 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
rc = run_external_process(*ESPHOME_COMMAND, "config", str(new_path))
if rc != 0:
print(color(AnsiFore.BOLD_RED, "Rename failed. Reverting changes."))
safe_print(color(AnsiFore.BOLD_RED, "Rename failed. Reverting changes."))
new_path.unlink()
return 1
@@ -2087,7 +2187,7 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
if CORE.config_path != new_path:
CORE.config_path.unlink()
print(color(AnsiFore.BOLD_GREEN, "SUCCESS"))
safe_print(color(AnsiFore.BOLD_GREEN, "SUCCESS"))
print()
return 0
@@ -2498,7 +2598,12 @@ def parse_args(argv):
# a deprecation warning).
arguments = argv[1:]
argcomplete.autocomplete(parser)
# argcomplete only does anything when the shell-completion machinery
# invokes us with _ARGCOMPLETE set; skip the import otherwise.
if "_ARGCOMPLETE" in os.environ:
import argcomplete
argcomplete.autocomplete(parser)
if len(arguments) > 0 and arguments[0] in SIMPLE_CONFIG_ACTIONS:
args, unknown_args = parser.parse_known_args(arguments)
@@ -2509,6 +2614,49 @@ def parse_args(argv):
return parser.parse_args(arguments)
def _warn_if_source_tree_mismatch() -> None:
"""Warn when the checkout the user is standing in is not the one being run.
An editable install records one absolute path, so a venv shared between git
worktrees (or reused after a checkout is copied or renamed) keeps importing
the tree it was installed from. Every command then silently runs, and
compiles, sources the user is not looking at. Only fires inside a checkout,
so ordinary installs never see it.
"""
try:
cwd = Path.cwd()
except OSError:
return # working directory is gone; a diagnostic must not break startup
for candidate in (cwd, *cwd.parents):
if (candidate / "esphome" / "__main__.py").is_file():
standing_in = candidate.resolve()
break
else:
return # not inside a checkout; nothing to compare against
running = Path(__file__).resolve().parent.parent
# Both sides are resolved, so on a case-sensitive filesystem this matches
# plain equality. samefile() compares device and inode, which additionally
# covers a case-insensitive filesystem (macOS) reaching one directory by
# differently cased paths. Falls back to equality if either path is gone.
try:
same = standing_in.samefile(running)
except OSError:
same = standing_in == running
if same:
return
_LOGGER.warning(
"Running ESPHome from a different checkout than the one you are in:\n"
" running from: %s\n"
" you are in: %s\n"
"The installed esphome resolves to the first, so its sources are used.\n"
"Run 'python -m esphome' from the second to use that one instead.",
running,
standing_in,
)
def run_esphome(argv):
from esphome.address_cache import AddressCache
@@ -2527,6 +2675,7 @@ def run_esphome(argv):
args.log_level = "CRITICAL"
setup_log(log_level=args.log_level)
_warn_if_source_tree_mismatch()
if args.command in PRE_CONFIG_ACTIONS:
try:
@@ -2558,10 +2707,11 @@ def run_esphome(argv):
return 0
# Bundle support: if the configuration is a .esphomebundle, extract it
# and rewrite conf_path to the extracted YAML config.
from esphome.bundle import is_bundle_path, prepare_bundle_for_compile
# and rewrite conf_path to the extracted YAML config. The suffix check
# stays inline so the ordinary run never imports esphome.bundle.
if conf_path.name.lower().endswith(BUNDLE_EXTENSION):
from esphome.bundle import prepare_bundle_for_compile
if is_bundle_path(conf_path):
_LOGGER.info("Extracting config bundle %s...", conf_path)
conf_path = prepare_bundle_for_compile(conf_path)
# Update the argument so downstream code sees the extracted path
@@ -2583,10 +2733,14 @@ def run_esphome(argv):
# Skipped when -s overrides are passed, since the cache was written
# against the previous substitution set.
config: ConfigType | None = None
cache_eligible = (
cache_write_eligible = (
args.command in ("upload", "logs") and not command_line_substitutions
)
if cache_eligible:
# An explicit --toolchain must re-run the per-platform validators, so
# gate only the cache read; the refresh below saves the result unless
# the sidecar records a different toolchain.
cache_read_eligible = cache_write_eligible and args.toolchain is None
if cache_read_eligible:
from esphome.compiled_config import load_compiled_config
config = load_compiled_config(conf_path)
@@ -2596,31 +2750,32 @@ def run_esphome(argv):
conf_path.name,
)
if config is None:
cache_missed = config is None
if cache_missed:
from esphome.config import read_config
config = read_config(
command_line_substitutions,
skip_external_update=skip_external,
# Snapshot only needed by `esphome config --no-defaults`.
snapshot_user_config=getattr(args, "no_defaults", False),
)
# Refresh the cache so the next upload/logs hits the fast path
# instead of re-running read_config. Skip when the storage
# sidecar is absent (no compile has run): the cache would
# never be loaded back, so writing secrets to disk is wasted.
if cache_eligible and config is not None:
from esphome.compiled_config import save_compiled_config
from esphome.storage_json import ext_storage_path
if ext_storage_path(conf_path.name).exists():
save_compiled_config(config)
if config is None:
return 2
if config is None:
return 2
CORE.config = config
# Fallback for platforms whose validators didn't set the toolchain
# (only the esp32 component reads esp32.framework.toolchain). All
# other platforms only support PlatformIO today.
# The cache fast path skips validation, and legacy sidecars lack the
# toolchain field. Must run before the cache refresh below.
if CORE.toolchain is None:
CORE.toolchain = Toolchain.PLATFORMIO
# Refresh the cache so the next upload/logs hits the fast path
# instead of re-running read_config.
if cache_write_eligible and cache_missed:
from esphome.compiled_config import save_compiled_config_and_sidecar
save_compiled_config_and_sidecar(config)
if args.command not in POST_CONFIG_ACTIONS:
safe_print(f"Unknown command {args.command}")
return 1
+8 -27
View File
@@ -20,6 +20,7 @@ from . import (
RAM_SECTIONS,
MemoryAnalyzer,
)
from .toolchain import find_elf_path, find_idedata_path, idedata_candidates
if TYPE_CHECKING:
from . import ComponentMemory
@@ -759,45 +760,25 @@ def main():
print(f"Error: {build_path} is not a directory", file=sys.stderr)
sys.exit(1)
# Find firmware.elf
elf_file = None
for elf_candidate in [
build_path / "firmware.elf",
build_path / ".pioenvs" / build_path.name / "firmware.elf",
]:
if elf_candidate.exists():
elf_file = str(elf_candidate)
break
if not elf_file:
print(f"Error: firmware.elf not found in {build_dir}", file=sys.stderr)
elf_path = find_elf_path(build_path)
if not elf_path:
print(f"Error: no firmware ELF found in {build_dir}", file=sys.stderr)
sys.exit(1)
# Find idedata.json - check current directory first, then home
device_name = build_path.name
idedata_candidates = [
Path.cwd() / ".esphome" / "idedata" / f"{device_name}.json",
Path.home() / ".esphome" / "idedata" / f"{device_name}.json",
]
elf_file = str(elf_path)
idedata = None
for idedata_path in idedata_candidates:
if not idedata_path.exists():
continue
if idedata_path := find_idedata_path(build_path):
try:
with idedata_path.open(encoding="utf-8") as f:
raw_data = json.load(f)
idedata = IDEData(raw_data)
print(f"Loaded idedata from: {idedata_path}", file=sys.stderr)
break
except (json.JSONDecodeError, OSError) as e:
print(f"Warning: Failed to load idedata: {e}", file=sys.stderr)
if not idedata:
print(
f"Warning: idedata not found (searched {idedata_candidates[0]} and {idedata_candidates[1]})",
file=sys.stderr,
)
searched = "\n ".join(str(p) for p in idedata_candidates(build_path))
print(f"Warning: idedata not found, searched:\n {searched}", file=sys.stderr)
analyzer = MemoryAnalyzerCLI(elf_file, idedata=idedata)
analyzer.analyze()
+72
View File
@@ -23,6 +23,78 @@ TOOLCHAIN_PREFIXES = [
]
def find_elf_path(build_path: Path) -> Path | None:
"""Locate the firmware ELF inside an ESPHome build directory.
The layout depends on the toolchain that produced the build, so try each
known one in turn.
Args:
build_path: Path to an ESPHome build directory
Returns:
Path to the ELF file, or None if no known layout matches
"""
name = build_path.name
for candidate in (
# Native ESP-IDF: idf.py writes build/<name>.elf, which ESPHome copies
# to build/firmware.elf (see espidf.toolchain.create_elf_copy)
build_path / "build" / "firmware.elf",
# PlatformIO
build_path / "firmware.elf",
build_path / ".pioenvs" / name / "firmware.elf",
# LibreTiny uses raw_firmware.elf
build_path / "raw_firmware.elf",
build_path / ".pioenvs" / name / "raw_firmware.elf",
# Zephyr (nRF52); the SDK nests the artifacts one level deeper from 2.9.2
build_path / ".pioenvs" / name / "zephyr" / "zephyr" / "zephyr.elf",
build_path / ".pioenvs" / name / "zephyr" / "zephyr.elf",
):
if candidate.is_file():
return candidate
return None
def idedata_candidates(build_path: Path) -> list[Path]:
"""Return the idedata locations searched for a build directory, in order.
Exposed so a caller reporting "not found" can name the paths it tried
without keeping its own copy of the list.
Args:
build_path: Path to an ESPHome build directory
Returns:
The candidate idedata JSON paths, most specific first
"""
name = build_path.name
return [
# In .pioenvs for test builds
build_path / ".pioenvs" / name / "idedata.json",
# Both toolchains cache it in the data dir, which holds this build dir:
# <data_dir>/idedata/<name>.json next to <data_dir>/build/<name>
build_path.parent.parent / "idedata" / f"{name}.json",
# Regular builds, invoked from the config dir or from anywhere
Path.cwd() / ".esphome" / "idedata" / f"{name}.json",
Path.home() / ".esphome" / "idedata" / f"{name}.json",
]
def find_idedata_path(build_path: Path) -> Path | None:
"""Locate the idedata JSON belonging to an ESPHome build directory.
Args:
build_path: Path to an ESPHome build directory
Returns:
Path to the idedata JSON, or None if it was not found
"""
for candidate in idedata_candidates(build_path):
if candidate.is_file():
return candidate
return None
def _find_in_platformio_packages(tool_name: str) -> str | None:
"""Search for a tool in PlatformIO package directories.
+198
View File
@@ -0,0 +1,198 @@
from __future__ import annotations
import asyncio
from contextlib import suppress
import logging
import threading
from typing import TYPE_CHECKING, Any
import warnings
# Suppress protobuf version warnings
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore", category=UserWarning, message=".*Protobuf gencode version.*"
)
from aioesphomeapi import APIClient, parse_log_message
from aioesphomeapi.log_runner import async_run
from esphome.const import CONF_ENCRYPTION, CONF_KEY, CONF_PORT, __version__
from esphome.core import CORE
from esphome.stacktrace import LogLineProcessor
from esphome.util import safe_print
if TYPE_CHECKING:
from collections.abc import Callable
from aioesphomeapi.api_pb2 import (
SubscribeLogsResponse, # pylint: disable=no-name-in-module
)
_LOGGER = logging.getLogger(__name__)
async def async_run_logs(
config: dict[str, Any],
addresses: list[str],
subscribe_states: bool = True,
mqtt_resolver: Callable[[threading.Event], list[str]] | None = None,
) -> None:
"""Run the logs command in the event loop.
If ``mqtt_resolver`` is given, it is called in a worker thread (paho-mqtt
has no asyncio support on Windows) concurrently with the connection
attempts to ``addresses``, and any addresses it discovers are fed into
the running client. It owns its own failure handling (returning [] when
discovery fails) and must honor the ``threading.Event`` it is passed so
teardown is not delayed by the lookup's wait window; the initial broker
connect itself is only bounded by the socket timeout.
"""
from datetime import datetime
conf = config["api"]
name = config["esphome"]["name"]
port: int = int(conf[CONF_PORT])
noise_psk: str | None = None
if (encryption := conf.get(CONF_ENCRYPTION)) and (key := encryption.get(CONF_KEY)):
noise_psk = key
_LOGGER.info(
"Starting log output from %s using esphome API", " or ".join(addresses)
)
cli = APIClient(
addresses[0], # Primary address for compatibility
port,
"", # Password auth removed in 2026.1.0
client_info=f"ESPHome Logs {__version__}",
noise_psk=noise_psk,
addresses=addresses, # Pass all addresses for automatic retry
provide_time=False,
)
# Decoder resolution policy lives in LogLineProcessor.
processor = LogLineProcessor(config, CORE.target_platform)
mqtt_task: asyncio.Task[None] | None = None
mqtt_stop_event = threading.Event()
def _cancel_mqtt_discovery() -> None:
"""Stop the broker lookup once a connection has been established.
Its answer is only useful while still disconnected: after that it
either duplicates the connected address or arrives too late to
matter, so don't keep an idle broker session open for it.
"""
mqtt_stop_event.set()
if mqtt_task is not None and not mqtt_task.done():
mqtt_task.cancel()
async def _resolve_mqtt_addresses() -> None:
"""Discover the device address via the MQTT broker in the background."""
try:
mqtt_ips = await asyncio.to_thread(mqtt_resolver, mqtt_stop_event)
if not mqtt_ips:
_LOGGER.debug(
"MQTT discovery %s",
"aborted" if mqtt_stop_event.is_set() else "found no addresses",
)
return
if cli.add_addresses(mqtt_ips):
_LOGGER.info("Discovered address(es) via MQTT: %s", ", ".join(mqtt_ips))
else:
_LOGGER.debug(
"MQTT-discovered address(es) already known: %s", ", ".join(mqtt_ips)
)
except Exception: # pylint: disable=broad-except
# A background task failure would otherwise stay invisible for
# the whole session and only re-raise at teardown
_LOGGER.exception("MQTT address discovery failed")
def on_log(msg: SubscribeLogsResponse) -> None:
"""Handle a new log message."""
time_ = datetime.now().astimezone()
message: bytes = msg.message
text = message.decode("utf8", "backslashreplace")
nanoseconds = time_.microsecond // 1000
timestamp = (
f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}.{nanoseconds:03}]"
)
for parsed_msg in parse_log_message(text, timestamp):
# safe_print handles the dashboard \033 escaping and falls back
# to backslashreplace encoding on stdouts that can't represent
# the wifi signal-bar block characters (Windows redirected
# cp1252 pipe).
safe_print(parsed_msg)
for raw_line in text.splitlines():
processor.process_line(raw_line)
# Safe to fall back to plaintext here only for this diagnostics use
# case: the stream is one-way from device to client, and this code
# never accepts commands or acts on any message the device sends.
# An on-path attacker could still both inject fabricated log lines
# and passively read the device's log output (and any state data
# delivered when subscribe_states is enabled), so this does lose
# confidentiality as well as authentication/integrity. That tradeoff
# is acceptable for operator-visible logs, which aioesphomeapi also
# warns may come from an unverified device. Never mirror this opt-in
# for any connection that sends data to the device or uses Home
# Assistant actions.
stop = await async_run(
cli,
on_log,
name=name,
subscribe_states=subscribe_states,
allow_plaintext_fallback=True,
# A top-level ``deep_sleep:`` block means the device is only awake
# briefly; cap the reconnect backoff so a wake window is not missed.
deep_sleep="deep_sleep" in config,
on_connect=_cancel_mqtt_discovery if mqtt_resolver is not None else None,
)
try:
# Don't start (or keep) the broker lookup if a connection already
# succeeded; the stop event doubles as the not-needed-anymore latch
# and get_esphome_device_ip returns immediately when it is set.
if mqtt_resolver is not None and not mqtt_stop_event.is_set():
mqtt_task = asyncio.create_task(_resolve_mqtt_addresses())
await asyncio.Event().wait()
finally:
try:
if mqtt_task is not None:
# Unblock the worker thread first so it can't hold up
# loop.shutdown_default_executor() for the full lookup timeout.
mqtt_stop_event.set()
# Give the worker a moment to exit through its own error
# handling; cancelling first would race out a late failure.
done, _ = await asyncio.wait([mqtt_task], timeout=1.0)
if not done:
mqtt_task.cancel()
# return_exceptions keeps a CancelledError from the cancel()
# above from re-raising here and jumping over the stop() below.
# The task handles Exception itself, so only a BaseException
# escape (e.g. SystemExit from the worker) can land here.
(result,) = await asyncio.gather(mqtt_task, return_exceptions=True)
if isinstance(result, BaseException) and not isinstance(
result, asyncio.CancelledError
):
_LOGGER.error("MQTT address discovery failed", exc_info=result)
finally:
# Must run even if a second cancellation lands mid-cleanup above
await stop()
def run_logs(
config: dict[str, Any],
addresses: list[str],
subscribe_states: bool = True,
mqtt_resolver: Callable[[threading.Event], list[str]] | None = None,
) -> None:
"""Run the logs command."""
with suppress(KeyboardInterrupt):
asyncio.run(
async_run_logs(
config,
addresses,
subscribe_states=subscribe_states,
mqtt_resolver=mqtt_resolver,
)
)
+531
View File
@@ -0,0 +1,531 @@
"""Arduino-core backend for the shared PlatformIO library converter.
Bundled names build straight from the framework tree; everything else goes
through ``esphome.platformio.library``. Mirrors ``lib_ldf_mode=off``: each
library builds its own archive; all include dirs join one global path.
Deviations from PlatformIO: flat-layout libraries get the recursive default
source filter; ``dot_a_linkage`` is honored; bundled libraries never run a
manifest ``extraScript``; manifest ``-I`` flags join the global include path;
``precompiled``/``ldflags`` properties are refused by name.
"""
from __future__ import annotations
from dataclasses import dataclass, field
import logging
from pathlib import Path
import re
from esphome.core import CORE, EsphomeError, Library
from esphome.helpers import walk_files
from esphome.platformio.extra_script import apply_extra_script
from esphome.platformio.library import (
DEFAULT_BUILD_INCLUDE_DIR,
DEFAULT_BUILD_SRC_FILTER,
ESPHOME_DATA_KEY,
ESPHOME_DATA_LINK_FLAGS_KEY,
LIBRARY_HEADER_SUFFIXES,
SRC_FILE_EXTENSIONS,
ConvertedLibrary,
IncompatiblePlatform,
InvalidLibrary,
LibraryBackend,
_url_or_none,
check_library_data,
collect_filtered_files,
convert_libraries,
ensure_list,
is_lib_ignored,
lex_build_flags,
lib_ignore_set,
normalize_dependencies,
parse_library_json,
parse_library_properties,
warn_properties_depends,
)
_LOGGER = logging.getLogger(__name__)
@dataclass
class ArduinoLibrary:
"""One resolved library, ready for the ninja generator."""
name: str
sources: list[Path] = field(default_factory=list)
include_dirs: list[Path] = field(default_factory=list)
# Extra compile flags private to this library's own sources
flags: list[str] = field(default_factory=list)
# PlatformIO's build.libArchive / Arduino's dot_a_linkage: when False the
# objects go to the linker directly (symbols nothing references survive)
lib_archive: bool = True
# Link inputs the library contributes (-L dirs / -l libs, e.g. from
# precompiled vendor blobs) and -Wl, options for the firmware link
link_dirs: list[Path] = field(default_factory=list)
link_libs: list[str] = field(default_factory=list)
link_flags: list[str] = field(default_factory=list)
# Source-like suffixes the case-sensitive suffix map rejects
_UNMAPPED_SOURCE_SUFFIXES = frozenset(
{s.lower() for s in SRC_FILE_EXTENSIONS} | {".ino"}
)
# Filename-plain names: an allowlist excludes separators, drive colons,
# and dot-only names by shape
_SAFE_LIBRARY_NAME_RE = re.compile(r"[A-Za-z0-9_][A-Za-z0-9_. +-]*\Z")
def _is_safe_library_name(name: object) -> bool:
"""Whether a name may be joined under the framework's libraries dir."""
return isinstance(name, str) and _SAFE_LIBRARY_NAME_RE.fullmatch(name) is not None
def _manifest_build(name: str, data: object) -> dict:
"""The manifest's ``build`` section; malformed manifests fail by name."""
build = data.get("build", {}) if isinstance(data, dict) else None
if not isinstance(build, dict):
raise EsphomeError(f"Library {name} has a malformed manifest")
return build
def _resolve_src_dir(name: str, read_path: Path, build: dict) -> str:
"""Resolve PIO's source dir: manifest srcDir, else src/Src, else the root."""
if "srcDir" not in build:
return next((d for d in ("src", "Src") if (read_path / d).is_dir()), ".")
# A declared srcDir (falsy included) that does not resolve is a manifest error
src_dir = build["srcDir"]
if not (isinstance(src_dir, str) and src_dir and (read_path / src_dir).is_dir()):
raise EsphomeError(
f"Library {name} declares srcDir {src_dir!r} which does not exist"
)
return src_dir
def _reject_unsupported_link_fields(name: str, data: dict) -> None:
# PIO honors these; ignoring them would fail at link with no stated
# cause. Property values are strings, so "false" is not a declaration.
precompiled = data.get("precompiled")
if precompiled and str(precompiled).strip().lower() != "false":
raise EsphomeError(
f"Library {name} declares precompiled, which this backend does not support"
)
if data.get("ldflags"):
raise EsphomeError(
f"Library {name} declares ldflags, which this backend does not support"
)
def _resolve_lib_archive(name: str, data: dict, build: dict) -> bool:
"""build.libArchive, else dot_a_linkage (an Arduino IDE property PIO
ignores; a deliberate extra), else archive."""
# Strict parse: bool("false") is True
def _parse(key: str, raw: object) -> bool:
if isinstance(raw, bool):
return raw
value = str(raw).strip().lower()
if value in ("true", "false"):
return value == "true"
raise EsphomeError(f"Library {name} has a malformed {key} value {raw!r}")
if "libArchive" in build:
return _parse("libArchive", build["libArchive"])
if "dot_a_linkage" in data:
return _parse("dot_a_linkage", data["dot_a_linkage"])
return True
def _classify_build_flags(
name: str, read_path: Path, lib: ArduinoLibrary, flag_tokens: list[str]
) -> list[str]:
"""Route the lexed build.flags into the library's flag lists.
Returns the ``-I`` arguments for the include-dir resolution.
"""
include_flags: list[str] = []
for tok in flag_tokens:
if tok.startswith("-I"):
include_flags.append(tok[2:])
elif tok.startswith("-L"):
link_dir = (read_path / tok[2:]).resolve()
if not link_dir.is_dir():
# Kept (the linker ignores missing -L dirs); the warning
# names the culprit before a bare "cannot find -lfoo"
_LOGGER.warning(
"Library %s declares library dir %s which does not exist",
name,
tok[2:],
)
lib.link_dirs.append(link_dir)
elif tok.startswith("-l"):
lib.link_libs.append(tok[2:])
elif tok.startswith("-Wl,"):
lib.link_flags.append(tok)
else:
lib.flags.append(tok)
return include_flags
def _resolve_include_dirs(
name: str,
read_path: Path,
lib: ArduinoLibrary,
build: dict,
src_dir: str,
include_flags: list[str],
) -> None:
include_dir = build.get("includeDir", DEFAULT_BUILD_INCLUDE_DIR)
if not isinstance(include_dir, str):
raise EsphomeError(f"Library {name} has a malformed includeDir")
for d, explicit in [
(include_dir, "includeDir" in build),
(src_dir, False), # _resolve_src_dir already validated it
*((flag, True) for flag in include_flags),
]:
if (path := (read_path / d)).is_dir():
lib.include_dirs.append(path.resolve())
elif explicit:
# Warn-and-drop (unlike srcDir): a missing include dir is
# harmless until a header is needed, and the compile names it
_LOGGER.warning(
"Library %s declares include dir %s which does not exist", name, d
)
def _collect_lib_sources(
name: str,
read_path: Path,
lib: ArduinoLibrary,
src_dir: str,
src_filter: list[str],
) -> None:
sources: list[Path] = []
dropped: list[str] = []
saw_header = False
for f in collect_filtered_files(read_path / src_dir, src_filter):
path = Path(f)
suffix = path.suffix
if suffix in SRC_FILE_EXTENSIONS:
# resolve() per file: srcFilter patterns may escape src_dir
sources.append(path.resolve())
elif suffix.lower() in _UNMAPPED_SOURCE_SUFFIXES:
# A source-like suffix the case-sensitive map rejects (.CPP,
# .ino) is a dropped compilation unit; headers fall through
dropped.append(path.name)
elif suffix.lower() in LIBRARY_HEADER_SUFFIXES:
saw_header = True
lib.sources = sorted(sources)
if dropped:
_LOGGER.warning(
"Library %s: %d file(s) with unmapped source suffixes are not compiled: %s",
name,
len(dropped),
", ".join(sorted(dropped)),
)
if not lib.sources and not saw_header:
# Matched headers mean header-only; a filter matching nothing is
# a manifest/tree problem (a truly empty tree raises elsewhere)
_LOGGER.warning("Library %s: no source files matched", name)
def _library_info(name: str, read_path: Path, data: dict) -> ArduinoLibrary:
"""Resolve one library's sources, include dirs, and flags (PIO semantics)."""
build = _manifest_build(name, data)
_reject_unsupported_link_fields(name, data)
src_dir = _resolve_src_dir(name, read_path, build)
src_filter = ensure_list(build.get("srcFilter", DEFAULT_BUILD_SRC_FILTER))
if not all(isinstance(entry, str) for entry in src_filter):
raise EsphomeError(f"Library {name} has a malformed srcFilter")
lib = ArduinoLibrary(name=name, lib_archive=_resolve_lib_archive(name, data, build))
# PlatformIO shell-lexes each build.flags entry
include_flags = _classify_build_flags(
name, read_path, lib, lex_build_flags(build.get("flags", []), f"library {name}")
)
_resolve_include_dirs(name, read_path, lib, build, src_dir, include_flags)
_collect_lib_sources(name, read_path, lib, src_dir, src_filter)
return lib
def _bundled_library(framework_path: Path, name: str) -> ArduinoLibrary:
"""A library bundled with the Arduino core, read from the framework tree.
``library.json`` wins over ``library.properties`` when both exist, as in
PlatformIO's LibBuilderFactory; only the JSON manifest can carry a
``build`` section (srcDir, srcFilter, flags).
"""
lib_dir = framework_path / "libraries" / name
manifest_json = lib_dir / "library.json"
if manifest_json.is_file():
try:
data = parse_library_json(manifest_json)
except ValueError as err: # JSONDecodeError
raise EsphomeError(
f"Bundled library {name} has a corrupt library.json ({err}); "
"the framework install may be incomplete (run 'esphome clean-all')"
) from err
elif (manifest := lib_dir / "library.properties").is_file():
data = parse_library_properties(manifest)
else:
# Debug, not warning: the legacy manifest-less layout is legal and
# the 3.1.2 core ships one such library (FSTools), so a warning
# would be unactionable noise on every build using it
_LOGGER.debug("Bundled library %s has no manifest; using defaults", name)
data = {}
if isinstance(data, dict):
# Bundled manifest deps are never walked; make the skip visible
if data.get("dependencies"):
_LOGGER.warning(
"Bundled library %s declares dependencies, which are not "
"resolved automatically; add them with add_library() if needed",
name,
)
warn_properties_depends(name, data)
build = data.get("build")
if isinstance(build, dict) and build.get("extraScript"):
# Scripts only run on the converted path; building without
# the script's flags would miscompile
raise EsphomeError(
f"Bundled library {name} declares an extraScript, which is "
"not run for bundled libraries"
)
lib = _library_info(name, lib_dir, data)
_assert_tree_has_code(
name,
lib_dir,
"the framework install may be incomplete (run 'esphome clean-all')",
)
return lib
def _assert_tree_has_code(name: str, root: Path, hint: str) -> None:
"""An empty or half-extracted tree can never link; fail by name (a
warning would scroll away and resurface as undefined symbols)."""
if not any(
Path(p).suffix in SRC_FILE_EXTENSIONS
or Path(p).suffix.lower() in LIBRARY_HEADER_SUFFIXES
for p in walk_files(root)
):
raise EsphomeError(f"Library {name} has no sources or headers; {hint}")
def _external_short_name(name: str) -> str:
"""The short library name of a requested spec.
"owner/Name" and plain names take the last path segment; "Name=<url>"
takes the declared name. Git tails (".git", "#ref") are stripped like
the walk's URL normalization; the comparand is a manifest dependency
name, never a spec.
"""
head, sep, tail = name.partition("=")
if sep and "://" in tail:
return head
short = name.rsplit("/", maxsplit=1)[-1]
return short.partition("#")[0].removesuffix(".git")
def _check_unfulfilled_provides(
provided_requests: set[str], satisfied: set[str], still_requested: set[str]
) -> None:
"""Fail by name when a walk-skipped dependency was never added.
An unfulfilled provides() promise only surfaces as undefined symbols
at link. The walk records across re-resolutions, so a name no final
manifest still requests is stale state, never a failure.
"""
if missing := sorted((provided_requests & still_requested) - satisfied):
raise EsphomeError(
"provides() skipped these dependencies but nothing added them: "
f"{', '.join(missing)}; the build is missing libraries"
)
def resolve_libraries(
framework_path: Path, *, pio_platform: str, board_mcu: str, cache_key: str
) -> list[ArduinoLibrary]:
"""Resolve every ``cg.add_library()`` entry into an :class:`ArduinoLibrary`.
``pio_platform``/``board_mcu`` filter manifests the way PlatformIO would
for that core (e.g. ``espressif8266``/``esp8266``); ``cache_key`` keys the
shared converter's download cache.
The returned list is not topologically sorted, so the caller must link
the archives inside one ``--start-group``/``--end-group`` pair (the
bundled-first grouping is incidental).
"""
bundled: list[ArduinoLibrary] = []
external: list[Library] = []
# PlatformIO's lib_ignore covers framework-bundled libraries too; the
# shared converter only filters the registry/git ones.
lib_ignore = lib_ignore_set()
# Exact directory names keep membership case-sensitive everywhere
# (an is_dir() probe would match "wire" on macOS/Windows and build
# the bundled Wire twice)
libraries_dir = framework_path / "libraries"
if not libraries_dir.is_dir():
# A registry fallback would fail later with a misleading
# package-not-found error per bundled name
raise EsphomeError(
f"{libraries_dir} is missing; the framework install may be "
"incomplete (run 'esphome clean-all')"
)
bundled_dir_names = frozenset(p.name for p in libraries_dir.iterdir() if p.is_dir())
def _provided(name: object) -> bool:
return _is_safe_library_name(name) and name in bundled_dir_names
for library in CORE.platformio_libraries.values():
if is_lib_ignored(library.name, lib_ignore):
continue
# Bundled only for a bare name with a matching framework dir; pinned
# or unmatched names resolve from the registry, as under PlatformIO.
if not library.repository and not library.version and _provided(library.name):
# Bundled manifest deps are not walked; _bundled_library warns
bundled.append(_bundled_library(framework_path, library.name))
else:
external.append(library)
converted: list[ArduinoLibrary] = []
bundled_names = {lib.name for lib in bundled}
converted_manifest_names: set[str] = set()
# Bundled candidates skipped on purpose (platform filter); the
# provides() reconciliation must count them as satisfied
knowingly_skipped: set[str] = set()
# Dependency names of the manifests actually emitted; a walk recording
# for a since-re-resolved manifest must not fail the reconciliation
final_dep_names: set[str] = set()
# Ordered set of bundled dependency names to add once conversion is done
pending_bundled: dict[str, None] = {}
# Deps matching a separately-requested external are already in the build
# (a duplicate archive means duplicate-symbol link errors)
external_short_names = {
_external_short_name(lib.name) for lib in external if lib.name
}
def _add_bundled_dependencies(component: ConvertedLibrary) -> None:
# A version-less bare name ("Hash") is a core-bundled library the
# shared converter cannot resolve from the registry
for dep in normalize_dependencies(
component.data.get("dependencies"), component.name
):
# normalize_dependencies guarantees a non-empty str name
name = dep["name"]
final_dep_names.add(name)
if "/" in name:
owner, _, pkg = name.partition("/")
if _is_safe_library_name(owner) and _is_safe_library_name(pkg):
# Owner-qualified; the converter resolves it from the registry
continue
if not _is_safe_library_name(name):
# The name becomes a path component; never join a traversal
_LOGGER.warning(
"Ignoring malformed dependency entry %r of library %s",
dep,
component.name,
)
continue
if name in external_short_names:
if _provided(name):
# A bundled copy is suppressed; a coincidental name
# collision would surface as link errors
_LOGGER.warning(
"Dependency %s of %s is assumed satisfied by a "
"requested external library; the bundled copy is "
"not added",
name,
component.name,
)
else:
_LOGGER.debug(
"Dependency %s of %s assumed satisfied by a requested "
"external library",
name,
component.name,
)
continue
if name in bundled_names or is_lib_ignored(name, lib_ignore):
continue
if _url_or_none(dep.get("version")) is not None:
# A URL names one specific source; never add the bundled copy
continue
if dep.get("owner") or not _provided(name):
# Only owner-less framework-tree names take the bundled
# copy (PIO's process_dependencies); the walk reports drops
continue
try:
# framework=None: the walk already warned for non-platform
# causes; debug keeps one fault from warning twice (pinned
# by test_nonplatform_rejection_warns_once_through_real_converter)
check_library_data(dep, pio_platform, None)
except IncompatiblePlatform as err:
# A knowing skip (platform filter), not a broken promise
knowingly_skipped.add(name)
_LOGGER.debug("Skip bundled candidate %s: %s", name, err)
continue
except InvalidLibrary as err:
# Malformed manifest data never counts as satisfied; the
# walk owns the warning (see the warns-once test above)
_LOGGER.debug("Skip malformed bundled candidate %s: %s", name, err)
continue
# Deferred: a later manifest name may satisfy this
pending_bundled.setdefault(name)
def _emit(component: ConvertedLibrary) -> None:
apply_extra_script(
component, board_mcu=lambda: board_mcu, pio_platform=pio_platform
)
_assert_tree_has_code(
component.get_require_name(),
component.source_dir,
"the download may be incomplete (run 'esphome clean-all')",
)
if isinstance(manifest_name := component.data.get("name"), str):
converted_manifest_names.add(manifest_name)
lib = _library_info(
component.get_require_name(), component.source_dir, component.data
)
# Extra-script LINKFLAGS travel outside build.flags; dropping
# them would link wrong with no stated cause
lib.link_flags.extend(
component.data.get(ESPHOME_DATA_KEY, {}).get(
ESPHOME_DATA_LINK_FLAGS_KEY, []
)
)
converted.append(lib)
_add_bundled_dependencies(component)
backend = LibraryBackend(
platform=pio_platform,
framework="arduino",
emit=_emit,
cache_key=cache_key,
# The walk must not resolve bundled names from the registry;
# _add_bundled_dependencies adds them after emit
provides=_provided,
)
if external:
convert_libraries(external, backend)
for name in pending_bundled:
if name in converted_manifest_names:
# The converted library is this one; the bundled copy would
# double the archive. Warn like the external_short_names twin.
_LOGGER.warning(
"Dependency %s is assumed satisfied by a converted library's "
"manifest name; the bundled copy is not added",
name,
)
continue
bundled_names.add(name)
bundled.append(_bundled_library(framework_path, name))
_check_unfulfilled_provides(
backend.provided_requests,
bundled_names
| converted_manifest_names
| external_short_names
| knowingly_skipped,
final_dep_names,
)
return bundled + converted
+9
View File
@@ -0,0 +1,9 @@
"""Native (PlatformIO-free) build support for the ESP8266 Arduino core.
This package downloads the Arduino ESP8266 core and the xtensa-lx106
toolchain, generates a ninja build for them plus the ESPHome sources, and
drives the build directly — the ESP8266 equivalent of ``esphome.espidf``.
Deliberately importable without the esp8266 component to avoid circular
imports; the component wires these modules in via lazy imports.
"""
+164
View File
@@ -0,0 +1,164 @@
"""Download and install the Arduino ESP8266 core, toolchain, and ninja.
Artifacts land in a machine-global cache (shared across projects, like the
ESP-IDF install in ``esphome.espidf.framework``):
<cache>/arduino8266/frameworks/<version>/ framework-arduinoespressif8266
<cache>/arduino8266/toolchains/<version>/ toolchain-xtensa (gcc 10.3)
Packages come from the PlatformIO registry (identical bits to the PlatformIO
backend); ``ESPHOME_ARDUINO8266_*_MIRRORS`` overrides the URLs. ninja comes
from PATH or the ninja PyPI wheel.
"""
from __future__ import annotations
import os
from pathlib import Path
from typing import NamedTuple
from esphome.build_helpers.ccache import ccache_defaults_env
from esphome.build_helpers.ninja import find_ninja
from esphome.build_helpers.tools_cache import ARDUINO8266_TOOLS_CACHE, tools_cache_path
from esphome.core import EsphomeError, Version
from esphome.framework_helpers import str_to_lst_of_str
from esphome.platformio.registry import install_package, prefetch_packages
FRAMEWORK_PACKAGE = "framework-arduinoespressif8266"
TOOLCHAIN_PACKAGE = "toolchain-xtensa"
# gcc 10.3, the toolchain Arduino core 3.x builds with; the build
# generator's compile flags are tuned to it.
TOOLCHAIN_VERSION = "2.100300.220621"
ESPHOME_ARDUINO8266_FRAMEWORK_MIRRORS = str_to_lst_of_str(
os.environ.get("ESPHOME_ARDUINO8266_FRAMEWORK_MIRRORS", "")
)
ESPHOME_ARDUINO8266_TOOLCHAIN_MIRRORS = str_to_lst_of_str(
os.environ.get("ESPHOME_ARDUINO8266_TOOLCHAIN_MIRRORS", "")
)
def get_arduino8266_tools_path() -> Path:
# Machine-global so all projects share one install; see
# espidf.framework.get_idf_tools_path for the location rationale.
return tools_cache_path(*ARDUINO8266_TOOLS_CACHE)
# 3.1.1 rather than 3.1.0: the registry has no package for 3.1.0, and the
# encoder below cannot name 3.0.0/3.0.1 either (see its docstring)
MIN_FRAMEWORK_VERSION = Version(3, 1, 1)
def framework_package_version(ver: Version) -> str:
"""Map an Arduino core version to its registry package version (3.1.2 ->
3.30102.0; the leading 3 is the package major).
Exact registry names only for cores > 2.6.2 and >= 3.0.2; callers floor
at MIN_FRAMEWORK_VERSION.
"""
if ver.major > 3:
raise EsphomeError(
f"Arduino core {ver} is not supported yet; "
"the newest known core series is 3.x"
)
if ver <= Version(2, 6, 2):
# Cores <= 2.6.2 use the older 1.x/2.x package-major encodings (same
# boundary as _format_framework_arduino_version's era guard)
raise EsphomeError(
f"Arduino core {ver} uses an older package encoding than this "
"helper implements (newer than 2.6.2)"
)
return f"3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
def get_framework_path(package_version: str) -> Path:
return get_arduino8266_tools_path() / "frameworks" / package_version
def get_toolchain_path() -> Path:
return get_arduino8266_tools_path() / "toolchains" / TOOLCHAIN_VERSION
class InstalledPaths(NamedTuple):
"""Locations of the installed framework, toolchain, and ninja binary."""
framework: Path
toolchain: Path
ninja: Path
def check_and_install(framework_version: Version) -> InstalledPaths:
"""Ensure framework, toolchain, and ninja are installed; return their paths."""
if framework_version < MIN_FRAMEWORK_VERSION:
# Config validation enforces this too; keep the module honest when
# called directly.
raise EsphomeError(
f"The native toolchain requires the Arduino core "
f">= {MIN_FRAMEWORK_VERSION}, got {framework_version}"
)
# Probe the cheap local dependency before ~110 MB of downloads
ninja_path = find_ninja()
package_version = framework_package_version(framework_version)
framework_path = get_framework_path(package_version)
downloads_dir = get_arduino8266_tools_path() / "downloads"
toolchain_path = get_toolchain_path()
# One spec per package: the prefetch and the installs must agree
specs = (
(
FRAMEWORK_PACKAGE,
package_version,
framework_path,
ESPHOME_ARDUINO8266_FRAMEWORK_MIRRORS,
("cores/esp8266", "tools/sdk", "libraries"),
),
(
TOOLCHAIN_PACKAGE,
TOOLCHAIN_VERSION,
toolchain_path,
ESPHOME_ARDUINO8266_TOOLCHAIN_MIRRORS,
# xtensa-lx106-elf pins the target: every gcc package has a bin/
("bin", "xtensa-lx106-elf"),
),
)
# Fetch both archives at once; the installs below verify and extract
prefetch_packages([spec[:4] for spec in specs], downloads_dir)
for name, version, dest, mirrors, expect in specs:
install_package(name, version, dest, mirrors, downloads_dir, expect=expect)
return InstalledPaths(
framework=framework_path, toolchain=toolchain_path, ninja=ninja_path
)
def toolchain_tool(toolchain_path: Path, name: str) -> Path:
"""Path to one toolchain tool (gcc, g++, ar, size, addr2line, ...).
The single owner of the ``bin/xtensa-lx106-elf-<name>`` layout and the
Windows suffix, so a toolchain package bump touches one spot.
"""
suffix = ".exe" if os.name == "nt" else ""
return toolchain_path / "bin" / f"xtensa-lx106-elf-{name}{suffix}"
def get_build_env(toolchain_path: Path, ccache: str | None) -> dict[str, str]:
env = os.environ.copy()
# Drop empty entries: a trailing separator from an absent PATH would
# make the shell search the current directory for tools
parts = [
str(toolchain_path / "bin"),
*filter(None, env.get("PATH", "").split(os.pathsep)),
]
env["PATH"] = os.pathsep.join(parts)
env.update(ccache_env(ccache))
return env
def ccache_env(ccache: str | None) -> dict[str, str]:
"""Return ccache settings for the build subprocess (not os.environ).
``ccache`` is the pre-resolved binary (resolve_ccache_path), or None
when disabled. Values the user already set in the environment are
respected.
"""
if ccache is None:
return {}
return ccache_defaults_env(get_arduino8266_tools_path() / "ccache")
+111 -18
View File
@@ -11,43 +11,136 @@ from __future__ import annotations
import asyncio
from collections.abc import Awaitable, Callable
from itertools import count
import logging
import threading
from typing import cast
_LOGGER = logging.getLogger(__name__)
# How long the orphan watcher waits for an abandoned coroutine before giving
# up, so a hung operation does not park a watcher thread forever.
ORPHAN_WAIT_TIMEOUT = 300.0
_runner_ids = count(1)
class AsyncDispatchTimeout(TimeoutError):
"""The caller stopped waiting; the coroutine was abandoned.
A subclass so callers can tell the dispatcher's own expiry apart from a
``TimeoutError`` raised inside the coroutine, while existing
``except TimeoutError`` handlers keep working.
"""
class AsyncThreadRunner[T](threading.Thread):
"""Run an async coroutine in a daemon thread and expose its result.
The runner catches all exceptions from the coroutine and stores them in
``exception`` so ``event`` is always set — this prevents callers waiting
on ``event`` from hanging forever when the coroutine crashes.
Typical usage::
runner = AsyncThreadRunner(lambda: my_coro(arg))
runner.start()
if not runner.event.wait(timeout=5.0):
... # timed out
if runner.exception is not None:
raise runner.exception
result = runner.result
``event`` is always set, even when the coroutine crashes, so waiters
never hang; ``completed`` distinguishes a delivered result (even a
legitimate ``None``) from a coroutine that never finished. Prefer
:func:`run_async`; use this class directly only when a failure should
degrade to a default value instead of raising.
"""
def __init__(self, coro_factory: Callable[[], Awaitable[T]]) -> None:
super().__init__(daemon=True)
super().__init__(daemon=True, name=f"async-thread-runner-{next(_runner_ids)}")
self._coro_factory = coro_factory
self.result: T | None = None
self.exception: BaseException | None = None
self.completed = False
self.event = threading.Event()
async def _runner(self) -> None:
try:
self.result = await self._coro_factory()
except Exception as exc: # noqa: BLE001 # pylint: disable=broad-except
# Capture all exceptions so ``event`` is always set — otherwise a
# crash would hang the waiter forever.
# Distinguishes a delivered result from "never ran", since None
# is a valid result value.
self.completed = True
except BaseException as exc: # noqa: BLE001 # pylint: disable=broad-except
# Capture everything, including BaseException — otherwise a
# cancellation or SystemExit would leave ``exception`` unset and
# waiters would mistake the empty ``result`` for success.
self.exception = exc
finally:
self.event.set()
def run(self) -> None:
asyncio.run(self._runner())
try:
asyncio.run(self._runner())
except BaseException as exc: # noqa: BLE001 # pylint: disable=broad-except
# asyncio.run itself can fail before _runner executes (e.g. loop
# creation under fd exhaustion); record it so waiters never hang.
# A failure during loop cleanup after the coroutine completed
# must not clobber the delivered result, hence the guard.
if self.exception is None and not self.completed:
self.exception = exc
else:
_LOGGER.debug(
"Event loop teardown failed after outcome recorded",
exc_info=True,
)
finally:
self.event.set()
def run_async[T](
coro_factory: Callable[[], Awaitable[T]],
timeout: float | None = None,
on_orphan: Callable[[T], None] | None = None,
) -> T:
"""Run a coroutine in a daemon-thread event loop and return its result.
Raises :class:`AsyncDispatchTimeout` if the coroutine does not finish
within ``timeout`` seconds; the thread is abandoned and exits with the
interpreter. If the abandoned coroutine later produces a result,
``on_orphan`` (if given) is called with it so resources such as a
connected socket can be released; delivery is best effort and bounded
by ``ORPHAN_WAIT_TIMEOUT``.
"""
runner: AsyncThreadRunner[T] = AsyncThreadRunner(coro_factory)
runner.start()
if not runner.event.wait(timeout):
def _cleanup() -> None:
if not runner.event.wait(ORPHAN_WAIT_TIMEOUT):
# The one state where a resource can genuinely leak; leave
# a trace so a recurring hang is attributable.
_LOGGER.info(
"Orphan watcher gave up after %.0fs; a late result may leak",
ORPHAN_WAIT_TIMEOUT,
)
return
if not runner.completed:
# The only place an abandoned thread's real error surfaces;
# without it a late failure hides behind the TimeoutError.
# INFO, not DEBUG: it fires at most once per abandoned
# operation and the cause may not reproduce on a rerun.
_LOGGER.info(
"Abandoned async operation failed",
exc_info=runner.exception,
)
return
if (result := runner.result) is None:
return
if on_orphan is None:
_LOGGER.debug("Discarding late result; no on_orphan handler")
return
try:
on_orphan(result)
except Exception: # pylint: disable=broad-except
# INFO, not DEBUG: a failed release means a real leak, and
# it fires at most once per abandoned operation.
_LOGGER.info("Error releasing orphaned result", exc_info=True)
threading.Thread(
target=_cleanup, daemon=True, name="async-orphan-cleanup"
).start()
raise AsyncDispatchTimeout("Timed out waiting for async operation")
if (exc := runner.exception) is not None:
raise exc
if not runner.completed:
raise RuntimeError("Async operation finished without a result or an exception")
return cast("T", runner.result)
+108
View File
@@ -0,0 +1,108 @@
"""Tiny cross-platform build steps invoked from the generated ninja file.
Plain script (not ``python -m``): it runs from ninja with whatever Python
started esphome and must not depend on the package being importable.
Subcommands:
ar <ar-binary> <archive> <rspfile> remove stale archive, then ``ar rcs``
copy <src> <dst> copy a file
The ar rspfile carries one object path per line (the generating rule must
use ``$in_newline``, never ``$in``).
"""
from pathlib import Path
import shutil
import subprocess
import sys
def _read_rspfile(rspfile: str) -> list[str]:
r"""The object paths listed in ``rspfile``, unquoted.
GNU ar treats backslashes in response files as escapes (corrupts
Windows paths), so the caller expands the list into argv; strip the
simple surrounding quote ninja adds to special paths, then undo
ninja's POSIX escape for an embedded quote ('a'\\''b.o' -> a'b.o).
"""
return [
line[1:-1].replace("'\\''", "'")
if len(line) >= 2 and line[0] == line[-1] and line[0] in "'\""
else line
for line in Path(rspfile).read_text(encoding="utf-8").splitlines()
if line
]
def _run_ar(ar: str, archive: str, rspfile: str) -> int:
# Remove first: ``ar rcs`` replaces members but never drops ones whose
# source was removed from the build, which would leak stale objects.
Path(archive).unlink(missing_ok=True)
objects = _read_rspfile(rspfile)
if not objects:
# An empty archive would "succeed" here and fail far away at link
print(f"ar: no objects listed in {rspfile} for {archive}", file=sys.stderr)
return 1
# Batch by argv length: expanding the rspfile gives back the Windows
# 32767-char command-line limit it existed to avoid. "rcs" creates,
# "qs" appends; the s keeps the symbol index explicit on every ar.
op = "rcs"
ok = False
try:
while objects:
batch = [objects.pop(0)]
batch_len = len(batch[0])
while objects and batch_len + len(objects[0]) < 25000:
batch_len += len(objects[0]) + 1
batch.append(objects.pop(0))
rc = subprocess.run(
[ar, op, archive, *batch], check=False, close_fds=False
).returncode
if rc != 0:
return rc
op = "qs"
ok = True
return 0
finally:
if not ok:
# Any failure (bad exit, missing ar binary, interrupt) must not
# leave a truncated archive behind
Path(archive).unlink(missing_ok=True)
def _run_copy(src: str, dst: str) -> int:
try:
shutil.copyfile(src, dst)
except OSError as err:
# Never leave a partially written output (e.g. a firmware image);
# SameFileError means dst IS src, where unlinking destroys the input
if not isinstance(err, shutil.SameFileError):
Path(dst).unlink(missing_ok=True)
print(f"copy: {src} -> {dst} failed: {err}", file=sys.stderr)
return 1
return 0
# mode -> (handler, expected operand count); surplus argv means a
# mis-specified ninja rule and must error, not silently drop operands
_MODES = {"ar": (_run_ar, 3), "copy": (_run_copy, 2)}
def main() -> int:
mode = sys.argv[1] if len(sys.argv) > 1 else ""
if entry := _MODES.get(mode):
handler, argc = entry
args = sys.argv[2:]
if len(args) != argc:
print(
f"build_tool {mode}: expected {argc} arguments, got {len(args)}",
file=sys.stderr,
)
return 1
return handler(*args)
print(f"unknown build_tool mode: {mode}", file=sys.stderr)
return 1
if __name__ == "__main__": # pragma: no cover
sys.exit(main())
+92 -34
View File
@@ -1,11 +1,18 @@
"""ESP-IDF direct build generator for ESPHome."""
import json
import logging
from pathlib import Path
from esphome.components.esp32 import get_esp32_variant, idf_version
from esphome.components.esp32 import (
get_esp32_variant,
get_excluded_builtin_components,
get_managed_component_require_names,
idf_version,
)
import esphome.config_validation as cv
from esphome.core import CORE
from esphome.espidf import variant_to_idf_target
from esphome.framework_helpers import (
get_project_compile_flags,
get_project_cxx_compile_flags,
@@ -13,6 +20,8 @@ from esphome.framework_helpers import (
)
from esphome.helpers import mkdir_p, write_file_if_changed
_LOGGER = logging.getLogger(__name__)
# Replaces the IDF default C++ standard (-std=gnu++2b appended to
# CXX_COMPILE_OPTIONS by project.cmake's __build_init) with the one set via
# cg.set_cpp_standard(). Emitted between include(project.cmake) and project(),
@@ -26,11 +35,12 @@ idf_build_set_property(CXX_COMPILE_OPTIONS "${{esphome_cxx_compile_options}}")""
def get_available_components() -> list[str] | None:
"""Get list of built-in ESP-IDF components from project_description.json.
"""List the built-in ESP-IDF components from ``project_description.json``.
Excludes ``src``, IDF-managed components (``managed_components/``), and
converted PIO libs (``pio_components/``). Returns ``None`` if the build
dir or ``project_description.json`` isn't ready yet.
Only components below its ``idf_path/components`` count, which leaves out
``src``, IDF-managed components, converted PIO libs and project local
ones such as the Arduino ``component_stubs``. Returns ``None`` if the
build dir or ``project_description.json`` isn't ready yet.
"""
if CORE.build_path is None:
return None
@@ -41,41 +51,44 @@ def get_available_components() -> list[str] | None:
try:
with project_desc.open(encoding="utf-8") as f:
data = json.load(f)
component_info = data.get("build_component_info", {})
result = []
for name, info in component_info.items():
# Exclude our own src component
if name == "src":
continue
# Exclude IDF-managed and converted-PIO components (external).
comp_dir = info.get("dir", "")
if "managed_components" in comp_dir or "pio_components" in comp_dir:
continue
result.append(name)
return result
except (json.JSONDecodeError, OSError):
root = (Path(data["idf_path"]) / "components").resolve()
result = [
name
for name, info in data.get("build_component_info", {}).items()
if (comp_dir := info.get("dir"))
and Path(comp_dir).resolve().is_relative_to(root)
]
except (json.JSONDecodeError, KeyError, OSError) as err:
_LOGGER.debug("Could not read %s: %s", project_desc, err)
return None
if not result:
_LOGGER.warning("No ESP-IDF components found under %s", root)
return result
def has_discovered_components() -> bool:
"""Check if we have discovered components from a previous configure."""
return get_available_components() is not None
"""Check if a previous configure discovered any built-in components."""
return bool(get_available_components())
def get_project_cmakelists(minimal: bool = False) -> str:
def _cmake_quote(value: str) -> str:
"""Quote a cmake arg value for a set() line. add_cmake_arg rejects
whitespace, quotes, and '$', so only backslashes need escaping."""
escaped = value.replace("\\", "\\\\")
return f'"{escaped}"'
def get_project_cmakelists(
minimal: bool = False, builtin_components: list[str] | None = None
) -> str:
"""Generate the top-level CMakeLists.txt for ESP-IDF project.
When ``minimal`` is true, omit ``ESPHOME_PROJECT_BUILTIN_COMPONENTS``
since ``project_description.json`` may be stale on the first write.
``builtin_components`` supplies the discovered list (from the cache)
instead of reading it from ``project_description.json``.
"""
# Get IDF target from ESP32 variant (e.g., ESP32S3 -> esp32s3)
variant = get_esp32_variant()
idf_target = variant.lower().replace("-", "")
idf_target = variant_to_idf_target(get_esp32_variant())
# esp_idf_size 2.x (bundled with IDF >=6.0) made NG the default and
# removed the --ng flag; on 1.x (IDF 5.5) --ng is required to get
@@ -109,6 +122,15 @@ def get_project_cmakelists(minimal: bool = False) -> str:
else ""
)
# CMake variables registered via cg.add_cmake_arg(). Emitted before
# include(project.cmake) so values like EXCLUDE_COMPONENTS are already
# set when project.cmake seeds the component list, and on minimal
# (discovery) writes too so excluded components never register.
cmake_args = "\n".join(
f"set({name} {_cmake_quote(value)})"
for name, value in sorted(CORE.cmake_args.items())
)
# Per-project list exposed as a CMake variable so converted PIO libs
# can reference ${ESPHOME_PROJECT_MANAGED_COMPONENTS} without baking
# project-specific names into their cached CMakeLists.
@@ -119,8 +141,6 @@ def get_project_cmakelists(minimal: bool = False) -> str:
# runs as a separate CMake script invocation that doesn't load the
# project's top-level CMakeLists; without this, ${ESPHOME_PROJECT_
# MANAGED_COMPONENTS} in a converted-lib REQUIRES expands to empty).
from esphome.components.esp32 import get_managed_component_require_names
managed_components_property = "\n".join(
f"idf_build_set_property(ESPHOME_PROJECT_MANAGED_COMPONENTS {name} APPEND)"
for name in get_managed_component_require_names()
@@ -131,12 +151,24 @@ def get_project_cmakelists(minimal: bool = False) -> str:
# component's REQUIRES including real IDF components). Referenced by
# src/CMakeLists and by each converted PIO lib's CMakeLists. Skipped
# on minimal writes because project_description.json may be stale.
# Excluded components are dropped here as well: a stale
# project_description.json from a build without exclusions may still
# list them, and requiring an excluded component pulls it back into
# the build (IDF requirement expansion overrides EXCLUDE_COMPONENTS).
# Derived from the EXCLUDE_COMPONENTS cmake arg emitted above so the
# two can never disagree within one generated file.
builtin_components_property = (
""
if minimal
else "\n".join(
f"idf_build_set_property(ESPHOME_PROJECT_BUILTIN_COMPONENTS {name} APPEND)"
for name in sorted(get_available_components() or [])
for name in sorted(
set(
builtin_components
if builtin_components is not None
else get_available_components() or []
).difference(CORE.cmake_args.get("EXCLUDE_COMPONENTS", "").split(";"))
)
)
)
@@ -163,6 +195,8 @@ set(CMAKE_NINJA_FORCE_RESPONSE_FILE 1)
set(IDF_TARGET {idf_target})
set(EXTRA_COMPONENT_DIRS ${{CMAKE_SOURCE_DIR}}/src)
{cmake_args}
include($ENV{{IDF_PATH}}/tools/cmake/project.cmake)
{cpp_standard_options}
@@ -210,15 +244,27 @@ def get_component_cmakelists() -> str:
if(CMAKE_SCRIPT_MODE_FILE)
file(GLOB_RECURSE app_sources
"${{CMAKE_CURRENT_SOURCE_DIR}}/*.cpp"
"${{CMAKE_CURRENT_SOURCE_DIR}}/*.cc"
"${{CMAKE_CURRENT_SOURCE_DIR}}/*.cxx"
"${{CMAKE_CURRENT_SOURCE_DIR}}/*.c++"
"${{CMAKE_CURRENT_SOURCE_DIR}}/*.c"
"${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.cpp"
"${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.cc"
"${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.cxx"
"${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.c++"
"${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.c"
)
else()
file(GLOB_RECURSE app_sources CONFIGURE_DEPENDS
"${{CMAKE_CURRENT_SOURCE_DIR}}/*.cpp"
"${{CMAKE_CURRENT_SOURCE_DIR}}/*.cc"
"${{CMAKE_CURRENT_SOURCE_DIR}}/*.cxx"
"${{CMAKE_CURRENT_SOURCE_DIR}}/*.c++"
"${{CMAKE_CURRENT_SOURCE_DIR}}/*.c"
"${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.cpp"
"${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.cc"
"${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.cxx"
"${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.c++"
"${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.c"
)
endif()
@@ -236,7 +282,9 @@ target_link_options(${{COMPONENT_LIB}} PUBLIC
"""
def write_project(minimal: bool = False) -> None:
def write_project(
minimal: bool = False, builtin_components: list[str] | None = None
) -> None:
"""Write ESP-IDF project files."""
mkdir_p(CORE.build_path)
mkdir_p(CORE.relative_src_path())
@@ -244,7 +292,7 @@ def write_project(minimal: bool = False) -> None:
# Write top-level CMakeLists.txt
write_file_if_changed(
CORE.relative_build_path("CMakeLists.txt"),
get_project_cmakelists(minimal=minimal),
get_project_cmakelists(minimal=minimal, builtin_components=builtin_components),
)
# Write component CMakeLists.txt in src/
@@ -252,3 +300,13 @@ def write_project(minimal: bool = False) -> None:
CORE.relative_src_path("CMakeLists.txt"),
get_component_cmakelists(),
)
# Snapshot the exclusion set so has_outdated_files() can trigger a
# discovery reconfigure when it changes. Excluded components never
# register in project_description.json, so re-including one (e.g. a
# config gains mqtt) requires a fresh discovery pass before the
# ESPHOME_PROJECT_BUILTIN_COMPONENTS property can list it.
write_file_if_changed(
CORE.relative_build_path("exclude_components.esphomeinternal"),
";".join(get_excluded_builtin_components()),
)
+11
View File
@@ -63,6 +63,17 @@ def get_ini_content():
# Add extra script for C++ flags
CORE.add_platformio_option("extra_scripts", [f"pre:{CXX_FLAGS_FILE_NAME}"])
# Add CMake args. A user-supplied value (str or list) is deliberately
# replaced; this option was always overwritten at FINAL priority.
if CORE.cmake_args:
CORE.add_platformio_option(
"board_build.cmake_extra_args",
" ".join(
f"-D{name}={value}" for name, value in sorted(CORE.cmake_args.items())
),
replace=True,
)
content = "[platformio]\n"
content += f"description = ESPHome {__version__}\n"
+1
View File
@@ -0,0 +1 @@
"""Build helpers shared by the native (non-PlatformIO) toolchains."""
+92
View File
@@ -0,0 +1,92 @@
"""Shared ccache policy for build backends: env-knob parsing, binary
resolution, and default ``CCACHE_*`` values."""
from __future__ import annotations
import logging
import os
from pathlib import Path
from esphome.framework_helpers import strip_win_long_path_prefix, tool_version_runs
from esphome.helpers import FALSY_ENV_STRINGS, TRUTHY_ENV_STRINGS
_LOGGER = logging.getLogger(__name__)
def _ccache_runs(ccache: str) -> bool:
"""Return True when the ``ccache`` found on PATH actually runs."""
return tool_version_runs(
ccache,
"Ignoring ccache at %s because it failed to run; compiling without ccache",
)
def parse_enable_env(name: str) -> bool | None:
"""Strictly parse an on/off environment knob; None when unset or invalid.
``bool(str)`` truthiness would flip ``no``/``off`` to enabled, so only
1/true/yes/on and 0/false/no/off count; anything else warns and reads
as unset so the caller's default policy applies.
"""
raw = os.environ.get(name)
if raw is None:
return None
lowered = raw.strip().lower()
if not lowered:
# ENV KNOB= (Docker/CI) has always read as a disable
return False
if lowered in TRUTHY_ENV_STRINGS:
return True
if lowered in FALSY_ENV_STRINGS:
return False
_LOGGER.warning("Ignoring unrecognized %s=%r; use 1 or 0", name, raw)
return None
def resolve_ccache_path() -> str | None:
"""The ccache binary to wrap compiles with, or None when disabled.
An explicit ``ESPHOME_CCACHE_ENABLE=1`` skips the runnability probe; the
Windows extended-length prefix is stripped before probing (#18399).
"""
import shutil
explicit = parse_enable_env("ESPHOME_CCACHE_ENABLE")
if explicit is False:
return None
ccache = shutil.which("ccache")
if ccache is None:
if explicit:
_LOGGER.warning(
"ESPHOME_CCACHE_ENABLE is set but no ccache binary is on PATH; "
"compiling without ccache"
)
return None
ccache = strip_win_long_path_prefix(ccache)
if not explicit and not _ccache_runs(ccache):
return None
return ccache
def ccache_defaults_env(cache_dir: Path) -> dict[str, str]:
"""Default ``CCACHE_*`` values for a build subprocess (not os.environ).
Values the user already set in the environment are respected. Depend
mode is on: both native backends emit depfiles (-MMD / CMake), which
keeps cache-miss overhead low.
"""
from esphome.core import CORE
# An unset build_path means the env was built before preload; fail loudly
# rather than silently drop CCACHE_BASEDIR.
if CORE.build_path is None:
raise ValueError(
"CORE.build_path must be set before constructing the build environment"
)
defaults = {
"CCACHE_DIR": str(cache_dir),
"CCACHE_NOHASHDIR": "true",
"CCACHE_DEPEND": "1",
"CCACHE_BASEDIR": str(Path(CORE.build_path).resolve()),
}
return {k: v for k, v in defaults.items() if k not in os.environ}
+380
View File
@@ -0,0 +1,380 @@
"""Derive idedata from a native (non-PlatformIO) build's ``compile_commands.json``.
PlatformIO exposes a curated ``pio run -t idedata`` JSON; the native
toolchains have no such command, but each build produces a
``compile_commands.json`` (CMAKE_EXPORT_COMPILE_COMMANDS for ESP-IDF, ninja's
compdb tool otherwise). This module turns that file into the same fields
consumers (IDE integration, clang-tidy) expect:
{cc_path, cxx_path, cxx_flags, defines, includes: {build, toolchain}}
"""
from __future__ import annotations
import json
import logging
import os
from pathlib import Path
import shlex
import subprocess
from esphome.core import EsphomeError
from esphome.helpers import write_file
# Everything idedata generation may raise after a successful link; idedata
# is a bonus artifact, so consumers warn instead of failing the build
IDEDATA_BEST_EFFORT_ERRORS = (
EsphomeError,
LookupError,
OSError,
RuntimeError,
ValueError,
)
_LOGGER = logging.getLogger(__name__)
# C++ translation-unit suffixes used to identify ESPHome source files.
_CXX_SUFFIXES = (".cpp", ".cc")
# Suffixes of input/output files that appear bare on the command line (and so
# must not be mistaken for compiler flags).
_INPUT_FILE_SUFFIXES = (*_CXX_SUFFIXES, ".c", ".o", ".S", ".s")
# Path marker identifying an ESPHome source translation unit.
_ESPHOME_SRC_MARKER = "/src/esphome/"
def _is_esphome_src(file: str) -> bool:
"""Whether ``file`` is an ESPHome C++ translation unit; normalized to
``/`` first since Windows compile DBs use backslashes."""
return _ESPHOME_SRC_MARKER in file.replace("\\", "/") and file.endswith(
_CXX_SUFFIXES
)
def _split_command(command: str) -> list[str]:
r"""Tokenize a compile_commands.json / response-file command string.
On Windows, tokenize per Windows ``argv`` rules via ``CommandLineToArgvW``.
ESP-IDF's compile_commands.json there mixes two backslash conventions in one
string: literal path separators in the compiler path (``C:\Users\...g++.exe``,
no quote follows) and shell quote-escaping in -D defines (``-DVER=\"1.2.3\"``).
Only the real Windows parser — where a backslash escapes solely a following
quote — handles both, and it is the exact tokenizer the compiler is launched
with. ``shlex`` cannot: POSIX mode eats the path separators, and disabling
its escape mangles the defines.
"""
if os.name != "nt":
return shlex.split(command)
import ctypes
from ctypes import wintypes
# CommandLineToArgvW("") returns the current process name, not []; guard it
# so an empty response file tokenizes the same as it would via shlex.
if not command.strip():
return []
CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
CommandLineToArgvW.argtypes = [wintypes.LPCWSTR, ctypes.POINTER(ctypes.c_int)]
CommandLineToArgvW.restype = ctypes.POINTER(wintypes.LPWSTR)
argc = ctypes.c_int()
argv = CommandLineToArgvW(command, ctypes.byref(argc))
if not argv: # pragma: no cover
raise ctypes.WinError()
try:
return [argv[i] for i in range(argc.value)]
finally:
ctypes.windll.kernel32.LocalFree(argv)
def _expand_response_files(tokens: list[str], directory: Path) -> list[str]:
"""Inline any ``@response-file`` arguments (paths relative to ``directory``).
GCC response files embed flags that must be expanded so GCC-only flags
inside them (e.g. ``-mlongcalls``) can be filtered downstream; left as
``@file`` clang would read them and choke.
"""
out: list[str] = []
for tok in tokens:
if tok.startswith("@"):
rf = Path(tok[1:])
if not rf.is_absolute():
rf = directory / rf
try:
out.extend(
_expand_response_files(
_split_command(rf.read_text(encoding="utf-8")), directory
)
)
continue
except OSError as err:
# Keep the literal token if the file can't be read, but log it
# so the (otherwise opaque) downstream clang failure is traceable.
_LOGGER.warning("Could not read response file %s: %s", rf, err)
out.append(tok)
return out
def _pick_entry(entries: list[dict]) -> dict:
"""Pick a representative ESPHome C++ TU; all share the same component
flags/defines."""
for entry in entries:
if _is_esphome_src(entry["file"]):
return entry
for entry in entries:
if entry["file"].endswith(_CXX_SUFFIXES):
return entry
raise ValueError("no C++ translation unit found in compile_commands.json")
# Compiler launchers that may prefix a compile command; a closed launcher
# denylist beats enumerating compiler names, an open set.
_LAUNCHER_STEMS = frozenset({"ccache", "sccache", "distcc", "icecc", "buildcache"})
def _is_launcher(token: str) -> bool:
return Path(token).stem.lower() in _LAUNCHER_STEMS
def parse_entry(
entry: dict, launcher: str | None = None
) -> tuple[str, list[str], list[str], list[str]]:
"""Parse one compile_commands entry -> (cxx_path, defines, includes, cxx_flags)."""
directory = Path(entry["directory"])
tokens = _expand_response_files(_split_command(entry["command"]), directory)
def _include(raw: str) -> str:
# Resolve against the entry's ``directory`` so cached idedata works
# from any cwd; emit forward slashes to match the JSON's own entries
raw = raw.strip()
if raw and not Path(raw).is_absolute():
raw = os.path.normpath(directory / raw)
return raw.replace("\\", "/")
# A launcher-wrapped command ("ccache g++ ...") names the compiler second
if launcher is not None and tokens[:1] == [launcher]:
tokens = tokens[1:]
if not tokens:
# An empty command, or one that was only the launcher; fail by name
raise ValueError(f"empty compile command for {entry.get('file')}")
if _is_launcher(tokens[0]) and len(tokens) > 1 and not tokens[1].startswith("-"):
# Stale DB built with a launcher this run no longer configures; the
# real compiler is the next token
_LOGGER.warning("Stripping unconfigured launcher %s", tokens[0])
tokens = tokens[1:]
# token0 is the compiler path; the rest of the command already uses forward
# slashes on Windows, so normalize it too for a consistent idedata file.
cxx_path = tokens[0].replace("\\", "/")
# Enforced here so no caller can record ccache as the compiler
reject_launcher_compiler(cxx_path)
defines: list[str] = []
includes: list[str] = []
cxx_flags: list[str] = []
it = iter(tokens[1:])
for tok in it:
if tok in ("-c", "-o"):
next(it, None) # drop the flag and its argument (input/output)
elif tok.startswith("-D"):
# ``.strip()`` handles tokens like ``-D CONFIGURED=1`` (a single
# quoted arg with a space after -D) that some flags arrive as.
defines.append(tok[2:].strip() if len(tok) > 2 else next(it, "").strip())
elif tok.startswith("-I"):
includes.append(_include(tok[2:] if len(tok) > 2 else next(it, "")))
elif tok == "-isystem":
includes.append(_include(next(it, "")))
elif tok.startswith("-isystem"):
includes.append(_include(tok[len("-isystem") :]))
elif tok in ("-MT", "-MF", "-MQ"):
next(it, None) # dependency-file flag + its argument
elif tok.startswith(("-MD", "-MMD", "-MP", "-MM")):
pass # dependency-generation flags, no argument
elif tok.endswith(_INPUT_FILE_SUFFIXES):
pass # input/output files
else:
cxx_flags.append(tok)
return cxx_path, defines, includes, cxx_flags
def get_toolchain_includes(cxx_path: str) -> list[str]:
"""Query the compiler for its builtin ``#include <...>`` search dirs."""
result = subprocess.run(
[cxx_path, "-E", "-x", "c++", "-", "-v"],
input="",
text=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.PIPE,
check=False,
close_fds=False,
)
includes: list[str] = []
capture = False
for line in result.stderr.splitlines():
if "#include <...> search starts here:" in line:
capture = True
continue
if "End of search list." in line:
break
if capture:
includes.append(line.strip())
if result.returncode != 0 or not includes:
raise RuntimeError(
f"Could not query builtin include dirs from {cxx_path} "
f"(return code {result.returncode}); stderr:\n{result.stderr.strip()}"
)
return includes
def _cc_path_from_cxx(cxx_path: str) -> str:
"""Derive the C compiler path from the C++ compiler path.
compile_commands.json only names the C++ compiler, but consumers reach the
rest of the toolchain (objdump, readelf, addr2line) by rewriting the tail of
``cc_path``, so they need the ``gcc``-suffixed name.
"""
stem, suffix = (
(cxx_path[: -len(".exe")], ".exe")
if cxx_path.endswith(".exe")
else (cxx_path, "")
)
# Rewrite the program name only when it is g++ itself, or a toolchain
# prefixed one such as xtensa-esp32-elf-g++ -> xtensa-esp32-elf-gcc.
# Requiring a separator before the "g++" keeps names that merely end in
# those three characters intact: "clang++" must not become "clangcc".
head = stem[: -len("g++")]
if stem.endswith("g++") and (not head or head.endswith(("-", "/", "\\"))):
stem = f"{head}gcc"
return f"{stem}{suffix}"
def _cache_usable(cached: object) -> bool:
"""Check a cached idedata dict against the guarantees of the write path.
Caches written by older versions predate the launcher rejection and the
include-union shape; serving one would bypass both. The dict check also
keeps "in" from substring-matching a bare JSON string.
"""
if not isinstance(cached, dict) or "cc_path" not in cached:
return False
cxx_path = cached.get("cxx_path")
if not isinstance(cxx_path, str) or _is_launcher(cxx_path):
return False
includes = cached.get("includes")
return isinstance(includes, dict) and isinstance(includes.get("build"), list)
def load_or_build_idedata(
compile_commands: Path,
elf_path: Path,
cache: Path,
launcher: str | None = None,
) -> dict | None:
"""Return idedata for a compile_commands.json build, cached on mtime.
Shared by the native ESP-IDF and ESP8266 Arduino toolchains. Returns None
when the compile DB doesn't exist yet (nothing was built). ``launcher``
is the compiler-launcher path (ccache) the build was generated with, if
any; commands in the compile DB are prefixed with it.
"""
if not compile_commands.is_file():
_LOGGER.debug("No %s yet; skipping idedata generation", compile_commands)
return None
if cache.is_file() and cache.stat().st_mtime >= compile_commands.stat().st_mtime:
try:
cached = json.loads(cache.read_text(encoding="utf-8"))
except (ValueError, OSError) as err:
# A recurring cause (interrupted write, disk full) would otherwise
# look like unexplained slow builds
_LOGGER.warning("Discarding unreadable idedata cache %s: %s", cache, err)
else:
if _cache_usable(cached):
# Re-stamp so a relocated build dir cannot serve a stale ELF path
cached["prog_path"] = str(elf_path)
return cached
_LOGGER.debug("Regenerating idedata: cache %s fails validation", cache)
data = idedata_from_build(compile_commands, launcher)
data["prog_path"] = str(elf_path)
cache.parent.mkdir(parents=True, exist_ok=True)
# Atomic so a crash mid-write cannot leave a truncated cache
write_file(cache, json.dumps(data, indent=2) + "\n")
return data
def reject_launcher_compiler(cxx_path: str) -> None:
"""Reject a compile DB naming a launcher (ccache) as the compiler; it
must never be probed, cached, or consumed."""
if _is_launcher(cxx_path):
raise EsphomeError(
f"compile_commands.json names the launcher {cxx_path} as the "
"compiler; the compile database is unusable"
)
def idedata_from_build(compile_commands: Path, launcher: str | None = None) -> dict:
"""Parse compile_commands.json into the idedata fields consumers expect.
A single compile entry only carries the include set its own translation
unit was built with (per-component under ESP-IDF), but consumers
(clang-tidy) analyze ESPHome headers that transitively pull in other
components. So take cxx_path / cxx_flags / defines from a representative
ESPHome TU, but union the include dirs across all ESPHome TUs to get a
project-wide superset (as PlatformIO's idedata provides).
"""
entries = json.loads(Path(compile_commands).read_text(encoding="utf-8"))
if not isinstance(entries, list) or not all(isinstance(e, dict) for e in entries):
# A TypeError here would escape IDEDATA_BEST_EFFORT_ERRORS
raise EsphomeError(f"{compile_commands} is not a compile-command list")
representative = _pick_entry(entries)
cxx_path, defines, rep_includes, cxx_flags = parse_entry(representative, launcher)
# Seed with the representative's includes so it is not parsed twice
has_esphome_tu = _is_esphome_src(representative["file"])
build_includes: dict[str, None] = dict.fromkeys(
rep_includes if has_esphome_tu else ()
)
def _shape(entry: dict) -> str:
# directory + command minus TU-specific paths: same shape means the
# same include set, so tokenize once per shape. Response-file
# commands never dedupe (the .rsp contents differ per object)
command = entry["command"]
directory = entry.get("directory", "")
if "@" in command:
return f"unique:{directory}|{entry.get('output') or command}"
stripped = command.replace(entry.get("file", ""), "").replace(
entry.get("output", ""), ""
)
return f"{directory}|{stripped}"
seen_shapes = {_shape(representative)}
for entry in entries:
if entry is representative or not _is_esphome_src(entry["file"]):
continue
has_esphome_tu = True
if (shape := _shape(entry)) in seen_shapes:
_LOGGER.debug("Include union: %s shares a command shape", entry["file"])
continue
seen_shapes.add(shape)
for inc in parse_entry(entry, launcher)[2]:
build_includes.setdefault(inc, None)
if not has_esphome_tu:
# An arbitrary fallback TU breaks clang-tidy/IDE consumers, and a
# warning would be cached into permanence; call sites downgrade this
raise EsphomeError(
f"No ESPHome translation unit found in {compile_commands}; "
"refusing to cache unusable idedata"
)
return {
"cc_path": _cc_path_from_cxx(cxx_path),
"cxx_path": cxx_path,
"cxx_flags": cxx_flags,
"defines": defines,
"includes": {
"build": list(build_includes),
"toolchain": get_toolchain_includes(cxx_path),
},
}
+92
View File
@@ -0,0 +1,92 @@
"""Platform-neutral helpers for ninja-driven native builds."""
from __future__ import annotations
import logging
import os
from pathlib import Path
import re
import shutil
from esphome.core import EsphomeError
from esphome.framework_helpers import strip_win_long_path_prefix, tool_version_runs
_LOGGER = logging.getLogger(__name__)
def _ninja_runs(binary: str) -> bool:
"""Whether the ninja found on PATH actually runs (see tool_version_runs)."""
return tool_version_runs(
binary,
"Ignoring ninja at %s because it failed to run; "
"falling back to the bundled wheel",
)
def find_ninja() -> Path:
"""Locate the ninja binary: a runnable PATH hit first, else the ninja
PyPI wheel."""
if binary := shutil.which("ninja"):
binary = strip_win_long_path_prefix(binary)
if _ninja_runs(binary):
return Path(binary)
import_error: ImportError | None = None
try:
import ninja
except ImportError as err:
import_error = err
wheel_binary = None
else:
wheel_binary = Path(ninja.BIN_DIR) / (
"ninja.exe" if os.name == "nt" else "ninja"
)
if wheel_binary is None or not wheel_binary.is_file():
raise EsphomeError(
"ninja not found on PATH or in the ninja package; reinstall the "
"esphome Python environment"
) from import_error
return wheel_binary
def escape(value: Path | str) -> str:
"""Escape a path or token for a ninja file."""
return str(value).replace("$", "$$").replace(":", "$:").replace(" ", "$ ")
def quote_arg(tok: str) -> str:
"""Quote with the CreateProcess argv rule (as ``subprocess.list2cmdline``):
backslash runs double only before a quote. Windows-only; ``$`` must
already be doubled for ninja.
"""
quoted = re.sub(r'(\\*)"', lambda m: m.group(1) * 2 + '\\"', tok)
quoted = re.sub(r"(\\+)\Z", lambda m: m.group(1) * 2, quoted)
return f'"{quoted}"'
# Force-quote any token containing a character outside the shlex.quote-style
# safe set: ninja hands POSIX commands to /bin/sh -c, so bare (, ;, <, *, `
# and friends would be re-parsed as shell syntax.
_NEEDS_QUOTE = re.compile(r"[^\w@%+=:,./-]")
def shell_token(tok: str, force: bool = False) -> str:
"""Re-quote a lexed token for the platform shell; ``force`` always quotes.
Single quotes on POSIX (/bin/sh), the argv rule on Windows
(CreateProcess). ``$`` is doubled first because ninja expands it before
the command reaches the shell.
"""
tok = tok.replace("$", "$$") # ninja would expand a bare $ to nothing
if not (force or not tok or _NEEDS_QUOTE.search(tok)):
return tok
# An empty token must become '' / "" or it vanishes from the argv
if os.name == "nt":
return quote_arg(tok)
# shlex.quote's rule; inlined because the $-doubled token must not be
# re-examined for safe characters
return "'" + tok.replace("'", "'\"'\"'") + "'"
def quote_path(value: Path | str) -> str:
"""Force-quote a path for the ninja command line (shell/CreateProcess)."""
return shell_token(str(value), force=True)
+24
View File
@@ -0,0 +1,24 @@
"""The PlatformIO-format size bar shared by the native toolchains."""
from __future__ import annotations
def format_bar(used: int, total: int) -> str:
"""Match PlatformIO's ``_format_availale_bytes`` (sic, pioupload.py) exactly."""
pct_raw = used / total if total else 0
blocks = 10
filled = min(int(round(blocks * pct_raw)), blocks)
progress = "=" * filled
return (
f"[{progress:<{blocks}}] {pct_raw: 6.1%} "
f"(used {used:d} bytes from {total:d} bytes)"
)
def print_size_line(label: str, used: int, total: int) -> None:
"""One PlatformIO-format summary line (``RAM``/``Flash``).
The label padding is part of the format: ``script/ci_memory_impact_extract.py``
matches these lines verbatim.
"""
print(f"{label + ':':<7}{format_bar(used, total)}")
+36
View File
@@ -0,0 +1,36 @@
"""Machine-global tools cache location shared by the native backends."""
from __future__ import annotations
from pathlib import Path
def tools_cache_path(env_var: str, subdir: str) -> Path:
"""A backend's machine-global tools directory, with an env override.
A blank/whitespace override is treated as unset: ``Path("")`` resolves
to the CWD, which ``clean-all`` would then delete.
"""
import platformdirs
from esphome.helpers import get_str_env
if prefix := get_str_env(env_var, "").strip():
# resolve(): symlinked prefixes otherwise trip idf.py's
# venv-mismatch warning on every build
return Path(prefix).expanduser().resolve()
# appauthor=False keeps the Windows path short (no vendor segment);
# deep IDF trees run into MAX_PATH otherwise
return (
Path(platformdirs.user_cache_dir("esphome", appauthor=False)) / subdir
).resolve()
# (env override, cache subdir) per native backend. writer.clean_all wipes
# every entry via tools_cache_path, so listing a cache here is the single
# step that registers it for removal; the backends' own path getters use
# the same named pairs so the two cannot drift.
IDF_TOOLS_CACHE = ("ESPHOME_ESP_IDF_PREFIX", "idf")
SDK_NRF_TOOLS_CACHE = ("ESPHOME_SDK_NRF_PREFIX", "sdk-nrf")
ARDUINO8266_TOOLS_CACHE = ("ESPHOME_ARDUINO8266_PREFIX", "arduino8266")
TOOLS_CACHE_SPECS = (IDF_TOOLS_CACHE, SDK_NRF_TOOLS_CACHE, ARDUINO8266_TOOLS_CACHE)
+37 -6
View File
@@ -20,6 +20,7 @@ from typing import Any
from esphome import const, yaml_util
from esphome.const import (
BUNDLE_EXTENSION,
CONF_ESPHOME,
CONF_EXTERNAL_COMPONENTS,
CONF_INCLUDES,
@@ -29,12 +30,12 @@ from esphome.const import (
CONF_TYPE,
)
from esphome.core import CORE, EsphomeError
from esphome.util import filter_yaml_files
_LOGGER = logging.getLogger(__name__)
DOMAIN = "bundle"
BUNDLE_EXTENSION = ".esphomebundle.tar.gz"
MANIFEST_FILENAME = "manifest.json"
CURRENT_MANIFEST_VERSION = 1
MAX_DECOMPRESSED_SIZE = 500 * 1024 * 1024 # 500 MB
@@ -128,6 +129,9 @@ class BundleData:
"""Files components asked to include, keyed under DOMAIN in CORE.data."""
extra_files: list[Path] = field(default_factory=list)
# Directories whose YAML files are scanned for !secret references but
# never bundled, e.g. git package checkouts the builder re-fetches.
secret_scan_dirs: set[Path] = field(default_factory=set)
# Original config dir parsed from an extracted bundle's manifest.json,
# kept in the path flavor of the machine the bundle was created on.
# The checked flag makes the manifest lookup happen at most once per run;
@@ -155,6 +159,30 @@ def add_bundle_file(path: Path) -> None:
_get_data().extra_files.append(CORE.relative_config_path(path))
def add_secret_scan_dir(path: Path) -> None:
"""Register a directory to scan for ``!secret`` references when bundling.
The directory's files are not added to the bundle. Components call this
for YAML the build consumes without bundling it such as git-fetched
packages, which the builder re-fetches so the secrets those files
reference are still shipped in the filtered secrets file.
A relative path is taken as relative to the config directory.
"""
if not path.is_absolute():
path = CORE.relative_config_path(path)
_get_data().secret_scan_dirs.add(path)
def _secret_scan_yaml_files() -> list[Path]:
"""Return the YAML files inside registered secret-scan directories."""
return filter_yaml_files(
f
for scan_dir in _get_data().secret_scan_dirs
for f in yaml_util.find_files(scan_dir, "*")
)
# Windows paths start with a drive letter or contain backslashes; POSIX
# paths do neither in practice, so this is how the flavor of a recorded
# path string is recognized on any host.
@@ -310,6 +338,7 @@ class ConfigBundleCreator:
yaml_sources = [
bf.source for bf in files if bf.source.suffix in (".yaml", ".yml")
]
yaml_sources.extend(_secret_scan_yaml_files())
used_secret_keys = _find_used_secret_keys(yaml_sources)
filtered_secrets = self._build_filtered_secrets(used_secret_keys)
@@ -394,6 +423,13 @@ class ConfigBundleCreator:
"""
discovered = yaml_util.discover_user_yaml_files(self._config_path)
self._secrets_paths.update(discovered.secrets)
# A !secret inside a file this re-parse does not reach (for example
# a git-fetched package the builder re-fetches) still resolves
# against the config-dir secrets.yaml at build time, so always
# consider that file; filtering no-ops when no key matches.
default_secrets = self._config_dir / yaml_util.SECRET_YAML
if default_secrets.is_file():
self._secrets_paths.add(default_secrets.resolve())
config_resolved = self._config_path.resolve()
for fpath in discovered.files:
if fpath == config_resolved:
@@ -719,11 +755,6 @@ def _validate_tar_members(tar: tarfile.TarFile, target_dir: Path) -> None:
)
def is_bundle_path(path: Path) -> bool:
"""Check if a path looks like a bundle file."""
return path.name.lower().endswith(BUNDLE_EXTENSION)
def _add_bytes_to_tar(tar: tarfile.TarFile, name: str, data: bytes) -> None:
"""Add in-memory bytes to a tar archive with deterministic metadata."""
info = tarfile.TarInfo(name=name)
+5
View File
@@ -25,6 +25,7 @@ from esphome.cpp_generator import ( # noqa: F401
add,
add_build_flag,
add_build_unflag,
add_cmake_arg,
add_cxx_build_flag,
add_define,
add_global,
@@ -49,10 +50,13 @@ from esphome.cpp_helpers import ( # noqa: F401
build_registry_entry,
build_registry_list,
extract_registry_entry_config,
get_slot_count,
gpio_pin_expression,
past_safe_mode,
register_component,
register_parented,
set_setup_priority,
slot_counter,
)
from esphome.cpp_types import ( # noqa: F401
NAN,
@@ -74,6 +78,7 @@ from esphome.cpp_types import ( # noqa: F401
StringRef,
arduino_json_ns,
bool_,
char,
const_char_ptr,
double,
esphome_ns,
+186 -43
View File
@@ -1,26 +1,193 @@
"""Validated-config cache for the upload/logs fast path.
compile dumps the validated config to <data_dir>/storage/<file>.validated.yaml;
compile dumps the validated config to <data_dir>/storage/<file>.validated.json;
the next upload/logs for that YAML reuses it instead of running the full
read_config pipeline. YAML round-trip (yaml_util.dump/load_yaml) keeps
!lambda/!include/IDs/paths intact; mtime gates staleness.
read_config pipeline. The cache is deliberately lossy: only ``!lambda``
bodies survive typed (``Lambda``); IDs, time periods, MAC/IP addresses,
paths, UUIDs and enums store the same string form the YAML dumper
produced for them. JSON additionally coerces non-str dict keys to
strings; validated configs only use string keys (every schema key
validator is ``cv.string``). mtime gates staleness.
"""
from __future__ import annotations
import json
import logging
from pathlib import Path
from typing import Any
from esphome.core import CORE
from esphome.const import __version__ as ESPHOME_VERSION
from esphome.core import CORE, EsphomeError, Lambda
from esphome.helpers import write_file
from esphome.storage_json import StorageJSON, ext_storage_path
from esphome.storage_json import StorageJSON, ext_storage_path, storage_path
from esphome.types import ConfigType
_LOGGER = logging.getLogger(__name__)
# Bump when the on-disk shape changes; a mismatched version falls back
# to read_config. The envelope also stamps the writing esphome version:
# after an upgrade the cache holds the previous release's validation, so
# it falls back once and the re-save self-heals.
_CACHE_VERSION = 1
_LAMBDA_KEY = "__esphome_lambda__"
def compiled_config_path(config_filename: str) -> Path:
"""Path to the cached validated config alongside the storage sidecar."""
return CORE.data_dir / "storage" / f"{config_filename}.validated.json"
def save_compiled_config(config: ConfigType) -> None:
"""Write the validated-config cache. Always-write so mtime stays fresh.
Mode 0600 because config validation resolved !secret inline.
Failures are non-fatal: the fast path falls back to read_config.
"""
try:
# The legacy YAML cache holds inline-resolved secrets and nothing
# reads it anymore; drop it even when the write below fails. A
# failed removal leaves resolved secrets on disk, so it warns.
try:
_legacy_compiled_config_path(CORE.config_filename).unlink(missing_ok=True)
except OSError as err:
_LOGGER.warning(
"Could not remove the legacy validated-config cache: %s", err
)
rendered = json.dumps(
{"v": _CACHE_VERSION, "esphome": ESPHOME_VERSION, "config": config},
separators=(",", ":"),
default=_json_default,
)
write_file(compiled_config_path(CORE.config_filename), rendered, private=True)
except TypeError as err:
# Structural, not transient: this config can never cache (e.g. a
# non-basic dict key), so every upload/logs pays the slow path.
_LOGGER.warning("Cannot cache the validated config: %s", err)
except Exception as err: # noqa: BLE001 # pylint: disable=broad-except
# Likely persistent (permissions, full disk): every upload/logs
# pays the slow path until it clears, so surface it.
_LOGGER.warning("Skipping compiled config cache write: %s", err)
def save_compiled_config_and_sidecar(config: ConfigType) -> None:
"""Refresh the cache from the upload/logs fallback (CORE.config must be set).
The cache is only written when a complete sidecar is on disk:
load_compiled_config can't use it otherwise, and it holds resolved
secrets.
"""
if _refresh_sidecar():
save_compiled_config(config)
def _refresh_sidecar() -> bool:
"""Ensure a complete sidecar is on disk; True when one is.
Writes one (without claiming a build) when missing or wizard-only.
Failures are non-fatal; the next upload/logs pays the slow path again.
"""
try:
path = storage_path()
try:
old = StorageJSON.load_strict(path)
except Exception as err: # noqa: BLE001 # pylint: disable=broad-except
# Present but unreadable: it may hold a real build's metadata,
# and a fresh rewrite would also stop the next compile from
# cleaning a possibly incoherent build tree.
_LOGGER.warning(
"Not caching: storage sidecar %s is unreadable (%s)", path, err
)
return False
if old is not None and old.can_apply_to_core():
if (
old.toolchain is not None
and CORE.toolchain is not None
and old.toolchain != CORE.toolchain.value
):
# Platforms normalize toolchain-sensitive keys differently;
# never cache a config validated under a different toolchain
# than the compile's
_LOGGER.debug(
"Not caching: config validated with toolchain %r but the "
"last compile used %r",
CORE.toolchain.value,
old.toolchain,
)
return False
# Compile-written; nothing to refresh.
return True
if CORE.build_path is not None and CORE.build_path.exists():
# An unvalidated build tree: its absent or mismatched sidecar
# is what makes the next compile wipe it, so don't vouch for
# a build this run never saw.
_LOGGER.warning(
"Not caching: build tree %s has no matching sidecar; "
"'esphome compile' will settle it",
CORE.build_path,
)
return False
new = StorageJSON.from_esphome_core(CORE, old, claim_build=False)
if not new.can_apply_to_core():
_LOGGER.warning("Not caching: rebuilt storage sidecar is still incomplete")
return False
new.save(path)
return True
except (OSError, EsphomeError) as err:
# write_file wraps OSError into EsphomeError. Persistent
# (unwritable storage dir), so surface that every upload/logs
# pays the slow path.
_LOGGER.warning("Could not refresh the storage sidecar: %s", err)
except Exception: # noqa: BLE001 # pylint: disable=broad-except
# A structural bug; keep the traceback so it isn't mistaken
# for the I/O failure above.
_LOGGER.warning(
"Unexpected error refreshing the storage sidecar", exc_info=True
)
return False
def load_compiled_config(conf_path: Path) -> ConfigType | None:
"""Load the cached validated config and apply storage metadata to CORE.
Returns None (caller falls back to read_config) when the cache is
missing, older than the source YAML, unparseable, a different cache
version, or the sidecar is incomplete. The loaded config carries no
source ranges; callers must not feed it into read_config/write_cpp.
"""
cache_path = compiled_config_path(conf_path.name)
if not _cache_is_fresh(cache_path, conf_path):
return None
try:
envelope = json.loads(
cache_path.read_text(encoding="utf-8"), object_hook=_decode_object
)
except (OSError, ValueError) as err:
_LOGGER.debug("Ignoring unreadable compiled config cache: %s", err)
return None
if (
not isinstance(envelope, dict)
or envelope.get("v") != _CACHE_VERSION
or envelope.get("esphome") != ESPHOME_VERSION
or not isinstance(config := envelope.get("config"), dict)
):
_LOGGER.debug("Ignoring compiled config cache with a foreign envelope")
return None
storage = StorageJSON.load(ext_storage_path(conf_path.name))
if storage is None or not storage.can_apply_to_core():
_LOGGER.debug("Ignoring compiled config cache: sidecar missing or incomplete")
return None
storage.apply_to_core()
return config
# Remove before 2027.8: by then every maintained install has saved the
# JSON cache at least once and dropped its legacy YAML file.
def _legacy_compiled_config_path(config_filename: str) -> Path:
"""Path of the pre-JSON YAML cache; only ever removed."""
return CORE.data_dir / "storage" / f"{config_filename}.validated.yaml"
@@ -32,45 +199,21 @@ def _cache_is_fresh(cache_path: Path, source_path: Path) -> bool:
return False
def save_compiled_config(config: ConfigType) -> None:
"""Write the validated-config cache. Always-write so mtime stays fresh.
def _json_default(value: Any) -> Any:
"""Mirror ESPHomeDumper's representers: Lambda stays typed, the rest
stringify (IDs, time periods, MAC/IP addresses, paths, UUIDs, enums).
Mode 0600 because show_secrets=True resolves !secret inline.
Failures are non-fatal: the fast path falls back to read_config.
IncludeFile/Extend/Remove have no JSON mirror and would stringify
wrong, but none survive validation (config.py's packages merge and
the substitution pass consume them) so no guard is spent on them.
"""
from esphome import yaml_util
try:
rendered = yaml_util.dump(config, show_secrets=True)
write_file(compiled_config_path(CORE.config_filename), rendered, private=True)
except Exception as err: # noqa: BLE001 # pylint: disable=broad-except
_LOGGER.debug("Skipping compiled config cache write: %s", err)
if isinstance(value, Lambda):
return {_LAMBDA_KEY: value.value}
return str(value)
def load_compiled_config(conf_path: Path) -> ConfigType | None:
"""Load the cached validated config and apply storage metadata to CORE.
Returns None (caller falls back to read_config) when the cache is
missing, older than the source YAML, unparseable, or the sidecar
is incomplete.
"""
cache_path = compiled_config_path(conf_path.name)
if not _cache_is_fresh(cache_path, conf_path):
return None
from esphome import yaml_util
try:
config = yaml_util.load_yaml(cache_path, clear_secrets=False)
except Exception: # noqa: BLE001 # pylint: disable=broad-except
return None
storage = StorageJSON.load(ext_storage_path(conf_path.name))
if storage is None:
return None
# apply_to_core assumes a real compile wrote the sidecar; wizard-only
# sidecars leave both of these unset and can't drive upload/logs.
if not storage.core_platform and not storage.target_platform:
return None
storage.apply_to_core()
return config
def _decode_object(obj: dict[str, Any]) -> Any:
"""Revive the Lambda sentinel; every other mapping passes through."""
if len(obj) == 1 and isinstance(value := obj.get(_LAMBDA_KEY), str):
return Lambda(value)
return obj
+10
View File
@@ -0,0 +1,10 @@
"""Component alias registry.
Generated by script/build_alias_registry.py - do not edit manually.
See the component-alias section of esphome/loader.py.
"""
# alias -> (canonical component, removal version or None)
COMPONENT_ALIASES: dict[str, tuple[str, str | None]] = {
"rp2040": ("rp2", "2027.7.0"),
}
+2 -1
View File
@@ -6,6 +6,7 @@ from esphome.const import (
STATE_CLASS_MEASUREMENT,
UNIT_METER,
)
from esphome.types import ConfigType
CODEOWNERS = ["@MrSuicideParrot"]
DEPENDENCIES = ["uart"]
@@ -35,7 +36,7 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
+2 -1
View File
@@ -6,6 +6,7 @@ from esphome.const import (
STATE_CLASS_MEASUREMENT,
UNIT_MILLIMETER,
)
from esphome.types import ConfigType
CODEOWNERS = ["@TH-Braemer"]
DEPENDENCIES = ["uart"]
@@ -35,7 +36,7 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
+2 -1
View File
@@ -3,6 +3,7 @@ import esphome.codegen as cg
from esphome.components import stepper
import esphome.config_validation as cv
from esphome.const import CONF_DIR_PIN, CONF_ID, CONF_SLEEP_PIN, CONF_STEP_PIN
from esphome.types import ConfigType
a4988_ns = cg.esphome_ns.namespace("a4988")
A4988 = a4988_ns.class_("A4988", stepper.Stepper, cg.Component)
@@ -17,7 +18,7 @@ CONFIG_SCHEMA = stepper.STEPPER_SCHEMA.extend(
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await stepper.register_stepper(var, config)
@@ -9,6 +9,7 @@ from esphome.const import (
STATE_CLASS_MEASUREMENT,
UNIT_GRAMS_PER_CUBIC_METER,
)
from esphome.types import ConfigType
absolute_humidity_ns = cg.esphome_ns.namespace("absolute_humidity")
AbsoluteHumidityComponent = absolute_humidity_ns.class_(
@@ -43,7 +44,7 @@ CONFIG_SCHEMA = (
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
+8 -1
View File
@@ -4,6 +4,7 @@ from esphome.components import output
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_METHOD, CONF_MIN_POWER
from esphome.core import CORE
from esphome.types import ConfigType
CODEOWNERS = ["@glmnet"]
@@ -48,7 +49,13 @@ CONFIG_SCHEMA = cv.All(
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
if CORE.is_esp32:
from esphome.components.esp32 import include_builtin_idf_component
# Re-enable the gptimer driver (excluded by default to save compile time)
include_builtin_idf_component("esp_driver_gptimer")
if CORE.is_esp8266:
# ac_dimmer uses setTimer1Callback which requires the waveform generator
from esphome.components.esp8266.const import require_waveform
+4 -1
View File
@@ -4,6 +4,9 @@ from esphome.components.light.effects import register_addressable_effect
from esphome.components.light.types import AddressableLightEffect
import esphome.config_validation as cv
from esphome.const import CONF_NAME, CONF_UART_ID
from esphome.core import ID
from esphome.cpp_generator import MockObj
from esphome.types import ConfigType
DEPENDENCIES = ["uart"]
@@ -21,7 +24,7 @@ CONFIG_SCHEMA = cv.Schema({})
"Adalight",
{cv.GenerateID(CONF_UART_ID): cv.use_id(uart.UARTComponent)},
)
async def adalight_light_effect_to_code(config, effect_id):
async def adalight_light_effect_to_code(config: ConfigType, effect_id: ID) -> MockObj:
effect = cg.new_Pvariable(effect_id, config[CONF_NAME])
await uart.register_uart_device(effect, config)
return effect
+5 -1
View File
@@ -1,3 +1,5 @@
from typing import Any
from esphome import pins
import esphome.codegen as cg
from esphome.components.esp32 import (
@@ -16,6 +18,7 @@ from esphome.components.esp32 import (
import esphome.config_validation as cv
from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER, PLATFORM_ESP8266
from esphome.core import CORE
from esphome.types import ConfigType
CODEOWNERS = ["@esphome/core"]
@@ -225,12 +228,13 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
}
def validate_adc_pin(value):
def validate_adc_pin(value: Any) -> ConfigType | str:
if str(value).upper() == "VCC":
if CORE.is_rp2:
return pins.internal_gpio_input_pin_schema(29)
return cv.only_on([PLATFORM_ESP8266])("VCC")
# Deprecated in favour of the `internal_temperature` platform, remove before 2027.2.0
if str(value).upper() == "TEMPERATURE":
return cv.only_on_rp2("TEMPERATURE")
+1 -1
View File
@@ -3,7 +3,7 @@
namespace esphome::adc {
static const char *const TAG = "adc.common";
static const char *const TAG = "adc";
const LogString *sampling_mode_to_str(SamplingMode mode) {
switch (mode) {
+1 -1
View File
@@ -6,7 +6,7 @@
namespace esphome::adc {
static const char *const TAG = "adc.esp32";
static const char *const TAG = "adc";
adc_oneshot_unit_handle_t ADCSensor::shared_adc_handles[2] = {nullptr, nullptr};
@@ -13,7 +13,7 @@ ADC_MODE(ADC_VCC)
namespace esphome::adc {
static const char *const TAG = "adc.esp8266";
static const char *const TAG = "adc";
void ADCSensor::setup() {
#ifndef USE_ADC_SENSOR_VCC
@@ -5,7 +5,7 @@
namespace esphome::adc {
static const char *const TAG = "adc.libretiny";
static const char *const TAG = "adc";
void ADCSensor::setup() {
#ifndef USE_ADC_SENSOR_VCC
+21 -2
View File
@@ -17,7 +17,26 @@
namespace esphome::adc {
static const char *const TAG = "adc.rp2";
static const char *const TAG = "adc";
// The on-die temperature sensor sits on the last ADC channel: input 4 on RP2040
// and RP2350A, but input 8 on RP2350B, which has eight external channels rather
// than four.
//
// This deliberately does not use the SDK's ADC_TEMPERATURE_CHANNEL_NUM. That
// derives from NUM_ADC_CHANNELS, which <pico.h> settles from a board header, and
// arduino-pico supplies a fixed B-die one for every RP2350 build. The real die
// is only declared later, by the variant's pins_arduino.h, so the SDK constant
// reads 8 on A-die boards. PICO_RP2350A itself is correct by the time this file
// is compiled, on both arduino-pico and pico-sdk builds.
#if defined(PICO_RP2350) && !defined(PICO_RP2350A)
#error "PICO_RP2350A is not defined, so the RP2350 die is unknown and the temperature ADC channel cannot be chosen"
#endif
#if defined(PICO_RP2350) && !PICO_RP2350A
static constexpr uint8_t TEMPERATURE_ADC_INPUT = 8;
#else
static constexpr uint8_t TEMPERATURE_ADC_INPUT = 4;
#endif
void ADCSensor::setup() {
static bool initialized = false;
@@ -52,7 +71,7 @@ float ADCSensor::sample() {
if (this->is_temperature_) {
adc_set_temp_sensor_enabled(true);
delay(1);
adc_select_input(4);
adc_select_input(TEMPERATURE_ADC_INPUT);
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
raw = adc_read();
+1 -1
View File
@@ -7,7 +7,7 @@
namespace esphome::adc {
static const char *const TAG = "adc.zephyr";
static const char *const TAG = "adc";
void ADCSensor::setup() {
if (!adc_is_ready_dt(this->channel_)) {
+26 -8
View File
@@ -10,8 +10,8 @@ from esphome.components.esp32 import (
from esphome.components.nrf52.const import AIN_TO_GPIO, EXTRA_ADC
from esphome.components.zephyr import (
zephyr_add_overlay,
zephyr_add_overlay_builder,
zephyr_add_prj_conf,
zephyr_add_user,
)
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
@@ -52,7 +52,7 @@ _attenuation = cv.enum(ATTENUATION_MODES, lower=True)
_sampling_mode = cv.enum(SAMPLING_MODES, lower=True)
def validate_config(config):
def validate_config(config: ConfigType) -> ConfigType:
if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto":
raise cv.Invalid("Automatic attenuation cannot be used when raw output is set")
@@ -67,6 +67,13 @@ def validate_config(config):
# Alter value here so `config` command prints the recommended change
config[CONF_ATTENUATION] = _attenuation("12db")
# Remove before 2027.2.0
if config[CONF_PIN] == "TEMPERATURE":
_LOGGER.warning(
"[adc] `pin: TEMPERATURE` is deprecated, use the `internal_temperature` "
"sensor platform instead. Will be removed in 2027.2.0"
)
return config
@@ -113,7 +120,19 @@ CONFIG_SCHEMA = cv.All(
CONF_ADC_CHANNEL_ID = "adc_channel_id"
async def to_code(config):
def _overlay_io_channels() -> str:
channel_count = CORE.data[CONF_ADC_CHANNEL_ID]
entries = ", ".join(f"<&adc {channel_id}>" for channel_id in range(channel_count))
return f"""
/ {{
zephyr,user {{
io-channels = {entries};
}};
}};
"""
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await sensor.register_sensor(var, config)
@@ -121,6 +140,7 @@ async def to_code(config):
if config[CONF_PIN] == "VCC":
cg.add_define("USE_ADC_SENSOR_VCC")
elif config[CONF_PIN] == "TEMPERATURE":
# Remove before 2027.2.0
cg.add(var.set_is_temperature())
elif not CORE.is_nrf52 or config[CONF_PIN][CONF_NUMBER] not in EXTRA_ADC:
pin = await cg.gpio_pin_expression(config[CONF_PIN])
@@ -173,9 +193,8 @@ async def to_code(config):
if isinstance(pin_number, int):
GPIO_TO_AIN = {v: k for k, v in AIN_TO_GPIO.items()}
pin_number = GPIO_TO_AIN[pin_number]
zephyr_add_user("io-channels", f"<&adc {channel_id}>")
zephyr_add_overlay(
f"""
zephyr_add_overlay_builder(_overlay_io_channels)
zephyr_add_overlay(f"""
&adc {{
#address-cells = <1>;
#size-cells = <0>;
@@ -190,8 +209,7 @@ async def to_code(config):
zephyr,oversampling = <8>;
}};
}};
"""
)
""")
FILTER_SOURCE_FILES = filter_source_files_from_platform(
+2 -1
View File
@@ -2,6 +2,7 @@ import esphome.codegen as cg
from esphome.components import spi
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.types import ConfigType
DEPENDENCIES = ["spi"]
MULTI_CONF = True
@@ -17,7 +18,7 @@ CONFIG_SCHEMA = cv.Schema(
).extend(spi.spi_device_schema(cs_pin_required=True))
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await spi.register_spi_device(var, config)
@@ -2,6 +2,7 @@ import esphome.codegen as cg
from esphome.components import sensor, voltage_sampler
import esphome.config_validation as cv
from esphome.const import CONF_CHANNEL, CONF_ID
from esphome.types import ConfigType
from .. import ADC128S102, adc128s102_ns
@@ -28,7 +29,7 @@ CONFIG_SCHEMA = (
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(
config[CONF_ID],
config[CONF_CHANNEL],
@@ -11,6 +11,7 @@ from esphome.const import (
CONF_UPDATE_INTERVAL,
CONF_WIDTH,
)
from esphome.types import ConfigType
CODEOWNERS = ["@justfalter"]
@@ -38,7 +39,7 @@ CONFIG_SCHEMA = cv.All(
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
wrapped_light = await cg.get_variable(config[CONF_ADDRESSABLE_LIGHT_ID])
cg.add(var.set_width(config[CONF_WIDTH]))
+4 -3
View File
@@ -36,6 +36,7 @@ from esphome.const import (
UNIT_WATT,
UNIT_WATT_HOURS,
)
from esphome.cpp_generator import MockObj
from esphome.types import ConfigType
DEPENDENCIES = ["i2c"]
@@ -243,7 +244,7 @@ CONFIG_SCHEMA = cv.All(
)
async def neutral_channel(config):
async def neutral_channel(config: ConfigType) -> MockObj:
var = cg.new_Pvariable(config[CONF_ID])
current = config[CONF_CURRENT]
@@ -257,7 +258,7 @@ async def neutral_channel(config):
return var
async def power_channel(config):
async def power_channel(config: ConfigType) -> MockObj:
var = cg.new_Pvariable(config[CONF_ID])
for sensor_type in POWER_SENSOR_TYPES:
@@ -280,7 +281,7 @@ async def power_channel(config):
return var
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
+3 -1
View File
@@ -23,6 +23,8 @@ from esphome.const import (
UNIT_VOLT_AMPS_REACTIVE,
UNIT_WATT,
)
from esphome.cpp_generator import MockObj
from esphome.types import ConfigType
CODEOWNERS = ["@angelnu"]
@@ -163,7 +165,7 @@ ADE7953_CONFIG_SCHEMA = cv.Schema(
).extend(cv.polling_component_schema("60s"))
async def register_ade7953(var, config):
async def register_ade7953(var: MockObj, config: ConfigType) -> None:
await cg.register_component(var, config)
if irq_pin_config := config.get(CONF_IRQ_PIN):
+2 -1
View File
@@ -2,6 +2,7 @@ import esphome.codegen as cg
from esphome.components import ade7953_base, i2c
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.types import ConfigType
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["ade7953_base"]
@@ -20,7 +21,7 @@ CONFIG_SCHEMA = (
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await i2c.register_i2c_device(var, config)
await ade7953_base.register_ade7953(var, config)
+2 -1
View File
@@ -2,6 +2,7 @@ import esphome.codegen as cg
from esphome.components import ade7953_base, spi
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.types import ConfigType
DEPENDENCIES = ["spi"]
AUTO_LOAD = ["ade7953_base"]
@@ -20,7 +21,7 @@ CONFIG_SCHEMA = (
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await spi.register_spi_device(var, config)
await ade7953_base.register_ade7953(var, config)
+2 -1
View File
@@ -2,6 +2,7 @@ import esphome.codegen as cg
from esphome.components import i2c
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.types import ConfigType
DEPENDENCIES = ["i2c"]
MULTI_CONF = True
@@ -24,7 +25,7 @@ CONFIG_SCHEMA = (
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
@@ -11,6 +11,7 @@ from esphome.const import (
STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
)
from esphome.types import ConfigType
from .. import CONF_ADS1115_ID, ADS1115Component, ads1115_ns
@@ -86,7 +87,7 @@ CONFIG_SCHEMA = (
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await sensor.register_sensor(var, config)
await cg.register_component(var, config)
+2 -1
View File
@@ -2,6 +2,7 @@ import esphome.codegen as cg
from esphome.components import spi
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.types import ConfigType
CODEOWNERS = ["@solomondg1"]
DEPENDENCIES = ["spi"]
@@ -23,7 +24,7 @@ CONFIG_SCHEMA = (
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await spi.register_spi_device(var, config)
@@ -11,6 +11,7 @@ from esphome.const import (
UNIT_CELSIUS,
UNIT_VOLT,
)
from esphome.types import ConfigType
from .. import ADS1118, CONF_ADS1118_ID, ads1118_ns
@@ -86,7 +87,7 @@ CONFIG_SCHEMA = cv.typed_schema(
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
await cg.register_parented(var, config[CONF_ADS1118_ID])
+16 -3
View File
@@ -17,6 +17,9 @@ from esphome.const import (
UNIT_OHM,
UNIT_PARTS_PER_BILLION,
)
from esphome.core import ID
from esphome.cpp_generator import MockObj, TemplateArgsType
from esphome.types import ConfigType
CONF_RESISTANCE = "resistance"
@@ -62,7 +65,7 @@ CONFIG_SCHEMA = (
FINAL_VALIDATE_SCHEMA = i2c.final_validate_device_schema("ags10", max_frequency="15khz")
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
@@ -94,7 +97,12 @@ AGS10_NEW_I2C_ADDRESS_SCHEMA = cv.maybe_simple_value(
AGS10_NEW_I2C_ADDRESS_SCHEMA,
synchronous=True,
)
async def ags10newi2caddress_to_code(config, action_id, template_arg, args):
async def ags10newi2caddress_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
address = await cg.templatable(config[CONF_ADDRESS], args, cg.uint8)
@@ -126,7 +134,12 @@ AGS10_SET_ZERO_POINT_SCHEMA = cv.Schema(
AGS10_SET_ZERO_POINT_SCHEMA,
synchronous=True,
)
async def ags10setzeropoint_to_code(config, action_id, template_arg, args):
async def ags10setzeropoint_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
mode = await cg.templatable(
+2 -1
View File
@@ -12,6 +12,7 @@ from esphome.const import (
UNIT_CELSIUS,
UNIT_PERCENT,
)
from esphome.types import ConfigType
DEPENDENCIES = ["i2c"]
@@ -50,7 +51,7 @@ CONFIG_SCHEMA = (
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
+10 -2
View File
@@ -4,6 +4,9 @@ from esphome.components import i2c
from esphome.components.audio_dac import AudioDac
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_MODE
from esphome.core import ID
from esphome.cpp_generator import MockObj, TemplateArgsType
from esphome.types import ConfigType
CODEOWNERS = ["@kbx81"]
DEPENDENCIES = ["i2c"]
@@ -39,7 +42,12 @@ SET_AUTO_MUTE_ACTION_SCHEMA = cv.maybe_simple_value(
SET_AUTO_MUTE_ACTION_SCHEMA,
synchronous=True,
)
async def aic3204_set_volume_to_code(config, action_id, template_arg, args):
async def aic3204_set_volume_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
@@ -49,7 +57,7 @@ async def aic3204_set_volume_to_code(config, action_id, template_arg, args):
return var
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
+14 -10
View File
@@ -1,23 +1,27 @@
import esphome.codegen as cg
from esphome.components import esp32_ble_tracker
from esphome.components import ble_device_base
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.types import ConfigType
DEPENDENCIES = ["esp32_ble_tracker"]
AUTO_LOAD = ["ble_device_base"]
CODEOWNERS = ["@jeromelaban"]
airthings_ble_ns = cg.esphome_ns.namespace("airthings_ble")
AirthingsListener = airthings_ble_ns.class_(
"AirthingsListener", esp32_ble_tracker.ESPBTDeviceListener
"AirthingsListener", ble_device_base.ESPBTDeviceListener
)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(AirthingsListener),
}
).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
CONFIG_SCHEMA = cv.All(
ble_device_base.rename_legacy_hub_id("airthings_ble"),
cv.Schema(
{
cv.GenerateID(): cv.declare_id(AirthingsListener),
}
).extend(ble_device_base.BLE_DEVICE_SCHEMA),
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await esp32_ble_tracker.register_ble_device(var, config)
await ble_device_base.register_ble_device(var, config)
@@ -2,15 +2,13 @@
#include "esphome/core/log.h"
#include <cinttypes>
#ifdef USE_ESP32
namespace esphome::airthings_ble {
static const char *const TAG = "airthings_ble";
bool AirthingsListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
bool AirthingsListener::parse_device(const ble_device_base::ESPBTDevice &device) {
for (auto &it : device.get_manufacturer_datas()) {
if (it.uuid == esp32_ble_tracker::ESPBTUUID::from_uint32(0x0334)) {
if (it.uuid == ble_device_base::ESPBTUUID::from_uint32(0x0334)) {
if (it.data.size() < 4)
continue;
@@ -29,5 +27,3 @@ bool AirthingsListener::parse_device(const esp32_ble_tracker::ESPBTDevice &devic
}
} // namespace esphome::airthings_ble
#endif
@@ -1,17 +1,13 @@
#pragma once
#ifdef USE_ESP32
#include "esphome/core/component.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/ble_device_base/ble_device.h"
namespace esphome::airthings_ble {
class AirthingsListener final : public esp32_ble_tracker::ESPBTDeviceListener {
class AirthingsListener final : public ble_device_base::ESPBTDeviceListener {
public:
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
bool parse_device(const ble_device_base::ESPBTDevice &device) override;
};
} // namespace esphome::airthings_ble
#endif
@@ -20,6 +20,8 @@ from esphome.const import (
UNIT_PERCENT,
UNIT_VOLT,
)
from esphome.cpp_generator import MockObj
from esphome.types import ConfigType
CODEOWNERS = ["@ncareau", "@jeromelaban"]
@@ -78,7 +80,7 @@ BASE_SCHEMA = (
)
async def wave_base_to_code(var, config):
async def wave_base_to_code(var: MockObj, config: ConfigType) -> None:
await cg.register_component(var, config)
await ble_client.register_ble_node(var, config)
@@ -2,6 +2,7 @@ import esphome.codegen as cg
from esphome.components import airthings_wave_base
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.types import ConfigType
DEPENDENCIES = airthings_wave_base.DEPENDENCIES
@@ -20,6 +21,6 @@ CONFIG_SCHEMA = airthings_wave_base.BASE_SCHEMA.extend(
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await airthings_wave_base.wave_base_to_code(var, config)
@@ -83,7 +83,7 @@ CONFIG_SCHEMA = cv.All(
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await airthings_wave_base.wave_base_to_code(var, config)
+2 -1
View File
@@ -20,6 +20,7 @@ from esphome.const import (
UNIT_VOLT,
UNIT_WATT,
)
from esphome.types import ConfigType
alpha3_ns = cg.esphome_ns.namespace("alpha3")
Alpha3 = alpha3_ns.class_("Alpha3", ble_client.BLEClientNode, cg.PollingComponent)
@@ -68,7 +69,7 @@ CONFIG_SCHEMA = (
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await ble_client.register_ble_node(var, config)
+2 -1
View File
@@ -11,6 +11,7 @@ from esphome.const import (
UNIT_CELSIUS,
UNIT_PERCENT,
)
from esphome.types import ConfigType
DEPENDENCIES = ["i2c"]
@@ -40,7 +41,7 @@ CONFIG_SCHEMA = (
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
+2 -1
View File
@@ -11,6 +11,7 @@ from esphome.const import (
UNIT_CELSIUS,
UNIT_PERCENT,
)
from esphome.types import ConfigType
DEPENDENCIES = ["i2c"]
@@ -42,7 +43,7 @@ CONFIG_SCHEMA = (
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
+2 -1
View File
@@ -2,6 +2,7 @@ import esphome.codegen as cg
from esphome.components import ble_client, cover
import esphome.config_validation as cv
from esphome.const import CONF_PIN
from esphome.types import ConfigType
CODEOWNERS = ["@buxtronix"]
DEPENDENCIES = ["ble_client"]
@@ -27,7 +28,7 @@ CONFIG_SCHEMA = (
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = await cover.new_cover(config)
cg.add(var.set_pin(config[CONF_PIN]))
cg.add(var.set_invert_position(config[CONF_INVERT_POSITION]))
+2 -1
View File
@@ -11,6 +11,7 @@ from esphome.const import (
STATE_CLASS_MEASUREMENT,
UNIT_PERCENT,
)
from esphome.types import ConfigType
AUTO_LOAD = ["am43"]
CODEOWNERS = ["@buxtronix"]
@@ -42,7 +43,7 @@ CONFIG_SCHEMA = (
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await ble_client.register_ble_node(var, config)
@@ -2,6 +2,7 @@ import esphome.codegen as cg
from esphome.components import binary_sensor, sensor
import esphome.config_validation as cv
from esphome.const import CONF_SENSOR_ID, CONF_THRESHOLD
from esphome.types import ConfigType
analog_threshold_ns = cg.esphome_ns.namespace("analog_threshold")
@@ -32,7 +33,7 @@ CONFIG_SCHEMA = (
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = await binary_sensor.new_binary_sensor(config)
await cg.register_component(var, config)
+5
View File
@@ -13,8 +13,13 @@
import esphome.components.image as espImage
import esphome.config_validation as cv
from . import image as animation_image
from .image import ANIMATION_CONFIG_SCHEMA, setup_animation
# The deprecated top-level `animation:` shim gets the same batched
# downloads as the `image:` platform form.
PREFETCH_FILES = animation_image.PREFETCH_FILES
AUTO_LOAD = ["image", "file"]
CODEOWNERS = ["@syndlex"]
DEPENDENCIES = ["display"]
+13 -1
View File
@@ -1,13 +1,20 @@
from esphome import automation
import esphome.codegen as cg
from esphome.components.const import CONF_LOOP
from esphome.components.file import image as file_image
from esphome.components.file.image import image_schema, write_image
from esphome.components.image import Image_, validate_settings
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_REPEAT
from esphome.core import ID
from esphome.cpp_generator import MockObj, TemplateArgsType
from esphome.types import ConfigType
CODEOWNERS = ["@syndlex"]
# The animation platform shares the file platform's remote file handling,
# including its batch-download hook.
PREFETCH_FILES = file_image.PREFETCH_FILES
AUTO_LOAD = ["file"]
DEPENDENCIES = ["display"]
@@ -74,7 +81,12 @@ SET_FRAME_SCHEMA = cv.Schema(
@automation.register_action(
"animation.set_frame", SetFrameAction, SET_FRAME_SCHEMA, synchronous=True
)
async def animation_action_to_code(config, action_id, template_arg, args):
async def animation_action_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
+2 -1
View File
@@ -2,6 +2,7 @@ import esphome.codegen as cg
from esphome.components import ble_client, climate
import esphome.config_validation as cv
from esphome.const import CONF_UNIT_OF_MEASUREMENT
from esphome.types import ConfigType
UNITS = {
"f": "f",
@@ -28,7 +29,7 @@ CONFIG_SCHEMA = (
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = await climate.new_climate(config)
await cg.register_component(var, config)
await ble_client.register_ble_node(var, config)
+6 -2
View File
@@ -1,6 +1,8 @@
# Based on this datasheet:
# https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf
from typing import Any
import esphome.codegen as cg
from esphome.components import i2c, sensor
import esphome.config_validation as cv
@@ -11,6 +13,8 @@ from esphome.const import (
STATE_CLASS_MEASUREMENT,
UNIT_LUX,
)
from esphome.cpp_generator import MockObj
from esphome.types import ConfigType
DEPENDENCIES = ["i2c"]
@@ -55,7 +59,7 @@ AMBIENT_LIGHT_GAINS = {
}
def _validate_measurement_rate(value):
def _validate_measurement_rate(value: Any) -> MockObj:
value = cv.positive_time_period_milliseconds(value)
return cv.enum(MEASUREMENT_RATES, int=True)(value.total_milliseconds)
@@ -85,7 +89,7 @@ CONFIG_SCHEMA = (
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
+2 -1
View File
@@ -2,6 +2,7 @@ import esphome.codegen as cg
from esphome.components import i2c
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.types import ConfigType
DEPENDENCIES = ["i2c"]
MULTI_CONF = True
@@ -57,7 +58,7 @@ CONFIG_SCHEMA = (
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)

Some files were not shown because too many files have changed in this diff Show More