1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-18 15:55:46 +00:00

Compare commits

..

144 Commits

Author SHA1 Message Date
Jesse Hills
7a869a33f0 Merge pull request #10665 from esphome/bump-2025.9.0b1
2025.9.0b1
2025-09-11 07:24:33 +12:00
Keith Burzinski
56e85b3ef9 [thermostat] Rename timer enums to mitigate naming conflict (#10666) 2025-09-10 22:58:35 +12:00
Jesse Hills
2401f81be3 Bump version to 2025.9.0b1 2025-09-10 19:48:01 +12:00
Josip Šimun Kuči
52a7e26c6d [inkplate] Rename component and fix grayscale (#10200)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-10 18:46:58 +12:00
Jesse Hills
d729dc20a8 Merge branch 'release' into dev 2025-09-10 17:04:21 +12:00
Jesse Hills
d3b7a9687b Merge pull request #10664 from esphome/bump-2025.8.4
2025.8.4
2025-09-10 17:03:37 +12:00
Jesse Hills
9d7fc11108 Bump version to 2025.8.4 2025-09-10 13:56:50 +12:00
tomaszduda23
7969627d3e [light] add missing header (#10590) 2025-09-10 13:56:50 +12:00
Clyde Stubbs
82d2e367d4 [kmeteriso] Fix i2c call (#10618) 2025-09-10 13:56:50 +12:00
Keith Burzinski
972aa691e4 [sen5x] Fix initialization (#10603) 2025-09-10 13:56:50 +12:00
Daniel M
ac61b8f893 [bl0940] extend configuration options of bl0940 device (#8158)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-09-10 13:50:49 +12:00
Keith Burzinski
d9f625e5c8 [thermostat] General clean-up, optimization, properly support "auto" mode (#10561)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-09-10 13:24:50 +12:00
Jonathan Swoboda
e218f16f0f Allow both files and directories to be passed to update-all (#10575)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2025-09-09 18:06:59 -05:00
J. Nick Koston
422d209786 [api] Add timezone support to GetTimeResponse for automatic timezone synchronization (#10661) 2025-09-09 15:54:50 -05:00
dependabot[bot]
e972e1f8c2 Bump aioesphomeapi from 40.0.2 to 40.1.0 (#10662)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-09 20:33:36 +00:00
dependabot[bot]
cfb90b7b18 Bump pytest-cov from 6.3.0 to 7.0.0 (#10660)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-09 15:15:53 -05:00
mrtoy-me
8976ea2436 [ms5611] remove delay in setup (#10658) 2025-09-10 07:31:06 +12:00
tomaszduda23
01ff09064d [nrf52] add more tests (#10591) 2025-09-10 07:29:49 +12:00
tomaszduda23
39212f0d7f allow to implement show_logs as external component (#10523) 2025-09-09 11:45:42 -05:00
Mischa Siekmann
8993f4e6b4 RingBuffer: Make partial writing optional (#10302) 2025-09-09 08:39:47 -05:00
J. Nick Koston
7adad0ee49 [core] Refactor insertion sort functions to eliminate code duplication (#10653) 2025-09-09 02:03:35 -05:00
J. Nick Koston
dd8815ec9d [core] Reduce flash usage by refactoring looping component partitioning (#10652) 2025-09-09 01:17:30 -05:00
Jesse Hills
59e62a1f44 Sort codeowners using case-insensitive (#10651)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-09-08 21:26:22 -05:00
Edward Firmo
f5f84fe825 [nextion] Increase delay before reboot to prevent TFT upload interruption (#10402) 2025-09-09 12:57:24 +12:00
Edward Firmo
90c2fdd565 [adc] Fix autorange negative coefficient bug causing incorrect voltage readings (#10549) 2025-09-09 12:56:18 +12:00
tomaszduda23
f6d69231e8 [light] add missing header (#10590) 2025-09-09 11:10:29 +12:00
J. Nick Koston
5cc0e21bc7 [core] Reduce unnecessary nesting in scheduler loop (#10644) 2025-09-09 09:04:07 +12:00
Thomas Rupprecht
703b592793 Add I2S Audio Port for ESP32-C5/C6/H2 (#10414) 2025-09-08 14:03:41 -04:00
J. Nick Koston
75c9430d91 [core] Fix serial upload regression from DNS resolution PR #10595 (#10648) 2025-09-08 10:41:03 -05:00
J. Nick Koston
e5bba00deb [esp32] Reduce GPIO memory usage by 50% through bit-packing (#10556) 2025-09-08 08:46:30 -05:00
J. Nick Koston
8d90f13e97 [core] Store component source strings in flash on ESP8266 (breaking change) (#10621) 2025-09-07 20:10:00 -05:00
J. Nick Koston
666e33e70b [api] Store plaintext error message in PROGMEM on ESP8266 (#10634) 2025-09-07 20:09:47 -05:00
Clyde Stubbs
7eaaa4e426 [mipi_rgb] Unified driver for MIPI RGB displays (#9892)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-09-08 12:56:34 +12:00
J. Nick Koston
166ad942ef [scheduler] Reduce SchedulerItem memory usage by 7.4% on 32-bit platforms (#10553)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-07 18:51:07 -05:00
J. Nick Koston
0ff08bbc09 [mcp23016] Migrate to CachedGpioExpander to reduce I2C bus usage (#10581)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-07 18:26:49 -05:00
J. Nick Koston
6e2bcabbc9 [sx1509] Migrate to CachedGpioExpander to reduce I2C bus usage (#10588)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-09-07 18:26:33 -05:00
J. Nick Koston
afa191ae41 [pcf8574] Migrate to CachedGpioExpander to reduce I2C bus usage (#10573)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-09-07 18:26:22 -05:00
J. Nick Koston
93da52c4d2 [pca9554] Migrate to CachedGpioExpander to reduce I2C bus usage (#10571)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-09-07 18:26:11 -05:00
J. Nick Koston
0cc0979674 [pca6416a] Migrate to CachedGpioExpander to reduce I2C bus usage (#10587) 2025-09-08 10:59:23 +12:00
Jesse Hills
629f1e94f1 [ota] Fix duplicate include and sort (#10643) 2025-09-07 22:58:41 +00:00
J. Nick Koston
8c28f346c7 [gpio_expander] Add intelligent pin type selection to CachedGpioExpander template (#10577) 2025-09-08 10:57:02 +12:00
dependabot[bot]
3cf36e2f94 Bump aioesphomeapi from 40.0.1 to 40.0.2 (#10641)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-07 17:32:21 -05:00
J. Nick Koston
1ac07c96b1 [esphome] Store OTA component log strings in flash on ESP8266 (#10570) 2025-09-08 10:30:39 +12:00
J. Nick Koston
91228c82e6 [esp8266][logger] Store LOG_LEVELS strings in PROGMEM to reduce RAM usage (#10569) 2025-09-08 10:29:45 +12:00
J. Nick Koston
28d16728d3 [core] Add memory pool to scheduler to reduce heap fragmentation (#10536) 2025-09-08 10:27:58 +12:00
dependabot[bot]
f24a182ba2 Bump pytest-cov from 6.2.1 to 6.3.0 (#10640)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-07 17:26:31 -05:00
dependabot[bot]
0065fe1516 Bump zeroconf from 0.147.0 to 0.147.2 (#10642)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-07 17:26:06 -05:00
J. Nick Koston
148fa698cc Fix DNS resolution inconsistency between logs and OTA operations (#10595)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-09-08 10:25:22 +12:00
J. Nick Koston
b25506b045 [core] Skip redundant process_to_add() call when no scheduler items added (#10630)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-08 09:10:55 +12:00
J. Nick Koston
0c737fc4df [core] Convert LOG_UPDATE_INTERVAL macro to function to reduce flash usage (#10636) 2025-09-08 09:09:15 +12:00
davidmonro
a8b8507ffc Atm90e32/26 device class fixes (#10629) 2025-09-08 08:06:10 +12:00
J. Nick Koston
c33bb3a8a9 [esp8266] Store component warning strings in flash to reduce RAM usage (#10623) 2025-09-06 23:56:45 -05:00
Clyde Stubbs
4d09932320 [kmeteriso] Fix i2c call (#10618) 2025-09-06 13:51:44 +10:00
Keith Burzinski
e018b15641 [sen5x] Various optimizing & tidying up (#10602) 2025-09-05 20:10:48 -05:00
J. Nick Koston
3fd469cfe8 [esp8266][api] Store error strings in PROGMEM to reduce RAM usage (#10568) 2025-09-05 18:16:43 -05:00
J. Nick Koston
1359142106 [api] Store Noise protocol prologue in flash on ESP8266 (#10598) 2025-09-05 18:10:18 -05:00
J. Nick Koston
487ba4dad0 [mdns] Move constant strings to flash on ESP8266 (#10599) 2025-09-05 18:08:25 -05:00
J. Nick Koston
694c590eb6 [captive_portal] ESP8266: Move strings to PROGMEM (saves 192 bytes RAM) (#10600) 2025-09-05 18:02:12 -05:00
J. Nick Koston
b74463c3e6 [light] ESP8266: Store log strings in flash memory (#10611) 2025-09-05 17:59:24 -05:00
J. Nick Koston
98e8a0c201 [gpio] ESP8266: Store log strings in flash memory (#10610) 2025-09-05 17:57:24 -05:00
J. Nick Koston
91b2f75d04 [script] ESP8266: Store log format strings in PROGMEM (saves 240 bytes RAM) (#10614) 2025-09-05 17:56:00 -05:00
J. Nick Koston
f1806046a9 [web_server] ESP8266: Store OTA response strings in PROGMEM (saves 52 bytes RAM) (#10616) 2025-09-05 17:53:23 -05:00
J. Nick Koston
5b283d6d38 [sensor] ESP8266: Use LogString for state_class_to_string() to save RAM (#10617) 2025-09-05 17:51:35 -05:00
J. Nick Koston
1340665ac7 [logger] Use LogString for UART selection strings (saves 28 bytes RAM on ESP8266) (#10615)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-05 17:47:53 -05:00
J. Nick Koston
1510db277c [esphome] ESP8266: Move OTA error strings to PROGMEM (saves 116 bytes RAM) (#10620) 2025-09-05 17:44:23 -05:00
Keith Burzinski
a49669ee58 [sensirion_common] Tidy up, optimize (#10604) 2025-09-05 17:17:20 -05:00
Keith Burzinski
09b40b882e [sgp30] Tidy up, optimize (#10607) 2025-09-05 14:20:11 -05:00
Keith Burzinski
0069163d31 [sps30] Tidy up, optimize (#10606) 2025-09-05 14:11:14 -05:00
Keith Burzinski
86c2af4882 [sen5x] Fix initialization (#10603) 2025-09-05 18:37:57 +12:00
J. Nick Koston
b4b795dcaf [i2c] Optimize memory usage with stack allocation for small buffers (#10565)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2025-09-05 04:26:46 +00:00
dependabot[bot]
b8ed7ec145 Bump aioesphomeapi from 40.0.0 to 40.0.1 (#10596)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-05 01:08:15 +00:00
dependabot[bot]
365a427b57 Bump aioesphomeapi from 39.0.1 to 40.0.0 (#10594)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 23:37:03 +00:00
dependabot[bot]
e327ae8c95 Bump pytest-mock from 3.14.1 to 3.15.0 (#10593)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 23:36:11 +00:00
dependabot[bot]
4c2f356b35 Bump ruff from 0.12.11 to 0.12.12 (#10578)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2025-09-04 19:24:23 +00:00
dependabot[bot]
e55bce83e3 Bump actions/stale from 9.1.0 to 10.0.0 (#10582)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 14:20:11 -05:00
dependabot[bot]
ba2433197e Bump actions/github-script from 7.0.1 to 8.0.0 (#10583)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 14:16:17 -05:00
dependabot[bot]
c471bdb446 Bump actions/setup-python from 5.6.0 to 6.0.0 in /.github/actions/restore-python (#10586)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 14:15:57 -05:00
dependabot[bot]
cbac9caa52 Bump actions/setup-python from 5.6.0 to 6.0.0 (#10584)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 14:15:43 -05:00
dependabot[bot]
edf7094662 Bump esphome-dashboard from 20250828.0 to 20250904.0 (#10580)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 14:14:50 -05:00
dependabot[bot]
25489b6009 Bump codecov/codecov-action from 5.5.0 to 5.5.1 (#10585)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 14:14:28 -05:00
dependabot[bot]
dc45a613f3 Bump pytest from 8.4.1 to 8.4.2 (#10579)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 14:14:18 -05:00
dependabot[bot]
e0617e01e0 Bump pypa/gh-action-pypi-publish from 1.12.4 to 1.13.0 in /.github/workflows (#10572)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 11:10:53 -05:00
Jesse Hills
c7ee727af4 Merge branch 'release' into dev 2025-09-04 22:10:21 +12:00
Jesse Hills
c5b2a9e24b Merge pull request #10558 from esphome/bump-2025.8.3
2025.8.3
2025-09-04 22:09:37 +12:00
J. Nick Koston
101d553df9 [esp8266] Reduce preference memory usage by 40% through field optimization (#10557) 2025-09-04 02:46:50 -05:00
J. Nick Koston
8fb6420b1c [esp8266] Store GPIO initialization arrays in PROGMEM to save RAM (#10560) 2025-09-04 02:44:12 -05:00
Maxim Raznatovski
c03d978b46 [wizard] extend the wizard dashboard API to allow upload and empty config options (#10203) 2025-09-04 14:02:49 +12:00
Jesse Hills
2d3cdf60ba Bump version to 2025.8.3 2025-09-04 09:06:00 +12:00
J. Nick Koston
a29fef166b [api] Fix VERY_VERBOSE logging compilation error with bool arrays (#10539) 2025-09-04 09:06:00 +12:00
Jonathan Swoboda
9fe94f1201 [esp32] Clear IDF environment variables (#10527)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-09-04 09:06:00 +12:00
Anton Viktorov
1b8978a89a [i2c] Fix bug write_register16 (#10547) 2025-09-04 09:06:00 +12:00
Jonathan Swoboda
6f188d1284 [esp32] Rebuild when idf_component.yml changes (#10540) 2025-09-04 09:06:00 +12:00
Clyde Stubbs
a1a336783e [mcp4461] Fix read transaction (#10465)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-09-04 09:06:00 +12:00
Clyde Stubbs
c55bc93f70 [mipi_dsi] Fix config for Guition screen (#10464) 2025-09-04 09:06:00 +12:00
J. Nick Koston
de998f2f39 Fix incorrect entity count due to undefined execution order with globals (#10497) 2025-09-04 09:06:00 +12:00
Oliver Kleinecke
950299e52b Update mcp4461.cpp (#10479) 2025-09-04 09:06:00 +12:00
J. Nick Koston
23c6650902 [api] Fix VERY_VERBOSE logging compilation error with bool arrays (#10539) 2025-09-04 08:07:13 +12:00
Jonathan Swoboda
5759692627 [esp32] Clear IDF environment variables (#10527)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-09-03 19:59:47 +00:00
Jonathan Swoboda
0ab65c225e [wifi] Check for esp32_hosted on no wifi variants (#10528) 2025-09-04 07:58:42 +12:00
J. Nick Koston
8aeb6d3ba2 [bluetooth_proxy] Change default for active connections to true (#10546) 2025-09-04 07:27:39 +12:00
Anton Viktorov
c3359edb33 [i2c] Fix bug write_register16 (#10547) 2025-09-03 17:18:26 +00:00
Jonathan Swoboda
4d681ffe3d [esp32] Rebuild when idf_component.yml changes (#10540) 2025-09-03 11:47:51 -04:00
J. Nick Koston
68628a85b1 [core] Use get_unit_of_measurement_ref() in entity logging to avoid string allocations (#10532) 2025-09-03 06:08:57 +00:00
J. Nick Koston
086f1982fa [core] Use get_device_class_ref() in entity platform logging to avoid string allocations (#10531) 2025-09-03 14:26:53 +12:00
J. Nick Koston
5ba1c32242 [host] Fix memory allocation in preferences load() method (#10506) 2025-09-03 14:26:43 +12:00
J. Nick Koston
d2b23ba3a7 [sensor] Change state_class_to_string() to return const char* to avoid allocations (#10533) 2025-09-03 14:24:16 +12:00
J. Nick Koston
83fbd77c4a [core] Use get_icon_ref() in entity platform logging to avoid string allocations (#10530) 2025-09-03 14:23:46 +12:00
J. Nick Koston
1a054299d4 [core] Optimize fnv1_hash to avoid string allocations for static entities (#10529) 2025-09-02 21:17:14 -05:00
Jonathan Swoboda
e3fb9c2a78 [esp32] Remove hardcoding of ulp (#10535) 2025-09-02 23:51:17 +00:00
J. Nick Koston
d1276dc6df [core] Replace magic coroutine priority numbers with self-documenting CoroPriority enum (#10518) 2025-09-02 21:41:50 +00:00
Eyal
f286bc57f3 [core] Fix timezone offset calculation (#10426) 2025-09-02 16:45:25 +12:00
Clyde Stubbs
ed48282d09 [mcp4461] Fix read transaction (#10465)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-09-02 08:53:03 +12:00
Clyde Stubbs
2ddd8c72d6 [mipi_dsi] Fix config for Guition screen (#10464) 2025-09-02 08:51:31 +12:00
Mischa Siekmann
d0b4bc48e4 [wifi] Guard wifi error cases introduced in IDF5.2 by a version check (#10466) 2025-09-02 08:51:03 +12:00
tomaszduda23
77dbe77117 [nrf52] fix missing bootloader (#10519) 2025-09-01 12:30:02 -05:00
J. Nick Koston
6daeffcefd [bluetooth_proxy] Expose configured scanning mode in API responses (#10490) 2025-09-01 13:07:29 +12:00
J. Nick Koston
6d834c019d Fix incorrect entity count due to undefined execution order with globals (#10497) 2025-09-01 13:01:15 +12:00
tomaszduda23
905e2906fe [nrf52] add dfu (#9319)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-09-01 10:54:35 +12:00
Jesse Hills
a25b544c3b [display] Allow page actions to have auto generated display id (#10460) 2025-09-01 09:22:11 +12:00
Felix Kaechele
da21174c6d [sntp] Use callbacks to trigger on_time_sync for ESP32 and ESP8266 (#10390)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-09-01 09:02:56 +12:00
DT-art1
e29f0ee7f8 Add JPEG encoder support via new camera_encoder component (#9459)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-08-31 09:26:15 -05:00
Jesse Hills
983b3cb879 [mipi] Add type to models for better type hinting downstream (#10475) 2025-08-30 16:43:26 +10:00
dependabot[bot]
fd568d9af3 Bump aioesphomeapi from 39.0.0 to 39.0.1 (#10491)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-29 10:14:15 -05:00
Clyde Stubbs
ca72286386 [lvgl] Update hello world (#10469) 2025-08-29 15:42:39 +10:00
Ben Curtis
dea68bebd8 Adjust sen5x to match VOC/NOX datasheet (#9894)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-08-29 14:00:54 +12:00
Clyde Stubbs
ef98f67b41 [lvgl] Replace spinbox step with selected_digit (#10349)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-08-29 13:58:58 +12:00
Oliver Kleinecke
6a92b691a0 Update mcp4461.cpp (#10479) 2025-08-29 12:53:54 +12:00
Clyde Stubbs
bc960cf6d2 [mapping] Use custom allocator (#9972) 2025-08-29 12:52:37 +12:00
Jesse Hills
461ce69296 Merge branch 'release' into dev 2025-08-29 12:39:26 +12:00
Jesse Hills
6a20e6f9ad Merge pull request #10485 from esphome/bump-2025.8.2
2025.8.2
2025-08-29 12:38:45 +12:00
dependabot[bot]
cde00a1f4c Bump esphome-dashboard from 20250814.0 to 20250828.0 (#10484)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-29 11:02:15 +12:00
J. Nick Koston
5dc691874b [bluetooth_proxy] Remove unused ClientState::SEARCHING state (#10318) 2025-08-29 10:30:14 +12:00
dependabot[bot]
c526ab9a3f Bump ruff from 0.12.10 to 0.12.11 (#10483)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2025-08-28 22:20:23 +00:00
Jesse Hills
07875a8b1e Bump version to 2025.8.2 2025-08-29 10:16:19 +12:00
J. Nick Koston
ba4789970c [esphome] Fix OTA watchdog resets by validating all magic bytes before blocking (#10401) 2025-08-29 10:16:19 +12:00
Vinicius Fortuna
015977cfdf [rtttl] Fix RTTTL for speakers (#10381) 2025-08-29 10:16:19 +12:00
J. Nick Koston
e513c0f004 Fix AttributeError when uploading OTA to offline OpenThread devices (#10459) 2025-08-29 10:16:19 +12:00
Clyde Stubbs
a11970aee0 [wifi] Fix retry with hidden networks. (#10445) 2025-08-29 10:16:19 +12:00
Clyde Stubbs
4ab37b069b [i2c] Perform register reads as single transactions (#10389)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-08-29 10:16:08 +12:00
Clyde Stubbs
b6bb6699d1 [mipi_spi] Fix dimensions (#10443) 2025-08-29 10:15:30 +12:00
J. Nick Koston
078eaff9a8 [wifi] Fix reconnection failures after adapter restart by not clearing netif pointers (#10458) 2025-08-29 10:15:30 +12:00
J. Nick Koston
a7786b75a0 [esp32_ble_tracker] Remove duplicate client promotion logic (#10321) 2025-08-29 10:14:51 +12:00
J. Nick Koston
d4c11dac8c [esphome] Fix OTA watchdog resets by validating all magic bytes before blocking (#10401) 2025-08-29 10:12:38 +12:00
DAVe3283
2f2f2f7d15 [absolute_humidity] Fix typo (#10474) 2025-08-29 10:04:19 +12:00
355 changed files with 8067 additions and 2572 deletions

View File

@@ -17,7 +17,7 @@ runs:
steps:
- name: Set up Python ${{ inputs.python-version }}
id: python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
with:
python-version: ${{ inputs.python-version }}
- name: Restore Python virtual environment

View File

@@ -32,7 +32,7 @@ jobs:
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
- name: Auto Label PR
uses: actions/github-script@v7.0.1
uses: actions/github-script@v8.0.0
with:
github-token: ${{ steps.generate-token.outputs.token }}
script: |

View File

@@ -23,7 +23,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v5.0.0
- name: Set up Python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
with:
python-version: "3.11"
@@ -47,7 +47,7 @@ jobs:
fi
- if: failure()
name: Review PR
uses: actions/github-script@v7.0.1
uses: actions/github-script@v8.0.0
with:
script: |
await github.rest.pulls.createReview({
@@ -70,7 +70,7 @@ jobs:
esphome/components/api/api_pb2_service.*
- if: success()
name: Dismiss review
uses: actions/github-script@v7.0.1
uses: actions/github-script@v8.0.0
with:
script: |
let reviews = await github.rest.pulls.listReviews({

View File

@@ -23,7 +23,7 @@ jobs:
uses: actions/checkout@v5.0.0
- name: Set up Python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
with:
python-version: "3.11"
@@ -41,7 +41,7 @@ jobs:
- if: failure()
name: Request changes
uses: actions/github-script@v7.0.1
uses: actions/github-script@v8.0.0
with:
script: |
await github.rest.pulls.createReview({
@@ -54,7 +54,7 @@ jobs:
- if: success()
name: Dismiss review
uses: actions/github-script@v7.0.1
uses: actions/github-script@v8.0.0
with:
script: |
let reviews = await github.rest.pulls.listReviews({

View File

@@ -45,7 +45,7 @@ jobs:
steps:
- uses: actions/checkout@v5.0.0
- name: Set up Python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
with:
python-version: "3.11"
- name: Set up Docker Buildx

View File

@@ -42,7 +42,7 @@ jobs:
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment
@@ -156,7 +156,7 @@ jobs:
. venv/bin/activate
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5.5.0
uses: codecov/codecov-action@v5.5.1
with:
token: ${{ secrets.CODECOV_TOKEN }}
- name: Save Python virtual environment cache
@@ -217,7 +217,7 @@ jobs:
uses: actions/checkout@v5.0.0
- name: Set up Python 3.13
id: python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
with:
python-version: "3.13"
- name: Restore Python virtual environment

View File

@@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Request reviews from component codeowners
uses: actions/github-script@v7.0.1
uses: actions/github-script@v8.0.0
with:
script: |
const owner = context.repo.owner;

View File

@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Add external component comment
uses: actions/github-script@v7.0.1
uses: actions/github-script@v8.0.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |

View File

@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Notify codeowners for component issues
uses: actions/github-script@v7.0.1
uses: actions/github-script@v8.0.0
with:
script: |
const owner = context.repo.owner;

View File

@@ -62,7 +62,7 @@ jobs:
steps:
- uses: actions/checkout@v5.0.0
- name: Set up Python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
with:
python-version: "3.x"
- name: Build
@@ -70,7 +70,7 @@ jobs:
pip3 install build
python3 -m build
- name: Publish
uses: pypa/gh-action-pypi-publish@v1.12.4
uses: pypa/gh-action-pypi-publish@v1.13.0
with:
skip-existing: true
@@ -94,7 +94,7 @@ jobs:
steps:
- uses: actions/checkout@v5.0.0
- name: Set up Python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
with:
python-version: "3.11"
@@ -220,7 +220,7 @@ jobs:
- deploy-manifest
steps:
- name: Trigger Workflow
uses: actions/github-script@v7.0.1
uses: actions/github-script@v8.0.0
with:
github-token: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
script: |
@@ -246,7 +246,7 @@ jobs:
environment: ${{ needs.init.outputs.deploy_env }}
steps:
- name: Trigger Workflow
uses: actions/github-script@v7.0.1
uses: actions/github-script@v8.0.0
with:
github-token: ${{ secrets.DEPLOY_ESPHOME_SCHEMA_REPO_TOKEN }}
script: |

View File

@@ -17,7 +17,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9.1.0
- uses: actions/stale@v10.0.0
with:
days-before-pr-stale: 90
days-before-pr-close: 7
@@ -37,7 +37,7 @@ jobs:
close-issues:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9.1.0
- uses: actions/stale@v10.0.0
with:
days-before-pr-stale: -1
days-before-pr-close: -1

View File

@@ -16,7 +16,7 @@ jobs:
- merge-after-release
steps:
- name: Check for ${{ matrix.label }} label
uses: actions/github-script@v7.0.1
uses: actions/github-script@v8.0.0
with:
script: |
const { data: labels } = await github.rest.issues.listLabelsOnIssue({

View File

@@ -22,7 +22,7 @@ jobs:
path: lib/home-assistant
- name: Setup Python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
with:
python-version: 3.13

View File

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

View File

@@ -66,7 +66,7 @@ esphome/components/binary_sensor/* @esphome/core
esphome/components/bk72xx/* @kuba2k2
esphome/components/bl0906/* @athom-tech @jesserockz @tarontop
esphome/components/bl0939/* @ziceva
esphome/components/bl0940/* @tobias-
esphome/components/bl0940/* @dan-s-github @tobias-
esphome/components/bl0942/* @dbuezas @dwmw2
esphome/components/ble_client/* @buxtronix @clydebarrow
esphome/components/bluetooth_proxy/* @bdraco @jesserockz
@@ -88,7 +88,8 @@ esphome/components/bp1658cj/* @Cossid
esphome/components/bp5758d/* @Cossid
esphome/components/button/* @esphome/core
esphome/components/bytebuffer/* @clydebarrow
esphome/components/camera/* @DT-art1 @bdraco
esphome/components/camera/* @bdraco @DT-art1
esphome/components/camera_encoder/* @DT-art1
esphome/components/canbus/* @danielschramm @mvturnho
esphome/components/cap1188/* @mreditor97
esphome/components/captive_portal/* @esphome/core
@@ -144,9 +145,9 @@ esphome/components/es8156/* @kbx81
esphome/components/es8311/* @kahrendt @kroimon
esphome/components/es8388/* @P4uLT
esphome/components/esp32/* @esphome/core
esphome/components/esp32_ble/* @Rapsssito @bdraco @jesserockz
esphome/components/esp32_ble/* @bdraco @jesserockz @Rapsssito
esphome/components/esp32_ble_client/* @bdraco @jesserockz
esphome/components/esp32_ble_server/* @Rapsssito @clydebarrow @jesserockz
esphome/components/esp32_ble_server/* @clydebarrow @jesserockz @Rapsssito
esphome/components/esp32_ble_tracker/* @bdraco
esphome/components/esp32_camera_web_server/* @ayufan
esphome/components/esp32_can/* @Sympatron
@@ -166,7 +167,7 @@ esphome/components/ezo_pmp/* @carlos-sarmiento
esphome/components/factory_reset/* @anatoly-savchenkov
esphome/components/fastled_base/* @OttoWinter
esphome/components/feedback/* @ianchi
esphome/components/fingerprint_grow/* @OnFreund @alexborro @loongyh
esphome/components/fingerprint_grow/* @alexborro @loongyh @OnFreund
esphome/components/font/* @clydebarrow @esphome/core
esphome/components/fs3000/* @kahrendt
esphome/components/ft5x06/* @clydebarrow
@@ -202,7 +203,7 @@ esphome/components/heatpumpir/* @rob-deutsch
esphome/components/hitachi_ac424/* @sourabhjaiswal
esphome/components/hm3301/* @freekode
esphome/components/hmac_md5/* @dwmw2
esphome/components/homeassistant/* @OttoWinter @esphome/core
esphome/components/homeassistant/* @esphome/core @OttoWinter
esphome/components/homeassistant/number/* @landonr
esphome/components/homeassistant/switch/* @Links2004
esphome/components/honeywell_hih_i2c/* @Benichou34
@@ -227,13 +228,13 @@ esphome/components/iaqcore/* @yozik04
esphome/components/ili9xxx/* @clydebarrow @nielsnl68
esphome/components/improv_base/* @esphome/core
esphome/components/improv_serial/* @esphome/core
esphome/components/ina226/* @Sergio303 @latonita
esphome/components/ina226/* @latonita @Sergio303
esphome/components/ina260/* @mreditor97
esphome/components/ina2xx_base/* @latonita
esphome/components/ina2xx_i2c/* @latonita
esphome/components/ina2xx_spi/* @latonita
esphome/components/inkbird_ibsth1_mini/* @fkirill
esphome/components/inkplate6/* @jesserockz
esphome/components/inkplate/* @jesserockz @JosipKuci
esphome/components/integration/* @OttoWinter
esphome/components/internal_temperature/* @Mat931
esphome/components/interval/* @esphome/core
@@ -276,8 +277,8 @@ esphome/components/max7219digit/* @rspaargaren
esphome/components/max9611/* @mckaymatthew
esphome/components/mcp23008/* @jesserockz
esphome/components/mcp23017/* @jesserockz
esphome/components/mcp23s08/* @SenexCrenshaw @jesserockz
esphome/components/mcp23s17/* @SenexCrenshaw @jesserockz
esphome/components/mcp23s08/* @jesserockz @SenexCrenshaw
esphome/components/mcp23s17/* @jesserockz @SenexCrenshaw
esphome/components/mcp23x08_base/* @jesserockz
esphome/components/mcp23x17_base/* @jesserockz
esphome/components/mcp23xxx_base/* @jesserockz
@@ -298,6 +299,7 @@ esphome/components/mics_4514/* @jesserockz
esphome/components/midea/* @dudanov
esphome/components/midea_ir/* @dudanov
esphome/components/mipi_dsi/* @clydebarrow
esphome/components/mipi_rgb/* @clydebarrow
esphome/components/mipi_spi/* @clydebarrow
esphome/components/mitsubishi/* @RubyBailey
esphome/components/mixer/speaker/* @kahrendt
@@ -341,7 +343,7 @@ esphome/components/ota/* @esphome/core
esphome/components/output/* @esphome/core
esphome/components/packet_transport/* @clydebarrow
esphome/components/pca6416a/* @Mat931
esphome/components/pca9554/* @clydebarrow @hwstar
esphome/components/pca9554/* @bdraco @clydebarrow @hwstar
esphome/components/pcf85063/* @brogon
esphome/components/pcf8563/* @KoenBreeman
esphome/components/pi4ioe5v6408/* @jesserockz
@@ -352,9 +354,9 @@ esphome/components/pm2005/* @andrewjswan
esphome/components/pmsa003i/* @sjtrny
esphome/components/pmsx003/* @ximex
esphome/components/pmwcs3/* @SeByDocKy
esphome/components/pn532/* @OttoWinter @jesserockz
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
esphome/components/pn532_spi/* @OttoWinter @jesserockz
esphome/components/pn532/* @jesserockz @OttoWinter
esphome/components/pn532_i2c/* @jesserockz @OttoWinter
esphome/components/pn532_spi/* @jesserockz @OttoWinter
esphome/components/pn7150/* @jesserockz @kbx81
esphome/components/pn7150_i2c/* @jesserockz @kbx81
esphome/components/pn7160/* @jesserockz @kbx81
@@ -363,7 +365,7 @@ esphome/components/pn7160_spi/* @jesserockz @kbx81
esphome/components/power_supply/* @esphome/core
esphome/components/preferences/* @esphome/core
esphome/components/psram/* @esphome/core
esphome/components/pulse_meter/* @TrentHouliston @cstaahl @stevebaxter
esphome/components/pulse_meter/* @cstaahl @stevebaxter @TrentHouliston
esphome/components/pvvx_mithermometer/* @pasiz
esphome/components/pylontech/* @functionpointer
esphome/components/qmp6988/* @andrewpc
@@ -404,7 +406,7 @@ esphome/components/sensirion_common/* @martgras
esphome/components/sensor/* @esphome/core
esphome/components/sfa30/* @ghsensdev
esphome/components/sgp40/* @SenexCrenshaw
esphome/components/sgp4x/* @SenexCrenshaw @martgras
esphome/components/sgp4x/* @martgras @SenexCrenshaw
esphome/components/shelly_dimmer/* @edge90 @rnauber
esphome/components/sht3xd/* @mrtoy-me
esphome/components/sht4x/* @sjtrny

View File

@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = 2025.9.0-dev
PROJECT_NUMBER = 2025.9.0b1
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a

View File

@@ -396,27 +396,29 @@ def check_permissions(port: str):
)
def upload_program(config: ConfigType, args: ArgsProtocol, host: str) -> int | str:
def upload_program(
config: ConfigType, args: ArgsProtocol, devices: list[str]
) -> tuple[int, str | None]:
host = devices[0]
try:
module = importlib.import_module("esphome.components." + CORE.target_platform)
if getattr(module, "upload_program")(config, args, host):
return 0
return 0, host
except AttributeError:
pass
if get_port_type(host) == "SERIAL":
check_permissions(host)
exit_code = 1
if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266):
file = getattr(args, "file", None)
return upload_using_esptool(config, host, file, args.upload_speed)
exit_code = upload_using_esptool(config, host, file, args.upload_speed)
elif CORE.target_platform == PLATFORM_RP2040 or CORE.is_libretiny:
exit_code = upload_using_platformio(config, host)
# else: Unknown target platform, exit_code remains 1
if CORE.target_platform in (PLATFORM_RP2040):
return upload_using_platformio(config, host)
if CORE.is_libretiny:
return upload_using_platformio(config, host)
return 1 # Unknown target platform
return exit_code, host if exit_code == 0 else None
ota_conf = {}
for ota_item in config.get(CONF_OTA, []):
@@ -433,10 +435,10 @@ def upload_program(config: ConfigType, args: ArgsProtocol, host: str) -> int | s
remote_port = int(ota_conf[CONF_PORT])
password = ota_conf.get(CONF_PASSWORD, "")
binary = args.file if getattr(args, "file", None) is not None else CORE.firmware_bin
# Check if we should use MQTT for address resolution
# This happens when no device was specified, or the current host is "MQTT"/"OTA"
devices: list[str] = args.device or []
if (
CONF_MQTT in config # pylint: disable=too-many-boolean-expressions
and (not devices or host in ("MQTT", "OTA"))
@@ -447,17 +449,23 @@ def upload_program(config: ConfigType, args: ArgsProtocol, host: str) -> int | s
):
from esphome import mqtt
host = mqtt.get_esphome_device_ip(
config, args.username, args.password, args.client_id
)
devices = [
mqtt.get_esphome_device_ip(
config, args.username, args.password, args.client_id
)
]
if getattr(args, "file", None) is not None:
return espota2.run_ota(host, remote_port, password, args.file)
return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
return espota2.run_ota(devices, remote_port, password, binary)
def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int | None:
try:
module = importlib.import_module("esphome.components." + CORE.target_platform)
if getattr(module, "show_logs")(config, args, devices):
return 0
except AttributeError:
pass
if "logger" not in config:
raise EsphomeError("Logger is not configured!")
@@ -551,17 +559,11 @@ def command_upload(args: ArgsProtocol, config: ConfigType) -> int | None:
purpose="uploading",
)
# Try each device until one succeeds
exit_code = 1
for device in devices:
_LOGGER.info("Uploading to %s", device)
exit_code = upload_program(config, args, device)
if exit_code == 0:
_LOGGER.info("Successfully uploaded program.")
return 0
if len(devices) > 1:
_LOGGER.warning("Failed to upload to %s", device)
exit_code, _ = upload_program(config, args, devices)
if exit_code == 0:
_LOGGER.info("Successfully uploaded program.")
else:
_LOGGER.warning("Failed to upload to %s", devices)
return exit_code
@@ -614,19 +616,11 @@ def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
purpose="uploading",
)
# Try each device for upload until one succeeds
successful_device: str | None = None
for device in devices:
_LOGGER.info("Uploading to %s", device)
exit_code = upload_program(config, args, device)
if exit_code == 0:
_LOGGER.info("Successfully uploaded program.")
successful_device = device
break
if len(devices) > 1:
_LOGGER.warning("Failed to upload to %s", device)
if successful_device is None:
exit_code, successful_device = upload_program(config, args, devices)
if exit_code == 0:
_LOGGER.info("Successfully uploaded program.")
else:
_LOGGER.warning("Failed to upload to %s", devices)
return exit_code
if args.no_logs:

View File

@@ -61,11 +61,10 @@ void AbsoluteHumidityComponent::loop() {
ESP_LOGW(TAG, "No valid state from temperature sensor!");
}
if (no_humidity) {
ESP_LOGW(TAG, "No valid state from temperature sensor!");
ESP_LOGW(TAG, "No valid state from humidity sensor!");
}
ESP_LOGW(TAG, "Unable to calculate absolute humidity.");
this->publish_state(NAN);
this->status_set_warning();
this->status_set_warning(LOG_STR("Unable to calculate absolute humidity."));
return;
}
@@ -87,9 +86,8 @@ void AbsoluteHumidityComponent::loop() {
es = es_wobus(temperature_c);
break;
default:
ESP_LOGE(TAG, "Invalid saturation vapor pressure equation selection!");
this->publish_state(NAN);
this->status_set_error();
this->status_set_error("Invalid saturation vapor pressure equation selection!");
return;
}
ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es);

View File

@@ -241,6 +241,8 @@ float ADCSensor::sample_autorange_() {
cali_config.bitwidth = ADC_BITWIDTH_DEFAULT;
err = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);
ESP_LOGVV(TAG, "Autorange atten=%d: Calibration handle creation %s (err=%d)", atten,
(err == ESP_OK) ? "SUCCESS" : "FAILED", err);
#else
adc_cali_line_fitting_config_t cali_config = {
.unit_id = this->adc_unit_,
@@ -251,10 +253,14 @@ float ADCSensor::sample_autorange_() {
#endif
};
err = adc_cali_create_scheme_line_fitting(&cali_config, &handle);
ESP_LOGVV(TAG, "Autorange atten=%d: Calibration handle creation %s (err=%d)", atten,
(err == ESP_OK) ? "SUCCESS" : "FAILED", err);
#endif
int raw;
err = adc_oneshot_read(this->adc_handle_, this->channel_, &raw);
ESP_LOGVV(TAG, "Autorange atten=%d: Raw ADC read %s, value=%d (err=%d)", atten,
(err == ESP_OK) ? "SUCCESS" : "FAILED", raw, err);
if (err != ESP_OK) {
ESP_LOGW(TAG, "ADC read failed in autorange with error %d", err);
@@ -275,8 +281,10 @@ float ADCSensor::sample_autorange_() {
err = adc_cali_raw_to_voltage(handle, raw, &voltage_mv);
if (err == ESP_OK) {
voltage = voltage_mv / 1000.0f;
ESP_LOGVV(TAG, "Autorange atten=%d: CALIBRATED - raw=%d -> %dmV -> %.6fV", atten, raw, voltage_mv, voltage);
} else {
voltage = raw * 3.3f / 4095.0f;
ESP_LOGVV(TAG, "Autorange atten=%d: UNCALIBRATED FALLBACK - raw=%d -> %.6fV (3.3V ref)", atten, raw, voltage);
}
// Clean up calibration handle
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
@@ -287,6 +295,7 @@ float ADCSensor::sample_autorange_() {
#endif
} else {
voltage = raw * 3.3f / 4095.0f;
ESP_LOGVV(TAG, "Autorange atten=%d: NO CALIBRATION - raw=%d -> %.6fV (3.3V ref)", atten, raw, voltage);
}
return {raw, voltage};
@@ -324,18 +333,32 @@ float ADCSensor::sample_autorange_() {
}
const int adc_half = 2048;
uint32_t c12 = std::min(raw12, adc_half);
uint32_t c6 = adc_half - std::abs(raw6 - adc_half);
uint32_t c2 = adc_half - std::abs(raw2 - adc_half);
uint32_t c0 = std::min(4095 - raw0, adc_half);
uint32_t csum = c12 + c6 + c2 + c0;
const uint32_t c12 = std::min(raw12, adc_half);
const int32_t c6_signed = adc_half - std::abs(raw6 - adc_half);
const uint32_t c6 = (c6_signed > 0) ? c6_signed : 0; // Clamp to prevent underflow
const int32_t c2_signed = adc_half - std::abs(raw2 - adc_half);
const uint32_t c2 = (c2_signed > 0) ? c2_signed : 0; // Clamp to prevent underflow
const uint32_t c0 = std::min(4095 - raw0, adc_half);
const uint32_t csum = c12 + c6 + c2 + c0;
ESP_LOGVV(TAG, "Autorange summary:");
ESP_LOGVV(TAG, " Raw readings: 12db=%d, 6db=%d, 2.5db=%d, 0db=%d", raw12, raw6, raw2, raw0);
ESP_LOGVV(TAG, " Voltages: 12db=%.6f, 6db=%.6f, 2.5db=%.6f, 0db=%.6f", mv12, mv6, mv2, mv0);
ESP_LOGVV(TAG, " Coefficients: c12=%u, c6=%u, c2=%u, c0=%u, sum=%u", c12, c6, c2, c0, csum);
if (csum == 0) {
ESP_LOGE(TAG, "Invalid weight sum in autorange calculation");
return NAN;
}
return (mv12 * c12 + mv6 * c6 + mv2 * c2 + mv0 * c0) / csum;
const float final_result = (mv12 * c12 + mv6 * c6 + mv2 * c2 + mv0 * c0) / csum;
ESP_LOGV(TAG, "Autorange final: (%.6f*%u + %.6f*%u + %.6f*%u + %.6f*%u)/%u = %.6fV", mv12, c12, mv6, c6, mv2, c2, mv0,
c0, csum, final_result);
return final_result;
}
} // namespace adc

View File

@@ -96,7 +96,7 @@ void AHT10Component::read_data_() {
ESP_LOGD(TAG, "Read attempt %d at %ums", this->read_count_, (unsigned) (millis() - this->start_time_));
}
if (this->read(data, 6) != i2c::ERROR_OK) {
this->status_set_warning("Read failed, will retry");
this->status_set_warning(LOG_STR("Read failed, will retry"));
this->restart_read_();
return;
}
@@ -113,7 +113,7 @@ void AHT10Component::read_data_() {
} else {
ESP_LOGD(TAG, "Invalid humidity, retrying");
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL);
this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
}
this->restart_read_();
return;
@@ -144,7 +144,7 @@ void AHT10Component::update() {
return;
this->start_time_ = millis();
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL);
this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
return;
}
this->restart_read_();

View File

@@ -13,7 +13,7 @@ from esphome.const import (
CONF_TRIGGER_ID,
CONF_WEB_SERVER,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass
@@ -345,6 +345,6 @@ async def alarm_control_panel_is_armed_to_code(
return cg.new_Pvariable(condition_id, template_arg, paren)
@coroutine_with_priority(100.0)
@coroutine_with_priority(CoroPriority.CORE)
async def to_code(config):
cg.add_global(alarm_control_panel_ns.using)

View File

@@ -24,7 +24,7 @@ from esphome.const import (
CONF_TRIGGER_ID,
CONF_VARIABLES,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.core import CORE, CoroPriority, coroutine_with_priority
DOMAIN = "api"
DEPENDENCIES = ["network"]
@@ -134,7 +134,7 @@ CONFIG_SCHEMA = cv.All(
)
@coroutine_with_priority(40.0)
@coroutine_with_priority(CoroPriority.WEB)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)

View File

@@ -818,6 +818,7 @@ message GetTimeResponse {
option (no_delay) = true;
fixed32 epoch_seconds = 1;
string timezone = 2;
}
// ==================== USER-DEFINES SERVICES ====================
@@ -1712,6 +1713,7 @@ message BluetoothScannerStateResponse {
BluetoothScannerState state = 1;
BluetoothScannerMode mode = 2;
BluetoothScannerMode configured_mode = 3;
}
message BluetoothScannerSetModeRequest {

View File

@@ -112,7 +112,7 @@ void APIConnection::start() {
APIError err = this->helper_->init();
if (err != APIError::OK) {
on_fatal_error();
this->log_warning_("Helper init failed", err);
this->log_warning_(LOG_STR("Helper init failed"), err);
return;
}
this->client_info_.peername = helper_->getpeername();
@@ -159,7 +159,7 @@ void APIConnection::loop() {
break;
} else if (err != APIError::OK) {
on_fatal_error();
this->log_warning_("Reading failed", err);
this->log_warning_(LOG_STR("Reading failed"), err);
return;
} else {
this->last_traffic_ = now;
@@ -1070,8 +1070,14 @@ void APIConnection::camera_image(const CameraImageRequest &msg) {
#ifdef USE_HOMEASSISTANT_TIME
void APIConnection::on_get_time_response(const GetTimeResponse &value) {
if (homeassistant::global_homeassistant_time != nullptr)
if (homeassistant::global_homeassistant_time != nullptr) {
homeassistant::global_homeassistant_time->set_epoch_time(value.epoch_seconds);
#ifdef USE_TIME_TIMEZONE
if (!value.timezone.empty() && value.timezone != homeassistant::global_homeassistant_time->get_timezone()) {
homeassistant::global_homeassistant_time->set_timezone(value.timezone);
}
#endif
}
}
#endif
@@ -1565,7 +1571,7 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
return false;
if (err != APIError::OK) {
on_fatal_error();
this->log_warning_("Packet write failed", err);
this->log_warning_(LOG_STR("Packet write failed"), err);
return false;
}
// Do not set last_traffic_ on send
@@ -1752,7 +1758,7 @@ void APIConnection::process_batch_() {
std::span<const PacketInfo>(packet_info, packet_count));
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
on_fatal_error();
this->log_warning_("Batch write failed", err);
this->log_warning_(LOG_STR("Batch write failed"), err);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -1830,11 +1836,14 @@ void APIConnection::process_state_subscriptions_() {
}
#endif // USE_API_HOMEASSISTANT_STATES
void APIConnection::log_warning_(const char *message, APIError err) {
ESP_LOGW(TAG, "%s: %s %s errno=%d", this->get_client_combined_info().c_str(), message, api_error_to_str(err), errno);
void APIConnection::log_warning_(const LogString *message, APIError err) {
ESP_LOGW(TAG, "%s: %s %s errno=%d", this->get_client_combined_info().c_str(), LOG_STR_ARG(message),
LOG_STR_ARG(api_error_to_logstr(err)), errno);
}
void APIConnection::log_socket_operation_failed_(APIError err) { this->log_warning_("Socket operation failed", err); }
void APIConnection::log_socket_operation_failed_(APIError err) {
this->log_warning_(LOG_STR("Socket operation failed"), err);
}
} // namespace esphome::api
#endif

View File

@@ -732,7 +732,7 @@ class APIConnection final : public APIServerConnection {
}
// Helper function to log API errors with errno
void log_warning_(const char *message, APIError err);
void log_warning_(const LogString *message, APIError err);
// Specific helper for duplicated error message
void log_socket_operation_failed_(APIError err);
};

View File

@@ -23,59 +23,59 @@ static const char *const TAG = "api.frame_helper";
#define LOG_PACKET_SENDING(data, len) ((void) 0)
#endif
const char *api_error_to_str(APIError err) {
const LogString *api_error_to_logstr(APIError err) {
// not using switch to ensure compiler doesn't try to build a big table out of it
if (err == APIError::OK) {
return "OK";
return LOG_STR("OK");
} else if (err == APIError::WOULD_BLOCK) {
return "WOULD_BLOCK";
return LOG_STR("WOULD_BLOCK");
} else if (err == APIError::BAD_INDICATOR) {
return "BAD_INDICATOR";
return LOG_STR("BAD_INDICATOR");
} else if (err == APIError::BAD_DATA_PACKET) {
return "BAD_DATA_PACKET";
return LOG_STR("BAD_DATA_PACKET");
} else if (err == APIError::TCP_NODELAY_FAILED) {
return "TCP_NODELAY_FAILED";
return LOG_STR("TCP_NODELAY_FAILED");
} else if (err == APIError::TCP_NONBLOCKING_FAILED) {
return "TCP_NONBLOCKING_FAILED";
return LOG_STR("TCP_NONBLOCKING_FAILED");
} else if (err == APIError::CLOSE_FAILED) {
return "CLOSE_FAILED";
return LOG_STR("CLOSE_FAILED");
} else if (err == APIError::SHUTDOWN_FAILED) {
return "SHUTDOWN_FAILED";
return LOG_STR("SHUTDOWN_FAILED");
} else if (err == APIError::BAD_STATE) {
return "BAD_STATE";
return LOG_STR("BAD_STATE");
} else if (err == APIError::BAD_ARG) {
return "BAD_ARG";
return LOG_STR("BAD_ARG");
} else if (err == APIError::SOCKET_READ_FAILED) {
return "SOCKET_READ_FAILED";
return LOG_STR("SOCKET_READ_FAILED");
} else if (err == APIError::SOCKET_WRITE_FAILED) {
return "SOCKET_WRITE_FAILED";
return LOG_STR("SOCKET_WRITE_FAILED");
} else if (err == APIError::OUT_OF_MEMORY) {
return "OUT_OF_MEMORY";
return LOG_STR("OUT_OF_MEMORY");
} else if (err == APIError::CONNECTION_CLOSED) {
return "CONNECTION_CLOSED";
return LOG_STR("CONNECTION_CLOSED");
}
#ifdef USE_API_NOISE
else if (err == APIError::BAD_HANDSHAKE_PACKET_LEN) {
return "BAD_HANDSHAKE_PACKET_LEN";
return LOG_STR("BAD_HANDSHAKE_PACKET_LEN");
} else if (err == APIError::HANDSHAKESTATE_READ_FAILED) {
return "HANDSHAKESTATE_READ_FAILED";
return LOG_STR("HANDSHAKESTATE_READ_FAILED");
} else if (err == APIError::HANDSHAKESTATE_WRITE_FAILED) {
return "HANDSHAKESTATE_WRITE_FAILED";
return LOG_STR("HANDSHAKESTATE_WRITE_FAILED");
} else if (err == APIError::HANDSHAKESTATE_BAD_STATE) {
return "HANDSHAKESTATE_BAD_STATE";
return LOG_STR("HANDSHAKESTATE_BAD_STATE");
} else if (err == APIError::CIPHERSTATE_DECRYPT_FAILED) {
return "CIPHERSTATE_DECRYPT_FAILED";
return LOG_STR("CIPHERSTATE_DECRYPT_FAILED");
} else if (err == APIError::CIPHERSTATE_ENCRYPT_FAILED) {
return "CIPHERSTATE_ENCRYPT_FAILED";
return LOG_STR("CIPHERSTATE_ENCRYPT_FAILED");
} else if (err == APIError::HANDSHAKESTATE_SETUP_FAILED) {
return "HANDSHAKESTATE_SETUP_FAILED";
return LOG_STR("HANDSHAKESTATE_SETUP_FAILED");
} else if (err == APIError::HANDSHAKESTATE_SPLIT_FAILED) {
return "HANDSHAKESTATE_SPLIT_FAILED";
return LOG_STR("HANDSHAKESTATE_SPLIT_FAILED");
} else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) {
return "BAD_HANDSHAKE_ERROR_BYTE";
return LOG_STR("BAD_HANDSHAKE_ERROR_BYTE");
}
#endif
return "UNKNOWN";
return LOG_STR("UNKNOWN");
}
// Default implementation for loop - handles sending buffered data

View File

@@ -66,7 +66,7 @@ enum class APIError : uint16_t {
#endif
};
const char *api_error_to_str(APIError err);
const LogString *api_error_to_logstr(APIError err);
class APIFrameHelper {
public:

View File

@@ -10,10 +10,18 @@
#include <cstring>
#include <cinttypes>
#ifdef USE_ESP8266
#include <pgmspace.h>
#endif
namespace esphome::api {
static const char *const TAG = "api.noise";
#ifdef USE_ESP8266
static const char PROLOGUE_INIT[] PROGMEM = "NoiseAPIInit";
#else
static const char *const PROLOGUE_INIT = "NoiseAPIInit";
#endif
static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->client_info_->get_combined_info().c_str(), ##__VA_ARGS__)
@@ -27,42 +35,42 @@ static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
#endif
/// Convert a noise error code to a readable error
std::string noise_err_to_str(int err) {
const LogString *noise_err_to_logstr(int err) {
if (err == NOISE_ERROR_NO_MEMORY)
return "NO_MEMORY";
return LOG_STR("NO_MEMORY");
if (err == NOISE_ERROR_UNKNOWN_ID)
return "UNKNOWN_ID";
return LOG_STR("UNKNOWN_ID");
if (err == NOISE_ERROR_UNKNOWN_NAME)
return "UNKNOWN_NAME";
return LOG_STR("UNKNOWN_NAME");
if (err == NOISE_ERROR_MAC_FAILURE)
return "MAC_FAILURE";
return LOG_STR("MAC_FAILURE");
if (err == NOISE_ERROR_NOT_APPLICABLE)
return "NOT_APPLICABLE";
return LOG_STR("NOT_APPLICABLE");
if (err == NOISE_ERROR_SYSTEM)
return "SYSTEM";
return LOG_STR("SYSTEM");
if (err == NOISE_ERROR_REMOTE_KEY_REQUIRED)
return "REMOTE_KEY_REQUIRED";
return LOG_STR("REMOTE_KEY_REQUIRED");
if (err == NOISE_ERROR_LOCAL_KEY_REQUIRED)
return "LOCAL_KEY_REQUIRED";
return LOG_STR("LOCAL_KEY_REQUIRED");
if (err == NOISE_ERROR_PSK_REQUIRED)
return "PSK_REQUIRED";
return LOG_STR("PSK_REQUIRED");
if (err == NOISE_ERROR_INVALID_LENGTH)
return "INVALID_LENGTH";
return LOG_STR("INVALID_LENGTH");
if (err == NOISE_ERROR_INVALID_PARAM)
return "INVALID_PARAM";
return LOG_STR("INVALID_PARAM");
if (err == NOISE_ERROR_INVALID_STATE)
return "INVALID_STATE";
return LOG_STR("INVALID_STATE");
if (err == NOISE_ERROR_INVALID_NONCE)
return "INVALID_NONCE";
return LOG_STR("INVALID_NONCE");
if (err == NOISE_ERROR_INVALID_PRIVATE_KEY)
return "INVALID_PRIVATE_KEY";
return LOG_STR("INVALID_PRIVATE_KEY");
if (err == NOISE_ERROR_INVALID_PUBLIC_KEY)
return "INVALID_PUBLIC_KEY";
return LOG_STR("INVALID_PUBLIC_KEY");
if (err == NOISE_ERROR_INVALID_FORMAT)
return "INVALID_FORMAT";
return LOG_STR("INVALID_FORMAT");
if (err == NOISE_ERROR_INVALID_SIGNATURE)
return "INVALID_SIGNATURE";
return to_string(err);
return LOG_STR("INVALID_SIGNATURE");
return LOG_STR("UNKNOWN");
}
/// Initialize the frame helper, returns OK if successful.
@@ -75,7 +83,11 @@ APIError APINoiseFrameHelper::init() {
// init prologue
size_t old_size = prologue_.size();
prologue_.resize(old_size + PROLOGUE_INIT_LEN);
#ifdef USE_ESP8266
memcpy_P(prologue_.data() + old_size, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
#else
std::memcpy(prologue_.data() + old_size, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
#endif
state_ = State::CLIENT_HELLO;
return APIError::OK;
@@ -83,18 +95,18 @@ APIError APINoiseFrameHelper::init() {
// Helper for handling handshake frame errors
APIError APINoiseFrameHelper::handle_handshake_frame_error_(APIError aerr) {
if (aerr == APIError::BAD_INDICATOR) {
send_explicit_handshake_reject_("Bad indicator byte");
send_explicit_handshake_reject_(LOG_STR("Bad indicator byte"));
} else if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
send_explicit_handshake_reject_("Bad handshake packet len");
send_explicit_handshake_reject_(LOG_STR("Bad handshake packet len"));
}
return aerr;
}
// Helper for handling noise library errors
APIError APINoiseFrameHelper::handle_noise_error_(int err, const char *func_name, APIError api_err) {
APIError APINoiseFrameHelper::handle_noise_error_(int err, const LogString *func_name, APIError api_err) {
if (err != 0) {
state_ = State::FAILED;
HELPER_LOG("%s failed: %s", func_name, noise_err_to_str(err).c_str());
HELPER_LOG("%s failed: %s", LOG_STR_ARG(func_name), LOG_STR_ARG(noise_err_to_logstr(err)));
return api_err;
}
return APIError::OK;
@@ -279,11 +291,11 @@ APIError APINoiseFrameHelper::state_action_() {
}
if (frame.empty()) {
send_explicit_handshake_reject_("Empty handshake message");
send_explicit_handshake_reject_(LOG_STR("Empty handshake message"));
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
} else if (frame[0] != 0x00) {
HELPER_LOG("Bad handshake error byte: %u", frame[0]);
send_explicit_handshake_reject_("Bad handshake error byte");
send_explicit_handshake_reject_(LOG_STR("Bad handshake error byte"));
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
}
@@ -293,8 +305,10 @@ APIError APINoiseFrameHelper::state_action_() {
err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr);
if (err != 0) {
// Special handling for MAC failure
send_explicit_handshake_reject_(err == NOISE_ERROR_MAC_FAILURE ? "Handshake MAC failure" : "Handshake error");
return handle_noise_error_(err, "noise_handshakestate_read_message", APIError::HANDSHAKESTATE_READ_FAILED);
send_explicit_handshake_reject_(err == NOISE_ERROR_MAC_FAILURE ? LOG_STR("Handshake MAC failure")
: LOG_STR("Handshake error"));
return handle_noise_error_(err, LOG_STR("noise_handshakestate_read_message"),
APIError::HANDSHAKESTATE_READ_FAILED);
}
aerr = check_handshake_finished_();
@@ -307,8 +321,8 @@ APIError APINoiseFrameHelper::state_action_() {
noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1);
err = noise_handshakestate_write_message(handshake_, &mbuf, nullptr);
APIError aerr_write =
handle_noise_error_(err, "noise_handshakestate_write_message", APIError::HANDSHAKESTATE_WRITE_FAILED);
APIError aerr_write = handle_noise_error_(err, LOG_STR("noise_handshakestate_write_message"),
APIError::HANDSHAKESTATE_WRITE_FAILED);
if (aerr_write != APIError::OK)
return aerr_write;
buffer[0] = 0x00; // success
@@ -331,15 +345,31 @@ APIError APINoiseFrameHelper::state_action_() {
}
return APIError::OK;
}
void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &reason) {
void APINoiseFrameHelper::send_explicit_handshake_reject_(const LogString *reason) {
#ifdef USE_STORE_LOG_STR_IN_FLASH
// On ESP8266 with flash strings, we need to use PROGMEM-aware functions
size_t reason_len = strlen_P(reinterpret_cast<PGM_P>(reason));
std::vector<uint8_t> data;
data.resize(reason.length() + 1);
data.resize(reason_len + 1);
data[0] = 0x01; // failure
// Copy error message from PROGMEM
if (reason_len > 0) {
memcpy_P(data.data() + 1, reinterpret_cast<PGM_P>(reason), reason_len);
}
#else
// Normal memory access
const char *reason_str = LOG_STR_ARG(reason);
size_t reason_len = strlen(reason_str);
std::vector<uint8_t> data;
data.resize(reason_len + 1);
data[0] = 0x01; // failure
// Copy error message in bulk
if (!reason.empty()) {
std::memcpy(data.data() + 1, reason.c_str(), reason.length());
if (reason_len > 0) {
std::memcpy(data.data() + 1, reason_str, reason_len);
}
#endif
// temporarily remove failed state
auto orig_state = state_;
@@ -368,7 +398,8 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
noise_buffer_init(mbuf);
noise_buffer_set_inout(mbuf, frame.data(), frame.size(), frame.size());
err = noise_cipherstate_decrypt(recv_cipher_, &mbuf);
APIError decrypt_err = handle_noise_error_(err, "noise_cipherstate_decrypt", APIError::CIPHERSTATE_DECRYPT_FAILED);
APIError decrypt_err =
handle_noise_error_(err, LOG_STR("noise_cipherstate_decrypt"), APIError::CIPHERSTATE_DECRYPT_FAILED);
if (decrypt_err != APIError::OK)
return decrypt_err;
@@ -450,7 +481,8 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st
4 + packet.payload_size + frame_footer_size_);
int err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
APIError aerr = handle_noise_error_(err, "noise_cipherstate_encrypt", APIError::CIPHERSTATE_ENCRYPT_FAILED);
APIError aerr =
handle_noise_error_(err, LOG_STR("noise_cipherstate_encrypt"), APIError::CIPHERSTATE_ENCRYPT_FAILED);
if (aerr != APIError::OK)
return aerr;
@@ -504,25 +536,27 @@ APIError APINoiseFrameHelper::init_handshake_() {
nid_.modifier_ids[0] = NOISE_MODIFIER_PSK0;
err = noise_handshakestate_new_by_id(&handshake_, &nid_, NOISE_ROLE_RESPONDER);
APIError aerr = handle_noise_error_(err, "noise_handshakestate_new_by_id", APIError::HANDSHAKESTATE_SETUP_FAILED);
APIError aerr =
handle_noise_error_(err, LOG_STR("noise_handshakestate_new_by_id"), APIError::HANDSHAKESTATE_SETUP_FAILED);
if (aerr != APIError::OK)
return aerr;
const auto &psk = ctx_->get_psk();
err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size());
aerr = handle_noise_error_(err, "noise_handshakestate_set_pre_shared_key", APIError::HANDSHAKESTATE_SETUP_FAILED);
aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_set_pre_shared_key"),
APIError::HANDSHAKESTATE_SETUP_FAILED);
if (aerr != APIError::OK)
return aerr;
err = noise_handshakestate_set_prologue(handshake_, prologue_.data(), prologue_.size());
aerr = handle_noise_error_(err, "noise_handshakestate_set_prologue", APIError::HANDSHAKESTATE_SETUP_FAILED);
aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_set_prologue"), APIError::HANDSHAKESTATE_SETUP_FAILED);
if (aerr != APIError::OK)
return aerr;
// set_prologue copies it into handshakestate, so we can get rid of it now
prologue_ = {};
err = noise_handshakestate_start(handshake_);
aerr = handle_noise_error_(err, "noise_handshakestate_start", APIError::HANDSHAKESTATE_SETUP_FAILED);
aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_start"), APIError::HANDSHAKESTATE_SETUP_FAILED);
if (aerr != APIError::OK)
return aerr;
return APIError::OK;
@@ -540,7 +574,8 @@ APIError APINoiseFrameHelper::check_handshake_finished_() {
return APIError::HANDSHAKESTATE_BAD_STATE;
}
int err = noise_handshakestate_split(handshake_, &send_cipher_, &recv_cipher_);
APIError aerr = handle_noise_error_(err, "noise_handshakestate_split", APIError::HANDSHAKESTATE_SPLIT_FAILED);
APIError aerr =
handle_noise_error_(err, LOG_STR("noise_handshakestate_split"), APIError::HANDSHAKESTATE_SPLIT_FAILED);
if (aerr != APIError::OK)
return aerr;

View File

@@ -32,9 +32,9 @@ class APINoiseFrameHelper final : public APIFrameHelper {
APIError write_frame_(const uint8_t *data, uint16_t len);
APIError init_handshake_();
APIError check_handshake_finished_();
void send_explicit_handshake_reject_(const std::string &reason);
void send_explicit_handshake_reject_(const LogString *reason);
APIError handle_handshake_frame_error_(APIError aerr);
APIError handle_noise_error_(int err, const char *func_name, APIError api_err);
APIError handle_noise_error_(int err, const LogString *func_name, APIError api_err);
// Pointers first (4 bytes each)
NoiseHandshakeState *handshake_{nullptr};

View File

@@ -10,6 +10,10 @@
#include <cstring>
#include <cinttypes>
#ifdef USE_ESP8266
#include <pgmspace.h>
#endif
namespace esphome::api {
static const char *const TAG = "api.plaintext";
@@ -197,11 +201,20 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
// We must send at least 3 bytes to be read, so we add
// a message after the indicator byte to ensures its long
// enough and can aid in debugging.
const char msg[] = "\x00"
"Bad indicator byte";
static constexpr uint8_t INDICATOR_MSG_SIZE = 19;
#ifdef USE_ESP8266
static const char MSG_PROGMEM[] PROGMEM = "\x00"
"Bad indicator byte";
char msg[INDICATOR_MSG_SIZE];
memcpy_P(msg, MSG_PROGMEM, INDICATOR_MSG_SIZE);
iov[0].iov_base = (void *) msg;
iov[0].iov_len = 19;
this->write_raw_(iov, 1, 19);
#else
static const char MSG[] = "\x00"
"Bad indicator byte";
iov[0].iov_base = (void *) MSG;
#endif
iov[0].iov_len = INDICATOR_MSG_SIZE;
this->write_raw_(iov, 1, INDICATOR_MSG_SIZE);
}
return aerr;
}

View File

@@ -901,6 +901,16 @@ bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDel
return true;
}
#endif
bool GetTimeResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2:
this->timezone = value.as_string();
break;
default:
return false;
}
return true;
}
bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1:
@@ -911,8 +921,14 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
}
return true;
}
void GetTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->epoch_seconds); }
void GetTimeResponse::calculate_size(ProtoSize &size) const { size.add_fixed32(1, this->epoch_seconds); }
void GetTimeResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->epoch_seconds);
buffer.encode_string(2, this->timezone_ref_);
}
void GetTimeResponse::calculate_size(ProtoSize &size) const {
size.add_fixed32(1, this->epoch_seconds);
size.add_length(1, this->timezone_ref_.size());
}
#ifdef USE_API_SERVICES
void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->name_ref_);
@@ -2153,10 +2169,12 @@ void BluetoothDeviceClearCacheResponse::calculate_size(ProtoSize &size) const {
void BluetoothScannerStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(1, static_cast<uint32_t>(this->state));
buffer.encode_uint32(2, static_cast<uint32_t>(this->mode));
buffer.encode_uint32(3, static_cast<uint32_t>(this->configured_mode));
}
void BluetoothScannerStateResponse::calculate_size(ProtoSize &size) const {
size.add_uint32(1, static_cast<uint32_t>(this->state));
size.add_uint32(1, static_cast<uint32_t>(this->mode));
size.add_uint32(1, static_cast<uint32_t>(this->configured_mode));
}
bool BluetoothScannerSetModeRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {

View File

@@ -1174,11 +1174,14 @@ class GetTimeRequest final : public ProtoMessage {
class GetTimeResponse final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 37;
static constexpr uint8_t ESTIMATED_SIZE = 5;
static constexpr uint8_t ESTIMATED_SIZE = 14;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "get_time_response"; }
#endif
uint32_t epoch_seconds{0};
std::string timezone{};
StringRef timezone_ref_{};
void set_timezone(const StringRef &ref) { this->timezone_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -1187,6 +1190,7 @@ class GetTimeResponse final : public ProtoDecodableMessage {
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
#ifdef USE_API_SERVICES
class ListEntitiesServicesArgument final : public ProtoMessage {
@@ -2214,12 +2218,13 @@ class BluetoothDeviceClearCacheResponse final : public ProtoMessage {
class BluetoothScannerStateResponse final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 126;
static constexpr uint8_t ESTIMATED_SIZE = 4;
static constexpr uint8_t ESTIMATED_SIZE = 6;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "bluetooth_scanner_state_response"; }
#endif
enums::BluetoothScannerState state{};
enums::BluetoothScannerMode mode{};
enums::BluetoothScannerMode configured_mode{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP

View File

@@ -1110,7 +1110,17 @@ void HomeAssistantStateResponse::dump_to(std::string &out) const {
}
#endif
void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeRequest {}"); }
void GetTimeResponse::dump_to(std::string &out) const { dump_field(out, "epoch_seconds", this->epoch_seconds); }
void GetTimeResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "GetTimeResponse");
dump_field(out, "epoch_seconds", this->epoch_seconds);
out.append(" timezone: ");
if (!this->timezone_ref_.empty()) {
out.append("'").append(this->timezone_ref_.c_str()).append("'");
} else {
out.append("'").append(this->timezone).append("'");
}
out.append("\n");
}
#ifdef USE_API_SERVICES
void ListEntitiesServicesArgument::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesServicesArgument");
@@ -1135,7 +1145,7 @@ void ExecuteServiceArgument::dump_to(std::string &out) const {
dump_field(out, "string_", this->string_);
dump_field(out, "int_", this->int_);
for (const auto it : this->bool_array) {
dump_field(out, "bool_array", it, 4);
dump_field(out, "bool_array", static_cast<bool>(it), 4);
}
for (const auto &it : this->int_array) {
dump_field(out, "int_array", it, 4);
@@ -1704,6 +1714,7 @@ void BluetoothScannerStateResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "BluetoothScannerStateResponse");
dump_field(out, "state", static_cast<enums::BluetoothScannerState>(this->state));
dump_field(out, "mode", static_cast<enums::BluetoothScannerMode>(this->mode));
dump_field(out, "configured_mode", static_cast<enums::BluetoothScannerMode>(this->configured_mode));
}
void BluetoothScannerSetModeRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "BluetoothScannerSetModeRequest");

View File

@@ -8,7 +8,7 @@ from esphome.const import (
PLATFORM_LN882X,
PLATFORM_RTL87XX,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.core import CORE, CoroPriority, coroutine_with_priority
CODEOWNERS = ["@esphome/core"]
@@ -27,7 +27,7 @@ CONFIG_SCHEMA = cv.All(
)
@coroutine_with_priority(200.0)
@coroutine_with_priority(CoroPriority.NETWORK_TRANSPORT)
async def to_code(config):
if CORE.is_esp32 or CORE.is_libretiny:
# https://github.com/ESP32Async/AsyncTCP

View File

@@ -16,6 +16,7 @@ from esphome.const import (
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_REACTIVE_POWER,
DEVICE_CLASS_VOLTAGE,
ICON_CURRENT_AC,
ICON_LIGHTBULB,
@@ -78,6 +79,7 @@ CONFIG_SCHEMA = (
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
icon=ICON_LIGHTBULB,
accuracy_decimals=2,
device_class=DEVICE_CLASS_REACTIVE_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(

View File

@@ -17,10 +17,12 @@ from esphome.const import (
CONF_REACTIVE_POWER,
CONF_REVERSE_ACTIVE_ENERGY,
CONF_VOLTAGE,
DEVICE_CLASS_APPARENT_POWER,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_REACTIVE_POWER,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
ENTITY_CATEGORY_DIAGNOSTIC,
@@ -100,13 +102,13 @@ ATM90E32_PHASE_SCHEMA = cv.Schema(
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
icon=ICON_LIGHTBULB,
accuracy_decimals=2,
device_class=DEVICE_CLASS_POWER,
device_class=DEVICE_CLASS_REACTIVE_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_APPARENT_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT_AMPS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_POWER,
device_class=DEVICE_CLASS_APPARENT_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(

View File

@@ -2,7 +2,7 @@ from esphome import automation
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_MIC_GAIN
from esphome.core import coroutine_with_priority
from esphome.core import CoroPriority, coroutine_with_priority
CODEOWNERS = ["@kbx81"]
IS_PLATFORM_COMPONENT = True
@@ -35,7 +35,7 @@ async def audio_adc_set_mic_gain_to_code(config, action_id, template_arg, args):
return var
@coroutine_with_priority(100.0)
@coroutine_with_priority(CoroPriority.CORE)
async def to_code(config):
cg.add_define("USE_AUDIO_ADC")
cg.add_global(audio_adc_ns.using)

View File

@@ -3,7 +3,7 @@ from esphome.automation import maybe_simple_id
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_VOLUME
from esphome.core import coroutine_with_priority
from esphome.core import CoroPriority, coroutine_with_priority
CODEOWNERS = ["@kbx81"]
IS_PLATFORM_COMPONENT = True
@@ -51,7 +51,7 @@ async def audio_dac_set_volume_to_code(config, action_id, template_arg, args):
return var
@coroutine_with_priority(100.0)
@coroutine_with_priority(CoroPriority.CORE)
async def to_code(config):
cg.add_define("USE_AUDIO_DAC")
cg.add_global(audio_dac_ns.using)

View File

@@ -12,7 +12,7 @@ constexpr static const uint8_t AXS_READ_TOUCHPAD[11] = {0xb5, 0xab, 0xa5, 0x5a,
#define ERROR_CHECK(err) \
if ((err) != i2c::ERROR_OK) { \
this->status_set_warning("Failed to communicate"); \
this->status_set_warning(LOG_STR("Failed to communicate")); \
return; \
}

View File

@@ -493,7 +493,7 @@ void BedJetHub::dump_config() {
" ble_client.app_id: %d\n"
" ble_client.conn_id: %d",
this->get_name().c_str(), this->parent()->app_id, this->parent()->get_conn_id());
LOG_UPDATE_INTERVAL(this)
LOG_UPDATE_INTERVAL(this);
ESP_LOGCONFIG(TAG, " Child components (%d):", this->children_.size());
for (auto *child : this->children_) {
ESP_LOGCONFIG(TAG, " - %s", child->describe().c_str());

View File

@@ -59,7 +59,7 @@ from esphome.const import (
DEVICE_CLASS_VIBRATION,
DEVICE_CLASS_WINDOW,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass
from esphome.util import Registry
@@ -652,7 +652,7 @@ async def binary_sensor_is_off_to_code(config, condition_id, template_arg, args)
return cg.new_Pvariable(condition_id, template_arg, paren, False)
@coroutine_with_priority(100.0)
@coroutine_with_priority(CoroPriority.CORE)
async def to_code(config):
cg.add_global(binary_sensor_ns.using)

View File

@@ -15,8 +15,8 @@ void log_binary_sensor(const char *tag, const char *prefix, const char *type, Bi
ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());
if (!obj->get_device_class().empty()) {
ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->get_device_class().c_str());
if (!obj->get_device_class_ref().empty()) {
ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->get_device_class_ref().c_str());
}
}

View File

@@ -1 +1,6 @@
CODEOWNERS = ["@tobias-"]
import esphome.codegen as cg
CODEOWNERS = ["@tobias-", "@dan-s-github"]
CONF_BL0940_ID = "bl0940_id"
bl0940_ns = cg.esphome_ns.namespace("bl0940")

View File

@@ -7,28 +7,26 @@ namespace bl0940 {
static const char *const TAG = "bl0940";
static const uint8_t BL0940_READ_COMMAND = 0x50; // 0x58 according to documentation
static const uint8_t BL0940_FULL_PACKET = 0xAA;
static const uint8_t BL0940_PACKET_HEADER = 0x55; // 0x58 according to documentation
static const uint8_t BL0940_PACKET_HEADER = 0x55; // 0x58 according to en doc but 0x55 in cn doc
static const uint8_t BL0940_WRITE_COMMAND = 0xA0; // 0xA8 according to documentation
static const uint8_t BL0940_REG_I_FAST_RMS_CTRL = 0x10;
static const uint8_t BL0940_REG_MODE = 0x18;
static const uint8_t BL0940_REG_SOFT_RESET = 0x19;
static const uint8_t BL0940_REG_USR_WRPROT = 0x1A;
static const uint8_t BL0940_REG_TPS_CTRL = 0x1B;
const uint8_t BL0940_INIT[5][6] = {
static const uint8_t BL0940_INIT[5][5] = {
// Reset to default
{BL0940_WRITE_COMMAND, BL0940_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38},
{BL0940_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38},
// Enable User Operation Write
{BL0940_WRITE_COMMAND, BL0940_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0},
{BL0940_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0},
// 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS
{BL0940_WRITE_COMMAND, BL0940_REG_MODE, 0x00, 0x10, 0x00, 0x37},
{BL0940_REG_MODE, 0x00, 0x10, 0x00, 0x37},
// 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS
{BL0940_WRITE_COMMAND, BL0940_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE},
{BL0940_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE},
// 0x181C = Half cycle, Fast RMS threshold 6172
{BL0940_WRITE_COMMAND, BL0940_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}};
{BL0940_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}};
void BL0940::loop() {
DataPacket buffer;
@@ -36,8 +34,8 @@ void BL0940::loop() {
return;
}
if (read_array((uint8_t *) &buffer, sizeof(buffer))) {
if (validate_checksum(&buffer)) {
received_package_(&buffer);
if (this->validate_checksum_(&buffer)) {
this->received_package_(&buffer);
}
} else {
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message");
@@ -46,35 +44,151 @@ void BL0940::loop() {
}
}
bool BL0940::validate_checksum(const DataPacket *data) {
uint8_t checksum = BL0940_READ_COMMAND;
bool BL0940::validate_checksum_(DataPacket *data) {
uint8_t checksum = this->read_command_;
// Whole package but checksum
for (uint32_t i = 0; i < sizeof(data->raw) - 1; i++) {
checksum += data->raw[i];
uint8_t *raw = (uint8_t *) data;
for (uint32_t i = 0; i < sizeof(*data) - 1; i++) {
checksum += raw[i];
}
checksum ^= 0xFF;
if (checksum != data->checksum) {
ESP_LOGW(TAG, "BL0940 invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum);
ESP_LOGW(TAG, "Invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum);
}
return checksum == data->checksum;
}
void BL0940::update() {
this->flush();
this->write_byte(BL0940_READ_COMMAND);
this->write_byte(this->read_command_);
this->write_byte(BL0940_FULL_PACKET);
}
void BL0940::setup() {
#ifdef USE_NUMBER
// add calibration callbacks
if (this->voltage_calibration_number_ != nullptr) {
this->voltage_calibration_number_->add_on_state_callback(
[this](float state) { this->voltage_calibration_callback_(state); });
if (this->voltage_calibration_number_->has_state()) {
this->voltage_calibration_callback_(this->voltage_calibration_number_->state);
}
}
if (this->current_calibration_number_ != nullptr) {
this->current_calibration_number_->add_on_state_callback(
[this](float state) { this->current_calibration_callback_(state); });
if (this->current_calibration_number_->has_state()) {
this->current_calibration_callback_(this->current_calibration_number_->state);
}
}
if (this->power_calibration_number_ != nullptr) {
this->power_calibration_number_->add_on_state_callback(
[this](float state) { this->power_calibration_callback_(state); });
if (this->power_calibration_number_->has_state()) {
this->power_calibration_callback_(this->power_calibration_number_->state);
}
}
if (this->energy_calibration_number_ != nullptr) {
this->energy_calibration_number_->add_on_state_callback(
[this](float state) { this->energy_calibration_callback_(state); });
if (this->energy_calibration_number_->has_state()) {
this->energy_calibration_callback_(this->energy_calibration_number_->state);
}
}
#endif
// calculate calibrated reference values
this->voltage_reference_cal_ = this->voltage_reference_ / this->voltage_cal_;
this->current_reference_cal_ = this->current_reference_ / this->current_cal_;
this->power_reference_cal_ = this->power_reference_ / this->power_cal_;
this->energy_reference_cal_ = this->energy_reference_ / this->energy_cal_;
for (auto *i : BL0940_INIT) {
this->write_array(i, 6);
this->write_byte(this->write_command_), this->write_array(i, 5);
delay(1);
}
this->flush();
}
float BL0940::update_temp_(sensor::Sensor *sensor, ube16_t temperature) const {
auto tb = (float) (temperature.h << 8 | temperature.l);
float BL0940::calculate_power_reference_() {
// calculate power reference based on voltage and current reference
return this->voltage_reference_cal_ * this->current_reference_cal_ * 4046 / 324004 / 79931;
}
float BL0940::calculate_energy_reference_() {
// formula: 3600000 * 4046 * RL * R1 * 1000 / (1638.4 * 256) / Vref² / (R1 + R2)
// or: power_reference_ * 3600000 / (1638.4 * 256)
return this->power_reference_cal_ * 3600000 / (1638.4 * 256);
}
float BL0940::calculate_calibration_value_(float state) { return (100 + state) / 100; }
void BL0940::reset_calibration() {
#ifdef USE_NUMBER
if (this->current_calibration_number_ != nullptr && this->current_cal_ != 1) {
this->current_calibration_number_->make_call().set_value(0).perform();
}
if (this->voltage_calibration_number_ != nullptr && this->voltage_cal_ != 1) {
this->voltage_calibration_number_->make_call().set_value(0).perform();
}
if (this->power_calibration_number_ != nullptr && this->power_cal_ != 1) {
this->power_calibration_number_->make_call().set_value(0).perform();
}
if (this->energy_calibration_number_ != nullptr && this->energy_cal_ != 1) {
this->energy_calibration_number_->make_call().set_value(0).perform();
}
#endif
ESP_LOGD(TAG, "external calibration values restored to initial state");
}
void BL0940::current_calibration_callback_(float state) {
this->current_cal_ = this->calculate_calibration_value_(state);
ESP_LOGV(TAG, "update current calibration state: %f", this->current_cal_);
this->recalibrate_();
}
void BL0940::voltage_calibration_callback_(float state) {
this->voltage_cal_ = this->calculate_calibration_value_(state);
ESP_LOGV(TAG, "update voltage calibration state: %f", this->voltage_cal_);
this->recalibrate_();
}
void BL0940::power_calibration_callback_(float state) {
this->power_cal_ = this->calculate_calibration_value_(state);
ESP_LOGV(TAG, "update power calibration state: %f", this->power_cal_);
this->recalibrate_();
}
void BL0940::energy_calibration_callback_(float state) {
this->energy_cal_ = this->calculate_calibration_value_(state);
ESP_LOGV(TAG, "update energy calibration state: %f", this->energy_cal_);
this->recalibrate_();
}
void BL0940::recalibrate_() {
ESP_LOGV(TAG, "Recalibrating reference values");
this->voltage_reference_cal_ = this->voltage_reference_ / this->voltage_cal_;
this->current_reference_cal_ = this->current_reference_ / this->current_cal_;
if (this->voltage_cal_ != 1 || this->current_cal_ != 1) {
this->power_reference_ = this->calculate_power_reference_();
}
this->power_reference_cal_ = this->power_reference_ / this->power_cal_;
if (this->voltage_cal_ != 1 || this->current_cal_ != 1 || this->power_cal_ != 1) {
this->energy_reference_ = this->calculate_energy_reference_();
}
this->energy_reference_cal_ = this->energy_reference_ / this->energy_cal_;
ESP_LOGD(TAG,
"Recalibrated reference values:\n"
"Voltage: %f\n, Current: %f\n, Power: %f\n, Energy: %f\n",
this->voltage_reference_cal_, this->current_reference_cal_, this->power_reference_cal_,
this->energy_reference_cal_);
}
float BL0940::update_temp_(sensor::Sensor *sensor, uint16_le_t temperature) const {
auto tb = (float) temperature;
float converted_temp = ((float) 170 / 448) * (tb / 2 - 32) - 45;
if (sensor != nullptr) {
if (sensor->has_state() && std::abs(converted_temp - sensor->get_state()) > max_temperature_diff_) {
@@ -87,33 +201,40 @@ float BL0940::update_temp_(sensor::Sensor *sensor, ube16_t temperature) const {
return converted_temp;
}
void BL0940::received_package_(const DataPacket *data) const {
void BL0940::received_package_(DataPacket *data) {
// Bad header
if (data->frame_header != BL0940_PACKET_HEADER) {
ESP_LOGI(TAG, "Invalid data. Header mismatch: %d", data->frame_header);
return;
}
float v_rms = (float) to_uint32_t(data->v_rms) / voltage_reference_;
float i_rms = (float) to_uint32_t(data->i_rms) / current_reference_;
float watt = (float) to_int32_t(data->watt) / power_reference_;
uint32_t cf_cnt = to_uint32_t(data->cf_cnt);
float total_energy_consumption = (float) cf_cnt / energy_reference_;
// cf_cnt is only 24 bits, so track overflows
uint32_t cf_cnt = (uint24_t) data->cf_cnt;
cf_cnt |= this->prev_cf_cnt_ & 0xff000000;
if (cf_cnt < this->prev_cf_cnt_) {
cf_cnt += 0x1000000;
}
this->prev_cf_cnt_ = cf_cnt;
float tps1 = update_temp_(internal_temperature_sensor_, data->tps1);
float tps2 = update_temp_(external_temperature_sensor_, data->tps2);
float v_rms = (uint24_t) data->v_rms / this->voltage_reference_cal_;
float i_rms = (uint24_t) data->i_rms / this->current_reference_cal_;
float watt = (int24_t) data->watt / this->power_reference_cal_;
float total_energy_consumption = cf_cnt / this->energy_reference_cal_;
if (voltage_sensor_ != nullptr) {
voltage_sensor_->publish_state(v_rms);
float tps1 = update_temp_(this->internal_temperature_sensor_, data->tps1);
float tps2 = update_temp_(this->external_temperature_sensor_, data->tps2);
if (this->voltage_sensor_ != nullptr) {
this->voltage_sensor_->publish_state(v_rms);
}
if (current_sensor_ != nullptr) {
current_sensor_->publish_state(i_rms);
if (this->current_sensor_ != nullptr) {
this->current_sensor_->publish_state(i_rms);
}
if (power_sensor_ != nullptr) {
power_sensor_->publish_state(watt);
if (this->power_sensor_ != nullptr) {
this->power_sensor_->publish_state(watt);
}
if (energy_sensor_ != nullptr) {
energy_sensor_->publish_state(total_energy_consumption);
if (this->energy_sensor_ != nullptr) {
this->energy_sensor_->publish_state(total_energy_consumption);
}
ESP_LOGV(TAG, "BL0940: U %fV, I %fA, P %fW, Cnt %" PRId32 ", ∫P %fkWh, T1 %f°C, T2 %f°C", v_rms, i_rms, watt, cf_cnt,
@@ -121,7 +242,27 @@ void BL0940::received_package_(const DataPacket *data) const {
}
void BL0940::dump_config() { // NOLINT(readability-function-cognitive-complexity)
ESP_LOGCONFIG(TAG, "BL0940:");
ESP_LOGCONFIG(TAG,
"BL0940:\n"
" LEGACY MODE: %s\n"
" READ CMD: 0x%02X\n"
" WRITE CMD: 0x%02X\n"
" ------------------\n"
" Current reference: %f\n"
" Energy reference: %f\n"
" Power reference: %f\n"
" Voltage reference: %f\n",
TRUEFALSE(this->legacy_mode_enabled_), this->read_command_, this->write_command_,
this->current_reference_, this->energy_reference_, this->power_reference_, this->voltage_reference_);
#ifdef USE_NUMBER
ESP_LOGCONFIG(TAG,
"BL0940:\n"
" Current calibration: %f\n"
" Energy calibration: %f\n"
" Power calibration: %f\n"
" Voltage calibration: %f\n",
this->current_cal_, this->energy_cal_, this->power_cal_, this->voltage_cal_);
#endif
LOG_SENSOR("", "Voltage", this->voltage_sensor_);
LOG_SENSOR("", "Current", this->current_sensor_);
LOG_SENSOR("", "Power", this->power_sensor_);
@@ -130,9 +271,5 @@ void BL0940::dump_config() { // NOLINT(readability-function-cognitive-complexit
LOG_SENSOR("", "External temperature", this->external_temperature_sensor_);
}
uint32_t BL0940::to_uint32_t(ube24_t input) { return input.h << 16 | input.m << 8 | input.l; }
int32_t BL0940::to_int32_t(sbe24_t input) { return input.h << 16 | input.m << 8 | input.l; }
} // namespace bl0940
} // namespace esphome

View File

@@ -1,66 +1,48 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/uart/uart.h"
#include "esphome/core/datatypes.h"
#include "esphome/core/defines.h"
#ifdef USE_BUTTON
#include "esphome/components/button/button.h"
#endif
#ifdef USE_NUMBER
#include "esphome/components/number/number.h"
#endif
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/uart/uart.h"
namespace esphome {
namespace bl0940 {
static const float BL0940_PREF = 1430;
static const float BL0940_UREF = 33000;
static const float BL0940_IREF = 275000; // 2750 from tasmota. Seems to generate values 100 times too high
// Measured to 297J per click according to power consumption of 5 minutes
// Converted to kWh (3.6MJ per kwH). Used to be 256 * 1638.4
static const float BL0940_EREF = 3.6e6 / 297;
struct ube24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
uint8_t l;
uint8_t m;
uint8_t h;
} __attribute__((packed));
struct ube16_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
uint8_t l;
uint8_t h;
} __attribute__((packed));
struct sbe24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
uint8_t l;
uint8_t m;
int8_t h;
} __attribute__((packed));
// Caveat: All these values are big endian (low - middle - high)
union DataPacket { // NOLINT(altera-struct-pack-align)
uint8_t raw[35];
struct {
uint8_t frame_header; // value of 0x58 according to docs. 0x55 according to Tasmota real world tests. Reality wins.
ube24_t i_fast_rms; // 0x00
ube24_t i_rms; // 0x04
ube24_t RESERVED0; // reserved
ube24_t v_rms; // 0x06
ube24_t RESERVED1; // reserved
sbe24_t watt; // 0x08
ube24_t RESERVED2; // reserved
ube24_t cf_cnt; // 0x0A
ube24_t RESERVED3; // reserved
ube16_t tps1; // 0x0c
uint8_t RESERVED4; // value of 0x00
ube16_t tps2; // 0x0c
uint8_t RESERVED5; // value of 0x00
uint8_t checksum; // checksum
};
struct DataPacket {
uint8_t frame_header; // Packet header (0x58 in EN docs, 0x55 in CN docs and Tasmota tests)
uint24_le_t i_fast_rms; // Fast RMS current
uint24_le_t i_rms; // RMS current
uint24_t RESERVED0; // Reserved
uint24_le_t v_rms; // RMS voltage
uint24_t RESERVED1; // Reserved
int24_le_t watt; // Active power (can be negative for bidirectional measurement)
uint24_t RESERVED2; // Reserved
uint24_le_t cf_cnt; // Energy pulse count
uint24_t RESERVED3; // Reserved
uint16_le_t tps1; // Internal temperature sensor 1
uint8_t RESERVED4; // Reserved (should be 0x00)
uint16_le_t tps2; // Internal temperature sensor 2
uint8_t RESERVED5; // Reserved (should be 0x00)
uint8_t checksum; // Packet checksum
} __attribute__((packed));
class BL0940 : public PollingComponent, public uart::UARTDevice {
public:
// Sensor setters
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; }
// Temperature sensor setters
void set_internal_temperature_sensor(sensor::Sensor *internal_temperature_sensor) {
internal_temperature_sensor_ = internal_temperature_sensor;
}
@@ -68,42 +50,105 @@ class BL0940 : public PollingComponent, public uart::UARTDevice {
external_temperature_sensor_ = external_temperature_sensor;
}
void loop() override;
// Configuration setters
void set_legacy_mode(bool enable) { this->legacy_mode_enabled_ = enable; }
void set_read_command(uint8_t read_command) { this->read_command_ = read_command; }
void set_write_command(uint8_t write_command) { this->write_command_ = write_command; }
// Reference value setters (used for calibration and conversion)
void set_current_reference(float current_ref) { this->current_reference_ = current_ref; }
void set_energy_reference(float energy_ref) { this->energy_reference_ = energy_ref; }
void set_power_reference(float power_ref) { this->power_reference_ = power_ref; }
void set_voltage_reference(float voltage_ref) { this->voltage_reference_ = voltage_ref; }
#ifdef USE_NUMBER
// Calibration number setters (for Home Assistant number entities)
void set_current_calibration_number(number::Number *num) { this->current_calibration_number_ = num; }
void set_voltage_calibration_number(number::Number *num) { this->voltage_calibration_number_ = num; }
void set_power_calibration_number(number::Number *num) { this->power_calibration_number_ = num; }
void set_energy_calibration_number(number::Number *num) { this->energy_calibration_number_ = num; }
#endif
#ifdef USE_BUTTON
// Resets all calibration values to defaults (can be triggered by a button)
void reset_calibration();
#endif
// Core component methods
void loop() override;
void update() override;
void setup() override;
void dump_config() override;
protected:
sensor::Sensor *voltage_sensor_{nullptr};
sensor::Sensor *current_sensor_{nullptr};
// NB This may be negative as the circuits is seemingly able to measure
// power in both directions
sensor::Sensor *power_sensor_{nullptr};
sensor::Sensor *energy_sensor_{nullptr};
sensor::Sensor *internal_temperature_sensor_{nullptr};
sensor::Sensor *external_temperature_sensor_{nullptr};
// --- Sensor pointers ---
sensor::Sensor *voltage_sensor_{nullptr}; // Voltage sensor
sensor::Sensor *current_sensor_{nullptr}; // Current sensor
sensor::Sensor *power_sensor_{nullptr}; // Power sensor (can be negative for bidirectional)
sensor::Sensor *energy_sensor_{nullptr}; // Energy sensor
sensor::Sensor *internal_temperature_sensor_{nullptr}; // Internal temperature sensor
sensor::Sensor *external_temperature_sensor_{nullptr}; // External temperature sensor
// Max difference between two measurements of the temperature. Used to avoid noise.
float max_temperature_diff_{0};
// Divide by this to turn into Watt
float power_reference_ = BL0940_PREF;
// Divide by this to turn into Volt
float voltage_reference_ = BL0940_UREF;
// Divide by this to turn into Ampere
float current_reference_ = BL0940_IREF;
// Divide by this to turn into kWh
float energy_reference_ = BL0940_EREF;
#ifdef USE_NUMBER
// --- Calibration number entities (for dynamic calibration via HA UI) ---
number::Number *voltage_calibration_number_{nullptr};
number::Number *current_calibration_number_{nullptr};
number::Number *power_calibration_number_{nullptr};
number::Number *energy_calibration_number_{nullptr};
#endif
float update_temp_(sensor::Sensor *sensor, ube16_t packed_temperature) const;
// --- Internal state ---
uint32_t prev_cf_cnt_ = 0; // Previous energy pulse count (for energy calculation)
float max_temperature_diff_{0}; // Max allowed temperature difference between two measurements (noise filter)
static uint32_t to_uint32_t(ube24_t input);
// --- Reference values for conversion ---
float power_reference_; // Divider for raw power to get Watts
float power_reference_cal_; // Calibrated power reference
float voltage_reference_; // Divider for raw voltage to get Volts
float voltage_reference_cal_; // Calibrated voltage reference
float current_reference_; // Divider for raw current to get Amperes
float current_reference_cal_; // Calibrated current reference
float energy_reference_; // Divider for raw energy to get kWh
float energy_reference_cal_; // Calibrated energy reference
static int32_t to_int32_t(sbe24_t input);
// --- Home Assistant calibration values (multipliers, default 1) ---
float current_cal_{1};
float voltage_cal_{1};
float power_cal_{1};
float energy_cal_{1};
static bool validate_checksum(const DataPacket *data);
// --- Protocol commands ---
uint8_t read_command_;
uint8_t write_command_;
void received_package_(const DataPacket *data) const;
// --- Mode flags ---
bool legacy_mode_enabled_ = true;
// --- Methods ---
// Converts packed temperature value to float and updates the sensor
float update_temp_(sensor::Sensor *sensor, uint16_le_t packed_temperature) const;
// Validates the checksum of a received data packet
bool validate_checksum_(DataPacket *data);
// Handles a received data packet
void received_package_(DataPacket *data);
// Calculates reference values for calibration and conversion
float calculate_energy_reference_();
float calculate_power_reference_();
float calculate_calibration_value_(float state);
// Calibration update callbacks (used with number entities)
void current_calibration_callback_(float state);
void voltage_calibration_callback_(float state);
void power_calibration_callback_(float state);
void energy_calibration_callback_(float state);
void reset_calibration_callback_();
// Recalculates all reference values after calibration changes
void recalibrate_();
};
} // namespace bl0940
} // namespace esphome

View File

@@ -0,0 +1,27 @@
import esphome.codegen as cg
from esphome.components import button
import esphome.config_validation as cv
from esphome.const import ENTITY_CATEGORY_CONFIG, ICON_RESTART
from .. import CONF_BL0940_ID, bl0940_ns
from ..sensor import BL0940
CalibrationResetButton = bl0940_ns.class_(
"CalibrationResetButton", button.Button, cg.Component
)
CONFIG_SCHEMA = cv.All(
button.button_schema(
CalibrationResetButton,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_RESTART,
)
.extend({cv.GenerateID(CONF_BL0940_ID): cv.use_id(BL0940)})
.extend(cv.COMPONENT_SCHEMA)
)
async def to_code(config):
var = await button.new_button(config)
await cg.register_component(var, config)
await cg.register_parented(var, config[CONF_BL0940_ID])

View File

@@ -0,0 +1,20 @@
#include "calibration_reset_button.h"
#include "../bl0940.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace bl0940 {
static const char *const TAG = "bl0940.button.calibration_reset";
void CalibrationResetButton::dump_config() { LOG_BUTTON("", "Calibration Reset Button", this); }
void CalibrationResetButton::press_action() {
ESP_LOGI(TAG, "Resetting calibration defaults...");
this->parent_->reset_calibration();
}
} // namespace bl0940
} // namespace esphome

View File

@@ -0,0 +1,19 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/button/button.h"
namespace esphome {
namespace bl0940 {
class BL0940; // Forward declaration of BL0940 class
class CalibrationResetButton : public button::Button, public Component, public Parented<BL0940> {
public:
void dump_config() override;
void press_action() override;
};
} // namespace bl0940
} // namespace esphome

View File

@@ -0,0 +1,94 @@
import esphome.codegen as cg
from esphome.components import number
import esphome.config_validation as cv
from esphome.const import (
CONF_MAX_VALUE,
CONF_MIN_VALUE,
CONF_MODE,
CONF_RESTORE_VALUE,
CONF_STEP,
ENTITY_CATEGORY_CONFIG,
UNIT_PERCENT,
)
from .. import CONF_BL0940_ID, bl0940_ns
from ..sensor import BL0940
# Define calibration types
CONF_CURRENT_CALIBRATION = "current_calibration"
CONF_VOLTAGE_CALIBRATION = "voltage_calibration"
CONF_POWER_CALIBRATION = "power_calibration"
CONF_ENERGY_CALIBRATION = "energy_calibration"
BL0940Number = bl0940_ns.class_("BL0940Number")
CalibrationNumber = bl0940_ns.class_(
"CalibrationNumber", number.Number, cg.PollingComponent
)
def validate_min_max(config):
if config[CONF_MAX_VALUE] <= config[CONF_MIN_VALUE]:
raise cv.Invalid("max_value must be greater than min_value")
return config
CALIBRATION_SCHEMA = cv.All(
number.number_schema(
CalibrationNumber,
entity_category=ENTITY_CATEGORY_CONFIG,
unit_of_measurement=UNIT_PERCENT,
)
.extend(
{
cv.Optional(CONF_MODE, default="BOX"): cv.enum(number.NUMBER_MODES),
cv.Optional(CONF_MAX_VALUE, default=10): cv.All(
cv.float_, cv.Range(max=50)
),
cv.Optional(CONF_MIN_VALUE, default=-10): cv.All(
cv.float_, cv.Range(min=-50)
),
cv.Optional(CONF_STEP, default=0.1): cv.positive_float,
cv.Optional(CONF_RESTORE_VALUE): cv.boolean,
}
)
.extend(cv.COMPONENT_SCHEMA),
validate_min_max,
)
# Configuration schema for BL0940 numbers
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(BL0940Number),
cv.GenerateID(CONF_BL0940_ID): cv.use_id(BL0940),
cv.Optional(CONF_CURRENT_CALIBRATION): CALIBRATION_SCHEMA,
cv.Optional(CONF_VOLTAGE_CALIBRATION): CALIBRATION_SCHEMA,
cv.Optional(CONF_POWER_CALIBRATION): CALIBRATION_SCHEMA,
cv.Optional(CONF_ENERGY_CALIBRATION): CALIBRATION_SCHEMA,
}
)
async def to_code(config):
# Get the BL0940 component instance
bl0940 = await cg.get_variable(config[CONF_BL0940_ID])
# Process all calibration types
for cal_type, setter_method in [
(CONF_CURRENT_CALIBRATION, "set_current_calibration_number"),
(CONF_VOLTAGE_CALIBRATION, "set_voltage_calibration_number"),
(CONF_POWER_CALIBRATION, "set_power_calibration_number"),
(CONF_ENERGY_CALIBRATION, "set_energy_calibration_number"),
]:
if conf := config.get(cal_type):
var = await number.new_number(
conf,
min_value=conf.get(CONF_MIN_VALUE),
max_value=conf.get(CONF_MAX_VALUE),
step=conf.get(CONF_STEP),
)
await cg.register_component(var, conf)
if restore_value := config.get(CONF_RESTORE_VALUE):
cg.add(var.set_restore_value(restore_value))
cg.add(getattr(bl0940, setter_method)(var))

View File

@@ -0,0 +1,29 @@
#include "calibration_number.h"
#include "esphome/core/log.h"
namespace esphome {
namespace bl0940 {
static const char *const TAG = "bl0940.number";
void CalibrationNumber::setup() {
float value = 0.0f;
if (this->restore_value_) {
this->pref_ = global_preferences->make_preference<float>(this->get_object_id_hash());
if (!this->pref_.load(&value)) {
value = 0.0f;
}
}
this->publish_state(value);
}
void CalibrationNumber::control(float value) {
this->publish_state(value);
if (this->restore_value_)
this->pref_.save(&value);
}
void CalibrationNumber::dump_config() { LOG_NUMBER("", "Calibration Number", this); }
} // namespace bl0940
} // namespace esphome

View File

@@ -0,0 +1,26 @@
#pragma once
#include "esphome/components/number/number.h"
#include "esphome/core/component.h"
#include "esphome/core/preferences.h"
namespace esphome {
namespace bl0940 {
class CalibrationNumber : public number::Number, public Component {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; }
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
protected:
void control(float value) override;
bool restore_value_{true};
ESPPreferenceObject pref_;
};
} // namespace bl0940
} // namespace esphome

View File

@@ -8,6 +8,7 @@ from esphome.const import (
CONF_ID,
CONF_INTERNAL_TEMPERATURE,
CONF_POWER,
CONF_REFERENCE_VOLTAGE,
CONF_VOLTAGE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY,
@@ -23,12 +24,133 @@ from esphome.const import (
UNIT_WATT,
)
from . import bl0940_ns
DEPENDENCIES = ["uart"]
bl0940_ns = cg.esphome_ns.namespace("bl0940")
BL0940 = bl0940_ns.class_("BL0940", cg.PollingComponent, uart.UARTDevice)
CONF_LEGACY_MODE = "legacy_mode"
CONF_READ_COMMAND = "read_command"
CONF_WRITE_COMMAND = "write_command"
CONF_RESISTOR_SHUNT = "resistor_shunt"
CONF_RESISTOR_ONE = "resistor_one"
CONF_RESISTOR_TWO = "resistor_two"
CONF_CURRENT_REFERENCE = "current_reference"
CONF_ENERGY_REFERENCE = "energy_reference"
CONF_POWER_REFERENCE = "power_reference"
CONF_VOLTAGE_REFERENCE = "voltage_reference"
DEFAULT_BL0940_READ_COMMAND = 0x58
DEFAULT_BL0940_WRITE_COMMAND = 0xA1
# Values according to BL0940 application note:
# https://www.belling.com.cn/media/file_object/bel_product/BL0940/guide/BL0940_APPNote_TSSOP14_V1.04_EN.pdf
DEFAULT_BL0940_VREF = 1.218 # Vref = 1.218
DEFAULT_BL0940_RL = 1 # RL = 1 mΩ
DEFAULT_BL0940_R1 = 0.51 # R1 = 0.51 kΩ
DEFAULT_BL0940_R2 = 1950 # R2 = 5 x 390 kΩ -> 1950 kΩ
# ----------------------------------------------------
# values from initial implementation
DEFAULT_BL0940_LEGACY_READ_COMMAND = 0x50
DEFAULT_BL0940_LEGACY_WRITE_COMMAND = 0xA0
DEFAULT_BL0940_LEGACY_UREF = 33000
DEFAULT_BL0940_LEGACY_IREF = 275000
DEFAULT_BL0940_LEGACY_PREF = 1430
# Measured to 297J per click according to power consumption of 5 minutes
# Converted to kWh (3.6MJ per kwH). Used to be 256 * 1638.4
DEFAULT_BL0940_LEGACY_EREF = 3.6e6 / 297
# ----------------------------------------------------
# methods to calculate voltage and current reference values
def calculate_voltage_reference(vref, r_one, r_two):
# formula: 79931 / Vref * (R1 * 1000) / (R1 + R2)
return 79931 / vref * (r_one * 1000) / (r_one + r_two)
def calculate_current_reference(vref, r_shunt):
# formula: 324004 * RL / Vref
return 324004 * r_shunt / vref
def calculate_power_reference(voltage_reference, current_reference):
# calculate power reference based on voltage and current reference
return voltage_reference * current_reference * 4046 / 324004 / 79931
def calculate_energy_reference(power_reference):
# formula: power_reference * 3600000 / (1638.4 * 256)
return power_reference * 3600000 / (1638.4 * 256)
def validate_legacy_mode(config):
# Only allow schematic calibration options if legacy_mode is False
if config.get(CONF_LEGACY_MODE, True):
forbidden = [
CONF_REFERENCE_VOLTAGE,
CONF_RESISTOR_SHUNT,
CONF_RESISTOR_ONE,
CONF_RESISTOR_TWO,
]
for key in forbidden:
if key in config:
raise cv.Invalid(
f"Option '{key}' is only allowed when legacy_mode: false"
)
return config
def set_command_defaults(config):
# Set defaults for read_command and write_command based on legacy_mode
legacy = config.get(CONF_LEGACY_MODE, True)
if legacy:
config.setdefault(CONF_READ_COMMAND, DEFAULT_BL0940_LEGACY_READ_COMMAND)
config.setdefault(CONF_WRITE_COMMAND, DEFAULT_BL0940_LEGACY_WRITE_COMMAND)
else:
config.setdefault(CONF_READ_COMMAND, DEFAULT_BL0940_READ_COMMAND)
config.setdefault(CONF_WRITE_COMMAND, DEFAULT_BL0940_WRITE_COMMAND)
return config
def set_reference_values(config):
# Set default reference values based on legacy_mode
if config.get(CONF_LEGACY_MODE, True):
config.setdefault(CONF_VOLTAGE_REFERENCE, DEFAULT_BL0940_LEGACY_UREF)
config.setdefault(CONF_CURRENT_REFERENCE, DEFAULT_BL0940_LEGACY_IREF)
config.setdefault(CONF_POWER_REFERENCE, DEFAULT_BL0940_LEGACY_PREF)
config.setdefault(CONF_ENERGY_REFERENCE, DEFAULT_BL0940_LEGACY_PREF)
else:
vref = config.get(CONF_VOLTAGE_REFERENCE, DEFAULT_BL0940_VREF)
r_one = config.get(CONF_RESISTOR_ONE, DEFAULT_BL0940_R1)
r_two = config.get(CONF_RESISTOR_TWO, DEFAULT_BL0940_R2)
r_shunt = config.get(CONF_RESISTOR_SHUNT, DEFAULT_BL0940_RL)
config.setdefault(
CONF_VOLTAGE_REFERENCE, calculate_voltage_reference(vref, r_one, r_two)
)
config.setdefault(
CONF_CURRENT_REFERENCE, calculate_current_reference(vref, r_shunt)
)
config.setdefault(
CONF_POWER_REFERENCE,
calculate_power_reference(
config.get(CONF_VOLTAGE_REFERENCE), config.get(CONF_CURRENT_REFERENCE)
),
)
config.setdefault(
CONF_ENERGY_REFERENCE,
calculate_energy_reference(config.get(CONF_POWER_REFERENCE)),
)
return config
CONFIG_SCHEMA = (
cv.Schema(
{
@@ -69,10 +191,24 @@ CONFIG_SCHEMA = (
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_LEGACY_MODE, default=True): cv.boolean,
cv.Optional(CONF_READ_COMMAND): cv.hex_uint8_t,
cv.Optional(CONF_WRITE_COMMAND): cv.hex_uint8_t,
cv.Optional(CONF_REFERENCE_VOLTAGE): cv.float_,
cv.Optional(CONF_RESISTOR_SHUNT): cv.float_,
cv.Optional(CONF_RESISTOR_ONE): cv.float_,
cv.Optional(CONF_RESISTOR_TWO): cv.float_,
cv.Optional(CONF_CURRENT_REFERENCE): cv.float_,
cv.Optional(CONF_ENERGY_REFERENCE): cv.float_,
cv.Optional(CONF_POWER_REFERENCE): cv.float_,
cv.Optional(CONF_VOLTAGE_REFERENCE): cv.float_,
}
)
.extend(cv.polling_component_schema("60s"))
.extend(uart.UART_DEVICE_SCHEMA)
.add_extra(validate_legacy_mode)
.add_extra(set_command_defaults)
.add_extra(set_reference_values)
)
@@ -99,3 +235,16 @@ async def to_code(config):
if external_temperature_config := config.get(CONF_EXTERNAL_TEMPERATURE):
sens = await sensor.new_sensor(external_temperature_config)
cg.add(var.set_external_temperature_sensor(sens))
# enable legacy mode
cg.add(var.set_legacy_mode(config.get(CONF_LEGACY_MODE)))
# Set bl0940 commands after validator has determined which defaults to use if not set
cg.add(var.set_read_command(config.get(CONF_READ_COMMAND)))
cg.add(var.set_write_command(config.get(CONF_WRITE_COMMAND)))
# Set reference values after validator has set the values either from defaults or calculated
cg.add(var.set_current_reference(config.get(CONF_CURRENT_REFERENCE)))
cg.add(var.set_voltage_reference(config.get(CONF_VOLTAGE_REFERENCE)))
cg.add(var.set_power_reference(config.get(CONF_POWER_REFERENCE)))
cg.add(var.set_energy_reference(config.get(CONF_ENERGY_REFERENCE)))

View File

@@ -149,7 +149,7 @@ void BL0942::setup() {
this->write_reg_(BL0942_REG_USR_WRPROT, 0);
if (this->read_reg_(BL0942_REG_MODE) != mode)
this->status_set_warning("BL0942 setup failed!");
this->status_set_warning(LOG_STR("BL0942 setup failed!"));
this->flush();
}

View File

@@ -80,7 +80,7 @@ CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(BluetoothProxy),
cv.Optional(CONF_ACTIVE, default=False): cv.boolean,
cv.Optional(CONF_ACTIVE, default=True): cv.boolean,
cv.SplitDefault(CONF_CACHE_SERVICES, esp32_idf=True): cv.All(
cv.only_with_esp_idf, cv.boolean
),

View File

@@ -24,6 +24,9 @@ void BluetoothProxy::setup() {
this->connections_free_response_.limit = BLUETOOTH_PROXY_MAX_CONNECTIONS;
this->connections_free_response_.free = BLUETOOTH_PROXY_MAX_CONNECTIONS;
// Capture the configured scan mode from YAML before any API changes
this->configured_scan_active_ = this->parent_->get_scan_active();
this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) {
if (this->api_connection_ != nullptr) {
this->send_bluetooth_scanner_state_(state);
@@ -36,6 +39,9 @@ void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerSta
resp.state = static_cast<api::enums::BluetoothScannerState>(state);
resp.mode = this->parent_->get_scan_active() ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE
: api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE;
resp.configured_mode = this->configured_scan_active_
? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE
: api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE;
this->api_connection_->send_message(resp, api::BluetoothScannerStateResponse::MESSAGE_TYPE);
}
@@ -183,6 +189,12 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
this->send_device_connection(msg.address, false);
return;
}
if (!msg.has_address_type) {
ESP_LOGE(TAG, "[%d] [%s] Missing address type in connect request", connection->get_connection_index(),
connection->address_str().c_str());
this->send_device_connection(msg.address, false);
return;
}
if (connection->state() == espbt::ClientState::CONNECTED ||
connection->state() == espbt::ClientState::ESTABLISHED) {
this->log_connection_request_ignored_(connection, connection->state());
@@ -209,13 +221,9 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
connection->set_connection_type(espbt::ConnectionType::V3_WITHOUT_CACHE);
this->log_connection_info_(connection, "v3 without cache");
}
if (msg.has_address_type) {
uint64_to_bd_addr(msg.address, connection->remote_bda_);
connection->set_remote_addr_type(static_cast<esp_ble_addr_type_t>(msg.address_type));
connection->set_state(espbt::ClientState::DISCOVERED);
} else {
connection->set_state(espbt::ClientState::SEARCHING);
}
uint64_to_bd_addr(msg.address, connection->remote_bda_);
connection->set_remote_addr_type(static_cast<esp_ble_addr_type_t>(msg.address_type));
connection->set_state(espbt::ClientState::DISCOVERED);
this->send_connections_free();
break;
}

View File

@@ -161,7 +161,8 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, publ
// Group 4: 1-byte types grouped together
bool active_;
uint8_t connection_count_{0};
// 2 bytes used, 2 bytes padding
bool configured_scan_active_{false}; // Configured scan mode from YAML
// 3 bytes used, 1 byte padding
};
extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

View File

@@ -17,7 +17,7 @@ from esphome.const import (
DEVICE_CLASS_RESTART,
DEVICE_CLASS_UPDATE,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass
@@ -134,6 +134,6 @@ async def button_press_to_code(config, action_id, template_arg, args):
return cg.new_Pvariable(action_id, template_arg, paren)
@coroutine_with_priority(100.0)
@coroutine_with_priority(CoroPriority.CORE)
async def to_code(config):
cg.add_global(button_ns.using)

View File

@@ -14,8 +14,8 @@ void log_button(const char *tag, const char *prefix, const char *type, Button *o
ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());
if (!obj->get_icon().empty()) {
ESP_LOGCONFIG(tag, "%s Icon: '%s'", prefix, obj->get_icon().c_str());
if (!obj->get_icon_ref().empty()) {
ESP_LOGCONFIG(tag, "%s Icon: '%s'", prefix, obj->get_icon_ref().c_str());
}
}

View File

@@ -0,0 +1,18 @@
#pragma once
#include <cinttypes>
#include <cstddef>
namespace esphome::camera {
/// Interface for a generic buffer that stores image data.
class Buffer {
public:
/// Returns a pointer to the buffer's data.
virtual uint8_t *get_data_buffer() = 0;
/// Returns the length of the buffer in bytes.
virtual size_t get_data_length() = 0;
virtual ~Buffer() = default;
};
} // namespace esphome::camera

View File

@@ -0,0 +1,20 @@
#include "buffer_impl.h"
namespace esphome::camera {
BufferImpl::BufferImpl(size_t size) {
this->data_ = this->allocator_.allocate(size);
this->size_ = size;
}
BufferImpl::BufferImpl(CameraImageSpec *spec) {
this->data_ = this->allocator_.allocate(spec->bytes_per_image());
this->size_ = spec->bytes_per_image();
}
BufferImpl::~BufferImpl() {
if (this->data_ != nullptr)
this->allocator_.deallocate(this->data_, this->size_);
}
} // namespace esphome::camera

View File

@@ -0,0 +1,26 @@
#pragma once
#include "buffer.h"
#include "camera.h"
namespace esphome::camera {
/// Default implementation of Buffer Interface.
/// Uses a RAMAllocator for memory reservation.
class BufferImpl : public Buffer {
public:
explicit BufferImpl(size_t size);
explicit BufferImpl(CameraImageSpec *spec);
// -------- Buffer --------
uint8_t *get_data_buffer() override { return data_; }
size_t get_data_length() override { return size_; }
// ------------------------
~BufferImpl() override;
protected:
RAMAllocator<uint8_t> allocator_;
size_t size_{};
uint8_t *data_{};
};
} // namespace esphome::camera

View File

@@ -15,6 +15,26 @@ namespace camera {
*/
enum CameraRequester : uint8_t { IDLE, API_REQUESTER, WEB_REQUESTER };
/// Enumeration of different pixel formats.
enum PixelFormat : uint8_t {
PIXEL_FORMAT_GRAYSCALE = 0, ///< 8-bit grayscale.
PIXEL_FORMAT_RGB565, ///< 16-bit RGB (5-6-5).
PIXEL_FORMAT_BGR888, ///< RGB pixel data in 8-bit format, stored as B, G, R (1 byte each).
};
/// Returns string name for a given PixelFormat.
inline const char *to_string(PixelFormat format) {
switch (format) {
case PIXEL_FORMAT_GRAYSCALE:
return "PIXEL_FORMAT_GRAYSCALE";
case PIXEL_FORMAT_RGB565:
return "PIXEL_FORMAT_RGB565";
case PIXEL_FORMAT_BGR888:
return "PIXEL_FORMAT_BGR888";
}
return "PIXEL_FORMAT_UNKNOWN";
}
/** Abstract camera image base class.
* Encapsulates the JPEG encoded data and it is shared among
* all connected clients.
@@ -43,6 +63,29 @@ class CameraImageReader {
virtual ~CameraImageReader() {}
};
/// Specification of a caputured camera image.
/// This struct defines the format and size details for images captured
/// or processed by a camera component.
struct CameraImageSpec {
uint16_t width;
uint16_t height;
PixelFormat format;
size_t bytes_per_pixel() {
switch (format) {
case PIXEL_FORMAT_GRAYSCALE:
return 1;
case PIXEL_FORMAT_RGB565:
return 2;
case PIXEL_FORMAT_BGR888:
return 3;
}
return 1;
}
size_t bytes_per_row() { return bytes_per_pixel() * width; }
size_t bytes_per_image() { return bytes_per_pixel() * width * height; }
};
/** Abstract camera base class. Collaborates with API.
* 1) API server starts and installs callback (add_image_callback)
* which is called by the camera when a new image is available.

View File

@@ -0,0 +1,69 @@
#pragma once
#include "buffer.h"
#include "camera.h"
namespace esphome::camera {
/// Result codes from the encoder used to control camera pipeline flow.
enum EncoderError : uint8_t {
ENCODER_ERROR_SUCCESS = 0, ///< Encoding succeeded, continue pipeline normally.
ENCODER_ERROR_SKIP_FRAME, ///< Skip current frame, try again on next frame.
ENCODER_ERROR_RETRY_FRAME, ///< Retry current frame, after buffer growth or for incremental encoding.
ENCODER_ERROR_CONFIGURATION ///< Fatal config error, shut down pipeline.
};
/// Converts EncoderError to string.
inline const char *to_string(EncoderError error) {
switch (error) {
case ENCODER_ERROR_SUCCESS:
return "ENCODER_ERROR_SUCCESS";
case ENCODER_ERROR_SKIP_FRAME:
return "ENCODER_ERROR_SKIP_FRAME";
case ENCODER_ERROR_RETRY_FRAME:
return "ENCODER_ERROR_RETRY_FRAME";
case ENCODER_ERROR_CONFIGURATION:
return "ENCODER_ERROR_CONFIGURATION";
}
return "ENCODER_ERROR_INVALID";
}
/// Interface for an encoder buffer supporting resizing and variable-length data.
class EncoderBuffer {
public:
/// Sets logical buffer size, reallocates if needed.
/// @param size Required size in bytes.
/// @return true on success, false on allocation failure.
virtual bool set_buffer_size(size_t size) = 0;
/// Returns a pointer to the buffer data.
virtual uint8_t *get_data() const = 0;
/// Returns number of bytes currently used.
virtual size_t get_size() const = 0;
/// Returns total allocated buffer size.
virtual size_t get_max_size() const = 0;
virtual ~EncoderBuffer() = default;
};
/// Interface for image encoders used in a camera pipeline.
class Encoder {
public:
/// Encodes pixel data from a previous camera pipeline stage.
/// @param spec Specification of the input pixel data.
/// @param pixels Image pixels in RGB or grayscale format, as specified in @p spec.
/// @return EncoderError Indicating the result of the encoding operation.
virtual EncoderError encode_pixels(CameraImageSpec *spec, Buffer *pixels) = 0;
/// Returns the encoder's output buffer.
/// @return Pointer to an EncoderBuffer containing encoded data.
virtual EncoderBuffer *get_output_buffer() = 0;
/// Prints the encoder's configuration to the log.
virtual void dump_config() = 0;
virtual ~Encoder() = default;
};
} // namespace esphome::camera

View File

@@ -0,0 +1,62 @@
import esphome.codegen as cg
from esphome.components.esp32 import add_idf_component
import esphome.config_validation as cv
from esphome.const import CONF_BUFFER_SIZE, CONF_ID, CONF_TYPE
from esphome.core import CORE
from esphome.types import ConfigType
CODEOWNERS = ["@DT-art1"]
AUTO_LOAD = ["camera"]
CONF_BUFFER_EXPAND_SIZE = "buffer_expand_size"
CONF_ENCODER_BUFFER_ID = "encoder_buffer_id"
CONF_QUALITY = "quality"
ESP32_CAMERA_ENCODER = "esp32_camera"
camera_ns = cg.esphome_ns.namespace("camera")
camera_encoder_ns = cg.esphome_ns.namespace("camera_encoder")
Encoder = camera_ns.class_("Encoder")
EncoderBufferImpl = camera_encoder_ns.class_("EncoderBufferImpl")
ESP32CameraJPEGEncoder = camera_encoder_ns.class_("ESP32CameraJPEGEncoder", Encoder)
MAX_JPEG_BUFFER_SIZE_2MB = 2 * 1024 * 1024
ESP32_CAMERA_ENCODER_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(ESP32CameraJPEGEncoder),
cv.Optional(CONF_QUALITY, default=80): cv.int_range(1, 100),
cv.Optional(CONF_BUFFER_SIZE, default=4096): cv.int_range(
1024, MAX_JPEG_BUFFER_SIZE_2MB
),
cv.Optional(CONF_BUFFER_EXPAND_SIZE, default=1024): cv.int_range(
0, MAX_JPEG_BUFFER_SIZE_2MB
),
cv.GenerateID(CONF_ENCODER_BUFFER_ID): cv.declare_id(EncoderBufferImpl),
}
)
CONFIG_SCHEMA = cv.typed_schema(
{
ESP32_CAMERA_ENCODER: ESP32_CAMERA_ENCODER_SCHEMA,
},
default_type=ESP32_CAMERA_ENCODER,
)
async def to_code(config: ConfigType) -> None:
buffer = cg.new_Pvariable(config[CONF_ENCODER_BUFFER_ID])
cg.add(buffer.set_buffer_size(config[CONF_BUFFER_SIZE]))
if config[CONF_TYPE] == ESP32_CAMERA_ENCODER:
if CORE.using_esp_idf:
add_idf_component(name="espressif/esp32-camera", ref="2.1.0")
cg.add_build_flag("-DUSE_ESP32_CAMERA_JPEG_ENCODER")
var = cg.new_Pvariable(
config[CONF_ID],
config[CONF_QUALITY],
buffer,
)
cg.add(var.set_buffer_expand_size(config[CONF_BUFFER_EXPAND_SIZE]))

View File

@@ -0,0 +1,23 @@
#include "encoder_buffer_impl.h"
namespace esphome::camera_encoder {
bool EncoderBufferImpl::set_buffer_size(size_t size) {
if (size > this->capacity_) {
uint8_t *p = this->allocator_.reallocate(this->data_, size);
if (p == nullptr)
return false;
this->data_ = p;
this->capacity_ = size;
}
this->size_ = size;
return true;
}
EncoderBufferImpl::~EncoderBufferImpl() {
if (this->data_ != nullptr)
this->allocator_.deallocate(this->data_, this->capacity_);
}
} // namespace esphome::camera_encoder

View File

@@ -0,0 +1,25 @@
#pragma once
#include "esphome/components/camera/encoder.h"
#include "esphome/core/helpers.h"
namespace esphome::camera_encoder {
class EncoderBufferImpl : public camera::EncoderBuffer {
public:
// --- EncoderBuffer ---
bool set_buffer_size(size_t size) override;
uint8_t *get_data() const override { return this->data_; }
size_t get_size() const override { return this->size_; }
size_t get_max_size() const override { return this->capacity_; }
// ----------------------
~EncoderBufferImpl() override;
protected:
RAMAllocator<uint8_t> allocator_;
size_t capacity_{};
size_t size_{};
uint8_t *data_{};
};
} // namespace esphome::camera_encoder

View File

@@ -0,0 +1,82 @@
#ifdef USE_ESP32_CAMERA_JPEG_ENCODER
#include "esp32_camera_jpeg_encoder.h"
namespace esphome::camera_encoder {
static const char *const TAG = "camera_encoder";
ESP32CameraJPEGEncoder::ESP32CameraJPEGEncoder(uint8_t quality, camera::EncoderBuffer *output) {
this->quality_ = quality;
this->output_ = output;
}
camera::EncoderError ESP32CameraJPEGEncoder::encode_pixels(camera::CameraImageSpec *spec, camera::Buffer *pixels) {
this->bytes_written_ = 0;
this->out_of_output_memory_ = false;
bool success = fmt2jpg_cb(pixels->get_data_buffer(), pixels->get_data_length(), spec->width, spec->height,
to_internal_(spec->format), this->quality_, callback_, this);
if (!success)
return camera::ENCODER_ERROR_CONFIGURATION;
if (this->out_of_output_memory_) {
if (this->buffer_expand_size_ <= 0)
return camera::ENCODER_ERROR_SKIP_FRAME;
size_t current_size = this->output_->get_max_size();
size_t new_size = this->output_->get_max_size() + this->buffer_expand_size_;
if (!this->output_->set_buffer_size(new_size)) {
ESP_LOGE(TAG, "Failed to expand output buffer.");
this->buffer_expand_size_ = 0;
return camera::ENCODER_ERROR_SKIP_FRAME;
}
ESP_LOGD(TAG, "Output buffer expanded (%u -> %u).", current_size, this->output_->get_max_size());
return camera::ENCODER_ERROR_RETRY_FRAME;
}
this->output_->set_buffer_size(this->bytes_written_);
return camera::ENCODER_ERROR_SUCCESS;
}
void ESP32CameraJPEGEncoder::dump_config() {
ESP_LOGCONFIG(TAG,
"ESP32 Camera JPEG Encoder:\n"
" Size: %zu\n"
" Quality: %d\n"
" Expand: %d\n",
this->output_->get_max_size(), this->quality_, this->buffer_expand_size_);
}
size_t ESP32CameraJPEGEncoder::callback_(void *arg, size_t index, const void *data, size_t len) {
ESP32CameraJPEGEncoder *that = reinterpret_cast<ESP32CameraJPEGEncoder *>(arg);
uint8_t *buffer = that->output_->get_data();
size_t buffer_length = that->output_->get_max_size();
if (index + len > buffer_length) {
that->out_of_output_memory_ = true;
return 0;
}
std::memcpy(&buffer[index], data, len);
that->bytes_written_ += len;
return len;
}
pixformat_t ESP32CameraJPEGEncoder::to_internal_(camera::PixelFormat format) {
switch (format) {
case camera::PIXEL_FORMAT_GRAYSCALE:
return PIXFORMAT_GRAYSCALE;
case camera::PIXEL_FORMAT_RGB565:
return PIXFORMAT_RGB565;
// Internal representation for RGB is in byte order: B, G, R
case camera::PIXEL_FORMAT_BGR888:
return PIXFORMAT_RGB888;
}
return PIXFORMAT_GRAYSCALE;
}
} // namespace esphome::camera_encoder
#endif

View File

@@ -0,0 +1,39 @@
#pragma once
#ifdef USE_ESP32_CAMERA_JPEG_ENCODER
#include <esp_camera.h>
#include "esphome/components/camera/encoder.h"
namespace esphome::camera_encoder {
/// Encoder that uses the software-based JPEG implementation from Espressif's esp32-camera component.
class ESP32CameraJPEGEncoder : public camera::Encoder {
public:
/// Constructs a ESP32CameraJPEGEncoder instance.
/// @param quality Sets the quality of the encoded image (1-100).
/// @param output Pointer to preallocated output buffer.
ESP32CameraJPEGEncoder(uint8_t quality, camera::EncoderBuffer *output);
/// Sets the number of bytes to expand the output buffer on underflow during encoding.
/// @param buffer_expand_size Number of bytes to expand the buffer.
void set_buffer_expand_size(size_t buffer_expand_size) { this->buffer_expand_size_ = buffer_expand_size; }
// -------- Encoder --------
camera::EncoderError encode_pixels(camera::CameraImageSpec *spec, camera::Buffer *pixels) override;
camera::EncoderBuffer *get_output_buffer() override { return output_; }
void dump_config() override;
// -------------------------
protected:
static size_t callback_(void *arg, size_t index, const void *data, size_t len);
pixformat_t to_internal_(camera::PixelFormat format);
camera::EncoderBuffer *output_{};
size_t buffer_expand_size_{};
size_t bytes_written_{};
uint8_t quality_{};
bool out_of_output_memory_{};
};
} // namespace esphome::camera_encoder
#endif

View File

@@ -10,7 +10,7 @@ from esphome.const import (
PLATFORM_LN882X,
PLATFORM_RTL87XX,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.core import CORE, CoroPriority, coroutine_with_priority
AUTO_LOAD = ["web_server_base", "ota.web_server"]
DEPENDENCIES = ["wifi"]
@@ -40,7 +40,7 @@ CONFIG_SCHEMA = cv.All(
)
@coroutine_with_priority(64.0)
@coroutine_with_priority(CoroPriority.COMMUNICATION)
async def to_code(config):
paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID])

View File

@@ -11,17 +11,35 @@ namespace captive_portal {
static const char *const TAG = "captive_portal";
void CaptivePortal::handle_config(AsyncWebServerRequest *request) {
AsyncResponseStream *stream = request->beginResponseStream("application/json");
stream->addHeader("cache-control", "public, max-age=0, must-revalidate");
AsyncResponseStream *stream = request->beginResponseStream(F("application/json"));
stream->addHeader(F("cache-control"), F("public, max-age=0, must-revalidate"));
#ifdef USE_ESP8266
stream->print(F("{\"mac\":\""));
stream->print(get_mac_address_pretty().c_str());
stream->print(F("\",\"name\":\""));
stream->print(App.get_name().c_str());
stream->print(F("\",\"aps\":[{}"));
#else
stream->printf(R"({"mac":"%s","name":"%s","aps":[{})", get_mac_address_pretty().c_str(), App.get_name().c_str());
#endif
for (auto &scan : wifi::global_wifi_component->get_scan_result()) {
if (scan.get_is_hidden())
continue;
// Assumes no " in ssid, possible unicode isses?
// Assumes no " in ssid, possible unicode isses?
#ifdef USE_ESP8266
stream->print(F(",{\"ssid\":\""));
stream->print(scan.get_ssid().c_str());
stream->print(F("\",\"rssi\":"));
stream->print(scan.get_rssi());
stream->print(F(",\"lock\":"));
stream->print(scan.get_with_auth());
stream->print(F("}"));
#else
stream->printf(R"(,{"ssid":"%s","rssi":%d,"lock":%d})", scan.get_ssid().c_str(), scan.get_rssi(),
scan.get_with_auth());
#endif
}
stream->print(F("]}"));
request->send(stream);
@@ -34,7 +52,7 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) {
ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str());
wifi::global_wifi_component->save_wifi_sta(ssid, psk);
wifi::global_wifi_component->start_scanning();
request->redirect("/?save");
request->redirect(F("/?save"));
}
void CaptivePortal::setup() {
@@ -53,18 +71,23 @@ void CaptivePortal::start() {
this->dns_server_ = make_unique<DNSServer>();
this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError);
network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip();
this->dns_server_->start(53, "*", ip);
this->dns_server_->start(53, F("*"), ip);
// Re-enable loop() when DNS server is started
this->enable_loop();
#endif
this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) {
if (!this->active_ || req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) {
req->send(404, "text/html", "File not found");
req->send(404, F("text/html"), F("File not found"));
return;
}
#ifdef USE_ESP8266
String url = F("http://");
url += wifi::global_wifi_component->wifi_soft_ap_ip().str().c_str();
#else
auto url = "http://" + wifi::global_wifi_component->wifi_soft_ap_ip().str();
#endif
req->redirect(url.c_str());
});
@@ -73,19 +96,19 @@ void CaptivePortal::start() {
}
void CaptivePortal::handleRequest(AsyncWebServerRequest *req) {
if (req->url() == "/") {
if (req->url() == F("/")) {
#ifndef USE_ESP8266
auto *response = req->beginResponse(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ));
auto *response = req->beginResponse(200, F("text/html"), INDEX_GZ, sizeof(INDEX_GZ));
#else
auto *response = req->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ));
auto *response = req->beginResponse_P(200, F("text/html"), INDEX_GZ, sizeof(INDEX_GZ));
#endif
response->addHeader("Content-Encoding", "gzip");
response->addHeader(F("Content-Encoding"), F("gzip"));
req->send(response);
return;
} else if (req->url() == "/config.json") {
} else if (req->url() == F("/config.json")) {
this->handle_config(req);
return;
} else if (req->url() == "/wifisave") {
} else if (req->url() == F("/wifisave")) {
this->handle_wifisave(req);
return;
}

View File

@@ -45,11 +45,11 @@ class CaptivePortal : public AsyncWebHandler, public Component {
return false;
if (request->method() == HTTP_GET) {
if (request->url() == "/")
if (request->url() == F("/"))
return true;
if (request->url() == "/config.json")
if (request->url() == F("/config.json"))
return true;
if (request->url() == "/wifisave")
if (request->url() == F("/wifisave"))
return true;
}

View File

@@ -152,7 +152,7 @@ void CCS811Component::send_env_data_() {
void CCS811Component::dump_config() {
ESP_LOGCONFIG(TAG, "CCS811");
LOG_I2C_DEVICE(this)
LOG_UPDATE_INTERVAL(this)
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "CO2 Sensor", this->co2_);
LOG_SENSOR(" ", "TVOC Sensor", this->tvoc_);
LOG_TEXT_SENSOR(" ", "Firmware Version Sensor", this->version_)

View File

@@ -47,7 +47,7 @@ from esphome.const import (
CONF_VISUAL,
CONF_WEB_SERVER,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass
@@ -517,6 +517,6 @@ async def climate_control_to_code(config, action_id, template_arg, args):
return var
@coroutine_with_priority(100.0)
@coroutine_with_priority(CoroPriority.CORE)
async def to_code(config):
cg.add_global(climate_ns.using)

View File

@@ -32,7 +32,7 @@ from esphome.const import (
DEVICE_CLASS_SHUTTER,
DEVICE_CLASS_WINDOW,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass
@@ -263,6 +263,6 @@ async def cover_control_to_code(config, action_id, template_arg, args):
return var
@coroutine_with_priority(100.0)
@coroutine_with_priority(CoroPriority.CORE)
async def to_code(config):
cg.add_global(cover_ns.using)

View File

@@ -19,8 +19,8 @@ const extern float COVER_CLOSED;
if (traits_.get_is_assumed_state()) { \
ESP_LOGCONFIG(TAG, "%s Assumed State: YES", prefix); \
} \
if (!(obj)->get_device_class().empty()) { \
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
if (!(obj)->get_device_class_ref().empty()) { \
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class_ref().c_str()); \
} \
}

View File

@@ -64,7 +64,7 @@ bool DallasTemperatureSensor::read_scratch_pad_() {
}
} else {
ESP_LOGW(TAG, "'%s' - reading scratch pad failed bus reset", this->get_name().c_str());
this->status_set_warning("bus reset failed");
this->status_set_warning(LOG_STR("bus reset failed"));
}
return success;
}
@@ -124,7 +124,7 @@ bool DallasTemperatureSensor::check_scratch_pad_() {
crc8(this->scratch_pad_, 8));
#endif
if (!chksum_validity) {
this->status_set_warning("scratch pad checksum invalid");
this->status_set_warning(LOG_STR("scratch pad checksum invalid"));
ESP_LOGD(TAG, "Scratch pad: %02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X (%02X)", this->scratch_pad_[0],
this->scratch_pad_[1], this->scratch_pad_[2], this->scratch_pad_[3], this->scratch_pad_[4],
this->scratch_pad_[5], this->scratch_pad_[6], this->scratch_pad_[7], this->scratch_pad_[8],

View File

@@ -21,7 +21,7 @@ from esphome.const import (
CONF_WEB_SERVER,
CONF_YEAR,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass
@@ -172,7 +172,7 @@ async def new_datetime(config, *args):
return var
@coroutine_with_priority(100.0)
@coroutine_with_priority(CoroPriority.CORE)
async def to_code(config):
cg.add_global(datetime_ns.using)

View File

@@ -16,8 +16,8 @@ namespace datetime {
#define LOG_DATETIME_DATE(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
if (!(obj)->get_icon().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
if (!(obj)->get_icon_ref().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon_ref().c_str()); \
} \
}

View File

@@ -16,8 +16,8 @@ namespace datetime {
#define LOG_DATETIME_DATETIME(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
if (!(obj)->get_icon().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
if (!(obj)->get_icon_ref().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon_ref().c_str()); \
} \
}

View File

@@ -16,8 +16,8 @@ namespace datetime {
#define LOG_DATETIME_TIME(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
if (!(obj)->get_icon().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
if (!(obj)->get_icon_ref().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon_ref().c_str()); \
} \
}

View File

@@ -52,7 +52,7 @@ void DebugComponent::on_shutdown() {
char buffer[REBOOT_MAX_LEN]{};
auto pref = global_preferences->make_preference(REBOOT_MAX_LEN, fnv1_hash(REBOOT_KEY + App.get_name()));
if (component != nullptr) {
strncpy(buffer, component->get_component_source(), REBOOT_MAX_LEN - 1);
strncpy(buffer, LOG_STR_ARG(component->get_component_log_str()), REBOOT_MAX_LEN - 1);
buffer[REBOOT_MAX_LEN - 1] = '\0';
}
ESP_LOGD(TAG, "Storing reboot source: %s", buffer);

View File

@@ -15,7 +15,7 @@ from esphome.const import (
CONF_UPDATE_INTERVAL,
SCHEDULER_DONT_RUN,
)
from esphome.core import coroutine_with_priority
from esphome.core import CoroPriority, coroutine_with_priority
IS_PLATFORM_COMPONENT = True
@@ -176,7 +176,7 @@ async def display_page_show_to_code(config, action_id, template_arg, args):
DisplayPageShowNextAction,
maybe_simple_id(
{
cv.Required(CONF_ID): cv.templatable(cv.use_id(Display)),
cv.GenerateID(CONF_ID): cv.templatable(cv.use_id(Display)),
}
),
)
@@ -190,7 +190,7 @@ async def display_page_show_next_to_code(config, action_id, template_arg, args):
DisplayPageShowPrevAction,
maybe_simple_id(
{
cv.Required(CONF_ID): cv.templatable(cv.use_id(Display)),
cv.GenerateID(CONF_ID): cv.templatable(cv.use_id(Display)),
}
),
)
@@ -218,7 +218,7 @@ async def display_is_displaying_page_to_code(config, condition_id, template_arg,
return var
@coroutine_with_priority(100.0)
@coroutine_with_priority(CoroPriority.CORE)
async def to_code(config):
cg.add_global(display_ns.using)
cg.add_define("USE_DISPLAY")

View File

@@ -40,6 +40,7 @@ from esphome.cpp_generator import RawExpression
import esphome.final_validate as fv
from esphome.helpers import copy_file_if_changed, mkdir_p, write_file_if_changed
from esphome.types import ConfigType
from esphome.writer import clean_cmake_cache
from .boards import BOARDS, STANDARD_BOARDS
from .const import ( # noqa
@@ -840,6 +841,9 @@ async def to_code(config):
if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_CUSTOM_MAC]:
cg.add_define("USE_ESP32_IGNORE_EFUSE_CUSTOM_MAC")
for clean_var in ("IDF_PATH", "IDF_TOOLS_PATH"):
os.environ.pop(clean_var, None)
add_extra_script(
"post",
"post_build.py",
@@ -855,11 +859,6 @@ async def to_code(config):
cg.add_platformio_option("platform_packages", [conf[CONF_SOURCE]])
# platformio/toolchain-esp32ulp does not support linux_aarch64 yet and has not been updated for over 2 years
# This is espressif's own published version which is more up to date.
cg.add_platformio_option(
"platform_packages", ["espressif/toolchain-esp32ulp@2.35.0-20220830"]
)
add_idf_sdkconfig_option(f"CONFIG_IDF_TARGET_{variant}", True)
add_idf_sdkconfig_option(
f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True
@@ -1079,7 +1078,11 @@ def _write_idf_component_yml():
contents = yaml_util.dump({"dependencies": dependencies})
else:
contents = ""
write_file_if_changed(yml_path, contents)
if write_file_if_changed(yml_path, contents):
dependencies_lock = CORE.relative_build_path("dependencies.lock")
if os.path.isfile(dependencies_lock):
os.remove(dependencies_lock)
clean_cmake_cache()
# Called by writer.py

View File

@@ -54,13 +54,13 @@ struct ISRPinArg {
ISRInternalGPIOPin ESP32InternalGPIOPin::to_isr() const {
auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
arg->pin = this->pin_;
arg->pin = this->get_pin_num();
arg->flags = gpio::FLAG_NONE;
arg->inverted = inverted_;
arg->inverted = this->pin_flags_.inverted;
#if defined(USE_ESP32_VARIANT_ESP32)
arg->use_rtc = rtc_gpio_is_valid_gpio(this->pin_);
arg->use_rtc = rtc_gpio_is_valid_gpio(this->get_pin_num());
if (arg->use_rtc)
arg->rtc_pin = rtc_io_number_get(this->pin_);
arg->rtc_pin = rtc_io_number_get(this->get_pin_num());
#endif
return ISRInternalGPIOPin((void *) arg);
}
@@ -69,23 +69,23 @@ void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpi
gpio_int_type_t idf_type = GPIO_INTR_ANYEDGE;
switch (type) {
case gpio::INTERRUPT_RISING_EDGE:
idf_type = inverted_ ? GPIO_INTR_NEGEDGE : GPIO_INTR_POSEDGE;
idf_type = this->pin_flags_.inverted ? GPIO_INTR_NEGEDGE : GPIO_INTR_POSEDGE;
break;
case gpio::INTERRUPT_FALLING_EDGE:
idf_type = inverted_ ? GPIO_INTR_POSEDGE : GPIO_INTR_NEGEDGE;
idf_type = this->pin_flags_.inverted ? GPIO_INTR_POSEDGE : GPIO_INTR_NEGEDGE;
break;
case gpio::INTERRUPT_ANY_EDGE:
idf_type = GPIO_INTR_ANYEDGE;
break;
case gpio::INTERRUPT_LOW_LEVEL:
idf_type = inverted_ ? GPIO_INTR_HIGH_LEVEL : GPIO_INTR_LOW_LEVEL;
idf_type = this->pin_flags_.inverted ? GPIO_INTR_HIGH_LEVEL : GPIO_INTR_LOW_LEVEL;
break;
case gpio::INTERRUPT_HIGH_LEVEL:
idf_type = inverted_ ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL;
idf_type = this->pin_flags_.inverted ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL;
break;
}
gpio_set_intr_type(pin_, idf_type);
gpio_intr_enable(pin_);
gpio_set_intr_type(this->get_pin_num(), idf_type);
gpio_intr_enable(this->get_pin_num());
if (!isr_service_installed) {
auto res = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL3);
if (res != ESP_OK) {
@@ -94,31 +94,31 @@ void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpi
}
isr_service_installed = true;
}
gpio_isr_handler_add(pin_, func, arg);
gpio_isr_handler_add(this->get_pin_num(), func, arg);
}
std::string ESP32InternalGPIOPin::dump_summary() const {
char buffer[32];
snprintf(buffer, sizeof(buffer), "GPIO%" PRIu32, static_cast<uint32_t>(pin_));
snprintf(buffer, sizeof(buffer), "GPIO%" PRIu32, static_cast<uint32_t>(this->pin_));
return buffer;
}
void ESP32InternalGPIOPin::setup() {
gpio_config_t conf{};
conf.pin_bit_mask = 1ULL << static_cast<uint32_t>(pin_);
conf.mode = flags_to_mode(flags_);
conf.pull_up_en = flags_ & gpio::FLAG_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
conf.pull_down_en = flags_ & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE;
conf.pin_bit_mask = 1ULL << static_cast<uint32_t>(this->pin_);
conf.mode = flags_to_mode(this->flags_);
conf.pull_up_en = this->flags_ & gpio::FLAG_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
conf.pull_down_en = this->flags_ & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE;
conf.intr_type = GPIO_INTR_DISABLE;
gpio_config(&conf);
if (flags_ & gpio::FLAG_OUTPUT) {
gpio_set_drive_capability(pin_, drive_strength_);
if (this->flags_ & gpio::FLAG_OUTPUT) {
gpio_set_drive_capability(this->get_pin_num(), this->get_drive_strength());
}
}
void ESP32InternalGPIOPin::pin_mode(gpio::Flags flags) {
// can't call gpio_config here because that logs in esp-idf which may cause issues
gpio_set_direction(pin_, flags_to_mode(flags));
gpio_set_direction(this->get_pin_num(), flags_to_mode(flags));
gpio_pull_mode_t pull_mode = GPIO_FLOATING;
if ((flags & gpio::FLAG_PULLUP) && (flags & gpio::FLAG_PULLDOWN)) {
pull_mode = GPIO_PULLUP_PULLDOWN;
@@ -127,12 +127,16 @@ void ESP32InternalGPIOPin::pin_mode(gpio::Flags flags) {
} else if (flags & gpio::FLAG_PULLDOWN) {
pull_mode = GPIO_PULLDOWN_ONLY;
}
gpio_set_pull_mode(pin_, pull_mode);
gpio_set_pull_mode(this->get_pin_num(), pull_mode);
}
bool ESP32InternalGPIOPin::digital_read() { return bool(gpio_get_level(pin_)) != inverted_; }
void ESP32InternalGPIOPin::digital_write(bool value) { gpio_set_level(pin_, value != inverted_ ? 1 : 0); }
void ESP32InternalGPIOPin::detach_interrupt() const { gpio_intr_disable(pin_); }
bool ESP32InternalGPIOPin::digital_read() {
return bool(gpio_get_level(this->get_pin_num())) != this->pin_flags_.inverted;
}
void ESP32InternalGPIOPin::digital_write(bool value) {
gpio_set_level(this->get_pin_num(), value != this->pin_flags_.inverted ? 1 : 0);
}
void ESP32InternalGPIOPin::detach_interrupt() const { gpio_intr_disable(this->get_pin_num()); }
} // namespace esp32

View File

@@ -7,12 +7,18 @@
namespace esphome {
namespace esp32 {
// Static assertions to ensure our bit-packed fields can hold the enum values
static_assert(GPIO_NUM_MAX <= 256, "gpio_num_t has too many values for uint8_t");
static_assert(GPIO_DRIVE_CAP_MAX <= 4, "gpio_drive_cap_t has too many values for 2-bit field");
class ESP32InternalGPIOPin : public InternalGPIOPin {
public:
void set_pin(gpio_num_t pin) { pin_ = pin; }
void set_inverted(bool inverted) { inverted_ = inverted; }
void set_drive_strength(gpio_drive_cap_t drive_strength) { drive_strength_ = drive_strength; }
void set_flags(gpio::Flags flags) { flags_ = flags; }
void set_pin(gpio_num_t pin) { this->pin_ = static_cast<uint8_t>(pin); }
void set_inverted(bool inverted) { this->pin_flags_.inverted = inverted; }
void set_drive_strength(gpio_drive_cap_t drive_strength) {
this->pin_flags_.drive_strength = static_cast<uint8_t>(drive_strength);
}
void set_flags(gpio::Flags flags) { this->flags_ = flags; }
void setup() override;
void pin_mode(gpio::Flags flags) override;
@@ -21,17 +27,26 @@ class ESP32InternalGPIOPin : public InternalGPIOPin {
std::string dump_summary() const override;
void detach_interrupt() const override;
ISRInternalGPIOPin to_isr() const override;
uint8_t get_pin() const override { return (uint8_t) pin_; }
gpio::Flags get_flags() const override { return flags_; }
bool is_inverted() const override { return inverted_; }
uint8_t get_pin() const override { return this->pin_; }
gpio::Flags get_flags() const override { return this->flags_; }
bool is_inverted() const override { return this->pin_flags_.inverted; }
gpio_num_t get_pin_num() const { return static_cast<gpio_num_t>(this->pin_); }
gpio_drive_cap_t get_drive_strength() const { return static_cast<gpio_drive_cap_t>(this->pin_flags_.drive_strength); }
protected:
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
gpio_num_t pin_;
gpio_drive_cap_t drive_strength_;
gpio::Flags flags_;
bool inverted_;
// Memory layout: 8 bytes total on 32-bit systems
// - 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)
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
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
static bool isr_service_installed;
};

View File

@@ -3,17 +3,7 @@ import re
from esphome import automation
import esphome.codegen as cg
from esphome.components.esp32 import (
VARIANT_ESP32C2,
VARIANT_ESP32C3,
VARIANT_ESP32C5,
VARIANT_ESP32C6,
VARIANT_ESP32H2,
VARIANT_ESP32S3,
add_idf_sdkconfig_option,
const,
get_esp32_variant,
)
from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant
import esphome.config_validation as cv
from esphome.const import (
CONF_ENABLE_ON_BOOT,
@@ -21,10 +11,8 @@ from esphome.const import (
CONF_ID,
CONF_NAME,
CONF_NAME_ADD_MAC_SUFFIX,
CONF_TX_POWER,
)
from esphome.core import CORE, TimePeriod
from esphome.cpp_types import MockObj
import esphome.final_validate as fv
DEPENDENCIES = ["esp32"]
@@ -163,8 +151,7 @@ IO_CAPABILITY = {
esp_power_level_t = cg.global_ns.enum("esp_power_level_t")
# Power level mappings for code generation - ESP32 classic
TX_POWER_LEVELS_ESP32 = {
TX_POWER_LEVELS = {
-12: esp_power_level_t.ESP_PWR_LVL_N12,
-9: esp_power_level_t.ESP_PWR_LVL_N9,
-6: esp_power_level_t.ESP_PWR_LVL_N6,
@@ -175,53 +162,6 @@ TX_POWER_LEVELS_ESP32 = {
9: esp_power_level_t.ESP_PWR_LVL_P9,
}
# Power level mappings for code generation - Extended variants
TX_POWER_LEVELS_EXT = {
-24: esp_power_level_t.ESP_PWR_LVL_N24,
-21: esp_power_level_t.ESP_PWR_LVL_N21,
-18: esp_power_level_t.ESP_PWR_LVL_N18,
-15: esp_power_level_t.ESP_PWR_LVL_N15,
-12: esp_power_level_t.ESP_PWR_LVL_N12,
-9: esp_power_level_t.ESP_PWR_LVL_N9,
-6: esp_power_level_t.ESP_PWR_LVL_N6,
-3: esp_power_level_t.ESP_PWR_LVL_N3,
0: esp_power_level_t.ESP_PWR_LVL_N0,
3: esp_power_level_t.ESP_PWR_LVL_P3,
6: esp_power_level_t.ESP_PWR_LVL_P6,
9: esp_power_level_t.ESP_PWR_LVL_P9,
12: esp_power_level_t.ESP_PWR_LVL_P12,
15: esp_power_level_t.ESP_PWR_LVL_P15,
18: esp_power_level_t.ESP_PWR_LVL_P18,
20: esp_power_level_t.ESP_PWR_LVL_P20,
}
def _get_tx_power_levels() -> dict[str, MockObj]:
variant = get_esp32_variant()
if variant in [
VARIANT_ESP32C2,
VARIANT_ESP32C3,
VARIANT_ESP32C5,
VARIANT_ESP32C6,
VARIANT_ESP32H2,
VARIANT_ESP32S3,
]:
return TX_POWER_LEVELS_EXT
return TX_POWER_LEVELS_ESP32
def validate_tx_power(value: int) -> int:
value = cv.decibel(value)
power_levels = _get_tx_power_levels()
if value not in power_levels:
raise cv.Invalid(
f"TX power {value}dBm is not valid. "
f"Valid values are: {', '.join(str(v) + 'dBm' for v in sorted(power_levels.keys()))}"
)
# Return just the dBm value, we'll map it to enum in to_code
return value
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(ESP32BLE),
@@ -229,7 +169,6 @@ CONFIG_SCHEMA = cv.Schema(
cv.Optional(CONF_IO_CAPABILITY, default="none"): cv.enum(
IO_CAPABILITY, lower=True
),
cv.Optional(CONF_TX_POWER): validate_tx_power,
cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean,
cv.Optional(CONF_ADVERTISING, default=False): cv.boolean,
cv.Optional(
@@ -320,9 +259,6 @@ async def to_code(config):
cg.add(var.set_advertising_cycle_time(config[CONF_ADVERTISING_CYCLE_TIME]))
if (name := config.get(CONF_NAME)) is not None:
cg.add(var.set_name(name))
if (tx_power := config.get(CONF_TX_POWER)) is not None:
# The validation already returned the enum value
cg.add(var.set_tx_power(_get_tx_power_levels()[tx_power]))
await cg.register_component(var, config)
if CORE.using_esp_idf:

View File

@@ -212,15 +212,6 @@ bool ESP32BLE::ble_setup_() {
return false;
}
// Set TX power for all BLE operations (advertising, scanning, connections)
err = esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_DEFAULT, this->tx_power_);
if (err != ESP_OK) {
ESP_LOGW(TAG, "esp_ble_tx_power_set failed: %s", esp_err_to_name(err));
// Continue anyway as this is not critical
} else {
ESP_LOGD(TAG, "BLE TX power set to level %d", this->tx_power_);
}
// BLE takes some time to be fully set up, 200ms should be more than enough
delay(200); // NOLINT
@@ -529,106 +520,11 @@ void ESP32BLE::dump_config() {
io_capability_s = "invalid";
break;
}
// Convert TX power level to dBm for display
int tx_power_dbm = 0;
#if defined(CONFIG_IDF_TARGET_ESP32)
// ESP32 classic power levels (0-7)
switch (this->tx_power_) {
case 0:
tx_power_dbm = -12;
break; // ESP_PWR_LVL_N12
case 1:
tx_power_dbm = -9;
break; // ESP_PWR_LVL_N9
case 2:
tx_power_dbm = -6;
break; // ESP_PWR_LVL_N6
case 3:
tx_power_dbm = -3;
break; // ESP_PWR_LVL_N3
case 4:
tx_power_dbm = 0;
break; // ESP_PWR_LVL_N0
case 5:
tx_power_dbm = 3;
break; // ESP_PWR_LVL_P3
case 6:
tx_power_dbm = 6;
break; // ESP_PWR_LVL_P6
case 7:
tx_power_dbm = 9;
break; // ESP_PWR_LVL_P9
default:
tx_power_dbm = 0;
break;
}
#elif defined(CONFIG_IDF_TARGET_ESP32C2) || defined(CONFIG_IDF_TARGET_ESP32C3) || \
defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32H2) || \
defined(CONFIG_IDF_TARGET_ESP32S3)
// Extended power levels for C2/C3/C5/C6/H2/S3 (0-15)
switch (this->tx_power_) {
case 0:
tx_power_dbm = -24;
break; // ESP_PWR_LVL_N24
case 1:
tx_power_dbm = -21;
break; // ESP_PWR_LVL_N21
case 2:
tx_power_dbm = -18;
break; // ESP_PWR_LVL_N18
case 3:
tx_power_dbm = -15;
break; // ESP_PWR_LVL_N15
case 4:
tx_power_dbm = -12;
break; // ESP_PWR_LVL_N12
case 5:
tx_power_dbm = -9;
break; // ESP_PWR_LVL_N9
case 6:
tx_power_dbm = -6;
break; // ESP_PWR_LVL_N6
case 7:
tx_power_dbm = -3;
break; // ESP_PWR_LVL_N3
case 8:
tx_power_dbm = 0;
break; // ESP_PWR_LVL_N0
case 9:
tx_power_dbm = 3;
break; // ESP_PWR_LVL_P3
case 10:
tx_power_dbm = 6;
break; // ESP_PWR_LVL_P6
case 11:
tx_power_dbm = 9;
break; // ESP_PWR_LVL_P9
case 12:
tx_power_dbm = 12;
break; // ESP_PWR_LVL_P12
case 13:
tx_power_dbm = 15;
break; // ESP_PWR_LVL_P15
case 14:
tx_power_dbm = 18;
break; // ESP_PWR_LVL_P18
case 15:
tx_power_dbm = 20;
break; // ESP_PWR_LVL_P20
default:
tx_power_dbm = 0;
break;
}
#else
// Unknown variant
tx_power_dbm = 0;
#endif
ESP_LOGCONFIG(TAG,
"BLE:\n"
" MAC address: %s\n"
" IO Capability: %s\n"
" TX Power: %d dBm",
format_mac_address_pretty(mac_address).c_str(), io_capability_s, tx_power_dbm);
" IO Capability: %s",
format_mac_address_pretty(mac_address).c_str(), io_capability_s);
} else {
ESP_LOGCONFIG(TAG, "Bluetooth stack is not enabled");
}

View File

@@ -20,7 +20,6 @@
#ifdef USE_ESP32
#include <esp_bt.h>
#include <esp_gap_ble_api.h>
#include <esp_gattc_api.h>
#include <esp_gatts_api.h>
@@ -95,7 +94,6 @@ class BLEStatusEventHandler {
class ESP32BLE : public Component {
public:
void set_io_capability(IoCapability io_capability) { this->io_cap_ = (esp_ble_io_cap_t) io_capability; }
void set_tx_power(esp_power_level_t tx_power) { this->tx_power_ = tx_power; }
void set_advertising_cycle_time(uint32_t advertising_cycle_time) {
this->advertising_cycle_time_ = advertising_cycle_time;
@@ -174,7 +172,6 @@ class ESP32BLE : public Component {
// 1-byte aligned members (grouped together to minimize padding)
BLEComponentState state_{BLE_COMPONENT_STATE_OFF}; // 1 byte (uint8_t enum)
bool enable_on_boot_{}; // 1 byte
esp_power_level_t tx_power_{ESP_PWR_LVL_P9}; // 1 byte (default: +9 dBm)
};
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)

View File

@@ -93,7 +93,7 @@ bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
return false;
if (this->address_ == 0 || device.address_uint64() != this->address_)
return false;
if (this->state_ != espbt::ClientState::IDLE && this->state_ != espbt::ClientState::SEARCHING)
if (this->state_ != espbt::ClientState::IDLE)
return false;
this->log_event_("Found device");
@@ -168,8 +168,7 @@ void BLEClientBase::unconditional_disconnect() {
this->log_gattc_warning_("esp_ble_gattc_close", err);
}
if (this->state_ == espbt::ClientState::SEARCHING || this->state_ == espbt::ClientState::READY_TO_CONNECT ||
this->state_ == espbt::ClientState::DISCOVERED) {
if (this->state_ == espbt::ClientState::READY_TO_CONNECT || this->state_ == espbt::ClientState::DISCOVERED) {
this->set_address(0);
this->set_state(espbt::ClientState::IDLE);
} else {

View File

@@ -30,7 +30,7 @@ from esphome.const import (
CONF_SERVICE_UUID,
CONF_TRIGGER_ID,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.enum import StrEnum
from esphome.types import ConfigType
@@ -368,7 +368,7 @@ async def to_code(config):
# This needs to be run as a job with very low priority so that all components have
# chance to call register_ble_tracker and register_client before the list is checked
# and added to the global defines list.
@coroutine_with_priority(-1000)
@coroutine_with_priority(CoroPriority.FINAL)
async def _add_ble_features():
# Add feature-specific defines based on what's needed
if BLEFeatures.ESP_BT_DEVICE in _required_features:

View File

@@ -49,8 +49,6 @@ const char *client_state_to_string(ClientState state) {
return "DISCONNECTING";
case ClientState::IDLE:
return "IDLE";
case ClientState::SEARCHING:
return "SEARCHING";
case ClientState::DISCOVERED:
return "DISCOVERED";
case ClientState::READY_TO_CONNECT:
@@ -136,9 +134,8 @@ void ESP32BLETracker::loop() {
ClientStateCounts counts = this->count_client_states_();
if (counts != this->client_state_counts_) {
this->client_state_counts_ = counts;
ESP_LOGD(TAG, "connecting: %d, discovered: %d, searching: %d, disconnecting: %d",
this->client_state_counts_.connecting, this->client_state_counts_.discovered,
this->client_state_counts_.searching, this->client_state_counts_.disconnecting);
ESP_LOGD(TAG, "connecting: %d, discovered: %d, disconnecting: %d", this->client_state_counts_.connecting,
this->client_state_counts_.discovered, this->client_state_counts_.disconnecting);
}
if (this->scanner_state_ == ScannerState::FAILED ||
@@ -158,10 +155,8 @@ void ESP32BLETracker::loop() {
https://github.com/espressif/esp-idf/issues/6688
*/
bool promote_to_connecting = counts.discovered && !counts.searching && !counts.connecting;
if (this->scanner_state_ == ScannerState::IDLE && !counts.connecting && !counts.disconnecting &&
!promote_to_connecting) {
if (this->scanner_state_ == ScannerState::IDLE && !counts.connecting && !counts.disconnecting && !counts.discovered) {
#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
this->update_coex_preference_(false);
#endif
@@ -170,12 +165,11 @@ void ESP32BLETracker::loop() {
}
}
// If there is a discovered client and no connecting
// clients and no clients using the scanner to search for
// devices, then promote the discovered client to ready to connect.
// clients, then promote the discovered client to ready to connect.
// We check both RUNNING and IDLE states because:
// - RUNNING: gap_scan_event_handler initiates stop_scan_() but promotion can happen immediately
// - IDLE: Scanner has already stopped (naturally or by gap_scan_event_handler)
if (promote_to_connecting &&
if (counts.discovered && !counts.connecting &&
(this->scanner_state_ == ScannerState::RUNNING || this->scanner_state_ == ScannerState::IDLE)) {
this->try_promote_discovered_clients_();
}
@@ -307,14 +301,7 @@ void ESP32BLETracker::gap_scan_event_handler(const BLEScanResult &scan_result) {
if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
// Process the scan result immediately
bool found_discovered_client = this->process_scan_result_(scan_result);
// If we found a discovered client that needs promotion, stop scanning
// This replaces the promote_to_connecting logic from loop()
if (found_discovered_client && this->scanner_state_ == ScannerState::RUNNING) {
ESP_LOGD(TAG, "Found discovered client, stopping scan for connection");
this->stop_scan_();
}
this->process_scan_result_(scan_result);
} else if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_CMPL_EVT) {
// Scan finished on its own
if (this->scanner_state_ != ScannerState::RUNNING) {
@@ -640,9 +627,8 @@ void ESP32BLETracker::dump_config() {
this->scan_duration_, this->scan_interval_ * 0.625f, this->scan_window_ * 0.625f,
this->scan_active_ ? "ACTIVE" : "PASSIVE", YESNO(this->scan_continuous_));
ESP_LOGCONFIG(TAG, " Scanner State: %s", this->scanner_state_to_string_(this->scanner_state_));
ESP_LOGCONFIG(TAG, " Connecting: %d, discovered: %d, searching: %d, disconnecting: %d",
this->client_state_counts_.connecting, this->client_state_counts_.discovered,
this->client_state_counts_.searching, this->client_state_counts_.disconnecting);
ESP_LOGCONFIG(TAG, " Connecting: %d, discovered: %d, disconnecting: %d", this->client_state_counts_.connecting,
this->client_state_counts_.discovered, this->client_state_counts_.disconnecting);
if (this->scan_start_fail_count_) {
ESP_LOGCONFIG(TAG, " Scan Start Fail Count: %d", this->scan_start_fail_count_);
}
@@ -720,20 +706,9 @@ bool ESPBTDevice::resolve_irk(const uint8_t *irk) const {
ecb_ciphertext[13] == ((addr64 >> 16) & 0xff);
}
bool ESP32BLETracker::has_connecting_clients_() const {
for (auto *client : this->clients_) {
auto state = client->state();
if (state == ClientState::CONNECTING || state == ClientState::READY_TO_CONNECT) {
return true;
}
}
return false;
}
#endif // USE_ESP32_BLE_DEVICE
bool ESP32BLETracker::process_scan_result_(const BLEScanResult &scan_result) {
bool found_discovered_client = false;
void ESP32BLETracker::process_scan_result_(const BLEScanResult &scan_result) {
// Process raw advertisements
if (this->raw_advertisements_) {
for (auto *listener : this->listeners_) {
@@ -759,14 +734,6 @@ bool ESP32BLETracker::process_scan_result_(const BLEScanResult &scan_result) {
for (auto *client : this->clients_) {
if (client->parse_device(device)) {
found = true;
// Check if this client is discovered and needs promotion
if (client->state() == ClientState::DISCOVERED) {
// Only check for connecting clients if we found a discovered client
// This matches the original logic: !connecting && client->state() == DISCOVERED
if (!this->has_connecting_clients_()) {
found_discovered_client = true;
}
}
}
}
@@ -775,8 +742,6 @@ bool ESP32BLETracker::process_scan_result_(const BLEScanResult &scan_result) {
}
#endif // USE_ESP32_BLE_DEVICE
}
return found_discovered_client;
}
void ESP32BLETracker::cleanup_scan_state_(bool is_stop_complete) {

View File

@@ -141,12 +141,10 @@ class ESPBTDeviceListener {
struct ClientStateCounts {
uint8_t connecting = 0;
uint8_t discovered = 0;
uint8_t searching = 0;
uint8_t disconnecting = 0;
bool operator==(const ClientStateCounts &other) const {
return connecting == other.connecting && discovered == other.discovered && searching == other.searching &&
disconnecting == other.disconnecting;
return connecting == other.connecting && discovered == other.discovered && disconnecting == other.disconnecting;
}
bool operator!=(const ClientStateCounts &other) const { return !(*this == other); }
@@ -159,8 +157,6 @@ enum class ClientState : uint8_t {
DISCONNECTING,
// Connection is idle, no device detected.
IDLE,
// Searching for device.
SEARCHING,
// Device advertisement found.
DISCOVERED,
// Device is discovered and the scanner is stopped
@@ -292,12 +288,7 @@ class ESP32BLETracker : public Component,
/// Common cleanup logic when transitioning scanner to IDLE state
void cleanup_scan_state_(bool is_stop_complete);
/// Process a single scan result immediately
/// Returns true if a discovered client needs promotion to READY_TO_CONNECT
bool process_scan_result_(const BLEScanResult &scan_result);
#ifdef USE_ESP32_BLE_DEVICE
/// Check if any clients are in connecting or ready to connect state
bool has_connecting_clients_() const;
#endif
void process_scan_result_(const BLEScanResult &scan_result);
/// Handle scanner failure states
void handle_scanner_failure_();
/// Try to promote discovered clients to ready to connect
@@ -321,9 +312,6 @@ class ESP32BLETracker : public Component,
case ClientState::DISCOVERED:
counts.discovered++;
break;
case ClientState::SEARCHING:
counts.searching++;
break;
case ClientState::CONNECTING:
case ClientState::READY_TO_CONNECT:
counts.connecting++;

View File

@@ -17,7 +17,7 @@ from esphome.const import (
PLATFORM_ESP8266,
ThreadModel,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.helpers import copy_file_if_changed
from .boards import BOARDS, ESP8266_LD_SCRIPTS
@@ -176,7 +176,7 @@ CONFIG_SCHEMA = cv.All(
)
@coroutine_with_priority(1000)
@coroutine_with_priority(CoroPriority.PLATFORM)
async def to_code(config):
cg.add(esp8266_ns.setup_preferences())

View File

@@ -58,8 +58,8 @@ extern "C" void resetPins() { // NOLINT
#ifdef USE_ESP8266_EARLY_PIN_INIT
for (int i = 0; i < 16; i++) {
uint8_t mode = ESPHOME_ESP8266_GPIO_INITIAL_MODE[i];
uint8_t level = ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[i];
uint8_t mode = progmem_read_byte(&ESPHOME_ESP8266_GPIO_INITIAL_MODE[i]);
uint8_t level = progmem_read_byte(&ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[i]);
if (mode != 255)
pinMode(i, mode); // NOLINT
if (level != 255)

View File

@@ -17,7 +17,7 @@ from esphome.const import (
CONF_PULLUP,
PLATFORM_ESP8266,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from . import boards
from .const import KEY_BOARD, KEY_ESP8266, KEY_PIN_INITIAL_STATES, esp8266_ns
@@ -188,7 +188,7 @@ async def esp8266_pin_to_code(config):
return var
@coroutine_with_priority(-999.0)
@coroutine_with_priority(CoroPriority.WORKAROUNDS)
async def add_pin_initial_states_array():
# Add includes at the very end, so that they override everything
initial_states: list[PinInitialState] = CORE.data[KEY_ESP8266][
@@ -199,11 +199,11 @@ async def add_pin_initial_states_array():
cg.add_global(
cg.RawExpression(
f"const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_MODE[16] = {{{initial_modes_s}}}"
f"const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_MODE[16] PROGMEM = {{{initial_modes_s}}}"
)
)
cg.add_global(
cg.RawExpression(
f"const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[16] = {{{initial_levels_s}}}"
f"const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[16] PROGMEM = {{{initial_levels_s}}}"
)
)

View File

@@ -1,6 +1,7 @@
#ifdef USE_ESP8266
#include <c_types.h>
#include <cinttypes>
extern "C" {
#include "spi_flash.h"
}
@@ -119,16 +120,16 @@ static bool load_from_rtc(size_t offset, uint32_t *data, size_t len) {
class ESP8266PreferenceBackend : public ESPPreferenceBackend {
public:
size_t offset = 0;
uint32_t type = 0;
uint16_t offset = 0;
uint8_t length_words = 0; // Max 255 words (1020 bytes of data)
bool in_flash = false;
size_t length_words = 0;
bool save(const uint8_t *data, size_t len) override {
if (bytes_to_words(len) != length_words) {
return false;
}
size_t buffer_size = length_words + 1;
size_t buffer_size = static_cast<size_t>(length_words) + 1;
std::unique_ptr<uint32_t[]> buffer(new uint32_t[buffer_size]()); // Note the () for zero-initialization
memcpy(buffer.get(), data, len);
buffer[length_words] = calculate_crc(buffer.get(), buffer.get() + length_words, type);
@@ -142,7 +143,7 @@ class ESP8266PreferenceBackend : public ESPPreferenceBackend {
if (bytes_to_words(len) != length_words) {
return false;
}
size_t buffer_size = length_words + 1;
size_t buffer_size = static_cast<size_t>(length_words) + 1;
std::unique_ptr<uint32_t[]> buffer(new uint32_t[buffer_size]());
bool ret = in_flash ? load_from_flash(offset, buffer.get(), buffer_size)
: load_from_rtc(offset, buffer.get(), buffer_size);
@@ -176,15 +177,19 @@ class ESP8266Preferences : public ESPPreferences {
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override {
uint32_t length_words = bytes_to_words(length);
if (length_words > 255) {
ESP_LOGE(TAG, "Preference too large: %" PRIu32 " words > 255", length_words);
return {};
}
if (in_flash) {
uint32_t start = current_flash_offset;
uint32_t end = start + length_words + 1;
if (end > ESP8266_FLASH_STORAGE_SIZE)
return {};
auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
pref->offset = start;
pref->offset = static_cast<uint16_t>(start);
pref->type = type;
pref->length_words = length_words;
pref->length_words = static_cast<uint8_t>(length_words);
pref->in_flash = true;
current_flash_offset = end;
return {pref};
@@ -210,9 +215,9 @@ class ESP8266Preferences : public ESPPreferences {
uint32_t rtc_offset = in_normal ? start + 32 : start - 96;
auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
pref->offset = rtc_offset;
pref->offset = static_cast<uint16_t>(rtc_offset);
pref->type = type;
pref->length_words = length_words;
pref->length_words = static_cast<uint8_t>(length_words);
pref->in_flash = false;
current_offset += length_words + 1;
return pref;

View File

@@ -16,7 +16,7 @@ from esphome.const import (
CONF_SAFE_MODE,
CONF_VERSION,
)
from esphome.core import coroutine_with_priority
from esphome.core import CoroPriority, coroutine_with_priority
import esphome.final_validate as fv
_LOGGER = logging.getLogger(__name__)
@@ -121,7 +121,7 @@ CONFIG_SCHEMA = (
FINAL_VALIDATE_SCHEMA = ota_esphome_final_validate
@coroutine_with_priority(52.0)
@coroutine_with_priority(CoroPriority.COMMUNICATION)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_port(config[CONF_PORT]))

View File

@@ -30,19 +30,19 @@ void ESPHomeOTAComponent::setup() {
this->server_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections
if (this->server_ == nullptr) {
this->log_socket_error_("creation");
this->log_socket_error_(LOG_STR("creation"));
this->mark_failed();
return;
}
int enable = 1;
int err = this->server_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
if (err != 0) {
this->log_socket_error_("reuseaddr");
this->log_socket_error_(LOG_STR("reuseaddr"));
// we can still continue
}
err = this->server_->setblocking(false);
if (err != 0) {
this->log_socket_error_("non-blocking");
this->log_socket_error_(LOG_STR("non-blocking"));
this->mark_failed();
return;
}
@@ -51,21 +51,21 @@ void ESPHomeOTAComponent::setup() {
socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->port_);
if (sl == 0) {
this->log_socket_error_("set sockaddr");
this->log_socket_error_(LOG_STR("set sockaddr"));
this->mark_failed();
return;
}
err = this->server_->bind((struct sockaddr *) &server, sizeof(server));
if (err != 0) {
this->log_socket_error_("bind");
this->log_socket_error_(LOG_STR("bind"));
this->mark_failed();
return;
}
err = this->server_->listen(4);
if (err != 0) {
this->log_socket_error_("listen");
this->log_socket_error_(LOG_STR("listen"));
this->mark_failed();
return;
}
@@ -100,8 +100,8 @@ void ESPHomeOTAComponent::handle_handshake_() {
/// Handle the initial OTA handshake.
///
/// This method is non-blocking and will return immediately if no data is available.
/// It waits for the first magic byte (0x6C) before proceeding to handle_data_().
/// A 10-second timeout is enforced from initial connection.
/// It reads all 5 magic bytes (0x6C, 0x26, 0xF7, 0x5C, 0x45) non-blocking
/// before proceeding to handle_data_(). A 10-second timeout is enforced from initial connection.
if (this->client_ == nullptr) {
// We already checked server_->ready() in loop(), so we can accept directly
@@ -114,18 +114,19 @@ void ESPHomeOTAComponent::handle_handshake_() {
return;
int err = this->client_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
if (err != 0) {
this->log_socket_error_("nodelay");
this->log_socket_error_(LOG_STR("nodelay"));
this->cleanup_connection_();
return;
}
err = this->client_->setblocking(false);
if (err != 0) {
this->log_socket_error_("non-blocking");
this->log_socket_error_(LOG_STR("non-blocking"));
this->cleanup_connection_();
return;
}
this->log_start_("handshake");
this->log_start_(LOG_STR("handshake"));
this->client_connect_time_ = App.get_loop_component_start_time();
this->magic_buf_pos_ = 0; // Reset magic buffer position
}
// Check for handshake timeout
@@ -136,34 +137,47 @@ void ESPHomeOTAComponent::handle_handshake_() {
return;
}
// Try to read first byte of magic bytes
uint8_t first_byte;
ssize_t read = this->client_->read(&first_byte, 1);
// Try to read remaining magic bytes
if (this->magic_buf_pos_ < 5) {
// Read as many bytes as available
uint8_t bytes_to_read = 5 - this->magic_buf_pos_;
ssize_t read = this->client_->read(this->magic_buf_ + this->magic_buf_pos_, bytes_to_read);
if (read == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
return; // No data yet, try again next loop
}
if (read <= 0) {
// Error or connection closed
if (read == -1) {
this->log_socket_error_("reading first byte");
} else {
ESP_LOGW(TAG, "Remote closed during handshake");
if (read == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
return; // No data yet, try again next loop
}
this->cleanup_connection_();
return;
if (read <= 0) {
// Error or connection closed
if (read == -1) {
this->log_socket_error_(LOG_STR("reading magic bytes"));
} else {
ESP_LOGW(TAG, "Remote closed during handshake");
}
this->cleanup_connection_();
return;
}
this->magic_buf_pos_ += read;
}
// Got first byte, check if it's the magic byte
if (first_byte != 0x6C) {
ESP_LOGW(TAG, "Invalid initial byte: 0x%02X", first_byte);
this->cleanup_connection_();
return;
}
// Check if we have all 5 magic bytes
if (this->magic_buf_pos_ == 5) {
// Validate magic bytes
static const uint8_t MAGIC_BYTES[5] = {0x6C, 0x26, 0xF7, 0x5C, 0x45};
if (memcmp(this->magic_buf_, MAGIC_BYTES, 5) != 0) {
ESP_LOGW(TAG, "Magic bytes mismatch! 0x%02X-0x%02X-0x%02X-0x%02X-0x%02X", this->magic_buf_[0],
this->magic_buf_[1], this->magic_buf_[2], this->magic_buf_[3], this->magic_buf_[4]);
// Send error response (non-blocking, best effort)
uint8_t error = static_cast<uint8_t>(ota::OTA_RESPONSE_ERROR_MAGIC);
this->client_->write(&error, 1);
this->cleanup_connection_();
return;
}
// First byte is valid, continue with data handling
this->handle_data_();
// All 5 magic bytes are valid, continue with data handling
this->handle_data_();
}
}
void ESPHomeOTAComponent::handle_data_() {
@@ -186,18 +200,6 @@ void ESPHomeOTAComponent::handle_data_() {
size_t size_acknowledged = 0;
#endif
// Read remaining 4 bytes of magic (we already read the first byte 0x6C in handle_handshake_)
if (!this->readall_(buf, 4)) {
this->log_read_error_("magic bytes");
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
}
// Check remaining magic bytes: 0x26, 0xF7, 0x5C, 0x45
if (buf[0] != 0x26 || buf[1] != 0xF7 || buf[2] != 0x5C || buf[3] != 0x45) {
ESP_LOGW(TAG, "Magic bytes mismatch! 0x6C-0x%02X-0x%02X-0x%02X-0x%02X", buf[0], buf[1], buf[2], buf[3]);
error_code = ota::OTA_RESPONSE_ERROR_MAGIC;
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
}
// Send OK and version - 2 bytes
buf[0] = ota::OTA_RESPONSE_OK;
buf[1] = USE_OTA_VERSION;
@@ -207,7 +209,7 @@ void ESPHomeOTAComponent::handle_data_() {
// Read features - 1 byte
if (!this->readall_(buf, 1)) {
this->log_read_error_("features");
this->log_read_error_(LOG_STR("features"));
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
}
ota_features = buf[0]; // NOLINT
@@ -286,7 +288,7 @@ void ESPHomeOTAComponent::handle_data_() {
// Read size, 4 bytes MSB first
if (!this->readall_(buf, 4)) {
this->log_read_error_("size");
this->log_read_error_(LOG_STR("size"));
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
}
ota_size = 0;
@@ -300,7 +302,7 @@ void ESPHomeOTAComponent::handle_data_() {
// starting the update, set the warning status and notify
// listeners. This ensures that port scanners do not
// accidentally trigger the update process.
this->log_start_("update");
this->log_start_(LOG_STR("update"));
this->status_set_warning();
#ifdef USE_OTA_STATE_CALLBACK
this->state_callback_.call(ota::OTA_STARTED, 0.0f, 0);
@@ -318,7 +320,7 @@ void ESPHomeOTAComponent::handle_data_() {
// Read binary MD5, 32 bytes
if (!this->readall_(buf, 32)) {
this->log_read_error_("MD5 checksum");
this->log_read_error_(LOG_STR("MD5 checksum"));
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
}
sbuf[32] = '\0';
@@ -391,7 +393,7 @@ void ESPHomeOTAComponent::handle_data_() {
// Read ACK
if (!this->readall_(buf, 1) || buf[0] != ota::OTA_RESPONSE_OK) {
this->log_read_error_("ack");
this->log_read_error_(LOG_STR("ack"));
// do not go to error, this is not fatal
}
@@ -475,18 +477,21 @@ float ESPHomeOTAComponent::get_setup_priority() const { return setup_priority::A
uint16_t ESPHomeOTAComponent::get_port() const { return this->port_; }
void ESPHomeOTAComponent::set_port(uint16_t port) { this->port_ = port; }
void ESPHomeOTAComponent::log_socket_error_(const char *msg) { ESP_LOGW(TAG, "Socket %s: errno %d", msg, errno); }
void ESPHomeOTAComponent::log_socket_error_(const LogString *msg) {
ESP_LOGW(TAG, "Socket %s: errno %d", LOG_STR_ARG(msg), errno);
}
void ESPHomeOTAComponent::log_read_error_(const char *what) { ESP_LOGW(TAG, "Read %s failed", what); }
void ESPHomeOTAComponent::log_read_error_(const LogString *what) { ESP_LOGW(TAG, "Read %s failed", LOG_STR_ARG(what)); }
void ESPHomeOTAComponent::log_start_(const char *phase) {
ESP_LOGD(TAG, "Starting %s from %s", phase, this->client_->getpeername().c_str());
void ESPHomeOTAComponent::log_start_(const LogString *phase) {
ESP_LOGD(TAG, "Starting %s from %s", LOG_STR_ARG(phase), this->client_->getpeername().c_str());
}
void ESPHomeOTAComponent::cleanup_connection_() {
this->client_->close();
this->client_ = nullptr;
this->client_connect_time_ = 0;
this->magic_buf_pos_ = 0;
}
void ESPHomeOTAComponent::yield_and_feed_watchdog_() {

View File

@@ -2,10 +2,11 @@
#include "esphome/core/defines.h"
#ifdef USE_OTA
#include "esphome/core/helpers.h"
#include "esphome/core/preferences.h"
#include "esphome/components/ota/ota_backend.h"
#include "esphome/components/socket/socket.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/preferences.h"
namespace esphome {
@@ -31,9 +32,9 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
void handle_data_();
bool readall_(uint8_t *buf, size_t len);
bool writeall_(const uint8_t *buf, size_t len);
void log_socket_error_(const char *msg);
void log_read_error_(const char *what);
void log_start_(const char *phase);
void log_socket_error_(const LogString *msg);
void log_read_error_(const LogString *what);
void log_start_(const LogString *phase);
void cleanup_connection_();
void yield_and_feed_watchdog_();
@@ -41,11 +42,13 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
std::string password_;
#endif // USE_OTA_PASSWORD
uint16_t port_;
uint32_t client_connect_time_{0};
std::unique_ptr<socket::Socket> server_;
std::unique_ptr<socket::Socket> client_;
uint32_t client_connect_time_{0};
uint16_t port_;
uint8_t magic_buf_[5];
uint8_t magic_buf_pos_{0};
};
} // namespace esphome

View File

@@ -38,7 +38,12 @@ from esphome.const import (
KEY_CORE,
KEY_FRAMEWORK_VERSION,
)
from esphome.core import CORE, TimePeriodMilliseconds, coroutine_with_priority
from esphome.core import (
CORE,
CoroPriority,
TimePeriodMilliseconds,
coroutine_with_priority,
)
import esphome.final_validate as fv
CONFLICTS_WITH = ["wifi"]
@@ -289,7 +294,7 @@ def phy_register(address: int, value: int, page: int):
)
@coroutine_with_priority(60.0)
@coroutine_with_priority(CoroPriority.COMMUNICATION)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)

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