1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-02 08:01:50 +00:00

Compare commits

...

746 Commits

Author SHA1 Message Date
Keith Burzinski
c4de9e87e4 Merge pull request #8438 from esphome/bump-2025.3.0
2025.3.0
2025-03-19 23:37:13 -05:00
Keith Burzinski
918924d697 Bump version to 2025.3.0 2025-03-19 20:54:32 -05:00
Keith Burzinski
e2c16b4baa Merge pull request #8436 from esphome/bump-2025.3.0b5
2025.3.0b5
2025-03-19 16:01:39 -05:00
Keith Burzinski
10a9162f48 Bump version to 2025.3.0b5 2025-03-19 14:36:04 -05:00
Kevin Ahrendt
fbc884772c [audio] Bugfix: fix flac decoding glitches by using esp-audio-libs v1.1.3 (#8431) 2025-03-19 14:36:03 -05:00
Keith Burzinski
54e3153f27 Merge pull request #8428 from esphome/bump-2025.3.0b4
2025.3.0b4
2025-03-18 15:21:15 -05:00
Keith Burzinski
c2e0a01106 Bump version to 2025.3.0b4 2025-03-18 14:43:26 -05:00
Clyde Stubbs
d2c2439b97 [core] Handle mis-typed platform name more cleanly (#8424) 2025-03-18 14:43:25 -05:00
Keith Burzinski
a8d33dd26a [docker] Bump libfreetype (#8426) 2025-03-18 14:43:25 -05:00
Keith Burzinski
da41a9204e [docker] Bump curl, git, openssh-client, libopenjp2-7, nginx-light (#8419) 2025-03-18 14:43:25 -05:00
Keith Burzinski
5c6368b6b8 Merge pull request #8415 from esphome/bump-2025.3.0b3
2025.3.0b3
2025-03-16 01:53:55 -05:00
Keith Burzinski
9bd7060f6b Bump version to 2025.3.0b3 2025-03-16 01:23:06 -05:00
Mikkel Jeppesen
fb1d178abc Added getters for graphs ymin and ymax (#8112)
Co-authored-by: guillempages <guillempages@users.noreply.github.com>
2025-03-16 01:23:06 -05:00
Clyde Stubbs
90c96a0a0f [font] Fix issues with bitmap fonts (#8407) 2025-03-16 01:23:05 -05:00
Keith Burzinski
1bdf0fdc57 Merge pull request #8400 from esphome/bump-2025.3.0b2
2025.3.0b2
2025-03-13 01:31:17 -05:00
Keith Burzinski
4d95ff2ae0 Bump version to 2025.3.0b2 2025-03-12 23:23:27 -05:00
dependabot[bot]
f36d400058 Bump tornado from 6.4 to 6.4.2 (#8398)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-12 23:23:26 -05:00
J. Nick Koston
c63cf9d151 Bump cryptography to 44.0.2 (#8399) 2025-03-12 23:23:26 -05:00
J. Nick Koston
0a02c1461e Rework pyproject.toml to make it parseable by dependabot (#8397) 2025-03-12 23:23:26 -05:00
J. Nick Koston
b3a69c6c05 Bump aioesphomeapi to 29.6.0 (#8396) 2025-03-12 23:23:26 -05:00
Kevin Ahrendt
dd113f2972 [api] add voice assistant announce to the api (#8395) 2025-03-12 23:23:26 -05:00
Kevin Ahrendt
3c5a0091ee [core] add reallocation support to RAMAllocator (#8390) 2025-03-12 23:23:26 -05:00
Kevin Ahrendt
bf65b73569 [speaker, resampler, mixer] Make volume and mute getters virtual (#8391) 2025-03-12 23:23:26 -05:00
Kevin Ahrendt
a2b123a29a [audio, mixer] Memory and CPU performance improvements (#8387) 2025-03-12 23:23:25 -05:00
J. Nick Koston
3575f52cdf Bump mdns library to 1.8.0 (#8378)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2025-03-12 23:23:25 -05:00
Keith Burzinski
52269305ec Merge pull request #8389 from esphome/bump-2025.3.0b1
2025.3.0b1
2025-03-12 03:40:31 -05:00
Keith Burzinski
37fabd7c0a Bump version to 2025.3.0b1 2025-03-12 01:11:50 -05:00
djasper-ha
4aa7ad1e33 mcp2515: Add missing CFG1 assignment to be able to use 50kbps with a 16MHz crystal. (#8375) 2025-03-11 22:31:01 +11:00
J. Nick Koston
42e432754e Bump zeroconf to 0.146.1 (#8365)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2025-03-11 08:08:02 +00:00
Shivam Maurya
2379f02008 Bump esptool to 4.8.1latest (#8367)
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
2025-03-11 02:34:47 -05:00
J. Nick Koston
d3145dd95b Bump aioesphomeapi to 29.5.1 (#8364) 2025-03-11 02:31:09 -05:00
Clyde Stubbs
ab77dd691b Revert "[io_bus] Initial implementation" (#8384) 2025-03-11 20:02:01 +13:00
Clyde Stubbs
b54c0fd60a [cst816] Remove binary sensor (#8377) 2025-03-09 23:54:40 -05:00
Clyde Stubbs
75d1eeeffe [touchscreen] Axis swap bugfix (#8376) 2025-03-09 22:04:34 -05:00
Dennis Marinus
10cea51739 allow touchscreen buttons outside of display dimensions (#8296)
Co-authored-by: Dennis Marinus <dmarinus@apple.com>
Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com>
2025-03-08 10:41:54 +11:00
Quentin Raynaud
83e090cc7e [time] fix recalc_timestamp_local (#8239) 2025-03-07 00:34:04 -08:00
Clyde Stubbs
583f8f598a [lvgl] Fix initialisation race condition (Bugfix) (#8369) 2025-03-07 01:58:21 -06:00
Chris Djali
3e9556c6c2 Initialise h-bridge switch to requested initial state (#8363) 2025-03-06 16:43:04 -08:00
Kevin Ahrendt
83cba0d7bd [i2s_audio] Bugfix: Speaker incorrectly delays when sending data (#8361) 2025-03-05 21:32:45 -06:00
tomaszduda23
1d6d0d66dc [udp] fix clang tidy (#8351) 2025-03-03 15:08:42 -06:00
Gustavo de León
4ed78023b6 [bmp085] Fix error in read of pressure (#8359) 2025-03-03 15:06:30 -06:00
Damien Sorel
323209523b [ld2450] fix null exception & zone target_count not published (#8348)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2025-03-03 10:44:15 -06:00
Jesse Hills
46a4f4eba9 Merge branch 'release' into dev 2025-03-03 21:24:04 +13:00
Jesse Hills
53fda0e96d Merge pull request #8358 from esphome/bump-2025.2.2
2025.2.2
2025-03-03 21:23:20 +13:00
Jesse Hills
0350eafc1e [helpers] Allow RAMAllocator to be told the size of the object manually (#8356) 2025-03-03 01:11:19 -06:00
Jesse Hills
7b8e68c73a Bump version to 2025.2.2 2025-03-03 17:15:40 +13:00
Jesse Hills
db666e44a7 [ltr390] Move calculation to allow dynamic setting of gain and resolution (#8343) 2025-03-03 17:15:40 +13:00
J. Nick Koston
903d033e0f Bump aioesphomeapi to 29.3.2 (#8353) 2025-03-03 17:15:40 +13:00
Kevin Ahrendt
19d938ce48 [audio] Determine http timeout based on duration since last successful read (#8341) 2025-03-03 17:15:40 +13:00
J. Nick Koston
653318479a Fix end_of_scan_ not being called while disconnecting (#8328) 2025-03-03 17:15:40 +13:00
Jesse Hills
2af5fd5210 [ltr390] Move calculation to allow dynamic setting of gain and resolution (#8343) 2025-03-03 15:35:52 +13:00
J. Nick Koston
29e388b231 Bump aioesphomeapi to 29.3.2 (#8353) 2025-03-03 15:35:32 +13:00
Jesse Hills
d9e23fdb5c [dashboard] Rename trash/delete to archive (#8357) 2025-03-03 15:24:05 +13:00
dependabot[bot]
10eacaccba Bump docker/setup-qemu-action from 3.5.0 to 3.6.0 in the docker-actions group (#8346)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-02 22:23:26 +01:00
Samuel Sieb
23687b2afd [tmp1075] fix component for TMP1075N (#8317) 2025-03-02 07:10:18 -08:00
Clyde Stubbs
f11ad9ad5b [io_bus] Initial implementation (#8227) 2025-02-28 16:04:36 +13:00
Timo Beckers
74a25a7e76 Cover component for Tormatic and Novoferm garage doors (#5933) 2025-02-28 15:57:30 +13:00
Kevin Ahrendt
23e04e18f8 [audio] Determine http timeout based on duration since last successful read (#8341) 2025-02-28 11:43:51 +13:00
tomaszduda23
aed5020a83 [nrf52, core] unified way how all platforms handle SplitDefault (#7715)
Co-authored-by: Tomasz Duda <tomaszduda23@gmai.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-02-28 09:24:28 +13:00
Jesse Hills
476f1b701b [zeroconf] Ruff formatting (#8338) 2025-02-28 09:12:21 +13:00
dependabot[bot]
7c3a7b68d3 Bump actions/cache from 4.2.1 to 4.2.2 in /.github/actions/restore-python (#8337)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-28 09:12:04 +13:00
dependabot[bot]
75dc0d3fb7 Bump actions/cache from 4.2.1 to 4.2.2 (#8336)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-28 09:11:40 +13:00
Clyde Stubbs
9bc4f68d87 [font] Use freetype instead of Pillow for font rendering (#8300) 2025-02-28 08:50:51 +13:00
functionpointer
1029202848 [mlx90393] Fix inverted gain and resolution. Expose temperature_compensation and hallconf. (#7635)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-02-28 07:28:12 +13:00
dependabot[bot]
a831905bba Bump docker/build-push-action from 6.14.0 to 6.15.0 in /.github/actions/build-image (#8332)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-28 07:00:23 +13:00
dependabot[bot]
faffd79545 Bump actions/download-artifact from 4.1.8 to 4.1.9 (#8331)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-28 07:00:10 +13:00
dependabot[bot]
7714147071 Bump the docker-actions group with 2 updates (#8330)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-28 06:59:58 +13:00
Keith Burzinski
4da42dedc8 [ld2450] Fix misplaced `ifdef` and related logic (#8335) 2025-02-28 06:58:19 +13:00
J. Nick Koston
28f283d545 Fix end_of_scan_ not being called while disconnecting (#8328) 2025-02-28 06:56:55 +13:00
J. Nick Koston
3048f303c5 dashboard: Implement automatic ping fallback (#8263) 2025-02-27 15:17:07 +00:00
J. Nick Koston
63a7234767 Include the bluetooth mac address in the device info when proxy is enabled (#8203) 2025-02-27 13:37:11 +00:00
Anton Viktorov
c19621e238 MSA311 and MSA301 accelerometer support (#6795)
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
2025-02-27 14:48:47 +11:00
barchasse38
bc96eb9d52 Update arduino-heatpumpir and add new protocol for Panasonic AC (#8309) 2025-02-26 04:29:33 -06:00
Keith Burzinski
7375dde39c [ld2450] Fix for "unknown" sensor states (#8305) 2025-02-25 20:49:12 -06:00
Pawel
1b7111affb Add option to include vars in remote packages (#7606)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-02-26 14:02:51 +13:00
tomaszduda23
a511926aed [core] SplitDefault unit test (#8324) 2025-02-26 11:29:55 +13:00
Jesse Hills
6b36cb95c9 Merge branch 'release' into dev 2025-02-26 11:01:56 +13:00
Jesse Hills
c13174c318 Merge pull request #8326 from esphome/bump-2025.2.1
2025.2.1
2025-02-26 11:01:14 +13:00
Jonathan Swoboda
d5da341138 [i2c] Fix i2c issue on idf 5.3 (#8283) 2025-02-26 10:49:09 +13:00
Jesse Hills
8fa157581e Bump version to 2025.2.1 2025-02-26 09:49:22 +13:00
Keith Burzinski
7114d6bdd1 [esp32_touch] Fix variants, add tests for variants (#8320) 2025-02-26 09:49:22 +13:00
J. Nick Koston
eca0c21966 Fix bluetooth race when disconnect called while still connecting (#8297) 2025-02-26 09:49:22 +13:00
esphomebot
20c9c410af Update webserver local assets to 20250224-195901 (#8312) 2025-02-26 09:49:22 +13:00
J. Nick Koston
79af437f48 Fix BLE max notifications with ESP-IDF 5.x (#8301) 2025-02-26 09:49:22 +13:00
J. Nick Koston
6e27003787 Bump aioesphomeapi to 29.1.1 (#8274) 2025-02-26 09:49:22 +13:00
tomaszduda23
b7b2f3e61c [core] make upload_program more generic (#8321) 2025-02-26 09:24:05 +13:00
Keith Burzinski
9448737a92 [esp32_touch] Fix variants, add tests for variants (#8320) 2025-02-26 09:14:39 +13:00
J. Nick Koston
6f2bf4ec4c Fix bluetooth race when disconnect called while still connecting (#8297) 2025-02-26 09:13:30 +13:00
kkosik20
54cea6c41e Adding support for chsc6x touch controller (#8258) 2025-02-25 15:03:28 +11:00
tomaszduda23
e754d0a58b [i2c] python code style (#8311) 2025-02-25 16:10:49 +13:00
Nick Kinnan
5e44a035a3 web_server: ensure fair network sharing + prevent lost state changes via deferred publish at high event load (#7538)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2025-02-25 13:19:31 +11:00
rforro
c424fea524 ili9xxx: Add support for GC9D01N circle display (#8302) 2025-02-25 10:45:45 +11:00
Nick Kinnan
6aba1dbd73 [api] ensure fair network sharing + prevent lost state changes via deferred publish at high event load (#7547)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2025-02-25 10:20:21 +11:00
dependabot[bot]
422fb8f1a5 Bump actions/upload-artifact from 4.6.0 to 4.6.1 (#8295) 2025-02-25 10:04:00 +13:00
dependabot[bot]
2988bbb8ce Bump peter-evans/create-pull-request from 7.0.6 to 7.0.7 (#8314) 2025-02-25 10:03:18 +13:00
esphomebot
59299bffc8 Update webserver local assets to 20250224-195901 (#8312) 2025-02-25 10:02:54 +13:00
Kevin Ahrendt
3410aee42e [socket] add connect method (#8308) 2025-02-25 09:32:54 +13:00
J. Nick Koston
96682f5cbe Fix BLE max notifications with ESP-IDF 5.x (#8301) 2025-02-24 14:12:15 +00:00
J. Nick Koston
bfa3254d6c Bump aioesphomeapi to 29.1.1 (#8274) 2025-02-24 07:34:20 +13:00
tomaszduda23
990d1e3bb0 [ota] set USE_OTA_VERSION 2 in defines (#8299) 2025-02-24 07:33:52 +13:00
tomaszduda23
755b0bbfc7 [core, dashboard] load external component to get get_download_types (#8139) 2025-02-22 14:19:17 -06:00
Katherine Whitlock
c281351732 Finish up transition from black-format to ruff (#8294) 2025-02-21 13:02:55 -06:00
dependabot[bot]
9f603a474f Bump docker/build-push-action from 6.13.0 to 6.14.0 in /.github/actions/build-image (#8281)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-21 00:59:18 +01:00
Hareesh M U
bf739506c3 [ld2450] Add new component (#5674)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Marcus Better <marcus@better.se>
Co-authored-by: Trevor Schirmer <24777085+TrevorSchirmer@users.noreply.github.com>
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2025-02-20 03:16:08 -06:00
Katherine Whitlock
3020083564 Ruff format for CI (#8276) 2025-02-19 13:24:43 -06:00
Jesse Hills
31e90e5544 Merge branch 'release' into dev 2025-02-19 22:19:56 +13:00
Jesse Hills
7c9726859f Merge pull request #8275 from esphome/bump-2025.2.0
2025.2.0
2025-02-19 22:19:09 +13:00
dependabot[bot]
7529fb10b4 Bump actions/cache from 4.2.0 to 4.2.1 (#8271)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-19 16:46:22 +13:00
Jesse Hills
ba79e2d7b1 Bump version to 2025.2.0 2025-02-19 13:40:15 +13:00
dependabot[bot]
7006bd24a5 Bump actions/cache from 4.2.0 to 4.2.1 in /.github/actions/restore-python (#8273) 2025-02-18 23:23:56 +00:00
Jesse Hills
58311c9a0d Merge branch 'beta' into dev 2025-02-19 10:44:39 +13:00
Jesse Hills
ae65f76dfe Merge pull request #8272 from esphome/bump-2025.2.0b6
2025.2.0b6
2025-02-19 10:44:08 +13:00
Jesse Hills
4d380214df Bump version to 2025.2.0b6 2025-02-19 09:22:52 +13:00
J. Nick Koston
c5ebf7683e Bump zeroconf to 0.145.1 (#8267) 2025-02-19 09:22:52 +13:00
G-Two
a973adda67 Increase default repeat delay for Toto remote transmitter protocol (#8265) 2025-02-19 09:22:52 +13:00
J. Nick Koston
d9b419eaf5 Bump openssh-client to 1:9.2p1-2+deb12u4 to fix docker builds (#8269) 2025-02-19 09:22:52 +13:00
J. Nick Koston
02bf33c548 Bump zeroconf to 0.145.1 (#8267) 2025-02-18 17:38:41 +00:00
G-Two
b3db04a3d3 Increase default repeat delay for Toto remote transmitter protocol (#8265) 2025-02-19 06:30:03 +13:00
J. Nick Koston
56034e3e79 Bump openssh-client to 1:9.2p1-2+deb12u4 to fix docker builds (#8269) 2025-02-19 06:11:58 +13:00
J. Nick Koston
abbd72e802 Use the process CPU count to determine how many children to create (#8268) 2025-02-19 06:10:33 +13:00
Jesse Hills
1257640e48 Merge branch 'beta' into dev 2025-02-18 14:14:05 +13:00
Jesse Hills
2bc9782ce7 Merge pull request #8264 from esphome/bump-2025.2.0b5
2025.2.0b5
2025-02-18 14:13:33 +13:00
Jesse Hills
6583e17810 Bump version to 2025.2.0b5 2025-02-18 13:39:42 +13:00
J. Nick Koston
64c8bcef2e Bump aioesphomeapi to 29.1.0 (#8105) 2025-02-18 13:39:42 +13:00
J. Nick Koston
f9da8dbfb8 Replace glyphsets with esphome_glyphsets (#8261) 2025-02-18 13:39:42 +13:00
J. Nick Koston
74f7197543 Bump aioesphomeapi to 29.1.0 (#8105) 2025-02-17 16:27:06 -06:00
J. Nick Koston
c21b8bd417 Switch to native arm runners for docker CI (#8262) 2025-02-18 11:19:11 +13:00
J. Nick Koston
1eb658cc5b Replace glyphsets with esphome_glyphsets (#8261) 2025-02-17 21:48:24 +00:00
Jesse Hills
8b251efb75 Merge branch 'beta' into dev 2025-02-17 13:10:17 +13:00
Jesse Hills
26d25464da Merge pull request #8259 from esphome/bump-2025.2.0b4
2025.2.0b4
2025-02-17 13:09:45 +13:00
Jesse Hills
78b55e22ee Bump version to 2025.2.0b4 2025-02-17 12:14:06 +13:00
Ali Jafri
9ee5227fe0 DHT platform now supports modules with inbuilt external resistor (#8257) 2025-02-17 12:14:06 +13:00
J. Nick Koston
e89603fe3b Bump zeroconf to 0.144.3 (#8253) 2025-02-17 12:14:06 +13:00
Djordje Mandic
c0804d665d [scd30] Increase minimal CONF_UPDATE_INTERVAL from 1 to 2 seconds (#8256) 2025-02-17 12:14:05 +13:00
Samuel Sieb
a67b85eabf don't crash on null pages (#8254)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2025-02-17 12:14:05 +13:00
Ali Jafri
a47e27885f DHT platform now supports modules with inbuilt external resistor (#8257) 2025-02-17 11:05:54 +13:00
J. Nick Koston
2e66b33672 Bump zeroconf to 0.144.3 (#8253) 2025-02-17 08:53:19 +13:00
Djordje Mandic
e21ef22706 [scd30] Increase minimal CONF_UPDATE_INTERVAL from 1 to 2 seconds (#8256) 2025-02-17 08:09:42 +13:00
Samuel Sieb
93c2878c21 don't crash on null pages (#8254)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2025-02-16 01:02:51 -06:00
Jesse Hills
b3ad6a03e6 Merge branch 'beta' into dev 2025-02-14 16:47:17 +13:00
Jesse Hills
6e45a7c9af Merge pull request #8251 from esphome/bump-2025.2.0b3
2025.2.0b3
2025-02-14 16:43:58 +13:00
Jesse Hills
e17582544e Bump version to 2025.2.0b3 2025-02-14 14:28:42 +13:00
Jesse Hills
daa7960031 Fix crash when storage file doesnt exist yet (#8249) 2025-02-14 14:28:41 +13:00
Dániel Márai
6999cc0581 Add support for the DAC on the S2 (#8030)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-02-14 14:28:41 +13:00
Jonathan Swoboda
92ad6286aa [logger] Fix bug causing global log level to be overwritten (#8248) 2025-02-14 14:28:41 +13:00
guillempages
1111aa167f [online_image]Fix reset if buffer not allocated (#8236) 2025-02-14 14:28:41 +13:00
Jesse Hills
143b0d3de4 Fix crash when storage file doesnt exist yet (#8249) 2025-02-14 14:27:11 +13:00
Dániel Márai
788c41e6f4 Add support for the DAC on the S2 (#8030)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-02-14 13:15:01 +13:00
Jonathan Swoboda
46b6dcdfbf [logger] Fix bug causing global log level to be overwritten (#8248) 2025-02-13 19:56:08 +00:00
Jesse Hills
d05f641dd0 Merge branch 'beta' into dev 2025-02-13 21:31:40 +13:00
Jesse Hills
897873496a Merge pull request #8246 from esphome/bump-2025.2.0b2
2025.2.0b2
2025-02-13 21:31:05 +13:00
Jesse Hills
b0f6dd7d9c Bump version to 2025.2.0b2 2025-02-13 20:44:12 +13:00
Keith Burzinski
be5639faf1 [modbus_controller] Remove stream dependency (#8244) 2025-02-13 20:44:12 +13:00
Keith Burzinski
e9a537784e [graph] Remove `stream` dependency (#8243) 2025-02-13 20:44:12 +13:00
Gábor Poczkodi
35d303809e [cse7766] Remove stream dependency (#7720)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2025-02-13 20:44:12 +13:00
Jesse Hills
4740f12ce8 [core] Fix `config_dir` for dashboard (#8242) 2025-02-13 20:44:12 +13:00
J. Nick Koston
c8e7e275a4 Bump zeroconf to 0.144.1 (#8238) 2025-02-13 20:44:12 +13:00
Jesse Hills
077ee5b714 [core] Ignore dot-prefixed config entries when looking for target platform (#8240) 2025-02-13 20:44:12 +13:00
Keith Burzinski
fa029e8fc7 [modbus_controller] Extend tests (#8245) 2025-02-13 20:40:02 +13:00
Keith Burzinski
ace953bd50 [modbus_controller] Remove stream dependency (#8244) 2025-02-13 04:34:16 +00:00
Keith Burzinski
e190ef9e9b [graph] Remove `stream` dependency (#8243) 2025-02-13 03:37:29 +00:00
Gábor Poczkodi
2868210d46 [cse7766] Remove stream dependency (#7720)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2025-02-13 03:07:14 +00:00
Jesse Hills
72f6461871 [core] Fix `config_dir` for dashboard (#8242) 2025-02-13 02:57:57 +00:00
J. Nick Koston
4a95468fd2 Bump zeroconf to 0.144.1 (#8238) 2025-02-13 14:17:00 +13:00
Jesse Hills
43319d4c8a [core] Ignore dot-prefixed config entries when looking for target platform (#8240) 2025-02-12 21:05:46 +00:00
guillempages
3b7a7a2262 [online_image]Fix reset if buffer not allocated (#8236) 2025-02-12 20:55:32 +11:00
Jesse Hills
de2d21862b Merge branch 'beta' into dev 2025-02-12 17:24:36 +13:00
Jesse Hills
3d48eb26cd Merge pull request #8237 from esphome/bump-2025.2.0b1
2025.2.0b1
2025-02-12 17:24:00 +13:00
Jesse Hills
ab0d38fbda Bump version to 2025.3.0-dev 2025-02-12 13:53:43 +13:00
Jesse Hills
2b75e34719 Bump version to 2025.2.0b1 2025-02-12 13:53:43 +13:00
Jesse Hills
0b6c416680 Bump esphome-dashboard to 20250212.0 (#8235) 2025-02-12 13:16:17 +13:00
Neil Ségard
7bb2c3c496 Add support for Waveshare 7.3" ACeP 7-Color display (#6380) 2025-02-12 10:31:56 +11:00
Michael Grüner
88cfdc33d4 GDEY042T81 e-paper displays support (#8061)
Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com>
2025-02-12 10:17:34 +11:00
Daniël Koek
a2f1b90238 Add GDEY029T94 support (#7931)
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
2025-02-12 10:16:33 +11:00
Rachasak Ragkamnerd
0401ee9428 added Waveshare BWR Mode for the 4.2in Display (#7995)
Co-authored-by: rrachasak <dev@rachasak.org>
Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com>
2025-02-12 08:35:07 +11:00
tmpeh
14d7931bd6 Added Waveshare e-paper display model "7.50inv2p" to the waveshare_epaper component. (#7751)
Co-authored-by: Tim Pehla <tim.pehla@uni-bielefeld.de>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
2025-02-12 09:41:52 +13:00
Jordan Zucker
6b3f3e1da6 [prometheus] Adding valve entity metrics (#8223) 2025-02-12 08:51:55 +13:00
Kevin Ahrendt
33f9d66e81 [voice_assistant] Add announce support (#8232) 2025-02-12 07:20:39 +13:00
Kevin Ahrendt
46d19d82c2 [speaker] Bugfix: Ensure all audio is played after completely decoding a file (#8231) 2025-02-12 07:14:59 +13:00
guillempages
c9e7562aff [online_image] Improve error handling (#8212) 2025-02-11 22:12:13 +11:00
guillempages
8b7aa4c110 [http_request]Use std::string for headers (#8225) 2025-02-11 11:39:03 +11:00
Táta GEEK
b667ceaced Add waveshare 2.9inch e-Paper HAT (D) (#7906)
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
2025-02-11 11:35:56 +11:00
mystster
abdf215d3a Add partial update of GDEW029T5 e-paper display (#8162)
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
2025-02-11 07:29:27 +11:00
Kevin Ahrendt
84836f15db [speaker] Media Player Components PR9 (#8171)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-02-11 08:00:23 +13:00
Jonathan Swoboda
8be9f02693 [ota] Increase socket timeout earlier in OTA script (#8129) 2025-02-10 17:42:40 +13:00
Igor Novgorodov
1ab1768b6a Add ADC sampling method option (#8131)
Co-authored-by: Djordje Mandic <6750655+DjordjeMandic@users.noreply.github.com>
2025-02-10 17:32:54 +13:00
Stefan Rado
0d13e2040d Don't activate venv in devcontainer (#8128) 2025-02-10 17:12:46 +13:00
Awesome Walrus
fd24b1423c Fix pref conflict of WiFi creds and fast_connect (#8219) 2025-02-10 16:54:37 +13:00
Clyde Stubbs
66c35a9432 [waveshare_epaper] Rationalise and complete tests (#8221) 2025-02-10 16:46:05 +13:00
Craig Andrews
45b8810ab8 [online_image] Set Accept header (#8216) 2025-02-10 15:55:16 +13:00
Clyde Stubbs
ff7d232ee6 [logger] Add runtime level select (#8222)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-02-10 15:53:26 +13:00
guillempages
0cd3af2fcd [online_image]Pin specific version of JPEG library (#8217) 2025-02-10 13:17:29 +13:00
Keith Burzinski
8897a9866d [CI] Consolidate some tests (T) (#8208) 2025-02-10 10:43:21 +13:00
Keith Burzinski
dc8646cda6 [CI] Consolidate some tests (U, V, W, X, Y, Z) (#8210) 2025-02-10 10:43:17 +13:00
Keith Burzinski
353924257a [CI] Consolidate some tests (S) (#8206) 2025-02-10 10:43:10 +13:00
Keith Burzinski
da3d007d7b Markdown tweaks/updates (#8211) 2025-02-10 10:40:19 +13:00
G-Two
9e3359cdf2 Add Toto protocol to remote receiver and transmitter (#8177) 2025-02-06 23:08:06 -06:00
Jonathan Swoboda
7e626b04f2 [esp32_rmt] Set pull-up and open-drain modes based on pin schema (#8178)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2025-02-06 22:09:24 -06:00
dependabot[bot]
4eb551864d Bump the docker-actions group with 2 updates (#8215)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-06 20:33:57 +01:00
bdm310
e337bd7beb [sdl] Implement binary sensors from keystrokes (#8207)
Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com>
2025-02-05 21:53:23 +11:00
Jan Schröter
57739b8bb0 [uponor_smatrix] add target temperature as sensor (#7745) 2025-02-05 15:53:05 +13:00
Jordan Zucker
65ca000e6d [prometheus] Add update entity to prometheus metrics (#8173) 2025-02-05 15:43:44 +13:00
Keith Burzinski
bf6874b52e [CI] Consolidate some tests (Q, R) (#8205) 2025-02-05 15:37:22 +13:00
Keith Burzinski
cecce0f3cb [CI] Consolidate some tests (N, O, P) (#8204) 2025-02-05 15:37:15 +13:00
Clyde Stubbs
4d8f58db94 [preferences] Better handling of flash_write_interval (#8199) 2025-02-05 15:34:30 +13:00
Clyde Stubbs
977333a73c [lvgl] Make layouts work properly on base display (#8193) 2025-02-05 14:44:51 +13:00
Clyde Stubbs
1215d2ffeb [xxtea] Extract encryption functions to separate component (#8183)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-02-05 12:22:40 +13:00
Clyde Stubbs
9b56f9cc6d [lvgl] add triggers for swipe gestures (#8190) 2025-02-05 12:13:21 +13:00
Jonathan Swoboda
2e61229aed [i2c] Workaround for i2c on s2 (#8188) 2025-02-05 12:09:37 +13:00
Keith Burzinski
55203143df [CI] Consolidate some tests (I, J) (#8200) 2025-02-05 12:06:08 +13:00
Keith Burzinski
4e4566361f [CI] Consolidate some tests (M) (#8202) 2025-02-05 12:05:59 +13:00
Keith Burzinski
4273449003 [CI] Consolidate some tests (K, L) (#8201) 2025-02-05 12:05:53 +13:00
Keith Burzinski
f8fae676b1 [CI] Consolidate some tests (H) (#8198) 2025-02-05 12:05:50 +13:00
Keith Burzinski
211aee91e5 [CI] Consolidate some tests (G) (#8196) 2025-02-05 12:05:45 +13:00
Keith Burzinski
6e3527a88b [CI] Consolidate some tests (F) (#8195) 2025-02-05 12:05:35 +13:00
Keith Burzinski
06f9764f51 [CI] Consolidate some tests (E) (#8191) 2025-02-05 12:05:24 +13:00
Keith Burzinski
693d813c4b [CI] Consolidate some tests (D) (#8189) 2025-02-05 12:05:17 +13:00
Keith Burzinski
61ad2510fc [CI] Consolidate some tests (C) (#8186) 2025-02-05 12:05:08 +13:00
Keith Burzinski
53c15f6716 [CI] Consolidate some tests (B) (#8185) 2025-02-05 12:05:02 +13:00
Keith Burzinski
d4ac2d3c7e [CI] Consolidate some tests (A) (#8184) 2025-02-05 12:04:53 +13:00
Kevin Ahrendt
6f4e8f1fbf [mixer] Media Player Components PR8 (#8170)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-02-04 23:00:02 +00:00
Kevin Ahrendt
847cff06b3 [resampler] Media Player Components PR7 (#8169)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-02-05 09:18:11 +13:00
Jesse Hills
bd34697715 Remove arm/v7 container image support (#8194) 2025-02-05 07:56:38 +13:00
Kevin Ahrendt
6b55df36c7 [audio] Media Player Components PR6 (#8168)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-02-04 15:58:35 +13:00
Kevin Ahrendt
b8f9eaecd8 [audio] Media Player Components PR5 (#8167) 2025-02-03 23:47:50 +00:00
Kevin Ahrendt
c8bbc2e84c [audio] Media Player Components PR4 (#8166)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-02-03 22:34:20 +00:00
Djordje Mandic
5108b9a8b7 Make get_flags() in GPIOPin mandatory (#8182)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2025-02-03 11:14:55 -06:00
Djordje Mandic
8de5af4eec Add virtual get_flags() to GPIOPin and implementation in InternalGPIOPin derivatives (#8151) 2025-02-02 21:55:55 -06:00
Kevin Ahrendt
6e5e681055 [audio] Media Player Components PR3 (#8165) 2025-02-03 02:54:55 +00:00
Kevin Ahrendt
f6cf99384b [audio, i2s_audio, speaker] Media Player Components PR2 (#8164)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-02-03 15:25:41 +13:00
Kevin Ahrendt
2b711e532b [i2s_audio] Media Player Components PR1 (#8163) 2025-02-02 21:38:10 +01:00
J. Nick Koston
72c6f04a97 Bump zeroconf to 0.143.0 (#8104) 2025-02-02 21:35:52 +01:00
Rodrigo Martín
03e2701bd0 feat(core): Add support for <...> includes (#8132) 2025-02-02 21:34:38 +01:00
Jonathan Swoboda
051fa3a49f [remote_base] Add default value for offset in is_valid (#8159) 2025-02-01 04:13:38 -06:00
NicoIIT
7392397630 Use abspath for config path dir (#8044) 2025-01-29 15:03:42 +01:00
Jonathan Swoboda
714e2d3e56 [remote_transmitter] Fix issues with 32bit rollover on esp8266 and libretiny (#8056)
Co-authored-by: Jonathan Swoboda <jonathan.swoboda>
2025-01-29 07:34:10 -06:00
dependabot[bot]
12d6c1bbca Bump actions/setup-python from 5.3.0 to 5.4.0 in /.github/actions/restore-python (#8153)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-29 14:31:49 +01:00
dependabot[bot]
7727879f01 Bump actions/setup-python from 5.3.0 to 5.4.0 (#8154)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-29 14:30:30 +01:00
dependabot[bot]
334e952a34 Bump pypa/gh-action-pypi-publish from 1.12.3 to 1.12.4 (#8137)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-29 13:40:31 +01:00
dependabot[bot]
f9856135d0 Bump docker/build-push-action from 6.12.0 to 6.13.0 in /.github/actions/build-image (#8136)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-29 13:40:25 +01:00
Olliver Schinagl
ba3e5e8ecb [climate] Accept °K as intended (#8134) 2025-01-30 00:27:55 +13:00
Jonathan Swoboda
67ccd0eb7f [esp32_rmt] Increase default symbols in led strip and remove IRAM config (#8133) 2025-01-29 04:51:04 -06:00
Clyde Stubbs
619ce93dec [display] Properly handle case of auto_clear_enabled: false (#8156) 2025-01-29 04:45:29 -06:00
Jimmy Hedman
9957840dfc Add multicast support to udp component (#8051) 2025-01-29 21:00:18 +11:00
Stefan Rado
a23ce416ea Fix forgotten uses of use_transparency (#8115) 2025-01-29 14:54:10 +11:00
Clyde Stubbs
2489f95107 [logger] Ensure PRIu32 and friends are available (#8155) 2025-01-28 23:58:06 +00:00
guillempages
7dab1a6082 [online_image] Add JPEG support to online_image (#8127)
Co-authored-by: Jimmy Hedman <jimmy.hedman@gmail.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Rodrigo Martín <contact@rodrigomartin.dev>
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
2025-01-29 10:35:43 +11:00
Rodrigo Martín
f7f8bf4da4 [esp32_ble_server] Create custom services, characteristics and descriptors (#7009)
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
2025-01-28 22:00:28 +11:00
J. Nick Koston
dd18a219db Include Bluetooth connection slot allocations in connections free message (#8148) 2025-01-28 06:57:52 +04:00
Jimmy Hedman
dbf4c2c4da Update mdns for ESP-IDF (#8145) 2025-01-26 22:23:57 -06:00
guillempages
fc847c1de8 [online_image] Code Improvements (#8130) 2025-01-24 07:32:03 +11:00
Jesse Hills
7fccc9ff86 [online_image] Add binary bmp support (#8116)
Co-authored-by: guillempages <guillempages@users.noreply.github.com>
2025-01-23 15:10:19 +13:00
Olliver Schinagl
dee1d84979 [spi] Fix data type in bitbash transfer_() (#8125) 2025-01-22 23:41:55 +00:00
Oskari Lemmelä
65b2d48a6f Fix mqtt climate step rounding (#8121) 2025-01-23 12:32:45 +13:00
brambo123
8aeb08f868 [ads1115] Add sample rate control (#8102) 2025-01-23 12:31:07 +13:00
Djordje Mandic
d4857a1727 Add verbose logging for pulse width calculation in pulse_meter (#8124) 2025-01-23 12:07:26 +13:00
tomaszduda23
0c032bc431 [core] add support for custom platform (#7616)
Co-authored-by: Tomasz Duda <tomaszduda23@gmai.com>
2025-01-23 12:06:07 +13:00
Keith Burzinski
5a103543c4 [esp32] Set logger default interface for C6 (#8126) 2025-01-22 23:00:40 +00:00
Frederik
01ab6d3ddc [debug] fix debug_esp32 printf for partition size and address (#8122)
Co-authored-by: Djordje Mandic <6750655+DjordjeMandic@users.noreply.github.com>
2025-01-23 09:37:32 +11:00
Keith Burzinski
f2170c633a [es7243e] Add support for ES7243E audio ADC (#8098) 2025-01-23 09:23:22 +13:00
Citric Li
c2e52f4b11 Add: Human Presence and Target Count to the Seeed Studio MR60BHA2 (#8010)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Spencer Yan <spencer@spenyan.com>
2025-01-22 13:01:15 +13:00
Keith Burzinski
4843bbd38a [custom] Remove platforms (#8119) 2025-01-22 12:56:51 +13:00
dependabot[bot]
78ce8f014a Bump actions/stale from 9.0.0 to 9.1.0 (#8120)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-22 08:15:56 +13:00
Jesse Hills
b454f63b36 [core] Remove old style platform configuration (#8118) 2025-01-21 00:32:47 -06:00
Jonathan Swoboda
db644542ed [esp32_touch] Fix deprecated warning (#8092)
Co-authored-by: Jonathan Swoboda <jonathan.swoboda>
2025-01-21 16:17:32 +13:00
Keith Burzinski
716a8b87e1 [es8156] Add support for ES8156 audio DAC (#8085)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-01-21 16:15:18 +13:00
Clyde Stubbs
0f4e274e52 [uptime] Cosmetic improvements for uptime text_sensor (#8101) 2025-01-21 15:43:50 +13:00
Keith Burzinski
576dbd6f0c [audio_adc] Add new `audio_adc` component (#8094) 2025-01-21 15:35:40 +13:00
Jesse Hills
c3d00b45f7 Update defines.h for esp-idf 5.1.5 (#8117) 2025-01-21 01:50:04 +00:00
Mikkel Jeppesen
98b872abc7 Fixed incorrect display dimension (#8110) 2025-01-20 09:36:07 +11:00
guillempages
75026be951 [online_image] Use RAMAllocator (#8114) 2025-01-19 22:16:37 +00:00
guillempages
47a0ec467a [image]Rename option "use_transparency" (#8113) 2025-01-20 08:34:38 +11:00
Jesse Hills
9e40d4cf45 Merge branch 'release' into dev 2025-01-17 14:47:56 +13:00
Jesse Hills
fecae2f740 Merge pull request #8100 from esphome/bump-2024.12.4
2024.12.4
2025-01-17 14:47:16 +13:00
Jesse Hills
5a01670803 Bump version to 2024.12.4 2025-01-17 13:40:12 +13:00
Jesse Hills
c2423b18cb Bump python3-setuptools to 66.1.1-1+deb12u1 (#8074) 2025-01-17 13:40:11 +13:00
Jesse Hills
2363b3dfd6 Merge branch 'release' into dev 2025-01-17 13:32:53 +13:00
Jesse Hills
628e47f670 Merge pull request #8099 from esphome/bump-2024.12.3
2024.12.3
2025-01-17 13:32:12 +13:00
Jesse Hills
7666581c54 Bump version to 2024.12.3 2025-01-17 12:24:22 +13:00
Kevin Ahrendt
03c36920ff [http_request] Bugfix: run update function in a task (#8018) 2025-01-17 12:24:22 +13:00
Piotr Szulc
abdd6b232f Fixed libretiny preference wrongly detecting change in the data to store (#7990) 2025-01-17 12:24:22 +13:00
j-sepul
07be7ad7e2 Increase Daly-BMS coltage cells from 16 to 18 cells (#8057) 2025-01-17 11:08:04 +13:00
Katherine Whitlock
820e3488d0 Remove black-formatter from pre-commit hooks (#8097) 2025-01-17 10:44:26 +13:00
Kevin Ahrendt
8c6c45e6c1 [http_request] Bugfix: run update function in a task (#8018) 2025-01-17 10:43:41 +13:00
Katherine Whitlock
16bf56b0f9 Fix running pre-commit on Windows (#8095) 2025-01-17 09:10:20 +13:00
Clyde Stubbs
49c01c26f1 Revert "Add resistance_sampler interface for config validation" (#8093)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-01-16 16:12:30 +11:00
dependabot[bot]
b4a804cc77 Bump docker/build-push-action from 6.11.0 to 6.12.0 in /.github/actions/build-image (#8090)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-16 14:53:23 +13:00
Jordan Zucker
df26ace0f1 [prometheus] Select, media_player, and number prometheus metrics (#7895) 2025-01-15 16:56:22 +13:00
Jesse Hills
e779a8bcb2 [event] Store `last_event_type` in class (#8088) 2025-01-15 16:54:45 +13:00
Jesse Hills
c458fd18df Bump version to 2025.2.0-dev 2025-01-15 16:49:58 +13:00
Kevin Ahrendt
98817a5bbf [es7210] add support for es7210 ADC (#8007) 2025-01-15 16:47:22 +13:00
Saninn Salas Diaz
c43d8460bd fix(web_server/fan): send speed update values even when fan is off (#8086) 2025-01-15 15:14:58 +13:00
Clyde Stubbs
17b88f2e3e [lvgl] fix lvgl.widget.update and friends (#8087) 2025-01-15 14:29:51 +13:00
Clyde Stubbs
dac9768f6a [spi] Restore `SPIDelegateDummy` (#8019) 2025-01-15 13:56:52 +13:00
Clyde Stubbs
e8d2ad4ce8 [ili9xxx] psram and 8 bit changes (#8084) 2025-01-15 11:53:44 +13:00
Clyde Stubbs
c3412df169 [image] Fix mdi images (#8082) 2025-01-15 11:29:27 +13:00
Clyde Stubbs
fc2b15e307 [uptime] Add text_sensor (#8028) 2025-01-15 11:27:47 +13:00
Stefan Rado
bdb1094b47 Allow external libraries to use ESP_LOGx macros (#8078) 2025-01-14 14:20:52 +11:00
Clyde Stubbs
6262fb8fcf [lvgl] fix tests (#8075) 2025-01-13 15:32:54 -06:00
Nate Clark
f319472066 web_server: Adds REST API POST endpoints to arm and disarm (#7985) 2025-01-13 17:35:29 +13:00
Dusan Cervenka
b4a2b50ee0 Fixed topic when mac is used (#7988) 2025-01-13 17:34:07 +13:00
Piotr Szulc
30bb806f26 Fixed libretiny preference wrongly detecting change in the data to store (#7990) 2025-01-13 17:31:01 +13:00
NP v/d Spek
9874d17613 add missing include in base_automation.h (#8001) 2025-01-13 17:29:38 +13:00
Ryan Henderson
13909b7994 [esp32_wifi] Enhance WiFi component with TCPIP core locking. (#7997) 2025-01-13 17:26:23 +13:00
Ryan Henderson
df50e57409 Include esp_mac.h and C++20 str_startswith/str_ends (#7999) 2025-01-13 17:18:20 +13:00
Ryan Henderson
3fa67fad32 Fix compile errors with pioarduino/platform-espressif32: wifi_component_esp32_arduino.cpp (#7998) 2025-01-13 17:17:28 +13:00
Douglas
8fbd512952 Use ESPHome logo on readme page according to theme (light/dark) (#7992) 2025-01-13 17:16:43 +13:00
Edward Firmo
528d3672b4 [psram] Improve total PSRAM display in logs by using rounded KB values (#8008)
Co-authored-by: Djordje Mandic <6750655+DjordjeMandic@users.noreply.github.com>
2025-01-13 17:11:48 +13:00
Edward Firmo
fef50afef8 [debug] Add ESP32 partition table logging to dump_config (#8012) 2025-01-13 17:08:20 +13:00
Edward Firmo
aa1879082c [debug] Add framework type to debug info (#8013) 2025-01-13 17:06:44 +13:00
Djordje Mandic
d8c943972b [core] fix comment for crc8 function in helpers.h (#8016) 2025-01-13 17:05:53 +13:00
Kyle Cascade
f3ebb4eb39 Added VERY_VERBOSE dfplayer printing (#8026) 2025-01-13 16:23:35 +13:00
Clyde Stubbs
f1c0570e3b [image] Transparency changes; code refactor (#7908) 2025-01-13 16:21:42 +13:00
Keith Burzinski
aa87c60717 [nextion] Brightness control tweaks (#8027) 2025-01-13 16:12:54 +13:00
Clyde Stubbs
92a8ebe1f8 [json] use correct formatting (#8039) 2025-01-13 15:56:42 +13:00
Marcin Żbik
dd3ffc7f29 Fix Waveshare 7in5bv3bwr image quality in BWR mode (#8043)
Co-authored-by: zbikmarc <zbimarc+github@gmail.com>
2025-01-13 15:55:30 +13:00
Jonathan Swoboda
aac3841991 [esp32] Fix arch_get_cpu_freq_hz (#8047)
Co-authored-by: Jonathan Swoboda <jonathan.swoboda>
2025-01-13 14:45:35 +13:00
Mischa Siekmann
fb87a1c0bc Allow CONF_RMT_CHANNEL parameter for IDF 4.X (#8035) 2025-01-13 14:42:03 +13:00
Jesse Hills
4409471cd1 Bump python3-setuptools to 66.1.1-1+deb12u1 (#8074) 2025-01-13 14:32:10 +13:00
dependabot[bot]
739edce268 Bump docker/build-push-action from 6.10.0 to 6.11.0 in /.github/actions/build-image (#8053)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-13 12:55:53 +13:00
dependabot[bot]
f25f3334d1 Bump docker/setup-qemu-action from 3.2.0 to 3.3.0 in the docker-actions group (#8052)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-13 12:55:37 +13:00
dependabot[bot]
571935fb3b Bump peter-evans/create-pull-request from 7.0.5 to 7.0.6 (#8024)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-13 12:55:00 +13:00
dependabot[bot]
7c39422692 Bump actions/upload-artifact from 4.5.0 to 4.6.0 (#8058)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-13 12:54:44 +13:00
tomaszduda23
731fb1d172 [spi] relay on KEY_TARGET_PLATFORM as the other platforms does (#8066) 2025-01-13 11:15:39 +13:00
Brian Whicheloe
40bee2a854 Add log level env var (#7604)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-01-13 08:15:22 +13:00
Jimmy Hedman
d69926485c Convert IPAddress to use Pythonmodule ipaddress (#8072) 2025-01-13 08:12:38 +13:00
Clyde Stubbs
fe80750743 [display] auto_clear_enabled defaults (#7986) 2025-01-13 07:56:54 +13:00
Clyde Stubbs
109d737d5d [lvgl] Implement lvgl.page.is_showing: condition (#8055) 2025-01-13 07:53:26 +13:00
Clyde Stubbs
bd17ee8e33 [config] Early check for required version (#8000) 2025-01-13 07:50:13 +13:00
Clyde Stubbs
f1712cffa8 [spi_led_strip] Fix priority (#8021) 2025-01-13 07:49:05 +13:00
Clyde Stubbs
0df6a913b3 [lgvl] disp_bg_image and disp_bg_opa changes (#8025) 2025-01-13 07:46:17 +13:00
Clyde Stubbs
8a98b69a57 [lvgl] fix bg_image_src (#8005)
Co-authored-by: clydeps <U5yx99dok9>
2025-01-13 07:42:03 +13:00
Clyde Stubbs
4530e4d60f [lvgl] remove default state (#8038) 2025-01-13 07:40:50 +13:00
Juan Jose Restrepo
4d7c6b28e1 Update sprinkler.cpp (#7996) 2025-01-10 17:22:30 -06:00
Jimmy Hedman
de603c7565 Enable udp to work (on ipv4) when ipv6 is enabled (#8060)
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
2025-01-10 21:10:19 +00:00
Peter Zich
a498fb5dcf Fix braceless else statements (#7799) 2025-01-09 00:47:30 -06:00
Samu Németh
78543e1e15 Fixed comment typo in light_color_values.h (#8050)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2025-01-08 22:37:52 +00:00
Clyde Stubbs
5e72b7196b Remove rmt channel from idf tests (#8054) 2025-01-08 21:14:08 +00:00
Clyde Stubbs
a0615a92f0 [addressable_light] Remove rmt channel from idf tests (#7987) 2025-01-08 14:25:10 -06:00
Peter Zich
dc5b408748 Initialize esp32_rmt_led_strip buffer (#8036) 2025-01-05 19:50:35 -06:00
Jonathan Swoboda
387bde665e [esp32_rmt] IDF 5+ update fixes (#8002)
Co-authored-by: Jonathan Swoboda <jonathan.swoboda>
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2024-12-24 03:15:40 -06:00
tomaszduda23
45beea68eb [ble_client, bluetooth_proxy, esp32_ble_client, esp32_ble_tracker] fix ble proxy stop working (#7901)
Co-authored-by: J. Nick Koston <nick@koston.org>
2024-12-22 19:49:04 -10:00
Keith Burzinski
c457d8835e Merge branch 'release' into dev 2024-12-20 18:56:18 -06:00
Keith Burzinski
4b51ba3fa4 Merge pull request #7989 from esphome/bump-2024.12.2
2024.12.2
2024-12-20 18:56:03 -06:00
Keith Burzinski
499953e3f4 Bump version to 2024.12.2 2024-12-20 14:34:11 -06:00
Keith Burzinski
69f1a81e1d [esp32_ble] Fix for Improv (#7984) 2024-12-20 14:34:11 -06:00
Keith Burzinski
37fcccbb1c [esp32] Fix flash size warning when using IDF (#7983) 2024-12-20 14:34:10 -06:00
Keith Burzinski
f3cb179f54 [esp32_ble] Fix for Improv (#7984) 2024-12-20 14:16:18 -06:00
Keith Burzinski
ba2edbc189 [esp32] Fix flash size warning when using IDF (#7983) 2024-12-20 01:28:08 -06:00
tomaszduda23
f33b4a714e [esp32_ble] do not skip events if queue is blocked (#7960) 2024-12-19 14:45:40 -10:00
Jesse Hills
85d863601b Merge branch 'release' into dev 2024-12-19 19:48:11 +13:00
Jesse Hills
fe0700166a Merge pull request #7982 from esphome/bump-2024.12.1
2024.12.1
2024-12-19 19:47:30 +13:00
Jesse Hills
d28cf011d1 Bump version to 2024.12.1 2024-12-19 17:07:43 +13:00
Kevin Ahrendt
434879ea04 [core] Bugfix: Implement ring buffer with xRingbuffer (#7973) 2024-12-19 17:07:43 +13:00
dependabot[bot]
7da07303c9 Bump actions/upload-artifact from 4.4.3 to 4.5.0 (#7981)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-19 16:42:29 +13:00
Clyde Stubbs
b33b4481ea [helpers] Provide calls to get free heap and largest available block. (#7978) 2024-12-19 16:40:08 +13:00
Clyde Stubbs
ac631711ab [qspi_dbi] Bugfix and new features (#7979) 2024-12-19 16:30:23 +13:00
Jonathan Swoboda
265b6ec445 [esp32_rmt] Updates for IDF 5+ (#7770)
Co-authored-by: Jonathan Swoboda <jonathan.swoboda>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2024-12-18 20:31:22 -06:00
Kevin Ahrendt
61499dbdd8 [core] Bugfix: Implement ring buffer with xRingbuffer (#7973) 2024-12-19 15:07:07 +13:00
Jesse Hills
0aaef9293b Merge branch 'release' into dev 2024-12-18 17:07:26 +13:00
Jesse Hills
0f0b829bc6 Merge pull request #7976 from esphome/bump-2024.12.0
2024.12.0
2024-12-18 17:06:44 +13:00
Djordje Mandic
a9d883b65a [midea] Add Fahrenheit support to midea_ac.follow_me action (#7762) 2024-12-18 13:47:43 +13:00
Jesse Hills
d330e73c1e Bump version to 2024.12.0 2024-12-18 11:35:43 +13:00
Jonathan Swoboda
7554e954fe [core] Add c6 and h2 to split default (#7974)
Co-authored-by: Jonathan Swoboda <jonathan.swoboda>
2024-12-18 10:12:14 +13:00
Jesse Hills
752af94a75 Merge branch 'beta' into dev 2024-12-18 10:03:48 +13:00
Jesse Hills
561d92d402 Merge pull request #7975 from esphome/bump-2024.12.0b3
2024.12.0b3
2024-12-18 10:02:03 +13:00
Jesse Hills
1a69236473 Bump version to 2024.12.0b3 2024-12-18 07:43:38 +13:00
Jesse Hills
c86ea99145 [esp32_ble] Use RAMAllocator to avoid panic abort from `new` (#7936) 2024-12-18 07:43:38 +13:00
Jesse Hills
7661609049 Bump esphome-dashboard to 20241217.1 (#7971) 2024-12-18 07:43:38 +13:00
Jesse Hills
c38826824f [dashboard] Accept basic auth header (#7965) 2024-12-18 07:43:38 +13:00
Clyde Stubbs
e890486043 [font] cleanly handle font file format exception (Bugfix) (#7970) 2024-12-18 07:43:38 +13:00
Jesse Hills
ccc9fd4a3f [esp32_ble] Use RAMAllocator to avoid panic abort from `new` (#7936) 2024-12-17 12:10:38 -06:00
Jesse Hills
54fbf5184e Bump esphome-dashboard to 20241217.1 (#7971) 2024-12-17 17:32:52 +13:00
Jesse Hills
759df7ae6c [dashboard] Accept basic auth header (#7965) 2024-12-16 22:26:16 -06:00
dependabot[bot]
3d56397e58 Bump docker/setup-buildx-action from 3.7.1 to 3.8.0 in the docker-actions group (#7969)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-17 14:09:09 +13:00
Clyde Stubbs
9f6c64afa6 [font] cleanly handle font file format exception (Bugfix) (#7970) 2024-12-17 14:07:43 +13:00
Jesse Hills
663e18310d [ci] Dont run main ci suite on docker files (#7966) 2024-12-16 16:58:42 -06:00
Jesse Hills
1a89aa8fbf [uart] Use `SOC_UART_NUM as number of uarts instead of UART_NUM_MAX` (#7967) 2024-12-16 05:52:34 +00:00
Edward Firmo
e04743e381 [debug] Detailed reset reason (#7729)
Co-authored-by: Ramil Valitov <ramilvalitov@gmail.com>
2024-12-16 12:12:45 +13:00
Oleg Tarasov
a6957b9d3b [opentherm] Message ordering, on-the-fly message editing, code improvements (#7903) 2024-12-16 12:04:26 +13:00
Edward Firmo
9816c27031 [nextion] Remove _internal from non-protected functions (#7656) 2024-12-16 11:00:44 +13:00
luar123
ea06740b46 Fix adc channel for ESP32-H2 (#7964) 2024-12-16 10:59:54 +13:00
Jesse Hills
9a5ec1b9e6 Merge branch 'beta' into dev 2024-12-16 10:42:53 +13:00
Jesse Hills
6dcbd1a8ae Merge pull request #7963 from esphome/bump-2024.12.0b2
2024.12.0b2
2024-12-16 10:42:21 +13:00
Jesse Hills
63b0930ae8 Bump version to 2024.12.0b2 2024-12-16 07:57:06 +13:00
Edward Firmo
5382bd2a97 [adc] Restore missing LIBRETINY code in a separated file (#7955) 2024-12-16 07:57:06 +13:00
Kevin Ahrendt
de1fbd390b [i2s_audio] Bugfix: Correctly set ring buffer size (#7959) 2024-12-16 07:57:06 +13:00
Jonathan Swoboda
af23357dca [core] Move delay_microseconds_safe to iram (#7957)
Co-authored-by: Jonathan Swoboda <jonathan.swoboda>
2024-12-16 07:57:06 +13:00
Jesse Hills
0fbe6c0d8b [sgp30] Set default update interval to 60s (#7952) 2024-12-16 07:57:06 +13:00
Jonathan Swoboda
4e1ff31342 [const] Add RMT CONF variables to const.py (#7953)
Co-authored-by: Jonathan Swoboda <jonathan.swoboda>
2024-12-16 07:57:06 +13:00
Edward Firmo
df4224e779 [nextion] Publishes is_connected() (#7961) 2024-12-16 07:30:47 +13:00
Edward Firmo
5877c57a35 [adc] Restore missing LIBRETINY code in a separated file (#7955) 2024-12-15 07:55:04 +13:00
Kevin Ahrendt
7f2ca800c1 [i2s_audio] Bugfix: Correctly set ring buffer size (#7959) 2024-12-13 23:17:58 -06:00
Edward Firmo
ce7ff15c8a [pulse_counter] Fix volatile increment/decrement deprecation warnings (#7954) 2024-12-14 08:21:54 +11:00
Edward Firmo
88742e0399 [rotary_encoder] Fix volatile increment/decrement deprecation warnings (#7958) 2024-12-14 08:16:11 +11:00
Jonathan Swoboda
c187cb547c [core] Move delay_microseconds_safe to iram (#7957)
Co-authored-by: Jonathan Swoboda <jonathan.swoboda>
2024-12-13 11:45:10 -08:00
Jesse Hills
42bc960a36 [sgp30] Set default update interval to 60s (#7952) 2024-12-12 03:37:51 -06:00
Jonathan Swoboda
ba63d266d8 [const] Add RMT CONF variables to const.py (#7953)
Co-authored-by: Jonathan Swoboda <jonathan.swoboda>
2024-12-12 04:37:22 +00:00
Jesse Hills
90baba4db7 Merge branch 'beta' into dev 2024-12-11 21:19:19 +13:00
Jesse Hills
1656ced351 Merge pull request #7950 from esphome/bump-2024.12.0b1
2024.12.0b1
2024-12-11 21:18:43 +13:00
Jesse Hills
1dfd15e607 Bump version to 2025.1.0-dev 2024-12-11 15:55:29 +13:00
Jesse Hills
5dcaf1241f Bump version to 2024.12.0b1 2024-12-11 15:55:29 +13:00
Jesse Hills
7aa54b6879 [i2c] Use correct macro to determine number of i2c peripherals for idf (#7947) 2024-12-10 10:24:06 +00:00
Jesse Hills
444e162c92 Synchronise esp32 boards with platform version 51.03.07 (#7945) 2024-12-10 06:39:00 +00:00
Clyde Stubbs
bb27eaaf1e [lvgl] Add on_change event (#7939) 2024-12-10 14:25:29 +13:00
Clyde Stubbs
517f659da8 [lvgl] Fix image mode property (Bugfix) (#7938) 2024-12-10 14:23:30 +13:00
Jesse Hills
5a92e24662 [const] Move `CONF_TEMPERATURE_COMPENSATION` to common const.py (#7943) 2024-12-10 14:22:30 +13:00
Edward Firmo
437b236a4d [adc] Split files by platform (#7940) 2024-12-10 13:38:45 +13:00
dependabot[bot]
14eac3dbce Bump pypa/gh-action-pypi-publish from 1.12.2 to 1.12.3 (#7941)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-09 23:44:39 +01:00
Yoonji Park
132a096ae7 Add font anti-aliasing for grayscale display (#7934) 2024-12-09 22:13:21 +11:00
Jesse Hills
440080a753 [display] Fix strftime overload ignoring alignment (#7937) 2024-12-09 17:09:29 +13:00
David Schneider
f15e3cfb9b Optimize QMC5883L reads (#7889) 2024-12-09 15:51:37 +13:00
Citric Lee
9d000e9abf Add: Seeed Studio MR60BHA2 mmWave Sensor (#7589)
Co-authored-by: Spencer Yan <spencer@spenyan.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-12-09 15:28:41 +13:00
Jesse Hills
97fd7493b5 Merge branch 'release' into dev 2024-12-06 17:23:14 +13:00
Jesse Hills
4c87658503 Merge pull request #7929 from esphome/bump-2024.11.3
2024.11.3
2024-12-06 17:22:32 +13:00
Jesse Hills
c80e035bd5 Bump version to 2024.11.3 2024-12-06 15:55:51 +13:00
Clyde Stubbs
c8ec0bb7ea [esp32] Fix crash with empty platformio_options: value (#7920) 2024-12-06 15:55:51 +13:00
Clyde Stubbs
86ae1c5931 [lvgl] Fix msgbox content (#7912) 2024-12-06 15:55:51 +13:00
Clyde Stubbs
d0958f7cf2 [lvgl] Bugfixes (#7896) 2024-12-06 15:55:51 +13:00
Jesse Hills
982ce1db72 Cast port to int for ota pushing (#7888) 2024-12-06 15:55:51 +13:00
Krzysztof Zdulski
f042c6e643 Fix recalc_timestamp_utc (#7894) 2024-12-06 15:55:51 +13:00
Jesse Hills
5fcd26bfe9 [st7920] Remove unnecessary warning when drawing outside display bounds (#7868) 2024-12-06 15:55:51 +13:00
FreeBear-nc
5717d557f5 Add IRAM_ATTR to all functions used during interrupts on esp8266 chips. (#7840) 2024-12-06 15:55:51 +13:00
guillempages
3bac45e737 [online_image]Don't access decoder if not initialized (#7882) 2024-12-06 15:55:50 +13:00
Samuel Sieb
e623989878 fix local time timestamp calculation (#7807)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2024-12-06 15:55:50 +13:00
Oleg Tarasov
39cbc6b183 [opentherm] Fix out of memory errors on ESP8266 (#7835) 2024-12-06 15:55:50 +13:00
Keith Burzinski
749a5e3348 [modbus] More clean-up (#7921) 2024-12-06 15:41:53 +13:00
Jesse Hills
b0e3ac01e8 Update project description (#7928) 2024-12-06 15:24:20 +13:00
Jesse Hills
58123845ff Move docker oci labels to correct image (#7927) 2024-12-06 14:11:11 +13:00
alorente
bfd75d736c Add OCI Image Labels (#7924) 2024-12-06 13:21:14 +13:00
Clyde Stubbs
4e3195b474 [esp32] Fix crash with empty platformio_options: value (#7920) 2024-12-06 13:16:59 +13:00
dependabot[bot]
d3a71a1d45 Bump actions/cache from 4.1.2 to 4.2.0 in /.github/actions/restore-python (#7925)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-06 13:11:46 +13:00
dependabot[bot]
555bdac604 Bump actions/cache from 4.1.2 to 4.2.0 (#7926)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-06 13:11:31 +13:00
Keith Burzinski
acc8d24a32 [esp32] Use pioarduino + IDF 5.1.5 as default for IDF builds (#7706)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-12-05 02:39:30 -06:00
Jesse Hills
f3cc1e541a [esp32_rmt_led_strip] Add `COMPONENT_SCHEMA` extending (#7918) 2024-12-04 21:44:59 -06:00
Keith Burzinski
ece72c6b18 [i2s_audio] Speaker type fix (#7919) 2024-12-04 21:03:38 -06:00
Sebastian Muszynski
4e839d42d0 [CI] Update clang-tidy to 18.1.8 (#7915) 2024-12-04 15:44:34 -06:00
Pavlo Dudnytskyi
d429aa8bb8 Haier AC quiet mode switch fix (#7902) 2024-12-05 10:43:00 +13:00
Kevin Ahrendt
472402745d [i2s_audio] Bugfix: Follow configured bits per sample (#7916) 2024-12-05 10:18:14 +13:00
mikosoft83
016fac2496 Add strftime variant with background color (#7714)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-12-04 13:18:00 +13:00
Clyde Stubbs
79478cdb8a [sntp] Resolve warnings from ESP-IDF 5.x (#7913) 2024-12-04 13:13:07 +13:00
Keith Burzinski
dbed74b50d [docker] Fix clang-tidy installation (#7910) 2024-12-04 12:26:27 +13:00
Keith Burzinski
d00ec7e544 [helpers] clang-tidy fix for #7706 (#7909) 2024-12-04 12:23:17 +13:00
Clyde Stubbs
a37ff2dbd9 [lvgl] Fix msgbox content (#7912) 2024-12-03 20:48:50 +00:00
Clyde Stubbs
00ddb0a427 [font] Restore correct default glyphs for bitmap fonts (#7907) 2024-12-03 19:50:56 +13:00
Clyde Stubbs
c95887a14a [lvgl] Bugfixes (#7896) 2024-12-03 19:50:11 +13:00
Jesse Hills
dc5942a59b [ble] Allow setting shorter name for ble advertisements (#7867)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2024-12-02 23:38:44 -06:00
kbullet
584dbf2668 MQTT sensors handling of publishing NaN values (#7768)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-12-02 23:50:05 +00:00
Keith Burzinski
9c8976be13 [CI] Update clang-tidy to 18.1.3 (#7822) 2024-12-03 11:29:45 +13:00
Clyde Stubbs
e08a9cc3a3 [font et. al.] Remove explicit check for pillow installed. (#7891) 2024-12-03 11:27:51 +13:00
Keith Burzinski
b79a3d6727 [CI] Bump GHA runners to `ubuntu-24.04` (#7905) 2024-12-03 06:42:44 +13:00
David Woodhouse
fb96e3588d Add H-Bridge switch component (#7421)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2024-12-02 02:16:58 -06:00
Keith Burzinski
edd847ea40 [modbus_controller] Clang fixes (#7899) 2024-12-02 13:27:32 +13:00
Jesse Hills
83d6834e27 Cast port to int for ota pushing (#7888) 2024-12-01 17:10:18 +01:00
Keith Burzinski
8f69d07061 [hx711] clang-tidy fixes for #7822 (#7900) 2024-12-01 17:08:52 +01:00
Krzysztof Zdulski
30477c764d Fix recalc_timestamp_utc (#7894) 2024-11-29 13:05:00 -08:00
Jesse Hills
217a80a178 [st7920] Remove unnecessary warning when drawing outside display bounds (#7868) 2024-11-28 16:57:11 +13:00
FreeBear-nc
5486b40aab Add IRAM_ATTR to all functions used during interrupts on esp8266 chips. (#7840) 2024-11-28 16:56:37 +13:00
guillempages
beb8ab50e2 [online_image]Don't access decoder if not initialized (#7882) 2024-11-28 16:55:20 +13:00
Max Slotov
7cdf5b55ef [deep_sleep] fix deep_sleep not keeping awake when sleep_duration is defined (#7885) 2024-11-28 16:51:07 +13:00
Clyde Stubbs
c9b0490305 [lvgl] Make image update via lambda work (#7886) 2024-11-28 16:48:48 +13:00
Keith Burzinski
d305870284 [network] clang-tidy fixes for #7822 (#7870) 2024-11-28 11:25:34 +13:00
Keith Burzinski
ff5004d7db [dht] clang-tidy fixes for #7822 (#7871) 2024-11-28 11:25:15 +13:00
Keith Burzinski
7aa3a1a1cc [apds9306] clang-tidy fixes for #7822 (#7872) 2024-11-28 11:25:00 +13:00
Keith Burzinski
e124151e5c [ezo] clang-tidy fixes for #7822 (#7873) 2024-11-28 11:24:43 +13:00
Keith Burzinski
e229ed0da3 [logger] clang-tidy fixes for #7822 (#7875) 2024-11-28 11:23:40 +13:00
Keith Burzinski
12cdeca48a [various] clang-tidy fixes for #7822 (#7874) 2024-11-28 11:23:20 +13:00
Keith Burzinski
a825ef59d4 [nextion] clang-tidy fixes for #7822 (#7878) 2024-11-28 11:22:37 +13:00
Keith Burzinski
65a5216d17 [pca6416a, pca9554] clang-tidy fixes for #7822 (#7879) 2024-11-28 11:22:18 +13:00
Keith Burzinski
567256bd62 [rotary_encoder] clang-tidy fixes for #7822 (#7880) 2024-11-28 11:21:10 +13:00
Keith Burzinski
4da57c35d0 [uln2003] clang-tidy fixes for #7822 (#7881) 2024-11-28 11:20:51 +13:00
Keith Burzinski
f2e8e655ba [mqtt] clang-tidy fixes for #7822 (#7877) 2024-11-28 11:19:41 +13:00
Keith Burzinski
8439232b11 [esp32_ble] clang-tidy fixes for #7822 (#7883) 2024-11-28 11:18:43 +13:00
Keith Burzinski
e6c730ab10 [max31865] clang-tidy fixes for #7822 (#7876) 2024-11-28 11:16:54 +13:00
Jesse Hills
e49df765d2 Merge branch 'release' into dev 2024-11-28 07:22:24 +13:00
Jesse Hills
e6da55b925 Merge pull request #7869 from esphome/bump-2024.11.2
2024.11.2
2024-11-28 07:21:44 +13:00
Jesse Hills
c894645747 Bump version to 2024.11.2 2024-11-27 14:06:21 +13:00
Samuel Sieb
2539cba610 [honeywell] use warning instead of failing (#7862)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2024-11-27 14:06:21 +13:00
Samuel Sieb
5ddbe5cdba [wifi] fix 32 char SSIDs (#7834)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2024-11-27 14:06:20 +13:00
Samuel Sieb
4c7552eca4 keypad binary sensors should be initially off (#7808)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2024-11-27 14:06:20 +13:00
Ramil Valitov
72bf0086e4 [fix] Status sensor does not check if required network component is missing (#7734)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-11-27 14:06:20 +13:00
TFGF
1b91e0027b [Modbus Controller] Fix issue #6477. Online automation triggering Offline (#7801) 2024-11-27 14:06:20 +13:00
Samuel Sieb
e9851e7eb2 fix modbus crashing when bad data returned (#7810)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2024-11-27 14:06:20 +13:00
Clyde Stubbs
80fedbc1a5 [qspi_dbi] Fix init sequences (Bugfix) (#7805) 2024-11-27 14:06:20 +13:00
Clyde Stubbs
a4a71797d9 [docker] Leave run-time required libraries installed. (#7804) 2024-11-27 14:06:20 +13:00
Clyde Stubbs
4a97064b2c [lvgl] Bugfixes (#7803) 2024-11-27 14:06:20 +13:00
tomaszduda23
a3ef2ed7fd python lint for platform components (#7864) 2024-11-27 09:56:43 +13:00
dependabot[bot]
3a8b41daa3 Bump docker/build-push-action from 6.9.0 to 6.10.0 in /.github/actions/build-image (#7866)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-26 21:06:56 +01:00
Jesse Hills
921be1a17c Move `USE_CAPTIVE_PORTAL` into all define groups it can be used with (#7863) 2024-11-27 07:09:16 +13:00
Keith Burzinski
e3d673d16c [helpers, optional] clang-tidy fixes for #7822 (#7841) 2024-11-27 07:08:02 +13:00
Keith Burzinski
39f3f795e2 [mqtt] clang-tidy fixes for #7822 (#7851) 2024-11-27 07:07:53 +13:00
Keith Burzinski
53691d28a8 [haier] clang-tidy fixes for #7822 (#7849) 2024-11-27 07:07:42 +13:00
Keith Burzinski
3730b0310b [sprinkler] clang-tidy fixes for #7822 (#7857) 2024-11-27 07:07:36 +13:00
Keith Burzinski
2b9013699d [alarm_control_panel] clang-tidy fixes for #7822 (#7845) 2024-11-26 11:05:39 +01:00
Samuel Sieb
be78827274 [honeywell] use warning instead of failing (#7862)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2024-11-26 23:05:20 +13:00
Keith Burzinski
cd1ee96606 [cse7766] clang-tidy fixes for #7822 (#7846) 2024-11-26 11:04:50 +01:00
Keith Burzinski
2fa8d907b3 [ltr501] clang-tidy fixes for #7822 (#7850) 2024-11-26 11:01:34 +01:00
Keith Burzinski
4c383906c4 [pipsolar] clang-tidy fixes for #7822 (#7855) 2024-11-26 11:00:40 +01:00
Keith Burzinski
bdc6302ea1 [sun_gtil2] clang-tidy fixes for #7822 (#7858) 2024-11-26 11:00:03 +01:00
Keith Burzinski
31c13e4c16 [output] clang-tidy fixes for #7822 (#7854) 2024-11-26 10:59:29 +01:00
Keith Burzinski
6b59f55a50 [nfc, pn532, pn7150, pn7160] clang-tidy fixes for #7822 (#7853) 2024-11-26 10:58:18 +01:00
Keith Burzinski
e6bd2238ce [sim800l] clang-tidy fixes for #7822 (#7856) 2024-11-26 10:54:16 +01:00
Keith Burzinski
2d4688a206 [shelly_dimmer] clang-tidy fixes for #7822 (#7844) 2024-11-26 10:53:23 +01:00
Keith Burzinski
536bcab5de [nextion] clang-tidy fixes for #7822 (#7852) 2024-11-26 10:52:57 +01:00
Keith Burzinski
1c2d2bce5a [display_menu_base] clang-tidy fixes for #7822 (#7847) 2024-11-26 10:52:26 +01:00
Keith Burzinski
2eac8b6c46 [camera_web_server] Use header instead of mock struct (#7823) 2024-11-26 10:50:33 +01:00
Jesse Hills
6e50e2aa65 Fix entity name validation to allow "Off" and "On" (#7821) 2024-11-26 10:50:16 +01:00
Keith Burzinski
841d278224 [dsmr] clang-tidy fixes for #7822 (#7848) 2024-11-26 10:47:57 +01:00
Keith Burzinski
11076e4614 [wireguard] clang-tidy fixes for #7822 (#7859) 2024-11-26 10:47:24 +01:00
Keith Burzinski
72df3d1606 [xiaomi_ble] clang-tidy fixes for #7822 (#7860) 2024-11-26 10:37:20 +01:00
Keith Burzinski
ae6736311a [lvgl] clang-tidy fixes for #7822 (#7843) 2024-11-26 04:29:36 +00:00
Citric Lee
c0dcecc465 Add: Seeed Studio mr60fda2 mmwave sensor (#7576)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Spencer Yan <spencer@spenyan.com>
2024-11-26 13:53:21 +13:00
Samuel Sieb
d9d368d38e add on_key trigger to matrix_keypad (#7830)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2024-11-26 13:21:47 +13:00
Samuel Sieb
a70cee1dc1 fix local time timestamp calculation (#7807)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2024-11-26 13:15:01 +13:00
Samuel Sieb
f4766ab74f [wifi] fix 32 char SSIDs (#7834)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2024-11-26 12:58:21 +13:00
Keith Burzinski
4fbf41472a [CI] Add/update some system include paths (#7831) 2024-11-25 17:41:27 -06:00
Keith Burzinski
6ee02c47c2 [homeassistant.number] Return when value not set (#7839) 2024-11-25 22:42:12 +00:00
JonasB2497
140d77061b added Waveshare BWR Mode for the 7.5in Display (#7687) 2024-11-26 11:29:58 +13:00
programmingbgloDE
d6f4f05090 Add waveshare 1 45 in v2 b support (#7052) 2024-11-26 11:26:48 +13:00
Keith Burzinski
bdb91112ea [helpers] Add NOLINT for Mutex private field `handle_` (#7838) 2024-11-25 16:20:03 -06:00
Keith Burzinski
b027b6a711 [opentherm] Add nolint for 8266 static global (#7837) 2024-11-26 10:57:40 +13:00
Oleg Tarasov
89ecfc2004 [opentherm] Fix out of memory errors on ESP8266 (#7835) 2024-11-26 10:47:01 +13:00
Keith Burzinski
cf835d1580 [opentherm] Follow variable naming convention (#7833) 2024-11-25 03:50:24 -06:00
Keith Burzinski
17a09cd221 [audio] Header modernization (#7832) 2024-11-25 03:50:18 -06:00
Keith Burzinski
1bd2d41ffd [uart] void functions should return nothing (#7829) 2024-11-25 21:39:22 +13:00
Keith Burzinski
aa6cea6f7e [sx1509] Fix up includes (#7828) 2024-11-25 08:27:36 +00:00
Keith Burzinski
ebf895990b [stepper] Remove unnecessary `#include` (#7827) 2024-11-25 08:25:04 +00:00
Keith Burzinski
46a435f5f2 [safe_mode] Remove unused capture (#7826) 2024-11-25 08:24:35 +00:00
Keith Burzinski
6c548a1596 [ota] void functions should return nothing (#7825) 2024-11-25 08:23:00 +00:00
Keith Burzinski
7f75f2135d [nextion] Remove assignment within if (#7824) 2024-11-25 08:22:50 +00:00
Samuel Sieb
c49f7293fe binary_sensor for switch state (#7819) 2024-11-24 23:24:23 -08:00
Jesse Hills
71496574e9 Move `CONF_NAME_ADD_MAC_SUFFIX to const.py` (#7820) 2024-11-25 17:26:36 +13:00
Samuel Sieb
b95b4a0694 keypad binary sensors should be initially off (#7808)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2024-11-25 11:40:51 +13:00
Samuel Sieb
59653ec785 allow multiple graphical menus (#7809)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2024-11-25 11:40:28 +13:00
Ramil Valitov
e02f3cdac7 [fix] Status sensor does not check if required network component is missing (#7734)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-11-25 11:23:30 +13:00
TFGF
d4d630823c [Modbus Controller] Fix issue #6477. Online automation triggering Offline (#7801) 2024-11-25 11:15:10 +13:00
Rodrigo Martín
9fc1377b44 feat(WiFi): Add wifi.configure action (#7335) 2024-11-25 11:06:21 +13:00
Samuel Sieb
e3e3d92347 fix modbus crashing when bad data returned (#7810)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2024-11-25 09:42:46 +13:00
Clyde Stubbs
13077095c2 [qspi_dbi] Fix init sequences (Bugfix) (#7805) 2024-11-25 09:27:09 +13:00
Clyde Stubbs
4001d82ca2 [docker] Leave run-time required libraries installed. (#7804) 2024-11-25 09:25:51 +13:00
Clyde Stubbs
4936ca1700 [lvgl] Bugfixes (#7803) 2024-11-25 09:25:16 +13:00
NP v/d Spek
2ecd5cff07 [wifi] Make wifi_channel_() public (#7818) 2024-11-25 09:16:51 +13:00
Petr Kejval
dea297c8d7 [nextion] Add publish actions (#7646)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2024-11-22 22:52:02 -06:00
Jesse Hills
ef7c5c6055 Merge branch 'release' into dev 2024-11-22 11:34:47 +13:00
Jesse Hills
ee3cfb2b76 Merge pull request #7798 from esphome/bump-2024.11.1
2024.11.1
2024-11-22 11:23:56 +13:00
Jesse Hills
2cc2a2153b Bump version to 2024.11.1 2024-11-22 10:08:00 +13:00
J. Nick Koston
e51f3d9498 Ensure storage I/O for ignored devices runs in the executor (#7792) 2024-11-22 10:08:00 +13:00
Alain Turbide
1c1f3f7c55 Fix for OTA mode not activating in safe_mode when OTA section has an on_xxxx action (#7796)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-11-22 10:08:00 +13:00
Spencer Owen
ea424b0699 Check for min_version earlier in validation (#7797) 2024-11-22 10:08:00 +13:00
Manuel Kasper
489d0d20d2 [qspi_dbi] Fix garbled graphics on RM690B0 (#7795) 2024-11-22 10:08:00 +13:00
Jesse Hills
f04e3de7b8 [speaker] Add missing auto-load for `audio` (#7794) 2024-11-22 10:08:00 +13:00
Jesse Hills
a0693060e4 [rtttl] Clamp gain between 0 and 1 (#7793) 2024-11-22 10:08:00 +13:00
Clyde Stubbs
888b237964 [http_request] Fix within context with parameters. (Bugfix) (#7790) 2024-11-22 10:08:00 +13:00
J. Nick Koston
122ff731ef Ensure storage I/O for ignored devices runs in the executor (#7792) 2024-11-22 09:41:31 +13:00
Alain Turbide
3232866dc3 Fix for OTA mode not activating in safe_mode when OTA section has an on_xxxx action (#7796)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-11-22 09:39:32 +13:00
Spencer Owen
ccf2854b61 Check for min_version earlier in validation (#7797) 2024-11-22 08:24:10 +13:00
Manuel Kasper
03ae6b2c1b [qspi_dbi] Fix garbled graphics on RM690B0 (#7795) 2024-11-21 20:46:49 +11:00
Jesse Hills
6bcbbcce02 [speaker] Add missing auto-load for `audio` (#7794) 2024-11-21 02:10:20 -06:00
Jesse Hills
fbb9967117 [rtttl] Clamp gain between 0 and 1 (#7793) 2024-11-21 00:22:02 -06:00
Clyde Stubbs
6d4f787f67 [http_request] Fix within context with parameters. (Bugfix) (#7790) 2024-11-21 13:10:28 +13:00
Kjell Braden
5e27a8df1f enable rp2040 for online_image (#7769) 2024-11-21 07:29:48 +13:00
Edward Firmo
846b091aac [nextion] New trigger on_buffer_overflow (#7772) 2024-11-21 07:28:21 +13:00
Jonathan Swoboda
372d68a177 [remote_base] Fix extra comma in dump raw (#7774)
Co-authored-by: Jonathan Swoboda <jonathan.swoboda>
2024-11-21 07:27:23 +13:00
Jesse Hills
4fc19902ab Merge branch 'release' into dev 2024-11-21 06:44:07 +13:00
Jesse Hills
9a7d5dcad8 Merge pull request #7789 from esphome/bump-2024.11.0
2024.11.0
2024-11-21 06:42:54 +13:00
Jesse Hills
ef78c404dd Bump version to 2024.11.0 2024-11-20 21:29:42 +13:00
Jesse Hills
c857f98557 Merge branch 'beta' into dev 2024-11-20 20:18:12 +13:00
Jesse Hills
01a24de3a8 Merge pull request #7788 from esphome/bump-2024.11.0b4
2024.11.0b4
2024-11-20 20:17:38 +13:00
Jesse Hills
ae46dcef7e Bump version to 2024.11.0b4 2024-11-20 17:50:30 +13:00
Jesse Hills
872b8ee753 Bump esphome-dashboard to 20241120.0 (#7787) 2024-11-20 17:50:29 +13:00
Jesse Hills
eb8a2326ad [http_request] Feed watchdog timeout around http request functions (#7786) 2024-11-20 17:50:29 +13:00
Jesse Hills
cf63d627fe Bump esphome-dashboard to 20241120.0 (#7787) 2024-11-20 17:39:28 +13:00
Jesse Hills
49e9c43339 [http_request] Feed watchdog timeout around http request functions (#7786) 2024-11-19 18:54:19 -06:00
Jesse Hills
f1dc9537ff Merge branch 'beta' into dev 2024-11-20 07:28:20 +13:00
Jesse Hills
1ad535d030 Merge pull request #7784 from esphome/bump-2024.11.0b3
2024.11.0b3
2024-11-20 07:27:37 +13:00
Jesse Hills
1ed27b7cc0 Bump version to 2024.11.0b3 2024-11-19 09:04:30 +13:00
Jesse Hills
585586780b Bump esphome-dashboard to 20241118.0 (#7782) 2024-11-19 09:04:30 +13:00
pethans
50aeefc662 TuyaFan control should use oscillation_type (#7776)
Co-authored-by: Peter Hanson <phanson@whistler.lan>
2024-11-19 09:04:30 +13:00
Jesse Hills
6e41c22e9d Bump esphome-dashboard to 20241118.0 (#7782) 2024-11-18 20:44:39 +13:00
pethans
e81191ebd2 TuyaFan control should use oscillation_type (#7776)
Co-authored-by: Peter Hanson <phanson@whistler.lan>
2024-11-18 07:47:29 +13:00
dependabot[bot]
b29c119408 Bump codecov/codecov-action from 4 to 5 (#7771)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-15 12:43:52 +01:00
Jesse Hills
e819185de1 Merge branch 'beta' into dev 2024-11-14 15:33:40 +13:00
Jesse Hills
00465f4a6f Merge pull request #7765 from esphome/bump-2024.11.0b2
2024.11.0b2
2024-11-14 15:33:10 +13:00
Jesse Hills
f4dc11477f Bump version to 2024.11.0b2 2024-11-14 14:21:43 +13:00
Fabio Bonelli
754352b4d7 ld2420: fix typo in log message (#7758) 2024-11-14 14:21:43 +13:00
Jordan Zucker
67a4e56fcf Disable bluetooth proxy during update (#7695)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-11-14 14:21:43 +13:00
Felipe Santos
9bc7b74d01 Fix reactive power unit of measurement from VAR to var (#7757)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-11-14 14:21:43 +13:00
Roving Ronin
15bfc4c91f Update UNIT_VOLT_AMPS_REACTIVE = "var" (Currently 'VAR') (#7643) 2024-11-14 14:21:43 +13:00
Kevin Ahrendt
a0159a2746 [i2s_audio] Bugfix: Adjust I2S speaker setup priority (#7759) 2024-11-14 14:21:43 +13:00
luar123
44545a18a0 Fix temperature and humidity for bme680 with bsec2 (#7728) 2024-11-14 14:21:43 +13:00
Fabio Bonelli
0b51ec2c88 ld2420: fix typo in log message (#7758) 2024-11-14 13:57:51 +13:00
Jordan Zucker
5e62c489b0 Disable bluetooth proxy during update (#7695)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-11-14 13:57:09 +13:00
Felipe Santos
d015088855 Fix reactive power unit of measurement from VAR to var (#7757)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-11-14 13:44:18 +13:00
Roving Ronin
39c889e662 Update UNIT_VOLT_AMPS_REACTIVE = "var" (Currently 'VAR') (#7643) 2024-11-14 13:43:21 +13:00
Kevin Ahrendt
c7c8711c9c [i2s_audio] Bugfix: Adjust I2S speaker setup priority (#7759) 2024-11-14 06:39:02 +13:00
Jesse Hills
0a92405f2d Merge branch 'beta' into dev 2024-11-13 17:33:07 +13:00
Jesse Hills
b4b6b75e84 Merge pull request #7756 from esphome/bump-2024.11.0b1
2024.11.0b1
2024-11-13 17:32:32 +13:00
Jesse Hills
a2cab960a9 Bump version to 2024.12.0-dev 2024-11-13 13:49:38 +13:00
luar123
1f7f03f563 Fix temperature and humidity for bme680 with bsec2 (#7728) 2024-11-12 18:18:10 -06:00
Jesse Hills
80226694d5 Bump version to 2024.11.0b1 2024-11-13 13:16:13 +13:00
Kyle Cascade
053465d3f6 Updated dfplayer logging to be more user-friendly (#7740) 2024-11-13 11:54:25 +13:00
TFGF
7d75c9157b [Modbus Controller] Added on_online and on_offline automation (#7417) 2024-11-13 09:48:40 +13:00
Kevin Ahrendt
b367c01b4b [core] Ring buffer write functions use const pointer parameter (#7750) 2024-11-13 07:48:03 +13:00
Edward Firmo
e6a1254e65 [sun] Implements is_above_horizon() (#7754) 2024-11-13 07:23:00 +13:00
FreeBear-nc
1e80c4807e Message to string extend (#7755) 2024-11-13 07:20:48 +13:00
Kevin Ahrendt
928b39f495 [i2s_audio] I2S speaker improvements (#7749) 2024-11-13 07:20:12 +13:00
Oleg Tarasov
58d028ac13 Add OpenTherm component (part 3: rest of the sensors) (#7676)
Co-authored-by: FreeBear <freebear@tuxcnc.org>
Co-authored-by: FreeBear-nc <67865163+FreeBear-nc@users.noreply.github.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-11-12 16:19:42 +13:00
Djordje Mandic
a2dccc4730 [midea] Add temperature validation in do_follow_me method (bugfix) (#7736) 2024-11-11 17:14:01 +13:00
Clyde Stubbs
ffee2f0e88 [lvgl] Implement keypads (#7719) 2024-11-11 16:07:48 +13:00
Clyde Stubbs
d885d65c9b [sensor] Make some values templatable (#7735) 2024-11-11 14:18:05 +13:00
Kyle Cascade
c35240ca32 Remove the choice for MQTT logging if it is disabled (#7723) 2024-11-11 14:13:43 +13:00
Jesse Hills
7c00c5db70 [docker] Bump curl, iputils-ping and libssl-dev (#7748) 2024-11-11 09:44:02 +13:00
Jesse Hills
335faf858b Fix dashboard ip resolving (#7747) 2024-11-11 08:55:19 +13:00
Jesse Hills
1829e68730 Merge branch 'release' into dev 2024-11-08 22:42:27 +13:00
Jesse Hills
b8eadb2ba5 Merge pull request #7732 from esphome/bump-2024.10.3
2024.10.3
2024-11-08 22:41:29 +13:00
Jesse Hills
551ea37882 Bump version to 2024.10.3 2024-11-08 17:02:31 +13:00
Clyde Stubbs
3a25eaca3f [lvgl] Ensure images are configured before using them. (Bugfix) (#7721) 2024-11-08 17:02:31 +13:00
Bonne Eggleston
e85cbf26f8 Fixes modbus timing error (#7674) 2024-11-08 17:02:31 +13:00
Clyde Stubbs
2ec17eed58 [rpi_dpi_rgb] Fix get_width and height (Bugfix) (#7675)
Co-authored-by: clydeps <U5yx99dok9>
2024-11-08 17:02:31 +13:00
David Woodhouse
2f77d31690 OTA: Fix IPv6 and multiple address support (#7414) 2024-11-08 16:38:13 +13:00
dependabot[bot]
3f123d7542 Bump pypa/gh-action-pypi-publish from 1.11.0 to 1.12.2 (#7730)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-08 12:42:36 +13:00
Clyde Stubbs
d189cc1fbe [lvgl] Fix id config for the lvgl component (Bugfix) (#7731)
Co-authored-by: clydeps <U5yx99dok9>
2024-11-07 23:39:01 +00:00
Ramil Valitov
c0658ffe2c [fix] deprecated legacy driver tsens (#7658)
Co-authored-by: luar123 <49960470+luar123@users.noreply.github.com>
2024-11-08 11:10:58 +13:00
Clyde Stubbs
248b0bc378 [lvgl] Allow multiple LVGL instances (#7712)
Co-authored-by: clydeps <U5yx99dok9>
2024-11-08 09:05:23 +13:00
Rodrigo Martín
80b4c26481 feat(MQTT): Add enable, disable and enable_on_boot (#7716) 2024-11-06 13:56:48 +13:00
Clyde Stubbs
5bb4d042e4 [spi_device] rename mode to spi_mode (#7724) 2024-11-05 18:54:47 -06:00
Jesse Hills
dcc537d0d4 [lvgl] Don't just throw key error if someone types a bad layout type (#7722)
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
2024-11-06 08:45:40 +11:00
Clyde Stubbs
2dca3d79e4 [lvgl] Ensure images are configured before using them. (Bugfix) (#7721) 2024-11-05 11:32:18 +11:00
tomaszduda23
01497c891d datetime fix build_language_schema (#7710)
Co-authored-by: Tomasz Duda <tomaszduda23@gmai.com>
2024-11-04 10:22:16 +13:00
Samuel Sieb
77bb46ff3b handle bad pin schemas (#7711)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2024-11-01 20:54:34 +11:00
Jesse Hills
cefbfb75bd [esp32_ble] Add disconnect as a virtual function to `ESPBTClient` (#7705) 2024-10-31 05:46:35 -05:00
Clyde Stubbs
749b942132 [lvlg] fix tests (#7708) 2024-10-31 06:37:32 +00:00
Faidon Liambotis
a043022444 [font] Add support for "glyphsets" (#7429)
Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com>
2024-10-31 16:36:23 +13:00
Clyde Stubbs
8b7e061f3a [touchscreen] Calibration fixes (#7704) 2024-10-31 15:15:39 +13:00
Jesse Hills
74ea1b60e3 [CI] Fix webserver defines to be present based on platform, not just framework (#7703) 2024-10-31 11:37:54 +13:00
Clyde Stubbs
5a2fed3569 [spi] Add mosi pin checks for displays (#7702) 2024-10-30 22:28:18 +00:00
Jason Nagin
e85157db4b Add config for current temperature precision (#7699) 2024-10-31 08:34:33 +13:00
Clyde Stubbs
d3563e4e97 [sdl] Allow window to be resized. (#7698) 2024-10-31 08:30:46 +13:00
Kevin Ahrendt
765579dabb [es8311] Add es8311 dac component (#7693) 2024-10-31 08:29:24 +13:00
dependabot[bot]
6afd004ec5 Bump pypa/gh-action-pypi-publish from 1.10.3 to 1.11.0 (#7700)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-31 08:25:36 +13:00
Clyde Stubbs
ee3ee3a63b [http_request] Implement on_error trigger for requests (#7696) 2024-10-30 14:10:58 +13:00
Jordan Zucker
aae2ee2ecb Add in area and device to the prometheus labels (#7692) 2024-10-30 14:03:10 +13:00
Ilia Sotnikov
bac6880a1e fix: [climate] Allow substitutions in visual.temperature_step.{target_temperature,current_temperature} (#7679) 2024-10-29 16:32:55 -07:00
tomaszduda23
0982ab58ac fix build error (#7694)
Co-authored-by: Tomasz Duda <tomaszduda23@gmai.com>
2024-10-30 07:53:36 +13:00
Samuel Sieb
38dd566e0c remove use of delay (#7680)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-10-29 04:12:54 +00:00
tomaszduda23
71e1e3b5f8 let make new platform implementation in external components (#7615)
Co-authored-by: Tomasz Duda <tomaszduda23@gmai.com>
2024-10-29 16:58:36 +13:00
Rodrigo Martín
abbd7faa64 fix(WiFi): Fix strncpy missing NULL terminator [-Werror=stringop-truncation] (#7668) 2024-10-29 16:56:50 +13:00
Bonne Eggleston
aa0e155e22 Fixes modbus timing error (#7674) 2024-10-29 16:52:39 +13:00
Sean Brogan
0dab280440 Mopeka Pro Check improvement to allow user to configure the sensor reporting for lower quality readings (#7475) 2024-10-29 16:49:06 +13:00
Jordan Zucker
90b076eccd Add more prometheus metrics (#7683) 2024-10-29 16:43:02 +13:00
Jordan Zucker
444c0fc67f Add asdf to gitignore (and dockerignore) (#7686) 2024-10-29 16:09:22 +13:00
Satoshi YAMADA
302ba2874e Support W5500 SPI-Ethernet polling mode if framework is supported (#7503) 2024-10-29 16:08:08 +13:00
Clyde Stubbs
df750d0d11 [http_request] Add enum for status codes (#7690) 2024-10-29 16:05:58 +13:00
Clyde Stubbs
63e4d4b493 [font] Fix failure with bitmap fonts (#7691) 2024-10-29 13:56:32 +11:00
Clyde Stubbs
88627095fb [http_request] Always return defined server response status (#7689) 2024-10-29 11:12:32 +11:00
Clyde Stubbs
858d97ccef [bytebuffer] Rework ByteBuffer using templates (#7638) 2024-10-29 11:08:29 +13:00
Clyde Stubbs
22f30d42a6 [lvgl] Implement qrcode (#7623) 2024-10-29 11:05:51 +13:00
Clyde Stubbs
1e2497748d [rpi_dpi_rgb] Fix get_width and height (Bugfix) (#7675)
Co-authored-by: clydeps <U5yx99dok9>
2024-10-27 15:17:09 +13:00
SeByDocKy
34de2bbe99 gp8403 : Add the possibility to use substitution for channel selection (#7681) 2024-10-26 14:54:57 -07:00
Oleg Tarasov
21cb941bbe Add OpenTherm component (part 2.1: sensor platform) (#7529)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-10-25 15:00:28 +13:00
Clyde Stubbs
33fdbbe30c [image][online_image][animation] Fix transparency in RGB565 (#7631) 2024-10-25 11:05:25 +13:00
dependabot[bot]
09f9d91577 Bump actions/setup-python from 5.2.0 to 5.3.0 (#7670)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-10-25 10:57:09 +13:00
dependabot[bot]
34a8eaddb2 Bump actions/setup-python from 5.2.0 to 5.3.0 in /.github/actions/restore-python (#7671)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-25 10:56:48 +13:00
tomaszduda23
7dbda12008 [code-quality] weikai.h (#7601) 2024-10-25 10:55:58 +13:00
Kevin Ahrendt
4101d5dad1 [media_player] Add new media player conditions (#7667) 2024-10-25 10:26:39 +13:00
tomaszduda23
c20e1975d1 unified way how all platforms handle get_download_types (#7617)
Co-authored-by: Tomasz Duda <tomaszduda23@gmai.com>
2024-10-25 10:25:19 +13:00
Jesse Hills
4fa3c6915c Bump esphome-dashboard to 20241025.0 (#7669) 2024-10-25 08:10:30 +13:00
Jesse Hills
ca5c73d170 Support ignoring discovered devices from the dashboard (#7665) 2024-10-25 07:55:14 +13:00
Aaron Solochek
5b5c2fe71b updating ESP32 board definitions (#7650) 2024-10-24 13:25:53 +13:00
tomaszduda23
9acc21e81a unified way how all platforms handle copy_files (#7614)
Co-authored-by: Tomasz Duda <tomaszduda23@gmai.com>
2024-10-24 10:04:59 +13:00
Kevin Ahrendt
bff0e81ed3 [speaker, i2s_audio] Support audio_dac component, mute actions, and improved logging (#7664)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-10-24 09:37:38 +13:00
dependabot[bot]
2feffddc55 Bump actions/cache from 4.1.1 to 4.1.2 (#7660)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-24 08:06:53 +13:00
dependabot[bot]
4289e00ad0 Bump actions/cache from 4.1.1 to 4.1.2 in /.github/actions/restore-python (#7659)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-24 08:06:45 +13:00
Jesse Hills
574ee404d2 Merge branch 'release' into dev 2024-10-24 08:05:09 +13:00
Jesse Hills
9caf5f8b31 Merge pull request #7663 from esphome/bump-2024.10.2
2024.10.2
2024-10-24 08:04:29 +13:00
Jesse Hills
127acfde64 Bump version to 2024.10.2 2024-10-24 07:15:40 +13:00
Kevin Ahrendt
156ad773c9 [voice_assistant] Bugfix: Fix crash on start (#7662) 2024-10-24 07:15:40 +13:00
Clyde Stubbs
8d90d256bf [lvgl] Some properties were not templatable (Bugfix) (#7655) 2024-10-24 07:15:40 +13:00
Kyle Cascade
833565feb9 Humanized the missing MQTT log topic error message (#7634) 2024-10-24 07:15:40 +13:00
Kevin Ahrendt
fdebf04196 [voice_assistant] Bugfix: Fix crash on start (#7662) 2024-10-24 06:25:31 +13:00
Gábor Poczkodi
dd8d25e43f i2c_device (#7641) 2024-10-23 16:23:10 +13:00
Clyde Stubbs
68844c4869 [lvgl] Some properties were not templatable (Bugfix) (#7655) 2024-10-23 12:16:55 +13:00
Kyle Cascade
7c0543862a Humanized the missing MQTT log topic error message (#7634) 2024-10-22 17:11:23 +13:00
Rodrigo Martín
a932ca2f64 feat(MQTT): Add subscribe QoS to discovery (#7648) 2024-10-22 16:38:25 +13:00
Edward Firmo
2597975ae0 [rtttl] Add get_gain() (#7647) 2024-10-22 16:29:16 +13:00
Clyde Stubbs
6330177d24 [lvgl] Allow strings to be interpreted as integers (Bugfix) (#7652) 2024-10-22 16:10:09 +13:00
Clyde Stubbs
3ac730fb2f [lvgl] Fix rotation code for 90deg (Bugfix) (#7653) 2024-10-22 16:06:58 +13:00
Clyde Stubbs
ff48f53989 [image] Fix compile time problem with host image not using lvgl (#7654) 2024-10-22 16:05:39 +13:00
Clyde Stubbs
8bb4316956 [lvgl] light schema should require widget: not led: (Bugfix) (#7649) 2024-10-22 16:03:32 +13:00
Jesse Hills
40cdb778f5 Merge branch 'release' into dev 2024-10-22 14:00:31 +13:00
Jesse Hills
dfd174e1a5 Merge pull request #7651 from esphome/bump-2024.10.1
2024.10.1
2024-10-22 13:59:49 +13:00
Jesse Hills
735c04cd69 Bump version to 2024.10.1 2024-10-22 12:57:17 +13:00
Michael Hansen
d95b370998 Move setting global voice assistant to constructor (#7630)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-10-22 12:57:17 +13:00
Clyde Stubbs
3ebdd62c67 [lvgl] Remove states from style definitions (Bugfix) (#7645) 2024-10-22 12:57:17 +13:00
Clyde Stubbs
c26c96b8f4 [config] Ensure user-supplied build flags don't get silently overwritten (#7622) 2024-10-22 12:57:17 +13:00
Keith Burzinski
748256b3ee [wifi] Support custom MAC on Arduino, too (#7644) 2024-10-22 12:57:17 +13:00
Samuel Sieb
10791db82e auto-load preferences (#7642)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2024-10-22 12:57:17 +13:00
Lennart
3dd34f6628 Fix broken ibeacon_uuid config in ble_rssi (#7640) 2024-10-22 12:57:17 +13:00
Clyde Stubbs
7004053538 [config] Fix crash with empty substitutions block (#7612) 2024-10-22 12:57:17 +13:00
Michael Hansen
dc42427c60 Move setting global voice assistant to constructor (#7630)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-10-21 23:14:07 +00:00
Clyde Stubbs
40ad6befa8 [lvgl] Remove states from style definitions (Bugfix) (#7645) 2024-10-22 11:51:40 +13:00
Clyde Stubbs
612e2c1644 [lvgl] Defer display rotation reset until setup(). (Bugfix) (#7627) 2024-10-22 11:50:16 +13:00
Clyde Stubbs
c8d0cde329 [config] Ensure user-supplied build flags don't get silently overwritten (#7622) 2024-10-22 11:49:12 +13:00
Keith Burzinski
5e8794175d [wifi] Support custom MAC on Arduino, too (#7644) 2024-10-22 11:46:41 +13:00
Samuel Sieb
657527655d auto-load preferences (#7642)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2024-10-21 13:40:43 +13:00
Jesse Hills
f7543a7b8d Update Pull request template (#7620) 2024-10-21 11:28:52 +13:00
Lennart
43a020641b Fix broken ibeacon_uuid config in ble_rssi (#7640) 2024-10-21 08:16:08 +13:00
Shivam Maurya
c019ff34bc Bump bme68x_bsec2 version to 1.8.2610 (#7626) 2024-10-17 20:15:28 -05:00
guillempages
ef6ccddc0d [lvgl] Allow esphome::Image in lambda to update image source directly (#7624) 2024-10-18 07:23:37 +11:00
Clyde Stubbs
8bbe4efded [lvgl] Revise code generation to allow early widget creation (#7611) 2024-10-17 15:20:19 +13:00
tomaszduda23
f490585f66 [code-quality] udp component (#7602)
Co-authored-by: Tomasz Duda <tomaszduda23@gmai.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-10-17 12:38:02 +11:00
Clyde Stubbs
fcfc76b01b [lvgl] Roller and Dropdown enhancements; (#7608) 2024-10-17 14:03:48 +13:00
Clyde Stubbs
5ad68e926d [axs15231] Touchscreen driver (#7592) 2024-10-17 13:44:20 +13:00
Clyde Stubbs
56fa6fef85 [config] Fix crash with empty substitutions block (#7612) 2024-10-17 13:32:22 +13:00
Ramil Valitov
c9e5919739 [fix] ESP32-C6 BLE compile error (#7580)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-10-17 13:31:02 +13:00
functionpointer
0451b31f9e Bump arduino-mlx90393 to 1.0.2 (#7618) 2024-10-17 13:17:20 +13:00
Kevin Ahrendt
1c845e0ff8 [speaker, i2s_audio] I2S Speaker implementation using a ring buffer (#7605) 2024-10-17 11:47:11 +13:00
Rui Chen
22478ffb0f chore: bump platformio to 6.1.16 to support py3.13 build (#7590) 2024-10-16 17:26:48 +13:00
Rui Chen
c38cc128db chore: bump pyyaml to 6.0.2 to support py3.13 build (#7610) 2024-10-16 17:26:17 +13:00
Paul Blacknell
fa01149771 Add support for Analog Devices MAX17043 battery fuel gauge (#7522)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2024-10-16 16:28:24 +13:00
Clyde Stubbs
254522dd93 [qspi_dbi] Rename from qspi_amoled, add features (#7594)
Co-authored-by: clydeps <U5yx99dok9>
2024-10-16 16:26:50 +13:00
Clyde Stubbs
6a86d92781 [lvgl] Implement better software rotation (#7595) 2024-10-16 16:26:06 +13:00
Ramil Valitov
b274d6901a [fix] deprecated functions warnings for logger component with ESP IDF version 5.3.0+ (#7600) 2024-10-16 16:25:47 +13:00
Aleksandr Artemev
3ef31e55ca [display] filled_ring and filled_gauge methods added (#7420) 2024-10-16 16:25:05 +13:00
Seth Girvan
fb002ac3b0 Add TC74 temperature sensor (#7460)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-10-16 16:24:37 +13:00
Clyde Stubbs
de943908bd [automation] Implement all and any condition shortcuts (#7565) 2024-10-16 16:23:43 +13:00
tomaszduda23
b0a25872da [code-quality] statsd component (#7603)
Co-authored-by: Tomasz Duda <tomaszduda23@gmai.com>
2024-10-16 16:22:45 +13:00
Jesse Hills
403d450f47 Merge branch 'release' into dev 2024-10-16 16:19:08 +13:00
Jesse Hills
d6b96ad51d Merge pull request #7609 from esphome/bump-2024.10.0
2024.10.0
2024-10-16 16:18:27 +13:00
Jesse Hills
9b4b50a3a6 Bump version to 2024.10.0 2024-10-16 14:29:17 +13:00
Ramil Valitov
2cca26ada4 [fix] ESP32-C6: internal temperature reporting (#7579) 2024-10-15 06:59:23 +13:00
tomaszduda23
312799babf Update test_build_components (#7597)
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
2024-10-14 14:31:37 +13:00
Jesse Hills
5bc5a9dcb6 Merge branch 'beta' into dev 2024-10-14 10:58:02 +13:00
Niclas Larsson
39e922580a Fix update sequence when update is set to false (#5225) (#7407) 2024-10-14 09:17:37 +13:00
Clyde Stubbs
77d0bfc4bb [touchscreen] Fix coordinates when using rotation (#7591) 2024-10-14 09:10:48 +13:00
RFDarter
654cee6f83 [web_server] expose event compoent to REST (#7587) 2024-10-14 07:50:22 +13:00
RFDarter
cf14c02b8a [web_server] Event component grouping (#7586) 2024-10-14 07:50:13 +13:00
Pietro
42f6095960 [core][esp32_rmt_led_strip] Migrate ExternalRAMAllocator to RAMAllocator
And add psram flag to esp32_rmt_led_strip
Co-authored-by: guillempages <guillempages@users.noreply.github.com>
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
2024-10-13 20:24:17 +11:00
Clyde Stubbs
f224984858 [CI] failures when installing using apt-get. (#7593) 2024-10-12 18:51:51 -05:00
Jesse Hills
efe4c5e3bc [light] Add `initial_state` configuration (#7577) 2024-10-11 13:13:32 -04:00
Ramil Valitov
cedb671f07 [fix] ESP32-C6 Reset Reasons (#7578) 2024-10-11 07:51:21 +13:00
dependabot[bot]
c18bd3ac81 Bump actions/upload-artifact from 4.4.2 to 4.4.3 (#7575)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-10 16:07:40 +13:00
Ilia Sotnikov
b08432bd0d Update pillow to 10.4.0 (#7566) 2024-10-10 13:44:07 +11:00
Samuel Sieb
4bac9707fe fix uart settings check (#7573) 2024-10-09 05:44:19 -05:00
Jesse Hills
7e16cda949 Merge branch 'beta' into dev 2024-10-09 17:48:32 +13:00
Jesse Hills
1c05f5af03 Bump version to 2024.11.0-dev 2024-10-09 15:01:49 +13:00
3059 changed files with 64904 additions and 51708 deletions

View File

@@ -7,28 +7,39 @@ Checks: >-
-boost-*, -boost-*,
-bugprone-easily-swappable-parameters, -bugprone-easily-swappable-parameters,
-bugprone-implicit-widening-of-multiplication-result, -bugprone-implicit-widening-of-multiplication-result,
-bugprone-multi-level-implicit-pointer-conversion,
-bugprone-narrowing-conversions, -bugprone-narrowing-conversions,
-bugprone-signed-char-misuse, -bugprone-signed-char-misuse,
-bugprone-switch-missing-default-case,
-cert-dcl50-cpp, -cert-dcl50-cpp,
-cert-err33-c, -cert-err33-c,
-cert-err58-cpp, -cert-err58-cpp,
-cert-oop57-cpp, -cert-oop57-cpp,
-cert-str34-c, -cert-str34-c,
-clang-analyzer-optin.core.EnumCastOutOfRange,
-clang-analyzer-optin.cplusplus.UninitializedObject, -clang-analyzer-optin.cplusplus.UninitializedObject,
-clang-analyzer-osx.*, -clang-analyzer-osx.*,
-clang-diagnostic-delete-abstract-non-virtual-dtor, -clang-diagnostic-delete-abstract-non-virtual-dtor,
-clang-diagnostic-delete-non-abstract-non-virtual-dtor, -clang-diagnostic-delete-non-abstract-non-virtual-dtor,
-clang-diagnostic-deprecated-declarations,
-clang-diagnostic-ignored-optimization-argument, -clang-diagnostic-ignored-optimization-argument,
-clang-diagnostic-missing-field-initializers,
-clang-diagnostic-shadow-field, -clang-diagnostic-shadow-field,
-clang-diagnostic-unused-const-variable, -clang-diagnostic-unused-const-variable,
-clang-diagnostic-unused-parameter, -clang-diagnostic-unused-parameter,
-clang-diagnostic-vla-cxx-extension,
-concurrency-*, -concurrency-*,
-cppcoreguidelines-avoid-c-arrays, -cppcoreguidelines-avoid-c-arrays,
-cppcoreguidelines-avoid-const-or-ref-data-members,
-cppcoreguidelines-avoid-do-while,
-cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-init-variables, -cppcoreguidelines-init-variables,
-cppcoreguidelines-macro-to-enum,
-cppcoreguidelines-macro-usage, -cppcoreguidelines-macro-usage,
-cppcoreguidelines-missing-std-forward,
-cppcoreguidelines-narrowing-conversions, -cppcoreguidelines-narrowing-conversions,
-cppcoreguidelines-non-private-member-variables-in-classes, -cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-owning-memory,
-cppcoreguidelines-prefer-member-initializer, -cppcoreguidelines-prefer-member-initializer,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay, -cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-pro-bounds-constant-array-index, -cppcoreguidelines-pro-bounds-constant-array-index,
@@ -40,7 +51,9 @@ Checks: >-
-cppcoreguidelines-pro-type-static-cast-downcast, -cppcoreguidelines-pro-type-static-cast-downcast,
-cppcoreguidelines-pro-type-union-access, -cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-rvalue-reference-param-not-moved,
-cppcoreguidelines-special-member-functions, -cppcoreguidelines-special-member-functions,
-cppcoreguidelines-use-default-member-init,
-cppcoreguidelines-virtual-class-destructor, -cppcoreguidelines-virtual-class-destructor,
-fuchsia-multiple-inheritance, -fuchsia-multiple-inheritance,
-fuchsia-overloaded-operator, -fuchsia-overloaded-operator,
@@ -60,20 +73,32 @@ Checks: >-
-llvm-include-order, -llvm-include-order,
-llvm-qualified-auto, -llvm-qualified-auto,
-llvmlibc-*, -llvmlibc-*,
-misc-non-private-member-variables-in-classes, -misc-const-correctness,
-misc-include-cleaner,
-misc-no-recursion, -misc-no-recursion,
-misc-non-private-member-variables-in-classes,
-misc-unused-parameters, -misc-unused-parameters,
-modernize-avoid-c-arrays, -misc-use-anonymous-namespace,
-modernize-avoid-bind, -modernize-avoid-bind,
-modernize-avoid-c-arrays,
-modernize-concat-nested-namespaces, -modernize-concat-nested-namespaces,
-modernize-macro-to-enum,
-modernize-return-braced-init-list, -modernize-return-braced-init-list,
-modernize-type-traits,
-modernize-use-auto, -modernize-use-auto,
-modernize-use-constraints,
-modernize-use-default-member-init, -modernize-use-default-member-init,
-modernize-use-equals-default, -modernize-use-equals-default,
-modernize-use-trailing-return-type,
-modernize-use-nodiscard, -modernize-use-nodiscard,
-modernize-use-nullptr,
-modernize-use-nodiscard,
-modernize-use-nullptr,
-modernize-use-trailing-return-type,
-mpi-*, -mpi-*,
-objc-*, -objc-*,
-performance-enum-size,
-readability-avoid-nested-conditional-operator,
-readability-container-contains,
-readability-container-data-pointer, -readability-container-data-pointer,
-readability-convert-member-functions-to-static, -readability-convert-member-functions-to-static,
-readability-else-after-return, -readability-else-after-return,
@@ -82,11 +107,14 @@ Checks: >-
-readability-isolate-declaration, -readability-isolate-declaration,
-readability-magic-numbers, -readability-magic-numbers,
-readability-make-member-function-const, -readability-make-member-function-const,
-readability-named-parameter,
-readability-redundant-casting,
-readability-redundant-inline-specifier,
-readability-redundant-member-init,
-readability-redundant-string-init, -readability-redundant-string-init,
-readability-uppercase-literal-suffix, -readability-uppercase-literal-suffix,
-readability-use-anyofallof, -readability-use-anyofallof,
WarningsAsErrors: '*' WarningsAsErrors: '*'
AnalyzeTemporaryDtors: false
FormatStyle: google FormatStyle: google
CheckOptions: CheckOptions:
- key: google-readability-function-size.StatementThreshold - key: google-readability-function-size.StatementThreshold

View File

@@ -31,7 +31,7 @@
"ms-python.python", "ms-python.python",
"ms-python.pylint", "ms-python.pylint",
"ms-python.flake8", "ms-python.flake8",
"ms-python.black-formatter", "charliermarsh.ruff",
"visualstudioexptteam.vscodeintellicode", "visualstudioexptteam.vscodeintellicode",
// yaml // yaml
"redhat.vscode-yaml", "redhat.vscode-yaml",
@@ -49,14 +49,11 @@
"flake8.args": [ "flake8.args": [
"--config=${workspaceFolder}/.flake8" "--config=${workspaceFolder}/.flake8"
], ],
"black-formatter.args": [ "ruff.configuration": "${workspaceFolder}/pyproject.toml",
"--config",
"${workspaceFolder}/pyproject.toml"
],
"[python]": { "[python]": {
// VS will say "Value is not accepted" before building the devcontainer, but the warning // VS will say "Value is not accepted" before building the devcontainer, but the warning
// should go away after build is completed. // should go away after build is completed.
"editor.defaultFormatter": "ms-python.black-formatter" "editor.defaultFormatter": "charliermarsh.ruff"
}, },
"editor.formatOnPaste": false, "editor.formatOnPaste": false,
"editor.formatOnSave": true, "editor.formatOnSave": true,

View File

@@ -75,6 +75,9 @@ target/
# pyenv # pyenv
.python-version .python-version
# asdf
.tool-versions
# celery beat schedule file # celery beat schedule file
celerybeat-schedule celerybeat-schedule

View File

@@ -7,11 +7,16 @@
- [ ] Bugfix (non-breaking change which fixes an issue) - [ ] Bugfix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality) - [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Code quality improvements to existing code or addition of tests
- [ ] Other - [ ] Other
**Related issue or feature (if applicable):** fixes <link to issue> **Related issue or feature (if applicable):**
**Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** esphome/esphome-docs#<esphome-docs PR number goes here> - fixes <link to issue>
**Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):**
- esphome/esphome-docs#<esphome-docs PR number goes here>
## Test Environment ## Test Environment
@@ -23,12 +28,6 @@
- [ ] RTL87xx - [ ] RTL87xx
## Example entry for `config.yaml`: ## Example entry for `config.yaml`:
<!--
Supplying a configuration snippet, makes it easier for a maintainer to test
your PR. Furthermore, for new integrations, it gives an impression of how
the configuration would look like.
Note: Remove this section if this PR does not have an example entry.
-->
```yaml ```yaml
# Example config.yaml # Example config.yaml

View File

@@ -46,7 +46,7 @@ runs:
- name: Build and push to ghcr by digest - name: Build and push to ghcr by digest
id: build-ghcr id: build-ghcr
uses: docker/build-push-action@v6.9.0 uses: docker/build-push-action@v6.15.0
env: env:
DOCKER_BUILD_SUMMARY: false DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false DOCKER_BUILD_RECORD_UPLOAD: false
@@ -72,7 +72,7 @@ runs:
- name: Build and push to dockerhub by digest - name: Build and push to dockerhub by digest
id: build-dockerhub id: build-dockerhub
uses: docker/build-push-action@v6.9.0 uses: docker/build-push-action@v6.15.0
env: env:
DOCKER_BUILD_SUMMARY: false DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false DOCKER_BUILD_RECORD_UPLOAD: false

View File

@@ -17,12 +17,12 @@ runs:
steps: steps:
- name: Set up Python ${{ inputs.python-version }} - name: Set up Python ${{ inputs.python-version }}
id: python id: python
uses: actions/setup-python@v5.2.0 uses: actions/setup-python@v5.4.0
with: with:
python-version: ${{ inputs.python-version }} python-version: ${{ inputs.python-version }}
- name: Restore Python virtual environment - name: Restore Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache/restore@v4.1.1 uses: actions/cache/restore@v4.2.2
with: with:
path: venv path: venv
# yamllint disable-line rule:line-length # yamllint disable-line rule:line-length

View File

@@ -23,7 +23,7 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.7 uses: actions/checkout@v4.1.7
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5.2.0 uses: actions/setup-python@v5.4.0
with: with:
python-version: "3.11" python-version: "3.11"

View File

@@ -33,22 +33,20 @@ concurrency:
jobs: jobs:
check-docker: check-docker:
name: Build docker containers name: Build docker containers
runs-on: ubuntu-latest runs-on: ${{ matrix.os }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
arch: [amd64, armv7, aarch64] os: ["ubuntu-latest", "ubuntu-24.04-arm"]
build_type: ["ha-addon", "docker", "lint"] build_type: ["ha-addon", "docker", "lint"]
steps: steps:
- uses: actions/checkout@v4.1.7 - uses: actions/checkout@v4.1.7
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5.2.0 uses: actions/setup-python@v5.4.0
with: with:
python-version: "3.9" python-version: "3.9"
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.7.1 uses: docker/setup-buildx-action@v3.10.0
- name: Set up QEMU
uses: docker/setup-qemu-action@v3.2.0
- name: Set TAG - name: Set TAG
run: | run: |
@@ -58,6 +56,6 @@ jobs:
run: | run: |
docker/build.py \ docker/build.py \
--tag "${TAG}" \ --tag "${TAG}" \
--arch "${{ matrix.arch }}" \ --arch "${{ matrix.os == 'ubuntu-24.04-arm' && 'aarch64' || 'amd64' }}" \
--build-type "${{ matrix.build_type }}" \ --build-type "${{ matrix.build_type }}" \
build build

View File

@@ -13,6 +13,7 @@ on:
- ".github/workflows/ci.yml" - ".github/workflows/ci.yml"
- "!.yamllint" - "!.yamllint"
- "!.github/dependabot.yml" - "!.github/dependabot.yml"
- "!docker/**"
merge_group: merge_group:
permissions: permissions:
@@ -30,7 +31,7 @@ concurrency:
jobs: jobs:
common: common:
name: Create common environment name: Create common environment
runs-on: ubuntu-latest runs-on: ubuntu-24.04
outputs: outputs:
cache-key: ${{ steps.cache-key.outputs.key }} cache-key: ${{ steps.cache-key.outputs.key }}
steps: steps:
@@ -41,12 +42,12 @@ jobs:
run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python id: python
uses: actions/setup-python@v5.2.0 uses: actions/setup-python@v5.4.0
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment - name: Restore Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v4.1.1 uses: actions/cache@v4.2.2
with: with:
path: venv path: venv
# yamllint disable-line rule:line-length # yamllint disable-line rule:line-length
@@ -60,9 +61,9 @@ jobs:
pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
pip install -e . pip install -e .
black: ruff:
name: Check black name: Check ruff
runs-on: ubuntu-latest runs-on: ubuntu-24.04
needs: needs:
- common - common
steps: steps:
@@ -73,17 +74,17 @@ jobs:
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }} cache-key: ${{ needs.common.outputs.cache-key }}
- name: Run black - name: Run Ruff
run: | run: |
. venv/bin/activate . venv/bin/activate
black --verbose esphome tests ruff format esphome tests
- name: Suggested changes - name: Suggested changes
run: script/ci-suggest-changes run: script/ci-suggest-changes
if: always() if: always()
flake8: flake8:
name: Check flake8 name: Check flake8
runs-on: ubuntu-latest runs-on: ubuntu-24.04
needs: needs:
- common - common
steps: steps:
@@ -104,7 +105,7 @@ jobs:
pylint: pylint:
name: Check pylint name: Check pylint
runs-on: ubuntu-latest runs-on: ubuntu-24.04
needs: needs:
- common - common
steps: steps:
@@ -125,7 +126,7 @@ jobs:
pyupgrade: pyupgrade:
name: Check pyupgrade name: Check pyupgrade
runs-on: ubuntu-latest runs-on: ubuntu-24.04
needs: needs:
- common - common
steps: steps:
@@ -146,7 +147,7 @@ jobs:
ci-custom: ci-custom:
name: Run script/ci-custom name: Run script/ci-custom
runs-on: ubuntu-latest runs-on: ubuntu-24.04
needs: needs:
- common - common
steps: steps:
@@ -219,13 +220,13 @@ jobs:
. venv/bin/activate . venv/bin/activate
pytest -vv --cov-report=xml --tb=native tests pytest -vv --cov-report=xml --tb=native tests
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
uses: codecov/codecov-action@v4 uses: codecov/codecov-action@v5
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
clang-format: clang-format:
name: Check clang-format name: Check clang-format
runs-on: ubuntu-latest runs-on: ubuntu-24.04
needs: needs:
- common - common
steps: steps:
@@ -251,10 +252,10 @@ jobs:
clang-tidy: clang-tidy:
name: ${{ matrix.name }} name: ${{ matrix.name }}
runs-on: ubuntu-latest runs-on: ubuntu-24.04
needs: needs:
- common - common
- black - ruff
- ci-custom - ci-custom
- clang-format - clang-format
- flake8 - flake8
@@ -302,23 +303,18 @@ jobs:
- name: Cache platformio - name: Cache platformio
if: github.ref == 'refs/heads/dev' if: github.ref == 'refs/heads/dev'
uses: actions/cache@v4.1.1 uses: actions/cache@v4.2.2
with: with:
path: ~/.platformio path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }} key: platformio-${{ matrix.pio_cache_key }}
- name: Cache platformio - name: Cache platformio
if: github.ref != 'refs/heads/dev' if: github.ref != 'refs/heads/dev'
uses: actions/cache/restore@v4.1.1 uses: actions/cache/restore@v4.2.2
with: with:
path: ~/.platformio path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }} key: platformio-${{ matrix.pio_cache_key }}
- name: Install clang-tidy
run: |
sudo apt-get update
sudo apt-get install clang-tidy-14
- name: Register problem matchers - name: Register problem matchers
run: | run: |
echo "::add-matcher::.github/workflows/matchers/gcc.json" echo "::add-matcher::.github/workflows/matchers/gcc.json"
@@ -345,7 +341,7 @@ jobs:
if: always() if: always()
list-components: list-components:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
needs: needs:
- common - common
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'
@@ -387,7 +383,7 @@ jobs:
test-build-components: test-build-components:
name: Component test ${{ matrix.file }} name: Component test ${{ matrix.file }}
runs-on: ubuntu-latest runs-on: ubuntu-24.04
needs: needs:
- common - common
- list-components - list-components
@@ -421,7 +417,7 @@ jobs:
test-build-components-splitter: test-build-components-splitter:
name: Split components for testing into 20 groups maximum name: Split components for testing into 20 groups maximum
runs-on: ubuntu-latest runs-on: ubuntu-24.04
needs: needs:
- common - common
- list-components - list-components
@@ -439,7 +435,7 @@ jobs:
test-build-components-split: test-build-components-split:
name: Test split components name: Test split components
runs-on: ubuntu-latest runs-on: ubuntu-24.04
needs: needs:
- common - common
- list-components - list-components
@@ -483,10 +479,10 @@ jobs:
ci-status: ci-status:
name: CI Status name: CI Status
runs-on: ubuntu-latest runs-on: ubuntu-24.04
needs: needs:
- common - common
- black - ruff
- ci-custom - ci-custom
- clang-format - clang-format
- flake8 - flake8

View File

@@ -1,11 +1,11 @@
{ {
"problemMatcher": [ "problemMatcher": [
{ {
"owner": "black", "owner": "ruff",
"severity": "error", "severity": "error",
"pattern": [ "pattern": [
{ {
"regexp": "^(.*): (Please format this file with the black formatter)", "regexp": "^(.*): (Please format this file with the ruff formatter)",
"file": 1, "file": 1,
"message": 2 "message": 2
} }

View File

@@ -53,7 +53,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4.1.7 - uses: actions/checkout@v4.1.7
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5.2.0 uses: actions/setup-python@v5.4.0
with: with:
python-version: "3.x" python-version: "3.x"
- name: Set up python environment - name: Set up python environment
@@ -65,7 +65,7 @@ jobs:
pip3 install build pip3 install build
python3 -m build python3 -m build
- name: Publish - name: Publish
uses: pypa/gh-action-pypi-publish@v1.10.3 uses: pypa/gh-action-pypi-publish@v1.12.4
deploy-docker: deploy-docker:
name: Build ESPHome ${{ matrix.platform }} name: Build ESPHome ${{ matrix.platform }}
@@ -80,20 +80,19 @@ jobs:
matrix: matrix:
platform: platform:
- linux/amd64 - linux/amd64
- linux/arm/v7
- linux/arm64 - linux/arm64
steps: steps:
- uses: actions/checkout@v4.1.7 - uses: actions/checkout@v4.1.7
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5.2.0 uses: actions/setup-python@v5.4.0
with: with:
python-version: "3.9" python-version: "3.9"
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.7.1 uses: docker/setup-buildx-action@v3.10.0
- name: Set up QEMU - name: Set up QEMU
if: matrix.platform != 'linux/amd64' if: matrix.platform != 'linux/amd64'
uses: docker/setup-qemu-action@v3.2.0 uses: docker/setup-qemu-action@v3.6.0
- name: Log in to docker hub - name: Log in to docker hub
uses: docker/login-action@v3.3.0 uses: docker/login-action@v3.3.0
@@ -141,7 +140,7 @@ jobs:
echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT
- name: Upload digests - name: Upload digests
uses: actions/upload-artifact@v4.4.2 uses: actions/upload-artifact@v4.6.1
with: with:
name: digests-${{ steps.sanitize.outputs.name }} name: digests-${{ steps.sanitize.outputs.name }}
path: /tmp/digests path: /tmp/digests
@@ -177,14 +176,14 @@ jobs:
- uses: actions/checkout@v4.1.7 - uses: actions/checkout@v4.1.7
- name: Download digests - name: Download digests
uses: actions/download-artifact@v4.1.8 uses: actions/download-artifact@v4.1.9
with: with:
pattern: digests-* pattern: digests-*
path: /tmp/digests path: /tmp/digests
merge-multiple: true merge-multiple: true
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.7.1 uses: docker/setup-buildx-action@v3.10.0
- name: Log in to docker hub - name: Log in to docker hub
if: matrix.registry == 'dockerhub' if: matrix.registry == 'dockerhub'

View File

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

View File

@@ -22,7 +22,7 @@ jobs:
path: lib/home-assistant path: lib/home-assistant
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v5.2.0 uses: actions/setup-python@v5.4.0
with: with:
python-version: 3.12 python-version: 3.12
@@ -36,7 +36,7 @@ jobs:
python ./script/sync-device_class.py python ./script/sync-device_class.py
- name: Commit changes - name: Commit changes
uses: peter-evans/create-pull-request@v7.0.5 uses: peter-evans/create-pull-request@v7.0.7
with: with:
commit-message: "Synchronise Device Classes from Home Assistant" commit-message: "Synchronise Device Classes from Home Assistant"
committer: esphomebot <esphome@nabucasa.com> committer: esphomebot <esphome@nabucasa.com>

3
.gitignore vendored
View File

@@ -75,6 +75,9 @@ cov.xml
# pyenv # pyenv
.python-version .python-version
# asdf
.tool-versions
# Environments # Environments
.env .env
.venv .venv

View File

@@ -4,21 +4,13 @@
repos: repos:
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version. # Ruff version.
rev: v0.5.4 rev: v0.9.2
hooks: hooks:
# Run the linter. # Run the linter.
- id: ruff - id: ruff
args: [--fix] args: [--fix]
# Run the formatter. # Run the formatter.
- id: ruff-format - id: ruff-format
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 24.4.2
hooks:
- id: black
args:
- --safe
- --quiet
files: ^((esphome|script|tests)/.+)?[^/]+\.py$
- repo: https://github.com/PyCQA/flake8 - repo: https://github.com/PyCQA/flake8
rev: 6.1.0 rev: 6.1.0
hooks: hooks:
@@ -53,6 +45,6 @@ repos:
hooks: hooks:
- id: pylint - id: pylint
name: pylint name: pylint
entry: script/run-in-env.sh pylint entry: python3 script/run-in-env.py pylint
language: script language: system
types: [python] types: [python]

View File

@@ -48,7 +48,10 @@ esphome/components/at581x/* @X-Ryl669
esphome/components/atc_mithermometer/* @ahpohl esphome/components/atc_mithermometer/* @ahpohl
esphome/components/atm90e26/* @danieltwagner esphome/components/atm90e26/* @danieltwagner
esphome/components/atm90e32/* @circuitsetup @descipher esphome/components/atm90e32/* @circuitsetup @descipher
esphome/components/audio/* @kahrendt
esphome/components/audio_adc/* @kbx81
esphome/components/audio_dac/* @kbx81 esphome/components/audio_dac/* @kbx81
esphome/components/axs15231/* @clydebarrow
esphome/components/b_parasite/* @rbaron esphome/components/b_parasite/* @rbaron
esphome/components/ballu/* @bazuchan esphome/components/ballu/* @bazuchan
esphome/components/bang_bang/* @OttoWinter esphome/components/bang_bang/* @OttoWinter
@@ -83,12 +86,14 @@ esphome/components/bmp581/* @kahrendt
esphome/components/bp1658cj/* @Cossid esphome/components/bp1658cj/* @Cossid
esphome/components/bp5758d/* @Cossid esphome/components/bp5758d/* @Cossid
esphome/components/button/* @esphome/core esphome/components/button/* @esphome/core
esphome/components/bytebuffer/* @clydebarrow
esphome/components/canbus/* @danielschramm @mvturnho esphome/components/canbus/* @danielschramm @mvturnho
esphome/components/cap1188/* @mreditor97 esphome/components/cap1188/* @mreditor97
esphome/components/captive_portal/* @OttoWinter esphome/components/captive_portal/* @OttoWinter
esphome/components/ccs811/* @habbie esphome/components/ccs811/* @habbie
esphome/components/cd74hc4067/* @asoehlke esphome/components/cd74hc4067/* @asoehlke
esphome/components/ch422g/* @clydebarrow @jesterret esphome/components/ch422g/* @clydebarrow @jesterret
esphome/components/chsc6x/* @kkosik20
esphome/components/climate/* @esphome/core esphome/components/climate/* @esphome/core
esphome/components/climate_ir/* @glmnet esphome/components/climate_ir/* @glmnet
esphome/components/color_temperature/* @jesserockz esphome/components/color_temperature/* @jesserockz
@@ -128,6 +133,10 @@ esphome/components/ens160_base/* @latonita @vincentscode
esphome/components/ens160_i2c/* @latonita esphome/components/ens160_i2c/* @latonita
esphome/components/ens160_spi/* @latonita esphome/components/ens160_spi/* @latonita
esphome/components/ens210/* @itn3rd77 esphome/components/ens210/* @itn3rd77
esphome/components/es7210/* @kahrendt
esphome/components/es7243e/* @kbx81
esphome/components/es8156/* @kbx81
esphome/components/es8311/* @kahrendt @kroimon
esphome/components/esp32/* @esphome/core esphome/components/esp32/* @esphome/core
esphome/components/esp32_ble/* @Rapsssito @jesserockz esphome/components/esp32_ble/* @Rapsssito @jesserockz
esphome/components/esp32_ble_client/* @jesserockz esphome/components/esp32_ble_client/* @jesserockz
@@ -140,6 +149,7 @@ esphome/components/esp32_rmt_led_strip/* @jesserockz
esphome/components/esp8266/* @esphome/core esphome/components/esp8266/* @esphome/core
esphome/components/ethernet_info/* @gtjadsonsantos esphome/components/ethernet_info/* @gtjadsonsantos
esphome/components/event/* @nohat esphome/components/event/* @nohat
esphome/components/event_emitter/* @Rapsssito
esphome/components/exposure_notifications/* @OttoWinter esphome/components/exposure_notifications/* @OttoWinter
esphome/components/ezo/* @ssieb esphome/components/ezo/* @ssieb
esphome/components/ezo_pmp/* @carlos-sarmiento esphome/components/ezo_pmp/* @carlos-sarmiento
@@ -175,6 +185,7 @@ esphome/components/haier/text_sensor/* @paveldn
esphome/components/havells_solar/* @sourabhjaiswal esphome/components/havells_solar/* @sourabhjaiswal
esphome/components/hbridge/fan/* @WeekendWarrior esphome/components/hbridge/fan/* @WeekendWarrior
esphome/components/hbridge/light/* @DotNetDann esphome/components/hbridge/light/* @DotNetDann
esphome/components/hbridge/switch/* @dwmw2
esphome/components/he60r/* @clydebarrow esphome/components/he60r/* @clydebarrow
esphome/components/heatpumpir/* @rob-deutsch esphome/components/heatpumpir/* @rob-deutsch
esphome/components/hitachi_ac424/* @sourabhjaiswal esphome/components/hitachi_ac424/* @sourabhjaiswal
@@ -196,10 +207,11 @@ esphome/components/htu31d/* @betterengineering
esphome/components/hydreon_rgxx/* @functionpointer esphome/components/hydreon_rgxx/* @functionpointer
esphome/components/hyt271/* @Philippe12 esphome/components/hyt271/* @Philippe12
esphome/components/i2c/* @esphome/core esphome/components/i2c/* @esphome/core
esphome/components/i2c_device/* @gabest11
esphome/components/i2s_audio/* @jesserockz esphome/components/i2s_audio/* @jesserockz
esphome/components/i2s_audio/media_player/* @jesserockz esphome/components/i2s_audio/media_player/* @jesserockz
esphome/components/i2s_audio/microphone/* @jesserockz esphome/components/i2s_audio/microphone/* @jesserockz
esphome/components/i2s_audio/speaker/* @jesserockz esphome/components/i2s_audio/speaker/* @jesserockz @kahrendt
esphome/components/iaqcore/* @yozik04 esphome/components/iaqcore/* @yozik04
esphome/components/ili9xxx/* @clydebarrow @nielsnl68 esphome/components/ili9xxx/* @clydebarrow @nielsnl68
esphome/components/improv_base/* @esphome/core esphome/components/improv_base/* @esphome/core
@@ -223,6 +235,7 @@ esphome/components/kuntze/* @ssieb
esphome/components/lcd_menu/* @numo68 esphome/components/lcd_menu/* @numo68
esphome/components/ld2410/* @regevbr @sebcaps esphome/components/ld2410/* @regevbr @sebcaps
esphome/components/ld2420/* @descipher esphome/components/ld2420/* @descipher
esphome/components/ld2450/* @hareeshmu
esphome/components/ledc/* @OttoWinter esphome/components/ledc/* @OttoWinter
esphome/components/libretiny/* @kuba2k2 esphome/components/libretiny/* @kuba2k2
esphome/components/libretiny_pwm/* @kuba2k2 esphome/components/libretiny_pwm/* @kuba2k2
@@ -231,12 +244,14 @@ esphome/components/lightwaverf/* @max246
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
esphome/components/lock/* @esphome/core esphome/components/lock/* @esphome/core
esphome/components/logger/* @esphome/core esphome/components/logger/* @esphome/core
esphome/components/logger/select/* @clydebarrow
esphome/components/ltr390/* @latonita @sjtrny esphome/components/ltr390/* @latonita @sjtrny
esphome/components/ltr501/* @latonita esphome/components/ltr501/* @latonita
esphome/components/ltr_als_ps/* @latonita esphome/components/ltr_als_ps/* @latonita
esphome/components/lvgl/* @clydebarrow esphome/components/lvgl/* @clydebarrow
esphome/components/m5stack_8angle/* @rnauber esphome/components/m5stack_8angle/* @rnauber
esphome/components/matrix_keypad/* @ssieb esphome/components/matrix_keypad/* @ssieb
esphome/components/max17043/* @blacknell
esphome/components/max31865/* @DAVe3283 esphome/components/max31865/* @DAVe3283
esphome/components/max44009/* @berfenger esphome/components/max44009/* @berfenger
esphome/components/max6956/* @looping40 esphome/components/max6956/* @looping40
@@ -265,6 +280,7 @@ esphome/components/mics_4514/* @jesserockz
esphome/components/midea/* @dudanov esphome/components/midea/* @dudanov
esphome/components/midea_ir/* @dudanov esphome/components/midea_ir/* @dudanov
esphome/components/mitsubishi/* @RubyBailey esphome/components/mitsubishi/* @RubyBailey
esphome/components/mixer/speaker/* @kahrendt
esphome/components/mlx90393/* @functionpointer esphome/components/mlx90393/* @functionpointer
esphome/components/mlx90614/* @jesserockz esphome/components/mlx90614/* @jesserockz
esphome/components/mmc5603/* @benhoff esphome/components/mmc5603/* @benhoff
@@ -283,6 +299,7 @@ esphome/components/mopeka_std_check/* @Fabian-Schmidt
esphome/components/mpl3115a2/* @kbickar esphome/components/mpl3115a2/* @kbickar
esphome/components/mpu6886/* @fabaff esphome/components/mpu6886/* @fabaff
esphome/components/ms8607/* @e28eta esphome/components/ms8607/* @e28eta
esphome/components/msa3xx/* @latonita
esphome/components/nau7802/* @cujomalainey esphome/components/nau7802/* @cujomalainey
esphome/components/network/* @esphome/core esphome/components/network/* @esphome/core
esphome/components/nextion/* @edwardtfn @senexcrenshaw esphome/components/nextion/* @edwardtfn @senexcrenshaw
@@ -295,7 +312,7 @@ esphome/components/noblex/* @AGalfra
esphome/components/npi19/* @bakerkj esphome/components/npi19/* @bakerkj
esphome/components/number/* @esphome/core esphome/components/number/* @esphome/core
esphome/components/one_wire/* @ssieb esphome/components/one_wire/* @ssieb
esphome/components/online_image/* @guillempages esphome/components/online_image/* @clydebarrow @guillempages
esphome/components/opentherm/* @olegtarasov esphome/components/opentherm/* @olegtarasov
esphome/components/ota/* @esphome/core esphome/components/ota/* @esphome/core
esphome/components/output/* @esphome/core esphome/components/output/* @esphome/core
@@ -324,14 +341,14 @@ esphome/components/pvvx_mithermometer/* @pasiz
esphome/components/pylontech/* @functionpointer esphome/components/pylontech/* @functionpointer
esphome/components/qmp6988/* @andrewpc esphome/components/qmp6988/* @andrewpc
esphome/components/qr_code/* @wjtje esphome/components/qr_code/* @wjtje
esphome/components/qspi_amoled/* @clydebarrow esphome/components/qspi_dbi/* @clydebarrow
esphome/components/qwiic_pir/* @kahrendt esphome/components/qwiic_pir/* @kahrendt
esphome/components/radon_eye_ble/* @jeffeb3 esphome/components/radon_eye_ble/* @jeffeb3
esphome/components/radon_eye_rd200/* @jeffeb3 esphome/components/radon_eye_rd200/* @jeffeb3
esphome/components/rc522/* @glmnet esphome/components/rc522/* @glmnet
esphome/components/rc522_i2c/* @glmnet esphome/components/rc522_i2c/* @glmnet
esphome/components/rc522_spi/* @glmnet esphome/components/rc522_spi/* @glmnet
esphome/components/resistance_sampler/* @jesserockz esphome/components/resampler/speaker/* @kahrendt
esphome/components/restart/* @esphome/core esphome/components/restart/* @esphome/core
esphome/components/rf_bridge/* @jesserockz esphome/components/rf_bridge/* @jesserockz
esphome/components/rgbct/* @jesserockz esphome/components/rgbct/* @jesserockz
@@ -344,10 +361,12 @@ esphome/components/rtttl/* @glmnet
esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti
esphome/components/scd4x/* @martgras @sjtrny esphome/components/scd4x/* @martgras @sjtrny
esphome/components/script/* @esphome/core esphome/components/script/* @esphome/core
esphome/components/sdl/* @clydebarrow esphome/components/sdl/* @bdm310 @clydebarrow
esphome/components/sdm_meter/* @jesserockz @polyfaces esphome/components/sdm_meter/* @jesserockz @polyfaces
esphome/components/sdp3x/* @Azimath esphome/components/sdp3x/* @Azimath
esphome/components/seeed_mr24hpc1/* @limengdu esphome/components/seeed_mr24hpc1/* @limengdu
esphome/components/seeed_mr60bha2/* @limengdu
esphome/components/seeed_mr60fda2/* @limengdu
esphome/components/selec_meter/* @sourabhjaiswal esphome/components/selec_meter/* @sourabhjaiswal
esphome/components/select/* @esphome/core esphome/components/select/* @esphome/core
esphome/components/sen0321/* @notjj esphome/components/sen0321/* @notjj
@@ -373,7 +392,8 @@ esphome/components/smt100/* @piechade
esphome/components/sn74hc165/* @jesserockz esphome/components/sn74hc165/* @jesserockz
esphome/components/socket/* @esphome/core esphome/components/socket/* @esphome/core
esphome/components/sonoff_d1/* @anatoly-savchenkov esphome/components/sonoff_d1/* @anatoly-savchenkov
esphome/components/speaker/* @jesserockz esphome/components/speaker/* @jesserockz @kahrendt
esphome/components/speaker/media_player/* @kahrendt @synesthesiam
esphome/components/spi/* @clydebarrow @esphome/core esphome/components/spi/* @clydebarrow @esphome/core
esphome/components/spi_device/* @clydebarrow esphome/components/spi_device/* @clydebarrow
esphome/components/spi_led_strip/* @clydebarrow esphome/components/spi_led_strip/* @clydebarrow
@@ -402,7 +422,9 @@ esphome/components/substitutions/* @esphome/core
esphome/components/sun/* @OttoWinter esphome/components/sun/* @OttoWinter
esphome/components/sun_gtil2/* @Mat931 esphome/components/sun_gtil2/* @Mat931
esphome/components/switch/* @esphome/core esphome/components/switch/* @esphome/core
esphome/components/switch/binary_sensor/* @ssieb
esphome/components/t6615/* @tylermenezes esphome/components/t6615/* @tylermenezes
esphome/components/tc74/* @sethgirvan
esphome/components/tca9548a/* @andreashergert1984 esphome/components/tca9548a/* @andreashergert1984
esphome/components/tca9555/* @mobrembski esphome/components/tca9555/* @mobrembski
esphome/components/tcl112/* @glmnet esphome/components/tcl112/* @glmnet
@@ -426,6 +448,7 @@ esphome/components/tmp102/* @timsavage
esphome/components/tmp1075/* @sybrenstuvel esphome/components/tmp1075/* @sybrenstuvel
esphome/components/tmp117/* @Azimath esphome/components/tmp117/* @Azimath
esphome/components/tof10120/* @wstrzalka esphome/components/tof10120/* @wstrzalka
esphome/components/tormatic/* @ti-mo
esphome/components/toshiba/* @kbx81 esphome/components/toshiba/* @kbx81
esphome/components/touchscreen/* @jesserockz @nielsnl68 esphome/components/touchscreen/* @jesserockz @nielsnl68
esphome/components/tsl2591/* @wjcarpenter esphome/components/tsl2591/* @wjcarpenter
@@ -482,5 +505,6 @@ esphome/components/xiaomi_mhoc401/* @vevsvevs
esphome/components/xiaomi_rtcgq02lm/* @jesserockz esphome/components/xiaomi_rtcgq02lm/* @jesserockz
esphome/components/xl9535/* @mreditor97 esphome/components/xl9535/* @mreditor97
esphome/components/xpt2046/touchscreen/* @nielsnl68 @numo68 esphome/components/xpt2046/touchscreen/* @nielsnl68 @numo68
esphome/components/xxtea/* @clydebarrow
esphome/components/zhlt01/* @cfeenstra1024 esphome/components/zhlt01/* @cfeenstra1024
esphome/components/zio_ultrasonic/* @kahrendt esphome/components/zio_ultrasonic/* @kahrendt

View File

@@ -1,12 +1,14 @@
# Contributing to ESPHome # Contributing to ESPHome [![Discord Chat](https://img.shields.io/discord/429907082951524364.svg)](https://discord.gg/KhAMKrd) [![GitHub release](https://img.shields.io/github/release/esphome/esphome.svg)](https://GitHub.com/esphome/esphome/releases/)
For a detailed guide, please see https://esphome.io/guides/contributing.html#contributing-to-esphome We welcome contributions to the ESPHome suite of code and documentation!
Things to note when contributing: Please read our [contributing guide](https://esphome.io/guides/contributing.html) if you wish to contribute to the
project and be sure to join us on [Discord](https://discord.gg/KhAMKrd).
- Please test your changes :) **See also:**
- If a new feature is added or an existing user-facing feature is changed, you should also
update the [docs](https://github.com/esphome/esphome-docs). See [contributing to esphome-docs](https://esphome.io/guides/contributing.html#contributing-to-esphomedocs) [Documentation](https://esphome.io) -- [Issues](https://github.com/esphome/issues/issues) -- [Feature requests](https://github.com/esphome/feature-requests/issues)
for more information.
- Please also update the tests in the `tests/` folder. You can do so by just adding a line in one of the YAML files ---
which checks if your new feature compiles correctly.
[![ESPHome - A project from the Open Home Foundation](https://www.openhomefoundation.org/badges/esphome.png)](https://www.openhomefoundation.org/)

View File

@@ -1,11 +1,16 @@
# ESPHome [![Discord Chat](https://img.shields.io/discord/429907082951524364.svg)](https://discord.gg/KhAMKrd) [![GitHub release](https://img.shields.io/github/release/esphome/esphome.svg)](https://GitHub.com/esphome/esphome/releases/) # ESPHome [![Discord Chat](https://img.shields.io/discord/429907082951524364.svg)](https://discord.gg/KhAMKrd) [![GitHub release](https://img.shields.io/github/release/esphome/esphome.svg)](https://GitHub.com/esphome/esphome/releases/)
[![ESPHome Logo](https://esphome.io/_images/logo-text.png)](https://esphome.io/) <a href="https://esphome.io/">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://esphome.io/_static/logo-text-on-dark.svg", alt="ESPHome Logo">
<img src="https://esphome.io/_static/logo-text-on-light.svg" alt="ESPHome Logo">
</picture>
</a>
**Documentation:** https://esphome.io/ ---
For issues, please go to [the issue tracker](https://github.com/esphome/issues/issues). [Documentation](https://esphome.io) -- [Issues](https://github.com/esphome/issues/issues) -- [Feature requests](https://github.com/esphome/feature-requests/issues)
For feature requests, please see [feature requests](https://github.com/esphome/feature-requests/issues). ---
[![ESPHome - A project from the Open Home Foundation](https://www.openhomefoundation.org/badges/esphome.png)](https://www.openhomefoundation.org/) [![ESPHome - A project from the Open Home Foundation](https://www.openhomefoundation.org/badges/esphome.png)](https://www.openhomefoundation.org/)

View File

@@ -29,36 +29,17 @@ RUN \
# Use pinned versions so that we get updates with build caching # Use pinned versions so that we get updates with build caching
&& apt-get install -y --no-install-recommends \ && apt-get install -y --no-install-recommends \
python3-pip=23.0.1+dfsg-1 \ python3-pip=23.0.1+dfsg-1 \
python3-setuptools=66.1.1-1 \ python3-setuptools=66.1.1-1+deb12u1 \
python3-venv=3.11.2-1+b1 \ python3-venv=3.11.2-1+b1 \
python3-wheel=0.38.4-2 \ python3-wheel=0.38.4-2 \
iputils-ping=3:20221126-1 \ iputils-ping=3:20221126-1+deb12u1 \
git=1:2.39.5-0+deb12u1 \ git=1:2.39.5-0+deb12u2 \
curl=7.88.1-10+deb12u7 \ curl=7.88.1-10+deb12u12 \
openssh-client=1:9.2p1-2+deb12u3 \ openssh-client=1:9.2p1-2+deb12u5 \
python3-cffi=1.15.1-5 \ python3-cffi=1.15.1-5 \
libcairo2=1.16.0-7 \ libcairo2=1.16.0-7 \
libmagic1=1:5.44-3 \ libmagic1=1:5.44-3 \
patch=2.7.6-7 \ patch=2.7.6-7 \
&& ( \
( \
[ "$TARGETARCH$TARGETVARIANT" = "armv7" ] && \
apt-get install -y --no-install-recommends \
build-essential=12.9 \
python3-dev=3.11.2-1+b1 \
zlib1g-dev=1:1.2.13.dfsg-1 \
libjpeg-dev=1:2.1.5-2 \
libfreetype-dev=2.12.1+dfsg-5+deb12u3 \
libssl-dev=3.0.14-1~deb12u2 \
libffi-dev=3.4.4-1 \
libopenjp2-7=2.5.0-2 \
libtiff6=4.5.0-6+deb12u1 \
cargo=0.66.0+ds1-1 \
pkg-config=1.8.1-1 \
gcc-arm-linux-gnueabihf=4:12.2.0-3 \
) \
|| [ "$TARGETARCH$TARGETVARIANT" != "armv7" ] \
) \
&& rm -rf \ && rm -rf \
/tmp/* \ /tmp/* \
/var/{cache,log}/* \ /var/{cache,log}/* \
@@ -70,23 +51,11 @@ ENV \
# Store globally installed pio libs in /piolibs # Store globally installed pio libs in /piolibs
PLATFORMIO_GLOBALLIB_DIR=/piolibs PLATFORMIO_GLOBALLIB_DIR=/piolibs
# Support legacy binaries on Debian multiarch system. There is no "correct" way
# to do this, other than using properly built toolchains...
# See: https://unix.stackexchange.com/questions/553743/correct-way-to-add-lib-ld-linux-so-3-in-debian
RUN \ RUN \
if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
ln -s /lib/arm-linux-gnueabihf/ld-linux-armhf.so.3 /lib/ld-linux.so.3; \
fi
RUN \
# Ubuntu python3-pip is missing wheel
if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \
fi; \
pip3 install \ pip3 install \
--break-system-packages --no-cache-dir \ --break-system-packages --no-cache-dir \
# Keep platformio version in sync with requirements.txt # Keep platformio version in sync with requirements.txt
platformio==6.1.15 \ platformio==6.1.16 \
# Change some platformio settings # Change some platformio settings
&& platformio settings set enable_telemetry No \ && platformio settings set enable_telemetry No \
&& platformio settings set check_platformio_interval 1000000 \ && platformio settings set check_platformio_interval 1000000 \
@@ -97,15 +66,42 @@ RUN \
# tmpfs is for https://github.com/rust-lang/cargo/issues/8719 # tmpfs is for https://github.com/rust-lang/cargo/issues/8719
COPY requirements.txt requirements_optional.txt / COPY requirements.txt requirements_optional.txt /
RUN --mount=type=tmpfs,target=/root/.cargo if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \ RUN --mount=type=tmpfs,target=/root/.cargo <<END-OF-RUN
curl -L https://www.piwheels.org/cp311/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl -o /tmp/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl \ # Fail on any non-zero status
&& pip3 install --break-system-packages --no-cache-dir /tmp/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl \ set -e
&& rm /tmp/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl \
&& export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \ # install build tools in case wheels are not available
fi; \ BUILD_DEPS="
CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse CARGO_HOME=/root/.cargo \ build-essential=12.9
pip3 install \ python3-dev=3.11.2-1+b1
--break-system-packages --no-cache-dir -r /requirements.txt -r /requirements_optional.txt zlib1g-dev=1:1.2.13.dfsg-1
libjpeg-dev=1:2.1.5-2
libfreetype-dev=2.12.1+dfsg-5+deb12u4
libssl-dev=3.0.15-1~deb12u1
libffi-dev=3.4.4-1
cargo=0.66.0+ds1-1
pkg-config=1.8.1-1
"
LIB_DEPS="
libtiff6=4.5.0-6+deb12u1
libopenjp2-7=2.5.0-2+deb12u1
"
if [ "$TARGETARCH$TARGETVARIANT" = "arm64" ]
then
apt-get update
apt-get install -y --no-install-recommends $BUILD_DEPS $LIB_DEPS
fi
CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse CARGO_HOME=/root/.cargo
pip3 install --break-system-packages --no-cache-dir -r /requirements.txt -r /requirements_optional.txt
if [ "$TARGETARCH$TARGETVARIANT" = "arm64" ]
then
apt-get remove -y --purge --auto-remove $BUILD_DEPS
rm -rf /tmp/* /var/{cache,log}/* /var/lib/apt/lists/*
fi
END-OF-RUN
COPY script/platformio_install_deps.py platformio.ini / COPY script/platformio_install_deps.py platformio.ini /
RUN /platformio_install_deps.py /platformio.ini --libraries RUN /platformio_install_deps.py /platformio.ini --libraries
@@ -119,11 +115,7 @@ FROM base AS docker
# Copy esphome and install # Copy esphome and install
COPY . /esphome COPY . /esphome
RUN if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \ RUN pip3 install --break-system-packages --no-cache-dir -e /esphome
export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \
fi; \
pip3 install \
--break-system-packages --no-cache-dir -e /esphome
# Settings for dashboard # Settings for dashboard
ENV USERNAME="" PASSWORD="" ENV USERNAME="" PASSWORD=""
@@ -147,6 +139,18 @@ ENTRYPOINT ["/entrypoint.sh"]
CMD ["dashboard", "/config"] CMD ["dashboard", "/config"]
ARG BUILD_VERSION=dev
# Labels
LABEL \
org.opencontainers.image.authors="The ESPHome Authors" \
org.opencontainers.image.title="ESPHome" \
org.opencontainers.image.description="ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems" \
org.opencontainers.image.url="https://esphome.io/" \
org.opencontainers.image.documentation="https://esphome.io/" \
org.opencontainers.image.source="https://github.com/esphome/esphome" \
org.opencontainers.image.licenses="ESPHome" \
org.opencontainers.image.version=${BUILD_VERSION}
# ======================= hassio-type image ======================= # ======================= hassio-type image =======================
@@ -156,7 +160,7 @@ RUN \
apt-get update \ apt-get update \
# Use pinned versions so that we get updates with build caching # Use pinned versions so that we get updates with build caching
&& apt-get install -y --no-install-recommends \ && apt-get install -y --no-install-recommends \
nginx-light=1.22.1-9 \ nginx-light=1.22.1-9+deb12u1 \
&& rm -rf \ && rm -rf \
/tmp/* \ /tmp/* \
/var/{cache,log}/* \ /var/{cache,log}/* \
@@ -169,16 +173,12 @@ COPY docker/ha-addon-rootfs/ /
# Copy esphome and install # Copy esphome and install
COPY . /esphome COPY . /esphome
RUN if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \ RUN pip3 install --break-system-packages --no-cache-dir -e /esphome
export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \
fi; \
pip3 install \
--break-system-packages --no-cache-dir -e /esphome
# Labels # Labels
LABEL \ LABEL \
io.hass.name="ESPHome" \ io.hass.name="ESPHome" \
io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \ io.hass.description="ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems" \
io.hass.type="addon" \ io.hass.type="addon" \
io.hass.version="${BUILD_VERSION}" io.hass.version="${BUILD_VERSION}"
# io.hass.arch is inherited from addon-debian-base # io.hass.arch is inherited from addon-debian-base
@@ -193,27 +193,25 @@ ENV \
PLATFORMIO_CORE_DIR=/esphome/.temp/platformio PLATFORMIO_CORE_DIR=/esphome/.temp/platformio
RUN \ RUN \
apt-get update \ curl -L https://apt.llvm.org/llvm-snapshot.gpg.key -o /etc/apt/trusted.gpg.d/apt.llvm.org.asc \
&& echo "deb http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-18 main" > /etc/apt/sources.list.d/llvm.sources.list \
&& apt-get update \
# Use pinned versions so that we get updates with build caching # Use pinned versions so that we get updates with build caching
&& apt-get install -y --no-install-recommends \ && apt-get install -y --no-install-recommends \
clang-format-13=1:13.0.1-11+b2 \ clang-format-13=1:13.0.1-11+b2 \
clang-tidy-14=1:14.0.6-12 \
patch=2.7.6-7 \ patch=2.7.6-7 \
software-properties-common=0.99.30-4.1~deb12u1 \ software-properties-common=0.99.30-4.1~deb12u1 \
nano=7.2-1+deb12u1 \ nano=7.2-1+deb12u1 \
build-essential=12.9 \ build-essential=12.9 \
python3-dev=3.11.2-1+b1 \ python3-dev=3.11.2-1+b1 \
clang-tidy-18=1:18.1.8~++20240731024826+3b5b5c1ec4a3-1~exp1~20240731144843.145 \
&& rm -rf \ && rm -rf \
/tmp/* \ /tmp/* \
/var/{cache,log}/* \ /var/{cache,log}/* \
/var/lib/apt/lists/* /var/lib/apt/lists/*
COPY requirements_test.txt / COPY requirements_test.txt /
RUN if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \ RUN pip3 install --break-system-packages --no-cache-dir -r /requirements_test.txt
export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \
fi; \
pip3 install \
--break-system-packages --no-cache-dir -r /requirements_test.txt
VOLUME ["/esphome"] VOLUME ["/esphome"]
WORKDIR /esphome WORKDIR /esphome

View File

@@ -1,22 +1,19 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from dataclasses import dataclass
import subprocess
import argparse import argparse
from platform import machine from dataclasses import dataclass
import shlex
import re import re
import shlex
import subprocess
import sys import sys
CHANNEL_DEV = "dev" CHANNEL_DEV = "dev"
CHANNEL_BETA = "beta" CHANNEL_BETA = "beta"
CHANNEL_RELEASE = "release" CHANNEL_RELEASE = "release"
CHANNELS = [CHANNEL_DEV, CHANNEL_BETA, CHANNEL_RELEASE] CHANNELS = [CHANNEL_DEV, CHANNEL_BETA, CHANNEL_RELEASE]
ARCH_AMD64 = "amd64" ARCH_AMD64 = "amd64"
ARCH_ARMV7 = "armv7"
ARCH_AARCH64 = "aarch64" ARCH_AARCH64 = "aarch64"
ARCHS = [ARCH_AMD64, ARCH_ARMV7, ARCH_AARCH64] ARCHS = [ARCH_AMD64, ARCH_AARCH64]
TYPE_DOCKER = "docker" TYPE_DOCKER = "docker"
TYPE_HA_ADDON = "ha-addon" TYPE_HA_ADDON = "ha-addon"
@@ -76,7 +73,6 @@ class DockerParams:
}[build_type] }[build_type]
platform = { platform = {
ARCH_AMD64: "linux/amd64", ARCH_AMD64: "linux/amd64",
ARCH_ARMV7: "linux/arm/v7",
ARCH_AARCH64: "linux/arm64", ARCH_AARCH64: "linux/arm64",
}[arch] }[arch]
target = { target = {

View File

@@ -23,10 +23,6 @@ if bashio::config.true 'streamer_mode'; then
export ESPHOME_STREAMER_MODE=true export ESPHOME_STREAMER_MODE=true
fi fi
if bashio::config.true 'status_use_ping'; then
export ESPHOME_DASHBOARD_USE_PING=true
fi
if bashio::config.has_value 'relative_url'; then if bashio::config.has_value 'relative_url'; then
export ESPHOME_DASHBOARD_RELATIVE_URL=$(bashio::config 'relative_url') export ESPHOME_DASHBOARD_RELATIVE_URL=$(bashio::config 'relative_url')
fi fi

View File

@@ -2,6 +2,7 @@
import argparse import argparse
from datetime import datetime from datetime import datetime
import functools import functools
import importlib
import logging import logging
import os import os
import re import re
@@ -20,6 +21,8 @@ from esphome.const import (
CONF_DEASSERT_RTS_DTR, CONF_DEASSERT_RTS_DTR,
CONF_DISABLED, CONF_DISABLED,
CONF_ESPHOME, CONF_ESPHOME,
CONF_LEVEL,
CONF_LOG_TOPIC,
CONF_LOGGER, CONF_LOGGER,
CONF_MDNS, CONF_MDNS,
CONF_MQTT, CONF_MQTT,
@@ -30,6 +33,7 @@ from esphome.const import (
CONF_PLATFORMIO_OPTIONS, CONF_PLATFORMIO_OPTIONS,
CONF_PORT, CONF_PORT,
CONF_SUBSTITUTIONS, CONF_SUBSTITUTIONS,
CONF_TOPIC,
PLATFORM_BK72XX, PLATFORM_BK72XX,
PLATFORM_ESP32, PLATFORM_ESP32,
PLATFORM_ESP8266, PLATFORM_ESP8266,
@@ -38,7 +42,7 @@ from esphome.const import (
SECRETS_FILES, SECRETS_FILES,
) )
from esphome.core import CORE, EsphomeError, coroutine from esphome.core import CORE, EsphomeError, coroutine
from esphome.helpers import indent, is_ip_address, get_bool_env from esphome.helpers import get_bool_env, indent, is_ip_address
from esphome.log import Fore, color, setup_log from esphome.log import Fore, color, setup_log
from esphome.util import ( from esphome.util import (
get_serial_ports, get_serial_ports,
@@ -63,7 +67,7 @@ def choose_prompt(options, purpose: str = None):
return options[0][1] return options[0][1]
safe_print( safe_print(
f'Found multiple options{f" for {purpose}" if purpose else ""}, please choose one:' f"Found multiple options{f' for {purpose}' if purpose else ''}, please choose one:"
) )
for i, (desc, _) in enumerate(options): for i, (desc, _) in enumerate(options):
safe_print(f" [{i + 1}] {desc}") safe_print(f" [{i + 1}] {desc}")
@@ -95,8 +99,12 @@ def choose_upload_log_host(
options.append((f"Over The Air ({CORE.address})", CORE.address)) options.append((f"Over The Air ({CORE.address})", CORE.address))
if default == "OTA": if default == "OTA":
return CORE.address return CORE.address
if show_mqtt and CONF_MQTT in CORE.config: if (
options.append((f"MQTT ({CORE.config['mqtt'][CONF_BROKER]})", "MQTT")) show_mqtt
and (mqtt_config := CORE.config.get(CONF_MQTT))
and mqtt_logging_enabled(mqtt_config)
):
options.append((f"MQTT ({mqtt_config[CONF_BROKER]})", "MQTT"))
if default == "OTA": if default == "OTA":
return "MQTT" return "MQTT"
if default is not None: if default is not None:
@@ -106,6 +114,17 @@ def choose_upload_log_host(
return choose_prompt(options, purpose=purpose) return choose_prompt(options, purpose=purpose)
def mqtt_logging_enabled(mqtt_config):
log_topic = mqtt_config[CONF_LOG_TOPIC]
if log_topic is None:
return False
if CONF_TOPIC not in log_topic:
return False
if log_topic.get(CONF_LEVEL, None) == "NONE":
return False
return True
def get_port_type(port): def get_port_type(port):
if port.startswith("/") or port.startswith("COM"): if port.startswith("/") or port.startswith("COM"):
return "SERIAL" return "SERIAL"
@@ -318,6 +337,13 @@ def check_permissions(port):
def upload_program(config, args, host): def upload_program(config, args, host):
try:
module = importlib.import_module("esphome.components." + CORE.target_platform)
if getattr(module, "upload_program")(config, args, host):
return 0
except AttributeError:
pass
if get_port_type(host) == "SERIAL": if get_port_type(host) == "SERIAL":
check_permissions(host) check_permissions(host)
if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266): if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266):
@@ -345,7 +371,7 @@ def upload_program(config, args, host):
from esphome import espota2 from esphome import espota2
remote_port = ota_conf[CONF_PORT] remote_port = int(ota_conf[CONF_PORT])
password = ota_conf.get(CONF_PASSWORD, "") password = ota_conf.get(CONF_PASSWORD, "")
if ( if (
@@ -378,7 +404,7 @@ def show_logs(config, args, port):
port = mqtt.get_esphome_device_ip( port = mqtt.get_esphome_device_ip(
config, args.username, args.password, args.client_id config, args.username, args.password, args.client_id
) )[0]
from esphome.components.api.client import run_logs from esphome.components.api.client import run_logs
@@ -740,6 +766,14 @@ def parse_args(argv):
options_parser.add_argument( options_parser.add_argument(
"-q", "--quiet", help="Disable all ESPHome logs.", action="store_true" "-q", "--quiet", help="Disable all ESPHome logs.", action="store_true"
) )
options_parser.add_argument(
"-l",
"--log-level",
help="Set the log level.",
default=os.getenv("ESPHOME_LOG_LEVEL", "INFO"),
action="store",
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
)
options_parser.add_argument( options_parser.add_argument(
"--dashboard", help=argparse.SUPPRESS, action="store_true" "--dashboard", help=argparse.SUPPRESS, action="store_true"
) )
@@ -969,11 +1003,16 @@ def run_esphome(argv):
args = parse_args(argv) args = parse_args(argv)
CORE.dashboard = args.dashboard CORE.dashboard = args.dashboard
# Override log level if verbose is set
if args.verbose:
args.log_level = "DEBUG"
elif args.quiet:
args.log_level = "CRITICAL"
setup_log( setup_log(
args.verbose, log_level=args.log_level,
args.quiet,
# Show timestamp for dashboard access logs # Show timestamp for dashboard access logs
args.command == "dashboard", include_timestamp=args.command == "dashboard",
) )
if args.command in PRE_CONFIG_ACTIONS: if args.command in PRE_CONFIG_ACTIONS:

View File

@@ -1,6 +1,8 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ALL,
CONF_ANY,
CONF_AUTOMATION_ID, CONF_AUTOMATION_ID,
CONF_CONDITION, CONF_CONDITION,
CONF_COUNT, CONF_COUNT,
@@ -73,6 +75,13 @@ def validate_potentially_and_condition(value):
return validate_condition(value) return validate_condition(value)
def validate_potentially_or_condition(value):
if isinstance(value, list):
with cv.remove_prepend_path(["or"]):
return validate_condition({"or": value})
return validate_condition(value)
DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component) DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component)
LambdaAction = cg.esphome_ns.class_("LambdaAction", Action) LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
IfAction = cg.esphome_ns.class_("IfAction", Action) IfAction = cg.esphome_ns.class_("IfAction", Action)
@@ -166,6 +175,18 @@ async def or_condition_to_code(config, condition_id, template_arg, args):
return cg.new_Pvariable(condition_id, template_arg, conditions) return cg.new_Pvariable(condition_id, template_arg, conditions)
@register_condition("all", AndCondition, validate_condition_list)
async def all_condition_to_code(config, condition_id, template_arg, args):
conditions = await build_condition_list(config, template_arg, args)
return cg.new_Pvariable(condition_id, template_arg, conditions)
@register_condition("any", OrCondition, validate_condition_list)
async def any_condition_to_code(config, condition_id, template_arg, args):
conditions = await build_condition_list(config, template_arg, args)
return cg.new_Pvariable(condition_id, template_arg, conditions)
@register_condition("not", NotCondition, validate_potentially_and_condition) @register_condition("not", NotCondition, validate_potentially_and_condition)
async def not_condition_to_code(config, condition_id, template_arg, args): async def not_condition_to_code(config, condition_id, template_arg, args):
condition = await build_condition(config, template_arg, args) condition = await build_condition(config, template_arg, args)
@@ -223,15 +244,21 @@ async def delay_action_to_code(config, action_id, template_arg, args):
IfAction, IfAction,
cv.All( cv.All(
{ {
cv.Required(CONF_CONDITION): validate_potentially_and_condition, cv.Exclusive(
CONF_CONDITION, CONF_CONDITION
): validate_potentially_and_condition,
cv.Exclusive(CONF_ANY, CONF_CONDITION): validate_potentially_or_condition,
cv.Exclusive(CONF_ALL, CONF_CONDITION): validate_potentially_and_condition,
cv.Optional(CONF_THEN): validate_action_list, cv.Optional(CONF_THEN): validate_action_list,
cv.Optional(CONF_ELSE): validate_action_list, cv.Optional(CONF_ELSE): validate_action_list,
}, },
cv.has_at_least_one_key(CONF_THEN, CONF_ELSE), cv.has_at_least_one_key(CONF_THEN, CONF_ELSE),
cv.has_at_least_one_key(CONF_CONDITION, CONF_ANY, CONF_ALL),
), ),
) )
async def if_action_to_code(config, action_id, template_arg, args): async def if_action_to_code(config, action_id, template_arg, args):
conditions = await build_condition(config[CONF_CONDITION], template_arg, args) cond_conf = next(el for el in config if el in (CONF_ANY, CONF_ALL, CONF_CONDITION))
conditions = await build_condition(config[cond_conf], template_arg, args)
var = cg.new_Pvariable(action_id, template_arg, conditions) var = cg.new_Pvariable(action_id, template_arg, conditions)
if CONF_THEN in config: if CONF_THEN in config:
actions = await build_action_list(config[CONF_THEN], template_arg, args) actions = await build_action_list(config[CONF_THEN], template_arg, args)

View File

@@ -1,11 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins from esphome import pins
from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER import esphome.codegen as cg
from esphome.core import CORE
from esphome.components.esp32 import get_esp32_variant from esphome.components.esp32 import get_esp32_variant
from esphome.const import PLATFORM_ESP8266
from esphome.components.esp32.const import ( from esphome.components.esp32.const import (
VARIANT_ESP32, VARIANT_ESP32,
VARIANT_ESP32C2, VARIANT_ESP32C2,
@@ -15,6 +10,9 @@ from esphome.components.esp32.const import (
VARIANT_ESP32S2, VARIANT_ESP32S2,
VARIANT_ESP32S3, VARIANT_ESP32S3,
) )
import esphome.config_validation as cv
from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER, PLATFORM_ESP8266
from esphome.core import CORE
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
@@ -38,6 +36,14 @@ ATTENUATION_MODES = {
"auto": "auto", "auto": "auto",
} }
sampling_mode = adc_ns.enum("SamplingMode", is_class=True)
SAMPLING_MODES = {
"avg": sampling_mode.AVG,
"min": sampling_mode.MIN,
"max": sampling_mode.MAX,
}
adc1_channel_t = cg.global_ns.enum("adc1_channel_t") adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
adc2_channel_t = cg.global_ns.enum("adc2_channel_t") adc2_channel_t = cg.global_ns.enum("adc2_channel_t")
@@ -102,11 +108,11 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
6: adc1_channel_t.ADC1_CHANNEL_6, 6: adc1_channel_t.ADC1_CHANNEL_6,
}, },
VARIANT_ESP32H2: { VARIANT_ESP32H2: {
0: adc1_channel_t.ADC1_CHANNEL_0, 1: adc1_channel_t.ADC1_CHANNEL_0,
1: adc1_channel_t.ADC1_CHANNEL_1, 2: adc1_channel_t.ADC1_CHANNEL_1,
2: adc1_channel_t.ADC1_CHANNEL_2, 3: adc1_channel_t.ADC1_CHANNEL_2,
3: adc1_channel_t.ADC1_CHANNEL_3, 4: adc1_channel_t.ADC1_CHANNEL_3,
4: adc1_channel_t.ADC1_CHANNEL_4, 5: adc1_channel_t.ADC1_CHANNEL_4,
}, },
} }

View File

@@ -3,13 +3,12 @@
#include "esphome/components/sensor/sensor.h" #include "esphome/components/sensor/sensor.h"
#include "esphome/components/voltage_sampler/voltage_sampler.h" #include "esphome/components/voltage_sampler/voltage_sampler.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#ifdef USE_ESP32 #ifdef USE_ESP32
#include <esp_adc_cal.h> #include <esp_adc_cal.h>
#include "driver/adc.h" #include "driver/adc.h"
#endif #endif // USE_ESP32
namespace esphome { namespace esphome {
namespace adc { namespace adc {
@@ -29,6 +28,21 @@ static const adc_atten_t ADC_ATTEN_DB_12_COMPAT = ADC_ATTEN_DB_11;
#endif #endif
#endif // USE_ESP32 #endif // USE_ESP32
enum class SamplingMode : uint8_t { AVG = 0, MIN = 1, MAX = 2 };
const LogString *sampling_mode_to_str(SamplingMode mode);
class Aggregator {
public:
void add_sample(uint32_t value);
uint32_t aggregate();
Aggregator(SamplingMode mode);
protected:
SamplingMode mode_{SamplingMode::AVG};
uint32_t aggr_{0};
uint32_t samples_{0};
};
class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler { class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler {
public: public:
#ifdef USE_ESP32 #ifdef USE_ESP32
@@ -43,7 +57,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
this->channel1_ = ADC1_CHANNEL_MAX; this->channel1_ = ADC1_CHANNEL_MAX;
} }
void set_autorange(bool autorange) { this->autorange_ = autorange; } void set_autorange(bool autorange) { this->autorange_ = autorange; }
#endif #endif // USE_ESP32
/// Update ADC values /// Update ADC values
void update() override; void update() override;
@@ -55,24 +69,26 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; } void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; }
void set_output_raw(bool output_raw) { this->output_raw_ = output_raw; } void set_output_raw(bool output_raw) { this->output_raw_ = output_raw; }
void set_sample_count(uint8_t sample_count); void set_sample_count(uint8_t sample_count);
void set_sampling_mode(SamplingMode sampling_mode);
float sample() override; float sample() override;
#ifdef USE_ESP8266 #ifdef USE_ESP8266
std::string unique_id() override; std::string unique_id() override;
#endif #endif // USE_ESP8266
#ifdef USE_RP2040 #ifdef USE_RP2040
void set_is_temperature() { this->is_temperature_ = true; } void set_is_temperature() { this->is_temperature_ = true; }
#endif #endif // USE_RP2040
protected: protected:
InternalGPIOPin *pin_; InternalGPIOPin *pin_;
bool output_raw_{false}; bool output_raw_{false};
uint8_t sample_count_{1}; uint8_t sample_count_{1};
SamplingMode sampling_mode_{SamplingMode::AVG};
#ifdef USE_RP2040 #ifdef USE_RP2040
bool is_temperature_{false}; bool is_temperature_{false};
#endif #endif // USE_RP2040
#ifdef USE_ESP32 #ifdef USE_ESP32
adc_atten_t attenuation_{ADC_ATTEN_DB_0}; adc_atten_t attenuation_{ADC_ATTEN_DB_0};
@@ -83,8 +99,8 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
esp_adc_cal_characteristics_t cal_characteristics_[SOC_ADC_ATTEN_NUM] = {}; esp_adc_cal_characteristics_t cal_characteristics_[SOC_ADC_ATTEN_NUM] = {};
#else #else
esp_adc_cal_characteristics_t cal_characteristics_[ADC_ATTEN_MAX] = {}; esp_adc_cal_characteristics_t cal_characteristics_[ADC_ATTEN_MAX] = {};
#endif #endif // ESP_IDF_VERSION_MAJOR
#endif #endif // USE_ESP32
}; };
} // namespace adc } // namespace adc

View File

@@ -0,0 +1,79 @@
#include "adc_sensor.h"
#include "esphome/core/log.h"
namespace esphome {
namespace adc {
static const char *const TAG = "adc.common";
const LogString *sampling_mode_to_str(SamplingMode mode) {
switch (mode) {
case SamplingMode::AVG:
return LOG_STR("average");
case SamplingMode::MIN:
return LOG_STR("minimum");
case SamplingMode::MAX:
return LOG_STR("maximum");
}
return LOG_STR("unknown");
}
Aggregator::Aggregator(SamplingMode mode) {
this->mode_ = mode;
// set to max uint if mode is "min"
if (mode == SamplingMode::MIN) {
this->aggr_ = UINT32_MAX;
}
}
void Aggregator::add_sample(uint32_t value) {
this->samples_ += 1;
switch (this->mode_) {
case SamplingMode::AVG:
this->aggr_ += value;
break;
case SamplingMode::MIN:
if (value < this->aggr_) {
this->aggr_ = value;
}
break;
case SamplingMode::MAX:
if (value > this->aggr_) {
this->aggr_ = value;
}
}
}
uint32_t Aggregator::aggregate() {
if (this->mode_ == SamplingMode::AVG) {
if (this->samples_ == 0) {
return this->aggr_;
}
return (this->aggr_ + (this->samples_ >> 1)) / this->samples_; // NOLINT(clang-analyzer-core.DivideZero)
}
return this->aggr_;
}
void ADCSensor::update() {
float value_v = this->sample();
ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v);
this->publish_state(value_v);
}
void ADCSensor::set_sample_count(uint8_t sample_count) {
if (sample_count != 0) {
this->sample_count_ = sample_count;
}
}
void ADCSensor::set_sampling_mode(SamplingMode sampling_mode) { this->sampling_mode_ = sampling_mode; }
float ADCSensor::get_setup_priority() const { return setup_priority::DATA; }
} // namespace adc
} // namespace esphome

View File

@@ -1,30 +1,13 @@
#ifdef USE_ESP32
#include "adc_sensor.h" #include "adc_sensor.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#ifdef USE_ESP8266
#ifdef USE_ADC_SENSOR_VCC
#include <Esp.h>
ADC_MODE(ADC_VCC)
#else
#include <Arduino.h>
#endif
#endif
#ifdef USE_RP2040
#ifdef CYW43_USES_VSYS_PIN
#include "pico/cyw43_arch.h"
#endif
#include <hardware/adc.h>
#endif
namespace esphome { namespace esphome {
namespace adc { namespace adc {
static const char *const TAG = "adc"; static const char *const TAG = "adc.esp32";
// 13-bit for S2, 12-bit for all other ESP32 variants
#ifdef USE_ESP32
static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast<adc_bits_width_t>(ADC_WIDTH_MAX - 1); static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast<adc_bits_width_t>(ADC_WIDTH_MAX - 1);
#ifndef SOC_ADC_RTC_MAX_BITWIDTH #ifndef SOC_ADC_RTC_MAX_BITWIDTH
@@ -32,24 +15,15 @@ static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast<adc_bits_widt
static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 13; static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 13;
#else #else
static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 12; static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 12;
#endif #endif // USE_ESP32_VARIANT_ESP32S2
#endif #endif // SOC_ADC_RTC_MAX_BITWIDTH
static const int ADC_MAX = (1 << SOC_ADC_RTC_MAX_BITWIDTH) - 1; // 4095 (12 bit) or 8191 (13 bit) static const int ADC_MAX = (1 << SOC_ADC_RTC_MAX_BITWIDTH) - 1;
static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1; // 2048 (12 bit) or 4096 (13 bit) static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1;
#endif
#ifdef USE_RP2040 void ADCSensor::setup() {
extern "C"
#endif
void
ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
#if !defined(USE_ADC_SENSOR_VCC) && !defined(USE_RP2040)
this->pin_->setup();
#endif
#ifdef USE_ESP32
if (this->channel1_ != ADC1_CHANNEL_MAX) { if (this->channel1_ != ADC1_CHANNEL_MAX) {
adc1_config_width(ADC_WIDTH_MAX_SOC_BITS); adc1_config_width(ADC_WIDTH_MAX_SOC_BITS);
if (!this->autorange_) { if (!this->autorange_) {
@@ -61,7 +35,6 @@ extern "C"
} }
} }
// load characteristics for each attenuation
for (int32_t i = 0; i <= ADC_ATTEN_DB_12_COMPAT; i++) { for (int32_t i = 0; i <= ADC_ATTEN_DB_12_COMPAT; i++) {
auto adc_unit = this->channel1_ != ADC1_CHANNEL_MAX ? ADC_UNIT_1 : ADC_UNIT_2; auto adc_unit = this->channel1_ != ADC1_CHANNEL_MAX ? ADC_UNIT_1 : ADC_UNIT_2;
auto cal_value = esp_adc_cal_characterize(adc_unit, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS, auto cal_value = esp_adc_cal_characterize(adc_unit, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS,
@@ -79,31 +52,10 @@ extern "C"
break; break;
} }
} }
#endif // USE_ESP32
#ifdef USE_RP2040
static bool initialized = false;
if (!initialized) {
adc_init();
initialized = true;
}
#endif
ESP_LOGCONFIG(TAG, "ADC '%s' setup finished!", this->get_name().c_str());
} }
void ADCSensor::dump_config() { void ADCSensor::dump_config() {
LOG_SENSOR("", "ADC Sensor", this); LOG_SENSOR("", "ADC Sensor", this);
#if defined(USE_ESP8266) || defined(USE_LIBRETINY)
#ifdef USE_ADC_SENSOR_VCC
ESP_LOGCONFIG(TAG, " Pin: VCC");
#else
LOG_PIN(" Pin: ", this->pin_);
#endif
#endif // USE_ESP8266 || USE_LIBRETINY
#ifdef USE_ESP32
LOG_PIN(" Pin: ", this->pin_); LOG_PIN(" Pin: ", this->pin_);
if (this->autorange_) { if (this->autorange_) {
ESP_LOGCONFIG(TAG, " Attenuation: auto"); ESP_LOGCONFIG(TAG, " Attenuation: auto");
@@ -125,58 +77,15 @@ void ADCSensor::dump_config() {
break; break;
} }
} }
#endif // USE_ESP32
#ifdef USE_RP2040
if (this->is_temperature_) {
ESP_LOGCONFIG(TAG, " Pin: Temperature");
} else {
#ifdef USE_ADC_SENSOR_VCC
ESP_LOGCONFIG(TAG, " Pin: VCC");
#else
LOG_PIN(" Pin: ", this->pin_);
#endif // USE_ADC_SENSOR_VCC
}
#endif // USE_RP2040
ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_); ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_);
ESP_LOGCONFIG(TAG, " Sampling mode: %s", LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)));
LOG_UPDATE_INTERVAL(this); LOG_UPDATE_INTERVAL(this);
} }
float ADCSensor::get_setup_priority() const { return setup_priority::DATA; }
void ADCSensor::update() {
float value_v = this->sample();
ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v);
this->publish_state(value_v);
}
void ADCSensor::set_sample_count(uint8_t sample_count) {
if (sample_count != 0) {
this->sample_count_ = sample_count;
}
}
#ifdef USE_ESP8266
float ADCSensor::sample() {
uint32_t raw = 0;
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
#ifdef USE_ADC_SENSOR_VCC
raw += ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance)
#else
raw += analogRead(this->pin_->get_pin()); // NOLINT
#endif
}
raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
if (this->output_raw_) {
return raw;
}
return raw / 1024.0f;
}
#endif
#ifdef USE_ESP32
float ADCSensor::sample() { float ADCSensor::sample() {
if (!this->autorange_) { if (!this->autorange_) {
uint32_t sum = 0; auto aggr = Aggregator(this->sampling_mode_);
for (uint8_t sample = 0; sample < this->sample_count_; sample++) { for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
int raw = -1; int raw = -1;
if (this->channel1_ != ADC1_CHANNEL_MAX) { if (this->channel1_ != ADC1_CHANNEL_MAX) {
@@ -187,13 +96,14 @@ float ADCSensor::sample() {
if (raw == -1) { if (raw == -1) {
return NAN; return NAN;
} }
sum += raw;
aggr.add_sample(raw);
} }
sum = (sum + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
if (this->output_raw_) { if (this->output_raw_) {
return sum; return aggr.aggregate();
} }
uint32_t mv = esp_adc_cal_raw_to_voltage(sum, &this->cal_characteristics_[(int32_t) this->attenuation_]); uint32_t mv =
esp_adc_cal_raw_to_voltage(aggr.aggregate(), &this->cal_characteristics_[(int32_t) this->attenuation_]);
return mv / 1000.0f; return mv / 1000.0f;
} }
@@ -240,93 +150,17 @@ float ADCSensor::sample() {
uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_2_5]); uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_2_5]);
uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_0]); uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_0]);
// Contribution of each value, in range 0-2048 (12 bit ADC) or 0-4096 (13 bit ADC)
uint32_t c12 = std::min(raw12, ADC_HALF); uint32_t c12 = std::min(raw12, ADC_HALF);
uint32_t c6 = ADC_HALF - std::abs(raw6 - ADC_HALF); uint32_t c6 = ADC_HALF - std::abs(raw6 - ADC_HALF);
uint32_t c2 = ADC_HALF - std::abs(raw2 - ADC_HALF); uint32_t c2 = ADC_HALF - std::abs(raw2 - ADC_HALF);
uint32_t c0 = std::min(ADC_MAX - raw0, ADC_HALF); uint32_t c0 = std::min(ADC_MAX - raw0, ADC_HALF);
// max theoretical csum value is 4096*4 = 16384
uint32_t csum = c12 + c6 + c2 + c0; uint32_t csum = c12 + c6 + c2 + c0;
// each mv is max 3900; so max value is 3900*4096*4, fits in unsigned32
uint32_t mv_scaled = (mv12 * c12) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0); uint32_t mv_scaled = (mv12 * c12) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0);
return mv_scaled / (float) (csum * 1000U); return mv_scaled / (float) (csum * 1000U);
} }
#endif // USE_ESP32
#ifdef USE_RP2040
float ADCSensor::sample() {
if (this->is_temperature_) {
adc_set_temp_sensor_enabled(true);
delay(1);
adc_select_input(4);
uint32_t raw = 0;
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
raw += adc_read();
}
raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
adc_set_temp_sensor_enabled(false);
if (this->output_raw_) {
return raw;
}
return raw * 3.3f / 4096.0f;
} else {
uint8_t pin = this->pin_->get_pin();
#ifdef CYW43_USES_VSYS_PIN
if (pin == PICO_VSYS_PIN) {
// Measuring VSYS on Raspberry Pico W needs to be wrapped with
// `cyw43_thread_enter()`/`cyw43_thread_exit()` as discussed in
// https://github.com/raspberrypi/pico-sdk/issues/1222, since Wifi chip and
// VSYS ADC both share GPIO29
cyw43_thread_enter();
}
#endif // CYW43_USES_VSYS_PIN
adc_gpio_init(pin);
adc_select_input(pin - 26);
uint32_t raw = 0;
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
raw += adc_read();
}
raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
#ifdef CYW43_USES_VSYS_PIN
if (pin == PICO_VSYS_PIN) {
cyw43_thread_exit();
}
#endif // CYW43_USES_VSYS_PIN
if (this->output_raw_) {
return raw;
}
float coeff = pin == PICO_VSYS_PIN ? 3.0 : 1.0;
return raw * 3.3f / 4096.0f * coeff;
}
}
#endif
#ifdef USE_LIBRETINY
float ADCSensor::sample() {
uint32_t raw = 0;
if (this->output_raw_) {
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
raw += analogRead(this->pin_->get_pin()); // NOLINT
}
raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
return raw;
}
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
raw += analogReadVoltage(this->pin_->get_pin()); // NOLINT
}
raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
return raw / 1000.0f;
}
#endif // USE_LIBRETINY
#ifdef USE_ESP8266
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
#endif
} // namespace adc } // namespace adc
} // namespace esphome } // namespace esphome
#endif // USE_ESP32

View File

@@ -0,0 +1,62 @@
#ifdef USE_ESP8266
#include "adc_sensor.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#ifdef USE_ADC_SENSOR_VCC
#include <Esp.h>
ADC_MODE(ADC_VCC)
#else
#include <Arduino.h>
#endif // USE_ADC_SENSOR_VCC
namespace esphome {
namespace adc {
static const char *const TAG = "adc.esp8266";
void ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
#ifndef USE_ADC_SENSOR_VCC
this->pin_->setup();
#endif
}
void ADCSensor::dump_config() {
LOG_SENSOR("", "ADC Sensor", this);
#ifdef USE_ADC_SENSOR_VCC
ESP_LOGCONFIG(TAG, " Pin: VCC");
#else
LOG_PIN(" Pin: ", this->pin_);
#endif // USE_ADC_SENSOR_VCC
ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_);
ESP_LOGCONFIG(TAG, " Sampling mode: %s", LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)));
LOG_UPDATE_INTERVAL(this);
}
float ADCSensor::sample() {
auto aggr = Aggregator(this->sampling_mode_);
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
uint32_t raw = 0;
#ifdef USE_ADC_SENSOR_VCC
raw = ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance)
#else
raw = analogRead(this->pin_->get_pin()); // NOLINT
#endif // USE_ADC_SENSOR_VCC
aggr.add_sample(raw);
}
if (this->output_raw_) {
return aggr.aggregate();
}
return aggr.aggregate() / 1024.0f;
}
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
} // namespace adc
} // namespace esphome
#endif // USE_ESP8266

View File

@@ -0,0 +1,53 @@
#ifdef USE_LIBRETINY
#include "adc_sensor.h"
#include "esphome/core/log.h"
namespace esphome {
namespace adc {
static const char *const TAG = "adc.libretiny";
void ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
#ifndef USE_ADC_SENSOR_VCC
this->pin_->setup();
#endif // !USE_ADC_SENSOR_VCC
}
void ADCSensor::dump_config() {
LOG_SENSOR("", "ADC Sensor", this);
#ifdef USE_ADC_SENSOR_VCC
ESP_LOGCONFIG(TAG, " Pin: VCC");
#else // USE_ADC_SENSOR_VCC
LOG_PIN(" Pin: ", this->pin_);
#endif // USE_ADC_SENSOR_VCC
ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_);
ESP_LOGCONFIG(TAG, " Sampling mode: %s", LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)));
LOG_UPDATE_INTERVAL(this);
}
float ADCSensor::sample() {
uint32_t raw = 0;
auto aggr = Aggregator(this->sampling_mode_);
if (this->output_raw_) {
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
raw = analogRead(this->pin_->get_pin()); // NOLINT
aggr.add_sample(raw);
}
return aggr.aggregate();
}
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
raw = analogReadVoltage(this->pin_->get_pin()); // NOLINT
aggr.add_sample(raw);
}
return aggr.aggregate() / 1000.0f;
}
} // namespace adc
} // namespace esphome
#endif // USE_LIBRETINY

View File

@@ -0,0 +1,96 @@
#ifdef USE_RP2040
#include "adc_sensor.h"
#include "esphome/core/log.h"
#ifdef CYW43_USES_VSYS_PIN
#include "pico/cyw43_arch.h"
#endif // CYW43_USES_VSYS_PIN
#include <hardware/adc.h>
namespace esphome {
namespace adc {
static const char *const TAG = "adc.rp2040";
void ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
static bool initialized = false;
if (!initialized) {
adc_init();
initialized = true;
}
}
void ADCSensor::dump_config() {
LOG_SENSOR("", "ADC Sensor", this);
if (this->is_temperature_) {
ESP_LOGCONFIG(TAG, " Pin: Temperature");
} else {
#ifdef USE_ADC_SENSOR_VCC
ESP_LOGCONFIG(TAG, " Pin: VCC");
#else
LOG_PIN(" Pin: ", this->pin_);
#endif // USE_ADC_SENSOR_VCC
}
ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_);
ESP_LOGCONFIG(TAG, " Sampling mode: %s", LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)));
LOG_UPDATE_INTERVAL(this);
}
float ADCSensor::sample() {
uint32_t raw = 0;
auto aggr = Aggregator(this->sampling_mode_);
if (this->is_temperature_) {
adc_set_temp_sensor_enabled(true);
delay(1);
adc_select_input(4);
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
raw = adc_read();
aggr.add_sample(raw);
}
adc_set_temp_sensor_enabled(false);
if (this->output_raw_) {
return aggr.aggregate();
}
return aggr.aggregate() * 3.3f / 4096.0f;
}
uint8_t pin = this->pin_->get_pin();
#ifdef CYW43_USES_VSYS_PIN
if (pin == PICO_VSYS_PIN) {
// Measuring VSYS on Raspberry Pico W needs to be wrapped with
// `cyw43_thread_enter()`/`cyw43_thread_exit()` as discussed in
// https://github.com/raspberrypi/pico-sdk/issues/1222, since Wifi chip and
// VSYS ADC both share GPIO29
cyw43_thread_enter();
}
#endif // CYW43_USES_VSYS_PIN
adc_gpio_init(pin);
adc_select_input(pin - 26);
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
raw = adc_read();
aggr.add_sample(raw);
}
#ifdef CYW43_USES_VSYS_PIN
if (pin == PICO_VSYS_PIN) {
cyw43_thread_exit();
}
#endif // CYW43_USES_VSYS_PIN
if (this->output_raw_) {
return aggr.aggregate();
}
float coeff = pin == PICO_VSYS_PIN ? 3.0f : 1.0f;
return aggr.aggregate() * 3.3f / 4096.0f * coeff;
}
} // namespace adc
} // namespace esphome
#endif // USE_RP2040

View File

@@ -1,11 +1,9 @@
import logging import logging
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv
import esphome.final_validate as fv
from esphome.core import CORE
from esphome.components import sensor, voltage_sampler from esphome.components import sensor, voltage_sampler
from esphome.components.esp32 import get_esp32_variant from esphome.components.esp32 import get_esp32_variant
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ATTENUATION, CONF_ATTENUATION,
CONF_ID, CONF_ID,
@@ -17,10 +15,14 @@ from esphome.const import (
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_VOLT, UNIT_VOLT,
) )
from esphome.core import CORE
import esphome.final_validate as fv
from . import ( from . import (
ATTENUATION_MODES, ATTENUATION_MODES,
ESP32_VARIANT_ADC1_PIN_TO_CHANNEL, ESP32_VARIANT_ADC1_PIN_TO_CHANNEL,
ESP32_VARIANT_ADC2_PIN_TO_CHANNEL, ESP32_VARIANT_ADC2_PIN_TO_CHANNEL,
SAMPLING_MODES,
adc_ns, adc_ns,
validate_adc_pin, validate_adc_pin,
) )
@@ -30,9 +32,11 @@ _LOGGER = logging.getLogger(__name__)
AUTO_LOAD = ["voltage_sampler"] AUTO_LOAD = ["voltage_sampler"]
CONF_SAMPLES = "samples" CONF_SAMPLES = "samples"
CONF_SAMPLING_MODE = "sampling_mode"
_attenuation = cv.enum(ATTENUATION_MODES, lower=True) _attenuation = cv.enum(ATTENUATION_MODES, lower=True)
_sampling_mode = cv.enum(SAMPLING_MODES, lower=True)
def validate_config(config): def validate_config(config):
@@ -88,6 +92,7 @@ CONFIG_SCHEMA = cv.All(
cv.only_on_esp32, _attenuation cv.only_on_esp32, _attenuation
), ),
cv.Optional(CONF_SAMPLES, default=1): cv.int_range(min=1, max=255), cv.Optional(CONF_SAMPLES, default=1): cv.int_range(min=1, max=255),
cv.Optional(CONF_SAMPLING_MODE, default="avg"): _sampling_mode,
} }
) )
.extend(cv.polling_component_schema("60s")), .extend(cv.polling_component_schema("60s")),
@@ -112,6 +117,7 @@ async def to_code(config):
cg.add(var.set_output_raw(config[CONF_RAW])) cg.add(var.set_output_raw(config[CONF_RAW]))
cg.add(var.set_sample_count(config[CONF_SAMPLES])) cg.add(var.set_sample_count(config[CONF_SAMPLES]))
cg.add(var.set_sampling_mode(config[CONF_SAMPLING_MODE]))
if attenuation := config.get(CONF_ATTENUATION): if attenuation := config.get(CONF_ATTENUATION):
if attenuation == "auto": if attenuation == "auto":

View File

@@ -9,8 +9,6 @@ static const char *const TAG = "ads1115";
static const uint8_t ADS1115_REGISTER_CONVERSION = 0x00; static const uint8_t ADS1115_REGISTER_CONVERSION = 0x00;
static const uint8_t ADS1115_REGISTER_CONFIG = 0x01; static const uint8_t ADS1115_REGISTER_CONFIG = 0x01;
static const uint8_t ADS1115_DATA_RATE_860_SPS = 0b111; // 3300_SPS for ADS1015
void ADS1115Component::setup() { void ADS1115Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up ADS1115..."); ESP_LOGCONFIG(TAG, "Setting up ADS1115...");
uint16_t value; uint16_t value;
@@ -43,9 +41,9 @@ void ADS1115Component::setup() {
config |= 0b0000000100000000; config |= 0b0000000100000000;
} }
// Set data rate - 860 samples per second (we're in singleshot mode) // Set data rate - 860 samples per second
// 0bxxxxxxxx100xxxxx // 0bxxxxxxxx100xxxxx
config |= ADS1115_DATA_RATE_860_SPS << 5; config |= ADS1115_860SPS << 5;
// Set comparator mode - hysteresis // Set comparator mode - hysteresis
// 0bxxxxxxxxxxx0xxxx // 0bxxxxxxxxxxx0xxxx
@@ -77,7 +75,7 @@ void ADS1115Component::dump_config() {
} }
} }
float ADS1115Component::request_measurement(ADS1115Multiplexer multiplexer, ADS1115Gain gain, float ADS1115Component::request_measurement(ADS1115Multiplexer multiplexer, ADS1115Gain gain,
ADS1115Resolution resolution) { ADS1115Resolution resolution, ADS1115Samplerate samplerate) {
uint16_t config = this->prev_config_; uint16_t config = this->prev_config_;
// Multiplexer // Multiplexer
// 0bxBBBxxxxxxxxxxxx // 0bxBBBxxxxxxxxxxxx
@@ -89,6 +87,11 @@ float ADS1115Component::request_measurement(ADS1115Multiplexer multiplexer, ADS1
config &= 0b1111000111111111; config &= 0b1111000111111111;
config |= (gain & 0b111) << 9; config |= (gain & 0b111) << 9;
// Sample rate
// 0bxxxxxxxxBBBxxxxx
config &= 0b1111111100011111;
config |= (samplerate & 0b111) << 5;
if (!this->continuous_mode_) { if (!this->continuous_mode_) {
// Start conversion // Start conversion
config |= 0b1000000000000000; config |= 0b1000000000000000;
@@ -101,8 +104,54 @@ float ADS1115Component::request_measurement(ADS1115Multiplexer multiplexer, ADS1
} }
this->prev_config_ = config; this->prev_config_ = config;
// about 1.2 ms with 860 samples per second // Delay calculated as: ceil((1000/SPS)+.5)
delay(2); if (resolution == ADS1015_12_BITS) {
switch (samplerate) {
case ADS1115_8SPS:
delay(9);
break;
case ADS1115_16SPS:
delay(5);
break;
case ADS1115_32SPS:
delay(3);
break;
case ADS1115_64SPS:
case ADS1115_128SPS:
delay(2);
break;
default:
delay(1);
break;
}
} else {
switch (samplerate) {
case ADS1115_8SPS:
delay(126); // NOLINT
break;
case ADS1115_16SPS:
delay(63); // NOLINT
break;
case ADS1115_32SPS:
delay(32);
break;
case ADS1115_64SPS:
delay(17);
break;
case ADS1115_128SPS:
delay(9);
break;
case ADS1115_250SPS:
delay(5);
break;
case ADS1115_475SPS:
delay(3);
break;
case ADS1115_860SPS:
delay(2);
break;
}
}
// in continuous mode, conversion will always be running, rely on the delay // in continuous mode, conversion will always be running, rely on the delay
// to ensure conversion is taking place with the correct settings // to ensure conversion is taking place with the correct settings

View File

@@ -33,6 +33,17 @@ enum ADS1115Resolution {
ADS1015_12_BITS = 12, ADS1015_12_BITS = 12,
}; };
enum ADS1115Samplerate {
ADS1115_8SPS = 0b000,
ADS1115_16SPS = 0b001,
ADS1115_32SPS = 0b010,
ADS1115_64SPS = 0b011,
ADS1115_128SPS = 0b100,
ADS1115_250SPS = 0b101,
ADS1115_475SPS = 0b110,
ADS1115_860SPS = 0b111
};
class ADS1115Component : public Component, public i2c::I2CDevice { class ADS1115Component : public Component, public i2c::I2CDevice {
public: public:
void setup() override; void setup() override;
@@ -42,7 +53,8 @@ class ADS1115Component : public Component, public i2c::I2CDevice {
void set_continuous_mode(bool continuous_mode) { continuous_mode_ = continuous_mode; } void set_continuous_mode(bool continuous_mode) { continuous_mode_ = continuous_mode; }
/// Helper method to request a measurement from a sensor. /// Helper method to request a measurement from a sensor.
float request_measurement(ADS1115Multiplexer multiplexer, ADS1115Gain gain, ADS1115Resolution resolution); float request_measurement(ADS1115Multiplexer multiplexer, ADS1115Gain gain, ADS1115Resolution resolution,
ADS1115Samplerate samplerate);
protected: protected:
uint16_t prev_config_{0}; uint16_t prev_config_{0};

View File

@@ -5,6 +5,7 @@ from esphome.const import (
CONF_GAIN, CONF_GAIN,
CONF_MULTIPLEXER, CONF_MULTIPLEXER,
CONF_RESOLUTION, CONF_RESOLUTION,
CONF_SAMPLE_RATE,
DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_VOLT, UNIT_VOLT,
@@ -43,6 +44,17 @@ RESOLUTION = {
"12_BITS": ADS1115Resolution.ADS1015_12_BITS, "12_BITS": ADS1115Resolution.ADS1015_12_BITS,
} }
ADS1115Samplerate = ads1115_ns.enum("ADS1115Samplerate")
SAMPLERATE = {
"8": ADS1115Samplerate.ADS1115_8SPS,
"16": ADS1115Samplerate.ADS1115_16SPS,
"32": ADS1115Samplerate.ADS1115_32SPS,
"64": ADS1115Samplerate.ADS1115_64SPS,
"128": ADS1115Samplerate.ADS1115_128SPS,
"250": ADS1115Samplerate.ADS1115_250SPS,
"475": ADS1115Samplerate.ADS1115_475SPS,
"860": ADS1115Samplerate.ADS1115_860SPS,
}
ADS1115Sensor = ads1115_ns.class_( ADS1115Sensor = ads1115_ns.class_(
"ADS1115Sensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler "ADS1115Sensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
@@ -64,6 +76,9 @@ CONFIG_SCHEMA = (
cv.Optional(CONF_RESOLUTION, default="16_BITS"): cv.enum( cv.Optional(CONF_RESOLUTION, default="16_BITS"): cv.enum(
RESOLUTION, upper=True, space="_" RESOLUTION, upper=True, space="_"
), ),
cv.Optional(CONF_SAMPLE_RATE, default="860"): cv.enum(
SAMPLERATE, string=True
),
} }
) )
.extend(cv.polling_component_schema("60s")) .extend(cv.polling_component_schema("60s"))
@@ -79,3 +94,4 @@ async def to_code(config):
cg.add(var.set_multiplexer(config[CONF_MULTIPLEXER])) cg.add(var.set_multiplexer(config[CONF_MULTIPLEXER]))
cg.add(var.set_gain(config[CONF_GAIN])) cg.add(var.set_gain(config[CONF_GAIN]))
cg.add(var.set_resolution(config[CONF_RESOLUTION])) cg.add(var.set_resolution(config[CONF_RESOLUTION]))
cg.add(var.set_samplerate(config[CONF_SAMPLE_RATE]))

View File

@@ -8,7 +8,7 @@ namespace ads1115 {
static const char *const TAG = "ads1115.sensor"; static const char *const TAG = "ads1115.sensor";
float ADS1115Sensor::sample() { float ADS1115Sensor::sample() {
return this->parent_->request_measurement(this->multiplexer_, this->gain_, this->resolution_); return this->parent_->request_measurement(this->multiplexer_, this->gain_, this->resolution_, this->samplerate_);
} }
void ADS1115Sensor::update() { void ADS1115Sensor::update() {
@@ -24,6 +24,7 @@ void ADS1115Sensor::dump_config() {
ESP_LOGCONFIG(TAG, " Multiplexer: %u", this->multiplexer_); ESP_LOGCONFIG(TAG, " Multiplexer: %u", this->multiplexer_);
ESP_LOGCONFIG(TAG, " Gain: %u", this->gain_); ESP_LOGCONFIG(TAG, " Gain: %u", this->gain_);
ESP_LOGCONFIG(TAG, " Resolution: %u", this->resolution_); ESP_LOGCONFIG(TAG, " Resolution: %u", this->resolution_);
ESP_LOGCONFIG(TAG, " Sample rate: %u", this->samplerate_);
} }
} // namespace ads1115 } // namespace ads1115

View File

@@ -21,6 +21,7 @@ class ADS1115Sensor : public sensor::Sensor,
void set_multiplexer(ADS1115Multiplexer multiplexer) { this->multiplexer_ = multiplexer; } void set_multiplexer(ADS1115Multiplexer multiplexer) { this->multiplexer_ = multiplexer; }
void set_gain(ADS1115Gain gain) { this->gain_ = gain; } void set_gain(ADS1115Gain gain) { this->gain_ = gain; }
void set_resolution(ADS1115Resolution resolution) { this->resolution_ = resolution; } void set_resolution(ADS1115Resolution resolution) { this->resolution_ = resolution; }
void set_samplerate(ADS1115Samplerate samplerate) { this->samplerate_ = samplerate; }
float sample() override; float sample() override;
void dump_config() override; void dump_config() override;
@@ -29,6 +30,7 @@ class ADS1115Sensor : public sensor::Sensor,
ADS1115Multiplexer multiplexer_; ADS1115Multiplexer multiplexer_;
ADS1115Gain gain_; ADS1115Gain gain_;
ADS1115Resolution resolution_; ADS1115Resolution resolution_;
ADS1115Samplerate samplerate_;
}; };
} // namespace ads1115 } // namespace ads1115

View File

@@ -72,10 +72,9 @@ void AlarmControlPanelCall::validate_() {
this->state_.reset(); this->state_.reset();
return; return;
} }
if (state == ACP_STATE_DISARMED && if (state == ACP_STATE_DISARMED && !this->parent_->is_state_armed(this->parent_->get_state()) &&
!(this->parent_->is_state_armed(this->parent_->get_state()) || this->parent_->get_state() != ACP_STATE_PENDING && this->parent_->get_state() != ACP_STATE_ARMING &&
this->parent_->get_state() == ACP_STATE_PENDING || this->parent_->get_state() == ACP_STATE_ARMING || this->parent_->get_state() != ACP_STATE_TRIGGERED) {
this->parent_->get_state() == ACP_STATE_TRIGGERED)) {
ESP_LOGW(TAG, "Cannot disarm when not armed"); ESP_LOGW(TAG, "Cannot disarm when not armed");
this->state_.reset(); this->state_.reset();
return; return;

View File

@@ -1,29 +1,10 @@
import logging import logging
from esphome import automation, core from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import font
import esphome.components.image as espImage import esphome.components.image as espImage
from esphome.components.image import (
CONF_USE_TRANSPARENCY,
LOCAL_SCHEMA,
SOURCE_LOCAL,
SOURCE_WEB,
WEB_SCHEMA,
)
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import CONF_ID, CONF_REPEAT
CONF_FILE,
CONF_ID,
CONF_PATH,
CONF_RAW_DATA_ID,
CONF_REPEAT,
CONF_RESIZE,
CONF_SOURCE,
CONF_TYPE,
CONF_URL,
)
from esphome.core import CORE, HexInt
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -31,6 +12,7 @@ AUTO_LOAD = ["image"]
CODEOWNERS = ["@syndlex"] CODEOWNERS = ["@syndlex"]
DEPENDENCIES = ["display"] DEPENDENCIES = ["display"]
MULTI_CONF = True MULTI_CONF = True
MULTI_CONF_NO_DEFAULT = True
CONF_LOOP = "loop" CONF_LOOP = "loop"
CONF_START_FRAME = "start_frame" CONF_START_FRAME = "start_frame"
@@ -52,86 +34,19 @@ SetFrameAction = animation_ns.class_(
"AnimationSetFrameAction", automation.Action, cg.Parented.template(Animation_) "AnimationSetFrameAction", automation.Action, cg.Parented.template(Animation_)
) )
TYPED_FILE_SCHEMA = cv.typed_schema( CONFIG_SCHEMA = espImage.IMAGE_SCHEMA.extend(
{ {
SOURCE_LOCAL: LOCAL_SCHEMA, cv.Required(CONF_ID): cv.declare_id(Animation_),
SOURCE_WEB: WEB_SCHEMA, cv.Optional(CONF_LOOP): cv.All(
},
key=CONF_SOURCE,
)
def _file_schema(value):
if isinstance(value, str):
return validate_file_shorthand(value)
return TYPED_FILE_SCHEMA(value)
FILE_SCHEMA = cv.Schema(_file_schema)
def validate_file_shorthand(value):
value = cv.string_strict(value)
if value.startswith("http://") or value.startswith("https://"):
return FILE_SCHEMA(
{ {
CONF_SOURCE: SOURCE_WEB, cv.Optional(CONF_START_FRAME, default=0): cv.positive_int,
CONF_URL: value, cv.Optional(CONF_END_FRAME): cv.positive_int,
cv.Optional(CONF_REPEAT): cv.positive_int,
} }
) ),
return FILE_SCHEMA( },
{
CONF_SOURCE: SOURCE_LOCAL,
CONF_PATH: value,
}
)
def validate_cross_dependencies(config):
"""
Validate fields whose possible values depend on other fields.
For example, validate that explicitly transparent image types
have "use_transparency" set to True.
Also set the default value for those kind of dependent fields.
"""
image_type = config[CONF_TYPE]
is_transparent_type = image_type in ["TRANSPARENT_BINARY", "RGBA"]
# If the use_transparency option was not specified, set the default depending on the image type
if CONF_USE_TRANSPARENCY not in config:
config[CONF_USE_TRANSPARENCY] = is_transparent_type
if is_transparent_type and not config[CONF_USE_TRANSPARENCY]:
raise cv.Invalid(f"Image type {image_type} must always be transparent.")
return config
ANIMATION_SCHEMA = cv.Schema(
cv.All(
{
cv.Required(CONF_ID): cv.declare_id(Animation_),
cv.Required(CONF_FILE): FILE_SCHEMA,
cv.Optional(CONF_RESIZE): cv.dimensions,
cv.Optional(CONF_TYPE, default="BINARY"): cv.enum(
espImage.IMAGE_TYPE, upper=True
),
# Not setting default here on purpose; the default depends on the image type,
# and thus will be set in the "validate_cross_dependencies" validator.
cv.Optional(CONF_USE_TRANSPARENCY): cv.boolean,
cv.Optional(CONF_LOOP): cv.All(
{
cv.Optional(CONF_START_FRAME, default=0): cv.positive_int,
cv.Optional(CONF_END_FRAME): cv.positive_int,
cv.Optional(CONF_REPEAT): cv.positive_int,
}
),
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
},
validate_cross_dependencies,
)
) )
CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA)
NEXT_FRAME_SCHEMA = automation.maybe_simple_id( NEXT_FRAME_SCHEMA = automation.maybe_simple_id(
{ {
@@ -165,183 +80,26 @@ async def animation_action_to_code(config, action_id, template_arg, args):
async def to_code(config): async def to_code(config):
from PIL import Image (
prog_arr,
width,
height,
image_type,
trans_value,
frame_count,
) = await espImage.write_image(config, all_frames=True)
conf_file = config[CONF_FILE]
if conf_file[CONF_SOURCE] == SOURCE_LOCAL:
path = CORE.relative_config_path(conf_file[CONF_PATH])
elif conf_file[CONF_SOURCE] == SOURCE_WEB:
path = espImage.compute_local_image_path(conf_file).as_posix()
else:
raise core.EsphomeError(f"Unknown animation source: {conf_file[CONF_SOURCE]}")
try:
image = Image.open(path)
except Exception as e:
raise core.EsphomeError(f"Could not load image file {path}: {e}")
width, height = image.size
frames = image.n_frames
if CONF_RESIZE in config:
new_width_max, new_height_max = config[CONF_RESIZE]
ratio = min(new_width_max / width, new_height_max / height)
width, height = int(width * ratio), int(height * ratio)
elif width > 500 or height > 500:
_LOGGER.warning(
'The image "%s" you requested is very big. Please consider'
" using the resize parameter.",
path,
)
transparent = config[CONF_USE_TRANSPARENCY]
if config[CONF_TYPE] == "GRAYSCALE":
data = [0 for _ in range(height * width * frames)]
pos = 0
for frameIndex in range(frames):
image.seek(frameIndex)
frame = image.convert("LA", dither=Image.Dither.NONE)
if CONF_RESIZE in config:
frame = frame.resize([width, height])
pixels = list(frame.getdata())
if len(pixels) != height * width:
raise core.EsphomeError(
f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height * width})"
)
for pix, a in pixels:
if transparent:
if pix == 1:
pix = 0
if a < 0x80:
pix = 1
data[pos] = pix
pos += 1
elif config[CONF_TYPE] == "RGBA":
data = [0 for _ in range(height * width * 4 * frames)]
pos = 0
for frameIndex in range(frames):
image.seek(frameIndex)
frame = image.convert("RGBA")
if CONF_RESIZE in config:
frame = frame.resize([width, height])
pixels = list(frame.getdata())
if len(pixels) != height * width:
raise core.EsphomeError(
f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height * width})"
)
for pix in pixels:
data[pos] = pix[0]
pos += 1
data[pos] = pix[1]
pos += 1
data[pos] = pix[2]
pos += 1
data[pos] = pix[3]
pos += 1
elif config[CONF_TYPE] == "RGB24":
data = [0 for _ in range(height * width * 3 * frames)]
pos = 0
for frameIndex in range(frames):
image.seek(frameIndex)
frame = image.convert("RGBA")
if CONF_RESIZE in config:
frame = frame.resize([width, height])
pixels = list(frame.getdata())
if len(pixels) != height * width:
raise core.EsphomeError(
f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height * width})"
)
for r, g, b, a in pixels:
if transparent:
if r == 0 and g == 0 and b == 1:
b = 0
if a < 0x80:
r = 0
g = 0
b = 1
data[pos] = r
pos += 1
data[pos] = g
pos += 1
data[pos] = b
pos += 1
elif config[CONF_TYPE] in ["RGB565", "TRANSPARENT_IMAGE"]:
data = [0 for _ in range(height * width * 2 * frames)]
pos = 0
for frameIndex in range(frames):
image.seek(frameIndex)
frame = image.convert("RGBA")
if CONF_RESIZE in config:
frame = frame.resize([width, height])
pixels = list(frame.getdata())
if len(pixels) != height * width:
raise core.EsphomeError(
f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height * width})"
)
for r, g, b, a in pixels:
R = r >> 3
G = g >> 2
B = b >> 3
rgb = (R << 11) | (G << 5) | B
if transparent:
if rgb == 0x0020:
rgb = 0
if a < 0x80:
rgb = 0x0020
data[pos] = rgb >> 8
pos += 1
data[pos] = rgb & 0xFF
pos += 1
elif config[CONF_TYPE] in ["BINARY", "TRANSPARENT_BINARY"]:
width8 = ((width + 7) // 8) * 8
data = [0 for _ in range((height * width8 // 8) * frames)]
for frameIndex in range(frames):
image.seek(frameIndex)
if transparent:
alpha = image.split()[-1]
has_alpha = alpha.getextrema()[0] < 0xFF
else:
has_alpha = False
frame = image.convert("1", dither=Image.Dither.NONE)
if CONF_RESIZE in config:
frame = frame.resize([width, height])
if transparent:
alpha = alpha.resize([width, height])
for x, y in [(i, j) for i in range(width) for j in range(height)]:
if transparent and has_alpha:
if not alpha.getpixel((x, y)):
continue
elif frame.getpixel((x, y)):
continue
pos = x + y * width8 + (height * width8 * frameIndex)
data[pos // 8] |= 0x80 >> (pos % 8)
else:
raise core.EsphomeError(
f"Animation f{config[CONF_ID]} has not supported type {config[CONF_TYPE]}."
)
rhs = [HexInt(x) for x in data]
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
var = cg.new_Pvariable( var = cg.new_Pvariable(
config[CONF_ID], config[CONF_ID],
prog_arr, prog_arr,
width, width,
height, height,
frames, frame_count,
espImage.IMAGE_TYPE[config[CONF_TYPE]], image_type,
trans_value,
) )
cg.add(var.set_transparency(transparent))
if loop_config := config.get(CONF_LOOP): if loop_config := config.get(CONF_LOOP):
start = loop_config[CONF_START_FRAME] start = loop_config[CONF_START_FRAME]
end = loop_config.get(CONF_END_FRAME, frames) end = loop_config.get(CONF_END_FRAME, frame_count)
count = loop_config.get(CONF_REPEAT, -1) count = loop_config.get(CONF_REPEAT, -1)
cg.add(var.set_loop(start, end, count)) cg.add(var.set_loop(start, end, count))

View File

@@ -6,8 +6,8 @@ namespace esphome {
namespace animation { namespace animation {
Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count,
image::ImageType type) image::ImageType type, image::Transparency transparent)
: Image(data_start, width, height, type), : Image(data_start, width, height, type, transparent),
animation_data_start_(data_start), animation_data_start_(data_start),
current_frame_(0), current_frame_(0),
animation_frame_count_(animation_frame_count), animation_frame_count_(animation_frame_count),
@@ -62,7 +62,7 @@ void Animation::set_frame(int frame) {
} }
void Animation::update_data_start_() { void Animation::update_data_start_() {
const uint32_t image_size = image_type_to_width_stride(this->width_, this->type_) * this->height_; const uint32_t image_size = this->get_width_stride() * this->height_;
this->data_start_ = this->animation_data_start_ + image_size * this->current_frame_; this->data_start_ = this->animation_data_start_ + image_size * this->current_frame_;
} }

View File

@@ -8,7 +8,8 @@ namespace animation {
class Animation : public image::Image { class Animation : public image::Image {
public: public:
Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, image::ImageType type); Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, image::ImageType type,
image::Transparency transparent);
uint32_t get_animation_frame_count() const; uint32_t get_animation_frame_count() const;
int get_current_frame() const; int get_current_frame() const;

View File

@@ -122,7 +122,8 @@ void APDS9306::update() {
this->status_clear_warning(); this->status_clear_warning();
if (!(status &= 0b00001000)) { // No new data status &= 0b00001000;
if (!status) { // No new data
return; return;
} }

View File

@@ -227,6 +227,9 @@ message DeviceInfoResponse {
uint32 voice_assistant_feature_flags = 17; uint32 voice_assistant_feature_flags = 17;
string suggested_area = 16; string suggested_area = 16;
// The Bluetooth mac address of the device. For example "AC:BC:32:89:0E:AA"
string bluetooth_mac_address = 18;
} }
message ListEntitiesRequest { message ListEntitiesRequest {
@@ -1381,6 +1384,7 @@ message BluetoothConnectionsFreeResponse {
uint32 free = 1; uint32 free = 1;
uint32 limit = 2; uint32 limit = 2;
repeated uint64 allocated = 3;
} }
message BluetoothGATTErrorResponse { message BluetoothGATTErrorResponse {
@@ -1563,6 +1567,8 @@ message VoiceAssistantAnnounceRequest {
string media_id = 1; string media_id = 1;
string text = 2; string text = 2;
string preannounce_media_id = 3;
bool start_conversation = 4;
} }
message VoiceAssistantAnnounceFinished { message VoiceAssistantAnnounceFinished {

View File

@@ -28,8 +28,38 @@ namespace api {
static const char *const TAG = "api.connection"; static const char *const TAG = "api.connection";
static const int ESP32_CAMERA_STOP_STREAM = 5000; static const int ESP32_CAMERA_STOP_STREAM = 5000;
// helper for allowing only unique entries in the queue
void DeferredMessageQueue::dmq_push_back_with_dedup_(void *source, send_message_t *send_message) {
DeferredMessage item(source, send_message);
auto iter = std::find_if(this->deferred_queue_.begin(), this->deferred_queue_.end(),
[&item](const DeferredMessage &test) -> bool { return test == item; });
if (iter != this->deferred_queue_.end()) {
(*iter) = item;
} else {
this->deferred_queue_.push_back(item);
}
}
void DeferredMessageQueue::process_queue() {
while (!deferred_queue_.empty()) {
DeferredMessage &de = deferred_queue_.front();
if (de.send_message_(this->api_connection_, de.source_)) {
// O(n) but memory efficiency is more important than speed here which is why std::vector was chosen
deferred_queue_.erase(deferred_queue_.begin());
} else {
break;
}
}
}
void DeferredMessageQueue::defer(void *source, send_message_t *send_message) {
this->dmq_push_back_with_dedup_(source, send_message);
}
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent) APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
: parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) { : parent_(parent), deferred_message_queue_(this), initial_state_iterator_(this), list_entities_iterator_(this) {
this->proto_write_buffer_.reserve(64); this->proto_write_buffer_.reserve(64);
#if defined(USE_API_PLAINTEXT) #if defined(USE_API_PLAINTEXT)
@@ -116,8 +146,12 @@ void APIConnection::loop() {
return; return;
} }
this->list_entities_iterator_.advance(); this->deferred_message_queue_.process_queue();
this->initial_state_iterator_.advance();
if (!this->list_entities_iterator_.completed())
this->list_entities_iterator_.advance();
if (!this->initial_state_iterator_.completed() && this->list_entities_iterator_.completed())
this->initial_state_iterator_.advance();
static uint32_t keepalive = 60000; static uint32_t keepalive = 60000;
static uint8_t max_ping_retries = 60; static uint8_t max_ping_retries = 60;
@@ -210,13 +244,31 @@ bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary
if (!this->state_subscription_) if (!this->state_subscription_)
return false; return false;
if (!APIConnection::try_send_binary_sensor_state(this, binary_sensor, state)) {
this->deferred_message_queue_.defer(binary_sensor, try_send_binary_sensor_state);
}
return true;
}
void APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor) {
if (!APIConnection::try_send_binary_sensor_info(this, binary_sensor)) {
this->deferred_message_queue_.defer(binary_sensor, try_send_binary_sensor_info);
}
}
bool APIConnection::try_send_binary_sensor_state(APIConnection *api, void *v_binary_sensor) {
binary_sensor::BinarySensor *binary_sensor = reinterpret_cast<binary_sensor::BinarySensor *>(v_binary_sensor);
return APIConnection::try_send_binary_sensor_state(api, binary_sensor, binary_sensor->state);
}
bool APIConnection::try_send_binary_sensor_state(APIConnection *api, binary_sensor::BinarySensor *binary_sensor,
bool state) {
BinarySensorStateResponse resp; BinarySensorStateResponse resp;
resp.key = binary_sensor->get_object_id_hash(); resp.key = binary_sensor->get_object_id_hash();
resp.state = state; resp.state = state;
resp.missing_state = !binary_sensor->has_state(); resp.missing_state = !binary_sensor->has_state();
return this->send_binary_sensor_state_response(resp); return api->send_binary_sensor_state_response(resp);
} }
bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor) { bool APIConnection::try_send_binary_sensor_info(APIConnection *api, void *v_binary_sensor) {
binary_sensor::BinarySensor *binary_sensor = reinterpret_cast<binary_sensor::BinarySensor *>(v_binary_sensor);
ListEntitiesBinarySensorResponse msg; ListEntitiesBinarySensorResponse msg;
msg.object_id = binary_sensor->get_object_id(); msg.object_id = binary_sensor->get_object_id();
msg.key = binary_sensor->get_object_id_hash(); msg.key = binary_sensor->get_object_id_hash();
@@ -228,7 +280,7 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_
msg.disabled_by_default = binary_sensor->is_disabled_by_default(); msg.disabled_by_default = binary_sensor->is_disabled_by_default();
msg.icon = binary_sensor->get_icon(); msg.icon = binary_sensor->get_icon();
msg.entity_category = static_cast<enums::EntityCategory>(binary_sensor->get_entity_category()); msg.entity_category = static_cast<enums::EntityCategory>(binary_sensor->get_entity_category());
return this->send_list_entities_binary_sensor_response(msg); return api->send_list_entities_binary_sensor_response(msg);
} }
#endif #endif
@@ -237,6 +289,19 @@ bool APIConnection::send_cover_state(cover::Cover *cover) {
if (!this->state_subscription_) if (!this->state_subscription_)
return false; return false;
if (!APIConnection::try_send_cover_state(this, cover)) {
this->deferred_message_queue_.defer(cover, try_send_cover_state);
}
return true;
}
void APIConnection::send_cover_info(cover::Cover *cover) {
if (!APIConnection::try_send_cover_info(this, cover)) {
this->deferred_message_queue_.defer(cover, try_send_cover_info);
}
}
bool APIConnection::try_send_cover_state(APIConnection *api, void *v_cover) {
cover::Cover *cover = reinterpret_cast<cover::Cover *>(v_cover);
auto traits = cover->get_traits(); auto traits = cover->get_traits();
CoverStateResponse resp{}; CoverStateResponse resp{};
resp.key = cover->get_object_id_hash(); resp.key = cover->get_object_id_hash();
@@ -246,9 +311,10 @@ bool APIConnection::send_cover_state(cover::Cover *cover) {
if (traits.get_supports_tilt()) if (traits.get_supports_tilt())
resp.tilt = cover->tilt; resp.tilt = cover->tilt;
resp.current_operation = static_cast<enums::CoverOperation>(cover->current_operation); resp.current_operation = static_cast<enums::CoverOperation>(cover->current_operation);
return this->send_cover_state_response(resp); return api->send_cover_state_response(resp);
} }
bool APIConnection::send_cover_info(cover::Cover *cover) { bool APIConnection::try_send_cover_info(APIConnection *api, void *v_cover) {
cover::Cover *cover = reinterpret_cast<cover::Cover *>(v_cover);
auto traits = cover->get_traits(); auto traits = cover->get_traits();
ListEntitiesCoverResponse msg; ListEntitiesCoverResponse msg;
msg.key = cover->get_object_id_hash(); msg.key = cover->get_object_id_hash();
@@ -264,7 +330,7 @@ bool APIConnection::send_cover_info(cover::Cover *cover) {
msg.disabled_by_default = cover->is_disabled_by_default(); msg.disabled_by_default = cover->is_disabled_by_default();
msg.icon = cover->get_icon(); msg.icon = cover->get_icon();
msg.entity_category = static_cast<enums::EntityCategory>(cover->get_entity_category()); msg.entity_category = static_cast<enums::EntityCategory>(cover->get_entity_category());
return this->send_list_entities_cover_response(msg); return api->send_list_entities_cover_response(msg);
} }
void APIConnection::cover_command(const CoverCommandRequest &msg) { void APIConnection::cover_command(const CoverCommandRequest &msg) {
cover::Cover *cover = App.get_cover_by_key(msg.key); cover::Cover *cover = App.get_cover_by_key(msg.key);
@@ -300,6 +366,19 @@ bool APIConnection::send_fan_state(fan::Fan *fan) {
if (!this->state_subscription_) if (!this->state_subscription_)
return false; return false;
if (!APIConnection::try_send_fan_state(this, fan)) {
this->deferred_message_queue_.defer(fan, try_send_fan_state);
}
return true;
}
void APIConnection::send_fan_info(fan::Fan *fan) {
if (!APIConnection::try_send_fan_info(this, fan)) {
this->deferred_message_queue_.defer(fan, try_send_fan_info);
}
}
bool APIConnection::try_send_fan_state(APIConnection *api, void *v_fan) {
fan::Fan *fan = reinterpret_cast<fan::Fan *>(v_fan);
auto traits = fan->get_traits(); auto traits = fan->get_traits();
FanStateResponse resp{}; FanStateResponse resp{};
resp.key = fan->get_object_id_hash(); resp.key = fan->get_object_id_hash();
@@ -313,9 +392,10 @@ bool APIConnection::send_fan_state(fan::Fan *fan) {
resp.direction = static_cast<enums::FanDirection>(fan->direction); resp.direction = static_cast<enums::FanDirection>(fan->direction);
if (traits.supports_preset_modes()) if (traits.supports_preset_modes())
resp.preset_mode = fan->preset_mode; resp.preset_mode = fan->preset_mode;
return this->send_fan_state_response(resp); return api->send_fan_state_response(resp);
} }
bool APIConnection::send_fan_info(fan::Fan *fan) { bool APIConnection::try_send_fan_info(APIConnection *api, void *v_fan) {
fan::Fan *fan = reinterpret_cast<fan::Fan *>(v_fan);
auto traits = fan->get_traits(); auto traits = fan->get_traits();
ListEntitiesFanResponse msg; ListEntitiesFanResponse msg;
msg.key = fan->get_object_id_hash(); msg.key = fan->get_object_id_hash();
@@ -332,7 +412,7 @@ bool APIConnection::send_fan_info(fan::Fan *fan) {
msg.disabled_by_default = fan->is_disabled_by_default(); msg.disabled_by_default = fan->is_disabled_by_default();
msg.icon = fan->get_icon(); msg.icon = fan->get_icon();
msg.entity_category = static_cast<enums::EntityCategory>(fan->get_entity_category()); msg.entity_category = static_cast<enums::EntityCategory>(fan->get_entity_category());
return this->send_list_entities_fan_response(msg); return api->send_list_entities_fan_response(msg);
} }
void APIConnection::fan_command(const FanCommandRequest &msg) { void APIConnection::fan_command(const FanCommandRequest &msg) {
fan::Fan *fan = App.get_fan_by_key(msg.key); fan::Fan *fan = App.get_fan_by_key(msg.key);
@@ -361,6 +441,19 @@ bool APIConnection::send_light_state(light::LightState *light) {
if (!this->state_subscription_) if (!this->state_subscription_)
return false; return false;
if (!APIConnection::try_send_light_state(this, light)) {
this->deferred_message_queue_.defer(light, try_send_light_state);
}
return true;
}
void APIConnection::send_light_info(light::LightState *light) {
if (!APIConnection::try_send_light_info(this, light)) {
this->deferred_message_queue_.defer(light, try_send_light_info);
}
}
bool APIConnection::try_send_light_state(APIConnection *api, void *v_light) {
light::LightState *light = reinterpret_cast<light::LightState *>(v_light);
auto traits = light->get_traits(); auto traits = light->get_traits();
auto values = light->remote_values; auto values = light->remote_values;
auto color_mode = values.get_color_mode(); auto color_mode = values.get_color_mode();
@@ -380,9 +473,10 @@ bool APIConnection::send_light_state(light::LightState *light) {
resp.warm_white = values.get_warm_white(); resp.warm_white = values.get_warm_white();
if (light->supports_effects()) if (light->supports_effects())
resp.effect = light->get_effect_name(); resp.effect = light->get_effect_name();
return this->send_light_state_response(resp); return api->send_light_state_response(resp);
} }
bool APIConnection::send_light_info(light::LightState *light) { bool APIConnection::try_send_light_info(APIConnection *api, void *v_light) {
light::LightState *light = reinterpret_cast<light::LightState *>(v_light);
auto traits = light->get_traits(); auto traits = light->get_traits();
ListEntitiesLightResponse msg; ListEntitiesLightResponse msg;
msg.key = light->get_object_id_hash(); msg.key = light->get_object_id_hash();
@@ -415,7 +509,7 @@ bool APIConnection::send_light_info(light::LightState *light) {
for (auto *effect : light->get_effects()) for (auto *effect : light->get_effects())
msg.effects.push_back(effect->get_name()); msg.effects.push_back(effect->get_name());
} }
return this->send_list_entities_light_response(msg); return api->send_list_entities_light_response(msg);
} }
void APIConnection::light_command(const LightCommandRequest &msg) { void APIConnection::light_command(const LightCommandRequest &msg) {
light::LightState *light = App.get_light_by_key(msg.key); light::LightState *light = App.get_light_by_key(msg.key);
@@ -459,13 +553,30 @@ bool APIConnection::send_sensor_state(sensor::Sensor *sensor, float state) {
if (!this->state_subscription_) if (!this->state_subscription_)
return false; return false;
if (!APIConnection::try_send_sensor_state(this, sensor, state)) {
this->deferred_message_queue_.defer(sensor, try_send_sensor_state);
}
return true;
}
void APIConnection::send_sensor_info(sensor::Sensor *sensor) {
if (!APIConnection::try_send_sensor_info(this, sensor)) {
this->deferred_message_queue_.defer(sensor, try_send_sensor_info);
}
}
bool APIConnection::try_send_sensor_state(APIConnection *api, void *v_sensor) {
sensor::Sensor *sensor = reinterpret_cast<sensor::Sensor *>(v_sensor);
return APIConnection::try_send_sensor_state(api, sensor, sensor->state);
}
bool APIConnection::try_send_sensor_state(APIConnection *api, sensor::Sensor *sensor, float state) {
SensorStateResponse resp{}; SensorStateResponse resp{};
resp.key = sensor->get_object_id_hash(); resp.key = sensor->get_object_id_hash();
resp.state = state; resp.state = state;
resp.missing_state = !sensor->has_state(); resp.missing_state = !sensor->has_state();
return this->send_sensor_state_response(resp); return api->send_sensor_state_response(resp);
} }
bool APIConnection::send_sensor_info(sensor::Sensor *sensor) { bool APIConnection::try_send_sensor_info(APIConnection *api, void *v_sensor) {
sensor::Sensor *sensor = reinterpret_cast<sensor::Sensor *>(v_sensor);
ListEntitiesSensorResponse msg; ListEntitiesSensorResponse msg;
msg.key = sensor->get_object_id_hash(); msg.key = sensor->get_object_id_hash();
msg.object_id = sensor->get_object_id(); msg.object_id = sensor->get_object_id();
@@ -482,7 +593,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
msg.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class()); msg.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class());
msg.disabled_by_default = sensor->is_disabled_by_default(); msg.disabled_by_default = sensor->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(sensor->get_entity_category()); msg.entity_category = static_cast<enums::EntityCategory>(sensor->get_entity_category());
return this->send_list_entities_sensor_response(msg); return api->send_list_entities_sensor_response(msg);
} }
#endif #endif
@@ -491,12 +602,29 @@ bool APIConnection::send_switch_state(switch_::Switch *a_switch, bool state) {
if (!this->state_subscription_) if (!this->state_subscription_)
return false; return false;
if (!APIConnection::try_send_switch_state(this, a_switch, state)) {
this->deferred_message_queue_.defer(a_switch, try_send_switch_state);
}
return true;
}
void APIConnection::send_switch_info(switch_::Switch *a_switch) {
if (!APIConnection::try_send_switch_info(this, a_switch)) {
this->deferred_message_queue_.defer(a_switch, try_send_switch_info);
}
}
bool APIConnection::try_send_switch_state(APIConnection *api, void *v_a_switch) {
switch_::Switch *a_switch = reinterpret_cast<switch_::Switch *>(v_a_switch);
return APIConnection::try_send_switch_state(api, a_switch, a_switch->state);
}
bool APIConnection::try_send_switch_state(APIConnection *api, switch_::Switch *a_switch, bool state) {
SwitchStateResponse resp{}; SwitchStateResponse resp{};
resp.key = a_switch->get_object_id_hash(); resp.key = a_switch->get_object_id_hash();
resp.state = state; resp.state = state;
return this->send_switch_state_response(resp); return api->send_switch_state_response(resp);
} }
bool APIConnection::send_switch_info(switch_::Switch *a_switch) { bool APIConnection::try_send_switch_info(APIConnection *api, void *v_a_switch) {
switch_::Switch *a_switch = reinterpret_cast<switch_::Switch *>(v_a_switch);
ListEntitiesSwitchResponse msg; ListEntitiesSwitchResponse msg;
msg.key = a_switch->get_object_id_hash(); msg.key = a_switch->get_object_id_hash();
msg.object_id = a_switch->get_object_id(); msg.object_id = a_switch->get_object_id();
@@ -508,7 +636,7 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) {
msg.disabled_by_default = a_switch->is_disabled_by_default(); msg.disabled_by_default = a_switch->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(a_switch->get_entity_category()); msg.entity_category = static_cast<enums::EntityCategory>(a_switch->get_entity_category());
msg.device_class = a_switch->get_device_class(); msg.device_class = a_switch->get_device_class();
return this->send_list_entities_switch_response(msg); return api->send_list_entities_switch_response(msg);
} }
void APIConnection::switch_command(const SwitchCommandRequest &msg) { void APIConnection::switch_command(const SwitchCommandRequest &msg) {
switch_::Switch *a_switch = App.get_switch_by_key(msg.key); switch_::Switch *a_switch = App.get_switch_by_key(msg.key);
@@ -528,13 +656,31 @@ bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor,
if (!this->state_subscription_) if (!this->state_subscription_)
return false; return false;
if (!APIConnection::try_send_text_sensor_state(this, text_sensor, std::move(state))) {
this->deferred_message_queue_.defer(text_sensor, try_send_text_sensor_state);
}
return true;
}
void APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor) {
if (!APIConnection::try_send_text_sensor_info(this, text_sensor)) {
this->deferred_message_queue_.defer(text_sensor, try_send_text_sensor_info);
}
}
bool APIConnection::try_send_text_sensor_state(APIConnection *api, void *v_text_sensor) {
text_sensor::TextSensor *text_sensor = reinterpret_cast<text_sensor::TextSensor *>(v_text_sensor);
return APIConnection::try_send_text_sensor_state(api, text_sensor, text_sensor->state);
}
bool APIConnection::try_send_text_sensor_state(APIConnection *api, text_sensor::TextSensor *text_sensor,
std::string state) {
TextSensorStateResponse resp{}; TextSensorStateResponse resp{};
resp.key = text_sensor->get_object_id_hash(); resp.key = text_sensor->get_object_id_hash();
resp.state = std::move(state); resp.state = std::move(state);
resp.missing_state = !text_sensor->has_state(); resp.missing_state = !text_sensor->has_state();
return this->send_text_sensor_state_response(resp); return api->send_text_sensor_state_response(resp);
} }
bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor) { bool APIConnection::try_send_text_sensor_info(APIConnection *api, void *v_text_sensor) {
text_sensor::TextSensor *text_sensor = reinterpret_cast<text_sensor::TextSensor *>(v_text_sensor);
ListEntitiesTextSensorResponse msg; ListEntitiesTextSensorResponse msg;
msg.key = text_sensor->get_object_id_hash(); msg.key = text_sensor->get_object_id_hash();
msg.object_id = text_sensor->get_object_id(); msg.object_id = text_sensor->get_object_id();
@@ -546,7 +692,7 @@ bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor)
msg.disabled_by_default = text_sensor->is_disabled_by_default(); msg.disabled_by_default = text_sensor->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(text_sensor->get_entity_category()); msg.entity_category = static_cast<enums::EntityCategory>(text_sensor->get_entity_category());
msg.device_class = text_sensor->get_device_class(); msg.device_class = text_sensor->get_device_class();
return this->send_list_entities_text_sensor_response(msg); return api->send_list_entities_text_sensor_response(msg);
} }
#endif #endif
@@ -555,6 +701,19 @@ bool APIConnection::send_climate_state(climate::Climate *climate) {
if (!this->state_subscription_) if (!this->state_subscription_)
return false; return false;
if (!APIConnection::try_send_climate_state(this, climate)) {
this->deferred_message_queue_.defer(climate, try_send_climate_state);
}
return true;
}
void APIConnection::send_climate_info(climate::Climate *climate) {
if (!APIConnection::try_send_climate_info(this, climate)) {
this->deferred_message_queue_.defer(climate, try_send_climate_info);
}
}
bool APIConnection::try_send_climate_state(APIConnection *api, void *v_climate) {
climate::Climate *climate = reinterpret_cast<climate::Climate *>(v_climate);
auto traits = climate->get_traits(); auto traits = climate->get_traits();
ClimateStateResponse resp{}; ClimateStateResponse resp{};
resp.key = climate->get_object_id_hash(); resp.key = climate->get_object_id_hash();
@@ -583,9 +742,10 @@ bool APIConnection::send_climate_state(climate::Climate *climate) {
resp.current_humidity = climate->current_humidity; resp.current_humidity = climate->current_humidity;
if (traits.get_supports_target_humidity()) if (traits.get_supports_target_humidity())
resp.target_humidity = climate->target_humidity; resp.target_humidity = climate->target_humidity;
return this->send_climate_state_response(resp); return api->send_climate_state_response(resp);
} }
bool APIConnection::send_climate_info(climate::Climate *climate) { bool APIConnection::try_send_climate_info(APIConnection *api, void *v_climate) {
climate::Climate *climate = reinterpret_cast<climate::Climate *>(v_climate);
auto traits = climate->get_traits(); auto traits = climate->get_traits();
ListEntitiesClimateResponse msg; ListEntitiesClimateResponse msg;
msg.key = climate->get_object_id_hash(); msg.key = climate->get_object_id_hash();
@@ -626,7 +786,7 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
msg.supported_custom_presets.push_back(custom_preset); msg.supported_custom_presets.push_back(custom_preset);
for (auto swing_mode : traits.get_supported_swing_modes()) for (auto swing_mode : traits.get_supported_swing_modes())
msg.supported_swing_modes.push_back(static_cast<enums::ClimateSwingMode>(swing_mode)); msg.supported_swing_modes.push_back(static_cast<enums::ClimateSwingMode>(swing_mode));
return this->send_list_entities_climate_response(msg); return api->send_list_entities_climate_response(msg);
} }
void APIConnection::climate_command(const ClimateCommandRequest &msg) { void APIConnection::climate_command(const ClimateCommandRequest &msg) {
climate::Climate *climate = App.get_climate_by_key(msg.key); climate::Climate *climate = App.get_climate_by_key(msg.key);
@@ -663,13 +823,30 @@ bool APIConnection::send_number_state(number::Number *number, float state) {
if (!this->state_subscription_) if (!this->state_subscription_)
return false; return false;
if (!APIConnection::try_send_number_state(this, number, state)) {
this->deferred_message_queue_.defer(number, try_send_number_state);
}
return true;
}
void APIConnection::send_number_info(number::Number *number) {
if (!APIConnection::try_send_number_info(this, number)) {
this->deferred_message_queue_.defer(number, try_send_number_info);
}
}
bool APIConnection::try_send_number_state(APIConnection *api, void *v_number) {
number::Number *number = reinterpret_cast<number::Number *>(v_number);
return APIConnection::try_send_number_state(api, number, number->state);
}
bool APIConnection::try_send_number_state(APIConnection *api, number::Number *number, float state) {
NumberStateResponse resp{}; NumberStateResponse resp{};
resp.key = number->get_object_id_hash(); resp.key = number->get_object_id_hash();
resp.state = state; resp.state = state;
resp.missing_state = !number->has_state(); resp.missing_state = !number->has_state();
return this->send_number_state_response(resp); return api->send_number_state_response(resp);
} }
bool APIConnection::send_number_info(number::Number *number) { bool APIConnection::try_send_number_info(APIConnection *api, void *v_number) {
number::Number *number = reinterpret_cast<number::Number *>(v_number);
ListEntitiesNumberResponse msg; ListEntitiesNumberResponse msg;
msg.key = number->get_object_id_hash(); msg.key = number->get_object_id_hash();
msg.object_id = number->get_object_id(); msg.object_id = number->get_object_id();
@@ -687,7 +864,7 @@ bool APIConnection::send_number_info(number::Number *number) {
msg.max_value = number->traits.get_max_value(); msg.max_value = number->traits.get_max_value();
msg.step = number->traits.get_step(); msg.step = number->traits.get_step();
return this->send_list_entities_number_response(msg); return api->send_list_entities_number_response(msg);
} }
void APIConnection::number_command(const NumberCommandRequest &msg) { void APIConnection::number_command(const NumberCommandRequest &msg) {
number::Number *number = App.get_number_by_key(msg.key); number::Number *number = App.get_number_by_key(msg.key);
@@ -705,15 +882,29 @@ bool APIConnection::send_date_state(datetime::DateEntity *date) {
if (!this->state_subscription_) if (!this->state_subscription_)
return false; return false;
if (!APIConnection::try_send_date_state(this, date)) {
this->deferred_message_queue_.defer(date, try_send_date_state);
}
return true;
}
void APIConnection::send_date_info(datetime::DateEntity *date) {
if (!APIConnection::try_send_date_info(this, date)) {
this->deferred_message_queue_.defer(date, try_send_date_info);
}
}
bool APIConnection::try_send_date_state(APIConnection *api, void *v_date) {
datetime::DateEntity *date = reinterpret_cast<datetime::DateEntity *>(v_date);
DateStateResponse resp{}; DateStateResponse resp{};
resp.key = date->get_object_id_hash(); resp.key = date->get_object_id_hash();
resp.missing_state = !date->has_state(); resp.missing_state = !date->has_state();
resp.year = date->year; resp.year = date->year;
resp.month = date->month; resp.month = date->month;
resp.day = date->day; resp.day = date->day;
return this->send_date_state_response(resp); return api->send_date_state_response(resp);
} }
bool APIConnection::send_date_info(datetime::DateEntity *date) { bool APIConnection::try_send_date_info(APIConnection *api, void *v_date) {
datetime::DateEntity *date = reinterpret_cast<datetime::DateEntity *>(v_date);
ListEntitiesDateResponse msg; ListEntitiesDateResponse msg;
msg.key = date->get_object_id_hash(); msg.key = date->get_object_id_hash();
msg.object_id = date->get_object_id(); msg.object_id = date->get_object_id();
@@ -724,7 +915,7 @@ bool APIConnection::send_date_info(datetime::DateEntity *date) {
msg.disabled_by_default = date->is_disabled_by_default(); msg.disabled_by_default = date->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(date->get_entity_category()); msg.entity_category = static_cast<enums::EntityCategory>(date->get_entity_category());
return this->send_list_entities_date_response(msg); return api->send_list_entities_date_response(msg);
} }
void APIConnection::date_command(const DateCommandRequest &msg) { void APIConnection::date_command(const DateCommandRequest &msg) {
datetime::DateEntity *date = App.get_date_by_key(msg.key); datetime::DateEntity *date = App.get_date_by_key(msg.key);
@@ -742,15 +933,29 @@ bool APIConnection::send_time_state(datetime::TimeEntity *time) {
if (!this->state_subscription_) if (!this->state_subscription_)
return false; return false;
if (!APIConnection::try_send_time_state(this, time)) {
this->deferred_message_queue_.defer(time, try_send_time_state);
}
return true;
}
void APIConnection::send_time_info(datetime::TimeEntity *time) {
if (!APIConnection::try_send_time_info(this, time)) {
this->deferred_message_queue_.defer(time, try_send_time_info);
}
}
bool APIConnection::try_send_time_state(APIConnection *api, void *v_time) {
datetime::TimeEntity *time = reinterpret_cast<datetime::TimeEntity *>(v_time);
TimeStateResponse resp{}; TimeStateResponse resp{};
resp.key = time->get_object_id_hash(); resp.key = time->get_object_id_hash();
resp.missing_state = !time->has_state(); resp.missing_state = !time->has_state();
resp.hour = time->hour; resp.hour = time->hour;
resp.minute = time->minute; resp.minute = time->minute;
resp.second = time->second; resp.second = time->second;
return this->send_time_state_response(resp); return api->send_time_state_response(resp);
} }
bool APIConnection::send_time_info(datetime::TimeEntity *time) { bool APIConnection::try_send_time_info(APIConnection *api, void *v_time) {
datetime::TimeEntity *time = reinterpret_cast<datetime::TimeEntity *>(v_time);
ListEntitiesTimeResponse msg; ListEntitiesTimeResponse msg;
msg.key = time->get_object_id_hash(); msg.key = time->get_object_id_hash();
msg.object_id = time->get_object_id(); msg.object_id = time->get_object_id();
@@ -761,7 +966,7 @@ bool APIConnection::send_time_info(datetime::TimeEntity *time) {
msg.disabled_by_default = time->is_disabled_by_default(); msg.disabled_by_default = time->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(time->get_entity_category()); msg.entity_category = static_cast<enums::EntityCategory>(time->get_entity_category());
return this->send_list_entities_time_response(msg); return api->send_list_entities_time_response(msg);
} }
void APIConnection::time_command(const TimeCommandRequest &msg) { void APIConnection::time_command(const TimeCommandRequest &msg) {
datetime::TimeEntity *time = App.get_time_by_key(msg.key); datetime::TimeEntity *time = App.get_time_by_key(msg.key);
@@ -779,6 +984,19 @@ bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) {
if (!this->state_subscription_) if (!this->state_subscription_)
return false; return false;
if (!APIConnection::try_send_datetime_state(this, datetime)) {
this->deferred_message_queue_.defer(datetime, try_send_datetime_state);
}
return true;
}
void APIConnection::send_datetime_info(datetime::DateTimeEntity *datetime) {
if (!APIConnection::try_send_datetime_info(this, datetime)) {
this->deferred_message_queue_.defer(datetime, try_send_datetime_info);
}
}
bool APIConnection::try_send_datetime_state(APIConnection *api, void *v_datetime) {
datetime::DateTimeEntity *datetime = reinterpret_cast<datetime::DateTimeEntity *>(v_datetime);
DateTimeStateResponse resp{}; DateTimeStateResponse resp{};
resp.key = datetime->get_object_id_hash(); resp.key = datetime->get_object_id_hash();
resp.missing_state = !datetime->has_state(); resp.missing_state = !datetime->has_state();
@@ -786,9 +1004,10 @@ bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) {
ESPTime state = datetime->state_as_esptime(); ESPTime state = datetime->state_as_esptime();
resp.epoch_seconds = state.timestamp; resp.epoch_seconds = state.timestamp;
} }
return this->send_date_time_state_response(resp); return api->send_date_time_state_response(resp);
} }
bool APIConnection::send_datetime_info(datetime::DateTimeEntity *datetime) { bool APIConnection::try_send_datetime_info(APIConnection *api, void *v_datetime) {
datetime::DateTimeEntity *datetime = reinterpret_cast<datetime::DateTimeEntity *>(v_datetime);
ListEntitiesDateTimeResponse msg; ListEntitiesDateTimeResponse msg;
msg.key = datetime->get_object_id_hash(); msg.key = datetime->get_object_id_hash();
msg.object_id = datetime->get_object_id(); msg.object_id = datetime->get_object_id();
@@ -799,7 +1018,7 @@ bool APIConnection::send_datetime_info(datetime::DateTimeEntity *datetime) {
msg.disabled_by_default = datetime->is_disabled_by_default(); msg.disabled_by_default = datetime->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(datetime->get_entity_category()); msg.entity_category = static_cast<enums::EntityCategory>(datetime->get_entity_category());
return this->send_list_entities_date_time_response(msg); return api->send_list_entities_date_time_response(msg);
} }
void APIConnection::datetime_command(const DateTimeCommandRequest &msg) { void APIConnection::datetime_command(const DateTimeCommandRequest &msg) {
datetime::DateTimeEntity *datetime = App.get_datetime_by_key(msg.key); datetime::DateTimeEntity *datetime = App.get_datetime_by_key(msg.key);
@@ -817,13 +1036,30 @@ bool APIConnection::send_text_state(text::Text *text, std::string state) {
if (!this->state_subscription_) if (!this->state_subscription_)
return false; return false;
if (!APIConnection::try_send_text_state(this, text, std::move(state))) {
this->deferred_message_queue_.defer(text, try_send_text_state);
}
return true;
}
void APIConnection::send_text_info(text::Text *text) {
if (!APIConnection::try_send_text_info(this, text)) {
this->deferred_message_queue_.defer(text, try_send_text_info);
}
}
bool APIConnection::try_send_text_state(APIConnection *api, void *v_text) {
text::Text *text = reinterpret_cast<text::Text *>(v_text);
return APIConnection::try_send_text_state(api, text, text->state);
}
bool APIConnection::try_send_text_state(APIConnection *api, text::Text *text, std::string state) {
TextStateResponse resp{}; TextStateResponse resp{};
resp.key = text->get_object_id_hash(); resp.key = text->get_object_id_hash();
resp.state = std::move(state); resp.state = std::move(state);
resp.missing_state = !text->has_state(); resp.missing_state = !text->has_state();
return this->send_text_state_response(resp); return api->send_text_state_response(resp);
} }
bool APIConnection::send_text_info(text::Text *text) { bool APIConnection::try_send_text_info(APIConnection *api, void *v_text) {
text::Text *text = reinterpret_cast<text::Text *>(v_text);
ListEntitiesTextResponse msg; ListEntitiesTextResponse msg;
msg.key = text->get_object_id_hash(); msg.key = text->get_object_id_hash();
msg.object_id = text->get_object_id(); msg.object_id = text->get_object_id();
@@ -837,7 +1073,7 @@ bool APIConnection::send_text_info(text::Text *text) {
msg.max_length = text->traits.get_max_length(); msg.max_length = text->traits.get_max_length();
msg.pattern = text->traits.get_pattern(); msg.pattern = text->traits.get_pattern();
return this->send_list_entities_text_response(msg); return api->send_list_entities_text_response(msg);
} }
void APIConnection::text_command(const TextCommandRequest &msg) { void APIConnection::text_command(const TextCommandRequest &msg) {
text::Text *text = App.get_text_by_key(msg.key); text::Text *text = App.get_text_by_key(msg.key);
@@ -855,13 +1091,30 @@ bool APIConnection::send_select_state(select::Select *select, std::string state)
if (!this->state_subscription_) if (!this->state_subscription_)
return false; return false;
if (!APIConnection::try_send_select_state(this, select, std::move(state))) {
this->deferred_message_queue_.defer(select, try_send_select_state);
}
return true;
}
void APIConnection::send_select_info(select::Select *select) {
if (!APIConnection::try_send_select_info(this, select)) {
this->deferred_message_queue_.defer(select, try_send_select_info);
}
}
bool APIConnection::try_send_select_state(APIConnection *api, void *v_select) {
select::Select *select = reinterpret_cast<select::Select *>(v_select);
return APIConnection::try_send_select_state(api, select, select->state);
}
bool APIConnection::try_send_select_state(APIConnection *api, select::Select *select, std::string state) {
SelectStateResponse resp{}; SelectStateResponse resp{};
resp.key = select->get_object_id_hash(); resp.key = select->get_object_id_hash();
resp.state = std::move(state); resp.state = std::move(state);
resp.missing_state = !select->has_state(); resp.missing_state = !select->has_state();
return this->send_select_state_response(resp); return api->send_select_state_response(resp);
} }
bool APIConnection::send_select_info(select::Select *select) { bool APIConnection::try_send_select_info(APIConnection *api, void *v_select) {
select::Select *select = reinterpret_cast<select::Select *>(v_select);
ListEntitiesSelectResponse msg; ListEntitiesSelectResponse msg;
msg.key = select->get_object_id_hash(); msg.key = select->get_object_id_hash();
msg.object_id = select->get_object_id(); msg.object_id = select->get_object_id();
@@ -875,7 +1128,7 @@ bool APIConnection::send_select_info(select::Select *select) {
for (const auto &option : select->traits.get_options()) for (const auto &option : select->traits.get_options())
msg.options.push_back(option); msg.options.push_back(option);
return this->send_list_entities_select_response(msg); return api->send_list_entities_select_response(msg);
} }
void APIConnection::select_command(const SelectCommandRequest &msg) { void APIConnection::select_command(const SelectCommandRequest &msg) {
select::Select *select = App.get_select_by_key(msg.key); select::Select *select = App.get_select_by_key(msg.key);
@@ -889,7 +1142,13 @@ void APIConnection::select_command(const SelectCommandRequest &msg) {
#endif #endif
#ifdef USE_BUTTON #ifdef USE_BUTTON
bool APIConnection::send_button_info(button::Button *button) { void APIConnection::send_button_info(button::Button *button) {
if (!APIConnection::try_send_button_info(this, button)) {
this->deferred_message_queue_.defer(button, try_send_button_info);
}
}
bool APIConnection::try_send_button_info(APIConnection *api, void *v_button) {
button::Button *button = reinterpret_cast<button::Button *>(v_button);
ListEntitiesButtonResponse msg; ListEntitiesButtonResponse msg;
msg.key = button->get_object_id_hash(); msg.key = button->get_object_id_hash();
msg.object_id = button->get_object_id(); msg.object_id = button->get_object_id();
@@ -900,7 +1159,7 @@ bool APIConnection::send_button_info(button::Button *button) {
msg.disabled_by_default = button->is_disabled_by_default(); msg.disabled_by_default = button->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(button->get_entity_category()); msg.entity_category = static_cast<enums::EntityCategory>(button->get_entity_category());
msg.device_class = button->get_device_class(); msg.device_class = button->get_device_class();
return this->send_list_entities_button_response(msg); return api->send_list_entities_button_response(msg);
} }
void APIConnection::button_command(const ButtonCommandRequest &msg) { void APIConnection::button_command(const ButtonCommandRequest &msg) {
button::Button *button = App.get_button_by_key(msg.key); button::Button *button = App.get_button_by_key(msg.key);
@@ -916,12 +1175,29 @@ bool APIConnection::send_lock_state(lock::Lock *a_lock, lock::LockState state) {
if (!this->state_subscription_) if (!this->state_subscription_)
return false; return false;
if (!APIConnection::try_send_lock_state(this, a_lock, state)) {
this->deferred_message_queue_.defer(a_lock, try_send_lock_state);
}
return true;
}
void APIConnection::send_lock_info(lock::Lock *a_lock) {
if (!APIConnection::try_send_lock_info(this, a_lock)) {
this->deferred_message_queue_.defer(a_lock, try_send_lock_info);
}
}
bool APIConnection::try_send_lock_state(APIConnection *api, void *v_a_lock) {
lock::Lock *a_lock = reinterpret_cast<lock::Lock *>(v_a_lock);
return APIConnection::try_send_lock_state(api, a_lock, a_lock->state);
}
bool APIConnection::try_send_lock_state(APIConnection *api, lock::Lock *a_lock, lock::LockState state) {
LockStateResponse resp{}; LockStateResponse resp{};
resp.key = a_lock->get_object_id_hash(); resp.key = a_lock->get_object_id_hash();
resp.state = static_cast<enums::LockState>(state); resp.state = static_cast<enums::LockState>(state);
return this->send_lock_state_response(resp); return api->send_lock_state_response(resp);
} }
bool APIConnection::send_lock_info(lock::Lock *a_lock) { bool APIConnection::try_send_lock_info(APIConnection *api, void *v_a_lock) {
lock::Lock *a_lock = reinterpret_cast<lock::Lock *>(v_a_lock);
ListEntitiesLockResponse msg; ListEntitiesLockResponse msg;
msg.key = a_lock->get_object_id_hash(); msg.key = a_lock->get_object_id_hash();
msg.object_id = a_lock->get_object_id(); msg.object_id = a_lock->get_object_id();
@@ -934,7 +1210,7 @@ bool APIConnection::send_lock_info(lock::Lock *a_lock) {
msg.entity_category = static_cast<enums::EntityCategory>(a_lock->get_entity_category()); msg.entity_category = static_cast<enums::EntityCategory>(a_lock->get_entity_category());
msg.supports_open = a_lock->traits.get_supports_open(); msg.supports_open = a_lock->traits.get_supports_open();
msg.requires_code = a_lock->traits.get_requires_code(); msg.requires_code = a_lock->traits.get_requires_code();
return this->send_list_entities_lock_response(msg); return api->send_list_entities_lock_response(msg);
} }
void APIConnection::lock_command(const LockCommandRequest &msg) { void APIConnection::lock_command(const LockCommandRequest &msg) {
lock::Lock *a_lock = App.get_lock_by_key(msg.key); lock::Lock *a_lock = App.get_lock_by_key(msg.key);
@@ -960,13 +1236,27 @@ bool APIConnection::send_valve_state(valve::Valve *valve) {
if (!this->state_subscription_) if (!this->state_subscription_)
return false; return false;
if (!APIConnection::try_send_valve_state(this, valve)) {
this->deferred_message_queue_.defer(valve, try_send_valve_state);
}
return true;
}
void APIConnection::send_valve_info(valve::Valve *valve) {
if (!APIConnection::try_send_valve_info(this, valve)) {
this->deferred_message_queue_.defer(valve, try_send_valve_info);
}
}
bool APIConnection::try_send_valve_state(APIConnection *api, void *v_valve) {
valve::Valve *valve = reinterpret_cast<valve::Valve *>(v_valve);
ValveStateResponse resp{}; ValveStateResponse resp{};
resp.key = valve->get_object_id_hash(); resp.key = valve->get_object_id_hash();
resp.position = valve->position; resp.position = valve->position;
resp.current_operation = static_cast<enums::ValveOperation>(valve->current_operation); resp.current_operation = static_cast<enums::ValveOperation>(valve->current_operation);
return this->send_valve_state_response(resp); return api->send_valve_state_response(resp);
} }
bool APIConnection::send_valve_info(valve::Valve *valve) { bool APIConnection::try_send_valve_info(APIConnection *api, void *v_valve) {
valve::Valve *valve = reinterpret_cast<valve::Valve *>(v_valve);
auto traits = valve->get_traits(); auto traits = valve->get_traits();
ListEntitiesValveResponse msg; ListEntitiesValveResponse msg;
msg.key = valve->get_object_id_hash(); msg.key = valve->get_object_id_hash();
@@ -981,7 +1271,7 @@ bool APIConnection::send_valve_info(valve::Valve *valve) {
msg.assumed_state = traits.get_is_assumed_state(); msg.assumed_state = traits.get_is_assumed_state();
msg.supports_position = traits.get_supports_position(); msg.supports_position = traits.get_supports_position();
msg.supports_stop = traits.get_supports_stop(); msg.supports_stop = traits.get_supports_stop();
return this->send_list_entities_valve_response(msg); return api->send_list_entities_valve_response(msg);
} }
void APIConnection::valve_command(const ValveCommandRequest &msg) { void APIConnection::valve_command(const ValveCommandRequest &msg) {
valve::Valve *valve = App.get_valve_by_key(msg.key); valve::Valve *valve = App.get_valve_by_key(msg.key);
@@ -1002,6 +1292,19 @@ bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_pla
if (!this->state_subscription_) if (!this->state_subscription_)
return false; return false;
if (!APIConnection::try_send_media_player_state(this, media_player)) {
this->deferred_message_queue_.defer(media_player, try_send_media_player_state);
}
return true;
}
void APIConnection::send_media_player_info(media_player::MediaPlayer *media_player) {
if (!APIConnection::try_send_media_player_info(this, media_player)) {
this->deferred_message_queue_.defer(media_player, try_send_media_player_info);
}
}
bool APIConnection::try_send_media_player_state(APIConnection *api, void *v_media_player) {
media_player::MediaPlayer *media_player = reinterpret_cast<media_player::MediaPlayer *>(v_media_player);
MediaPlayerStateResponse resp{}; MediaPlayerStateResponse resp{};
resp.key = media_player->get_object_id_hash(); resp.key = media_player->get_object_id_hash();
@@ -1011,9 +1314,10 @@ bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_pla
resp.state = static_cast<enums::MediaPlayerState>(report_state); resp.state = static_cast<enums::MediaPlayerState>(report_state);
resp.volume = media_player->volume; resp.volume = media_player->volume;
resp.muted = media_player->is_muted(); resp.muted = media_player->is_muted();
return this->send_media_player_state_response(resp); return api->send_media_player_state_response(resp);
} }
bool APIConnection::send_media_player_info(media_player::MediaPlayer *media_player) { bool APIConnection::try_send_media_player_info(APIConnection *api, void *v_media_player) {
media_player::MediaPlayer *media_player = reinterpret_cast<media_player::MediaPlayer *>(v_media_player);
ListEntitiesMediaPlayerResponse msg; ListEntitiesMediaPlayerResponse msg;
msg.key = media_player->get_object_id_hash(); msg.key = media_player->get_object_id_hash();
msg.object_id = media_player->get_object_id(); msg.object_id = media_player->get_object_id();
@@ -1037,7 +1341,7 @@ bool APIConnection::send_media_player_info(media_player::MediaPlayer *media_play
msg.supported_formats.push_back(media_format); msg.supported_formats.push_back(media_format);
} }
return this->send_list_entities_media_player_response(msg); return api->send_list_entities_media_player_response(msg);
} }
void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) { void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) {
media_player::MediaPlayer *media_player = App.get_media_player_by_key(msg.key); media_player::MediaPlayer *media_player = App.get_media_player_by_key(msg.key);
@@ -1062,7 +1366,7 @@ void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) {
#endif #endif
#ifdef USE_ESP32_CAMERA #ifdef USE_ESP32_CAMERA
void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) { void APIConnection::set_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) {
if (!this->state_subscription_) if (!this->state_subscription_)
return; return;
if (this->image_reader_.available()) if (this->image_reader_.available())
@@ -1071,7 +1375,13 @@ void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage>
image->was_requested_by(esphome::esp32_camera::IDLE)) image->was_requested_by(esphome::esp32_camera::IDLE))
this->image_reader_.set_image(std::move(image)); this->image_reader_.set_image(std::move(image));
} }
bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) { void APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
if (!APIConnection::try_send_camera_info(this, camera)) {
this->deferred_message_queue_.defer(camera, try_send_camera_info);
}
}
bool APIConnection::try_send_camera_info(APIConnection *api, void *v_camera) {
esp32_camera::ESP32Camera *camera = reinterpret_cast<esp32_camera::ESP32Camera *>(v_camera);
ListEntitiesCameraResponse msg; ListEntitiesCameraResponse msg;
msg.key = camera->get_object_id_hash(); msg.key = camera->get_object_id_hash();
msg.object_id = camera->get_object_id(); msg.object_id = camera->get_object_id();
@@ -1081,7 +1391,7 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
msg.disabled_by_default = camera->is_disabled_by_default(); msg.disabled_by_default = camera->is_disabled_by_default();
msg.icon = camera->get_icon(); msg.icon = camera->get_icon();
msg.entity_category = static_cast<enums::EntityCategory>(camera->get_entity_category()); msg.entity_category = static_cast<enums::EntityCategory>(camera->get_entity_category());
return this->send_list_entities_camera_response(msg); return api->send_list_entities_camera_response(msg);
} }
void APIConnection::camera_image(const CameraImageRequest &msg) { void APIConnection::camera_image(const CameraImageRequest &msg) {
if (esp32_camera::global_esp32_camera == nullptr) if (esp32_camera::global_esp32_camera == nullptr)
@@ -1268,12 +1578,28 @@ bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmCon
if (!this->state_subscription_) if (!this->state_subscription_)
return false; return false;
if (!APIConnection::try_send_alarm_control_panel_state(this, a_alarm_control_panel)) {
this->deferred_message_queue_.defer(a_alarm_control_panel, try_send_alarm_control_panel_state);
}
return true;
}
void APIConnection::send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
if (!APIConnection::try_send_alarm_control_panel_info(this, a_alarm_control_panel)) {
this->deferred_message_queue_.defer(a_alarm_control_panel, try_send_alarm_control_panel_info);
}
}
bool APIConnection::try_send_alarm_control_panel_state(APIConnection *api, void *v_a_alarm_control_panel) {
alarm_control_panel::AlarmControlPanel *a_alarm_control_panel =
reinterpret_cast<alarm_control_panel::AlarmControlPanel *>(v_a_alarm_control_panel);
AlarmControlPanelStateResponse resp{}; AlarmControlPanelStateResponse resp{};
resp.key = a_alarm_control_panel->get_object_id_hash(); resp.key = a_alarm_control_panel->get_object_id_hash();
resp.state = static_cast<enums::AlarmControlPanelState>(a_alarm_control_panel->get_state()); resp.state = static_cast<enums::AlarmControlPanelState>(a_alarm_control_panel->get_state());
return this->send_alarm_control_panel_state_response(resp); return api->send_alarm_control_panel_state_response(resp);
} }
bool APIConnection::send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { bool APIConnection::try_send_alarm_control_panel_info(APIConnection *api, void *v_a_alarm_control_panel) {
alarm_control_panel::AlarmControlPanel *a_alarm_control_panel =
reinterpret_cast<alarm_control_panel::AlarmControlPanel *>(v_a_alarm_control_panel);
ListEntitiesAlarmControlPanelResponse msg; ListEntitiesAlarmControlPanelResponse msg;
msg.key = a_alarm_control_panel->get_object_id_hash(); msg.key = a_alarm_control_panel->get_object_id_hash();
msg.object_id = a_alarm_control_panel->get_object_id(); msg.object_id = a_alarm_control_panel->get_object_id();
@@ -1285,7 +1611,7 @@ bool APIConnection::send_alarm_control_panel_info(alarm_control_panel::AlarmCont
msg.supported_features = a_alarm_control_panel->get_supported_features(); msg.supported_features = a_alarm_control_panel->get_supported_features();
msg.requires_code = a_alarm_control_panel->get_requires_code(); msg.requires_code = a_alarm_control_panel->get_requires_code();
msg.requires_code_to_arm = a_alarm_control_panel->get_requires_code_to_arm(); msg.requires_code_to_arm = a_alarm_control_panel->get_requires_code_to_arm();
return this->send_list_entities_alarm_control_panel_response(msg); return api->send_list_entities_alarm_control_panel_response(msg);
} }
void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) { void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) {
alarm_control_panel::AlarmControlPanel *a_alarm_control_panel = App.get_alarm_control_panel_by_key(msg.key); alarm_control_panel::AlarmControlPanel *a_alarm_control_panel = App.get_alarm_control_panel_by_key(msg.key);
@@ -1322,13 +1648,28 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe
#endif #endif
#ifdef USE_EVENT #ifdef USE_EVENT
bool APIConnection::send_event(event::Event *event, std::string event_type) { void APIConnection::send_event(event::Event *event, std::string event_type) {
if (!APIConnection::try_send_event(this, event, std::move(event_type))) {
this->deferred_message_queue_.defer(event, try_send_event);
}
}
void APIConnection::send_event_info(event::Event *event) {
if (!APIConnection::try_send_event_info(this, event)) {
this->deferred_message_queue_.defer(event, try_send_event_info);
}
}
bool APIConnection::try_send_event(APIConnection *api, void *v_event) {
event::Event *event = reinterpret_cast<event::Event *>(v_event);
return APIConnection::try_send_event(api, event, *(event->last_event_type));
}
bool APIConnection::try_send_event(APIConnection *api, event::Event *event, std::string event_type) {
EventResponse resp{}; EventResponse resp{};
resp.key = event->get_object_id_hash(); resp.key = event->get_object_id_hash();
resp.event_type = std::move(event_type); resp.event_type = std::move(event_type);
return this->send_event_response(resp); return api->send_event_response(resp);
} }
bool APIConnection::send_event_info(event::Event *event) { bool APIConnection::try_send_event_info(APIConnection *api, void *v_event) {
event::Event *event = reinterpret_cast<event::Event *>(v_event);
ListEntitiesEventResponse msg; ListEntitiesEventResponse msg;
msg.key = event->get_object_id_hash(); msg.key = event->get_object_id_hash();
msg.object_id = event->get_object_id(); msg.object_id = event->get_object_id();
@@ -1341,7 +1682,7 @@ bool APIConnection::send_event_info(event::Event *event) {
msg.device_class = event->get_device_class(); msg.device_class = event->get_device_class();
for (const auto &event_type : event->get_event_types()) for (const auto &event_type : event->get_event_types())
msg.event_types.push_back(event_type); msg.event_types.push_back(event_type);
return this->send_list_entities_event_response(msg); return api->send_list_entities_event_response(msg);
} }
#endif #endif
@@ -1350,6 +1691,19 @@ bool APIConnection::send_update_state(update::UpdateEntity *update) {
if (!this->state_subscription_) if (!this->state_subscription_)
return false; return false;
if (!APIConnection::try_send_update_state(this, update)) {
this->deferred_message_queue_.defer(update, try_send_update_state);
}
return true;
}
void APIConnection::send_update_info(update::UpdateEntity *update) {
if (!APIConnection::try_send_update_info(this, update)) {
this->deferred_message_queue_.defer(update, try_send_update_info);
}
}
bool APIConnection::try_send_update_state(APIConnection *api, void *v_update) {
update::UpdateEntity *update = reinterpret_cast<update::UpdateEntity *>(v_update);
UpdateStateResponse resp{}; UpdateStateResponse resp{};
resp.key = update->get_object_id_hash(); resp.key = update->get_object_id_hash();
resp.missing_state = !update->has_state(); resp.missing_state = !update->has_state();
@@ -1366,9 +1720,10 @@ bool APIConnection::send_update_state(update::UpdateEntity *update) {
resp.release_url = update->update_info.release_url; resp.release_url = update->update_info.release_url;
} }
return this->send_update_state_response(resp); return api->send_update_state_response(resp);
} }
bool APIConnection::send_update_info(update::UpdateEntity *update) { bool APIConnection::try_send_update_info(APIConnection *api, void *v_update) {
update::UpdateEntity *update = reinterpret_cast<update::UpdateEntity *>(v_update);
ListEntitiesUpdateResponse msg; ListEntitiesUpdateResponse msg;
msg.key = update->get_object_id_hash(); msg.key = update->get_object_id_hash();
msg.object_id = update->get_object_id(); msg.object_id = update->get_object_id();
@@ -1379,7 +1734,7 @@ bool APIConnection::send_update_info(update::UpdateEntity *update) {
msg.disabled_by_default = update->is_disabled_by_default(); msg.disabled_by_default = update->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(update->get_entity_category()); msg.entity_category = static_cast<enums::EntityCategory>(update->get_entity_category());
msg.device_class = update->get_device_class(); msg.device_class = update->get_device_class();
return this->send_list_entities_update_response(msg); return api->send_list_entities_update_response(msg);
} }
void APIConnection::update_command(const UpdateCommandRequest &msg) { void APIConnection::update_command(const UpdateCommandRequest &msg) {
update::UpdateEntity *update = App.get_update_by_key(msg.key); update::UpdateEntity *update = App.get_update_by_key(msg.key);
@@ -1403,7 +1758,7 @@ void APIConnection::update_command(const UpdateCommandRequest &msg) {
} }
#endif #endif
bool APIConnection::send_log_message(int level, const char *tag, const char *line) { bool APIConnection::try_send_log_message(int level, const char *tag, const char *line) {
if (this->log_subscription_ < level) if (this->log_subscription_ < level)
return false; return false;
@@ -1488,6 +1843,7 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
resp.legacy_bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->get_legacy_version(); resp.legacy_bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->get_legacy_version();
resp.bluetooth_proxy_feature_flags = bluetooth_proxy::global_bluetooth_proxy->get_feature_flags(); resp.bluetooth_proxy_feature_flags = bluetooth_proxy::global_bluetooth_proxy->get_feature_flags();
resp.bluetooth_mac_address = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_mac_address_pretty();
#endif #endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
resp.legacy_voice_assistant_version = voice_assistant::global_voice_assistant->get_legacy_version(); resp.legacy_voice_assistant_version = voice_assistant::global_voice_assistant->get_legacy_version();

View File

@@ -14,6 +14,46 @@
namespace esphome { namespace esphome {
namespace api { namespace api {
using send_message_t = bool(APIConnection *, void *);
/*
This class holds a pointer to the source component that wants to publish a message, and a pointer to a function that
will lazily publish that message. The two pointers allow dedup in the deferred queue if multiple publishes for the
same component are backed up, and take up only 8 bytes of memory. The entry in the deferred queue (a std::vector) is
the DeferredMessage instance itself (not a pointer to one elsewhere in heap) so still only 8 bytes per entry. Even
100 backed up messages (you'd have to have at least 100 sensors publishing because of dedup) would take up only 0.8
kB.
*/
class DeferredMessageQueue {
struct DeferredMessage {
friend class DeferredMessageQueue;
protected:
void *source_;
send_message_t *send_message_;
public:
DeferredMessage(void *source, send_message_t *send_message) : source_(source), send_message_(send_message) {}
bool operator==(const DeferredMessage &test) const {
return (source_ == test.source_ && send_message_ == test.send_message_);
}
} __attribute__((packed));
protected:
// vector is used very specifically for its zero memory overhead even though items are popped from the front (memory
// footprint is more important than speed here)
std::vector<DeferredMessage> deferred_queue_;
APIConnection *api_connection_;
// helper for allowing only unique entries in the queue
void dmq_push_back_with_dedup_(void *source, send_message_t *send_message);
public:
DeferredMessageQueue(APIConnection *api_connection) : api_connection_(api_connection) {}
void process_queue();
void defer(void *source, send_message_t *send_message);
};
class APIConnection : public APIServerConnection { class APIConnection : public APIServerConnection {
public: public:
APIConnection(std::unique_ptr<socket::Socket> socket, APIServer *parent); APIConnection(std::unique_ptr<socket::Socket> socket, APIServer *parent);
@@ -28,96 +68,140 @@ class APIConnection : public APIServerConnection {
} }
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state); bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state);
bool send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor); void send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor);
static bool try_send_binary_sensor_state(APIConnection *api, void *v_binary_sensor);
static bool try_send_binary_sensor_state(APIConnection *api, binary_sensor::BinarySensor *binary_sensor, bool state);
static bool try_send_binary_sensor_info(APIConnection *api, void *v_binary_sensor);
#endif #endif
#ifdef USE_COVER #ifdef USE_COVER
bool send_cover_state(cover::Cover *cover); bool send_cover_state(cover::Cover *cover);
bool send_cover_info(cover::Cover *cover); void send_cover_info(cover::Cover *cover);
static bool try_send_cover_state(APIConnection *api, void *v_cover);
static bool try_send_cover_info(APIConnection *api, void *v_cover);
void cover_command(const CoverCommandRequest &msg) override; void cover_command(const CoverCommandRequest &msg) override;
#endif #endif
#ifdef USE_FAN #ifdef USE_FAN
bool send_fan_state(fan::Fan *fan); bool send_fan_state(fan::Fan *fan);
bool send_fan_info(fan::Fan *fan); void send_fan_info(fan::Fan *fan);
static bool try_send_fan_state(APIConnection *api, void *v_fan);
static bool try_send_fan_info(APIConnection *api, void *v_fan);
void fan_command(const FanCommandRequest &msg) override; void fan_command(const FanCommandRequest &msg) override;
#endif #endif
#ifdef USE_LIGHT #ifdef USE_LIGHT
bool send_light_state(light::LightState *light); bool send_light_state(light::LightState *light);
bool send_light_info(light::LightState *light); void send_light_info(light::LightState *light);
static bool try_send_light_state(APIConnection *api, void *v_light);
static bool try_send_light_info(APIConnection *api, void *v_light);
void light_command(const LightCommandRequest &msg) override; void light_command(const LightCommandRequest &msg) override;
#endif #endif
#ifdef USE_SENSOR #ifdef USE_SENSOR
bool send_sensor_state(sensor::Sensor *sensor, float state); bool send_sensor_state(sensor::Sensor *sensor, float state);
bool send_sensor_info(sensor::Sensor *sensor); void send_sensor_info(sensor::Sensor *sensor);
static bool try_send_sensor_state(APIConnection *api, void *v_sensor);
static bool try_send_sensor_state(APIConnection *api, sensor::Sensor *sensor, float state);
static bool try_send_sensor_info(APIConnection *api, void *v_sensor);
#endif #endif
#ifdef USE_SWITCH #ifdef USE_SWITCH
bool send_switch_state(switch_::Switch *a_switch, bool state); bool send_switch_state(switch_::Switch *a_switch, bool state);
bool send_switch_info(switch_::Switch *a_switch); void send_switch_info(switch_::Switch *a_switch);
static bool try_send_switch_state(APIConnection *api, void *v_a_switch);
static bool try_send_switch_state(APIConnection *api, switch_::Switch *a_switch, bool state);
static bool try_send_switch_info(APIConnection *api, void *v_a_switch);
void switch_command(const SwitchCommandRequest &msg) override; void switch_command(const SwitchCommandRequest &msg) override;
#endif #endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
bool send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state); bool send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state);
bool send_text_sensor_info(text_sensor::TextSensor *text_sensor); void send_text_sensor_info(text_sensor::TextSensor *text_sensor);
static bool try_send_text_sensor_state(APIConnection *api, void *v_text_sensor);
static bool try_send_text_sensor_state(APIConnection *api, text_sensor::TextSensor *text_sensor, std::string state);
static bool try_send_text_sensor_info(APIConnection *api, void *v_text_sensor);
#endif #endif
#ifdef USE_ESP32_CAMERA #ifdef USE_ESP32_CAMERA
void send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image); void set_camera_state(std::shared_ptr<esp32_camera::CameraImage> image);
bool send_camera_info(esp32_camera::ESP32Camera *camera); void send_camera_info(esp32_camera::ESP32Camera *camera);
static bool try_send_camera_info(APIConnection *api, void *v_camera);
void camera_image(const CameraImageRequest &msg) override; void camera_image(const CameraImageRequest &msg) override;
#endif #endif
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
bool send_climate_state(climate::Climate *climate); bool send_climate_state(climate::Climate *climate);
bool send_climate_info(climate::Climate *climate); void send_climate_info(climate::Climate *climate);
static bool try_send_climate_state(APIConnection *api, void *v_climate);
static bool try_send_climate_info(APIConnection *api, void *v_climate);
void climate_command(const ClimateCommandRequest &msg) override; void climate_command(const ClimateCommandRequest &msg) override;
#endif #endif
#ifdef USE_NUMBER #ifdef USE_NUMBER
bool send_number_state(number::Number *number, float state); bool send_number_state(number::Number *number, float state);
bool send_number_info(number::Number *number); void send_number_info(number::Number *number);
static bool try_send_number_state(APIConnection *api, void *v_number);
static bool try_send_number_state(APIConnection *api, number::Number *number, float state);
static bool try_send_number_info(APIConnection *api, void *v_number);
void number_command(const NumberCommandRequest &msg) override; void number_command(const NumberCommandRequest &msg) override;
#endif #endif
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
bool send_date_state(datetime::DateEntity *date); bool send_date_state(datetime::DateEntity *date);
bool send_date_info(datetime::DateEntity *date); void send_date_info(datetime::DateEntity *date);
static bool try_send_date_state(APIConnection *api, void *v_date);
static bool try_send_date_info(APIConnection *api, void *v_date);
void date_command(const DateCommandRequest &msg) override; void date_command(const DateCommandRequest &msg) override;
#endif #endif
#ifdef USE_DATETIME_TIME #ifdef USE_DATETIME_TIME
bool send_time_state(datetime::TimeEntity *time); bool send_time_state(datetime::TimeEntity *time);
bool send_time_info(datetime::TimeEntity *time); void send_time_info(datetime::TimeEntity *time);
static bool try_send_time_state(APIConnection *api, void *v_time);
static bool try_send_time_info(APIConnection *api, void *v_time);
void time_command(const TimeCommandRequest &msg) override; void time_command(const TimeCommandRequest &msg) override;
#endif #endif
#ifdef USE_DATETIME_DATETIME #ifdef USE_DATETIME_DATETIME
bool send_datetime_state(datetime::DateTimeEntity *datetime); bool send_datetime_state(datetime::DateTimeEntity *datetime);
bool send_datetime_info(datetime::DateTimeEntity *datetime); void send_datetime_info(datetime::DateTimeEntity *datetime);
static bool try_send_datetime_state(APIConnection *api, void *v_datetime);
static bool try_send_datetime_info(APIConnection *api, void *v_datetime);
void datetime_command(const DateTimeCommandRequest &msg) override; void datetime_command(const DateTimeCommandRequest &msg) override;
#endif #endif
#ifdef USE_TEXT #ifdef USE_TEXT
bool send_text_state(text::Text *text, std::string state); bool send_text_state(text::Text *text, std::string state);
bool send_text_info(text::Text *text); void send_text_info(text::Text *text);
static bool try_send_text_state(APIConnection *api, void *v_text);
static bool try_send_text_state(APIConnection *api, text::Text *text, std::string state);
static bool try_send_text_info(APIConnection *api, void *v_text);
void text_command(const TextCommandRequest &msg) override; void text_command(const TextCommandRequest &msg) override;
#endif #endif
#ifdef USE_SELECT #ifdef USE_SELECT
bool send_select_state(select::Select *select, std::string state); bool send_select_state(select::Select *select, std::string state);
bool send_select_info(select::Select *select); void send_select_info(select::Select *select);
static bool try_send_select_state(APIConnection *api, void *v_select);
static bool try_send_select_state(APIConnection *api, select::Select *select, std::string state);
static bool try_send_select_info(APIConnection *api, void *v_select);
void select_command(const SelectCommandRequest &msg) override; void select_command(const SelectCommandRequest &msg) override;
#endif #endif
#ifdef USE_BUTTON #ifdef USE_BUTTON
bool send_button_info(button::Button *button); void send_button_info(button::Button *button);
static bool try_send_button_info(APIConnection *api, void *v_button);
void button_command(const ButtonCommandRequest &msg) override; void button_command(const ButtonCommandRequest &msg) override;
#endif #endif
#ifdef USE_LOCK #ifdef USE_LOCK
bool send_lock_state(lock::Lock *a_lock, lock::LockState state); bool send_lock_state(lock::Lock *a_lock, lock::LockState state);
bool send_lock_info(lock::Lock *a_lock); void send_lock_info(lock::Lock *a_lock);
static bool try_send_lock_state(APIConnection *api, void *v_a_lock);
static bool try_send_lock_state(APIConnection *api, lock::Lock *a_lock, lock::LockState state);
static bool try_send_lock_info(APIConnection *api, void *v_a_lock);
void lock_command(const LockCommandRequest &msg) override; void lock_command(const LockCommandRequest &msg) override;
#endif #endif
#ifdef USE_VALVE #ifdef USE_VALVE
bool send_valve_state(valve::Valve *valve); bool send_valve_state(valve::Valve *valve);
bool send_valve_info(valve::Valve *valve); void send_valve_info(valve::Valve *valve);
static bool try_send_valve_state(APIConnection *api, void *v_valve);
static bool try_send_valve_info(APIConnection *api, void *v_valve);
void valve_command(const ValveCommandRequest &msg) override; void valve_command(const ValveCommandRequest &msg) override;
#endif #endif
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
bool send_media_player_state(media_player::MediaPlayer *media_player); bool send_media_player_state(media_player::MediaPlayer *media_player);
bool send_media_player_info(media_player::MediaPlayer *media_player); void send_media_player_info(media_player::MediaPlayer *media_player);
static bool try_send_media_player_state(APIConnection *api, void *v_media_player);
static bool try_send_media_player_info(APIConnection *api, void *v_media_player);
void media_player_command(const MediaPlayerCommandRequest &msg) override; void media_player_command(const MediaPlayerCommandRequest &msg) override;
#endif #endif
bool send_log_message(int level, const char *tag, const char *line); bool try_send_log_message(int level, const char *tag, const char *line);
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
if (!this->service_call_subscription_) if (!this->service_call_subscription_)
return; return;
@@ -160,18 +244,25 @@ class APIConnection : public APIServerConnection {
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel); bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
bool send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel); void send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
static bool try_send_alarm_control_panel_state(APIConnection *api, void *v_a_alarm_control_panel);
static bool try_send_alarm_control_panel_info(APIConnection *api, void *v_a_alarm_control_panel);
void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override; void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override;
#endif #endif
#ifdef USE_EVENT #ifdef USE_EVENT
bool send_event(event::Event *event, std::string event_type); void send_event(event::Event *event, std::string event_type);
bool send_event_info(event::Event *event); void send_event_info(event::Event *event);
static bool try_send_event(APIConnection *api, void *v_event);
static bool try_send_event(APIConnection *api, event::Event *event, std::string event_type);
static bool try_send_event_info(APIConnection *api, void *v_event);
#endif #endif
#ifdef USE_UPDATE #ifdef USE_UPDATE
bool send_update_state(update::UpdateEntity *update); bool send_update_state(update::UpdateEntity *update);
bool send_update_info(update::UpdateEntity *update); void send_update_info(update::UpdateEntity *update);
static bool try_send_update_state(APIConnection *api, void *v_update);
static bool try_send_update_info(APIConnection *api, void *v_update);
void update_command(const UpdateCommandRequest &msg) override; void update_command(const UpdateCommandRequest &msg) override;
#endif #endif
@@ -262,6 +353,7 @@ class APIConnection : public APIServerConnection {
bool service_call_subscription_{false}; bool service_call_subscription_{false};
bool next_close_ = false; bool next_close_ = false;
APIServer *parent_; APIServer *parent_;
DeferredMessageQueue deferred_message_queue_;
InitialStateIterator initial_state_iterator_; InitialStateIterator initial_state_iterator_;
ListEntitiesIterator list_entities_iterator_; ListEntitiesIterator list_entities_iterator_;
int state_subs_at_ = -1; int state_subs_at_ = -1;

View File

@@ -838,6 +838,10 @@ bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited v
this->suggested_area = value.as_string(); this->suggested_area = value.as_string();
return true; return true;
} }
case 18: {
this->bluetooth_mac_address = value.as_string();
return true;
}
default: default:
return false; return false;
} }
@@ -860,6 +864,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(14, this->legacy_voice_assistant_version); buffer.encode_uint32(14, this->legacy_voice_assistant_version);
buffer.encode_uint32(17, this->voice_assistant_feature_flags); buffer.encode_uint32(17, this->voice_assistant_feature_flags);
buffer.encode_string(16, this->suggested_area); buffer.encode_string(16, this->suggested_area);
buffer.encode_string(18, this->bluetooth_mac_address);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void DeviceInfoResponse::dump_to(std::string &out) const { void DeviceInfoResponse::dump_to(std::string &out) const {
@@ -937,6 +942,10 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
out.append(" suggested_area: "); out.append(" suggested_area: ");
out.append("'").append(this->suggested_area).append("'"); out.append("'").append(this->suggested_area).append("'");
out.append("\n"); out.append("\n");
out.append(" bluetooth_mac_address: ");
out.append("'").append(this->bluetooth_mac_address).append("'");
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@@ -6430,6 +6439,10 @@ bool BluetoothConnectionsFreeResponse::decode_varint(uint32_t field_id, ProtoVar
this->limit = value.as_uint32(); this->limit = value.as_uint32();
return true; return true;
} }
case 3: {
this->allocated.push_back(value.as_uint64());
return true;
}
default: default:
return false; return false;
} }
@@ -6437,6 +6450,9 @@ bool BluetoothConnectionsFreeResponse::decode_varint(uint32_t field_id, ProtoVar
void BluetoothConnectionsFreeResponse::encode(ProtoWriteBuffer buffer) const { void BluetoothConnectionsFreeResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(1, this->free); buffer.encode_uint32(1, this->free);
buffer.encode_uint32(2, this->limit); buffer.encode_uint32(2, this->limit);
for (auto &it : this->allocated) {
buffer.encode_uint64(3, it, true);
}
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothConnectionsFreeResponse::dump_to(std::string &out) const { void BluetoothConnectionsFreeResponse::dump_to(std::string &out) const {
@@ -6451,6 +6467,13 @@ void BluetoothConnectionsFreeResponse::dump_to(std::string &out) const {
sprintf(buffer, "%" PRIu32, this->limit); sprintf(buffer, "%" PRIu32, this->limit);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
for (const auto &it : this->allocated) {
out.append(" allocated: ");
sprintf(buffer, "%llu", it);
out.append(buffer);
out.append("\n");
}
out.append("}"); out.append("}");
} }
#endif #endif
@@ -7071,6 +7094,16 @@ void VoiceAssistantTimerEventResponse::dump_to(std::string &out) const {
out.append("}"); out.append("}");
} }
#endif #endif
bool VoiceAssistantAnnounceRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 4: {
this->start_conversation = value.as_bool();
return true;
}
default:
return false;
}
}
bool VoiceAssistantAnnounceRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool VoiceAssistantAnnounceRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 1: { case 1: {
@@ -7081,6 +7114,10 @@ bool VoiceAssistantAnnounceRequest::decode_length(uint32_t field_id, ProtoLength
this->text = value.as_string(); this->text = value.as_string();
return true; return true;
} }
case 3: {
this->preannounce_media_id = value.as_string();
return true;
}
default: default:
return false; return false;
} }
@@ -7088,6 +7125,8 @@ bool VoiceAssistantAnnounceRequest::decode_length(uint32_t field_id, ProtoLength
void VoiceAssistantAnnounceRequest::encode(ProtoWriteBuffer buffer) const { void VoiceAssistantAnnounceRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->media_id); buffer.encode_string(1, this->media_id);
buffer.encode_string(2, this->text); buffer.encode_string(2, this->text);
buffer.encode_string(3, this->preannounce_media_id);
buffer.encode_bool(4, this->start_conversation);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void VoiceAssistantAnnounceRequest::dump_to(std::string &out) const { void VoiceAssistantAnnounceRequest::dump_to(std::string &out) const {
@@ -7100,6 +7139,14 @@ void VoiceAssistantAnnounceRequest::dump_to(std::string &out) const {
out.append(" text: "); out.append(" text: ");
out.append("'").append(this->text).append("'"); out.append("'").append(this->text).append("'");
out.append("\n"); out.append("\n");
out.append(" preannounce_media_id: ");
out.append("'").append(this->preannounce_media_id).append("'");
out.append("\n");
out.append(" start_conversation: ");
out.append(YESNO(this->start_conversation));
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif

View File

@@ -354,6 +354,7 @@ class DeviceInfoResponse : public ProtoMessage {
uint32_t legacy_voice_assistant_version{0}; uint32_t legacy_voice_assistant_version{0};
uint32_t voice_assistant_feature_flags{0}; uint32_t voice_assistant_feature_flags{0};
std::string suggested_area{}; std::string suggested_area{};
std::string bluetooth_mac_address{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@@ -1624,6 +1625,7 @@ class BluetoothConnectionsFreeResponse : public ProtoMessage {
public: public:
uint32_t free{0}; uint32_t free{0};
uint32_t limit{0}; uint32_t limit{0};
std::vector<uint64_t> allocated{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@@ -1830,6 +1832,8 @@ class VoiceAssistantAnnounceRequest : public ProtoMessage {
public: public:
std::string media_id{}; std::string media_id{};
std::string text{}; std::string text{};
std::string preannounce_media_id{};
bool start_conversation{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@@ -1837,6 +1841,7 @@ class VoiceAssistantAnnounceRequest : public ProtoMessage {
protected: protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
}; };
class VoiceAssistantAnnounceFinished : public ProtoMessage { class VoiceAssistantAnnounceFinished : public ProtoMessage {
public: public:

View File

@@ -72,7 +72,7 @@ void APIServer::setup() {
logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) { logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) {
for (auto &c : this->clients_) { for (auto &c : this->clients_) {
if (!c->remove_) if (!c->remove_)
c->send_log_message(level, tag, message); c->try_send_log_message(level, tag, message);
} }
}); });
} }
@@ -86,7 +86,7 @@ void APIServer::setup() {
[this](const std::shared_ptr<esp32_camera::CameraImage> &image) { [this](const std::shared_ptr<esp32_camera::CameraImage> &image) {
for (auto &c : this->clients_) { for (auto &c : this->clients_) {
if (!c->remove_) if (!c->remove_)
c->send_camera_state(image); c->set_camera_state(image);
} }
}); });
} }

View File

@@ -1,12 +1,11 @@
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
import logging
from datetime import datetime from datetime import datetime
from typing import Any import logging
from typing import TYPE_CHECKING, Any
from aioesphomeapi import APIClient from aioesphomeapi import APIClient
from aioesphomeapi.api_pb2 import SubscribeLogsResponse
from aioesphomeapi.log_runner import async_run from aioesphomeapi.log_runner import async_run
from esphome.const import CONF_KEY, CONF_PASSWORD, CONF_PORT, __version__ from esphome.const import CONF_KEY, CONF_PASSWORD, CONF_PORT, __version__
@@ -14,6 +13,12 @@ from esphome.core import CORE
from . import CONF_ENCRYPTION from . import CONF_ENCRYPTION
if TYPE_CHECKING:
from aioesphomeapi.api_pb2 import (
SubscribeLogsResponse, # pylint: disable=no-name-in-module
)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@@ -10,37 +10,63 @@ namespace api {
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
return this->client_->send_binary_sensor_info(binary_sensor); this->client_->send_binary_sensor_info(binary_sensor);
return true;
} }
#endif #endif
#ifdef USE_COVER #ifdef USE_COVER
bool ListEntitiesIterator::on_cover(cover::Cover *cover) { return this->client_->send_cover_info(cover); } bool ListEntitiesIterator::on_cover(cover::Cover *cover) {
this->client_->send_cover_info(cover);
return true;
}
#endif #endif
#ifdef USE_FAN #ifdef USE_FAN
bool ListEntitiesIterator::on_fan(fan::Fan *fan) { return this->client_->send_fan_info(fan); } bool ListEntitiesIterator::on_fan(fan::Fan *fan) {
this->client_->send_fan_info(fan);
return true;
}
#endif #endif
#ifdef USE_LIGHT #ifdef USE_LIGHT
bool ListEntitiesIterator::on_light(light::LightState *light) { return this->client_->send_light_info(light); } bool ListEntitiesIterator::on_light(light::LightState *light) {
this->client_->send_light_info(light);
return true;
}
#endif #endif
#ifdef USE_SENSOR #ifdef USE_SENSOR
bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { return this->client_->send_sensor_info(sensor); } bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) {
this->client_->send_sensor_info(sensor);
return true;
}
#endif #endif
#ifdef USE_SWITCH #ifdef USE_SWITCH
bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_info(a_switch); } bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) {
this->client_->send_switch_info(a_switch);
return true;
}
#endif #endif
#ifdef USE_BUTTON #ifdef USE_BUTTON
bool ListEntitiesIterator::on_button(button::Button *button) { return this->client_->send_button_info(button); } bool ListEntitiesIterator::on_button(button::Button *button) {
this->client_->send_button_info(button);
return true;
}
#endif #endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) { bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) {
return this->client_->send_text_sensor_info(text_sensor); this->client_->send_text_sensor_info(text_sensor);
return true;
} }
#endif #endif
#ifdef USE_LOCK #ifdef USE_LOCK
bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_info(a_lock); } bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) {
this->client_->send_lock_info(a_lock);
return true;
}
#endif #endif
#ifdef USE_VALVE #ifdef USE_VALVE
bool ListEntitiesIterator::on_valve(valve::Valve *valve) { return this->client_->send_valve_info(valve); } bool ListEntitiesIterator::on_valve(valve::Valve *valve) {
this->client_->send_valve_info(valve);
return true;
}
#endif #endif
bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); } bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); }
@@ -52,55 +78,83 @@ bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) {
#ifdef USE_ESP32_CAMERA #ifdef USE_ESP32_CAMERA
bool ListEntitiesIterator::on_camera(esp32_camera::ESP32Camera *camera) { bool ListEntitiesIterator::on_camera(esp32_camera::ESP32Camera *camera) {
return this->client_->send_camera_info(camera); this->client_->send_camera_info(camera);
return true;
} }
#endif #endif
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
bool ListEntitiesIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_info(climate); } bool ListEntitiesIterator::on_climate(climate::Climate *climate) {
this->client_->send_climate_info(climate);
return true;
}
#endif #endif
#ifdef USE_NUMBER #ifdef USE_NUMBER
bool ListEntitiesIterator::on_number(number::Number *number) { return this->client_->send_number_info(number); } bool ListEntitiesIterator::on_number(number::Number *number) {
this->client_->send_number_info(number);
return true;
}
#endif #endif
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
bool ListEntitiesIterator::on_date(datetime::DateEntity *date) { return this->client_->send_date_info(date); } bool ListEntitiesIterator::on_date(datetime::DateEntity *date) {
this->client_->send_date_info(date);
return true;
}
#endif #endif
#ifdef USE_DATETIME_TIME #ifdef USE_DATETIME_TIME
bool ListEntitiesIterator::on_time(datetime::TimeEntity *time) { return this->client_->send_time_info(time); } bool ListEntitiesIterator::on_time(datetime::TimeEntity *time) {
this->client_->send_time_info(time);
return true;
}
#endif #endif
#ifdef USE_DATETIME_DATETIME #ifdef USE_DATETIME_DATETIME
bool ListEntitiesIterator::on_datetime(datetime::DateTimeEntity *datetime) { bool ListEntitiesIterator::on_datetime(datetime::DateTimeEntity *datetime) {
return this->client_->send_datetime_info(datetime); this->client_->send_datetime_info(datetime);
return true;
} }
#endif #endif
#ifdef USE_TEXT #ifdef USE_TEXT
bool ListEntitiesIterator::on_text(text::Text *text) { return this->client_->send_text_info(text); } bool ListEntitiesIterator::on_text(text::Text *text) {
this->client_->send_text_info(text);
return true;
}
#endif #endif
#ifdef USE_SELECT #ifdef USE_SELECT
bool ListEntitiesIterator::on_select(select::Select *select) { return this->client_->send_select_info(select); } bool ListEntitiesIterator::on_select(select::Select *select) {
this->client_->send_select_info(select);
return true;
}
#endif #endif
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
bool ListEntitiesIterator::on_media_player(media_player::MediaPlayer *media_player) { bool ListEntitiesIterator::on_media_player(media_player::MediaPlayer *media_player) {
return this->client_->send_media_player_info(media_player); this->client_->send_media_player_info(media_player);
return true;
} }
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
return this->client_->send_alarm_control_panel_info(a_alarm_control_panel); this->client_->send_alarm_control_panel_info(a_alarm_control_panel);
return true;
} }
#endif #endif
#ifdef USE_EVENT #ifdef USE_EVENT
bool ListEntitiesIterator::on_event(event::Event *event) { return this->client_->send_event_info(event); } bool ListEntitiesIterator::on_event(event::Event *event) {
this->client_->send_event_info(event);
return true;
}
#endif #endif
#ifdef USE_UPDATE #ifdef USE_UPDATE
bool ListEntitiesIterator::on_update(update::UpdateEntity *update) { return this->client_->send_update_info(update); } bool ListEntitiesIterator::on_update(update::UpdateEntity *update) {
this->client_->send_update_info(update);
return true;
}
#endif #endif
} // namespace api } // namespace api

View File

@@ -80,6 +80,7 @@ class ListEntitiesIterator : public ComponentIterator {
bool on_update(update::UpdateEntity *update) override; bool on_update(update::UpdateEntity *update) override;
#endif #endif
bool on_end() override; bool on_end() override;
bool completed() { return this->state_ == IteratorState::NONE; }
protected: protected:
APIConnection *client_; APIConnection *client_;

View File

@@ -76,6 +76,8 @@ class InitialStateIterator : public ComponentIterator {
#ifdef USE_UPDATE #ifdef USE_UPDATE
bool on_update(update::UpdateEntity *update) override; bool on_update(update::UpdateEntity *update) override;
#endif #endif
bool completed() { return this->state_ == IteratorState::NONE; }
protected: protected:
APIConnection *client_; APIConnection *client_;
}; };

View File

@@ -0,0 +1,121 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_BITS_PER_SAMPLE, CONF_NUM_CHANNELS, CONF_SAMPLE_RATE
import esphome.final_validate as fv
CODEOWNERS = ["@kahrendt"]
audio_ns = cg.esphome_ns.namespace("audio")
AudioFile = audio_ns.struct("AudioFile")
AudioFileType = audio_ns.enum("AudioFileType", is_class=True)
AUDIO_FILE_TYPE_ENUM = {
"NONE": AudioFileType.NONE,
"WAV": AudioFileType.WAV,
"MP3": AudioFileType.MP3,
"FLAC": AudioFileType.FLAC,
}
CONF_MIN_BITS_PER_SAMPLE = "min_bits_per_sample"
CONF_MAX_BITS_PER_SAMPLE = "max_bits_per_sample"
CONF_MIN_CHANNELS = "min_channels"
CONF_MAX_CHANNELS = "max_channels"
CONF_MIN_SAMPLE_RATE = "min_sample_rate"
CONF_MAX_SAMPLE_RATE = "max_sample_rate"
CONFIG_SCHEMA = cv.All(
cv.Schema({}),
)
AUDIO_COMPONENT_SCHEMA = cv.Schema(
{
cv.Optional(CONF_BITS_PER_SAMPLE): cv.int_range(8, 32),
cv.Optional(CONF_NUM_CHANNELS): cv.int_range(1, 2),
cv.Optional(CONF_SAMPLE_RATE): cv.int_range(8000, 48000),
}
)
_UNDEF = object()
def set_stream_limits(
min_bits_per_sample: int = _UNDEF,
max_bits_per_sample: int = _UNDEF,
min_channels: int = _UNDEF,
max_channels: int = _UNDEF,
min_sample_rate: int = _UNDEF,
max_sample_rate: int = _UNDEF,
):
def set_limits_in_config(config):
if min_bits_per_sample is not _UNDEF:
config[CONF_MIN_BITS_PER_SAMPLE] = min_bits_per_sample
if max_bits_per_sample is not _UNDEF:
config[CONF_MAX_BITS_PER_SAMPLE] = max_bits_per_sample
if min_channels is not _UNDEF:
config[CONF_MIN_CHANNELS] = min_channels
if max_channels is not _UNDEF:
config[CONF_MAX_CHANNELS] = max_channels
if min_sample_rate is not _UNDEF:
config[CONF_MIN_SAMPLE_RATE] = min_sample_rate
if max_sample_rate is not _UNDEF:
config[CONF_MAX_SAMPLE_RATE] = max_sample_rate
return set_limits_in_config
def final_validate_audio_schema(
name: str,
*,
audio_device: str,
bits_per_sample: int,
channels: int,
sample_rate: int,
):
def validate_audio_compatiblity(audio_config):
audio_schema = {}
try:
cv.int_range(
min=audio_config.get(CONF_MIN_BITS_PER_SAMPLE),
max=audio_config.get(CONF_MAX_BITS_PER_SAMPLE),
)(bits_per_sample)
except cv.Invalid as exc:
raise cv.Invalid(
f"Invalid configuration for the {name} component. The {CONF_BITS_PER_SAMPLE} {str(exc)}"
) from exc
try:
cv.int_range(
min=audio_config.get(CONF_MIN_CHANNELS),
max=audio_config.get(CONF_MAX_CHANNELS),
)(channels)
except cv.Invalid as exc:
raise cv.Invalid(
f"Invalid configuration for the {name} component. The {CONF_NUM_CHANNELS} {str(exc)}"
) from exc
try:
cv.int_range(
min=audio_config.get(CONF_MIN_SAMPLE_RATE),
max=audio_config.get(CONF_MAX_SAMPLE_RATE),
)(sample_rate)
return cv.Schema(audio_schema, extra=cv.ALLOW_EXTRA)(audio_config)
except cv.Invalid as exc:
raise cv.Invalid(
f"Invalid configuration for the {name} component. The {CONF_SAMPLE_RATE} {str(exc)}"
) from exc
return cv.Schema(
{
cv.Required(audio_device): fv.id_declaration_match_schema(
validate_audio_compatiblity
)
},
extra=cv.ALLOW_EXTRA,
)
async def to_code(config):
cg.add_library("esphome/esp-audio-libs", "1.1.3")

View File

@@ -0,0 +1,67 @@
#include "audio.h"
namespace esphome {
namespace audio {
// Euclidean's algorithm for finding the greatest common divisor
static uint32_t gcd(uint32_t a, uint32_t b) {
while (b != 0) {
uint32_t t = b;
b = a % b;
a = t;
}
return a;
}
AudioStreamInfo::AudioStreamInfo(uint8_t bits_per_sample, uint8_t channels, uint32_t sample_rate)
: bits_per_sample_(bits_per_sample), channels_(channels), sample_rate_(sample_rate) {
this->ms_sample_rate_gcd_ = gcd(1000, this->sample_rate_);
this->bytes_per_sample_ = (this->bits_per_sample_ + 7) / 8;
}
uint32_t AudioStreamInfo::frames_to_microseconds(uint32_t frames) const {
return (frames * 1000000 + (this->sample_rate_ >> 1)) / this->sample_rate_;
}
uint32_t AudioStreamInfo::frames_to_milliseconds_with_remainder(uint32_t *total_frames) const {
uint32_t unprocessable_frames = *total_frames % (this->sample_rate_ / this->ms_sample_rate_gcd_);
uint32_t frames_for_ms_calculation = *total_frames - unprocessable_frames;
uint32_t playback_ms = (frames_for_ms_calculation * 1000) / this->sample_rate_;
*total_frames = unprocessable_frames;
return playback_ms;
}
bool AudioStreamInfo::operator==(const AudioStreamInfo &rhs) const {
return (this->bits_per_sample_ == rhs.get_bits_per_sample()) && (this->channels_ == rhs.get_channels()) &&
(this->sample_rate_ == rhs.get_sample_rate());
}
const char *audio_file_type_to_string(AudioFileType file_type) {
switch (file_type) {
#ifdef USE_AUDIO_FLAC_SUPPORT
case AudioFileType::FLAC:
return "FLAC";
#endif
#ifdef USE_AUDIO_MP3_SUPPORT
case AudioFileType::MP3:
return "MP3";
#endif
case AudioFileType::WAV:
return "WAV";
default:
return "unknown";
}
}
void scale_audio_samples(const int16_t *audio_samples, int16_t *output_buffer, int16_t scale_factor,
size_t samples_to_scale) {
// Note the assembly dsps_mulc function has audio glitches if the input and output buffers are the same.
for (int i = 0; i < samples_to_scale; i++) {
int32_t acc = (int32_t) audio_samples[i] * (int32_t) scale_factor;
output_buffer[i] = (int16_t) (acc >> 15);
}
}
} // namespace audio
} // namespace esphome

View File

@@ -0,0 +1,139 @@
#pragma once
#include "esphome/core/defines.h"
#include <cstddef>
#include <cstdint>
namespace esphome {
namespace audio {
class AudioStreamInfo {
/* Class to respresent important parameters of the audio stream that also provides helper function to convert between
* various audio related units.
*
* - An audio sample represents a unit of audio for one channel.
* - A frame represents a unit of audio with a sample for every channel.
*
* In gneneral, converting between bytes, samples, and frames shouldn't result in rounding errors so long as frames
* are used as the main unit when transferring audio data. Durations may result in rounding for certain sample rates;
* e.g., 44.1 KHz. The ``frames_to_milliseconds_with_remainder`` function should be used for accuracy, as it takes
* into account the remainder rather than just ignoring any rounding.
*/
public:
AudioStreamInfo()
: AudioStreamInfo(16, 1, 16000){}; // Default values represent ESPHome's audio components historical values
AudioStreamInfo(uint8_t bits_per_sample, uint8_t channels, uint32_t sample_rate);
uint8_t get_bits_per_sample() const { return this->bits_per_sample_; }
uint8_t get_channels() const { return this->channels_; }
uint32_t get_sample_rate() const { return this->sample_rate_; }
/// @brief Convert bytes to duration in milliseconds.
/// @param bytes Number of bytes to convert
/// @return Duration in milliseconds that will store `bytes` bytes of audio. May round down for certain sample rates
/// or values of `bytes`.
uint32_t bytes_to_ms(size_t bytes) const {
return bytes * 1000 / (this->sample_rate_ * this->bytes_per_sample_ * this->channels_);
}
/// @brief Convert bytes to frames.
/// @param bytes Number of bytes to convert
/// @return Audio frames that will store `bytes` bytes.
uint32_t bytes_to_frames(size_t bytes) const { return (bytes / (this->bytes_per_sample_ * this->channels_)); }
/// @brief Convert bytes to samples.
/// @param bytes Number of bytes to convert
/// @return Audio samples that will store `bytes` bytes.
uint32_t bytes_to_samples(size_t bytes) const { return (bytes / this->bytes_per_sample_); }
/// @brief Converts frames to bytes.
/// @param frames Number of frames to convert.
/// @return Number of bytes that will store `frames` frames of audio.
size_t frames_to_bytes(uint32_t frames) const { return frames * this->bytes_per_sample_ * this->channels_; }
/// @brief Converts samples to bytes.
/// @param samples Number of samples to convert.
/// @return Number of bytes that will store `samples` samples of audio.
size_t samples_to_bytes(uint32_t samples) const { return samples * this->bytes_per_sample_; }
/// @brief Converts duration to frames.
/// @param ms Duration in milliseconds
/// @return Audio frames that will store `ms` milliseconds of audio. May round down for certain sample rates.
uint32_t ms_to_frames(uint32_t ms) const { return (ms * this->sample_rate_) / 1000; }
/// @brief Converts duration to samples.
/// @param ms Duration in milliseconds
/// @return Audio samples that will store `ms` milliseconds of audio. May round down for certain sample rates.
uint32_t ms_to_samples(uint32_t ms) const { return (ms * this->channels_ * this->sample_rate_) / 1000; }
/// @brief Converts duration to bytes. May round down for certain sample rates.
/// @param ms Duration in milliseconds
/// @return Bytes that will store `ms` milliseconds of audio. May round down for certain sample rates.
size_t ms_to_bytes(uint32_t ms) const {
return (ms * this->bytes_per_sample_ * this->channels_ * this->sample_rate_) / 1000;
}
/// @brief Computes the duration, in microseconds, the given amount of frames represents.
/// @param frames Number of audio frames
/// @return Duration in microseconds `frames` respresents. May be slightly inaccurate due to integer divison rounding
/// for certain sample rates.
uint32_t frames_to_microseconds(uint32_t frames) const;
/// @brief Computes the duration, in milliseconds, the given amount of frames represents. Avoids
/// accumulating rounding errors by updating `frames` with the remainder after converting.
/// @param frames Pointer to uint32_t with the number of audio frames. Replaced with the remainder.
/// @return Duration in milliseconds `frames` represents. Always less than or equal to the actual value due to
/// rounding.
uint32_t frames_to_milliseconds_with_remainder(uint32_t *frames) const;
// Class comparison operators
bool operator==(const AudioStreamInfo &rhs) const;
bool operator!=(const AudioStreamInfo &rhs) const { return !operator==(rhs); }
protected:
uint8_t bits_per_sample_;
uint8_t channels_;
uint32_t sample_rate_;
// The greatest common divisor between 1000 ms = 1 second and the sample rate. Used to avoid accumulating error when
// converting from frames to duration. Computed at construction.
uint32_t ms_sample_rate_gcd_;
// Conversion factor derived from the number of bits per sample. Assumes audio data is aligned to the byte. Computed
// at construction.
size_t bytes_per_sample_;
};
enum class AudioFileType : uint8_t {
NONE = 0,
#ifdef USE_AUDIO_FLAC_SUPPORT
FLAC,
#endif
#ifdef USE_AUDIO_MP3_SUPPORT
MP3,
#endif
WAV,
};
struct AudioFile {
const uint8_t *data;
size_t length;
AudioFileType file_type;
};
/// @brief Helper function to convert file type to a const char string
/// @param file_type
/// @return const char pointer to the readable file type
const char *audio_file_type_to_string(AudioFileType file_type);
/// @brief Scales Q15 fixed point audio samples. Scales in place if audio_samples == output_buffer.
/// @param audio_samples PCM int16 audio samples
/// @param output_buffer Buffer to store the scaled samples
/// @param scale_factor Q15 fixed point scaling factor
/// @param samples_to_scale Number of samples to scale
void scale_audio_samples(const int16_t *audio_samples, int16_t *output_buffer, int16_t scale_factor,
size_t samples_to_scale);
} // namespace audio
} // namespace esphome

View File

@@ -0,0 +1,393 @@
#include "audio_decoder.h"
#ifdef USE_ESP32
#include "esphome/core/hal.h"
namespace esphome {
namespace audio {
static const uint32_t DECODING_TIMEOUT_MS = 50; // The decode function will yield after this duration
static const uint32_t READ_WRITE_TIMEOUT_MS = 20; // Timeout for transferring audio data
static const uint32_t MAX_POTENTIALLY_FAILED_COUNT = 10;
AudioDecoder::AudioDecoder(size_t input_buffer_size, size_t output_buffer_size) {
this->input_transfer_buffer_ = AudioSourceTransferBuffer::create(input_buffer_size);
this->output_transfer_buffer_ = AudioSinkTransferBuffer::create(output_buffer_size);
}
AudioDecoder::~AudioDecoder() {
#ifdef USE_AUDIO_MP3_SUPPORT
if (this->audio_file_type_ == AudioFileType::MP3) {
esp_audio_libs::helix_decoder::MP3FreeDecoder(this->mp3_decoder_);
}
#endif
}
esp_err_t AudioDecoder::add_source(std::weak_ptr<RingBuffer> &input_ring_buffer) {
if (this->input_transfer_buffer_ != nullptr) {
this->input_transfer_buffer_->set_source(input_ring_buffer);
return ESP_OK;
}
return ESP_ERR_NO_MEM;
}
esp_err_t AudioDecoder::add_sink(std::weak_ptr<RingBuffer> &output_ring_buffer) {
if (this->output_transfer_buffer_ != nullptr) {
this->output_transfer_buffer_->set_sink(output_ring_buffer);
return ESP_OK;
}
return ESP_ERR_NO_MEM;
}
#ifdef USE_SPEAKER
esp_err_t AudioDecoder::add_sink(speaker::Speaker *speaker) {
if (this->output_transfer_buffer_ != nullptr) {
this->output_transfer_buffer_->set_sink(speaker);
return ESP_OK;
}
return ESP_ERR_NO_MEM;
}
#endif
esp_err_t AudioDecoder::start(AudioFileType audio_file_type) {
if ((this->input_transfer_buffer_ == nullptr) || (this->output_transfer_buffer_ == nullptr)) {
return ESP_ERR_NO_MEM;
}
this->audio_file_type_ = audio_file_type;
this->potentially_failed_count_ = 0;
this->end_of_file_ = false;
switch (this->audio_file_type_) {
#ifdef USE_AUDIO_FLAC_SUPPORT
case AudioFileType::FLAC:
this->flac_decoder_ = make_unique<esp_audio_libs::flac::FLACDecoder>();
this->free_buffer_required_ =
this->output_transfer_buffer_->capacity(); // Adjusted and reallocated after reading the header
break;
#endif
#ifdef USE_AUDIO_MP3_SUPPORT
case AudioFileType::MP3:
this->mp3_decoder_ = esp_audio_libs::helix_decoder::MP3InitDecoder();
// MP3 always has 1152 samples per chunk
this->free_buffer_required_ = 1152 * sizeof(int16_t) * 2; // samples * size per sample * channels
// Always reallocate the output transfer buffer to the smallest necessary size
this->output_transfer_buffer_->reallocate(this->free_buffer_required_);
break;
#endif
case AudioFileType::WAV:
this->wav_decoder_ = make_unique<esp_audio_libs::wav_decoder::WAVDecoder>();
this->wav_decoder_->reset();
// Processing WAVs doesn't actually require a specific amount of buffer size, as it is already in PCM format.
// Thus, we don't reallocate to a minimum size.
this->free_buffer_required_ = 1024;
if (this->output_transfer_buffer_->capacity() < this->free_buffer_required_) {
this->output_transfer_buffer_->reallocate(this->free_buffer_required_);
}
break;
case AudioFileType::NONE:
default:
return ESP_ERR_NOT_SUPPORTED;
break;
}
return ESP_OK;
}
AudioDecoderState AudioDecoder::decode(bool stop_gracefully) {
if (stop_gracefully) {
if (this->output_transfer_buffer_->available() == 0) {
if (this->end_of_file_) {
// The file decoder indicates it reached the end of file
return AudioDecoderState::FINISHED;
}
if (!this->input_transfer_buffer_->has_buffered_data()) {
// If all the internal buffers are empty, the decoding is done
return AudioDecoderState::FINISHED;
}
}
}
if (this->potentially_failed_count_ > MAX_POTENTIALLY_FAILED_COUNT) {
if (stop_gracefully) {
// No more new data is going to come in, so decoding is done
return AudioDecoderState::FINISHED;
}
return AudioDecoderState::FAILED;
}
FileDecoderState state = FileDecoderState::MORE_TO_PROCESS;
uint32_t decoding_start = millis();
bool first_loop_iteration = true;
size_t bytes_processed = 0;
size_t bytes_available_before_processing = 0;
while (state == FileDecoderState::MORE_TO_PROCESS) {
// Transfer decoded out
if (!this->pause_output_) {
// Never shift the data in the output transfer buffer to avoid unnecessary, slow data moves
size_t bytes_written =
this->output_transfer_buffer_->transfer_data_to_sink(pdMS_TO_TICKS(READ_WRITE_TIMEOUT_MS), false);
if (this->audio_stream_info_.has_value()) {
this->accumulated_frames_written_ += this->audio_stream_info_.value().bytes_to_frames(bytes_written);
this->playback_ms_ +=
this->audio_stream_info_.value().frames_to_milliseconds_with_remainder(&this->accumulated_frames_written_);
}
} else {
// If paused, block to avoid wasting CPU resources
delay(READ_WRITE_TIMEOUT_MS);
}
// Verify there is enough space to store more decoded audio and that the function hasn't been running too long
if ((this->output_transfer_buffer_->free() < this->free_buffer_required_) ||
(millis() - decoding_start > DECODING_TIMEOUT_MS)) {
return AudioDecoderState::DECODING;
}
// Decode more audio
// Only shift data on the first loop iteration to avoid unnecessary, slow moves
size_t bytes_read = this->input_transfer_buffer_->transfer_data_from_source(pdMS_TO_TICKS(READ_WRITE_TIMEOUT_MS),
first_loop_iteration);
if (!first_loop_iteration && (this->input_transfer_buffer_->available() < bytes_processed)) {
// Less data is available than what was processed in last iteration, so don't attempt to decode.
// This attempts to avoid the decoder from consistently trying to decode an incomplete frame. The transfer buffer
// will shift the remaining data to the start and copy more from the source the next time the decode function is
// called
break;
}
bytes_available_before_processing = this->input_transfer_buffer_->available();
if ((this->potentially_failed_count_ > 10) && (bytes_read == 0)) {
// Failed to decode in last attempt and there is no new data
if ((this->input_transfer_buffer_->free() == 0) && first_loop_iteration) {
// The input buffer is full. Since it previously failed on the exact same data, we can never recover
state = FileDecoderState::FAILED;
} else {
// Attempt to get more data next time
state = FileDecoderState::IDLE;
}
} else if (this->input_transfer_buffer_->available() == 0) {
// No data to decode, attempt to get more data next time
state = FileDecoderState::IDLE;
} else {
switch (this->audio_file_type_) {
#ifdef USE_AUDIO_FLAC_SUPPORT
case AudioFileType::FLAC:
state = this->decode_flac_();
break;
#endif
#ifdef USE_AUDIO_MP3_SUPPORT
case AudioFileType::MP3:
state = this->decode_mp3_();
break;
#endif
case AudioFileType::WAV:
state = this->decode_wav_();
break;
case AudioFileType::NONE:
default:
state = FileDecoderState::IDLE;
break;
}
}
first_loop_iteration = false;
bytes_processed = bytes_available_before_processing - this->input_transfer_buffer_->available();
if (state == FileDecoderState::POTENTIALLY_FAILED) {
++this->potentially_failed_count_;
} else if (state == FileDecoderState::END_OF_FILE) {
this->end_of_file_ = true;
} else if (state == FileDecoderState::FAILED) {
return AudioDecoderState::FAILED;
} else if (state == FileDecoderState::MORE_TO_PROCESS) {
this->potentially_failed_count_ = 0;
}
}
return AudioDecoderState::DECODING;
}
#ifdef USE_AUDIO_FLAC_SUPPORT
FileDecoderState AudioDecoder::decode_flac_() {
if (!this->audio_stream_info_.has_value()) {
// Header hasn't been read
auto result = this->flac_decoder_->read_header(this->input_transfer_buffer_->get_buffer_start(),
this->input_transfer_buffer_->available());
if (result == esp_audio_libs::flac::FLAC_DECODER_HEADER_OUT_OF_DATA) {
return FileDecoderState::POTENTIALLY_FAILED;
}
if (result != esp_audio_libs::flac::FLAC_DECODER_SUCCESS) {
// Couldn't read FLAC header
return FileDecoderState::FAILED;
}
size_t bytes_consumed = this->flac_decoder_->get_bytes_index();
this->input_transfer_buffer_->decrease_buffer_length(bytes_consumed);
// Reallocate the output transfer buffer to the smallest necessary size
this->free_buffer_required_ = flac_decoder_->get_output_buffer_size_bytes();
if (!this->output_transfer_buffer_->reallocate(this->free_buffer_required_)) {
// Couldn't reallocate output buffer
return FileDecoderState::FAILED;
}
this->audio_stream_info_ =
audio::AudioStreamInfo(this->flac_decoder_->get_sample_depth(), this->flac_decoder_->get_num_channels(),
this->flac_decoder_->get_sample_rate());
return FileDecoderState::MORE_TO_PROCESS;
}
uint32_t output_samples = 0;
auto result = this->flac_decoder_->decode_frame(
this->input_transfer_buffer_->get_buffer_start(), this->input_transfer_buffer_->available(),
reinterpret_cast<int16_t *>(this->output_transfer_buffer_->get_buffer_end()), &output_samples);
if (result == esp_audio_libs::flac::FLAC_DECODER_ERROR_OUT_OF_DATA) {
// Not an issue, just needs more data that we'll get next time.
return FileDecoderState::POTENTIALLY_FAILED;
}
size_t bytes_consumed = this->flac_decoder_->get_bytes_index();
this->input_transfer_buffer_->decrease_buffer_length(bytes_consumed);
if (result > esp_audio_libs::flac::FLAC_DECODER_ERROR_OUT_OF_DATA) {
// Corrupted frame, don't retry with current buffer content, wait for new sync
return FileDecoderState::POTENTIALLY_FAILED;
}
// We have successfully decoded some input data and have new output data
this->output_transfer_buffer_->increase_buffer_length(
this->audio_stream_info_.value().samples_to_bytes(output_samples));
if (result == esp_audio_libs::flac::FLAC_DECODER_NO_MORE_FRAMES) {
return FileDecoderState::END_OF_FILE;
}
return FileDecoderState::MORE_TO_PROCESS;
}
#endif
#ifdef USE_AUDIO_MP3_SUPPORT
FileDecoderState AudioDecoder::decode_mp3_() {
// Look for the next sync word
int buffer_length = (int) this->input_transfer_buffer_->available();
int32_t offset =
esp_audio_libs::helix_decoder::MP3FindSyncWord(this->input_transfer_buffer_->get_buffer_start(), buffer_length);
if (offset < 0) {
// New data may have the sync word
this->input_transfer_buffer_->decrease_buffer_length(buffer_length);
return FileDecoderState::POTENTIALLY_FAILED;
}
// Advance read pointer to match the offset for the syncword
this->input_transfer_buffer_->decrease_buffer_length(offset);
uint8_t *buffer_start = this->input_transfer_buffer_->get_buffer_start();
buffer_length = (int) this->input_transfer_buffer_->available();
int err = esp_audio_libs::helix_decoder::MP3Decode(this->mp3_decoder_, &buffer_start, &buffer_length,
(int16_t *) this->output_transfer_buffer_->get_buffer_end(), 0);
size_t consumed = this->input_transfer_buffer_->available() - buffer_length;
this->input_transfer_buffer_->decrease_buffer_length(consumed);
if (err) {
switch (err) {
case esp_audio_libs::helix_decoder::ERR_MP3_OUT_OF_MEMORY:
// Intentional fallthrough
case esp_audio_libs::helix_decoder::ERR_MP3_NULL_POINTER:
return FileDecoderState::FAILED;
break;
default:
// Most errors are recoverable by moving on to the next frame, so mark as potentailly failed
return FileDecoderState::POTENTIALLY_FAILED;
break;
}
} else {
esp_audio_libs::helix_decoder::MP3FrameInfo mp3_frame_info;
esp_audio_libs::helix_decoder::MP3GetLastFrameInfo(this->mp3_decoder_, &mp3_frame_info);
if (mp3_frame_info.outputSamps > 0) {
int bytes_per_sample = (mp3_frame_info.bitsPerSample / 8);
this->output_transfer_buffer_->increase_buffer_length(mp3_frame_info.outputSamps * bytes_per_sample);
if (!this->audio_stream_info_.has_value()) {
this->audio_stream_info_ =
audio::AudioStreamInfo(mp3_frame_info.bitsPerSample, mp3_frame_info.nChans, mp3_frame_info.samprate);
}
}
}
return FileDecoderState::MORE_TO_PROCESS;
}
#endif
FileDecoderState AudioDecoder::decode_wav_() {
if (!this->audio_stream_info_.has_value()) {
// Header hasn't been processed
esp_audio_libs::wav_decoder::WAVDecoderResult result = this->wav_decoder_->decode_header(
this->input_transfer_buffer_->get_buffer_start(), this->input_transfer_buffer_->available());
if (result == esp_audio_libs::wav_decoder::WAV_DECODER_SUCCESS_IN_DATA) {
this->input_transfer_buffer_->decrease_buffer_length(this->wav_decoder_->bytes_processed());
this->audio_stream_info_ = audio::AudioStreamInfo(
this->wav_decoder_->bits_per_sample(), this->wav_decoder_->num_channels(), this->wav_decoder_->sample_rate());
this->wav_bytes_left_ = this->wav_decoder_->chunk_bytes_left();
this->wav_has_known_end_ = (this->wav_bytes_left_ > 0);
return FileDecoderState::MORE_TO_PROCESS;
} else if (result == esp_audio_libs::wav_decoder::WAV_DECODER_WARNING_INCOMPLETE_DATA) {
// Available data didn't have the full header
return FileDecoderState::POTENTIALLY_FAILED;
} else {
return FileDecoderState::FAILED;
}
} else {
if (!this->wav_has_known_end_ || (this->wav_bytes_left_ > 0)) {
size_t bytes_to_copy = this->input_transfer_buffer_->available();
if (this->wav_has_known_end_) {
bytes_to_copy = std::min(bytes_to_copy, this->wav_bytes_left_);
}
bytes_to_copy = std::min(bytes_to_copy, this->output_transfer_buffer_->free());
if (bytes_to_copy > 0) {
std::memcpy(this->output_transfer_buffer_->get_buffer_end(), this->input_transfer_buffer_->get_buffer_start(),
bytes_to_copy);
this->input_transfer_buffer_->decrease_buffer_length(bytes_to_copy);
this->output_transfer_buffer_->increase_buffer_length(bytes_to_copy);
if (this->wav_has_known_end_) {
this->wav_bytes_left_ -= bytes_to_copy;
}
}
return FileDecoderState::IDLE;
}
}
return FileDecoderState::END_OF_FILE;
}
} // namespace audio
} // namespace esphome
#endif

View File

@@ -0,0 +1,135 @@
#pragma once
#ifdef USE_ESP32
#include "audio.h"
#include "audio_transfer_buffer.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "esphome/core/ring_buffer.h"
#ifdef USE_SPEAKER
#include "esphome/components/speaker/speaker.h"
#endif
#include "esp_err.h"
// esp-audio-libs
#ifdef USE_AUDIO_FLAC_SUPPORT
#include <flac_decoder.h>
#endif
#ifdef USE_AUDIO_MP3_SUPPORT
#include <mp3_decoder.h>
#endif
#include <wav_decoder.h>
namespace esphome {
namespace audio {
enum class AudioDecoderState : uint8_t {
DECODING = 0, // More data is available to decode
FINISHED, // All file data has been decoded and transferred
FAILED, // Encountered an error
};
// Only used within the AudioDecoder class; conveys the state of the particular file type decoder
enum class FileDecoderState : uint8_t {
MORE_TO_PROCESS, // Successsfully read a file chunk and more data is available to decode
IDLE, // Not enough data to decode, waiting for more to be transferred
POTENTIALLY_FAILED, // Decoder encountered a potentially recoverable error if more file data is available
FAILED, // Decoder encoutnered an uncrecoverable error
END_OF_FILE, // The specific file decoder knows its the end of the file
};
class AudioDecoder {
/*
* @brief Class that facilitates decoding an audio file.
* The audio file is read from a ring buffer source, decoded, and sent to an audio sink (ring buffer or speaker
* component).
* Supports wav, flac, and mp3 formats.
*/
public:
/// @brief Allocates the input and output transfer buffers
/// @param input_buffer_size Size of the input transfer buffer in bytes.
/// @param output_buffer_size Size of the output transfer buffer in bytes.
AudioDecoder(size_t input_buffer_size, size_t output_buffer_size);
/// @brief Deallocates the MP3 decoder (the flac and wav decoders are deallocated automatically)
~AudioDecoder();
/// @brief Adds a source ring buffer for raw file data. Takes ownership of the ring buffer in a shared_ptr.
/// @param input_ring_buffer weak_ptr of a shared_ptr of the sink ring buffer to transfer ownership
/// @return ESP_OK if successsful, ESP_ERR_NO_MEM if the transfer buffer wasn't allocated
esp_err_t add_source(std::weak_ptr<RingBuffer> &input_ring_buffer);
/// @brief Adds a sink ring buffer for decoded audio. Takes ownership of the ring buffer in a shared_ptr.
/// @param output_ring_buffer weak_ptr of a shared_ptr of the sink ring buffer to transfer ownership
/// @return ESP_OK if successsful, ESP_ERR_NO_MEM if the transfer buffer wasn't allocated
esp_err_t add_sink(std::weak_ptr<RingBuffer> &output_ring_buffer);
#ifdef USE_SPEAKER
/// @brief Adds a sink speaker for decoded audio.
/// @param speaker pointer to speaker component
/// @return ESP_OK if successsful, ESP_ERR_NO_MEM if the transfer buffer wasn't allocated
esp_err_t add_sink(speaker::Speaker *speaker);
#endif
/// @brief Sets up decoding the file
/// @param audio_file_type AudioFileType of the file
/// @return ESP_OK if successful, ESP_ERR_NO_MEM if the transfer buffers fail to allocate, or ESP_ERR_NOT_SUPPORTED if
/// the format isn't supported.
esp_err_t start(AudioFileType audio_file_type);
/// @brief Decodes audio from the ring buffer source and writes to the sink.
/// @param stop_gracefully If true, it indicates the file source is finished. The decoder will decode all the
/// reamining data and then finish.
/// @return AudioDecoderState
AudioDecoderState decode(bool stop_gracefully);
/// @brief Gets the audio stream information, if it has been decoded from the files header
/// @return optional<AudioStreamInfo> with the audio information. If not available yet, returns no value.
const optional<audio::AudioStreamInfo> &get_audio_stream_info() const { return this->audio_stream_info_; }
/// @brief Returns the duration of audio (in milliseconds) decoded and sent to the sink
/// @return Duration of decoded audio in milliseconds
uint32_t get_playback_ms() const { return this->playback_ms_; }
/// @brief Pauses sending resampled audio to the sink. If paused, it will continue to process internal buffers.
/// @param pause_state If true, audio data is not sent to the sink.
void set_pause_output_state(bool pause_state) { this->pause_output_ = pause_state; }
protected:
std::unique_ptr<esp_audio_libs::wav_decoder::WAVDecoder> wav_decoder_;
#ifdef USE_AUDIO_FLAC_SUPPORT
FileDecoderState decode_flac_();
std::unique_ptr<esp_audio_libs::flac::FLACDecoder> flac_decoder_;
#endif
#ifdef USE_AUDIO_MP3_SUPPORT
FileDecoderState decode_mp3_();
esp_audio_libs::helix_decoder::HMP3Decoder mp3_decoder_;
#endif
FileDecoderState decode_wav_();
std::unique_ptr<AudioSourceTransferBuffer> input_transfer_buffer_;
std::unique_ptr<AudioSinkTransferBuffer> output_transfer_buffer_;
AudioFileType audio_file_type_{AudioFileType::NONE};
optional<AudioStreamInfo> audio_stream_info_{};
size_t free_buffer_required_{0};
size_t wav_bytes_left_{0};
uint32_t potentially_failed_count_{0};
bool end_of_file_{false};
bool wav_has_known_end_{false};
bool pause_output_{false};
uint32_t accumulated_frames_written_{0};
uint32_t playback_ms_{0};
};
} // namespace audio
} // namespace esphome
#endif

View File

@@ -0,0 +1,308 @@
#include "audio_reader.h"
#ifdef USE_ESP_IDF
#include "esphome/core/defines.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
#include "esp_crt_bundle.h"
#endif
namespace esphome {
namespace audio {
static const uint32_t READ_WRITE_TIMEOUT_MS = 20;
static const uint32_t CONNECTION_TIMEOUT_MS = 5000;
// The number of times the http read times out with no data before throwing an error
static const uint32_t ERROR_COUNT_NO_DATA_READ_TIMEOUT = 100;
static const size_t HTTP_STREAM_BUFFER_SIZE = 2048;
static const uint8_t MAX_REDIRECTION = 5;
// Some common HTTP status codes - borrowed from http_request component accessed 20241224
enum HttpStatus {
HTTP_STATUS_OK = 200,
HTTP_STATUS_NO_CONTENT = 204,
HTTP_STATUS_PARTIAL_CONTENT = 206,
/* 3xx - Redirection */
HTTP_STATUS_MULTIPLE_CHOICES = 300,
HTTP_STATUS_MOVED_PERMANENTLY = 301,
HTTP_STATUS_FOUND = 302,
HTTP_STATUS_SEE_OTHER = 303,
HTTP_STATUS_NOT_MODIFIED = 304,
HTTP_STATUS_TEMPORARY_REDIRECT = 307,
HTTP_STATUS_PERMANENT_REDIRECT = 308,
/* 4XX - CLIENT ERROR */
HTTP_STATUS_BAD_REQUEST = 400,
HTTP_STATUS_UNAUTHORIZED = 401,
HTTP_STATUS_FORBIDDEN = 403,
HTTP_STATUS_NOT_FOUND = 404,
HTTP_STATUS_METHOD_NOT_ALLOWED = 405,
HTTP_STATUS_NOT_ACCEPTABLE = 406,
HTTP_STATUS_LENGTH_REQUIRED = 411,
/* 5xx - Server Error */
HTTP_STATUS_INTERNAL_ERROR = 500
};
AudioReader::~AudioReader() { this->cleanup_connection_(); }
esp_err_t AudioReader::add_sink(const std::weak_ptr<RingBuffer> &output_ring_buffer) {
if (current_audio_file_ != nullptr) {
// A transfer buffer isn't ncessary for a local file
this->file_ring_buffer_ = output_ring_buffer.lock();
return ESP_OK;
}
if (this->output_transfer_buffer_ != nullptr) {
this->output_transfer_buffer_->set_sink(output_ring_buffer);
return ESP_OK;
}
return ESP_ERR_INVALID_STATE;
}
esp_err_t AudioReader::start(AudioFile *audio_file, AudioFileType &file_type) {
file_type = AudioFileType::NONE;
this->current_audio_file_ = audio_file;
this->file_current_ = audio_file->data;
file_type = audio_file->file_type;
return ESP_OK;
}
esp_err_t AudioReader::start(const std::string &uri, AudioFileType &file_type) {
file_type = AudioFileType::NONE;
this->cleanup_connection_();
if (uri.empty()) {
return ESP_ERR_INVALID_ARG;
}
esp_http_client_config_t client_config = {};
client_config.url = uri.c_str();
client_config.cert_pem = nullptr;
client_config.disable_auto_redirect = false;
client_config.max_redirection_count = 10;
client_config.event_handler = http_event_handler;
client_config.user_data = this;
client_config.buffer_size = HTTP_STREAM_BUFFER_SIZE;
client_config.keep_alive_enable = true;
client_config.timeout_ms = CONNECTION_TIMEOUT_MS; // Shouldn't trigger watchdog resets if caller runs in a task
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
if (uri.find("https:") != std::string::npos) {
client_config.crt_bundle_attach = esp_crt_bundle_attach;
}
#endif
this->client_ = esp_http_client_init(&client_config);
if (this->client_ == nullptr) {
return ESP_FAIL;
}
esp_err_t err = esp_http_client_open(this->client_, 0);
if (err != ESP_OK) {
this->cleanup_connection_();
return err;
}
int64_t header_length = esp_http_client_fetch_headers(this->client_);
if (header_length < 0) {
this->cleanup_connection_();
return ESP_FAIL;
}
int status_code = esp_http_client_get_status_code(this->client_);
if ((status_code < HTTP_STATUS_OK) || (status_code > HTTP_STATUS_PERMANENT_REDIRECT)) {
this->cleanup_connection_();
return ESP_FAIL;
}
ssize_t redirect_count = 0;
while ((esp_http_client_set_redirection(this->client_) == ESP_OK) && (redirect_count < MAX_REDIRECTION)) {
err = esp_http_client_open(this->client_, 0);
if (err != ESP_OK) {
this->cleanup_connection_();
return ESP_FAIL;
}
header_length = esp_http_client_fetch_headers(this->client_);
if (header_length < 0) {
this->cleanup_connection_();
return ESP_FAIL;
}
status_code = esp_http_client_get_status_code(this->client_);
if ((status_code < HTTP_STATUS_OK) || (status_code > HTTP_STATUS_PERMANENT_REDIRECT)) {
this->cleanup_connection_();
return ESP_FAIL;
}
++redirect_count;
}
if (this->audio_file_type_ == AudioFileType::NONE) {
// Failed to determine the file type from the header, fallback to using the url
char url[500];
err = esp_http_client_get_url(this->client_, url, 500);
if (err != ESP_OK) {
this->cleanup_connection_();
return err;
}
std::string url_string = str_lower_case(url);
if (str_endswith(url_string, ".wav")) {
file_type = AudioFileType::WAV;
}
#ifdef USE_AUDIO_MP3_SUPPORT
else if (str_endswith(url_string, ".mp3")) {
file_type = AudioFileType::MP3;
}
#endif
#ifdef USE_AUDIO_FLAC_SUPPORT
else if (str_endswith(url_string, ".flac")) {
file_type = AudioFileType::FLAC;
}
#endif
else {
file_type = AudioFileType::NONE;
this->cleanup_connection_();
return ESP_ERR_NOT_SUPPORTED;
}
} else {
file_type = this->audio_file_type_;
}
this->last_data_read_ms_ = millis();
this->output_transfer_buffer_ = AudioSinkTransferBuffer::create(this->buffer_size_);
if (this->output_transfer_buffer_ == nullptr) {
return ESP_ERR_NO_MEM;
}
return ESP_OK;
}
AudioReaderState AudioReader::read() {
if (this->client_ != nullptr) {
return this->http_read_();
} else if (this->current_audio_file_ != nullptr) {
return this->file_read_();
}
return AudioReaderState::FAILED;
}
AudioFileType AudioReader::get_audio_type(const char *content_type) {
#ifdef USE_AUDIO_MP3_SUPPORT
if (strcasecmp(content_type, "mp3") == 0 || strcasecmp(content_type, "audio/mp3") == 0 ||
strcasecmp(content_type, "audio/mpeg") == 0) {
return AudioFileType::MP3;
}
#endif
if (strcasecmp(content_type, "audio/wav") == 0) {
return AudioFileType::WAV;
}
#ifdef USE_AUDIO_FLAC_SUPPORT
if (strcasecmp(content_type, "audio/flac") == 0 || strcasecmp(content_type, "audio/x-flac") == 0) {
return AudioFileType::FLAC;
}
#endif
return AudioFileType::NONE;
}
esp_err_t AudioReader::http_event_handler(esp_http_client_event_t *evt) {
// Based on https://github.com/maroc81/WeatherLily/tree/main/main/net accessed 20241224
AudioReader *this_reader = (AudioReader *) evt->user_data;
switch (evt->event_id) {
case HTTP_EVENT_ON_HEADER:
if (strcasecmp(evt->header_key, "Content-Type") == 0) {
this_reader->audio_file_type_ = get_audio_type(evt->header_value);
}
break;
default:
break;
}
return ESP_OK;
}
AudioReaderState AudioReader::file_read_() {
size_t remaining_bytes = this->current_audio_file_->length - (this->file_current_ - this->current_audio_file_->data);
if (remaining_bytes > 0) {
size_t bytes_written = this->file_ring_buffer_->write_without_replacement(this->file_current_, remaining_bytes,
pdMS_TO_TICKS(READ_WRITE_TIMEOUT_MS));
this->file_current_ += bytes_written;
return AudioReaderState::READING;
}
return AudioReaderState::FINISHED;
}
AudioReaderState AudioReader::http_read_() {
this->output_transfer_buffer_->transfer_data_to_sink(pdMS_TO_TICKS(READ_WRITE_TIMEOUT_MS), false);
if (esp_http_client_is_complete_data_received(this->client_)) {
if (this->output_transfer_buffer_->available() == 0) {
this->cleanup_connection_();
return AudioReaderState::FINISHED;
}
} else if (this->output_transfer_buffer_->free() > 0) {
size_t bytes_to_read = this->output_transfer_buffer_->free();
int received_len =
esp_http_client_read(this->client_, (char *) this->output_transfer_buffer_->get_buffer_end(), bytes_to_read);
if (received_len > 0) {
this->output_transfer_buffer_->increase_buffer_length(received_len);
this->last_data_read_ms_ = millis();
} else if (received_len < 0) {
// HTTP read error
this->cleanup_connection_();
return AudioReaderState::FAILED;
} else {
if (bytes_to_read > 0) {
// Read timed out
if ((millis() - this->last_data_read_ms_) > CONNECTION_TIMEOUT_MS) {
this->cleanup_connection_();
return AudioReaderState::FAILED;
}
delay(READ_WRITE_TIMEOUT_MS);
}
}
}
return AudioReaderState::READING;
}
void AudioReader::cleanup_connection_() {
if (this->client_ != nullptr) {
esp_http_client_close(this->client_);
esp_http_client_cleanup(this->client_);
this->client_ = nullptr;
}
}
} // namespace audio
} // namespace esphome
#endif

View File

@@ -0,0 +1,85 @@
#pragma once
#ifdef USE_ESP_IDF
#include "audio.h"
#include "audio_transfer_buffer.h"
#include "esphome/core/ring_buffer.h"
#include "esp_err.h"
#include <esp_http_client.h>
namespace esphome {
namespace audio {
enum class AudioReaderState : uint8_t {
READING = 0, // More data is available to read
FINISHED, // All data has been read and transferred
FAILED, // Encountered an error
};
class AudioReader {
/*
* @brief Class that facilitates reading a raw audio file.
* Files can be read from flash (stored in a AudioFile struct) or from an http source.
* The file data is sent to a ring buffer sink.
*/
public:
/// @brief Constructs an AudioReader object.
/// The transfer buffer isn't allocated here, but only if necessary (an http source) in the start function.
/// @param buffer_size Transfer buffer size in bytes.
AudioReader(size_t buffer_size) : buffer_size_(buffer_size) {}
~AudioReader();
/// @brief Adds a sink ring buffer for audio data. Takes ownership of the ring buffer in a shared_ptr
/// @param output_ring_buffer weak_ptr of a shared_ptr of the sink ring buffer to transfer ownership
/// @return ESP_OK if successful, ESP_ERR_INVALID_STATE otherwise
esp_err_t add_sink(const std::weak_ptr<RingBuffer> &output_ring_buffer);
/// @brief Starts reading an audio file from an http source. The transfer buffer is allocated here.
/// @param uri Web url to the http file.
/// @param file_type AudioFileType variable passed-by-reference indicating the type of file being read.
/// @return ESP_OK if successful, an ESP_ERR* code otherwise.
esp_err_t start(const std::string &uri, AudioFileType &file_type);
/// @brief Starts reading an audio file from flash. No transfer buffer is allocated.
/// @param audio_file AudioFile struct containing the file.
/// @param file_type AudioFileType variable passed-by-reference indicating the type of file being read.
/// @return ESP_OK
esp_err_t start(AudioFile *audio_file, AudioFileType &file_type);
/// @brief Reads new file data from the source and sends to the ring buffer sink.
/// @return AudioReaderState
AudioReaderState read();
protected:
/// @brief Monitors the http client events to attempt determining the file type from the Content-Type header
static esp_err_t http_event_handler(esp_http_client_event_t *evt);
/// @brief Determines the audio file type from the http header's Content-Type key
/// @param content_type string with the Content-Type key
/// @return AudioFileType of the url, if it can be determined. If not, return AudioFileType::NONE.
static AudioFileType get_audio_type(const char *content_type);
AudioReaderState file_read_();
AudioReaderState http_read_();
std::shared_ptr<RingBuffer> file_ring_buffer_;
std::unique_ptr<AudioSinkTransferBuffer> output_transfer_buffer_;
void cleanup_connection_();
size_t buffer_size_;
uint32_t last_data_read_ms_;
esp_http_client_handle_t client_{nullptr};
AudioFile *current_audio_file_{nullptr};
AudioFileType audio_file_type_{AudioFileType::NONE};
const uint8_t *file_current_{nullptr};
};
} // namespace audio
} // namespace esphome
#endif

View File

@@ -0,0 +1,161 @@
#include "audio_resampler.h"
#ifdef USE_ESP32
#include "esphome/core/hal.h"
namespace esphome {
namespace audio {
static const uint32_t READ_WRITE_TIMEOUT_MS = 20;
AudioResampler::AudioResampler(size_t input_buffer_size, size_t output_buffer_size)
: input_buffer_size_(input_buffer_size), output_buffer_size_(output_buffer_size) {
this->input_transfer_buffer_ = AudioSourceTransferBuffer::create(input_buffer_size);
this->output_transfer_buffer_ = AudioSinkTransferBuffer::create(output_buffer_size);
}
esp_err_t AudioResampler::add_source(std::weak_ptr<RingBuffer> &input_ring_buffer) {
if (this->input_transfer_buffer_ != nullptr) {
this->input_transfer_buffer_->set_source(input_ring_buffer);
return ESP_OK;
}
return ESP_ERR_NO_MEM;
}
esp_err_t AudioResampler::add_sink(std::weak_ptr<RingBuffer> &output_ring_buffer) {
if (this->output_transfer_buffer_ != nullptr) {
this->output_transfer_buffer_->set_sink(output_ring_buffer);
return ESP_OK;
}
return ESP_ERR_NO_MEM;
}
#ifdef USE_SPEAKER
esp_err_t AudioResampler::add_sink(speaker::Speaker *speaker) {
if (this->output_transfer_buffer_ != nullptr) {
this->output_transfer_buffer_->set_sink(speaker);
return ESP_OK;
}
return ESP_ERR_NO_MEM;
}
#endif
esp_err_t AudioResampler::start(AudioStreamInfo &input_stream_info, AudioStreamInfo &output_stream_info,
uint16_t number_of_taps, uint16_t number_of_filters) {
this->input_stream_info_ = input_stream_info;
this->output_stream_info_ = output_stream_info;
if ((this->input_transfer_buffer_ == nullptr) || (this->output_transfer_buffer_ == nullptr)) {
return ESP_ERR_NO_MEM;
}
if ((input_stream_info.get_bits_per_sample() > 32) || (output_stream_info.get_bits_per_sample() > 32) ||
(input_stream_info_.get_channels() != output_stream_info.get_channels())) {
return ESP_ERR_NOT_SUPPORTED;
}
if ((input_stream_info.get_sample_rate() != output_stream_info.get_sample_rate()) ||
(input_stream_info.get_bits_per_sample() != output_stream_info.get_bits_per_sample())) {
this->resampler_ = make_unique<esp_audio_libs::resampler::Resampler>(
input_stream_info.bytes_to_samples(this->input_buffer_size_),
output_stream_info.bytes_to_samples(this->output_buffer_size_));
// Use cascaded biquad filters when downsampling to avoid aliasing
bool use_pre_filter = output_stream_info.get_sample_rate() < input_stream_info.get_sample_rate();
esp_audio_libs::resampler::ResamplerConfiguration resample_config = {
.source_sample_rate = static_cast<float>(input_stream_info.get_sample_rate()),
.target_sample_rate = static_cast<float>(output_stream_info.get_sample_rate()),
.source_bits_per_sample = input_stream_info.get_bits_per_sample(),
.target_bits_per_sample = output_stream_info.get_bits_per_sample(),
.channels = input_stream_info_.get_channels(),
.use_pre_or_post_filter = use_pre_filter,
.subsample_interpolate = false, // Doubles the CPU load. Using more filters is a better alternative
.number_of_taps = number_of_taps,
.number_of_filters = number_of_filters,
};
if (!this->resampler_->initialize(resample_config)) {
// Failed to allocate the resampler's internal buffers
return ESP_ERR_NO_MEM;
}
}
return ESP_OK;
}
AudioResamplerState AudioResampler::resample(bool stop_gracefully, int32_t *ms_differential) {
if (stop_gracefully) {
if (!this->input_transfer_buffer_->has_buffered_data() && (this->output_transfer_buffer_->available() == 0)) {
return AudioResamplerState::FINISHED;
}
}
if (!this->pause_output_) {
// Move audio data to the sink without shifting the data in the output transfer buffer to avoid unnecessary, slow
// data moves
this->output_transfer_buffer_->transfer_data_to_sink(pdMS_TO_TICKS(READ_WRITE_TIMEOUT_MS), false);
} else {
// If paused, block to avoid wasting CPU resources
delay(READ_WRITE_TIMEOUT_MS);
}
this->input_transfer_buffer_->transfer_data_from_source(pdMS_TO_TICKS(READ_WRITE_TIMEOUT_MS));
if (this->input_transfer_buffer_->available() == 0) {
// No samples available to process
return AudioResamplerState::RESAMPLING;
}
const size_t bytes_free = this->output_transfer_buffer_->free();
const uint32_t frames_free = this->output_stream_info_.bytes_to_frames(bytes_free);
const size_t bytes_available = this->input_transfer_buffer_->available();
const uint32_t frames_available = this->input_stream_info_.bytes_to_frames(bytes_available);
if ((this->input_stream_info_.get_sample_rate() != this->output_stream_info_.get_sample_rate()) ||
(this->input_stream_info_.get_bits_per_sample() != this->output_stream_info_.get_bits_per_sample())) {
// Adjust gain by -3 dB to avoid clipping due to the resampling process
esp_audio_libs::resampler::ResamplerResults results =
this->resampler_->resample(this->input_transfer_buffer_->get_buffer_start(),
this->output_transfer_buffer_->get_buffer_end(), frames_available, frames_free, -3);
this->input_transfer_buffer_->decrease_buffer_length(this->input_stream_info_.frames_to_bytes(results.frames_used));
this->output_transfer_buffer_->increase_buffer_length(
this->output_stream_info_.frames_to_bytes(results.frames_generated));
// Resampling causes slight differences in the durations used versus generated. Computes the difference in
// millisconds. The callback function passing the played audio duration uses the difference to convert from output
// duration to input duration.
this->accumulated_frames_used_ += results.frames_used;
this->accumulated_frames_generated_ += results.frames_generated;
const int32_t used_ms =
this->input_stream_info_.frames_to_milliseconds_with_remainder(&this->accumulated_frames_used_);
const int32_t generated_ms =
this->output_stream_info_.frames_to_milliseconds_with_remainder(&this->accumulated_frames_generated_);
*ms_differential = used_ms - generated_ms;
} else {
// No resampling required, copy samples directly to the output transfer buffer
*ms_differential = 0;
const size_t bytes_to_transfer = std::min(this->output_stream_info_.frames_to_bytes(frames_free),
this->input_stream_info_.frames_to_bytes(frames_available));
std::memcpy((void *) this->output_transfer_buffer_->get_buffer_end(),
(void *) this->input_transfer_buffer_->get_buffer_start(), bytes_to_transfer);
this->input_transfer_buffer_->decrease_buffer_length(bytes_to_transfer);
this->output_transfer_buffer_->increase_buffer_length(bytes_to_transfer);
}
return AudioResamplerState::RESAMPLING;
}
} // namespace audio
} // namespace esphome
#endif

View File

@@ -0,0 +1,101 @@
#pragma once
#ifdef USE_ESP32
#include "audio.h"
#include "audio_transfer_buffer.h"
#include "esphome/core/defines.h"
#include "esphome/core/ring_buffer.h"
#ifdef USE_SPEAKER
#include "esphome/components/speaker/speaker.h"
#endif
#include "esp_err.h"
#include <resampler.h> // esp-audio-libs
namespace esphome {
namespace audio {
enum class AudioResamplerState : uint8_t {
RESAMPLING, // More data is available to resample
FINISHED, // All file data has been resampled and transferred
FAILED, // Unused state included for consistency among Audio classes
};
class AudioResampler {
/*
* @brief Class that facilitates resampling audio.
* The audio data is read from a ring buffer source, resampled, and sent to an audio sink (ring buffer or speaker
* component). Also supports converting bits per sample.
*/
public:
/// @brief Allocates the input and output transfer buffers
/// @param input_buffer_size Size of the input transfer buffer in bytes.
/// @param output_buffer_size Size of the output transfer buffer in bytes.
AudioResampler(size_t input_buffer_size, size_t output_buffer_size);
/// @brief Adds a source ring buffer for audio data. Takes ownership of the ring buffer in a shared_ptr.
/// @param input_ring_buffer weak_ptr of a shared_ptr of the sink ring buffer to transfer ownership
/// @return ESP_OK if successsful, ESP_ERR_NO_MEM if the transfer buffer wasn't allocated
esp_err_t add_source(std::weak_ptr<RingBuffer> &input_ring_buffer);
/// @brief Adds a sink ring buffer for resampled audio. Takes ownership of the ring buffer in a shared_ptr.
/// @param output_ring_buffer weak_ptr of a shared_ptr of the sink ring buffer to transfer ownership
/// @return ESP_OK if successsful, ESP_ERR_NO_MEM if the transfer buffer wasn't allocated
esp_err_t add_sink(std::weak_ptr<RingBuffer> &output_ring_buffer);
#ifdef USE_SPEAKER
/// @brief Adds a sink speaker for decoded audio.
/// @param speaker pointer to speaker component
/// @return ESP_OK if successsful, ESP_ERR_NO_MEM if the transfer buffer wasn't allocated
esp_err_t add_sink(speaker::Speaker *speaker);
#endif
/// @brief Sets up the class to resample.
/// @param input_stream_info The incoming sample rate, bits per sample, and number of channels
/// @param output_stream_info The desired outgoing sample rate, bits per sample, and number of channels
/// @param number_of_taps Number of taps per FIR filter
/// @param number_of_filters Number of FIR filters
/// @return ESP_OK if it is able to convert the incoming stream,
/// ESP_ERR_NO_MEM if the transfer buffers failed to allocate,
/// ESP_ERR_NOT_SUPPORTED if the stream can't be converted.
esp_err_t start(AudioStreamInfo &input_stream_info, AudioStreamInfo &output_stream_info, uint16_t number_of_taps,
uint16_t number_of_filters);
/// @brief Resamples audio from the ring buffer source and writes to the sink.
/// @param stop_gracefully If true, it indicates the file decoder is finished. The resampler will resample all the
/// remaining audio and then finish.
/// @param ms_differential Pointer to a (int32_t) variable that will store the difference, in milliseconds, between
/// the duration of input audio used and the duration of output audio generated.
/// @return AudioResamplerState
AudioResamplerState resample(bool stop_gracefully, int32_t *ms_differential);
/// @brief Pauses sending resampled audio to the sink. If paused, it will continue to process internal buffers.
/// @param pause_state If true, audio data is not sent to the sink.
void set_pause_output_state(bool pause_state) { this->pause_output_ = pause_state; }
protected:
std::unique_ptr<AudioSourceTransferBuffer> input_transfer_buffer_;
std::unique_ptr<AudioSinkTransferBuffer> output_transfer_buffer_;
size_t input_buffer_size_;
size_t output_buffer_size_;
uint32_t accumulated_frames_used_{0};
uint32_t accumulated_frames_generated_{0};
bool pause_output_{false};
AudioStreamInfo input_stream_info_;
AudioStreamInfo output_stream_info_;
std::unique_ptr<esp_audio_libs::resampler::Resampler> resampler_;
};
} // namespace audio
} // namespace esphome
#endif

View File

@@ -0,0 +1,175 @@
#include "audio_transfer_buffer.h"
#ifdef USE_ESP32
#include "esphome/core/helpers.h"
namespace esphome {
namespace audio {
AudioTransferBuffer::~AudioTransferBuffer() { this->deallocate_buffer_(); };
std::unique_ptr<AudioSinkTransferBuffer> AudioSinkTransferBuffer::create(size_t buffer_size) {
std::unique_ptr<AudioSinkTransferBuffer> sink_buffer = make_unique<AudioSinkTransferBuffer>();
if (!sink_buffer->allocate_buffer_(buffer_size)) {
return nullptr;
}
return sink_buffer;
}
std::unique_ptr<AudioSourceTransferBuffer> AudioSourceTransferBuffer::create(size_t buffer_size) {
std::unique_ptr<AudioSourceTransferBuffer> source_buffer = make_unique<AudioSourceTransferBuffer>();
if (!source_buffer->allocate_buffer_(buffer_size)) {
return nullptr;
}
return source_buffer;
}
size_t AudioTransferBuffer::free() const {
if (this->buffer_size_ == 0) {
return 0;
}
return this->buffer_size_ - (this->buffer_length_ + (this->data_start_ - this->buffer_));
}
void AudioTransferBuffer::decrease_buffer_length(size_t bytes) {
this->buffer_length_ -= bytes;
if (this->buffer_length_ > 0) {
this->data_start_ += bytes;
} else {
// All the data in the buffer has been consumed, reset the start pointer
this->data_start_ = this->buffer_;
}
}
void AudioTransferBuffer::increase_buffer_length(size_t bytes) { this->buffer_length_ += bytes; }
void AudioTransferBuffer::clear_buffered_data() {
this->buffer_length_ = 0;
if (this->ring_buffer_.use_count() > 0) {
this->ring_buffer_->reset();
}
}
void AudioSinkTransferBuffer::clear_buffered_data() {
this->buffer_length_ = 0;
if (this->ring_buffer_.use_count() > 0) {
this->ring_buffer_->reset();
}
#ifdef USE_SPEAKER
if (this->speaker_ != nullptr) {
this->speaker_->stop();
}
#endif
}
bool AudioTransferBuffer::has_buffered_data() const {
if (this->ring_buffer_.use_count() > 0) {
return ((this->ring_buffer_->available() > 0) || (this->available() > 0));
}
return (this->available() > 0);
}
bool AudioTransferBuffer::reallocate(size_t new_buffer_size) {
if (this->buffer_length_ > 0) {
// Buffer currently has data, so reallocation is impossible
return false;
}
this->deallocate_buffer_();
return this->allocate_buffer_(new_buffer_size);
}
bool AudioTransferBuffer::allocate_buffer_(size_t buffer_size) {
this->buffer_size_ = buffer_size;
RAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
this->buffer_ = allocator.allocate(this->buffer_size_);
if (this->buffer_ == nullptr) {
return false;
}
this->data_start_ = this->buffer_;
this->buffer_length_ = 0;
return true;
}
void AudioTransferBuffer::deallocate_buffer_() {
if (this->buffer_ != nullptr) {
RAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
allocator.deallocate(this->buffer_, this->buffer_size_);
this->buffer_ = nullptr;
this->data_start_ = nullptr;
}
this->buffer_size_ = 0;
this->buffer_length_ = 0;
}
size_t AudioSourceTransferBuffer::transfer_data_from_source(TickType_t ticks_to_wait, bool pre_shift) {
if (pre_shift) {
// Shift data in buffer to start
if (this->buffer_length_ > 0) {
memmove(this->buffer_, this->data_start_, this->buffer_length_);
}
this->data_start_ = this->buffer_;
}
size_t bytes_to_read = this->free();
size_t bytes_read = 0;
if (bytes_to_read > 0) {
if (this->ring_buffer_.use_count() > 0) {
bytes_read = this->ring_buffer_->read((void *) this->get_buffer_end(), bytes_to_read, ticks_to_wait);
}
this->increase_buffer_length(bytes_read);
}
return bytes_read;
}
size_t AudioSinkTransferBuffer::transfer_data_to_sink(TickType_t ticks_to_wait, bool post_shift) {
size_t bytes_written = 0;
if (this->available()) {
#ifdef USE_SPEAKER
if (this->speaker_ != nullptr) {
bytes_written = this->speaker_->play(this->data_start_, this->available(), ticks_to_wait);
} else
#endif
if (this->ring_buffer_.use_count() > 0) {
bytes_written =
this->ring_buffer_->write_without_replacement((void *) this->data_start_, this->available(), ticks_to_wait);
}
this->decrease_buffer_length(bytes_written);
}
if (post_shift) {
// Shift unwritten data to the start of the buffer
memmove(this->buffer_, this->data_start_, this->buffer_length_);
this->data_start_ = this->buffer_;
}
return bytes_written;
}
bool AudioSinkTransferBuffer::has_buffered_data() const {
#ifdef USE_SPEAKER
if (this->speaker_ != nullptr) {
return (this->speaker_->has_buffered_data() || (this->available() > 0));
}
#endif
if (this->ring_buffer_.use_count() > 0) {
return ((this->ring_buffer_->available() > 0) || (this->available() > 0));
}
return (this->available() > 0);
}
} // namespace audio
} // namespace esphome
#endif

View File

@@ -0,0 +1,144 @@
#pragma once
#ifdef USE_ESP32
#include "esphome/core/defines.h"
#include "esphome/core/ring_buffer.h"
#ifdef USE_SPEAKER
#include "esphome/components/speaker/speaker.h"
#endif
#include "esp_err.h"
#include <freertos/FreeRTOS.h>
namespace esphome {
namespace audio {
class AudioTransferBuffer {
/*
* @brief Class that facilitates tranferring data between a buffer and an audio source or sink.
* The transfer buffer is a typical C array that temporarily holds data for processing in other audio components.
* Both sink and source transfer buffers can use a ring buffer as the sink/source.
* - The ring buffer is stored in a shared_ptr, so destroying the transfer buffer object will release ownership.
*/
public:
/// @brief Destructor that deallocates the transfer buffer
~AudioTransferBuffer();
/// @brief Returns a pointer to the start of the transfer buffer where available() bytes of exisiting data can be read
uint8_t *get_buffer_start() const { return this->data_start_; }
/// @brief Returns a pointer to the end of the transfer buffer where free() bytes of new data can be written
uint8_t *get_buffer_end() const { return this->data_start_ + this->buffer_length_; }
/// @brief Updates the internal state of the transfer buffer. This should be called after reading data
/// @param bytes The number of bytes consumed/read
void decrease_buffer_length(size_t bytes);
/// @brief Updates the internal state of the transfer buffer. This should be called after writing data
/// @param bytes The number of bytes written
void increase_buffer_length(size_t bytes);
/// @brief Returns the transfer buffer's currently available bytes to read
size_t available() const { return this->buffer_length_; }
/// @brief Returns the transfer buffers allocated bytes
size_t capacity() const { return this->buffer_size_; }
/// @brief Returns the transfer buffer's currrently free bytes available to write
size_t free() const;
/// @brief Clears data in the transfer buffer and, if possible, the source/sink.
virtual void clear_buffered_data();
/// @brief Tests if there is any data in the tranfer buffer or the source/sink.
/// @return True if there is data, false otherwise.
virtual bool has_buffered_data() const;
bool reallocate(size_t new_buffer_size);
protected:
/// @brief Allocates the transfer buffer in external memory, if available.
/// @param buffer_size The number of bytes to allocate
/// @return True is successful, false otherwise.
bool allocate_buffer_(size_t buffer_size);
/// @brief Deallocates the buffer and resets the class variables.
void deallocate_buffer_();
// A possible source or sink for the transfer buffer
std::shared_ptr<RingBuffer> ring_buffer_;
uint8_t *buffer_{nullptr};
uint8_t *data_start_{nullptr};
size_t buffer_size_{0};
size_t buffer_length_{0};
};
class AudioSinkTransferBuffer : public AudioTransferBuffer {
/*
* @brief A class that implements a transfer buffer for audio sinks.
* Supports writing processed data in the transfer buffer to a ring buffer or a speaker component.
*/
public:
/// @brief Creates a new sink transfer buffer.
/// @param buffer_size Size of the transfer buffer in bytes.
/// @return unique_ptr if successfully allocated, nullptr otherwise
static std::unique_ptr<AudioSinkTransferBuffer> create(size_t buffer_size);
/// @brief Writes any available data in the transfer buffer to the sink.
/// @param ticks_to_wait FreeRTOS ticks to block while waiting for the sink to have enough space
/// @param post_shift If true, all remaining data is moved to the start of the buffer after transferring to the sink.
/// Defaults to true.
/// @return Number of bytes written
size_t transfer_data_to_sink(TickType_t ticks_to_wait, bool post_shift = true);
/// @brief Adds a ring buffer as the transfer buffer's sink.
/// @param ring_buffer weak_ptr to the allocated ring buffer
void set_sink(const std::weak_ptr<RingBuffer> &ring_buffer) { this->ring_buffer_ = ring_buffer.lock(); }
#ifdef USE_SPEAKER
/// @brief Adds a speaker as the transfer buffer's sink.
/// @param speaker Pointer to the speaker component
void set_sink(speaker::Speaker *speaker) { this->speaker_ = speaker; }
#endif
void clear_buffered_data() override;
bool has_buffered_data() const override;
protected:
#ifdef USE_SPEAKER
speaker::Speaker *speaker_{nullptr};
#endif
};
class AudioSourceTransferBuffer : public AudioTransferBuffer {
/*
* @brief A class that implements a transfer buffer for audio sources.
* Supports reading audio data from a ring buffer into the transfer buffer for processing.
*/
public:
/// @brief Creates a new source transfer buffer.
/// @param buffer_size Size of the transfer buffer in bytes.
/// @return unique_ptr if successfully allocated, nullptr otherwise
static std::unique_ptr<AudioSourceTransferBuffer> create(size_t buffer_size);
/// @brief Reads any available data from the sink into the transfer buffer.
/// @param ticks_to_wait FreeRTOS ticks to block while waiting for the source to have enough data
/// @param pre_shift If true, any unwritten data is moved to the start of the buffer before transferring from the
/// source. Defaults to true.
/// @return Number of bytes read
size_t transfer_data_from_source(TickType_t ticks_to_wait, bool pre_shift = true);
/// @brief Adds a ring buffer as the transfer buffer's source.
/// @param ring_buffer weak_ptr to the allocated ring buffer
void set_source(const std::weak_ptr<RingBuffer> &ring_buffer) { this->ring_buffer_ = ring_buffer.lock(); };
};
} // namespace audio
} // namespace esphome
#endif

View File

@@ -0,0 +1,41 @@
from esphome import automation
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_MIC_GAIN
from esphome.core import coroutine_with_priority
CODEOWNERS = ["@kbx81"]
IS_PLATFORM_COMPONENT = True
audio_adc_ns = cg.esphome_ns.namespace("audio_adc")
AudioAdc = audio_adc_ns.class_("AudioAdc")
SetMicGainAction = audio_adc_ns.class_("SetMicGainAction", automation.Action)
SET_MIC_GAIN_ACTION_SCHEMA = cv.maybe_simple_value(
{
cv.GenerateID(): cv.use_id(AudioAdc),
cv.Required(CONF_MIC_GAIN): cv.templatable(cv.decibel),
},
key=CONF_MIC_GAIN,
)
@automation.register_action(
"audio_adc.set_mic_gain", SetMicGainAction, SET_MIC_GAIN_ACTION_SCHEMA
)
async def audio_adc_set_mic_gain_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
template_ = await cg.templatable(config.get(CONF_MIC_GAIN), args, float)
cg.add(var.set_mic_gain(template_))
return var
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_define("USE_AUDIO_ADC")
cg.add_global(audio_adc_ns.using)

View File

@@ -0,0 +1,17 @@
#pragma once
#include "esphome/core/defines.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace audio_adc {
class AudioAdc {
public:
virtual bool set_mic_gain(float mic_gain) = 0;
virtual float mic_gain() = 0;
};
} // namespace audio_adc
} // namespace esphome

View File

@@ -0,0 +1,23 @@
#pragma once
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "audio_adc.h"
namespace esphome {
namespace audio_adc {
template<typename... Ts> class SetMicGainAction : public Action<Ts...> {
public:
explicit SetMicGainAction(AudioAdc *audio_adc) : audio_adc_(audio_adc) {}
TEMPLATABLE_VALUE(float, mic_gain)
void play(Ts... x) override { this->audio_adc_->set_mic_gain(this->mic_gain_.value(x...)); }
protected:
AudioAdc *audio_adc_;
};
} // namespace audio_adc
} // namespace esphome

View File

@@ -0,0 +1,6 @@
import esphome.codegen as cg
CODEOWNERS = ["@clydebarrow"]
DEPENDENCIES = ["i2c"]
axs15231_ns = cg.esphome_ns.namespace("axs15231")

View File

@@ -0,0 +1,36 @@
from esphome import pins
import esphome.codegen as cg
from esphome.components import i2c, touchscreen
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN
from .. import axs15231_ns
AXS15231Touchscreen = axs15231_ns.class_(
"AXS15231Touchscreen",
touchscreen.Touchscreen,
i2c.I2CDevice,
)
CONFIG_SCHEMA = (
touchscreen.touchscreen_schema("50ms")
.extend(
{
cv.GenerateID(): cv.declare_id(AXS15231Touchscreen),
cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema,
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
}
)
.extend(i2c.i2c_device_schema(0x3B))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await touchscreen.register_touchscreen(var, config)
await i2c.register_i2c_device(var, config)
if interrupt_pin := config.get(CONF_INTERRUPT_PIN):
cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin)))
if reset_pin := config.get(CONF_RESET_PIN):
cg.add(var.set_reset_pin(await cg.gpio_pin_expression(reset_pin)))

View File

@@ -0,0 +1,64 @@
#include "axs15231_touchscreen.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace axs15231 {
static const char *const TAG = "ax15231.touchscreen";
constexpr static const uint8_t AXS_READ_TOUCHPAD[11] = {0xb5, 0xab, 0xa5, 0x5a, 0x0, 0x0, 0x0, 0x8};
#define ERROR_CHECK(err) \
if ((err) != i2c::ERROR_OK) { \
this->status_set_warning("Failed to communicate"); \
return; \
}
void AXS15231Touchscreen::setup() {
ESP_LOGCONFIG(TAG, "Setting up AXS15231 Touchscreen...");
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(false);
delay(5);
this->reset_pin_->digital_write(true);
delay(10);
}
if (this->interrupt_pin_ != nullptr) {
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT);
this->interrupt_pin_->setup();
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
}
this->x_raw_max_ = this->display_->get_native_width();
this->y_raw_max_ = this->display_->get_native_height();
ESP_LOGCONFIG(TAG, "AXS15231 Touchscreen setup complete");
}
void AXS15231Touchscreen::update_touches() {
i2c::ErrorCode err;
uint8_t data[8]{};
err = this->write(AXS_READ_TOUCHPAD, sizeof(AXS_READ_TOUCHPAD), false);
ERROR_CHECK(err);
err = this->read(data, sizeof(data));
ERROR_CHECK(err);
this->status_clear_warning();
if (data[0] != 0) // no touches
return;
uint16_t x = encode_uint16(data[2] & 0xF, data[3]);
uint16_t y = encode_uint16(data[4] & 0xF, data[5]);
this->add_raw_touch_position_(0, x, y);
}
void AXS15231Touchscreen::dump_config() {
ESP_LOGCONFIG(TAG, "AXS15231 Touchscreen:");
LOG_I2C_DEVICE(this);
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
LOG_PIN(" Reset Pin: ", this->reset_pin_);
ESP_LOGCONFIG(TAG, " Width: %d", this->x_raw_max_);
ESP_LOGCONFIG(TAG, " Height: %d", this->y_raw_max_);
}
} // namespace axs15231
} // namespace esphome

View File

@@ -0,0 +1,27 @@
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/touchscreen/touchscreen.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace axs15231 {
class AXS15231Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; }
protected:
void update_touches() override;
InternalGPIOPin *interrupt_pin_{};
GPIOPin *reset_pin_{};
};
} // namespace axs15231
} // namespace esphome

View File

@@ -58,7 +58,7 @@ class BinarySensor : public EntityBase, public EntityBase_DeviceClass {
void publish_initial_state(bool state); void publish_initial_state(bool state);
/// The current reported state of the binary sensor. /// The current reported state of the binary sensor.
bool state; bool state{false};
void add_filter(Filter *filter); void add_filter(Filter *filter);
void add_filters(const std::vector<Filter *> &filters); void add_filters(const std::vector<Filter *> &filters);

View File

@@ -15,10 +15,11 @@ from esphome.components.libretiny.const import (
) )
from esphome.core import CORE from esphome.core import CORE
from .boards import BK72XX_BOARDS, BK72XX_BOARD_PINS from .boards import BK72XX_BOARD_PINS, BK72XX_BOARDS
CODEOWNERS = ["@kuba2k2"] CODEOWNERS = ["@kuba2k2"]
AUTO_LOAD = ["libretiny"] AUTO_LOAD = ["libretiny"]
IS_TARGET_PLATFORM = True
COMPONENT_DATA = LibreTinyComponent( COMPONENT_DATA = LibreTinyComponent(
name=COMPONENT_BK72XX, name=COMPONENT_BK72XX,

View File

@@ -25,8 +25,7 @@ void BLEClient::loop() {
void BLEClient::dump_config() { void BLEClient::dump_config() {
ESP_LOGCONFIG(TAG, "BLE Client:"); ESP_LOGCONFIG(TAG, "BLE Client:");
ESP_LOGCONFIG(TAG, " Address: %s", this->address_str().c_str()); BLEClientBase::dump_config();
ESP_LOGCONFIG(TAG, " Auto-Connect: %s", TRUEFALSE(this->auto_connect_));
} }
bool BLEClient::parse_device(const espbt::ESPBTDevice &device) { bool BLEClient::parse_device(const espbt::ESPBTDevice &device) {

View File

@@ -11,6 +11,7 @@ from esphome.const import (
DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_SIGNAL_STRENGTH,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_DECIBEL_MILLIWATT, UNIT_DECIBEL_MILLIWATT,
CONF_NOTIFY,
) )
from .. import ble_client_ns from .. import ble_client_ns
@@ -19,7 +20,6 @@ DEPENDENCIES = ["ble_client"]
CONF_DESCRIPTOR_UUID = "descriptor_uuid" CONF_DESCRIPTOR_UUID = "descriptor_uuid"
CONF_NOTIFY = "notify"
CONF_ON_NOTIFY = "on_notify" CONF_ON_NOTIFY = "on_notify"
TYPE_CHARACTERISTIC = "characteristic" TYPE_CHARACTERISTIC = "characteristic"
TYPE_RSSI = "rssi" TYPE_RSSI = "rssi"

View File

@@ -6,6 +6,7 @@ from esphome.const import (
CONF_CHARACTERISTIC_UUID, CONF_CHARACTERISTIC_UUID,
CONF_ID, CONF_ID,
CONF_SERVICE_UUID, CONF_SERVICE_UUID,
CONF_NOTIFY,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
) )
@@ -15,7 +16,6 @@ DEPENDENCIES = ["ble_client"]
CONF_DESCRIPTOR_UUID = "descriptor_uuid" CONF_DESCRIPTOR_UUID = "descriptor_uuid"
CONF_NOTIFY = "notify"
CONF_ON_NOTIFY = "on_notify" CONF_ON_NOTIFY = "on_notify"
adv_data_t = cg.std_vector.template(cg.uint8) adv_data_t = cg.std_vector.template(cg.uint8)

View File

@@ -45,7 +45,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
cv.Optional(CONF_IBEACON_MAJOR): cv.uint16_t, cv.Optional(CONF_IBEACON_MAJOR): cv.uint16_t,
cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t, cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t,
cv.Optional(CONF_IBEACON_UUID): cv.uuid, cv.Optional(CONF_IBEACON_UUID): esp32_ble_tracker.bt_uuid,
} }
) )
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
@@ -79,7 +79,7 @@ async def to_code(config):
cg.add(var.set_service_uuid128(uuid128)) cg.add(var.set_service_uuid128(uuid128))
if ibeacon_uuid := config.get(CONF_IBEACON_UUID): if ibeacon_uuid := config.get(CONF_IBEACON_UUID):
ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(ibeacon_uuid)) ibeacon_uuid = esp32_ble_tracker.as_reversed_hex_array(ibeacon_uuid)
cg.add(var.set_ibeacon_uuid(ibeacon_uuid)) cg.add(var.set_ibeacon_uuid(ibeacon_uuid))
if (ibeacon_major := config.get(CONF_IBEACON_MAJOR)) is not None: if (ibeacon_major := config.get(CONF_IBEACON_MAJOR)) is not None:

View File

@@ -13,6 +13,11 @@ namespace bluetooth_proxy {
static const char *const TAG = "bluetooth_proxy.connection"; static const char *const TAG = "bluetooth_proxy.connection";
void BluetoothConnection::dump_config() {
ESP_LOGCONFIG(TAG, "BLE Connection:");
BLEClientBase::dump_config();
}
bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) { esp_ble_gattc_cb_param_t *param) {
if (!BLEClientBase::gattc_event_handler(event, gattc_if, param)) if (!BLEClientBase::gattc_event_handler(event, gattc_if, param))

View File

@@ -11,6 +11,7 @@ class BluetoothProxy;
class BluetoothConnection : public esp32_ble_client::BLEClientBase { class BluetoothConnection : public esp32_ble_client::BLEClientBase {
public: public:
void dump_config() override;
bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override; esp_ble_gattc_cb_param_t *param) override;
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;

View File

@@ -475,6 +475,11 @@ void BluetoothProxy::send_connections_free() {
api::BluetoothConnectionsFreeResponse call; api::BluetoothConnectionsFreeResponse call;
call.free = this->get_bluetooth_connections_free(); call.free = this->get_bluetooth_connections_free();
call.limit = this->get_bluetooth_connections_limit(); call.limit = this->get_bluetooth_connections_limit();
for (auto *connection : this->connections_) {
if (connection->address_ != 0) {
call.allocated.push_back(connection->address_);
}
}
this->api_connection_->send_bluetooth_connections_free_response(call); this->api_connection_->send_bluetooth_connections_free_response(call);
} }

View File

@@ -15,6 +15,9 @@
#include "bluetooth_connection.h" #include "bluetooth_connection.h"
#include <esp_bt.h>
#include <esp_bt_device.h>
namespace esphome { namespace esphome {
namespace bluetooth_proxy { namespace bluetooth_proxy {
@@ -114,6 +117,11 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
return flags; return flags;
} }
std::string get_bluetooth_mac_address_pretty() {
const uint8_t *mac = esp_bt_dev_get_address();
return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}
protected: protected:
void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device); void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device);

View File

@@ -16,7 +16,7 @@ CODEOWNERS = ["@neffs", "@kbx81"]
DOMAIN = "bme68x_bsec2" DOMAIN = "bme68x_bsec2"
BSEC2_LIBRARY_VERSION = "v1.7.2502" BSEC2_LIBRARY_VERSION = "v1.8.2610"
CONF_ALGORITHM_OUTPUT = "algorithm_output" CONF_ALGORITHM_OUTPUT = "algorithm_output"
CONF_BME68X_BSEC2_ID = "bme68x_bsec2_id" CONF_BME68X_BSEC2_ID = "bme68x_bsec2_id"

View File

@@ -204,11 +204,11 @@ void BME68xBSEC2Component::update_subscription_() {
} }
void BME68xBSEC2Component::run_() { void BME68xBSEC2Component::run_() {
this->op_mode_ = this->bsec_settings_.op_mode;
int64_t curr_time_ns = this->get_time_ns_(); int64_t curr_time_ns = this->get_time_ns_();
if (curr_time_ns < this->next_call_ns_) { if (curr_time_ns < this->bsec_settings_.next_call) {
return; return;
} }
this->op_mode_ = this->bsec_settings_.op_mode;
uint8_t status; uint8_t status;
ESP_LOGV(TAG, "Performing sensor run"); ESP_LOGV(TAG, "Performing sensor run");
@@ -219,57 +219,60 @@ void BME68xBSEC2Component::run_() {
ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC2 error code %d)", this->bsec_status_); ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC2 error code %d)", this->bsec_status_);
return; return;
} }
this->next_call_ns_ = this->bsec_settings_.next_call;
if (this->bsec_settings_.trigger_measurement) { switch (this->bsec_settings_.op_mode) {
bme68x_get_conf(&bme68x_conf, &this->bme68x_); case BME68X_FORCED_MODE:
bme68x_get_conf(&bme68x_conf, &this->bme68x_);
bme68x_conf.os_hum = this->bsec_settings_.humidity_oversampling; bme68x_conf.os_hum = this->bsec_settings_.humidity_oversampling;
bme68x_conf.os_temp = this->bsec_settings_.temperature_oversampling; bme68x_conf.os_temp = this->bsec_settings_.temperature_oversampling;
bme68x_conf.os_pres = this->bsec_settings_.pressure_oversampling; bme68x_conf.os_pres = this->bsec_settings_.pressure_oversampling;
bme68x_set_conf(&bme68x_conf, &this->bme68x_); bme68x_set_conf(&bme68x_conf, &this->bme68x_);
this->bme68x_heatr_conf_.enable = BME68X_ENABLE;
this->bme68x_heatr_conf_.heatr_temp = this->bsec_settings_.heater_temperature;
this->bme68x_heatr_conf_.heatr_dur = this->bsec_settings_.heater_duration;
// status = bme68x_set_op_mode(this->bsec_settings_.op_mode, &this->bme68x_);
status = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &this->bme68x_heatr_conf_, &this->bme68x_);
status = bme68x_set_op_mode(BME68X_FORCED_MODE, &this->bme68x_);
this->op_mode_ = BME68X_FORCED_MODE;
ESP_LOGV(TAG, "Using forced mode");
break;
case BME68X_PARALLEL_MODE:
if (this->op_mode_ != this->bsec_settings_.op_mode) {
bme68x_get_conf(&bme68x_conf, &this->bme68x_);
bme68x_conf.os_hum = this->bsec_settings_.humidity_oversampling;
bme68x_conf.os_temp = this->bsec_settings_.temperature_oversampling;
bme68x_conf.os_pres = this->bsec_settings_.pressure_oversampling;
bme68x_set_conf(&bme68x_conf, &this->bme68x_);
switch (this->bsec_settings_.op_mode) {
case BME68X_FORCED_MODE:
this->bme68x_heatr_conf_.enable = BME68X_ENABLE; this->bme68x_heatr_conf_.enable = BME68X_ENABLE;
this->bme68x_heatr_conf_.heatr_temp = this->bsec_settings_.heater_temperature; this->bme68x_heatr_conf_.heatr_temp_prof = this->bsec_settings_.heater_temperature_profile;
this->bme68x_heatr_conf_.heatr_dur = this->bsec_settings_.heater_duration; this->bme68x_heatr_conf_.heatr_dur_prof = this->bsec_settings_.heater_duration_profile;
this->bme68x_heatr_conf_.profile_len = this->bsec_settings_.heater_profile_len;
this->bme68x_heatr_conf_.shared_heatr_dur =
BSEC_TOTAL_HEAT_DUR -
(bme68x_get_meas_dur(BME68X_PARALLEL_MODE, &bme68x_conf, &this->bme68x_) / INT64_C(1000));
status = bme68x_set_op_mode(this->bsec_settings_.op_mode, &this->bme68x_); status = bme68x_set_heatr_conf(BME68X_PARALLEL_MODE, &this->bme68x_heatr_conf_, &this->bme68x_);
status = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &this->bme68x_heatr_conf_, &this->bme68x_);
status = bme68x_set_op_mode(BME68X_FORCED_MODE, &this->bme68x_);
this->op_mode_ = BME68X_FORCED_MODE;
this->sleep_mode_ = false;
ESP_LOGV(TAG, "Using forced mode");
break; status = bme68x_set_op_mode(BME68X_PARALLEL_MODE, &this->bme68x_);
case BME68X_PARALLEL_MODE: this->op_mode_ = BME68X_PARALLEL_MODE;
if (this->op_mode_ != this->bsec_settings_.op_mode) { ESP_LOGV(TAG, "Using parallel mode");
this->bme68x_heatr_conf_.enable = BME68X_ENABLE; }
this->bme68x_heatr_conf_.heatr_temp_prof = this->bsec_settings_.heater_temperature_profile; break;
this->bme68x_heatr_conf_.heatr_dur_prof = this->bsec_settings_.heater_duration_profile; case BME68X_SLEEP_MODE:
this->bme68x_heatr_conf_.profile_len = this->bsec_settings_.heater_profile_len; if (this->op_mode_ != this->bsec_settings_.op_mode) {
this->bme68x_heatr_conf_.shared_heatr_dur = bme68x_set_op_mode(BME68X_SLEEP_MODE, &this->bme68x_);
BSEC_TOTAL_HEAT_DUR - this->op_mode_ = BME68X_SLEEP_MODE;
(bme68x_get_meas_dur(BME68X_PARALLEL_MODE, &bme68x_conf, &this->bme68x_) / INT64_C(1000)); ESP_LOGV(TAG, "Using sleep mode");
}
status = bme68x_set_heatr_conf(BME68X_PARALLEL_MODE, &this->bme68x_heatr_conf_, &this->bme68x_); break;
}
status = bme68x_set_op_mode(BME68X_PARALLEL_MODE, &this->bme68x_);
this->op_mode_ = BME68X_PARALLEL_MODE;
this->sleep_mode_ = false;
ESP_LOGV(TAG, "Using parallel mode");
}
break;
case BME68X_SLEEP_MODE:
if (!this->sleep_mode_) {
bme68x_set_op_mode(BME68X_SLEEP_MODE, &this->bme68x_);
this->sleep_mode_ = true;
ESP_LOGV(TAG, "Using sleep mode");
}
break;
}
if (this->bsec_settings_.trigger_measurement && this->bsec_settings_.op_mode != BME68X_SLEEP_MODE) {
uint32_t meas_dur = 0; uint32_t meas_dur = 0;
meas_dur = bme68x_get_meas_dur(this->op_mode_, &bme68x_conf, &this->bme68x_); meas_dur = bme68x_get_meas_dur(this->op_mode_, &bme68x_conf, &this->bme68x_);
ESP_LOGV(TAG, "Queueing read in %uus", meas_dur); ESP_LOGV(TAG, "Queueing read in %uus", meas_dur);

View File

@@ -113,13 +113,11 @@ class BME68xBSEC2Component : public Component {
struct bme68x_heatr_conf bme68x_heatr_conf_; struct bme68x_heatr_conf bme68x_heatr_conf_;
uint8_t op_mode_; // operating mode of sensor uint8_t op_mode_; // operating mode of sensor
bool sleep_mode_;
bsec_library_return_t bsec_status_{BSEC_OK}; bsec_library_return_t bsec_status_{BSEC_OK};
int8_t bme68x_status_{BME68X_OK}; int8_t bme68x_status_{BME68X_OK};
int64_t last_time_ms_{0}; int64_t last_time_ms_{0};
uint32_t millis_overflow_counter_{0}; uint32_t millis_overflow_counter_{0};
int64_t next_call_ns_{0};
std::queue<std::function<void()>> queue_; std::queue<std::function<void()>> queue_;

View File

@@ -95,7 +95,7 @@ void BMP085Component::read_pressure_() {
return; return;
} }
uint32_t value = (uint32_t(buffer[0]) << 16) | (uint32_t(buffer[1]) << 8) | uint32_t(buffer[0]); uint32_t value = (uint32_t(buffer[0]) << 16) | (uint32_t(buffer[1]) << 8) | uint32_t(buffer[2]);
if ((value >> 5) == 0) { if ((value >> 5) == 0) {
ESP_LOGW(TAG, "Invalid pressure!"); ESP_LOGW(TAG, "Invalid pressure!");
this->status_set_warning(); this->status_set_warning();

View File

@@ -0,0 +1,5 @@
CODEOWNERS = ["@clydebarrow"]
# Allows bytebuffer to be configured in yaml, to allow use of the C++ api.
CONFIG_SCHEMA = {}

View File

@@ -0,0 +1,421 @@
#pragma once
#include <utility>
#include <vector>
#include <cinttypes>
#include <cstddef>
#include "esphome/core/helpers.h"
namespace esphome {
namespace bytebuffer {
enum Endian { LITTLE, BIG };
/**
* A class modelled on the Java ByteBuffer class. It wraps a vector of bytes and permits putting and getting
* items of various sizes, with an automatically incremented position.
*
* There are three variables maintained pointing into the buffer:
*
* capacity: the maximum amount of data that can be stored - set on construction and cannot be changed
* limit: the limit of the data currently available to get or put
* position: the current insert or extract position
*
* 0 <= position <= limit <= capacity
*
* In addition a mark can be set to the current position with mark(). A subsequent call to reset() will restore
* the position to the mark.
*
* The buffer can be marked to be little-endian (default) or big-endian. All subsequent operations will use that order.
*
* The flip() operation will reset the position to 0 and limit to the current position. This is useful for reading
* data from a buffer after it has been written.
*
* The code is defined here in the header file rather than in a .cpp file, so that it does not get compiled if not used.
* The templated functions ensure that only those typed functions actually used are compiled. The functions
* are implicitly inline-able which will aid performance.
*/
class ByteBuffer {
public:
// Default constructor (compatibility with TEMPLATABLE_VALUE)
// Creates a zero-length ByteBuffer which is little use to anybody.
ByteBuffer() : ByteBuffer(std::vector<uint8_t>()) {}
/**
* Create a new Bytebuffer with the given capacity
*/
ByteBuffer(size_t capacity, Endian endianness = LITTLE)
: data_(std::vector<uint8_t>(capacity)), endianness_(endianness), limit_(capacity){};
// templated functions to implement putting and getting data of various types. There are two flavours of all
// functions - one that uses the position as the offset, and updates the position accordingly, and one that
// takes an explicit offset and does not update the position.
// Separate temnplates are provided for types that fit into 32 bits and those that are bigger. These delegate
// the actual put/get to common code based around those sizes.
// This reduces the code size and execution time for smaller types. A similar structure for e.g. 16 bits is unlikely
// to provide any further benefit given that all target platforms are native 32 bit.
template<typename T>
T get(typename std::enable_if<std::is_integral<T>::value, T>::type * = 0,
typename std::enable_if<(sizeof(T) <= sizeof(uint32_t)), T>::type * = 0) {
// integral types that fit into 32 bit
return static_cast<T>(this->get_uint32_(sizeof(T)));
}
template<typename T>
T get(size_t offset, typename std::enable_if<std::is_integral<T>::value, T>::type * = 0,
typename std::enable_if<(sizeof(T) <= sizeof(uint32_t)), T>::type * = 0) {
return static_cast<T>(this->get_uint32_(offset, sizeof(T)));
}
template<typename T>
void put(const T &value, typename std::enable_if<std::is_integral<T>::value, T>::type * = 0,
typename std::enable_if<(sizeof(T) <= sizeof(uint32_t)), T>::type * = 0) {
this->put_uint32_(static_cast<uint32_t>(value), sizeof(T));
}
template<typename T>
void put(const T &value, size_t offset, typename std::enable_if<std::is_integral<T>::value, T>::type * = 0,
typename std::enable_if<(sizeof(T) <= sizeof(uint32_t)), T>::type * = 0) {
this->put_uint32_(static_cast<uint32_t>(value), offset, sizeof(T));
}
// integral types that do not fit into 32 bit (basically only 64 bit types)
template<typename T>
T get(typename std::enable_if<std::is_integral<T>::value, T>::type * = 0,
typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) {
return static_cast<T>(this->get_uint64_(sizeof(T)));
}
template<typename T>
T get(size_t offset, typename std::enable_if<std::is_integral<T>::value, T>::type * = 0,
typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) {
return static_cast<T>(this->get_uint64_(offset, sizeof(T)));
}
template<typename T>
void put(const T &value, typename std::enable_if<std::is_integral<T>::value, T>::type * = 0,
typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) {
this->put_uint64_(value, sizeof(T));
}
template<typename T>
void put(const T &value, size_t offset, typename std::enable_if<std::is_integral<T>::value, T>::type * = 0,
typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) {
this->put_uint64_(static_cast<uint64_t>(value), offset, sizeof(T));
}
// floating point types. Caters for 32 and 64 bit floating point.
template<typename T>
T get(typename std::enable_if<std::is_floating_point<T>::value, T>::type * = 0,
typename std::enable_if<(sizeof(T) == sizeof(uint32_t)), T>::type * = 0) {
return bit_cast<T>(this->get_uint32_(sizeof(T)));
}
template<typename T>
T get(typename std::enable_if<std::is_floating_point<T>::value, T>::type * = 0,
typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) {
return bit_cast<T>(this->get_uint64_(sizeof(T)));
}
template<typename T>
T get(size_t offset, typename std::enable_if<std::is_floating_point<T>::value, T>::type * = 0,
typename std::enable_if<(sizeof(T) == sizeof(uint32_t)), T>::type * = 0) {
return bit_cast<T>(this->get_uint32_(offset, sizeof(T)));
}
template<typename T>
T get(size_t offset, typename std::enable_if<std::is_floating_point<T>::value, T>::type * = 0,
typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) {
return bit_cast<T>(this->get_uint64_(offset, sizeof(T)));
}
template<typename T>
void put(const T &value, typename std::enable_if<std::is_floating_point<T>::value, T>::type * = 0,
typename std::enable_if<(sizeof(T) <= sizeof(uint32_t)), T>::type * = 0) {
this->put_uint32_(bit_cast<uint32_t>(value), sizeof(T));
}
template<typename T>
void put(const T &value, typename std::enable_if<std::is_floating_point<T>::value, T>::type * = 0,
typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) {
this->put_uint64_(bit_cast<uint64_t>(value), sizeof(T));
}
template<typename T>
void put(const T &value, size_t offset, typename std::enable_if<std::is_floating_point<T>::value, T>::type * = 0,
typename std::enable_if<(sizeof(T) <= sizeof(uint32_t)), T>::type * = 0) {
this->put_uint32_(bit_cast<uint32_t>(value), offset, sizeof(T));
}
template<typename T>
void put(const T &value, size_t offset, typename std::enable_if<std::is_floating_point<T>::value, T>::type * = 0,
typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) {
this->put_uint64_(bit_cast<uint64_t>(value), offset, sizeof(T));
}
template<typename T> static ByteBuffer wrap(T value, Endian endianness = LITTLE) {
ByteBuffer buffer = ByteBuffer(sizeof(T), endianness);
buffer.put(value);
buffer.flip();
return buffer;
}
static ByteBuffer wrap(std::vector<uint8_t> const &data, Endian endianness = LITTLE) {
ByteBuffer buffer = {data};
buffer.endianness_ = endianness;
return buffer;
}
static ByteBuffer wrap(const uint8_t *ptr, size_t len, Endian endianness = LITTLE) {
return wrap(std::vector<uint8_t>(ptr, ptr + len), endianness);
}
// convenience functions with explicit types named..
void put_float(float value) { this->put(value); }
void put_double(double value) { this->put(value); }
uint8_t get_uint8() { return this->data_[this->position_++]; }
// Get a 16 bit unsigned value, increment by 2
uint16_t get_uint16() { return this->get<uint16_t>(); }
// Get a 24 bit unsigned value, increment by 3
uint32_t get_uint24() { return this->get_uint32_(3); };
// Get a 32 bit unsigned value, increment by 4
uint32_t get_uint32() { return this->get<uint32_t>(); };
// Get a 64 bit unsigned value, increment by 8
uint64_t get_uint64() { return this->get<uint64_t>(); };
// Signed versions of the get functions
uint8_t get_int8() { return static_cast<int8_t>(this->get_uint8()); };
int16_t get_int16() { return this->get<uint16_t>(); }
int32_t get_int32() { return this->get<int32_t>(); }
int64_t get_int64() { return this->get<int64_t>(); }
// Get a float value, increment by 4
float get_float() { return this->get<float>(); }
// Get a double value, increment by 8
double get_double() { return this->get<double>(); }
// Get a bool value, increment by 1
bool get_bool() { return static_cast<bool>(this->get_uint8()); }
uint32_t get_int24(size_t offset) {
auto value = this->get_uint24(offset);
uint32_t mask = (~static_cast<uint32_t>(0)) << 23;
if ((value & mask) != 0)
value |= mask;
return value;
}
uint32_t get_int24() {
auto value = this->get_uint24();
uint32_t mask = (~static_cast<uint32_t>(0)) << 23;
if ((value & mask) != 0)
value |= mask;
return value;
}
std::vector<uint8_t> get_vector(size_t length, size_t offset) {
auto start = this->data_.begin() + offset;
return {start, start + length};
}
std::vector<uint8_t> get_vector(size_t length) {
auto result = this->get_vector(length, this->position_);
this->position_ += length;
return result;
}
// Convenience named functions
void put_uint8(uint8_t value) { this->data_[this->position_++] = value; }
void put_uint16(uint16_t value) { this->put(value); }
void put_uint24(uint32_t value) { this->put_uint32_(value, 3); }
void put_uint32(uint32_t value) { this->put(value); }
void put_uint64(uint64_t value) { this->put(value); }
// Signed versions of the put functions
void put_int8(int8_t value) { this->put_uint8(static_cast<uint8_t>(value)); }
void put_int16(int16_t value) { this->put(value); }
void put_int24(int32_t value) { this->put_uint32_(value, 3); }
void put_int32(int32_t value) { this->put(value); }
void put_int64(int64_t value) { this->put(value); }
// Extra put functions
void put_bool(bool value) { this->put_uint8(value); }
// versions of the above with an offset, these do not update the position
uint64_t get_uint64(size_t offset) { return this->get<uint64_t>(offset); }
uint32_t get_uint24(size_t offset) { return this->get_uint32_(offset, 3); };
double get_double(size_t offset) { return get<double>(offset); }
// Get one byte from the buffer, increment position by 1
uint8_t get_uint8(size_t offset) { return this->data_[offset]; }
// Get a 16 bit unsigned value, increment by 2
uint16_t get_uint16(size_t offset) { return get<uint16_t>(offset); }
// Get a 24 bit unsigned value, increment by 3
uint32_t get_uint32(size_t offset) { return this->get<uint32_t>(offset); };
// Get a 64 bit unsigned value, increment by 8
uint8_t get_int8(size_t offset) { return get<int8_t>(offset); }
int16_t get_int16(size_t offset) { return get<int16_t>(offset); }
int32_t get_int32(size_t offset) { return get<int32_t>(offset); }
int64_t get_int64(size_t offset) { return get<int64_t>(offset); }
// Get a float value, increment by 4
float get_float(size_t offset) { return get<float>(offset); }
// Get a double value, increment by 8
// Get a bool value, increment by 1
bool get_bool(size_t offset) { return this->get_uint8(offset); }
void put_uint8(uint8_t value, size_t offset) { this->data_[offset] = value; }
void put_uint16(uint16_t value, size_t offset) { this->put(value, offset); }
void put_uint24(uint32_t value, size_t offset) { this->put(value, offset); }
void put_uint32(uint32_t value, size_t offset) { this->put(value, offset); }
void put_uint64(uint64_t value, size_t offset) { this->put(value, offset); }
// Signed versions of the put functions
void put_int8(int8_t value, size_t offset) { this->put_uint8(static_cast<uint8_t>(value), offset); }
void put_int16(int16_t value, size_t offset) { this->put(value, offset); }
void put_int24(int32_t value, size_t offset) { this->put_uint32_(value, offset, 3); }
void put_int32(int32_t value, size_t offset) { this->put(value, offset); }
void put_int64(int64_t value, size_t offset) { this->put(value, offset); }
// Extra put functions
void put_float(float value, size_t offset) { this->put(value, offset); }
void put_double(double value, size_t offset) { this->put(value, offset); }
void put_bool(bool value, size_t offset) { this->put_uint8(value, offset); }
void put(const std::vector<uint8_t> &value, size_t offset) {
std::copy(value.begin(), value.end(), this->data_.begin() + offset);
}
void put_vector(const std::vector<uint8_t> &value, size_t offset) { this->put(value, offset); }
void put(const std::vector<uint8_t> &value) {
this->put_vector(value, this->position_);
this->position_ += value.size();
}
void put_vector(const std::vector<uint8_t> &value) { this->put(value); }
// Getters
inline size_t get_capacity() const { return this->data_.size(); }
inline size_t get_position() const { return this->position_; }
inline size_t get_limit() const { return this->limit_; }
inline size_t get_remaining() const { return this->get_limit() - this->get_position(); }
inline Endian get_endianness() const { return this->endianness_; }
inline void mark() { this->mark_ = this->position_; }
inline void big_endian() { this->endianness_ = BIG; }
inline void little_endian() { this->endianness_ = LITTLE; }
// retrieve a pointer to the underlying data.
std::vector<uint8_t> get_data() { return this->data_; };
void get_bytes(void *dest, size_t length) {
std::copy(this->data_.begin() + this->position_, this->data_.begin() + this->position_ + length, (uint8_t *) dest);
this->position_ += length;
}
void get_bytes(void *dest, size_t length, size_t offset) {
std::copy(this->data_.begin() + offset, this->data_.begin() + offset + length, (uint8_t *) dest);
}
void rewind() { this->position_ = 0; }
void reset() { this->position_ = this->mark_; }
void set_limit(size_t limit) { this->limit_ = limit; }
void set_position(size_t position) { this->position_ = position; }
void clear() {
this->limit_ = this->get_capacity();
this->position_ = 0;
}
void flip() {
this->limit_ = this->position_;
this->position_ = 0;
}
protected:
uint64_t get_uint64_(size_t offset, size_t length) const {
uint64_t value = 0;
if (this->endianness_ == LITTLE) {
offset += length;
while (length-- != 0) {
value <<= 8;
value |= this->data_[--offset];
}
} else {
while (length-- != 0) {
value <<= 8;
value |= this->data_[offset++];
}
}
return value;
}
uint64_t get_uint64_(size_t length) {
auto result = this->get_uint64_(this->position_, length);
this->position_ += length;
return result;
}
uint32_t get_uint32_(size_t offset, size_t length) const {
uint32_t value = 0;
if (this->endianness_ == LITTLE) {
offset += length;
while (length-- != 0) {
value <<= 8;
value |= this->data_[--offset];
}
} else {
while (length-- != 0) {
value <<= 8;
value |= this->data_[offset++];
}
}
return value;
}
uint32_t get_uint32_(size_t length) {
auto result = this->get_uint32_(this->position_, length);
this->position_ += length;
return result;
}
/// Putters
void put_uint64_(uint64_t value, size_t length) {
this->put_uint64_(value, this->position_, length);
this->position_ += length;
}
void put_uint32_(uint32_t value, size_t length) {
this->put_uint32_(value, this->position_, length);
this->position_ += length;
}
void put_uint64_(uint64_t value, size_t offset, size_t length) {
if (this->endianness_ == LITTLE) {
while (length-- != 0) {
this->data_[offset++] = static_cast<uint8_t>(value);
value >>= 8;
}
} else {
offset += length;
while (length-- != 0) {
this->data_[--offset] = static_cast<uint8_t>(value);
value >>= 8;
}
}
}
void put_uint32_(uint32_t value, size_t offset, size_t length) {
if (this->endianness_ == LITTLE) {
while (length-- != 0) {
this->data_[offset++] = static_cast<uint8_t>(value);
value >>= 8;
}
} else {
offset += length;
while (length-- != 0) {
this->data_[--offset] = static_cast<uint8_t>(value);
value >>= 8;
}
}
}
ByteBuffer(std::vector<uint8_t> const &data) : data_(data), limit_(data.size()) {}
std::vector<uint8_t> data_;
Endian endianness_{LITTLE};
size_t position_{0};
size_t mark_{0};
size_t limit_{0};
};
} // namespace bytebuffer
} // namespace esphome

View File

@@ -57,6 +57,8 @@ class CH422GGPIOPin : public GPIOPin {
void set_inverted(bool inverted) { inverted_ = inverted; } void set_inverted(bool inverted) { inverted_ = inverted; }
void set_flags(gpio::Flags flags); void set_flags(gpio::Flags flags);
gpio::Flags get_flags() const override { return this->flags_; }
protected: protected:
CH422GComponent *parent_{}; CH422GComponent *parent_{};
uint8_t pin_{}; uint8_t pin_{};

View File

@@ -0,0 +1,2 @@
CODEOWNERS = ["@kkosik20"]
DEPENDENCIES = ["i2c"]

View File

@@ -0,0 +1,47 @@
#include "chsc6x_touchscreen.h"
namespace esphome {
namespace chsc6x {
void CHSC6XTouchscreen::setup() {
ESP_LOGCONFIG(TAG, "Setting up CHSC6X Touchscreen...");
if (this->interrupt_pin_ != nullptr) {
this->interrupt_pin_->setup();
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
}
if (this->x_raw_max_ == this->x_raw_min_) {
this->x_raw_max_ = this->display_->get_native_width();
}
if (this->y_raw_max_ == this->y_raw_min_) {
this->y_raw_max_ = this->display_->get_native_height();
}
ESP_LOGCONFIG(TAG, "CHSC6X Touchscreen setup complete");
}
void CHSC6XTouchscreen::update_touches() {
uint8_t data[CHSC6X_REG_STATUS_LEN];
if (!this->read_bytes(CHSC6X_REG_STATUS, data, sizeof(data))) {
return;
}
uint8_t num_of_touches = data[CHSC6X_REG_STATUS_TOUCH];
if (num_of_touches == 1) {
uint16_t x = data[CHSC6X_REG_STATUS_X_COR];
uint16_t y = data[CHSC6X_REG_STATUS_Y_COR];
this->add_raw_touch_position_(0, x, y);
}
}
void CHSC6XTouchscreen::dump_config() {
ESP_LOGCONFIG(TAG, "CHSC6X Touchscreen:");
LOG_I2C_DEVICE(this);
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
ESP_LOGCONFIG(TAG, " Touch timeout: %d", this->touch_timeout_);
ESP_LOGCONFIG(TAG, " x_raw_max_: %d", this->x_raw_max_);
ESP_LOGCONFIG(TAG, " y_raw_max_: %d", this->y_raw_max_);
}
} // namespace chsc6x
} // namespace esphome

View File

@@ -0,0 +1,34 @@
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/touchscreen/touchscreen.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
namespace esphome {
namespace chsc6x {
static const char *const TAG = "chsc6x.touchscreen";
static const uint8_t CHSC6X_REG_STATUS = 0x00;
static const uint8_t CHSC6X_REG_STATUS_TOUCH = 0x00;
static const uint8_t CHSC6X_REG_STATUS_X_COR = 0x02;
static const uint8_t CHSC6X_REG_STATUS_Y_COR = 0x04;
static const uint8_t CHSC6X_REG_STATUS_LEN = 0x05;
static const uint8_t CHSC6X_CHIP_ID = 0x2e;
class CHSC6XTouchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice {
public:
void setup() override;
void update_touches() override;
void dump_config() override;
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
protected:
InternalGPIOPin *interrupt_pin_{};
};
} // namespace chsc6x
} // namespace esphome

View File

@@ -0,0 +1,33 @@
from esphome import pins
import esphome.codegen as cg
from esphome.components import i2c, touchscreen
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_INTERRUPT_PIN
chsc6x_ns = cg.esphome_ns.namespace("chsc6x")
CHSC6XTouchscreen = chsc6x_ns.class_(
"CHSC6XTouchscreen",
touchscreen.Touchscreen,
i2c.I2CDevice,
)
CONFIG_SCHEMA = (
touchscreen.touchscreen_schema("100ms")
.extend(
{
cv.GenerateID(): cv.declare_id(CHSC6XTouchscreen),
cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema,
}
)
.extend(i2c.i2c_device_schema(0x2E))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await touchscreen.register_touchscreen(var, config)
await i2c.register_i2c_device(var, config)
if interrupt_pin := config.get(CONF_INTERRUPT_PIN):
cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin)))

View File

@@ -115,14 +115,24 @@ CONF_MAX_HUMIDITY = "max_humidity"
CONF_TARGET_HUMIDITY = "target_humidity" CONF_TARGET_HUMIDITY = "target_humidity"
visual_temperature = cv.float_with_unit( visual_temperature = cv.float_with_unit(
"visual_temperature", "(°C|° C|°|C|° K|° K|K|°F|° F|F)?" "visual_temperature", "(°C|° C|°|C|°K|° K|K|°F|° F|F)?"
) )
def single_visual_temperature(value): VISUAL_TEMPERATURE_STEP_SCHEMA = cv.Schema(
if isinstance(value, dict): {
return value cv.Required(CONF_TARGET_TEMPERATURE): visual_temperature,
cv.Required(CONF_CURRENT_TEMPERATURE): visual_temperature,
}
)
def visual_temperature_step(value):
# Allow defining target/current temperature steps separately
if isinstance(value, dict):
return VISUAL_TEMPERATURE_STEP_SCHEMA(value)
# Otherwise, use the single value for both properties
value = visual_temperature(value) value = visual_temperature(value)
return VISUAL_TEMPERATURE_STEP_SCHEMA( return VISUAL_TEMPERATURE_STEP_SCHEMA(
{ {
@@ -141,16 +151,6 @@ ControlTrigger = climate_ns.class_(
"ControlTrigger", automation.Trigger.template(ClimateCall.operator("ref")) "ControlTrigger", automation.Trigger.template(ClimateCall.operator("ref"))
) )
VISUAL_TEMPERATURE_STEP_SCHEMA = cv.Any(
single_visual_temperature,
cv.Schema(
{
cv.Required(CONF_TARGET_TEMPERATURE): visual_temperature,
cv.Required(CONF_CURRENT_TEMPERATURE): visual_temperature,
}
),
)
CLIMATE_SCHEMA = ( CLIMATE_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA) .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
@@ -162,7 +162,7 @@ CLIMATE_SCHEMA = (
{ {
cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature, cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature,
cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature, cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature,
cv.Optional(CONF_TEMPERATURE_STEP): VISUAL_TEMPERATURE_STEP_SCHEMA, cv.Optional(CONF_TEMPERATURE_STEP): visual_temperature_step,
cv.Optional(CONF_MIN_HUMIDITY): cv.percentage_int, cv.Optional(CONF_MIN_HUMIDITY): cv.percentage_int,
cv.Optional(CONF_MAX_HUMIDITY): cv.percentage_int, cv.Optional(CONF_MAX_HUMIDITY): cv.percentage_int,
} }

View File

@@ -37,8 +37,9 @@ void ClimateIR::setup() {
this->publish_state(); this->publish_state();
}); });
this->current_temperature = this->sensor_->state; this->current_temperature = this->sensor_->state;
} else } else {
this->current_temperature = NAN; this->current_temperature = NAN;
}
// restore set points // restore set points
auto restore = this->restore_state_(); auto restore = this->restore_state_();
if (restore.has_value()) { if (restore.has_value()) {

View File

@@ -131,8 +131,9 @@ bool CoolixClimate::on_coolix(climate::Climate *parent, remote_base::RemoteRecei
} else { } else {
parent->mode = climate::CLIMATE_MODE_FAN_ONLY; parent->mode = climate::CLIMATE_MODE_FAN_ONLY;
} }
} else } else {
parent->mode = climate::CLIMATE_MODE_COOL; parent->mode = climate::CLIMATE_MODE_COOL;
}
// Fan Speed // Fan Speed
if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || parent->mode == climate::CLIMATE_MODE_HEAT_COOL || if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || parent->mode == climate::CLIMATE_MODE_HEAT_COOL ||

View File

@@ -1,8 +1,5 @@
#include "cse7766.h" #include "cse7766.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include <cinttypes>
#include <iomanip>
#include <sstream>
namespace esphome { namespace esphome {
namespace cse7766 { namespace cse7766 {
@@ -43,7 +40,7 @@ bool CSE7766Component::check_byte_() {
uint8_t index = this->raw_data_index_; uint8_t index = this->raw_data_index_;
uint8_t byte = this->raw_data_[index]; uint8_t byte = this->raw_data_[index];
if (index == 0) { if (index == 0) {
return !((byte != 0x55) && ((byte & 0xF0) != 0xF0) && (byte != 0xAA)); return (byte == 0x55) || ((byte & 0xF0) == 0xF0) || (byte == 0xAA);
} }
if (index == 1) { if (index == 1) {
@@ -72,12 +69,8 @@ bool CSE7766Component::check_byte_() {
void CSE7766Component::parse_data_() { void CSE7766Component::parse_data_() {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
{ {
std::stringstream ss; std::string s = format_hex_pretty(this->raw_data_, sizeof(this->raw_data_));
ss << "Raw data:" << std::hex << std::uppercase << std::setfill('0'); ESP_LOGVV(TAG, "Raw data: %s", s.c_str());
for (uint8_t i = 0; i < 23; i++) {
ss << ' ' << std::setw(2) << static_cast<unsigned>(this->raw_data_[i]);
}
ESP_LOGVV(TAG, "%s", ss.str().c_str());
} }
#endif #endif
@@ -211,21 +204,20 @@ void CSE7766Component::parse_data_() {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
{ {
std::stringstream ss; std::string buf = "Parsed:";
ss << "Parsed:";
if (have_voltage) { if (have_voltage) {
ss << " V=" << voltage << "V"; buf += str_sprintf(" V=%fV", voltage);
} }
if (have_current) { if (have_current) {
ss << " I=" << current * 1000.0f << "mA (~" << calculated_current * 1000.0f << "mA)"; buf += str_sprintf(" I=%fmA (~%fmA)", current * 1000.0f, calculated_current * 1000.0f);
} }
if (have_power) { if (have_power) {
ss << " P=" << power << "W"; buf += str_sprintf(" P=%fW", power);
} }
if (energy != 0.0f) { if (energy != 0.0f) {
ss << " E=" << energy << "kWh (" << cf_pulses << ")"; buf += str_sprintf(" E=%fkWh (%u)", energy, cf_pulses);
} }
ESP_LOGVV(TAG, "%s", ss.str().c_str()); ESP_LOGVV(TAG, "%s", buf.c_str());
} }
#endif #endif
} }

View File

@@ -1,28 +1,5 @@
import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import binary_sensor
from .. import cst816_ns CONFIG_SCHEMA = cv.invalid(
from ..touchscreen import CST816Touchscreen, CST816ButtonListener "The CST816 binary sensor has been removed. Instead use the touchscreen binary sensor with the 'use_raw' flag set."
CONF_CST816_ID = "cst816_id"
CST816Button = cst816_ns.class_(
"CST816Button",
binary_sensor.BinarySensor,
cg.Component,
CST816ButtonListener,
cg.Parented.template(CST816Touchscreen),
) )
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(CST816Button).extend(
{
cv.GenerateID(CONF_CST816_ID): cv.use_id(CST816Touchscreen),
}
)
async def to_code(config):
var = await binary_sensor.new_binary_sensor(config)
await cg.register_component(var, config)
await cg.register_parented(var, config[CONF_CST816_ID])

View File

@@ -1,27 +0,0 @@
#pragma once
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/cst816/touchscreen/cst816_touchscreen.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace cst816 {
class CST816Button : public binary_sensor::BinarySensor,
public Component,
public CST816ButtonListener,
public Parented<CST816Touchscreen> {
public:
void setup() override {
this->parent_->register_button_listener(this);
this->publish_initial_state(false);
}
void dump_config() override { LOG_BINARY_SENSOR("", "CST816 Button", this); }
void update_button(bool state) override { this->publish_state(state); }
};
} // namespace cst816
} // namespace esphome

View File

@@ -37,14 +37,6 @@ void CST816Touchscreen::continue_setup_() {
ESP_LOGCONFIG(TAG, "CST816 Touchscreen setup complete"); ESP_LOGCONFIG(TAG, "CST816 Touchscreen setup complete");
} }
void CST816Touchscreen::update_button_state_(bool state) {
if (this->button_touched_ == state)
return;
this->button_touched_ = state;
for (auto *listener : this->button_listeners_)
listener->update_button(state);
}
void CST816Touchscreen::setup() { void CST816Touchscreen::setup() {
ESP_LOGCONFIG(TAG, "Setting up CST816 Touchscreen..."); ESP_LOGCONFIG(TAG, "Setting up CST816 Touchscreen...");
if (this->reset_pin_ != nullptr) { if (this->reset_pin_ != nullptr) {
@@ -68,18 +60,13 @@ void CST816Touchscreen::update_touches() {
} }
uint8_t num_of_touches = data[REG_TOUCH_NUM] & 3; uint8_t num_of_touches = data[REG_TOUCH_NUM] & 3;
if (num_of_touches == 0) { if (num_of_touches == 0) {
this->update_button_state_(false);
return; return;
} }
uint16_t x = encode_uint16(data[REG_XPOS_HIGH] & 0xF, data[REG_XPOS_LOW]); uint16_t x = encode_uint16(data[REG_XPOS_HIGH] & 0xF, data[REG_XPOS_LOW]);
uint16_t y = encode_uint16(data[REG_YPOS_HIGH] & 0xF, data[REG_YPOS_LOW]); uint16_t y = encode_uint16(data[REG_YPOS_HIGH] & 0xF, data[REG_YPOS_LOW]);
ESP_LOGV(TAG, "Read touch %d/%d", x, y); ESP_LOGV(TAG, "Read touch %d/%d", x, y);
if (x >= this->x_raw_max_) { this->add_raw_touch_position_(0, x, y);
this->update_button_state_(true);
} else {
this->add_raw_touch_position_(0, x, y);
}
} }
void CST816Touchscreen::dump_config() { void CST816Touchscreen::dump_config() {
@@ -87,6 +74,8 @@ void CST816Touchscreen::dump_config() {
LOG_I2C_DEVICE(this); LOG_I2C_DEVICE(this);
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
LOG_PIN(" Reset Pin: ", this->reset_pin_); LOG_PIN(" Reset Pin: ", this->reset_pin_);
ESP_LOGCONFIG(TAG, " X Raw Min: %d, X Raw Max: %d", this->x_raw_min_, this->x_raw_max_);
ESP_LOGCONFIG(TAG, " Y Raw Min: %d, Y Raw Max: %d", this->y_raw_min_, this->y_raw_max_);
const char *name; const char *name;
switch (this->chip_id_) { switch (this->chip_id_) {
case CST820_CHIP_ID: case CST820_CHIP_ID:

View File

@@ -40,7 +40,6 @@ class CST816Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice
public: public:
void setup() override; void setup() override;
void update_touches() override; void update_touches() override;
void register_button_listener(CST816ButtonListener *listener) { this->button_listeners_.push_back(listener); }
void dump_config() override; void dump_config() override;
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
@@ -49,14 +48,11 @@ class CST816Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice
protected: protected:
void continue_setup_(); void continue_setup_();
void update_button_state_(bool state);
InternalGPIOPin *interrupt_pin_{}; InternalGPIOPin *interrupt_pin_{};
GPIOPin *reset_pin_{}; GPIOPin *reset_pin_{};
uint8_t chip_id_{}; uint8_t chip_id_{};
bool skip_probe_{}; // if set, do not expect to be able to probe the controller on the i2c bus. bool skip_probe_{}; // if set, do not expect to be able to probe the controller on the i2c bus.
std::vector<CST816ButtonListener *> button_listeners_;
bool button_touched_{};
}; };
} // namespace cst816 } // namespace cst816

View File

@@ -1,3 +0,0 @@
import esphome.codegen as cg
custom_ns = cg.esphome_ns.namespace("custom")

View File

@@ -1,31 +1,5 @@
import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import binary_sensor
from esphome.const import CONF_BINARY_SENSORS, CONF_ID, CONF_LAMBDA
from .. import custom_ns
CustomBinarySensorConstructor = custom_ns.class_("CustomBinarySensorConstructor") CONFIG_SCHEMA = cv.invalid(
'The "custom" component has been removed. Consider conversion to an external component.\nhttps://esphome.io/guides/contributing#a-note-about-custom-components'
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(CustomBinarySensorConstructor),
cv.Required(CONF_LAMBDA): cv.returning_lambda,
cv.Required(CONF_BINARY_SENSORS): cv.ensure_list(
binary_sensor.binary_sensor_schema()
),
}
) )
async def to_code(config):
template_ = await cg.process_lambda(
config[CONF_LAMBDA],
[],
return_type=cg.std_vector.template(binary_sensor.BinarySensorPtr),
)
rhs = CustomBinarySensorConstructor(template_)
custom = cg.variable(config[CONF_ID], rhs)
for i, conf in enumerate(config[CONF_BINARY_SENSORS]):
rhs = custom.Pget_binary_sensor(i)
await binary_sensor.register_binary_sensor(rhs, conf)

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