Compare commits

...
Author SHA1 Message Date
Tomasz Duda bfb473e1e5 - Replace flat CORE.data key with DOMAIN + ZephyrPWMData dataclass via _get_data()
- Store period_ns: int on PWMBlock instead of frequency: float, eliminating float equality comparison for block grouping and the conversion in to_code
  - Replace hardcoded "zephyr_pwm" platform filter string with DOMAIN
  - Add _pin_schema to reject allow_other_uses: true, letting the pin registry catch duplicate pins automatically
  - Fix PWMBlock.pins type from list[Any] to list[int]
2026-08-02 18:18:05 +02:00
Tomasz Duda 0e2496a49a Merge remote-tracking branch 'origin/dev' into feat/nrf52_pwm 2026-08-02 16:00:00 +02:00
esphome[bot]andGitHub 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 SwobodaandGitHub 5821915aad [heatpumpir] Expose configured min/max as the visual temperature range (#17985) 2026-08-01 12:37:44 -04:00
esphome[bot]andGitHub fae2f7a11d Bump bundled esphome-device-builder to 1.8.2 (#17995) 2026-08-01 08:52:42 -05:00
Keith BurzinskiandGitHub 1df2759db6 [nextion] Reconnect stale HTTP connection during TFT upload (#17824) 2026-07-31 23:05:45 -05:00
Keith BurzinskiandGitHub ce9b221be4 [serial_proxy] Restrict port operations to the subscriber and harden subscription handling (#17796) 2026-07-31 23:05:06 -05:00
Edvard FilistovičandGitHub e50fae3f46 [ln882h_ble] BLE controller support for LN882H (#17777) 2026-07-31 22:40:57 +00:00
ArdumineandGitHub 15711f8112 [openthread_info] Add more sensors for Openthread info (#17276) 2026-07-31 12:13:44 -04:00
Jesse HillsandGitHub 7ad662781d [ci] Order jobs to follow the CI flow and close ci-status gaps (#17979) 2026-07-31 07:53:59 -04:00
TesseractTimmeeGitHubrootpre-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 HillsandGitHub 40b4cd8e9b [ci] Drop the unused Codecov upload token (#17977) 2026-07-31 06:32:33 -04:00
Jesse HillsandGitHub e11a295680 [ci] Report an empty Codecov upload when pytest is skipped (#17976) 2026-07-31 16:27:05 +12:00
Jesse HillsandGitHub f9cd789f7c [ci] Tell stacked pull requests apart from hand-built chains (#17980) 2026-07-31 15:39:36 +12:00
Jesse HillsandGitHub 80c17fb2cd [ci] Authenticate the stale workflow as esphome[bot] (#17974) 2026-07-31 13:52:01 +12:00
J. Nick KostonandGitHub fb21320826 [api] Fix double free when overflow buffer drain is re-entered (#17969) 2026-07-30 14:48:17 -10:00
Jesse HillsandGitHub 3899429cfa [esp32] Restrict toolchain validation to supported values (#17972) 2026-07-30 20:34:44 -04:00
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 KostonandGitHub 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 KostonandGitHub 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
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 SwobodaandGitHub 60662830dc [ci] Switch stale to the shared real-activity workflow (#17960) 2026-07-30 15:41:10 -04:00
esphome[bot]andGitHub 33ccaf8a85 Bump bundled esphome-device-builder to 1.8.1 (#17964) 2026-07-30 08:54:39 -10:00
Pieter ViljoenandGitHub d64e1ebba3 [ble_client] Do not walk the GATT cache after releasing services (#17919) 2026-07-30 08:31:32 -10:00
esphome[bot]GitHubesphome[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 HillsandGitHub c23a107a87 [config_validation] Add underscore/hyphen normalization to one_of (#17792) 2026-07-30 18:36:28 +12:00
Kevin AhrendtandGitHub 4224d90567 [runtime_image] Allow decoding into a caller-owned buffer (#17936) 2026-07-29 23:32:21 -04:00
Jonathan SwobodaandGitHub 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 SwobodaandGitHub be78b8011d [esp32] Add platformio toolchain deprecation warning (#17947) 2026-07-29 17:17:39 -04:00
dependabot[bot]GitHubdependabot[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
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]GitHubdependabot[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]GitHubdependabot[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 KostonandGitHub ca1f89e500 [ci] Enable the ruff rule requiring explicit encoding on text file I/O (#17897) 2026-07-29 13:44:39 -04:00
Kevin AhrendtandGitHub 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 BurzinskiGitHubpre-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
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 FodorandGitHub 363a91c185 [epaper_spi] Add Inkplate 6COLOR support (#17717) 2026-07-30 00:31:22 +10:00
Clyde StubbsandGitHub 4209f242e5 [mipi_spi] Add Waveshare ESP32-S3-Touch-LCD-3.5B (#17513) 2026-07-28 23:18:49 -04:00
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 HarveyandGitHub 4c571c3ec7 [sen6x] Rename voc/nox sensor keys to voc_index/nox_index (#17725) 2026-07-28 22:35:53 -04:00
Brandon HarveyandGitHub a9df82fa0a [sen5x] Rename voc/nox sensor keys to voc_index/nox_index (#17724) 2026-07-28 22:35:34 -04:00
Brandon HarveyandGitHub 6ad804fbc5 [sgp4x] Rename voc/nox sensor keys to voc_index/nox_index (#17723) 2026-07-28 22:35:07 -04:00
Brandon HarveyandGitHub 227f1d3842 [core] Reject configs that set both keys in cv.rename_key (#17916) 2026-07-28 22:32:59 -04:00
dependabot[bot]GitHubdependabot[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 KostonandGitHub 324e222bd2 [improv_serial] Reduce per-loop overhead (#16019) 2026-07-28 15:38:29 -04:00
dependabot[bot]GitHubdependabot[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 HikriandGitHub 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čandGitHub 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 HillsandGitHub 985a08e247 Merge pull request #17905 from esphome/bump-2026.7.3
2026.7.3
2026-07-28 22:43:16 +12:00
Edvard FilistovičandGitHub 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 StubbsandJesse Hills 71349a6feb [light] Ensure binary light is off with brightness 0 (#17893) 2026-07-28 21:32:51 +12:00
Jonathan SwobodaandJesse Hills 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 KostonandJesse Hills 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 KostonandJesse Hills 5a87ad8fc0 [wifi] Lock scan results shared with the captive portal web task (#17850) 2026-07-28 21:32:51 +12:00
esphome[bot]andJesse Hills e40579ad93 Bump bundled esphome-device-builder to 1.7.0 (#17871) 2026-07-28 21:32:51 +12:00
Rui MarinhoandJesse Hills 37fe59dc37 [espidf] Include .cc, .cxx and .c++ sources in the app source glob (#17754) 2026-07-28 21:32:51 +12:00
Jonathan SwobodaandJesse Hills 3829d368ff [espidf] Honor compile_process_limit in native ESP-IDF builds (#17857) 2026-07-28 21:32:42 +12:00
Marcus VoßandJesse Hills 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]andJesse Hills e38ae51de2 Bump bundled esphome-device-builder to 1.6.10 (#17801) 2026-07-28 21:29:28 +12:00
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čandGitHub bb8ffac269 [const] Move CONF_SCAN_PARAMETERS and CONF_WINDOW to components/const (#17900) 2026-07-27 21:19:17 -10:00
Edvard FilistovičandGitHub 10303b3fa7 [ble_device_base] Add mac_lsb_first_to_uint64 address-packing helper (#17901) 2026-07-27 21:17:40 -10:00
Jesse HillsandGitHub 93eec5c961 [ci] Attribute device class sync commits to the esphome[bot] account (#17898) 2026-07-28 16:19:00 +12:00
J. Nick KostonandGitHub 16930c4e2c [ci] Read the GitHub event file as UTF-8 (#17896) 2026-07-27 17:33:08 -10:00
Edvard FilistovičandGitHub c2476911a3 [bluetooth_proxy] Answer UNPAIR with BluetoothDeviceUnpairingResponse (#17895) 2026-07-27 17:14:37 -10:00
Clyde StubbsandGitHub 98d99fb9fd [light] Ensure binary light is off with brightness 0 (#17893) 2026-07-28 14:19:44 +12:00
Jesse HillsandGitHub ce6c122449 [core] Move JSON string escaping into helpers (#17879) 2026-07-27 22:16:57 -04:00
Brandon HarveyandGitHub f1f8b102e2 [core] Add optional deprecation warning to cv.rename_key (#17740) 2026-07-27 22:10:34 -04:00
Egor VorontsovandGitHub 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čandGitHub 01616c4f30 [bk72xx_ble_tracker] BLE 5.x scanner for BK72xx (#17135) 2026-07-27 11:58:29 -10:00
J. Nick KostonGitHubcopilot-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 SwobodaandGitHub 6420de53f0 [ethernet] Set SPI CS hold time for ENC28J60 (#17885) 2026-07-27 17:24:53 -04:00
J. Nick KostonGitHubcopilot-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
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
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 HillsandGitHub 56028d0932 [safe_mode] Prevent OTA rollback when entering deep sleep (#17699) 2026-07-27 16:30:52 -04:00
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
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]GitHubdependabot[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 KostonandGitHub c395ea00cc [libretiny] Speed up builds with ccache when available (#17726) 2026-07-27 18:54:32 +00:00
J. Nick KostonandGitHub 89654fea66 [rp2] Speed up builds with ccache when available (#17727) 2026-07-27 18:52:29 +00:00
J. Nick KostonandGitHub 700c0b0460 [esp8266] Speed up builds with ccache when available (#17722) 2026-07-27 08:42:52 -10:00
J. Nick KostonandGitHub c0aa121c3d [espidf] Install only the toolchains for the variants being built (#17688) 2026-07-27 08:39:03 -10:00
dependabot[bot]GitHubdependabot[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]GitHubdependabot[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]GitHubdependabot[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
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 HillsandGitHub a930baab7c [captive_portal] Escape SSID when building config JSON (#17872) 2026-07-27 19:42:44 +12:00
J. Nick KostonandGitHub 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 KostonandGitHub 8f321bf346 [wifi] Lock scan results shared with the captive portal web task (#17850) 2026-07-27 17:31:24 +12:00
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
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]andGitHub b395a87cad Bump bundled esphome-device-builder to 1.7.0 (#17871) 2026-07-26 13:13:55 -10:00
Jonathan SwobodaandGitHub 8a7d2d0ca0 [ci] Enforce list form for platform domains in test fixtures (#17869) 2026-07-26 19:01:15 -04:00
Rui MarinhoandGitHub ed066cf0fe [espidf] Include .cc, .cxx and .c++ sources in the app source glob (#17754) 2026-07-27 10:34:29 +12:00
Jonathan SwobodaandGitHub 741fbbff5a [dallas_temp][ds2484] Fix one_wire test configs for grouped CI batches (#17868) 2026-07-26 17:49:57 -04:00
Edvard FilistovičandGitHub cf17749307 [ble_device_base] Shared address-type string and discovered-device log (#17861) 2026-07-26 00:49:29 -10:00
Bonne EgglestonandGitHub 98f2a0b4a4 [modbus] Typed send helpers; migrate in-tree callers off send() (#17859) 2026-07-25 21:57:35 -10:00
J. Nick KostonGitHubcopilot-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
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
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 KostonandGitHub e0644ab20a [esp32] Capture fault address and raw cause in crash handler (#17769) 2026-07-25 18:12:53 -10:00
dependabot[bot]GitHubdependabot[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]GitHubdependabot[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 SwobodaandGitHub 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
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
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 GammonandGitHub 58385bebff [mipi_spi] Add ST77916 / ESP-VoCat model (#17679) 2026-07-25 02:57:17 +00:00
Michael PöttgenandGitHub eb7575f59e [gsl3670] Add a model configuration for the Guition JC8012P4A1 (#17843) 2026-07-25 11:33:17 +10:00
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
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
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ßandGitHub 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
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]andGitHub f0800336b8 Bump bundled esphome-device-builder to 1.6.10 (#17801) 2026-07-23 00:02:31 -10:00
Edvard FilistovičandGitHub 33fa9f5069 [bk72xx_ble] BLE controller support for BK72xx (BLE 5.x) (#17775) 2026-07-23 08:54:30 +00:00
StasGitHubKeith 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 HillsandGitHub 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
Jesse HillsandGitHub b832e135cb Merge pull request #17794 from esphome/bump-2026.7.2
2026.7.2
2026-07-23 14:41:52 +12:00
Jesse Hills 66615a24a7 Bump version to 2026.7.2 2026-07-23 13:55:39 +12:00
esphome[bot]andJesse Hills 52ace9c448 Bump bundled esphome-device-builder to 1.6.9 (#17793) 2026-07-23 13:55:39 +12:00
J. Nick KostonandJesse Hills e1cedaba87 [bundle] Remap absolute file paths when compiling an extracted bundle (#17765) 2026-07-23 13:55:38 +12:00
f7bf4e9727 [mipi_spi] fix partial update bug (#17747)
Co-authored-by: clyde <2366188+clydebarrow@users.noreply.github.com>
2026-07-23 13:55:38 +12:00
Kevin AhrendtandJesse Hills e46bdf9e18 [sendspin] Bump sendspin-cpp to v0.7.0 (#17781) 2026-07-23 13:55:38 +12:00
Brandon HarveyandJesse Hills a08070a836 [sen5x] Add model option to override autodetection (#17764) 2026-07-23 13:55:38 +12:00
dependabot[bot]andJesse Hills 18f93c0e41 Bump aioesphomeapi from 45.6.2 to 45.7.0 (#17768)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-23 13:55:38 +12:00
rwalker777GitHubpre-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]andGitHub 4817904637 Bump bundled esphome-device-builder to 1.6.9 (#17793) 2026-07-22 15:49:38 -10:00
J. Nick KostonandGitHub 98c6fa294a [bundle] Remap absolute file paths when compiling an extracted bundle (#17765) 2026-07-23 11:28:22 +12:00
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 AhrendtandGitHub 5dfce9b90c [sendspin] Bump sendspin-cpp to v0.7.0 (#17781) 2026-07-22 08:41:33 -10:00
Brandon HarveyandGitHub 69085a7a42 [sen5x] Add model option to override autodetection (#17764) 2026-07-21 13:18:21 -10:00
dependabot[bot]andGitHub 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]andGitHub 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]andGitHub 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]andGitHub 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]andGitHub 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čandGitHub 4d3b4659d7 [ble_device_base] Platform-neutral BLE layer; esp32_ble_tracker implements BLEHub (#17150) 2026-07-21 12:32:14 -10:00
J. Nick KostonandGitHub 37be04f58e [core] Reduce memory footprint of esphome upload (#17684) 2026-07-21 12:21:12 -10:00
dependabot[bot]andGitHub 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]andGitHub 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]andGitHub 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]andGitHub 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]andGitHub 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
Jesse HillsandGitHub bbc5669efa Merge pull request #17739 from esphome/bump-2026.7.1
2026.7.1
2026-07-21 10:44:34 +12:00
Guanzhong ChenandJesse Hills 718b04c15a [zephyr] implement ISRInternalGPIOPin::digital_write (#17601) 2026-07-21 09:27:55 +12:00
Jonathan SwobodaandJesse Hills 24a8634a4b [haier] Fix outdoor defrost temperature reporting the coil temperature (#17492) 2026-07-21 09:27:55 +12:00
Jonathan SwobodaandJesse Hills fc6664d737 [emc2101] Fix negative external temperatures reported as large positives (#17494) 2026-07-21 09:27:55 +12:00
dependabot[bot]andJesse Hills 388e411469 Bump aioesphomeapi from 45.6.1 to 45.6.2 (#17654)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-21 09:17:18 +12:00
Jesse Hills a8ebdcb8f2 Bump version to 2026.7.1 2026-07-21 08:25:51 +12:00
J. Nick KostonandJesse Hills f5f1c48f51 [esp8266] Fail fast when Rosetta 2 is missing on Apple Silicon Macs (#17737) 2026-07-21 08:25:51 +12:00
J. Nick KostonandJesse Hills 7c55de311f [wireguard] Mark private keys sensitive, stop redacting public keys (#17736) 2026-07-21 08:25:46 +12:00
J. Nick KostonandJesse Hills 307faa6c38 [espidf] Resume interrupted toolchain downloads instead of restarting (#17706) 2026-07-21 08:18:16 +12:00
J. Nick KostonandJesse Hills 5f2adcf9b3 [platformio] Include cache path in invalid library error (#17692) 2026-07-21 08:18:16 +12:00
7738464f0b [http_request] Fix usage of http response body (#17713)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-07-21 08:18:16 +12:00
J. Nick KostonandJesse Hills 5a86e26f68 [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-21 08:18:16 +12:00
J. Nick KostonJesse Hillspre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
786b47d8c2 [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-21 08:18:15 +12:00
esphome[bot]andJesse Hills 83092ea05c Bump bundled esphome-device-builder to 1.6.8 (#17708) 2026-07-21 08:18:15 +12:00
629afd38f6 [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-21 08:18:15 +12:00
J. Nick KostonandJesse Hills 3ffc3a9610 [espidf] Make openocd-esp32 optional so its libusb check cannot break installs (#17686) 2026-07-21 08:18:15 +12:00
J. Nick KostonandJesse Hills 9de7bd7461 [git] Detect interrupted clones and re-clone automatically (#17690) 2026-07-21 08:18:15 +12:00
J. Nick KostonandJesse Hills 5df922e0df [platformio] Accept git URLs passed as the library name (#17697) 2026-07-21 08:18:15 +12:00
J. Nick KostonandJesse Hills b571d2a5ab [micro_wake_word] Download models in parallel (#17701) 2026-07-21 08:18:15 +12:00
Jesse Hills 231a2897c0 [ssd1306] Fix offset_x being ignored on SH1106/SH1107 displays (#17700) 2026-07-21 08:18:15 +12:00
Brandon HarveyandJesse Hills 2223b14794 Split multi-token build.flags entries when generating ESP-IDF component CMakeLists (#17649) 2026-07-21 08:18:15 +12:00
esphome[bot]andJesse Hills 3d340f4d90 Bump bundled esphome-device-builder to 1.6.7 (#17696) 2026-07-21 08:18:15 +12:00
esphome[bot]andJesse Hills 2f99466f3a Bump bundled esphome-device-builder to 1.6.6 (#17681) 2026-07-21 08:18:15 +12:00
esphome[bot]andJesse Hills f05e15522c Bump bundled esphome-device-builder to 1.6.5 (#17675) 2026-07-21 08:18:15 +12:00
Jonathan SwobodaandJesse Hills f37dad683b [esp32] Bump recommended ESP-IDF to 5.5.5 and Arduino to 3.3.10 (#17669) 2026-07-21 08:18:15 +12:00
esphome[bot]andJesse Hills a0fb14bf54 Bump bundled esphome-device-builder to 1.6.4 (#17662) 2026-07-21 08:18:15 +12:00
Jesse Hills d4443be0c1 [nrf52] Install PlatformIO toolchain Python packages into a dedicated venv (#17635) 2026-07-21 08:18:15 +12:00
J. Nick KostonandJesse Hills 0dfb573e18 [logs] Cap the logs reconnect backoff for deep-sleep devices (#17656) 2026-07-21 08:18:15 +12:00
J. Nick KostonandJesse Hills 572fc033cd Ship component requirements.txt files in the sdist and wheel (#17660) 2026-07-21 08:18:15 +12:00
J. Nick KostonandJesse Hills 4062f0a323 Pin cryptography to 48.0.1 on Intel macOS (#17658) 2026-07-21 08:18:15 +12:00
Brandon HarveyandJesse Hills cd40fb1c68 [core] Fix srcFilter exclusions being silently ignored on Windows (#17648) 2026-07-21 08:18:15 +12:00
Jonathan SwobodaandJesse Hills 7afe7750cd [espidf] Suggest installing missing system libraries when the tools install fails (#17619) 2026-07-21 08:18:15 +12:00
esphome[bot]andJesse Hills e08bdf8cca Bump bundled esphome-device-builder to 1.6.3 (#17651) 2026-07-21 08:18:15 +12:00
dependabot[bot]andJesse Hills b3172ecae8 Bump aioesphomeapi from 45.6.0 to 45.6.1 (#17653)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-21 08:18:14 +12:00
esphome[bot]Jesse Hillsesphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2b4d9c0af9 Bump bundled esphome-device-builder to 1.6.2 (#17640)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-07-21 08:18:14 +12:00
Jesse Hills 3a10d2c187 [nrf52] Set ZEPHYR_SDK_INSTALL_DIR for Zephyr SDK discovery (#17633) 2026-07-21 08:18:14 +12:00
Jonathan SwobodaandJesse Hills 05e2c6b133 [web_server_idf] Use core format_hex_to helper for digest auth (fixes Arduino build) (#17608) 2026-07-21 08:18:14 +12:00
Jonathan SwobodaandJesse Hills 2797349c75 [http_request] Fix use-after-return of header collection state in IDF backend (#17627) 2026-07-21 08:18:14 +12:00
Jonathan SwobodaandJesse Hills 95a01ac2ab [core] Improve framework mirror selection, download errors, and version parsing (#17615) 2026-07-21 08:18:14 +12:00
J. Nick KostonandJesse Hills ea01c909b7 [micro_wake_word] Include the local model file in bundles (#17604) 2026-07-21 08:18:14 +12:00
Jonathan SwobodaandJesse Hills d3655597ea [as3935_i2c] Use repeated start when reading registers (#17584) 2026-07-21 08:18:14 +12:00
J. Nick KostonandGitHub cd8d76fa34 [esp8266] Fail fast when Rosetta 2 is missing on Apple Silicon Macs (#17737) 2026-07-21 08:17:01 +12:00
J. Nick KostonandGitHub 500d4aa9e9 [wireguard] Mark private keys sensitive, stop redacting public keys (#17736) 2026-07-20 20:13:56 +00:00
J. Nick KostonandGitHub 271c1b409a [espidf] Resume interrupted toolchain downloads instead of restarting (#17706) 2026-07-21 08:12:17 +12:00
8463dd22ec Fix log message case
Co-authored-by: tomaszduda23 <tomaszduda23@gmail.com>
2026-07-20 22:12:16 +02:00
J. Nick KostonandGitHub 9a0e8606cd [platformio] Include cache path in invalid library error (#17692) 2026-07-21 08:08:46 +12:00
tomaszduda23andGitHub 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
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
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
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 KostonGitHubpre-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]andGitHub 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 KostonandGitHub 5fa93513f2 [espidf] Make openocd-esp32 optional so its libusb check cannot break installs (#17686) 2026-07-20 17:05:50 +12:00
J. Nick KostonandGitHub 4a1f54ce4b [git] Detect interrupted clones and re-clone automatically (#17690) 2026-07-20 16:34:29 +12:00
J. Nick KostonandGitHub 76655cf951 [platformio] Accept git URLs passed as the library name (#17697) 2026-07-20 16:32:20 +12:00
J. Nick KostonandGitHub dfb988c563 [micro_wake_word] Download models in parallel (#17701) 2026-07-20 16:16:15 +12:00
Jesse HillsandGitHub 0443b4849b [ssd1306] Fix offset_x being ignored on SH1106/SH1107 displays (#17700) 2026-07-20 15:51:46 +12:00
Brandon HarveyandGitHub 977376d55c Split multi-token build.flags entries when generating ESP-IDF component CMakeLists (#17649) 2026-07-19 12:20:43 -10:00
esphome[bot]andGitHub 1f93345af8 Bump bundled esphome-device-builder to 1.6.7 (#17696) 2026-07-19 09:17:53 -10:00
esphome[bot]andGitHub 96dd2382c1 Bump bundled esphome-device-builder to 1.6.6 (#17681) 2026-07-18 21:25:56 -10:00
esphome[bot]andGitHub 3064fb0d48 Bump bundled esphome-device-builder to 1.6.5 (#17675) 2026-07-18 11:46:14 -10:00
Jonathan SwobodaandGitHub 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
tomaszduda23andGitHub 0b0e706349 [nrf52] add platform: ultrasonic test (#17665) 2026-07-18 08:05:19 -04:00
tomaszduda23andGitHub 017040ec8e [zigbee] add radio power off stats (#17521) 2026-07-18 09:09:52 +02:00
TomandGitHub 52fe461e9c [ds248x] Add OneWireBus platform for DS248x I2C-to-1Wire bridges (#12717) 2026-07-17 22:01:15 -07:00
esphome[bot]andGitHub a7dad14449 Bump bundled esphome-device-builder to 1.6.4 (#17662) 2026-07-17 17:44:52 -10:00
Jesse HillsandGitHub 1493a09521 [nrf52] Install PlatformIO toolchain Python packages into a dedicated venv (#17635) 2026-07-17 23:22:23 -04:00
J. Nick KostonandGitHub 2a59c0ad9e [logs] Cap the logs reconnect backoff for deep-sleep devices (#17656) 2026-07-17 17:16:14 -10:00
Jonathan SwobodaandGitHub 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 KostonandGitHub bacc223360 Ship component requirements.txt files in the sdist and wheel (#17660) 2026-07-17 15:59:12 -10:00
J. Nick KostonandGitHub 94be1da893 Pin cryptography to 48.0.1 on Intel macOS (#17658) 2026-07-17 15:46:16 -10:00
Brandon HarveyandGitHub cb66dc01ab [core] Fix srcFilter exclusions being silently ignored on Windows (#17648) 2026-07-17 21:45:13 -04:00
Jonathan SwobodaandGitHub ebaf32c82a [ci] Add LibreTiny clang-tidy environments (#17491) 2026-07-17 21:38:55 -04:00
Jonathan SwobodaandGitHub f918c299b1 [espidf] Suggest installing missing system libraries when the tools install fails (#17619) 2026-07-17 19:46:45 -04:00
dependabot[bot]andGitHub 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]andGitHub 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]andGitHub 6f27c7f3fe Bump bundled esphome-device-builder to 1.6.3 (#17651) 2026-07-17 12:25:18 -10:00
dependabot[bot]andGitHub 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]GitHubesphome[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 BurzinskiandGitHub 2132cae1c9 [nextion] Fix RP2040 clang-tidy failure from TFT upload guard mismatch (#17638) 2026-07-17 08:07:38 -04:00
Jesse HillsandGitHub 735f8d607d [nrf52] Set ZEPHYR_SDK_INSTALL_DIR for Zephyr SDK discovery (#17633) 2026-07-18 00:04:35 +12:00
Jonathan SwobodaandGitHub cc6392785f [runtime_image] Prevent integer overflow in image buffer size calculation (#17624) 2026-07-16 18:36:18 -04:00
Jonathan SwobodaandGitHub 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]GitHubdependabot[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 SwobodaandGitHub 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]GitHubdependabot[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]GitHubdependabot[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 SwobodaandGitHub 6c8d0050ab [esp32_ble_server] Validate descriptor write length before copying (#17626) 2026-07-16 15:32:44 -04:00
Jonathan SwobodaandGitHub 09da2766ed [bluetooth_proxy] Bound GATT characteristic/descriptor enumeration to allocated size (#17625) 2026-07-16 15:32:09 -04:00
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 SwobodaandGitHub 5b37049dff [midea] Restrict to platforms where MideaUART actually builds (#17613) 2026-07-16 15:23:46 -04:00
Jonathan SwobodaandGitHub c0e78a5574 [core] Improve framework mirror selection, download errors, and version parsing (#17615) 2026-07-16 15:22:19 -04:00
Jonathan SwobodaandGitHub f53394e46f [mlx90393] Compile out on BK72xx where the bundled library cannot build (#17614) 2026-07-16 15:14:18 -04:00
J. Nick KostonandGitHub 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 KostonandGitHub dcdd044234 [web_server] Use alarm_control_panel as the domain in JSON (#17594) 2026-07-16 08:48:47 -10:00
J. Nick KostonandGitHub bc8510c6d8 [micro_wake_word] Include the local model file in bundles (#17604) 2026-07-16 08:09:30 -04:00
J. Nick KostonandGitHub 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 KostonandGitHub 14e71e190c [ci] Restore memory impact detail for ESP-IDF builds (#17587) 2026-07-16 08:00:19 -04:00
Guanzhong ChenandGitHub 2333e6eef5 [zephyr] implement ISRInternalGPIOPin::digital_write (#17601) 2026-07-16 07:57:30 -04:00
Jonathan SwobodaandGitHub d748c04b28 [libretiny] Code fixes for upcoming LibreTiny clang-tidy scans (#17596) 2026-07-16 07:25:46 -04:00
Jonathan SwobodaandGitHub 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
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 KostonandGitHub f88621f2e1 [network] Remove deprecated IPAddress::str() (#17589) 2026-07-16 05:12:06 +00:00
J. Nick KostonandGitHub ec3f7ec16e [api] Remove outdated API version warning (#17593) 2026-07-16 05:07:26 +00:00
J. Nick KostonandGitHub 880cb1db43 [voice_assistant] Remove deprecated Timer::to_string() (#17591) 2026-07-16 05:06:49 +00:00
J. Nick KostonandGitHub f336c45177 [water_heater] Remove deprecated WaterHeaterCall::get_state() (#17592) 2026-07-16 05:06:38 +00:00
J. Nick KostonandGitHub b12d392e02 [esp32_ble] Remove deprecated ESPBTUUID::to_string() (#17590) 2026-07-16 05:06:30 +00:00
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 KostonandGitHub e327a7f52f [scheduler] Remove deprecated set_retry/cancel_retry (#17585) 2026-07-15 17:58:41 -10:00
Jonathan SwobodaandGitHub 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 SwobodaandGitHub f91aa7aa13 [as3935_i2c] Use repeated start when reading registers (#17584) 2026-07-16 14:26:23 +12:00
lsellensandGitHub be3b27f378 [rc522_i2c] Change default address to match whats in the docs (#17566) 2026-07-15 21:20:07 -04:00
Jesse HillsandGitHub 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 NoerenbergandGitHub c4975e1870 [cc1101] Add FOCCFG and BSCFG config options (#17577) 2026-07-15 21:17:39 -04:00
Jonathan SwobodaandGitHub 5a3c2f4d11 [ci] Add rp2 clang-tidy environment (#17486) 2026-07-15 21:17:21 -04:00
Jonathan SwobodaandGitHub e700b01406 [haier] Fix outdoor defrost temperature reporting the coil temperature (#17492) 2026-07-15 21:16:38 -04:00
Jonathan SwobodaandGitHub 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 HillsandGitHub 920a8b761b Merge pull request #17583 from esphome/bump-2026.7.0
2026.7.0
2026-07-16 12:59:09 +12:00
Jonathan SwobodaandJesse Hills 5d2372937e [ci] Stop per-PR cache copies from crowding the 10GB Actions cache quota (#17463) 2026-07-16 10:16:18 +12:00
Jesse Hills 6c401d406f Bump version to 2026.7.0 2026-07-16 09:52:34 +12:00
Jesse Hills b0489e21b2 Merge branch 'beta' into dev 2026-07-16 09:45:16 +12:00
Jesse HillsandGitHub 78180a40c4 Merge pull request #17582 from esphome/bump-2026.7.0b4
2026.7.0b4
2026-07-16 09:44:41 +12:00
Jesse Hills 63fae2c36a Bump version to 2026.7.0b4 2026-07-16 08:23:57 +12:00
Jesse Hills 878d8a2f6a [core] Fix wait_until crash when re-entered from its own continuation (#17571) 2026-07-16 08:23:57 +12:00
esphome[bot]Jesse Hillsesphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
e056e99fd4 Bump bundled esphome-device-builder to 1.6.1 (#17575)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-07-16 08:23:57 +12:00
Jesse Hills 3b915cf16b [api] Warn when Home Assistant actions are sent with no subscribed client (#17560) 2026-07-16 08:23:57 +12:00
esphome[bot]andJesse Hills d1150d148d Bump bundled esphome-device-builder to 1.6.0 (#17573) 2026-07-16 08:23:57 +12:00
luar123andJesse Hills b5b426492a [zigbee] bump esp-zigbee-sdk to 2.0.3 (#17564) 2026-07-16 08:23:57 +12:00
Jonathan SwobodaandJesse Hills 724df7b11e [atc_mithermometer] Make duplicate-packet counter per-instance (#17496) 2026-07-16 08:23:57 +12:00
Daniele PalumboandJesse Hills 26db22546d [mcp23017] reset IPOL registers to 0x00 on setup (#17177)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2026-07-16 08:23:57 +12:00
Jesse HillsandGitHub a3ab7961e0 [core] Fix wait_until crash when re-entered from its own continuation (#17571) 2026-07-16 08:14:55 +12:00
Hajo NoerenbergandGitHub 54987d7d23 [cc1101] Export CC1101Listener to Python (#17576) 2026-07-15 12:38:08 -04:00
esphome[bot]GitHubesphome[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 HillsandGitHub b295b8d5a2 [api] Warn when Home Assistant actions are sent with no subscribed client (#17560) 2026-07-15 23:11:47 +12:00
esphome[bot]andGitHub 4d3d06959b Bump bundled esphome-device-builder to 1.6.0 (#17573) 2026-07-14 23:59:19 -10:00
Jesse HillsandGitHub a5583dcba6 [tests] Add test_display component to free touchscreen tests from display pins (#17540) 2026-07-15 11:22:47 +12:00
luar123andGitHub e2b62bcd00 [zigbee] bump esp-zigbee-sdk to 2.0.3 (#17564) 2026-07-14 17:27:17 -04:00
Jonathan SwobodaandGitHub 4275343231 [atc_mithermometer] Make duplicate-packet counter per-instance (#17496) 2026-07-15 09:23:37 +12:00
dependabot[bot]andGitHub 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
Jesse HillsandGitHub 00d2d55e09 Merge pull request #17556 from esphome/bump-2026.7.0b3
2026.7.0b3
2026-07-14 16:45:55 +12:00
esphome[bot]andJesse Hills a833685a73 Bump bundled esphome-device-builder to 1.5.0 (#17558) 2026-07-14 14:51:55 +12:00
esphome[bot]andGitHub 2753ab1f45 Bump bundled esphome-device-builder to 1.5.0 (#17558) 2026-07-13 16:50:08 -10:00
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 0a1065da75 [script] Recursively expand nested packages when merging component tests (#17557) 2026-07-14 14:11:18 +12:00
Jesse HillsandGitHub db9a09d05a [script] Recursively expand nested packages when merging component tests (#17557) 2026-07-14 14:11:03 +12:00
Jesse Hills 6fadf353b1 Bump version to 2026.7.0b3 2026-07-14 12:40:45 +12:00
Keith BurzinskiandJesse Hills 8da377ab43 [nextion] Fix unbounded queue growth and OOM crash when display sends no data (#17553) 2026-07-14 12:40:45 +12:00
Jonathan SwobodaandJesse Hills 1da8900ffc [veml7700][as7341][ltr501] Fix device class on raw-count sensors (#17549) 2026-07-14 12:40:45 +12:00
J. Nick KostonandJesse Hills 519ce38b79 [libretiny] Keep renamed board generic-ln882hki validating against generic-ln882h (#17542) 2026-07-14 12:40:45 +12:00
Jesse Hills 989797be53 [web_server] Add HTTP digest authentication with selectable scheme (#17541) 2026-07-14 12:40:45 +12:00
Jesse Hills 583adc9e69 [web_server] Use dict-style packages in tests so they can be batch-grouped (#17544) 2026-07-14 12:40:45 +12:00
Keith BurzinskiandJesse Hills af9a0404d9 [esp32] Do not require verification_key with Secure Boot V2 signing schemes (#17497) 2026-07-14 12:40:45 +12:00
Jesse Hills f38e7f2de2 [web_server] Add CORS origin checking with allowed_origins (#17530) 2026-07-14 12:40:22 +12:00
Jesse Hills 3e75020007 [gsl3670] Fix i2c package variant in esp32-s3-idf test (#17535) 2026-07-14 12:39:14 +12:00
Keith BurzinskiandGitHub ca77cc585c [nextion] Fix unbounded queue growth and OOM crash when display sends no data (#17553) 2026-07-14 11:56:03 +12:00
Jonathan SwobodaandGitHub f1e4726f4e [veml7700][as7341][ltr501] Fix device class on raw-count sensors (#17549) 2026-07-14 11:53:31 +12:00
J. Nick KostonandGitHub 49bbceb1da [libretiny] Keep renamed board generic-ln882hki validating against generic-ln882h (#17542) 2026-07-14 11:51:54 +12:00
Jesse HillsandGitHub 65d6c028ce [web_server] Add HTTP digest authentication with selectable scheme (#17541) 2026-07-14 10:33:44 +12:00
Clyde StubbsandGitHub 5e3e2f82c9 [script] Fix duplicate import in build_codeowners.py (#17543) 2026-07-13 10:16:53 -04:00
Jesse HillsandGitHub 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 BurzinskiandGitHub bcfb438a81 [esp32] Do not require verification_key with Secure Boot V2 signing schemes (#17497) 2026-07-13 16:23:40 +12:00
Jesse HillsandGitHub a8dfd00cc6 [web_server] Add CORS origin checking with allowed_origins (#17530) 2026-07-13 15:38:48 +12:00
Jesse HillsandGitHub 4a82b10783 [ci] Group component test output into collapsible CI log sections (#17536) 2026-07-13 15:29:35 +12:00
Jesse HillsandGitHub 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
Jesse HillsandGitHub feb53a209b Merge pull request #17532 from esphome/bump-2026.7.0b2
2026.7.0b2
2026-07-13 13:24:45 +12:00
Jonathan SwobodaandJesse Hills c607f64288 [mipi_rgb] Test in isolation to avoid bus/pin merge conflicts (#17534) 2026-07-13 12:01:24 +12:00
Jonathan SwobodaandGitHub b3e03868b3 [mipi_rgb] Test in isolation to avoid bus/pin merge conflicts (#17534) 2026-07-13 12:00:22 +12:00
Jesse HillsandJonathan Swoboda 7ee7a26cad [mipi_rgb] Use dict-style packages in test so it can be batch-grouped
Convert the i2c include to a named dict-style package key so CI can group
this component's build with others sharing the same bus, instead of flagging
it as needing migration.
2026-07-12 18:47:28 -04:00
Jesse HillsandGitHub 0ff11674ef [mipi_rgb] Use dict-style packages in test so it can be batch-grouped (#17533) 2026-07-12 18:38:38 -04:00
Jesse Hills 2a67e5c599 Bump version to 2026.7.0b2 2026-07-13 09:19:24 +12:00
Keith BurzinskiandJesse Hills a9591d7aac [zwave_proxy] Fix parser gaps and harden frame and subscription handling (#17461) 2026-07-13 09:19:24 +12:00
J. Nick KostonandJesse Hills bcac3ebe2b [api] Provision encryption keys over an encrypted zero-PSK noise connection (#17482) 2026-07-13 09:19:23 +12:00
Jesse Hills 27b598c5aa [core] Classify entity metadata visibility for the visual editor (#17503) 2026-07-13 09:19:23 +12:00
luar123andJesse Hills 9ba2cbbfdd [zigbee] prevent task watchdog trigger with large configs. (#17506) 2026-07-13 09:19:23 +12:00
luar123andJesse Hills a4650a2345 [zigbee] Fix merge endpoint (#17511) 2026-07-13 09:19:23 +12:00
J. Nick KostonandJesse Hills 1e5cfe6b0f [web_server] Fix unused function warning for json_state_str (#17524) 2026-07-13 09:19:23 +12:00
J. Nick KostonandJesse Hills 665e788cc9 [mdns] Fix missing device info TXT records when native API is not enabled (#17520) 2026-07-13 09:19:23 +12:00
J. Nick KostonandJesse Hills d89b4c0b59 [core] Make config-hash independent of machine-local paths (#17523) 2026-07-13 09:19:23 +12:00
esphome[bot]andJesse Hills 5afe418a8e Bump bundled esphome-device-builder to 1.4.3 (#17522) 2026-07-13 09:19:23 +12:00
tomaszduda23andJesse Hills 312f6f2049 [deep_sleep] feed watchdog in deep sleep (#17516) 2026-07-13 09:19:23 +12:00
eb0848d538 [mipi_dsi] New model for M5Stack Tab5 (#17500)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 09:19:23 +12:00
Clyde StubbsJesse Hillspre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
1a573919d1 [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-13 09:19:23 +12:00
esphome[bot]andJesse Hills 692cf7abd1 Bump bundled esphome-device-builder to 1.4.2 (#17512) 2026-07-13 09:19:23 +12:00
f0afd9e660 [mipi][mipi_spi][mipi_dsi][mipi_rgb] Transform cleanup (#17405)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 09:19:23 +12:00
esphome[bot]andJesse Hills 262ee421f6 Bump bundled esphome-device-builder to 1.4.1 (#17507) 2026-07-13 09:19:23 +12:00
esphome[bot]andJesse Hills 050a006459 Bump bundled esphome-device-builder to 1.4.0 (#17495) 2026-07-13 09:19:23 +12:00
Jonathan SwobodaandJesse Hills 8518d0633b [web_server] Serialize entity state strings without a copy buffer (#17488) 2026-07-13 09:19:23 +12:00
dependabot[bot]andJesse Hills 284fe85271 Bump aioesphomeapi from 45.5.2 to 45.6.0 (#17490)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-13 09:19:23 +12:00
Jonathan SwobodaandJesse Hills 020a6a8fd1 [mcp4461] Fix wiper increment/decrement write length (#17487) 2026-07-13 09:19:23 +12:00
Jonathan SwobodaandJesse Hills 196b979df8 [usb_uart] Fix output chunk length truncated to 8 bits (#17480) 2026-07-13 09:19:23 +12:00
Keith BurzinskiandGitHub 434cffb745 [zwave_proxy] Fix parser gaps and harden frame and subscription handling (#17461) 2026-07-13 08:58:17 +12:00
J. Nick KostonandGitHub 2b3027a7fd [api] Provision encryption keys over an encrypted zero-PSK noise connection (#17482) 2026-07-13 08:55:30 +12:00
Jesse HillsandGitHub e27a14ec70 [core] Classify entity metadata visibility for the visual editor (#17503) 2026-07-12 08:33:29 -04:00
luar123andGitHub 91c42381f6 [zigbee] prevent task watchdog trigger with large configs. (#17506) 2026-07-12 08:32:01 -04:00
luar123andGitHub a39607476a [zigbee] Fix merge endpoint (#17511) 2026-07-12 08:31:39 -04:00
J. Nick KostonandGitHub b098571a6f [web_server] Fix unused function warning for json_state_str (#17524) 2026-07-11 14:31:14 -10:00
J. Nick KostonandGitHub 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 KostonandGitHub c0636e2bf7 [core] Make config-hash independent of machine-local paths (#17523) 2026-07-11 14:30:47 -10:00
esphome[bot]andGitHub 65353006c8 Bump bundled esphome-device-builder to 1.4.3 (#17522) 2026-07-11 13:32:45 -10:00
tomaszduda23andGitHub 35a99f478e [deep_sleep] feed watchdog in deep sleep (#17516) 2026-07-11 09:48:11 -04:00
dependabot[bot]GitHubdependabot[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]GitHubdependabot[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]GitHubdependabot[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
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 StubbsGitHubpre-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
RaymondandGitHub 9f62cf9243 [mipi_dsi] Add JC8012P4A1-V2 (#17457) 2026-07-11 21:24:00 +10:00
esphome[bot]andGitHub 478bca026c Bump bundled esphome-device-builder to 1.4.2 (#17512) 2026-07-10 16:37:00 -10:00
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]andGitHub 83aaed71e1 Bump bundled esphome-device-builder to 1.4.1 (#17507) 2026-07-10 10:30:19 -10:00
Jesse HillsandGitHub 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]andGitHub 7a1e0bbbec Bump bundled esphome-device-builder to 1.4.0 (#17495) 2026-07-09 15:36:42 -10:00
Jonathan SwobodaandGitHub b2226b91ff [web_server] Serialize entity state strings without a copy buffer (#17488) 2026-07-09 16:51:04 -04:00
dependabot[bot]andGitHub 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]andGitHub 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 SwobodaandGitHub 4292e7988e [mcp4461] Fix wiper increment/decrement write length (#17487) 2026-07-09 15:27:05 -04:00
Jonathan SwobodaandGitHub 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
Jesse HillsandGitHub 6c4640f4f1 Merge pull request #17460 from esphome/bump-2026.7.0b1
2026.7.0b1
2026-07-09 16:54:10 +12:00
Jonathan SwobodaandGitHub 435dde67d0 [ci] Stop per-PR cache copies from crowding the 10GB Actions cache quota (#17463) 2026-07-08 23:29:23 -04:00
Jonathan SwobodaandJesse Hills 19e89aa7f2 [gsl3670] Reference the test display explicitly so grouped CI builds validate (#17462) 2026-07-09 13:44:40 +12:00
Jonathan SwobodaandGitHub 9c92ab63fb [gsl3670] Reference the test display explicitly so grouped CI builds validate (#17462) 2026-07-09 13:43:56 +12:00
Elvin LuffandGitHub 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
Jesse Hills f1622ac96a Bump version to 2026.7.0b1 2026-07-09 12:30:53 +12:00
Jesse Hills c66f4d667e Merge branch 'release' into dev 2026-07-09 12:21:34 +12:00
Jesse HillsandGitHub 3bfbaaebf3 Merge pull request #17459 from esphome/bump-2026.6.5
2026.6.5
2026-07-09 12:20:50 +12:00
Jesse Hills 98b79b132a Bump version to 2026.6.5 2026-07-09 12:09:05 +12:00
esphome[bot]Jesse Hillsesphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
f6221f0007 Bump bundled esphome-device-builder to 1.1.0 (#17412)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-07-09 12:09:05 +12:00
esphome[bot]andJesse Hills 263b375088 Bump bundled esphome-device-builder to 1.0.29 (#17384) 2026-07-09 12:09:05 +12:00
esphome[bot]andJesse Hills a1f819e9b8 Bump bundled esphome-device-builder to 1.0.28 (#17382) 2026-07-09 12:09:05 +12:00
esphome[bot]Jesse Hillsesphome[bot] <115708604+esphome[bot]@users.noreply.github.com>Jonathan Swoboda
ca7f50f37f Bump bundled esphome-device-builder to 1.0.27 (#17370)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-07-09 12:09:05 +12:00
esphome[bot]andJesse Hills 7b92fe95af Bump bundled esphome-device-builder to 1.0.26 (#17369) 2026-07-09 12:09:05 +12:00
esphome[bot]andJesse Hills 42ddf0870c Bump bundled esphome-device-builder to 1.0.25 (#17333) 2026-07-09 12:09:05 +12:00
esphome[bot]andJesse Hills 0512dd2339 Bump bundled esphome-device-builder to 1.0.24 (#17332) 2026-07-09 12:09:05 +12:00
Clyde StubbsandGitHub 8ccf0dbd37 [gsl3670] Add new touchscreen component (#16285) 2026-07-09 12:04:43 +12:00
Jesse HillsandGitHub 7c130fc970 [core] Hide build & framework internals from the visual editor (#17449) 2026-07-09 11:58:40 +12:00
Clyde StubbsandGitHub 640e0973ac [lvgl] Dynamic rotation features (#16773) 2026-07-08 15:54:28 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
9088875491 Bump github/codeql-action/analyze from 4.36.3 to 4.37.0 (#17456)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-08 15:42:48 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
99ec2cc00a Bump CodSpeedHQ/action from 4.18.2 to 4.18.4 (#17455)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-08 15:42:38 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
99ff7e198a Bump astral-sh/setup-uv from 8.3.1 to 8.3.2 (#17454)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-08 15:42:18 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
d4bb20d34b Bump github/codeql-action/init from 4.36.3 to 4.37.0 (#17453)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-08 15:42:05 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2db001710c Bump astral-sh/setup-uv from 8.3.1 to 8.3.2 in /.github/actions/restore-python (#17452)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-08 15:41:50 -04:00
Jesse HillsandGitHub dd0d0942f5 [image] Restructure into a platform component (#17416) 2026-07-09 07:38:58 +12:00
Jonathan SwobodaandGitHub 26f48ee9ea [lvgl] Fix ImageValidator.process signature to match base (#17451) 2026-07-09 07:21:23 +12:00
luar123andGitHub 84f4fbeaa8 [zigbee] Allow to combine and merge endpoints on esp32 (#17402) 2026-07-08 14:41:36 -04:00
ce46895270 [uart][usb_uart] Implement runtime settings update (#16990)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2026-07-08 13:30:49 -04:00
esphome[bot]GitHubesphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
e7933a5387 Bump bundled esphome-device-builder to 1.3.1 (#17450)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-07-08 13:06:38 -04:00
Clyde StubbsandGitHub b787281388 [lvgl] Add direct use of mapping (#15863) 2026-07-08 13:00:42 -04:00
bba3a9657b [lvgl] Add animations (#16796)
Co-authored-by: clydeps <U5yx99dok9>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-07-08 12:17:57 -04:00
Kevin AhrendtandGitHub 2f5465c0e8 [sendspin] Suppress WiFi roam scanning while playing (#17133) 2026-07-08 12:07:42 -04:00
9c40ed5d71 [provisioning] Add provisioning window (#17152)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 01:01:20 -05:00
esphome[bot]GitHubesphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
93bc02b308 Bump bundled esphome-device-builder to 1.3.0 (#17448)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-07-07 20:39:55 -05:00
Jesse HillsandGitHub b8af90750f [web_server_idf] Map more common HTTP status codes in responses (#17447) 2026-07-08 13:18:46 +12:00
Jesse HillsandGitHub 65ef05dd1f [web_server_idf] Deliver raw POST bodies to custom handlers via handleBody() (#17433) 2026-07-07 20:51:00 -04:00
731486d9b0 [modbus] Finalize unreleased API surface before 2026.7 (#17434)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 00:20:14 +00:00
Jonathan SwobodaandGitHub 731e9fda03 [internal_temperature] Support all ESP32 variants with a temperature sensor (#17438) 2026-07-08 09:12:57 +12:00
dependabot[bot]andGitHub 35c7496cd7 Bump CodSpeedHQ/action from 4.18.1 to 4.18.2 (#17445)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-07 16:02:38 -05:00
dependabot[bot]andGitHub 71b746b6fa Bump astral-sh/setup-uv from 8.3.0 to 8.3.1 (#17444)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-07 16:02:26 -05:00
dependabot[bot]andGitHub 1913818b1c Bump astral-sh/setup-uv from 8.3.0 to 8.3.1 in /.github/actions/restore-python (#17442)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-07 16:02:16 -05:00
J. Nick KostonandGitHub b4ad0eb86b [esp32_ble] Fix boot loop when the hosted co-processor does not answer BT bring-up (#17429) 2026-07-07 14:59:18 +00:00
7f0e826c32 [lvgl] Add paused option to suppress updates on boot (#16973)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-07-07 14:50:36 +00:00
76ee3fe887 [audio_file] Accept mp1/mp2 puremagic detections as MP3 (#17436)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-07 08:56:48 -04:00
af4a6e7ec3 [usb_uart] Fix FTDI RX data stall / corruption and input restart reliability (#17348)
Co-authored-by: Oliver Kleinecke <kleinecke.oliver@googlemail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 21:55:49 +10:00
40c3a4320f [core] add const for litre per hour (#17389)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-07 21:44:44 +10:00
J. Nick KostonandGitHub 3c2dad67f4 [network] Fix logged use_address with MAC suffix and build it at runtime (#17432) 2026-07-07 02:07:34 -05:00
Remco van EssenandGitHub f823a23ea4 [pcm5122] Add analog gain, channel mixing, volume range, standby/powerdown switch, and XSMT enable pin support (#17313) 2026-07-07 00:15:37 -05:00
Keith BurzinskiandGitHub 9aed1d2700 [esp32] Add NVS encryption (HMAC scheme) (#17004) 2026-07-07 16:04:38 +12:00
9857d508d9 [light] Preserve brightness on turn-off. (#17103)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-07 15:57:06 +12:00
Clyde StubbsandGitHub ad7c980c4b [lvgl] Continue activity while display busy (#17374) 2026-07-07 03:16:29 +00:00
J. Nick KostonandGitHub e8d37e5bd3 [libretiny] Use standard logger tag names (#17431) 2026-07-06 20:55:12 -04:00
J. Nick KostonandGitHub a36c3063b2 [web_server] Use known message length in SSE send path (#17400) 2026-07-06 20:55:02 -04:00
J. Nick KostonandGitHub 902cf6a679 [analyze_memory] Report aliased RAM symbols once in the RAM strings report (#17397) 2026-07-06 20:54:53 -04:00
J. Nick KostonandGitHub a10e005bb4 [esp8266] Strip lwIP glue dhcp stub message strings from DRAM (#17395) 2026-07-06 20:54:44 -04:00
d9998eff20 [esp32] Add software OTA downgrade protection (#17315)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 20:39:18 -04:00
Clyde StubbsandGitHub 2dd7ac090f [mipi_spi] Add M5STACK ATOM3SR display (#17344) 2026-07-06 20:37:44 -04:00
Jesse HillsandGitHub c4689989c7 Mark configurable classes as final (20/21: wts01-zephyr_ble_server) (#16971) 2026-07-06 19:42:18 -04:00
ebff49072e [modbus] Store ModbusFrame inline to cut per-frame heap churn (#17282)
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-06 23:12:25 +00:00
esphome[bot]andGitHub 0b311962b5 Bump bundled esphome-device-builder to 1.2.0 (#17430) 2026-07-06 17:09:51 -05:00
Jonathan SwobodaandGitHub b22def399f [espnow] Add max_payload_size option for ESP-NOW v2 frames (#17360) 2026-07-06 17:08:56 -04:00
cdd334284e [rp2] Rename rp2040 platform to rp2 (#17145)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-07-07 08:20:24 +12:00
Jesse HillsandGitHub 9caf431740 [tests] Document dict-style packages requirement for batch grouping (#17420) 2026-07-07 07:55:53 +12:00
tomaszduda23andGitHub 468b32b986 [nrf52] add better error message for OTA error (#17407) 2026-07-06 15:50:57 -04:00
39ad583b39 [nrf52] allow to build for non nrf52840 boards (#17373)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-07-06 15:48:24 -04:00
J. Nick KostonandGitHub 90403576c4 [wifi] Accept boolean-like strings for fast_connect again (#17414) 2026-07-06 14:42:30 -05:00
J. Nick KostonandGitHub 51fa25856d [bluetooth_proxy] Take over stale advertisement subscription instead of rejecting the new subscriber (#17423) 2026-07-06 14:42:14 -05:00
dependabot[bot]andGitHub 27d4b63a8a Bump astral-sh/setup-uv from 8.2.0 to 8.3.0 (#17428)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-06 14:41:55 -05:00
dependabot[bot]andGitHub 104c2f86f6 Bump astral-sh/setup-uv from 8.2.0 to 8.3.0 in /.github/actions/restore-python (#17427)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-06 14:41:43 -05:00
dependabot[bot]andGitHub e64a79f431 Bump setuptools from 82.0.1 to 83.0.0 (#17426)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-06 14:41:29 -05:00
Oliver KleineckeandGitHub b79db76099 [core] helpers.h - Implement pop_back method for vector (#17390) 2026-07-06 13:54:20 -05:00
66ab807596 [modbus] API naming (#17378)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 11:43:31 -05:00
Jesse HillsandGitHub 2067da4ff5 Mark configurable classes as final (11/21: microphone-ms8607) (#16962) 2026-07-06 16:33:07 +12:00
Jesse HillsandGitHub cfdd6d383f Mark configurable classes as final (19/21: uart-wl_134) (#16970) 2026-07-06 16:32:42 +12:00
Jesse HillsandGitHub bdd51bd476 Mark configurable classes as final (16/21: sm10bit_base-ssd1331_spi) (#16967) 2026-07-06 16:32:35 +12:00
Jesse HillsandGitHub af7b6e3589 Mark configurable classes as final (17/21: ssd1351_spi-tem3200) (#16968) 2026-07-06 16:32:26 +12:00
Jesse HillsandGitHub fd86417bf5 [cst328] Update test package (#17415) 2026-07-06 16:29:45 +12:00
J. Nick KostonandGitHub e095c457ff [esp32] Suppress -Wvolatile in the direct ESP-IDF build (#17404) 2026-07-06 03:02:12 +00:00
Jesse HillsandGitHub 39c0f9cc84 [cst328] Use dict-style packages so batch grouping deduplicates the i2c bus (#17413) 2026-07-05 22:57:56 -04:00
tomaszduda23andGitHub 3f94e6dcbb [nrf52] let user select libc version (#17408) 2026-07-05 19:52:39 -04:00
b9588a8984 [mipi_spi] Add Waveshare-ESP32-S3-TOUCH-AMOLED-1.64 (#17386)
Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com>
2026-07-06 09:29:03 +10:00
esphome[bot]GitHubesphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
105d1362a2 Bump bundled esphome-device-builder to 1.1.0 (#17412)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-07-05 15:31:07 -05:00
Christoph Walcher bec1158775 remove SetFrequencyAction declaration 2026-07-05 14:54:45 +00:00
Christoph Walcher e7671dd854 Enforce same frequency on whole block 2026-07-05 14:35:29 +00:00
Clyde StubbsandGitHub f6c260a2c5 [ci] Make import time budget more realistic (#17406) 2026-07-05 08:01:48 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
c720186170 Bump smpclient from 6.0.0 to 7.2.0 (#16928)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-05 06:50:13 +02:00
8ab19c0242 [esp32] Add RTC-backed preferences (honor in_flash flag) (#17073)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 11:48:30 +10:00
esphome[bot]andGitHub 2c24e82ba3 Bump bundled esphome-device-builder to 1.0.29 (#17384) 2026-07-04 15:20:48 -05:00
Jonathan SwobodaandGitHub fcfaa43e1e [ci] Name the sdk-nrf cache steps after the nRF Connect SDK (#17392) 2026-07-04 12:46:16 -04:00
esphome[bot]andGitHub 4c8e45a222 Bump bundled esphome-device-builder to 1.0.28 (#17382) 2026-07-03 23:17:49 -05:00
Citric LiGitHubpre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>Claude Opus 4.8Clyde Stubbs
7878052533 [epaper_spi] Add T133A01 6-color e-paper driver for reTerminal E1004 (#16706)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
2026-07-04 03:50:54 +00:00
e94fcda8b7 [cst328] Touch screen (Waveshare ESP32-S3-Touch-LCD-2.8) (#8011)
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
2026-07-04 12:13:22 +10:00
Jonathan SwobodaandGitHub 5738c60206 [nrf52] Run clang-tidy against the native sdk-nrf toolchain (#17364) 2026-07-03 18:09:01 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
a035d84474 Bump docker/login-action from 4.3.0 to 4.4.0 in the docker-actions group (#17380)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-03 16:55:47 -04:00
Jonathan SwobodaandGitHub a8fd26ef6d Merge branch 'dev' into feat/nrf52_pwm 2026-07-03 15:11:40 -04:00
7ad43358c2 [zigbee] Bump zigbee sdk to 2.0.2 (#16869)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-07-03 14:28:29 -04:00
Jonathan SwobodaandGitHub 187cd51867 [ci] Carry native-toolchain needs on component test batches (#17359) 2026-07-03 13:47:08 -04:00
Christoph WalcherandCopilot Autofix powered by AI 1310fe685a feat: add support for nrf52 pwm
add tests

update codeowners

[pre-commit.ci lite] apply automatic fixes

fix preproc if

refactor to zephyr_pwm

validate pwm channel limit

Post copilot review

support inverted & check min frequency

typing for builder

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

protect parent

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

check max frequency

support multiple blocks

validate max pwm components

fix test config

refactor psels

remove key_user
2026-07-03 16:21:00 +00:00
fd16eec416 [nrf52] switch nrf52 builds to native sdk by default (#17319)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: ESPHome Device Builder <device-builder@esphome.io>
2026-07-03 12:16:55 -04:00
tomaszduda23andGitHub ea14a93e67 [nrf52] fix crash report for native build (#17371) 2026-07-03 09:33:42 -04:00
esphome[bot]GitHubesphome[bot] <115708604+esphome[bot]@users.noreply.github.com>Jonathan Swoboda
c456fc98ab Bump bundled esphome-device-builder to 1.0.27 (#17370)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-07-03 09:31:21 -04:00
711d8bb0ad Synchronise Device Classes from Home Assistant (#17372)
Co-authored-by: esphomebot <esphome@openhomefoundation.org>
2026-07-03 08:33:00 -04:00
Anunay KulshresthaGitHubClaude Opus 4.8pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>tomaszduda23
c3233739c5 [zephyr] Implement GPIO interrupts (ISRInternalGPIOPin) (#17077)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: tomaszduda23 <tomaszduda23@gmail.com>
2026-07-03 11:43:21 +02:00
Joseph SpirosandGitHub 5fe36a45ed [core] Skip MAC-suffix mDNS discovery for non-mDNS addresses (#16874) 2026-07-03 11:48:55 +12:00
esphome[bot]andGitHub 0725157bf5 Bump bundled esphome-device-builder to 1.0.26 (#17369) 2026-07-02 18:48:08 -05:00
Jonathan SwobodaandGitHub 9f589ec4fc [api] Register homeassistant.action with synchronous=False to fix stale trigger args in response callbacks (#17367) 2026-07-02 19:47:12 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
5417a16f9d Update argcomplete requirement from >=3.6.3 to >=3.7.0 (#17334)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-02 18:37:22 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
f447c88b4c Bump docker/build-push-action from 7.2.0 to 7.3.0 in /.github/actions/build-image (#17336)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-02 18:37:12 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
0b48ca0027 Bump the docker-actions group with 2 updates (#17361)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-02 18:36:33 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
bef6773281 Bump github/codeql-action/init from 4.36.2 to 4.36.3 (#17362)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-02 18:36:01 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
4f0968f1df Bump github/codeql-action/analyze from 4.36.2 to 4.36.3 (#17363)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-02 18:35:46 -04:00
Jonathan SwobodaandGitHub 648f5e1b06 [nrf52] Install native sdk-nrf into a machine-global cache dir (#17353) 2026-07-02 15:42:59 -04:00
Jonathan SwobodaandGitHub 65fc10d627 [nrf52] Build PlatformIO libraries as Zephyr modules (sdk-nrf) (#17250) 2026-07-02 10:26:34 -04:00
41cf842d5d [zephyr][nrf52] Rebuild native build when config inputs change (#17318)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-07-02 14:13:56 +00:00
792dfbcbbf [st7123] add ST7123 touch controller component (M5Stack Tab5) (#12075)
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
2026-07-02 18:40:59 +10:00
TwisterssandGitHub 06c7ac37d1 [epaper_spi] Add Waveshare 7.5" V2 BWR support (#15719) 2026-07-02 17:33:58 +10:00
Oliver KleineckeandGitHub 0666cb8635 [usb_uart] Add per-device-type maximum baud rate cap (#17259) 2026-07-02 13:44:09 +10:00
Clyde StubbsandGitHub 5b8bf51022 [power_supply] Make enable_on_boot high priority (#16914) 2026-07-02 13:56:01 +12:00
Oliver KleineckeandGitHub 4a7c58d5ae [usb_uart] Fix format specifier warnings for uint32_t in ft23xx and pl2303 (#17342) 2026-07-02 11:26:56 +10:00
J. Nick KostonandGitHub 7522780c67 [esp8266] Strip dead libstdc++ throw message strings from DRAM (#17341) 2026-07-01 18:25:34 -05:00
dependabot[bot]andGitHub e4a68c2da3 Bump pillow from 12.2.0 to 12.3.0 (#17335)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-01 15:25:55 -05:00
esphome[bot]andGitHub 0427d20c5b Bump bundled esphome-device-builder to 1.0.25 (#17333) 2026-07-01 15:02:01 -05:00
Bonne EgglestonGitHubClaude Opus 4.8pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>J. Nick Koston
d25d160686 [modbus_server] Fix register range issues and allow partial reads (#17205)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-07-01 15:01:48 -05:00
esphome[bot]andGitHub 0e260e5cbb Bump bundled esphome-device-builder to 1.0.24 (#17332) 2026-07-01 12:52:09 -05:00
Jesse Hills 58780cd765 Merge branch 'release' into dev 2026-07-01 16:26:24 +12:00
Jesse HillsandGitHub 1fc73731a8 Merge pull request #17317 from esphome/bump-2026.6.4
2026.6.4
2026-07-01 16:25:39 +12:00
Jesse Hills e47feace11 Bump version to 2026.6.4 2026-07-01 13:18:54 +12:00
esphome[bot]andJesse Hills 4472d3b61b Bump bundled esphome-device-builder to 1.0.23 (#17316) 2026-07-01 13:18:54 +12:00
Jonathan SwobodaandJesse Hills 06c5bcbc66 [espnow] Drop oversized received frames to prevent buffer overflow (#17271) 2026-07-01 13:18:54 +12:00
Jonathan SwobodaandJesse Hills 6c44775bf5 [bluetooth_proxy] Fix -Wtype-limits warning with active: false (#17273) 2026-07-01 13:18:54 +12:00
Clyde StubbsandJesse Hills b127363fa0 [mipi_spi] Bug fixes (#17247) 2026-07-01 13:18:54 +12:00
esphome[bot]andJesse Hills 782b58bbeb Bump bundled esphome-device-builder to 1.0.22 2026-07-01 13:18:50 +12:00
5de508ad8c [es8388] Fix DAC unable to unmute once muted (#17221)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-07-01 13:18:40 +12:00
Jesse Hills 054c8ba485 [config_validation] Fix multicast typo in error message (#17206) 2026-07-01 13:18:35 +12:00
Julian LunzandJesse Hills 3b2be021b2 [adc] Only call cyw43_thread_enter/exit for VSYS when WiFi is active on RP2040 (#17203) 2026-07-01 13:11:21 +12:00
esphome[bot]andGitHub 848defedd8 Bump bundled esphome-device-builder to 1.0.23 (#17316) 2026-07-01 13:09:48 +12:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
4c9ed129cf Bump awalsh128/cache-apt-pkgs-action from 1.6.0 to 1.6.3 (#17310)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-30 18:28:04 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
c8b37fb1c8 Bump platformdirs from 4.9.4 to 4.10.0 (#17309)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-30 17:18:33 -04:00
Jonathan SwobodaandGitHub 1b556f5d0c [ethernet] Fix ETH_SPEED_1000M build on IDF 6.0 (enum added in 6.1) (#17311) 2026-06-30 17:17:47 -04:00
Jonathan SwobodaandGitHub 8a3d0aeafb [tests] Add esp32-c61-idf base file for grouped component tests (#17293) 2026-06-30 19:33:17 +00:00
Jonathan SwobodaandGitHub 9468ad628c [espnow] Drop oversized received frames to prevent buffer overflow (#17271) 2026-06-30 14:57:34 -04:00
Jonathan SwobodaandGitHub 990431aa5b [bluetooth_proxy] Fix -Wtype-limits warning with active: false (#17273) 2026-06-30 14:56:30 -04:00
Jonathan SwobodaandGitHub b79cbcbde7 [espidf] Install native ESP-IDF into a machine-global cache dir (#17306) 2026-06-30 14:56:18 -04:00
Bonne EgglestonandGitHub afb5922f37 [modbus] Update client components to use ModbusClientDevice (#11987) 2026-06-30 13:26:51 -05:00
Jonathan SwobodaandGitHub 3035355c0a [ci] Widen import-time margin for CI runner variance (#17287) 2026-06-30 11:38:31 -04:00
Jonathan SwobodaandGitHub 43b3aa0712 [ci] Fix nRF52 zigbee/network test-grouping conflict (#17295) 2026-06-30 11:37:59 -04:00
Jonathan SwobodaandGitHub 12b78e7c47 [qmi8658] Pin i2c_id in test config to fix grouped component test conflict (#17303) 2026-06-30 11:37:17 -04:00
Clyde StubbsandGitHub fb5d8b5d4c [mipi_spi] Bug fixes (#17247) 2026-06-30 08:43:01 -04:00
9e72027b64 [devcontainer] Align base image with production, fix Python venv and build tools (#17296)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 08:33:33 -04:00
faa5f72500 [mqtt] Add LN882X (LN882H) platform support (#17297)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-06-30 08:16:18 -04:00
Jonathan SwobodaandGitHub 359c6a7265 [libretiny] Update LibreTiny to v1.13.0 (#17288) 2026-06-29 22:34:32 -04:00
J. Nick KostonandGitHub a031da351d Merge pull request #17294 from esphome/bump-device-builder-1.0.22 2026-06-29 19:29:08 -07:00
esphome[bot]andgithub-actions[bot] 091b6a0ba0 Bump bundled esphome-device-builder to 1.0.22 2026-06-30 02:07:06 +00:00
Jesse HillsandGitHub cf9d97d5ae [pixoo] Add Divoom Pixoo display component (#16974) 2026-06-30 11:20:48 +12:00
Clyde StubbsandGitHub 3e1a6b4e11 [cst9220] Add CST9220 and CST9217 touchscreen support (#16888) 2026-06-30 11:18:01 +12:00
Clyde StubbsandGitHub 5c7245dfcd [qmi8658] Motion platform for QMI8658 IMU (#16889) 2026-06-30 11:04:25 +12:00
Clyde StubbsandGitHub 1611345c55 [agents] Add English language AI guidelines for documentation (#17290) 2026-06-29 22:37:17 +00:00
Jonathan SwobodaandGitHub b36e20d60b Revert "Bump awalsh128/cache-apt-pkgs-action from 1.6.0 to 1.6.2 (#17286)" (#17289) 2026-06-29 18:25:16 -04:00
ArdumineandGitHub 93eb6f78e0 [network] Enlarge Zephyr net buffer pool and TCP windows on nRF52/Zephyr plataform (#17278) 2026-06-29 17:32:57 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
e308075e3f Bump puremagic from 1.30 to 2.2.0 (#17285)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-29 16:12:23 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
405607e9d2 Bump esptool from 5.3.0 to 5.3.1 (#17284)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-29 16:06:36 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
797ed23765 Bump tzlocal from 5.4.3 to 5.4.4 (#17283)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-29 16:02:11 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Jonathan Swoboda
8780c7e0ac Bump awalsh128/cache-apt-pkgs-action from 1.6.0 to 1.6.2 (#17286)
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-06-29 16:01:41 -04:00
Jonathan SwobodaandGitHub 136e343988 [ethernet] Generic and YT8531 PHY over RGMII (gigabit) for ESP32-S31 (#17277) 2026-06-29 14:13:11 -04:00
Jonathan SwobodaandGitHub b8690c8e31 [core] Drop Python 3.11 support (#17280) 2026-06-29 12:32:28 -04:00
Kevin AhrendtandGitHub 2778c62d07 [audio] Bump microMP3 to v0.4.0 (#17279) 2026-06-29 11:33:56 -04:00
Jesse Hills 7984349c36 Merge branch 'release' into dev 2026-06-29 22:32:34 +12:00
Jesse HillsandGitHub 0cbbd64577 Merge pull request #17223 from esphome/bump-2026.6.3
2026.6.3
2026-06-29 22:31:44 +12:00
Jesse Hills a618ee11b4 Bump version to 2026.6.3 2026-06-29 20:30:24 +12:00
TomandJesse Hills 6251c26cc6 [espnow] Fix espnow crash when send() is called without a callback (#17266) 2026-06-29 20:30:24 +12:00
Jonathan SwobodaandJesse Hills 4fbe0d87ec [wifi] Fix crash when WiFi is enabled late alongside ESP-NOW (#17239) 2026-06-29 20:30:24 +12:00
esphome[bot]Jesse Hillsesphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
24d8e99c50 Bump bundled esphome-device-builder to 1.0.21 (#17257)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-06-29 20:30:24 +12:00
Jonathan SwobodaandJesse Hills 14b6a0ede1 [espnow] Don't throttle ESP-NOW RX when deep_sleep is present (#17240) 2026-06-29 20:30:24 +12:00
Jonathan SwobodaandJesse Hills 1793ca5eac [core] Suppress unactionable legacy-redaction warning for substitutions (#17242) 2026-06-29 20:30:24 +12:00
esphome[bot]andJesse Hills 62e19bcb27 Bump bundled esphome-device-builder to 1.0.20 (#17244) 2026-06-29 20:30:24 +12:00
84d1c34c28 [core] Fix area saved as null in storage.json (#17219)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-06-29 20:30:24 +12:00
esphome[bot]Jesse Hillsesphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
f78cbf9200 Bump bundled esphome-device-builder to 1.0.19 (#17217)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-06-29 20:30:23 +12:00
esphome[bot]Jesse Hillsesphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
eb711381d3 Bump bundled esphome-device-builder to 1.0.18 (#17212)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-06-29 20:30:23 +12:00
Jonathan SwobodaandJesse Hills 9a1daa5247 [hbridge] Fix light stuck on one polarity (#17162) 2026-06-29 20:30:18 +12:00
f3d61ca3e1 [mipi][mipi_spi] Swap native dimensions for swap_xy hardware transform (#17201)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 20:29:44 +12:00
Jonathan SwobodaandJesse Hills 29dfd820c6 [wifi] Report STA IP, not SoftAP IP, in wifi_info on ESP8266 (#17185) 2026-06-29 20:29:44 +12:00
Jonathan SwobodaandJesse Hills 8bc5b97298 [network] Set IPv4 type tag on all lwIP platforms, not just esp32 (#17200) 2026-06-29 20:29:44 +12:00
Jonathan SwobodaandJesse Hills 7a64163c4f [esp32] Accept '#' as ESP-IDF source ref separator (#17193) 2026-06-29 20:29:44 +12:00
esphome[bot]andJesse Hills dfe14f9c3a Bump bundled esphome-device-builder to 1.0.17 (#17199) 2026-06-29 20:29:44 +12:00
esphome[bot]andJesse Hills 26cf373ae7 Bump bundled esphome-device-builder to 1.0.16 (#17182) 2026-06-29 20:29:44 +12:00
Geoffrey FrogeyeJesse Hillspre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
94ccddf176 [opentherm] Support power scaling disabled (#17183)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-06-29 20:29:44 +12:00
Clyde StubbsandJesse Hills 2ec24505d0 [mipi_spi] Suppress sequence errors when page selection used (#17176) 2026-06-29 20:29:44 +12:00
esphome[bot]andJesse Hills 4f7faa7712 Bump bundled esphome-device-builder to 1.0.15 (#17170) 2026-06-29 20:29:44 +12:00
Clyde StubbsandJesse Hills b3dcaac262 [mipi_spi] Warn on MODE3 default for display without CS pin (#17153) 2026-06-29 20:29:44 +12:00
ee118d384a [esp32] Use POSIX path for secure-boot signing/verification keys Fixes #17164 (#17166)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-06-29 20:29:44 +12:00
Jonathan SwobodaandJesse Hills 8d36167e11 [esp32_ble_server] Fix set_value action with by-reference triggers (#17156) 2026-06-29 20:29:44 +12:00
esphome[bot]andJesse Hills 6d559a32df Bump bundled esphome-device-builder to 1.0.14 (#17139) 2026-06-29 20:29:37 +12:00
Jonathan SwobodaandJesse Hills bf0d31b3ab [espidf] Don't fail framework check on broken unrelated PATH tools (#17053) 2026-06-29 20:23:23 +12:00
dependabot[bot]andJesse Hills d8ffb732b7 Bump zeroconf from 0.149.16 to 0.150.0 (#17137)
Signed-off-by: dependabot[bot] <support@github.com>
2026-06-29 20:23:23 +12:00
TomandGitHub 9e8261056c [espnow] Fix espnow crash when send() is called without a callback (#17266) 2026-06-28 23:06:12 -04:00
5f311d281e [esphome] Warn when a YAML merge (<<:) drops a key (#17246)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-29 07:17:01 +10:00
J. Nick KostonandGitHub a336ad6732 [mcp4725] Use constexpr bit shift instead of powf for full-scale value (#17261) 2026-06-28 17:07:51 -04:00
8434d54cc7 [modbus] Reinstate turnaround delay after broadcasts (Revert #17209) (#17263)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-06-28 17:07:25 -04:00
Jonathan SwobodaandGitHub b62f7a41c9 [multiple] Single-precision float math, avoid double promotion (stragglers) (#17260) 2026-06-28 16:15:38 -04:00
4ebecf514a [modbus_server] Simplify server response handling (#12376)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-06-28 19:41:47 +00:00
Jonathan SwobodaandGitHub 2f32c88ae5 [wifi] Fix crash when WiFi is enabled late alongside ESP-NOW (#17239) 2026-06-28 15:16:42 -04:00
Jonathan SwobodaandGitHub 95449068e7 [multiple] Single-precision float math, avoid double promotion (batch 4/4) (#17256) 2026-06-28 14:18:41 -04:00
Jonathan SwobodaandGitHub 556def78aa [multiple] Single-precision float math, avoid double promotion (batch 3/4) (#17255) 2026-06-28 14:18:30 -04:00
Jonathan SwobodaandGitHub b7803cf9b5 [multiple] Single-precision float math, avoid double promotion (batch 2/4) (#17254) 2026-06-28 14:18:20 -04:00
Jonathan SwobodaandGitHub 40820287f1 [multiple] Single-precision float math, avoid double promotion (batch 1/4) (#17253) 2026-06-28 14:18:13 -04:00
Jonathan SwobodaandGitHub 6210dfb4d0 [core] Use single-precision float math to avoid double promotion (#17252) 2026-06-28 14:18:01 -04:00
esphome[bot]GitHubesphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
45c712b17b Bump bundled esphome-device-builder to 1.0.21 (#17257)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-06-28 09:41:49 -07:00
alorenteGitHubpre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>CopilotCitric LikoosoliCursorClyde StubbsClaude Opus 4.8
8e23065b86 [it8951] Add IT8951 e-paper controller support to epaper_spi (#15346)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Citric Li <37475446+limengdu@users.noreply.github.com>
Co-authored-by: koosoli <koosoli@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 21:14:05 +10:00
Jonathan SwobodaandGitHub d3892b8399 [platformio] Extract toolchain-agnostic PlatformIO library converter (#17243) 2026-06-28 07:03:09 -04:00
Jonathan SwobodaandGitHub bda789052d [espnow] Don't throttle ESP-NOW RX when deep_sleep is present (#17240) 2026-06-27 22:17:09 +00:00
Jonathan SwobodaandGitHub 0fb100f2d1 [core] Suppress unactionable legacy-redaction warning for substitutions (#17242) 2026-06-27 18:05:00 -04:00
esphome[bot]andGitHub fd7fc6b8e8 Bump bundled esphome-device-builder to 1.0.20 (#17244) 2026-06-27 15:00:23 -07:00
690e8c3fb9 [nrf52] add upload for native build (#17100)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-06-27 15:50:28 -04:00
a0742a9535 [api] Add nRF52 support (#17226)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-06-27 14:22:34 -04:00
Jonathan SwobodaandGitHub ccc57475b7 [deep_sleep] Add ESP32-C5 support (#17237) 2026-06-27 12:04:45 -04:00
24ec65e68e [esp32] Only warn about S3 PSRAM pins (GPIO33-37) in octal mode (#17222)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-06-27 11:26:47 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
436938b931 Bump actions/cache/restore from 6.0.0 to 6.1.0 (#17231)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-27 10:33:13 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
063c4371de Bump actions/cache from 6.0.0 to 6.1.0 (#17230)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-27 10:33:01 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
7ad4cbf46f Bump actions/cache/save from 6.0.0 to 6.1.0 (#17229)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-27 10:32:50 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
88875daf52 Bump actions/cache/restore from 6.0.0 to 6.1.0 in /.github/actions/restore-python (#17228)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-27 10:32:39 -04:00
7811781a96 [es8388] Fix DAC unable to unmute once muted (#17221)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-06-27 10:21:36 -04:00
da5e11d196 [core] Fix area saved as null in storage.json (#17219)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-06-27 09:49:56 -04:00
ArdumineGitHubtomaszduda23pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>Jonathan Swoboda
75cdabee3d [socket] Add BSD socket support for nRF52 (#16699)
Co-authored-by: tomaszduda23 <tomaszduda23@gmail.com>
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>
2026-06-26 09:30:07 -04:00
esphome[bot]GitHubesphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
f9f28a6a00 Bump bundled esphome-device-builder to 1.0.19 (#17217)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-06-26 07:48:52 +02:00
be8523a73c [mdns] Add mDNS to Zephyr and nRF52 (#16924)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-06-25 16:33:38 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
f49bed47de Bump ruff from 0.15.19 to 0.15.20 (#17216)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-25 16:07:55 -04:00
Franck NijhofandGitHub 239211e521 [time] Defer aioesphomeapi import to speed up config validation (#17214) 2026-06-25 15:34:44 -04:00
Franck NijhofandGitHub cc646b2213 [core] Defer requests import in framework_helpers to speed up config validation (#17215) 2026-06-25 15:34:33 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
ddf075a2dd Bump aioesphomeapi from 45.3.1 to 45.5.2 (#17211)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-25 18:00:52 +00:00
esphome[bot]GitHubesphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
e304c318fb Bump bundled esphome-device-builder to 1.0.18 (#17212)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-06-25 19:47:24 +02:00
Jonathan SwobodaandGitHub e27390bddb [hbridge] Fix light stuck on one polarity (#17162) 2026-06-25 12:36:10 -04:00
rwrozelleGitHubpre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>Jonathan SwobodaClaude Sonnet 4.6
6f36ce6429 [openthread] Provide action to control poll_period when device MTD (#11766)
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-06-25 12:35:15 -04:00
Jonathan SwobodaandGitHub 1d5490fd91 [modbus] Only apply turnaround delay after broadcasts (#17209) 2026-06-25 11:38:34 -04:00
Jonathan SwobodaandGitHub 18c7f60410 [uart] Validate fixed UART settings at config time for fixed-baud components (#17207) 2026-06-25 10:19:05 -04:00
4f70f6b2a6 [mipi][mipi_spi] Swap native dimensions for swap_xy hardware transform (#17201)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 08:36:36 -04:00
Jesse HillsandGitHub 29a6105730 [ms8607] Mark configurable classes as final (#17147) 2026-06-25 08:31:15 -04:00
Jesse HillsandGitHub 46cf052ec5 [config_validation] Fix multicast typo in error message (#17206) 2026-06-25 08:28:43 -04:00
Jesse HillsandGitHub e5d8c22b47 Mark configurable classes as final (13/21: pmsa003i-rc522) (#16964) 2026-06-25 13:20:11 +02:00
Jesse HillsandGitHub 64acb358a5 Mark configurable classes as final (8/21: hm3301-integration) (#16959) 2026-06-25 13:20:00 +02:00
Jesse HillsandGitHub d511f0614d Mark configurable classes as final (18/21: template-tx20) (#16969) 2026-06-25 13:19:51 +02:00
Jesse HillsandGitHub eb9ca517e3 Mark configurable classes as final (14/21: rc522_i2c-scd4x) (#16965) 2026-06-25 13:19:39 +02:00
Jesse HillsandGitHub f769457bb0 Mark configurable classes as final (15/21: script-slow_pwm) (#16966) 2026-06-25 13:19:30 +02:00
Tomasz WitkeandGitHub 0fcf512148 [image] Use LVGL 9 color formats (#16871) 2026-06-25 21:03:50 +10:00
Jesse HillsandGitHub 8c68e95568 [config_validation] Add tests for 100% validator coverage (#17204) 2026-06-25 22:33:28 +12:00
FaeandGitHub d8eee03556 [host] Fix handling of directory for preferences (#11160) 2026-06-25 10:20:15 +00:00
Julian LunzandGitHub 23933c1b58 [adc] Only call cyw43_thread_enter/exit for VSYS when WiFi is active on RP2040 (#17203) 2026-06-24 23:21:41 -07:00
Jonathan SwobodaandGitHub 8c9f4fba8f [wifi] Report STA IP, not SoftAP IP, in wifi_info on ESP8266 (#17185) 2026-06-24 22:28:15 -04:00
Jonathan SwobodaandGitHub 92554f4e67 [network] Set IPv4 type tag on all lwIP platforms, not just esp32 (#17200) 2026-06-24 22:28:06 -04:00
Jonathan SwobodaandGitHub 1dfafce06a [i2c][spi] Wire ESP32-S31/H4/H21 bus capabilities (#17188) 2026-06-24 21:05:29 -04:00
Jonathan SwobodaandGitHub abbcfd213f [tinyusb][usb_cdc_acm][usb_host][usb_uart] Support ESP32-S31/H4 (#17190) 2026-06-24 20:51:03 -04:00
Jonathan SwobodaandGitHub 23aff5202b [wifi][openthread] Wire ESP32-S31/H4/H21 radio support (#17186) 2026-06-24 20:42:41 -04:00
Jonathan SwobodaandGitHub 91e515ca7c [esp32] Accept '#' as ESP-IDF source ref separator (#17193) 2026-06-24 20:40:28 -04:00
Jonathan SwobodaandGitHub 538f554bdb [psram] Support ESP32-S31/H4 (#17192) 2026-06-24 20:38:55 -04:00
Jonathan SwobodaandGitHub e96717f6cd [waveshare_io_ch32v003] Pin i2c_id in test to avoid grouping conflict (#17191) 2026-06-24 20:16:53 -04:00
dependabot[bot]andGitHub aff5e248ed Bump actions/setup-python from 6.2.0 to 6.3.0 in /.github/actions/restore-python (#17194)
Signed-off-by: dependabot[bot] <support@github.com>
2026-06-25 02:05:30 +02:00
dependabot[bot]andGitHub 155439be74 Bump actions/setup-python from 6.2.0 to 6.3.0 (#17197)
Signed-off-by: dependabot[bot] <support@github.com>
2026-06-25 02:05:13 +02:00
dependabot[bot]andGitHub fa34c67950 Bump CodSpeedHQ/action from 4.17.6 to 4.18.1 (#17198)
Signed-off-by: dependabot[bot] <support@github.com>
2026-06-25 02:04:52 +02:00
dependabot[bot]andGitHub b688474444 Bump ruff from 0.15.18 to 0.15.19 (#17195)
Signed-off-by: dependabot[bot] <support@github.com>
2026-06-25 02:04:37 +02:00
esphome[bot]andGitHub 72b663fc40 Bump bundled esphome-device-builder to 1.0.17 (#17199) 2026-06-25 02:04:22 +02:00
esphome[bot]andGitHub f471329d60 Bump bundled esphome-device-builder to 1.0.16 (#17182) 2026-06-24 17:15:00 +02:00
Geoffrey FrogeyeGitHubpre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
e8acd24fd9 [opentherm] Support power scaling disabled (#17183)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-06-24 09:29:57 -04:00
Clyde StubbsandGitHub 18f29f8d2b [mipi_spi] Suppress sequence errors when page selection used (#17176) 2026-06-24 08:05:23 -04:00
Anton ViktorovandGitHub cbcf23426d [waveshare_io_ch32v003] Waveshare I/O Expander component (#10071) 2026-06-24 20:08:59 +10:00
Jesse HillsandGitHub e6455c5b44 Mark configurable classes as final (12/21: msa3xx-pm2005) (#16963) 2026-06-23 18:27:07 -05:00
Jesse HillsandGitHub 2b8916fc4e [ci] Exclude test changes from small-pr/medium-pr size labels (#17172) 2026-06-24 11:13:34 +12:00
esphome[bot]andGitHub dae078fc56 Bump bundled esphome-device-builder to 1.0.15 (#17170) 2026-06-24 11:06:15 +12:00
Clyde StubbsandGitHub 72686bd4af [mipi_spi] Warn on MODE3 default for display without CS pin (#17153) 2026-06-24 08:43:39 +10:00
344da7c4f4 [docker] Move build deps to base image, drop app apt step (#17167)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2026-06-23 17:35:07 -04:00
Jesse HillsandGitHub 84de814e6f [config_validation] Make bind_key a sensitive dual-mode validator (#17146) 2026-06-24 09:05:39 +12:00
Jonathan SwobodaandGitHub 1d32b6c9e0 [espidf] Enable ccache by default for ESP-IDF builds (#17163) 2026-06-23 16:41:13 -04:00
49536693b7 [esp32] Use POSIX path for secure-boot signing/verification keys Fixes #17164 (#17166)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-06-23 16:40:49 -04:00
dependabot[bot]andGitHub a24a63e61b Bump actions/cache from 5.0.5 to 6.0.0 (#17168)
Signed-off-by: dependabot[bot] <support@github.com>
2026-06-23 15:03:28 -05:00
dependabot[bot]andGitHub e3b644c2a0 Bump actions/cache from 5.0.5 to 6.0.0 in /.github/actions/restore-python (#17169)
Signed-off-by: dependabot[bot] <support@github.com>
2026-06-23 15:03:16 -05:00
Jonathan SwobodaandGitHub 7763ce958d [tests] Disable Hypothesis deadline on IP validation property tests (#17138) 2026-06-23 15:21:40 -04:00
Jonathan SwobodaandGitHub ff001b9e45 [esp32_ble_server] Fix set_value action with by-reference triggers (#17156) 2026-06-23 15:21:26 -04:00
Jonathan SwobodaandGitHub c2d79c972c [docker] Install ccache in the image (#17157) 2026-06-23 15:04:42 -04:00
Jonathan SwobodaandGitHub e0377bbbd3 [ci] Enable ccache for component batch builds (~7% faster) (#17136) 2026-06-23 15:04:23 -04:00
Berik VisschersandGitHub eae65a6b88 [bme680_bsec][bme68x_bsec2][const] Move BME sensor constants to shared component consts (#17160) 2026-06-23 11:44:48 -04:00
Jesse HillsandGitHub 225d426d95 [core] Use CORE.is_* platform helpers in __main__ (#17144) 2026-06-23 08:40:16 -04:00
Zach IsbachandGitHub 9614bc20a0 [epaper_spi] Add support for Waveshare 2.13" V4 series B (R/B/W) (#16828) 2026-06-23 11:00:19 +00:00
arunderwoodandGitHub 41747c2de7 [epaper_spi] Add support for the Inkplate 2 (#16856) 2026-06-23 16:50:02 +10:00
Jesse HillsandGitHub c70d56807f [motion] Make motion test configs mergeable in CI (#17149) 2026-06-23 16:44:58 +12:00
Jesse HillsandGitHub 69d700727d [docker] Remove dead HA addon env exports (streamer_mode, relative_url) (#17140) 2026-06-23 11:25:24 +12:00
esphome[bot]andGitHub 5fcf656806 Bump bundled esphome-device-builder to 1.0.14 (#17139) 2026-06-22 17:45:22 -05:00
Jonathan SwobodaandGitHub 1ace836744 [espidf] Don't fail framework check on broken unrelated PATH tools (#17053) 2026-06-22 18:41:21 -04:00
3a4831bd7e [ble_nus] Atomic log-line framing (no partial ring-buffer writes) (#17105)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: tomaszduda23 <tomaszduda23@gmail.com>
2026-06-22 21:04:11 +00:00
dependabot[bot]andGitHub 6c1724874b Bump zeroconf from 0.149.16 to 0.150.0 (#17137)
Signed-off-by: dependabot[bot] <support@github.com>
2026-06-22 15:04:22 -05:00
2483576909 [sx126x] Add data whitening options (#17102)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-06-22 10:51:10 -04:00
esphome[bot]andGitHub 0df1db6205 Bump bundled esphome-device-builder to 1.0.13 (#17132) 2026-06-22 00:01:22 -05:00
J. Nick KostonandGitHub 614eae7a3b [dashboard_import] Store package_import_url in flash on ESP8266 (#17127) 2026-06-21 23:18:06 -05:00
Jesse HillsandGitHub 2fe67a6eda [graphical_display_menu] Mark configurable classes as final (#17129) 2026-06-21 23:17:47 -05:00
Jesse HillsandGitHub e88f69b5f8 Mark configurable classes as final (7/21: gcja5-hlw8032) (#16958) 2026-06-21 21:05:05 -05:00
Jesse HillsandGitHub c69cfd44be Mark configurable classes as final (5/21) (#16956) 2026-06-21 21:04:55 -05:00
Jesse HillsandGitHub 9f5ed6fdfd Mark configurable classes as final (6/21) (#16957) 2026-06-21 21:04:45 -05:00
Jesse HillsandGitHub 0891473280 Mark configurable classes as final (10/21: matrix_keypad-micronova) (#16961) 2026-06-21 21:04:34 -05:00
Jesse HillsandGitHub 2982d7c834 Mark configurable classes as final (9/21) (#16960) 2026-06-21 21:04:16 -05:00
Jesse HillsandGitHub 026bac4cd1 [ld2420] Mark configurable classes as final (#17130) 2026-06-22 13:27:56 +12:00
J. Nick KostonandGitHub 7fcc890e84 [rp2040] Bump arduino-pico framework to 5.6.1 (#17122) 2026-06-22 13:02:46 +12:00
1838 changed files with 63267 additions and 20104 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
ARG BUILD_BASE_VERSION=2025.04.0
ARG BUILD_BASE_VERSION=2026.06.1
FROM ghcr.io/esphome/docker-base:debian-${BUILD_BASE_VERSION} AS base
+5 -2
View File
@@ -15,7 +15,6 @@
// uncomment and edit the path in order to pass through local USB serial to the container
// , "--device=/dev/ttyACM0"
],
"appPort": 6052,
// if you are using avahi in the host device, uncomment these to allow the
// devcontainer to find devices via mdns
//"mounts": [
@@ -41,7 +40,11 @@
],
"settings": {
"python.languageServer": "Pylance",
"python.pythonPath": "/usr/bin/python3",
// Use the container's pre-provisioned venv (built by the Dockerfile, outside the
// bind-mounted workspace) rather than a ./venv that may leak in from the host and
// mismatch the container's Python. See .devcontainer/Dockerfile (esphome-venv).
"python.defaultInterpreterPath": "/home/esphome/.local/esphome-venv/bin/python",
"python.terminal.activateEnvironment": true,
"pylint.args": [
"--rcfile=${workspaceFolder}/pyproject.toml"
],
+2
View File
@@ -1,3 +1,5 @@
# Normalize line endings to LF in the repository
* text eol=lf
*.png binary
*.gif binary
*.apng binary
+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
+2 -2
View File
@@ -42,7 +42,7 @@ runs:
- name: Build and push to ghcr by digest
id: build-ghcr
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
env:
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false
@@ -67,7 +67,7 @@ runs:
- name: Build and push to dockerhub by digest
id: build-dockerhub
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
env:
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false
+4 -2
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:
+49
View File
@@ -0,0 +1,49 @@
name: Cache nRF Connect SDK
description: >
Resolve the pinned sdk-nrf version and cache the native sdk-nrf install
(west workspace, Zephyr SDK toolchain, python env) at ~/.esphome-sdk-nrf.
Every job that installs sdk-nrf natively (the nrf52 clang-tidy job and,
once the component tests build natively, their batches) shares one cache.
Callers must set env ESPHOME_SDK_NRF_PREFIX: ~/.esphome-sdk-nrf and have
the Python venv already restored.
inputs:
restore-only:
description: >
When "true", only restore -- never save the cache, even on dev. Use from
jobs that may not produce a complete install (e.g. a component batch
that fails mid-install), so a partial install is never written.
default: "false"
runs:
using: composite
steps:
- name: Resolve sdk-nrf and toolchain versions for cache key
# Both versions are pinned in code, not in any file that feeds the
# other cache keys, so resolve them explicitly. Keying on them means
# the cache invalidates when either is bumped (actions/cache never
# overwrites a key).
id: version
shell: bash
run: |
. venv/bin/activate
version=$(python -c '
from esphome.components.nrf52 import RECOMMENDED_SDK_NRF_VERSION
from esphome.components.nrf52.framework import TOOLCHAIN_VERSION
print(f"{RECOMMENDED_SDK_NRF_VERSION}-{TOOLCHAIN_VERSION}")')
echo "version=$version" >> "$GITHUB_OUTPUT"
# Mirror cache-esp-idf: only dev-branch runs write the shared cache (so it
# lives in the default-branch scope readable by all PRs); PRs are
# restore-only and never push multi-GB artifacts into their own scope.
- name: Cache nRF Connect SDK install (write on dev)
if: github.ref == 'refs/heads/dev' && inputs.restore-only != 'true'
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.esphome-sdk-nrf
# yamllint disable-line rule:line-length
key: ${{ runner.os }}-esphome-sdk-nrf-${{ steps.version.outputs.version }}-${{ hashFiles('esphome/components/nrf52/requirements.txt') }}
- name: Cache nRF Connect SDK install (restore-only off dev)
if: github.ref != 'refs/heads/dev' || inputs.restore-only == 'true'
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.esphome-sdk-nrf
# yamllint disable-line rule:line-length
key: ${{ runner.os }}-esphome-sdk-nrf-${{ steps.version.outputs.version }}-${{ hashFiles('esphome/components/nrf52/requirements.txt') }}
+6 -3
View File
@@ -17,12 +17,12 @@ runs:
steps:
- name: Set up Python ${{ inputs.python-version }}
id: python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ inputs.python-version }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: venv
# yamllint disable-line rule:line-length
@@ -32,9 +32,12 @@ 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@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
enable-cache: true
# Pull request saves land in per-PR scopes nothing else can
# reuse; dev pushes seed the shared copy instead.
save-cache: ${{ github.event_name != 'pull_request' }}
# Pin uv version so the action does not have to fetch the
# manifest from raw.githubusercontent.com on every cache
# miss; that fetch flakes on Windows runners.
@@ -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'
]
};
+79 -16
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,8 +33,41 @@ 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;
@@ -42,7 +75,11 @@ async function detectMergeBranch(context) {
labels.add('merging-to-release');
} else if (baseRef === 'beta') {
labels.add('merging-to-beta');
} 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 !== 'dev') {
// A chain built by hand: it must not merge until its base branch does.
labels.add('chained-pr');
}
@@ -147,19 +184,9 @@ async function detectCoreChanges(changedFiles) {
}
// Strategy: PR size detection
async function detectPRSize(prFiles, totalAdditions, totalDeletions, totalChanges, isMegaPR, SMALL_PR_THRESHOLD, MEDIUM_PR_THRESHOLD, TOO_BIG_THRESHOLD) {
async function detectPRSize(prFiles, totalAdditions, totalDeletions, isMegaPR, SMALL_PR_THRESHOLD, MEDIUM_PR_THRESHOLD, TOO_BIG_THRESHOLD) {
const labels = new Set();
if (totalChanges <= SMALL_PR_THRESHOLD) {
labels.add('small-pr');
return labels;
}
if (totalChanges <= MEDIUM_PR_THRESHOLD) {
labels.add('medium-pr');
return labels;
}
const testAdditions = prFiles
.filter(file => file.filename.startsWith('tests/'))
.reduce((sum, file) => sum + (file.additions || 0), 0);
@@ -167,7 +194,24 @@ async function detectPRSize(prFiles, totalAdditions, totalDeletions, totalChange
.filter(file => file.filename.startsWith('tests/'))
.reduce((sum, file) => sum + (file.deletions || 0), 0);
const nonTestChanges = (totalAdditions - testAdditions) - (totalDeletions - testDeletions);
const nonTestAdditions = totalAdditions - testAdditions;
const nonTestDeletions = totalDeletions - testDeletions;
// small/medium count churn (additions + deletions) so a balanced refactor isn't undersized.
const nonTestChurn = nonTestAdditions + nonTestDeletions;
if (nonTestChurn <= SMALL_PR_THRESHOLD) {
labels.add('small-pr');
return labels;
}
if (nonTestChurn <= MEDIUM_PR_THRESHOLD) {
labels.add('medium-pr');
return labels;
}
// too-big uses net line delta (additions - deletions), matching the review message in reviews.js.
const nonTestChanges = nonTestAdditions - nonTestDeletions;
// Don't add too-big if mega-pr label is already present
if (nonTestChanges > TOO_BIG_THRESHOLD && !isMegaPR) {
@@ -238,6 +282,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' },
@@ -348,12 +393,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 =
@@ -369,6 +416,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 =>
+3 -3
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,12 +118,12 @@ 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),
detectCoreChanges(changedFiles),
detectPRSize(prFiles, totalAdditions, totalDeletions, totalChanges, isMegaPR, SMALL_PR_THRESHOLD, MEDIUM_PR_THRESHOLD, TOO_BIG_THRESHOLD),
detectPRSize(prFiles, totalAdditions, totalDeletions, isMegaPR, SMALL_PR_THRESHOLD, MEDIUM_PR_THRESHOLD, TOO_BIG_THRESHOLD),
detectDashboardChanges(changedFiles),
detectGitHubActionsChanges(changedFiles),
detectCodeOwner(github, context, changedFiles),
@@ -1,6 +1,14 @@
const { describe, it } = require('node:test');
const assert = require('node:assert/strict');
const { detectNewPlatforms, detectNewComponents } = 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,107 @@ 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 } = {}) {
const pull_request = { number: 1, base: { ref: baseRef } };
if (stack !== undefined) {
pull_request.stack = stack;
}
return {
repo: { owner: 'esphome', repo: 'esphome' },
payload: { pull_request }
};
}
// 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);
});
});
// ---------------------------------------------------------------------------
// detectNewPlatforms
// ---------------------------------------------------------------------------
@@ -145,3 +254,198 @@ describe('detectNewComponents', () => {
assert.equal(result.labels.size, 0);
});
});
// ---------------------------------------------------------------------------
// 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
// ---------------------------------------------------------------------------
describe('detectPRSize', () => {
const SMALL = 30;
const MEDIUM = 100;
const TOO_BIG = 1000;
function size(prFiles, isMegaPR = false) {
const totalAdditions = prFiles.reduce((sum, file) => sum + (file.additions || 0), 0);
const totalDeletions = prFiles.reduce((sum, file) => sum + (file.deletions || 0), 0);
return detectPRSize(prFiles, totalAdditions, totalDeletions, isMegaPR, SMALL, MEDIUM, TOO_BIG);
}
it('counts only non-test changes toward small-pr', async () => {
// 10 source + 5000 test lines -> non-test churn of 10 is still small.
const labels = await size([
{ filename: 'esphome/components/foo/foo.cpp', additions: 10, deletions: 0 },
{ filename: 'tests/components/foo/test.esp32-idf.yaml', additions: 5000, deletions: 0 },
]);
assert.ok(labels.has('small-pr'));
assert.equal(labels.size, 1);
});
it('counts additions and deletions as churn (not net delta)', async () => {
// A balanced refactor (40 added, 40 removed) is 80 lines of churn -> medium, not small.
const labels = await size([
{ filename: 'esphome/components/foo/foo.cpp', additions: 40, deletions: 40 },
]);
assert.ok(labels.has('medium-pr'));
assert.equal(labels.size, 1);
});
it('labels medium-pr when non-test changes exceed small threshold', async () => {
const labels = await size([
{ filename: 'esphome/components/foo/foo.cpp', additions: 60, deletions: 0 },
{ filename: 'tests/components/foo/test.esp32-idf.yaml', additions: 5000, deletions: 0 },
]);
assert.ok(labels.has('medium-pr'));
assert.equal(labels.size, 1);
});
it('uses net delta (not churn) for too-big', async () => {
// 600 added + 600 removed: 1200 churn (above too-big) but 0 net delta -> not too-big.
const labels = await size([
{ filename: 'esphome/components/foo/foo.cpp', additions: 600, deletions: 600 },
]);
assert.equal(labels.size, 0);
});
it('labels too-big when non-test changes exceed the big threshold', async () => {
const labels = await size([
{ filename: 'esphome/components/foo/foo.cpp', additions: 2000, deletions: 0 },
{ filename: 'tests/components/foo/test.esp32-idf.yaml', additions: 5000, deletions: 0 },
]);
assert.ok(labels.has('too-big'));
assert.equal(labels.size, 1);
});
it('does not label too-big when mega-pr is set', async () => {
const labels = await size([
{ filename: 'esphome/components/foo/foo.cpp', additions: 2000, deletions: 0 },
], true);
assert.equal(labels.size, 0);
});
it('produces no size label for a large mega-pr in the gap above medium', async () => {
// Non-test changes land between MEDIUM and TOO_BIG: not small/medium, and mega-pr suppresses too-big.
const labels = await size([
{ filename: 'esphome/components/foo/foo.cpp', additions: 500, deletions: 0 },
], true);
assert.equal(labels.size, 0);
});
});
+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
+7 -4
View File
@@ -21,17 +21,20 @@ 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@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.11"
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@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
enable-cache: true
# Pull-request-only workflow: a save could never be shared and
# would only consume quota.
save-cache: "false"
# Pin uv version so the action does not have to fetch the
# manifest from raw.githubusercontent.com on every cache
# miss; that fetch flakes on Windows runners.
+14 -12
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@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.11"
python-version: "3.12"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.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@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -145,16 +147,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@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.11"
python-version: "3.12"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Log in to the GitHub container registry
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -202,7 +204,7 @@ 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:
+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
@@ -60,7 +60,7 @@ jobs:
if: steps.pr.outputs.skip != 'true'
uses: ./.github/actions/restore-python
with:
python-version: "3.11"
python-version: "3.12"
cache-key: ${{ hashFiles('.cache-key') }}
- name: Download memory analysis artifacts
+428 -267
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@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
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@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
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@9f6577fd37b5cf773ab1b9be929714a0dcd15661 # 2026.7.0
+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:
+14 -14
View File
@@ -20,7 +20,7 @@ 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
# yamllint disable rule:line-length
@@ -60,9 +60,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@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.x"
- name: Build
@@ -70,7 +70,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 +92,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@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.11"
python-version: "3.12"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Log in to docker hub
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.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@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -168,7 +168,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 +178,17 @@ jobs:
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Log in to docker hub
if: matrix.registry == 'dockerhub'
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.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@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.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@203cea60ebfd18e2b966e57750750e0417a9feec # 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,
+6 -6
View File
@@ -28,16 +28,16 @@ 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@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.14"
@@ -47,7 +47,7 @@ jobs:
# setup-python interpreter so subsequent ``pre-commit`` /
# ``script/run-in-env.py`` steps find the deps without a
# ``uv run`` prefix.
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
enable-cache: true
# Pin uv version so the action does not have to fetch the
@@ -96,8 +96,8 @@ jobs:
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 -2
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.0
hooks:
# Run the linter.
- id: ruff
@@ -40,7 +40,7 @@ repos:
rev: v3.21.2
hooks:
- id: pyupgrade
args: [--py311-plus]
args: [--py312-plus]
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.37.1
hooks:
+72 -24
View File
@@ -9,7 +9,7 @@ This document provides essential context for AI models interacting with this pro
## 2. Core Technologies & Stack
* **Languages:** Python (>=3.11), C++ (gnu++20)
* **Languages:** Python (>=3.12), C++ (gnu++20)
* **Frameworks & Runtimes:** PlatformIO, Arduino, ESP-IDF.
* **Build Systems:** PlatformIO is the primary build system. CMake is used as an alternative.
* **Configuration:** YAML.
@@ -191,11 +191,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 +232,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 +246,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 +257,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 +277,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 +333,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 +388,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 +404,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
@@ -427,13 +451,14 @@ This document provides essential context for AI models interacting with this pro
When a PR's only edits to a component are `validate.*.yaml` files (no source changes, no `test.*.yaml` changes, and the component isn't pulled in as a dependency of another changed component), CI skips the compile stage for that component entirely and only runs config validation. This is decided in `script/determine-jobs.py` via `_component_change_is_validate_only` and surfaced as the `validate_only_components` output that the `test-build-components-split` job consumes.
* **Test Grouping with Packages:** Components that use shared bus packages can be grouped together in CI to reduce build count. **Never define buses (uart, i2c, spi, modbus) directly in test YAML files** — always use packages from `test_build_components/common/`:
* **Test Grouping with Packages:** Components that use shared bus packages can be grouped together in CI to reduce build count. **Never define buses (uart, i2c, spi, modbus) directly in test YAML files** — always use packages from `test_build_components/common/`.
All includes in test files must go through dict-style `packages:` so that batch grouping works correctly — the grouping scripts only understand dict-style packages. Never use list-style packages (`packages: [- !include ...]`) or top-level merge keys (`<<: !include common.yaml`). Bus packages are keyed by the bus name; the component's `common.yaml` is keyed by the component name (e.g. `cst328: !include common.yaml`):
```yaml
# test.esp32-idf.yaml — use packages for buses
# test.esp32-idf.yaml — everything included via named packages
packages:
uart: !include ../../test_build_components/common/uart_115200/esp32-idf.yaml
<<: !include common.yaml
my_component: !include common.yaml
```
```yaml
# common.yaml — component config only, NO bus definitions
@@ -470,7 +495,7 @@ This document provides essential context for AI models interacting with this pro
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.
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.
@@ -616,6 +641,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
@@ -635,20 +661,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)
```
@@ -703,9 +733,27 @@ 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
The project uses English for non-code content. When drafting documentation, code comments, commit messages,
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.
+17 -1
View File
@@ -69,11 +69,14 @@ 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_proxy/* @bdraco @jesserockz
esphome/components/bm8563/* @abmantis
@@ -122,7 +125,9 @@ esphome/components/cover/* @esphome/core
esphome/components/cs5460a/* @balrog-kun
esphome/components/cse7761/* @berfenger
esphome/components/cst226/* @clydebarrow
esphome/components/cst328/* @latonita
esphome/components/cst816/* @clydebarrow
esphome/components/cst9220/* @clydebarrow
esphome/components/ct_clamp/* @jesserockz
esphome/components/current_based/* @djwmarcx
esphome/components/dac7678/* @NickB1
@@ -143,6 +148,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
@@ -185,6 +191,7 @@ esphome/components/ezo_pmp/* @carlos-sarmiento
esphome/components/factory_reset/* @anatoly-savchenkov
esphome/components/fastled_base/* @OttoWinter
esphome/components/feedback/* @ianchi
esphome/components/file/* @esphome/core
esphome/components/fingerprint_grow/* @alexborro @loongyh @OnFreund
esphome/components/font/* @clydebarrow @esphome/core
esphome/components/fs3000/* @kahrendt
@@ -206,6 +213,7 @@ esphome/components/gree/switch/* @nagyrobi
esphome/components/grove_gas_mc_v2/* @YorkshireIoT
esphome/components/grove_tb6612fng/* @max246
esphome/components/growatt_solar/* @leeuwte
esphome/components/gsl3670/* @clydebarrow
esphome/components/gt911/* @clydebarrow @jesserockz
esphome/components/haier/* @paveldn
esphome/components/haier/binary_sensor/* @paveldn
@@ -266,6 +274,7 @@ esphome/components/integration/* @OttoWinter
esphome/components/internal_temperature/* @Mat931
esphome/components/interval/* @esphome/core
esphome/components/ir_rf_proxy/* @kbx81
esphome/components/it8951/* @koosoli @limengdu @Passific
esphome/components/jsn_sr04t/* @Mafus1
esphome/components/json/* @esphome/core
esphome/components/kamstrup_kmp/* @cfeenstra1024
@@ -286,6 +295,7 @@ 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/ln882x/* @lamauny
esphome/components/lock/* @esphome/core
esphome/components/logger/* @esphome/core
@@ -385,6 +395,7 @@ esphome/components/pcm5122/* @remcom
esphome/components/pi4ioe5v6408/* @jesserockz
esphome/components/pid/* @OttoWinter
esphome/components/pipsolar/* @andreashergert1984
esphome/components/pixoo/* @jesserockz
esphome/components/pm1006/* @habbie
esphome/components/pm2005/* @andrewjswan
esphome/components/pmsa003i/* @sjtrny
@@ -400,10 +411,12 @@ esphome/components/pn7160_i2c/* @jesserockz @kbx81
esphome/components/pn7160_spi/* @jesserockz @kbx81
esphome/components/power_supply/* @esphome/core
esphome/components/preferences/* @esphome/core
esphome/components/provisioning/* @esphome/core
esphome/components/psram/* @esphome/core
esphome/components/pulse_meter/* @cstaahl @stevebaxter @TrentHouliston
esphome/components/pvvx_mithermometer/* @pasiz
esphome/components/pylontech/* @functionpointer
esphome/components/qmi8658/* @clydebarrow
esphome/components/qmp6988/* @andrewpc
esphome/components/qr_code/* @wjtje
esphome/components/qspi_dbi/* @clydebarrow
@@ -421,7 +434,7 @@ esphome/components/rf_bridge/* @jesserockz
esphome/components/rgbct/* @jesserockz
esphome/components/ring_buffer/* @kahrendt
esphome/components/router/speaker/* @kahrendt
esphome/components/rp2040/* @jesserockz
esphome/components/rp2/* @jesserockz
esphome/components/rp2040_ble/* @bdraco
esphome/components/rp2040_pio_led_strip/* @Papa-DMan
esphome/components/rp2040_pwm/* @jesserockz
@@ -497,6 +510,7 @@ esphome/components/ssd1331_base/* @kbx81
esphome/components/ssd1331_spi/* @kbx81
esphome/components/ssd1351_base/* @kbx81
esphome/components/ssd1351_spi/* @kbx81
esphome/components/st7123/* @miniskipper
esphome/components/st7567_base/* @latonita
esphome/components/st7567_i2c/* @latonita
esphome/components/st7567_spi/* @latonita
@@ -578,6 +592,7 @@ esphome/components/wake_on_lan/* @clydebarrow @willwill2will54
esphome/components/watchdog/* @oarcher
esphome/components/water_heater/* @dhoeben
esphome/components/waveshare_epaper/* @clydebarrow
esphome/components/waveshare_io_ch32v003/* @latonita
esphome/components/web_server/ota/* @esphome/core
esphome/components/web_server_base/* @esphome/core
esphome/components/web_server_idf/* @dentra
@@ -613,6 +628,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.0-dev
PROJECT_NUMBER = 2026.8.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
View File
@@ -6,3 +6,4 @@ recursive-include esphome *.cpp *.h *.tcc *.c
recursive-include esphome *.py.script
recursive-include esphome *.jinja
recursive-include esphome LICENSE.txt
recursive-include esphome requirements.txt
+46
View File
@@ -79,6 +79,48 @@ These *are* security bugs in this repo, and we want to hear about them privately
- Flaws that weaken the device's API encryption (Noise), OTA, or web server auth
below their documented guarantees.
## The web server is an open HTTP API by design
The `web_server` component exposes a plain HTTP interface for viewing and
controlling entities, and, when the `web_server` OTA platform is enabled, for
uploading firmware at `/update`. Its only access controls are the optional
`web_server` `auth:` credentials and the network the device sits on.
When `auth:` is not configured, every endpoint is reachable by any client that
can reach the device. This is intentional; enabling `web_server` without `auth:`
is choosing an open control surface, in the same way that running native OTA
without a password leaves OTA open. The API is documented and is meant to be
called by other devices, scripts, and pages.
As defense-in-depth, the web server checks the `Origin` header on browser requests
to its entity control and state endpoints: a request whose `Origin` does not match
the address the device is served on is rejected, and the `allowed_origins` option
widens that list. This blocks the common "confused deputy" (CSRF) case where a page
the operator visits drives the device through their browser. It is **not** an
authentication boundary: it only constrains browsers. Any client that omits the
`Origin` header — `curl`, scripts, or other non-browser callers on the same
network — reaches every endpoint exactly as before. The check also does not cover
the web OTA `/update` endpoint. The device performs no CSRF-token or `Referer`
validation. The following are therefore **not** vulnerabilities in this repository:
- Requests without an `Origin` header (for example `curl`) reaching the control
endpoints, whether or not `web_server` `auth:` is set.
- Requests from an origin the operator added to `allowed_origins`.
- Cross-origin or CSRF firmware upload through the web OTA endpoint (`/update`) when
web OTA is enabled without `web_server` `auth:`. The `/update` endpoint is not
covered by the `Origin` check; this is the same exposure as running OTA without a
password.
The supported defenses are `web_server` `auth:`, protecting OTA (a web password or
a native OTA password), and keeping devices on a trusted, segmented network. See
the security best practices guide linked above.
What remains in scope is bypassing `web_server` `auth:` when it *is* configured,
and any memory-safety or protocol bug in the server reachable without credentials.
This section documents the current design and scope; it is not a judgment that the
design is optimal or that it will not change.
## Explicitly out of scope
- Local attackers who already have shell access on the host that runs `esphome`.
@@ -86,6 +128,10 @@ These *are* security bugs in this repo, and we want to hear about them privately
- Operator-supplied hostile YAML (covered above — config authoring is trusted).
- Attacks that require an already-authenticated device peer (someone who already
holds the API key / OTA / web credentials).
- Access to the device web server or its web OTA endpoint by non-browser clients
(those that send no `Origin` header). The web server is an open HTTP API by
design (see above); browser cross-origin requests are blocked by default, but the
real controls are `web_server` `auth:` and network isolation.
- Anything in the dashboard / device-builder — report that in its own repository
(linked at the top).
- Deployments where the operator removed protections or exposed credentials. See
+2 -12
View File
@@ -1,5 +1,5 @@
ARG BUILD_VERSION=dev
ARG BUILD_BASE_VERSION=2026.06.0
ARG BUILD_BASE_VERSION=2026.06.1
ARG BUILD_TYPE=docker
FROM ghcr.io/esphome/docker-base:debian-${BUILD_BASE_VERSION} AS base-source-docker
@@ -11,16 +11,6 @@ FROM base-source-${BUILD_TYPE} AS base
RUN git config --system --add safe.directory "*" \
&& git config --system advice.detachedHead false
# Install build tools for Python packages that require compilation
# (e.g., ruamel.yaml.clib used by ESP-IDF's idf-component-manager).
# Also install libusb-1.0 at runtime so the ESP-IDF tools installer can
# validate openocd-esp32 (it dynamically links libusb-1.0.so.0); without
# it idf_tools.py rejects the openocd install with exit 127 and aborts
# the whole framework setup.
RUN apt-get update \
&& apt-get install -y --no-install-recommends build-essential libusb-1.0-0 \
&& rm -rf /var/lib/apt/lists/*
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
RUN pip install --no-cache-dir -U pip uv==0.10.1
@@ -32,7 +22,7 @@ RUN \
-r /requirements.txt
# Install the ESPHome Device Builder dashboard.
RUN uv pip install --no-cache-dir esphome-device-builder==1.0.12
RUN uv pip install --no-cache-dir esphome-device-builder==1.9.0
RUN \
platformio settings set enable_telemetry No \
+5
View File
@@ -21,6 +21,11 @@ export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms"
export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages"
export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache"
# Keep the native toolchain installs on the persistent cache root, not the
# container's ephemeral user cache dir (re-downloaded on every restart).
export ESPHOME_ESP_IDF_PREFIX="$(dirname "${pio_cache_base}")/idf"
export ESPHOME_SDK_NRF_PREFIX="$(dirname "${pio_cache_base}")/sdk-nrf"
# If /build is mounted, use that as the build path
# otherwise use path in /config (so that builds aren't lost on container restart)
if [[ -d /build ]]; then
@@ -15,18 +15,15 @@ export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms"
export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages"
export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache"
# Keep the native toolchain installs on the persistent /data volume, not the
# container's ephemeral user cache dir (wiped on every add-on update/restart).
export ESPHOME_ESP_IDF_PREFIX=/data/cache/idf
export ESPHOME_SDK_NRF_PREFIX=/data/cache/sdk-nrf
if bashio::config.true 'leave_front_door_open'; then
export DISABLE_HA_AUTHENTICATION=true
fi
if bashio::config.true 'streamer_mode'; then
export ESPHOME_STREAMER_MODE=true
fi
if bashio::config.has_value 'relative_url'; then
export ESPHOME_DASHBOARD_RELATIVE_URL=$(bashio::config 'relative_url')
fi
if bashio::config.has_value 'default_compile_process_limit'; then
export ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT=$(bashio::config 'default_compile_process_limit')
else
+1 -1
View File
@@ -2,6 +2,6 @@ esphome:
name: docker-test-ln882x-arduino
ln882x:
board: generic-ln882hki
board: generic-ln882h
logger:
+72 -32
View File
@@ -16,14 +16,10 @@ import sys
import time
from typing import Protocol
import argcomplete
# 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.const import (
ALLOWED_NAME_CHARS,
ARGUMENT_HELP_DEVICE,
@@ -52,11 +48,6 @@ from esphome.const import (
CONF_WEB_SERVER,
CONF_WIFI,
ENV_NOGITIGNORE,
KEY_CORE,
KEY_TARGET_PLATFORM,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_RP2040,
SECRETS_FILES,
Toolchain,
)
@@ -230,8 +221,9 @@ def _discover_mac_suffix_devices() -> list[str] | None:
Returns:
- ``None`` when discovery isn't applicable (``name_add_mac_suffix`` off,
mDNS disabled, or ``CORE.address`` is already an IP). Callers should
then fall back to whatever default OTA address they normally use.
mDNS disabled, or ``CORE.address`` isn't a ``.local`` mDNS address).
Callers should then fall back to whatever default OTA address they
normally use.
- ``[]`` when discovery ran but found nothing. Callers should NOT fall
back to the base name: with ``name_add_mac_suffix`` enabled, the base
name by definition doesn't exist on the network.
@@ -241,7 +233,7 @@ def _discover_mac_suffix_devices() -> list[str] | None:
``aioesphomeapi`` via :func:`_resolve_network_devices`) reuses the IPs we
already have without opening a second Zeroconf client.
"""
if not (has_name_add_mac_suffix() and has_mdns() and has_non_ip_address()):
if not (has_name_add_mac_suffix() and has_mdns() and has_mdns_address()):
return None
from esphome.zeroconf import discover_mdns_devices
@@ -359,7 +351,7 @@ def choose_upload_log_host(
bootsel_permission_error = False
if (
purpose == Purpose.UPLOADING
and CORE.data.get(KEY_CORE, {}).get(KEY_TARGET_PLATFORM) == PLATFORM_RP2040
and CORE.is_rp2
and (picotool := _find_picotool()) is not None
):
bootsel = detect_rp2040_bootsel(picotool)
@@ -406,7 +398,7 @@ def choose_upload_log_host(
# Show helpful BOOTSEL instructions for RP2040 when no BOOTSEL device is found
if (
purpose == Purpose.UPLOADING
and CORE.data.get(KEY_CORE, {}).get(KEY_TARGET_PLATFORM) == PLATFORM_RP2040
and CORE.is_rp2
and not any(get_port_type(opt[1]) == PortType.BOOTSEL for opt in options)
):
if bootsel_permission_error:
@@ -508,17 +500,22 @@ def has_mdns() -> bool:
def has_non_ip_address() -> bool:
"""Check if CORE.address is set and is not an IP address."""
"""Check if ``CORE.address`` is set and is not an IP address."""
return CORE.address is not None and not is_ip_address(CORE.address)
def has_mdns_address() -> bool:
"""Check if ``CORE.address`` is a ``.local`` mDNS hostname."""
return CORE.address is not None and CORE.address.endswith(".local")
def has_ip_address() -> bool:
"""Check if CORE.address is a valid IP address."""
"""Check if ``CORE.address`` is a valid IP address."""
return CORE.address is not None and is_ip_address(CORE.address)
def has_resolvable_address() -> bool:
"""Check if CORE.address is resolvable (via mDNS, DNS, or is an IP address)."""
"""Check if ``CORE.address`` is resolvable (via mDNS, DNS, or is an IP address)."""
# Any address (IP, mDNS hostname, or regular DNS hostname) is resolvable
# The resolve_ip_address() function in helpers.py handles all types via AsyncResolver
if CORE.address is None:
@@ -537,7 +534,7 @@ def has_resolvable_address() -> bool:
return True
# .local mDNS hostnames are only resolvable if mDNS is enabled
return not CORE.address.endswith(".local")
return not has_mdns_address()
def has_name_add_mac_suffix() -> bool:
@@ -703,6 +700,8 @@ 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)
@@ -738,6 +737,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...")
@@ -775,6 +775,13 @@ def compile_program(args: ArgsProtocol, config: ConfigType) -> int:
check_placeholder_credentials(config)
# Keep this here, NOT in codegen: config-hash and --only-generate must keep
# working on machines that cannot run the toolchain.
if CORE.is_esp8266:
from esphome.components.esp8266 import check_rosetta
check_rosetta()
# NOTE: "Build path:" format is parsed by script/ci_memory_impact_extract.py
# If you change this format, update the regex in that script as well
_LOGGER.info("Compiling app... Build path: %s", CORE.build_path)
@@ -984,7 +991,7 @@ def upload_using_platformio(config: ConfigType, port: str) -> int:
# RP2040 platform-raspberrypi build recipe expects firmware.bin.signed for
# the upload target, but 'nobuild' skips the build phase that creates it.
# Create it here so the upload doesn't fail.
if CORE.data.get(KEY_CORE, {}).get(KEY_TARGET_PLATFORM) == PLATFORM_RP2040:
if CORE.is_rp2:
idedata = toolchain.get_idedata(config)
build_dir = Path(idedata.firmware_elf_path).parent
firmware_bin = build_dir / "firmware.bin"
@@ -1169,10 +1176,10 @@ def upload_program(
check_permissions(host)
exit_code = 1
if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266):
if CORE.is_esp32 or CORE.is_esp8266:
file = getattr(args, "file", None)
exit_code = upload_using_esptool(config, host, file, args.upload_speed)
elif CORE.target_platform == PLATFORM_RP2040 or CORE.is_libretiny:
elif CORE.is_rp2 or CORE.is_libretiny:
exit_code = upload_using_platformio(config, host)
# else: Unknown target platform, exit_code remains 1
@@ -1456,6 +1463,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)
@@ -1493,12 +1501,37 @@ _LEGACY_REDACTION_REMOVAL = "2026.12.0"
def _redact_with_legacy_fallback(output: str) -> str:
unmarked: set[str] = set()
# Track the top-level ``substitutions:`` block. Its keys are arbitrary
# user-chosen names with no schema validator, so the ``cv.sensitive(...)``
# migration named in the warning can't be applied to them. Their values are
# still redacted, but emitting the (unactionable) deprecation warning would
# only confuse users.
in_substitutions = False
def _replace(m: re.Match[str]) -> str:
unmarked.add(m.group("key"))
return f"{m.group('key')}: \\033[8m{m.group('val')}\\033[28m"
output = _LEGACY_REDACTION_RE.sub(_replace, output)
lines = output.split("\n")
for i, line in enumerate(lines):
# A non-indented, non-blank line is a top-level key that opens or
# closes the substitutions block.
if line and not line[0].isspace():
in_substitutions = line.startswith(f"{CONF_SUBSTITUTIONS}:")
m = _LEGACY_REDACTION_RE.search(line)
if m is None:
continue
key = m.group("key")
if not in_substitutions:
# Public keys (e.g. wireguard's peer_public_key) are not secret;
# redacting them and telling maintainers to mark them cv.sensitive
# would be wrong on both counts. Substitution keys are user-named
# with no schema behind them, so anything secret-shaped there
# (public or not) stays conservatively redacted.
if "public" in key.split("_"):
continue
unmarked.add(key)
lines[i] = (
f"{line[: m.start()]}{key}: "
f"\\033[8m{m.group('val')}\\033[28m{line[m.end() :]}"
)
output = "\n".join(lines)
for key in sorted(unmarked):
_LOGGER.warning(
"Field '%s' is being redacted by a legacy substring heuristic. "
@@ -1629,10 +1662,7 @@ def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
# After BOOTSEL upload, wait for a new serial port to appear
# so it shows up in the log chooser
if (
successful_device is None
and CORE.data.get(KEY_CORE, {}).get(KEY_TARGET_PLATFORM) == PLATFORM_RP2040
):
if successful_device is None and CORE.is_rp2:
_wait_for_serial_port(known_ports=pre_upload_ports)
# If exactly one new serial port appeared, use it directly
serial_ports = get_serial_ports()
@@ -2377,7 +2407,10 @@ def parse_args(argv):
)
parser_clean_all = subparsers.add_parser(
"clean-all", help="Clean all build and platform files."
"clean-all",
help="Clean all build and platform files, including machine-global "
"toolchain caches shared by all configurations, so other projects will "
"re-download them on next build.",
)
parser_clean_all.add_argument(
"configuration", help="Your YAML file or configuration directory.", nargs="*"
@@ -2465,7 +2498,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)
@@ -2564,6 +2602,8 @@ def run_esphome(argv):
)
if config is None:
from esphome.config import read_config
config = read_config(
command_line_substitutions,
skip_external_update=skip_external,
+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()
+34 -10
View File
@@ -8,7 +8,7 @@ memory-constrained platforms like ESP8266.
from __future__ import annotations
from collections import defaultdict
from dataclasses import dataclass
from dataclasses import dataclass, field
import logging
from pathlib import Path
import re
@@ -65,6 +65,7 @@ class RamSymbol:
size: int
section: str
demangled: str = "" # Demangled name, set after batch demangling
aliases: list[str] = field(default_factory=list) # Other names at same address
class RamStringsAnalyzer:
@@ -235,6 +236,11 @@ class RamStringsAnalyzer:
except (subprocess.CalledProcessError, FileNotFoundError):
return
# Track symbols by address so aliases (multiple names for the same
# object, e.g. the newlib __lock___* mutexes that all alias one
# StaticSemaphore_t) are reported once instead of once per name.
symbols_by_addr: dict[int, RamSymbol] = {}
for line in output.split("\n"):
parts = line.split()
if len(parts) < 4:
@@ -253,6 +259,18 @@ class RamStringsAnalyzer:
if sym_type not in DATA_SYMBOL_TYPES:
continue
if (existing := symbols_by_addr.get(addr)) is not None:
# Prefer a global (uppercase type) name as the primary so
# nm output order can't hide it behind a local alias.
if sym_type.isupper() and existing.sym_type.islower():
existing.aliases.append(existing.name)
existing.name = name
existing.sym_type = sym_type
else:
existing.aliases.append(name)
existing.size = max(existing.size, size)
continue
# Check if symbol is in a RAM section
for section_name in self.ram_sections:
if section_name not in self.sections:
@@ -260,15 +278,15 @@ class RamStringsAnalyzer:
section = self.sections[section_name]
if section.address <= addr < section.address + section.size:
self.ram_symbols.append(
RamSymbol(
name=name,
sym_type=sym_type,
address=addr,
size=size,
section=section_name,
)
symbol = RamSymbol(
name=name,
sym_type=sym_type,
address=addr,
size=size,
section=section_name,
)
symbols_by_addr[addr] = symbol
self.ram_symbols.append(symbol)
break
def _demangle_symbols(self) -> None:
@@ -436,7 +454,13 @@ class RamStringsAnalyzer:
for symbol in largest_symbols:
# Use demangled name if available, otherwise raw name
display_name = symbol.demangled or symbol.name
name_display = display_name[:49] if len(display_name) > 49 else display_name
# Truncate the name, not the alias note, so merged aliases stay
# visible even for long demangled C++ names.
alias_note = f" (+{len(symbol.aliases)} aliases)" if symbol.aliases else ""
max_name_len = 49 - len(alias_note)
if len(display_name) > max_name_len:
display_name = display_name[:max_name_len]
name_display = display_name + alias_note
lines.append(
f"{name_display:<50} {symbol.sym_type:<6} {symbol.size:>8} B {symbol.section}"
)
+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.
+3 -6
View File
@@ -12,12 +12,9 @@ from __future__ import annotations
import asyncio
from collections.abc import Awaitable, Callable
import threading
from typing import Generic, TypeVar
_T = TypeVar("_T")
class AsyncThreadRunner(threading.Thread, Generic[_T]):
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
@@ -35,10 +32,10 @@ class AsyncThreadRunner(threading.Thread, Generic[_T]):
result = runner.result
"""
def __init__(self, coro_factory: Callable[[], Awaitable[_T]]) -> None:
def __init__(self, coro_factory: Callable[[], Awaitable[T]]) -> None:
super().__init__(daemon=True)
self._coro_factory = coro_factory
self.result: _T | None = None
self.result: T | None = None
self.exception: BaseException | None = None
self.event = threading.Event()
+27 -1
View File
@@ -6,7 +6,11 @@ from pathlib import Path
from esphome.components.esp32 import get_esp32_variant, idf_version
import esphome.config_validation as cv
from esphome.core import CORE
from esphome.framework_helpers import get_project_compile_flags, get_project_link_flags
from esphome.framework_helpers import (
get_project_compile_flags,
get_project_cxx_compile_flags,
get_project_link_flags,
)
from esphome.helpers import mkdir_p, write_file_if_changed
# Replaces the IDF default C++ standard (-std=gnu++2b appended to
@@ -91,6 +95,14 @@ def get_project_cmakelists(minimal: bool = False) -> str:
for flag in project_compile_opts
)
# Flags registered via cg.add_cxx_build_flag() go on CXX_COMPILE_OPTIONS
# (not COMPILE_OPTIONS) because GCC warns when a C++-only flag such as
# -Wno-volatile is passed on a C compile.
cxx_compile_options = "\n".join(
f'idf_build_set_property(CXX_COMPILE_OPTIONS "{flag}" APPEND)'
for flag in get_project_cxx_compile_flags()
)
cpp_standard_options = (
CPP_STANDARD_TEMPLATE.format(standard=CORE.cpp_standard)
if CORE.cpp_standard
@@ -155,6 +167,8 @@ include($ENV{{IDF_PATH}}/tools/cmake/project.cmake)
{cpp_standard_options}
{cxx_compile_options}
{extra_compile_options}
{managed_components_property}
@@ -196,15 +210,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()
+2 -3
View File
@@ -108,7 +108,6 @@ Import("env")
def write_cxx_flags_script() -> None:
path = CORE.relative_build_path(CXX_FLAGS_FILE_NAME)
contents = CXX_FLAGS_FILE_CONTENTS
if not CORE.is_host:
contents += 'env.Append(CXXFLAGS=["-Wno-volatile"])'
contents += "\n"
for flag in sorted(CORE.cxx_build_flags):
contents += f'env.Append(CXXFLAGS=["{flag}"])\n'
write_file_if_changed(path, contents)
+135 -3
View File
@@ -7,12 +7,12 @@ and compiled directly: ``esphome compile my_device.esphomebundle.tar.gz``
from __future__ import annotations
from dataclasses import dataclass
from dataclasses import dataclass, field
from enum import StrEnum
import io
import json
import logging
from pathlib import Path
from pathlib import Path, PurePath, PurePosixPath, PureWindowsPath
import re
import shutil
import tarfile
@@ -32,6 +32,8 @@ from esphome.core import CORE, EsphomeError
_LOGGER = logging.getLogger(__name__)
DOMAIN = "bundle"
BUNDLE_EXTENSION = ".esphomebundle.tar.gz"
MANIFEST_FILENAME = "manifest.json"
CURRENT_MANIFEST_VERSION = 1
@@ -49,6 +51,7 @@ class ManifestKey(StrEnum):
MANIFEST_VERSION = "manifest_version"
ESPHOME_VERSION = "esphome_version"
CONFIG_FILENAME = "config_filename"
CONFIG_DIR = "config_dir"
FILES = "files"
HAS_SECRETS = "has_secrets"
@@ -120,6 +123,126 @@ def _find_used_secret_keys(yaml_files: list[Path]) -> set[str]:
return keys
@dataclass
class BundleData:
"""Files components asked to include, keyed under DOMAIN in CORE.data."""
extra_files: list[Path] = field(default_factory=list)
# 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;
# CORE.data is cleared between runs.
original_config_dir: PurePath | None = None
original_config_dir_checked: bool = False
def _get_data() -> BundleData:
if DOMAIN not in CORE.data:
CORE.data[DOMAIN] = BundleData()
return CORE.data[DOMAIN]
def add_bundle_file(path: Path) -> None:
"""Register a file that a bundle must include.
Bundle discovery walks the validated config, so it only finds files the config
names. Components call this during validation for files it cannot see, such as a
file that is referenced from inside another file.
A relative path is taken as relative to the config directory. Files outside the
config directory are skipped when the bundle is built.
"""
_get_data().extra_files.append(CORE.relative_config_path(path))
# 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.
_WINDOWS_DRIVE_RE = re.compile(r"^[A-Za-z]:")
def _path_flavor(value: str) -> type[PurePath]:
"""Pick the pure path class matching the flavor ``value`` was written in."""
if "\\" in value or _WINDOWS_DRIVE_RE.match(value):
return PureWindowsPath
return PurePosixPath
def _load_original_config_dir() -> PurePath | None:
"""Read the original config dir from an extracted bundle's manifest.
Returns None when the current config dir is not an extracted bundle or
the manifest does not record the original config dir.
"""
manifest_path = CORE.config_dir / MANIFEST_FILENAME
try:
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
except FileNotFoundError:
# The common case: this config dir is not an extracted bundle.
return None
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as err:
# A manifest.json is present but unreadable or malformed. Say so
# instead of letting it look identical to "not a bundle".
_LOGGER.warning("Bundle: ignoring unreadable %s: %s", manifest_path, err)
return None
if not isinstance(manifest, dict):
return None
# A manifest.json in the config dir does not have to be ours. Only trust
# one that looks like a bundle manifest for exactly this config file.
version = manifest.get(ManifestKey.MANIFEST_VERSION)
if not isinstance(version, int) or version < 1:
return None
if manifest.get(ManifestKey.CONFIG_FILENAME) != CORE.config_path.name:
return None
config_dir = manifest.get(ManifestKey.CONFIG_DIR)
if not isinstance(config_dir, str) or not config_dir:
return None
return _path_flavor(config_dir)(config_dir)
def remap_bundle_path(value: str) -> Path | None:
"""Remap an absolute path from the machine a bundle was created on.
A bundled config may reference files by absolute path. The referenced
files ship inside the bundle at their config-relative locations, but the
YAML text is copied verbatim, so after extraction on another machine the
absolute reference points at a path that only existed on the creating
machine. The bundle manifest records that machine's config dir; when
``value`` names a path that lived under it, return the corresponding
file next to the extracted config.
``value`` is the raw path string from the config. It is parsed with the
original machine's path flavor, so a bundle created on Windows remaps on
a POSIX build server and vice versa.
Returns None when not compiling an extracted bundle, when ``value`` was
not under the original config dir, or when the bundle does not contain
the file.
"""
data = _get_data()
if not data.original_config_dir_checked:
data.original_config_dir_checked = True
data.original_config_dir = _load_original_config_dir()
original_dir = data.original_config_dir
if original_dir is None:
return None
path = type(original_dir)(value)
if not path.is_absolute():
return None
try:
rel = path.relative_to(original_dir)
except ValueError:
return None
# relative_to is lexical, so ".." segments survive it. Refuse them: the
# remapped file must land strictly inside the extracted config tree.
if ".." in rel.parts:
return None
remapped = CORE.relative_config_path(Path(*rel.parts))
if not remapped.exists():
return None
return remapped
@dataclass
class BundleFile:
"""A file to include in the bundle."""
@@ -146,6 +269,7 @@ class BundleManifest:
config_filename: str
files: list[str]
has_secrets: bool
config_dir: str | None = None
class ConfigBundleCreator:
@@ -286,13 +410,18 @@ class ConfigBundleCreator:
with known file extensions are also resolved and checked.
Core ESPHome concepts that use relative paths or directories
are handled explicitly.
are handled explicitly. Files the config does not name at all are
registered by their component with add_bundle_file().
"""
config = self._config
# Generic walk: find all file paths in the validated config
self._walk_config_for_files(config)
# Files registered by components during validation
for extra_file in _get_data().extra_files:
self._add_file(extra_file)
# --- Core ESPHome concepts needing explicit handling ---
# esphome.includes / includes_c - can be relative paths and directories
@@ -405,6 +534,7 @@ class ConfigBundleCreator:
ManifestKey.MANIFEST_VERSION: CURRENT_MANIFEST_VERSION,
ManifestKey.ESPHOME_VERSION: const.__version__,
ManifestKey.CONFIG_FILENAME: self._config_path.name,
ManifestKey.CONFIG_DIR: str(self._config_dir),
ManifestKey.FILES: [f.path for f in files],
ManifestKey.HAS_SECRETS: has_secrets,
}
@@ -489,12 +619,14 @@ def read_bundle_manifest(bundle_path: Path) -> BundleManifest:
except tarfile.TarError as err:
raise EsphomeError(f"Failed to read bundle: {err}") from err
config_dir = manifest.get(ManifestKey.CONFIG_DIR)
return BundleManifest(
manifest_version=manifest[ManifestKey.MANIFEST_VERSION],
esphome_version=manifest.get(ManifestKey.ESPHOME_VERSION, "unknown"),
config_filename=manifest[ManifestKey.CONFIG_FILENAME],
files=manifest.get(ManifestKey.FILES, []),
has_secrets=manifest.get(ManifestKey.HAS_SECRETS, False),
config_dir=config_dir if isinstance(config_dir, str) else None,
)
+2
View File
@@ -25,6 +25,7 @@ from esphome.cpp_generator import ( # noqa: F401
add,
add_build_flag,
add_build_unflag,
add_cxx_build_flag,
add_define,
add_global,
add_library,
@@ -52,6 +53,7 @@ from esphome.cpp_helpers import ( # noqa: F401
past_safe_mode,
register_component,
register_parented,
set_setup_priority,
)
from esphome.cpp_types import ( # noqa: F401
NAN,
+6
View File
@@ -0,0 +1,6 @@
# Importing `esphome.loader` here installs the component-alias
# ``sys.meta_path`` finder before any submodule lookup runs. Without this,
# `from esphome.components import <legacy_alias>` from a fresh interpreter
# can race the finder install and raise ImportError, since the legacy
# alias dir no longer exists on disk.
from esphome import loader as _loader # noqa: F401
+1 -1
View File
@@ -25,7 +25,7 @@ void A01nyubComponent::check_buffer_() {
if (this->buffer_[3] == checksum) {
float distance = (this->buffer_[1] << 8) + this->buffer_[2];
if (distance > 280) {
float meters = distance / 1000.0;
float meters = distance / 1000.0f;
ESP_LOGV(TAG, "Distance from sensor: %f mm, %f m", distance, meters);
this->publish_state(meters);
} else {
+1 -1
View File
@@ -216,7 +216,7 @@ void AcDimmer::setup() {
}
void AcDimmer::write_state(float state) {
state = std::acos(1 - (2 * state)) / std::numbers::pi; // RMS power compensation
state = std::acos(1 - (2 * state)) / std::numbers::pi_v<float>; // RMS power compensation
auto new_value = static_cast<uint16_t>(roundf(state * 65535));
if (new_value != 0 && this->store_.value == 0)
this->store_.init_cycle = this->init_with_half_cycle_;
+4 -4
View File
@@ -227,12 +227,12 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
def validate_adc_pin(value):
if str(value).upper() == "VCC":
if CORE.is_rp2040:
if CORE.is_rp2:
return pins.internal_gpio_input_pin_schema(29)
return cv.only_on([PLATFORM_ESP8266])("VCC")
if str(value).upper() == "TEMPERATURE":
return cv.only_on_rp2040("TEMPERATURE")
return cv.only_on_rp2("TEMPERATURE")
if CORE.is_esp32:
conf = pins.internal_gpio_input_pin_schema(value)
@@ -261,11 +261,11 @@ def validate_adc_pin(value):
raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC")
return conf
if CORE.is_rp2040:
if CORE.is_rp2:
conf = pins.internal_gpio_input_pin_schema(value)
number = conf[CONF_NUMBER]
if number not in (26, 27, 28, 29):
raise cv.Invalid("RP2040: Only pins 26, 27, 28 and 29 support ADC")
raise cv.Invalid("RP2: Only pins 26, 27, 28 and 29 support ADC")
return conf
if CORE.is_libretiny:
+4 -4
View File
@@ -123,9 +123,9 @@ class ADCSensor final : public sensor::Sensor, public PollingComponent, public v
void set_autorange(bool autorange) { this->autorange_ = autorange; }
#endif // USE_ESP32
#ifdef USE_RP2040
#ifdef USE_RP2
void set_is_temperature() { this->is_temperature_ = true; }
#endif // USE_RP2040
#endif // USE_RP2
protected:
uint8_t sample_count_{1};
@@ -152,9 +152,9 @@ class ADCSensor final : public sensor::Sensor, public PollingComponent, public v
static adc_oneshot_unit_handle_t shared_adc_handles[2];
#endif // USE_ESP32
#ifdef USE_RP2040
#ifdef USE_RP2
bool is_temperature_{false};
#endif // USE_RP2040
#endif // USE_RP2
#ifdef USE_ZEPHYR
const struct adc_dt_spec *channel_ = nullptr;
@@ -1,4 +1,4 @@
#ifdef USE_RP2040
#ifdef USE_RP2
#include "adc_sensor.h"
#include "esphome/core/log.h"
@@ -17,7 +17,7 @@
namespace esphome::adc {
static const char *const TAG = "adc.rp2040";
static const char *const TAG = "adc.rp2";
void ADCSensor::setup() {
static bool initialized = false;
@@ -66,15 +66,18 @@ float ADCSensor::sample() {
}
uint8_t pin = this->pin_->get_pin();
#ifdef CYW43_USES_VSYS_PIN
#if defined(CYW43_USES_VSYS_PIN) && defined(USE_WIFI)
if (pin == PICO_VSYS_PIN) {
// Measuring VSYS on Raspberry Pico W needs to be wrapped with
// `cyw43_thread_enter()`/`cyw43_thread_exit()` as discussed in
// https://github.com/raspberrypi/pico-sdk/issues/1222, since Wifi chip and
// VSYS ADC both share GPIO29
// VSYS ADC both share GPIO29.
// The USE_WIFI guard is required because CYW43_USES_VSYS_PIN can be defined
// transitively (e.g. via lwip_wrap.h) even on non-WiFi boards where the CYW43
// driver is never initialized; calling cyw43_thread_enter() there hard-faults.
cyw43_thread_enter();
}
#endif // CYW43_USES_VSYS_PIN
#endif // defined(CYW43_USES_VSYS_PIN) && defined(USE_WIFI)
adc_gpio_init(pin);
adc_select_input(pin - 26);
@@ -84,11 +87,11 @@ float ADCSensor::sample() {
aggr.add_sample(raw);
}
#ifdef CYW43_USES_VSYS_PIN
#if defined(CYW43_USES_VSYS_PIN) && defined(USE_WIFI)
if (pin == PICO_VSYS_PIN) {
cyw43_thread_exit();
}
#endif // CYW43_USES_VSYS_PIN
#endif // defined(CYW43_USES_VSYS_PIN) && defined(USE_WIFI)
if (this->output_raw_) {
return aggr.aggregate();
@@ -99,4 +102,4 @@ float ADCSensor::sample() {
} // namespace esphome::adc
#endif // USE_RP2040
#endif // USE_RP2
+17 -7
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
@@ -113,6 +113,18 @@ CONFIG_SCHEMA = cv.All(
CONF_ADC_CHANNEL_ID = "adc_channel_id"
def _overlay_io_channels():
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):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
@@ -173,9 +185,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 +201,7 @@ async def to_code(config):
zephyr,oversampling = <8>;
}};
}};
"""
)
""")
FILTER_SOURCE_FILES = filter_source_files_from_platform(
@@ -201,7 +211,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
PlatformFramework.ESP32_IDF,
},
"adc_sensor_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
"adc_sensor_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
"adc_sensor_rp2.cpp": {PlatformFramework.RP2_ARDUINO},
"adc_sensor_libretiny.cpp": {
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
+5 -5
View File
@@ -114,13 +114,13 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
this->decoder_->decode(param->notify.value, param->notify.value_len);
if (this->decoder_->has_position()) {
this->position = ((float) this->decoder_->position_ / 100.0);
this->position = ((float) this->decoder_->position_ / 100.0f);
if (!this->invert_position_)
this->position = 1 - this->position;
if (this->position > 0.97)
this->position = 1.0;
if (this->position < 0.02)
this->position = 0.0;
if (this->position > 0.97f)
this->position = 1.0f;
if (this->position < 0.02f)
this->position = 0.0f;
this->publish_state();
}
+20 -98
View File
@@ -1,114 +1,36 @@
import logging
# ---------------------------------------------------------------------------
# Legacy top-level `animation:` deprecation shim -- REMOVE this whole file after
# 2027.1.0.
#
# Animations are now a platform of the `image:` component (`platform:
# animation`); the real schema, actions and codegen live in `image.py`. This
# module only keeps the deprecated top-level `animation:` key working during the
# deprecation window: it reuses that schema/codegen and adds a one-shot
# deprecation warning (with a pasteable migrated `image:` block) at validation
# time. Deleting this file drops the top-level form entirely.
# ---------------------------------------------------------------------------
from esphome import automation
import esphome.codegen as cg
from esphome.components.const import CONF_LOOP
import esphome.components.image as espImage
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_REPEAT
_LOGGER = logging.getLogger(__name__)
from .image import ANIMATION_CONFIG_SCHEMA, setup_animation
AUTO_LOAD = ["image"]
AUTO_LOAD = ["image", "file"]
CODEOWNERS = ["@syndlex"]
DEPENDENCIES = ["display"]
MULTI_CONF = True
MULTI_CONF_NO_DEFAULT = True
CONF_START_FRAME = "start_frame"
CONF_END_FRAME = "end_frame"
CONF_FRAME = "frame"
DOMAIN = "animation"
animation_ns = cg.esphome_ns.namespace("animation")
LEGACY_REMOVAL_VERSION = "2027.1.0"
Animation_ = animation_ns.class_("Animation", espImage.Image_)
# Actions
NextFrameAction = animation_ns.class_(
"AnimationNextFrameAction", automation.Action, cg.Parented.template(Animation_)
)
PrevFrameAction = animation_ns.class_(
"AnimationPrevFrameAction", automation.Action, cg.Parented.template(Animation_)
)
SetFrameAction = animation_ns.class_(
"AnimationSetFrameAction", automation.Action, cg.Parented.template(Animation_)
_capture_legacy_entry, _warn_legacy_animation = (
espImage.legacy_platform_migration_warning(DOMAIN, DOMAIN, LEGACY_REMOVAL_VERSION)
)
CONFIG_SCHEMA = cv.All(
espImage.IMAGE_SCHEMA.extend(
{
cv.Required(CONF_ID): cv.declare_id(Animation_),
cv.Optional(CONF_LOOP): cv.All(
{
cv.Optional(CONF_START_FRAME, default=0): cv.positive_int,
cv.Optional(CONF_END_FRAME): cv.positive_int,
cv.Optional(CONF_REPEAT): cv.positive_int,
}
),
},
),
espImage.validate_settings,
)
CONFIG_SCHEMA = cv.All(_capture_legacy_entry, ANIMATION_CONFIG_SCHEMA)
FINAL_VALIDATE_SCHEMA = _warn_legacy_animation
NEXT_FRAME_SCHEMA = automation.maybe_simple_id(
{
cv.GenerateID(): cv.use_id(Animation_),
}
)
PREV_FRAME_SCHEMA = automation.maybe_simple_id(
{
cv.GenerateID(): cv.use_id(Animation_),
}
)
SET_FRAME_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.use_id(Animation_),
cv.Required(CONF_FRAME): cv.uint16_t,
}
)
@automation.register_action(
"animation.next_frame", NextFrameAction, NEXT_FRAME_SCHEMA, synchronous=True
)
@automation.register_action(
"animation.prev_frame", PrevFrameAction, PREV_FRAME_SCHEMA, synchronous=True
)
@automation.register_action(
"animation.set_frame", SetFrameAction, SET_FRAME_SCHEMA, synchronous=True
)
async def animation_action_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
if (frame := config.get(CONF_FRAME)) is not None:
template_ = await cg.templatable(frame, args, cg.uint16)
cg.add(var.set_frame(template_))
return var
async def to_code(config):
(
prog_arr,
width,
height,
image_type,
trans_value,
frame_count,
) = await espImage.write_image(config, all_frames=True)
var = cg.new_Pvariable(
config[CONF_ID],
prog_arr,
width,
height,
frame_count,
image_type,
trans_value,
)
if loop_config := config.get(CONF_LOOP):
start = loop_config[CONF_START_FRAME]
end = loop_config.get(CONF_END_FRAME, frame_count)
count = loop_config.get(CONF_REPEAT, -1)
cg.add(var.set_loop(start, end, count))
to_code = setup_animation
+115
View File
@@ -0,0 +1,115 @@
from esphome import automation
import esphome.codegen as cg
from esphome.components.const import CONF_LOOP
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.types import ConfigType
CODEOWNERS = ["@syndlex"]
AUTO_LOAD = ["file"]
DEPENDENCIES = ["display"]
CONF_START_FRAME = "start_frame"
CONF_END_FRAME = "end_frame"
CONF_FRAME = "frame"
animation_ns = cg.esphome_ns.namespace("animation")
Animation_ = animation_ns.class_("Animation", Image_)
# Actions
NextFrameAction = animation_ns.class_(
"AnimationNextFrameAction", automation.Action, cg.Parented.template(Animation_)
)
PrevFrameAction = animation_ns.class_(
"AnimationPrevFrameAction", automation.Action, cg.Parented.template(Animation_)
)
SetFrameAction = animation_ns.class_(
"AnimationSetFrameAction", automation.Action, cg.Parented.template(Animation_)
)
ANIMATION_SCHEMA = image_schema(Animation_).extend(
{
cv.Optional(CONF_LOOP): cv.All(
{
cv.Optional(CONF_START_FRAME, default=0): cv.positive_int,
cv.Optional(CONF_END_FRAME): cv.positive_int,
cv.Optional(CONF_REPEAT): cv.positive_int,
}
),
},
)
# Shared schema used by both the (deprecated) top-level `animation:` key and the
# `image:` `platform: animation` entry.
ANIMATION_CONFIG_SCHEMA = cv.All(ANIMATION_SCHEMA, validate_settings)
NEXT_FRAME_SCHEMA = automation.maybe_simple_id(
{
cv.GenerateID(): cv.use_id(Animation_),
}
)
PREV_FRAME_SCHEMA = automation.maybe_simple_id(
{
cv.GenerateID(): cv.use_id(Animation_),
}
)
SET_FRAME_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.use_id(Animation_),
cv.Required(CONF_FRAME): cv.uint16_t,
}
)
@automation.register_action(
"animation.next_frame", NextFrameAction, NEXT_FRAME_SCHEMA, synchronous=True
)
@automation.register_action(
"animation.prev_frame", PrevFrameAction, PREV_FRAME_SCHEMA, synchronous=True
)
@automation.register_action(
"animation.set_frame", SetFrameAction, SET_FRAME_SCHEMA, synchronous=True
)
async def animation_action_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
if (frame := config.get(CONF_FRAME)) is not None:
template_ = await cg.templatable(frame, args, cg.uint16)
cg.add(var.set_frame(template_))
return var
async def setup_animation(config: ConfigType) -> None:
(
prog_arr,
width,
height,
image_type,
trans_value,
frame_count,
) = await write_image(config, all_frames=True)
var = cg.new_Pvariable(
config[CONF_ID],
prog_arr,
width,
height,
frame_count,
image_type,
trans_value,
)
if loop_config := config.get(CONF_LOOP):
start = loop_config[CONF_START_FRAME]
end = loop_config.get(CONF_END_FRAME, frame_count)
count = loop_config.get(CONF_REPEAT, -1)
cg.add(var.set_loop(start, end, count))
CONFIG_SCHEMA = ANIMATION_CONFIG_SCHEMA
to_code = setup_animation
+2 -2
View File
@@ -6,9 +6,9 @@
namespace esphome::anova {
float ftoc(float f) { return (f - 32.0) * (5.0f / 9.0f); }
float ftoc(float f) { return (f - 32.0f) * (5.0f / 9.0f); }
float ctof(float c) { return (c * 9.0f / 5.0f) + 32.0; }
float ctof(float c) { return (c * 9.0f / 5.0f) + 32.0f; }
AnovaPacket *AnovaCodec::clean_packet_() {
this->packet_.length = strlen((char *) this->packet_.data);
+35 -7
View File
@@ -112,6 +112,23 @@ CONF_MAX_SEND_QUEUE = "max_send_queue"
CONF_STATE_SUBSCRIPTION_ONLY = "state_subscription_only"
def _register_provisioning_source(config: ConfigType) -> ConfigType:
"""Register the API as a provisioning source when encryption is enabled.
With no ``key`` the device boots unprovisioned and is set up on first
connection; a YAML ``key`` means it is born provisioned. Either way the API
drives the provisioning manager, so it counts as a source for `provisioning:`.
A hardcoded ``key`` is reported so `provisioning:` can warn about it.
"""
if (encryption := config.get(CONF_ENCRYPTION)) is not None:
from esphome.components import provisioning
provisioning.register_source("api")
if CONF_KEY in encryption:
provisioning.report_hardcoded_credentials("api")
return config
def validate_encryption_key(value):
value = cv.string_strict(value)
try:
@@ -300,21 +317,23 @@ CONFIG_SCHEMA = cv.All(
CONF_LISTEN_BACKLOG,
esp8266=1, # Limited RAM (~40KB free), LWIP raw sockets
esp32=4, # More RAM (520KB), BSD sockets
rp2040=1, # Limited RAM (264KB), LWIP raw sockets like ESP8266
rp2=1, # Limited RAM (264KB), LWIP raw sockets like ESP8266
bk72xx=4, # Moderate RAM, BSD-style sockets
rtl87xx=4, # Moderate RAM, BSD-style sockets
host=4, # Abundant resources
ln882x=4, # Moderate RAM
nrf52=4, # ~256KB RAM, BSD sockets
): cv.int_range(min=1, max=10),
cv.SplitDefault(
CONF_MAX_CONNECTIONS,
esp8266=4, # ~40KB free RAM, each connection uses ~500-1000 bytes
esp32=5, # 520KB RAM available
rp2040=4, # 264KB RAM but LWIP constraints
rp2=4, # 264KB RAM but LWIP constraints
bk72xx=5, # Moderate RAM
rtl87xx=5, # Moderate RAM
host=8, # Abundant resources
ln882x=5, # Moderate RAM
nrf52=4, # ~256KB RAM, BSD sockets, Thread (single HA controller)
): cv.int_range(min=1, max=20),
# Maximum queued send buffers per connection before dropping connection
# Each buffer uses ~8-12 bytes overhead plus actual message size
@@ -324,7 +343,7 @@ CONFIG_SCHEMA = cv.All(
CONF_MAX_SEND_QUEUE,
esp8266=4, # Limited RAM, need to fail fast
esp32=8, # More RAM, can buffer more
rp2040=8, # Moderate RAM
rp2=8, # Moderate RAM
bk72xx=8, # Moderate RAM
nrf52=8, # Moderate RAM
rtl87xx=8, # Moderate RAM
@@ -335,6 +354,7 @@ CONFIG_SCHEMA = cv.All(
).extend(cv.COMPONENT_SCHEMA),
cv.rename_key(CONF_SERVICES, CONF_ACTIONS),
_consume_api_sockets,
_register_provisioning_source,
)
@@ -468,8 +488,11 @@ async def to_code(config: ConfigType) -> None:
cg.add_define("USE_API_NOISE_PSK_FROM_YAML")
else:
# No key provided, but encryption desired
# This will allow a plaintext client to provide a noise key,
# send it to the device, and then switch to noise.
# Until a key is set, the device accepts both Noise connections
# using the well-known all-zeros PSK (preferred: the key travels
# encrypted, protecting against passive sniffing) and plaintext
# connections (deprecated, remove after 2027.2.0) so a client can
# provide a noise key and the device then switches to noise only.
# The key will be saved in flash and used for future connections
# and plaintext disabled. Only a factory reset can remove it.
cg.add_define("USE_API_PLAINTEXT")
@@ -538,17 +561,20 @@ HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
)
# synchronous=False: when on_success/on_error is configured, play() stores the
# trigger args until the HomeassistantActionResponse arrives, so non-owning args
# (StringRef into the API receive buffer) must not be used.
@automation.register_action(
"homeassistant.action",
HomeAssistantServiceCallAction,
HOMEASSISTANT_ACTION_ACTION_SCHEMA,
synchronous=True,
synchronous=False,
)
@automation.register_action(
"homeassistant.service",
HomeAssistantServiceCallAction,
HOMEASSISTANT_ACTION_ACTION_SCHEMA,
synchronous=True,
synchronous=False,
)
async def homeassistant_service_to_code(
config: ConfigType,
@@ -642,6 +668,8 @@ HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema(
)
# synchronous=True is safe here: the event schema has no on_success/on_error,
# so play() never stores the trigger args.
@automation.register_action(
"homeassistant.event",
HomeAssistantServiceCallAction,
+19
View File
@@ -158,6 +158,16 @@ message AuthenticationResponse {
bool invalid_password = 1;
}
// Reason a party is requesting the connection be closed.
enum DisconnectReason {
// No specific reason / not provided (default for older peers).
DISCONNECT_REASON_UNSPECIFIED = 0;
// The device's provisioning window has expired. The device must be reset
// (power-cycled) to reopen the provisioning window before it will accept a
// connection again.
DISCONNECT_REASON_PROVISIONING_CLOSED = 1;
}
// Request to close the connection.
// Can be sent by both the client and server
message DisconnectRequest {
@@ -166,6 +176,10 @@ message DisconnectRequest {
option (no_delay) = true;
// Do not close the connection before the acknowledgement arrives
// Optional reason the connection is being closed. Older peers that do not
// send this field will report DISCONNECT_REASON_UNSPECIFIED (0).
DisconnectReason reason = 1;
}
message DisconnectResponse {
@@ -296,6 +310,11 @@ message DeviceInfoResponse {
// Serial proxy instance metadata
repeated SerialProxyInfo serial_proxies = 25 [(field_ifdef) = "USE_SERIAL_PROXY", (fixed_array_size_define) = "SERIAL_PROXY_COUNT"];
// Device is unprovisioned and accepts Noise handshakes with the well-known
// all-zeros PSK, so the api encryption key can be provisioned without being
// sent in plaintext (protects against passive sniffing, not active MITM)
bool api_encryption_provisionable = 26 [(field_ifdef) = "USE_API_NOISE"];
}
message ListEntitiesRequest {
+86 -30
View File
@@ -25,6 +25,9 @@
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/version.h"
#ifdef USE_PROVISIONING
#include "esphome/components/provisioning/provisioning.h"
#endif
#ifdef USE_DEEP_SLEEP
#include "esphome/components/deep_sleep/deep_sleep_component.h"
@@ -195,6 +198,29 @@ APIConnection::~APIConnection() {
#endif
}
#if defined(USE_API_NOISE) && defined(USE_API_PLAINTEXT)
void APIConnection::upgrade_helper_to_noise_() {
// The client opened with a Noise hello while this device has no encryption
// key set. Replace the plaintext helper with a Noise helper so the key can
// be provisioned over an encrypted channel: the noise context PSK is all
// zeros when unprovisioned, and NNpsk0 still runs a fresh ephemeral X25519
// exchange, so a passive listener cannot read the session. A publicly known
// PSK authenticates nobody; this protects against sniffing only.
auto *plaintext = static_cast<APIPlaintextFrameHelper *>(this->helper_.get());
uint8_t header[3];
uint8_t header_len = plaintext->get_consumed_header(header);
auto *noise = new APINoiseFrameHelper(plaintext->release_socket_for_switch(), this->parent_->get_noise_ctx());
// Carry over the peername-based client name (Hello has not arrived yet)
const char *name = plaintext->get_client_name();
noise->set_client_name(name, strlen(name));
this->helper_.reset(noise); // destroys the plaintext helper
APIError err = noise->init_from_handoff(header, header_len);
if (err != APIError::OK) {
this->fatal_error_with_log_(LOG_STR("Noise handoff failed"), err);
}
}
#endif // USE_API_NOISE && USE_API_PLAINTEXT
void APIConnection::destroy_active_iterator_() {
switch (this->active_iterator_) {
case ActiveIterator::LIST_ENTITIES:
@@ -253,6 +279,15 @@ void APIConnection::loop() {
// No more data available
break;
} else if (err != APIError::OK) {
#if defined(USE_API_NOISE) && defined(USE_API_PLAINTEXT)
// Checked inside the error branch to keep the hot err == OK path
// free of it; this can only fire on the first bytes of a plaintext
// helper on an unprovisioned device
if (err == APIError::PROTOCOL_SWITCH_TO_NOISE) {
this->upgrade_helper_to_noise_();
return;
}
#endif
this->fatal_error_with_log_(LOG_STR("Reading failed"), err);
return;
} else {
@@ -759,6 +794,7 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection
msg.supports_action = traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION);
// Current feature flags and other supported parameters
msg.feature_flags = traits.get_feature_flags();
msg.temperature_unit = static_cast<enums::TemperatureUnit>(traits.get_temperature_unit());
msg.supported_modes = &traits.get_supported_modes();
msg.visual_min_temperature = traits.get_visual_min_temperature();
msg.visual_max_temperature = traits.get_visual_max_temperature();
@@ -1293,7 +1329,8 @@ void APIConnection::on_voice_assistant_announce_request(const VoiceAssistantAnno
}
}
bool APIConnection::send_voice_assistant_get_configuration_response_(const VoiceAssistantConfigurationRequest &msg) {
bool APIConnection::send_voice_assistant_get_configuration_response_(
const VoiceAssistantConfigurationRequest & /*msg*/) {
VoiceAssistantConfigurationResponse resp;
if (!this->check_voice_assistant_api_connection_()) {
// send_message encodes synchronously, so this stack local outlives the encode
@@ -1313,22 +1350,6 @@ bool APIConnection::send_voice_assistant_get_configuration_response_(const Voice
}
}
// Filter external wake words
for (auto &wake_word : msg.external_wake_words) {
if (wake_word.model_type != "micro") {
// microWakeWord only
continue;
}
resp.available_wake_words.emplace_back();
auto &resp_wake_word = resp.available_wake_words.back();
resp_wake_word.id = StringRef(wake_word.id);
resp_wake_word.wake_word = StringRef(wake_word.wake_word);
for (const auto &lang : wake_word.trained_languages) {
resp_wake_word.trained_languages.push_back(lang);
}
}
resp.active_wake_words = &config.active_wake_words;
resp.max_active_wake_words = config.max_active_wake_words;
return this->send_message(resp);
@@ -1348,7 +1369,7 @@ void APIConnection::on_voice_assistant_set_configuration(const VoiceAssistantSet
#ifdef USE_ZWAVE_PROXY
void APIConnection::on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) {
zwave_proxy::global_zwave_proxy->send_frame(msg.data, msg.data_len);
zwave_proxy::global_zwave_proxy->send_frame(this, msg.data, msg.data_len);
}
void APIConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg) {
@@ -1433,6 +1454,7 @@ uint16_t APIConnection::try_send_water_heater_info(EntityBase *entity, APIConnec
msg.target_temperature_step = traits.get_target_temperature_step();
msg.supported_modes = &traits.get_supported_modes();
msg.supported_features = traits.get_feature_flags();
msg.temperature_unit = static_cast<enums::TemperatureUnit>(traits.get_temperature_unit());
return fill_and_encode_entity_info(wh, msg, conn, remaining_size);
}
@@ -1521,8 +1543,8 @@ void APIConnection::on_serial_proxy_configure_request(const SerialProxyConfigure
static_cast<uint32_t>(proxies.size()));
return;
}
proxies[msg.instance]->configure(msg.baudrate, msg.flow_control, static_cast<uint8_t>(msg.parity), msg.stop_bits,
msg.data_size);
proxies[msg.instance]->configure(this, msg.baudrate, msg.flow_control, static_cast<uint8_t>(msg.parity),
msg.stop_bits, msg.data_size);
}
void APIConnection::on_serial_proxy_write_request(const SerialProxyWriteRequest &msg) {
@@ -1531,7 +1553,7 @@ void APIConnection::on_serial_proxy_write_request(const SerialProxyWriteRequest
ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range", msg.instance);
return;
}
proxies[msg.instance]->write_from_client(msg.data, msg.data_len);
proxies[msg.instance]->write_from_client(this, msg.data, msg.data_len);
}
void APIConnection::on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &msg) {
@@ -1540,7 +1562,7 @@ void APIConnection::on_serial_proxy_set_modem_pins_request(const SerialProxySetM
ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range", msg.instance);
return;
}
proxies[msg.instance]->set_modem_pins(msg.line_states);
proxies[msg.instance]->set_modem_pins(this, msg.line_states);
}
void APIConnection::on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &msg) {
@@ -1711,12 +1733,6 @@ bool APIConnection::send_hello_response_(const HelloRequest &msg) {
ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu16 ".%" PRIu16, this->helper_->get_client_name(),
this->helper_->get_peername_to(peername), this->client_api_version_major_, this->client_api_version_minor_);
// TODO: Remove before 2026.8.0 (one version after get_object_id backward compat removal)
if (!this->client_supports_api_version(1, 14)) {
ESP_LOGW(TAG, "'%s' using outdated API %" PRIu16 ".%" PRIu16 ", update to 1.14+", this->helper_->get_client_name(),
this->client_api_version_major_, this->client_api_version_minor_);
}
HelloResponse resp;
resp.api_version_major = 1;
resp.api_version_minor = 14;
@@ -1724,6 +1740,19 @@ bool APIConnection::send_hello_response_(const HelloRequest &msg) {
resp.server_info = ESPHOME_VERSION_REF;
resp.name = StringRef(App.get_name());
#ifdef USE_PROVISIONING
if (provisioning::global_provisioning_manager != nullptr && provisioning::global_provisioning_manager->closed()) {
// The provisioning window has closed without the device being provisioned.
// Acknowledge the hello so the client can read the server name, then request
// disconnect with the reason. Authentication is intentionally not completed.
this->log_client_(ESPHOME_LOG_LEVEL_WARN, LOG_STR("Provisioning closed; rejecting connection"));
this->send_message(resp);
DisconnectRequest req;
req.reason = enums::DISCONNECT_REASON_PROVISIONING_CLOSED;
return this->send_message(req);
}
#endif
// Auto-authenticate - password auth was removed in ESPHome 2026.1.0
this->complete_authentication_();
@@ -1759,7 +1788,7 @@ bool APIConnection::send_device_info_response_() {
// Manufacturer string - define once, handle ESP8266 PROGMEM separately
#if defined(USE_ESP8266) || defined(USE_ESP32)
#define ESPHOME_MANUFACTURER "Espressif"
#elif defined(USE_RP2040)
#elif defined(USE_RP2)
#define ESPHOME_MANUFACTURER "Raspberry Pi"
#elif defined(USE_BK72XX)
#define ESPHOME_MANUFACTURER "Beken"
@@ -1844,6 +1873,12 @@ bool APIConnection::send_device_info_response_() {
#endif
#ifdef USE_API_NOISE
resp.api_encryption_supported = true;
#ifndef USE_API_NOISE_PSK_FROM_YAML
// No key from YAML: while no key is set, the key can be provisioned over a
// zero-PSK Noise connection. Gated on the YAML define (not the plaintext
// one) so this advertisement survives the plaintext removal in 2027.2.0.
resp.api_encryption_provisionable = !this->parent_->get_noise_ctx().has_psk();
#endif
#endif
#ifdef USE_DEVICES
size_t device_index = 0;
@@ -1874,7 +1909,8 @@ void APIConnection::on_hello_request(const HelloRequest &msg) {
this->on_fatal_error();
}
}
void APIConnection::on_disconnect_request() {
void APIConnection::on_disconnect_request(const DisconnectRequest & /*msg*/) {
// The reason is informational when a client disconnects us; we always ack and close.
if (!this->send_disconnect_response_()) {
this->on_fatal_error();
}
@@ -2002,6 +2038,15 @@ bool APIConnection::send_noise_encryption_set_key_response_(const NoiseEncryptio
NoiseEncryptionSetKeyResponse resp;
resp.success = false;
#ifdef USE_PROVISIONING
// Refuse to set a key once the provisioning window has closed (defense in depth;
// such connections are already rejected at hello).
if (provisioning::global_provisioning_manager != nullptr && provisioning::global_provisioning_manager->closed()) {
ESP_LOGW(TAG, "Provisioning closed; rejecting key set");
return this->send_message(resp);
}
#endif
psk_t psk{};
if (msg.key_len == 0) {
if (this->parent_->clear_noise_psk(true)) {
@@ -2011,10 +2056,21 @@ bool APIConnection::send_noise_encryption_set_key_response_(const NoiseEncryptio
}
} else if (base64_decode(msg.key, msg.key_len, psk.data(), psk.size()) != psk.size()) {
ESP_LOGW(TAG, "Invalid encryption key length");
} else if (APINoiseContext::is_all_zeros(psk)) {
// Accepting the reserved provisioning PSK would report success without
// enabling encryption (or silently clear an existing key)
ESP_LOGW(TAG, "Rejecting all-zero encryption key");
} else if (!this->parent_->save_noise_psk(psk, true)) {
ESP_LOGW(TAG, "Failed to save encryption key");
} else {
resp.success = true;
#ifdef USE_API_PLAINTEXT
if (this->helper_->frame_footer_size() == 0) {
// Plaintext transport has no frame footer; Noise always has the MAC footer.
// Remove after 2027.2.0 together with plaintext support on keyless devices.
ESP_LOGW(TAG, "Key received over plaintext; deprecated, will be removed in 2027.2.0");
}
#endif
}
return this->send_message(resp);
+16 -7
View File
@@ -18,8 +18,8 @@
#ifdef USE_ESP32_CRASH_HANDLER
#include "esphome/components/esp32/crash_handler.h"
#endif
#ifdef USE_RP2040_CRASH_HANDLER
#include "esphome/components/rp2040/crash_handler.h"
#ifdef USE_RP2_CRASH_HANDLER
#include "esphome/components/rp2/crash_handler.h"
#endif
#ifdef USE_ESP8266_CRASH_HANDLER
#include "esphome/components/esp8266/crash_handler.h"
@@ -166,10 +166,14 @@ class APIConnection final : public APIServerConnectionBase {
#endif
bool try_send_log_message(int level, const char *tag, const char *line, size_t message_len);
#ifdef USE_API_HOMEASSISTANT_SERVICES
void send_homeassistant_action(const HomeassistantActionRequest &call) {
// Returns whether this client has subscribed to Home Assistant actions; the message
// is only handed to the send path when subscribed. A true return does not guarantee
// delivery - it lets the caller warn when no connected client has the subscription.
bool send_homeassistant_action(const HomeassistantActionRequest &call) {
if (!this->flags_.service_call_subscription)
return;
return false;
this->send_message(call);
return true;
}
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
void on_homeassistant_action_response(const HomeassistantActionResponse &msg);
@@ -259,7 +263,7 @@ class APIConnection final : public APIServerConnectionBase {
void on_get_time_response(const GetTimeResponse &value);
#endif
void on_hello_request(const HelloRequest &msg);
void on_disconnect_request();
void on_disconnect_request(const DisconnectRequest &msg);
void on_ping_request();
void on_device_info_request();
void on_list_entities_request() { this->begin_iterator_(ActiveIterator::LIST_ENTITIES); }
@@ -279,8 +283,8 @@ class APIConnection final : public APIServerConnectionBase {
esp32::crash_handler_log();
esp32::crash_handler_clear();
#endif
#ifdef USE_RP2040_CRASH_HANDLER
rp2040::crash_handler_log();
#ifdef USE_RP2_CRASH_HANDLER
rp2::crash_handler_log();
#endif
#ifdef USE_ESP8266_CRASH_HANDLER
esp8266::crash_handler_log();
@@ -626,6 +630,11 @@ class APIConnection final : public APIServerConnectionBase {
void destroy_active_iterator_();
void begin_iterator_(ActiveIterator type);
void finalize_iterator_sync_();
#if defined(USE_API_NOISE) && defined(USE_API_PLAINTEXT)
// Swap the plaintext helper for a Noise helper after the client opened
// with a Noise hello on an unprovisioned device (zero-PSK provisioning).
void upgrade_helper_to_noise_();
#endif
#ifdef USE_CAMERA
std::unique_ptr<camera::CameraImageReader> image_reader_;
#endif
@@ -97,6 +97,8 @@ const LogString *api_error_to_logstr(APIError err) {
return LOG_STR("BAD_HANDSHAKE_ERROR_BYTE");
}
#endif
// PROTOCOL_SWITCH_TO_NOISE is intercepted in APIConnection::loop() before
// any logging can happen, so it intentionally has no entry here.
return LOG_STR("UNKNOWN");
}
+11
View File
@@ -88,6 +88,11 @@ enum class APIError : uint16_t {
HANDSHAKESTATE_SPLIT_FAILED = 1020,
BAD_HANDSHAKE_ERROR_BYTE = 1021,
#endif
#if defined(USE_API_NOISE) && defined(USE_API_PLAINTEXT)
// Not an error: an unprovisioned device received a Noise client hello on a
// plaintext connection; the caller must hand the socket off to a Noise helper.
PROTOCOL_SWITCH_TO_NOISE = 1023,
#endif
};
const LogString *api_error_to_logstr(APIError err);
@@ -200,6 +205,12 @@ class APIFrameHelper {
// or track that they stopped early and retry without this check.
// See Socket::ready() for details.
bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); }
#if defined(USE_API_NOISE) && defined(USE_API_PLAINTEXT)
// Move the socket out of this helper so a replacement helper can take it
// over (plaintext to Noise handoff on unprovisioned devices). The drained
// helper must be destroyed right after.
std::unique_ptr<socket::Socket> release_socket_for_switch() { return std::move(this->socket_); }
#endif
// Release excess memory from internal buffers after initial sync
void release_buffers() {
// rx_buf_: Safe to clear only if no partial read in progress.
@@ -109,6 +109,40 @@ APIError APINoiseFrameHelper::init() {
state_ = State::CLIENT_HELLO;
return APIError::OK;
}
#ifdef USE_API_PLAINTEXT
APIError APINoiseFrameHelper::init_from_handoff(const uint8_t *header, uint8_t header_len) {
APIError err = this->init();
if (err != APIError::OK) {
return err;
}
// Seed the header bytes the plaintext helper consumed before detecting the
// Noise indicator; try_read_frame_ resumes from rx_header_buf_len_.
std::memcpy(this->rx_header_buf_, header, header_len);
this->rx_header_buf_len_ = header_len;
// Pump the handshake without gating on socket_->ready(): on LWIP the
// plaintext helper's partial read can drain rcvevent while the rest of the
// client hello sits in the lastdata cache, so ready() may report false even
// though data is available.
return this->pump_handshake_();
}
#endif // USE_API_PLAINTEXT
/// Drive the handshake state machine until DATA, WOULD_BLOCK, or a fatal
/// error. WOULD_BLOCK is not an error: reads stop naturally on EWOULDBLOCK
/// and resume on the next loop().
APIError APINoiseFrameHelper::pump_handshake_() {
while (this->state_ != State::DATA) {
APIError err = this->state_action_();
if (err == APIError::WOULD_BLOCK) {
break;
}
if (err != APIError::OK) {
return err;
}
}
return APIError::OK;
}
// Helper for handling handshake frame errors
APIError APINoiseFrameHelper::handle_handshake_frame_error_(APIError aerr) {
if (aerr == APIError::BAD_INDICATOR) {
@@ -131,16 +165,13 @@ APIError APINoiseFrameHelper::handle_noise_error_(int err, const LogString *func
/// Run through handshake messages (if in that phase)
APIError APINoiseFrameHelper::loop() {
// Cache ready() outside the loop. On ESP8266 LWIP raw TCP, ready() returns false once
// the rx buffer is consumed. Re-checking each iteration would block handshake writes
// that must follow reads, deadlocking the handshake. state_action() will return
// WOULD_BLOCK when no more data is available to read.
bool socket_ready = this->socket_->ready();
while (state_ != State::DATA && socket_ready) {
APIError err = state_action_();
if (err == APIError::WOULD_BLOCK) {
break;
}
// Check ready() once, not per state transition. On ESP8266 LWIP raw TCP,
// ready() returns false once the rx buffer is consumed. Re-checking each
// iteration would block handshake writes that must follow reads,
// deadlocking the handshake. pump_handshake_() stops on WOULD_BLOCK when
// no more data is available to read.
if (state_ != State::DATA && this->socket_->ready()) {
APIError err = this->pump_handshake_();
if (err != APIError::OK) {
return err;
}
@@ -22,12 +22,20 @@ class APINoiseFrameHelper final : public APIFrameHelper {
}
~APINoiseFrameHelper() override;
APIError init() override;
#ifdef USE_API_PLAINTEXT
// Take over a connection whose first bytes were consumed by a plaintext
// helper on an unprovisioned device (see APIError::PROTOCOL_SWITCH_TO_NOISE).
// Seeds the already-read header bytes and pumps the handshake state machine
// until it would block.
APIError init_from_handoff(const uint8_t *header, uint8_t header_len);
#endif
APIError loop() override;
APIError read_packet(ReadPacketBuffer *buffer) override;
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
APIError write_protobuf_messages(ProtoWriteBuffer buffer, std::span<const MessageInfo> messages) override;
protected:
APIError pump_handshake_();
APIError state_action_();
APIError state_action_client_hello_();
APIError state_action_server_hello_();
@@ -89,6 +89,17 @@ APIError APIPlaintextFrameHelper::try_read_frame_() {
// If this was the first read, validate the indicator byte
if (rx_header_buf_pos_ == 0 && received > 0) {
if (rx_header_buf_[0] != 0x00) {
#ifdef USE_API_NOISE
// Dual build (encryption supported but no key set): a 0x01 first byte
// is a Noise client hello. Hand the connection off to a Noise helper
// running the all-zeros provisioning PSK so the encryption key can be
// set without crossing the wire in plaintext. Preserve the bytes we
// already consumed; they are the start of the Noise 3-byte header.
if (rx_header_buf_[0] == 0x01) {
rx_header_buf_pos_ = static_cast<uint8_t>(received);
return APIError::PROTOCOL_SWITCH_TO_NOISE;
}
#endif
state_ = State::FAILED;
HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]);
return APIError::BAD_INDICATOR;
@@ -23,6 +23,15 @@ class APIPlaintextFrameHelper final : public APIFrameHelper {
APIError read_packet(ReadPacketBuffer *buffer) override;
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
APIError write_protobuf_messages(ProtoWriteBuffer buffer, std::span<const MessageInfo> messages) override;
#ifdef USE_API_NOISE
// After try_read_frame_ returned PROTOCOL_SWITCH_TO_NOISE: copy out the
// header bytes already consumed from the socket (at most 3, the size of the
// Noise fixed header) so the replacement Noise helper can be seeded with them.
uint8_t get_consumed_header(uint8_t out[3]) const {
memcpy(out, this->rx_header_buf_, this->rx_header_buf_pos_);
return this->rx_header_buf_pos_;
}
#endif
protected:
APIError try_read_frame_();
+12 -5
View File
@@ -10,13 +10,20 @@ using psk_t = std::array<uint8_t, 32>;
class APINoiseContext {
public:
// The all-zeros PSK is reserved: it marks the device as unprovisioned and
// doubles as the well-known provisioning PSK that unprovisioned devices
// accept for Noise handshakes (passive-sniffing protection only, no
// authentication). It is never a valid real key.
static bool is_all_zeros(const psk_t &psk) {
uint8_t acc = 0;
for (uint8_t b : psk) {
acc |= b;
}
return acc == 0;
}
void set_psk(psk_t psk) {
this->psk_ = psk;
bool has_psk = false;
for (auto i : psk) {
has_psk |= i;
}
this->has_psk_ = has_psk;
this->has_psk_ = !is_all_zeros(psk);
}
const psk_t &get_psk() const { return this->psk_; }
bool has_psk() const { return this->has_psk_; }
+19 -2
View File
@@ -12,6 +12,22 @@ APIOverflowBuffer::~APIOverflowBuffer() {
}
ssize_t APIOverflowBuffer::try_drain(socket::Socket *socket) {
// socket->write() can re-enter this function: a log message emitted from an
// lwip callback during the write goes out over the API and lands back in the
// frame helper's write/drain path. If a nested drain ran here it would send
// and free the entry the outer drain is still holding, causing a double free.
// Report "no progress" instead; the outer drain keeps draining, and the
// nested send is enqueued behind the existing backlog.
if (this->draining_)
return 0;
// RAII so the flag is cleared on every return path
struct DrainGuard {
explicit DrainGuard(bool &flag) : flag_(flag) { flag_ = true; }
~DrainGuard() { this->flag_ = false; }
bool &flag_;
} guard(this->draining_);
while (this->count_ > 0) {
Entry *front = this->queue_[this->head_];
@@ -29,11 +45,12 @@ ssize_t APIOverflowBuffer::try_drain(socket::Socket *socket) {
return sent;
}
// Entry fully sent — free it and advance
Entry::destroy(front);
// Entry fully sent — unlink it before freeing so a freed pointer is never
// reachable from the queue
this->queue_[this->head_] = nullptr;
this->head_ = (this->head_ + 1) % API_MAX_SEND_QUEUE;
this->count_--;
Entry::destroy(front);
}
return 0; // All drained
@@ -69,6 +69,10 @@ class APIOverflowBuffer {
uint8_t head_{0};
uint8_t tail_{0};
uint8_t count_{0};
// Guards against re-entrant drains: socket->write() can re-enter the API
// send path (e.g. a log message emitted from an lwip callback), and a nested
// drain would free the entry the outer drain is still holding.
bool draining_{false};
};
} // namespace esphome::api
+26
View File
@@ -47,6 +47,26 @@ uint32_t HelloResponse::calculate_size() const {
size += 2 + this->name.size();
return size;
}
bool DisconnectRequest::decode_varint(uint32_t field_id, proto_varint_value_t value) {
switch (field_id) {
case 1:
this->reason = static_cast<enums::DisconnectReason>(value);
break;
default:
return false;
}
return true;
}
uint8_t *DisconnectRequest::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
uint8_t *__restrict__ pos = buffer.get_pos();
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, static_cast<uint32_t>(this->reason));
return pos;
}
uint32_t DisconnectRequest::calculate_size() const {
uint32_t size = 0;
size += this->reason ? 2 : 0;
return size;
}
#ifdef USE_AREAS
uint8_t *AreaInfo::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
uint8_t *__restrict__ pos = buffer.get_pos();
@@ -150,6 +170,9 @@ uint8_t *DeviceInfoResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_
for (const auto &it : this->serial_proxies) {
ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 25, it);
}
#endif
#ifdef USE_API_NOISE
ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 26, this->api_encryption_provisionable);
#endif
return pos;
}
@@ -212,6 +235,9 @@ uint32_t DeviceInfoResponse::calculate_size() const {
for (const auto &it : this->serial_proxies) {
size += ProtoSize::calc_message_force(2, it.calculate_size());
}
#endif
#ifdef USE_API_NOISE
size += ProtoSize::calc_bool(2, this->api_encryption_provisionable);
#endif
return size;
}
+14 -3
View File
@@ -11,6 +11,10 @@ namespace esphome::api {
namespace enums {
enum DisconnectReason : uint32_t {
DISCONNECT_REASON_UNSPECIFIED = 0,
DISCONNECT_REASON_PROVISIONING_CLOSED = 1,
};
enum SerialProxyPortType : uint32_t {
SERIAL_PROXY_PORT_TYPE_TTL = 0,
SERIAL_PROXY_PORT_TYPE_RS232 = 1,
@@ -427,18 +431,22 @@ class HelloResponse final : public ProtoMessage {
protected:
};
class DisconnectRequest final : public ProtoMessage {
class DisconnectRequest final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 5;
static constexpr uint8_t ESTIMATED_SIZE = 0;
static constexpr uint8_t ESTIMATED_SIZE = 2;
#ifdef HAS_PROTO_MESSAGE_DUMP
const LogString *message_name() const override { return LOG_STR("disconnect_request"); }
#endif
enums::DisconnectReason reason{};
uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
};
class DisconnectResponse final : public ProtoMessage {
public:
@@ -525,7 +533,7 @@ class SerialProxyInfo final : public ProtoMessage {
class DeviceInfoResponse final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 10;
static constexpr uint16_t ESTIMATED_SIZE = 309;
static constexpr uint16_t ESTIMATED_SIZE = 312;
#ifdef HAS_PROTO_MESSAGE_DUMP
const LogString *message_name() const override { return LOG_STR("device_info_response"); }
#endif
@@ -580,6 +588,9 @@ class DeviceInfoResponse final : public ProtoMessage {
#endif
#ifdef USE_SERIAL_PROXY
std::array<SerialProxyInfo, SERIAL_PROXY_COUNT> serial_proxies{};
#endif
#ifdef USE_API_NOISE
bool api_encryption_provisionable{false};
#endif
uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const;
uint32_t calculate_size() const;
+15 -1
View File
@@ -125,6 +125,16 @@ static void dump_bytes_field(DumpBuffer &out, const char *field_name, const uint
}
#pragma GCC diagnostic pop
template<> const char *proto_enum_to_string<enums::DisconnectReason>(enums::DisconnectReason value) {
switch (value) {
case enums::DISCONNECT_REASON_UNSPECIFIED:
return ESPHOME_PSTR("DISCONNECT_REASON_UNSPECIFIED");
case enums::DISCONNECT_REASON_PROVISIONING_CLOSED:
return ESPHOME_PSTR("DISCONNECT_REASON_PROVISIONING_CLOSED");
default:
return ESPHOME_PSTR("UNKNOWN");
}
}
template<> const char *proto_enum_to_string<enums::SerialProxyPortType>(enums::SerialProxyPortType value) {
switch (value) {
case enums::SERIAL_PROXY_PORT_TYPE_TTL:
@@ -864,7 +874,8 @@ const char *HelloResponse::dump_to(DumpBuffer &out) const {
return out.c_str();
}
const char *DisconnectRequest::dump_to(DumpBuffer &out) const {
out.append_p(ESPHOME_PSTR("DisconnectRequest {}"));
MessageDumpHelper helper(out, ESPHOME_PSTR("DisconnectRequest"));
dump_field(out, ESPHOME_PSTR("reason"), static_cast<enums::DisconnectReason>(this->reason));
return out.c_str();
}
const char *DisconnectResponse::dump_to(DumpBuffer &out) const {
@@ -971,6 +982,9 @@ const char *DeviceInfoResponse::dump_to(DumpBuffer &out) const {
it.dump_to(out);
out.append("\n");
}
#endif
#ifdef USE_API_NOISE
dump_field(out, ESPHOME_PSTR("api_encryption_provisionable"), this->api_encryption_provisionable);
#endif
return out.c_str();
}
@@ -31,6 +31,13 @@
#include <vector>
#include <string>
#if defined(LOG_LEVEL_NONE)
// Zephyr defines LOG_LEVEL_NONE as a logging macro that collides with the LogLevel enum value of
// the same name in the generated api_pb2.h. Undefine it for the rest of this translation unit so
// the enum parses; nothing below needs Zephyr's logging macro.
#undef LOG_LEVEL_NONE
#endif
namespace esphome::api {
// This file only provides includes, no actual code
+4 -2
View File
@@ -51,10 +51,12 @@ void APIConnection::read_message_(uint32_t msg_size, uint32_t msg_type, const ui
break;
}
case DisconnectRequest::MESSAGE_TYPE: {
DisconnectRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_receive_message_(LOG_STR("on_disconnect_request"));
this->log_receive_message_(LOG_STR("on_disconnect_request"), msg);
#endif
this->on_disconnect_request();
this->on_disconnect_request(msg);
break;
}
case DisconnectResponse::MESSAGE_TYPE: {
+1 -1
View File
@@ -21,7 +21,7 @@ class APIServerConnectionBase {
void on_hello_request(const HelloRequest &value){};
void on_disconnect_request(){};
void on_disconnect_request(const DisconnectRequest &value){};
void on_disconnect_response(){};
void on_ping_request(){};
void on_ping_response(){};
+62 -12
View File
@@ -107,8 +107,30 @@ void APIServer::setup() {
// Initialize last_connected_ for reboot timeout tracking
this->last_connected_ = App.get_loop_component_start_time();
// Set warning status if reboot timeout is enabled
if (this->reboot_timeout_ != 0) {
#if defined(USE_PROVISIONING) && defined(USE_API_NOISE)
// Register with the provisioning manager (provisioning:) as a source and
// report our current state (provisioned == an encryption key is set). When the
// window closes, disconnect any client still attempting to provision so it learns
// the reason. The manager owns the timeout, window state and on_timeout automation.
if (provisioning::global_provisioning_manager != nullptr) {
this->provisioning_source_ = provisioning::global_provisioning_manager->register_source();
provisioning::global_provisioning_manager->set_source_provisioned(this->provisioning_source_,
this->noise_ctx_.has_psk());
provisioning::global_provisioning_manager->add_on_closed_callback([this]() {
for (auto &c : this->active_clients()) {
DisconnectRequest req;
req.reason = enums::DISCONNECT_REASON_PROVISIONING_CLOSED;
// Best-effort: if the send buffer is full the reason is dropped, but the
// client still learns the window is closed when it reconnects (rejected at
// hello) or via the socket close.
c->send_message(req);
}
});
}
#endif
// Set warning status if reboot timeout is enabled (suppressed while provisioning
// is pending so the device waits to be onboarded instead of rebooting).
if (this->reboot_timeout_ != 0 && !this->provisioning_pending_()) {
this->status_set_warning(LOG_STR("waiting for client connection"));
}
}
@@ -121,8 +143,10 @@ void APIServer::loop() {
if (this->api_connection_count_ == 0) {
// Check reboot timeout - done in loop to avoid scheduler heap churn
// (cancelled scheduler items sit in heap memory until their scheduled time)
if (this->reboot_timeout_ != 0) {
// (cancelled scheduler items sit in heap memory until their scheduled time).
// Suppressed while a provisioning window is pending so the device waits to be
// onboarded / reset instead of rebooting itself; resumes once provisioned.
if (this->reboot_timeout_ != 0 && !this->provisioning_pending_()) {
const uint32_t now = App.get_loop_component_start_time();
if (now - this->last_connected_ > this->reboot_timeout_) {
ESP_LOGE(TAG, "No clients; rebooting");
@@ -194,7 +218,8 @@ void APIServer::remove_client_(uint8_t client_index) {
this->clients_[last_index].reset();
// Last client disconnected - set warning and start tracking for reboot timeout
if (this->api_connection_count_ == 0 && this->reboot_timeout_ != 0) {
// (suppressed while provisioning is pending - see loop()).
if (this->api_connection_count_ == 0 && this->reboot_timeout_ != 0 && !this->provisioning_pending_()) {
this->status_set_warning(LOG_STR("waiting for client connection"));
this->last_connected_ = App.get_loop_component_start_time();
}
@@ -232,7 +257,7 @@ void __attribute__((flatten)) APIServer::accept_new_connections_() {
conn->start();
// First client connected - clear warning and update timestamp
if (this->api_connection_count_ == 1 && this->reboot_timeout_ != 0) {
if (this->api_connection_count_ == 1 && this->reboot_timeout_ != 0 && !this->provisioning_pending_()) {
this->status_clear_warning();
this->last_connected_ = App.get_loop_component_start_time();
}
@@ -240,12 +265,13 @@ void __attribute__((flatten)) APIServer::accept_new_connections_() {
}
void APIServer::dump_config() {
char addr_buf[network::USE_ADDRESS_BUFFER_SIZE];
ESP_LOGCONFIG(TAG,
"Server:\n"
" Address: %s:%u\n"
" Listen backlog: %u\n"
" Max connections: %u",
network::get_use_address(), this->port_, this->listen_backlog_, MAX_API_CONNECTIONS);
network::get_use_address_to(addr_buf), this->port_, this->listen_backlog_, MAX_API_CONNECTIONS);
#ifdef USE_API_NOISE
ESP_LOGCONFIG(TAG, " Noise encryption: %s", YESNO(this->noise_ctx_.has_psk()));
if (!this->noise_ctx_.has_psk()) {
@@ -400,8 +426,16 @@ void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = bat
#ifdef USE_API_HOMEASSISTANT_SERVICES
void APIServer::send_homeassistant_action(const HomeassistantActionRequest &call) {
bool has_subscriber = false;
for (auto &client : this->active_clients()) {
client->send_homeassistant_action(call);
has_subscriber |= client->send_homeassistant_action(call);
}
if (!has_subscriber) {
// Home Assistant subscribes to actions shortly *after* authenticating, so actions
// fired right at connection time (on_client_connected, on_time_sync, ...) can
// arrive before the subscription and are lost - warn instead of failing silently.
ESP_LOGW(TAG, "Home Assistant %s '%s' dropped; %s", call.is_event ? "event" : "action", call.service.c_str(),
this->is_connected() ? "client has not subscribed to actions (yet)" : "no client connected");
}
}
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
@@ -571,8 +605,16 @@ bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
}
SavedNoisePsk new_saved_psk{psk};
return this->update_noise_psk_(new_saved_psk, LOG_STR("Noise PSK saved"), LOG_STR("Failed to save Noise PSK"),
make_active);
bool result = this->update_noise_psk_(new_saved_psk, LOG_STR("Noise PSK saved"), LOG_STR("Failed to save Noise PSK"),
make_active);
#ifdef USE_PROVISIONING
// The device now has a key; report provisioned so the provisioning window is
// satisfied and the reboot timeout resumes normal operation.
if (result && provisioning::global_provisioning_manager != nullptr) {
provisioning::global_provisioning_manager->set_source_provisioned(this->provisioning_source_, true);
}
#endif
return result;
#endif
}
bool APIServer::clear_noise_psk(bool make_active) {
@@ -583,8 +625,16 @@ bool APIServer::clear_noise_psk(bool make_active) {
return false;
#else
SavedNoisePsk empty_psk{};
return this->update_noise_psk_(empty_psk, LOG_STR("Noise PSK cleared"), LOG_STR("Failed to clear Noise PSK"),
make_active);
bool result = this->update_noise_psk_(empty_psk, LOG_STR("Noise PSK cleared"), LOG_STR("Failed to clear Noise PSK"),
make_active);
#ifdef USE_PROVISIONING
// The key was cleared; report unprovisioned so a subsequent reboot reopens the
// provisioning window.
if (result && provisioning::global_provisioning_manager != nullptr) {
provisioning::global_provisioning_manager->set_source_provisioned(this->provisioning_source_, false);
}
#endif
return result;
#endif
}
#endif
+20 -1
View File
@@ -14,6 +14,9 @@
#include "esphome/core/controller.h"
#include "esphome/core/log.h"
#include "esphome/core/string_ref.h"
#ifdef USE_PROVISIONING
#include "esphome/components/provisioning/provisioning.h"
#endif
#ifdef USE_LOGGER
#include "esphome/components/logger/logger.h"
#endif
@@ -255,6 +258,19 @@ class APIServer final : public Component,
// Remove a disconnected client by index. Swaps with the last populated slot and resets it.
void __attribute__((noinline)) remove_client_(uint8_t client_index);
#ifdef USE_PROVISIONING
// True while a configured provisioning window is still pending (the device is
// unprovisioned). Suppresses the reboot timeout and its warning so the device is
// not auto-rebooted while waiting to be provisioned. False when no provisioning
// window is configured.
bool provisioning_pending_() const {
return provisioning::global_provisioning_manager != nullptr &&
provisioning::global_provisioning_manager->window_pending();
}
#else
bool provisioning_pending_() const { return false; }
#endif
#ifdef USE_API_NOISE
bool update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg, const LogString *fail_log_msg,
bool make_active);
@@ -332,7 +348,10 @@ class APIServer final : public Component,
uint8_t listen_backlog_{4};
bool shutting_down_ = false;
uint8_t api_connection_count_{0};
// 7 bytes used, 1 byte padding
#if defined(USE_PROVISIONING) && defined(USE_API_NOISE)
// Index assigned by the provisioning manager for reporting this transport's state.
uint8_t provisioning_source_{0};
#endif
#ifdef USE_API_NOISE
APINoiseContext noise_ctx_;
+17 -11
View File
@@ -18,7 +18,7 @@ with warnings.catch_warnings():
import contextlib
from esphome.const import CONF_KEY, CONF_PORT, __version__
from esphome.core import CORE, EsphomeError
from esphome.core import CORE
from esphome.util import safe_print
from . import CONF_ENCRYPTION
@@ -36,15 +36,17 @@ class _LogLineProcessor:
"""Feeds incoming log lines to the stack-trace decoder.
Two responsibilities beyond just calling the decoder:
1. Catch EsphomeError. on_log runs inside an asyncio protocol
callback; if an exception escapes, the loop tears the transport
down with "Fatal error: protocol.data_received() call failed."
and ReconnectLogic immediately reconnects, the device replays
the same crash trace, and we loop forever.
2. Disable decoding after the first failure. _decode_pc shells out
to PlatformIO via _run_idedata, which is expensive; a single
crash dump can contain many PC/BT lines and we don't want to
retry the failing subprocess for each one.
1. Catch everything the decoder can raise. aioesphomeapi isolates
exceptions raised by log handlers, so an escaping one no longer
kills the session, but it does log a full traceback per line. A
crash dump carries a PC line plus one per backtrace frame, so the
tracebacks bury the dump the user is trying to read. Decoding is a
diagnostic nicety; nothing it raises is worth that noise.
2. Disable decoding after the first failure. _decode_pc shells out to
the toolchain to resolve addr2line, which is expensive; a single
crash dump can contain many PC/BT lines and we don't want to retry
the failing subprocess for each one. This only works if every
failure is caught, which is why 1 is not narrowed to EsphomeError.
"""
def __init__(self, config: dict[str, Any], platform_handler: Any | None) -> None:
@@ -61,12 +63,13 @@ class _LogLineProcessor:
self.backtrace_state = self._platform_handler(
self._config, raw_line, self.backtrace_state
)
except EsphomeError as exc:
except Exception as exc: # noqa: BLE001 # pylint: disable=broad-except
self._decode_enabled = False
self.backtrace_state = False
# _run_idedata raises EsphomeError with no message; fall back
# to a generic explanation when str(exc) is empty.
detail = str(exc) or "build artifacts not found locally"
_LOGGER.debug("Stack-trace decoding failed", exc_info=True)
_LOGGER.warning(
"Crash trace decoding unavailable: %s. "
"Run 'esphome compile' for this device to enable PC decoding.",
@@ -152,6 +155,9 @@ async def async_run_logs(
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,
)
try:
await asyncio.Event().wait()
+1
View File
@@ -7,6 +7,7 @@ AQICalculatorType = aqi_ns.enum("AQICalculatorType")
CONF_AQI = "aqi"
CONF_CALCULATION_TYPE = "calculation_type"
CONF_EXTENDED_RANGE = "extended_range"
AQI_CALCULATION_TYPE = {
"CAQI": AQICalculatorType.CAQI_TYPE,
@@ -6,7 +6,7 @@ namespace esphome::aqi {
class AbstractAQICalculator {
public:
virtual uint16_t get_aqi(float pm2_5_value, float pm10_0_value) = 0;
virtual uint16_t get_aqi(float pm2_5_value, float pm10_0_value, bool extended_range) = 0;
};
} // namespace esphome::aqi
+20 -10
View File
@@ -11,10 +11,12 @@ namespace esphome::aqi {
class AQICalculator : public AbstractAQICalculator {
public:
uint16_t get_aqi(float pm2_5_value, float pm10_0_value) override {
float pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
float pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
uint16_t get_aqi(float pm2_5_value, float pm10_0_value, bool extended_range) override {
float pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID, extended_range);
float pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID, extended_range);
float aqi = std::max({pm2_5_index, pm10_0_index, 0.0f});
// extended_range lets the index run past the standard maximum, so clamp to the sensor's range.
aqi = std::min(aqi, static_cast<float>(std::numeric_limits<uint16_t>::max()));
return static_cast<uint16_t>(std::lround(aqi));
}
@@ -30,7 +32,7 @@ class AQICalculator : public AbstractAQICalculator {
{35.5f, 55.5f},
{55.5f, 125.5f},
{125.5f, 225.5f},
{225.5f, std::numeric_limits<float>::max()}
{225.5f, 500.4f} // EPA 2024: AQI 301-500 maps to PM2.5 225.5-500.4 ug/m3
// clang-format on
};
@@ -41,11 +43,11 @@ class AQICalculator : public AbstractAQICalculator {
{155.0f, 255.0f},
{255.0f, 355.0f},
{355.0f, 425.0f},
{425.0f, std::numeric_limits<float>::max()}
{425.0f, 604.0f} // EPA: AQI 301-500 maps to PM10 425-604 ug/m3 (top of the 401-500 band)
// clang-format on
};
static float calculate_index(float value, const float array[NUM_LEVELS][2]) {
static float calculate_index(float value, const float array[NUM_LEVELS][2], bool extended_range) {
int grid_index = get_grid_index(value, array);
if (grid_index == -1) {
return -1.0f;
@@ -55,14 +57,22 @@ class AQICalculator : public AbstractAQICalculator {
float conc_lo = array[grid_index][0];
float conc_hi = array[grid_index][1];
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
float index = (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
// Concentrations above the highest breakpoint run the linear fit past aqi_hi. By default we
// clamp to the standard maximum; with extended_range we keep the extrapolated "over-range"
// value so heavy pollution reports numbers beyond what the standard defines.
if (grid_index == NUM_LEVELS - 1 && !extended_range && index > aqi_hi) {
return aqi_hi;
}
return index;
}
static int get_grid_index(float value, const float array[NUM_LEVELS][2]) {
for (int i = 0; i < NUM_LEVELS; i++) {
const bool in_range =
(value >= array[i][0]) && ((i == NUM_LEVELS - 1) ? (value <= array[i][1]) // last bucket inclusive
: (value < array[i][1])); // others exclusive on hi
// The top band is open-ended: any value at or above its lower breakpoint falls into it,
// and calculate_index() decides whether to clamp or extrapolate.
const bool in_range = (value >= array[i][0]) && (i == NUM_LEVELS - 1 || value < array[i][1]);
if (in_range) {
return i;
}
+2 -1
View File
@@ -24,6 +24,7 @@ void AQISensor::setup() {
void AQISensor::dump_config() {
ESP_LOGCONFIG(TAG, "AQI Sensor:");
ESP_LOGCONFIG(TAG, " Calculation Type: %s", this->aqi_calc_type_ == AQI_TYPE ? "AQI" : "CAQI");
ESP_LOGCONFIG(TAG, " Extended Range: %s", this->extended_range_ ? "enabled" : "disabled");
if (this->pm_2_5_sensor_ != nullptr) {
ESP_LOGCONFIG(TAG, " PM2.5 Sensor: '%s'", this->pm_2_5_sensor_->get_name().c_str());
}
@@ -44,7 +45,7 @@ void AQISensor::calculate_aqi_() {
return;
}
uint16_t aqi = calculator->get_aqi(this->pm_2_5_value_, this->pm_10_0_value_);
uint16_t aqi = calculator->get_aqi(this->pm_2_5_value_, this->pm_10_0_value_, this->extended_range_);
this->publish_state(aqi);
}
+2
View File
@@ -14,6 +14,7 @@ class AQISensor final : public sensor::Sensor, public Component {
void set_pm_2_5_sensor(sensor::Sensor *sensor) { this->pm_2_5_sensor_ = sensor; }
void set_pm_10_0_sensor(sensor::Sensor *sensor) { this->pm_10_0_sensor_ = sensor; }
void set_aqi_calculation_type(AQICalculatorType type) { this->aqi_calc_type_ = type; }
void set_extended_range(bool extended_range) { this->extended_range_ = extended_range; }
protected:
void calculate_aqi_();
@@ -21,6 +22,7 @@ class AQISensor final : public sensor::Sensor, public Component {
sensor::Sensor *pm_2_5_sensor_{nullptr};
sensor::Sensor *pm_10_0_sensor_{nullptr};
AQICalculatorType aqi_calc_type_{AQI_TYPE};
bool extended_range_{false};
AQICalculatorFactory aqi_calculator_factory_;
float pm_2_5_value_{NAN};
+13 -10
View File
@@ -9,25 +9,28 @@ namespace esphome::aqi {
class CAQICalculator : public AbstractAQICalculator {
public:
uint16_t get_aqi(float pm2_5_value, float pm10_0_value) override {
// The CAQI (CITEAIR) scale defines no maximum: its top "Very high" class is simply ">100". We
// therefore always extrapolate the top band past 100 without limit, so the extended_range flag
// (which lifts the AQI calculator's fixed 500 cap) has no meaning here and is ignored.
uint16_t get_aqi(float pm2_5_value, float pm10_0_value, bool /*extended_range*/) override {
float pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
float pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
float aqi = std::max({pm2_5_index, pm10_0_index, 0.0f});
aqi = std::min(aqi, static_cast<float>(std::numeric_limits<uint16_t>::max()));
return static_cast<uint16_t>(std::lround(aqi));
}
protected:
static constexpr int NUM_LEVELS = 5;
static constexpr int NUM_LEVELS = 4;
static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 75}, {76, 100}, {101, 400}};
static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 75}, {76, 100}};
static constexpr float PM2_5_GRID[NUM_LEVELS][2] = {
// clang-format off
{0.0f, 15.1f},
{15.1f, 30.1f},
{30.1f, 55.1f},
{55.1f, 110.1f},
{110.1f, std::numeric_limits<float>::max()}
{55.1f, 110.1f}
// clang-format on
};
@@ -36,8 +39,7 @@ class CAQICalculator : public AbstractAQICalculator {
{0.0f, 25.1f},
{25.1f, 50.1f},
{50.1f, 90.1f},
{90.1f, 180.1f},
{180.1f, std::numeric_limits<float>::max()}
{90.1f, 180.1f}
// clang-format on
};
@@ -52,14 +54,15 @@ class CAQICalculator : public AbstractAQICalculator {
float conc_lo = array[grid_index][0];
float conc_hi = array[grid_index][1];
// The top band is open-ended (see get_grid_index), so for concentrations above the last
// breakpoint this linear fit extrapolates past 100 unbounded, matching CAQI's open ">100" class.
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
}
static int get_grid_index(float value, const float array[NUM_LEVELS][2]) {
for (int i = 0; i < NUM_LEVELS; i++) {
const bool in_range =
(value >= array[i][0]) && ((i == NUM_LEVELS - 1) ? (value <= array[i][1]) // last bucket inclusive
: (value < array[i][1])); // others exclusive on hi
// The top band is open-ended: any value at or above its lower breakpoint falls into it.
const bool in_range = (value >= array[i][0]) && (i == NUM_LEVELS - 1 || value < array[i][1]);
if (in_range) {
return i;
}
+17 -3
View File
@@ -8,14 +8,25 @@ from esphome.const import (
STATE_CLASS_MEASUREMENT,
)
from . import AQI_CALCULATION_TYPE, CONF_CALCULATION_TYPE, aqi_ns
from . import AQI_CALCULATION_TYPE, CONF_CALCULATION_TYPE, CONF_EXTENDED_RANGE, aqi_ns
CODEOWNERS = ["@jasstrong"]
DEPENDENCIES = ["sensor"]
AQISensor = aqi_ns.class_("AQISensor", sensor.Sensor, cg.Component)
CONFIG_SCHEMA = (
def _validate_extended_range(config):
if CONF_EXTENDED_RANGE in config and config[CONF_CALCULATION_TYPE] == "CAQI":
raise cv.Invalid(
f"'{CONF_EXTENDED_RANGE}' is not supported with 'calculation_type: CAQI'. "
"CAQI has no maximum value by specification, so it is always reported unbounded.",
[CONF_EXTENDED_RANGE],
)
return config
CONFIG_SCHEMA = cv.All(
sensor.sensor_schema(
AQISensor,
accuracy_decimals=0,
@@ -29,9 +40,11 @@ CONFIG_SCHEMA = (
cv.Required(CONF_CALCULATION_TYPE): cv.enum(
AQI_CALCULATION_TYPE, upper=True
),
cv.Optional(CONF_EXTENDED_RANGE): cv.boolean,
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(cv.COMPONENT_SCHEMA),
_validate_extended_range,
)
@@ -46,3 +59,4 @@ async def to_code(config):
cg.add(var.set_pm_10_0_sensor(pm_10_0_sensor))
cg.add(var.set_aqi_calculation_type(config[CONF_CALCULATION_TYPE]))
cg.add(var.set_extended_range(config.get(CONF_EXTENDED_RANGE, False)))
+1 -5
View File
@@ -24,11 +24,7 @@ void I2CAS3935Component::write_register(uint8_t reg, uint8_t mask, uint8_t bits,
uint8_t I2CAS3935Component::read_register(uint8_t reg) {
uint8_t value;
if (write(&reg, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Writing register failed!");
return 0;
}
if (read(&value, 1) != i2c::ERROR_OK) {
if (!this->read_byte(reg, &value)) {
ESP_LOGW(TAG, "Reading register failed!");
return 0;
}
+2 -2
View File
@@ -5,7 +5,7 @@ from esphome.const import (
CONF_CLEAR,
CONF_GAIN,
CONF_ID,
DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_EMPTY,
ICON_BRIGHTNESS_5,
STATE_CLASS_MEASUREMENT,
)
@@ -54,7 +54,7 @@ SENSOR_SCHEMA = sensor.sensor_schema(
unit_of_measurement=UNIT_COUNTS,
icon=ICON_BRIGHTNESS_5,
accuracy_decimals=0,
device_class=DEVICE_CLASS_ILLUMINANCE,
device_class=DEVICE_CLASS_EMPTY,
state_class=STATE_CLASS_MEASUREMENT,
)
+3 -3
View File
@@ -13,7 +13,7 @@ def AUTO_LOAD() -> list[str]:
if (
not CORE.is_esp32
and not CORE.is_esp8266
and not CORE.is_rp2040
and not CORE.is_rp2
and not CORE.is_libretiny
):
return ["socket"]
@@ -37,7 +37,7 @@ async def to_code(config):
elif CORE.is_esp8266:
# https://github.com/ESP32Async/ESPAsyncTCP
cg.add_library("ESP32Async/ESPAsyncTCP", "2.0.0")
elif CORE.is_rp2040:
elif CORE.is_rp2:
# https://github.com/ayushsharma82/RPAsyncTCP
# RPAsyncTCP is a drop-in replacement for AsyncTCP_RP2040W with better
# ESPAsyncWebServer compatibility
@@ -47,6 +47,6 @@ async def to_code(config):
def FILTER_SOURCE_FILES() -> list[str]:
# Exclude socket implementation for platforms that use AsyncTCP libraries
if CORE.is_esp32 or CORE.is_esp8266 or CORE.is_rp2040 or CORE.is_libretiny:
if CORE.is_esp32 or CORE.is_esp8266 or CORE.is_rp2 or CORE.is_libretiny:
return ["async_tcp_socket.cpp"]
return []
+1 -1
View File
@@ -7,7 +7,7 @@
#elif defined(USE_ESP8266)
// Use ESPAsyncTCP library for ESP8266 (always Arduino)
#include <ESPAsyncTCP.h>
#elif defined(USE_RP2040)
#elif defined(USE_RP2)
// Use RPAsyncTCP library for RP2040
#include <RPAsyncTCP.h>
#else
@@ -1,6 +1,6 @@
#include "async_tcp_socket.h"
#if !defined(USE_ESP32) && !defined(USE_ESP8266) && !defined(USE_RP2040) && !defined(USE_LIBRETINY) && \
#if !defined(USE_ESP32) && !defined(USE_ESP8266) && !defined(USE_RP2) && !defined(USE_LIBRETINY) && \
(defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS))
#include "esphome/components/network/util.h"
@@ -2,7 +2,7 @@
#include "esphome/core/defines.h"
#if !defined(USE_ESP32) && !defined(USE_ESP8266) && !defined(USE_RP2040) && !defined(USE_LIBRETINY) && \
#if !defined(USE_ESP32) && !defined(USE_ESP8266) && !defined(USE_RP2) && !defined(USE_LIBRETINY) && \
(defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS))
#include "esphome/components/socket/socket.h"
@@ -65,12 +65,11 @@ optional<ParseResult> ATCMiThermometer::parse_header_(const esp32_ble_tracker::S
return {};
}
static uint8_t last_frame_count = 0;
if (last_frame_count == raw[12]) {
ESP_LOGVV(TAG, "parse_header(): duplicate data packet received (%hhu).", last_frame_count);
if (this->last_frame_count_ == raw[12]) {
ESP_LOGVV(TAG, "parse_header(): duplicate data packet received (%hhu).", this->last_frame_count_);
return {};
}
last_frame_count = raw[12];
this->last_frame_count_ = raw[12];
return result;
}
@@ -38,6 +38,8 @@ class ATCMiThermometer final : public Component, public esp32_ble_tracker::ESPBT
sensor::Sensor *battery_voltage_{nullptr};
sensor::Sensor *signal_strength_{nullptr};
uint8_t last_frame_count_{0};
optional<ParseResult> parse_header_(const esp32_ble_tracker::ServiceData &service_data);
bool parse_message_(const std::vector<uint8_t> &message, ParseResult &result);
bool report_results_(const optional<ParseResult> &result, const char *address);
+1 -1
View File
@@ -395,7 +395,7 @@ async def to_code(config):
)
if data.mp3_support:
cg.add_define("USE_AUDIO_MP3_SUPPORT")
add_idf_component(name="esphome/micro-mp3", ref="0.3.0")
add_idf_component(name="esphome/micro-mp3", ref="0.4.0")
_emit_memory_pair(
data.mp3.buffer_memory,
"CONFIG_MICRO_MP3_PREFER_PSRAM",
+3 -1
View File
@@ -113,7 +113,9 @@ def read_audio_file_and_type(file_config: ConfigType) -> tuple[bytes, MockObj]:
media_file_type = audio.AUDIO_FILE_TYPE_ENUM["NONE"]
if file_type == "wav":
media_file_type = audio.AUDIO_FILE_TYPE_ENUM["WAV"]
elif file_type in ("mp3", "mpeg", "mpga"):
elif file_type in ("mp1", "mp2", "mp3", "mpeg", "mpga"):
# With puremagic >=2.0 this can cause some MP3 (Layer III) files to be labeled as "mp1"/"mp2".
# Treat those labels as MP3 so we still pick the MP3 decoder.
media_file_type = audio.AUDIO_FILE_TYPE_ENUM["MP3"]
elif file_type == "flac":
media_file_type = audio.AUDIO_FILE_TYPE_ENUM["FLAC"]
@@ -37,15 +37,15 @@ namespace esphome::beken_spi_led_strip {
static const char *const TAG = "beken_spi_led_strip";
struct spi_data_t {
struct SpiData {
SemaphoreHandle_t dma_tx_semaphore;
volatile bool tx_in_progress;
bool first_run;
};
static spi_data_t *spi_data = nullptr;
static SpiData *spi_data = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static void set_spi_ctrl_register(unsigned long bit, bool val) {
static void set_spi_ctrl_register(uint32_t bit, bool val) {
uint32_t value = REG_READ(SPI_CTRL);
if (val == 0) {
value &= ~bit;
@@ -55,7 +55,7 @@ static void set_spi_ctrl_register(unsigned long bit, bool val) {
REG_WRITE(SPI_CTRL, value);
}
static void set_spi_config_register(unsigned long bit, bool val) {
static void set_spi_config_register(uint32_t bit, bool val) {
uint32_t value = REG_READ(SPI_CONFIG);
if (val == 0) {
value &= ~bit;
@@ -67,7 +67,7 @@ static void set_spi_config_register(unsigned long bit, bool val) {
void spi_dma_tx_enable(bool enable) {
GDMA_CFG_ST en_cfg;
set_spi_config_register(SPI_TX_EN, enable ? 1 : 0);
set_spi_config_register(SPI_TX_EN, enable);
en_cfg.channel = SPI_TX_DMA_CHANNEL;
en_cfg.param = enable ? 1 : 0;
sddev_control(GDMA_DEV_NAME, CMD_GDMA_SET_DMA_ENABLE, &en_cfg);
@@ -110,13 +110,13 @@ static void spi_set_clock(uint32_t max_hz) {
param &= ~(SPI_CKR_MASK << SPI_CKR_POSI);
param |= (div << SPI_CKR_POSI);
REG_WRITE(SPI_CTRL, param);
ESP_LOGD(TAG, "target frequency: %d, actual frequency: %d", max_hz, source_clk / 2 / div);
ESP_LOGD(TAG, "target frequency: %" PRIu32 ", actual frequency: %d", max_hz, source_clk / 2 / div);
}
void spi_dma_tx_finish_callback(unsigned int param) {
spi_data->tx_in_progress = false;
xSemaphoreGive(spi_data->dma_tx_semaphore);
spi_dma_tx_enable(0);
spi_dma_tx_enable(false);
}
void BekenSPILEDStripLightOutput::setup() {
@@ -161,7 +161,7 @@ void BekenSPILEDStripLightOutput::setup() {
return;
}
spi_data = (spi_data_t *) calloc(1, sizeof(spi_data_t));
spi_data = (SpiData *) calloc(1, sizeof(SpiData)); // NOLINT(cppcoreguidelines-no-malloc)
if (spi_data == nullptr) {
ESP_LOGE(TAG, "Cannot allocate spi_data!");
this->mark_failed();
@@ -177,20 +177,20 @@ void BekenSPILEDStripLightOutput::setup() {
spi_data->first_run = true;
set_spi_ctrl_register(MSTEN, 0);
set_spi_ctrl_register(BIT_WDTH, 0);
set_spi_ctrl_register(MSTEN, false);
set_spi_ctrl_register(BIT_WDTH, false);
spi_set_clock(this->spi_frequency_);
set_spi_ctrl_register(CKPOL, 0);
set_spi_ctrl_register(CKPHA, 0);
set_spi_ctrl_register(MSTEN, 1);
set_spi_ctrl_register(SPIEN, 1);
set_spi_ctrl_register(CKPOL, false);
set_spi_ctrl_register(CKPHA, false);
set_spi_ctrl_register(MSTEN, true);
set_spi_ctrl_register(SPIEN, true);
set_spi_ctrl_register(TXINT_EN, 0);
set_spi_ctrl_register(RXINT_EN, 0);
set_spi_config_register(SPI_TX_FINISH_EN, 1);
set_spi_config_register(SPI_RX_FINISH_EN, 1);
set_spi_ctrl_register(RXOVR_EN, 0);
set_spi_ctrl_register(TXOVR_EN, 0);
set_spi_ctrl_register(TXINT_EN, false);
set_spi_ctrl_register(RXINT_EN, false);
set_spi_config_register(SPI_TX_FINISH_EN, true);
set_spi_config_register(SPI_RX_FINISH_EN, true);
set_spi_ctrl_register(RXOVR_EN, false);
set_spi_ctrl_register(TXOVR_EN, false);
value = REG_READ(SPI_CTRL);
value &= ~CTRL_NSSMD_3;
@@ -199,7 +199,7 @@ void BekenSPILEDStripLightOutput::setup() {
value = GFUNC_MODE_SPI_DMA;
sddev_control(GPIO_DEV_NAME, CMD_GPIO_ENABLE_SECOND, &value);
set_spi_ctrl_register(SPI_S_CS_UP_INT_EN, 0);
set_spi_ctrl_register(SPI_S_CS_UP_INT_EN, false);
GDMA_CFG_ST en_cfg;
GDMACFG_TPYES_ST init_cfg;
@@ -210,7 +210,7 @@ void BekenSPILEDStripLightOutput::setup() {
init_cfg.dstptr_incr = 0;
init_cfg.srcptr_incr = 1;
init_cfg.src_start_addr = this->dma_buf_;
init_cfg.dst_start_addr = (void *) SPI_DAT; // SPI_DMA_REG4_TXFIFO
init_cfg.dst_start_addr = (void *) SPI_DAT; // NOLINT(performance-no-int-to-ptr) SPI_DMA_REG4_TXFIFO
init_cfg.channel = SPI_TX_DMA_CHANNEL;
init_cfg.prio = 0; // 10
init_cfg.u.type4.src_loop_start_addr = this->dma_buf_;
@@ -230,7 +230,7 @@ void BekenSPILEDStripLightOutput::setup() {
en_cfg.param = 0;
sddev_control(GDMA_DEV_NAME, CMD_GDMA_CFG_SRCADDR_LOOP, &en_cfg);
spi_dma_tx_enable(0);
spi_dma_tx_enable(false);
value = REG_READ(SPI_CONFIG);
value &= ~(0xFFF << 8);
@@ -247,7 +247,8 @@ void BekenSPILEDStripLightOutput::set_led_params(uint8_t bit0, uint8_t bit1, uin
void BekenSPILEDStripLightOutput::write_state(light::LightState *state) {
// protect from refreshing too often
uint32_t now = micros();
if (*this->max_refresh_rate_ != 0 && (now - this->last_refresh_) < *this->max_refresh_rate_) {
if (this->max_refresh_rate_.has_value() && *this->max_refresh_rate_ != 0 &&
(now - this->last_refresh_) < *this->max_refresh_rate_) {
// try again next loop iteration, so that this change won't get lost
this->schedule_show();
return;
@@ -293,7 +294,7 @@ void BekenSPILEDStripLightOutput::write_state(light::LightState *state) {
}
spi_data->first_run = false;
spi_dma_tx_enable(1);
spi_dma_tx_enable(true);
this->status_clear_warning();
}
@@ -376,7 +377,7 @@ void BekenSPILEDStripLightOutput::dump_config() {
" RGB Order: %s\n"
" Max refresh rate: %" PRIu32 "\n"
" Number of LEDs: %u",
rgb_order, *this->max_refresh_rate_, this->num_leds_);
rgb_order, this->max_refresh_rate_.value_or(0), this->num_leds_);
}
float BekenSPILEDStripLightOutput::get_setup_priority() const { return setup_priority::HARDWARE; }
+3 -1
View File
@@ -448,7 +448,9 @@ _BINARY_SENSOR_SCHEMA = (
cv.Exclusive(
CONF_TRIGGER_ON_INITIAL_STATE, CONF_TRIGGER_ON_INITIAL_STATE
): cv.boolean,
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
cv.Optional(
CONF_DEVICE_CLASS, visibility=cv.Visibility.ADVANCED
): validate_device_class,
cv.Optional(CONF_FILTERS): validate_filters,
cv.Optional(CONF_ON_PRESS): automation.validate_automation({}),
cv.Optional(CONF_ON_RELEASE): automation.validate_automation({}),
@@ -112,7 +112,7 @@ float BinarySensorMap::bayesian_predicate_(bool sensor_state, float prior, float
prob_state_source_false = 1 - prob_given_false;
}
return prob_state_source_true / (prior * prob_state_source_true + (1.0 - prior) * prob_state_source_false);
return prob_state_source_true / (prior * prob_state_source_true + (1.0f - prior) * prob_state_source_false);
}
void BinarySensorMap::add_channel(binary_sensor::BinarySensor *sensor, float value) {
File diff suppressed because it is too large Load Diff

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