Compare commits

...
Author SHA1 Message Date
J. Nick Koston 230b6926ab Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 23:41:57 -05:00
J. Nick Koston 4f315ae8ba Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 23:41:57 -05:00
J. Nick Koston 4e88823faf Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 23:41:57 -05:00
J. Nick Koston ec6123e0ef Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 23:41:57 -05:00
J. Nick Koston da3c6dec7f Merge branch 'esp8266-native-library-converter' into esp8266-native-shared-helpers 2026-08-22 23:41:56 -05:00
J. Nick Koston cfac36c647 Merge branch 'esp8266-native-idedata-helpers' into esp8266-native-library-converter 2026-08-22 23:41:56 -05:00
J. Nick Koston d179f43e7c Correct the zero-partition comment: the skip fails CI extraction loudly, it does not feed it 2026-08-22 23:41:55 -05:00
J. Nick Koston ad2a3e120a Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 23:23:41 -05:00
J. Nick Koston e5fcaf3892 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 23:23:40 -05:00
J. Nick Koston af16c444d4 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 23:23:40 -05:00
J. Nick Koston 327630523c Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 23:23:40 -05:00
J. Nick Koston 0e4f4237fd Merge branch 'esp8266-native-library-converter' into esp8266-native-shared-helpers 2026-08-22 23:23:40 -05:00
J. Nick Koston 96a74cd217 Merge branch 'esp8266-native-idedata-helpers' into esp8266-native-library-converter 2026-08-22 23:23:40 -05:00
J. Nick Koston dc64c93f39 [core] Register the OTA component in dummy_main like its siblings 2026-08-22 23:23:39 -05:00
J. Nick Koston d5e3d44fd4 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 23:21:07 -05:00
J. Nick Koston 32097fcb00 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 23:21:07 -05:00
J. Nick Koston 4bb0fa8761 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 23:21:07 -05:00
J. Nick Koston 1b109518f7 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 23:21:06 -05:00
J. Nick Koston 5793a5f08a Merge branch 'esp8266-native-library-converter' into esp8266-native-shared-helpers 2026-08-22 23:21:06 -05:00
J. Nick Koston 9d0d1b42d5 Merge branch 'esp8266-native-idedata-helpers' into esp8266-native-library-converter 2026-08-22 23:21:06 -05:00
J. Nick Koston 8230c16f12 Order the launcher strip before the empty guard, never dedupe rsp commands, harden the size summary 2026-08-22 23:21:04 -05:00
J. Nick Koston fd077062c9 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 22:58:48 -05:00
J. Nick Koston a1d0713a54 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 22:58:48 -05:00
J. Nick Koston 9e96d53868 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 22:58:47 -05:00
J. Nick Koston 905c806009 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 22:58:47 -05:00
J. Nick Koston 42f59f94c9 Merge branch 'esp8266-native-library-converter' into esp8266-native-shared-helpers 2026-08-22 22:58:47 -05:00
J. Nick Koston 35bb8388a2 Defer superseded wave downloads, dedupe tool dests, validate captured buckets, right-size the log levels 2026-08-22 22:58:44 -05:00
J. Nick Koston 99ec278c67 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 22:39:48 -05:00
J. Nick Koston 6b7a56be67 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 22:39:48 -05:00
J. Nick Koston 6a0e7dc595 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 22:39:48 -05:00
J. Nick Koston 6fda5b9a07 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 22:39:48 -05:00
J. Nick Koston 5ac6e2b3c6 Merge branch 'esp8266-native-library-converter' into esp8266-native-shared-helpers 2026-08-22 22:39:47 -05:00
J. Nick Koston b08026e413 Trigger the IDF infra job on the extraScript shim now that it exists 2026-08-22 22:39:34 -05:00
J. Nick Koston f6c6708f24 Merge branch 'esp8266-native-idedata-helpers' into esp8266-native-library-converter 2026-08-22 22:39:33 -05:00
J. Nick Koston 48c6948ae8 Guard empty compile commands, refuse a compile DB with no ESPHome TU, share the launcher rejection 2026-08-22 22:39:16 -05:00
J. Nick Koston f65aa87450 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 22:32:37 -05:00
J. Nick Koston 2dfa23f5e5 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 22:32:37 -05:00
J. Nick Koston e4911b4cc2 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 22:32:37 -05:00
J. Nick Koston e714ef4dfc Reconcile BOARDS with the generated table: d1_wroom_02 joins with its shipped flash layout 2026-08-22 22:32:26 -05:00
J. Nick Koston f0203528f5 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 22:05:04 -05:00
J. Nick Koston cd291fb35b Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 22:05:04 -05:00
J. Nick Koston e9466ff738 Merge branch 'esp8266-native-library-converter' into esp8266-native-shared-helpers 2026-08-22 22:05:03 -05:00
J. Nick Koston b9317de2e8 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 22:05:03 -05:00
J. Nick Koston 4d6300b400 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 22:05:03 -05:00
J. Nick Koston 030e79fa5a Keep the empty-argument flag check next to the lexer that leaves the bare token 2026-08-22 22:04:47 -05:00
J. Nick Koston da9c0bdd14 Merge branch 'esp8266-native-library-converter' into esp8266-native-shared-helpers 2026-08-22 21:59:57 -05:00
J. Nick Koston a1e6370e8b [core] Harden the shared library converter and download registry archives in parallel 2026-08-22 21:59:19 -05:00
J. Nick Koston dada0f2c2b [core] Extract the shared idedata and size-summary helpers into build_helpers 2026-08-22 21:58:41 -05:00
J. Nick Koston a5e87d3c8f Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 21:41:57 -05:00
J. Nick Koston ba1ad765ea Name the probe failure cause in the tool-version warning 2026-08-22 21:41:50 -05:00
J. Nick Koston 767bb61a14 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 21:41:28 -05:00
J. Nick Koston 21abf1692b Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 21:41:27 -05:00
J. Nick Koston a22c2a9768 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 21:41:27 -05:00
J. Nick Koston 18206967a1 Validate captured CPPDEFINES, warn on empty idedata includes, name the idedata consumers 2026-08-22 21:40:57 -05:00
J. Nick Koston 32a0fff6b8 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 21:28:28 -05:00
J. Nick Koston 2f0ad8a57d Surface unexpected prefetch failures; restore the dropped cache-path rationales 2026-08-22 21:28:18 -05:00
J. Nick Koston 75150fda52 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 21:27:39 -05:00
J. Nick Koston 791e2b8204 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 21:27:39 -05:00
J. Nick Koston ce52b93249 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 21:27:39 -05:00
J. Nick Koston d9e18ee982 Support subscript env reads in extra scripts; make the INFO-capture fixture real 2026-08-22 21:27:31 -05:00
J. Nick Koston 44d447ada0 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 21:24:07 -05:00
J. Nick Koston f49a5879c3 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 21:24:07 -05:00
J. Nick Koston 918444ecbb Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 21:24:07 -05:00
J. Nick Koston d26d3fdc33 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 21:24:06 -05:00
J. Nick Koston 227f5c67e2 Merge remote-tracking branch 'origin/dev' into esp8266-native-shared-helpers
# Conflicts:
#	tests/unit_tests/test_main.py
2026-08-22 21:23:40 -05:00
J. Nick Koston 3d540b1597 Require a resolved ccache, dedupe the package specs, use the shared cache spec 2026-08-22 21:16:27 -05:00
J. Nick Koston ca79cb1a20 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer
# Conflicts:
#	esphome/writer.py
2026-08-22 21:14:48 -05:00
J. Nick Koston e557e84468 Route the registry prefetch through the shared downloader; register tools caches in one table 2026-08-22 21:14:18 -05:00
J. Nick Koston 5c30acfe5b Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 21:12:39 -05:00
J. Nick Koston cc27854fb2 Point the consumed-options comment at its now-real consumer 2026-08-22 21:12:37 -05:00
J. Nick Koston 044d593f09 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 21:12:36 -05:00
J. Nick Koston 68e04f5982 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 21:12:36 -05:00
J. Nick Koston d6d80ee323 Consolidate the batch-download scaffold, share the idedata error set, dedupe compdb tokenizing 2026-08-22 21:12:20 -05:00
J. Nick KostonandGitHub f0651e5c9b [radio_frequency] Inline the trivial make_call helper (#18645) 2026-08-22 21:07:34 -05:00
J. Nick KostonandGitHub f24b731f95 [infrared] Inline the trivial make_call helper (#18644) 2026-08-22 21:07:14 -05:00
J. Nick KostonandGitHub 01ad424d12 [datetime] Inline the trivial make_call helpers (#18643) 2026-08-22 21:07:03 -05:00
J. Nick KostonandGitHub 435d522683 [text] Inline the trivial Text publish_state forwarding overloads (#18642) 2026-08-22 21:06:47 -05:00
J. Nick KostonandGitHub a63c3bc0c7 [valve] Inline the trivial Valve and ValveCall accessors (#18641) 2026-08-22 21:06:31 -05:00
J. Nick KostonandGitHub b83ce91528 [display] Inline the trivial DisplayPage setters and page navigation helpers (#18639) 2026-08-22 21:06:03 -05:00
J. Nick KostonandGitHub a30238aab6 [wireguard] Inline the trivial Wireguard setters (#18638) 2026-08-22 21:05:51 -05:00
J. Nick KostonandGitHub 47156c9a5b [mqtt] Inline the trivial MQTT client, component and sensor accessors (#18637) 2026-08-22 21:05:39 -05:00
J. Nick KostonandGitHub 78240c9a46 [sprinkler] Inline the trivial Sprinkler accessors (#18636) 2026-08-22 21:04:57 -05:00
J. Nick KostonandGitHub 5a9f06e584 [thermostat] Inline the trivial ThermostatClimate setters and getters (#18635) 2026-08-22 21:04:41 -05:00
J. Nick KostonandGitHub ecb007da70 [deep_sleep] Inline the trivial DeepSleepComponent setters (#18634) 2026-08-22 21:04:23 -05:00
J. Nick KostonandGitHub ad1a4fca36 [version] Inline the trivial VersionTextSensor setters (#18633) 2026-08-22 21:04:08 -05:00
J. Nick KostonandGitHub 832a738588 [switch] Inline the trivial inverted accessors (#18632) 2026-08-22 21:03:51 -05:00
J. Nick KostonandGitHub fedb3ac5c1 [fan] Inline the trivial Fan call helpers (#18631) 2026-08-22 21:03:38 -05:00
J. Nick KostonandGitHub cb4e55e444 [cover] Inline the trivial Cover and CoverCall accessors (#18630) 2026-08-22 21:03:15 -05:00
J. Nick KostonandGitHub 3ef5a8e6a4 [water_heater] Inline the trivial visual override setters (#18629) 2026-08-22 21:03:07 -05:00
J. Nick KostonandGitHub 0e915e9b8b [core] Inline the ESPTime::strftime std::string overload (#18628) 2026-08-22 21:02:58 -05:00
J. Nick KostonandGitHub dba3b287dd [api] Inline the trivial APIServer accessors (#18627) 2026-08-22 21:02:14 -05:00
J. Nick KostonandGitHub ce019f508d [safe_mode] Inline the trivial set_safe_mode setters (#18626) 2026-08-22 21:02:04 -05:00
J. Nick KostonandGitHub 5c2286cc4a [climate] Inline the trivial visual override setters (#18624) 2026-08-22 21:01:55 -05:00
J. Nick KostonandGitHub efc0a94112 [text_sensor] Inline the trivial TextSensor forwarding overloads (#18623) 2026-08-22 21:01:42 -05:00
J. Nick KostonandGitHub c60062c418 [sensor] Inline the trivial ExponentialMovingAverageFilter setters (#18622) 2026-08-22 21:01:28 -05:00
J. Nick KostonandGitHub 763a1d9371 [select] Inline the trivial Select accessors (#18621) 2026-08-22 21:01:16 -05:00
J. Nick KostonandGitHub 4db1666024 [light] Inline the trivial LightState accessors (#18620) 2026-08-22 21:00:59 -05:00
J. Nick KostonandGitHub 0dc69aab1e [logger] Inline the trivial Logger accessors (#18619) 2026-08-22 21:00:49 -05:00
J. Nick KostonandGitHub a282cb095e [ethernet] Inline the trivial EthernetComponent setters (#18618) 2026-08-22 21:00:39 -05:00
J. Nick KostonandGitHub 259e7182a3 [esp32] Exclude esp_gdbstub from the build by default (#18604) 2026-08-22 20:59:55 -05:00
J. Nick KostonandGitHub 74bdf275d2 [core] Dump the main.cpp config comment with sorted keys (#18653) 2026-08-22 20:58:28 -05:00
J. Nick KostonandGitHub 14499223fd [esp8266] Don't report stale crash state after hardware WDT resets (#18597) 2026-08-22 20:58:13 -05:00
J. Nick KostonandGitHub 6aab523dd9 [esp32_ble] Log connection parameter update results (#18607) 2026-08-22 20:57:58 -05:00
J. Nick KostonandGitHub 1f31e51446 [esphome] Inline the trivial OTA port accessors (#18625) 2026-08-22 20:57:37 -05:00
J. Nick KostonandGitHub c062d0c717 [ota] Log prepare, upload, and total OTA timing in espota2 (#18582) 2026-08-22 20:57:13 -05:00
J. Nick Koston 2218743c4a Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 20:13:47 -05:00
J. Nick Koston de709dec71 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 20:13:47 -05:00
J. Nick Koston 2e3dc3c514 Note the consumed-options export is unused until the backend lands 2026-08-22 20:13:45 -05:00
J. Nick Koston 149ca8c954 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 20:01:31 -05:00
J. Nick Koston 45c4a9b143 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 20:01:31 -05:00
J. Nick Koston bf0a4ea40f Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 20:01:31 -05:00
J. Nick Koston 0d33c3bf07 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 20:01:31 -05:00
J. Nick Koston 2acba9b364 Materialize the tracker mock before the prefetch threads race its creation 2026-08-22 20:01:13 -05:00
J. Nick Koston 36b0b62a8f Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 19:55:33 -05:00
J. Nick Koston a174c306d8 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 19:55:33 -05:00
J. Nick Koston 35f3087fcc Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 19:55:33 -05:00
J. Nick Koston 8475a4bf09 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 19:55:33 -05:00
J. Nick Koston f4bc7dda35 Fault the cache read directly so the unreadable-cache test holds on Windows 2026-08-22 19:55:16 -05:00
J. Nick Koston 3e4ace6b84 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 19:46:46 -05:00
J. Nick Koston d72e2f1c60 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 19:46:46 -05:00
J. Nick Koston cbbc0a8fa9 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 19:46:45 -05:00
J. Nick Koston 1f0fa0ef8b Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 19:46:45 -05:00
J. Nick Koston 7d246893be Surface prefetch failures, harden the idedata and build-info cache reads 2026-08-22 19:46:42 -05:00
J. Nick Koston 225c3f4cdb Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 19:32:13 -05:00
J. Nick Koston 3697401bc7 Serialize prefetch downloads on the install lock; export the canonical ccache off spelling 2026-08-22 19:32:10 -05:00
J. Nick Koston c3297c2663 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 19:14:41 -05:00
J. Nick Koston c3129d598c Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 19:14:41 -05:00
J. Nick Koston 82b20313b0 Export the consumed platformio_options set so the ignored-option warning cannot drift 2026-08-22 19:14:32 -05:00
J. Nick KostonandGitHub dcabaedff1 [bk72xx_ble] Block BK7238 until the LibreTiny bonding partition fix lands (#18649) 2026-08-23 11:59:24 +12:00
J. Nick Koston 27aef88077 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 17:10:23 -05:00
J. Nick Koston a5fde9429d Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 17:10:23 -05:00
J. Nick Koston e63d995d1f Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 17:10:23 -05:00
J. Nick Koston ad0859c5bd Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 17:10:23 -05:00
J. Nick Koston bff8bea83e Degrade garbage Content-Length probes; never prefetch an unverifiable tool archive 2026-08-22 17:10:19 -05:00
J. Nick Koston 0c5586c7dc Download the Arduino core and toolchain archives in parallel 2026-08-22 16:59:07 -05:00
J. Nick Koston 4ece0da68c Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 16:58:37 -05:00
J. Nick Koston 712ee229e5 Prefetch pending registry packages in parallel under one combined bar 2026-08-22 16:58:24 -05:00
J. Nick Koston 80c9c4cef3 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 16:41:53 -05:00
J. Nick Koston bc98ba3b46 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 16:41:52 -05:00
J. Nick Koston 3974a810bc Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 16:41:52 -05:00
J. Nick Koston dcf921d5f6 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 16:41:52 -05:00
J. Nick Koston 49d060fdfd Correct the _content_lengths contract docs and pin the unknown-size fallback 2026-08-22 16:41:40 -05:00
J. Nick Koston 466d67788e Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 16:10:09 -05:00
J. Nick Koston f2f5ea127e Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 16:10:09 -05:00
J. Nick Koston 2acde317d2 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 16:10:08 -05:00
J. Nick Koston d1d2f186ab Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 16:10:08 -05:00
J. Nick Koston f2ff1cc986 Keep per-file bars when sizes are unknown; harden the prefetch probes
An unknown Content-Length (probe failure, non-2xx, or a CDN omitting it
on HEAD) now skips the parallel prefetch so the sequential downloads
keep their per-file bars, instead of a silent multi-MB batch; the HEAD
helper returns None for unknown, distinct from a genuine zero, and
catches only RequestException. The cache probe logs its failure at
debug. The espidf tool prefetch reads sizes with .get so a missing size
only drops the bar instead of aborting the resumable prefetch. The
idedata warning logs its traceback at debug for parser regressions, and
framework_helpers joins the ESP-IDF infra CI triggers it is imported
by.
2026-08-22 16:10:03 -05:00
J. Nick Koston 11964b86a8 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 15:51:42 -05:00
J. Nick Koston a7777e4589 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 15:51:41 -05:00
J. Nick Koston 4e6a8e297e Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 15:51:41 -05:00
J. Nick Koston 2315f54f2e Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 15:51:41 -05:00
J. Nick Koston 86ac8f661b Fold the dependency-entry validation into one helper
The name and version checks were duplicated across the dict-shorthand
and list spellings with four identical warnings; _valid_dependency_entry
carries them once.
2026-08-22 15:51:38 -05:00
J. Nick Koston 7f6aaeb46c Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 15:46:47 -05:00
J. Nick Koston 7a938af0f8 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 15:46:47 -05:00
J. Nick Koston 689861ffc9 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 15:46:47 -05:00
J. Nick Koston 776b3844f9 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 15:46:47 -05:00
J. Nick Koston eec01ea793 Diagnosable prefetch failures, Ctrl-C-safe pool, typed dependency versions
The library prefetch pool now cancels queued futures on interrupt like
the espidf pool it mirrors, prefetch and HEAD failures log their cause
at debug (a suppressed bar is traceable), and non-2xx HEAD statuses read
as unknown size. normalize_dependencies rejects a non-string version in
both spellings (a container would raise from set.add(); an int fails
opaquely in the registry), and the walk's version-less skip logs the
dependency at debug until the arduino-backend reconciliation lands.
2026-08-22 15:46:38 -05:00
J. Nick Koston b888d41cb9 Diagnosable prefetch failures, Ctrl-C-safe pool, typed dependency versions
The library prefetch pool now cancels queued futures on interrupt like
the espidf pool it mirrors, prefetch and HEAD failures log their cause
at debug (a suppressed bar is traceable), and non-2xx HEAD statuses read
as unknown size. normalize_dependencies rejects a non-string version in
both spellings (a container would raise from set.add(); an int fails
opaquely in the registry), and the walk's version-less skip logs the
dependency at debug until the arduino-backend reconciliation lands.
2026-08-22 15:46:06 -05:00
J. Nick Koston 8bcf42921a Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 15:33:40 -05:00
J. Nick Koston b3f058613c Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 15:33:40 -05:00
J. Nick Koston 97701122be Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 15:33:40 -05:00
J. Nick Koston f1d043c840 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 15:33:40 -05:00
J. Nick Koston 1eda5a8534 Skip already-cached archives in the download prefetch
The wave logged 'Downloading N libraries' before checking the
extraction markers, so a warm build announced downloads it never made.
URLSource gains an is_cached probe (best-effort in the prefetch) and a
fully warm wave stays silent.
2026-08-22 15:33:37 -05:00
J. Nick Koston 1652088009 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 15:21:04 -05:00
J. Nick Koston 4cbfdd3200 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra
# Conflicts:
#	tests/unit_tests/test_framework_helpers.py
2026-08-22 15:21:01 -05:00
J. Nick Koston 520bf3f63b Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 15:20:42 -05:00
J. Nick Koston 1f2b532394 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 15:20:42 -05:00
J. Nick Koston 1cf5df3d59 Merge the espidf parallel-prefetch branch and give library downloads its combined bar
Brings in #18513 (BatchDownloadProgress and the download_with_resume
progress callback) and extends the plumbing one level:
download_from_mirrors passes a progress callback to both target kinds,
Source/ConvertedLibrary.download thread it through, and the library
wave prefetch draws one combined bar sized by parallel HEAD requests
(no sizes, no bar). The thread-local bar suppression this replaces is
removed; a batch caller passing a tracker also owns the messaging, so
the per-file Downloading line only prints for solo downloads.
2026-08-22 15:19:58 -05:00
J. Nick Koston a2a93cc186 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 15:03:21 -05:00
J. Nick Koston 6171561b70 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 15:03:21 -05:00
J. Nick Koston 91836a8c69 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 15:03:20 -05:00
J. Nick Koston 35af304911 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 15:03:20 -05:00
J. Nick Koston f4482275cb Pin sortedness on a nested fixture in the comment-order test 2026-08-22 15:03:07 -05:00
J. Nick Koston 4aba6c4670 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 14:54:58 -05:00
J. Nick Koston d653a550bb Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 14:54:57 -05:00
J. Nick Koston f599e37375 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 14:54:57 -05:00
J. Nick Koston 6f79518ee2 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 14:54:57 -05:00
J. Nick Koston fd7e340e81 Type-hint the to_code test stub 2026-08-22 14:54:39 -05:00
J. Nick Koston 84a8ac3772 Use a docstring instead of pass in the to_code test stub 2026-08-22 14:54:24 -05:00
J. Nick Koston 961bff4790 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 14:52:40 -05:00
J. Nick Koston cb02e15184 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 14:52:40 -05:00
J. Nick Koston 910147139a Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 14:52:39 -05:00
J. Nick Koston ab49aff931 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 14:52:39 -05:00
J. Nick Koston 4a459e3d0d Lift the remaining function-local imports in test_main to top level 2026-08-22 14:52:38 -05:00
J. Nick Koston 8a4ade10e6 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 14:49:42 -05:00
J. Nick Koston 8cffd44ffd Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 14:49:42 -05:00
J. Nick Koston e16008fc58 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 14:49:42 -05:00
J. Nick Koston 35a1136bea Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 14:49:41 -05:00
J. Nick Koston 3b091676bf Drop the unreachable nameless-dependency check in the walk
normalize_dependencies guarantees every entry carries a non-empty
string name, so only the version can be absent.
2026-08-22 14:49:39 -05:00
J. Nick Koston 622be18709 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 14:44:17 -05:00
J. Nick Koston ade6dbabae Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 14:44:17 -05:00
J. Nick Koston 182dc7f636 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 14:44:17 -05:00
J. Nick Koston d7af5f37b6 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 14:44:17 -05:00
J. Nick Koston 121795fa8d Trim comment and docstring essays 2026-08-22 14:44:12 -05:00
J. Nick Koston 4e48764bbe Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 14:41:34 -05:00
J. Nick Koston deecfade13 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 14:41:34 -05:00
J. Nick Koston ec237a2d06 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 14:41:33 -05:00
J. Nick Koston a2267eb686 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 14:41:33 -05:00
J. Nick Koston dbd6f323c0 Extract the build-info staleness check so its branches are testable
copy_src_tree is always mocked in the writer tests, so the new
missing/stale/unreadable debug branches were unreachable; the decision
moves into _build_info_stale with a test covering every branch.
2026-08-22 14:41:30 -05:00
J. Nick Koston 62a4cb3fe4 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 14:38:09 -05:00
J. Nick Koston d3eaac6f47 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 14:38:08 -05:00
J. Nick Koston 2eec194d87 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing
# Conflicts:
#	tests/unit_tests/test_main.py
2026-08-22 14:38:04 -05:00
J. Nick Koston 3a9a6edc0e Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 14:37:46 -05:00
J. Nick Koston 11875ec040 Dump the main.cpp config comment with sorted keys
voluptuous fills schema defaults in set-iteration order, so the
validated dict's key order changes with the process hash seed; the
unsorted dump churned the comment block in main.cpp and relinked the
firmware on every esphome run for any config using a defaults-heavy
action (logger.log in a button's on_press was enough). Affects the
PlatformIO path identically.
2026-08-22 14:37:42 -05:00
J. Nick Koston 51221688f6 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 14:31:39 -05:00
J. Nick Koston f695e9dceb Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 14:31:39 -05:00
J. Nick Koston d8f6368716 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 14:31:38 -05:00
J. Nick Koston a97b836012 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 14:31:38 -05:00
J. Nick Koston afeb1a5ea1 Name the changed source when the writer flags a rebuild
Every sources_changed site now logs which file (or which build-info
staleness) triggered it, so a config that relinks on every run can be
diagnosed from one -v compile instead of diffing the build tree.
2026-08-22 14:31:36 -05:00
J. Nick Koston 2d2750e031 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 14:11:40 -05:00
J. Nick Koston 0d508ccbc0 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra
# Conflicts:
#	tests/unit_tests/test_framework_helpers.py
2026-08-22 14:11:26 -05:00
J. Nick Koston bf7a617934 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 14:11:02 -05:00
J. Nick Koston 803eef7de6 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 14:11:02 -05:00
J. Nick Koston fb06b4d631 Download a wave of registry libraries in parallel
The walk now drains its frontier, prefetches the wave's registry
archives with a small thread pool (deduped by URL, progress bars
suppressed per thread since parallel bars would interleave), and then
processes the wave sequentially; the sequential download() call stays
authoritative so failures surface with resume exactly as before. Same
approach as the espidf tool prefetch in #18513.
2026-08-22 14:10:40 -05:00
J. Nick Koston ae2893992a Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 13:51:47 -05:00
J. Nick Koston 6b46e9fd03 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 13:51:47 -05:00
J. Nick Koston 2f053371ce Guard the honored board_build branch against the empty-list form
voluptuous accepts [] for [cv.string], so vals[-1] could raise an
IndexError at codegen time; an empty list now falls through to the
ignored-option warning.
2026-08-22 13:51:35 -05:00
J. Nick Koston 3bd4cdb760 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 13:15:59 -05:00
J. Nick Koston 9533c09b69 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 13:15:59 -05:00
J. Nick Koston 2dac9112be Pin the scalar shape for the honored board_build options; note the dispatch coupling
The schema permits the list form, so the routing now stores the last
value (like a later platformio.ini line) and the generator always sees a
scalar. The using_native_toolchain docstring points at write_cpp_file's
dispatch so NATIVE_TOOLCHAINS membership and the project-writing branch
flip together.
2026-08-22 13:15:47 -05:00
J. Nick Koston 478584354e Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 12:52:52 -05:00
J. Nick Koston 9c7f68b813 Hoist the boolean spelling tables to helpers so cv.boolean and the env parser share one source
cv.boolean's inline tuples and the ccache env tables were two copies of
the same spellings; TRUTHY_BOOL_STRINGS/FALSY_BOOL_STRINGS now live in
esphome/helpers.py, cv.boolean consumes them, and parse_enable_env
derives its tables by adding the 1/0 env convention.
2026-08-22 12:52:38 -05:00
J. Nick Koston b62d59ca26 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 12:47:07 -05:00
J. Nick Koston e52703617e Name the env boolean spelling tables like device-builder does
parse_enable_env's inline tuples become TRUTHY_ENV_STRINGS and
FALSY_ENV_STRINGS frozensets mirroring cv.boolean's spellings (enable
and disable included) plus the 1/0 env convention, matching
device-builder's TRUTHY_BOOL_STRINGS pattern.
2026-08-22 12:47:05 -05:00
J. Nick Koston 57cbadbe9f Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 12:46:30 -05:00
J. Nick Koston a6ca775a48 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 12:46:29 -05:00
J. Nick Koston 315f41e884 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 12:46:29 -05:00
J. Nick Koston 65609bb94b Move the backend-agnostic library hardening down from the arduino backend PR
The typed IncompatiblePlatform exception, dependency_is_usable,
warn_properties_depends, the lex_build_flags helper (espidf switches to
it), the normalize_dependencies validation, the manifest shape check,
the component-level drop-warning split, and the SCons case-sensitive
.C/.C++ suffixes all harden the shared converter independently of the
arduino backend, so they belong in this PR; the provides hook and the
version-less reconciliation stay with the backend that needs them.
2026-08-22 12:46:16 -05:00
J. Nick Koston 4a1138820d Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 12:15:39 -05:00
J. Nick Koston 7c07276c71 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 12:15:38 -05:00
J. Nick Koston 60cf44de3b Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 12:15:38 -05:00
J. Nick Koston 072b65c0e2 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 12:15:38 -05:00
J. Nick Koston f71867ed2d Merge remote-tracking branch 'origin/dev' into esp8266-native-shared-helpers 2026-08-22 12:15:30 -05:00
J. Nick Koston 9676e0c0f0 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 11:55:58 -05:00
J. Nick Koston cedecf1f83 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 11:55:58 -05:00
J. Nick Koston f28be76c84 Fix comment drift: conditional cache refresh, future-tense generator consumer 2026-08-22 11:55:15 -05:00
J. Nick Koston cdde9dd230 Trim comment essays and hoist function-local test imports 2026-08-22 11:39:54 -05:00
J. Nick Koston 935db6a63c Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-22 11:38:49 -05:00
J. Nick Koston 90b29e231d Trim comment essays and hoist function-local test imports 2026-08-22 11:38:46 -05:00
J. Nick Koston 315abf21f8 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-22 11:37:21 -05:00
J. Nick Koston 24e5b1800f Trim comment essays and hoist function-local test imports 2026-08-22 11:37:16 -05:00
J. Nick Koston 11eca53a18 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-22 11:35:41 -05:00
J. Nick Koston f08e06f574 Trim comment essays and hoist function-local test imports 2026-08-22 11:35:35 -05:00
J. Nick Koston 27f4db8458 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-22 11:34:57 -05:00
J. Nick Koston e99e0cc8b8 Trim comment essays and hoist function-local test imports 2026-08-22 11:34:32 -05:00
d119ad6c60 [usb_uart] Extract non-final USBUartChannelBase from USBUartChannel (#17472)
Co-authored-by: p1ngb4ck <p@local>
2026-08-22 10:47:46 -05:00
esphome[bot]andGitHub 74fc2e367a Bump bundled esphome-device-builder to 1.12.4 (#18651) 2026-08-22 15:42:53 +00:00
ea10f94376 [core] Add type annotations to component Python (10/11) (#18347)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-08-22 10:31:47 -05:00
Brandon HarveyGitHubpre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>J. Nick KostonBluetooth Devices Bot
ef1d77885d [captive_portal] Show each network once in the scan list (#17847)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Bluetooth Devices Bot <bluetooth@koston.org>
2026-08-22 05:04:08 +00:00
Keith BurzinskiandGitHub dccf55eadc [remote_transmitter] Use hardware PWM on rtl87xx to fix watchdog crash (#18579) 2026-08-21 23:53:16 -05:00
65704e881f [mitsubishi_cn105] Add Fahrenheit support (#15488)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-21 23:52:18 -05:00
Brandon HarveyandGitHub a30e82459f [deep_sleep] Reject wakeup_pin_mode at both levels on BK72xx (#18615) 2026-08-21 23:51:19 -05:00
Brandon HarveyandGitHub 5a300e92f1 [wifi] Inline the trivial WiFiScanResult accessors (#18613) 2026-08-22 04:14:56 +00:00
8e9fb0f93c [remote_transmitter] Fix repeat gap timing on LibreTiny Beken (#18585)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-21 23:10:47 -05:00
J. Nick Koston 4eb31c45da Isolate the preresolved-ccache test from ambient CCACHE_DIR, future-tense the sentinel comment 2026-08-21 21:27:49 -05:00
J. Nick Koston 4bea2dd058 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-21 21:01:47 -05:00
J. Nick Koston db0e52a36b Cover shell_token's Windows branch on the coverage-uploading runner 2026-08-21 21:01:37 -05:00
J. Nick Koston 6a963ba794 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-21 20:56:37 -05:00
J. Nick Koston cbf9e0ab14 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-21 20:56:37 -05:00
J. Nick Koston a527b21761 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-21 20:56:36 -05:00
J. Nick Koston 4d63564cdd Phrase the board-table regen comment imperatively 2026-08-21 20:56:27 -05:00
J. Nick Koston 012b571a2e Make the ccache sentinel public; sibling modules pass it as a default 2026-08-21 20:55:07 -05:00
J. Nick Koston 4a16365f13 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-21 20:51:56 -05:00
J. Nick Koston 587873a2da Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-21 20:51:56 -05:00
J. Nick Koston 4101ad4fb1 One owner for the toolchain tool layout, thread a pre-resolved ccache
toolchain_tool carries the bin/xtensa-lx106-elf-<name> pattern and the
Windows suffix that four call sites previously spelled out (only one of
which handled the suffix). ccache_env and get_build_env accept the
already-resolved ccache path so run_compile can resolve once instead of
paying the PATH scan and runnability probe three times per build.
2026-08-21 20:46:10 -05:00
J. Nick Koston e9d6132c61 Name the honored native-arduino pio options once
NATIVE_ARDUINO_PIO_OPTIONS is the single list the routing reads; the
backend's ignored-option warning consumes it too, so honoring a new key
is one edit instead of two synchronized ones.
2026-08-21 20:43:43 -05:00
J. Nick Koston 7298eed5b3 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-21 20:27:43 -05:00
J. Nick Koston 78a1e88915 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-21 20:27:43 -05:00
J. Nick Koston d8c02221cc Route board_build.ldscript through to the native ESP8266 generator too
Published configs override it to reserve a filesystem region
(SmartIntercom's eagle.flash.4m2m.ld) or to correct a board's assumed
flash size (2 MB ESP8285 plugs pinning eagle.flash.2m.ld); dropping it
changes the flash layout under them.
2026-08-21 20:27:33 -05:00
J. Nick Koston 76d8c9eb46 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-21 20:25:32 -05:00
J. Nick Koston a703acf85f Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-21 20:25:32 -05:00
J. Nick Koston 7fb18f395f Honor board_build.f_cpu under the native ESP8266 Arduino toolchain
Dropping it was a real regression: many published ESP8266 configs pin
board_build.f_cpu: 160000000L for timing-sensitive integrations
(MHI-AC-Ctrl documents the 160 MHz requirement in its example), and the
warn-and-drop left those devices at 80 MHz. The option now routes into
CORE.platformio_options under toolchain: arduino for the generator to
consume; other native toolchains keep the warning.
2026-08-21 20:25:23 -05:00
J. Nick Koston 97ed253c38 Say the floor's config-validation guard is future wiring, not present
Nothing at this commit enforces MIN_FRAMEWORK_VERSION during
validation; the native backend's validator arrives with the wiring PR.
The comment no longer invites skipping it.
2026-08-21 16:07:30 -05:00
J. Nick Koston cbf1241401 Resolve the ccache path on every call, matching the espidf backend
functools.cache froze the ESPHOME_CCACHE_ENABLE/PATH decision for the
process lifetime, which a long-lived host like device-builder never
restarts, and forced a cache_clear fixture in the tests. The docstring
and test now pin the uncached delegation.
2026-08-21 15:33:51 -05:00
J. Nick Koston 33aea420ac Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-21 15:19:10 -05:00
J. Nick Koston e3d29e9fff Name a non-list registry system field instead of a TypeError
file["system"] normalized None and str but let an int or dict fall
through to the in test, where an int raises TypeError and a dict
becomes a key test; both now raise the same Unexpected-response error
as the other shape guards.
2026-08-21 15:18:54 -05:00
J. Nick Koston 4dc2d34399 State the encoder's real registry parity and why the 3.1.1 floor exists
The docstring claimed exact registry names for every core above 2.6.2;
2.6.3, 3.0.0 and 3.0.1 ship with non-zero trailing components the
formula cannot produce. Name them, say which callers are safe (the ~
range on the PlatformIO path, the floored check_and_install), and
credit the floor with covering 3.0.0/3.0.1 as well as the absent 3.1.0.
2026-08-21 14:50:36 -05:00
esphome[bot]andGitHub 11ea819bc7 Bump aioesphomeapi from 45.12.0 to 45.13.1 (#18600) 2026-08-21 14:36:47 -05:00
J. Nick Koston c07a5a8da4 Match the encoder's lower bound to the PlatformIO era guard, drop the unused logger
<= 2.6.2 instead of < 2.6.3: a 2.6.2 pre-release sorts above 2.6.2 and
belongs to the package-major-3 encoding, so the helper now describes
the same set as _format_framework_arduino_version and a 2.6.2-b1 pin
formats as it does on dev. The module never logged, so its _LOGGER and
logging import go.
2026-08-21 14:32:27 -05:00
J. Nick Koston 93a768531d Anchor the 4.x error to the version key, bound the encoder, build PATH without empty entries
cv.Invalid now carries path=[CONF_VERSION] so the rejection lands on
the framework version line the comment promised, matching the esp32
convention. framework_package_version raises for cores before 2.6.3
instead of encoding them wrongly for a caller that skipped the
PlatformIO path's era guard. get_build_env joins PATH from filtered
parts so an absent PATH cannot leave a trailing separator that the
shell reads as the current directory.
2026-08-21 14:12:20 -05:00
esphome[bot]andGitHub abc9098bd8 Bump bundled esphome-device-builder to 1.12.3 (#18601) 2026-08-21 14:05:00 -05:00
J. Nick Koston e3cf2407ca Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-21 13:13:15 -05:00
J. Nick Koston 45cbe0aa4b Parse IDF_CCACHE_ENABLE strictly through the shared helper, fix the ccache docstrings
parse_enable_env carries the 1/true/yes/on and 0/false/no/off table for
both knobs, so IDF_CCACHE_ENABLE=off disables instead of reading as
truthy and suppressing the shared opt-out. The ccache module docstring
and the ESP-IDF _ccache_env docstring now describe the precedence this
PR actually ships, and the probe test duplicated by the helper move is
dropped from the PlatformIO toolchain tests.
2026-08-21 13:12:57 -05:00
Kevin AhrendtandGitHub 00cffa09a2 [sendspin] Convert tests to package-style includes (#18588) 2026-08-21 13:05:07 -05:00
J. Nick Koston 1f1cab9c37 Anchor the 4.x rejection to the framework version line, isolate the ccache env test
The installer's EsphomeError becomes cv.Invalid at the config
validator, and test_ccache_env clears the ambient environment so a
developer's exported CCACHE_DIR cannot fail it.
2026-08-21 12:51:09 -05:00
J. Nick Koston 85335de839 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-21 12:25:13 -05:00
J. Nick Koston 0f7cd3fada Single-quote POSIX shell tokens, share the tool probe, harden registry checks
shell_token now picks the quoting style per platform: single quotes on
POSIX (sh expands nothing inside them, so backslash runs, $VAR, and
backticks reach the compiler exactly as lexed, matching SCons's
no-shell spawn) and the CreateProcess argv rule on Windows. A test
round-trips every case through a real /bin/sh.

The ninja and ccache runnability probes collapse into one
tool_version_runs helper in framework_helpers. The registry names a
non-dict top-level payload like the inner guards, and the expect layout
check also runs on marker hits so a marked install that later lost
files fails by name. The ESP-IDF ccache gate defers to
resolve_ccache_path so ESPHOME_CCACHE_ENABLE=0 disables ccache there
too, with IDF_CCACHE_ENABLE still taking precedence.
2026-08-21 12:25:03 -05:00
J. Nick Koston 653f9e8f2a Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-21 11:14:35 -05:00
J. Nick Koston 510f667bd1 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-21 11:14:35 -05:00
J. Nick Koston c93cf55967 Skip the cache refresh when the CLI toolchain differs from the compile's
Platforms normalize toolchain-sensitive keys differently (the esp32
board name, for one), so a config validated under a mismatching
--toolchain must not overwrite the cache while the sidecar keeps the
compile's toolchain. The guard lives in _refresh_sidecar where the old
sidecar is already loaded; legacy sidecars without the field are
unaffected.
2026-08-21 11:14:26 -05:00
Jesse HillsandGitHub aa944456e0 [core] Add type annotations to component Python (6/11) (#18343) 2026-08-21 11:11:52 -05:00
Jonathan SwobodaandGitHub 409d74a48d [esp32_hosted] Fire on_update_available trigger when update is detected (#18591) 2026-08-21 11:53:44 -04:00
J. Nick Koston f3f6fe8a7d Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-21 09:58:44 -05:00
J. Nick Koston efe1b0e744 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-21 09:58:44 -05:00
J. Nick Koston 6cb446baf8 State what the cache refresh actually writes
The comment claimed the refresh's sidecar records the CLI toolchain;
_refresh_sidecar returns early when a compile-written sidecar exists,
so the compile's toolchain stands. Reword the comment and the test
docstring to match.
2026-08-21 09:58:32 -05:00
J. Nick Koston 0fa081cd80 Name malformed registry elements, parse ESPHOME_CCACHE_ENABLE strictly, require a package layout check
Registry version and file entries that are not dicts now raise the same
Unexpected-response error as the other shape guards instead of an
AttributeError traceback. install_package rejects an empty expect so
every install is layout-validated before the marker is written.
ESPHOME_CCACHE_ENABLE values outside 1/true/yes/on and 0/false/no/off
warn and are treated as unset instead of bool(str) flipping them to
enabled. The strip_win_long_path_prefix test moved next to the function
in test_framework_helpers, and a stale comment naming its old module is
fixed.
2026-08-21 09:58:16 -05:00
Frédéric MetrichGitHubpre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>Claude Sonnet 4.6
7957808f00 [emontx] Fix sensor state_class defaults not being applied correctly (#17610)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-21 09:57:58 -05:00
J. Nick Koston 8fd8766167 Sandbox ESPHOME_ARDUINO8266_PREFIX in the writer test isolation fixture
The autouse fixture pinned the idf and sdk-nrf prefixes but not the new
arduino8266 one, so running the suite with that env var exported deleted
the pointed-to install. Also adds the clean_all removal test the other
two native installs already have.
2026-08-21 09:35:53 -05:00
J. Nick Koston 225c62dbf1 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-21 01:05:51 -05:00
J. Nick Koston 0b0194cf41 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-21 01:05:51 -05:00
J. Nick Koston 8b9773c4bd Gate only the cache read on an explicit CLI toolchain, keep the refresh
The upload/logs cache split: an explicit --toolchain still skips the
read and runs the per-platform validators, but the freshly validated
config is saved again so a later plain run keeps the fast path (the
sidecar records the resolved toolchain). check_supported_toolchain is
module-private now since it has no caller outside config_validation,
and require_platformio_toolchain's docstring describes what actually
happens when another platform's toolchain is passed.
2026-08-21 01:05:38 -05:00
J. Nick Koston 078bcba473 Backend-neutral unsupported-core message, pin the PIO source formatter 2026-08-20 23:55:15 -05:00
J. Nick Koston 85acafb646 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-20 23:54:17 -05:00
J. Nick Koston 18a2aa2097 Probe the PATH ninja, chain import errors, harden registry payload checks, honest ccache docstring 2026-08-20 23:53:40 -05:00
J. Nick Koston 88f1c4474f Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-20 23:47:51 -05:00
J. Nick Koston 6ed01b06c3 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-20 23:47:51 -05:00
J. Nick Koston 24eb6354f9 Run validators when a CLI toolchain is given on the cache fast path, cover every LibreTiny family 2026-08-20 23:47:47 -05:00
J. Nick Koston 355d02ee6a Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-20 22:08:43 -05:00
J. Nick Koston f5ac480dff Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-20 22:08:43 -05:00
J. Nick Koston 19555e8eaf Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-20 22:08:43 -05:00
J. Nick Koston d47dc0d992 Pin fingerprint stability and sensitivity behaviorally, note the platform pairing 2026-08-20 22:08:41 -05:00
J. Nick Koston 6a363c1746 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-20 21:09:43 -05:00
J. Nick Koston b0a214830a Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-20 21:09:43 -05:00
J. Nick Koston 38744e4222 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-20 21:09:43 -05:00
J. Nick Koston 9624abcd57 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-20 21:09:42 -05:00
J. Nick Koston 2c229debe8 Best-effort bad encodings, dedupe append warnings, tolerate structurally odd compile DBs 2026-08-20 21:09:38 -05:00
J. Nick Koston 5857bfcf52 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-20 20:53:32 -05:00
J. Nick Koston 378a01a838 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-20 20:53:31 -05:00
J. Nick Koston e266a449e0 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-20 20:53:31 -05:00
J. Nick Koston 6a0ce046fd Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-20 20:53:31 -05:00
J. Nick Koston 6b6344b454 Cover the healthy post-compile idedata path 2026-08-20 20:53:30 -05:00
J. Nick Koston a0e82cd000 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-20 20:48:32 -05:00
J. Nick Koston a56c465d6d Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-20 20:48:32 -05:00
J. Nick Koston b3d908d10e Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-20 20:48:32 -05:00
J. Nick Koston 09fd3fb53b Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-20 20:48:32 -05:00
J. Nick Koston 73ab04330e Treat sys.exit(0) as script success, fail on an unreadable declared script 2026-08-20 20:48:30 -05:00
J. Nick Koston a255f3b07c Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-20 20:46:31 -05:00
J. Nick Koston 57b69cae81 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-20 20:46:30 -05:00
J. Nick Koston 9fccf1ce33 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-20 20:46:30 -05:00
J. Nick Koston b8bb4453e0 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-20 20:46:30 -05:00
J. Nick Koston 91eed262c8 Repr zero-arg script exceptions, surface a None idedata after an IDF build 2026-08-20 20:46:15 -05:00
J. Nick Koston c1a0ed0d46 Reject only unknown package majors: 2.6.3+ cores share the package-major-3 encoding 2026-08-20 20:44:24 -05:00
J. Nick Koston 4969d9bf4a Pin the lx106 layout, reject non-3.x cores, pin installer args, delegate ccache policy tests 2026-08-20 20:39:20 -05:00
J. Nick Koston 1c5441b69e Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-20 20:38:30 -05:00
J. Nick Koston ec8437fea2 Quote empty tokens, honest docstrings, strict registry payloads, required layout checks, honest patch targets 2026-08-20 20:38:09 -05:00
J. Nick Koston 8513dfeebd Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-20 20:36:32 -05:00
J. Nick Koston b42babf749 Route platformio build_unflags natively, correct the gate and fallback comments 2026-08-20 20:35:49 -05:00
J. Nick Koston e1c4984498 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-20 20:34:25 -05:00
J. Nick Koston 3063b9067a State what the fingerprint test actually pins 2026-08-20 20:34:24 -05:00
J. Nick Koston a73180e073 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-20 19:51:30 -05:00
J. Nick Koston b7a3181d13 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-20 19:51:28 -05:00
J. Nick Koston 1958746ae0 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-20 19:51:28 -05:00
J. Nick Koston 62c27826ee Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-20 19:51:27 -05:00
J. Nick Koston c95e0b90f5 Raise on a traversing extraScript, survive sys.exit in vendored scripts, fix the caveats wording 2026-08-20 19:51:26 -05:00
J. Nick Koston cc3f9be805 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-20 19:29:29 -05:00
J. Nick Koston b05efb0811 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-20 19:29:29 -05:00
J. Nick Koston 321bc3b459 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-20 19:29:28 -05:00
J. Nick Koston 84a3c39562 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-20 19:29:28 -05:00
J. Nick Koston 5cf2135038 Cover the warn-once dedup branch 2026-08-20 19:29:27 -05:00
J. Nick Koston b3afd951fe Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-20 19:25:46 -05:00
J. Nick Koston 8dd065997f Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-20 19:25:46 -05:00
J. Nick Koston 13e84e9bff Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-20 19:25:45 -05:00
J. Nick Koston 7ad97697b2 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-20 19:25:45 -05:00
J. Nick Koston 5bd328e6c4 Fail on a declared-but-absent extraScript, warn once per unsupported env method, split and widen tests 2026-08-20 19:25:33 -05:00
J. Nick Koston 878883bb67 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-20 19:02:47 -05:00
J. Nick Koston 8c671ae8ea Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-20 19:02:46 -05:00
J. Nick Koston ac1160de25 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-20 19:02:46 -05:00
J. Nick Koston 5cec72020f Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-20 19:02:46 -05:00
J. Nick Koston ef2f16713a Widen the post-compile idedata guard to every failure the comment promises 2026-08-20 19:02:35 -05:00
J. Nick Koston 0268abc1e4 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-20 18:40:46 -05:00
J. Nick Koston c103e4d974 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-20 18:40:46 -05:00
J. Nick Koston 06fc234b12 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-20 18:40:46 -05:00
J. Nick Koston 12f8b14418 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-20 18:40:46 -05:00
J. Nick Koston 790c6d6353 Drop the never-hitting parse memo, guard the post-compile idedata refresh, pin the kind map 2026-08-20 18:40:29 -05:00
J. Nick Koston b0b3017d78 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-20 18:18:29 -05:00
J. Nick Koston 47780fec6e Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra
# Conflicts:
#	esphome/platformio/toolchain.py
2026-08-20 18:18:26 -05:00
J. Nick Koston c3e7d37d29 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-20 18:18:07 -05:00
J. Nick Koston 77fa411f04 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-20 18:18:07 -05:00
J. Nick Koston bc6b997350 Restore the lazy shlex import, pin the probe convention, assert the shim diagnostics 2026-08-20 18:17:56 -05:00
J. Nick KostonandGitHub 52bfc0efb1 [espnow] Fix dump_config crash when enable_on_boot is false (#18572) 2026-08-20 18:06:23 -05:00
J. Nick Koston 1bdddcc55e Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-20 17:54:08 -05:00
J. Nick Koston a5a1a017d0 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra
# Conflicts:
#	esphome/platformio/toolchain.py
2026-08-20 17:53:57 -05:00
J. Nick Koston 9f3df90dba Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-20 17:52:58 -05:00
J. Nick Koston e20c43520b Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-20 17:52:57 -05:00
J. Nick Koston 9b12934307 Reject a launcher compile DB by name before the probe, discard partial script captures, log dropped env keys 2026-08-20 17:52:54 -05:00
J. Nick Koston eb38afb569 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-20 17:30:00 -05:00
J. Nick Koston 60896b3533 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-20 17:30:00 -05:00
J. Nick Koston 46baef5231 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-20 17:29:59 -05:00
J. Nick Koston f93d46e684 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-20 17:29:59 -05:00
J. Nick Koston d33a097152 Name every skipped size line, best-effort script compile, unusable idedata returns None 2026-08-20 17:29:47 -05:00
46f90d0c54 [core] Add portable strcasestr implementation named str_contains_ignore_case (#18497)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-20 17:28:17 -05:00
guillempagesGitHubpre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>J. Nick KostonJ. Nick Koston
5177972c04 [runtime_image] keep decoder allocated (#18488)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-20 17:27:44 -05:00
J. Nick Koston 14a58b788d Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-20 17:12:32 -05:00
J. Nick Koston 454f299cd2 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-20 17:12:31 -05:00
J. Nick Koston 70a3880c9f Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-20 17:12:31 -05:00
J. Nick Koston 8008eda57f Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-20 17:12:31 -05:00
J. Nick Koston 466f890e1f Clean errors for non-string flags, visible size-summary skips, partial extra-script capture 2026-08-20 17:12:19 -05:00
esphome[bot]andGitHub ece90ee97b Bump bundled esphome-device-builder to 1.12.2 (#18573) 2026-08-20 16:59:30 -05:00
J. Nick Koston 8af1b9a523 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-20 16:54:39 -05:00
J. Nick Koston 9c543c9776 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-20 16:54:39 -05:00
J. Nick Koston fd47d5bcd2 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-20 16:54:39 -05:00
J. Nick Koston 57b7f03e42 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-20 16:54:39 -05:00
J. Nick Koston 4ab2a7ba98 Join flag args per entry like ParseFlags, pin the launcher strip log, record the asm merge decision 2026-08-20 16:54:28 -05:00
J. Nick Koston 96aabe428a Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-20 16:51:30 -05:00
J. Nick Koston cb2dfddf17 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-20 16:51:30 -05:00
J. Nick Koston 20f0e37f43 Cover the unresolved-toolchain ordering guard 2026-08-20 16:51:29 -05:00
J. Nick Koston f4eefa111d Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-20 16:46:52 -05:00
J. Nick Koston 2bbca6229a Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-20 16:46:51 -05:00
J. Nick Koston a50c4db187 Type the toolchain factories, guard the legacy libretiny stub, tighten the gate comments 2026-08-20 16:46:48 -05:00
J. Nick Koston 1ecbb57b24 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-20 16:34:31 -05:00
J. Nick Koston 2e0b330366 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-20 16:34:31 -05:00
J. Nick Koston aea97d1022 Cover the arduino toolchain predicate 2026-08-20 16:34:21 -05:00
J. Nick Koston 65f5c302c5 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-20 16:30:26 -05:00
J. Nick Koston 16be3b1bbb Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-20 16:30:26 -05:00
J. Nick Koston 84940f1726 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-20 16:30:26 -05:00
J. Nick Koston 519ea38d73 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-20 16:30:26 -05:00
J. Nick Koston a0f0ad8729 Restore the underscore in test_parse_library_json 2026-08-20 16:30:13 -05:00
J. Nick Koston 51af18cdfb Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-20 16:25:48 -05:00
J. Nick Koston efacdd57ac Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-20 16:25:48 -05:00
J. Nick Koston 2280d3352d Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-20 16:25:48 -05:00
J. Nick Koston 0fad14be59 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-20 16:25:48 -05:00
J. Nick Koston 57a9c71da8 Cover the malformed build.flags rejection 2026-08-20 16:25:47 -05:00
J. Nick Koston 3d8a3c6527 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-20 16:24:32 -05:00
J. Nick Koston e618c99f7d Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-20 16:24:32 -05:00
J. Nick Koston 89df2787f2 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-20 16:24:32 -05:00
J. Nick Koston 53f1cc116a Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-20 16:24:32 -05:00
J. Nick Koston 1483b7c10b Detect launchers by a closed denylist instead of guessing compiler names 2026-08-20 16:24:21 -05:00
J. Nick Koston c8e816bf48 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-20 16:20:45 -05:00
J. Nick Koston 53935615f1 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-20 16:20:45 -05:00
J. Nick Koston 360c51329c Cover the native-toolchain predicate 2026-08-20 16:20:37 -05:00
J. Nick Koston 2e183f262b Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-20 16:13:21 -05:00
J. Nick Koston 37de984df5 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-20 16:13:20 -05:00
J. Nick Koston c96c05eea4 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-build-infra 2026-08-20 16:13:20 -05:00
J. Nick Koston 2cf4087e2f Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-20 16:13:20 -05:00
J. Nick Koston 7fe0847e5c Update espidf test to the public parse_library_json name 2026-08-20 16:13:19 -05:00
J. Nick Koston 80815f1dc7 Make parse_library_json public alongside its properties sibling 2026-08-20 16:12:52 -05:00
J. Nick Koston 5b19c139cb Force-quote shell metacharacters outside the safe token set 2026-08-20 16:09:09 -05:00
J. Nick Koston e187522490 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer 2026-08-20 16:06:32 -05:00
J. Nick Koston 79b8bf2657 Carry the ninja escaping and quoting helpers with tests 2026-08-20 16:06:30 -05:00
J. Nick Koston e36b8c5446 Point the ccache probe patch at its new module 2026-08-20 16:04:43 -05:00
J. Nick Koston 9c52c349d6 Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer
# Conflicts:
#	esphome/components/nrf52/framework.py
#	esphome/espidf/framework.py
#	esphome/framework_helpers.py
#	esphome/platformio/toolchain.py
2026-08-20 16:04:05 -05:00
J. Nick Koston d4861d89b0 Add the shared registry, ninja, ccache, and tools-cache infrastructure for native toolchains 2026-08-20 16:03:38 -05:00
J. Nick Koston 3166136db0 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-20 16:02:03 -05:00
J. Nick Koston 8f5f349e1b Pin the decoy anchor, guard unselected segments, fingerprint the module source 2026-08-20 16:01:06 -05:00
J. Nick Koston 83b4da1777 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-20 15:59:10 -05:00
J. Nick Koston 12c1b15578 Reject malformed build.flags by name and pin the idedata cache-hit path 2026-08-20 15:59:03 -05:00
J. Nick Koston e6debb980d Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-framework-installer 2026-08-20 15:55:01 -05:00
J. Nick Koston b3d5fccba5 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-20 15:55:01 -05:00
J. Nick Koston 6a84d67786 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-20 15:55:01 -05:00
J. Nick Koston b90aeb17e3 Hoist find_ninja to build_helpers, type the installed paths, dedupe espidf ccache env 2026-08-20 15:46:07 -05:00
J. Nick Koston 3a51514fc1 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-framework-installer 2026-08-20 15:44:34 -05:00
J. Nick Koston 6ad3e0d8ad Add toolchain validator factories and a native-toolchain predicate 2026-08-20 15:44:23 -05:00
J. Nick Koston 7a3f700206 Act on bad-compiler detection, atomic logged idedata cache, memoized entry parsing, callable board_mcu 2026-08-20 15:41:39 -05:00
J. Nick Koston 5a5496f7f7 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-framework-installer
# Conflicts:
#	esphome/platformio/toolchain.py
2026-08-20 15:26:58 -05:00
J. Nick Koston 9d9953ea47 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-20 15:26:39 -05:00
J. Nick Koston 7d9c18a330 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-20 15:26:38 -05:00
J. Nick Koston 113259275c Move the PlatformIO registry installer to esphome.platformio.registry and transliterate get_systype 2026-08-20 15:25:12 -05:00
dependabot[bot]andGitHub 4cfa4893ef Bump docker/setup-buildx-action from 4.2.0 to 4.3.0 in the docker-actions group (#18564)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-20 15:24:28 -05:00
J. Nick Koston 5a4337c240 Share the size line format, derive source extensions from a kind map, close_fds on the ccache probe 2026-08-20 15:21:31 -05:00
J. Nick Koston 38b6f222b5 Use a host table and the shared downloader for registry metadata 2026-08-20 15:12:37 -05:00
dependabot[bot]andGitHub edd4a86d14 Bump prek from 0.4.13 to 0.4.14 (#18563)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-20 15:02:15 -05:00
Jesse HillsandGitHub 44dcd82d78 [mipi_spi] Toggle D/C only while holding the SPI bus (#18529) 2026-08-21 07:58:11 +12:00
Jesse HillsandGitHub 12da2140cf [core] Add type annotations to component Python (11/11) (#18348) 2026-08-21 07:57:14 +12:00
Jesse HillsandGitHub c006e9804a [core] Add type annotations to component Python (9/11) (#18346) 2026-08-21 07:56:48 +12:00
6343c11873 [core] Add type annotations to component Python (5/11) (#18342)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-08-21 07:56:02 +12:00
J. Nick Koston c72de504fd Hard install lock, ninja-first install order, enforced floor, backoff, and by-name host failures everywhere 2026-08-20 14:43:48 -05:00
J. Nick Koston ff167e5193 Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-framework-installer 2026-08-20 14:41:55 -05:00
J. Nick Koston 2861cbb749 Harden the shared toolchain check and pin every platform family's rejection 2026-08-20 14:41:23 -05:00
J. Nick Koston 8bf699cf79 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-20 14:39:21 -05:00
J. Nick Koston 375cf95240 Anchor the segment regex, reject unknown segments, and pin real-script fixtures 2026-08-20 14:38:25 -05:00
J. Nick Koston f44398b09e Fix the remaining ccache probe patch target 2026-08-20 14:24:28 -05:00
J. Nick Koston 1307cc25c5 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-20 14:14:44 -05:00
J. Nick Koston 353ef2dee1 Merge branch 'esp8266-native-build-surgery' into esp8266-native-framework-installer 2026-08-20 14:14:44 -05:00
J. Nick Koston 18e3eac419 Export a surgery fingerprint for linker-script cache stamps 2026-08-20 14:14:32 -05:00
J. Nick Koston d9cc72c650 Point the ccache tests at the shared helpers 2026-08-20 14:07:34 -05:00
J. Nick Koston 0fe9d87124 Share the ccache and tools-path helpers across the native backends 2026-08-20 14:04:38 -05:00
J. Nick Koston 5398ca98eb Merge branch 'esp8266-native-toolchain-plumbing' into esp8266-native-framework-installer 2026-08-20 13:59:14 -05:00
J. Nick Koston bbf2788684 Share one unsupported-toolchain check across platforms 2026-08-20 13:58:46 -05:00
J. Nick Koston c7e1d044a3 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-20 13:57:53 -05:00
J. Nick Koston 0620ce81c3 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-20 13:57:51 -05:00
J. Nick Koston 2564ad0deb Warn once per non-compiler path, accept versioned compiler names, and pin the trigger table 2026-08-20 13:57:30 -05:00
J. Nick Koston fa45c4a016 Merge branch 'esp8266-native-build-surgery' into esp8266-native-framework-installer 2026-08-20 13:44:29 -05:00
J. Nick Koston 3d7c0eac57 Merge branch 'esp8266-native-build-surgery' into esp8266-native-toolchain-plumbing 2026-08-20 13:44:29 -05:00
J. Nick Koston e3936f500f Patch exactly the named segments and share the segment-line pattern 2026-08-20 13:44:12 -05:00
J. Nick Koston cea86989c9 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-20 13:42:37 -05:00
J. Nick Koston e82453e80e Complete the unit-test package chain so test modules import fully qualified 2026-08-20 13:42:15 -05:00
J. Nick Koston 3eec5085da Give the format_bar tests a unique basename 2026-08-20 13:39:46 -05:00
J. Nick Koston 57c8648210 Drop the test package init that collides with script/build_helpers.py on the CI path 2026-08-20 13:38:54 -05:00
J. Nick Koston 1db7309f4a Merge branch 'esp8266-native-shared-helpers' into esp8266-native-framework-installer 2026-08-20 13:35:42 -05:00
J. Nick Koston 1d0a1d2b38 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-toolchain-plumbing 2026-08-20 13:35:42 -05:00
J. Nick Koston 46fa42fa33 Merge branch 'esp8266-native-shared-helpers' into esp8266-native-build-surgery 2026-08-20 13:35:42 -05:00
J. Nick Koston 9bd548ba92 Move the extraScript machinery into the platformio package and tidy the extraction 2026-08-20 13:35:07 -05:00
J. Nick Koston 3e77654719 Add the ESP8266 Arduino framework and toolchain installer 2026-08-20 12:58:06 -05:00
J. Nick Koston 04ad9524f5 Add the arduino toolchain enum and validate --toolchain on every platform 2026-08-20 12:56:34 -05:00
J. Nick Koston 72da2b1ce2 Add the ESP8266 linker-script surgery and board build metadata 2026-08-20 12:53:50 -05:00
J. Nick Koston ab58f1080a Cover the tidy-project idedata assembly and hoist its helper import 2026-08-20 12:50:56 -05:00
J. Nick Koston 47942fb166 Note the upstream spelling in the format_bar citation 2026-08-20 12:47:54 -05:00
J. Nick Koston f326c3f04d Move the shared helpers into a neutral build_helpers package 2026-08-20 12:43:34 -05:00
J. Nick Koston 3b51ec2ae7 Extract the shared native-build helpers from the ESP-IDF backend 2026-08-20 12:36:42 -05:00
David van 't WoutandGitHub 2ab09e1a77 [core] Add add_cmake_arg (#18498) 2026-08-20 12:30:33 -05:00
J. Nick KostonandGitHub a3ea77c2f1 [core] Keep templated !include filenames as strings so Windows path normalization cannot corrupt them (#18549) 2026-08-20 11:32:15 -05:00
J. Nick KostonandGitHub 7fe4399b94 [esp32] Grow the default IDF component exclusion list (#18536) 2026-08-20 16:11:53 +00:00
Jesse HillsandGitHub 545f762568 [core] Add type annotations to component Python (8/11) (#18345) 2026-08-20 12:08:26 -04:00
Jesse HillsandGitHub e83439eaae [core] Add type annotations to component Python (7/11) (#18344) 2026-08-20 12:07:10 -04:00
Jesse HillsandGitHub b6a9761dae [core] Add type annotations to component Python (4/11) (#18341) 2026-08-20 10:59:51 -04:00
Jesse HillsandGitHub fbe4b39a16 [core] Add type annotations to component Python (3/11) (#18340) 2026-08-20 10:58:58 -04:00
ecca240eef [core] Add type annotations to component Python (2/11) (#18339)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-08-20 14:26:16 +00:00
J. Nick KostonandGitHub 347a6155f8 [uptime] Skip the timestamp sensor source when no time component is configured (#18535) 2026-08-20 10:07:17 -04:00
J. Nick KostonandGitHub a8e721abeb [esp32] Apply IDF component exclusions to native toolchain builds (#18531) 2026-08-20 10:03:49 -04:00
J. Nick KostonandGitHub 2d62ea78d2 [ota] Skip partition-access OTA sources when the feature is disabled (#18532) 2026-08-20 10:00:11 -04:00
J. Nick KostonandGitHub 8ef0f38f4e [ethernet] Skip the custom W5500 SPI driver for other ethernet types (#18533) 2026-08-20 09:59:30 -04:00
Jesse HillsandGitHub 3c47ab42d6 [core] Add type annotations to component Python (1/11) (#18338) 2026-08-20 09:55:15 -04:00
Alar AunandGitHub aafeca5859 [modbus_controller] Brace single-statement log bodies to fix -Wempty-body (#18543) 2026-08-20 09:54:28 -04:00
J. Nick KostonandGitHub 132f494195 [nrf52] Rebuild the Python env when its interpreter symlink dangles (#18540) 2026-08-20 07:59:57 -04:00
esphome[bot]andGitHub 5e9de7c94b Bump bundled esphome-device-builder to 1.12.1 (#18541) 2026-08-20 00:00:44 -05:00
Markus HällandGitHub b7d0b676fc [wifi] Take the lwIP core lock around sntp_servermode_dhcp() (#18511) 2026-08-19 23:31:27 -05:00
Jesse Hills 55b45fc6fb Merge branch 'release' into dev 2026-08-20 14:32:46 +12:00
Jesse HillsandGitHub 6f8dbb6fbc Merge pull request #18527 from esphome/bump-2026.8.0
2026.8.0
2026-08-20 14:32:15 +12:00
J. Nick KostonandJesse Hills ca97c86d65 [ci] Install requirements_dev.txt when the venv cache misses (#18502) 2026-08-20 13:12:33 +12:00
Jesse Hills 828eac90f3 Bump version to 2026.8.0 2026-08-20 11:51:59 +12:00
J. Nick KostonandJesse Hills d9359a70c1 [tests] Keep PlatformIO libdeps per xdist worker to stop a compile race (#18524) 2026-08-20 11:51:57 +12:00
Jesse Hills 29404a782c Merge branch 'beta' into dev 2026-08-20 10:32:46 +12:00
Jesse HillsandGitHub e75a7a61fa Merge pull request #18523 from esphome/bump-2026.8.0b6
2026.8.0b6
2026-08-20 10:32:26 +12:00
J. Nick KostonandGitHub 185f12266a [tests] Keep PlatformIO libdeps per xdist worker to stop a compile race (#18524) 2026-08-19 17:29:10 -05:00
J. Nick KostonandJesse Hills c455991962 [ci] Key PlatformIO cache on the Python version so a runner image bump does not serve a broken LibreTiny venv (#18512) 2026-08-20 09:38:44 +12:00
Jesse Hills f735dcadc0 Bump version to 2026.8.0b6 2026-08-20 09:33:05 +12:00
J. Nick KostonandJesse Hills 78a65eabdc [ci] Stop jobs hanging on apt by restoring the cached apt action and bounding raw apt calls (#18518) 2026-08-20 09:33:03 +12:00
b3fda9973e [image] Restore defaults:/files: support for platform entries (#18032)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-20 09:33:03 +12:00
esphome[bot]andJesse Hills 4a85c98285 Bump bundled esphome-device-builder to 1.12.0 (#18514) 2026-08-20 09:33:03 +12:00
esphome[bot]andJesse Hills 2c92a2498e Bump bundled esphome-device-builder to 1.11.5 (#18507) 2026-08-20 09:33:03 +12:00
esphome[bot]andJesse Hills 74e22b5ad7 Bump bundled esphome-device-builder to 1.11.4 (#18506) 2026-08-20 09:33:03 +12:00
esphome[bot]andJesse Hills e9e77d02a0 Bump bundled esphome-device-builder to 1.11.3 (#18505) 2026-08-20 09:33:03 +12:00
Jonathan SwobodaandJesse Hills 7418fcce8d [ci] Stop persisting the integration test ccache (#18504) 2026-08-20 09:33:03 +12:00
Jonathan SwobodaandJesse Hills b768e2a1ce [esp32] Fix ESP32-P4 bootloop on rev3 (v3.x) chips when only variant is set (#18500) 2026-08-20 09:33:03 +12:00
J. Nick KostonandJesse Hills 6084314cc9 [vscode] Report the origin of an unexpected exception during validation (#18494) 2026-08-20 09:33:03 +12:00
J. Nick KostonandJesse Hills 2df953f3d7 [platformio] Give the ccache wrapper a cmd.exe safe path (#18495) 2026-08-20 09:33:03 +12:00
10e592fa3a [modbus] CRC scan all unknown function codes (#18483)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-20 09:33:03 +12:00
J. Nick KostonandJesse Hills a99a8f364e [api] Bump noise-c to 0.1.21 (#18484) 2026-08-20 09:33:03 +12:00
J. Nick KostonandJesse Hills 200a1644a5 [ci] Fail the benchmark job when the C++ benchmark build fails (#18480) 2026-08-20 09:33:03 +12:00
J. Nick KostonandJesse Hills 9daae377fc [api] Bump noise-c to 0.1.20 (#18482) 2026-08-20 09:33:02 +12:00
J. Nick KostonandGitHub 28384673f6 Merge branch 'dev' into espidf_parallel_tool_prefetch 2026-08-19 16:32:48 -05:00
J. Nick KostonandGitHub 0b1065feee [ci] Stop jobs hanging on apt by restoring the cached apt action and bounding raw apt calls (#18518) 2026-08-20 09:30:34 +12:00
Clyde StubbsandGitHub 47da743d11 [ai] Add instructions for concise comments (#18522) 2026-08-19 16:09:12 -05:00
J. Nick Koston b361342bde Do not end a progress bar that never drew a frame 2026-08-19 15:13:04 -05:00
J. Nick KostonandGitHub d34eed6d2d Merge branch 'dev' into espidf_parallel_tool_prefetch 2026-08-19 14:41:44 -05:00
J. Nick Koston 12238eec3b Log prefetch failures after the bar, assert on the shared progress instance 2026-08-19 13:57:15 -05:00
J. Nick Koston fe53ff6b6b Finish the batch bar, cancel queued archives on Ctrl-C, report known sizes 2026-08-19 13:28:42 -05:00
470226ca03 [image] Restore defaults:/files: support for platform entries (#18032)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-19 12:30:12 -05:00
esphome[bot]andGitHub 7b7107556f Bump bundled esphome-device-builder to 1.12.0 (#18514) 2026-08-19 12:21:58 -05:00
J. Nick Koston d0f38d7644 [espidf] Download tool archives in parallel during the install prefetch 2026-08-19 12:03:39 -05:00
J. Nick KostonandGitHub f90b776071 [ci] Key PlatformIO cache on the Python version so a runner image bump does not serve a broken LibreTiny venv (#18512) 2026-08-19 16:39:01 +00:00
esphome[bot]andGitHub e26237e57d Bump bundled esphome-device-builder to 1.11.5 (#18507) 2026-08-19 00:42:05 -05:00
esphome[bot]andGitHub d2c3f749ab Bump bundled esphome-device-builder to 1.11.4 (#18506) 2026-08-18 23:24:23 -05:00
esphome[bot]andGitHub b7cc271219 Bump bundled esphome-device-builder to 1.11.3 (#18505) 2026-08-18 21:57:51 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
17eed7055b Bump resvg-py from 0.3.4 to 0.4.0 (#18460)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-18 22:22:51 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
b7121940c8 Update wheel requirement from <0.48,>=0.43 to >=0.43,<0.49 (#18459)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-18 22:08:23 -04:00
Jonathan SwobodaandGitHub 07fa16e2e7 [ci] Stop persisting the integration test ccache (#18504) 2026-08-18 21:38:18 -04:00
Jonathan SwobodaandGitHub 8aa7db15e5 [esp32] Fix ESP32-P4 bootloop on rev3 (v3.x) chips when only variant is set (#18500) 2026-08-18 21:25:09 -04:00
J. Nick KostonandGitHub 8b637b339b [vscode] Report the origin of an unexpected exception during validation (#18494) 2026-08-18 19:37:10 -05:00
J. Nick KostonandGitHub 4f866c563b [platformio] Give the ccache wrapper a cmd.exe safe path (#18495) 2026-08-18 19:36:57 -05:00
J. Nick KostonandGitHub 7a999f9a48 [ci] Install requirements_dev.txt when the venv cache misses (#18502) 2026-08-18 19:06:55 -05:00
esphome[bot]andGitHub 0a88c81d95 Bump aioesphomeapi from 45.11.0 to 45.12.0 (#18501) 2026-08-18 18:38:23 -05:00
J. Nick KostonandGitHub 804e8fb856 [socket] Remove constant duplicated by the beta merge (#18496) 2026-08-18 15:44:38 -05:00
Jesse Hills 43939a9199 Merge branch 'beta' into dev 2026-08-19 08:01:33 +12:00
Jesse HillsandGitHub f414a07bcd Merge pull request #18481 from esphome/bump-2026.8.0b5
2026.8.0b5
2026-08-19 08:01:14 +12:00
esphome[bot]andGitHub 5c9d050ebe Bump aioesphomeapi from 45.10.3 to 45.11.0 (#18493) 2026-08-18 17:02:54 +00:00
285a508e09 [modbus] CRC scan all unknown function codes (#18483)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-18 11:09:12 -05:00
J. Nick KostonandGitHub 92f55f721f [api] Bump noise-c to 0.1.21 (#18484) 2026-08-18 14:32:37 +00:00
Jonathan SwobodaandGitHub 476d540065 [ci] Fall back to files API when PR diff exceeds GitHub line limit (#18486) 2026-08-18 10:22:28 -04:00
J. Nick KostonandGitHub ae730d6357 [ci] Fail the benchmark job when the C++ benchmark build fails (#18480) 2026-08-18 09:03:33 -05:00
J. Nick KostonandGitHub 9823205ef3 [api] Bump noise-c to 0.1.20 (#18482) 2026-08-18 09:03:14 -05:00
Jesse Hills d1391c2b10 Bump version to 2026.8.0b5 2026-08-18 16:04:49 +12:00
J. Nick KostonandJesse Hills 8b888f31e0 [gpio_expander][pcf8574][pca9554][tca9555][pca6416a][pi4ioe5v6408][mcp23016][mcp23xxx_base] Reject unsupported interrupt_pin options (inverted, allow_other_uses) (#18472) 2026-08-18 16:04:47 +12:00
Jesse Hills 6a247dfe91 [light] Replace rgb_order/is_rgbw/is_wrgb with channel_colors (#18474) 2026-08-18 16:01:54 +12:00
J. Nick KostonandJesse Hills 482869fbbe [socket] Fix multi-second TCP stalls on ESP8266 by yielding to the SYS context (#18455) 2026-08-18 14:27:54 +12:00
esphome[bot]andJesse Hills 4dea147386 Bump bundled esphome-device-builder to 1.11.2 (#18477) 2026-08-18 14:27:54 +12:00
J. Nick KostonandJesse Hills 1fd6337254 [api] Bump noise-c to 0.1.19 (#18473) 2026-08-18 14:27:54 +12:00
esphome[bot]andJesse Hills 4ce6d59484 Bump bundled esphome-device-builder to 1.11.1 (#18475) 2026-08-18 14:27:54 +12:00
J. Nick KostonandJesse Hills 014cc19902 [api] Bump noise-c to 0.1.18 (#18451) 2026-08-18 14:27:54 +12:00
esphome[bot]Jesse Hillsesphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
b9041566ea Bump aioesphomeapi from 45.10.2 to 45.10.3 (#18433)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-08-18 14:27:54 +12:00
J. Nick KostonandJesse Hills 3a403c40d5 [ld2420] Drop the setup priority override so setup runs after the UART bus (#18428) 2026-08-18 14:27:54 +12:00
esphome[bot]andJesse Hills 096e71bd67 Bump aioesphomeapi from 45.10.1 to 45.10.2 (#18357) 2026-08-18 14:27:54 +12:00
J. Nick KostonandGitHub fffa902a1a [gpio_expander][pcf8574][pca9554][tca9555][pca6416a][pi4ioe5v6408][mcp23016][mcp23xxx_base] Reject unsupported interrupt_pin options (inverted, allow_other_uses) (#18472) 2026-08-18 13:32:03 +12:00
Jesse HillsandGitHub 463e3833da [light] Replace rgb_order/is_rgbw/is_wrgb with channel_colors (#18474) 2026-08-18 13:19:48 +12:00
J. Nick KostonandGitHub 4416aacebb [socket] Fix multi-second TCP stalls on ESP8266 by yielding to the SYS context (#18455) 2026-08-18 13:09:29 +12:00
esphome[bot]andGitHub 15a626bcf3 Bump bundled esphome-device-builder to 1.11.2 (#18477) 2026-08-17 19:16:38 -05:00
J. Nick KostonandGitHub a347a2e879 [api] Bump noise-c to 0.1.19 (#18473) 2026-08-17 18:58:11 -05:00
dependabot[bot]andGitHub 7be4566b41 Bump platformdirs from 4.11.2 to 4.11.3 (#18468)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-17 18:33:13 -05:00
esphome[bot]andGitHub 96e26c6a5f Bump bundled esphome-device-builder to 1.11.1 (#18475) 2026-08-17 17:54:23 -05:00
J. Nick KostonandGitHub 7362c01c67 [core] Replace base64 lookup tables with arithmetic mapping (#18454) 2026-08-17 16:14:06 -05:00
Jesse HillsandGitHub e45b4e4938 [core] Make FINAL_VALIDATE_SCHEMA functions return None (#18457) 2026-08-18 08:26:24 +12:00
J. Nick KostonandGitHub e32329cc11 [core] Sync pre-commit ruff hook with requirements (0.16.3) (#18469) 2026-08-17 15:00:37 -05:00
dependabot[bot]andGitHub 199e368fe2 Bump astral-sh/setup-uv from 9.0.0 to 10.0.1 (#18463)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-17 14:57:50 -05:00
dependabot[bot]andGitHub 55726120db Bump esphome/workflows/.github/workflows/lock.yml from 2026.7.0 to 2026.8.1 (#18465)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-17 14:47:28 -05:00
dependabot[bot]andGitHub 346ba7e831 Bump github/codeql-action/analyze from 4.37.6 to 4.37.7 (#18467)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-17 14:47:18 -05:00
dependabot[bot]andGitHub 9518006724 Bump ruff from 0.16.2 to 0.16.3 (#18458)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-17 14:46:04 -05:00
dependabot[bot]andGitHub 3d5f6f692f Bump filelock from 3.32.2 to 3.32.3 (#18461)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-17 14:45:41 -05:00
dependabot[bot]andGitHub 4712c15c75 Bump github/codeql-action/init from 4.37.6 to 4.37.7 (#18466)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-17 14:42:24 -05:00
dependabot[bot]andGitHub 9bc72529a6 Bump astral-sh/setup-uv from 9.0.0 to 10.0.1 in /.github/actions/restore-python (#18462)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-17 14:42:08 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
6d20943a9c Bump esphome/workflows/.github/workflows/stale.yml from 61fd37a044cad4e9aa4303027b2a61b6a34da855 to a1c1485ab46ef41a84a6a9d8abd7fa4b7628fd70 (#18464)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-17 15:38:06 -04:00
J. Nick KostonandGitHub a3af82867b [api] Bump noise-c to 0.1.18 (#18451) 2026-08-17 14:00:39 -05:00
J. Nick KostonandGitHub 27483a4101 [core] Retry gh CLI calls on transient network errors in CI scripts (#18292) 2026-08-17 13:15:31 -05:00
J. Nick KostonandGitHub 6d20ebc66b [socket] Shorten lwip TAG string (#18450) 2026-08-17 17:18:43 +00:00
J. Nick KostonandGitHub 031a038b49 [deep_sleep] Shorten bk72xx TAG string (#18445) 2026-08-17 11:56:37 -05:00
J. Nick KostonandGitHub f6c7434b2a [i2c] Shorten platform backend TAG strings (#18440) 2026-08-17 11:54:55 -05:00
J. Nick KostonandGitHub 3d9fecb562 [remote_receiver] Shorten esp32 TAG string (#18447) 2026-08-17 11:54:52 -05:00
J. Nick KostonandGitHub ebe93e2c68 [bluetooth_connection] Shorten platform TAG strings (#18449) 2026-08-17 11:54:35 -05:00
J. Nick KostonandGitHub 1f4fcead38 [nextion] Shorten upload TAG strings (#18448) 2026-08-17 11:54:29 -05:00
J. Nick KostonandGitHub 1f000ba668 [mqtt] Shorten esp32 backend TAG string (#18446) 2026-08-17 11:54:17 -05:00
J. Nick KostonandGitHub e0d28d7f5c [http_request] Shorten platform backend TAG strings (#18444) 2026-08-17 11:54:03 -05:00
J. Nick KostonandGitHub 47a58dd799 [internal_temperature] Shorten platform TAG strings (#18443) 2026-08-17 11:53:56 -05:00
J. Nick KostonandGitHub 37bea1c153 [spi] Shorten platform backend TAG strings (#18442) 2026-08-17 11:53:48 -05:00
J. Nick KostonandGitHub d1a7b8df8b [adc] Shorten platform TAG strings (#18441) 2026-08-17 11:53:42 -05:00
J. Nick KostonandGitHub c01f24553c [uart] Shorten platform backend TAG strings (#18439) 2026-08-17 11:53:29 -05:00
J. Nick KostonandGitHub 07e8b303b9 [ota] Shorten platform backend TAG strings (#18438) 2026-08-17 11:52:56 -05:00
esphome[bot]GitHubesphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
ebb0923362 Bump aioesphomeapi from 45.10.2 to 45.10.3 (#18433)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-08-16 22:34:00 -05:00
J. Nick KostonandGitHub e1c279718f [ld2420] Drop the setup priority override so setup runs after the UART bus (#18428) 2026-08-16 21:57:12 -05:00
J. Nick KostonandGitHub cf764740cf [api] Create the camera image reader lazily (#18421) 2026-08-16 21:56:58 -05:00
J. Nick KostonandGitHub 58d549ed4c [api] Move NoiseProtocolId off the connection object (#18420) 2026-08-16 21:56:37 -05:00
Jesse Hills 6b22d8068d Merge branch 'beta' into dev 2026-08-17 11:36:10 +12:00
Jesse HillsandGitHub 443d8f1f28 Merge pull request #18424 from esphome/bump-2026.8.0b4
2026.8.0b4
2026-08-17 11:33:27 +12:00
Jesse Hills 1ec21a2245 Bump version to 2026.8.0b4 2026-08-17 10:22:41 +12:00
J. Nick KostonandJesse Hills bb7d4c3630 [esp32] Split crash handler addr2line hint per core (#18418) 2026-08-17 10:22:39 +12:00
J. Nick KostonandJesse Hills f42fe9af29 [core] Skip redundant ESP8266 main loop wake posts from ISR context (#18416) 2026-08-17 10:22:39 +12:00
J. Nick KostonandJesse Hills 0bc2d71370 [bk72xx_ble] Fail early with a clear error on non BLE 5.x SoCs (#18406) 2026-08-17 10:22:39 +12:00
luar123andJesse Hills 594c12b3d9 [zigbee] bump esp-zigbee-sdk to 2.0.4 (#18415) 2026-08-17 10:22:39 +12:00
Jonathan SwobodaandJesse Hills bca72e9b6d [sensor] Pass NaN through the delta filter again (#18400) 2026-08-17 10:22:39 +12:00
J. Nick KostonandJesse Hills ce09504c92 [platformio] Skip ccache when the binary on PATH fails to run (#18407) 2026-08-17 10:22:39 +12:00
esphome[bot]andJesse Hills dda4566b9e Bump bundled esphome-device-builder to 1.11.0 (#18403) 2026-08-17 10:22:39 +12:00
Samuel SiebandJesse Hills 46a5665a66 [rotary_encoder] account for min and max value when resetting (#18197) 2026-08-17 10:22:39 +12:00
esphome[bot]andJesse Hills 9161f74bb1 Bump bundled esphome-device-builder to 1.10.0 (#18389) 2026-08-17 10:22:39 +12:00
J. Nick KostonandGitHub 3f01f9895f [esp32_hosted] Require ESP-IDF 5.3 or newer (#18417) 2026-08-16 12:44:49 -07:00
J. Nick KostonandGitHub 801a1817b5 [esp32] Split crash handler addr2line hint per core (#18418) 2026-08-16 12:44:00 -07:00
J. Nick KostonandGitHub 32c76ae828 [core] Skip redundant ESP8266 main loop wake posts from ISR context (#18416) 2026-08-16 12:43:27 -07:00
J. Nick KostonandGitHub c664f5fc95 [bk72xx_ble] Fail early with a clear error on non BLE 5.x SoCs (#18406) 2026-08-16 11:06:22 -07:00
luar123andGitHub 2bc4681fd6 [zigbee] bump esp-zigbee-sdk to 2.0.4 (#18415) 2026-08-16 13:33:25 -04:00
Jonathan SwobodaandGitHub 646501b0ef [sensor] Pass NaN through the delta filter again (#18400) 2026-08-16 12:36:51 -04:00
J. Nick KostonandGitHub de3e657d8b [platformio] Skip ccache when the binary on PATH fails to run (#18407) 2026-08-16 08:42:47 -07:00
esphome[bot]andGitHub 1add726892 Bump bundled esphome-device-builder to 1.11.0 (#18403) 2026-08-15 13:36:52 -07:00
Samuel SiebandGitHub 5a000cf5e4 [rotary_encoder] account for min and max value when resetting (#18197) 2026-08-15 11:16:04 -07:00
Joppy FurrandGitHub 6ed676fe32 [lvgl] Restore long_press_repeat_time functionality (#18393) 2026-08-14 23:14:53 -07:00
esphome[bot]andGitHub 7cceddb8a3 Bump bundled esphome-device-builder to 1.10.0 (#18389) 2026-08-15 01:48:27 +00:00
J. Nick KostonandGitHub 039b897e7b [ethernet] Defer clk_mode removal to 2026.11.0 (#18380) 2026-08-14 18:45:36 -07:00
J. Nick KostonandGitHub b178f74e5d [core] Save the validated config cache on the first upload or logs run (#18367) 2026-08-14 18:45:23 -07:00
Clyde StubbsandGitHub e5224e22ae [ci] Compare merge-branch base ref against the default branch (#18385) 2026-08-15 06:18:22 +10:00
Clyde StubbsandGitHub be66e8b99c [ci] Disable CodSpeed benchmarks job outside esphome/esphome (#18372) 2026-08-15 04:33:47 +10:00
Jesse Hills 4b91c8aff4 Merge branch 'beta' into dev 2026-08-14 23:15:57 +12:00
Jesse HillsandGitHub 9d7997f55a Merge pull request #18368 from esphome/bump-2026.8.0b3
2026.8.0b3
2026-08-14 22:49:40 +12:00
Jesse Hills 4db47de556 Bump version to 2026.8.0b3 2026-08-14 17:36:56 +12:00
Karl BeeckenandJesse Hills c8de632764 [core] fix PYTHONPATH leak (#18360) 2026-08-14 17:36:54 +12:00
J. Nick KostonandJesse Hills b794b7b1d1 [core] Restore cv.parse_esphome_version as a deprecated helper (#18366) 2026-08-14 17:36:54 +12:00
J. Nick KostonandJesse Hills add18d4e35 [core] Partially revert "Hash entity keys from the raw name to fix collisions" (#18361) 2026-08-14 17:36:54 +12:00
J. Nick KostonandJesse Hills 02c1810c3a [core] Load component aliases from a generated registry (#18335) 2026-08-14 17:36:54 +12:00
J. Nick KostonandJesse Hills 4f3153375a [ota] Retry uploads that fail from network errors (#18332) 2026-08-14 17:36:54 +12:00
J. Nick KostonandJesse Hills 1c3a67b5e8 [wifi] Fix ESP8266 crash in cnx_node_search when lwIP transmits after disconnect (#18333) 2026-08-14 17:36:54 +12:00
J. Nick KostonandJesse Hills 236ff33a09 [esp32_ble] Silence spurious warnings for local key GAP events (#18359) 2026-08-14 17:36:54 +12:00
J. Nick KostonandJesse Hills 7c07fb48c5 [esp32_ble_tracker] Fix missed BLE advertisements with WiFi on ESP-IDF 5.5.5 (#18356) 2026-08-14 17:36:54 +12:00
J. Nick KostonandJesse Hills d72bab79d7 [web_server_base] Stop deleting the web server on captive portal teardown (#18324) 2026-08-14 17:36:54 +12:00
Karl BeeckenandGitHub 617e2ec1e0 [core] fix PYTHONPATH leak (#18360) 2026-08-14 17:23:34 +12:00
J. Nick KostonandGitHub 1afac0312d [core] Restore cv.parse_esphome_version as a deprecated helper (#18366) 2026-08-14 17:22:22 +12:00
J. Nick KostonandGitHub b05465145f [core] Add preference key stability integration test (#18364) 2026-08-13 21:14:30 -05:00
dependabot[bot]andGitHub 990fc402fd Bump bleak from 2.1.1 to 3.0.2 (#16246)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-13 20:24:02 -05:00
J. Nick KostonandGitHub dd51624fbb [core] Partially revert "Hash entity keys from the raw name to fix collisions" (#18361) 2026-08-13 20:06:48 -05:00
dependabot[bot]andGitHub 45a056e337 Bump platformdirs from 4.11.1 to 4.11.2 (#18363)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-13 18:54:51 -05:00
dependabot[bot]andGitHub 37782f7206 Bump prek from 0.4.12 to 0.4.13 (#18362)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-13 18:54:37 -05:00
J. Nick KostonandGitHub 945c2458b3 [core] Load component aliases from a generated registry (#18335) 2026-08-13 13:31:19 -05:00
J. Nick KostonandGitHub 7420d23867 [ota] Retry uploads that fail from network errors (#18332) 2026-08-13 13:31:06 -05:00
J. Nick KostonandGitHub 191686c5b3 [wifi] Fix ESP8266 crash in cnx_node_search when lwIP transmits after disconnect (#18333) 2026-08-13 13:30:53 -05:00
J. Nick KostonandGitHub 137351fa8d [esp32_ble] Silence spurious warnings for local key GAP events (#18359) 2026-08-13 13:27:01 -05:00
J. Nick KostonandGitHub db5173697a [esp32_ble_tracker] Fix missed BLE advertisements with WiFi on ESP-IDF 5.5.5 (#18356) 2026-08-13 13:22:48 -05:00
Oliver KleineckeGitHubOliver Kleineckepre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
c7940382a9 [const] move CONF_LABEL to components/const (#18354)
Co-authored-by: Oliver Kleinecke <kleinecke.oliver@googlemail.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-08-13 14:18:22 -04:00
esphome[bot]andGitHub 87045ab9c0 Bump aioesphomeapi from 45.10.1 to 45.10.2 (#18357) 2026-08-13 11:14:14 -05:00
f1c4086778 [const] Move CONF_SLOT to components/const (#18350)
Co-authored-by: Oliver Kleinecke <p1ngb4ck@users.noreply.github.com>
2026-08-13 23:36:11 +12:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Jonathan Swoboda
f337d0acff Bump pylint from 4.0.6 to 4.0.7 (#18320)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-08-13 02:13:29 +00:00
J. Nick KostonandGitHub 9cc05b30d4 [web_server_base] Stop deleting the web server on captive portal teardown (#18324) 2026-08-12 21:09:01 -05:00
Jesse Hills e0b68c4d6d Merge branch 'beta' into dev 2026-08-13 14:06:46 +12:00
Jesse HillsandGitHub 9c3407cfdd Merge pull request #18334 from esphome/bump-2026.8.0b2
2026.8.0b2
2026-08-13 14:06:28 +12:00
Jesse Hills d3e27054f6 Bump version to 2026.8.0b2 2026-08-13 13:30:47 +12:00
J. Nick KostonandJesse Hills bd58b5c8b3 [core] Retry framework downloads on transient network errors (#18330) 2026-08-13 13:30:46 +12:00
J. Nick KostonandJesse Hills c1a326f32e [core] Don't block logs startup on MQTT IP discovery when addresses are known (#18313) 2026-08-13 13:30:46 +12:00
J. Nick KostonandJesse Hills a14ea0e8fa [ld2420] Fix out-of-bounds read when device reports unknown command error (#18322) 2026-08-13 13:30:46 +12:00
esphome[bot]andJesse Hills 48d6368ff9 Bump bundled esphome-device-builder to 1.9.6 (#18328) 2026-08-13 13:30:46 +12:00
Kevin AhrendtandJesse Hills 83cff59fdd [sendspin] Bump sendspin-cpp to v0.7.2 (#18316) 2026-08-13 13:30:46 +12:00
esphome[bot]andJesse Hills 89489b1f0d Bump aioesphomeapi from 45.10.0 to 45.10.1 (#18318) 2026-08-13 13:30:46 +12:00
J. Nick KostonandJesse Hills 7569a7b5ce [usb_uart] Fix uint32_t format specifier warning in pl2303 (#18310) 2026-08-13 13:30:45 +12:00
J. Nick KostonandGitHub 8e624b4117 [core] Retry framework downloads on transient network errors (#18330) 2026-08-13 12:14:50 +12:00
J. Nick KostonandGitHub 787a909aa4 [core] Don't block logs startup on MQTT IP discovery when addresses are known (#18313) 2026-08-13 12:11:32 +12:00
J. Nick KostonandGitHub 905485b673 [ld2420] Fix out-of-bounds read when device reports unknown command error (#18322) 2026-08-13 11:49:34 +12:00
esphome[bot]andGitHub 99677390e0 Bump bundled esphome-device-builder to 1.9.6 (#18328) 2026-08-12 16:27:14 -05:00
Kevin AhrendtandGitHub f2121130f9 [sendspin] Bump sendspin-cpp to v0.7.2 (#18316) 2026-08-12 12:49:54 -04:00
esphome[bot]andGitHub 1a01c34ec4 Bump aioesphomeapi from 45.10.0 to 45.10.1 (#18318) 2026-08-12 16:33:45 +00:00
J. Nick KostonandGitHub 3c46cc9c35 [usb_uart] Fix uint32_t format specifier warning in pl2303 (#18310) 2026-08-12 10:59:24 -04:00
Jesse Hills e192ec8fee Merge branch 'beta' into dev 2026-08-13 00:29:08 +12:00
Jesse HillsandGitHub ec6b4263a2 Merge pull request #18314 from esphome/bump-2026.8.0b1
2026.8.0b1
2026-08-13 00:28:40 +12:00
Jesse Hills 8a1aa5753d Bump version to 2026.9.0-dev 2026-08-12 22:53:42 +12:00
Jesse Hills 3e4661fe1e Bump version to 2026.8.0b1 2026-08-12 22:53:42 +12:00
J. Nick KostonandGitHub 22153be4cd [core] Add esphome logs over web_server HTTP SSE (#17110) 2026-08-12 22:00:07 +12:00
3f490fe1ed [internal_temperature] Read the RP2 on-die sensor directly instead of via the Arduino API (#18262)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-08-12 21:42:05 +12:00
J. Nick KostonandGitHub 58a42fe5c2 [core] Batch remote file downloads during config validation (#18069) 2026-08-12 18:44:35 +12:00
Josef ZweckGitHubJ. Nick Kostonpre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
25c0c2c97b [hoermann_hcp] Add garage light control (#18190)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-08-12 06:10:23 +00:00
J. Nick KostonandGitHub 622942482c [rp2] Size the lwIP segment pool and heap for concurrent senders (#18257) 2026-08-12 17:30:17 +12:00
Keith BurzinskiandGitHub cffd775450 [ethernet][network][wifi] Arbitrate the default route from the network priority list (#17797) 2026-08-12 16:21:37 +12:00
J. Nick KostonandGitHub 8d87ba34d9 [api] Move the generic buffer-full log to very verbose (#18300) 2026-08-12 04:04:07 +00:00
J. Nick KostonandGitHub 08c6585915 [esp32_ble_tracker] Don't log an error when a scan stop is already in flight (#18306) 2026-08-12 16:03:28 +12:00
J. Nick KostonandGitHub 3ef74d17af [bluetooth_proxy] Give partial advertisement batches 200ms to fill on Wi-Fi (#18303) 2026-08-12 16:02:51 +12:00
J. Nick KostonandGitHub 5d04c1dc18 [esp32_ble_tracker] Demote scan-state echoes to verbose (#18302) 2026-08-12 16:01:45 +12:00
J. Nick KostonandGitHub a3d599ac69 [bluetooth_connection] Demote rp2 backend connection logs to verbose (#18301) 2026-08-12 16:00:37 +12:00
J. Nick KostonandGitHub a4e05cd1c8 [bluetooth_proxy] Move per-advertisement logging to very verbose (#18299) 2026-08-12 15:59:41 +12:00
J. Nick KostonandGitHub 1758653330 [voice_assistant] Do not consume the audio chunk when the send is refused (#18295) 2026-08-12 15:59:26 +12:00
J. Nick KostonandGitHub a2feff8f68 [api] Mark send_message nodiscard so refused frames are never silent (#18293) 2026-08-11 22:23:08 -05:00
J. Nick KostonandGitHub 37eae9b466 [api] Gate the bluetooth connection messages on their own define (#18281) 2026-08-11 21:31:13 -05:00
Jesse HillsandGitHub 9605b34c69 [adc] Deprecate pin: TEMPERATURE in favour of internal_temperature (#18304) 2026-08-12 14:20:41 +12:00
Jesse HillsandGitHub bab62b345b [adc] Fix internal temperature channel on RP2350A under arduino-pico (#18307) 2026-08-12 14:19:59 +12:00
Jesse HillsandGitHub 3349046c5d [rp2] Record the RP2350 die on generated board entries (#18305) 2026-08-12 01:56:05 +00:00
J. Nick KostonandGitHub 3f5b8139f3 [bluetooth_proxy] Latch the connection replies and tighten the send paths (#18278) 2026-08-12 13:52:25 +12:00
be5e28ea9e [core] Warn when running a different source tree than the one you are in (#18288)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-11 21:06:57 -04:00
J. Nick KostonandGitHub 9f28638ee7 [bluetooth_proxy] Reset every owed reply in one place (#18276) 2026-08-11 19:15:21 -05:00
J. Nick KostonandGitHub 201f843e95 [bluetooth_proxy] Latch the unpair reply (#18274) 2026-08-11 18:57:51 -05:00
J. Nick KostonandGitHub 37a59a07bc [bluetooth_proxy] Retry dropped GATT acks and stop the retry log spam (#18259) 2026-08-11 18:40:47 -05:00
9556c2bc4c [mitsubishi_cn105] Add vertical vane control action (#16737)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-11 18:39:05 -05:00
3540012529 [tests] Build tests from the tree they run in, not the venv's editable install (#18248)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-08-11 19:09:51 -04:00
e0b112c584 [sendspin] Clear metadata and controller state on disconnect (#18289)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-11 21:03:27 +00:00
Kevin AhrendtandGitHub 7f0d6a8696 [sendspin] Add image platform for artwork (#17937) 2026-08-11 15:49:08 -05:00
Kevin AhrendtandGitHub 4a7de87bff [audio] Bump microDecoder to v0.4.0 (#18291) 2026-08-11 15:33:37 -05:00
esphome[bot]andGitHub b9d1d2f06b Bump aioesphomeapi from 45.9.0 to 45.10.0 (#18294) 2026-08-11 20:02:53 +00:00
dependabot[bot]andGitHub 74c30c62ef Bump setuptools from 83.0.0 to 84.0.0 (#18290)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-11 14:36:59 -05:00
J. Nick KostonandGitHub 94fbfa05de [core] Show the out-of-flash tip instead of crashing the build (#18280) 2026-08-11 14:35:06 -05:00
55bd63732d [mitsubishi_cn105] Add vertical vane state trigger (#16727)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-11 13:56:12 -05:00
J. Nick KostonandGitHub 3ae651af7b [core] Stop dropping complete lines behind an unfinished one (#18279) 2026-08-11 12:38:55 -05:00
esphome[bot]andGitHub 55e8bc3b14 Bump aioesphomeapi from 45.8.0 to 45.9.0 (#18283) 2026-08-11 17:23:36 +00:00
J. Nick KostonandGitHub 9938a2487d [core] Show the last line when output stops without a newline (#18265) 2026-08-11 10:55:42 -05:00
J. Nick KostonandGitHub aee41d64c2 [rp2040_ble][bluetooth_connection] 3 connection slots on rp2 with esp32 parity (#18247) 2026-08-11 08:43:08 -05:00
J. Nick KostonandGitHub f3a5a9fbd5 [core] Retry transient git network failures with backoff (#18242) 2026-08-11 08:41:55 -05:00
8ee3c8d41d [modbus] Properly support client-mode broadcast sends (#17467)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-11 08:31:15 -05:00
J. Nick KostonandGitHub eefd2a00c7 [espidf] Flush the runner's output so dashboard builds stream (#18264) 2026-08-11 08:11:42 -05:00
J. Nick KostonandGitHub e8852c5950 [core] Flush stdout in safe_print so logs stream live (#18261) 2026-08-11 08:11:23 -05:00
069f40f653 [hoermann_hcp] Add connectivity binary sensor (#18189)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-11 08:09:33 -05:00
Jesse HillsandGitHub 04dd6b3a55 [core] Use MAC address size constants instead of literals (#18254) 2026-08-11 08:27:32 -04:00
Jesse HillsandGitHub 2ff55e3058 [rp2] Use SDK clock query directly in arch_get_cpu_freq_hz (#18269) 2026-08-11 08:23:08 -04:00
Jesse HillsandGitHub e5cdda9ee5 [adc] Fix RP2350B internal temperature reading wrong ADC channel (#18270) 2026-08-11 08:22:52 -04:00
Jesse HillsandGitHub ecec3a19c7 [debug] Remove Arduino core dependency from RP2 platform code (#18267) 2026-08-11 08:22:20 -04:00
J. Nick KostonandGitHub 9823ad6abc [tests] Fix flaky modbus server/controller integration tests (#18258) 2026-08-10 23:56:32 -05:00
J. Nick KostonandGitHub 51b0240fb8 [rp2] Fix %f formatting in logs (#18256) 2026-08-10 22:05:19 -05:00
Jesse HillsandGitHub f697cf2011 [api] Add DeviceCapabilities message for optional-feature flags (#17984) 2026-08-11 14:52:19 +12:00
Jesse HillsandGitHub 8728aaa616 [core] Use MAC address size constants in BLE components (#18252) 2026-08-11 14:21:03 +12:00
J. Nick KostonandGitHub eab9a47aa2 [bluetooth_proxy] Retry dropped services-done, disconnect and scanner-state notifications (#18225) 2026-08-11 14:20:39 +12:00
Jesse HillsandGitHub c2ba2fbfc4 [ld24xx] Use MAC address size constants instead of literals (#18253) 2026-08-11 14:18:09 +12:00
J. Nick KostonandGitHub 3de01d2a2e Bump aioesphomeapi to 45.8.0 (#18251) 2026-08-10 19:56:35 -05:00
dependabot[bot]andGitHub 791d6659c0 Bump platformdirs from 4.11.0 to 4.11.1 (#18250)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-11 00:43:35 +00:00
007c677da1 [mitsubishi_cn105] Refactor property encoding/decoding (#16709)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-10 18:58:34 -05:00
2a0f2d59f0 [modbus_server] Add coil/discrete-input support (#17464)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-10 18:13:12 -05:00
c8d2c3691a [mitsubishi_cn105] Add vertical vane direction select (#16723)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-08-10 17:58:37 -05:00
82a63658f9 [hoermann_hcp] Add Hörmann HCP garage door component (#17355)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-10 17:20:20 -05:00
Bonne EgglestonandGitHub 596827c51c [modbus_client] Add read/write multiple registers (FC 0x17) (#18215) 2026-08-10 15:18:48 -05:00
J. Nick KostonandGitHub 8a16ead8ce [web_server] Fix basic auth with long credentials (#18237) 2026-08-10 14:49:14 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
334afdefb5 Update argcomplete requirement from >=3.7.0 to >=3.7.2 (#18243)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 15:44:32 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
e7a2980b9c Bump ruff from 0.16.1 to 0.16.2 (#18245)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 15:44:30 -04:00
dependabot[bot]andGitHub b5a78c6c46 Bump CodSpeedHQ/action from 5.0.2 to 5.0.3 (#18246)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-10 19:36:22 +00:00
dependabot[bot]andGitHub 8650d175f4 Bump aiohappyeyeballs from 2.6.2 to 2.7.1 (#18244)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-10 14:26:41 -05:00
2999e7b925 [modbus] Add API for reading/writing coils and discrete inputs in server mode (#17264)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-10 14:23:49 -05:00
J. Nick KostonGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>bdraco
207ae2e4cb [bk72xx_ble] Support active scanning by packing the GAPM start command (#18169)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: bdraco <663432+bdraco@users.noreply.github.com>
2026-08-10 14:02:37 -05:00
Boris KrivonogandGitHub 9e78a768a2 [mitsubishi_cn105] Extract top-level hub (#16987) 2026-08-10 14:02:16 -05:00
J. Nick KostonandGitHub e4d08a73b0 [ufm01] Prefix PassiveReadResult enumerators to avoid Realtek SDK macro collision (#18240) 2026-08-10 13:03:01 -05:00
J. Nick KostonandGitHub 4cf7ad9c63 [ble_device_base] Treat a partially bound merger as unbound (#18235) 2026-08-10 13:02:27 -05:00
J. Nick KostonandGitHub 293d0b90d9 [core] Document enum class value naming to avoid platform SDK macro collisions (#18241) 2026-08-10 13:02:02 -05:00
J. Nick KostonandGitHub 8f1e439792 [bluetooth_proxy] Finish the connection scan before reserving a slot (#18239) 2026-08-10 12:56:16 -05:00
J. Nick KostonandGitHub ad733272e5 [bluetooth_proxy] Pair the advertisement flush time with hub_ to close alignment holes (#18234) 2026-08-10 12:40:22 -05:00
J. Nick KostonandGitHub b7c6245388 [wifi] Save fast connect settings and reset roaming bookkeeping after driver initiated roams (#18167) 2026-08-10 12:39:32 -05:00
2184ec2928 [ethernet] Add CH390 SPI ethernet support (#18226)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-10 12:24:16 -05:00
J. Nick KostonandGitHub bae7f19323 [core] Extend '/' in names deprecation window to 2027.7.0 (#18236) 2026-08-10 11:32:02 -05:00
56ec21d950 [ufm01] Improve startup with reset retry and passive polling fallback (#17567)
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-08-10 12:31:31 -04:00
J. Nick KostonandGitHub 2798ef4de2 [ota] Enforce the backend contract with a concept (#18192) 2026-08-10 11:20:38 -05:00
J. Nick KostonandGitHub e90b4abe9c [core] Enforce the preferences contracts with concepts (#18191) 2026-08-10 11:20:24 -05:00
J. Nick KostonandGitHub f3d1fc0d64 [bluetooth_proxy] Migrate esp32 onto the neutral GATT backend (#18198) 2026-08-10 10:47:56 -05:00
J. Nick KostonandGitHub c8c929d487 [ble_device_base] Merge adv and scan response before delivery on rp2 (#18217) 2026-08-10 10:06:06 -05:00
J. Nick KostonandGitHub d9567b2974 Normalize marker-wrapped callable keys in the schema dump (#18218) 2026-08-10 10:05:37 -05:00
J. Nick KostonandGitHub 02fa18b74f [core] Skip colorama init for terminal and dashboard runs (#18224) 2026-08-10 09:44:01 -05:00
Kevin AhrendtandGitHub 3656375516 [sendspin] Bump sendspin-cpp to v0.7.1 (#18232) 2026-08-10 09:00:29 -05:00
b0a9bfd381 [ld6002b] Add area and zone configuration (5/5) (#17823)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-08-10 09:59:32 -04:00
J. Nick KostonandGitHub 25cb440005 [core] Use Happy Eyeballs for remote file downloads (#18050) 2026-08-10 17:29:00 +12:00
J. Nick KostonandGitHub 0f59ef36a9 [core] Store the validated-config cache as JSON to drop YAML off the upload fast path (#18106) 2026-08-10 17:26:41 +12:00
esphome[bot]andGitHub 3de8c7f95c Bump bundled esphome-device-builder to 1.9.5 (#18223) 2026-08-10 01:49:54 +00:00
Jesse HillsandGitHub 989dbd7550 [ci] Name release runs after the version or dev tag they build (#18110) 2026-08-10 10:10:38 +12:00
ab12e5490f [modbus] Rename send_pdu() to queue_pdu() (#18196)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-09 17:00:44 -05:00
J. Nick KostonandGitHub e9f428983e [ble_device_base] Bind BLEHub to the build's tracker at compile time (#18181) 2026-08-09 08:57:31 -05:00
J. Nick KostonandGitHub 862b13c8dd [esp32_ble_tracker] Retire the scanner-state listener interface (#18179) 2026-08-08 20:58:48 -05:00
Edu_CoderandGitHub 2bb01853e0 [tuya] GMT time 0x0C command handler (#17158) 2026-08-08 17:53:10 -07:00
J. Nick KostonandGitHub c7d6b4aaa4 [esp32_ble_tracker] Retire the raw listener path and parser-type enum (#18177) 2026-08-08 19:13:07 -05:00
J. Nick KostonandGitHub 8c74e3d5ef [bluetooth_proxy] Deliver scanner state through the hub callback (#18175) 2026-08-08 15:59:49 -05:00
J. Nick KostonandGitHub 04384e0f5b [ble_device_base] Bind the GATT backend at compile time (#18185) 2026-08-08 15:59:32 -05:00
J. Nick KostonandGitHub 7218aa4803 [bluetooth_connection] Explicit pairing for rp2 (#18166) 2026-08-08 09:51:12 -05:00
J. Nick KostonandGitHub 9cb46aa584 [bluetooth_proxy] Deliver esp32 advertisements through the hub callback (#18173) 2026-08-08 09:50:32 -05:00
Josef ZweckandGitHub 747c5c3e40 [modbus] Make sure we log on no accepting device (#18187) 2026-08-08 09:34:59 -05:00
Edvard FilistovičandGitHub 68acc055bf [ble_device_base] Migrate BLE sensor platforms to the neutral layer (batch 13: xiaomi_rtcgq02lm, xiaomi_wx08zm, xiaomi_xmwsdj04mmc) (#18183) 2026-08-08 02:06:40 -05:00
2730c10c2c [modbus] Route broadcast writes (address 0) to all server devices (#17387)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-08 01:35:49 -05:00
Edvard FilistovičandGitHub 252bb3333e [ble_device_base] Migrate BLE sensor platforms to the neutral layer (batch 12: xiaomi_miscale, xiaomi_mjyd02yla, xiaomi_mue4094rt) (#18180) 2026-08-08 01:34:08 -05:00
Edvard FilistovičandGitHub 98f4854ecd [ble_device_base] Migrate BLE sensor platforms to the neutral layer (batch 11: xiaomi_lywsdcgq, xiaomi_mhoc303, xiaomi_mhoc401) (#18178) 2026-08-08 01:01:24 -05:00
Edvard FilistovičandGitHub 413a4c5885 [ble_device_base] Migrate BLE sensor platforms to the neutral layer (batch 10: xiaomi_lywsd02, xiaomi_lywsd02mmc, xiaomi_lywsd03mmc) (#18174) 2026-08-08 00:27:44 -05:00
Edvard FilistovičandGitHub 087b80eeb5 [ble_device_base] Migrate BLE sensor platforms to the neutral layer (batch 9: xiaomi_hhccjcy10, xiaomi_hhccpot002, xiaomi_jqjcy01ym) (#18172) 2026-08-07 23:42:37 -05:00
745dee0734 [usb_cdc_acm] Don't discard queued TX data when USB flush times out (#17637)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-07 23:24:56 -05:00
Edvard FilistovičandGitHub b986530efc [ble_device_base] Migrate BLE sensor platforms to the neutral layer (batch 8: xiaomi_cgpr1, xiaomi_gcls002, xiaomi_hhccjcy01) (#18171) 2026-08-07 22:51:33 -05:00
Edvard FilistovičandGitHub 3aaea907ba [ble_device_base] Migrate BLE sensor platforms to the neutral layer (batch 7: xiaomi_cgd1, xiaomi_cgdk2, xiaomi_cgg1) (#18170) 2026-08-07 21:59:21 -05:00
Edvard FilistovičandGitHub 1d184b43eb [ble_device_base] Migrate BLE sensor platforms to the neutral layer (batch 6: thermopro_ble, exposure_notifications, xiaomi_ble) (#18168) 2026-08-07 20:47:52 -05:00
J. Nick KostonandGitHub 8d494a84c5 [bluetooth_connection] Engine follow-ups from the rp2 GATT series (#18159) 2026-08-07 16:25:55 -05:00
b0aec6dc2f [modbus_client] Lambda sugar (#18146)
Co-authored-by: Claude <noreply@anthropic.com>
2026-08-07 16:09:15 -05:00
4a7d270494 [ble_device_base] Migrate BLE sensor platforms to the neutral layer (batch 5: airthings_ble, inkbird_ibsth1_mini, radon_eye_ble) (#18165)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-08-07 20:46:43 +00:00
dependabot[bot]andGitHub ae26483be6 Bump github/codeql-action/init from 4.37.5 to 4.37.6 (#18164)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-07 19:27:57 +00:00
dependabot[bot]andGitHub 5d7bd179b1 Bump github/codeql-action/analyze from 4.37.5 to 4.37.6 (#18163)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-07 14:27:09 -05:00
Edvard FilistovičandGitHub 23a3454551 [ble_device_base] Migrate BLE sensor platforms to the neutral layer (batch 4: ruuvi_ble, ruuvitag, b_parasite) (#18161) 2026-08-07 14:21:46 -05:00
f5ee72753d [modbus_client] Add typed read/write actions (#18078)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Claude <noreply@anthropic.com>
2026-08-07 14:18:20 -05:00
esphome[bot]andGitHub ee13996ee6 Bump bundled esphome-device-builder to 1.9.4 (#18162) 2026-08-07 19:17:26 +00:00
c24e61439b [modbus] Add server support for read/write multiple registers (0x17) (#17357)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-07 14:17:11 -05:00
J. Nick KostonandGitHub 2e1c517821 [bluetooth_proxy] Enable active connections on rp2 (#18132) 2026-08-07 13:51:41 -05:00
950cfc4da3 [ld6002b] Add select and button platforms (4/5) (#17822)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-08-07 14:33:37 -04:00
715b14aeba [ble_device_base] Migrate BLE sensor platforms to the neutral layer (batch 3: mopeka_ble, mopeka_pro_check, mopeka_std_check) (#17951)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-08-07 12:59:27 -05:00
J. Nick KostonandGitHub d7b5ad77da [bluetooth_connection] Add BTstack GATT client backend for rp2 (#18131) 2026-08-07 12:53:09 -05:00
Edvard FilistovičandGitHub 77bfda6f1f [ble_device_base] Migrate BLE sensor platforms to the neutral layer (batch 2: atc_mithermometer, pvvx_mithermometer, bthome_mithermometer) (#17950) 2026-08-07 12:48:18 -05:00
Oliver KleineckeGitHubOliver KleineckeClaude Sonnet 4.6Copilot Autofix powered by AIstorage splitpre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
b326cefea7 [esp32] refactor esp32-vfs default configs to FINAL co-routine & add some defaults (#17337)
Co-authored-by: Oliver Kleinecke <kleinecke.oliver@googlemail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: storage split <ai@local>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-08-07 12:36:59 -05:00
J. Nick KostonandGitHub c0e70d9beb [rp2040_ble] Add scan arbitration and GATT client hooks (#18155) 2026-08-07 11:50:18 -05:00
J. Nick KostonandGitHub c5e165d062 [bluetooth_proxy] Make the GATT dispatch platform neutral (#18130) 2026-08-07 11:29:19 -05:00
Edvard FilistovičandGitHub 5f483b11b6 [ble_device_base] Migrate BLE sensor platforms to the neutral layer (batch 1: ble_presence, ble_rssi, ble_scanner) (#17716) 2026-08-07 16:19:08 +00:00
b802740997 [ld6002b] Add switch, number and text sensor platforms (3/5) (#17821)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-08-07 11:11:29 -04:00
Edvard FilistovičandGitHub 1bbe8b415f [bluetooth_proxy] Only advance the scanner-state detector when the frame was sent (#18154) 2026-08-07 15:01:50 +00:00
8b0e23d55b [bluetooth_proxy] Fold scanner-state bookkeeping into the sender; extend platform-gate tests (#18150)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-08-07 14:30:00 +00:00
J. Nick KostonandGitHub 2f2634bf6b [bluetooth_connection] Move BluetoothConnection out of bluetooth_proxy (#18129) 2026-08-07 08:54:28 -05:00
Jonathan SwobodaandGitHub 9c93d8925f [ota] Use PSA crypto for signature verification on ESP-IDF 6 (#18145) 2026-08-07 07:41:48 -04:00
1430 changed files with 49378 additions and 10012 deletions
+3 -3
View File
@@ -32,7 +32,7 @@ runs:
# detects the activated venv via ``VIRTUAL_ENV`` so the venv layout
# downstream jobs rely on is preserved.
if: steps.cache-venv.outputs.cache-hit != 'true'
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: true
# Pull request saves land in per-PR scopes nothing else can
@@ -49,7 +49,7 @@ runs:
python -m venv venv
source venv/bin/activate
python --version
uv pip install -r requirements.txt -r requirements_test.txt
uv pip install -r requirements.txt -r requirements_dev.txt -r requirements_test.txt
uv pip install -e .
- name: Create Python virtual environment
if: steps.cache-venv.outputs.cache-hit != 'true' && runner.os == 'Windows'
@@ -58,5 +58,5 @@ runs:
python -m venv venv
source ./venv/Scripts/activate
python --version
uv pip install -r requirements.txt -r requirements_test.txt
uv pip install -r requirements.txt -r requirements_dev.txt -r requirements_test.txt
uv pip install -e .
+2 -1
View File
@@ -70,6 +70,7 @@ async function isStackedPr(github, context) {
async function detectMergeBranch(github, context) {
const labels = new Set();
const baseRef = context.payload.pull_request.base.ref;
const defaultBranch = context.payload.repository.default_branch;
if (baseRef === 'release') {
labels.add('merging-to-release');
@@ -78,7 +79,7 @@ async function detectMergeBranch(github, context) {
} 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') {
} else if (baseRef !== defaultBranch) {
// A chain built by hand: it must not merge until its base branch does.
labels.add('chained-pr');
}
@@ -43,14 +43,14 @@ const WITHOUT_SCHEMA = 'CODEOWNERS = ["@esphome/core"]';
// 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 } = {}) {
function makeMergeContext(baseRef, { stack, defaultBranch = 'dev' } = {}) {
const pull_request = { number: 1, base: { ref: baseRef } };
if (stack !== undefined) {
pull_request.stack = stack;
}
return {
repo: { owner: 'esphome', repo: 'esphome' },
payload: { pull_request }
payload: { pull_request, repository: { default_branch: defaultBranch } }
};
}
@@ -136,6 +136,21 @@ describe('detectMergeBranch', () => {
assert.deepEqual(Array.from(labels).sort(), ['chained-pr']);
assert.equal(state.calls, 1);
});
it('base ref matches default branch adds no labels', async () => {
const { github } = makeStackGithub({ stack: null });
const context = makeMergeContext('other', { defaultBranch: 'other' });
const labels = await detectMergeBranch(github, context);
assert.deepEqual(Array.from(labels).sort(), []);
});
it('base ref dev when the default branch is main adds chained-pr', async () => {
const { github } = makeStackGithub({ stack: null });
const context = makeMergeContext('dev', { defaultBranch: 'main' });
const labels = await detectMergeBranch(github, context);
assert.deepEqual(Array.from(labels).sort(), ['chained-pr']);
});
});
// ---------------------------------------------------------------------------
+26 -4
View File
@@ -29,7 +29,7 @@ jobs:
- 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@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: true
# Pull-request-only workflow: a save could never be shared and
@@ -41,10 +41,32 @@ jobs:
version: "0.11.15"
- name: Install apt dependencies
# PR-only workflow, so nothing on dev could seed a shared apt cache
# entry; the cached apt action would save one copy per PR. Plain apt
# with every call bounded: the apt.conf.d timeouts make a dead
# mirror fail over in seconds, and timeout runs under sudo so it can
# kill apt-get itself. Install without update first: image lists are
# fresh, and the index refresh is what a congested mirror makes slow.
timeout-minutes: 15
run: |
sudo apt update
sudo apt-cache show protobuf-compiler
sudo apt install -y protobuf-compiler
sudo tee /etc/apt/apt.conf.d/99ci-acquire-timeouts >/dev/null <<'EOF'
Acquire::Retries "1";
Acquire::http::Timeout "15";
Acquire::https::Timeout "15";
EOF
# Common path: the image's package lists are fresh enough.
if sudo DEBIAN_FRONTEND=noninteractive timeout -k 15 90 \
apt-get install -y protobuf-compiler; then
protoc --version
exit 0
fi
# Rescue path: refresh the lists once with a generous bound; the
# apt config already fails a stalled mirror over quickly.
sudo DEBIAN_FRONTEND=noninteractive timeout -k 10 30 \
dpkg --configure -a || true
sudo timeout -k 15 300 apt-get update
sudo DEBIAN_FRONTEND=noninteractive timeout -k 15 300 \
apt-get install -y protobuf-compiler
protoc --version
- name: Install python dependencies
run: uv pip install --system aioesphomeapi -c requirements.txt -r requirements_dev.txt
+2 -2
View File
@@ -67,7 +67,7 @@ jobs:
with:
python-version: "3.12"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
- name: Determine tag and whether to push
id: tag
@@ -153,7 +153,7 @@ jobs:
with:
python-version: "3.12"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
- name: Log in to the GitHub container registry
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
+107 -43
View File
@@ -49,7 +49,7 @@ jobs:
# detects the activated venv via ``VIRTUAL_ENV`` so downstream jobs
# that ``. venv/bin/activate`` see an identical layout.
if: steps.cache-venv.outputs.cache-hit != 'true'
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: true
# Pull request saves land in per-PR scopes nothing else can
@@ -68,6 +68,22 @@ jobs:
uv pip install -r requirements.txt -r requirements_dev.txt -r requirements_test.txt
uv pip install -e .
seed-apt-cache:
name: Seed apt package cache
runs-on: ubuntu-24.04
# PR-branch cache saves are invisible to other PRs, so dev/beta/release
# pushes seed the one shared entry PR jobs restore. The key is derived
# only from the package list and version; keep both identical in every
# step that restores it. In ci-status needs so a broken seed fails dev.
if: github.event_name == 'push'
timeout-minutes: 10
steps:
- name: Install apt packages (cached)
uses: awalsh128/cache-apt-pkgs-action@553a35bb8ebd9fcabcb1c9451aa4c98e1b4ca8a9 # v1.6.3
with:
packages: libsdl2-dev ccache
version: 1.1
determine-jobs:
name: Determine which jobs to run
runs-on: ubuntu-24.04
@@ -179,6 +195,7 @@ jobs:
. venv/bin/activate
script/ci-custom.py
script/build_codeowners.py --check
script/build_alias_registry.py --check
script/build_language_schema.py --check
script/generate-esp32-boards.py --check
script/generate-rp2-boards.py --check
@@ -322,7 +339,8 @@ jobs:
integration-tests:
name: Run integration tests (${{ matrix.bucket.name }})
runs-on: ubuntu-latest
# Must match seed-apt-cache's image: the apt cache key has no OS in it.
runs-on: ubuntu-24.04
needs:
- common
- determine-jobs
@@ -334,24 +352,16 @@ jobs:
steps:
- name: Check out code from GitHub
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install ccache
# Speeds up the host compiles: tests in a bucket compile overlapping
# component sets, so later tests reuse earlier tests' objects.
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends ccache
- name: Restore ccache (restore-only)
# esphome stores the PlatformIO ccache under the machine-global cache
# dir (see _ccache_env() in esphome/platformio/toolchain.py). The
# bucket-name prefix prefers a same-bucket seed; the bare prefix falls
# back to any seed when the bucket layout differs from dev.
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
- name: Install apt packages (cached)
# ccache speeds up the host compiles. A cache hit never touches apt
# (mirror outages cannot hang the job); the timeout bounds the cold
# path. Packages and version must match seed-apt-cache exactly;
# libsdl2-dev is unused here and carried only for cache-key parity.
timeout-minutes: 10
uses: awalsh128/cache-apt-pkgs-action@553a35bb8ebd9fcabcb1c9451aa4c98e1b4ca8a9 # v1.6.3
with:
path: ~/.cache/esphome/platformio-ccache
key: integration-ccache-${{ matrix.bucket.name }}-${{ github.sha }}
restore-keys: |
integration-ccache-${{ matrix.bucket.name }}-
integration-ccache-
packages: libsdl2-dev ccache
version: 1.1
- name: Set up Python 3.13
id: python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
@@ -366,7 +376,7 @@ jobs:
- name: Set up uv
# Only needed on cache miss to populate the venv.
if: steps.cache-venv.outputs.cache-hit != 'true'
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: true
# Pull request saves land in per-PR scopes nothing else can
@@ -400,14 +410,6 @@ jobs:
# esphome stores the PlatformIO ccache under the machine-global cache
# dir (see _ccache_env() in esphome/platformio/toolchain.py).
run: CCACHE_DIR="$HOME/.cache/esphome/platformio-ccache" ccache -s
- name: Save ccache
# Pull request saves land in per-PR scopes nothing else can reuse;
# dev pushes seed the shared copy instead.
if: github.event_name != 'pull_request'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/esphome/platformio-ccache
key: integration-ccache-${{ matrix.bucket.name }}-${{ github.sha }}
import-time:
name: Check import esphome.__main__ time
@@ -440,12 +442,17 @@ jobs:
benchmarks:
name: Run CodSpeed benchmarks
runs-on: ubuntu-24.04
timeout-minutes: 30
needs:
- common
- determine-jobs
if: >-
(github.event_name == 'push' && github.ref_name == 'dev') ||
(github.event_name == 'pull_request' && needs.determine-jobs.outputs.benchmarks == 'true')
github.repository == 'esphome/esphome' && (
(github.event_name == 'push' && github.ref_name == 'dev') ||
(github.event_name == 'pull_request' && needs.determine-jobs.outputs.benchmarks == 'true')
)
# CodSpeed benchmarks require a CodSpeed account linked to the repository to run
# (https://codspeed.io) -- disabled on forks that aren't esphome/esphome itself.
steps:
- name: Check out code from GitHub
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -459,14 +466,60 @@ jobs:
- name: Build benchmarks
id: build
run: |
# pipefail: without it a failed build is masked by the grep/cut
# pipeline below, leaving BINARY empty and silently dropping every
# C++ benchmark from the run while the job still reports success.
set -o pipefail
. venv/bin/activate
export BENCHMARK_LIB_CONFIG=$(python script/setup_codspeed_lib.py)
# --build-only prints BUILD_BINARY=<path> to stdout
BINARY=$(script/cpp_benchmark.py --all --build-only | grep '^BUILD_BINARY=' | tail -1 | cut -d= -f2-)
BENCHMARK_LIB_CONFIG=$(python script/setup_codspeed_lib.py)
export BENCHMARK_LIB_CONFIG
# --build-only prints BUILD_BINARY=<path> to stdout; the grep is
# non-fatal so a missing marker reaches the check below instead of
# tripping errexit at this assignment
BINARY=$(script/cpp_benchmark.py --all --build-only | { grep '^BUILD_BINARY=' || true; } | tail -1 | cut -d= -f2-)
if [ -z "$BINARY" ]; then
echo "::error::Benchmark build did not report a binary path"
exit 1
fi
echo "binary=$BINARY" >> $GITHUB_OUTPUT
- name: Bound apt fetches and pre-install libc6-dbg
# The CodSpeed runner installs valgrind + libc6-dbg via its own
# unbounded apt-get update; per-invocation apt options cannot reach
# it. The apt.conf.d timeouts below bound every later apt call in
# this job, the runner's included. Pre-installing libc6-dbg lets the
# runner skip apt once its valgrind cache is restored (it checks
# ``dpkg -s libc6-dbg``, so the cache action's unregistered restores
# would not count). Install without update first: image lists are
# fresh, and the index refresh is what a congested mirror makes
# slow. Best effort; the job timeout is the last backstop.
timeout-minutes: 15
continue-on-error: true
run: |
sudo tee /etc/apt/apt.conf.d/99ci-acquire-timeouts >/dev/null <<'EOF'
Acquire::Retries "1";
Acquire::http::Timeout "15";
Acquire::https::Timeout "15";
EOF
if dpkg -s libc6-dbg >/dev/null 2>&1; then
echo "libc6-dbg already installed"
exit 0
fi
# Common path: the image's package lists are fresh enough.
if sudo DEBIAN_FRONTEND=noninteractive timeout -k 15 90 \
apt-get install -y libc6-dbg; then
exit 0
fi
# Rescue path: refresh the lists once with a generous bound; the
# apt config already fails a stalled mirror over quickly.
sudo DEBIAN_FRONTEND=noninteractive timeout -k 10 30 \
dpkg --configure -a || true
sudo timeout -k 15 300 apt-get update
sudo DEBIAN_FRONTEND=noninteractive timeout -k 15 300 \
apt-get install -y libc6-dbg
- name: Run CodSpeed benchmarks
uses: CodSpeedHQ/action@0ca9cbbf4623b599a6c3ed4fc8a922942705d9f1 # v5.0.2
uses: CodSpeedHQ/action@4296e51e7041e24dadb86d1d6e8b9320d223dbe8 # v5.0.3
with:
run: |
. venv/bin/activate
@@ -549,24 +602,29 @@ jobs:
fetch-depth: 2
- name: Restore Python
id: restore-python
uses: ./.github/actions/restore-python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
# Key on the exact Python version as well: LibreTiny creates a venv under
# ~/.platformio/penv whose interpreter is a symlink into the runner's
# hosted toolcache, so a cache saved on an older runner image breaks once
# a new image ships a newer patch release and drops the old interpreter.
- name: Cache platformio
if: github.ref == 'refs/heads/dev' && matrix.pio_cache_key
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
key: platformio-${{ matrix.pio_cache_key }}-${{ steps.restore-python.outputs.python-version }}-${{ hashFiles('platformio.ini') }}
- name: Cache platformio
if: github.ref != 'refs/heads/dev' && matrix.pio_cache_key
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
key: platformio-${{ matrix.pio_cache_key }}-${{ steps.restore-python.outputs.python-version }}-${{ hashFiles('platformio.ini') }}
- name: Cache ESP-IDF install
if: matrix.cache_idf
@@ -883,12 +941,17 @@ jobs:
- name: List components
run: echo ${{ matrix.batch.components }}
- name: Install apt packages
# Not cached: this job is pull-request-only, so a cache save could
# never be shared and would only consume quota.
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends libsdl2-dev ccache
- name: Install apt packages (cached)
# A cache hit (seeded on dev by seed-apt-cache) never touches apt,
# so mirror outages cannot hang this PR-only job; the timeout bounds
# the cold path. Packages and version must match seed-apt-cache
# exactly. The action has no --no-install-recommends; same package
# set this job used before #17463.
timeout-minutes: 10
uses: awalsh128/cache-apt-pkgs-action@553a35bb8ebd9fcabcb1c9451aa4c98e1b4ca8a9 # v1.6.3
with:
packages: libsdl2-dev ccache
version: 1.1
- name: Check out code from GitHub
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -1090,7 +1153,7 @@ jobs:
# install step (order-of-magnitude faster on cold boots,
# with its own wheel cache). actions/setup-python still
# provides the interpreter.
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: true
# Pull request saves land in per-PR scopes nothing else can
@@ -1423,6 +1486,7 @@ jobs:
# this check.
needs:
- common
- seed-apt-cache
- determine-jobs
- ci-custom
- pylint
+2 -2
View File
@@ -56,7 +56,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5
uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
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@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5
uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
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@9f6577fd37b5cf773ab1b9be929714a0dcd15661 # 2026.7.0
uses: esphome/workflows/.github/workflows/lock.yml@0fdd5e311b7e744069166696072a1a9cbc5fbeb6 # 2026.8.1
+38
View File
@@ -0,0 +1,38 @@
---
name: Nightly Dev Release
# Works out the dated dev tag and starts the release workflow with it, so that
# the release run is named after the tag it builds. A workflow run name is
# fixed when the run starts and cannot read a file or the current date.
on:
schedule:
- cron: "0 2 * * *"
permissions:
contents: read # actions/checkout to read the version from esphome/const.py
jobs:
trigger:
name: Start release build
if: github.repository == 'esphome/esphome'
runs-on: ubuntu-latest
permissions:
contents: read # actions/checkout to read the version from esphome/const.py
actions: write # gh workflow run starts release.yml
steps:
- name: Check out the repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Start the release workflow
env:
GH_TOKEN: ${{ github.token }}
run: |
VERSION=$(sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p" esphome/const.py)
if [[ -z "$VERSION" ]]; then
echo "::error::Could not read __version__ from esphome/const.py"
exit 1
fi
TAG="${VERSION}$(date --utc '+%Y%m%d')"
echo "Starting release build for ${TAG}"
gh workflow run release.yml --ref "${GITHUB_REF_NAME}" --field tag="${TAG}"
+32 -8
View File
@@ -1,12 +1,23 @@
---
name: Publish Release
# Releases (production and beta) are named after the version they publish.
# Dev builds are named after the dated dev tag, which is passed in by the
# nightly workflow because a run name cannot compute it itself.
run-name: ${{ github.event.inputs.tag || github.event.release.tag_name || format('Manual build ({0})', github.ref_name) }}
on:
workflow_dispatch:
inputs:
tag:
description: >-
Tag to build. Only supported on dev, where the nightly workflow
uses it. Leave empty to build the version from esphome/const.py
with today's date appended.
required: false
default: ""
release:
types: [published]
schedule:
- cron: "0 2 * * *"
permissions:
contents: read # actions/checkout for all jobs; deploy jobs add their own scopes when they need to write
@@ -23,6 +34,8 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Get tag
id: tag
env:
INPUT_TAG: ${{ github.event.inputs.tag }}
# yamllint disable rule:line-length
run: |
if [[ "${{ github.event_name }}" = "release" ]]; then
@@ -34,12 +47,23 @@ jobs:
ENVIRONMENT="production"
fi
else
TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p")
today="$(date --utc '+%Y%m%d')"
TAG="${TAG}${today}"
BRANCH=${GITHUB_REF#refs/heads/}
# The nightly workflow passes the finished tag so that the run name
# matches what is built. Without it, work it out here.
TAG="${INPUT_TAG}"
if [[ -n "$TAG" && "$BRANCH" != "dev" ]]; then
echo "::error::The tag input is only supported on dev. A build from ${BRANCH} has to use the tag worked out here, which carries the branch name, so that it cannot publish over the dev, beta, latest or stable images."
exit 1
fi
if [[ -z "$TAG" ]]; then
TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p")
today="$(date --utc '+%Y%m%d')"
TAG="${TAG}${today}"
if [[ "$BRANCH" != "dev" ]]; then
TAG="${TAG}-${BRANCH}"
fi
fi
if [[ "$BRANCH" != "dev" ]]; then
TAG="${TAG}-${BRANCH}"
BRANCH_BUILD="true"
ENVIRONMENT=""
else
@@ -99,7 +123,7 @@ jobs:
python-version: "3.12"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
- name: Log in to docker hub
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
@@ -178,7 +202,7 @@ jobs:
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
- name: Log in to docker hub
if: matrix.registry == 'dockerhub'
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
# 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@61fd37a044cad4e9aa4303027b2a61b6a34da855 # main
uses: esphome/workflows/.github/workflows/stale.yml@a1c1485ab46ef41a84a6a9d8abd7fa4b7628fd70 # main
secrets:
ESPHOME_GITHUB_APP_PRIVATE_KEY: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
with:
+1 -1
View File
@@ -47,7 +47,7 @@ jobs:
# setup-python interpreter so subsequent ``prek`` /
# ``script/run-in-env.py`` steps find the deps without a
# ``uv run`` prefix.
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: true
# Pin uv version so the action does not have to fetch the
+1 -1
View File
@@ -11,7 +11,7 @@ ci:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.16.0
rev: v0.16.3
hooks:
# Run the linter.
- id: ruff
+16
View File
@@ -57,6 +57,12 @@ This document provides essential context for AI models interacting with this pro
- Function-local constants: `lower_snake_case`
- Protected/private fields: `lower_snake_case_with_trailing_underscore_`
- Favor descriptive names over abbreviations
- Enumerator names: prefix every value of an `enum class` with the enum name converted to
`UPPER_SNAKE_CASE` (e.g. `UARTFlushResult::UART_FLUSH_RESULT_SUCCESS`). Never use bare
names like `SUCCESS`, `FAILURE`, `OK`, or `FAIL`: platform SDK headers define macros with
these common names (for example the Realtek SDKs used by LibreTiny define
`#define SUCCESS 0` in `basic_types.h`), and the preprocessor replaces the enumerator
before the compiler sees it, breaking the build and clang-tidy on those platforms.
* **Python Idioms:**
* **Assignment expressions (PEP 572):** Prefer the walrus operator (`:=`) wherever it removes a redundant lookup or a throwaway temporary. The most common case in component code is presence-checking a config key and then indexing it separately — fetch once with `.get()` and bind in the condition instead:
@@ -757,3 +763,13 @@ The project uses English for non-code content. When drafting documentation, code
PR descriptions, and similar text, avoid technical jargon. Instead, express concepts in plain English,
using standard technical terms only when required. Ensure the text is readily comprehensible to a wide
audience, including non-native English speakers.
## 10. Code Comments
Code comments on individual lines should be used only where necessary to flag issues that may not be obvious
on a simple reading of the code. Keep them short (e.g. 1 or 2 lines).
Function and method comment blocks may include more detail as required to make
calling contracts clear and document parameter usage, but should still be kept concise.
Avoid redundancy and repetition; comments should never simply restate what the code already says.
+3
View File
@@ -78,6 +78,7 @@ esphome/components/bl0942/* @dbuezas @dwmw2
esphome/components/ble_client/* @buxtronix @clydebarrow
esphome/components/ble_device_base/* @Bl00d-B0b
esphome/components/ble_nus/* @tomaszduda23
esphome/components/bluetooth_connection/* @bdraco @jesserockz
esphome/components/bluetooth_proxy/* @bdraco @jesserockz
esphome/components/bm8563/* @abmantis
esphome/components/bme280_base/* @esphome/core
@@ -237,6 +238,7 @@ esphome/components/hlw8032/* @rici4kubicek
esphome/components/hm3301/* @freekode
esphome/components/hmac_md5/* @dwmw2
esphome/components/hmac_sha256/* @dwmw2
esphome/components/hoermann_hcp/* @zweckj
esphome/components/homeassistant/* @esphome/core @OttoWinter
esphome/components/homeassistant/number/* @landonr
esphome/components/homeassistant/switch/* @Links2004
@@ -464,6 +466,7 @@ esphome/components/sen21231/* @shreyaskarnik
esphome/components/sen5x/* @martgras
esphome/components/sen6x/* @martgras @mebner86 @tuct
esphome/components/sendspin/* @kahrendt
esphome/components/sendspin/image/* @kahrendt
esphome/components/sendspin/media_player/* @kahrendt
esphome/components/sendspin/media_source/* @kahrendt
esphome/components/sendspin/sensor/* @kahrendt
+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.8.0-dev
PROJECT_NUMBER = 2026.9.0-dev
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
+1 -1
View File
@@ -22,7 +22,7 @@ RUN \
-r /requirements.txt
# Install the ESPHome Device Builder dashboard.
RUN uv pip install --no-cache-dir esphome-device-builder==1.9.3
RUN uv pip install --no-cache-dir esphome-device-builder==1.12.4
RUN \
platformio settings set enable_telemetry No \
+242 -89
View File
@@ -10,7 +10,7 @@ from pathlib import Path
import re
import sys
import time
from typing import Protocol
from typing import TYPE_CHECKING, Protocol
# Note: Do not import modules from esphome.components here, as this would
# cause them to be loaded before external components are processed, resulting
@@ -21,7 +21,6 @@ from esphome.const import (
ARGUMENT_HELP_DEVICE,
BUNDLE_EXTENSION,
CONF_API,
CONF_AUTH,
CONF_BAUD_RATE,
CONF_BROKER,
CONF_DEASSERT_RTS_DTR,
@@ -29,6 +28,7 @@ from esphome.const import (
CONF_DISCOVER_IP,
CONF_ESPHOME,
CONF_LEVEL,
CONF_LOG,
CONF_LOG_TOPIC,
CONF_LOGGER,
CONF_MDNS,
@@ -42,7 +42,7 @@ from esphome.const import (
CONF_PORT,
CONF_SUBSTITUTIONS,
CONF_TOPIC,
CONF_USERNAME,
CONF_VERSION,
CONF_WEB_SERVER,
CONF_WIFI,
ENV_NOGITIGNORE,
@@ -71,6 +71,9 @@ from esphome.util import (
safe_print,
)
if TYPE_CHECKING:
import threading
# Keep expensive imports (zeroconf, writer, yaml_util, etc.) out of this
# module's top level. Every `esphome` invocation — including fast paths
# like `esphome version` — pays the cost of what's imported here before
@@ -273,8 +276,8 @@ def _unresolved_default_error(purpose: Purpose, defaults: list[str]) -> str:
if purpose == Purpose.LOGGING and not has_api():
return (
"Cannot view logs over the network: no 'api:' component is "
"configured. Network log streaming requires the native API; add "
"an 'api:' component, enable MQTT logging, or view logs over USB."
"configured. Add an 'api:' component, enable MQTT logging, add a "
"'web_server:' component, or view logs over USB."
)
if purpose == Purpose.UPLOADING and not has_ota():
return (
@@ -314,9 +317,12 @@ def choose_upload_log_host(
]
resolved.append(choose_prompt(options, purpose=purpose))
elif device == "OTA":
# Logs can stream over a network transport via the native API
# or the web_server HTTP SSE feed.
network_logging = has_api() or has_web_server_logging()
# ensure IP adresses are used first
if is_ip_address(CORE.address) and (
(purpose == Purpose.LOGGING and has_api())
(purpose == Purpose.LOGGING and network_logging)
or (purpose == Purpose.UPLOADING and has_ota())
):
resolved.extend(_resolve_with_cache(CORE.address, purpose))
@@ -328,7 +334,11 @@ def choose_upload_log_host(
if has_mqtt_logging():
resolved.append("MQTT")
if has_api() and has_non_ip_address() and has_resolvable_address():
if (
network_logging
and has_non_ip_address()
and has_resolvable_address()
):
resolved.extend(_ota_hostnames_for_default(purpose))
elif purpose == Purpose.UPLOADING:
@@ -390,7 +400,7 @@ def choose_upload_log_host(
mqtt_config = CORE.config[CONF_MQTT]
options.append((f"MQTT ({mqtt_config[CONF_BROKER]})", "MQTT"))
if has_api():
if has_api() or has_web_server_logging():
add_ota_options()
elif purpose == Purpose.UPLOADING and has_ota():
@@ -483,6 +493,21 @@ def has_web_server_ota() -> bool:
)
def has_web_server_logging() -> bool:
"""Check if logs can be streamed over the web_server HTTP SSE endpoint.
The ``web_server`` component exposes a ``/events`` Server-Sent Events
stream that carries ``event: log`` frames. This requires version 2+ (the
v1 UI has no ``/events`` endpoint) and the ``log`` option enabled (default).
"""
web_conf = CORE.config.get(CONF_WEB_SERVER)
if web_conf is None:
return False
if web_conf.get(CONF_VERSION, 2) == 1:
return False
return web_conf.get(CONF_LOG, True)
def has_mqtt_ip_lookup() -> bool:
"""Check if MQTT is available and IP lookup is supported."""
if CONF_MQTT not in CORE.config:
@@ -545,11 +570,48 @@ def has_name_add_mac_suffix() -> bool:
def mqtt_get_ip(
config: ConfigType, username: str, password: str, client_id: str
config: ConfigType,
username: str,
password: str,
client_id: str,
stop_event: "threading.Event | None" = None,
) -> list[str]:
from esphome import mqtt
return mqtt.get_esphome_device_ip(config, username, password, client_id)
return mqtt.get_esphome_device_ip(
config, username, password, client_id, stop_event=stop_event
)
def _add_network_device(device: str, network_devices: list[str]) -> None:
"""Append a device to the list, expanding it through ``CORE.address_cache``.
If the hostname is already in the address cache (e.g. populated by mDNS
discovery), substitute the cached IPs so aioesphomeapi doesn't open its
own Zeroconf to re-resolve it. Duplicates are dropped.
"""
if CORE.address_cache and (cached := CORE.address_cache.get_addresses(device)):
network_devices.extend(addr for addr in cached if addr not in network_devices)
elif device not in network_devices:
network_devices.append(device)
def _split_network_devices(devices: list[str]) -> tuple[list[str], bool]:
"""Split the device list into direct addresses and an MQTT-lookup flag.
Direct addresses are expanded through ``CORE.address_cache`` and deduped
the same way ``_resolve_network_devices`` does; MQTT/MQTTIP magic strings
are not resolved, only reported via the returned bool so the caller can
defer the broker lookup.
"""
network_devices: list[str] = []
has_mqtt_lookup = False
for device in devices:
if get_port_type(device) in _MQTT_PORT_TYPES:
has_mqtt_lookup = True
else:
_add_network_device(device, network_devices)
return network_devices, has_mqtt_lookup
def _resolve_network_devices(
@@ -582,40 +644,44 @@ def _resolve_network_devices(
if port_type in _MQTT_PORT_TYPES:
# Only resolve MQTT once, even if multiple MQTT entries
if not mqtt_resolved:
try:
mqtt_ips = mqtt_get_ip(
config, args.username, args.password, args.client_id
)
# pylint can't infer mqtt_get_ip's return through its
# lazy ``from esphome import mqtt`` import, so it flags
# the genexpr below.
network_devices.extend(
addr
for addr in mqtt_ips # pylint: disable=not-an-iterable
if addr not in network_devices
)
except EsphomeError as err:
_LOGGER.warning(
"MQTT IP discovery failed (%s), will try other devices if available",
err,
)
mqtt_ips = _mqtt_get_ip_or_warn(
config, args.username, args.password, args.client_id
)
network_devices.extend(
addr for addr in mqtt_ips if addr not in network_devices
)
mqtt_resolved = True
continue
# If the hostname is already in the address cache (e.g. populated by
# mDNS discovery), substitute the cached IPs so aioesphomeapi doesn't
# open its own Zeroconf to re-resolve it.
if CORE.address_cache and (cached := CORE.address_cache.get_addresses(device)):
network_devices.extend(
addr for addr in cached if addr not in network_devices
)
elif device not in network_devices:
# Regular network address or IP - add if not already present
network_devices.append(device)
_add_network_device(device, network_devices)
return network_devices
def _mqtt_get_ip_or_warn(
config: ConfigType,
username: str,
password: str,
client_id: str,
stop_event: "threading.Event | None" = None,
) -> list[str]:
"""Look up the device IP via MQTT, returning [] with a warning on failure.
This owns the failure policy for MQTT IP discovery on paths that have
other addresses to fall back on: a broker problem must not abort the
operation. Also used as the deferred resolver handed to ``run_logs``,
where it runs in a worker thread.
"""
try:
return mqtt_get_ip(config, username, password, client_id, stop_event=stop_event)
except EsphomeError as err:
_LOGGER.warning(
"MQTT IP discovery failed (%s), will try other devices if available",
err,
)
return []
def run_miniterm(config: ConfigType, port: str, args) -> int:
from datetime import datetime
@@ -696,9 +762,11 @@ def _wrap_to_code(name, comp, yaml_util):
async def wrapped(conf):
cg.add(cg.LineComment(f"{name}:"))
if comp.config_schema is not None:
conf_str = yaml_util.dump(conf)
# sort_keys: voluptuous fills defaults in set order, so an
# unsorted dump would churn main.cpp and relink every run
conf_str = yaml_util.dump(conf, sort_keys=True)
conf_str = conf_str.replace("//", "")
# remove tailing \ to avoid multi-line comment warning
# remove trailing \ to avoid multi-line comment warning
conf_str = conf_str.replace("\\\n", "\n")
cg.add(cg.LineComment(indent(conf_str)))
await coro(conf)
@@ -789,7 +857,20 @@ def compile_program(args: ArgsProtocol, config: ConfigType) -> int:
toolchain.create_factory_bin()
toolchain.create_ota_bin()
toolchain.create_elf_copy()
toolchain.get_idedata()
from esphome.build_helpers.idedata import IDEDATA_BEST_EFFORT_ERRORS
try:
if toolchain.get_idedata() is None:
_LOGGER.warning("No idedata was generated for this build")
except IDEDATA_BEST_EFFORT_ERRORS as err:
# The firmware already built; an idedata failure must not fail
# a successful build.
_LOGGER.warning(
"Could not generate idedata: %s (IDE, clang-tidy, and "
"memory-analysis data will be unavailable for this build)",
err,
)
_LOGGER.debug("Idedata failure detail", exc_info=True)
else:
from esphome.platformio import toolchain
@@ -1291,25 +1372,23 @@ def _upload_via_native_api(
def _upload_via_web_server(
config: ConfigType, network_devices: list[str], binary: Path
) -> tuple[int, str | None]:
web_conf = config.get(CONF_WEB_SERVER)
if not web_conf:
raise EsphomeError(
f"Cannot upload via web_server OTA: the {CONF_WEB_SERVER} component "
f"is not configured."
)
remote_port = int(web_conf[CONF_PORT])
auth = web_conf.get(CONF_AUTH) or {}
username = auth.get(CONF_USERNAME)
password = auth.get(CONF_PASSWORD)
from esphome import web_server_ota
from esphome.web_server_helpers import get_web_server_connection
remote_port, username, password = get_web_server_connection(config)
return web_server_ota.run_ota(
network_devices, remote_port, username, password, binary
)
def _show_logs_via_web_server(config: ConfigType, network_devices: list[str]) -> int:
from esphome import web_server_logs
from esphome.web_server_helpers import get_web_server_connection
port, username, password = get_web_server_connection(config)
return web_server_logs.run_logs(network_devices, port, username, password)
# Layout of esp_partition_info_t on flash. Each entry is 32 bytes, leading with a
# 16-bit little-endian magic. ESP-IDF defines ESP_PARTITION_MAGIC = 0x50AA (stored as
# bytes 0xAA, 0x50) for partition entries and ESP_PARTITION_MAGIC_MD5 = 0xEBEB for the
@@ -1418,17 +1497,37 @@ def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int
return run_miniterm(config, port, args)
# Check if we should use API for logging
# Resolve MQTT magic strings to actual IP addresses
if has_api() and (
network_devices := _resolve_network_devices(devices, config, args)
):
from esphome.api_client import run_logs
if has_api():
network_devices, has_mqtt_lookup = _split_network_devices(devices)
mqtt_resolver = None
if has_mqtt_lookup:
if network_devices:
# Addresses are already known, so don't block startup on the
# MQTT broker lookup; hand it to run_logs as a deferred
# resolver that runs in the background and feeds discovered
# addresses into the running log client, keeping MQTT as a
# fallback for when the known addresses are stale (e.g. DHCP
# reassigned the IP).
mqtt_resolver = functools.partial(
_mqtt_get_ip_or_warn,
config,
args.username,
args.password,
args.client_id,
)
else:
# The MQTT lookup is the only way to find the device; resolve
# it up front since the client needs an address to start with.
network_devices = _resolve_network_devices(devices, config, args)
if network_devices:
from esphome.api_client import run_logs
return run_logs(
config,
network_devices,
subscribe_states=_should_subscribe_states(args),
)
return run_logs(
config,
network_devices,
subscribe_states=_should_subscribe_states(args),
mqtt_resolver=mqtt_resolver,
)
if port_type in (PortType.NETWORK, PortType.MQTT) and has_mqtt_logging():
from esphome import mqtt
@@ -1437,6 +1536,13 @@ def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int
config, args.topic, args.username, args.password, args.client_id
)
# Fall back to the web_server HTTP SSE log stream for devices that have
# web_server: but no api: (the logging counterpart to web_server OTA).
if has_web_server_logging() and (
network_devices := _resolve_network_devices(devices, config, args)
):
return _show_logs_via_web_server(config, network_devices)
raise EsphomeError("No remote or local logging method configured (api/mqtt/logger)")
@@ -1941,7 +2047,7 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
new_name = args.name
for c in new_name:
if c not in ALLOWED_NAME_CHARS:
print(
safe_print(
color(
AnsiFore.BOLD_RED,
f"'{c}' is an invalid character for names. Valid characters are: "
@@ -1954,7 +2060,7 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
yaml = yaml_util.load_yaml(CORE.config_path)
if CONF_ESPHOME not in yaml or CONF_NAME not in yaml[CONF_ESPHOME]:
print(
safe_print(
color(
AnsiFore.BOLD_RED, "Complex YAML files cannot be automatically renamed."
)
@@ -2001,7 +2107,9 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
)
> 1
):
print(color(AnsiFore.BOLD_RED, "Too many matches in YAML to safely rename"))
safe_print(
color(AnsiFore.BOLD_RED, "Too many matches in YAML to safely rename")
)
return 1
new_raw = re.sub(
@@ -2019,7 +2127,7 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
# ``kitchen``; running ``esphome rename weird-file.yaml kitchen``
# would otherwise just re-flash the same hostname).
if new_name == old_name:
print(
safe_print(
color(
AnsiFore.BOLD_RED,
f"'{new_name}' is already the device's name.",
@@ -2029,7 +2137,7 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
new_path: Path = CORE.config_dir / (new_name + ".yaml")
if new_path.resolve() == CORE.config_path.resolve():
print(
safe_print(
color(
AnsiFore.BOLD_RED,
f"'{new_name}' is already the device's name.",
@@ -2037,7 +2145,7 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
)
return 1
if new_path.exists():
print(
safe_print(
color(
AnsiFore.BOLD_RED,
f"Cannot rename: {new_path} already exists. "
@@ -2045,7 +2153,7 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
)
)
return 1
print(
safe_print(
f"Updating {color(AnsiFore.CYAN, str(CORE.config_path))} to {color(AnsiFore.CYAN, str(new_path))}"
)
print()
@@ -2054,7 +2162,7 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
rc = run_external_process(*ESPHOME_COMMAND, "config", str(new_path))
if rc != 0:
print(color(AnsiFore.BOLD_RED, "Rename failed. Reverting changes."))
safe_print(color(AnsiFore.BOLD_RED, "Rename failed. Reverting changes."))
new_path.unlink()
return 1
@@ -2080,7 +2188,7 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
if CORE.config_path != new_path:
CORE.config_path.unlink()
print(color(AnsiFore.BOLD_GREEN, "SUCCESS"))
safe_print(color(AnsiFore.BOLD_GREEN, "SUCCESS"))
print()
return 0
@@ -2507,6 +2615,49 @@ def parse_args(argv):
return parser.parse_args(arguments)
def _warn_if_source_tree_mismatch() -> None:
"""Warn when the checkout the user is standing in is not the one being run.
An editable install records one absolute path, so a venv shared between git
worktrees (or reused after a checkout is copied or renamed) keeps importing
the tree it was installed from. Every command then silently runs, and
compiles, sources the user is not looking at. Only fires inside a checkout,
so ordinary installs never see it.
"""
try:
cwd = Path.cwd()
except OSError:
return # working directory is gone; a diagnostic must not break startup
for candidate in (cwd, *cwd.parents):
if (candidate / "esphome" / "__main__.py").is_file():
standing_in = candidate.resolve()
break
else:
return # not inside a checkout; nothing to compare against
running = Path(__file__).resolve().parent.parent
# Both sides are resolved, so on a case-sensitive filesystem this matches
# plain equality. samefile() compares device and inode, which additionally
# covers a case-insensitive filesystem (macOS) reaching one directory by
# differently cased paths. Falls back to equality if either path is gone.
try:
same = standing_in.samefile(running)
except OSError:
same = standing_in == running
if same:
return
_LOGGER.warning(
"Running ESPHome from a different checkout than the one you are in:\n"
" running from: %s\n"
" you are in: %s\n"
"The installed esphome resolves to the first, so its sources are used.\n"
"Run 'python -m esphome' from the second to use that one instead.",
running,
standing_in,
)
def run_esphome(argv):
from esphome.address_cache import AddressCache
@@ -2525,6 +2676,7 @@ def run_esphome(argv):
args.log_level = "CRITICAL"
setup_log(log_level=args.log_level)
_warn_if_source_tree_mismatch()
if args.command in PRE_CONFIG_ACTIONS:
try:
@@ -2582,10 +2734,14 @@ def run_esphome(argv):
# Skipped when -s overrides are passed, since the cache was written
# against the previous substitution set.
config: ConfigType | None = None
cache_eligible = (
cache_write_eligible = (
args.command in ("upload", "logs") and not command_line_substitutions
)
if cache_eligible:
# An explicit --toolchain must re-run the per-platform validators, so
# gate only the cache read; the refresh below saves the result unless
# the sidecar records a different toolchain.
cache_read_eligible = cache_write_eligible and args.toolchain is None
if cache_read_eligible:
from esphome.compiled_config import load_compiled_config
config = load_compiled_config(conf_path)
@@ -2595,7 +2751,8 @@ def run_esphome(argv):
conf_path.name,
)
if config is None:
cache_missed = config is None
if cache_missed:
from esphome.config import read_config
config = read_config(
@@ -2604,26 +2761,22 @@ def run_esphome(argv):
# Snapshot only needed by `esphome config --no-defaults`.
snapshot_user_config=getattr(args, "no_defaults", False),
)
# Refresh the cache so the next upload/logs hits the fast path
# instead of re-running read_config. Skip when the storage
# sidecar is absent (no compile has run): the cache would
# never be loaded back, so writing secrets to disk is wasted.
if cache_eligible and config is not None:
from esphome.compiled_config import save_compiled_config
from esphome.storage_json import ext_storage_path
if ext_storage_path(conf_path.name).exists():
save_compiled_config(config)
if config is None:
return 2
if config is None:
return 2
CORE.config = config
# Fallback for platforms whose validators didn't set the toolchain
# (only the esp32 component reads esp32.framework.toolchain). All
# other platforms only support PlatformIO today.
# The cache fast path skips validation, and legacy sidecars lack the
# toolchain field. Must run before the cache refresh below.
if CORE.toolchain is None:
CORE.toolchain = Toolchain.PLATFORMIO
# Refresh the cache so the next upload/logs hits the fast path
# instead of re-running read_config.
if cache_write_eligible and cache_missed:
from esphome.compiled_config import save_compiled_config_and_sidecar
save_compiled_config_and_sidecar(config)
if args.command not in POST_CONFIG_ACTIONS:
safe_print(f"Unknown command {args.command}")
return 1
+84 -3
View File
@@ -3,6 +3,7 @@ from __future__ import annotations
import asyncio
from contextlib import suppress
import logging
import threading
from typing import TYPE_CHECKING, Any
import warnings
@@ -20,6 +21,8 @@ from esphome.stacktrace import LogLineProcessor
from esphome.util import safe_print
if TYPE_CHECKING:
from collections.abc import Callable
from aioesphomeapi.api_pb2 import (
SubscribeLogsResponse, # pylint: disable=no-name-in-module
)
@@ -32,8 +35,18 @@ async def async_run_logs(
config: dict[str, Any],
addresses: list[str],
subscribe_states: bool = True,
mqtt_resolver: Callable[[threading.Event], list[str]] | None = None,
) -> None:
"""Run the logs command in the event loop."""
"""Run the logs command in the event loop.
If ``mqtt_resolver`` is given, it is called in a worker thread (paho-mqtt
has no asyncio support on Windows) concurrently with the connection
attempts to ``addresses``, and any addresses it discovers are fed into
the running client. It owns its own failure handling (returning [] when
discovery fails) and must honor the ``threading.Event`` it is passed so
teardown is not delayed by the lookup's wait window; the initial broker
connect itself is only bounded by the socket timeout.
"""
from datetime import datetime
conf = config["api"]
@@ -60,6 +73,41 @@ async def async_run_logs(
# Decoder resolution policy lives in LogLineProcessor.
processor = LogLineProcessor(config, CORE.target_platform)
mqtt_task: asyncio.Task[None] | None = None
mqtt_stop_event = threading.Event()
def _cancel_mqtt_discovery() -> None:
"""Stop the broker lookup once a connection has been established.
Its answer is only useful while still disconnected: after that it
either duplicates the connected address or arrives too late to
matter, so don't keep an idle broker session open for it.
"""
mqtt_stop_event.set()
if mqtt_task is not None and not mqtt_task.done():
mqtt_task.cancel()
async def _resolve_mqtt_addresses() -> None:
"""Discover the device address via the MQTT broker in the background."""
try:
mqtt_ips = await asyncio.to_thread(mqtt_resolver, mqtt_stop_event)
if not mqtt_ips:
_LOGGER.debug(
"MQTT discovery %s",
"aborted" if mqtt_stop_event.is_set() else "found no addresses",
)
return
if cli.add_addresses(mqtt_ips):
_LOGGER.info("Discovered address(es) via MQTT: %s", ", ".join(mqtt_ips))
else:
_LOGGER.debug(
"MQTT-discovered address(es) already known: %s", ", ".join(mqtt_ips)
)
except Exception: # pylint: disable=broad-except
# A background task failure would otherwise stay invisible for
# the whole session and only re-raise at teardown
_LOGGER.exception("MQTT address discovery failed")
def on_log(msg: SubscribeLogsResponse) -> None:
"""Handle a new log message."""
time_ = datetime.now().astimezone()
@@ -98,20 +146,53 @@ async def async_run_logs(
# A top-level ``deep_sleep:`` block means the device is only awake
# briefly; cap the reconnect backoff so a wake window is not missed.
deep_sleep="deep_sleep" in config,
on_connect=_cancel_mqtt_discovery if mqtt_resolver is not None else None,
)
try:
# Don't start (or keep) the broker lookup if a connection already
# succeeded; the stop event doubles as the not-needed-anymore latch
# and get_esphome_device_ip returns immediately when it is set.
if mqtt_resolver is not None and not mqtt_stop_event.is_set():
mqtt_task = asyncio.create_task(_resolve_mqtt_addresses())
await asyncio.Event().wait()
finally:
await stop()
try:
if mqtt_task is not None:
# Unblock the worker thread first so it can't hold up
# loop.shutdown_default_executor() for the full lookup timeout.
mqtt_stop_event.set()
# Give the worker a moment to exit through its own error
# handling; cancelling first would race out a late failure.
done, _ = await asyncio.wait([mqtt_task], timeout=1.0)
if not done:
mqtt_task.cancel()
# return_exceptions keeps a CancelledError from the cancel()
# above from re-raising here and jumping over the stop() below.
# The task handles Exception itself, so only a BaseException
# escape (e.g. SystemExit from the worker) can land here.
(result,) = await asyncio.gather(mqtt_task, return_exceptions=True)
if isinstance(result, BaseException) and not isinstance(
result, asyncio.CancelledError
):
_LOGGER.error("MQTT address discovery failed", exc_info=result)
finally:
# Must run even if a second cancellation lands mid-cleanup above
await stop()
def run_logs(
config: dict[str, Any],
addresses: list[str],
subscribe_states: bool = True,
mqtt_resolver: Callable[[threading.Event], list[str]] | None = None,
) -> None:
"""Run the logs command."""
with suppress(KeyboardInterrupt):
asyncio.run(
async_run_logs(config, addresses, subscribe_states=subscribe_states)
async_run_logs(
config,
addresses,
subscribe_states=subscribe_states,
mqtt_resolver=mqtt_resolver,
)
)
+9
View File
@@ -0,0 +1,9 @@
"""Native (PlatformIO-free) build support for the ESP8266 Arduino core.
This package downloads the Arduino ESP8266 core and the xtensa-lx106
toolchain, generates a ninja build for them plus the ESPHome sources, and
drives the build directly — the ESP8266 equivalent of ``esphome.espidf``.
Deliberately importable without the esp8266 component to avoid circular
imports; the component wires these modules in via lazy imports.
"""
+164
View File
@@ -0,0 +1,164 @@
"""Download and install the Arduino ESP8266 core, toolchain, and ninja.
Artifacts land in a machine-global cache (shared across projects, like the
ESP-IDF install in ``esphome.espidf.framework``):
<cache>/arduino8266/frameworks/<version>/ framework-arduinoespressif8266
<cache>/arduino8266/toolchains/<version>/ toolchain-xtensa (gcc 10.3)
Packages come from the PlatformIO registry (identical bits to the PlatformIO
backend); ``ESPHOME_ARDUINO8266_*_MIRRORS`` overrides the URLs. ninja comes
from PATH or the ninja PyPI wheel.
"""
from __future__ import annotations
import os
from pathlib import Path
from typing import NamedTuple
from esphome.build_helpers.ccache import ccache_defaults_env
from esphome.build_helpers.ninja import find_ninja
from esphome.build_helpers.tools_cache import ARDUINO8266_TOOLS_CACHE, tools_cache_path
from esphome.core import EsphomeError, Version
from esphome.framework_helpers import str_to_lst_of_str
from esphome.platformio.registry import install_package, prefetch_packages
FRAMEWORK_PACKAGE = "framework-arduinoespressif8266"
TOOLCHAIN_PACKAGE = "toolchain-xtensa"
# gcc 10.3, the toolchain Arduino core 3.x builds with; the build
# generator's compile flags are tuned to it.
TOOLCHAIN_VERSION = "2.100300.220621"
ESPHOME_ARDUINO8266_FRAMEWORK_MIRRORS = str_to_lst_of_str(
os.environ.get("ESPHOME_ARDUINO8266_FRAMEWORK_MIRRORS", "")
)
ESPHOME_ARDUINO8266_TOOLCHAIN_MIRRORS = str_to_lst_of_str(
os.environ.get("ESPHOME_ARDUINO8266_TOOLCHAIN_MIRRORS", "")
)
def get_arduino8266_tools_path() -> Path:
# Machine-global so all projects share one install; see
# espidf.framework.get_idf_tools_path for the location rationale.
return tools_cache_path(*ARDUINO8266_TOOLS_CACHE)
# 3.1.1 rather than 3.1.0: the registry has no package for 3.1.0, and the
# encoder below cannot name 3.0.0/3.0.1 either (see its docstring)
MIN_FRAMEWORK_VERSION = Version(3, 1, 1)
def framework_package_version(ver: Version) -> str:
"""Map an Arduino core version to its registry package version (3.1.2 ->
3.30102.0; the leading 3 is the package major).
Exact registry names only for cores > 2.6.2 and >= 3.0.2; callers floor
at MIN_FRAMEWORK_VERSION.
"""
if ver.major > 3:
raise EsphomeError(
f"Arduino core {ver} is not supported yet; "
"the newest known core series is 3.x"
)
if ver <= Version(2, 6, 2):
# Cores <= 2.6.2 use the older 1.x/2.x package-major encodings (same
# boundary as _format_framework_arduino_version's era guard)
raise EsphomeError(
f"Arduino core {ver} uses an older package encoding than this "
"helper implements (newer than 2.6.2)"
)
return f"3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
def get_framework_path(package_version: str) -> Path:
return get_arduino8266_tools_path() / "frameworks" / package_version
def get_toolchain_path() -> Path:
return get_arduino8266_tools_path() / "toolchains" / TOOLCHAIN_VERSION
class InstalledPaths(NamedTuple):
"""Locations of the installed framework, toolchain, and ninja binary."""
framework: Path
toolchain: Path
ninja: Path
def check_and_install(framework_version: Version) -> InstalledPaths:
"""Ensure framework, toolchain, and ninja are installed; return their paths."""
if framework_version < MIN_FRAMEWORK_VERSION:
# Config validation enforces this too; keep the module honest when
# called directly.
raise EsphomeError(
f"The native toolchain requires the Arduino core "
f">= {MIN_FRAMEWORK_VERSION}, got {framework_version}"
)
# Probe the cheap local dependency before ~110 MB of downloads
ninja_path = find_ninja()
package_version = framework_package_version(framework_version)
framework_path = get_framework_path(package_version)
downloads_dir = get_arduino8266_tools_path() / "downloads"
toolchain_path = get_toolchain_path()
# One spec per package: the prefetch and the installs must agree
specs = (
(
FRAMEWORK_PACKAGE,
package_version,
framework_path,
ESPHOME_ARDUINO8266_FRAMEWORK_MIRRORS,
("cores/esp8266", "tools/sdk", "libraries"),
),
(
TOOLCHAIN_PACKAGE,
TOOLCHAIN_VERSION,
toolchain_path,
ESPHOME_ARDUINO8266_TOOLCHAIN_MIRRORS,
# xtensa-lx106-elf pins the target: every gcc package has a bin/
("bin", "xtensa-lx106-elf"),
),
)
# Fetch both archives at once; the installs below verify and extract
prefetch_packages([spec[:4] for spec in specs], downloads_dir)
for name, version, dest, mirrors, expect in specs:
install_package(name, version, dest, mirrors, downloads_dir, expect=expect)
return InstalledPaths(
framework=framework_path, toolchain=toolchain_path, ninja=ninja_path
)
def toolchain_tool(toolchain_path: Path, name: str) -> Path:
"""Path to one toolchain tool (gcc, g++, ar, size, addr2line, ...).
The single owner of the ``bin/xtensa-lx106-elf-<name>`` layout and the
Windows suffix, so a toolchain package bump touches one spot.
"""
suffix = ".exe" if os.name == "nt" else ""
return toolchain_path / "bin" / f"xtensa-lx106-elf-{name}{suffix}"
def get_build_env(toolchain_path: Path, ccache: str | None) -> dict[str, str]:
env = os.environ.copy()
# Drop empty entries: a trailing separator from an absent PATH would
# make the shell search the current directory for tools
parts = [
str(toolchain_path / "bin"),
*filter(None, env.get("PATH", "").split(os.pathsep)),
]
env["PATH"] = os.pathsep.join(parts)
env.update(ccache_env(ccache))
return env
def ccache_env(ccache: str | None) -> dict[str, str]:
"""Return ccache settings for the build subprocess (not os.environ).
``ccache`` is the pre-resolved binary (resolve_ccache_path), or None
when disabled. Values the user already set in the environment are
respected.
"""
if ccache is None:
return {}
return ccache_defaults_env(get_arduino8266_tools_path() / "ccache")
+45 -4
View File
@@ -3,7 +3,12 @@
import json
from pathlib import Path
from esphome.components.esp32 import get_esp32_variant, idf_version
from esphome.components.esp32 import (
get_esp32_variant,
get_excluded_builtin_components,
get_managed_component_require_names,
idf_version,
)
import esphome.config_validation as cv
from esphome.core import CORE
from esphome.framework_helpers import (
@@ -67,6 +72,13 @@ def has_discovered_components() -> bool:
return get_available_components() is not None
def _cmake_quote(value: str) -> str:
"""Quote a cmake arg value for a set() line. add_cmake_arg rejects
whitespace, quotes, and '$', so only backslashes need escaping."""
escaped = value.replace("\\", "\\\\")
return f'"{escaped}"'
def get_project_cmakelists(minimal: bool = False) -> str:
"""Generate the top-level CMakeLists.txt for ESP-IDF project.
@@ -109,6 +121,15 @@ def get_project_cmakelists(minimal: bool = False) -> str:
else ""
)
# CMake variables registered via cg.add_cmake_arg(). Emitted before
# include(project.cmake) so values like EXCLUDE_COMPONENTS are already
# set when project.cmake seeds the component list, and on minimal
# (discovery) writes too so excluded components never register.
cmake_args = "\n".join(
f"set({name} {_cmake_quote(value)})"
for name, value in sorted(CORE.cmake_args.items())
)
# Per-project list exposed as a CMake variable so converted PIO libs
# can reference ${ESPHOME_PROJECT_MANAGED_COMPONENTS} without baking
# project-specific names into their cached CMakeLists.
@@ -119,8 +140,6 @@ def get_project_cmakelists(minimal: bool = False) -> str:
# runs as a separate CMake script invocation that doesn't load the
# project's top-level CMakeLists; without this, ${ESPHOME_PROJECT_
# MANAGED_COMPONENTS} in a converted-lib REQUIRES expands to empty).
from esphome.components.esp32 import get_managed_component_require_names
managed_components_property = "\n".join(
f"idf_build_set_property(ESPHOME_PROJECT_MANAGED_COMPONENTS {name} APPEND)"
for name in get_managed_component_require_names()
@@ -131,12 +150,22 @@ def get_project_cmakelists(minimal: bool = False) -> str:
# component's REQUIRES including real IDF components). Referenced by
# src/CMakeLists and by each converted PIO lib's CMakeLists. Skipped
# on minimal writes because project_description.json may be stale.
# Excluded components are dropped here as well: a stale
# project_description.json from a build without exclusions may still
# list them, and requiring an excluded component pulls it back into
# the build (IDF requirement expansion overrides EXCLUDE_COMPONENTS).
# Derived from the EXCLUDE_COMPONENTS cmake arg emitted above so the
# two can never disagree within one generated file.
builtin_components_property = (
""
if minimal
else "\n".join(
f"idf_build_set_property(ESPHOME_PROJECT_BUILTIN_COMPONENTS {name} APPEND)"
for name in sorted(get_available_components() or [])
for name in sorted(
set(get_available_components() or []).difference(
CORE.cmake_args.get("EXCLUDE_COMPONENTS", "").split(";")
)
)
)
)
@@ -163,6 +192,8 @@ set(CMAKE_NINJA_FORCE_RESPONSE_FILE 1)
set(IDF_TARGET {idf_target})
set(EXTRA_COMPONENT_DIRS ${{CMAKE_SOURCE_DIR}}/src)
{cmake_args}
include($ENV{{IDF_PATH}}/tools/cmake/project.cmake)
{cpp_standard_options}
@@ -264,3 +295,13 @@ def write_project(minimal: bool = False) -> None:
CORE.relative_src_path("CMakeLists.txt"),
get_component_cmakelists(),
)
# Snapshot the exclusion set so has_outdated_files() can trigger a
# discovery reconfigure when it changes. Excluded components never
# register in project_description.json, so re-including one (e.g. a
# config gains mqtt) requires a fresh discovery pass before the
# ESPHOME_PROJECT_BUILTIN_COMPONENTS property can list it.
write_file_if_changed(
CORE.relative_build_path("exclude_components.esphomeinternal"),
";".join(get_excluded_builtin_components()),
)
+11
View File
@@ -63,6 +63,17 @@ def get_ini_content():
# Add extra script for C++ flags
CORE.add_platformio_option("extra_scripts", [f"pre:{CXX_FLAGS_FILE_NAME}"])
# Add CMake args. A user-supplied value (str or list) is deliberately
# replaced; this option was always overwritten at FINAL priority.
if CORE.cmake_args:
CORE.add_platformio_option(
"board_build.cmake_extra_args",
" ".join(
f"-D{name}={value}" for name, value in sorted(CORE.cmake_args.items())
),
replace=True,
)
content = "[platformio]\n"
content += f"description = ESPHome {__version__}\n"
+1
View File
@@ -0,0 +1 @@
"""Build helpers shared by the native (non-PlatformIO) toolchains."""
+93
View File
@@ -0,0 +1,93 @@
"""Shared ccache policy for build backends: env-knob parsing, binary
resolution, and default ``CCACHE_*`` values."""
from __future__ import annotations
import logging
import os
from pathlib import Path
from esphome.framework_helpers import strip_win_long_path_prefix, tool_version_runs
from esphome.helpers import FALSY_BOOL_STRINGS, TRUTHY_BOOL_STRINGS
_LOGGER = logging.getLogger(__name__)
# cv.boolean's spelling tables plus the 1/0 env convention
TRUTHY_ENV_STRINGS = TRUTHY_BOOL_STRINGS | {"1"}
FALSY_ENV_STRINGS = FALSY_BOOL_STRINGS | {"0"}
def _ccache_runs(ccache: str) -> bool:
"""Return True when the ``ccache`` found on PATH actually runs."""
return tool_version_runs(
ccache,
"Ignoring ccache at %s because it failed to run; compiling without ccache",
)
def parse_enable_env(name: str) -> bool | None:
"""Strictly parse an on/off environment knob; None when unset or invalid.
``bool(str)`` truthiness would flip ``no``/``off`` to enabled, so only
1/true/yes/on and 0/false/no/off count; anything else warns and reads
as unset so the caller's default policy applies.
"""
raw = os.environ.get(name)
if raw is None:
return None
lowered = raw.strip().lower()
if lowered in TRUTHY_ENV_STRINGS:
return True
if lowered in FALSY_ENV_STRINGS:
return False
_LOGGER.warning("Ignoring unrecognized %s=%r; use 1 or 0", name, raw)
return None
def resolve_ccache_path() -> str | None:
"""The ccache binary to wrap compiles with, or None when disabled.
An explicit ``ESPHOME_CCACHE_ENABLE=1`` skips the runnability probe; the
Windows extended-length prefix is stripped before probing (#18399).
"""
import shutil
explicit = parse_enable_env("ESPHOME_CCACHE_ENABLE")
if explicit is False:
return None
ccache = shutil.which("ccache")
if ccache is None:
if explicit:
_LOGGER.warning(
"ESPHOME_CCACHE_ENABLE is set but no ccache binary is on PATH; "
"compiling without ccache"
)
return None
ccache = strip_win_long_path_prefix(ccache)
if not explicit and not _ccache_runs(ccache):
return None
return ccache
def ccache_defaults_env(cache_dir: Path) -> dict[str, str]:
"""Default ``CCACHE_*`` values for a build subprocess (not os.environ).
Values the user already set in the environment are respected. Depend
mode is on: both native backends emit depfiles (-MMD / CMake), which
keeps cache-miss overhead low.
"""
from esphome.core import CORE
# An unset build_path means the env was built before preload; fail loudly
# rather than silently drop CCACHE_BASEDIR.
if CORE.build_path is None:
raise ValueError(
"CORE.build_path must be set before constructing the build environment"
)
defaults = {
"CCACHE_DIR": str(cache_dir),
"CCACHE_NOHASHDIR": "true",
"CCACHE_DEPEND": "1",
"CCACHE_BASEDIR": str(Path(CORE.build_path).resolve()),
}
return {k: v for k, v in defaults.items() if k not in os.environ}
@@ -1,10 +1,10 @@
"""Derive idedata from an ESP-IDF native-toolchain ``compile_commands.json``.
"""Derive idedata from a native (non-PlatformIO) build's ``compile_commands.json``.
PlatformIO exposes a curated ``pio run -t idedata`` JSON; the native ESP-IDF
toolchain has no such command, but its CMake build emits
``build/compile_commands.json`` (CMAKE_EXPORT_COMPILE_COMMANDS). This module
turns that file into the same fields consumers (IDE integration, clang-tidy)
expect:
PlatformIO exposes a curated ``pio run -t idedata`` JSON; the native
toolchains have no such command, but each build produces a
``compile_commands.json`` (CMAKE_EXPORT_COMPILE_COMMANDS for ESP-IDF, ninja's
compdb tool otherwise). This module turns that file into the same fields
consumers (IDE integration, clang-tidy) expect:
{cc_path, cxx_path, cxx_flags, defines, includes: {build, toolchain}}
"""
@@ -18,6 +18,20 @@ from pathlib import Path
import shlex
import subprocess
from esphome.core import EsphomeError
from esphome.helpers import write_file
# Everything idedata generation may raise after a successful link. Broad on
# purpose, and shared by every consumer: idedata is a bonus artifact, so
# these must be caught and warned about, never allowed to fail the build.
IDEDATA_BEST_EFFORT_ERRORS = (
EsphomeError,
LookupError,
OSError,
RuntimeError,
ValueError,
)
_LOGGER = logging.getLogger(__name__)
# C++ translation-unit suffixes used to identify ESPHome source files.
@@ -120,7 +134,18 @@ def _pick_entry(entries: list[dict]) -> dict:
raise ValueError("no C++ translation unit found in compile_commands.json")
def _parse_entry(entry: dict) -> tuple[str, list[str], list[str], list[str]]:
# Compiler launchers that may prefix a compile command; a closed launcher
# denylist beats enumerating compiler names, an open set.
_LAUNCHER_STEMS = frozenset({"ccache", "sccache", "distcc", "icecc", "buildcache"})
def _is_launcher(token: str) -> bool:
return Path(token).stem.lower() in _LAUNCHER_STEMS
def parse_entry(
entry: dict, launcher: str | None = None
) -> tuple[str, list[str], list[str], list[str]]:
"""Parse one compile_commands entry -> (cxx_path, defines, includes, cxx_flags)."""
directory = Path(entry["directory"])
tokens = _expand_response_files(_split_command(entry["command"]), directory)
@@ -136,6 +161,20 @@ def _parse_entry(entry: dict) -> tuple[str, list[str], list[str], list[str]]:
raw = os.path.normpath(directory / raw)
return raw.replace("\\", "/")
# A launcher-wrapped command ("ccache g++ ...") names the compiler second
if launcher is not None and tokens[:1] == [launcher]:
tokens = tokens[1:]
if not tokens:
# _split_command("") is [] by design, and a command that is only
# the launcher strips to nothing; fail like _pick_entry does
# instead of an IndexError traceback
raise ValueError(f"empty compile command for {entry.get('file')}")
if _is_launcher(tokens[0]) and len(tokens) > 1 and not tokens[1].startswith("-"):
# A stale compile DB built with a launcher the current run no longer
# configures: the real compiler is the next token. Warn: the DB is
# stale and worth regenerating.
_LOGGER.warning("Stripping unconfigured launcher %s", tokens[0])
tokens = tokens[1:]
# token0 is the compiler path; the rest of the command already uses forward
# slashes on Windows, so normalize it too for a consistent idedata file.
cxx_path = tokens[0].replace("\\", "/")
@@ -168,7 +207,7 @@ def _parse_entry(entry: dict) -> tuple[str, list[str], list[str], list[str]]:
return cxx_path, defines, includes, cxx_flags
def _get_toolchain_includes(cxx_path: str) -> list[str]:
def get_toolchain_includes(cxx_path: str) -> list[str]:
"""Query the compiler for its builtin ``#include <...>`` search dirs."""
result = subprocess.run(
[cxx_path, "-E", "-x", "c++", "-", "-v"],
@@ -219,26 +258,114 @@ def _cc_path_from_cxx(cxx_path: str) -> str:
return f"{stem}{suffix}"
def idedata_from_build(compile_commands: Path) -> dict:
def load_or_build_idedata(
compile_commands: Path,
elf_path: Path,
cache: Path,
launcher: str | None = None,
) -> dict | None:
"""Return idedata for a compile_commands.json build, cached on mtime.
Shared by the native ESP-IDF and ESP8266 Arduino toolchains. Returns None
when the compile DB doesn't exist yet (nothing was built). ``launcher``
is the compiler-launcher path (ccache) the build was generated with, if
any; commands in the compile DB are prefixed with it.
"""
if not compile_commands.is_file():
_LOGGER.debug("No %s yet; skipping idedata generation", compile_commands)
return None
if cache.is_file() and cache.stat().st_mtime >= compile_commands.stat().st_mtime:
try:
cached = json.loads(cache.read_text(encoding="utf-8"))
except (ValueError, OSError) as err:
# A recurring cause (interrupted write, disk full) would otherwise
# look like unexplained slow builds
_LOGGER.warning("Discarding unreadable idedata cache %s: %s", cache, err)
else:
# Rebuild pre-cc_path caches on the field, not the timestamp;
# the type check keeps "in" from substring-matching a string
if isinstance(cached, dict) and "cc_path" in cached:
return cached
data = idedata_from_build(compile_commands, launcher)
data["prog_path"] = str(elf_path)
cache.parent.mkdir(parents=True, exist_ok=True)
# Atomic so a crash mid-write cannot leave a truncated cache
write_file(cache, json.dumps(data, indent=2) + "\n")
return data
def reject_launcher_compiler(cxx_path: str) -> None:
"""Reject a compile DB that names a launcher (ccache) as the compiler.
Reject before the toolchain probe, which would fail opaquely on a
launcher; the unusable compile DB must never be cached or consumed.
"""
if _is_launcher(cxx_path):
raise EsphomeError(
f"compile_commands.json names the launcher {cxx_path} as the "
"compiler; the compile database is unusable"
)
def idedata_from_build(compile_commands: Path, launcher: str | None = None) -> dict:
"""Parse compile_commands.json into the idedata fields consumers expect.
A single ESP-IDF compile entry only carries its own component's REQUIRES
include set, but consumers (clang-tidy) analyze ESPHome headers that
transitively pull in other components. So take cxx_path / cxx_flags /
defines from a representative ESPHome TU, but union the include dirs across
all ESPHome TUs to get a project-wide superset (as PlatformIO's idedata
provides).
A single compile entry only carries the include set its own translation
unit was built with (per-component under ESP-IDF), but consumers
(clang-tidy) analyze ESPHome headers that transitively pull in other
components. So take cxx_path / cxx_flags / defines from a representative
ESPHome TU, but union the include dirs across all ESPHome TUs to get a
project-wide superset (as PlatformIO's idedata provides).
"""
entries = json.loads(Path(compile_commands).read_text(encoding="utf-8"))
cxx_path, defines, _, cxx_flags = _parse_entry(_pick_entry(entries))
build_includes: dict[str, None] = {}
representative = _pick_entry(entries)
cxx_path, defines, rep_includes, cxx_flags = parse_entry(representative, launcher)
reject_launcher_compiler(cxx_path)
# Seed with the representative's includes so it is not parsed twice
has_esphome_tu = _is_esphome_src(representative["file"])
build_includes: dict[str, None] = dict.fromkeys(
rep_includes if has_esphome_tu else ()
)
def _shape(entry: dict) -> str:
# The command minus its TU-specific paths: entries sharing a shape
# carry identical include sets (one ninja rule), so tokenize once
# per shape instead of once per TU. Response-file commands never
# dedupe: per-object .rsp names strip to one shape while the files
# may hold different include sets.
command = entry["command"]
if "@" in command:
return f"unique:{entry['file']}"
return command.replace(entry.get("file", ""), "").replace(
entry.get("output", ""), ""
)
seen_shapes = {_shape(representative)}
for entry in entries:
if not _is_esphome_src(entry["file"]):
if entry is representative or not _is_esphome_src(entry["file"]):
continue
for inc in _parse_entry(entry)[2]:
has_esphome_tu = True
if (shape := _shape(entry)) in seen_shapes:
_LOGGER.debug("Include union: %s shares a command shape", entry["file"])
continue
seen_shapes.add(shape)
for inc in parse_entry(entry, launcher)[2]:
build_includes.setdefault(inc, None)
if not has_esphome_tu:
# _pick_entry fell back to an arbitrary C++ entry: idedata built
# from it breaks clang-tidy/IDE consumers, and a one-time warning
# would be cached into permanence. The best-effort call sites
# downgrade this to a build warning.
raise EsphomeError(
f"No ESPHome translation unit found in {compile_commands}; "
"refusing to cache unusable idedata"
)
return {
"cc_path": _cc_path_from_cxx(cxx_path),
"cxx_path": cxx_path,
@@ -246,6 +373,6 @@ def idedata_from_build(compile_commands: Path) -> dict:
"defines": defines,
"includes": {
"build": list(build_includes),
"toolchain": _get_toolchain_includes(cxx_path),
"toolchain": get_toolchain_includes(cxx_path),
},
}
+92
View File
@@ -0,0 +1,92 @@
"""Platform-neutral helpers for ninja-driven native builds."""
from __future__ import annotations
import logging
import os
from pathlib import Path
import re
import shutil
from esphome.core import EsphomeError
from esphome.framework_helpers import strip_win_long_path_prefix, tool_version_runs
_LOGGER = logging.getLogger(__name__)
def _ninja_runs(binary: str) -> bool:
"""Whether the ninja found on PATH actually runs (see tool_version_runs)."""
return tool_version_runs(
binary,
"Ignoring ninja at %s because it failed to run; "
"falling back to the bundled wheel",
)
def find_ninja() -> Path:
"""Locate the ninja binary: a runnable PATH hit first, else the ninja
PyPI wheel."""
if binary := shutil.which("ninja"):
binary = strip_win_long_path_prefix(binary)
if _ninja_runs(binary):
return Path(binary)
import_error: ImportError | None = None
try:
import ninja
except ImportError as err:
import_error = err
wheel_binary = None
else:
wheel_binary = Path(ninja.BIN_DIR) / (
"ninja.exe" if os.name == "nt" else "ninja"
)
if wheel_binary is None or not wheel_binary.is_file():
raise EsphomeError(
"ninja not found on PATH or in the ninja package; reinstall the "
"esphome Python environment"
) from import_error
return wheel_binary
def escape(value: Path | str) -> str:
"""Escape a path or token for a ninja file."""
return str(value).replace("$", "$$").replace(":", "$:").replace(" ", "$ ")
def quote_arg(tok: str) -> str:
"""Quote with the CreateProcess argv rule (as ``subprocess.list2cmdline``):
backslash runs double only before a quote. Windows-only; ``$`` must
already be doubled for ninja.
"""
quoted = re.sub(r'(\\*)"', lambda m: m.group(1) * 2 + '\\"', tok)
quoted = re.sub(r"(\\+)\Z", lambda m: m.group(1) * 2, quoted)
return f'"{quoted}"'
# Force-quote any token containing a character outside the shlex.quote-style
# safe set: ninja hands POSIX commands to /bin/sh -c, so bare (, ;, <, *, `
# and friends would be re-parsed as shell syntax.
_NEEDS_QUOTE = re.compile(r"[^\w@%+=:,./-]")
def shell_token(tok: str, force: bool = False) -> str:
"""Re-quote a lexed token for the platform shell; ``force`` always quotes.
Single quotes on POSIX (/bin/sh), the argv rule on Windows
(CreateProcess). ``$`` is doubled first because ninja expands it before
the command reaches the shell.
"""
tok = tok.replace("$", "$$") # ninja would expand a bare $ to nothing
if not (force or not tok or _NEEDS_QUOTE.search(tok)):
return tok
# An empty token must become '' / "" or it vanishes from the argv
if os.name == "nt":
return quote_arg(tok)
# shlex.quote's rule; inlined because the $-doubled token must not be
# re-examined for safe characters
return "'" + tok.replace("'", "'\"'\"'") + "'"
def quote_path(value: Path | str) -> str:
"""Force-quote a path for the ninja command line (shell/CreateProcess)."""
return shell_token(str(value), force=True)
+24
View File
@@ -0,0 +1,24 @@
"""The PlatformIO-format size bar shared by the native toolchains."""
from __future__ import annotations
def format_bar(used: int, total: int) -> str:
"""Match PlatformIO's ``_format_availale_bytes`` (sic, pioupload.py) exactly."""
pct_raw = used / total if total else 0
blocks = 10
filled = min(int(round(blocks * pct_raw)), blocks)
progress = "=" * filled
return (
f"[{progress:<{blocks}}] {pct_raw: 6.1%} "
f"(used {used:d} bytes from {total:d} bytes)"
)
def print_size_line(label: str, used: int, total: int) -> None:
"""One PlatformIO-format summary line (``RAM``/``Flash``).
The label padding is part of the format: ``script/ci_memory_impact_extract.py``
matches these lines verbatim.
"""
print(f"{label + ':':<7}{format_bar(used, total)}")
+36
View File
@@ -0,0 +1,36 @@
"""Machine-global tools cache location shared by the native backends."""
from __future__ import annotations
from pathlib import Path
def tools_cache_path(env_var: str, subdir: str) -> Path:
"""A backend's machine-global tools directory, with an env override.
A blank/whitespace override is treated as unset: ``Path("")`` resolves
to the CWD, which ``clean-all`` would then delete.
"""
import platformdirs
from esphome.helpers import get_str_env
if prefix := get_str_env(env_var, "").strip():
# resolve(): symlinked prefixes otherwise trip idf.py's
# venv-mismatch warning on every build
return Path(prefix).expanduser().resolve()
# appauthor=False keeps the Windows path short (no vendor segment);
# deep IDF trees run into MAX_PATH otherwise
return (
Path(platformdirs.user_cache_dir("esphome", appauthor=False)) / subdir
).resolve()
# (env override, cache subdir) per native backend. writer.clean_all wipes
# every entry via tools_cache_path, so listing a cache here is the single
# step that registers it for removal; the backends' own path getters use
# the same named pairs so the two cannot drift.
IDF_TOOLS_CACHE = ("ESPHOME_ESP_IDF_PREFIX", "idf")
SDK_NRF_TOOLS_CACHE = ("ESPHOME_SDK_NRF_PREFIX", "sdk-nrf")
ARDUINO8266_TOOLS_CACHE = ("ESPHOME_ARDUINO8266_PREFIX", "arduino8266")
TOOLS_CACHE_SPECS = (IDF_TOOLS_CACHE, SDK_NRF_TOOLS_CACHE, ARDUINO8266_TOOLS_CACHE)
+1
View File
@@ -25,6 +25,7 @@ from esphome.cpp_generator import ( # noqa: F401
add,
add_build_flag,
add_build_unflag,
add_cmake_arg,
add_cxx_build_flag,
add_define,
add_global,
+186 -50
View File
@@ -1,26 +1,193 @@
"""Validated-config cache for the upload/logs fast path.
compile dumps the validated config to <data_dir>/storage/<file>.validated.yaml;
compile dumps the validated config to <data_dir>/storage/<file>.validated.json;
the next upload/logs for that YAML reuses it instead of running the full
read_config pipeline. YAML round-trip (yaml_util.dump/load_yaml) keeps
!lambda/!include/IDs/paths intact; mtime gates staleness.
read_config pipeline. The cache is deliberately lossy: only ``!lambda``
bodies survive typed (``Lambda``); IDs, time periods, MAC/IP addresses,
paths, UUIDs and enums store the same string form the YAML dumper
produced for them. JSON additionally coerces non-str dict keys to
strings; validated configs only use string keys (every schema key
validator is ``cv.string``). mtime gates staleness.
"""
from __future__ import annotations
import json
import logging
from pathlib import Path
from typing import Any
from esphome.core import CORE
from esphome.const import __version__ as ESPHOME_VERSION
from esphome.core import CORE, EsphomeError, Lambda
from esphome.helpers import write_file
from esphome.storage_json import StorageJSON, ext_storage_path
from esphome.storage_json import StorageJSON, ext_storage_path, storage_path
from esphome.types import ConfigType
_LOGGER = logging.getLogger(__name__)
# Bump when the on-disk shape changes; a mismatched version falls back
# to read_config. The envelope also stamps the writing esphome version:
# after an upgrade the cache holds the previous release's validation, so
# it falls back once and the re-save self-heals.
_CACHE_VERSION = 1
_LAMBDA_KEY = "__esphome_lambda__"
def compiled_config_path(config_filename: str) -> Path:
"""Path to the cached validated config alongside the storage sidecar."""
return CORE.data_dir / "storage" / f"{config_filename}.validated.json"
def save_compiled_config(config: ConfigType) -> None:
"""Write the validated-config cache. Always-write so mtime stays fresh.
Mode 0600 because config validation resolved !secret inline.
Failures are non-fatal: the fast path falls back to read_config.
"""
try:
# The legacy YAML cache holds inline-resolved secrets and nothing
# reads it anymore; drop it even when the write below fails. A
# failed removal leaves resolved secrets on disk, so it warns.
try:
_legacy_compiled_config_path(CORE.config_filename).unlink(missing_ok=True)
except OSError as err:
_LOGGER.warning(
"Could not remove the legacy validated-config cache: %s", err
)
rendered = json.dumps(
{"v": _CACHE_VERSION, "esphome": ESPHOME_VERSION, "config": config},
separators=(",", ":"),
default=_json_default,
)
write_file(compiled_config_path(CORE.config_filename), rendered, private=True)
except TypeError as err:
# Structural, not transient: this config can never cache (e.g. a
# non-basic dict key), so every upload/logs pays the slow path.
_LOGGER.warning("Cannot cache the validated config: %s", err)
except Exception as err: # noqa: BLE001 # pylint: disable=broad-except
# Likely persistent (permissions, full disk): every upload/logs
# pays the slow path until it clears, so surface it.
_LOGGER.warning("Skipping compiled config cache write: %s", err)
def save_compiled_config_and_sidecar(config: ConfigType) -> None:
"""Refresh the cache from the upload/logs fallback (CORE.config must be set).
The cache is only written when a complete sidecar is on disk:
load_compiled_config can't use it otherwise, and it holds resolved
secrets.
"""
if _refresh_sidecar():
save_compiled_config(config)
def _refresh_sidecar() -> bool:
"""Ensure a complete sidecar is on disk; True when one is.
Writes one (without claiming a build) when missing or wizard-only.
Failures are non-fatal; the next upload/logs pays the slow path again.
"""
try:
path = storage_path()
try:
old = StorageJSON.load_strict(path)
except Exception as err: # noqa: BLE001 # pylint: disable=broad-except
# Present but unreadable: it may hold a real build's metadata,
# and a fresh rewrite would also stop the next compile from
# cleaning a possibly incoherent build tree.
_LOGGER.warning(
"Not caching: storage sidecar %s is unreadable (%s)", path, err
)
return False
if old is not None and old.can_apply_to_core():
if (
old.toolchain is not None
and CORE.toolchain is not None
and old.toolchain != CORE.toolchain.value
):
# Platforms normalize toolchain-sensitive keys differently;
# never cache a config validated under a different toolchain
# than the compile's
_LOGGER.debug(
"Not caching: config validated with toolchain %r but the "
"last compile used %r",
CORE.toolchain.value,
old.toolchain,
)
return False
# Compile-written; nothing to refresh.
return True
if CORE.build_path is not None and CORE.build_path.exists():
# An unvalidated build tree: its absent or mismatched sidecar
# is what makes the next compile wipe it, so don't vouch for
# a build this run never saw.
_LOGGER.warning(
"Not caching: build tree %s has no matching sidecar; "
"'esphome compile' will settle it",
CORE.build_path,
)
return False
new = StorageJSON.from_esphome_core(CORE, old, claim_build=False)
if not new.can_apply_to_core():
_LOGGER.warning("Not caching: rebuilt storage sidecar is still incomplete")
return False
new.save(path)
return True
except (OSError, EsphomeError) as err:
# write_file wraps OSError into EsphomeError. Persistent
# (unwritable storage dir), so surface that every upload/logs
# pays the slow path.
_LOGGER.warning("Could not refresh the storage sidecar: %s", err)
except Exception: # noqa: BLE001 # pylint: disable=broad-except
# A structural bug; keep the traceback so it isn't mistaken
# for the I/O failure above.
_LOGGER.warning(
"Unexpected error refreshing the storage sidecar", exc_info=True
)
return False
def load_compiled_config(conf_path: Path) -> ConfigType | None:
"""Load the cached validated config and apply storage metadata to CORE.
Returns None (caller falls back to read_config) when the cache is
missing, older than the source YAML, unparseable, a different cache
version, or the sidecar is incomplete. The loaded config carries no
source ranges; callers must not feed it into read_config/write_cpp.
"""
cache_path = compiled_config_path(conf_path.name)
if not _cache_is_fresh(cache_path, conf_path):
return None
try:
envelope = json.loads(
cache_path.read_text(encoding="utf-8"), object_hook=_decode_object
)
except (OSError, ValueError) as err:
_LOGGER.debug("Ignoring unreadable compiled config cache: %s", err)
return None
if (
not isinstance(envelope, dict)
or envelope.get("v") != _CACHE_VERSION
or envelope.get("esphome") != ESPHOME_VERSION
or not isinstance(config := envelope.get("config"), dict)
):
_LOGGER.debug("Ignoring compiled config cache with a foreign envelope")
return None
storage = StorageJSON.load(ext_storage_path(conf_path.name))
if storage is None or not storage.can_apply_to_core():
_LOGGER.debug("Ignoring compiled config cache: sidecar missing or incomplete")
return None
storage.apply_to_core()
return config
# Remove before 2027.8: by then every maintained install has saved the
# JSON cache at least once and dropped its legacy YAML file.
def _legacy_compiled_config_path(config_filename: str) -> Path:
"""Path of the pre-JSON YAML cache; only ever removed."""
return CORE.data_dir / "storage" / f"{config_filename}.validated.yaml"
@@ -32,52 +199,21 @@ def _cache_is_fresh(cache_path: Path, source_path: Path) -> bool:
return False
def save_compiled_config(config: ConfigType) -> None:
"""Write the validated-config cache. Always-write so mtime stays fresh.
def _json_default(value: Any) -> Any:
"""Mirror ESPHomeDumper's representers: Lambda stays typed, the rest
stringify (IDs, time periods, MAC/IP addresses, paths, UUIDs, enums).
Mode 0600 because show_secrets=True resolves !secret inline.
Failures are non-fatal: the fast path falls back to read_config.
IncludeFile/Extend/Remove have no JSON mirror and would stringify
wrong, but none survive validation (config.py's packages merge and
the substitution pass consume them) so no guard is spent on them.
"""
from esphome import yaml_util
try:
rendered = yaml_util.dump(config, show_secrets=True)
write_file(compiled_config_path(CORE.config_filename), rendered, private=True)
except Exception as err: # noqa: BLE001 # pylint: disable=broad-except
_LOGGER.debug("Skipping compiled config cache write: %s", err)
if isinstance(value, Lambda):
return {_LAMBDA_KEY: value.value}
return str(value)
def load_compiled_config(conf_path: Path) -> ConfigType | None:
"""Load the cached validated config and apply storage metadata to CORE.
Returns None (caller falls back to read_config) when the cache is
missing, older than the source YAML, unparseable, or the sidecar
is incomplete.
"""
cache_path = compiled_config_path(conf_path.name)
if not _cache_is_fresh(cache_path, conf_path):
return None
from esphome import yaml_util
try:
# Fast path never validates or generates code - no source ranges
# needed (see load_yaml). Callers must not feed this config into
# read_config/write_cpp: the esp_range consumers in config.py and
# cpp_generator.py are isinstance-guarded and would degrade
# silently (wrong error/lambda locations) instead of raising.
config = yaml_util.load_yaml(
cache_path, clear_secrets=False, track_document_range=False
)
except Exception: # noqa: BLE001 # pylint: disable=broad-except
return None
storage = StorageJSON.load(ext_storage_path(conf_path.name))
if storage is None:
return None
# apply_to_core assumes a real compile wrote the sidecar; wizard-only
# sidecars leave both of these unset and can't drive upload/logs.
if not storage.core_platform and not storage.target_platform:
return None
storage.apply_to_core()
return config
def _decode_object(obj: dict[str, Any]) -> Any:
"""Revive the Lambda sentinel; every other mapping passes through."""
if len(obj) == 1 and isinstance(value := obj.get(_LAMBDA_KEY), str):
return Lambda(value)
return obj
+10
View File
@@ -0,0 +1,10 @@
"""Component alias registry.
Generated by script/build_alias_registry.py - do not edit manually.
See the component-alias section of esphome/loader.py.
"""
# alias -> (canonical component, removal version or None)
COMPONENT_ALIASES: dict[str, tuple[str, str | None]] = {
"rp2040": ("rp2", "2027.7.0"),
}
+6
View File
@@ -49,6 +49,12 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config):
if CORE.is_esp32:
from esphome.components.esp32 import include_builtin_idf_component
# Re-enable the gptimer driver (excluded by default to save compile time)
include_builtin_idf_component("esp_driver_gptimer")
if CORE.is_esp8266:
# ac_dimmer uses setTimer1Callback which requires the waveform generator
from esphome.components.esp8266.const import require_waveform
+5 -1
View File
@@ -1,3 +1,5 @@
from typing import Any
from esphome import pins
import esphome.codegen as cg
from esphome.components.esp32 import (
@@ -16,6 +18,7 @@ from esphome.components.esp32 import (
import esphome.config_validation as cv
from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER, PLATFORM_ESP8266
from esphome.core import CORE
from esphome.types import ConfigType
CODEOWNERS = ["@esphome/core"]
@@ -225,12 +228,13 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
}
def validate_adc_pin(value):
def validate_adc_pin(value: Any) -> ConfigType | str:
if str(value).upper() == "VCC":
if CORE.is_rp2:
return pins.internal_gpio_input_pin_schema(29)
return cv.only_on([PLATFORM_ESP8266])("VCC")
# Deprecated in favour of the `internal_temperature` platform, remove before 2027.2.0
if str(value).upper() == "TEMPERATURE":
return cv.only_on_rp2("TEMPERATURE")
+1 -1
View File
@@ -3,7 +3,7 @@
namespace esphome::adc {
static const char *const TAG = "adc.common";
static const char *const TAG = "adc";
const LogString *sampling_mode_to_str(SamplingMode mode) {
switch (mode) {
+1 -1
View File
@@ -6,7 +6,7 @@
namespace esphome::adc {
static const char *const TAG = "adc.esp32";
static const char *const TAG = "adc";
adc_oneshot_unit_handle_t ADCSensor::shared_adc_handles[2] = {nullptr, nullptr};
@@ -13,7 +13,7 @@ ADC_MODE(ADC_VCC)
namespace esphome::adc {
static const char *const TAG = "adc.esp8266";
static const char *const TAG = "adc";
void ADCSensor::setup() {
#ifndef USE_ADC_SENSOR_VCC
@@ -5,7 +5,7 @@
namespace esphome::adc {
static const char *const TAG = "adc.libretiny";
static const char *const TAG = "adc";
void ADCSensor::setup() {
#ifndef USE_ADC_SENSOR_VCC
+21 -2
View File
@@ -17,7 +17,26 @@
namespace esphome::adc {
static const char *const TAG = "adc.rp2";
static const char *const TAG = "adc";
// The on-die temperature sensor sits on the last ADC channel: input 4 on RP2040
// and RP2350A, but input 8 on RP2350B, which has eight external channels rather
// than four.
//
// This deliberately does not use the SDK's ADC_TEMPERATURE_CHANNEL_NUM. That
// derives from NUM_ADC_CHANNELS, which <pico.h> settles from a board header, and
// arduino-pico supplies a fixed B-die one for every RP2350 build. The real die
// is only declared later, by the variant's pins_arduino.h, so the SDK constant
// reads 8 on A-die boards. PICO_RP2350A itself is correct by the time this file
// is compiled, on both arduino-pico and pico-sdk builds.
#if defined(PICO_RP2350) && !defined(PICO_RP2350A)
#error "PICO_RP2350A is not defined, so the RP2350 die is unknown and the temperature ADC channel cannot be chosen"
#endif
#if defined(PICO_RP2350) && !PICO_RP2350A
static constexpr uint8_t TEMPERATURE_ADC_INPUT = 8;
#else
static constexpr uint8_t TEMPERATURE_ADC_INPUT = 4;
#endif
void ADCSensor::setup() {
static bool initialized = false;
@@ -52,7 +71,7 @@ float ADCSensor::sample() {
if (this->is_temperature_) {
adc_set_temp_sensor_enabled(true);
delay(1);
adc_select_input(4);
adc_select_input(TEMPERATURE_ADC_INPUT);
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
raw = adc_read();
+1 -1
View File
@@ -7,7 +7,7 @@
namespace esphome::adc {
static const char *const TAG = "adc.zephyr";
static const char *const TAG = "adc";
void ADCSensor::setup() {
if (!adc_is_ready_dt(this->channel_)) {
+11 -3
View File
@@ -52,7 +52,7 @@ _attenuation = cv.enum(ATTENUATION_MODES, lower=True)
_sampling_mode = cv.enum(SAMPLING_MODES, lower=True)
def validate_config(config):
def validate_config(config: ConfigType) -> ConfigType:
if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto":
raise cv.Invalid("Automatic attenuation cannot be used when raw output is set")
@@ -67,6 +67,13 @@ def validate_config(config):
# Alter value here so `config` command prints the recommended change
config[CONF_ATTENUATION] = _attenuation("12db")
# Remove before 2027.2.0
if config[CONF_PIN] == "TEMPERATURE":
_LOGGER.warning(
"[adc] `pin: TEMPERATURE` is deprecated, use the `internal_temperature` "
"sensor platform instead. Will be removed in 2027.2.0"
)
return config
@@ -113,7 +120,7 @@ CONFIG_SCHEMA = cv.All(
CONF_ADC_CHANNEL_ID = "adc_channel_id"
def _overlay_io_channels():
def _overlay_io_channels() -> str:
channel_count = CORE.data[CONF_ADC_CHANNEL_ID]
entries = ", ".join(f"<&adc {channel_id}>" for channel_id in range(channel_count))
return f"""
@@ -125,7 +132,7 @@ def _overlay_io_channels():
"""
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await sensor.register_sensor(var, config)
@@ -133,6 +140,7 @@ async def to_code(config):
if config[CONF_PIN] == "VCC":
cg.add_define("USE_ADC_SENSOR_VCC")
elif config[CONF_PIN] == "TEMPERATURE":
# Remove before 2027.2.0
cg.add(var.set_is_temperature())
elif not CORE.is_nrf52 or config[CONF_PIN][CONF_NUMBER] not in EXTRA_ADC:
pin = await cg.gpio_pin_expression(config[CONF_PIN])
+16 -3
View File
@@ -17,6 +17,9 @@ from esphome.const import (
UNIT_OHM,
UNIT_PARTS_PER_BILLION,
)
from esphome.core import ID
from esphome.cpp_generator import MockObj, TemplateArgsType
from esphome.types import ConfigType
CONF_RESISTANCE = "resistance"
@@ -62,7 +65,7 @@ CONFIG_SCHEMA = (
FINAL_VALIDATE_SCHEMA = i2c.final_validate_device_schema("ags10", max_frequency="15khz")
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
@@ -94,7 +97,12 @@ AGS10_NEW_I2C_ADDRESS_SCHEMA = cv.maybe_simple_value(
AGS10_NEW_I2C_ADDRESS_SCHEMA,
synchronous=True,
)
async def ags10newi2caddress_to_code(config, action_id, template_arg, args):
async def ags10newi2caddress_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
address = await cg.templatable(config[CONF_ADDRESS], args, cg.uint8)
@@ -126,7 +134,12 @@ AGS10_SET_ZERO_POINT_SCHEMA = cv.Schema(
AGS10_SET_ZERO_POINT_SCHEMA,
synchronous=True,
)
async def ags10setzeropoint_to_code(config, action_id, template_arg, args):
async def ags10setzeropoint_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
mode = await cg.templatable(
+10 -2
View File
@@ -4,6 +4,9 @@ from esphome.components import i2c
from esphome.components.audio_dac import AudioDac
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_MODE
from esphome.core import ID
from esphome.cpp_generator import MockObj, TemplateArgsType
from esphome.types import ConfigType
CODEOWNERS = ["@kbx81"]
DEPENDENCIES = ["i2c"]
@@ -39,7 +42,12 @@ SET_AUTO_MUTE_ACTION_SCHEMA = cv.maybe_simple_value(
SET_AUTO_MUTE_ACTION_SCHEMA,
synchronous=True,
)
async def aic3204_set_volume_to_code(config, action_id, template_arg, args):
async def aic3204_set_volume_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
@@ -49,7 +57,7 @@ async def aic3204_set_volume_to_code(config, action_id, template_arg, args):
return var
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
+12 -9
View File
@@ -1,23 +1,26 @@
import esphome.codegen as cg
from esphome.components import esp32_ble_tracker
from esphome.components import ble_device_base
import esphome.config_validation as cv
from esphome.const import CONF_ID
DEPENDENCIES = ["esp32_ble_tracker"]
AUTO_LOAD = ["ble_device_base"]
CODEOWNERS = ["@jeromelaban"]
airthings_ble_ns = cg.esphome_ns.namespace("airthings_ble")
AirthingsListener = airthings_ble_ns.class_(
"AirthingsListener", esp32_ble_tracker.ESPBTDeviceListener
"AirthingsListener", ble_device_base.ESPBTDeviceListener
)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(AirthingsListener),
}
).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
CONFIG_SCHEMA = cv.All(
ble_device_base.rename_legacy_hub_id("airthings_ble"),
cv.Schema(
{
cv.GenerateID(): cv.declare_id(AirthingsListener),
}
).extend(ble_device_base.BLE_DEVICE_SCHEMA),
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await esp32_ble_tracker.register_ble_device(var, config)
await ble_device_base.register_ble_device(var, config)
@@ -2,15 +2,13 @@
#include "esphome/core/log.h"
#include <cinttypes>
#ifdef USE_ESP32
namespace esphome::airthings_ble {
static const char *const TAG = "airthings_ble";
bool AirthingsListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
bool AirthingsListener::parse_device(const ble_device_base::ESPBTDevice &device) {
for (auto &it : device.get_manufacturer_datas()) {
if (it.uuid == esp32_ble_tracker::ESPBTUUID::from_uint32(0x0334)) {
if (it.uuid == ble_device_base::ESPBTUUID::from_uint32(0x0334)) {
if (it.data.size() < 4)
continue;
@@ -29,5 +27,3 @@ bool AirthingsListener::parse_device(const esp32_ble_tracker::ESPBTDevice &devic
}
} // namespace esphome::airthings_ble
#endif
@@ -1,17 +1,13 @@
#pragma once
#ifdef USE_ESP32
#include "esphome/core/component.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/ble_device_base/ble_device.h"
namespace esphome::airthings_ble {
class AirthingsListener final : public esp32_ble_tracker::ESPBTDeviceListener {
class AirthingsListener final : public ble_device_base::ESPBTDeviceListener {
public:
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
bool parse_device(const ble_device_base::ESPBTDevice &device) override;
};
} // namespace esphome::airthings_ble
#endif
+5
View File
@@ -13,8 +13,13 @@
import esphome.components.image as espImage
import esphome.config_validation as cv
from . import image as animation_image
from .image import ANIMATION_CONFIG_SCHEMA, setup_animation
# The deprecated top-level `animation:` shim gets the same batched
# downloads as the `image:` platform form.
PREFETCH_FILES = animation_image.PREFETCH_FILES
AUTO_LOAD = ["image", "file"]
CODEOWNERS = ["@syndlex"]
DEPENDENCIES = ["display"]
+13 -1
View File
@@ -1,13 +1,20 @@
from esphome import automation
import esphome.codegen as cg
from esphome.components.const import CONF_LOOP
from esphome.components.file import image as file_image
from esphome.components.file.image import image_schema, write_image
from esphome.components.image import Image_, validate_settings
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_REPEAT
from esphome.core import ID
from esphome.cpp_generator import MockObj, TemplateArgsType
from esphome.types import ConfigType
CODEOWNERS = ["@syndlex"]
# The animation platform shares the file platform's remote file handling,
# including its batch-download hook.
PREFETCH_FILES = file_image.PREFETCH_FILES
AUTO_LOAD = ["file"]
DEPENDENCIES = ["display"]
@@ -74,7 +81,12 @@ SET_FRAME_SCHEMA = cv.Schema(
@automation.register_action(
"animation.set_frame", SetFrameAction, SET_FRAME_SCHEMA, synchronous=True
)
async def animation_action_to_code(config, action_id, template_arg, args):
async def animation_action_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
+2 -1
View File
@@ -2,6 +2,7 @@ import esphome.codegen as cg
from esphome.components import i2c
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.types import ConfigType
DEPENDENCIES = ["i2c"]
MULTI_CONF = True
@@ -57,7 +58,7 @@ CONFIG_SCHEMA = (
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
+2 -1
View File
@@ -2,6 +2,7 @@ import esphome.codegen as cg
from esphome.components import binary_sensor
import esphome.config_validation as cv
from esphome.const import CONF_DIRECTION, DEVICE_CLASS_MOVING
from esphome.types import ConfigType
from . import APDS9960, CONF_APDS9960_ID
@@ -19,7 +20,7 @@ CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_APDS9960_ID])
var = await binary_sensor.new_binary_sensor(config)
func = getattr(hub, f"set_{config[CONF_DIRECTION]}_direction_binary_sensor")
+2 -1
View File
@@ -7,6 +7,7 @@ from esphome.const import (
STATE_CLASS_MEASUREMENT,
UNIT_PERCENT,
)
from esphome.types import ConfigType
from . import APDS9960, CONF_APDS9960_ID
@@ -27,7 +28,7 @@ CONFIG_SCHEMA = sensor.sensor_schema(
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_APDS9960_ID])
var = await sensor.new_sensor(config)
func = getattr(hub, f"set_{config[CONF_TYPE]}_sensor")
+27 -11
View File
@@ -1,5 +1,6 @@
import base64
import logging
from typing import Any
from esphome import automation
from esphome.automation import Condition
@@ -129,7 +130,7 @@ def _register_provisioning_source(config: ConfigType) -> ConfigType:
return config
def validate_encryption_key(value):
def validate_encryption_key(value: Any) -> str:
value = cv.string_strict(value)
try:
decoded = base64.b64decode(value, validate=True)
@@ -217,7 +218,7 @@ def _auto_detect_supports_response(config: ConfigType) -> ConfigType:
return config
def _validate_supports_response(value):
def _validate_supports_response(value: Any) -> str:
"""Validate supports_response after auto-detection has set the value."""
return cv.enum(SUPPORTS_RESPONSE_OPTIONS, lower=True)(value)
@@ -256,7 +257,7 @@ ENCRYPTION_SCHEMA = cv.Schema(
)
def _encryption_schema(config):
def _encryption_schema(config: ConfigType | None) -> ConfigType:
if config is None:
config = {}
return ENCRYPTION_SCHEMA(config)
@@ -393,7 +394,7 @@ async def to_code(config: ConfigType) -> None:
if actions := config.get(CONF_ACTIONS, []):
# Collect all triggers first, then register all at once with initializer_list
triggers: list[cg.Pvariable] = []
triggers: list[cg.MockObj] = []
for conf in actions:
func_args: list[tuple[MockObj, str]] = []
service_template_args: list[MockObj] = [] # User service argument types
@@ -497,7 +498,7 @@ async def to_code(config: ConfigType) -> None:
# and plaintext disabled. Only a factory reset can remove it.
cg.add_define("USE_API_PLAINTEXT")
cg.add_define("USE_API_NOISE")
cg.add_library("esphome/noise-c", "0.1.11")
cg.add_library("esphome/noise-c", "0.1.21")
# Enable optimized memzero/memcmp in libsodium instead of volatile byte loops
cg.add_build_flag("-DHAVE_WEAK_SYMBOLS=1")
cg.add_build_flag("-DHAVE_INLINE_ASM=1")
@@ -581,7 +582,7 @@ async def homeassistant_service_to_code(
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
):
) -> MockObj:
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
serv = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, serv, False)
@@ -647,7 +648,7 @@ async def homeassistant_service_to_code(
return var
def validate_homeassistant_event(value):
def validate_homeassistant_event(value: Any) -> str:
value = cv.string(value)
if not value.startswith("esphome."):
raise cv.Invalid(
@@ -676,7 +677,12 @@ HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema(
HOMEASSISTANT_EVENT_ACTION_SCHEMA,
synchronous=True,
)
async def homeassistant_event_to_code(config, action_id, template_arg, args):
async def homeassistant_event_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
serv = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, serv, True)
@@ -724,7 +730,12 @@ HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value(
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA,
synchronous=True,
)
async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args):
async def homeassistant_tag_scanned_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
serv = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, serv, True)
@@ -740,7 +751,7 @@ CONF_SUCCESS = "success"
CONF_ERROR_MESSAGE = "error_message"
def _validate_api_respond_data(config):
def _validate_api_respond_data(config: ConfigType) -> ConfigType:
"""Set flag during validation so AUTO_LOAD can include json component."""
if CONF_DATA in config:
CORE.data.setdefault(DOMAIN, {})[CONF_CAPTURE_RESPONSE] = True
@@ -824,7 +835,12 @@ API_CONNECTED_CONDITION_SCHEMA = cv.Schema(
@automation.register_condition(
"api.connected", APIConnectedCondition, API_CONNECTED_CONDITION_SCHEMA
)
async def api_connected_to_code(config, condition_id, template_arg, args):
async def api_connected_to_code(
config: ConfigType,
condition_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
var = cg.new_Pvariable(condition_id, template_arg)
templ = await cg.templatable(config[CONF_STATE_SUBSCRIPTION_ONLY], args, cg.bool_)
cg.add(var.set_state_subscription_only(templ))
+94 -22
View File
@@ -19,6 +19,7 @@ service APIConnection {
rpc device_info (DeviceInfoRequest) returns (DeviceInfoResponse) {
option (needs_authentication) = false;
}
rpc device_capabilities (DeviceCapabilitiesRequest) returns (DeviceCapabilitiesResponse) {}
rpc list_entities (ListEntitiesRequest) returns (void) {}
rpc subscribe_states (SubscribeStatesRequest) returns (void) {}
rpc subscribe_logs (SubscribeLogsRequest) returns (void) {}
@@ -243,6 +244,12 @@ message SerialProxyInfo {
// model = 127 (core/config.BOARD_MAX_LENGTH, validated in platform schemas)
// project_name/project_version = 127 (core/config.PROJECT_MAX_LENGTH)
// suggested_area = 120 (core/config.FRIENDLY_NAME_MAX_LEN via AREA_SCHEMA)
//
// Some fields below are marked "Superseded by DeviceCapabilitiesResponse". They
// have moved to that message as of API 1.15, but are still sent here so that
// older clients keep working. Do NOT mark them (deprecated) until the removal
// release: in this repo (deprecated) makes the generator drop the field
// entirely, so the device would stop sending it.
message DeviceInfoResponse {
option (id) = 10;
option (source) = SOURCE_SERVER;
@@ -280,6 +287,8 @@ message DeviceInfoResponse {
// Deprecated in API version 1.9
uint32 legacy_bluetooth_proxy_version = 11 [deprecated=true, (field_ifdef) = "USE_BLUETOOTH_PROXY"];
// Superseded by DeviceCapabilitiesResponse.bluetooth_proxy as of API 1.15.
uint32 bluetooth_proxy_feature_flags = 15 [(field_ifdef) = "USE_BLUETOOTH_PROXY"];
string manufacturer = 12 [(max_data_length) = 20, (force) = true];
@@ -288,11 +297,14 @@ message DeviceInfoResponse {
// Deprecated in API version 1.10
uint32 legacy_voice_assistant_version = 14 [deprecated=true, (field_ifdef) = "USE_VOICE_ASSISTANT"];
// Superseded by DeviceCapabilitiesResponse.voice_assistant as of API 1.15.
uint32 voice_assistant_feature_flags = 17 [(field_ifdef) = "USE_VOICE_ASSISTANT"];
string suggested_area = 16 [(max_data_length) = 120, (force) = true, (field_ifdef) = "USE_AREAS"];
// The Bluetooth mac address of the device. For example "AC:BC:32:89:0E:AA"
// Superseded by DeviceCapabilitiesResponse.bluetooth_proxy.mac_address as of API 1.15.
string bluetooth_mac_address = 18 [(max_data_length) = 17, (force) = true, (field_ifdef) = "USE_BLUETOOTH_PROXY"];
// Supports receiving and saving api encryption key
@@ -305,10 +317,13 @@ message DeviceInfoResponse {
AreaInfo area = 22 [(field_ifdef) = "USE_AREAS"];
// Indicates if Z-Wave proxy support is available and features supported
// Superseded by DeviceCapabilitiesResponse.zwave_proxy as of API 1.15.
uint32 zwave_proxy_feature_flags = 23 [(field_ifdef) = "USE_ZWAVE_PROXY"];
// Superseded by DeviceCapabilitiesResponse.zwave_proxy as of API 1.15.
uint32 zwave_home_id = 24 [(field_ifdef) = "USE_ZWAVE_PROXY"];
// Serial proxy instance metadata
// Superseded by DeviceCapabilitiesResponse.serial_proxies as of API 1.15.
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
@@ -317,6 +332,63 @@ message DeviceInfoResponse {
bool api_encryption_provisionable = 26 [(field_ifdef) = "USE_API_NOISE"];
}
// ==================== DEVICE CAPABILITIES ====================
// Asks the device which optional features it supports.
//
// This message exists so that DeviceInfoResponse does not have to keep growing
// a flat list of feature flags. DeviceInfoResponse is served before
// authentication, so it is limited to identity information. Capabilities are
// only served on an authenticated connection (encrypted as well, when
// encryption is configured).
//
// Clients that see api_version >= 1.15 should read these values from
// DeviceCapabilitiesResponse and ignore the matching DeviceInfoResponse fields.
// Older clients keep reading DeviceInfoResponse, which still carries the same
// values, so this is not a breaking change.
message DeviceCapabilitiesRequest {
option (id) = 149;
option (source) = SOURCE_CLIENT;
// Empty
}
// Each feature gets its own sub-message so that it can gain fields over time
// without crowding the top-level field numbering.
//
// Note: a sub-message whose fields are all at their default value is not sent
// at all, so the presence of a sub-message is not a reliable test for "this
// feature is compiled in". Clients should test a value inside it, for example
// a non-zero feature_flags, exactly as they do today with DeviceInfoResponse.
message BluetoothProxyCapabilities {
// Bitmask of the features this proxy supports
uint32 feature_flags = 1;
// The Bluetooth mac address of the device. For example "AC:BC:32:89:0E:AA"
string mac_address = 2 [(max_data_length) = 17, (force) = true];
}
message VoiceAssistantCapabilities {
// Bitmask of the features this voice assistant supports
uint32 feature_flags = 1;
}
message ZWaveProxyCapabilities {
// Bitmask of the features this proxy supports
uint32 feature_flags = 1;
uint32 home_id = 2;
}
message DeviceCapabilitiesResponse {
option (id) = 150;
option (source) = SOURCE_SERVER;
BluetoothProxyCapabilities bluetooth_proxy = 1 [(field_ifdef) = "USE_BLUETOOTH_PROXY"];
VoiceAssistantCapabilities voice_assistant = 2 [(field_ifdef) = "USE_VOICE_ASSISTANT"];
ZWaveProxyCapabilities zwave_proxy = 3 [(field_ifdef) = "USE_ZWAVE_PROXY"];
repeated SerialProxyInfo serial_proxies = 4
[(field_ifdef) = "USE_SERIAL_PROXY", (fixed_array_size_define) = "SERIAL_PROXY_COUNT"];
}
message ListEntitiesRequest {
option (id) = 11;
option (source) = SOURCE_CLIENT;
@@ -1688,7 +1760,7 @@ enum BluetoothDeviceRequestType {
message BluetoothDeviceRequest {
option (id) = 68;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_BLUETOOTH_PROXY";
option (ifdef) = "USE_BLUETOOTH_PROXY_CONNECTIONS";
uint64 address = 1;
BluetoothDeviceRequestType request_type = 2;
@@ -1699,7 +1771,7 @@ message BluetoothDeviceRequest {
message BluetoothDeviceConnectionResponse {
option (id) = 69;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BLUETOOTH_PROXY";
option (ifdef) = "USE_BLUETOOTH_PROXY_CONNECTIONS";
uint64 address = 1;
bool connected = 2;
@@ -1710,7 +1782,7 @@ message BluetoothDeviceConnectionResponse {
message BluetoothGATTGetServicesRequest {
option (id) = 70;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_BLUETOOTH_PROXY";
option (ifdef) = "USE_BLUETOOTH_PROXY_CONNECTIONS";
uint64 address = 1;
}
@@ -1754,7 +1826,7 @@ message BluetoothGATTService {
message BluetoothGATTGetServicesResponse {
option (id) = 71;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BLUETOOTH_PROXY";
option (ifdef) = "USE_BLUETOOTH_PROXY_CONNECTIONS";
uint64 address = 1;
repeated BluetoothGATTService services = 2;
@@ -1763,7 +1835,7 @@ message BluetoothGATTGetServicesResponse {
message BluetoothGATTGetServicesDoneResponse {
option (id) = 72;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BLUETOOTH_PROXY";
option (ifdef) = "USE_BLUETOOTH_PROXY_CONNECTIONS";
uint64 address = 1;
}
@@ -1771,7 +1843,7 @@ message BluetoothGATTGetServicesDoneResponse {
message BluetoothGATTReadRequest {
option (id) = 73;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_BLUETOOTH_PROXY";
option (ifdef) = "USE_BLUETOOTH_PROXY_CONNECTIONS";
uint64 address = 1;
uint32 handle = 2;
@@ -1780,7 +1852,7 @@ message BluetoothGATTReadRequest {
message BluetoothGATTReadResponse {
option (id) = 74;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BLUETOOTH_PROXY";
option (ifdef) = "USE_BLUETOOTH_PROXY_CONNECTIONS";
uint64 address = 1;
uint32 handle = 2;
@@ -1792,7 +1864,7 @@ message BluetoothGATTReadResponse {
message BluetoothGATTWriteRequest {
option (id) = 75;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_BLUETOOTH_PROXY";
option (ifdef) = "USE_BLUETOOTH_PROXY_CONNECTIONS";
uint64 address = 1;
uint32 handle = 2;
@@ -1804,7 +1876,7 @@ message BluetoothGATTWriteRequest {
message BluetoothGATTReadDescriptorRequest {
option (id) = 76;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_BLUETOOTH_PROXY";
option (ifdef) = "USE_BLUETOOTH_PROXY_CONNECTIONS";
uint64 address = 1;
uint32 handle = 2;
@@ -1813,7 +1885,7 @@ message BluetoothGATTReadDescriptorRequest {
message BluetoothGATTWriteDescriptorRequest {
option (id) = 77;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_BLUETOOTH_PROXY";
option (ifdef) = "USE_BLUETOOTH_PROXY_CONNECTIONS";
uint64 address = 1;
uint32 handle = 2;
@@ -1824,7 +1896,7 @@ message BluetoothGATTWriteDescriptorRequest {
message BluetoothGATTNotifyRequest {
option (id) = 78;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_BLUETOOTH_PROXY";
option (ifdef) = "USE_BLUETOOTH_PROXY_CONNECTIONS";
uint64 address = 1;
uint32 handle = 2;
@@ -1834,7 +1906,7 @@ message BluetoothGATTNotifyRequest {
message BluetoothGATTNotifyDataResponse {
option (id) = 79;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BLUETOOTH_PROXY";
option (ifdef) = "USE_BLUETOOTH_PROXY_CONNECTIONS";
uint64 address = 1;
uint32 handle = 2;
@@ -1845,13 +1917,13 @@ message BluetoothGATTNotifyDataResponse {
message SubscribeBluetoothConnectionsFreeRequest {
option (id) = 80;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_BLUETOOTH_PROXY";
option (ifdef) = "USE_BLUETOOTH_PROXY_CONNECTIONS";
}
message BluetoothConnectionsFreeResponse {
option (id) = 81;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BLUETOOTH_PROXY";
option (ifdef) = "USE_BLUETOOTH_PROXY_CONNECTIONS";
uint32 free = 1;
uint32 limit = 2;
@@ -1864,7 +1936,7 @@ message BluetoothConnectionsFreeResponse {
message BluetoothGATTErrorResponse {
option (id) = 82;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BLUETOOTH_PROXY";
option (ifdef) = "USE_BLUETOOTH_PROXY_CONNECTIONS";
uint64 address = 1;
uint32 handle = 2;
@@ -1874,7 +1946,7 @@ message BluetoothGATTErrorResponse {
message BluetoothGATTWriteResponse {
option (id) = 83;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BLUETOOTH_PROXY";
option (ifdef) = "USE_BLUETOOTH_PROXY_CONNECTIONS";
uint64 address = 1;
uint32 handle = 2;
@@ -1883,7 +1955,7 @@ message BluetoothGATTWriteResponse {
message BluetoothGATTNotifyResponse {
option (id) = 84;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BLUETOOTH_PROXY";
option (ifdef) = "USE_BLUETOOTH_PROXY_CONNECTIONS";
uint64 address = 1;
uint32 handle = 2;
@@ -1892,7 +1964,7 @@ message BluetoothGATTNotifyResponse {
message BluetoothDevicePairingResponse {
option (id) = 85;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BLUETOOTH_PROXY";
option (ifdef) = "USE_BLUETOOTH_PROXY_CONNECTIONS";
uint64 address = 1;
bool paired = 2;
@@ -1902,7 +1974,7 @@ message BluetoothDevicePairingResponse {
message BluetoothDeviceUnpairingResponse {
option (id) = 86;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BLUETOOTH_PROXY";
option (ifdef) = "USE_BLUETOOTH_PROXY_CONNECTIONS";
uint64 address = 1;
bool success = 2;
@@ -1918,7 +1990,7 @@ message UnsubscribeBluetoothLEAdvertisementsRequest {
message BluetoothDeviceClearCacheResponse {
option (id) = 88;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BLUETOOTH_PROXY";
option (ifdef) = "USE_BLUETOOTH_PROXY_CONNECTIONS";
uint64 address = 1;
bool success = 2;
@@ -2735,7 +2807,7 @@ message SerialProxyRequestResponse {
message BluetoothSetConnectionParamsRequest {
option (id) = 145;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_BLUETOOTH_PROXY";
option (ifdef) = "USE_BLUETOOTH_PROXY_CONNECTIONS";
uint64 address = 1;
uint32 min_interval = 2; // units of 1.25ms
@@ -2747,7 +2819,7 @@ message BluetoothSetConnectionParamsRequest {
message BluetoothSetConnectionParamsResponse {
option (id) = 146;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BLUETOOTH_PROXY";
option (ifdef) = "USE_BLUETOOTH_PROXY_CONNECTIONS";
uint64 address = 1;
int32 error = 2;
+118 -32
View File
@@ -23,6 +23,7 @@
#include "esphome/core/application.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/version.h"
#ifdef USE_PROVISIONING
@@ -88,6 +89,13 @@ static_assert(ESPHOME_DEVICE_NAME_MAX_LEN <= 31, "Update max_data_length for nam
static_assert(ESPHOME_FRIENDLY_NAME_MAX_LEN <= 120, "Update max_data_length for friendly_name in api.proto");
static const char *const TAG = "api.connection";
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_WARN
void log_dropped_message(const char *tag, int line, const LogString *what) {
esp_log_printf_(ESPHOME_LOG_LEVEL_WARN, tag, line, ESPHOME_LOG_FORMAT("%s dropped, TCP buffer full"),
LOG_STR_ARG(what));
}
#endif
#ifdef USE_CAMERA
static const int CAMERA_STOP_STREAM = 5000;
#endif
@@ -152,11 +160,6 @@ APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *pa
#else
#error "No frame helper defined"
#endif
#ifdef USE_CAMERA
if (camera::Camera::instance() != nullptr) {
this->image_reader_ = std::unique_ptr<camera::CameraImageReader>{camera::Camera::instance()->create_image_reader()};
}
#endif
}
void APIConnection::start() {
@@ -440,7 +443,7 @@ void APIConnection::on_disconnect_response() {
uint16_t APIConnection::fill_and_encode_entity_state(EntityBase *entity, StateResponseProtoMessage &msg,
CalculateSizeFn size_fn, MessageEncodeFn encode_fn,
APIConnection *conn, uint32_t remaining_size) {
msg.key = entity->get_entity_key();
msg.key = entity->get_object_id_hash();
#ifdef USE_DEVICES
msg.device_id = entity->get_device_id();
#endif
@@ -451,7 +454,7 @@ uint16_t APIConnection::fill_and_encode_entity_info(EntityBase *entity, InfoResp
CalculateSizeFn size_fn, MessageEncodeFn encode_fn,
APIConnection *conn, uint32_t remaining_size) {
// Set common fields that are shared by all entity types
msg.key = entity->get_entity_key();
msg.key = entity->get_object_id_hash();
if (entity->has_own_name()) {
msg.name = entity->get_name();
@@ -1132,6 +1135,7 @@ void APIConnection::try_send_camera_image_() {
if (!this->image_reader_)
return;
const auto *cam = camera::Camera::instance();
// Send as many chunks as possible without blocking
while (this->image_reader_->available()) {
if (!this->helper_->can_write_without_blocking())
@@ -1141,11 +1145,11 @@ void APIConnection::try_send_camera_image_() {
bool done = this->image_reader_->available() == to_send;
CameraImageResponse msg;
msg.key = camera::Camera::instance()->get_entity_key();
msg.key = cam->get_object_id_hash();
msg.set_data(this->image_reader_->peek_data_buffer(), to_send);
msg.done = done;
#ifdef USE_DEVICES
msg.device_id = camera::Camera::instance()->get_device_id();
msg.device_id = cam->get_device_id();
#endif
if (!this->send_message(msg)) {
@@ -1161,15 +1165,19 @@ void APIConnection::try_send_camera_image_() {
void APIConnection::set_camera_state(std::shared_ptr<camera::CameraImage> image) {
if (!this->flags_.state_subscription)
return;
if (!this->image_reader_)
if (this->image_reader_ && this->image_reader_->available())
return;
if (this->image_reader_->available())
if (!image->was_requested_by(esphome::camera::API_REQUESTER) && !image->was_requested_by(esphome::camera::IDLE))
return;
if (image->was_requested_by(esphome::camera::API_REQUESTER) || image->was_requested_by(esphome::camera::IDLE)) {
this->image_reader_->set_image(std::move(image));
// Try to send immediately to reduce latency
this->try_send_camera_image_();
if (!this->image_reader_) {
// Created on the first image this connection will send, so connections
// that never receive one never pay for a reader. Only a registered
// camera's listener can reach this, so instance() is non-null here.
this->image_reader_ = std::unique_ptr<camera::CameraImageReader>{camera::Camera::instance()->create_image_reader()};
}
this->image_reader_->set_image(std::move(image));
// Try to send immediately to reduce latency
this->try_send_camera_image_();
}
uint16_t APIConnection::try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
auto *camera = static_cast<camera::Camera *>(entity);
@@ -1235,6 +1243,7 @@ void APIConnection::on_subscribe_bluetooth_le_advertisements_request(
void APIConnection::on_unsubscribe_bluetooth_le_advertisements_request() {
bluetooth_proxy::global_bluetooth_proxy->unsubscribe_api_connection(this);
}
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
void APIConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) {
bluetooth_proxy::global_bluetooth_proxy->bluetooth_device_request(msg);
}
@@ -1268,13 +1277,15 @@ void APIConnection::on_subscribe_bluetooth_connections_free_request() {
}
}
void APIConnection::on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &msg) {
bluetooth_proxy::global_bluetooth_proxy->bluetooth_set_connection_params(msg);
}
#endif
void APIConnection::on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) {
bluetooth_proxy::global_bluetooth_proxy->bluetooth_scanner_set_mode(
msg.mode == enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE);
}
void APIConnection::on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &msg) {
bluetooth_proxy::global_bluetooth_proxy->bluetooth_set_connection_params(msg);
}
#endif
#ifdef USE_VOICE_ASSISTANT
@@ -1532,7 +1543,13 @@ void APIConnection::on_infrared_rf_transmit_raw_timings_request(const InfraredRF
#endif
#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY)
void APIConnection::send_infrared_rf_receive_event(const InfraredRFReceiveEvent &msg) { this->send_message(msg); }
void APIConnection::send_infrared_rf_receive_event(const InfraredRFReceiveEvent &msg) {
if (!this->send_message(msg)) {
// V: fires per decoded frame with no subscription gate, so a warning
// would flood the congested link it reports on.
ESP_LOGV(TAG, "IR/RF event dropped, TCP buffer full");
}
}
#endif
#ifdef USE_SERIAL_PROXY
@@ -1574,7 +1591,9 @@ void APIConnection::on_serial_proxy_get_modem_pins_request(const SerialProxyGetM
SerialProxyGetModemPinsResponse resp{};
resp.instance = msg.instance;
resp.line_states = proxies[msg.instance]->get_modem_pins();
this->send_message(resp);
if (!this->send_message(resp)) {
API_LOG_MSG_DROPPED(TAG, "Serial proxy response");
}
}
void APIConnection::on_serial_proxy_request(const SerialProxyRequest &msg) {
@@ -1606,7 +1625,9 @@ void APIConnection::on_serial_proxy_request(const SerialProxyRequest &msg) {
resp.status = enums::SERIAL_PROXY_STATUS_ERROR;
break;
}
this->send_message(resp);
if (!this->send_message(resp)) {
API_LOG_MSG_DROPPED(TAG, "Serial proxy response");
}
break;
}
default:
@@ -1615,7 +1636,11 @@ void APIConnection::on_serial_proxy_request(const SerialProxyRequest &msg) {
}
}
void APIConnection::send_serial_proxy_data(const SerialProxyDataReceived &msg) { this->send_message(msg); }
void APIConnection::send_serial_proxy_data(const SerialProxyDataReceived &msg) {
if (!this->send_message(msg)) {
ESP_LOGV(TAG, "Serial proxy data dropped, TCP buffer full");
}
}
#endif
#ifdef USE_INFRARED
@@ -1735,7 +1760,7 @@ bool APIConnection::send_hello_response_(const HelloRequest &msg) {
HelloResponse resp;
resp.api_version_major = 1;
resp.api_version_minor = 14;
resp.api_version_minor = 15;
// Send only the version string - the client only logs this for debugging and doesn't use it otherwise
resp.server_info = ESPHOME_VERSION_REF;
resp.name = StringRef(App.get_name());
@@ -1746,7 +1771,9 @@ bool APIConnection::send_hello_response_(const HelloRequest &msg) {
// 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);
if (!this->send_message(resp)) {
API_LOG_MSG_DROPPED(TAG, "Hello response");
}
DisconnectRequest req;
req.reason = enums::DISCONNECT_REASON_PROVISIONING_CLOSED;
return this->send_message(req);
@@ -1771,9 +1798,8 @@ bool APIConnection::send_device_info_response_() {
#ifdef USE_AREAS
resp.suggested_area = StringRef(App.get_area());
#endif
// Stack buffer for MAC address (XX:XX:XX:XX:XX:XX\0 = 18 bytes)
char mac_address[18];
uint8_t mac[6];
char mac_address[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
uint8_t mac[MAC_ADDRESS_SIZE];
get_mac_address_raw(mac);
format_mac_addr_upper(mac, mac_address);
resp.mac_address = StringRef(mac_address);
@@ -1849,8 +1875,7 @@ bool APIConnection::send_device_info_response_() {
#endif
#ifdef USE_BLUETOOTH_PROXY
resp.bluetooth_proxy_feature_flags = bluetooth_proxy::global_bluetooth_proxy->get_feature_flags();
// Stack buffer for Bluetooth MAC address (XX:XX:XX:XX:XX:XX\0 = 18 bytes)
char bluetooth_mac[18];
char bluetooth_mac[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_mac_address_pretty(bluetooth_mac);
resp.bluetooth_mac_address = StringRef(bluetooth_mac);
#endif
@@ -1904,6 +1929,35 @@ bool APIConnection::send_device_info_response_() {
return this->send_message(resp);
}
bool APIConnection::send_device_capabilities_response_() {
// These are the same values DeviceInfoResponse still reports for older clients. Keep the blocks
// below in sync with send_device_info_response_() until those copies are removed.
DeviceCapabilitiesResponse resp;
#ifdef USE_BLUETOOTH_PROXY
resp.bluetooth_proxy.feature_flags = bluetooth_proxy::global_bluetooth_proxy->get_feature_flags();
char bluetooth_mac[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_mac_address_pretty(bluetooth_mac);
resp.bluetooth_proxy.mac_address = StringRef(bluetooth_mac);
#endif
#ifdef USE_VOICE_ASSISTANT
resp.voice_assistant.feature_flags = voice_assistant::global_voice_assistant->get_feature_flags();
#endif
#ifdef USE_ZWAVE_PROXY
resp.zwave_proxy.feature_flags = zwave_proxy::global_zwave_proxy->get_feature_flags();
resp.zwave_proxy.home_id = zwave_proxy::global_zwave_proxy->get_home_id();
#endif
#ifdef USE_SERIAL_PROXY
size_t serial_proxy_index = 0;
for (auto const &proxy : App.get_serial_proxies()) {
if (serial_proxy_index >= SERIAL_PROXY_COUNT)
break;
auto &info = resp.serial_proxies[serial_proxy_index++];
info.name = StringRef(proxy->get_name());
info.port_type = proxy->get_port_type();
}
#endif
return this->send_message(resp);
}
void APIConnection::on_hello_request(const HelloRequest &msg) {
if (!this->send_hello_response_(msg)) {
this->on_fatal_error();
@@ -1925,6 +1979,11 @@ void APIConnection::on_device_info_request() {
this->on_fatal_error();
}
}
void APIConnection::on_device_capabilities_request() {
if (!this->send_device_capabilities_response_()) {
this->on_fatal_error();
}
}
#ifdef USE_API_HOMEASSISTANT_STATES
void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
@@ -2003,7 +2062,9 @@ void APIConnection::send_execute_service_response(uint32_t call_id, bool success
resp.call_id = call_id;
resp.success = success;
resp.error_message = error_message;
this->send_message(resp);
if (!this->send_message(resp)) {
API_LOG_MSG_DROPPED(TAG, "Action response");
}
}
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
void APIConnection::send_execute_service_response(uint32_t call_id, bool success, StringRef error_message,
@@ -2014,12 +2075,34 @@ void APIConnection::send_execute_service_response(uint32_t call_id, bool success
resp.error_message = error_message;
resp.response_data = response_data;
resp.response_data_len = response_data_len;
this->send_message(resp);
if (!this->send_message(resp)) {
API_LOG_MSG_DROPPED(TAG, "Action response");
}
}
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES
#endif
#ifdef USE_API_HOMEASSISTANT_SERVICES
bool APIConnection::send_homeassistant_action(const HomeassistantActionRequest &call) {
if (!this->flags_.service_call_subscription)
return false;
if (!this->send_message(call)) {
API_LOG_MSG_DROPPED(TAG, "Action request");
}
return true;
}
#endif // USE_API_HOMEASSISTANT_SERVICES
#ifdef USE_HOMEASSISTANT_TIME
void APIConnection::send_time_request() {
GetTimeRequest req;
if (!this->send_message(req)) {
API_LOG_MSG_DROPPED(TAG, "Time request");
}
}
#endif // USE_HOMEASSISTANT_TIME
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
void APIConnection::on_homeassistant_action_response(const HomeassistantActionResponse &msg) {
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
@@ -2094,7 +2177,10 @@ bool APIConnection::try_to_clear_buffer_slow_(bool log_out_of_space) {
if (this->helper_->can_write_without_blocking())
return true;
if (log_out_of_space) {
ESP_LOGV(TAG, "Cannot send message because of TCP buffer space");
// VV: refusals are either reported by the sending call site (naming what
// was lost) or retried without loss (the deferred batch), so this generic
// line only duplicates them.
ESP_LOGVV(TAG, "Cannot send message because of TCP buffer space");
}
return false;
}
+22 -13
View File
@@ -25,6 +25,7 @@
#include "esphome/components/esp8266/crash_handler.h"
#endif
#include "esphome/core/entity_base.h"
#include "esphome/core/log.h"
#include "esphome/core/string_ref.h"
#include <functional>
@@ -40,6 +41,16 @@ namespace esphome::api {
// Forward-declared to break the api_server.h cycle; full-type inlines are in api_connection_buffer.h.
class APIServer;
// One shared flash string for every refused-frame warning: send_message()
// fails as soon as the TCP buffer is full, and each caller only pays for its
// short name. The guard drops the helper and its arguments below WARN.
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_WARN
void log_dropped_message(const char *tag, int line, const LogString *what);
#define API_LOG_MSG_DROPPED(tag, what) esphome::api::log_dropped_message(tag, __LINE__, LOG_STR(what))
#else
#define API_LOG_MSG_DROPPED(tag, what)
#endif
// Keepalive timeout in milliseconds
static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000;
// Maximum number of entities to process in a single batch during initial state/info sending
@@ -169,12 +180,7 @@ class APIConnection final : public APIServerConnectionBase {
// 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 false;
this->send_message(call);
return true;
}
bool send_homeassistant_action(const HomeassistantActionRequest &call);
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
void on_homeassistant_action_response(const HomeassistantActionResponse &msg);
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
@@ -183,6 +189,7 @@ class APIConnection final : public APIServerConnectionBase {
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg);
void on_unsubscribe_bluetooth_le_advertisements_request();
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
void on_bluetooth_device_request(const BluetoothDeviceRequest &msg);
void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg);
void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg);
@@ -191,15 +198,13 @@ class APIConnection final : public APIServerConnectionBase {
void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg);
void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg);
void on_subscribe_bluetooth_connections_free_request();
void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg);
void on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &msg);
#endif
void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg);
#endif
#ifdef USE_HOMEASSISTANT_TIME
void send_time_request() {
GetTimeRequest req;
this->send_message(req);
}
void send_time_request();
#endif
#ifdef USE_VOICE_ASSISTANT
@@ -266,6 +271,7 @@ class APIConnection final : public APIServerConnectionBase {
void on_disconnect_request(const DisconnectRequest &msg);
void on_ping_request();
void on_device_info_request();
void on_device_capabilities_request();
void on_list_entities_request() { this->begin_iterator_(ActiveIterator::LIST_ENTITIES); }
void on_subscribe_states_request() {
this->flags_.state_subscription = true;
@@ -334,7 +340,9 @@ class APIConnection final : public APIServerConnectionBase {
// Function pointer type for type-erased size calculation
using CalculateSizeFn = uint32_t (*)(const void *);
template<typename T> bool send_message(const T &msg) {
/// Returns false as soon as the TCP buffer is full. Marked nodiscard so we
/// have no silent failures: every caller must handle (or log) a refusal.
template<typename T> [[nodiscard]] bool send_message(const T &msg) {
if constexpr (T::ESTIMATED_SIZE == 0) {
return this->send_message_(0, T::MESSAGE_TYPE, &encode_msg_noop, &msg);
} else {
@@ -385,10 +393,11 @@ class APIConnection final : public APIServerConnectionBase {
bool send_disconnect_response_();
bool send_ping_response_();
bool send_device_info_response_();
bool send_device_capabilities_response_();
#ifdef USE_API_NOISE
bool send_noise_encryption_set_key_response_(const NoiseEncryptionSetKeyRequest &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
bool send_subscribe_bluetooth_connections_free_response_();
#endif
#ifdef USE_VOICE_ASSISTANT
+2 -2
View File
@@ -149,7 +149,7 @@ class APIFrameHelper {
// holding data too long waiting for Nagle's timer causes buffer exhaustion
// and dropped messages.
//
// ESP32 (TCP_SND_BUF=4×MSS+) / RP2040 (8×MSS) / LibreTiny (4×MSS): 4 logs per cycle
// ESP32 (TCP_SND_BUF=4×MSS+) / RP2040 (4×MSS) / LibreTiny (4×MSS): 4 logs per cycle
// ESP8266 (2×MSS): 3 logs per cycle (tightest buffers)
//
// Flow (ESP32/RP2040/LT): Log 1 (Nagle on) -> Log 2 -> Log 3 -> Log 4 (NODELAY, flush)
@@ -312,7 +312,7 @@ class APIFrameHelper {
// Values 1..LOG_NAGLE_COUNT count log messages in the current Nagle batch.
// After LOG_NAGLE_COUNT logs, we flush by re-enabling NODELAY and resetting to 0.
// ESP8266 has the tightest TCP send buffer (2×MSS) and needs conservative batching.
// ESP32 (4×MSS+), RP2040 (8×MSS), and LibreTiny (4×MSS) can coalesce more.
// ESP32 (4×MSS+), RP2040 (4×MSS), and LibreTiny (4×MSS) can coalesce more.
#ifdef USE_ESP8266
static constexpr uint8_t LOG_NAGLE_COUNT = 2;
#else
@@ -591,18 +591,21 @@ APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) {
*/
APIError APINoiseFrameHelper::init_handshake_() {
int err;
memset(&nid_, 0, sizeof(nid_));
// const char *proto = "Noise_NNpsk0_25519_ChaChaPoly_SHA256";
// err = noise_protocol_name_to_id(&nid_, proto, strlen(proto));
nid_.pattern_id = NOISE_PATTERN_NN;
nid_.cipher_id = NOISE_CIPHER_CHACHAPOLY;
nid_.dh_id = NOISE_DH_CURVE25519;
nid_.prefix_id = NOISE_PREFIX_STANDARD;
nid_.hybrid_id = NOISE_DH_NONE;
nid_.hash_id = NOISE_HASH_SHA256;
nid_.modifier_ids[0] = NOISE_MODIFIER_PSK0;
// Noise_NNpsk0_25519_ChaChaPoly_SHA256, built on the stack:
// noise_handshakestate_new_by_id copies it, so a member would waste
// 104 bytes per connection, and a static const would sit in RAM on
// ESP8266 (.rodata is DRAM there).
const NoiseProtocolId nid = {
.prefix_id = NOISE_PREFIX_STANDARD,
.pattern_id = NOISE_PATTERN_NN,
.modifier_ids = {NOISE_MODIFIER_PSK0},
.dh_id = NOISE_DH_CURVE25519,
.cipher_id = NOISE_CIPHER_CHACHAPOLY,
.hash_id = NOISE_HASH_SHA256,
.hybrid_id = NOISE_DH_NONE,
};
err = noise_handshakestate_new_by_id(&handshake_, &nid_, NOISE_ROLE_RESPONDER);
err = noise_handshakestate_new_by_id(&handshake_, &nid, NOISE_ROLE_RESPONDER);
APIError aerr =
handle_noise_error_(err, LOG_STR("noise_handshakestate_new_by_id"), APIError::HANDSHAKESTATE_SETUP_FAILED);
if (aerr != APIError::OK)
@@ -63,9 +63,6 @@ class APINoiseFrameHelper final : public APIFrameHelper {
// Buffer for noise handshake prologue (released after handshake)
APIBuffer prologue_;
// NoiseProtocolId (size depends on implementation)
NoiseProtocolId nid_;
// Group small types together
// Fixed-size header buffer for noise protocol:
// 1 byte for indicator + 2 bytes for message size (16-bit value, not varint)
+81 -1
View File
@@ -241,6 +241,82 @@ uint32_t DeviceInfoResponse::calculate_size() const {
#endif
return size;
}
#ifdef USE_BLUETOOTH_PROXY
uint8_t *BluetoothProxyCapabilities::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
uint8_t *__restrict__ pos = buffer.get_pos();
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, this->feature_flags);
ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 18, this->mac_address);
return pos;
}
uint32_t BluetoothProxyCapabilities::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_uint32(1, this->feature_flags);
size += 2 + this->mac_address.size();
return size;
}
#endif
#ifdef USE_VOICE_ASSISTANT
uint8_t *VoiceAssistantCapabilities::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
uint8_t *__restrict__ pos = buffer.get_pos();
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, this->feature_flags);
return pos;
}
uint32_t VoiceAssistantCapabilities::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_uint32(1, this->feature_flags);
return size;
}
#endif
#ifdef USE_ZWAVE_PROXY
uint8_t *ZWaveProxyCapabilities::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
uint8_t *__restrict__ pos = buffer.get_pos();
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, this->feature_flags);
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, this->home_id);
return pos;
}
uint32_t ZWaveProxyCapabilities::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_uint32(1, this->feature_flags);
size += ProtoSize::calc_uint32(1, this->home_id);
return size;
}
#endif
uint8_t *DeviceCapabilitiesResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
uint8_t *__restrict__ pos = buffer.get_pos();
#ifdef USE_BLUETOOTH_PROXY
ProtoEncode::encode_optional_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 1, this->bluetooth_proxy);
#endif
#ifdef USE_VOICE_ASSISTANT
ProtoEncode::encode_optional_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 2, this->voice_assistant);
#endif
#ifdef USE_ZWAVE_PROXY
ProtoEncode::encode_optional_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 3, this->zwave_proxy);
#endif
#ifdef USE_SERIAL_PROXY
for (const auto &it : this->serial_proxies) {
ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 4, it);
}
#endif
return pos;
}
uint32_t DeviceCapabilitiesResponse::calculate_size() const {
uint32_t size = 0;
#ifdef USE_BLUETOOTH_PROXY
size += ProtoSize::calc_message(1, this->bluetooth_proxy.calculate_size());
#endif
#ifdef USE_VOICE_ASSISTANT
size += ProtoSize::calc_message(1, this->voice_assistant.calculate_size());
#endif
#ifdef USE_ZWAVE_PROXY
size += ProtoSize::calc_message(1, this->zwave_proxy.calculate_size());
#endif
#ifdef USE_SERIAL_PROXY
for (const auto &it : this->serial_proxies) {
size += ProtoSize::calc_message_force(1, it.calculate_size());
}
#endif
return size;
}
#ifdef USE_BINARY_SENSOR
uint8_t *ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
uint8_t *__restrict__ pos = buffer.get_pos();
@@ -2406,6 +2482,8 @@ BluetoothLERawAdvertisementsResponse::calculate_size() const {
}
return size;
}
#endif
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
bool BluetoothDeviceRequest::decode_varint(uint32_t field_id, proto_varint_value_t value) {
switch (field_id) {
case 1:
@@ -2782,6 +2860,8 @@ uint32_t BluetoothDeviceClearCacheResponse::calculate_size() const {
size += ProtoSize::calc_int32(1, this->error);
return size;
}
#endif
#ifdef USE_BLUETOOTH_PROXY
uint8_t *BluetoothScannerStateResponse::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->state));
@@ -4145,7 +4225,7 @@ uint32_t SerialProxyRequestResponse::calculate_size() const {
return size;
}
#endif
#ifdef USE_BLUETOOTH_PROXY
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
bool BluetoothSetConnectionParamsRequest::decode_varint(uint32_t field_id, proto_varint_value_t value) {
switch (field_id) {
case 1:
+76 -2
View File
@@ -225,7 +225,7 @@ enum MediaPlayerFormatPurpose : uint32_t {
MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT = 1,
};
#endif
#ifdef USE_BLUETOOTH_PROXY
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
enum BluetoothDeviceRequestType : uint32_t {
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0,
BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1,
@@ -235,6 +235,8 @@ enum BluetoothDeviceRequestType : uint32_t {
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5,
BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE = 6,
};
#endif
#ifdef USE_BLUETOOTH_PROXY
enum BluetoothScannerState : uint32_t {
BLUETOOTH_SCANNER_STATE_IDLE = 0,
BLUETOOTH_SCANNER_STATE_STARTING = 1,
@@ -600,6 +602,74 @@ class DeviceInfoResponse final : public ProtoMessage {
protected:
};
#ifdef USE_BLUETOOTH_PROXY
class BluetoothProxyCapabilities final : public ProtoMessage {
public:
uint32_t feature_flags{0};
StringRef mac_address{};
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:
};
#endif
#ifdef USE_VOICE_ASSISTANT
class VoiceAssistantCapabilities final : public ProtoMessage {
public:
uint32_t feature_flags{0};
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:
};
#endif
#ifdef USE_ZWAVE_PROXY
class ZWaveProxyCapabilities final : public ProtoMessage {
public:
uint32_t feature_flags{0};
uint32_t home_id{0};
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:
};
#endif
class DeviceCapabilitiesResponse final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 150;
static constexpr uint8_t ESTIMATED_SIZE = 102;
#ifdef HAS_PROTO_MESSAGE_DUMP
const LogString *message_name() const override { return LOG_STR("device_capabilities_response"); }
#endif
#ifdef USE_BLUETOOTH_PROXY
BluetoothProxyCapabilities bluetooth_proxy{};
#endif
#ifdef USE_VOICE_ASSISTANT
VoiceAssistantCapabilities voice_assistant{};
#endif
#ifdef USE_ZWAVE_PROXY
ZWaveProxyCapabilities zwave_proxy{};
#endif
#ifdef USE_SERIAL_PROXY
std::array<SerialProxyInfo, SERIAL_PROXY_COUNT> serial_proxies{};
#endif
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:
};
class ListEntitiesDoneResponse final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 19;
@@ -1931,6 +2001,8 @@ class BluetoothLERawAdvertisementsResponse final : public ProtoMessage {
protected:
};
#endif
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
class BluetoothDeviceRequest final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 68;
@@ -2316,6 +2388,8 @@ class BluetoothDeviceClearCacheResponse final : public ProtoMessage {
protected:
};
#endif
#ifdef USE_BLUETOOTH_PROXY
class BluetoothScannerStateResponse final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 126;
@@ -3290,7 +3364,7 @@ class SerialProxyRequestResponse final : public ProtoMessage {
protected:
};
#endif
#ifdef USE_BLUETOOTH_PROXY
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
class BluetoothSetConnectionParamsRequest final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 145;
+1 -1
View File
@@ -3,7 +3,7 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_BLUETOOTH_PROXY
#if defined(USE_BLUETOOTH_PROXY) || defined(USE_BLUETOOTH_PROXY_CONNECTIONS)
#ifndef USE_API_VARINT64
#define USE_API_VARINT64
#endif
+57 -2
View File
@@ -584,7 +584,7 @@ template<> const char *proto_enum_to_string<enums::MediaPlayerFormatPurpose>(enu
}
}
#endif
#ifdef USE_BLUETOOTH_PROXY
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
template<>
const char *proto_enum_to_string<enums::BluetoothDeviceRequestType>(enums::BluetoothDeviceRequestType value) {
switch (value) {
@@ -606,6 +606,8 @@ const char *proto_enum_to_string<enums::BluetoothDeviceRequestType>(enums::Bluet
return ESPHOME_PSTR("UNKNOWN");
}
}
#endif
#ifdef USE_BLUETOOTH_PROXY
template<> const char *proto_enum_to_string<enums::BluetoothScannerState>(enums::BluetoothScannerState value) {
switch (value) {
case enums::BLUETOOTH_SCANNER_STATE_IDLE:
@@ -988,6 +990,55 @@ const char *DeviceInfoResponse::dump_to(DumpBuffer &out) const {
#endif
return out.c_str();
}
#ifdef USE_BLUETOOTH_PROXY
const char *BluetoothProxyCapabilities::dump_to(DumpBuffer &out) const {
MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothProxyCapabilities"));
dump_field(out, ESPHOME_PSTR("feature_flags"), this->feature_flags);
dump_field(out, ESPHOME_PSTR("mac_address"), this->mac_address);
return out.c_str();
}
#endif
#ifdef USE_VOICE_ASSISTANT
const char *VoiceAssistantCapabilities::dump_to(DumpBuffer &out) const {
MessageDumpHelper helper(out, ESPHOME_PSTR("VoiceAssistantCapabilities"));
dump_field(out, ESPHOME_PSTR("feature_flags"), this->feature_flags);
return out.c_str();
}
#endif
#ifdef USE_ZWAVE_PROXY
const char *ZWaveProxyCapabilities::dump_to(DumpBuffer &out) const {
MessageDumpHelper helper(out, ESPHOME_PSTR("ZWaveProxyCapabilities"));
dump_field(out, ESPHOME_PSTR("feature_flags"), this->feature_flags);
dump_field(out, ESPHOME_PSTR("home_id"), this->home_id);
return out.c_str();
}
#endif
const char *DeviceCapabilitiesResponse::dump_to(DumpBuffer &out) const {
MessageDumpHelper helper(out, ESPHOME_PSTR("DeviceCapabilitiesResponse"));
#ifdef USE_BLUETOOTH_PROXY
out.append(2, ' ').append_p(ESPHOME_PSTR("bluetooth_proxy")).append(": ");
this->bluetooth_proxy.dump_to(out);
out.append("\n");
#endif
#ifdef USE_VOICE_ASSISTANT
out.append(2, ' ').append_p(ESPHOME_PSTR("voice_assistant")).append(": ");
this->voice_assistant.dump_to(out);
out.append("\n");
#endif
#ifdef USE_ZWAVE_PROXY
out.append(2, ' ').append_p(ESPHOME_PSTR("zwave_proxy")).append(": ");
this->zwave_proxy.dump_to(out);
out.append("\n");
#endif
#ifdef USE_SERIAL_PROXY
for (const auto &it : this->serial_proxies) {
out.append(4, ' ').append_p(ESPHOME_PSTR("serial_proxies")).append(": ");
it.dump_to(out);
out.append("\n");
}
#endif
return out.c_str();
}
const char *ListEntitiesDoneResponse::dump_to(DumpBuffer &out) const {
out.append_p(ESPHOME_PSTR("ListEntitiesDoneResponse {}"));
return out.c_str();
@@ -1953,6 +2004,8 @@ const char *BluetoothLERawAdvertisementsResponse::dump_to(DumpBuffer &out) const
}
return out.c_str();
}
#endif
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
const char *BluetoothDeviceRequest::dump_to(DumpBuffer &out) const {
MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothDeviceRequest"));
dump_field(out, ESPHOME_PSTR("address"), this->address);
@@ -2124,6 +2177,8 @@ const char *BluetoothDeviceClearCacheResponse::dump_to(DumpBuffer &out) const {
dump_field(out, ESPHOME_PSTR("error"), this->error);
return out.c_str();
}
#endif
#ifdef USE_BLUETOOTH_PROXY
const char *BluetoothScannerStateResponse::dump_to(DumpBuffer &out) const {
MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothScannerStateResponse"));
dump_field(out, ESPHOME_PSTR("state"), static_cast<enums::BluetoothScannerState>(this->state));
@@ -2715,7 +2770,7 @@ const char *SerialProxyRequestResponse::dump_to(DumpBuffer &out) const {
return out.c_str();
}
#endif
#ifdef USE_BLUETOOTH_PROXY
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
const char *BluetoothSetConnectionParamsRequest::dump_to(DumpBuffer &out) const {
MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothSetConnectionParamsRequest"));
dump_field(out, ESPHOME_PSTR("address"), this->address);
+16 -9
View File
@@ -302,7 +302,7 @@ void APIConnection::read_message_(uint32_t msg_size, uint32_t msg_type, const ui
break;
}
#endif
#ifdef USE_BLUETOOTH_PROXY
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
case BluetoothDeviceRequest::MESSAGE_TYPE: {
BluetoothDeviceRequest msg;
msg.decode(msg_data, msg_size);
@@ -313,7 +313,7 @@ void APIConnection::read_message_(uint32_t msg_size, uint32_t msg_type, const ui
break;
}
#endif
#ifdef USE_BLUETOOTH_PROXY
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
case BluetoothGATTGetServicesRequest::MESSAGE_TYPE: {
BluetoothGATTGetServicesRequest msg;
msg.decode(msg_data, msg_size);
@@ -324,7 +324,7 @@ void APIConnection::read_message_(uint32_t msg_size, uint32_t msg_type, const ui
break;
}
#endif
#ifdef USE_BLUETOOTH_PROXY
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
case BluetoothGATTReadRequest::MESSAGE_TYPE: {
BluetoothGATTReadRequest msg;
msg.decode(msg_data, msg_size);
@@ -335,7 +335,7 @@ void APIConnection::read_message_(uint32_t msg_size, uint32_t msg_type, const ui
break;
}
#endif
#ifdef USE_BLUETOOTH_PROXY
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
case BluetoothGATTWriteRequest::MESSAGE_TYPE: {
BluetoothGATTWriteRequest msg;
msg.decode(msg_data, msg_size);
@@ -346,7 +346,7 @@ void APIConnection::read_message_(uint32_t msg_size, uint32_t msg_type, const ui
break;
}
#endif
#ifdef USE_BLUETOOTH_PROXY
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
case BluetoothGATTReadDescriptorRequest::MESSAGE_TYPE: {
BluetoothGATTReadDescriptorRequest msg;
msg.decode(msg_data, msg_size);
@@ -357,7 +357,7 @@ void APIConnection::read_message_(uint32_t msg_size, uint32_t msg_type, const ui
break;
}
#endif
#ifdef USE_BLUETOOTH_PROXY
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
case BluetoothGATTWriteDescriptorRequest::MESSAGE_TYPE: {
BluetoothGATTWriteDescriptorRequest msg;
msg.decode(msg_data, msg_size);
@@ -368,7 +368,7 @@ void APIConnection::read_message_(uint32_t msg_size, uint32_t msg_type, const ui
break;
}
#endif
#ifdef USE_BLUETOOTH_PROXY
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
case BluetoothGATTNotifyRequest::MESSAGE_TYPE: {
BluetoothGATTNotifyRequest msg;
msg.decode(msg_data, msg_size);
@@ -379,7 +379,7 @@ void APIConnection::read_message_(uint32_t msg_size, uint32_t msg_type, const ui
break;
}
#endif
#ifdef USE_BLUETOOTH_PROXY
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
case 80 /* SubscribeBluetoothConnectionsFreeRequest is empty */: {
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_receive_message_(LOG_STR("on_subscribe_bluetooth_connections_free_request"));
@@ -694,7 +694,7 @@ void APIConnection::read_message_(uint32_t msg_size, uint32_t msg_type, const ui
break;
}
#endif
#ifdef USE_BLUETOOTH_PROXY
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
case BluetoothSetConnectionParamsRequest::MESSAGE_TYPE: {
BluetoothSetConnectionParamsRequest msg;
msg.decode(msg_data, msg_size);
@@ -705,6 +705,13 @@ void APIConnection::read_message_(uint32_t msg_size, uint32_t msg_type, const ui
break;
}
#endif
case 149 /* DeviceCapabilitiesRequest is empty */: {
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_receive_message_(LOG_STR("on_device_capabilities_request"));
#endif
this->on_device_capabilities_request();
break;
}
default:
break;
}
+11 -9
View File
@@ -27,6 +27,8 @@ class APIServerConnectionBase {
void on_ping_response(){};
void on_device_info_request(){};
void on_device_capabilities_request(){};
void on_list_entities_request(){};
void on_subscribe_states_request(){};
@@ -113,32 +115,32 @@ class APIServerConnectionBase {
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
void on_bluetooth_device_request(const BluetoothDeviceRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
void on_subscribe_bluetooth_connections_free_request(){};
#endif
@@ -233,7 +235,7 @@ class APIServerConnectionBase {
void on_serial_proxy_request(const SerialProxyRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
void on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &value){};
#endif
};
+11 -14
View File
@@ -123,7 +123,9 @@ void APIServer::setup() {
// 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);
if (!c->send_message(req)) {
API_LOG_MSG_DROPPED(TAG, "Disconnect request");
}
}
});
}
@@ -394,8 +396,11 @@ void APIServer::on_update(update::UpdateEntity *obj) {
void APIServer::on_zwave_proxy_request(const ZWaveProxyRequest &msg) {
// We could add code to manage a second subscription type, but, since this message type is
// very infrequent and small, we simply send it to all clients
for (auto &c : this->active_clients())
c->send_message(msg);
for (auto &c : this->active_clients()) {
if (!c->send_message(msg)) {
API_LOG_MSG_DROPPED(TAG, "Home ID notification");
}
}
}
#endif
@@ -418,12 +423,6 @@ void APIServer::send_infrared_rf_receive_event([[maybe_unused]] uint32_t device_
API_DISPATCH_UPDATE(alarm_control_panel::AlarmControlPanel, alarm_control_panel)
#endif
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
void APIServer::set_port(uint16_t port) { this->port_ = port; }
void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = batch_delay; }
#ifdef USE_API_HOMEASSISTANT_SERVICES
void APIServer::send_homeassistant_action(const HomeassistantActionRequest &call) {
bool has_subscriber = false;
@@ -548,10 +547,6 @@ const std::vector<APIServer::HomeAssistantStateSubscription> &APIServer::get_sta
}
#endif
uint16_t APIServer::get_port() const { return this->port_; }
void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
#ifdef USE_API_NOISE
bool APIServer::update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg,
const LogString *fail_log_msg, bool make_active) {
@@ -576,7 +571,9 @@ bool APIServer::update_noise_psk_(const SavedNoisePsk &new_psk, const LogString
ESP_LOGW(TAG, "Disconnecting all clients to reset PSK");
for (auto &c : this->active_clients()) {
DisconnectRequest req;
c->send_message(req);
if (!c->send_message(req)) {
API_LOG_MSG_DROPPED(TAG, "Disconnect request");
}
}
});
}
+5 -5
View File
@@ -51,8 +51,8 @@ class APIServer final : public Component,
public:
APIServer();
void setup() override;
uint16_t get_port() const;
float get_setup_priority() const override;
uint16_t get_port() const { return this->port_; }
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
void loop() override;
void dump_config() override;
void on_shutdown() override;
@@ -63,9 +63,9 @@ class APIServer final : public Component,
#ifdef USE_CAMERA
void on_camera_image(const std::shared_ptr<camera::CameraImage> &image) override;
#endif
void set_port(uint16_t port);
void set_reboot_timeout(uint32_t reboot_timeout);
void set_batch_delay(uint16_t batch_delay);
void set_port(uint16_t port) { this->port_ = port; }
void set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
void set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = batch_delay; }
uint16_t get_batch_delay() const { return batch_delay_; }
void set_listen_backlog(uint8_t listen_backlog) { this->listen_backlog_ = listen_backlog; }
+3 -1
View File
@@ -14,6 +14,8 @@ from esphome.const import (
CONF_TUNE_ANTENNA,
CONF_WATCHDOG_THRESHOLD,
)
from esphome.cpp_generator import MockObj
from esphome.types import ConfigType
MULTI_CONF = True
@@ -42,7 +44,7 @@ AS3935_SCHEMA = cv.Schema(
)
async def setup_as3935(var, config):
async def setup_as3935(var: MockObj, config: ConfigType) -> None:
await cg.register_component(var, config)
irq_pin = await cg.gpio_pin_expression(config[CONF_IRQ_PIN])
+2 -1
View File
@@ -1,6 +1,7 @@
import esphome.codegen as cg
from esphome.components import binary_sensor
import esphome.config_validation as cv
from esphome.types import ConfigType
from . import AS3935, CONF_AS3935_ID
@@ -13,7 +14,7 @@ CONFIG_SCHEMA = binary_sensor.binary_sensor_schema().extend(
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_AS3935_ID])
var = await binary_sensor.new_binary_sensor(config)
cg.add(hub.set_thunder_alert_binary_sensor(var))
+2 -1
View File
@@ -9,6 +9,7 @@ from esphome.const import (
STATE_CLASS_MEASUREMENT,
UNIT_KILOMETER,
)
from esphome.types import ConfigType
from . import AS3935, CONF_AS3935_ID
@@ -31,7 +32,7 @@ CONFIG_SCHEMA = cv.Schema(
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_AS3935_ID])
if distance_config := config.get(CONF_DISTANCE):
+14 -10
View File
@@ -1,3 +1,6 @@
from collections.abc import Callable
from typing import Any
from esphome import pins
import esphome.codegen as cg
from esphome.components import i2c
@@ -11,6 +14,7 @@ from esphome.const import (
CONF_RANGE,
CONF_WATCHDOG,
)
from esphome.types import ConfigType
CODEOWNERS = ["@ammmze"]
DEPENDENCIES = ["i2c"]
@@ -72,13 +76,13 @@ POSITION_TO_ANGLE = 360 / RESOLUTION
MIN_RANGE = round(18 * ANGLE_TO_POSITION)
def angle(min=-360, max=360):
def angle(min: float = -360, max: float = 360) -> Callable[[Any], Any]:
return cv.All(
cv.float_with_unit("angle", "(°|deg)"), cv.float_range(min=min, max=max)
)
def angle_to_position(value, min=-360, max=360):
def angle_to_position(value: Any, min: float = -360, max: float = 360) -> int:
try:
value = angle(min=min, max=max)(value)
return (RESOLUTION + round(value * ANGLE_TO_POSITION)) % RESOLUTION
@@ -86,17 +90,17 @@ def angle_to_position(value, min=-360, max=360):
raise cv.Invalid(f"When using angle, {e.error_message}") from e
def percent_to_position(value):
def percent_to_position(value: Any) -> int:
value = cv.possibly_negative_percentage(value)
return (RESOLUTION + round(value * RESOLUTION)) % RESOLUTION
def position(min=-MAX_POSITION, max=MAX_POSITION):
def position(min: int = -MAX_POSITION, max: int = MAX_POSITION) -> Callable[[Any], Any]:
"""Validate that the config option is a position.
Accepts integers, degrees, or percentage (of 360 degrees).
"""
def validator(value):
def validator(value: Any) -> int:
if isinstance(value, str) and value.endswith("%"):
value = percent_to_position(value)
@@ -112,7 +116,7 @@ def position(min=-MAX_POSITION, max=MAX_POSITION):
return validator
def position_range():
def position_range() -> Callable[[Any], Any]:
"""Validate that value given is a valid range for the device.
A valid range is one of the following:
- a value of 0 (meaning full range)
@@ -129,7 +133,7 @@ def position_range():
zero_validator,
)
def validator(value):
def validator(value: Any) -> Any:
is_negative_str = isinstance(value, str) and value.startswith("-")
is_negative_num = isinstance(value, (float, int)) and value < 0
if is_negative_str or is_negative_num:
@@ -139,13 +143,13 @@ def position_range():
return validator
def has_valid_range_config():
def has_valid_range_config() -> Callable[[ConfigType], ConfigType]:
"""Validate that that the config start + end position results in a valid
positional range, which must be >= 18degrees
"""
range_validator = position_range()
def validator(config):
def validator(config: ConfigType) -> ConfigType:
# if we don't have an end position, then there is nothing to do
if CONF_END_POSITION not in config:
return config
@@ -203,7 +207,7 @@ CONFIG_SCHEMA = cv.All(
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
+2 -1
View File
@@ -11,6 +11,7 @@ from esphome.const import (
ICON_ROTATE_RIGHT,
STATE_CLASS_MEASUREMENT,
)
from esphome.types import ConfigType
from .. import AS5600Component, as5600_ns
@@ -77,7 +78,7 @@ CONFIG_SCHEMA = (
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_parented(var, config[CONF_AS5600_ID])
await cg.register_component(var, config)
+16 -3
View File
@@ -4,6 +4,9 @@ import esphome.codegen as cg
from esphome.components import i2c
import esphome.config_validation as cv
from esphome.const import CONF_FREQUENCY, CONF_ID
from esphome.core import ID
from esphome.cpp_generator import MockObj, TemplateArgsType
from esphome.types import ConfigType
CODEOWNERS = ["@X-Ryl669"]
DEPENDENCIES = ["i2c"]
@@ -70,7 +73,7 @@ CONFIG_SCHEMA = cv.All(
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
@@ -91,7 +94,12 @@ AT581XSettingsAction = at581x_ns.class_("AT581XSettingsAction", automation.Actio
),
synchronous=True,
)
async def at581x_reset_to_code(config, action_id, template_arg, args):
async def at581x_reset_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
@@ -163,7 +171,12 @@ RADAR_SETTINGS_SCHEMA = cv.Schema(
RADAR_SETTINGS_SCHEMA,
synchronous=True,
)
async def at581x_settings_to_code(config, action_id, template_arg, args):
async def at581x_settings_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
+2 -1
View File
@@ -2,6 +2,7 @@ import esphome.codegen as cg
from esphome.components import switch
import esphome.config_validation as cv
from esphome.const import DEVICE_CLASS_SWITCH, ICON_WIFI
from esphome.types import ConfigType
from .. import CONF_AT581X_ID, AT581XComponent, at581x_ns
@@ -22,7 +23,7 @@ CONFIG_SCHEMA = switch.switch_schema(
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
at581x_component = await cg.get_variable(config[CONF_AT581X_ID])
s = await switch.new_switch(config)
await cg.register_parented(s, config[CONF_AT581X_ID])
@@ -1,8 +1,6 @@
#include "atc_mithermometer.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
namespace esphome::atc_mithermometer {
static const char *const TAG = "atc_mithermometer";
@@ -15,7 +13,7 @@ void ATCMiThermometer::dump_config() {
LOG_SENSOR(" ", "Battery Voltage", this->battery_voltage_);
}
bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
bool ATCMiThermometer::parse_device(const ble_device_base::ESPBTDevice &device) {
if (device.address_uint64() != this->address_) {
ESP_LOGVV(TAG, "parse_device(): unknown MAC address.");
return false;
@@ -52,7 +50,7 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device
return success;
}
optional<ParseResult> ATCMiThermometer::parse_header_(const esp32_ble_tracker::ServiceData &service_data) {
optional<ParseResult> ATCMiThermometer::parse_header_(const ble_device_base::ServiceData &service_data) {
ParseResult result;
if (!service_data.uuid.contains(0x1A, 0x18)) {
ESP_LOGVV(TAG, "parse_header(): no service data UUID magic bytes.");
@@ -132,5 +130,3 @@ bool ATCMiThermometer::report_results_(const optional<ParseResult> &result, cons
}
} // namespace esphome::atc_mithermometer
#endif
@@ -2,12 +2,10 @@
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/ble_device_base/ble_device.h"
#include <vector>
#ifdef USE_ESP32
namespace esphome::atc_mithermometer {
struct ParseResult {
@@ -18,11 +16,11 @@ struct ParseResult {
int raw_offset;
};
class ATCMiThermometer final : public Component, public esp32_ble_tracker::ESPBTDeviceListener {
class ATCMiThermometer final : public Component, public ble_device_base::ESPBTDeviceListener {
public:
void set_address(uint64_t address) { address_ = address; };
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
bool parse_device(const ble_device_base::ESPBTDevice &device) override;
void dump_config() override;
void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; }
void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; }
@@ -40,11 +38,9 @@ class ATCMiThermometer final : public Component, public esp32_ble_tracker::ESPBT
uint8_t last_frame_count_{0};
optional<ParseResult> parse_header_(const esp32_ble_tracker::ServiceData &service_data);
optional<ParseResult> parse_header_(const ble_device_base::ServiceData &service_data);
bool parse_message_(const std::vector<uint8_t> &message, ParseResult &result);
bool report_results_(const optional<ParseResult> &result, const char *address);
};
} // namespace esphome::atc_mithermometer
#endif
@@ -1,5 +1,5 @@
import esphome.codegen as cg
from esphome.components import esp32_ble_tracker, sensor
from esphome.components import ble_device_base, sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_BATTERY_LEVEL,
@@ -24,14 +24,15 @@ from esphome.const import (
CODEOWNERS = ["@ahpohl"]
DEPENDENCIES = ["esp32_ble_tracker"]
AUTO_LOAD = ["ble_device_base"]
atc_mithermometer_ns = cg.esphome_ns.namespace("atc_mithermometer")
ATCMiThermometer = atc_mithermometer_ns.class_(
"ATCMiThermometer", esp32_ble_tracker.ESPBTDeviceListener, cg.Component
"ATCMiThermometer", ble_device_base.ESPBTDeviceListener, cg.Component
)
CONFIG_SCHEMA = (
CONFIG_SCHEMA = cv.All(
ble_device_base.rename_legacy_hub_id("atc_mithermometer"),
cv.Schema(
{
cv.GenerateID(): cv.declare_id(ATCMiThermometer),
@@ -71,15 +72,15 @@ CONFIG_SCHEMA = (
),
}
)
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA)
.extend(ble_device_base.BLE_DEVICE_SCHEMA),
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await esp32_ble_tracker.register_ble_device(var, config)
await ble_device_base.register_ble_device(var, config)
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
@@ -2,6 +2,7 @@ import esphome.codegen as cg
from esphome.components import button
import esphome.config_validation as cv
from esphome.const import CONF_ID, ENTITY_CATEGORY_CONFIG, ICON_SCALE
from esphome.types import ConfigType
from .. import atm90e32_ns
from ..sensor import ATM90E32Component
@@ -67,7 +68,7 @@ CONFIG_SCHEMA = {
}
async def to_code(config):
async def to_code(config: ConfigType) -> None:
parent = await cg.get_variable(config[CONF_ID])
if run_gain := config.get(CONF_RUN_GAIN_CALIBRATION):
@@ -15,6 +15,7 @@ from esphome.const import (
UNIT_AMPERE,
UNIT_VOLT,
)
from esphome.types import ConfigType
from .. import atm90e32_ns
from ..sensor import ATM90E32Component
@@ -90,7 +91,7 @@ CONFIG_SCHEMA = cv.Schema(
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
parent = await cg.get_variable(config[CONF_ID])
if voltage_cfg := config.get(CONF_REFERENCE_VOLTAGE):
+2 -1
View File
@@ -41,6 +41,7 @@ from esphome.const import (
UNIT_WATT,
UNIT_WATT_HOURS,
)
from esphome.types import ConfigType
from . import atm90e32_ns
@@ -191,7 +192,7 @@ CONFIG_SCHEMA = (
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_instance_id(str(config[CONF_ID])))
await cg.register_component(var, config)
@@ -2,6 +2,7 @@ import esphome.codegen as cg
from esphome.components import text_sensor
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C
from esphome.types import ConfigType
from ..sensor import ATM90E32Component
@@ -34,7 +35,7 @@ CONFIG_SCHEMA = cv.Schema(
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
parent = await cg.get_variable(config[CONF_ID])
if phase_cfg := config.get(CONF_PHASE_STATUS):
+13 -8
View File
@@ -1,4 +1,6 @@
from collections.abc import Callable
from dataclasses import dataclass, field
from typing import Any
import esphome.codegen as cg
from esphome.components.esp32 import (
@@ -15,6 +17,7 @@ from esphome.const import (
)
from esphome.core import CORE
import esphome.final_validate as fv
from esphome.types import ConfigType
AUTO_LOAD = ["ring_buffer"]
CODEOWNERS = ["@kahrendt"]
@@ -125,10 +128,10 @@ CONF_THREADSAFE = "threadsafe"
_MEMORY_LOCATION_VALIDATOR = cv.one_of(*MEMORY_LOCATIONS, lower=True)
def _maybe_empty_codec(schema):
def _maybe_empty_codec(schema: cv.Schema) -> Callable[[Any], Any]:
"""Wrap a codec dict schema so that a bare key (None value) is treated as an empty dict."""
def validator(value):
def validator(value: Any) -> Any:
if value is None:
value = {}
return schema(value)
@@ -200,14 +203,14 @@ def set_stream_limits(
max_channels: int = cv.UNDEFINED,
min_sample_rate: int = cv.UNDEFINED,
max_sample_rate: int = cv.UNDEFINED,
):
) -> Callable[[ConfigType], None]:
"""Sets the limits for the audio stream that audio component can handle
When the component sinks audio (e.g., a speaker), these indicate the limits to the audio it can receive.
When the component sources audio (e.g., a microphone), these indicate the limits to the audio it can send.
"""
def set_limits_in_config(config):
def set_limits_in_config(config: ConfigType) -> None:
if min_bits_per_sample is not cv.UNDEFINED:
config[CONF_MIN_BITS_PER_SAMPLE] = min_bits_per_sample
if max_bits_per_sample is not cv.UNDEFINED:
@@ -233,7 +236,7 @@ def final_validate_audio_schema(
sample_rate: int = cv.UNDEFINED,
enabled_channels: list[int] = cv.UNDEFINED,
audio_device_issue: bool = False,
):
) -> cv.Schema:
"""Validates audio compatibility when passed between different components.
The component derived from ``AUDIO_COMPONENT_SCHEMA`` should call ``set_stream_limits`` in a validator to specify its compatible settings
@@ -251,7 +254,7 @@ def final_validate_audio_schema(
audio_device_issue (bool, optional): Format the error message to indicate the problem is in the configuration for the ``audio_device`` component. Defaults to False.
"""
def validate_audio_compatiblity(audio_config):
def validate_audio_compatiblity(audio_config: ConfigType) -> ConfigType:
audio_schema = {}
if bits_per_sample is not cv.UNDEFINED:
@@ -329,7 +332,7 @@ def _emit_memory_pair(value: str | None, psram_key: str, internal_key: str) -> N
add_idf_sdkconfig_option(internal_key, True)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
# Re-enable ESP-IDF's HTTP client (excluded by default to save compile time)
include_builtin_idf_component("esp_http_client")
@@ -371,7 +374,7 @@ async def to_code(config):
data.wav_support = True
if data.micro_decoder_support:
add_idf_component(name="esphome/micro-decoder", ref="0.2.0")
add_idf_component(name="esphome/micro-decoder", ref="0.4.0")
# All codecs are enabled by default in micro-decoder, so disable the ones that aren't requested to save flash
if not data.flac_support:
@@ -380,6 +383,8 @@ async def to_code(config):
add_idf_sdkconfig_option("CONFIG_MICRO_DECODER_CODEC_MP3", False)
if not data.opus_support:
add_idf_sdkconfig_option("CONFIG_MICRO_DECODER_CODEC_OPUS", False)
# Vorbis is unsupported in ESPHome, so always disable it
add_idf_sdkconfig_option("CONFIG_MICRO_DECODER_CODEC_VORBIS", False)
if not data.wav_support:
add_idf_sdkconfig_option("CONFIG_MICRO_DECODER_CODEC_WAV", False)
+1 -1
View File
@@ -86,7 +86,7 @@ AudioFileType detect_audio_file_type(const char *content_type, const char *url)
// Match "audio/ogg" with a codecs parameter containing "opus"
// Valid forms: audio/ogg;codecs=opus, audio/ogg; codecs="opus", etc.
// Plain "audio/ogg" without opus is not matched (almost always Ogg Vorbis)
if (strncasecmp(content_type, "audio/ogg", 9) == 0 && strcasestr(content_type + 9, "opus") != nullptr) {
if (strncasecmp(content_type, "audio/ogg", 9) == 0 && str_contains_ignore_case(content_type + 9, "opus")) {
return AudioFileType::OPUS;
}
#endif
+10 -3
View File
@@ -2,7 +2,9 @@ from esphome import automation
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_MIC_GAIN
from esphome.core import CoroPriority, coroutine_with_priority
from esphome.core import ID, CoroPriority, coroutine_with_priority
from esphome.cpp_generator import MockObj, TemplateArgsType
from esphome.types import ConfigType
CODEOWNERS = ["@kbx81"]
IS_PLATFORM_COMPONENT = True
@@ -28,7 +30,12 @@ SET_MIC_GAIN_ACTION_SCHEMA = cv.maybe_simple_value(
SET_MIC_GAIN_ACTION_SCHEMA,
synchronous=True,
)
async def audio_adc_set_mic_gain_to_code(config, action_id, template_arg, args):
async def audio_adc_set_mic_gain_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
@@ -39,6 +46,6 @@ async def audio_adc_set_mic_gain_to_code(config, action_id, template_arg, args):
@coroutine_with_priority(CoroPriority.CORE)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
cg.add_define("USE_AUDIO_ADC")
cg.add_global(audio_adc_ns.using)
+16 -4
View File
@@ -3,7 +3,9 @@ from esphome.automation import maybe_simple_id
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_VOLUME
from esphome.core import CoroPriority, coroutine_with_priority
from esphome.core import ID, CoroPriority, coroutine_with_priority
from esphome.cpp_generator import MockObj, TemplateArgsType
from esphome.types import ConfigType
CODEOWNERS = ["@kbx81"]
IS_PLATFORM_COMPONENT = True
@@ -37,7 +39,12 @@ SET_VOLUME_ACTION_SCHEMA = cv.maybe_simple_value(
@automation.register_action(
"audio_dac.mute_on", MuteOnAction, MUTE_ACTION_SCHEMA, synchronous=True
)
async def audio_dac_mute_action_to_code(config, action_id, template_arg, args):
async def audio_dac_mute_action_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@@ -48,7 +55,12 @@ async def audio_dac_mute_action_to_code(config, action_id, template_arg, args):
SET_VOLUME_ACTION_SCHEMA,
synchronous=True,
)
async def audio_dac_set_volume_to_code(config, action_id, template_arg, args):
async def audio_dac_set_volume_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
@@ -59,6 +71,6 @@ async def audio_dac_set_volume_to_code(config, action_id, template_arg, args):
@coroutine_with_priority(CoroPriority.CORE)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
cg.add_define("USE_AUDIO_DAC")
cg.add_global(audio_dac_ns.using)
+1 -5
View File
@@ -1,8 +1,6 @@
#include "b_parasite.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
namespace esphome::b_parasite {
static const char *const TAG = "b_parasite";
@@ -16,7 +14,7 @@ void BParasite::dump_config() {
LOG_SENSOR(" ", "Illuminance", this->illuminance_);
}
bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
bool BParasite::parse_device(const ble_device_base::ESPBTDevice &device) {
if (device.address_uint64() != address_) {
ESP_LOGVV(TAG, "parse_device(): unknown MAC address.");
return false;
@@ -113,5 +111,3 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
}
} // namespace esphome::b_parasite
#endif // USE_ESP32
+3 -7
View File
@@ -2,18 +2,16 @@
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#ifdef USE_ESP32
#include "esphome/components/ble_device_base/ble_device.h"
namespace esphome::b_parasite {
class BParasite final : public Component, public esp32_ble_tracker::ESPBTDeviceListener {
class BParasite final : public Component, public ble_device_base::ESPBTDeviceListener {
public:
void set_address(uint64_t address) { address_ = address; };
void set_bindkey(const std::string &bindkey);
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
bool parse_device(const ble_device_base::ESPBTDevice &device) override;
void dump_config() override;
void set_battery_voltage(sensor::Sensor *battery_voltage) { battery_voltage_ = battery_voltage; }
@@ -35,5 +33,3 @@ class BParasite final : public Component, public esp32_ble_tracker::ESPBTDeviceL
};
} // namespace esphome::b_parasite
#endif // USE_ESP32
+7 -6
View File
@@ -1,5 +1,5 @@
import esphome.codegen as cg
from esphome.components import esp32_ble_tracker, sensor
from esphome.components import ble_device_base, sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_BATTERY_VOLTAGE,
@@ -23,14 +23,15 @@ from esphome.const import (
CODEOWNERS = ["@rbaron"]
DEPENDENCIES = ["esp32_ble_tracker"]
AUTO_LOAD = ["ble_device_base"]
b_parasite_ns = cg.esphome_ns.namespace("b_parasite")
BParasite = b_parasite_ns.class_(
"BParasite", esp32_ble_tracker.ESPBTDeviceListener, cg.Component
"BParasite", ble_device_base.ESPBTDeviceListener, cg.Component
)
CONFIG_SCHEMA = (
CONFIG_SCHEMA = cv.All(
ble_device_base.rename_legacy_hub_id("b_parasite"),
cv.Schema(
{
cv.GenerateID(): cv.declare_id(BParasite),
@@ -68,15 +69,15 @@ CONFIG_SCHEMA = (
),
}
)
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA)
.extend(ble_device_base.BLE_DEVICE_SCHEMA),
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await esp32_ble_tracker.register_ble_device(var, config)
await ble_device_base.register_ble_device(var, config)
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
+4 -2
View File
@@ -2,6 +2,8 @@ import esphome.codegen as cg
from esphome.components import ble_client, time
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_RECEIVE_TIMEOUT, CONF_TIME_ID
from esphome.cpp_generator import MockObj
from esphome.types import ConfigType
CODEOWNERS = ["@jhansche"]
DEPENDENCIES = ["ble_client"]
@@ -32,12 +34,12 @@ BEDJET_CLIENT_SCHEMA = cv.Schema(
)
async def register_bedjet_child(var, config):
async def register_bedjet_child(var: MockObj, config: ConfigType) -> None:
parent = await cg.get_variable(config[CONF_BEDJET_ID])
cg.add(parent.register_child(var))
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await ble_client.register_ble_node(var, config)
@@ -2,6 +2,7 @@ import esphome.codegen as cg
from esphome.components import climate
import esphome.config_validation as cv
from esphome.const import CONF_HEAT_MODE, CONF_TEMPERATURE_SOURCE
from esphome.types import ConfigType
from .. import BEDJET_CLIENT_SCHEMA, bedjet_ns, register_bedjet_child
@@ -37,7 +38,7 @@ CONFIG_SCHEMA = (
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = await climate.new_climate(config)
await cg.register_component(var, config)
await register_bedjet_child(var, config)
+2 -1
View File
@@ -1,6 +1,7 @@
import esphome.codegen as cg
from esphome.components import fan
import esphome.config_validation as cv
from esphome.types import ConfigType
from .. import BEDJET_CLIENT_SCHEMA, bedjet_ns, register_bedjet_child
@@ -16,7 +17,7 @@ CONFIG_SCHEMA = (
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = await fan.new_fan(config)
await cg.register_component(var, config)
await register_bedjet_child(var, config)
+2 -1
View File
@@ -9,6 +9,7 @@ from esphome.const import (
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
)
from esphome.types import ConfigType
from .. import BEDJET_CLIENT_SCHEMA, bedjet_ns, register_bedjet_child
@@ -38,7 +39,7 @@ CONFIG_SCHEMA = cv.Schema(
).extend(BEDJET_CLIENT_SCHEMA)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await register_bedjet_child(var, config)
@@ -300,46 +300,12 @@ void BekenSPILEDStripLightOutput::write_state(light::LightState *state) {
}
light::ESPColorView BekenSPILEDStripLightOutput::get_view_internal(int32_t index) const {
int32_t r = 0, g = 0, b = 0;
switch (this->rgb_order_) {
case ORDER_RGB:
r = 0;
g = 1;
b = 2;
break;
case ORDER_RBG:
r = 0;
g = 2;
b = 1;
break;
case ORDER_GRB:
r = 1;
g = 0;
b = 2;
break;
case ORDER_GBR:
r = 2;
g = 0;
b = 1;
break;
case ORDER_BGR:
r = 2;
g = 1;
b = 0;
break;
case ORDER_BRG:
r = 1;
g = 2;
b = 0;
break;
}
uint8_t multiplier = this->is_rgbw_ || this->is_wrgb_ ? 4 : 3;
uint8_t white = this->is_wrgb_ ? 0 : 3;
return {this->buf_ + (index * multiplier) + r + this->is_wrgb_,
this->buf_ + (index * multiplier) + g + this->is_wrgb_,
this->buf_ + (index * multiplier) + b + this->is_wrgb_,
this->is_rgbw_ || this->is_wrgb_ ? this->buf_ + (index * multiplier) + white : nullptr,
const light::ChannelColors &colors = this->channel_colors_;
uint8_t *led = this->buf_ + (index * colors.bytes_per_led());
return {led + colors.r,
led + colors.g,
led + colors.b,
colors.has_white() ? led + colors.w : nullptr,
&this->effect_data_[index],
&this->correction_};
}
@@ -349,35 +315,12 @@ void BekenSPILEDStripLightOutput::dump_config() {
"Beken SPI LED Strip:\n"
" Pin: %u",
this->pin_);
const char *rgb_order;
switch (this->rgb_order_) {
case ORDER_RGB:
rgb_order = "RGB";
break;
case ORDER_RBG:
rgb_order = "RBG";
break;
case ORDER_GRB:
rgb_order = "GRB";
break;
case ORDER_GBR:
rgb_order = "GBR";
break;
case ORDER_BGR:
rgb_order = "BGR";
break;
case ORDER_BRG:
rgb_order = "BRG";
break;
default:
rgb_order = "UNKNOWN";
break;
}
char channel_colors[5];
ESP_LOGCONFIG(TAG,
" RGB Order: %s\n"
" Channel colors: %s\n"
" Max refresh rate: %" PRIu32 "\n"
" Number of LEDs: %u",
rgb_order, this->max_refresh_rate_.value_or(0), this->num_leds_);
this->channel_colors_.to_string(channel_colors), this->max_refresh_rate_.value_or(0), this->num_leds_);
}
float BekenSPILEDStripLightOutput::get_setup_priority() const { return setup_priority::HARDWARE; }
@@ -3,6 +3,7 @@
#ifdef USE_BK72XX
#include "esphome/components/light/addressable_light.h"
#include "esphome/components/light/channel_colors.h"
#include "esphome/components/light/light_output.h"
#include "esphome/core/color.h"
#include "esphome/core/component.h"
@@ -10,15 +11,6 @@
namespace esphome::beken_spi_led_strip {
enum RGBOrder : uint8_t {
ORDER_RGB,
ORDER_RBG,
ORDER_GRB,
ORDER_GBR,
ORDER_BGR,
ORDER_BRG,
};
class BekenSPILEDStripLightOutput final : public light::AddressableLight {
public:
void setup() override;
@@ -28,7 +20,7 @@ class BekenSPILEDStripLightOutput final : public light::AddressableLight {
int32_t size() const override { return this->num_leds_; }
light::LightTraits get_traits() override {
auto traits = light::LightTraits();
if (this->is_rgbw_ || this->is_wrgb_) {
if (this->channel_colors_.has_white()) {
traits.set_supported_color_modes({light::ColorMode::RGB_WHITE, light::ColorMode::WHITE});
} else {
traits.set_supported_color_modes({light::ColorMode::RGB});
@@ -38,16 +30,13 @@ class BekenSPILEDStripLightOutput final : public light::AddressableLight {
void set_pin(uint8_t pin) { this->pin_ = pin; }
void set_num_leds(uint16_t num_leds) { this->num_leds_ = num_leds; }
void set_is_rgbw(bool is_rgbw) { this->is_rgbw_ = is_rgbw; }
void set_is_wrgb(bool is_wrgb) { this->is_wrgb_ = is_wrgb; }
void set_channel_colors(light::ChannelColors channel_colors) { this->channel_colors_ = channel_colors; }
/// Set a maximum refresh rate in µs as some lights do not like being updated too often.
void set_max_refresh_rate(uint32_t interval_us) { this->max_refresh_rate_ = interval_us; }
void set_led_params(uint8_t bit0, uint8_t bit1, uint32_t spi_frequency);
void set_rgb_order(RGBOrder rgb_order) { this->rgb_order_ = rgb_order; }
void clear_effect_data() override {
for (int i = 0; i < this->size(); i++)
this->effect_data_[i] = 0;
@@ -58,7 +47,7 @@ class BekenSPILEDStripLightOutput final : public light::AddressableLight {
protected:
light::ESPColorView get_view_internal(int32_t index) const override;
size_t get_buffer_size_() const { return this->num_leds_ * (this->is_rgbw_ || this->is_wrgb_ ? 4 : 3); }
size_t get_buffer_size_() const { return this->num_leds_ * this->channel_colors_.bytes_per_led(); }
uint8_t *buf_{nullptr};
uint8_t *effect_data_{nullptr};
@@ -66,13 +55,11 @@ class BekenSPILEDStripLightOutput final : public light::AddressableLight {
uint8_t pin_;
uint16_t num_leds_;
bool is_rgbw_;
bool is_wrgb_;
uint32_t spi_frequency_{6666666};
uint8_t bit0_{0xE0};
uint8_t bit1_{0xFC};
RGBOrder rgb_order_;
light::ChannelColors channel_colors_{0, 1, 2, light::ChannelColors::NO_WHITE};
uint32_t last_refresh_{0};
optional<uint32_t> max_refresh_rate_{};
+17 -24
View File
@@ -3,6 +3,7 @@ from dataclasses import dataclass
from esphome import pins
import esphome.codegen as cg
from esphome.components import libretiny, light
from esphome.components.const import CONF_CHANNEL_COLORS, CONF_IS_WRGB
import esphome.config_validation as cv
from esphome.const import (
CONF_CHIPSET,
@@ -13,6 +14,7 @@ from esphome.const import (
CONF_PIN,
CONF_RGB_ORDER,
)
from esphome.types import ConfigType
CODEOWNERS = ["@Mat931"]
DEPENDENCIES = ["libretiny"]
@@ -22,17 +24,6 @@ BekenSPILEDStripLightOutput = beken_spi_led_strip_ns.class_(
"BekenSPILEDStripLightOutput", light.AddressableLight
)
RGBOrder = beken_spi_led_strip_ns.enum("RGBOrder")
RGB_ORDERS = {
"RGB": RGBOrder.ORDER_RGB,
"RBG": RGBOrder.ORDER_RBG,
"GRB": RGBOrder.ORDER_GRB,
"GBR": RGBOrder.ORDER_GBR,
"BGR": RGBOrder.ORDER_BGR,
"BRG": RGBOrder.ORDER_BRG,
}
@dataclass
class LEDStripTimings:
@@ -57,8 +48,6 @@ CHIPSETS = {
}
CONF_IS_WRGB = "is_wrgb"
SUPPORTED_PINS = {
libretiny.const.FAMILY_BK7231N: [16],
libretiny.const.FAMILY_BK7231T: [16],
@@ -79,10 +68,9 @@ def _validate_pin(value):
return value
def _validate_num_leds(value):
max_num_leds = 165 # 170
if value[CONF_IS_RGBW] or value[CONF_IS_WRGB]:
max_num_leds = 123 # 127
def _validate_num_leds(value: ConfigType) -> ConfigType:
# A white channel makes each LED one byte wider, so fewer of them fit in the DMA buffer.
max_num_leds = 123 if "W" in value[CONF_CHANNEL_COLORS] else 165 # 127 / 170
if value[CONF_NUM_LEDS] > max_num_leds:
raise cv.Invalid(
f"The maximum number of LEDs for this configuration is {max_num_leds}.",
@@ -99,18 +87,23 @@ CONFIG_SCHEMA = cv.All(
pins.internal_gpio_output_pin_number, _validate_pin
),
cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
cv.Required(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True),
cv.Optional(CONF_CHANNEL_COLORS): light.validate_channel_colors,
# Deprecated in favour of CONF_CHANNEL_COLORS, remove in 2027.3.0
cv.Optional(CONF_RGB_ORDER): cv.one_of(*light.RGB_ORDERS, upper=True),
cv.Optional(CONF_IS_RGBW): cv.boolean,
cv.Optional(CONF_IS_WRGB): cv.boolean,
cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds,
cv.Required(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True),
cv.Optional(CONF_IS_RGBW, default=False): cv.boolean,
cv.Optional(CONF_IS_WRGB, default=False): cv.boolean,
}
),
light.migrate_channel_colors(
removed_in="2027.3.0", component="beken_spi_led_strip"
),
_validate_num_leds,
)
async def to_code(config):
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
await light.register_light(var, config)
await cg.register_component(var, config)
@@ -130,6 +123,6 @@ async def to_code(config):
)
)
cg.add(var.set_rgb_order(config[CONF_RGB_ORDER]))
cg.add(var.set_is_rgbw(config[CONF_IS_RGBW]))
cg.add(var.set_is_wrgb(config[CONF_IS_WRGB]))
cg.add(
var.set_channel_colors(light.channel_colors_struct(config[CONF_CHANNEL_COLORS]))
)
+50 -19
View File
@@ -4,12 +4,15 @@ The platform analog of esp32_ble / rp2040_ble: owns the Beken BDK BLE stack
bring-up and the controller BLE address. Consumers (bk72xx_ble_tracker) build
on this component and contain no SDK calls of their own.
Supported SoCs (BLE 5.x): BK7231N/BK7236 (BLE 5.1), BK7238/BK7252N/BK7253
(BLE 5.2), and any future BLE-5.x SoC. Capability is detected at compile time,
not by a chip list: the C++ guards on `__has_include("ble_api.h")` the Beken
BLE 5.x public API header, which the LibreTiny beken-72xx builder ships only
for BLE-5.x SoCs. BK7231T/BK7251/BK7271 (BLE 4.2) and BK7231Q (no BLE) fail
with a clear #error.
Supported SoCs (BLE 5.x): BK7231N/BK7236 (BLE 5.1), BK7252N/BK7253 (BLE 5.2),
and any future BLE-5.x SoC. BK7238 (BLE 5.2) is blocked for now: with BLE
compiled in, the Beken SDK erases the bootloader flash sector at boot because
LibreTiny's partition table has no BLE bonding entry (esphome#18646,
libretiny-eu/libretiny#408). Known non-5.x families and BK7238 are rejected in
to_code. Unknown families are capability-checked at compile time via
`__has_include("app_ble.h")`, a header only on the BLE 5.x include path
(ble_api.h ships for every SoC, so it cannot be the probe). A non-5.x build
fails with a clear #error.
No framework patch is needed: the LibreTiny beken-72xx builder already compiles
and links the BLE 5.x stack (CFG_SUPPORT_BLE=1 + CFG_BLE_VERSION=BLE_VERSION_5_x;
@@ -21,9 +24,16 @@ import logging
import esphome.codegen as cg
from esphome.components import libretiny
from esphome.components.libretiny.const import FAMILY_BK7231N, FAMILY_BK7238
from esphome.components.libretiny.const import (
FAMILY_BK7231N,
FAMILY_BK7231Q,
FAMILY_BK7231T,
FAMILY_BK7238,
FAMILY_BK7251,
)
import esphome.config_validation as cv
from esphome.const import CONF_ENABLE_ON_BOOT, CONF_ID
from esphome.core import EsphomeError
from esphome.types import ConfigType
DEPENDENCIES = ["bk72xx"]
@@ -50,7 +60,39 @@ CONFIG_SCHEMA = cv.Schema(
request_scan_listener_slot = cg.slot_counter("BK72XX_BLE_SCAN_LISTENER_COUNT")
def _unsupported_family_message(family: str) -> str | None:
if family in (FAMILY_BK7231T, FAMILY_BK7251):
return (
f"bk72xx_ble does not support {family}: this SoC has the Beken BLE 4.2 "
"stack; a BLE 5.x SoC such as BK7231N or BK7238 is required"
)
if family == FAMILY_BK7231Q:
return "bk72xx_ble does not support BK7231Q: this SoC has no BLE"
if family == FAMILY_BK7238:
return (
"bk72xx_ble is disabled on BK7238: with BLE compiled in, the Beken SDK "
"erases the bootloader flash sector at boot and the device can no longer "
"start (see https://github.com/esphome/esphome/issues/18646); support "
"returns once the LibreTiny partition table fix "
"(libretiny-eu/libretiny#408) is released"
)
return None
def _final_validate(config: ConfigType) -> None:
# Warn only: a hard error here would break the validate-only CI fixtures,
# which run on a BLE 4.2 board. The hard error is raised at codegen.
if msg := _unsupported_family_message(libretiny.get_libretiny_family()):
_LOGGER.warning("%s (this configuration cannot compile)", msg)
FINAL_VALIDATE_SCHEMA = _final_validate
async def to_code(config: ConfigType) -> None:
if msg := _unsupported_family_message(libretiny.get_libretiny_family()):
raise EsphomeError(msg)
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
@@ -82,18 +124,7 @@ async def to_code(config: ConfigType) -> None:
# BK7231N, but NOT on BK7238 (its BLE stack has no such symbol; the address is
# derived from the WiFi MAC instead — the BDK's own fallback). Tell the C++
# which path is available so it doesn't reference a missing symbol.
family = libretiny.get_libretiny_family()
if family == FAMILY_BK7231N:
if libretiny.get_libretiny_family() == FAMILY_BK7231N:
cg.add_define("BK72XX_BLE_HAS_COMMON_BDADDR")
elif family == FAMILY_BK7238:
# ESPHome's LibreTiny disables BLE on BK7238 because the SDK can hang at
# WiFi STA startup when BLE init runs. This component re-enables BLE, so
# warn loudly: BK7238 is accepted but not hardware-verified and may be
# WiFi-unstable with BLE on.
_LOGGER.warning(
"bk72xx_ble on BK7238: enabling BLE is known to risk a WiFi STA startup "
"hang on this family and is not yet hardware-verified. Expect possible "
"instability."
)
cg.add_define("USE_BK72XX_BLE")
+119
View File
@@ -0,0 +1,119 @@
// Every SDK call the scan reconciler makes. The BDK's own start hardcodes
// passive (the active bit is commented out in both stacks), so
// bdk_scan_start() packs the GAPM_ACTIVITY_START_CMD itself, field-for-field
// the SDK's app_ble_start_scaning() except that prop takes the mode, armed
// through the SDK's own operation bookkeeping. The component pins
// beken-bdk 3.0.78; the static asserts catch a layout change on a bump.
#include "bdk_scan.h"
#ifdef USE_BK72XX_BLE
// Same SDK gate as bk72xx_ble.cpp (which carries the explanatory #error).
#if !defined(CLANG_TIDY) && __has_include("ble_api.h") && __has_include("app_ble.h")
extern "C" {
#include "app_ble.h" // app_ble_env, app_ble_run, app_ble_reset, actv_state_t,
// app_ble_actv_state_get, app_ble_env_state_get,
// app_ble_get_idle_actv_idx_handle, UNKNOW_ACT_IDX,
// bk_ble_* (via ble_api_5_x.h)
#include "kernel_msg.h" // KERNEL_MSG_ALLOC, kernel_msg_send
#if __has_include("gapm_msg.h")
#include "gapm_msg.h" // BLE 5.2 (BK7238/BK7252N): gapm_activity_start_cmd, GAPM_SCAN_*
#else
#include "gapm_task.h" // BLE 5.1 (BK7231N/BK7236): same declarations, older header name
#endif
}
#include "esphome/core/log.h"
namespace esphome::bk72xx_ble {
static const char *const TAG = "bk72xx_ble";
// Pin the SDK surface this file depends on: a beken-bdk bump that moves these
// must fail the build, not corrupt the kernel message.
static_assert(GAPM_SCAN_PROP_PHY_1M_BIT == (1 << 0) && GAPM_SCAN_PROP_ACTIVE_1M_BIT == (1 << 2) &&
sizeof(struct gapm_scan_param) == 16 && sizeof(struct gapm_scan_wd_op_param) == 4,
"beken-bdk GAPM scan layout changed; revalidate bdk_scan_start() "
"against the SDK's app_ble_start_scaning()");
static_assert(INVALID_ACTIVITY_IDX == UNKNOW_ACT_IDX,
"beken-bdk activity sentinel changed; revalidate the scan reconciler");
static_assert(GAPM_REPORT_TYPE_SCAN_RSP_EXT == 2 && GAPM_REPORT_TYPE_SCAN_RSP_LEG == 3 &&
GAPM_REPORT_INFO_SCAN_ADV_BIT == (1 << 5),
"beken-bdk GAPM report info changed; revalidate the tracker's demux constants");
bool bdk_scan_ready() { return app_ble_env_state_get() == APP_BLE_READY; }
BdkActivityState bdk_scan_state(uint8_t activity_idx) {
if (activity_idx == INVALID_ACTIVITY_IDX)
return BdkActivityState::IDLE;
switch (app_ble_actv_state_get(activity_idx)) {
case ACTV_IDLE:
return BdkActivityState::IDLE;
case ACTV_SCAN_CREATED:
return BdkActivityState::CREATED;
case ACTV_SCAN_STARTED:
return BdkActivityState::STARTED;
default:
return BdkActivityState::OTHER;
}
}
uint8_t bdk_scan_acquire_activity() {
uint8_t idx = app_ble_get_idle_actv_idx_handle(SCAN_ACTV);
if (idx == INVALID_ACTIVITY_IDX)
ESP_LOGE(TAG, "Scan start failed: no idle activity handle");
return idx;
}
BdkOpResult bdk_scan_create(uint8_t activity_idx) {
ble_err_t ret = bk_ble_create_scaning(activity_idx, nullptr);
if (ret == ERR_SUCCESS)
return BdkOpResult::OK;
if (ret == ERR_BLE_STATUS)
return BdkOpResult::BUSY;
ESP_LOGE(TAG, "Scan activity create failed (err %d)", static_cast<int>(ret));
return BdkOpResult::FAILED;
}
BdkOpResult bdk_scan_start(uint8_t activity_idx, uint16_t interval, uint16_t window, bool active) {
app_ble_run(activity_idx, BLE_START_SCAN, 1 << BLE_OP_START_SCAN_POS, nullptr);
struct gapm_activity_start_cmd *cmd =
KERNEL_MSG_ALLOC(GAPM_ACTIVITY_START_CMD, TASK_BLE_GAPM, TASK_BLE_APP, gapm_activity_start_cmd);
if (cmd == nullptr) {
app_ble_reset(); // the SDK's own failure path for an unsent operation
ESP_LOGE(TAG, "Scan start failed: kernel message allocation");
return BdkOpResult::FAILED;
}
cmd->operation = GAPM_START_ACTIVITY;
cmd->actv_idx = app_ble_env.actvs[activity_idx].gap_advt_idx;
cmd->u_param.scan_param.type = GAPM_SCAN_TYPE_OBSERVER;
cmd->u_param.scan_param.prop = GAPM_SCAN_PROP_PHY_1M_BIT | (active ? GAPM_SCAN_PROP_ACTIVE_1M_BIT : 0);
cmd->u_param.scan_param.scan_param_1m.scan_intv = interval;
cmd->u_param.scan_param.scan_param_1m.scan_wd = window;
cmd->u_param.scan_param.scan_param_coded.scan_intv = 0;
cmd->u_param.scan_param.scan_param_coded.scan_wd = 0;
cmd->u_param.scan_param.dup_filt_pol = 0;
cmd->u_param.scan_param.rsvd = 0;
cmd->u_param.scan_param.duration = 0; // scan until stopped
cmd->u_param.scan_param.period = 10; // matches the SDK's passive start
kernel_msg_send(cmd);
return BdkOpResult::OK;
}
BdkOpResult bdk_scan_release(uint8_t activity_idx, bool created, int *err_out) {
ble_err_t ret = created ? bk_ble_delete_scaning(activity_idx, nullptr) : bk_ble_scan_stop(activity_idx, nullptr);
*err_out = static_cast<int>(ret);
if (ret == ERR_SUCCESS)
return BdkOpResult::OK;
// DEBUG on purpose: the reconciler WARNs once per streak and the stuck
// ERROR carries this code — a per-retry ERROR would be unbounded.
ESP_LOGD(TAG, "Scan release %s (err %d)", ret == ERR_BLE_STATUS ? "rejected" : "failed", static_cast<int>(ret));
return ret == ERR_BLE_STATUS ? BdkOpResult::BUSY : BdkOpResult::FAILED;
}
} // namespace esphome::bk72xx_ble
#endif // !CLANG_TIDY && ble_api.h && app_ble.h
#endif // USE_BK72XX_BLE
+51
View File
@@ -0,0 +1,51 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_BK72XX_BLE
#include <cstdint>
namespace esphome::bk72xx_ble {
/// Activity index value marking "no scan activity", the BDK's own convention
/// (asserted against its symbol in bdk_scan.cpp).
inline constexpr uint8_t INVALID_ACTIVITY_IDX = 0xFF;
/// Scan-relevant controller activity states, read live from the SDK.
enum class BdkActivityState : uint8_t {
IDLE, ///< No activity (or one whose create failed).
CREATED, ///< Created but not started.
STARTED, ///< Scanning.
OTHER, ///< A non-scan or transitional state; settles on a later read.
};
/// Outcome of a BDK scan operation request.
enum class BdkOpResult : uint8_t {
OK, ///< Accepted; completion is asynchronous.
BUSY, ///< Another controller operation is in flight; retry later.
FAILED, ///< Rejected.
};
/// True when no controller operation is in flight (APP_BLE_READY).
bool bdk_scan_ready();
/// Live state of the given activity; INVALID_ACTIVITY_IDX reads as IDLE.
BdkActivityState bdk_scan_state(uint8_t activity_idx);
/// Claim an idle activity slot; INVALID_ACTIVITY_IDX when none is free.
uint8_t bdk_scan_acquire_activity();
/// Create the scan activity (asynchronous); started once CREATED is observed.
BdkOpResult bdk_scan_create(uint8_t activity_idx);
/// Start a created activity: the packed GAPM start, taking the scan mode the
/// BDK's own start path hardcodes away. Fire-and-forget; FAILED when the
/// kernel message could not be allocated (the armed SDK operation is rolled
/// back).
BdkOpResult bdk_scan_start(uint8_t activity_idx, uint16_t interval, uint16_t window, bool active);
/// Release the activity: delete when never started (a stop would be
/// rejected), stop otherwise. BUSY on a transient rejection (retry), FAILED
/// on any other error; err_out receives the SDK code (0 on success).
/// Teardown is asynchronous — observe IDLE to confirm.
BdkOpResult bdk_scan_release(uint8_t activity_idx, bool created, int *err_out);
} // namespace esphome::bk72xx_ble
#endif // USE_BK72XX_BLE
+270 -45
View File
@@ -5,7 +5,8 @@
// talks to the Beken BDK BLE stack:
// - one-time stack bring-up (ble_set_notice_cb() + ble_entry()),
// - the controller BLE address,
// - the raw controller scan primitives (bk_ble_scan_start/stop),
// - the scan reconciler (request, pacing, bring-up budget) over the
// bdk_scan surface,
// - the scan-report ring: the BDK notice callback (BLE task) takes a report
// from a fixed pool and pushes it on a lock-free SPSC queue; loop() drains,
// dispatches on the main task and returns reports to the pool — the same
@@ -20,32 +21,39 @@
#include "bk72xx_ble.h" // pulls esphome/core/defines.h for USE_BK72XX_BLE
#include "bdk_scan.h" // the raw BDK scan surface (state reads, starts, release)
#ifdef USE_BK72XX_BLE
#include <cstring>
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h" // get_mac_address_raw()
#include "esphome/core/log.h"
// ---------------------------------------------------------------------------
// SDK-capability gate (not a chip allowlist).
// This component drives the Beken BLE *5.x* controller via its public API,
// `ble_api.h`, which the LibreTiny beken-72xx builder ships only for the
// BLE-5.x SoCs (it selects the `ble_pub` 5.x stack from CFG_BLE_VERSION; the
// 4.2 SoCs build a different, older API with no ble_api.h). Gate on the header
// itself so any BLE-5.x Beken chip — present or future — is supported without a
// hard-coded list, and a non-5.x build fails here with a clear message instead
// of a cryptic "ble_api.h: No such file or directory".
// This component drives the Beken BLE *5.x* controller. `ble_api.h` cannot be
// the probe: it ships for every SoC (driver/include) and merely switches on
// CFG_BLE_VERSION internally. `app_ble.h` is on the include path only when the
// LibreTiny beken-72xx builder selects a 5.x stack, so gating on it supports
// any BLE-5.x chip — present or future — without a hard-coded list, and a
// non-5.x build fails here with a clear message instead of a cryptic
// "app_ble.h: No such file or directory".
// ---------------------------------------------------------------------------
#if defined(CLANG_TIDY)
// The clang-tidy environment does not carry the full Beken BDK BLE 5.x API
// (its ble_api.h variant lacks parts of the 5.x surface), so there is nothing
// accurate to analyze the SDK calls against — skip the file under analysis.
#define BK72XX_BLE_NO_SDK
#elif !__has_include("ble_api.h")
#elif !__has_include("ble_api.h") || !__has_include("app_ble.h")
// Also skip the SDK body: #error does not stop the preprocessor, and on a 4.2
// SoC ble_api.h exists, so without the guard the 5.x symbols would fail one by
// one and bury this message.
#define BK72XX_BLE_NO_SDK
#error \
"bk72xx_ble requires a BLE 5.x Beken SDK (ble_api.h). Supported SoCs: BK7231N/BK7236 (BLE 5.1) and BK7238/BK7252N/BK7253 (BLE 5.2). BK7231T/BK7251/BK7271 (BLE 4.2) and BK7231Q (no BLE) are not supported."
"bk72xx_ble requires a BLE 5.x Beken SDK (app_ble.h). Supported SoCs: BK7231N/BK7236 (BLE 5.1) and BK7238/BK7252N/BK7253 (BLE 5.2). BK7231T/BK7251/BK7271 (BLE 4.2) and BK7231Q (no BLE) are not supported."
#endif
#ifndef BK72XX_BLE_NO_SDK
@@ -57,9 +65,8 @@
// are C headers consumed from C++ (a standard C-header-from-C++ pattern).
// ---------------------------------------------------------------------------
extern "C" {
#include "ble_api.h" // bk_ble_scan_start/stop, ble_entry, ble_set_notice_cb,
// app_ble_get_idle_actv_idx_handle, struct scan_param,
// recv_adv_t, ble_notice_t, BLE_5_REPORT_ADV, SCAN_ACTV
#include "ble_api.h" // ble_set_notice_cb, recv_adv_t, ble_notice_t,
// BLE_5_REPORT_ADV (scan primitives live in bdk_scan.cpp)
#ifdef BK72XX_BLE_HAS_COMMON_BDADDR
#include "common_bt_defines.h" // struct bd_addr
// The controller's public BLE address, populated by the BDK during ble_entry().
@@ -76,6 +83,12 @@ namespace esphome::bk72xx_ble {
static const char *const TAG = "bk72xx_ble";
static constexpr uint32_t RECONCILE_RETRY_MS = 10; // pump floor for fast loops
static constexpr uint32_t RECONCILE_REJECTED_RETRY_MS = 500; // retry gate after a rejected release
static constexpr uint32_t RECONCILE_PENDING_TIMEOUT_MS = 2000; // bring-up budget before FAILED
static constexpr uint32_t SCAN_LIVENESS_CHECK_MS = 1000; // settled-scan re-check cadence
static constexpr uint32_t TEARDOWN_STUCK_ERROR_MS = 30000; // stuck-teardown ERROR (stop also goes FAILED)
// The BDK notice callback is a plain C function pointer with no user argument,
// so it reaches the (single) component instance through a file-static pointer.
static BK72xxBLE *s_ble = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
@@ -95,21 +108,22 @@ static void ble_notice_callback(ble_notice_t notice, void *param) {
const recv_adv_t *info = reinterpret_cast<const recv_adv_t *>(param);
// rssi is a signed dBm carried in a uint8_t; cast through int8_t (standard for
// a signed dBm value packed in a uint8_t).
s_ble->enqueue_scan_report(info->adv_addr, static_cast<int8_t>(info->rssi), info->adv_addr_type, info->data,
info->data_len);
s_ble->enqueue_scan_report(info->adv_addr, static_cast<int8_t>(info->rssi), info->adv_addr_type,
static_cast<uint8_t>(info->evt_type), info->data, info->data_len);
}
void BK72xxBLE::enqueue_scan_report(const uint8_t *mac, int8_t rssi, uint8_t addr_type, const uint8_t *data,
uint16_t data_len) {
void BK72xxBLE::enqueue_scan_report(const uint8_t *mac, int8_t rssi, uint8_t addr_type, uint8_t evt_type,
const uint8_t *data, uint16_t data_len) {
BLEScanReport *report = this->report_pool_.allocate();
if (report == nullptr) {
// Pool exhausted — the queue is full; count and drop.
this->report_queue_.increment_dropped_count();
return;
}
memcpy(report->mac, mac, 6);
memcpy(report->mac, mac, MAC_ADDRESS_SIZE);
report->rssi = rssi;
report->addr_type = addr_type;
report->evt_type = evt_type;
report->data_len =
(data_len <= sizeof(report->data)) ? static_cast<uint8_t>(data_len) : static_cast<uint8_t>(sizeof(report->data));
memcpy(report->data, data, report->data_len);
@@ -123,6 +137,9 @@ void BK72xxBLE::enqueue_scan_report(const uint8_t *mac, int8_t rssi, uint8_t add
void BK72xxBLE::setup() {
s_ble = this;
// The report pool grows lazily on purpose: the BDK notice callback runs in
// task context (malloc-safe, unlike rp2040's IRQ path), and typical traffic
// stays far below the pool cap, so not warming contains RAM.
// Resolve the MAC early so get_mac_lsb_first() is valid for consumers before
// the stack is up (it is re-read once ble_entry() has run).
this->resolve_mac_();
@@ -173,6 +190,30 @@ void BK72xxBLE::enable() {
}
void BK72xxBLE::loop() {
// Keep reconciling toward the requested scan state (e.g. complete a stop
// that arrived while a controller operation was in flight), and re-check a
// settled scan at low frequency: a controller-side drop re-enters the
// bring-up, and the budget's FAILED feeds the tracker's recovery.
// Keep driving until settled: any PENDING, plus a terminal stop whose slot
// must still be freed. A FAILED scan request is the one combination not
// re-driven here — that belongs to the tracker's backoff.
const uint32_t pump_now = App.get_loop_component_start_time();
if (this->last_result_ == ScanOpResult::PENDING ||
(!this->scan_wanted_ && this->last_result_ == ScanOpResult::FAILED)) {
const uint32_t gate = (this->release_warned_ || this->last_result_ == ScanOpResult::FAILED)
? RECONCILE_REJECTED_RETRY_MS
: RECONCILE_RETRY_MS;
if (pump_now - this->last_advance_ms_ >= gate)
this->advance_();
} else if (this->scan_wanted_ && this->last_result_ == ScanOpResult::SETTLED &&
pump_now - this->last_advance_ms_ >= SCAN_LIVENESS_CHECK_MS) {
// Re-check a settled scan; scan_start() refills the bring-up budget.
// WARN: the only report of a drop that recovers inside its budget.
if (this->scan_start(this->requested_.interval, this->requested_.window, this->requested_.active) !=
ScanOpResult::SETTLED)
ESP_LOGW(TAG, "Controller dropped the scan; restarting");
}
// Drain the lock-free ring filled by the BLE task; all per-report work runs
// here on the main task, then the report returns to the pool.
BLEScanReport *report = this->report_queue_.pop();
@@ -193,7 +234,7 @@ void BK72xxBLE::loop() {
ESP_LOGW(TAG, "Dropped %u scan reports due to queue overflow", dropped);
}
void BK72xxBLE::get_mac_lsb_first(uint8_t out[6]) const {
void BK72xxBLE::get_mac_lsb_first(uint8_t out[MAC_ADDRESS_SIZE]) const {
for (int i = 0; i < 6; i++)
out[i] = this->ble_mac_[i];
}
@@ -226,7 +267,7 @@ void BK72xxBLE::resolve_mac_() {
}
}
if (nonzero) {
memcpy(this->ble_mac_, common_default_bdaddr.addr, 6);
memcpy(this->ble_mac_, common_default_bdaddr.addr, MAC_ADDRESS_SIZE);
return;
}
#endif
@@ -238,54 +279,238 @@ void BK72xxBLE::resolve_mac_() {
// (verified against the BK7231N BLE-5.1 and BK7252N/BK7238 BLE-5.2 SDK sources), so it
// matches on every device, including the last-byte == 0xFF edge that a 24-bit increment
// would carry differently.
uint8_t wifi_mac[6];
uint8_t wifi_mac[MAC_ADDRESS_SIZE];
get_mac_address_raw(wifi_mac); // MSB-first
const uint8_t ble[6] = {wifi_mac[0], wifi_mac[1], wifi_mac[2],
wifi_mac[3], wifi_mac[4], static_cast<uint8_t>(wifi_mac[5] + 1)};
const uint8_t ble[MAC_ADDRESS_SIZE] = {wifi_mac[0], wifi_mac[1], wifi_mac[2],
wifi_mac[3], wifi_mac[4], static_cast<uint8_t>(wifi_mac[5] + 1)};
// Store LSB-first to match recv_adv_t adv_addr ordering.
for (int i = 0; i < 6; i++)
this->ble_mac_[i] = ble[5 - i];
}
// ---------------------------------------------------------------------------
// Controller scan primitives
// Scan reconciler
// ---------------------------------------------------------------------------
bool BK72xxBLE::scan_start(uint16_t interval, uint16_t window) {
// Episode boundary: fresh teardown deadline and error bookkeeping.
void BK72xxBLE::reset_teardown_episode_() {
this->teardown_since_ms_ = 0;
this->restarting_ = false;
this->last_release_err_ = 0;
}
ScanOpResult BK72xxBLE::scan_start(uint16_t interval, uint16_t window, bool active) {
if (!this->is_active())
this->enable();
if (this->scan_actv_idx_ != 0xFF) {
// Already scanning — stop first so this call cleanly restarts with the new
// parameters (the BDK cannot start a second scan on a busy activity).
this->scan_stop();
const ScanParams params{active, interval, window};
// A new episode refills the budget and gets a fresh teardown deadline; a
// re-call observing an in-flight bring-up (last result PENDING) must not.
if (this->last_result_ != ScanOpResult::PENDING || !this->scan_wanted_ || params != this->requested_) {
this->pending_since_ms_ = App.get_loop_component_start_time();
this->reset_teardown_episode_();
}
this->scan_wanted_ = true;
this->requested_ = params;
return this->advance_();
}
struct scan_param sp;
memset(&sp, 0, sizeof(sp));
sp.channel_map = 7; // advertising channels 37/38/39
sp.interval = interval;
sp.window = window;
void BK72xxBLE::scan_stop() {
if (this->scan_wanted_) {
// A stamp inherited from a stuck restart would fail the stop on its
// first advance.
this->reset_teardown_episode_();
}
this->scan_wanted_ = false;
this->advance_();
}
this->scan_actv_idx_ = app_ble_get_idle_actv_idx_handle(SCAN_ACTV);
if (this->scan_actv_idx_ == 0xFF) {
ESP_LOGE(TAG, "Scan start failed: no idle activity handle");
bool BK72xxBLE::flush_pending_stop(uint32_t timeout_ms) {
// millis() on both sides: the loop clock is frozen while this blocks.
const uint32_t start = millis();
while (!this->scan_wanted_ && this->last_result_ == ScanOpResult::PENDING) {
if (millis() - start >= timeout_ms)
return false;
delay(RECONCILE_RETRY_MS);
this->advance_();
}
return this->last_result_ == ScanOpResult::SETTLED;
}
// Teardown is asynchronous: the handle is kept until an IDLE observation
// confirms the radio is idle. A rejection WARNs once per failure streak and
// widens the pump gate; the epilogue owns the stuck-teardown deadline.
void BK72xxBLE::release_activity_(BdkActivityState state) {
const BdkOpResult result =
bdk_scan_release(this->scan_activity_idx_, state == BdkActivityState::CREATED, &this->last_release_err_);
if (result == BdkOpResult::OK) {
this->release_warned_ = false;
return;
}
if (!this->release_warned_) {
// A hard error carries its code immediately; the 30 s stuck ERROR follows
// if it persists.
if (result == BdkOpResult::FAILED) {
ESP_LOGW(TAG, "Scan activity release failed (err %d); retrying", this->last_release_err_);
} else {
ESP_LOGW(TAG, "Scan activity release rejected; retrying");
}
this->release_warned_ = true;
}
}
// Stamp/track the teardown episode; once past the deadline, ERROR (re-logged
// each interval) and report stuck.
bool BK72xxBLE::teardown_stuck_(uint32_t now) {
if (this->teardown_since_ms_ == 0) {
this->teardown_since_ms_ = now;
this->teardown_stuck_log_ms_ = now; // first ERROR fires at the deadline
return false;
}
ble_err_t ret = bk_ble_scan_start(this->scan_actv_idx_, &sp, nullptr);
if (ret != ERR_SUCCESS) {
ESP_LOGE(TAG, "Scan start failed (err %d)", static_cast<int>(ret));
this->scan_actv_idx_ = 0xFF;
if (now - this->teardown_since_ms_ < TEARDOWN_STUCK_ERROR_MS)
return false;
if (now - this->teardown_stuck_log_ms_ >= TEARDOWN_STUCK_ERROR_MS) {
if (this->last_release_err_ != 0) {
ESP_LOGE(TAG, "Scan teardown cannot proceed; scanner is stuck (release err %d)", this->last_release_err_);
} else {
// No rejected release this episode: stuck waiting on the controller.
ESP_LOGE(TAG, "Scan teardown cannot proceed; scanner is stuck (controller busy)");
}
this->teardown_stuck_log_ms_ = now;
}
return true;
}
void BK72xxBLE::scan_stop() {
if (this->scan_actv_idx_ != 0xFF) {
bk_ble_scan_stop(this->scan_actv_idx_, nullptr);
this->scan_actv_idx_ = 0xFF;
// One SDK operation per call toward the latched request; controller state is
// read live each time (it changes on the BLE task, so nothing is mirrored).
// The epilogue owns all deadlines and episode bookkeeping.
ScanOpResult BK72xxBLE::advance_() {
if (!this->scan_wanted_ && this->scan_activity_idx_ == INVALID_ACTIVITY_IDX) {
// Nothing to do; also keeps SDK reads off the pre-enable() path.
this->last_result_ = ScanOpResult::SETTLED;
return ScanOpResult::SETTLED;
}
const BdkActivityState state = bdk_scan_state(this->scan_activity_idx_);
const bool ready = bdk_scan_ready();
ScanOpResult result = this->scan_wanted_ ? this->advance_start_(state, ready) : this->advance_stop_(state, ready);
const uint32_t now = App.get_loop_component_start_time();
this->last_advance_ms_ = now;
if (result == ScanOpResult::SETTLED || (state == BdkActivityState::IDLE && ready)) {
// Any teardown episode is over (IDLE observed with the controller
// settled, or e.g. a mode flip that settled back without ever reaching
// IDLE). An IDLE read while an operation is in flight proves nothing —
// a stop deferred there must keep its episode running.
this->reset_teardown_episode_();
this->release_warned_ = false;
}
if (this->restarting_ && (state == BdkActivityState::IDLE || state == BdkActivityState::CREATED)) {
// The mode-change release is observed complete; the rest is a normal
// bring-up on a fresh budget.
this->restarting_ = false;
this->pending_since_ms_ = now;
}
// Not chained to the clear above: a bring-up waiting at IDLE (create still
// in flight) must keep spending its budget.
if (result == ScanOpResult::PENDING) {
if (this->scan_wanted_ && state != BdkActivityState::STARTED && !this->restarting_) {
// A downed radio spends the bring-up budget; exhausting it hands
// recovery to the tracker's backoff.
if (now - this->pending_since_ms_ >= RECONCILE_PENDING_TIMEOUT_MS) {
ESP_LOGE(TAG, "Scan bring-up did not settle; giving up until the next start");
result = ScanOpResult::FAILED;
}
} else {
// A teardown is pending: a stop, or a mode-change release still in
// flight (restarting_); either way the bring-up budget waits.
if (this->scan_wanted_)
this->pending_since_ms_ = now;
if (this->teardown_stuck_(now)) {
// Terminal for stop AND restart: the tracker's backoff owns recovery
// (a stop's release keeps re-driving from loop(); a restart is
// re-requested through scan_start() with a fresh deadline).
result = ScanOpResult::FAILED;
}
}
}
this->last_result_ = result;
return result;
}
ScanOpResult BK72xxBLE::advance_stop_(BdkActivityState state, bool ready) {
if (state == BdkActivityState::IDLE && ready) {
// Fully torn down (or never created): the radio is idle. IDLE is trusted
// only when the controller is settled — mid-create the slot still reads
// IDLE, and dropping the handle then would leak the activity once the
// create lands.
this->scan_activity_idx_ = INVALID_ACTIVITY_IDX;
return ScanOpResult::SETTLED;
}
if (!ready) {
// Acting mid-operation could delete an activity whose start lands
// afterwards, leaking the slot with the radio on; wait.
if (this->last_result_ == ScanOpResult::SETTLED)
ESP_LOGD(TAG, "Scan stop deferred (controller busy)");
return ScanOpResult::PENDING;
}
// Settled, so CREATED unambiguously means "never started".
this->release_activity_(state);
return ScanOpResult::PENDING; // confirmed once IDLE is observed
}
ScanOpResult BK72xxBLE::advance_start_(BdkActivityState state, bool ready) {
if (state == BdkActivityState::STARTED) {
if (this->applied_ == this->requested_)
return ScanOpResult::SETTLED;
// Running with different mode or parameters: tear down (the SDK stop
// chain also deletes the activity) and recreate on a later advance.
if (ready) {
this->release_activity_(state);
// Invalidate so a flip back to the old params cannot SETTLE against the
// activity being deleted (interval 0 never matches a real request).
this->applied_.interval = 0;
this->restarting_ = true;
}
return ScanOpResult::PENDING;
}
if (!ready) {
if (this->last_result_ == ScanOpResult::SETTLED)
ESP_LOGD(TAG, "Scan start deferred (controller busy)");
return ScanOpResult::PENDING;
}
if (state == BdkActivityState::CREATED) {
// Fire-and-forget: SETTLED only once a later advance observes the scan
// running, so a rejected start is retried rather than silently dead. On
// failure the created activity is intact; keep the handle.
if (bdk_scan_start(this->scan_activity_idx_, this->requested_.interval, this->requested_.window,
this->requested_.active) != BdkOpResult::OK)
return ScanOpResult::FAILED;
this->applied_ = this->requested_;
return ScanOpResult::PENDING;
}
if (state == BdkActivityState::OTHER)
return ScanOpResult::PENDING; // transitional; settles on a later read
// IDLE and ready: acquire a slot and create. A kept index is deliberately
// reused: SDK delete returns the slot to idle and create requires an idle
// slot, so it equals a fresh acquire — while clearing here would orphan a
// create still in flight (the BUSY race below).
if (this->scan_activity_idx_ == INVALID_ACTIVITY_IDX) {
this->scan_activity_idx_ = bdk_scan_acquire_activity();
if (this->scan_activity_idx_ == INVALID_ACTIVITY_IDX)
return ScanOpResult::FAILED;
}
switch (bdk_scan_create(this->scan_activity_idx_)) {
case BdkOpResult::BUSY: // raced the BLE task; keep the index, the retry resumes this slot
case BdkOpResult::OK:
return ScanOpResult::PENDING;
case BdkOpResult::FAILED:
break;
}
// Safe to clear (unlike BUSY): acquire is a pure search, so a rejected
// create leaves the slot IDLE for re-acquire.
this->scan_activity_idx_ = INVALID_ACTIVITY_IDX;
return ScanOpResult::FAILED;
}
} // namespace esphome::bk72xx_ble
+63 -11
View File
@@ -11,6 +11,8 @@
#include <cstdint>
#include "bdk_scan.h"
namespace esphome::bk72xx_ble {
enum class BLEComponentState : uint8_t {
@@ -19,11 +21,32 @@ enum class BLEComponentState : uint8_t {
ACTIVE,
};
/// Outcome of one reconciliation step.
enum class ScanOpResult : uint8_t {
SETTLED, ///< The request is reached: scan observed running, or stopped
///< with the activity fully released.
PENDING, ///< A step is in flight; loop() keeps advancing — call
///< scan_start() again to learn the outcome.
FAILED, ///< The controller rejected a step; retry later.
};
/// One scan request: mode plus timing, in BLE units (0.625 ms).
struct ScanParams {
bool active;
uint16_t interval;
uint16_t window;
bool operator==(const ScanParams &) const = default;
};
/// One advertisement report from the controller.
struct BLEScanReport {
uint8_t mac[6]; // LSB-first, as the controller delivers it
int8_t rssi; // signed dBm
uint8_t mac[MAC_ADDRESS_SIZE]; // LSB-first, as the controller delivers it
int8_t rssi; // signed dBm
uint8_t addr_type;
// GAPM report info byte (recv_adv_t.evt_type): bits 0-2 report type
// (1 = legacy adv, 3 = legacy scan response), bit 5 scannable — lets the
// tracker's merger tell the two frames apart.
uint8_t evt_type;
uint8_t data_len; // bytes valid in data[]
uint8_t data[62]; // legacy advertisement (31) + scan response (31)
@@ -60,7 +83,7 @@ class BK72xxBLE final : public Component {
void set_enable_on_boot(bool enable_on_boot) { this->enable_on_boot_ = enable_on_boot; }
/// Controller BLE address, least-significant octet first (BLE convention).
void get_mac_lsb_first(uint8_t out[6]) const;
void get_mac_lsb_first(uint8_t out[MAC_ADDRESS_SIZE]) const;
#ifdef BK72XX_BLE_SCAN_LISTENER_COUNT
/// Register a consumer for scan reports (delivered on the main task via loop()).
@@ -69,18 +92,33 @@ class BK72xxBLE final : public Component {
void register_scan_listener(BLEScanListener *listener) { this->scan_listeners_.push_back(listener); }
#endif
/// Start the controller scan. Interval/window are in BLE units (0.625 ms).
/// Enables the stack first if needed. Returns false on controller failure.
bool scan_start(uint16_t interval, uint16_t window);
/// Stop the controller scan (no-op when not scanning).
/// Request a scan (interval/window in 0.625 ms BLE units); enables the
/// stack first if needed. PENDING until the scan is observed running —
/// loop() keeps advancing, call again to learn the outcome.
ScanOpResult scan_start(uint16_t interval, uint16_t window, bool active);
/// Request the scanner stopped and the activity released; steps that
/// cannot run yet are completed from loop().
void scan_stop();
/// Drive a requested stop until the radio is observed idle, bounded by
/// timeout_ms (for OTA). Returns false if it still has not settled.
bool flush_pending_stop(uint32_t timeout_ms);
/// Last reconciliation outcome; on FAILED the consumer's retry policy owns
/// recovery.
ScanOpResult last_scan_result() const { return this->last_result_; }
/// Internal: buffer one controller report (BDK notice callback, BLE task
/// context — bounded copy under the scheduler lock, nothing else).
void enqueue_scan_report(const uint8_t *mac, int8_t rssi, uint8_t addr_type, const uint8_t *data, uint16_t data_len);
void enqueue_scan_report(const uint8_t *mac, int8_t rssi, uint8_t addr_type, uint8_t evt_type, const uint8_t *data,
uint16_t data_len);
protected:
void resolve_mac_();
ScanOpResult advance_();
ScanOpResult advance_stop_(BdkActivityState state, bool ready);
ScanOpResult advance_start_(BdkActivityState state, bool ready);
bool teardown_stuck_(uint32_t now);
void reset_teardown_episode_();
void release_activity_(BdkActivityState state);
#ifdef BK72XX_BLE_SCAN_LISTENER_COUNT
// Codegen-sized: no heap allocation, no std::vector template instantiation —
@@ -95,10 +133,24 @@ class BK72xxBLE final : public Component {
// allocate() returns nullptr before push() can fail. This prevents leaking a
// pool slot on a failed push and keeps release() off the producer path.
esphome::EventPool<BLEScanReport, MAX_SCAN_REPORT_QUEUE_SIZE - 1> report_pool_;
uint8_t ble_mac_[6]{0}; // LSB-first (BLE convention)
uint8_t scan_actv_idx_{0xFF};
BLEComponentState state_{BLEComponentState::STATE_OFF};
// Largest-to-smallest: padding only at the tail, absorbed by future byte fields.
uint32_t last_advance_ms_{0};
uint32_t pending_since_ms_{0}; // bring-up budget anchor; refilled on request change
uint32_t teardown_since_ms_{0}; // unfinished teardown episode start; 0 = none
uint32_t teardown_stuck_log_ms_{0}; // last stuck-teardown ERROR; re-logged each TEARDOWN_STUCK_ERROR_MS
int last_release_err_{0}; // SDK code of the episode's last failed release; 0 = none
ScanParams requested_{}; // latched by scan_start()
ScanParams applied_{}; // last params we commanded; mismatch with requested_ restarts
uint8_t ble_mac_[MAC_ADDRESS_SIZE]{0}; // LSB-first (BLE convention)
uint8_t scan_activity_idx_{INVALID_ACTIVITY_IDX};
bool scan_wanted_{false}; // the latched request is to scan (vs stopped)
bool release_warned_{false}; // gates the release WARN; widens the pump gate
bool restarting_{false}; // mode-change release in flight; teardown deadline governs until released
bool enable_on_boot_{false};
// PENDING means advance_() has more to do; loop() drives it, paced and
// (for a bring-up) bounded.
ScanOpResult last_result_{ScanOpResult::SETTLED};
BLEComponentState state_{BLEComponentState::STATE_OFF};
};
} // namespace esphome::bk72xx_ble
@@ -25,6 +25,7 @@ from esphome.components.ble_device_base import automation as ble_automation
from esphome.components.const import CONF_ON_SCAN_END, CONF_SCAN_PARAMETERS, CONF_WINDOW
import esphome.config_validation as cv
from esphome.const import (
CONF_ACTIVE,
CONF_CONTINUOUS,
CONF_DURATION,
CONF_ID,
@@ -144,6 +145,12 @@ async def stop_scan_action_to_code(
async def to_code(config: ConfigType) -> None:
# Selects the BLEHub alias arm in ble_device_base/ble_hub_impl.h.
cg.add_define("USE_BK72XX_BLE_TRACKER")
# Compiles the shared adv + scan-response merge (the BDK delivers the pair
# as separate reports).
cg.add_define("USE_BLE_SCAN_RESPONSE_MERGER")
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
@@ -161,6 +168,7 @@ async def to_code(config: ConfigType) -> None:
cg.add(var.set_scan_window(ble_device_base.to_ble_units(scan[CONF_WINDOW])))
cg.add(var.set_scan_duration(scan[CONF_DURATION].total_milliseconds))
cg.add(var.set_configured_continuous(scan[CONF_CONTINUOUS]))
cg.add(var.set_scan_active(scan[CONF_ACTIVE]))
for conf in config.get(CONF_ON_BLE_ADVERTISE, []):
await ble_automation.advertise_trigger_to_code(conf, var)

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