Compare commits

..

369 Commits

Author SHA1 Message Date
Otto winter
88632b22e2 Complete 2021-09-06 12:44:53 +02:00
Otto winter
44041d2526 Updates 2021-08-23 20:23:39 +02:00
Otto winter
7cfc36cb70 Add noise API transport support 2021-08-16 20:18:01 +02:00
Otto winter
08dd72e716 Echo component and noise 2021-08-12 13:45:51 +02:00
Otto winter
7b7e5f7db5 Fixes 2021-08-10 19:40:38 +02:00
Otto winter
c9b170eab9 Merge remote-tracking branch 'origin/dev' into socket-refactor-and-ssl 2021-08-10 19:06:46 +02:00
Dave T
1771e673d2 Warn if underscore character is used in hostname (#2079)
* Prevent underscore character being used in 'name'.

* Restrict underscores in hostnames, not all names.

* Use hostname validator for node name.

* Allow underscore in hostname but warn once.

* Add renaming instructions link to warning.

* Point underscore warning to FAQ section

Co-authored-by: Otto Winter <otto@otto-winter.com>

Co-authored-by: Otto Winter <otto@otto-winter.com>
2021-08-10 14:14:42 +02:00
Jesse Hills
d258e06fd7 Add rgbct and color_temperature light platforms (#2138)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2021-08-10 21:28:56 +12:00
Otto Winter
854f4a8896 Format dev temp idedata (#2142) 2021-08-10 11:14:04 +02:00
Stefan Agner
f94c221a9a Increase task wdt timeout for ESP32/ESP32-C3 (#2096) 2021-08-10 11:10:52 +02:00
Stephen Tierney
6a2f0f5143 Add support for PMSA003i (#1501)
Co-authored-by: Otto Winter <otto@otto-winter.com>
Co-authored-by: steve <steve@Hackintosh.local>
Co-authored-by: Otto winter <otto@otto-winter.com>
2021-08-10 11:00:16 +02:00
WJCarpenter
183e2a8471 Support component tsl2591 (#2131)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
Co-authored-by: WJCarpenter <bill@carpenter.org>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-08-10 10:48:06 +02:00
Oxan van Leeuwen
c6c2842bdb Always abort on allocation when out-of-memory (#2129)
Co-authored-by: Otto winter <otto@otto-winter.com>
2021-08-10 10:46:46 +02:00
Guillermo Ruffino
f26767b65e Dsmr component (#1881)
Co-authored-by: Otto winter <otto@otto-winter.com>
2021-08-10 10:32:16 +02:00
Keith Burzinski
98d32876b5 Thermostat enhancements 2 (#2114) 2021-08-10 10:16:44 +02:00
Trammell Hudson
e5d0f3c036 waveshare_epaper: add support for ttgo t5 b74 variant display (#1869)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-08-10 10:09:46 +02:00
Adrián Panella
cc15aaacbb RFC: status_led: allow to share single light (#1974)
Co-authored-by: Otto winter <otto@otto-winter.com>
2021-08-10 09:55:34 +02:00
Oxan van Leeuwen
553df1d57b Don't mark COLOR_* constants as static in header (#2141) 2021-08-10 09:53:48 +02:00
Tom Matheussen
b92311402a Adds CGPR1 - Qingping Motion & Ambient light sensor support (#1675)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-08-10 17:06:04 +12:00
Jesse Hills
93796491af Allow entities to be disabled by default in HA (#2113)
Co-authored-by: Otto Winter <otto@otto-winter.com>
2021-08-10 13:45:31 +12:00
Oxan van Leeuwen
c038cf27a7 Don't discard cold/warm white brightness in constant brightness mode (#2136) 2021-08-10 13:30:29 +12:00
Otto Winter
1f42d32eb5 Fix some issues with deprecated argv syntax detection (#2127) 2021-08-10 13:27:21 +12:00
Adrián Panella
06bde559da Add Dish Network protocol (#2117)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-08-10 13:26:42 +12:00
Keith Burzinski
922f7167f5 Add new Toshiba AC unit protocol (#1987) 2021-08-10 13:25:11 +12:00
Keith Burzinski
90c0d3e12f Add Toshiba AC generic IR remote protocol (#2019) 2021-08-10 13:21:10 +12:00
Oxan van Leeuwen
bf5f846fc6 Refactor clang-tidy script to use actual compiler flags and includes (#2133)
Co-authored-by: Otto winter <otto@otto-winter.com>
2021-08-09 22:43:18 +02:00
Oxan van Leeuwen
926bcc71ae Only compile protobuf dumping when very verbose logging is enabled (#2139) 2021-08-09 22:32:06 +02:00
Otto winter
40dd9c5dce Socket refactor and SSL 2021-08-09 20:54:50 +02:00
Jesse Hills
ea4a458214 Removed unused arguments from rgbww code (#2137) 2021-08-09 16:44:52 +12:00
Jesse Hills
b3ae3e1feb Tidy HA addon (#1937) 2021-08-09 10:30:19 +12:00
buxtronix
fe7af21c91 Anova fahrenheit support (#2126)
Co-authored-by: Ben Buxton <bb@cactii.net>
2021-08-09 08:05:36 +12:00
Sourabh Jaiswal
29f72037fe Added support for Hitachi AC424 remote type (#2101) 2021-08-08 10:59:52 -03:00
Oxan van Leeuwen
1d6b4bfcef Don't stop effects if brightness goes to zero (#2134) 2021-08-07 23:24:47 +12:00
Stefan Agner
5bfac5ec09 Allow multiple unnamed libraries (#2132) 2021-08-07 23:16:34 +12:00
Oxan van Leeuwen
dfffaace26 Drop legacy esphomeyaml command wrapper code (#2130) 2021-08-07 23:15:32 +12:00
Oxan van Leeuwen
1d5f628c7a Add support for ESP8266 Arduino v3.0.1 (#2128) 2021-08-07 23:14:57 +12:00
Jesse Hills
cb8a6f66fa Add state classes to pvvx_mithermometer (#2125) 2021-08-05 12:05:36 +12:00
Oxan van Leeuwen
cb21c7c18d Fix crash when using addressable_set with out-of-range indices (#2120) 2021-08-05 11:30:32 +12:00
Oxan van Leeuwen
0d104776bc Various follow-up fixes to color mode changes (#2118) 2021-08-05 11:28:39 +12:00
Otto Winter
768c71830b Fix external components not refreshing with default or high refresh time (#2122) 2021-08-04 17:33:17 +02:00
Oxan van Leeuwen
ceb0564ebf Fix mixup between ColorMode and ColorCapability (#2121) 2021-08-04 12:32:42 +12:00
Oxan van Leeuwen
20f7eb7327 Add version argument to ESPDEPRECATED macro (#2116) 2021-08-04 10:43:01 +12:00
Oxan van Leeuwen
441d5bd44d Migrate COLOR constants to Color class & disallow implicit conversions to Color (#2093)
Co-authored-by: Xo Wang <xo@geekshavefeelings.com>
2021-08-04 09:21:57 +12:00
brambo123
9fa19df2ff Fix time.on_time triggering if time jumped back (#1806)
Co-authored-by: Otto winter <otto@otto-winter.com>
2021-08-03 19:56:23 +02:00
Rob Gridley
39f64f597e Add SM16703 to supported FastLED chipsets (#1751)
Co-authored-by: Otto winter <otto@otto-winter.com>
2021-08-03 16:56:29 +02:00
Brett Profitt
160429eb24 Add support for Waveshare E-Paper 4.2" B V2 (#1610)
Co-authored-by: Otto winter <otto@otto-winter.com>
2021-08-03 16:41:53 +02:00
Andreas Hergert
6516c64e67 Add min_save_interval to total_energy/integration for memory wear (#1665)
Co-authored-by: Andreas Hergert <andreas.hergert@otrs.com>
Co-authored-by: Otto Winter <otto@otto-winter.com>
2021-08-03 16:41:34 +02:00
dependabot[bot]
4c8a703084 Bump esptool from 2.8 to 3.1 (#1839)
Bumps [esptool](https://github.com/espressif/esptool) from 2.8 to 3.1.
- [Release notes](https://github.com/espressif/esptool/releases)
- [Commits](https://github.com/espressif/esptool/compare/v2.8...v3.1)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-08-03 08:49:45 +02:00
Keith Burzinski
335210d788 Thermostat enhancements and code clean-up (#2073) 2021-08-02 11:08:24 +02:00
Jesse Hills
9b04e657db Fix import (#2108) 2021-08-02 20:53:34 +12:00
Jesse Hills
f7311aa025 Dont force 0 state instead of min_power unless explicit config set (#2107) 2021-08-02 20:33:00 +12:00
John "Warthog9" Hawley
fb24e55c8d pmsx003: add standard particle, particle counts (#1694) 2021-08-02 10:32:08 +02:00
Carlos Garcia Saura
b58ca46a46 [duty_cycle] initialize two missing variables (#2088) 2021-08-02 10:28:25 +02:00
Jesse Hills
76991cdcc4 Add select entities and implement template select (#2067)
Co-authored-by: Otto Winter <otto@otto-winter.com>
2021-08-02 20:00:51 +12:00
John K. Luebs
69c7cf783e Fix missing include in light_traits.h (#2105) 2021-08-02 07:13:46 +12:00
Otto Winter
f751c3828e Fix MQTT light include (#2104)
Fixes https://github.com/esphome/issues/issues/2285
2021-08-01 12:33:10 +02:00
Otto Winter
5c65f9f9ad Convert sensor_schema to use kwargs (#2094)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-08-01 12:21:32 +02:00
Eric Severance
81ae6709e4 Fix parity bit calculation for ESP8266SoftwareSerial (#1873) 2021-08-01 20:56:05 +12:00
Łukasz Śliwiński
593a3d48fb Use proper schema for the analog pin shorthand (#2103)
The wrong error message is displayed like:
> GPIO17 (TOUT) is an analog-only pin on the ESP8266.
in case of the analog pin validation because the method `shorthand_analog_pin` uses wrong `GPIO_FULL_INPUT_PIN_SCHEMA` instead of `GPIO_FULL_ANALOG_PIN_SCHEMA`.
2021-08-01 00:37:48 +12:00
Paul Monigatti
a8b90283d8 Fix min/max keys in MQTT Number to match Home Assistant (#2102) 2021-07-31 23:20:10 +12:00
Guillermo Ruffino
80076f935d fix diplay trigger missing base class (#2099) 2021-07-30 16:55:26 +12:00
Otto Winter
34e8979d40 Convert more code to async-def syntax (#2095) 2021-07-30 10:54:10 +12:00
Jesse Hills
2966a62429 Set pulse meter total to use state class measurement and last reset type auto (#2097) 2021-07-30 10:53:33 +12:00
Oxan van Leeuwen
5983ccc55c Color mode implementation (#2012) 2021-07-29 19:11:56 +02:00
Kodey Converse
de382b704c Add device class support to MQTT cover (#2092) 2021-07-29 16:08:48 +02:00
Otto Winter
16dbbfabc6 Add demo integration (#2085) 2021-07-29 11:50:55 +02:00
Otto Winter
af8d04818d Pull ESP32 Wifi fixes from arduino-esp32 (#2069) 2021-07-29 11:44:19 +02:00
Mike Meessen
ee19ef1aac Add support for the HRXL MaxSonar WR series sensors (#2020) 2021-07-29 11:37:31 +02:00
Tyler Menezes
5e2d4e332a Add T6615 (#1170)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-07-29 11:24:36 +02:00
rnauber
c6c857dfff Add support for the TLC5947 24-Channel, 12-Bit PWM LED Driver (#2066)
Co-authored-by: Richard Nauber <richard@nauber.dev>
2021-07-29 11:16:04 +02:00
Nicholas Peters
8dbac20f8b Add SDP3x sensor (#2064)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Otto Winter <otto@otto-winter.com>
2021-07-29 10:57:52 +02:00
Jesse Hills
513066ba52 Use sensor_schema for total_daily_energy (#2090)
Co-authored-by: Otto Winter <otto@otto-winter.com>
2021-07-29 19:46:15 +12:00
Jesse Hills
316777f757 HLW8012 - Dump energy sensor config (#2082) 2021-07-29 07:53:54 +12:00
Stefan Agner
246950159d Bump ESPAsyncWebServer-esphome to 1.3.0 (#2075) 2021-07-28 21:24:10 +02:00
Otto Winter
31d6a54b06 Fix PID climate breaks when restoring old modes (#2086) 2021-07-28 21:23:41 +02:00
dependabot[bot]
5c3a6164bb Bump pylint from 2.9.5 to 2.9.6 (#2087) 2021-07-28 21:23:24 +02:00
Oxan van Leeuwen
1652914d39 Make light.addressable_set color parameters behave as documented & consistent with elsewhere (#2009) 2021-07-28 19:49:43 +02:00
Otto Winter
618cfd9ec5 Add sensor monetary device_class (#2083) 2021-07-28 15:34:18 +02:00
Peter van Dijk
f97cfe9916 pm1006: add rx-only support (#2038) 2021-07-28 10:41:21 +02:00
Jesse Hills
b9259a0238 Bump esphome dashboard to 20210728.0 (#2081) 2021-07-28 15:18:31 +12:00
John K. Luebs
5abbe385c5 More Tuya MCU robustness (#2080) 2021-07-28 14:01:15 +12:00
Otto Winter
b0a3891498 Fix MQTT climate custom fan modes without regular ones (#2071) 2021-07-27 07:46:13 +12:00
Otto Winter
2a9e3d84fd Fix climate restore schema changed resulting in invalid restore (#2068)
Co-authored-by: Stefan Agner <stefan@agner.ch>
2021-07-26 15:19:50 +02:00
Oxan van Leeuwen
a3dcac62f9 Fix a bunch of typos (#2058)
Co-authored-by: Stefan Agner <stefan@agner.ch>
Co-authored-by: Otto Winter <otto@otto-winter.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-07-26 14:48:57 +02:00
Chris Nussbaum
6b535b11f8 Couple more updates for the Tuya component (#2065)
Co-authored-by: Chris Nussbaum <chris.nussbaum@protolabs.com>
2021-07-26 21:39:03 +12:00
Stefan Agner
d9f09a7523 Initial ESP32-C3-DevKitM-1 board support (#2062)
Co-authored-by: Stijn Tintel <stijn@linux-ipv6.be>
2021-07-26 11:10:56 +02:00
Stefan Agner
159744e09e Support library override using named library with repository (#2056) 2021-07-26 10:50:45 +02:00
Stefan Agner
c2637a76f7 Print BLE 128-bit UUIDs according to spec (#2061)
Since the iterator integer counts the bytes backwards we need to
use the complement to 15.
2021-07-26 10:49:38 +02:00
buxtronix
237edd75d1 Log warning about lack of support for Anova nano (#2063)
Co-authored-by: Ben Buxton <bb@cactii.net>
2021-07-26 10:41:54 +02:00
WeekendWarrior1
a34d5e3901 Move configure_rmt() into setup() (#2028) 2021-07-26 09:32:08 +02:00
WeekendWarrior1
1dd43a75f2 Correctly invert esp32 RMT TX (#2022) 2021-07-26 09:20:02 +02:00
Trevor North
1f5cbca509 Merge build flags from platformio_options (#1651) 2021-07-26 08:59:18 +02:00
Oxan van Leeuwen
3749c11f21 Fix clang-format script behaviour without -i + code cleanup (#2002)
Co-authored-by: Stefan Agner <stefan@agner.ch>
2021-07-26 09:54:32 +12:00
Stefan Agner
66cdb761dc Fix minor build issues with Arduino ESP32 2.0.0-rc1 (#2057) 2021-07-24 21:55:25 +12:00
Stefan Agner
f0d9ad6a4e Add TAG to all compile units (#2060)
When using static TAG is only valid in the current compile unit. For
some reason it seems that the current ESP8266/ESP32 compiler use the
instance from ble.cpp, but it seems that this causes issues with newer
compiler leading to compile time errors like this:
In file included from /root/.platformio/packages/framework-arduinoespressif32/cores/esp32/esp32-hal-log.h:164,
                 from /root/.platformio/packages/framework-arduinoespressif32/cores/esp32/esp32-hal.h:71,
                 from /root/.platformio/packages/framework-arduinoespressif32/cores/esp32/Arduino.h:36,
                 from src/esphome/core/esphal.h:3,
                 from src/esphome/core/helpers.h:10,
                 from src/esphome/components/esp32_ble/ble_uuid.h:3,
                 from src/esphome/components/esp32_ble/ble_advertising.cpp:5:
src/esphome/components/esp32_ble/ble_advertising.cpp: In member function 'void esphome::esp32_ble::BLEAdvertising::start()':
src/esphome/components/esp32_ble/ble_advertising.cpp:64:14: error: 'TAG' was not declared in this scope
     ESP_LOGE(TAG, "esp_ble_gap_config_adv_data failed (Advertising): %d", err);
              ^~~
2021-07-24 03:53:59 +12:00
carstenschroeder
03e317d052 Fixes new auto mode COOL and HEAT after #1994 (#2053) 2021-07-22 14:39:57 +02:00
Sergey V. DUDANOV
ba461e51a8 midea_ac: fix presets implementation (#2054) 2021-07-22 14:39:21 +02:00
Maurice Makaay
80949521b6 Accept change as proposed by black. (#2055) 2021-07-22 14:37:42 +02:00
Sourabh Jaiswal
acbb8e9fd0 Added support for Selec Energy Meter (#1993)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-07-22 15:31:28 +12:00
Pasi Suominen
90394a50df Added support for pvvx_mithermometer sensor (#1546)
Co-authored-by: Pasi Suominen <pasiz@pasizdesk.pasiz.net>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-07-22 15:21:08 +12:00
Jesse Hills
5379794f16 Add test5 back to CI (#2052) 2021-07-22 13:24:01 +12:00
Keith Burzinski
0a32321c85 Thermostat fixes+updates 1 (#2032)
Co-authored-by: Otto Winter <otto@otto-winter.com>
2021-07-22 09:09:31 +12:00
dependabot[bot]
c9062599df Bump pylint from 2.9.4 to 2.9.5 (#2050)
Bumps [pylint](https://github.com/PyCQA/pylint) from 2.9.4 to 2.9.5.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog)
- [Commits](https://github.com/PyCQA/pylint/compare/v2.9.4...v2.9.5)

---
updated-dependencies:
- dependency-name: pylint
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-22 08:58:41 +12:00
dependabot[bot]
fc42f14448 Bump pylint from 2.8.2 to 2.9.4 (#2047)
* Bump pylint from 2.8.2 to 2.9.4

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

---
updated-dependencies:
- dependency-name: pylint
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

* Fix up functionality needed for latest pylint (#2049)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Sean Vig <sean.v.775@gmail.com>
2021-07-21 14:40:09 +12:00
Sean Vig
3e65e6c69a Remove superfluous polling on ADS1115 (#2015) 2021-07-21 09:35:45 +12:00
Jesse Hills
3b3297d269 Adding last_reset_type to sensors that should support it. (#2039) 2021-07-21 09:20:20 +12:00
Oxan van Leeuwen
fc0deb642a Fix white value transition for addressable lights (#2045) 2021-07-21 08:42:03 +12:00
Stefan Agner
9f2b2f51ff Esp32 c3 support (#2035) 2021-07-20 11:12:22 +02:00
Otto Winter
01a4b4e82f ESP32 ADC use esp-idf (#2024)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-07-20 17:05:56 +12:00
Sean Vig
766866197b Correct ADS1115 handling of multiple sensors in continuous mode (#2016)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-07-20 17:05:25 +12:00
Paulus Schoutsen
9b5a3cbcd3 Bump dashboard to 20210719.0 (#2043) 2021-07-19 21:44:39 -07:00
Sergey V. DUDANOV
d2ed3b9bec midea_ac: Fix turbo mode. Preset BOOST. (#2029) 2021-07-20 16:26:07 +12:00
Jesse Hills
99d2db42cd Add restore_value to template number (#2041) 2021-07-20 15:40:42 +12:00
Jesse Hills
7619507e6c Convert Arduino boolean to bool (#2042) 2021-07-20 15:31:54 +12:00
Jesse Hills
71d9d64a02 Number and Template Number updates (#2036)
Co-authored-by: Otto winter <otto@otto-winter.com>
2021-07-20 08:22:49 +12:00
dependabot[bot]
2e49fd7b48 Bump black from 21.6b0 to 21.7b0 (#2031)
Bumps [black](https://github.com/psf/black) from 21.6b0 to 21.7b0.
- [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)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-19 08:09:00 +12:00
Otto Winter
06912b492f Improve external components error messages (#2026) 2021-07-16 10:23:08 +02:00
Otto Winter
442e58b07a Dashboard disable assets caching (#2025) 2021-07-16 10:22:42 +02:00
Otto Winter
799f04efc0 GH Actions CI use GHCR (#2027) 2021-07-15 21:51:52 +02:00
Otto Winter
cc7dbeada6 Refactor docker build system and workflows (#2023) 2021-07-15 21:30:04 +02:00
Sean Vig
45d368e3a1 Always tick mdns in ethernet component (#2018) 2021-07-15 16:12:48 +12:00
Sean Vig
628a94bad3 Fix ethernet component hostname handling (#2010)
Co-authored-by: Otto Winter <otto@otto-winter.com>
2021-07-15 15:45:41 +12:00
SenexCrenshaw
0651716b96 Nextion upload and sensors (#1464)
Co-authored-by: Senex Crenshaw <senexcrenshaw@gmail.com>
2021-07-15 12:51:15 +12:00
Jesse Hills
0992609bf4 Bump version to v1.21.0-dev 2021-07-15 07:45:05 +12:00
St4n
c399905675 [Teleinfo] do not stop parsing frame if there is only a CRC error (#1999)
Co-authored-by: Stephane Angot <s.angot@meetic-corp.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-07-14 21:21:39 +12:00
Stefan Agner
5cb0c11feb Introduce clamp as a template function (#1953) 2021-07-14 17:08:18 +12:00
WeekendWarrior1
08b67e7aea catch 0.0 in float set_level pre-adjustment (#2013) 2021-07-14 14:43:30 +12:00
Jesse Hills
07ae8ec553 Remove a whole bunch of deprecated/removed stuff (#1981) 2021-07-14 14:42:16 +12:00
Sourabh Jaiswal
04c3a43c17 Added support for havells_solar sensor (#1988)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-07-14 13:05:51 +12:00
dependabot[bot]
b632344596 Bump black from 21.5b1 to 21.6b0 (#2011)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-13 11:39:04 +02:00
Oxan van Leeuwen
fb8ec79a52 Color brightness fixes (#2008) 2021-07-13 12:28:29 +12:00
Huub Eikens
7dd16df846 Sgp30 sensor improvements (#1510)
Co-authored-by: Umberto73 <huub@eikens.com>
Co-authored-by: Guillermo Ruffino <glm.net@gmail.com>
2021-07-13 09:21:54 +12:00
Otto Winter
551e9c6111 Bang bang climate new mode meanings (#1996) 2021-07-12 22:56:55 +02:00
Mikko Tervala
cc9f0b3f47 Add support for IBS-TH1 External Sensor (#1983) 2021-07-13 08:55:53 +12:00
monkeyclass
d77c3abdc0 Fixed lolin32 lite key (#2001)
Co-authored-by: monkeyclass <oliver_reinholdt@hotmail.com>
2021-07-13 07:37:34 +12:00
Jesse Hills
dd37a4e04c Add Number entities (from Home Assistant) (#1971)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2021-07-13 07:20:12 +12:00
Oxan van Leeuwen
1f5c79bd17 Fix deprecation message for old climate swing mode methods (#2003) 2021-07-11 16:51:24 +12:00
Maurice Makaay
623570a117 Add state callback to ota component (#1816)
Co-authored-by: Maurice Makaay <mmakaay1@xs4all.net>
Co-authored-by: Guillermo Ruffino <glm.net@gmail.com>
2021-07-11 07:52:19 +12:00
carstenschroeder
cdbc146e5d Climate modes COOL and HEAT are auto modes (#1994) 2021-07-10 11:37:55 +02:00
Otto Winter
7ae611256a Improve climate mode code docs (#1995)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-07-10 11:23:04 +02:00
dependabot[bot]
b62c47fede Bump pytest-asyncio from 0.14.0 to 0.15.1 (#1793)
Bumps [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) from 0.14.0 to 0.15.1.
- [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases)
- [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v0.14.0...v0.15.1)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-09 23:28:37 +02:00
dependabot[bot]
99f497c3b8 Bump pytest-cov from 2.11.1 to 2.12.1 (#1855)
Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 2.11.1 to 2.12.1.
- [Release notes](https://github.com/pytest-dev/pytest-cov/releases)
- [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-cov/compare/v2.11.1...v2.12.1)

---
updated-dependencies:
- dependency-name: pytest-cov
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-09 23:28:28 +02:00
dependabot[bot]
4f88c2489b Bump protobuf from 3.17.0 to 3.17.3 (#1986)
Bumps [protobuf](https://github.com/protocolbuffers/protobuf) from 3.17.0 to 3.17.3.
- [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.17.0...v3.17.3)

---
updated-dependencies:
- dependency-name: protobuf
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-09 23:27:08 +02:00
Michael Gorven
294ba1fca7 Support custom fan modes in mqtt_climate (#1989)
Co-authored-by: Michael Gorven <michael@gorven.za.net>
2021-07-09 23:25:27 +02:00
Jesse Hills
be61b38a2c Allow WiFi AP to use device name (#1990) 2021-07-09 00:39:37 +12:00
Oxan van Leeuwen
f9797825ad Change color model to fix white channel issues (#1895) 2021-07-08 21:37:47 +12:00
Oxan van Leeuwen
fd4b7d4588 Don't try compat parsing for "esphome version" (#1966) 2021-07-06 10:17:36 +12:00
Jesse Hills
062cedc200 remote_receiver: use config parent receiver for registering dumpers (#1980) 2021-07-06 10:15:29 +12:00
WeekendWarrior1
79b9d0579d Add stepper.set_acceleration and stepper.set_deceleration to stepper component (#1977) 2021-07-05 13:22:43 +12:00
buxtronix
ab31117bf3 Anova ble component (#1752)
Co-authored-by: Ben Buxton <bb@cactii.net>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-07-05 11:59:12 +12:00
Adrián Panella
d31040f5d8 hlw8012: fix constants for BL0937 (#1973)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-07-05 11:09:09 +12:00
dependabot[bot]
52d19fa43d Bump pytest-mock from 3.5.1 to 3.6.1 (#1754)
Bumps [pytest-mock](https://github.com/pytest-dev/pytest-mock) from 3.5.1 to 3.6.1.
- [Release notes](https://github.com/pytest-dev/pytest-mock/releases)
- [Changelog](https://github.com/pytest-dev/pytest-mock/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-mock/compare/v3.5.1...v3.6.1)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-05 10:27:13 +12:00
Martin Weinelt
8ca34f7098 Bump hypothesis from 5.21.0 to 5.49.0 (#1753) 2021-07-05 10:10:59 +12:00
Oxan van Leeuwen
4c4099966a Fix invalid escape sequences in regex (#1814) 2021-07-05 10:04:18 +12:00
Paul Doidge
86ac7f3a59 Time Based Cover: Fixed apparent race condition on ESP32 chips (#1984) 2021-07-05 07:39:18 +12:00
Trevor North
9e400a7857 Fix tuya fan speed send (#1978) 2021-07-04 23:47:22 +12:00
Otto Winter
d5278351da Rename master branch to release (#1976) 2021-07-02 15:42:36 +02:00
definitio
36861595f1 Add device_class support for MQTT integration (#1832) 2021-07-01 15:36:01 +02:00
Frederik Gladhorn
d604321f37 Simplify initializing glyph_data (#1970)
Make it easier to read the initialization with zeros, no loop required.
2021-06-30 20:36:48 +02:00
bazuchan
964ab65497 Climate component for Ballu air conditioners with remote model YKR-K/002E (#1939) 2021-06-28 16:26:30 -03:00
Jesse Hills
3b940b1c04 Set is_valid to true straight away when min_length is 0 (#1960) 2021-06-25 07:09:07 +12:00
Jesse Hills
5fca480921 Bump dashboard to 20210623.0 (#1958) 2021-06-24 12:38:59 +12:00
Oxan van Leeuwen
7051f897bc Validate color temperature values for RGBWW/CWWW lights (#1957)
Check if the color temperature of the cold white channel is colder (less) than
the warm white channel.
2021-06-24 00:07:27 +02:00
Otto Winter
2cb3015a28 Compat argv parsing improvements (#1952) 2021-06-23 20:27:08 +02:00
Otto Winter
d0859a7d33 Add climate preset NONE again (#1951) 2021-06-23 20:25:19 +02:00
Jesse Hills
61ebc629f6 Bump esphome-dashboard to 20210622.0 (#1955) 2021-06-22 22:15:11 -07:00
Stefan Agner
32f2da77f8 More VSCode devcontainer improvements (#1934) 2021-06-22 16:37:05 +02:00
Otto Winter
bfca3f242a Disallow power_save_mode NONE if used together with BLE (#1950) 2021-06-22 10:53:10 +02:00
Stefan Agner
3dfff2930a Improve DHT read timings (#1901)
Make sure that the initial rising edge is properly detected even if
timing is somewhat off.

Set MCU start signal to 1ms for AM2302.
2021-06-22 10:07:14 +02:00
Paulus Schoutsen
027e0de48e Bump dashboard to 20210621.0 (#1946) 2021-06-22 16:17:46 +12:00
Otto Winter
c811141a4f API raise minor version for climate changes (#1947) 2021-06-21 22:02:18 +02:00
Otto Winter
871c0ee2a5 Rework climate traits (#1941)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-06-21 21:17:01 +02:00
Jesse Hills
b8a7741c61 Update generation script to add const (#1945) 2021-06-21 21:27:35 +12:00
Jesse Hills
b6011b9353 Fix bad climate control enum (#1942) 2021-06-21 14:17:38 +12:00
Jesse Hills
40a5005d94 Allow wifi setup to proceed when there is no sta or ap (#1931) 2021-06-21 09:00:16 +12:00
Sergey V. DUDANOV
c5eba21ff6 Fix midea_ac query frame (#1940) 2021-06-21 08:59:12 +12:00
Jesse Hills
4891cfef56 Add data sizes to tuya log message (#1938) 2021-06-18 15:50:56 +12:00
Chris Nussbaum
4395664547 Don't send Tuya commands while currently receiving a message (#1886)
Co-authored-by: Chris Nussbaum <chris.nussbaum@protolabs.com>
2021-06-18 14:58:39 +12:00
Keith Burzinski
04d926af39 Add variable bit width for Samsung protocol (#1927) 2021-06-18 13:54:46 +12:00
Stefan Agner
f9a31c1abb Fix error print in script/helpers.py (#1935) 2021-06-18 13:49:25 +12:00
Jesse Hills
dca1c0f160 Replace CLIMATE_MODE_AUTO with CLIMATE_MODE_HEAT_COOL in most cases (#1933) 2021-06-18 11:48:40 +12:00
Otto Winter
2419bc3678 Improve config final validation (#1917) 2021-06-18 07:54:14 +12:00
Sergey V. DUDANOV
c19b3ecd43 Fix: midea_ac: fixed query status frame (#1922) 2021-06-18 07:39:13 +12:00
Paulus Schoutsen
ef1e91d838 Update dashboard to 20210617.0 (#1930) 2021-06-18 07:35:54 +12:00
Barry Loong
e5929225eb Fix typo in test3.yaml (#1928) 2021-06-17 21:39:59 +12:00
Franck Nijhof
607c3ae651 Fix update-all from dashboard (#1924) 2021-06-17 05:39:04 +12:00
Jesse Hills
5591832b50 Shorten the ble name to prevent crash with long device names (#1920) 2021-06-16 13:49:06 +12:00
Paulus Schoutsen
25b116048c Bump dashboard to 20210615.0 (#1918) 2021-06-16 09:04:17 +12:00
Guillermo Ruffino
24ba9eba46 fixes compatibility with esphome cfg vscode (#1911) 2021-06-15 20:20:24 +12:00
Otto Winter
424c34225f Run script/setup in devcontainer instead of pip install (#1913) 2021-06-15 20:19:21 +12:00
Paulus Schoutsen
d781f3a11b Bump frontend to 20210614.0 (#1912) 2021-06-15 20:18:27 +12:00
Stefan Agner
a80f9ed336 Support ESP8266 Arduino 3.0.0 (#1897)
Co-authored-by: Otto Winter <otto@otto-winter.com>
2021-06-15 08:50:58 +02:00
Otto Winter
92bbedfa5a Fix #1908 mutating input parameter 2021-06-15 08:48:18 +02:00
Jesse Hills
86710ed483 Validate that either networks, ap, or improv is set up (#1910) 2021-06-15 13:16:43 +12:00
Jesse Hills
da7eb9ac90 Allow no networks or AP to be set. (#1908)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2021-06-15 11:05:10 +12:00
dentra
c411043681 Adds support cpp to vscode (#1828)
Co-authored-by: Stefan Agner <stefan@agner.ch>
2021-06-15 10:45:22 +12:00
Oxan van Leeuwen
93f8ee7e60 Fix non-const global (#1907) 2021-06-15 09:12:06 +12:00
Oxan van Leeuwen
0efc1f06f2 Split files in light component (#1893) 2021-06-14 18:01:56 +02:00
Paulus Schoutsen
9ad9d64ac7 Add new wizard + allow installing firmware over webserial (#1887) 2021-06-12 10:49:05 +12:00
Jesse Hills
5a2cfa2798 Move esp32_ble_server to its own component (#1898) 2021-06-12 08:31:15 +12:00
Jesse Hills
eb24da7c82 Ensure wifi is in at least station mode before starting improv (#1899) 2021-06-11 23:28:00 +12:00
Oxan van Leeuwen
f93e261d75 Convert st7735.cpp to use Unix line separators (#1894) 2021-06-11 08:33:12 +12:00
Stefan Agner
501f88ca86 Avoid non-const globals and enable clang-tidy check (#1892) 2021-06-11 08:19:44 +12:00
Otto Winter
360effcb72 Activate some clang-tidy checks (#1884) 2021-06-10 13:04:40 +02:00
Otto Winter
eb9bd69405 Configure clang-format for consistent pointer alignment (#1890) 2021-06-10 12:55:20 +02:00
Jesse Hills
11b8210e36 Update ambiguous command (#1889) 2021-06-10 21:13:18 +12:00
Geoff Davis
d23376b81e Add support for waveshare_epaper 1.54v2 (#1843)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-06-10 14:05:27 +12:00
Jesse Hills
99d90845b5 BLE loop use (#1882) 2021-06-10 14:04:39 +12:00
Oxan van Leeuwen
ea0127e42b Simplify LightCall validation (#1874) 2021-06-09 17:01:05 +02: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
1180 changed files with 45542 additions and 13727 deletions

View File

@@ -49,7 +49,7 @@ ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true

View File

@@ -4,14 +4,29 @@ Checks: >-
-abseil-*,
-android-*,
-boost-*,
-bugprone-macro-parentheses,
-bugprone-branch-clone,
-bugprone-narrowing-conversions,
-bugprone-signed-char-misuse,
-bugprone-too-small-loop-variable,
-cert-dcl50-cpp,
-cert-err58-cpp,
-clang-analyzer-core.CallAndMessage,
-cert-oop57-cpp,
-cert-str34-c,
-clang-analyzer-optin.cplusplus.UninitializedObject,
-clang-analyzer-osx.*,
-clang-analyzer-security.*,
-clang-diagnostic-delete-abstract-non-virtual-dtor,
-clang-diagnostic-delete-non-abstract-non-virtual-dtor,
-clang-diagnostic-shadow-field,
-clang-diagnostic-sign-compare,
-clang-diagnostic-unused-variable,
-clang-diagnostic-unused-const-variable,
-cppcoreguidelines-avoid-c-arrays,
-cppcoreguidelines-avoid-goto,
-cppcoreguidelines-c-copy-assignment-signature,
-cppcoreguidelines-avoid-magic-numbers,
-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,
@@ -24,42 +39,52 @@ Checks: >-
-cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-special-member-functions,
-fuchsia-*,
-fuchsia-default-arguments,
-fuchsia-multiple-inheritance,
-fuchsia-overloaded-operator,
-fuchsia-statically-constructed-objects,
-fuchsia-default-arguments-declarations,
-fuchsia-default-arguments-calls,
-google-build-using-namespace,
-google-explicit-constructor,
-google-readability-braces-around-statements,
-google-readability-casting,
-google-readability-todo,
-google-runtime-int,
-google-runtime-references,
-hicpp-*,
-llvm-else-after-return,
-llvm-header-guard,
-llvm-include-order,
-misc-unconventional-assign-operator,
-llvm-qualified-auto,
-llvmlibc-*,
-misc-non-private-member-variables-in-classes,
-misc-no-recursion,
-misc-unused-parameters,
-modernize-deprecated-headers,
-modernize-pass-by-value,
-modernize-pass-by-value,
-modernize-avoid-c-arrays,
-modernize-return-braced-init-list,
-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,
-warnings-as-errors,
-zircon-*
-readability-redundant-string-init,
-readability-uppercase-literal-suffix,
-readability-use-anyofallof,
-warnings-as-errors
WarningsAsErrors: '*'
HeaderFilterRegex: '^.*/src/esphome/.*'
AnalyzeTemporaryDtors: false
FormatStyle: google
CheckOptions:

View File

@@ -2,16 +2,29 @@
"name": "ESPHome Dev",
"context": "..",
"dockerFile": "../docker/Dockerfile.dev",
"postCreateCommand": "mkdir -p config && pip3 install -e .",
"runArgs": ["--privileged", "-e", "ESPHOME_DASHBOARD_USE_PING=1"],
"postCreateCommand": [
"script/devcontainer-post-create"
],
"runArgs": [
"--privileged",
"-e",
"ESPHOME_DASHBOARD_USE_PING=1"
],
"appPort": 6052,
"extensions": [
// python
"ms-python.python",
"visualstudioexptteam.vscodeintellicode",
"redhat.vscode-yaml"
// yaml
"redhat.vscode-yaml",
// cpp
"ms-vscode.cpptools",
// editorconfig
"editorconfig.editorconfig",
],
"settings": {
"python.pythonPath": "/usr/local/bin/python",
"python.languageServer": "Pylance",
"python.pythonPath": "/usr/bin/python3",
"python.linting.pylintEnabled": true,
"python.linting.enabled": true,
"python.formatting.provider": "black",
@@ -19,7 +32,7 @@
"editor.formatOnSave": true,
"editor.formatOnType": true,
"files.trimTrailingWhitespace": true,
"terminal.integrated.shell.linux": "/bin/bash",
"terminal.integrated.defaultProfile.linux": "bash",
"yaml.customTags": [
"!secret scalar",
"!lambda scalar",
@@ -27,6 +40,18 @@
"!include_dir_list scalar",
"!include_dir_merge_list scalar",
"!include_dir_merge_named scalar"
]
],
"files.exclude": {
"**/.git": true,
"**/.DS_Store": true,
"**/*.pyc": {
"when": "$(basename).py"
},
"**/__pycache__": true
},
"files.associations": {
"**/.vscode/*.json": "jsonc"
},
"C_Cpp.clang_format_path": "/usr/bin/clang-format-11",
}
}

View File

@@ -103,6 +103,10 @@ venv.bak/
# mypy
.mypy_cache/
# PlatformIO
.pio/
# ESPHome
config/
examples/
Dockerfile

View File

@@ -7,7 +7,7 @@ insert_final_newline = true
charset = utf-8
# python
[*.{py}]
[*.py]
indent_style = space
indent_size = 4
@@ -25,4 +25,10 @@ indent_size = 2
[*.{yaml,yml}]
indent_style = space
indent_size = 2
quote_type = single
quote_type = single
# JSON
[*.json]
indent_style = space
indent_size = 2

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

@@ -3,7 +3,7 @@ name: CI for docker images
# Only run when docker paths change
on:
push:
branches: [dev, beta, master]
branches: [dev, beta, release]
paths:
- 'docker/**'
- '.github/workflows/**'
@@ -18,38 +18,23 @@ jobs:
name: Build docker containers
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
arch: [amd64, armv7, aarch64]
build_type: ["hassio", "docker"]
build_type: ["ha-addon", "docker", "lint"]
steps:
- uses: actions/checkout@v2
- name: Set up env variables
run: |
base_version="3.0.0"
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.9'
- name: Set TAG
run: |
echo "TAG=check" >> $GITHUB_ENV
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
build_to="esphome/esphome-hassio-${{ matrix.arch }}"
dockerfile="docker/Dockerfile.hassio"
else
build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}"
build_to="esphome/esphome-${{ matrix.arch }}"
dockerfile="docker/Dockerfile"
fi
echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
- name: Pull for cache
run: |
docker pull "${BUILD_TO}:dev" || true
- name: Register QEMU binfmt
run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes
- run: |
docker build \
--build-arg "BUILD_FROM=${BUILD_FROM}" \
--build-arg "BUILD_VERSION=ci" \
--cache-from "${BUILD_TO}:dev" \
--file "${DOCKERFILE}" \
.
- name: Run build
run: |
docker/build.py \
--tag "${TAG}" \
--arch "${{ matrix.arch }}" \
--build-type "${{ matrix.build_type }}" \
build

View File

@@ -4,67 +4,98 @@ name: CI
on:
push:
# On dev branch release-dev already performs CI checks
# On other branches the `pull_request` trigger will be used
branches: [beta, master]
branches: [dev, beta, release]
pull_request:
jobs:
lint-clang-format:
ci-with-container:
name: ${{ matrix.name }}
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
steps:
- uses: actions/checkout@v2
# Set up the pio project so that the cpp checks know how files are compiled
# (build flags, libraries etc)
- name: Set up platformio environment
run: pio init --ide atom
- name: Run clang-format
run: script/clang-format -i
- name: Suggest changes
run: script/ci-suggest-changes
lint-clang-tidy:
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
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
strategy:
fail-fast: false
matrix:
split: [1, 2, 3, 4]
include:
- id: clang-format
name: Run script/clang-format
- id: clang-tidy
name: Run script/clang-tidy 1/4
split: 1
- id: clang-tidy
name: Run script/clang-tidy 2/4
split: 2
- id: clang-tidy
name: Run script/clang-tidy 3/4
split: 3
- id: clang-tidy
name: Run script/clang-tidy 4/4
split: 4
# cpp lint job runs with esphome-lint docker image so that clang-format-*
# doesn't have to be installed
container: ghcr.io/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
# (build flags, libraries etc)
- name: Set up platformio environment
run: pio init --ide atom
- name: Register problem matchers
run: |
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
echo "::add-matcher::.github/workflows/matchers/gcc.json"
# Also run git-diff-index so that the step is marked as failed on formatting errors,
# since clang-format doesn't do anything but change files if -i is passed.
- name: Run clang-format
run: |
script/clang-format -i
git diff-index --quiet HEAD --
if: ${{ matrix.id == 'clang-format' }}
- name: Run clang-tidy
run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }}
- name: Suggest changes
run: script/ci-suggest-changes
if: ${{ matrix.id == 'clang-tidy' }}
lint-python:
- name: Suggested changes
run: script/ci-suggest-changes
if: always()
ci:
# Don't use the esphome-lint docker image because it may contain outdated requirements.
# This way, all dependencies are cached via the cache action.
name: ${{ matrix.name }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- id: ci-custom
name: Run script/ci-custom
- id: lint-python
name: Run script/lint-python
- id: test
file: tests/test1.yaml
name: Test tests/test1.yaml
- id: test
file: tests/test2.yaml
name: Test tests/test2.yaml
- id: test
file: tests/test3.yaml
name: Test tests/test3.yaml
- id: test
file: tests/test4.yaml
name: Test tests/test4.yaml
- id: test
file: tests/test5.yaml
name: Test tests/test5.yaml
- id: pytest
name: Run pytest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.7'
- name: Cache pip modules
uses: actions/cache@v1
with:
@@ -72,6 +103,17 @@ jobs:
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
restore-keys: |
esphome-pip-3.7-
# Use per test platformio cache because tests have different platform versions
- name: Cache ~/.platformio
uses: actions/cache@v1
with:
path: ~/.platformio
key: test-home-platformio-${{ matrix.file }}-${{ hashFiles('esphome/core/config.py') }}
restore-keys: |
test-home-platformio-${{ matrix.file }}-
if: ${{ matrix.id == 'test' }}
- name: Set up python environment
run: script/setup
@@ -80,81 +122,22 @@ jobs:
echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
echo "::add-matcher::.github/workflows/matchers/lint-python.json"
echo "::add-matcher::.github/workflows/matchers/python.json"
echo "::add-matcher::.github/workflows/matchers/pytest.json"
echo "::add-matcher::.github/workflows/matchers/gcc.json"
- name: Lint Custom
run: script/ci-custom.py
run: |
script/ci-custom.py
script/build_codeowners.py --check
if: ${{ matrix.id == 'ci-custom' }}
- name: Lint Python
run: script/lint-python
- name: Lint CODEOWNERS
run: script/build_codeowners.py --check
if: ${{ matrix.id == 'lint-python' }}
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
test:
- test1
- test2
- test3
- test4
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.7'
- name: Cache pip modules
uses: actions/cache@v1
with:
path: ~/.cache/pip
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
restore-keys: |
esphome-pip-3.7-
# Use per test platformio cache because tests have different platform versions
- name: Cache ~/.platformio
uses: actions/cache@v1
with:
path: ~/.platformio
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core_config.py') }}
restore-keys: |
test-home-platformio-${{ matrix.test }}-
- name: Set up environment
run: script/setup
- run: esphome compile ${{ matrix.file }}
if: ${{ matrix.id == 'test' }}
- name: Register problem matchers
run: |
echo "::add-matcher::.github/workflows/matchers/gcc.json"
echo "::add-matcher::.github/workflows/matchers/python.json"
- run: esphome tests/${{ matrix.test }}.yaml compile
pytest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.7'
- name: Cache pip modules
uses: actions/cache@v1
with:
path: ~/.cache/pip
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
restore-keys: |
esphome-pip-3.7-
- name: Set up environment
run: script/setup
- name: Install Github Actions annotator
run: pip install pytest-github-actions-annotate-failures
- name: Register problem matchers
run: |
echo "::add-matcher::.github/workflows/matchers/python.json"
- name: Run pytest
run: |
pytest \
-qq \
--durations=10 \
-o console_output_style=count \
tests
pytest -vv --tb=native tests
if: ${{ matrix.id == 'pytest' }}

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

@@ -0,0 +1,100 @@
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:
deploy-docker:
name: Build and publish docker containers
if: github.repository == 'esphome/esphome'
runs-on: ubuntu-latest
strategy:
matrix:
arch: [amd64, armv7, aarch64]
build_type: ["lint"]
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.9'
- name: Set TAG
run: |
echo "TAG=1.1" >> $GITHUB_ENV
- name: Run build
run: |
docker/build.py \
--tag "${TAG}" \
--arch "${{ matrix.arch }}" \
--build-type "${{ matrix.build_type }}" \
build
- name: Log in to docker hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the GitHub container registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Run push
run: |
docker/build.py \
--tag "${TAG}" \
--arch "${{ matrix.arch }}" \
--build-type "${{ matrix.build_type }}" \
push
deploy-docker-manifest:
if: github.repository == 'esphome/esphome'
runs-on: ubuntu-latest
needs: [deploy-docker]
strategy:
matrix:
build_type: ["lint"]
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.9'
- name: Set TAG
run: |
echo "TAG=1.1" >> $GITHUB_ENV
- name: Enable experimental manifest support
run: |
mkdir -p ~/.docker
echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json
- name: Log in to docker hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the GitHub container registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Run manifest
run: |
docker/build.py \
--tag "${TAG}" \
--build-type "${{ matrix.build_type }}" \
manifest

19
.github/workflows/matchers/pytest.json vendored Normal file
View File

@@ -0,0 +1,19 @@
{
"problemMatcher": [
{
"owner": "pytest",
"fileLocation": "absolute",
"pattern": [
{
"regexp": "^\\s+File \"(.*)\", line (\\d+), in (.*)$",
"file": 1,
"line": 2
},
{
"regexp": "^\\s+(.*)$",
"message": 1
}
]
}
]
}

View File

@@ -1,246 +0,0 @@
name: Publish dev releases to docker hub
on:
push:
branches:
- dev
jobs:
# THE LINT/TEST JOBS ARE COPIED FROM ci.yaml
lint-clang-format:
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
steps:
- uses: actions/checkout@v2
# Set up the pio project so that the cpp checks know how files are compiled
# (build flags, libraries etc)
- name: Set up platformio environment
run: pio init --ide atom
- name: Run clang-format
run: script/clang-format -i
- name: Suggest changes
run: script/ci-suggest-changes
lint-clang-tidy:
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
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
strategy:
fail-fast: false
matrix:
split: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v2
# Set up the pio project so that the cpp checks know how files are compiled
# (build flags, libraries etc)
- name: Set up platformio environment
run: pio init --ide atom
- name: Register problem matchers
run: |
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
echo "::add-matcher::.github/workflows/matchers/gcc.json"
- name: Run clang-tidy
run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }}
- name: Suggest changes
run: script/ci-suggest-changes
lint-python:
# Don't use the esphome-lint docker image because it may contain outdated requirements.
# This way, all dependencies are cached via the cache action.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.7'
- name: Cache pip modules
uses: actions/cache@v1
with:
path: ~/.cache/pip
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
restore-keys: |
esphome-pip-3.7-
- name: Set up python environment
run: script/setup
- name: Register problem matchers
run: |
echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
echo "::add-matcher::.github/workflows/matchers/lint-python.json"
echo "::add-matcher::.github/workflows/matchers/python.json"
- name: Lint Custom
run: script/ci-custom.py
- name: Lint Python
run: script/lint-python
- name: Lint CODEOWNERS
run: script/build_codeowners.py --check
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
test:
- test1
- test2
- test3
- test4
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.7'
- name: Cache pip modules
uses: actions/cache@v1
with:
path: ~/.cache/pip
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
restore-keys: |
esphome-pip-3.7-
# Use per test platformio cache because tests have different platform versions
- name: Cache ~/.platformio
uses: actions/cache@v1
with:
path: ~/.platformio
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core_config.py') }}
restore-keys: |
test-home-platformio-${{ matrix.test }}-
- name: Set up environment
run: script/setup
- name: Register problem matchers
run: |
echo "::add-matcher::.github/workflows/matchers/gcc.json"
echo "::add-matcher::.github/workflows/matchers/python.json"
- run: esphome tests/${{ matrix.test }}.yaml compile
pytest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.7'
- name: Cache pip modules
uses: actions/cache@v1
with:
path: ~/.cache/pip
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
restore-keys: |
esphome-pip-3.7-
- name: Set up environment
run: script/setup
- name: Install Github Actions annotator
run: pip install pytest-github-actions-annotate-failures
- name: Register problem matchers
run: |
echo "::add-matcher::.github/workflows/matchers/python.json"
- name: Run pytest
run: |
pytest \
-qq \
--durations=10 \
-o console_output_style=count \
tests
deploy-docker:
name: Build and publish docker containers
if: github.repository == 'esphome/esphome'
runs-on: ubuntu-latest
needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest]
strategy:
matrix:
arch: [amd64, armv7, aarch64]
# Hassio dev image doesn't use esphome/esphome-hassio-$arch and uses base directly
build_type: ["docker"]
steps:
- uses: actions/checkout@v2
- name: Set TAG
run: |
TAG="${GITHUB_SHA:0:7}"
echo "TAG=${TAG}" >> $GITHUB_ENV
- name: Set up env variables
run: |
base_version="3.0.0"
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
build_to="esphome/esphome-hassio-${{ matrix.arch }}"
dockerfile="docker/Dockerfile.hassio"
else
build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}"
build_to="esphome/esphome-${{ matrix.arch }}"
dockerfile="docker/Dockerfile"
fi
echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
- name: Pull for cache
run: |
docker pull "${BUILD_TO}:dev" || true
- name: Register QEMU binfmt
run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes
- run: |
docker build \
--build-arg "BUILD_FROM=${BUILD_FROM}" \
--build-arg "BUILD_VERSION=${TAG}" \
--tag "${BUILD_TO}:${TAG}" \
--tag "${BUILD_TO}:dev" \
--cache-from "${BUILD_TO}:dev" \
--file "${DOCKERFILE}" \
.
- 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 "${BUILD_TO}:${TAG}"
docker push "${BUILD_TO}:dev"
deploy-docker-manifest:
if: github.repository == 'esphome/esphome'
runs-on: ubuntu-latest
needs: [deploy-docker]
steps:
- name: Enable experimental manifest support
run: |
mkdir -p ~/.docker
echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json
- name: Set TAG
run: |
TAG="${GITHUB_SHA:0:7}"
echo "TAG=${TAG}" >> $GITHUB_ENV
- 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}"
- name: "Create the manifest"
run: |
docker manifest create esphome/esphome:${TAG} \
esphome/esphome-aarch64:${TAG} \
esphome/esphome-amd64:${TAG} \
esphome/esphome-armv7:${TAG}
docker manifest push esphome/esphome:${TAG}
docker manifest create esphome/esphome:dev \
esphome/esphome-aarch64:${TAG} \
esphome/esphome-amd64:${TAG} \
esphome/esphome-armv7:${TAG}
docker manifest push esphome/esphome:dev

View File

@@ -1,163 +1,35 @@
name: Publish Release
on:
workflow_dispatch:
release:
types: [published]
schedule:
- cron: "0 2 * * *"
jobs:
# THE LINT/TEST JOBS ARE COPIED FROM ci.yaml
lint-clang-format:
init:
name: Initialize build
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
outputs:
tag: ${{ steps.tag.outputs.tag }}
steps:
- uses: actions/checkout@v2
# Set up the pio project so that the cpp checks know how files are compiled
# (build flags, libraries etc)
- name: Set up platformio environment
run: pio init --ide atom
- name: Run clang-format
run: script/clang-format -i
- name: Suggest changes
run: script/ci-suggest-changes
lint-clang-tidy:
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
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
strategy:
fail-fast: false
matrix:
split: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v2
# Set up the pio project so that the cpp checks know how files are compiled
# (build flags, libraries etc)
- name: Set up platformio environment
run: pio init --ide atom
- name: Register problem matchers
- name: Get tag
id: tag
run: |
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
echo "::add-matcher::.github/workflows/matchers/gcc.json"
- name: Run clang-tidy
run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }}
- name: Suggest changes
run: script/ci-suggest-changes
lint-python:
# Don't use the esphome-lint docker image because it may contain outdated requirements.
# This way, all dependencies are cached via the cache action.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.7'
- name: Cache pip modules
uses: actions/cache@v1
with:
path: ~/.cache/pip
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
restore-keys: |
esphome-pip-3.7-
- name: Set up python environment
run: script/setup
- name: Register problem matchers
run: |
echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
echo "::add-matcher::.github/workflows/matchers/lint-python.json"
echo "::add-matcher::.github/workflows/matchers/python.json"
- name: Lint Custom
run: script/ci-custom.py
- name: Lint Python
run: script/lint-python
- name: Lint CODEOWNERS
run: script/build_codeowners.py --check
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
test:
- test1
- test2
- test3
- test4
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.7'
- name: Cache pip modules
uses: actions/cache@v1
with:
path: ~/.cache/pip
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
restore-keys: |
esphome-pip-3.7-
# Use per test platformio cache because tests have different platform versions
- name: Cache ~/.platformio
uses: actions/cache@v1
with:
path: ~/.platformio
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core_config.py') }}
restore-keys: |
test-home-platformio-${{ matrix.test }}-
- name: Set up environment
run: script/setup
- name: Register problem matchers
run: |
echo "::add-matcher::.github/workflows/matchers/gcc.json"
echo "::add-matcher::.github/workflows/matchers/python.json"
- run: esphome tests/${{ matrix.test }}.yaml compile
pytest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.7'
- name: Cache pip modules
uses: actions/cache@v1
with:
path: ~/.cache/pip
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
restore-keys: |
esphome-pip-3.7-
- name: Set up environment
run: script/setup
- name: Install Github Actions annotator
run: pip install pytest-github-actions-annotate-failures
- name: Register problem matchers
run: |
echo "::add-matcher::.github/workflows/matchers/python.json"
- name: Run pytest
run: |
pytest \
-qq \
--durations=10 \
-o console_output_style=count \
tests
if [[ "$GITHUB_EVENT_NAME" = "release" ]]; then
TAG="${GITHUB_REF#refs/tags/v}"
else
TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p")
today="$(date --utc '+%Y%m%d')"
TAG="${TAG}${today}"
fi
echo "::set-output name=tag::${TAG}"
deploy-pypi:
name: Build and publish to PyPi
if: github.repository == 'esphome/esphome'
needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest]
if: github.repository == 'esphome/esphome' && github.event_name == 'release'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@@ -181,119 +53,85 @@ jobs:
name: Build and publish docker containers
if: github.repository == 'esphome/esphome'
runs-on: ubuntu-latest
needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest]
needs: [init]
strategy:
matrix:
arch: [amd64, armv7, aarch64]
build_type: ["hassio", "docker"]
build_type: ["ha-addon", "docker"]
steps:
- uses: actions/checkout@v2
- name: Set TAG
run: |
TAG="${GITHUB_REF#refs/tags/v}"
echo "TAG=${TAG}" >> $GITHUB_ENV
- name: Set up env variables
run: |
base_version="3.0.0"
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.9'
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
build_to="esphome/esphome-hassio-${{ matrix.arch }}"
dockerfile="docker/Dockerfile.hassio"
else
build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}"
build_to="esphome/esphome-${{ matrix.arch }}"
dockerfile="docker/Dockerfile"
fi
- name: Run build
run: |
docker/build.py \
--tag "${{ needs.init.outputs.tag }}" \
--arch "${{ matrix.arch }}" \
--build-type "${{ matrix.build_type }}" \
build
if [[ "${{ github.event.release.prerelease }}" == "true" ]]; then
cache_tag="beta"
else
cache_tag="latest"
fi
- name: Log in to docker hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the GitHub container registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Set env variables so these values don't need to be calculated again
echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
echo "CACHE_TAG=${cache_tag}" >> $GITHUB_ENV
- name: Pull for cache
run: |
docker pull "${BUILD_TO}:${CACHE_TAG}" || true
- name: Register QEMU binfmt
run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes
- run: |
docker build \
--build-arg "BUILD_FROM=${BUILD_FROM}" \
--build-arg "BUILD_VERSION=${TAG}" \
--tag "${BUILD_TO}:${TAG}" \
--cache-from "${BUILD_TO}:${CACHE_TAG}" \
--file "${DOCKERFILE}" \
.
- 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 "${BUILD_TO}:${TAG}"
# Always publish to beta tag (also full releases)
- name: Publish docker beta tag
run: |
docker tag "${BUILD_TO}:${TAG}" "${BUILD_TO}:beta"
docker push "${BUILD_TO}:beta"
- if: ${{ !github.event.release.prerelease }}
name: Publish docker latest tag
run: |
docker tag "${BUILD_TO}:${TAG}" "${BUILD_TO}:latest"
docker push "${BUILD_TO}:latest"
- name: Run push
run: |
docker/build.py \
--tag "${{ needs.init.outputs.tag }}" \
--arch "${{ matrix.arch }}" \
--build-type "${{ matrix.build_type }}" \
push
deploy-docker-manifest:
if: github.repository == 'esphome/esphome'
runs-on: ubuntu-latest
needs: [deploy-docker]
needs: [init, deploy-docker]
strategy:
matrix:
build_type: ["ha-addon", "docker"]
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.9'
- name: Enable experimental manifest support
run: |
mkdir -p ~/.docker
echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json
- name: Set TAG
run: |
TAG="${GITHUB_REF#refs/tags/v}"
echo "TAG=${TAG}" >> $GITHUB_ENV
- 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}"
- name: "Create the manifest"
run: |
docker manifest create esphome/esphome:${TAG} \
esphome/esphome-aarch64:${TAG} \
esphome/esphome-amd64:${TAG} \
esphome/esphome-armv7:${TAG}
docker manifest push esphome/esphome:${TAG}
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the GitHub container registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Publish docker beta tag
- name: Run manifest
run: |
docker manifest create esphome/esphome:beta \
esphome/esphome-aarch64:${TAG} \
esphome/esphome-amd64:${TAG} \
esphome/esphome-armv7:${TAG}
docker manifest push esphome/esphome:beta
- name: Publish docker latest tag
if: ${{ !github.event.release.prerelease }}
run: |
docker manifest create esphome/esphome:latest \
esphome/esphome-aarch64:${TAG} \
esphome/esphome-amd64:${TAG} \
esphome/esphome-armv7:${TAG}
docker manifest push esphome/esphome:latest
docker/build.py \
--tag "${{ needs.init.outputs.tag }}" \
--build-type "${{ matrix.build_type }}" \
manifest
deploy-hassio-repo:
if: github.repository == 'esphome/esphome'
if: github.repository == 'esphome/esphome' && github.event_name == 'release'
runs-on: ubuntu-latest
needs: [deploy-docker]
steps:
@@ -306,4 +144,4 @@ jobs:
-X POST \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/esphome/hassio/actions/workflows/bump-version.yml/dispatches \
-d "{\"ref\":\"master\",\"inputs\":{\"version\":\"$TAG\"}}"
-d "{\"ref\":\"main\",\"inputs\":{\"version\":\"$TAG\"}}"

6
.gitignore vendored
View File

@@ -13,6 +13,9 @@ __pycache__/
# Intellij Idea
.idea
# Vim
*.swp
# Hide some OS X stuff
.DS_Store
.AppleDouble
@@ -100,6 +103,8 @@ CMakeLists.txt
# CMake
cmake-build-debug/
cmake-build-livingroom8266/
cmake-build-livingroom32/
cmake-build-release/
CMakeCache.txt
@@ -120,4 +125,5 @@ config/
tests/build/
tests/.esphome/
/.temp-clang-tidy.cpp
/.temp/
.pio/

View File

@@ -23,5 +23,5 @@ repos:
- id: no-commit-to-branch
args:
- --branch=dev
- --branch=master
- --branch=release
- --branch=beta

35
.vscode/tasks.json vendored
View File

@@ -1,11 +1,32 @@
{
"version": "2.0.0",
"tasks": [
"version": "2.0.0",
"tasks": [
{
"label": "run",
"type": "shell",
"command": "python3 -m esphome dashboard config/",
"problemMatcher": []
},
{
"label": "clang-tidy",
"type": "shell",
"command": "./script/clang-tidy",
"problemMatcher": [
{
"label": "run",
"type": "shell",
"command": "python3 -m esphome config dashboard",
"problemMatcher": []
"owner": "clang-tidy",
"fileLocation": "absolute",
"pattern": [
{
"regexp": "^(.*):(\\d+):(\\d+):\\s+(error):\\s+(.*) \\[([a-z0-9,\\-]+)\\]\\s*$",
"file": 1,
"line": 2,
"column": 3,
"severity": 4,
"message": 5
}
]
}
]
]
}
]
}

View File

@@ -15,29 +15,46 @@ esphome/components/ac_dimmer/* @glmnet
esphome/components/adc/* @esphome/core
esphome/components/addressable_light/* @justfalter
esphome/components/animation/* @syndlex
esphome/components/anova/* @buxtronix
esphome/components/api/* @OttoWinter
esphome/components/async_tcp/* @OttoWinter
esphome/components/atc_mithermometer/* @ahpohl
esphome/components/b_parasite/* @rbaron
esphome/components/ballu/* @bazuchan
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/color_temperature/* @jesserockz
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/dsmr/* @glmnet @zuidwijk
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/havells_solar/* @sourabhjaiswal
esphome/components/hitachi_ac424/* @sourabhjaiswal
esphome/components/homeassistant/* @OttoWinter
esphome/components/hrxl_maxsonar_wr/* @netmikey
esphome/components/i2c/* @esphome/core
esphome/components/improv/* @jesserockz
esphome/components/inkbird_ibsth1_mini/* @fkirill
esphome/components/inkplate6/* @jesserockz
esphome/components/integration/* @OttoWinter
@@ -58,26 +75,43 @@ 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/nextion/* @senexcrenshaw
esphome/components/nextion/binary_sensor/* @senexcrenshaw
esphome/components/nextion/sensor/* @senexcrenshaw
esphome/components/nextion/switch/* @senexcrenshaw
esphome/components/nextion/text_sensor/* @senexcrenshaw
esphome/components/nfc/* @jesserockz
esphome/components/number/* @esphome/core
esphome/components/ota/* @esphome/core
esphome/components/output/* @esphome/core
esphome/components/pid/* @OttoWinter
esphome/components/pmsa003i/* @sjtrny
esphome/components/pn532/* @OttoWinter @jesserockz
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
esphome/components/pn532_spi/* @OttoWinter @jesserockz
esphome/components/power_supply/* @esphome/core
esphome/components/pulse_meter/* @stevebaxter
esphome/components/pvvx_mithermometer/* @pasiz
esphome/components/rc522/* @glmnet
esphome/components/rc522_i2c/* @glmnet
esphome/components/rc522_spi/* @glmnet
esphome/components/restart/* @esphome/core
esphome/components/rf_bridge/* @jesserockz
esphome/components/rgbct/* @jesserockz
esphome/components/rtttl/* @glmnet
esphome/components/script/* @esphome/core
esphome/components/sdm_meter/* @jesserockz @polyfaces
esphome/components/sdp3x/* @Azimath
esphome/components/selec_meter/* @sourabhjaiswal
esphome/components/select/* @esphome/core
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 +129,19 @@ esphome/components/st7789v/* @kbx81
esphome/components/substitutions/* @esphome/core
esphome/components/sun/* @OttoWinter
esphome/components/switch/* @esphome/core
esphome/components/t6615/* @tylermenezes
esphome/components/tca9548a/* @andreashergert1984
esphome/components/tcl112/* @glmnet
esphome/components/teleinfo/* @0hax
esphome/components/thermostat/* @kbx81
esphome/components/time/* @OttoWinter
esphome/components/tlc5947/* @rnauber
esphome/components/tm1637/* @glmnet
esphome/components/tmp102/* @timsavage
esphome/components/tmp117/* @Azimath
esphome/components/tof10120/* @wstrzalka
esphome/components/toshiba/* @kbx81
esphome/components/tsl2591/* @wjcarpenter
esphome/components/tuya/binary_sensor/* @jesserockz
esphome/components/tuya/climate/* @jesserockz
esphome/components/tuya/sensor/* @jesserockz
@@ -112,3 +153,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,4 +1,4 @@
# ESPHome [![Build Status](https://travis-ci.org/esphome/esphome.svg?branch=master)](https://travis-ci.org/esphome/esphome) [![Discord Chat](https://img.shields.io/discord/429907082951524364.svg)](https://discord.gg/KhAMKrd) [![GitHub release](https://img.shields.io/github/release/esphome/esphome.svg)](https://GitHub.com/esphome/esphome/releases/)
# ESPHome [![Discord Chat](https://img.shields.io/discord/429907082951524364.svg)](https://discord.gg/KhAMKrd) [![GitHub release](https://img.shields.io/github/release/esphome/esphome.svg)](https://GitHub.com/esphome/esphome/releases/)
[![ESPHome Logo](https://esphome.io/_images/logo-text.png)](https://esphome.io/)

View File

@@ -1,10 +1,10 @@
ARG BUILD_FROM=esphome/esphome-base-amd64:3.0.0
ARG BUILD_FROM=esphome/esphome-base:latest
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,13 +1 @@
FROM esphome/esphome-base-amd64:3.0.0
COPY . .
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
python3-wheel \
net-tools \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /workspaces
ENV SHELL /bin/bash
FROM esphome/esphome-lint:1.1

View File

@@ -1,10 +1,10 @@
ARG BUILD_FROM
ARG BUILD_FROM=esphome/esphome-hassio-base:latest
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,9 @@
FROM esphome/esphome-lint-base:3.0.0
ARG BUILD_FROM=esphome/esphome-lint-base:latest
FROM ${BUILD_FROM}
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"]

177
docker/build.py Executable file
View File

@@ -0,0 +1,177 @@
#!/usr/bin/env python3
from dataclasses import dataclass
import subprocess
import argparse
import platform
import shlex
import re
import sys
CHANNEL_DEV = 'dev'
CHANNEL_BETA = 'beta'
CHANNEL_RELEASE = 'release'
CHANNELS = [CHANNEL_DEV, CHANNEL_BETA, CHANNEL_RELEASE]
ARCH_AMD64 = 'amd64'
ARCH_ARMV7 = 'armv7'
ARCH_AARCH64 = 'aarch64'
ARCHS = [ARCH_AMD64, ARCH_ARMV7, ARCH_AARCH64]
TYPE_DOCKER = 'docker'
TYPE_HA_ADDON = 'ha-addon'
TYPE_LINT = 'lint'
TYPES = [TYPE_DOCKER, TYPE_HA_ADDON, TYPE_LINT]
BASE_VERSION = "3.6.0"
parser = argparse.ArgumentParser()
parser.add_argument("--tag", type=str, required=True, help="The main docker tag to push to. If a version number also adds latest and/or beta tag")
parser.add_argument("--arch", choices=ARCHS, required=False, help="The architecture to build for")
parser.add_argument("--build-type", choices=TYPES, required=True, help="The type of build to run")
parser.add_argument("--dry-run", action="store_true", help="Don't run any commands, just print them")
subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True)
build_parser = subparsers.add_parser("build", help="Build the image")
push_parser = subparsers.add_parser("push", help="Tag the already built image and push it to docker hub")
manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images")
# only lists some possibilities, doesn't have to be perfect
# https://stackoverflow.com/a/45125525
UNAME_TO_ARCH = {
"x86_64": ARCH_AMD64,
"aarch64": ARCH_AARCH64,
"aarch64_be": ARCH_AARCH64,
"arm": ARCH_ARMV7,
}
@dataclass(frozen=True)
class DockerParams:
build_from: str
build_to: str
manifest_to: str
dockerfile: str
@classmethod
def for_type_arch(cls, build_type, arch):
prefix = {
TYPE_DOCKER: "esphome/esphome",
TYPE_HA_ADDON: "esphome/esphome-hassio",
TYPE_LINT: "esphome/esphome-lint"
}[build_type]
build_from = f"ghcr.io/{prefix}-base-{arch}:{BASE_VERSION}"
build_to = f"{prefix}-{arch}"
dockerfile = {
TYPE_DOCKER: "docker/Dockerfile",
TYPE_HA_ADDON: "docker/Dockerfile.hassio",
TYPE_LINT: "docker/Dockerfile.lint",
}[build_type]
return cls(
build_from=build_from,
build_to=build_to,
manifest_to=prefix,
dockerfile=dockerfile
)
def main():
args = parser.parse_args()
def run_command(*cmd, ignore_error: bool = False):
print(f"$ {shlex.join(list(cmd))}")
if not args.dry_run:
rc = subprocess.call(list(cmd))
if rc != 0 and not ignore_error:
print("Command failed")
sys.exit(1)
# detect channel from tag
match = re.match(r'^\d+\.\d+(?:\.\d+)?(b\d+)?$', args.tag)
if match is None:
channel = CHANNEL_DEV
elif match.group(1) is None:
channel = CHANNEL_RELEASE
else:
channel = CHANNEL_BETA
tags_to_push = [args.tag]
if channel == CHANNEL_DEV:
tags_to_push.append("dev")
elif channel == CHANNEL_BETA:
tags_to_push.append("beta")
elif channel == CHANNEL_RELEASE:
# Additionally push to beta
tags_to_push.append("beta")
tags_to_push.append("latest")
if args.command == "build":
# 1. pull cache image
params = DockerParams.for_type_arch(args.build_type, args.arch)
cache_tag = {
CHANNEL_DEV: "dev",
CHANNEL_BETA: "beta",
CHANNEL_RELEASE: "latest",
}[channel]
cache_img = f"ghcr.io/{params.build_to}:{cache_tag}"
run_command("docker", "pull", cache_img, ignore_error=True)
# 2. register QEMU binfmt (if not host arch)
is_native = UNAME_TO_ARCH.get(platform.machine()) == args.arch
if not is_native:
run_command(
"docker", "run", "--rm", "--privileged", "multiarch/qemu-user-static:5.2.0-2",
"--reset", "-p", "yes"
)
# 3. build
run_command(
"docker", "build",
"--build-arg", f"BUILD_FROM={params.build_from}",
"--build-arg", f"BUILD_VERSION={args.tag}",
"--tag", f"{params.build_to}:{args.tag}",
"--cache-from", cache_img,
"--file", params.dockerfile,
"."
)
elif args.command == "push":
params = DockerParams.for_type_arch(args.build_type, args.arch)
imgs = [f"{params.build_to}:{tag}" for tag in tags_to_push]
imgs += [f"ghcr.io/{params.build_to}:{tag}" for tag in tags_to_push]
src = imgs[0]
# 1. tag images
for img in imgs[1:]:
run_command(
"docker", "tag", src, img
)
# 2. push images
for img in imgs:
run_command(
"docker", "push", img
)
elif args.command == "manifest":
manifest = DockerParams.for_type_arch(args.build_type, ARCH_AMD64).manifest_to
targets = [f"{manifest}:{tag}" for tag in tags_to_push]
targets += [f"ghcr.io/{manifest}:{tag}" for tag in tags_to_push]
# 1. Create manifests
for target in targets:
cmd = ["docker", "manifest", "create", target]
for arch in ARCHS:
src = f"{DockerParams.for_type_arch(args.build_type, arch).build_to}:{args.tag}"
if target.startswith("ghcr.io"):
src = f"ghcr.io/{src}"
cmd.append(src)
run_command(*cmd)
# 2. Push manifests
for target in targets:
run_command(
"docker", "manifest", "push", target
)
if __name__ == "__main__":
main()

View File

@@ -1,23 +0,0 @@
#!/usr/bin/with-contenv bashio
# ==============================================================================
# Community Hass.io Add-ons: ESPHome
# This files installs the user ESPHome version if specified
# ==============================================================================
declare esphome_version
if bashio::config.has_value 'esphome_version'; then
esphome_version=$(bashio::config 'esphome_version')
if [[ $esphome_version == *":"* ]]; then
IFS=':' read -r -a array <<< "$esphome_version"
username=${array[0]}
ref=${array[1]}
else
username="esphome"
ref=$esphome_version
fi
full_url="https://github.com/${username}/esphome/archive/${ref}.zip"
bashio::log.info "Installing esphome version '${esphome_version}' (${full_url})..."
pip3 install -U --no-cache-dir "${full_url}" \
|| bashio::exit.nok "Failed installing esphome pinned version."
fi

View File

@@ -1,11 +0,0 @@
#!/usr/bin/with-contenv bashio
# ==============================================================================
# Community Hass.io Add-ons: ESPHome
# This files migrates the esphome config directory from the old path
# ==============================================================================
if [[ ! -d /config/esphome && -d /config/esphomeyaml ]]; then
echo "Moving config directory from /config/esphomeyaml to /config/esphome"
mv /config/esphomeyaml /config/esphome
mv /config/esphome/.esphomeyaml /config/esphome/.esphome
fi

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,
@@ -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,101 @@ 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",
"update-all",
],
)
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
deprecated_argv_suggestion = None
if ["dashboard", "config"] == argv[1:3] or ["version"] == argv[1:2]:
# this is most likely meant in new-style arg format. do not try compat parsing
pass
else:
try:
result, unparsed = compat_parser.parse_known_args(argv[1:])
last_option = len(argv) - len(unparsed) - 1 - len(result.configuration)
unparsed = [
"--device" if arg in ("--upload-port", "--serial-port") else arg
for arg in unparsed
]
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.
pass
# 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 +573,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 +700,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 +719,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 +737,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

@@ -192,6 +192,11 @@ class APIClient(threading.Thread):
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._socket.settimeout(10.0)
self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
import ssl
context = ssl.SSLContext()
self._socket = context.wrap_socket(self._socket)
try:
self._socket.connect((ip, self._port))
except OSError as err:

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

875
esphome/boards.py Normal file
View File

@@ -0,0 +1,875 @@
ESP8266_BASE_PINS = {
"A0": 17,
"SS": 15,
"MOSI": 13,
"MISO": 12,
"SCK": 14,
"SDA": 4,
"SCL": 5,
"RX": 3,
"TX": 1,
}
ESP8266_BOARD_PINS = {
"d1": {
"D0": 3,
"D1": 1,
"D2": 16,
"D3": 5,
"D4": 4,
"D5": 14,
"D6": 12,
"D7": 13,
"D8": 0,
"D9": 2,
"D10": 15,
"D11": 13,
"D12": 14,
"D13": 14,
"D14": 4,
"D15": 5,
"LED": 2,
},
"d1_mini": {
"D0": 16,
"D1": 5,
"D2": 4,
"D3": 0,
"D4": 2,
"D5": 14,
"D6": 12,
"D7": 13,
"D8": 15,
"LED": 2,
},
"d1_mini_lite": "d1_mini",
"d1_mini_pro": "d1_mini",
"esp01": {},
"esp01_1m": {},
"esp07": {},
"esp12e": {},
"esp210": {},
"esp8285": {},
"esp_wroom_02": {},
"espduino": {"LED": 16},
"espectro": {"LED": 15, "BUTTON": 2},
"espino": {"LED": 2, "LED_RED": 2, "LED_GREEN": 4, "LED_BLUE": 5, "BUTTON": 0},
"espinotee": {"LED": 16},
"espresso_lite_v1": {"LED": 16},
"espresso_lite_v2": {"LED": 2},
"gen4iod": {},
"heltec_wifi_kit_8": "d1_mini",
"huzzah": {
"LED": 0,
"LED_RED": 0,
"LED_BLUE": 2,
"D4": 4,
"D5": 5,
"D12": 12,
"D13": 13,
"D14": 14,
"D15": 15,
"D16": 16,
},
"inventone": {},
"modwifi": {},
"nodemcu": {
"D0": 16,
"D1": 5,
"D2": 4,
"D3": 0,
"D4": 2,
"D5": 14,
"D6": 12,
"D7": 13,
"D8": 15,
"D9": 3,
"D10": 1,
"LED": 16,
},
"nodemcuv2": "nodemcu",
"oak": {
"P0": 2,
"P1": 5,
"P2": 0,
"P3": 3,
"P4": 1,
"P5": 4,
"P6": 15,
"P7": 13,
"P8": 12,
"P9": 14,
"P10": 16,
"P11": 17,
"LED": 5,
},
"phoenix_v1": {"LED": 16},
"phoenix_v2": {"LED": 2},
"sparkfunBlynk": "thing",
"thing": {"LED": 5, "SDA": 2, "SCL": 14},
"thingdev": "thing",
"wifi_slot": {"LED": 2},
"wifiduino": {
"D0": 3,
"D1": 1,
"D2": 2,
"D3": 0,
"D4": 4,
"D5": 5,
"D6": 16,
"D7": 14,
"D8": 12,
"D9": 13,
"D10": 15,
"D11": 13,
"D12": 12,
"D13": 14,
},
"wifinfo": {
"LED": 12,
"D0": 16,
"D1": 5,
"D2": 4,
"D3": 0,
"D4": 2,
"D5": 14,
"D6": 12,
"D7": 13,
"D8": 15,
"D9": 3,
"D10": 1,
},
"wio_link": {"LED": 2, "GROVE": 15, "D0": 14, "D1": 12, "D2": 13, "BUTTON": 0},
"wio_node": {"LED": 2, "GROVE": 15, "D0": 3, "D1": 5, "BUTTON": 0},
"xinabox_cw01": {"SDA": 2, "SCL": 14, "LED": 5, "LED_RED": 12, "LED_GREEN": 13},
}
FLASH_SIZE_1_MB = 2 ** 20
FLASH_SIZE_512_KB = FLASH_SIZE_1_MB // 2
FLASH_SIZE_2_MB = 2 * FLASH_SIZE_1_MB
FLASH_SIZE_4_MB = 4 * FLASH_SIZE_1_MB
FLASH_SIZE_16_MB = 16 * FLASH_SIZE_1_MB
ESP8266_FLASH_SIZES = {
"d1": FLASH_SIZE_4_MB,
"d1_mini": FLASH_SIZE_4_MB,
"d1_mini_lite": FLASH_SIZE_1_MB,
"d1_mini_pro": FLASH_SIZE_16_MB,
"esp01": FLASH_SIZE_512_KB,
"esp01_1m": FLASH_SIZE_1_MB,
"esp07": FLASH_SIZE_4_MB,
"esp12e": FLASH_SIZE_4_MB,
"esp210": FLASH_SIZE_4_MB,
"esp8285": FLASH_SIZE_1_MB,
"esp_wroom_02": FLASH_SIZE_2_MB,
"espduino": FLASH_SIZE_4_MB,
"espectro": FLASH_SIZE_4_MB,
"espino": FLASH_SIZE_4_MB,
"espinotee": FLASH_SIZE_4_MB,
"espresso_lite_v1": FLASH_SIZE_4_MB,
"espresso_lite_v2": FLASH_SIZE_4_MB,
"gen4iod": FLASH_SIZE_512_KB,
"heltec_wifi_kit_8": FLASH_SIZE_4_MB,
"huzzah": FLASH_SIZE_4_MB,
"inventone": FLASH_SIZE_4_MB,
"modwifi": FLASH_SIZE_2_MB,
"nodemcu": FLASH_SIZE_4_MB,
"nodemcuv2": FLASH_SIZE_4_MB,
"oak": FLASH_SIZE_4_MB,
"phoenix_v1": FLASH_SIZE_4_MB,
"phoenix_v2": FLASH_SIZE_4_MB,
"sparkfunBlynk": FLASH_SIZE_4_MB,
"thing": FLASH_SIZE_512_KB,
"thingdev": FLASH_SIZE_512_KB,
"wifi_slot": FLASH_SIZE_1_MB,
"wifiduino": FLASH_SIZE_4_MB,
"wifinfo": FLASH_SIZE_1_MB,
"wio_link": FLASH_SIZE_4_MB,
"wio_node": FLASH_SIZE_4_MB,
"xinabox_cw01": FLASH_SIZE_4_MB,
}
ESP8266_LD_SCRIPTS = {
FLASH_SIZE_512_KB: ("eagle.flash.512k0.ld", "eagle.flash.512k.ld"),
FLASH_SIZE_1_MB: ("eagle.flash.1m0.ld", "eagle.flash.1m.ld"),
FLASH_SIZE_2_MB: ("eagle.flash.2m.ld", "eagle.flash.2m.ld"),
FLASH_SIZE_4_MB: ("eagle.flash.4m.ld", "eagle.flash.4m.ld"),
FLASH_SIZE_16_MB: ("eagle.flash.16m.ld", "eagle.flash.16m14m.ld"),
}
ESP32_BASE_PINS = {
"TX": 1,
"RX": 3,
"SDA": 21,
"SCL": 22,
"SS": 5,
"MOSI": 23,
"MISO": 19,
"SCK": 18,
"A0": 36,
"A3": 39,
"A4": 32,
"A5": 33,
"A6": 34,
"A7": 35,
"A10": 4,
"A11": 0,
"A12": 2,
"A13": 15,
"A14": 13,
"A15": 12,
"A16": 14,
"A17": 27,
"A18": 25,
"A19": 26,
"T0": 4,
"T1": 0,
"T2": 2,
"T3": 15,
"T4": 13,
"T5": 12,
"T6": 14,
"T7": 27,
"T8": 33,
"T9": 32,
"DAC1": 25,
"DAC2": 26,
"SVP": 36,
"SVN": 39,
}
ESP32_BOARD_PINS = {
"alksesp32": {
"A0": 32,
"A1": 33,
"A2": 25,
"A3": 26,
"A4": 27,
"A5": 14,
"A6": 12,
"A7": 15,
"D0": 40,
"D1": 41,
"D10": 19,
"D11": 21,
"D12": 22,
"D13": 23,
"D2": 15,
"D3": 2,
"D4": 0,
"D5": 4,
"D6": 16,
"D7": 17,
"D8": 5,
"D9": 18,
"DHT_PIN": 26,
"LED": 23,
"L_B": 5,
"L_G": 17,
"L_R": 22,
"L_RGB_B": 16,
"L_RGB_G": 21,
"L_RGB_R": 4,
"L_Y": 23,
"MISO": 22,
"MOSI": 21,
"PHOTO": 25,
"PIEZO1": 19,
"PIEZO2": 18,
"POT1": 32,
"POT2": 33,
"S1": 4,
"S2": 16,
"S3": 18,
"S4": 19,
"S5": 21,
"SCK": 23,
"SCL": 14,
"SDA": 27,
"SS": 19,
"SW1": 15,
"SW2": 2,
"SW3": 0,
},
"bpi-bit": {
"BUTTON_A": 35,
"BUTTON_B": 27,
"BUZZER": 25,
"LIGHT_SENSOR1": 36,
"LIGHT_SENSOR2": 39,
"MPU9250_INT": 0,
"P0": 25,
"P1": 32,
"P10": 26,
"P11": 27,
"P12": 2,
"P13": 18,
"P14": 19,
"P15": 23,
"P16": 5,
"P19": 22,
"P2": 33,
"P20": 21,
"P3": 13,
"P4": 15,
"P5": 35,
"P6": 12,
"P7": 14,
"P8": 16,
"P9": 17,
"RGB_LED": 4,
"TEMPERATURE_SENSOR": 34,
},
"d-duino-32": {
"D1": 5,
"D10": 1,
"D2": 4,
"D3": 0,
"D4": 2,
"D5": 14,
"D6": 12,
"D7": 13,
"D8": 15,
"D9": 3,
"MISO": 12,
"MOSI": 13,
"SCK": 14,
"SCL": 4,
"SDA": 5,
"SS": 15,
},
"esp-wrover-kit": {},
"esp32-devkitlipo": {},
"esp32-evb": {
"BUTTON": 34,
"MISO": 15,
"MOSI": 2,
"SCK": 14,
"SCL": 16,
"SDA": 13,
"SS": 17,
},
"esp32-gateway": {"BUTTON": 34, "LED": 33, "SCL": 16, "SDA": 32},
"esp32-poe-iso": {
"BUTTON": 34,
"MISO": 15,
"MOSI": 2,
"SCK": 14,
"SCL": 16,
"SDA": 13,
},
"esp32-poe": {"BUTTON": 34, "MISO": 15, "MOSI": 2, "SCK": 14, "SCL": 16, "SDA": 13},
"esp32-pro": {
"BUTTON": 34,
"MISO": 15,
"MOSI": 2,
"SCK": 14,
"SCL": 16,
"SDA": 13,
"SS": 17,
},
"esp320": {
"LED": 5,
"MISO": 12,
"MOSI": 13,
"SCK": 14,
"SCL": 14,
"SDA": 2,
"SS": 15,
},
"esp32cam": {},
"esp32dev": {},
"esp32doit-devkit-v1": {"LED": 2},
"esp32thing": {"BUTTON": 0, "LED": 5, "SS": 2},
"esp32vn-iot-uno": {},
"espea32": {"BUTTON": 0, "LED": 5},
"espectro32": {"LED": 15, "SD_SS": 33},
"espino32": {"BUTTON": 0, "LED": 16},
"featheresp32": {
"A0": 26,
"A1": 25,
"A10": 27,
"A11": 12,
"A12": 13,
"A13": 35,
"A2": 34,
"A4": 36,
"A5": 4,
"A6": 14,
"A7": 32,
"A8": 15,
"A9": 33,
"Ax": 2,
"LED": 13,
"MOSI": 18,
"RX": 16,
"SCK": 5,
"SDA": 23,
"SS": 33,
"TX": 17,
},
"firebeetle32": {"LED": 2},
"fm-devkit": {
"D0": 34,
"D1": 35,
"D10": 0,
"D2": 32,
"D3": 33,
"D4": 27,
"D5": 14,
"D6": 12,
"D7": 13,
"D8": 15,
"D9": 23,
"I2S_DOUT": 22,
"I2S_LRCLK": 25,
"I2S_MCLK": 2,
"I2S_SCLK": 26,
"LED": 5,
"SCL": 17,
"SDA": 16,
"SW1": 4,
"SW2": 18,
"SW3": 19,
"SW4": 21,
},
"frogboard": {},
"heltec_wifi_kit_32": {
"A1": 37,
"A2": 38,
"BUTTON": 0,
"LED": 25,
"RST_OLED": 16,
"SCL_OLED": 15,
"SDA_OLED": 4,
"Vext": 21,
},
"heltec_wifi_lora_32": {
"BUTTON": 0,
"DIO0": 26,
"DIO1": 33,
"DIO2": 32,
"LED": 25,
"MOSI": 27,
"RST_LoRa": 14,
"RST_OLED": 16,
"SCK": 5,
"SCL_OLED": 15,
"SDA_OLED": 4,
"SS": 18,
"Vext": 21,
},
"heltec_wifi_lora_32_V2": {
"BUTTON": 0,
"DIO0": 26,
"DIO1": 35,
"DIO2": 34,
"LED": 25,
"MOSI": 27,
"RST_LoRa": 14,
"RST_OLED": 16,
"SCK": 5,
"SCL_OLED": 15,
"SDA_OLED": 4,
"SS": 18,
"Vext": 21,
},
"heltec_wireless_stick": {
"BUTTON": 0,
"DIO0": 26,
"DIO1": 35,
"DIO2": 34,
"LED": 25,
"MOSI": 27,
"RST_LoRa": 14,
"RST_OLED": 16,
"SCK": 5,
"SCL_OLED": 15,
"SDA_OLED": 4,
"SS": 18,
"Vext": 21,
},
"hornbill32dev": {"BUTTON": 0, "LED": 13},
"hornbill32minima": {"SS": 2},
"intorobot": {
"A1": 39,
"A2": 35,
"A3": 25,
"A4": 26,
"A5": 14,
"A6": 12,
"A7": 15,
"A8": 13,
"A9": 2,
"BUTTON": 0,
"D0": 19,
"D1": 23,
"D2": 18,
"D3": 17,
"D4": 16,
"D5": 5,
"D6": 4,
"LED": 4,
"MISO": 17,
"MOSI": 16,
"RGB_B_BUILTIN": 22,
"RGB_G_BUILTIN": 21,
"RGB_R_BUILTIN": 27,
"SCL": 19,
"SDA": 23,
"T0": 19,
"T1": 23,
"T2": 18,
"T3": 17,
"T4": 16,
"T5": 5,
"T6": 4,
},
"iotaap_magnolia": {},
"iotbusio": {},
"iotbusproteus": {},
"lolin32": {"LED": 5},
"lolin32_lite": {"LED": 22},
"lolin_d32": {"LED": 5, "_VBAT": 35},
"lolin_d32_pro": {"LED": 5, "_VBAT": 35},
"lopy": {
"A1": 37,
"A2": 38,
"LED": 0,
"MISO": 37,
"MOSI": 22,
"SCK": 13,
"SCL": 13,
"SDA": 12,
"SS": 17,
},
"lopy4": {
"A1": 37,
"A2": 38,
"LED": 0,
"MISO": 37,
"MOSI": 22,
"SCK": 13,
"SCL": 13,
"SDA": 12,
"SS": 18,
},
"m5stack-core-esp32": {
"ADC1": 35,
"ADC2": 36,
"G0": 0,
"G1": 1,
"G12": 12,
"G13": 13,
"G15": 15,
"G16": 16,
"G17": 17,
"G18": 18,
"G19": 19,
"G2": 2,
"G21": 21,
"G22": 22,
"G23": 23,
"G25": 25,
"G26": 26,
"G3": 3,
"G34": 34,
"G35": 35,
"G36": 36,
"G5": 5,
"RXD2": 16,
"TXD2": 17,
},
"m5stack-fire": {
"ADC1": 35,
"ADC2": 36,
"G0": 0,
"G1": 1,
"G12": 12,
"G13": 13,
"G15": 15,
"G16": 16,
"G17": 17,
"G18": 18,
"G19": 19,
"G2": 2,
"G21": 21,
"G22": 22,
"G23": 23,
"G25": 25,
"G26": 26,
"G3": 3,
"G34": 34,
"G35": 35,
"G36": 36,
"G5": 5,
},
"m5stack-grey": {
"ADC1": 35,
"ADC2": 36,
"G0": 0,
"G1": 1,
"G12": 12,
"G13": 13,
"G15": 15,
"G16": 16,
"G17": 17,
"G18": 18,
"G19": 19,
"G2": 2,
"G21": 21,
"G22": 22,
"G23": 23,
"G25": 25,
"G26": 26,
"G3": 3,
"G34": 34,
"G35": 35,
"G36": 36,
"G5": 5,
"RXD2": 16,
"TXD2": 17,
},
"m5stick-c": {
"ADC1": 35,
"ADC2": 36,
"G0": 0,
"G10": 10,
"G26": 26,
"G32": 32,
"G33": 33,
"G36": 36,
"G37": 37,
"G39": 39,
"G9": 9,
"MISO": 36,
"MOSI": 15,
"SCK": 13,
"SCL": 33,
"SDA": 32,
},
"magicbit": {
"BLUE_LED": 17,
"BUZZER": 25,
"GREEN_LED": 16,
"LDR": 36,
"LED": 16,
"LEFT_BUTTON": 35,
"MOTOR1A": 27,
"MOTOR1B": 18,
"MOTOR2A": 16,
"MOTOR2B": 17,
"POT": 39,
"RED_LED": 27,
"RIGHT_PUTTON": 34,
"YELLOW_LED": 18,
},
"mhetesp32devkit": {"LED": 2},
"mhetesp32minikit": {"LED": 2},
"microduino-core-esp32": {
"A0": 12,
"A1": 13,
"A10": 25,
"A11": 26,
"A12": 27,
"A13": 14,
"A2": 15,
"A3": 4,
"A6": 38,
"A7": 37,
"A8": 32,
"A9": 33,
"D0": 3,
"D1": 1,
"D10": 5,
"D11": 23,
"D12": 19,
"D13": 18,
"D14": 12,
"D15": 13,
"D16": 15,
"D17": 4,
"D18": 22,
"D19": 21,
"D2": 16,
"D20": 38,
"D21": 37,
"D3": 17,
"D4": 32,
"D5": 33,
"D6": 25,
"D7": 26,
"D8": 27,
"D9": 14,
"SCL": 21,
"SCL1": 13,
"SDA": 22,
"SDA1": 12,
},
"nano32": {"BUTTON": 0, "LED": 16},
"nina_w10": {
"D0": 3,
"D1": 1,
"D10": 5,
"D11": 19,
"D12": 23,
"D13": 18,
"D14": 13,
"D15": 12,
"D16": 32,
"D17": 33,
"D18": 21,
"D19": 34,
"D2": 26,
"D20": 36,
"D21": 39,
"D3": 25,
"D4": 35,
"D5": 27,
"D6": 22,
"D7": 0,
"D8": 15,
"D9": 14,
"LED_BLUE": 21,
"LED_GREEN": 33,
"LED_RED": 23,
"SCL": 13,
"SDA": 12,
"SW1": 33,
"SW2": 27,
},
"node32s": {},
"nodemcu-32s": {"BUTTON": 0, "LED": 2},
"odroid_esp32": {"ADC1": 35, "ADC2": 36, "LED": 2, "SCL": 4, "SDA": 15, "SS": 22},
"onehorse32dev": {"A1": 37, "A2": 38, "BUTTON": 0, "LED": 5},
"oroca_edubot": {
"A0": 34,
"A1": 39,
"A2": 36,
"A3": 33,
"D0": 4,
"D1": 16,
"D2": 17,
"D3": 22,
"D4": 23,
"D5": 5,
"D6": 18,
"D7": 19,
"D8": 33,
"LED": 13,
"MOSI": 18,
"RX": 16,
"SCK": 5,
"SDA": 23,
"SS": 2,
"TX": 17,
"VBAT": 35,
},
"pico32": {},
"pocket_32": {"LED": 16},
"pycom_gpy": {
"A1": 37,
"A2": 38,
"LED": 0,
"MISO": 37,
"MOSI": 22,
"SCK": 13,
"SCL": 13,
"SDA": 12,
"SS": 17,
},
"quantum": {},
"sparkfun_lora_gateway_1-channel": {"MISO": 12, "MOSI": 13, "SCK": 14, "SS": 16},
"tinypico": {},
"ttgo-lora32-v1": {
"A1": 37,
"A2": 38,
"BUTTON": 0,
"LED": 2,
"MOSI": 27,
"SCK": 5,
"SS": 18,
},
"ttgo-t-beam": {"BUTTON": 39, "LED": 14, "MOSI": 27, "SCK": 5, "SS": 18},
"ttgo-t-watch": {"BUTTON": 36, "MISO": 2, "MOSI": 15, "SCK": 14, "SS": 13},
"ttgo-t1": {"LED": 22, "MISO": 2, "MOSI": 15, "SCK": 14, "SCL": 23, "SS": 13},
"ttgo-t7-v13-mini32": {"LED": 22},
"ttgo-t7-v14-mini32": {"LED": 19},
"turta_iot_node": {},
"vintlabs-devkit-v1": {
"LED": 2,
"PWM0": 12,
"PWM1": 13,
"PWM2": 14,
"PWM3": 15,
"PWM4": 16,
"PWM5": 17,
"PWM6": 18,
"PWM7": 19,
},
"wemos_d1_mini32": {
"D0": 26,
"D1": 22,
"D2": 21,
"D3": 17,
"D4": 16,
"D5": 18,
"D6": 19,
"D7": 23,
"D8": 5,
"LED": 2,
"RXD": 3,
"TXD": 1,
"_VBAT": 35,
},
"wemosbat": {"LED": 16},
"wesp32": {"MISO": 32, "SCL": 4, "SDA": 15},
"widora-air": {
"A1": 39,
"A2": 35,
"A3": 25,
"A4": 26,
"A5": 14,
"A6": 12,
"A7": 15,
"A8": 13,
"A9": 2,
"BUTTON": 0,
"D0": 19,
"D1": 23,
"D2": 18,
"D3": 17,
"D4": 16,
"D5": 5,
"D6": 4,
"LED": 25,
"MISO": 17,
"MOSI": 16,
"SCL": 19,
"SDA": 23,
"T0": 19,
"T1": 23,
"T2": 18,
"T3": 17,
"T4": 16,
"T5": 5,
"T6": 4,
},
"xinabox_cw02": {"LED": 27},
}
ESP32_C3_BASE_PINS = {
"TX": 21,
"RX": 20,
"ADC1_0": 0,
"ADC1_1": 1,
"ADC1_2": 2,
"ADC1_3": 3,
"ADC1_4": 4,
"ADC2_0": 5,
}
ESP32_C3_BOARD_PINS = {
"esp32-c3-devkitm-1": {"LED": 8},
"esp32-c3-devkitc-02": "esp32-c3-devkitm-1",
}

View File

@@ -19,6 +19,7 @@ from esphome.cpp_generator import ( # noqa
Statement,
LineComment,
progmem_array,
static_const_array,
statement,
variable,
new_variable,
@@ -59,6 +60,7 @@ from esphome.cpp_types import ( # noqa
uint8,
uint16,
uint32,
uint64,
int32,
const_char_ptr,
NAN,

View File

@@ -4,7 +4,7 @@
namespace esphome {
namespace a4988 {
static const char *TAG = "a4988.stepper";
static const char *const TAG = "a4988.stepper";
void A4988::setup() {
ESP_LOGCONFIG(TAG, "Setting up A4988...");

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

@@ -9,15 +9,15 @@
namespace esphome {
namespace ac_dimmer {
static const char *TAG = "ac_dimmer";
static const char *const TAG = "ac_dimmer";
// Global array to store dimmer objects
static AcDimmerDataStore *all_dimmers[32];
static AcDimmerDataStore *all_dimmers[32]; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
/// Time in microseconds the gate should be held high
/// 10µs should be long enough for most triacs
/// For reference: BT136 datasheet says 2µs nominal (page 7)
static uint32_t GATE_ENABLE_TIME = 10;
static const uint32_t GATE_ENABLE_TIME = 10;
/// Function called from timer interrupt
/// Input is current time in microseconds (micros())
@@ -125,7 +125,7 @@ void ICACHE_RAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
}
void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store) {
// Attaching pin interrupts on the same pin will override the previous interupt
// Attaching pin interrupts on the same pin will override the previous interrupt
// However, the user expects that multiple dimmers sharing the same ZC pin will work.
// We solve this in a bit of a hacky way: On each pin interrupt, we check all dimmers
// if any of them are using the same ZC pin, and also trigger the interrupt for *them*.

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

@@ -4,7 +4,7 @@
namespace esphome {
namespace adalight {
static const char *TAG = "adalight_light_effect";
static const char *const TAG = "adalight_light_effect";
static const uint32_t ADALIGHT_ACK_INTERVAL = 1000;
static const uint32_t ADALIGHT_RECEIVE_TIMEOUT = 1000;
@@ -42,7 +42,7 @@ void AdalightLightEffect::reset_frame_(light::AddressableLight &it) {
void AdalightLightEffect::blank_all_leds_(light::AddressableLight &it) {
for (int led = it.size(); led-- > 0;) {
it[led].set(COLOR_BLACK);
it[led].set(Color::BLACK);
}
}

View File

@@ -8,10 +8,50 @@ ADC_MODE(ADC_VCC)
namespace esphome {
namespace adc {
static const char *TAG = "adc";
static const char *const TAG = "adc";
#ifdef ARDUINO_ARCH_ESP32
void ADCSensor::set_attenuation(adc_attenuation_t attenuation) { this->attenuation_ = attenuation; }
void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; }
inline adc1_channel_t gpio_to_adc1(uint8_t pin) {
#if CONFIG_IDF_TARGET_ESP32
switch (pin) {
case 36:
return ADC1_CHANNEL_0;
case 37:
return ADC1_CHANNEL_1;
case 38:
return ADC1_CHANNEL_2;
case 39:
return ADC1_CHANNEL_3;
case 32:
return ADC1_CHANNEL_4;
case 33:
return ADC1_CHANNEL_5;
case 34:
return ADC1_CHANNEL_6;
case 35:
return ADC1_CHANNEL_7;
default:
return ADC1_CHANNEL_MAX;
}
#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2
switch (pin) {
case 0:
return ADC1_CHANNEL_0;
case 1:
return ADC1_CHANNEL_1;
case 2:
return ADC1_CHANNEL_2;
case 3:
return ADC1_CHANNEL_3;
case 4:
return ADC1_CHANNEL_4;
default:
return ADC1_CHANNEL_MAX;
}
#endif
}
#endif
void ADCSensor::setup() {
@@ -21,7 +61,11 @@ void ADCSensor::setup() {
#endif
#ifdef ARDUINO_ARCH_ESP32
analogSetPinAttenuation(this->pin_, this->attenuation_);
adc1_config_channel_atten(gpio_to_adc1(pin_), attenuation_);
adc1_config_width(ADC_WIDTH_BIT_12);
#if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2
adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_));
#endif
#endif
}
void ADCSensor::dump_config() {
@@ -36,18 +80,20 @@ void ADCSensor::dump_config() {
#ifdef ARDUINO_ARCH_ESP32
ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_);
switch (this->attenuation_) {
case ADC_0db:
case ADC_ATTEN_DB_0:
ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)");
break;
case ADC_2_5db:
case ADC_ATTEN_DB_2_5:
ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)");
break;
case ADC_6db:
case ADC_ATTEN_DB_6:
ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)");
break;
case ADC_11db:
case ADC_ATTEN_DB_11:
ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)");
break;
default: // This is to satisfy the unused ADC_ATTEN_MAX
break;
}
#endif
LOG_UPDATE_INTERVAL(this);
@@ -60,21 +106,43 @@ void ADCSensor::update() {
}
float ADCSensor::sample() {
#ifdef ARDUINO_ARCH_ESP32
float value_v = analogRead(this->pin_) / 4095.0f; // NOLINT
int raw = adc1_get_raw(gpio_to_adc1(pin_));
float value_v = raw / 4095.0f;
#if CONFIG_IDF_TARGET_ESP32
switch (this->attenuation_) {
case ADC_0db:
case ADC_ATTEN_DB_0:
value_v *= 1.1;
break;
case ADC_2_5db:
case ADC_ATTEN_DB_2_5:
value_v *= 1.5;
break;
case ADC_6db:
case ADC_ATTEN_DB_6:
value_v *= 2.2;
break;
case ADC_11db:
case ADC_ATTEN_DB_11:
value_v *= 3.9;
break;
default: // This is to satisfy the unused ADC_ATTEN_MAX
break;
}
#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2
switch (this->attenuation_) {
case ADC_ATTEN_DB_0:
value_v *= 0.84;
break;
case ADC_ATTEN_DB_2_5:
value_v *= 1.13;
break;
case ADC_ATTEN_DB_6:
value_v *= 1.56;
break;
case ADC_ATTEN_DB_11:
value_v *= 3.0;
break;
default: // This is to satisfy the unused ADC_ATTEN_MAX
break;
}
#endif
return value_v;
#endif

View File

@@ -6,6 +6,10 @@
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/voltage_sampler/voltage_sampler.h"
#ifdef ARDUINO_ARCH_ESP32
#include "driver/adc.h"
#endif
namespace esphome {
namespace adc {
@@ -13,7 +17,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
public:
#ifdef ARDUINO_ARCH_ESP32
/// Set the attenuation for this pin. Only available on the ESP32.
void set_attenuation(adc_attenuation_t attenuation);
void set_attenuation(adc_atten_t attenuation);
#endif
/// Update adc values.
@@ -34,7 +38,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
uint8_t pin_;
#ifdef ARDUINO_ARCH_ESP32
adc_attenuation_t attenuation_{ADC_0db};
adc_atten_t attenuation_{ADC_ATTEN_DB_0};
#endif
};

View File

@@ -7,7 +7,7 @@ from esphome.const import (
CONF_ID,
CONF_PIN,
DEVICE_CLASS_VOLTAGE,
ICON_EMPTY,
STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
)
@@ -15,10 +15,10 @@ from esphome.const import (
AUTO_LOAD = ["voltage_sampler"]
ATTENUATION_MODES = {
"0db": cg.global_ns.ADC_0db,
"2.5db": cg.global_ns.ADC_2_5db,
"6db": cg.global_ns.ADC_6db,
"11db": cg.global_ns.ADC_11db,
"0db": cg.global_ns.ADC_ATTEN_DB_0,
"2.5db": cg.global_ns.ADC_ATTEN_DB_2_5,
"6db": cg.global_ns.ADC_ATTEN_DB_6,
"11db": cg.global_ns.ADC_ATTEN_DB_11,
}
@@ -35,7 +35,12 @@ ADCSensor = adc_ns.class_(
)
CONFIG_SCHEMA = (
sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE)
sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
)
.extend(
{
cv.GenerateID(): cv.declare_id(ADCSensor),
@@ -49,10 +54,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

@@ -4,7 +4,7 @@
namespace esphome {
namespace addressable_light {
static const char* TAG = "addressable_light.display";
static const char *const TAG = "addressable_light.display";
int AddressableLightDisplay::get_width_internal() { return this->width_; }
int AddressableLightDisplay::get_height_internal() { return this->height_; }
@@ -24,7 +24,7 @@ void AddressableLightDisplay::update() {
void AddressableLightDisplay::display() {
bool dirty = false;
uint8_t old_r, old_g, old_b, old_w;
Color* c;
Color *c;
for (uint32_t offset = 0; offset < this->addressable_light_buffer_.size(); offset++) {
c = &(this->addressable_light_buffer_[offset]);

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

@@ -4,7 +4,7 @@
namespace esphome {
namespace ade7953 {
static const char *TAG = "ade7953";
static const char *const TAG = "ade7953";
void ADE7953::dump_config() {
ESP_LOGCONFIG(TAG, "ADE7953:");
@@ -21,8 +21,8 @@ void ADE7953::dump_config() {
}
#define ADE_PUBLISH_(name, factor) \
if (name && this->name##_sensor_) { \
float value = *name / factor; \
if ((name) && this->name##_sensor_) { \
float value = *(name) / (factor); \
this->name##_sensor_->publish_state(value); \
}
#define ADE_PUBLISH(name, factor) ADE_PUBLISH_(name, factor)

View File

@@ -8,7 +8,7 @@ from esphome.const import (
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_POWER,
DEVICE_CLASS_VOLTAGE,
ICON_EMPTY,
STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
UNIT_AMPERE,
UNIT_WATT,
@@ -31,19 +31,34 @@ 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_of_measurement=UNIT_VOLT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CURRENT_A): sensor.sensor_schema(
UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CURRENT_B): sensor.sensor_schema(
UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ACTIVE_POWER_A): sensor.sensor_schema(
UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER
unit_of_measurement=UNIT_WATT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ACTIVE_POWER_B): sensor.sensor_schema(
UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER
unit_of_measurement=UNIT_WATT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
@@ -52,10 +67,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 +85,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

@@ -4,7 +4,7 @@
namespace esphome {
namespace ads1115 {
static const char *TAG = "ads1115";
static const char *const TAG = "ads1115";
static const uint8_t ADS1115_REGISTER_CONVERSION = 0x00;
static const uint8_t ADS1115_REGISTER_CONFIG = 0x01;
@@ -64,11 +64,6 @@ void ADS1115Component::setup() {
return;
}
this->prev_config_ = config;
for (auto *sensor : this->sensors_) {
this->set_interval(sensor->get_name(), sensor->update_interval(),
[this, sensor] { this->request_measurement(sensor); });
}
}
void ADS1115Component::dump_config() {
ESP_LOGCONFIG(TAG, "Setting up ADS1115...");
@@ -107,17 +102,22 @@ float ADS1115Component::request_measurement(ADS1115Sensor *sensor) {
}
this->prev_config_ = config;
// about 1.6 ms with 860 samples per second
// about 1.2 ms with 860 samples per second
delay(2);
uint32_t start = millis();
while (this->read_byte_16(ADS1115_REGISTER_CONFIG, &config) && (config >> 15) == 0) {
if (millis() - start > 100) {
ESP_LOGW(TAG, "Reading ADS1115 timed out");
this->status_set_warning();
return NAN;
// in continuous mode, conversion will always be running, rely on the delay
// to ensure conversion is taking place with the correct settings
// can we use the rdy pin to trigger when a conversion is done?
if (!this->continuous_mode_) {
uint32_t start = millis();
while (this->read_byte_16(ADS1115_REGISTER_CONFIG, &config) && (config >> 15) == 0) {
if (millis() - start > 100) {
ESP_LOGW(TAG, "Reading ADS1115 timed out");
this->status_set_warning();
return NAN;
}
yield();
}
yield();
}
}

View File

@@ -5,7 +5,7 @@ from esphome.const import (
CONF_GAIN,
CONF_MULTIPLEXER,
DEVICE_CLASS_VOLTAGE,
ICON_EMPTY,
STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
CONF_ID,
)
@@ -51,7 +51,12 @@ 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_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
)
.extend(
{
cv.GenerateID(): cv.declare_id(ADS1115Sensor),
@@ -64,11 +69,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

@@ -10,7 +10,7 @@
//
// According to the datasheet, the component is supposed to respond in more than 75ms. In fact, it can answer almost
// immediately for temperature. But for humidity, it takes >90ms to get a valid data. From experience, we have best
// results making successive requests; the current implementation make 3 attemps with a delay of 30ms each time.
// results making successive requests; the current implementation makes 3 attempts with a delay of 30ms each time.
#include "aht10.h"
#include "esphome/core/log.h"
@@ -18,12 +18,12 @@
namespace esphome {
namespace aht10 {
static const char *TAG = "aht10";
static const char *const TAG = "aht10";
static const uint8_t AHT10_CALIBRATE_CMD[] = {0xE1};
static const uint8_t AHT10_MEASURE_CMD[] = {0xAC, 0x33, 0x00};
static const uint8_t AHT10_DEFAULT_DELAY = 5; // ms, for calibration and temperature measurement
static const uint8_t AHT10_HUMIDITY_DELAY = 30; // ms
static const uint8_t AHT10_ATTEMPS = 3; // safety margin, normally 3 attemps are enough: 3*30=90ms
static const uint8_t AHT10_ATTEMPTS = 3; // safety margin, normally 3 attempts are enough: 3*30=90ms
void AHT10Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up AHT10...");
@@ -58,8 +58,9 @@ void AHT10Component::update() {
uint8_t delay = AHT10_DEFAULT_DELAY;
if (this->humidity_sensor_ != nullptr)
delay = AHT10_HUMIDITY_DELAY;
for (int i = 0; i < AHT10_ATTEMPS; ++i) {
ESP_LOGVV(TAG, "Attemps %u at %6ld", i, millis());
for (int i = 0; i < AHT10_ATTEMPTS; ++i) {
ESP_LOGVV(TAG, "Attempt %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

@@ -7,7 +7,7 @@ from esphome.const import (
CONF_TEMPERATURE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
ICON_EMPTY,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
)
@@ -22,10 +22,16 @@ CONFIG_SCHEMA = (
{
cv.GenerateID(): cv.declare_id(AHT10Component),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
UNIT_CELSIUS, ICON_EMPTY, 2, DEVICE_CLASS_TEMPERATURE
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
UNIT_PERCENT, ICON_EMPTY, 2, DEVICE_CLASS_HUMIDITY
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
@@ -34,15 +40,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

@@ -9,7 +9,7 @@
namespace esphome {
namespace am2320 {
static const char *TAG = "am2320";
static const char *const TAG = "am2320";
// ---=== Calc CRC16 ===---
uint16_t crc_16(uint8_t *ptr, uint8_t length) {

View File

@@ -7,8 +7,8 @@ from esphome.const import (
CONF_TEMPERATURE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
ICON_EMPTY,
UNIT_PERCENT,
)
@@ -24,10 +24,16 @@ CONFIG_SCHEMA = (
{
cv.GenerateID(): cv.declare_id(AM2320Component),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
@@ -36,15 +42,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

@@ -0,0 +1,150 @@
#include "anova.h"
#include "esphome/core/log.h"
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace anova {
static const char *TAG = "anova";
using namespace esphome::climate;
void Anova::dump_config() { LOG_CLIMATE("", "Anova BLE Cooker", this); }
void Anova::setup() {
this->codec_ = new AnovaCodec();
this->current_request_ = 0;
}
void Anova::loop() {}
void Anova::control(const ClimateCall &call) {
if (call.get_mode().has_value()) {
ClimateMode mode = *call.get_mode();
AnovaPacket *pkt;
switch (mode) {
case climate::CLIMATE_MODE_OFF:
pkt = this->codec_->get_stop_request();
break;
case climate::CLIMATE_MODE_HEAT:
pkt = this->codec_->get_start_request();
break;
default:
ESP_LOGW(TAG, "Unsupported mode: %d", mode);
return;
}
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status)
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
}
if (call.get_target_temperature().has_value()) {
auto pkt = this->codec_->get_set_target_temp_request(*call.get_target_temperature());
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status)
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
}
}
void Anova::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_DISCONNECT_EVT: {
this->current_temperature = NAN;
this->target_temperature = NAN;
this->publish_state();
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
auto chr = this->parent_->get_characteristic(ANOVA_SERVICE_UUID, ANOVA_CHARACTERISTIC_UUID);
if (chr == nullptr) {
ESP_LOGW(TAG, "[%s] No control service found at device, not an Anova..?", this->get_name().c_str());
ESP_LOGW(TAG, "[%s] Note, this component does not currently support Anova Nano.", this->get_name().c_str());
break;
}
this->char_handle_ = chr->handle;
auto status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, chr->handle);
if (status) {
ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status);
}
break;
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
this->node_state = espbt::ClientState::Established;
this->current_request_ = 0;
this->update();
break;
}
case ESP_GATTC_NOTIFY_EVT: {
if (param->notify.handle != this->char_handle_)
break;
this->codec_->decode(param->notify.value, param->notify.value_len);
if (this->codec_->has_target_temp()) {
this->target_temperature = this->codec_->target_temp_;
}
if (this->codec_->has_current_temp()) {
this->current_temperature = this->codec_->current_temp_;
}
if (this->codec_->has_running()) {
this->mode = this->codec_->running_ ? climate::CLIMATE_MODE_HEAT : climate::CLIMATE_MODE_OFF;
}
if (this->codec_->has_unit()) {
this->fahrenheit_ = (this->codec_->unit_ == 'f');
ESP_LOGD(TAG, "Anova units is %s", this->fahrenheit_ ? "fahrenheit" : "celcius");
this->current_request_++;
}
this->publish_state();
if (this->current_request_ > 1) {
AnovaPacket *pkt = nullptr;
switch (this->current_request_++) {
case 2:
pkt = this->codec_->get_read_target_temp_request();
break;
case 3:
pkt = this->codec_->get_read_current_temp_request();
break;
default:
this->current_request_ = 1;
break;
}
if (pkt != nullptr) {
auto status =
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, pkt->length,
pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status)
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(),
status);
}
}
break;
}
default:
break;
}
}
void Anova::set_unit_of_measurement(const char *unit) { this->fahrenheit_ = !strncmp(unit, "f", 1); }
void Anova::update() {
if (this->node_state != espbt::ClientState::Established)
return;
if (this->current_request_ < 2) {
auto pkt = this->codec_->get_read_device_status_request();
if (this->current_request_ == 0)
auto pkt = this->codec_->get_set_unit_request(this->fahrenheit_ ? 'f' : 'c');
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status)
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
this->current_request_++;
}
}
} // namespace anova
} // namespace esphome
#endif

View File

@@ -0,0 +1,52 @@
#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/climate/climate.h"
#include "anova_base.h"
#ifdef ARDUINO_ARCH_ESP32
#include <esp_gattc_api.h>
namespace esphome {
namespace anova {
namespace espbt = esphome::esp32_ble_tracker;
static const uint16_t ANOVA_SERVICE_UUID = 0xFFE0;
static const uint16_t ANOVA_CHARACTERISTIC_UUID = 0xFFE1;
class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode, public PollingComponent {
public:
void setup() override;
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; }
climate::ClimateTraits traits() {
auto traits = climate::ClimateTraits();
traits.set_supports_current_temperature(true);
traits.set_supports_heat_mode(true);
traits.set_visual_min_temperature(25.0);
traits.set_visual_max_temperature(100.0);
traits.set_visual_temperature_step(0.1);
return traits;
}
void set_unit_of_measurement(const char *);
protected:
AnovaCodec *codec_;
void control(const climate::ClimateCall &call) override;
uint16_t char_handle_;
uint8_t current_request_;
bool fahrenheit_;
};
} // namespace anova
} // namespace esphome
#endif

View File

@@ -0,0 +1,137 @@
#include "anova_base.h"
namespace esphome {
namespace anova {
float ftoc(float f) { return (f - 32.0) * (5.0f / 9.0f); }
float ctof(float c) { return (c * 9.0f / 5.0f) + 32.0; }
AnovaPacket *AnovaCodec::clean_packet_() {
this->packet_.length = strlen((char *) this->packet_.data);
this->packet_.data[this->packet_.length] = '\0';
ESP_LOGV("anova", "SendPkt: %s\n", this->packet_.data);
return &this->packet_;
}
AnovaPacket *AnovaCodec::get_read_device_status_request() {
this->current_query_ = READ_DEVICE_STATUS;
sprintf((char *) this->packet_.data, "%s", CMD_READ_DEVICE_STATUS);
return this->clean_packet_();
}
AnovaPacket *AnovaCodec::get_read_target_temp_request() {
this->current_query_ = READ_TARGET_TEMPERATURE;
sprintf((char *) this->packet_.data, "%s", CMD_READ_TARGET_TEMP);
return this->clean_packet_();
}
AnovaPacket *AnovaCodec::get_read_current_temp_request() {
this->current_query_ = READ_CURRENT_TEMPERATURE;
sprintf((char *) this->packet_.data, "%s", CMD_READ_CURRENT_TEMP);
return this->clean_packet_();
}
AnovaPacket *AnovaCodec::get_read_unit_request() {
this->current_query_ = READ_UNIT;
sprintf((char *) this->packet_.data, "%s", CMD_READ_UNIT);
return this->clean_packet_();
}
AnovaPacket *AnovaCodec::get_read_data_request() {
this->current_query_ = READ_DATA;
sprintf((char *) this->packet_.data, "%s", CMD_READ_DATA);
return this->clean_packet_();
}
AnovaPacket *AnovaCodec::get_set_target_temp_request(float temperature) {
this->current_query_ = SET_TARGET_TEMPERATURE;
if (this->fahrenheit_)
temperature = ctof(temperature);
sprintf((char *) this->packet_.data, CMD_SET_TARGET_TEMP, temperature);
return this->clean_packet_();
}
AnovaPacket *AnovaCodec::get_set_unit_request(char unit) {
this->current_query_ = SET_UNIT;
sprintf((char *) this->packet_.data, CMD_SET_TEMP_UNIT, unit);
return this->clean_packet_();
}
AnovaPacket *AnovaCodec::get_start_request() {
this->current_query_ = START;
sprintf((char *) this->packet_.data, CMD_START);
return this->clean_packet_();
}
AnovaPacket *AnovaCodec::get_stop_request() {
this->current_query_ = STOP;
sprintf((char *) this->packet_.data, CMD_STOP);
return this->clean_packet_();
}
void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
memset(this->buf_, 0, 32);
strncpy(this->buf_, (char *) data, length);
this->has_target_temp_ = this->has_current_temp_ = this->has_unit_ = this->has_running_ = false;
switch (this->current_query_) {
case READ_DEVICE_STATUS: {
if (!strncmp(this->buf_, "stopped", 7)) {
this->has_running_ = true;
this->running_ = false;
}
if (!strncmp(this->buf_, "running", 7)) {
this->has_running_ = true;
this->running_ = true;
}
break;
}
case START: {
if (!strncmp(this->buf_, "start", 5)) {
this->has_running_ = true;
this->running_ = true;
}
break;
}
case STOP: {
if (!strncmp(this->buf_, "stop", 4)) {
this->has_running_ = true;
this->running_ = false;
}
break;
}
case READ_TARGET_TEMPERATURE: {
this->target_temp_ = strtof(this->buf_, nullptr);
if (this->fahrenheit_)
this->target_temp_ = ftoc(this->target_temp_);
this->has_target_temp_ = true;
break;
}
case SET_TARGET_TEMPERATURE: {
this->target_temp_ = strtof(this->buf_, nullptr);
if (this->fahrenheit_)
this->target_temp_ = ftoc(this->target_temp_);
this->has_target_temp_ = true;
break;
}
case READ_CURRENT_TEMPERATURE: {
this->current_temp_ = strtof(this->buf_, nullptr);
if (this->fahrenheit_)
this->current_temp_ = ftoc(this->current_temp_);
this->has_current_temp_ = true;
break;
}
case SET_UNIT:
case READ_UNIT: {
this->unit_ = this->buf_[0];
this->fahrenheit_ = this->buf_[0] == 'f';
this->has_unit_ = true;
break;
}
default:
break;
}
}
} // namespace anova
} // namespace esphome

View File

@@ -0,0 +1,80 @@
#pragma once
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace anova {
enum CurrentQuery {
NONE,
READ_DEVICE_STATUS,
READ_TARGET_TEMPERATURE,
READ_CURRENT_TEMPERATURE,
READ_DATA,
READ_UNIT,
SET_TARGET_TEMPERATURE,
SET_UNIT,
START,
STOP,
};
struct AnovaPacket {
uint16_t length;
uint8_t data[24];
};
#define CMD_READ_DEVICE_STATUS "status\r"
#define CMD_READ_TARGET_TEMP "read set temp\r"
#define CMD_READ_CURRENT_TEMP "read temp\r"
#define CMD_READ_UNIT "read unit\r"
#define CMD_READ_DATA "read data\r"
#define CMD_SET_TARGET_TEMP "set temp %.1f\r"
#define CMD_SET_TEMP_UNIT "set unit %c\r"
#define CMD_START "start\r"
#define CMD_STOP "stop\r"
class AnovaCodec {
public:
AnovaPacket *get_read_device_status_request();
AnovaPacket *get_read_target_temp_request();
AnovaPacket *get_read_current_temp_request();
AnovaPacket *get_read_data_request();
AnovaPacket *get_read_unit_request();
AnovaPacket *get_set_target_temp_request(float temperature);
AnovaPacket *get_set_unit_request(char unit);
AnovaPacket *get_start_request();
AnovaPacket *get_stop_request();
void decode(const uint8_t *data, uint16_t length);
bool has_target_temp() { return this->has_target_temp_; }
bool has_current_temp() { return this->has_current_temp_; }
bool has_unit() { return this->has_unit_; }
bool has_running() { return this->has_running_; }
union {
float target_temp_;
float current_temp_;
char unit_;
bool running_;
};
protected:
AnovaPacket *clean_packet_();
AnovaPacket packet_;
bool has_target_temp_;
bool has_current_temp_;
bool has_unit_;
bool has_running_;
char buf_[32];
bool fahrenheit_;
CurrentQuery current_query_;
};
} // namespace anova
} // namespace esphome

View File

@@ -0,0 +1,36 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import climate, ble_client
from esphome.const import CONF_ID, CONF_UNIT_OF_MEASUREMENT
UNITS = {
"f": "f",
"c": "c",
}
CODEOWNERS = ["@buxtronix"]
DEPENDENCIES = ["ble_client"]
anova_ns = cg.esphome_ns.namespace("anova")
Anova = anova_ns.class_(
"Anova", climate.Climate, ble_client.BLEClientNode, cg.PollingComponent
)
CONFIG_SCHEMA = (
climate.CLIMATE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(Anova),
cv.Required(CONF_UNIT_OF_MEASUREMENT): cv.enum(UNITS),
}
)
.extend(ble_client.BLE_CLIENT_SCHEMA)
.extend(cv.polling_component_schema("60s"))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await climate.register_climate(var, config)
await ble_client.register_ble_node(var, config)
cg.add(var.set_unit_of_measurement(config[CONF_UNIT_OF_MEASUREMENT]))

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

@@ -4,10 +4,10 @@
namespace esphome {
namespace apds9960 {
static const char *TAG = "apds9960";
static const char *const TAG = "apds9960";
#define APDS9960_ERROR_CHECK(func) \
if (!func) { \
if (!(func)) { \
this->mark_failed(); \
return; \
}

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,12 @@
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,
STATE_CLASS_MEASUREMENT,
UNIT_PERCENT,
ICON_LIGHTBULB,
)
from . import APDS9960, CONF_APDS9960_ID
DEPENDENCIES = ["apds9960"]
@@ -15,7 +20,10 @@ TYPES = {
}
CONFIG_SCHEMA = sensor.sensor_schema(
UNIT_PERCENT, ICON_LIGHTBULB, 1, DEVICE_CLASS_EMPTY
unit_of_measurement=UNIT_PERCENT,
icon=ICON_LIGHTBULB,
accuracy_decimals=1,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True),
@@ -24,8 +32,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

@@ -1,3 +1,5 @@
import base64
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
@@ -6,6 +8,7 @@ from esphome.const import (
CONF_DATA,
CONF_DATA_TEMPLATE,
CONF_ID,
CONF_KEY,
CONF_PASSWORD,
CONF_PORT,
CONF_REBOOT_TIMEOUT,
@@ -19,7 +22,7 @@ from esphome.const import (
from esphome.core import coroutine_with_priority
DEPENDENCIES = ["network"]
AUTO_LOAD = ["async_tcp"]
AUTO_LOAD = ["socket"]
CODEOWNERS = ["@OttoWinter"]
api_ns = cg.esphome_ns.namespace("api")
@@ -41,6 +44,22 @@ SERVICE_ARG_NATIVE_TYPES = {
"float[]": cg.std_vector.template(float),
"string[]": cg.std_vector.template(cg.std_string),
}
CONF_ENCRYPTION = "encryption"
def validate_encryption_key(value):
value = cv.string_strict(value)
try:
decoded = base64.b64decode(value, validate=True)
except ValueError as err:
raise cv.Invalid("Invalid key format, please check it's using base64") from err
if len(decoded) != 32:
raise cv.Invalid("Encryption key must be base64 and 32 bytes long")
# Return original data for roundtrip conversion
return value
CONFIG_SCHEMA = cv.Schema(
{
@@ -63,14 +82,19 @@ CONFIG_SCHEMA = cv.Schema(
),
}
),
cv.Optional(CONF_ENCRYPTION): cv.Schema(
{
cv.Required(CONF_KEY): validate_encryption_key,
}
),
}
).extend(cv.COMPONENT_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 +114,15 @@ 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)
if CONF_ENCRYPTION in config:
conf = config[CONF_ENCRYPTION]
decoded = base64.b64decode(conf[CONF_KEY])
cg.add(var.set_noise_psk(list(decoded)))
cg.add_define("USE_API_NOISE")
else:
cg.add_define("USE_API_PLAINTEXT")
cg.add_define("USE_API")
cg.add_global(api_ns.using)
@@ -116,21 +148,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 +191,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 +222,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

@@ -38,6 +38,8 @@ service APIConnection {
rpc switch_command (SwitchCommandRequest) returns (void) {}
rpc camera_image (CameraImageRequest) returns (void) {}
rpc climate_command (ClimateCommandRequest) returns (void) {}
rpc number_command (NumberCommandRequest) returns (void) {}
rpc select_command (SelectCommandRequest) returns (void) {}
}
@@ -176,6 +178,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 {
@@ -208,6 +214,7 @@ message ListEntitiesBinarySensorResponse {
string device_class = 5;
bool is_status_binary_sensor = 6;
bool disabled_by_default = 7;
}
message BinarySensorStateResponse {
option (id) = 21;
@@ -237,6 +244,7 @@ message ListEntitiesCoverResponse {
bool supports_position = 6;
bool supports_tilt = 7;
string device_class = 8;
bool disabled_by_default = 9;
}
enum LegacyCoverState {
@@ -304,6 +312,7 @@ message ListEntitiesFanResponse {
bool supports_speed = 6;
bool supports_direction = 7;
int32 supported_speed_count = 8;
bool disabled_by_default = 9;
}
enum FanSpeed {
FAN_SPEED_LOW = 0;
@@ -347,6 +356,18 @@ message FanCommandRequest {
}
// ==================== LIGHT ====================
enum ColorMode {
COLOR_MODE_UNKNOWN = 0;
COLOR_MODE_ON_OFF = 1;
COLOR_MODE_BRIGHTNESS = 2;
COLOR_MODE_WHITE = 7;
COLOR_MODE_COLOR_TEMPERATURE = 11;
COLOR_MODE_COLD_WARM_WHITE = 19;
COLOR_MODE_RGB = 35;
COLOR_MODE_RGB_WHITE = 39;
COLOR_MODE_RGB_COLOR_TEMPERATURE = 47;
COLOR_MODE_RGB_COLD_WARM_WHITE = 51;
}
message ListEntitiesLightResponse {
option (id) = 15;
option (source) = SOURCE_SERVER;
@@ -357,13 +378,16 @@ message ListEntitiesLightResponse {
string name = 3;
string unique_id = 4;
bool supports_brightness = 5;
bool supports_rgb = 6;
bool supports_white_value = 7;
bool supports_color_temperature = 8;
repeated ColorMode supported_color_modes = 12;
// next four supports_* are for legacy clients, newer clients should use color modes
bool legacy_supports_brightness = 5 [deprecated=true];
bool legacy_supports_rgb = 6 [deprecated=true];
bool legacy_supports_white_value = 7 [deprecated=true];
bool legacy_supports_color_temperature = 8 [deprecated=true];
float min_mireds = 9;
float max_mireds = 10;
repeated string effects = 11;
bool disabled_by_default = 13;
}
message LightStateResponse {
option (id) = 24;
@@ -374,11 +398,15 @@ message LightStateResponse {
fixed32 key = 1;
bool state = 2;
float brightness = 3;
ColorMode color_mode = 11;
float color_brightness = 10;
float red = 4;
float green = 5;
float blue = 6;
float white = 7;
float color_temperature = 8;
float cold_white = 12;
float warm_white = 13;
string effect = 9;
}
message LightCommandRequest {
@@ -392,6 +420,10 @@ message LightCommandRequest {
bool state = 3;
bool has_brightness = 4;
float brightness = 5;
bool has_color_mode = 22;
ColorMode color_mode = 23;
bool has_color_brightness = 20;
float color_brightness = 21;
bool has_rgb = 6;
float red = 7;
float green = 8;
@@ -400,6 +432,10 @@ message LightCommandRequest {
float white = 11;
bool has_color_temperature = 12;
float color_temperature = 13;
bool has_cold_white = 24;
float cold_white = 25;
bool has_warm_white = 26;
float warm_white = 27;
bool has_transition_length = 14;
uint32 transition_length = 15;
bool has_flash_length = 16;
@@ -409,6 +445,17 @@ message LightCommandRequest {
}
// ==================== SENSOR ====================
enum SensorStateClass {
STATE_CLASS_NONE = 0;
STATE_CLASS_MEASUREMENT = 1;
}
enum SensorLastResetType {
LAST_RESET_NONE = 0;
LAST_RESET_NEVER = 1;
LAST_RESET_AUTO = 2;
}
message ListEntitiesSensorResponse {
option (id) = 16;
option (source) = SOURCE_SERVER;
@@ -424,6 +471,9 @@ message ListEntitiesSensorResponse {
int32 accuracy_decimals = 7;
bool force_update = 8;
string device_class = 9;
SensorStateClass state_class = 10;
SensorLastResetType last_reset_type = 11;
bool disabled_by_default = 12;
}
message SensorStateResponse {
option (id) = 25;
@@ -451,6 +501,7 @@ message ListEntitiesSwitchResponse {
string icon = 5;
bool assumed_state = 6;
bool disabled_by_default = 7;
}
message SwitchStateResponse {
option (id) = 26;
@@ -483,6 +534,7 @@ message ListEntitiesTextSensorResponse {
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
}
message TextSensorStateResponse {
option (id) = 27;
@@ -561,6 +613,7 @@ message SubscribeHomeAssistantStateResponse {
option (id) = 39;
option (source) = SOURCE_SERVER;
string entity_id = 1;
string attribute = 2;
}
message HomeAssistantStateResponse {
@@ -570,6 +623,7 @@ message HomeAssistantStateResponse {
string entity_id = 1;
string state = 2;
string attribute = 3;
}
// ==================== IMPORT TIME ====================
@@ -640,6 +694,7 @@ message ListEntitiesCameraResponse {
fixed32 key = 2;
string name = 3;
string unique_id = 4;
bool disabled_by_default = 5;
}
message CameraImageResponse {
@@ -664,11 +719,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 +752,16 @@ enum ClimateAction {
CLIMATE_ACTION_DRYING = 5;
CLIMATE_ACTION_FAN = 6;
}
enum ClimatePreset {
CLIMATE_PRESET_NONE = 0;
CLIMATE_PRESET_HOME = 1;
CLIMATE_PRESET_AWAY = 2;
CLIMATE_PRESET_BOOST = 3;
CLIMATE_PRESET_COMFORT = 4;
CLIMATE_PRESET_ECO = 5;
CLIMATE_PRESET_SLEEP = 6;
CLIMATE_PRESET_ACTIVITY = 7;
}
message ListEntitiesClimateResponse {
option (id) = 46;
option (source) = SOURCE_SERVER;
@@ -712,10 +778,16 @@ message ListEntitiesClimateResponse {
float visual_min_temperature = 8;
float visual_max_temperature = 9;
float visual_temperature_step = 10;
bool supports_away = 11;
// for older peer versions - in new system this
// is if CLIMATE_PRESET_AWAY exists is supported_presets
bool legacy_supports_away = 11;
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;
bool disabled_by_default = 18;
}
message ClimateStateResponse {
option (id) = 47;
@@ -729,10 +801,14 @@ message ClimateStateResponse {
float target_temperature = 4;
float target_temperature_low = 5;
float target_temperature_high = 6;
bool away = 7;
// For older peers, equal to preset == CLIMATE_PRESET_AWAY
bool legacy_away = 7;
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;
@@ -749,10 +825,93 @@ message ClimateCommandRequest {
float target_temperature_low = 7;
bool has_target_temperature_high = 8;
float target_temperature_high = 9;
bool has_away = 10;
bool away = 11;
// legacy, for older peers, newer ones should use CLIMATE_PRESET_AWAY in preset
bool has_legacy_away = 10;
bool legacy_away = 11;
bool has_fan_mode = 12;
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;
}
// ==================== NUMBER ====================
message ListEntitiesNumberResponse {
option (id) = 49;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_NUMBER";
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
float min_value = 6;
float max_value = 7;
float step = 8;
bool disabled_by_default = 9;
}
message NumberStateResponse {
option (id) = 50;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_NUMBER";
option (no_delay) = true;
fixed32 key = 1;
float state = 2;
// If the number does not have a valid state yet.
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
bool missing_state = 3;
}
message NumberCommandRequest {
option (id) = 51;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_NUMBER";
option (no_delay) = true;
fixed32 key = 1;
float state = 2;
}
// ==================== SELECT ====================
message ListEntitiesSelectResponse {
option (id) = 52;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_SELECT";
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
repeated string options = 6;
bool disabled_by_default = 7;
}
message SelectStateResponse {
option (id) = 53;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_SELECT";
option (no_delay) = true;
fixed32 key = 1;
string state = 2;
// If the select does not have a valid state yet.
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
bool missing_state = 3;
}
message SelectCommandRequest {
option (id) = 54;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_SELECT";
option (no_delay) = true;
fixed32 key = 1;
string state = 2;
}

View File

@@ -2,6 +2,7 @@
#include "esphome/core/log.h"
#include "esphome/core/util.h"
#include "esphome/core/version.h"
#include <errno.h>
#ifdef USE_DEEP_SLEEP
#include "esphome/components/deep_sleep/deep_sleep_component.h"
@@ -16,76 +17,38 @@
namespace esphome {
namespace api {
static const char *TAG = "api.connection";
static const char *const TAG = "api.connection";
APIConnection::APIConnection(AsyncClient *client, APIServer *parent)
: client_(client), parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) {
this->client_->onError([](void *s, AsyncClient *c, int8_t error) { ((APIConnection *) s)->on_error_(error); }, this);
this->client_->onDisconnect([](void *s, AsyncClient *c) { ((APIConnection *) s)->on_disconnect_(); }, this);
this->client_->onTimeout([](void *s, AsyncClient *c, uint32_t time) { ((APIConnection *) s)->on_timeout_(time); },
this);
this->client_->onData([](void *s, AsyncClient *c, void *buf,
size_t len) { ((APIConnection *) s)->on_data_(reinterpret_cast<uint8_t *>(buf), len); },
this);
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
: parent_(parent),
initial_state_iterator_(parent, this),
list_entities_iterator_(parent, this) {
this->proto_write_buffer_.reserve(64);
this->send_buffer_.reserve(64);
this->recv_buffer_.reserve(32);
this->client_info_ = this->client_->remoteIP().toString().c_str();
#ifdef USE_API_NOISE
helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())};
#elif defined(USE_API_PLAINTEXT)
helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
#else
#error "No api frame helper enabled"
#endif
}
void APIConnection::start() {
this->last_traffic_ = millis();
}
APIConnection::~APIConnection() { delete this->client_; }
void APIConnection::on_error_(int8_t error) { this->remove_ = true; }
void APIConnection::on_disconnect_() { this->remove_ = true; }
void APIConnection::on_timeout_(uint32_t time) { this->on_fatal_error(); }
void APIConnection::on_data_(uint8_t *buf, size_t len) {
if (len == 0 || buf == nullptr)
APIError err = helper_->init();
if (err != APIError::OK) {
ESP_LOGW(TAG, "Helper init failed: %d errno=%d", (int) err, errno);
remove_ = true;
return;
this->recv_buffer_.insert(this->recv_buffer_.end(), buf, buf + len);
}
void APIConnection::parse_recv_buffer_() {
if (this->recv_buffer_.empty() || this->remove_)
return;
while (!this->recv_buffer_.empty()) {
if (this->recv_buffer_[0] != 0x00) {
ESP_LOGW(TAG, "Invalid preamble from %s", this->client_info_.c_str());
this->on_fatal_error();
return;
}
uint32_t i = 1;
const uint32_t size = this->recv_buffer_.size();
uint32_t consumed;
auto msg_size_varint = ProtoVarInt::parse(&this->recv_buffer_[i], size - i, &consumed);
if (!msg_size_varint.has_value())
// not enough data there yet
return;
i += consumed;
uint32_t msg_size = msg_size_varint->as_uint32();
auto msg_type_varint = ProtoVarInt::parse(&this->recv_buffer_[i], size - i, &consumed);
if (!msg_type_varint.has_value())
// not enough data there yet
return;
i += consumed;
uint32_t msg_type = msg_type_varint->as_uint32();
if (size - i < msg_size)
// message body not fully received
return;
uint8_t *msg = &this->recv_buffer_[i];
this->read_message(msg_size, msg_type, msg);
if (this->remove_)
return;
// pop front
uint32_t total = i + msg_size;
this->recv_buffer_.erase(this->recv_buffer_.begin(), this->recv_buffer_.begin() + total);
this->last_traffic_ = millis();
}
client_info_ = helper_->getpeername();
helper_->set_log_info(client_info_);
}
void APIConnection::disconnect_client() {
this->client_->close();
void APIConnection::force_disconnect_client() {
this->helper_->close();
this->remove_ = true;
}
@@ -93,61 +56,78 @@ void APIConnection::loop() {
if (this->remove_)
return;
if (this->next_close_) {
this->disconnect_client();
return;
}
if (!network_is_connected()) {
// when network is disconnected force disconnect immediately
// don't wait for timeout
this->on_fatal_error();
return;
}
if (this->client_->disconnected()) {
// failsafe for disconnect logic
this->on_disconnect_();
if (this->next_close_) {
this->helper_->close();
this->remove_ = true;
return;
}
this->parse_recv_buffer_();
APIError err = helper_->loop();
if (err != APIError::OK) {
on_fatal_error();
ESP_LOGW(TAG, "%s: Socket operation failed: %d", client_info_.c_str(), (int) err);
return;
}
ReadPacketBuffer buffer;
err = helper_->read_packet(&buffer);
if (err == APIError::WOULD_BLOCK) {
// pass
} else if (err != APIError::OK) {
on_fatal_error();
ESP_LOGW(TAG, "%s: Reading failed: %d", client_info_.c_str(), (int) err);
return;
} else {
this->last_traffic_ = millis();
// read a packet
this->read_message(
buffer.data_len,
buffer.type,
&buffer.container[buffer.data_offset]
);
if (this->remove_)
return;
}
this->list_entities_iterator_.advance();
this->initial_state_iterator_.advance();
const uint32_t keepalive = 60000;
const uint32_t now = millis();
if (this->sent_ping_) {
// Disconnect if not responded within 2.5*keepalive
if (millis() - this->last_traffic_ > (keepalive * 5) / 2) {
if (now - this->last_traffic_ > (keepalive * 5) / 2) {
this->force_disconnect_client();
ESP_LOGW(TAG, "'%s' didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str());
this->disconnect_client();
}
} else if (millis() - this->last_traffic_ > keepalive) {
} else if (now - this->last_traffic_ > keepalive) {
this->sent_ping_ = true;
this->send_ping_request(PingRequest());
}
#ifdef USE_ESP32_CAMERA
if (this->image_reader_.available()) {
uint32_t space = this->client_->space();
// reserve 15 bytes for metadata, and at least 64 bytes of data
if (space >= 15 + 64) {
uint32_t to_send = std::min(space - 15, this->image_reader_.available());
auto buffer = this->create_buffer();
// fixed32 key = 1;
buffer.encode_fixed32(1, esp32_camera::global_esp32_camera->get_object_id_hash());
// bytes data = 2;
buffer.encode_bytes(2, this->image_reader_.peek_data_buffer(), to_send);
// bool done = 3;
bool done = this->image_reader_.available() == to_send;
buffer.encode_bool(3, done);
bool success = this->send_buffer(buffer, 44);
if (this->image_reader_.available() && this->helper_->can_write_without_blocking()) {
uint32_t to_send = std::min((size_t) 1024, this->image_reader_.available());
auto buffer = this->create_buffer();
// fixed32 key = 1;
buffer.encode_fixed32(1, esp32_camera::global_esp32_camera->get_object_id_hash());
// bytes data = 2;
buffer.encode_bytes(2, this->image_reader_.peek_data_buffer(), to_send);
// bool done = 3;
bool done = this->image_reader_.available() == to_send;
buffer.encode_bool(3, done);
bool success = this->send_buffer(buffer, 44);
if (success) {
this->image_reader_.consume_data(to_send);
}
if (success && done) {
this->image_reader_.return_image();
}
if (success) {
this->image_reader_.consume_data(to_send);
}
if (success && done) {
this->image_reader_.return_image();
}
}
#endif
@@ -176,6 +156,7 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_
msg.unique_id = get_default_unique_id("binary_sensor", binary_sensor);
msg.device_class = binary_sensor->get_device_class();
msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
msg.disabled_by_default = binary_sensor->is_disabled_by_default();
return this->send_list_entities_binary_sensor_response(msg);
}
#endif
@@ -207,6 +188,7 @@ bool APIConnection::send_cover_info(cover::Cover *cover) {
msg.supports_position = traits.get_supports_position();
msg.supports_tilt = traits.get_supports_tilt();
msg.device_class = cover->get_device_class();
msg.disabled_by_default = cover->is_disabled_by_default();
return this->send_list_entities_cover_response(msg);
}
void APIConnection::cover_command(const CoverCommandRequest &msg) {
@@ -268,6 +250,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) {
msg.supports_speed = traits.supports_speed();
msg.supports_direction = traits.supports_direction();
msg.supported_speed_count = traits.supported_speed_count();
msg.disabled_by_default = fan->is_disabled_by_default();
return this->send_list_entities_fan_response(msg);
}
void APIConnection::fan_command(const FanCommandRequest &msg) {
@@ -301,21 +284,28 @@ bool APIConnection::send_light_state(light::LightState *light) {
auto traits = light->get_traits();
auto values = light->remote_values;
auto color_mode = values.get_color_mode();
LightStateResponse resp{};
resp.key = light->get_object_id_hash();
resp.state = values.is_on();
if (traits.get_supports_brightness())
resp.color_mode = static_cast<enums::ColorMode>(color_mode);
if (color_mode & light::ColorCapability::BRIGHTNESS)
resp.brightness = values.get_brightness();
if (traits.get_supports_rgb()) {
if (color_mode & light::ColorCapability::RGB) {
resp.color_brightness = values.get_color_brightness();
resp.red = values.get_red();
resp.green = values.get_green();
resp.blue = values.get_blue();
}
if (traits.get_supports_rgb_white_value())
if (color_mode & light::ColorCapability::WHITE)
resp.white = values.get_white();
if (traits.get_supports_color_temperature())
if (color_mode & light::ColorCapability::COLOR_TEMPERATURE)
resp.color_temperature = values.get_color_temperature();
if (color_mode & light::ColorCapability::COLD_WARM_WHITE) {
resp.cold_white = values.get_cold_white();
resp.warm_white = values.get_warm_white();
}
if (light->supports_effects())
resp.effect = light->get_effect_name();
return this->send_light_state_response(resp);
@@ -327,11 +317,21 @@ bool APIConnection::send_light_info(light::LightState *light) {
msg.object_id = light->get_object_id();
msg.name = light->get_name();
msg.unique_id = get_default_unique_id("light", light);
msg.supports_brightness = traits.get_supports_brightness();
msg.supports_rgb = traits.get_supports_rgb();
msg.supports_white_value = traits.get_supports_rgb_white_value();
msg.supports_color_temperature = traits.get_supports_color_temperature();
if (msg.supports_color_temperature) {
msg.disabled_by_default = light->is_disabled_by_default();
for (auto mode : traits.get_supported_color_modes())
msg.supported_color_modes.push_back(static_cast<enums::ColorMode>(mode));
msg.legacy_supports_brightness = traits.supports_color_capability(light::ColorCapability::BRIGHTNESS);
msg.legacy_supports_rgb = traits.supports_color_capability(light::ColorCapability::RGB);
msg.legacy_supports_white_value =
msg.legacy_supports_rgb && (traits.supports_color_capability(light::ColorCapability::WHITE) ||
traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE));
msg.legacy_supports_color_temperature = traits.supports_color_capability(light::ColorCapability::COLOR_TEMPERATURE) ||
traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE);
if (msg.legacy_supports_color_temperature) {
msg.min_mireds = traits.get_min_mireds();
msg.max_mireds = traits.get_max_mireds();
}
@@ -352,6 +352,10 @@ void APIConnection::light_command(const LightCommandRequest &msg) {
call.set_state(msg.state);
if (msg.has_brightness)
call.set_brightness(msg.brightness);
if (msg.has_color_mode)
call.set_color_mode(static_cast<light::ColorMode>(msg.color_mode));
if (msg.has_color_brightness)
call.set_color_brightness(msg.color_brightness);
if (msg.has_rgb) {
call.set_red(msg.red);
call.set_green(msg.green);
@@ -361,6 +365,10 @@ void APIConnection::light_command(const LightCommandRequest &msg) {
call.set_white(msg.white);
if (msg.has_color_temperature)
call.set_color_temperature(msg.color_temperature);
if (msg.has_cold_white)
call.set_cold_white(msg.cold_white);
if (msg.has_warm_white)
call.set_warm_white(msg.warm_white);
if (msg.has_transition_length)
call.set_transition_length(msg.transition_length);
if (msg.has_flash_length)
@@ -395,6 +403,10 @@ 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);
msg.last_reset_type = static_cast<enums::SensorLastResetType>(sensor->last_reset_type);
msg.disabled_by_default = sensor->is_disabled_by_default();
return this->send_list_entities_sensor_response(msg);
}
#endif
@@ -417,6 +429,7 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) {
msg.unique_id = get_default_unique_id("switch", a_switch);
msg.icon = a_switch->get_icon();
msg.assumed_state = a_switch->assumed_state();
msg.disabled_by_default = a_switch->is_disabled_by_default();
return this->send_list_entities_switch_response(msg);
}
void APIConnection::switch_command(const SwitchCommandRequest &msg) {
@@ -451,6 +464,7 @@ bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor)
if (msg.unique_id.empty())
msg.unique_id = get_default_unique_id("text_sensor", text_sensor);
msg.icon = text_sensor->get_icon();
msg.disabled_by_default = text_sensor->is_disabled_by_default();
return this->send_list_entities_text_sensor_response(msg);
}
#endif
@@ -473,10 +487,16 @@ bool APIConnection::send_climate_state(climate::Climate *climate) {
} else {
resp.target_temperature = climate->target_temperature;
}
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());
resp.legacy_away = resp.preset == enums::CLIMATE_PRESET_AWAY;
}
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);
@@ -488,29 +508,31 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
msg.object_id = climate->get_object_id();
msg.name = climate->get_name();
msg.unique_id = get_default_unique_id("climate", climate);
msg.disabled_by_default = climate->is_disabled_by_default();
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}) {
if (traits.supports_mode(mode))
msg.supported_modes.push_back(static_cast<enums::ClimateMode>(mode));
}
for (auto mode : traits.get_supported_modes())
msg.supported_modes.push_back(static_cast<enums::ClimateMode>(mode));
msg.visual_min_temperature = traits.get_visual_min_temperature();
msg.visual_max_temperature = traits.get_visual_max_temperature();
msg.visual_temperature_step = traits.get_visual_temperature_step();
msg.supports_away = traits.get_supports_away();
msg.legacy_supports_away = traits.supports_preset(climate::CLIMATE_PRESET_AWAY);
msg.supports_action = traits.get_supports_action();
for (auto fan_mode : {climate::CLIMATE_FAN_ON, climate::CLIMATE_FAN_OFF, climate::CLIMATE_FAN_AUTO,
climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH,
climate::CLIMATE_FAN_MIDDLE, climate::CLIMATE_FAN_FOCUS, climate::CLIMATE_FAN_DIFFUSE}) {
if (traits.supports_fan_mode(fan_mode))
msg.supported_fan_modes.push_back(static_cast<enums::ClimateFanMode>(fan_mode));
}
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))
msg.supported_swing_modes.push_back(static_cast<enums::ClimateSwingMode>(swing_mode));
}
for (auto fan_mode : traits.get_supported_fan_modes())
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 : traits.get_supported_presets())
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 : traits.get_supported_swing_modes())
msg.supported_swing_modes.push_back(static_cast<enums::ClimateSwingMode>(swing_mode));
return this->send_list_entities_climate_response(msg);
}
void APIConnection::climate_command(const ClimateCommandRequest &msg) {
@@ -527,16 +549,95 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
call.set_target_temperature_low(msg.target_temperature_low);
if (msg.has_target_temperature_high)
call.set_target_temperature_high(msg.target_temperature_high);
if (msg.has_away)
call.set_away(msg.away);
if (msg.has_legacy_away)
call.set_preset(msg.legacy_away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME);
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();
}
#endif
#ifdef USE_NUMBER
bool APIConnection::send_number_state(number::Number *number, float state) {
if (!this->state_subscription_)
return false;
NumberStateResponse resp{};
resp.key = number->get_object_id_hash();
resp.state = state;
resp.missing_state = !number->has_state();
return this->send_number_state_response(resp);
}
bool APIConnection::send_number_info(number::Number *number) {
ListEntitiesNumberResponse msg;
msg.key = number->get_object_id_hash();
msg.object_id = number->get_object_id();
msg.name = number->get_name();
msg.unique_id = get_default_unique_id("number", number);
msg.icon = number->traits.get_icon();
msg.disabled_by_default = number->is_disabled_by_default();
msg.min_value = number->traits.get_min_value();
msg.max_value = number->traits.get_max_value();
msg.step = number->traits.get_step();
return this->send_list_entities_number_response(msg);
}
void APIConnection::number_command(const NumberCommandRequest &msg) {
number::Number *number = App.get_number_by_key(msg.key);
if (number == nullptr)
return;
auto call = number->make_call();
call.set_value(msg.state);
call.perform();
}
#endif
#ifdef USE_SELECT
bool APIConnection::send_select_state(select::Select *select, std::string state) {
if (!this->state_subscription_)
return false;
SelectStateResponse resp{};
resp.key = select->get_object_id_hash();
resp.state = std::move(state);
resp.missing_state = !select->has_state();
return this->send_select_state_response(resp);
}
bool APIConnection::send_select_info(select::Select *select) {
ListEntitiesSelectResponse msg;
msg.key = select->get_object_id_hash();
msg.object_id = select->get_object_id();
msg.name = select->get_name();
msg.unique_id = get_default_unique_id("select", select);
msg.icon = select->traits.get_icon();
msg.disabled_by_default = select->is_disabled_by_default();
for (const auto &option : select->traits.get_options())
msg.options.push_back(option);
return this->send_list_entities_select_response(msg);
}
void APIConnection::select_command(const SelectCommandRequest &msg) {
select::Select *select = App.get_select_by_key(msg.key);
if (select == nullptr)
return;
auto call = select->make_call();
call.set_option(msg.state);
call.perform();
}
#endif
#ifdef USE_ESP32_CAMERA
void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) {
if (!this->state_subscription_)
@@ -551,6 +652,7 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
msg.object_id = camera->get_object_id();
msg.name = camera->get_name();
msg.unique_id = get_default_unique_id("camera", camera);
msg.disabled_by_default = camera->is_disabled_by_default();
return this->send_list_entities_camera_response(msg);
}
void APIConnection::camera_image(const CameraImageRequest &msg) {
@@ -596,13 +698,13 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin
}
HelloResponse APIConnection::hello(const HelloRequest &msg) {
this->client_info_ = msg.client_info + " (" + this->client_->remoteIP().toString().c_str();
this->client_info_ += ")";
this->client_info_ = msg.client_info + " (" + this->helper_->getpeername() + ")";
this->helper_->set_log_info(client_info_);
ESP_LOGV(TAG, "Hello from client: '%s'", this->client_info_.c_str());
HelloResponse resp;
resp.api_version_major = 1;
resp.api_version_minor = 4;
resp.api_version_minor = 6;
resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
this->connection_state_ = ConnectionState::CONNECTED;
return resp;
@@ -637,13 +739,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 +767,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;
@@ -669,44 +777,35 @@ void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistant
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) {
if (this->remove_)
return false;
if (!this->helper_->can_write_without_blocking())
return false;
std::vector<uint8_t> header;
header.push_back(0x00);
ProtoVarInt(buffer.get_buffer()->size()).encode(header);
ProtoVarInt(message_type).encode(header);
size_t needed_space = buffer.get_buffer()->size() + header.size();
if (needed_space > this->client_->space()) {
delay(0);
if (needed_space > this->client_->space()) {
// SubscribeLogsResponse
if (message_type != 29) {
ESP_LOGV(TAG, "Cannot send message because of TCP buffer space");
}
delay(0);
return false;
}
APIError err = this->helper_->write_packet(
message_type,
buffer.get_buffer()->data(),
buffer.get_buffer()->size()
);
if (err == APIError::WOULD_BLOCK)
return false;
if (err != APIError::OK) {
on_fatal_error();
ESP_LOGW(TAG, "%s: Packet write failed %d errno=%d", client_info_.c_str(), (int) err, errno);
return false;
}
this->client_->add(reinterpret_cast<char *>(header.data()), header.size(),
ASYNC_WRITE_FLAG_COPY | ASYNC_WRITE_FLAG_MORE);
this->client_->add(reinterpret_cast<char *>(buffer.get_buffer()->data()), buffer.get_buffer()->size(),
ASYNC_WRITE_FLAG_COPY);
bool ret = this->client_->send();
return ret;
this->last_traffic_ = millis();
return true;
}
void APIConnection::on_unauthenticated_access() {
ESP_LOGD(TAG, "'%s' tried to access without authentication.", this->client_info_.c_str());
this->on_fatal_error();
ESP_LOGD(TAG, "'%s' tried to access without authentication.", this->client_info_.c_str());
}
void APIConnection::on_no_setup_connection() {
ESP_LOGD(TAG, "'%s' tried to access without full connection.", this->client_info_.c_str());
this->on_fatal_error();
ESP_LOGD(TAG, "'%s' tried to access without full connection.", this->client_info_.c_str());
}
void APIConnection::on_fatal_error() {
ESP_LOGV(TAG, "Error: Disconnecting %s", this->client_info_.c_str());
this->client_->close();
this->helper_->close();
this->remove_ = true;
}

View File

@@ -5,16 +5,18 @@
#include "api_pb2.h"
#include "api_pb2_service.h"
#include "api_server.h"
#include "api_frame_helper.h"
namespace esphome {
namespace api {
class APIConnection : public APIServerConnection {
public:
APIConnection(AsyncClient *client, APIServer *parent);
virtual ~APIConnection();
APIConnection(std::unique_ptr<socket::Socket> socket, APIServer *parent);
virtual ~APIConnection() = default;
void disconnect_client();
void start();
void force_disconnect_client();
void loop();
bool send_list_info_done() {
@@ -62,6 +64,16 @@ class APIConnection : public APIServerConnection {
bool send_climate_state(climate::Climate *climate);
bool send_climate_info(climate::Climate *climate);
void climate_command(const ClimateCommandRequest &msg) override;
#endif
#ifdef USE_NUMBER
bool send_number_state(number::Number *number, float state);
bool send_number_info(number::Number *number);
void number_command(const NumberCommandRequest &msg) override;
#endif
#ifdef USE_SELECT
bool send_select_state(select::Select *select, std::string state);
bool send_select_info(select::Select *select);
void select_command(const SelectCommandRequest &msg) override;
#endif
bool send_log_message(int level, const char *tag, const char *line);
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
@@ -77,8 +89,8 @@ class APIConnection : public APIServerConnection {
#endif
void on_disconnect_response(const DisconnectResponse &value) override {
// we initiated disconnect_client
this->next_close_ = true;
this->helper_->close();
this->remove_ = true;
}
void on_ping_response(const PingResponse &value) override {
// we initiated ping
@@ -92,6 +104,8 @@ class APIConnection : public APIServerConnection {
ConnectResponse connect(const ConnectRequest &msg) override;
DisconnectResponse disconnect(const DisconnectRequest &msg) override {
// remote initiated disconnect_client
// don't close yet, we still need to send the disconnect response
// close will happen on next loop
this->next_close_ = true;
DisconnectResponse resp;
return resp;
@@ -125,19 +139,16 @@ class APIConnection : public APIServerConnection {
void on_unauthenticated_access() override;
void on_no_setup_connection() override;
ProtoWriteBuffer create_buffer() override {
this->send_buffer_.clear();
return {&this->send_buffer_};
// FIXME: ensure no recursive writes can happen
this->proto_write_buffer_.clear();
return {&this->proto_write_buffer_};
}
bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) override;
protected:
friend APIServer;
void on_error_(int8_t error);
void on_disconnect_();
void on_timeout_(uint32_t time);
void on_data_(uint8_t *buf, size_t len);
void parse_recv_buffer_();
bool send_(const void *buf, size_t len, bool force);
enum class ConnectionState {
WAITING_FOR_HELLO,
@@ -147,8 +158,10 @@ class APIConnection : public APIServerConnection {
bool remove_{false};
std::vector<uint8_t> send_buffer_;
std::vector<uint8_t> recv_buffer_;
// Buffer used to encode proto messages
// Re-use to prevent allocations
std::vector<uint8_t> proto_write_buffer_;
std::unique_ptr<APIFrameHelper> helper_;
std::string client_info_;
#ifdef USE_ESP32_CAMERA
@@ -160,9 +173,7 @@ class APIConnection : public APIServerConnection {
uint32_t last_traffic_;
bool sent_ping_{false};
bool service_call_subscription_{false};
bool current_nodelay_{false};
bool next_close_{false};
AsyncClient *client_;
bool next_close_ = false;
APIServer *parent_;
InitialStateIterator initial_state_iterator_;
ListEntitiesIterator list_entities_iterator_;

View File

@@ -0,0 +1,907 @@
#include "api_frame_helper.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include "proto.h"
namespace esphome {
namespace api {
static const char *const TAG = "api.socket";
/// Is the given return value (from read/write syscalls) a wouldblock error?
bool is_would_block(ssize_t ret) {
if (ret == -1) {
return errno == EWOULDBLOCK || errno == EAGAIN;
}
return ret == 0;
}
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, info_.c_str(), ##__VA_ARGS__)
#ifdef USE_API_NOISE
static const char *const PROLOGUE_INIT = "NoiseAPIInit";
/// Convert a noise error code to a readable error
std::string noise_err_to_str(int err) {
if (err == NOISE_ERROR_NO_MEMORY)
return "NO_MEMORY";
if (err == NOISE_ERROR_UNKNOWN_ID)
return "UNKNOWN_ID";
if (err == NOISE_ERROR_UNKNOWN_NAME)
return "UNKNOWN_NAME";
if (err == NOISE_ERROR_MAC_FAILURE)
return "MAC_FAILURE";
if (err == NOISE_ERROR_NOT_APPLICABLE)
return "NOT_APPLICABLE";
if (err == NOISE_ERROR_SYSTEM)
return "SYSTEM";
if (err == NOISE_ERROR_REMOTE_KEY_REQUIRED)
return "REMOTE_KEY_REQUIRED";
if (err == NOISE_ERROR_LOCAL_KEY_REQUIRED)
return "LOCAL_KEY_REQUIRED";
if (err == NOISE_ERROR_PSK_REQUIRED)
return "PSK_REQUIRED";
if (err == NOISE_ERROR_INVALID_LENGTH)
return "INVALID_LENGTH";
if (err == NOISE_ERROR_INVALID_PARAM)
return "INVALID_PARAM";
if (err == NOISE_ERROR_INVALID_STATE)
return "INVALID_STATE";
if (err == NOISE_ERROR_INVALID_NONCE)
return "INVALID_NONCE";
if (err == NOISE_ERROR_INVALID_PRIVATE_KEY)
return "INVALID_PRIVATE_KEY";
if (err == NOISE_ERROR_INVALID_PUBLIC_KEY)
return "INVALID_PUBLIC_KEY";
if (err == NOISE_ERROR_INVALID_FORMAT)
return "INVALID_FORMAT";
if (err == NOISE_ERROR_INVALID_SIGNATURE)
return "INVALID_SIGNATURE";
return to_string(err);
}
/// Initialize the frame helper, returns OK if successful.
APIError APINoiseFrameHelper::init() {
if (state_ != State::INITIALIZE || socket_ == nullptr) {
HELPER_LOG("Bad state for init %d", (int) state_);
return APIError::BAD_STATE;
}
int err = socket_->setblocking(false);
if (err != 0) {
state_ = State::FAILED;
HELPER_LOG("Setting nonblocking failed with errno %d", errno);
return APIError::TCP_NONBLOCKING_FAILED;
}
int enable = 1;
err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
if (err != 0) {
state_ = State::FAILED;
HELPER_LOG("Setting nodelay failed with errno %d", errno);
return APIError::TCP_NODELAY_FAILED;
}
// init prologue
prologue_.insert(prologue_.end(), PROLOGUE_INIT, PROLOGUE_INIT + strlen(PROLOGUE_INIT));
state_ = State::CLIENT_HELLO;
return APIError::OK;
}
/// Run through handshake messages (if in that phase)
APIError APINoiseFrameHelper::loop() {
APIError err = state_action_();
if (err == APIError::WOULD_BLOCK)
return APIError::OK;
if (err != APIError::OK)
return err;
if (!tx_buf_.empty()) {
err = try_send_tx_buf_();
if (err != APIError::OK) {
return err;
}
}
return APIError::OK;
}
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
*
* @param frame: The struct to hold the frame information in.
* msg_start: points to the start of the payload - this pointer is only valid until the next
* try_receive_raw_ call
*
* @return 0 if a full packet is in rx_buf_
* @return -1 if error, check errno.
*
* errno EWOULDBLOCK: Packet could not be read without blocking. Try again later.
* errno ENOMEM: Not enough memory for reading packet.
* errno API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
* errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase.
*/
APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
int err;
APIError aerr;
if (frame == nullptr) {
HELPER_LOG("Bad argument for try_read_frame_");
return APIError::BAD_ARG;
}
// read header
if (rx_header_buf_len_ < 3) {
// no header information yet
size_t to_read = 3 - rx_header_buf_len_;
ssize_t received = socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read);
if (is_would_block(received)) {
return APIError::WOULD_BLOCK;
} else if (received == -1) {
state_ = State::FAILED;
HELPER_LOG("Socket read failed with errno %d", errno);
return APIError::SOCKET_READ_FAILED;
}
rx_header_buf_len_ += received;
if (received != to_read) {
// not a full read
return APIError::WOULD_BLOCK;
}
// header reading done
}
// read body
uint8_t indicator = rx_header_buf_[0];
if (indicator != 0x01) {
state_ = State::FAILED;
HELPER_LOG("Bad indicator byte %u", indicator);
return APIError::BAD_INDICATOR;
}
uint16_t msg_size = (((uint16_t) rx_header_buf_[1]) << 8) | rx_header_buf_[2];
if (state_ != State::DATA && msg_size > 128) {
// for handshake message only permit up to 128 byte
state_ = State::FAILED;
HELPER_LOG("Bad packet len for handshake: %d", msg_size);
return APIError::BAD_HANDSHAKE_PACKET_LEN;
}
// reserve space for body
if (rx_buf_.size() != msg_size) {
rx_buf_.resize(msg_size);
}
if (rx_buf_len_ < msg_size) {
// more data to read
size_t to_read = msg_size - rx_buf_len_;
ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read);
if (is_would_block(received)) {
return APIError::WOULD_BLOCK;
} else if (received == -1) {
state_ = State::FAILED;
HELPER_LOG("Socket read failed with errno %d", errno);
return APIError::SOCKET_READ_FAILED;
}
rx_buf_len_ += received;
if (received != to_read) {
// not all read
return APIError::WOULD_BLOCK;
}
}
// uncomment for even more debugging
// ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str());
frame->msg = std::move(rx_buf_);
// consume msg
rx_buf_ = {};
rx_buf_len_ = 0;
rx_header_buf_len_ = 0;
return APIError::OK;
}
/** To be called from read/write methods.
*
* This method runs through the internal handshake methods, if in that state.
*
* If the handshake is still active when this method returns and a read/write can't take place at
* the moment, returns WOULD_BLOCK.
* If an error occured, returns that error. Only returns OK if the transport is ready for data
* traffic.
*/
APIError APINoiseFrameHelper::state_action_() {
int err;
APIError aerr;
if (state_ == State::INITIALIZE) {
HELPER_LOG("Bad state for method: %d", (int) state_);
return APIError::BAD_STATE;
}
if (state_ == State::CLIENT_HELLO) {
// waiting for client hello
ParsedFrame frame;
aerr = try_read_frame_(&frame);
if (aerr != APIError::OK)
return aerr;
// ignore contents, may be used in future for flags
prologue_.push_back((uint8_t) (frame.msg.size() >> 8));
prologue_.push_back((uint8_t) frame.msg.size());
prologue_.insert(prologue_.end(), frame.msg.begin(), frame.msg.end());
state_ = State::SERVER_HELLO;
}
if (state_ == State::SERVER_HELLO) {
// send server hello
uint8_t msg[1];
msg[0] = 0x01; // chosen proto
aerr = write_frame_(msg, 1);
if (aerr != APIError::OK)
return aerr;
// start handshake
aerr = init_handshake_();
if (aerr != APIError::OK)
return aerr;
state_ = State::HANDSHAKE;
}
if (state_ == State::HANDSHAKE) {
int action = noise_handshakestate_get_action(handshake_);
if (action == NOISE_ACTION_READ_MESSAGE) {
// waiting for handshake msg
ParsedFrame frame;
aerr = try_read_frame_(&frame);
if (aerr == APIError::BAD_INDICATOR) {
send_explicit_handshake_reject_("Bad indicator byte");
return aerr;
}
if (frame.msg.size() < 1 || frame.msg[0] != 0x00) {
aerr = APIError::BAD_HANDSHAKE_PACKET_LEN;
}
if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
send_explicit_handshake_reject_("Bad handshake packet len");
return aerr;
}
if (aerr != APIError::OK)
return aerr;
NoiseBuffer mbuf;
noise_buffer_init(mbuf);
noise_buffer_set_input(mbuf, frame.msg.data() + 1, frame.msg.size() - 1);
err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr);
if (err != 0) {
// TODO: explicit rejection
state_ = State::FAILED;
HELPER_LOG("noise_handshakestate_read_message failed: %s", noise_err_to_str(err).c_str());
if (err == NOISE_ERROR_MAC_FAILURE) {
send_explicit_handshake_reject_("Handshake MAC failure");
} else {
send_explicit_handshake_reject_("Handshake error");
}
return APIError::HANDSHAKESTATE_READ_FAILED;
}
aerr = check_handshake_finished_();
if (aerr != APIError::OK)
return aerr;
} else if (action == NOISE_ACTION_WRITE_MESSAGE) {
uint8_t buffer[65];
NoiseBuffer mbuf;
noise_buffer_init(mbuf);
noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1);
err = noise_handshakestate_write_message(handshake_, &mbuf, nullptr);
if (err != 0) {
state_ = State::FAILED;
HELPER_LOG("noise_handshakestate_write_message failed: %s", noise_err_to_str(err).c_str());
return APIError::HANDSHAKESTATE_WRITE_FAILED;
}
buffer[0] = 0x00; // success
aerr = write_frame_(buffer, mbuf.size + 1);
if (aerr != APIError::OK)
return aerr;
aerr = check_handshake_finished_();
if (aerr != APIError::OK)
return aerr;
} else {
// bad state for action
state_ = State::FAILED;
HELPER_LOG("Bad action for handshake: %d", action);
return APIError::HANDSHAKESTATE_BAD_STATE;
}
}
if (state_ == State::CLOSED || state_ == State::FAILED) {
return APIError::BAD_STATE;
}
return APIError::OK;
}
void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &reason) {
std::vector<uint8_t> data;
data.reserve(reason.size() + 1);
data[0] = 0x01; // failure
for (size_t i = 0; i < reason.size(); i++) {
data[i+1] = (uint8_t) reason[i];
}
write_frame_(data.data(), data.size());
}
APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
int err;
APIError aerr;
aerr = state_action_();
if (aerr != APIError::OK) {
return aerr;
}
if (state_ != State::DATA) {
return APIError::WOULD_BLOCK;
}
ParsedFrame frame;
aerr = try_read_frame_(&frame);
if (aerr != APIError::OK)
return aerr;
NoiseBuffer mbuf;
noise_buffer_init(mbuf);
noise_buffer_set_inout(mbuf, frame.msg.data(), frame.msg.size(), frame.msg.size());
err = noise_cipherstate_decrypt(recv_cipher_, &mbuf);
if (err != 0) {
state_ = State::FAILED;
HELPER_LOG("noise_cipherstate_decrypt failed: %s", noise_err_to_str(err).c_str());
return APIError::CIPHERSTATE_DECRYPT_FAILED;
}
size_t msg_size = mbuf.size;
uint8_t *msg_data = frame.msg.data();
if (msg_size < 4) {
state_ = State::FAILED;
HELPER_LOG("Bad data packet: size %d too short", msg_size);
return APIError::BAD_DATA_PACKET;
}
// uint16_t type;
// uint16_t data_len;
// uint8_t *data;
// uint8_t *padding; zero or more bytes to fill up the rest of the packet
uint16_t type = (((uint16_t) msg_data[0]) << 8) | msg_data[1];
uint16_t data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3];
if (data_len > msg_size - 4) {
state_ = State::FAILED;
HELPER_LOG("Bad data packet: data_len %u greater than msg_size %u", data_len, msg_size);
return APIError::BAD_DATA_PACKET;
}
buffer->container = std::move(frame.msg);
buffer->data_offset = 4;
buffer->data_len = data_len;
buffer->type = type;
return APIError::OK;
}
bool APINoiseFrameHelper::can_write_without_blocking() {
return state_ == State::DATA && tx_buf_.empty();
}
APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) {
int err;
APIError aerr;
aerr = state_action_();
if (aerr != APIError::OK) {
return aerr;
}
if (state_ != State::DATA) {
return APIError::WOULD_BLOCK;
}
size_t padding = 0;
size_t msg_len = 4 + payload_len + padding;
size_t frame_len = 3 + msg_len + noise_cipherstate_get_mac_length(send_cipher_);
auto tmpbuf = std::unique_ptr<uint8_t[]>{new (std::nothrow) uint8_t[frame_len]};
if (tmpbuf == nullptr) {
HELPER_LOG("Could not allocate for writing packet");
return APIError::OUT_OF_MEMORY;
}
tmpbuf[0] = 0x01; // indicator
// tmpbuf[1], tmpbuf[2] to be set later
const uint8_t msg_offset = 3;
const uint8_t payload_offset = msg_offset + 4;
tmpbuf[msg_offset + 0] = (uint8_t) (type >> 8); // type
tmpbuf[msg_offset + 1] = (uint8_t) type;
tmpbuf[msg_offset + 2] = (uint8_t) (payload_len >> 8); // data_len
tmpbuf[msg_offset + 3] = (uint8_t) payload_len;
// copy data
std::copy(payload, payload + payload_len, &tmpbuf[payload_offset]);
// fill padding with zeros
std::fill(&tmpbuf[payload_offset + payload_len], &tmpbuf[frame_len], 0);
NoiseBuffer mbuf;
noise_buffer_init(mbuf);
noise_buffer_set_inout(mbuf, &tmpbuf[msg_offset], msg_len, frame_len - msg_offset);
err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
if (err != 0) {
state_ = State::FAILED;
HELPER_LOG("noise_cipherstate_encrypt failed: %s", noise_err_to_str(err).c_str());
return APIError::CIPHERSTATE_ENCRYPT_FAILED;
}
size_t total_len = 3 + mbuf.size;
tmpbuf[1] = (uint8_t) (mbuf.size >> 8);
tmpbuf[2] = (uint8_t) mbuf.size;
// write raw to not have two packets sent if NAGLE disabled
aerr = write_raw_(&tmpbuf[0], total_len);
if (aerr != APIError::OK) {
return aerr;
}
return APIError::OK;
}
APIError APINoiseFrameHelper::try_send_tx_buf_() {
// try send from tx_buf
while (state_ != State::CLOSED && !tx_buf_.empty()) {
ssize_t sent = socket_->write(tx_buf_.data(), tx_buf_.size());
if (sent == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN)
break;
state_ = State::FAILED;
HELPER_LOG("Socket write failed with errno %d", errno);
return APIError::SOCKET_WRITE_FAILED;
} else if (sent == 0) {
break;
}
// TODO: inefficient if multiple packets in txbuf
// replace with deque of buffers
tx_buf_.erase(tx_buf_.begin(), tx_buf_.begin() + sent);
}
return APIError::OK;
}
/** Write the data to the socket, or buffer it a write would block
*
* @param data The data to write
* @param len The length of data
*/
APIError APINoiseFrameHelper::write_raw_(const uint8_t *data, size_t len) {
if (len == 0)
return APIError::OK;
int err;
APIError aerr;
// uncomment for even more debugging
// ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str());
if (!tx_buf_.empty()) {
// try to empty tx_buf_ first
aerr = try_send_tx_buf_();
if (aerr != APIError::OK && aerr != APIError::WOULD_BLOCK)
return aerr;
}
if (!tx_buf_.empty()) {
// tx buf not empty, can't write now because then stream would be inconsistent
tx_buf_.insert(tx_buf_.end(), data, data + len);
return APIError::OK;
}
ssize_t sent = socket_->write(data, len);
if (is_would_block(sent)) {
// operation would block, add buffer to tx_buf
tx_buf_.insert(tx_buf_.end(), data, data + len);
return APIError::OK;
} else if (sent == -1) {
// an error occured
state_ = State::FAILED;
HELPER_LOG("Socket write failed with errno %d", errno);
return APIError::SOCKET_WRITE_FAILED;
} else if (sent != len) {
// partially sent, add end to tx_buf
tx_buf_.insert(tx_buf_.end(), data + sent, data + len);
return APIError::OK;
}
// fully sent
return APIError::OK;
}
APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) {
APIError aerr;
uint8_t header[3];
header[0] = 0x01; // indicator
header[1] = (uint8_t) (len >> 8);
header[2] = (uint8_t) len;
aerr = write_raw_(header, 3);
if (aerr != APIError::OK)
return aerr;
aerr = write_raw_(data, len);
return aerr;
}
/** Initiate the data structures for the handshake.
*
* @return 0 on success, -1 on error (check errno)
*/
APIError APINoiseFrameHelper::init_handshake_() {
int err;
memset(&nid_, 0, sizeof(nid_));
// const char *proto = "Noise_NNpsk0_25519_ChaChaPoly_SHA256";
// err = noise_protocol_name_to_id(&nid_, proto, strlen(proto));
nid_.pattern_id = NOISE_PATTERN_NN;
nid_.cipher_id = NOISE_CIPHER_CHACHAPOLY;
nid_.dh_id = NOISE_DH_CURVE25519;
nid_.prefix_id = NOISE_PREFIX_STANDARD;
nid_.hybrid_id = NOISE_DH_NONE;
nid_.hash_id = NOISE_HASH_SHA256;
nid_.modifier_ids[0] = NOISE_MODIFIER_PSK0;
err = noise_handshakestate_new_by_id(&handshake_, &nid_, NOISE_ROLE_RESPONDER);
if (err != 0) {
state_ = State::FAILED;
HELPER_LOG("noise_handshakestate_new_by_id failed: %s", noise_err_to_str(err).c_str());
return APIError::HANDSHAKESTATE_SETUP_FAILED;
}
const auto &psk = ctx_->get_psk();
err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size());
if (err != 0) {
state_ = State::FAILED;
HELPER_LOG("noise_handshakestate_set_pre_shared_key failed: %s", noise_err_to_str(err).c_str());
return APIError::HANDSHAKESTATE_SETUP_FAILED;
}
err = noise_handshakestate_set_prologue(handshake_, prologue_.data(), prologue_.size());
if (err != 0) {
state_ = State::FAILED;
HELPER_LOG("noise_handshakestate_set_prologue failed: %s", noise_err_to_str(err).c_str());
return APIError::HANDSHAKESTATE_SETUP_FAILED;
}
// set_prologue copies it into handshakestate, so we can get rid of it now
prologue_ = {};
err = noise_handshakestate_start(handshake_);
if (err != 0) {
state_ = State::FAILED;
HELPER_LOG("noise_handshakestate_start failed: %s", noise_err_to_str(err).c_str());
return APIError::HANDSHAKESTATE_SETUP_FAILED;
}
return APIError::OK;
}
APIError APINoiseFrameHelper::check_handshake_finished_() {
assert(state_ == State::HANDSHAKE);
int action = noise_handshakestate_get_action(handshake_);
if (action == NOISE_ACTION_READ_MESSAGE || action == NOISE_ACTION_WRITE_MESSAGE)
return APIError::OK;
if (action != NOISE_ACTION_SPLIT) {
state_ = State::FAILED;
HELPER_LOG("Bad action for handshake: %d", action);
return APIError::HANDSHAKESTATE_BAD_STATE;
}
int err = noise_handshakestate_split(handshake_, &send_cipher_, &recv_cipher_);
if (err != 0) {
state_ = State::FAILED;
HELPER_LOG("noise_handshakestate_split failed: %s", noise_err_to_str(err).c_str());
return APIError::HANDSHAKESTATE_SPLIT_FAILED;
}
HELPER_LOG("Handshake complete!");
noise_handshakestate_free(handshake_);
handshake_ = nullptr;
state_ = State::DATA;
return APIError::OK;
}
APINoiseFrameHelper::~APINoiseFrameHelper() {
if (handshake_ != nullptr) {
noise_handshakestate_free(handshake_);
handshake_ = nullptr;
}
if (send_cipher_ != nullptr) {
noise_cipherstate_free(send_cipher_);
send_cipher_ = nullptr;
}
if (recv_cipher_ != nullptr) {
noise_cipherstate_free(recv_cipher_);
recv_cipher_ = nullptr;
}
}
APIError APINoiseFrameHelper::close() {
state_ = State::CLOSED;
int err = socket_->close();
if (err == -1)
return APIError::CLOSE_FAILED;
return APIError::OK;
}
APIError APINoiseFrameHelper::shutdown(int how) {
int err = socket_->shutdown(how);
if (err == -1)
return APIError::SHUTDOWN_FAILED;
if (how == SHUT_RDWR) {
state_ = State::CLOSED;
}
return APIError::OK;
}
extern "C" {
// declare how noise generates random bytes (here with a good HWRNG based on the RF system)
void noise_rand_bytes(void *output, size_t len) {
esphome::fill_random(reinterpret_cast<uint8_t *>(output), len);
}
}
#endif // USE_API_NOISE
#ifdef USE_API_PLAINTEXT
/// Initialize the frame helper, returns OK if successful.
APIError APIPlaintextFrameHelper::init() {
if (state_ != State::INITIALIZE || socket_ == nullptr) {
HELPER_LOG("Bad state for init %d", (int) state_);
return APIError::BAD_STATE;
}
int err = socket_->setblocking(false);
if (err != 0) {
state_ = State::FAILED;
HELPER_LOG("Setting nonblocking failed with errno %d", errno);
return APIError::TCP_NONBLOCKING_FAILED;
}
int enable = 1;
err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
if (err != 0) {
state_ = State::FAILED;
HELPER_LOG("Setting nodelay failed with errno %d", errno);
return APIError::TCP_NODELAY_FAILED;
}
state_ = State::DATA;
return APIError::OK;
}
/// Not used for plaintext
APIError APIPlaintextFrameHelper::loop() {
if (state_ != State::DATA) {
return APIError::BAD_STATE;
}
// try send pending TX data
if (!tx_buf_.empty()) {
APIError err = try_send_tx_buf_();
if (err != APIError::OK) {
return err;
}
}
return APIError::OK;
}
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
*
* @param frame: The struct to hold the frame information in.
* msg: store the parsed frame in that struct
*
* @return See APIError
*
* error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
*/
APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
int err;
APIError aerr;
if (frame == nullptr) {
HELPER_LOG("Bad argument for try_read_frame_");
return APIError::BAD_ARG;
}
// read header
while (!rx_header_parsed_) {
uint8_t data;
ssize_t received = socket_->read(&data, 1);
if (is_would_block(received)) {
return APIError::WOULD_BLOCK;
} else if (received == -1) {
state_ = State::FAILED;
HELPER_LOG("Socket read failed with errno %d", errno);
return APIError::SOCKET_READ_FAILED;
}
rx_header_buf_.push_back(data);
// try parse header
if (rx_header_buf_[0] != 0x00) {
state_ = State::FAILED;
HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]);
return APIError::BAD_INDICATOR;
}
size_t i = 1;
size_t consumed = 0;
auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[i], rx_header_buf_.size() - i, &consumed);
if (!msg_size_varint.has_value()) {
// not enough data there yet
continue;
}
i += consumed;
rx_header_parsed_len_ = msg_size_varint->as_uint32();
auto msg_type_varint = ProtoVarInt::parse(&rx_header_buf_[i], rx_header_buf_.size() - i, &consumed);
if (!msg_type_varint.has_value()) {
// not enough data there yet
continue;
}
rx_header_parsed_type_ = msg_type_varint->as_uint32();
rx_header_parsed_ = true;
}
// header reading done
// reserve space for body
if (rx_buf_.size() != rx_header_parsed_len_) {
rx_buf_.resize(rx_header_parsed_len_);
}
if (rx_buf_len_ < rx_header_parsed_len_) {
// more data to read
size_t to_read = rx_header_parsed_len_ - rx_buf_len_;
ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read);
if (is_would_block(received)) {
return APIError::WOULD_BLOCK;
} else if (received == -1) {
state_ = State::FAILED;
HELPER_LOG("Socket read failed with errno %d", errno);
return APIError::SOCKET_READ_FAILED;
}
rx_buf_len_ += received;
if (received != to_read) {
// not all read
return APIError::WOULD_BLOCK;
}
}
// uncomment for even more debugging
// ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str());
frame->msg = std::move(rx_buf_);
// consume msg
rx_buf_ = {};
rx_buf_len_ = 0;
rx_header_buf_.clear();
rx_header_parsed_ = false;
return APIError::OK;
}
APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
int err;
APIError aerr;
if (state_ != State::DATA) {
return APIError::WOULD_BLOCK;
}
ParsedFrame frame;
aerr = try_read_frame_(&frame);
if (aerr != APIError::OK)
return aerr;
buffer->container = std::move(frame.msg);
buffer->data_offset = 0;
buffer->data_len = rx_header_parsed_len_;
buffer->type = rx_header_parsed_type_;
return APIError::OK;
}
bool APIPlaintextFrameHelper::can_write_without_blocking() {
return state_ == State::DATA && tx_buf_.empty();
}
APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) {
int err;
APIError aerr;
if (state_ != State::DATA) {
return APIError::BAD_STATE;
}
std::vector<uint8_t> header;
header.push_back(0x00);
ProtoVarInt(payload_len).encode(header);
ProtoVarInt(type).encode(header);
aerr = write_raw_(&header[0], header.size());
if (aerr != APIError::OK) {
return aerr;
}
aerr = write_raw_(payload, payload_len);
if (aerr != APIError::OK) {
return aerr;
}
return APIError::OK;
}
APIError APIPlaintextFrameHelper::try_send_tx_buf_() {
// try send from tx_buf
while (state_ != State::CLOSED && !tx_buf_.empty()) {
ssize_t sent = socket_->write(tx_buf_.data(), tx_buf_.size());
if (sent == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN)
break;
state_ = State::FAILED;
HELPER_LOG("Socket write failed with errno %d", errno);
return APIError::SOCKET_WRITE_FAILED;
} else if (sent == 0) {
break;
}
// TODO: inefficient if multiple packets in txbuf
// replace with deque of buffers
tx_buf_.erase(tx_buf_.begin(), tx_buf_.begin() + sent);
}
return APIError::OK;
}
/** Write the data to the socket, or buffer it a write would block
*
* @param data The data to write
* @param len The length of data
*/
APIError APIPlaintextFrameHelper::write_raw_(const uint8_t *data, size_t len) {
if (len == 0)
return APIError::OK;
int err;
APIError aerr;
// uncomment for even more debugging
// ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str());
if (!tx_buf_.empty()) {
// try to empty tx_buf_ first
aerr = try_send_tx_buf_();
if (aerr != APIError::OK && aerr != APIError::WOULD_BLOCK)
return aerr;
}
if (!tx_buf_.empty()) {
// tx buf not empty, can't write now because then stream would be inconsistent
tx_buf_.insert(tx_buf_.end(), data, data + len);
return APIError::OK;
}
ssize_t sent = socket_->write(data, len);
if (is_would_block(sent)) {
// operation would block, add buffer to tx_buf
tx_buf_.insert(tx_buf_.end(), data, data + len);
return APIError::OK;
} else if (sent == -1) {
// an error occured
state_ = State::FAILED;
HELPER_LOG("Socket write failed with errno %d", errno);
return APIError::SOCKET_WRITE_FAILED;
} else if (sent != len) {
// partially sent, add end to tx_buf
tx_buf_.insert(tx_buf_.end(), data + sent, data + len);
return APIError::OK;
}
// fully sent
return APIError::OK;
}
APIError APIPlaintextFrameHelper::write_frame_(const uint8_t *data, size_t len) {
APIError aerr;
uint8_t header[3];
header[0] = 0x01; // indicator
header[1] = (uint8_t) (len >> 8);
header[2] = (uint8_t) len;
aerr = write_raw_(header, 3);
if (aerr != APIError::OK)
return aerr;
aerr = write_raw_(data, len);
return aerr;
}
APIError APIPlaintextFrameHelper::close() {
state_ = State::CLOSED;
int err = socket_->close();
if (err == -1)
return APIError::CLOSE_FAILED;
return APIError::OK;
}
APIError APIPlaintextFrameHelper::shutdown(int how) {
int err = socket_->shutdown(how);
if (err == -1)
return APIError::SHUTDOWN_FAILED;
if (how == SHUT_RDWR) {
state_ = State::CLOSED;
}
return APIError::OK;
}
#endif // USE_API_PLAINTEXT
} // namespace api
} // namespace esphome

View File

@@ -0,0 +1,186 @@
#pragma once
#include <cstdint>
#include <vector>
#include <deque>
#include "esphome/core/defines.h"
#ifdef USE_API_NOISE
#include "noise/protocol.h"
#endif
#include "esphome/components/socket/socket.h"
#include "api_noise_context.h"
namespace esphome {
namespace api {
struct ReadPacketBuffer {
std::vector<uint8_t> container;
uint16_t type;
size_t data_offset;
size_t data_len;
};
struct PacketBuffer {
const std::vector<uint8_t> container;
uint16_t type;
uint8_t data_offset;
uint8_t data_len;
};
enum class APIError : int {
OK = 0,
WOULD_BLOCK = 1001,
BAD_HANDSHAKE_PACKET_LEN = 1002,
BAD_INDICATOR = 1003,
BAD_DATA_PACKET = 1004,
TCP_NODELAY_FAILED = 1005,
TCP_NONBLOCKING_FAILED = 1006,
CLOSE_FAILED = 1007,
SHUTDOWN_FAILED = 1008,
BAD_STATE = 1009,
BAD_ARG = 1010,
SOCKET_READ_FAILED = 1011,
SOCKET_WRITE_FAILED = 1012,
HANDSHAKESTATE_READ_FAILED = 1013,
HANDSHAKESTATE_WRITE_FAILED = 1014,
HANDSHAKESTATE_BAD_STATE = 1015,
CIPHERSTATE_DECRYPT_FAILED = 1016,
CIPHERSTATE_ENCRYPT_FAILED = 1017,
OUT_OF_MEMORY = 1018,
HANDSHAKESTATE_SETUP_FAILED = 1019,
HANDSHAKESTATE_SPLIT_FAILED = 1020,
};
class APIFrameHelper {
public:
virtual APIError init() = 0;
virtual APIError loop() = 0;
virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
virtual bool can_write_without_blocking() = 0;
virtual APIError write_packet(uint16_t type, const uint8_t *data, size_t len) = 0;
virtual std::string getpeername() = 0;
virtual APIError close() = 0;
virtual APIError shutdown(int how) = 0;
// Give this helper a name for logging
virtual void set_log_info(std::string info) = 0;
};
#ifdef USE_API_NOISE
class APINoiseFrameHelper : public APIFrameHelper {
public:
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx) : socket_(std::move(socket)), ctx_(ctx) {}
~APINoiseFrameHelper();
APIError init() override;
APIError loop() override;
APIError read_packet(ReadPacketBuffer *buffer) override;
bool can_write_without_blocking() override;
APIError write_packet(uint16_t type, const uint8_t *data, size_t len) override;
std::string getpeername() override{
return socket_->getpeername();
}
APIError close() override;
APIError shutdown(int how) override;
// Give this helper a name for logging
void set_log_info(std::string info) override {
info_ = std::move(info);
}
protected:
struct ParsedFrame {
std::vector<uint8_t> msg;
};
APIError state_action_();
APIError try_read_frame_(ParsedFrame *frame);
APIError try_send_tx_buf_();
APIError write_frame_(const uint8_t *data, size_t len);
APIError write_raw_(const uint8_t *data, size_t len);
APIError init_handshake_();
APIError check_handshake_finished_();
void send_explicit_handshake_reject_(const std::string &reason);
std::unique_ptr<socket::Socket> socket_;
std::string info_;
uint8_t rx_header_buf_[3];
size_t rx_header_buf_len_ = 0;
std::vector<uint8_t> rx_buf_;
size_t rx_buf_len_ = 0;
std::vector<uint8_t> tx_buf_;
std::vector<uint8_t> prologue_;
std::shared_ptr<APINoiseContext> ctx_;
NoiseHandshakeState *handshake_ = nullptr;
NoiseCipherState *send_cipher_ = nullptr;
NoiseCipherState *recv_cipher_ = nullptr;
NoiseProtocolId nid_;
enum class State {
INITIALIZE = 1,
CLIENT_HELLO = 2,
SERVER_HELLO = 3,
HANDSHAKE = 4,
DATA = 5,
CLOSED = 6,
FAILED = 7,
} state_ = State::INITIALIZE;
};
#endif // USE_API_NOISE
#ifdef USE_API_PLAINTEXT
class APIPlaintextFrameHelper : public APIFrameHelper {
public:
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : socket_(std::move(socket)) {}
~APIPlaintextFrameHelper() = default;
APIError init() override;
APIError loop() override;
APIError read_packet(ReadPacketBuffer *buffer) override;
bool can_write_without_blocking() override;
APIError write_packet(uint16_t type, const uint8_t *data, size_t len) override;
std::string getpeername() override {
return socket_->getpeername();
}
APIError close() override;
APIError shutdown(int how) override;
// Give this helper a name for logging
void set_log_info(std::string info) override {
info_ = std::move(info);
}
protected:
struct ParsedFrame {
std::vector<uint8_t> msg;
};
APIError try_read_frame_(ParsedFrame *frame);
APIError try_send_tx_buf_();
APIError write_frame_(const uint8_t *data, size_t len);
APIError write_raw_(const uint8_t *data, size_t len);
std::unique_ptr<socket::Socket> socket_;
std::string info_;
std::vector<uint8_t> rx_header_buf_;
bool rx_header_parsed_ = false;
uint32_t rx_header_parsed_type_ = 0;
uint32_t rx_header_parsed_len_ = 0;
std::vector<uint8_t> rx_buf_;
size_t rx_buf_len_ = 0;
std::vector<uint8_t> tx_buf_;
enum class State {
INITIALIZE = 1,
DATA = 2,
CLOSED = 3,
FAILED = 4,
} state_ = State::INITIALIZE;
};
#endif
} // namespace api
} // namespace esphome

View File

@@ -0,0 +1,27 @@
#pragma once
#include <cstdint>
#include <array>
#include "esphome/core/defines.h"
namespace esphome {
namespace api {
#ifdef USE_API_NOISE
using psk_t = std::array<uint8_t, 32>;
class APINoiseContext {
public:
void set_psk(psk_t psk) {
psk_ = std::move(psk);
}
const psk_t &get_psk() const {
return psk_;
}
protected:
psk_t psk_;
};
#endif // USE_API_NOISE
} // namespace api
} // namespace esphome

View File

@@ -62,6 +62,54 @@ template<> const char *proto_enum_to_string<enums::FanDirection>(enums::FanDirec
return "UNKNOWN";
}
}
template<> const char *proto_enum_to_string<enums::ColorMode>(enums::ColorMode value) {
switch (value) {
case enums::COLOR_MODE_UNKNOWN:
return "COLOR_MODE_UNKNOWN";
case enums::COLOR_MODE_ON_OFF:
return "COLOR_MODE_ON_OFF";
case enums::COLOR_MODE_BRIGHTNESS:
return "COLOR_MODE_BRIGHTNESS";
case enums::COLOR_MODE_WHITE:
return "COLOR_MODE_WHITE";
case enums::COLOR_MODE_COLOR_TEMPERATURE:
return "COLOR_MODE_COLOR_TEMPERATURE";
case enums::COLOR_MODE_COLD_WARM_WHITE:
return "COLOR_MODE_COLD_WARM_WHITE";
case enums::COLOR_MODE_RGB:
return "COLOR_MODE_RGB";
case enums::COLOR_MODE_RGB_WHITE:
return "COLOR_MODE_RGB_WHITE";
case enums::COLOR_MODE_RGB_COLOR_TEMPERATURE:
return "COLOR_MODE_RGB_COLOR_TEMPERATURE";
case enums::COLOR_MODE_RGB_COLD_WARM_WHITE:
return "COLOR_MODE_RGB_COLD_WARM_WHITE";
default:
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::SensorLastResetType>(enums::SensorLastResetType value) {
switch (value) {
case enums::LAST_RESET_NONE:
return "LAST_RESET_NONE";
case enums::LAST_RESET_NEVER:
return "LAST_RESET_NEVER";
case enums::LAST_RESET_AUTO:
return "LAST_RESET_AUTO";
default:
return "UNKNOWN";
}
}
template<> const char *proto_enum_to_string<enums::LogLevel>(enums::LogLevel value) {
switch (value) {
case enums::LOG_LEVEL_NONE:
@@ -108,8 +156,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 +166,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 +228,28 @@ 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_NONE:
return "CLIMATE_PRESET_NONE";
case enums::CLIMATE_PRESET_HOME:
return "CLIMATE_PRESET_HOME";
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_ECO:
return "CLIMATE_PRESET_ECO";
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: {
@@ -189,6 +261,7 @@ bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value)
}
}
void HelloRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->client_info); }
#ifdef HAS_PROTO_MESSAGE_DUMP
void HelloRequest::dump_to(std::string &out) const {
char buffer[64];
out.append("HelloRequest {\n");
@@ -197,6 +270,7 @@ void HelloRequest::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
#endif
bool HelloResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
@@ -226,6 +300,7 @@ void HelloResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(2, this->api_version_minor);
buffer.encode_string(3, this->server_info);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void HelloResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("HelloResponse {\n");
@@ -244,6 +319,7 @@ void HelloResponse::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
#endif
bool ConnectRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
@@ -255,6 +331,7 @@ bool ConnectRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value
}
}
void ConnectRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->password); }
#ifdef HAS_PROTO_MESSAGE_DUMP
void ConnectRequest::dump_to(std::string &out) const {
char buffer[64];
out.append("ConnectRequest {\n");
@@ -263,6 +340,7 @@ void ConnectRequest::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
#endif
bool ConnectResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
@@ -274,6 +352,7 @@ bool ConnectResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
}
}
void ConnectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); }
#ifdef HAS_PROTO_MESSAGE_DUMP
void ConnectResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("ConnectResponse {\n");
@@ -282,16 +361,27 @@ void ConnectResponse::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
#endif
void DisconnectRequest::encode(ProtoWriteBuffer buffer) const {}
#ifdef HAS_PROTO_MESSAGE_DUMP
void DisconnectRequest::dump_to(std::string &out) const { out.append("DisconnectRequest {}"); }
#endif
void DisconnectResponse::encode(ProtoWriteBuffer buffer) const {}
#ifdef HAS_PROTO_MESSAGE_DUMP
void DisconnectResponse::dump_to(std::string &out) const { out.append("DisconnectResponse {}"); }
#endif
void PingRequest::encode(ProtoWriteBuffer buffer) const {}
#ifdef HAS_PROTO_MESSAGE_DUMP
void PingRequest::dump_to(std::string &out) const { out.append("PingRequest {}"); }
#endif
void PingResponse::encode(ProtoWriteBuffer buffer) const {}
#ifdef HAS_PROTO_MESSAGE_DUMP
void PingResponse::dump_to(std::string &out) const { out.append("PingResponse {}"); }
#endif
void DeviceInfoRequest::encode(ProtoWriteBuffer buffer) const {}
#ifdef HAS_PROTO_MESSAGE_DUMP
void DeviceInfoRequest::dump_to(std::string &out) const { out.append("DeviceInfoRequest {}"); }
#endif
bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
@@ -328,6 +418,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,7 +438,10 @@ 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);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void DeviceInfoResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("DeviceInfoResponse {\n");
@@ -371,20 +472,39 @@ 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("}");
}
#endif
void ListEntitiesRequest::encode(ProtoWriteBuffer buffer) const {}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesRequest::dump_to(std::string &out) const { out.append("ListEntitiesRequest {}"); }
#endif
void ListEntitiesDoneResponse::encode(ProtoWriteBuffer buffer) const {}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesDoneResponse::dump_to(std::string &out) const { out.append("ListEntitiesDoneResponse {}"); }
#endif
void SubscribeStatesRequest::encode(ProtoWriteBuffer buffer) const {}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeStatesRequest::dump_to(std::string &out) const { out.append("SubscribeStatesRequest {}"); }
#endif
bool ListEntitiesBinarySensorResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 6: {
this->is_status_binary_sensor = value.as_bool();
return true;
}
case 7: {
this->disabled_by_default = value.as_bool();
return true;
}
default:
return false;
}
@@ -428,7 +548,9 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->device_class);
buffer.encode_bool(6, this->is_status_binary_sensor);
buffer.encode_bool(7, this->disabled_by_default);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("ListEntitiesBinarySensorResponse {\n");
@@ -456,8 +578,13 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
out.append(" is_status_binary_sensor: ");
out.append(YESNO(this->is_status_binary_sensor));
out.append("\n");
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append("}");
}
#endif
bool BinarySensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
@@ -487,6 +614,7 @@ void BinarySensorStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(2, this->state);
buffer.encode_bool(3, this->missing_state);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BinarySensorStateResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("BinarySensorStateResponse {\n");
@@ -504,6 +632,7 @@ void BinarySensorStateResponse::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
#endif
bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 5: {
@@ -518,6 +647,10 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val
this->supports_tilt = value.as_bool();
return true;
}
case 9: {
this->disabled_by_default = value.as_bool();
return true;
}
default:
return false;
}
@@ -563,7 +696,9 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(6, this->supports_position);
buffer.encode_bool(7, this->supports_tilt);
buffer.encode_string(8, this->device_class);
buffer.encode_bool(9, this->disabled_by_default);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesCoverResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("ListEntitiesCoverResponse {\n");
@@ -599,8 +734,13 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const {
out.append(" device_class: ");
out.append("'").append(this->device_class).append("'");
out.append("\n");
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append("}");
}
#endif
bool CoverStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
@@ -640,6 +780,7 @@ void CoverStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_float(4, this->tilt);
buffer.encode_enum<enums::CoverOperation>(5, this->current_operation);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void CoverStateResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("CoverStateResponse {\n");
@@ -667,6 +808,7 @@ void CoverStateResponse::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
#endif
bool CoverCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
@@ -721,6 +863,7 @@ void CoverCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_float(7, this->tilt);
buffer.encode_bool(8, this->stop);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void CoverCommandRequest::dump_to(std::string &out) const {
char buffer[64];
out.append("CoverCommandRequest {\n");
@@ -760,6 +903,7 @@ void CoverCommandRequest::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
#endif
bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 5: {
@@ -778,6 +922,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value
this->supported_speed_count = value.as_int32();
return true;
}
case 9: {
this->disabled_by_default = value.as_bool();
return true;
}
default:
return false;
}
@@ -819,7 +967,9 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(6, this->supports_speed);
buffer.encode_bool(7, this->supports_direction);
buffer.encode_int32(8, this->supported_speed_count);
buffer.encode_bool(9, this->disabled_by_default);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesFanResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("ListEntitiesFanResponse {\n");
@@ -856,8 +1006,13 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
sprintf(buffer, "%d", this->supported_speed_count);
out.append(buffer);
out.append("\n");
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append("}");
}
#endif
bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
@@ -902,6 +1057,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_enum<enums::FanDirection>(5, this->direction);
buffer.encode_int32(6, this->speed_level);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void FanStateResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("FanStateResponse {\n");
@@ -932,6 +1088,7 @@ void FanStateResponse::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
#endif
bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
@@ -1001,6 +1158,7 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(10, this->has_speed_level);
buffer.encode_int32(11, this->speed_level);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void FanCommandRequest::dump_to(std::string &out) const {
char buffer[64];
out.append("FanCommandRequest {\n");
@@ -1051,22 +1209,31 @@ void FanCommandRequest::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
#endif
bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 12: {
this->supported_color_modes.push_back(value.as_enum<enums::ColorMode>());
return true;
}
case 5: {
this->supports_brightness = value.as_bool();
this->legacy_supports_brightness = value.as_bool();
return true;
}
case 6: {
this->supports_rgb = value.as_bool();
this->legacy_supports_rgb = value.as_bool();
return true;
}
case 7: {
this->supports_white_value = value.as_bool();
this->legacy_supports_white_value = value.as_bool();
return true;
}
case 8: {
this->supports_color_temperature = value.as_bool();
this->legacy_supports_color_temperature = value.as_bool();
return true;
}
case 13: {
this->disabled_by_default = value.as_bool();
return true;
}
default:
@@ -1118,16 +1285,21 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_bool(5, this->supports_brightness);
buffer.encode_bool(6, this->supports_rgb);
buffer.encode_bool(7, this->supports_white_value);
buffer.encode_bool(8, this->supports_color_temperature);
for (auto &it : this->supported_color_modes) {
buffer.encode_enum<enums::ColorMode>(12, it, true);
}
buffer.encode_bool(5, this->legacy_supports_brightness);
buffer.encode_bool(6, this->legacy_supports_rgb);
buffer.encode_bool(7, this->legacy_supports_white_value);
buffer.encode_bool(8, this->legacy_supports_color_temperature);
buffer.encode_float(9, this->min_mireds);
buffer.encode_float(10, this->max_mireds);
for (auto &it : this->effects) {
buffer.encode_string(11, it, true);
}
buffer.encode_bool(13, this->disabled_by_default);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesLightResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("ListEntitiesLightResponse {\n");
@@ -1148,20 +1320,26 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const {
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" supports_brightness: ");
out.append(YESNO(this->supports_brightness));
for (const auto &it : this->supported_color_modes) {
out.append(" supported_color_modes: ");
out.append(proto_enum_to_string<enums::ColorMode>(it));
out.append("\n");
}
out.append(" legacy_supports_brightness: ");
out.append(YESNO(this->legacy_supports_brightness));
out.append("\n");
out.append(" supports_rgb: ");
out.append(YESNO(this->supports_rgb));
out.append(" legacy_supports_rgb: ");
out.append(YESNO(this->legacy_supports_rgb));
out.append("\n");
out.append(" supports_white_value: ");
out.append(YESNO(this->supports_white_value));
out.append(" legacy_supports_white_value: ");
out.append(YESNO(this->legacy_supports_white_value));
out.append("\n");
out.append(" supports_color_temperature: ");
out.append(YESNO(this->supports_color_temperature));
out.append(" legacy_supports_color_temperature: ");
out.append(YESNO(this->legacy_supports_color_temperature));
out.append("\n");
out.append(" min_mireds: ");
@@ -1179,14 +1357,23 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const {
out.append("'").append(it).append("'");
out.append("\n");
}
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append("}");
}
#endif
bool LightStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
this->state = value.as_bool();
return true;
}
case 11: {
this->color_mode = value.as_enum<enums::ColorMode>();
return true;
}
default:
return false;
}
@@ -1211,6 +1398,10 @@ bool LightStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
this->brightness = value.as_float();
return true;
}
case 10: {
this->color_brightness = value.as_float();
return true;
}
case 4: {
this->red = value.as_float();
return true;
@@ -1231,6 +1422,14 @@ bool LightStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
this->color_temperature = value.as_float();
return true;
}
case 12: {
this->cold_white = value.as_float();
return true;
}
case 13: {
this->warm_white = value.as_float();
return true;
}
default:
return false;
}
@@ -1239,13 +1438,18 @@ void LightStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_bool(2, this->state);
buffer.encode_float(3, this->brightness);
buffer.encode_enum<enums::ColorMode>(11, this->color_mode);
buffer.encode_float(10, this->color_brightness);
buffer.encode_float(4, this->red);
buffer.encode_float(5, this->green);
buffer.encode_float(6, this->blue);
buffer.encode_float(7, this->white);
buffer.encode_float(8, this->color_temperature);
buffer.encode_float(12, this->cold_white);
buffer.encode_float(13, this->warm_white);
buffer.encode_string(9, this->effect);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void LightStateResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("LightStateResponse {\n");
@@ -1263,6 +1467,15 @@ void LightStateResponse::dump_to(std::string &out) const {
out.append(buffer);
out.append("\n");
out.append(" color_mode: ");
out.append(proto_enum_to_string<enums::ColorMode>(this->color_mode));
out.append("\n");
out.append(" color_brightness: ");
sprintf(buffer, "%g", this->color_brightness);
out.append(buffer);
out.append("\n");
out.append(" red: ");
sprintf(buffer, "%g", this->red);
out.append(buffer);
@@ -1288,11 +1501,22 @@ void LightStateResponse::dump_to(std::string &out) const {
out.append(buffer);
out.append("\n");
out.append(" cold_white: ");
sprintf(buffer, "%g", this->cold_white);
out.append(buffer);
out.append("\n");
out.append(" warm_white: ");
sprintf(buffer, "%g", this->warm_white);
out.append(buffer);
out.append("\n");
out.append(" effect: ");
out.append("'").append(this->effect).append("'");
out.append("\n");
out.append("}");
}
#endif
bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
@@ -1307,6 +1531,18 @@ bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
this->has_brightness = value.as_bool();
return true;
}
case 22: {
this->has_color_mode = value.as_bool();
return true;
}
case 23: {
this->color_mode = value.as_enum<enums::ColorMode>();
return true;
}
case 20: {
this->has_color_brightness = value.as_bool();
return true;
}
case 6: {
this->has_rgb = value.as_bool();
return true;
@@ -1319,6 +1555,14 @@ bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
this->has_color_temperature = value.as_bool();
return true;
}
case 24: {
this->has_cold_white = value.as_bool();
return true;
}
case 26: {
this->has_warm_white = value.as_bool();
return true;
}
case 14: {
this->has_transition_length = value.as_bool();
return true;
@@ -1363,6 +1607,10 @@ bool LightCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
this->brightness = value.as_float();
return true;
}
case 21: {
this->color_brightness = value.as_float();
return true;
}
case 7: {
this->red = value.as_float();
return true;
@@ -1383,6 +1631,14 @@ bool LightCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
this->color_temperature = value.as_float();
return true;
}
case 25: {
this->cold_white = value.as_float();
return true;
}
case 27: {
this->warm_white = value.as_float();
return true;
}
default:
return false;
}
@@ -1393,6 +1649,10 @@ void LightCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(3, this->state);
buffer.encode_bool(4, this->has_brightness);
buffer.encode_float(5, this->brightness);
buffer.encode_bool(22, this->has_color_mode);
buffer.encode_enum<enums::ColorMode>(23, this->color_mode);
buffer.encode_bool(20, this->has_color_brightness);
buffer.encode_float(21, this->color_brightness);
buffer.encode_bool(6, this->has_rgb);
buffer.encode_float(7, this->red);
buffer.encode_float(8, this->green);
@@ -1401,6 +1661,10 @@ void LightCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_float(11, this->white);
buffer.encode_bool(12, this->has_color_temperature);
buffer.encode_float(13, this->color_temperature);
buffer.encode_bool(24, this->has_cold_white);
buffer.encode_float(25, this->cold_white);
buffer.encode_bool(26, this->has_warm_white);
buffer.encode_float(27, this->warm_white);
buffer.encode_bool(14, this->has_transition_length);
buffer.encode_uint32(15, this->transition_length);
buffer.encode_bool(16, this->has_flash_length);
@@ -1408,6 +1672,7 @@ void LightCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(18, this->has_effect);
buffer.encode_string(19, this->effect);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void LightCommandRequest::dump_to(std::string &out) const {
char buffer[64];
out.append("LightCommandRequest {\n");
@@ -1433,6 +1698,23 @@ void LightCommandRequest::dump_to(std::string &out) const {
out.append(buffer);
out.append("\n");
out.append(" has_color_mode: ");
out.append(YESNO(this->has_color_mode));
out.append("\n");
out.append(" color_mode: ");
out.append(proto_enum_to_string<enums::ColorMode>(this->color_mode));
out.append("\n");
out.append(" has_color_brightness: ");
out.append(YESNO(this->has_color_brightness));
out.append("\n");
out.append(" color_brightness: ");
sprintf(buffer, "%g", this->color_brightness);
out.append(buffer);
out.append("\n");
out.append(" has_rgb: ");
out.append(YESNO(this->has_rgb));
out.append("\n");
@@ -1470,6 +1752,24 @@ void LightCommandRequest::dump_to(std::string &out) const {
out.append(buffer);
out.append("\n");
out.append(" has_cold_white: ");
out.append(YESNO(this->has_cold_white));
out.append("\n");
out.append(" cold_white: ");
sprintf(buffer, "%g", this->cold_white);
out.append(buffer);
out.append("\n");
out.append(" has_warm_white: ");
out.append(YESNO(this->has_warm_white));
out.append("\n");
out.append(" warm_white: ");
sprintf(buffer, "%g", this->warm_white);
out.append(buffer);
out.append("\n");
out.append(" has_transition_length: ");
out.append(YESNO(this->has_transition_length));
out.append("\n");
@@ -1497,6 +1797,7 @@ void LightCommandRequest::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
#endif
bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 7: {
@@ -1507,6 +1808,18 @@ 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;
}
case 11: {
this->last_reset_type = value.as_enum<enums::SensorLastResetType>();
return true;
}
case 12: {
this->disabled_by_default = value.as_bool();
return true;
}
default:
return false;
}
@@ -1561,7 +1874,11 @@ 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);
buffer.encode_enum<enums::SensorLastResetType>(11, this->last_reset_type);
buffer.encode_bool(12, this->disabled_by_default);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSensorResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("ListEntitiesSensorResponse {\n");
@@ -1602,8 +1919,21 @@ 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(" last_reset_type: ");
out.append(proto_enum_to_string<enums::SensorLastResetType>(this->last_reset_type));
out.append("\n");
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append("}");
}
#endif
bool SensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 3: {
@@ -1633,6 +1963,7 @@ void SensorStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_float(2, this->state);
buffer.encode_bool(3, this->missing_state);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SensorStateResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("SensorStateResponse {\n");
@@ -1651,12 +1982,17 @@ void SensorStateResponse::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
#endif
bool ListEntitiesSwitchResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 6: {
this->assumed_state = value.as_bool();
return true;
}
case 7: {
this->disabled_by_default = value.as_bool();
return true;
}
default:
return false;
}
@@ -1700,7 +2036,9 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->assumed_state);
buffer.encode_bool(7, this->disabled_by_default);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("ListEntitiesSwitchResponse {\n");
@@ -1728,8 +2066,13 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
out.append(" assumed_state: ");
out.append(YESNO(this->assumed_state));
out.append("\n");
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append("}");
}
#endif
bool SwitchStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
@@ -1754,6 +2097,7 @@ void SwitchStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_bool(2, this->state);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SwitchStateResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("SwitchStateResponse {\n");
@@ -1767,6 +2111,7 @@ void SwitchStateResponse::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
#endif
bool SwitchCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
@@ -1791,6 +2136,7 @@ void SwitchCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_bool(2, this->state);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SwitchCommandRequest::dump_to(std::string &out) const {
char buffer[64];
out.append("SwitchCommandRequest {\n");
@@ -1804,6 +2150,17 @@ void SwitchCommandRequest::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
#endif
bool ListEntitiesTextSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 6: {
this->disabled_by_default = value.as_bool();
return true;
}
default:
return false;
}
}
bool ListEntitiesTextSensorResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
@@ -1842,7 +2199,9 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("ListEntitiesTextSensorResponse {\n");
@@ -1866,8 +2225,13 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append("}");
}
#endif
bool TextSensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 3: {
@@ -1903,6 +2267,7 @@ void TextSensorStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(2, this->state);
buffer.encode_bool(3, this->missing_state);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void TextSensorStateResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("TextSensorStateResponse {\n");
@@ -1920,6 +2285,7 @@ void TextSensorStateResponse::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
#endif
bool SubscribeLogsRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
@@ -1938,6 +2304,7 @@ void SubscribeLogsRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_enum<enums::LogLevel>(1, this->level);
buffer.encode_bool(2, this->dump_config);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeLogsRequest::dump_to(std::string &out) const {
char buffer[64];
out.append("SubscribeLogsRequest {\n");
@@ -1950,6 +2317,7 @@ void SubscribeLogsRequest::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
#endif
bool SubscribeLogsResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
@@ -1984,6 +2352,7 @@ void SubscribeLogsResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(3, this->message);
buffer.encode_bool(4, this->send_failed);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeLogsResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("SubscribeLogsResponse {\n");
@@ -2004,10 +2373,13 @@ void SubscribeLogsResponse::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
#endif
void SubscribeHomeassistantServicesRequest::encode(ProtoWriteBuffer buffer) const {}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeHomeassistantServicesRequest::dump_to(std::string &out) const {
out.append("SubscribeHomeassistantServicesRequest {}");
}
#endif
bool HomeassistantServiceMap::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
@@ -2026,6 +2398,7 @@ void HomeassistantServiceMap::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->key);
buffer.encode_string(2, this->value);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void HomeassistantServiceMap::dump_to(std::string &out) const {
char buffer[64];
out.append("HomeassistantServiceMap {\n");
@@ -2038,6 +2411,7 @@ void HomeassistantServiceMap::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
#endif
bool HomeassistantServiceResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 5: {
@@ -2083,6 +2457,7 @@ void HomeassistantServiceResponse::encode(ProtoWriteBuffer buffer) const {
}
buffer.encode_bool(5, this->is_event);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void HomeassistantServiceResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("HomeassistantServiceResponse {\n");
@@ -2113,31 +2488,45 @@ void HomeassistantServiceResponse::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
#endif
void SubscribeHomeAssistantStatesRequest::encode(ProtoWriteBuffer buffer) const {}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeHomeAssistantStatesRequest::dump_to(std::string &out) const {
out.append("SubscribeHomeAssistantStatesRequest {}");
}
#endif
bool SubscribeHomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
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);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("SubscribeHomeAssistantStateResponse {\n");
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("}");
}
#endif
bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
@@ -2148,6 +2537,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,7 +2548,9 @@ 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);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void HomeAssistantStateResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("HomeAssistantStateResponse {\n");
@@ -2166,10 +2561,17 @@ 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("}");
}
#endif
void GetTimeRequest::encode(ProtoWriteBuffer buffer) const {}
#ifdef HAS_PROTO_MESSAGE_DUMP
void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeRequest {}"); }
#endif
bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
@@ -2181,6 +2583,7 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
}
}
void GetTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->epoch_seconds); }
#ifdef HAS_PROTO_MESSAGE_DUMP
void GetTimeResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("GetTimeResponse {\n");
@@ -2190,6 +2593,7 @@ void GetTimeResponse::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
#endif
bool ListEntitiesServicesArgument::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
@@ -2214,6 +2618,7 @@ void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->name);
buffer.encode_enum<enums::ServiceArgType>(2, this->type);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesServicesArgument::dump_to(std::string &out) const {
char buffer[64];
out.append("ListEntitiesServicesArgument {\n");
@@ -2226,6 +2631,7 @@ void ListEntitiesServicesArgument::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
#endif
bool ListEntitiesServicesResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
@@ -2257,6 +2663,7 @@ void ListEntitiesServicesResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_message<ListEntitiesServicesArgument>(3, it, true);
}
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesServicesResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("ListEntitiesServicesResponse {\n");
@@ -2276,6 +2683,7 @@ void ListEntitiesServicesResponse::dump_to(std::string &out) const {
}
out.append("}");
}
#endif
bool ExecuteServiceArgument::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
@@ -2349,6 +2757,7 @@ void ExecuteServiceArgument::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(9, it, true);
}
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ExecuteServiceArgument::dump_to(std::string &out) const {
char buffer[64];
out.append("ExecuteServiceArgument {\n");
@@ -2402,6 +2811,7 @@ void ExecuteServiceArgument::dump_to(std::string &out) const {
}
out.append("}");
}
#endif
bool ExecuteServiceRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2: {
@@ -2428,6 +2838,7 @@ void ExecuteServiceRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_message<ExecuteServiceArgument>(2, it, true);
}
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ExecuteServiceRequest::dump_to(std::string &out) const {
char buffer[64];
out.append("ExecuteServiceRequest {\n");
@@ -2443,6 +2854,17 @@ void ExecuteServiceRequest::dump_to(std::string &out) const {
}
out.append("}");
}
#endif
bool ListEntitiesCameraResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 5: {
this->disabled_by_default = value.as_bool();
return true;
}
default:
return false;
}
}
bool ListEntitiesCameraResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
@@ -2476,7 +2898,9 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_bool(5, this->disabled_by_default);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesCameraResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("ListEntitiesCameraResponse {\n");
@@ -2496,8 +2920,13 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const {
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append("}");
}
#endif
bool CameraImageResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 3: {
@@ -2533,6 +2962,7 @@ void CameraImageResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(2, this->data);
buffer.encode_bool(3, this->done);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void CameraImageResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("CameraImageResponse {\n");
@@ -2550,6 +2980,7 @@ void CameraImageResponse::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
#endif
bool CameraImageRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
@@ -2568,6 +2999,7 @@ void CameraImageRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(1, this->single);
buffer.encode_bool(2, this->stream);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void CameraImageRequest::dump_to(std::string &out) const {
char buffer[64];
out.append("CameraImageRequest {\n");
@@ -2580,6 +3012,7 @@ void CameraImageRequest::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
#endif
bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 5: {
@@ -2595,7 +3028,7 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v
return true;
}
case 11: {
this->supports_away = value.as_bool();
this->legacy_supports_away = value.as_bool();
return true;
}
case 12: {
@@ -2610,6 +3043,14 @@ 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;
}
case 18: {
this->disabled_by_default = value.as_bool();
return true;
}
default:
return false;
}
@@ -2628,6 +3069,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;
}
@@ -2667,7 +3116,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_float(8, this->visual_min_temperature);
buffer.encode_float(9, this->visual_max_temperature);
buffer.encode_float(10, this->visual_temperature_step);
buffer.encode_bool(11, this->supports_away);
buffer.encode_bool(11, this->legacy_supports_away);
buffer.encode_bool(12, this->supports_action);
for (auto &it : this->supported_fan_modes) {
buffer.encode_enum<enums::ClimateFanMode>(13, it, true);
@@ -2675,7 +3124,18 @@ 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);
}
buffer.encode_bool(18, this->disabled_by_default);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesClimateResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("ListEntitiesClimateResponse {\n");
@@ -2725,8 +3185,8 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
out.append(buffer);
out.append("\n");
out.append(" supports_away: ");
out.append(YESNO(this->supports_away));
out.append(" legacy_supports_away: ");
out.append(YESNO(this->legacy_supports_away));
out.append("\n");
out.append(" supports_action: ");
@@ -2744,8 +3204,31 @@ 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(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append("}");
}
#endif
bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
@@ -2753,7 +3236,7 @@ bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
return true;
}
case 7: {
this->away = value.as_bool();
this->legacy_away = value.as_bool();
return true;
}
case 8: {
@@ -2768,6 +3251,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;
}
@@ -2805,11 +3306,15 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_float(4, this->target_temperature);
buffer.encode_float(5, this->target_temperature_low);
buffer.encode_float(6, this->target_temperature_high);
buffer.encode_bool(7, this->away);
buffer.encode_bool(7, this->legacy_away);
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);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ClimateStateResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("ClimateStateResponse {\n");
@@ -2842,8 +3347,8 @@ void ClimateStateResponse::dump_to(std::string &out) const {
out.append(buffer);
out.append("\n");
out.append(" away: ");
out.append(YESNO(this->away));
out.append(" legacy_away: ");
out.append(YESNO(this->legacy_away));
out.append("\n");
out.append(" action: ");
@@ -2857,8 +3362,21 @@ 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("}");
}
#endif
bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
@@ -2882,11 +3400,11 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
return true;
}
case 10: {
this->has_away = value.as_bool();
this->has_legacy_away = value.as_bool();
return true;
}
case 11: {
this->away = value.as_bool();
this->legacy_away = value.as_bool();
return true;
}
case 12: {
@@ -2905,6 +3423,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;
}
@@ -2941,13 +3489,20 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_float(7, this->target_temperature_low);
buffer.encode_bool(8, this->has_target_temperature_high);
buffer.encode_float(9, this->target_temperature_high);
buffer.encode_bool(10, this->has_away);
buffer.encode_bool(11, this->away);
buffer.encode_bool(10, this->has_legacy_away);
buffer.encode_bool(11, this->legacy_away);
buffer.encode_bool(12, this->has_fan_mode);
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);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ClimateCommandRequest::dump_to(std::string &out) const {
char buffer[64];
out.append("ClimateCommandRequest {\n");
@@ -2991,12 +3546,12 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
out.append(buffer);
out.append("\n");
out.append(" has_away: ");
out.append(YESNO(this->has_away));
out.append(" has_legacy_away: ");
out.append(YESNO(this->has_legacy_away));
out.append("\n");
out.append(" away: ");
out.append(YESNO(this->away));
out.append(" legacy_away: ");
out.append(YESNO(this->legacy_away));
out.append("\n");
out.append(" has_fan_mode: ");
@@ -3014,8 +3569,414 @@ 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("}");
}
#endif
bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 9: {
this->disabled_by_default = value.as_bool();
return true;
}
default:
return false;
}
}
bool ListEntitiesNumberResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->object_id = value.as_string();
return true;
}
case 3: {
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
}
default:
return false;
}
}
bool ListEntitiesNumberResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 2: {
this->key = value.as_fixed32();
return true;
}
case 6: {
this->min_value = value.as_float();
return true;
}
case 7: {
this->max_value = value.as_float();
return true;
}
case 8: {
this->step = value.as_float();
return true;
}
default:
return false;
}
}
void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_float(6, this->min_value);
buffer.encode_float(7, this->max_value);
buffer.encode_float(8, this->step);
buffer.encode_bool(9, this->disabled_by_default);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesNumberResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("ListEntitiesNumberResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
out.append("\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
out.append(buffer);
out.append("\n");
out.append(" name: ");
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append(" min_value: ");
sprintf(buffer, "%g", this->min_value);
out.append(buffer);
out.append("\n");
out.append(" max_value: ");
sprintf(buffer, "%g", this->max_value);
out.append(buffer);
out.append("\n");
out.append(" step: ");
sprintf(buffer, "%g", this->step);
out.append(buffer);
out.append("\n");
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append("}");
}
#endif
bool NumberStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 3: {
this->missing_state = value.as_bool();
return true;
}
default:
return false;
}
}
bool NumberStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
this->key = value.as_fixed32();
return true;
}
case 2: {
this->state = value.as_float();
return true;
}
default:
return false;
}
}
void NumberStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_float(2, this->state);
buffer.encode_bool(3, this->missing_state);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void NumberStateResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("NumberStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
out.append(buffer);
out.append("\n");
out.append(" state: ");
sprintf(buffer, "%g", this->state);
out.append(buffer);
out.append("\n");
out.append(" missing_state: ");
out.append(YESNO(this->missing_state));
out.append("\n");
out.append("}");
}
#endif
bool NumberCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
this->key = value.as_fixed32();
return true;
}
case 2: {
this->state = value.as_float();
return true;
}
default:
return false;
}
}
void NumberCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_float(2, this->state);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void NumberCommandRequest::dump_to(std::string &out) const {
char buffer[64];
out.append("NumberCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
out.append(buffer);
out.append("\n");
out.append(" state: ");
sprintf(buffer, "%g", this->state);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
bool ListEntitiesSelectResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 7: {
this->disabled_by_default = value.as_bool();
return true;
}
default:
return false;
}
}
bool ListEntitiesSelectResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->object_id = value.as_string();
return true;
}
case 3: {
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
}
case 6: {
this->options.push_back(value.as_string());
return true;
}
default:
return false;
}
}
bool ListEntitiesSelectResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 2: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
for (auto &it : this->options) {
buffer.encode_string(6, it, true);
}
buffer.encode_bool(7, this->disabled_by_default);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSelectResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("ListEntitiesSelectResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
out.append("\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
out.append(buffer);
out.append("\n");
out.append(" name: ");
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
for (const auto &it : this->options) {
out.append(" options: ");
out.append("'").append(it).append("'");
out.append("\n");
}
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append("}");
}
#endif
bool SelectStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 3: {
this->missing_state = value.as_bool();
return true;
}
default:
return false;
}
}
bool SelectStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2: {
this->state = value.as_string();
return true;
}
default:
return false;
}
}
bool SelectStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void SelectStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_string(2, this->state);
buffer.encode_bool(3, this->missing_state);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SelectStateResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("SelectStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
out.append(buffer);
out.append("\n");
out.append(" state: ");
out.append("'").append(this->state).append("'");
out.append("\n");
out.append(" missing_state: ");
out.append(YESNO(this->missing_state));
out.append("\n");
out.append("}");
}
#endif
bool SelectCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2: {
this->state = value.as_string();
return true;
}
default:
return false;
}
}
bool SelectCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void SelectCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_string(2, this->state);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SelectCommandRequest::dump_to(std::string &out) const {
char buffer[64];
out.append("SelectCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
out.append(buffer);
out.append("\n");
out.append(" state: ");
out.append("'").append(this->state).append("'");
out.append("\n");
out.append("}");
}
#endif
} // namespace api
} // namespace esphome

View File

@@ -32,6 +32,27 @@ enum FanDirection : uint32_t {
FAN_DIRECTION_FORWARD = 0,
FAN_DIRECTION_REVERSE = 1,
};
enum ColorMode : uint32_t {
COLOR_MODE_UNKNOWN = 0,
COLOR_MODE_ON_OFF = 1,
COLOR_MODE_BRIGHTNESS = 2,
COLOR_MODE_WHITE = 7,
COLOR_MODE_COLOR_TEMPERATURE = 11,
COLOR_MODE_COLD_WARM_WHITE = 19,
COLOR_MODE_RGB = 35,
COLOR_MODE_RGB_WHITE = 39,
COLOR_MODE_RGB_COLOR_TEMPERATURE = 47,
COLOR_MODE_RGB_COLD_WARM_WHITE = 51,
};
enum SensorStateClass : uint32_t {
STATE_CLASS_NONE = 0,
STATE_CLASS_MEASUREMENT = 1,
};
enum SensorLastResetType : uint32_t {
LAST_RESET_NONE = 0,
LAST_RESET_NEVER = 1,
LAST_RESET_AUTO = 2,
};
enum LogLevel : uint32_t {
LOG_LEVEL_NONE = 0,
LOG_LEVEL_ERROR = 1,
@@ -53,11 +74,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,25 +106,39 @@ enum ClimateAction : uint32_t {
CLIMATE_ACTION_DRYING = 5,
CLIMATE_ACTION_FAN = 6,
};
enum ClimatePreset : uint32_t {
CLIMATE_PRESET_NONE = 0,
CLIMATE_PRESET_HOME = 1,
CLIMATE_PRESET_AWAY = 2,
CLIMATE_PRESET_BOOST = 3,
CLIMATE_PRESET_COMFORT = 4,
CLIMATE_PRESET_ECO = 5,
CLIMATE_PRESET_SLEEP = 6,
CLIMATE_PRESET_ACTIVITY = 7,
};
} // namespace enums
class HelloRequest : public ProtoMessage {
public:
std::string client_info{}; // NOLINT
std::string client_info{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class 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;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
@@ -110,18 +146,22 @@ class HelloResponse : public ProtoMessage {
};
class ConnectRequest : public ProtoMessage {
public:
std::string password{}; // NOLINT
std::string password{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class ConnectResponse : public ProtoMessage {
public:
bool invalid_password{false}; // NOLINT
bool invalid_password{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
@@ -129,49 +169,63 @@ class ConnectResponse : public ProtoMessage {
class DisconnectRequest : public ProtoMessage {
public:
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
};
class DisconnectResponse : public ProtoMessage {
public:
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
};
class PingRequest : public ProtoMessage {
public:
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
};
class PingResponse : public ProtoMessage {
public:
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
};
class DeviceInfoRequest : public ProtoMessage {
public:
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
};
class 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;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
@@ -180,34 +234,43 @@ class DeviceInfoResponse : public ProtoMessage {
class ListEntitiesRequest : public ProtoMessage {
public:
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
};
class ListEntitiesDoneResponse : public ProtoMessage {
public:
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
};
class SubscribeStatesRequest : public ProtoMessage {
public:
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
};
class 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};
bool disabled_by_default{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
@@ -216,11 +279,13 @@ 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;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
@@ -228,16 +293,19 @@ 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{};
bool disabled_by_default{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
@@ -246,13 +314,15 @@ 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;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
@@ -260,16 +330,18 @@ 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;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
@@ -277,16 +349,19 @@ 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};
bool disabled_by_default{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
@@ -295,14 +370,16 @@ 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;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
@@ -310,19 +387,21 @@ 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;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
@@ -330,19 +409,23 @@ 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{};
std::vector<enums::ColorMode> supported_color_modes{};
bool legacy_supports_brightness{false};
bool legacy_supports_rgb{false};
bool legacy_supports_white_value{false};
bool legacy_supports_color_temperature{false};
float min_mireds{0.0f};
float max_mireds{0.0f};
std::vector<std::string> effects{};
bool disabled_by_default{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
@@ -351,17 +434,23 @@ 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};
enums::ColorMode color_mode{};
float color_brightness{0.0f};
float red{0.0f};
float green{0.0f};
float blue{0.0f};
float white{0.0f};
float color_temperature{0.0f};
float cold_white{0.0f};
float warm_white{0.0f};
std::string effect{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
@@ -370,27 +459,37 @@ 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_color_mode{false};
enums::ColorMode color_mode{};
bool has_color_brightness{false};
float color_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_cold_white{false};
float cold_white{0.0f};
bool has_warm_white{false};
float warm_white{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;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
@@ -399,17 +498,22 @@ 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{};
enums::SensorLastResetType last_reset_type{};
bool disabled_by_default{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
@@ -418,11 +522,13 @@ 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;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
@@ -430,14 +536,17 @@ 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};
bool disabled_by_default{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
@@ -446,10 +555,12 @@ 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;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
@@ -457,10 +568,12 @@ 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;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
@@ -468,25 +581,31 @@ 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{};
bool disabled_by_default{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
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 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;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
@@ -495,22 +614,26 @@ 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;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
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;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
@@ -519,29 +642,35 @@ class SubscribeLogsResponse : public ProtoMessage {
class SubscribeHomeassistantServicesRequest : public ProtoMessage {
public:
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
};
class HomeassistantServiceMap : public ProtoMessage {
public:
std::string key{}; // NOLINT
std::string value{}; // NOLINT
std::string key{};
std::string value{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class 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;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
@@ -550,25 +679,33 @@ class HomeassistantServiceResponse : public ProtoMessage {
class SubscribeHomeAssistantStatesRequest : public ProtoMessage {
public:
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
};
class SubscribeHomeAssistantStateResponse : public ProtoMessage {
public:
std::string entity_id{}; // NOLINT
std::string entity_id{};
std::string attribute{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class 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;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
@@ -576,25 +713,31 @@ class HomeAssistantStateResponse : public ProtoMessage {
class GetTimeRequest : public ProtoMessage {
public:
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
};
class GetTimeResponse : public ProtoMessage {
public:
uint32_t epoch_seconds{0}; // NOLINT
uint32_t epoch_seconds{0};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
};
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;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
@@ -602,11 +745,13 @@ 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;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
@@ -614,17 +759,19 @@ 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;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
@@ -633,10 +780,12 @@ 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;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
@@ -644,24 +793,30 @@ 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{};
bool disabled_by_default{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
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 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;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
@@ -670,32 +825,40 @@ 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;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
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 legacy_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{};
bool disabled_by_default{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
@@ -704,47 +867,156 @@ 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 legacy_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;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
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_legacy_away{false};
bool legacy_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;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
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 ListEntitiesNumberResponse : public ProtoMessage {
public:
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
std::string icon{};
float min_value{0.0f};
float max_value{0.0f};
float step{0.0f};
bool disabled_by_default{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
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 NumberStateResponse : public ProtoMessage {
public:
uint32_t key{0};
float state{0.0f};
bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class NumberCommandRequest : public ProtoMessage {
public:
uint32_t key{0};
float state{0.0f};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
};
class ListEntitiesSelectResponse : public ProtoMessage {
public:
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
std::string icon{};
std::vector<std::string> options{};
bool disabled_by_default{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
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 SelectStateResponse : public ProtoMessage {
public:
uint32_t key{0};
std::string state{};
bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
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 SelectCommandRequest : public ProtoMessage {
public:
uint32_t key{0};
std::string state{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
} // namespace api
} // namespace esphome

View File

@@ -6,61 +6,85 @@
namespace esphome {
namespace api {
static const char *TAG = "api.service";
static const char *const TAG = "api.service";
bool APIServerConnectionBase::send_hello_response(const HelloResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_hello_response: %s", msg.dump().c_str());
#endif
return this->send_message_<HelloResponse>(msg, 2);
}
bool APIServerConnectionBase::send_connect_response(const ConnectResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_connect_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ConnectResponse>(msg, 4);
}
bool APIServerConnectionBase::send_disconnect_request(const DisconnectRequest &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_disconnect_request: %s", msg.dump().c_str());
#endif
return this->send_message_<DisconnectRequest>(msg, 5);
}
bool APIServerConnectionBase::send_disconnect_response(const DisconnectResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_disconnect_response: %s", msg.dump().c_str());
#endif
return this->send_message_<DisconnectResponse>(msg, 6);
}
bool APIServerConnectionBase::send_ping_request(const PingRequest &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_ping_request: %s", msg.dump().c_str());
#endif
return this->send_message_<PingRequest>(msg, 7);
}
bool APIServerConnectionBase::send_ping_response(const PingResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_ping_response: %s", msg.dump().c_str());
#endif
return this->send_message_<PingResponse>(msg, 8);
}
bool APIServerConnectionBase::send_device_info_response(const DeviceInfoResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_device_info_response: %s", msg.dump().c_str());
#endif
return this->send_message_<DeviceInfoResponse>(msg, 10);
}
bool APIServerConnectionBase::send_list_entities_done_response(const ListEntitiesDoneResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_done_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesDoneResponse>(msg, 19);
}
#ifdef USE_BINARY_SENSOR
bool APIServerConnectionBase::send_list_entities_binary_sensor_response(const ListEntitiesBinarySensorResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_binary_sensor_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesBinarySensorResponse>(msg, 12);
}
#endif
#ifdef USE_BINARY_SENSOR
bool APIServerConnectionBase::send_binary_sensor_state_response(const BinarySensorStateResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_binary_sensor_state_response: %s", msg.dump().c_str());
#endif
return this->send_message_<BinarySensorStateResponse>(msg, 21);
}
#endif
#ifdef USE_COVER
bool APIServerConnectionBase::send_list_entities_cover_response(const ListEntitiesCoverResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_cover_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesCoverResponse>(msg, 13);
}
#endif
#ifdef USE_COVER
bool APIServerConnectionBase::send_cover_state_response(const CoverStateResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_cover_state_response: %s", msg.dump().c_str());
#endif
return this->send_message_<CoverStateResponse>(msg, 22);
}
#endif
@@ -68,13 +92,17 @@ bool APIServerConnectionBase::send_cover_state_response(const CoverStateResponse
#endif
#ifdef USE_FAN
bool APIServerConnectionBase::send_list_entities_fan_response(const ListEntitiesFanResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_fan_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesFanResponse>(msg, 14);
}
#endif
#ifdef USE_FAN
bool APIServerConnectionBase::send_fan_state_response(const FanStateResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_fan_state_response: %s", msg.dump().c_str());
#endif
return this->send_message_<FanStateResponse>(msg, 23);
}
#endif
@@ -82,13 +110,17 @@ bool APIServerConnectionBase::send_fan_state_response(const FanStateResponse &ms
#endif
#ifdef USE_LIGHT
bool APIServerConnectionBase::send_list_entities_light_response(const ListEntitiesLightResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_light_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesLightResponse>(msg, 15);
}
#endif
#ifdef USE_LIGHT
bool APIServerConnectionBase::send_light_state_response(const LightStateResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_light_state_response: %s", msg.dump().c_str());
#endif
return this->send_message_<LightStateResponse>(msg, 24);
}
#endif
@@ -96,25 +128,33 @@ bool APIServerConnectionBase::send_light_state_response(const LightStateResponse
#endif
#ifdef USE_SENSOR
bool APIServerConnectionBase::send_list_entities_sensor_response(const ListEntitiesSensorResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_sensor_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesSensorResponse>(msg, 16);
}
#endif
#ifdef USE_SENSOR
bool APIServerConnectionBase::send_sensor_state_response(const SensorStateResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_sensor_state_response: %s", msg.dump().c_str());
#endif
return this->send_message_<SensorStateResponse>(msg, 25);
}
#endif
#ifdef USE_SWITCH
bool APIServerConnectionBase::send_list_entities_switch_response(const ListEntitiesSwitchResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_switch_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesSwitchResponse>(msg, 17);
}
#endif
#ifdef USE_SWITCH
bool APIServerConnectionBase::send_switch_state_response(const SwitchStateResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_switch_state_response: %s", msg.dump().c_str());
#endif
return this->send_message_<SwitchStateResponse>(msg, 26);
}
#endif
@@ -122,13 +162,17 @@ bool APIServerConnectionBase::send_switch_state_response(const SwitchStateRespon
#endif
#ifdef USE_TEXT_SENSOR
bool APIServerConnectionBase::send_list_entities_text_sensor_response(const ListEntitiesTextSensorResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_text_sensor_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesTextSensorResponse>(msg, 18);
}
#endif
#ifdef USE_TEXT_SENSOR
bool APIServerConnectionBase::send_text_sensor_state_response(const TextSensorStateResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_text_sensor_state_response: %s", msg.dump().c_str());
#endif
return this->send_message_<TextSensorStateResponse>(msg, 27);
}
#endif
@@ -136,35 +180,49 @@ bool APIServerConnectionBase::send_subscribe_logs_response(const SubscribeLogsRe
return this->send_message_<SubscribeLogsResponse>(msg, 29);
}
bool APIServerConnectionBase::send_homeassistant_service_response(const HomeassistantServiceResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_homeassistant_service_response: %s", msg.dump().c_str());
#endif
return this->send_message_<HomeassistantServiceResponse>(msg, 35);
}
bool APIServerConnectionBase::send_subscribe_home_assistant_state_response(
const SubscribeHomeAssistantStateResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_subscribe_home_assistant_state_response: %s", msg.dump().c_str());
#endif
return this->send_message_<SubscribeHomeAssistantStateResponse>(msg, 39);
}
bool APIServerConnectionBase::send_get_time_request(const GetTimeRequest &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_get_time_request: %s", msg.dump().c_str());
#endif
return this->send_message_<GetTimeRequest>(msg, 36);
}
bool APIServerConnectionBase::send_get_time_response(const GetTimeResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_get_time_response: %s", msg.dump().c_str());
#endif
return this->send_message_<GetTimeResponse>(msg, 37);
}
bool APIServerConnectionBase::send_list_entities_services_response(const ListEntitiesServicesResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_services_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesServicesResponse>(msg, 41);
}
#ifdef USE_ESP32_CAMERA
bool APIServerConnectionBase::send_list_entities_camera_response(const ListEntitiesCameraResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_camera_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesCameraResponse>(msg, 43);
}
#endif
#ifdef USE_ESP32_CAMERA
bool APIServerConnectionBase::send_camera_image_response(const CameraImageResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_camera_image_response: %s", msg.dump().c_str());
#endif
return this->send_message_<CameraImageResponse>(msg, 44);
}
#endif
@@ -172,87 +230,147 @@ bool APIServerConnectionBase::send_camera_image_response(const CameraImageRespon
#endif
#ifdef USE_CLIMATE
bool APIServerConnectionBase::send_list_entities_climate_response(const ListEntitiesClimateResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_climate_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesClimateResponse>(msg, 46);
}
#endif
#ifdef USE_CLIMATE
bool APIServerConnectionBase::send_climate_state_response(const ClimateStateResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_climate_state_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ClimateStateResponse>(msg, 47);
}
#endif
#ifdef USE_CLIMATE
#endif
#ifdef USE_NUMBER
bool APIServerConnectionBase::send_list_entities_number_response(const ListEntitiesNumberResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_number_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesNumberResponse>(msg, 49);
}
#endif
#ifdef USE_NUMBER
bool APIServerConnectionBase::send_number_state_response(const NumberStateResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_number_state_response: %s", msg.dump().c_str());
#endif
return this->send_message_<NumberStateResponse>(msg, 50);
}
#endif
#ifdef USE_NUMBER
#endif
#ifdef USE_SELECT
bool APIServerConnectionBase::send_list_entities_select_response(const ListEntitiesSelectResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_select_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesSelectResponse>(msg, 52);
}
#endif
#ifdef USE_SELECT
bool APIServerConnectionBase::send_select_state_response(const SelectStateResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_select_state_response: %s", msg.dump().c_str());
#endif
return this->send_message_<SelectStateResponse>(msg, 53);
}
#endif
#ifdef USE_SELECT
#endif
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
switch (msg_type) {
case 1: {
HelloRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_hello_request: %s", msg.dump().c_str());
#endif
this->on_hello_request(msg);
break;
}
case 3: {
ConnectRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_connect_request: %s", msg.dump().c_str());
#endif
this->on_connect_request(msg);
break;
}
case 5: {
DisconnectRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_disconnect_request: %s", msg.dump().c_str());
#endif
this->on_disconnect_request(msg);
break;
}
case 6: {
DisconnectResponse msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_disconnect_response: %s", msg.dump().c_str());
#endif
this->on_disconnect_response(msg);
break;
}
case 7: {
PingRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_ping_request: %s", msg.dump().c_str());
#endif
this->on_ping_request(msg);
break;
}
case 8: {
PingResponse msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_ping_response: %s", msg.dump().c_str());
#endif
this->on_ping_response(msg);
break;
}
case 9: {
DeviceInfoRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_device_info_request: %s", msg.dump().c_str());
#endif
this->on_device_info_request(msg);
break;
}
case 11: {
ListEntitiesRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_list_entities_request: %s", msg.dump().c_str());
#endif
this->on_list_entities_request(msg);
break;
}
case 20: {
SubscribeStatesRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_subscribe_states_request: %s", msg.dump().c_str());
#endif
this->on_subscribe_states_request(msg);
break;
}
case 28: {
SubscribeLogsRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_subscribe_logs_request: %s", msg.dump().c_str());
#endif
this->on_subscribe_logs_request(msg);
break;
}
@@ -260,7 +378,9 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
#ifdef USE_COVER
CoverCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_cover_command_request: %s", msg.dump().c_str());
#endif
this->on_cover_command_request(msg);
#endif
break;
@@ -269,7 +389,9 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
#ifdef USE_FAN
FanCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_fan_command_request: %s", msg.dump().c_str());
#endif
this->on_fan_command_request(msg);
#endif
break;
@@ -278,7 +400,9 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
#ifdef USE_LIGHT
LightCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_light_command_request: %s", msg.dump().c_str());
#endif
this->on_light_command_request(msg);
#endif
break;
@@ -287,7 +411,9 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
#ifdef USE_SWITCH
SwitchCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_switch_command_request: %s", msg.dump().c_str());
#endif
this->on_switch_command_request(msg);
#endif
break;
@@ -295,42 +421,54 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
case 34: {
SubscribeHomeassistantServicesRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_subscribe_homeassistant_services_request: %s", msg.dump().c_str());
#endif
this->on_subscribe_homeassistant_services_request(msg);
break;
}
case 36: {
GetTimeRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_get_time_request: %s", msg.dump().c_str());
#endif
this->on_get_time_request(msg);
break;
}
case 37: {
GetTimeResponse msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_get_time_response: %s", msg.dump().c_str());
#endif
this->on_get_time_response(msg);
break;
}
case 38: {
SubscribeHomeAssistantStatesRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_subscribe_home_assistant_states_request: %s", msg.dump().c_str());
#endif
this->on_subscribe_home_assistant_states_request(msg);
break;
}
case 40: {
HomeAssistantStateResponse msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_home_assistant_state_response: %s", msg.dump().c_str());
#endif
this->on_home_assistant_state_response(msg);
break;
}
case 42: {
ExecuteServiceRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_execute_service_request: %s", msg.dump().c_str());
#endif
this->on_execute_service_request(msg);
break;
}
@@ -338,7 +476,9 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
#ifdef USE_ESP32_CAMERA
CameraImageRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_camera_image_request: %s", msg.dump().c_str());
#endif
this->on_camera_image_request(msg);
#endif
break;
@@ -347,8 +487,32 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
#ifdef USE_CLIMATE
ClimateCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_climate_command_request: %s", msg.dump().c_str());
#endif
this->on_climate_command_request(msg);
#endif
break;
}
case 51: {
#ifdef USE_NUMBER
NumberCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_number_command_request: %s", msg.dump().c_str());
#endif
this->on_number_command_request(msg);
#endif
break;
}
case 54: {
#ifdef USE_SELECT
SelectCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str());
#endif
this->on_select_command_request(msg);
#endif
break;
}
@@ -547,6 +711,32 @@ void APIServerConnection::on_climate_command_request(const ClimateCommandRequest
this->climate_command(msg);
}
#endif
#ifdef USE_NUMBER
void APIServerConnection::on_number_command_request(const NumberCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->number_command(msg);
}
#endif
#ifdef USE_SELECT
void APIServerConnection::on_select_command_request(const SelectCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->select_command(msg);
}
#endif
} // namespace api
} // namespace esphome

View File

@@ -111,6 +111,24 @@ class APIServerConnectionBase : public ProtoService {
#endif
#ifdef USE_CLIMATE
virtual void on_climate_command_request(const ClimateCommandRequest &value){};
#endif
#ifdef USE_NUMBER
bool send_list_entities_number_response(const ListEntitiesNumberResponse &msg);
#endif
#ifdef USE_NUMBER
bool send_number_state_response(const NumberStateResponse &msg);
#endif
#ifdef USE_NUMBER
virtual void on_number_command_request(const NumberCommandRequest &value){};
#endif
#ifdef USE_SELECT
bool send_list_entities_select_response(const ListEntitiesSelectResponse &msg);
#endif
#ifdef USE_SELECT
bool send_select_state_response(const SelectStateResponse &msg);
#endif
#ifdef USE_SELECT
virtual void on_select_command_request(const SelectCommandRequest &value){};
#endif
protected:
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
@@ -147,6 +165,12 @@ class APIServerConnection : public APIServerConnectionBase {
#endif
#ifdef USE_CLIMATE
virtual void climate_command(const ClimateCommandRequest &msg) = 0;
#endif
#ifdef USE_NUMBER
virtual void number_command(const NumberCommandRequest &msg) = 0;
#endif
#ifdef USE_SELECT
virtual void select_command(const SelectCommandRequest &msg) = 0;
#endif
protected:
void on_hello_request(const HelloRequest &msg) override;
@@ -179,6 +203,12 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_CLIMATE
void on_climate_command_request(const ClimateCommandRequest &msg) override;
#endif
#ifdef USE_NUMBER
void on_number_command_request(const NumberCommandRequest &msg) override;
#endif
#ifdef USE_SELECT
void on_select_command_request(const SelectCommandRequest &msg) override;
#endif
};
} // namespace api

View File

@@ -5,6 +5,8 @@
#include "esphome/core/util.h"
#include "esphome/core/defines.h"
#include "esphome/core/version.h"
#include <errno.h>
//#include <arpa/inet.h>
#ifdef USE_LOGGER
#include "esphome/components/logger/logger.h"
@@ -15,26 +17,60 @@
namespace esphome {
namespace api {
static const char *TAG = "api";
static const char *const TAG = "api";
// APIServer
void APIServer::setup() {
ESP_LOGCONFIG(TAG, "Setting up Home Assistant API server...");
this->setup_controller();
this->server_ = AsyncServer(this->port_);
this->server_.setNoDelay(false);
this->server_.begin();
this->server_.onClient(
[](void *s, AsyncClient *client) {
if (client == nullptr)
return;
socket_ = socket::socket(AF_INET, SOCK_STREAM, 0);
if (socket_ == nullptr) {
ESP_LOGW(TAG, "Could not create socket.");
this->mark_failed();
return;
}
int enable = 1;
int err = socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
if (err != 0) {
ESP_LOGW(TAG, "Socket unable to set reuseaddr: errno %d", err);
// we can still continue
}
err = socket_->setblocking(false);
if (err != 0) {
ESP_LOGW(TAG, "Socket unable to set nonblocking mode: errno %d", err);
this->mark_failed();
return;
}
/*struct sockaddr_storage dest_addr;
memset(&dest_addr, 0, sizeof(dest_addr));
struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in *) &dest_addr;
dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY);
dest_addr_ip4->sin_family = AF_INET;
dest_addr_ip4->sin_port = htons(this->port_);
err = socket_->bind((struct sockaddr *) &dest_addr, sizeof(dest_addr));*/
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = INADDR_ANY;
server.sin_port = htons(this->port_);
err = socket_->bind((struct sockaddr *) &server, sizeof(server));
if (err != 0) {
ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno);
this->mark_failed();
return;
}
err = socket_->listen(4);
if (err != 0) {
ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno);
this->mark_failed();
return;
}
// can't print here because in lwIP thread
// ESP_LOGD(TAG, "New client connected from %s", client->remoteIP().toString().c_str());
auto *a_this = (APIServer *) s;
a_this->clients_.push_back(new APIConnection(client, a_this));
},
this);
#ifdef USE_LOGGER
if (logger::global_logger != nullptr) {
logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) {
@@ -59,6 +95,20 @@ void APIServer::setup() {
#endif
}
void APIServer::loop() {
// Accept new clients
while (true) {
struct sockaddr_storage source_addr;
socklen_t addr_len = sizeof(source_addr);
auto sock = socket_->accept((struct sockaddr *) &source_addr, &addr_len);
if (!sock)
break;
ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str());
auto *conn = new APIConnection(std::move(sock), this);
clients_.push_back(conn);
conn->start();
}
// Partition clients into remove and active
auto new_end =
std::partition(this->clients_.begin(), this->clients_.end(), [](APIConnection *conn) { return !conn->remove_; });
@@ -180,7 +230,7 @@ void APIServer::on_switch_update(switch_::Switch *obj, bool state) {
#endif
#ifdef USE_TEXT_SENSOR
void APIServer::on_text_sensor_update(text_sensor::TextSensor *obj, std::string state) {
void APIServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) {
if (obj->is_internal())
return;
for (auto *c : this->clients_)
@@ -197,9 +247,27 @@ void APIServer::on_climate_update(climate::Climate *obj) {
}
#endif
#ifdef USE_NUMBER
void APIServer::on_number_update(number::Number *obj, float state) {
if (obj->is_internal())
return;
for (auto *c : this->clients_)
c->send_number_state(obj, state);
}
#endif
#ifdef USE_SELECT
void APIServer::on_select_update(select::Select *obj, const std::string &state) {
if (obj->is_internal())
return;
for (auto *c : this->clients_)
c->send_select_state(obj, state);
}
#endif
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
void APIServer::set_port(uint16_t port) { this->port_ = port; }
APIServer *global_api_server = nullptr;
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
void APIServer::set_password(const std::string &password) { this->password_ = password; }
void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
@@ -208,9 +276,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

@@ -4,20 +4,14 @@
#include "esphome/core/controller.h"
#include "esphome/core/defines.h"
#include "esphome/core/log.h"
#include "esphome/components/socket/socket.h"
#include "api_pb2.h"
#include "api_pb2_service.h"
#include "util.h"
#include "list_entities.h"
#include "subscribe_state.h"
#include "homeassistant_service.h"
#include "user_services.h"
#ifdef ARDUINO_ARCH_ESP32
#include <AsyncTCP.h>
#endif
#ifdef ARDUINO_ARCH_ESP8266
#include <ESPAsyncTCP.h>
#endif
#include "api_noise_context.h"
namespace esphome {
namespace api {
@@ -36,6 +30,16 @@ class APIServer : public Component, public Controller {
void set_port(uint16_t port);
void set_password(const std::string &password);
void set_reboot_timeout(uint32_t reboot_timeout);
#ifdef USE_API_NOISE
void set_noise_psk(psk_t psk) {
noise_ctx_->set_psk(std::move(psk));
}
std::shared_ptr<APINoiseContext> get_noise_ctx() {
return noise_ctx_;
}
#endif // USE_API_NOISE
void handle_disconnect(APIConnection *conn);
#ifdef USE_BINARY_SENSOR
void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) override;
@@ -56,10 +60,16 @@ class APIServer : public Component, public Controller {
void on_switch_update(switch_::Switch *obj, bool state) override;
#endif
#ifdef USE_TEXT_SENSOR
void on_text_sensor_update(text_sensor::TextSensor *obj, std::string state) override;
void on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) override;
#endif
#ifdef USE_CLIMATE
void on_climate_update(climate::Climate *obj) override;
#endif
#ifdef USE_NUMBER
void on_number_update(number::Number *obj, float state) override;
#endif
#ifdef USE_SELECT
void on_select_update(select::Select *obj, const std::string &state) override;
#endif
void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
@@ -71,15 +81,17 @@ 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_; }
protected:
AsyncServer server_{0};
std::unique_ptr<socket::Socket> socket_ = nullptr;
uint16_t port_{6053};
uint32_t reboot_timeout_{300000};
uint32_t last_connected_{0};
@@ -87,9 +99,13 @@ class APIServer : public Component, public Controller {
std::string password_;
std::vector<HomeAssistantStateSubscription> state_subs_;
std::vector<UserServiceDescriptor *> user_services_;
#ifdef USE_API_NOISE
std::shared_ptr<APINoiseContext> noise_ctx_ = std::make_shared<APINoiseContext>();
#endif // USE_API_NOISE
};
extern APIServer *global_api_server;
extern APIServer *global_api_server; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
template<typename... Ts> class APIConnectedCondition : public Condition<Ts...> {
public:

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

@@ -51,5 +51,13 @@ bool ListEntitiesIterator::on_camera(esp32_camera::ESP32Camera *camera) {
bool ListEntitiesIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_info(climate); }
#endif
#ifdef USE_NUMBER
bool ListEntitiesIterator::on_number(number::Number *number) { return this->client_->send_number_info(number); }
#endif
#ifdef USE_SELECT
bool ListEntitiesIterator::on_select(select::Select *select) { return this->client_->send_select_info(select); }
#endif
} // namespace api
} // namespace esphome

View File

@@ -39,6 +39,12 @@ class ListEntitiesIterator : public ComponentIterator {
#endif
#ifdef USE_CLIMATE
bool on_climate(climate::Climate *climate) override;
#endif
#ifdef USE_NUMBER
bool on_number(number::Number *number) override;
#endif
#ifdef USE_SELECT
bool on_select(select::Select *select) override;
#endif
bool on_end() override;

View File

@@ -5,7 +5,7 @@
namespace esphome {
namespace api {
static const char *TAG = "api.proto";
static const char *const TAG = "api.proto";
void ProtoMessage::decode(const uint8_t *buffer, size_t length) {
uint32_t i = 0;
@@ -80,11 +80,13 @@ void ProtoMessage::decode(const uint8_t *buffer, size_t length) {
}
}
#ifdef HAS_PROTO_MESSAGE_DUMP
std::string ProtoMessage::dump() const {
std::string out;
this->dump_to(out);
return out;
}
#endif
} // namespace api
} // namespace esphome

View File

@@ -1,8 +1,13 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
#define HAS_PROTO_MESSAGE_DUMP
#endif
namespace esphome {
namespace api {
@@ -243,8 +248,10 @@ class ProtoMessage {
public:
virtual void encode(ProtoWriteBuffer buffer) const = 0;
void decode(const uint8_t *buffer, size_t length);
#ifdef HAS_PROTO_MESSAGE_DUMP
std::string dump() const;
virtual void dump_to(std::string &out) const = 0;
#endif
protected:
virtual bool decode_varint(uint32_t field_id, ProtoVarInt value) { return false; }

View File

@@ -37,6 +37,16 @@ bool InitialStateIterator::on_text_sensor(text_sensor::TextSensor *text_sensor)
#ifdef USE_CLIMATE
bool InitialStateIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_state(climate); }
#endif
#ifdef USE_NUMBER
bool InitialStateIterator::on_number(number::Number *number) {
return this->client_->send_number_state(number, number->state);
}
#endif
#ifdef USE_SELECT
bool InitialStateIterator::on_select(select::Select *select) {
return this->client_->send_select_state(select, select->state);
}
#endif
InitialStateIterator::InitialStateIterator(APIServer *server, APIConnection *client)
: ComponentIterator(server), client_(client) {}

View File

@@ -36,6 +36,12 @@ class InitialStateIterator : public ComponentIterator {
#endif
#ifdef USE_CLIMATE
bool on_climate(climate::Climate *climate) override;
#endif
#ifdef USE_NUMBER
bool on_number(number::Number *number) override;
#endif
#ifdef USE_SELECT
bool on_select(select::Select *select) override;
#endif
protected:
APIConnection *client_;

View File

@@ -15,7 +15,7 @@ template<> std::string get_execute_arg_value<std::string>(const ExecuteServiceAr
template<> std::vector<bool> get_execute_arg_value<std::vector<bool>>(const ExecuteServiceArgument &arg) {
return arg.bool_array;
}
template<> std::vector<int> get_execute_arg_value<std::vector<int>>(const ExecuteServiceArgument &arg) {
template<> std::vector<int32_t> get_execute_arg_value<std::vector<int32_t>>(const ExecuteServiceArgument &arg) {
return arg.int_array;
}
template<> std::vector<float> get_execute_arg_value<std::vector<float>>(const ExecuteServiceArgument &arg) {

View File

@@ -1,5 +1,7 @@
#pragma once
#include <utility>
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "api_pb2.h"
@@ -20,8 +22,8 @@ template<typename T> enums::ServiceArgType to_service_arg_type();
template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
public:
UserServiceBase(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names)
: name_(name), arg_names_(arg_names) {
UserServiceBase(std::string name, const std::array<std::string, sizeof...(Ts)> &arg_names)
: name_(std::move(name)), arg_names_(arg_names) {
this->key_ = fnv1_hash(this->name_);
}

View File

@@ -167,6 +167,36 @@ void ComponentIterator::advance() {
}
}
break;
#endif
#ifdef USE_NUMBER
case IteratorState::NUMBER:
if (this->at_ >= App.get_numbers().size()) {
advance_platform = true;
} else {
auto *number = App.get_numbers()[this->at_];
if (number->is_internal()) {
success = true;
break;
} else {
success = this->on_number(number);
}
}
break;
#endif
#ifdef USE_SELECT
case IteratorState::SELECT:
if (this->at_ >= App.get_selects().size()) {
advance_platform = true;
} else {
auto *select = App.get_selects()[this->at_];
if (select->is_internal()) {
success = true;
break;
} else {
success = this->on_select(select);
}
}
break;
#endif
case IteratorState::MAX:
if (this->on_end()) {

View File

@@ -47,6 +47,12 @@ class ComponentIterator {
#endif
#ifdef USE_CLIMATE
virtual bool on_climate(climate::Climate *climate) = 0;
#endif
#ifdef USE_NUMBER
virtual bool on_number(number::Number *number) = 0;
#endif
#ifdef USE_SELECT
virtual bool on_select(select::Select *select) = 0;
#endif
virtual bool on_end();
@@ -81,6 +87,12 @@ class ComponentIterator {
#endif
#ifdef USE_CLIMATE
CLIMATE,
#endif
#ifdef USE_NUMBER
NUMBER,
#endif
#ifdef USE_SELECT
SELECT,
#endif
MAX,
} state_{IteratorState::NONE};

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

@@ -4,7 +4,7 @@
namespace esphome {
namespace as3935 {
static const char *TAG = "as3935";
static const char *const TAG = "as3935";
void AS3935Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up AS3935...");

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

@@ -4,9 +4,8 @@ from esphome.components import sensor
from esphome.const import (
CONF_DISTANCE,
CONF_LIGHTNING_ENERGY,
DEVICE_CLASS_EMPTY,
STATE_CLASS_NONE,
UNIT_KILOMETER,
UNIT_EMPTY,
ICON_SIGNAL_DISTANCE_VARIANT,
ICON_FLASH,
)
@@ -18,24 +17,29 @@ 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_of_measurement=UNIT_KILOMETER,
icon=ICON_SIGNAL_DISTANCE_VARIANT,
accuracy_decimals=1,
state_class=STATE_CLASS_NONE,
),
cv.Optional(CONF_LIGHTNING_ENERGY): sensor.sensor_schema(
UNIT_EMPTY, ICON_FLASH, 1, DEVICE_CLASS_EMPTY
icon=ICON_FLASH,
accuracy_decimals=1,
state_class=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

@@ -4,7 +4,7 @@
namespace esphome {
namespace as3935_i2c {
static const char *TAG = "as3935_i2c";
static const char *const TAG = "as3935_i2c";
void I2CAS3935Component::write_register(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t start_pos) {
uint8_t write_reg;

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

@@ -4,7 +4,7 @@
namespace esphome {
namespace as3935_spi {
static const char *TAG = "as3935_spi";
static const char *const TAG = "as3935_spi";
void SPIAS3935Component::setup() {
ESP_LOGI(TAG, "SPIAS3935Component setup started!");
@@ -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

@@ -6,7 +6,7 @@
namespace esphome {
namespace atc_mithermometer {
static const char *TAG = "atc_mithermometer";
static const char *const TAG = "atc_mithermometer";
void ATCMiThermometer::dump_config() {
ESP_LOGCONFIG(TAG, "ATC MiThermometer");

View File

@@ -12,7 +12,7 @@ from esphome.const import (
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
ICON_EMPTY,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
UNIT_VOLT,
@@ -33,16 +33,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_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_HUMIDITY
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_BATTERY
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_BATTERY,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
@@ -51,22 +63,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

@@ -5,7 +5,7 @@
namespace esphome {
namespace atm90e32 {
static const char *TAG = "atm90e32";
static const char *const TAG = "atm90e32";
void ATM90E32Component::update() {
if (this->read16_(ATM90E32_REGISTER_METEREN) != 1) {
@@ -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

@@ -39,7 +39,7 @@ static const uint16_t ATM90E32_STATUS_S0_OVPHASEBST = 1 << 11; // Over voltage
static const uint16_t ATM90E32_STATUS_S0_OVPHASECST = 1 << 10; // Over voltage on phase C
static const uint16_t ATM90E32_STATUS_S0_UREVWNST = 1 << 9; // Voltage Phase Sequence Error status
static const uint16_t ATM90E32_STATUS_S0_IREVWNST = 1 << 8; // Current Phase Sequence Error status
static const uint16_t ATM90E32_STATUS_S0_INOV0ST = 1 << 7; // Calculated N line current greater tha INWarnTh reg
static const uint16_t ATM90E32_STATUS_S0_INOV0ST = 1 << 7; // Calculated N line current greater than INWarnTh reg
static const uint16_t ATM90E32_STATUS_S0_TQNOLOADST = 1 << 6; // All phase sum reactive power no-load condition status
static const uint16_t ATM90E32_STATUS_S0_TPNOLOADST = 1 << 5; // All phase sum active power no-load condition status
static const uint16_t ATM90E32_STATUS_S0_TASNOLOADST = 1 << 4; // All phase sum apparent power no-load status

View File

@@ -3,34 +3,37 @@ 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,
DEVICE_CLASS_VOLTAGE,
ICON_EMPTY,
ICON_LIGHTBULB,
ICON_CURRENT_AC,
LAST_RESET_TYPE_AUTO,
STATE_CLASS_MEASUREMENT,
UNIT_HERTZ,
UNIT_VOLT,
UNIT_AMPERE,
UNIT_WATT,
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 +62,47 @@ 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_of_measurement=UNIT_VOLT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_POWER): sensor.sensor_schema(
UNIT_WATT, ICON_EMPTY, 2, DEVICE_CLASS_POWER
unit_of_measurement=UNIT_WATT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema(
UNIT_VOLT_AMPS_REACTIVE, ICON_LIGHTBULB, 2, DEVICE_CLASS_EMPTY
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
icon=ICON_LIGHTBULB,
accuracy_decimals=2,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
UNIT_EMPTY, ICON_EMPTY, 2, DEVICE_CLASS_POWER_FACTOR
accuracy_decimals=2,
device_class=DEVICE_CLASS_POWER_FACTOR,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_FORWARD_ACTIVE_ENERGY): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT_HOURS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_MEASUREMENT,
last_reset_type=LAST_RESET_TYPE_AUTO,
),
cv.Optional(CONF_REVERSE_ACTIVE_ENERGY): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT_HOURS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_MEASUREMENT,
last_reset_type=LAST_RESET_TYPE_AUTO,
),
cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t,
cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t,
@@ -86,10 +117,16 @@ 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_of_measurement=UNIT_HERTZ,
icon=ICON_CURRENT_AC,
accuracy_decimals=1,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CHIP_TEMPERATURE): sensor.sensor_schema(
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=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 +140,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 +152,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 *const 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 * Celsius.
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,80 @@
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,
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_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_MOISTURE): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=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

View File

@@ -0,0 +1,239 @@
#include "ballu.h"
#include "esphome/core/log.h"
namespace esphome {
namespace ballu {
static const char *const TAG = "ballu.climate";
const uint16_t BALLU_HEADER_MARK = 9000;
const uint16_t BALLU_HEADER_SPACE = 4500;
const uint16_t BALLU_BIT_MARK = 575;
const uint16_t BALLU_ONE_SPACE = 1675;
const uint16_t BALLU_ZERO_SPACE = 550;
const uint32_t BALLU_CARRIER_FREQUENCY = 38000;
const uint8_t BALLU_STATE_LENGTH = 13;
const uint8_t BALLU_AUTO = 0;
const uint8_t BALLU_COOL = 0x20;
const uint8_t BALLU_DRY = 0x40;
const uint8_t BALLU_HEAT = 0x80;
const uint8_t BALLU_FAN = 0xc0;
const uint8_t BALLU_FAN_AUTO = 0xa0;
const uint8_t BALLU_FAN_HIGH = 0x20;
const uint8_t BALLU_FAN_MED = 0x40;
const uint8_t BALLU_FAN_LOW = 0x60;
const uint8_t BALLU_SWING_VER = 0x07;
const uint8_t BALLU_SWING_HOR = 0xe0;
const uint8_t BALLU_POWER = 0x20;
void BalluClimate::transmit_state() {
uint8_t remote_state[BALLU_STATE_LENGTH] = {0};
auto temp = (uint8_t) roundf(clamp(this->target_temperature, YKR_K_002E_TEMP_MIN, YKR_K_002E_TEMP_MAX));
auto swing_ver =
((this->swing_mode == climate::CLIMATE_SWING_VERTICAL) || (this->swing_mode == climate::CLIMATE_SWING_BOTH));
auto swing_hor =
((this->swing_mode == climate::CLIMATE_SWING_HORIZONTAL) || (this->swing_mode == climate::CLIMATE_SWING_BOTH));
remote_state[0] = 0xc3;
remote_state[1] = ((temp - 8) << 3) | (swing_ver ? 0 : BALLU_SWING_VER);
remote_state[2] = swing_hor ? 0 : BALLU_SWING_HOR;
remote_state[9] = (this->mode == climate::CLIMATE_MODE_OFF) ? 0 : BALLU_POWER;
remote_state[11] = 0x1e;
// Fan speed
switch (this->fan_mode.value()) {
case climate::CLIMATE_FAN_HIGH:
remote_state[4] |= BALLU_FAN_HIGH;
break;
case climate::CLIMATE_FAN_MEDIUM:
remote_state[4] |= BALLU_FAN_MED;
break;
case climate::CLIMATE_FAN_LOW:
remote_state[4] |= BALLU_FAN_LOW;
break;
case climate::CLIMATE_FAN_AUTO:
remote_state[4] |= BALLU_FAN_AUTO;
break;
default:
break;
}
// Mode
switch (this->mode) {
case climate::CLIMATE_MODE_AUTO:
remote_state[6] |= BALLU_AUTO;
break;
case climate::CLIMATE_MODE_HEAT:
remote_state[6] |= BALLU_HEAT;
break;
case climate::CLIMATE_MODE_COOL:
remote_state[6] |= BALLU_COOL;
break;
case climate::CLIMATE_MODE_DRY:
remote_state[6] |= BALLU_DRY;
break;
case climate::CLIMATE_MODE_FAN_ONLY:
remote_state[6] |= BALLU_FAN;
break;
case climate::CLIMATE_MODE_OFF:
remote_state[6] |= BALLU_AUTO;
default:
break;
}
// Checksum
for (uint8_t i = 0; i < BALLU_STATE_LENGTH - 1; i++)
remote_state[12] += remote_state[i];
ESP_LOGV(TAG, "Sending: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", remote_state[0],
remote_state[1], remote_state[2], remote_state[3], remote_state[4], remote_state[5], remote_state[6],
remote_state[7], remote_state[8], remote_state[9], remote_state[10], remote_state[11], remote_state[12]);
// Send code
auto transmit = this->transmitter_->transmit();
auto data = transmit.get_data();
data->set_carrier_frequency(38000);
// Header
data->mark(BALLU_HEADER_MARK);
data->space(BALLU_HEADER_SPACE);
// Data
for (uint8_t i : remote_state) {
for (uint8_t j = 0; j < 8; j++) {
data->mark(BALLU_BIT_MARK);
bool bit = i & (1 << j);
data->space(bit ? BALLU_ONE_SPACE : BALLU_ZERO_SPACE);
}
}
// Footer
data->mark(BALLU_BIT_MARK);
transmit.perform();
}
bool BalluClimate::on_receive(remote_base::RemoteReceiveData data) {
// Validate header
if (!data.expect_item(BALLU_HEADER_MARK, BALLU_HEADER_SPACE)) {
ESP_LOGV(TAG, "Header fail");
return false;
}
uint8_t remote_state[BALLU_STATE_LENGTH] = {0};
// Read all bytes.
for (int i = 0; i < BALLU_STATE_LENGTH; i++) {
// Read bit
for (int j = 0; j < 8; j++) {
if (data.expect_item(BALLU_BIT_MARK, BALLU_ONE_SPACE))
remote_state[i] |= 1 << j;
else if (!data.expect_item(BALLU_BIT_MARK, BALLU_ZERO_SPACE)) {
ESP_LOGV(TAG, "Byte %d bit %d fail", i, j);
return false;
}
}
ESP_LOGVV(TAG, "Byte %d %02X", i, remote_state[i]);
}
// Validate footer
if (!data.expect_mark(BALLU_BIT_MARK)) {
ESP_LOGV(TAG, "Footer fail");
return false;
}
uint8_t checksum = 0;
// Calculate checksum and compare with signal value.
for (uint8_t i = 0; i < BALLU_STATE_LENGTH - 1; i++)
checksum += remote_state[i];
if (checksum != remote_state[BALLU_STATE_LENGTH - 1]) {
ESP_LOGVV(TAG, "Checksum fail");
return false;
}
ESP_LOGV(TAG, "Received: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", remote_state[0],
remote_state[1], remote_state[2], remote_state[3], remote_state[4], remote_state[5], remote_state[6],
remote_state[7], remote_state[8], remote_state[9], remote_state[10], remote_state[11], remote_state[12]);
// verify header remote code
if (remote_state[0] != 0xc3)
return false;
// powr on/off button
ESP_LOGV(TAG, "Power: %02X", (remote_state[9] & BALLU_POWER));
if ((remote_state[9] & BALLU_POWER) != BALLU_POWER) {
this->mode = climate::CLIMATE_MODE_OFF;
} else {
auto mode = remote_state[6] & 0xe0;
ESP_LOGV(TAG, "Mode: %02X", mode);
switch (mode) {
case BALLU_HEAT:
this->mode = climate::CLIMATE_MODE_HEAT;
break;
case BALLU_COOL:
this->mode = climate::CLIMATE_MODE_COOL;
break;
case BALLU_DRY:
this->mode = climate::CLIMATE_MODE_DRY;
break;
case BALLU_FAN:
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
break;
case BALLU_AUTO:
this->mode = climate::CLIMATE_MODE_AUTO;
break;
}
}
// Set received temp
int temp = remote_state[1] & 0xf8;
ESP_LOGVV(TAG, "Temperature Raw: %02X", temp);
temp = ((uint8_t) temp >> 3) + 8;
ESP_LOGVV(TAG, "Temperature Climate: %u", temp);
this->target_temperature = temp;
// Set received fan speed
auto fan = remote_state[4] & 0xe0;
ESP_LOGVV(TAG, "Fan: %02X", fan);
switch (fan) {
case BALLU_FAN_HIGH:
this->fan_mode = climate::CLIMATE_FAN_HIGH;
break;
case BALLU_FAN_MED:
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
break;
case BALLU_FAN_LOW:
this->fan_mode = climate::CLIMATE_FAN_LOW;
break;
case BALLU_FAN_AUTO:
default:
this->fan_mode = climate::CLIMATE_FAN_AUTO;
break;
}
// Set received swing status
ESP_LOGVV(TAG, "Swing status: %02X %02X", remote_state[1] & BALLU_SWING_VER, remote_state[2] & BALLU_SWING_HOR);
if (((remote_state[1] & BALLU_SWING_VER) != BALLU_SWING_VER) &&
((remote_state[2] & BALLU_SWING_HOR) != BALLU_SWING_HOR)) {
this->swing_mode = climate::CLIMATE_SWING_BOTH;
} else if ((remote_state[1] & BALLU_SWING_VER) != BALLU_SWING_VER) {
this->swing_mode = climate::CLIMATE_SWING_VERTICAL;
} else if ((remote_state[2] & BALLU_SWING_HOR) != BALLU_SWING_HOR) {
this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
} else {
this->swing_mode = climate::CLIMATE_SWING_OFF;
}
this->publish_state();
return true;
}
} // namespace ballu
} // namespace esphome

View File

@@ -0,0 +1,31 @@
#pragma once
#include "esphome/components/climate_ir/climate_ir.h"
namespace esphome {
namespace ballu {
// Support for Ballu air conditioners with YKR-K/002E remote
// Temperature
const float YKR_K_002E_TEMP_MIN = 16.0;
const float YKR_K_002E_TEMP_MAX = 32.0;
class BalluClimate : public climate_ir::ClimateIR {
public:
BalluClimate()
: climate_ir::ClimateIR(YKR_K_002E_TEMP_MIN, YKR_K_002E_TEMP_MAX, 1.0f, true, true,
{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM,
climate::CLIMATE_FAN_HIGH},
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL,
climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {}
protected:
/// Transmit via IR the state of this climate controller.
void transmit_state() override;
/// Handle received IR Buffer
bool on_receive(remote_base::RemoteReceiveData data) override;
};
} // namespace ballu
} // namespace esphome

View File

@@ -0,0 +1,21 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import climate_ir
from esphome.const import CONF_ID
AUTO_LOAD = ["climate_ir"]
CODEOWNERS = ["@bazuchan"]
ballu_ns = cg.esphome_ns.namespace("ballu")
BalluClimate = ballu_ns.class_("BalluClimate", climate_ir.ClimateIR)
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(BalluClimate),
}
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await climate_ir.register_climate_ir(var, config)

View File

@@ -4,7 +4,7 @@
namespace esphome {
namespace bang_bang {
static const char *TAG = "bang_bang.climate";
static const char *const TAG = "bang_bang.climate";
void BangBangClimate::setup() {
this->sensor_->add_on_state_callback([this](float state) {
@@ -21,7 +21,12 @@ void BangBangClimate::setup() {
restore->to_call(this).perform();
} else {
// restore from defaults, change_away handles those for us
this->mode = climate::CLIMATE_MODE_AUTO;
if (supports_cool_ && supports_heat_)
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
else if (supports_cool_)
this->mode = climate::CLIMATE_MODE_COOL;
else if (supports_heat_)
this->mode = climate::CLIMATE_MODE_HEAT;
this->change_away_(false);
}
}
@@ -32,8 +37,8 @@ void BangBangClimate::control(const climate::ClimateCall &call) {
this->target_temperature_low = *call.get_target_temperature_low();
if (call.get_target_temperature_high().has_value())
this->target_temperature_high = *call.get_target_temperature_high();
if (call.get_away().has_value())
this->change_away_(*call.get_away());
if (call.get_preset().has_value())
this->change_away_(*call.get_preset() == climate::CLIMATE_PRESET_AWAY);
this->compute_state_();
this->publish_state();
@@ -41,21 +46,27 @@ void BangBangClimate::control(const climate::ClimateCall &call) {
climate::ClimateTraits BangBangClimate::traits() {
auto traits = climate::ClimateTraits();
traits.set_supports_current_temperature(true);
traits.set_supports_auto_mode(true);
traits.set_supports_cool_mode(this->supports_cool_);
traits.set_supports_heat_mode(this->supports_heat_);
traits.set_supported_modes({
climate::CLIMATE_MODE_OFF,
});
if (supports_cool_)
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
if (supports_heat_)
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
if (supports_cool_ && supports_heat_)
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL);
traits.set_supports_two_point_target_temperature(true);
traits.set_supports_away(this->supports_away_);
if (supports_away_)
traits.set_supported_presets({
climate::CLIMATE_PRESET_HOME,
climate::CLIMATE_PRESET_AWAY,
});
traits.set_supports_action(true);
return traits;
}
void BangBangClimate::compute_state_() {
if (this->mode != climate::CLIMATE_MODE_AUTO) {
// in non-auto mode, switch directly to appropriate action
// - HEAT mode -> HEATING action
// - COOL mode -> COOLING action
// - OFF mode -> OFF action (not IDLE!)
this->switch_to_action_(static_cast<climate::ClimateAction>(this->mode));
if (this->mode == climate::CLIMATE_MODE_OFF) {
this->switch_to_action_(climate::CLIMATE_ACTION_OFF);
return;
}
if (isnan(this->current_temperature) || isnan(this->target_temperature_low) || isnan(this->target_temperature_high)) {
@@ -140,7 +151,7 @@ void BangBangClimate::change_away_(bool away) {
this->target_temperature_low = this->away_config_.default_temperature_low;
this->target_temperature_high = this->away_config_.default_temperature_high;
}
this->away = away;
this->preset = away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME;
}
void BangBangClimate::set_normal_config(const BangBangClimateTargetTempConfig &normal_config) {
this->normal_config_ = normal_config;

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

@@ -4,7 +4,7 @@
namespace esphome {
namespace bh1750 {
static const char *TAG = "bh1750.sensor";
static const char *const TAG = "bh1750.sensor";
static const uint8_t BH1750_COMMAND_POWER_ON = 0b00000001;
static const uint8_t BH1750_COMMAND_MT_REG_HI = 0b01000000; // last 3 bits
@@ -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

@@ -5,8 +5,9 @@ from esphome.const import (
CONF_ID,
CONF_RESOLUTION,
DEVICE_CLASS_ILLUMINANCE,
ICON_EMPTY,
STATE_CLASS_MEASUREMENT,
UNIT_LUX,
CONF_MEASUREMENT_DURATION,
)
DEPENDENCIES = ["i2c"]
@@ -25,16 +26,24 @@ 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_of_measurement=UNIT_LUX,
accuracy_decimals=1,
device_class=DEVICE_CLASS_ILLUMINANCE,
state_class=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 +51,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

@@ -4,7 +4,7 @@
namespace esphome {
namespace binary {
static const char *TAG = "binary.fan";
static const char *const TAG = "binary.fan";
void binary::BinaryFan::dump_config() {
ESP_LOGCONFIG(TAG, "Fan '%s':", this->fan_->get_name().c_str());

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

@@ -12,7 +12,7 @@ class BinaryLightOutput : public light::LightOutput {
void set_output(output::BinaryOutput *output) { output_ = output; }
light::LightTraits get_traits() override {
auto traits = light::LightTraits();
traits.set_supports_brightness(false);
traits.set_supported_color_modes({light::ColorMode::ON_OFF});
return traits;
}
void write_state(light::LightState *state) override {

View File

@@ -4,7 +4,9 @@ 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_DISABLED_BY_DEFAULT,
CONF_FILTERS,
CONF_ID,
CONF_INTERNAL,
@@ -21,7 +23,6 @@ from esphome.const import (
CONF_STATE,
CONF_TIMING,
CONF_TRIGGER_ID,
CONF_FOR,
CONF_NAME,
CONF_MQTT_ID,
DEVICE_CLASS_EMPTY,
@@ -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(
@@ -269,7 +316,7 @@ def validate_multi_click_timing(value):
device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend(
BINARY_SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
{
cv.GenerateID(): cv.declare_id(BinarySensor),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
@@ -325,18 +372,13 @@ BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend(
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
}
),
cv.Optional(CONF_INVERTED): cv.invalid(
"The inverted binary_sensor property has been replaced by the "
"new 'invert' binary sensor filter. Please see "
"https://esphome.io/components/binary_sensor/index.html."
),
}
)
@coroutine
def setup_binary_sensor_core_(var, config):
async def setup_binary_sensor_core_(var, config):
cg.add(var.set_name(config[CONF_NAME]))
cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT]))
if CONF_INTERNAL in config:
cg.add(var.set_internal(config[CONF_INTERNAL]))
if CONF_DEVICE_CLASS in config:
@@ -344,28 +386,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,40 +423,34 @@ 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(
{
cv.Required(CONF_ID): cv.use_id(BinarySensor),
cv.Optional(CONF_FOR): cv.invalid(
"This option has been removed in 1.13, please use the "
"'for' condition instead."
),
}
)
@@ -422,20 +458,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

@@ -4,7 +4,7 @@
namespace esphome {
namespace binary_sensor {
static const char *TAG = "binary_sensor.automation";
static const char *const TAG = "binary_sensor.automation";
void binary_sensor::MultiClickTrigger::on_state_(bool state) {
// Handle duplicate events
@@ -80,6 +80,10 @@ void binary_sensor::MultiClickTrigger::schedule_cooldown_() {
this->cancel_timeout("is_not_valid");
}
void binary_sensor::MultiClickTrigger::schedule_is_valid_(uint32_t min_length) {
if (min_length == 0) {
this->is_valid_ = true;
return;
}
this->is_valid_ = false;
this->set_timeout("is_valid", min_length, [this]() {
ESP_LOGV(TAG, "Multi Click: You can now %s the button.", this->parent_->state ? "RELEASE" : "PRESS");

View File

@@ -1,5 +1,7 @@
#pragma once
#include <utility>
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
@@ -87,8 +89,8 @@ class DoubleClickTrigger : public Trigger<> {
class MultiClickTrigger : public Trigger<>, public Component {
public:
explicit MultiClickTrigger(BinarySensor *parent, const std::vector<MultiClickTriggerEvent> &timing)
: parent_(parent), timing_(timing) {}
explicit MultiClickTrigger(BinarySensor *parent, std::vector<MultiClickTriggerEvent> timing)
: parent_(parent), timing_(std::move(timing)) {}
void setup() override {
this->last_state_ = this->parent_->state;

View File

@@ -5,7 +5,7 @@ namespace esphome {
namespace binary_sensor {
static const char *TAG = "binary_sensor";
static const char *const TAG = "binary_sensor";
void BinarySensor::add_on_state_callback(std::function<void(bool)> &&callback) {
this->state_callback_.add(std::move(callback));
@@ -61,7 +61,7 @@ void BinarySensor::add_filter(Filter *filter) {
last_filter->next_ = filter;
}
}
void BinarySensor::add_filters(std::vector<Filter *> filters) {
void BinarySensor::add_filters(const std::vector<Filter *> &filters) {
for (Filter *filter : filters) {
this->add_filter(filter);
}

View File

@@ -9,10 +9,10 @@ namespace esphome {
namespace binary_sensor {
#define LOG_BINARY_SENSOR(prefix, type, obj) \
if (obj != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, obj->get_name().c_str()); \
if (!obj->get_device_class().empty()) { \
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, obj->get_device_class().c_str()); \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \
if (!(obj)->get_device_class().empty()) { \
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
} \
}
@@ -60,7 +60,7 @@ class BinarySensor : public Nameable {
std::string get_device_class();
void add_filter(Filter *filter);
void add_filters(std::vector<Filter *> filters);
void add_filters(const std::vector<Filter *> &filters);
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)

View File

@@ -1,11 +1,13 @@
#include "filter.h"
#include "binary_sensor.h"
#include <utility>
namespace esphome {
namespace binary_sensor {
static const char *TAG = "sensor.filter";
static const char *const TAG = "sensor.filter";
void Filter::output(bool value, bool is_initial) {
if (!this->dedup_.next(value))
@@ -64,7 +66,51 @@ float DelayedOffFilter::get_setup_priority() const { return setup_priority::HARD
optional<bool> InvertFilter::new_value(bool value, bool is_initial) { return !value; }
LambdaFilter::LambdaFilter(const std::function<optional<bool>(bool)> &f) : f_(f) {}
AutorepeatFilter::AutorepeatFilter(std::vector<AutorepeatFilterTiming> timings) : timings_(std::move(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(std::function<optional<bool>(bool)> f) : f_(std::move(f)) {}
optional<bool> LambdaFilter::new_value(bool value, bool is_initial) { return this->f_(value); }

View File

@@ -66,9 +66,36 @@ 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(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);
explicit LambdaFilter(std::function<optional<bool>(bool)> f);
optional<bool> new_value(bool value, bool is_initial) override;

View File

@@ -4,7 +4,7 @@
namespace esphome {
namespace binary_sensor_map {
static const char *TAG = "binary_sensor_map";
static const char *const TAG = "binary_sensor_map";
void BinarySensorMap::dump_config() { LOG_SENSOR(" ", "binary_sensor_map", this); }

View File

@@ -7,11 +7,10 @@ from esphome.const import (
CONF_CHANNELS,
CONF_VALUE,
CONF_TYPE,
DEVICE_CLASS_EMPTY,
UNIT_EMPTY,
ICON_CHECK_CIRCLE_OUTLINE,
CONF_BINARY_SENSOR,
CONF_GROUP,
STATE_CLASS_NONE,
)
DEPENDENCIES = ["binary_sensor"]
@@ -34,7 +33,9 @@ entry = {
CONFIG_SCHEMA = cv.typed_schema(
{
CONF_GROUP: sensor.sensor_schema(
UNIT_EMPTY, ICON_CHECK_CIRCLE_OUTLINE, 0, DEVICE_CLASS_EMPTY
icon=ICON_CHECK_CIRCLE_OUTLINE,
accuracy_decimals=0,
state_class=STATE_CLASS_NONE,
).extend(
{
cv.GenerateID(): cv.declare_id(BinarySensorMap),
@@ -48,14 +49,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 *const 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,127 @@
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 (
CONF_ID,
CONF_LAMBDA,
STATE_CLASS_NONE,
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(
accuracy_decimals=0,
state_class=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 *const 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 *const 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,7 +6,7 @@
namespace esphome {
namespace ble_presence {
static const char *TAG = "ble_presence";
static const char *const TAG = "ble_presence";
void BLEPresenceDevice::dump_config() { LOG_BINARY_SENSOR("", "BLE Presence", this); }

View File

@@ -6,7 +6,7 @@
namespace esphome {
namespace ble_rssi {
static const char *TAG = "ble_rssi";
static const char *const TAG = "ble_rssi";
void BLERSSISensor::dump_config() { LOG_SENSOR("", "BLE RSSI Sensor", this); }

View File

@@ -6,8 +6,8 @@ from esphome.const import (
CONF_MAC_ADDRESS,
CONF_ID,
DEVICE_CLASS_SIGNAL_STRENGTH,
STATE_CLASS_MEASUREMENT,
UNIT_DECIBEL,
ICON_EMPTY,
)
DEPENDENCIES = ["esp32_ble_tracker"]
@@ -18,7 +18,12 @@ 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_of_measurement=UNIT_DECIBEL,
accuracy_decimals=0,
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
state_class=STATE_CLASS_MEASUREMENT,
)
.extend(
{
cv.GenerateID(): cv.declare_id(BLERSSISensor),
@@ -32,11 +37,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

@@ -6,7 +6,7 @@
namespace esphome {
namespace ble_scanner {
static const char *TAG = "ble_scanner";
static const char *const TAG = "ble_scanner";
void BLEScanner::dump_config() { LOG_TEXT_SENSOR("", "BLE Scanner", this); }

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

@@ -4,7 +4,7 @@
namespace esphome {
namespace bme280 {
static const char *TAG = "bme280.sensor";
static const char *const TAG = "bme280.sensor";
static const uint8_t BME280_REGISTER_DIG_T1 = 0x88;
static const uint8_t BME280_REGISTER_DIG_T2 = 0x8A;

View File

@@ -11,7 +11,7 @@ from esphome.const import (
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
ICON_EMPTY,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_HECTOPASCAL,
UNIT_PERCENT,
@@ -48,7 +48,10 @@ CONFIG_SCHEMA = (
{
cv.GenerateID(): cv.declare_id(BME280Component),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
@@ -57,7 +60,10 @@ CONFIG_SCHEMA = (
}
),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE
unit_of_measurement=UNIT_HECTOPASCAL,
accuracy_decimals=1,
device_class=DEVICE_CLASS_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
@@ -66,7 +72,10 @@ CONFIG_SCHEMA = (
}
),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
@@ -84,26 +93,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

@@ -4,7 +4,7 @@
namespace esphome {
namespace bme680 {
static const char *TAG = "bme680.sensor";
static const char *const TAG = "bme680.sensor";
static const uint8_t BME680_REGISTER_COEFF1 = 0x89;
static const uint8_t BME680_REGISTER_COEFF2 = 0xE1;

View File

@@ -12,14 +12,13 @@ from esphome.const import (
CONF_OVERSAMPLING,
CONF_PRESSURE,
CONF_TEMPERATURE,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_OHM,
ICON_GAS_CYLINDER,
UNIT_CELSIUS,
ICON_EMPTY,
UNIT_HECTOPASCAL,
UNIT_PERCENT,
)
@@ -58,7 +57,10 @@ CONFIG_SCHEMA = (
{
cv.GenerateID(): cv.declare_id(BME680Component),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
@@ -67,7 +69,10 @@ CONFIG_SCHEMA = (
}
),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE
unit_of_measurement=UNIT_HECTOPASCAL,
accuracy_decimals=1,
device_class=DEVICE_CLASS_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
@@ -76,7 +81,10 @@ CONFIG_SCHEMA = (
}
),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
@@ -85,7 +93,10 @@ CONFIG_SCHEMA = (
}
),
cv.Optional(CONF_GAS_RESISTANCE): sensor.sensor_schema(
UNIT_OHM, ICON_GAS_CYLINDER, 1, DEVICE_CLASS_EMPTY
unit_of_measurement=UNIT_OHM,
icon=ICON_GAS_CYLINDER,
accuracy_decimals=1,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
IIR_FILTER_OPTIONS, upper=True
@@ -114,32 +125,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 *const 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,126 @@
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_HUMIDITY,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
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_of_measurement=UNIT_CELSIUS,
icon=ICON_THERMOMETER,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)}
),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
unit_of_measurement=UNIT_HECTOPASCAL,
icon=ICON_GAUGE,
accuracy_decimals=1,
device_class=DEVICE_CLASS_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)}
),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
icon=ICON_WATER_PERCENT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=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_of_measurement=UNIT_OHM,
icon=ICON_GAS_CYLINDER,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_IAQ): sensor.sensor_schema(
unit_of_measurement=UNIT_IAQ,
icon=ICON_GAUGE,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_IAQ_ACCURACY): sensor.sensor_schema(
icon=ICON_ACCURACY,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CO2_EQUIVALENT): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION,
icon=ICON_TEST_TUBE,
accuracy_decimals=1,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_BREATH_VOC_EQUIVALENT): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION,
icon=ICON_TEST_TUBE,
accuracy_decimals=1,
state_class=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

@@ -4,7 +4,7 @@
namespace esphome {
namespace bmp085 {
static const char *TAG = "bmp085.sensor";
static const char *const TAG = "bmp085.sensor";
static const uint8_t BMP085_ADDRESS = 0x77;
static const uint8_t BMP085_REGISTER_AC1_H = 0xAA;

View File

@@ -7,8 +7,8 @@ from esphome.const import (
CONF_TEMPERATURE,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
ICON_EMPTY,
UNIT_HECTOPASCAL,
)
@@ -24,10 +24,16 @@ CONFIG_SCHEMA = (
{
cv.GenerateID(): cv.declare_id(BMP085Component),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE
unit_of_measurement=UNIT_HECTOPASCAL,
accuracy_decimals=1,
device_class=DEVICE_CLASS_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
@@ -36,17 +42,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

@@ -4,7 +4,7 @@
namespace esphome {
namespace bmp280 {
static const char *TAG = "bmp280.sensor";
static const char *const TAG = "bmp280.sensor";
static const uint8_t BMP280_REGISTER_STATUS = 0xF3;
static const uint8_t BMP280_REGISTER_CONTROL = 0xF4;

View File

@@ -7,8 +7,8 @@ from esphome.const import (
CONF_TEMPERATURE,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
ICON_EMPTY,
UNIT_HECTOPASCAL,
CONF_IIR_FILTER,
CONF_OVERSAMPLING,
@@ -45,7 +45,10 @@ CONFIG_SCHEMA = (
{
cv.GenerateID(): cv.declare_id(BMP280Component),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
@@ -54,7 +57,10 @@ CONFIG_SCHEMA = (
}
),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE
unit_of_measurement=UNIT_HECTOPASCAL,
accuracy_decimals=1,
device_class=DEVICE_CLASS_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
@@ -72,19 +78,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

@@ -4,7 +4,7 @@
namespace esphome {
namespace canbus {
static const char *TAG = "canbus";
static const char *const TAG = "canbus";
void Canbus::setup() {
ESP_LOGCONFIG(TAG, "Setting up Canbus...");

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

@@ -6,7 +6,7 @@
namespace esphome {
namespace captive_portal {
static const char *TAG = "captive_portal";
static const char *const TAG = "captive_portal";
void CaptivePortal::handle_index(AsyncWebServerRequest *request) {
AsyncResponseStream *stream = request->beginResponseStream("text/html");
@@ -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_) {
@@ -168,7 +147,7 @@ float CaptivePortal::get_setup_priority() const {
}
void CaptivePortal::dump_config() { ESP_LOGCONFIG(TAG, "Captive Portal:"); }
CaptivePortal *global_captive_portal = nullptr;
CaptivePortal *global_captive_portal = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace captive_portal
} // namespace esphome

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,16 +62,13 @@ 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};
};
extern CaptivePortal *global_captive_portal;
extern CaptivePortal *global_captive_portal; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace captive_portal
} // namespace esphome

View File

@@ -4,7 +4,7 @@
namespace esphome {
namespace ccs811 {
static const char *TAG = "ccs811";
static const char *const TAG = "ccs811";
// based on
// - https://cdn.sparkfun.com/datasheets/BreakoutBoards/CCS811_Programming_Guide.pdf

View File

@@ -3,10 +3,12 @@ import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_ID,
DEVICE_CLASS_EMPTY,
ICON_RADIATOR,
STATE_CLASS_MEASUREMENT,
UNIT_PARTS_PER_MILLION,
UNIT_PARTS_PER_BILLION,
CONF_BASELINE,
CONF_ECO2,
CONF_TEMPERATURE,
CONF_TVOC,
CONF_HUMIDITY,
@@ -20,18 +22,21 @@ CCS811Component = ccs811_ns.class_(
"CCS811Component", cg.PollingComponent, i2c.I2CDevice
)
CONF_ECO2 = "eco2"
CONF_BASELINE = "baseline"
CONFIG_SCHEMA = (
cv.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_of_measurement=UNIT_PARTS_PER_MILLION,
icon=ICON_MOLECULE_CO2,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Required(CONF_TVOC): sensor.sensor_schema(
UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0, DEVICE_CLASS_EMPTY
unit_of_measurement=UNIT_PARTS_PER_BILLION,
icon=ICON_RADIATOR,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_BASELINE): cv.hex_uint16_t,
cv.Optional(CONF_TEMPERATURE): cv.use_id(sensor.Sensor),
@@ -43,22 +48,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,15 @@ from esphome import automation
from esphome.components import mqtt
from esphome.const import (
CONF_AWAY,
CONF_CUSTOM_FAN_MODE,
CONF_CUSTOM_PRESET,
CONF_DISABLED_BY_DEFAULT,
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 +23,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 +37,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_MODE_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 +61,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,
@@ -69,7 +87,7 @@ validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True)
# Actions
ControlAction = climate_ns.class_("ControlAction", automation.Action)
CLIMATE_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend(
CLIMATE_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.GenerateID(): cv.declare_id(Climate),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTClimateComponent),
@@ -85,9 +103,9 @@ 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]))
cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT]))
if CONF_INTERNAL in config:
cg.add(var.set_internal(config[CONF_INTERNAL]))
visual = config[CONF_VISUAL]
@@ -100,15 +118,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 +136,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 +150,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 {
@@ -24,8 +27,13 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
call.set_target_temperature(this->target_temperature_.optional_value(x...));
call.set_target_temperature_low(this->target_temperature_low_.optional_value(x...));
call.set_target_temperature_high(this->target_temperature_high_.optional_value(x...));
call.set_away(this->away_.optional_value(x...));
if (away_.has_value()) {
call.set_preset(away_.value(x...) ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME);
}
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,10 +1,9 @@
#include "climate.h"
#include "esphome/core/log.h"
namespace esphome {
namespace climate {
static const char *TAG = "climate";
static const char *const TAG = "climate";
void ClimateCall::perform() {
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
@@ -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);
@@ -30,9 +43,6 @@ void ClimateCall::perform() {
if (this->target_temperature_high_.has_value()) {
ESP_LOGD(TAG, " Target Temperature High: %.2f", *this->target_temperature_high_);
}
if (this->away_.has_value()) {
ESP_LOGD(TAG, " Away Mode: %s", ONOFF(*this->away_));
}
this->parent_->control(*this);
}
void ClimateCall::validate_() {
@@ -44,13 +54,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)) {
@@ -93,12 +122,6 @@ void ClimateCall::validate_() {
this->target_temperature_high_.reset();
}
}
if (this->away_.has_value()) {
if (!traits.get_supports_away()) {
ESP_LOGW(TAG, " Cannot set away mode for this device!");
this->away_.reset();
}
}
}
ClimateCall &ClimateCall::set_mode(ClimateMode mode) {
this->mode_ = mode;
@@ -117,6 +140,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 +149,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 +172,57 @@ 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());
if (this->parent_->get_traits().supports_custom_fan_mode(fan_mode)) {
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 {
if (this->parent_->get_traits().supports_custom_preset(preset)) {
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;
@@ -186,15 +258,23 @@ const optional<ClimateMode> &ClimateCall::get_mode() const { return this->mode_;
const optional<float> &ClimateCall::get_target_temperature() const { return this->target_temperature_; }
const optional<float> &ClimateCall::get_target_temperature_low() const { return this->target_temperature_low_; }
const optional<float> &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; }
const optional<bool> &ClimateCall::get_away() const { return this->away_; }
optional<bool> ClimateCall::get_away() const {
if (!this->preset_.has_value())
return {};
return *this->preset_ == ClimatePreset::CLIMATE_PRESET_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;
this->preset_ = away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME;
return *this;
}
ClimateCall &ClimateCall::set_away(optional<bool> away) {
this->away_ = away;
if (away.has_value())
this->preset_ = *away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME;
return *this;
}
ClimateCall &ClimateCall::set_target_temperature_high(optional<float> target_temperature_high) {
@@ -215,6 +295,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) {
@@ -226,8 +312,12 @@ void Climate::add_on_state_callback(std::function<void()> &&callback) {
this->state_callback_.add(std::move(callback));
}
// Random 32bit value; If this changes existing restore preferences are invalidated
static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL;
optional<ClimateDeviceRestoreState> Climate::restore_state_() {
this->rtc_ = global_preferences.make_preference<ClimateDeviceRestoreState>(this->get_object_id_hash());
this->rtc_ =
global_preferences.make_preference<ClimateDeviceRestoreState>(this->get_object_id_hash() ^ RESTORE_STATE_VERSION);
ClimateDeviceRestoreState recovered{};
if (!this->rtc_.load(&recovered))
return {};
@@ -246,11 +336,32 @@ void Climate::save_state_() {
} else {
state.target_temperature = this->target_temperature;
}
if (traits.get_supports_away()) {
state.away = this->away;
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_supports_fan_modes()) {
state.fan_mode = this->fan_mode;
if (!traits.get_supported_custom_fan_modes().empty() && custom_fan_mode.has_value()) {
state.uses_custom_fan_mode = true;
const auto &supported = traits.get_supported_custom_fan_modes();
std::vector<std::string> vec{supported.begin(), supported.end()};
auto it = std::find(vec.begin(), vec.end(), custom_fan_mode);
if (it != vec.end()) {
state.custom_fan_mode = std::distance(vec.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;
const auto &supported = traits.get_supported_custom_presets();
std::vector<std::string> vec{supported.begin(), supported.end()};
auto it = std::find(vec.begin(), vec.end(), custom_preset);
// only set custom preset if value exists, otherwise leave it as is
if (it != vec.cend()) {
state.custom_preset = std::distance(vec.begin(), it);
}
}
if (traits.get_supports_swing_modes()) {
state.swing_mode = this->swing_mode;
@@ -266,8 +377,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));
@@ -281,9 +401,6 @@ void Climate::publish_state() {
} else {
ESP_LOGD(TAG, " Target Temperature: %.2f°C", this->target_temperature);
}
if (traits.get_supports_away()) {
ESP_LOGD(TAG, " Away: %s", ONOFF(this->away));
}
// Send state to frontend
this->state_callback_.call();
@@ -329,12 +446,12 @@ ClimateCall ClimateDeviceRestoreState::to_call(Climate *climate) {
} else {
call.set_target_temperature(this->target_temperature);
}
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);
}
@@ -349,12 +466,28 @@ void ClimateDeviceRestoreState::apply(Climate *climate) {
} else {
climate->target_temperature = this->target_temperature;
}
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) {
// std::set has consistent order (lexicographic for strings), so this is ok
const auto &modes = traits.get_supported_custom_fan_modes();
std::vector<std::string> modes_vec{modes.begin(), modes.end()};
if (custom_fan_mode < modes_vec.size()) {
climate->custom_fan_mode = modes_vec[this->custom_fan_mode];
}
}
if (traits.get_supports_presets() && !this->uses_custom_preset) {
climate->preset = this->preset;
}
if (!traits.get_supported_custom_presets().empty() && uses_custom_preset) {
// std::set has consistent order (lexicographic for strings), so this is ok
const auto &presets = traits.get_supported_custom_presets();
std::vector<std::string> presets_vec{presets.begin(), presets.end()};
if (custom_preset < presets_vec.size()) {
climate->custom_preset = presets_vec[this->custom_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"
@@ -10,8 +11,8 @@ namespace esphome {
namespace climate {
#define LOG_CLIMATE(prefix, type, obj) \
if (obj != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, obj->get_name().c_str()); \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \
}
class Climate;
@@ -62,7 +63,9 @@ class ClimateCall {
* For climate devices with two point target temperature control
*/
ClimateCall &set_target_temperature_high(optional<float> target_temperature_high);
ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead", "v1.20")
ClimateCall &set_away(bool away);
ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead", "v1.20")
ClimateCall &set_away(optional<bool> away);
/// Set the fan mode of the climate device.
ClimateCall &set_fan_mode(ClimateFanMode fan_mode);
@@ -70,12 +73,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();
@@ -83,9 +96,13 @@ class ClimateCall {
const optional<float> &get_target_temperature() const;
const optional<float> &get_target_temperature_low() const;
const optional<float> &get_target_temperature_high() const;
const optional<bool> &get_away() const;
ESPDEPRECATED("get_away() is deprecated, please use .get_preset() instead", "v1.20")
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_();
@@ -95,16 +112,27 @@ class ClimateCall {
optional<float> target_temperature_;
optional<float> target_temperature_low_;
optional<float> target_temperature_high_;
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.
/// Make sure to update RESTORE_STATE_VERSION when changing the struct entries.
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;
@@ -133,7 +161,7 @@ struct ClimateDeviceRestoreState {
*
* The entire state of the climate device is encoded in public properties of the base class (current_temperature,
* mode etc). These are read-only for the user and rw for integrations. The reason these are public
* is for simple access to them from lambdas `if (id(my_climate).mode == climate::CLIMATE_MODE_AUTO) ...`
* is for simple access to them from lambdas `if (id(my_climate).mode == climate::CLIMATE_MODE_HEAT_COOL) ...`
*/
class Climate : public Nameable {
public:
@@ -165,14 +193,24 @@ class Climate : public Nameable {
* Away allows climate devices to have two different target temperature configs:
* one for normal mode and one for away mode.
*/
ESPDEPRECATED("away is deprecated, use preset instead", "v1.20")
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.
*

View File

@@ -17,6 +17,8 @@ const char *climate_mode_to_string(ClimateMode mode) {
return "FAN_ONLY";
case CLIMATE_MODE_DRY:
return "DRY";
case CLIMATE_MODE_HEAT_COOL:
return "HEAT_COOL";
default:
return "UNKNOWN";
}
@@ -80,5 +82,28 @@ const char *climate_swing_mode_to_string(ClimateSwingMode swing_mode) {
}
}
const char *climate_preset_to_string(ClimatePreset preset) {
switch (preset) {
case climate::CLIMATE_PRESET_NONE:
return "NONE";
case climate::CLIMATE_PRESET_HOME:
return "HOME";
case climate::CLIMATE_PRESET_ECO:
return "ECO";
case climate::CLIMATE_PRESET_AWAY:
return "AWAY";
case climate::CLIMATE_PRESET_BOOST:
return "BOOST";
case climate::CLIMATE_PRESET_COMFORT:
return "COMFORT";
case climate::CLIMATE_PRESET_SLEEP:
return "SLEEP";
case climate::CLIMATE_PRESET_ACTIVITY:
return "ACTIVITY";
default:
return "UNKNOWN";
}
}
} // namespace climate
} // namespace esphome

View File

@@ -7,37 +7,41 @@ namespace climate {
/// Enum for all modes a climate device can be in.
enum ClimateMode : uint8_t {
/// The climate device is off (not in auto, heat or cool mode)
/// The climate device is off
CLIMATE_MODE_OFF = 0,
/// The climate device is set to automatically change the heating/cooling cycle
CLIMATE_MODE_AUTO = 1,
/// The climate device is manually set to cool mode (not in auto mode!)
/// The climate device is set to heat/cool to reach the target temperature.
CLIMATE_MODE_HEAT_COOL = 1,
/// The climate device is set to cool to reach the target temperature
CLIMATE_MODE_COOL = 2,
/// The climate device is manually set to heat mode (not in auto mode!)
/// The climate device is set to heat to reach the target temperature
CLIMATE_MODE_HEAT = 3,
/// The climate device is manually set to fan only mode
/// The climate device only has the fan enabled, no heating or cooling is taking place
CLIMATE_MODE_FAN_ONLY = 4,
/// The climate device is manually set to dry mode
/// The climate device is set to dry/humidity mode
CLIMATE_MODE_DRY = 5,
/** The climate device is adjusting the temperatre dynamically.
* For example, the target temperature can be adjusted based on a schedule, or learned behavior.
* The target temperature can't be adjusted when in this mode.
*/
CLIMATE_MODE_AUTO = 6
};
/// Enum for the current action of the climate device. Values match those of ClimateMode.
enum ClimateAction : uint8_t {
/// The climate device is off (inactive or no power)
CLIMATE_ACTION_OFF = 0,
/// The climate device is actively cooling (usually in cool or auto mode)
/// The climate device is actively cooling
CLIMATE_ACTION_COOLING = 2,
/// The climate device is actively heating (usually in heat or auto mode)
/// The climate device is actively heating
CLIMATE_ACTION_HEATING = 3,
/// The climate device is idle (monitoring climate but no action needed)
CLIMATE_ACTION_IDLE = 4,
/// The climate device is drying (either mode DRY or AUTO)
/// The climate device is drying
CLIMATE_ACTION_DRYING = 5,
/// The climate device is in fan only mode (either mode FAN_ONLY or AUTO)
/// The climate device is in fan only mode
CLIMATE_ACTION_FAN = 6,
};
/// Enum for all modes a climate fan can be in
enum ClimateFanMode : uint8_t {
/// The fan mode is set to On
CLIMATE_FAN_ON = 0,
@@ -61,7 +65,7 @@ enum ClimateFanMode : uint8_t {
/// Enum for all modes a climate swing can be in
enum ClimateSwingMode : uint8_t {
/// The sing mode is set to Off
/// The swing mode is set to Off
CLIMATE_SWING_OFF = 0,
/// The fan mode is set to Both
CLIMATE_SWING_BOTH = 1,
@@ -71,6 +75,26 @@ enum ClimateSwingMode : uint8_t {
CLIMATE_SWING_HORIZONTAL = 3,
};
/// Enum for all modes a climate swing can be in
enum ClimatePreset : uint8_t {
/// No preset is active
CLIMATE_PRESET_NONE = 0,
/// Device is in home preset
CLIMATE_PRESET_HOME = 1,
/// Device is in away preset
CLIMATE_PRESET_AWAY = 2,
/// Device is in boost preset
CLIMATE_PRESET_BOOST = 3,
/// Device is in comfort preset
CLIMATE_PRESET_COMFORT = 4,
/// Device is running an energy-saving preset
CLIMATE_PRESET_ECO = 5,
/// Device is prepared for sleep
CLIMATE_PRESET_SLEEP = 6,
/// Device is reacting to activity (e.g., movement sensors)
CLIMATE_PRESET_ACTIVITY = 7,
};
/// Convert the given ClimateMode to a human-readable string.
const char *climate_mode_to_string(ClimateMode mode);
@@ -83,5 +107,8 @@ const char *climate_fan_mode_to_string(ClimateFanMode mode);
/// Convert the given ClimateSwingMode to a human-readable string.
const char *climate_swing_mode_to_string(ClimateSwingMode mode);
/// Convert the given ClimateSwingMode to a human-readable string.
const char *climate_preset_to_string(ClimatePreset preset);
} // namespace climate
} // namespace esphome

View File

@@ -4,50 +4,6 @@
namespace esphome {
namespace climate {
bool ClimateTraits::supports_mode(ClimateMode mode) const {
switch (mode) {
case CLIMATE_MODE_OFF:
return true;
case CLIMATE_MODE_AUTO:
return this->supports_auto_mode_;
case CLIMATE_MODE_COOL:
return this->supports_cool_mode_;
case CLIMATE_MODE_HEAT:
return this->supports_heat_mode_;
case CLIMATE_MODE_FAN_ONLY:
return this->supports_fan_only_mode_;
case CLIMATE_MODE_DRY:
return this->supports_dry_mode_;
default:
return false;
}
}
bool ClimateTraits::get_supports_current_temperature() const { return supports_current_temperature_; }
void ClimateTraits::set_supports_current_temperature(bool supports_current_temperature) {
supports_current_temperature_ = supports_current_temperature;
}
bool ClimateTraits::get_supports_two_point_target_temperature() const { return supports_two_point_target_temperature_; }
void ClimateTraits::set_supports_two_point_target_temperature(bool supports_two_point_target_temperature) {
supports_two_point_target_temperature_ = supports_two_point_target_temperature;
}
void ClimateTraits::set_supports_auto_mode(bool supports_auto_mode) { supports_auto_mode_ = supports_auto_mode; }
void ClimateTraits::set_supports_cool_mode(bool supports_cool_mode) { supports_cool_mode_ = supports_cool_mode; }
void ClimateTraits::set_supports_heat_mode(bool supports_heat_mode) { supports_heat_mode_ = supports_heat_mode; }
void ClimateTraits::set_supports_fan_only_mode(bool supports_fan_only_mode) {
supports_fan_only_mode_ = supports_fan_only_mode;
}
void ClimateTraits::set_supports_dry_mode(bool supports_dry_mode) { supports_dry_mode_ = supports_dry_mode; }
void ClimateTraits::set_supports_away(bool supports_away) { supports_away_ = supports_away; }
void ClimateTraits::set_supports_action(bool supports_action) { supports_action_ = supports_action; }
float ClimateTraits::get_visual_min_temperature() const { return visual_min_temperature_; }
void ClimateTraits::set_visual_min_temperature(float visual_min_temperature) {
visual_min_temperature_ = visual_min_temperature;
}
float ClimateTraits::get_visual_max_temperature() const { return visual_max_temperature_; }
void ClimateTraits::set_visual_max_temperature(float visual_max_temperature) {
visual_max_temperature_ = visual_max_temperature;
}
float ClimateTraits::get_visual_temperature_step() const { return visual_temperature_step_; }
int8_t ClimateTraits::get_temperature_accuracy_decimals() const {
// use printf %g to find number of digits based on temperature step
char buf[32];
@@ -59,95 +15,6 @@ int8_t ClimateTraits::get_temperature_accuracy_decimals() const {
return str.length() - dot_pos - 1;
}
void ClimateTraits::set_visual_temperature_step(float temperature_step) { visual_temperature_step_ = temperature_step; }
bool ClimateTraits::get_supports_away() const { return supports_away_; }
bool ClimateTraits::get_supports_action() const { return supports_action_; }
void ClimateTraits::set_supports_fan_mode_on(bool supports_fan_mode_on) {
this->supports_fan_mode_on_ = supports_fan_mode_on;
}
void ClimateTraits::set_supports_fan_mode_off(bool supports_fan_mode_off) {
this->supports_fan_mode_off_ = supports_fan_mode_off;
}
void ClimateTraits::set_supports_fan_mode_auto(bool supports_fan_mode_auto) {
this->supports_fan_mode_auto_ = supports_fan_mode_auto;
}
void ClimateTraits::set_supports_fan_mode_low(bool supports_fan_mode_low) {
this->supports_fan_mode_low_ = supports_fan_mode_low;
}
void ClimateTraits::set_supports_fan_mode_medium(bool supports_fan_mode_medium) {
this->supports_fan_mode_medium_ = supports_fan_mode_medium;
}
void ClimateTraits::set_supports_fan_mode_high(bool supports_fan_mode_high) {
this->supports_fan_mode_high_ = supports_fan_mode_high;
}
void ClimateTraits::set_supports_fan_mode_middle(bool supports_fan_mode_middle) {
this->supports_fan_mode_middle_ = supports_fan_mode_middle;
}
void ClimateTraits::set_supports_fan_mode_focus(bool supports_fan_mode_focus) {
this->supports_fan_mode_focus_ = supports_fan_mode_focus;
}
void ClimateTraits::set_supports_fan_mode_diffuse(bool supports_fan_mode_diffuse) {
this->supports_fan_mode_diffuse_ = supports_fan_mode_diffuse;
}
bool ClimateTraits::supports_fan_mode(ClimateFanMode fan_mode) const {
switch (fan_mode) {
case climate::CLIMATE_FAN_ON:
return this->supports_fan_mode_on_;
case climate::CLIMATE_FAN_OFF:
return this->supports_fan_mode_off_;
case climate::CLIMATE_FAN_AUTO:
return this->supports_fan_mode_auto_;
case climate::CLIMATE_FAN_LOW:
return this->supports_fan_mode_low_;
case climate::CLIMATE_FAN_MEDIUM:
return this->supports_fan_mode_medium_;
case climate::CLIMATE_FAN_HIGH:
return this->supports_fan_mode_high_;
case climate::CLIMATE_FAN_MIDDLE:
return this->supports_fan_mode_middle_;
case climate::CLIMATE_FAN_FOCUS:
return this->supports_fan_mode_focus_;
case climate::CLIMATE_FAN_DIFFUSE:
return this->supports_fan_mode_diffuse_;
default:
return false;
}
}
bool ClimateTraits::get_supports_fan_modes() const {
return this->supports_fan_mode_on_ || this->supports_fan_mode_off_ || this->supports_fan_mode_auto_ ||
this->supports_fan_mode_low_ || this->supports_fan_mode_medium_ || this->supports_fan_mode_high_ ||
this->supports_fan_mode_middle_ || this->supports_fan_mode_focus_ || this->supports_fan_mode_diffuse_;
}
void ClimateTraits::set_supports_swing_mode_off(bool supports_swing_mode_off) {
this->supports_swing_mode_off_ = supports_swing_mode_off;
}
void ClimateTraits::set_supports_swing_mode_both(bool supports_swing_mode_both) {
this->supports_swing_mode_both_ = supports_swing_mode_both;
}
void ClimateTraits::set_supports_swing_mode_vertical(bool supports_swing_mode_vertical) {
this->supports_swing_mode_vertical_ = supports_swing_mode_vertical;
}
void ClimateTraits::set_supports_swing_mode_horizontal(bool supports_swing_mode_horizontal) {
this->supports_swing_mode_horizontal_ = supports_swing_mode_horizontal;
}
bool ClimateTraits::supports_swing_mode(ClimateSwingMode swing_mode) const {
switch (swing_mode) {
case climate::CLIMATE_SWING_OFF:
return this->supports_swing_mode_off_;
case climate::CLIMATE_SWING_BOTH:
return this->supports_swing_mode_both_;
case climate::CLIMATE_SWING_VERTICAL:
return this->supports_swing_mode_vertical_;
case climate::CLIMATE_SWING_HORIZONTAL:
return this->supports_swing_mode_horizontal_;
default:
return false;
}
}
bool ClimateTraits::get_supports_swing_modes() const {
return this->supports_swing_mode_off_ || this->supports_swing_mode_both_ || supports_swing_mode_vertical_ ||
supports_swing_mode_horizontal_;
}
} // namespace climate
} // namespace esphome

View File

@@ -1,6 +1,8 @@
#pragma once
#include "esphome/core/helpers.h"
#include "climate_mode.h"
#include <set>
namespace esphome {
namespace climate {
@@ -23,8 +25,6 @@ namespace climate {
* - heat mode (increases current temperature)
* - dry mode (removes humidity from air)
* - fan mode (only turns on fan)
* - supports away - away mode means that the climate device supports two different
* target temperature settings: one target temp setting for "away" mode and one for non-away mode.
* - supports action - if the climate device supports reporting the active
* current action of the device with the action property.
* - supports fan modes - optionally, if it has a fan which can be configured in different ways:
@@ -40,69 +40,147 @@ namespace climate {
*/
class ClimateTraits {
public:
bool get_supports_current_temperature() const;
void set_supports_current_temperature(bool supports_current_temperature);
bool get_supports_two_point_target_temperature() const;
void set_supports_two_point_target_temperature(bool supports_two_point_target_temperature);
void set_supports_auto_mode(bool supports_auto_mode);
void set_supports_cool_mode(bool supports_cool_mode);
void set_supports_heat_mode(bool supports_heat_mode);
void set_supports_fan_only_mode(bool supports_fan_only_mode);
void set_supports_dry_mode(bool supports_dry_mode);
void set_supports_away(bool supports_away);
bool get_supports_away() const;
void set_supports_action(bool supports_action);
bool get_supports_action() const;
bool supports_mode(ClimateMode mode) const;
void set_supports_fan_mode_on(bool supports_fan_mode_on);
void set_supports_fan_mode_off(bool supports_fan_mode_off);
void set_supports_fan_mode_auto(bool supports_fan_mode_auto);
void set_supports_fan_mode_low(bool supports_fan_mode_low);
void set_supports_fan_mode_medium(bool supports_fan_mode_medium);
void set_supports_fan_mode_high(bool supports_fan_mode_high);
void set_supports_fan_mode_middle(bool supports_fan_mode_middle);
void set_supports_fan_mode_focus(bool supports_fan_mode_focus);
void set_supports_fan_mode_diffuse(bool supports_fan_mode_diffuse);
bool supports_fan_mode(ClimateFanMode fan_mode) const;
bool get_supports_fan_modes() const;
void set_supports_swing_mode_off(bool supports_swing_mode_off);
void set_supports_swing_mode_both(bool supports_swing_mode_both);
void set_supports_swing_mode_vertical(bool supports_swing_mode_vertical);
void set_supports_swing_mode_horizontal(bool supports_swing_mode_horizontal);
bool supports_swing_mode(ClimateSwingMode swing_mode) const;
bool get_supports_swing_modes() const;
bool get_supports_current_temperature() const { return supports_current_temperature_; }
void set_supports_current_temperature(bool supports_current_temperature) {
supports_current_temperature_ = supports_current_temperature;
}
bool get_supports_two_point_target_temperature() const { return supports_two_point_target_temperature_; }
void set_supports_two_point_target_temperature(bool supports_two_point_target_temperature) {
supports_two_point_target_temperature_ = supports_two_point_target_temperature;
}
void set_supported_modes(std::set<ClimateMode> modes) { supported_modes_ = std::move(modes); }
void add_supported_mode(ClimateMode mode) { supported_modes_.insert(mode); }
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
void set_supports_auto_mode(bool supports_auto_mode) { set_mode_support_(CLIMATE_MODE_AUTO, supports_auto_mode); }
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
void set_supports_cool_mode(bool supports_cool_mode) { set_mode_support_(CLIMATE_MODE_COOL, supports_cool_mode); }
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
void set_supports_heat_mode(bool supports_heat_mode) { set_mode_support_(CLIMATE_MODE_HEAT, supports_heat_mode); }
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
void set_supports_heat_cool_mode(bool supported) { set_mode_support_(CLIMATE_MODE_HEAT_COOL, supported); }
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
void set_supports_fan_only_mode(bool supports_fan_only_mode) {
set_mode_support_(CLIMATE_MODE_FAN_ONLY, supports_fan_only_mode);
}
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
void set_supports_dry_mode(bool supports_dry_mode) { set_mode_support_(CLIMATE_MODE_DRY, supports_dry_mode); }
bool supports_mode(ClimateMode mode) const { return supported_modes_.count(mode); }
const std::set<ClimateMode> get_supported_modes() const { return supported_modes_; }
float get_visual_min_temperature() const;
void set_visual_min_temperature(float visual_min_temperature);
float get_visual_max_temperature() const;
void set_visual_max_temperature(float visual_max_temperature);
float get_visual_temperature_step() const;
void set_supports_action(bool supports_action) { supports_action_ = supports_action; }
bool get_supports_action() const { return supports_action_; }
void set_supported_fan_modes(std::set<ClimateFanMode> modes) { supported_fan_modes_ = std::move(modes); }
void add_supported_fan_mode(ClimateFanMode mode) { supported_fan_modes_.insert(mode); }
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
void set_supports_fan_mode_on(bool supported) { set_fan_mode_support_(CLIMATE_FAN_ON, supported); }
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
void set_supports_fan_mode_off(bool supported) { set_fan_mode_support_(CLIMATE_FAN_OFF, supported); }
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
void set_supports_fan_mode_auto(bool supported) { set_fan_mode_support_(CLIMATE_FAN_AUTO, supported); }
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
void set_supports_fan_mode_low(bool supported) { set_fan_mode_support_(CLIMATE_FAN_LOW, supported); }
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
void set_supports_fan_mode_medium(bool supported) { set_fan_mode_support_(CLIMATE_FAN_MEDIUM, supported); }
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
void set_supports_fan_mode_high(bool supported) { set_fan_mode_support_(CLIMATE_FAN_HIGH, supported); }
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
void set_supports_fan_mode_middle(bool supported) { set_fan_mode_support_(CLIMATE_FAN_MIDDLE, supported); }
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
void set_supports_fan_mode_focus(bool supported) { set_fan_mode_support_(CLIMATE_FAN_FOCUS, supported); }
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
void set_supports_fan_mode_diffuse(bool supported) { set_fan_mode_support_(CLIMATE_FAN_DIFFUSE, supported); }
bool supports_fan_mode(ClimateFanMode fan_mode) const { return supported_fan_modes_.count(fan_mode); }
bool get_supports_fan_modes() const { return !supported_fan_modes_.empty() || !supported_custom_fan_modes_.empty(); }
const std::set<ClimateFanMode> get_supported_fan_modes() const { return supported_fan_modes_; }
void set_supported_custom_fan_modes(std::set<std::string> supported_custom_fan_modes) {
supported_custom_fan_modes_ = std::move(supported_custom_fan_modes);
}
const std::set<std::string> &get_supported_custom_fan_modes() const { return supported_custom_fan_modes_; }
bool supports_custom_fan_mode(const std::string &custom_fan_mode) const {
return supported_custom_fan_modes_.count(custom_fan_mode);
}
void set_supported_presets(std::set<ClimatePreset> presets) { supported_presets_ = std::move(presets); }
void add_supported_preset(ClimatePreset preset) { supported_presets_.insert(preset); }
bool supports_preset(ClimatePreset preset) const { return supported_presets_.count(preset); }
bool get_supports_presets() const { return !supported_presets_.empty(); }
const std::set<climate::ClimatePreset> &get_supported_presets() const { return supported_presets_; }
void set_supported_custom_presets(std::set<std::string> supported_custom_presets) {
supported_custom_presets_ = std::move(supported_custom_presets);
}
const std::set<std::string> &get_supported_custom_presets() const { return supported_custom_presets_; }
bool supports_custom_preset(const std::string &custom_preset) const {
return supported_custom_presets_.count(custom_preset);
}
ESPDEPRECATED("This method is deprecated, use set_supported_presets() instead", "v1.20")
void set_supports_away(bool supports) {
if (supports) {
supported_presets_.insert(CLIMATE_PRESET_AWAY);
supported_presets_.insert(CLIMATE_PRESET_HOME);
}
}
ESPDEPRECATED("This method is deprecated, use supports_preset() instead", "v1.20")
bool get_supports_away() const { return supports_preset(CLIMATE_PRESET_AWAY); }
void set_supported_swing_modes(std::set<ClimateSwingMode> modes) { supported_swing_modes_ = std::move(modes); }
void add_supported_swing_mode(ClimateSwingMode mode) { supported_swing_modes_.insert(mode); }
ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead", "v1.20")
void set_supports_swing_mode_off(bool supported) { set_swing_mode_support_(CLIMATE_SWING_OFF, supported); }
ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead", "v1.20")
void set_supports_swing_mode_both(bool supported) { set_swing_mode_support_(CLIMATE_SWING_BOTH, supported); }
ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead", "v1.20")
void set_supports_swing_mode_vertical(bool supported) { set_swing_mode_support_(CLIMATE_SWING_VERTICAL, supported); }
ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead", "v1.20")
void set_supports_swing_mode_horizontal(bool supported) {
set_swing_mode_support_(CLIMATE_SWING_HORIZONTAL, supported);
}
bool supports_swing_mode(ClimateSwingMode swing_mode) const { return supported_swing_modes_.count(swing_mode); }
bool get_supports_swing_modes() const { return !supported_swing_modes_.empty(); }
const std::set<ClimateSwingMode> get_supported_swing_modes() { return supported_swing_modes_; }
float get_visual_min_temperature() const { return visual_min_temperature_; }
void set_visual_min_temperature(float visual_min_temperature) { visual_min_temperature_ = visual_min_temperature; }
float get_visual_max_temperature() const { return visual_max_temperature_; }
void set_visual_max_temperature(float visual_max_temperature) { visual_max_temperature_ = visual_max_temperature; }
float get_visual_temperature_step() const { return visual_temperature_step_; }
int8_t get_temperature_accuracy_decimals() const;
void set_visual_temperature_step(float temperature_step);
void set_visual_temperature_step(float temperature_step) { visual_temperature_step_ = temperature_step; }
protected:
void set_mode_support_(climate::ClimateMode mode, bool supported) {
if (supported) {
supported_modes_.insert(mode);
} else {
supported_modes_.erase(mode);
}
}
void set_fan_mode_support_(climate::ClimateFanMode mode, bool supported) {
if (supported) {
supported_fan_modes_.insert(mode);
} else {
supported_fan_modes_.erase(mode);
}
}
void set_swing_mode_support_(climate::ClimateSwingMode mode, bool supported) {
if (supported) {
supported_swing_modes_.insert(mode);
} else {
supported_swing_modes_.erase(mode);
}
}
bool supports_current_temperature_{false};
bool supports_two_point_target_temperature_{false};
bool supports_auto_mode_{false};
bool supports_cool_mode_{false};
bool supports_heat_mode_{false};
bool supports_fan_only_mode_{false};
bool supports_dry_mode_{false};
bool supports_away_{false};
std::set<climate::ClimateMode> supported_modes_ = {climate::CLIMATE_MODE_OFF};
bool supports_action_{false};
bool supports_fan_mode_on_{false};
bool supports_fan_mode_off_{false};
bool supports_fan_mode_auto_{false};
bool supports_fan_mode_low_{false};
bool supports_fan_mode_medium_{false};
bool supports_fan_mode_high_{false};
bool supports_fan_mode_middle_{false};
bool supports_fan_mode_focus_{false};
bool supports_fan_mode_diffuse_{false};
bool supports_swing_mode_off_{false};
bool supports_swing_mode_both_{false};
bool supports_swing_mode_vertical_{false};
bool supports_swing_mode_horizontal_{false};
std::set<climate::ClimateFanMode> supported_fan_modes_;
std::set<climate::ClimateSwingMode> supported_swing_modes_;
std::set<climate::ClimatePreset> supported_presets_;
std::set<std::string> supported_custom_fan_modes_;
std::set<std::string> supported_custom_presets_;
float visual_min_temperature_{10};
float visual_max_temperature_{30};

View File

@@ -9,7 +9,6 @@ from esphome.components import (
)
from esphome.components.remote_base import CONF_RECEIVER_ID, CONF_TRANSMITTER_ID
from esphome.const import CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT, CONF_SENSOR
from esphome.core import coroutine
AUTO_LOAD = ["sensor", "remote_base"]
CODEOWNERS = ["@glmnet"]
@@ -39,19 +38,18 @@ CLIMATE_IR_WITH_RECEIVER_SCHEMA = CLIMATE_IR_SCHEMA.extend(
)
@coroutine
def register_climate_ir(var, config):
yield cg.register_component(var, config)
yield climate.register_climate(var, config)
async def register_climate_ir(var, config):
await cg.register_component(var, config)
await climate.register_climate(var, config)
cg.add(var.set_supports_cool(config[CONF_SUPPORTS_COOL]))
cg.add(var.set_supports_heat(config[CONF_SUPPORTS_HEAT]))
if CONF_SENSOR in config:
sens = yield cg.get_variable(config[CONF_SENSOR])
sens = await cg.get_variable(config[CONF_SENSOR])
cg.add(var.set_sensor(sens))
if CONF_RECEIVER_ID in config:
receiver = yield cg.get_variable(config[CONF_RECEIVER_ID])
receiver = await cg.get_variable(config[CONF_RECEIVER_ID])
cg.add(receiver.register_listener(var))
transmitter = yield cg.get_variable(config[CONF_TRANSMITTER_ID])
transmitter = await cg.get_variable(config[CONF_TRANSMITTER_ID])
cg.add(var.set_transmitter(transmitter))

View File

@@ -4,68 +4,27 @@
namespace esphome {
namespace climate_ir {
static const char *TAG = "climate_ir";
static const char *const TAG = "climate_ir";
climate::ClimateTraits ClimateIR::traits() {
auto traits = climate::ClimateTraits();
traits.set_supports_current_temperature(this->sensor_ != nullptr);
traits.set_supports_auto_mode(true);
traits.set_supports_cool_mode(this->supports_cool_);
traits.set_supports_heat_mode(this->supports_heat_);
traits.set_supports_dry_mode(this->supports_dry_);
traits.set_supports_fan_only_mode(this->supports_fan_only_);
traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL});
if (supports_cool_)
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
if (supports_heat_)
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
if (supports_dry_)
traits.add_supported_mode(climate::CLIMATE_MODE_DRY);
if (supports_fan_only_)
traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY);
traits.set_supports_two_point_target_temperature(false);
traits.set_supports_away(false);
traits.set_visual_min_temperature(this->minimum_temperature_);
traits.set_visual_max_temperature(this->maximum_temperature_);
traits.set_visual_temperature_step(this->temperature_step_);
for (auto fan_mode : this->fan_modes_) {
switch (fan_mode) {
case climate::CLIMATE_FAN_AUTO:
traits.set_supports_fan_mode_auto(true);
break;
case climate::CLIMATE_FAN_DIFFUSE:
traits.set_supports_fan_mode_diffuse(true);
break;
case climate::CLIMATE_FAN_FOCUS:
traits.set_supports_fan_mode_focus(true);
break;
case climate::CLIMATE_FAN_HIGH:
traits.set_supports_fan_mode_high(true);
break;
case climate::CLIMATE_FAN_LOW:
traits.set_supports_fan_mode_low(true);
break;
case climate::CLIMATE_FAN_MEDIUM:
traits.set_supports_fan_mode_medium(true);
break;
case climate::CLIMATE_FAN_MIDDLE:
traits.set_supports_fan_mode_middle(true);
break;
case climate::CLIMATE_FAN_OFF:
traits.set_supports_fan_mode_off(true);
break;
case climate::CLIMATE_FAN_ON:
traits.set_supports_fan_mode_on(true);
break;
}
}
for (auto swing_mode : this->swing_modes_) {
switch (swing_mode) {
case climate::CLIMATE_SWING_OFF:
traits.set_supports_swing_mode_off(true);
break;
case climate::CLIMATE_SWING_BOTH:
traits.set_supports_swing_mode_both(true);
break;
case climate::CLIMATE_SWING_VERTICAL:
traits.set_supports_swing_mode_vertical(true);
break;
case climate::CLIMATE_SWING_HORIZONTAL:
traits.set_supports_swing_mode_horizontal(true);
break;
}
}
traits.set_supported_fan_modes(fan_modes_);
traits.set_supported_swing_modes(swing_modes_);
return traits;
}

View File

@@ -1,5 +1,7 @@
#pragma once
#include <utility>
#include "esphome/components/climate/climate.h"
#include "esphome/components/remote_base/remote_base.h"
#include "esphome/components/remote_transmitter/remote_transmitter.h"
@@ -19,16 +21,15 @@ namespace climate_ir {
class ClimateIR : public climate::Climate, public Component, public remote_base::RemoteReceiverListener {
public:
ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f,
bool supports_dry = false, bool supports_fan_only = false,
std::vector<climate::ClimateFanMode> fan_modes = {},
std::vector<climate::ClimateSwingMode> swing_modes = {}) {
bool supports_dry = false, bool supports_fan_only = false, std::set<climate::ClimateFanMode> fan_modes = {},
std::set<climate::ClimateSwingMode> swing_modes = {}) {
this->minimum_temperature_ = minimum_temperature;
this->maximum_temperature_ = maximum_temperature;
this->temperature_step_ = temperature_step;
this->supports_dry_ = supports_dry;
this->supports_fan_only_ = supports_fan_only;
this->fan_modes_ = fan_modes;
this->swing_modes_ = swing_modes;
this->fan_modes_ = std::move(fan_modes);
this->swing_modes_ = std::move(swing_modes);
}
void setup() override;
@@ -58,8 +59,8 @@ class ClimateIR : public climate::Climate, public Component, public remote_base:
bool supports_heat_{true};
bool supports_dry_{false};
bool supports_fan_only_{false};
std::vector<climate::ClimateFanMode> fan_modes_ = {};
std::vector<climate::ClimateSwingMode> swing_modes_ = {};
std::set<climate::ClimateFanMode> fan_modes_ = {};
std::set<climate::ClimateSwingMode> swing_modes_ = {};
remote_transmitter::RemoteTransmitterComponent *transmitter_;
sensor::Sensor *sensor_{nullptr};

View File

@@ -36,9 +36,9 @@ CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield climate_ir.register_climate_ir(var, config)
await climate_ir.register_climate_ir(var, config)
cg.add(var.set_header_high(config[CONF_HEADER_HIGH]))
cg.add(var.set_header_low(config[CONF_HEADER_LOW]))

View File

@@ -4,7 +4,7 @@
namespace esphome {
namespace climate_ir_lg {
static const char *TAG = "climate.climate_ir_lg";
static const char *const TAG = "climate.climate_ir_lg";
const uint32_t COMMAND_ON = 0x00000;
const uint32_t COMMAND_ON_AI = 0x03000;
@@ -39,7 +39,7 @@ void LgIrClimate::transmit_state() {
send_swing_cmd_ = false;
remote_state |= COMMAND_SWING;
} else {
if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode == climate::CLIMATE_MODE_AUTO) {
if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
remote_state |= COMMAND_ON_AI;
} else if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode != climate::CLIMATE_MODE_OFF) {
remote_state |= COMMAND_ON;
@@ -52,7 +52,7 @@ void LgIrClimate::transmit_state() {
case climate::CLIMATE_MODE_HEAT:
remote_state |= COMMAND_HEAT;
break;
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
remote_state |= COMMAND_AUTO;
break;
case climate::CLIMATE_MODE_DRY:
@@ -72,7 +72,7 @@ void LgIrClimate::transmit_state() {
remote_state |= FAN_AUTO;
} else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_DRY ||
this->mode == climate::CLIMATE_MODE_HEAT) {
switch (this->fan_mode) {
switch (this->fan_mode.value()) {
case climate::CLIMATE_FAN_HIGH:
remote_state |= FAN_MAX;
break;
@@ -89,12 +89,12 @@ void LgIrClimate::transmit_state() {
}
}
if (this->mode == climate::CLIMATE_MODE_AUTO) {
if (this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
this->fan_mode = climate::CLIMATE_FAN_AUTO;
// remote_state |= FAN_MODE_AUTO_DRY;
}
if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT) {
auto temp = (uint8_t) roundf(clamp(this->target_temperature, TEMP_MIN, TEMP_MAX));
auto temp = (uint8_t) roundf(clamp<float>(this->target_temperature, TEMP_MIN, TEMP_MAX));
remote_state |= ((temp - 15) << TEMP_SHIFT);
}
}
@@ -128,7 +128,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
if ((remote_state & COMMAND_MASK) == COMMAND_ON) {
this->mode = climate::CLIMATE_MODE_COOL;
} else if ((remote_state & COMMAND_MASK) == COMMAND_ON_AI) {
this->mode = climate::CLIMATE_MODE_AUTO;
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
}
if ((remote_state & COMMAND_MASK) == COMMAND_OFF) {
@@ -138,7 +138,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
this->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF;
} else {
if ((remote_state & COMMAND_MASK) == COMMAND_AUTO)
this->mode = climate::CLIMATE_MODE_AUTO;
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
else if ((remote_state & COMMAND_MASK) == COMMAND_DRY_FAN)
this->mode = climate::CLIMATE_MODE_DRY;
else if ((remote_state & COMMAND_MASK) == COMMAND_HEAT) {
@@ -152,7 +152,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
this->target_temperature = ((remote_state & TEMP_MASK) >> TEMP_SHIFT) + 15;
// Fan Speed
if (this->mode == climate::CLIMATE_MODE_AUTO) {
if (this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
this->fan_mode = climate::CLIMATE_FAN_AUTO;
} else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT ||
this->mode == climate::CLIMATE_MODE_DRY) {

View File

@@ -26,7 +26,7 @@ CONFIG_SCHEMA = cv.Schema(
).extend(cv.COMPONENT_SCHEMA)
def to_code(config):
async def to_code(config):
r = 0
if CONF_RED in config:
r = int(config[CONF_RED] * 255)

View File

@@ -0,0 +1,38 @@
#pragma once
#include "esphome/components/light/light_output.h"
#include "esphome/components/output/float_output.h"
#include "esphome/core/component.h"
namespace esphome {
namespace color_temperature {
class CTLightOutput : public light::LightOutput {
public:
void set_color_temperature(output::FloatOutput *color_temperature) { color_temperature_ = color_temperature; }
void set_brightness(output::FloatOutput *brightness) { brightness_ = brightness; }
void set_cold_white_temperature(float cold_white_temperature) { cold_white_temperature_ = cold_white_temperature; }
void set_warm_white_temperature(float warm_white_temperature) { warm_white_temperature_ = warm_white_temperature; }
light::LightTraits get_traits() override {
auto traits = light::LightTraits();
traits.set_supported_color_modes({light::ColorMode::COLOR_TEMPERATURE});
traits.set_min_mireds(this->cold_white_temperature_);
traits.set_max_mireds(this->warm_white_temperature_);
return traits;
}
void write_state(light::LightState *state) override {
float color_temperature, brightness;
state->current_values_as_ct(&color_temperature, &brightness);
this->color_temperature_->set_level(color_temperature);
this->brightness_->set_level(brightness);
}
protected:
output::FloatOutput *color_temperature_;
output::FloatOutput *brightness_;
float cold_white_temperature_;
float warm_white_temperature_;
};
} // namespace color_temperature
} // namespace esphome

View File

@@ -0,0 +1,42 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import light, output
from esphome.const import (
CONF_BRIGHTNESS,
CONF_COLOR_TEMPERATURE,
CONF_OUTPUT_ID,
CONF_COLD_WHITE_COLOR_TEMPERATURE,
CONF_WARM_WHITE_COLOR_TEMPERATURE,
)
CODEOWNERS = ["@jesserockz"]
color_temperature_ns = cg.esphome_ns.namespace("color_temperature")
CTLightOutput = color_temperature_ns.class_("CTLightOutput", light.LightOutput)
CONFIG_SCHEMA = cv.All(
light.RGB_LIGHT_SCHEMA.extend(
{
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(CTLightOutput),
cv.Required(CONF_COLOR_TEMPERATURE): cv.use_id(output.FloatOutput),
cv.Required(CONF_BRIGHTNESS): cv.use_id(output.FloatOutput),
cv.Required(CONF_COLD_WHITE_COLOR_TEMPERATURE): cv.color_temperature,
cv.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): cv.color_temperature,
}
),
light.validate_color_temperature_channels,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
await light.register_light(var, config)
color_temperature = await cg.get_variable(config[CONF_COLOR_TEMPERATURE])
cg.add(var.set_color_temperature(color_temperature))
brightness = await cg.get_variable(config[CONF_BRIGHTNESS])
cg.add(var.set_brightness(brightness))
cg.add(var.set_cold_white_temperature(config[CONF_COLD_WHITE_COLOR_TEMPERATURE]))
cg.add(var.set_warm_white_temperature(config[CONF_WARM_WHITE_COLOR_TEMPERATURE]))

View File

@@ -16,6 +16,6 @@ CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield climate_ir.register_climate_ir(var, config)
await climate_ir.register_climate_ir(var, config)

View File

@@ -4,7 +4,7 @@
namespace esphome {
namespace coolix {
static const char *TAG = "coolix.climate";
static const char *const TAG = "coolix.climate";
const uint32_t COOLIX_OFF = 0xB27BE0;
const uint32_t COOLIX_SWING = 0xB26BE0;
@@ -70,7 +70,7 @@ void CoolixClimate::transmit_state() {
case climate::CLIMATE_MODE_HEAT:
remote_state |= COOLIX_HEAT;
break;
case climate::CLIMATE_MODE_AUTO:
case climate::CLIMATE_MODE_HEAT_COOL:
remote_state |= COOLIX_AUTO;
break;
case climate::CLIMATE_MODE_FAN_ONLY:
@@ -84,16 +84,16 @@ void CoolixClimate::transmit_state() {
}
if (this->mode != climate::CLIMATE_MODE_OFF) {
if (this->mode != climate::CLIMATE_MODE_FAN_ONLY) {
auto temp = (uint8_t) roundf(clamp(this->target_temperature, COOLIX_TEMP_MIN, COOLIX_TEMP_MAX));
auto temp = (uint8_t) roundf(clamp<float>(this->target_temperature, COOLIX_TEMP_MIN, COOLIX_TEMP_MAX));
remote_state |= COOLIX_TEMP_MAP[temp - COOLIX_TEMP_MIN];
} else {
remote_state |= COOLIX_FAN_TEMP_CODE;
}
if (this->mode == climate::CLIMATE_MODE_AUTO || this->mode == climate::CLIMATE_MODE_DRY) {
if (this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_DRY) {
this->fan_mode = climate::CLIMATE_FAN_AUTO;
remote_state |= COOLIX_FAN_MODE_AUTO_DRY;
} else {
switch (this->fan_mode) {
switch (this->fan_mode.value()) {
case climate::CLIMATE_FAN_HIGH:
remote_state |= COOLIX_FAN_MAX;
break;
@@ -197,7 +197,7 @@ bool CoolixClimate::on_receive(remote_base::RemoteReceiveData data) {
if ((remote_state & COOLIX_MODE_MASK) == COOLIX_HEAT)
this->mode = climate::CLIMATE_MODE_HEAT;
else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_AUTO)
this->mode = climate::CLIMATE_MODE_AUTO;
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_DRY_FAN) {
if ((remote_state & COOLIX_FAN_MASK) == COOLIX_FAN_MODE_AUTO_DRY)
this->mode = climate::CLIMATE_MODE_DRY;
@@ -207,7 +207,7 @@ bool CoolixClimate::on_receive(remote_base::RemoteReceiveData data) {
this->mode = climate::CLIMATE_MODE_COOL;
// Fan Speed
if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || this->mode == climate::CLIMATE_MODE_AUTO ||
if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || this->mode == climate::CLIMATE_MODE_HEAT_COOL ||
this->mode == climate::CLIMATE_MODE_DRY)
this->fan_mode = climate::CLIMATE_FAN_AUTO;
else if ((remote_state & COOLIX_FAN_MIN) == COOLIX_FAN_MIN)

View File

@@ -4,6 +4,7 @@ from esphome import automation
from esphome.automation import maybe_simple_id, Condition
from esphome.components import mqtt
from esphome.const import (
CONF_DISABLED_BY_DEFAULT,
CONF_ID,
CONF_INTERNAL,
CONF_DEVICE_CLASS,
@@ -14,7 +15,7 @@ from esphome.const import (
CONF_MQTT_ID,
CONF_NAME,
)
from esphome.core import CORE, coroutine, coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
IS_PLATFORM_COMPONENT = True
@@ -63,7 +64,7 @@ CoverPublishAction = cover_ns.class_("CoverPublishAction", automation.Action)
CoverIsOpenCondition = cover_ns.class_("CoverIsOpenCondition", Condition)
CoverIsClosedCondition = cover_ns.class_("CoverIsClosedCondition", Condition)
COVER_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend(
COVER_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.GenerateID(): cv.declare_id(Cover),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent),
@@ -73,9 +74,9 @@ COVER_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend(
)
@coroutine
def setup_cover_core_(var, config):
async def setup_cover_core_(var, config):
cg.add(var.set_name(config[CONF_NAME]))
cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT]))
if CONF_INTERNAL in config:
cg.add(var.set_internal(config[CONF_INTERNAL]))
if CONF_DEVICE_CLASS in config:
@@ -83,15 +84,14 @@ def setup_cover_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_cover(var, config):
async def register_cover(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_cover(var))
yield setup_cover_core_(var, config)
await setup_cover_core_(var, config)
COVER_ACTION_SCHEMA = maybe_simple_id(
@@ -102,21 +102,21 @@ COVER_ACTION_SCHEMA = maybe_simple_id(
@automation.register_action("cover.open", OpenAction, COVER_ACTION_SCHEMA)
def cover_open_to_code(config, action_id, template_arg, args):
paren = yield cg.get_variable(config[CONF_ID])
yield cg.new_Pvariable(action_id, template_arg, paren)
async def cover_open_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action("cover.close", CloseAction, COVER_ACTION_SCHEMA)
def cover_close_to_code(config, action_id, template_arg, args):
paren = yield cg.get_variable(config[CONF_ID])
yield cg.new_Pvariable(action_id, template_arg, paren)
async def cover_close_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action("cover.stop", StopAction, COVER_ACTION_SCHEMA)
def cover_stop_to_code(config, action_id, template_arg, args):
paren = yield cg.get_variable(config[CONF_ID])
yield cg.new_Pvariable(action_id, template_arg, paren)
async def cover_stop_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
COVER_CONTROL_ACTION_SCHEMA = cv.Schema(
@@ -131,25 +131,25 @@ COVER_CONTROL_ACTION_SCHEMA = cv.Schema(
@automation.register_action("cover.control", ControlAction, COVER_CONTROL_ACTION_SCHEMA)
def cover_control_to_code(config, action_id, template_arg, args):
paren = yield cg.get_variable(config[CONF_ID])
async def cover_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_STOP in config:
template_ = yield cg.templatable(config[CONF_STOP], args, bool)
template_ = await cg.templatable(config[CONF_STOP], args, bool)
cg.add(var.set_stop(template_))
if CONF_STATE in config:
template_ = yield cg.templatable(config[CONF_STATE], args, float)
template_ = await cg.templatable(config[CONF_STATE], args, float)
cg.add(var.set_position(template_))
if CONF_POSITION in config:
template_ = yield cg.templatable(config[CONF_POSITION], args, float)
template_ = await cg.templatable(config[CONF_POSITION], args, float)
cg.add(var.set_position(template_))
if CONF_TILT in config:
template_ = yield cg.templatable(config[CONF_TILT], args, float)
template_ = await cg.templatable(config[CONF_TILT], args, float)
cg.add(var.set_tilt(template_))
yield var
return var
@coroutine_with_priority(100.0)
def to_code(config):
async def to_code(config):
cg.add_define("USE_COVER")
cg.add_global(cover_ns.using)

View File

@@ -4,7 +4,7 @@
namespace esphome {
namespace cover {
static const char *TAG = "cover";
static const char *const TAG = "cover";
const float COVER_OPEN = 1.0f;
const float COVER_CLOSED = 0.0f;

View File

@@ -12,14 +12,14 @@ const extern float COVER_OPEN;
const extern float COVER_CLOSED;
#define LOG_COVER(prefix, type, obj) \
if (obj != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, obj->get_name().c_str()); \
auto traits_ = obj->get_traits(); \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \
auto traits_ = (obj)->get_traits(); \
if (traits_.get_is_assumed_state()) { \
ESP_LOGCONFIG(TAG, "%s Assumed State: YES", prefix); \
} \
if (!obj->get_device_class().empty()) { \
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, obj->get_device_class().c_str()); \
if (!(obj)->get_device_class().empty()) { \
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
} \
}
@@ -110,15 +110,12 @@ class Cover : public Nameable {
/// The current operation of the cover (idle, opening, closing).
CoverOperation current_operation{COVER_OPERATION_IDLE};
union {
/** The position of the cover from 0.0 (fully closed) to 1.0 (fully open).
*
* For binary covers this is always equals to 0.0 or 1.0 (see also COVER_OPEN and
* COVER_CLOSED constants).
*/
float position;
ESPDEPRECATED("<cover>.state is deprecated, please use .position instead") float state;
};
/** The position of the cover from 0.0 (fully closed) to 1.0 (fully open).
*
* For binary covers this is always equals to 0.0 or 1.0 (see also COVER_OPEN and
* COVER_CLOSED constants).
*/
float position;
/// The current tilt value of the cover from 0.0 to 1.0.
float tilt{COVER_OPEN};

View File

View File

@@ -0,0 +1,342 @@
#include "cs5460a.h"
#include "esphome/core/log.h"
namespace esphome {
namespace cs5460a {
static const char *const TAG = "cs5460a";
void CS5460AComponent::write_register_(enum CS5460ARegister addr, uint32_t value) {
this->write_byte(CMD_WRITE | (addr << 1));
this->write_byte(value >> 16);
this->write_byte(value >> 8);
this->write_byte(value >> 0);
}
uint32_t CS5460AComponent::read_register_(uint8_t addr) {
uint32_t value;
this->write_byte(CMD_READ | (addr << 1));
value = (uint32_t) this->transfer_byte(CMD_SYNC0) << 16;
value |= (uint32_t) this->transfer_byte(CMD_SYNC0) << 8;
value |= this->transfer_byte(CMD_SYNC0) << 0;
return value;
}
bool CS5460AComponent::softreset_() {
uint32_t pc = ((uint8_t) phase_offset_ & 0x3f) | (phase_offset_ < 0 ? 0x40 : 0);
uint32_t config = (1 << 0) | /* K = 0b0001 */
(current_hpf_ ? 1 << 5 : 0) | /* IHPF */
(voltage_hpf_ ? 1 << 6 : 0) | /* VHPF */
(pga_gain_ << 16) | /* Gi */
(pc << 17); /* PC */
int cnt = 0;
/* Serial resynchronization */
this->write_byte(CMD_SYNC1);
this->write_byte(CMD_SYNC1);
this->write_byte(CMD_SYNC1);
this->write_byte(CMD_SYNC0);
/* Reset */
this->write_register_(REG_CONFIG, 1 << 7);
delay(10);
while (cnt++ < 50 && (this->read_register_(REG_CONFIG) & 0x81) != 0x000001)
;
if (cnt > 50)
return false;
this->write_register_(REG_CONFIG, config);
return true;
}
void CS5460AComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up CS5460A...");
float current_full_scale = (pga_gain_ == CS5460A_PGA_GAIN_10X) ? 0.25 : 0.10;
float voltage_full_scale = 0.25;
current_multiplier_ = current_full_scale / (fabsf(current_gain_) * 0x1000000);
voltage_multiplier_ = voltage_full_scale / (voltage_gain_ * 0x1000000);
/*
* Calculate power from the Energy register because the Power register
* stores instantaneous power which varies a lot in each AC cycle,
* while the Energy value is accumulated over the "computation cycle"
* which should be an integer number of AC cycles.
*/
power_multiplier_ =
(current_full_scale * voltage_full_scale * 4096) / (current_gain_ * voltage_gain_ * samples_ * 0x800000);
pulse_freq_ =
(current_full_scale * voltage_full_scale) / (fabsf(current_gain_) * voltage_gain_ * pulse_energy_wh_ * 3600);
hw_init_();
}
void CS5460AComponent::hw_init_() {
this->spi_setup();
this->enable();
if (!this->softreset_()) {
this->disable();
ESP_LOGE(TAG, "CS5460A reset failed!");
this->mark_failed();
return;
}
uint32_t status = this->read_register_(REG_STATUS);
ESP_LOGCONFIG(TAG, " Version: %x", (status >> 6) & 7);
this->write_register_(REG_CYCLE_COUNT, samples_);
this->write_register_(REG_PULSE_RATE, lroundf(pulse_freq_ * 32.0f));
/* Use one of the power saving features (assuming external oscillator), reset other CONTROL bits,
* sometimes softreset_() is not enough */
this->write_register_(REG_CONTROL, 0x000004);
this->restart_();
this->disable();
ESP_LOGCONFIG(TAG, " Init ok");
}
/* Doesn't reset the register values etc., just restarts the "computation cycle" */
void CS5460AComponent::restart_() {
int cnt;
this->enable();
/* Stop running conversion, wake up if needed */
this->write_byte(CMD_POWER_UP);
/* Start continuous conversion */
this->write_byte(CMD_START_CONT);
this->disable();
this->started_();
}
void CS5460AComponent::started_() {
/*
* Try to guess when the next batch of results is going to be ready and
* schedule next STATUS check some time before that moment. This assumes
* two things:
* * a new "computation cycle" started just now. If it started some
* time ago we may be a late next time, but hopefully less late in each
* iteration -- that's why we schedule the next check in some 0.8 of
* the time we actually expect the next reading ready.
* * MCLK rate is 4.096MHz and K == 1. If there's a CS5460A module in
* use with a different clock this will need to be parametrised.
*/
expect_data_ts_ = millis() + samples_ * 1024 / 4096;
schedule_next_check_();
}
void CS5460AComponent::schedule_next_check_() {
int32_t time_left = expect_data_ts_ - millis();
/* First try at 0.8 of the actual expected time (if it's in the future) */
if (time_left > 0)
time_left -= time_left / 5;
if (time_left > -500) {
/* But not sooner than in 30ms from now */
if (time_left < 30)
time_left = 30;
} else {
/*
* If the measurement is more than 0.5s overdue start worrying. The
* device may be stuck because of an overcurrent error or similar,
* from now on just retry every 1s. After 15s try a reset, if it
* fails we give up and mark the component "failed".
*/
if (time_left > -15000) {
time_left = 1000;
this->status_momentary_warning("warning", 1000);
} else {
ESP_LOGCONFIG(TAG, "Device officially stuck, resetting");
this->cancel_timeout("status-check");
this->hw_init_();
return;
}
}
this->set_timeout("status-check", time_left, [this]() {
if (!this->check_status_())
this->schedule_next_check_();
});
}
bool CS5460AComponent::check_status_() {
this->enable();
uint32_t status = this->read_register_(REG_STATUS);
if (!(status & 0xcbf83c)) {
this->disable();
return false;
}
uint32_t clear = 1 << 20;
/* TODO: Report if IC=0 but only once as it can't be cleared */
if (status & (1 << 2)) {
clear |= 1 << 2;
ESP_LOGE(TAG, "Low supply detected");
this->status_momentary_warning("warning", 500);
}
if (status & (1 << 3)) {
clear |= 1 << 3;
ESP_LOGE(TAG, "Modulator oscillation on current channel");
this->status_momentary_warning("warning", 500);
}
if (status & (1 << 4)) {
clear |= 1 << 4;
ESP_LOGE(TAG, "Modulator oscillation on voltage channel");
this->status_momentary_warning("warning", 500);
}
if (status & (1 << 5)) {
clear |= 1 << 5;
ESP_LOGE(TAG, "Watch-dog timeout");
this->status_momentary_warning("warning", 500);
}
if (status & (1 << 11)) {
clear |= 1 << 11;
ESP_LOGE(TAG, "EOUT Energy Accumulation Register out of range");
this->status_momentary_warning("warning", 500);
}
if (status & (1 << 12)) {
clear |= 1 << 12;
ESP_LOGE(TAG, "Energy out of range");
this->status_momentary_warning("warning", 500);
}
if (status & (1 << 13)) {
clear |= 1 << 13;
ESP_LOGE(TAG, "RMS voltage out of range");
this->status_momentary_warning("warning", 500);
}
if (status & (1 << 14)) {
clear |= 1 << 14;
ESP_LOGE(TAG, "RMS current out of range");
this->status_momentary_warning("warning", 500);
}
if (status & (1 << 15)) {
clear |= 1 << 15;
ESP_LOGE(TAG, "Power calculation out of range");
this->status_momentary_warning("warning", 500);
}
if (status & (1 << 16)) {
clear |= 1 << 16;
ESP_LOGE(TAG, "Voltage out of range");
this->status_momentary_warning("warning", 500);
}
if (status & (1 << 17)) {
clear |= 1 << 17;
ESP_LOGE(TAG, "Current out of range");
this->status_momentary_warning("warning", 500);
}
if (status & (1 << 19)) {
clear |= 1 << 19;
ESP_LOGE(TAG, "Divide overflowed");
}
if (status & (1 << 22)) {
bool dir = status & (1 << 21);
if (current_gain_ < 0)
dir = !dir;
ESP_LOGI(TAG, "Energy counter %s pulse", dir ? "negative" : "positive");
clear |= 1 << 22;
}
uint32_t raw_current = 0; /* Calm the validators */
uint32_t raw_voltage = 0;
uint32_t raw_energy = 0;
if (status & (1 << 23)) {
clear |= 1 << 23;
if (current_sensor_ != nullptr)
raw_current = this->read_register_(REG_IRMS);
if (voltage_sensor_ != nullptr)
raw_voltage = this->read_register_(REG_VRMS);
}
if (status & ((1 << 23) | (1 << 5))) {
/* Read to clear the WDT bit */
raw_energy = this->read_register_(REG_E);
}
this->write_register_(REG_STATUS, clear);
this->disable();
/*
* Schedule the next STATUS check assuming that DRDY was asserted very
* recently, then publish the new values. Do this last for reentrancy in
* case the publish triggers a restart() or for whatever reason needs to
* cancel the timeout set in schedule_next_check_(), or needs to use SPI.
* If the current or power values haven't changed one bit it may be that
* the chip somehow forgot to update the registers -- seen happening very
* rarely. In that case don't publish them because the user may have
* the input connected to a multiplexer and may have switched channels
* since the previous reading and we'd be publishing the stale value for
* the new channel. If the value *was* updated it's very unlikely that
* it wouldn't have changed, especially power/energy which are affected
* by the noise on both the current and value channels (in case of energy,
* accumulated over many conversion cycles.)
*/
if (status & (1 << 23)) {
this->started_();
if (current_sensor_ != nullptr && raw_current != prev_raw_current_) {
current_sensor_->publish_state(raw_current * current_multiplier_);
prev_raw_current_ = raw_current;
}
if (voltage_sensor_ != nullptr)
voltage_sensor_->publish_state(raw_voltage * voltage_multiplier_);
if (power_sensor_ != nullptr && raw_energy != prev_raw_energy_) {
int32_t raw = (int32_t)(raw_energy << 8) >> 8; /* Sign-extend */
power_sensor_->publish_state(raw * power_multiplier_);
prev_raw_energy_ = raw_energy;
}
return true;
}
return false;
}
void CS5460AComponent::dump_config() {
uint32_t state = this->get_component_state();
ESP_LOGCONFIG(TAG, "CS5460A:");
ESP_LOGCONFIG(TAG, " Init status: %s",
state == COMPONENT_STATE_LOOP ? "OK" : (state == COMPONENT_STATE_FAILED ? "failed" : "other"));
LOG_PIN(" CS Pin: ", cs_);
ESP_LOGCONFIG(TAG, " Samples / cycle: %u", samples_);
ESP_LOGCONFIG(TAG, " Phase offset: %i", phase_offset_);
ESP_LOGCONFIG(TAG, " PGA Gain: %s", pga_gain_ == CS5460A_PGA_GAIN_50X ? "50x" : "10x");
ESP_LOGCONFIG(TAG, " Current gain: %.5f", current_gain_);
ESP_LOGCONFIG(TAG, " Voltage gain: %.5f", voltage_gain_);
ESP_LOGCONFIG(TAG, " Current HPF: %s", current_hpf_ ? "enabled" : "disabled");
ESP_LOGCONFIG(TAG, " Voltage HPF: %s", voltage_hpf_ ? "enabled" : "disabled");
ESP_LOGCONFIG(TAG, " Pulse energy: %.2f Wh", pulse_energy_wh_);
LOG_SENSOR(" ", "Voltage", voltage_sensor_);
LOG_SENSOR(" ", "Current", current_sensor_);
LOG_SENSOR(" ", "Power", power_sensor_);
}
} // namespace cs5460a
} // namespace esphome

View File

@@ -0,0 +1,123 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/spi/spi.h"
namespace esphome {
namespace cs5460a {
enum CS5460ACommand {
CMD_SYNC0 = 0xfe,
CMD_SYNC1 = 0xff,
CMD_START_SINGLE = 0xe0,
CMD_START_CONT = 0xe8,
CMD_POWER_UP = 0xa0,
CMD_POWER_STANDBY = 0x88,
CMD_POWER_SLEEP = 0x90,
CMD_CALIBRATION = 0xc0,
CMD_READ = 0x00,
CMD_WRITE = 0x40,
};
enum CS5460ARegister {
REG_CONFIG = 0x00,
REG_IDCOFF = 0x01,
REG_IGN = 0x02,
REG_VDCOFF = 0x03,
REG_VGN = 0x04,
REG_CYCLE_COUNT = 0x05,
REG_PULSE_RATE = 0x06,
REG_I = 0x07,
REG_V = 0x08,
REG_P = 0x09,
REG_E = 0x0a,
REG_IRMS = 0x0b,
REG_VRMS = 0x0c,
REG_TBC = 0x0d,
REG_POFF = 0x0e,
REG_STATUS = 0x0f,
REG_IACOFF = 0x10,
REG_VACOFF = 0x11,
REG_MASK = 0x1a,
REG_CONTROL = 0x1c,
};
/** Enum listing the current channel aplifiergain settings for the CS5460A.
*/
enum CS5460APGAGain {
CS5460A_PGA_GAIN_10X = 0b0,
CS5460A_PGA_GAIN_50X = 0b1,
};
class CS5460AComponent : public Component,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_1MHZ> {
public:
void set_samples(uint32_t samples) { samples_ = samples; }
void set_phase_offset(int8_t phase_offset) { phase_offset_ = phase_offset; }
void set_pga_gain(CS5460APGAGain pga_gain) { pga_gain_ = pga_gain; }
void set_gains(float current_gain, float voltage_gain) {
current_gain_ = current_gain;
voltage_gain_ = voltage_gain;
}
void set_hpf_enable(bool current_hpf, bool voltage_hpf) {
current_hpf_ = current_hpf;
voltage_hpf_ = voltage_hpf;
}
void set_pulse_energy_wh(float pulse_energy_wh) { pulse_energy_wh_ = pulse_energy_wh; }
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
void restart() { restart_(); }
void setup() override;
void loop() override {}
float get_setup_priority() const override { return setup_priority::DATA; }
void dump_config() override;
protected:
uint32_t samples_;
int8_t phase_offset_;
CS5460APGAGain pga_gain_;
float current_gain_;
float voltage_gain_;
bool current_hpf_;
bool voltage_hpf_;
float pulse_energy_wh_;
sensor::Sensor *current_sensor_{nullptr};
sensor::Sensor *voltage_sensor_{nullptr};
sensor::Sensor *power_sensor_{nullptr};
void write_register_(enum CS5460ARegister addr, uint32_t value);
uint32_t read_register_(uint8_t addr);
bool softreset_();
void hw_init_();
void restart_();
void started_();
void schedule_next_check_();
bool check_status_();
float current_multiplier_;
float voltage_multiplier_;
float power_multiplier_;
float pulse_freq_;
uint32_t expect_data_ts_;
uint32_t prev_raw_current_{0};
uint32_t prev_raw_energy_{0};
};
template<typename... Ts> class CS5460ARestartAction : public Action<Ts...> {
public:
CS5460ARestartAction(CS5460AComponent *cs5460a) : cs5460a_(cs5460a) {}
void play(Ts... x) override { cs5460a_->restart(); }
protected:
CS5460AComponent *cs5460a_;
};
} // namespace cs5460a
} // namespace esphome

View File

@@ -0,0 +1,141 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import spi, sensor
from esphome.const import (
CONF_CURRENT,
CONF_ID,
CONF_POWER,
CONF_VOLTAGE,
UNIT_VOLT,
UNIT_AMPERE,
UNIT_WATT,
DEVICE_CLASS_POWER,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_VOLTAGE,
)
from esphome import automation
from esphome.automation import maybe_simple_id
CODEOWNERS = ["@balrog-kun"]
DEPENDENCIES = ["spi"]
cs5460a_ns = cg.esphome_ns.namespace("cs5460a")
CS5460APGAGain = cs5460a_ns.enum("CS5460APGAGain")
PGA_GAIN_OPTIONS = {
"10X": CS5460APGAGain.CS5460A_PGA_GAIN_10X,
"50X": CS5460APGAGain.CS5460A_PGA_GAIN_50X,
}
CS5460AComponent = cs5460a_ns.class_("CS5460AComponent", spi.SPIDevice, cg.Component)
CS5460ARestartAction = cs5460a_ns.class_("CS5460ARestartAction", automation.Action)
CONF_SAMPLES = "samples"
CONF_PHASE_OFFSET = "phase_offset"
CONF_PGA_GAIN = "pga_gain"
CONF_CURRENT_GAIN = "current_gain"
CONF_VOLTAGE_GAIN = "voltage_gain"
CONF_CURRENT_HPF = "current_hpf"
CONF_VOLTAGE_HPF = "voltage_hpf"
CONF_PULSE_ENERGY = "pulse_energy"
def validate_config(config):
current_gain = abs(config[CONF_CURRENT_GAIN]) * (
1.0 if config[CONF_PGA_GAIN] == "10X" else 5.0
)
voltage_gain = config[CONF_VOLTAGE_GAIN]
pulse_energy = config[CONF_PULSE_ENERGY]
if current_gain == 0.0 or voltage_gain == 0.0:
raise cv.Invalid("The gains can't be zero")
max_energy = (0.25 * 0.25 / 3600 / (2 ** -4)) / (voltage_gain * current_gain)
min_energy = (0.25 * 0.25 / 3600 / (2 ** 18)) / (voltage_gain * current_gain)
mech_min_energy = (0.25 * 0.25 / 3600 / 7.8) / (voltage_gain * current_gain)
if pulse_energy < min_energy or pulse_energy > max_energy:
raise cv.Invalid(
"For given current&voltage gains, the pulse energy must be between "
f"{min_energy} Wh and {max_energy} Wh and in mechanical counter mode "
f"between {mech_min_energy} Wh and {max_energy} Wh"
)
return config
validate_energy = cv.float_with_unit("energy", "(Wh|WH|wh)?", optional_unit=True)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(CS5460AComponent),
cv.Optional(CONF_SAMPLES, default=4000): cv.int_range(min=1, max=0xFFFFFF),
cv.Optional(CONF_PHASE_OFFSET, default=0): cv.int_range(min=-64, max=63),
cv.Optional(CONF_PGA_GAIN, default="10X"): cv.enum(
PGA_GAIN_OPTIONS, upper=True
),
cv.Optional(CONF_CURRENT_GAIN, default=0.001): cv.negative_one_to_one_float,
cv.Optional(CONF_VOLTAGE_GAIN, default=0.001): cv.zero_to_one_float,
cv.Optional(CONF_CURRENT_HPF, default=True): cv.boolean,
cv.Optional(CONF_VOLTAGE_HPF, default=True): cv.boolean,
cv.Optional(CONF_PULSE_ENERGY, default=10.0): validate_energy,
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_VOLTAGE,
),
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=1,
device_class=DEVICE_CLASS_CURRENT,
),
cv.Optional(CONF_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_POWER,
),
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(spi.spi_device_schema(cs_pin_required=False)),
validate_config,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await spi.register_spi_device(var, config)
cg.add(var.set_samples(config[CONF_SAMPLES]))
cg.add(var.set_phase_offset(config[CONF_PHASE_OFFSET]))
cg.add(var.set_pga_gain(config[CONF_PGA_GAIN]))
cg.add(var.set_gains(config[CONF_CURRENT_GAIN], config[CONF_VOLTAGE_GAIN]))
cg.add(var.set_hpf_enable(config[CONF_CURRENT_HPF], config[CONF_VOLTAGE_HPF]))
cg.add(var.set_pulse_energy_wh(config[CONF_PULSE_ENERGY]))
if CONF_VOLTAGE in config:
conf = config[CONF_VOLTAGE]
sens = await sensor.new_sensor(conf)
cg.add(var.set_voltage_sensor(sens))
if CONF_CURRENT in config:
conf = config[CONF_CURRENT]
sens = await sensor.new_sensor(conf)
cg.add(var.set_current_sensor(sens))
if CONF_POWER in config:
conf = config[CONF_POWER]
sens = await sensor.new_sensor(conf)
cg.add(var.set_power_sensor(sens))
@automation.register_action(
"cs5460a.restart",
CS5460ARestartAction,
maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(CS5460AComponent),
}
),
)
async def restart_action_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)

View File

@@ -4,7 +4,7 @@
namespace esphome {
namespace cse7766 {
static const char *TAG = "cse7766";
static const char *const TAG = "cse7766";
void CSE7766Component::loop() {
const uint32_t now = millis();

View File

@@ -9,7 +9,7 @@ from esphome.const import (
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_POWER,
DEVICE_CLASS_VOLTAGE,
ICON_EMPTY,
STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
UNIT_AMPERE,
UNIT_WATT,
@@ -27,35 +27,47 @@ CONFIG_SCHEMA = (
{
cv.GenerateID(): cv.declare_id(CSE7766Component),
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_POWER): sensor.sensor_schema(
UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER
unit_of_measurement=UNIT_WATT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(uart.UART_DEVICE_SCHEMA)
)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"cse7766", baud_rate=4800, require_rx=True
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield uart.register_uart_device(var, config)
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
if CONF_VOLTAGE in config:
conf = config[CONF_VOLTAGE]
sens = yield sensor.new_sensor(conf)
sens = await sensor.new_sensor(conf)
cg.add(var.set_voltage_sensor(sens))
if CONF_CURRENT in config:
conf = config[CONF_CURRENT]
sens = yield sensor.new_sensor(conf)
sens = await sensor.new_sensor(conf)
cg.add(var.set_current_sensor(sens))
if CONF_POWER in config:
conf = config[CONF_POWER]
sens = yield sensor.new_sensor(conf)
sens = await sensor.new_sensor(conf)
cg.add(var.set_power_sensor(sens))

View File

@@ -6,7 +6,7 @@
namespace esphome {
namespace ct_clamp {
static const char *TAG = "ct_clamp";
static const char *const TAG = "ct_clamp";
void CTClampSensor::setup() {
this->is_calibrating_offset_ = true;

View File

@@ -5,7 +5,7 @@ from esphome.const import (
CONF_SENSOR,
CONF_ID,
DEVICE_CLASS_CURRENT,
ICON_EMPTY,
STATE_CLASS_MEASUREMENT,
UNIT_AMPERE,
)
@@ -18,7 +18,12 @@ ct_clamp_ns = cg.esphome_ns.namespace("ct_clamp")
CTClampSensor = ct_clamp_ns.class_("CTClampSensor", sensor.Sensor, cg.PollingComponent)
CONFIG_SCHEMA = (
sensor.sensor_schema(UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT)
sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
)
.extend(
{
cv.GenerateID(): cv.declare_id(CTClampSensor),
@@ -32,11 +37,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)
await cg.register_component(var, config)
await sensor.register_sensor(var, config)
sens = yield cg.get_variable(config[CONF_SENSOR])
sens = await cg.get_variable(config[CONF_SENSOR])
cg.add(var.set_source(sens))
cg.add(var.set_sample_duration(config[CONF_SAMPLE_DURATION]))

View File

@@ -17,8 +17,8 @@ CONFIG_SCHEMA = cv.Schema(
)
def to_code(config):
template_ = yield cg.process_lambda(
async def to_code(config):
template_ = await cg.process_lambda(
config[CONF_LAMBDA],
[],
return_type=cg.std_vector.template(binary_sensor.BinarySensorPtr),
@@ -28,4 +28,4 @@ def to_code(config):
custom = cg.variable(config[CONF_ID], rhs)
for i, conf in enumerate(config[CONF_BINARY_SENSORS]):
rhs = custom.Pget_binary_sensor(i)
yield binary_sensor.register_binary_sensor(rhs, conf)
await binary_sensor.register_binary_sensor(rhs, conf)

View File

@@ -4,7 +4,7 @@
namespace esphome {
namespace custom {
static const char *TAG = "custom.binary_sensor";
static const char *const TAG = "custom.binary_sensor";
void CustomBinarySensorConstructor::dump_config() {
for (auto *child : this->binary_sensors_) {

View File

@@ -16,8 +16,8 @@ CONFIG_SCHEMA = cv.Schema(
)
def to_code(config):
template_ = yield cg.process_lambda(
async def to_code(config):
template_ = await cg.process_lambda(
config[CONF_LAMBDA],
[],
return_type=cg.std_vector.template(climate.Climate.operator("ptr")),
@@ -27,4 +27,4 @@ def to_code(config):
custom = cg.variable(config[CONF_ID], rhs)
for i, conf in enumerate(config[CONF_CLIMATES]):
rhs = custom.Pget_climate(i)
yield climate.register_climate(rhs, conf)
await climate.register_climate(rhs, conf)

View File

@@ -16,8 +16,8 @@ CONFIG_SCHEMA = cv.Schema(
)
def to_code(config):
template_ = yield cg.process_lambda(
async def to_code(config):
template_ = await cg.process_lambda(
config[CONF_LAMBDA],
[],
return_type=cg.std_vector.template(cover.Cover.operator("ptr")),
@@ -27,4 +27,4 @@ def to_code(config):
custom = cg.variable(config[CONF_ID], rhs)
for i, conf in enumerate(config[CONF_COVERS]):
rhs = custom.Pget_cover(i)
yield cover.register_cover(rhs, conf)
await cover.register_cover(rhs, conf)

View File

@@ -16,8 +16,8 @@ CONFIG_SCHEMA = cv.Schema(
)
def to_code(config):
template_ = yield cg.process_lambda(
async def to_code(config):
template_ = await cg.process_lambda(
config[CONF_LAMBDA],
[],
return_type=cg.std_vector.template(light.LightOutput.operator("ptr")),
@@ -27,4 +27,4 @@ def to_code(config):
custom = cg.variable(config[CONF_ID], rhs)
for i, conf in enumerate(config[CONF_LIGHTS]):
rhs = custom.Pget_light(i)
yield light.register_light(rhs, conf)
await light.register_light(rhs, conf)

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