Compare commits

..

178 Commits

Author SHA1 Message Date
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
J. Nick Koston
a92a08c2de [api] Fix string lifetime issue in fill_and_encode_entity_info for dynamic object_id (#10482) 2025-08-28 18:40:36 +00:00
Vinicius Fortuna
75595b08be [rtttl] Fix RTTTL for speakers (#10381) 2025-08-28 13:53:57 +12:00
J. Nick Koston
3c7aba0681 Fix AttributeError when uploading OTA to offline OpenThread devices (#10459) 2025-08-28 09:23:43 +12:00
Clyde Stubbs
e5d1c30797 [wifi] Fix retry with hidden networks. (#10445) 2025-08-28 09:16:26 +12:00
Clyde Stubbs
c171d13c8c [i2c] Perform register reads as single transactions (#10389)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-08-27 19:30:33 +10:00
Clyde Stubbs
65d63de9b6 [mipi_spi] Fix dimensions (#10443) 2025-08-27 19:30:01 +10:00
J. Nick Koston
9e712e4127 [wifi] Fix reconnection failures after adapter restart by not clearing netif pointers (#10458) 2025-08-26 23:49:47 -05:00
Clyde Stubbs
9007621fd7 Revert "[core] Dont copy platform source files if there are no entities of that type" (#10441) 2025-08-26 09:15:44 +10:00
Thomas Rupprecht
c01a26607e improve const imports of esphome.const (#10438) 2025-08-26 09:45:03 +12:00
Jesse Hills
f6ca70970f Merge branch 'release' into dev 2025-08-26 08:48:51 +12:00
Jesse Hills
4dc11f05a7 Merge pull request #10427 from esphome/bump-2025.8.1
2025.8.1
2025-08-26 08:48:10 +12:00
Jesse Hills
5e508f7461 [core] Dont copy platform source files if there are no entities of that type (#10436) 2025-08-25 14:46:54 -05:00
Jonathan Rascher
2aceb56606 Merge commit from fork
Ensures auth check doesn't pass erroneously when the client-supplied
digest is shorter than the correct digest, but happens to match a
prefix of the correct value (e.g., same username + certain substrings of
the password).
2025-08-25 16:00:04 +12:00
Jesse Hills
d071a074ef Bump version to 2025.8.1 2025-08-25 15:59:35 +12:00
Clyde Stubbs
7a459c8c20 [web_server] Use oi.esphome.io for css and js assets (#10296) 2025-08-25 15:59:35 +12:00
J. Nick Koston
aebd21958a [test] Add integration test for light effect memory corruption fix (#10417) 2025-08-25 15:59:35 +12:00
J. Nick Koston
c542db8bfe [esp32_ble_tracker] Fix on_scan_end trigger compilation without USE_ESP32_BLE_DEVICE (#10399) 2025-08-25 15:59:35 +12:00
Clyde Stubbs
d9dcfe66ec [lvgl] Fix meter rotation (#10342) 2025-08-25 15:59:35 +12:00
J. Nick Koston
8517c2e903 [esp32_ble_client] Reduce log level for harmless BLE timeout race conditions (#10339)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-25 15:59:34 +12:00
J. Nick Koston
684384892a [deep_sleep] Fix ESP32-C6 compilation error with gpio_deep_sleep_hold_en() (#10345) 2025-08-25 15:59:34 +12:00
J. Nick Koston
d560831d79 [script] Fix parallel mode scripts with delays cancelling each other (#10324) 2025-08-25 15:59:34 +12:00
J. Nick Koston
fcc3c8e1b6 [esp32_ble] Increase GATT connection retry count to use full timeout window (#10376) 2025-08-25 15:59:34 +12:00
J. Nick Koston
959ffde60e [esp32_ble_client] Optimize BLE connection parameters for different connection types (#10356) 2025-08-25 15:59:34 +12:00
J. Nick Koston
07715dd50f [pvvx_mithermometer] Fix race condition with BLE authentication (#10327) 2025-08-25 15:59:34 +12:00
J. Nick Koston
03836ee2d2 [core] Improve error reporting for entity name conflicts with non-ASCII characters (#10329) 2025-08-25 15:59:34 +12:00
Clyde Stubbs
50408d9abb [http_request] Fix for host after ArduinoJson library bump (#10348) 2025-08-25 15:59:34 +12:00
Jesse Hills
0de7259428 [api] Add `USE_API_HOMEASSISTANT_SERVICES if using tag_scanned` action (#10316) 2025-08-25 15:59:34 +12:00
J. Nick Koston
d054709c2d [esp32_ble_client] Add log helper functions to reduce flash usage by 120 bytes (#10243) 2025-08-25 15:59:34 +12:00
J. Nick Koston
da16887915 [api] Add zero-copy StringRef methods for compilation_time and effect_name (#10257) 2025-08-25 15:59:34 +12:00
Jonathan Rascher
6da8ec8d55 Merge commit from fork
Ensures auth check doesn't pass erroneously when the client-supplied
digest is shorter than the correct digest, but happens to match a
prefix of the correct value (e.g., same username + certain substrings of
the password).
2025-08-25 15:40:19 +12:00
J. Nick Koston
d2752b38c9 [core] Fix preference storage to account for device_id (#10333)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-25 12:22:16 +12:00
J. Nick Koston
6004367ee2 [esp32_ble_client] Add missing ESP_GATTC_UNREG_FOR_NOTIFY_EVT logging (#10347) 2025-08-25 12:07:04 +12:00
Thomas Rupprecht
ecfeb8e4d3 improve AI instructions (#10416) 2025-08-25 11:51:28 +12:00
Clyde Stubbs
456c31262d [web_server] Use oi.esphome.io for css and js assets (#10296) 2025-08-25 09:04:32 +12:00
J. Nick Koston
9f02575287 [test] Add integration test for light effect memory corruption fix (#10417) 2025-08-25 08:58:46 +12:00
J. Nick Koston
07bca6103f [esp32_ble_tracker] Fix on_scan_end trigger compilation without USE_ESP32_BLE_DEVICE (#10399) 2025-08-25 08:57:09 +12:00
Clyde Stubbs
a58c3950bc [lvgl] Fix meter rotation (#10342) 2025-08-25 06:52:37 +10:00
J. Nick Koston
8fe582309e [esp32_ble_client] Reduce log level for harmless BLE timeout race conditions (#10339)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-25 08:51:54 +12:00
J. Nick Koston
b41a61c76e [deep_sleep] Fix ESP32-C6 compilation error with gpio_deep_sleep_hold_en() (#10345) 2025-08-25 08:51:23 +12:00
J. Nick Koston
61a5023888 [script] Fix parallel mode scripts with delays cancelling each other (#10324) 2025-08-25 08:49:52 +12:00
J. Nick Koston
4396bc0d1a [esp32_ble] Increase GATT connection retry count to use full timeout window (#10376) 2025-08-25 08:49:37 +12:00
J. Nick Koston
acfce581fa [esp32_ble_client] Optimize BLE connection parameters for different connection types (#10356) 2025-08-25 08:17:26 +12:00
J. Nick Koston
88303f39fa [pvvx_mithermometer] Fix race condition with BLE authentication (#10327) 2025-08-25 08:16:12 +12:00
J. Nick Koston
ca19959d7c [core] Improve error reporting for entity name conflicts with non-ASCII characters (#10329) 2025-08-25 08:11:54 +12:00
Clyde Stubbs
9737b35579 [http_request] Fix for host after ArduinoJson library bump (#10348) 2025-08-25 07:55:44 +12:00
Clyde Stubbs
be9c20c357 [mipi_spi] Add model (#10392) 2025-08-25 07:52:52 +12:00
Thomas Rupprecht
12ba4b142e Update Python to 3.11 in AI instructions (#10407) 2025-08-24 21:03:14 +12:00
Thomas Rupprecht
c096c6934d fix temperature config validation regex (#9575)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-08-24 08:56:06 +00:00
tomaszduda23
17f787fc36 [nrf52] fix build in dashboard (#10323) 2025-08-23 12:17:42 +00:00
tomaszduda23
5cd9a86dcb [nrf52] update toolchain to v0.17.4, support mac (#10391) 2025-08-23 16:20:16 +10:00
dependabot[bot]
83fe4b4ff3 Bump ruff from 0.12.9 to 0.12.10 (#10362)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-08-21 15:36:06 -05:00
Jesse Hills
94accd5abe [ld2420] Rename c++ files for predictable doxygen generation (#10315) 2025-08-21 18:49:26 +12:00
Jesse Hills
3ca0015284 [opentherm] Rename c++ files for predictable doxygen generation (#10314) 2025-08-21 18:48:48 +12:00
dependabot[bot]
33eddb6035 Bump codecov/codecov-action from 5.4.3 to 5.5.0 (#10336)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-20 16:46:04 -05:00
Jesse Hills
72c58ae36d [core] Add idf-tidy env for esp32-c6 (#10270) 2025-08-20 10:13:50 -04:00
J. Nick Koston
35411d199f [homeassistant] Add compilation test for homeassistant.tag_scanned action (#10319) 2025-08-20 10:10:20 -04:00
Jesse Hills
d45944a9e2 [api] Add `USE_API_HOMEASSISTANT_SERVICES if using tag_scanned` action (#10316) 2025-08-20 06:47:20 -05:00
Jesse Hills
86f306ba9e [CI] Also require tests for `new-features` (#10311) 2025-08-20 22:02:14 +12:00
Jesse Hills
1b3b2f6e6f Merge branch 'release' into dev 2025-08-20 19:58:48 +12:00
Jesse Hills
2adb993242 Merge pull request #10309 from esphome/bump-2025.8.0
2025.8.0
2025-08-20 19:58:01 +12:00
J. Nick Koston
3ff5b4773b [bluetooth_proxy] Mark BluetoothConnection and BluetoothProxy as final for compiler optimizations (#10280) 2025-08-20 14:48:40 +12:00
J. Nick Koston
2cbf4f30f9 [libretiny] Optimize preferences is_changed() by replacing temporary vector with unique_ptr (#10272) 2025-08-20 14:48:04 +12:00
J. Nick Koston
56b6dd31f1 [core] Eliminate heap allocation in teardown_components by using StaticVector (#10256)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-20 14:45:13 +12:00
dependabot[bot]
fc1b49e87d Bump ruamel-yaml from 0.18.14 to 0.18.15 (#10310)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-20 14:42:33 +12:00
J. Nick Koston
0089619518 [web_server] Reduce flash usage by consolidating defer calls in switch and lock handlers (#10297) 2025-08-19 21:41:34 -05:00
Jesse Hills
5a6db28f1d [CI] Base `too-big` label on new additions only (#10307) 2025-08-20 14:39:29 +12:00
J. Nick Koston
6819bbd8f8 [esp32_ble_client] Add log helper functions to reduce flash usage by 120 bytes (#10243) 2025-08-20 14:38:32 +12:00
Edward Firmo
634f687c3e [light] Add support for querying effects by index (#10195)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-08-20 14:38:13 +12:00
J. Nick Koston
e2a9b85924 [number] Convert LOG_NUMBER macro to function to reduce flash usage (#10293) 2025-08-20 14:36:05 +12:00
J. Nick Koston
4ccc6aee09 [button] Convert LOG_BUTTON macro to function to reduce flash usage (#10295)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-20 14:35:53 +12:00
J. Nick Koston
0eab908b0e [sensor] Convert LOG_SENSOR macro to function to reduce flash usage (#10290) 2025-08-20 14:35:45 +12:00
J. Nick Koston
3964f9794b [binary_sensor] Convert LOG_BINARY_SENSOR macro to function to reduce flash usage (#10294) 2025-08-20 14:35:09 +12:00
Jesse Hills
a45137434b [quality] Convert remaining `to_code to async` (#10271) 2025-08-20 14:34:45 +12:00
J. Nick Koston
9b1ebdb6da [mdns] Reduce flash usage and prevent RAM over-allocation in service compilation (#10287) 2025-08-20 14:34:34 +12:00
J. Nick Koston
5a1533bea9 [api] Avoid object_id string allocations for all entity info messages (#10260) 2025-08-20 14:28:13 +12:00
Jesse Hills
0b50ef227b [helper] Make crc8 function more flexible to avoid reimplementation in individual components (#10201) 2025-08-20 14:27:08 +12:00
J. Nick Koston
0e31bc1a67 [api] Add zero-copy StringRef methods for compilation_time and effect_name (#10257) 2025-08-20 14:26:53 +12:00
Jesse Hills
8e67df8059 Bump version to 2025.8.0 2025-08-20 10:45:57 +12:00
Jesse Hills
e1a0949ddb Merge branch 'beta' into dev 2025-08-20 10:31:10 +12:00
Jesse Hills
c5b2c8d971 Merge pull request #10308 from esphome/bump-2025.8.0b4
2025.8.0b4
2025-08-20 10:30:37 +12:00
J. Nick Koston
a8775ba60b [safe_mode] Reduce flash usage by 184 bytes through code optimization (#10284) 2025-08-19 16:57:24 -05:00
Jesse Hills
104906ca11 Bump version to 2025.8.0b4 2025-08-20 09:40:19 +12:00
J. Nick Koston
ad5f6f0cfe [bluetooth_proxy] Fix connection slot race by deferring slot release until GATT close (#10303) 2025-08-20 09:40:19 +12:00
Patrick
8356f7fcd3 [pipsolar] fix faults_present, fix update interval (#10289) 2025-08-20 09:40:19 +12:00
Ben Winslow
225de226b0 [atm90e32] Only read 1 register per SPI transaction per datasheet. (#10258) 2025-08-20 09:40:19 +12:00
J. Nick Koston
2aaf951357 [bluetooth_proxy] Fix connection slot race by deferring slot release until GATT close (#10303) 2025-08-20 07:27:22 +12:00
Jesse Hills
82718e62e7 Merge branch 'beta' into dev 2025-08-19 20:40:45 +12:00
Jesse Hills
fd07e1d979 Merge pull request #10298 from esphome/bump-2025.8.0b3
2025.8.0b3
2025-08-19 20:40:12 +12:00
Patrick
4dab9c4400 [pipsolar] fix faults_present, fix update interval (#10289) 2025-08-19 15:52:01 +12:00
Ben Winslow
7e23d865e6 [atm90e32] Only read 1 register per SPI transaction per datasheet. (#10258) 2025-08-19 15:45:30 +12:00
Jesse Hills
8f118232e4 [CI] Rename and expand needs-docs workflow (#10299) 2025-08-19 15:35:48 +12:00
Jesse Hills
23554cda06 Bump version to 2025.8.0b3 2025-08-19 13:09:22 +12:00
Ben Winslow
064385eac6 [nextion] Don't include terminating NUL in nextion text_sensor states (#10273) 2025-08-19 13:09:22 +12:00
Jesse Hills
6502ed70de [esp32] Write variant to sdkconfig file (#10267) 2025-08-19 13:09:22 +12:00
J. Nick Koston
bb894c3e32 [core] Fix scheduler race condition where cancelled items still execute (#10268) 2025-08-19 13:09:22 +12:00
Ben Winslow
c5858b7032 [core] Fix post-OTA logs display when using esphome run and MQTT (#10274) 2025-08-19 13:09:22 +12:00
Ben Winslow
99f57ecb73 [senseair] Discard 0 ppm readings with "Out Of Range" bit set. (#10275) 2025-08-19 13:09:22 +12:00
J. Nick Koston
cc6c892678 [esp32_ble] Store GATTC/GATTS param and small data inline to nearly eliminate heap allocations (#10249)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-19 13:09:22 +12:00
RFDarter
07a98d2525 [web_server] fix cover_all_json_generator wrong detail (#10252) 2025-08-19 13:09:22 +12:00
J. Nick Koston
e80f616366 [esp32_ble] Optimize BLE event memory usage by eliminating std::vector overhead (#10247)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-19 13:09:22 +12:00
J. Nick Koston
46be877594 [bluetooth_proxy] Remove redundant connection type check after V1 removal (#10208) 2025-08-19 13:09:21 +12:00
J. Nick Koston
ac8b48a53c [core] Trigger clean build when components are removed from configuration (#10235)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-19 13:09:21 +12:00
J. Nick Koston
7fdbd8528a [wifi] Automatically disable Enterprise WiFi support when EAP is not configured (#10242) 2025-08-19 13:09:21 +12:00
Katherine Whitlock
80970f972b Improve error reporting for add_library (#10226)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-19 13:09:21 +12:00
Jesse Hills
3c7865cd6f [esp32_ble] Add `USE_ESP32_BLE_UUID` when advertising is desired (#10230) 2025-08-19 13:09:21 +12:00
Ben Winslow
3a6a66537c [nextion] Don't include terminating NUL in nextion text_sensor states (#10273) 2025-08-18 19:20:13 -05:00
Jesse Hills
7118bea031 [esp32] Write variant to sdkconfig file (#10267) 2025-08-19 12:17:34 +12:00
J. Nick Koston
44bd8e5b54 [api] Optimize protobuf decode loop for better performance and maintainability (#10277) 2025-08-18 16:14:20 -05:00
J. Nick Koston
efaeb91803 [api] Mark APIConnection as final for compiler optimizations (#10279) 2025-08-18 16:01:45 -05:00
J. Nick Koston
761c6c6685 [api] Mark protobuf message classes as final to enable compiler optimizations (#10276) 2025-08-18 15:55:30 -05:00
J. Nick Koston
1f55486896 [api] Optimize APIFrameHelper virtual methods and mark implementations as final (#10278) 2025-08-18 15:55:11 -05:00
J. Nick Koston
6818439109 [core] Fix scheduler race condition where cancelled items still execute (#10268) 2025-08-18 11:14:41 -04:00
J. Nick Koston
0a77423073 [esp8266] Replace std::vector with std::unique_ptr in preferences to save flash (#10245) 2025-08-18 09:01:39 -04:00
Ben Winslow
c29f8d0187 [core] Fix post-OTA logs display when using esphome run and MQTT (#10274) 2025-08-17 21:36:35 -05:00
Ben Winslow
2a3f80a82c [senseair] Discard 0 ppm readings with "Out Of Range" bit set. (#10275) 2025-08-18 14:09:42 +12:00
J. Nick Koston
75f3adcd95 [esp32_ble] Store GATTC/GATTS param and small data inline to nearly eliminate heap allocations (#10249)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-18 07:49:50 +12:00
328 changed files with 5341 additions and 2238 deletions

View File

@@ -9,7 +9,7 @@ This document provides essential context for AI models interacting with this pro
## 2. Core Technologies & Stack ## 2. Core Technologies & Stack
* **Languages:** Python (>=3.10), C++ (gnu++20) * **Languages:** Python (>=3.11), C++ (gnu++20)
* **Frameworks & Runtimes:** PlatformIO, Arduino, ESP-IDF. * **Frameworks & Runtimes:** PlatformIO, Arduino, ESP-IDF.
* **Build Systems:** PlatformIO is the primary build system. CMake is used as an alternative. * **Build Systems:** PlatformIO is the primary build system. CMake is used as an alternative.
* **Configuration:** YAML. * **Configuration:** YAML.
@@ -38,7 +38,7 @@ This document provides essential context for AI models interacting with this pro
5. **Dashboard** (`esphome/dashboard/`): A web-based interface for device configuration, management, and OTA updates. 5. **Dashboard** (`esphome/dashboard/`): A web-based interface for device configuration, management, and OTA updates.
* **Platform Support:** * **Platform Support:**
1. **ESP32** (`components/esp32/`): Espressif ESP32 family. Supports multiple variants (S2, S3, C3, etc.) and both IDF and Arduino frameworks. 1. **ESP32** (`components/esp32/`): Espressif ESP32 family. Supports multiple variants (Original, C2, C3, C5, C6, H2, P4, S2, S3) with ESP-IDF framework. Arduino framework supports only a subset of the variants (Original, C3, S2, S3).
2. **ESP8266** (`components/esp8266/`): Espressif ESP8266. Arduino framework only, with memory constraints. 2. **ESP8266** (`components/esp8266/`): Espressif ESP8266. Arduino framework only, with memory constraints.
3. **RP2040** (`components/rp2040/`): Raspberry Pi Pico/RP2040. Arduino framework with PIO (Programmable I/O) support. 3. **RP2040** (`components/rp2040/`): Raspberry Pi Pico/RP2040. Arduino framework with PIO (Programmable I/O) support.
4. **LibreTiny** (`components/libretiny/`): Realtek and Beken chips. Supports multiple chip families and auto-generated components. 4. **LibreTiny** (`components/libretiny/`): Realtek and Beken chips. Supports multiple chip families and auto-generated components.
@@ -150,7 +150,8 @@ This document provides essential context for AI models interacting with this pro
* **Configuration Validation:** * **Configuration Validation:**
* **Common Validators:** `cv.int_`, `cv.float_`, `cv.string`, `cv.boolean`, `cv.int_range(min=0, max=100)`, `cv.positive_int`, `cv.percentage`. * **Common Validators:** `cv.int_`, `cv.float_`, `cv.string`, `cv.boolean`, `cv.int_range(min=0, max=100)`, `cv.positive_int`, `cv.percentage`.
* **Complex Validation:** `cv.All(cv.string, cv.Length(min=1, max=50))`, `cv.Any(cv.int_, cv.string)`. * **Complex Validation:** `cv.All(cv.string, cv.Length(min=1, max=50))`, `cv.Any(cv.int_, cv.string)`.
* **Platform-Specific:** `cv.only_on(["esp32", "esp8266"])`, `cv.only_with_arduino`. * **Platform-Specific:** `cv.only_on(["esp32", "esp8266"])`, `esp32.only_on_variant(...)`, `cv.only_on_esp32`, `cv.only_on_esp8266`, `cv.only_on_rp2040`.
* **Framework-Specific:** `cv.only_with_framework(...)`, `cv.only_with_arduino`, `cv.only_with_esp_idf`.
* **Schema Extensions:** * **Schema Extensions:**
```python ```python
CONFIG_SCHEMA = cv.Schema({ ... }) CONFIG_SCHEMA = cv.Schema({ ... })

View File

@@ -1 +1 @@
6af8b429b94191fe8e239fcb3b73f7982d0266cb5b05ffbc81edaeac1bc8c273 4368db58e8f884aff245996b1e8b644cc0796c0bb2fa706d5740d40b823d3ac9

View File

@@ -105,7 +105,9 @@ jobs:
// Calculate data from PR files // Calculate data from PR files
const changedFiles = prFiles.map(file => file.filename); const changedFiles = prFiles.map(file => file.filename);
const totalChanges = prFiles.reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0); const totalAdditions = prFiles.reduce((sum, file) => sum + (file.additions || 0), 0);
const totalDeletions = prFiles.reduce((sum, file) => sum + (file.deletions || 0), 0);
const totalChanges = totalAdditions + totalDeletions;
console.log('Current labels:', currentLabels.join(', ')); console.log('Current labels:', currentLabels.join(', '));
console.log('Changed files:', changedFiles.length); console.log('Changed files:', changedFiles.length);
@@ -231,16 +233,21 @@ jobs:
// Strategy: PR size detection // Strategy: PR size detection
async function detectPRSize() { async function detectPRSize() {
const labels = new Set(); const labels = new Set();
const testChanges = prFiles
.filter(file => file.filename.startsWith('tests/'))
.reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0);
const nonTestChanges = totalChanges - testChanges;
if (totalChanges <= SMALL_PR_THRESHOLD) { if (totalChanges <= SMALL_PR_THRESHOLD) {
labels.add('small-pr'); labels.add('small-pr');
return labels;
} }
const testAdditions = prFiles
.filter(file => file.filename.startsWith('tests/'))
.reduce((sum, file) => sum + (file.additions || 0), 0);
const testDeletions = prFiles
.filter(file => file.filename.startsWith('tests/'))
.reduce((sum, file) => sum + (file.deletions || 0), 0);
const nonTestChanges = (totalAdditions - testAdditions) - (totalDeletions - testDeletions);
// Don't add too-big if mega-pr label is already present // Don't add too-big if mega-pr label is already present
if (nonTestChanges > TOO_BIG_THRESHOLD && !isMegaPR) { if (nonTestChanges > TOO_BIG_THRESHOLD && !isMegaPR) {
labels.add('too-big'); labels.add('too-big');
@@ -375,7 +382,7 @@ jobs:
const labels = new Set(); const labels = new Set();
// Check for missing tests // Check for missing tests
if ((allLabels.has('new-component') || allLabels.has('new-platform')) && !allLabels.has('has-tests')) { if ((allLabels.has('new-component') || allLabels.has('new-platform') || allLabels.has('new-feature')) && !allLabels.has('has-tests')) {
labels.add('needs-tests'); labels.add('needs-tests');
} }
@@ -412,10 +419,13 @@ jobs:
// Too big message // Too big message
if (finalLabels.includes('too-big')) { if (finalLabels.includes('too-big')) {
const testChanges = prFiles const testAdditions = prFiles
.filter(file => file.filename.startsWith('tests/')) .filter(file => file.filename.startsWith('tests/'))
.reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0); .reduce((sum, file) => sum + (file.additions || 0), 0);
const nonTestChanges = totalChanges - testChanges; const testDeletions = prFiles
.filter(file => file.filename.startsWith('tests/'))
.reduce((sum, file) => sum + (file.deletions || 0), 0);
const nonTestChanges = (totalAdditions - testAdditions) - (totalDeletions - testDeletions);
const tooManyLabels = finalLabels.length > MAX_LABELS; const tooManyLabels = finalLabels.length > MAX_LABELS;
const tooManyChanges = nonTestChanges > TOO_BIG_THRESHOLD; const tooManyChanges = nonTestChanges > TOO_BIG_THRESHOLD;

View File

@@ -156,7 +156,7 @@ jobs:
. venv/bin/activate . venv/bin/activate
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/ pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
uses: codecov/codecov-action@v5.4.3 uses: codecov/codecov-action@v5.5.0
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
- name: Save Python virtual environment cache - name: Save Python virtual environment cache

View File

@@ -1,24 +0,0 @@
name: Needs Docs
on:
pull_request:
types: [labeled, unlabeled]
jobs:
check:
name: Check
runs-on: ubuntu-latest
steps:
- name: Check for needs-docs label
uses: actions/github-script@v7.0.1
with:
script: |
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number
});
const needsDocs = labels.find(label => label.name === 'needs-docs');
if (needsDocs) {
core.setFailed('Pull request needs docs');
}

View File

@@ -0,0 +1,30 @@
name: Status check labels
on:
pull_request:
types: [labeled, unlabeled]
jobs:
check:
name: Check ${{ matrix.label }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
label:
- needs-docs
- merge-after-release
steps:
- name: Check for ${{ matrix.label }} label
uses: actions/github-script@v7.0.1
with:
script: |
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number
});
const hasLabel = labels.find(label => label.name === '${{ matrix.label }}');
if (hasLabel) {
core.setFailed('Pull request cannot be merged, it is labeled as ${{ matrix.label }}');
}

View File

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

View File

@@ -89,6 +89,7 @@ esphome/components/bp5758d/* @Cossid
esphome/components/button/* @esphome/core esphome/components/button/* @esphome/core
esphome/components/bytebuffer/* @clydebarrow esphome/components/bytebuffer/* @clydebarrow
esphome/components/camera/* @DT-art1 @bdraco esphome/components/camera/* @DT-art1 @bdraco
esphome/components/camera_encoder/* @DT-art1
esphome/components/canbus/* @danielschramm @mvturnho esphome/components/canbus/* @danielschramm @mvturnho
esphome/components/cap1188/* @mreditor97 esphome/components/cap1188/* @mreditor97
esphome/components/captive_portal/* @esphome/core esphome/components/captive_portal/* @esphome/core

View File

@@ -132,14 +132,17 @@ def choose_upload_log_host(
] ]
resolved.append(choose_prompt(options, purpose=purpose)) resolved.append(choose_prompt(options, purpose=purpose))
elif device == "OTA": elif device == "OTA":
if (show_ota and "ota" in CORE.config) or ( if CORE.address and (
show_api and "api" in CORE.config (show_ota and "ota" in CORE.config)
or (show_api and "api" in CORE.config)
): ):
resolved.append(CORE.address) resolved.append(CORE.address)
elif show_mqtt and has_mqtt_logging(): elif show_mqtt and has_mqtt_logging():
resolved.append("MQTT") resolved.append("MQTT")
else: else:
resolved.append(device) resolved.append(device)
if not resolved:
_LOGGER.error("All specified devices: %s could not be resolved.", defaults)
return resolved return resolved
# No devices specified, show interactive chooser # No devices specified, show interactive chooser
@@ -476,7 +479,7 @@ def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int
from esphome.components.api.client import run_logs from esphome.components.api.client import run_logs
return run_logs(config, addresses_to_use) return run_logs(config, addresses_to_use)
if get_port_type(port) == "MQTT" and "mqtt" in config: if get_port_type(port) in ("NETWORK", "MQTT") and "mqtt" in config:
from esphome import mqtt from esphome import mqtt
return mqtt.show_logs( return mqtt.show_logs(

View File

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

View File

@@ -89,7 +89,7 @@ void AGS10Component::dump_config() {
bool AGS10Component::new_i2c_address(uint8_t newaddress) { bool AGS10Component::new_i2c_address(uint8_t newaddress) {
uint8_t rev_newaddress = ~newaddress; uint8_t rev_newaddress = ~newaddress;
std::array<uint8_t, 5> data{newaddress, rev_newaddress, newaddress, rev_newaddress, 0}; std::array<uint8_t, 5> data{newaddress, rev_newaddress, newaddress, rev_newaddress, 0};
data[4] = calc_crc8_(data, 4); data[4] = crc8(data.data(), 4, 0xFF, 0x31, true);
if (!this->write_bytes(REG_ADDRESS, data)) { if (!this->write_bytes(REG_ADDRESS, data)) {
this->error_code_ = COMMUNICATION_FAILED; this->error_code_ = COMMUNICATION_FAILED;
this->status_set_warning(); this->status_set_warning();
@@ -109,7 +109,7 @@ bool AGS10Component::set_zero_point_with_current_resistance() { return this->set
bool AGS10Component::set_zero_point_with(uint16_t value) { bool AGS10Component::set_zero_point_with(uint16_t value) {
std::array<uint8_t, 5> data{0x00, 0x0C, (uint8_t) ((value >> 8) & 0xFF), (uint8_t) (value & 0xFF), 0}; std::array<uint8_t, 5> data{0x00, 0x0C, (uint8_t) ((value >> 8) & 0xFF), (uint8_t) (value & 0xFF), 0};
data[4] = calc_crc8_(data, 4); data[4] = crc8(data.data(), 4, 0xFF, 0x31, true);
if (!this->write_bytes(REG_CALIBRATION, data)) { if (!this->write_bytes(REG_CALIBRATION, data)) {
this->error_code_ = COMMUNICATION_FAILED; this->error_code_ = COMMUNICATION_FAILED;
this->status_set_warning(); this->status_set_warning();
@@ -184,7 +184,7 @@ template<size_t N> optional<std::array<uint8_t, N>> AGS10Component::read_and_che
auto res = *data; auto res = *data;
auto crc_byte = res[len]; auto crc_byte = res[len];
if (crc_byte != calc_crc8_(res, len)) { if (crc_byte != crc8(res.data(), len, 0xFF, 0x31, true)) {
this->error_code_ = CRC_CHECK_FAILED; this->error_code_ = CRC_CHECK_FAILED;
ESP_LOGE(TAG, "Reading AGS10 version failed: crc error!"); ESP_LOGE(TAG, "Reading AGS10 version failed: crc error!");
return optional<std::array<uint8_t, N>>(); return optional<std::array<uint8_t, N>>();
@@ -192,20 +192,5 @@ template<size_t N> optional<std::array<uint8_t, N>> AGS10Component::read_and_che
return data; return data;
} }
template<size_t N> uint8_t AGS10Component::calc_crc8_(std::array<uint8_t, N> dat, uint8_t num) {
uint8_t i, byte1, crc = 0xFF;
for (byte1 = 0; byte1 < num; byte1++) {
crc ^= (dat[byte1]);
for (i = 0; i < 8; i++) {
if (crc & 0x80) {
crc = (crc << 1) ^ 0x31;
} else {
crc = (crc << 1);
}
}
}
return crc;
}
} // namespace ags10 } // namespace ags10
} // namespace esphome } // namespace esphome

View File

@@ -1,9 +1,9 @@
#pragma once #pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome { namespace esphome {
namespace ags10 { namespace ags10 {
@@ -99,16 +99,6 @@ class AGS10Component : public PollingComponent, public i2c::I2CDevice {
* Read, checks and returns data from the sensor. * Read, checks and returns data from the sensor.
*/ */
template<size_t N> optional<std::array<uint8_t, N>> read_and_check_(uint8_t a_register); template<size_t N> optional<std::array<uint8_t, N>> read_and_check_(uint8_t a_register);
/**
* Calculates CRC8 value.
*
* CRC8 calculation, initial value: 0xFF, polynomial: 0x31 (x8+ x5+ x4+1)
*
* @param[in] dat the data buffer
* @param num number of bytes in the buffer
*/
template<size_t N> uint8_t calc_crc8_(std::array<uint8_t, N> dat, uint8_t num);
}; };
template<typename... Ts> class AGS10NewI2cAddressAction : public Action<Ts...>, public Parented<AGS10Component> { template<typename... Ts> class AGS10NewI2cAddressAction : public Action<Ts...>, public Parented<AGS10Component> {

View File

@@ -18,6 +18,6 @@ CONFIG_SCHEMA = cv.Schema(
).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) ).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
def to_code(config): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
yield esp32_ble_tracker.register_ble_device(var, config) await esp32_ble_tracker.register_ble_device(var, config)

View File

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

View File

@@ -29,22 +29,6 @@ namespace am2315c {
static const char *const TAG = "am2315c"; static const char *const TAG = "am2315c";
uint8_t AM2315C::crc8_(uint8_t *data, uint8_t len) {
uint8_t crc = 0xFF;
while (len--) {
crc ^= *data++;
for (uint8_t i = 0; i < 8; i++) {
if (crc & 0x80) {
crc <<= 1;
crc ^= 0x31;
} else {
crc <<= 1;
}
}
}
return crc;
}
bool AM2315C::reset_register_(uint8_t reg) { bool AM2315C::reset_register_(uint8_t reg) {
// code based on demo code sent by www.aosong.com // code based on demo code sent by www.aosong.com
// no further documentation. // no further documentation.
@@ -86,7 +70,7 @@ bool AM2315C::convert_(uint8_t *data, float &humidity, float &temperature) {
humidity = raw * 9.5367431640625e-5; humidity = raw * 9.5367431640625e-5;
raw = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5]; raw = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
temperature = raw * 1.9073486328125e-4 - 50; temperature = raw * 1.9073486328125e-4 - 50;
return this->crc8_(data, 6) == data[6]; return crc8(data, 6, 0xFF, 0x31, true) == data[6];
} }
void AM2315C::setup() { void AM2315C::setup() {

View File

@@ -21,9 +21,9 @@
// SOFTWARE. // SOFTWARE.
#pragma once #pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h" #include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
namespace esphome { namespace esphome {
namespace am2315c { namespace am2315c {
@@ -39,7 +39,6 @@ class AM2315C : public PollingComponent, public i2c::I2CDevice {
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; } void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
protected: protected:
uint8_t crc8_(uint8_t *data, uint8_t len);
bool convert_(uint8_t *data, float &humidity, float &temperature); bool convert_(uint8_t *data, float &humidity, float &temperature);
bool reset_register_(uint8_t reg); bool reset_register_(uint8_t reg);

View File

@@ -24,7 +24,7 @@ from esphome.const import (
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_VARIABLES, CONF_VARIABLES,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, CoroPriority, coroutine_with_priority
DOMAIN = "api" DOMAIN = "api"
DEPENDENCIES = ["network"] 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): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config) await cg.register_component(var, config)
@@ -321,6 +321,7 @@ HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value(
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA, HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA,
) )
async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args): async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args):
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
serv = await cg.get_variable(config[CONF_ID]) serv = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, serv, True) var = cg.new_Pvariable(action_id, template_arg, serv, True)
cg.add(var.set_service("esphome.tag_scanned")) cg.add(var.set_service("esphome.tag_scanned"))

View File

@@ -1712,6 +1712,7 @@ message BluetoothScannerStateResponse {
BluetoothScannerState state = 1; BluetoothScannerState state = 1;
BluetoothScannerMode mode = 2; BluetoothScannerMode mode = 2;
BluetoothScannerMode configured_mode = 3;
} }
message BluetoothScannerSetModeRequest { message BluetoothScannerSetModeRequest {

View File

@@ -465,9 +465,7 @@ uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *
resp.cold_white = values.get_cold_white(); resp.cold_white = values.get_cold_white();
resp.warm_white = values.get_warm_white(); resp.warm_white = values.get_warm_white();
if (light->supports_effects()) { if (light->supports_effects()) {
// get_effect_name() returns temporary std::string - must store it resp.set_effect(light->get_effect_name_ref());
std::string effect_name = light->get_effect_name();
resp.set_effect(StringRef(effect_name));
} }
return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
@@ -1425,9 +1423,7 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION); static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION);
resp.set_esphome_version(ESPHOME_VERSION_REF); resp.set_esphome_version(ESPHOME_VERSION_REF);
// get_compilation_time() returns temporary std::string - must store it resp.set_compilation_time(App.get_compilation_time_ref());
std::string compilation_time = App.get_compilation_time();
resp.set_compilation_time(StringRef(compilation_time));
// Compile-time StringRef constants for manufacturers // Compile-time StringRef constants for manufacturers
#if defined(USE_ESP8266) || defined(USE_ESP32) #if defined(USE_ESP8266) || defined(USE_ESP32)

View File

@@ -44,7 +44,7 @@ static constexpr size_t MAX_PACKETS_PER_BATCH = 64; // ESP32 has 8KB+ stack, HO
static constexpr size_t MAX_PACKETS_PER_BATCH = 32; // ESP8266/RP2040/etc have smaller stacks static constexpr size_t MAX_PACKETS_PER_BATCH = 32; // ESP8266/RP2040/etc have smaller stacks
#endif #endif
class APIConnection : public APIServerConnection { class APIConnection final : public APIServerConnection {
public: public:
friend class APIServer; friend class APIServer;
friend class ListEntitiesIterator; friend class ListEntitiesIterator;
@@ -301,9 +301,17 @@ class APIConnection : public APIServerConnection {
APIConnection *conn, uint32_t remaining_size, bool is_single) { APIConnection *conn, uint32_t remaining_size, bool is_single) {
// Set common fields that are shared by all entity types // Set common fields that are shared by all entity types
msg.key = entity->get_object_id_hash(); msg.key = entity->get_object_id_hash();
// IMPORTANT: get_object_id() may return a temporary std::string // Try to use static reference first to avoid allocation
std::string object_id = entity->get_object_id(); StringRef static_ref = entity->get_object_id_ref_for_api_();
// Store dynamic string outside the if-else to maintain lifetime
std::string object_id;
if (!static_ref.empty()) {
msg.set_object_id(static_ref);
} else {
// Dynamic case - need to allocate
object_id = entity->get_object_id();
msg.set_object_id(StringRef(object_id)); msg.set_object_id(StringRef(object_id));
}
if (entity->has_own_name()) { if (entity->has_own_name()) {
msg.set_name(entity->get_name()); msg.set_name(entity->get_name());

View File

@@ -104,9 +104,9 @@ class APIFrameHelper {
// The buffer contains all messages with appropriate padding before each // The buffer contains all messages with appropriate padding before each
virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) = 0; virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) = 0;
// Get the frame header padding required by this protocol // Get the frame header padding required by this protocol
virtual uint8_t frame_header_padding() = 0; uint8_t frame_header_padding() const { return frame_header_padding_; }
// Get the frame footer size required by this protocol // Get the frame footer size required by this protocol
virtual uint8_t frame_footer_size() = 0; uint8_t frame_footer_size() const { return frame_footer_size_; }
// Check if socket has data ready to read // Check if socket has data ready to read
bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); } bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); }

View File

@@ -7,7 +7,7 @@
namespace esphome::api { namespace esphome::api {
class APINoiseFrameHelper : public APIFrameHelper { class APINoiseFrameHelper final : public APIFrameHelper {
public: public:
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx, APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx,
const ClientInfo *client_info) const ClientInfo *client_info)
@@ -25,10 +25,6 @@ class APINoiseFrameHelper : public APIFrameHelper {
APIError read_packet(ReadPacketBuffer *buffer) override; APIError read_packet(ReadPacketBuffer *buffer) override;
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override; APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override; APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
// Get the frame header padding required by this protocol
uint8_t frame_header_padding() override { return frame_header_padding_; }
// Get the frame footer size required by this protocol
uint8_t frame_footer_size() override { return frame_footer_size_; }
protected: protected:
APIError state_action_(); APIError state_action_();

View File

@@ -235,8 +235,8 @@ APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer
for (const auto &packet : packets) { for (const auto &packet : packets) {
// Calculate varint sizes for header layout // Calculate varint sizes for header layout
uint8_t size_varint_len = api::ProtoSize::varint(packet.payload_size); uint8_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(packet.payload_size));
uint8_t type_varint_len = api::ProtoSize::varint(packet.message_type); uint8_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(packet.message_type));
uint8_t total_header_len = 1 + size_varint_len + type_varint_len; uint8_t total_header_len = 1 + size_varint_len + type_varint_len;
// Calculate where to start writing the header // Calculate where to start writing the header
@@ -271,8 +271,9 @@ APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer
buf_start[header_offset] = 0x00; // indicator buf_start[header_offset] = 0x00; // indicator
// Encode varints directly into buffer // Encode varints directly into buffer
encode_varint_unchecked(buf_start + header_offset + 1, packet.payload_size); ProtoVarInt(packet.payload_size).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len);
encode_varint_unchecked(buf_start + header_offset + 1 + size_varint_len, packet.message_type); ProtoVarInt(packet.message_type)
.encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len);
// Add iovec for this packet (header + payload) // Add iovec for this packet (header + payload)
size_t packet_len = static_cast<size_t>(total_header_len + packet.payload_size); size_t packet_len = static_cast<size_t>(total_header_len + packet.payload_size);

View File

@@ -5,7 +5,7 @@
namespace esphome::api { namespace esphome::api {
class APIPlaintextFrameHelper : public APIFrameHelper { class APIPlaintextFrameHelper final : public APIFrameHelper {
public: public:
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket, const ClientInfo *client_info) APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket, const ClientInfo *client_info)
: APIFrameHelper(std::move(socket), client_info) { : APIFrameHelper(std::move(socket), client_info) {
@@ -22,9 +22,6 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
APIError read_packet(ReadPacketBuffer *buffer) override; APIError read_packet(ReadPacketBuffer *buffer) override;
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override; APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override; APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
uint8_t frame_header_padding() override { return frame_header_padding_; }
// Get the frame footer size required by this protocol
uint8_t frame_footer_size() override { return frame_footer_size_; }
protected: protected:
APIError try_read_frame_(std::vector<uint8_t> *frame); APIError try_read_frame_(std::vector<uint8_t> *frame);

View File

@@ -2153,10 +2153,12 @@ void BluetoothDeviceClearCacheResponse::calculate_size(ProtoSize &size) const {
void BluetoothScannerStateResponse::encode(ProtoWriteBuffer buffer) const { void BluetoothScannerStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(1, static_cast<uint32_t>(this->state)); buffer.encode_uint32(1, static_cast<uint32_t>(this->state));
buffer.encode_uint32(2, static_cast<uint32_t>(this->mode)); 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 { 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->state));
size.add_uint32(1, static_cast<uint32_t>(this->mode)); 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) { bool BluetoothScannerSetModeRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) { switch (field_id) {

File diff suppressed because it is too large Load Diff

View File

@@ -1135,7 +1135,7 @@ void ExecuteServiceArgument::dump_to(std::string &out) const {
dump_field(out, "string_", this->string_); dump_field(out, "string_", this->string_);
dump_field(out, "int_", this->int_); dump_field(out, "int_", this->int_);
for (const auto it : this->bool_array) { 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) { for (const auto &it : this->int_array) {
dump_field(out, "int_array", it, 4); dump_field(out, "int_array", it, 4);
@@ -1704,6 +1704,7 @@ void BluetoothScannerStateResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "BluetoothScannerStateResponse"); MessageDumpHelper helper(out, "BluetoothScannerStateResponse");
dump_field(out, "state", static_cast<enums::BluetoothScannerState>(this->state)); dump_field(out, "state", static_cast<enums::BluetoothScannerState>(this->state));
dump_field(out, "mode", static_cast<enums::BluetoothScannerMode>(this->mode)); 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 { void BluetoothScannerSetModeRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "BluetoothScannerSetModeRequest"); MessageDumpHelper helper(out, "BluetoothScannerSetModeRequest");

View File

@@ -8,74 +8,70 @@ namespace esphome::api {
static const char *const TAG = "api.proto"; static const char *const TAG = "api.proto";
void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) { void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
uint32_t i = 0; const uint8_t *ptr = buffer;
bool error = false; const uint8_t *end = buffer + length;
while (i < length) {
while (ptr < end) {
uint32_t consumed; uint32_t consumed;
auto res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
// Parse field header
auto res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
if (!res.has_value()) { if (!res.has_value()) {
ESP_LOGV(TAG, "Invalid field start at %" PRIu32, i); ESP_LOGV(TAG, "Invalid field start at offset %ld", (long) (ptr - buffer));
break; return;
} }
uint32_t field_type = (res->as_uint32()) & 0b111; uint32_t tag = res->as_uint32();
uint32_t field_id = (res->as_uint32()) >> 3; uint32_t field_type = tag & 0b111;
i += consumed; uint32_t field_id = tag >> 3;
ptr += consumed;
switch (field_type) { switch (field_type) {
case 0: { // VarInt case 0: { // VarInt
res = ProtoVarInt::parse(&buffer[i], length - i, &consumed); res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
if (!res.has_value()) { if (!res.has_value()) {
ESP_LOGV(TAG, "Invalid VarInt at %" PRIu32, i); ESP_LOGV(TAG, "Invalid VarInt at offset %ld", (long) (ptr - buffer));
error = true; return;
break;
} }
if (!this->decode_varint(field_id, *res)) { if (!this->decode_varint(field_id, *res)) {
ESP_LOGV(TAG, "Cannot decode VarInt field %" PRIu32 " with value %" PRIu32 "!", field_id, res->as_uint32()); ESP_LOGV(TAG, "Cannot decode VarInt field %" PRIu32 " with value %" PRIu32 "!", field_id, res->as_uint32());
} }
i += consumed; ptr += consumed;
break; break;
} }
case 2: { // Length-delimited case 2: { // Length-delimited
res = ProtoVarInt::parse(&buffer[i], length - i, &consumed); res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
if (!res.has_value()) { if (!res.has_value()) {
ESP_LOGV(TAG, "Invalid Length Delimited at %" PRIu32, i); ESP_LOGV(TAG, "Invalid Length Delimited at offset %ld", (long) (ptr - buffer));
error = true; return;
break;
} }
uint32_t field_length = res->as_uint32(); uint32_t field_length = res->as_uint32();
i += consumed; ptr += consumed;
if (field_length > length - i) { if (ptr + field_length > end) {
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %" PRIu32, i); ESP_LOGV(TAG, "Out-of-bounds Length Delimited at offset %ld", (long) (ptr - buffer));
error = true; return;
break;
} }
if (!this->decode_length(field_id, ProtoLengthDelimited(&buffer[i], field_length))) { if (!this->decode_length(field_id, ProtoLengthDelimited(ptr, field_length))) {
ESP_LOGV(TAG, "Cannot decode Length Delimited field %" PRIu32 "!", field_id); ESP_LOGV(TAG, "Cannot decode Length Delimited field %" PRIu32 "!", field_id);
} }
i += field_length; ptr += field_length;
break; break;
} }
case 5: { // 32-bit case 5: { // 32-bit
if (length - i < 4) { if (ptr + 4 > end) {
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at %" PRIu32, i); ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at offset %ld", (long) (ptr - buffer));
error = true; return;
break;
} }
uint32_t val = encode_uint32(buffer[i + 3], buffer[i + 2], buffer[i + 1], buffer[i]); uint32_t val = encode_uint32(ptr[3], ptr[2], ptr[1], ptr[0]);
if (!this->decode_32bit(field_id, Proto32Bit(val))) { if (!this->decode_32bit(field_id, Proto32Bit(val))) {
ESP_LOGV(TAG, "Cannot decode 32-bit field %" PRIu32 " with value %" PRIu32 "!", field_id, val); ESP_LOGV(TAG, "Cannot decode 32-bit field %" PRIu32 " with value %" PRIu32 "!", field_id, val);
} }
i += 4; ptr += 4;
break; break;
} }
default: default:
ESP_LOGV(TAG, "Invalid field type at %" PRIu32, i); ESP_LOGV(TAG, "Invalid field type %u at offset %ld", field_type, (long) (ptr - buffer));
error = true; return;
break;
}
if (error) {
break;
} }
} }
} }

View File

@@ -124,6 +124,34 @@ class ProtoVarInt {
// with ZigZag encoding // with ZigZag encoding
return decode_zigzag64(this->value_); return decode_zigzag64(this->value_);
} }
/**
* Encode the varint value to a pre-allocated buffer without bounds checking.
*
* @param buffer The pre-allocated buffer to write the encoded varint to
* @param len The size of the buffer in bytes
*
* @note The caller is responsible for ensuring the buffer is large enough
* to hold the encoded value. Use ProtoSize::varint() to calculate
* the exact size needed before calling this method.
* @note No bounds checking is performed for performance reasons.
*/
void encode_to_buffer_unchecked(uint8_t *buffer, size_t len) {
uint64_t val = this->value_;
if (val <= 0x7F) {
buffer[0] = val;
return;
}
size_t i = 0;
while (val && i < len) {
uint8_t temp = val & 0x7F;
val >>= 7;
if (val) {
buffer[i++] = temp | 0x80;
} else {
buffer[i++] = temp;
}
}
}
void encode(std::vector<uint8_t> &out) { void encode(std::vector<uint8_t> &out) {
uint64_t val = this->value_; uint64_t val = this->value_;
if (val <= 0x7F) { if (val <= 0x7F) {
@@ -302,28 +330,6 @@ class ProtoWriteBuffer {
std::vector<uint8_t> *buffer_; std::vector<uint8_t> *buffer_;
}; };
/**
* @brief Encode a uint16_t value as a varint directly to a buffer without bounds checking
*
* @param buffer The pre-allocated buffer to write the encoded varint to
* @param value The uint16_t value to encode (0-65535)
*
* @note The caller is responsible for ensuring the buffer is large enough (max 3 bytes for uint16_t)
* @note No bounds checking is performed for performance reasons
*/
inline void encode_varint_unchecked(uint8_t *buffer, uint16_t value) {
if (value < 128) {
buffer[0] = value;
} else if (value < 16384) {
buffer[0] = (value & 0x7F) | 0x80;
buffer[1] = value >> 7;
} else {
buffer[0] = (value & 0x7F) | 0x80;
buffer[1] = ((value >> 7) & 0x7F) | 0x80;
buffer[2] = value >> 14;
}
}
// Forward declaration // Forward declaration
class ProtoSize; class ProtoSize;
@@ -380,33 +386,6 @@ class ProtoSize {
uint32_t get_size() const { return total_size_; } uint32_t get_size() const { return total_size_; }
/**
* @brief Calculates the size in bytes needed to encode a uint8_t value as a varint
*
* @param value The uint8_t value to calculate size for
* @return The number of bytes needed to encode the value (1 or 2)
*/
static constexpr uint8_t varint(uint8_t value) {
// For uint8_t (0-255), we need at most 2 bytes
return (value < 128) ? 1 : 2;
}
/**
* @brief Calculates the size in bytes needed to encode a uint16_t value as a varint
*
* @param value The uint16_t value to calculate size for
* @return The number of bytes needed to encode the value (1-3)
*/
static constexpr uint8_t varint(uint16_t value) {
// For uint16_t (0-65535), we need at most 3 bytes
if (value < 128)
return 1; // 7 bits
else if (value < 16384)
return 2; // 14 bits
else
return 3; // 15-16 bits
}
/** /**
* @brief Calculates the size in bytes needed to encode a uint32_t value as a varint * @brief Calculates the size in bytes needed to encode a uint32_t value as a varint
* *
@@ -416,9 +395,11 @@ class ProtoSize {
static constexpr uint32_t varint(uint32_t value) { static constexpr uint32_t varint(uint32_t value) {
// Optimized varint size calculation using leading zeros // Optimized varint size calculation using leading zeros
// Each 7 bits requires one byte in the varint encoding // Each 7 bits requires one byte in the varint encoding
if (value < 128) { if (value < 128)
return 1; // 7 bits, common case for small values return 1; // 7 bits, common case for small values
} else if (value < 16384) {
// For larger values, count bytes needed based on the position of the highest bit set
if (value < 16384) {
return 2; // 14 bits return 2; // 14 bits
} else if (value < 2097152) { } else if (value < 2097152) {
return 3; // 21 bits return 3; // 21 bits
@@ -792,7 +773,7 @@ inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessa
this->buffer_->resize(this->buffer_->size() + varint_length_bytes); this->buffer_->resize(this->buffer_->size() + varint_length_bytes);
// Write the length varint directly // Write the length varint directly
encode_varint_unchecked(this->buffer_->data() + begin, static_cast<uint16_t>(msg_length_bytes)); ProtoVarInt(msg_length_bytes).encode_to_buffer_unchecked(this->buffer_->data() + begin, varint_length_bytes);
// Now encode the message content - it will append to the buffer // Now encode the message content - it will append to the buffer
value.encode(*this); value.encode(*this);

View File

@@ -8,7 +8,7 @@ from esphome.const import (
PLATFORM_LN882X, PLATFORM_LN882X,
PLATFORM_RTL87XX, PLATFORM_RTL87XX,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, CoroPriority, coroutine_with_priority
CODEOWNERS = ["@esphome/core"] 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): async def to_code(config):
if CORE.is_esp32 or CORE.is_libretiny: if CORE.is_esp32 or CORE.is_libretiny:
# https://github.com/ESP32Async/AsyncTCP # https://github.com/ESP32Async/AsyncTCP

View File

@@ -382,20 +382,15 @@ float ATM90E32Component::get_setup_priority() const { return setup_priority::IO;
// R/C registers can conly be cleared after the LastSPIData register is updated (register 78H) // R/C registers can conly be cleared after the LastSPIData register is updated (register 78H)
// Peakdetect period: 05H. Bit 15:8 are PeakDet_period in ms. 7:0 are Sag_period // Peakdetect period: 05H. Bit 15:8 are PeakDet_period in ms. 7:0 are Sag_period
// Default is 143FH (20ms, 63ms) // Default is 143FH (20ms, 63ms)
uint16_t ATM90E32Component::read16_transaction_(uint16_t a_register) { uint16_t ATM90E32Component::read16_(uint16_t a_register) {
this->enable();
delay_microseconds_safe(1); // min delay between CS low and first SCK is 200ns - 1us is plenty
uint8_t addrh = (1 << 7) | ((a_register >> 8) & 0x03); uint8_t addrh = (1 << 7) | ((a_register >> 8) & 0x03);
uint8_t addrl = (a_register & 0xFF); uint8_t addrl = (a_register & 0xFF);
uint8_t data[4] = {addrh, addrl, 0x00, 0x00}; uint8_t data[4] = {addrh, addrl, 0x00, 0x00};
this->transfer_array(data, 4); this->transfer_array(data, 4);
uint16_t output = encode_uint16(data[2], data[3]); uint16_t output = encode_uint16(data[2], data[3]);
ESP_LOGVV(TAG, "read16_ 0x%04" PRIX16 " output 0x%04" PRIX16, a_register, output); ESP_LOGVV(TAG, "read16_ 0x%04" PRIX16 " output 0x%04" PRIX16, a_register, output);
return output;
}
uint16_t ATM90E32Component::read16_(uint16_t a_register) {
this->enable();
delay_microseconds_safe(1); // min delay between CS low and first SCK is 200ns - 1us is plenty
uint16_t output = this->read16_transaction_(a_register);
delay_microseconds_safe(1); // allow the last clock to propagate before releasing CS delay_microseconds_safe(1); // allow the last clock to propagate before releasing CS
this->disable(); this->disable();
delay_microseconds_safe(1); // meet minimum CS high time before next transaction delay_microseconds_safe(1); // meet minimum CS high time before next transaction
@@ -403,14 +398,8 @@ uint16_t ATM90E32Component::read16_(uint16_t a_register) {
} }
int ATM90E32Component::read32_(uint16_t addr_h, uint16_t addr_l) { int ATM90E32Component::read32_(uint16_t addr_h, uint16_t addr_l) {
this->enable(); const uint16_t val_h = this->read16_(addr_h);
delay_microseconds_safe(1); const uint16_t val_l = this->read16_(addr_l);
const uint16_t val_h = this->read16_transaction_(addr_h);
delay_microseconds_safe(1);
const uint16_t val_l = this->read16_transaction_(addr_l);
delay_microseconds_safe(1);
this->disable();
delay_microseconds_safe(1);
const int32_t val = (val_h << 16) | val_l; const int32_t val = (val_h << 16) | val_l;
ESP_LOGVV(TAG, ESP_LOGVV(TAG,

View File

@@ -140,7 +140,6 @@ class ATM90E32Component : public PollingComponent,
number::Number *ref_currents_[3]{nullptr, nullptr, nullptr}; number::Number *ref_currents_[3]{nullptr, nullptr, nullptr};
#endif #endif
uint16_t read16_(uint16_t a_register); uint16_t read16_(uint16_t a_register);
uint16_t read16_transaction_(uint16_t a_register);
int read32_(uint16_t addr_h, uint16_t addr_l); int read32_(uint16_t addr_h, uint16_t addr_l);
void write16_(uint16_t a_register, uint16_t val, bool validate = true); void write16_(uint16_t a_register, uint16_t val, bool validate = true);
float get_local_phase_voltage_(uint8_t phase); float get_local_phase_voltage_(uint8_t phase);

View File

@@ -2,7 +2,7 @@ from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_MIC_GAIN 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"] CODEOWNERS = ["@kbx81"]
IS_PLATFORM_COMPONENT = True 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 return var
@coroutine_with_priority(100.0) @coroutine_with_priority(CoroPriority.CORE)
async def to_code(config): async def to_code(config):
cg.add_define("USE_AUDIO_ADC") cg.add_define("USE_AUDIO_ADC")
cg.add_global(audio_adc_ns.using) 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.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_VOLUME 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"] CODEOWNERS = ["@kbx81"]
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True
@@ -51,7 +51,7 @@ async def audio_dac_set_volume_to_code(config, action_id, template_arg, args):
return var return var
@coroutine_with_priority(100.0) @coroutine_with_priority(CoroPriority.CORE)
async def to_code(config): async def to_code(config):
cg.add_define("USE_AUDIO_DAC") cg.add_define("USE_AUDIO_DAC")
cg.add_global(audio_dac_ns.using) cg.add_global(audio_dac_ns.using)

View File

@@ -41,7 +41,7 @@ void AXS15231Touchscreen::update_touches() {
i2c::ErrorCode err; i2c::ErrorCode err;
uint8_t data[8]{}; uint8_t data[8]{};
err = this->write(AXS_READ_TOUCHPAD, sizeof(AXS_READ_TOUCHPAD), false); err = this->write(AXS_READ_TOUCHPAD, sizeof(AXS_READ_TOUCHPAD));
ERROR_CHECK(err); ERROR_CHECK(err);
err = this->read(data, sizeof(data)); err = this->read(data, sizeof(data));
ERROR_CHECK(err); ERROR_CHECK(err);

View File

@@ -59,7 +59,7 @@ from esphome.const import (
DEVICE_CLASS_VIBRATION, DEVICE_CLASS_VIBRATION,
DEVICE_CLASS_WINDOW, 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.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
from esphome.util import Registry 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) 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): async def to_code(config):
cg.add_global(binary_sensor_ns.using) cg.add_global(binary_sensor_ns.using)

View File

@@ -7,6 +7,19 @@ namespace binary_sensor {
static const char *const TAG = "binary_sensor"; static const char *const TAG = "binary_sensor";
// Function implementation of LOG_BINARY_SENSOR macro to reduce code size
void log_binary_sensor(const char *tag, const char *prefix, const char *type, BinarySensor *obj) {
if (obj == nullptr) {
return;
}
ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());
if (!obj->get_device_class_ref().empty()) {
ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->get_device_class_ref().c_str());
}
}
void BinarySensor::publish_state(bool new_state) { void BinarySensor::publish_state(bool new_state) {
if (this->filter_list_ == nullptr) { if (this->filter_list_ == nullptr) {
this->send_state_internal(new_state); this->send_state_internal(new_state);

View File

@@ -10,13 +10,10 @@ namespace esphome {
namespace binary_sensor { namespace binary_sensor {
#define LOG_BINARY_SENSOR(prefix, type, obj) \ class BinarySensor;
if ((obj) != nullptr) { \ void log_binary_sensor(const char *tag, const char *prefix, const char *type, BinarySensor *obj);
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
if (!(obj)->get_device_class().empty()) { \ #define LOG_BINARY_SENSOR(prefix, type, obj) log_binary_sensor(TAG, prefix, LOG_STR_LITERAL(type), obj)
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
} \
}
#define SUB_BINARY_SENSOR(name) \ #define SUB_BINARY_SENSOR(name) \
protected: \ protected: \

View File

@@ -27,7 +27,7 @@ CONFIG_SCHEMA = cv.All(
) )
def to_code(config): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add( cg.add(
@@ -63,6 +63,6 @@ def to_code(config):
) )
cg.add(var.set_char_uuid128(uuid128)) cg.add(var.set_char_uuid128(uuid128))
cg.add(var.set_require_response(config[CONF_REQUIRE_RESPONSE])) cg.add(var.set_require_response(config[CONF_REQUIRE_RESPONSE]))
yield output.register_output(var, config) await output.register_output(var, config)
yield ble_client.register_ble_node(var, config) await ble_client.register_ble_node(var, config)
yield cg.register_component(var, config) await cg.register_component(var, config)

View File

@@ -80,7 +80,7 @@ CONFIG_SCHEMA = cv.All(
cv.Schema( cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(BluetoothProxy), 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.SplitDefault(CONF_CACHE_SERVICES, esp32_idf=True): cv.All(
cv.only_with_esp_idf, cv.boolean cv.only_with_esp_idf, cv.boolean
), ),

View File

@@ -375,10 +375,19 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
switch (event) { switch (event) {
case ESP_GATTC_DISCONNECT_EVT: { case ESP_GATTC_DISCONNECT_EVT: {
this->reset_connection_(param->disconnect.reason); // Don't reset connection yet - wait for CLOSE_EVT to ensure controller has freed resources
// This prevents race condition where we mark slot as free before controller cleanup is complete
ESP_LOGD(TAG, "[%d] [%s] Disconnect, reason=0x%02x", this->connection_index_, this->address_str_.c_str(),
param->disconnect.reason);
// Send disconnection notification but don't free the slot yet
this->proxy_->send_device_connection(this->address_, false, 0, param->disconnect.reason);
break; break;
} }
case ESP_GATTC_CLOSE_EVT: { case ESP_GATTC_CLOSE_EVT: {
ESP_LOGD(TAG, "[%d] [%s] Close, reason=0x%02x, freeing slot", this->connection_index_, this->address_str_.c_str(),
param->close.reason);
// Now the GATT connection is fully closed and controller resources are freed
// Safe to mark the connection slot as available
this->reset_connection_(param->close.reason); this->reset_connection_(param->close.reason);
break; break;
} }

View File

@@ -8,7 +8,7 @@ namespace esphome::bluetooth_proxy {
class BluetoothProxy; class BluetoothProxy;
class BluetoothConnection : public esp32_ble_client::BLEClientBase { class BluetoothConnection final : public esp32_ble_client::BLEClientBase {
public: public:
void dump_config() override; void dump_config() override;
void loop() override; void loop() override;

View File

@@ -24,6 +24,9 @@ void BluetoothProxy::setup() {
this->connections_free_response_.limit = BLUETOOTH_PROXY_MAX_CONNECTIONS; this->connections_free_response_.limit = BLUETOOTH_PROXY_MAX_CONNECTIONS;
this->connections_free_response_.free = 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) { this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) {
if (this->api_connection_ != nullptr) { if (this->api_connection_ != nullptr) {
this->send_bluetooth_scanner_state_(state); 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.state = static_cast<api::enums::BluetoothScannerState>(state);
resp.mode = this->parent_->get_scan_active() ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE resp.mode = this->parent_->get_scan_active() ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE
: api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE; : 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); 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); this->send_device_connection(msg.address, false);
return; 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 || if (connection->state() == espbt::ClientState::CONNECTED ||
connection->state() == espbt::ClientState::ESTABLISHED) { connection->state() == espbt::ClientState::ESTABLISHED) {
this->log_connection_request_ignored_(connection, connection->state()); 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); connection->set_connection_type(espbt::ConnectionType::V3_WITHOUT_CACHE);
this->log_connection_info_(connection, "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_); 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_remote_addr_type(static_cast<esp_ble_addr_type_t>(msg.address_type));
connection->set_state(espbt::ClientState::DISCOVERED); connection->set_state(espbt::ClientState::DISCOVERED);
} else {
connection->set_state(espbt::ClientState::SEARCHING);
}
this->send_connections_free(); this->send_connections_free();
break; break;
} }

View File

@@ -50,7 +50,7 @@ enum BluetoothProxySubscriptionFlag : uint32_t {
SUBSCRIPTION_RAW_ADVERTISEMENTS = 1 << 0, SUBSCRIPTION_RAW_ADVERTISEMENTS = 1 << 0,
}; };
class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Component { class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
friend class BluetoothConnection; // Allow connection to update connections_free_response_ friend class BluetoothConnection; // Allow connection to update connections_free_response_
public: public:
BluetoothProxy(); BluetoothProxy();
@@ -161,7 +161,8 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
// Group 4: 1-byte types grouped together // Group 4: 1-byte types grouped together
bool active_; bool active_;
uint8_t connection_count_{0}; 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) extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

View File

@@ -203,7 +203,7 @@ void BMI160Component::dump_config() {
i2c::ErrorCode BMI160Component::read_le_int16_(uint8_t reg, int16_t *value, uint8_t len) { i2c::ErrorCode BMI160Component::read_le_int16_(uint8_t reg, int16_t *value, uint8_t len) {
uint8_t raw_data[len * 2]; uint8_t raw_data[len * 2];
// read using read_register because we have little-endian data, and read_bytes_16 will swap it // read using read_register because we have little-endian data, and read_bytes_16 will swap it
i2c::ErrorCode err = this->read_register(reg, raw_data, len * 2, true); i2c::ErrorCode err = this->read_register(reg, raw_data, len * 2);
if (err != i2c::ERROR_OK) { if (err != i2c::ERROR_OK) {
return err; return err;
} }

View File

@@ -63,12 +63,12 @@ void BMP280Component::setup() {
// Read the chip id twice, to work around a bug where the first read is 0. // Read the chip id twice, to work around a bug where the first read is 0.
// https://community.st.com/t5/stm32-mcus-products/issue-with-reading-bmp280-chip-id-using-spi/td-p/691855 // https://community.st.com/t5/stm32-mcus-products/issue-with-reading-bmp280-chip-id-using-spi/td-p/691855
if (!this->read_byte(0xD0, &chip_id)) { if (!this->bmp_read_byte(0xD0, &chip_id)) {
this->error_code_ = COMMUNICATION_FAILED; this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed(ESP_LOG_MSG_COMM_FAIL); this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
return; return;
} }
if (!this->read_byte(0xD0, &chip_id)) { if (!this->bmp_read_byte(0xD0, &chip_id)) {
this->error_code_ = COMMUNICATION_FAILED; this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed(ESP_LOG_MSG_COMM_FAIL); this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
return; return;
@@ -80,7 +80,7 @@ void BMP280Component::setup() {
} }
// Send a soft reset. // Send a soft reset.
if (!this->write_byte(BMP280_REGISTER_RESET, BMP280_SOFT_RESET)) { if (!this->bmp_write_byte(BMP280_REGISTER_RESET, BMP280_SOFT_RESET)) {
this->mark_failed("Reset failed"); this->mark_failed("Reset failed");
return; return;
} }
@@ -89,7 +89,7 @@ void BMP280Component::setup() {
uint8_t retry = 5; uint8_t retry = 5;
do { do {
delay(2); delay(2);
if (!this->read_byte(BMP280_REGISTER_STATUS, &status)) { if (!this->bmp_read_byte(BMP280_REGISTER_STATUS, &status)) {
this->mark_failed("Error reading status register"); this->mark_failed("Error reading status register");
return; return;
} }
@@ -115,14 +115,14 @@ void BMP280Component::setup() {
this->calibration_.p9 = this->read_s16_le_(0x9E); this->calibration_.p9 = this->read_s16_le_(0x9E);
uint8_t config_register = 0; uint8_t config_register = 0;
if (!this->read_byte(BMP280_REGISTER_CONFIG, &config_register)) { if (!this->bmp_read_byte(BMP280_REGISTER_CONFIG, &config_register)) {
this->mark_failed("Read config"); this->mark_failed("Read config");
return; return;
} }
config_register &= ~0b11111100; config_register &= ~0b11111100;
config_register |= 0b000 << 5; // 0.5 ms standby time config_register |= 0b000 << 5; // 0.5 ms standby time
config_register |= (this->iir_filter_ & 0b111) << 2; config_register |= (this->iir_filter_ & 0b111) << 2;
if (!this->write_byte(BMP280_REGISTER_CONFIG, config_register)) { if (!this->bmp_write_byte(BMP280_REGISTER_CONFIG, config_register)) {
this->mark_failed("Write config"); this->mark_failed("Write config");
return; return;
} }
@@ -159,7 +159,7 @@ void BMP280Component::update() {
meas_value |= (this->temperature_oversampling_ & 0b111) << 5; meas_value |= (this->temperature_oversampling_ & 0b111) << 5;
meas_value |= (this->pressure_oversampling_ & 0b111) << 2; meas_value |= (this->pressure_oversampling_ & 0b111) << 2;
meas_value |= 0b01; // Forced mode meas_value |= 0b01; // Forced mode
if (!this->write_byte(BMP280_REGISTER_CONTROL, meas_value)) { if (!this->bmp_write_byte(BMP280_REGISTER_CONTROL, meas_value)) {
this->status_set_warning(); this->status_set_warning();
return; return;
} }
@@ -188,9 +188,10 @@ void BMP280Component::update() {
} }
float BMP280Component::read_temperature_(int32_t *t_fine) { float BMP280Component::read_temperature_(int32_t *t_fine) {
uint8_t data[3]; uint8_t data[3]{};
if (!this->read_bytes(BMP280_REGISTER_TEMPDATA, data, 3)) if (!this->bmp_read_bytes(BMP280_REGISTER_TEMPDATA, data, 3))
return NAN; return NAN;
ESP_LOGV(TAG, "Read temperature data, raw: %02X %02X %02X", data[0], data[1], data[2]);
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF); int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
adc >>= 4; adc >>= 4;
if (adc == 0x80000) { if (adc == 0x80000) {
@@ -212,7 +213,7 @@ float BMP280Component::read_temperature_(int32_t *t_fine) {
float BMP280Component::read_pressure_(int32_t t_fine) { float BMP280Component::read_pressure_(int32_t t_fine) {
uint8_t data[3]; uint8_t data[3];
if (!this->read_bytes(BMP280_REGISTER_PRESSUREDATA, data, 3)) if (!this->bmp_read_bytes(BMP280_REGISTER_PRESSUREDATA, data, 3))
return NAN; return NAN;
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF); int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
adc >>= 4; adc >>= 4;
@@ -258,12 +259,12 @@ void BMP280Component::set_pressure_oversampling(BMP280Oversampling pressure_over
void BMP280Component::set_iir_filter(BMP280IIRFilter iir_filter) { this->iir_filter_ = iir_filter; } void BMP280Component::set_iir_filter(BMP280IIRFilter iir_filter) { this->iir_filter_ = iir_filter; }
uint8_t BMP280Component::read_u8_(uint8_t a_register) { uint8_t BMP280Component::read_u8_(uint8_t a_register) {
uint8_t data = 0; uint8_t data = 0;
this->read_byte(a_register, &data); this->bmp_read_byte(a_register, &data);
return data; return data;
} }
uint16_t BMP280Component::read_u16_le_(uint8_t a_register) { uint16_t BMP280Component::read_u16_le_(uint8_t a_register) {
uint16_t data = 0; uint16_t data = 0;
this->read_byte_16(a_register, &data); this->bmp_read_byte_16(a_register, &data);
return (data >> 8) | (data << 8); return (data >> 8) | (data << 8);
} }
int16_t BMP280Component::read_s16_le_(uint8_t a_register) { return this->read_u16_le_(a_register); } int16_t BMP280Component::read_s16_le_(uint8_t a_register) { return this->read_u16_le_(a_register); }

View File

@@ -67,12 +67,12 @@ class BMP280Component : public PollingComponent {
float get_setup_priority() const override; float get_setup_priority() const override;
void update() override; void update() override;
virtual bool read_byte(uint8_t a_register, uint8_t *data) = 0;
virtual bool write_byte(uint8_t a_register, uint8_t data) = 0;
virtual bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0;
virtual bool read_byte_16(uint8_t a_register, uint16_t *data) = 0;
protected: protected:
virtual bool bmp_read_byte(uint8_t a_register, uint8_t *data) = 0;
virtual bool bmp_write_byte(uint8_t a_register, uint8_t data) = 0;
virtual bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0;
virtual bool bmp_read_byte_16(uint8_t a_register, uint16_t *data) = 0;
/// Read the temperature value and store the calculated ambient temperature in t_fine. /// Read the temperature value and store the calculated ambient temperature in t_fine.
float read_temperature_(int32_t *t_fine); float read_temperature_(int32_t *t_fine);
/// Read the pressure value in hPa using the provided t_fine value. /// Read the pressure value in hPa using the provided t_fine value.

View File

@@ -5,19 +5,6 @@
namespace esphome { namespace esphome {
namespace bmp280_i2c { namespace bmp280_i2c {
bool BMP280I2CComponent::read_byte(uint8_t a_register, uint8_t *data) {
return I2CDevice::read_byte(a_register, data);
};
bool BMP280I2CComponent::write_byte(uint8_t a_register, uint8_t data) {
return I2CDevice::write_byte(a_register, data);
};
bool BMP280I2CComponent::read_bytes(uint8_t a_register, uint8_t *data, size_t len) {
return I2CDevice::read_bytes(a_register, data, len);
};
bool BMP280I2CComponent::read_byte_16(uint8_t a_register, uint16_t *data) {
return I2CDevice::read_byte_16(a_register, data);
};
void BMP280I2CComponent::dump_config() { void BMP280I2CComponent::dump_config() {
LOG_I2C_DEVICE(this); LOG_I2C_DEVICE(this);
BMP280Component::dump_config(); BMP280Component::dump_config();

View File

@@ -11,10 +11,12 @@ static const char *const TAG = "bmp280_i2c.sensor";
/// This class implements support for the BMP280 Temperature+Pressure i2c sensor. /// This class implements support for the BMP280 Temperature+Pressure i2c sensor.
class BMP280I2CComponent : public esphome::bmp280_base::BMP280Component, public i2c::I2CDevice { class BMP280I2CComponent : public esphome::bmp280_base::BMP280Component, public i2c::I2CDevice {
public: public:
bool read_byte(uint8_t a_register, uint8_t *data) override; bool bmp_read_byte(uint8_t a_register, uint8_t *data) override { return read_byte(a_register, data); }
bool write_byte(uint8_t a_register, uint8_t data) override; bool bmp_write_byte(uint8_t a_register, uint8_t data) override { return write_byte(a_register, data); }
bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) override; bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) override {
bool read_byte_16(uint8_t a_register, uint16_t *data) override; return read_bytes(a_register, data, len);
}
bool bmp_read_byte_16(uint8_t a_register, uint16_t *data) override { return read_byte_16(a_register, data); }
void dump_config() override; void dump_config() override;
}; };

View File

@@ -28,7 +28,7 @@ void BMP280SPIComponent::setup() {
// 0x77 is transferred, for read access, the byte 0xF7 is transferred. // 0x77 is transferred, for read access, the byte 0xF7 is transferred.
// https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp280-ds001.pdf // https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp280-ds001.pdf
bool BMP280SPIComponent::read_byte(uint8_t a_register, uint8_t *data) { bool BMP280SPIComponent::bmp_read_byte(uint8_t a_register, uint8_t *data) {
this->enable(); this->enable();
this->transfer_byte(set_bit(a_register, 7)); this->transfer_byte(set_bit(a_register, 7));
*data = this->transfer_byte(0); *data = this->transfer_byte(0);
@@ -36,7 +36,7 @@ bool BMP280SPIComponent::read_byte(uint8_t a_register, uint8_t *data) {
return true; return true;
} }
bool BMP280SPIComponent::write_byte(uint8_t a_register, uint8_t data) { bool BMP280SPIComponent::bmp_write_byte(uint8_t a_register, uint8_t data) {
this->enable(); this->enable();
this->transfer_byte(clear_bit(a_register, 7)); this->transfer_byte(clear_bit(a_register, 7));
this->transfer_byte(data); this->transfer_byte(data);
@@ -44,7 +44,7 @@ bool BMP280SPIComponent::write_byte(uint8_t a_register, uint8_t data) {
return true; return true;
} }
bool BMP280SPIComponent::read_bytes(uint8_t a_register, uint8_t *data, size_t len) { bool BMP280SPIComponent::bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) {
this->enable(); this->enable();
this->transfer_byte(set_bit(a_register, 7)); this->transfer_byte(set_bit(a_register, 7));
this->read_array(data, len); this->read_array(data, len);
@@ -52,7 +52,7 @@ bool BMP280SPIComponent::read_bytes(uint8_t a_register, uint8_t *data, size_t le
return true; return true;
} }
bool BMP280SPIComponent::read_byte_16(uint8_t a_register, uint16_t *data) { bool BMP280SPIComponent::bmp_read_byte_16(uint8_t a_register, uint16_t *data) {
this->enable(); this->enable();
this->transfer_byte(set_bit(a_register, 7)); this->transfer_byte(set_bit(a_register, 7));
((uint8_t *) data)[1] = this->transfer_byte(0); ((uint8_t *) data)[1] = this->transfer_byte(0);

View File

@@ -10,10 +10,10 @@ class BMP280SPIComponent : public esphome::bmp280_base::BMP280Component,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_200KHZ> { spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_200KHZ> {
void setup() override; void setup() override;
bool read_byte(uint8_t a_register, uint8_t *data) override; bool bmp_read_byte(uint8_t a_register, uint8_t *data) override;
bool write_byte(uint8_t a_register, uint8_t data) override; bool bmp_write_byte(uint8_t a_register, uint8_t data) override;
bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) override; bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
bool read_byte_16(uint8_t a_register, uint16_t *data) override; bool bmp_read_byte_16(uint8_t a_register, uint16_t *data) override;
}; };
} // namespace bmp280_spi } // namespace bmp280_spi

View File

@@ -17,7 +17,7 @@ from esphome.const import (
DEVICE_CLASS_RESTART, DEVICE_CLASS_RESTART,
DEVICE_CLASS_UPDATE, 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.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass 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) return cg.new_Pvariable(action_id, template_arg, paren)
@coroutine_with_priority(100.0) @coroutine_with_priority(CoroPriority.CORE)
async def to_code(config): async def to_code(config):
cg.add_global(button_ns.using) cg.add_global(button_ns.using)

View File

@@ -6,6 +6,19 @@ namespace button {
static const char *const TAG = "button"; static const char *const TAG = "button";
// Function implementation of LOG_BUTTON macro to reduce code size
void log_button(const char *tag, const char *prefix, const char *type, Button *obj) {
if (obj == nullptr) {
return;
}
ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());
if (!obj->get_icon_ref().empty()) {
ESP_LOGCONFIG(tag, "%s Icon: '%s'", prefix, obj->get_icon_ref().c_str());
}
}
void Button::press() { void Button::press() {
ESP_LOGD(TAG, "'%s' Pressed.", this->get_name().c_str()); ESP_LOGD(TAG, "'%s' Pressed.", this->get_name().c_str());
this->press_action(); this->press_action();

View File

@@ -7,13 +7,10 @@
namespace esphome { namespace esphome {
namespace button { namespace button {
#define LOG_BUTTON(prefix, type, obj) \ class Button;
if ((obj) != nullptr) { \ void log_button(const char *tag, const char *prefix, const char *type, Button *obj);
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
if (!(obj)->get_icon().empty()) { \ #define LOG_BUTTON(prefix, type, obj) log_button(TAG, prefix, LOG_STR_LITERAL(type), obj)
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
} \
}
#define SUB_BUTTON(name) \ #define SUB_BUTTON(name) \
protected: \ protected: \

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 }; 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. /** Abstract camera image base class.
* Encapsulates the JPEG encoded data and it is shared among * Encapsulates the JPEG encoded data and it is shared among
* all connected clients. * all connected clients.
@@ -43,6 +63,29 @@ class CameraImageReader {
virtual ~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. /** Abstract camera base class. Collaborates with API.
* 1) API server starts and installs callback (add_image_callback) * 1) API server starts and installs callback (add_image_callback)
* which is called by the camera when a new image is available. * 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_LN882X,
PLATFORM_RTL87XX, 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"] AUTO_LOAD = ["web_server_base", "ota.web_server"]
DEPENDENCIES = ["wifi"] 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): async def to_code(config):
paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID]) paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID])

View File

@@ -153,8 +153,8 @@ void CCS811Component::dump_config() {
ESP_LOGCONFIG(TAG, "CCS811"); ESP_LOGCONFIG(TAG, "CCS811");
LOG_I2C_DEVICE(this) LOG_I2C_DEVICE(this)
LOG_UPDATE_INTERVAL(this) LOG_UPDATE_INTERVAL(this)
LOG_SENSOR(" ", "CO2 Sensor", this->co2_) LOG_SENSOR(" ", "CO2 Sensor", this->co2_);
LOG_SENSOR(" ", "TVOC Sensor", this->tvoc_) LOG_SENSOR(" ", "TVOC Sensor", this->tvoc_);
LOG_TEXT_SENSOR(" ", "Firmware Version Sensor", this->version_) LOG_TEXT_SENSOR(" ", "Firmware Version Sensor", this->version_)
if (this->baseline_) { if (this->baseline_) {
ESP_LOGCONFIG(TAG, " Baseline: %04X", *this->baseline_); ESP_LOGCONFIG(TAG, " Baseline: %04X", *this->baseline_);

View File

@@ -91,7 +91,7 @@ bool CH422GComponent::read_inputs_() {
// Write a register. Can't use the standard write_byte() method because there is no single pre-configured i2c address. // Write a register. Can't use the standard write_byte() method because there is no single pre-configured i2c address.
bool CH422GComponent::write_reg_(uint8_t reg, uint8_t value) { bool CH422GComponent::write_reg_(uint8_t reg, uint8_t value) {
auto err = this->bus_->write(reg, &value, 1); auto err = this->bus_->write_readv(reg, &value, 1, nullptr, 0);
if (err != i2c::ERROR_OK) { if (err != i2c::ERROR_OK) {
this->status_set_warning(str_sprintf("write failed for register 0x%X, error %d", reg, err).c_str()); this->status_set_warning(str_sprintf("write failed for register 0x%X, error %d", reg, err).c_str());
return false; return false;
@@ -102,7 +102,7 @@ bool CH422GComponent::write_reg_(uint8_t reg, uint8_t value) {
uint8_t CH422GComponent::read_reg_(uint8_t reg) { uint8_t CH422GComponent::read_reg_(uint8_t reg) {
uint8_t value; uint8_t value;
auto err = this->bus_->read(reg, &value, 1); auto err = this->bus_->write_readv(reg, nullptr, 0, &value, 1);
if (err != i2c::ERROR_OK) { if (err != i2c::ERROR_OK) {
this->status_set_warning(str_sprintf("read failed for register 0x%X, error %d", reg, err).c_str()); this->status_set_warning(str_sprintf("read failed for register 0x%X, error %d", reg, err).c_str());
return 0; return 0;

View File

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

View File

@@ -327,7 +327,7 @@ void Climate::add_on_control_callback(std::function<void(ClimateCall &)> &&callb
static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL; static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL;
optional<ClimateDeviceRestoreState> Climate::restore_state_() { optional<ClimateDeviceRestoreState> Climate::restore_state_() {
this->rtc_ = global_preferences->make_preference<ClimateDeviceRestoreState>(this->get_object_id_hash() ^ this->rtc_ = global_preferences->make_preference<ClimateDeviceRestoreState>(this->get_preference_hash() ^
RESTORE_STATE_VERSION); RESTORE_STATE_VERSION);
ClimateDeviceRestoreState recovered{}; ClimateDeviceRestoreState recovered{};
if (!this->rtc_.load(&recovered)) if (!this->rtc_.load(&recovered))

View File

@@ -32,7 +32,7 @@ from esphome.const import (
DEVICE_CLASS_SHUTTER, DEVICE_CLASS_SHUTTER,
DEVICE_CLASS_WINDOW, 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.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
@@ -228,9 +228,9 @@ async def cover_stop_to_code(config, action_id, template_arg, args):
@automation.register_action("cover.toggle", ToggleAction, COVER_ACTION_SCHEMA) @automation.register_action("cover.toggle", ToggleAction, COVER_ACTION_SCHEMA)
def cover_toggle_to_code(config, action_id, template_arg, args): async def cover_toggle_to_code(config, action_id, template_arg, args):
paren = yield cg.get_variable(config[CONF_ID]) paren = await cg.get_variable(config[CONF_ID])
yield cg.new_Pvariable(action_id, template_arg, paren) return cg.new_Pvariable(action_id, template_arg, paren)
COVER_CONTROL_ACTION_SCHEMA = cv.Schema( COVER_CONTROL_ACTION_SCHEMA = cv.Schema(
@@ -263,6 +263,6 @@ async def cover_control_to_code(config, action_id, template_arg, args):
return var return var
@coroutine_with_priority(100.0) @coroutine_with_priority(CoroPriority.CORE)
async def to_code(config): async def to_code(config):
cg.add_global(cover_ns.using) cg.add_global(cover_ns.using)

View File

@@ -194,7 +194,7 @@ void Cover::publish_state(bool save) {
} }
} }
optional<CoverRestoreState> Cover::restore_state_() { optional<CoverRestoreState> Cover::restore_state_() {
this->rtc_ = global_preferences->make_preference<CoverRestoreState>(this->get_object_id_hash()); this->rtc_ = global_preferences->make_preference<CoverRestoreState>(this->get_preference_hash());
CoverRestoreState recovered{}; CoverRestoreState recovered{};
if (!this->rtc_.load(&recovered)) if (!this->rtc_.load(&recovered))
return {}; return {};

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,5 @@
#ifdef USE_ESP32 #ifdef USE_ESP32
#include "soc/soc_caps.h"
#include "driver/gpio.h" #include "driver/gpio.h"
#include "deep_sleep_component.h" #include "deep_sleep_component.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
@@ -83,7 +84,11 @@ void DeepSleepComponent::deep_sleep_() {
} }
gpio_sleep_set_direction(gpio_pin, GPIO_MODE_INPUT); gpio_sleep_set_direction(gpio_pin, GPIO_MODE_INPUT);
gpio_hold_en(gpio_pin); gpio_hold_en(gpio_pin);
#if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP
// Some ESP32 variants support holding a single GPIO during deep sleep without this function
// For those variants, gpio_hold_en() is sufficient to hold the pin state during deep sleep
gpio_deep_sleep_hold_en(); gpio_deep_sleep_hold_en();
#endif
bool level = !this->wakeup_pin_->is_inverted(); bool level = !this->wakeup_pin_->is_inverted();
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) { if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
level = !level; level = !level;
@@ -120,7 +125,11 @@ void DeepSleepComponent::deep_sleep_() {
} }
gpio_sleep_set_direction(gpio_pin, GPIO_MODE_INPUT); gpio_sleep_set_direction(gpio_pin, GPIO_MODE_INPUT);
gpio_hold_en(gpio_pin); gpio_hold_en(gpio_pin);
#if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP
// Some ESP32 variants support holding a single GPIO during deep sleep without this function
// For those variants, gpio_hold_en() is sufficient to hold the pin state during deep sleep
gpio_deep_sleep_hold_en(); gpio_deep_sleep_hold_en();
#endif
bool level = !this->wakeup_pin_->is_inverted(); bool level = !this->wakeup_pin_->is_inverted();
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) { if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
level = !level; level = !level;

View File

@@ -15,7 +15,7 @@ from esphome.const import (
CONF_UPDATE_INTERVAL, CONF_UPDATE_INTERVAL,
SCHEDULER_DONT_RUN, SCHEDULER_DONT_RUN,
) )
from esphome.core import coroutine_with_priority from esphome.core import CoroPriority, coroutine_with_priority
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True
@@ -176,7 +176,7 @@ async def display_page_show_to_code(config, action_id, template_arg, args):
DisplayPageShowNextAction, DisplayPageShowNextAction,
maybe_simple_id( 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, DisplayPageShowPrevAction,
maybe_simple_id( 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 return var
@coroutine_with_priority(100.0) @coroutine_with_priority(CoroPriority.CORE)
async def to_code(config): async def to_code(config):
cg.add_global(display_ns.using) cg.add_global(display_ns.using)
cg.add_define("USE_DISPLAY") cg.add_define("USE_DISPLAY")

View File

@@ -41,7 +41,7 @@ void DutyTimeSensor::setup() {
uint32_t seconds = 0; uint32_t seconds = 0;
if (this->restore_) { if (this->restore_) {
this->pref_ = global_preferences->make_preference<uint32_t>(this->get_object_id_hash()); this->pref_ = global_preferences->make_preference<uint32_t>(this->get_preference_hash());
this->pref_.load(&seconds); this->pref_.load(&seconds);
} }

View File

@@ -83,7 +83,7 @@ void EE895Component::write_command_(uint16_t addr, uint16_t reg_cnt) {
crc16 = calc_crc16_(address, 6); crc16 = calc_crc16_(address, 6);
address[5] = crc16 & 0xFF; address[5] = crc16 & 0xFF;
address[6] = (crc16 >> 8) & 0xFF; address[6] = (crc16 >> 8) & 0xFF;
this->write(address, 7, true); this->write(address, 7);
} }
float EE895Component::read_float_() { float EE895Component::read_float_() {

View File

@@ -40,6 +40,7 @@ from esphome.cpp_generator import RawExpression
import esphome.final_validate as fv import esphome.final_validate as fv
from esphome.helpers import copy_file_if_changed, mkdir_p, write_file_if_changed from esphome.helpers import copy_file_if_changed, mkdir_p, write_file_if_changed
from esphome.types import ConfigType from esphome.types import ConfigType
from esphome.writer import clean_cmake_cache
from .boards import BOARDS, STANDARD_BOARDS from .boards import BOARDS, STANDARD_BOARDS
from .const import ( # noqa from .const import ( # noqa
@@ -824,8 +825,9 @@ async def to_code(config):
cg.set_cpp_standard("gnu++20") cg.set_cpp_standard("gnu++20")
cg.add_build_flag("-DUSE_ESP32") cg.add_build_flag("-DUSE_ESP32")
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{config[CONF_VARIANT]}") variant = config[CONF_VARIANT]
cg.add_define("ESPHOME_VARIANT", VARIANT_FRIENDLY[config[CONF_VARIANT]]) cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{variant}")
cg.add_define("ESPHOME_VARIANT", VARIANT_FRIENDLY[variant])
cg.add_define(ThreadModel.MULTI_ATOMICS) cg.add_define(ThreadModel.MULTI_ATOMICS)
cg.add_platformio_option("lib_ldf_mode", "off") cg.add_platformio_option("lib_ldf_mode", "off")
@@ -839,6 +841,9 @@ async def to_code(config):
if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_CUSTOM_MAC]: if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_CUSTOM_MAC]:
cg.add_define("USE_ESP32_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( add_extra_script(
"post", "post",
"post_build.py", "post_build.py",
@@ -854,11 +859,7 @@ async def to_code(config):
cg.add_platformio_option("platform_packages", [conf[CONF_SOURCE]]) 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 add_idf_sdkconfig_option(f"CONFIG_IDF_TARGET_{variant}", True)
# 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( add_idf_sdkconfig_option(
f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True
) )
@@ -1077,7 +1078,11 @@ def _write_idf_component_yml():
contents = yaml_util.dump({"dependencies": dependencies}) contents = yaml_util.dump({"dependencies": dependencies})
else: else:
contents = "" 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 # Called by writer.py

View File

@@ -5,9 +5,14 @@ from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components.esp32 import 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 import esphome.config_validation as cv
from esphome.const import CONF_ENABLE_ON_BOOT, CONF_ESPHOME, CONF_ID, CONF_NAME from esphome.const import (
CONF_ENABLE_ON_BOOT,
CONF_ESPHOME,
CONF_ID,
CONF_NAME,
CONF_NAME_ADD_MAC_SUFFIX,
)
from esphome.core import CORE, TimePeriod from esphome.core import CORE, TimePeriod
from esphome.core.config import CONF_NAME_ADD_MAC_SUFFIX
import esphome.final_validate as fv import esphome.final_validate as fv
DEPENDENCIES = ["esp32"] DEPENDENCIES = ["esp32"]
@@ -280,6 +285,10 @@ async def to_code(config):
add_idf_sdkconfig_option( add_idf_sdkconfig_option(
"CONFIG_BT_BLE_ESTAB_LINK_CONN_TOUT", timeout_seconds "CONFIG_BT_BLE_ESTAB_LINK_CONN_TOUT", timeout_seconds
) )
# Increase GATT client connection retry count for problematic devices
# Default in ESP-IDF is 3, we increase to 10 for better reliability with
# low-power/timing-sensitive devices
add_idf_sdkconfig_option("CONFIG_BT_GATTC_CONNECT_RETRY_COUNT", 10)
# Set the maximum number of notification registrations # Set the maximum number of notification registrations
# This controls how many BLE characteristics can have notifications enabled # This controls how many BLE characteristics can have notifications enabled

View File

@@ -306,7 +306,7 @@ void ESP32BLE::loop() {
case BLEEvent::GATTS: { case BLEEvent::GATTS: {
esp_gatts_cb_event_t event = ble_event->event_.gatts.gatts_event; esp_gatts_cb_event_t event = ble_event->event_.gatts.gatts_event;
esp_gatt_if_t gatts_if = ble_event->event_.gatts.gatts_if; esp_gatt_if_t gatts_if = ble_event->event_.gatts.gatts_if;
esp_ble_gatts_cb_param_t *param = ble_event->event_.gatts.gatts_param; esp_ble_gatts_cb_param_t *param = &ble_event->event_.gatts.gatts_param;
ESP_LOGV(TAG, "gatts_event [esp_gatt_if: %d] - %d", gatts_if, event); ESP_LOGV(TAG, "gatts_event [esp_gatt_if: %d] - %d", gatts_if, event);
for (auto *gatts_handler : this->gatts_event_handlers_) { for (auto *gatts_handler : this->gatts_event_handlers_) {
gatts_handler->gatts_event_handler(event, gatts_if, param); gatts_handler->gatts_event_handler(event, gatts_if, param);
@@ -316,7 +316,7 @@ void ESP32BLE::loop() {
case BLEEvent::GATTC: { case BLEEvent::GATTC: {
esp_gattc_cb_event_t event = ble_event->event_.gattc.gattc_event; esp_gattc_cb_event_t event = ble_event->event_.gattc.gattc_event;
esp_gatt_if_t gattc_if = ble_event->event_.gattc.gattc_if; esp_gatt_if_t gattc_if = ble_event->event_.gattc.gattc_if;
esp_ble_gattc_cb_param_t *param = ble_event->event_.gattc.gattc_param; esp_ble_gattc_cb_param_t *param = &ble_event->event_.gattc.gattc_param;
ESP_LOGV(TAG, "gattc_event [esp_gatt_if: %d] - %d", gattc_if, event); ESP_LOGV(TAG, "gattc_event [esp_gatt_if: %d] - %d", gattc_if, event);
for (auto *gattc_handler : this->gattc_event_handlers_) { for (auto *gattc_handler : this->gattc_event_handlers_) {
gattc_handler->gattc_event_handler(event, gattc_if, param); gattc_handler->gattc_event_handler(event, gattc_if, param);

View File

@@ -61,10 +61,24 @@ static_assert(offsetof(esp_ble_gap_cb_param_t, read_rssi_cmpl.rssi) == sizeof(es
static_assert(offsetof(esp_ble_gap_cb_param_t, read_rssi_cmpl.remote_addr) == sizeof(esp_bt_status_t) + sizeof(int8_t), static_assert(offsetof(esp_ble_gap_cb_param_t, read_rssi_cmpl.remote_addr) == sizeof(esp_bt_status_t) + sizeof(int8_t),
"remote_addr must follow rssi in read_rssi_cmpl"); "remote_addr must follow rssi in read_rssi_cmpl");
// Param struct sizes on ESP32
static constexpr size_t GATTC_PARAM_SIZE = 28;
static constexpr size_t GATTS_PARAM_SIZE = 32;
// Maximum size for inline storage of data
// GATTC: 80 - 28 (param) - 8 (other fields) = 44 bytes for data
// GATTS: 80 - 32 (param) - 8 (other fields) = 40 bytes for data
static constexpr size_t GATTC_INLINE_DATA_SIZE = 44;
static constexpr size_t GATTS_INLINE_DATA_SIZE = 40;
// Verify param struct sizes
static_assert(sizeof(esp_ble_gattc_cb_param_t) == GATTC_PARAM_SIZE, "GATTC param size unexpected");
static_assert(sizeof(esp_ble_gatts_cb_param_t) == GATTS_PARAM_SIZE, "GATTS param size unexpected");
// Received GAP, GATTC and GATTS events are only queued, and get processed in the main loop(). // Received GAP, GATTC and GATTS events are only queued, and get processed in the main loop().
// This class stores each event with minimal memory usage. // This class stores each event with minimal memory usage.
// GAP events (99% of traffic) don't have the heap allocation overhead. // GAP events (99% of traffic) don't have the heap allocation overhead.
// GATTC/GATTS events use heap allocation for their param and data. // GATTC/GATTS events use heap allocation for their param and inline storage for small data.
// //
// Event flow: // Event flow:
// 1. ESP-IDF BLE stack calls our static handlers in the BLE task context // 1. ESP-IDF BLE stack calls our static handlers in the BLE task context
@@ -111,21 +125,21 @@ class BLEEvent {
this->init_gap_data_(e, p); this->init_gap_data_(e, p);
} }
// Constructor for GATTC events - uses heap allocation // Constructor for GATTC events - param stored inline, data may use heap
// IMPORTANT: The heap allocation is REQUIRED and must not be removed as an optimization. // IMPORTANT: We MUST copy the param struct because the pointer from ESP-IDF
// The param pointer from ESP-IDF is only valid during the callback execution. // is only valid during the callback execution. Since BLE events are processed
// Since BLE events are processed asynchronously in the main loop, we must create // asynchronously in the main loop, we store our own copy inline to ensure
// our own copy to ensure the data remains valid until the event is processed. // the data remains valid until the event is processed.
BLEEvent(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) { BLEEvent(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
this->type_ = GATTC; this->type_ = GATTC;
this->init_gattc_data_(e, i, p); this->init_gattc_data_(e, i, p);
} }
// Constructor for GATTS events - uses heap allocation // Constructor for GATTS events - param stored inline, data may use heap
// IMPORTANT: The heap allocation is REQUIRED and must not be removed as an optimization. // IMPORTANT: We MUST copy the param struct because the pointer from ESP-IDF
// The param pointer from ESP-IDF is only valid during the callback execution. // is only valid during the callback execution. Since BLE events are processed
// Since BLE events are processed asynchronously in the main loop, we must create // asynchronously in the main loop, we store our own copy inline to ensure
// our own copy to ensure the data remains valid until the event is processed. // the data remains valid until the event is processed.
BLEEvent(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) { BLEEvent(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
this->type_ = GATTS; this->type_ = GATTS;
this->init_gatts_data_(e, i, p); this->init_gatts_data_(e, i, p);
@@ -135,27 +149,32 @@ class BLEEvent {
~BLEEvent() { this->release(); } ~BLEEvent() { this->release(); }
// Default constructor for pre-allocation in pool // Default constructor for pre-allocation in pool
BLEEvent() : type_(GAP) {} BLEEvent() : event_{}, type_(GAP) {}
// Invoked on return to EventPool - clean up any heap-allocated data // Invoked on return to EventPool - clean up any heap-allocated data
void release() { void release() {
if (this->type_ == GAP) { switch (this->type_) {
return; case GAP:
// GAP events don't have heap allocations
break;
case GATTC:
// Param is now stored inline, only delete heap data if it was heap-allocated
if (!this->event_.gattc.is_inline && this->event_.gattc.data.heap_data != nullptr) {
delete[] this->event_.gattc.data.heap_data;
} }
if (this->type_ == GATTC) { // Clear critical fields to prevent issues if type changes
delete this->event_.gattc.gattc_param; this->event_.gattc.is_inline = false;
delete[] this->event_.gattc.data; this->event_.gattc.data.heap_data = nullptr;
this->event_.gattc.gattc_param = nullptr; break;
this->event_.gattc.data = nullptr; case GATTS:
this->event_.gattc.data_len = 0; // Param is now stored inline, only delete heap data if it was heap-allocated
return; if (!this->event_.gatts.is_inline && this->event_.gatts.data.heap_data != nullptr) {
delete[] this->event_.gatts.data.heap_data;
} }
if (this->type_ == GATTS) { // Clear critical fields to prevent issues if type changes
delete this->event_.gatts.gatts_param; this->event_.gatts.is_inline = false;
delete[] this->event_.gatts.data; this->event_.gatts.data.heap_data = nullptr;
this->event_.gatts.gatts_param = nullptr; break;
this->event_.gatts.data = nullptr;
this->event_.gatts.data_len = 0;
} }
} }
@@ -207,21 +226,29 @@ class BLEEvent {
// NOLINTNEXTLINE(readability-identifier-naming) // NOLINTNEXTLINE(readability-identifier-naming)
struct gattc_event { struct gattc_event {
esp_gattc_cb_event_t gattc_event; esp_ble_gattc_cb_param_t gattc_param; // Stored inline (28 bytes)
esp_gatt_if_t gattc_if; esp_gattc_cb_event_t gattc_event; // 4 bytes
esp_ble_gattc_cb_param_t *gattc_param; // Heap-allocated union {
uint8_t *data; // Heap-allocated raw buffer (manually managed) uint8_t *heap_data; // 4 bytes when heap-allocated
uint16_t data_len; // Track size separately uint8_t inline_data[GATTC_INLINE_DATA_SIZE]; // 44 bytes when stored inline
} gattc; } data; // 44 bytes total
uint16_t data_len; // 2 bytes
esp_gatt_if_t gattc_if; // 1 byte
bool is_inline; // 1 byte - true when data is stored inline
} gattc; // Total: 80 bytes
// NOLINTNEXTLINE(readability-identifier-naming) // NOLINTNEXTLINE(readability-identifier-naming)
struct gatts_event { struct gatts_event {
esp_gatts_cb_event_t gatts_event; esp_ble_gatts_cb_param_t gatts_param; // Stored inline (32 bytes)
esp_gatt_if_t gatts_if; esp_gatts_cb_event_t gatts_event; // 4 bytes
esp_ble_gatts_cb_param_t *gatts_param; // Heap-allocated union {
uint8_t *data; // Heap-allocated raw buffer (manually managed) uint8_t *heap_data; // 4 bytes when heap-allocated
uint16_t data_len; // Track size separately uint8_t inline_data[GATTS_INLINE_DATA_SIZE]; // 40 bytes when stored inline
} gatts; } data; // 40 bytes total
uint16_t data_len; // 2 bytes
esp_gatt_if_t gatts_if; // 1 byte
bool is_inline; // 1 byte - true when data is stored inline
} gatts; // Total: 80 bytes
} event_; // 80 bytes } event_; // 80 bytes
ble_event_t type_; ble_event_t type_;
@@ -236,6 +263,29 @@ class BLEEvent {
const esp_ble_sec_t &security() const { return event_.gap.security; } const esp_ble_sec_t &security() const { return event_.gap.security; }
private: private:
// Helper to copy data with inline storage optimization
template<typename EventStruct, size_t InlineSize>
void copy_data_with_inline_storage_(EventStruct &event, const uint8_t *src_data, uint16_t len,
uint8_t **param_value_ptr) {
event.data_len = len;
if (len > 0) {
if (len <= InlineSize) {
event.is_inline = true;
memcpy(event.data.inline_data, src_data, len);
*param_value_ptr = event.data.inline_data;
} else {
event.is_inline = false;
event.data.heap_data = new uint8_t[len];
memcpy(event.data.heap_data, src_data, len);
*param_value_ptr = event.data.heap_data;
}
} else {
event.is_inline = false;
event.data.heap_data = nullptr;
*param_value_ptr = nullptr;
}
}
// Initialize GAP event data // Initialize GAP event data
void init_gap_data_(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) { void init_gap_data_(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
this->event_.gap.gap_event = e; this->event_.gap.gap_event = e;
@@ -320,48 +370,37 @@ class BLEEvent {
this->event_.gattc.gattc_if = i; this->event_.gattc.gattc_if = i;
if (p == nullptr) { if (p == nullptr) {
this->event_.gattc.gattc_param = nullptr; // Zero out the param struct when null
this->event_.gattc.data = nullptr; memset(&this->event_.gattc.gattc_param, 0, sizeof(this->event_.gattc.gattc_param));
this->event_.gattc.is_inline = false;
this->event_.gattc.data.heap_data = nullptr;
this->event_.gattc.data_len = 0; this->event_.gattc.data_len = 0;
return; // Invalid event, but we can't log in header file return; // Invalid event, but we can't log in header file
} }
// Heap-allocate param and data // Copy param struct inline (no heap allocation!)
// Heap allocation is used because GATTC/GATTS events are rare (<1% of events) // GATTC/GATTS events are rare (<1% of events) but we can still store them inline
// while GAP events (99%) are stored inline to minimize memory usage // along with small data payloads, eliminating all heap allocations for typical BLE operations
// IMPORTANT: This heap allocation provides clear ownership semantics: // CRITICAL: This copy is REQUIRED for memory safety - the ESP-IDF param pointer
// - The BLEEvent owns the allocated memory for its lifetime // is only valid during the callback and will be reused/freed after we return
// - The data remains valid from the BLE callback context until processed in the main loop this->event_.gattc.gattc_param = *p;
// - Without this copy, we'd have use-after-free bugs as ESP-IDF reuses the callback memory
this->event_.gattc.gattc_param = new esp_ble_gattc_cb_param_t(*p);
// Copy data for events that need it // Copy data for events that need it
// The param struct contains pointers (e.g., notify.value) that point to temporary buffers. // The param struct contains pointers (e.g., notify.value) that point to temporary buffers.
// We must copy this data to ensure it remains valid when the event is processed later. // We must copy this data to ensure it remains valid when the event is processed later.
switch (e) { switch (e) {
case ESP_GATTC_NOTIFY_EVT: case ESP_GATTC_NOTIFY_EVT:
this->event_.gattc.data_len = p->notify.value_len; copy_data_with_inline_storage_<decltype(this->event_.gattc), GATTC_INLINE_DATA_SIZE>(
if (p->notify.value_len > 0) { this->event_.gattc, p->notify.value, p->notify.value_len, &this->event_.gattc.gattc_param.notify.value);
this->event_.gattc.data = new uint8_t[p->notify.value_len];
memcpy(this->event_.gattc.data, p->notify.value, p->notify.value_len);
} else {
this->event_.gattc.data = nullptr;
}
this->event_.gattc.gattc_param->notify.value = this->event_.gattc.data;
break; break;
case ESP_GATTC_READ_CHAR_EVT: case ESP_GATTC_READ_CHAR_EVT:
case ESP_GATTC_READ_DESCR_EVT: case ESP_GATTC_READ_DESCR_EVT:
this->event_.gattc.data_len = p->read.value_len; copy_data_with_inline_storage_<decltype(this->event_.gattc), GATTC_INLINE_DATA_SIZE>(
if (p->read.value_len > 0) { this->event_.gattc, p->read.value, p->read.value_len, &this->event_.gattc.gattc_param.read.value);
this->event_.gattc.data = new uint8_t[p->read.value_len];
memcpy(this->event_.gattc.data, p->read.value, p->read.value_len);
} else {
this->event_.gattc.data = nullptr;
}
this->event_.gattc.gattc_param->read.value = this->event_.gattc.data;
break; break;
default: default:
this->event_.gattc.data = nullptr; this->event_.gattc.is_inline = false;
this->event_.gattc.data.heap_data = nullptr;
this->event_.gattc.data_len = 0; this->event_.gattc.data_len = 0;
break; break;
} }
@@ -373,37 +412,32 @@ class BLEEvent {
this->event_.gatts.gatts_if = i; this->event_.gatts.gatts_if = i;
if (p == nullptr) { if (p == nullptr) {
this->event_.gatts.gatts_param = nullptr; // Zero out the param struct when null
this->event_.gatts.data = nullptr; memset(&this->event_.gatts.gatts_param, 0, sizeof(this->event_.gatts.gatts_param));
this->event_.gatts.is_inline = false;
this->event_.gatts.data.heap_data = nullptr;
this->event_.gatts.data_len = 0; this->event_.gatts.data_len = 0;
return; // Invalid event, but we can't log in header file return; // Invalid event, but we can't log in header file
} }
// Heap-allocate param and data // Copy param struct inline (no heap allocation!)
// Heap allocation is used because GATTC/GATTS events are rare (<1% of events) // GATTC/GATTS events are rare (<1% of events) but we can still store them inline
// while GAP events (99%) are stored inline to minimize memory usage // along with small data payloads, eliminating all heap allocations for typical BLE operations
// IMPORTANT: This heap allocation provides clear ownership semantics: // CRITICAL: This copy is REQUIRED for memory safety - the ESP-IDF param pointer
// - The BLEEvent owns the allocated memory for its lifetime // is only valid during the callback and will be reused/freed after we return
// - The data remains valid from the BLE callback context until processed in the main loop this->event_.gatts.gatts_param = *p;
// - Without this copy, we'd have use-after-free bugs as ESP-IDF reuses the callback memory
this->event_.gatts.gatts_param = new esp_ble_gatts_cb_param_t(*p);
// Copy data for events that need it // Copy data for events that need it
// The param struct contains pointers (e.g., write.value) that point to temporary buffers. // The param struct contains pointers (e.g., write.value) that point to temporary buffers.
// We must copy this data to ensure it remains valid when the event is processed later. // We must copy this data to ensure it remains valid when the event is processed later.
switch (e) { switch (e) {
case ESP_GATTS_WRITE_EVT: case ESP_GATTS_WRITE_EVT:
this->event_.gatts.data_len = p->write.len; copy_data_with_inline_storage_<decltype(this->event_.gatts), GATTS_INLINE_DATA_SIZE>(
if (p->write.len > 0) { this->event_.gatts, p->write.value, p->write.len, &this->event_.gatts.gatts_param.write.value);
this->event_.gatts.data = new uint8_t[p->write.len];
memcpy(this->event_.gatts.data, p->write.value, p->write.len);
} else {
this->event_.gatts.data = nullptr;
}
this->event_.gatts.gatts_param->write.value = this->event_.gatts.data;
break; break;
default: default:
this->event_.gatts.data = nullptr; this->event_.gatts.is_inline = false;
this->event_.gatts.data.heap_data = nullptr;
this->event_.gatts.data_len = 0; this->event_.gatts.data_len = 0;
break; break;
} }
@@ -414,6 +448,15 @@ class BLEEvent {
// The gap member in the union should be 80 bytes (including the gap_event enum) // The gap member in the union should be 80 bytes (including the gap_event enum)
static_assert(sizeof(decltype(((BLEEvent *) nullptr)->event_.gap)) <= 80, "gap_event struct has grown beyond 80 bytes"); static_assert(sizeof(decltype(((BLEEvent *) nullptr)->event_.gap)) <= 80, "gap_event struct has grown beyond 80 bytes");
// Verify GATTC and GATTS structs don't exceed GAP struct size
// This ensures the union size is determined by GAP (the most common event type)
static_assert(sizeof(decltype(((BLEEvent *) nullptr)->event_.gattc)) <=
sizeof(decltype(((BLEEvent *) nullptr)->event_.gap)),
"gattc_event struct exceeds gap_event size - union size would increase");
static_assert(sizeof(decltype(((BLEEvent *) nullptr)->event_.gatts)) <=
sizeof(decltype(((BLEEvent *) nullptr)->event_.gap)),
"gatts_event struct exceeds gap_event size - union size would increase");
// Verify esp_ble_sec_t fits within our union // Verify esp_ble_sec_t fits within our union
static_assert(sizeof(esp_ble_sec_t) <= 73, "esp_ble_sec_t is larger than BLEScanResult"); static_assert(sizeof(esp_ble_sec_t) <= 73, "esp_ble_sec_t is larger than BLEScanResult");

View File

@@ -7,6 +7,7 @@
#include <esp_gap_ble_api.h> #include <esp_gap_ble_api.h>
#include <esp_gatt_defs.h> #include <esp_gatt_defs.h>
#include <esp_gattc_api.h>
namespace esphome::esp32_ble_client { namespace esphome::esp32_ble_client {
@@ -92,7 +93,7 @@ bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
return false; return false;
if (this->address_ == 0 || device.address_uint64() != this->address_) if (this->address_ == 0 || device.address_uint64() != this->address_)
return false; return false;
if (this->state_ != espbt::ClientState::IDLE && this->state_ != espbt::ClientState::SEARCHING) if (this->state_ != espbt::ClientState::IDLE)
return false; return false;
this->log_event_("Found device"); this->log_event_("Found device");
@@ -111,43 +112,19 @@ void BLEClientBase::connect() {
this->remote_addr_type_); this->remote_addr_type_);
this->paired_ = false; this->paired_ = false;
// Set preferred connection parameters before connecting // Determine connection parameters based on connection type
// Use FAST for all V3 connections (better latency and reliability) if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
// Use MEDIUM for V1/legacy connections (balanced performance) // V3 without cache needs fast params for service discovery
uint16_t min_interval, max_interval, timeout; this->set_conn_params_(FAST_MIN_CONN_INTERVAL, FAST_MAX_CONN_INTERVAL, 0, FAST_CONN_TIMEOUT, "fast");
const char *param_type; } else if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
// V3 with cache can use medium params
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE || this->set_conn_params_(MEDIUM_MIN_CONN_INTERVAL, MEDIUM_MAX_CONN_INTERVAL, 0, MEDIUM_CONN_TIMEOUT, "medium");
this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
min_interval = FAST_MIN_CONN_INTERVAL;
max_interval = FAST_MAX_CONN_INTERVAL;
timeout = FAST_CONN_TIMEOUT;
param_type = "fast";
} else {
min_interval = MEDIUM_MIN_CONN_INTERVAL;
max_interval = MEDIUM_MAX_CONN_INTERVAL;
timeout = MEDIUM_CONN_TIMEOUT;
param_type = "medium";
} }
// For V1/Legacy, don't set params - use ESP-IDF defaults
auto param_ret = esp_ble_gap_set_prefer_conn_params(this->remote_bda_, min_interval, max_interval, // Open the connection
0, // latency: 0
timeout);
if (param_ret != ESP_OK) {
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gap_set_prefer_conn_params failed: %d", this->connection_index_,
this->address_str_.c_str(), param_ret);
} else {
this->log_connection_params_(param_type);
}
// Now open the connection
auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true); auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true);
if (ret) { this->handle_connection_result_(ret);
this->log_gattc_warning_("esp_ble_gattc_open", ret);
this->set_state(espbt::ClientState::IDLE);
} else {
this->set_state(espbt::ClientState::CONNECTING);
}
} }
esp_err_t BLEClientBase::pair() { return esp_ble_set_encryption(this->remote_bda_, ESP_BLE_SEC_ENCRYPT); } esp_err_t BLEClientBase::pair() { return esp_ble_set_encryption(this->remote_bda_, ESP_BLE_SEC_ENCRYPT); }
@@ -159,7 +136,7 @@ void BLEClientBase::disconnect() {
return; return;
} }
if (this->state_ == espbt::ClientState::CONNECTING || this->conn_id_ == UNSET_CONN_ID) { if (this->state_ == espbt::ClientState::CONNECTING || this->conn_id_ == UNSET_CONN_ID) {
ESP_LOGW(TAG, "[%d] [%s] Disconnecting before connected, disconnect scheduled.", this->connection_index_, ESP_LOGD(TAG, "[%d] [%s] Disconnect before connected, disconnect scheduled", this->connection_index_,
this->address_str_.c_str()); this->address_str_.c_str());
this->want_disconnect_ = true; this->want_disconnect_ = true;
return; return;
@@ -172,13 +149,11 @@ void BLEClientBase::unconditional_disconnect() {
ESP_LOGI(TAG, "[%d] [%s] Disconnecting (conn_id: %d).", this->connection_index_, this->address_str_.c_str(), ESP_LOGI(TAG, "[%d] [%s] Disconnecting (conn_id: %d).", this->connection_index_, this->address_str_.c_str(),
this->conn_id_); this->conn_id_);
if (this->state_ == espbt::ClientState::DISCONNECTING) { if (this->state_ == espbt::ClientState::DISCONNECTING) {
ESP_LOGE(TAG, "[%d] [%s] Tried to disconnect while already disconnecting.", this->connection_index_, this->log_error_("Already disconnecting");
this->address_str_.c_str());
return; return;
} }
if (this->conn_id_ == UNSET_CONN_ID) { if (this->conn_id_ == UNSET_CONN_ID) {
ESP_LOGE(TAG, "[%d] [%s] No connection ID set, cannot disconnect.", this->connection_index_, this->log_error_("conn id unset, cannot disconnect");
this->address_str_.c_str());
return; return;
} }
auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_); auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
@@ -193,8 +168,7 @@ void BLEClientBase::unconditional_disconnect() {
this->log_gattc_warning_("esp_ble_gattc_close", err); this->log_gattc_warning_("esp_ble_gattc_close", err);
} }
if (this->state_ == espbt::ClientState::SEARCHING || this->state_ == espbt::ClientState::READY_TO_CONNECT || if (this->state_ == espbt::ClientState::READY_TO_CONNECT || this->state_ == espbt::ClientState::DISCOVERED) {
this->state_ == espbt::ClientState::DISCOVERED) {
this->set_address(0); this->set_address(0);
this->set_state(espbt::ClientState::IDLE); this->set_state(espbt::ClientState::IDLE);
} else { } else {
@@ -234,17 +208,51 @@ void BLEClientBase::log_connection_params_(const char *param_type) {
ESP_LOGD(TAG, "[%d] [%s] %s conn params", this->connection_index_, this->address_str_.c_str(), param_type); ESP_LOGD(TAG, "[%d] [%s] %s conn params", this->connection_index_, this->address_str_.c_str(), param_type);
} }
void BLEClientBase::restore_medium_conn_params_() { void BLEClientBase::handle_connection_result_(esp_err_t ret) {
// Restore to medium connection parameters after initial connection phase if (ret) {
// This balances performance with bandwidth usage for normal operation this->log_gattc_warning_("esp_ble_gattc_open", ret);
this->set_state(espbt::ClientState::IDLE);
} else {
this->set_state(espbt::ClientState::CONNECTING);
}
}
void BLEClientBase::log_error_(const char *message) {
ESP_LOGE(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_.c_str(), message);
}
void BLEClientBase::log_error_(const char *message, int code) {
ESP_LOGE(TAG, "[%d] [%s] %s=%d", this->connection_index_, this->address_str_.c_str(), message, code);
}
void BLEClientBase::log_warning_(const char *message) {
ESP_LOGW(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_.c_str(), message);
}
void BLEClientBase::update_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency,
uint16_t timeout, const char *param_type) {
esp_ble_conn_update_params_t conn_params = {{0}}; esp_ble_conn_update_params_t conn_params = {{0}};
memcpy(conn_params.bda, this->remote_bda_, sizeof(esp_bd_addr_t)); memcpy(conn_params.bda, this->remote_bda_, sizeof(esp_bd_addr_t));
conn_params.min_int = MEDIUM_MIN_CONN_INTERVAL; conn_params.min_int = min_interval;
conn_params.max_int = MEDIUM_MAX_CONN_INTERVAL; conn_params.max_int = max_interval;
conn_params.latency = 0; conn_params.latency = latency;
conn_params.timeout = MEDIUM_CONN_TIMEOUT; conn_params.timeout = timeout;
this->log_connection_params_("medium"); this->log_connection_params_(param_type);
esp_ble_gap_update_conn_params(&conn_params); esp_err_t err = esp_ble_gap_update_conn_params(&conn_params);
if (err != ESP_OK) {
this->log_gattc_warning_("esp_ble_gap_update_conn_params", err);
}
}
void BLEClientBase::set_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout,
const char *param_type) {
// Set preferred connection parameters before connecting
// These will be used when establishing the connection
this->log_connection_params_(param_type);
esp_err_t err = esp_ble_gap_set_prefer_conn_params(this->remote_bda_, min_interval, max_interval, latency, timeout);
if (err != ESP_OK) {
this->log_gattc_warning_("esp_ble_gap_set_prefer_conn_params", err);
}
} }
bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if, bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
@@ -264,8 +272,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
this->app_id); this->app_id);
this->gattc_if_ = esp_gattc_if; this->gattc_if_ = esp_gattc_if;
} else { } else {
ESP_LOGE(TAG, "[%d] [%s] gattc app registration failed id=%d code=%d", this->connection_index_, this->log_error_("gattc app registration failed status", param->reg.status);
this->address_str_.c_str(), param->reg.app_id, param->reg.status);
this->status_ = param->reg.status; this->status_ = param->reg.status;
this->mark_failed(); this->mark_failed();
} }
@@ -277,11 +284,21 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
this->log_gattc_event_("OPEN"); this->log_gattc_event_("OPEN");
// conn_id was already set in ESP_GATTC_CONNECT_EVT // conn_id was already set in ESP_GATTC_CONNECT_EVT
this->service_count_ = 0; this->service_count_ = 0;
// ESP-IDF's BLE stack may send ESP_GATTC_OPEN_EVT after esp_ble_gattc_open() returns an
// error, if the error occurred at the BTA/GATT layer. This can result in the event
// arriving after we've already transitioned to IDLE state.
if (this->state_ == espbt::ClientState::IDLE) {
ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT in IDLE state (status=%d), ignoring", this->connection_index_,
this->address_str_.c_str(), param->open.status);
break;
}
if (this->state_ != espbt::ClientState::CONNECTING) { if (this->state_ != espbt::ClientState::CONNECTING) {
// This should not happen but lets log it in case it does // This should not happen but lets log it in case it does
// because it means we have a bad assumption about how the // because it means we have a bad assumption about how the
// ESP BT stack works. // ESP BT stack works.
ESP_LOGE(TAG, "[%d] [%s] Got ESP_GATTC_OPEN_EVT while in %s state, status=%d", this->connection_index_, ESP_LOGE(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT in %s state (status=%d)", this->connection_index_,
this->address_str_.c_str(), espbt::client_state_to_string(this->state_), param->open.status); this->address_str_.c_str(), espbt::client_state_to_string(this->state_), param->open.status);
} }
if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) { if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
@@ -301,13 +318,14 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
this->set_state(espbt::ClientState::CONNECTED); this->set_state(espbt::ClientState::CONNECTED);
ESP_LOGI(TAG, "[%d] [%s] Connection open", this->connection_index_, this->address_str_.c_str()); ESP_LOGI(TAG, "[%d] [%s] Connection open", this->connection_index_, this->address_str_.c_str());
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) { if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
// Restore to medium connection parameters for cached connections too // Cached connections already connected with medium parameters, no update needed
this->restore_medium_conn_params_();
// only set our state, subclients might have more stuff to do yet. // only set our state, subclients might have more stuff to do yet.
this->state_ = espbt::ClientState::ESTABLISHED; this->state_ = espbt::ClientState::ESTABLISHED;
break; break;
} }
ESP_LOGD(TAG, "[%d] [%s] Searching for services", this->connection_index_, this->address_str_.c_str()); // For V3_WITHOUT_CACHE, we already set fast params before connecting
// No need to update them again here
this->log_event_("Searching for services");
esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr); esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr);
break; break;
} }
@@ -332,8 +350,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
// Check if we were disconnected while waiting for service discovery // Check if we were disconnected while waiting for service discovery
if (param->disconnect.reason == ESP_GATT_CONN_TERMINATE_PEER_USER && if (param->disconnect.reason == ESP_GATT_CONN_TERMINATE_PEER_USER &&
this->state_ == espbt::ClientState::CONNECTED) { this->state_ == espbt::ClientState::CONNECTED) {
ESP_LOGW(TAG, "[%d] [%s] Disconnected by remote during service discovery", this->connection_index_, this->log_warning_("Remote closed during discovery");
this->address_str_.c_str());
} else { } else {
ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason 0x%02x", this->connection_index_, ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason 0x%02x", this->connection_index_,
this->address_str_.c_str(), param->disconnect.reason); this->address_str_.c_str(), param->disconnect.reason);
@@ -389,12 +406,11 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
if (this->conn_id_ != param->search_cmpl.conn_id) if (this->conn_id_ != param->search_cmpl.conn_id)
return false; return false;
this->log_gattc_event_("SEARCH_CMPL"); this->log_gattc_event_("SEARCH_CMPL");
// For V3 connections, restore to medium connection parameters after service discovery // For V3_WITHOUT_CACHE, switch back to medium connection parameters after service discovery
// This balances performance with bandwidth usage after the critical discovery phase // This balances performance with bandwidth usage after the critical discovery phase
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE || if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) { this->update_conn_params_(MEDIUM_MIN_CONN_INTERVAL, MEDIUM_MAX_CONN_INTERVAL, 0, MEDIUM_CONN_TIMEOUT, "medium");
this->restore_medium_conn_params_(); } else if (this->connection_type_ != espbt::ConnectionType::V3_WITH_CACHE) {
} else {
#ifdef USE_ESP32_BLE_DEVICE #ifdef USE_ESP32_BLE_DEVICE
for (auto &svc : this->services_) { for (auto &svc : this->services_) {
ESP_LOGV(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_.c_str(), ESP_LOGV(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_.c_str(),
@@ -478,6 +494,11 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
break; break;
} }
case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
this->log_gattc_event_("UNREG_FOR_NOTIFY");
break;
}
default: default:
// ideally would check all other events for matching conn_id // ideally would check all other events for matching conn_id
ESP_LOGD(TAG, "[%d] [%s] Event %d", this->connection_index_, this->address_str_.c_str(), event); ESP_LOGD(TAG, "[%d] [%s] Event %d", this->connection_index_, this->address_str_.c_str(), event);
@@ -506,16 +527,14 @@ void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_
return; return;
esp_bd_addr_t bd_addr; esp_bd_addr_t bd_addr;
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t)); memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
ESP_LOGI(TAG, "[%d] [%s] auth complete. remote BD_ADDR: %s", this->connection_index_, this->address_str_.c_str(), ESP_LOGI(TAG, "[%d] [%s] auth complete addr: %s", this->connection_index_, this->address_str_.c_str(),
format_hex(bd_addr, 6).c_str()); format_hex(bd_addr, 6).c_str());
if (!param->ble_security.auth_cmpl.success) { if (!param->ble_security.auth_cmpl.success) {
ESP_LOGE(TAG, "[%d] [%s] auth fail reason = 0x%x", this->connection_index_, this->address_str_.c_str(), this->log_error_("auth fail reason", param->ble_security.auth_cmpl.fail_reason);
param->ble_security.auth_cmpl.fail_reason);
} else { } else {
this->paired_ = true; this->paired_ = true;
ESP_LOGD(TAG, "[%d] [%s] auth success. address type = %d auth mode = %d", this->connection_index_, ESP_LOGD(TAG, "[%d] [%s] auth success type = %d mode = %d", this->connection_index_, this->address_str_.c_str(),
this->address_str_.c_str(), param->ble_security.auth_cmpl.addr_type, param->ble_security.auth_cmpl.addr_type, param->ble_security.auth_cmpl.auth_mode);
param->ble_security.auth_cmpl.auth_mode);
} }
break; break;

View File

@@ -133,10 +133,18 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
void log_event_(const char *name); void log_event_(const char *name);
void log_gattc_event_(const char *name); void log_gattc_event_(const char *name);
void restore_medium_conn_params_(); void update_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout,
const char *param_type);
void set_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout,
const char *param_type);
void log_gattc_warning_(const char *operation, esp_gatt_status_t status); void log_gattc_warning_(const char *operation, esp_gatt_status_t status);
void log_gattc_warning_(const char *operation, esp_err_t err); void log_gattc_warning_(const char *operation, esp_err_t err);
void log_connection_params_(const char *param_type); void log_connection_params_(const char *param_type);
void handle_connection_result_(esp_err_t ret);
// Compact error logging helpers to reduce flash usage
void log_error_(const char *message);
void log_error_(const char *message, int code);
void log_warning_(const char *message);
}; };
} // namespace esphome::esp32_ble_client } // namespace esphome::esp32_ble_client

View File

@@ -30,7 +30,7 @@ from esphome.const import (
CONF_SERVICE_UUID, CONF_SERVICE_UUID,
CONF_TRIGGER_ID, 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.enum import StrEnum
from esphome.types import ConfigType 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 # 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 # chance to call register_ble_tracker and register_client before the list is checked
# and added to the global defines list. # and added to the global defines list.
@coroutine_with_priority(-1000) @coroutine_with_priority(CoroPriority.FINAL)
async def _add_ble_features(): async def _add_ble_features():
# Add feature-specific defines based on what's needed # Add feature-specific defines based on what's needed
if BLEFeatures.ESP_BT_DEVICE in _required_features: if BLEFeatures.ESP_BT_DEVICE in _required_features:

View File

@@ -80,14 +80,17 @@ class BLEManufacturerDataAdvertiseTrigger : public Trigger<const adv_data_t &>,
ESPBTUUID uuid_; ESPBTUUID uuid_;
}; };
#endif // USE_ESP32_BLE_DEVICE
class BLEEndOfScanTrigger : public Trigger<>, public ESPBTDeviceListener { class BLEEndOfScanTrigger : public Trigger<>, public ESPBTDeviceListener {
public: public:
explicit BLEEndOfScanTrigger(ESP32BLETracker *parent) { parent->register_listener(this); } explicit BLEEndOfScanTrigger(ESP32BLETracker *parent) { parent->register_listener(this); }
#ifdef USE_ESP32_BLE_DEVICE
bool parse_device(const ESPBTDevice &device) override { return false; } bool parse_device(const ESPBTDevice &device) override { return false; }
#endif
void on_scan_end() override { this->trigger(); } void on_scan_end() override { this->trigger(); }
}; };
#endif // USE_ESP32_BLE_DEVICE
template<typename... Ts> class ESP32BLEStartScanAction : public Action<Ts...> { template<typename... Ts> class ESP32BLEStartScanAction : public Action<Ts...> {
public: public:

View File

@@ -49,8 +49,6 @@ const char *client_state_to_string(ClientState state) {
return "DISCONNECTING"; return "DISCONNECTING";
case ClientState::IDLE: case ClientState::IDLE:
return "IDLE"; return "IDLE";
case ClientState::SEARCHING:
return "SEARCHING";
case ClientState::DISCOVERED: case ClientState::DISCOVERED:
return "DISCOVERED"; return "DISCOVERED";
case ClientState::READY_TO_CONNECT: case ClientState::READY_TO_CONNECT:
@@ -136,9 +134,8 @@ void ESP32BLETracker::loop() {
ClientStateCounts counts = this->count_client_states_(); ClientStateCounts counts = this->count_client_states_();
if (counts != this->client_state_counts_) { if (counts != this->client_state_counts_) {
this->client_state_counts_ = counts; this->client_state_counts_ = counts;
ESP_LOGD(TAG, "connecting: %d, discovered: %d, searching: %d, disconnecting: %d", ESP_LOGD(TAG, "connecting: %d, discovered: %d, disconnecting: %d", this->client_state_counts_.connecting,
this->client_state_counts_.connecting, this->client_state_counts_.discovered, this->client_state_counts_.discovered, this->client_state_counts_.disconnecting);
this->client_state_counts_.searching, this->client_state_counts_.disconnecting);
} }
if (this->scanner_state_ == ScannerState::FAILED || if (this->scanner_state_ == ScannerState::FAILED ||
@@ -158,10 +155,8 @@ void ESP32BLETracker::loop() {
https://github.com/espressif/esp-idf/issues/6688 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 && if (this->scanner_state_ == ScannerState::IDLE && !counts.connecting && !counts.disconnecting && !counts.discovered) {
!promote_to_connecting) {
#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE #ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
this->update_coex_preference_(false); this->update_coex_preference_(false);
#endif #endif
@@ -170,12 +165,11 @@ void ESP32BLETracker::loop() {
} }
} }
// If there is a discovered client and no connecting // If there is a discovered client and no connecting
// clients and no clients using the scanner to search for // clients, then promote the discovered client to ready to connect.
// devices, then promote the discovered client to ready to connect.
// We check both RUNNING and IDLE states because: // We check both RUNNING and IDLE states because:
// - RUNNING: gap_scan_event_handler initiates stop_scan_() but promotion can happen immediately // - 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) // - 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->scanner_state_ == ScannerState::RUNNING || this->scanner_state_ == ScannerState::IDLE)) {
this->try_promote_discovered_clients_(); 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) { if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
// Process the scan result immediately // Process the scan result immediately
bool found_discovered_client = this->process_scan_result_(scan_result); 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_();
}
} else if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_CMPL_EVT) { } else if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_CMPL_EVT) {
// Scan finished on its own // Scan finished on its own
if (this->scanner_state_ != ScannerState::RUNNING) { 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_duration_, this->scan_interval_ * 0.625f, this->scan_window_ * 0.625f,
this->scan_active_ ? "ACTIVE" : "PASSIVE", YESNO(this->scan_continuous_)); 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, " Scanner State: %s", this->scanner_state_to_string_(this->scanner_state_));
ESP_LOGCONFIG(TAG, " Connecting: %d, discovered: %d, searching: %d, disconnecting: %d", ESP_LOGCONFIG(TAG, " Connecting: %d, discovered: %d, disconnecting: %d", this->client_state_counts_.connecting,
this->client_state_counts_.connecting, this->client_state_counts_.discovered, this->client_state_counts_.discovered, this->client_state_counts_.disconnecting);
this->client_state_counts_.searching, this->client_state_counts_.disconnecting);
if (this->scan_start_fail_count_) { if (this->scan_start_fail_count_) {
ESP_LOGCONFIG(TAG, " Scan Start Fail Count: %d", 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); 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 #endif // USE_ESP32_BLE_DEVICE
bool ESP32BLETracker::process_scan_result_(const BLEScanResult &scan_result) { void ESP32BLETracker::process_scan_result_(const BLEScanResult &scan_result) {
bool found_discovered_client = false;
// Process raw advertisements // Process raw advertisements
if (this->raw_advertisements_) { if (this->raw_advertisements_) {
for (auto *listener : this->listeners_) { for (auto *listener : this->listeners_) {
@@ -759,14 +734,6 @@ bool ESP32BLETracker::process_scan_result_(const BLEScanResult &scan_result) {
for (auto *client : this->clients_) { for (auto *client : this->clients_) {
if (client->parse_device(device)) { if (client->parse_device(device)) {
found = true; 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 #endif // USE_ESP32_BLE_DEVICE
} }
return found_discovered_client;
} }
void ESP32BLETracker::cleanup_scan_state_(bool is_stop_complete) { void ESP32BLETracker::cleanup_scan_state_(bool is_stop_complete) {

View File

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

View File

@@ -17,7 +17,7 @@ from esphome.const import (
PLATFORM_ESP8266, PLATFORM_ESP8266,
ThreadModel, 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 esphome.helpers import copy_file_if_changed
from .boards import BOARDS, ESP8266_LD_SCRIPTS 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): async def to_code(config):
cg.add(esp8266_ns.setup_preferences()) cg.add(esp8266_ns.setup_preferences())

View File

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

View File

@@ -17,7 +17,7 @@ from esphome.const import (
CONF_PULLUP, CONF_PULLUP,
PLATFORM_ESP8266, PLATFORM_ESP8266,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, CoroPriority, coroutine_with_priority
from . import boards from . import boards
from .const import KEY_BOARD, KEY_ESP8266, KEY_PIN_INITIAL_STATES, esp8266_ns 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 return var
@coroutine_with_priority(-999.0) @coroutine_with_priority(CoroPriority.WORKAROUNDS)
async def add_pin_initial_states_array(): async def add_pin_initial_states_array():
# Add includes at the very end, so that they override everything # Add includes at the very end, so that they override everything
initial_states: list[PinInitialState] = CORE.data[KEY_ESP8266][ initial_states: list[PinInitialState] = CORE.data[KEY_ESP8266][
@@ -199,11 +199,11 @@ async def add_pin_initial_states_array():
cg.add_global( cg.add_global(
cg.RawExpression( 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.add_global(
cg.RawExpression( 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 #ifdef USE_ESP8266
#include <c_types.h> #include <c_types.h>
#include <cinttypes>
extern "C" { extern "C" {
#include "spi_flash.h" #include "spi_flash.h"
} }
@@ -12,7 +13,7 @@ extern "C" {
#include "preferences.h" #include "preferences.h"
#include <cstring> #include <cstring>
#include <vector> #include <memory>
namespace esphome { namespace esphome {
namespace esp8266 { namespace esp8266 {
@@ -67,6 +68,8 @@ static uint32_t get_esp8266_flash_sector() {
} }
static uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; } static uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; }
static inline size_t bytes_to_words(size_t bytes) { return (bytes + 3) / 4; }
template<class It> uint32_t calculate_crc(It first, It last, uint32_t type) { template<class It> uint32_t calculate_crc(It first, It last, uint32_t type) {
uint32_t crc = type; uint32_t crc = type;
while (first != last) { while (first != last) {
@@ -117,47 +120,42 @@ static bool load_from_rtc(size_t offset, uint32_t *data, size_t len) {
class ESP8266PreferenceBackend : public ESPPreferenceBackend { class ESP8266PreferenceBackend : public ESPPreferenceBackend {
public: public:
size_t offset = 0;
uint32_t type = 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; bool in_flash = false;
size_t length_words = 0;
bool save(const uint8_t *data, size_t len) override { bool save(const uint8_t *data, size_t len) override {
if ((len + 3) / 4 != length_words) { if (bytes_to_words(len) != length_words) {
return false; return false;
} }
std::vector<uint32_t> buffer; size_t buffer_size = static_cast<size_t>(length_words) + 1;
buffer.resize(length_words + 1); std::unique_ptr<uint32_t[]> buffer(new uint32_t[buffer_size]()); // Note the () for zero-initialization
memcpy(buffer.data(), data, len); memcpy(buffer.get(), data, len);
buffer[buffer.size() - 1] = calculate_crc(buffer.begin(), buffer.end() - 1, type); buffer[length_words] = calculate_crc(buffer.get(), buffer.get() + length_words, type);
if (in_flash) { if (in_flash) {
return save_to_flash(offset, buffer.data(), buffer.size()); return save_to_flash(offset, buffer.get(), buffer_size);
} else {
return save_to_rtc(offset, buffer.data(), buffer.size());
} }
return save_to_rtc(offset, buffer.get(), buffer_size);
} }
bool load(uint8_t *data, size_t len) override { bool load(uint8_t *data, size_t len) override {
if ((len + 3) / 4 != length_words) { if (bytes_to_words(len) != length_words) {
return false; return false;
} }
std::vector<uint32_t> buffer; size_t buffer_size = static_cast<size_t>(length_words) + 1;
buffer.resize(length_words + 1); std::unique_ptr<uint32_t[]> buffer(new uint32_t[buffer_size]());
bool ret; bool ret = in_flash ? load_from_flash(offset, buffer.get(), buffer_size)
if (in_flash) { : load_from_rtc(offset, buffer.get(), buffer_size);
ret = load_from_flash(offset, buffer.data(), buffer.size());
} else {
ret = load_from_rtc(offset, buffer.data(), buffer.size());
}
if (!ret) if (!ret)
return false; return false;
uint32_t crc = calculate_crc(buffer.begin(), buffer.end() - 1, type); uint32_t crc = calculate_crc(buffer.get(), buffer.get() + length_words, type);
if (buffer[buffer.size() - 1] != crc) { if (buffer[length_words] != crc) {
return false; return false;
} }
memcpy(data, buffer.data(), len); memcpy(data, buffer.get(), len);
return true; return true;
} }
}; };
@@ -178,16 +176,20 @@ class ESP8266Preferences : public ESPPreferences {
} }
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override {
uint32_t length_words = (length + 3) / 4; 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) { if (in_flash) {
uint32_t start = current_flash_offset; uint32_t start = current_flash_offset;
uint32_t end = start + length_words + 1; uint32_t end = start + length_words + 1;
if (end > ESP8266_FLASH_STORAGE_SIZE) if (end > ESP8266_FLASH_STORAGE_SIZE)
return {}; return {};
auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
pref->offset = start; pref->offset = static_cast<uint16_t>(start);
pref->type = type; pref->type = type;
pref->length_words = length_words; pref->length_words = static_cast<uint8_t>(length_words);
pref->in_flash = true; pref->in_flash = true;
current_flash_offset = end; current_flash_offset = end;
return {pref}; return {pref};
@@ -213,9 +215,9 @@ class ESP8266Preferences : public ESPPreferences {
uint32_t rtc_offset = in_normal ? start + 32 : start - 96; uint32_t rtc_offset = in_normal ? start + 32 : start - 96;
auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
pref->offset = rtc_offset; pref->offset = static_cast<uint16_t>(rtc_offset);
pref->type = type; pref->type = type;
pref->length_words = length_words; pref->length_words = static_cast<uint8_t>(length_words);
pref->in_flash = false; pref->in_flash = false;
current_offset += length_words + 1; current_offset += length_words + 1;
return pref; return pref;

View File

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

View File

@@ -100,8 +100,8 @@ void ESPHomeOTAComponent::handle_handshake_() {
/// Handle the initial OTA handshake. /// Handle the initial OTA handshake.
/// ///
/// This method is non-blocking and will return immediately if no data is available. /// 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_(). /// It reads all 5 magic bytes (0x6C, 0x26, 0xF7, 0x5C, 0x45) non-blocking
/// A 10-second timeout is enforced from initial connection. /// before proceeding to handle_data_(). A 10-second timeout is enforced from initial connection.
if (this->client_ == nullptr) { if (this->client_ == nullptr) {
// We already checked server_->ready() in loop(), so we can accept directly // We already checked server_->ready() in loop(), so we can accept directly
@@ -126,6 +126,7 @@ void ESPHomeOTAComponent::handle_handshake_() {
} }
this->log_start_("handshake"); this->log_start_("handshake");
this->client_connect_time_ = App.get_loop_component_start_time(); this->client_connect_time_ = App.get_loop_component_start_time();
this->magic_buf_pos_ = 0; // Reset magic buffer position
} }
// Check for handshake timeout // Check for handshake timeout
@@ -136,9 +137,11 @@ void ESPHomeOTAComponent::handle_handshake_() {
return; return;
} }
// Try to read first byte of magic bytes // Try to read remaining magic bytes
uint8_t first_byte; if (this->magic_buf_pos_ < 5) {
ssize_t read = this->client_->read(&first_byte, 1); // 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)) { if (read == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
return; // No data yet, try again next loop return; // No data yet, try again next loop
@@ -147,7 +150,7 @@ void ESPHomeOTAComponent::handle_handshake_() {
if (read <= 0) { if (read <= 0) {
// Error or connection closed // Error or connection closed
if (read == -1) { if (read == -1) {
this->log_socket_error_("reading first byte"); this->log_socket_error_("reading magic bytes");
} else { } else {
ESP_LOGW(TAG, "Remote closed during handshake"); ESP_LOGW(TAG, "Remote closed during handshake");
} }
@@ -155,16 +158,27 @@ void ESPHomeOTAComponent::handle_handshake_() {
return; return;
} }
// Got first byte, check if it's the magic byte this->magic_buf_pos_ += read;
if (first_byte != 0x6C) { }
ESP_LOGW(TAG, "Invalid initial byte: 0x%02X", first_byte);
// 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_(); this->cleanup_connection_();
return; return;
} }
// First byte is valid, continue with data handling // All 5 magic bytes are valid, continue with data handling
this->handle_data_(); this->handle_data_();
} }
}
void ESPHomeOTAComponent::handle_data_() { void ESPHomeOTAComponent::handle_data_() {
/// Handle the OTA data transfer and update process. /// Handle the OTA data transfer and update process.
@@ -186,18 +200,6 @@ void ESPHomeOTAComponent::handle_data_() {
size_t size_acknowledged = 0; size_t size_acknowledged = 0;
#endif #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 // Send OK and version - 2 bytes
buf[0] = ota::OTA_RESPONSE_OK; buf[0] = ota::OTA_RESPONSE_OK;
buf[1] = USE_OTA_VERSION; buf[1] = USE_OTA_VERSION;
@@ -487,6 +489,7 @@ void ESPHomeOTAComponent::cleanup_connection_() {
this->client_->close(); this->client_->close();
this->client_ = nullptr; this->client_ = nullptr;
this->client_connect_time_ = 0; this->client_connect_time_ = 0;
this->magic_buf_pos_ = 0;
} }
void ESPHomeOTAComponent::yield_and_feed_watchdog_() { void ESPHomeOTAComponent::yield_and_feed_watchdog_() {

View File

@@ -41,11 +41,13 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
std::string password_; std::string password_;
#endif // USE_OTA_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> server_;
std::unique_ptr<socket::Socket> client_; 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 } // namespace esphome

View File

@@ -38,7 +38,12 @@ from esphome.const import (
KEY_CORE, KEY_CORE,
KEY_FRAMEWORK_VERSION, 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 import esphome.final_validate as fv
CONFLICTS_WITH = ["wifi"] 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): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config) await cg.register_component(var, config)

View File

@@ -17,7 +17,7 @@ from esphome.const import (
DEVICE_CLASS_EMPTY, DEVICE_CLASS_EMPTY,
DEVICE_CLASS_MOTION, DEVICE_CLASS_MOTION,
) )
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.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
@@ -143,6 +143,6 @@ async def event_fire_to_code(config, action_id, template_arg, args):
return var return var
@coroutine_with_priority(100.0) @coroutine_with_priority(CoroPriority.CORE)
async def to_code(config): async def to_code(config):
cg.add_global(event_ns.using) cg.add_global(event_ns.using)

View File

@@ -13,11 +13,11 @@ namespace event {
#define LOG_EVENT(prefix, type, obj) \ #define LOG_EVENT(prefix, type, obj) \
if ((obj) != nullptr) { \ if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
if (!(obj)->get_icon().empty()) { \ if (!(obj)->get_icon_ref().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon_ref().c_str()); \
} \ } \
if (!(obj)->get_device_class().empty()) { \ if (!(obj)->get_device_class_ref().empty()) { \
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \ ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class_ref().c_str()); \
} \ } \
} }

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