1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-01 07:31:51 +00:00

Compare commits

...

136 Commits

Author SHA1 Message Date
J. Nick Koston
9913d7d9e9 Merge branch 'determine_jobs_comp_split' into determine_jobs_comp_split_test 2025-10-29 19:01:26 -05:00
J. Nick Koston
119dc8b530 flatten 2025-10-29 19:01:12 -05:00
J. Nick Koston
3c8f4c1d5d Merge branch 'determine_jobs_comp_split' into determine_jobs_comp_split_test 2025-10-29 18:58:30 -05:00
J. Nick Koston
4892638768 flatten 2025-10-29 18:58:07 -05:00
J. Nick Koston
306448c02c Merge branch 'determine_jobs_comp_split' into determine_jobs_comp_split_test 2025-10-29 18:54:31 -05:00
J. Nick Koston
eb73a23ab1 flatten 2025-10-29 18:53:50 -05:00
J. Nick Koston
22cb0fbb70 flatten 2025-10-29 18:53:35 -05:00
J. Nick Koston
04d22becd0 Merge branch 'determine_jobs_comp_split' into determine_jobs_comp_split_test 2025-10-29 18:48:14 -05:00
J. Nick Koston
36a7951f7a fix 2025-10-29 18:48:00 -05:00
J. Nick Koston
81382db3d0 DNM: ci testing 2025-10-29 18:45:29 -05:00
J. Nick Koston
e0b02ccfc5 [ci] Consolidate component splitting into determine-jobs 2025-10-29 18:43:49 -05:00
Stuart Parmenter
918650f15a [lvgl] memset canvas buffer to prevent display of random garbage (#11582)
Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com>
2025-10-29 21:06:45 +00:00
Stuart Parmenter
287f65cbaf [lvgl] fix typo from previous refactor (#11596) 2025-10-30 07:27:31 +11:00
Javier Peletier
f18c70a256 [core] Fix substitution id redefinition false positive (#11603) 2025-10-30 07:06:55 +13:00
Jonathan Swoboda
6fb490f49b [remote_transmitter] Add non-blocking mode (#11524) 2025-10-29 12:40:22 -04:00
J. Nick Koston
66cf7c3a3b [lvgl] Fix nested lambdas in automations unable to access parameters (#11583)
Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com>
2025-10-29 17:07:48 +11:00
dependabot[bot]
f29021b5ef Bump ruff from 0.14.1 to 0.14.2 (#11519)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2025-10-29 05:23:42 +00:00
dependabot[bot]
7549ca4d39 Bump actions/download-artifact from 5.0.0 to 6.0.0 (#11521)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-29 00:20:13 -05:00
dependabot[bot]
33e7a2101b Bump actions/upload-artifact from 4.6.2 to 5.0.0 (#11520)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-29 00:20:05 -05:00
dependabot[bot]
59a216bfcb Bump github/codeql-action from 4.30.9 to 4.31.0 (#11522)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-29 00:19:47 -05:00
Jesse Hills
09d89000ad [core] Remove deprecated schema constants (#11591) 2025-10-29 00:14:02 -05:00
Kent Gibson
b6c9ece0e6 template_alarm_control_panel readability improvements (#11593) 2025-10-29 00:10:36 -05:00
dependabot[bot]
7169556722 Bump aioesphomeapi from 42.4.0 to 42.5.0 (#11597)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-29 04:46:47 +00:00
J. Nick Koston
f6e4c0cb52 [ci] Fix component tests not running when only test files change (#11580) 2025-10-29 16:22:28 +13:00
J. Nick Koston
f3634edc22 [select] Store options in flash to reduce RAM usage (#11514) 2025-10-29 15:28:16 +13:00
Jesse Hills
a609343cb6 [fan] Remove deprecated set_speed function (#11590) 2025-10-28 21:06:30 -05:00
Clyde Stubbs
5528c3c765 [mipi_rgb] Fix rotation with custom model (#11585) 2025-10-29 14:37:14 +13:00
Anton Sergunov
0d805355f5 Fix the LiberTiny bug with UART pin setup (#11518) 2025-10-29 14:33:16 +13:00
Jesse Hills
99f48ae51c [logger] Improve level validation errors (#11589)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-29 01:29:40 +00:00
Jesse Hills
25e4aafd71 [ci] Fix auto labeller workflow with wrong comment for too-big with labels (#11592) 2025-10-29 14:28:29 +13:00
Kent Gibson
4f2d54be4e template_alarm_control_panel cleanups (#11469) 2025-10-29 13:48:26 +13:00
dependabot[bot]
249cd7415b Bump aioesphomeapi from 42.3.0 to 42.4.0 (#11586)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-29 00:32:41 +00:00
J. Nick Koston
78d780105b [ci] Change upper Python version being tested to 3.13 (#11587) 2025-10-28 19:24:37 -05:00
Jesse Hills
466d4522bc [http_request] Pass trigger variables into on_response/on_error (#11464) 2025-10-29 12:17:16 +13:00
Javier Peletier
e462217500 [packages] Tighten package validation (#11584) 2025-10-29 11:18:47 +13:00
J. Nick Koston
f1bce262ed [uart] Optimize UART components to eliminate temporary vector allocations (#11570)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-10-29 09:48:20 +13:00
J. Nick Koston
7ed7e7ad26 [climate] Replace std::set with FiniteSetMask for trait storage (#11466) 2025-10-29 08:46:44 +13:00
J. Nick Koston
08b8454555 [ble_client] Use function pointers for lambda actions and sensors (#11564)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-10-29 08:10:32 +13:00
J. Nick Koston
0119e17f04 [ci] Remove base bus components exclusion from memory impact analysis (#11572) 2025-10-29 08:08:13 +13:00
J. Nick Koston
c3f40de844 [modbus_controller] Optimize lambdas to use function pointers instead of std::function (#11566) 2025-10-29 08:06:13 +13:00
J. Nick Koston
7dd829cfca [esp32_ble_server][esp32_improv] Eliminate unnecessary heap allocations (#11569) 2025-10-29 08:05:12 +13:00
J. Nick Koston
da19673f51 Add additional uart test coverage (#11571) 2025-10-29 08:03:09 +13:00
rwrozelle
f5e32d03d0 [http_request] update timeout to be uint32_t (#11577) 2025-10-28 12:41:48 -04:00
J. Nick Koston
f3b69383fd Add additional modbus compile tests (#11567) 2025-10-28 16:43:16 +13:00
J. Nick Koston
aba72809d3 Additional tests for ble_client lambdas (#11565) 2025-10-28 16:43:10 +13:00
aanban
85205a28d2 [remote_base] add support for Dyson cool AM07 tower fan (#10163)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-10-27 22:49:16 -04:00
Edward Firmo
285e006637 [nextion] Add set_component_visibility() method for dynamic visibility control (#11530)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-10-28 13:22:28 +13:00
Edward Firmo
5647f36900 [nextion] Remove TFT upload baud rate validation to reduce flash usage (#11012) 2025-10-28 13:21:17 +13:00
Samuel Sieb
1e9309ffff [tuya] allow enum for eco id (#11544)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2025-10-28 13:20:21 +13:00
Daniel Herrmann
ce8a6a6c43 fix: load_cert_chain requires the path, not a file object (#11543) 2025-10-28 12:24:13 +13:00
J. Nick Koston
dfb4b31bf9 [template] Store initial option as index in template select (#11523) 2025-10-28 11:37:40 +13:00
J. Nick Koston
31b1b50ad9 [number] Skip set_mode call when using default AUTO mode (#11537) 2025-10-28 11:16:38 +13:00
J. Nick Koston
3377080272 [core] Simplify ESPTime::strftime() and save 20 bytes flash (#11539) 2025-10-28 11:16:09 +13:00
Keith Burzinski
d65ad69338 [uart] Fix order of initialization calls (#11510) 2025-10-27 17:09:45 -05:00
J. Nick Koston
dfa69173ea [api] Use FixedVector const references for service array arguments (#11546) 2025-10-28 11:03:44 +13:00
J. Nick Koston
f44615cc8d [template] Optimize all template platforms to use function pointers for stateless lambdas (#11555) 2025-10-28 11:00:02 +13:00
J. Nick Koston
bda4769bd3 [core] Optimize TemplatableValue to use function pointers for stateless lambdas (#11554) 2025-10-27 21:05:40 +00:00
J. Nick Koston
14b057f54e [light] Optimize LambdaLightEffect and AddressableLambdaLightEffect with function pointers (#11556) 2025-10-27 20:14:16 +00:00
J. Nick Koston
e26b5874d7 [api] Register user services with initializer_list (#11545) 2025-10-28 09:07:31 +13:00
J. Nick Koston
00f22e5c36 [network] Eliminate runtime string parsing for IP address initialization (#11561) 2025-10-28 08:51:08 +13:00
Javier Peletier
51e080c2d3 [substitutions] fix #11077 Preserve ESPHomeDatabase (document metadata) in substitutions (#11087)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-10-27 19:46:26 +00:00
J. Nick Koston
3c18558003 Optimize stateless lambdas to use function pointers (#11551) 2025-10-28 08:06:22 +13:00
Jonathan Swoboda
7394cbf773 [core] Don't allow python 3.14 (#11527) 2025-10-26 09:00:08 -04:00
J. Nick Koston
1577a46efd [gpio] Skip set_inverted() call for default false value (#11538) 2025-10-25 22:09:42 -07:00
J. Nick Koston
e212ed024d [sntp] Replace std::vector<std::string> with std::array<const char*> to save heap memory (#11525) 2025-10-25 10:00:43 -07:00
Jonathan Swoboda
5fdd90c71a [esp32] Add IDF 5.4.3 to platform list and switch to tar.xz (#11528) 2025-10-25 00:27:39 -07:00
Jonathan Swoboda
6929bdb415 [remote_transmitter] Remove delays and use RMT instead (#11505) 2025-10-24 15:01:30 -04:00
J. Nick Koston
2c85ba037e [http_request] Pass collect_headers by const reference instead of by value (#11494) 2025-10-23 20:01:48 -07:00
J. Nick Koston
2440bbdceb [core][sensor] Eliminate redundant default value setters in generated code (#11495) 2025-10-23 20:01:23 -07:00
Jesse Hills
3ac8eb7696 Merge branch 'release' into dev 2025-10-24 14:08:56 +13:00
Jesse Hills
6a478b9070 Merge pull request #11506 from esphome/bump-2025.10.3
2025.10.3
2025-10-24 14:08:12 +13:00
Jesse Hills
a32a1d11fb Bump version to 2025.10.3 2025-10-24 07:51:38 +13:00
Markus
daeb8ef88c [core] handle mixed IP and DNS addresses correctly in resolve_ip_address (#11503)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2025-10-24 07:51:38 +13:00
Anton Sergunov
febee437d6 [uart] Make rx pin respect pullup and pulldown settings (#9248) 2025-10-24 07:51:38 +13:00
Peter Zich
de2f475dbd [hdc1080] Make HDC1080_CMD_CONFIGURATION failure a warning (and log it) (#11355)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-10-24 07:51:38 +13:00
Markus
fa3ec6f732 [core] handle mixed IP and DNS addresses correctly in resolve_ip_address (#11503)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2025-10-23 11:32:07 -07:00
dependabot[bot]
e490aec6b4 Bump ruamel-yaml from 0.18.15 to 0.18.16 (#11482)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-23 10:25:36 -07:00
J. Nick Koston
8da8095a6a [tests] Isolate gps component to prevent TinyGPSPlus millis() conflicts (#11499) 2025-10-23 10:11:13 -07:00
Patrick
ab14c0cd72 [pipsolar] improve sensor readout in HA, set unknown state on timeout / error (#10292)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-10-23 11:32:02 -04:00
J. Nick Koston
917deac7cb [scheduler] Remove unused <deque> include after defer queue optimization (#11491) 2025-10-23 04:02:19 +00:00
dependabot[bot]
3d21adecd3 Bump aioesphomeapi from 42.2.0 to 42.3.0 (#11493)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-23 02:58:09 +00:00
J. Nick Koston
5b023f9369 [ethernet] Add RMII GPIO pin conflict validation (#11488) 2025-10-22 16:37:50 -10:00
dependabot[bot]
6c2ce5cacf Bump bleak from 1.0.1 to 1.1.1 (#11492)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-22 16:36:30 -10:00
J. Nick Koston
d23e25f099 [api] Fix clang-tidy modernize-use-emplace warning for light effects (#11490) 2025-10-22 21:31:51 -05:00
optimusprimespace
9b78098eec [hdc2010] New component (#6674)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-10-22 22:24:17 -04:00
Keith Burzinski
7e5b82c5f3 [improv_serial] Various optimizations (#11473)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-10-23 14:24:08 +13:00
J. Nick Koston
2864e989bd [light] Extract ColorModeMask into generic FiniteSetMask helper (#11472)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-10-23 14:22:46 +13:00
J. Nick Koston
6efe346cc5 [light] Use std::initializer_list for add_effects to reduce flash overhead (#11485) 2025-10-23 14:21:53 +13:00
J. Nick Koston
f2f6c597ef [light] Store effect names in flash (const char*) to save RAM (#11487)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-23 14:17:57 +13:00
tomaszduda23
b91b12d77a [nrf52] support BLE --device for logging (#9861)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-10-22 14:55:34 -10:00
J. Nick Koston
7f567bdfbe [fan] Add basic fan compile tests (#11484) 2025-10-23 10:53:15 +13:00
Daniel Stiner
f2de8df556 [openthread] Fix OTA by populating CORE.address with device's mDNS address (#11095)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
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>
2025-10-22 11:07:01 -10:00
J. Nick Koston
1c67a61945 [ci] Fix WiFi testing mode validation and component splitter for variant-only tests (#11481) 2025-10-23 08:10:24 +13:00
Jonathan Swoboda
77141d3e83 [esp32] Set the location of the IDF component manager cache (#11467) 2025-10-22 14:28:18 -04:00
J. Nick Koston
f592f79bce [ci] Fix component splitter for components with only variant tests (#11476) 2025-10-22 07:30:27 -10:00
J. Nick Koston
6edbb94529 [ci] Fix test detection for components with only variant tests (#11474) 2025-10-22 00:06:14 -10:00
Jeff Brown
d37eb59fd7 [light] Eliminate dimming undershoot during addressable light transition (#11471) 2025-10-22 08:22:33 +00:00
Jeff Brown
e2b3617df3 [climate] Fix restore state for fan mode, preset, and swing mode (#11126)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2025-10-21 22:08:40 -10:00
J. Nick Koston
e1c851cab8 [wifi] Optimize WiFi network storage with FixedVector (#11458)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-10-22 05:23:10 +00:00
J. Nick Koston
146b067d62 [light] Add compile test for addressable lights (#11465) 2025-10-22 16:59:39 +13:00
J. Nick Koston
5b15827009 [CI] Fix component detection when core files change in determine-jobs (#11461) 2025-10-22 16:58:40 +13:00
J. Nick Koston
0de79ba291 [event] Replace std::set with FixedVector for event type storage (#11463) 2025-10-22 16:57:18 +13:00
J. Nick Koston
e3aaf6a144 [wifi] Test multiple stas in wifi compile tests (#11460) 2025-10-22 16:55:46 +13:00
J. Nick Koston
78ffeb30fb [binary_sensor] Optimize MultiClickTrigger with FixedVector (#11453)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-10-22 16:55:13 +13:00
Jesse Hills
2c1927fd12 [api] Allow clearing noise psk if dynamically set (#11429) 2025-10-22 14:24:56 +13:00
Jesse Hills
c6ae1a5909 [core] Stop clang-format "fixing" a single line (#11462) 2025-10-22 01:00:27 +00:00
J. Nick Koston
9c712744be [light] Replace std::vector with FixedVector in strobe and color_wipe effects (#11455) 2025-10-22 11:40:19 +13:00
Javier Peletier
ae50a09b4e C++ components unit test framework (#9284)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-10-21 22:21:22 +00:00
Jeff Brown
1ea80594c6 [light] Improve gamma correction precision (#11141)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2025-10-21 22:11:11 +00:00
J. Nick Koston
8500323d39 [esp32] Add advanced options to disable unused VFS features (saves ~8.7 KB flash) (#11441) 2025-10-22 10:47:31 +13:00
J. Nick Koston
6f7db2f5f7 [gpio] Optimize switch interlock with FixedVector (#11448)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-10-21 11:35:34 -10:00
J. Nick Koston
9922c65912 Add compile tests for binary_sensor MultiClickTrigger (#11454)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-10-22 10:32:48 +13:00
J. Nick Koston
f2469077d9 [light] Add tests for AddressableColorWipeEffectColor/StrobeLightEffectColor (#11456) 2025-10-22 10:31:18 +13:00
Jesse Hills
742eca92d8 [CI] Add auto label for chained PRs (#11457) 2025-10-21 11:22:56 -10:00
J. Nick Koston
548913b471 Add gpio switch interlock compile tests (#11449) 2025-10-22 10:12:32 +13:00
Anton Sergunov
a05c5ea240 [uart] Make rx pin respect pullup and pulldown settings (#9248) 2025-10-22 10:10:25 +13:00
Jeff Brown
8e8a2bde95 [light] Decouple AddressableLight and Light transition classes (#11166)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-10-21 10:37:29 -10:00
Petr Kejval
80265a6bd2 [sensor] Add optimistic option to heartbeat filter (#10993)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-10-21 09:17:07 -04:00
J. Nick Koston
87e9a7a1bd [climate] Remove unnecessary vector allocations in state save/restore (#11445) 2025-10-21 04:35:18 -05:00
J. Nick Koston
3aedfe8be3 [binary_sensor] Optimize AutorepeatFilter with FixedVector (#11444) 2025-10-21 04:30:13 -05:00
J. Nick Koston
7f2cc47ed6 [binary_sensor] Add compile test for auto repeat (#11443) 2025-10-21 04:25:59 -05:00
J. Nick Koston
a5542e0d2b [sensor] Optimize calibration and Or filters with FixedVector (#11437) 2025-10-20 21:38:05 -10:00
Keith Burzinski
66afe4a9be [climate] Add some integration tests (#11439) 2025-10-21 02:26:18 -05:00
J. Nick Koston
0ae9009e41 [ci] Fix clang-tidy split mode for core file changes (#11434) 2025-10-20 20:39:50 -10:00
J. Nick Koston
0b2f5fcd7e Add additional sensor filter tests (#11438) 2025-10-20 20:39:21 -10:00
J. Nick Koston
7a2887e2ed [analyze-memory] Improve symbol categorization accuracy (#11440) 2025-10-20 20:39:05 -10:00
J. Nick Koston
cd2d3f061d [espnow] Fix compilation error with initializer_list after #11433 (#11436) 2025-10-20 19:58:24 -10:00
J. Nick Koston
73f5d01c2d [core] Optimize automation actions memory usage with std::initializer_list (#11433) 2025-10-21 04:32:58 +00:00
Jesse Hills
0938609f7a [improv] Put next_url behind defines to save flash (#11420)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-10-21 16:58:26 +13:00
J. Nick Koston
77203f0cb4 [text_sensor] Optimize filters with FixedVector (1.6KB flash savings) (#11423)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-10-21 03:24:51 +00:00
J. Nick Koston
040130e357 [ci] Fix memory impact workflow for new components (#11421) 2025-10-21 16:02:07 +13:00
J. Nick Koston
85959e3004 [sensor,text_sensor,binary_sensor] Optimize filter parameters with std::initializer_list (#11426) 2025-10-21 15:47:13 +13:00
Jonathan Swoboda
a809a13729 [core] Add support for extern "C" includes (#11422) 2025-10-21 15:46:50 +13:00
J. Nick Koston
3b6ff615e8 [ci] Fix clang-tidy split decision to account for component dependencies (#11430) 2025-10-21 15:39:15 +13:00
J. Nick Koston
05216db5f0 ESP8266: Complete testing mode memory patches with DRAM and Flash (#11427) 2025-10-21 15:26:49 +13:00
J. Nick Koston
9f668b0c4b Add basic text_sensor tests (#11424) 2025-10-21 15:26:41 +13:00
339 changed files with 6570 additions and 2037 deletions

View File

@@ -1 +1 @@
d7693a1e996cacd4a3d1c9a16336799c2a8cc3db02e4e74084151ce964581248
3d46b63015d761c85ca9cb77ab79a389509e5776701fb22aed16e7b79d432c0c

View File

@@ -53,6 +53,7 @@ jobs:
'new-target-platform',
'merging-to-release',
'merging-to-beta',
'chained-pr',
'core',
'small-pr',
'dashboard',
@@ -140,6 +141,8 @@ jobs:
labels.add('merging-to-release');
} else if (baseRef === 'beta') {
labels.add('merging-to-beta');
} else if (baseRef !== 'dev') {
labels.add('chained-pr');
}
return labels;
@@ -413,7 +416,7 @@ jobs:
}
// Generate review messages
function generateReviewMessages(finalLabels) {
function generateReviewMessages(finalLabels, originalLabelCount) {
const messages = [];
const prAuthor = context.payload.pull_request.user.login;
@@ -427,15 +430,15 @@ jobs:
.reduce((sum, file) => sum + (file.deletions || 0), 0);
const nonTestChanges = (totalAdditions - testAdditions) - (totalDeletions - testDeletions);
const tooManyLabels = finalLabels.length > MAX_LABELS;
const tooManyLabels = originalLabelCount > MAX_LABELS;
const tooManyChanges = nonTestChanges > TOO_BIG_THRESHOLD;
let message = `${TOO_BIG_MARKER}\n### 📦 Pull Request Size\n\n`;
if (tooManyLabels && tooManyChanges) {
message += `This PR is too large with ${nonTestChanges} line changes (excluding tests) and affects ${finalLabels.length} different components/areas.`;
message += `This PR is too large with ${nonTestChanges} line changes (excluding tests) and affects ${originalLabelCount} different components/areas.`;
} else if (tooManyLabels) {
message += `This PR affects ${finalLabels.length} different components/areas.`;
message += `This PR affects ${originalLabelCount} different components/areas.`;
} else {
message += `This PR is too large with ${nonTestChanges} line changes (excluding tests).`;
}
@@ -463,8 +466,8 @@ jobs:
}
// Handle reviews
async function handleReviews(finalLabels) {
const reviewMessages = generateReviewMessages(finalLabels);
async function handleReviews(finalLabels, originalLabelCount) {
const reviewMessages = generateReviewMessages(finalLabels, originalLabelCount);
const hasReviewableLabels = finalLabels.some(label =>
['too-big', 'needs-codeowners'].includes(label)
);
@@ -528,8 +531,8 @@ jobs:
const apiData = await fetchApiData();
const baseRef = context.payload.pull_request.base.ref;
// Early exit for non-dev branches
if (baseRef !== 'dev') {
// Early exit for release and beta branches only
if (baseRef === 'release' || baseRef === 'beta') {
const branchLabels = await detectMergeBranch();
const finalLabels = Array.from(branchLabels);
@@ -624,6 +627,7 @@ jobs:
// Handle too many labels (only for non-mega PRs)
const tooManyLabels = finalLabels.length > MAX_LABELS;
const originalLabelCount = finalLabels.length;
if (tooManyLabels && !isMegaPR && !finalLabels.includes('too-big')) {
finalLabels = ['too-big'];
@@ -632,7 +636,7 @@ jobs:
console.log('Computed labels:', finalLabels.join(', '));
// Handle reviews
await handleReviews(finalLabels);
await handleReviews(finalLabels, originalLabelCount);
// Apply labels
if (finalLabels.length > 0) {

View File

@@ -62,7 +62,7 @@ jobs:
run: git diff
- if: failure()
name: Archive artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: generated-proto-files
path: |

View File

@@ -114,7 +114,7 @@ jobs:
matrix:
python-version:
- "3.11"
- "3.14"
- "3.13"
os:
- ubuntu-latest
- macOS-latest
@@ -123,9 +123,9 @@ jobs:
# Minimize CI resource usage
# by only running the Python version
# version used for docker images on Windows and macOS
- python-version: "3.14"
- python-version: "3.13"
os: windows-latest
- python-version: "3.14"
- python-version: "3.13"
os: macOS-latest
runs-on: ${{ matrix.os }}
needs:
@@ -178,6 +178,9 @@ jobs:
component-test-count: ${{ steps.determine.outputs.component-test-count }}
changed-cpp-file-count: ${{ steps.determine.outputs.changed-cpp-file-count }}
memory_impact: ${{ steps.determine.outputs.memory-impact }}
cpp-unit-tests-run-all: ${{ steps.determine.outputs.cpp-unit-tests-run-all }}
cpp-unit-tests-components: ${{ steps.determine.outputs.cpp-unit-tests-components }}
component-test-batches: ${{ steps.determine.outputs.component-test-batches }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
@@ -210,6 +213,9 @@ jobs:
echo "component-test-count=$(echo "$output" | jq -r '.component_test_count')" >> $GITHUB_OUTPUT
echo "changed-cpp-file-count=$(echo "$output" | jq -r '.changed_cpp_file_count')" >> $GITHUB_OUTPUT
echo "memory-impact=$(echo "$output" | jq -c '.memory_impact')" >> $GITHUB_OUTPUT
echo "cpp-unit-tests-run-all=$(echo "$output" | jq -r '.cpp_unit_tests_run_all')" >> $GITHUB_OUTPUT
echo "cpp-unit-tests-components=$(echo "$output" | jq -c '.cpp_unit_tests_components')" >> $GITHUB_OUTPUT
echo "component-test-batches=$(echo "$output" | jq -c '.component_test_batches')" >> $GITHUB_OUTPUT
integration-tests:
name: Run integration tests
@@ -247,6 +253,33 @@ jobs:
. venv/bin/activate
pytest -vv --no-cov --tb=native -n auto tests/integration/
cpp-unit-tests:
name: Run C++ unit tests
runs-on: ubuntu-24.04
needs:
- common
- determine-jobs
if: github.event_name == 'pull_request' && (needs.determine-jobs.outputs.cpp-unit-tests-run-all == 'true' || needs.determine-jobs.outputs.cpp-unit-tests-components != '[]')
steps:
- name: Check out code from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Restore Python
uses: ./.github/actions/restore-python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Run cpp_unit_test.py
run: |
. venv/bin/activate
if [ "${{ needs.determine-jobs.outputs.cpp-unit-tests-run-all }}" = "true" ]; then
script/cpp_unit_test.py --all
else
ARGS=$(echo '${{ needs.determine-jobs.outputs.cpp-unit-tests-components }}' | jq -r '.[] | @sh' | xargs)
script/cpp_unit_test.py $ARGS
fi
clang-tidy-single:
name: ${{ matrix.name }}
runs-on: ubuntu-24.04
@@ -505,59 +538,18 @@ jobs:
run: script/ci-suggest-changes
if: always()
test-build-components-splitter:
name: Split components for intelligent grouping (40 weighted per batch)
runs-on: ubuntu-24.04
needs:
- common
- determine-jobs
if: github.event_name == 'pull_request' && fromJSON(needs.determine-jobs.outputs.component-test-count) > 0
outputs:
matrix: ${{ steps.split.outputs.components }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Restore Python
uses: ./.github/actions/restore-python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Split components intelligently based on bus configurations
id: split
run: |
. venv/bin/activate
# Use intelligent splitter that groups components with same bus configs
components='${{ needs.determine-jobs.outputs.changed-components-with-tests }}'
# Only isolate directly changed components when targeting dev branch
# For beta/release branches, group everything for faster CI
if [[ "${{ github.base_ref }}" == beta* ]] || [[ "${{ github.base_ref }}" == release* ]]; then
directly_changed='[]'
echo "Target branch: ${{ github.base_ref }} - grouping all components"
else
directly_changed='${{ needs.determine-jobs.outputs.directly-changed-components-with-tests }}'
echo "Target branch: ${{ github.base_ref }} - isolating directly changed components"
fi
echo "Splitting components intelligently..."
output=$(python3 script/split_components_for_ci.py --components "$components" --directly-changed "$directly_changed" --batch-size 40 --output github)
echo "$output" >> $GITHUB_OUTPUT
test-build-components-split:
name: Test components batch (${{ matrix.components }})
runs-on: ubuntu-24.04
needs:
- common
- determine-jobs
- test-build-components-splitter
if: github.event_name == 'pull_request' && fromJSON(needs.determine-jobs.outputs.component-test-count) > 0
strategy:
fail-fast: false
max-parallel: ${{ (startsWith(github.base_ref, 'beta') || startsWith(github.base_ref, 'release')) && 8 || 4 }}
matrix:
components: ${{ fromJson(needs.test-build-components-splitter.outputs.matrix) }}
components: ${{ fromJson(needs.determine-jobs.outputs.component-test-batches) }}
steps:
- name: Show disk space
run: |
@@ -818,7 +810,7 @@ jobs:
fi
- name: Upload memory analysis JSON
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: memory-analysis-target
path: memory-analysis-target.json
@@ -882,7 +874,7 @@ jobs:
--platform "$platform"
- name: Upload memory analysis JSON
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: memory-analysis-pr
path: memory-analysis-pr.json
@@ -912,13 +904,13 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Download target analysis JSON
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with:
name: memory-analysis-target
path: ./memory-analysis
continue-on-error: true
- name: Download PR analysis JSON
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with:
name: memory-analysis-pr
path: ./memory-analysis
@@ -949,7 +941,6 @@ jobs:
- clang-tidy-nosplit
- clang-tidy-split
- determine-jobs
- test-build-components-splitter
- test-build-components-split
- pre-commit-ci-lite
- memory-impact-target-branch

View File

@@ -58,7 +58,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
uses: github/codeql-action/init@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
@@ -86,6 +86,6 @@ jobs:
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
uses: github/codeql-action/analyze@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0
with:
category: "/language:${{matrix.language}}"

View File

@@ -138,7 +138,7 @@ jobs:
# version: ${{ needs.init.outputs.tag }}
- name: Upload digests
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: digests-${{ matrix.platform.arch }}
path: /tmp/digests
@@ -171,7 +171,7 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Download digests
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with:
pattern: digests-*
path: /tmp/digests

View File

@@ -14,6 +14,7 @@ jobs:
label:
- needs-docs
- merge-after-release
- chained-pr
steps:
- name: Check for ${{ matrix.label }} label
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0

View File

@@ -11,7 +11,7 @@ ci:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.14.1
rev: v0.14.2
hooks:
# Run the linter.
- id: ruff

View File

@@ -201,6 +201,7 @@ esphome/components/havells_solar/* @sourabhjaiswal
esphome/components/hbridge/fan/* @WeekendWarrior
esphome/components/hbridge/light/* @DotNetDann
esphome/components/hbridge/switch/* @dwmw2
esphome/components/hdc2010/* @optimusprimespace @ssieb
esphome/components/he60r/* @clydebarrow
esphome/components/heatpumpir/* @rob-deutsch
esphome/components/hitachi_ac424/* @sourabhjaiswal

View File

@@ -231,9 +231,22 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
api_component = (name, mem)
break
# Combine all components to analyze: top ESPHome + all external + API if not already included
components_to_analyze = list(top_esphome_components) + list(
top_external_components
# Also include wifi_stack and other important system components if they exist
system_components_to_include = [
# Empty list - we've finished debugging symbol categorization
# Add component names here if you need to debug their symbols
]
system_components = [
(name, mem)
for name, mem in components
if name in system_components_to_include
]
# Combine all components to analyze: top ESPHome + all external + API if not already included + system components
components_to_analyze = (
list(top_esphome_components)
+ list(top_external_components)
+ system_components
)
if api_component and api_component not in components_to_analyze:
components_to_analyze.append(api_component)

View File

@@ -127,40 +127,39 @@ SYMBOL_PATTERNS = {
"tryget_socket_unconn",
"cs_create_ctrl_sock",
"netbuf_alloc",
"tcp_", # TCP protocol functions
"udp_", # UDP protocol functions
"lwip_", # LwIP stack functions
"eagle_lwip", # ESP-specific LwIP functions
"new_linkoutput", # Link output function
"acd_", # Address Conflict Detection (ACD)
"eth_", # Ethernet functions
"mac_enable_bb", # MAC baseband enable
"reassemble_and_dispatch", # Packet reassembly
],
# dhcp must come before libc to avoid "dhcp_select" matching "select" pattern
"dhcp": ["dhcp", "handle_dhcp"],
"ipv6_stack": ["nd6_", "ip6_", "mld6_", "icmp6_", "icmp6_input"],
"wifi_stack": [
"ieee80211",
"hostap",
"sta_",
"ap_",
"scan_",
"wifi_",
"wpa_",
"wps_",
"esp_wifi",
"cnx_",
"wpa3_",
"sae_",
"wDev_",
"ic_",
"mac_",
"esf_buf",
"gWpaSm",
"sm_WPA",
"eapol_",
"owe_",
"wifiLowLevelInit",
"s_do_mapping",
"gScanStruct",
"ppSearchTxframe",
"ppMapWaitTxq",
"ppFillAMPDUBar",
"ppCheckTxConnTrafficIdle",
"ppCalTkipMic",
# Order matters! More specific categories must come before general ones.
# mdns must come before bluetooth to avoid "_mdns_disable_pcb" matching "ble_" pattern
"mdns_lib": ["mdns"],
# memory_mgmt must come before wifi_stack to catch mmu_hal_* symbols
"memory_mgmt": [
"mem_",
"memory_",
"tlsf_",
"memp_",
"pbuf_",
"pbuf_alloc",
"pbuf_copy_partial_pbuf",
"esp_mmu_map",
"mmu_hal_",
"s_do_mapping", # Memory mapping function, not WiFi
"hash_map_", # Hash map data structure
"umm_assimilate", # UMM malloc assimilation
],
"bluetooth": ["bt_", "ble_", "l2c_", "gatt_", "gap_", "hci_", "BT_init"],
"wifi_bt_coex": ["coex"],
# Bluetooth categories must come BEFORE wifi_stack to avoid misclassification
# Many BLE symbols contain patterns like "ble_" that would otherwise match wifi patterns
"bluetooth_rom": ["r_ble", "r_lld", "r_llc", "r_llm"],
"bluedroid_bt": [
"bluedroid",
@@ -207,6 +206,61 @@ SYMBOL_PATTERNS = {
"copy_extra_byte_in_db",
"parse_read_local_supported_commands_response",
],
"bluetooth": [
"bt_",
"_ble_", # More specific than "ble_" to avoid matching "able_", "enable_", "disable_"
"l2c_",
"l2ble_", # L2CAP for BLE
"gatt_",
"gap_",
"hci_",
"btsnd_hcic_", # Bluetooth HCI command send functions
"BT_init",
"BT_tx_", # Bluetooth transmit functions
"esp_ble_", # Catch esp_ble_* functions
],
"bluetooth_ll": [
"llm_", # Link layer manager
"llc_", # Link layer control
"lld_", # Link layer driver
"ld_acl_", # Link layer ACL (Asynchronous Connection-Oriented)
"llcp_", # Link layer control protocol
"lmp_", # Link manager protocol
],
"wifi_bt_coex": ["coex"],
"wifi_stack": [
"ieee80211",
"hostap",
"sta_",
"wifi_ap_", # More specific than "ap_" to avoid matching "cap_", "map_"
"wifi_scan_", # More specific than "scan_" to avoid matching "_scan_" in other contexts
"wifi_",
"wpa_",
"wps_",
"esp_wifi",
"cnx_",
"wpa3_",
"sae_",
"wDev_",
"ic_mac_", # More specific than "mac_" to avoid matching emac_
"esf_buf",
"gWpaSm",
"sm_WPA",
"eapol_",
"owe_",
"wifiLowLevelInit",
# Removed "s_do_mapping" - this is memory management, not WiFi
"gScanStruct",
"ppSearchTxframe",
"ppMapWaitTxq",
"ppFillAMPDUBar",
"ppCheckTxConnTrafficIdle",
"ppCalTkipMic",
"phy_force_wifi",
"phy_unforce_wifi",
"write_wifi_chan",
"wifi_track_pll",
],
"crypto_math": [
"ecp_",
"bignum_",
@@ -231,13 +285,36 @@ SYMBOL_PATTERNS = {
"p_256_init_curve",
"shift_sub_rows",
"rshift",
"rijndaelEncrypt", # AES Rijndael encryption
],
# System and Arduino core functions must come before libc
"esp_system": [
"system_", # ESP system functions
"postmortem_", # Postmortem reporting
],
"arduino_core": [
"pinMode",
"resetPins",
"millis",
"micros",
"delay(", # More specific - Arduino delay function with parenthesis
"delayMicroseconds",
"digitalWrite",
"digitalRead",
],
"sntp": ["sntp_", "sntp_recv"],
"scheduler": [
"run_scheduled_",
"compute_scheduled_",
"event_TaskQueue",
],
"hw_crypto": ["esp_aes", "esp_sha", "esp_rsa", "esp_bignum", "esp_mpi"],
"libc": [
"printf",
"scanf",
"malloc",
"free",
"_free", # More specific than "free" to match _free, __free_r, etc. but not arbitrary "free" substring
"umm_free", # UMM malloc free function
"memcpy",
"memset",
"strcpy",
@@ -259,7 +336,7 @@ SYMBOL_PATTERNS = {
"_setenv_r",
"_tzset_unlocked_r",
"__tzcalc_limits",
"select",
"_select", # More specific than "select" to avoid matching "dhcp_select", etc.
"scalbnf",
"strtof",
"strtof_l",
@@ -316,8 +393,24 @@ SYMBOL_PATTERNS = {
"CSWTCH$",
"dst$",
"sulp",
"_strtol_l", # String to long with locale
"__cvt", # Convert
"__utoa", # Unsigned to ASCII
"__global_locale", # Global locale
"_ctype_", # Character type
"impure_data", # Impure data
],
"string_ops": [
"strcmp",
"strncmp",
"strchr",
"strstr",
"strtok",
"strdup",
"strncasecmp_P", # String compare (case insensitive, from program memory)
"strnlen_P", # String length (from program memory)
"strncat_P", # String concatenate (from program memory)
],
"string_ops": ["strcmp", "strncmp", "strchr", "strstr", "strtok", "strdup"],
"memory_alloc": ["malloc", "calloc", "realloc", "free", "_sbrk"],
"file_io": [
"fread",
@@ -338,10 +431,26 @@ SYMBOL_PATTERNS = {
"vsscanf",
],
"cpp_anonymous": ["_GLOBAL__N_", "n$"],
"cpp_runtime": ["__cxx", "_ZN", "_ZL", "_ZSt", "__gxx_personality", "_Z16"],
"exception_handling": ["__cxa_", "_Unwind_", "__gcc_personality", "uw_frame_state"],
# Plain C patterns only - C++ symbols will be categorized via DEMANGLED_PATTERNS
"nvs": ["nvs_"], # Plain C NVS functions
"ota": ["ota_", "OTA", "esp_ota", "app_desc"],
# cpp_runtime: Removed _ZN, _ZL to let DEMANGLED_PATTERNS categorize C++ symbols properly
# Only keep patterns that are truly runtime-specific and not categorizable by namespace
"cpp_runtime": ["__cxx", "_ZSt", "__gxx_personality", "_Z16"],
"exception_handling": [
"__cxa_",
"_Unwind_",
"__gcc_personality",
"uw_frame_state",
"search_object", # Search for exception handling object
"get_cie_encoding", # Get CIE encoding
"add_fdes", # Add frame description entries
"fde_unencoded_compare", # Compare FDEs
"fde_mixed_encoding_compare", # Compare mixed encoding FDEs
"frame_downheap", # Frame heap operations
"frame_heapsort", # Frame heap sorting
],
"static_init": ["_GLOBAL__sub_I_"],
"mdns_lib": ["mdns"],
"phy_radio": [
"phy_",
"rf_",
@@ -394,10 +503,47 @@ SYMBOL_PATTERNS = {
"txcal_debuge_mode",
"ant_wifitx_cfg",
"reg_init_begin",
"tx_cap_init", # TX capacitance init
"ram_set_txcap", # RAM TX capacitance setting
"tx_atten_", # TX attenuation
"txiq_", # TX I/Q calibration
"ram_cal_", # RAM calibration
"ram_rxiq_", # RAM RX I/Q
"readvdd33", # Read VDD33
"test_tout", # Test timeout
"tsen_meas", # Temperature sensor measurement
"bbpll_cal", # Baseband PLL calibration
"set_cal_", # Set calibration
"set_rfanagain_", # Set RF analog gain
"set_txdc_", # Set TX DC
"get_vdd33_", # Get VDD33
"gen_rx_gain_table", # Generate RX gain table
"ram_ana_inf_gating_en", # RAM analog interface gating enable
"tx_cont_en", # TX continuous enable
"tx_delay_cfg", # TX delay configuration
"tx_gain_table_set", # TX gain table set
"check_and_reset_hw_deadlock", # Hardware deadlock check
"s_config", # System/hardware config
"chan14_mic_cfg", # Channel 14 MIC config
],
"wifi_phy_pp": [
"pp_",
"ppT",
"ppR",
"ppP",
"ppInstall",
"ppCalTxAMPDULength",
"ppCheckTx", # Packet processor TX check
"ppCal", # Packet processor calibration
"HdlAllBuffedEb", # Handle buffered EB
],
"wifi_phy_pp": ["pp_", "ppT", "ppR", "ppP", "ppInstall", "ppCalTxAMPDULength"],
"wifi_lmac": ["lmac"],
"wifi_device": ["wdev", "wDev_"],
"wifi_device": [
"wdev",
"wDev_",
"ic_set_sta", # Set station mode
"ic_set_vif", # Set virtual interface
],
"power_mgmt": [
"pm_",
"sleep",
@@ -406,15 +552,7 @@ SYMBOL_PATTERNS = {
"deep_sleep",
"power_down",
"g_pm",
],
"memory_mgmt": [
"mem_",
"memory_",
"tlsf_",
"memp_",
"pbuf_",
"pbuf_alloc",
"pbuf_copy_partial_pbuf",
"pmc", # Power Management Controller
],
"hal_layer": ["hal_"],
"clock_mgmt": [
@@ -439,7 +577,6 @@ SYMBOL_PATTERNS = {
"error_handling": ["panic", "abort", "assert", "error_", "fault"],
"authentication": ["auth"],
"ppp_protocol": ["ppp", "ipcp_", "lcp_", "chap_", "LcpEchoCheck"],
"dhcp": ["dhcp", "handle_dhcp"],
"ethernet_phy": [
"emac_",
"eth_phy_",
@@ -618,7 +755,15 @@ SYMBOL_PATTERNS = {
"ampdu_dispatch_upto",
],
"ieee802_11": ["ieee802_11_", "ieee802_11_parse_elems"],
"rate_control": ["rssi_margin", "rcGetSched", "get_rate_fcc_index"],
"rate_control": [
"rssi_margin",
"rcGetSched",
"get_rate_fcc_index",
"rcGetRate", # Get rate
"rc_get_", # Rate control getters
"rc_set_", # Rate control setters
"rc_enable_", # Rate control enable functions
],
"nan": ["nan_dp_", "nan_dp_post_tx", "nan_dp_delete_peer"],
"channel_mgmt": ["chm_init", "chm_set_current_channel"],
"trace": ["trc_init", "trc_onAmpduOp"],
@@ -799,31 +944,18 @@ SYMBOL_PATTERNS = {
"supports_interlaced_inquiry_scan",
"supports_reading_remote_extended_features",
],
"bluetooth_ll": [
"lld_pdu_",
"ld_acl_",
"lld_stop_ind_handler",
"lld_evt_winsize_change",
"config_lld_evt_funcs_reset",
"config_lld_funcs_reset",
"config_llm_funcs_reset",
"llm_set_long_adv_data",
"lld_retry_tx_prog",
"llc_link_sup_to_ind_handler",
"config_llc_funcs_reset",
"lld_evt_rxwin_compute",
"config_btdm_funcs_reset",
"config_ea_funcs_reset",
"llc_defalut_state_tab_reset",
"config_rwip_funcs_reset",
"ke_lmp_rx_flooding_detect",
],
}
# Demangled patterns: patterns found in demangled C++ names
DEMANGLED_PATTERNS = {
"gpio_driver": ["GPIO"],
"uart_driver": ["UART"],
# mdns_lib must come before network_stack to avoid "udp" matching "_udpReadBuffer" in MDNSResponder
"mdns_lib": [
"MDNSResponder",
"MDNSImplementation",
"MDNS",
],
"network_stack": [
"lwip",
"tcp",
@@ -836,6 +968,24 @@ DEMANGLED_PATTERNS = {
"ethernet",
"ppp",
"slip",
"UdpContext", # UDP context class
"DhcpServer", # DHCP server class
],
"arduino_core": [
"String::", # Arduino String class
"Print::", # Arduino Print class
"HardwareSerial::", # Serial class
"IPAddress::", # IP address class
"EspClass::", # ESP class
"experimental::_SPI", # Experimental SPI
],
"ota": [
"UpdaterClass",
"Updater::",
],
"wifi": [
"ESP8266WiFi",
"WiFi::",
],
"wifi_stack": ["NetworkInterface"],
"nimble_bt": [
@@ -854,7 +1004,6 @@ DEMANGLED_PATTERNS = {
"rtti": ["__type_info", "__class_type_info"],
"web_server_lib": ["AsyncWebServer", "AsyncWebHandler", "WebServer"],
"async_tcp": ["AsyncClient", "AsyncServer"],
"mdns_lib": ["mdns"],
"json_lib": [
"ArduinoJson",
"JsonDocument",

View File

@@ -16,7 +16,12 @@ from esphome.const import (
CONF_UPDATE_INTERVAL,
)
from esphome.core import ID
from esphome.cpp_generator import MockObj, MockObjClass, TemplateArgsType
from esphome.cpp_generator import (
LambdaExpression,
MockObj,
MockObjClass,
TemplateArgsType,
)
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
from esphome.types import ConfigType
from esphome.util import Registry
@@ -87,6 +92,7 @@ def validate_potentially_or_condition(value):
DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component)
LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
StatelessLambdaAction = cg.esphome_ns.class_("StatelessLambdaAction", Action)
IfAction = cg.esphome_ns.class_("IfAction", Action)
WhileAction = cg.esphome_ns.class_("WhileAction", Action)
RepeatAction = cg.esphome_ns.class_("RepeatAction", Action)
@@ -97,9 +103,40 @@ ResumeComponentAction = cg.esphome_ns.class_("ResumeComponentAction", Action)
Automation = cg.esphome_ns.class_("Automation")
LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition)
StatelessLambdaCondition = cg.esphome_ns.class_("StatelessLambdaCondition", Condition)
ForCondition = cg.esphome_ns.class_("ForCondition", Condition, cg.Component)
def new_lambda_pvariable(
id_obj: ID,
lambda_expr: LambdaExpression,
stateless_class: MockObjClass,
template_arg: cg.TemplateArguments | None = None,
) -> MockObj:
"""Create Pvariable for lambda, using stateless class if applicable.
Combines ID selection and Pvariable creation in one call. For stateless
lambdas (empty capture), uses function pointer instead of std::function.
Args:
id_obj: The ID object (action_id, condition_id, or filter_id)
lambda_expr: The lambda expression object
stateless_class: The stateless class to use for stateless lambdas
template_arg: Optional template arguments (for actions/conditions)
Returns:
The created Pvariable
"""
# For stateless lambdas, use function pointer instead of std::function
if lambda_expr.capture == "":
id_obj = id_obj.copy()
id_obj.type = stateless_class
if template_arg is not None:
return cg.new_Pvariable(id_obj, template_arg, lambda_expr)
return cg.new_Pvariable(id_obj, lambda_expr)
def validate_automation(extra_schema=None, extra_validators=None, single=False):
if extra_schema is None:
extra_schema = {}
@@ -240,7 +277,9 @@ async def lambda_condition_to_code(
args: TemplateArgsType,
) -> MockObj:
lambda_ = await cg.process_lambda(config, args, return_type=bool)
return cg.new_Pvariable(condition_id, template_arg, lambda_)
return new_lambda_pvariable(
condition_id, lambda_, StatelessLambdaCondition, template_arg
)
@register_condition(
@@ -406,7 +445,7 @@ async def lambda_action_to_code(
args: TemplateArgsType,
) -> MockObj:
lambda_ = await cg.process_lambda(config, args, return_type=cg.void)
return cg.new_Pvariable(action_id, template_arg, lambda_)
return new_lambda_pvariable(action_id, lambda_, StatelessLambdaAction, template_arg)
@register_action(

View File

@@ -62,6 +62,7 @@ from esphome.cpp_types import ( # noqa: F401
EntityBase,
EntityCategory,
ESPTime,
FixedVector,
GPIOPin,
InternalGPIOPin,
JsonObject,

View File

@@ -9,7 +9,7 @@ static const char *const TAG = "adalight_light_effect";
static const uint32_t ADALIGHT_ACK_INTERVAL = 1000;
static const uint32_t ADALIGHT_RECEIVE_TIMEOUT = 1000;
AdalightLightEffect::AdalightLightEffect(const std::string &name) : AddressableLightEffect(name) {}
AdalightLightEffect::AdalightLightEffect(const char *name) : AddressableLightEffect(name) {}
void AdalightLightEffect::start() {
AddressableLightEffect::start();

View File

@@ -11,7 +11,7 @@ namespace adalight {
class AdalightLightEffect : public light::AddressableLightEffect, public uart::UARTDevice {
public:
AdalightLightEffect(const std::string &name);
AdalightLightEffect(const char *name);
void start() override;
void stop() override;

View File

@@ -172,12 +172,6 @@ def alarm_control_panel_schema(
return _ALARM_CONTROL_PANEL_SCHEMA.extend(schema)
# Remove before 2025.11.0
ALARM_CONTROL_PANEL_SCHEMA = alarm_control_panel_schema(AlarmControlPanel)
ALARM_CONTROL_PANEL_SCHEMA.add_extra(
cv.deprecated_schema_constant("alarm_control_panel")
)
ALARM_CONTROL_PANEL_ACTION_SCHEMA = maybe_simple_id(
{
cv.GenerateID(): cv.use_id(AlarmControlPanel),

View File

@@ -71,10 +71,12 @@ SERVICE_ARG_NATIVE_TYPES = {
"int": cg.int32,
"float": float,
"string": cg.std_string,
"bool[]": cg.std_vector.template(bool),
"int[]": cg.std_vector.template(cg.int32),
"float[]": cg.std_vector.template(float),
"string[]": cg.std_vector.template(cg.std_string),
"bool[]": cg.FixedVector.template(bool).operator("const").operator("ref"),
"int[]": cg.FixedVector.template(cg.int32).operator("const").operator("ref"),
"float[]": cg.FixedVector.template(float).operator("const").operator("ref"),
"string[]": cg.FixedVector.template(cg.std_string)
.operator("const")
.operator("ref"),
}
CONF_ENCRYPTION = "encryption"
CONF_BATCH_DELAY = "batch_delay"
@@ -258,6 +260,10 @@ async def to_code(config):
if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]:
cg.add_define("USE_API_SERVICES")
# Set USE_API_CUSTOM_SERVICES if external components need dynamic service registration
if config[CONF_CUSTOM_SERVICES]:
cg.add_define("USE_API_CUSTOM_SERVICES")
if config[CONF_HOMEASSISTANT_SERVICES]:
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
@@ -265,6 +271,8 @@ async def to_code(config):
cg.add_define("USE_API_HOMEASSISTANT_STATES")
if actions := config.get(CONF_ACTIONS, []):
# Collect all triggers first, then register all at once with initializer_list
triggers: list[cg.Pvariable] = []
for conf in actions:
template_args = []
func_args = []
@@ -278,8 +286,10 @@ async def to_code(config):
trigger = cg.new_Pvariable(
conf[CONF_TRIGGER_ID], templ, conf[CONF_ACTION], service_arg_names
)
cg.add(var.register_user_service(trigger))
triggers.append(trigger)
await automation.build_automation(trigger, func_args, conf)
# Register all services at once - single allocation, no reallocations
cg.add(var.initialize_user_services(triggers))
if CONF_ON_CLIENT_CONNECTED in config:
cg.add_define("USE_API_CLIENT_CONNECTED_TRIGGER")

View File

@@ -989,7 +989,7 @@ message ListEntitiesClimateResponse {
bool supports_current_temperature = 5; // Deprecated: use feature_flags
bool supports_two_point_target_temperature = 6; // Deprecated: use feature_flags
repeated ClimateMode supported_modes = 7 [(container_pointer) = "std::set<climate::ClimateMode>"];
repeated ClimateMode supported_modes = 7 [(container_pointer_no_template) = "climate::ClimateModeMask"];
float visual_min_temperature = 8;
float visual_max_temperature = 9;
float visual_target_temperature_step = 10;
@@ -998,11 +998,11 @@ message ListEntitiesClimateResponse {
// Deprecated in API version 1.5
bool legacy_supports_away = 11 [deprecated=true];
bool supports_action = 12; // Deprecated: use feature_flags
repeated ClimateFanMode supported_fan_modes = 13 [(container_pointer) = "std::set<climate::ClimateFanMode>"];
repeated ClimateSwingMode supported_swing_modes = 14 [(container_pointer) = "std::set<climate::ClimateSwingMode>"];
repeated string supported_custom_fan_modes = 15 [(container_pointer) = "std::set"];
repeated ClimatePreset supported_presets = 16 [(container_pointer) = "std::set<climate::ClimatePreset>"];
repeated string supported_custom_presets = 17 [(container_pointer) = "std::set"];
repeated ClimateFanMode supported_fan_modes = 13 [(container_pointer_no_template) = "climate::ClimateFanModeMask"];
repeated ClimateSwingMode supported_swing_modes = 14 [(container_pointer_no_template) = "climate::ClimateSwingModeMask"];
repeated string supported_custom_fan_modes = 15 [(container_pointer) = "std::vector"];
repeated ClimatePreset supported_presets = 16 [(container_pointer_no_template) = "climate::ClimatePresetMask"];
repeated string supported_custom_presets = 17 [(container_pointer) = "std::vector"];
bool disabled_by_default = 18;
string icon = 19 [(field_ifdef) = "USE_ENTITY_ICON"];
EntityCategory entity_category = 20;
@@ -1143,7 +1143,7 @@ message ListEntitiesSelectResponse {
reserved 4; // Deprecated: was string unique_id
string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"];
repeated string options = 6 [(container_pointer) = "std::vector"];
repeated string options = 6 [(container_pointer_no_template) = "FixedVector<const char *>"];
bool disabled_by_default = 7;
EntityCategory entity_category = 8;
uint32 device_id = 9 [(field_ifdef) = "USE_DEVICES"];

View File

@@ -1,3 +1,5 @@
// random comment
#include "api_connection.h"
#ifdef USE_API
#ifdef USE_API_NOISE
@@ -486,7 +488,7 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c
if (light->supports_effects()) {
msg.effects.emplace_back("None");
for (auto *effect : light->get_effects()) {
msg.effects.push_back(effect->get_name());
msg.effects.emplace_back(effect->get_name());
}
}
return fill_and_encode_entity_info(light, msg, ListEntitiesLightResponse::MESSAGE_TYPE, conn, remaining_size,
@@ -669,18 +671,18 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection
msg.supports_action = traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION);
// Current feature flags and other supported parameters
msg.feature_flags = traits.get_feature_flags();
msg.supported_modes = &traits.get_supported_modes_for_api_();
msg.supported_modes = &traits.get_supported_modes();
msg.visual_min_temperature = traits.get_visual_min_temperature();
msg.visual_max_temperature = traits.get_visual_max_temperature();
msg.visual_target_temperature_step = traits.get_visual_target_temperature_step();
msg.visual_current_temperature_step = traits.get_visual_current_temperature_step();
msg.visual_min_humidity = traits.get_visual_min_humidity();
msg.visual_max_humidity = traits.get_visual_max_humidity();
msg.supported_fan_modes = &traits.get_supported_fan_modes_for_api_();
msg.supported_custom_fan_modes = &traits.get_supported_custom_fan_modes_for_api_();
msg.supported_presets = &traits.get_supported_presets_for_api_();
msg.supported_custom_presets = &traits.get_supported_custom_presets_for_api_();
msg.supported_swing_modes = &traits.get_supported_swing_modes_for_api_();
msg.supported_fan_modes = &traits.get_supported_fan_modes();
msg.supported_custom_fan_modes = &traits.get_supported_custom_fan_modes();
msg.supported_presets = &traits.get_supported_presets();
msg.supported_custom_presets = &traits.get_supported_custom_presets();
msg.supported_swing_modes = &traits.get_supported_swing_modes();
return fill_and_encode_entity_info(climate, msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
@@ -1572,7 +1574,13 @@ bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryption
resp.success = false;
psk_t psk{};
if (base64_decode(msg.key, psk.data(), msg.key.size()) != psk.size()) {
if (msg.key.empty()) {
if (this->parent_->clear_noise_psk(true)) {
resp.success = true;
} else {
ESP_LOGW(TAG, "Failed to clear encryption key");
}
} else if (base64_decode(msg.key, psk.data(), msg.key.size()) != psk.size()) {
ESP_LOGW(TAG, "Invalid encryption key length");
} else if (!this->parent_->save_noise_psk(psk, true)) {
ESP_LOGW(TAG, "Failed to save encryption key");

View File

@@ -1475,8 +1475,8 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const {
#ifdef USE_ENTITY_ICON
buffer.encode_string(5, this->icon_ref_);
#endif
for (const auto &it : *this->options) {
buffer.encode_string(6, it, true);
for (const char *it : *this->options) {
buffer.encode_string(6, it, strlen(it), true);
}
buffer.encode_bool(7, this->disabled_by_default);
buffer.encode_uint32(8, static_cast<uint32_t>(this->entity_category));
@@ -1492,8 +1492,8 @@ void ListEntitiesSelectResponse::calculate_size(ProtoSize &size) const {
size.add_length(1, this->icon_ref_.size());
#endif
if (!this->options->empty()) {
for (const auto &it : *this->options) {
size.add_length_force(1, it.size());
for (const char *it : *this->options) {
size.add_length_force(1, strlen(it));
}
}
size.add_bool(1, this->disabled_by_default);

View File

@@ -1377,16 +1377,16 @@ class ListEntitiesClimateResponse final : public InfoResponseProtoMessage {
#endif
bool supports_current_temperature{false};
bool supports_two_point_target_temperature{false};
const std::set<climate::ClimateMode> *supported_modes{};
const climate::ClimateModeMask *supported_modes{};
float visual_min_temperature{0.0f};
float visual_max_temperature{0.0f};
float visual_target_temperature_step{0.0f};
bool supports_action{false};
const std::set<climate::ClimateFanMode> *supported_fan_modes{};
const std::set<climate::ClimateSwingMode> *supported_swing_modes{};
const std::set<std::string> *supported_custom_fan_modes{};
const std::set<climate::ClimatePreset> *supported_presets{};
const std::set<std::string> *supported_custom_presets{};
const climate::ClimateFanModeMask *supported_fan_modes{};
const climate::ClimateSwingModeMask *supported_swing_modes{};
const std::vector<std::string> *supported_custom_fan_modes{};
const climate::ClimatePresetMask *supported_presets{};
const std::vector<std::string> *supported_custom_presets{};
float visual_current_temperature_step{0.0f};
bool supports_current_humidity{false};
bool supports_target_humidity{false};
@@ -1534,7 +1534,7 @@ class ListEntitiesSelectResponse final : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_select_response"; }
#endif
const std::vector<std::string> *options{};
const FixedVector<const char *> *options{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP

View File

@@ -88,6 +88,12 @@ static void dump_field(std::string &out, const char *field_name, StringRef value
out.append("\n");
}
static void dump_field(std::string &out, const char *field_name, const char *value, int indent = 2) {
append_field_prefix(out, field_name, indent);
out.append("'").append(value).append("'");
out.append("\n");
}
template<typename T> static void dump_field(std::string &out, const char *field_name, T value, int indent = 2) {
append_field_prefix(out, field_name, indent);
out.append(proto_enum_to_string<T>(value));

View File

@@ -468,6 +468,31 @@ 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, const psk_t &active_psk, bool make_active) {
if (!this->noise_pref_.save(&new_psk)) {
ESP_LOGW(TAG, "%s", LOG_STR_ARG(fail_log_msg));
return false;
}
// ensure it's written immediately
if (!global_preferences->sync()) {
ESP_LOGW(TAG, "Failed to sync preferences");
return false;
}
ESP_LOGD(TAG, "%s", LOG_STR_ARG(save_log_msg));
if (make_active) {
this->set_timeout(100, [this, active_psk]() {
ESP_LOGW(TAG, "Disconnecting all clients to reset PSK");
this->set_noise_psk(active_psk);
for (auto &c : this->clients_) {
DisconnectRequest req;
c->send_message(req, DisconnectRequest::MESSAGE_TYPE);
}
});
}
return true;
}
bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
#ifdef USE_API_NOISE_PSK_FROM_YAML
// When PSK is set from YAML, this function should never be called
@@ -482,27 +507,21 @@ bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
}
SavedNoisePsk new_saved_psk{psk};
if (!this->noise_pref_.save(&new_saved_psk)) {
ESP_LOGW(TAG, "Failed to save Noise PSK");
return false;
}
// ensure it's written immediately
if (!global_preferences->sync()) {
ESP_LOGW(TAG, "Failed to sync preferences");
return false;
}
ESP_LOGD(TAG, "Noise PSK saved");
if (make_active) {
this->set_timeout(100, [this, psk]() {
ESP_LOGW(TAG, "Disconnecting all clients to reset PSK");
this->set_noise_psk(psk);
for (auto &c : this->clients_) {
DisconnectRequest req;
c->send_message(req, DisconnectRequest::MESSAGE_TYPE);
}
});
}
return true;
return this->update_noise_psk_(new_saved_psk, LOG_STR("Noise PSK saved"), LOG_STR("Failed to save Noise PSK"), psk,
make_active);
#endif
}
bool APIServer::clear_noise_psk(bool make_active) {
#ifdef USE_API_NOISE_PSK_FROM_YAML
// When PSK is set from YAML, this function should never be called
// but if it is, reject the change
ESP_LOGW(TAG, "Key set in YAML");
return false;
#else
SavedNoisePsk empty_psk{};
psk_t empty{};
return this->update_noise_psk_(empty_psk, LOG_STR("Noise PSK cleared"), LOG_STR("Failed to clear Noise PSK"), empty,
make_active);
#endif
}
#endif

View File

@@ -53,6 +53,7 @@ class APIServer : public Component, public Controller {
#ifdef USE_API_NOISE
bool save_noise_psk(psk_t psk, bool make_active = true);
bool clear_noise_psk(bool make_active = true);
void set_noise_psk(psk_t psk) { noise_ctx_->set_psk(psk); }
std::shared_ptr<APINoiseContext> get_noise_ctx() { return noise_ctx_; }
#endif // USE_API_NOISE
@@ -124,8 +125,14 @@ class APIServer : public Component, public Controller {
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
#endif // USE_API_HOMEASSISTANT_SERVICES
#ifdef USE_API_SERVICES
void initialize_user_services(std::initializer_list<UserServiceDescriptor *> services) {
this->user_services_.assign(services);
}
#ifdef USE_API_CUSTOM_SERVICES
// Only compile push_back method when custom_services: true (external components)
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
#endif
#endif
#ifdef USE_HOMEASSISTANT_TIME
void request_time();
#endif
@@ -174,6 +181,10 @@ class APIServer : public Component, public Controller {
protected:
void schedule_reboot_timeout_();
#ifdef USE_API_NOISE
bool update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg, const LogString *fail_log_msg,
const psk_t &active_psk, bool make_active);
#endif // USE_API_NOISE
// Pointers and pointer-like types first (4 bytes each)
std::unique_ptr<socket::Socket> socket_ = nullptr;
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER

View File

@@ -53,8 +53,14 @@ class CustomAPIDevice {
template<typename T, typename... Ts>
void register_service(void (T::*callback)(Ts...), const std::string &name,
const std::array<std::string, sizeof...(Ts)> &arg_names) {
#ifdef USE_API_CUSTOM_SERVICES
auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback); // NOLINT
global_api_server->register_user_service(service);
#else
static_assert(
sizeof(T) == 0,
"register_service() requires 'custom_services: true' in the 'api:' section of your YAML configuration");
#endif
}
#else
template<typename T, typename... Ts>
@@ -86,8 +92,14 @@ class CustomAPIDevice {
*/
#ifdef USE_API_SERVICES
template<typename T> void register_service(void (T::*callback)(), const std::string &name) {
#ifdef USE_API_CUSTOM_SERVICES
auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback); // NOLINT
global_api_server->register_user_service(service);
#else
static_assert(
sizeof(T) == 0,
"register_service() requires 'custom_services: true' in the 'api:' section of your YAML configuration");
#endif
}
#else
template<typename T> void register_service(void (T::*callback)(), const std::string &name) {

View File

@@ -11,23 +11,58 @@ template<> int32_t get_execute_arg_value<int32_t>(const ExecuteServiceArgument &
}
template<> float get_execute_arg_value<float>(const ExecuteServiceArgument &arg) { return arg.float_; }
template<> std::string get_execute_arg_value<std::string>(const ExecuteServiceArgument &arg) { return arg.string_; }
// Legacy std::vector versions for external components using custom_api_device.h - optimized with reserve
template<> std::vector<bool> get_execute_arg_value<std::vector<bool>>(const ExecuteServiceArgument &arg) {
return std::vector<bool>(arg.bool_array.begin(), arg.bool_array.end());
std::vector<bool> result;
result.reserve(arg.bool_array.size());
result.insert(result.end(), arg.bool_array.begin(), arg.bool_array.end());
return result;
}
template<> std::vector<int32_t> get_execute_arg_value<std::vector<int32_t>>(const ExecuteServiceArgument &arg) {
return std::vector<int32_t>(arg.int_array.begin(), arg.int_array.end());
std::vector<int32_t> result;
result.reserve(arg.int_array.size());
result.insert(result.end(), arg.int_array.begin(), arg.int_array.end());
return result;
}
template<> std::vector<float> get_execute_arg_value<std::vector<float>>(const ExecuteServiceArgument &arg) {
return std::vector<float>(arg.float_array.begin(), arg.float_array.end());
std::vector<float> result;
result.reserve(arg.float_array.size());
result.insert(result.end(), arg.float_array.begin(), arg.float_array.end());
return result;
}
template<> std::vector<std::string> get_execute_arg_value<std::vector<std::string>>(const ExecuteServiceArgument &arg) {
return std::vector<std::string>(arg.string_array.begin(), arg.string_array.end());
std::vector<std::string> result;
result.reserve(arg.string_array.size());
result.insert(result.end(), arg.string_array.begin(), arg.string_array.end());
return result;
}
// New FixedVector const reference versions for YAML-generated services - zero-copy
template<>
const FixedVector<bool> &get_execute_arg_value<const FixedVector<bool> &>(const ExecuteServiceArgument &arg) {
return arg.bool_array;
}
template<>
const FixedVector<int32_t> &get_execute_arg_value<const FixedVector<int32_t> &>(const ExecuteServiceArgument &arg) {
return arg.int_array;
}
template<>
const FixedVector<float> &get_execute_arg_value<const FixedVector<float> &>(const ExecuteServiceArgument &arg) {
return arg.float_array;
}
template<>
const FixedVector<std::string> &get_execute_arg_value<const FixedVector<std::string> &>(
const ExecuteServiceArgument &arg) {
return arg.string_array;
}
template<> enums::ServiceArgType to_service_arg_type<bool>() { return enums::SERVICE_ARG_TYPE_BOOL; }
template<> enums::ServiceArgType to_service_arg_type<int32_t>() { return enums::SERVICE_ARG_TYPE_INT; }
template<> enums::ServiceArgType to_service_arg_type<float>() { return enums::SERVICE_ARG_TYPE_FLOAT; }
template<> enums::ServiceArgType to_service_arg_type<std::string>() { return enums::SERVICE_ARG_TYPE_STRING; }
// Legacy std::vector versions for external components using custom_api_device.h
template<> enums::ServiceArgType to_service_arg_type<std::vector<bool>>() { return enums::SERVICE_ARG_TYPE_BOOL_ARRAY; }
template<> enums::ServiceArgType to_service_arg_type<std::vector<int32_t>>() {
return enums::SERVICE_ARG_TYPE_INT_ARRAY;
@@ -39,4 +74,18 @@ template<> enums::ServiceArgType to_service_arg_type<std::vector<std::string>>()
return enums::SERVICE_ARG_TYPE_STRING_ARRAY;
}
// New FixedVector const reference versions for YAML-generated services
template<> enums::ServiceArgType to_service_arg_type<const FixedVector<bool> &>() {
return enums::SERVICE_ARG_TYPE_BOOL_ARRAY;
}
template<> enums::ServiceArgType to_service_arg_type<const FixedVector<int32_t> &>() {
return enums::SERVICE_ARG_TYPE_INT_ARRAY;
}
template<> enums::ServiceArgType to_service_arg_type<const FixedVector<float> &>() {
return enums::SERVICE_ARG_TYPE_FLOAT_ARRAY;
}
template<> enums::ServiceArgType to_service_arg_type<const FixedVector<std::string> &>() {
return enums::SERVICE_ARG_TYPE_STRING_ARRAY;
}
} // namespace esphome::api

View File

@@ -99,9 +99,8 @@ enum BedjetCommand : uint8_t {
static const uint8_t BEDJET_FAN_SPEED_COUNT = 20;
static const char *const BEDJET_FAN_STEP_NAMES[BEDJET_FAN_SPEED_COUNT] = BEDJET_FAN_STEP_NAMES_;
static constexpr const char *const BEDJET_FAN_STEP_NAMES[BEDJET_FAN_SPEED_COUNT] = BEDJET_FAN_STEP_NAMES_;
static const std::string BEDJET_FAN_STEP_NAME_STRINGS[BEDJET_FAN_SPEED_COUNT] = BEDJET_FAN_STEP_NAMES_;
static const std::set<std::string> BEDJET_FAN_STEP_NAMES_SET BEDJET_FAN_STEP_NAMES_;
} // namespace bedjet
} // namespace esphome

View File

@@ -43,7 +43,7 @@ class BedJetClimate : public climate::Climate, public BedJetClient, public Polli
});
// It would be better if we had a slider for the fan modes.
traits.set_supported_custom_fan_modes(BEDJET_FAN_STEP_NAMES_SET);
traits.set_supported_custom_fan_modes(BEDJET_FAN_STEP_NAMES);
traits.set_supported_presets({
// If we support NONE, then have to decide what happens if the user switches to it (turn off?)
// climate::CLIMATE_PRESET_NONE,

View File

@@ -155,6 +155,7 @@ DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Compon
InvertFilter = binary_sensor_ns.class_("InvertFilter", Filter)
AutorepeatFilter = binary_sensor_ns.class_("AutorepeatFilter", Filter, cg.Component)
LambdaFilter = binary_sensor_ns.class_("LambdaFilter", Filter)
StatelessLambdaFilter = binary_sensor_ns.class_("StatelessLambdaFilter", Filter)
SettleFilter = binary_sensor_ns.class_("SettleFilter", Filter, cg.Component)
_LOGGER = getLogger(__name__)
@@ -264,20 +265,31 @@ async def delayed_off_filter_to_code(config, filter_id):
),
)
async def autorepeat_filter_to_code(config, filter_id):
timings = []
if len(config) > 0:
timings.extend(
(conf[CONF_DELAY], conf[CONF_TIME_OFF], conf[CONF_TIME_ON])
for conf in config
)
else:
timings.append(
(
cv.time_period_str_unit(DEFAULT_DELAY).total_milliseconds,
cv.time_period_str_unit(DEFAULT_TIME_OFF).total_milliseconds,
cv.time_period_str_unit(DEFAULT_TIME_ON).total_milliseconds,
timings = [
cg.StructInitializer(
cg.MockObj("AutorepeatFilterTiming", "esphome::binary_sensor::"),
("delay", conf[CONF_DELAY]),
("time_off", conf[CONF_TIME_OFF]),
("time_on", conf[CONF_TIME_ON]),
)
)
for conf in config
]
else:
timings = [
cg.StructInitializer(
cg.MockObj("AutorepeatFilterTiming", "esphome::binary_sensor::"),
("delay", cv.time_period_str_unit(DEFAULT_DELAY).total_milliseconds),
(
"time_off",
cv.time_period_str_unit(DEFAULT_TIME_OFF).total_milliseconds,
),
(
"time_on",
cv.time_period_str_unit(DEFAULT_TIME_ON).total_milliseconds,
),
)
]
var = cg.new_Pvariable(filter_id, timings)
await cg.register_component(var, {})
return var
@@ -288,7 +300,7 @@ async def lambda_filter_to_code(config, filter_id):
lambda_ = await cg.process_lambda(
config, [(bool, "x")], return_type=cg.optional.template(bool)
)
return cg.new_Pvariable(filter_id, lambda_)
return automation.new_lambda_pvariable(filter_id, lambda_, StatelessLambdaFilter)
@register_filter(
@@ -536,11 +548,6 @@ def binary_sensor_schema(
return _BINARY_SENSOR_SCHEMA.extend(schema)
# Remove before 2025.11.0
BINARY_SENSOR_SCHEMA = binary_sensor_schema()
BINARY_SENSOR_SCHEMA.add_extra(cv.deprecated_schema_constant("binary_sensor"))
async def setup_binary_sensor_core_(var, config):
await setup_entity(var, config, "binary_sensor")

View File

@@ -2,11 +2,11 @@
#include <cinttypes>
#include <utility>
#include <vector>
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
namespace esphome {
@@ -92,8 +92,8 @@ class DoubleClickTrigger : public Trigger<> {
class MultiClickTrigger : public Trigger<>, public Component {
public:
explicit MultiClickTrigger(BinarySensor *parent, std::vector<MultiClickTriggerEvent> timing)
: parent_(parent), timing_(std::move(timing)) {}
explicit MultiClickTrigger(BinarySensor *parent, std::initializer_list<MultiClickTriggerEvent> timing)
: parent_(parent), timing_(timing) {}
void setup() override {
this->last_state_ = this->parent_->get_state_default(false);
@@ -115,7 +115,7 @@ class MultiClickTrigger : public Trigger<>, public Component {
void trigger_();
BinarySensor *parent_;
std::vector<MultiClickTriggerEvent> timing_;
FixedVector<MultiClickTriggerEvent> timing_;
uint32_t invalid_cooldown_{1000};
optional<size_t> at_index_{};
bool last_state_{false};

View File

@@ -51,7 +51,7 @@ void BinarySensor::add_filter(Filter *filter) {
last_filter->next_ = filter;
}
}
void BinarySensor::add_filters(const std::vector<Filter *> &filters) {
void BinarySensor::add_filters(std::initializer_list<Filter *> filters) {
for (Filter *filter : filters) {
this->add_filter(filter);
}

View File

@@ -4,7 +4,7 @@
#include "esphome/core/helpers.h"
#include "esphome/components/binary_sensor/filter.h"
#include <vector>
#include <initializer_list>
namespace esphome {
@@ -48,7 +48,7 @@ class BinarySensor : public StatefulEntityBase<bool>, public EntityBase_DeviceCl
void publish_initial_state(bool new_state);
void add_filter(Filter *filter);
void add_filters(const std::vector<Filter *> &filters);
void add_filters(std::initializer_list<Filter *> filters);
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)

View File

@@ -1,7 +1,6 @@
#include "filter.h"
#include "binary_sensor.h"
#include <utility>
namespace esphome {
@@ -68,7 +67,7 @@ float DelayedOffFilter::get_setup_priority() const { return setup_priority::HARD
optional<bool> InvertFilter::new_value(bool value) { return !value; }
AutorepeatFilter::AutorepeatFilter(std::vector<AutorepeatFilterTiming> timings) : timings_(std::move(timings)) {}
AutorepeatFilter::AutorepeatFilter(std::initializer_list<AutorepeatFilterTiming> timings) : timings_(timings) {}
optional<bool> AutorepeatFilter::new_value(bool value) {
if (value) {

View File

@@ -4,8 +4,6 @@
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include <vector>
namespace esphome {
namespace binary_sensor {
@@ -82,11 +80,6 @@ class InvertFilter : public Filter {
};
struct AutorepeatFilterTiming {
AutorepeatFilterTiming(uint32_t delay, uint32_t off, uint32_t on) {
this->delay = delay;
this->time_off = off;
this->time_on = on;
}
uint32_t delay;
uint32_t time_off;
uint32_t time_on;
@@ -94,7 +87,7 @@ struct AutorepeatFilterTiming {
class AutorepeatFilter : public Filter, public Component {
public:
explicit AutorepeatFilter(std::vector<AutorepeatFilterTiming> timings);
explicit AutorepeatFilter(std::initializer_list<AutorepeatFilterTiming> timings);
optional<bool> new_value(bool value) override;
@@ -104,7 +97,7 @@ class AutorepeatFilter : public Filter, public Component {
void next_timing_();
void next_value_(bool val);
std::vector<AutorepeatFilterTiming> timings_;
FixedVector<AutorepeatFilterTiming> timings_;
uint8_t active_timing_{0};
};
@@ -118,6 +111,21 @@ class LambdaFilter : public Filter {
std::function<optional<bool>(bool)> f_;
};
/** Optimized lambda filter for stateless lambdas (no capture).
*
* Uses function pointer instead of std::function to reduce memory overhead.
* Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function).
*/
class StatelessLambdaFilter : public Filter {
public:
explicit StatelessLambdaFilter(optional<bool> (*f)(bool)) : f_(f) {}
optional<bool> new_value(bool value) override { return this->f_(value); }
protected:
optional<bool> (*f_)(bool);
};
class SettleFilter : public Filter, public Component {
public:
optional<bool> new_value(bool value) override;

View File

@@ -96,8 +96,11 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
BLEClientWriteAction(BLEClient *ble_client) {
ble_client->register_ble_node(this);
ble_client_ = ble_client;
this->construct_simple_value_();
}
~BLEClientWriteAction() { this->destroy_simple_value_(); }
void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
@@ -106,14 +109,18 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
void set_value_template(std::function<std::vector<uint8_t>(Ts...)> func) {
this->value_template_ = std::move(func);
has_simple_value_ = false;
void set_value_template(std::vector<uint8_t> (*func)(Ts...)) {
this->destroy_simple_value_();
this->value_.template_func = func;
this->has_simple_value_ = false;
}
void set_value_simple(const std::vector<uint8_t> &value) {
this->value_simple_ = value;
has_simple_value_ = true;
if (!this->has_simple_value_) {
this->construct_simple_value_();
}
this->value_.simple = value;
this->has_simple_value_ = true;
}
void play(Ts... x) override {}
@@ -121,7 +128,7 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
void play_complex(Ts... x) override {
this->num_running_++;
this->var_ = std::make_tuple(x...);
auto value = this->has_simple_value_ ? this->value_simple_ : this->value_template_(x...);
auto value = this->has_simple_value_ ? this->value_.simple : this->value_.template_func(x...);
// on write failure, continue the automation chain rather than stopping so that e.g. disconnect can work.
if (!write(value))
this->play_next_(x...);
@@ -194,10 +201,22 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
}
private:
void construct_simple_value_() { new (&this->value_.simple) std::vector<uint8_t>(); }
void destroy_simple_value_() {
if (this->has_simple_value_) {
this->value_.simple.~vector();
}
}
BLEClient *ble_client_;
bool has_simple_value_ = true;
std::vector<uint8_t> value_simple_;
std::function<std::vector<uint8_t>(Ts...)> value_template_{};
union Value {
std::vector<uint8_t> simple;
std::vector<uint8_t> (*template_func)(Ts...);
Value() {} // trivial constructor
~Value() {} // trivial destructor - we manage lifetime via discriminator
} value_;
espbt::ESPBTUUID service_uuid_;
espbt::ESPBTUUID char_uuid_;
std::tuple<Ts...> var_{};
@@ -213,9 +232,9 @@ template<typename... Ts> class BLEClientPasskeyReplyAction : public Action<Ts...
void play(Ts... x) override {
uint32_t passkey;
if (has_simple_value_) {
passkey = this->value_simple_;
passkey = this->value_.simple;
} else {
passkey = this->value_template_(x...);
passkey = this->value_.template_func(x...);
}
if (passkey > 999999)
return;
@@ -224,21 +243,23 @@ template<typename... Ts> class BLEClientPasskeyReplyAction : public Action<Ts...
esp_ble_passkey_reply(remote_bda, true, passkey);
}
void set_value_template(std::function<uint32_t(Ts...)> func) {
this->value_template_ = std::move(func);
has_simple_value_ = false;
void set_value_template(uint32_t (*func)(Ts...)) {
this->value_.template_func = func;
this->has_simple_value_ = false;
}
void set_value_simple(const uint32_t &value) {
this->value_simple_ = value;
has_simple_value_ = true;
this->value_.simple = value;
this->has_simple_value_ = true;
}
private:
BLEClient *parent_{nullptr};
bool has_simple_value_ = true;
uint32_t value_simple_{0};
std::function<uint32_t(Ts...)> value_template_{};
union {
uint32_t simple;
uint32_t (*template_func)(Ts...);
} value_{.simple = 0};
};
template<typename... Ts> class BLEClientNumericComparisonReplyAction : public Action<Ts...> {
@@ -249,27 +270,29 @@ template<typename... Ts> class BLEClientNumericComparisonReplyAction : public Ac
esp_bd_addr_t remote_bda;
memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t));
if (has_simple_value_) {
esp_ble_confirm_reply(remote_bda, this->value_simple_);
esp_ble_confirm_reply(remote_bda, this->value_.simple);
} else {
esp_ble_confirm_reply(remote_bda, this->value_template_(x...));
esp_ble_confirm_reply(remote_bda, this->value_.template_func(x...));
}
}
void set_value_template(std::function<bool(Ts...)> func) {
this->value_template_ = std::move(func);
has_simple_value_ = false;
void set_value_template(bool (*func)(Ts...)) {
this->value_.template_func = func;
this->has_simple_value_ = false;
}
void set_value_simple(const bool &value) {
this->value_simple_ = value;
has_simple_value_ = true;
this->value_.simple = value;
this->has_simple_value_ = true;
}
private:
BLEClient *parent_{nullptr};
bool has_simple_value_ = true;
bool value_simple_{false};
std::function<bool(Ts...)> value_template_{};
union {
bool simple;
bool (*template_func)(Ts...);
} value_{.simple = false};
};
template<typename... Ts> class BLEClientRemoveBondAction : public Action<Ts...> {

View File

@@ -117,9 +117,9 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
}
float BLESensor::parse_data_(uint8_t *value, uint16_t value_len) {
if (this->data_to_value_func_.has_value()) {
if (this->has_data_to_value_) {
std::vector<uint8_t> data(value, value + value_len);
return (*this->data_to_value_func_)(data);
return this->data_to_value_func_(data);
} else {
return value[0];
}

View File

@@ -15,8 +15,6 @@ namespace ble_client {
namespace espbt = esphome::esp32_ble_tracker;
using data_to_value_t = std::function<float(std::vector<uint8_t>)>;
class BLESensor : public sensor::Sensor, public PollingComponent, public BLEClientNode {
public:
void loop() override;
@@ -33,13 +31,17 @@ class BLESensor : public sensor::Sensor, public PollingComponent, public BLEClie
void set_descr_uuid16(uint16_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
void set_descr_uuid32(uint32_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
void set_descr_uuid128(uint8_t *uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
void set_data_to_value(data_to_value_t &&lambda) { this->data_to_value_func_ = lambda; }
void set_data_to_value(float (*lambda)(const std::vector<uint8_t> &)) {
this->data_to_value_func_ = lambda;
this->has_data_to_value_ = true;
}
void set_enable_notify(bool notify) { this->notify_ = notify; }
uint16_t handle;
protected:
float parse_data_(uint8_t *value, uint16_t value_len);
optional<data_to_value_t> data_to_value_func_{};
bool has_data_to_value_{false};
float (*data_to_value_func_)(const std::vector<uint8_t> &){};
bool notify_;
espbt::ESPBTUUID service_uuid_;
espbt::ESPBTUUID char_uuid_;

View File

@@ -84,11 +84,6 @@ def button_schema(
return _BUTTON_SCHEMA.extend(schema)
# Remove before 2025.11.0
BUTTON_SCHEMA = button_schema(Button)
BUTTON_SCHEMA.add_extra(cv.deprecated_schema_constant("button"))
async def setup_button_core_(var, config):
await setup_entity(var, config, "button")

View File

@@ -270,11 +270,6 @@ def climate_schema(
return _CLIMATE_SCHEMA.extend(schema)
# Remove before 2025.11.0
CLIMATE_SCHEMA = climate_schema(Climate)
CLIMATE_SCHEMA.add_extra(cv.deprecated_schema_constant("climate"))
async def setup_climate_core_(var, config):
await setup_entity(var, config, "climate")

View File

@@ -385,12 +385,14 @@ void Climate::save_state_() {
if (!traits.get_supported_custom_fan_modes().empty() && custom_fan_mode.has_value()) {
state.uses_custom_fan_mode = true;
const auto &supported = traits.get_supported_custom_fan_modes();
std::vector<std::string> vec{supported.begin(), supported.end()};
for (size_t i = 0; i < vec.size(); i++) {
if (vec[i] == custom_fan_mode) {
// std::vector maintains insertion order
size_t i = 0;
for (const auto &mode : supported) {
if (mode == custom_fan_mode) {
state.custom_fan_mode = i;
break;
}
i++;
}
}
if (traits.get_supports_presets() && preset.has_value()) {
@@ -400,12 +402,14 @@ void Climate::save_state_() {
if (!traits.get_supported_custom_presets().empty() && custom_preset.has_value()) {
state.uses_custom_preset = true;
const auto &supported = traits.get_supported_custom_presets();
std::vector<std::string> vec{supported.begin(), supported.end()};
for (size_t i = 0; i < vec.size(); i++) {
if (vec[i] == custom_preset) {
// std::vector maintains insertion order
size_t i = 0;
for (const auto &preset : supported) {
if (preset == custom_preset) {
state.custom_preset = i;
break;
}
i++;
}
}
if (traits.get_supports_swing_modes()) {
@@ -520,13 +524,23 @@ ClimateCall ClimateDeviceRestoreState::to_call(Climate *climate) {
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY)) {
call.set_target_humidity(this->target_humidity);
}
if (traits.get_supports_fan_modes() || !traits.get_supported_custom_fan_modes().empty()) {
if (this->uses_custom_fan_mode) {
if (this->custom_fan_mode < traits.get_supported_custom_fan_modes().size()) {
call.fan_mode_.reset();
call.custom_fan_mode_ = *std::next(traits.get_supported_custom_fan_modes().cbegin(), this->custom_fan_mode);
}
} else if (traits.supports_fan_mode(this->fan_mode)) {
call.set_fan_mode(this->fan_mode);
}
if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) {
if (this->uses_custom_preset) {
if (this->custom_preset < traits.get_supported_custom_presets().size()) {
call.preset_.reset();
call.custom_preset_ = *std::next(traits.get_supported_custom_presets().cbegin(), this->custom_preset);
}
} else if (traits.supports_preset(this->preset)) {
call.set_preset(this->preset);
}
if (traits.get_supports_swing_modes()) {
if (traits.supports_swing_mode(this->swing_mode)) {
call.set_swing_mode(this->swing_mode);
}
return call;
@@ -545,29 +559,25 @@ void ClimateDeviceRestoreState::apply(Climate *climate) {
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY)) {
climate->target_humidity = this->target_humidity;
}
if (traits.get_supports_fan_modes() && !this->uses_custom_fan_mode) {
if (this->uses_custom_fan_mode) {
if (this->custom_fan_mode < traits.get_supported_custom_fan_modes().size()) {
climate->fan_mode.reset();
climate->custom_fan_mode = *std::next(traits.get_supported_custom_fan_modes().cbegin(), this->custom_fan_mode);
}
} else if (traits.supports_fan_mode(this->fan_mode)) {
climate->fan_mode = this->fan_mode;
climate->custom_fan_mode.reset();
}
if (!traits.get_supported_custom_fan_modes().empty() && this->uses_custom_fan_mode) {
// std::set has consistent order (lexicographic for strings), so this is ok
const auto &modes = traits.get_supported_custom_fan_modes();
std::vector<std::string> modes_vec{modes.begin(), modes.end()};
if (custom_fan_mode < modes_vec.size()) {
climate->custom_fan_mode = modes_vec[this->custom_fan_mode];
if (this->uses_custom_preset) {
if (this->custom_preset < traits.get_supported_custom_presets().size()) {
climate->preset.reset();
climate->custom_preset = *std::next(traits.get_supported_custom_presets().cbegin(), this->custom_preset);
}
}
if (traits.get_supports_presets() && !this->uses_custom_preset) {
} else if (traits.supports_preset(this->preset)) {
climate->preset = this->preset;
climate->custom_preset.reset();
}
if (!traits.get_supported_custom_presets().empty() && uses_custom_preset) {
// std::set has consistent order (lexicographic for strings), so this is ok
const auto &presets = traits.get_supported_custom_presets();
std::vector<std::string> presets_vec{presets.begin(), presets.end()};
if (custom_preset < presets_vec.size()) {
climate->custom_preset = presets_vec[this->custom_preset];
}
}
if (traits.get_supports_swing_modes()) {
if (traits.supports_swing_mode(this->swing_mode)) {
climate->swing_mode = this->swing_mode;
}
climate->publish_state();

View File

@@ -33,6 +33,7 @@ class Climate;
class ClimateCall {
public:
explicit ClimateCall(Climate *parent) : parent_(parent) {}
friend struct ClimateDeviceRestoreState;
/// Set the mode of the climate device.
ClimateCall &set_mode(ClimateMode mode);

View File

@@ -7,6 +7,7 @@ namespace esphome {
namespace climate {
/// Enum for all modes a climate device can be in.
/// NOTE: If adding values, update ClimateModeMask in climate_traits.h to use the new last value
enum ClimateMode : uint8_t {
/// The climate device is off
CLIMATE_MODE_OFF = 0,
@@ -24,7 +25,7 @@ enum ClimateMode : uint8_t {
* For example, the target temperature can be adjusted based on a schedule, or learned behavior.
* The target temperature can't be adjusted when in this mode.
*/
CLIMATE_MODE_AUTO = 6
CLIMATE_MODE_AUTO = 6 // Update ClimateModeMask in climate_traits.h if adding values after this
};
/// Enum for the current action of the climate device. Values match those of ClimateMode.
@@ -43,6 +44,7 @@ enum ClimateAction : uint8_t {
CLIMATE_ACTION_FAN = 6,
};
/// NOTE: If adding values, update ClimateFanModeMask in climate_traits.h to use the new last value
enum ClimateFanMode : uint8_t {
/// The fan mode is set to On
CLIMATE_FAN_ON = 0,
@@ -63,10 +65,11 @@ enum ClimateFanMode : uint8_t {
/// The fan mode is set to Diffuse
CLIMATE_FAN_DIFFUSE = 8,
/// The fan mode is set to Quiet
CLIMATE_FAN_QUIET = 9,
CLIMATE_FAN_QUIET = 9, // Update ClimateFanModeMask in climate_traits.h if adding values after this
};
/// Enum for all modes a climate swing can be in
/// NOTE: If adding values, update ClimateSwingModeMask in climate_traits.h to use the new last value
enum ClimateSwingMode : uint8_t {
/// The swing mode is set to Off
CLIMATE_SWING_OFF = 0,
@@ -75,10 +78,11 @@ enum ClimateSwingMode : uint8_t {
/// The fan mode is set to Vertical
CLIMATE_SWING_VERTICAL = 2,
/// The fan mode is set to Horizontal
CLIMATE_SWING_HORIZONTAL = 3,
CLIMATE_SWING_HORIZONTAL = 3, // Update ClimateSwingModeMask in climate_traits.h if adding values after this
};
/// Enum for all preset modes
/// NOTE: If adding values, update ClimatePresetMask in climate_traits.h to use the new last value
enum ClimatePreset : uint8_t {
/// No preset is active
CLIMATE_PRESET_NONE = 0,
@@ -95,7 +99,7 @@ enum ClimatePreset : uint8_t {
/// Device is prepared for sleep
CLIMATE_PRESET_SLEEP = 6,
/// Device is reacting to activity (e.g., movement sensors)
CLIMATE_PRESET_ACTIVITY = 7,
CLIMATE_PRESET_ACTIVITY = 7, // Update ClimatePresetMask in climate_traits.h if adding values after this
};
enum ClimateFeature : uint32_t {

View File

@@ -1,19 +1,33 @@
#pragma once
#include <set>
#include <vector>
#include "climate_mode.h"
#include "esphome/core/finite_set_mask.h"
#include "esphome/core/helpers.h"
namespace esphome {
#ifdef USE_API
namespace api {
class APIConnection;
} // namespace api
#endif
namespace climate {
// Type aliases for climate enum bitmasks
// These replace std::set<EnumType> to eliminate red-black tree overhead
// For contiguous enums starting at 0, DefaultBitPolicy provides 1:1 mapping (enum value = bit position)
// Bitmask size is automatically calculated from the last enum value
using ClimateModeMask = FiniteSetMask<ClimateMode, DefaultBitPolicy<ClimateMode, CLIMATE_MODE_AUTO + 1>>;
using ClimateFanModeMask = FiniteSetMask<ClimateFanMode, DefaultBitPolicy<ClimateFanMode, CLIMATE_FAN_QUIET + 1>>;
using ClimateSwingModeMask =
FiniteSetMask<ClimateSwingMode, DefaultBitPolicy<ClimateSwingMode, CLIMATE_SWING_HORIZONTAL + 1>>;
using ClimatePresetMask = FiniteSetMask<ClimatePreset, DefaultBitPolicy<ClimatePreset, CLIMATE_PRESET_ACTIVITY + 1>>;
// Lightweight linear search for small vectors (1-20 items)
// Avoids std::find template overhead
template<typename T> inline bool vector_contains(const std::vector<T> &vec, const T &value) {
for (const auto &item : vec) {
if (item == value)
return true;
}
return false;
}
/** This class contains all static data for climate devices.
*
* All climate devices must support these features:
@@ -107,48 +121,60 @@ class ClimateTraits {
}
}
void set_supported_modes(std::set<ClimateMode> modes) { this->supported_modes_ = std::move(modes); }
void set_supported_modes(ClimateModeMask modes) { this->supported_modes_ = modes; }
void add_supported_mode(ClimateMode mode) { this->supported_modes_.insert(mode); }
bool supports_mode(ClimateMode mode) const { return this->supported_modes_.count(mode); }
const std::set<ClimateMode> &get_supported_modes() const { return this->supported_modes_; }
const ClimateModeMask &get_supported_modes() const { return this->supported_modes_; }
void set_supported_fan_modes(std::set<ClimateFanMode> modes) { this->supported_fan_modes_ = std::move(modes); }
void set_supported_fan_modes(ClimateFanModeMask modes) { this->supported_fan_modes_ = modes; }
void add_supported_fan_mode(ClimateFanMode mode) { this->supported_fan_modes_.insert(mode); }
void add_supported_custom_fan_mode(const std::string &mode) { this->supported_custom_fan_modes_.insert(mode); }
void add_supported_custom_fan_mode(const std::string &mode) { this->supported_custom_fan_modes_.push_back(mode); }
bool supports_fan_mode(ClimateFanMode fan_mode) const { return this->supported_fan_modes_.count(fan_mode); }
bool get_supports_fan_modes() const {
return !this->supported_fan_modes_.empty() || !this->supported_custom_fan_modes_.empty();
}
const std::set<ClimateFanMode> &get_supported_fan_modes() const { return this->supported_fan_modes_; }
const ClimateFanModeMask &get_supported_fan_modes() const { return this->supported_fan_modes_; }
void set_supported_custom_fan_modes(std::set<std::string> supported_custom_fan_modes) {
void set_supported_custom_fan_modes(std::vector<std::string> supported_custom_fan_modes) {
this->supported_custom_fan_modes_ = std::move(supported_custom_fan_modes);
}
const std::set<std::string> &get_supported_custom_fan_modes() const { return this->supported_custom_fan_modes_; }
void set_supported_custom_fan_modes(std::initializer_list<std::string> modes) {
this->supported_custom_fan_modes_ = modes;
}
template<size_t N> void set_supported_custom_fan_modes(const char *const (&modes)[N]) {
this->supported_custom_fan_modes_.assign(modes, modes + N);
}
const std::vector<std::string> &get_supported_custom_fan_modes() const { return this->supported_custom_fan_modes_; }
bool supports_custom_fan_mode(const std::string &custom_fan_mode) const {
return this->supported_custom_fan_modes_.count(custom_fan_mode);
return vector_contains(this->supported_custom_fan_modes_, custom_fan_mode);
}
void set_supported_presets(std::set<ClimatePreset> presets) { this->supported_presets_ = std::move(presets); }
void set_supported_presets(ClimatePresetMask presets) { this->supported_presets_ = presets; }
void add_supported_preset(ClimatePreset preset) { this->supported_presets_.insert(preset); }
void add_supported_custom_preset(const std::string &preset) { this->supported_custom_presets_.insert(preset); }
void add_supported_custom_preset(const std::string &preset) { this->supported_custom_presets_.push_back(preset); }
bool supports_preset(ClimatePreset preset) const { return this->supported_presets_.count(preset); }
bool get_supports_presets() const { return !this->supported_presets_.empty(); }
const std::set<climate::ClimatePreset> &get_supported_presets() const { return this->supported_presets_; }
const ClimatePresetMask &get_supported_presets() const { return this->supported_presets_; }
void set_supported_custom_presets(std::set<std::string> supported_custom_presets) {
void set_supported_custom_presets(std::vector<std::string> supported_custom_presets) {
this->supported_custom_presets_ = std::move(supported_custom_presets);
}
const std::set<std::string> &get_supported_custom_presets() const { return this->supported_custom_presets_; }
void set_supported_custom_presets(std::initializer_list<std::string> presets) {
this->supported_custom_presets_ = presets;
}
template<size_t N> void set_supported_custom_presets(const char *const (&presets)[N]) {
this->supported_custom_presets_.assign(presets, presets + N);
}
const std::vector<std::string> &get_supported_custom_presets() const { return this->supported_custom_presets_; }
bool supports_custom_preset(const std::string &custom_preset) const {
return this->supported_custom_presets_.count(custom_preset);
return vector_contains(this->supported_custom_presets_, custom_preset);
}
void set_supported_swing_modes(std::set<ClimateSwingMode> modes) { this->supported_swing_modes_ = std::move(modes); }
void set_supported_swing_modes(ClimateSwingModeMask modes) { this->supported_swing_modes_ = modes; }
void add_supported_swing_mode(ClimateSwingMode mode) { this->supported_swing_modes_.insert(mode); }
bool supports_swing_mode(ClimateSwingMode swing_mode) const { return this->supported_swing_modes_.count(swing_mode); }
bool get_supports_swing_modes() const { return !this->supported_swing_modes_.empty(); }
const std::set<ClimateSwingMode> &get_supported_swing_modes() const { return this->supported_swing_modes_; }
const ClimateSwingModeMask &get_supported_swing_modes() const { return this->supported_swing_modes_; }
float get_visual_min_temperature() const { return this->visual_min_temperature_; }
void set_visual_min_temperature(float visual_min_temperature) {
@@ -179,23 +205,6 @@ class ClimateTraits {
void set_visual_max_humidity(float visual_max_humidity) { this->visual_max_humidity_ = visual_max_humidity; }
protected:
#ifdef USE_API
// The API connection is a friend class to access internal methods
friend class api::APIConnection;
// These methods return references to internal data structures.
// They are used by the API to avoid copying data when encoding messages.
// Warning: Do not use these methods outside of the API connection code.
// They return references to internal data that can be invalidated.
const std::set<ClimateMode> &get_supported_modes_for_api_() const { return this->supported_modes_; }
const std::set<ClimateFanMode> &get_supported_fan_modes_for_api_() const { return this->supported_fan_modes_; }
const std::set<std::string> &get_supported_custom_fan_modes_for_api_() const {
return this->supported_custom_fan_modes_;
}
const std::set<climate::ClimatePreset> &get_supported_presets_for_api_() const { return this->supported_presets_; }
const std::set<std::string> &get_supported_custom_presets_for_api_() const { return this->supported_custom_presets_; }
const std::set<ClimateSwingMode> &get_supported_swing_modes_for_api_() const { return this->supported_swing_modes_; }
#endif
void set_mode_support_(climate::ClimateMode mode, bool supported) {
if (supported) {
this->supported_modes_.insert(mode);
@@ -226,12 +235,12 @@ class ClimateTraits {
float visual_min_humidity_{30};
float visual_max_humidity_{99};
std::set<climate::ClimateMode> supported_modes_ = {climate::CLIMATE_MODE_OFF};
std::set<climate::ClimateFanMode> supported_fan_modes_;
std::set<climate::ClimateSwingMode> supported_swing_modes_;
std::set<climate::ClimatePreset> supported_presets_;
std::set<std::string> supported_custom_fan_modes_;
std::set<std::string> supported_custom_presets_;
climate::ClimateModeMask supported_modes_{climate::CLIMATE_MODE_OFF};
climate::ClimateFanModeMask supported_fan_modes_;
climate::ClimateSwingModeMask supported_swing_modes_;
climate::ClimatePresetMask supported_presets_;
std::vector<std::string> supported_custom_fan_modes_;
std::vector<std::string> supported_custom_presets_;
};
} // namespace climate

View File

@@ -1,10 +1,9 @@
import logging
from esphome import core
import esphome.codegen as cg
from esphome.components import climate, remote_base, sensor
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_SENSOR, CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT
from esphome.const import CONF_SENSOR, CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT
from esphome.cpp_generator import MockObjClass
_LOGGER = logging.getLogger(__name__)
@@ -52,26 +51,6 @@ def climate_ir_with_receiver_schema(
)
# Remove before 2025.11.0
def deprecated_schema_constant(config):
type: str = "unknown"
if (id := config.get(CONF_ID)) is not None and isinstance(id, core.ID):
type = str(id.type).split("::", maxsplit=1)[0]
_LOGGER.warning(
"Using `climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA` is deprecated and will be removed in ESPHome 2025.11.0. "
"Please use `climate_ir.climate_ir_with_receiver_schema(...)` instead. "
"If you are seeing this, report an issue to the external_component author and ask them to update it. "
"https://developers.esphome.io/blog/2025/05/14/_schema-deprecations/. "
"Component using this schema: %s",
type,
)
return config
CLIMATE_IR_WITH_RECEIVER_SCHEMA = climate_ir_with_receiver_schema(ClimateIR)
CLIMATE_IR_WITH_RECEIVER_SCHEMA.add_extra(deprecated_schema_constant)
async def register_climate_ir(var, config):
await cg.register_component(var, config)
await remote_base.register_transmittable(var, config)

View File

@@ -24,16 +24,18 @@ class ClimateIR : public Component,
public remote_base::RemoteTransmittable {
public:
ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f,
bool supports_dry = false, bool supports_fan_only = false, std::set<climate::ClimateFanMode> fan_modes = {},
std::set<climate::ClimateSwingMode> swing_modes = {}, std::set<climate::ClimatePreset> presets = {}) {
bool supports_dry = false, bool supports_fan_only = false,
climate::ClimateFanModeMask fan_modes = climate::ClimateFanModeMask(),
climate::ClimateSwingModeMask swing_modes = climate::ClimateSwingModeMask(),
climate::ClimatePresetMask presets = climate::ClimatePresetMask()) {
this->minimum_temperature_ = minimum_temperature;
this->maximum_temperature_ = maximum_temperature;
this->temperature_step_ = temperature_step;
this->supports_dry_ = supports_dry;
this->supports_fan_only_ = supports_fan_only;
this->fan_modes_ = std::move(fan_modes);
this->swing_modes_ = std::move(swing_modes);
this->presets_ = std::move(presets);
this->fan_modes_ = fan_modes;
this->swing_modes_ = swing_modes;
this->presets_ = presets;
}
void setup() override;
@@ -60,9 +62,9 @@ class ClimateIR : public Component,
bool supports_heat_{true};
bool supports_dry_{false};
bool supports_fan_only_{false};
std::set<climate::ClimateFanMode> fan_modes_ = {};
std::set<climate::ClimateSwingMode> swing_modes_ = {};
std::set<climate::ClimatePreset> presets_ = {};
climate::ClimateFanModeMask fan_modes_{};
climate::ClimateSwingModeMask swing_modes_{};
climate::ClimatePresetMask presets_{};
sensor::Sensor *sensor_{nullptr};
};

View File

@@ -151,11 +151,6 @@ def cover_schema(
return _COVER_SCHEMA.extend(schema)
# Remove before 2025.11.0
COVER_SCHEMA = cover_schema(Cover)
COVER_SCHEMA.add_extra(cv.deprecated_schema_constant("cover"))
async def setup_cover_core_(var, config):
await setup_entity(var, config, "cover")

View File

@@ -80,8 +80,8 @@ void E131Component::add_effect(E131AddressableLightEffect *light_effect) {
return;
}
ESP_LOGD(TAG, "Registering '%s' for universes %d-%d.", light_effect->get_name().c_str(),
light_effect->get_first_universe(), light_effect->get_last_universe());
ESP_LOGD(TAG, "Registering '%s' for universes %d-%d.", light_effect->get_name(), light_effect->get_first_universe(),
light_effect->get_last_universe());
light_effects_.insert(light_effect);
@@ -95,8 +95,8 @@ void E131Component::remove_effect(E131AddressableLightEffect *light_effect) {
return;
}
ESP_LOGD(TAG, "Unregistering '%s' for universes %d-%d.", light_effect->get_name().c_str(),
light_effect->get_first_universe(), light_effect->get_last_universe());
ESP_LOGD(TAG, "Unregistering '%s' for universes %d-%d.", light_effect->get_name(), light_effect->get_first_universe(),
light_effect->get_last_universe());
light_effects_.erase(light_effect);

View File

@@ -9,7 +9,7 @@ namespace e131 {
static const char *const TAG = "e131_addressable_light_effect";
static const int MAX_DATA_SIZE = (sizeof(E131Packet::values) - 1);
E131AddressableLightEffect::E131AddressableLightEffect(const std::string &name) : AddressableLightEffect(name) {}
E131AddressableLightEffect::E131AddressableLightEffect(const char *name) : AddressableLightEffect(name) {}
int E131AddressableLightEffect::get_data_per_universe() const { return get_lights_per_universe() * channels_; }
@@ -58,8 +58,8 @@ bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet
std::min(it->size(), std::min(output_offset + get_lights_per_universe(), output_offset + packet.count - 1));
auto *input_data = packet.values + 1;
ESP_LOGV(TAG, "Applying data for '%s' on %d universe, for %" PRId32 "-%d.", get_name().c_str(), universe,
output_offset, output_end);
ESP_LOGV(TAG, "Applying data for '%s' on %d universe, for %" PRId32 "-%d.", get_name(), universe, output_offset,
output_end);
switch (channels_) {
case E131_MONO:

View File

@@ -13,7 +13,7 @@ enum E131LightChannels { E131_MONO = 1, E131_RGB = 3, E131_RGBW = 4 };
class E131AddressableLightEffect : public light::AddressableLightEffect {
public:
E131AddressableLightEffect(const std::string &name);
E131AddressableLightEffect(const char *name);
void start() override;
void stop() override;

View File

@@ -304,9 +304,13 @@ def _format_framework_arduino_version(ver: cv.Version) -> str:
def _format_framework_espidf_version(ver: cv.Version, release: str) -> str:
# format the given espidf (https://github.com/pioarduino/esp-idf/releases) version to
# a PIO platformio/framework-espidf value
if ver == cv.Version(5, 4, 3) or ver >= cv.Version(5, 5, 1):
ext = "tar.xz"
else:
ext = "zip"
if release:
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}.{release}/esp-idf-v{str(ver)}.zip"
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}/esp-idf-v{str(ver)}.zip"
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}.{release}/esp-idf-v{str(ver)}.{ext}"
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}/esp-idf-v{str(ver)}.{ext}"
def _is_framework_url(source: str) -> str:
@@ -355,6 +359,7 @@ ESP_IDF_FRAMEWORK_VERSION_LOOKUP = {
ESP_IDF_PLATFORM_VERSION_LOOKUP = {
cv.Version(5, 5, 1): cv.Version(55, 3, 31, "1"),
cv.Version(5, 5, 0): cv.Version(55, 3, 31, "1"),
cv.Version(5, 4, 3): cv.Version(55, 3, 32),
cv.Version(5, 4, 2): cv.Version(54, 3, 21, "2"),
cv.Version(5, 4, 1): cv.Version(54, 3, 21, "2"),
cv.Version(5, 4, 0): cv.Version(54, 3, 21, "2"),
@@ -550,6 +555,32 @@ CONF_ENABLE_LWIP_BRIDGE_INTERFACE = "enable_lwip_bridge_interface"
CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING = "enable_lwip_tcpip_core_locking"
CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY = "enable_lwip_check_thread_safety"
CONF_DISABLE_LIBC_LOCKS_IN_IRAM = "disable_libc_locks_in_iram"
CONF_DISABLE_VFS_SUPPORT_TERMIOS = "disable_vfs_support_termios"
CONF_DISABLE_VFS_SUPPORT_SELECT = "disable_vfs_support_select"
CONF_DISABLE_VFS_SUPPORT_DIR = "disable_vfs_support_dir"
# VFS requirement tracking
# Components that need VFS features can call require_vfs_select() or require_vfs_dir()
KEY_VFS_SELECT_REQUIRED = "vfs_select_required"
KEY_VFS_DIR_REQUIRED = "vfs_dir_required"
def require_vfs_select() -> None:
"""Mark that VFS select support is required by a component.
Call this from components that use esp_vfs_eventfd or other VFS select features.
This prevents CONFIG_VFS_SUPPORT_SELECT from being disabled.
"""
CORE.data[KEY_VFS_SELECT_REQUIRED] = True
def require_vfs_dir() -> None:
"""Mark that VFS directory support is required by a component.
Call this from components that use directory functions (opendir, readdir, mkdir, etc.).
This prevents CONFIG_VFS_SUPPORT_DIR from being disabled.
"""
CORE.data[KEY_VFS_DIR_REQUIRED] = True
def _validate_idf_component(config: ConfigType) -> ConfigType:
@@ -615,6 +646,13 @@ FRAMEWORK_SCHEMA = cv.All(
cv.Optional(
CONF_DISABLE_LIBC_LOCKS_IN_IRAM, default=True
): cv.boolean,
cv.Optional(
CONF_DISABLE_VFS_SUPPORT_TERMIOS, default=True
): cv.boolean,
cv.Optional(
CONF_DISABLE_VFS_SUPPORT_SELECT, default=True
): cv.boolean,
cv.Optional(CONF_DISABLE_VFS_SUPPORT_DIR, default=True): cv.boolean,
cv.Optional(CONF_EXECUTE_FROM_PSRAM): cv.boolean,
}
),
@@ -844,6 +882,11 @@ async def to_code(config):
for clean_var in ("IDF_PATH", "IDF_TOOLS_PATH"):
os.environ.pop(clean_var, None)
# Set the location of the IDF component manager cache
os.environ["IDF_COMPONENT_CACHE_PATH"] = str(
CORE.relative_internal_path(".espressif")
)
add_extra_script(
"post",
"post_build.py",
@@ -962,6 +1005,43 @@ async def to_code(config):
if advanced.get(CONF_DISABLE_LIBC_LOCKS_IN_IRAM, True):
add_idf_sdkconfig_option("CONFIG_LIBC_LOCKS_PLACE_IN_IRAM", False)
# Disable VFS support for termios (terminal I/O functions)
# ESPHome doesn't use termios functions on ESP32 (only used in host UART driver).
# Saves approximately 1.8KB of flash when disabled (default).
add_idf_sdkconfig_option(
"CONFIG_VFS_SUPPORT_TERMIOS",
not advanced.get(CONF_DISABLE_VFS_SUPPORT_TERMIOS, True),
)
# Disable VFS support for select() with file descriptors
# ESPHome only uses select() with sockets via lwip_select(), which still works.
# VFS select is only needed for UART/eventfd file descriptors.
# Components that need it (e.g., openthread) call require_vfs_select().
# Saves approximately 2.7KB of flash when disabled (default).
if CORE.data.get(KEY_VFS_SELECT_REQUIRED, False):
# Component requires VFS select - force enable regardless of user setting
add_idf_sdkconfig_option("CONFIG_VFS_SUPPORT_SELECT", True)
else:
# No component needs it - allow user to control (default: disabled)
add_idf_sdkconfig_option(
"CONFIG_VFS_SUPPORT_SELECT",
not advanced.get(CONF_DISABLE_VFS_SUPPORT_SELECT, True),
)
# Disable VFS support for directory functions (opendir, readdir, mkdir, etc.)
# ESPHome doesn't use directory functions on ESP32.
# Components that need it (e.g., storage components) call require_vfs_dir().
# Saves approximately 0.5KB+ of flash when disabled (default).
if CORE.data.get(KEY_VFS_DIR_REQUIRED, False):
# Component requires VFS directory support - force enable regardless of user setting
add_idf_sdkconfig_option("CONFIG_VFS_SUPPORT_DIR", True)
else:
# No component needs it - allow user to control (default: disabled)
add_idf_sdkconfig_option(
"CONFIG_VFS_SUPPORT_DIR",
not advanced.get(CONF_DISABLE_VFS_SUPPORT_DIR, True),
)
cg.add_platformio_option("board_build.partitions", "partitions.csv")
if CONF_PARTITIONS in config:
add_extra_build_file(

View File

@@ -40,13 +40,13 @@ class ESP32InternalGPIOPin : public InternalGPIOPin {
// - 3 bytes for members below
// - 1 byte padding for alignment
// - 4 bytes for vtable pointer
uint8_t pin_; // GPIO pin number (0-255, actual max ~54 on ESP32)
gpio::Flags flags_; // GPIO flags (1 byte)
uint8_t pin_; // GPIO pin number (0-255, actual max ~54 on ESP32)
gpio::Flags flags_{}; // GPIO flags (1 byte)
struct PinFlags {
uint8_t inverted : 1; // Invert pin logic (1 bit)
uint8_t drive_strength : 2; // Drive strength 0-3 (2 bits)
uint8_t reserved : 5; // Reserved for future use (5 bits)
} pin_flags_; // Total: 1 byte
} pin_flags_{}; // Total: 1 byte
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
static bool isr_service_installed;
};

View File

@@ -223,7 +223,10 @@ async def esp32_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
num = config[CONF_NUMBER]
cg.add(var.set_pin(getattr(gpio_num_t, f"GPIO_NUM_{num}")))
cg.add(var.set_inverted(config[CONF_INVERTED]))
# Only set if true to avoid bloating setup() function
# (inverted bit in pin_flags_ bitfield is zero-initialized to false)
if config[CONF_INVERTED]:
cg.add(var.set_inverted(True))
if CONF_DRIVE_STRENGTH in config:
cg.add(var.set_drive_strength(config[CONF_DRIVE_STRENGTH]))
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))

View File

@@ -461,7 +461,9 @@ async def parse_value(value_config, args):
if isinstance(value, str):
value = list(value.encode(value_config[CONF_STRING_ENCODING]))
if isinstance(value, list):
return cg.std_vector.template(cg.uint8)(value)
# Generate initializer list {1, 2, 3} instead of std::vector<uint8_t>({1, 2, 3})
# This calls the set_value(std::initializer_list<uint8_t>) overload
return cg.ArrayInitializer(*value)
val = cg.RawExpression(f"{value_config[CONF_TYPE]}({cg.safe_exp(value)})")
return ByteBuffer_ns.wrap(val, value_config[CONF_ENDIANNESS])

View File

@@ -35,13 +35,18 @@ BLECharacteristic::BLECharacteristic(const ESPBTUUID uuid, uint32_t properties)
void BLECharacteristic::set_value(ByteBuffer buffer) { this->set_value(buffer.get_data()); }
void BLECharacteristic::set_value(const std::vector<uint8_t> &buffer) {
void BLECharacteristic::set_value(std::vector<uint8_t> &&buffer) {
xSemaphoreTake(this->set_value_lock_, 0L);
this->value_ = buffer;
this->value_ = std::move(buffer);
xSemaphoreGive(this->set_value_lock_);
}
void BLECharacteristic::set_value(std::initializer_list<uint8_t> data) {
this->set_value(std::vector<uint8_t>(data)); // Delegate to move overload
}
void BLECharacteristic::set_value(const std::string &buffer) {
this->set_value(std::vector<uint8_t>(buffer.begin(), buffer.end()));
this->set_value(std::vector<uint8_t>(buffer.begin(), buffer.end())); // Delegate to move overload
}
void BLECharacteristic::notify() {

View File

@@ -33,7 +33,8 @@ class BLECharacteristic {
~BLECharacteristic();
void set_value(ByteBuffer buffer);
void set_value(const std::vector<uint8_t> &buffer);
void set_value(std::vector<uint8_t> &&buffer);
void set_value(std::initializer_list<uint8_t> data);
void set_value(const std::string &buffer);
void set_broadcast_property(bool value);

View File

@@ -46,15 +46,17 @@ void BLEDescriptor::do_create(BLECharacteristic *characteristic) {
this->state_ = CREATING;
}
void BLEDescriptor::set_value(std::vector<uint8_t> buffer) {
size_t length = buffer.size();
void BLEDescriptor::set_value(std::vector<uint8_t> &&buffer) { this->set_value_impl_(buffer.data(), buffer.size()); }
void BLEDescriptor::set_value(std::initializer_list<uint8_t> data) { this->set_value_impl_(data.begin(), data.size()); }
void BLEDescriptor::set_value_impl_(const uint8_t *data, size_t length) {
if (length > this->value_.attr_max_len) {
ESP_LOGE(TAG, "Size %d too large, must be no bigger than %d", length, this->value_.attr_max_len);
return;
}
this->value_.attr_len = length;
memcpy(this->value_.attr_value, buffer.data(), length);
memcpy(this->value_.attr_value, data, length);
}
void BLEDescriptor::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,

View File

@@ -27,7 +27,8 @@ class BLEDescriptor {
void do_create(BLECharacteristic *characteristic);
ESPBTUUID get_uuid() const { return this->uuid_; }
void set_value(std::vector<uint8_t> buffer);
void set_value(std::vector<uint8_t> &&buffer);
void set_value(std::initializer_list<uint8_t> data);
void set_value(ByteBuffer buffer) { this->set_value(buffer.get_data()); }
void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
@@ -42,6 +43,8 @@ class BLEDescriptor {
}
protected:
void set_value_impl_(const uint8_t *data, size_t length);
BLECharacteristic *characteristic_{nullptr};
ESPBTUUID uuid_;
uint16_t handle_{0xFFFF};

View File

@@ -112,7 +112,7 @@ async def to_code(config):
cg.add_define("USE_IMPROV")
await improv_base.setup_improv_core(var, config)
await improv_base.setup_improv_core(var, config, "esp32_improv")
cg.add(var.set_identify_duration(config[CONF_IDENTIFY_DURATION]))
cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION]))

View File

@@ -270,8 +270,8 @@ void ESP32ImprovComponent::set_error_(improv::Error error) {
}
}
void ESP32ImprovComponent::send_response_(std::vector<uint8_t> &response) {
this->rpc_response_->set_value(ByteBuffer::wrap(response));
void ESP32ImprovComponent::send_response_(std::vector<uint8_t> &&response) {
this->rpc_response_->set_value(std::move(response));
if (this->state_ != improv::STATE_STOPPED)
this->rpc_response_->notify();
}
@@ -389,11 +389,13 @@ void ESP32ImprovComponent::check_wifi_connection_() {
std::string url_strings[3];
size_t url_count = 0;
#ifdef USE_ESP32_IMPROV_NEXT_URL
// Add next_url if configured (should be first per Improv BLE spec)
std::string next_url = this->get_formatted_next_url_();
if (!next_url.empty()) {
url_strings[url_count++] = std::move(next_url);
}
#endif
// Add default URLs for backward compatibility
url_strings[url_count++] = ESPHOME_MY_LINK;
@@ -407,10 +409,8 @@ void ESP32ImprovComponent::check_wifi_connection_() {
}
}
#endif
// Pass to build_rpc_response using vector constructor from iterators to avoid extra copies
std::vector<uint8_t> data = improv::build_rpc_response(
improv::WIFI_SETTINGS, std::vector<std::string>(url_strings, url_strings + url_count));
this->send_response_(data);
this->send_response_(improv::build_rpc_response(improv::WIFI_SETTINGS,
std::vector<std::string>(url_strings, url_strings + url_count)));
} else if (this->is_active() && this->state_ != improv::STATE_PROVISIONED) {
ESP_LOGD(TAG, "WiFi provisioned externally");
}

View File

@@ -109,7 +109,7 @@ class ESP32ImprovComponent : public Component, public improv_base::ImprovBase {
void set_state_(improv::State state, bool update_advertising = true);
void set_error_(improv::Error error);
improv::State get_initial_state_() const;
void send_response_(std::vector<uint8_t> &response);
void send_response_(std::vector<uint8_t> &&response);
void process_incoming_data_();
void on_wifi_connect_timeout_();
void check_wifi_connection_();

View File

@@ -190,7 +190,9 @@ async def to_code(config):
cg.add_define("ESPHOME_VARIANT", "ESP8266")
cg.add_define(ThreadModel.SINGLE)
cg.add_platformio_option("extra_scripts", ["pre:iram_fix.py", "post:post_build.py"])
cg.add_platformio_option(
"extra_scripts", ["pre:testing_mode.py", "post:post_build.py"]
)
conf = config[CONF_FRAMEWORK]
cg.add_platformio_option("framework", "arduino")
@@ -230,9 +232,9 @@ async def to_code(config):
# For cases where nullptrs can be handled, use nothrow: `new (std::nothrow) T;`
cg.add_build_flag("-DNEW_OOM_ABORT")
# In testing mode, fake a larger IRAM to allow linking grouped component tests
# Real ESP8266 hardware only has 32KB IRAM, but for CI testing we pretend it has 2MB
# This is done via a pre-build script that generates a custom linker script
# In testing mode, fake larger memory to allow linking grouped component tests
# Real ESP8266 hardware only has 32KB IRAM and ~80KB RAM, but for CI testing
# we pretend it has much larger memory to test that components compile together
if CORE.testing_mode:
cg.add_build_flag("-DESPHOME_TESTING_MODE")
@@ -271,8 +273,8 @@ def copy_files():
post_build_file,
CORE.relative_build_path("post_build.py"),
)
iram_fix_file = dir / "iram_fix.py.script"
testing_mode_file = dir / "testing_mode.py.script"
copy_file_if_changed(
iram_fix_file,
CORE.relative_build_path("iram_fix.py"),
testing_mode_file,
CORE.relative_build_path("testing_mode.py"),
)

View File

@@ -29,8 +29,8 @@ class ESP8266GPIOPin : public InternalGPIOPin {
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
uint8_t pin_;
bool inverted_;
gpio::Flags flags_;
bool inverted_{};
gpio::Flags flags_{};
};
} // namespace esp8266

View File

@@ -165,7 +165,10 @@ async def esp8266_pin_to_code(config):
num = config[CONF_NUMBER]
mode = config[CONF_MODE]
cg.add(var.set_pin(num))
cg.add(var.set_inverted(config[CONF_INVERTED]))
# Only set if true to avoid bloating setup() function
# (inverted bit in pin_flags_ bitfield is zero-initialized to false)
if config[CONF_INVERTED]:
cg.add(var.set_inverted(True))
cg.add(var.set_flags(pins.gpio_flags_expr(mode)))
if num < 16:
initial_state: PinInitialState = CORE.data[KEY_ESP8266][KEY_PIN_INITIAL_STATES][

View File

@@ -1,44 +0,0 @@
import os
import re
# pylint: disable=E0602
Import("env") # noqa
def patch_linker_script_after_preprocess(source, target, env):
"""Patch the local linker script after PlatformIO preprocesses it."""
# Check if we're in testing mode by looking for the define
build_flags = env.get("BUILD_FLAGS", [])
testing_mode = any("-DESPHOME_TESTING_MODE" in flag for flag in build_flags)
if not testing_mode:
return
# Get the local linker script path
build_dir = env.subst("$BUILD_DIR")
local_ld = os.path.join(build_dir, "ld", "local.eagle.app.v6.common.ld")
if not os.path.exists(local_ld):
return
# Read the linker script
with open(local_ld, "r") as f:
content = f.read()
# Replace IRAM size from 0x8000 (32KB) to 0x200000 (2MB)
# The line looks like: iram1_0_seg : org = 0x40100000, len = 0x8000
updated = re.sub(
r"(iram1_0_seg\s*:\s*org\s*=\s*0x40100000\s*,\s*len\s*=\s*)0x8000",
r"\g<1>0x200000",
content,
)
if updated != content:
with open(local_ld, "w") as f:
f.write(updated)
print("ESPHome: Patched IRAM size to 2MB for testing mode")
# Hook into the build process right before linking
# This runs after PlatformIO has already preprocessed the linker scripts
env.AddPreAction("$BUILD_DIR/${PROGNAME}.elf", patch_linker_script_after_preprocess)

View File

@@ -0,0 +1,166 @@
import os
import re
# pylint: disable=E0602
Import("env") # noqa
# Memory sizes for testing mode (allow larger builds for CI component grouping)
TESTING_IRAM_SIZE = "0x200000" # 2MB
TESTING_DRAM_SIZE = "0x200000" # 2MB
TESTING_FLASH_SIZE = "0x2000000" # 32MB
def patch_segment_size(content, segment_name, new_size, label):
"""Patch a memory segment's length in linker script.
Args:
content: Linker script content
segment_name: Name of the segment (e.g., 'iram1_0_seg')
new_size: New size as hex string (e.g., '0x200000')
label: Human-readable label for logging (e.g., 'IRAM')
Returns:
Tuple of (patched_content, was_patched)
"""
# Match: segment_name : org = 0x..., len = 0x...
pattern = rf"({segment_name}\s*:\s*org\s*=\s*0x[0-9a-fA-F]+\s*,\s*len\s*=\s*)0x[0-9a-fA-F]+"
new_content = re.sub(pattern, rf"\g<1>{new_size}", content)
return new_content, new_content != content
def apply_memory_patches(content):
"""Apply IRAM, DRAM, and Flash patches to linker script content.
Args:
content: Linker script content as string
Returns:
Patched content as string
"""
patches_applied = []
# Patch IRAM (for larger code in IRAM)
content, patched = patch_segment_size(content, "iram1_0_seg", TESTING_IRAM_SIZE, "IRAM")
if patched:
patches_applied.append("IRAM")
# Patch DRAM (for larger BSS/data sections)
content, patched = patch_segment_size(content, "dram0_0_seg", TESTING_DRAM_SIZE, "DRAM")
if patched:
patches_applied.append("DRAM")
# Patch Flash (for larger code sections)
content, patched = patch_segment_size(content, "irom0_0_seg", TESTING_FLASH_SIZE, "Flash")
if patched:
patches_applied.append("Flash")
if patches_applied:
iram_mb = int(TESTING_IRAM_SIZE, 16) // (1024 * 1024)
dram_mb = int(TESTING_DRAM_SIZE, 16) // (1024 * 1024)
flash_mb = int(TESTING_FLASH_SIZE, 16) // (1024 * 1024)
print(f" Patched memory segments: {', '.join(patches_applied)} (IRAM/DRAM: {iram_mb}MB, Flash: {flash_mb}MB)")
return content
def patch_linker_script_file(filepath, description):
"""Patch a linker script file in the build directory with enlarged memory segments.
This function modifies linker scripts in the build directory only (never SDK files).
It patches IRAM, DRAM, and Flash segments to allow larger builds in testing mode.
Args:
filepath: Path to the linker script file in the build directory
description: Human-readable description for logging
Returns:
True if the file was patched, False if already patched or not found
"""
if not os.path.exists(filepath):
print(f"ESPHome: {description} not found at {filepath}")
return False
print(f"ESPHome: Patching {description}...")
with open(filepath, "r") as f:
content = f.read()
patched_content = apply_memory_patches(content)
if patched_content != content:
with open(filepath, "w") as f:
f.write(patched_content)
print(f"ESPHome: Successfully patched {description}")
return True
else:
print(f"ESPHome: {description} already patched or no changes needed")
return False
def patch_local_linker_script(source, target, env):
"""Patch the local.eagle.app.v6.common.ld in build directory.
This patches the preprocessed linker script that PlatformIO creates in the build
directory, enlarging IRAM, DRAM, and Flash segments for testing mode.
Args:
source: SCons source nodes
target: SCons target nodes
env: SCons environment
"""
# Check if we're in testing mode
build_flags = env.get("BUILD_FLAGS", [])
testing_mode = any("-DESPHOME_TESTING_MODE" in flag for flag in build_flags)
if not testing_mode:
return
# Patch the local linker script if it exists
build_dir = env.subst("$BUILD_DIR")
ld_dir = os.path.join(build_dir, "ld")
if os.path.exists(ld_dir):
local_ld = os.path.join(ld_dir, "local.eagle.app.v6.common.ld")
if os.path.exists(local_ld):
patch_linker_script_file(local_ld, "local.eagle.app.v6.common.ld")
# Check if we're in testing mode
build_flags = env.get("BUILD_FLAGS", [])
testing_mode = any("-DESPHOME_TESTING_MODE" in flag for flag in build_flags)
if testing_mode:
# Create a custom linker script in the build directory with patched memory limits
# This allows larger IRAM/DRAM/Flash for CI component grouping tests
build_dir = env.subst("$BUILD_DIR")
ldscript = env.GetProjectOption("board_build.ldscript", "")
assert ldscript, "No linker script configured in board_build.ldscript"
framework_dir = env.PioPlatform().get_package_dir("framework-arduinoespressif8266")
assert framework_dir is not None, "Could not find framework-arduinoespressif8266 package"
# Read the original SDK linker script (read-only, SDK is never modified)
sdk_ld = os.path.join(framework_dir, "tools", "sdk", "ld", ldscript)
# Create a custom version in the build directory (isolated, temporary)
custom_ld = os.path.join(build_dir, f"testing_{ldscript}")
if os.path.exists(sdk_ld) and not os.path.exists(custom_ld):
# Read the SDK linker script
with open(sdk_ld, "r") as f:
content = f.read()
# Apply memory patches (IRAM: 2MB, DRAM: 2MB, Flash: 32MB)
patched_content = apply_memory_patches(content)
# Write the patched linker script to the build directory
with open(custom_ld, "w") as f:
f.write(patched_content)
print(f"ESPHome: Created custom linker script: {custom_ld}")
# Tell the linker to use our custom script from the build directory
assert os.path.exists(custom_ld), f"Custom linker script not found: {custom_ld}"
env.Replace(LDSCRIPT_PATH=custom_ld)
print(f"ESPHome: Using custom linker script with patched memory limits")
# Also patch local.eagle.app.v6.common.ld after PlatformIO creates it
env.AddPreAction("$BUILD_DIR/${PROGNAME}.elf", patch_local_linker_script)

View File

@@ -14,13 +14,13 @@ template<typename... Ts> class SendAction : public Action<Ts...>, public Parente
TEMPLATABLE_VALUE(std::vector<uint8_t>, data);
public:
void add_on_sent(const std::vector<Action<Ts...> *> &actions) {
void add_on_sent(const std::initializer_list<Action<Ts...> *> &actions) {
this->sent_.add_actions(actions);
if (this->flags_.wait_for_sent) {
this->sent_.add_action(new LambdaAction<Ts...>([this](Ts... x) { this->play_next_(x...); }));
}
}
void add_on_error(const std::vector<Action<Ts...> *> &actions) {
void add_on_error(const std::initializer_list<Action<Ts...> *> &actions) {
this->error_.add_actions(actions);
if (this->flags_.wait_for_sent) {
this->error_.add_action(new LambdaAction<Ts...>([this](Ts... x) {

View File

@@ -14,7 +14,7 @@ from esphome.components.esp32.const import (
VARIANT_ESP32S2,
VARIANT_ESP32S3,
)
from esphome.components.network import IPAddress
from esphome.components.network import ip_address_literal
from esphome.components.spi import CONF_INTERFACE_INDEX, get_spi_interface
import esphome.config_validation as cv
from esphome.const import (
@@ -32,6 +32,7 @@ from esphome.const import (
CONF_MISO_PIN,
CONF_MODE,
CONF_MOSI_PIN,
CONF_NUMBER,
CONF_PAGE_ID,
CONF_PIN,
CONF_POLLING_INTERVAL,
@@ -52,12 +53,36 @@ from esphome.core import (
coroutine_with_priority,
)
import esphome.final_validate as fv
from esphome.types import ConfigType
CONFLICTS_WITH = ["wifi"]
DEPENDENCIES = ["esp32"]
AUTO_LOAD = ["network"]
LOGGER = logging.getLogger(__name__)
# RMII pins that are hardcoded on ESP32 classic and cannot be changed
# These pins are used by the internal Ethernet MAC when using RMII PHYs
ESP32_RMII_FIXED_PINS = {
19: "EMAC_TXD0",
21: "EMAC_TX_EN",
22: "EMAC_TXD1",
25: "EMAC_RXD0",
26: "EMAC_RXD1",
27: "EMAC_RX_CRS_DV",
}
# RMII default pins for ESP32-P4
# These are the default pins used by ESP-IDF and are configurable in principle,
# but ESPHome's ethernet component currently has no way to change them
ESP32P4_RMII_DEFAULT_PINS = {
34: "EMAC_TXD0",
35: "EMAC_TXD1",
28: "EMAC_RX_CRS_DV",
29: "EMAC_RXD0",
30: "EMAC_RXD1",
49: "EMAC_TX_EN",
}
ethernet_ns = cg.esphome_ns.namespace("ethernet")
PHYRegister = ethernet_ns.struct("PHYRegister")
CONF_PHY_ADDR = "phy_addr"
@@ -273,7 +298,7 @@ CONFIG_SCHEMA = cv.All(
)
def _final_validate(config):
def _final_validate_spi(config):
if config[CONF_TYPE] not in SPI_ETHERNET_TYPES:
return
if spi_configs := fv.full_config.get().get(CONF_SPI):
@@ -292,17 +317,14 @@ def _final_validate(config):
)
FINAL_VALIDATE_SCHEMA = _final_validate
def manual_ip(config):
return cg.StructInitializer(
ManualIP,
("static_ip", IPAddress(str(config[CONF_STATIC_IP]))),
("gateway", IPAddress(str(config[CONF_GATEWAY]))),
("subnet", IPAddress(str(config[CONF_SUBNET]))),
("dns1", IPAddress(str(config[CONF_DNS1]))),
("dns2", IPAddress(str(config[CONF_DNS2]))),
("static_ip", ip_address_literal(config[CONF_STATIC_IP])),
("gateway", ip_address_literal(config[CONF_GATEWAY])),
("subnet", ip_address_literal(config[CONF_SUBNET])),
("dns1", ip_address_literal(config[CONF_DNS1])),
("dns2", ip_address_literal(config[CONF_DNS2])),
)
@@ -383,3 +405,57 @@ async def to_code(config):
if CORE.using_arduino:
cg.add_library("WiFi", None)
def _final_validate_rmii_pins(config: ConfigType) -> None:
"""Validate that RMII pins are not used by other components."""
# Only validate for RMII-based PHYs on ESP32/ESP32P4
if config[CONF_TYPE] in SPI_ETHERNET_TYPES or config[CONF_TYPE] == "OPENETH":
return # SPI and OPENETH don't use RMII
variant = get_esp32_variant()
if variant == VARIANT_ESP32:
rmii_pins = ESP32_RMII_FIXED_PINS
is_configurable = False
elif variant == VARIANT_ESP32P4:
rmii_pins = ESP32P4_RMII_DEFAULT_PINS
is_configurable = True
else:
return # No RMII validation needed for other variants
# Check all used pins against RMII reserved pins
for pin_list in pins.PIN_SCHEMA_REGISTRY.pins_used.values():
for pin_path, _, pin_config in pin_list:
pin_num = pin_config.get(CONF_NUMBER)
if pin_num not in rmii_pins:
continue
# Found a conflict - show helpful error message
pin_function = rmii_pins[pin_num]
component_path = ".".join(str(p) for p in pin_path)
if is_configurable:
error_msg = (
f"GPIO{pin_num} is used by Ethernet RMII "
f"({pin_function}) with the current default "
f"configuration. This conflicts with '{component_path}'. "
f"Please choose a different GPIO pin for "
f"'{component_path}'."
)
else:
error_msg = (
f"GPIO{pin_num} is reserved for Ethernet RMII "
f"({pin_function}) and cannot be used. This pin is "
f"hardcoded by ESP-IDF and cannot be changed when using "
f"RMII Ethernet PHYs. Please choose a different GPIO pin "
f"for '{component_path}'."
)
raise cv.Invalid(error_msg, path=pin_path)
def _final_validate(config: ConfigType) -> ConfigType:
"""Final validation for Ethernet component."""
_final_validate_spi(config)
_final_validate_rmii_pins(config)
return config
FINAL_VALIDATE_SCHEMA = _final_validate

View File

@@ -85,11 +85,6 @@ def event_schema(
return _EVENT_SCHEMA.extend(schema)
# Remove before 2025.11.0
EVENT_SCHEMA = event_schema()
EVENT_SCHEMA.add_extra(cv.deprecated_schema_constant("event"))
async def setup_event_core_(var, config, *, event_types: list[str]):
await setup_entity(var, config, "event")

View File

@@ -8,12 +8,19 @@ namespace event {
static const char *const TAG = "event";
void Event::trigger(const std::string &event_type) {
auto found = types_.find(event_type);
if (found == types_.end()) {
// Linear search - faster than std::set for small datasets (1-5 items typical)
const std::string *found = nullptr;
for (const auto &type : this->types_) {
if (type == event_type) {
found = &type;
break;
}
}
if (found == nullptr) {
ESP_LOGE(TAG, "'%s': invalid event type for trigger(): %s", this->get_name().c_str(), event_type.c_str());
return;
}
last_event_type = &(*found);
last_event_type = found;
ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), last_event_type->c_str());
this->event_callback_.call(event_type);
}

View File

@@ -1,6 +1,5 @@
#pragma once
#include <set>
#include <string>
#include "esphome/core/component.h"
@@ -26,13 +25,13 @@ class Event : public EntityBase, public EntityBase_DeviceClass {
const std::string *last_event_type;
void trigger(const std::string &event_type);
void set_event_types(const std::set<std::string> &event_types) { this->types_ = event_types; }
std::set<std::string> get_event_types() const { return this->types_; }
void set_event_types(const std::initializer_list<std::string> &event_types) { this->types_ = event_types; }
const FixedVector<std::string> &get_event_types() const { return this->types_; }
void add_on_event_callback(std::function<void(const std::string &event_type)> &&callback);
protected:
CallbackManager<void(const std::string &event_type)> event_callback_;
std::set<std::string> types_;
FixedVector<std::string> types_;
};
} // namespace event

View File

@@ -189,10 +189,6 @@ def fan_schema(
return _FAN_SCHEMA.extend(schema)
# Remove before 2025.11.0
FAN_SCHEMA = fan_schema(Fan)
FAN_SCHEMA.add_extra(cv.deprecated_schema_constant("fan"))
_PRESET_MODES_SCHEMA = cv.All(
cv.ensure_list(cv.string_strict),
cv.Length(min=1),

View File

@@ -60,8 +60,6 @@ class FanCall {
this->speed_ = speed;
return *this;
}
ESPDEPRECATED("set_speed() with string argument is deprecated, use integer argument instead.", "2021.9")
FanCall &set_speed(const char *legacy_speed);
optional<int> get_speed() const { return this->speed_; }
FanCall &set_direction(FanDirection direction) {
this->direction_ = direction;

View File

@@ -67,7 +67,7 @@ void GPIOSwitch::write_state(bool state) {
this->pin_->digital_write(state);
this->publish_state(state);
}
void GPIOSwitch::set_interlock(const std::vector<Switch *> &interlock) { this->interlock_ = interlock; }
void GPIOSwitch::set_interlock(const std::initializer_list<Switch *> &interlock) { this->interlock_ = interlock; }
} // namespace gpio
} // namespace esphome

View File

@@ -2,10 +2,9 @@
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/components/switch/switch.h"
#include <vector>
namespace esphome {
namespace gpio {
@@ -19,14 +18,14 @@ class GPIOSwitch : public switch_::Switch, public Component {
void setup() override;
void dump_config() override;
void set_interlock(const std::vector<Switch *> &interlock);
void set_interlock(const std::initializer_list<Switch *> &interlock);
void set_interlock_wait_time(uint32_t interlock_wait_time) { interlock_wait_time_ = interlock_wait_time; }
protected:
void write_state(bool state) override;
GPIOPin *pin_;
std::vector<Switch *> interlock_;
FixedVector<Switch *> interlock_;
uint32_t interlock_wait_time_{0};
};

View File

@@ -171,7 +171,7 @@ void HaierClimateBase::toggle_power() {
PendingAction({ActionRequest::TOGGLE_POWER, esphome::optional<haier_protocol::HaierMessage>()});
}
void HaierClimateBase::set_supported_swing_modes(const std::set<climate::ClimateSwingMode> &modes) {
void HaierClimateBase::set_supported_swing_modes(climate::ClimateSwingModeMask modes) {
this->traits_.set_supported_swing_modes(modes);
if (!modes.empty())
this->traits_.add_supported_swing_mode(climate::CLIMATE_SWING_OFF);
@@ -179,13 +179,13 @@ void HaierClimateBase::set_supported_swing_modes(const std::set<climate::Climate
void HaierClimateBase::set_answer_timeout(uint32_t timeout) { this->haier_protocol_.set_answer_timeout(timeout); }
void HaierClimateBase::set_supported_modes(const std::set<climate::ClimateMode> &modes) {
void HaierClimateBase::set_supported_modes(climate::ClimateModeMask modes) {
this->traits_.set_supported_modes(modes);
this->traits_.add_supported_mode(climate::CLIMATE_MODE_OFF); // Always available
this->traits_.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL); // Always available
}
void HaierClimateBase::set_supported_presets(const std::set<climate::ClimatePreset> &presets) {
void HaierClimateBase::set_supported_presets(climate::ClimatePresetMask presets) {
this->traits_.set_supported_presets(presets);
if (!presets.empty())
this->traits_.add_supported_preset(climate::CLIMATE_PRESET_NONE);

View File

@@ -1,7 +1,6 @@
#pragma once
#include <chrono>
#include <set>
#include "esphome/components/climate/climate.h"
#include "esphome/components/uart/uart.h"
#include "esphome/core/automation.h"
@@ -60,9 +59,9 @@ class HaierClimateBase : public esphome::Component,
void send_power_off_command();
void toggle_power();
void reset_protocol() { this->reset_protocol_request_ = true; };
void set_supported_modes(const std::set<esphome::climate::ClimateMode> &modes);
void set_supported_swing_modes(const std::set<esphome::climate::ClimateSwingMode> &modes);
void set_supported_presets(const std::set<esphome::climate::ClimatePreset> &presets);
void set_supported_modes(esphome::climate::ClimateModeMask modes);
void set_supported_swing_modes(esphome::climate::ClimateSwingModeMask modes);
void set_supported_presets(esphome::climate::ClimatePresetMask presets);
bool valid_connection() const { return this->protocol_phase_ >= ProtocolPhases::IDLE; };
size_t available() noexcept override { return esphome::uart::UARTDevice::available(); };
size_t read_array(uint8_t *data, size_t len) noexcept override {

View File

@@ -1033,9 +1033,9 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
{
// Swing mode
ClimateSwingMode old_swing_mode = this->swing_mode;
const std::set<ClimateSwingMode> &swing_modes = traits_.get_supported_swing_modes();
bool vertical_swing_supported = swing_modes.find(CLIMATE_SWING_VERTICAL) != swing_modes.end();
bool horizontal_swing_supported = swing_modes.find(CLIMATE_SWING_HORIZONTAL) != swing_modes.end();
const auto &swing_modes = traits_.get_supported_swing_modes();
bool vertical_swing_supported = swing_modes.count(CLIMATE_SWING_VERTICAL);
bool horizontal_swing_supported = swing_modes.count(CLIMATE_SWING_HORIZONTAL);
if (horizontal_swing_supported &&
(packet.control.horizontal_swing_mode == (uint8_t) hon_protocol::HorizontalSwingMode::AUTO)) {
if (vertical_swing_supported &&
@@ -1218,13 +1218,13 @@ void HonClimate::fill_control_messages_queue_() {
(uint8_t) hon_protocol::DataParameters::QUIET_MODE,
quiet_mode_buf, 2);
}
if ((fast_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_BOOST) != presets.end()))) {
if ((fast_mode_buf[1] != 0xFF) && presets.count(climate::ClimatePreset::CLIMATE_PRESET_BOOST)) {
this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
(uint8_t) hon_protocol::DataParameters::FAST_MODE,
fast_mode_buf, 2);
}
if ((away_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_AWAY) != presets.end()))) {
if ((away_mode_buf[1] != 0xFF) && presets.count(climate::ClimatePreset::CLIMATE_PRESET_AWAY)) {
this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
(uint8_t) hon_protocol::DataParameters::TEN_DEGREE,

View File

@@ -0,0 +1 @@
CODEOWNERS = ["@optimusprimespace", "@ssieb"]

View File

@@ -0,0 +1,111 @@
#include "esphome/core/hal.h"
#include "hdc2010.h"
// https://github.com/vigsterkr/homebridge-hdc2010/blob/main/src/hdc2010.js
// https://github.com/lime-labs/HDC2080-Arduino/blob/master/src/HDC2080.cpp
namespace esphome {
namespace hdc2010 {
static const char *const TAG = "hdc2010";
static const uint8_t HDC2010_ADDRESS = 0x40; // 0b1000000 or 0b1000001 from datasheet
static const uint8_t HDC2010_CMD_CONFIGURATION_MEASUREMENT = 0x8F;
static const uint8_t HDC2010_CMD_START_MEASUREMENT = 0xF9;
static const uint8_t HDC2010_CMD_TEMPERATURE_LOW = 0x00;
static const uint8_t HDC2010_CMD_TEMPERATURE_HIGH = 0x01;
static const uint8_t HDC2010_CMD_HUMIDITY_LOW = 0x02;
static const uint8_t HDC2010_CMD_HUMIDITY_HIGH = 0x03;
static const uint8_t CONFIG = 0x0E;
static const uint8_t MEASUREMENT_CONFIG = 0x0F;
void HDC2010Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
const uint8_t data[2] = {
0b00000000, // resolution 14bit for both humidity and temperature
0b00000000 // reserved
};
if (!this->write_bytes(HDC2010_CMD_CONFIGURATION_MEASUREMENT, data, 2)) {
ESP_LOGW(TAG, "Initial config instruction error");
this->status_set_warning();
return;
}
// Set measurement mode to temperature and humidity
uint8_t config_contents;
this->read_register(MEASUREMENT_CONFIG, &config_contents, 1);
config_contents = (config_contents & 0xF9); // Always set to TEMP_AND_HUMID mode
this->write_bytes(MEASUREMENT_CONFIG, &config_contents, 1);
// Set rate to manual
this->read_register(CONFIG, &config_contents, 1);
config_contents &= 0x8F;
this->write_bytes(CONFIG, &config_contents, 1);
// Set temperature resolution to 14bit
this->read_register(CONFIG, &config_contents, 1);
config_contents &= 0x3F;
this->write_bytes(CONFIG, &config_contents, 1);
// Set humidity resolution to 14bit
this->read_register(CONFIG, &config_contents, 1);
config_contents &= 0xCF;
this->write_bytes(CONFIG, &config_contents, 1);
}
void HDC2010Component::dump_config() {
ESP_LOGCONFIG(TAG, "HDC2010:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
}
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
}
void HDC2010Component::update() {
// Trigger measurement
uint8_t config_contents;
this->read_register(CONFIG, &config_contents, 1);
config_contents |= 0x01;
this->write_bytes(MEASUREMENT_CONFIG, &config_contents, 1);
// 1ms delay after triggering the sample
set_timeout(1, [this]() {
if (this->temperature_sensor_ != nullptr) {
float temp = this->read_temp();
this->temperature_sensor_->publish_state(temp);
ESP_LOGD(TAG, "Temp=%.1f°C", temp);
}
if (this->humidity_sensor_ != nullptr) {
float humidity = this->read_humidity();
this->humidity_sensor_->publish_state(humidity);
ESP_LOGD(TAG, "Humidity=%.1f%%", humidity);
}
});
}
float HDC2010Component::read_temp() {
uint8_t byte[2];
this->read_register(HDC2010_CMD_TEMPERATURE_LOW, &byte[0], 1);
this->read_register(HDC2010_CMD_TEMPERATURE_HIGH, &byte[1], 1);
uint16_t temp = encode_uint16(byte[1], byte[0]);
return (float) temp * 0.0025177f - 40.0f;
}
float HDC2010Component::read_humidity() {
uint8_t byte[2];
this->read_register(HDC2010_CMD_HUMIDITY_LOW, &byte[0], 1);
this->read_register(HDC2010_CMD_HUMIDITY_HIGH, &byte[1], 1);
uint16_t humidity = encode_uint16(byte[1], byte[0]);
return (float) humidity * 0.001525879f;
}
} // namespace hdc2010
} // namespace esphome

View File

@@ -0,0 +1,32 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace hdc2010 {
class HDC2010Component : public PollingComponent, public i2c::I2CDevice {
public:
void set_temperature_sensor(sensor::Sensor *temperature) { this->temperature_sensor_ = temperature; }
void set_humidity_sensor(sensor::Sensor *humidity) { this->humidity_sensor_ = humidity; }
/// Setup the sensor and check for connection.
void setup() override;
void dump_config() override;
/// Retrieve the latest sensor values. This operation takes approximately 16ms.
void update() override;
float read_temp();
float read_humidity();
protected:
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};
};
} // namespace hdc2010
} // namespace esphome

View File

@@ -0,0 +1,56 @@
import esphome.codegen as cg
from esphome.components import i2c, sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_HUMIDITY,
CONF_ID,
CONF_TEMPERATURE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
)
DEPENDENCIES = ["i2c"]
hdc2010_ns = cg.esphome_ns.namespace("hdc2010")
HDC2010Component = hdc2010_ns.class_(
"HDC2010Component", cg.PollingComponent, i2c.I2CDevice
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(HDC2010Component),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x40))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if temperature_config := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(temperature_config)
cg.add(var.set_temperature_sensor(sens))
if humidity_config := config.get(CONF_HUMIDITY):
sens = await sensor.new_sensor(humidity_config)
cg.add(var.set_humidity_sensor(sens))

View File

@@ -97,12 +97,11 @@ const float TEMP_MAX = 100; // Celsius
class HeatpumpIRClimate : public climate_ir::ClimateIR {
public:
HeatpumpIRClimate()
: climate_ir::ClimateIR(
TEMP_MIN, TEMP_MAX, 1.0f, true, true,
std::set<climate::ClimateFanMode>{climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM,
climate::CLIMATE_FAN_HIGH, climate::CLIMATE_FAN_AUTO},
std::set<climate::ClimateSwingMode>{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_HORIZONTAL,
climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_BOTH}) {}
: climate_ir::ClimateIR(TEMP_MIN, TEMP_MAX, 1.0f, true, true,
{climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH,
climate::CLIMATE_FAN_AUTO},
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_HORIZONTAL,
climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_BOTH}) {}
void setup() override;
void set_protocol(Protocol protocol) { this->protocol_ = protocol; }
void set_horizontal_default(HorizontalDirection horizontal_direction) {

View File

@@ -28,8 +28,8 @@ class HostGPIOPin : public InternalGPIOPin {
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
uint8_t pin_;
bool inverted_;
gpio::Flags flags_;
bool inverted_{};
gpio::Flags flags_{};
};
} // namespace host

View File

@@ -57,6 +57,9 @@ async def host_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
num = config[CONF_NUMBER]
cg.add(var.set_pin(num))
cg.add(var.set_inverted(config[CONF_INVERTED]))
# Only set if true to avoid bloating setup() function
# (inverted bit in pin_flags_ bitfield is zero-initialized to false)
if config[CONF_INVERTED]:
cg.add(var.set_inverted(True))
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
return var

View File

@@ -12,7 +12,6 @@ from esphome.const import (
CONF_ON_ERROR,
CONF_ON_RESPONSE,
CONF_TIMEOUT,
CONF_TRIGGER_ID,
CONF_URL,
CONF_WATCHDOG_TIMEOUT,
PLATFORM_HOST,
@@ -216,16 +215,8 @@ HTTP_REQUEST_ACTION_SCHEMA = cv.Schema(
f"{CONF_VERIFY_SSL} has moved to the base component configuration."
),
cv.Optional(CONF_CAPTURE_RESPONSE, default=False): cv.boolean,
cv.Optional(CONF_ON_RESPONSE): automation.validate_automation(
{cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(HttpRequestResponseTrigger)}
),
cv.Optional(CONF_ON_ERROR): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
automation.Trigger.template()
)
}
),
cv.Optional(CONF_ON_RESPONSE): automation.validate_automation(single=True),
cv.Optional(CONF_ON_ERROR): automation.validate_automation(single=True),
cv.Optional(CONF_MAX_RESPONSE_BUFFER_SIZE, default="1kB"): cv.validate_bytes,
}
)
@@ -280,7 +271,12 @@ async def http_request_action_to_code(config, action_id, template_arg, args):
template_ = await cg.templatable(config[CONF_URL], args, cg.std_string)
cg.add(var.set_url(template_))
cg.add(var.set_method(config[CONF_METHOD]))
cg.add(var.set_capture_response(config[CONF_CAPTURE_RESPONSE]))
capture_response = config[CONF_CAPTURE_RESPONSE]
if capture_response:
cg.add(var.set_capture_response(capture_response))
cg.add_define("USE_HTTP_REQUEST_RESPONSE")
cg.add(var.set_max_response_buffer_size(config[CONF_MAX_RESPONSE_BUFFER_SIZE]))
if CONF_BODY in config:
@@ -303,21 +299,26 @@ async def http_request_action_to_code(config, action_id, template_arg, args):
for value in config.get(CONF_COLLECT_HEADERS, []):
cg.add(var.add_collect_header(value))
for conf in config.get(CONF_ON_RESPONSE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
cg.add(var.register_response_trigger(trigger))
await automation.build_automation(
trigger,
[
(cg.std_shared_ptr.template(HttpContainer), "response"),
(cg.std_string_ref, "body"),
],
conf,
)
for conf in config.get(CONF_ON_ERROR, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
cg.add(var.register_error_trigger(trigger))
await automation.build_automation(trigger, [], conf)
if response_conf := config.get(CONF_ON_RESPONSE):
if capture_response:
await automation.build_automation(
var.get_success_trigger_with_response(),
[
(cg.std_shared_ptr.template(HttpContainer), "response"),
(cg.std_string_ref, "body"),
*args,
],
response_conf,
)
else:
await automation.build_automation(
var.get_success_trigger(),
[(cg.std_shared_ptr.template(HttpContainer), "response"), *args],
response_conf,
)
if error_conf := config.get(CONF_ON_ERROR):
await automation.build_automation(var.get_error_trigger(), args, error_conf)
return var

View File

@@ -124,7 +124,7 @@ class HttpRequestComponent : public Component {
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
void set_useragent(const char *useragent) { this->useragent_ = useragent; }
void set_timeout(uint16_t timeout) { this->timeout_ = timeout; }
void set_timeout(uint32_t timeout) { this->timeout_ = timeout; }
void set_watchdog_timeout(uint32_t watchdog_timeout) { this->watchdog_timeout_ = watchdog_timeout; }
uint32_t get_watchdog_timeout() const { return this->watchdog_timeout_; }
void set_follow_redirects(bool follow_redirects) { this->follow_redirects_ = follow_redirects; }
@@ -169,11 +169,11 @@ class HttpRequestComponent : public Component {
protected:
virtual std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method,
const std::string &body, const std::list<Header> &request_headers,
std::set<std::string> collect_headers) = 0;
const std::set<std::string> &collect_headers) = 0;
const char *useragent_{nullptr};
bool follow_redirects_{};
uint16_t redirect_limit_{};
uint16_t timeout_{4500};
uint32_t timeout_{4500};
uint32_t watchdog_timeout_{0};
};
@@ -183,7 +183,9 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
TEMPLATABLE_VALUE(std::string, url)
TEMPLATABLE_VALUE(const char *, method)
TEMPLATABLE_VALUE(std::string, body)
#ifdef USE_HTTP_REQUEST_RESPONSE
TEMPLATABLE_VALUE(bool, capture_response)
#endif
void add_request_header(const char *key, TemplatableValue<const char *, Ts...> value) {
this->request_headers_.insert({key, value});
@@ -195,9 +197,14 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
void set_json(std::function<void(Ts..., JsonObject)> json_func) { this->json_func_ = json_func; }
void register_response_trigger(HttpRequestResponseTrigger *trigger) { this->response_triggers_.push_back(trigger); }
#ifdef USE_HTTP_REQUEST_RESPONSE
Trigger<std::shared_ptr<HttpContainer>, std::string &, Ts...> *get_success_trigger_with_response() const {
return this->success_trigger_with_response_;
}
#endif
Trigger<std::shared_ptr<HttpContainer>, Ts...> *get_success_trigger() const { return this->success_trigger_; }
void register_error_trigger(Trigger<> *trigger) { this->error_triggers_.push_back(trigger); }
Trigger<Ts...> *get_error_trigger() const { return this->error_trigger_; }
void set_max_response_buffer_size(size_t max_response_buffer_size) {
this->max_response_buffer_size_ = max_response_buffer_size;
@@ -228,17 +235,20 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
auto container = this->parent_->start(this->url_.value(x...), this->method_.value(x...), body, request_headers,
this->collect_headers_);
auto captured_args = std::make_tuple(x...);
if (container == nullptr) {
for (auto *trigger : this->error_triggers_)
trigger->trigger();
std::apply([this](Ts... captured_args_inner) { this->error_trigger_->trigger(captured_args_inner...); },
captured_args);
return;
}
size_t content_length = container->content_length;
size_t max_length = std::min(content_length, this->max_response_buffer_size_);
std::string response_body;
#ifdef USE_HTTP_REQUEST_RESPONSE
if (this->capture_response_.value(x...)) {
std::string response_body;
RAMAllocator<uint8_t> allocator;
uint8_t *buf = allocator.allocate(max_length);
if (buf != nullptr) {
@@ -253,19 +263,17 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
response_body.assign((char *) buf, read_index);
allocator.deallocate(buf, max_length);
}
}
if (this->response_triggers_.size() == 1) {
// if there is only one trigger, no need to copy the response body
this->response_triggers_[0]->process(container, response_body);
} else {
for (auto *trigger : this->response_triggers_) {
// with multiple triggers, pass a copy of the response body to each
// one so that modifications made in one trigger are not visible to
// the others
auto response_body_copy = std::string(response_body);
trigger->process(container, response_body_copy);
}
std::apply(
[this, &container, &response_body](Ts... captured_args_inner) {
this->success_trigger_with_response_->trigger(container, response_body, captured_args_inner...);
},
captured_args);
} else
#endif
{
std::apply([this, &container](
Ts... captured_args_inner) { this->success_trigger_->trigger(container, captured_args_inner...); },
captured_args);
}
container->end();
}
@@ -283,8 +291,13 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
std::set<std::string> collect_headers_{"content-type", "content-length"};
std::map<const char *, TemplatableValue<std::string, Ts...>> json_{};
std::function<void(Ts..., JsonObject)> json_func_{nullptr};
std::vector<HttpRequestResponseTrigger *> response_triggers_{};
std::vector<Trigger<> *> error_triggers_{};
#ifdef USE_HTTP_REQUEST_RESPONSE
Trigger<std::shared_ptr<HttpContainer>, std::string &, Ts...> *success_trigger_with_response_ =
new Trigger<std::shared_ptr<HttpContainer>, std::string &, Ts...>();
#endif
Trigger<std::shared_ptr<HttpContainer>, Ts...> *success_trigger_ =
new Trigger<std::shared_ptr<HttpContainer>, Ts...>();
Trigger<Ts...> *error_trigger_ = new Trigger<Ts...>();
size_t max_response_buffer_size_{SIZE_MAX};
};

View File

@@ -17,7 +17,7 @@ static const char *const TAG = "http_request.arduino";
std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &url, const std::string &method,
const std::string &body,
const std::list<Header> &request_headers,
std::set<std::string> collect_headers) {
const std::set<std::string> &collect_headers) {
if (!network::is_connected()) {
this->status_momentary_error("failed", 1000);
ESP_LOGW(TAG, "HTTP Request failed; Not connected to network");

View File

@@ -33,7 +33,7 @@ class HttpRequestArduino : public HttpRequestComponent {
protected:
std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body,
const std::list<Header> &request_headers,
std::set<std::string> collect_headers) override;
const std::set<std::string> &collect_headers) override;
};
} // namespace http_request

View File

@@ -20,7 +20,7 @@ static const char *const TAG = "http_request.host";
std::shared_ptr<HttpContainer> HttpRequestHost::perform(const std::string &url, const std::string &method,
const std::string &body,
const std::list<Header> &request_headers,
std::set<std::string> response_headers) {
const std::set<std::string> &response_headers) {
if (!network::is_connected()) {
this->status_momentary_error("failed", 1000);
ESP_LOGW(TAG, "HTTP Request failed; Not connected to network");

View File

@@ -20,7 +20,7 @@ class HttpRequestHost : public HttpRequestComponent {
public:
std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body,
const std::list<Header> &request_headers,
std::set<std::string> response_headers) override;
const std::set<std::string> &response_headers) override;
void set_ca_path(const char *ca_path) { this->ca_path_ = ca_path; }
protected:

View File

@@ -55,7 +55,7 @@ esp_err_t HttpRequestIDF::http_event_handler(esp_http_client_event_t *evt) {
std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, const std::string &method,
const std::string &body,
const std::list<Header> &request_headers,
std::set<std::string> collect_headers) {
const std::set<std::string> &collect_headers) {
if (!network::is_connected()) {
this->status_momentary_error("failed", 1000);
ESP_LOGE(TAG, "HTTP Request failed; Not connected to network");

View File

@@ -39,7 +39,7 @@ class HttpRequestIDF : public HttpRequestComponent {
protected:
std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body,
const std::list<Header> &request_headers,
std::set<std::string> collect_headers) override;
const std::set<std::string> &collect_headers) override;
// if zero ESP-IDF will use DEFAULT_HTTP_BUF_SIZE
uint16_t buffer_size_rx_{};
uint16_t buffer_size_tx_{};

View File

@@ -125,7 +125,7 @@ lv_img_dsc_t *Image::get_lv_img_dsc() {
case IMAGE_TYPE_RGB:
#if LV_COLOR_DEPTH == 32
switch (this->transparent_) {
switch (this->transparency_) {
case TRANSPARENCY_ALPHA_CHANNEL:
this->dsc_.header.cf = LV_IMG_CF_TRUE_COLOR_ALPHA;
break;
@@ -156,7 +156,8 @@ lv_img_dsc_t *Image::get_lv_img_dsc() {
break;
}
#else
this->dsc_.header.cf = this->transparent_ == TRANSPARENCY_ALPHA_CHANNEL ? LV_IMG_CF_RGB565A8 : LV_IMG_CF_RGB565;
this->dsc_.header.cf =
this->transparency_ == TRANSPARENCY_ALPHA_CHANNEL ? LV_IMG_CF_RGB565A8 : LV_IMG_CF_RGB565;
#endif
break;
}

View File

@@ -3,6 +3,8 @@ import re
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import __version__
from esphome.cpp_generator import MockObj
from esphome.types import ConfigType
CODEOWNERS = ["@esphome/core"]
@@ -35,7 +37,9 @@ def _process_next_url(url: str):
return url
async def setup_improv_core(var, config):
if CONF_NEXT_URL in config:
cg.add(var.set_next_url(_process_next_url(config[CONF_NEXT_URL])))
async def setup_improv_core(var: MockObj, config: ConfigType, component: str):
if next_url := config.get(CONF_NEXT_URL):
cg.add(var.set_next_url(_process_next_url(next_url)))
cg.add_define(f"USE_{component.upper()}_NEXT_URL")
cg.add_library("improv/Improv", "1.2.4")

View File

@@ -2,10 +2,12 @@
#include "esphome/components/network/util.h"
#include "esphome/core/application.h"
#include "esphome/core/defines.h"
namespace esphome {
namespace improv_base {
#if defined(USE_ESP32_IMPROV_NEXT_URL) || defined(USE_IMPROV_SERIAL_NEXT_URL)
static constexpr const char DEVICE_NAME_PLACEHOLDER[] = "{{device_name}}";
static constexpr size_t DEVICE_NAME_PLACEHOLDER_LEN = sizeof(DEVICE_NAME_PLACEHOLDER) - 1;
static constexpr const char IP_ADDRESS_PLACEHOLDER[] = "{{ip_address}}";
@@ -43,6 +45,7 @@ std::string ImprovBase::get_formatted_next_url_() {
return formatted_url;
}
#endif
} // namespace improv_base
} // namespace esphome

View File

@@ -1,17 +1,22 @@
#pragma once
#include <string>
#include "esphome/core/defines.h"
namespace esphome {
namespace improv_base {
class ImprovBase {
public:
#if defined(USE_ESP32_IMPROV_NEXT_URL) || defined(USE_IMPROV_SERIAL_NEXT_URL)
void set_next_url(const std::string &next_url) { this->next_url_ = next_url; }
#endif
protected:
#if defined(USE_ESP32_IMPROV_NEXT_URL) || defined(USE_IMPROV_SERIAL_NEXT_URL)
std::string get_formatted_next_url_();
std::string next_url_;
#endif
};
} // namespace improv_base

View File

@@ -43,4 +43,4 @@ FINAL_VALIDATE_SCHEMA = validate_logger
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await improv_base.setup_improv_core(var, config)
await improv_base.setup_improv_core(var, config, "improv_serial")

View File

@@ -28,6 +28,38 @@ void ImprovSerialComponent::setup() {
}
}
void ImprovSerialComponent::loop() {
if (this->last_read_byte_ && (millis() - this->last_read_byte_ > IMPROV_SERIAL_TIMEOUT)) {
this->last_read_byte_ = 0;
this->rx_buffer_.clear();
ESP_LOGV(TAG, "Timeout");
}
auto byte = this->read_byte_();
while (byte.has_value()) {
if (this->parse_improv_serial_byte_(byte.value())) {
this->last_read_byte_ = millis();
} else {
this->last_read_byte_ = 0;
this->rx_buffer_.clear();
}
byte = this->read_byte_();
}
if (this->state_ == improv::STATE_PROVISIONING) {
if (wifi::global_wifi_component->is_connected()) {
wifi::global_wifi_component->save_wifi_sta(this->connecting_sta_.get_ssid(),
this->connecting_sta_.get_password());
this->connecting_sta_ = {};
this->cancel_timeout("wifi-connect-timeout");
this->set_state_(improv::STATE_PROVISIONED);
std::vector<uint8_t> url = this->build_rpc_settings_response_(improv::WIFI_SETTINGS);
this->send_response_(url);
}
}
}
void ImprovSerialComponent::dump_config() { ESP_LOGCONFIG(TAG, "Improv Serial:"); }
optional<uint8_t> ImprovSerialComponent::read_byte_() {
@@ -78,8 +110,28 @@ optional<uint8_t> ImprovSerialComponent::read_byte_() {
return byte;
}
void ImprovSerialComponent::write_data_(std::vector<uint8_t> &data) {
data.push_back('\n');
void ImprovSerialComponent::write_data_(const uint8_t *data, const size_t size) {
// First, set length field
this->tx_header_[TX_LENGTH_IDX] = this->tx_header_[TX_TYPE_IDX] == TYPE_RPC_RESPONSE ? size : 1;
const bool there_is_data = data != nullptr && size > 0;
// If there_is_data, checksum must not include our optional data byte
const uint8_t header_checksum_len = there_is_data ? TX_BUFFER_SIZE - 3 : TX_BUFFER_SIZE - 2;
// Only transmit the full buffer length if there is no data (only state/error byte is provided in this case)
const uint8_t header_tx_len = there_is_data ? TX_BUFFER_SIZE - 3 : TX_BUFFER_SIZE;
// Calculate checksum for message
uint8_t checksum = 0;
for (uint8_t i = 0; i < header_checksum_len; i++) {
checksum += this->tx_header_[i];
}
if (there_is_data) {
// Include data in checksum
for (size_t i = 0; i < size; i++) {
checksum += data[i];
}
}
this->tx_header_[TX_CHECKSUM_IDX] = checksum;
#ifdef USE_ESP32
switch (logger::global_logger->get_uart()) {
case logger::UART_SELECTION_UART0:
@@ -87,68 +139,52 @@ void ImprovSerialComponent::write_data_(std::vector<uint8_t> &data) {
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \
!defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3)
case logger::UART_SELECTION_UART2:
#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3
uart_write_bytes(this->uart_num_, data.data(), data.size());
#endif
uart_write_bytes(this->uart_num_, this->tx_header_, header_tx_len);
if (there_is_data) {
uart_write_bytes(this->uart_num_, data, size);
uart_write_bytes(this->uart_num_, &this->tx_header_[TX_CHECKSUM_IDX], 2); // Footer: checksum and newline
}
break;
#if defined(USE_LOGGER_USB_CDC) && defined(CONFIG_ESP_CONSOLE_USB_CDC)
case logger::UART_SELECTION_USB_CDC: {
const char *msg = (char *) data.data();
esp_usb_console_write_buf(msg, data.size());
case logger::UART_SELECTION_USB_CDC:
esp_usb_console_write_buf((const char *) this->tx_header_, header_tx_len);
if (there_is_data) {
esp_usb_console_write_buf((const char *) data, size);
esp_usb_console_write_buf((const char *) &this->tx_header_[TX_CHECKSUM_IDX],
2); // Footer: checksum and newline
}
break;
}
#endif // USE_LOGGER_USB_CDC
#endif
#ifdef USE_LOGGER_USB_SERIAL_JTAG
case logger::UART_SELECTION_USB_SERIAL_JTAG:
usb_serial_jtag_write_bytes((char *) data.data(), data.size(), 20 / portTICK_PERIOD_MS);
delay(10);
usb_serial_jtag_ll_txfifo_flush(); // fixes for issue in IDF 4.4.7
usb_serial_jtag_write_bytes((const char *) this->tx_header_, header_tx_len, 20 / portTICK_PERIOD_MS);
if (there_is_data) {
usb_serial_jtag_write_bytes((const char *) data, size, 20 / portTICK_PERIOD_MS);
usb_serial_jtag_write_bytes((const char *) &this->tx_header_[TX_CHECKSUM_IDX], 2,
20 / portTICK_PERIOD_MS); // Footer: checksum and newline
}
break;
#endif // USE_LOGGER_USB_SERIAL_JTAG
#endif
default:
break;
}
#elif defined(USE_ARDUINO)
this->hw_serial_->write(data.data(), data.size());
this->hw_serial_->write(this->tx_header_, header_tx_len);
if (there_is_data) {
this->hw_serial_->write(data, size);
this->hw_serial_->write(&this->tx_header_[TX_CHECKSUM_IDX], 2); // Footer: checksum and newline
}
#endif
}
void ImprovSerialComponent::loop() {
if (this->last_read_byte_ && (millis() - this->last_read_byte_ > IMPROV_SERIAL_TIMEOUT)) {
this->last_read_byte_ = 0;
this->rx_buffer_.clear();
ESP_LOGV(TAG, "Improv Serial timeout");
}
auto byte = this->read_byte_();
while (byte.has_value()) {
if (this->parse_improv_serial_byte_(byte.value())) {
this->last_read_byte_ = millis();
} else {
this->last_read_byte_ = 0;
this->rx_buffer_.clear();
}
byte = this->read_byte_();
}
if (this->state_ == improv::STATE_PROVISIONING) {
if (wifi::global_wifi_component->is_connected()) {
wifi::global_wifi_component->save_wifi_sta(this->connecting_sta_.get_ssid(),
this->connecting_sta_.get_password());
this->connecting_sta_ = {};
this->cancel_timeout("wifi-connect-timeout");
this->set_state_(improv::STATE_PROVISIONED);
std::vector<uint8_t> url = this->build_rpc_settings_response_(improv::WIFI_SETTINGS);
this->send_response_(url);
}
}
}
std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) {
std::vector<std::string> urls;
#ifdef USE_IMPROV_SERIAL_NEXT_URL
if (!this->next_url_.empty()) {
urls.push_back(this->get_formatted_next_url_());
}
#endif
#ifdef USE_WEBSERVER
for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) {
if (ip.is_ip4()) {
@@ -175,13 +211,13 @@ std::vector<uint8_t> ImprovSerialComponent::build_version_info_() {
bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) {
size_t at = this->rx_buffer_.size();
this->rx_buffer_.push_back(byte);
ESP_LOGV(TAG, "Improv Serial byte: 0x%02X", byte);
ESP_LOGV(TAG, "Byte: 0x%02X", byte);
const uint8_t *raw = &this->rx_buffer_[0];
return improv::parse_improv_serial_byte(
at, byte, raw, [this](improv::ImprovCommand command) -> bool { return this->parse_improv_payload_(command); },
[this](improv::Error error) -> void {
ESP_LOGW(TAG, "Error decoding Improv payload");
ESP_LOGW(TAG, "Error decoding payload");
this->set_error_(error);
});
}
@@ -197,7 +233,7 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command
wifi::global_wifi_component->set_sta(sta);
wifi::global_wifi_component->start_connecting(sta, false);
this->set_state_(improv::STATE_PROVISIONING);
ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
ESP_LOGD(TAG, "Received settings: SSID=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
command.password.c_str());
auto f = std::bind(&ImprovSerialComponent::on_wifi_connect_timeout_, this);
@@ -238,7 +274,7 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command
return true;
}
default: {
ESP_LOGW(TAG, "Unknown Improv payload");
ESP_LOGW(TAG, "Unknown payload");
this->set_error_(improv::ERROR_UNKNOWN_RPC);
return false;
}
@@ -247,57 +283,26 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command
void ImprovSerialComponent::set_state_(improv::State state) {
this->state_ = state;
std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
data.resize(11);
data[6] = IMPROV_SERIAL_VERSION;
data[7] = TYPE_CURRENT_STATE;
data[8] = 1;
data[9] = state;
uint8_t checksum = 0x00;
for (uint8_t d : data)
checksum += d;
data[10] = checksum;
this->write_data_(data);
this->tx_header_[TX_TYPE_IDX] = TYPE_CURRENT_STATE;
this->tx_header_[TX_DATA_IDX] = state;
this->write_data_();
}
void ImprovSerialComponent::set_error_(improv::Error error) {
std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
data.resize(11);
data[6] = IMPROV_SERIAL_VERSION;
data[7] = TYPE_ERROR_STATE;
data[8] = 1;
data[9] = error;
uint8_t checksum = 0x00;
for (uint8_t d : data)
checksum += d;
data[10] = checksum;
this->write_data_(data);
this->tx_header_[TX_TYPE_IDX] = TYPE_ERROR_STATE;
this->tx_header_[TX_DATA_IDX] = error;
this->write_data_();
}
void ImprovSerialComponent::send_response_(std::vector<uint8_t> &response) {
std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
data.resize(9);
data[6] = IMPROV_SERIAL_VERSION;
data[7] = TYPE_RPC_RESPONSE;
data[8] = response.size();
data.insert(data.end(), response.begin(), response.end());
uint8_t checksum = 0x00;
for (uint8_t d : data)
checksum += d;
data.push_back(checksum);
this->write_data_(data);
this->tx_header_[TX_TYPE_IDX] = TYPE_RPC_RESPONSE;
this->write_data_(response.data(), response.size());
}
void ImprovSerialComponent::on_wifi_connect_timeout_() {
this->set_error_(improv::ERROR_UNABLE_TO_CONNECT);
this->set_state_(improv::STATE_AUTHORIZED);
ESP_LOGW(TAG, "Timed out trying to connect to given WiFi network");
ESP_LOGW(TAG, "Timed out while connecting to Wi-Fi network");
wifi::global_wifi_component->clear_sta();
}

View File

@@ -26,6 +26,16 @@
namespace esphome {
namespace improv_serial {
// TX buffer layout constants
static constexpr uint8_t TX_HEADER_SIZE = 6; // Bytes 0-5 = "IMPROV"
static constexpr uint8_t TX_VERSION_IDX = 6;
static constexpr uint8_t TX_TYPE_IDX = 7;
static constexpr uint8_t TX_LENGTH_IDX = 8;
static constexpr uint8_t TX_DATA_IDX = 9; // For state/error messages only
static constexpr uint8_t TX_CHECKSUM_IDX = 10;
static constexpr uint8_t TX_NEWLINE_IDX = 11;
static constexpr uint8_t TX_BUFFER_SIZE = 12;
enum ImprovSerialType : uint8_t {
TYPE_CURRENT_STATE = 0x01,
TYPE_ERROR_STATE = 0x02,
@@ -57,7 +67,22 @@ class ImprovSerialComponent : public Component, public improv_base::ImprovBase {
std::vector<uint8_t> build_version_info_();
optional<uint8_t> read_byte_();
void write_data_(std::vector<uint8_t> &data);
void write_data_(const uint8_t *data = nullptr, size_t size = 0);
uint8_t tx_header_[TX_BUFFER_SIZE] = {
'I', // 0: Header
'M', // 1: Header
'P', // 2: Header
'R', // 3: Header
'O', // 4: Header
'V', // 5: Header
IMPROV_SERIAL_VERSION, // 6: Version
0, // 7: ImprovSerialType
0, // 8: Length
0, // 9...X: Data (here, one byte reserved for state/error)
0, // X + 10: Checksum
'\n',
};
#ifdef USE_ESP32
uart_port_t uart_num_;

View File

@@ -199,6 +199,9 @@ async def component_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
num = config[CONF_NUMBER]
cg.add(var.set_pin(num))
cg.add(var.set_inverted(config[CONF_INVERTED]))
# Only set if true to avoid bloating setup() function
# (inverted bit in pin_flags_ bitfield is zero-initialized to false)
if config[CONF_INVERTED]:
cg.add(var.set_inverted(True))
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
return var

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