1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-01 15:41:52 +00:00

Compare commits

...

185 Commits

Author SHA1 Message Date
Jesse Hills
e006045f59 Merge pull request #1921 from esphome/bump-1.19.0b6
1.19.0b6
2021-06-16 15:42:43 +12:00
Jesse Hills
fff3645901 Bump version to v1.19.0b6 2021-06-16 13:51:31 +12:00
Jesse Hills
a5383fd208 Shorten the ble name to prevent crash with long device names (#1920) 2021-06-16 13:51:31 +12:00
Jesse Hills
9ce3a2059f Merge pull request #1919 from esphome/bump-1.19.0b5
1.19.0b5
2021-06-16 09:45:10 +12:00
Jesse Hills
69e6cf2c0c Bump version to v1.19.0b5 2021-06-16 09:06:50 +12:00
Paulus Schoutsen
28635124f9 Bump dashboard to 20210615.0 (#1918) 2021-06-16 09:06:50 +12:00
Jesse Hills
035be87a83 Merge pull request #1914 from esphome/bump-1.19.0b4
1.19.0b4
2021-06-15 20:47:38 +12:00
Jesse Hills
e8bdbc45a9 Bump version to v1.19.0b4 2021-06-15 20:26:53 +12:00
Guillermo Ruffino
429caccefa fixes compatibility with esphome cfg vscode (#1911) 2021-06-15 20:26:53 +12:00
Paulus Schoutsen
744ca1af7c Bump frontend to 20210614.0 (#1912) 2021-06-15 20:26:52 +12:00
Jesse Hills
106f0d611f Validate that either networks, ap, or improv is set up (#1910) 2021-06-15 20:26:52 +12:00
Jesse Hills
d826416684 Allow no networks or AP to be set. (#1908)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2021-06-15 20:26:52 +12:00
Jesse Hills
81959804df Merge pull request #1900 from esphome/bump-1.19.0b3
1.19.0b3
2021-06-12 11:15:03 +12:00
Jesse Hills
75b524ddc4 Fix formatting from cherry-pick conflict 2021-06-12 11:07:58 +12:00
Jesse Hills
f599c36272 Bump version to v1.19.0b3 2021-06-12 11:03:09 +12:00
Paulus Schoutsen
9bb64315f3 Add new wizard + allow installing firmware over webserial (#1887) 2021-06-12 11:03:09 +12:00
Jesse Hills
575badc690 Move esp32_ble_server to its own component (#1898) 2021-06-12 11:02:59 +12:00
Jesse Hills
4b91cfb7f9 Ensure wifi is in at least station mode before starting improv (#1899) 2021-06-12 11:01:54 +12:00
Jesse Hills
a57a842f7b Merge pull request #1888 from esphome/bump-1.19.0b2
1.19.0b2
2021-06-10 17:50:38 +12:00
Jesse Hills
a8c253a2a5 Bump version to v1.19.0b2 2021-06-10 17:17:52 +12:00
Geoff Davis
8b737aabd9 Add support for waveshare_epaper 1.54v2 (#1843)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-06-10 17:17:52 +12:00
Jesse Hills
0db4815f3d BLE loop use (#1882) 2021-06-10 17:17:52 +12:00
Oxan van Leeuwen
139db58a66 Simplify LightCall validation (#1874) 2021-06-10 17:17:52 +12:00
Jesse Hills
c32fec7432 Merge pull request #1883 from esphome/bump-1.19.0b1
1.19.0b1
2021-06-09 19:25:57 +12:00
Jesse Hills
8bd23dd457 Bump version to v1.19.0b1 2021-06-09 18:59:06 +12:00
Jesse Hills
97a12c0169 Bump version to v1.20.0-dev 2021-06-09 17:23:42 +12:00
René Klomp
635916737b Update total_pulses_ at every detected pulse (#1875) 2021-06-09 16:48:51 +12:00
Jesse Hills
65c50e4f01 Add platform and board to mdns response when API is used (#1871) 2021-06-09 14:23:48 +12:00
Jesse Hills
5cf18235e3 Allow setting creator project name and version into code (#1872) 2021-06-09 13:04:00 +12:00
Stefan Agner
b80f3fdec9 Fix Clang 11 finds (#1880) 2021-06-09 08:47:43 +12:00
Jesse Hills
0426be9280 Fixes for BLE/improv (#1878) 2021-06-09 08:45:51 +12:00
Otto Winter
7c678659d4 Remove explain changes section from PR template (#1876) 2021-06-08 22:23:23 +02:00
Stefan Agner
13fe9e83fa Use Clang 11 (#1846) 2021-06-08 22:16:17 +02:00
Otto Winter
4711f36a1f Bump base image to 3.4.0 (#1879) 2021-06-08 22:03:04 +02:00
Otto Winter
01e2a51132 Implement versioning for esphome/esphome-lint docker images (#1877) 2021-06-08 21:28:19 +02:00
Jesse Hills
a70a205ace Improv - BLE WiFi provisioning (#1807)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-06-08 11:56:21 +12:00
Oxan van Leeuwen
33625e2dd3 CLI user experience improvements (#1805)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-06-08 11:14:12 +12:00
Stefan Agner
0277218319 Bump Docker base version to 3.1.0 (#1864)
Bump Docker base version to 3.1.0 which includes Arduino SDK 1.0.6
for ESP32.
2021-06-08 07:02:31 +12:00
Stefan Agner
18a8c727fa Fix SCD30 configuration on ESP32 (#1830) 2021-06-05 20:56:54 +12:00
Stefan Agner
80ad784a4e Avoid unnecessary waits to stabilize the VOC algorithm (#1834) 2021-06-05 20:52:16 +12:00
Lumpusz
ebadaa9660 Add preset, custom_preset and custom_fan_mode support to climate (#1471)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-06-04 22:04:54 +12:00
Samuel Sieb
7bc51582f0 make crc16 function accessible (#1857) 2021-06-03 16:13:42 +12:00
Franck Nijhof
11fb54c74e Add support for Sensor state class (#1835)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-06-03 13:49:56 +12:00
Stefan Rado
913ac8b7e8 Support raw datapoints for tuya components (#1669) 2021-06-03 13:10:29 +12:00
foxsam21
2b9350ce76 Added vol +/- control to dfplayer (#1856)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-06-03 10:12:23 +12:00
André Klitzing
3b18f1b87f Use size_t for length parameter (#1799) 2021-06-03 10:11:41 +12:00
Trevor North
c5c24c1989 Tuya improvements (#1491)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-06-02 21:31:56 +12:00
Ryan Mounce
c3938d04f3 Support Tuya light color temperature control (#1412)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-06-02 09:32:16 +12:00
David Kiliani
f968713be8 Add optional lambda to BLESensor for raw data parsing (#1851)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-06-01 21:46:54 +12:00
Jesse Hills
7b11284008 Fix ble client esp_gatt_if comparison (#1852) 2021-06-01 21:12:41 +12:00
zaluthar
f39c0d52ee Added support for Xiaomi CGDK2 (#1451)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-06-01 20:26:38 +12:00
buxtronix
a3756a9600 Copy missing BLE client characteristic read data (#1818)
Co-authored-by: Ben Buxton <bb@cactii.net>
2021-06-01 19:56:05 +12:00
0hax
afa436fe8f teleinfo: use text_sensor and sensor. (#1403)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Otto winter <otto@otto-winter.com>
2021-06-01 13:32:09 +12:00
Roberto Wagner
48b5ea9e59 Update fingerprint count after enroll (#1811) 2021-06-01 12:22:09 +12:00
Maurice Makaay
56974153f1 I2c raw cmds with multiplexer (#1817)
Co-authored-by: Maurice Makaay <mmakaay1@xs4all.net>
2021-06-01 10:36:25 +12:00
Jesse Hills
9a2cd71571 Don't check uart settings for modbus (#1850) 2021-06-01 09:57:03 +12:00
Stefan Agner
d1c6368283 Use built-in validation for altitude (#1831)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-06-01 07:40:57 +12:00
Adrián Panella
5c3268b8d4 Rf Bridge: add bucket sniffing and beep functionality (#1819)
Co-authored-by: Otto winter <otto@otto-winter.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-06-01 07:24:22 +12:00
Oxan van Leeuwen
25af5ab7c6 Drop 128x160 ESP-32 camera resolution (#1813) 2021-05-31 22:27:41 +12:00
Andrew Zaborowski
4d586b1446 Add CS5460A power-meter component (#1474)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-05-31 16:07:33 +12:00
polyfaces
bb759d52c8 Add support for SDMXXX energy meters (#1260)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-05-31 16:05:49 +12:00
testbughub
9a2cf05c5f Added bottom segment to digit 9 (#1847) 2021-05-30 20:54:26 -03:00
Guillermo Ruffino
c79d700d03 Add validate to components (#1631)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-05-31 10:06:45 +12:00
Thomas Dietrich
482a3aebc9 Fix typo in wizard (#1836) 2021-05-28 13:34:54 +12:00
Guillermo Ruffino
387f249363 fix dallas pin validation (#1692) 2021-05-28 13:15:52 +12:00
Guillermo Ruffino
3d917d0b7e lambda condition should return (#1833) 2021-05-26 08:03:59 +12:00
Oxan van Leeuwen
824f3187ac Update platformio.ini settings and fix test apps (#1815)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Otto Winter <otto@otto-winter.com>
2021-05-25 07:47:45 +12:00
Otto Winter
e3c27a483c Update sensor device classes from HA (#1825) 2021-05-24 21:45:51 +02:00
Otto Winter
a33bb32874 Convert components to async-def syntax (#1823)
* Convert components to async-def syntax

* Remove stray @coroutine

* Manual part

* Convert complexer components code to async-def

* Manual cleanup

* More manual cleanup
2021-05-24 21:45:31 +02:00
dependabot[bot]
93d9d4b50a Bump protobuf from 3.15.8 to 3.17.0 (#1776)
Bumps [protobuf](https://github.com/protocolbuffers/protobuf) from 3.15.8 to 3.17.0.
- [Release notes](https://github.com/protocolbuffers/protobuf/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf/blob/master/generate_changelog.py)
- [Commits](https://github.com/protocolbuffers/protobuf/compare/v3.15.8...v3.17.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-24 11:23:29 +02:00
Otto Winter
2376a2c941 Convert components to async-def syntax (#1821) 2021-05-24 10:58:29 +02:00
Otto Winter
b92702a312 Document considerations when changing recommended framework version (#1822) 2021-05-23 23:24:00 +02:00
Jim Bauwens
b11d5f6799 Allow segments in a light partition to be reversed (#1484)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-05-24 08:57:48 +12:00
Stanislav Meduna
072dce340e Add the on_page_change display trigger (#1687)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-05-24 08:56:04 +12:00
Trevor North
cccb1a2c9e BME680 BSEC: Allow sample rate overrides for T/P/H sensors (#1710) 2021-05-24 08:27:19 +12:00
Stanislav Meduna
063d9c47a4 Refactor font creation to save stack (#1707) 2021-05-24 08:24:54 +12:00
wifwucite
8d8d421286 allow default option for typed_schema (#1700)
Co-authored-by: Otto Winter <otto@otto-winter.com>
2021-05-24 08:24:24 +12:00
Franck Nijhof
0ce57e5a39 Light & Switch Inverted Restore mode (#1810) 2021-05-24 08:20:11 +12:00
Otto Winter
aebad04c0b Convert core components to async-def coroutine syntax (#1658)
Co-authored-by: Guillermo Ruffino <glm.net@gmail.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-05-23 22:10:30 +02:00
Anthony Uk
514d11d46f tm1637 - support 6 character displays (#1803) 2021-05-21 20:04:25 -03:00
Anthony Uk
96e46db272 Added fan triggers on_turn_on and on_turn_off (#1726) 2021-05-19 16:12:43 +12:00
Stefan Agner
76f78877f6 Use latest version of the NeoPixelBus-esphome library (#1701) 2021-05-19 14:55:49 +12:00
Jesse Hills
4ffa68b773 Merge branch 'master' into dev 2021-05-19 14:02:04 +12:00
Jesse Hills
8eaffee160 Merge pull request #1801 from esphome/bump-1.18.0
1.18.0
2021-05-19 12:59:13 +12:00
Jesse Hills
557a622f71 Bump version to v1.18.0 2021-05-19 11:43:47 +12:00
Jesse Hills
e9c6556296 Merge branch 'beta' into bump-1.18.0 2021-05-19 11:43:47 +12:00
André Klitzing
dce9d59dfe Do not use Serial2 for ESP32C3, too (#1798)
src/esphome/components/logger/logger.cpp: In member function 'void esphome::logger::Logger::pre_setup()':
src/esphome/components/logger/logger.cpp:142:29: error: 'Serial2' was not declared in this scope
         this->hw_serial_ = &Serial2;
2021-05-18 19:16:51 +12:00
romerod
d3e291b442 Add on_tag_removed trigger to pn532 (#1436)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-05-18 11:54:09 +12:00
Otto Winter
d4686c0fb1 Introduce new async-def coroutine syntax (#1657) 2021-05-17 17:14:15 +12:00
Jesse Hills
ae9b247f47 Merge pull request #1792 from esphome/bump-1.18.0b4
1.18.0b4
2021-05-17 14:44:49 +12:00
Jesse Hills
a59761d292 Bump version to v1.18.0b4 2021-05-17 13:53:35 +12:00
Jesse Hills
030c87d142 Allow RC522 components to have multiple configurations (#1782) 2021-05-17 13:53:35 +12:00
Jesse Hills
95ed3e9d46 Revert "Added bottom segment to digit 9 (#1787)" (#1791)
This reverts commit 9ecead2645.
2021-05-17 13:22:49 +12:00
Stanislav Meduna
d0eaebe19f Add support for the XPT2046 touchscreen controller (#1542) 2021-05-17 13:03:58 +12:00
testbughub
9ecead2645 Added bottom segment to digit 9 (#1787) 2021-05-17 11:38:49 +12:00
Stefan Agner
98166dfa66 Bump Arduino SDK for ESP32 to 1.0.6 (#1789)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-05-17 11:38:26 +12:00
Franck Nijhof
5645be4e0f Add attribute support to Home Assistant sensors (#1770)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-05-17 11:16:22 +12:00
Jesse Hills
9a7a205510 Generate protobuf code closer to formatted files (#1790) 2021-05-17 10:54:17 +12:00
Jesse Hills
7e3b8fd346 Allow RC522 components to have multiple configurations (#1782) 2021-05-15 17:02:52 +12:00
Guillermo Ruffino
3d6dcc9eee Add more json schema generation features (#1690)
* some enums

* extract enums, light effects remote_receiver etc

* more pins schema

* update to core changes
2021-05-14 20:35:39 -03:00
Jesse Hills
4f6982fbc5 Add action to set total pulses on pulse_meter (#1757) 2021-05-14 20:06:31 +12:00
Stanislav Meduna
00c144daeb Autorepeat filter for the binary sensors (#1681)
* add the autorepeat filter

* add a test for the autorepeat filter

* make no timing equivalent to a single default one
2021-05-13 17:36:53 -03:00
Jesse Hills
fddb05c845 Merge pull request #1775 from esphome/bump-1.18.0b3
1.18.0b3
2021-05-13 07:48:44 +12:00
Jesse Hills
3b13e62d3f Bump version to v1.18.0b3 2021-05-13 07:17:14 +12:00
BoukeHaarsma23
9e7acd6f75 Add sm2135 component (#1736)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-05-13 07:17:13 +12:00
Yuval Aboulafia
7d9c043f1d Use core constants for sample duration on bh1750 (#1764)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-05-13 07:17:13 +12:00
Guillermo Ruffino
bafbeefb37 Switch to esphome/AsyncTCP-esphome v1.2.2. (#1762) 2021-05-13 07:17:13 +12:00
BoukeHaarsma23
54660300e9 Add sm2135 component (#1736)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-05-13 07:14:01 +12:00
onde2rock
5dc40049be Add function to set SDS011 sensor in sleeping state (#1416)
Co-authored-by: aurelien <aurelien.antunes@gmail.com>
2021-05-12 16:38:29 +12:00
RubyBailey
1e46b4073f Mitsubishi Heat Pump - Fixed default transmit_state to be generic instead of for a specific type of heat pump (#1414)
Co-authored-by: RubyBailey <ruby_bailey11@hotmail.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-05-12 16:07:28 +12:00
krunkel
29fc4af0fc Add delay to aht10.cpp (#1498) 2021-05-12 15:50:26 +12:00
dependabot[bot]
4030a2e253 Bump pytest from 6.2.3 to 6.2.4 (#1769)
Bumps [pytest](https://github.com/pytest-dev/pytest) from 6.2.3 to 6.2.4.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/6.2.3...6.2.4)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-12 15:44:33 +12:00
dependabot[bot]
ea80cb751b Bump flake8 from 3.9.0 to 3.9.2 (#1763)
Bumps [flake8](https://gitlab.com/pycqa/flake8) from 3.9.0 to 3.9.2.
- [Release notes](https://gitlab.com/pycqa/flake8/tags)
- [Commits](https://gitlab.com/pycqa/flake8/compare/3.9.0...3.9.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-12 15:33:20 +12:00
dependabot[bot]
6aa61dbce7 Bump black from 21.5b0 to 21.5b1 (#1768)
Bumps [black](https://github.com/psf/black) from 21.5b0 to 21.5b1.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/commits)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-12 15:25:23 +12:00
Yuval Aboulafia
cdc9c99d40 Use core constants for sample duration on bh1750 (#1764)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-05-12 10:16:59 +12:00
Jesse Hills
07f2931841 Merge pull request #1771 from esphome/bump-1.18.0b2
1.18.0b2
2021-05-12 10:14:41 +12:00
Jesse Hills
616ad04131 Bump version to v1.18.0b2 2021-05-12 09:57:40 +12:00
André Klitzing
b966e58f9e Fix build issues for idf 4.2 (Support ESP32-S2) (#1433)
Co-authored-by: Otto winter <otto@otto-winter.com>
2021-05-12 09:57:39 +12:00
André Klitzing
a546677b08 Fix build issues for idf 4.2 (Support ESP32-S2) (#1433)
Co-authored-by: Otto winter <otto@otto-winter.com>
2021-05-12 08:31:40 +12:00
Jesse Hills
5c3af1d3f6 Allow duration for deep_sleep.enter to be templateable (#1765) 2021-05-11 12:36:14 -03:00
Ciprian Constantinescu
66b0b6feeb Update const.py (#1748)
* Update const.py

Added cubic meter for https://github.com/esphome/feature-requests/issues/1185

* Update const.py

ordered alphabetically
2021-05-11 12:16:51 -03:00
Maurice Makaay
7665a220a0 Fix error when using %% in printf format. (#1713)
For printf formatting, a check is done to see if the number of
arguments matches the number of printf formatting placeholders.

The escape code `%%` that is used for representing a literal `%`
is also counted as a placeholder, but no argument will be provided
for that one.

This makes it impossible to use something like `("%f%%", percentage)`
in the code. In such case, one gets the error:

  `Found 2 printf-patterns (%f, %%), but 1 args were given!`

This commit fixes this behavior by omitting the `%%` from the matches.

Co-authored-by: Maurice Makaay <mmakaay1@xs4all.net>
2021-05-11 12:15:29 -03:00
Guillermo Ruffino
4250af4dd9 Switch to esphome/AsyncTCP-esphome v1.2.2. (#1762) 2021-05-11 12:05:49 -03:00
dependabot[bot]
73252ccd25 Bump pylint from 2.7.2 to 2.8.2 (#1729)
* Bump pylint from 2.7.2 to 2.8.2

Bumps [pylint](https://github.com/PyCQA/pylint) from 2.7.2 to 2.8.2.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Changelog](https://github.com/PyCQA/pylint/blob/master/ChangeLog)
- [Commits](https://github.com/PyCQA/pylint/compare/pylint-2.7.2...v2.8.2)

Signed-off-by: dependabot[bot] <support@github.com>

* fix pylint

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Guillermo Ruffino <glm.net@gmail.com>
2021-05-10 17:57:25 -03:00
dependabot[bot]
33bf78c369 Bump black from 20.8b1 to 21.5b0 (#1745)
Bumps [black](https://github.com/psf/black) from 20.8b1 to 21.5b0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/master/CHANGES.md)
- [Commits](https://github.com/psf/black/commits)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-10 17:25:37 -03:00
Jesse Hills
f33c2a48eb Merge pull request #1761 from esphome/bump-1.18.0b1
1.18.0b1
2021-05-10 08:54:58 +12:00
Jesse Hills
96ded4e402 Fix quotes 2021-05-10 08:05:08 +12:00
Jesse Hills
44d5be6e7b Bump version to v1.18.0b1 2021-05-10 07:54:06 +12:00
Jesse Hills
076124eb71 Bump version to v1.19.0-dev 2021-05-10 07:54:06 +12:00
Jesse Hills
44562dbef1 Merge branch 'dev' into bump-1.18.0b1 2021-05-10 07:54:06 +12:00
Mauricio Bonani
cafdcaec29 Update copyright year (#1760) 2021-05-10 07:40:06 +12:00
Jesse Hills
b660e5a7fa Merge pull request #1759 from esphome/bump-1.17.2
1.17.2
2021-05-09 22:58:57 +12:00
Jesse Hills
3b4ea0ed0a Bump version to v1.17.2 2021-05-09 22:37:31 +12:00
Vegetto
403b6e32e3 fixes #858 - esphome crashes with neolightbus and RMT (#1667)
Co-authored-by: Otto Winter <otto@otto-winter.com>
2021-05-09 22:37:31 +12:00
Otto Winter
229bf719a2 Implement external custom components installing from YAML (#1630)
* Move components import loading to importlib MetaPathFinder and importlib.resources

* Add external_components component

* Fix

* Fix

* fix cv.url return

* fix validate shorthand git

* implement git refresh

* Use finders from sys.path_hooks instead of looking for __init__.py

* use github:// schema

* error handling

* add test

* fix handling git output

* revert file check handling

* fix test

* allow full component path be specified for local

* fix test

* fix path handling

* lint

Co-authored-by: Guillermo Ruffino <glm.net@gmail.com>
2021-05-07 15:02:17 -03:00
Farzad E
2225594ee8 Added an option to disable mDNS (#1716)
* Added an option to disable mDNS

* Fixed linter issues

* Moved the enable_mdns option to WiFi and Ethernet components

* extracted common method for add mdns library

* lint

Co-authored-by: Guillermo Ruffino <glm.net@gmail.com>
2021-05-06 12:31:42 -03:00
mbo18
f7232b199a Change wifi signal strength unit to dBm (#1734) 2021-05-05 20:13:14 +12:00
Ryan Mounce
ffc6fe9ca0 Add support for controlling Tuya fan direction (#1409) 2021-05-05 16:24:31 +12:00
Alex Konradi
06b8e4df27 Call Stepper::should_step_ every loop iteration (#1373) 2021-05-05 14:32:15 +12:00
Alex
b103be99e8 Do not call component update on failed components (#1392)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-05-05 14:31:38 +12:00
d-two
28ed42d879 Add Hyperion Support (#1339) 2021-05-05 14:09:38 +12:00
0x0a11c0de
98f0d75180 Fix #1940: Implement speed_count in TuyaFan (#1654)
Co-authored-by: Frank Riley <fhriley@gmail.com>
2021-05-04 14:11:24 +12:00
buxtronix
dd4fb85170 Ble client fixes (#1739)
Co-authored-by: Ben Buxton <bb@cactii.net>
2021-05-04 08:51:03 +12:00
Jesse Hills
07b3327102 Update email addresses (#1733) 2021-05-03 11:51:10 +12:00
buxtronix
02aa75f68c BLE client support on ESP32 (#1177)
Co-authored-by: Ben Buxton <bb@cactii.net>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-05-03 11:10:50 +12:00
Alex
07db9319ad Swap fan and swing fields for Fujitu ACs (#1635) 2021-04-30 11:53:44 +12:00
Wojtek Strzalka
4ae4a4ee88 Support for TOF10120 distance sensor (#1375) 2021-04-29 11:49:46 +12:00
Guillermo Ruffino
a7c648b60b RC522 fixes (#1479) 2021-04-29 10:50:24 +12:00
Barry Loong
4d7c1ae143 Add Grow Fingerprint Reader (#1356) 2021-04-29 10:08:27 +12:00
elyorkhakimov
cc6d1e85cc Addition of forward and reverse active energy counters to ATM90E32 sensor component (#1271)
Co-authored-by: Elyor Khakimov <elyorkhakimov@forwardnetworks.com>
2021-04-29 07:15:50 +12:00
Stephen Tierney
7fb116d87d Add support for SHT4X (#1512) 2021-04-28 20:22:46 +12:00
Christian Ferbar
cc43e01e34 Add monochromatic effects: Pulse, Random (#1616) 2021-04-28 15:05:04 +12:00
Guillermo Ruffino
6d3ccf4df5 Change mac add mac suffix from underscore to dash (#1702) 2021-04-27 13:41:16 +12:00
Guillermo Ruffino
bb3d0706d3 Revert "Bump AsyncTCP-esphome to 1.2.1. (#1693)" (#1709)
This reverts commit aed6f2b1ea.
2021-04-21 10:33:59 -03:00
Richard Klingler
820dedbcd2 Added / to default glyphs (#1691)
Co-authored-by: Charlie Root <klingler@blender.klingler.net>
2021-04-21 12:10:45 +02:00
Maurice Makaay
aed6f2b1ea Bump AsyncTCP-esphome to 1.2.1. (#1693)
Co-authored-by: Maurice Makaay <mmakaay1@xs4all.net>
2021-04-19 20:15:37 -03:00
John Coggeshall
bf1885af3f Implementing the remainder of GPS data for the GPS component. (#1676) 2021-04-11 19:15:23 +02:00
Otto Winter
d8e4f5d56b Sensor Average Filter Fix Floating Pointer Error Accumulating (#1624) 2021-04-09 10:27:18 +02:00
dependabot[bot]
af616473aa Bump protobuf from 3.15.7 to 3.15.8 (#1682)
Bumps [protobuf](https://github.com/protocolbuffers/protobuf) from 3.15.7 to 3.15.8.
- [Release notes](https://github.com/protocolbuffers/protobuf/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf/blob/master/generate_changelog.py)
- [Commits](https://github.com/protocolbuffers/protobuf/compare/v3.15.7...v3.15.8)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-09 10:14:38 +02:00
SenexCrenshaw
2033ac34e5 Sgp40 (#1513)
* Start of SGP40 dev

* Clean up

* Initial Commit

* VOC is working

* Fixed up sensor config

* Lint Fixes
Added in save/restore baseline
Noted original repo in header

* Lint Fixes
Added to test

* Lint Fixes

* Added additional check on restoring

* Removed double check

* Changed defines to static const double

* Changed defines to const
Do not send voc index until sensor stabilizes

* Fixed sensor stabilization message

* Fixup according to PR

* samples_read increment fix

* Fixed missing device class

* Choose a SENSOR device class

* Moved some sensors for tests

Co-authored-by: Guillermo Ruffino <glm.net@gmail.com>
2021-04-08 22:40:19 -03:00
Otto Winter
5e239d3d88 Automate building and publishing of esphome-lint docker image (#1680) 2021-04-08 17:08:11 +02:00
Otto Winter
30893afd67 Add Arduino ESP32 version mapping (#1679)
See also https://github.com/platformio/platform-espressif32/releases/tag/v3.2.0
2021-04-08 17:07:19 +02:00
SenexCrenshaw
a39bb7b92f Support custom build_flags for bme680_bsec (#1678) 2021-04-08 17:06:54 +02:00
Dan Gentry
b53f9f2a81 Daylight Saving Time spelling fix (#1677) 2021-04-08 17:04:40 +02:00
Otto Winter
586e36906d Fix sensor.sensor_schema interface changed (#1659) 2021-04-08 14:37:55 +02:00
Gareth Cooper
e6f8e73705 Receive long MQTT payload (#1590) 2021-04-08 14:26:34 +02:00
Peter Kuehne
eaf9735eda Disallow _ in node name (#1632) 2021-04-08 14:26:01 +02:00
Guillermo Ruffino
2e50e1f506 Fix servo detach chopped PWM (#1650) 2021-04-08 14:22:30 +02:00
Anthony Uk
76a6c39f25 mqtt_client: Added MQTTClientComponent::unsubscribe() (#1672)
Co-authored-by: Otto Winter <otto@otto-winter.com>
2021-04-08 14:21:42 +02:00
rbaron
bf01c22e1f Adds support for b-parasite soil moisture sensor (#1666) 2021-04-08 13:59:30 +02:00
Otto Winter
99f14e03d4 Fix colorlog removing colors and refactor color code (#1671) 2021-04-08 13:58:01 +02:00
Otto Winter
d92c8ccadf Raise minimum python version to 3.7 (#1673) 2021-04-08 13:57:29 +02:00
Otto Winter
7964b724ed Rewrite sun component calculations (#1661) 2021-04-07 12:16:36 +02:00
dependabot[bot]
e0c5b45694 Bump protobuf from 3.15.6 to 3.15.7 (#1662)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Otto Winter <otto@otto-winter.com>
2021-04-06 14:43:10 +02:00
dependabot[bot]
26407e001b Bump pytest from 6.2.2 to 6.2.3 (#1663)
Bumps [pytest](https://github.com/pytest-dev/pytest) from 6.2.2 to 6.2.3.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/6.2.2...6.2.3)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Otto Winter <otto@otto-winter.com>
2021-04-06 14:42:50 +02:00
Vegetto
3ecae3f16f fixes #858 - esphome crashes with neolightbus and RMT (#1667)
Co-authored-by: Otto Winter <otto@otto-winter.com>
2021-04-06 14:31:38 +02:00
Anthony Uk
5c359856ff Fixed CustomComponentConstructor::get_component() (#1653)
Co-authored-by: Guillermo Ruffino <glm.net@gmail.com>
2021-04-06 13:50:31 +02:00
Trevor North
2d618768d5 Add BME680 via BSEC integration (#1313) 2021-04-06 12:19:56 +02:00
Stanislav Meduna
808e3be324 Add the display.is_displaying_page condition (#1646)
* display: add the display.is_displaying_page condition

* use maybe_simple_value for page_id

* add test
2021-04-02 23:00:41 -03:00
Andreas Hergert
9e23987db8 Add I2CMultiplexer in generel and the TCA9548A in special (#1410)
* Added I2CMultiplexer in generel and the TCA9548A in special

* cleanup

* tidy

* tidy

* tidy

* tidy

* Update CODEOWNERS

* Update CODEOWNERS

* added CODEOWNERS

* Fix CODEOWNERS

* protected function

* fixed scan

* fixed style

* added to test1.yaml

* Update esphome/components/tca9548a/__init__.py

* Update esphome/components/i2c/__init__.py

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>

* Update esphome/components/i2c/i2c.cpp

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>

* Update esphome/components/i2c/__init__.py

* Update esphome/components/i2c/__init__.py

Co-authored-by: Guillermo Ruffino <glm.net@gmail.com>

* Update esphome/components/i2c/i2c.cpp

Co-authored-by: Guillermo Ruffino <glm.net@gmail.com>

* added define statements for I2C Multiplexer

* fix

* try to tidy

* bug fix

* tidy

* override fix

* only change channel if different

* tidy

* added test

* testfix

* added defines

* tidy

* fix dep

* like recommended

Co-authored-by: Andreas Hergert <andreas.hergert@otrs.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Guillermo Ruffino <glm.net@gmail.com>
2021-03-29 16:50:30 -03:00
Guillermo Ruffino
ad76312f66 fix servo not reattaching with same target (#1649) 2021-03-28 22:35:39 -03:00
SenexCrenshaw
2028362fd5 Buffer allocation and TRUEFALSE templates (#1644) 2021-03-27 11:01:37 +13:00
Paulus Schoutsen
13e0d6b9a1 Update FUNDING.yml 2021-03-26 11:39:29 -07:00
Diego Elio Pettenò
a6255c31fe Add optional bindkey support for CGG1. (#1407) 2021-03-26 07:33:06 +13:00
buxtronix
46356cbc4a Fix BLE UUID matching (#1637)
Co-authored-by: Ben Buxton <bb@cactii.net>
2021-03-23 18:21:04 +13:00
627 changed files with 18749 additions and 6979 deletions

View File

@@ -4,14 +4,35 @@ Checks: >-
-abseil-*,
-android-*,
-boost-*,
-bugprone-branch-clone,
-bugprone-macro-parentheses,
-bugprone-narrowing-conversions,
-bugprone-reserved-identifier,
-bugprone-signed-char-misuse,
-bugprone-suspicious-include,
-bugprone-too-small-loop-variable,
-bugprone-unhandled-self-assignment,
-cert-dcl37-c,
-cert-dcl50-cpp,
-cert-dcl51-cpp,
-cert-err58-cpp,
-cert-oop54-cpp,
-cert-oop57-cpp,
-cert-str34-c,
-clang-analyzer-core.CallAndMessage,
-clang-analyzer-optin.*,
-clang-analyzer-osx.*,
-clang-analyzer-security.*,
-clang-diagnostic-shadow-field,
-cppcoreguidelines-avoid-c-arrays,
-cppcoreguidelines-avoid-goto,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-avoid-non-const-global-variables,
-cppcoreguidelines-c-copy-assignment-signature,
-cppcoreguidelines-init-variables,
-cppcoreguidelines-macro-usage,
-cppcoreguidelines-narrowing-conversions,
-cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-owning-memory,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-pro-bounds-constant-array-index,
@@ -37,10 +58,16 @@ Checks: >-
-google-runtime-int,
-google-runtime-references,
-hicpp-*,
-llvm-else-after-return,
-llvm-header-guard,
-llvm-include-order,
-llvm-qualified-auto,
-llvmlibc-*,
-misc-non-private-member-variables-in-classes,
-misc-no-recursion,
-misc-unconventional-assign-operator,
-misc-unused-parameters,
-modernize-avoid-c-arrays,
-modernize-deprecated-headers,
-modernize-pass-by-value,
-modernize-pass-by-value,
@@ -48,14 +75,25 @@ Checks: >-
-modernize-use-auto,
-modernize-use-default-member-init,
-modernize-use-equals-default,
-modernize-use-trailing-return-type,
-mpi-*,
-objc-*,
-performance-unnecessary-value-param,
-readability-braces-around-statements,
-readability-const-return-type,
-readability-convert-member-functions-to-static,
-readability-else-after-return,
-readability-implicit-bool-conversion,
-readability-isolate-declaration,
-readability-magic-numbers,
-readability-make-member-function-const,
-readability-named-parameter,
-readability-qualified-auto,
-readability-redundant-access-specifiers,
-readability-redundant-member-init,
-readability-redundant-string-init,
-readability-uppercase-literal-suffix,
-readability-use-anyofallof,
-warnings-as-errors,
-zircon-*
WarningsAsErrors: '*'

7
.github/FUNDING.yml vendored
View File

@@ -1,8 +1,3 @@
# These are supported funding model platforms
github:
patreon: ottowinter
open_collective:
ko_fi:
tidelift:
custom: https://esphome.io/guides/supporters.html
custom: https://www.nabucasa.com

View File

@@ -1,25 +1,22 @@
# What does this implement/fix?
Quick description
Quick description and explanation of changes
## Types of changes
- [ ] Bugfix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Configuration change (this will require users to update their yaml configuration files to keep working)
- [ ] Other
**Related issue or feature (if applicable):** fixes <link to issue>
**Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** esphome/esphome-docs#<esphome-docs PR number goes here>
# Test Environment
## Test Environment
- [ ] ESP32
- [ ] ESP8266
- [ ] Windows
- [ ] Mac OS
- [ ] Linux
## Example entry for `config.yaml`:
<!--
@@ -34,11 +31,6 @@ Quick description
```
# Explain your changes
Describe your changes here to communicate to the maintainers **why we should accept this pull request**.
Very important to fill if no issue linked
## Checklist:
- [ ] The code change is tested and works locally.
- [ ] Tests have been added to verify that the new code works (under `tests/` folder).

View File

@@ -26,7 +26,7 @@ jobs:
- uses: actions/checkout@v2
- name: Set up env variables
run: |
base_version="3.0.0"
base_version="3.4.0"
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"

View File

@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
# cpp lint job runs with esphome-lint docker image so that clang-format-*
# doesn't have to be installed
container: esphome/esphome-lint:latest
container: esphome/esphome-lint:1.1
steps:
- uses: actions/checkout@v2
# Set up the pio project so that the cpp checks know how files are compiled
@@ -32,7 +32,7 @@ jobs:
runs-on: ubuntu-latest
# cpp lint job runs with esphome-lint docker image so that clang-format-*
# doesn't have to be installed
container: esphome/esphome-lint:latest
container: esphome/esphome-lint:1.1
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
strategy:
fail-fast: false
@@ -97,6 +97,7 @@ jobs:
- test2
- test3
- test4
- test5
steps:
- uses: actions/checkout@v2
- name: Set up Python
@@ -115,7 +116,7 @@ jobs:
uses: actions/cache@v1
with:
path: ~/.platformio
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core_config.py') }}
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }}
restore-keys: |
test-home-platformio-${{ matrix.test }}-
- name: Set up environment
@@ -126,7 +127,7 @@ jobs:
run: |
echo "::add-matcher::.github/workflows/matchers/gcc.json"
echo "::add-matcher::.github/workflows/matchers/python.json"
- run: esphome tests/${{ matrix.test }}.yaml compile
- run: esphome compile tests/${{ matrix.test }}.yaml
pytest:
runs-on: ubuntu-latest

42
.github/workflows/docker-lint-build.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
name: Build and publish lint docker image
# Only run when docker paths change
on:
push:
branches: [dev]
paths:
- 'docker/Dockerfile.lint'
- 'requirements.txt'
- 'requirements_optional.txt'
- 'requirements_test.txt'
- 'platformio.ini'
- '.github/workflows/docker-lint-build.yml'
jobs:
publish-docker-lint-iage:
name: Build docker containers
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set TAG
run: |
echo "TAG=1.1" >> $GITHUB_ENV
- name: Pull for cache
run: |
docker pull "esphome/esphome-lint:latest" || true
- name: Build
run: |
docker build \
--cache-from "esphome/esphome-lint:latest" \
--file "docker/Dockerfile.lint" \
--tag "esphome/esphome-lint:latest" \
--tag "esphome/esphome-lint:${TAG}" \
.
- name: Log in to docker hub
env:
DOCKER_USER: ${{ secrets.DOCKER_USER }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
- run: |
docker push "esphome/esphome-lint:${TAG}"
docker push "esphome/esphome-lint:latest"

View File

@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
# cpp lint job runs with esphome-lint docker image so that clang-format-*
# doesn't have to be installed
container: esphome/esphome-lint:latest
container: esphome/esphome-lint:1.1
steps:
- uses: actions/checkout@v2
# Set up the pio project so that the cpp checks know how files are compiled
@@ -29,7 +29,7 @@ jobs:
runs-on: ubuntu-latest
# cpp lint job runs with esphome-lint docker image so that clang-format-*
# doesn't have to be installed
container: esphome/esphome-lint:latest
container: esphome/esphome-lint:1.1
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
strategy:
fail-fast: false
@@ -94,6 +94,7 @@ jobs:
- test2
- test3
- test4
- test5
steps:
- uses: actions/checkout@v2
- name: Set up Python
@@ -112,7 +113,7 @@ jobs:
uses: actions/cache@v1
with:
path: ~/.platformio
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core_config.py') }}
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }}
restore-keys: |
test-home-platformio-${{ matrix.test }}-
- name: Set up environment
@@ -123,7 +124,7 @@ jobs:
run: |
echo "::add-matcher::.github/workflows/matchers/gcc.json"
echo "::add-matcher::.github/workflows/matchers/python.json"
- run: esphome tests/${{ matrix.test }}.yaml compile
- run: esphome compile tests/${{ matrix.test }}.yaml
pytest:
runs-on: ubuntu-latest
@@ -174,7 +175,7 @@ jobs:
echo "TAG=${TAG}" >> $GITHUB_ENV
- name: Set up env variables
run: |
base_version="3.0.0"
base_version="3.4.0"
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"

View File

@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
# cpp lint job runs with esphome-lint docker image so that clang-format-*
# doesn't have to be installed
container: esphome/esphome-lint:latest
container: esphome/esphome-lint:1.1
steps:
- uses: actions/checkout@v2
# Set up the pio project so that the cpp checks know how files are compiled
@@ -28,7 +28,7 @@ jobs:
runs-on: ubuntu-latest
# cpp lint job runs with esphome-lint docker image so that clang-format-*
# doesn't have to be installed
container: esphome/esphome-lint:latest
container: esphome/esphome-lint:1.1
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
strategy:
fail-fast: false
@@ -93,6 +93,7 @@ jobs:
- test2
- test3
- test4
- test5
steps:
- uses: actions/checkout@v2
- name: Set up Python
@@ -111,7 +112,7 @@ jobs:
uses: actions/cache@v1
with:
path: ~/.platformio
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core_config.py') }}
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }}
restore-keys: |
test-home-platformio-${{ matrix.test }}-
- name: Set up environment
@@ -121,7 +122,7 @@ jobs:
run: |
echo "::add-matcher::.github/workflows/matchers/gcc.json"
echo "::add-matcher::.github/workflows/matchers/python.json"
- run: esphome tests/${{ matrix.test }}.yaml compile
- run: esphome compile tests/${{ matrix.test }}.yaml
pytest:
runs-on: ubuntu-latest
@@ -194,7 +195,7 @@ jobs:
echo "TAG=${TAG}" >> $GITHUB_ENV
- name: Set up env variables
run: |
base_version="3.0.0"
base_version="3.4.0"
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"

2
.gitignore vendored
View File

@@ -100,6 +100,8 @@ CMakeLists.txt
# CMake
cmake-build-debug/
cmake-build-livingroom8266/
cmake-build-livingroom32/
cmake-build-release/
CMakeCache.txt

2
.vscode/tasks.json vendored
View File

@@ -4,7 +4,7 @@
{
"label": "run",
"type": "shell",
"command": "python3 -m esphome config dashboard",
"command": "python3 -m esphome dashboard config",
"problemMatcher": []
}
]

View File

@@ -18,26 +18,36 @@ esphome/components/animation/* @syndlex
esphome/components/api/* @OttoWinter
esphome/components/async_tcp/* @OttoWinter
esphome/components/atc_mithermometer/* @ahpohl
esphome/components/b_parasite/* @rbaron
esphome/components/bang_bang/* @OttoWinter
esphome/components/binary_sensor/* @esphome/core
esphome/components/ble_client/* @buxtronix
esphome/components/bme680_bsec/* @trvrnrth
esphome/components/canbus/* @danielschramm @mvturnho
esphome/components/captive_portal/* @OttoWinter
esphome/components/climate/* @esphome/core
esphome/components/climate_ir/* @glmnet
esphome/components/coolix/* @glmnet
esphome/components/cover/* @esphome/core
esphome/components/cs5460a/* @balrog-kun
esphome/components/ct_clamp/* @jesserockz
esphome/components/debug/* @OttoWinter
esphome/components/dfplayer/* @glmnet
esphome/components/dht/* @OttoWinter
esphome/components/ds1307/* @badbadc0ffee
esphome/components/esp32_ble/* @jesserockz
esphome/components/esp32_ble_server/* @jesserockz
esphome/components/esp32_improv/* @jesserockz
esphome/components/exposure_notifications/* @OttoWinter
esphome/components/ezo/* @ssieb
esphome/components/fastled_base/* @OttoWinter
esphome/components/fingerprint_grow/* @OnFreund @loongyh
esphome/components/globals/* @esphome/core
esphome/components/gpio/* @esphome/core
esphome/components/gps/* @coogle
esphome/components/homeassistant/* @OttoWinter
esphome/components/i2c/* @esphome/core
esphome/components/improv/* @jesserockz
esphome/components/inkbird_ibsth1_mini/* @fkirill
esphome/components/inkplate6/* @jesserockz
esphome/components/integration/* @OttoWinter
@@ -58,6 +68,7 @@ esphome/components/mcp2515/* @danielschramm @mvturnho
esphome/components/mcp9808/* @k7hpn
esphome/components/midea_ac/* @dudanov
esphome/components/midea_dongle/* @dudanov
esphome/components/mitsubishi/* @RubyBailey
esphome/components/network/* @esphome/core
esphome/components/nfc/* @jesserockz
esphome/components/ota/* @esphome/core
@@ -75,9 +86,13 @@ esphome/components/restart/* @esphome/core
esphome/components/rf_bridge/* @jesserockz
esphome/components/rtttl/* @glmnet
esphome/components/script/* @esphome/core
esphome/components/sdm_meter/* @jesserockz @polyfaces
esphome/components/sensor/* @esphome/core
esphome/components/sgp40/* @SenexCrenshaw
esphome/components/sht4x/* @sjtrny
esphome/components/shutdown/* @esphome/core
esphome/components/sim800l/* @glmnet
esphome/components/sm2135/* @BoukeHaarsma23
esphome/components/spi/* @esphome/core
esphome/components/ssd1322_base/* @kbx81
esphome/components/ssd1322_spi/* @kbx81
@@ -95,12 +110,14 @@ esphome/components/st7789v/* @kbx81
esphome/components/substitutions/* @esphome/core
esphome/components/sun/* @OttoWinter
esphome/components/switch/* @esphome/core
esphome/components/tca9548a/* @andreashergert1984
esphome/components/tcl112/* @glmnet
esphome/components/teleinfo/* @0hax
esphome/components/thermostat/* @kbx81
esphome/components/time/* @OttoWinter
esphome/components/tm1637/* @glmnet
esphome/components/tmp102/* @timsavage
esphome/components/tof10120/* @wstrzalka
esphome/components/tuya/binary_sensor/* @jesserockz
esphome/components/tuya/climate/* @jesserockz
esphome/components/tuya/sensor/* @jesserockz
@@ -112,3 +129,4 @@ esphome/components/web_server_base/* @OttoWinter
esphome/components/whirlpool/* @glmnet
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
esphome/components/xiaomi_mhoc401/* @vevsvevs
esphome/components/xpt2046/* @numo68

View File

@@ -8,19 +8,19 @@ In the interest of fostering an open and welcoming environment, we as contributo
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
- The use of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
@@ -34,7 +34,7 @@ This Code of Conduct applies both within project spaces and in public spaces whe
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contact@otto-winter.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at esphome@nabucasa.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.

View File

@@ -1,10 +1,10 @@
ARG BUILD_FROM=esphome/esphome-base-amd64:3.0.0
ARG BUILD_FROM=esphome/esphome-base-amd64:3.4.0
FROM ${BUILD_FROM}
# First install requirements to leverage caching when requirements don't change
COPY requirements.txt docker/platformio_install_deps.py platformio.ini /
COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
RUN \
pip3 install --no-cache-dir -r /requirements.txt \
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
&& /platformio_install_deps.py /platformio.ini
# Then copy esphome and install
@@ -27,4 +27,4 @@ WORKDIR /config
# in every docker command twice
ENTRYPOINT ["esphome"]
# When no arguments given, start the dashboard in the workdir
CMD ["/config", "dashboard"]
CMD ["dashboard", "/config"]

View File

@@ -1,4 +1,4 @@
FROM esphome/esphome-base-amd64:3.0.0
FROM esphome/esphome-base-amd64:3.4.0
COPY . .

View File

@@ -2,9 +2,9 @@ ARG BUILD_FROM
FROM ${BUILD_FROM}
# First install requirements to leverage caching when requirements don't change
COPY requirements.txt docker/platformio_install_deps.py platformio.ini /
COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
RUN \
pip3 install --no-cache-dir -r /requirements.txt \
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
&& /platformio_install_deps.py /platformio.ini
# Copy root filesystem

View File

@@ -1,8 +1,8 @@
FROM esphome/esphome-lint-base:3.0.0
FROM esphome/esphome-lint-base:3.4.0
COPY requirements.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini /
COPY requirements.txt requirements_optional.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini /
RUN \
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_test.txt \
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt -r /requirements_test.txt \
&& /platformio_install_deps.py /platformio.ini
VOLUME ["/esphome"]

View File

@@ -23,4 +23,4 @@ if bashio::config.has_value 'relative_url'; then
fi
bashio::log.info "Starting ESPHome dashboard..."
exec esphome /config/esphome dashboard --socket /var/run/esphome.sock --hassio
exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --hassio

View File

@@ -18,7 +18,7 @@ from esphome.const import (
CONF_ESPHOME,
CONF_PLATFORMIO_OPTIONS,
)
from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority
from esphome.core import CORE, EsphomeError, coroutine
from esphome.helpers import indent
from esphome.util import (
run_external_command,
@@ -127,15 +127,16 @@ def wrap_to_code(name, comp):
coro = coroutine(comp.to_code)
@functools.wraps(comp.to_code)
@coroutine_with_priority(coro.priority)
def wrapped(conf):
async def wrapped(conf):
cg.add(cg.LineComment(f"{name}:"))
if comp.config_schema is not None:
conf_str = yaml_util.dump(conf)
conf_str = conf_str.replace("//", "")
cg.add(cg.LineComment(indent(conf_str)))
yield coro(conf)
await coro(conf)
if hasattr(coro, "priority"):
wrapped.priority = coro.priority
return wrapped
@@ -267,7 +268,7 @@ def clean_mqtt(config, args):
def command_wizard(args):
from esphome import wizard
return wizard.wizard(args.configuration[0])
return wizard.wizard(args.configuration)
def command_config(args, config):
@@ -283,7 +284,7 @@ def command_vscode(args):
logging.disable(logging.INFO)
logging.disable(logging.WARNING)
CORE.config_path = args.configuration[0]
CORE.config_path = args.configuration
vscode.read_config(args)
@@ -303,7 +304,7 @@ def command_compile(args, config):
def command_upload(args, config):
port = choose_upload_log_host(
default=args.upload_port,
default=args.device,
check_default=None,
show_ota=True,
show_mqtt=False,
@@ -318,7 +319,7 @@ def command_upload(args, config):
def command_logs(args, config):
port = choose_upload_log_host(
default=args.serial_port,
default=args.device,
check_default=None,
show_ota=False,
show_mqtt=True,
@@ -336,7 +337,7 @@ def command_run(args, config):
return exit_code
_LOGGER.info("Successfully compiled program.")
port = choose_upload_log_host(
default=args.upload_port,
default=args.device,
check_default=None,
show_ota=True,
show_mqtt=False,
@@ -349,7 +350,7 @@ def command_run(args, config):
if args.no_logs:
return 0
port = choose_upload_log_host(
default=args.upload_port,
default=args.device,
check_default=port,
show_ota=False,
show_mqtt=True,
@@ -393,7 +394,7 @@ def command_update_all(args):
import click
success = {}
files = list_yaml_files(args.configuration[0])
files = list_yaml_files(args.configuration)
twidth = 60
def print_bar(middle_text):
@@ -407,7 +408,7 @@ def command_update_all(args):
print("-" * twidth)
print()
rc = run_external_process(
"esphome", "--dashboard", f, "run", "--no-logs", "--upload-port", "OTA"
"esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA"
)
if rc == 0:
print_bar("[{}] {}".format(color(Fore.BOLD_GREEN, "SUCCESS"), f))
@@ -452,15 +453,17 @@ POST_CONFIG_ACTIONS = {
def parse_args(argv):
parser = argparse.ArgumentParser(description=f"ESPHome v{const.__version__}")
parser.add_argument(
"-v", "--verbose", help="Enable verbose esphome logs.", action="store_true"
options_parser = argparse.ArgumentParser(add_help=False)
options_parser.add_argument(
"-v", "--verbose", help="Enable verbose ESPHome logs.", action="store_true"
)
parser.add_argument(
"-q", "--quiet", help="Disable all esphome logs.", action="store_true"
options_parser.add_argument(
"-q", "--quiet", help="Disable all ESPHome logs.", action="store_true"
)
parser.add_argument("--dashboard", help=argparse.SUPPRESS, action="store_true")
parser.add_argument(
options_parser.add_argument(
"--dashboard", help=argparse.SUPPRESS, action="store_true"
)
options_parser.add_argument(
"-s",
"--substitution",
nargs=2,
@@ -468,17 +471,88 @@ def parse_args(argv):
help="Add a substitution",
metavar=("key", "value"),
)
parser.add_argument(
"configuration", help="Your YAML configuration file.", nargs="*"
# Keep backward compatibility with the old command line format of
# esphome <config> <command>.
#
# Unfortunately this can't be done by adding another configuration argument to the
# main config parser, as argparse is greedy when parsing arguments, so in regular
# usage it'll eat the command as the configuration argument and error out out
# because it can't parse the configuration as a command.
#
# Instead, construct an ad-hoc parser for the old format that doesn't actually
# process the arguments, but parses them enough to let us figure out if the old
# format is used. In that case, swap the command and configuration in the arguments
# and continue on with the normal parser (after raising a deprecation warning).
#
# Disable argparse's built-in help option and add it manually to prevent this
# parser from printing the help messagefor the old format when invoked with -h.
compat_parser = argparse.ArgumentParser(parents=[options_parser], add_help=False)
compat_parser.add_argument("-h", "--help")
compat_parser.add_argument("configuration", nargs="*")
compat_parser.add_argument(
"command",
choices=[
"config",
"compile",
"upload",
"logs",
"run",
"clean-mqtt",
"wizard",
"mqtt-fingerprint",
"version",
"clean",
"dashboard",
"vscode",
],
)
subparsers = parser.add_subparsers(help="Commands", dest="command")
# on Python 3.9+ we can simply set exit_on_error=False in the constructor
def _raise(x):
raise argparse.ArgumentError(None, x)
compat_parser.error = _raise
try:
result, unparsed = compat_parser.parse_known_args(argv[1:])
last_option = len(argv) - len(unparsed) - 1 - len(result.configuration)
argv = argv[0:last_option] + [result.command] + result.configuration + unparsed
deprecated_argv_suggestion = argv
except argparse.ArgumentError:
# This is not an old-style command line, so we don't have to do anything.
deprecated_argv_suggestion = None
# And continue on with regular parsing
parser = argparse.ArgumentParser(
description=f"ESPHome v{const.__version__}", parents=[options_parser]
)
parser.set_defaults(deprecated_argv_suggestion=deprecated_argv_suggestion)
mqtt_options = argparse.ArgumentParser(add_help=False)
mqtt_options.add_argument("--topic", help="Manually set the MQTT topic.")
mqtt_options.add_argument("--username", help="Manually set the MQTT username.")
mqtt_options.add_argument("--password", help="Manually set the MQTT password.")
mqtt_options.add_argument("--client-id", help="Manually set the MQTT client id.")
subparsers = parser.add_subparsers(
help="Command to run:", dest="command", metavar="command"
)
subparsers.required = True
subparsers.add_parser("config", help="Validate the configuration and spit it out.")
parser_config = subparsers.add_parser(
"config", help="Validate the configuration and spit it out."
)
parser_config.add_argument(
"configuration", help="Your YAML configuration file(s).", nargs="+"
)
parser_compile = subparsers.add_parser(
"compile", help="Read the configuration and compile a program."
)
parser_compile.add_argument(
"configuration", help="Your YAML configuration file(s).", nargs="+"
)
parser_compile.add_argument(
"--only-generate",
help="Only generate source code, do not compile.",
@@ -486,106 +560,124 @@ def parse_args(argv):
)
parser_upload = subparsers.add_parser(
"upload", help="Validate the configuration " "and upload the latest binary."
"upload", help="Validate the configuration and upload the latest binary."
)
parser_upload.add_argument(
"--upload-port",
help="Manually specify the upload port to use. "
"For example /dev/cu.SLAB_USBtoUART.",
"configuration", help="Your YAML configuration file(s).", nargs="+"
)
parser_upload.add_argument(
"--device",
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
)
parser_logs = subparsers.add_parser(
"logs", help="Validate the configuration " "and show all MQTT logs."
"logs",
help="Validate the configuration and show all logs.",
parents=[mqtt_options],
)
parser_logs.add_argument("--topic", help="Manually set the topic to subscribe to.")
parser_logs.add_argument("--username", help="Manually set the username.")
parser_logs.add_argument("--password", help="Manually set the password.")
parser_logs.add_argument("--client-id", help="Manually set the client id.")
parser_logs.add_argument(
"--serial-port",
help="Manually specify a serial port to use"
"For example /dev/cu.SLAB_USBtoUART.",
"configuration", help="Your YAML configuration file.", nargs=1
)
parser_logs.add_argument(
"--device",
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
)
parser_run = subparsers.add_parser(
"run",
help="Validate the configuration, create a binary, "
"upload it, and start MQTT logs.",
help="Validate the configuration, create a binary, upload it, and start logs.",
parents=[mqtt_options],
)
parser_run.add_argument(
"--upload-port",
help="Manually specify the upload port/ip to use. "
"For example /dev/cu.SLAB_USBtoUART.",
"configuration", help="Your YAML configuration file(s).", nargs="+"
)
parser_run.add_argument(
"--no-logs", help="Disable starting MQTT logs.", action="store_true"
"--device",
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
)
parser_run.add_argument(
"--topic", help="Manually set the topic to subscribe to for logs."
"--no-logs", help="Disable starting logs.", action="store_true"
)
parser_run.add_argument(
"--username", help="Manually set the MQTT username for logs."
)
parser_run.add_argument(
"--password", help="Manually set the MQTT password for logs."
)
parser_run.add_argument("--client-id", help="Manually set the client id for logs.")
parser_clean = subparsers.add_parser(
"clean-mqtt", help="Helper to clear an MQTT topic from " "retain messages."
"clean-mqtt",
help="Helper to clear retained messages from an MQTT topic.",
parents=[mqtt_options],
)
parser_clean.add_argument(
"configuration", help="Your YAML configuration file(s).", nargs="+"
)
parser_clean.add_argument("--topic", help="Manually set the topic to subscribe to.")
parser_clean.add_argument("--username", help="Manually set the username.")
parser_clean.add_argument("--password", help="Manually set the password.")
parser_clean.add_argument("--client-id", help="Manually set the client id.")
subparsers.add_parser(
parser_wizard = subparsers.add_parser(
"wizard",
help="A helpful setup wizard that will guide "
"you through setting up esphome.",
help="A helpful setup wizard that will guide you through setting up ESPHome.",
)
parser_wizard.add_argument(
"configuration",
help="Your YAML configuration file.",
)
subparsers.add_parser(
parser_fingerprint = subparsers.add_parser(
"mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker."
)
parser_fingerprint.add_argument(
"configuration", help="Your YAML configuration file(s).", nargs="+"
)
subparsers.add_parser("version", help="Print the esphome version and exit.")
subparsers.add_parser("version", help="Print the ESPHome version and exit.")
subparsers.add_parser("clean", help="Delete all temporary build files.")
parser_clean = subparsers.add_parser(
"clean", help="Delete all temporary build files."
)
parser_clean.add_argument(
"configuration", help="Your YAML configuration file(s).", nargs="+"
)
dashboard = subparsers.add_parser(
parser_dashboard = subparsers.add_parser(
"dashboard", help="Create a simple web server for a dashboard."
)
dashboard.add_argument(
parser_dashboard.add_argument(
"configuration",
help="Your YAML configuration file directory.",
)
parser_dashboard.add_argument(
"--port",
help="The HTTP port to open connections on. Defaults to 6052.",
type=int,
default=6052,
)
dashboard.add_argument(
parser_dashboard.add_argument(
"--username",
help="The optional username to require " "for authentication.",
help="The optional username to require for authentication.",
type=str,
default="",
)
dashboard.add_argument(
parser_dashboard.add_argument(
"--password",
help="The optional password to require " "for authentication.",
help="The optional password to require for authentication.",
type=str,
default="",
)
dashboard.add_argument(
parser_dashboard.add_argument(
"--open-ui", help="Open the dashboard UI in a browser.", action="store_true"
)
dashboard.add_argument("--hassio", help=argparse.SUPPRESS, action="store_true")
dashboard.add_argument(
parser_dashboard.add_argument(
"--hassio", help=argparse.SUPPRESS, action="store_true"
)
parser_dashboard.add_argument(
"--socket", help="Make the dashboard serve under a unix socket", type=str
)
vscode = subparsers.add_parser("vscode", help=argparse.SUPPRESS)
vscode.add_argument("--ace", action="store_true")
parser_vscode = subparsers.add_parser("vscode")
parser_vscode.add_argument(
"configuration", help="Your YAML configuration file.", nargs=1
)
parser_vscode.add_argument("--ace", action="store_true")
subparsers.add_parser("update-all", help=argparse.SUPPRESS)
parser_update = subparsers.add_parser("update-all")
parser_update.add_argument(
"configuration", help="Your YAML configuration file directory.", nargs=1
)
return parser.parse_args(argv[1:])
@@ -595,14 +687,18 @@ def run_esphome(argv):
CORE.dashboard = args.dashboard
setup_log(args.verbose, args.quiet)
if args.command != "version" and not args.configuration:
_LOGGER.error("Missing configuration parameter, see esphome --help.")
return 1
if args.deprecated_argv_suggestion is not None and args.command != "vscode":
_LOGGER.warning(
"Calling ESPHome with the configuration before the command is deprecated "
"and will be removed in the future. "
)
_LOGGER.warning("Please instead use:")
_LOGGER.warning(" esphome %s", " ".join(args.deprecated_argv_suggestion[1:]))
if sys.version_info < (3, 6, 0):
if sys.version_info < (3, 7, 0):
_LOGGER.error(
"You're running ESPHome with Python <3.6. ESPHome is no longer compatible "
"with this Python version. Please reinstall ESPHome with Python 3.6+"
"You're running ESPHome with Python <3.7. ESPHome is no longer compatible "
"with this Python version. Please reinstall ESPHome with Python 3.7+"
)
return 1
@@ -610,7 +706,7 @@ def run_esphome(argv):
try:
return PRE_CONFIG_ACTIONS[args.command](args)
except EsphomeError as e:
_LOGGER.error(e)
_LOGGER.error(e, exc_info=args.verbose)
return 1
for conf_path in args.configuration:
@@ -628,7 +724,7 @@ def run_esphome(argv):
try:
rc = POST_CONFIG_ACTIONS[args.command](args, config)
except EsphomeError as e:
_LOGGER.error(e)
_LOGGER.error(e, exc_info=args.verbose)
return 1
if rc != 0:
return rc

View File

@@ -10,7 +10,6 @@ from esphome.const import (
CONF_TYPE_ID,
CONF_TIME,
)
from esphome.core import coroutine
from esphome.jsonschema import jschema_extractor
from esphome.util import Registry
@@ -142,27 +141,27 @@ NotCondition = cg.esphome_ns.class_("NotCondition", Condition)
@register_condition("and", AndCondition, validate_condition_list)
def and_condition_to_code(config, condition_id, template_arg, args):
conditions = yield build_condition_list(config, template_arg, args)
yield cg.new_Pvariable(condition_id, template_arg, conditions)
async def and_condition_to_code(config, condition_id, template_arg, args):
conditions = await build_condition_list(config, template_arg, args)
return cg.new_Pvariable(condition_id, template_arg, conditions)
@register_condition("or", OrCondition, validate_condition_list)
def or_condition_to_code(config, condition_id, template_arg, args):
conditions = yield build_condition_list(config, template_arg, args)
yield cg.new_Pvariable(condition_id, template_arg, conditions)
async def or_condition_to_code(config, condition_id, template_arg, args):
conditions = await build_condition_list(config, template_arg, args)
return cg.new_Pvariable(condition_id, template_arg, conditions)
@register_condition("not", NotCondition, validate_potentially_and_condition)
def not_condition_to_code(config, condition_id, template_arg, args):
condition = yield build_condition(config, template_arg, args)
yield cg.new_Pvariable(condition_id, template_arg, condition)
async def not_condition_to_code(config, condition_id, template_arg, args):
condition = await build_condition(config, template_arg, args)
return cg.new_Pvariable(condition_id, template_arg, condition)
@register_condition("lambda", LambdaCondition, cv.lambda_)
def lambda_condition_to_code(config, condition_id, template_arg, args):
lambda_ = yield cg.process_lambda(config, args, return_type=bool)
yield cg.new_Pvariable(condition_id, template_arg, lambda_)
@register_condition("lambda", LambdaCondition, cv.returning_lambda)
async def lambda_condition_to_code(config, condition_id, template_arg, args):
lambda_ = await cg.process_lambda(config, args, return_type=bool)
return cg.new_Pvariable(condition_id, template_arg, lambda_)
@register_condition(
@@ -177,26 +176,26 @@ def lambda_condition_to_code(config, condition_id, template_arg, args):
}
).extend(cv.COMPONENT_SCHEMA),
)
def for_condition_to_code(config, condition_id, template_arg, args):
condition = yield build_condition(
async def for_condition_to_code(config, condition_id, template_arg, args):
condition = await build_condition(
config[CONF_CONDITION], cg.TemplateArguments(), []
)
var = cg.new_Pvariable(condition_id, template_arg, condition)
yield cg.register_component(var, config)
templ = yield cg.templatable(config[CONF_TIME], args, cg.uint32)
await cg.register_component(var, config)
templ = await cg.templatable(config[CONF_TIME], args, cg.uint32)
cg.add(var.set_time(templ))
yield var
return var
@register_action(
"delay", DelayAction, cv.templatable(cv.positive_time_period_milliseconds)
)
def delay_action_to_code(config, action_id, template_arg, args):
async def delay_action_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
yield cg.register_component(var, {})
template_ = yield cg.templatable(config, args, cg.uint32)
await cg.register_component(var, {})
template_ = await cg.templatable(config, args, cg.uint32)
cg.add(var.set_delay(template_))
yield var
return var
@register_action(
@@ -211,16 +210,16 @@ def delay_action_to_code(config, action_id, template_arg, args):
cv.has_at_least_one_key(CONF_THEN, CONF_ELSE),
),
)
def if_action_to_code(config, action_id, template_arg, args):
conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
async def if_action_to_code(config, action_id, template_arg, args):
conditions = await build_condition(config[CONF_CONDITION], template_arg, args)
var = cg.new_Pvariable(action_id, template_arg, conditions)
if CONF_THEN in config:
actions = yield build_action_list(config[CONF_THEN], template_arg, args)
actions = await build_action_list(config[CONF_THEN], template_arg, args)
cg.add(var.add_then(actions))
if CONF_ELSE in config:
actions = yield build_action_list(config[CONF_ELSE], template_arg, args)
actions = await build_action_list(config[CONF_ELSE], template_arg, args)
cg.add(var.add_else(actions))
yield var
return var
@register_action(
@@ -233,12 +232,12 @@ def if_action_to_code(config, action_id, template_arg, args):
}
),
)
def while_action_to_code(config, action_id, template_arg, args):
conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
async def while_action_to_code(config, action_id, template_arg, args):
conditions = await build_condition(config[CONF_CONDITION], template_arg, args)
var = cg.new_Pvariable(action_id, template_arg, conditions)
actions = yield build_action_list(config[CONF_THEN], template_arg, args)
actions = await build_action_list(config[CONF_THEN], template_arg, args)
cg.add(var.add_then(actions))
yield var
return var
def validate_wait_until(value):
@@ -253,17 +252,17 @@ def validate_wait_until(value):
@register_action("wait_until", WaitUntilAction, validate_wait_until)
def wait_until_action_to_code(config, action_id, template_arg, args):
conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
async def wait_until_action_to_code(config, action_id, template_arg, args):
conditions = await build_condition(config[CONF_CONDITION], template_arg, args)
var = cg.new_Pvariable(action_id, template_arg, conditions)
yield cg.register_component(var, {})
yield var
await cg.register_component(var, {})
return var
@register_action("lambda", LambdaAction, cv.lambda_)
def lambda_action_to_code(config, action_id, template_arg, args):
lambda_ = yield cg.process_lambda(config, args, return_type=cg.void)
yield cg.new_Pvariable(action_id, template_arg, lambda_)
async def lambda_action_to_code(config, action_id, template_arg, args):
lambda_ = await cg.process_lambda(config, args, return_type=cg.void)
return cg.new_Pvariable(action_id, template_arg, lambda_)
@register_action(
@@ -275,54 +274,51 @@ def lambda_action_to_code(config, action_id, template_arg, args):
}
),
)
def component_update_action_to_code(config, action_id, template_arg, args):
comp = yield cg.get_variable(config[CONF_ID])
yield cg.new_Pvariable(action_id, template_arg, comp)
async def component_update_action_to_code(config, action_id, template_arg, args):
comp = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, comp)
@coroutine
def build_action(full_config, template_arg, args):
async def build_action(full_config, template_arg, args):
registry_entry, config = cg.extract_registry_entry_config(
ACTION_REGISTRY, full_config
)
action_id = full_config[CONF_TYPE_ID]
builder = registry_entry.coroutine_fun
yield builder(config, action_id, template_arg, args)
ret = await builder(config, action_id, template_arg, args)
return ret
@coroutine
def build_action_list(config, templ, arg_type):
async def build_action_list(config, templ, arg_type):
actions = []
for conf in config:
action = yield build_action(conf, templ, arg_type)
action = await build_action(conf, templ, arg_type)
actions.append(action)
yield actions
return actions
@coroutine
def build_condition(full_config, template_arg, args):
async def build_condition(full_config, template_arg, args):
registry_entry, config = cg.extract_registry_entry_config(
CONDITION_REGISTRY, full_config
)
action_id = full_config[CONF_TYPE_ID]
builder = registry_entry.coroutine_fun
yield builder(config, action_id, template_arg, args)
ret = await builder(config, action_id, template_arg, args)
return ret
@coroutine
def build_condition_list(config, templ, args):
async def build_condition_list(config, templ, args):
conditions = []
for conf in config:
condition = yield build_condition(conf, templ, args)
condition = await build_condition(conf, templ, args)
conditions.append(condition)
yield conditions
return conditions
@coroutine
def build_automation(trigger, args, config):
async def build_automation(trigger, args, config):
arg_types = [arg[0] for arg in args]
templ = cg.TemplateArguments(*arg_types)
obj = cg.new_Pvariable(config[CONF_AUTOMATION_ID], templ, trigger)
actions = yield build_action_list(config[CONF_THEN], templ, args)
actions = await build_action_list(config[CONF_THEN], templ, args)
cg.add(obj.add_actions(actions))
yield obj
return obj

View File

@@ -19,6 +19,7 @@ from esphome.cpp_generator import ( # noqa
Statement,
LineComment,
progmem_array,
static_const_array,
statement,
variable,
new_variable,

View File

@@ -18,16 +18,16 @@ CONFIG_SCHEMA = stepper.STEPPER_SCHEMA.extend(
).extend(cv.COMPONENT_SCHEMA)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield stepper.register_stepper(var, config)
await cg.register_component(var, config)
await stepper.register_stepper(var, config)
step_pin = yield cg.gpio_pin_expression(config[CONF_STEP_PIN])
step_pin = await cg.gpio_pin_expression(config[CONF_STEP_PIN])
cg.add(var.set_step_pin(step_pin))
dir_pin = yield cg.gpio_pin_expression(config[CONF_DIR_PIN])
dir_pin = await cg.gpio_pin_expression(config[CONF_DIR_PIN])
cg.add(var.set_dir_pin(dir_pin))
if CONF_SLEEP_PIN in config:
sleep_pin = yield cg.gpio_pin_expression(config[CONF_SLEEP_PIN])
sleep_pin = await cg.gpio_pin_expression(config[CONF_SLEEP_PIN])
cg.add(var.set_sleep_pin(sleep_pin))

View File

@@ -32,18 +32,18 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
).extend(cv.COMPONENT_SCHEMA)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
await cg.register_component(var, config)
# override default min power to 10%
if CONF_MIN_POWER not in config:
config[CONF_MIN_POWER] = 0.1
yield output.register_output(var, config)
await output.register_output(var, config)
pin = yield cg.gpio_pin_expression(config[CONF_GATE_PIN])
pin = await cg.gpio_pin_expression(config[CONF_GATE_PIN])
cg.add(var.set_gate_pin(pin))
pin = yield cg.gpio_pin_expression(config[CONF_ZERO_CROSS_PIN])
pin = await cg.gpio_pin_expression(config[CONF_ZERO_CROSS_PIN])
cg.add(var.set_zero_cross_pin(pin))
cg.add(var.set_init_with_half_cycle(config[CONF_INIT_WITH_HALF_CYCLE]))
cg.add(var.set_method(config[CONF_METHOD]))

View File

@@ -21,8 +21,7 @@ CONFIG_SCHEMA = cv.Schema({})
"Adalight",
{cv.GenerateID(CONF_UART_ID): cv.use_id(uart.UARTComponent)},
)
def adalight_light_effect_to_code(config, effect_id):
async def adalight_light_effect_to_code(config, effect_id):
effect = cg.new_Pvariable(effect_id, config[CONF_NAME])
yield uart.register_uart_device(effect, config)
yield effect
await uart.register_uart_device(effect, config)
return effect

View File

@@ -8,6 +8,7 @@ from esphome.const import (
CONF_PIN,
DEVICE_CLASS_VOLTAGE,
ICON_EMPTY,
STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
)
@@ -35,7 +36,9 @@ ADCSensor = adc_ns.class_(
)
CONFIG_SCHEMA = (
sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE)
sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
)
.extend(
{
cv.GenerateID(): cv.declare_id(ADCSensor),
@@ -49,10 +52,10 @@ CONFIG_SCHEMA = (
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield sensor.register_sensor(var, config)
await cg.register_component(var, config)
await sensor.register_sensor(var, config)
if config[CONF_PIN] == "VCC":
cg.add_define("USE_ADC_SENSOR_VCC")

View File

@@ -38,18 +38,18 @@ CONFIG_SCHEMA = cv.All(
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
wrapped_light = yield cg.get_variable(config[CONF_ADDRESSABLE_LIGHT_ID])
wrapped_light = await cg.get_variable(config[CONF_ADDRESSABLE_LIGHT_ID])
cg.add(var.set_width(config[CONF_WIDTH]))
cg.add(var.set_height(config[CONF_HEIGHT]))
cg.add(var.set_light(wrapped_light))
yield cg.register_component(var, config)
yield display.register_display(var, config)
await cg.register_component(var, config)
await display.register_display(var, config)
if CONF_PIXEL_MAPPER in config:
pixel_mapper_template_ = yield cg.process_lambda(
pixel_mapper_template_ = await cg.process_lambda(
config[CONF_PIXEL_MAPPER],
[(int, "x"), (int, "y")],
return_type=cg.int_,
@@ -57,7 +57,7 @@ def to_code(config):
cg.add(var.set_pixel_mapper(pixel_mapper_template_))
if CONF_LAMBDA in config:
lambda_ = yield cg.process_lambda(
lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void
)
cg.add(var.set_writer(lambda_))

View File

@@ -9,6 +9,7 @@ from esphome.const import (
DEVICE_CLASS_POWER,
DEVICE_CLASS_VOLTAGE,
ICON_EMPTY,
STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
UNIT_AMPERE,
UNIT_WATT,
@@ -31,19 +32,27 @@ CONFIG_SCHEMA = (
cv.GenerateID(): cv.declare_id(ADE7953),
cv.Optional(CONF_IRQ_PIN): pins.input_pin,
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
),
cv.Optional(CONF_CURRENT_A): sensor.sensor_schema(
UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT
UNIT_AMPERE,
ICON_EMPTY,
2,
DEVICE_CLASS_CURRENT,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CURRENT_B): sensor.sensor_schema(
UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT
UNIT_AMPERE,
ICON_EMPTY,
2,
DEVICE_CLASS_CURRENT,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ACTIVE_POWER_A): sensor.sensor_schema(
UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER
UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
),
cv.Optional(CONF_ACTIVE_POWER_B): sensor.sensor_schema(
UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER
UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
),
}
)
@@ -52,10 +61,10 @@ CONFIG_SCHEMA = (
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield i2c.register_i2c_device(var, config)
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if CONF_IRQ_PIN in config:
cg.add(var.set_irq_pin(config[CONF_IRQ_PIN]))
@@ -70,5 +79,5 @@ def to_code(config):
if key not in config:
continue
conf = config[key]
sens = yield sensor.new_sensor(conf)
sens = await sensor.new_sensor(conf)
cg.add(getattr(var, f"set_{key}_sensor")(sens))

View File

@@ -23,9 +23,9 @@ CONFIG_SCHEMA = (
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield i2c.register_i2c_device(var, config)
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
cg.add(var.set_continuous_mode(config[CONF_CONTINUOUS_MODE]))

View File

@@ -6,6 +6,7 @@ from esphome.const import (
CONF_MULTIPLEXER,
DEVICE_CLASS_VOLTAGE,
ICON_EMPTY,
STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
CONF_ID,
)
@@ -51,7 +52,9 @@ ADS1115Sensor = ads1115_ns.class_(
CONF_ADS1115_ID = "ads1115_id"
CONFIG_SCHEMA = (
sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE)
sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
)
.extend(
{
cv.GenerateID(): cv.declare_id(ADS1115Sensor),
@@ -64,11 +67,11 @@ CONFIG_SCHEMA = (
)
def to_code(config):
paren = yield cg.get_variable(config[CONF_ADS1115_ID])
async def to_code(config):
paren = await cg.get_variable(config[CONF_ADS1115_ID])
var = cg.new_Pvariable(config[CONF_ID], paren)
yield sensor.register_sensor(var, config)
yield cg.register_component(var, config)
await sensor.register_sensor(var, config)
await cg.register_component(var, config)
cg.add(var.set_multiplexer(config[CONF_MULTIPLEXER]))
cg.add(var.set_gain(config[CONF_GAIN]))

View File

@@ -60,6 +60,7 @@ void AHT10Component::update() {
delay = AHT10_HUMIDITY_DELAY;
for (int i = 0; i < AHT10_ATTEMPS; ++i) {
ESP_LOGVV(TAG, "Attemps %u at %6ld", i, millis());
delay_microseconds_accurate(4);
if (!this->read_bytes(0, data, 6, delay)) {
ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
} else if ((data[0] & 0x80) == 0x80) { // Bit[7] = 0b1, device is busy

View File

@@ -8,6 +8,7 @@ from esphome.const import (
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
ICON_EMPTY,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
)
@@ -22,10 +23,18 @@ CONFIG_SCHEMA = (
{
cv.GenerateID(): cv.declare_id(AHT10Component),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
UNIT_CELSIUS, ICON_EMPTY, 2, DEVICE_CLASS_TEMPERATURE
UNIT_CELSIUS,
ICON_EMPTY,
2,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
UNIT_PERCENT, ICON_EMPTY, 2, DEVICE_CLASS_HUMIDITY
UNIT_PERCENT,
ICON_EMPTY,
2,
DEVICE_CLASS_HUMIDITY,
STATE_CLASS_MEASUREMENT,
),
}
)
@@ -34,15 +43,15 @@ CONFIG_SCHEMA = (
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield i2c.register_i2c_device(var, config)
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config:
sens = yield sensor.new_sensor(config[CONF_TEMPERATURE])
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
cg.add(var.set_temperature_sensor(sens))
if CONF_HUMIDITY in config:
sens = yield sensor.new_sensor(config[CONF_HUMIDITY])
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
cg.add(var.set_humidity_sensor(sens))

View File

@@ -7,6 +7,7 @@ from esphome.const import (
CONF_TEMPERATURE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
ICON_EMPTY,
UNIT_PERCENT,
@@ -24,10 +25,18 @@ CONFIG_SCHEMA = (
{
cv.GenerateID(): cv.declare_id(AM2320Component),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
UNIT_CELSIUS,
ICON_EMPTY,
1,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY
UNIT_PERCENT,
ICON_EMPTY,
1,
DEVICE_CLASS_HUMIDITY,
STATE_CLASS_MEASUREMENT,
),
}
)
@@ -36,15 +45,15 @@ CONFIG_SCHEMA = (
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield i2c.register_i2c_device(var, config)
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config:
sens = yield sensor.new_sensor(config[CONF_TEMPERATURE])
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
cg.add(var.set_temperature_sensor(sens))
if CONF_HUMIDITY in config:
sens = yield sensor.new_sensor(config[CONF_HUMIDITY])
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
cg.add(var.set_humidity_sensor(sens))

View File

@@ -34,7 +34,7 @@ CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA)
CODEOWNERS = ["@syndlex"]
def to_code(config):
async def to_code(config):
from PIL import Image
path = CORE.relative_config_path(config[CONF_FILE])

View File

@@ -23,7 +23,7 @@ CONFIG_SCHEMA = (
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield i2c.register_i2c_device(var, config)
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)

View File

@@ -24,8 +24,8 @@ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
)
def to_code(config):
hub = yield cg.get_variable(config[CONF_APDS9960_ID])
var = yield binary_sensor.new_binary_sensor(config)
async def to_code(config):
hub = await cg.get_variable(config[CONF_APDS9960_ID])
var = await binary_sensor.new_binary_sensor(config)
func = getattr(hub, DIRECTIONS[config[CONF_DIRECTION]])
cg.add(func(var))

View File

@@ -1,7 +1,13 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import CONF_TYPE, DEVICE_CLASS_EMPTY, UNIT_PERCENT, ICON_LIGHTBULB
from esphome.const import (
CONF_TYPE,
DEVICE_CLASS_EMPTY,
STATE_CLASS_MEASUREMENT,
UNIT_PERCENT,
ICON_LIGHTBULB,
)
from . import APDS9960, CONF_APDS9960_ID
DEPENDENCIES = ["apds9960"]
@@ -15,7 +21,7 @@ TYPES = {
}
CONFIG_SCHEMA = sensor.sensor_schema(
UNIT_PERCENT, ICON_LIGHTBULB, 1, DEVICE_CLASS_EMPTY
UNIT_PERCENT, ICON_LIGHTBULB, 1, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT
).extend(
{
cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True),
@@ -24,8 +30,8 @@ CONFIG_SCHEMA = sensor.sensor_schema(
)
def to_code(config):
hub = yield cg.get_variable(config[CONF_APDS9960_ID])
var = yield sensor.new_sensor(config)
async def to_code(config):
hub = await cg.get_variable(config[CONF_APDS9960_ID])
var = await sensor.new_sensor(config)
func = getattr(hub, TYPES[config[CONF_TYPE]])
cg.add(func(var))

View File

@@ -68,9 +68,9 @@ CONFIG_SCHEMA = cv.Schema(
@coroutine_with_priority(40.0)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
await cg.register_component(var, config)
cg.add(var.set_port(config[CONF_PORT]))
cg.add(var.set_password(config[CONF_PASSWORD]))
@@ -90,7 +90,7 @@ def to_code(config):
conf[CONF_TRIGGER_ID], templ, conf[CONF_SERVICE], service_arg_names
)
cg.add(var.register_user_service(trigger))
yield automation.build_automation(trigger, func_args, conf)
await automation.build_automation(trigger, func_args, conf)
cg.add_define("USE_API")
cg.add_global(api_ns.using)
@@ -116,21 +116,21 @@ HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema(
HomeAssistantServiceCallAction,
HOMEASSISTANT_SERVICE_ACTION_SCHEMA,
)
def homeassistant_service_to_code(config, action_id, template_arg, args):
serv = yield cg.get_variable(config[CONF_ID])
async def homeassistant_service_to_code(config, action_id, template_arg, args):
serv = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, serv, False)
templ = yield cg.templatable(config[CONF_SERVICE], args, None)
templ = await cg.templatable(config[CONF_SERVICE], args, None)
cg.add(var.set_service(templ))
for key, value in config[CONF_DATA].items():
templ = yield cg.templatable(value, args, None)
templ = await cg.templatable(value, args, None)
cg.add(var.add_data(key, templ))
for key, value in config[CONF_DATA_TEMPLATE].items():
templ = yield cg.templatable(value, args, None)
templ = await cg.templatable(value, args, None)
cg.add(var.add_data_template(key, templ))
for key, value in config[CONF_VARIABLES].items():
templ = yield cg.templatable(value, args, None)
templ = await cg.templatable(value, args, None)
cg.add(var.add_variable(key, templ))
yield var
return var
def validate_homeassistant_event(value):
@@ -159,21 +159,21 @@ HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema(
HomeAssistantServiceCallAction,
HOMEASSISTANT_EVENT_ACTION_SCHEMA,
)
def homeassistant_event_to_code(config, action_id, template_arg, args):
serv = yield cg.get_variable(config[CONF_ID])
async def homeassistant_event_to_code(config, action_id, template_arg, args):
serv = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, serv, True)
templ = yield cg.templatable(config[CONF_EVENT], args, None)
templ = await cg.templatable(config[CONF_EVENT], args, None)
cg.add(var.set_service(templ))
for key, value in config[CONF_DATA].items():
templ = yield cg.templatable(value, args, None)
templ = await cg.templatable(value, args, None)
cg.add(var.add_data(key, templ))
for key, value in config[CONF_DATA_TEMPLATE].items():
templ = yield cg.templatable(value, args, None)
templ = await cg.templatable(value, args, None)
cg.add(var.add_data_template(key, templ))
for key, value in config[CONF_VARIABLES].items():
templ = yield cg.templatable(value, args, None)
templ = await cg.templatable(value, args, None)
cg.add(var.add_variable(key, templ))
yield var
return var
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value(
@@ -190,15 +190,15 @@ HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value(
HomeAssistantServiceCallAction,
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA,
)
def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args):
serv = yield cg.get_variable(config[CONF_ID])
async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args):
serv = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, serv, True)
cg.add(var.set_service("esphome.tag_scanned"))
templ = yield cg.templatable(config[CONF_TAG], args, cg.std_string)
templ = await cg.templatable(config[CONF_TAG], args, cg.std_string)
cg.add(var.add_data("tag_id", templ))
yield var
return var
@automation.register_condition("api.connected", APIConnectedCondition, {})
def api_connected_to_code(config, condition_id, template_arg, args):
yield cg.new_Pvariable(condition_id, template_arg)
async def api_connected_to_code(config, condition_id, template_arg, args):
return cg.new_Pvariable(condition_id, template_arg)

View File

@@ -176,6 +176,10 @@ message DeviceInfoResponse {
string model = 6;
bool has_deep_sleep = 7;
// The esphome project details if set
string project_name = 8;
string project_version = 9;
}
message ListEntitiesRequest {
@@ -409,6 +413,11 @@ message LightCommandRequest {
}
// ==================== SENSOR ====================
enum SensorStateClass {
STATE_CLASS_NONE = 0;
STATE_CLASS_MEASUREMENT = 1;
}
message ListEntitiesSensorResponse {
option (id) = 16;
option (source) = SOURCE_SERVER;
@@ -424,6 +433,7 @@ message ListEntitiesSensorResponse {
int32 accuracy_decimals = 7;
bool force_update = 8;
string device_class = 9;
SensorStateClass state_class = 10;
}
message SensorStateResponse {
option (id) = 25;
@@ -561,6 +571,7 @@ message SubscribeHomeAssistantStateResponse {
option (id) = 39;
option (source) = SOURCE_SERVER;
string entity_id = 1;
string attribute = 2;
}
message HomeAssistantStateResponse {
@@ -570,6 +581,7 @@ message HomeAssistantStateResponse {
string entity_id = 1;
string state = 2;
string attribute = 3;
}
// ==================== IMPORT TIME ====================
@@ -664,11 +676,12 @@ message CameraImageRequest {
// ==================== CLIMATE ====================
enum ClimateMode {
CLIMATE_MODE_OFF = 0;
CLIMATE_MODE_AUTO = 1;
CLIMATE_MODE_HEAT_COOL = 1;
CLIMATE_MODE_COOL = 2;
CLIMATE_MODE_HEAT = 3;
CLIMATE_MODE_FAN_ONLY = 4;
CLIMATE_MODE_DRY = 5;
CLIMATE_MODE_AUTO = 6;
}
enum ClimateFanMode {
CLIMATE_FAN_ON = 0;
@@ -696,6 +709,15 @@ enum ClimateAction {
CLIMATE_ACTION_DRYING = 5;
CLIMATE_ACTION_FAN = 6;
}
enum ClimatePreset {
CLIMATE_PRESET_ECO = 0;
CLIMATE_PRESET_AWAY = 1;
CLIMATE_PRESET_BOOST = 2;
CLIMATE_PRESET_COMFORT = 3;
CLIMATE_PRESET_HOME = 4;
CLIMATE_PRESET_SLEEP = 5;
CLIMATE_PRESET_ACTIVITY = 6;
}
message ListEntitiesClimateResponse {
option (id) = 46;
option (source) = SOURCE_SERVER;
@@ -716,6 +738,9 @@ message ListEntitiesClimateResponse {
bool supports_action = 12;
repeated ClimateFanMode supported_fan_modes = 13;
repeated ClimateSwingMode supported_swing_modes = 14;
repeated string supported_custom_fan_modes = 15;
repeated ClimatePreset supported_presets = 16;
repeated string supported_custom_presets = 17;
}
message ClimateStateResponse {
option (id) = 47;
@@ -733,6 +758,9 @@ message ClimateStateResponse {
ClimateAction action = 8;
ClimateFanMode fan_mode = 9;
ClimateSwingMode swing_mode = 10;
string custom_fan_mode = 11;
ClimatePreset preset = 12;
string custom_preset = 13;
}
message ClimateCommandRequest {
option (id) = 48;
@@ -755,4 +783,10 @@ message ClimateCommandRequest {
ClimateFanMode fan_mode = 13;
bool has_swing_mode = 14;
ClimateSwingMode swing_mode = 15;
bool has_custom_fan_mode = 16;
string custom_fan_mode = 17;
bool has_preset = 18;
ClimatePreset preset = 19;
bool has_custom_preset = 20;
string custom_preset = 21;
}

View File

@@ -395,6 +395,8 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
msg.accuracy_decimals = sensor->get_accuracy_decimals();
msg.force_update = sensor->get_force_update();
msg.device_class = sensor->get_device_class();
msg.state_class = static_cast<enums::SensorStateClass>(sensor->state_class);
return this->send_list_entities_sensor_response(msg);
}
#endif
@@ -475,8 +477,14 @@ bool APIConnection::send_climate_state(climate::Climate *climate) {
}
if (traits.get_supports_away())
resp.away = climate->away;
if (traits.get_supports_fan_modes())
resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode);
if (traits.get_supports_fan_modes() && climate->fan_mode.has_value())
resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode.value());
if (!traits.get_supported_custom_fan_modes().empty() && climate->custom_fan_mode.has_value())
resp.custom_fan_mode = climate->custom_fan_mode.value();
if (traits.get_supports_presets() && climate->preset.has_value())
resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value());
if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value())
resp.custom_preset = climate->custom_preset.value();
if (traits.get_supports_swing_modes())
resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode);
return this->send_climate_state_response(resp);
@@ -490,8 +498,9 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
msg.unique_id = get_default_unique_id("climate", climate);
msg.supports_current_temperature = traits.get_supports_current_temperature();
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
for (auto mode : {climate::CLIMATE_MODE_AUTO, climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL,
climate::CLIMATE_MODE_HEAT, climate::CLIMATE_MODE_DRY, climate::CLIMATE_MODE_FAN_ONLY}) {
for (auto mode :
{climate::CLIMATE_MODE_AUTO, climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL, climate::CLIMATE_MODE_HEAT,
climate::CLIMATE_MODE_DRY, climate::CLIMATE_MODE_FAN_ONLY, climate::CLIMATE_MODE_HEAT_COOL}) {
if (traits.supports_mode(mode))
msg.supported_modes.push_back(static_cast<enums::ClimateMode>(mode));
}
@@ -506,6 +515,18 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
if (traits.supports_fan_mode(fan_mode))
msg.supported_fan_modes.push_back(static_cast<enums::ClimateFanMode>(fan_mode));
}
for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes()) {
msg.supported_custom_fan_modes.push_back(custom_fan_mode);
}
for (auto preset : {climate::CLIMATE_PRESET_ECO, climate::CLIMATE_PRESET_AWAY, climate::CLIMATE_PRESET_BOOST,
climate::CLIMATE_PRESET_COMFORT, climate::CLIMATE_PRESET_HOME, climate::CLIMATE_PRESET_SLEEP,
climate::CLIMATE_PRESET_ACTIVITY}) {
if (traits.supports_preset(preset))
msg.supported_presets.push_back(static_cast<enums::ClimatePreset>(preset));
}
for (auto const &custom_preset : traits.get_supported_custom_presets()) {
msg.supported_custom_presets.push_back(custom_preset);
}
for (auto swing_mode : {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, climate::CLIMATE_SWING_VERTICAL,
climate::CLIMATE_SWING_HORIZONTAL}) {
if (traits.supports_swing_mode(swing_mode))
@@ -531,6 +552,12 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
call.set_away(msg.away);
if (msg.has_fan_mode)
call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode));
if (msg.has_custom_fan_mode)
call.set_fan_mode(msg.custom_fan_mode);
if (msg.has_preset)
call.set_preset(static_cast<climate::ClimatePreset>(msg.preset));
if (msg.has_custom_preset)
call.set_preset(msg.custom_preset);
if (msg.has_swing_mode)
call.set_swing_mode(static_cast<climate::ClimateSwingMode>(msg.swing_mode));
call.perform();
@@ -637,13 +664,18 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
#endif
#ifdef USE_DEEP_SLEEP
resp.has_deep_sleep = deep_sleep::global_has_deep_sleep;
#endif
#ifdef ESPHOME_PROJECT_NAME
resp.project_name = ESPHOME_PROJECT_NAME;
resp.project_version = ESPHOME_PROJECT_VERSION;
#endif
return resp;
}
void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
for (auto &it : this->parent_->get_state_subs())
if (it.entity_id == msg.entity_id)
if (it.entity_id == msg.entity_id && it.attribute.value() == msg.attribute) {
it.callback(msg.state);
}
}
void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
bool found = false;
@@ -660,6 +692,7 @@ void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistant
for (auto &it : this->parent_->get_state_subs()) {
SubscribeHomeAssistantStateResponse resp;
resp.entity_id = it.entity_id;
resp.attribute = it.attribute.value();
if (!this->send_subscribe_home_assistant_state_response(resp)) {
this->on_fatal_error();
return;

View File

@@ -62,6 +62,16 @@ template<> const char *proto_enum_to_string<enums::FanDirection>(enums::FanDirec
return "UNKNOWN";
}
}
template<> const char *proto_enum_to_string<enums::SensorStateClass>(enums::SensorStateClass value) {
switch (value) {
case enums::STATE_CLASS_NONE:
return "STATE_CLASS_NONE";
case enums::STATE_CLASS_MEASUREMENT:
return "STATE_CLASS_MEASUREMENT";
default:
return "UNKNOWN";
}
}
template<> const char *proto_enum_to_string<enums::LogLevel>(enums::LogLevel value) {
switch (value) {
case enums::LOG_LEVEL_NONE:
@@ -108,8 +118,8 @@ template<> const char *proto_enum_to_string<enums::ClimateMode>(enums::ClimateMo
switch (value) {
case enums::CLIMATE_MODE_OFF:
return "CLIMATE_MODE_OFF";
case enums::CLIMATE_MODE_AUTO:
return "CLIMATE_MODE_AUTO";
case enums::CLIMATE_MODE_HEAT_COOL:
return "CLIMATE_MODE_HEAT_COOL";
case enums::CLIMATE_MODE_COOL:
return "CLIMATE_MODE_COOL";
case enums::CLIMATE_MODE_HEAT:
@@ -118,6 +128,8 @@ template<> const char *proto_enum_to_string<enums::ClimateMode>(enums::ClimateMo
return "CLIMATE_MODE_FAN_ONLY";
case enums::CLIMATE_MODE_DRY:
return "CLIMATE_MODE_DRY";
case enums::CLIMATE_MODE_AUTO:
return "CLIMATE_MODE_AUTO";
default:
return "UNKNOWN";
}
@@ -178,6 +190,26 @@ template<> const char *proto_enum_to_string<enums::ClimateAction>(enums::Climate
return "UNKNOWN";
}
}
template<> const char *proto_enum_to_string<enums::ClimatePreset>(enums::ClimatePreset value) {
switch (value) {
case enums::CLIMATE_PRESET_ECO:
return "CLIMATE_PRESET_ECO";
case enums::CLIMATE_PRESET_AWAY:
return "CLIMATE_PRESET_AWAY";
case enums::CLIMATE_PRESET_BOOST:
return "CLIMATE_PRESET_BOOST";
case enums::CLIMATE_PRESET_COMFORT:
return "CLIMATE_PRESET_COMFORT";
case enums::CLIMATE_PRESET_HOME:
return "CLIMATE_PRESET_HOME";
case enums::CLIMATE_PRESET_SLEEP:
return "CLIMATE_PRESET_SLEEP";
case enums::CLIMATE_PRESET_ACTIVITY:
return "CLIMATE_PRESET_ACTIVITY";
default:
return "UNKNOWN";
}
}
bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
@@ -328,6 +360,14 @@ bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited v
this->model = value.as_string();
return true;
}
case 8: {
this->project_name = value.as_string();
return true;
}
case 9: {
this->project_version = value.as_string();
return true;
}
default:
return false;
}
@@ -340,6 +380,8 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(5, this->compilation_time);
buffer.encode_string(6, this->model);
buffer.encode_bool(7, this->has_deep_sleep);
buffer.encode_string(8, this->project_name);
buffer.encode_string(9, this->project_version);
}
void DeviceInfoResponse::dump_to(std::string &out) const {
char buffer[64];
@@ -371,6 +413,14 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
out.append(" has_deep_sleep: ");
out.append(YESNO(this->has_deep_sleep));
out.append("\n");
out.append(" project_name: ");
out.append("'").append(this->project_name).append("'");
out.append("\n");
out.append(" project_version: ");
out.append("'").append(this->project_version).append("'");
out.append("\n");
out.append("}");
}
void ListEntitiesRequest::encode(ProtoWriteBuffer buffer) const {}
@@ -1507,6 +1557,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->force_update = value.as_bool();
return true;
}
case 10: {
this->state_class = value.as_enum<enums::SensorStateClass>();
return true;
}
default:
return false;
}
@@ -1561,6 +1615,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_int32(7, this->accuracy_decimals);
buffer.encode_bool(8, this->force_update);
buffer.encode_string(9, this->device_class);
buffer.encode_enum<enums::SensorStateClass>(10, this->state_class);
}
void ListEntitiesSensorResponse::dump_to(std::string &out) const {
char buffer[64];
@@ -1602,6 +1657,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const {
out.append(" device_class: ");
out.append("'").append(this->device_class).append("'");
out.append("\n");
out.append(" state_class: ");
out.append(proto_enum_to_string<enums::SensorStateClass>(this->state_class));
out.append("\n");
out.append("}");
}
bool SensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
@@ -2123,12 +2182,17 @@ bool SubscribeHomeAssistantStateResponse::decode_length(uint32_t field_id, Proto
this->entity_id = value.as_string();
return true;
}
case 2: {
this->attribute = value.as_string();
return true;
}
default:
return false;
}
}
void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->entity_id);
buffer.encode_string(2, this->attribute);
}
void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const {
char buffer[64];
@@ -2136,6 +2200,10 @@ void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const {
out.append(" entity_id: ");
out.append("'").append(this->entity_id).append("'");
out.append("\n");
out.append(" attribute: ");
out.append("'").append(this->attribute).append("'");
out.append("\n");
out.append("}");
}
bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
@@ -2148,6 +2216,10 @@ bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDel
this->state = value.as_string();
return true;
}
case 3: {
this->attribute = value.as_string();
return true;
}
default:
return false;
}
@@ -2155,6 +2227,7 @@ bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDel
void HomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->entity_id);
buffer.encode_string(2, this->state);
buffer.encode_string(3, this->attribute);
}
void HomeAssistantStateResponse::dump_to(std::string &out) const {
char buffer[64];
@@ -2166,6 +2239,10 @@ void HomeAssistantStateResponse::dump_to(std::string &out) const {
out.append(" state: ");
out.append("'").append(this->state).append("'");
out.append("\n");
out.append(" attribute: ");
out.append("'").append(this->attribute).append("'");
out.append("\n");
out.append("}");
}
void GetTimeRequest::encode(ProtoWriteBuffer buffer) const {}
@@ -2610,6 +2687,10 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v
this->supported_swing_modes.push_back(value.as_enum<enums::ClimateSwingMode>());
return true;
}
case 16: {
this->supported_presets.push_back(value.as_enum<enums::ClimatePreset>());
return true;
}
default:
return false;
}
@@ -2628,6 +2709,14 @@ bool ListEntitiesClimateResponse::decode_length(uint32_t field_id, ProtoLengthDe
this->unique_id = value.as_string();
return true;
}
case 15: {
this->supported_custom_fan_modes.push_back(value.as_string());
return true;
}
case 17: {
this->supported_custom_presets.push_back(value.as_string());
return true;
}
default:
return false;
}
@@ -2675,6 +2764,15 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
for (auto &it : this->supported_swing_modes) {
buffer.encode_enum<enums::ClimateSwingMode>(14, it, true);
}
for (auto &it : this->supported_custom_fan_modes) {
buffer.encode_string(15, it, true);
}
for (auto &it : this->supported_presets) {
buffer.encode_enum<enums::ClimatePreset>(16, it, true);
}
for (auto &it : this->supported_custom_presets) {
buffer.encode_string(17, it, true);
}
}
void ListEntitiesClimateResponse::dump_to(std::string &out) const {
char buffer[64];
@@ -2744,6 +2842,24 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
out.append(proto_enum_to_string<enums::ClimateSwingMode>(it));
out.append("\n");
}
for (const auto &it : this->supported_custom_fan_modes) {
out.append(" supported_custom_fan_modes: ");
out.append("'").append(it).append("'");
out.append("\n");
}
for (const auto &it : this->supported_presets) {
out.append(" supported_presets: ");
out.append(proto_enum_to_string<enums::ClimatePreset>(it));
out.append("\n");
}
for (const auto &it : this->supported_custom_presets) {
out.append(" supported_custom_presets: ");
out.append("'").append(it).append("'");
out.append("\n");
}
out.append("}");
}
bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
@@ -2768,6 +2884,24 @@ bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
this->swing_mode = value.as_enum<enums::ClimateSwingMode>();
return true;
}
case 12: {
this->preset = value.as_enum<enums::ClimatePreset>();
return true;
}
default:
return false;
}
}
bool ClimateStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 11: {
this->custom_fan_mode = value.as_string();
return true;
}
case 13: {
this->custom_preset = value.as_string();
return true;
}
default:
return false;
}
@@ -2809,6 +2943,9 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_enum<enums::ClimateAction>(8, this->action);
buffer.encode_enum<enums::ClimateFanMode>(9, this->fan_mode);
buffer.encode_enum<enums::ClimateSwingMode>(10, this->swing_mode);
buffer.encode_string(11, this->custom_fan_mode);
buffer.encode_enum<enums::ClimatePreset>(12, this->preset);
buffer.encode_string(13, this->custom_preset);
}
void ClimateStateResponse::dump_to(std::string &out) const {
char buffer[64];
@@ -2857,6 +2994,18 @@ void ClimateStateResponse::dump_to(std::string &out) const {
out.append(" swing_mode: ");
out.append(proto_enum_to_string<enums::ClimateSwingMode>(this->swing_mode));
out.append("\n");
out.append(" custom_fan_mode: ");
out.append("'").append(this->custom_fan_mode).append("'");
out.append("\n");
out.append(" preset: ");
out.append(proto_enum_to_string<enums::ClimatePreset>(this->preset));
out.append("\n");
out.append(" custom_preset: ");
out.append("'").append(this->custom_preset).append("'");
out.append("\n");
out.append("}");
}
bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
@@ -2905,6 +3054,36 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
this->swing_mode = value.as_enum<enums::ClimateSwingMode>();
return true;
}
case 16: {
this->has_custom_fan_mode = value.as_bool();
return true;
}
case 18: {
this->has_preset = value.as_bool();
return true;
}
case 19: {
this->preset = value.as_enum<enums::ClimatePreset>();
return true;
}
case 20: {
this->has_custom_preset = value.as_bool();
return true;
}
default:
return false;
}
}
bool ClimateCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 17: {
this->custom_fan_mode = value.as_string();
return true;
}
case 21: {
this->custom_preset = value.as_string();
return true;
}
default:
return false;
}
@@ -2947,6 +3126,12 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_enum<enums::ClimateFanMode>(13, this->fan_mode);
buffer.encode_bool(14, this->has_swing_mode);
buffer.encode_enum<enums::ClimateSwingMode>(15, this->swing_mode);
buffer.encode_bool(16, this->has_custom_fan_mode);
buffer.encode_string(17, this->custom_fan_mode);
buffer.encode_bool(18, this->has_preset);
buffer.encode_enum<enums::ClimatePreset>(19, this->preset);
buffer.encode_bool(20, this->has_custom_preset);
buffer.encode_string(21, this->custom_preset);
}
void ClimateCommandRequest::dump_to(std::string &out) const {
char buffer[64];
@@ -3014,6 +3199,30 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
out.append(" swing_mode: ");
out.append(proto_enum_to_string<enums::ClimateSwingMode>(this->swing_mode));
out.append("\n");
out.append(" has_custom_fan_mode: ");
out.append(YESNO(this->has_custom_fan_mode));
out.append("\n");
out.append(" custom_fan_mode: ");
out.append("'").append(this->custom_fan_mode).append("'");
out.append("\n");
out.append(" has_preset: ");
out.append(YESNO(this->has_preset));
out.append("\n");
out.append(" preset: ");
out.append(proto_enum_to_string<enums::ClimatePreset>(this->preset));
out.append("\n");
out.append(" has_custom_preset: ");
out.append(YESNO(this->has_custom_preset));
out.append("\n");
out.append(" custom_preset: ");
out.append("'").append(this->custom_preset).append("'");
out.append("\n");
out.append("}");
}

View File

@@ -32,6 +32,10 @@ enum FanDirection : uint32_t {
FAN_DIRECTION_FORWARD = 0,
FAN_DIRECTION_REVERSE = 1,
};
enum SensorStateClass : uint32_t {
STATE_CLASS_NONE = 0,
STATE_CLASS_MEASUREMENT = 1,
};
enum LogLevel : uint32_t {
LOG_LEVEL_NONE = 0,
LOG_LEVEL_ERROR = 1,
@@ -53,11 +57,12 @@ enum ServiceArgType : uint32_t {
};
enum ClimateMode : uint32_t {
CLIMATE_MODE_OFF = 0,
CLIMATE_MODE_AUTO = 1,
CLIMATE_MODE_HEAT_COOL = 1,
CLIMATE_MODE_COOL = 2,
CLIMATE_MODE_HEAT = 3,
CLIMATE_MODE_FAN_ONLY = 4,
CLIMATE_MODE_DRY = 5,
CLIMATE_MODE_AUTO = 6,
};
enum ClimateFanMode : uint32_t {
CLIMATE_FAN_ON = 0,
@@ -84,12 +89,21 @@ enum ClimateAction : uint32_t {
CLIMATE_ACTION_DRYING = 5,
CLIMATE_ACTION_FAN = 6,
};
enum ClimatePreset : uint32_t {
CLIMATE_PRESET_ECO = 0,
CLIMATE_PRESET_AWAY = 1,
CLIMATE_PRESET_BOOST = 2,
CLIMATE_PRESET_COMFORT = 3,
CLIMATE_PRESET_HOME = 4,
CLIMATE_PRESET_SLEEP = 5,
CLIMATE_PRESET_ACTIVITY = 6,
};
} // namespace enums
class HelloRequest : public ProtoMessage {
public:
std::string client_info{}; // NOLINT
std::string client_info{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -98,9 +112,9 @@ class HelloRequest : public ProtoMessage {
};
class HelloResponse : public ProtoMessage {
public:
uint32_t api_version_major{0}; // NOLINT
uint32_t api_version_minor{0}; // NOLINT
std::string server_info{}; // NOLINT
uint32_t api_version_major{0};
uint32_t api_version_minor{0};
std::string server_info{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -110,7 +124,7 @@ class HelloResponse : public ProtoMessage {
};
class ConnectRequest : public ProtoMessage {
public:
std::string password{}; // NOLINT
std::string password{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -119,7 +133,7 @@ class ConnectRequest : public ProtoMessage {
};
class ConnectResponse : public ProtoMessage {
public:
bool invalid_password{false}; // NOLINT
bool invalid_password{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -163,13 +177,15 @@ class DeviceInfoRequest : public ProtoMessage {
};
class DeviceInfoResponse : public ProtoMessage {
public:
bool uses_password{false}; // NOLINT
std::string name{}; // NOLINT
std::string mac_address{}; // NOLINT
std::string esphome_version{}; // NOLINT
std::string compilation_time{}; // NOLINT
std::string model{}; // NOLINT
bool has_deep_sleep{false}; // NOLINT
bool uses_password{false};
std::string name{};
std::string mac_address{};
std::string esphome_version{};
std::string compilation_time{};
std::string model{};
bool has_deep_sleep{false};
std::string project_name{};
std::string project_version{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -200,12 +216,12 @@ class SubscribeStatesRequest : public ProtoMessage {
};
class ListEntitiesBinarySensorResponse : public ProtoMessage {
public:
std::string object_id{}; // NOLINT
uint32_t key{0}; // NOLINT
std::string name{}; // NOLINT
std::string unique_id{}; // NOLINT
std::string device_class{}; // NOLINT
bool is_status_binary_sensor{false}; // NOLINT
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
std::string device_class{};
bool is_status_binary_sensor{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -216,9 +232,9 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage {
};
class BinarySensorStateResponse : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
bool state{false}; // NOLINT
bool missing_state{false}; // NOLINT
uint32_t key{0};
bool state{false};
bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -228,14 +244,14 @@ class BinarySensorStateResponse : public ProtoMessage {
};
class ListEntitiesCoverResponse : public ProtoMessage {
public:
std::string object_id{}; // NOLINT
uint32_t key{0}; // NOLINT
std::string name{}; // NOLINT
std::string unique_id{}; // NOLINT
bool assumed_state{false}; // NOLINT
bool supports_position{false}; // NOLINT
bool supports_tilt{false}; // NOLINT
std::string device_class{}; // NOLINT
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
bool assumed_state{false};
bool supports_position{false};
bool supports_tilt{false};
std::string device_class{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -246,11 +262,11 @@ class ListEntitiesCoverResponse : public ProtoMessage {
};
class CoverStateResponse : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
enums::LegacyCoverState legacy_state{}; // NOLINT
float position{0.0f}; // NOLINT
float tilt{0.0f}; // NOLINT
enums::CoverOperation current_operation{}; // NOLINT
uint32_t key{0};
enums::LegacyCoverState legacy_state{};
float position{0.0f};
float tilt{0.0f};
enums::CoverOperation current_operation{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -260,14 +276,14 @@ class CoverStateResponse : public ProtoMessage {
};
class CoverCommandRequest : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
bool has_legacy_command{false}; // NOLINT
enums::LegacyCoverCommand legacy_command{}; // NOLINT
bool has_position{false}; // NOLINT
float position{0.0f}; // NOLINT
bool has_tilt{false}; // NOLINT
float tilt{0.0f}; // NOLINT
bool stop{false}; // NOLINT
uint32_t key{0};
bool has_legacy_command{false};
enums::LegacyCoverCommand legacy_command{};
bool has_position{false};
float position{0.0f};
bool has_tilt{false};
float tilt{0.0f};
bool stop{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -277,14 +293,14 @@ class CoverCommandRequest : public ProtoMessage {
};
class ListEntitiesFanResponse : public ProtoMessage {
public:
std::string object_id{}; // NOLINT
uint32_t key{0}; // NOLINT
std::string name{}; // NOLINT
std::string unique_id{}; // NOLINT
bool supports_oscillation{false}; // NOLINT
bool supports_speed{false}; // NOLINT
bool supports_direction{false}; // NOLINT
int32_t supported_speed_count{0}; // NOLINT
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
bool supports_oscillation{false};
bool supports_speed{false};
bool supports_direction{false};
int32_t supported_speed_count{0};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -295,12 +311,12 @@ class ListEntitiesFanResponse : public ProtoMessage {
};
class FanStateResponse : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
bool state{false}; // NOLINT
bool oscillating{false}; // NOLINT
enums::FanSpeed speed{}; // NOLINT
enums::FanDirection direction{}; // NOLINT
int32_t speed_level{0}; // NOLINT
uint32_t key{0};
bool state{false};
bool oscillating{false};
enums::FanSpeed speed{};
enums::FanDirection direction{};
int32_t speed_level{0};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -310,17 +326,17 @@ class FanStateResponse : public ProtoMessage {
};
class FanCommandRequest : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
bool has_state{false}; // NOLINT
bool state{false}; // NOLINT
bool has_speed{false}; // NOLINT
enums::FanSpeed speed{}; // NOLINT
bool has_oscillating{false}; // NOLINT
bool oscillating{false}; // NOLINT
bool has_direction{false}; // NOLINT
enums::FanDirection direction{}; // NOLINT
bool has_speed_level{false}; // NOLINT
int32_t speed_level{0}; // NOLINT
uint32_t key{0};
bool has_state{false};
bool state{false};
bool has_speed{false};
enums::FanSpeed speed{};
bool has_oscillating{false};
bool oscillating{false};
bool has_direction{false};
enums::FanDirection direction{};
bool has_speed_level{false};
int32_t speed_level{0};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -330,17 +346,17 @@ class FanCommandRequest : public ProtoMessage {
};
class ListEntitiesLightResponse : public ProtoMessage {
public:
std::string object_id{}; // NOLINT
uint32_t key{0}; // NOLINT
std::string name{}; // NOLINT
std::string unique_id{}; // NOLINT
bool supports_brightness{false}; // NOLINT
bool supports_rgb{false}; // NOLINT
bool supports_white_value{false}; // NOLINT
bool supports_color_temperature{false}; // NOLINT
float min_mireds{0.0f}; // NOLINT
float max_mireds{0.0f}; // NOLINT
std::vector<std::string> effects{}; // NOLINT
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
bool supports_brightness{false};
bool supports_rgb{false};
bool supports_white_value{false};
bool supports_color_temperature{false};
float min_mireds{0.0f};
float max_mireds{0.0f};
std::vector<std::string> effects{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -351,15 +367,15 @@ class ListEntitiesLightResponse : public ProtoMessage {
};
class LightStateResponse : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
bool state{false}; // NOLINT
float brightness{0.0f}; // NOLINT
float red{0.0f}; // NOLINT
float green{0.0f}; // NOLINT
float blue{0.0f}; // NOLINT
float white{0.0f}; // NOLINT
float color_temperature{0.0f}; // NOLINT
std::string effect{}; // NOLINT
uint32_t key{0};
bool state{false};
float brightness{0.0f};
float red{0.0f};
float green{0.0f};
float blue{0.0f};
float white{0.0f};
float color_temperature{0.0f};
std::string effect{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -370,25 +386,25 @@ class LightStateResponse : public ProtoMessage {
};
class LightCommandRequest : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
bool has_state{false}; // NOLINT
bool state{false}; // NOLINT
bool has_brightness{false}; // NOLINT
float brightness{0.0f}; // NOLINT
bool has_rgb{false}; // NOLINT
float red{0.0f}; // NOLINT
float green{0.0f}; // NOLINT
float blue{0.0f}; // NOLINT
bool has_white{false}; // NOLINT
float white{0.0f}; // NOLINT
bool has_color_temperature{false}; // NOLINT
float color_temperature{0.0f}; // NOLINT
bool has_transition_length{false}; // NOLINT
uint32_t transition_length{0}; // NOLINT
bool has_flash_length{false}; // NOLINT
uint32_t flash_length{0}; // NOLINT
bool has_effect{false}; // NOLINT
std::string effect{}; // NOLINT
uint32_t key{0};
bool has_state{false};
bool state{false};
bool has_brightness{false};
float brightness{0.0f};
bool has_rgb{false};
float red{0.0f};
float green{0.0f};
float blue{0.0f};
bool has_white{false};
float white{0.0f};
bool has_color_temperature{false};
float color_temperature{0.0f};
bool has_transition_length{false};
uint32_t transition_length{0};
bool has_flash_length{false};
uint32_t flash_length{0};
bool has_effect{false};
std::string effect{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -399,15 +415,16 @@ class LightCommandRequest : public ProtoMessage {
};
class ListEntitiesSensorResponse : public ProtoMessage {
public:
std::string object_id{}; // NOLINT
uint32_t key{0}; // NOLINT
std::string name{}; // NOLINT
std::string unique_id{}; // NOLINT
std::string icon{}; // NOLINT
std::string unit_of_measurement{}; // NOLINT
int32_t accuracy_decimals{0}; // NOLINT
bool force_update{false}; // NOLINT
std::string device_class{}; // NOLINT
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
std::string icon{};
std::string unit_of_measurement{};
int32_t accuracy_decimals{0};
bool force_update{false};
std::string device_class{};
enums::SensorStateClass state_class{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -418,9 +435,9 @@ class ListEntitiesSensorResponse : public ProtoMessage {
};
class SensorStateResponse : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
float state{0.0f}; // NOLINT
bool missing_state{false}; // NOLINT
uint32_t key{0};
float state{0.0f};
bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -430,12 +447,12 @@ class SensorStateResponse : public ProtoMessage {
};
class ListEntitiesSwitchResponse : public ProtoMessage {
public:
std::string object_id{}; // NOLINT
uint32_t key{0}; // NOLINT
std::string name{}; // NOLINT
std::string unique_id{}; // NOLINT
std::string icon{}; // NOLINT
bool assumed_state{false}; // NOLINT
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
std::string icon{};
bool assumed_state{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -446,8 +463,8 @@ class ListEntitiesSwitchResponse : public ProtoMessage {
};
class SwitchStateResponse : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
bool state{false}; // NOLINT
uint32_t key{0};
bool state{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -457,8 +474,8 @@ class SwitchStateResponse : public ProtoMessage {
};
class SwitchCommandRequest : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
bool state{false}; // NOLINT
uint32_t key{0};
bool state{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -468,11 +485,11 @@ class SwitchCommandRequest : public ProtoMessage {
};
class ListEntitiesTextSensorResponse : public ProtoMessage {
public:
std::string object_id{}; // NOLINT
uint32_t key{0}; // NOLINT
std::string name{}; // NOLINT
std::string unique_id{}; // NOLINT
std::string icon{}; // NOLINT
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
std::string icon{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -482,9 +499,9 @@ class ListEntitiesTextSensorResponse : public ProtoMessage {
};
class TextSensorStateResponse : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
std::string state{}; // NOLINT
bool missing_state{false}; // NOLINT
uint32_t key{0};
std::string state{};
bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -495,8 +512,8 @@ class TextSensorStateResponse : public ProtoMessage {
};
class SubscribeLogsRequest : public ProtoMessage {
public:
enums::LogLevel level{}; // NOLINT
bool dump_config{false}; // NOLINT
enums::LogLevel level{};
bool dump_config{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -505,10 +522,10 @@ class SubscribeLogsRequest : public ProtoMessage {
};
class SubscribeLogsResponse : public ProtoMessage {
public:
enums::LogLevel level{}; // NOLINT
std::string tag{}; // NOLINT
std::string message{}; // NOLINT
bool send_failed{false}; // NOLINT
enums::LogLevel level{};
std::string tag{};
std::string message{};
bool send_failed{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -525,8 +542,8 @@ class SubscribeHomeassistantServicesRequest : public ProtoMessage {
};
class HomeassistantServiceMap : public ProtoMessage {
public:
std::string key{}; // NOLINT
std::string value{}; // NOLINT
std::string key{};
std::string value{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -535,11 +552,11 @@ class HomeassistantServiceMap : public ProtoMessage {
};
class HomeassistantServiceResponse : public ProtoMessage {
public:
std::string service{}; // NOLINT
std::vector<HomeassistantServiceMap> data{}; // NOLINT
std::vector<HomeassistantServiceMap> data_template{}; // NOLINT
std::vector<HomeassistantServiceMap> variables{}; // NOLINT
bool is_event{false}; // NOLINT
std::string service{};
std::vector<HomeassistantServiceMap> data{};
std::vector<HomeassistantServiceMap> data_template{};
std::vector<HomeassistantServiceMap> variables{};
bool is_event{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -556,7 +573,8 @@ class SubscribeHomeAssistantStatesRequest : public ProtoMessage {
};
class SubscribeHomeAssistantStateResponse : public ProtoMessage {
public:
std::string entity_id{}; // NOLINT
std::string entity_id{};
std::string attribute{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -565,8 +583,9 @@ class SubscribeHomeAssistantStateResponse : public ProtoMessage {
};
class HomeAssistantStateResponse : public ProtoMessage {
public:
std::string entity_id{}; // NOLINT
std::string state{}; // NOLINT
std::string entity_id{};
std::string state{};
std::string attribute{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -582,7 +601,7 @@ class GetTimeRequest : public ProtoMessage {
};
class GetTimeResponse : public ProtoMessage {
public:
uint32_t epoch_seconds{0}; // NOLINT
uint32_t epoch_seconds{0};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -591,8 +610,8 @@ class GetTimeResponse : public ProtoMessage {
};
class ListEntitiesServicesArgument : public ProtoMessage {
public:
std::string name{}; // NOLINT
enums::ServiceArgType type{}; // NOLINT
std::string name{};
enums::ServiceArgType type{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -602,9 +621,9 @@ class ListEntitiesServicesArgument : public ProtoMessage {
};
class ListEntitiesServicesResponse : public ProtoMessage {
public:
std::string name{}; // NOLINT
uint32_t key{0}; // NOLINT
std::vector<ListEntitiesServicesArgument> args{}; // NOLINT
std::string name{};
uint32_t key{0};
std::vector<ListEntitiesServicesArgument> args{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -614,15 +633,15 @@ class ListEntitiesServicesResponse : public ProtoMessage {
};
class ExecuteServiceArgument : public ProtoMessage {
public:
bool bool_{false}; // NOLINT
int32_t legacy_int{0}; // NOLINT
float float_{0.0f}; // NOLINT
std::string string_{}; // NOLINT
int32_t int_{0}; // NOLINT
std::vector<bool> bool_array{}; // NOLINT
std::vector<int32_t> int_array{}; // NOLINT
std::vector<float> float_array{}; // NOLINT
std::vector<std::string> string_array{}; // NOLINT
bool bool_{false};
int32_t legacy_int{0};
float float_{0.0f};
std::string string_{};
int32_t int_{0};
std::vector<bool> bool_array{};
std::vector<int32_t> int_array{};
std::vector<float> float_array{};
std::vector<std::string> string_array{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -633,8 +652,8 @@ class ExecuteServiceArgument : public ProtoMessage {
};
class ExecuteServiceRequest : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
std::vector<ExecuteServiceArgument> args{}; // NOLINT
uint32_t key{0};
std::vector<ExecuteServiceArgument> args{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -644,10 +663,10 @@ class ExecuteServiceRequest : public ProtoMessage {
};
class ListEntitiesCameraResponse : public ProtoMessage {
public:
std::string object_id{}; // NOLINT
uint32_t key{0}; // NOLINT
std::string name{}; // NOLINT
std::string unique_id{}; // NOLINT
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -657,9 +676,9 @@ class ListEntitiesCameraResponse : public ProtoMessage {
};
class CameraImageResponse : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
std::string data{}; // NOLINT
bool done{false}; // NOLINT
uint32_t key{0};
std::string data{};
bool done{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -670,8 +689,8 @@ class CameraImageResponse : public ProtoMessage {
};
class CameraImageRequest : public ProtoMessage {
public:
bool single{false}; // NOLINT
bool stream{false}; // NOLINT
bool single{false};
bool stream{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -680,20 +699,23 @@ class CameraImageRequest : public ProtoMessage {
};
class ListEntitiesClimateResponse : public ProtoMessage {
public:
std::string object_id{}; // NOLINT
uint32_t key{0}; // NOLINT
std::string name{}; // NOLINT
std::string unique_id{}; // NOLINT
bool supports_current_temperature{false}; // NOLINT
bool supports_two_point_target_temperature{false}; // NOLINT
std::vector<enums::ClimateMode> supported_modes{}; // NOLINT
float visual_min_temperature{0.0f}; // NOLINT
float visual_max_temperature{0.0f}; // NOLINT
float visual_temperature_step{0.0f}; // NOLINT
bool supports_away{false}; // NOLINT
bool supports_action{false}; // NOLINT
std::vector<enums::ClimateFanMode> supported_fan_modes{}; // NOLINT
std::vector<enums::ClimateSwingMode> supported_swing_modes{}; // NOLINT
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
bool supports_current_temperature{false};
bool supports_two_point_target_temperature{false};
std::vector<enums::ClimateMode> supported_modes{};
float visual_min_temperature{0.0f};
float visual_max_temperature{0.0f};
float visual_temperature_step{0.0f};
bool supports_away{false};
bool supports_action{false};
std::vector<enums::ClimateFanMode> supported_fan_modes{};
std::vector<enums::ClimateSwingMode> supported_swing_modes{};
std::vector<std::string> supported_custom_fan_modes{};
std::vector<enums::ClimatePreset> supported_presets{};
std::vector<std::string> supported_custom_presets{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@@ -704,45 +726,56 @@ class ListEntitiesClimateResponse : public ProtoMessage {
};
class ClimateStateResponse : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
enums::ClimateMode mode{}; // NOLINT
float current_temperature{0.0f}; // NOLINT
float target_temperature{0.0f}; // NOLINT
float target_temperature_low{0.0f}; // NOLINT
float target_temperature_high{0.0f}; // NOLINT
bool away{false}; // NOLINT
enums::ClimateAction action{}; // NOLINT
enums::ClimateFanMode fan_mode{}; // NOLINT
enums::ClimateSwingMode swing_mode{}; // NOLINT
uint32_t key{0};
enums::ClimateMode mode{};
float current_temperature{0.0f};
float target_temperature{0.0f};
float target_temperature_low{0.0f};
float target_temperature_high{0.0f};
bool away{false};
enums::ClimateAction action{};
enums::ClimateFanMode fan_mode{};
enums::ClimateSwingMode swing_mode{};
std::string custom_fan_mode{};
enums::ClimatePreset preset{};
std::string custom_preset{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ClimateCommandRequest : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
bool has_mode{false}; // NOLINT
enums::ClimateMode mode{}; // NOLINT
bool has_target_temperature{false}; // NOLINT
float target_temperature{0.0f}; // NOLINT
bool has_target_temperature_low{false}; // NOLINT
float target_temperature_low{0.0f}; // NOLINT
bool has_target_temperature_high{false}; // NOLINT
float target_temperature_high{0.0f}; // NOLINT
bool has_away{false}; // NOLINT
bool away{false}; // NOLINT
bool has_fan_mode{false}; // NOLINT
enums::ClimateFanMode fan_mode{}; // NOLINT
bool has_swing_mode{false}; // NOLINT
enums::ClimateSwingMode swing_mode{}; // NOLINT
uint32_t key{0};
bool has_mode{false};
enums::ClimateMode mode{};
bool has_target_temperature{false};
float target_temperature{0.0f};
bool has_target_temperature_low{false};
float target_temperature_low{0.0f};
bool has_target_temperature_high{false};
float target_temperature_high{0.0f};
bool has_away{false};
bool away{false};
bool has_fan_mode{false};
enums::ClimateFanMode fan_mode{};
bool has_swing_mode{false};
enums::ClimateSwingMode swing_mode{};
bool has_custom_fan_mode{false};
std::string custom_fan_mode{};
bool has_preset{false};
enums::ClimatePreset preset{};
bool has_custom_preset{false};
std::string custom_preset{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};

View File

@@ -208,9 +208,11 @@ void APIServer::send_homeassistant_service_call(const HomeassistantServiceRespon
}
}
APIServer::APIServer() { global_api_server = this; }
void APIServer::subscribe_home_assistant_state(std::string entity_id, std::function<void(std::string)> f) {
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f) {
this->state_subs_.push_back(HomeAssistantStateSubscription{
.entity_id = std::move(entity_id),
.attribute = std::move(attribute),
.callback = std::move(f),
});
}

View File

@@ -71,10 +71,12 @@ class APIServer : public Component, public Controller {
struct HomeAssistantStateSubscription {
std::string entity_id;
optional<std::string> attribute;
std::function<void(std::string)> callback;
};
void subscribe_home_assistant_state(std::string entity_id, std::function<void(std::string)> f);
void subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f);
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; }

View File

@@ -76,13 +76,13 @@ class CustomAPIDevice {
global_api_server->register_user_service(service);
}
/** Subscribe to the state of an entity from Home Assistant.
/** Subscribe to the state (or attribute state) of an entity from Home Assistant.
*
* Usage:
*
* ```cpp
* void setup() override {
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast");
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "climate.kitchen", "current_temperature");
* }
*
* void on_state_changed(std::string state) {
@@ -93,17 +93,19 @@ class CustomAPIDevice {
* @tparam T The class type creating the service, automatically deduced from the function pointer.
* @param callback The member function to call when the entity state changes.
* @param entity_id The entity_id to track.
* @param attribute The entity state attribute to track.
*/
template<typename T>
void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id) {
void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id,
const std::string &attribute = "") {
auto f = std::bind(callback, (T *) this, std::placeholders::_1);
global_api_server->subscribe_home_assistant_state(entity_id, f);
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), f);
}
/** Subscribe to the state of an entity from Home Assistant.
/** Subscribe to the state (or attribute state) of an entity from Home Assistant.
*
* Usage:
*
*å
* ```cpp
* void setup() override {
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast");
@@ -117,11 +119,13 @@ class CustomAPIDevice {
* @tparam T The class type creating the service, automatically deduced from the function pointer.
* @param callback The member function to call when the entity state changes.
* @param entity_id The entity_id to track.
* @param attribute The entity state attribute to track.
*/
template<typename T>
void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id) {
void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id,
const std::string &attribute = "") {
auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1);
global_api_server->subscribe_home_assistant_state(entity_id, f);
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), f);
}
/** Call a Home Assistant service from ESPHome.

View File

@@ -11,7 +11,6 @@ from esphome.const import (
CONF_DIV_RATIO,
CONF_CAPACITANCE,
)
from esphome.core import coroutine
AUTO_LOAD = ["sensor", "binary_sensor"]
MULTI_CONF = True
@@ -40,11 +39,10 @@ AS3935_SCHEMA = cv.Schema(
)
@coroutine
def setup_as3935(var, config):
yield cg.register_component(var, config)
async def setup_as3935(var, config):
await cg.register_component(var, config)
irq_pin = yield cg.gpio_pin_expression(config[CONF_IRQ_PIN])
irq_pin = await cg.gpio_pin_expression(config[CONF_IRQ_PIN])
cg.add(var.set_irq_pin(irq_pin))
cg.add(var.set_indoor(config[CONF_INDOOR]))
cg.add(var.set_noise_level(config[CONF_NOISE_LEVEL]))

View File

@@ -12,7 +12,7 @@ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
)
def to_code(config):
hub = yield cg.get_variable(config[CONF_AS3935_ID])
var = yield binary_sensor.new_binary_sensor(config)
async def to_code(config):
hub = await cg.get_variable(config[CONF_AS3935_ID])
var = await binary_sensor.new_binary_sensor(config)
cg.add(hub.set_thunder_alert_binary_sensor(var))

View File

@@ -5,6 +5,7 @@ from esphome.const import (
CONF_DISTANCE,
CONF_LIGHTNING_ENERGY,
DEVICE_CLASS_EMPTY,
STATE_CLASS_NONE,
UNIT_KILOMETER,
UNIT_EMPTY,
ICON_SIGNAL_DISTANCE_VARIANT,
@@ -18,24 +19,28 @@ CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935),
cv.Optional(CONF_DISTANCE): sensor.sensor_schema(
UNIT_KILOMETER, ICON_SIGNAL_DISTANCE_VARIANT, 1, DEVICE_CLASS_EMPTY
UNIT_KILOMETER,
ICON_SIGNAL_DISTANCE_VARIANT,
1,
DEVICE_CLASS_EMPTY,
STATE_CLASS_NONE,
),
cv.Optional(CONF_LIGHTNING_ENERGY): sensor.sensor_schema(
UNIT_EMPTY, ICON_FLASH, 1, DEVICE_CLASS_EMPTY
UNIT_EMPTY, ICON_FLASH, 1, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE
),
}
).extend(cv.COMPONENT_SCHEMA)
def to_code(config):
hub = yield cg.get_variable(config[CONF_AS3935_ID])
async def to_code(config):
hub = await cg.get_variable(config[CONF_AS3935_ID])
if CONF_DISTANCE in config:
conf = config[CONF_DISTANCE]
distance_sensor = yield sensor.new_sensor(conf)
distance_sensor = await sensor.new_sensor(conf)
cg.add(hub.set_distance_sensor(distance_sensor))
if CONF_LIGHTNING_ENERGY in config:
conf = config[CONF_LIGHTNING_ENERGY]
lightning_energy_sensor = yield sensor.new_sensor(conf)
lightning_energy_sensor = await sensor.new_sensor(conf)
cg.add(hub.set_energy_sensor(lightning_energy_sensor))

View File

@@ -20,7 +20,7 @@ CONFIG_SCHEMA = cv.All(
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield as3935.setup_as3935(var, config)
yield i2c.register_i2c_device(var, config)
await as3935.setup_as3935(var, config)
await i2c.register_i2c_device(var, config)

View File

@@ -20,7 +20,7 @@ CONFIG_SCHEMA = cv.All(
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield as3935.setup_as3935(var, config)
yield spi.register_spi_device(var, config)
await as3935.setup_as3935(var, config)
await spi.register_spi_device(var, config)

View File

@@ -33,7 +33,7 @@ void SPIAS3935Component::write_register(uint8_t reg, uint8_t mask, uint8_t bits,
uint8_t SPIAS3935Component::read_register(uint8_t reg) {
uint8_t value = 0;
this->enable();
this->write_byte(reg |= SPI_READ_M);
this->write_byte(reg | SPI_READ_M);
value = this->read_byte();
// According to datsheet, the chip select must be written HIGH, LOW, HIGH
// to correctly end the READ command.

View File

@@ -6,10 +6,10 @@ CODEOWNERS = ["@OttoWinter"]
@coroutine_with_priority(200.0)
def to_code(config):
async def to_code(config):
if CORE.is_esp32:
# https://github.com/OttoWinter/AsyncTCP/blob/master/library.json
cg.add_library("AsyncTCP-esphome", "1.1.1")
# https://github.com/esphome/AsyncTCP/blob/master/library.json
cg.add_library("esphome/AsyncTCP-esphome", "1.2.2")
elif CORE.is_esp8266:
# https://github.com/OttoWinter/ESPAsyncTCP
cg.add_library("ESPAsyncTCP-esphome", "1.2.3")

View File

@@ -13,6 +13,7 @@ from esphome.const import (
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
ICON_EMPTY,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
UNIT_VOLT,
@@ -33,16 +34,28 @@ CONFIG_SCHEMA = (
cv.GenerateID(): cv.declare_id(ATCMiThermometer),
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
UNIT_CELSIUS,
ICON_EMPTY,
1,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_HUMIDITY
UNIT_PERCENT,
ICON_EMPTY,
0,
DEVICE_CLASS_HUMIDITY,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_BATTERY
UNIT_PERCENT,
ICON_EMPTY,
0,
DEVICE_CLASS_BATTERY,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE
UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
),
}
)
@@ -51,22 +64,22 @@ CONFIG_SCHEMA = (
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield esp32_ble_tracker.register_ble_device(var, config)
await cg.register_component(var, config)
await esp32_ble_tracker.register_ble_device(var, config)
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
if CONF_TEMPERATURE in config:
sens = yield sensor.new_sensor(config[CONF_TEMPERATURE])
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
cg.add(var.set_temperature(sens))
if CONF_HUMIDITY in config:
sens = yield sensor.new_sensor(config[CONF_HUMIDITY])
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
cg.add(var.set_humidity(sens))
if CONF_BATTERY_LEVEL in config:
sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL])
sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL])
cg.add(var.set_battery_level(sens))
if CONF_BATTERY_VOLTAGE in config:
sens = yield sensor.new_sensor(config[CONF_BATTERY_VOLTAGE])
sens = await sensor.new_sensor(config[CONF_BATTERY_VOLTAGE])
cg.add(var.set_battery_voltage(sens))

View File

@@ -58,6 +58,24 @@ void ATM90E32Component::update() {
if (this->phase_[2].power_factor_sensor_ != nullptr) {
this->phase_[2].power_factor_sensor_->publish_state(this->get_power_factor_c_());
}
if (this->phase_[0].forward_active_energy_sensor_ != nullptr) {
this->phase_[0].forward_active_energy_sensor_->publish_state(this->get_forward_active_energy_a_());
}
if (this->phase_[1].forward_active_energy_sensor_ != nullptr) {
this->phase_[1].forward_active_energy_sensor_->publish_state(this->get_forward_active_energy_b_());
}
if (this->phase_[2].forward_active_energy_sensor_ != nullptr) {
this->phase_[2].forward_active_energy_sensor_->publish_state(this->get_forward_active_energy_c_());
}
if (this->phase_[0].reverse_active_energy_sensor_ != nullptr) {
this->phase_[0].reverse_active_energy_sensor_->publish_state(this->get_reverse_active_energy_a_());
}
if (this->phase_[1].reverse_active_energy_sensor_ != nullptr) {
this->phase_[1].reverse_active_energy_sensor_->publish_state(this->get_reverse_active_energy_b_());
}
if (this->phase_[2].reverse_active_energy_sensor_ != nullptr) {
this->phase_[2].reverse_active_energy_sensor_->publish_state(this->get_reverse_active_energy_c_());
}
if (this->freq_sensor_ != nullptr) {
this->freq_sensor_->publish_state(this->get_frequency_());
}
@@ -119,16 +137,22 @@ void ATM90E32Component::dump_config() {
LOG_SENSOR(" ", "Power A", this->phase_[0].power_sensor_);
LOG_SENSOR(" ", "Reactive Power A", this->phase_[0].reactive_power_sensor_);
LOG_SENSOR(" ", "PF A", this->phase_[0].power_factor_sensor_);
LOG_SENSOR(" ", "Active Forward Energy A", this->phase_[0].forward_active_energy_sensor_);
LOG_SENSOR(" ", "Active Reverse Energy A", this->phase_[0].reverse_active_energy_sensor_);
LOG_SENSOR(" ", "Voltage B", this->phase_[1].voltage_sensor_);
LOG_SENSOR(" ", "Current B", this->phase_[1].current_sensor_);
LOG_SENSOR(" ", "Power B", this->phase_[1].power_sensor_);
LOG_SENSOR(" ", "Reactive Power B", this->phase_[1].reactive_power_sensor_);
LOG_SENSOR(" ", "PF B", this->phase_[1].power_factor_sensor_);
LOG_SENSOR(" ", "Active Forward Energy B", this->phase_[1].forward_active_energy_sensor_);
LOG_SENSOR(" ", "Active Reverse Energy B", this->phase_[1].reverse_active_energy_sensor_);
LOG_SENSOR(" ", "Voltage C", this->phase_[2].voltage_sensor_);
LOG_SENSOR(" ", "Current C", this->phase_[2].current_sensor_);
LOG_SENSOR(" ", "Power C", this->phase_[2].power_sensor_);
LOG_SENSOR(" ", "Reactive Power C", this->phase_[2].reactive_power_sensor_);
LOG_SENSOR(" ", "PF C", this->phase_[2].power_factor_sensor_);
LOG_SENSOR(" ", "Active Forward Energy C", this->phase_[2].forward_active_energy_sensor_);
LOG_SENSOR(" ", "Active Reverse Energy C", this->phase_[2].reverse_active_energy_sensor_);
LOG_SENSOR(" ", "Frequency", this->freq_sensor_);
LOG_SENSOR(" ", "Chip Temp", this->chip_temperature_sensor_);
}
@@ -239,6 +263,30 @@ float ATM90E32Component::get_power_factor_c_() {
int16_t pf = this->read16_(ATM90E32_REGISTER_PFMEANC);
return (float) pf / 1000;
}
float ATM90E32Component::get_forward_active_energy_a_() {
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYA);
return (float) val * 10 / 3200; // convert register value to WattHours
}
float ATM90E32Component::get_forward_active_energy_b_() {
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYB);
return (float) val * 10 / 3200;
}
float ATM90E32Component::get_forward_active_energy_c_() {
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYC);
return (float) val * 10 / 3200;
}
float ATM90E32Component::get_reverse_active_energy_a_() {
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYA);
return (float) val * 10 / 3200;
}
float ATM90E32Component::get_reverse_active_energy_b_() {
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYB);
return (float) val * 10 / 3200;
}
float ATM90E32Component::get_reverse_active_energy_c_() {
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYC);
return (float) val * 10 / 3200;
}
float ATM90E32Component::get_frequency_() {
uint16_t freq = this->read16_(ATM90E32_REGISTER_FREQ);
return (float) freq / 100;

View File

@@ -20,6 +20,12 @@ class ATM90E32Component : public PollingComponent,
void set_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].current_sensor_ = obj; }
void set_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_sensor_ = obj; }
void set_reactive_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].reactive_power_sensor_ = obj; }
void set_forward_active_energy_sensor(int phase, sensor::Sensor *obj) {
this->phase_[phase].forward_active_energy_sensor_ = obj;
}
void set_reverse_active_energy_sensor(int phase, sensor::Sensor *obj) {
this->phase_[phase].reverse_active_energy_sensor_ = obj;
}
void set_power_factor_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_factor_sensor_ = obj; }
void set_volt_gain(int phase, uint16_t gain) { this->phase_[phase].volt_gain_ = gain; }
void set_ct_gain(int phase, uint16_t gain) { this->phase_[phase].ct_gain_ = gain; }
@@ -52,6 +58,12 @@ class ATM90E32Component : public PollingComponent,
float get_power_factor_a_();
float get_power_factor_b_();
float get_power_factor_c_();
float get_forward_active_energy_a_();
float get_forward_active_energy_b_();
float get_forward_active_energy_c_();
float get_reverse_active_energy_a_();
float get_reverse_active_energy_b_();
float get_reverse_active_energy_c_();
float get_frequency_();
float get_chip_temperature_();
@@ -63,6 +75,8 @@ class ATM90E32Component : public PollingComponent,
sensor::Sensor *power_sensor_{nullptr};
sensor::Sensor *reactive_power_sensor_{nullptr};
sensor::Sensor *power_factor_sensor_{nullptr};
sensor::Sensor *forward_active_energy_sensor_{nullptr};
sensor::Sensor *reverse_active_energy_sensor_{nullptr};
} phase_[3];
sensor::Sensor *freq_sensor_{nullptr};
sensor::Sensor *chip_temperature_sensor_{nullptr};

View File

@@ -3,13 +3,17 @@ import esphome.config_validation as cv
from esphome.components import sensor, spi
from esphome.const import (
CONF_ID,
CONF_REACTIVE_POWER,
CONF_VOLTAGE,
CONF_CURRENT,
CONF_POWER,
CONF_POWER_FACTOR,
CONF_FREQUENCY,
CONF_FORWARD_ACTIVE_ENERGY,
CONF_REVERSE_ACTIVE_ENERGY,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_TEMPERATURE,
@@ -17,6 +21,7 @@ from esphome.const import (
ICON_EMPTY,
ICON_LIGHTBULB,
ICON_CURRENT_AC,
STATE_CLASS_MEASUREMENT,
UNIT_HERTZ,
UNIT_VOLT,
UNIT_AMPERE,
@@ -24,13 +29,13 @@ from esphome.const import (
UNIT_EMPTY,
UNIT_CELSIUS,
UNIT_VOLT_AMPS_REACTIVE,
UNIT_WATT_HOURS,
)
CONF_PHASE_A = "phase_a"
CONF_PHASE_B = "phase_b"
CONF_PHASE_C = "phase_c"
CONF_REACTIVE_POWER = "reactive_power"
CONF_LINE_FREQUENCY = "line_frequency"
CONF_CHIP_TEMPERATURE = "chip_temperature"
CONF_GAIN_PGA = "gain_pga"
@@ -59,19 +64,37 @@ ATM90E32Component = atm90e32_ns.class_(
ATM90E32_PHASE_SCHEMA = cv.Schema(
{
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE
UNIT_VOLT,
ICON_EMPTY,
2,
DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT
UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT
),
cv.Optional(CONF_POWER): sensor.sensor_schema(
UNIT_WATT, ICON_EMPTY, 2, DEVICE_CLASS_POWER
UNIT_WATT, ICON_EMPTY, 2, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
),
cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema(
UNIT_VOLT_AMPS_REACTIVE, ICON_LIGHTBULB, 2, DEVICE_CLASS_EMPTY
UNIT_VOLT_AMPS_REACTIVE,
ICON_LIGHTBULB,
2,
DEVICE_CLASS_EMPTY,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
UNIT_EMPTY, ICON_EMPTY, 2, DEVICE_CLASS_POWER_FACTOR
UNIT_EMPTY,
ICON_EMPTY,
2,
DEVICE_CLASS_POWER_FACTOR,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_FORWARD_ACTIVE_ENERGY): sensor.sensor_schema(
UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_MEASUREMENT
),
cv.Optional(CONF_REVERSE_ACTIVE_ENERGY): sensor.sensor_schema(
UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_MEASUREMENT
),
cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t,
cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t,
@@ -86,10 +109,18 @@ CONFIG_SCHEMA = (
cv.Optional(CONF_PHASE_B): ATM90E32_PHASE_SCHEMA,
cv.Optional(CONF_PHASE_C): ATM90E32_PHASE_SCHEMA,
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY
UNIT_HERTZ,
ICON_CURRENT_AC,
1,
DEVICE_CLASS_EMPTY,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CHIP_TEMPERATURE): sensor.sensor_schema(
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
UNIT_CELSIUS,
ICON_EMPTY,
1,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
),
cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True),
cv.Optional(CONF_CURRENT_PHASES, default="3"): cv.enum(
@@ -103,10 +134,10 @@ CONFIG_SCHEMA = (
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield spi.register_spi_device(var, config)
await cg.register_component(var, config)
await spi.register_spi_device(var, config)
for i, phase in enumerate([CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]):
if phase not in config:
@@ -115,25 +146,31 @@ def to_code(config):
cg.add(var.set_volt_gain(i, conf[CONF_GAIN_VOLTAGE]))
cg.add(var.set_ct_gain(i, conf[CONF_GAIN_CT]))
if CONF_VOLTAGE in conf:
sens = yield sensor.new_sensor(conf[CONF_VOLTAGE])
sens = await sensor.new_sensor(conf[CONF_VOLTAGE])
cg.add(var.set_voltage_sensor(i, sens))
if CONF_CURRENT in conf:
sens = yield sensor.new_sensor(conf[CONF_CURRENT])
sens = await sensor.new_sensor(conf[CONF_CURRENT])
cg.add(var.set_current_sensor(i, sens))
if CONF_POWER in conf:
sens = yield sensor.new_sensor(conf[CONF_POWER])
sens = await sensor.new_sensor(conf[CONF_POWER])
cg.add(var.set_power_sensor(i, sens))
if CONF_REACTIVE_POWER in conf:
sens = yield sensor.new_sensor(conf[CONF_REACTIVE_POWER])
sens = await sensor.new_sensor(conf[CONF_REACTIVE_POWER])
cg.add(var.set_reactive_power_sensor(i, sens))
if CONF_POWER_FACTOR in conf:
sens = yield sensor.new_sensor(conf[CONF_POWER_FACTOR])
sens = await sensor.new_sensor(conf[CONF_POWER_FACTOR])
cg.add(var.set_power_factor_sensor(i, sens))
if CONF_FORWARD_ACTIVE_ENERGY in conf:
sens = await sensor.new_sensor(conf[CONF_FORWARD_ACTIVE_ENERGY])
cg.add(var.set_forward_active_energy_sensor(i, sens))
if CONF_REVERSE_ACTIVE_ENERGY in conf:
sens = await sensor.new_sensor(conf[CONF_REVERSE_ACTIVE_ENERGY])
cg.add(var.set_reverse_active_energy_sensor(i, sens))
if CONF_FREQUENCY in config:
sens = yield sensor.new_sensor(config[CONF_FREQUENCY])
sens = await sensor.new_sensor(config[CONF_FREQUENCY])
cg.add(var.set_freq_sensor(sens))
if CONF_CHIP_TEMPERATURE in config:
sens = yield sensor.new_sensor(config[CONF_CHIP_TEMPERATURE])
sens = await sensor.new_sensor(config[CONF_CHIP_TEMPERATURE])
cg.add(var.set_chip_temperature_sensor(sens))
cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES]))

View File

@@ -0,0 +1,82 @@
#include "b_parasite.h"
#include "esphome/core/log.h"
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace b_parasite {
static const char* TAG = "b_parasite";
void BParasite::dump_config() {
ESP_LOGCONFIG(TAG, "b_parasite");
LOG_SENSOR(" ", "Battery Voltage", this->battery_voltage_);
LOG_SENSOR(" ", "Temperature", this->temperature_);
LOG_SENSOR(" ", "Humidity", this->humidity_);
LOG_SENSOR(" ", "Soil Moisture", this->soil_moisture_);
}
bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice& device) {
if (device.address_uint64() != address_) {
ESP_LOGVV(TAG, "parse_device(): unknown MAC address.");
return false;
}
ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str());
const auto& service_datas = device.get_service_datas();
if (service_datas.size() != 1) {
ESP_LOGE(TAG, "Unexpected service_datas size (%d)", service_datas.size());
return false;
}
const auto& service_data = service_datas[0];
ESP_LOGVV(TAG, "Service data:");
for (const uint8_t byte : service_data.data) {
ESP_LOGVV(TAG, "0x%02x", byte);
}
const auto& data = service_data.data;
// Counter for deduplicating messages.
uint8_t counter = data[1] & 0x0f;
if (last_processed_counter_ == counter) {
ESP_LOGVV(TAG, "Skipping already processed counter (%u)", counter);
return false;
}
// Battery voltage in millivolts.
uint16_t battery_millivolt = data[2] << 8 | data[3];
float battery_voltage = battery_millivolt / 1000.0f;
// Temperature in 1000 * Celcius.
uint16_t temp_millicelcius = data[4] << 8 | data[5];
float temp_celcius = temp_millicelcius / 1000.0f;
// Relative air humidity in the range [0, 2^16).
uint16_t humidity = data[6] << 8 | data[7];
float humidity_percent = (100.0f * humidity) / (1 << 16);
// Relative soil moisture in [0 - 2^16).
uint16_t soil_moisture = data[8] << 8 | data[9];
float moisture_percent = (100.0f * soil_moisture) / (1 << 16);
if (battery_voltage_ != nullptr) {
battery_voltage_->publish_state(battery_voltage);
}
if (temperature_ != nullptr) {
temperature_->publish_state(temp_celcius);
}
if (humidity_ != nullptr) {
humidity_->publish_state(humidity_percent);
}
if (soil_moisture_ != nullptr) {
soil_moisture_->publish_state(moisture_percent);
}
last_processed_counter_ = counter;
return true;
}
} // namespace b_parasite
} // namespace esphome
#endif // ARDUINO_ARCH_ESP32

View File

@@ -0,0 +1,40 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace b_parasite {
class BParasite : public Component, public esp32_ble_tracker::ESPBTDeviceListener {
public:
void set_address(uint64_t address) { address_ = address; };
void set_bindkey(const std::string &bindkey);
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void set_battery_voltage(sensor::Sensor *battery_voltage) { battery_voltage_ = battery_voltage; }
void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; }
void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; }
void set_soil_moisture(sensor::Sensor *soil_moisture) { soil_moisture_ = soil_moisture; }
protected:
// The received advertisement packet contains an unsigned 4 bits wrap-around counter
// for deduplicating messages.
int8_t last_processed_counter_ = -1;
uint64_t address_;
sensor::Sensor *battery_voltage_{nullptr};
sensor::Sensor *temperature_{nullptr};
sensor::Sensor *humidity_{nullptr};
sensor::Sensor *soil_moisture_{nullptr};
};
} // namespace b_parasite
} // namespace esphome
#endif // ARDUINO_ARCH_ESP32

View File

@@ -0,0 +1,81 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, esp32_ble_tracker
from esphome.const import (
CONF_BATTERY_VOLTAGE,
CONF_HUMIDITY,
CONF_ID,
CONF_MOISTURE,
CONF_MAC_ADDRESS,
CONF_TEMPERATURE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
ICON_EMPTY,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
UNIT_VOLT,
)
CODEOWNERS = ["@rbaron"]
DEPENDENCIES = ["esp32_ble_tracker"]
b_parasite_ns = cg.esphome_ns.namespace("b_parasite")
BParasite = b_parasite_ns.class_(
"BParasite", esp32_ble_tracker.ESPBTDeviceListener, cg.Component
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(BParasite),
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
UNIT_CELSIUS,
ICON_EMPTY,
1,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
UNIT_PERCENT,
ICON_EMPTY,
1,
DEVICE_CLASS_HUMIDITY,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
),
cv.Optional(CONF_MOISTURE): sensor.sensor_schema(
UNIT_PERCENT,
ICON_EMPTY,
1,
DEVICE_CLASS_HUMIDITY,
STATE_CLASS_MEASUREMENT,
),
}
)
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA)
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await esp32_ble_tracker.register_ble_device(var, config)
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
for (config_key, setter) in [
(CONF_TEMPERATURE, var.set_temperature),
(CONF_HUMIDITY, var.set_humidity),
(CONF_BATTERY_VOLTAGE, var.set_battery_voltage),
(CONF_MOISTURE, var.set_soil_moisture),
]:
if config_key in config:
sens = await sensor.new_sensor(config[config_key])
cg.add(setter(sens))

View File

@@ -39,12 +39,12 @@ CONFIG_SCHEMA = cv.All(
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield climate.register_climate(var, config)
await cg.register_component(var, config)
await climate.register_climate(var, config)
sens = yield cg.get_variable(config[CONF_SENSOR])
sens = await cg.get_variable(config[CONF_SENSOR])
cg.add(var.set_sensor(sens))
normal_config = BangBangClimateTargetTempConfig(
@@ -53,17 +53,17 @@ def to_code(config):
)
cg.add(var.set_normal_config(normal_config))
yield automation.build_automation(
await automation.build_automation(
var.get_idle_trigger(), [], config[CONF_IDLE_ACTION]
)
if CONF_COOL_ACTION in config:
yield automation.build_automation(
await automation.build_automation(
var.get_cool_trigger(), [], config[CONF_COOL_ACTION]
)
cg.add(var.set_supports_cool(True))
if CONF_HEAT_ACTION in config:
yield automation.build_automation(
await automation.build_automation(
var.get_heat_trigger(), [], config[CONF_HEAT_ACTION]
)
cg.add(var.set_supports_heat(True))

View File

@@ -17,8 +17,8 @@ void BH1750Sensor::setup() {
return;
}
uint8_t mtreg_hi = (this->measurement_time_ >> 5) & 0b111;
uint8_t mtreg_lo = (this->measurement_time_ >> 0) & 0b11111;
uint8_t mtreg_hi = (this->measurement_duration_ >> 5) & 0b111;
uint8_t mtreg_lo = (this->measurement_duration_ >> 0) & 0b11111;
this->write_bytes(BH1750_COMMAND_MT_REG_HI | mtreg_hi, nullptr, 0);
this->write_bytes(BH1750_COMMAND_MT_REG_LO | mtreg_lo, nullptr, 0);
}
@@ -77,7 +77,7 @@ void BH1750Sensor::read_data_() {
}
float lx = float(raw_value) / 1.2f;
lx *= 69.0f / this->measurement_time_;
lx *= 69.0f / this->measurement_duration_;
ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), lx);
this->publish_state(lx);
this->status_clear_warning();

View File

@@ -28,7 +28,7 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c:
* @param resolution The new resolution of the sensor.
*/
void set_resolution(BH1750Resolution resolution);
void set_measurement_time(uint8_t measurement_time) { measurement_time_ = measurement_time; }
void set_measurement_duration(uint8_t measurement_duration) { measurement_duration_ = measurement_duration; }
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)
@@ -41,7 +41,7 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c:
void read_data_();
BH1750Resolution resolution_{BH1750_RESOLUTION_0P5_LX};
uint8_t measurement_time_;
uint8_t measurement_duration_;
};
} // namespace bh1750

View File

@@ -6,7 +6,9 @@ from esphome.const import (
CONF_RESOLUTION,
DEVICE_CLASS_ILLUMINANCE,
ICON_EMPTY,
STATE_CLASS_MEASUREMENT,
UNIT_LUX,
CONF_MEASUREMENT_DURATION,
)
DEPENDENCIES = ["i2c"]
@@ -25,16 +27,21 @@ BH1750Sensor = bh1750_ns.class_(
CONF_MEASUREMENT_TIME = "measurement_time"
CONFIG_SCHEMA = (
sensor.sensor_schema(UNIT_LUX, ICON_EMPTY, 1, DEVICE_CLASS_ILLUMINANCE)
sensor.sensor_schema(
UNIT_LUX, ICON_EMPTY, 1, DEVICE_CLASS_ILLUMINANCE, STATE_CLASS_MEASUREMENT
)
.extend(
{
cv.GenerateID(): cv.declare_id(BH1750Sensor),
cv.Optional(CONF_RESOLUTION, default=0.5): cv.enum(
BH1750_RESOLUTIONS, float=True
),
cv.Optional(CONF_MEASUREMENT_TIME, default=69): cv.int_range(
cv.Optional(CONF_MEASUREMENT_DURATION, default=69): cv.int_range(
min=31, max=254
),
cv.Optional(CONF_MEASUREMENT_TIME): cv.invalid(
"The 'measurement_time' option has been replaced with 'measurement_duration' in 1.18.0"
),
}
)
.extend(cv.polling_component_schema("60s"))
@@ -42,11 +49,11 @@ CONFIG_SCHEMA = (
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield sensor.register_sensor(var, config)
yield i2c.register_i2c_device(var, config)
await cg.register_component(var, config)
await sensor.register_sensor(var, config)
await i2c.register_i2c_device(var, config)
cg.add(var.set_resolution(config[CONF_RESOLUTION]))
cg.add(var.set_measurement_time(config[CONF_MEASUREMENT_TIME]))
cg.add(var.set_measurement_duration(config[CONF_MEASUREMENT_DURATION]))

View File

@@ -21,19 +21,19 @@ CONFIG_SCHEMA = fan.FAN_SCHEMA.extend(
).extend(cv.COMPONENT_SCHEMA)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
yield cg.register_component(var, config)
await cg.register_component(var, config)
fan_ = yield fan.create_fan_state(config)
fan_ = await fan.create_fan_state(config)
cg.add(var.set_fan(fan_))
output_ = yield cg.get_variable(config[CONF_OUTPUT])
output_ = await cg.get_variable(config[CONF_OUTPUT])
cg.add(var.set_output(output_))
if CONF_OSCILLATION_OUTPUT in config:
oscillation_output = yield cg.get_variable(config[CONF_OSCILLATION_OUTPUT])
oscillation_output = await cg.get_variable(config[CONF_OSCILLATION_OUTPUT])
cg.add(var.set_oscillating(oscillation_output))
if CONF_DIRECTION_OUTPUT in config:
direction_output = yield cg.get_variable(config[CONF_DIRECTION_OUTPUT])
direction_output = await cg.get_variable(config[CONF_DIRECTION_OUTPUT])
cg.add(var.set_direction(direction_output))

View File

@@ -14,9 +14,9 @@ CONFIG_SCHEMA = light.BINARY_LIGHT_SCHEMA.extend(
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
yield light.register_light(var, config)
await light.register_light(var, config)
out = yield cg.get_variable(config[CONF_OUTPUT])
out = await cg.get_variable(config[CONF_OUTPUT])
cg.add(var.set_output(out))

View File

@@ -4,6 +4,7 @@ from esphome import automation, core
from esphome.automation import Condition, maybe_simple_id
from esphome.components import mqtt
from esphome.const import (
CONF_DELAY,
CONF_DEVICE_CLASS,
CONF_FILTERS,
CONF_ID,
@@ -50,7 +51,7 @@ from esphome.const import (
DEVICE_CLASS_VIBRATION,
DEVICE_CLASS_WINDOW,
)
from esphome.core import CORE, coroutine, coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
from esphome.util import Registry
CODEOWNERS = ["@esphome/core"]
@@ -120,6 +121,7 @@ DelayedOnOffFilter = binary_sensor_ns.class_("DelayedOnOffFilter", Filter, cg.Co
DelayedOnFilter = binary_sensor_ns.class_("DelayedOnFilter", Filter, cg.Component)
DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Component)
InvertFilter = binary_sensor_ns.class_("InvertFilter", Filter)
AutorepeatFilter = binary_sensor_ns.class_("AutorepeatFilter", Filter, cg.Component)
LambdaFilter = binary_sensor_ns.class_("LambdaFilter", Filter)
FILTER_REGISTRY = Registry()
@@ -127,43 +129,88 @@ validate_filters = cv.validate_registry("filter", FILTER_REGISTRY)
@FILTER_REGISTRY.register("invert", InvertFilter, {})
def invert_filter_to_code(config, filter_id):
yield cg.new_Pvariable(filter_id)
async def invert_filter_to_code(config, filter_id):
return cg.new_Pvariable(filter_id)
@FILTER_REGISTRY.register(
"delayed_on_off", DelayedOnOffFilter, cv.positive_time_period_milliseconds
)
def delayed_on_off_filter_to_code(config, filter_id):
async def delayed_on_off_filter_to_code(config, filter_id):
var = cg.new_Pvariable(filter_id, config)
yield cg.register_component(var, {})
yield var
await cg.register_component(var, {})
return var
@FILTER_REGISTRY.register(
"delayed_on", DelayedOnFilter, cv.positive_time_period_milliseconds
)
def delayed_on_filter_to_code(config, filter_id):
async def delayed_on_filter_to_code(config, filter_id):
var = cg.new_Pvariable(filter_id, config)
yield cg.register_component(var, {})
yield var
await cg.register_component(var, {})
return var
@FILTER_REGISTRY.register(
"delayed_off", DelayedOffFilter, cv.positive_time_period_milliseconds
)
def delayed_off_filter_to_code(config, filter_id):
async def delayed_off_filter_to_code(config, filter_id):
var = cg.new_Pvariable(filter_id, config)
yield cg.register_component(var, {})
yield var
await cg.register_component(var, {})
return var
CONF_TIME_OFF = "time_off"
CONF_TIME_ON = "time_on"
DEFAULT_DELAY = "1s"
DEFAULT_TIME_OFF = "100ms"
DEFAULT_TIME_ON = "900ms"
@FILTER_REGISTRY.register(
"autorepeat",
AutorepeatFilter,
cv.All(
cv.ensure_list(
{
cv.Optional(
CONF_DELAY, default=DEFAULT_DELAY
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_TIME_OFF, default=DEFAULT_TIME_OFF
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_TIME_ON, default=DEFAULT_TIME_ON
): cv.positive_time_period_milliseconds,
}
),
),
)
async def autorepeat_filter_to_code(config, filter_id):
timings = []
if len(config) > 0:
for conf in config:
timings.append((conf[CONF_DELAY], conf[CONF_TIME_OFF], conf[CONF_TIME_ON]))
else:
timings.append(
(
cv.time_period_str_unit(DEFAULT_DELAY).total_milliseconds,
cv.time_period_str_unit(DEFAULT_TIME_OFF).total_milliseconds,
cv.time_period_str_unit(DEFAULT_TIME_ON).total_milliseconds,
)
)
var = cg.new_Pvariable(filter_id, timings)
await cg.register_component(var, {})
return var
@FILTER_REGISTRY.register("lambda", LambdaFilter, cv.returning_lambda)
def lambda_filter_to_code(config, filter_id):
lambda_ = yield cg.process_lambda(
async def lambda_filter_to_code(config, filter_id):
lambda_ = await cg.process_lambda(
config, [(bool, "x")], return_type=cg.optional.template(bool)
)
yield cg.new_Pvariable(filter_id, lambda_)
return cg.new_Pvariable(filter_id, lambda_)
MULTI_CLICK_TIMING_SCHEMA = cv.Schema(
@@ -334,8 +381,7 @@ BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend(
)
@coroutine
def setup_binary_sensor_core_(var, config):
async def setup_binary_sensor_core_(var, config):
cg.add(var.set_name(config[CONF_NAME]))
if CONF_INTERNAL in config:
cg.add(var.set_internal(config[CONF_INTERNAL]))
@@ -344,28 +390,28 @@ def setup_binary_sensor_core_(var, config):
if CONF_INVERTED in config:
cg.add(var.set_inverted(config[CONF_INVERTED]))
if CONF_FILTERS in config:
filters = yield cg.build_registry_list(FILTER_REGISTRY, config[CONF_FILTERS])
filters = await cg.build_registry_list(FILTER_REGISTRY, config[CONF_FILTERS])
cg.add(var.add_filters(filters))
for conf in config.get(CONF_ON_PRESS, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
yield automation.build_automation(trigger, [], conf)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_RELEASE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
yield automation.build_automation(trigger, [], conf)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_CLICK, []):
trigger = cg.new_Pvariable(
conf[CONF_TRIGGER_ID], var, conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH]
)
yield automation.build_automation(trigger, [], conf)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_DOUBLE_CLICK, []):
trigger = cg.new_Pvariable(
conf[CONF_TRIGGER_ID], var, conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH]
)
yield automation.build_automation(trigger, [], conf)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_MULTI_CLICK, []):
timings = []
@@ -381,31 +427,29 @@ def setup_binary_sensor_core_(var, config):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, timings)
if CONF_INVALID_COOLDOWN in conf:
cg.add(trigger.set_invalid_cooldown(conf[CONF_INVALID_COOLDOWN]))
yield cg.register_component(trigger, conf)
yield automation.build_automation(trigger, [], conf)
await cg.register_component(trigger, conf)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_STATE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
yield automation.build_automation(trigger, [(bool, "x")], conf)
await automation.build_automation(trigger, [(bool, "x")], conf)
if CONF_MQTT_ID in config:
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
yield mqtt.register_mqtt_component(mqtt_, config)
await mqtt.register_mqtt_component(mqtt_, config)
@coroutine
def register_binary_sensor(var, config):
async def register_binary_sensor(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_binary_sensor(var))
yield setup_binary_sensor_core_(var, config)
await setup_binary_sensor_core_(var, config)
@coroutine
def new_binary_sensor(config):
async def new_binary_sensor(config):
var = cg.new_Pvariable(config[CONF_ID], config[CONF_NAME])
yield register_binary_sensor(var, config)
yield var
await register_binary_sensor(var, config)
return var
BINARY_SENSOR_CONDITION_SCHEMA = maybe_simple_id(
@@ -422,20 +466,20 @@ BINARY_SENSOR_CONDITION_SCHEMA = maybe_simple_id(
@automation.register_condition(
"binary_sensor.is_on", BinarySensorCondition, BINARY_SENSOR_CONDITION_SCHEMA
)
def binary_sensor_is_on_to_code(config, condition_id, template_arg, args):
paren = yield cg.get_variable(config[CONF_ID])
yield cg.new_Pvariable(condition_id, template_arg, paren, True)
async def binary_sensor_is_on_to_code(config, condition_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(condition_id, template_arg, paren, True)
@automation.register_condition(
"binary_sensor.is_off", BinarySensorCondition, BINARY_SENSOR_CONDITION_SCHEMA
)
def binary_sensor_is_off_to_code(config, condition_id, template_arg, args):
paren = yield cg.get_variable(config[CONF_ID])
yield cg.new_Pvariable(condition_id, template_arg, paren, False)
async def binary_sensor_is_off_to_code(config, condition_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(condition_id, template_arg, paren, False)
@coroutine_with_priority(100.0)
def to_code(config):
async def to_code(config):
cg.add_define("USE_BINARY_SENSOR")
cg.add_global(binary_sensor_ns.using)

View File

@@ -64,6 +64,50 @@ float DelayedOffFilter::get_setup_priority() const { return setup_priority::HARD
optional<bool> InvertFilter::new_value(bool value, bool is_initial) { return !value; }
AutorepeatFilter::AutorepeatFilter(const std::vector<AutorepeatFilterTiming> &timings) : timings_(timings) {}
optional<bool> AutorepeatFilter::new_value(bool value, bool is_initial) {
if (value) {
// Ignore if already running
if (this->active_timing_ != 0)
return {};
this->next_timing_();
return true;
} else {
this->cancel_timeout("TIMING");
this->cancel_timeout("ON_OFF");
this->active_timing_ = 0;
return false;
}
}
void AutorepeatFilter::next_timing_() {
// Entering this method
// 1st time: starts waiting the first delay
// 2nd time: starts waiting the second delay and starts toggling with the first time_off / _on
// last time: no delay to start but have to bump the index to reflect the last
if (this->active_timing_ < this->timings_.size())
this->set_timeout("TIMING", this->timings_[this->active_timing_].delay, [this]() { this->next_timing_(); });
if (this->active_timing_ <= this->timings_.size()) {
this->active_timing_++;
}
if (this->active_timing_ == 2)
this->next_value_(false);
// Leaving this method: if the toggling is started, it has to use [active_timing_ - 2] for the intervals
}
void AutorepeatFilter::next_value_(bool val) {
const AutorepeatFilterTiming &timing = this->timings_[this->active_timing_ - 2];
this->output(val, false); // This is at least the second one so not initial
this->set_timeout("ON_OFF", val ? timing.time_on : timing.time_off, [this, val]() { this->next_value_(!val); });
}
float AutorepeatFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
LambdaFilter::LambdaFilter(const std::function<optional<bool>(bool)> &f) : f_(f) {}
optional<bool> LambdaFilter::new_value(bool value, bool is_initial) { return this->f_(value); }

View File

@@ -66,6 +66,33 @@ class InvertFilter : public Filter {
optional<bool> new_value(bool value, bool is_initial) override;
};
struct AutorepeatFilterTiming {
AutorepeatFilterTiming(uint32_t delay, uint32_t off, uint32_t on) {
this->delay = delay;
this->time_off = off;
this->time_on = on;
}
uint32_t delay;
uint32_t time_off;
uint32_t time_on;
};
class AutorepeatFilter : public Filter, public Component {
public:
explicit AutorepeatFilter(const std::vector<AutorepeatFilterTiming> &timings);
optional<bool> new_value(bool value, bool is_initial) override;
float get_setup_priority() const override;
protected:
void next_timing_();
void next_value_(bool val);
std::vector<AutorepeatFilterTiming> timings_;
uint8_t active_timing_{0};
};
class LambdaFilter : public Filter {
public:
explicit LambdaFilter(const std::function<optional<bool>(bool)> &f);

View File

@@ -12,6 +12,7 @@ from esphome.const import (
ICON_CHECK_CIRCLE_OUTLINE,
CONF_BINARY_SENSOR,
CONF_GROUP,
STATE_CLASS_NONE,
)
DEPENDENCIES = ["binary_sensor"]
@@ -34,7 +35,11 @@ entry = {
CONFIG_SCHEMA = cv.typed_schema(
{
CONF_GROUP: sensor.sensor_schema(
UNIT_EMPTY, ICON_CHECK_CIRCLE_OUTLINE, 0, DEVICE_CLASS_EMPTY
UNIT_EMPTY,
ICON_CHECK_CIRCLE_OUTLINE,
0,
DEVICE_CLASS_EMPTY,
STATE_CLASS_NONE,
).extend(
{
cv.GenerateID(): cv.declare_id(BinarySensorMap),
@@ -48,14 +53,14 @@ CONFIG_SCHEMA = cv.typed_schema(
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield sensor.register_sensor(var, config)
await cg.register_component(var, config)
await sensor.register_sensor(var, config)
constant = SENSOR_MAP_TYPES[config[CONF_TYPE]]
cg.add(var.set_sensor_type(constant))
for ch in config[CONF_CHANNELS]:
input_var = yield cg.get_variable(ch[CONF_BINARY_SENSOR])
input_var = await cg.get_variable(ch[CONF_BINARY_SENSOR])
cg.add(var.add_channel(input_var, ch[CONF_VALUE]))

View File

@@ -0,0 +1,85 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import esp32_ble_tracker
from esphome.const import (
CONF_ID,
CONF_MAC_ADDRESS,
CONF_NAME,
CONF_ON_CONNECT,
CONF_ON_DISCONNECT,
CONF_TRIGGER_ID,
)
from esphome import automation
CODEOWNERS = ["@buxtronix"]
DEPENDENCIES = ["esp32_ble_tracker"]
ble_client_ns = cg.esphome_ns.namespace("ble_client")
BLEClient = ble_client_ns.class_(
"BLEClient", cg.Component, esp32_ble_tracker.ESPBTClient
)
BLEClientNode = ble_client_ns.class_("BLEClientNode")
BLEClientNodeConstRef = BLEClientNode.operator("ref").operator("const")
# Triggers
BLEClientConnectTrigger = ble_client_ns.class_(
"BLEClientConnectTrigger", automation.Trigger.template(BLEClientNodeConstRef)
)
BLEClientDisconnectTrigger = ble_client_ns.class_(
"BLEClientDisconnectTrigger", automation.Trigger.template(BLEClientNodeConstRef)
)
# Espressif platformio framework is built with MAX_BLE_CONN to 3, so
# enforce this in yaml checks.
MULTI_CONF = 3
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(BLEClient),
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
cv.Optional(CONF_NAME): cv.string,
cv.Optional(CONF_ON_CONNECT): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
BLEClientConnectTrigger
),
}
),
cv.Optional(CONF_ON_DISCONNECT): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
BLEClientDisconnectTrigger
),
}
),
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
)
CONF_BLE_CLIENT_ID = "ble_client_id"
BLE_CLIENT_SCHEMA = cv.Schema(
{
cv.Required(CONF_BLE_CLIENT_ID): cv.use_id(BLEClient),
}
)
async def register_ble_node(var, config):
parent = await cg.get_variable(config[CONF_BLE_CLIENT_ID])
cg.add(parent.register_ble_node(var))
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await esp32_ble_tracker.register_client(var, config)
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
for conf in config.get(CONF_ON_CONNECT, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_DISCONNECT, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)

View File

@@ -0,0 +1,37 @@
#pragma once
#include "esphome/core/automation.h"
#include "esphome/components/ble_client/ble_client.h"
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace ble_client {
class BLEClientConnectTrigger : public Trigger<>, public BLEClientNode {
public:
explicit BLEClientConnectTrigger(BLEClient *parent) { parent->register_ble_node(this); }
void loop() override {}
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
if (event == ESP_GATTC_OPEN_EVT && param->open.status == ESP_GATT_OK)
this->trigger();
if (event == ESP_GATTC_SEARCH_CMPL_EVT)
this->node_state = espbt::ClientState::Established;
}
};
class BLEClientDisconnectTrigger : public Trigger<>, public BLEClientNode {
public:
explicit BLEClientDisconnectTrigger(BLEClient *parent) { parent->register_ble_node(this); }
void loop() override {}
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
if (event == ESP_GATTC_DISCONNECT_EVT && memcmp(param->disconnect.remote_bda, this->parent_->remote_bda, 6) == 0)
this->trigger();
if (event == ESP_GATTC_SEARCH_CMPL_EVT)
this->node_state = espbt::ClientState::Established;
}
};
} // namespace ble_client
} // namespace esphome
#endif

View File

@@ -0,0 +1,392 @@
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "ble_client.h"
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace ble_client {
static const char *TAG = "ble_client";
void BLEClient::setup() {
auto ret = esp_ble_gattc_app_register(this->app_id);
if (ret) {
ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret);
this->mark_failed();
}
this->set_states(espbt::ClientState::Idle);
this->enabled = true;
}
void BLEClient::loop() {
if (this->state() == espbt::ClientState::Discovered) {
this->connect();
}
for (auto *node : this->nodes_)
node->loop();
}
void BLEClient::dump_config() {
ESP_LOGCONFIG(TAG, "BLE Client:");
ESP_LOGCONFIG(TAG, " Address: %s", this->address_str().c_str());
}
bool BLEClient::parse_device(const espbt::ESPBTDevice &device) {
if (!this->enabled)
return false;
if (device.address_uint64() != this->address)
return false;
if (this->state() != espbt::ClientState::Idle)
return false;
ESP_LOGD(TAG, "Found device at MAC address [%s]", device.address_str().c_str());
this->set_states(espbt::ClientState::Discovered);
auto addr = device.address_uint64();
this->remote_bda[0] = (addr >> 40) & 0xFF;
this->remote_bda[1] = (addr >> 32) & 0xFF;
this->remote_bda[2] = (addr >> 24) & 0xFF;
this->remote_bda[3] = (addr >> 16) & 0xFF;
this->remote_bda[4] = (addr >> 8) & 0xFF;
this->remote_bda[5] = (addr >> 0) & 0xFF;
return true;
}
std::string BLEClient::address_str() const {
char buf[20];
sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x", (uint8_t)(this->address >> 40) & 0xff,
(uint8_t)(this->address >> 32) & 0xff, (uint8_t)(this->address >> 24) & 0xff,
(uint8_t)(this->address >> 16) & 0xff, (uint8_t)(this->address >> 8) & 0xff,
(uint8_t)(this->address >> 0) & 0xff);
std::string ret;
ret = buf;
return ret;
}
void BLEClient::set_enabled(bool enabled) {
if (enabled == this->enabled)
return;
if (!enabled && this->state() != espbt::ClientState::Idle) {
ESP_LOGI(TAG, "[%s] Disabling BLE client.", this->address_str().c_str());
auto ret = esp_ble_gattc_close(this->gattc_if, this->conn_id);
if (ret) {
ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s status=%d", this->address_str().c_str(), ret);
}
}
this->enabled = enabled;
}
void BLEClient::connect() {
ESP_LOGI(TAG, "Attempting BLE connection to %s", this->address_str().c_str());
auto ret = esp_ble_gattc_open(this->gattc_if, this->remote_bda, BLE_ADDR_TYPE_PUBLIC, true);
if (ret) {
ESP_LOGW(TAG, "esp_ble_gattc_open error, address=%s status=%d", this->address_str().c_str(), ret);
this->set_states(espbt::ClientState::Idle);
} else {
this->set_states(espbt::ClientState::Connecting);
}
}
void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
esp_ble_gattc_cb_param_t *param) {
if (event == ESP_GATTC_REG_EVT && this->app_id != param->reg.app_id)
return;
if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if)
return;
bool all_established = this->all_nodes_established();
switch (event) {
case ESP_GATTC_REG_EVT: {
if (param->reg.status == ESP_GATT_OK) {
ESP_LOGV(TAG, "gattc registered app id %d", this->app_id);
this->gattc_if = esp_gattc_if;
} else {
ESP_LOGE(TAG, "gattc app registration failed id=%d code=%d", param->reg.app_id, param->reg.status);
}
break;
}
case ESP_GATTC_OPEN_EVT: {
ESP_LOGV(TAG, "[%s] ESP_GATTC_OPEN_EVT", this->address_str().c_str());
if (param->open.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "connect to %s failed, status=%d", this->address_str().c_str(), param->open.status);
this->set_states(espbt::ClientState::Idle);
break;
}
this->conn_id = param->open.conn_id;
auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if, param->open.conn_id);
if (ret) {
ESP_LOGW(TAG, "esp_ble_gattc_send_mtu_req failed, status=%d", ret);
}
break;
}
case ESP_GATTC_CFG_MTU_EVT: {
if (param->cfg_mtu.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "cfg_mtu to %s failed, status %d", this->address_str().c_str(), param->cfg_mtu.status);
this->set_states(espbt::ClientState::Idle);
break;
}
ESP_LOGV(TAG, "cfg_mtu status %d, mtu %d", param->cfg_mtu.status, param->cfg_mtu.mtu);
esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, NULL);
break;
}
case ESP_GATTC_DISCONNECT_EVT: {
if (memcmp(param->disconnect.remote_bda, this->remote_bda, 6) != 0) {
return;
}
ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT", this->address_str().c_str());
for (auto &svc : this->services_)
delete svc;
this->services_.clear();
this->set_states(espbt::ClientState::Idle);
break;
}
case ESP_GATTC_SEARCH_RES_EVT: {
BLEService *ble_service = new BLEService();
ble_service->uuid = espbt::ESPBTUUID::from_uuid(param->search_res.srvc_id.uuid);
ble_service->start_handle = param->search_res.start_handle;
ble_service->end_handle = param->search_res.end_handle;
ble_service->client = this;
this->services_.push_back(ble_service);
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
ESP_LOGV(TAG, "[%s] ESP_GATTC_SEARCH_CMPL_EVT", this->address_str().c_str());
for (auto &svc : this->services_) {
ESP_LOGI(TAG, "Service UUID: %s", svc->uuid.to_string().c_str());
ESP_LOGI(TAG, " start_handle: 0x%x end_handle: 0x%x", svc->start_handle, svc->end_handle);
svc->parse_characteristics();
}
this->set_states(espbt::ClientState::Connected);
this->set_state(espbt::ClientState::Established);
break;
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
auto descr = this->get_config_descriptor(param->reg_for_notify.handle);
if (descr == nullptr) {
ESP_LOGW(TAG, "No descriptor found for notify of handle 0x%x", param->reg_for_notify.handle);
break;
}
if (descr->uuid.get_uuid().len != ESP_UUID_LEN_16 ||
descr->uuid.get_uuid().uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) {
ESP_LOGW(TAG, "Handle 0x%x (uuid %s) is not a client config char uuid", param->reg_for_notify.handle,
descr->uuid.to_string().c_str());
break;
}
uint8_t notify_en = 1;
auto status = esp_ble_gattc_write_char_descr(this->gattc_if, this->conn_id, descr->handle, sizeof(notify_en),
&notify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status) {
ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, status=%d", status);
}
break;
}
default:
break;
}
for (auto *node : this->nodes_)
node->gattc_event_handler(event, esp_gattc_if, param);
// Delete characteristics after clients have used them to save RAM.
if (!all_established && this->all_nodes_established()) {
for (auto &svc : this->services_)
delete svc;
this->services_.clear();
}
}
// Parse GATT values into a float for a sensor.
// Ref: https://www.bluetooth.com/specifications/assigned-numbers/format-types/
float BLEClient::parse_char_value(uint8_t *value, uint16_t length) {
// A length of one means a single octet value.
if (length == 0)
return 0;
if (length == 1)
return (float) ((uint8_t) value[0]);
switch (value[0]) {
case 0x1: // boolean.
case 0x2: // 2bit.
case 0x3: // nibble.
case 0x4: // uint8.
return (float) ((uint8_t) value[1]);
case 0x5: // uint12.
case 0x6: // uint16.
if (length > 2) {
return (float) ((uint16_t)(value[1] << 8) + (uint16_t) value[2]);
}
case 0x7: // uint24.
if (length > 3) {
return (float) ((uint32_t)(value[1] << 16) + (uint32_t)(value[2] << 8) + (uint32_t)(value[3]));
}
case 0x8: // uint32.
if (length > 4) {
return (float) ((uint32_t)(value[1] << 24) + (uint32_t)(value[2] << 16) + (uint32_t)(value[3] << 8) +
(uint32_t)(value[4]));
}
case 0xC: // int8.
return (float) ((int8_t) value[1]);
case 0xD: // int12.
case 0xE: // int16.
if (length > 2) {
return (float) ((int16_t)(value[1] << 8) + (int16_t) value[2]);
}
case 0xF: // int24.
if (length > 3) {
return (float) ((int32_t)(value[1] << 16) + (int32_t)(value[2] << 8) + (int32_t)(value[3]));
}
case 0x10: // int32.
if (length > 4) {
return (float) ((int32_t)(value[1] << 24) + (int32_t)(value[2] << 16) + (int32_t)(value[3] << 8) +
(int32_t)(value[4]));
}
}
ESP_LOGW(TAG, "Cannot parse characteristic value of type 0x%x length %d", value[0], length);
return NAN;
}
BLEService *BLEClient::get_service(espbt::ESPBTUUID uuid) {
for (auto svc : this->services_)
if (svc->uuid == uuid)
return svc;
return nullptr;
}
BLEService *BLEClient::get_service(uint16_t uuid) { return this->get_service(espbt::ESPBTUUID::from_uint16(uuid)); }
BLECharacteristic *BLEClient::get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr) {
auto svc = this->get_service(service);
if (svc == nullptr)
return nullptr;
return svc->get_characteristic(chr);
}
BLECharacteristic *BLEClient::get_characteristic(uint16_t service, uint16_t chr) {
return this->get_characteristic(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr));
}
BLEDescriptor *BLEClient::get_config_descriptor(uint16_t handle) {
for (auto &svc : this->services_)
for (auto &chr : svc->characteristics)
if (chr->handle == handle)
for (auto &desc : chr->descriptors)
if (desc->uuid == espbt::ESPBTUUID::from_uint16(0x2902))
return desc;
return nullptr;
}
BLECharacteristic *BLEService::get_characteristic(espbt::ESPBTUUID uuid) {
for (auto &chr : this->characteristics)
if (chr->uuid == uuid)
return chr;
return nullptr;
}
BLECharacteristic *BLEService::get_characteristic(uint16_t uuid) {
return this->get_characteristic(espbt::ESPBTUUID::from_uint16(uuid));
}
BLEDescriptor *BLEClient::get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr) {
auto svc = this->get_service(service);
if (svc == nullptr)
return nullptr;
auto ch = svc->get_characteristic(chr);
if (ch == nullptr)
return nullptr;
return ch->get_descriptor(descr);
}
BLEDescriptor *BLEClient::get_descriptor(uint16_t service, uint16_t chr, uint16_t descr) {
return this->get_descriptor(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr),
espbt::ESPBTUUID::from_uint16(descr));
}
BLEService::~BLEService() {
for (auto &chr : this->characteristics)
delete chr;
}
void BLEService::parse_characteristics() {
uint16_t offset = 0;
esp_gattc_char_elem_t result;
while (true) {
uint16_t count = 1;
esp_gatt_status_t status = esp_ble_gattc_get_all_char(
this->client->gattc_if, this->client->conn_id, this->start_handle, this->end_handle, &result, &count, offset);
if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) {
break;
}
if (status != ESP_GATT_OK) {
ESP_LOGW(TAG, "esp_ble_gattc_get_all_char error, status=%d", status);
break;
}
if (count == 0) {
break;
}
BLECharacteristic *characteristic = new BLECharacteristic();
characteristic->uuid = espbt::ESPBTUUID::from_uuid(result.uuid);
characteristic->properties = result.properties;
characteristic->handle = result.char_handle;
characteristic->service = this;
this->characteristics.push_back(characteristic);
ESP_LOGI(TAG, " characteristic %s, handle 0x%x, properties 0x%x", characteristic->uuid.to_string().c_str(),
characteristic->handle, characteristic->properties);
characteristic->parse_descriptors();
offset++;
}
}
BLECharacteristic::~BLECharacteristic() {
for (auto &desc : this->descriptors)
delete desc;
}
void BLECharacteristic::parse_descriptors() {
uint16_t offset = 0;
esp_gattc_descr_elem_t result;
while (true) {
uint16_t count = 1;
esp_gatt_status_t status = esp_ble_gattc_get_all_descr(
this->service->client->gattc_if, this->service->client->conn_id, this->handle, &result, &count, offset);
if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) {
break;
}
if (status != ESP_GATT_OK) {
ESP_LOGW(TAG, "esp_ble_gattc_get_all_descr error, status=%d", status);
break;
}
if (count == 0) {
break;
}
BLEDescriptor *desc = new BLEDescriptor();
desc->uuid = espbt::ESPBTUUID::from_uuid(result.uuid);
desc->handle = result.handle;
desc->characteristic = this;
this->descriptors.push_back(desc);
ESP_LOGV(TAG, " descriptor %s, handle 0x%x", desc->uuid.to_string().c_str(), desc->handle);
offset++;
}
}
BLEDescriptor *BLECharacteristic::get_descriptor(espbt::ESPBTUUID uuid) {
for (auto &desc : this->descriptors)
if (desc->uuid == uuid)
return desc;
return nullptr;
}
BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) {
return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid));
}
} // namespace ble_client
} // namespace esphome
#endif

View File

@@ -0,0 +1,140 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#ifdef ARDUINO_ARCH_ESP32
#include <string>
#include <array>
#include <esp_gap_ble_api.h>
#include <esp_gattc_api.h>
#include <esp_bt_defs.h>
namespace esphome {
namespace ble_client {
namespace espbt = esphome::esp32_ble_tracker;
class BLEClient;
class BLEService;
class BLECharacteristic;
class BLEClientNode {
public:
virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) = 0;
virtual void loop() = 0;
void set_address(uint64_t address) { address_ = address; }
espbt::ESPBTClient *client;
// This should be transitioned to Established once the node no longer needs
// the services/descriptors/characteristics of the parent client. This will
// allow some memory to be freed.
espbt::ClientState node_state;
BLEClient *parent() { return this->parent_; }
void set_ble_client_parent(BLEClient *parent) { this->parent_ = parent; }
protected:
BLEClient *parent_;
uint64_t address_;
};
class BLEDescriptor {
public:
espbt::ESPBTUUID uuid;
uint16_t handle;
BLECharacteristic *characteristic;
};
class BLECharacteristic {
public:
~BLECharacteristic();
espbt::ESPBTUUID uuid;
uint16_t handle;
esp_gatt_char_prop_t properties;
std::vector<BLEDescriptor *> descriptors;
void parse_descriptors();
BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
BLEDescriptor *get_descriptor(uint16_t uuid);
BLEService *service;
};
class BLEService {
public:
~BLEService();
espbt::ESPBTUUID uuid;
uint16_t start_handle;
uint16_t end_handle;
std::vector<BLECharacteristic *> characteristics;
BLEClient *client;
void parse_characteristics();
BLECharacteristic *get_characteristic(espbt::ESPBTUUID uuid);
BLECharacteristic *get_characteristic(uint16_t uuid);
};
class BLEClient : public espbt::ESPBTClient, public Component {
public:
void setup() override;
void dump_config() override;
void loop() override;
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
bool parse_device(const espbt::ESPBTDevice &device) override;
void on_scan_end() override {}
void connect();
void set_address(uint64_t address) { this->address = address; }
void set_enabled(bool enabled);
void register_ble_node(BLEClientNode *node) {
node->client = this;
node->set_ble_client_parent(this);
this->nodes_.push_back(node);
}
BLEService *get_service(espbt::ESPBTUUID uuid);
BLEService *get_service(uint16_t uuid);
BLECharacteristic *get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr);
BLECharacteristic *get_characteristic(uint16_t service, uint16_t chr);
BLEDescriptor *get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr);
BLEDescriptor *get_descriptor(uint16_t service, uint16_t chr, uint16_t descr);
// Get the configuration descriptor for the given characteristic handle.
BLEDescriptor *get_config_descriptor(uint16_t handle);
float parse_char_value(uint8_t *value, uint16_t length);
int gattc_if;
esp_bd_addr_t remote_bda;
uint16_t conn_id;
uint64_t address;
bool enabled;
std::string address_str() const;
protected:
void set_states(espbt::ClientState st) {
this->set_state(st);
for (auto &node : nodes_)
node->node_state = st;
}
bool all_nodes_established() {
if (this->state() != espbt::ClientState::Established)
return false;
for (auto &node : nodes_)
if (node->node_state != espbt::ClientState::Established)
return false;
return true;
}
std::vector<BLEClientNode *> nodes_;
std::vector<BLEService *> services_;
};
} // namespace ble_client
} // namespace esphome
#endif

View File

@@ -0,0 +1,129 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, ble_client, esp32_ble_tracker
from esphome.const import (
DEVICE_CLASS_EMPTY,
CONF_ID,
CONF_LAMBDA,
STATE_CLASS_NONE,
UNIT_EMPTY,
ICON_EMPTY,
CONF_TRIGGER_ID,
CONF_SERVICE_UUID,
)
from esphome import automation
from .. import ble_client_ns
DEPENDENCIES = ["ble_client"]
CONF_CHARACTERISTIC_UUID = "characteristic_uuid"
CONF_DESCRIPTOR_UUID = "descriptor_uuid"
CONF_NOTIFY = "notify"
CONF_ON_NOTIFY = "on_notify"
adv_data_t = cg.std_vector.template(cg.uint8)
adv_data_t_const_ref = adv_data_t.operator("ref").operator("const")
BLESensor = ble_client_ns.class_(
"BLESensor", sensor.Sensor, cg.PollingComponent, ble_client.BLEClientNode
)
BLESensorNotifyTrigger = ble_client_ns.class_(
"BLESensorNotifyTrigger", automation.Trigger.template(cg.float_)
)
CONFIG_SCHEMA = cv.All(
sensor.sensor_schema(
UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE
)
.extend(
{
cv.GenerateID(): cv.declare_id(BLESensor),
cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
cv.Optional(CONF_DESCRIPTOR_UUID): esp32_ble_tracker.bt_uuid,
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
cv.Optional(CONF_NOTIFY, default=False): cv.boolean,
cv.Optional(CONF_ON_NOTIFY): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
BLESensorNotifyTrigger
),
}
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(ble_client.BLE_CLIENT_SCHEMA)
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add(
var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
cg.add(
var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID])
cg.add(var.set_service_uuid128(uuid128))
if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add(
var.set_char_uuid16(
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
)
)
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
esp32_ble_tracker.bt_uuid32_format
):
cg.add(
var.set_char_uuid32(
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
)
)
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
esp32_ble_tracker.bt_uuid128_format
):
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_CHARACTERISTIC_UUID])
cg.add(var.set_char_uuid128(uuid128))
if CONF_DESCRIPTOR_UUID in config:
if len(config[CONF_DESCRIPTOR_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add(
var.set_descr_uuid16(
esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID])
)
)
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
esp32_ble_tracker.bt_uuid32_format
):
cg.add(
var.set_descr_uuid32(
esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID])
)
)
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
esp32_ble_tracker.bt_uuid128_format
):
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_DESCRIPTOR_UUID])
cg.add(var.set_descr_uuid128(uuid128))
if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(adv_data_t_const_ref, "x")], return_type=cg.float_
)
cg.add(var.set_data_to_value(lambda_))
await cg.register_component(var, config)
await ble_client.register_ble_node(var, config)
cg.add(var.set_enable_notify(config[CONF_NOTIFY]))
await sensor.register_sensor(var, config)
for conf in config.get(CONF_ON_NOTIFY, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await ble_client.register_ble_node(trigger, config)
await automation.build_automation(trigger, [(float, "x")], conf)

View File

@@ -0,0 +1,37 @@
#pragma once
#include "esphome/core/automation.h"
#include "esphome/components/ble_client/sensor/ble_sensor.h"
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace ble_client {
class BLESensorNotifyTrigger : public Trigger<float>, public BLESensor {
public:
explicit BLESensorNotifyTrigger(BLESensor *sensor) { sensor_ = sensor; }
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
switch (event) {
case ESP_GATTC_SEARCH_CMPL_EVT: {
this->sensor_->node_state = espbt::ClientState::Established;
break;
}
case ESP_GATTC_NOTIFY_EVT: {
if (param->notify.conn_id != this->sensor_->parent()->conn_id || param->notify.handle != this->sensor_->handle)
break;
this->trigger(this->sensor_->parent()->parse_char_value(param->notify.value, param->notify.value_len));
}
default:
break;
}
}
protected:
BLESensor *sensor_;
};
} // namespace ble_client
} // namespace esphome
#endif

View File

@@ -0,0 +1,138 @@
#include "ble_sensor.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace ble_client {
static const char *TAG = "ble_sensor";
uint32_t BLESensor::hash_base() { return 343459825UL; }
void BLESensor::loop() {}
void BLESensor::dump_config() {
LOG_SENSOR("", "BLE Sensor", this);
ESP_LOGCONFIG(TAG, " MAC address : %s", this->parent()->address_str().c_str());
ESP_LOGCONFIG(TAG, " Service UUID : %s", this->service_uuid_.to_string().c_str());
ESP_LOGCONFIG(TAG, " Characteristic UUID: %s", this->char_uuid_.to_string().c_str());
ESP_LOGCONFIG(TAG, " Descriptor UUID : %s", this->descr_uuid_.to_string().c_str());
ESP_LOGCONFIG(TAG, " Notifications : %s", YESNO(this->notify_));
LOG_UPDATE_INTERVAL(this);
}
void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) {
switch (event) {
case ESP_GATTC_OPEN_EVT: {
if (param->open.status == ESP_GATT_OK) {
ESP_LOGI(TAG, "[%s] Connected successfully!", this->get_name().c_str());
break;
}
break;
}
case ESP_GATTC_DISCONNECT_EVT: {
ESP_LOGW(TAG, "[%s] Disconnected!", this->get_name().c_str());
this->status_set_warning();
this->publish_state(NAN);
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
this->handle = 0;
auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
if (chr == nullptr) {
this->status_set_warning();
this->publish_state(NAN);
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(),
this->char_uuid_.to_string().c_str());
break;
}
this->handle = chr->handle;
if (this->descr_uuid_.get_uuid().len > 0) {
auto descr = chr->get_descriptor(this->descr_uuid_);
if (descr == nullptr) {
this->status_set_warning();
this->publish_state(NAN);
ESP_LOGW(TAG, "No sensor descriptor found at service %s char %s descr %s",
this->service_uuid_.to_string().c_str(), this->char_uuid_.to_string().c_str(),
this->descr_uuid_.to_string().c_str());
break;
}
this->handle = descr->handle;
}
if (this->notify_) {
auto status =
esp_ble_gattc_register_for_notify(this->parent()->gattc_if, this->parent()->remote_bda, chr->handle);
if (status) {
ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status);
}
} else {
this->node_state = espbt::ClientState::Established;
}
break;
}
case ESP_GATTC_READ_CHAR_EVT: {
if (param->read.conn_id != this->parent()->conn_id)
break;
if (param->read.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
break;
}
if (param->read.handle == this->handle) {
this->status_clear_warning();
this->publish_state(this->parse_data(param->read.value, param->read.value_len));
}
break;
}
case ESP_GATTC_NOTIFY_EVT: {
if (param->notify.conn_id != this->parent()->conn_id || param->notify.handle != this->handle)
break;
ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(),
param->notify.handle, param->notify.value[0]);
this->publish_state(this->parse_data(param->notify.value, param->notify.value_len));
break;
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
this->node_state = espbt::ClientState::Established;
break;
}
default:
break;
}
}
float BLESensor::parse_data(uint8_t *value, uint16_t value_len) {
if (this->data_to_value_func_.has_value()) {
std::vector<uint8_t> data(value, value + value_len);
return (*this->data_to_value_func_)(data);
} else {
return value[0];
}
}
void BLESensor::update() {
if (this->node_state != espbt::ClientState::Established) {
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->get_name().c_str());
return;
}
if (this->handle == 0) {
ESP_LOGW(TAG, "[%s] Cannot poll, no service or characteristic found", this->get_name().c_str());
return;
}
auto status =
esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle, ESP_GATT_AUTH_REQ_NONE);
if (status) {
this->status_set_warning();
this->publish_state(NAN);
ESP_LOGW(TAG, "[%s] Error sending read request for sensor, status=%d", this->get_name().c_str(), status);
}
}
} // namespace ble_client
} // namespace esphome
#endif

View File

@@ -0,0 +1,51 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/ble_client/ble_client.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/sensor/sensor.h"
#ifdef ARDUINO_ARCH_ESP32
#include <esp_gattc_api.h>
namespace esphome {
namespace ble_client {
namespace espbt = esphome::esp32_ble_tracker;
using data_to_value_t = std::function<float(std::vector<uint8_t>)>;
class BLESensor : public sensor::Sensor, public PollingComponent, public BLEClientNode {
public:
void loop() override;
void update() override;
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
void set_char_uuid16(uint16_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
void set_descr_uuid16(uint16_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
void set_descr_uuid32(uint32_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
void set_descr_uuid128(uint8_t *uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
void set_data_to_value(data_to_value_t &&lambda_) { this->data_to_value_func_ = lambda_; }
void set_enable_notify(bool notify) { this->notify_ = notify; }
uint16_t handle;
protected:
uint32_t hash_base() override;
float parse_data(uint8_t *value, uint16_t value_len);
optional<data_to_value_t> data_to_value_func_{};
bool notify_;
espbt::ESPBTUUID service_uuid_;
espbt::ESPBTUUID char_uuid_;
espbt::ESPBTUUID descr_uuid_;
};
} // namespace ble_client
} // namespace esphome
#endif

View File

@@ -0,0 +1,30 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import switch, ble_client
from esphome.const import CONF_ICON, CONF_ID, CONF_INVERTED, ICON_BLUETOOTH
from .. import ble_client_ns
BLEClientSwitch = ble_client_ns.class_(
"BLEClientSwitch", switch.Switch, cg.Component, ble_client.BLEClientNode
)
CONFIG_SCHEMA = (
switch.SWITCH_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(BLEClientSwitch),
cv.Optional(CONF_INVERTED): cv.invalid(
"BLE client switches do not support inverted mode!"
),
cv.Optional(CONF_ICON, default=ICON_BLUETOOTH): switch.icon,
}
)
.extend(ble_client.BLE_CLIENT_SCHEMA)
.extend(cv.COMPONENT_SCHEMA)
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await switch.register_switch(var, config)
await ble_client.register_ble_node(var, config)

View File

@@ -0,0 +1,39 @@
#include "ble_switch.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace ble_client {
static const char *TAG = "ble_switch";
void BLEClientSwitch::write_state(bool state) {
this->parent_->set_enabled(state);
this->publish_state(state);
}
void BLEClientSwitch::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) {
switch (event) {
case ESP_GATTC_REG_EVT:
this->publish_state(this->parent_->enabled);
break;
case ESP_GATTC_OPEN_EVT:
this->node_state = espbt::ClientState::Established;
break;
case ESP_GATTC_DISCONNECT_EVT:
this->node_state = espbt::ClientState::Idle;
this->publish_state(this->parent_->enabled);
break;
default:
break;
}
}
void BLEClientSwitch::dump_config() { LOG_SWITCH("", "BLE Client Switch", this); }
} // namespace ble_client
} // namespace esphome
#endif

View File

@@ -0,0 +1,30 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/ble_client/ble_client.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/switch/switch.h"
#ifdef ARDUINO_ARCH_ESP32
#include <esp_gattc_api.h>
namespace esphome {
namespace ble_client {
namespace espbt = esphome::esp32_ble_tracker;
class BLEClientSwitch : public switch_::Switch, public Component, public BLEClientNode {
public:
void dump_config() override;
void loop() override {}
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
float get_setup_priority() const override { return setup_priority::DATA; }
protected:
void write_state(bool state) override;
};
} // namespace ble_client
} // namespace esphome
#endif

View File

@@ -27,11 +27,11 @@ CONFIG_SCHEMA = cv.All(
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield esp32_ble_tracker.register_ble_device(var, config)
yield binary_sensor.register_binary_sensor(var, config)
await cg.register_component(var, config)
await esp32_ble_tracker.register_ble_device(var, config)
await binary_sensor.register_binary_sensor(var, config)
if CONF_MAC_ADDRESS in config:
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))

View File

@@ -6,6 +6,7 @@ from esphome.const import (
CONF_MAC_ADDRESS,
CONF_ID,
DEVICE_CLASS_SIGNAL_STRENGTH,
STATE_CLASS_MEASUREMENT,
UNIT_DECIBEL,
ICON_EMPTY,
)
@@ -18,7 +19,13 @@ BLERSSISensor = ble_rssi_ns.class_(
)
CONFIG_SCHEMA = cv.All(
sensor.sensor_schema(UNIT_DECIBEL, ICON_EMPTY, 0, DEVICE_CLASS_SIGNAL_STRENGTH)
sensor.sensor_schema(
UNIT_DECIBEL,
ICON_EMPTY,
0,
DEVICE_CLASS_SIGNAL_STRENGTH,
STATE_CLASS_MEASUREMENT,
)
.extend(
{
cv.GenerateID(): cv.declare_id(BLERSSISensor),
@@ -32,11 +39,11 @@ CONFIG_SCHEMA = cv.All(
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield esp32_ble_tracker.register_ble_device(var, config)
yield sensor.register_sensor(var, config)
await cg.register_component(var, config)
await esp32_ble_tracker.register_ble_device(var, config)
await sensor.register_sensor(var, config)
if CONF_MAC_ADDRESS in config:
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))

View File

@@ -24,8 +24,8 @@ CONFIG_SCHEMA = cv.All(
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield esp32_ble_tracker.register_ble_device(var, config)
yield text_sensor.register_text_sensor(var, config)
await cg.register_component(var, config)
await esp32_ble_tracker.register_ble_device(var, config)
await text_sensor.register_text_sensor(var, config)

View File

@@ -12,6 +12,7 @@ from esphome.const import (
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
ICON_EMPTY,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_HECTOPASCAL,
UNIT_PERCENT,
@@ -48,7 +49,11 @@ CONFIG_SCHEMA = (
{
cv.GenerateID(): cv.declare_id(BME280Component),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
UNIT_CELSIUS,
ICON_EMPTY,
1,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
@@ -57,7 +62,11 @@ CONFIG_SCHEMA = (
}
),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE
UNIT_HECTOPASCAL,
ICON_EMPTY,
1,
DEVICE_CLASS_PRESSURE,
STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
@@ -66,7 +75,11 @@ CONFIG_SCHEMA = (
}
),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY
UNIT_PERCENT,
ICON_EMPTY,
1,
DEVICE_CLASS_HUMIDITY,
STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
@@ -84,26 +97,26 @@ CONFIG_SCHEMA = (
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield i2c.register_i2c_device(var, config)
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config:
conf = config[CONF_TEMPERATURE]
sens = yield sensor.new_sensor(conf)
sens = await sensor.new_sensor(conf)
cg.add(var.set_temperature_sensor(sens))
cg.add(var.set_temperature_oversampling(conf[CONF_OVERSAMPLING]))
if CONF_PRESSURE in config:
conf = config[CONF_PRESSURE]
sens = yield sensor.new_sensor(conf)
sens = await sensor.new_sensor(conf)
cg.add(var.set_pressure_sensor(sens))
cg.add(var.set_pressure_oversampling(conf[CONF_OVERSAMPLING]))
if CONF_HUMIDITY in config:
conf = config[CONF_HUMIDITY]
sens = yield sensor.new_sensor(conf)
sens = await sensor.new_sensor(conf)
cg.add(var.set_humidity_sensor(sens))
cg.add(var.set_humidity_oversampling(conf[CONF_OVERSAMPLING]))

View File

@@ -16,6 +16,7 @@ from esphome.const import (
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_OHM,
ICON_GAS_CYLINDER,
UNIT_CELSIUS,
@@ -58,7 +59,11 @@ CONFIG_SCHEMA = (
{
cv.GenerateID(): cv.declare_id(BME680Component),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
UNIT_CELSIUS,
ICON_EMPTY,
1,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
@@ -67,7 +72,11 @@ CONFIG_SCHEMA = (
}
),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE
UNIT_HECTOPASCAL,
ICON_EMPTY,
1,
DEVICE_CLASS_PRESSURE,
STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
@@ -76,7 +85,11 @@ CONFIG_SCHEMA = (
}
),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY
UNIT_PERCENT,
ICON_EMPTY,
1,
DEVICE_CLASS_HUMIDITY,
STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
@@ -85,7 +98,11 @@ CONFIG_SCHEMA = (
}
),
cv.Optional(CONF_GAS_RESISTANCE): sensor.sensor_schema(
UNIT_OHM, ICON_GAS_CYLINDER, 1, DEVICE_CLASS_EMPTY
UNIT_OHM,
ICON_GAS_CYLINDER,
1,
DEVICE_CLASS_EMPTY,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
IIR_FILTER_OPTIONS, upper=True
@@ -114,32 +131,32 @@ CONFIG_SCHEMA = (
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield i2c.register_i2c_device(var, config)
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config:
conf = config[CONF_TEMPERATURE]
sens = yield sensor.new_sensor(conf)
sens = await sensor.new_sensor(conf)
cg.add(var.set_temperature_sensor(sens))
cg.add(var.set_temperature_oversampling(conf[CONF_OVERSAMPLING]))
if CONF_PRESSURE in config:
conf = config[CONF_PRESSURE]
sens = yield sensor.new_sensor(conf)
sens = await sensor.new_sensor(conf)
cg.add(var.set_pressure_sensor(sens))
cg.add(var.set_pressure_oversampling(conf[CONF_OVERSAMPLING]))
if CONF_HUMIDITY in config:
conf = config[CONF_HUMIDITY]
sens = yield sensor.new_sensor(conf)
sens = await sensor.new_sensor(conf)
cg.add(var.set_humidity_sensor(sens))
cg.add(var.set_humidity_oversampling(conf[CONF_OVERSAMPLING]))
if CONF_GAS_RESISTANCE in config:
conf = config[CONF_GAS_RESISTANCE]
sens = yield sensor.new_sensor(conf)
sens = await sensor.new_sensor(conf)
cg.add(var.set_gas_resistance_sensor(sens))
cg.add(var.set_iir_filter(IIR_FILTER_OPTIONS[config[CONF_IIR_FILTER]]))

View File

@@ -0,0 +1,64 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c
from esphome.const import CONF_ID
CODEOWNERS = ["@trvrnrth"]
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["sensor", "text_sensor"]
CONF_BME680_BSEC_ID = "bme680_bsec_id"
CONF_TEMPERATURE_OFFSET = "temperature_offset"
CONF_IAQ_MODE = "iaq_mode"
CONF_SAMPLE_RATE = "sample_rate"
CONF_STATE_SAVE_INTERVAL = "state_save_interval"
bme680_bsec_ns = cg.esphome_ns.namespace("bme680_bsec")
IAQMode = bme680_bsec_ns.enum("IAQMode")
IAQ_MODE_OPTIONS = {
"STATIC": IAQMode.IAQ_MODE_STATIC,
"MOBILE": IAQMode.IAQ_MODE_MOBILE,
}
SampleRate = bme680_bsec_ns.enum("SampleRate")
SAMPLE_RATE_OPTIONS = {
"LP": SampleRate.SAMPLE_RATE_LP,
"ULP": SampleRate.SAMPLE_RATE_ULP,
}
BME680BSECComponent = bme680_bsec_ns.class_(
"BME680BSECComponent", cg.Component, i2c.I2CDevice
)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(BME680BSECComponent),
cv.Optional(CONF_TEMPERATURE_OFFSET, default=0): cv.temperature,
cv.Optional(CONF_IAQ_MODE, default="STATIC"): cv.enum(
IAQ_MODE_OPTIONS, upper=True
),
cv.Optional(CONF_SAMPLE_RATE, default="LP"): cv.enum(
SAMPLE_RATE_OPTIONS, upper=True
),
cv.Optional(
CONF_STATE_SAVE_INTERVAL, default="6hours"
): cv.positive_time_period_minutes,
}
).extend(i2c.i2c_device_schema(0x76))
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
cg.add(var.set_temperature_offset(config[CONF_TEMPERATURE_OFFSET]))
cg.add(var.set_iaq_mode(config[CONF_IAQ_MODE]))
cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE]))
cg.add(
var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds)
)
cg.add_define("USE_BSEC")
cg.add_library("BSEC Software Library", "1.6.1480")

View File

@@ -0,0 +1,426 @@
#include "bme680_bsec.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include <string>
namespace esphome {
namespace bme680_bsec {
#ifdef USE_BSEC
static const char *TAG = "bme680_bsec.sensor";
static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"};
BME680BSECComponent *BME680BSECComponent::instance;
void BME680BSECComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up BME680 via BSEC...");
BME680BSECComponent::instance = this;
this->bsec_status_ = bsec_init();
if (this->bsec_status_ != BSEC_OK) {
this->mark_failed();
return;
}
this->bme680_.dev_id = this->address_;
this->bme680_.intf = BME680_I2C_INTF;
this->bme680_.read = BME680BSECComponent::read_bytes_wrapper;
this->bme680_.write = BME680BSECComponent::write_bytes_wrapper;
this->bme680_.delay_ms = BME680BSECComponent::delay_ms;
this->bme680_.amb_temp = 25;
this->bme680_status_ = bme680_init(&this->bme680_);
if (this->bme680_status_ != BME680_OK) {
this->mark_failed();
return;
}
if (this->sample_rate_ == SAMPLE_RATE_ULP) {
const uint8_t bsec_config[] = {
#include "config/generic_33v_300s_28d/bsec_iaq.txt"
};
this->set_config_(bsec_config);
} else {
const uint8_t bsec_config[] = {
#include "config/generic_33v_3s_28d/bsec_iaq.txt"
};
this->set_config_(bsec_config);
}
this->update_subscription_();
if (this->bsec_status_ != BSEC_OK) {
this->mark_failed();
return;
}
this->load_state_();
}
void BME680BSECComponent::set_config_(const uint8_t *config) {
uint8_t work_buffer[BSEC_MAX_WORKBUFFER_SIZE];
this->bsec_status_ = bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, work_buffer, sizeof(work_buffer));
}
float BME680BSECComponent::calc_sensor_sample_rate_(SampleRate sample_rate) {
if (sample_rate == SAMPLE_RATE_DEFAULT) {
sample_rate = this->sample_rate_;
}
return sample_rate == SAMPLE_RATE_ULP ? BSEC_SAMPLE_RATE_ULP : BSEC_SAMPLE_RATE_LP;
}
void BME680BSECComponent::update_subscription_() {
bsec_sensor_configuration_t virtual_sensors[BSEC_NUMBER_OUTPUTS];
int num_virtual_sensors = 0;
if (this->iaq_sensor_) {
virtual_sensors[num_virtual_sensors].sensor_id =
this->iaq_mode_ == IAQ_MODE_STATIC ? BSEC_OUTPUT_STATIC_IAQ : BSEC_OUTPUT_IAQ;
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT);
num_virtual_sensors++;
}
if (this->co2_equivalent_sensor_) {
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_CO2_EQUIVALENT;
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT);
num_virtual_sensors++;
}
if (this->breath_voc_equivalent_sensor_) {
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_BREATH_VOC_EQUIVALENT;
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT);
num_virtual_sensors++;
}
if (this->pressure_sensor_) {
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_RAW_PRESSURE;
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(this->pressure_sample_rate_);
num_virtual_sensors++;
}
if (this->gas_resistance_sensor_) {
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_RAW_GAS;
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT);
num_virtual_sensors++;
}
if (this->temperature_sensor_) {
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE;
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(this->temperature_sample_rate_);
num_virtual_sensors++;
}
if (this->humidity_sensor_) {
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY;
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(this->humidity_sample_rate_);
num_virtual_sensors++;
}
bsec_sensor_configuration_t sensor_settings[BSEC_MAX_PHYSICAL_SENSOR];
uint8_t num_sensor_settings = BSEC_MAX_PHYSICAL_SENSOR;
this->bsec_status_ =
bsec_update_subscription(virtual_sensors, num_virtual_sensors, sensor_settings, &num_sensor_settings);
}
void BME680BSECComponent::dump_config() {
ESP_LOGCONFIG(TAG, "BME680 via BSEC:");
bsec_version_t version;
bsec_get_version(&version);
ESP_LOGCONFIG(TAG, " BSEC Version: %d.%d.%d.%d", version.major, version.minor, version.major_bugfix,
version.minor_bugfix);
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication failed (BSEC Status: %d, BME680 Status: %d)", this->bsec_status_,
this->bme680_status_);
}
ESP_LOGCONFIG(TAG, " Temperature Offset: %.2f", this->temperature_offset_);
ESP_LOGCONFIG(TAG, " IAQ Mode: %s", this->iaq_mode_ == IAQ_MODE_STATIC ? "Static" : "Mobile");
ESP_LOGCONFIG(TAG, " Sample Rate: %s", BME680_BSEC_SAMPLE_RATE_LOG(this->sample_rate_));
ESP_LOGCONFIG(TAG, " State Save Interval: %ims", this->state_save_interval_ms_);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
ESP_LOGCONFIG(TAG, " Sample Rate: %s", BME680_BSEC_SAMPLE_RATE_LOG(this->temperature_sample_rate_));
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
ESP_LOGCONFIG(TAG, " Sample Rate: %s", BME680_BSEC_SAMPLE_RATE_LOG(this->pressure_sample_rate_));
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
ESP_LOGCONFIG(TAG, " Sample Rate: %s", BME680_BSEC_SAMPLE_RATE_LOG(this->humidity_sample_rate_));
LOG_SENSOR(" ", "Gas Resistance", this->gas_resistance_sensor_);
LOG_SENSOR(" ", "IAQ", this->iaq_sensor_);
LOG_SENSOR(" ", "Numeric IAQ Accuracy", this->iaq_accuracy_sensor_);
LOG_TEXT_SENSOR(" ", "IAQ Accuracy", this->iaq_accuracy_text_sensor_);
LOG_SENSOR(" ", "CO2 Equivalent", this->co2_equivalent_sensor_);
LOG_SENSOR(" ", "Breath VOC Equivalent", this->breath_voc_equivalent_sensor_);
}
float BME680BSECComponent::get_setup_priority() const { return setup_priority::DATA; }
void BME680BSECComponent::loop() {
this->run_();
if (this->bsec_status_ < BSEC_OK || this->bme680_status_ < BME680_OK) {
this->status_set_error();
} else {
this->status_clear_error();
}
if (this->bsec_status_ > BSEC_OK || this->bme680_status_ > BME680_OK) {
this->status_set_warning();
} else {
this->status_clear_warning();
}
}
void BME680BSECComponent::run_() {
int64_t curr_time_ns = this->get_time_ns_();
if (curr_time_ns < this->next_call_ns_) {
return;
}
ESP_LOGV(TAG, "Performing sensor run");
bsec_bme_settings_t bme680_settings;
this->bsec_status_ = bsec_sensor_control(curr_time_ns, &bme680_settings);
if (this->bsec_status_ < BSEC_OK) {
ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC Error Code %d)", this->bsec_status_);
return;
}
this->next_call_ns_ = bme680_settings.next_call;
if (bme680_settings.trigger_measurement) {
this->bme680_.tph_sett.os_temp = bme680_settings.temperature_oversampling;
this->bme680_.tph_sett.os_pres = bme680_settings.pressure_oversampling;
this->bme680_.tph_sett.os_hum = bme680_settings.humidity_oversampling;
this->bme680_.gas_sett.run_gas = bme680_settings.run_gas;
this->bme680_.gas_sett.heatr_temp = bme680_settings.heater_temperature;
this->bme680_.gas_sett.heatr_dur = bme680_settings.heating_duration;
this->bme680_.power_mode = BME680_FORCED_MODE;
uint16_t desired_settings = BME680_OST_SEL | BME680_OSP_SEL | BME680_OSH_SEL | BME680_GAS_SENSOR_SEL;
this->bme680_status_ = bme680_set_sensor_settings(desired_settings, &this->bme680_);
if (this->bme680_status_ != BME680_OK) {
ESP_LOGW(TAG, "Failed to set sensor settings (BME680 Error Code %d)", this->bme680_status_);
return;
}
this->bme680_status_ = bme680_set_sensor_mode(&this->bme680_);
if (this->bme680_status_ != BME680_OK) {
ESP_LOGW(TAG, "Failed to set sensor mode (BME680 Error Code %d)", this->bme680_status_);
return;
}
uint16_t meas_dur = 0;
bme680_get_profile_dur(&meas_dur, &this->bme680_);
ESP_LOGV(TAG, "Queueing read in %ums", meas_dur);
this->set_timeout("read", meas_dur,
[this, curr_time_ns, bme680_settings]() { this->read_(curr_time_ns, bme680_settings); });
} else {
ESP_LOGV(TAG, "Measurement not required");
this->read_(curr_time_ns, bme680_settings);
}
}
void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme680_settings) {
ESP_LOGV(TAG, "Reading data");
if (bme680_settings.trigger_measurement) {
while (this->bme680_.power_mode != BME680_SLEEP_MODE) {
this->bme680_status_ = bme680_get_sensor_mode(&this->bme680_);
if (this->bme680_status_ != BME680_OK) {
ESP_LOGW(TAG, "Failed to get sensor mode (BME680 Error Code %d)", this->bme680_status_);
}
}
}
if (!bme680_settings.process_data) {
ESP_LOGV(TAG, "Data processing not required");
return;
}
struct bme680_field_data data;
this->bme680_status_ = bme680_get_sensor_data(&data, &this->bme680_);
if (this->bme680_status_ != BME680_OK) {
ESP_LOGW(TAG, "Failed to get sensor data (BME680 Error Code %d)", this->bme680_status_);
return;
}
if (!(data.status & BME680_NEW_DATA_MSK)) {
ESP_LOGD(TAG, "BME680 did not report new data");
return;
}
bsec_input_t inputs[BSEC_MAX_PHYSICAL_SENSOR]; // Temperature, Pressure, Humidity & Gas Resistance
uint8_t num_inputs = 0;
if (bme680_settings.process_data & BSEC_PROCESS_TEMPERATURE) {
inputs[num_inputs].sensor_id = BSEC_INPUT_TEMPERATURE;
inputs[num_inputs].signal = data.temperature / 100.0f;
inputs[num_inputs].time_stamp = trigger_time_ns;
num_inputs++;
// Temperature offset from the real temperature due to external heat sources
inputs[num_inputs].sensor_id = BSEC_INPUT_HEATSOURCE;
inputs[num_inputs].signal = this->temperature_offset_;
inputs[num_inputs].time_stamp = trigger_time_ns;
num_inputs++;
}
if (bme680_settings.process_data & BSEC_PROCESS_HUMIDITY) {
inputs[num_inputs].sensor_id = BSEC_INPUT_HUMIDITY;
inputs[num_inputs].signal = data.humidity / 1000.0f;
inputs[num_inputs].time_stamp = trigger_time_ns;
num_inputs++;
}
if (bme680_settings.process_data & BSEC_PROCESS_PRESSURE) {
inputs[num_inputs].sensor_id = BSEC_INPUT_PRESSURE;
inputs[num_inputs].signal = data.pressure;
inputs[num_inputs].time_stamp = trigger_time_ns;
num_inputs++;
}
if (bme680_settings.process_data & BSEC_PROCESS_GAS) {
if (data.status & BME680_GASM_VALID_MSK) {
inputs[num_inputs].sensor_id = BSEC_INPUT_GASRESISTOR;
inputs[num_inputs].signal = data.gas_resistance;
inputs[num_inputs].time_stamp = trigger_time_ns;
num_inputs++;
} else {
ESP_LOGD(TAG, "BME680 did not report gas data");
}
}
if (num_inputs < 1) {
ESP_LOGD(TAG, "No signal inputs available for BSEC");
return;
}
bsec_output_t outputs[BSEC_NUMBER_OUTPUTS];
uint8_t num_outputs = BSEC_NUMBER_OUTPUTS;
this->bsec_status_ = bsec_do_steps(inputs, num_inputs, outputs, &num_outputs);
if (this->bsec_status_ != BSEC_OK) {
ESP_LOGW(TAG, "BSEC failed to process signals (BSEC Error Code %d)", this->bsec_status_);
return;
}
if (num_outputs < 1) {
ESP_LOGD(TAG, "No signal outputs provided by BSEC");
return;
}
this->publish_(outputs, num_outputs);
}
void BME680BSECComponent::publish_(const bsec_output_t *outputs, uint8_t num_outputs) {
ESP_LOGV(TAG, "Publishing sensor states");
for (uint8_t i = 0; i < num_outputs; i++) {
switch (outputs[i].sensor_id) {
case BSEC_OUTPUT_IAQ:
case BSEC_OUTPUT_STATIC_IAQ:
uint8_t accuracy;
accuracy = outputs[i].accuracy;
this->publish_sensor_state_(this->iaq_sensor_, outputs[i].signal);
this->publish_sensor_state_(this->iaq_accuracy_text_sensor_, IAQ_ACCURACY_STATES[accuracy]);
this->publish_sensor_state_(this->iaq_accuracy_sensor_, accuracy, true);
// Queue up an opportunity to save state
this->defer("save_state", [this, accuracy]() { this->save_state_(accuracy); });
break;
case BSEC_OUTPUT_CO2_EQUIVALENT:
this->publish_sensor_state_(this->co2_equivalent_sensor_, outputs[i].signal);
break;
case BSEC_OUTPUT_BREATH_VOC_EQUIVALENT:
this->publish_sensor_state_(this->breath_voc_equivalent_sensor_, outputs[i].signal);
break;
case BSEC_OUTPUT_RAW_PRESSURE:
this->publish_sensor_state_(this->pressure_sensor_, outputs[i].signal / 100.0f);
break;
case BSEC_OUTPUT_RAW_GAS:
this->publish_sensor_state_(this->gas_resistance_sensor_, outputs[i].signal);
break;
case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE:
this->publish_sensor_state_(this->temperature_sensor_, outputs[i].signal);
break;
case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY:
this->publish_sensor_state_(this->humidity_sensor_, outputs[i].signal);
break;
}
}
}
int64_t BME680BSECComponent::get_time_ns_() {
int64_t time_ms = millis();
if (this->last_time_ms_ > time_ms) {
this->millis_overflow_counter_++;
}
this->last_time_ms_ = time_ms;
return (time_ms + ((int64_t) this->millis_overflow_counter_ << 32)) * INT64_C(1000000);
}
void BME680BSECComponent::publish_sensor_state_(sensor::Sensor *sensor, float value, bool change_only) {
if (!sensor || (change_only && sensor->has_state() && sensor->state == value)) {
return;
}
sensor->publish_state(value);
}
void BME680BSECComponent::publish_sensor_state_(text_sensor::TextSensor *sensor, std::string value) {
if (!sensor || (sensor->has_state() && sensor->state == value)) {
return;
}
sensor->publish_state(value);
}
int8_t BME680BSECComponent::read_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len) {
return BME680BSECComponent::instance->read_bytes(a_register, data, len) ? 0 : -1;
}
int8_t BME680BSECComponent::write_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len) {
return BME680BSECComponent::instance->write_bytes(a_register, data, len) ? 0 : -1;
}
void BME680BSECComponent::delay_ms(uint32_t period) {
ESP_LOGV(TAG, "Delaying for %ums", period);
delay(period);
}
void BME680BSECComponent::load_state_() {
uint32_t hash = fnv1_hash("bme680_bsec_state_" + to_string(this->address_));
this->bsec_state_ = global_preferences.make_preference<uint8_t[BSEC_MAX_STATE_BLOB_SIZE]>(hash, true);
uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
if (this->bsec_state_.load(&state)) {
ESP_LOGV(TAG, "Loading state");
uint8_t work_buffer[BSEC_MAX_WORKBUFFER_SIZE];
this->bsec_status_ = bsec_set_state(state, BSEC_MAX_STATE_BLOB_SIZE, work_buffer, sizeof(work_buffer));
if (this->bsec_status_ != BSEC_OK) {
ESP_LOGW(TAG, "Failed to load state (BSEC Error Code %d)", this->bsec_status_);
}
ESP_LOGI(TAG, "Loaded state");
}
}
void BME680BSECComponent::save_state_(uint8_t accuracy) {
if (accuracy < 3 || (millis() - this->last_state_save_ms_ < this->state_save_interval_ms_)) {
return;
}
ESP_LOGV(TAG, "Saving state");
uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
uint8_t work_buffer[BSEC_MAX_STATE_BLOB_SIZE];
uint32_t num_serialized_state = BSEC_MAX_STATE_BLOB_SIZE;
this->bsec_status_ =
bsec_get_state(0, state, BSEC_MAX_STATE_BLOB_SIZE, work_buffer, BSEC_MAX_STATE_BLOB_SIZE, &num_serialized_state);
if (this->bsec_status_ != BSEC_OK) {
ESP_LOGW(TAG, "Failed fetch state for save (BSEC Error Code %d)", this->bsec_status_);
return;
}
if (!this->bsec_state_.save(&state)) {
ESP_LOGW(TAG, "Failed to save state");
return;
}
this->last_state_save_ms_ = millis();
ESP_LOGI(TAG, "Saved state");
}
#endif
} // namespace bme680_bsec
} // namespace esphome

View File

@@ -0,0 +1,110 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/text_sensor/text_sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/core/preferences.h"
#include <map>
#ifdef USE_BSEC
#include <bsec.h>
#endif
namespace esphome {
namespace bme680_bsec {
#ifdef USE_BSEC
enum IAQMode {
IAQ_MODE_STATIC = 0,
IAQ_MODE_MOBILE = 1,
};
enum SampleRate {
SAMPLE_RATE_LP = 0,
SAMPLE_RATE_ULP = 1,
SAMPLE_RATE_DEFAULT = 2,
};
#define BME680_BSEC_SAMPLE_RATE_LOG(r) (r == SAMPLE_RATE_DEFAULT ? "Default" : (r == SAMPLE_RATE_ULP ? "ULP" : "LP"))
class BME680BSECComponent : public Component, public i2c::I2CDevice {
public:
void set_temperature_offset(float offset) { this->temperature_offset_ = offset; }
void set_iaq_mode(IAQMode iaq_mode) { this->iaq_mode_ = iaq_mode; }
void set_state_save_interval(uint32_t interval) { this->state_save_interval_ms_ = interval; }
void set_sample_rate(SampleRate sample_rate) { this->sample_rate_ = sample_rate; }
void set_temperature_sample_rate(SampleRate sample_rate) { this->temperature_sample_rate_ = sample_rate; }
void set_pressure_sample_rate(SampleRate sample_rate) { this->pressure_sample_rate_ = sample_rate; }
void set_humidity_sample_rate(SampleRate sample_rate) { this->humidity_sample_rate_ = sample_rate; }
void set_temperature_sensor(sensor::Sensor *sensor) { this->temperature_sensor_ = sensor; }
void set_pressure_sensor(sensor::Sensor *sensor) { this->pressure_sensor_ = sensor; }
void set_humidity_sensor(sensor::Sensor *sensor) { this->humidity_sensor_ = sensor; }
void set_gas_resistance_sensor(sensor::Sensor *sensor) { this->gas_resistance_sensor_ = sensor; }
void set_iaq_sensor(sensor::Sensor *sensor) { this->iaq_sensor_ = sensor; }
void set_iaq_accuracy_text_sensor(text_sensor::TextSensor *sensor) { this->iaq_accuracy_text_sensor_ = sensor; }
void set_iaq_accuracy_sensor(sensor::Sensor *sensor) { this->iaq_accuracy_sensor_ = sensor; }
void set_co2_equivalent_sensor(sensor::Sensor *sensor) { this->co2_equivalent_sensor_ = sensor; }
void set_breath_voc_equivalent_sensor(sensor::Sensor *sensor) { this->breath_voc_equivalent_sensor_ = sensor; }
static BME680BSECComponent *instance;
static int8_t read_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len);
static int8_t write_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len);
static void delay_ms(uint32_t period);
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void loop() override;
protected:
void set_config_(const uint8_t *config);
float calc_sensor_sample_rate_(SampleRate sample_rate);
void update_subscription_();
void run_();
void read_(int64_t trigger_time_ns, bsec_bme_settings_t bme680_settings);
void publish_(const bsec_output_t *outputs, uint8_t num_outputs);
int64_t get_time_ns_();
void publish_sensor_state_(sensor::Sensor *sensor, float value, bool change_only = false);
void publish_sensor_state_(text_sensor::TextSensor *sensor, std::string value);
void load_state_();
void save_state_(uint8_t accuracy);
struct bme680_dev bme680_;
bsec_library_return_t bsec_status_{BSEC_OK};
int8_t bme680_status_{BME680_OK};
int64_t last_time_ms_{0};
uint32_t millis_overflow_counter_{0};
int64_t next_call_ns_{0};
ESPPreferenceObject bsec_state_;
uint32_t state_save_interval_ms_{21600000}; // 6 hours - 4 times a day
uint32_t last_state_save_ms_ = 0;
float temperature_offset_{0};
IAQMode iaq_mode_{IAQ_MODE_STATIC};
SampleRate sample_rate_{SAMPLE_RATE_LP}; // Core/gas sample rate
SampleRate temperature_sample_rate_{SAMPLE_RATE_DEFAULT};
SampleRate pressure_sample_rate_{SAMPLE_RATE_DEFAULT};
SampleRate humidity_sample_rate_{SAMPLE_RATE_DEFAULT};
sensor::Sensor *temperature_sensor_;
sensor::Sensor *pressure_sensor_;
sensor::Sensor *humidity_sensor_;
sensor::Sensor *gas_resistance_sensor_;
sensor::Sensor *iaq_sensor_;
text_sensor::TextSensor *iaq_accuracy_text_sensor_;
sensor::Sensor *iaq_accuracy_sensor_;
sensor::Sensor *co2_equivalent_sensor_;
sensor::Sensor *breath_voc_equivalent_sensor_;
};
#endif
} // namespace bme680_bsec
} // namespace esphome

View File

@@ -0,0 +1,122 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import (
CONF_GAS_RESISTANCE,
CONF_HUMIDITY,
CONF_PRESSURE,
CONF_TEMPERATURE,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_EMPTY,
UNIT_HECTOPASCAL,
UNIT_OHM,
UNIT_PARTS_PER_MILLION,
UNIT_PERCENT,
ICON_GAS_CYLINDER,
ICON_GAUGE,
ICON_THERMOMETER,
ICON_WATER_PERCENT,
)
from . import (
BME680BSECComponent,
CONF_BME680_BSEC_ID,
CONF_SAMPLE_RATE,
SAMPLE_RATE_OPTIONS,
)
DEPENDENCIES = ["bme680_bsec"]
CONF_IAQ = "iaq"
CONF_IAQ_ACCURACY = "iaq_accuracy"
CONF_CO2_EQUIVALENT = "co2_equivalent"
CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent"
UNIT_IAQ = "IAQ"
ICON_ACCURACY = "mdi:checkbox-marked-circle-outline"
ICON_TEST_TUBE = "mdi:test-tube"
TYPES = [
CONF_TEMPERATURE,
CONF_PRESSURE,
CONF_HUMIDITY,
CONF_GAS_RESISTANCE,
CONF_IAQ,
CONF_IAQ_ACCURACY,
CONF_CO2_EQUIVALENT,
CONF_BREATH_VOC_EQUIVALENT,
]
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_BME680_BSEC_ID): cv.use_id(BME680BSECComponent),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
UNIT_CELSIUS,
ICON_THERMOMETER,
1,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
).extend(
{cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)}
),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
UNIT_HECTOPASCAL,
ICON_GAUGE,
1,
DEVICE_CLASS_PRESSURE,
STATE_CLASS_MEASUREMENT,
).extend(
{cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)}
),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
UNIT_PERCENT,
ICON_WATER_PERCENT,
1,
DEVICE_CLASS_HUMIDITY,
STATE_CLASS_MEASUREMENT,
).extend(
{cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)}
),
cv.Optional(CONF_GAS_RESISTANCE): sensor.sensor_schema(
UNIT_OHM, ICON_GAS_CYLINDER, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT
),
cv.Optional(CONF_IAQ): sensor.sensor_schema(
UNIT_IAQ, ICON_GAUGE, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT
),
cv.Optional(CONF_IAQ_ACCURACY): sensor.sensor_schema(
UNIT_EMPTY, ICON_ACCURACY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT
),
cv.Optional(CONF_CO2_EQUIVALENT): sensor.sensor_schema(
UNIT_PARTS_PER_MILLION,
ICON_TEST_TUBE,
1,
DEVICE_CLASS_EMPTY,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_BREATH_VOC_EQUIVALENT): sensor.sensor_schema(
UNIT_PARTS_PER_MILLION,
ICON_TEST_TUBE,
1,
DEVICE_CLASS_EMPTY,
STATE_CLASS_MEASUREMENT,
),
}
)
async def setup_conf(config, key, hub):
if key in config:
conf = config[key]
sens = await sensor.new_sensor(conf)
cg.add(getattr(hub, f"set_{key}_sensor")(sens))
if CONF_SAMPLE_RATE in conf:
cg.add(getattr(hub, f"set_{key}_sample_rate")(conf[CONF_SAMPLE_RATE]))
async def to_code(config):
hub = await cg.get_variable(config[CONF_BME680_BSEC_ID])
for key in TYPES:
await setup_conf(config, key, hub)

View File

@@ -0,0 +1,38 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor
from esphome.const import CONF_ID, CONF_ICON
from . import BME680BSECComponent, CONF_BME680_BSEC_ID
DEPENDENCIES = ["bme680_bsec"]
CONF_IAQ_ACCURACY = "iaq_accuracy"
ICON_ACCURACY = "mdi:checkbox-marked-circle-outline"
TYPES = [CONF_IAQ_ACCURACY]
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_BME680_BSEC_ID): cv.use_id(BME680BSECComponent),
cv.Optional(CONF_IAQ_ACCURACY): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
cv.Optional(CONF_ICON, default=ICON_ACCURACY): cv.icon,
}
),
}
)
async def setup_conf(config, key, hub):
if key in config:
conf = config[key]
sens = cg.new_Pvariable(conf[CONF_ID])
await text_sensor.register_text_sensor(sens, conf)
cg.add(getattr(hub, f"set_{key}_text_sensor")(sens))
async def to_code(config):
hub = await cg.get_variable(config[CONF_BME680_BSEC_ID])
for key in TYPES:
await setup_conf(config, key, hub)

View File

@@ -7,6 +7,7 @@ from esphome.const import (
CONF_TEMPERATURE,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
ICON_EMPTY,
UNIT_HECTOPASCAL,
@@ -24,10 +25,18 @@ CONFIG_SCHEMA = (
{
cv.GenerateID(): cv.declare_id(BMP085Component),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
UNIT_CELSIUS,
ICON_EMPTY,
1,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE
UNIT_HECTOPASCAL,
ICON_EMPTY,
1,
DEVICE_CLASS_PRESSURE,
STATE_CLASS_MEASUREMENT,
),
}
)
@@ -36,17 +45,17 @@ CONFIG_SCHEMA = (
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield i2c.register_i2c_device(var, config)
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config:
conf = config[CONF_TEMPERATURE]
sens = yield sensor.new_sensor(conf)
sens = await sensor.new_sensor(conf)
cg.add(var.set_temperature(sens))
if CONF_PRESSURE in config:
conf = config[CONF_PRESSURE]
sens = yield sensor.new_sensor(conf)
sens = await sensor.new_sensor(conf)
cg.add(var.set_pressure(sens))

View File

@@ -7,6 +7,7 @@ from esphome.const import (
CONF_TEMPERATURE,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
ICON_EMPTY,
UNIT_HECTOPASCAL,
@@ -45,7 +46,11 @@ CONFIG_SCHEMA = (
{
cv.GenerateID(): cv.declare_id(BMP280Component),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
UNIT_CELSIUS,
ICON_EMPTY,
1,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
@@ -54,7 +59,11 @@ CONFIG_SCHEMA = (
}
),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE
UNIT_HECTOPASCAL,
ICON_EMPTY,
1,
DEVICE_CLASS_PRESSURE,
STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
@@ -72,19 +81,19 @@ CONFIG_SCHEMA = (
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield i2c.register_i2c_device(var, config)
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config:
conf = config[CONF_TEMPERATURE]
sens = yield sensor.new_sensor(conf)
sens = await sensor.new_sensor(conf)
cg.add(var.set_temperature_sensor(sens))
cg.add(var.set_temperature_oversampling(conf[CONF_OVERSAMPLING]))
if CONF_PRESSURE in config:
conf = config[CONF_PRESSURE]
sens = yield sensor.new_sensor(conf)
sens = await sensor.new_sensor(conf)
cg.add(var.set_pressure_sensor(sens))
cg.add(var.set_pressure_oversampling(conf[CONF_OVERSAMPLING]))

View File

@@ -1,7 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.core import CORE, coroutine
from esphome.core import CORE
from esphome.const import CONF_ID, CONF_TRIGGER_ID, CONF_DATA
CODEOWNERS = ["@mvturnho", "@danielschramm"]
@@ -82,10 +82,9 @@ CANBUS_SCHEMA = cv.Schema(
).extend(cv.COMPONENT_SCHEMA)
@coroutine
def setup_canbus_core_(var, config):
async def setup_canbus_core_(var, config):
validate_id(config[CONF_CAN_ID], config[CONF_USE_EXTENDED_ID])
yield cg.register_component(var, config)
await cg.register_component(var, config)
cg.add(var.set_can_id([config[CONF_CAN_ID]]))
cg.add(var.set_use_extended_id([config[CONF_USE_EXTENDED_ID]]))
cg.add(var.set_bitrate(CAN_SPEEDS[config[CONF_BIT_RATE]]))
@@ -95,17 +94,16 @@ def setup_canbus_core_(var, config):
ext_id = conf[CONF_USE_EXTENDED_ID]
validate_id(can_id, ext_id)
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, can_id, ext_id)
yield cg.register_component(trigger, conf)
yield automation.build_automation(
await cg.register_component(trigger, conf)
await automation.build_automation(
trigger, [(cg.std_vector.template(cg.uint8), "x")], conf
)
@coroutine
def register_canbus(var, config):
async def register_canbus(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.new_Pvariable(config[CONF_ID], var)
yield setup_canbus_core_(var, config)
await setup_canbus_core_(var, config)
# Actions
@@ -122,16 +120,16 @@ def register_canbus(var, config):
key=CONF_DATA,
),
)
def canbus_action_to_code(config, action_id, template_arg, args):
async def canbus_action_to_code(config, action_id, template_arg, args):
validate_id(config[CONF_CAN_ID], config[CONF_USE_EXTENDED_ID])
var = cg.new_Pvariable(action_id, template_arg)
yield cg.register_parented(var, config[CONF_CANBUS_ID])
await cg.register_parented(var, config[CONF_CANBUS_ID])
if CONF_CAN_ID in config:
can_id = yield cg.templatable(config[CONF_CAN_ID], args, cg.uint32)
can_id = await cg.templatable(config[CONF_CAN_ID], args, cg.uint32)
cg.add(var.set_can_id(can_id))
use_extended_id = yield cg.templatable(
use_extended_id = await cg.templatable(
config[CONF_USE_EXTENDED_ID], args, cg.uint32
)
cg.add(var.set_use_extended_id(use_extended_id))
@@ -140,8 +138,8 @@ def canbus_action_to_code(config, action_id, template_arg, args):
if isinstance(data, bytes):
data = [int(x) for x in data]
if cg.is_template(data):
templ = yield cg.templatable(data, args, cg.std_vector.template(cg.uint8))
templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8))
cg.add(var.set_data_template(templ))
else:
cg.add(var.set_data_static(data))
yield var
return var

View File

@@ -23,9 +23,9 @@ CONFIG_SCHEMA = cv.Schema(
@coroutine_with_priority(64.0)
def to_code(config):
paren = yield cg.get_variable(config[CONF_WEB_SERVER_BASE_ID])
async def to_code(config):
paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID])
var = cg.new_Pvariable(config[CONF_ID], paren)
yield cg.register_component(var, config)
await cg.register_component(var, config)
cg.add_define("USE_CAPTIVE_PORTAL")

View File

@@ -64,32 +64,11 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) {
ESP_LOGI(TAG, "Captive Portal Requested WiFi Settings Change:");
ESP_LOGI(TAG, " SSID='%s'", ssid.c_str());
ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str());
this->override_sta_(ssid, psk);
wifi::global_wifi_component->save_wifi_sta(ssid, psk);
request->redirect("/?save=true");
}
void CaptivePortal::override_sta_(const std::string &ssid, const std::string &password) {
CaptivePortalSettings save{};
strcpy(save.ssid, ssid.c_str());
strcpy(save.password, password.c_str());
this->pref_.save(&save);
wifi::WiFiAP sta{};
sta.set_ssid(ssid);
sta.set_password(password);
wifi::global_wifi_component->set_sta(sta);
}
void CaptivePortal::setup() {
// Hash with compilation time
// This ensures the AP override is not applied for OTA
uint32_t hash = fnv1_hash(App.get_compilation_time());
this->pref_ = global_preferences.make_preference<CaptivePortalSettings>(hash, true);
CaptivePortalSettings save{};
if (this->pref_.load(&save)) {
this->override_sta_(save.ssid, save.password);
}
}
void CaptivePortal::setup() {}
void CaptivePortal::start() {
this->base_->init();
if (!this->initialized_) {

View File

@@ -10,11 +10,6 @@ namespace esphome {
namespace captive_portal {
struct CaptivePortalSettings {
char ssid[33];
char password[65];
} PACKED; // NOLINT
class CaptivePortal : public AsyncWebHandler, public Component {
public:
CaptivePortal(web_server_base::WebServerBase *base);
@@ -67,12 +62,9 @@ class CaptivePortal : public AsyncWebHandler, public Component {
void handleRequest(AsyncWebServerRequest *req) override;
protected:
void override_sta_(const std::string &ssid, const std::string &password);
web_server_base::WebServerBase *base_;
bool initialized_{false};
bool active_{false};
ESPPreferenceObject pref_;
DNSServer *dns_server_{nullptr};
};

View File

@@ -5,6 +5,7 @@ from esphome.const import (
CONF_ID,
DEVICE_CLASS_EMPTY,
ICON_RADIATOR,
STATE_CLASS_MEASUREMENT,
UNIT_PARTS_PER_MILLION,
UNIT_PARTS_PER_BILLION,
CONF_TEMPERATURE,
@@ -28,10 +29,18 @@ CONFIG_SCHEMA = (
{
cv.GenerateID(): cv.declare_id(CCS811Component),
cv.Required(CONF_ECO2): sensor.sensor_schema(
UNIT_PARTS_PER_MILLION, ICON_MOLECULE_CO2, 0, DEVICE_CLASS_EMPTY
UNIT_PARTS_PER_MILLION,
ICON_MOLECULE_CO2,
0,
DEVICE_CLASS_EMPTY,
STATE_CLASS_MEASUREMENT,
),
cv.Required(CONF_TVOC): sensor.sensor_schema(
UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0, DEVICE_CLASS_EMPTY
UNIT_PARTS_PER_BILLION,
ICON_RADIATOR,
0,
DEVICE_CLASS_EMPTY,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_BASELINE): cv.hex_uint16_t,
cv.Optional(CONF_TEMPERATURE): cv.use_id(sensor.Sensor),
@@ -43,22 +52,22 @@ CONFIG_SCHEMA = (
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield i2c.register_i2c_device(var, config)
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
sens = yield sensor.new_sensor(config[CONF_ECO2])
sens = await sensor.new_sensor(config[CONF_ECO2])
cg.add(var.set_co2(sens))
sens = yield sensor.new_sensor(config[CONF_TVOC])
sens = await sensor.new_sensor(config[CONF_TVOC])
cg.add(var.set_tvoc(sens))
if CONF_BASELINE in config:
cg.add(var.set_baseline(config[CONF_BASELINE]))
if CONF_TEMPERATURE in config:
sens = yield cg.get_variable(config[CONF_TEMPERATURE])
sens = await cg.get_variable(config[CONF_TEMPERATURE])
cg.add(var.set_temperature(sens))
if CONF_HUMIDITY in config:
sens = yield cg.get_variable(config[CONF_HUMIDITY])
sens = await cg.get_variable(config[CONF_HUMIDITY])
cg.add(var.set_humidity(sens))

View File

@@ -4,11 +4,14 @@ from esphome import automation
from esphome.components import mqtt
from esphome.const import (
CONF_AWAY,
CONF_CUSTOM_FAN_MODE,
CONF_CUSTOM_PRESET,
CONF_ID,
CONF_INTERNAL,
CONF_MAX_TEMPERATURE,
CONF_MIN_TEMPERATURE,
CONF_MODE,
CONF_PRESET,
CONF_TARGET_TEMPERATURE,
CONF_TARGET_TEMPERATURE_HIGH,
CONF_TARGET_TEMPERATURE_LOW,
@@ -19,7 +22,7 @@ from esphome.const import (
CONF_FAN_MODE,
CONF_SWING_MODE,
)
from esphome.core import CORE, coroutine, coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
IS_PLATFORM_COMPONENT = True
@@ -33,11 +36,12 @@ ClimateTraits = climate_ns.class_("ClimateTraits")
ClimateMode = climate_ns.enum("ClimateMode")
CLIMATE_MODES = {
"OFF": ClimateMode.CLIMATE_MODE_OFF,
"AUTO": ClimateMode.CLIMATE_MODE_AUTO,
"HEAT_COOL": ClimateMode.CLIMATE_HEAT_COOL,
"COOL": ClimateMode.CLIMATE_MODE_COOL,
"HEAT": ClimateMode.CLIMATE_MODE_HEAT,
"DRY": ClimateMode.CLIMATE_MODE_DRY,
"FAN_ONLY": ClimateMode.CLIMATE_MODE_FAN_ONLY,
"AUTO": ClimateMode.CLIMATE_MODE_AUTO,
}
validate_climate_mode = cv.enum(CLIMATE_MODES, upper=True)
@@ -56,6 +60,19 @@ CLIMATE_FAN_MODES = {
validate_climate_fan_mode = cv.enum(CLIMATE_FAN_MODES, upper=True)
ClimatePreset = climate_ns.enum("ClimatePreset")
CLIMATE_PRESETS = {
"ECO": ClimatePreset.CLIMATE_PRESET_ECO,
"AWAY": ClimatePreset.CLIMATE_PRESET_AWAY,
"BOOST": ClimatePreset.CLIMATE_PRESET_BOOST,
"COMFORT": ClimatePreset.CLIMATE_PRESET_COMFORT,
"HOME": ClimatePreset.CLIMATE_PRESET_HOME,
"SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP,
"ACTIVITY": ClimatePreset.CLIMATE_PRESET_ACTIVITY,
}
validate_climate_preset = cv.enum(CLIMATE_PRESETS, upper=True)
ClimateSwingMode = climate_ns.enum("ClimateSwingMode")
CLIMATE_SWING_MODES = {
"OFF": ClimateSwingMode.CLIMATE_SWING_OFF,
@@ -85,8 +102,7 @@ CLIMATE_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend(
)
@coroutine
def setup_climate_core_(var, config):
async def setup_climate_core_(var, config):
cg.add(var.set_name(config[CONF_NAME]))
if CONF_INTERNAL in config:
cg.add(var.set_internal(config[CONF_INTERNAL]))
@@ -100,15 +116,14 @@ def setup_climate_core_(var, config):
if CONF_MQTT_ID in config:
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
yield mqtt.register_mqtt_component(mqtt_, config)
await mqtt.register_mqtt_component(mqtt_, config)
@coroutine
def register_climate(var, config):
async def register_climate(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_climate(var))
yield setup_climate_core_(var, config)
await setup_climate_core_(var, config)
CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema(
@@ -119,7 +134,12 @@ CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema(
cv.Optional(CONF_TARGET_TEMPERATURE_LOW): cv.templatable(cv.temperature),
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH): cv.templatable(cv.temperature),
cv.Optional(CONF_AWAY): cv.templatable(cv.boolean),
cv.Optional(CONF_FAN_MODE): cv.templatable(validate_climate_fan_mode),
cv.Exclusive(CONF_FAN_MODE, "fan_mode"): cv.templatable(
validate_climate_fan_mode
),
cv.Exclusive(CONF_CUSTOM_FAN_MODE, "fan_mode"): cv.string_strict,
cv.Exclusive(CONF_PRESET, "preset"): cv.templatable(validate_climate_preset),
cv.Exclusive(CONF_CUSTOM_PRESET, "preset"): cv.string_strict,
cv.Optional(CONF_SWING_MODE): cv.templatable(validate_climate_swing_mode),
}
)
@@ -128,40 +148,49 @@ CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema(
@automation.register_action(
"climate.control", ControlAction, CLIMATE_CONTROL_ACTION_SCHEMA
)
def climate_control_to_code(config, action_id, template_arg, args):
paren = yield cg.get_variable(config[CONF_ID])
async def climate_control_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)
if CONF_MODE in config:
template_ = yield cg.templatable(config[CONF_MODE], args, ClimateMode)
template_ = await cg.templatable(config[CONF_MODE], args, ClimateMode)
cg.add(var.set_mode(template_))
if CONF_TARGET_TEMPERATURE in config:
template_ = yield cg.templatable(config[CONF_TARGET_TEMPERATURE], args, float)
template_ = await cg.templatable(config[CONF_TARGET_TEMPERATURE], args, float)
cg.add(var.set_target_temperature(template_))
if CONF_TARGET_TEMPERATURE_LOW in config:
template_ = yield cg.templatable(
template_ = await cg.templatable(
config[CONF_TARGET_TEMPERATURE_LOW], args, float
)
cg.add(var.set_target_temperature_low(template_))
if CONF_TARGET_TEMPERATURE_HIGH in config:
template_ = yield cg.templatable(
template_ = await cg.templatable(
config[CONF_TARGET_TEMPERATURE_HIGH], args, float
)
cg.add(var.set_target_temperature_high(template_))
if CONF_AWAY in config:
template_ = yield cg.templatable(config[CONF_AWAY], args, bool)
template_ = await cg.templatable(config[CONF_AWAY], args, bool)
cg.add(var.set_away(template_))
if CONF_FAN_MODE in config:
template_ = yield cg.templatable(config[CONF_FAN_MODE], args, ClimateFanMode)
template_ = await cg.templatable(config[CONF_FAN_MODE], args, ClimateFanMode)
cg.add(var.set_fan_mode(template_))
if CONF_CUSTOM_FAN_MODE in config:
template_ = await cg.templatable(config[CONF_CUSTOM_FAN_MODE], args, str)
cg.add(var.set_custom_fan_mode(template_))
if CONF_PRESET in config:
template_ = await cg.templatable(config[CONF_PRESET], args, ClimatePreset)
cg.add(var.set_preset(template_))
if CONF_CUSTOM_PRESET in config:
template_ = await cg.templatable(config[CONF_CUSTOM_PRESET], args, str)
cg.add(var.set_custom_preset(template_))
if CONF_SWING_MODE in config:
template_ = yield cg.templatable(
template_ = await cg.templatable(
config[CONF_SWING_MODE], args, ClimateSwingMode
)
cg.add(var.set_swing_mode(template_))
yield var
return var
@coroutine_with_priority(100.0)
def to_code(config):
async def to_code(config):
cg.add_define("USE_CLIMATE")
cg.add_global(climate_ns.using)

View File

@@ -16,6 +16,9 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
TEMPLATABLE_VALUE(float, target_temperature_high)
TEMPLATABLE_VALUE(bool, away)
TEMPLATABLE_VALUE(ClimateFanMode, fan_mode)
TEMPLATABLE_VALUE(std::string, custom_fan_mode)
TEMPLATABLE_VALUE(ClimatePreset, preset)
TEMPLATABLE_VALUE(std::string, custom_preset)
TEMPLATABLE_VALUE(ClimateSwingMode, swing_mode)
void play(Ts... x) override {
@@ -26,6 +29,9 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
call.set_target_temperature_high(this->target_temperature_high_.optional_value(x...));
call.set_away(this->away_.optional_value(x...));
call.set_fan_mode(this->fan_mode_.optional_value(x...));
call.set_fan_mode(this->custom_fan_mode_.optional_value(x...));
call.set_preset(this->preset_.optional_value(x...));
call.set_preset(this->custom_preset_.optional_value(x...));
call.set_swing_mode(this->swing_mode_.optional_value(x...));
call.perform();
}

View File

@@ -1,5 +1,4 @@
#include "climate.h"
#include "esphome/core/log.h"
namespace esphome {
namespace climate {
@@ -13,10 +12,24 @@ void ClimateCall::perform() {
const char *mode_s = climate_mode_to_string(*this->mode_);
ESP_LOGD(TAG, " Mode: %s", mode_s);
}
if (this->custom_fan_mode_.has_value()) {
this->fan_mode_.reset();
ESP_LOGD(TAG, " Custom Fan: %s", this->custom_fan_mode_.value().c_str());
}
if (this->fan_mode_.has_value()) {
this->custom_fan_mode_.reset();
const char *fan_mode_s = climate_fan_mode_to_string(*this->fan_mode_);
ESP_LOGD(TAG, " Fan: %s", fan_mode_s);
}
if (this->custom_preset_.has_value()) {
this->preset_.reset();
ESP_LOGD(TAG, " Custom Preset: %s", this->custom_preset_.value().c_str());
}
if (this->preset_.has_value()) {
this->custom_preset_.reset();
const char *preset_s = climate_preset_to_string(*this->preset_);
ESP_LOGD(TAG, " Preset: %s", preset_s);
}
if (this->swing_mode_.has_value()) {
const char *swing_mode_s = climate_swing_mode_to_string(*this->swing_mode_);
ESP_LOGD(TAG, " Swing: %s", swing_mode_s);
@@ -44,13 +57,32 @@ void ClimateCall::validate_() {
this->mode_.reset();
}
}
if (this->fan_mode_.has_value()) {
if (this->custom_fan_mode_.has_value()) {
auto custom_fan_mode = *this->custom_fan_mode_;
if (!traits.supports_custom_fan_mode(custom_fan_mode)) {
ESP_LOGW(TAG, " Fan Mode %s is not supported by this device!", custom_fan_mode.c_str());
this->custom_fan_mode_.reset();
}
} else if (this->fan_mode_.has_value()) {
auto fan_mode = *this->fan_mode_;
if (!traits.supports_fan_mode(fan_mode)) {
ESP_LOGW(TAG, " Fan Mode %s is not supported by this device!", climate_fan_mode_to_string(fan_mode));
this->fan_mode_.reset();
}
}
if (this->custom_preset_.has_value()) {
auto custom_preset = *this->custom_preset_;
if (!traits.supports_custom_preset(custom_preset)) {
ESP_LOGW(TAG, " Preset %s is not supported by this device!", custom_preset.c_str());
this->custom_preset_.reset();
}
} else if (this->preset_.has_value()) {
auto preset = *this->preset_;
if (!traits.supports_preset(preset)) {
ESP_LOGW(TAG, " Preset %s is not supported by this device!", climate_preset_to_string(preset));
this->preset_.reset();
}
}
if (this->swing_mode_.has_value()) {
auto swing_mode = *this->swing_mode_;
if (!traits.supports_swing_mode(swing_mode)) {
@@ -117,6 +149,8 @@ ClimateCall &ClimateCall::set_mode(const std::string &mode) {
this->set_mode(CLIMATE_MODE_FAN_ONLY);
} else if (str_equals_case_insensitive(mode, "DRY")) {
this->set_mode(CLIMATE_MODE_DRY);
} else if (str_equals_case_insensitive(mode, "HEAT_COOL")) {
this->set_mode(CLIMATE_MODE_HEAT_COOL);
} else {
ESP_LOGW(TAG, "'%s' - Unrecognized mode %s", this->parent_->get_name().c_str(), mode.c_str());
}
@@ -124,6 +158,7 @@ ClimateCall &ClimateCall::set_mode(const std::string &mode) {
}
ClimateCall &ClimateCall::set_fan_mode(ClimateFanMode fan_mode) {
this->fan_mode_ = fan_mode;
this->custom_fan_mode_.reset();
return *this;
}
ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) {
@@ -146,11 +181,59 @@ ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) {
} else if (str_equals_case_insensitive(fan_mode, "DIFFUSE")) {
this->set_fan_mode(CLIMATE_FAN_DIFFUSE);
} else {
ESP_LOGW(TAG, "'%s' - Unrecognized fan mode %s", this->parent_->get_name().c_str(), fan_mode.c_str());
auto custom_fan_modes = this->parent_->get_traits().get_supported_custom_fan_modes();
if (std::find(custom_fan_modes.begin(), custom_fan_modes.end(), fan_mode) != custom_fan_modes.end()) {
this->custom_fan_mode_ = fan_mode;
this->fan_mode_.reset();
} else {
ESP_LOGW(TAG, "'%s' - Unrecognized fan mode %s", this->parent_->get_name().c_str(), fan_mode.c_str());
}
}
return *this;
}
ClimateCall &ClimateCall::set_fan_mode(optional<std::string> fan_mode) {
if (fan_mode.has_value()) {
this->set_fan_mode(fan_mode.value());
}
return *this;
}
ClimateCall &ClimateCall::set_preset(ClimatePreset preset) {
this->preset_ = preset;
this->custom_preset_.reset();
return *this;
}
ClimateCall &ClimateCall::set_preset(const std::string &preset) {
if (str_equals_case_insensitive(preset, "ECO")) {
this->set_preset(CLIMATE_PRESET_ECO);
} else if (str_equals_case_insensitive(preset, "AWAY")) {
this->set_preset(CLIMATE_PRESET_AWAY);
} else if (str_equals_case_insensitive(preset, "BOOST")) {
this->set_preset(CLIMATE_PRESET_BOOST);
} else if (str_equals_case_insensitive(preset, "COMFORT")) {
this->set_preset(CLIMATE_PRESET_COMFORT);
} else if (str_equals_case_insensitive(preset, "HOME")) {
this->set_preset(CLIMATE_PRESET_HOME);
} else if (str_equals_case_insensitive(preset, "SLEEP")) {
this->set_preset(CLIMATE_PRESET_SLEEP);
} else if (str_equals_case_insensitive(preset, "ACTIVITY")) {
this->set_preset(CLIMATE_PRESET_ACTIVITY);
} else {
auto custom_presets = this->parent_->get_traits().get_supported_custom_presets();
if (std::find(custom_presets.begin(), custom_presets.end(), preset) != custom_presets.end()) {
this->custom_preset_ = preset;
this->preset_.reset();
} else {
ESP_LOGW(TAG, "'%s' - Unrecognized preset %s", this->parent_->get_name().c_str(), preset.c_str());
}
}
return *this;
}
ClimateCall &ClimateCall::set_preset(optional<std::string> preset) {
if (preset.has_value()) {
this->set_preset(preset.value());
}
return *this;
}
ClimateCall &ClimateCall::set_swing_mode(ClimateSwingMode swing_mode) {
this->swing_mode_ = swing_mode;
return *this;
@@ -188,6 +271,9 @@ const optional<float> &ClimateCall::get_target_temperature_low() const { return
const optional<float> &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; }
const optional<bool> &ClimateCall::get_away() const { return this->away_; }
const optional<ClimateFanMode> &ClimateCall::get_fan_mode() const { return this->fan_mode_; }
const optional<std::string> &ClimateCall::get_custom_fan_mode() const { return this->custom_fan_mode_; }
const optional<ClimatePreset> &ClimateCall::get_preset() const { return this->preset_; }
const optional<std::string> &ClimateCall::get_custom_preset() const { return this->custom_preset_; }
const optional<ClimateSwingMode> &ClimateCall::get_swing_mode() const { return this->swing_mode_; }
ClimateCall &ClimateCall::set_away(bool away) {
this->away_ = away;
@@ -215,6 +301,12 @@ ClimateCall &ClimateCall::set_mode(optional<ClimateMode> mode) {
}
ClimateCall &ClimateCall::set_fan_mode(optional<ClimateFanMode> fan_mode) {
this->fan_mode_ = fan_mode;
this->custom_fan_mode_.reset();
return *this;
}
ClimateCall &ClimateCall::set_preset(optional<ClimatePreset> preset) {
this->preset_ = preset;
this->custom_preset_.reset();
return *this;
}
ClimateCall &ClimateCall::set_swing_mode(optional<ClimateSwingMode> swing_mode) {
@@ -249,8 +341,31 @@ void Climate::save_state_() {
if (traits.get_supports_away()) {
state.away = this->away;
}
if (traits.get_supports_fan_modes()) {
state.fan_mode = this->fan_mode;
if (traits.get_supports_fan_modes() && fan_mode.has_value()) {
state.uses_custom_fan_mode = false;
state.fan_mode = this->fan_mode.value();
}
if (!traits.get_supported_custom_fan_modes().empty() && custom_fan_mode.has_value()) {
state.uses_custom_fan_mode = true;
auto &custom_fan_modes = traits.get_supported_custom_fan_modes();
auto it = std::find(custom_fan_modes.begin(), custom_fan_modes.end(), this->custom_fan_mode.value());
// only set custom fan mode if value exists, otherwise leave it as is
if (it != custom_fan_modes.cend()) {
state.custom_fan_mode = std::distance(custom_fan_modes.begin(), it);
}
}
if (traits.get_supports_presets() && preset.has_value()) {
state.uses_custom_preset = false;
state.preset = this->preset.value();
}
if (!traits.get_supported_custom_presets().empty() && custom_preset.has_value()) {
state.uses_custom_preset = true;
auto custom_presets = traits.get_supported_custom_presets();
auto it = std::find(custom_presets.begin(), custom_presets.end(), this->custom_preset.value());
// only set custom preset if value exists, otherwise leave it as is
if (it != custom_presets.cend()) {
state.custom_preset = std::distance(custom_presets.begin(), it);
}
}
if (traits.get_supports_swing_modes()) {
state.swing_mode = this->swing_mode;
@@ -266,8 +381,17 @@ void Climate::publish_state() {
if (traits.get_supports_action()) {
ESP_LOGD(TAG, " Action: %s", climate_action_to_string(this->action));
}
if (traits.get_supports_fan_modes()) {
ESP_LOGD(TAG, " Fan Mode: %s", climate_fan_mode_to_string(this->fan_mode));
if (traits.get_supports_fan_modes() && this->fan_mode.has_value()) {
ESP_LOGD(TAG, " Fan Mode: %s", climate_fan_mode_to_string(this->fan_mode.value()));
}
if (!traits.get_supported_custom_fan_modes().empty() && this->custom_fan_mode.has_value()) {
ESP_LOGD(TAG, " Custom Fan Mode: %s", this->custom_fan_mode.value().c_str());
}
if (traits.get_supports_presets() && this->preset.has_value()) {
ESP_LOGD(TAG, " Preset: %s", climate_preset_to_string(this->preset.value()));
}
if (!traits.get_supported_custom_presets().empty() && this->custom_preset.has_value()) {
ESP_LOGD(TAG, " Custom Preset: %s", this->custom_preset.value().c_str());
}
if (traits.get_supports_swing_modes()) {
ESP_LOGD(TAG, " Swing Mode: %s", climate_swing_mode_to_string(this->swing_mode));
@@ -332,9 +456,12 @@ ClimateCall ClimateDeviceRestoreState::to_call(Climate *climate) {
if (traits.get_supports_away()) {
call.set_away(this->away);
}
if (traits.get_supports_fan_modes()) {
if (traits.get_supports_fan_modes() || !traits.get_supported_custom_fan_modes().empty()) {
call.set_fan_mode(this->fan_mode);
}
if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) {
call.set_preset(this->preset);
}
if (traits.get_supports_swing_modes()) {
call.set_swing_mode(this->swing_mode);
}
@@ -352,9 +479,21 @@ void ClimateDeviceRestoreState::apply(Climate *climate) {
if (traits.get_supports_away()) {
climate->away = this->away;
}
if (traits.get_supports_fan_modes()) {
if (traits.get_supports_fan_modes() && !this->uses_custom_fan_mode) {
climate->fan_mode = this->fan_mode;
}
if (!traits.get_supported_custom_fan_modes().empty() && this->uses_custom_fan_mode) {
climate->custom_fan_mode = traits.get_supported_custom_fan_modes()[this->custom_fan_mode];
}
if (traits.get_supports_presets() && !this->uses_custom_preset) {
climate->preset = this->preset;
}
if (!traits.get_supported_custom_presets().empty() && this->uses_custom_preset) {
climate->custom_preset = traits.get_supported_custom_presets()[this->custom_preset];
}
if (!traits.get_supported_custom_presets().empty() && uses_custom_preset) {
climate->custom_preset = traits.get_supported_custom_presets()[this->preset];
}
if (traits.get_supports_swing_modes()) {
climate->swing_mode = this->swing_mode;
}

View File

@@ -3,6 +3,7 @@
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/preferences.h"
#include "esphome/core/log.h"
#include "climate_mode.h"
#include "climate_traits.h"
@@ -70,12 +71,22 @@ class ClimateCall {
ClimateCall &set_fan_mode(optional<ClimateFanMode> fan_mode);
/// Set the fan mode of the climate device based on a string.
ClimateCall &set_fan_mode(const std::string &fan_mode);
/// Set the fan mode of the climate device based on a string.
ClimateCall &set_fan_mode(optional<std::string> fan_mode);
/// Set the swing mode of the climate device.
ClimateCall &set_swing_mode(ClimateSwingMode swing_mode);
/// Set the swing mode of the climate device.
ClimateCall &set_swing_mode(optional<ClimateSwingMode> swing_mode);
/// Set the swing mode of the climate device based on a string.
ClimateCall &set_swing_mode(const std::string &swing_mode);
/// Set the preset of the climate device.
ClimateCall &set_preset(ClimatePreset preset);
/// Set the preset of the climate device.
ClimateCall &set_preset(optional<ClimatePreset> preset);
/// Set the preset of the climate device based on a string.
ClimateCall &set_preset(const std::string &preset);
/// Set the preset of the climate device based on a string.
ClimateCall &set_preset(optional<std::string> preset);
void perform();
@@ -86,6 +97,9 @@ class ClimateCall {
const optional<bool> &get_away() const;
const optional<ClimateFanMode> &get_fan_mode() const;
const optional<ClimateSwingMode> &get_swing_mode() const;
const optional<std::string> &get_custom_fan_mode() const;
const optional<ClimatePreset> &get_preset() const;
const optional<std::string> &get_custom_preset() const;
protected:
void validate_();
@@ -98,13 +112,25 @@ class ClimateCall {
optional<bool> away_;
optional<ClimateFanMode> fan_mode_;
optional<ClimateSwingMode> swing_mode_;
optional<std::string> custom_fan_mode_;
optional<ClimatePreset> preset_;
optional<std::string> custom_preset_;
};
/// Struct used to save the state of the climate device in restore memory.
struct ClimateDeviceRestoreState {
ClimateMode mode;
bool away;
ClimateFanMode fan_mode;
bool uses_custom_fan_mode{false};
union {
ClimateFanMode fan_mode;
uint8_t custom_fan_mode;
};
bool uses_custom_preset{false};
union {
ClimatePreset preset;
uint8_t custom_preset;
};
ClimateSwingMode swing_mode;
union {
float target_temperature;
@@ -168,11 +194,20 @@ class Climate : public Nameable {
bool away{false};
/// The active fan mode of the climate device.
ClimateFanMode fan_mode;
optional<ClimateFanMode> fan_mode;
/// The active swing mode of the climate device.
ClimateSwingMode swing_mode;
/// The active custom fan mode of the climate device.
optional<std::string> custom_fan_mode;
/// The active preset of the climate device.
optional<ClimatePreset> preset;
/// The active custom preset mode of the climate device.
optional<std::string> custom_preset;
/** Add a callback for the climate device state, each time the state of the climate device is updated
* (using publish_state), this callback will be called.
*

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