Compare commits

...

443 Commits

Author SHA1 Message Date
Jesse Hills c83ecf764d Merge pull request #2410 from esphome/bump-2021.9.2
2021.9.2
2021-09-28 16:04:19 +13:00
Jesse Hills a2485a18cb Bump version to 2021.9.2 2021-09-28 15:41:58 +13:00
Jesse Hills 8ef2ad17b5 Fix lint issues in web_server_base (#2409) 2021-09-28 15:41:51 +13:00
Otto Winter 4579f78bf9 Merge pull request from GHSA-48mj-p7x2-5jfm 2021-09-28 15:39:41 +13:00
Sergey V. DUDANOV 1853407645 Midea fix (#2395) 2021-09-28 15:36:23 +13:00
Otto Winter cb5efc1c42 Bump aioesphomeapi to 9.1.1 (#2350) 2021-09-28 15:35:03 +13:00
Jesse Hills d26c2b1a44 Merge pull request #2346 from esphome/bump-2021.9.1
2021.9.1
2021-09-20 14:56:48 +12:00
Oxan van Leeuwen 8bda8e5393 Clean-up sensor integration (#2275) 2021-09-20 14:34:59 +12:00
Jesse Hills 954b8a0cff Bump version to 2021.9.1 2021-09-20 14:16:57 +12:00
Otto Winter 7c17e72db4 Add readv and writev for more efficient API packets (#2342) 2021-09-20 14:16:56 +12:00
Oxan van Leeuwen d180aee57f Apply color brightness to addressable light effects (#2321) 2021-09-20 14:16:56 +12:00
Oxan van Leeuwen e3ffecefc0 Cease using deprecated Cover methods in automations (#2326) 2021-09-20 14:16:56 +12:00
Oxan van Leeuwen 4c61cf153c Light transition fixes (#2320) 2021-09-20 14:16:56 +12:00
Paul Monigatti c78fb90e2f Fix MQTT discovery for sensor state_class (#2331) 2021-09-20 14:16:39 +12:00
Jesse Hills 321504cf29 Merge pull request #2328 from esphome/bump-2021.9.0
2021.9.0
2021-09-16 09:40:29 +12:00
Jesse Hills 0f4a7bf1f5 Bump version to 2021.9.0 2021-09-16 09:12:01 +12:00
Jesse Hills 711e74a12b Merge branch 'beta' into bump-2021.9.0 2021-09-16 09:12:01 +12:00
Jesse Hills aa8eb2c92a Merge pull request #2319 from esphome/bump-2021.9.0b5
2021.9.0b5
2021-09-15 19:51:55 +12:00
Jesse Hills b422a63b2a Bump version to 2021.9.0b5 2021-09-15 19:01:54 +12:00
Jesse Hills ad5f2cd748 Start a wifi scan after saving station details (#2315) 2021-09-15 19:01:54 +12:00
Maurice Makaay efae363739 Fix aioesphomeapi API logger with explicit api.port in the YAML. (#2310) 2021-09-15 19:01:54 +12:00
Jesse Hills 2d79d21c50 Simple time.sleep in place of threading wait due to upgraded zeroconf (#2307) 2021-09-15 19:01:54 +12:00
jsuanet 3b9d126322 Fix unit of measurement fields for DSMR power consumed/delivered fields (#2304)
Co-authored-by: Jos Suanet <jos@suanet.net>
2021-09-15 19:01:54 +12:00
Jesse Hills 896654aaef Merge pull request #2302 from esphome/bump-2021.9.0b4
2021.9.0b4
2021-09-14 23:23:51 +12:00
Jesse Hills 5fad38f65f Bump version to 2021.9.0b4 2021-09-14 23:07:08 +12:00
Jesse Hills 89f2ea5725 Fix binary strobe (#2301) 2021-09-14 23:07:07 +12:00
Jesse Hills a32ad33b4e Allow simple hostname for sntp servers (#2300) 2021-09-14 23:07:07 +12:00
Otto Winter a328fff5a7 Fix api noise explicit reject (#2297) 2021-09-14 23:07:07 +12:00
Otto Winter 233783c76c API Noise logging (#2298) 2021-09-14 23:07:07 +12:00
Otto Winter 39a18fb358 Bump platformio to 5.2.0 (#2291) 2021-09-14 23:07:02 +12:00
Jas Strong 460a144ca8 t6615: tolerate sensor dropping commands (#2255)
The Amphenol T6615 has a built-in calibration system which means that
the sensor could go away for a couple of seconds to figure itself out.
While this is happening, commands are silently dropped.

This caused the previous version of this code to lock up completely,
since there was no way for the command_ state machine to tick back to
the NONE state.

Instead of just breaking the state machine, which might be harmful on
a multi-core or multi-threaded device, add a timestamp and only break
the lock if it's been more than a second since the command was issued.

The command usually doesn't take more than a few milliseconds to
complete, so this should not affect things unduly.

While we're at it, rewrite the rx side to be more robust against
bytes going missing.

Instead of reading in the data essentially inline, read into a buffer
and process it when enough has been read to make progress.

If data stops coming when we expect it to, or the data is malformed,
have a timeout that sends a new command.

Co-authored-by: jas <jas@asspa.in>
2021-09-14 23:06:13 +12:00
Oxan van Leeuwen 23ead416d5 Suppress excessive warnings about deprecated Fan interfaces (#2270) 2021-09-14 23:06:13 +12:00
Oxan van Leeuwen 1b5f11bbee Only try compat parsing after regular parsing fails (#2269) 2021-09-14 23:06:13 +12:00
Jesse Hills 0da97289e6 Merge pull request #2295 from esphome/bump-2021.9.0b3
2021.9.0b3
2021-09-14 07:47:20 +12:00
Jesse Hills 91f12a50cf Bump version to 2021.9.0b3 2021-09-14 07:13:00 +12:00
Otto Winter e92a9d1d9e Fix API socket issues (#2288)
* Fix API socket issues

* Fix compile error against beta

* Format
2021-09-14 07:13:00 +12:00
Oxan van Leeuwen 4eb51ab4d6 Disable automatic usage of SNTP servers from DHCP (#2273) 2021-09-14 07:12:59 +12:00
Jesse Hills f1a8d957f8 Merge pull request #2267 from esphome/bump-2021.9.0b2
2021.9.0b2
2021-09-10 22:13:54 +12:00
Jesse Hills 9821a3442b Bump version to 2021.9.0b2 2021-09-10 21:34:38 +12:00
poptix 87842e097b sm300d2: Accept (undocumented) 0x80 checksum offset. (#2263)
Co-authored-by: Matt Hallacy <github@poptix.net>
2021-09-10 21:34:38 +12:00
Jesse Hills 7dd40e2014 Fix a few ESP32-C3 compiler issues (#2265)
* Fix using Serial when using ESP32-C3 standard pins

* Force type for std::min in pn532

* Fix variable size where size_t is different on exp32-c3
2021-09-10 21:34:38 +12:00
Jesse Hills a719998220 Merge pull request #2262 from esphome/bump-2021.9.0b1
2021.9.0b1
2021-09-09 11:40:33 +12:00
Jesse Hills 09a6fdf1c7 Bump version to 2021.9.0b1 2021-09-09 10:28:34 +12:00
Stephen Tierney d2616cbdfc PMSA003i Update state_class and async (#2216)
* Update component (state_class and async)

* No need to specify empty device class

* Remove unused import

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-09-09 10:14:08 +12:00
Keith Burzinski faf1c8bee8 SGP40 sensor start-up fix (#2178) 2021-09-09 09:42:35 +12:00
Peter van Dijk f09aca4865 pm1006: add support for sending a measurement request (#2214) 2021-09-09 09:35:00 +12:00
Jesse Hills cc52f37933 Revert "Dont dump legacy fields (#2241)" (#2259)
This reverts commit 97eba1eecc.
2021-09-09 09:29:08 +12:00
Otto Winter e5051eefbc API encryption (#2254) 2021-09-09 09:22:47 +12:00
Peter van Dijk 9e5cd0da51 ccs811: publish firmware version; log bootloader and HW version; fix a bug (#2006) 2021-09-09 09:19:43 +12:00
Sergey V. DUDANOV 4e120a291e Midea support v2 (#2188) 2021-09-09 09:10:02 +12:00
Otto Winter 2790d72bff Convert API to use sockets (#2253)
* Socket component

* Lint

* Lint

* Fix esp8266 missing INADDR_ANY

* API convert to sockets and frame helper

* Fix compile error

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-09-08 12:52:57 +02:00
Otto Winter e44f447d85 Fix socket not setting callbacks early enough (#2260) 2021-09-08 12:02:32 +02:00
Jesse Hills 4356581db0 Remove removesuffix 2021-09-08 19:51:20 +12:00
Jesse Hills f87a701b28 Bump dashboard to 20210908.0 and fix card names for yml (#2258) 2021-09-08 17:14:12 +12:00
Jesse Hills fa2eb46cd6 Allow .yml files to show up in the dashboard (#2257) 2021-09-08 16:11:09 +12:00
Otto Winter f924e80f43 Socket component (#2250) 2021-09-08 15:41:42 +12:00
Chris Nussbaum 6180ee8065 Template sensors always publish on update interval (#2224)
Co-authored-by: Chris Nussbaum <chris.nussbaum@protolabs.com>
2021-09-08 15:36:49 +12:00
wifwucite 1be106c0b5 Fix fan speed restore issue on boot (#1867) 2021-09-08 15:30:17 +12:00
dgtal1 b0533db2eb Add new trigger to fan component on_speed_set (#2246) 2021-09-08 15:15:57 +12:00
Otto Winter dba502c756 Logger prevent recursive logging (#2251) 2021-09-08 08:57:20 +12:00
Oxan van Leeuwen d9cb64b893 Add device classes new in HA 2021.9 (#2248) 2021-09-07 10:12:26 +12:00
Kamil Trzciński 2d91e6b977 template: select: fix initial_value cannot be used with lambda (#2244) 2021-09-07 08:00:08 +12:00
Alex 4a6f1f150a Fix runtime exception due to dict typing (#2243) 2021-09-06 14:48:28 +12:00
Peter van Dijk 7f76f3726f LOG_UPDATE_INTERVAL: correctly report "never" (#2240) 2021-09-06 14:47:13 +12:00
Jesse Hills e2d97b6f36 Light: include ON_OFF capability to BRIGHTNESS ColorMode (#2204) 2021-09-06 08:57:58 +12:00
Jesse Hills 2a653642f5 Fix encoding bug (#2242) 2021-09-06 08:57:37 +12:00
Jesse Hills 97eba1eecc Dont dump legacy fields (#2241) 2021-09-06 08:36:55 +12:00
Jesse Hills ff6bed54c6 Remove last_reset_type and convert all those sensors to TOTAL_INCREASING (#2233) 2021-09-06 08:30:47 +12:00
Jesse Hills f9b0666adf Allow using a git source for a package (#2193) 2021-09-06 08:23:06 +12:00
Jesse Hills ca12b8aa56 Move to use zeroconf library instead of inline copy (#2192) 2021-09-06 08:22:15 +12:00
Christian Ferbar 77508f7e44 Fix UARTComponent hardware vs software UART0 conflict (#2229)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2021-09-04 14:49:34 +12:00
Oxan van Leeuwen 54de0ca0da Reject template select/number/switches that don't handle user input (#2230) 2021-09-04 14:46:53 +12:00
Oxan van Leeuwen f364788c03 Expose WHITE/CWWW/RGBCT color modes over MQTT (#2231) 2021-09-04 14:32:33 +12:00
DAVe3283 00aaf84c37 Fix uptime's state_class (esphome/issues#2337) (#2205) 2021-09-03 16:58:30 +12:00
Kamil Trzciński b01bc76dc5 mqtt_sensor: properly send state_class via MQTT (#2228) 2021-09-03 16:37:18 +12:00
dependabot[bot] 910f812737 Bump pylint from 2.9.6 to 2.10.2 (#2197)
Bumps [pylint](https://github.com/PyCQA/pylint) from 2.9.6 to 2.10.2.
- [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.6...v2.10.2)

---
updated-dependencies:
- dependency-name: pylint
  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-09-03 16:20:42 +12:00
Chris Nussbaum a4d024f43d Add is_on and is_off conditions for the fan component (#2225)
Co-authored-by: Chris Nussbaum <chris.nussbaum@protolabs.com>
2021-09-02 12:16:11 +12:00
Alex 9937ad7fa0 Cleanup flash transitions (#2227) 2021-09-02 11:56:40 +12:00
Petko Bordjukov edcd88123d iBeacon support for ble_presence (#1627) 2021-09-02 11:46:15 +12:00
Alex ea1b5e19f0 Add transitions to light flash (#2201) 2021-08-31 14:18:16 +12:00
Jesse Hills 54337befc2 Fix some lint errors in pylint 2.10.2 (#2226) 2021-08-31 14:00:58 +12:00
Jérôme Laban 140ef791aa Support for the AirThings Wave Plus (#1656)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-08-31 14:00:30 +12:00
dependabot[bot] 58350b6c99 Bump pytest from 6.2.4 to 6.2.5 (#2223)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-08-31 11:10:52 +12:00
dependabot[bot] f186ff8b46 Bump black from 21.7b0 to 21.8b0 (#2222)
Bumps [black](https://github.com/psf/black) from 21.7b0 to 21.8b0.
- [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-08-31 10:40:06 +12:00
WeekendWarrior1 03190611bb Add H-Bridge fan component (#2212)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-08-31 10:10:22 +12:00
Guillermo Ruffino 37f322585e Glmnet schema 202105 (#2220) 2021-08-31 06:48:19 +12:00
Marcio Granzotto Rodrigues 9218e85bd6 Remove footer validation for fujitsu_general (#2196) 2021-08-31 06:03:30 +12:00
Jesse Hills f923ba87c0 Bump dashboard to 20210826.0 (#2211) 2021-08-30 09:41:14 +12:00
Oxan van Leeuwen fac49896df Update known boards (#2190) 2021-08-30 09:41:05 +12:00
Paul Monigatti 56225701f9 Fix Packages when using MQTT (#2210) 2021-08-30 09:27:38 +12:00
Alessandro Campolo b5de43b225 cs_pin made optional for ili9341 (#2219) 2021-08-30 09:07:06 +12:00
Jesse Hills b955527f6c Fix css/js file loading for webserver when esphome not executed form config directory (#2207) 2021-08-26 15:34:39 +12:00
marsjan155 94b28102f5 Add st7920 display, (#1440)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-08-26 14:33:03 +12:00
WeekendWarrior1 de871862a8 Optionally set direction on fan.turn_on action (#2171)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-08-26 10:25:24 +12:00
Jesse Hills 3be56fd502 Fix SDM energy units to be KILO... (#2206) 2021-08-26 09:27:00 +12:00
Jesse Hills 5086cd716f Merge pull request #2203 from esphome/bump-2021.8.2
2021.8.2
2021-08-25 19:59:13 +12:00
Jesse Hills 4937af0cd9 Bump version to 2021.8.2 2021-08-25 19:46:55 +12:00
Jesse Hills 877a5fda41 Revert "Light: include ON_OFF capability to BRIGHTNESS ColorMode (#2186)" (#2202)
This reverts commit b0fa317302.
2021-08-25 19:46:54 +12:00
Jesse Hills 39cd2838df Revert "Light: include ON_OFF capability to BRIGHTNESS ColorMode (#2186)" (#2202)
This reverts commit b0fa317302.
2021-08-25 19:38:51 +12:00
Stephan Peijnik-Steinwender 565473c90c ST7789V: Make backlight_pin optional (#2180) 2021-08-24 22:57:53 +12:00
Jesse Hills 1fac91a659 Merge pull request #2199 from esphome/bump-2021.8.1
2021.8.1
2021-08-24 14:40:24 +12:00
Jesse Hills 0a4837c1f0 Bump version to 2021.8.1 2021-08-24 14:26:28 +12:00
mtl010957 e7404183a0 Internally all temperature units are Celsius so just send it directly (#1840) 2021-08-24 14:26:28 +12:00
Samuel Sieb 44f8dcfb6e Fix template select lambda (#2198) 2021-08-24 14:26:27 +12:00
Chris Nussbaum 481e0e98f8 Tuya fan component uses enum datapoint type for speed instead of integer (#2182)
Co-authored-by: Chris Nussbaum <chris.nussbaum@protolabs.com>
2021-08-24 14:26:27 +12:00
puuu 9de40c26eb mqtt_light: remove legacy API config that is not compatible with HA 2021.8 (#2183) 2021-08-24 14:26:27 +12:00
Oxan van Leeuwen ad953f02d1 Fix addressable light control without transitions & effects with transitions (#2187) 2021-08-24 14:26:27 +12:00
puuu 3869e56521 Light: include ON_OFF capability to BRIGHTNESS ColorMode (#2186) 2021-08-24 14:26:27 +12:00
Jesse Hills 63d87b17aa Fix pypi download url (#2177) 2021-08-24 14:26:27 +12:00
mtl010957 ed68a0e773 Internally all temperature units are Celsius so just send it directly (#1840) 2021-08-24 13:38:59 +12:00
Samuel Sieb e2640c8368 Fix template select lambda (#2198) 2021-08-24 13:26:59 +12:00
Chris Nussbaum eff626248f Tuya fan component uses enum datapoint type for speed instead of integer (#2182)
Co-authored-by: Chris Nussbaum <chris.nussbaum@protolabs.com>
2021-08-24 13:20:39 +12:00
puuu ce29a3b07a mqtt_light: remove legacy API config that is not compatible with HA 2021.8 (#2183) 2021-08-24 13:18:40 +12:00
Otto Winter 1b89174558 Store source package in Component for debugging (#2070) 2021-08-24 06:49:19 +12:00
Oxan van Leeuwen 1c1ad32610 Add deprecated attribute to some deprecated types/methods (#2185) 2021-08-24 06:48:12 +12:00
Jesse Hills 71237e2f76 Fix template select log message mentioning number (#2194) 2021-08-23 21:21:30 +12:00
Oxan van Leeuwen 518c271eba Fix addressable light control without transitions & effects with transitions (#2187) 2021-08-23 20:44:24 +12:00
Oxan van Leeuwen d71996e58d Reduce static RAM usage (#2140) 2021-08-23 20:43:54 +12:00
Oxan van Leeuwen 2f33cd2db5 Remove double scheduling from addressable lights (#1963) 2021-08-23 20:00:38 +12:00
Oxan van Leeuwen 5ec9bb0fb5 Clean-up constant definitions (#2148) 2021-08-23 19:21:30 +12:00
Oxan van Leeuwen 8cc3cbb22e Add macros header with more usable Arduino version defines (#2145) 2021-08-23 19:19:21 +12:00
puuu b0fa317302 Light: include ON_OFF capability to BRIGHTNESS ColorMode (#2186) 2021-08-21 22:26:24 +12:00
Jesse Hills 5cb56bc677 Set SDM voltage state class to measurement (#2181) 2021-08-19 21:28:58 +12:00
Jesse Hills 21f8fd9fa5 Fix pypi download url (#2177) 2021-08-18 15:39:57 +12:00
Jesse Hills 2100ef63a9 Bump version to 2021.9.0-dev 2021-08-18 15:19:25 +12:00
Jesse Hills 29db77c9c9 Merge branch 'release' into dev 2021-08-18 15:17:33 +12:00
Jesse Hills 2e59ad90cc Fix docker release for new tags without v 2021-08-18 15:10:44 +12:00
Jesse Hills 1b8c9edcde Merge pull request #2176 from esphome/bump-2021.8.0
2021.8.0
2021-08-18 15:03:48 +12:00
Jesse Hills d4c2a85f9c Bump version to v2021.8.0 2021-08-18 14:25:10 +12:00
Jesse Hills 8c75b87e94 Merge pull request #2175 from esphome/bump-1.21.0b3
1.21.0b3
2021-08-18 11:37:39 +12:00
Jesse Hills 409d4b9d47 Bump version to v1.21.0b3 2021-08-18 11:11:39 +12:00
Jesse Hills 4e3b95d120 Add new total_increasing state-class for Home Assistant 2021.9+ (#2166) 2021-08-18 11:11:39 +12:00
Jesse Hills 61a9c9fa33 Remove specified accuracy_decimals from total_daily_energy (#2174) 2021-08-18 11:11:39 +12:00
Jesse Hills 9c605f2d46 Send dirty states when screen wakes up (#2167) 2021-08-18 11:11:39 +12:00
Franck Nijhof 44bb5a89c8 Add Gas device class to DSMR component (#2169) 2021-08-18 11:11:39 +12:00
Daniel Hyles cbdb96f105 Add a dummy color temp (#2161) 2021-08-18 11:11:39 +12:00
Oxan van Leeuwen 9ee3463d07 Initialize color temperature to value within range if possible (#2168) 2021-08-18 11:11:39 +12:00
Jesse Hills f0b14055b6 Add new total_increasing state-class for Home Assistant 2021.9+ (#2166) 2021-08-18 11:04:13 +12:00
Jesse Hills fbd9e87b51 Remove specified accuracy_decimals from total_daily_energy (#2174) 2021-08-18 10:32:19 +12:00
Jesse Hills edb3b77916 Send dirty states when screen wakes up (#2167) 2021-08-18 10:22:00 +12:00
Chris Nussbaum ebaa84617f Total daily energy methods (#2163)
Co-authored-by: Chris Nussbaum <chris.nussbaum@protolabs.com>
2021-08-18 07:16:02 +12:00
Franck Nijhof 8eb18995cb Add device class update to binary sensor (#2170) 2021-08-17 21:44:05 +12:00
Franck Nijhof ebabf0e7d8 Add Gas device class to DSMR component (#2169) 2021-08-17 21:43:51 +12:00
Maurice Makaay 607e1f823d Minor code cleanup in light components (#2162)
Co-authored-by: Maurice Makaay <mmakaay1@xs4all.net>
2021-08-17 20:12:29 +12:00
Daniel Hyles 3b52a306cd Add a dummy color temp (#2161) 2021-08-17 20:05:37 +12:00
Oxan van Leeuwen 0c370d5897 Initialize color temperature to value within range if possible (#2168) 2021-08-17 14:02:38 +12:00
Jesse Hills 8bf0448f41 Merge pull request #2160 from esphome/bump-1.21.0b2
1.21.0b2
2021-08-16 14:22:28 +12:00
Jesse Hills 14e04eb231 Bump version to v1.21.0b2 2021-08-16 12:32:48 +12:00
Otto Winter 1be9bac3a9 Fix native API log level enum values (#2151) 2021-08-16 12:32:48 +12:00
Keith Burzinski 02b5a3efb8 Thermostat delayed fan mode fix (#2158) 2021-08-16 12:32:48 +12:00
puuu bd457f64d8 let sensors announce its state_class via mqtt (#2155) 2021-08-16 12:32:48 +12:00
Oxan van Leeuwen 9efeea14f2 Always send all light state values in API (#2150) 2021-08-16 12:32:48 +12:00
Otto Winter 9b48ff5775 Fix native API log level enum values (#2151) 2021-08-16 11:57:50 +12:00
Keith Burzinski 117b58ebe6 Thermostat delayed fan mode fix (#2158) 2021-08-16 09:31:48 +12:00
puuu 303b699005 let sensors announce its state_class via mqtt (#2155) 2021-08-16 07:59:29 +12:00
Oxan van Leeuwen 9173da0416 Always send all light state values in API (#2150) 2021-08-16 07:40:34 +12:00
Jesse Hills d2cd65f5db Merge pull request #2144 from esphome/bump-1.21.0b1
1.21.0b1
2021-08-11 22:09:43 +12:00
Jesse Hills 2735f96516 Fix bad merge again 2021-08-11 21:54:24 +12:00
Jesse Hills 6847645782 Fix bad merge conflict 2021-08-11 21:45:04 +12:00
Jesse Hills b0bc898278 Bump version to v1.21.0b1 2021-08-11 21:27:39 +12:00
Jesse Hills c0f6af7213 Merge branch 'dev' into bump-1.21.0b1 2021-08-11 21:26:26 +12:00
Jesse Hills 5edebaf468 Bump version to v1.22.0-dev 2021-08-11 19:48:46 +12:00
Oxan van Leeuwen d436409153 Support multiple configuration directories for update-all subcommand (#1925) 2021-08-11 17:21:57 +12:00
Branimir Lambov 8c41fc2b1d Support for the DKE screen version of LilyGo-TTGO-T5 V2.3 (#1969)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-08-11 17:14:17 +12:00
Oxan van Leeuwen 46f17bea66 Modular light transformers (#2124) 2021-08-11 16:51:35 +12:00
Stefan Agner 11477dbc03 Fix format warning in Tuya component (#1954) 2021-08-11 16:50:05 +12:00
buxtronix 947c104eff Support for AM43 BLE blind motors (#1744)
Co-authored-by: Ben Buxton <bb@cactii.net>
Co-authored-by: Otto Winter <otto@otto-winter.com>
Co-authored-by: Stefan Agner <stefan@agner.ch>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: René Klomp <rene@klomp.ws>
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
Co-authored-by: Geoff Davis <geoff@geoffdavis.com>
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
Co-authored-by: dentra <dentra@users.noreply.github.com>
Co-authored-by: Guillermo Ruffino <glm.net@gmail.com>
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
Co-authored-by: Barry Loong <loongyh@users.noreply.github.com>
Co-authored-by: Sergey V. DUDANOV <sergey.dudanov@gmail.com>
Co-authored-by: Balazs Scheidler <bazsi77@gmail.com>
2021-08-11 16:07:10 +12:00
Stefan Agner e5366dbbe7 Add deassert_rts_dtr option to force RTS/DTR low when using miniterm (#2089) 2021-08-11 07:55:36 +12:00
Andreas Hergert d3375193a9 Feature pipsolar anh (#1664)
Co-authored-by: Andreas Hergert <andreas.hergert@otrs.com>
Co-authored-by: Otto Winter <otto@otto-winter.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-08-11 07:48:32 +12:00
Chris Nussbaum 6144ce1fe0 Break the Tuya set_datapoint_value method into separate methods per datapoint type (#2059)
Co-authored-by: Chris Nussbaum <chris.nussbaum@protolabs.com>
Co-authored-by: Trevor North <trevor@freedisc.co.uk>
2021-08-11 07:44:31 +12: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
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 5f27757039 Merge pull request #2123 from esphome/bump-1.20.4
1.20.4
2021-08-04 17:54:53 +02:00
Otto winter 532907219b Bump version to v1.20.4 2021-08-04 17:46:10 +02:00
Otto Winter eeaba74553 Fix external components not refreshing with default or high refresh time (#2122) 2021-08-04 17:46:10 +02:00
brambo123 dd637582a4 Fix time.on_time triggering if time jumped back (#1806)
Co-authored-by: Otto winter <otto@otto-winter.com>
2021-08-04 17:46:10 +02:00
Carlos Garcia Saura b0d12aeea1 [duty_cycle] initialize two missing variables (#2088) 2021-08-04 17:46:09 +02:00
Łukasz Śliwiński bdbd813455 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-04 17:46:09 +02:00
Paul Monigatti a6fac2b175 Fix min/max keys in MQTT Number to match Home Assistant (#2102) 2021-08-04 17:46:09 +02:00
Guillermo Ruffino 5ce923ea90 fix diplay trigger missing base class (#2099) 2021-08-04 17:46:08 +02:00
Otto Winter 29f0508dc2 Fix PID climate breaks when restoring old modes (#2086) 2021-08-04 17:46:08 +02:00
Otto Winter 3ffa59f0cd Fix climate restore schema changed resulting in invalid restore (#2068)
Co-authored-by: Stefan Agner <stefan@agner.ch>
2021-08-04 17:46:08 +02:00
WeekendWarrior1 790d6ef94c Move configure_rmt() into setup() (#2028) 2021-08-04 17:46:08 +02:00
WeekendWarrior1 7828f48b9a Correctly invert esp32 RMT TX (#2022) 2021-08-04 17:46:07 +02: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
Jesse Hills 9fbb3659a6 Merge pull request #2098 from esphome/bump-1.20.3
1.20.3
2021-07-30 15:46:22 +12:00
Jesse Hills fee446c28a Bump version to v1.20.3 2021-07-30 11:00:10 +12:00
Jesse Hills 1d56f0b035 Set pulse meter total to use state class measurement and last reset type auto (#2097) 2021-07-30 11:00:10 +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 341fddb9aa Merge pull request #2091 from esphome/bump-1.20.2
1.20.2
2021-07-29 20:42:05 +12:00
Jesse Hills 456824669f Bump version to v1.20.2 2021-07-29 19:51:17 +12:00
Jesse Hills 62f3039d82 Use sensor_schema for total_daily_energy (#2090)
Co-authored-by: Otto Winter <otto@otto-winter.com>
2021-07-29 19:51:17 +12:00
Jesse Hills be4c718859 HLW8012 - Dump energy sensor config (#2082) 2021-07-29 19:51:17 +12:00
Jesse Hills c2f9ed7c59 Bump esphome dashboard to 20210728.0 (#2081) 2021-07-29 19:51:17 +12:00
John K. Luebs bfac6607d1 More Tuya MCU robustness (#2080) 2021-07-29 19:51:17 +12: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
Jesse Hills e43dcded62 Merge pull request #2074 from esphome/bump-1.20.1
1.20.1
2021-07-27 11:00:48 +12:00
Jesse Hills 887081fd71 Bump version to v1.20.1 2021-07-27 09:43:05 +12:00
Otto Winter 71ded24fce Fix MQTT climate custom fan modes without regular ones (#2071) 2021-07-27 09:43:05 +12:00
Chris Nussbaum 1e2a9e8348 Couple more updates for the Tuya component (#2065)
Co-authored-by: Chris Nussbaum <chris.nussbaum@protolabs.com>
2021-07-27 09:43:05 +12:00
buxtronix 64a3aa7092 Log warning about lack of support for Anova nano (#2063)
Co-authored-by: Ben Buxton <bb@cactii.net>
2021-07-27 09:43:05 +12:00
carstenschroeder fda8dd4ce3 Fixes new auto mode COOL and HEAT after #1994 (#2053) 2021-07-27 09:43:04 +12:00
Sergey V. DUDANOV 1efabd27d8 midea_ac: fix presets implementation (#2054) 2021-07-27 09:43:04 +12:00
Maurice Makaay caa651e55b Accept change as proposed by black. (#2055) 2021-07-27 09:43:04 +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
Jesse Hills 10a6e9b4ee Merge pull request #2051 from esphome/bump-1.20.0
1.20.0
2021-07-22 08:32:30 +12:00
Jesse Hills 4b8ec44262 Bump version to v1.20.0 2021-07-22 07:55:49 +12:00
Jesse Hills bd74ed4bc0 Merge branch 'beta' into bump-1.20.0 2021-07-22 07:55:49 +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
Jesse Hills d01f296420 Merge pull request #2048 from esphome/bump-1.20.0b6
1.20.0b6
2021-07-21 11:35:27 +12:00
Jesse Hills 27112e2ace Bump version to v1.20.0b6 2021-07-21 10:52:48 +12:00
Sean Vig 837930234f Remove superfluous polling on ADS1115 (#2015) 2021-07-21 10:52:48 +12:00
Jesse Hills e19aa3bbe0 Adding last_reset_type to sensors that should support it. (#2039) 2021-07-21 10:52:48 +12:00
Oxan van Leeuwen 35b5c1ed56 Fix white value transition for addressable lights (#2045) 2021-07-21 10:52:48 +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
Jesse Hills c9d93ff685 Merge pull request #2044 from esphome/bump-1.20.0b5
1.20.0b5
2021-07-20 17:27:53 +12:00
Jesse Hills fa72990a63 Bump version to v1.20.0b5 2021-07-20 17:09:58 +12:00
Otto Winter e5afb1c4ea ESP32 ADC use esp-idf (#2024)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-07-20 17:09:57 +12:00
Sean Vig 73ead5f328 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:09:57 +12:00
Paulus Schoutsen 5c57b51378 Bump dashboard to 20210719.0 (#2043) 2021-07-20 17:09:57 +12:00
Sergey V. DUDANOV e25935ef21 midea_ac: Fix turbo mode. Preset BOOST. (#2029) 2021-07-20 17:09:57 +12:00
Jesse Hills c7a52c3894 Add restore_value to template number (#2041) 2021-07-20 17:09:57 +12: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 53a4689ed1 Merge pull request #2040 from esphome/bump-1.20.0b4
1.20.0b4
2021-07-20 12:29:15 +12:00
Jesse Hills 0a82e6e792 Bump version to v1.20.0b4 2021-07-20 10:28:23 +12:00
Jesse Hills 98855e4123 Number and Template Number updates (#2036)
Co-authored-by: Otto winter <otto@otto-winter.com>
2021-07-20 10:28:22 +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
Jesse Hills 6a09d7c49b Merge pull request #2034 from esphome/bump-1.20.0b3
1.20.0b3
2021-07-19 08:44:04 +12:00
Jesse Hills 46e50ba53f Bump version to v1.20.0b3 2021-07-19 08:28:58 +12:00
Otto Winter f1e3ff2ed2 Improve external components error messages (#2026) 2021-07-19 08:28:58 +12:00
Otto Winter 7787fa8f29 Dashboard disable assets caching (#2025) 2021-07-19 08:28:58 +12:00
Otto Winter 70902029f8 GH Actions CI use GHCR (#2027) 2021-07-19 08:28:58 +12:00
Otto Winter 4f9a56c884 Refactor docker build system and workflows (#2023) 2021-07-19 08:28:53 +12:00
Sean Vig 3715ba030b Fix ethernet component hostname handling (#2010)
Co-authored-by: Otto Winter <otto@otto-winter.com>
2021-07-19 08:25:08 +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
Jesse Hills 0c93be97a9 Merge pull request #2017 from esphome/bump-1.20.0b2
1.20.0b2
2021-07-15 14:59:08 +12:00
Jesse Hills 54eb6070fb Bump version to v1.20.0b2 2021-07-15 13:55:58 +12:00
SenexCrenshaw 4dbf1c521e Nextion upload and sensors (#1464)
Co-authored-by: Senex Crenshaw <senexcrenshaw@gmail.com>
2021-07-15 13:55:58 +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 f30b8f6b3c Merge pull request #2014 from esphome/bump-1.20.0b1
1.20.0b1
2021-07-15 08:17:04 +12:00
Jesse Hills 0992609bf4 Bump version to v1.21.0-dev 2021-07-15 07:45:05 +12:00
Jesse Hills 18c08f24ad Bump version to v1.20.0b1 2021-07-15 07:45:05 +12:00
Jesse Hills a7f53aea0e Merge branch 'dev' into bump-1.20.0b1 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 a91e6a6bdf Merge pull request #1959 from esphome/bump-1.19.4
1.19.4
2021-06-24 13:31:01 +12:00
Jesse Hills 8600620305 Bump version to v1.19.4 2021-06-24 12:49:45 +12:00
Jesse Hills 96721f305f Bump dashboard to 20210623.0 (#1958) 2021-06-24 12:49:45 +12:00
Otto Winter 2bf70d7d00 Compat argv parsing improvements (#1952) 2021-06-24 12:49:45 +12:00
Otto Winter 1d8c170f48 Add climate preset NONE again (#1951) 2021-06-24 12:49:45 +12:00
Otto Winter 6009c7edb4 Disallow power_save_mode NONE if used together with BLE (#1950) 2021-06-24 12:49:44 +12:00
Otto Winter e3f36c033e API raise minor version for climate changes (#1947) 2021-06-24 12:49:44 +12:00
Otto Winter d4eb0f1655 Rework climate traits (#1941)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-06-24 12:49:36 +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 e20ec00071 Merge pull request #1956 from esphome/bump-1.19.3
1.19.3
2021-06-23 20:16:42 +12:00
Jesse Hills 150114d774 Bump version to v1.19.3 2021-06-23 19:39:37 +12:00
Jesse Hills 89dfa5ea82 Bump esphome-dashboard to 20210622.0 (#1955) 2021-06-23 19:39:31 +12: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
954 changed files with 34605 additions and 15417 deletions
+1 -1
View File
@@ -49,7 +49,7 @@ ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
+9 -22
View File
@@ -5,30 +5,24 @@ Checks: >-
-android-*,
-boost-*,
-bugprone-branch-clone,
-bugprone-macro-parentheses,
-bugprone-narrowing-conversions,
-bugprone-reserved-identifier,
-bugprone-signed-char-misuse,
-bugprone-suspicious-include,
-bugprone-too-small-loop-variable,
-bugprone-unhandled-self-assignment,
-cert-dcl37-c,
-cert-dcl50-cpp,
-cert-dcl51-cpp,
-cert-err58-cpp,
-cert-oop54-cpp,
-cert-oop57-cpp,
-cert-str34-c,
-clang-analyzer-core.CallAndMessage,
-clang-analyzer-optin.*,
-clang-analyzer-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-avoid-magic-numbers,
-cppcoreguidelines-avoid-non-const-global-variables,
-cppcoreguidelines-c-copy-assignment-signature,
-cppcoreguidelines-init-variables,
-cppcoreguidelines-macro-usage,
-cppcoreguidelines-narrowing-conversions,
@@ -45,17 +39,17 @@ 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,
@@ -65,12 +59,8 @@ Checks: >-
-llvmlibc-*,
-misc-non-private-member-variables-in-classes,
-misc-no-recursion,
-misc-unconventional-assign-operator,
-misc-unused-parameters,
-modernize-avoid-c-arrays,
-modernize-deprecated-headers,
-modernize-pass-by-value,
-modernize-pass-by-value,
-modernize-return-braced-init-list,
-modernize-use-auto,
-modernize-use-default-member-init,
@@ -78,7 +68,6 @@ Checks: >-
-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,
@@ -94,10 +83,8 @@ Checks: >-
-readability-redundant-string-init,
-readability-uppercase-literal-suffix,
-readability-use-anyofallof,
-warnings-as-errors,
-zircon-*
-warnings-as-errors
WarningsAsErrors: '*'
HeaderFilterRegex: '^.*/src/esphome/.*'
AnalyzeTemporaryDtors: false
FormatStyle: google
CheckOptions:
+31 -6
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",
}
}
+4
View File
@@ -103,6 +103,10 @@ venv.bak/
# mypy
.mypy_cache/
# PlatformIO
.pio/
# ESPHome
config/
examples/
Dockerfile
+8 -2
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
+17 -32
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.4.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
+88 -106
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: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: 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:1.1
# 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,82 +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
- test5
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 compile tests/${{ matrix.test }}.yaml
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' }}
+83 -25
View File
@@ -13,30 +13,88 @@ on:
- '.github/workflows/docker-lint-build.yml'
jobs:
publish-docker-lint-iage:
name: Build docker containers
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 TAG
run: |
echo "TAG=1.1" >> $GITHUB_ENV
- name: Pull for cache
run: |
docker pull "esphome/esphome-lint:latest" || true
- name: Build
run: |
docker build \
--cache-from "esphome/esphome-lint:latest" \
--file "docker/Dockerfile.lint" \
--tag "esphome/esphome-lint:latest" \
--tag "esphome/esphome-lint:${TAG}" \
.
- name: Log in to docker hub
env:
DOCKER_USER: ${{ secrets.DOCKER_USER }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
- run: |
docker push "esphome/esphome-lint:${TAG}"
docker push "esphome/esphome-lint:latest"
- 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
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
}
]
}
]
}
-247
View File
@@ -1,247 +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: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: 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:1.1
# 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
- test5
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 compile tests/${{ matrix.test }}.yaml
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.4.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
+78 -241
View File
@@ -1,164 +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:1.1
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:1.1
# 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
- test5
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 compile tests/${{ matrix.test }}.yaml
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/}"
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
@@ -182,129 +53,95 @@ 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.4.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:
- env:
TOKEN: ${{ secrets.DEPLOY_HASSIO_TOKEN }}
run: |
TAG="${GITHUB_REF#refs/tags/v}"
TAG="${GITHUB_REF#refs/tags/}"
curl \
-u ":$TOKEN" \
-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\"}}"
+4
View File
@@ -13,6 +13,9 @@ __pycache__/
# Intellij Idea
.idea
# Vim
*.swp
# Hide some OS X stuff
.DS_Store
.AppleDouble
@@ -122,4 +125,5 @@ config/
tests/build/
tests/.esphome/
/.temp-clang-tidy.cpp
/.temp/
.pio/
+1 -1
View File
@@ -23,5 +23,5 @@ repos:
- id: no-commit-to-branch
args:
- --branch=dev
- --branch=master
- --branch=release
- --branch=beta
+28 -7
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 dashboard config",
"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
}
]
}
]
]
}
]
}
+36 -2
View File
@@ -14,19 +14,27 @@ esphome/core/* @esphome/core
esphome/components/ac_dimmer/* @glmnet
esphome/components/adc/* @esphome/core
esphome/components/addressable_light/* @justfalter
esphome/components/airthings_ble/* @jeromelaban
esphome/components/airthings_wave_plus/* @jeromelaban
esphome/components/am43/* @buxtronix
esphome/components/am43/cover/* @buxtronix
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/ccs811/* @habbie
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
@@ -35,6 +43,7 @@ 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
@@ -45,7 +54,12 @@ 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/hbridge/fan/* @WeekendWarrior
esphome/components/hbridge/light/* @DotNetDann
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
@@ -66,33 +80,47 @@ esphome/components/mcp23x17_base/* @jesserockz
esphome/components/mcp23xxx_base/* @jesserockz
esphome/components/mcp2515/* @danielschramm @mvturnho
esphome/components/mcp9808/* @k7hpn
esphome/components/midea_ac/* @dudanov
esphome/components/midea_dongle/* @dudanov
esphome/components/midea/* @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/pipsolar/* @andreashergert1984
esphome/components/pm1006/* @habbie
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/socket/* @esphome/core
esphome/components/spi/* @esphome/core
esphome/components/ssd1322_base/* @kbx81
esphome/components/ssd1322_spi/* @kbx81
@@ -107,17 +135,23 @@ esphome/components/ssd1351_base/* @kbx81
esphome/components/ssd1351_spi/* @kbx81
esphome/components/st7735/* @SenexCrenshaw
esphome/components/st7789v/* @kbx81
esphome/components/st7920/* @marsjan155
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
+1 -1
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/)
+1 -1
View File
@@ -1,4 +1,4 @@
ARG BUILD_FROM=esphome/esphome-base-amd64:3.4.0
ARG BUILD_FROM=esphome/esphome-base:latest
FROM ${BUILD_FROM}
# First install requirements to leverage caching when requirements don't change
+1 -13
View File
@@ -1,13 +1 @@
FROM esphome/esphome-base-amd64:3.4.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
+1 -1
View File
@@ -1,4 +1,4 @@
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
+2 -1
View File
@@ -1,4 +1,5 @@
FROM esphome/esphome-lint-base:3.4.0
ARG BUILD_FROM=esphome/esphome-lint-base:latest
FROM ${BUILD_FROM}
COPY requirements.txt requirements_optional.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini /
RUN \
+177
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 = "4.2.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()
@@ -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
@@ -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
+96 -64
View File
@@ -11,6 +11,7 @@ from esphome.config import iter_components, read_config, strip_default_ids
from esphome.const import (
CONF_BAUD_RATE,
CONF_BROKER,
CONF_DEASSERT_RTS_DTR,
CONF_LOGGER,
CONF_OTA,
CONF_PASSWORD,
@@ -99,10 +100,21 @@ def run_miniterm(config, port):
baud_rate = config["logger"][CONF_BAUD_RATE]
if baud_rate == 0:
_LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.")
return
_LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate)
backtrace_state = False
with serial.Serial(port, baudrate=baud_rate) as ser:
ser = serial.Serial()
ser.baudrate = baud_rate
ser.port = port
# We can't set to False by default since it leads to toggling and hence
# ESP32 resets on some platforms.
if config["logger"][CONF_DEASSERT_RTS_DTR]:
ser.dtr = False
ser.rts = False
with ser:
while True:
try:
raw = ser.readline()
@@ -244,7 +256,7 @@ def show_logs(config, args, port):
run_miniterm(config, port)
return 0
if get_port_type(port) == "NETWORK" and "api" in config:
from esphome.api.client import run_logs
from esphome.components.api.client import run_logs
return run_logs(config, port)
if get_port_type(port) == "MQTT" and "mqtt" in config:
@@ -284,7 +296,6 @@ def command_vscode(args):
logging.disable(logging.INFO)
logging.disable(logging.WARNING)
CORE.config_path = args.configuration
vscode.read_config(args)
@@ -394,7 +405,7 @@ def command_update_all(args):
import click
success = {}
files = list_yaml_files(args.configuration[0])
files = list_yaml_files(args.configuration)
twidth = 60
def print_bar(middle_text):
@@ -408,7 +419,7 @@ def command_update_all(args):
print("-" * twidth)
print()
rc = run_external_process(
"esphome", "--dashboard", "run", "--no-logs", "--device", "OTA", f
"esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA"
)
if rc == 0:
print_bar("[{}] {}".format(color(Fore.BOLD_GREEN, "SUCCESS"), f))
@@ -472,62 +483,9 @@ def parse_args(argv):
metavar=("key", "value"),
)
# 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",
],
)
# on Python 3.9+ we can simply set exit_on_error=False in the constructor
def _raise(x):
raise argparse.ArgumentError(None, x)
compat_parser.error = _raise
try:
result, unparsed = compat_parser.parse_known_args(argv[1:])
last_option = len(argv) - len(unparsed) - 1 - len(result.configuration)
argv = argv[0:last_option] + [result.command] + result.configuration + unparsed
deprecated_argv_suggestion = argv
except argparse.ArgumentError:
# This is not an old-style command line, so we don't have to do anything.
deprecated_argv_suggestion = None
# And continue on with regular parsing
parser = argparse.ArgumentParser(
description=f"ESPHome v{const.__version__}", parents=[options_parser]
)
parser.set_defaults(deprecated_argv_suggestion=deprecated_argv_suggestion)
mqtt_options = argparse.ArgumentParser(add_help=False)
mqtt_options.add_argument("--topic", help="Manually set the MQTT topic.")
@@ -669,17 +627,91 @@ def parse_args(argv):
)
parser_vscode = subparsers.add_parser("vscode")
parser_vscode.add_argument(
"configuration", help="Your YAML configuration file.", nargs=1
)
parser_vscode.add_argument("configuration", help="Your YAML configuration file.")
parser_vscode.add_argument("--ace", action="store_true")
parser_update = subparsers.add_parser("update-all")
parser_update.add_argument(
"configuration", help="Your YAML configuration file directory.", nargs=1
"configuration", help="Your YAML configuration file directories.", nargs="+"
)
return parser.parse_args(argv[1:])
# 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, if parsing using the current format fails, construct an ad-hoc parser
# 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 retry with the normal parser (and raise
# a deprecation warning).
arguments = argv[1:]
# On Python 3.9+ we can simply set exit_on_error=False in the constructor
def _raise(x):
raise argparse.ArgumentError(None, x)
# First, try new-style parsing, but don't exit in case of failure
try:
# duplicate parser so that we can use the original one to raise errors later on
current_parser = argparse.ArgumentParser(add_help=False, parents=[parser])
current_parser.set_defaults(deprecated_argv_suggestion=None)
current_parser.error = _raise
return current_parser.parse_args(arguments)
except argparse.ArgumentError:
pass
# Second, try compat parsing and rearrange the command-line if it succeeds
# 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", action="store_true")
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",
],
)
try:
compat_parser.error = _raise
result, unparsed = compat_parser.parse_known_args(argv[1:])
last_option = len(arguments) - len(unparsed) - 1 - len(result.configuration)
unparsed = [
"--device" if arg in ("--upload-port", "--serial-port") else arg
for arg in unparsed
]
arguments = (
arguments[0:last_option]
+ [result.command]
+ result.configuration
+ unparsed
)
deprecated_argv_suggestion = arguments
except argparse.ArgumentError:
# old-style parsing failed, don't suggest any argument
deprecated_argv_suggestion = None
# Finally, run the new-style parser again with the possibly swapped arguments,
# and let it error out if the command is unparsable.
parser.set_defaults(deprecated_argv_suggestion=deprecated_argv_suggestion)
return parser.parse_args(arguments)
def run_esphome(argv):
@@ -693,7 +725,7 @@ def run_esphome(argv):
"and will be removed in the future. "
)
_LOGGER.warning("Please instead use:")
_LOGGER.warning(" esphome %s", " ".join(args.deprecated_argv_suggestion[1:]))
_LOGGER.warning(" esphome %s", " ".join(args.deprecated_argv_suggestion))
if sys.version_info < (3, 7, 0):
_LOGGER.error(
File diff suppressed because one or more lines are too long
-518
View File
@@ -1,518 +0,0 @@
from datetime import datetime
import functools
import logging
import socket
import threading
import time
# pylint: disable=unused-import
from typing import Optional # noqa
from google.protobuf import message # noqa
from esphome import const
import esphome.api.api_pb2 as pb
from esphome.const import CONF_PASSWORD, CONF_PORT
from esphome.core import EsphomeError
from esphome.helpers import resolve_ip_address, indent
from esphome.log import color, Fore
from esphome.util import safe_print
_LOGGER = logging.getLogger(__name__)
class APIConnectionError(EsphomeError):
pass
MESSAGE_TYPE_TO_PROTO = {
1: pb.HelloRequest,
2: pb.HelloResponse,
3: pb.ConnectRequest,
4: pb.ConnectResponse,
5: pb.DisconnectRequest,
6: pb.DisconnectResponse,
7: pb.PingRequest,
8: pb.PingResponse,
9: pb.DeviceInfoRequest,
10: pb.DeviceInfoResponse,
11: pb.ListEntitiesRequest,
12: pb.ListEntitiesBinarySensorResponse,
13: pb.ListEntitiesCoverResponse,
14: pb.ListEntitiesFanResponse,
15: pb.ListEntitiesLightResponse,
16: pb.ListEntitiesSensorResponse,
17: pb.ListEntitiesSwitchResponse,
18: pb.ListEntitiesTextSensorResponse,
19: pb.ListEntitiesDoneResponse,
20: pb.SubscribeStatesRequest,
21: pb.BinarySensorStateResponse,
22: pb.CoverStateResponse,
23: pb.FanStateResponse,
24: pb.LightStateResponse,
25: pb.SensorStateResponse,
26: pb.SwitchStateResponse,
27: pb.TextSensorStateResponse,
28: pb.SubscribeLogsRequest,
29: pb.SubscribeLogsResponse,
30: pb.CoverCommandRequest,
31: pb.FanCommandRequest,
32: pb.LightCommandRequest,
33: pb.SwitchCommandRequest,
34: pb.SubscribeServiceCallsRequest,
35: pb.ServiceCallResponse,
36: pb.GetTimeRequest,
37: pb.GetTimeResponse,
}
def _varuint_to_bytes(value):
if value <= 0x7F:
return bytes([value])
ret = bytes()
while value:
temp = value & 0x7F
value >>= 7
if value:
ret += bytes([temp | 0x80])
else:
ret += bytes([temp])
return ret
def _bytes_to_varuint(value):
result = 0
bitpos = 0
for val in value:
result |= (val & 0x7F) << bitpos
bitpos += 7
if (val & 0x80) == 0:
return result
return None
# pylint: disable=too-many-instance-attributes,not-callable
class APIClient(threading.Thread):
def __init__(self, address, port, password):
threading.Thread.__init__(self)
self._address = address # type: str
self._port = port # type: int
self._password = password # type: Optional[str]
self._socket = None # type: Optional[socket.socket]
self._socket_open_event = threading.Event()
self._socket_write_lock = threading.Lock()
self._connected = False
self._authenticated = False
self._message_handlers = []
self._keepalive = 5
self._ping_timer = None
self.on_disconnect = None
self.on_connect = None
self.on_login = None
self.auto_reconnect = False
self._running_event = threading.Event()
self._stop_event = threading.Event()
@property
def stopped(self):
return self._stop_event.is_set()
def _refresh_ping(self):
if self._ping_timer is not None:
self._ping_timer.cancel()
self._ping_timer = None
def func():
self._ping_timer = None
if self._connected:
try:
self.ping()
except APIConnectionError as err:
self._fatal_error(err)
else:
self._refresh_ping()
self._ping_timer = threading.Timer(self._keepalive, func)
self._ping_timer.start()
def _cancel_ping(self):
if self._ping_timer is not None:
self._ping_timer.cancel()
self._ping_timer = None
def _close_socket(self):
self._cancel_ping()
if self._socket is not None:
self._socket.close()
self._socket = None
self._socket_open_event.clear()
self._connected = False
self._authenticated = False
self._message_handlers = []
def stop(self, force=False):
if self.stopped:
raise ValueError
if self._connected and not force:
try:
self.disconnect()
except APIConnectionError:
pass
self._close_socket()
self._stop_event.set()
if not force:
self.join()
def connect(self):
if not self._running_event.wait(0.1):
raise APIConnectionError("You need to call start() first!")
if self._connected:
self.disconnect(on_disconnect=False)
try:
ip = resolve_ip_address(self._address)
except EsphomeError as err:
_LOGGER.warning(
"Error resolving IP address of %s. Is it connected to WiFi?",
self._address,
)
_LOGGER.warning(
"(If this error persists, please set a static IP address: "
"https://esphome.io/components/wifi.html#manual-ips)"
)
raise APIConnectionError(err) from err
_LOGGER.info("Connecting to %s:%s (%s)", self._address, self._port, ip)
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)
try:
self._socket.connect((ip, self._port))
except OSError as err:
err = APIConnectionError(f"Error connecting to {ip}: {err}")
self._fatal_error(err)
raise err
self._socket.settimeout(0.1)
self._socket_open_event.set()
hello = pb.HelloRequest()
hello.client_info = f"ESPHome v{const.__version__}"
try:
resp = self._send_message_await_response(hello, pb.HelloResponse)
except APIConnectionError as err:
self._fatal_error(err)
raise err
_LOGGER.debug(
"Successfully connected to %s ('%s' API=%s.%s)",
self._address,
resp.server_info,
resp.api_version_major,
resp.api_version_minor,
)
self._connected = True
self._refresh_ping()
if self.on_connect is not None:
self.on_connect()
def _check_connected(self):
if not self._connected:
err = APIConnectionError("Must be connected!")
self._fatal_error(err)
raise err
def login(self):
self._check_connected()
if self._authenticated:
raise APIConnectionError("Already logged in!")
connect = pb.ConnectRequest()
if self._password is not None:
connect.password = self._password
resp = self._send_message_await_response(connect, pb.ConnectResponse)
if resp.invalid_password:
raise APIConnectionError("Invalid password!")
self._authenticated = True
if self.on_login is not None:
self.on_login()
def _fatal_error(self, err):
was_connected = self._connected
self._close_socket()
if was_connected and self.on_disconnect is not None:
self.on_disconnect(err)
def _write(self, data): # type: (bytes) -> None
if self._socket is None:
raise APIConnectionError("Socket closed")
# _LOGGER.debug("Write: %s", format_bytes(data))
with self._socket_write_lock:
try:
self._socket.sendall(data)
except OSError as err:
err = APIConnectionError(f"Error while writing data: {err}")
self._fatal_error(err)
raise err
def _send_message(self, msg):
# type: (message.Message) -> None
for message_type, klass in MESSAGE_TYPE_TO_PROTO.items():
if isinstance(msg, klass):
break
else:
raise ValueError
encoded = msg.SerializeToString()
_LOGGER.debug("Sending %s:\n%s", type(msg), indent(str(msg)))
req = bytes([0])
req += _varuint_to_bytes(len(encoded))
req += _varuint_to_bytes(message_type)
req += encoded
self._write(req)
def _send_message_await_response_complex(
self, send_msg, do_append, do_stop, timeout=5
):
event = threading.Event()
responses = []
def on_message(resp):
if do_append(resp):
responses.append(resp)
if do_stop(resp):
event.set()
self._message_handlers.append(on_message)
self._send_message(send_msg)
ret = event.wait(timeout)
try:
self._message_handlers.remove(on_message)
except ValueError:
pass
if not ret:
raise APIConnectionError("Timeout while waiting for message response!")
return responses
def _send_message_await_response(self, send_msg, response_type, timeout=5):
def is_response(msg):
return isinstance(msg, response_type)
return self._send_message_await_response_complex(
send_msg, is_response, is_response, timeout
)[0]
def device_info(self):
self._check_connected()
return self._send_message_await_response(
pb.DeviceInfoRequest(), pb.DeviceInfoResponse
)
def ping(self):
self._check_connected()
return self._send_message_await_response(pb.PingRequest(), pb.PingResponse)
def disconnect(self, on_disconnect=True):
self._check_connected()
try:
self._send_message_await_response(
pb.DisconnectRequest(), pb.DisconnectResponse
)
except APIConnectionError:
pass
self._close_socket()
if self.on_disconnect is not None and on_disconnect:
self.on_disconnect(None)
def _check_authenticated(self):
if not self._authenticated:
raise APIConnectionError("Must login first!")
def subscribe_logs(self, on_log, log_level=7, dump_config=False):
self._check_authenticated()
def on_msg(msg):
if isinstance(msg, pb.SubscribeLogsResponse):
on_log(msg)
self._message_handlers.append(on_msg)
req = pb.SubscribeLogsRequest(dump_config=dump_config)
req.level = log_level
self._send_message(req)
def _recv(self, amount):
ret = bytes()
if amount == 0:
return ret
while len(ret) < amount:
if self.stopped:
raise APIConnectionError("Stopped!")
if not self._socket_open_event.is_set():
raise APIConnectionError("No socket!")
try:
val = self._socket.recv(amount - len(ret))
except AttributeError as err:
raise APIConnectionError("Socket was closed") from err
except socket.timeout:
continue
except OSError as err:
raise APIConnectionError(f"Error while receiving data: {err}") from err
ret += val
return ret
def _recv_varint(self):
raw = bytes()
while not raw or raw[-1] & 0x80:
raw += self._recv(1)
return _bytes_to_varuint(raw)
def _run_once(self):
if not self._socket_open_event.wait(0.1):
return
# Preamble
if self._recv(1)[0] != 0x00:
raise APIConnectionError("Invalid preamble")
length = self._recv_varint()
msg_type = self._recv_varint()
raw_msg = self._recv(length)
if msg_type not in MESSAGE_TYPE_TO_PROTO:
_LOGGER.debug("Skipping message type %s", msg_type)
return
msg = MESSAGE_TYPE_TO_PROTO[msg_type]()
msg.ParseFromString(raw_msg)
_LOGGER.debug("Got message: %s:\n%s", type(msg), indent(str(msg)))
for msg_handler in self._message_handlers[:]:
msg_handler(msg)
self._handle_internal_messages(msg)
def run(self):
self._running_event.set()
while not self.stopped:
try:
self._run_once()
except APIConnectionError as err:
if self.stopped:
break
if self._connected:
_LOGGER.error("Error while reading incoming messages: %s", err)
self._fatal_error(err)
self._running_event.clear()
def _handle_internal_messages(self, msg):
if isinstance(msg, pb.DisconnectRequest):
self._send_message(pb.DisconnectResponse())
if self._socket is not None:
self._socket.close()
self._socket = None
self._connected = False
if self.on_disconnect is not None:
self.on_disconnect(None)
elif isinstance(msg, pb.PingRequest):
self._send_message(pb.PingResponse())
elif isinstance(msg, pb.GetTimeRequest):
resp = pb.GetTimeResponse()
resp.epoch_seconds = int(time.time())
self._send_message(resp)
def run_logs(config, address):
conf = config["api"]
port = conf[CONF_PORT]
password = conf[CONF_PASSWORD]
_LOGGER.info("Starting log output from %s using esphome API", address)
cli = APIClient(address, port, password)
stopping = False
retry_timer = []
has_connects = []
def try_connect(err, tries=0):
if stopping:
return
if err:
_LOGGER.warning("Disconnected from API: %s", err)
while retry_timer:
retry_timer.pop(0).cancel()
error = None
try:
cli.connect()
cli.login()
except APIConnectionError as err2: # noqa
error = err2
if error is None:
_LOGGER.info("Successfully connected to %s", address)
return
wait_time = int(min(1.5 ** min(tries, 100), 30))
if not has_connects:
_LOGGER.warning(
"Initial connection failed. The ESP might not be connected "
"to WiFi yet (%s). Re-Trying in %s seconds",
error,
wait_time,
)
else:
_LOGGER.warning(
"Couldn't connect to API (%s). Trying to reconnect in %s seconds",
error,
wait_time,
)
timer = threading.Timer(
wait_time, functools.partial(try_connect, None, tries + 1)
)
timer.start()
retry_timer.append(timer)
def on_log(msg):
time_ = datetime.now().time().strftime("[%H:%M:%S]")
text = msg.message
if msg.send_failed:
text = color(
Fore.WHITE,
"(Message skipped because it was too big to fit in "
"TCP buffer - This is only cosmetic)",
)
safe_print(time_ + text)
def on_login():
try:
cli.subscribe_logs(on_log, dump_config=not has_connects)
has_connects.append(True)
except APIConnectionError:
cli.disconnect()
cli.on_disconnect = try_connect
cli.on_login = on_login
cli.start()
try:
try_connect(None)
while True:
time.sleep(1)
except KeyboardInterrupt:
stopping = True
cli.stop(True)
while retry_timer:
retry_timer.pop(0).cancel()
return 0
+1152
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -60,6 +60,7 @@ from esphome.cpp_types import ( # noqa
uint8,
uint16,
uint32,
uint64,
int32,
const_char_ptr,
NAN,
+1 -1
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...");
+4 -4
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*.
@@ -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,8 +42,9 @@ 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);
}
it.schedule_show();
}
void AdalightLightEffect::apply(light::AddressableLight &it, const Color &current_color) {
@@ -133,6 +134,7 @@ AdalightLightEffect::Frame AdalightLightEffect::parse_frame_(light::AddressableL
it[led].set(Color(led_data[0], led_data[1], led_data[2], white));
}
it.schedule_show();
return CONSUMED;
}
+80 -12
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
+6 -2
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
};
+8 -6
View File
@@ -7,7 +7,6 @@ from esphome.const import (
CONF_ID,
CONF_PIN,
DEVICE_CLASS_VOLTAGE,
ICON_EMPTY,
STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
)
@@ -16,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,
}
@@ -37,7 +36,10 @@ ADCSensor = adc_ns.class_(
CONFIG_SCHEMA = (
sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
)
.extend(
{
@@ -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]);
+3 -3
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)
+20 -14
View File
@@ -8,7 +8,6 @@ from esphome.const import (
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_POWER,
DEVICE_CLASS_VOLTAGE,
ICON_EMPTY,
STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
UNIT_AMPERE,
@@ -32,27 +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, STATE_CLASS_MEASUREMENT
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,
STATE_CLASS_MEASUREMENT,
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,
STATE_CLASS_MEASUREMENT,
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, STATE_CLASS_MEASUREMENT
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, STATE_CLASS_MEASUREMENT
unit_of_measurement=UNIT_WATT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
+14 -14
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();
}
}
+4 -2
View File
@@ -5,7 +5,6 @@ from esphome.const import (
CONF_GAIN,
CONF_MULTIPLEXER,
DEVICE_CLASS_VOLTAGE,
ICON_EMPTY,
STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
CONF_ID,
@@ -53,7 +52,10 @@ ADS1115Sensor = ads1115_ns.class_(
CONF_ADS1115_ID = "ads1115_id"
CONFIG_SCHEMA = (
sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
)
.extend(
{
+5 -5
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,8 @@ 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...");
+8 -11
View File
@@ -7,7 +7,6 @@ from esphome.const import (
CONF_TEMPERATURE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
ICON_EMPTY,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
@@ -23,18 +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,
STATE_CLASS_MEASUREMENT,
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,
STATE_CLASS_MEASUREMENT,
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
@@ -0,0 +1,23 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import esp32_ble_tracker
from esphome.const import CONF_ID
DEPENDENCIES = ["esp32_ble_tracker"]
CODEOWNERS = ["@jeromelaban"]
airthings_ble_ns = cg.esphome_ns.namespace("airthings_ble")
AirthingsListener = airthings_ble_ns.class_(
"AirthingsListener", esp32_ble_tracker.ESPBTDeviceListener
)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(AirthingsListener),
}
).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield esp32_ble_tracker.register_ble_device(var, config)
@@ -0,0 +1,33 @@
#include "airthings_listener.h"
#include "esphome/core/log.h"
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace airthings_ble {
static const char *TAG = "airthings_ble";
bool AirthingsListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
for (auto &it : device.get_manufacturer_datas()) {
if (it.uuid == esp32_ble_tracker::ESPBTUUID::from_uint32(0x0334)) {
if (it.data.size() < 4)
continue;
uint32_t sn = it.data[0];
sn |= ((uint32_t) it.data[1] << 8);
sn |= ((uint32_t) it.data[2] << 16);
sn |= ((uint32_t) it.data[3] << 24);
ESP_LOGD(TAG, "Found AirThings device Serial:%u (MAC: %s)", sn, device.address_str().c_str());
return true;
}
}
return false;
}
} // namespace airthings_ble
} // namespace esphome
#endif
@@ -0,0 +1,20 @@
#pragma once
#ifdef ARDUINO_ARCH_ESP32
#include "esphome/core/component.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include <BLEDevice.h>
namespace esphome {
namespace airthings_ble {
class AirthingsListener : public esp32_ble_tracker::ESPBTDeviceListener {
public:
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
};
} // namespace airthings_ble
} // namespace esphome
#endif
@@ -0,0 +1 @@
CODEOWNERS = ["@jeromelaban"]
@@ -0,0 +1,142 @@
#include "airthings_wave_plus.h"
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace airthings_wave_plus {
void AirthingsWavePlus::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, "Connected successfully!");
}
break;
}
case ESP_GATTC_DISCONNECT_EVT: {
ESP_LOGW(TAG, "Disconnected!");
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
this->handle = 0;
auto chr = this->parent()->get_characteristic(service_uuid, sensors_data_characteristic_uuid);
if (chr == nullptr) {
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid.to_string().c_str(),
sensors_data_characteristic_uuid.to_string().c_str());
break;
}
this->handle = chr->handle;
this->node_state = espbt::ClientState::Established;
request_read_values_();
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) {
read_sensors_(param->read.value, param->read.value_len);
}
break;
}
default:
break;
}
}
void AirthingsWavePlus::read_sensors_(uint8_t *raw_value, uint16_t value_len) {
auto value = (WavePlusReadings *) raw_value;
if (sizeof(WavePlusReadings) <= value_len) {
ESP_LOGD(TAG, "version = %d", value->version);
if (value->version == 1) {
ESP_LOGD(TAG, "ambient light = %d", value->ambientLight);
this->humidity_sensor_->publish_state(value->humidity / 2.0f);
if (is_valid_radon_value_(value->radon)) {
this->radon_sensor_->publish_state(value->radon);
}
if (is_valid_radon_value_(value->radon_lt)) {
this->radon_long_term_sensor_->publish_state(value->radon_lt);
}
this->temperature_sensor_->publish_state(value->temperature / 100.0f);
this->pressure_sensor_->publish_state(value->pressure / 50.0f);
if (is_valid_co2_value_(value->co2)) {
this->co2_sensor_->publish_state(value->co2);
}
if (is_valid_voc_value_(value->voc)) {
this->tvoc_sensor_->publish_state(value->voc);
}
// This instance must not stay connected
// so other clients can connect to it (e.g. the
// mobile app).
parent()->set_enabled(false);
} else {
ESP_LOGE(TAG, "Invalid payload version (%d != 1, newer version or not a Wave Plus?)", value->version);
}
}
}
bool AirthingsWavePlus::is_valid_radon_value_(short radon) { return 0 <= radon && radon <= 16383; }
bool AirthingsWavePlus::is_valid_voc_value_(short voc) { return 0 <= voc && voc <= 16383; }
bool AirthingsWavePlus::is_valid_co2_value_(short co2) { return 0 <= co2 && co2 <= 16383; }
void AirthingsWavePlus::loop() {}
void AirthingsWavePlus::update() {
if (this->node_state != espbt::ClientState::Established) {
if (!parent()->enabled) {
ESP_LOGW(TAG, "Reconnecting to device");
parent()->set_enabled(true);
parent()->connect();
} else {
ESP_LOGW(TAG, "Connection in progress");
}
}
}
void AirthingsWavePlus::request_read_values_() {
auto status =
esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle, ESP_GATT_AUTH_REQ_NONE);
if (status) {
ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
}
}
void AirthingsWavePlus::dump_config() {
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
LOG_SENSOR(" ", "Radon", this->radon_sensor_);
LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
LOG_SENSOR(" ", "CO2", this->co2_sensor_);
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_);
}
AirthingsWavePlus::AirthingsWavePlus() : PollingComponent(10000) {
auto service_bt = *BLEUUID::fromString(std::string("b42e1c08-ade7-11e4-89d3-123b93f75cba")).getNative();
auto characteristic_bt = *BLEUUID::fromString(std::string("b42e2a68-ade7-11e4-89d3-123b93f75cba")).getNative();
service_uuid = espbt::ESPBTUUID::from_uuid(service_bt);
sensors_data_characteristic_uuid = espbt::ESPBTUUID::from_uuid(characteristic_bt);
}
void AirthingsWavePlus::setup() {}
} // namespace airthings_wave_plus
} // namespace esphome
#endif // ARDUINO_ARCH_ESP32
@@ -0,0 +1,79 @@
#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"
#include "esphome/core/log.h"
#include <algorithm>
#include <iterator>
#ifdef ARDUINO_ARCH_ESP32
#include <esp_gattc_api.h>
#include <BLEDevice.h>
using namespace esphome::ble_client;
namespace esphome {
namespace airthings_wave_plus {
static const char *TAG = "airthings_wave_plus";
class AirthingsWavePlus : public PollingComponent, public BLEClientNode {
public:
AirthingsWavePlus();
void setup() override;
void dump_config() override;
void update() 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;
void set_temperature(sensor::Sensor *temperature) { temperature_sensor_ = temperature; }
void set_radon(sensor::Sensor *radon) { radon_sensor_ = radon; }
void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; }
void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; }
void set_co2(sensor::Sensor *co2) { co2_sensor_ = co2; }
void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; }
protected:
bool is_valid_radon_value_(short radon);
bool is_valid_voc_value_(short voc);
bool is_valid_co2_value_(short co2);
void read_sensors_(uint8_t *value, uint16_t value_len);
void request_read_values_();
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *radon_sensor_{nullptr};
sensor::Sensor *radon_long_term_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};
sensor::Sensor *pressure_sensor_{nullptr};
sensor::Sensor *co2_sensor_{nullptr};
sensor::Sensor *tvoc_sensor_{nullptr};
uint16_t handle;
espbt::ESPBTUUID service_uuid;
espbt::ESPBTUUID sensors_data_characteristic_uuid;
struct WavePlusReadings {
uint8_t version;
uint8_t humidity;
uint8_t ambientLight;
uint8_t unused01;
uint16_t radon;
uint16_t radon_lt;
uint16_t temperature;
uint16_t pressure;
uint16_t co2;
uint16_t voc;
};
};
} // namespace airthings_wave_plus
} // namespace esphome
#endif // ARDUINO_ARCH_ESP32
@@ -0,0 +1,116 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, ble_client
from esphome.const import (
DEVICE_CLASS_CARBON_DIOXIDE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_PRESSURE,
STATE_CLASS_MEASUREMENT,
UNIT_PERCENT,
UNIT_CELSIUS,
UNIT_HECTOPASCAL,
ICON_RADIOACTIVE,
CONF_ID,
CONF_RADON,
CONF_RADON_LONG_TERM,
CONF_HUMIDITY,
CONF_TVOC,
CONF_CO2,
CONF_PRESSURE,
CONF_TEMPERATURE,
UNIT_BECQUEREL_PER_CUBIC_METER,
UNIT_PARTS_PER_MILLION,
UNIT_PARTS_PER_BILLION,
ICON_RADIATOR,
)
DEPENDENCIES = ["ble_client"]
airthings_wave_plus_ns = cg.esphome_ns.namespace("airthings_wave_plus")
AirthingsWavePlus = airthings_wave_plus_ns.class_(
"AirthingsWavePlus", cg.PollingComponent, ble_client.BLEClientNode
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(AirthingsWavePlus),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
accuracy_decimals=0,
),
cv.Optional(CONF_RADON): sensor.sensor_schema(
unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
icon=ICON_RADIOACTIVE,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_RADON_LONG_TERM): sensor.sensor_schema(
unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
icon=ICON_RADIOACTIVE,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
unit_of_measurement=UNIT_HECTOPASCAL,
accuracy_decimals=1,
device_class=DEVICE_CLASS_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CO2): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION,
accuracy_decimals=0,
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TVOC): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_BILLION,
icon=ICON_RADIATOR,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
.extend(cv.polling_component_schema("5mins"))
.extend(ble_client.BLE_CLIENT_SCHEMA)
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await ble_client.register_ble_node(var, config)
if CONF_HUMIDITY in config:
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
cg.add(var.set_humidity(sens))
if CONF_RADON in config:
sens = await sensor.new_sensor(config[CONF_RADON])
cg.add(var.set_radon(sens))
if CONF_RADON_LONG_TERM in config:
sens = await sensor.new_sensor(config[CONF_RADON_LONG_TERM])
cg.add(var.set_radon_long_term(sens))
if CONF_TEMPERATURE in config:
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
cg.add(var.set_temperature(sens))
if CONF_PRESSURE in config:
sens = await sensor.new_sensor(config[CONF_PRESSURE])
cg.add(var.set_pressure(sens))
if CONF_CO2 in config:
sens = await sensor.new_sensor(config[CONF_CO2])
cg.add(var.set_co2(sens))
if CONF_TVOC in config:
sens = await sensor.new_sensor(config[CONF_TVOC])
cg.add(var.set_tvoc(sens))
+1 -1
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) {
+8 -11
View File
@@ -9,7 +9,6 @@ from esphome.const import (
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
ICON_EMPTY,
UNIT_PERCENT,
)
@@ -25,18 +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,
STATE_CLASS_MEASUREMENT,
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,
STATE_CLASS_MEASUREMENT,
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
+115
View File
@@ -0,0 +1,115 @@
#include "am43.h"
#include "esphome/core/log.h"
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace am43 {
static const char *TAG = "am43";
void Am43::dump_config() {
ESP_LOGCONFIG(TAG, "AM43");
LOG_SENSOR(" ", "Battery", this->battery_);
LOG_SENSOR(" ", "Illuminance", this->illuminance_);
}
void Am43::setup() {
this->encoder_ = new Am43Encoder();
this->decoder_ = new Am43Decoder();
this->logged_in_ = false;
this->last_battery_update_ = 0;
this->current_sensor_ = 0;
}
void Am43::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: {
this->logged_in_ = false;
break;
}
case ESP_GATTC_DISCONNECT_EVT: {
this->logged_in_ = false;
this->node_state = espbt::ClientState::Idle;
if (this->battery_ != nullptr)
this->battery_->publish_state(NAN);
if (this->illuminance_ != nullptr)
this->illuminance_->publish_state(NAN);
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
auto chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID);
if (chr == nullptr) {
if (this->parent_->get_characteristic(AM43_TUYA_SERVICE_UUID, AM43_TUYA_CHARACTERISTIC_UUID) != nullptr) {
ESP_LOGE(TAG, "[%s] Detected a Tuya AM43 which is not supported, sorry.",
this->parent_->address_str().c_str());
} else {
ESP_LOGE(TAG, "[%s] No control service found at device, not an AM43..?",
this->parent_->address_str().c_str());
}
break;
}
this->char_handle_ = chr->handle;
break;
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
this->node_state = espbt::ClientState::Established;
this->update();
break;
}
case ESP_GATTC_NOTIFY_EVT: {
if (param->notify.handle != this->char_handle_)
break;
this->decoder_->decode(param->notify.value, param->notify.value_len);
if (this->battery_ != nullptr && this->decoder_->has_battery_level() &&
millis() - this->last_battery_update_ > 10000) {
this->battery_->publish_state(this->decoder_->battery_level_);
this->last_battery_update_ = millis();
}
if (this->illuminance_ != nullptr && this->decoder_->has_light_level()) {
this->illuminance_->publish_state(this->decoder_->light_level_);
}
if (this->current_sensor_ > 0) {
if (this->illuminance_ != nullptr) {
auto packet = this->encoder_->get_light_level_request();
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
packet->length, packet->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_sensor_ = 0;
}
break;
}
default:
break;
}
}
void Am43::update() {
if (this->node_state != espbt::ClientState::Established) {
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->parent_->address_str().c_str());
return;
}
if (this->current_sensor_ == 0) {
if (this->battery_ != nullptr) {
auto packet = this->encoder_->get_battery_level_request();
auto status =
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
packet->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_sensor_++;
}
}
} // namespace am43
} // namespace esphome
#endif
+45
View File
@@ -0,0 +1,45 @@
#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"
#include "esphome/components/am43/am43_base.h"
#ifdef ARDUINO_ARCH_ESP32
#include <esp_gattc_api.h>
namespace esphome {
namespace am43 {
namespace espbt = esphome::esp32_ble_tracker;
class Am43 : public esphome::ble_client::BLEClientNode, public PollingComponent {
public:
void setup() 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_battery(sensor::Sensor *battery) { battery_ = battery; }
void set_illuminance(sensor::Sensor *illuminance) { illuminance_ = illuminance; }
protected:
uint16_t char_handle_;
Am43Encoder *encoder_;
Am43Decoder *decoder_;
bool logged_in_;
sensor::Sensor *battery_{nullptr};
sensor::Sensor *illuminance_{nullptr};
uint8_t current_sensor_;
// The AM43 often gets into a state where it spams loads of battery update
// notifications. Here we will limit to no more than every 10s.
uint8_t last_battery_update_;
};
} // namespace am43
} // namespace esphome
#endif
+142
View File
@@ -0,0 +1,142 @@
#include "am43_base.h"
namespace esphome {
namespace am43 {
const uint8_t START_PACKET[5] = {0x00, 0xff, 0x00, 0x00, 0x9a};
std::string pkt_to_hex(const uint8_t *data, uint16_t len) {
char buf[64];
memset(buf, 0, 64);
for (int i = 0; i < len; i++)
sprintf(&buf[i * 2], "%02x", data[i]);
std::string ret = buf;
return ret;
}
Am43Packet *Am43Encoder::get_battery_level_request() {
uint8_t data = 0x1;
return this->encode_(0xA2, &data, 1);
}
Am43Packet *Am43Encoder::get_light_level_request() {
uint8_t data = 0x1;
return this->encode_(0xAA, &data, 1);
}
Am43Packet *Am43Encoder::get_position_request() {
uint8_t data = 0x1;
return this->encode_(CMD_GET_POSITION, &data, 1);
}
Am43Packet *Am43Encoder::get_send_pin_request(uint16_t pin) {
uint8_t data[2];
data[0] = (pin & 0xFF00) >> 8;
data[1] = pin & 0xFF;
return this->encode_(CMD_SEND_PIN, data, 2);
}
Am43Packet *Am43Encoder::get_open_request() {
uint8_t data = 0xDD;
return this->encode_(CMD_SET_STATE, &data, 1);
}
Am43Packet *Am43Encoder::get_close_request() {
uint8_t data = 0xEE;
return this->encode_(CMD_SET_STATE, &data, 1);
}
Am43Packet *Am43Encoder::get_stop_request() {
uint8_t data = 0xCC;
return this->encode_(CMD_SET_STATE, &data, 1);
}
Am43Packet *Am43Encoder::get_set_position_request(uint8_t position) {
return this->encode_(CMD_SET_POSITION, &position, 1);
}
void Am43Encoder::checksum_() {
uint8_t checksum = 0;
int i = 0;
for (i = 0; i < this->packet_.length; i++)
checksum = checksum ^ this->packet_.data[i];
this->packet_.data[i] = checksum ^ 0xff;
this->packet_.length++;
}
Am43Packet *Am43Encoder::encode_(uint8_t command, uint8_t *data, uint8_t length) {
memcpy(this->packet_.data, START_PACKET, 5);
this->packet_.data[5] = command;
this->packet_.data[6] = length;
memcpy(&this->packet_.data[7], data, length);
this->packet_.length = length + 7;
this->checksum_();
ESP_LOGV("am43", "ENC(%d): 0x%s", packet_.length, pkt_to_hex(packet_.data, packet_.length).c_str());
return &this->packet_;
}
#define VERIFY_MIN_LENGTH(x) \
if (length < (x)) \
return;
void Am43Decoder::decode(const uint8_t *data, uint16_t length) {
this->has_battery_level_ = false;
this->has_light_level_ = false;
this->has_set_position_response_ = false;
this->has_set_state_response_ = false;
this->has_position_ = false;
this->has_pin_response_ = false;
ESP_LOGV("am43", "DEC(%d): 0x%s", length, pkt_to_hex(data, length).c_str());
if (length < 2 || data[0] != 0x9a)
return;
switch (data[1]) {
case CMD_GET_BATTERY_LEVEL: {
VERIFY_MIN_LENGTH(8);
this->battery_level_ = data[7];
this->has_battery_level_ = true;
break;
}
case CMD_GET_LIGHT_LEVEL: {
VERIFY_MIN_LENGTH(5);
this->light_level_ = 100 * ((float) data[4] / 9);
this->has_light_level_ = true;
break;
}
case CMD_GET_POSITION: {
VERIFY_MIN_LENGTH(6);
this->position_ = data[5];
this->has_position_ = true;
break;
}
case CMD_NOTIFY_POSITION: {
VERIFY_MIN_LENGTH(5);
this->position_ = data[4];
this->has_position_ = true;
break;
}
case CMD_SEND_PIN: {
VERIFY_MIN_LENGTH(4);
this->pin_ok_ = data[3] == RESPONSE_ACK;
this->has_pin_response_ = true;
break;
}
case CMD_SET_POSITION: {
VERIFY_MIN_LENGTH(4);
this->set_position_ok_ = data[3] == RESPONSE_ACK;
this->has_set_position_response_ = true;
break;
}
case CMD_SET_STATE: {
VERIFY_MIN_LENGTH(4);
this->set_state_ok_ = data[3] == RESPONSE_ACK;
this->has_set_state_response_ = true;
break;
}
default:
break;
}
};
} // namespace am43
} // namespace esphome
+78
View File
@@ -0,0 +1,78 @@
#pragma once
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace am43 {
static const uint16_t AM43_SERVICE_UUID = 0xFE50;
static const uint16_t AM43_CHARACTERISTIC_UUID = 0xFE51;
//
// Tuya identifiers, only to detect and warn users as they are incompatible.
static const uint16_t AM43_TUYA_SERVICE_UUID = 0x1910;
static const uint16_t AM43_TUYA_CHARACTERISTIC_UUID = 0x2b11;
struct Am43Packet {
uint8_t length;
uint8_t data[24];
};
static const uint8_t CMD_GET_BATTERY_LEVEL = 0xA2;
static const uint8_t CMD_GET_LIGHT_LEVEL = 0xAA;
static const uint8_t CMD_GET_POSITION = 0xA7;
static const uint8_t CMD_SEND_PIN = 0x17;
static const uint8_t CMD_SET_STATE = 0x0A;
static const uint8_t CMD_SET_POSITION = 0x0D;
static const uint8_t CMD_NOTIFY_POSITION = 0xA1;
static const uint8_t RESPONSE_ACK = 0x5A;
static const uint8_t RESPONSE_NACK = 0xA5;
class Am43Encoder {
public:
Am43Packet *get_battery_level_request();
Am43Packet *get_light_level_request();
Am43Packet *get_position_request();
Am43Packet *get_send_pin_request(uint16_t pin);
Am43Packet *get_open_request();
Am43Packet *get_close_request();
Am43Packet *get_stop_request();
Am43Packet *get_set_position_request(uint8_t position);
protected:
void checksum_();
Am43Packet *encode_(uint8_t command, uint8_t *data, uint8_t length);
Am43Packet packet_;
};
class Am43Decoder {
public:
void decode(const uint8_t *data, uint16_t length);
bool has_battery_level() { return this->has_battery_level_; }
bool has_light_level() { return this->has_light_level_; }
bool has_set_position_response() { return this->has_set_position_response_; }
bool has_set_state_response() { return this->has_set_state_response_; }
bool has_position() { return this->has_position_; }
bool has_pin_response() { return this->has_pin_response_; }
union {
uint8_t position_;
uint8_t battery_level_;
float light_level_;
uint8_t set_position_ok_;
uint8_t set_state_ok_;
uint8_t pin_ok_;
};
protected:
bool has_battery_level_;
bool has_light_level_;
bool has_set_position_response_;
bool has_set_state_response_;
bool has_position_;
bool has_pin_response_;
};
} // namespace am43
} // namespace esphome
+36
View File
@@ -0,0 +1,36 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import cover, ble_client
from esphome.const import CONF_ID, CONF_PIN
CODEOWNERS = ["@buxtronix"]
DEPENDENCIES = ["ble_client"]
AUTO_LOAD = ["am43"]
CONF_INVERT_POSITION = "invert_position"
am43_ns = cg.esphome_ns.namespace("am43")
Am43Component = am43_ns.class_(
"Am43Component", cover.Cover, ble_client.BLEClientNode, cg.Component
)
CONFIG_SCHEMA = (
cover.COVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(Am43Component),
cv.Optional(CONF_PIN, default=8888): cv.int_range(min=0, max=0xFFFF),
cv.Optional(CONF_INVERT_POSITION, default=False): cv.boolean,
}
)
.extend(ble_client.BLE_CLIENT_SCHEMA)
.extend(cv.COMPONENT_SCHEMA)
)
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_pin(config[CONF_PIN]))
cg.add(var.set_invert_position(config[CONF_INVERT_POSITION]))
yield cg.register_component(var, config)
yield cover.register_cover(var, config)
yield ble_client.register_ble_node(var, config)
@@ -0,0 +1,149 @@
#include "am43_cover.h"
#include "esphome/core/log.h"
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace am43 {
static const char *TAG = "am43_cover";
using namespace esphome::cover;
void Am43Component::dump_config() {
LOG_COVER("", "AM43 Cover", this);
ESP_LOGCONFIG(TAG, " Device Pin: %d", this->pin_);
ESP_LOGCONFIG(TAG, " Invert Position: %d", (int) this->invert_position_);
}
void Am43Component::setup() {
this->position = COVER_OPEN;
this->encoder_ = new Am43Encoder();
this->decoder_ = new Am43Decoder();
this->logged_in_ = false;
}
void Am43Component::loop() {
if (this->node_state == espbt::ClientState::Established && !this->logged_in_) {
auto packet = this->encoder_->get_send_pin_request(this->pin_);
auto status =
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
ESP_LOGI(TAG, "[%s] Logging into AM43", this->get_name().c_str());
if (status)
ESP_LOGW(TAG, "[%s] Error writing set_pin to device, error = %d", this->get_name().c_str(), status);
else
this->logged_in_ = true;
}
}
CoverTraits Am43Component::get_traits() {
auto traits = CoverTraits();
traits.set_supports_position(true);
traits.set_supports_tilt(false);
traits.set_is_assumed_state(false);
return traits;
}
void Am43Component::control(const CoverCall &call) {
if (this->node_state != espbt::ClientState::Established) {
ESP_LOGW(TAG, "[%s] Cannot send cover control, not connected", this->get_name().c_str());
return;
}
if (call.get_stop()) {
auto packet = this->encoder_->get_stop_request();
auto status =
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status)
ESP_LOGW(TAG, "[%s] Error writing stop command to device, error = %d", this->get_name().c_str(), status);
}
if (call.get_position().has_value()) {
auto pos = *call.get_position();
if (this->invert_position_)
pos = 1 - pos;
auto packet = this->encoder_->get_set_position_request(100 - (uint8_t)(pos * 100));
auto status =
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status)
ESP_LOGW(TAG, "[%s] Error writing set_position command to device, error = %d", this->get_name().c_str(), status);
}
}
void Am43Component::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->logged_in_ = false;
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
auto chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID);
if (chr == nullptr) {
if (this->parent_->get_characteristic(AM43_TUYA_SERVICE_UUID, AM43_TUYA_CHARACTERISTIC_UUID) != nullptr) {
ESP_LOGE(TAG, "[%s] Detected a Tuya AM43 which is not supported, sorry.", this->get_name().c_str());
} else {
ESP_LOGE(TAG, "[%s] No control service found at device, not an AM43..?", 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;
break;
}
case ESP_GATTC_NOTIFY_EVT: {
if (param->notify.handle != this->char_handle_)
break;
this->decoder_->decode(param->notify.value, param->notify.value_len);
if (this->decoder_->has_position()) {
this->position = ((float) this->decoder_->position_ / 100.0);
if (!this->invert_position_)
this->position = 1 - this->position;
if (this->position > 0.97)
this->position = 1.0;
if (this->position < 0.02)
this->position = 0.0;
this->publish_state();
}
if (this->decoder_->has_pin_response()) {
if (this->decoder_->pin_ok_) {
ESP_LOGI(TAG, "[%s] AM43 pin accepted.", this->get_name().c_str());
auto packet = this->encoder_->get_position_request();
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP,
ESP_GATT_AUTH_REQ_NONE);
if (status)
ESP_LOGW(TAG, "[%s] Error writing set_position to device, error = %d", this->get_name().c_str(), status);
} else {
ESP_LOGW(TAG, "[%s] AM43 pin rejected!", this->get_name().c_str());
}
}
if (this->decoder_->has_set_position_response() && !this->decoder_->set_position_ok_)
ESP_LOGW(TAG, "[%s] Got nack after sending set_position. Bad pin?", this->get_name().c_str());
if (this->decoder_->has_set_state_response() && !this->decoder_->set_state_ok_)
ESP_LOGW(TAG, "[%s] Got nack after sending set_state. Bad pin?", this->get_name().c_str());
break;
}
default:
break;
}
}
} // namespace am43
} // namespace esphome
#endif
@@ -0,0 +1,45 @@
#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/cover/cover.h"
#include "esphome/components/am43/am43_base.h"
#ifdef ARDUINO_ARCH_ESP32
#include <esp_gattc_api.h>
namespace esphome {
namespace am43 {
namespace espbt = esphome::esp32_ble_tracker;
class Am43Component : public cover::Cover, public esphome::ble_client::BLEClientNode, public Component {
public:
void setup() 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;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
cover::CoverTraits get_traits() override;
void set_pin(uint16_t pin) { this->pin_ = pin; }
void set_invert_position(bool invert_position) { this->invert_position_ = invert_position; }
protected:
void control(const cover::CoverCall &call) override;
uint16_t char_handle_;
uint16_t pin_;
bool invert_position_;
Am43Encoder *encoder_;
Am43Decoder *decoder_;
bool logged_in_;
float position_;
};
} // namespace am43
} // namespace esphome
#endif
+46
View File
@@ -0,0 +1,46 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, ble_client
from esphome.const import (
CONF_ID,
CONF_BATTERY_LEVEL,
ICON_BATTERY,
CONF_ILLUMINANCE,
ICON_BRIGHTNESS_5,
UNIT_PERCENT,
)
CODEOWNERS = ["@buxtronix"]
am43_ns = cg.esphome_ns.namespace("am43")
Am43 = am43_ns.class_("Am43", ble_client.BLEClientNode, cg.PollingComponent)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(Am43),
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
UNIT_PERCENT, ICON_BATTERY, 0
),
cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(
UNIT_PERCENT, ICON_BRIGHTNESS_5, 0
),
}
)
.extend(ble_client.BLE_CLIENT_SCHEMA)
.extend(cv.polling_component_schema("120s"))
)
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield ble_client.register_ble_node(var, config)
if CONF_BATTERY_LEVEL in config:
sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL])
cg.add(var.set_battery(sens))
if CONF_ILLUMINANCE in config:
sens = yield sensor.new_sensor(config[CONF_ILLUMINANCE])
cg.add(var.set_illuminance(sens))
+150
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
+52
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
+137
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
+80
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
+36
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]))
+2 -2
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; \
}
+4 -2
View File
@@ -3,7 +3,6 @@ import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import (
CONF_TYPE,
DEVICE_CLASS_EMPTY,
STATE_CLASS_MEASUREMENT,
UNIT_PERCENT,
ICON_LIGHTBULB,
@@ -21,7 +20,10 @@ TYPES = {
}
CONFIG_SCHEMA = sensor.sensor_schema(
UNIT_PERCENT, ICON_LIGHTBULB, 1, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT
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),
+34 -1
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,6 +82,11 @@ CONFIG_SCHEMA = cv.Schema(
),
}
),
cv.Optional(CONF_ENCRYPTION): cv.Schema(
{
cv.Required(CONF_KEY): validate_encryption_key,
}
),
}
).extend(cv.COMPONENT_SCHEMA)
@@ -92,6 +116,15 @@ async def to_code(config):
cg.add(var.register_user_service(trigger))
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")
cg.add_library("esphome/noise-c", "0.1.1")
else:
cg.add_define("USE_API_PLAINTEXT")
cg.add_define("USE_API")
cg.add_global(api_ns.using)
+146 -19
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) {}
}
@@ -212,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;
@@ -241,6 +244,7 @@ message ListEntitiesCoverResponse {
bool supports_position = 6;
bool supports_tilt = 7;
string device_class = 8;
bool disabled_by_default = 9;
}
enum LegacyCoverState {
@@ -308,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;
@@ -351,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;
@@ -361,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;
@@ -378,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 {
@@ -396,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;
@@ -404,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;
@@ -416,6 +448,13 @@ message LightCommandRequest {
enum SensorStateClass {
STATE_CLASS_NONE = 0;
STATE_CLASS_MEASUREMENT = 1;
STATE_CLASS_TOTAL_INCREASING = 2;
}
enum SensorLastResetType {
LAST_RESET_NONE = 0;
LAST_RESET_NEVER = 1;
LAST_RESET_AUTO = 2;
}
message ListEntitiesSensorResponse {
@@ -434,6 +473,9 @@ message ListEntitiesSensorResponse {
bool force_update = 8;
string device_class = 9;
SensorStateClass state_class = 10;
// Last reset type removed in 2021.9.0
SensorLastResetType legacy_last_reset_type = 11;
bool disabled_by_default = 12;
}
message SensorStateResponse {
option (id) = 25;
@@ -461,6 +503,7 @@ message ListEntitiesSwitchResponse {
string icon = 5;
bool assumed_state = 6;
bool disabled_by_default = 7;
}
message SwitchStateResponse {
option (id) = 26;
@@ -493,6 +536,7 @@ message ListEntitiesTextSensorResponse {
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
}
message TextSensorStateResponse {
option (id) = 27;
@@ -513,9 +557,10 @@ enum LogLevel {
LOG_LEVEL_ERROR = 1;
LOG_LEVEL_WARN = 2;
LOG_LEVEL_INFO = 3;
LOG_LEVEL_DEBUG = 4;
LOG_LEVEL_VERBOSE = 5;
LOG_LEVEL_VERY_VERBOSE = 6;
LOG_LEVEL_CONFIG = 4;
LOG_LEVEL_DEBUG = 5;
LOG_LEVEL_VERBOSE = 6;
LOG_LEVEL_VERY_VERBOSE = 7;
}
message SubscribeLogsRequest {
option (id) = 28;
@@ -530,7 +575,6 @@ message SubscribeLogsResponse {
option (no_delay) = false;
LogLevel level = 1;
string tag = 2;
string message = 3;
bool send_failed = 4;
}
@@ -652,6 +696,7 @@ message ListEntitiesCameraResponse {
fixed32 key = 2;
string name = 3;
string unique_id = 4;
bool disabled_by_default = 5;
}
message CameraImageResponse {
@@ -710,13 +755,14 @@ enum ClimateAction {
CLIMATE_ACTION_FAN = 6;
}
enum ClimatePreset {
CLIMATE_PRESET_ECO = 0;
CLIMATE_PRESET_AWAY = 1;
CLIMATE_PRESET_BOOST = 2;
CLIMATE_PRESET_COMFORT = 3;
CLIMATE_PRESET_HOME = 4;
CLIMATE_PRESET_SLEEP = 5;
CLIMATE_PRESET_ACTIVITY = 6;
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;
@@ -734,13 +780,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;
@@ -754,7 +803,8 @@ 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;
@@ -777,8 +827,9 @@ 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;
@@ -790,3 +841,79 @@ message ClimateCommandRequest {
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;
}
+274 -196
View File
@@ -2,6 +2,7 @@
#include "esphome/core/log.h"
#include "esphome/core/util.h"
#include "esphome/core/version.h"
#include <cerrno>
#ifdef USE_DEEP_SLEEP
#include "esphome/components/deep_sleep/deep_sleep_component.h"
@@ -16,147 +17,148 @@
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();
#if defined(USE_API_PLAINTEXT)
helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
#elif defined(USE_API_NOISE)
helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())};
#else
#error "No frame helper defined"
#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) {
on_fatal_error();
ESP_LOGW(TAG, "%s: Helper init failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
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();
}
}
void APIConnection::disconnect_client() {
this->client_->close();
this->remove_ = true;
client_info_ = helper_->getpeername();
helper_->set_log_info(client_info_);
}
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();
ESP_LOGW(TAG, "%s: Network unavailable, disconnecting", client_info_.c_str());
return;
}
if (this->client_->disconnected()) {
// failsafe for disconnect logic
this->on_disconnect_();
if (this->next_close_) {
// requested a disconnect
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: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
return;
}
ReadPacketBuffer buffer;
err = helper_->read_packet(&buffer);
if (err == APIError::WOULD_BLOCK) {
// pass
} else if (err != APIError::OK) {
on_fatal_error();
if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) {
ESP_LOGW(TAG, "%s: Connection reset", client_info_.c_str());
} else {
ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
}
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) {
ESP_LOGW(TAG, "'%s' didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str());
this->disconnect_client();
if (now - this->last_traffic_ > (keepalive * 5) / 2) {
on_fatal_error();
ESP_LOGW(TAG, "%s didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str());
}
} 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
if (state_subs_at_ != -1) {
const auto &subs = this->parent_->get_state_subs();
if (state_subs_at_ >= subs.size()) {
state_subs_at_ = -1;
} else {
auto &it = subs[state_subs_at_];
SubscribeHomeAssistantStateResponse resp;
resp.entity_id = it.entity_id;
resp.attribute = it.attribute.value();
if (this->send_subscribe_home_assistant_state_response(resp)) {
state_subs_at_++;
}
}
}
}
std::string get_default_unique_id(const std::string &component_type, Nameable *nameable) {
return App.get_name() + component_type + nameable->get_object_id();
}
DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) {
// remote initiated disconnect_client
// don't close yet, we still need to send the disconnect response
// close will happen on next loop
ESP_LOGD(TAG, "%s requested disconnected", client_info_.c_str());
this->next_close_ = true;
DisconnectResponse resp;
return resp;
}
void APIConnection::on_disconnect_response(const DisconnectResponse &value) {
// pass
}
#ifdef USE_BINARY_SENSOR
bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state) {
if (!this->state_subscription_)
@@ -176,6 +178,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 +210,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) {
@@ -239,6 +243,9 @@ void APIConnection::cover_command(const CoverCommandRequest &msg) {
#endif
#ifdef USE_FAN
// Shut-up about usage of deprecated speed_level_to_enum/speed_enum_to_level functions for a bit.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
bool APIConnection::send_fan_state(fan::FanState *fan) {
if (!this->state_subscription_)
return false;
@@ -268,6 +275,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) {
@@ -292,6 +300,7 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
call.set_direction(static_cast<fan::FanDirection>(msg.direction));
call.perform();
}
#pragma GCC diagnostic pop
#endif
#ifdef USE_LIGHT
@@ -301,21 +310,21 @@ 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.brightness = values.get_brightness();
if (traits.get_supports_rgb()) {
resp.red = values.get_red();
resp.green = values.get_green();
resp.blue = values.get_blue();
}
if (traits.get_supports_rgb_white_value())
resp.white = values.get_white();
if (traits.get_supports_color_temperature())
resp.color_temperature = values.get_color_temperature();
resp.color_mode = static_cast<enums::ColorMode>(color_mode);
resp.brightness = values.get_brightness();
resp.color_brightness = values.get_color_brightness();
resp.red = values.get_red();
resp.green = values.get_green();
resp.blue = values.get_blue();
resp.white = values.get_white();
resp.color_temperature = values.get_color_temperature();
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 +336,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 +371,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 +384,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,7 +422,8 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
msg.accuracy_decimals = sensor->get_accuracy_decimals();
msg.force_update = sensor->get_force_update();
msg.device_class = sensor->get_device_class();
msg.state_class = static_cast<enums::SensorStateClass>(sensor->state_class);
msg.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class());
msg.disabled_by_default = sensor->is_disabled_by_default();
return this->send_list_entities_sensor_response(msg);
}
@@ -419,6 +447,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) {
@@ -453,6 +482,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
@@ -475,14 +505,14 @@ 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() && 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())
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())
@@ -496,42 +526,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, climate::CLIMATE_MODE_HEAT_COOL}) {
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 const &custom_fan_mode : traits.get_supported_custom_fan_modes()) {
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 : {climate::CLIMATE_PRESET_ECO, climate::CLIMATE_PRESET_AWAY, climate::CLIMATE_PRESET_BOOST,
climate::CLIMATE_PRESET_COMFORT, climate::CLIMATE_PRESET_HOME, climate::CLIMATE_PRESET_SLEEP,
climate::CLIMATE_PRESET_ACTIVITY}) {
if (traits.supports_preset(preset))
msg.supported_presets.push_back(static_cast<enums::ClimatePreset>(preset));
}
for (auto const &custom_preset : traits.get_supported_custom_presets()) {
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 : {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 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) {
@@ -548,8 +567,8 @@ 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)
@@ -564,6 +583,79 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
}
#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_)
@@ -578,6 +670,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) {
@@ -606,30 +699,20 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin
auto buffer = this->create_buffer();
// LogLevel level = 1;
buffer.encode_uint32(1, static_cast<uint32_t>(level));
// string tag = 2;
// buffer.encode_string(2, tag, strlen(tag));
// string message = 3;
buffer.encode_string(3, line, strlen(line));
// SubscribeLogsResponse - 29
bool success = this->send_buffer(buffer, 29);
if (!success) {
buffer = this->create_buffer();
// bool send_failed = 4;
buffer.encode_bool(4, true);
return this->send_buffer(buffer, 29);
} else {
return true;
}
return this->send_buffer(buffer, 29);
}
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;
@@ -641,7 +724,7 @@ ConnectResponse APIConnection::connect(const ConnectRequest &msg) {
// bool invalid_password = 1;
resp.invalid_password = !correct;
if (correct) {
ESP_LOGD(TAG, "Client '%s' connected successfully!", this->client_info_.c_str());
ESP_LOGD(TAG, "%s: Connected successfully", this->client_info_.c_str());
this->connection_state_ = ConnectionState::AUTHENTICATED;
#ifdef USE_HOMEASSISTANT_TIME
@@ -659,9 +742,7 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
resp.mac_address = get_mac_address_pretty();
resp.esphome_version = ESPHOME_VERSION;
resp.compilation_time = App.get_compilation_time();
#ifdef ARDUINO_BOARD
resp.model = ARDUINO_BOARD;
#endif
resp.model = ESPHOME_BOARD;
#ifdef USE_DEEP_SLEEP
resp.has_deep_sleep = deep_sleep::global_has_deep_sleep;
#endif
@@ -689,30 +770,20 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
}
}
void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) {
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;
}
}
state_subs_at_ = 0;
}
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) {
if (this->remove_)
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()) {
if (!this->helper_->can_write_without_blocking()) {
delay(0);
if (needed_space > this->client_->space()) {
APIError err = helper_->loop();
if (err != APIError::OK) {
on_fatal_error();
ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
return false;
}
if (!this->helper_->can_write_without_blocking()) {
// SubscribeLogsResponse
if (message_type != 29) {
ESP_LOGV(TAG, "Cannot send message because of TCP buffer space");
@@ -722,24 +793,31 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type)
}
}
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;
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();
if (err == APIError::SOCKET_WRITE_FAILED && errno == ECONNRESET) {
ESP_LOGW(TAG, "%s: Connection reset", client_info_.c_str());
} else {
ESP_LOGW(TAG, "%s: Packet write failed %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
}
return false;
}
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;
}
+26 -25
View File
@@ -5,16 +5,17 @@
#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 loop();
bool send_list_info_done() {
@@ -62,6 +63,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) {
@@ -76,10 +87,7 @@ class APIConnection : public APIServerConnection {
}
#endif
void on_disconnect_response(const DisconnectResponse &value) override {
// we initiated disconnect_client
this->next_close_ = true;
}
void on_disconnect_response(const DisconnectResponse &value) override;
void on_ping_response(const PingResponse &value) override {
// we initiated ping
this->sent_ping_ = false;
@@ -90,12 +98,7 @@ class APIConnection : public APIServerConnection {
#endif
HelloResponse hello(const HelloRequest &msg) override;
ConnectResponse connect(const ConnectRequest &msg) override;
DisconnectResponse disconnect(const DisconnectRequest &msg) override {
// remote initiated disconnect_client
this->next_close_ = true;
DisconnectResponse resp;
return resp;
}
DisconnectResponse disconnect(const DisconnectRequest &msg) override;
PingResponse ping(const PingRequest &msg) override { return {}; }
DeviceInfoResponse device_info(const DeviceInfoRequest &msg) override;
void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); }
@@ -125,19 +128,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 +147,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,12 +162,11 @@ 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_;
int state_subs_at_ = -1;
};
} // namespace api
+982
View File
@@ -0,0 +1,982 @@
#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;
}
const char *api_error_to_str(APIError err) {
// not using switch to ensure compiler doesn't try to build a big table out of it
if (err == APIError::OK) {
return "OK";
} else if (err == APIError::WOULD_BLOCK) {
return "WOULD_BLOCK";
} else if (err == APIError::BAD_HANDSHAKE_PACKET_LEN) {
return "BAD_HANDSHAKE_PACKET_LEN";
} else if (err == APIError::BAD_INDICATOR) {
return "BAD_INDICATOR";
} else if (err == APIError::BAD_DATA_PACKET) {
return "BAD_DATA_PACKET";
} else if (err == APIError::TCP_NODELAY_FAILED) {
return "TCP_NODELAY_FAILED";
} else if (err == APIError::TCP_NONBLOCKING_FAILED) {
return "TCP_NONBLOCKING_FAILED";
} else if (err == APIError::CLOSE_FAILED) {
return "CLOSE_FAILED";
} else if (err == APIError::SHUTDOWN_FAILED) {
return "SHUTDOWN_FAILED";
} else if (err == APIError::BAD_STATE) {
return "BAD_STATE";
} else if (err == APIError::BAD_ARG) {
return "BAD_ARG";
} else if (err == APIError::SOCKET_READ_FAILED) {
return "SOCKET_READ_FAILED";
} else if (err == APIError::SOCKET_WRITE_FAILED) {
return "SOCKET_WRITE_FAILED";
} else if (err == APIError::HANDSHAKESTATE_READ_FAILED) {
return "HANDSHAKESTATE_READ_FAILED";
} else if (err == APIError::HANDSHAKESTATE_WRITE_FAILED) {
return "HANDSHAKESTATE_WRITE_FAILED";
} else if (err == APIError::HANDSHAKESTATE_BAD_STATE) {
return "HANDSHAKESTATE_BAD_STATE";
} else if (err == APIError::CIPHERSTATE_DECRYPT_FAILED) {
return "CIPHERSTATE_DECRYPT_FAILED";
} else if (err == APIError::CIPHERSTATE_ENCRYPT_FAILED) {
return "CIPHERSTATE_ENCRYPT_FAILED";
} else if (err == APIError::OUT_OF_MEMORY) {
return "OUT_OF_MEMORY";
} else if (err == APIError::HANDSHAKESTATE_SETUP_FAILED) {
return "HANDSHAKESTATE_SETUP_FAILED";
} else if (err == APIError::HANDSHAKESTATE_SPLIT_FAILED) {
return "HANDSHAKESTATE_SPLIT_FAILED";
} else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) {
return "BAD_HANDSHAKE_ERROR_BYTE";
}
return "UNKNOWN";
}
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, info_.c_str(), ##__VA_ARGS__)
// uncomment to log raw packets
//#define HELPER_LOG_PACKETS
#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;
}
// 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 bytes
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
#ifdef HELPER_LOG_PACKETS
ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str());
#endif
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::BAD_INDICATOR) {
send_explicit_handshake_reject_("Bad indicator byte");
return aerr;
}
if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
send_explicit_handshake_reject_("Bad handshake packet len");
return aerr;
}
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 (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
send_explicit_handshake_reject_("Bad handshake packet len");
return aerr;
}
if (aerr != APIError::OK)
return aerr;
if (frame.msg.empty()) {
send_explicit_handshake_reject_("Empty handshake message");
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
} else if (frame.msg[0] != 0x00) {
HELPER_LOG("Bad handshake error byte: %u", frame.msg[0]);
send_explicit_handshake_reject_("Bad handshake error byte");
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
}
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) {
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.resize(reason.length() + 1);
data[0] = 0x01; // failure
for (size_t i = 0; i < reason.length(); i++) {
data[i + 1] = (uint8_t) reason[i];
}
// temporarily remove failed state
auto orig_state = state_;
state_ = State::EXPLICIT_REJECT;
write_frame_(data.data(), data.size());
state_ = orig_state;
}
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;
struct iovec iov;
iov.iov_base = &tmpbuf[0];
iov.iov_len = total_len;
// write raw to not have two packets sent if NAGLE disabled
return write_raw_(&iov, 1);
}
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 struct iovec *iov, int iovcnt) {
if (iovcnt == 0)
return APIError::OK;
int err;
APIError aerr;
size_t total_write_len = 0;
for (int i = 0; i < iovcnt; i++) {
#ifdef HELPER_LOG_PACKETS
ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
#endif
total_write_len += iov[i].iov_len;
}
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
for (int i = 0; i < iovcnt; i++) {
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
}
return APIError::OK;
}
ssize_t sent = socket_->writev(iov, iovcnt);
if (is_would_block(sent)) {
// operation would block, add buffer to tx_buf
for (int i = 0; i < iovcnt; i++) {
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_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 != total_write_len) {
// partially sent, add end to tx_buf
size_t to_consume = sent;
for (int i = 0; i < iovcnt; i++) {
if (to_consume >= iov[i].iov_len) {
to_consume -= iov[i].iov_len;
} else {
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume,
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
to_consume = 0;
}
}
return APIError::OK;
}
// fully sent
return APIError::OK;
}
APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) {
uint8_t header[3];
header[0] = 0x01; // indicator
header[1] = (uint8_t)(len >> 8);
header[2] = (uint8_t) len;
struct iovec iov[2];
iov[0].iov_base = header;
iov[0].iov_len = 3;
iov[1].iov_base = const_cast<uint8_t *>(data);
iov[1].iov_len = len;
return write_raw_(iov, 2);
}
/** 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;
}
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;
uint32_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
#ifdef HELPER_LOG_PACKETS
ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str());
#endif
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);
struct iovec iov[2];
iov[0].iov_base = &header[0];
iov[0].iov_len = header.size();
iov[1].iov_base = const_cast<uint8_t *>(payload);
iov[1].iov_len = payload_len;
return write_raw_(iov, 2);
}
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 (is_would_block(sent)) {
break;
} else if (sent == -1) {
state_ = State::FAILED;
HELPER_LOG("Socket write failed with errno %d", errno);
return APIError::SOCKET_WRITE_FAILED;
}
// 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 struct iovec *iov, int iovcnt) {
if (iovcnt == 0)
return APIError::OK;
int err;
APIError aerr;
size_t total_write_len = 0;
for (int i = 0; i < iovcnt; i++) {
#ifdef HELPER_LOG_PACKETS
ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
#endif
total_write_len += iov[i].iov_len;
}
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
for (int i = 0; i < iovcnt; i++) {
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
}
return APIError::OK;
}
ssize_t sent = socket_->writev(iov, iovcnt);
if (is_would_block(sent)) {
// operation would block, add buffer to tx_buf
for (int i = 0; i < iovcnt; i++) {
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_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 != total_write_len) {
// partially sent, add end to tx_buf
size_t to_consume = sent;
for (int i = 0; i < iovcnt; i++) {
if (to_consume >= iov[i].iov_len) {
to_consume -= iov[i].iov_len;
} else {
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume,
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
to_consume = 0;
}
}
return APIError::OK;
}
// fully sent
return APIError::OK;
}
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
+183
View File
@@ -0,0 +1,183 @@
#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,
BAD_HANDSHAKE_ERROR_BYTE = 1021,
};
const char *api_error_to_str(APIError err);
class APIFrameHelper {
public:
virtual ~APIFrameHelper() = default;
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 *payload, 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 struct iovec *iov, int iovcnt);
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,
EXPLICIT_REJECT = 8,
} 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 *payload, 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_raw_(const struct iovec *iov, int iovcnt);
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
@@ -0,0 +1,23 @@
#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
File diff suppressed because it is too large Load Diff
+259 -19
View File
@@ -32,18 +32,37 @@ 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,
STATE_CLASS_TOTAL_INCREASING = 2,
};
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,
LOG_LEVEL_WARN = 2,
LOG_LEVEL_INFO = 3,
LOG_LEVEL_DEBUG = 4,
LOG_LEVEL_VERBOSE = 5,
LOG_LEVEL_VERY_VERBOSE = 6,
LOG_LEVEL_CONFIG = 4,
LOG_LEVEL_DEBUG = 5,
LOG_LEVEL_VERBOSE = 6,
LOG_LEVEL_VERY_VERBOSE = 7,
};
enum ServiceArgType : uint32_t {
SERVICE_ARG_TYPE_BOOL = 0,
@@ -90,13 +109,14 @@ enum ClimateAction : uint32_t {
CLIMATE_ACTION_FAN = 6,
};
enum ClimatePreset : uint32_t {
CLIMATE_PRESET_ECO = 0,
CLIMATE_PRESET_AWAY = 1,
CLIMATE_PRESET_BOOST = 2,
CLIMATE_PRESET_COMFORT = 3,
CLIMATE_PRESET_HOME = 4,
CLIMATE_PRESET_SLEEP = 5,
CLIMATE_PRESET_ACTIVITY = 6,
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
@@ -105,7 +125,9 @@ class HelloRequest : public ProtoMessage {
public:
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;
@@ -116,7 +138,9 @@ class HelloResponse : public ProtoMessage {
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;
@@ -126,7 +150,9 @@ class ConnectRequest : public ProtoMessage {
public:
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;
@@ -135,7 +161,9 @@ class ConnectResponse : public ProtoMessage {
public:
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;
@@ -143,35 +171,45 @@ 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:
};
@@ -187,7 +225,9 @@ class DeviceInfoResponse : public ProtoMessage {
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;
@@ -196,21 +236,27 @@ 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:
};
@@ -222,8 +268,11 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage {
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;
@@ -236,7 +285,9 @@ class BinarySensorStateResponse : public ProtoMessage {
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;
@@ -252,8 +303,11 @@ class ListEntitiesCoverResponse : public ProtoMessage {
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;
@@ -268,7 +322,9 @@ class CoverStateResponse : public ProtoMessage {
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;
@@ -285,7 +341,9 @@ class CoverCommandRequest : public ProtoMessage {
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;
@@ -301,8 +359,11 @@ class ListEntitiesFanResponse : public ProtoMessage {
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;
@@ -318,7 +379,9 @@ class FanStateResponse : public ProtoMessage {
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;
@@ -338,7 +401,9 @@ class FanCommandRequest : public ProtoMessage {
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;
@@ -350,15 +415,19 @@ class ListEntitiesLightResponse : public ProtoMessage {
uint32_t key{0};
std::string name{};
std::string unique_id{};
bool supports_brightness{false};
bool supports_rgb{false};
bool supports_white_value{false};
bool supports_color_temperature{false};
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;
@@ -370,14 +439,20 @@ class LightStateResponse : public ProtoMessage {
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;
@@ -391,6 +466,10 @@ class LightCommandRequest : public ProtoMessage {
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};
@@ -399,6 +478,10 @@ class LightCommandRequest : public ProtoMessage {
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};
@@ -406,7 +489,9 @@ class LightCommandRequest : public ProtoMessage {
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;
@@ -425,8 +510,12 @@ class ListEntitiesSensorResponse : public ProtoMessage {
bool force_update{false};
std::string device_class{};
enums::SensorStateClass state_class{};
enums::SensorLastResetType legacy_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;
@@ -439,7 +528,9 @@ class SensorStateResponse : public ProtoMessage {
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;
@@ -453,8 +544,11 @@ class ListEntitiesSwitchResponse : public ProtoMessage {
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;
@@ -466,7 +560,9 @@ class SwitchStateResponse : public ProtoMessage {
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;
@@ -477,7 +573,9 @@ class SwitchCommandRequest : public ProtoMessage {
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;
@@ -490,12 +588,16 @@ class ListEntitiesTextSensorResponse : public ProtoMessage {
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:
@@ -503,7 +605,9 @@ class TextSensorStateResponse : public ProtoMessage {
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;
@@ -515,7 +619,9 @@ class SubscribeLogsRequest : public ProtoMessage {
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;
@@ -523,11 +629,12 @@ class SubscribeLogsRequest : public ProtoMessage {
class SubscribeLogsResponse : public ProtoMessage {
public:
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;
@@ -536,7 +643,9 @@ 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:
};
@@ -545,7 +654,9 @@ class HomeassistantServiceMap : public ProtoMessage {
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;
@@ -558,7 +669,9 @@ class HomeassistantServiceResponse : public ProtoMessage {
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;
@@ -567,7 +680,9 @@ 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:
};
@@ -576,7 +691,9 @@ class SubscribeHomeAssistantStateResponse : public ProtoMessage {
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;
@@ -587,7 +704,9 @@ class HomeAssistantStateResponse : public ProtoMessage {
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;
@@ -595,7 +714,9 @@ 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:
};
@@ -603,7 +724,9 @@ class GetTimeResponse : public ProtoMessage {
public:
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;
@@ -613,7 +736,9 @@ class ListEntitiesServicesArgument : public ProtoMessage {
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;
@@ -625,7 +750,9 @@ class ListEntitiesServicesResponse : public ProtoMessage {
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;
@@ -643,7 +770,9 @@ class ExecuteServiceArgument : public ProtoMessage {
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;
@@ -655,7 +784,9 @@ class ExecuteServiceRequest : public ProtoMessage {
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;
@@ -667,12 +798,16 @@ class ListEntitiesCameraResponse : public ProtoMessage {
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:
@@ -680,7 +815,9 @@ class CameraImageResponse : public ProtoMessage {
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;
@@ -692,7 +829,9 @@ class CameraImageRequest : public ProtoMessage {
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;
@@ -709,15 +848,18 @@ class ListEntitiesClimateResponse : public ProtoMessage {
float visual_min_temperature{0.0f};
float visual_max_temperature{0.0f};
float visual_temperature_step{0.0f};
bool supports_away{false};
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;
@@ -732,7 +874,7 @@ class ClimateStateResponse : public ProtoMessage {
float target_temperature{0.0f};
float target_temperature_low{0.0f};
float target_temperature_high{0.0f};
bool away{false};
bool legacy_away{false};
enums::ClimateAction action{};
enums::ClimateFanMode fan_mode{};
enums::ClimateSwingMode swing_mode{};
@@ -740,7 +882,9 @@ class ClimateStateResponse : public ProtoMessage {
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;
@@ -758,8 +902,8 @@ class ClimateCommandRequest : public ProtoMessage {
float target_temperature_low{0.0f};
bool has_target_temperature_high{false};
float target_temperature_high{0.0f};
bool has_away{false};
bool away{false};
bool has_legacy_away{false};
bool legacy_away{false};
bool has_fan_mode{false};
enums::ClimateFanMode fan_mode{};
bool has_swing_mode{false};
@@ -771,13 +915,109 @@ class ClimateCommandRequest : public ProtoMessage {
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
+191 -1
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
+30
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
+77 -19
View File
@@ -1,10 +1,11 @@
#include "api_server.h"
#include "api_connection.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include "esphome/core/util.h"
#include "esphome/core/defines.h"
#include "esphome/core/log.h"
#include "esphome/core/util.h"
#include "esphome/core/version.h"
#include <cerrno>
#ifdef USE_LOGGER
#include "esphome/components/logger/logger.h"
@@ -15,26 +16,51 @@
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_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = ESPHOME_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,12 +85,26 @@ 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_; });
// print disconnection messages
for (auto it = new_end; it != this->clients_.end(); ++it) {
ESP_LOGD(TAG, "Disconnecting %s", (*it)->client_info_.c_str());
ESP_LOGV(TAG, "Removing connection to %s", (*it)->client_info_.c_str());
}
// only then delete the pointers, otherwise log routine
// would access freed memory
@@ -180,7 +220,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 +237,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) {
+21 -11
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,12 @@ 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 +56,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); }
@@ -81,7 +87,7 @@ class APIServer : public Component, public Controller {
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};
@@ -89,9 +95,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:
+73
View File
@@ -0,0 +1,73 @@
import asyncio
import logging
from datetime import datetime
from typing import Optional
from aioesphomeapi import APIClient, ReconnectLogic, APIConnectionError, LogLevel
import zeroconf
from esphome.const import CONF_KEY, CONF_PORT, CONF_PASSWORD, __version__
from esphome.util import safe_print
from . import CONF_ENCRYPTION
_LOGGER = logging.getLogger(__name__)
async def async_run_logs(config, address):
conf = config["api"]
port: int = int(conf[CONF_PORT])
password: str = conf[CONF_PASSWORD]
noise_psk: Optional[str] = None
if CONF_ENCRYPTION in conf:
noise_psk = conf[CONF_ENCRYPTION][CONF_KEY]
_LOGGER.info("Starting log output from %s using esphome API", address)
zc = zeroconf.Zeroconf()
cli = APIClient(
asyncio.get_event_loop(),
address,
port,
password,
client_info=f"ESPHome Logs {__version__}",
noise_psk=noise_psk,
)
first_connect = True
def on_log(msg):
time_ = datetime.now().time().strftime("[%H:%M:%S]")
text = msg.message.decode("utf8", "backslashreplace")
safe_print(time_ + text)
async def on_connect():
nonlocal first_connect
try:
await cli.subscribe_logs(
on_log,
log_level=LogLevel.LOG_LEVEL_VERY_VERBOSE,
dump_config=first_connect,
)
first_connect = False
except APIConnectionError:
cli.disconnect()
async def on_disconnect():
_LOGGER.warning("Disconnected from API")
zc = zeroconf.Zeroconf()
reconnect = ReconnectLogic(
client=cli,
on_connect=on_connect,
on_disconnect=on_disconnect,
zeroconf_instance=zc,
)
await reconnect.start()
try:
while True:
await asyncio.sleep(60)
except KeyboardInterrupt:
await reconnect.stop()
zc.close()
def run_logs(config, address):
asyncio.run(async_run_logs(config, address))
+8
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
+6
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;
+3 -1
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
+7
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; }
@@ -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) {}
+6
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_;
+1 -1
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) {
+4 -2
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_);
}
+30
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()) {
+12
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};
+1 -1
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...");
+7 -8
View File
@@ -4,10 +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,
)
@@ -19,14 +17,15 @@ 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,
STATE_CLASS_NONE,
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, STATE_CLASS_NONE
icon=ICON_FLASH,
accuracy_decimals=1,
state_class=STATE_CLASS_NONE,
),
}
).extend(cv.COMPONENT_SCHEMA)
+1 -1
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;
+1 -1
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!");
@@ -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");
+16 -17
View File
@@ -12,7 +12,6 @@ from esphome.const import (
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
ICON_EMPTY,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
@@ -34,28 +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,
STATE_CLASS_MEASUREMENT,
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,
STATE_CLASS_MEASUREMENT,
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,
STATE_CLASS_MEASUREMENT,
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, STATE_CLASS_MEASUREMENT
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
+1 -1
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) {
+1 -1
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
+36 -32
View File
@@ -12,21 +12,19 @@ from esphome.const import (
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,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
UNIT_HERTZ,
UNIT_VOLT,
UNIT_AMPERE,
UNIT_WATT,
UNIT_EMPTY,
UNIT_CELSIUS,
UNIT_VOLT_AMPS_REACTIVE,
UNIT_WATT_HOURS,
@@ -64,37 +62,45 @@ ATM90E32Component = atm90e32_ns.class_(
ATM90E32_PHASE_SCHEMA = cv.Schema(
{
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
UNIT_VOLT,
ICON_EMPTY,
2,
DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT,
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, STATE_CLASS_MEASUREMENT
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, STATE_CLASS_MEASUREMENT
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,
STATE_CLASS_MEASUREMENT,
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,
STATE_CLASS_MEASUREMENT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_POWER_FACTOR,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_FORWARD_ACTIVE_ENERGY): sensor.sensor_schema(
UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_MEASUREMENT
unit_of_measurement=UNIT_WATT_HOURS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional(CONF_REVERSE_ACTIVE_ENERGY): sensor.sensor_schema(
UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_MEASUREMENT
unit_of_measurement=UNIT_WATT_HOURS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t,
cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t,
@@ -109,18 +115,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,
STATE_CLASS_MEASUREMENT,
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,
STATE_CLASS_MEASUREMENT,
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(

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