1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-02 16:11:53 +00:00

Compare commits

...

149 Commits

Author SHA1 Message Date
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
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
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
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
Jesse Hills
ef87a6657a Merge pull request #7599 from esphome/bump-2024.10.0b2
2024.10.0b2
2024-10-14 10:57:15 +13:00
Clyde Stubbs
27e1233fc0 [CI] failures when installing using apt-get. (#7593) 2024-10-14 09:51:43 +13:00
Jesse Hills
d24ad2e0e7 Bump version to 2024.10.0b2 2024-10-14 09:31:16 +13:00
Niclas Larsson
dda27d9de4 Fix update sequence when update is set to false (#5225) (#7407) 2024-10-14 09:31:16 +13:00
Clyde Stubbs
f52136338d [touchscreen] Fix coordinates when using rotation (#7591) 2024-10-14 09:31:15 +13:00
RFDarter
bafb0ad688 [web_server] Event component grouping (#7586) 2024-10-14 09:31:15 +13:00
Samuel Sieb
b617b92758 fix uart settings check (#7573) 2024-10-14 09:31:15 +13:00
Jesse Hills
8a025a6617 Merge pull request #7572 from esphome/bump-2024.10.0b1
2024.10.0b1
2024-10-09 17:44:02 +13:00
Jesse Hills
4a9d3a3927 Bump version to 2024.10.0b1 2024-10-09 15:01:49 +13:00
dependabot[bot]
26694cb55e Bump actions/cache from 4.1.0 to 4.1.1 in /.github/actions/restore-python (#7571)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-09 14:51:43 +13:00
dependabot[bot]
94ad1237ce Bump actions/cache from 4.1.0 to 4.1.1 (#7570)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-09 14:51:31 +13:00
dependabot[bot]
69467ea6ff Bump actions/upload-artifact from 4.4.1 to 4.4.2 (#7569)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-09 14:51:23 +13:00
Clyde Stubbs
66f500e594 [template/binary_sensor] Implement condition: option as alternative to lambda. (#7561) 2024-10-09 14:49:33 +13:00
Clyde Stubbs
fc97a6d1e3 [lvgl] Fix text component (#7563) 2024-10-09 14:43:28 +13:00
Clyde Stubbs
1a567b6986 [cst816] Allow skipping i2c probe (#7557) 2024-10-09 14:41:58 +13:00
baldisos
9211aad524 Update radon_eye_listener.cpp for more possible variants (#7567) 2024-10-09 14:33:50 +13:00
dependabot[bot]
6139b933c5 Bump actions/cache from 4.0.2 to 4.1.0 (#7558)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-08 02:00:10 +00:00
dependabot[bot]
3804b3b759 Bump actions/cache from 4.0.2 to 4.1.0 in /.github/actions/restore-python (#7560)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-08 14:34:26 +13:00
dependabot[bot]
659239e8cd Bump actions/upload-artifact from 4.4.0 to 4.4.1 (#7559)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-08 14:34:15 +13:00
Clyde Stubbs
52e59d1dad [ili9xxx] Put display into sleep mode on shutdown. (#7555) 2024-10-08 14:28:59 +13:00
Clyde Stubbs
b8630363e0 [online_image] Bugfix: Use std::string instead of const char * (#7556) 2024-10-07 11:47:07 -05:00
esphomebot
1d91601094 Update webserver local assets to 20241007-025551 (#7553) 2024-10-07 03:22:17 +00:00
Curtis Malainey
ea23f49e90 nau7802: new component (#6291) 2024-10-07 16:08:56 +13:00
Ken Baker
b2bf2bc448 Add Initial NPI-19 pressure sensor support (#7181)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-10-07 15:59:13 +13:00
Ken Baker
5ad5ef5a42 Add Initial TE-M3200 pressure sensor support (#6862)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-10-07 15:58:28 +13:00
RFDarter
86a34f4b17 [web_server] v3 entity grouping (#6833)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-10-07 15:52:26 +13:00
Clyde Stubbs
6a2ed8241e [lvgl] Fix: allow full range of styles on dropdown list. (#7552) 2024-10-07 15:43:41 +13:00
YorkshireIoT
03a95ee05f Feature/add seeed grove gmxxx multichannel gas support (#4304)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-10-07 15:34:46 +13:00
Clyde Stubbs
81f6750211 [lvgl] Bugfixes #3 (#7472) 2024-10-07 13:27:08 +13:00
tomaszduda23
cbc03aae80 [code-quality] fix clang-tidy api (#7279) 2024-10-07 11:55:11 +13:00
tomaszduda23
390299894e [code-quality] fix clang-tidy md5 and hmac_md5 (#7325)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-10-07 11:53:49 +13:00
dependabot[bot]
fc7628cdea Bump docker/build-push-action from 6.7.0 to 6.9.0 in /.github/actions/build-image (#7511)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-07 10:34:38 +13:00
dependabot[bot]
fbd600f43f Bump pypa/gh-action-pypi-publish from 1.10.2 to 1.10.3 (#7541)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-07 10:34:17 +13:00
dependabot[bot]
239eadb895 Bump docker/setup-buildx-action from 3.6.1 to 3.7.1 in the docker-actions group across 1 directory (#7542)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-07 10:34:03 +13:00
Keith Burzinski
e87169805c [wifi] Replace `USE_ESP32_IGNORE_EFUSE_MAC_CRC with IDF's CONFIG_ESP_MAC_IGNORE_MAC_CRC_ERROR` (#7502) 2024-10-07 10:02:53 +13:00
Keith Burzinski
9f85d99a22 [audio_dac] [aic3204] Add new component + platform (#7505) 2024-10-07 09:59:42 +13:00
Keith Burzinski
56e305f986 [bedjet_codec] Remove `assert()` (#7543) 2024-10-07 09:01:43 +13:00
Keith Burzinski
1c0ee5ae6b [thermostat] Remove `assert()`s (#7544) 2024-10-07 09:01:11 +13:00
Keith Burzinski
949e61db8d [bang-bang] Remove `assert()` (#7533) 2024-10-07 09:00:09 +13:00
Tobias Hoff
e31a96bfe2 Allow use of all pulse count unit channels if needed. (#7550) 2024-10-07 08:53:57 +13:00
Samuel Sieb
6a8e88b1cc CSE7766 needs even parity (#7549) 2024-10-07 08:49:52 +13:00
guillempages
b3cff566eb [lvgl] Remap image to img in "set_style_*" (#7546) 2024-10-06 09:44:18 +11:00
guillempages
0a62106b7b [image] Use "puremagic" instead of "magic" python module (#7536) 2024-10-05 17:07:32 +10:00
Keith Burzinski
1cf4818640 [CI] Use a list when reading idedata for includes (#7535) 2024-10-04 21:07:49 +13:00
RFDarter
523eedbc51 [web_server] Expose detail=all on all components (#7531) 2024-10-03 13:34:12 +13:00
David Woodhouse
e57a1ff42d Fix parsing of µs time periods in config (#7495) 2024-10-03 07:54:12 +13:00
Jesse Hills
361b6ab961 [mics_4514] Move consts to consts.py (#7528) 2024-10-02 04:27:32 -05:00
Keith Burzinski
0d80286bb3 [esp32] Add `ignore_efuse_custom_mac` config var (#7527) 2024-10-02 03:27:46 -05:00
Keith Burzinski
d00e0eb2d6 [wifi] Fix error message when no custom MAC is set (#7515) 2024-10-02 02:33:35 +00:00
Keith Burzinski
215f26fbe4 [CI] Remove `sorted` from library include dirs (#7526) 2024-10-02 13:08:12 +13:00
Jesse Hills
68928aee7c Merge branch 'release' into dev 2024-10-01 16:42:26 +13:00
Jesse Hills
ffb0080fc1 Merge pull request #7516 from esphome/bump-2024.9.2
2024.9.2
2024-10-01 16:41:38 +13:00
Jesse Hills
f784e5c9f6 Bump version to 2024.9.2 2024-10-01 15:33:40 +13:00
Jesse Hills
748bc85bfe [rp2040] Always use maxgerhardt platform fork (#7514) 2024-10-01 15:33:40 +13:00
Nick Kinnan
050e2547ea Prevent rp2040 randomly breaking the build (#7507)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-10-01 15:33:40 +13:00
Jesse Hills
c2518cff89 [config_validation] Fix bug with extras on schemas (#7497) 2024-10-01 15:33:39 +13:00
Samuel Sieb
4332301dbb fix bl0906 reset energy action (#7488)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2024-10-01 15:33:39 +13:00
Jesse Hills
d5fa17c316 [rp2040] Always use maxgerhardt platform fork (#7514) 2024-10-01 13:37:08 +13:00
@RubenKelevra
c1a28ba5e2 tcs34725: Remove IR compensation and improve illuminance and color temperature handling in extreme conditions (#7492) 2024-10-01 11:03:42 +13:00
@RubenKelevra
01e03b76a7 tcs34725: optimize fetch time with burst read for RGB and clear values (#7494)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-10-01 11:00:40 +13:00
dependabot[bot]
507d27e84a Bump pypa/gh-action-pypi-publish from 1.10.1 to 1.10.2 (#7487)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-30 21:48:36 +02:00
Darren Griffin
01f5ca26dc Add OHF logo to README (#7509) 2024-09-30 18:49:13 +02:00
Nick Kinnan
20cb2e147f Make time dependency optional (#7425)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-09-30 16:27:22 +13:00
Nick Kinnan
49a3d385eb Prevent rp2040 randomly breaking the build (#7507)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-09-30 14:59:12 +13:00
zry98
023cb4937e Add support for Sharp GP2Y1010AU0F PM2.5 sensor (#6007)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-09-30 14:22:27 +13:00
Keith Burzinski
529ff4bd52 [wifi] Use custom MAC address if programmed (#7498) 2024-09-27 10:24:18 +12:00
victorclaessen
3df25a183a Add clean_session as configurable option to the MQTT component (#7501) 2024-09-27 09:57:51 +12:00
Clyde Stubbs
c55b4f5e1b [ch422g] Add support for pins 8-11; make input work. (#7467) 2024-09-27 09:51:08 +12:00
Kevin Ahrendt
3b1b1071f1 [core] add ring buffer destructor (#7500) 2024-09-27 09:25:20 +12:00
Jesse Hills
21fbbc5fb9 [config_validation] Fix bug with extras on schemas (#7497) 2024-09-26 00:34:27 +00:00
@RubenKelevra
b61577b68b tcs34725: Add check for Division by Zero (#7485) 2024-09-25 14:28:22 +12:00
@RubenKelevra
fa9df32979 tcs34725: fix color/clear channel percentage calculations on long exposures (#7493) 2024-09-25 14:27:14 +12:00
Jonathan Swoboda
fcce70d416 Add remote transmitter triggers (#7483)
Co-authored-by: Jonathan Swoboda <jonathan.swoboda>
2024-09-25 14:09:24 +12:00
Samuel Sieb
8e54a622d3 fix bl0906 reset energy action (#7488)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2024-09-25 12:50:44 +12:00
David Sichau
294fe8d970 Support inkplate 5 and 5 V2 (#7448)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-09-25 12:50:01 +12:00
Nick Kinnan
cc53eb42b2 Add CSE7766 reactive power (#7301) 2024-09-23 22:53:13 -05:00
dependabot[bot]
4ece4a389e Bump peter-evans/create-pull-request from 7.0.3 to 7.0.5 (#7469)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-23 21:31:38 +02:00
Keith Burzinski
2ff863deb3 [micro_wake_word] Workaround for failing IDF 5+ tests (#7484) 2024-09-23 18:35:26 +12:00
Keith Burzinski
402a6a9edb [esp32_improv] Add triggers for various states (#7461)
Co-authored-by: NP v/d Spek <github_mail@lumensoft.nl>
2024-09-22 23:54:31 -05:00
Jesse Hills
af612add24 Merge branch 'release' into dev 2024-09-23 12:02:32 +12:00
Jesse Hills
1f8037d5bc Merge pull request #7482 from esphome/bump-2024.9.1
2024.9.1
2024-09-23 12:01:43 +12:00
Jesse Hills
f314ad8a5b Bump version to 2024.9.1 2024-09-23 10:40:47 +12:00
Michael Hansen
66f9597d9e Copy active wake words to message (#7481) 2024-09-23 10:40:47 +12:00
Tarik2142
c287673947 add "fan_mode" and "swing_mode" to REST API (#7476) 2024-09-23 09:35:57 +12:00
Michael Hansen
5f7bde2a2c Copy active wake words to message (#7481) 2024-09-23 07:44:53 +12:00
Clyde Stubbs
8e5d7337c8 [st7701s] Fix initialisation race (#7462) 2024-09-19 16:18:51 +12:00
Michał Obrembski
fb7e7eb80b Add tca9555 GPIO driver (#7146)
Co-authored-by: Michal Obrembski <michal@obrembski.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-09-19 16:17:22 +12:00
Clyde Stubbs
6d24e9ebb5 [lvgl] Enhancements (#7453) 2024-09-19 16:17:04 +12:00
Pietro
ddde64a48d Added i2s_comm_fmt parameter to i2s speaker component (#7449)
Co-authored-by: PxPert <pxpert@pxpert.cloud>
2024-09-19 16:16:39 +12:00
Pavlo Dudnytskyi
446f7e0a7e Haier climate integration update (#7416)
Co-authored-by: Pavlo Dudnytskyi <pdudnytskyi@astrata.eu>
2024-09-19 16:09:27 +12:00
Edward Firmo
d0dc275e30 [nextion] Optionally skip connection handshake (#6905)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-09-19 16:08:15 +12:00
Andrey Bodrov
9699719305 openeth ethernet / qemu support (#7020) 2024-09-19 16:07:39 +12:00
Jesse Hills
1596a85e4f Merge branch 'release' into dev 2024-09-19 07:57:38 +12:00
Jesse Hills
6fde5e0fed Merge pull request #7466 from esphome/bump-2024.9.0
2024.9.0
2024-09-19 07:56:46 +12:00
Jesse Hills
34229af38a Bump version to 2024.9.0 2024-09-18 16:56:07 +12:00
Jesse Hills
373cb44078 Merge branch 'beta' into dev 2024-09-18 16:05:49 +12:00
Jesse Hills
3744c7876b Merge pull request #7465 from esphome/bump-2024.9.0b4
2024.9.0b4
2024-09-18 14:33:58 +12:00
Jesse Hills
a930b377b0 Bump version to 2024.9.0b4 2024-09-18 12:57:27 +12:00
Michael Hansen
571c0eb827 Add voice assistant methods for configuration (#7459)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-09-18 12:57:27 +12:00
Jesse Hills
749f664330 Dont replace project name spaces with underlines (#7455) 2024-09-18 12:57:27 +12:00
Michael Hansen
f87d9be60d Add voice assistant configuration messages (#7445) 2024-09-18 12:57:27 +12:00
Michael Hansen
5a3e1d5792 Add voice assistant methods for configuration (#7459)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-09-18 11:38:39 +12:00
dependabot[bot]
cb86749545 Bump peter-evans/create-pull-request from 7.0.2 to 7.0.3 (#7457)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-17 13:47:16 +12:00
Jesse Hills
857a3dcf72 Dont replace project name spaces with underlines (#7455) 2024-09-16 01:03:51 -05:00
Jesse Hills
435789a960 Bump pylint from 3.1.0 to 3.2.7 (#7438) 2024-09-16 17:21:42 +12:00
Jesse Hills
18b3fbbf6d Merge branch 'beta' into dev 2024-09-16 16:36:03 +12:00
Jesse Hills
8bd182d96c Merge pull request #7454 from esphome/bump-2024.9.0b3
2024.9.0b3
2024-09-16 16:35:26 +12:00
Jesse Hills
73e469ae52 [modbus_controller] Fix linting and formatting issues (#7441) 2024-09-16 13:43:45 +12:00
Michael Hansen
3835ad8c1f Add voice assistant configuration messages (#7445) 2024-09-16 13:40:45 +12:00
Jesse Hills
a63b9a9e0c Bump version to 2024.9.0b3 2024-09-16 13:17:06 +12:00
Jesse Hills
6483ceb6eb [docker] Bump git from 1:2.39.2-1.1 to 1:2.39.5-0+deb12u1 (#7452) 2024-09-16 13:17:06 +12:00
Michael Hansen
e7fe9b374f Add sample_bytes to media player supported format (#7451) 2024-09-16 13:17:06 +12:00
Jesse Hills
9014fa4bf9 Merge branch 'beta' into dev 2024-09-16 12:32:56 +12:00
Jesse Hills
bfde7fd9d7 [docker] Bump git from 1:2.39.2-1.1 to 1:2.39.5-0+deb12u1 (#7452) 2024-09-16 12:32:39 +12:00
Jesse Hills
3e7161ad41 Merge pull request #7450 from esphome/bump-2024.9.0b2
2024.9.0b2
2024-09-16 12:32:18 +12:00
Michael Hansen
857d79dc71 Add sample_bytes to media player supported format (#7451) 2024-09-15 23:46:54 +00:00
Clyde Stubbs
f652cd3851 [st7701s] Make use of IDF5.x to speed up display operations (#7447) 2024-09-16 10:42:45 +12:00
Jesse Hills
5d8fb7cdf4 Bump version to 2024.9.0b2 2024-09-16 10:01:34 +12:00
Tomer
80e3de94d3 Improve manufacturer data tracing to identify BLE devices a bit easie… (#7332) 2024-09-16 10:01:34 +12:00
Jesse Hills
7f00b5eb65 [voice-assistant] Dont error on `no_wake_word` timeout error with streaming wake word (#7435) 2024-09-16 10:01:34 +12:00
Oleg Tarasov
de19d25a3c Add OpenTherm component (part 1: communication layer and hub) (#6645)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-09-16 09:59:10 +12:00
Jesse Hills
cf4bfcdce8 [thermostat] Fix linting and formatting issues (#7442) 2024-09-13 05:03:25 -05:00
Jesse Hills
c702a3f3ee [animation] Fix linting and formatting issues (#7439) 2024-09-13 16:16:57 +12:00
Jesse Hills
e4c90489f7 [image] Fix linting and formatting issues (#7440) 2024-09-13 16:16:24 +12:00
Jesse Hills
08c0715a30 [tm1638] Fix linting and formatting issues (#7443) 2024-09-13 16:15:00 +12:00
J. Nick Koston
0df44b5df1 Bump recommended ESP-IDF to 4.4.8 (#7349) 2024-09-13 14:06:50 +12:00
dependabot[bot]
e315b4d939 Bump peter-evans/create-pull-request from 7.0.0 to 7.0.2 (#7437)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-13 13:22:28 +12:00
Tomer
78d0e0baae Improve manufacturer data tracing to identify BLE devices a bit easie… (#7332) 2024-09-13 12:56:04 +12:00
300 changed files with 10943 additions and 4908 deletions

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.7.0 uses: docker/build-push-action@v6.9.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.7.0 uses: docker/build-push-action@v6.9.0
env: env:
DOCKER_BUILD_SUMMARY: false DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false DOCKER_BUILD_RECORD_UPLOAD: false

View File

@@ -22,7 +22,7 @@ runs:
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.0.2 uses: actions/cache/restore@v4.1.1
with: with:
path: venv path: venv
# yamllint disable-line rule:line-length # yamllint disable-line rule:line-length

View File

@@ -46,7 +46,7 @@ jobs:
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.6.1 uses: docker/setup-buildx-action@v3.7.1
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3.2.0 uses: docker/setup-qemu-action@v3.2.0

View File

@@ -46,7 +46,7 @@ jobs:
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.0.2 uses: actions/cache@v4.1.1
with: with:
path: venv path: venv
# yamllint disable-line rule:line-length # yamllint disable-line rule:line-length
@@ -302,20 +302,22 @@ jobs:
- name: Cache platformio - name: Cache platformio
if: github.ref == 'refs/heads/dev' if: github.ref == 'refs/heads/dev'
uses: actions/cache@v4.0.2 uses: actions/cache@v4.1.1
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.0.2 uses: actions/cache/restore@v4.1.1
with: with:
path: ~/.platformio path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }} key: platformio-${{ matrix.pio_cache_key }}
- name: Install clang-tidy - name: Install clang-tidy
run: sudo apt-get install clang-tidy-14 run: |
sudo apt-get update
sudo apt-get install clang-tidy-14
- name: Register problem matchers - name: Register problem matchers
run: | run: |
@@ -397,7 +399,9 @@ jobs:
file: ${{ fromJson(needs.list-components.outputs.components) }} file: ${{ fromJson(needs.list-components.outputs.components) }}
steps: steps:
- name: Install dependencies - name: Install dependencies
run: sudo apt-get install libsdl2-dev run: |
sudo apt-get update
sudo apt-get install libsdl2-dev
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.7 uses: actions/checkout@v4.1.7
@@ -451,7 +455,9 @@ jobs:
run: echo ${{ matrix.components }} run: echo ${{ matrix.components }}
- name: Install dependencies - name: Install dependencies
run: sudo apt-get install libsdl2-dev run: |
sudo apt-get update
sudo apt-get install libsdl2-dev
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.7 uses: actions/checkout@v4.1.7

View File

@@ -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.1 uses: pypa/gh-action-pypi-publish@v1.10.3
deploy-docker: deploy-docker:
name: Build ESPHome ${{ matrix.platform }} name: Build ESPHome ${{ matrix.platform }}
@@ -90,7 +90,7 @@ jobs:
python-version: "3.9" python-version: "3.9"
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.6.1 uses: docker/setup-buildx-action@v3.7.1
- 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.2.0
@@ -141,7 +141,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.0 uses: actions/upload-artifact@v4.4.2
with: with:
name: digests-${{ steps.sanitize.outputs.name }} name: digests-${{ steps.sanitize.outputs.name }}
path: /tmp/digests path: /tmp/digests
@@ -184,7 +184,7 @@ jobs:
merge-multiple: true merge-multiple: true
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.6.1 uses: docker/setup-buildx-action@v3.7.1
- name: Log in to docker hub - name: Log in to docker hub
if: matrix.registry == 'dockerhub' if: matrix.registry == 'dockerhub'

View File

@@ -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.0 uses: peter-evans/create-pull-request@v7.0.5
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>

View File

@@ -24,6 +24,7 @@ esphome/components/ade7953_i2c/* @angelnu
esphome/components/ade7953_spi/* @angelnu esphome/components/ade7953_spi/* @angelnu
esphome/components/ads1118/* @solomondg1 esphome/components/ads1118/* @solomondg1
esphome/components/ags10/* @mak-42 esphome/components/ags10/* @mak-42
esphome/components/aic3204/* @kbx81
esphome/components/airthings_ble/* @jeromelaban esphome/components/airthings_ble/* @jeromelaban
esphome/components/airthings_wave_base/* @jeromelaban @kpfleming @ncareau esphome/components/airthings_wave_base/* @jeromelaban @kpfleming @ncareau
esphome/components/airthings_wave_mini/* @ncareau esphome/components/airthings_wave_mini/* @ncareau
@@ -47,6 +48,7 @@ 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_dac/* @kbx81
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
@@ -86,7 +88,7 @@ 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/* @jesterret esphome/components/ch422g/* @clydebarrow @jesterret
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
@@ -152,6 +154,7 @@ esphome/components/ft63x6/* @gpambrozio
esphome/components/gcja5/* @gcormier esphome/components/gcja5/* @gcormier
esphome/components/gdk101/* @Szewcson esphome/components/gdk101/* @Szewcson
esphome/components/globals/* @esphome/core esphome/components/globals/* @esphome/core
esphome/components/gp2y1010au0f/* @zry98
esphome/components/gp8403/* @jesserockz esphome/components/gp8403/* @jesserockz
esphome/components/gpio/* @esphome/core esphome/components/gpio/* @esphome/core
esphome/components/gpio/one_wire/* @ssieb esphome/components/gpio/one_wire/* @ssieb
@@ -159,6 +162,7 @@ esphome/components/gps/* @coogle
esphome/components/graph/* @synco esphome/components/graph/* @synco
esphome/components/graphical_display_menu/* @MrMDavidson esphome/components/graphical_display_menu/* @MrMDavidson
esphome/components/gree/* @orestismers esphome/components/gree/* @orestismers
esphome/components/grove_gas_mc_v2/* @YorkshireIoT
esphome/components/grove_tb6612fng/* @max246 esphome/components/grove_tb6612fng/* @max246
esphome/components/growatt_solar/* @leeuwte esphome/components/growatt_solar/* @leeuwte
esphome/components/gt911/* @clydebarrow @jesserockz esphome/components/gt911/* @clydebarrow @jesserockz
@@ -166,6 +170,7 @@ esphome/components/haier/* @paveldn
esphome/components/haier/binary_sensor/* @paveldn esphome/components/haier/binary_sensor/* @paveldn
esphome/components/haier/button/* @paveldn esphome/components/haier/button/* @paveldn
esphome/components/haier/sensor/* @paveldn esphome/components/haier/sensor/* @paveldn
esphome/components/haier/switch/* @paveldn
esphome/components/haier/text_sensor/* @paveldn 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
@@ -278,6 +283,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/nau7802/* @cujomalainey
esphome/components/network/* @esphome/core esphome/components/network/* @esphome/core
esphome/components/nextion/* @edwardtfn @senexcrenshaw esphome/components/nextion/* @edwardtfn @senexcrenshaw
esphome/components/nextion/binary_sensor/* @senexcrenshaw esphome/components/nextion/binary_sensor/* @senexcrenshaw
@@ -286,9 +292,11 @@ esphome/components/nextion/switch/* @senexcrenshaw
esphome/components/nextion/text_sensor/* @senexcrenshaw esphome/components/nextion/text_sensor/* @senexcrenshaw
esphome/components/nfc/* @jesserockz @kbx81 esphome/components/nfc/* @jesserockz @kbx81
esphome/components/noblex/* @AGalfra esphome/components/noblex/* @AGalfra
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/* @guillempages
esphome/components/opentherm/* @olegtarasov
esphome/components/ota/* @esphome/core esphome/components/ota/* @esphome/core
esphome/components/output/* @esphome/core esphome/components/output/* @esphome/core
esphome/components/pca6416a/* @Mat931 esphome/components/pca6416a/* @Mat931
@@ -396,9 +404,11 @@ esphome/components/sun_gtil2/* @Mat931
esphome/components/switch/* @esphome/core esphome/components/switch/* @esphome/core
esphome/components/t6615/* @tylermenezes esphome/components/t6615/* @tylermenezes
esphome/components/tca9548a/* @andreashergert1984 esphome/components/tca9548a/* @andreashergert1984
esphome/components/tca9555/* @mobrembski
esphome/components/tcl112/* @glmnet esphome/components/tcl112/* @glmnet
esphome/components/tee501/* @Stock-M esphome/components/tee501/* @Stock-M
esphome/components/teleinfo/* @0hax esphome/components/teleinfo/* @0hax
esphome/components/tem3200/* @bakerkj
esphome/components/template/alarm_control_panel/* @grahambrown11 @hwstar esphome/components/template/alarm_control_panel/* @grahambrown11 @hwstar
esphome/components/template/datetime/* @rfdarter esphome/components/template/datetime/* @rfdarter
esphome/components/template/event/* @nohat esphome/components/template/event/* @nohat

View File

@@ -7,3 +7,5 @@
For issues, please go to [the issue tracker](https://github.com/esphome/issues/issues). For issues, please go to [the issue tracker](https://github.com/esphome/issues/issues).
For feature requests, please see [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/)

View File

@@ -33,7 +33,7 @@ RUN \
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 \
git=1:2.39.2-1.1 \ git=1:2.39.5-0+deb12u1 \
curl=7.88.1-10+deb12u7 \ curl=7.88.1-10+deb12u7 \
openssh-client=1:9.2p1-2+deb12u3 \ openssh-client=1:9.2p1-2+deb12u3 \
python3-cffi=1.15.1-5 \ python3-cffi=1.15.1-5 \

View File

View File

@@ -0,0 +1,173 @@
#include "aic3204.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace aic3204 {
static const char *const TAG = "aic3204";
#define ERROR_CHECK(err, msg) \
if (!(err)) { \
ESP_LOGE(TAG, msg); \
this->mark_failed(); \
return; \
}
void AIC3204::setup() {
ESP_LOGCONFIG(TAG, "Setting up AIC3204...");
// Set register page to 0
ERROR_CHECK(this->write_byte(AIC3204_PAGE_CTRL, 0x00), "Set page 0 failed");
// Initiate SW reset (PLL is powered off as part of reset)
ERROR_CHECK(this->write_byte(AIC3204_SW_RST, 0x01), "Software reset failed");
// *** Program clock settings ***
// Default is CODEC_CLKIN is from MCLK pin. Don't need to change this.
// MDAC*NDAC*FOSR*48Khz = mClk (24.576 MHz when the XMOS is expecting 48kHz audio)
// (See page 51 of https://www.ti.com/lit/ml/slaa557/slaa557.pdf)
// We do need MDAC*DOSR/32 >= the resource compute level for the processing block
// So here 2*128/32 = 8, which is equal to processing block 1 's resource compute
// See page 5 of https://www.ti.com/lit/an/slaa404c/slaa404c.pdf for the workflow
// for determining these settings.
// Power up NDAC and set to 2
ERROR_CHECK(this->write_byte(AIC3204_NDAC, 0x82), "Set NDAC failed");
// Power up MDAC and set to 2
ERROR_CHECK(this->write_byte(AIC3204_MDAC, 0x82), "Set MDAC failed");
// Program DOSR = 128
ERROR_CHECK(this->write_byte(AIC3204_DOSR, 0x80), "Set DOSR failed");
// Set Audio Interface Config: I2S, 32 bits, DOUT always driving
ERROR_CHECK(this->write_byte(AIC3204_CODEC_IF, 0x30), "Set CODEC_IF failed");
// For I2S Firmware only, set SCLK/MFP3 pin as Audio Data In
ERROR_CHECK(this->write_byte(AIC3204_SCLK_MFP3, 0x02), "Set SCLK/MFP3 failed");
ERROR_CHECK(this->write_byte(AIC3204_AUDIO_IF_4, 0x01), "Set AUDIO_IF_4 failed");
ERROR_CHECK(this->write_byte(AIC3204_AUDIO_IF_5, 0x01), "Set AUDIO_IF_5 failed");
// Program the DAC processing block to be used - PRB_P1
ERROR_CHECK(this->write_byte(AIC3204_DAC_SIG_PROC, 0x01), "Set DAC_SIG_PROC failed");
// *** Select Page 1 ***
ERROR_CHECK(this->write_byte(AIC3204_PAGE_CTRL, 0x01), "Set page 1 failed");
// Enable the internal AVDD_LDO:
ERROR_CHECK(this->write_byte(AIC3204_LDO_CTRL, 0x09), "Set LDO_CTRL failed");
// *** Program Analog Blocks ***
// Disable Internal Crude AVdd in presence of external AVdd supply or before powering up internal AVdd LDO
ERROR_CHECK(this->write_byte(AIC3204_PWR_CFG, 0x08), "Set PWR_CFG failed");
// Enable Master Analog Power Control
ERROR_CHECK(this->write_byte(AIC3204_LDO_CTRL, 0x01), "Set LDO_CTRL failed");
// Page 125: Common mode control register, set d6 to 1 to make the full chip common mode = 0.75 v
// We are using the internal AVdd regulator with a nominal output of 1.72 V (see LDO_CTRL_REGISTER on page 123)
// Page 86 says to only set the common mode voltage to 0.9 v if AVdd >= 1.8... but it isn't on our hardware
// We also adjust the HPL and HPR gains to -2dB gian later in this config flow compensate (see page 47)
// (All pages refer to the TLV320AIC3204 Application Reference Guide)
ERROR_CHECK(this->write_byte(AIC3204_CM_CTRL, 0x40), "Set CM_CTRL failed");
// *** Set PowerTune Modes ***
// Set the Left & Right DAC PowerTune mode to PTM_P3/4. Use Class-AB driver.
ERROR_CHECK(this->write_byte(AIC3204_PLAY_CFG1, 0x00), "Set PLAY_CFG1 failed");
ERROR_CHECK(this->write_byte(AIC3204_PLAY_CFG2, 0x00), "Set PLAY_CFG2 failed");
// Set the REF charging time to 40ms
ERROR_CHECK(this->write_byte(AIC3204_REF_STARTUP, 0x01), "Set REF_STARTUP failed");
// HP soft stepping settings for optimal pop performance at power up
// Rpop used is 6k with N = 6 and soft step = 20usec. This should work with 47uF coupling
// capacitor. Can try N=5,6 or 7 time constants as well. Trade-off delay vs “pop” sound.
ERROR_CHECK(this->write_byte(AIC3204_HP_START, 0x25), "Set HP_START failed");
// Route Left DAC to HPL
ERROR_CHECK(this->write_byte(AIC3204_HPL_ROUTE, 0x08), "Set HPL_ROUTE failed");
// Route Right DAC to HPR
ERROR_CHECK(this->write_byte(AIC3204_HPR_ROUTE, 0x08), "Set HPR_ROUTE failed");
// Route Left DAC to LOL
ERROR_CHECK(this->write_byte(AIC3204_LOL_ROUTE, 0x08), "Set LOL_ROUTE failed");
// Route Right DAC to LOR
ERROR_CHECK(this->write_byte(AIC3204_LOR_ROUTE, 0x08), "Set LOR_ROUTE failed");
// Unmute HPL and set gain to -2dB (see comment before configuring the AIC3204_CM_CTRL register)
ERROR_CHECK(this->write_byte(AIC3204_HPL_GAIN, 0x3e), "Set HPL_GAIN failed");
// Unmute HPR and set gain to -2dB (see comment before configuring the AIC3204_CM_CTRL register)
ERROR_CHECK(this->write_byte(AIC3204_HPR_GAIN, 0x3e), "Set HPR_GAIN failed");
// Unmute LOL and set gain to 0dB
ERROR_CHECK(this->write_byte(AIC3204_LOL_DRV_GAIN, 0x00), "Set LOL_DRV_GAIN failed");
// Unmute LOR and set gain to 0dB
ERROR_CHECK(this->write_byte(AIC3204_LOR_DRV_GAIN, 0x00), "Set LOR_DRV_GAIN failed");
// Power up HPL and HPR, LOL and LOR drivers
ERROR_CHECK(this->write_byte(AIC3204_OP_PWR_CTRL, 0x3C), "Set OP_PWR_CTRL failed");
// Wait for 2.5 sec for soft stepping to take effect before attempting power-up
this->set_timeout(2500, [this]() {
// *** Power Up DAC ***
// Select Page 0
ERROR_CHECK(this->write_byte(AIC3204_PAGE_CTRL, 0x00), "Set PAGE_CTRL failed");
// Power up the Left and Right DAC Channels. Route Left data to Left DAC and Right data to Right DAC.
// DAC Vol control soft step 1 step per DAC word clock.
ERROR_CHECK(this->write_byte(AIC3204_DAC_CH_SET1, 0xd4), "Set DAC_CH_SET1 failed");
// Set left and right DAC digital volume control
ERROR_CHECK(this->write_volume_(), "Set volume failed");
// Unmute left and right channels
ERROR_CHECK(this->write_mute_(), "Set mute failed");
});
}
void AIC3204::dump_config() {
ESP_LOGCONFIG(TAG, "AIC3204:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with AIC3204 failed");
}
}
bool AIC3204::set_mute_off() {
this->is_muted_ = false;
return this->write_mute_();
}
bool AIC3204::set_mute_on() {
this->is_muted_ = true;
return this->write_mute_();
}
bool AIC3204::set_auto_mute_mode(uint8_t auto_mute_mode) {
this->auto_mute_mode_ = auto_mute_mode & 0x07;
ESP_LOGVV(TAG, "Setting auto_mute_mode to 0x%.2x", this->auto_mute_mode_);
return this->write_mute_();
}
bool AIC3204::set_volume(float volume) {
this->volume_ = clamp<float>(volume, 0.0, 1.0);
return this->write_volume_();
}
bool AIC3204::is_muted() { return this->is_muted_; }
float AIC3204::volume() { return this->volume_; }
bool AIC3204::write_mute_() {
uint8_t mute_mode_byte = this->auto_mute_mode_ << 4; // auto-mute control is bits 4-6
mute_mode_byte |= this->is_muted_ ? 0x0c : 0x00; // mute bits are 2-3
if (!this->write_byte(AIC3204_PAGE_CTRL, 0x00) || !this->write_byte(AIC3204_DAC_CH_SET2, mute_mode_byte)) {
ESP_LOGE(TAG, "Writing mute modes failed");
return false;
}
return true;
}
bool AIC3204::write_volume_() {
const int8_t dvc_min_byte = -127;
const int8_t dvc_max_byte = 48;
int8_t volume_byte = dvc_min_byte + (this->volume_ * (dvc_max_byte - dvc_min_byte));
volume_byte = clamp<int8_t>(volume_byte, dvc_min_byte, dvc_max_byte);
ESP_LOGVV(TAG, "Setting volume to 0x%.2x", volume_byte & 0xFF);
if ((!this->write_byte(AIC3204_PAGE_CTRL, 0x00)) || (!this->write_byte(AIC3204_DACL_VOL_D, volume_byte)) ||
(!this->write_byte(AIC3204_DACR_VOL_D, volume_byte))) {
ESP_LOGE(TAG, "Writing volume failed");
return false;
}
return true;
}
} // namespace aic3204
} // namespace esphome

View File

@@ -0,0 +1,88 @@
#pragma once
#include "esphome/components/audio_dac/audio_dac.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace aic3204 {
// TLV320AIC3204 Register Addresses
// Page 0
static const uint8_t AIC3204_PAGE_CTRL = 0x00; // Register 0 - Page Control
static const uint8_t AIC3204_SW_RST = 0x01; // Register 1 - Software Reset
static const uint8_t AIC3204_CLK_PLL1 = 0x04; // Register 4 - Clock Setting Register 1, Multiplexers
static const uint8_t AIC3204_CLK_PLL2 = 0x05; // Register 5 - Clock Setting Register 2, P and R values
static const uint8_t AIC3204_CLK_PLL3 = 0x06; // Register 6 - Clock Setting Register 3, J values
static const uint8_t AIC3204_NDAC = 0x0B; // Register 11 - NDAC Divider Value
static const uint8_t AIC3204_MDAC = 0x0C; // Register 12 - MDAC Divider Value
static const uint8_t AIC3204_DOSR = 0x0E; // Register 14 - DOSR Divider Value (LS Byte)
static const uint8_t AIC3204_NADC = 0x12; // Register 18 - NADC Divider Value
static const uint8_t AIC3204_MADC = 0x13; // Register 19 - MADC Divider Value
static const uint8_t AIC3204_AOSR = 0x14; // Register 20 - AOSR Divider Value
static const uint8_t AIC3204_CODEC_IF = 0x1B; // Register 27 - CODEC Interface Control
static const uint8_t AIC3204_AUDIO_IF_4 = 0x1F; // Register 31 - Audio Interface Setting Register 4
static const uint8_t AIC3204_AUDIO_IF_5 = 0x20; // Register 32 - Audio Interface Setting Register 5
static const uint8_t AIC3204_SCLK_MFP3 = 0x38; // Register 56 - SCLK/MFP3 Function Control
static const uint8_t AIC3204_DAC_SIG_PROC = 0x3C; // Register 60 - DAC Sig Processing Block Control
static const uint8_t AIC3204_ADC_SIG_PROC = 0x3D; // Register 61 - ADC Sig Processing Block Control
static const uint8_t AIC3204_DAC_CH_SET1 = 0x3F; // Register 63 - DAC Channel Setup 1
static const uint8_t AIC3204_DAC_CH_SET2 = 0x40; // Register 64 - DAC Channel Setup 2
static const uint8_t AIC3204_DACL_VOL_D = 0x41; // Register 65 - DAC Left Digital Vol Control
static const uint8_t AIC3204_DACR_VOL_D = 0x42; // Register 66 - DAC Right Digital Vol Control
static const uint8_t AIC3204_DRC_ENABLE = 0x44;
static const uint8_t AIC3204_ADC_CH_SET = 0x51; // Register 81 - ADC Channel Setup
static const uint8_t AIC3204_ADC_FGA_MUTE = 0x52; // Register 82 - ADC Fine Gain Adjust/Mute
// Page 1
static const uint8_t AIC3204_PWR_CFG = 0x01; // Register 1 - Power Config
static const uint8_t AIC3204_LDO_CTRL = 0x02; // Register 2 - LDO Control
static const uint8_t AIC3204_PLAY_CFG1 = 0x03; // Register 3 - Playback Config 1
static const uint8_t AIC3204_PLAY_CFG2 = 0x04; // Register 4 - Playback Config 2
static const uint8_t AIC3204_OP_PWR_CTRL = 0x09; // Register 9 - Output Driver Power Control
static const uint8_t AIC3204_CM_CTRL = 0x0A; // Register 10 - Common Mode Control
static const uint8_t AIC3204_HPL_ROUTE = 0x0C; // Register 12 - HPL Routing Select
static const uint8_t AIC3204_HPR_ROUTE = 0x0D; // Register 13 - HPR Routing Select
static const uint8_t AIC3204_LOL_ROUTE = 0x0E; // Register 14 - LOL Routing Selection
static const uint8_t AIC3204_LOR_ROUTE = 0x0F; // Register 15 - LOR Routing Selection
static const uint8_t AIC3204_HPL_GAIN = 0x10; // Register 16 - HPL Driver Gain
static const uint8_t AIC3204_HPR_GAIN = 0x11; // Register 17 - HPR Driver Gain
static const uint8_t AIC3204_LOL_DRV_GAIN = 0x12; // Register 18 - LOL Driver Gain Setting
static const uint8_t AIC3204_LOR_DRV_GAIN = 0x13; // Register 19 - LOR Driver Gain Setting
static const uint8_t AIC3204_HP_START = 0x14; // Register 20 - Headphone Driver Startup
static const uint8_t AIC3204_LPGA_P_ROUTE = 0x34; // Register 52 - Left PGA Positive Input Route
static const uint8_t AIC3204_LPGA_N_ROUTE = 0x36; // Register 54 - Left PGA Negative Input Route
static const uint8_t AIC3204_RPGA_P_ROUTE = 0x37; // Register 55 - Right PGA Positive Input Route
static const uint8_t AIC3204_RPGA_N_ROUTE = 0x39; // Register 57 - Right PGA Negative Input Route
static const uint8_t AIC3204_LPGA_VOL = 0x3B; // Register 59 - Left PGA Volume
static const uint8_t AIC3204_RPGA_VOL = 0x3C; // Register 60 - Right PGA Volume
static const uint8_t AIC3204_ADC_PTM = 0x3D; // Register 61 - ADC Power Tune Config
static const uint8_t AIC3204_AN_IN_CHRG = 0x47; // Register 71 - Analog Input Quick Charging Config
static const uint8_t AIC3204_REF_STARTUP = 0x7B; // Register 123 - Reference Power Up Config
class AIC3204 : public audio_dac::AudioDac, public Component, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
bool set_mute_off() override;
bool set_mute_on() override;
bool set_auto_mute_mode(uint8_t auto_mute_mode);
bool set_volume(float volume) override;
bool is_muted() override;
float volume() override;
protected:
bool write_mute_();
bool write_volume_();
uint8_t auto_mute_mode_{0};
float volume_{0};
};
} // namespace aic3204
} // namespace esphome

View File

@@ -0,0 +1,52 @@
from esphome import automation
import esphome.codegen as cg
from esphome.components import i2c
from esphome.components.audio_dac import AudioDac
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_MODE
CODEOWNERS = ["@kbx81"]
DEPENDENCIES = ["i2c"]
aic3204_ns = cg.esphome_ns.namespace("aic3204")
AIC3204 = aic3204_ns.class_("AIC3204", AudioDac, cg.Component, i2c.I2CDevice)
SetAutoMuteAction = aic3204_ns.class_("SetAutoMuteAction", automation.Action)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(AIC3204),
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(i2c.i2c_device_schema(0x18))
)
SET_AUTO_MUTE_ACTION_SCHEMA = cv.maybe_simple_value(
{
cv.GenerateID(): cv.use_id(AIC3204),
cv.Required(CONF_MODE): cv.templatable(cv.int_range(max=7, min=0)),
},
key=CONF_MODE,
)
@automation.register_action(
"aic3204.set_auto_mute_mode", SetAutoMuteAction, SET_AUTO_MUTE_ACTION_SCHEMA
)
async def aic3204_set_volume_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_MODE), args, int)
cg.add(var.set_auto_mute_mode(template_))
return var
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)

View File

@@ -0,0 +1,23 @@
#pragma once
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "aic3204.h"
namespace esphome {
namespace aic3204 {
template<typename... Ts> class SetAutoMuteAction : public Action<Ts...> {
public:
explicit SetAutoMuteAction(AIC3204 *aic3204) : aic3204_(aic3204) {}
TEMPLATABLE_VALUE(uint8_t, auto_mute_mode)
void play(Ts... x) override { this->aic3204_->set_auto_mute_mode(this->auto_mute_mode_.value(x...)); }
protected:
AIC3204 *aic3204_;
};
} // namespace aic3204
} // namespace esphome

View File

@@ -9,7 +9,7 @@ from esphome.const import (
CONF_MQTT_ID, CONF_MQTT_ID,
CONF_ON_STATE, CONF_ON_STATE,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity from esphome.cpp_helpers import setup_entity
@@ -195,9 +195,8 @@ async def setup_alarm_control_panel_core_(var, config):
for conf in config.get(CONF_ON_READY, []): for conf in config.get(CONF_ON_READY, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf) await automation.build_automation(trigger, [], conf)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: if web_server_config := config.get(CONF_WEB_SERVER):
web_server_ = await cg.get_variable(webserver_id) await web_server.add_entity_config(var, web_server_config)
web_server.add_entity_to_sorting_list(web_server_, var, config)
if mqtt_id := config.get(CONF_MQTT_ID): if mqtt_id := config.get(CONF_MQTT_ID):
mqtt_ = cg.new_Pvariable(mqtt_id, var) mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config) await mqtt.register_mqtt_component(mqtt_, config)

View File

@@ -1,26 +1,26 @@
import logging import logging
from esphome import automation, core from esphome import automation, core
import esphome.codegen as cg
from esphome.components import font from esphome.components import font
import esphome.components.image as espImage import esphome.components.image as espImage
from esphome.components.image import ( from esphome.components.image import (
CONF_USE_TRANSPARENCY, CONF_USE_TRANSPARENCY,
LOCAL_SCHEMA, LOCAL_SCHEMA,
WEB_SCHEMA,
SOURCE_WEB,
SOURCE_LOCAL, SOURCE_LOCAL,
SOURCE_WEB,
WEB_SCHEMA,
) )
import esphome.config_validation as cv import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.const import ( from esphome.const import (
CONF_FILE, CONF_FILE,
CONF_ID, CONF_ID,
CONF_PATH,
CONF_RAW_DATA_ID, CONF_RAW_DATA_ID,
CONF_REPEAT, CONF_REPEAT,
CONF_RESIZE, CONF_RESIZE,
CONF_TYPE,
CONF_SOURCE, CONF_SOURCE,
CONF_PATH, CONF_TYPE,
CONF_URL, CONF_URL,
) )
from esphome.core import CORE, HexInt from esphome.core import CORE, HexInt
@@ -172,6 +172,9 @@ async def to_code(config):
path = CORE.relative_config_path(conf_file[CONF_PATH]) path = CORE.relative_config_path(conf_file[CONF_PATH])
elif conf_file[CONF_SOURCE] == SOURCE_WEB: elif conf_file[CONF_SOURCE] == SOURCE_WEB:
path = espImage.compute_local_image_path(conf_file).as_posix() path = espImage.compute_local_image_path(conf_file).as_posix()
else:
raise core.EsphomeError(f"Unknown animation source: {conf_file[CONF_SOURCE]}")
try: try:
image = Image.open(path) image = Image.open(path)
except Exception as e: except Exception as e:
@@ -183,13 +186,12 @@ async def to_code(config):
new_width_max, new_height_max = config[CONF_RESIZE] new_width_max, new_height_max = config[CONF_RESIZE]
ratio = min(new_width_max / width, new_height_max / height) ratio = min(new_width_max / width, new_height_max / height)
width, height = int(width * ratio), int(height * ratio) width, height = int(width * ratio), int(height * ratio)
else: elif width > 500 or height > 500:
if width > 500 or height > 500: _LOGGER.warning(
_LOGGER.warning( 'The image "%s" you requested is very big. Please consider'
'The image "%s" you requested is very big. Please consider' " using the resize parameter.",
" using the resize parameter.", path,
path, )
)
transparent = config[CONF_USE_TRANSPARENCY] transparent = config[CONF_USE_TRANSPARENCY]
@@ -306,6 +308,8 @@ async def to_code(config):
if transparent: if transparent:
alpha = image.split()[-1] alpha = image.split()[-1]
has_alpha = alpha.getextrema()[0] < 0xFF has_alpha = alpha.getextrema()[0] < 0xFF
else:
has_alpha = False
frame = image.convert("1", dither=Image.Dither.NONE) frame = image.convert("1", dither=Image.Dither.NONE)
if CONF_RESIZE in config: if CONF_RESIZE in config:
frame = frame.resize([width, height]) frame = frame.resize([width, height])

View File

@@ -62,6 +62,8 @@ service APIConnection {
rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {} rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {} rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {}
rpc voice_assistant_get_configuration(VoiceAssistantConfigurationRequest) returns (VoiceAssistantConfigurationResponse) {}
rpc voice_assistant_set_configuration(VoiceAssistantSetConfiguration) returns (void) {}
rpc alarm_control_panel_command (AlarmControlPanelCommandRequest) returns (void) {} rpc alarm_control_panel_command (AlarmControlPanelCommandRequest) returns (void) {}
} }
@@ -1118,6 +1120,7 @@ message MediaPlayerSupportedFormat {
uint32 sample_rate = 2; uint32 sample_rate = 2;
uint32 num_channels = 3; uint32 num_channels = 3;
MediaPlayerFormatPurpose purpose = 4; MediaPlayerFormatPurpose purpose = 4;
uint32 sample_bytes = 5;
} }
message ListEntitiesMediaPlayerResponse { message ListEntitiesMediaPlayerResponse {
option (id) = 63; option (id) = 63;
@@ -1570,6 +1573,36 @@ message VoiceAssistantAnnounceFinished {
bool success = 1; bool success = 1;
} }
message VoiceAssistantWakeWord {
string id = 1;
string wake_word = 2;
repeated string trained_languages = 3;
}
message VoiceAssistantConfigurationRequest {
option (id) = 121;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_VOICE_ASSISTANT";
}
message VoiceAssistantConfigurationResponse {
option (id) = 122;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_VOICE_ASSISTANT";
repeated VoiceAssistantWakeWord available_wake_words = 1;
repeated string active_wake_words = 2;
uint32 max_active_wake_words = 3;
}
message VoiceAssistantSetConfiguration {
option (id) = 123;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_VOICE_ASSISTANT";
repeated string active_wake_words = 1;
}
// ==================== ALARM CONTROL PANEL ==================== // ==================== ALARM CONTROL PANEL ====================
enum AlarmControlPanelState { enum AlarmControlPanelState {
ALARM_STATE_DISARMED = 0; ALARM_STATE_DISARMED = 0;

View File

@@ -1,4 +1,5 @@
#include "api_connection.h" #include "api_connection.h"
#ifdef USE_API
#include <cerrno> #include <cerrno>
#include <cinttypes> #include <cinttypes>
#include <utility> #include <utility>
@@ -1032,6 +1033,7 @@ bool APIConnection::send_media_player_info(media_player::MediaPlayer *media_play
media_format.sample_rate = supported_format.sample_rate; media_format.sample_rate = supported_format.sample_rate;
media_format.num_channels = supported_format.num_channels; media_format.num_channels = supported_format.num_channels;
media_format.purpose = static_cast<enums::MediaPlayerFormatPurpose>(supported_format.purpose); media_format.purpose = static_cast<enums::MediaPlayerFormatPurpose>(supported_format.purpose);
media_format.sample_bytes = supported_format.sample_bytes;
msg.supported_formats.push_back(media_format); msg.supported_formats.push_back(media_format);
} }
@@ -1223,6 +1225,42 @@ void APIConnection::on_voice_assistant_announce_request(const VoiceAssistantAnno
} }
} }
VoiceAssistantConfigurationResponse APIConnection::voice_assistant_get_configuration(
const VoiceAssistantConfigurationRequest &msg) {
VoiceAssistantConfigurationResponse resp;
if (voice_assistant::global_voice_assistant != nullptr) {
if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
return resp;
}
auto &config = voice_assistant::global_voice_assistant->get_configuration();
for (auto &wake_word : config.available_wake_words) {
VoiceAssistantWakeWord resp_wake_word;
resp_wake_word.id = wake_word.id;
resp_wake_word.wake_word = wake_word.wake_word;
for (const auto &lang : wake_word.trained_languages) {
resp_wake_word.trained_languages.push_back(lang);
}
resp.available_wake_words.push_back(std::move(resp_wake_word));
}
for (auto &wake_word_id : config.active_wake_words) {
resp.active_wake_words.push_back(wake_word_id);
}
resp.max_active_wake_words = config.max_active_wake_words;
}
return resp;
}
void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) {
if (voice_assistant::global_voice_assistant != nullptr) {
if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
return;
}
voice_assistant::global_voice_assistant->on_set_configuration(msg.active_wake_words);
}
}
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
@@ -1531,3 +1569,4 @@ void APIConnection::on_fatal_error() {
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome
#endif

View File

@@ -1,12 +1,13 @@
#pragma once #pragma once
#include "esphome/core/defines.h"
#ifdef USE_API
#include "api_frame_helper.h" #include "api_frame_helper.h"
#include "api_pb2.h" #include "api_pb2.h"
#include "api_pb2_service.h" #include "api_pb2_service.h"
#include "api_server.h" #include "api_server.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include <vector> #include <vector>
@@ -152,6 +153,9 @@ class APIConnection : public APIServerConnection {
void on_voice_assistant_audio(const VoiceAssistantAudio &msg) override; void on_voice_assistant_audio(const VoiceAssistantAudio &msg) override;
void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) override; void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) override;
void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) override; void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) override;
VoiceAssistantConfigurationResponse voice_assistant_get_configuration(
const VoiceAssistantConfigurationRequest &msg) override;
void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override;
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
@@ -265,3 +269,4 @@ class APIConnection : public APIServerConnection {
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome
#endif

View File

@@ -1,5 +1,5 @@
#include "api_frame_helper.h" #include "api_frame_helper.h"
#ifdef USE_API
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
@@ -1028,3 +1028,4 @@ APIError APIPlaintextFrameHelper::shutdown(int how) {
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome
#endif

View File

@@ -5,7 +5,7 @@
#include <vector> #include <vector>
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#ifdef USE_API
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
#include "noise/protocol.h" #include "noise/protocol.h"
#endif #endif
@@ -190,3 +190,4 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome
#endif

View File

@@ -5149,6 +5149,10 @@ bool MediaPlayerSupportedFormat::decode_varint(uint32_t field_id, ProtoVarInt va
this->purpose = value.as_enum<enums::MediaPlayerFormatPurpose>(); this->purpose = value.as_enum<enums::MediaPlayerFormatPurpose>();
return true; return true;
} }
case 5: {
this->sample_bytes = value.as_uint32();
return true;
}
default: default:
return false; return false;
} }
@@ -5168,6 +5172,7 @@ void MediaPlayerSupportedFormat::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(2, this->sample_rate); buffer.encode_uint32(2, this->sample_rate);
buffer.encode_uint32(3, this->num_channels); buffer.encode_uint32(3, this->num_channels);
buffer.encode_enum<enums::MediaPlayerFormatPurpose>(4, this->purpose); buffer.encode_enum<enums::MediaPlayerFormatPurpose>(4, this->purpose);
buffer.encode_uint32(5, this->sample_bytes);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void MediaPlayerSupportedFormat::dump_to(std::string &out) const { void MediaPlayerSupportedFormat::dump_to(std::string &out) const {
@@ -5190,6 +5195,11 @@ void MediaPlayerSupportedFormat::dump_to(std::string &out) const {
out.append(" purpose: "); out.append(" purpose: ");
out.append(proto_enum_to_string<enums::MediaPlayerFormatPurpose>(this->purpose)); out.append(proto_enum_to_string<enums::MediaPlayerFormatPurpose>(this->purpose));
out.append("\n"); out.append("\n");
out.append(" sample_bytes: ");
sprintf(buffer, "%" PRIu32, this->sample_bytes);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@@ -7114,6 +7124,140 @@ void VoiceAssistantAnnounceFinished::dump_to(std::string &out) const {
out.append("}"); out.append("}");
} }
#endif #endif
bool VoiceAssistantWakeWord::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->id = value.as_string();
return true;
}
case 2: {
this->wake_word = value.as_string();
return true;
}
case 3: {
this->trained_languages.push_back(value.as_string());
return true;
}
default:
return false;
}
}
void VoiceAssistantWakeWord::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->id);
buffer.encode_string(2, this->wake_word);
for (auto &it : this->trained_languages) {
buffer.encode_string(3, it, true);
}
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void VoiceAssistantWakeWord::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("VoiceAssistantWakeWord {\n");
out.append(" id: ");
out.append("'").append(this->id).append("'");
out.append("\n");
out.append(" wake_word: ");
out.append("'").append(this->wake_word).append("'");
out.append("\n");
for (const auto &it : this->trained_languages) {
out.append(" trained_languages: ");
out.append("'").append(it).append("'");
out.append("\n");
}
out.append("}");
}
#endif
void VoiceAssistantConfigurationRequest::encode(ProtoWriteBuffer buffer) const {}
#ifdef HAS_PROTO_MESSAGE_DUMP
void VoiceAssistantConfigurationRequest::dump_to(std::string &out) const {
out.append("VoiceAssistantConfigurationRequest {}");
}
#endif
bool VoiceAssistantConfigurationResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 3: {
this->max_active_wake_words = value.as_uint32();
return true;
}
default:
return false;
}
}
bool VoiceAssistantConfigurationResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->available_wake_words.push_back(value.as_message<VoiceAssistantWakeWord>());
return true;
}
case 2: {
this->active_wake_words.push_back(value.as_string());
return true;
}
default:
return false;
}
}
void VoiceAssistantConfigurationResponse::encode(ProtoWriteBuffer buffer) const {
for (auto &it : this->available_wake_words) {
buffer.encode_message<VoiceAssistantWakeWord>(1, it, true);
}
for (auto &it : this->active_wake_words) {
buffer.encode_string(2, it, true);
}
buffer.encode_uint32(3, this->max_active_wake_words);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void VoiceAssistantConfigurationResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("VoiceAssistantConfigurationResponse {\n");
for (const auto &it : this->available_wake_words) {
out.append(" available_wake_words: ");
it.dump_to(out);
out.append("\n");
}
for (const auto &it : this->active_wake_words) {
out.append(" active_wake_words: ");
out.append("'").append(it).append("'");
out.append("\n");
}
out.append(" max_active_wake_words: ");
sprintf(buffer, "%" PRIu32, this->max_active_wake_words);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
bool VoiceAssistantSetConfiguration::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->active_wake_words.push_back(value.as_string());
return true;
}
default:
return false;
}
}
void VoiceAssistantSetConfiguration::encode(ProtoWriteBuffer buffer) const {
for (auto &it : this->active_wake_words) {
buffer.encode_string(1, it, true);
}
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void VoiceAssistantSetConfiguration::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("VoiceAssistantSetConfiguration {\n");
for (const auto &it : this->active_wake_words) {
out.append(" active_wake_words: ");
out.append("'").append(it).append("'");
out.append("\n");
}
out.append("}");
}
#endif
bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) { switch (field_id) {
case 6: { case 6: {

View File

@@ -1277,6 +1277,7 @@ class MediaPlayerSupportedFormat : public ProtoMessage {
uint32_t sample_rate{0}; uint32_t sample_rate{0};
uint32_t num_channels{0}; uint32_t num_channels{0};
enums::MediaPlayerFormatPurpose purpose{}; enums::MediaPlayerFormatPurpose purpose{};
uint32_t sample_bytes{0};
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;
@@ -1848,6 +1849,53 @@ class VoiceAssistantAnnounceFinished : public ProtoMessage {
protected: protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override; bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
}; };
class VoiceAssistantWakeWord : public ProtoMessage {
public:
std::string id{};
std::string wake_word{};
std::vector<std::string> trained_languages{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class VoiceAssistantConfigurationRequest : public ProtoMessage {
public:
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
};
class VoiceAssistantConfigurationResponse : public ProtoMessage {
public:
std::vector<VoiceAssistantWakeWord> available_wake_words{};
std::vector<std::string> active_wake_words{};
uint32_t max_active_wake_words{0};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class VoiceAssistantSetConfiguration : public ProtoMessage {
public:
std::vector<std::string> active_wake_words{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class ListEntitiesAlarmControlPanelResponse : public ProtoMessage { class ListEntitiesAlarmControlPanelResponse : public ProtoMessage {
public: public:
std::string object_id{}; std::string object_id{};

View File

@@ -496,6 +496,19 @@ bool APIServerConnectionBase::send_voice_assistant_announce_finished(const Voice
return this->send_message_<VoiceAssistantAnnounceFinished>(msg, 120); return this->send_message_<VoiceAssistantAnnounceFinished>(msg, 120);
} }
#endif #endif
#ifdef USE_VOICE_ASSISTANT
#endif
#ifdef USE_VOICE_ASSISTANT
bool APIServerConnectionBase::send_voice_assistant_configuration_response(
const VoiceAssistantConfigurationResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_voice_assistant_configuration_response: %s", msg.dump().c_str());
#endif
return this->send_message_<VoiceAssistantConfigurationResponse>(msg, 122);
}
#endif
#ifdef USE_VOICE_ASSISTANT
#endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
bool APIServerConnectionBase::send_list_entities_alarm_control_panel_response( bool APIServerConnectionBase::send_list_entities_alarm_control_panel_response(
const ListEntitiesAlarmControlPanelResponse &msg) { const ListEntitiesAlarmControlPanelResponse &msg) {
@@ -1156,6 +1169,28 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ESP_LOGVV(TAG, "on_voice_assistant_announce_request: %s", msg.dump().c_str()); ESP_LOGVV(TAG, "on_voice_assistant_announce_request: %s", msg.dump().c_str());
#endif #endif
this->on_voice_assistant_announce_request(msg); this->on_voice_assistant_announce_request(msg);
#endif
break;
}
case 121: {
#ifdef USE_VOICE_ASSISTANT
VoiceAssistantConfigurationRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_voice_assistant_configuration_request: %s", msg.dump().c_str());
#endif
this->on_voice_assistant_configuration_request(msg);
#endif
break;
}
case 123: {
#ifdef USE_VOICE_ASSISTANT
VoiceAssistantSetConfiguration msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_voice_assistant_set_configuration: %s", msg.dump().c_str());
#endif
this->on_voice_assistant_set_configuration(msg);
#endif #endif
break; break;
} }
@@ -1646,6 +1681,35 @@ void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVo
this->subscribe_voice_assistant(msg); this->subscribe_voice_assistant(msg);
} }
#endif #endif
#ifdef USE_VOICE_ASSISTANT
void APIServerConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
VoiceAssistantConfigurationResponse ret = this->voice_assistant_get_configuration(msg);
if (!this->send_voice_assistant_configuration_response(ret)) {
this->on_fatal_error();
}
}
#endif
#ifdef USE_VOICE_ASSISTANT
void APIServerConnection::on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->voice_assistant_set_configuration(msg);
}
#endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) { void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) {
if (!this->is_connection_setup()) { if (!this->is_connection_setup()) {

View File

@@ -253,6 +253,15 @@ class APIServerConnectionBase : public ProtoService {
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
bool send_voice_assistant_announce_finished(const VoiceAssistantAnnounceFinished &msg); bool send_voice_assistant_announce_finished(const VoiceAssistantAnnounceFinished &msg);
#endif #endif
#ifdef USE_VOICE_ASSISTANT
virtual void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &value){};
#endif
#ifdef USE_VOICE_ASSISTANT
bool send_voice_assistant_configuration_response(const VoiceAssistantConfigurationResponse &msg);
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &value){};
#endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
bool send_list_entities_alarm_control_panel_response(const ListEntitiesAlarmControlPanelResponse &msg); bool send_list_entities_alarm_control_panel_response(const ListEntitiesAlarmControlPanelResponse &msg);
#endif #endif
@@ -425,6 +434,13 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0; virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0;
#endif #endif
#ifdef USE_VOICE_ASSISTANT
virtual VoiceAssistantConfigurationResponse voice_assistant_get_configuration(
const VoiceAssistantConfigurationRequest &msg) = 0;
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) = 0;
#endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0; virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0;
#endif #endif
@@ -526,6 +542,12 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override; void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override;
#endif #endif
#ifdef USE_VOICE_ASSISTANT
void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) override;
#endif
#ifdef USE_VOICE_ASSISTANT
void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override;
#endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override; void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override;
#endif #endif

View File

@@ -1,4 +1,5 @@
#include "api_server.h" #include "api_server.h"
#ifdef USE_API
#include <cerrno> #include <cerrno>
#include "api_connection.h" #include "api_connection.h"
#include "esphome/components/network/util.h" #include "esphome/components/network/util.h"
@@ -403,3 +404,4 @@ void APIServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlP
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome
#endif

View File

@@ -1,5 +1,7 @@
#pragma once #pragma once
#include "esphome/core/defines.h"
#ifdef USE_API
#include "api_noise_context.h" #include "api_noise_context.h"
#include "api_pb2.h" #include "api_pb2.h"
#include "api_pb2_service.h" #include "api_pb2_service.h"
@@ -7,7 +9,6 @@
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/controller.h" #include "esphome/core/controller.h"
#include "esphome/core/defines.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "list_entities.h" #include "list_entities.h"
#include "subscribe_state.h" #include "subscribe_state.h"
@@ -153,3 +154,4 @@ template<typename... Ts> class APIConnectedCondition : public Condition<Ts...> {
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome
#endif

View File

@@ -1,9 +1,9 @@
#pragma once #pragma once
#include <map> #include <map>
#include "user_services.h"
#include "api_server.h" #include "api_server.h"
#ifdef USE_API
#include "user_services.h"
namespace esphome { namespace esphome {
namespace api { namespace api {
@@ -216,3 +216,4 @@ class CustomAPIDevice {
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome
#endif

View File

@@ -1,10 +1,10 @@
#pragma once #pragma once
#include "api_server.h"
#ifdef USE_API
#include "api_pb2.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "api_pb2.h"
#include "api_server.h"
#include <vector> #include <vector>
namespace esphome { namespace esphome {
@@ -81,3 +81,4 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome
#endif

View File

@@ -1,4 +1,5 @@
#include "list_entities.h" #include "list_entities.h"
#ifdef USE_API
#include "api_connection.h" #include "api_connection.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
@@ -104,3 +105,4 @@ bool ListEntitiesIterator::on_update(update::UpdateEntity *update) { return this
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome
#endif

View File

@@ -1,9 +1,9 @@
#pragma once #pragma once
#include "esphome/core/defines.h"
#ifdef USE_API
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/component_iterator.h" #include "esphome/core/component_iterator.h"
#include "esphome/core/defines.h"
namespace esphome { namespace esphome {
namespace api { namespace api {
@@ -87,3 +87,4 @@ class ListEntitiesIterator : public ComponentIterator {
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome
#endif

View File

@@ -1,4 +1,5 @@
#include "subscribe_state.h" #include "subscribe_state.h"
#ifdef USE_API
#include "api_connection.h" #include "api_connection.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
@@ -84,3 +85,4 @@ InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(clie
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome
#endif

View File

@@ -1,10 +1,10 @@
#pragma once #pragma once
#include "esphome/core/defines.h"
#ifdef USE_API
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/component_iterator.h" #include "esphome/core/component_iterator.h"
#include "esphome/core/controller.h" #include "esphome/core/controller.h"
#include "esphome/core/defines.h"
namespace esphome { namespace esphome {
namespace api { namespace api {
@@ -82,3 +82,4 @@ class InitialStateIterator : public ComponentIterator {
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome
#endif

View File

@@ -0,0 +1,57 @@
from esphome import automation
from esphome.automation import maybe_simple_id
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_VOLUME
from esphome.core import coroutine_with_priority
CODEOWNERS = ["@kbx81"]
IS_PLATFORM_COMPONENT = True
audio_dac_ns = cg.esphome_ns.namespace("audio_dac")
AudioDac = audio_dac_ns.class_("AudioDac")
MuteOffAction = audio_dac_ns.class_("MuteOffAction", automation.Action)
MuteOnAction = audio_dac_ns.class_("MuteOnAction", automation.Action)
SetVolumeAction = audio_dac_ns.class_("SetVolumeAction", automation.Action)
MUTE_ACTION_SCHEMA = maybe_simple_id(
{
cv.GenerateID(): cv.use_id(AudioDac),
}
)
SET_VOLUME_ACTION_SCHEMA = cv.maybe_simple_value(
{
cv.GenerateID(): cv.use_id(AudioDac),
cv.Required(CONF_VOLUME): cv.templatable(cv.percentage),
},
key=CONF_VOLUME,
)
@automation.register_action("audio_dac.mute_off", MuteOffAction, MUTE_ACTION_SCHEMA)
@automation.register_action("audio_dac.mute_on", MuteOnAction, MUTE_ACTION_SCHEMA)
async def audio_dac_mute_action_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action(
"audio_dac.set_volume", SetVolumeAction, SET_VOLUME_ACTION_SCHEMA
)
async def audio_dac_set_volume_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_VOLUME), args, float)
cg.add(var.set_volume(template_))
return var
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_define("USE_AUDIO_DAC")
cg.add_global(audio_dac_ns.using)

View File

@@ -0,0 +1,23 @@
#pragma once
#include "esphome/core/defines.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace audio_dac {
class AudioDac {
public:
virtual bool set_mute_off() = 0;
virtual bool set_mute_on() = 0;
virtual bool set_volume(float volume) = 0;
virtual bool is_muted() = 0;
virtual float volume() = 0;
protected:
bool is_muted_{false};
};
} // namespace audio_dac
} // namespace esphome

View File

@@ -0,0 +1,43 @@
#pragma once
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "audio_dac.h"
namespace esphome {
namespace audio_dac {
template<typename... Ts> class MuteOffAction : public Action<Ts...> {
public:
explicit MuteOffAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {}
void play(Ts... x) override { this->audio_dac_->set_mute_off(); }
protected:
AudioDac *audio_dac_;
};
template<typename... Ts> class MuteOnAction : public Action<Ts...> {
public:
explicit MuteOnAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {}
void play(Ts... x) override { this->audio_dac_->set_mute_on(); }
protected:
AudioDac *audio_dac_;
};
template<typename... Ts> class SetVolumeAction : public Action<Ts...> {
public:
explicit SetVolumeAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {}
TEMPLATABLE_VALUE(float, volume)
void play(Ts... x) override { this->audio_dac_->set_volume(this->volume_.value(x...)); }
protected:
AudioDac *audio_dac_;
};
} // namespace audio_dac
} // namespace esphome

View File

@@ -157,8 +157,11 @@ void BangBangClimate::switch_to_action_(climate::ClimateAction action) {
default: default:
trig = nullptr; trig = nullptr;
} }
assert(trig != nullptr); if (trig != nullptr) {
trig->trigger(); trig->trigger();
} else {
ESP_LOGW(TAG, "trig not set - unsupported action");
}
this->action = action; this->action = action;
this->prev_trigger_ = trig; this->prev_trigger_ = trig;
this->publish_state(); this->publish_state();

View File

@@ -13,8 +13,10 @@ float bedjet_temp_to_f(const uint8_t temp) {
/** Cleans up the packet before sending. */ /** Cleans up the packet before sending. */
BedjetPacket *BedjetCodec::clean_packet_() { BedjetPacket *BedjetCodec::clean_packet_() {
// So far no commands require more than 2 bytes of data. // So far no commands require more than 2 bytes of data
assert(this->packet_.data_length <= 2); if (this->packet_.data_length > 2) {
ESP_LOGW(TAG, "Packet may be malformed");
}
for (int i = this->packet_.data_length; i < 2; i++) { for (int i = this->packet_.data_length; i < 2; i++) {
this->packet_.data[i] = '\0'; this->packet_.data[i] = '\0';
} }

View File

@@ -25,7 +25,7 @@ from esphome.const import (
CONF_STATE, CONF_STATE,
CONF_TIMING, CONF_TIMING,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER,
DEVICE_CLASS_BATTERY, DEVICE_CLASS_BATTERY,
DEVICE_CLASS_BATTERY_CHARGING, DEVICE_CLASS_BATTERY_CHARGING,
DEVICE_CLASS_CARBON_MONOXIDE, DEVICE_CLASS_CARBON_MONOXIDE,
@@ -543,9 +543,8 @@ async def setup_binary_sensor_core_(var, config):
mqtt_ = cg.new_Pvariable(mqtt_id, var) mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config) await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: if web_server_config := config.get(CONF_WEB_SERVER):
web_server_ = await cg.get_variable(webserver_id) await web_server.add_entity_config(var, web_server_config)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_binary_sensor(var, config): async def register_binary_sensor(var, config):

View File

@@ -145,8 +145,9 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
), ),
) )
async def reset_energy_to_code(config, action_id, template_arg, args): async def reset_energy_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg)
return cg.new_Pvariable(action_id, template_arg, paren) await cg.register_parented(var, config[CONF_ID])
return var
async def to_code(config): async def to_code(config):

View File

@@ -41,7 +41,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,
cv.Optional(CONF_TIMEOUT, default="5min"): cv.positive_time_period, cv.Optional(CONF_TIMEOUT, default="5min"): cv.positive_time_period,
cv.Optional(CONF_MIN_RSSI): cv.All( cv.Optional(CONF_MIN_RSSI): cv.All(
cv.decibel, cv.int_range(min=-100, max=-30) cv.decibel, cv.int_range(min=-100, max=-30)
@@ -83,7 +83,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

@@ -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

@@ -11,7 +11,7 @@ from esphome.const import (
CONF_MQTT_ID, CONF_MQTT_ID,
CONF_ON_PRESS, CONF_ON_PRESS,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER,
DEVICE_CLASS_EMPTY, DEVICE_CLASS_EMPTY,
DEVICE_CLASS_IDENTIFY, DEVICE_CLASS_IDENTIFY,
DEVICE_CLASS_RESTART, DEVICE_CLASS_RESTART,
@@ -97,9 +97,8 @@ async def setup_button_core_(var, config):
mqtt_ = cg.new_Pvariable(mqtt_id, var) mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config) await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: if web_server_config := config.get(CONF_WEB_SERVER):
web_server_ = await cg.get_variable(webserver_id) await web_server.add_entity_config(var, web_server_config)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_button(var, config): async def register_button(var, config):

View File

@@ -1,18 +1,20 @@
from esphome import pins from esphome import pins
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import i2c from esphome.components import i2c
from esphome.components.i2c import I2CBus
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_I2C_ID,
CONF_ID, CONF_ID,
CONF_INPUT, CONF_INPUT,
CONF_INVERTED, CONF_INVERTED,
CONF_MODE, CONF_MODE,
CONF_NUMBER, CONF_NUMBER,
CONF_OPEN_DRAIN,
CONF_OUTPUT, CONF_OUTPUT,
CONF_RESTORE_VALUE,
) )
CODEOWNERS = ["@jesterret"] CODEOWNERS = ["@jesterret", "@clydebarrow"]
DEPENDENCIES = ["i2c"] DEPENDENCIES = ["i2c"]
MULTI_CONF = True MULTI_CONF = True
ch422g_ns = cg.esphome_ns.namespace("ch422g") ch422g_ns = cg.esphome_ns.namespace("ch422g")
@@ -23,29 +25,36 @@ CH422GGPIOPin = ch422g_ns.class_(
) )
CONF_CH422G = "ch422g" CONF_CH422G = "ch422g"
CONFIG_SCHEMA = (
cv.Schema( # Note that no address is configurable - each register in the CH422G has a dedicated i2c address
{ CONFIG_SCHEMA = cv.Schema(
cv.Required(CONF_ID): cv.declare_id(CH422GComponent), {
cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean, cv.GenerateID(CONF_ID): cv.declare_id(CH422GComponent),
} cv.GenerateID(CONF_I2C_ID): cv.use_id(I2CBus),
) }
.extend(cv.COMPONENT_SCHEMA) ).extend(cv.COMPONENT_SCHEMA)
.extend(i2c.i2c_device_schema(0x24))
)
async def to_code(config): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE]))
await cg.register_component(var, config) await cg.register_component(var, config)
await i2c.register_i2c_device(var, config) # Can't use register_i2c_device because there is no CONF_ADDRESS
parent = await cg.get_variable(config[CONF_I2C_ID])
cg.add(var.set_i2c_bus(parent))
# This is used as a final validation step so that modes have been fully transformed.
def pin_mode_check(pin_config, _):
if pin_config[CONF_MODE][CONF_INPUT] and pin_config[CONF_NUMBER] >= 8:
raise cv.Invalid("CH422G only supports input on pins 0-7")
if pin_config[CONF_MODE][CONF_OPEN_DRAIN] and pin_config[CONF_NUMBER] < 8:
raise cv.Invalid("CH422G only supports open drain output on pins 8-11")
CH422G_PIN_SCHEMA = pins.gpio_base_schema( CH422G_PIN_SCHEMA = pins.gpio_base_schema(
CH422GGPIOPin, CH422GGPIOPin,
cv.int_range(min=0, max=7), cv.int_range(min=0, max=11),
modes=[CONF_INPUT, CONF_OUTPUT], modes=[CONF_INPUT, CONF_OUTPUT, CONF_OPEN_DRAIN],
).extend( ).extend(
{ {
cv.Required(CONF_CH422G): cv.use_id(CH422GComponent), cv.Required(CONF_CH422G): cv.use_id(CH422GComponent),
@@ -53,7 +62,7 @@ CH422G_PIN_SCHEMA = pins.gpio_base_schema(
) )
@pins.PIN_SCHEMA_REGISTRY.register(CONF_CH422G, CH422G_PIN_SCHEMA) @pins.PIN_SCHEMA_REGISTRY.register(CONF_CH422G, CH422G_PIN_SCHEMA, pin_mode_check)
async def ch422g_pin_to_code(config): async def ch422g_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
parent = await cg.get_variable(config[CONF_CH422G]) parent = await cg.get_variable(config[CONF_CH422G])

View File

@@ -4,33 +4,33 @@
namespace esphome { namespace esphome {
namespace ch422g { namespace ch422g {
const uint8_t CH422G_REG_IN = 0x26; static const uint8_t CH422G_REG_MODE = 0x24;
const uint8_t CH422G_REG_OUT = 0x38; static const uint8_t CH422G_MODE_OUTPUT = 0x01; // enables output mode on 0-7
const uint8_t OUT_REG_DEFAULT_VAL = 0xdf; static const uint8_t CH422G_MODE_OPEN_DRAIN = 0x04; // enables open drain mode on 8-11
static const uint8_t CH422G_REG_IN = 0x26; // read reg for input bits
static const uint8_t CH422G_REG_OUT = 0x38; // write reg for output bits 0-7
static const uint8_t CH422G_REG_OUT_UPPER = 0x23; // write reg for output bits 8-11
static const char *const TAG = "ch422g"; static const char *const TAG = "ch422g";
void CH422GComponent::setup() { void CH422GComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up CH422G..."); ESP_LOGCONFIG(TAG, "Setting up CH422G...");
// Test to see if device exists // set outputs before mode
if (!this->read_inputs_()) { this->write_outputs_();
// Set mode and check for errors
if (!this->set_mode_(this->mode_value_) || !this->read_inputs_()) {
ESP_LOGE(TAG, "CH422G not detected at 0x%02X", this->address_); ESP_LOGE(TAG, "CH422G not detected at 0x%02X", this->address_);
this->mark_failed(); this->mark_failed();
return; return;
} }
// restore defaults over whatever got saved on last boot ESP_LOGCONFIG(TAG, "Initialization complete. Warning: %d, Error: %d", this->status_has_warning(),
if (!this->restore_value_) { this->status_has_error());
this->write_output_(OUT_REG_DEFAULT_VAL);
}
ESP_LOGD(TAG, "Initialization complete. Warning: %d, Error: %d", this->status_has_warning(),
this->status_has_error());
} }
void CH422GComponent::loop() { void CH422GComponent::loop() {
// Clear all the previously read flags. // Clear all the previously read flags.
this->pin_read_cache_ = 0x00; this->pin_read_flags_ = 0x00;
} }
void CH422GComponent::dump_config() { void CH422GComponent::dump_config() {
@@ -41,82 +41,99 @@ void CH422GComponent::dump_config() {
} }
} }
// ch422g doesn't have any flag support (needs docs?) void CH422GComponent::pin_mode(uint8_t pin, gpio::Flags flags) {
void CH422GComponent::pin_mode(uint8_t pin, gpio::Flags flags) {} if (pin < 8) {
if (flags & gpio::FLAG_OUTPUT) {
this->mode_value_ |= CH422G_MODE_OUTPUT;
}
} else {
if (flags & gpio::FLAG_OPEN_DRAIN) {
this->mode_value_ |= CH422G_MODE_OPEN_DRAIN;
}
}
}
bool CH422GComponent::digital_read(uint8_t pin) { bool CH422GComponent::digital_read(uint8_t pin) {
if (this->pin_read_cache_ == 0 || this->pin_read_cache_ & (1 << pin)) { if (this->pin_read_flags_ == 0 || this->pin_read_flags_ & (1 << pin)) {
// Read values on first access or in case it's being read again in the same loop // Read values on first access or in case it's being read again in the same loop
this->read_inputs_(); this->read_inputs_();
} }
this->pin_read_cache_ |= (1 << pin); this->pin_read_flags_ |= (1 << pin);
return this->state_mask_ & (1 << pin); return (this->input_bits_ & (1 << pin)) != 0;
} }
void CH422GComponent::digital_write(uint8_t pin, bool value) { void CH422GComponent::digital_write(uint8_t pin, bool value) {
if (value) { if (value) {
this->write_output_(this->state_mask_ | (1 << pin)); this->output_bits_ |= (1 << pin);
} else { } else {
this->write_output_(this->state_mask_ & ~(1 << pin)); this->output_bits_ &= ~(1 << pin);
} }
this->write_outputs_();
} }
bool CH422GComponent::read_inputs_() { bool CH422GComponent::read_inputs_() {
if (this->is_failed()) { if (this->is_failed()) {
return false; return false;
} }
uint8_t result;
uint8_t temp = 0; // reading inputs requires the chip to be in input mode, possibly temporarily.
if ((this->last_error_ = this->read(&temp, 1)) != esphome::i2c::ERROR_OK) { if (this->mode_value_ & CH422G_MODE_OUTPUT) {
this->status_set_warning(str_sprintf("read_inputs_(): I2C I/O error: %d", (int) this->last_error_).c_str()); this->set_mode_(this->mode_value_ & ~CH422G_MODE_OUTPUT);
return false; result = this->read_reg_(CH422G_REG_IN);
this->set_mode_(this->mode_value_);
} else {
result = this->read_reg_(CH422G_REG_IN);
} }
this->input_bits_ = result;
uint8_t output = 0;
if ((this->last_error_ = this->bus_->read(CH422G_REG_IN, &output, 1)) != esphome::i2c::ERROR_OK) {
this->status_set_warning(str_sprintf("read_inputs_(): I2C I/O error: %d", (int) this->last_error_).c_str());
return false;
}
this->state_mask_ = output;
this->status_clear_warning(); this->status_clear_warning();
return true; return true;
} }
bool CH422GComponent::write_output_(uint8_t value) { // Write a register. Can't use the standard write_byte() method because there is no single pre-configured i2c address.
const uint8_t temp = 1; bool CH422GComponent::write_reg_(uint8_t reg, uint8_t value) {
if ((this->last_error_ = this->write(&temp, 1, false)) != esphome::i2c::ERROR_OK) { auto err = this->bus_->write(reg, &value, 1);
this->status_set_warning(str_sprintf("write_output_(): I2C I/O error: %d", (int) this->last_error_).c_str()); if (err != i2c::ERROR_OK) {
this->status_set_warning(str_sprintf("write failed for register 0x%X, error %d", reg, err).c_str());
return false; return false;
} }
uint8_t write_mask = value;
if ((this->last_error_ = this->bus_->write(CH422G_REG_OUT, &write_mask, 1)) != esphome::i2c::ERROR_OK) {
this->status_set_warning(
str_sprintf("write_output_(): I2C I/O error: %d for write_mask: %d", (int) this->last_error_, (int) write_mask)
.c_str());
return false;
}
this->state_mask_ = value;
this->status_clear_warning(); this->status_clear_warning();
return true; return true;
} }
uint8_t CH422GComponent::read_reg_(uint8_t reg) {
uint8_t value;
auto err = this->bus_->read(reg, &value, 1);
if (err != i2c::ERROR_OK) {
this->status_set_warning(str_sprintf("read failed for register 0x%X, error %d", reg, err).c_str());
return 0;
}
this->status_clear_warning();
return value;
}
bool CH422GComponent::set_mode_(uint8_t mode) { return this->write_reg_(CH422G_REG_MODE, mode); }
bool CH422GComponent::write_outputs_() {
return this->write_reg_(CH422G_REG_OUT, static_cast<uint8_t>(this->output_bits_)) &&
this->write_reg_(CH422G_REG_OUT_UPPER, static_cast<uint8_t>(this->output_bits_ >> 8));
}
float CH422GComponent::get_setup_priority() const { return setup_priority::IO; } float CH422GComponent::get_setup_priority() const { return setup_priority::IO; }
// Run our loop() method very early in the loop, so that we cache read values // Run our loop() method very early in the loop, so that we cache read values
// before other components call our digital_read() method. // before other components call our digital_read() method.
float CH422GComponent::get_loop_priority() const { return 9.0f; } // Just after WIFI float CH422GComponent::get_loop_priority() const { return 9.0f; } // Just after WIFI
void CH422GGPIOPin::setup() { pin_mode(flags_); }
void CH422GGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } void CH422GGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
bool CH422GGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } bool CH422GGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) ^ this->inverted_; }
void CH422GGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } void CH422GGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value ^ this->inverted_); }
std::string CH422GGPIOPin::dump_summary() const { return str_sprintf("EXIO%u via CH422G", pin_); } std::string CH422GGPIOPin::dump_summary() const { return str_sprintf("EXIO%u via CH422G", pin_); }
void CH422GGPIOPin::set_flags(gpio::Flags flags) {
flags_ = flags;
this->parent_->pin_mode(this->pin_, flags);
}
} // namespace ch422g } // namespace ch422g
} // namespace esphome } // namespace esphome

View File

@@ -23,32 +23,30 @@ class CH422GComponent : public Component, public i2c::I2CDevice {
void pin_mode(uint8_t pin, gpio::Flags flags); void pin_mode(uint8_t pin, gpio::Flags flags);
float get_setup_priority() const override; float get_setup_priority() const override;
float get_loop_priority() const override; float get_loop_priority() const override;
void dump_config() override; void dump_config() override;
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
protected: protected:
bool write_reg_(uint8_t reg, uint8_t value);
uint8_t read_reg_(uint8_t reg);
bool set_mode_(uint8_t mode);
bool read_inputs_(); bool read_inputs_();
bool write_outputs_();
bool write_output_(uint8_t value);
/// The mask to write as output state - 1 means HIGH, 0 means LOW /// The mask to write as output state - 1 means HIGH, 0 means LOW
uint8_t state_mask_{0x00}; uint16_t output_bits_{0x00};
/// Flags to check if read previously during this loop /// Flags to check if read previously during this loop
uint8_t pin_read_cache_ = {0x00}; uint8_t pin_read_flags_ = {0x00};
/// Storage for last I2C error seen /// Copy of last read values
esphome::i2c::ErrorCode last_error_; uint8_t input_bits_ = {0x00};
/// Whether we want to override stored values on expander /// Copy of the mode value
bool restore_value_{false}; uint8_t mode_value_{};
}; };
/// Helper class to expose a CH422G pin as an internal input GPIO pin. /// Helper class to expose a CH422G pin as a GPIO pin.
class CH422GGPIOPin : public GPIOPin { class CH422GGPIOPin : public GPIOPin {
public: public:
void setup() override; void setup() override{};
void pin_mode(gpio::Flags flags) override; void pin_mode(gpio::Flags flags) override;
bool digital_read() override; bool digital_read() override;
void digital_write(bool value) override; void digital_write(bool value) override;
@@ -57,13 +55,13 @@ class CH422GGPIOPin : public GPIOPin {
void set_parent(CH422GComponent *parent) { parent_ = parent; } void set_parent(CH422GComponent *parent) { parent_ = parent; }
void set_pin(uint8_t pin) { pin_ = pin; } void set_pin(uint8_t pin) { pin_ = pin; }
void set_inverted(bool inverted) { inverted_ = inverted; } void set_inverted(bool inverted) { inverted_ = inverted; }
void set_flags(gpio::Flags flags) { flags_ = flags; } void set_flags(gpio::Flags flags);
protected: protected:
CH422GComponent *parent_; CH422GComponent *parent_{};
uint8_t pin_; uint8_t pin_{};
bool inverted_; bool inverted_{};
gpio::Flags flags_; gpio::Flags flags_{};
}; };
} // namespace ch422g } // namespace ch422g

View File

@@ -43,7 +43,7 @@ from esphome.const import (
CONF_TEMPERATURE_STEP, CONF_TEMPERATURE_STEP,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_VISUAL, CONF_VISUAL,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity from esphome.cpp_helpers import setup_entity
@@ -408,9 +408,8 @@ async def setup_climate_core_(var, config):
trigger, [(ClimateCall.operator("ref"), "x")], conf trigger, [(ClimateCall.operator("ref"), "x")], conf
) )
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: if web_server_config := config.get(CONF_WEB_SERVER):
web_server_ = await cg.get_variable(webserver_id) await web_server.add_entity_config(var, web_server_config)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_climate(var, config): async def register_climate(var, config):

View File

@@ -17,7 +17,7 @@ from esphome.const import (
CONF_TILT_COMMAND_TOPIC, CONF_TILT_COMMAND_TOPIC,
CONF_TILT_STATE_TOPIC, CONF_TILT_STATE_TOPIC,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER,
DEVICE_CLASS_AWNING, DEVICE_CLASS_AWNING,
DEVICE_CLASS_BLIND, DEVICE_CLASS_BLIND,
DEVICE_CLASS_CURTAIN, DEVICE_CLASS_CURTAIN,
@@ -137,10 +137,6 @@ async def setup_cover_core_(var, config):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf) await automation.build_automation(trigger, [], conf)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None: if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
mqtt_ = cg.new_Pvariable(mqtt_id, var) mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config) await mqtt.register_mqtt_component(mqtt_, config)
@@ -156,6 +152,9 @@ async def setup_cover_core_(var, config):
if (tilt_command_topic := config.get(CONF_TILT_COMMAND_TOPIC)) is not None: if (tilt_command_topic := config.get(CONF_TILT_COMMAND_TOPIC)) is not None:
cg.add(mqtt_.set_custom_tilt_command_topic(tilt_command_topic)) cg.add(mqtt_.set_custom_tilt_command_topic(tilt_command_topic))
if web_server_config := config.get(CONF_WEB_SERVER):
await web_server.add_entity_config(var, web_server_config)
async def register_cover(var, config): async def register_cover(var, config):
if not CORE.has_id(config[CONF_ID]): if not CORE.has_id(config[CONF_ID]):

View File

@@ -147,6 +147,7 @@ void CSE7766Component::parse_data_() {
float power = 0.0f; float power = 0.0f;
if (power_cycle_exceeds_range) { if (power_cycle_exceeds_range) {
// Datasheet: power cycle exceeding range means active power is 0 // Datasheet: power cycle exceeding range means active power is 0
have_power = true;
if (this->power_sensor_ != nullptr) { if (this->power_sensor_ != nullptr) {
this->power_sensor_->publish_state(0.0f); this->power_sensor_->publish_state(0.0f);
} }
@@ -178,6 +179,15 @@ void CSE7766Component::parse_data_() {
if (this->apparent_power_sensor_ != nullptr) { if (this->apparent_power_sensor_ != nullptr) {
this->apparent_power_sensor_->publish_state(apparent_power); this->apparent_power_sensor_->publish_state(apparent_power);
} }
if (have_power && this->reactive_power_sensor_ != nullptr) {
const float reactive_power = apparent_power - power;
if (reactive_power < 0.0f) {
ESP_LOGD(TAG, "Impossible reactive power: %.4f is negative", reactive_power);
this->reactive_power_sensor_->publish_state(0.0f);
} else {
this->reactive_power_sensor_->publish_state(reactive_power);
}
}
if (this->power_factor_sensor_ != nullptr && (have_power || power_cycle_exceeds_range)) { if (this->power_factor_sensor_ != nullptr && (have_power || power_cycle_exceeds_range)) {
float pf = NAN; float pf = NAN;
if (apparent_power > 0) { if (apparent_power > 0) {
@@ -232,8 +242,9 @@ void CSE7766Component::dump_config() {
LOG_SENSOR(" ", "Power", this->power_sensor_); LOG_SENSOR(" ", "Power", this->power_sensor_);
LOG_SENSOR(" ", "Energy", this->energy_sensor_); LOG_SENSOR(" ", "Energy", this->energy_sensor_);
LOG_SENSOR(" ", "Apparent Power", this->apparent_power_sensor_); LOG_SENSOR(" ", "Apparent Power", this->apparent_power_sensor_);
LOG_SENSOR(" ", "Reactive Power", this->reactive_power_sensor_);
LOG_SENSOR(" ", "Power Factor", this->power_factor_sensor_); LOG_SENSOR(" ", "Power Factor", this->power_factor_sensor_);
this->check_uart_settings(4800); this->check_uart_settings(4800, 1, uart::UART_CONFIG_PARITY_EVEN);
} }
} // namespace cse7766 } // namespace cse7766

View File

@@ -16,6 +16,9 @@ class CSE7766Component : public Component, public uart::UARTDevice {
void set_apparent_power_sensor(sensor::Sensor *apparent_power_sensor) { void set_apparent_power_sensor(sensor::Sensor *apparent_power_sensor) {
apparent_power_sensor_ = apparent_power_sensor; apparent_power_sensor_ = apparent_power_sensor;
} }
void set_reactive_power_sensor(sensor::Sensor *reactive_power_sensor) {
reactive_power_sensor_ = reactive_power_sensor;
}
void set_power_factor_sensor(sensor::Sensor *power_factor_sensor) { power_factor_sensor_ = power_factor_sensor; } void set_power_factor_sensor(sensor::Sensor *power_factor_sensor) { power_factor_sensor_ = power_factor_sensor; }
void loop() override; void loop() override;
@@ -35,6 +38,7 @@ class CSE7766Component : public Component, public uart::UARTDevice {
sensor::Sensor *power_sensor_{nullptr}; sensor::Sensor *power_sensor_{nullptr};
sensor::Sensor *energy_sensor_{nullptr}; sensor::Sensor *energy_sensor_{nullptr};
sensor::Sensor *apparent_power_sensor_{nullptr}; sensor::Sensor *apparent_power_sensor_{nullptr};
sensor::Sensor *reactive_power_sensor_{nullptr};
sensor::Sensor *power_factor_sensor_{nullptr}; sensor::Sensor *power_factor_sensor_{nullptr};
uint32_t cf_pulses_total_{0}; uint32_t cf_pulses_total_{0};
uint16_t cf_pulses_last_{0}; uint16_t cf_pulses_last_{0};

View File

@@ -8,18 +8,21 @@ from esphome.const import (
CONF_ID, CONF_ID,
CONF_POWER, CONF_POWER,
CONF_POWER_FACTOR, CONF_POWER_FACTOR,
CONF_REACTIVE_POWER,
CONF_VOLTAGE, CONF_VOLTAGE,
DEVICE_CLASS_APPARENT_POWER, DEVICE_CLASS_APPARENT_POWER,
DEVICE_CLASS_CURRENT, DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY, DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER, DEVICE_CLASS_POWER,
DEVICE_CLASS_POWER_FACTOR, DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_REACTIVE_POWER,
DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING, STATE_CLASS_TOTAL_INCREASING,
UNIT_AMPERE, UNIT_AMPERE,
UNIT_VOLT, UNIT_VOLT,
UNIT_VOLT_AMPS, UNIT_VOLT_AMPS,
UNIT_VOLT_AMPS_REACTIVE,
UNIT_WATT, UNIT_WATT,
UNIT_WATT_HOURS, UNIT_WATT_HOURS,
) )
@@ -62,6 +65,12 @@ CONFIG_SCHEMA = cv.Schema(
device_class=DEVICE_CLASS_APPARENT_POWER, device_class=DEVICE_CLASS_APPARENT_POWER,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
accuracy_decimals=1,
device_class=DEVICE_CLASS_REACTIVE_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema( cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
accuracy_decimals=2, accuracy_decimals=2,
device_class=DEVICE_CLASS_POWER_FACTOR, device_class=DEVICE_CLASS_POWER_FACTOR,
@@ -70,7 +79,7 @@ CONFIG_SCHEMA = cv.Schema(
} }
).extend(uart.UART_DEVICE_SCHEMA) ).extend(uart.UART_DEVICE_SCHEMA)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"cse7766", baud_rate=4800, require_rx=True "cse7766", baud_rate=4800, parity="EVEN", require_rx=True
) )
@@ -94,6 +103,9 @@ async def to_code(config):
if apparent_power_config := config.get(CONF_APPARENT_POWER): if apparent_power_config := config.get(CONF_APPARENT_POWER):
sens = await sensor.new_sensor(apparent_power_config) sens = await sensor.new_sensor(apparent_power_config)
cg.add(var.set_apparent_power_sensor(sens)) cg.add(var.set_apparent_power_sensor(sens))
if reactive_power_config := config.get(CONF_REACTIVE_POWER):
sens = await sensor.new_sensor(reactive_power_config)
cg.add(var.set_reactive_power_sensor(sens))
if power_factor_config := config.get(CONF_POWER_FACTOR): if power_factor_config := config.get(CONF_POWER_FACTOR):
sens = await sensor.new_sensor(power_factor_config) sens = await sensor.new_sensor(power_factor_config)
cg.add(var.set_power_factor_sensor(sens)) cg.add(var.set_power_factor_sensor(sens))

View File

@@ -1,11 +1,10 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins from esphome import pins
import esphome.codegen as cg
from esphome.components import i2c, touchscreen from esphome.components import i2c, touchscreen
from esphome.const import CONF_INTERRUPT_PIN, CONF_ID, CONF_RESET_PIN import esphome.config_validation as cv
from .. import cst816_ns from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN
from .. import cst816_ns
CST816Touchscreen = cst816_ns.class_( CST816Touchscreen = cst816_ns.class_(
"CST816Touchscreen", "CST816Touchscreen",
@@ -14,11 +13,14 @@ CST816Touchscreen = cst816_ns.class_(
) )
CST816ButtonListener = cst816_ns.class_("CST816ButtonListener") CST816ButtonListener = cst816_ns.class_("CST816ButtonListener")
CONF_SKIP_PROBE = "skip_probe"
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
{ {
cv.GenerateID(): cv.declare_id(CST816Touchscreen), cv.GenerateID(): cv.declare_id(CST816Touchscreen),
cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema, cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema,
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_SKIP_PROBE, default=False): cv.boolean,
} }
).extend(i2c.i2c_device_schema(0x15)) ).extend(i2c.i2c_device_schema(0x15))
@@ -28,6 +30,7 @@ async def to_code(config):
await touchscreen.register_touchscreen(var, config) await touchscreen.register_touchscreen(var, config)
await i2c.register_i2c_device(var, config) await i2c.register_i2c_device(var, config)
cg.add(var.set_skip_probe(config[CONF_SKIP_PROBE]))
if interrupt_pin := config.get(CONF_INTERRUPT_PIN): if interrupt_pin := config.get(CONF_INTERRUPT_PIN):
cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin))) cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin)))
if reset_pin := config.get(CONF_RESET_PIN): if reset_pin := config.get(CONF_RESET_PIN):

View File

@@ -8,32 +8,33 @@ void CST816Touchscreen::continue_setup_() {
this->interrupt_pin_->setup(); this->interrupt_pin_->setup();
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE); this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
} }
if (!this->read_byte(REG_CHIP_ID, &this->chip_id_)) { if (this->read_byte(REG_CHIP_ID, &this->chip_id_)) {
switch (this->chip_id_) {
case CST820_CHIP_ID:
case CST826_CHIP_ID:
case CST716_CHIP_ID:
case CST816S_CHIP_ID:
case CST816D_CHIP_ID:
case CST816T_CHIP_ID:
break;
default:
this->mark_failed();
this->status_set_error(str_sprintf("Unknown chip ID 0x%02X", this->chip_id_).c_str());
return;
}
this->write_byte(REG_IRQ_CTL, IRQ_EN_MOTION);
} else if (!this->skip_probe_) {
this->status_set_error("Failed to read chip id");
this->mark_failed(); this->mark_failed();
esph_log_e(TAG, "Failed to read chip id");
return; return;
} }
switch (this->chip_id_) {
case CST820_CHIP_ID:
case CST826_CHIP_ID:
case CST716_CHIP_ID:
case CST816S_CHIP_ID:
case CST816D_CHIP_ID:
case CST816T_CHIP_ID:
break;
default:
this->mark_failed();
esph_log_e(TAG, "Unknown chip ID 0x%02X", this->chip_id_);
return;
}
this->write_byte(REG_IRQ_CTL, IRQ_EN_MOTION);
if (this->x_raw_max_ == this->x_raw_min_) { if (this->x_raw_max_ == this->x_raw_min_) {
this->x_raw_max_ = this->display_->get_native_width(); this->x_raw_max_ = this->display_->get_native_width();
} }
if (this->y_raw_max_ == this->y_raw_min_) { if (this->y_raw_max_ == this->y_raw_min_) {
this->y_raw_max_ = this->display_->get_native_height(); this->y_raw_max_ = this->display_->get_native_height();
} }
esph_log_config(TAG, "CST816 Touchscreen setup complete"); ESP_LOGCONFIG(TAG, "CST816 Touchscreen setup complete");
} }
void CST816Touchscreen::update_button_state_(bool state) { void CST816Touchscreen::update_button_state_(bool state) {
@@ -45,7 +46,7 @@ void CST816Touchscreen::update_button_state_(bool state) {
} }
void CST816Touchscreen::setup() { void CST816Touchscreen::setup() {
esph_log_config(TAG, "Setting up CST816 Touchscreen..."); ESP_LOGCONFIG(TAG, "Setting up CST816 Touchscreen...");
if (this->reset_pin_ != nullptr) { if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup(); this->reset_pin_->setup();
this->reset_pin_->digital_write(true); this->reset_pin_->digital_write(true);
@@ -73,7 +74,7 @@ void CST816Touchscreen::update_touches() {
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]);
esph_log_v(TAG, "Read touch %d/%d", x, y); ESP_LOGV(TAG, "Read touch %d/%d", x, y);
if (x >= this->x_raw_max_) { if (x >= this->x_raw_max_) {
this->update_button_state_(true); this->update_button_state_(true);
} else { } else {

View File

@@ -45,6 +45,7 @@ class CST816Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; } void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; }
void set_skip_probe(bool skip_probe) { this->skip_probe_ = skip_probe; }
protected: protected:
void continue_setup_(); void continue_setup_();
@@ -53,6 +54,7 @@ class CST816Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice
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.
std::vector<CST816ButtonListener *> button_listeners_; std::vector<CST816ButtonListener *> button_listeners_;
bool button_touched_{}; bool button_touched_{};
}; };

View File

@@ -18,7 +18,7 @@ from esphome.const import (
CONF_TIME_ID, CONF_TIME_ID,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_TYPE, CONF_TYPE,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER,
CONF_YEAR, CONF_YEAR,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
@@ -26,7 +26,6 @@ from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@rfdarter", "@jesserockz"] CODEOWNERS = ["@rfdarter", "@jesserockz"]
DEPENDENCIES = ["time"]
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True
@@ -62,20 +61,28 @@ DATETIME_MODES = [
] ]
_DATETIME_SCHEMA = ( def _validate_time_present(config):
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) config = config.copy()
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA) if CONF_ON_TIME in config and CONF_TIME_ID not in config:
.extend( time_id = cv.use_id(time.RealTimeClock)(None)
config[CONF_TIME_ID] = time_id
return config
_DATETIME_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
web_server.WEBSERVER_SORTING_SCHEMA,
cv.MQTT_COMMAND_COMPONENT_SCHEMA,
cv.Schema(
{ {
cv.Optional(CONF_ON_VALUE): automation.validate_automation( cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{ {
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DateTimeStateTrigger), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DateTimeStateTrigger),
} }
), ),
cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock), cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
} }
) ),
) ).add_extra(_validate_time_present)
def date_schema(class_: MockObjClass) -> cv.Schema: def date_schema(class_: MockObjClass) -> cv.Schema:
@@ -131,15 +138,15 @@ async def setup_datetime_core_(var, config):
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None: if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
mqtt_ = cg.new_Pvariable(mqtt_id, var) mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config) await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: if web_server_config := config.get(CONF_WEB_SERVER):
web_server_ = await cg.get_variable(webserver_id) await web_server.add_entity_config(var, web_server_config)
web_server.add_entity_to_sorting_list(web_server_, var, config)
for conf in config.get(CONF_ON_VALUE, []): for conf in config.get(CONF_ON_VALUE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(cg.ESPTime, "x")], conf) await automation.build_automation(trigger, [(cg.ESPTime, "x")], conf)
rtc = await cg.get_variable(config[CONF_TIME_ID]) if CONF_TIME_ID in config:
cg.add(var.set_rtc(rtc)) rtc = await cg.get_variable(config[CONF_TIME_ID])
cg.add(var.set_rtc(rtc))
for conf in config.get(CONF_ON_TIME, []): for conf in config.get(CONF_ON_TIME, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])

View File

@@ -4,8 +4,9 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/entity_base.h" #include "esphome/core/entity_base.h"
#include "esphome/core/time.h" #include "esphome/core/time.h"
#ifdef USE_TIME
#include "esphome/components/time/real_time_clock.h" #include "esphome/components/time/real_time_clock.h"
#endif
namespace esphome { namespace esphome {
namespace datetime { namespace datetime {
@@ -19,23 +20,29 @@ class DateTimeBase : public EntityBase {
void add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); } void add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); }
#ifdef USE_TIME
void set_rtc(time::RealTimeClock *rtc) { this->rtc_ = rtc; } void set_rtc(time::RealTimeClock *rtc) { this->rtc_ = rtc; }
time::RealTimeClock *get_rtc() const { return this->rtc_; } time::RealTimeClock *get_rtc() const { return this->rtc_; }
#endif
protected: protected:
CallbackManager<void()> state_callback_; CallbackManager<void()> state_callback_;
#ifdef USE_TIME
time::RealTimeClock *rtc_; time::RealTimeClock *rtc_;
#endif
bool has_state_{false}; bool has_state_{false};
}; };
#ifdef USE_TIME
class DateTimeStateTrigger : public Trigger<ESPTime> { class DateTimeStateTrigger : public Trigger<ESPTime> {
public: public:
explicit DateTimeStateTrigger(DateTimeBase *parent) { explicit DateTimeStateTrigger(DateTimeBase *parent) {
parent->add_on_state_callback([this, parent]() { this->trigger(parent->state_as_esptime()); }); parent->add_on_state_callback([this, parent]() { this->trigger(parent->state_as_esptime()); });
} }
}; };
#endif
} // namespace datetime } // namespace datetime
} // namespace esphome } // namespace esphome

View File

@@ -192,6 +192,7 @@ void DateTimeEntityRestoreState::apply(DateTimeEntity *time) {
time->publish_state(); time->publish_state();
} }
#ifdef USE_TIME
static const int MAX_TIMESTAMP_DRIFT = 900; // how far can the clock drift before we consider static const int MAX_TIMESTAMP_DRIFT = 900; // how far can the clock drift before we consider
// there has been a drastic time synchronization // there has been a drastic time synchronization
@@ -245,6 +246,7 @@ bool OnDateTimeTrigger::matches_(const ESPTime &time) const {
time.day_of_month == this->parent_->day && time.hour == this->parent_->hour && time.day_of_month == this->parent_->day && time.hour == this->parent_->hour &&
time.minute == this->parent_->minute && time.second == this->parent_->second; time.minute == this->parent_->minute && time.second == this->parent_->second;
} }
#endif
} // namespace datetime } // namespace datetime
} // namespace esphome } // namespace esphome

View File

@@ -134,6 +134,7 @@ template<typename... Ts> class DateTimeSetAction : public Action<Ts...>, public
} }
}; };
#ifdef USE_TIME
class OnDateTimeTrigger : public Trigger<>, public Component, public Parented<DateTimeEntity> { class OnDateTimeTrigger : public Trigger<>, public Component, public Parented<DateTimeEntity> {
public: public:
void loop() override; void loop() override;
@@ -143,6 +144,7 @@ class OnDateTimeTrigger : public Trigger<>, public Component, public Parented<Da
optional<ESPTime> last_check_; optional<ESPTime> last_check_;
}; };
#endif
} // namespace datetime } // namespace datetime
} // namespace esphome } // namespace esphome

View File

@@ -94,6 +94,7 @@ void TimeEntityRestoreState::apply(TimeEntity *time) {
time->publish_state(); time->publish_state();
} }
#ifdef USE_TIME
static const int MAX_TIMESTAMP_DRIFT = 900; // how far can the clock drift before we consider static const int MAX_TIMESTAMP_DRIFT = 900; // how far can the clock drift before we consider
// there has been a drastic time synchronization // there has been a drastic time synchronization
@@ -145,6 +146,7 @@ bool OnTimeTrigger::matches_(const ESPTime &time) const {
return time.is_valid() && time.hour == this->parent_->hour && time.minute == this->parent_->minute && return time.is_valid() && time.hour == this->parent_->hour && time.minute == this->parent_->minute &&
time.second == this->parent_->second; time.second == this->parent_->second;
} }
#endif
} // namespace datetime } // namespace datetime
} // namespace esphome } // namespace esphome

View File

@@ -113,6 +113,7 @@ template<typename... Ts> class TimeSetAction : public Action<Ts...>, public Pare
} }
}; };
#ifdef USE_TIME
class OnTimeTrigger : public Trigger<>, public Component, public Parented<TimeEntity> { class OnTimeTrigger : public Trigger<>, public Component, public Parented<TimeEntity> {
public: public:
void loop() override; void loop() override;
@@ -122,6 +123,7 @@ class OnTimeTrigger : public Trigger<>, public Component, public Parented<TimeEn
optional<ESPTime> last_check_; optional<ESPTime> last_check_;
}; };
#endif
} // namespace datetime } // namespace datetime
} // namespace esphome } // namespace esphome

View File

@@ -13,6 +13,7 @@ from esphome.const import (
CONF_COMPONENTS, CONF_COMPONENTS,
CONF_ESPHOME, CONF_ESPHOME,
CONF_FRAMEWORK, CONF_FRAMEWORK,
CONF_IGNORE_EFUSE_CUSTOM_MAC,
CONF_IGNORE_EFUSE_MAC_CRC, CONF_IGNORE_EFUSE_MAC_CRC,
CONF_NAME, CONF_NAME,
CONF_PATH, CONF_PATH,
@@ -52,6 +53,7 @@ from .const import ( # noqa
KEY_SDKCONFIG_OPTIONS, KEY_SDKCONFIG_OPTIONS,
KEY_SUBMODULES, KEY_SUBMODULES,
KEY_VARIANT, KEY_VARIANT,
VARIANT_ESP32,
VARIANT_FRIENDLY, VARIANT_FRIENDLY,
VARIANTS, VARIANTS,
) )
@@ -239,7 +241,7 @@ ARDUINO_PLATFORM_VERSION = cv.Version(5, 4, 0)
# The default/recommended esp-idf framework version # The default/recommended esp-idf framework version
# - https://github.com/espressif/esp-idf/releases # - https://github.com/espressif/esp-idf/releases
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf # - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 7) RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 8)
# The platformio/espressif32 version to use for esp-idf frameworks # The platformio/espressif32 version to use for esp-idf frameworks
# - https://github.com/platformio/platform-espressif32/releases # - https://github.com/platformio/platform-espressif32/releases
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 # - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
@@ -375,6 +377,15 @@ def final_validate(config):
f"Please specify {CONF_FLASH_SIZE} within esp32 configuration only" f"Please specify {CONF_FLASH_SIZE} within esp32 configuration only"
) )
if (
config[CONF_VARIANT] != VARIANT_ESP32
and CONF_ADVANCED in (conf_fw := config[CONF_FRAMEWORK])
and CONF_IGNORE_EFUSE_MAC_CRC in conf_fw[CONF_ADVANCED]
):
raise cv.Invalid(
f"{CONF_IGNORE_EFUSE_MAC_CRC} is not supported on {config[CONF_VARIANT]}"
)
return config return config
@@ -384,6 +395,13 @@ ARDUINO_FRAMEWORK_SCHEMA = cv.All(
cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
cv.Optional(CONF_SOURCE): cv.string_strict, cv.Optional(CONF_SOURCE): cv.string_strict,
cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version, cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
cv.Optional(CONF_ADVANCED, default={}): cv.Schema(
{
cv.Optional(
CONF_IGNORE_EFUSE_CUSTOM_MAC, default=False
): cv.boolean,
}
),
} }
), ),
_arduino_check_versions, _arduino_check_versions,
@@ -401,7 +419,10 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
}, },
cv.Optional(CONF_ADVANCED, default={}): cv.Schema( cv.Optional(CONF_ADVANCED, default={}): cv.Schema(
{ {
cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC, default=False): cv.boolean, cv.Optional(
CONF_IGNORE_EFUSE_CUSTOM_MAC, default=False
): cv.boolean,
cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC): cv.boolean,
} }
), ),
cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list( cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list(
@@ -480,6 +501,9 @@ async def to_code(config):
conf = config[CONF_FRAMEWORK] conf = config[CONF_FRAMEWORK]
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION]) cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
if CONF_ADVANCED in conf and conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_CUSTOM_MAC]:
cg.add_define("USE_ESP32_IGNORE_EFUSE_CUSTOM_MAC")
add_extra_script( add_extra_script(
"post", "post",
"post_build.py", "post_build.py",
@@ -526,8 +550,8 @@ async def to_code(config):
for name, value in conf[CONF_SDKCONFIG_OPTIONS].items(): for name, value in conf[CONF_SDKCONFIG_OPTIONS].items():
add_idf_sdkconfig_option(name, RawSdkconfigValue(value)) add_idf_sdkconfig_option(name, RawSdkconfigValue(value))
if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_MAC_CRC]: if conf[CONF_ADVANCED].get(CONF_IGNORE_EFUSE_MAC_CRC):
cg.add_define("USE_ESP32_IGNORE_EFUSE_MAC_CRC") add_idf_sdkconfig_option("CONFIG_ESP_MAC_IGNORE_MAC_CRC_ERROR", True)
if (framework_ver.major, framework_ver.minor) >= (4, 4): if (framework_ver.major, framework_ver.minor) >= (4, 4):
add_idf_sdkconfig_option( add_idf_sdkconfig_option(
"CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE", False "CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE", False

View File

@@ -31,6 +31,13 @@ ESPBTUUID ESPBTUUID::from_raw(const uint8_t *data) {
memcpy(ret.uuid_.uuid.uuid128, data, ESP_UUID_LEN_128); memcpy(ret.uuid_.uuid.uuid128, data, ESP_UUID_LEN_128);
return ret; return ret;
} }
ESPBTUUID ESPBTUUID::from_raw_reversed(const uint8_t *data) {
ESPBTUUID ret;
ret.uuid_.len = ESP_UUID_LEN_128;
for (int i = 0; i < ESP_UUID_LEN_128; i++)
ret.uuid_.uuid.uuid128[ESP_UUID_LEN_128 - 1 - i] = data[i];
return ret;
}
ESPBTUUID ESPBTUUID::from_raw(const std::string &data) { ESPBTUUID ESPBTUUID::from_raw(const std::string &data) {
ESPBTUUID ret; ESPBTUUID ret;
if (data.length() == 4) { if (data.length() == 4) {

View File

@@ -20,6 +20,7 @@ class ESPBTUUID {
static ESPBTUUID from_uint32(uint32_t uuid); static ESPBTUUID from_uint32(uint32_t uuid);
static ESPBTUUID from_raw(const uint8_t *data); static ESPBTUUID from_raw(const uint8_t *data);
static ESPBTUUID from_raw_reversed(const uint8_t *data);
static ESPBTUUID from_raw(const std::string &data); static ESPBTUUID from_raw(const std::string &data);

View File

@@ -462,14 +462,16 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e
ESP_LOGVV(TAG, " Service UUID: %s", uuid.to_string().c_str()); ESP_LOGVV(TAG, " Service UUID: %s", uuid.to_string().c_str());
} }
for (auto &data : this->manufacturer_datas_) { for (auto &data : this->manufacturer_datas_) {
ESP_LOGVV(TAG, " Manufacturer data: %s", format_hex_pretty(data.data).c_str()); auto ibeacon = ESPBLEiBeacon::from_manufacturer_data(data);
if (this->get_ibeacon().has_value()) { if (ibeacon.has_value()) {
auto ibeacon = this->get_ibeacon().value(); ESP_LOGVV(TAG, " Manufacturer iBeacon:");
ESP_LOGVV(TAG, " iBeacon data:"); ESP_LOGVV(TAG, " UUID: %s", ibeacon.value().get_uuid().to_string().c_str());
ESP_LOGVV(TAG, " UUID: %s", ibeacon.get_uuid().to_string().c_str()); ESP_LOGVV(TAG, " Major: %u", ibeacon.value().get_major());
ESP_LOGVV(TAG, " Major: %u", ibeacon.get_major()); ESP_LOGVV(TAG, " Minor: %u", ibeacon.value().get_minor());
ESP_LOGVV(TAG, " Minor: %u", ibeacon.get_minor()); ESP_LOGVV(TAG, " TXPower: %d", ibeacon.value().get_signal_power());
ESP_LOGVV(TAG, " TXPower: %d", ibeacon.get_signal_power()); } else {
ESP_LOGVV(TAG, " Manufacturer ID: %s, data: %s", data.uuid.to_string().c_str(),
format_hex_pretty(data.data).c_str());
} }
} }
for (auto &data : this->service_datas_) { for (auto &data : this->service_datas_) {
@@ -478,7 +480,7 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e
ESP_LOGVV(TAG, " Data: %s", format_hex_pretty(data.data).c_str()); ESP_LOGVV(TAG, " Data: %s", format_hex_pretty(data.data).c_str());
} }
ESP_LOGVV(TAG, "Adv data: %s", format_hex_pretty(param.ble_adv, param.adv_data_len + param.scan_rsp_len).c_str()); ESP_LOGVV(TAG, " Adv data: %s", format_hex_pretty(param.ble_adv, param.adv_data_len + param.scan_rsp_len).c_str());
#endif #endif
} }
void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &param) { void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &param) {

View File

@@ -44,10 +44,10 @@ class ESPBLEiBeacon {
ESPBLEiBeacon(const uint8_t *data); ESPBLEiBeacon(const uint8_t *data);
static optional<ESPBLEiBeacon> from_manufacturer_data(const ServiceData &data); static optional<ESPBLEiBeacon> from_manufacturer_data(const ServiceData &data);
uint16_t get_major() { return ((this->beacon_data_.major & 0xFF) << 8) | (this->beacon_data_.major >> 8); } uint16_t get_major() { return byteswap(this->beacon_data_.major); }
uint16_t get_minor() { return ((this->beacon_data_.minor & 0xFF) << 8) | (this->beacon_data_.minor >> 8); } uint16_t get_minor() { return byteswap(this->beacon_data_.minor); }
int8_t get_signal_power() { return this->beacon_data_.signal_power; } int8_t get_signal_power() { return this->beacon_data_.signal_power; }
ESPBTUUID get_uuid() { return ESPBTUUID::from_raw(this->beacon_data_.proximity_uuid); } ESPBTUUID get_uuid() { return ESPBTUUID::from_raw_reversed(this->beacon_data_.proximity_uuid); }
protected: protected:
struct { struct {

View File

@@ -1,7 +1,8 @@
from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import binary_sensor, esp32_ble_server, output from esphome.components import binary_sensor, esp32_ble_server, output
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_ID from esphome.const import CONF_ID, CONF_ON_STATE, CONF_TRIGGER_ID
AUTO_LOAD = ["esp32_ble_server"] AUTO_LOAD = ["esp32_ble_server"]
CODEOWNERS = ["@jesserockz"] CODEOWNERS = ["@jesserockz"]
@@ -11,13 +12,36 @@ CONF_AUTHORIZED_DURATION = "authorized_duration"
CONF_AUTHORIZER = "authorizer" CONF_AUTHORIZER = "authorizer"
CONF_BLE_SERVER_ID = "ble_server_id" CONF_BLE_SERVER_ID = "ble_server_id"
CONF_IDENTIFY_DURATION = "identify_duration" CONF_IDENTIFY_DURATION = "identify_duration"
CONF_ON_PROVISIONED = "on_provisioned"
CONF_ON_PROVISIONING = "on_provisioning"
CONF_ON_START = "on_start"
CONF_ON_STOP = "on_stop"
CONF_STATUS_INDICATOR = "status_indicator" CONF_STATUS_INDICATOR = "status_indicator"
CONF_WIFI_TIMEOUT = "wifi_timeout" CONF_WIFI_TIMEOUT = "wifi_timeout"
improv_ns = cg.esphome_ns.namespace("improv")
Error = improv_ns.enum("Error")
State = improv_ns.enum("State")
esp32_improv_ns = cg.esphome_ns.namespace("esp32_improv") esp32_improv_ns = cg.esphome_ns.namespace("esp32_improv")
ESP32ImprovComponent = esp32_improv_ns.class_( ESP32ImprovComponent = esp32_improv_ns.class_(
"ESP32ImprovComponent", cg.Component, esp32_ble_server.BLEServiceComponent "ESP32ImprovComponent", cg.Component, esp32_ble_server.BLEServiceComponent
) )
ESP32ImprovProvisionedTrigger = esp32_improv_ns.class_(
"ESP32ImprovProvisionedTrigger", automation.Trigger.template()
)
ESP32ImprovProvisioningTrigger = esp32_improv_ns.class_(
"ESP32ImprovProvisioningTrigger", automation.Trigger.template()
)
ESP32ImprovStartTrigger = esp32_improv_ns.class_(
"ESP32ImprovStartTrigger", automation.Trigger.template()
)
ESP32ImprovStateTrigger = esp32_improv_ns.class_(
"ESP32ImprovStateTrigger", automation.Trigger.template()
)
ESP32ImprovStoppedTrigger = esp32_improv_ns.class_(
"ESP32ImprovStoppedTrigger", automation.Trigger.template()
)
CONFIG_SCHEMA = cv.Schema( CONFIG_SCHEMA = cv.Schema(
@@ -37,6 +61,37 @@ CONFIG_SCHEMA = cv.Schema(
cv.Optional( cv.Optional(
CONF_WIFI_TIMEOUT, default="1min" CONF_WIFI_TIMEOUT, default="1min"
): cv.positive_time_period_milliseconds, ): cv.positive_time_period_milliseconds,
cv.Optional(CONF_ON_PROVISIONED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
ESP32ImprovProvisionedTrigger
),
}
),
cv.Optional(CONF_ON_PROVISIONING): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
ESP32ImprovProvisioningTrigger
),
}
),
cv.Optional(CONF_ON_START): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ESP32ImprovStartTrigger),
}
),
cv.Optional(CONF_ON_STATE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ESP32ImprovStateTrigger),
}
),
cv.Optional(CONF_ON_STOP): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
ESP32ImprovStoppedTrigger
),
}
),
} }
).extend(cv.COMPONENT_SCHEMA) ).extend(cv.COMPONENT_SCHEMA)
@@ -63,3 +118,29 @@ async def to_code(config):
if CONF_STATUS_INDICATOR in config: if CONF_STATUS_INDICATOR in config:
status_indicator = await cg.get_variable(config[CONF_STATUS_INDICATOR]) status_indicator = await cg.get_variable(config[CONF_STATUS_INDICATOR])
cg.add(var.set_status_indicator(status_indicator)) cg.add(var.set_status_indicator(status_indicator))
use_state_callback = False
for conf in config.get(CONF_ON_PROVISIONED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
use_state_callback = True
for conf in config.get(CONF_ON_PROVISIONING, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
use_state_callback = True
for conf in config.get(CONF_ON_START, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
use_state_callback = True
for conf in config.get(CONF_ON_STATE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger, [(State, "state"), (Error, "error")], conf
)
use_state_callback = True
for conf in config.get(CONF_ON_STOP, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
use_state_callback = True
if use_state_callback:
cg.add_define("USE_ESP32_IMPROV_STATE_CALLBACK")

View File

@@ -0,0 +1,72 @@
#pragma once
#ifdef USE_ESP32
#ifdef USE_ESP32_IMPROV_STATE_CALLBACK
#include "esp32_improv_component.h"
#include "esphome/core/automation.h"
#include <improv.h>
namespace esphome {
namespace esp32_improv {
class ESP32ImprovProvisionedTrigger : public Trigger<> {
public:
explicit ESP32ImprovProvisionedTrigger(ESP32ImprovComponent *parent) {
parent->add_on_state_callback([this, parent](improv::State state, improv::Error error) {
if (state == improv::STATE_PROVISIONED && !parent->is_failed()) {
trigger();
}
});
}
};
class ESP32ImprovProvisioningTrigger : public Trigger<> {
public:
explicit ESP32ImprovProvisioningTrigger(ESP32ImprovComponent *parent) {
parent->add_on_state_callback([this, parent](improv::State state, improv::Error error) {
if (state == improv::STATE_PROVISIONING && !parent->is_failed()) {
trigger();
}
});
}
};
class ESP32ImprovStartTrigger : public Trigger<> {
public:
explicit ESP32ImprovStartTrigger(ESP32ImprovComponent *parent) {
parent->add_on_state_callback([this, parent](improv::State state, improv::Error error) {
if ((state == improv::STATE_AUTHORIZED || state == improv::STATE_AWAITING_AUTHORIZATION) &&
!parent->is_failed()) {
trigger();
}
});
}
};
class ESP32ImprovStateTrigger : public Trigger<improv::State, improv::Error> {
public:
explicit ESP32ImprovStateTrigger(ESP32ImprovComponent *parent) {
parent->add_on_state_callback([this, parent](improv::State state, improv::Error error) {
if (!parent->is_failed()) {
trigger(state, error);
}
});
}
};
class ESP32ImprovStoppedTrigger : public Trigger<> {
public:
explicit ESP32ImprovStoppedTrigger(ESP32ImprovComponent *parent) {
parent->add_on_state_callback([this, parent](improv::State state, improv::Error error) {
if (state == improv::STATE_STOPPED && !parent->is_failed()) {
trigger();
}
});
}
};
} // namespace esp32_improv
} // namespace esphome
#endif
#endif

View File

@@ -68,7 +68,12 @@ void ESP32ImprovComponent::setup_characteristics() {
void ESP32ImprovComponent::loop() { void ESP32ImprovComponent::loop() {
if (!global_ble_server->is_running()) { if (!global_ble_server->is_running()) {
this->state_ = improv::STATE_STOPPED; if (this->state_ != improv::STATE_STOPPED) {
this->state_ = improv::STATE_STOPPED;
#ifdef USE_ESP32_IMPROV_STATE_CALLBACK
this->state_callback_.call(this->state_, this->error_state_);
#endif
}
this->incoming_data_.clear(); this->incoming_data_.clear();
return; return;
} }
@@ -217,6 +222,9 @@ void ESP32ImprovComponent::set_state_(improv::State state) {
service_data[7] = 0x00; // Reserved service_data[7] = 0x00; // Reserved
esp32_ble::global_ble->advertising_set_service_data(service_data); esp32_ble::global_ble->advertising_set_service_data(service_data);
#ifdef USE_ESP32_IMPROV_STATE_CALLBACK
this->state_callback_.call(this->state_, this->error_state_);
#endif
} }
void ESP32ImprovComponent::set_error_(improv::Error error) { void ESP32ImprovComponent::set_error_(improv::Error error) {
@@ -270,7 +278,7 @@ void ESP32ImprovComponent::dump_config() {
void ESP32ImprovComponent::process_incoming_data_() { void ESP32ImprovComponent::process_incoming_data_() {
uint8_t length = this->incoming_data_[1]; uint8_t length = this->incoming_data_[1];
ESP_LOGD(TAG, "Processing bytes - %s", format_hex_pretty(this->incoming_data_).c_str()); ESP_LOGV(TAG, "Processing bytes - %s", format_hex_pretty(this->incoming_data_).c_str());
if (this->incoming_data_.size() - 3 == length) { if (this->incoming_data_.size() - 3 == length) {
this->set_error_(improv::ERROR_NONE); this->set_error_(improv::ERROR_NONE);
improv::ImprovCommand command = improv::parse_improv_data(this->incoming_data_); improv::ImprovCommand command = improv::parse_improv_data(this->incoming_data_);
@@ -295,7 +303,7 @@ void ESP32ImprovComponent::process_incoming_data_() {
wifi::global_wifi_component->set_sta(sta); wifi::global_wifi_component->set_sta(sta);
wifi::global_wifi_component->start_connecting(sta, false); wifi::global_wifi_component->start_connecting(sta, false);
this->set_state_(improv::STATE_PROVISIONING); this->set_state_(improv::STATE_PROVISIONING);
ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(), ESP_LOGD(TAG, "Received Improv Wi-Fi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
command.password.c_str()); command.password.c_str());
auto f = std::bind(&ESP32ImprovComponent::on_wifi_connect_timeout_, this); auto f = std::bind(&ESP32ImprovComponent::on_wifi_connect_timeout_, this);
@@ -313,7 +321,7 @@ void ESP32ImprovComponent::process_incoming_data_() {
this->incoming_data_.clear(); this->incoming_data_.clear();
} }
} else if (this->incoming_data_.size() - 2 > length) { } else if (this->incoming_data_.size() - 2 > length) {
ESP_LOGV(TAG, "Too much data came in, or malformed resetting buffer..."); ESP_LOGV(TAG, "Too much data received or data malformed; resetting buffer...");
this->incoming_data_.clear(); this->incoming_data_.clear();
} else { } else {
ESP_LOGV(TAG, "Waiting for split data packets..."); ESP_LOGV(TAG, "Waiting for split data packets...");
@@ -327,7 +335,7 @@ void ESP32ImprovComponent::on_wifi_connect_timeout_() {
if (this->authorizer_ != nullptr) if (this->authorizer_ != nullptr)
this->authorized_start_ = millis(); this->authorized_start_ = millis();
#endif #endif
ESP_LOGW(TAG, "Timed out trying to connect to given WiFi network"); ESP_LOGW(TAG, "Timed out while connecting to Wi-Fi network");
wifi::global_wifi_component->clear_sta(); wifi::global_wifi_component->clear_sta();
} }

View File

@@ -9,6 +9,10 @@
#include "esphome/components/esp32_ble_server/ble_server.h" #include "esphome/components/esp32_ble_server/ble_server.h"
#include "esphome/components/wifi/wifi_component.h" #include "esphome/components/wifi/wifi_component.h"
#ifdef USE_ESP32_IMPROV_STATE_CALLBACK
#include "esphome/core/automation.h"
#endif
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h" #include "esphome/components/binary_sensor/binary_sensor.h"
#endif #endif
@@ -42,6 +46,11 @@ class ESP32ImprovComponent : public Component, public BLEServiceComponent {
void stop() override; void stop() override;
bool is_active() const { return this->state_ != improv::STATE_STOPPED; } bool is_active() const { return this->state_ != improv::STATE_STOPPED; }
#ifdef USE_ESP32_IMPROV_STATE_CALLBACK
void add_on_state_callback(std::function<void(improv::State, improv::Error)> &&callback) {
this->state_callback_.add(std::move(callback));
}
#endif
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
void set_authorizer(binary_sensor::BinarySensor *authorizer) { this->authorizer_ = authorizer; } void set_authorizer(binary_sensor::BinarySensor *authorizer) { this->authorizer_ = authorizer; }
#endif #endif
@@ -54,6 +63,9 @@ class ESP32ImprovComponent : public Component, public BLEServiceComponent {
void set_wifi_timeout(uint32_t wifi_timeout) { this->wifi_timeout_ = wifi_timeout; } void set_wifi_timeout(uint32_t wifi_timeout) { this->wifi_timeout_ = wifi_timeout; }
uint32_t get_wifi_timeout() const { return this->wifi_timeout_; } uint32_t get_wifi_timeout() const { return this->wifi_timeout_; }
improv::State get_improv_state() const { return this->state_; }
improv::Error get_improv_error_state() const { return this->error_state_; }
protected: protected:
bool should_start_{false}; bool should_start_{false};
bool setup_complete_{false}; bool setup_complete_{false};
@@ -84,6 +96,9 @@ class ESP32ImprovComponent : public Component, public BLEServiceComponent {
improv::State state_{improv::STATE_STOPPED}; improv::State state_{improv::STATE_STOPPED};
improv::Error error_state_{improv::ERROR_NONE}; improv::Error error_state_{improv::ERROR_NONE};
#ifdef USE_ESP32_IMPROV_STATE_CALLBACK
CallbackManager<void(improv::State, improv::Error)> state_callback_{};
#endif
bool status_indicator_state_{false}; bool status_indicator_state_{false};
void set_status_indicator_state_(bool state); void set_status_indicator_state_(bool state);

View File

@@ -59,6 +59,7 @@ ETHERNET_TYPES = {
"KSZ8081": EthernetType.ETHERNET_TYPE_KSZ8081, "KSZ8081": EthernetType.ETHERNET_TYPE_KSZ8081,
"KSZ8081RNA": EthernetType.ETHERNET_TYPE_KSZ8081RNA, "KSZ8081RNA": EthernetType.ETHERNET_TYPE_KSZ8081RNA,
"W5500": EthernetType.ETHERNET_TYPE_W5500, "W5500": EthernetType.ETHERNET_TYPE_W5500,
"OPENETH": EthernetType.ETHERNET_TYPE_OPENETH,
} }
SPI_ETHERNET_TYPES = ["W5500"] SPI_ETHERNET_TYPES = ["W5500"]
@@ -171,6 +172,7 @@ CONFIG_SCHEMA = cv.All(
"KSZ8081": RMII_SCHEMA, "KSZ8081": RMII_SCHEMA,
"KSZ8081RNA": RMII_SCHEMA, "KSZ8081RNA": RMII_SCHEMA,
"W5500": SPI_SCHEMA, "W5500": SPI_SCHEMA,
"OPENETH": BASE_SCHEMA,
}, },
upper=True, upper=True,
), ),
@@ -240,6 +242,9 @@ async def to_code(config):
if CORE.using_esp_idf: if CORE.using_esp_idf:
add_idf_sdkconfig_option("CONFIG_ETH_USE_SPI_ETHERNET", True) add_idf_sdkconfig_option("CONFIG_ETH_USE_SPI_ETHERNET", True)
add_idf_sdkconfig_option("CONFIG_ETH_SPI_ETHERNET_W5500", True) add_idf_sdkconfig_option("CONFIG_ETH_SPI_ETHERNET_W5500", True)
elif config[CONF_TYPE] == "OPENETH":
cg.add_define("USE_ETHERNET_OPENETH")
add_idf_sdkconfig_option("CONFIG_ETH_USE_OPENETH", True)
else: else:
cg.add(var.set_phy_addr(config[CONF_PHY_ADDR])) cg.add(var.set_phy_addr(config[CONF_PHY_ADDR]))
cg.add(var.set_mdc_pin(config[CONF_MDC_PIN])) cg.add(var.set_mdc_pin(config[CONF_MDC_PIN]))

View File

@@ -120,6 +120,8 @@ void EthernetComponent::setup() {
phy_config.reset_gpio_num = this->reset_pin_; phy_config.reset_gpio_num = this->reset_pin_;
esp_eth_mac_t *mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config); esp_eth_mac_t *mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config);
#elif defined(USE_ETHERNET_OPENETH)
esp_eth_mac_t *mac = esp_eth_mac_new_openeth(&mac_config);
#else #else
phy_config.phy_addr = this->phy_addr_; phy_config.phy_addr = this->phy_addr_;
phy_config.reset_gpio_num = this->power_pin_; phy_config.reset_gpio_num = this->power_pin_;
@@ -143,6 +145,13 @@ void EthernetComponent::setup() {
#endif #endif
switch (this->type_) { switch (this->type_) {
#ifdef USE_ETHERNET_OPENETH
case ETHERNET_TYPE_OPENETH: {
phy_config.autonego_timeout_ms = 1000;
this->phy_ = esp_eth_phy_new_dp83848(&phy_config);
break;
}
#endif
#if CONFIG_ETH_USE_ESP32_EMAC #if CONFIG_ETH_USE_ESP32_EMAC
case ETHERNET_TYPE_LAN8720: { case ETHERNET_TYPE_LAN8720: {
this->phy_ = esp_eth_phy_new_lan87xx(&phy_config); this->phy_ = esp_eth_phy_new_lan87xx(&phy_config);
@@ -302,6 +311,10 @@ void EthernetComponent::dump_config() {
eth_type = "W5500"; eth_type = "W5500";
break; break;
case ETHERNET_TYPE_OPENETH:
eth_type = "OPENETH";
break;
default: default:
eth_type = "Unknown"; eth_type = "Unknown";
break; break;

View File

@@ -25,6 +25,7 @@ enum EthernetType {
ETHERNET_TYPE_KSZ8081, ETHERNET_TYPE_KSZ8081,
ETHERNET_TYPE_KSZ8081RNA, ETHERNET_TYPE_KSZ8081RNA,
ETHERNET_TYPE_W5500, ETHERNET_TYPE_W5500,
ETHERNET_TYPE_OPENETH,
}; };
struct ManualIP { struct ManualIP {

View File

@@ -1,6 +1,6 @@
from esphome import automation from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import mqtt from esphome.components import mqtt, web_server
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_DEVICE_CLASS, CONF_DEVICE_CLASS,
@@ -11,6 +11,7 @@ from esphome.const import (
CONF_MQTT_ID, CONF_MQTT_ID,
CONF_ON_EVENT, CONF_ON_EVENT,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_WEB_SERVER,
DEVICE_CLASS_BUTTON, DEVICE_CLASS_BUTTON,
DEVICE_CLASS_DOORBELL, DEVICE_CLASS_DOORBELL,
DEVICE_CLASS_EMPTY, DEVICE_CLASS_EMPTY,
@@ -40,17 +41,21 @@ EventTrigger = event_ns.class_("EventTrigger", automation.Trigger.template())
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
EVENT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( EVENT_SCHEMA = (
{ cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTEventComponent), .extend(cv.MQTT_COMPONENT_SCHEMA)
cv.GenerateID(): cv.declare_id(Event), .extend(
cv.Optional(CONF_DEVICE_CLASS): validate_device_class, {
cv.Optional(CONF_ON_EVENT): automation.validate_automation( cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTEventComponent),
{ cv.GenerateID(): cv.declare_id(Event),
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(EventTrigger), cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
} cv.Optional(CONF_ON_EVENT): automation.validate_automation(
), {
} cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(EventTrigger),
}
),
}
)
) )
_UNDEF = object() _UNDEF = object()
@@ -97,6 +102,9 @@ async def setup_event_core_(var, config, *, event_types: list[str]):
mqtt_ = cg.new_Pvariable(mqtt_id, var) mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config) await mqtt.register_mqtt_component(mqtt_, config)
if web_server_config := config.get(CONF_WEB_SERVER):
await web_server.add_entity_config(var, web_server_config)
async def register_event(var, config, *, event_types: list[str]): async def register_event(var, config, *, event_types: list[str]):
if not CORE.has_id(config[CONF_ID]): if not CORE.has_id(config[CONF_ID]):

View File

@@ -25,7 +25,7 @@ from esphome.const import (
CONF_SPEED_LEVEL_STATE_TOPIC, CONF_SPEED_LEVEL_STATE_TOPIC,
CONF_SPEED_STATE_TOPIC, CONF_SPEED_STATE_TOPIC,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity from esphome.cpp_helpers import setup_entity
@@ -218,9 +218,8 @@ async def setup_fan_core_(var, config):
if (speed_command_topic := config.get(CONF_SPEED_COMMAND_TOPIC)) is not None: if (speed_command_topic := config.get(CONF_SPEED_COMMAND_TOPIC)) is not None:
cg.add(mqtt_.set_custom_speed_command_topic(speed_command_topic)) cg.add(mqtt_.set_custom_speed_command_topic(speed_command_topic))
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: if web_server_config := config.get(CONF_WEB_SERVER):
web_server_ = await cg.get_variable(webserver_id) await web_server.add_entity_config(var, web_server_config)
web_server.add_entity_to_sorting_list(web_server_, var, config)
for conf in config.get(CONF_ON_STATE, []): for conf in config.get(CONF_ON_STATE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)

View File

@@ -0,0 +1,67 @@
#include "gp2y1010au0f.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include <cinttypes>
namespace esphome {
namespace gp2y1010au0f {
static const char *const TAG = "gp2y1010au0f";
static const float MIN_VOLTAGE = 0.0f;
static const float MAX_VOLTAGE = 4.0f;
void GP2Y1010AU0FSensor::dump_config() {
LOG_SENSOR("", "Sharp GP2Y1010AU0F PM2.5 Sensor", this);
ESP_LOGCONFIG(TAG, " Sampling duration: %" PRId32 " ms", this->sample_duration_);
ESP_LOGCONFIG(TAG, " ADC voltage multiplier: %.3f", this->voltage_multiplier_);
LOG_UPDATE_INTERVAL(this);
}
void GP2Y1010AU0FSensor::update() {
is_sampling_ = true;
this->set_timeout("read", this->sample_duration_, [this]() {
this->is_sampling_ = false;
if (this->num_samples_ == 0)
return;
float mean = this->sample_sum_ / float(this->num_samples_);
ESP_LOGD(TAG, "ADC read voltage: %.3f V (mean from %" PRId32 " samples)", mean, this->num_samples_);
// PM2.5 calculation
// ref: https://www.howmuchsnow.com/arduino/airquality/
int16_t pm_2_5_value = 170 * mean;
this->publish_state(pm_2_5_value);
});
// reset readings
this->num_samples_ = 0;
this->sample_sum_ = 0.0f;
}
void GP2Y1010AU0FSensor::loop() {
if (!this->is_sampling_)
return;
// enable the internal IR LED
this->led_output_->turn_on();
// wait for the sensor to stabilize
delayMicroseconds(this->sample_wait_before_);
// perform a single sample
float read_voltage = this->source_->sample();
// disable the internal IR LED
this->led_output_->turn_off();
if (std::isnan(read_voltage))
return;
read_voltage = read_voltage * this->voltage_multiplier_ - this->voltage_offset_;
if (read_voltage < MIN_VOLTAGE || read_voltage > MAX_VOLTAGE)
return;
this->num_samples_++;
this->sample_sum_ += read_voltage;
}
} // namespace gp2y1010au0f
} // namespace esphome

View File

@@ -0,0 +1,52 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/voltage_sampler/voltage_sampler.h"
#include "esphome/components/output/binary_output.h"
namespace esphome {
namespace gp2y1010au0f {
class GP2Y1010AU0FSensor : public sensor::Sensor, public PollingComponent {
public:
void update() override;
void loop() override;
void dump_config() override;
float get_setup_priority() const override {
// after the base sensor has been initialized
return setup_priority::DATA - 1.0f;
}
void set_adc_source(voltage_sampler::VoltageSampler *source) { source_ = source; }
void set_voltage_refs(float offset, float multiplier) {
this->voltage_offset_ = offset;
this->voltage_multiplier_ = multiplier;
}
void set_led_output(output::BinaryOutput *output) { led_output_ = output; }
protected:
// duration in ms of the sampling phase
uint32_t sample_duration_ = 100;
// duration in us of the wait before sampling
// ref: https://global.sharp/products/device/lineup/data/pdf/datasheet/gp2y1010au_appl_e.pdf
uint32_t sample_wait_before_ = 280;
// duration in us of the wait after sampling
// it seems no need to delay on purpose since one ADC sampling takes longer than that (300-400 us on ESP8266)
// uint32_t sample_wait_after_ = 40;
// the sampling source to read voltage from
voltage_sampler::VoltageSampler *source_;
// ADC voltage reading offset
float voltage_offset_ = 0.0f;
// ADC voltage reading multiplier
float voltage_multiplier_ = 1.0f;
// the binary output to control the sampling LED
output::BinaryOutput *led_output_;
float sample_sum_ = 0.0f;
uint32_t num_samples_ = 0;
bool is_sampling_ = false;
};
} // namespace gp2y1010au0f
} // namespace esphome

View File

@@ -0,0 +1,61 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, voltage_sampler, output
from esphome.const import (
CONF_SENSOR,
CONF_OUTPUT,
DEVICE_CLASS_PM25,
STATE_CLASS_MEASUREMENT,
UNIT_MICROGRAMS_PER_CUBIC_METER,
ICON_CHEMICAL_WEAPON,
)
DEPENDENCIES = ["output"]
AUTO_LOAD = ["voltage_sampler"]
CODEOWNERS = ["@zry98"]
CONF_ADC_VOLTAGE_OFFSET = "adc_voltage_offset"
CONF_ADC_VOLTAGE_MULTIPLIER = "adc_voltage_multiplier"
gp2y1010au0f_ns = cg.esphome_ns.namespace("gp2y1010au0f")
GP2Y1010AU0FSensor = gp2y1010au0f_ns.class_(
"GP2Y1010AU0FSensor", sensor.Sensor, cg.PollingComponent
)
CONFIG_SCHEMA = (
sensor.sensor_schema(
GP2Y1010AU0FSensor,
unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER,
accuracy_decimals=0,
device_class=DEVICE_CLASS_PM25,
state_class=STATE_CLASS_MEASUREMENT,
icon=ICON_CHEMICAL_WEAPON,
)
.extend(
{
cv.Required(CONF_SENSOR): cv.use_id(voltage_sampler.VoltageSampler),
cv.Optional(CONF_ADC_VOLTAGE_OFFSET, default=0.0): cv.float_,
cv.Optional(CONF_ADC_VOLTAGE_MULTIPLIER, default=1.0): cv.float_,
cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput),
}
)
.extend(cv.polling_component_schema("60s"))
)
async def to_code(config):
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
# the ADC sensor to read voltage from
adc_sensor = await cg.get_variable(config[CONF_SENSOR])
cg.add(var.set_adc_source(adc_sensor))
cg.add(
var.set_voltage_refs(
config[CONF_ADC_VOLTAGE_OFFSET], config[CONF_ADC_VOLTAGE_MULTIPLIER]
)
)
# the binary output to control the module's internal IR LED
led_output = await cg.get_variable(config[CONF_OUTPUT])
cg.add(var.set_led_output(led_output))

View File

@@ -0,0 +1,38 @@
#pragma once
#include <array>
#include <cstdint>
#include "esphome/core/hal.h"
namespace esphome {
namespace gpio_expander {
/// @brief A class to cache the read state of a GPIO expander.
template<typename T, T N> class CachedGpioExpander {
public:
bool digital_read(T pin) {
if (!this->read_cache_invalidated_[pin]) {
this->read_cache_invalidated_[pin] = true;
return this->digital_read_cache(pin);
}
return this->digital_read_hw(pin);
}
void digital_write(T pin, bool value) { this->digital_write_hw(pin, value); }
protected:
virtual bool digital_read_hw(T pin) = 0;
virtual bool digital_read_cache(T pin) = 0;
virtual void digital_write_hw(T pin, bool value) = 0;
void reset_pin_cache_() {
for (T i = 0; i < N; i++) {
this->read_cache_invalidated_[i] = false;
}
}
std::array<bool, N> read_cache_invalidated_{};
};
} // namespace gpio_expander
} // namespace esphome

View File

@@ -0,0 +1,88 @@
#include "grove_gas_mc_v2.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
namespace esphome {
namespace grove_gas_mc_v2 {
static const char *const TAG = "grove_gas_mc_v2";
// I2C Commands for Grove Gas Multichannel V2 Sensor
// Taken from:
// https://github.com/Seeed-Studio/Seeed_Arduino_MultiGas/blob/master/src/Multichannel_Gas_GroveGasMultichannelV2.h
static const uint8_t GROVE_GAS_MC_V2_HEAT_ON = 0xFE;
static const uint8_t GROVE_GAS_MC_V2_HEAT_OFF = 0xFF;
static const uint8_t GROVE_GAS_MC_V2_READ_GM102B = 0x01;
static const uint8_t GROVE_GAS_MC_V2_READ_GM302B = 0x03;
static const uint8_t GROVE_GAS_MC_V2_READ_GM502B = 0x05;
static const uint8_t GROVE_GAS_MC_V2_READ_GM702B = 0x07;
bool GroveGasMultichannelV2Component::read_sensor_(uint8_t address, sensor::Sensor *sensor) {
if (sensor == nullptr) {
return true;
}
uint32_t value = 0;
if (!this->read_bytes(address, (uint8_t *) &value, 4)) {
ESP_LOGW(TAG, "Reading Grove Gas Sensor data failed!");
this->error_code_ = COMMUNICATION_FAILED;
this->status_set_warning();
return false;
}
sensor->publish_state(value);
return true;
}
void GroveGasMultichannelV2Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up Grove Multichannel Gas Sensor V2...");
// Before reading sensor values, must preheat sensor
if (!(this->write_bytes(GROVE_GAS_MC_V2_HEAT_ON, {}))) {
this->mark_failed();
this->error_code_ = APP_START_FAILED;
}
}
void GroveGasMultichannelV2Component::update() {
// Read from each of the gas sensors
if (!this->read_sensor_(GROVE_GAS_MC_V2_READ_GM102B, this->nitrogen_dioxide_sensor_))
return;
if (!this->read_sensor_(GROVE_GAS_MC_V2_READ_GM302B, this->ethanol_sensor_))
return;
if (!this->read_sensor_(GROVE_GAS_MC_V2_READ_GM502B, this->tvoc_sensor_))
return;
if (!this->read_sensor_(GROVE_GAS_MC_V2_READ_GM702B, this->carbon_monoxide_sensor_))
return;
this->status_clear_warning();
}
void GroveGasMultichannelV2Component::dump_config() {
ESP_LOGCONFIG(TAG, "Grove Multichannel Gas Sensor V2");
LOG_I2C_DEVICE(this)
LOG_UPDATE_INTERVAL(this)
LOG_SENSOR(" ", "Nitrogen Dioxide", this->nitrogen_dioxide_sensor_)
LOG_SENSOR(" ", "Ethanol", this->ethanol_sensor_)
LOG_SENSOR(" ", "Carbon Monoxide", this->carbon_monoxide_sensor_)
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_)
if (this->is_failed()) {
switch (this->error_code_) {
case COMMUNICATION_FAILED:
ESP_LOGW(TAG, "Communication failed! Is the sensor connected?");
break;
case APP_INVALID:
ESP_LOGW(TAG, "Sensor reported invalid APP installed.");
break;
case APP_START_FAILED:
ESP_LOGW(TAG, "Sensor reported APP start failed.");
break;
case UNKNOWN:
default:
ESP_LOGW(TAG, "Unknown setup error!");
break;
}
}
}
} // namespace grove_gas_mc_v2
} // namespace esphome

View File

@@ -0,0 +1,39 @@
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
#include "esphome/core/preferences.h"
namespace esphome {
namespace grove_gas_mc_v2 {
class GroveGasMultichannelV2Component : public PollingComponent, public i2c::I2CDevice {
SUB_SENSOR(tvoc)
SUB_SENSOR(carbon_monoxide)
SUB_SENSOR(nitrogen_dioxide)
SUB_SENSOR(ethanol)
public:
/// Setup the sensor and test for a connection.
void setup() override;
/// Schedule temperature+pressure readings.
void update() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
protected:
enum ErrorCode {
UNKNOWN,
COMMUNICATION_FAILED,
APP_INVALID,
APP_START_FAILED,
} error_code_{UNKNOWN};
bool read_sensor_(uint8_t address, sensor::Sensor *sensor);
};
} // namespace grove_gas_mc_v2
} // namespace esphome

View File

@@ -0,0 +1,77 @@
import esphome.codegen as cg
from esphome.components import i2c, sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_CARBON_MONOXIDE,
CONF_ETHANOL,
CONF_ID,
CONF_NITROGEN_DIOXIDE,
CONF_TVOC,
DEVICE_CLASS_CARBON_MONOXIDE,
DEVICE_CLASS_NITROGEN_DIOXIDE,
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
ICON_AIR_FILTER,
ICON_FLASK_ROUND_BOTTOM,
ICON_GAS_CYLINDER,
ICON_MOLECULE_CO,
STATE_CLASS_MEASUREMENT,
UNIT_MICROGRAMS_PER_CUBIC_METER,
UNIT_PARTS_PER_MILLION,
)
CODEOWNERS = ["@YorkshireIoT"]
DEPENDENCIES = ["i2c"]
grove_gas_mc_v2_ns = cg.esphome_ns.namespace("grove_gas_mc_v2")
GroveGasMultichannelV2Component = grove_gas_mc_v2_ns.class_(
"GroveGasMultichannelV2Component", cg.PollingComponent, i2c.I2CDevice
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(GroveGasMultichannelV2Component),
cv.Optional(CONF_TVOC): sensor.sensor_schema(
unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER,
icon=ICON_AIR_FILTER,
accuracy_decimals=0,
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CARBON_MONOXIDE): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION,
icon=ICON_MOLECULE_CO,
accuracy_decimals=0,
device_class=DEVICE_CLASS_CARBON_MONOXIDE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_NITROGEN_DIOXIDE): sensor.sensor_schema(
unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER,
icon=ICON_GAS_CYLINDER,
accuracy_decimals=0,
device_class=DEVICE_CLASS_NITROGEN_DIOXIDE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ETHANOL): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION,
icon=ICON_FLASK_ROUND_BOTTOM,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x08))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
for key in [CONF_TVOC, CONF_CARBON_MONOXIDE, CONF_NITROGEN_DIOXIDE, CONF_ETHANOL]:
if sensor_config := config.get(key):
sensor_ = await sensor.new_sensor(sensor_config)
cg.add(getattr(var, f"set_{key}_sensor")(sensor_))

View File

@@ -114,7 +114,6 @@ SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS = {
SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS = { SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS = {
"AWAY": ClimatePreset.CLIMATE_PRESET_AWAY, "AWAY": ClimatePreset.CLIMATE_PRESET_AWAY,
"BOOST": ClimatePreset.CLIMATE_PRESET_BOOST, "BOOST": ClimatePreset.CLIMATE_PRESET_BOOST,
"ECO": ClimatePreset.CLIMATE_PRESET_ECO,
"SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP, "SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP,
} }
@@ -240,7 +239,9 @@ CONFIG_SCHEMA = cv.All(
): cv.ensure_list( ): cv.ensure_list(
cv.enum(SUPPORTED_HON_CONTROL_METHODS, upper=True) cv.enum(SUPPORTED_HON_CONTROL_METHODS, upper=True)
), ),
cv.Optional(CONF_BEEPER, default=True): cv.boolean, cv.Optional(CONF_BEEPER): cv.invalid(
f"The {CONF_BEEPER} option is deprecated, use beeper_on/beeper_off actions or beeper switch for a haier platform instead"
),
cv.Optional( cv.Optional(
CONF_CONTROL_PACKET_SIZE, default=PROTOCOL_CONTROL_PACKET_SIZE CONF_CONTROL_PACKET_SIZE, default=PROTOCOL_CONTROL_PACKET_SIZE
): cv.int_range(min=PROTOCOL_CONTROL_PACKET_SIZE, max=50), ): cv.int_range(min=PROTOCOL_CONTROL_PACKET_SIZE, max=50),
@@ -254,7 +255,7 @@ CONFIG_SCHEMA = cv.All(
): cv.int_range(min=PROTOCOL_STATUS_MESSAGE_HEADER_SIZE), ): cv.int_range(min=PROTOCOL_STATUS_MESSAGE_HEADER_SIZE),
cv.Optional( cv.Optional(
CONF_SUPPORTED_PRESETS, CONF_SUPPORTED_PRESETS,
default=["BOOST", "ECO", "SLEEP"], # No AWAY by default default=["BOOST", "SLEEP"], # No AWAY by default
): cv.ensure_list( ): cv.ensure_list(
cv.enum(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS, upper=True) cv.enum(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS, upper=True)
), ),

View File

@@ -52,8 +52,6 @@ bool check_timeout(std::chrono::steady_clock::time_point now, std::chrono::stead
HaierClimateBase::HaierClimateBase() HaierClimateBase::HaierClimateBase()
: haier_protocol_(*this), : haier_protocol_(*this),
protocol_phase_(ProtocolPhases::SENDING_INIT_1), protocol_phase_(ProtocolPhases::SENDING_INIT_1),
display_status_(true),
health_mode_(false),
force_send_control_(false), force_send_control_(false),
forced_request_status_(false), forced_request_status_(false),
reset_protocol_request_(false), reset_protocol_request_(false),
@@ -127,21 +125,34 @@ haier_protocol::HaierMessage HaierClimateBase::get_wifi_signal_message_() {
} }
#endif #endif
bool HaierClimateBase::get_display_state() const { return this->display_status_; } void HaierClimateBase::save_settings() {
HaierBaseSettings settings{this->get_health_mode(), this->get_display_state()};
void HaierClimateBase::set_display_state(bool state) { if (!this->base_rtc_.save(&settings)) {
if (this->display_status_ != state) { ESP_LOGW(TAG, "Failed to save settings");
this->display_status_ = state;
this->force_send_control_ = true;
} }
} }
bool HaierClimateBase::get_health_mode() const { return this->health_mode_; } bool HaierClimateBase::get_display_state() const {
return (this->display_status_ == SwitchState::ON) || (this->display_status_ == SwitchState::PENDING_ON);
}
void HaierClimateBase::set_display_state(bool state) {
if (state != this->get_display_state()) {
this->display_status_ = state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF;
this->force_send_control_ = true;
this->save_settings();
}
}
bool HaierClimateBase::get_health_mode() const {
return (this->health_mode_ == SwitchState::ON) || (this->health_mode_ == SwitchState::PENDING_ON);
}
void HaierClimateBase::set_health_mode(bool state) { void HaierClimateBase::set_health_mode(bool state) {
if (this->health_mode_ != state) { if (state != this->get_health_mode()) {
this->health_mode_ = state; this->health_mode_ = state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF;
this->force_send_control_ = true; this->force_send_control_ = true;
this->save_settings();
} }
} }
@@ -287,6 +298,14 @@ void HaierClimateBase::loop() {
} }
this->process_phase(now); this->process_phase(now);
this->haier_protocol_.loop(); this->haier_protocol_.loop();
#ifdef USE_SWITCH
if ((this->display_switch_ != nullptr) && (this->display_switch_->state != this->get_display_state())) {
this->display_switch_->publish_state(this->get_display_state());
}
if ((this->health_mode_switch_ != nullptr) && (this->health_mode_switch_->state != this->get_health_mode())) {
this->health_mode_switch_->publish_state(this->get_health_mode());
}
#endif // USE_SWITCH
} }
void HaierClimateBase::process_protocol_reset() { void HaierClimateBase::process_protocol_reset() {
@@ -329,6 +348,26 @@ bool HaierClimateBase::prepare_pending_action() {
ClimateTraits HaierClimateBase::traits() { return traits_; } ClimateTraits HaierClimateBase::traits() { return traits_; }
void HaierClimateBase::initialization() {
constexpr uint32_t restore_settings_version = 0xA77D21EF;
this->base_rtc_ =
global_preferences->make_preference<HaierBaseSettings>(this->get_object_id_hash() ^ restore_settings_version);
HaierBaseSettings recovered;
if (!this->base_rtc_.load(&recovered)) {
recovered = {false, true};
}
this->display_status_ = recovered.display_state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF;
this->health_mode_ = recovered.health_mode ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF;
#ifdef USE_SWITCH
if (this->display_switch_ != nullptr) {
this->display_switch_->publish_state(this->get_display_state());
}
if (this->health_mode_switch_ != nullptr) {
this->health_mode_switch_->publish_state(this->get_health_mode());
}
#endif
}
void HaierClimateBase::control(const ClimateCall &call) { void HaierClimateBase::control(const ClimateCall &call) {
ESP_LOGD("Control", "Control call"); ESP_LOGD("Control", "Control call");
if (!this->valid_connection()) { if (!this->valid_connection()) {
@@ -353,6 +392,22 @@ void HaierClimateBase::control(const ClimateCall &call) {
} }
} }
#ifdef USE_SWITCH
void HaierClimateBase::set_display_switch(switch_::Switch *sw) {
this->display_switch_ = sw;
if ((this->display_switch_ != nullptr) && (this->valid_connection())) {
this->display_switch_->publish_state(this->get_display_state());
}
}
void HaierClimateBase::set_health_mode_switch(switch_::Switch *sw) {
this->health_mode_switch_ = sw;
if ((this->health_mode_switch_ != nullptr) && (this->valid_connection())) {
this->health_mode_switch_->publish_state(this->get_health_mode());
}
}
#endif
void HaierClimateBase::HvacSettings::reset() { void HaierClimateBase::HvacSettings::reset() {
this->valid = false; this->valid = false;
this->mode.reset(); this->mode.reset();

View File

@@ -8,6 +8,10 @@
// HaierProtocol // HaierProtocol
#include <protocol/haier_protocol.h> #include <protocol/haier_protocol.h>
#ifdef USE_SWITCH
#include "esphome/components/switch/switch.h"
#endif
namespace esphome { namespace esphome {
namespace haier { namespace haier {
@@ -20,10 +24,24 @@ enum class ActionRequest : uint8_t {
START_STERI_CLEAN = 5, // only hOn START_STERI_CLEAN = 5, // only hOn
}; };
struct HaierBaseSettings {
bool health_mode;
bool display_state;
};
class HaierClimateBase : public esphome::Component, class HaierClimateBase : public esphome::Component,
public esphome::climate::Climate, public esphome::climate::Climate,
public esphome::uart::UARTDevice, public esphome::uart::UARTDevice,
public haier_protocol::ProtocolStream { public haier_protocol::ProtocolStream {
#ifdef USE_SWITCH
public:
void set_display_switch(switch_::Switch *sw);
void set_health_mode_switch(switch_::Switch *sw);
protected:
switch_::Switch *display_switch_{nullptr};
switch_::Switch *health_mode_switch_{nullptr};
#endif
public: public:
HaierClimateBase(); HaierClimateBase();
HaierClimateBase(const HaierClimateBase &) = delete; HaierClimateBase(const HaierClimateBase &) = delete;
@@ -82,7 +100,8 @@ class HaierClimateBase : public esphome::Component,
virtual void process_phase(std::chrono::steady_clock::time_point now) = 0; virtual void process_phase(std::chrono::steady_clock::time_point now) = 0;
virtual haier_protocol::HaierMessage get_control_message() = 0; // NOLINT(readability-identifier-naming) virtual haier_protocol::HaierMessage get_control_message() = 0; // NOLINT(readability-identifier-naming)
virtual haier_protocol::HaierMessage get_power_message(bool state) = 0; // NOLINT(readability-identifier-naming) virtual haier_protocol::HaierMessage get_power_message(bool state) = 0; // NOLINT(readability-identifier-naming)
virtual void initialization(){}; virtual void save_settings();
virtual void initialization();
virtual bool prepare_pending_action(); virtual bool prepare_pending_action();
virtual void process_protocol_reset(); virtual void process_protocol_reset();
esphome::climate::ClimateTraits traits() override; esphome::climate::ClimateTraits traits() override;
@@ -127,13 +146,19 @@ class HaierClimateBase : public esphome::Component,
ActionRequest action; ActionRequest action;
esphome::optional<haier_protocol::HaierMessage> message; esphome::optional<haier_protocol::HaierMessage> message;
}; };
enum class SwitchState {
OFF = 0b00,
ON = 0b01,
PENDING_OFF = 0b10,
PENDING_ON = 0b11,
};
haier_protocol::ProtocolHandler haier_protocol_; haier_protocol::ProtocolHandler haier_protocol_;
ProtocolPhases protocol_phase_; ProtocolPhases protocol_phase_;
esphome::optional<PendingAction> action_request_; esphome::optional<PendingAction> action_request_;
uint8_t fan_mode_speed_; uint8_t fan_mode_speed_;
uint8_t other_modes_fan_speed_; uint8_t other_modes_fan_speed_;
bool display_status_; SwitchState display_status_{SwitchState::ON};
bool health_mode_; SwitchState health_mode_{SwitchState::OFF};
bool force_send_control_; bool force_send_control_;
bool forced_request_status_; bool forced_request_status_;
bool reset_protocol_request_; bool reset_protocol_request_;
@@ -148,6 +173,7 @@ class HaierClimateBase : public esphome::Component,
std::chrono::steady_clock::time_point last_status_request_; // To request AC status std::chrono::steady_clock::time_point last_status_request_; // To request AC status
std::chrono::steady_clock::time_point last_signal_request_; // To send WiFI signal level std::chrono::steady_clock::time_point last_signal_request_; // To send WiFI signal level
CallbackManager<void(const char *, size_t)> status_message_callback_{}; CallbackManager<void(const char *, size_t)> status_message_callback_{};
ESPPreferenceObject base_rtc_;
}; };
class StatusMessageTrigger : public Trigger<const char *, size_t> { class StatusMessageTrigger : public Trigger<const char *, size_t> {

View File

@@ -31,9 +31,32 @@ HonClimate::HonClimate()
HonClimate::~HonClimate() {} HonClimate::~HonClimate() {}
void HonClimate::set_beeper_state(bool state) { this->beeper_status_ = state; } void HonClimate::set_beeper_state(bool state) {
if (state != this->settings_.beeper_state) {
this->settings_.beeper_state = state;
#ifdef USE_SWITCH
this->beeper_switch_->publish_state(state);
#endif
this->hon_rtc_.save(&this->settings_);
}
}
bool HonClimate::get_beeper_state() const { return this->beeper_status_; } bool HonClimate::get_beeper_state() const { return this->settings_.beeper_state; }
void HonClimate::set_quiet_mode_state(bool state) {
if (state != this->get_quiet_mode_state()) {
this->quiet_mode_state_ = state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF;
this->settings_.quiet_mode_state = state;
#ifdef USE_SWITCH
this->quiet_mode_switch_->publish_state(state);
#endif
this->hon_rtc_.save(&this->settings_);
}
}
bool HonClimate::get_quiet_mode_state() const {
return (this->quiet_mode_state_ == SwitchState::ON) || (this->quiet_mode_state_ == SwitchState::PENDING_ON);
}
esphome::optional<hon_protocol::VerticalSwingMode> HonClimate::get_vertical_airflow() const { esphome::optional<hon_protocol::VerticalSwingMode> HonClimate::get_vertical_airflow() const {
return this->current_vertical_swing_; return this->current_vertical_swing_;
@@ -474,16 +497,19 @@ haier_protocol::HaierMessage HonClimate::get_power_message(bool state) {
} }
void HonClimate::initialization() { void HonClimate::initialization() {
constexpr uint32_t restore_settings_version = 0xE834D8DCUL; HaierClimateBase::initialization();
this->rtc_ = global_preferences->make_preference<HonSettings>(this->get_object_id_hash() ^ restore_settings_version); constexpr uint32_t restore_settings_version = 0x57EB59DDUL;
this->hon_rtc_ =
global_preferences->make_preference<HonSettings>(this->get_object_id_hash() ^ restore_settings_version);
HonSettings recovered; HonSettings recovered;
if (this->rtc_.load(&recovered)) { if (this->hon_rtc_.load(&recovered)) {
this->settings_ = recovered; this->settings_ = recovered;
} else { } else {
this->settings_ = {hon_protocol::VerticalSwingMode::CENTER, hon_protocol::HorizontalSwingMode::CENTER}; this->settings_ = {hon_protocol::VerticalSwingMode::CENTER, hon_protocol::HorizontalSwingMode::CENTER, true, false};
} }
this->current_vertical_swing_ = this->settings_.last_vertiacal_swing; this->current_vertical_swing_ = this->settings_.last_vertiacal_swing;
this->current_horizontal_swing_ = this->settings_.last_horizontal_swing; this->current_horizontal_swing_ = this->settings_.last_horizontal_swing;
this->quiet_mode_state_ = this->settings_.quiet_mode_state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF;
} }
haier_protocol::HaierMessage HonClimate::get_control_message() { haier_protocol::HaierMessage HonClimate::get_control_message() {
@@ -519,8 +545,7 @@ haier_protocol::HaierMessage HonClimate::get_control_message() {
out_data->ac_power = 1; out_data->ac_power = 1;
out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::FAN; out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::FAN;
out_data->fan_mode = this->fan_mode_speed_; // Auto doesn't work in fan only mode out_data->fan_mode = this->fan_mode_speed_; // Auto doesn't work in fan only mode
// Disabling boost and eco mode for Fan only // Disabling boost for Fan only
out_data->quiet_mode = 0;
out_data->fast_mode = 0; out_data->fast_mode = 0;
break; break;
case CLIMATE_MODE_COOL: case CLIMATE_MODE_COOL:
@@ -582,47 +607,34 @@ haier_protocol::HaierMessage HonClimate::get_control_message() {
} }
if (out_data->ac_power == 0) { if (out_data->ac_power == 0) {
// If AC is off - no presets allowed // If AC is off - no presets allowed
out_data->quiet_mode = 0;
out_data->fast_mode = 0; out_data->fast_mode = 0;
out_data->sleep_mode = 0; out_data->sleep_mode = 0;
} else if (climate_control.preset.has_value()) { } else if (climate_control.preset.has_value()) {
switch (climate_control.preset.value()) { switch (climate_control.preset.value()) {
case CLIMATE_PRESET_NONE: case CLIMATE_PRESET_NONE:
out_data->quiet_mode = 0;
out_data->fast_mode = 0;
out_data->sleep_mode = 0;
out_data->ten_degree = 0;
break;
case CLIMATE_PRESET_ECO:
// Eco is not supported in Fan only mode
out_data->quiet_mode = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 1 : 0;
out_data->fast_mode = 0; out_data->fast_mode = 0;
out_data->sleep_mode = 0; out_data->sleep_mode = 0;
out_data->ten_degree = 0; out_data->ten_degree = 0;
break; break;
case CLIMATE_PRESET_BOOST: case CLIMATE_PRESET_BOOST:
out_data->quiet_mode = 0;
// Boost is not supported in Fan only mode // Boost is not supported in Fan only mode
out_data->fast_mode = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 1 : 0; out_data->fast_mode = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 1 : 0;
out_data->sleep_mode = 0; out_data->sleep_mode = 0;
out_data->ten_degree = 0; out_data->ten_degree = 0;
break; break;
case CLIMATE_PRESET_AWAY: case CLIMATE_PRESET_AWAY:
out_data->quiet_mode = 0;
out_data->fast_mode = 0; out_data->fast_mode = 0;
out_data->sleep_mode = 0; out_data->sleep_mode = 0;
// 10 degrees allowed only in heat mode // 10 degrees allowed only in heat mode
out_data->ten_degree = (this->mode == CLIMATE_MODE_HEAT) ? 1 : 0; out_data->ten_degree = (this->mode == CLIMATE_MODE_HEAT) ? 1 : 0;
break; break;
case CLIMATE_PRESET_SLEEP: case CLIMATE_PRESET_SLEEP:
out_data->quiet_mode = 0;
out_data->fast_mode = 0; out_data->fast_mode = 0;
out_data->sleep_mode = 1; out_data->sleep_mode = 1;
out_data->ten_degree = 0; out_data->ten_degree = 0;
break; break;
default: default:
ESP_LOGE("Control", "Unsupported preset"); ESP_LOGE("Control", "Unsupported preset");
out_data->quiet_mode = 0;
out_data->fast_mode = 0; out_data->fast_mode = 0;
out_data->sleep_mode = 0; out_data->sleep_mode = 0;
out_data->ten_degree = 0; out_data->ten_degree = 0;
@@ -638,10 +650,23 @@ haier_protocol::HaierMessage HonClimate::get_control_message() {
out_data->horizontal_swing_mode = (uint8_t) this->pending_horizontal_direction_.value(); out_data->horizontal_swing_mode = (uint8_t) this->pending_horizontal_direction_.value();
this->pending_horizontal_direction_.reset(); this->pending_horizontal_direction_.reset();
} }
out_data->beeper_status = ((!this->beeper_status_) || (!has_hvac_settings)) ? 1 : 0; {
// Quiet mode
if ((out_data->ac_power == 0) || (out_data->ac_mode == (uint8_t) hon_protocol::ConditioningMode::FAN)) {
// If AC is off or in fan only mode - no quiet mode allowed
out_data->quiet_mode = 0;
} else {
out_data->quiet_mode = this->get_quiet_mode_state() ? 1 : 0;
}
// Clean quiet mode state pending flag
this->quiet_mode_state_ = (SwitchState) ((uint8_t) this->quiet_mode_state_ & 0b01);
}
out_data->beeper_status = ((!this->get_beeper_state()) || (!has_hvac_settings)) ? 1 : 0;
control_out_buffer[4] = 0; // This byte should be cleared before setting values control_out_buffer[4] = 0; // This byte should be cleared before setting values
out_data->display_status = this->display_status_ ? 1 : 0; out_data->display_status = this->get_display_state() ? 1 : 0;
out_data->health_mode = this->health_mode_ ? 1 : 0; this->display_status_ = (SwitchState) ((uint8_t) this->display_status_ & 0b01);
out_data->health_mode = this->get_health_mode() ? 1 : 0;
this->health_mode_ = (SwitchState) ((uint8_t) this->health_mode_ & 0b01);
return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS,
control_out_buffer, this->real_control_packet_size_); control_out_buffer, this->real_control_packet_size_);
@@ -765,6 +790,22 @@ void HonClimate::update_sub_text_sensor_(SubTextSensorType type, const std::stri
} }
#endif // USE_TEXT_SENSOR #endif // USE_TEXT_SENSOR
#ifdef USE_SWITCH
void HonClimate::set_beeper_switch(switch_::Switch *sw) {
this->beeper_switch_ = sw;
if (this->beeper_switch_ != nullptr) {
this->beeper_switch_->publish_state(this->get_beeper_state());
}
}
void HonClimate::set_quiet_mode_switch(switch_::Switch *sw) {
this->quiet_mode_switch_ = sw;
if (this->quiet_mode_switch_ != nullptr) {
this->quiet_mode_switch_->publish_state(this->settings_.quiet_mode_state);
}
}
#endif // USE_SWITCH
haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) { haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) {
size_t expected_size = size_t expected_size =
2 + this->status_message_header_size_ + this->real_control_packet_size_ + this->real_sensors_packet_size_; 2 + this->status_message_header_size_ + this->real_control_packet_size_ + this->real_sensors_packet_size_;
@@ -827,9 +868,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
{ {
// Extra modes/presets // Extra modes/presets
optional<ClimatePreset> old_preset = this->preset; optional<ClimatePreset> old_preset = this->preset;
if (packet.control.quiet_mode != 0) { if (packet.control.fast_mode != 0) {
this->preset = CLIMATE_PRESET_ECO;
} else if (packet.control.fast_mode != 0) {
this->preset = CLIMATE_PRESET_BOOST; this->preset = CLIMATE_PRESET_BOOST;
} else if (packet.control.sleep_mode != 0) { } else if (packet.control.sleep_mode != 0) {
this->preset = CLIMATE_PRESET_SLEEP; this->preset = CLIMATE_PRESET_SLEEP;
@@ -883,28 +922,26 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
} }
should_publish = should_publish || (!old_fan_mode.has_value()) || (old_fan_mode.value() != fan_mode.value()); should_publish = should_publish || (!old_fan_mode.has_value()) || (old_fan_mode.value() != fan_mode.value());
} }
{ // Display status
// Display status // should be before "Climate mode" because it is changing this->mode
// should be before "Climate mode" because it is changing this->mode if (packet.control.ac_power != 0) {
if (packet.control.ac_power != 0) { // if AC is off display status always ON so process it only when AC is on
// if AC is off display status always ON so process it only when AC is on bool disp_status = packet.control.display_status != 0;
bool disp_status = packet.control.display_status != 0; if (disp_status != this->get_display_state()) {
if (disp_status != this->display_status_) { // Do something only if display status changed
// Do something only if display status changed if (this->mode == CLIMATE_MODE_OFF) {
if (this->mode == CLIMATE_MODE_OFF) { // AC just turned on from remote need to turn off display
// AC just turned on from remote need to turn off display this->force_send_control_ = true;
this->force_send_control_ = true; } else if ((((uint8_t) this->health_mode_) & 0b10) == 0) {
} else { this->display_status_ = disp_status ? SwitchState::ON : SwitchState::OFF;
this->display_status_ = disp_status;
}
} }
} }
} }
{ // Health mode
// Health mode if ((((uint8_t) this->health_mode_) & 0b10) == 0) {
bool old_health_mode = this->health_mode_; bool old_health_mode = this->get_health_mode();
this->health_mode_ = packet.control.health_mode == 1; this->health_mode_ = packet.control.health_mode == 1 ? SwitchState::ON : SwitchState::OFF;
should_publish = should_publish || (old_health_mode != this->health_mode_); should_publish = should_publish || (old_health_mode != this->get_health_mode());
} }
{ {
CleaningState new_cleaning; CleaningState new_cleaning;
@@ -958,17 +995,36 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
} }
should_publish = should_publish || (old_mode != this->mode); should_publish = should_publish || (old_mode != this->mode);
} }
{
// Quiet mode, should be after climate mode
if ((this->mode != CLIMATE_MODE_FAN_ONLY) && (this->mode != CLIMATE_MODE_OFF) &&
((((uint8_t) this->quiet_mode_state_) & 0b10) == 0)) {
// In proper mode and not in pending state
bool new_quiet_mode = packet.control.quiet_mode != 0;
if (new_quiet_mode != this->get_quiet_mode_state()) {
this->quiet_mode_state_ = new_quiet_mode ? SwitchState::ON : SwitchState::OFF;
this->settings_.quiet_mode_state = new_quiet_mode;
this->hon_rtc_.save(&this->settings_);
}
}
}
{ {
// Swing mode // Swing mode
ClimateSwingMode old_swing_mode = this->swing_mode; ClimateSwingMode old_swing_mode = this->swing_mode;
if (packet.control.horizontal_swing_mode == (uint8_t) hon_protocol::HorizontalSwingMode::AUTO) { const std::set<ClimateSwingMode> &swing_modes = traits_.get_supported_swing_modes();
if (packet.control.vertical_swing_mode == (uint8_t) hon_protocol::VerticalSwingMode::AUTO) { bool vertical_swing_supported = swing_modes.find(CLIMATE_SWING_VERTICAL) != swing_modes.end();
bool horizontal_swing_supported = swing_modes.find(CLIMATE_SWING_HORIZONTAL) != swing_modes.end();
if (horizontal_swing_supported &&
(packet.control.horizontal_swing_mode == (uint8_t) hon_protocol::HorizontalSwingMode::AUTO)) {
if (vertical_swing_supported &&
(packet.control.vertical_swing_mode == (uint8_t) hon_protocol::VerticalSwingMode::AUTO)) {
this->swing_mode = CLIMATE_SWING_BOTH; this->swing_mode = CLIMATE_SWING_BOTH;
} else { } else {
this->swing_mode = CLIMATE_SWING_HORIZONTAL; this->swing_mode = CLIMATE_SWING_HORIZONTAL;
} }
} else { } else {
if (packet.control.vertical_swing_mode == (uint8_t) hon_protocol::VerticalSwingMode::AUTO) { if (vertical_swing_supported &&
(packet.control.vertical_swing_mode == (uint8_t) hon_protocol::VerticalSwingMode::AUTO)) {
this->swing_mode = CLIMATE_SWING_VERTICAL; this->swing_mode = CLIMATE_SWING_VERTICAL;
} else { } else {
this->swing_mode = CLIMATE_SWING_OFF; this->swing_mode = CLIMATE_SWING_OFF;
@@ -985,7 +1041,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
if (save_settings) { if (save_settings) {
this->settings_.last_vertiacal_swing = this->current_vertical_swing_.value(); this->settings_.last_vertiacal_swing = this->current_vertical_swing_.value();
this->settings_.last_horizontal_swing = this->current_horizontal_swing_.value(); this->settings_.last_horizontal_swing = this->current_horizontal_swing_.value();
this->rtc_.save(&this->settings_); this->hon_rtc_.save(&this->settings_);
} }
should_publish = should_publish || (old_swing_mode != this->swing_mode); should_publish = should_publish || (old_swing_mode != this->swing_mode);
} }
@@ -1017,7 +1073,7 @@ void HonClimate::fill_control_messages_queue_() {
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
(uint8_t) hon_protocol::DataParameters::BEEPER_STATUS, (uint8_t) hon_protocol::DataParameters::BEEPER_STATUS,
this->beeper_status_ ? ZERO_BUF : ONE_BUF, 2)); this->get_beeper_state() ? ZERO_BUF : ONE_BUF, 2));
} }
// Health mode // Health mode
{ {
@@ -1025,13 +1081,16 @@ void HonClimate::fill_control_messages_queue_() {
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
(uint8_t) hon_protocol::DataParameters::HEALTH_MODE, (uint8_t) hon_protocol::DataParameters::HEALTH_MODE,
this->health_mode_ ? ONE_BUF : ZERO_BUF, 2)); this->get_health_mode() ? ONE_BUF : ZERO_BUF, 2));
this->health_mode_ = (SwitchState) ((uint8_t) this->health_mode_ & 0b01);
} }
// Climate mode // Climate mode
ClimateMode climate_mode = this->mode;
bool new_power = this->mode != CLIMATE_MODE_OFF; bool new_power = this->mode != CLIMATE_MODE_OFF;
uint8_t fan_mode_buf[] = {0x00, 0xFF}; uint8_t fan_mode_buf[] = {0x00, 0xFF};
uint8_t quiet_mode_buf[] = {0x00, 0xFF}; uint8_t quiet_mode_buf[] = {0x00, 0xFF};
if (climate_control.mode.has_value()) { if (climate_control.mode.has_value()) {
climate_mode = climate_control.mode.value();
uint8_t buffer[2] = {0x00, 0x00}; uint8_t buffer[2] = {0x00, 0x00};
switch (climate_control.mode.value()) { switch (climate_control.mode.value()) {
case CLIMATE_MODE_OFF: case CLIMATE_MODE_OFF:
@@ -1076,8 +1135,6 @@ void HonClimate::fill_control_messages_queue_() {
(uint8_t) hon_protocol::DataParameters::AC_MODE, (uint8_t) hon_protocol::DataParameters::AC_MODE,
buffer, 2)); buffer, 2));
fan_mode_buf[1] = this->other_modes_fan_speed_; // Auto doesn't work in fan only mode fan_mode_buf[1] = this->other_modes_fan_speed_; // Auto doesn't work in fan only mode
// Disabling eco mode for Fan only
quiet_mode_buf[1] = 0;
break; break;
case CLIMATE_MODE_COOL: case CLIMATE_MODE_COOL:
new_power = true; new_power = true;
@@ -1108,30 +1165,20 @@ void HonClimate::fill_control_messages_queue_() {
uint8_t away_mode_buf[] = {0x00, 0xFF}; uint8_t away_mode_buf[] = {0x00, 0xFF};
if (!new_power) { if (!new_power) {
// If AC is off - no presets allowed // If AC is off - no presets allowed
quiet_mode_buf[1] = 0x00;
fast_mode_buf[1] = 0x00; fast_mode_buf[1] = 0x00;
away_mode_buf[1] = 0x00; away_mode_buf[1] = 0x00;
} else if (climate_control.preset.has_value()) { } else if (climate_control.preset.has_value()) {
switch (climate_control.preset.value()) { switch (climate_control.preset.value()) {
case CLIMATE_PRESET_NONE: case CLIMATE_PRESET_NONE:
quiet_mode_buf[1] = 0x00;
fast_mode_buf[1] = 0x00;
away_mode_buf[1] = 0x00;
break;
case CLIMATE_PRESET_ECO:
// Eco is not supported in Fan only mode
quiet_mode_buf[1] = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 0x01 : 0x00;
fast_mode_buf[1] = 0x00; fast_mode_buf[1] = 0x00;
away_mode_buf[1] = 0x00; away_mode_buf[1] = 0x00;
break; break;
case CLIMATE_PRESET_BOOST: case CLIMATE_PRESET_BOOST:
quiet_mode_buf[1] = 0x00;
// Boost is not supported in Fan only mode // Boost is not supported in Fan only mode
fast_mode_buf[1] = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 0x01 : 0x00; fast_mode_buf[1] = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 0x01 : 0x00;
away_mode_buf[1] = 0x00; away_mode_buf[1] = 0x00;
break; break;
case CLIMATE_PRESET_AWAY: case CLIMATE_PRESET_AWAY:
quiet_mode_buf[1] = 0x00;
fast_mode_buf[1] = 0x00; fast_mode_buf[1] = 0x00;
away_mode_buf[1] = (this->mode == CLIMATE_MODE_HEAT) ? 0x01 : 0x00; away_mode_buf[1] = (this->mode == CLIMATE_MODE_HEAT) ? 0x01 : 0x00;
break; break;
@@ -1140,8 +1187,18 @@ void HonClimate::fill_control_messages_queue_() {
break; break;
} }
} }
{
// Quiet mode
if (new_power && (climate_mode != CLIMATE_MODE_FAN_ONLY) && this->get_quiet_mode_state()) {
quiet_mode_buf[1] = 0x01;
} else {
quiet_mode_buf[1] = 0x00;
}
// Clean quiet mode state pending flag
this->quiet_mode_state_ = (SwitchState) ((uint8_t) this->quiet_mode_state_ & 0b01);
}
auto presets = this->traits_.get_supported_presets(); auto presets = this->traits_.get_supported_presets();
if ((quiet_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_ECO) != presets.end()))) { if (quiet_mode_buf[1] != 0xFF) {
this->control_messages_queue_.push( this->control_messages_queue_.push(
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +

View File

@@ -10,6 +10,9 @@
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
#include "esphome/components/text_sensor/text_sensor.h" #include "esphome/components/text_sensor/text_sensor.h"
#endif #endif
#ifdef USE_SWITCH
#include "esphome/components/switch/switch.h"
#endif
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "haier_base.h" #include "haier_base.h"
#include "hon_packet.h" #include "hon_packet.h"
@@ -28,6 +31,8 @@ enum class HonControlMethod { MONITOR_ONLY = 0, SET_GROUP_PARAMETERS, SET_SINGLE
struct HonSettings { struct HonSettings {
hon_protocol::VerticalSwingMode last_vertiacal_swing; hon_protocol::VerticalSwingMode last_vertiacal_swing;
hon_protocol::HorizontalSwingMode last_horizontal_swing; hon_protocol::HorizontalSwingMode last_horizontal_swing;
bool beeper_state;
bool quiet_mode_state;
}; };
class HonClimate : public HaierClimateBase { class HonClimate : public HaierClimateBase {
@@ -86,6 +91,15 @@ class HonClimate : public HaierClimateBase {
protected: protected:
void update_sub_text_sensor_(SubTextSensorType type, const std::string &value); void update_sub_text_sensor_(SubTextSensorType type, const std::string &value);
text_sensor::TextSensor *sub_text_sensors_[(size_t) SubTextSensorType::SUB_TEXT_SENSOR_TYPE_COUNT]{nullptr}; text_sensor::TextSensor *sub_text_sensors_[(size_t) SubTextSensorType::SUB_TEXT_SENSOR_TYPE_COUNT]{nullptr};
#endif
#ifdef USE_SWITCH
public:
void set_beeper_switch(switch_::Switch *sw);
void set_quiet_mode_switch(switch_::Switch *sw);
protected:
switch_::Switch *beeper_switch_{nullptr};
switch_::Switch *quiet_mode_switch_{nullptr};
#endif #endif
public: public:
HonClimate(); HonClimate();
@@ -95,6 +109,8 @@ class HonClimate : public HaierClimateBase {
void dump_config() override; void dump_config() override;
void set_beeper_state(bool state); void set_beeper_state(bool state);
bool get_beeper_state() const; bool get_beeper_state() const;
void set_quiet_mode_state(bool state);
bool get_quiet_mode_state() const;
esphome::optional<hon_protocol::VerticalSwingMode> get_vertical_airflow() const; esphome::optional<hon_protocol::VerticalSwingMode> get_vertical_airflow() const;
void set_vertical_airflow(hon_protocol::VerticalSwingMode direction); void set_vertical_airflow(hon_protocol::VerticalSwingMode direction);
esphome::optional<hon_protocol::HorizontalSwingMode> get_horizontal_airflow() const; esphome::optional<hon_protocol::HorizontalSwingMode> get_horizontal_airflow() const;
@@ -153,7 +169,6 @@ class HonClimate : public HaierClimateBase {
bool functions_[5]; bool functions_[5];
}; };
bool beeper_status_;
CleaningState cleaning_status_; CleaningState cleaning_status_;
bool got_valid_outdoor_temp_; bool got_valid_outdoor_temp_;
esphome::optional<hon_protocol::VerticalSwingMode> pending_vertical_direction_{}; esphome::optional<hon_protocol::VerticalSwingMode> pending_vertical_direction_{};
@@ -175,7 +190,8 @@ class HonClimate : public HaierClimateBase {
esphome::optional<hon_protocol::VerticalSwingMode> current_vertical_swing_{}; esphome::optional<hon_protocol::VerticalSwingMode> current_vertical_swing_{};
esphome::optional<hon_protocol::HorizontalSwingMode> current_horizontal_swing_{}; esphome::optional<hon_protocol::HorizontalSwingMode> current_horizontal_swing_{};
HonSettings settings_; HonSettings settings_;
ESPPreferenceObject rtc_; ESPPreferenceObject hon_rtc_;
SwitchState quiet_mode_state_{SwitchState::OFF};
}; };
class HaierAlarmStartTrigger : public Trigger<uint8_t, const char *> { class HaierAlarmStartTrigger : public Trigger<uint8_t, const char *> {

View File

@@ -376,8 +376,10 @@ haier_protocol::HaierMessage Smartair2Climate::get_control_message() {
} }
} }
} }
out_data->display_status = this->display_status_ ? 0 : 1; out_data->display_status = this->get_display_state() ? 0 : 1;
out_data->health_mode = this->health_mode_ ? 1 : 0; this->display_status_ = (SwitchState) ((uint8_t) this->display_status_ & 0b01);
out_data->health_mode = this->get_health_mode() ? 1 : 0;
this->health_mode_ = (SwitchState) ((uint8_t) this->health_mode_ & 0b01);
return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, 0x4D5F, control_out_buffer, return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, 0x4D5F, control_out_buffer,
sizeof(smartair2_protocol::HaierPacketControl)); sizeof(smartair2_protocol::HaierPacketControl));
} }
@@ -446,28 +448,26 @@ haier_protocol::HandlerError Smartair2Climate::process_status_message_(const uin
} }
should_publish = should_publish || (!old_fan_mode.has_value()) || (old_fan_mode.value() != fan_mode.value()); should_publish = should_publish || (!old_fan_mode.has_value()) || (old_fan_mode.value() != fan_mode.value());
} }
{ // Display status
// Display status // should be before "Climate mode" because it is changing this->mode
// should be before "Climate mode" because it is changing this->mode if (packet.control.ac_power != 0) {
if (packet.control.ac_power != 0) { // if AC is off display status always ON so process it only when AC is on
// if AC is off display status always ON so process it only when AC is on bool disp_status = packet.control.display_status == 0;
bool disp_status = packet.control.display_status == 0; if (disp_status != this->get_display_state()) {
if (disp_status != this->display_status_) { // Do something only if display status changed
// Do something only if display status changed if (this->mode == CLIMATE_MODE_OFF) {
if (this->mode == CLIMATE_MODE_OFF) { // AC just turned on from remote need to turn off display
// AC just turned on from remote need to turn off display this->force_send_control_ = true;
this->force_send_control_ = true; } else if ((((uint8_t) this->health_mode_) & 0b10) == 0) {
} else { this->display_status_ = disp_status ? SwitchState::ON : SwitchState::OFF;
this->display_status_ = disp_status;
}
} }
} }
} }
{ // Health mode
// Health mode if ((((uint8_t) this->health_mode_) & 0b10) == 0) {
bool old_health_mode = this->health_mode_; bool old_health_mode = this->get_health_mode();
this->health_mode_ = packet.control.health_mode == 1; this->health_mode_ = packet.control.health_mode == 1 ? SwitchState::ON : SwitchState::OFF;
should_publish = should_publish || (old_health_mode != this->health_mode_); should_publish = should_publish || (old_health_mode != this->get_health_mode());
} }
{ {
// Climate mode // Climate mode

View File

@@ -0,0 +1,91 @@
import esphome.codegen as cg
import esphome.config_validation as cv
import esphome.final_validate as fv
from esphome.components import switch
from esphome.const import (
CONF_BEEPER,
CONF_DISPLAY,
ENTITY_CATEGORY_CONFIG,
)
from ..climate import (
CONF_HAIER_ID,
CONF_PROTOCOL,
HaierClimateBase,
haier_ns,
PROTOCOL_HON,
)
CODEOWNERS = ["@paveldn"]
BeeperSwitch = haier_ns.class_("BeeperSwitch", switch.Switch)
HealthModeSwitch = haier_ns.class_("HealthModeSwitch", switch.Switch)
DisplaySwitch = haier_ns.class_("DisplaySwitch", switch.Switch)
QuietModeSwitch = haier_ns.class_("QuietModeSwitch", switch.Switch)
# Haier switches
CONF_HEALTH_MODE = "health_mode"
CONF_QUIET_MODE = "quiet_mode"
# Additional icons
ICON_LEAF = "mdi:leaf"
ICON_LED_ON = "mdi:led-on"
ICON_VOLUME_HIGH = "mdi:volume-high"
ICON_VOLUME_OFF = "mdi:volume-off"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_HAIER_ID): cv.use_id(HaierClimateBase),
cv.Optional(CONF_DISPLAY): switch.switch_schema(
DisplaySwitch,
icon=ICON_LED_ON,
entity_category=ENTITY_CATEGORY_CONFIG,
default_restore_mode="DISABLED",
),
cv.Optional(CONF_HEALTH_MODE): switch.switch_schema(
HealthModeSwitch,
icon=ICON_LEAF,
default_restore_mode="DISABLED",
),
# Beeper switch is only supported for HonClimate
cv.Optional(CONF_BEEPER): switch.switch_schema(
BeeperSwitch,
icon=ICON_VOLUME_HIGH,
entity_category=ENTITY_CATEGORY_CONFIG,
default_restore_mode="DISABLED",
),
# Quiet mode is only supported for HonClimate
cv.Optional(CONF_QUIET_MODE): switch.switch_schema(
QuietModeSwitch,
icon=ICON_VOLUME_OFF,
entity_category=ENTITY_CATEGORY_CONFIG,
default_restore_mode="DISABLED",
),
}
)
def _final_validate(config):
full_config = fv.full_config.get()
for switch_type in [CONF_BEEPER, CONF_QUIET_MODE]:
# Check switches that are only supported for HonClimate
if config.get(switch_type):
climate_path = full_config.get_path_for_id(config[CONF_HAIER_ID])[:-1]
climate_conf = full_config.get_config_for_path(climate_path)
protocol_type = climate_conf.get(CONF_PROTOCOL)
if protocol_type.casefold() != PROTOCOL_HON.casefold():
raise cv.Invalid(
f"{switch_type} switch is only supported for hon climate"
)
return config
FINAL_VALIDATE_SCHEMA = _final_validate
async def to_code(config):
parent = await cg.get_variable(config[CONF_HAIER_ID])
for switch_type in [CONF_DISPLAY, CONF_HEALTH_MODE, CONF_BEEPER, CONF_QUIET_MODE]:
if conf := config.get(switch_type):
sw_var = await switch.new_switch(conf)
await cg.register_parented(sw_var, parent)
cg.add(getattr(parent, f"set_{switch_type}_switch")(sw_var))

View File

@@ -0,0 +1,14 @@
#include "beeper.h"
namespace esphome {
namespace haier {
void BeeperSwitch::write_state(bool state) {
if (this->parent_->get_beeper_state() != state) {
this->parent_->set_beeper_state(state);
}
this->publish_state(state);
}
} // namespace haier
} // namespace esphome

View File

@@ -0,0 +1,18 @@
#pragma once
#include "esphome/components/switch/switch.h"
#include "../hon_climate.h"
namespace esphome {
namespace haier {
class BeeperSwitch : public switch_::Switch, public Parented<HonClimate> {
public:
BeeperSwitch() = default;
protected:
void write_state(bool state) override;
};
} // namespace haier
} // namespace esphome

View File

@@ -0,0 +1,14 @@
#include "display.h"
namespace esphome {
namespace haier {
void DisplaySwitch::write_state(bool state) {
if (this->parent_->get_display_state() != state) {
this->parent_->set_display_state(state);
}
this->publish_state(state);
}
} // namespace haier
} // namespace esphome

View File

@@ -0,0 +1,18 @@
#pragma once
#include "esphome/components/switch/switch.h"
#include "../haier_base.h"
namespace esphome {
namespace haier {
class DisplaySwitch : public switch_::Switch, public Parented<HaierClimateBase> {
public:
DisplaySwitch() = default;
protected:
void write_state(bool state) override;
};
} // namespace haier
} // namespace esphome

View File

@@ -0,0 +1,14 @@
#include "health_mode.h"
namespace esphome {
namespace haier {
void HealthModeSwitch::write_state(bool state) {
if (this->parent_->get_health_mode() != state) {
this->parent_->set_health_mode(state);
}
this->publish_state(state);
}
} // namespace haier
} // namespace esphome

View File

@@ -0,0 +1,18 @@
#pragma once
#include "esphome/components/switch/switch.h"
#include "../haier_base.h"
namespace esphome {
namespace haier {
class HealthModeSwitch : public switch_::Switch, public Parented<HaierClimateBase> {
public:
HealthModeSwitch() = default;
protected:
void write_state(bool state) override;
};
} // namespace haier
} // namespace esphome

View File

@@ -0,0 +1,14 @@
#include "quiet_mode.h"
namespace esphome {
namespace haier {
void QuietModeSwitch::write_state(bool state) {
if (this->parent_->get_quiet_mode_state() != state) {
this->parent_->set_quiet_mode_state(state);
}
this->publish_state(state);
}
} // namespace haier
} // namespace esphome

View File

@@ -0,0 +1,18 @@
#pragma once
#include "esphome/components/switch/switch.h"
#include "../hon_climate.h"
namespace esphome {
namespace haier {
class QuietModeSwitch : public switch_::Switch, public Parented<HonClimate> {
public:
QuietModeSwitch() = default;
protected:
void write_state(bool state) override;
};
} // namespace haier
} // namespace esphome

View File

@@ -1,6 +1,7 @@
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
#include "hmac_md5.h" #include "hmac_md5.h"
#ifdef USE_MD5
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
namespace esphome { namespace esphome {
@@ -54,3 +55,4 @@ bool HmacMD5::equals_hex(const char *expected) { return this->ohash_.equals_hex(
} // namespace hmac_md5 } // namespace hmac_md5
} // namespace esphome } // namespace esphome
#endif

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