Compare commits

...
Author SHA1 Message Date
J. Nick Koston 38ab0805a0 [logger] Add optional boot log buffer replayed over the API 2026-07-17 23:02:43 -10:00
tomaszduda23andGitHub 017040ec8e [zigbee] add radio power off stats (#17521) 2026-07-18 09:09:52 +02:00
TomandGitHub 52fe461e9c [ds248x] Add OneWireBus platform for DS248x I2C-to-1Wire bridges (#12717) 2026-07-17 22:01:15 -07:00
esphome[bot]andGitHub a7dad14449 Bump bundled esphome-device-builder to 1.6.4 (#17662) 2026-07-17 17:44:52 -10:00
Jesse HillsandGitHub 1493a09521 [nrf52] Install PlatformIO toolchain Python packages into a dedicated venv (#17635) 2026-07-17 23:22:23 -04:00
J. Nick KostonandGitHub 2a59c0ad9e [logs] Cap the logs reconnect backoff for deep-sleep devices (#17656) 2026-07-17 17:16:14 -10:00
Jonathan SwobodaandGitHub 70421fb14b [espidf] Prune tool download cache after install to shrink the ESP-IDF cache (#17661) 2026-07-17 22:49:50 -04:00
J. Nick KostonandGitHub bacc223360 Ship component requirements.txt files in the sdist and wheel (#17660) 2026-07-17 15:59:12 -10:00
J. Nick KostonandGitHub 94be1da893 Pin cryptography to 48.0.1 on Intel macOS (#17658) 2026-07-17 15:46:16 -10:00
Brandon HarveyandGitHub cb66dc01ab [core] Fix srcFilter exclusions being silently ignored on Windows (#17648) 2026-07-17 21:45:13 -04:00
Jonathan SwobodaandGitHub ebaf32c82a [ci] Add LibreTiny clang-tidy environments (#17491) 2026-07-17 21:38:55 -04:00
Jonathan SwobodaandGitHub f918c299b1 [espidf] Suggest installing missing system libraries when the tools install fails (#17619) 2026-07-17 19:46:45 -04:00
dependabot[bot]andGitHub 328a2018d2 Bump aioesphomeapi from 45.6.1 to 45.6.2 (#17654)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-17 13:32:47 -10:00
dependabot[bot]andGitHub a4a9feac16 Bump esphome/workflows/.github/workflows/lock.yml from 2026.4.1 to 2026.7.0 (#17652)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-17 12:26:04 -10:00
esphome[bot]andGitHub 6f27c7f3fe Bump bundled esphome-device-builder to 1.6.3 (#17651) 2026-07-17 12:25:18 -10:00
dependabot[bot]andGitHub 300ab1be35 Bump aioesphomeapi from 45.6.0 to 45.6.1 (#17653)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-17 12:00:54 -10:00
esphome[bot]GitHubesphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
7aed5fb94b Bump bundled esphome-device-builder to 1.6.2 (#17640)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-07-17 08:08:28 -04:00
Keith BurzinskiandGitHub 2132cae1c9 [nextion] Fix RP2040 clang-tidy failure from TFT upload guard mismatch (#17638) 2026-07-17 08:07:38 -04:00
Jesse HillsandGitHub 735f8d607d [nrf52] Set ZEPHYR_SDK_INSTALL_DIR for Zephyr SDK discovery (#17633) 2026-07-18 00:04:35 +12:00
Jonathan SwobodaandGitHub cc6392785f [runtime_image] Prevent integer overflow in image buffer size calculation (#17624) 2026-07-16 18:36:18 -04:00
Jonathan SwobodaandGitHub 7a2d13da90 [web_server_idf] Use core format_hex_to helper for digest auth (fixes Arduino build) (#17608) 2026-07-16 15:59:53 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
e2df9fb554 Bump ruff from 0.15.21 to 0.15.22 (#17628)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-16 15:43:43 -04:00
Jonathan SwobodaandGitHub 4507d05822 [http_request] Fix use-after-return of header collection state in IDF backend (#17627) 2026-07-16 15:34:49 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
3caae3031a Bump github/codeql-action/init from 4.37.0 to 4.37.1 (#17629)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-16 15:33:38 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
a624659856 Bump github/codeql-action/analyze from 4.37.0 to 4.37.1 (#17630)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-16 15:33:30 -04:00
Jonathan SwobodaandGitHub 6c8d0050ab [esp32_ble_server] Validate descriptor write length before copying (#17626) 2026-07-16 15:32:44 -04:00
Jonathan SwobodaandGitHub 09da2766ed [bluetooth_proxy] Bound GATT characteristic/descriptor enumeration to allocated size (#17625) 2026-07-16 15:32:09 -04:00
72bd45538e [opentherm] Rename OpenthermData accessors that collide with vendor SDK type macros (#17611)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-16 15:25:45 -04:00
Jonathan SwobodaandGitHub 5b37049dff [midea] Restrict to platforms where MideaUART actually builds (#17613) 2026-07-16 15:23:46 -04:00
Jonathan SwobodaandGitHub c0e78a5574 [core] Improve framework mirror selection, download errors, and version parsing (#17615) 2026-07-16 15:22:19 -04:00
Jonathan SwobodaandGitHub f53394e46f [mlx90393] Compile out on BK72xx where the bundled library cannot build (#17614) 2026-07-16 15:14:18 -04:00
J. Nick KostonandGitHub 2218edf20c [web_server] Switch entity id to the new format and drop name_id (#17586) 2026-07-16 08:49:43 -10:00
J. Nick KostonandGitHub dcdd044234 [web_server] Use alarm_control_panel as the domain in JSON (#17594) 2026-07-16 08:48:47 -10:00
J. Nick KostonandGitHub bc8510c6d8 [micro_wake_word] Include the local model file in bundles (#17604) 2026-07-16 08:09:30 -04:00
J. Nick KostonandGitHub 5b4ae22f58 [api] Don't spam tracebacks when decoding a crash without a local build (#17597) 2026-07-16 08:08:28 -04:00
J. Nick KostonandGitHub 14e71e190c [ci] Restore memory impact detail for ESP-IDF builds (#17587) 2026-07-16 08:00:19 -04:00
Guanzhong ChenandGitHub 2333e6eef5 [zephyr] implement ISRInternalGPIOPin::digital_write (#17601) 2026-07-16 07:57:30 -04:00
Jonathan SwobodaandGitHub d748c04b28 [libretiny] Code fixes for upcoming LibreTiny clang-tidy scans (#17596) 2026-07-16 07:25:46 -04:00
Jonathan SwobodaandGitHub 8d1a0446a2 [haier][teleinfo][hlk_fm22x][rp2040_ble] Rename enum members that collide with vendor SDK macros (#17595) 2026-07-16 07:25:37 -04:00
0887e01828 [improv_base] Bump Improv library to 1.2.6 (#17599)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 05:17:01 +00:00
J. Nick KostonandGitHub f88621f2e1 [network] Remove deprecated IPAddress::str() (#17589) 2026-07-16 05:12:06 +00:00
J. Nick KostonandGitHub ec3f7ec16e [api] Remove outdated API version warning (#17593) 2026-07-16 05:07:26 +00:00
J. Nick KostonandGitHub 880cb1db43 [voice_assistant] Remove deprecated Timer::to_string() (#17591) 2026-07-16 05:06:49 +00:00
J. Nick KostonandGitHub f336c45177 [water_heater] Remove deprecated WaterHeaterCall::get_state() (#17592) 2026-07-16 05:06:38 +00:00
J. Nick KostonandGitHub b12d392e02 [esp32_ble] Remove deprecated ESPBTUUID::to_string() (#17590) 2026-07-16 05:06:30 +00:00
49a1d2bb1b [veml3235] Overhaul auto-gain and fix latent config bugs (#17551)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 23:08:14 -05:00
J. Nick KostonandGitHub e327a7f52f [scheduler] Remove deprecated set_retry/cancel_retry (#17585) 2026-07-15 17:58:41 -10:00
Jonathan SwobodaandGitHub 90e838d327 [ci] Extend variant clang-tidy scans to USB logger, tsens and LP peripheral code (#17580) 2026-07-15 23:07:34 -04:00
Jonathan SwobodaandGitHub f91aa7aa13 [as3935_i2c] Use repeated start when reading registers (#17584) 2026-07-16 14:26:23 +12:00
lsellensandGitHub be3b27f378 [rc522_i2c] Change default address to match whats in the docs (#17566) 2026-07-15 21:20:07 -04:00
Jesse HillsandGitHub 3439a08b68 [deep_sleep] Add on_wake automation triggers (#17569) 2026-07-15 21:19:41 -04:00
dedca344f9 [aqi] Add extended_range option for over-range AQI values (#17570)
Co-authored-by: jas <jas@asspa.in>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 21:17:58 -04:00
Hajo NoerenbergandGitHub c4975e1870 [cc1101] Add FOCCFG and BSCFG config options (#17577) 2026-07-15 21:17:39 -04:00
Jonathan SwobodaandGitHub 5a3c2f4d11 [ci] Add rp2 clang-tidy environment (#17486) 2026-07-15 21:17:21 -04:00
Jonathan SwobodaandGitHub e700b01406 [haier] Fix outdoor defrost temperature reporting the coil temperature (#17492) 2026-07-15 21:16:38 -04:00
Jonathan SwobodaandGitHub d53d4c5b58 [emc2101] Fix negative external temperatures reported as large positives (#17494) 2026-07-15 21:16:24 -04:00
Jesse Hills 3c34fc950e Merge branch 'release' into dev 2026-07-16 12:59:58 +12:00
Jesse HillsandGitHub 920a8b761b Merge pull request #17583 from esphome/bump-2026.7.0
2026.7.0
2026-07-16 12:59:09 +12:00
Jonathan SwobodaandJesse Hills 5d2372937e [ci] Stop per-PR cache copies from crowding the 10GB Actions cache quota (#17463) 2026-07-16 10:16:18 +12:00
Jesse Hills 6c401d406f Bump version to 2026.7.0 2026-07-16 09:52:34 +12:00
Jesse Hills b0489e21b2 Merge branch 'beta' into dev 2026-07-16 09:45:16 +12:00
Jesse HillsandGitHub 78180a40c4 Merge pull request #17582 from esphome/bump-2026.7.0b4
2026.7.0b4
2026-07-16 09:44:41 +12:00
Jesse Hills 63fae2c36a Bump version to 2026.7.0b4 2026-07-16 08:23:57 +12:00
Jesse Hills 878d8a2f6a [core] Fix wait_until crash when re-entered from its own continuation (#17571) 2026-07-16 08:23:57 +12:00
esphome[bot]Jesse Hillsesphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
e056e99fd4 Bump bundled esphome-device-builder to 1.6.1 (#17575)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-07-16 08:23:57 +12:00
Jesse Hills 3b915cf16b [api] Warn when Home Assistant actions are sent with no subscribed client (#17560) 2026-07-16 08:23:57 +12:00
esphome[bot]andJesse Hills d1150d148d Bump bundled esphome-device-builder to 1.6.0 (#17573) 2026-07-16 08:23:57 +12:00
luar123andJesse Hills b5b426492a [zigbee] bump esp-zigbee-sdk to 2.0.3 (#17564) 2026-07-16 08:23:57 +12:00
Jonathan SwobodaandJesse Hills 724df7b11e [atc_mithermometer] Make duplicate-packet counter per-instance (#17496) 2026-07-16 08:23:57 +12:00
Daniele PalumboandJesse Hills 26db22546d [mcp23017] reset IPOL registers to 0x00 on setup (#17177)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2026-07-16 08:23:57 +12:00
Jesse HillsandGitHub a3ab7961e0 [core] Fix wait_until crash when re-entered from its own continuation (#17571) 2026-07-16 08:14:55 +12:00
Hajo NoerenbergandGitHub 54987d7d23 [cc1101] Export CC1101Listener to Python (#17576) 2026-07-15 12:38:08 -04:00
esphome[bot]GitHubesphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
053ce39fc6 Bump bundled esphome-device-builder to 1.6.1 (#17575)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-07-15 08:51:50 -04:00
Jesse HillsandGitHub b295b8d5a2 [api] Warn when Home Assistant actions are sent with no subscribed client (#17560) 2026-07-15 23:11:47 +12:00
esphome[bot]andGitHub 4d3d06959b Bump bundled esphome-device-builder to 1.6.0 (#17573) 2026-07-14 23:59:19 -10:00
Jesse HillsandGitHub a5583dcba6 [tests] Add test_display component to free touchscreen tests from display pins (#17540) 2026-07-15 11:22:47 +12:00
luar123andGitHub e2b62bcd00 [zigbee] bump esp-zigbee-sdk to 2.0.3 (#17564) 2026-07-14 17:27:17 -04:00
Jonathan SwobodaandGitHub 4275343231 [atc_mithermometer] Make duplicate-packet counter per-instance (#17496) 2026-07-15 09:23:37 +12:00
dependabot[bot]andGitHub 4ebe49b141 Bump clang-tidy from 22.1.7 to 22.1.8 (#17565)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-14 09:54:02 -10:00
Jesse Hills d3adb6fb81 Merge branch 'beta' into dev 2026-07-14 16:46:31 +12:00
Jesse HillsandGitHub 00d2d55e09 Merge pull request #17556 from esphome/bump-2026.7.0b3
2026.7.0b3
2026-07-14 16:45:55 +12:00
esphome[bot]andJesse Hills a833685a73 Bump bundled esphome-device-builder to 1.5.0 (#17558) 2026-07-14 14:51:55 +12:00
esphome[bot]andGitHub 2753ab1f45 Bump bundled esphome-device-builder to 1.5.0 (#17558) 2026-07-13 16:50:08 -10:00
b6b5b61640 [mcp23017] reset IPOL registers to 0x00 on setup (#17177)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2026-07-14 02:38:07 +00:00
Jesse Hills 0a1065da75 [script] Recursively expand nested packages when merging component tests (#17557) 2026-07-14 14:11:18 +12:00
Jesse HillsandGitHub db9a09d05a [script] Recursively expand nested packages when merging component tests (#17557) 2026-07-14 14:11:03 +12:00
Jesse Hills 6fadf353b1 Bump version to 2026.7.0b3 2026-07-14 12:40:45 +12:00
Keith BurzinskiandJesse Hills 8da377ab43 [nextion] Fix unbounded queue growth and OOM crash when display sends no data (#17553) 2026-07-14 12:40:45 +12:00
Jonathan SwobodaandJesse Hills 1da8900ffc [veml7700][as7341][ltr501] Fix device class on raw-count sensors (#17549) 2026-07-14 12:40:45 +12:00
J. Nick KostonandJesse Hills 519ce38b79 [libretiny] Keep renamed board generic-ln882hki validating against generic-ln882h (#17542) 2026-07-14 12:40:45 +12:00
Jesse Hills 989797be53 [web_server] Add HTTP digest authentication with selectable scheme (#17541) 2026-07-14 12:40:45 +12:00
Jesse Hills 583adc9e69 [web_server] Use dict-style packages in tests so they can be batch-grouped (#17544) 2026-07-14 12:40:45 +12:00
Keith BurzinskiandJesse Hills af9a0404d9 [esp32] Do not require verification_key with Secure Boot V2 signing schemes (#17497) 2026-07-14 12:40:45 +12:00
Jesse Hills f38e7f2de2 [web_server] Add CORS origin checking with allowed_origins (#17530) 2026-07-14 12:40:22 +12:00
Jesse Hills 3e75020007 [gsl3670] Fix i2c package variant in esp32-s3-idf test (#17535) 2026-07-14 12:39:14 +12:00
Keith BurzinskiandGitHub ca77cc585c [nextion] Fix unbounded queue growth and OOM crash when display sends no data (#17553) 2026-07-14 11:56:03 +12:00
Jonathan SwobodaandGitHub f1e4726f4e [veml7700][as7341][ltr501] Fix device class on raw-count sensors (#17549) 2026-07-14 11:53:31 +12:00
J. Nick KostonandGitHub 49bbceb1da [libretiny] Keep renamed board generic-ln882hki validating against generic-ln882h (#17542) 2026-07-14 11:51:54 +12:00
Jesse HillsandGitHub 65d6c028ce [web_server] Add HTTP digest authentication with selectable scheme (#17541) 2026-07-14 10:33:44 +12:00
Clyde StubbsandGitHub 5e3e2f82c9 [script] Fix duplicate import in build_codeowners.py (#17543) 2026-07-13 10:16:53 -04:00
Jesse HillsandGitHub d78cb09b17 [web_server] Use dict-style packages in tests so they can be batch-grouped (#17544) 2026-07-13 23:23:55 +12:00
Keith BurzinskiandGitHub bcfb438a81 [esp32] Do not require verification_key with Secure Boot V2 signing schemes (#17497) 2026-07-13 16:23:40 +12:00
Jesse HillsandGitHub a8dfd00cc6 [web_server] Add CORS origin checking with allowed_origins (#17530) 2026-07-13 15:38:48 +12:00
Jesse HillsandGitHub 4a82b10783 [ci] Group component test output into collapsible CI log sections (#17536) 2026-07-13 15:29:35 +12:00
Jesse HillsandGitHub 07460ebee4 [gsl3670] Fix i2c package variant in esp32-s3-idf test (#17535) 2026-07-13 15:28:55 +12:00
Jesse Hills cdc42e5e55 Merge branch 'beta' into dev 2026-07-13 13:25:21 +12:00
Jonathan SwobodaandGitHub b3e03868b3 [mipi_rgb] Test in isolation to avoid bus/pin merge conflicts (#17534) 2026-07-13 12:00:22 +12:00
Jesse HillsandGitHub 0ff11674ef [mipi_rgb] Use dict-style packages in test so it can be batch-grouped (#17533) 2026-07-12 18:38:38 -04:00
Keith BurzinskiandGitHub 434cffb745 [zwave_proxy] Fix parser gaps and harden frame and subscription handling (#17461) 2026-07-13 08:58:17 +12:00
J. Nick KostonandGitHub 2b3027a7fd [api] Provision encryption keys over an encrypted zero-PSK noise connection (#17482) 2026-07-13 08:55:30 +12:00
Jesse HillsandGitHub e27a14ec70 [core] Classify entity metadata visibility for the visual editor (#17503) 2026-07-12 08:33:29 -04:00
luar123andGitHub 91c42381f6 [zigbee] prevent task watchdog trigger with large configs. (#17506) 2026-07-12 08:32:01 -04:00
luar123andGitHub a39607476a [zigbee] Fix merge endpoint (#17511) 2026-07-12 08:31:39 -04:00
J. Nick KostonandGitHub b098571a6f [web_server] Fix unused function warning for json_state_str (#17524) 2026-07-11 14:31:14 -10:00
J. Nick KostonandGitHub 614fd88829 [mdns] Fix missing device info TXT records when native API is not enabled (#17520) 2026-07-11 14:31:01 -10:00
J. Nick KostonandGitHub c0636e2bf7 [core] Make config-hash independent of machine-local paths (#17523) 2026-07-11 14:30:47 -10:00
esphome[bot]andGitHub 65353006c8 Bump bundled esphome-device-builder to 1.4.3 (#17522) 2026-07-11 13:32:45 -10:00
tomaszduda23andGitHub 35a99f478e [deep_sleep] feed watchdog in deep sleep (#17516) 2026-07-11 09:48:11 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
b6a4dd237e Update tzdata requirement from >=2026.2 to >=2026.3 (#17510)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-11 09:45:42 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
0ef85783dc Bump actions/stale from 10.3.0 to 10.4.0 (#17509)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-11 09:45:23 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
5020179210 Bump ruff from 0.15.20 to 0.15.21 (#17508)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-11 09:45:02 -04:00
54529412dc [mipi_dsi] New model for M5Stack Tab5 (#17500)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 09:38:01 -04:00
Clyde StubbsGitHubpre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
e6525b5d93 [mipi][mipi_spi] SWRESET handling improved (#17504)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-07-11 09:36:26 -04:00
RaymondandGitHub 9f62cf9243 [mipi_dsi] Add JC8012P4A1-V2 (#17457) 2026-07-11 21:24:00 +10:00
esphome[bot]andGitHub 478bca026c Bump bundled esphome-device-builder to 1.4.2 (#17512) 2026-07-10 16:37:00 -10:00
e1719cd85d [mipi][mipi_spi][mipi_dsi][mipi_rgb] Transform cleanup (#17405)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 22:06:40 -04:00
esphome[bot]andGitHub 83aaed71e1 Bump bundled esphome-device-builder to 1.4.1 (#17507) 2026-07-10 10:30:19 -10:00
Jesse HillsandGitHub 88ca0d44e0 [docs] Document web server as an open HTTP API by design in threat model (#17465) 2026-07-10 16:38:23 +12:00
esphome[bot]andGitHub 7a1e0bbbec Bump bundled esphome-device-builder to 1.4.0 (#17495) 2026-07-09 15:36:42 -10:00
Jonathan SwobodaandGitHub b2226b91ff [web_server] Serialize entity state strings without a copy buffer (#17488) 2026-07-09 16:51:04 -04:00
dependabot[bot]andGitHub ba84f2ec55 Bump aioesphomeapi from 45.5.2 to 45.6.0 (#17490)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-09 19:53:42 +00:00
dependabot[bot]andGitHub 4b19de0c1a Bump CodSpeedHQ/action from 4.18.4 to 4.18.5 (#17489)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-09 19:35:16 +00:00
Jonathan SwobodaandGitHub 4292e7988e [mcp4461] Fix wiper increment/decrement write length (#17487) 2026-07-09 15:27:05 -04:00
Jonathan SwobodaandGitHub 9f21fd0b55 [usb_uart] Fix output chunk length truncated to 8 bits (#17480) 2026-07-09 15:04:42 -04:00
Jesse Hills 9327d011fc Merge branch 'beta' into dev 2026-07-09 16:54:49 +12:00
Jonathan SwobodaandGitHub 435dde67d0 [ci] Stop per-PR cache copies from crowding the 10GB Actions cache quota (#17463) 2026-07-08 23:29:23 -04:00
Jonathan SwobodaandGitHub 9c92ab63fb [gsl3670] Reference the test display explicitly so grouped CI builds validate (#17462) 2026-07-09 13:43:56 +12:00
Elvin LuffandGitHub acc8381cbb [epaper_spi] Remove noop deep sleep command (#15595) 2026-07-09 11:29:39 +10:00
Jesse Hills 0a8a7e22d2 Bump version to 2026.8.0-dev 2026-07-09 12:30:54 +12:00
254 changed files with 6438 additions and 1921 deletions
@@ -35,6 +35,9 @@ runs:
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
enable-cache: true
# Pull request saves land in per-PR scopes nothing else can
# reuse; dev pushes seed the shared copy instead.
save-cache: ${{ github.event_name != 'pull_request' }}
# Pin uv version so the action does not have to fetch the
# manifest from raw.githubusercontent.com on every cache
# miss; that fetch flakes on Windows runners.
+3
View File
@@ -32,6 +32,9 @@ jobs:
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
enable-cache: true
# Pull-request-only workflow: a save could never be shared and
# would only consume quota.
save-cache: "false"
# Pin uv version so the action does not have to fetch the
# manifest from raw.githubusercontent.com on every cache
# miss; that fetch flakes on Windows runners.
+85 -11
View File
@@ -52,6 +52,9 @@ jobs:
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
enable-cache: true
# Pull request saves land in per-PR scopes nothing else can
# reuse; dev pushes seed the shared copy instead.
save-cache: ${{ github.event_name != 'pull_request' }}
# Pin uv version so the action does not have to fetch the
# manifest from raw.githubusercontent.com on every cache
# miss; that fetch flakes on Windows runners.
@@ -174,6 +177,9 @@ jobs:
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
enable-cache: true
# Pull request saves land in per-PR scopes nothing else can
# reuse; dev pushes seed the shared copy instead.
save-cache: ${{ github.event_name != 'pull_request' }}
# Pin uv version so the action does not have to fetch the
# manifest from raw.githubusercontent.com on every cache
# miss; that fetch flakes on Windows runners.
@@ -375,6 +381,9 @@ jobs:
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
enable-cache: true
# Pull request saves land in per-PR scopes nothing else can
# reuse; dev pushes seed the shared copy instead.
save-cache: ${{ github.event_name != 'pull_request' }}
# Pin uv version so the action does not have to fetch the
# manifest from raw.githubusercontent.com on every cache
# miss; that fetch flakes on Windows runners.
@@ -456,7 +465,7 @@ jobs:
echo "binary=$BINARY" >> $GITHUB_OUTPUT
- name: Run CodSpeed benchmarks
uses: CodSpeedHQ/action@9f3a37ece7abc84992501a7fcd54d1704f3458fa # v4.18.4
uses: CodSpeedHQ/action@f99becdce5e5d51fd556489ebef684f4ecfd6286 # v4.18.5
with:
run: |
. venv/bin/activate
@@ -495,6 +504,15 @@ jobs:
options: --environment nrf52-tidy --grep USE_ZEPHYR --grep USE_NRF52
cache_sdk_nrf: true
ignore_errors: false
- id: clang-tidy
name: Run script/clang-tidy for RP2
options: --environment rp2-tidy --grep USE_RP2
pio_cache_key: tidyrp2
- id: clang-tidy
name: Run script/clang-tidy for LibreTiny
environments: bk72xx-tidy ln882h-tidy rtl87xxb-tidy rtl87xxc-tidy
options: --grep USE_LIBRETINY --grep USE_BK72XX --grep USE_RTL87XX --grep USE_LN882X
pio_cache_key: tidylibretiny
steps:
- name: Check out code from GitHub
@@ -558,10 +576,21 @@ jobs:
. venv/bin/activate
if [ "${{ steps.check_full_scan.outputs.full_scan }}" = "true" ]; then
echo "Running FULL clang-tidy scan (reason: ${{ steps.check_full_scan.outputs.reason }})"
script/clang-tidy --all-headers --fix ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }}
changed=""
else
echo "Running clang-tidy on changed files only"
script/clang-tidy --all-headers --fix --changed ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }}
changed="--changed"
fi
if [ -n "${{ matrix.environments }}" ]; then
rc=0
for env in ${{ matrix.environments }}; do
echo "::group::clang-tidy $env"
script/clang-tidy --all-headers --fix $changed --environment "$env" ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }} || rc=1
echo "::endgroup::"
done
exit $rc
else
script/clang-tidy --all-headers --fix $changed ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }}
fi
env:
# Also cache libdeps, store them in a ~/.platformio subfolder
@@ -735,7 +764,8 @@ jobs:
include:
- id: clang-tidy
name: Run script/clang-tidy for ESP32 S3
options: --environment esp32s3-idf-tidy --grep USE_ESP32_VARIANT_ESP32S3
# yamllint disable-line rule:line-length
options: --environment esp32s3-idf-tidy --grep SOC_TEMP_SENSOR_SUPPORTED --grep USE_ESP32_VARIANT_ESP32S3 --grep USE_LOGGER_USB_CDC
- id: clang-tidy
name: Run script/clang-tidy for ESP32 P4
# P4 has no native Wi-Fi/BLE; those run over the hosted co-processor,
@@ -745,7 +775,7 @@ jobs:
- id: clang-tidy
name: Run script/clang-tidy for ESP32 C6
# yamllint disable-line rule:line-length
options: --environment esp32c6-idf-tidy --grep USE_ESP32_VARIANT_ESP32C6 --grep USE_OPENTHREAD --grep USE_ZIGBEE
options: --environment esp32c6-idf-tidy --grep SOC_LP_I2C_SUPPORTED --grep USE_ESP32_VARIANT_ESP32C6 --grep USE_OPENTHREAD --grep USE_ZIGBEE
steps:
- name: Check out code from GitHub
@@ -828,11 +858,12 @@ jobs:
- name: List components
run: echo ${{ matrix.batch.components }}
- name: Cache apt packages
uses: awalsh128/cache-apt-pkgs-action@553a35bb8ebd9fcabcb1c9451aa4c98e1b4ca8a9 # v1.6.3
with:
packages: libsdl2-dev ccache
version: 1.1
- name: Install apt packages
# Not cached: this job is pull-request-only, so a cache save could
# never be shared and would only consume quota.
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends libsdl2-dev ccache
- name: Check out code from GitHub
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
@@ -1006,6 +1037,36 @@ jobs:
# Arduino framework via PlatformIO (only components with an esp32-ard test are built):
python3 script/test_build_components.py -e compile -t esp32-ard -c "$TEST_COMPONENTS" -f --toolchain platformio
pre-commit-seed-cache:
name: Seed pre-commit cache
runs-on: ubuntu-latest
needs:
- common
# Saves a dev-scoped pre-commit cache that pull request runs can
# restore, since pre-commit.ci lite itself never runs on dev pushes.
if: github.event_name == 'push' && github.ref == 'refs/heads/dev'
steps:
- name: Check out code from GitHub
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Restore Python
uses: ./.github/actions/restore-python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Cache pre-commit environments
id: cache-pre-commit
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pre-commit
# Must match the restore key in pre-commit-ci-lite
# yamllint disable-line rule:line-length
key: pre-commit-3|${{ env.pythonLocation }}|${{ hashFiles('.pre-commit-config.yaml') }}
- name: Install pre-commit hook environments
if: steps.cache-pre-commit.outputs.cache-hit != 'true'
run: |
python -m pip install pre-commit
pre-commit install-hooks
pre-commit-ci-lite:
name: pre-commit.ci lite
runs-on: ubuntu-latest
@@ -1021,9 +1082,22 @@ jobs:
with:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- uses: esphome/pre-commit-action@43cd1109c09c544d97196f7730ee5b2e0cc6d81e # v3.0.1 fork with pinned actions/cache
# Inlined from esphome/pre-commit-action with a restore-only cache
# step: the pre-commit-seed-cache job owns saving this cache, so
# pull request runs never write per-PR copies.
- name: Restore pre-commit cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pre-commit
# Must match the key pre-commit-seed-cache saves
# yamllint disable-line rule:line-length
key: pre-commit-3|${{ env.pythonLocation }}|${{ hashFiles('.pre-commit-config.yaml') }}
- name: Run pre-commit
env:
SKIP: pylint,ci-custom
run: |
python -m pip install pre-commit
pre-commit run --show-diff-on-failure --color=always --all-files
- uses: pre-commit-ci/lite-action@5d6cc0eb514c891a40562a58a8e71576c5c7fb43 # v1.1.0
if: always()
+2 -2
View File
@@ -56,7 +56,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
uses: github/codeql-action/init@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
@@ -84,6 +84,6 @@ jobs:
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
uses: github/codeql-action/analyze@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
with:
category: "/language:${{matrix.language}}"
+1 -1
View File
@@ -14,4 +14,4 @@ jobs:
permissions:
issues: write # issues.lock on closed issues
pull-requests: write # issues.lock on closed pull requests
uses: esphome/workflows/.github/workflows/lock.yml@025a1e6255610c498ed590403b7e510b69e474df # 2026.4.1
uses: esphome/workflows/.github/workflows/lock.yml@9f6577fd37b5cf773ab1b9be929714a0dcd15661 # 2026.7.0
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Stale
uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10.4.0
with:
debug-only: ${{ github.ref != 'refs/heads/dev' }} # Dry-run when not run on dev branch
remove-stale-when-updated: true
+1
View File
@@ -145,6 +145,7 @@ esphome/components/dlms_meter/* @latonita @PolarGoose @SimonFischer04 @Tomer27cz
esphome/components/dps310/* @kbx81
esphome/components/ds1307/* @badbadc0ffee
esphome/components/ds2484/* @mrk-its
esphome/components/ds248x/* @tomwellnitz
esphome/components/dsmr/* @glmnet @PolarGoose
esphome/components/duty_time/* @dudanov
esphome/components/ee895/* @Stock-M
+1 -1
View File
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = 2026.7.0b2
PROJECT_NUMBER = 2026.8.0-dev
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
+1
View File
@@ -6,3 +6,4 @@ recursive-include esphome *.cpp *.h *.tcc *.c
recursive-include esphome *.py.script
recursive-include esphome *.jinja
recursive-include esphome LICENSE.txt
recursive-include esphome requirements.txt
+46
View File
@@ -79,6 +79,48 @@ These *are* security bugs in this repo, and we want to hear about them privately
- Flaws that weaken the device's API encryption (Noise), OTA, or web server auth
below their documented guarantees.
## The web server is an open HTTP API by design
The `web_server` component exposes a plain HTTP interface for viewing and
controlling entities, and, when the `web_server` OTA platform is enabled, for
uploading firmware at `/update`. Its only access controls are the optional
`web_server` `auth:` credentials and the network the device sits on.
When `auth:` is not configured, every endpoint is reachable by any client that
can reach the device. This is intentional; enabling `web_server` without `auth:`
is choosing an open control surface, in the same way that running native OTA
without a password leaves OTA open. The API is documented and is meant to be
called by other devices, scripts, and pages.
As defense-in-depth, the web server checks the `Origin` header on browser requests
to its entity control and state endpoints: a request whose `Origin` does not match
the address the device is served on is rejected, and the `allowed_origins` option
widens that list. This blocks the common "confused deputy" (CSRF) case where a page
the operator visits drives the device through their browser. It is **not** an
authentication boundary: it only constrains browsers. Any client that omits the
`Origin` header — `curl`, scripts, or other non-browser callers on the same
network — reaches every endpoint exactly as before. The check also does not cover
the web OTA `/update` endpoint. The device performs no CSRF-token or `Referer`
validation. The following are therefore **not** vulnerabilities in this repository:
- Requests without an `Origin` header (for example `curl`) reaching the control
endpoints, whether or not `web_server` `auth:` is set.
- Requests from an origin the operator added to `allowed_origins`.
- Cross-origin or CSRF firmware upload through the web OTA endpoint (`/update`) when
web OTA is enabled without `web_server` `auth:`. The `/update` endpoint is not
covered by the `Origin` check; this is the same exposure as running OTA without a
password.
The supported defenses are `web_server` `auth:`, protecting OTA (a web password or
a native OTA password), and keeping devices on a trusted, segmented network. See
the security best practices guide linked above.
What remains in scope is bypassing `web_server` `auth:` when it *is* configured,
and any memory-safety or protocol bug in the server reachable without credentials.
This section documents the current design and scope; it is not a judgment that the
design is optimal or that it will not change.
## Explicitly out of scope
- Local attackers who already have shell access on the host that runs `esphome`.
@@ -86,6 +128,10 @@ These *are* security bugs in this repo, and we want to hear about them privately
- Operator-supplied hostile YAML (covered above — config authoring is trusted).
- Attacks that require an already-authenticated device peer (someone who already
holds the API key / OTA / web credentials).
- Access to the device web server or its web OTA endpoint by non-browser clients
(those that send no `Origin` header). The web server is an open HTTP API by
design (see above); browser cross-origin requests are blocked by default, but the
real controls are `web_server` `auth:` and network isolation.
- Anything in the dashboard / device-builder — report that in its own repository
(linked at the top).
- Deployments where the operator removed protections or exposed credentials. See
+1 -1
View File
@@ -22,7 +22,7 @@ RUN \
-r /requirements.txt
# Install the ESPHome Device Builder dashboard.
RUN uv pip install --no-cache-dir esphome-device-builder==1.4.3
RUN uv pip install --no-cache-dir esphome-device-builder==1.6.4
RUN \
platformio settings set enable_telemetry No \
+8 -27
View File
@@ -20,6 +20,7 @@ from . import (
RAM_SECTIONS,
MemoryAnalyzer,
)
from .toolchain import find_elf_path, find_idedata_path, idedata_candidates
if TYPE_CHECKING:
from . import ComponentMemory
@@ -759,45 +760,25 @@ def main():
print(f"Error: {build_path} is not a directory", file=sys.stderr)
sys.exit(1)
# Find firmware.elf
elf_file = None
for elf_candidate in [
build_path / "firmware.elf",
build_path / ".pioenvs" / build_path.name / "firmware.elf",
]:
if elf_candidate.exists():
elf_file = str(elf_candidate)
break
if not elf_file:
print(f"Error: firmware.elf not found in {build_dir}", file=sys.stderr)
elf_path = find_elf_path(build_path)
if not elf_path:
print(f"Error: no firmware ELF found in {build_dir}", file=sys.stderr)
sys.exit(1)
# Find idedata.json - check current directory first, then home
device_name = build_path.name
idedata_candidates = [
Path.cwd() / ".esphome" / "idedata" / f"{device_name}.json",
Path.home() / ".esphome" / "idedata" / f"{device_name}.json",
]
elf_file = str(elf_path)
idedata = None
for idedata_path in idedata_candidates:
if not idedata_path.exists():
continue
if idedata_path := find_idedata_path(build_path):
try:
with idedata_path.open(encoding="utf-8") as f:
raw_data = json.load(f)
idedata = IDEData(raw_data)
print(f"Loaded idedata from: {idedata_path}", file=sys.stderr)
break
except (json.JSONDecodeError, OSError) as e:
print(f"Warning: Failed to load idedata: {e}", file=sys.stderr)
if not idedata:
print(
f"Warning: idedata not found (searched {idedata_candidates[0]} and {idedata_candidates[1]})",
file=sys.stderr,
)
searched = "\n ".join(str(p) for p in idedata_candidates(build_path))
print(f"Warning: idedata not found, searched:\n {searched}", file=sys.stderr)
analyzer = MemoryAnalyzerCLI(elf_file, idedata=idedata)
analyzer.analyze()
+72
View File
@@ -23,6 +23,78 @@ TOOLCHAIN_PREFIXES = [
]
def find_elf_path(build_path: Path) -> Path | None:
"""Locate the firmware ELF inside an ESPHome build directory.
The layout depends on the toolchain that produced the build, so try each
known one in turn.
Args:
build_path: Path to an ESPHome build directory
Returns:
Path to the ELF file, or None if no known layout matches
"""
name = build_path.name
for candidate in (
# Native ESP-IDF: idf.py writes build/<name>.elf, which ESPHome copies
# to build/firmware.elf (see espidf.toolchain.create_elf_copy)
build_path / "build" / "firmware.elf",
# PlatformIO
build_path / "firmware.elf",
build_path / ".pioenvs" / name / "firmware.elf",
# LibreTiny uses raw_firmware.elf
build_path / "raw_firmware.elf",
build_path / ".pioenvs" / name / "raw_firmware.elf",
# Zephyr (nRF52); the SDK nests the artifacts one level deeper from 2.9.2
build_path / ".pioenvs" / name / "zephyr" / "zephyr" / "zephyr.elf",
build_path / ".pioenvs" / name / "zephyr" / "zephyr.elf",
):
if candidate.is_file():
return candidate
return None
def idedata_candidates(build_path: Path) -> list[Path]:
"""Return the idedata locations searched for a build directory, in order.
Exposed so a caller reporting "not found" can name the paths it tried
without keeping its own copy of the list.
Args:
build_path: Path to an ESPHome build directory
Returns:
The candidate idedata JSON paths, most specific first
"""
name = build_path.name
return [
# In .pioenvs for test builds
build_path / ".pioenvs" / name / "idedata.json",
# Both toolchains cache it in the data dir, which holds this build dir:
# <data_dir>/idedata/<name>.json next to <data_dir>/build/<name>
build_path.parent.parent / "idedata" / f"{name}.json",
# Regular builds, invoked from the config dir or from anywhere
Path.cwd() / ".esphome" / "idedata" / f"{name}.json",
Path.home() / ".esphome" / "idedata" / f"{name}.json",
]
def find_idedata_path(build_path: Path) -> Path | None:
"""Locate the idedata JSON belonging to an ESPHome build directory.
Args:
build_path: Path to an ESPHome build directory
Returns:
Path to the idedata JSON, or None if it was not found
"""
for candidate in idedata_candidates(build_path):
if candidate.is_file():
return candidate
return None
def _find_in_platformio_packages(tool_name: str) -> str | None:
"""Search for a tool in PlatformIO package directories.
+35 -2
View File
@@ -7,7 +7,7 @@ and compiled directly: ``esphome compile my_device.esphomebundle.tar.gz``
from __future__ import annotations
from dataclasses import dataclass
from dataclasses import dataclass, field
from enum import StrEnum
import io
import json
@@ -32,6 +32,8 @@ from esphome.core import CORE, EsphomeError
_LOGGER = logging.getLogger(__name__)
DOMAIN = "bundle"
BUNDLE_EXTENSION = ".esphomebundle.tar.gz"
MANIFEST_FILENAME = "manifest.json"
CURRENT_MANIFEST_VERSION = 1
@@ -120,6 +122,32 @@ def _find_used_secret_keys(yaml_files: list[Path]) -> set[str]:
return keys
@dataclass
class BundleData:
"""Files components asked to include, keyed under DOMAIN in CORE.data."""
extra_files: list[Path] = field(default_factory=list)
def _get_data() -> BundleData:
if DOMAIN not in CORE.data:
CORE.data[DOMAIN] = BundleData()
return CORE.data[DOMAIN]
def add_bundle_file(path: Path) -> None:
"""Register a file that a bundle must include.
Bundle discovery walks the validated config, so it only finds files the config
names. Components call this during validation for files it cannot see, such as a
file that is referenced from inside another file.
A relative path is taken as relative to the config directory. Files outside the
config directory are skipped when the bundle is built.
"""
_get_data().extra_files.append(CORE.relative_config_path(path))
@dataclass
class BundleFile:
"""A file to include in the bundle."""
@@ -286,13 +314,18 @@ class ConfigBundleCreator:
with known file extensions are also resolved and checked.
Core ESPHome concepts that use relative paths or directories
are handled explicitly.
are handled explicitly. Files the config does not name at all are
registered by their component with add_bundle_file().
"""
config = self._config
# Generic walk: find all file paths in the validated config
self._walk_config_for_files(config)
# Files registered by components during validation
for extra_file in _get_data().extra_files:
self._add_file(extra_file)
# --- Core ESPHome concepts needing explicit handling ---
# esphome.includes / includes_c - can be relative paths and directories
@@ -1746,12 +1746,6 @@ bool APIConnection::send_hello_response_(const HelloRequest &msg) {
ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu16 ".%" PRIu16, this->helper_->get_client_name(),
this->helper_->get_peername_to(peername), this->client_api_version_major_, this->client_api_version_minor_);
// TODO: Remove before 2026.8.0 (one version after get_object_id backward compat removal)
if (!this->client_supports_api_version(1, 14)) {
ESP_LOGW(TAG, "'%s' using outdated API %" PRIu16 ".%" PRIu16 ", update to 1.14+", this->helper_->get_client_name(),
this->client_api_version_major_, this->client_api_version_minor_);
}
HelloResponse resp;
resp.api_version_major = 1;
resp.api_version_minor = 14;
+6 -2
View File
@@ -166,10 +166,14 @@ class APIConnection final : public APIServerConnectionBase {
#endif
bool try_send_log_message(int level, const char *tag, const char *line, size_t message_len);
#ifdef USE_API_HOMEASSISTANT_SERVICES
void send_homeassistant_action(const HomeassistantActionRequest &call) {
// Returns whether this client has subscribed to Home Assistant actions; the message
// is only handed to the send path when subscribed. A true return does not guarantee
// delivery - it lets the caller warn when no connected client has the subscription.
bool send_homeassistant_action(const HomeassistantActionRequest &call) {
if (!this->flags_.service_call_subscription)
return;
return false;
this->send_message(call);
return true;
}
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
void on_homeassistant_action_response(const HomeassistantActionResponse &msg);
+9 -1
View File
@@ -426,8 +426,16 @@ void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = bat
#ifdef USE_API_HOMEASSISTANT_SERVICES
void APIServer::send_homeassistant_action(const HomeassistantActionRequest &call) {
bool has_subscriber = false;
for (auto &client : this->active_clients()) {
client->send_homeassistant_action(call);
has_subscriber |= client->send_homeassistant_action(call);
}
if (!has_subscriber) {
// Home Assistant subscribes to actions shortly *after* authenticating, so actions
// fired right at connection time (on_client_connected, on_time_sync, ...) can
// arrive before the subscription and are lost - warn instead of failing silently.
ESP_LOGW(TAG, "Home Assistant %s '%s' dropped; %s", call.is_event ? "event" : "action", call.service.c_str(),
this->is_connected() ? "client has not subscribed to actions (yet)" : "no client connected");
}
}
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
+17 -11
View File
@@ -18,7 +18,7 @@ with warnings.catch_warnings():
import contextlib
from esphome.const import CONF_KEY, CONF_PORT, __version__
from esphome.core import CORE, EsphomeError
from esphome.core import CORE
from esphome.util import safe_print
from . import CONF_ENCRYPTION
@@ -36,15 +36,17 @@ class _LogLineProcessor:
"""Feeds incoming log lines to the stack-trace decoder.
Two responsibilities beyond just calling the decoder:
1. Catch EsphomeError. on_log runs inside an asyncio protocol
callback; if an exception escapes, the loop tears the transport
down with "Fatal error: protocol.data_received() call failed."
and ReconnectLogic immediately reconnects, the device replays
the same crash trace, and we loop forever.
2. Disable decoding after the first failure. _decode_pc shells out
to PlatformIO via _run_idedata, which is expensive; a single
crash dump can contain many PC/BT lines and we don't want to
retry the failing subprocess for each one.
1. Catch everything the decoder can raise. aioesphomeapi isolates
exceptions raised by log handlers, so an escaping one no longer
kills the session, but it does log a full traceback per line. A
crash dump carries a PC line plus one per backtrace frame, so the
tracebacks bury the dump the user is trying to read. Decoding is a
diagnostic nicety; nothing it raises is worth that noise.
2. Disable decoding after the first failure. _decode_pc shells out to
the toolchain to resolve addr2line, which is expensive; a single
crash dump can contain many PC/BT lines and we don't want to retry
the failing subprocess for each one. This only works if every
failure is caught, which is why 1 is not narrowed to EsphomeError.
"""
def __init__(self, config: dict[str, Any], platform_handler: Any | None) -> None:
@@ -61,12 +63,13 @@ class _LogLineProcessor:
self.backtrace_state = self._platform_handler(
self._config, raw_line, self.backtrace_state
)
except EsphomeError as exc:
except Exception as exc: # noqa: BLE001 # pylint: disable=broad-except
self._decode_enabled = False
self.backtrace_state = False
# _run_idedata raises EsphomeError with no message; fall back
# to a generic explanation when str(exc) is empty.
detail = str(exc) or "build artifacts not found locally"
_LOGGER.debug("Stack-trace decoding failed", exc_info=True)
_LOGGER.warning(
"Crash trace decoding unavailable: %s. "
"Run 'esphome compile' for this device to enable PC decoding.",
@@ -152,6 +155,9 @@ async def async_run_logs(
name=name,
subscribe_states=subscribe_states,
allow_plaintext_fallback=True,
# A top-level ``deep_sleep:`` block means the device is only awake
# briefly; cap the reconnect backoff so a wake window is not missed.
deep_sleep="deep_sleep" in config,
)
try:
await asyncio.Event().wait()
+1
View File
@@ -7,6 +7,7 @@ AQICalculatorType = aqi_ns.enum("AQICalculatorType")
CONF_AQI = "aqi"
CONF_CALCULATION_TYPE = "calculation_type"
CONF_EXTENDED_RANGE = "extended_range"
AQI_CALCULATION_TYPE = {
"CAQI": AQICalculatorType.CAQI_TYPE,
@@ -6,7 +6,7 @@ namespace esphome::aqi {
class AbstractAQICalculator {
public:
virtual uint16_t get_aqi(float pm2_5_value, float pm10_0_value) = 0;
virtual uint16_t get_aqi(float pm2_5_value, float pm10_0_value, bool extended_range) = 0;
};
} // namespace esphome::aqi
+20 -10
View File
@@ -11,10 +11,12 @@ namespace esphome::aqi {
class AQICalculator : public AbstractAQICalculator {
public:
uint16_t get_aqi(float pm2_5_value, float pm10_0_value) override {
float pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
float pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
uint16_t get_aqi(float pm2_5_value, float pm10_0_value, bool extended_range) override {
float pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID, extended_range);
float pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID, extended_range);
float aqi = std::max({pm2_5_index, pm10_0_index, 0.0f});
// extended_range lets the index run past the standard maximum, so clamp to the sensor's range.
aqi = std::min(aqi, static_cast<float>(std::numeric_limits<uint16_t>::max()));
return static_cast<uint16_t>(std::lround(aqi));
}
@@ -30,7 +32,7 @@ class AQICalculator : public AbstractAQICalculator {
{35.5f, 55.5f},
{55.5f, 125.5f},
{125.5f, 225.5f},
{225.5f, std::numeric_limits<float>::max()}
{225.5f, 500.4f} // EPA 2024: AQI 301-500 maps to PM2.5 225.5-500.4 ug/m3
// clang-format on
};
@@ -41,11 +43,11 @@ class AQICalculator : public AbstractAQICalculator {
{155.0f, 255.0f},
{255.0f, 355.0f},
{355.0f, 425.0f},
{425.0f, std::numeric_limits<float>::max()}
{425.0f, 604.0f} // EPA: AQI 301-500 maps to PM10 425-604 ug/m3 (top of the 401-500 band)
// clang-format on
};
static float calculate_index(float value, const float array[NUM_LEVELS][2]) {
static float calculate_index(float value, const float array[NUM_LEVELS][2], bool extended_range) {
int grid_index = get_grid_index(value, array);
if (grid_index == -1) {
return -1.0f;
@@ -55,14 +57,22 @@ class AQICalculator : public AbstractAQICalculator {
float conc_lo = array[grid_index][0];
float conc_hi = array[grid_index][1];
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
float index = (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
// Concentrations above the highest breakpoint run the linear fit past aqi_hi. By default we
// clamp to the standard maximum; with extended_range we keep the extrapolated "over-range"
// value so heavy pollution reports numbers beyond what the standard defines.
if (grid_index == NUM_LEVELS - 1 && !extended_range && index > aqi_hi) {
return aqi_hi;
}
return index;
}
static int get_grid_index(float value, const float array[NUM_LEVELS][2]) {
for (int i = 0; i < NUM_LEVELS; i++) {
const bool in_range =
(value >= array[i][0]) && ((i == NUM_LEVELS - 1) ? (value <= array[i][1]) // last bucket inclusive
: (value < array[i][1])); // others exclusive on hi
// The top band is open-ended: any value at or above its lower breakpoint falls into it,
// and calculate_index() decides whether to clamp or extrapolate.
const bool in_range = (value >= array[i][0]) && (i == NUM_LEVELS - 1 || value < array[i][1]);
if (in_range) {
return i;
}
+2 -1
View File
@@ -24,6 +24,7 @@ void AQISensor::setup() {
void AQISensor::dump_config() {
ESP_LOGCONFIG(TAG, "AQI Sensor:");
ESP_LOGCONFIG(TAG, " Calculation Type: %s", this->aqi_calc_type_ == AQI_TYPE ? "AQI" : "CAQI");
ESP_LOGCONFIG(TAG, " Extended Range: %s", this->extended_range_ ? "enabled" : "disabled");
if (this->pm_2_5_sensor_ != nullptr) {
ESP_LOGCONFIG(TAG, " PM2.5 Sensor: '%s'", this->pm_2_5_sensor_->get_name().c_str());
}
@@ -44,7 +45,7 @@ void AQISensor::calculate_aqi_() {
return;
}
uint16_t aqi = calculator->get_aqi(this->pm_2_5_value_, this->pm_10_0_value_);
uint16_t aqi = calculator->get_aqi(this->pm_2_5_value_, this->pm_10_0_value_, this->extended_range_);
this->publish_state(aqi);
}
+2
View File
@@ -14,6 +14,7 @@ class AQISensor final : public sensor::Sensor, public Component {
void set_pm_2_5_sensor(sensor::Sensor *sensor) { this->pm_2_5_sensor_ = sensor; }
void set_pm_10_0_sensor(sensor::Sensor *sensor) { this->pm_10_0_sensor_ = sensor; }
void set_aqi_calculation_type(AQICalculatorType type) { this->aqi_calc_type_ = type; }
void set_extended_range(bool extended_range) { this->extended_range_ = extended_range; }
protected:
void calculate_aqi_();
@@ -21,6 +22,7 @@ class AQISensor final : public sensor::Sensor, public Component {
sensor::Sensor *pm_2_5_sensor_{nullptr};
sensor::Sensor *pm_10_0_sensor_{nullptr};
AQICalculatorType aqi_calc_type_{AQI_TYPE};
bool extended_range_{false};
AQICalculatorFactory aqi_calculator_factory_;
float pm_2_5_value_{NAN};
+13 -10
View File
@@ -9,25 +9,28 @@ namespace esphome::aqi {
class CAQICalculator : public AbstractAQICalculator {
public:
uint16_t get_aqi(float pm2_5_value, float pm10_0_value) override {
// The CAQI (CITEAIR) scale defines no maximum: its top "Very high" class is simply ">100". We
// therefore always extrapolate the top band past 100 without limit, so the extended_range flag
// (which lifts the AQI calculator's fixed 500 cap) has no meaning here and is ignored.
uint16_t get_aqi(float pm2_5_value, float pm10_0_value, bool /*extended_range*/) override {
float pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
float pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
float aqi = std::max({pm2_5_index, pm10_0_index, 0.0f});
aqi = std::min(aqi, static_cast<float>(std::numeric_limits<uint16_t>::max()));
return static_cast<uint16_t>(std::lround(aqi));
}
protected:
static constexpr int NUM_LEVELS = 5;
static constexpr int NUM_LEVELS = 4;
static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 75}, {76, 100}, {101, 400}};
static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 75}, {76, 100}};
static constexpr float PM2_5_GRID[NUM_LEVELS][2] = {
// clang-format off
{0.0f, 15.1f},
{15.1f, 30.1f},
{30.1f, 55.1f},
{55.1f, 110.1f},
{110.1f, std::numeric_limits<float>::max()}
{55.1f, 110.1f}
// clang-format on
};
@@ -36,8 +39,7 @@ class CAQICalculator : public AbstractAQICalculator {
{0.0f, 25.1f},
{25.1f, 50.1f},
{50.1f, 90.1f},
{90.1f, 180.1f},
{180.1f, std::numeric_limits<float>::max()}
{90.1f, 180.1f}
// clang-format on
};
@@ -52,14 +54,15 @@ class CAQICalculator : public AbstractAQICalculator {
float conc_lo = array[grid_index][0];
float conc_hi = array[grid_index][1];
// The top band is open-ended (see get_grid_index), so for concentrations above the last
// breakpoint this linear fit extrapolates past 100 unbounded, matching CAQI's open ">100" class.
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
}
static int get_grid_index(float value, const float array[NUM_LEVELS][2]) {
for (int i = 0; i < NUM_LEVELS; i++) {
const bool in_range =
(value >= array[i][0]) && ((i == NUM_LEVELS - 1) ? (value <= array[i][1]) // last bucket inclusive
: (value < array[i][1])); // others exclusive on hi
// The top band is open-ended: any value at or above its lower breakpoint falls into it.
const bool in_range = (value >= array[i][0]) && (i == NUM_LEVELS - 1 || value < array[i][1]);
if (in_range) {
return i;
}
+17 -3
View File
@@ -8,14 +8,25 @@ from esphome.const import (
STATE_CLASS_MEASUREMENT,
)
from . import AQI_CALCULATION_TYPE, CONF_CALCULATION_TYPE, aqi_ns
from . import AQI_CALCULATION_TYPE, CONF_CALCULATION_TYPE, CONF_EXTENDED_RANGE, aqi_ns
CODEOWNERS = ["@jasstrong"]
DEPENDENCIES = ["sensor"]
AQISensor = aqi_ns.class_("AQISensor", sensor.Sensor, cg.Component)
CONFIG_SCHEMA = (
def _validate_extended_range(config):
if CONF_EXTENDED_RANGE in config and config[CONF_CALCULATION_TYPE] == "CAQI":
raise cv.Invalid(
f"'{CONF_EXTENDED_RANGE}' is not supported with 'calculation_type: CAQI'. "
"CAQI has no maximum value by specification, so it is always reported unbounded.",
[CONF_EXTENDED_RANGE],
)
return config
CONFIG_SCHEMA = cv.All(
sensor.sensor_schema(
AQISensor,
accuracy_decimals=0,
@@ -29,9 +40,11 @@ CONFIG_SCHEMA = (
cv.Required(CONF_CALCULATION_TYPE): cv.enum(
AQI_CALCULATION_TYPE, upper=True
),
cv.Optional(CONF_EXTENDED_RANGE): cv.boolean,
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(cv.COMPONENT_SCHEMA),
_validate_extended_range,
)
@@ -46,3 +59,4 @@ async def to_code(config):
cg.add(var.set_pm_10_0_sensor(pm_10_0_sensor))
cg.add(var.set_aqi_calculation_type(config[CONF_CALCULATION_TYPE]))
cg.add(var.set_extended_range(config.get(CONF_EXTENDED_RANGE, False)))
+1 -5
View File
@@ -24,11 +24,7 @@ void I2CAS3935Component::write_register(uint8_t reg, uint8_t mask, uint8_t bits,
uint8_t I2CAS3935Component::read_register(uint8_t reg) {
uint8_t value;
if (write(&reg, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Writing register failed!");
return 0;
}
if (read(&value, 1) != i2c::ERROR_OK) {
if (!this->read_byte(reg, &value)) {
ESP_LOGW(TAG, "Reading register failed!");
return 0;
}
+2 -2
View File
@@ -5,7 +5,7 @@ from esphome.const import (
CONF_CLEAR,
CONF_GAIN,
CONF_ID,
DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_EMPTY,
ICON_BRIGHTNESS_5,
STATE_CLASS_MEASUREMENT,
)
@@ -54,7 +54,7 @@ SENSOR_SCHEMA = sensor.sensor_schema(
unit_of_measurement=UNIT_COUNTS,
icon=ICON_BRIGHTNESS_5,
accuracy_decimals=0,
device_class=DEVICE_CLASS_ILLUMINANCE,
device_class=DEVICE_CLASS_EMPTY,
state_class=STATE_CLASS_MEASUREMENT,
)
@@ -65,12 +65,11 @@ optional<ParseResult> ATCMiThermometer::parse_header_(const esp32_ble_tracker::S
return {};
}
static uint8_t last_frame_count = 0;
if (last_frame_count == raw[12]) {
ESP_LOGVV(TAG, "parse_header(): duplicate data packet received (%hhu).", last_frame_count);
if (this->last_frame_count_ == raw[12]) {
ESP_LOGVV(TAG, "parse_header(): duplicate data packet received (%hhu).", this->last_frame_count_);
return {};
}
last_frame_count = raw[12];
this->last_frame_count_ = raw[12];
return result;
}
@@ -38,6 +38,8 @@ class ATCMiThermometer final : public Component, public esp32_ble_tracker::ESPBT
sensor::Sensor *battery_voltage_{nullptr};
sensor::Sensor *signal_strength_{nullptr};
uint8_t last_frame_count_{0};
optional<ParseResult> parse_header_(const esp32_ble_tracker::ServiceData &service_data);
bool parse_message_(const std::vector<uint8_t> &message, ParseResult &result);
bool report_results_(const optional<ParseResult> &result, const char *address);
@@ -37,15 +37,15 @@ namespace esphome::beken_spi_led_strip {
static const char *const TAG = "beken_spi_led_strip";
struct spi_data_t {
struct SpiData {
SemaphoreHandle_t dma_tx_semaphore;
volatile bool tx_in_progress;
bool first_run;
};
static spi_data_t *spi_data = nullptr;
static SpiData *spi_data = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static void set_spi_ctrl_register(unsigned long bit, bool val) {
static void set_spi_ctrl_register(uint32_t bit, bool val) {
uint32_t value = REG_READ(SPI_CTRL);
if (val == 0) {
value &= ~bit;
@@ -55,7 +55,7 @@ static void set_spi_ctrl_register(unsigned long bit, bool val) {
REG_WRITE(SPI_CTRL, value);
}
static void set_spi_config_register(unsigned long bit, bool val) {
static void set_spi_config_register(uint32_t bit, bool val) {
uint32_t value = REG_READ(SPI_CONFIG);
if (val == 0) {
value &= ~bit;
@@ -67,7 +67,7 @@ static void set_spi_config_register(unsigned long bit, bool val) {
void spi_dma_tx_enable(bool enable) {
GDMA_CFG_ST en_cfg;
set_spi_config_register(SPI_TX_EN, enable ? 1 : 0);
set_spi_config_register(SPI_TX_EN, enable);
en_cfg.channel = SPI_TX_DMA_CHANNEL;
en_cfg.param = enable ? 1 : 0;
sddev_control(GDMA_DEV_NAME, CMD_GDMA_SET_DMA_ENABLE, &en_cfg);
@@ -110,13 +110,13 @@ static void spi_set_clock(uint32_t max_hz) {
param &= ~(SPI_CKR_MASK << SPI_CKR_POSI);
param |= (div << SPI_CKR_POSI);
REG_WRITE(SPI_CTRL, param);
ESP_LOGD(TAG, "target frequency: %d, actual frequency: %d", max_hz, source_clk / 2 / div);
ESP_LOGD(TAG, "target frequency: %" PRIu32 ", actual frequency: %d", max_hz, source_clk / 2 / div);
}
void spi_dma_tx_finish_callback(unsigned int param) {
spi_data->tx_in_progress = false;
xSemaphoreGive(spi_data->dma_tx_semaphore);
spi_dma_tx_enable(0);
spi_dma_tx_enable(false);
}
void BekenSPILEDStripLightOutput::setup() {
@@ -161,7 +161,7 @@ void BekenSPILEDStripLightOutput::setup() {
return;
}
spi_data = (spi_data_t *) calloc(1, sizeof(spi_data_t));
spi_data = (SpiData *) calloc(1, sizeof(SpiData)); // NOLINT(cppcoreguidelines-no-malloc)
if (spi_data == nullptr) {
ESP_LOGE(TAG, "Cannot allocate spi_data!");
this->mark_failed();
@@ -177,20 +177,20 @@ void BekenSPILEDStripLightOutput::setup() {
spi_data->first_run = true;
set_spi_ctrl_register(MSTEN, 0);
set_spi_ctrl_register(BIT_WDTH, 0);
set_spi_ctrl_register(MSTEN, false);
set_spi_ctrl_register(BIT_WDTH, false);
spi_set_clock(this->spi_frequency_);
set_spi_ctrl_register(CKPOL, 0);
set_spi_ctrl_register(CKPHA, 0);
set_spi_ctrl_register(MSTEN, 1);
set_spi_ctrl_register(SPIEN, 1);
set_spi_ctrl_register(CKPOL, false);
set_spi_ctrl_register(CKPHA, false);
set_spi_ctrl_register(MSTEN, true);
set_spi_ctrl_register(SPIEN, true);
set_spi_ctrl_register(TXINT_EN, 0);
set_spi_ctrl_register(RXINT_EN, 0);
set_spi_config_register(SPI_TX_FINISH_EN, 1);
set_spi_config_register(SPI_RX_FINISH_EN, 1);
set_spi_ctrl_register(RXOVR_EN, 0);
set_spi_ctrl_register(TXOVR_EN, 0);
set_spi_ctrl_register(TXINT_EN, false);
set_spi_ctrl_register(RXINT_EN, false);
set_spi_config_register(SPI_TX_FINISH_EN, true);
set_spi_config_register(SPI_RX_FINISH_EN, true);
set_spi_ctrl_register(RXOVR_EN, false);
set_spi_ctrl_register(TXOVR_EN, false);
value = REG_READ(SPI_CTRL);
value &= ~CTRL_NSSMD_3;
@@ -199,7 +199,7 @@ void BekenSPILEDStripLightOutput::setup() {
value = GFUNC_MODE_SPI_DMA;
sddev_control(GPIO_DEV_NAME, CMD_GPIO_ENABLE_SECOND, &value);
set_spi_ctrl_register(SPI_S_CS_UP_INT_EN, 0);
set_spi_ctrl_register(SPI_S_CS_UP_INT_EN, false);
GDMA_CFG_ST en_cfg;
GDMACFG_TPYES_ST init_cfg;
@@ -210,7 +210,7 @@ void BekenSPILEDStripLightOutput::setup() {
init_cfg.dstptr_incr = 0;
init_cfg.srcptr_incr = 1;
init_cfg.src_start_addr = this->dma_buf_;
init_cfg.dst_start_addr = (void *) SPI_DAT; // SPI_DMA_REG4_TXFIFO
init_cfg.dst_start_addr = (void *) SPI_DAT; // NOLINT(performance-no-int-to-ptr) SPI_DMA_REG4_TXFIFO
init_cfg.channel = SPI_TX_DMA_CHANNEL;
init_cfg.prio = 0; // 10
init_cfg.u.type4.src_loop_start_addr = this->dma_buf_;
@@ -230,7 +230,7 @@ void BekenSPILEDStripLightOutput::setup() {
en_cfg.param = 0;
sddev_control(GDMA_DEV_NAME, CMD_GDMA_CFG_SRCADDR_LOOP, &en_cfg);
spi_dma_tx_enable(0);
spi_dma_tx_enable(false);
value = REG_READ(SPI_CONFIG);
value &= ~(0xFFF << 8);
@@ -247,7 +247,8 @@ void BekenSPILEDStripLightOutput::set_led_params(uint8_t bit0, uint8_t bit1, uin
void BekenSPILEDStripLightOutput::write_state(light::LightState *state) {
// protect from refreshing too often
uint32_t now = micros();
if (*this->max_refresh_rate_ != 0 && (now - this->last_refresh_) < *this->max_refresh_rate_) {
if (this->max_refresh_rate_.has_value() && *this->max_refresh_rate_ != 0 &&
(now - this->last_refresh_) < *this->max_refresh_rate_) {
// try again next loop iteration, so that this change won't get lost
this->schedule_show();
return;
@@ -293,7 +294,7 @@ void BekenSPILEDStripLightOutput::write_state(light::LightState *state) {
}
spi_data->first_run = false;
spi_dma_tx_enable(1);
spi_dma_tx_enable(true);
this->status_clear_warning();
}
@@ -376,7 +377,7 @@ void BekenSPILEDStripLightOutput::dump_config() {
" RGB Order: %s\n"
" Max refresh rate: %" PRIu32 "\n"
" Number of LEDs: %u",
rgb_order, *this->max_refresh_rate_, this->num_leds_);
rgb_order, this->max_refresh_rate_.value_or(0), this->num_leds_);
}
float BekenSPILEDStripLightOutput::get_setup_priority() const { return setup_priority::HARDWARE; }
@@ -245,7 +245,9 @@ void BluetoothConnection::send_service_for_discovery_() {
service_resp.characteristics.init(total_char_count);
uint16_t char_offset = 0;
esp_gattc_char_elem_t char_result;
while (true) { // characteristics
// Bound by total_char_count: the vector is sized for it, and a malicious peripheral
// can make enumeration return more entries than the count query reported
while (char_offset < total_char_count) { // characteristics
uint16_t char_count = 1;
esp_gatt_status_t char_status =
esp_ble_gattc_get_all_char(this->gattc_if_, this->conn_id_, service_result.start_handle,
@@ -287,7 +289,7 @@ void BluetoothConnection::send_service_for_discovery_() {
characteristic_resp.descriptors.init(total_desc_count);
uint16_t desc_offset = 0;
esp_gattc_descr_elem_t desc_result;
while (true) { // descriptors
while (desc_offset < total_desc_count) { // descriptors
uint16_t desc_count = 1;
esp_gatt_status_t desc_status = esp_ble_gattc_get_all_descr(
this->gattc_if_, this->conn_id_, char_result.char_handle, &desc_result, &desc_count, desc_offset);
+77
View File
@@ -21,6 +21,7 @@ MULTI_CONF = True
ns = cg.esphome_ns.namespace("cc1101")
CC1101Component = ns.class_("CC1101Component", cg.Component, spi.SPIDevice)
CC1101Listener = ns.class_("CC1101Listener")
# Config keys
CONF_RX_ATTENUATION = "rx_attenuation"
@@ -48,6 +49,15 @@ CONF_FILTER_LENGTH_FSK_MSK = "filter_length_fsk_msk"
CONF_FILTER_LENGTH_ASK_OOK = "filter_length_ask_ook"
CONF_FREEZE = "freeze"
CONF_HYST_LEVEL = "hyst_level"
CONF_FOC_BS_CS_GATE = "foc_bs_cs_gate"
CONF_FOC_LIMIT = "foc_limit"
CONF_FOC_PRE_K = "foc_pre_k"
CONF_FOC_POST_K = "foc_post_k"
CONF_BS_LIMIT = "bs_limit"
CONF_BS_PRE_KI = "bs_pre_ki"
CONF_BS_PRE_KP = "bs_pre_kp"
CONF_BS_POST_KI = "bs_post_ki"
CONF_BS_POST_KP = "bs_post_kp"
# Packet mode config keys
CONF_PACKET_MODE = "packet_mode"
@@ -161,6 +171,64 @@ HYST_LEVEL = {
"High": HystLevel.HYST_LEVEL_HIGH,
}
FocLimit = ns.enum("FocLimit", True)
FOC_LIMIT = {
"Disabled": FocLimit.FOC_LIMIT_DISABLED,
"BW/8": FocLimit.FOC_LIMIT_BW_8,
"BW/4": FocLimit.FOC_LIMIT_BW_4,
"BW/2": FocLimit.FOC_LIMIT_BW_2,
}
FocPreK = ns.enum("FocPreK", True)
FOC_PRE_K = {
"K": FocPreK.FOC_PRE_K_K,
"2K": FocPreK.FOC_PRE_K_2K,
"3K": FocPreK.FOC_PRE_K_3K,
"4K": FocPreK.FOC_PRE_K_4K,
}
FocPostK = ns.enum("FocPostK", True)
FOC_POST_K = {
"Same": FocPostK.FOC_POST_K_SAME,
"K/2": FocPostK.FOC_POST_K_K_2,
}
BsLimit = ns.enum("BsLimit", True)
BS_LIMIT = {
"Disabled": BsLimit.BS_LIMIT_DISABLED,
"3.125%": BsLimit.BS_LIMIT_3P125_PERCENT,
"6.25%": BsLimit.BS_LIMIT_6P25_PERCENT,
"12.5%": BsLimit.BS_LIMIT_12P5_PERCENT,
}
BsPreKi = ns.enum("BsPreKi", True)
BS_PRE_KI = {
"KI": BsPreKi.BS_PRE_KI_KI,
"2KI": BsPreKi.BS_PRE_KI_2KI,
"3KI": BsPreKi.BS_PRE_KI_3KI,
"4KI": BsPreKi.BS_PRE_KI_4KI,
}
BsPreKp = ns.enum("BsPreKp", True)
BS_PRE_KP = {
"KP": BsPreKp.BS_PRE_KP_KP,
"2KP": BsPreKp.BS_PRE_KP_2KP,
"3KP": BsPreKp.BS_PRE_KP_3KP,
"4KP": BsPreKp.BS_PRE_KP_4KP,
}
BsPostKi = ns.enum("BsPostKi", True)
BS_POST_KI = {
"Same": BsPostKi.BS_POST_KI_SAME,
"KI/2": BsPostKi.BS_POST_KI_KI_2,
}
BsPostKp = ns.enum("BsPostKp", True)
BS_POST_KP = {
"Same": BsPostKp.BS_POST_KP_SAME,
"KP": BsPostKp.BS_POST_KP_KP,
}
# Optional settings to generate setter calls for
CONFIG_MAP = {
cv.Optional(CONF_OUTPUT_POWER, default=10): cv.float_range(min=-30.0, max=11.0),
@@ -214,6 +282,15 @@ CONFIG_MAP = {
cv.Optional(CONF_FREEZE): cv.enum(FREEZE, upper=False),
cv.Optional(CONF_WAIT_TIME, default="32"): cv.enum(WAIT_TIME, upper=False),
cv.Optional(CONF_HYST_LEVEL): cv.enum(HYST_LEVEL, upper=False),
cv.Optional(CONF_FOC_BS_CS_GATE): cv.boolean,
cv.Optional(CONF_FOC_LIMIT): cv.enum(FOC_LIMIT, upper=False),
cv.Optional(CONF_FOC_PRE_K): cv.enum(FOC_PRE_K, upper=False),
cv.Optional(CONF_FOC_POST_K): cv.enum(FOC_POST_K, upper=False),
cv.Optional(CONF_BS_LIMIT): cv.enum(BS_LIMIT, upper=False),
cv.Optional(CONF_BS_PRE_KI): cv.enum(BS_PRE_KI, upper=False),
cv.Optional(CONF_BS_PRE_KP): cv.enum(BS_PRE_KP, upper=False),
cv.Optional(CONF_BS_POST_KI): cv.enum(BS_POST_KI, upper=False),
cv.Optional(CONF_BS_POST_KP): cv.enum(BS_POST_KP, upper=False),
cv.Optional(CONF_PACKET_MODE, default=False): cv.boolean,
cv.Optional(CONF_PACKET_LENGTH): cv.uint8_t,
cv.Optional(CONF_CRC_ENABLE, default=False): cv.boolean,
+63
View File
@@ -672,6 +672,69 @@ void CC1101Component::set_hyst_level(HystLevel value) {
}
}
void CC1101Component::set_foc_bs_cs_gate(bool value) {
this->state_.FOC_BS_CS_GATE = value ? 1 : 0;
if (this->initialized_) {
this->write_(Register::FOCCFG);
}
}
void CC1101Component::set_foc_limit(FocLimit value) {
this->state_.FOC_LIMIT = static_cast<uint8_t>(value);
if (this->initialized_) {
this->write_(Register::FOCCFG);
}
}
void CC1101Component::set_foc_pre_k(FocPreK value) {
this->state_.FOC_PRE_K = static_cast<uint8_t>(value);
if (this->initialized_) {
this->write_(Register::FOCCFG);
}
}
void CC1101Component::set_foc_post_k(FocPostK value) {
this->state_.FOC_POST_K = static_cast<uint8_t>(value);
if (this->initialized_) {
this->write_(Register::FOCCFG);
}
}
void CC1101Component::set_bs_limit(BsLimit value) {
this->state_.BS_LIMIT = static_cast<uint8_t>(value);
if (this->initialized_) {
this->write_(Register::BSCFG);
}
}
void CC1101Component::set_bs_pre_ki(BsPreKi value) {
this->state_.BS_PRE_KI = static_cast<uint8_t>(value);
if (this->initialized_) {
this->write_(Register::BSCFG);
}
}
void CC1101Component::set_bs_pre_kp(BsPreKp value) {
this->state_.BS_PRE_KP = static_cast<uint8_t>(value);
if (this->initialized_) {
this->write_(Register::BSCFG);
}
}
void CC1101Component::set_bs_post_ki(BsPostKi value) {
this->state_.BS_POST_KI = static_cast<uint8_t>(value);
if (this->initialized_) {
this->write_(Register::BSCFG);
}
}
void CC1101Component::set_bs_post_kp(BsPostKp value) {
this->state_.BS_POST_KP = static_cast<uint8_t>(value);
if (this->initialized_) {
this->write_(Register::BSCFG);
}
}
void CC1101Component::set_packet_mode(bool value) {
this->state_.PKT_FORMAT =
static_cast<uint8_t>(value ? PacketFormat::PACKET_FORMAT_FIFO : PacketFormat::PACKET_FORMAT_ASYNC_SERIAL);
+11
View File
@@ -71,6 +71,17 @@ class CC1101Component final : public Component,
void set_wait_time(WaitTime value);
void set_hyst_level(HystLevel value);
// Frequency offset compensation and bit synchronization settings
void set_foc_bs_cs_gate(bool value);
void set_foc_limit(FocLimit value);
void set_foc_pre_k(FocPreK value);
void set_foc_post_k(FocPostK value);
void set_bs_limit(BsLimit value);
void set_bs_pre_ki(BsPreKi value);
void set_bs_pre_kp(BsPreKp value);
void set_bs_post_ki(BsPostKi value);
void set_bs_post_kp(BsPostKp value);
// Packet mode settings
void set_packet_mode(bool value);
void set_packet_length(uint8_t value);
+50
View File
@@ -231,6 +231,56 @@ enum class HystLevel : uint8_t {
HYST_LEVEL_HIGH,
};
enum class FocLimit : uint8_t {
FOC_LIMIT_DISABLED,
FOC_LIMIT_BW_8,
FOC_LIMIT_BW_4,
FOC_LIMIT_BW_2,
};
enum class FocPreK : uint8_t {
FOC_PRE_K_K,
FOC_PRE_K_2K,
FOC_PRE_K_3K,
FOC_PRE_K_4K,
};
enum class FocPostK : uint8_t {
FOC_POST_K_SAME,
FOC_POST_K_K_2,
};
enum class BsLimit : uint8_t {
BS_LIMIT_DISABLED,
BS_LIMIT_3P125_PERCENT,
BS_LIMIT_6P25_PERCENT,
BS_LIMIT_12P5_PERCENT,
};
enum class BsPreKi : uint8_t {
BS_PRE_KI_KI,
BS_PRE_KI_2KI,
BS_PRE_KI_3KI,
BS_PRE_KI_4KI,
};
enum class BsPreKp : uint8_t {
BS_PRE_KP_KP,
BS_PRE_KP_2KP,
BS_PRE_KP_3KP,
BS_PRE_KP_4KP,
};
enum class BsPostKi : uint8_t {
BS_POST_KI_SAME,
BS_POST_KI_KI_2,
};
enum class BsPostKp : uint8_t {
BS_POST_KP_SAME,
BS_POST_KP_KP,
};
enum class PacketFormat : uint8_t {
PACKET_FORMAT_FIFO,
PACKET_FORMAT_SYNC_SERIAL,
+2 -2
View File
@@ -28,7 +28,7 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
ESP_LOGD(TAG,
"LibreTiny debug info:\n"
" Version: %s\n"
" Chip: %s (%04x) @ %u MHz\n"
" Chip: %s (%04x) @ %" PRIu32 " MHz\n"
" Chip ID: 0x%06" PRIX32 "\n"
" Board: %s\n"
" Flash: %" PRIu32 " KiB\n"
@@ -38,7 +38,7 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
lt_get_board_code(), flash_kib, ram_kib, reset_reason);
pos = buf_append_str(buf, size, pos, "|Version: ");
pos = buf_append_str(buf, size, pos, LT_BANNER_STR + 10);
pos = buf_append_str(buf, size, pos, &LT_BANNER_STR[10]);
pos = buf_append_str(buf, size, pos, "|Reset Reason: ");
pos = buf_append_str(buf, size, pos, reset_reason);
pos = buf_append_str(buf, size, pos, "|Chip Name: ");
+1 -1
View File
@@ -74,7 +74,7 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
constexpr size_t size = DEVICE_INFO_BUFFER_SIZE;
char *buf = buffer.data();
uint32_t cpu_freq = ::rp2040.f_cpu();
uint32_t cpu_freq = RP2040::f_cpu();
ESP_LOGD(TAG, "CPU Frequency: %" PRIu32, cpu_freq);
pos = buf_append_printf(buf, size, pos, "|CPU Frequency: %" PRIu32, cpu_freq);
+50 -8
View File
@@ -30,6 +30,7 @@ from esphome.const import (
CONF_SECOND,
CONF_SLEEP_DURATION,
CONF_TIME_ID,
CONF_TRIGGER_ID,
CONF_WAKEUP_PIN,
PLATFORM_BK72XX,
PLATFORM_ESP32,
@@ -234,6 +235,15 @@ EXT1_WAKEUP_MODES = {
}
WakeupCauseToRunDuration = deep_sleep_ns.struct("WakeupCauseToRunDuration")
WakeupCause = deep_sleep_ns.enum("WakeupCause")
WakeTrigger = deep_sleep_ns.class_(
"WakeTrigger", automation.Trigger.template(WakeupCause), cg.Component
)
Ext1WakeTrigger = deep_sleep_ns.class_(
"Ext1WakeTrigger", automation.Trigger.template(), cg.Component
)
CONF_ON_WAKE = "on_wake"
CONF_WAKEUP_PIN_MODE = "wakeup_pin_mode"
CONF_ESP32_EXT1_WAKEUP = "esp32_ext1_wakeup"
CONF_TOUCH_WAKEUP = "touch_wakeup"
@@ -256,6 +266,22 @@ WAKEUP_PIN_SCHEMA = cv.Schema(
}
)
EXT1_WAKEUP_PIN_SCHEMA = cv.Schema(
{
cv.Required(CONF_PIN): cv.All(
pins.internal_gpio_input_pin_schema, validate_pin_number_esp32
),
cv.Optional(CONF_ON_WAKE): automation.validate_automation(
{cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Ext1WakeTrigger)}
),
}
)
# Entries that are not in the {pin: ..., on_wake: ...} form are treated as a
# bare pin config (the original syntax, e.g. a plain "GPIO5" or {number: 5}).
validate_ext1_wakeup_pin = cv.maybe_simple_value(EXT1_WAKEUP_PIN_SCHEMA, key=CONF_PIN)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
@@ -282,8 +308,7 @@ CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.Required(CONF_PINS): cv.ensure_list(
pins.internal_gpio_input_pin_schema,
validate_pin_number_esp32,
validate_ext1_wakeup_pin,
),
cv.Required(CONF_MODE): cv.All(
cv.enum(EXT1_WAKEUP_MODES, upper=True),
@@ -292,6 +317,12 @@ CONFIG_SCHEMA = cv.All(
}
),
),
cv.Optional(CONF_ON_WAKE): cv.All(
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX]),
automation.validate_automation(
{cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(WakeTrigger)}
),
),
cv.Optional(CONF_TOUCH_WAKEUP): cv.All(
cv.only_on_esp32,
esp32.only_on_variant(
@@ -323,7 +354,7 @@ async def to_code(config):
if CONF_WAKEUP_PIN in config:
pins_as_list = config.get(CONF_WAKEUP_PIN, [])
if CORE.is_bk72xx:
cg.add(var.init_wakeup_pins_(len(pins_as_list)))
cg.add(var.init_wakeup_pins(len(pins_as_list)))
for item in pins_as_list:
cg.add(
var.add_wakeup_pin(
@@ -362,16 +393,27 @@ async def to_code(config):
)
cg.add(var.set_run_duration(wakeup_cause_to_run_duration))
if CONF_ESP32_EXT1_WAKEUP in config:
conf = config[CONF_ESP32_EXT1_WAKEUP]
if (ext1_conf := config.get(CONF_ESP32_EXT1_WAKEUP)) is not None:
mask = 0
for pin in conf[CONF_PINS]:
mask |= 1 << pin[CONF_NUMBER]
for pin_conf in ext1_conf[CONF_PINS]:
number = pin_conf[CONF_PIN][CONF_NUMBER]
mask |= 1 << number
for wake_conf in pin_conf.get(CONF_ON_WAKE, []):
trigger = cg.new_Pvariable(wake_conf[CONF_TRIGGER_ID], number)
await cg.register_component(trigger, wake_conf)
await automation.build_automation(trigger, [], wake_conf)
cg.add_define("USE_DEEP_SLEEP_ON_WAKE")
struct = cg.StructInitializer(
Ext1Wakeup, ("mask", mask), ("wakeup_mode", conf[CONF_MODE])
Ext1Wakeup, ("mask", mask), ("wakeup_mode", ext1_conf[CONF_MODE])
)
cg.add(var.set_ext1_wakeup(struct))
for wake_conf in config.get(CONF_ON_WAKE, []):
trigger = cg.new_Pvariable(wake_conf[CONF_TRIGGER_ID])
await cg.register_component(trigger, wake_conf)
await automation.build_automation(trigger, [(WakeupCause, "cause")], wake_conf)
cg.add_define("USE_DEEP_SLEEP_ON_WAKE")
if CONF_TOUCH_WAKEUP in config:
cg.add(var.set_touch_wakeup(config[CONF_TOUCH_WAKEUP]))
if CORE.using_zephyr and "zigbee" not in CORE.loaded_integrations:
@@ -7,6 +7,21 @@ namespace esphome::deep_sleep {
static const char *const TAG = "deep_sleep.bk72xx";
#ifdef USE_DEEP_SLEEP_ON_WAKE
WakeupCause get_wakeup_cause() {
switch (lt_get_reboot_reason()) {
case REBOOT_REASON_SLEEP_GPIO:
return WAKEUP_CAUSE_GPIO;
case REBOOT_REASON_SLEEP_RTC:
return WAKEUP_CAUSE_TIMER;
case REBOOT_REASON_SLEEP_USB:
return WAKEUP_CAUSE_UNKNOWN;
default:
return WAKEUP_CAUSE_NONE;
}
}
#endif // USE_DEEP_SLEEP_ON_WAKE
optional<uint32_t> DeepSleepComponent::get_run_duration_() const { return this->run_duration_; }
void DeepSleepComponent::dump_config_platform_() {
@@ -15,15 +30,15 @@ void DeepSleepComponent::dump_config_platform_() {
}
}
bool DeepSleepComponent::pin_prevents_sleep_(WakeUpPinItem &pinItem) const {
return (pinItem.wakeup_pin_mode == WAKEUP_PIN_MODE_KEEP_AWAKE && pinItem.wakeup_pin != nullptr &&
!this->sleep_duration_.has_value() && (pinItem.wakeup_level == get_real_pin_state_(*pinItem.wakeup_pin)));
bool DeepSleepComponent::pin_prevents_sleep_(WakeUpPinItem &pin_item) const {
return (pin_item.wakeup_pin_mode == WAKEUP_PIN_MODE_KEEP_AWAKE && pin_item.wakeup_pin != nullptr &&
!this->sleep_duration_.has_value() && (pin_item.wakeup_level == get_real_pin_state_(*pin_item.wakeup_pin)));
}
bool DeepSleepComponent::prepare_to_sleep_() {
if (wakeup_pins_.size() > 0) {
if (!this->wakeup_pins_.empty()) {
for (WakeUpPinItem &item : this->wakeup_pins_) {
if (pin_prevents_sleep_(item)) {
if (this->pin_prevents_sleep_(item)) {
// Defer deep sleep until inactive
if (!this->next_enter_deep_sleep_) {
this->status_set_warning();
@@ -44,7 +59,7 @@ void DeepSleepComponent::deep_sleep_() {
item.wakeup_level = !item.wakeup_level;
}
}
ESP_LOGI(TAG, "Wake-up on P%u %s (%d)", item.wakeup_pin->get_pin(), item.wakeup_level ? "HIGH" : "LOW",
ESP_LOGI(TAG, "Wake-up on P%u %s (%" PRId32 ")", item.wakeup_pin->get_pin(), item.wakeup_level ? "HIGH" : "LOW",
static_cast<int32_t>(item.wakeup_pin_mode));
}
@@ -60,6 +60,65 @@ struct WakeupCauseToRunDuration {
#endif // USE_ESP32
#ifdef USE_DEEP_SLEEP_ON_WAKE
/// Why the device woke from deep sleep. Passed to on_wake automations.
enum WakeupCause : uint8_t {
/// The device did not wake from deep sleep (for example a cold boot, reset or OTA restart).
WAKEUP_CAUSE_NONE = 0,
/// The device woke from deep sleep, but the source could not be identified.
WAKEUP_CAUSE_UNKNOWN,
/// The device was woken by the sleep timer.
WAKEUP_CAUSE_TIMER,
/// The device was woken by a GPIO pin (wakeup_pin or esp32_ext1_wakeup).
WAKEUP_CAUSE_GPIO,
/// The device was woken by a touch pad.
WAKEUP_CAUSE_TOUCH,
};
/// Return why the device woke from deep sleep. Implemented per platform.
WakeupCause get_wakeup_cause();
/** Setup priority of on_wake triggers.
*
* Between restoring global variables (setup_priority::HARDWARE, 800) and on_boot automations at
* their default priority (600), so on_wake automations can update state (e.g. globals) that
* on_boot automations then use.
*/
inline constexpr float ON_WAKE_TRIGGER_SETUP_PRIORITY = 700.0f;
/// Fires once on boot when the device woke from deep sleep, with the wakeup cause.
class WakeTrigger : public Trigger<WakeupCause>, public Component {
public:
void setup() override {
const WakeupCause cause = get_wakeup_cause();
if (cause != WAKEUP_CAUSE_NONE) {
this->trigger(cause);
}
}
float get_setup_priority() const override { return ON_WAKE_TRIGGER_SETUP_PRIORITY; }
};
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3)
/// Fires once on boot when the device was woken from deep sleep by the given ext1 pin.
class Ext1WakeTrigger : public Trigger<>, public Component {
public:
explicit Ext1WakeTrigger(uint8_t pin) : pin_(pin) {}
void setup() override {
if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_EXT1 &&
(esp_sleep_get_ext1_wakeup_status() & (1ULL << this->pin_))) {
this->trigger();
}
}
float get_setup_priority() const override { return ON_WAKE_TRIGGER_SETUP_PRIORITY; }
protected:
uint8_t pin_;
};
#endif
#endif // USE_DEEP_SLEEP_ON_WAKE
template<typename... Ts> class EnterDeepSleepAction;
template<typename... Ts> class PreventDeepSleepAction;
@@ -84,7 +143,7 @@ class DeepSleepComponent final : public Component {
#endif // USE_ESP32
#if defined(USE_BK72XX)
void init_wakeup_pins_(size_t capacity) { this->wakeup_pins_.init(capacity); }
void init_wakeup_pins(size_t capacity) { this->wakeup_pins_.init(capacity); }
void add_wakeup_pin(InternalGPIOPin *wakeup_pin, WakeupPinMode wakeup_pin_mode) {
this->wakeup_pins_.emplace_back(WakeUpPinItem{wakeup_pin, wakeup_pin_mode, !wakeup_pin->is_inverted()});
}
@@ -132,7 +191,7 @@ class DeepSleepComponent final : public Component {
bool should_teardown_();
#ifdef USE_BK72XX
bool pin_prevents_sleep_(WakeUpPinItem &pinItem) const;
bool pin_prevents_sleep_(WakeUpPinItem &pin_item) const;
bool get_real_pin_state_(InternalGPIOPin &pin) const { return (pin.digital_read() ^ pin.is_inverted()); }
#endif // USE_BK72XX
@@ -30,6 +30,25 @@ namespace esphome::deep_sleep {
static const char *const TAG = "deep_sleep";
#ifdef USE_DEEP_SLEEP_ON_WAKE
WakeupCause get_wakeup_cause() {
switch (esp_sleep_get_wakeup_cause()) {
case ESP_SLEEP_WAKEUP_EXT0:
case ESP_SLEEP_WAKEUP_EXT1:
case ESP_SLEEP_WAKEUP_GPIO:
return WAKEUP_CAUSE_GPIO;
case ESP_SLEEP_WAKEUP_TIMER:
return WAKEUP_CAUSE_TIMER;
case ESP_SLEEP_WAKEUP_TOUCHPAD:
return WAKEUP_CAUSE_TOUCH;
case ESP_SLEEP_WAKEUP_UNDEFINED:
return WAKEUP_CAUSE_NONE;
default:
return WAKEUP_CAUSE_UNKNOWN;
}
}
#endif // USE_DEEP_SLEEP_ON_WAKE
optional<uint32_t> DeepSleepComponent::get_run_duration_() const {
if (this->wakeup_cause_to_run_duration_.has_value()) {
esp_sleep_wakeup_cause_t wakeup_cause = esp_sleep_get_wakeup_cause();
@@ -3,10 +3,27 @@
#include <Esp.h>
#ifdef USE_DEEP_SLEEP_ON_WAKE
extern "C" {
#include <user_interface.h>
}
#endif
namespace esphome::deep_sleep {
static const char *const TAG = "deep_sleep";
#ifdef USE_DEEP_SLEEP_ON_WAKE
WakeupCause get_wakeup_cause() {
// The ESP8266 can only wake from deep sleep through the RTC timer (via GPIO16 -> RST).
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
if (ESP.getResetInfoPtr()->reason == REASON_DEEP_SLEEP_AWAKE) {
return WAKEUP_CAUSE_TIMER;
}
return WAKEUP_CAUSE_NONE;
}
#endif // USE_DEEP_SLEEP_ON_WAKE
optional<uint32_t> DeepSleepComponent::get_run_duration_() const { return this->run_duration_; }
void DeepSleepComponent::dump_config_platform_() {}
+112
View File
@@ -0,0 +1,112 @@
from esphome import pins
import esphome.codegen as cg
from esphome.components import i2c
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_SLEEP_PIN, CONF_TYPE
CODEOWNERS = ["@tomwellnitz"]
MULTI_CONF = True
DEPENDENCIES = ["i2c"]
CONF_DS248X_ID = "ds248x_id"
CONF_BUS_SLEEP = "bus_sleep"
CONF_HUB_SLEEP = "hub_sleep"
CONF_ACTIVE_PULLUP = "active_pullup"
CONF_RESET_LOW_TIME = "reset_low_time"
CONF_MASTER_SAMPLE_TIME = "master_sample_time"
CONF_WRITE_0_LOW_TIME = "write_0_low_time"
CONF_RECOVERY_TIME = "recovery_time"
CONF_ACTIVE_PULLUP_RESISTANCE = "active_pullup_resistance"
TYPE_DS2482_100 = "ds2482-100"
TYPE_DS2482_101 = "ds2482-101"
TYPE_DS2482_800 = "ds2482-800"
TYPE_DS2484 = "ds2484"
CHANNEL_COUNTS = {
TYPE_DS2482_100: 1,
TYPE_DS2482_101: 1,
TYPE_DS2482_800: 8,
TYPE_DS2484: 1,
}
ds248x_ns = cg.esphome_ns.namespace("ds248x")
DS248xComponent = ds248x_ns.class_("DS248xComponent", cg.Component, i2c.I2CDevice)
def _component_schema(*extras):
schema = cv.Schema(
{
cv.GenerateID(): cv.declare_id(DS248xComponent),
cv.Optional(CONF_ACTIVE_PULLUP, default=False): cv.boolean,
}
)
for extra in extras:
schema = schema.extend(extra)
return schema.extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x18))
SLEEP_SCHEMA = {
cv.Optional(CONF_SLEEP_PIN): pins.internal_gpio_output_pin_schema,
cv.Optional(CONF_BUS_SLEEP, default=False): cv.boolean,
cv.Optional(CONF_HUB_SLEEP, default=False): cv.boolean,
}
DS2484_SCHEMA = {
cv.Optional(CONF_RESET_LOW_TIME): cv.int_range(min=0, max=15),
cv.Optional(CONF_MASTER_SAMPLE_TIME): cv.int_range(min=0, max=15),
cv.Optional(CONF_WRITE_0_LOW_TIME): cv.int_range(min=0, max=15),
cv.Optional(CONF_RECOVERY_TIME): cv.int_range(min=0, max=15),
cv.Optional(CONF_ACTIVE_PULLUP_RESISTANCE): cv.enum(
{
# DS2484 Table 7: value codes 0-5 map to 500 ohm, 6-15 map to 1000 ohm.
"500ohm": 0,
"1000ohm": 6,
}
),
}
CONFIG_SCHEMA = cv.typed_schema(
{
TYPE_DS2482_100: _component_schema(),
TYPE_DS2482_101: _component_schema(SLEEP_SCHEMA),
TYPE_DS2482_800: _component_schema(),
TYPE_DS2484: _component_schema(SLEEP_SCHEMA, DS2484_SCHEMA),
},
key=CONF_TYPE,
lower=True,
)
def get_channel_count(config):
return CHANNEL_COUNTS[config[CONF_TYPE]]
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
cg.add(var.set_active_pullup(config[CONF_ACTIVE_PULLUP]))
cg.add(var.set_channel_count(get_channel_count(config)))
if CONF_BUS_SLEEP in config:
cg.add(var.set_bus_sleep(config[CONF_BUS_SLEEP]))
if CONF_HUB_SLEEP in config:
cg.add(var.set_hub_sleep(config[CONF_HUB_SLEEP]))
if CONF_RESET_LOW_TIME in config:
cg.add(var.set_val_trstl(config[CONF_RESET_LOW_TIME]))
if CONF_MASTER_SAMPLE_TIME in config:
cg.add(var.set_val_tmsp(config[CONF_MASTER_SAMPLE_TIME]))
if CONF_WRITE_0_LOW_TIME in config:
cg.add(var.set_val_tw0l(config[CONF_WRITE_0_LOW_TIME]))
if CONF_RECOVERY_TIME in config:
cg.add(var.set_val_trec0(config[CONF_RECOVERY_TIME]))
if CONF_ACTIVE_PULLUP_RESISTANCE in config:
cg.add(var.set_val_rwpu(config[CONF_ACTIVE_PULLUP_RESISTANCE]))
if CONF_SLEEP_PIN in config:
pin = await cg.gpio_pin_expression(config[CONF_SLEEP_PIN])
cg.add(var.set_sleep_pin(pin))
+320
View File
@@ -0,0 +1,320 @@
#include "ds248x.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome::ds248x {
static const char *const TAG = "ds248x";
void DS248xComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up DS248x...");
// Wake up device if sleep pin is configured
if (this->sleep_pin_) {
this->sleep_pin_->setup();
this->sleep_pin_->pin_mode(esphome::gpio::FLAG_OUTPUT);
this->sleep_pin_->digital_write(true); // Wake up
delay(1); // DS2482-101 Datasheet: tOSCWUP = 100μs (using 10x margin)
}
// Probe device
ESP_LOGD(TAG, "Probing DS248x...");
uint8_t status = 0;
if (this->read(&status, 1) == i2c::ERROR_OK) {
ESP_LOGD(TAG, "Device responded! Status: 0x%02x", status);
} else {
ESP_LOGW(TAG, "Device did not respond. Trying reset anyway...");
}
if (!this->device_reset_()) {
ESP_LOGW(TAG, "DS248x reset failed during setup!");
}
// Configure device
if (!this->device_configure_()) {
ESP_LOGE(TAG, "DS248x configuration failed!");
this->mark_failed();
return;
}
// Reset to Channel 0
this->select_channel(0);
ESP_LOGI(TAG, "DS248x initialized successfully.");
}
void DS248xComponent::on_shutdown() {
if (this->sleep_pin_ && (this->hub_sleep_ || this->bus_sleep_)) {
this->sleep_pin_->digital_write(false); // Sleep
}
}
void DS248xComponent::dump_config() {
ESP_LOGCONFIG(TAG, "DS248x:");
LOG_I2C_DEVICE(this);
ESP_LOGCONFIG(TAG, " Channel Count: %d", this->channel_count_);
ESP_LOGCONFIG(TAG, " Active Pullup: %s", YESNO(this->active_pullup_));
if (this->ds2484_mode_) {
ESP_LOGCONFIG(TAG, " DS2484 Mode: enabled");
}
}
// --- Internal Helpers ---
// Datasheet command durations are sub-2ms; allow a little margin before forcing recovery.
static constexpr uint32_t BUSY_TIMEOUT_MS = 5;
bool DS248xComponent::set_read_pointer_(uint8_t ptr) { return this->write_byte(DS248X_COMMAND_SETREADPTR, ptr); }
bool DS248xComponent::wait_busy_() {
uint32_t start = millis();
do {
uint8_t status;
if (this->read(&status, 1) == i2c::ERROR_OK && !(status & DS248X_STATUS_BUSY))
return true;
delayMicroseconds(100);
} while (millis() - start < BUSY_TIMEOUT_MS);
ESP_LOGW(TAG, "DS248x busy timeout");
bool recovered = this->device_reset_() && this->device_configure_();
this->current_channel_ = -1;
if (!recovered) {
ESP_LOGE(TAG, "DS248x recovery failed after busy timeout");
this->mark_failed();
}
return false;
}
bool DS248xComponent::device_reset_() {
ESP_LOGD(TAG, "Resetting device...");
uint8_t cmd = DS248X_COMMAND_RESET;
if (this->write(&cmd, 1) != i2c::ERROR_OK)
return false;
uint8_t status;
if (this->read(&status, 1) != i2c::ERROR_OK)
return false;
if (!(status & DS248X_STATUS_RST)) {
ESP_LOGW(TAG, "Device reset failed (RST bit not set)");
return false;
}
this->current_channel_ = -1;
return true;
}
bool DS248xComponent::device_configure_() {
ESP_LOGD(TAG, "Configuring device...");
if (!this->write_config_()) {
ESP_LOGW(TAG, "Config write/verify failed");
return false;
}
ESP_LOGD(TAG, "Configured successfully");
// DS2484 Configuration
if (this->ds2484_mode_) {
if (this->ds2484_trstl_ != DS2484_PARAM_UNSET &&
!this->configure_ds2484_port_(DS2484_PORT_PARAM_TRSTL, this->ds2484_trstl_))
return false;
if (this->ds2484_tmsp_ != DS2484_PARAM_UNSET &&
!this->configure_ds2484_port_(DS2484_PORT_PARAM_TMSP, this->ds2484_tmsp_))
return false;
if (this->ds2484_tw0l_ != DS2484_PARAM_UNSET &&
!this->configure_ds2484_port_(DS2484_PORT_PARAM_TW0L, this->ds2484_tw0l_))
return false;
if (this->ds2484_trec0_ != DS2484_PARAM_UNSET &&
!this->configure_ds2484_port_(DS2484_PORT_PARAM_TREC0, this->ds2484_trec0_))
return false;
if (this->ds2484_rwpu_ != DS2484_PARAM_UNSET &&
!this->configure_ds2484_port_(DS2484_PORT_PARAM_RWPU, this->ds2484_rwpu_))
return false;
}
return true;
}
bool DS248xComponent::configure_ds2484_port_(uint8_t param, uint8_t val) {
uint8_t cmd = DS2484_COMMAND_ADJUSTPORT;
// Control Byte format (DS2484 Table 6): P[2:0] in bits 7:5, OD in bit 4, VAL[3:0] in bits 3:0
uint8_t data = ((param & 0x07) << 5) | (val & 0x0F);
// The DS2484 always acknowledges the Adjust 1-Wire Port control byte (datasheet "Adjust
// 1-Wire Port"), so a successful write confirms the update. We deliberately do not read
// back to verify: a single read of the Port Configuration register always returns the
// fixed 8-byte report starting at Byte 1 (tRSTL standard speed), not the parameter that
// was just written, so a per-parameter readback comparison would spuriously fail for
// tMSP/tW0L/tREC0/RWPU.
if (!this->write_byte(cmd, data)) {
ESP_LOGW(TAG, "DS2484 port config failed (param %d)", param);
return false;
}
return this->set_read_pointer_(DS248X_POINTER_STATUS);
}
bool DS248xComponent::write_config_() {
uint8_t config = 0;
if (this->active_pullup_)
config |= DS248X_CONFIG_ACTIVE_PULLUP;
// The DS248x only accepts the config byte if the upper nibble is the one's-complement of the lower nibble.
uint8_t config_byte = (config & 0x0F) | ((~config & 0x0F) << 4);
if (!this->write_byte(DS248X_COMMAND_WRITECONFIG, config_byte)) {
ESP_LOGW(TAG, "Failed to write config byte");
return false;
}
if (!this->set_read_pointer_(DS248X_POINTER_CONFIG)) {
return false;
}
uint8_t read_config;
if (this->read(&read_config, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Failed to read back config byte");
return false;
}
if ((read_config & 0x0F) != (config_byte & 0x0F)) {
ESP_LOGW(TAG, "Config mismatch! Wrote 0x%02x, Read 0x%02x", config_byte, read_config);
return false;
}
return this->set_read_pointer_(DS248X_POINTER_STATUS);
}
// --- Channel Selection ---
// Channel select codes: write code -> expected read code
static constexpr uint8_t CHANNEL_WRITE_CODES[8] = {0xF0, 0xE1, 0xD2, 0xC3, 0xB4, 0xA5, 0x96, 0x87};
static constexpr uint8_t CHANNEL_READ_CODES[8] = {0xB8, 0xB1, 0xAA, 0xA3, 0x9C, 0x95, 0x8E, 0x87};
bool DS248xComponent::select_channel(uint8_t channel) {
if (this->channel_count_ <= 1)
return true;
if (channel >= this->channel_count_)
return false;
if (this->current_channel_ == channel)
return true;
if (!this->write_byte(DS248X_COMMAND_CHANNELSELECT, CHANNEL_WRITE_CODES[channel])) {
this->current_channel_ = -1;
return false;
}
uint8_t read_code;
if (this->read(&read_code, 1) != i2c::ERROR_OK) {
this->current_channel_ = -1;
return false;
}
if (read_code != CHANNEL_READ_CODES[channel]) {
ESP_LOGW(TAG, "Channel select failed! Expected 0x%02x, got 0x%02x", CHANNEL_READ_CODES[channel], read_code);
this->current_channel_ = -1;
return false;
}
if (!this->set_read_pointer_(DS248X_POINTER_STATUS))
return false;
this->current_channel_ = channel;
return true;
}
// --- 1-Wire Bus Operations ---
bool DS248xComponent::ow_reset(bool &presence) {
if (!this->set_read_pointer_(DS248X_POINTER_STATUS))
return false;
uint8_t cmd = DS248X_COMMAND_RESETWIRE;
if (this->write(&cmd, 1) != i2c::ERROR_OK)
return false;
if (!this->wait_busy_()) {
ESP_LOGW(TAG, "ow_reset: wait busy failed");
return false;
}
uint8_t status;
if (this->read(&status, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "ow_reset: read status failed");
return false;
}
if (status & DS248X_STATUS_SD) {
ESP_LOGW(TAG, "Short detected on 1-Wire bus!");
return false;
}
presence = (status & DS248X_STATUS_PPD);
return true;
}
bool DS248xComponent::ow_write_byte(uint8_t byte) {
if (!this->set_read_pointer_(DS248X_POINTER_STATUS))
return false;
if (!this->wait_busy_()) {
ESP_LOGW(TAG, "Device busy before writing byte 0x%02x", byte);
return false;
}
uint8_t cmd[2] = {DS248X_COMMAND_WRITEBYTE, byte};
if (this->write(cmd, 2) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "I2C write failed for byte 0x%02x", byte);
return false;
}
if (!this->wait_busy_()) {
ESP_LOGW(TAG, "Timeout waiting for write byte to complete!");
return false;
}
return true;
}
bool DS248xComponent::ow_read_byte(uint8_t &byte) {
if (!this->set_read_pointer_(DS248X_POINTER_STATUS))
return false;
uint8_t cmd = DS248X_COMMAND_READBYTE;
if (this->write(&cmd, 1) != i2c::ERROR_OK)
return false;
if (!this->wait_busy_())
return false;
if (!this->set_read_pointer_(DS248X_POINTER_DATA))
return false;
if (this->read(&byte, 1) != i2c::ERROR_OK)
return false;
return true;
}
bool DS248xComponent::search_triplet(bool search_direction, uint8_t &status) {
if (!this->set_read_pointer_(DS248X_POINTER_STATUS))
return false;
// DS248x Datasheet: 1-Wire Triplet command requires 2 bytes:
// Byte 1: Command code 0x78
// Byte 2: Direction byte (bit 7 = V, search direction if discrepancy)
uint8_t buffer[2] = {DS248X_COMMAND_TRIPLET, static_cast<uint8_t>(search_direction ? 0x80 : 0x00)};
if (this->write(buffer, 2) != i2c::ERROR_OK)
return false;
if (!this->wait_busy_())
return false;
if (this->read(&status, 1) != i2c::ERROR_OK)
return false;
return true;
}
} // namespace esphome::ds248x
+133
View File
@@ -0,0 +1,133 @@
#pragma once
// DS248x I2C-to-1-Wire Bridge Family
// Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ds2482-100.pdf
// Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ds2482-800.pdf
// Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ds2484.pdf
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome::ds248x {
// DS248x I2C Commands
static constexpr uint8_t DS248X_COMMAND_RESET = 0xF0;
static constexpr uint8_t DS248X_COMMAND_SETREADPTR = 0xE1;
static constexpr uint8_t DS248X_COMMAND_WRITECONFIG = 0xD2;
static constexpr uint8_t DS248X_COMMAND_CHANNELSELECT = 0xC3;
static constexpr uint8_t DS248X_COMMAND_RESETWIRE = 0xB4;
static constexpr uint8_t DS248X_COMMAND_WRITEBYTE = 0xA5;
static constexpr uint8_t DS248X_COMMAND_READBYTE = 0x96;
static constexpr uint8_t DS248X_COMMAND_TRIPLET = 0x78;
static constexpr uint8_t DS2484_COMMAND_ADJUSTPORT = 0xC3;
// DS2484 "Adjust 1-Wire Port" parameter codes (datasheet Table 6, control byte P[2:0])
static constexpr uint8_t DS2484_PORT_PARAM_TRSTL = 0x0;
static constexpr uint8_t DS2484_PORT_PARAM_TMSP = 0x1;
static constexpr uint8_t DS2484_PORT_PARAM_TW0L = 0x2;
static constexpr uint8_t DS2484_PORT_PARAM_TREC0 = 0x3;
static constexpr uint8_t DS2484_PORT_PARAM_RWPU = 0x4;
// DS248x Status Register Bits
static constexpr uint8_t DS248X_STATUS_BUSY = 0x01;
static constexpr uint8_t DS248X_STATUS_PPD = 0x02;
static constexpr uint8_t DS248X_STATUS_SD = 0x04;
static constexpr uint8_t DS248X_STATUS_RST = 0x10;
static constexpr uint8_t DS248X_STATUS_SBR = 0x20;
static constexpr uint8_t DS248X_STATUS_TSB = 0x40;
static constexpr uint8_t DS248X_STATUS_DIR = 0x80;
// DS248x Register Pointers
static constexpr uint8_t DS248X_POINTER_STATUS = 0xF0;
static constexpr uint8_t DS248X_POINTER_DATA = 0xE1;
static constexpr uint8_t DS248X_POINTER_CONFIG = 0xC3;
// DS248x Configuration Bits
static constexpr uint8_t DS248X_CONFIG_ACTIVE_PULLUP = 0x01;
/**
* @brief DS248x I2C-to-1-Wire Bridge Component.
*
* This component manages the DS248x chip (DS2482-100, DS2482-800, DS2484).
* It provides low-level 1-Wire bus operations via I2C.
*
* Usage: Configure DS248xOneWireBus instances for each channel.
* These buses implement the one_wire::OneWireBus interface for compatibility
* with all existing 1-Wire device components (dallas_temp, etc.).
*/
class DS248xComponent : public Component, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
void on_shutdown() override;
float get_setup_priority() const override { return setup_priority::BUS; }
void set_sleep_pin(InternalGPIOPin *pin) { this->sleep_pin_ = pin; }
void set_bus_sleep(bool enabled) { this->bus_sleep_ = enabled; }
void set_hub_sleep(bool enabled) { this->hub_sleep_ = enabled; }
void set_channel_count(uint8_t count) { this->channel_count_ = count; }
void set_active_pullup(bool enabled) { this->active_pullup_ = enabled; }
// DS2484 Timing Parameters
void set_val_trstl(uint8_t val) {
this->ds2484_trstl_ = val;
this->ds2484_mode_ = true;
}
void set_val_tmsp(uint8_t val) {
this->ds2484_tmsp_ = val;
this->ds2484_mode_ = true;
}
void set_val_tw0l(uint8_t val) {
this->ds2484_tw0l_ = val;
this->ds2484_mode_ = true;
}
void set_val_trec0(uint8_t val) {
this->ds2484_trec0_ = val;
this->ds2484_mode_ = true;
}
void set_val_rwpu(uint8_t val) {
this->ds2484_rwpu_ = val;
this->ds2484_mode_ = true;
}
/// Get the channel count (1 for DS2482-100/DS2484, 8 for DS2482-800)
uint8_t get_channel_count() const { return this->channel_count_; }
// --- Core 1-Wire API (used by DS248xOneWireBus) ---
bool select_channel(uint8_t channel);
bool ow_reset(bool &presence);
bool ow_write_byte(uint8_t byte);
bool ow_read_byte(uint8_t &byte);
// --- Search support (used by DS248xOneWireBus) ---
bool search_triplet(bool search_direction, uint8_t &status);
protected:
InternalGPIOPin *sleep_pin_{nullptr};
uint8_t channel_count_ = 1;
bool bus_sleep_{false};
bool hub_sleep_{false};
bool active_pullup_ = false;
// DS2484 Config
bool ds2484_mode_ = false;
static constexpr uint8_t DS2484_PARAM_UNSET = 0xFF;
uint8_t ds2484_trstl_{DS2484_PARAM_UNSET};
uint8_t ds2484_tmsp_{DS2484_PARAM_UNSET};
uint8_t ds2484_tw0l_{DS2484_PARAM_UNSET};
uint8_t ds2484_trec0_{DS2484_PARAM_UNSET};
uint8_t ds2484_rwpu_{DS2484_PARAM_UNSET};
int8_t current_channel_{-1};
// Internal helpers
bool set_read_pointer_(uint8_t ptr);
bool wait_busy_();
bool device_reset_();
bool device_configure_();
bool configure_ds2484_port_(uint8_t param, uint8_t val);
bool write_config_();
};
} // namespace esphome::ds248x
@@ -0,0 +1,171 @@
#include "ds248x_one_wire_bus.h"
#include "ds248x.h"
#include "esphome/core/log.h"
namespace esphome::ds248x {
static const char *const TAG = "ds248x.one_wire";
void DS248xOneWireBus::setup() {
ESP_LOGCONFIG(TAG, "Setting up DS248x 1-Wire Bus (Channel %d)...", this->channel_);
// Parent setup happens in DS248xComponent::setup()
// We just need to scan for devices on this channel
if (!this->ensure_channel_()) {
ESP_LOGE(TAG, "Failed to select channel %d during setup", this->channel_);
this->mark_failed();
return;
}
// Perform device search on this channel
this->search();
ESP_LOGCONFIG(TAG, "Found %zu devices on channel %d", this->devices_.size(), this->channel_);
}
void DS248xOneWireBus::dump_config() {
ESP_LOGCONFIG(TAG, "DS248x 1-Wire Bus (Channel %d):", this->channel_);
this->dump_devices_(TAG);
}
bool DS248xOneWireBus::ensure_channel_() {
if (this->parent_ == nullptr) {
ESP_LOGE(TAG, "Parent not set!");
return false;
}
return this->parent_->select_channel(this->channel_);
}
int DS248xOneWireBus::reset_int() {
if (!this->ensure_channel_()) {
return -1;
}
bool presence = false;
if (!this->parent_->ow_reset(presence)) {
return -1;
}
return presence ? 1 : 0;
}
void DS248xOneWireBus::write8(uint8_t val) {
if (!this->ensure_channel_()) {
return;
}
if (!this->parent_->ow_write_byte(val)) {
ESP_LOGE(TAG, "Failed to write byte 0x%02X on channel %d", val, this->channel_);
}
}
void DS248xOneWireBus::write64(uint64_t val) {
if (!this->ensure_channel_()) {
return;
}
for (uint8_t i = 0; i < 8; i++) {
uint8_t byte = static_cast<uint8_t>(val >> (i * 8));
if (!this->parent_->ow_write_byte(byte)) {
ESP_LOGE(TAG, "Failed to write byte %d/8 (0x%02X) on channel %d - aborting write64", i + 1, byte, this->channel_);
return; // Stop writing to prevent sending corrupted data
}
}
}
uint8_t DS248xOneWireBus::read8() {
if (!this->ensure_channel_()) {
return 0;
}
uint8_t value = 0;
if (!this->parent_->ow_read_byte(value)) {
ESP_LOGE(TAG, "Failed to read byte on channel %d", this->channel_);
}
return value;
}
uint64_t DS248xOneWireBus::read64() {
if (!this->ensure_channel_()) {
return 0;
}
uint64_t value = 0;
for (uint8_t i = 0; i < 8; i++) {
uint8_t byte = 0;
if (!this->parent_->ow_read_byte(byte)) {
ESP_LOGE(TAG, "Failed to read byte %d/8 on channel %d - returning partial data", i + 1, this->channel_);
return value; // Return partial data to avoid blocking, caller should validate
}
value |= (static_cast<uint64_t>(byte) << (i * 8));
}
return value;
}
void DS248xOneWireBus::reset_search() {
this->search_last_discrepancy_ = 0;
this->search_last_device_flag_ = false;
this->search_address_ = 0;
}
uint64_t DS248xOneWireBus::search_int() {
if (!this->ensure_channel_()) {
return 0;
}
if (this->search_last_device_flag_) {
return 0;
}
uint8_t last_zero = 0;
uint64_t address = this->search_address_;
// Iterate through all 64 bits
for (uint8_t bit_number = 1; bit_number <= 64; bit_number++) {
uint64_t bit_mask = 1ULL << (bit_number - 1);
// Determine search direction
bool search_direction;
if (bit_number < this->search_last_discrepancy_) {
search_direction = (address & bit_mask) != 0;
} else {
search_direction = (bit_number == this->search_last_discrepancy_);
}
// Perform triplet operation
uint8_t status = 0;
if (!this->parent_->search_triplet(search_direction, status)) {
ESP_LOGW(TAG, "1-Wire triplet failed at bit %d on channel %d - aborting search", bit_number, this->channel_);
this->reset_search();
return 0;
}
bool id_bit = (status & DS248X_STATUS_SBR) != 0;
bool cmp_id_bit = (status & DS248X_STATUS_TSB) != 0;
bool dir_taken = (status & DS248X_STATUS_DIR) != 0;
if (id_bit && cmp_id_bit) {
// No devices participating
this->reset_search();
return 0;
}
if (!id_bit && !cmp_id_bit && !dir_taken) {
// Discrepancy, went 0 - record position
last_zero = bit_number;
}
// Update address based on direction taken
if (dir_taken) {
address |= bit_mask;
} else {
address &= ~bit_mask;
}
}
// Search successful
this->search_last_discrepancy_ = last_zero;
if (last_zero == 0) {
this->search_last_device_flag_ = true;
}
this->search_address_ = address;
return address;
}
} // namespace esphome::ds248x
@@ -0,0 +1,57 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/one_wire/one_wire_bus.h"
namespace esphome::ds248x {
class DS248xComponent;
/**
* @brief OneWireBus implementation for DS248x I2C-to-1-Wire bridges.
*
* This class wraps the DS248xComponent to provide the one_wire::OneWireBus interface,
* enabling compatibility with all existing 1-Wire device components (dallas_temp, etc.).
*
* For DS2482-800, multiple instances of this class can be created (one per channel).
* For DS2482-100/DS2484, a single instance is used.
*/
class DS248xOneWireBus : public one_wire::OneWireBus, public Component {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::BUS - 1.0f; }
/// Set the parent DS248x component
void set_parent(DS248xComponent *parent) { this->parent_ = parent; }
/// Set the 1-Wire channel (0-7, only relevant for DS2482-800)
void set_channel(uint8_t channel) { this->channel_ = channel; }
/// Get the channel number
uint8_t get_channel() const { return this->channel_; }
// OneWireBus interface implementation
int reset_int() override;
void write8(uint8_t val) override;
void write64(uint64_t val) override;
uint8_t read8() override;
uint64_t read64() override;
protected:
void reset_search() override;
uint64_t search_int() override;
/// Select the channel on the DS248x before any 1-Wire operation
bool ensure_channel_();
DS248xComponent *parent_{nullptr};
uint8_t channel_{0};
// Search state
uint64_t search_address_{0};
uint8_t search_last_discrepancy_{0};
bool search_last_device_flag_{false};
};
} // namespace esphome::ds248x
+56
View File
@@ -0,0 +1,56 @@
"""DS248x 1-Wire Bus Platform.
This platform creates one_wire bus instances backed by a DS248x I2C-to-1-Wire bridge.
It supports DS2482-100/101 (single channel), DS2482-800 (8 channels), and DS2484 (single channel).
For multi-channel devices (DS2482-800), create one platform entry per channel.
Each entry becomes a separate one_wire bus that can be used by dallas_temp and other 1-Wire devices.
"""
from esphome import final_validate as fv
import esphome.codegen as cg
from esphome.components.one_wire import OneWireBus
import esphome.config_validation as cv
from esphome.const import CONF_CHANNEL, CONF_ID
from . import CONF_DS248X_ID, DS248xComponent, ds248x_ns, get_channel_count
CODEOWNERS = ["@tomwellnitz"]
DEPENDENCIES = ["ds248x"]
DS248xOneWireBus = ds248x_ns.class_("DS248xOneWireBus", OneWireBus, cg.Component)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(DS248xOneWireBus),
cv.GenerateID(CONF_DS248X_ID): cv.use_id(DS248xComponent),
cv.Optional(CONF_CHANNEL, default=0): cv.int_range(min=0, max=7),
}
).extend(cv.COMPONENT_SCHEMA)
def _final_validate(config):
"""Validate that the channel is within the parent's channel count."""
fconf = fv.full_config.get()
path = fconf.get_path_for_id(config[CONF_DS248X_ID])[:-1]
parent_config = fconf.get_config_for_path(path)
channel_count = get_channel_count(parent_config)
channel = config[CONF_CHANNEL]
if channel >= channel_count:
raise cv.Invalid(
f"Channel {channel} is invalid for DS248x with {channel_count} channel(s). "
f"Valid range: 0-{channel_count - 1}"
)
FINAL_VALIDATE_SCHEMA = _final_validate
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
parent = await cg.get_variable(config[CONF_DS248X_ID])
cg.add(var.set_parent(parent))
cg.add(var.set_channel(config[CONF_CHANNEL]))
+3 -3
View File
@@ -145,9 +145,9 @@ float Emc2101Component::get_external_temperature() {
return NAN;
}
// join msb and lsb (5 least significant bits are not used)
uint16_t raw = (msb << 8 | lsb) >> 5;
return raw * 0.125;
// join msb and lsb (5 least significant bits are not used); msb is signed, so read as int16_t
int16_t raw = static_cast<int16_t>((msb << 8) | lsb) >> 5;
return raw * 0.125f;
}
float Emc2101Component::get_speed() {
@@ -14,10 +14,9 @@ void EPaperMono::refresh_screen(bool partial) {
}
void EPaperMono::deep_sleep() {
ESP_LOGV(TAG, "Deep sleep");
if (this->is_using_partial_update_()) {
this->cmd_data(0x10, {0x00}); // sleep in power on mode
} else {
// Deep sleep loses RAM so cannot be used with partial update
if (!this->is_using_partial_update_()) {
ESP_LOGV(TAG, "Deep sleep");
this->cmd_data(0x10, {0x03}); // deep sleep
}
}
+91 -24
View File
@@ -1160,6 +1160,74 @@ def _ota_downgrade_protection_errors(
return errs
_SIGNED_OTA_VERIFICATION_SCHEMA = cv.Schema(
{
cv.Optional(CONF_SIGNING_KEY): cv.file_,
cv.Optional(CONF_VERIFICATION_KEY): cv.file_,
cv.Optional(CONF_SIGNING_SCHEME, default="rsa3072"): cv.one_of(
*SIGNING_SCHEMES, lower=True
),
}
)
@schema_extractor("schema")
def _validate_signed_ota_verification(value):
if value is SCHEMA_EXTRACT:
# Expose the inner schema so the language-schema dumper can walk the
# signing_key / verification_key / signing_scheme options.
return _SIGNED_OTA_VERIFICATION_SCHEMA
if value is None:
# A bare `signed_ota_verification:` block is valid: the default V2
# scheme needs no keys (verify externally-signed binaries).
value = {}
return _validate_signed_ota_keys(_SIGNED_OTA_VERIFICATION_SCHEMA(value))
def _validate_signed_ota_keys(config: ConfigType) -> ConfigType:
"""Validate the signing/verification key combination for the selected scheme.
A verification key is only used by the Secure Boot V1 scheme (ecdsa_v1):
the public key is compiled into the app so it can verify externally-signed
images. ESP-IDF's CONFIG_SECURE_BOOT_VERIFICATION_KEY only takes effect
when the V1 ECDSA scheme is selected and binaries are not signed during
the build (see SECURE_BOOT_VERIFICATION_KEY in the bootloader Kconfig).
The V2 schemes (rsa3072, ecdsa256) embed the public key in the signature
block appended to each image, so verifying externally-signed binaries
needs no key in the config at all -- omitting both keys selects that
external-signing mode.
"""
has_signing_key = CONF_SIGNING_KEY in config
has_verification_key = CONF_VERIFICATION_KEY in config
scheme = config[CONF_SIGNING_SCHEME]
if has_signing_key and has_verification_key:
raise cv.Invalid(
f"Provide at most one of '{CONF_SIGNING_KEY}' and "
f"'{CONF_VERIFICATION_KEY}', not both.",
path=[CONF_VERIFICATION_KEY],
)
if scheme == "ecdsa_v1":
if not has_signing_key and not has_verification_key:
raise cv.Invalid(
f"Signing scheme 'ecdsa_v1' requires either '{CONF_SIGNING_KEY}' "
f"(to sign binaries during the build) or '{CONF_VERIFICATION_KEY}' "
f"(to verify binaries signed externally).",
path=[CONF_SIGNING_KEY],
)
elif has_verification_key:
raise cv.Invalid(
f"'{CONF_VERIFICATION_KEY}' is only used with signing scheme "
f"'ecdsa_v1'. With '{scheme}' the public key is embedded in each "
f"image's signature block, so no key file is needed to verify "
f"externally-signed binaries: remove '{CONF_VERIFICATION_KEY}', and "
f"set '{CONF_SIGNING_KEY}' only if binaries should be signed during "
f"the build.",
path=[CONF_VERIFICATION_KEY],
)
return config
def final_validate(config):
# Imported locally to avoid circular import issues
from esphome.components.psram import DOMAIN as PSRAM_DOMAIN
@@ -1361,7 +1429,7 @@ def final_validate(config):
)
else:
_LOGGER.info(
"Signed OTA verification is configured with a public verification key. "
"Signed OTA verification is enabled without a signing key. "
"Binaries will NOT be signed automatically during build. "
"You must sign them externally before flashing."
)
@@ -1640,18 +1708,9 @@ FRAMEWORK_SCHEMA = cv.Schema(
cv.Optional(
CONF_ENABLE_OTA_DOWNGRADE_PROTECTION, default=False
): cv.boolean,
cv.Optional(CONF_SIGNED_OTA_VERIFICATION): cv.All(
cv.Schema(
{
cv.Optional(CONF_SIGNING_KEY): cv.file_,
cv.Optional(CONF_VERIFICATION_KEY): cv.file_,
cv.Optional(
CONF_SIGNING_SCHEME, default="rsa3072"
): cv.one_of(*SIGNING_SCHEMES, lower=True),
}
),
cv.has_exactly_one_key(CONF_SIGNING_KEY, CONF_VERIFICATION_KEY),
),
cv.Optional(
CONF_SIGNED_OTA_VERIFICATION
): _validate_signed_ota_verification,
cv.Optional(CONF_NVS_ENCRYPTION): cv.Schema(
{
# eFuse key block (0-5) that stores the HMAC key from
@@ -2498,12 +2557,16 @@ async def to_code(config):
signed_ota[CONF_SIGNING_KEY].resolve().as_posix(),
)
else:
# Public key mode — verification only, external signing required
# External signing mode — binaries must be signed after the build
add_idf_sdkconfig_option("CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES", False)
add_idf_sdkconfig_option(
"CONFIG_SECURE_BOOT_VERIFICATION_KEY",
signed_ota[CONF_VERIFICATION_KEY].resolve().as_posix(),
)
if CONF_VERIFICATION_KEY in signed_ota:
# V1 ECDSA only: the public key is compiled into the app to
# verify externally-signed images. V2 schemes carry the public
# key in each image's signature block and need no key here.
add_idf_sdkconfig_option(
"CONFIG_SECURE_BOOT_VERIFICATION_KEY",
signed_ota[CONF_VERIFICATION_KEY].resolve().as_posix(),
)
cg.add_define("USE_OTA_SIGNED_VERIFICATION")
@@ -2995,19 +3058,23 @@ def copy_files():
def _decode_pc(config, addr):
# _decode_pc runs from the api log processor's asyncio callback, which
# only catches EsphomeError. Any other exception escaping here tears down
# the protocol and triggers an infinite reconnect/replay loop. Convert
# toolchain-resolution errors (e.g. missing build dir / cmake cache) into
# EsphomeError so the caller can disable decoding cleanly.
# Convert toolchain-resolution errors (e.g. missing build dir / cmake
# cache) into EsphomeError. The api log processor stops decoding on any
# exception, so this is about the message it reports rather than about
# catching it at all: EsphomeError carries an explanation worth showing
# the user, where a raw OSError repr does not.
if CORE.using_toolchain_esp_idf:
from esphome.espidf import toolchain as idf_toolchain
try:
addr2line_path = idf_toolchain.get_addr2line_path()
firmware_elf_path = idf_toolchain.get_elf_path()
except RuntimeError as err:
except (RuntimeError, OSError) as err:
# OSError covers a missing build directory or a cmake that isn't
# on PATH; both surface from the subprocess call, not as RuntimeError.
raise EsphomeError(f"ESP-IDF toolchain not available: {err}") from err
if not firmware_elf_path.is_file():
raise EsphomeError(f"Firmware ELF not found: {firmware_elf_path}")
else:
from esphome.platformio import toolchain
@@ -181,12 +181,6 @@ const char *ESPBTUUID::to_str(std::span<char, UUID_STR_LEN> output) const {
return output.data();
}
}
std::string ESPBTUUID::to_string() const {
char buf[UUID_STR_LEN];
this->to_str(buf);
return std::string(buf);
}
} // namespace esphome::esp32_ble
#endif // USE_ESP32_BLE_UUID
-3
View File
@@ -46,9 +46,6 @@ class ESPBTUUID {
esp_bt_uuid_t get_uuid() const;
// Remove before 2026.8.0
ESPDEPRECATED("Use to_str() instead. Removed in 2026.8.0", "2026.2.0")
std::string to_string() const; // NOLINT
const char *to_str(std::span<char, UUID_STR_LEN> output) const;
protected:
@@ -77,6 +77,10 @@ void BLEDescriptor::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_
case ESP_GATTS_WRITE_EVT: {
if (this->handle_ != param->write.handle)
break;
if (param->write.len > this->value_.attr_max_len) {
ESP_LOGE(TAG, "Size %d too large, must be no bigger than %d", param->write.len, this->value_.attr_max_len);
break;
}
this->value_.attr_len = param->write.len;
memcpy(this->value_.attr_value, param->write.value, param->write.len);
if (this->on_write_callback_) {
@@ -112,8 +112,10 @@ enum class EthernetComponentState : uint8_t {
// Platform-neutral duplex/speed types
#ifndef USE_ESP32
// NOLINTBEGIN(readability-identifier-naming)
enum eth_duplex_t { ETH_DUPLEX_HALF, ETH_DUPLEX_FULL };
enum eth_speed_t { ETH_SPEED_10M, ETH_SPEED_100M };
// NOLINTEND(readability-identifier-naming)
#endif
class EthernetComponent final : public Component {
@@ -187,17 +187,18 @@ void EthernetComponent::loop() {
}
void EthernetComponent::dump_config() {
const char *type_str = "Unknown";
#if defined(USE_ETHERNET_W5500)
type_str = "W5500";
const char *type_str = "W5500";
#elif defined(USE_ETHERNET_W5100)
type_str = "W5100";
const char *type_str = "W5100";
#elif defined(USE_ETHERNET_W6100)
type_str = "W6100";
const char *type_str = "W6100";
#elif defined(USE_ETHERNET_W6300)
type_str = "W6300";
const char *type_str = "W6300";
#elif defined(USE_ETHERNET_ENC28J60)
type_str = "ENC28J60";
const char *type_str = "ENC28J60";
#else
const char *type_str = "Unknown";
#endif
#if defined(USE_ETHERNET_W6300)
// W6300 uses PIO QSPI with hardcoded pins — SPI pin fields are not used
@@ -1,4 +1,4 @@
#ifdef USE_ARDUINO
#if defined(USE_ARDUINO) && !defined(USE_RP2) && !defined(USE_LIBRETINY)
#include "fastled_light.h"
#include "esphome/core/log.h"
@@ -1,6 +1,6 @@
#pragma once
#ifdef USE_ARDUINO
#if defined(USE_ARDUINO) && !defined(USE_RP2) && !defined(USE_LIBRETINY)
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
+2 -2
View File
@@ -132,7 +132,7 @@ void HaierClimateBase::save_settings() {
}
bool HaierClimateBase::get_display_state() const {
return (this->display_status_ == SwitchState::ON) || (this->display_status_ == SwitchState::PENDING_ON);
return (this->display_status_ == SwitchState::SWITCH_ON) || (this->display_status_ == SwitchState::PENDING_ON);
}
void HaierClimateBase::set_display_state(bool state) {
@@ -144,7 +144,7 @@ void HaierClimateBase::set_display_state(bool state) {
}
bool HaierClimateBase::get_health_mode() const {
return (this->health_mode_ == SwitchState::ON) || (this->health_mode_ == SwitchState::PENDING_ON);
return (this->health_mode_ == SwitchState::SWITCH_ON) || (this->health_mode_ == SwitchState::PENDING_ON);
}
void HaierClimateBase::set_health_mode(bool state) {
+4 -4
View File
@@ -147,8 +147,8 @@ class HaierClimateBase : public esphome::Component,
esphome::optional<haier_protocol::HaierMessage> message;
};
enum class SwitchState {
OFF = 0b00,
ON = 0b01,
SWITCH_OFF = 0b00,
SWITCH_ON = 0b01,
PENDING_OFF = 0b10,
PENDING_ON = 0b11,
};
@@ -157,8 +157,8 @@ class HaierClimateBase : public esphome::Component,
esphome::optional<PendingAction> action_request_;
uint8_t fan_mode_speed_;
uint8_t other_modes_fan_speed_;
SwitchState display_status_{SwitchState::ON};
SwitchState health_mode_{SwitchState::OFF};
SwitchState display_status_{SwitchState::SWITCH_ON};
SwitchState health_mode_{SwitchState::SWITCH_OFF};
bool force_send_control_;
bool forced_request_status_;
bool reset_protocol_request_;
+7 -7
View File
@@ -50,7 +50,7 @@ void HonClimate::set_quiet_mode_state(bool state) {
this->quiet_mode_state_ = state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF;
this->force_send_control_ = true;
} else {
this->quiet_mode_state_ = state ? SwitchState::ON : SwitchState::OFF;
this->quiet_mode_state_ = state ? SwitchState::SWITCH_ON : SwitchState::SWITCH_OFF;
}
this->settings_.quiet_mode_state = state;
#ifdef USE_SWITCH
@@ -63,7 +63,7 @@ void HonClimate::set_quiet_mode_state(bool state) {
}
bool HonClimate::get_quiet_mode_state() const {
return (this->quiet_mode_state_ == SwitchState::ON) || (this->quiet_mode_state_ == SwitchState::PENDING_ON);
return (this->quiet_mode_state_ == SwitchState::SWITCH_ON) || (this->quiet_mode_state_ == SwitchState::PENDING_ON);
}
esphome::optional<hon_protocol::VerticalSwingMode> HonClimate::get_vertical_airflow() const {
@@ -513,7 +513,7 @@ void HonClimate::initialization() {
}
this->current_vertical_swing_ = this->settings_.last_vertiacal_swing;
this->current_horizontal_swing_ = this->settings_.last_horizontal_swing;
this->quiet_mode_state_ = this->settings_.quiet_mode_state ? SwitchState::ON : SwitchState::OFF;
this->quiet_mode_state_ = this->settings_.quiet_mode_state ? SwitchState::SWITCH_ON : SwitchState::SWITCH_OFF;
}
haier_protocol::HaierMessage HonClimate::get_control_message() {
@@ -825,7 +825,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
#ifdef USE_SENSOR
this->update_sub_sensor_(SubSensorType::INDOOR_COIL_TEMPERATURE, bd_packet->indoor_coil_temperature / 2.0 - 20);
this->update_sub_sensor_(SubSensorType::OUTDOOR_COIL_TEMPERATURE, bd_packet->outdoor_coil_temperature - 64);
this->update_sub_sensor_(SubSensorType::OUTDOOR_DEFROST_TEMPERATURE, bd_packet->outdoor_coil_temperature - 64);
this->update_sub_sensor_(SubSensorType::OUTDOOR_DEFROST_TEMPERATURE, bd_packet->outdoor_defrost_temperature - 64);
this->update_sub_sensor_(SubSensorType::OUTDOOR_IN_AIR_TEMPERATURE, bd_packet->outdoor_in_air_temperature - 64);
this->update_sub_sensor_(SubSensorType::OUTDOOR_OUT_AIR_TEMPERATURE, bd_packet->outdoor_out_air_temperature - 64);
this->update_sub_sensor_(SubSensorType::POWER, encode_uint16(bd_packet->power[0], bd_packet->power[1]));
@@ -939,14 +939,14 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
// AC just turned on from remote need to turn off display
this->force_send_control_ = true;
} else if ((((uint8_t) this->display_status_) & 0b10) == 0) {
this->display_status_ = disp_status ? SwitchState::ON : SwitchState::OFF;
this->display_status_ = disp_status ? SwitchState::SWITCH_ON : SwitchState::SWITCH_OFF;
}
}
}
// Health mode
if ((((uint8_t) this->health_mode_) & 0b10) == 0) {
bool old_health_mode = this->get_health_mode();
this->health_mode_ = packet.control.health_mode == 1 ? SwitchState::ON : SwitchState::OFF;
this->health_mode_ = packet.control.health_mode == 1 ? SwitchState::SWITCH_ON : SwitchState::SWITCH_OFF;
should_publish = should_publish || (old_health_mode != this->get_health_mode());
}
{
@@ -1008,7 +1008,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
// In proper mode and not in pending state
bool new_quiet_mode = packet.control.quiet_mode != 0;
if (new_quiet_mode != this->get_quiet_mode_state()) {
this->quiet_mode_state_ = new_quiet_mode ? SwitchState::ON : SwitchState::OFF;
this->quiet_mode_state_ = new_quiet_mode ? SwitchState::SWITCH_ON : SwitchState::SWITCH_OFF;
this->settings_.quiet_mode_state = new_quiet_mode;
#ifdef USE_SWITCH
if (this->quiet_mode_switch_ != nullptr) {
+1 -1
View File
@@ -197,7 +197,7 @@ class HonClimate final : public HaierClimateBase {
esphome::optional<hon_protocol::HorizontalSwingMode> current_horizontal_swing_{};
HonSettings settings_{};
ESPPreferenceObject hon_rtc_;
SwitchState quiet_mode_state_{SwitchState::OFF};
SwitchState quiet_mode_state_{SwitchState::SWITCH_OFF};
};
} // namespace esphome::haier
@@ -464,14 +464,14 @@ haier_protocol::HandlerError Smartair2Climate::process_status_message_(const uin
// AC just turned on from remote need to turn off display
this->force_send_control_ = true;
} else if ((((uint8_t) this->health_mode_) & 0b10) == 0) {
this->display_status_ = disp_status ? SwitchState::ON : SwitchState::OFF;
this->display_status_ = disp_status ? SwitchState::SWITCH_ON : SwitchState::SWITCH_OFF;
}
}
}
// Health mode
if ((((uint8_t) this->health_mode_) & 0b10) == 0) {
bool old_health_mode = this->get_health_mode();
this->health_mode_ = packet.control.health_mode == 1 ? SwitchState::ON : SwitchState::OFF;
this->health_mode_ = packet.control.health_mode == 1 ? SwitchState::SWITCH_ON : SwitchState::SWITCH_OFF;
should_publish = should_publish || (old_health_mode != this->get_health_mode());
}
{
+1 -1
View File
@@ -242,7 +242,7 @@ void HlkFm22xComponent::handle_reply_(const uint8_t *data, size_t length) {
return;
}
if (data[1] != HlkFm22xResult::SUCCESS) {
if (data[1] != HlkFm22xResult::SUCCEEDED) {
ESP_LOGE(TAG, "Command <0x%.2X> failed. Error: 0x%.2X", data[0], data[1]);
switch (expected) {
case HlkFm22xCommand::ENROLL:
+1 -1
View File
@@ -41,7 +41,7 @@ enum HlkFm22xNoteType {
};
enum HlkFm22xResult {
SUCCESS = 0x00,
SUCCEEDED = 0x00,
REJECTED = 0x01,
ABORTED = 0x02,
FAILED4_CAMERA = 0x04,
+1 -1
View File
@@ -61,7 +61,7 @@ void HM3301Component::update() {
int16_t aqi_value = -1;
if (this->aqi_sensor_ != nullptr && pm_2_5_value != -1 && pm_10_0_value != -1) {
aqi::AbstractAQICalculator *calculator = this->aqi_calculator_factory_.get_calculator(this->aqi_calc_type_);
aqi_value = calculator->get_aqi(pm_2_5_value, pm_10_0_value);
aqi_value = calculator->get_aqi(pm_2_5_value, pm_10_0_value, /*extended_range=*/false);
}
if (pm_1_0_value != -1) {
@@ -1,6 +1,6 @@
#include "http_request_arduino.h"
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
#if defined(USE_ARDUINO) && !defined(USE_ESP32) && !defined(USE_LIBRETINY)
#include "esphome/components/network/util.h"
#include "esphome/components/watchdog/watchdog.h"
@@ -2,7 +2,7 @@
#include "http_request.h"
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
#if defined(USE_ARDUINO) && !defined(USE_ESP32) && !defined(USE_LIBRETINY)
#if defined(USE_RP2)
#include <HTTPClient.h>
@@ -19,11 +19,6 @@ namespace esphome::http_request {
static const char *const TAG = "http_request.idf";
static constexpr uint32_t ERROR_DURATION_MS = 1000;
struct UserData {
const std::vector<std::string> &lower_case_collect_headers;
std::vector<Header> &response_headers;
};
void HttpRequestIDF::dump_config() {
HttpRequestComponent::dump_config();
ESP_LOGCONFIG(TAG,
@@ -34,15 +29,15 @@ void HttpRequestIDF::dump_config() {
}
esp_err_t HttpRequestIDF::http_event_handler(esp_http_client_event_t *evt) {
UserData *user_data = (UserData *) evt->user_data;
auto *container = (HttpContainerIDF *) evt->user_data;
switch (evt->event_id) {
case HTTP_EVENT_ON_HEADER: {
const std::string header_name = str_lower_case(evt->header_key); // NOLINT
if (should_collect_header(user_data->lower_case_collect_headers, header_name)) {
if (should_collect_header(container->collect_headers_, header_name)) {
const std::string header_value = evt->header_value;
ESP_LOGD(TAG, "Received response header, name: %s, value: %s", header_name.c_str(), header_value.c_str());
user_data->response_headers.push_back({header_name, header_value});
container->response_headers_.push_back({header_name, header_value});
}
break;
}
@@ -124,8 +119,8 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
container->set_secure(secure);
auto user_data = UserData{lower_case_collect_headers, container->response_headers_};
esp_http_client_set_user_data(client, static_cast<void *>(&user_data));
container->collect_headers_ = lower_case_collect_headers;
esp_http_client_set_user_data(client, static_cast<void *>(container.get()));
for (const auto &header : request_headers) {
esp_http_client_set_header(client, header.name.c_str(), header.value.c_str());
@@ -24,6 +24,8 @@ class HttpContainerIDF : public HttpContainer {
protected:
friend class HttpRequestIDF;
esp_http_client_handle_t client_;
// Owned copy (not a reference): must outlive perform() for the response-header event handler
std::vector<std::string> collect_headers_;
};
class HttpRequestIDF final : public HttpRequestComponent {
+1 -1
View File
@@ -1,4 +1,4 @@
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
#if defined(USE_ARDUINO) && !defined(USE_ESP32) && !defined(USE_LIBRETINY)
#include "i2c_bus_arduino.h"
#include <Arduino.h>
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
#if defined(USE_ARDUINO) && !defined(USE_ESP32) && !defined(USE_LIBRETINY)
#include <Wire.h>
#include "esphome/core/component.h"
+1 -1
View File
@@ -42,4 +42,4 @@ async def setup_improv_core(var: MockObj, config: ConfigType, component: str):
cg.add(var.set_next_url(_process_next_url(next_url)))
cg.add_define(f"USE_{component.upper()}_NEXT_URL")
cg.add_library("improv/Improv", "1.2.4")
cg.add_library("improv/Improv", "1.2.6")
+15
View File
@@ -76,12 +76,27 @@ _BLE5_BK_SYS_CONFIG_OPTIONS = [
"CFG_SUPPORT_BLE=0",
]
# Board ids upstream LibreTiny renamed; configs written against the old id
# keep validating and building against the new one (with a warning).
# generic-ln882hki -> generic-ln882h: LibreTiny v1.13.0.
_RENAMED_BOARDS = {
"generic-ln882hki": "generic-ln882h",
}
def _detect_variant(value):
if KEY_LIBRETINY not in CORE.data:
raise cv.Invalid("Family component didn't populate core data properly!")
component: LibreTinyComponent = CORE.data[KEY_LIBRETINY][KEY_COMPONENT_DATA]
board = value[CONF_BOARD]
if board not in component.boards and (renamed := _RENAMED_BOARDS.get(board)):
_LOGGER.warning(
"Board '%s' was renamed to '%s'; please update your configuration",
board,
renamed,
)
value = value.copy()
value[CONF_BOARD] = board = renamed
# read board-default family if not specified
if board not in component.boards:
if CONF_FAMILY not in value:
+1 -1
View File
@@ -44,7 +44,7 @@ void arch_init() {
void arch_restart() {
lt_reboot();
while (1) {
while (true) {
}
}
+3
View File
@@ -44,6 +44,7 @@
// it is callable from Thumb code via interworking. The MRS CPSR instruction
// is ARM-only and user code here may be built in Thumb, so in_isr_context()
// defers to this port helper on BK72xx instead of reading CPSR inline.
// NOLINTNEXTLINE(readability-redundant-declaration)
extern "C" uint32_t platform_is_in_interrupt_context(void);
#endif
@@ -59,9 +60,11 @@ extern "C" void delayMicroseconds(unsigned int us);
// Forward decls from libretiny's <lt_api.h> family for the inline arch_*
// wrappers below. Pulling the full header would drag in the rest of the
// LibreTiny C API.
// NOLINTBEGIN(readability-redundant-declaration)
extern "C" void lt_wdt_feed(void);
extern "C" uint32_t lt_cpu_get_cycle_count(void);
extern "C" uint32_t lt_cpu_get_freq(void);
// NOLINTEND(readability-redundant-declaration)
namespace esphome::libretiny {}
@@ -13,14 +13,14 @@ void LTComponent::dump_config() {
"LibreTiny:\n"
" Version: %s\n"
" Loglevel: %u",
LT_BANNER_STR + 10, LT_LOGLEVEL);
&LT_BANNER_STR[10], LT_LOGLEVEL);
#if defined(__OPTIMIZE_SIZE__) && __OPTIMIZE_LEVEL__ > 0 && __OPTIMIZE_LEVEL__ <= 3
ESP_LOGCONFIG(TAG, " Optimization: -Os, SDK: -O" STRINGIFY_MACRO(__OPTIMIZE_LEVEL__));
#endif
#ifdef USE_TEXT_SENSOR
if (this->version_ != nullptr) {
this->version_->publish_state(LT_BANNER_STR + 10);
this->version_->publish_state(&LT_BANNER_STR[10]);
}
#endif // USE_TEXT_SENSOR
}
@@ -5,8 +5,10 @@
#include <cstdint>
// Forward declare FlashDB types to avoid pulling in flashdb.h
// NOLINTBEGIN(readability-identifier-naming)
struct fdb_kvdb;
struct fdb_blob;
// NOLINTEND(readability-identifier-naming)
namespace esphome::libretiny {
+34
View File
@@ -101,6 +101,7 @@ USB_SERIAL_JTAG = "USB_SERIAL_JTAG"
USB_CDC = "USB_CDC"
DEFAULT = "DEFAULT"
CONF_BOOT_LOG_BUFFER_SIZE = "boot_log_buffer_size"
CONF_INITIAL_LEVEL = "initial_level"
CONF_LOGGER_ID = "logger_id"
CONF_RUNTIME_TAG_LEVELS = "runtime_tag_levels"
@@ -240,6 +241,13 @@ CONFIG_SCHEMA = cv.All(
cv.validate_bytes, cv.int_range(min=160, max=65535)
),
cv.Optional(CONF_DEASSERT_RTS_DTR, default=False): cv.boolean,
cv.Optional(CONF_BOOT_LOG_BUFFER_SIZE, default=0): cv.All(
cv.validate_bytes,
cv.Any(
cv.int_(0), # Disabled
cv.int_range(min=512, max=16384),
),
),
cv.SplitDefault(
CONF_TASK_LOG_BUFFER_SIZE,
esp32=768, # Default: 768 bytes (~5-6 messages with 70-byte text plus thread names)
@@ -350,6 +358,11 @@ async def to_code(config: ConfigType) -> None:
if task_log_buffer_size > 0:
cg.add_define("USE_ESPHOME_TASK_LOG_BUFFER")
cg.add_define("ESPHOME_TASK_LOG_BUFFER_SIZE", task_log_buffer_size)
# Boot log buffer captures early WARN/ERROR lines for later replay
# (logger.replay_boot_logs action). Buffer is a direct member of Logger.
if (boot_log_buffer_size := config[CONF_BOOT_LOG_BUFFER_SIZE]) > 0:
cg.add_define("USE_LOGGER_BOOT_LOG_BUFFER")
cg.add_define("ESPHOME_LOGGER_BOOT_LOG_BUFFER_SIZE", boot_log_buffer_size)
log = cg.new_Pvariable(
config[CONF_ID],
baud_rate,
@@ -593,6 +606,27 @@ async def logger_set_level_to_code(config, action_id, template_arg, args):
)
@automation.register_action(
"logger.replay_boot_logs",
LambdaAction,
automation.maybe_conf(
CONF_LOGGER_ID,
{
cv.GenerateID(CONF_LOGGER_ID): cv.use_id(Logger),
},
),
synchronous=True,
)
async def logger_replay_boot_logs_to_code(config, action_id, template_arg, args):
logger = await cg.get_variable(config[CONF_LOGGER_ID])
text = str(cg.statement(logger.replay_boot_logs()))
lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void)
return automation.new_lambda_pvariable(
action_id, lambda_, StatelessLambdaAction, template_arg
)
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"logger_esp32.cpp": {
+59
View File
@@ -1,5 +1,6 @@
#include "logger.h"
#include <cinttypes>
#include <cstring>
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
@@ -202,6 +203,61 @@ void Logger::process_messages_() {
}
void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; }
#ifdef USE_LOGGER_BOOT_LOG_BUFFER
// Record layout in boot_log_buffer_: u16 text_len | u8 level | u32 millis | text
static constexpr uint16_t BOOT_LOG_RECORD_HEADER_SIZE = 7;
void Logger::boot_log_capture_(uint8_t level, const LogBuffer &buf) {
if (this->boot_log_done_ || level > ESPHOME_LOG_LEVEL_WARN || level == ESPHOME_LOG_LEVEL_NONE)
return;
// Strip the leading ANSI color prefix and trailing reset; the replay re-emits
// the text through the normal formatter, which colors it again.
const char *text = buf.data;
uint16_t len = buf.pos;
if (len > 0 && text[0] == '\033') {
const char *m = static_cast<const char *>(memchr(text, 'm', len));
if (m != nullptr) {
const uint16_t skip = static_cast<uint16_t>(m - text) + 1;
text += skip;
len -= skip;
}
}
if (len >= 4 && memcmp(text + len - 4, "\033[0m", 4) == 0)
len -= 4;
if (this->boot_log_pos_ + BOOT_LOG_RECORD_HEADER_SIZE + len > ESPHOME_LOGGER_BOOT_LOG_BUFFER_SIZE) {
this->boot_log_dropped_++;
return;
}
char *p = this->boot_log_buffer_ + this->boot_log_pos_;
const uint32_t now = millis();
memcpy(p, &len, sizeof(len));
p[2] = static_cast<char>(level);
memcpy(p + 3, &now, sizeof(now));
memcpy(p + BOOT_LOG_RECORD_HEADER_SIZE, text, len);
this->boot_log_pos_ += BOOT_LOG_RECORD_HEADER_SIZE + len;
}
void Logger::replay_boot_logs() {
// Stop capturing first so the replay output is not buffered again.
this->boot_log_done_ = true;
ESP_LOGI(TAG, "Replaying boot logs (%u bytes used, %u lines dropped)", this->boot_log_pos_, this->boot_log_dropped_);
uint16_t pos = 0;
while (pos + BOOT_LOG_RECORD_HEADER_SIZE <= this->boot_log_pos_) {
const char *p = this->boot_log_buffer_ + pos;
uint16_t len;
memcpy(&len, p, sizeof(len));
uint32_t stamp;
memcpy(&stamp, p + 3, sizeof(stamp));
esp_log_printf_(static_cast<uint8_t>(p[2]), TAG, __LINE__, ESPHOME_LOG_FORMAT("@%" PRIu32 "ms %.*s"), stamp,
static_cast<int>(len), p + BOOT_LOG_RECORD_HEADER_SIZE);
pos += BOOT_LOG_RECORD_HEADER_SIZE + len;
}
ESP_LOGI(TAG, "Boot log replay done");
}
#else
void Logger::replay_boot_logs() { ESP_LOGW(TAG, "Boot log buffer not configured (set 'boot_log_buffer_size')"); }
#endif // USE_LOGGER_BOOT_LOG_BUFFER
#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
void Logger::set_log_level(const char *tag, uint8_t log_level) { this->log_levels_[tag] = log_level; }
#endif
@@ -239,6 +295,9 @@ void Logger::dump_config() {
ESP_LOGCONFIG(TAG, " Task Log Buffer Size: %u bytes", static_cast<unsigned int>(this->log_buffer_.size()));
#endif
#endif
#ifdef USE_LOGGER_BOOT_LOG_BUFFER
ESP_LOGCONFIG(TAG, " Boot Log Buffer Size: %u bytes", ESPHOME_LOGGER_BOOT_LOG_BUFFER_SIZE);
#endif
#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
for (auto &it : this->log_levels_) {
+26
View File
@@ -174,6 +174,12 @@ class Logger final : public Component {
#endif
uint8_t get_log_level() { return this->current_level_; }
/// Re-emit WARN/ERROR messages captured during boot through the normal log
/// dispatch so connected listeners (API, MQTT, ...) can see them. Stops
/// further capture; safe to call multiple times. Warns when the logger was
/// built without 'boot_log_buffer_size'.
void replay_boot_logs();
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)
/// Set up this component.
@@ -291,6 +297,9 @@ class Logger final : public Component {
{
this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, buf, thread_name);
}
#ifdef USE_LOGGER_BOOT_LOG_BUFFER
this->boot_log_capture_(level, buf);
#endif
this->notify_listeners_(level, tag, buf);
this->write_log_buffer_to_console_(buf);
}
@@ -303,6 +312,9 @@ class Logger final : public Component {
LogBuffer &buf) {
buf.write_header(level, tag, line, thread_name);
buf.write_body(text, text_length);
#ifdef USE_LOGGER_BOOT_LOG_BUFFER
this->boot_log_capture_(level, buf);
#endif
this->notify_listeners_(level, tag, buf);
}
#endif
@@ -311,6 +323,11 @@ class Logger final : public Component {
const LogString *get_uart_selection_();
#endif
#ifdef USE_LOGGER_BOOT_LOG_BUFFER
/// Copy a formatted WARN/ERROR line (ANSI codes stripped) into the boot log buffer.
void boot_log_capture_(uint8_t level, const LogBuffer &buf);
#endif
// Group 4-byte aligned members first
uint32_t baud_rate_;
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
@@ -350,6 +367,11 @@ class Logger final : public Component {
std::vector<LoggerLevelListener *> level_listeners_; // Log level change listeners
#endif
// Group smaller types together at the end
#ifdef USE_LOGGER_BOOT_LOG_BUFFER
uint16_t boot_log_pos_{0}; // Bytes used in boot_log_buffer_
uint16_t boot_log_dropped_{0}; // Lines that did not fit
bool boot_log_done_{false}; // Set on first replay; stops further capture
#endif
uint8_t current_level_{ESPHOME_LOG_LEVEL_VERY_VERBOSE};
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2) || defined(USE_ZEPHYR)
UARTSelection uart_{UART_SELECTION_UART0};
@@ -368,6 +390,10 @@ class Logger final : public Component {
// Large buffers placed last to keep frequently-accessed member offsets small
char tx_buffer_[ESPHOME_LOGGER_TX_BUFFER_SIZE + 1]; // +1 for null terminator
#ifdef USE_LOGGER_BOOT_LOG_BUFFER
// Sequential records of early WARN/ERROR lines: u16 text_len | u8 level | u32 millis | text
char boot_log_buffer_[ESPHOME_LOGGER_BOOT_LOG_BUFFER_SIZE];
#endif
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
logger::TaskLogBuffer log_buffer_; // Embedded in Logger (no separate heap allocation)
#endif
@@ -20,7 +20,7 @@ TaskLogBuffer::~TaskLogBuffer() {
}
}
size_t TaskLogBuffer::available_contiguous_space() const {
size_t TaskLogBuffer::available_contiguous_space_() const {
if (this->head_ >= this->tail_) {
// head is ahead of or equal to tail
// Available space is from head to end, plus from start to tail
@@ -81,7 +81,7 @@ void TaskLogBuffer::release_message_main_loop() {
this->tail_ = 0;
}
this->message_count_--;
this->message_count_ = this->message_count_ - 1;
this->current_message_size_ = 0;
xSemaphoreGive(this->mutex_);
@@ -117,7 +117,7 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin
}
// Check if we have enough contiguous space
size_t contiguous = this->available_contiguous_space();
size_t contiguous = this->available_contiguous_space_();
if (contiguous < total_size) {
// Not enough contiguous space at end
@@ -128,9 +128,9 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin
}
// Need at least enough space to safely write padding marker (level field is at end of struct)
constexpr size_t PADDING_MARKER_MIN_SPACE = offsetof(LogMessage, level) + 1;
constexpr size_t padding_marker_min_space = offsetof(LogMessage, level) + 1;
if (space_at_start >= total_size && this->head_ > 0 && contiguous >= PADDING_MARKER_MIN_SPACE) {
if (space_at_start >= total_size && this->head_ > 0 && contiguous >= padding_marker_min_space) {
// Add padding marker (set level field to indicate this is padding, not a real message)
LogMessage *padding = reinterpret_cast<LogMessage *>(this->storage_ + this->head_);
padding->level = PADDING_MARKER_LEVEL;
@@ -180,7 +180,7 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin
this->head_ = 0;
}
this->message_count_++;
this->message_count_ = this->message_count_ + 1;
xSemaphoreGive(this->mutex_);
return true;
@@ -84,7 +84,7 @@ class TaskLogBuffer {
static inline size_t message_total_size(size_t text_length) { return sizeof(LogMessage) + text_length + 1; }
// Calculate available contiguous space at write position
size_t available_contiguous_space() const;
size_t available_contiguous_space_() const;
uint8_t storage_[ESPHOME_TASK_LOG_BUFFER_SIZE]; // Embedded in Logger (no separate heap allocation)
size_t head_{0}; // Write position
+5 -5
View File
@@ -15,7 +15,7 @@ from esphome.const import (
CONF_NAME,
CONF_REPEAT,
CONF_TYPE,
DEVICE_CLASS_DISTANCE,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_ILLUMINANCE,
ICON_BRIGHTNESS_5,
ICON_BRIGHTNESS_6,
@@ -159,7 +159,7 @@ CONFIG_SCHEMA = cv.All(
unit_of_measurement=UNIT_COUNTS,
icon=ICON_BRIGHTNESS_5,
accuracy_decimals=0,
device_class=DEVICE_CLASS_ILLUMINANCE,
device_class=DEVICE_CLASS_EMPTY,
state_class=STATE_CLASS_MEASUREMENT,
),
key=CONF_NAME,
@@ -169,7 +169,7 @@ CONFIG_SCHEMA = cv.All(
unit_of_measurement=UNIT_COUNTS,
icon=ICON_BRIGHTNESS_7,
accuracy_decimals=0,
device_class=DEVICE_CLASS_ILLUMINANCE,
device_class=DEVICE_CLASS_EMPTY,
state_class=STATE_CLASS_MEASUREMENT,
),
key=CONF_NAME,
@@ -179,7 +179,7 @@ CONFIG_SCHEMA = cv.All(
unit_of_measurement=UNIT_COUNTS,
icon=ICON_PROXIMITY,
accuracy_decimals=0,
device_class=DEVICE_CLASS_DISTANCE,
device_class=DEVICE_CLASS_EMPTY,
state_class=STATE_CLASS_MEASUREMENT,
),
key=CONF_NAME,
@@ -188,7 +188,7 @@ CONFIG_SCHEMA = cv.All(
sensor.sensor_schema(
icon=ICON_GAIN,
accuracy_decimals=0,
device_class=DEVICE_CLASS_ILLUMINANCE,
device_class=DEVICE_CLASS_EMPTY,
state_class=STATE_CLASS_MEASUREMENT,
),
key=CONF_NAME,
+4
View File
@@ -19,6 +19,10 @@ void MCP23017::setup() {
this->read_reg(mcp23x17_base::MCP23X17_OLATA, &this->olat_a_);
this->read_reg(mcp23x17_base::MCP23X17_OLATB, &this->olat_b_);
// Reset IPOL to 0x00: ESPHome handles 'inverted' in software.
this->write_reg(mcp23x17_base::MCP23X17_IPOLA, 0x00);
this->write_reg(mcp23x17_base::MCP23X17_IPOLB, 0x00);
uint8_t iocon_flags = 0;
if (this->open_drain_ints_) {
iocon_flags |= IOCON_ODR;
+2 -2
View File
@@ -27,8 +27,8 @@ static void register_libretiny(MDNSComponent *, StaticVector<MDNSService, MDNS_S
while (*service_type == '_') {
service_type++;
}
uint16_t port_ = service.port.value();
MDNS.addService(service_type, proto, port_);
uint16_t port = service.port.value();
MDNS.addService(service_type, proto, port);
for (const auto &record : service.txt_records) {
MDNS.addServiceTxt(service_type, proto, MDNS_STR_ARG(record.key), MDNS_STR_ARG(record.value));
}
+41 -4
View File
@@ -6,6 +6,7 @@ from urllib.parse import urljoin
from esphome import automation, external_files, git
from esphome.automation import register_action, register_condition
from esphome.bundle import add_bundle_file
import esphome.codegen as cg
from esphome.components import esp32, microphone, ota, psram
import esphome.config_validation as cv
@@ -28,6 +29,7 @@ from esphome.const import (
TYPE_LOCAL,
)
from esphome.core import CORE, HexInt
from esphome.types import ConfigType
_LOGGER = logging.getLogger(__name__)
@@ -236,10 +238,45 @@ HTTP_SCHEMA = cv.All(
_process_http_source,
)
LOCAL_SCHEMA = cv.Schema(
{
cv.Required(CONF_PATH): cv.All(_validate_json_filename, cv.file_),
}
def _register_local_model_file(config: ConfigType) -> ConfigType:
"""Register the model file that the manifest points to, so bundles include it.
The manifest names its model file relative to itself, so that path never appears
in the YAML and bundle discovery cannot find it on its own.
Problems with the manifest are logged and ignored here rather than raised. Loading
the manifest later reports them with better messages, and raising would be
swallowed by the shorthand validator, which then reports a confusing error about a
missing file in a git repository. Logging keeps the skipped registration
diagnosable if the manifest is only briefly unreadable, since the bundle would
then be built without the model file.
"""
manifest_path: Path = config[CONF_PATH]
try:
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
model = manifest[CONF_MODEL]
except (OSError, ValueError, KeyError, TypeError) as err:
_LOGGER.debug("Not registering a model file from %s: %s", manifest_path, err)
return config
if not isinstance(model, str):
_LOGGER.debug(
"Not registering a model file from %s: 'model' is %s, expected a string",
manifest_path,
type(model).__name__,
)
return config
add_bundle_file(manifest_path.parent / model)
return config
LOCAL_SCHEMA = cv.All(
cv.Schema(
{
cv.Required(CONF_PATH): cv.All(_validate_json_filename, cv.file_),
}
),
_register_local_model_file,
)
+1 -1
View File
@@ -1,4 +1,4 @@
#ifdef USE_ARDUINO
#if defined(USE_ARDUINO) && !defined(USE_RP2) && !defined(USE_LIBRETINY)
#include "esphome/core/log.h"
#include "ac_adapter.h"
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#ifdef USE_ARDUINO
#if defined(USE_ARDUINO) && !defined(USE_RP2) && !defined(USE_LIBRETINY)
// MideaUART
#include <Appliance/AirConditioner/AirConditioner.h>
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#ifdef USE_ARDUINO
#if defined(USE_ARDUINO) && !defined(USE_RP2) && !defined(USE_LIBRETINY)
#include "esphome/core/automation.h"
#include "air_conditioner.h"
+1 -1
View File
@@ -1,4 +1,4 @@
#ifdef USE_ARDUINO
#if defined(USE_ARDUINO) && !defined(USE_RP2) && !defined(USE_LIBRETINY)
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#ifdef USE_ARDUINO
#if defined(USE_ARDUINO) && !defined(USE_RP2) && !defined(USE_LIBRETINY)
// MideaUART
#include <Appliance/AirConditioner/AirConditioner.h>
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#ifdef USE_ARDUINO
#if defined(USE_ARDUINO) && !defined(USE_RP2) && !defined(USE_LIBRETINY)
// MideaUART
#include <Appliance/ApplianceBase.h>
+8
View File
@@ -25,6 +25,8 @@ from esphome.const import (
ICON_POWER,
ICON_THERMOMETER,
ICON_WATER_PERCENT,
PLATFORM_ESP32,
PLATFORM_ESP8266,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
@@ -152,6 +154,12 @@ CONFIG_SCHEMA = cv.All(
.extend(uart.UART_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA),
cv.only_with_arduino,
cv.only_on(
[
PLATFORM_ESP32,
PLATFORM_ESP8266,
]
),
)
# Actions
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#ifdef USE_ARDUINO
#if defined(USE_ARDUINO) && !defined(USE_RP2) && !defined(USE_LIBRETINY)
#ifdef USE_REMOTE_TRANSMITTER
#include "esphome/components/remote_base/midea_protocol.h"
@@ -318,4 +318,232 @@ DsiDriverChip(
(0xE0, 0x00),
]
)
# JC8012P4A1 V2 Driver Configuration (jd9365)
# Some units of this model have a different LCD panel but still use the same JD9365 driver chip.
# Using parameters from esp_lcd_jd9365.h and the working full init sequence
# ----------------------------------------------------------------------------------------------------------------------
# * Resolution: 800x1280
# * PCLK Frequency: 70 MHz
# * DSI Lane Bit Rate: 1.5 Gbps (using 2-Lane DSI configuration)
# * Horizontal Timing (hsync_pulse_width=20, hsync_back_porch=20, hsync_front_porch=40)
# * Vertical Timing (vsync_pulse_width=4, vsync_back_porch=10, vsync_front_porch=20)
# ----------------------------------------------------------------------------------------------------------------------
DsiDriverChip(
"JC8012P4A1-V2",
width=800,
height=1280,
hsync_back_porch=20,
hsync_pulse_width=20,
hsync_front_porch=40,
vsync_back_porch=10,
vsync_pulse_width=4,
vsync_front_porch=20,
pclk_frequency="70MHz",
lane_bit_rate="1500Mbps",
color_order="RGB",
reset_pin=27,
initsequence=[
(0xE0, 0x00),
(0xE1, 0x93),
(0xE2, 0x65),
(0xE3, 0xF8),
(0x80, 0x01),
(0xE0, 0x01),
(0x00, 0x00),
(0x01, 0x44),
(0x03, 0x10),
(0x04, 0x38),
(0x0C, 0x74),
(0x17, 0x00),
(0x18, 0xAF),
(0x19, 0x00),
(0x1A, 0x00),
(0x1B, 0xAF),
(0x1C, 0x00),
(0x35, 0x26),
(0x37, 0x09),
(0x38, 0x04),
(0x39, 0x00),
(0x3A, 0x01),
(0x3C, 0x78),
(0x3D, 0xFF),
(0x3E, 0xFF),
(0x3F, 0x7F),
(0x40, 0x06),
(0x41, 0xA0),
(0x42, 0x81),
(0x43, 0x1E),
(0x44, 0x0D),
(0x45, 0x28),
(0x55, 0x02),
(0x57, 0x69),
(0x59, 0x0A),
(0x5A, 0x2A),
(0x5B, 0x17),
(0x5D, 0x7F),
(0x5E, 0x6B),
(0x5F, 0x5C),
(0x60, 0x50),
(0x61, 0x4C),
(0x62, 0x3E),
(0x63, 0x41),
(0x64, 0x2B),
(0x65, 0x43),
(0x66, 0x42),
(0x67, 0x43),
(0x68, 0x62),
(0x69, 0x52),
(0x6A, 0x5A),
(0x6B, 0x4C),
(0x6C, 0x48),
(0x6D, 0x3A),
(0x6E, 0x28),
(0x6F, 0x10),
(0x70, 0x7F),
(0x71, 0x6B),
(0x72, 0x5C),
(0x73, 0x50),
(0x74, 0x4C),
(0x75, 0x3E),
(0x76, 0x41),
(0x77, 0x2B),
(0x78, 0x43),
(0x79, 0x42),
(0x7A, 0x43),
(0x7B, 0x62),
(0x7C, 0x52),
(0x7D, 0x5A),
(0x7E, 0x4C),
(0x7F, 0x48),
(0x80, 0x3A),
(0x81, 0x28),
(0x82, 0x10),
(0xE0, 0x02),
(0x00, 0x42),
(0x01, 0x42),
(0x02, 0x40),
(0x03, 0x40),
(0x04, 0x5E),
(0x05, 0x5E),
(0x06, 0x5F),
(0x07, 0x5F),
(0x08, 0x5F),
(0x09, 0x57),
(0x0A, 0x57),
(0x0B, 0x77),
(0x0C, 0x77),
(0x0D, 0x47),
(0x0E, 0x47),
(0x0F, 0x45),
(0x10, 0x45),
(0x11, 0x4B),
(0x12, 0x4B),
(0x13, 0x49),
(0x14, 0x49),
(0x15, 0x5F),
(0x16, 0x41),
(0x17, 0x41),
(0x18, 0x40),
(0x19, 0x40),
(0x1A, 0x5E),
(0x1B, 0x5E),
(0x1C, 0x5F),
(0x1D, 0x5F),
(0x1E, 0x5F),
(0x1F, 0x57),
(0x20, 0x57),
(0x21, 0x77),
(0x22, 0x77),
(0x23, 0x46),
(0x24, 0x46),
(0x25, 0x44),
(0x26, 0x44),
(0x27, 0x4A),
(0x28, 0x4A),
(0x29, 0x48),
(0x2A, 0x48),
(0x2B, 0x5F),
(0x2C, 0x01),
(0x2D, 0x01),
(0x2E, 0x00),
(0x2F, 0x00),
(0x30, 0x1F),
(0x31, 0x1F),
(0x32, 0x1E),
(0x33, 0x1E),
(0x34, 0x1F),
(0x35, 0x17),
(0x36, 0x17),
(0x37, 0x37),
(0x38, 0x37),
(0x39, 0x08),
(0x3A, 0x08),
(0x3B, 0x0A),
(0x3C, 0x0A),
(0x3D, 0x04),
(0x3E, 0x04),
(0x3F, 0x06),
(0x40, 0x06),
(0x41, 0x1F),
(0x42, 0x02),
(0x43, 0x02),
(0x44, 0x00),
(0x45, 0x00),
(0x46, 0x1F),
(0x47, 0x1F),
(0x48, 0x1E),
(0x49, 0x1E),
(0x4A, 0x1F),
(0x4B, 0x17),
(0x4C, 0x17),
(0x4D, 0x37),
(0x4E, 0x37),
(0x4F, 0x09),
(0x50, 0x09),
(0x51, 0x0B),
(0x52, 0x0B),
(0x53, 0x05),
(0x54, 0x05),
(0x55, 0x07),
(0x56, 0x07),
(0x57, 0x1F),
(0x58, 0x40),
(0x5B, 0x30),
(0x5C, 0x00),
(0x5D, 0x34),
(0x5E, 0x05),
(0x5F, 0x02),
(0x63, 0x00),
(0x64, 0x6A),
(0x67, 0x73),
(0x68, 0x07),
(0x69, 0x08),
(0x6A, 0x6A),
(0x6B, 0x08),
(0x6C, 0x00),
(0x6D, 0x00),
(0x6E, 0x00),
(0x6F, 0x88),
(0x75, 0xFF),
(0x77, 0xDD),
(0x78, 0x2C),
(0x79, 0x15),
(0x7A, 0x17),
(0x7D, 0x14),
(0x7E, 0x82),
(0xE0, 0x04),
(0x00, 0x0E),
(0x02, 0xB3),
(0x09, 0x60),
(0x0E, 0x48),
(0x37, 0x58),
(0x2B, 0x0F),
(0xE0, 0x05),
(0x15, 0x1D),
(0xE0, 0x00),
(0xE6, 0x02),
(0xE7, 0x0C)
]
)
# fmt: on
@@ -1,3 +1,5 @@
#ifndef USE_BK72XX
#include "sensor_mlx90393.h"
#include "esphome/core/log.h"
@@ -270,3 +272,5 @@ void MLX90393Cls::verify_settings_timeout_(MLX90393Setting stage) {
}
} // namespace esphome::mlx90393
#endif // USE_BK72XX
@@ -1,5 +1,7 @@
#pragma once
#ifndef USE_BK72XX
#include <MLX90393.h>
#include <MLX90393Hal.h>
#include "esphome/components/i2c/i2c.h"
@@ -76,3 +78,5 @@ class MLX90393Cls final : public PollingComponent, public i2c::I2CDevice, public
};
} // namespace esphome::mlx90393
#endif // USE_BK72XX
-27
View File
@@ -71,15 +71,6 @@ struct IPAddress {
bool is_ip4() const { return false; }
bool is_ip6() const { return this->is_set(); }
bool is_multicast() const { return net_ipv6_is_addr_mcast(&ip_addr_); }
// Remove before 2026.8.0
ESPDEPRECATED(
"str() is deprecated: use 'char buf[IP_ADDRESS_BUFFER_SIZE]; ip.str_to(buf);' instead. Removed in 2026.8.0",
"2026.2.0")
std::string str() const {
char buf[IP_ADDRESS_BUFFER_SIZE];
this->str_to(buf);
return buf;
}
char *str_to(char *buf) const {
if (inet_ntop(AF_INET6, &ip_addr_, buf, IP_ADDRESS_BUFFER_SIZE) == nullptr)
buf[0] = '\0';
@@ -95,15 +86,6 @@ struct IPAddress {
}
IPAddress(const std::string &in_address) { inet_aton(in_address.c_str(), &ip_addr_); }
IPAddress(const ip_addr_t *other_ip) { ip_addr_ = *other_ip; }
// Remove before 2026.8.0
ESPDEPRECATED(
"str() is deprecated: use 'char buf[IP_ADDRESS_BUFFER_SIZE]; ip.str_to(buf);' instead. Removed in 2026.8.0",
"2026.2.0")
std::string str() const {
char buf[IP_ADDRESS_BUFFER_SIZE];
this->str_to(buf);
return buf;
}
/// Write IP address to buffer. Buffer must be at least IP_ADDRESS_BUFFER_SIZE bytes.
char *str_to(char *buf) const {
inet_ntop(AF_INET, &ip_addr_, buf, IP_ADDRESS_BUFFER_SIZE);
@@ -186,15 +168,6 @@ struct IPAddress {
bool is_ip4() const { return IP_IS_V4(&ip_addr_); }
bool is_ip6() const { return IP_IS_V6(&ip_addr_); }
bool is_multicast() const { return ip_addr_ismulticast(&ip_addr_); }
// Remove before 2026.8.0
ESPDEPRECATED(
"str() is deprecated: use 'char buf[IP_ADDRESS_BUFFER_SIZE]; ip.str_to(buf);' instead. Removed in 2026.8.0",
"2026.2.0")
std::string str() const {
char buf[IP_ADDRESS_BUFFER_SIZE];
this->str_to(buf);
return buf;
}
/// Write IP address to buffer. Buffer must be at least IP_ADDRESS_BUFFER_SIZE bytes.
/// Output is lowercased per RFC 5952 (IPv6 hex digits a-f).
char *str_to(char *buf) const {
-4
View File
@@ -19,10 +19,6 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
},
"nextion_upload_arduino.cpp": {
PlatformFramework.ESP8266_ARDUINO,
PlatformFramework.RP2_ARDUINO,
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
}
)

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